From 880999b03aa1b70d73853a7fc0df46c20f0f74f7 Mon Sep 17 00:00:00 2001 From: Kevin_Ramirez Date: Thu, 4 Jun 2026 10:55:17 -0500 Subject: [PATCH] feat: plantilla base workspace SaaS MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Convierte el repositorio de Anexo76 en una plantilla limpia y reutilizable para nuevos proyectos del ecosistema Workspace de Aduanasoft. Cambios principales: - Elimina módulos específicos de Anexo76: a76, a24, sitar, public - Agrega módulo example/ con patrón CRUD de referencia (models/dto/service/routes) - Limpia migraciones Alembic: solo quedan las 6 de core (users, tenants, permissions) - Reemplaza todas las rutas del dashboard con stubs genéricos - Elimina lógica de negocio aduanera: shortcuts, CSV imports, permisos, catálogos - Simplifica variables de entorno: una sola WORKSPACE_URL deriva Hub y Keycloak - Agrega scripts/auth-mode.sh para alternar entre auth local y workspace - Configura docker-compose con nombres genéricos (app-*) - Corrige flujo SSO: elimina system-gate SCAF/SCAII que bloqueaba el login - Modo DEV_LOCAL_AUTH para desarrollo sin Keycloak ni Hub Co-Authored-By: Claude Sonnet 4.6 --- .env.example | 6 +- backend/.env.example | 10 +- backend/alembic/env.py | 2 +- .../8c9bad3da37f_seed_initial_data.py | 464 +- ..._add_system_column_to_classes_and_parts.py | 36 - ..._split_rfc_tax_id_clients_and_providers.py | 79 - ...orkspace_profile_fields_to_user_tenants.py | 4 +- ...b3_add_stock_unit_of_measure_to_classes.py | 43 - ...c4e8b2a_add_transportation_id_sequences.py | 67 - ...a5b6c7_seed_document_types_digitization.py | 57 - ...5d6_seed_sectors_for_existing_companies.py | 63 - .../e4f5a6b7c8d9_add_incoterm_daf_legacy.py | 30 - .../f1a2b3c4d5e6_isolate_equivalency_items.py | 75 - .../versions/g2h3i4j5k6l7_add_invite_codes.py | 4 +- backend/api/v1/common/base_models.py | 14 +- .../modules/a24/balance_movements/models.py | 352 - .../api/v1/modules/a24/discharges/models.py | 369 - .../api/v1/modules/a24/fa/fa_classes/dto.py | 107 - .../v1/modules/a24/fa/fa_classes/models.py | 49 - .../v1/modules/a24/fa/fa_classes/routes.py | 32 - .../v1/modules/a24/fa/fa_classes/service.py | 215 - .../modules/a24/fa/fa_item_lines/__init__.py | 17 - .../v1/modules/a24/fa/fa_item_lines/dto.py | 165 - .../v1/modules/a24/fa/fa_item_lines/models.py | 81 - .../v1/modules/a24/fa/fa_item_lines/routes.py | 35 - .../modules/a24/fa/fa_item_lines/service.py | 258 - .../api/v1/modules/a24/fa/fa_parts/models.py | 48 - backend/api/v1/modules/a24/inv/bom/models.py | 56 - .../v1/modules/a24/inv/inv_aphis/__init__.py | 0 .../api/v1/modules/a24/inv/inv_aphis/dto.py | 17 - .../inv_aphis/inv_aphis_catalog/__init__.py | 0 .../inv/inv_aphis/inv_aphis_catalog/dto.py | 57 - .../inv/inv_aphis/inv_aphis_catalog/models.py | 61 - .../inv/inv_aphis/inv_aphis_catalog/router.py | 62 - .../inv_aphis_characteristic/__init__.py | 0 .../inv_aphis/inv_aphis_characteristic/dto.py | 17 - .../inv_aphis_characteristic/models.py | 35 - .../inv_aphis_containers/__init__.py | 0 .../inv/inv_aphis/inv_aphis_containers/dto.py | 12 - .../inv_aphis/inv_aphis_containers/models.py | 30 - .../inv_aphis/inv_aphis_entities/__init__.py | 0 .../inv/inv_aphis/inv_aphis_entities/dto.py | 13 - .../inv_aphis/inv_aphis_entities/models.py | 31 - .../inv_aphis/inv_aphis_general/__init__.py | 0 .../inv/inv_aphis/inv_aphis_general/dto.py | 64 - .../inv/inv_aphis/inv_aphis_general/models.py | 72 - .../inv/inv_aphis/inv_aphis_lpcos/__init__.py | 0 .../a24/inv/inv_aphis/inv_aphis_lpcos/dto.py | 27 - .../inv/inv_aphis/inv_aphis_lpcos/models.py | 38 - .../inv_aphis/inv_aphis_routing/__init__.py | 0 .../inv/inv_aphis/inv_aphis_routing/dto.py | 12 - .../inv/inv_aphis/inv_aphis_routing/models.py | 30 - .../inv_aphis_stype_pitems/__init__.py | 0 .../inv_aphis/inv_aphis_stype_pitems/dto.py | 23 - .../inv_aphis_stype_pitems/models.py | 34 - .../v1/modules/a24/inv/inv_aphis/models.py | 17 - .../v1/modules/a24/inv/inv_classes/models.py | 20 - .../v1/modules/a24/inv/inv_parts/models.py | 174 - .../a24/inv/part_countries/__init__.py | 0 .../v1/modules/a24/inv/part_countries/dto.py | 81 - .../modules/a24/inv/part_countries/models.py | 73 - .../modules/a24/inv/part_countries/routes.py | 162 - .../modules/a24/inv/part_countries/service.py | 186 - backend/api/v1/modules/a24/router.py | 29 - backend/api/v1/modules/a76/__init__.py | 0 .../v1/modules/a76/app_settings/__init__.py | 1 - .../api/v1/modules/a76/app_settings/models.py | 33 - .../api/v1/modules/a76/app_settings/routes.py | 73 - .../v1/modules/a76/app_settings/schemas.py | 1042 - .../v1/modules/a76/app_settings/service.py | 153 - .../api/v1/modules/a76/audit_log/events.py | 242 - .../v1/modules/a76/audit_log/middleware.py | 27 - .../api/v1/modules/a76/audit_log/models.py | 51 - .../api/v1/modules/a76/audit_log/register.py | 169 - .../api/v1/modules/a76/audit_log/router.py | 462 - .../api/v1/modules/a76/audit_log/schemas.py | 78 - .../v1/modules/a76/audit_log/services/core.py | 209 - .../modules/a76/audit_log/services/service.py | 261 - .../modules/a76/audit_log/utils/__init__.py | 7 - .../a76/audit_log/utils/serialization.py | 50 - backend/api/v1/modules/a76/boms/__init__.py | 7 - backend/api/v1/modules/a76/boms/routes.py | 13 - .../api/v1/modules/a76/classes/__init__.py | 21 - backend/api/v1/modules/a76/classes/dto.py | 276 - backend/api/v1/modules/a76/classes/models.py | 124 - backend/api/v1/modules/a76/classes/routes.py | 151 - backend/api/v1/modules/a76/classes/service.py | 873 - .../v1/modules/a76/classes/test_classes.py | 36 - .../a76/clients_and_providers/__init__.py | 7 - .../modules/a76/clients_and_providers/dto.py | 280 - .../a76/clients_and_providers/models.py | 172 - .../a76/clients_and_providers/routes.py | 188 - .../a76/clients_and_providers/service.py | 505 - .../a76/clients_and_providers/validators.py | 27 - backend/api/v1/modules/a76/common/sorting.py | 61 - .../v1/modules/a76/csv_templates/__init__.py | 1 - .../v1/modules/a76/csv_templates/registry.py | 423 - .../v1/modules/a76/csv_templates/routes.py | 43 - .../api/v1/modules/a76/customs_brokers/dto.py | 124 - .../v1/modules/a76/customs_brokers/models.py | 94 - .../v1/modules/a76/customs_brokers/routes.py | 403 - .../modules/a76/customs_brokers/services.py | 257 - .../v1/modules/a76/doc_types_dig/__init__.py | 1 - .../api/v1/modules/a76/doc_types_dig/dto.py | 30 - .../v1/modules/a76/doc_types_dig/models.py | 30 - .../v1/modules/a76/doc_types_dig/routes.py | 95 - .../api/v1/modules/a76/doc_types_dig/seed.py | 40 - .../v1/modules/a76/doc_types_dig/service.py | 181 - .../a76/expediente_archivos/__init__.py | 0 .../v1/modules/a76/expediente_archivos/dto.py | 138 - .../expediente_archivos/external_service.py | 62 - .../modules/a76/expediente_archivos/models.py | 39 - .../modules/a76/expediente_archivos/routes.py | 399 - .../a76/expediente_archivos/service.py | 560 - .../modules/a76/expediente_archivos/tasks.py | 569 - .../a76/factura_cove/external_service.py | 213 - .../a76/factura_cove/report_service.py | 780 - .../api/v1/modules/a76/factura_cove/routes.py | 247 - .../v1/modules/a76/factura_cove/schemas.py | 136 - .../v1/modules/a76/factura_cove/service.py | 899 - .../api/v1/modules/a76/factura_cove/tasks.py | 407 - .../a76/factura_cove/templates/FooterCOVE.png | Bin 8903 -> 0 bytes .../a76/factura_cove/templates/HeaderCOVE.png | Bin 18294 -> 0 bytes .../factura_cove/templates/acuse_cove.html | 518 - .../classification_concepts/__init__.py | 0 .../classification_concepts/dto.py | 23 - .../classification_concepts/models.py | 22 - .../classification_concepts/routes.py | 20 - .../classification_concepts/service.py | 100 - .../a76/general_catalogs/company/__init__.py | 26 - .../a76/general_catalogs/company/dto.py | 489 - .../a76/general_catalogs/company/models.py | 153 - .../a76/general_catalogs/company/routes.py | 567 - .../a76/general_catalogs/company/service.py | 876 - .../company/submodels/__init__.py | 20 - .../company/submodels/address.py | 46 - .../company/submodels/certification.py | 55 - .../company/submodels/cfdi.py | 37 - .../company/submodels/digital_certificate.py | 44 - .../company/submodels/electronic_agent.py | 46 - .../company/submodels/prevalidator.py | 38 - .../general_catalogs/company/submodels/vu.py | 50 - .../general_catalogs/company/test_company.py | 36 - .../a76/general_catalogs/concepts/__init__.py | 0 .../a76/general_catalogs/concepts/dto.py | 46 - .../a76/general_catalogs/concepts/models.py | 58 - .../a76/general_catalogs/concepts/routes.py | 20 - .../a76/general_catalogs/concepts/service.py | 107 - .../customs_broker_concepts/__init__.py | 0 .../customs_broker_concepts/dto.py | 40 - .../customs_broker_concepts/models.py | 31 - .../customs_broker_concepts/routes.py | 31 - .../customs_broker_concepts/service.py | 132 - .../depreciation_catalog/__init__.py | 3 - .../depreciation_catalog/dto.py | 38 - .../depreciation_catalog/models.py | 25 - .../depreciation_catalog/routes.py | 73 - .../depreciation_catalog/seed.py | 34 - .../depreciation_catalog/service.py | 91 - .../a76/general_catalogs/doda/__init__.py | 3 - .../a76/general_catalogs/doda/alta_log_dto.py | 55 - .../general_catalogs/doda/alta_log_models.py | 39 - .../general_catalogs/doda/alta_log_service.py | 142 - .../a76/general_catalogs/doda/alta_service.py | 643 - .../modules/a76/general_catalogs/doda/dto.py | 437 - .../general_catalogs/doda/export_service.py | 318 - .../general_catalogs/doda/external_service.py | 107 - .../a76/general_catalogs/doda/fingerprint.py | 132 - .../a76/general_catalogs/doda/models.py | 269 - .../doda/payload_normalizer.py | 128 - .../a76/general_catalogs/doda/print_cache.py | 75 - .../general_catalogs/doda/report_service.py | 205 - .../a76/general_catalogs/doda/routes.py | 1132 - .../a76/general_catalogs/doda/service.py | 726 - .../doda/templates/doda_report.html | 160 - .../electronic_notices/__init__.py | 3 - .../electronic_notices/dto.py | 85 - .../electronic_notices/models.py | 48 - .../electronic_notices/routes.py | 72 - .../electronic_notices/service.py | 190 - .../equivalencies/__init__.py | 0 .../a76/general_catalogs/equivalencies/dto.py | 60 - .../general_catalogs/equivalencies/models.py | 68 - .../general_catalogs/equivalencies/routes.py | 47 - .../general_catalogs/equivalencies/service.py | 240 - .../error_catalogs/__init__.py | 3 - .../general_catalogs/error_catalogs/dto.py | 105 - .../general_catalogs/error_catalogs/models.py | 82 - .../general_catalogs/error_catalogs/routes.py | 203 - .../error_catalogs/service.py | 354 - .../a76/general_catalogs/exchange_rate/dto.py | 31 - .../general_catalogs/exchange_rate/models.py | 38 - .../general_catalogs/exchange_rate/routes.py | 127 - .../exchange_rate/services.py | 234 - .../exchange_rate/test_exchange_rate.py | 36 - .../general_catalogs/fda_catalog/__init__.py | 3 - .../a76/general_catalogs/fda_catalog/dto.py | 172 - .../general_catalogs/fda_catalog/models.py | 150 - .../general_catalogs/fda_catalog/routes.py | 278 - .../a76/general_catalogs/fda_catalog/seed.py | 15 - .../general_catalogs/fda_catalog/service.py | 267 - .../canadian_tariff_fractions/__init__.py | 0 .../canadian_tariff_fractions/models.py | 29 - .../canadian_tariff_fractions/routes.py | 108 - .../canadian_tariff_fractions/schemas.py | 32 - .../canadian_tariff_fractions/service.py | 73 - .../historical_tariff_fractions/dto.py | 29 - .../historical_tariff_fractions/models.py | 34 - .../historical_tariff_fractions/routes.py | 136 - .../historical_tariff_fractions/schemas.py | 45 - .../historical_tariff_fractions/seed.py | 6592 ---- .../historical_tariff_fractions/service.py | 177 - .../fractions/previous_fractions/models.py | 24 - .../fractions/previous_fractions/seed.py | 27112 --------------- .../fractions/tariff_fractions/__init__.py | 8 - .../fractions/tariff_fractions/dto.py | 91 - .../fractions/tariff_fractions/models.py | 49 - .../fractions/tariff_fractions/routes.py | 251 - .../fractions/tariff_fractions/seed.py | 8189 ----- .../fractions/tariff_fractions/service.py | 412 - .../fractions/us_tariff_fractions/__init__.py | 8 - .../fractions/us_tariff_fractions/dto.py | 94 - .../fractions/us_tariff_fractions/models.py | 48 - .../fractions/us_tariff_fractions/routes.py | 159 - .../fractions/us_tariff_fractions/seed.py | 417 - .../fractions/us_tariff_fractions/service.py | 211 - .../fractions/warning_fractions/models.py | 47 - .../fractions/warning_fractions/seed.py | 141 - .../general_catalogs/identifiers/__init__.py | 0 .../a76/general_catalogs/identifiers/dto.py | 61 - .../general_catalogs/identifiers/models.py | 62 - .../general_catalogs/identifiers/routes.py | 47 - .../general_catalogs/identifiers/service.py | 200 - .../a76/general_catalogs/inpc/__init__.py | 0 .../modules/a76/general_catalogs/inpc/dto.py | 25 - .../a76/general_catalogs/inpc/models.py | 25 - .../a76/general_catalogs/inpc/routes.py | 23 - .../a76/general_catalogs/inpc/service.py | 97 - .../a76/general_catalogs/legends/__init__.py | 0 .../a76/general_catalogs/legends/dto.py | 25 - .../a76/general_catalogs/legends/models.py | 22 - .../a76/general_catalogs/legends/routes.py | 22 - .../a76/general_catalogs/legends/service.py | 116 - .../a76/general_catalogs/location/__init__.py | 1 - .../a76/general_catalogs/location/dto.py | 36 - .../a76/general_catalogs/location/models.py | 84 - .../a76/general_catalogs/location/routes.py | 25 - .../a76/general_catalogs/location/service.py | 154 - .../general_catalogs/locations/__init__.py | 0 .../multi_currency_types/__init__.py | 0 .../multi_currency_types/dto.py | 33 - .../multi_currency_types/models.py | 35 - .../multi_currency_types/routes.py | 27 - .../multi_currency_types/service.py | 103 - .../a76/general_catalogs/packages/dto.py | 41 - .../a76/general_catalogs/packages/models.py | 34 - .../a76/general_catalogs/packages/routes.py | 30 - .../a76/general_catalogs/packages/services.py | 133 - .../general_catalogs/packages/test_package.py | 36 - .../a76/general_catalogs/ports/__init__.py | 0 .../modules/a76/general_catalogs/ports/dto.py | 32 - .../a76/general_catalogs/ports/models.py | 36 - .../a76/general_catalogs/ports/routes.py | 22 - .../a76/general_catalogs/ports/service.py | 94 - .../prevalidators/__init__.py | 3 - .../a76/general_catalogs/prevalidators/dto.py | 58 - .../general_catalogs/prevalidators/models.py | 40 - .../general_catalogs/prevalidators/routes.py | 148 - .../general_catalogs/prevalidators/service.py | 193 - .../v1/modules/a76/general_catalogs/router.py | 60 - .../modules/a76/general_catalogs/seal/dto.py | 31 - .../a76/general_catalogs/seal/models.py | 22 - .../a76/general_catalogs/seal/routes.py | 29 - .../a76/general_catalogs/seal/services.py | 106 - .../a76/general_catalogs/seal/test_seal.py | 36 - .../a76/general_catalogs/sectors/__init__.py | 0 .../a76/general_catalogs/sectors/dto.py | 30 - .../a76/general_catalogs/sectors/models.py | 23 - .../a76/general_catalogs/sectors/routes.py | 28 - .../a76/general_catalogs/sectors/seed.py | 36 - .../a76/general_catalogs/sectors/service.py | 152 - .../general_catalogs/signatures/__init__.py | 3 - .../a76/general_catalogs/signatures/dto.py | 47 - .../a76/general_catalogs/signatures/models.py | 39 - .../a76/general_catalogs/signatures/routes.py | 209 - .../general_catalogs/signatures/service.py | 135 - .../unit_conversions/__init__.py | 0 .../general_catalogs/unit_conversions/dto.py | 24 - .../unit_conversions/models.py | 39 - .../unit_conversions/routes.py | 21 - .../unit_conversions/service.py | 113 - .../units_of_measure/__init__.py | 0 .../general_catalogs/units_of_measure/dto.py | 134 - .../units_of_measure/models.py | 133 - .../units_of_measure/routes.py | 139 - .../general_catalogs/units_of_measure/seed.py | 49 - .../units_of_measure/seed_ace.py | 58 - .../units_of_measure/seed_adua.py | 24 - .../units_of_measure/seed_ame.py | 48 - .../units_of_measure/seed_oma.py | 1205 - .../units_of_measure/service.py | 169 - .../v1/modules/a76/invoice_settings/dto.py | 22 - .../v1/modules/a76/invoice_settings/models.py | 47 - .../v1/modules/a76/invoice_settings/routes.py | 131 - .../modules/a76/invoice_settings/services.py | 98 - .../modules/a76/invoices/catalog_service.py | 225 - .../a76/invoices/common/calculations.py | 50 - .../a76/invoices/common/common_validators.py | 995 - .../v1/modules/a76/invoices/common/mappers.py | 13 - .../common/process/review_equivalence.py | 81 - .../a76/invoices/common/process/review_uma.py | 128 - .../v1/modules/a76/invoices/docs/README.MD | 151 - .../exports/docs/REPO_exports_casos_de_uso.md | 379 - .../exports/docs/REPO_exports_diagramas.md | 269 - .../invoices/exports/process/main_process.py | 137 - .../exports/process/pre_validators.py | 147 - .../a76/invoices/exports/process/routes.py | 85 - .../exports/process/sub_process/__init__.py | 0 .../sub_process/assign_no_discharges.py | 82 - .../process/sub_process/assign_values.py | 255 - .../process/sub_process/compare_balances.py | 165 - .../process/sub_process/discharge_types.py | 73 - .../sub_process/download_balance_collector.py | 125 - .../sub_process/fill_available_balances.py | 221 - .../process/sub_process/finalize_invoice.py | 234 - .../sub_process/generate_definitive_import.py | 501 - .../sub_process/register_discharge_ledger.py | 244 - .../sub_process/register_discharge_series.py | 181 - .../sub_process/register_import_discharge.py | 171 - .../process/sub_process/review_class.py | 181 - .../sub_process/review_exchange_rate.py | 94 - .../process/sub_process/review_limits.py | 96 - .../sub_process/review_origin_procedure.py | 74 - .../sub_process/review_qty_vs_weight.py | 72 - .../process/sub_process/review_unit_cost.py | 60 - .../process/sub_process/series/__init__.py | 0 .../sub_process/series/review_qty_series.py | 130 - .../sub_process/series/review_series_exist.py | 149 - .../series/review_series_other_lines.py | 210 - .../sub_process/verify_consolidated.py | 82 - .../a76/invoices/exports/process/task.py | 57 - .../invoices/exports/revert/main_process.py | 391 - .../invoices/exports/revert/pre_validators.py | 42 - .../a76/invoices/exports/revert/routes.py | 92 - .../a76/invoices/exports/revert/task.py | 92 - .../a76/invoices/exports/validators/create.py | 85 - .../a76/invoices/exports/validators/update.py | 286 - .../a76/invoices/imports/balance/__init__.py | 0 .../imports/balance/create_balance_entries.py | 120 - .../imports/balance/void_balance_entries.py | 170 - .../imports/docs/REPO_imports_casos_de_uso.md | 315 - .../imports/docs/REPO_imports_diagramas.md | 222 - .../invoices/imports/process/main_process.py | 262 - .../imports/process/pre_validators.py | 149 - .../a76/invoices/imports/process/routes.py | 109 - .../process/sub_process/assing_values.py | 95 - .../sub_process/assing_values_def_mex.py | 159 - .../process/sub_process/review_classes.py | 208 - .../sub_process/review_exchange_rate.py | 69 - .../process/sub_process/review_limits.py | 80 - .../process/sub_process/review_rule_octave.py | 899 - .../process/sub_process/review_series.py | 102 - .../process/sub_process/review_weights.py | 102 - .../a76/invoices/imports/process/task.py | 70 - .../invoices/imports/revert/main_process.py | 229 - .../invoices/imports/revert/pre_validators.py | 42 - .../a76/invoices/imports/revert/routes.py | 116 - .../revert/sub_process/review_rule_octave.py | 94 - .../a76/invoices/imports/revert/task.py | 91 - .../a76/invoices/imports/validators/create.py | 95 - .../a76/invoices/imports/validators/update.py | 261 - backend/api/v1/modules/a76/invoices/models.py | 775 - .../v1/modules/a76/invoices/permission_map.py | 91 - backend/api/v1/modules/a76/invoices/routes.py | 781 - .../api/v1/modules/a76/invoices/schemas.py | 664 - .../api/v1/modules/a76/invoices/services.py | 2547 -- .../api/v1/modules/a76/item_presets/dto.py | 25 - .../api/v1/modules/a76/item_presets/models.py | 18 - .../api/v1/modules/a76/item_presets/routes.py | 72 - .../v1/modules/a76/item_presets/services.py | 63 - backend/api/v1/modules/a76/items/__init__.py | 23 - .../a76/items/common/common_validators.py | 33 - .../v1/modules/a76/items/common/fractions.py | 155 - .../items/exports/validators/calculations.py | 280 - .../a76/items/exports/validators/common.py | 480 - .../a76/items/exports/validators/create.py | 535 - .../a76/items/exports/validators/update.py | 375 - .../items/imports/validators/calculations.py | 169 - .../a76/items/imports/validators/common.py | 406 - .../a76/items/imports/validators/create.py | 417 - .../a76/items/imports/validators/update.py | 252 - .../a76/items/line_customs/__init__.py | 16 - .../modules/a76/items/line_customs/models.py | 62 - .../modules/a76/items/line_customs/schemas.py | 58 - .../a76/items/line_descriptions/__init__.py | 16 - .../a76/items/line_descriptions/models.py | 52 - .../a76/items/line_descriptions/schemas.py | 55 - .../a76/items/line_financials/__init__.py | 16 - .../a76/items/line_financials/models.py | 101 - .../a76/items/line_financials/schemas.py | 104 - .../a76/items/line_quantities/__init__.py | 16 - .../a76/items/line_quantities/models.py | 49 - .../a76/items/line_quantities/schemas.py | 48 - .../a76/items/line_references/__init__.py | 16 - .../a76/items/line_references/models.py | 36 - .../a76/items/line_references/schemas.py | 39 - backend/api/v1/modules/a76/items/models.py | 407 - backend/api/v1/modules/a76/items/routes.py | 299 - backend/api/v1/modules/a76/items/schemas.py | 401 - .../api/v1/modules/a76/items/series/models.py | 29 - .../v1/modules/a76/items/series/schemas.py | 30 - backend/api/v1/modules/a76/items/service.py | 1116 - .../v1/modules/a76/layouts_csv/__init__.py | 1 - .../modules/a76/layouts_csv/boms/__init__.py | 1 - .../a76/layouts_csv/boms/common/__init__.py | 1 - .../boms/common/common_validators.py | 79 - .../a76/layouts_csv/boms/common/fk_loader.py | 27 - .../a76/layouts_csv/boms/common/mappers.py | 49 - .../boms/csv_max_chars_for_headers.py | 11 - .../v1/modules/a76/layouts_csv/boms/routes.py | 178 - .../modules/a76/layouts_csv/boms/schemas.py | 22 - .../v1/modules/a76/layouts_csv/boms/tasks.py | 175 - .../a76/layouts_csv/boms/template_config.py | 61 - .../layouts_csv/boms/validators/__init__.py | 3 - .../a76/layouts_csv/boms/validators/common.py | 54 - .../a76/layouts_csv/boms/validators/create.py | 38 - .../cambio_regimen_regularizacion/__init__.py | 1 - .../cambio_regimen_regularizacion/routes.py | 191 - .../cambio_regimen_regularizacion/schemas.py | 23 - .../cambio_regimen_regularizacion/tasks.py | 145 - .../template_config.py | 146 - .../a76/layouts_csv/classes/__init__.py | 1 - .../layouts_csv/classes/common/__init__.py | 1 - .../classes/common/common_validators.py | 108 - .../layouts_csv/classes/common/fk_loader.py | 75 - .../a76/layouts_csv/classes/common/mappers.py | 82 - .../classes/csv_max_chars_for_headers.py | 16 - .../modules/a76/layouts_csv/classes/routes.py | 260 - .../a76/layouts_csv/classes/schemas.py | 21 - .../modules/a76/layouts_csv/classes/tasks.py | 329 - .../layouts_csv/classes/template_config.py | 198 - .../classes/validators/__init__.py | 3 - .../layouts_csv/classes/validators/common.py | 233 - .../layouts_csv/classes/validators/create.py | 90 - .../clients_and_providers/__init__.py | 2 - .../clients_and_providers/common/__init__.py | 1 - .../common/common_validators.py | 319 - .../common/csv_max_chars_for_headers.py | 42 - .../clients_and_providers/common/mappers.py | 223 - .../clients_and_providers/routes.py | 254 - .../clients_and_providers/schemas.py | 25 - .../clients_and_providers/tasks.py | 385 - .../clients_and_providers/template_config.py | 168 - .../validators/__init__.py | 3 - .../validators/common.py | 190 - .../validators/create.py | 8 - .../a76/layouts_csv/common/__init__.py | 1 - .../a76/layouts_csv/common/cell_value.py | 19 - .../a76/layouts_csv/common/csv_headers_en.py | 408 - .../a76/layouts_csv/common/csv_hint_inject.py | 66 - .../a76/layouts_csv/common/csv_reader.py | 282 - .../a76/layouts_csv/common/error_csv.py | 96 - .../layouts_csv/common/import_redis_keys.py | 69 - .../common/inject_csv_labels_en.py | 30 - .../v1/modules/a76/layouts_csv/common/meta.py | 35 - .../a76/layouts_csv/common/normalize.py | 16 - .../a76/layouts_csv/common/responses.py | 115 - .../modules/a76/layouts_csv/common/storage.py | 281 - .../a76/layouts_csv/common/template_locale.py | 160 - .../common/track_commit_dispatch.py | 75 - .../modules/a76/layouts_csv/common/victor.py | 172 - .../layouts_csv/customs_brokers/__init__.py | 1 - .../customs_brokers/common/__init__.py | 1 - .../common/common_validators.py | 320 - .../customs_brokers/common/fk_loader.py | 26 - .../customs_brokers/common/mappers.py | 77 - .../csv_max_chars_for_headers.py | 23 - .../a76/layouts_csv/customs_brokers/routes.py | 191 - .../layouts_csv/customs_brokers/schemas.py | 22 - .../a76/layouts_csv/customs_brokers/tasks.py | 286 - .../customs_brokers/template_config.py | 117 - .../customs_brokers/validators/__init__.py | 3 - .../customs_brokers/validators/common.py | 137 - .../customs_brokers/validators/create.py | 6 - .../a76/layouts_csv/drivers/__init__.py | 1 - .../layouts_csv/drivers/common/__init__.py | 1 - .../drivers/common/common_validators.py | 262 - .../layouts_csv/drivers/common/fk_loader.py | 58 - .../a76/layouts_csv/drivers/common/mappers.py | 79 - .../drivers/csv_max_chars_for_headers.py | 28 - .../modules/a76/layouts_csv/drivers/routes.py | 185 - .../a76/layouts_csv/drivers/schemas.py | 23 - .../modules/a76/layouts_csv/drivers/tasks.py | 482 - .../layouts_csv/drivers/template_config.py | 75 - .../drivers/validators/__init__.py | 3 - .../layouts_csv/drivers/validators/common.py | 154 - .../layouts_csv/drivers/validators/create.py | 69 - .../a76/layouts_csv/exchange_rate/__init__.py | 1 - .../exchange_rate/common/__init__.py | 1 - .../exchange_rate/common/common_validators.py | 140 - .../exchange_rate/common/mappers.py | 48 - .../csv_max_chars_for_headers.py | 11 - .../a76/layouts_csv/exchange_rate/routes.py | 200 - .../a76/layouts_csv/exchange_rate/schemas.py | 25 - .../a76/layouts_csv/exchange_rate/tasks.py | 269 - .../exchange_rate/template_config.py | 56 - .../exchange_rate/validators/__init__.py | 3 - .../exchange_rate/validators/common.py | 102 - .../exchange_rate/validators/create.py | 6 - .../a76/layouts_csv/exportacion/__init__.py | 1 - .../a76/layouts_csv/exportacion/routes.py | 247 - .../a76/layouts_csv/exportacion/schemas.py | 23 - .../a76/layouts_csv/exportacion/tasks.py | 154 - .../exportacion/template_config.py | 142 - .../a76/layouts_csv/facturas/__init__.py | 0 .../facturas/invoice_csv_column_hints.py | 116 - .../facturas/line_item_enrichment/__init__.py | 12 - .../line_item_enrichment/export_enrichment.py | 104 - .../line_item_enrichment/import_enrichment.py | 247 - .../a76/layouts_csv/facturas/routes.py | 253 - .../a76/layouts_csv/facturas/schemas.py | 20 - .../modules/a76/layouts_csv/facturas/tasks.py | 6665 ---- .../layouts_csv/facturas/template_config.py | 330 - .../facturas/validators/__init__.py | 42 - .../validators/consolidated_invoice_dates.py | 107 - .../facturas/validators/encabezados_cmex.py | 261 - .../facturas/validators/encabezados_expo.py | 469 - .../validators/encabezados_impo_def.py | 381 - .../validators/encabezados_impo_temp.py | 764 - .../facturas/validators/partidas_expo.py | 543 - .../facturas/validators/partidas_impo_def.py | 171 - .../facturas/validators/partidas_impo_temp.py | 552 - .../validators/pedimento_resolution.py | 278 - .../facturas/validators/series_expo.py | 279 - .../facturas/validators/series_impo_def.py | 323 - .../facturas/validators/series_impo_temp.py | 356 - .../facturas/validators/transport_catalog.py | 239 - .../modules/a76/layouts_csv/parts/__init__.py | 1 - .../a76/layouts_csv/parts/common/__init__.py | 1 - .../parts/common/common_validators.py | 49 - .../a76/layouts_csv/parts/common/fk_loader.py | 145 - .../a76/layouts_csv/parts/common/mappers.py | 204 - .../parts/csv_max_chars_for_headers.py | 29 - .../modules/a76/layouts_csv/parts/routes.py | 258 - .../modules/a76/layouts_csv/parts/schemas.py | 22 - .../v1/modules/a76/layouts_csv/parts/tasks.py | 382 - .../a76/layouts_csv/parts/template_config.py | 239 - .../layouts_csv/parts/validators/__init__.py | 4 - .../layouts_csv/parts/validators/common.py | 423 - .../layouts_csv/parts/validators/create.py | 127 - .../a76/layouts_csv/pedmientos/__init__.py | 1 - .../layouts_csv/pedmientos/common/__init__.py | 1 - .../pedmientos/common/common_validators.py | 150 - .../pedmientos/common/fk_loader.py | 167 - .../layouts_csv/pedmientos/common/mappers.py | 190 - .../pedmientos/pedimentos_csv_hints.py | 58 - .../a76/layouts_csv/pedmientos/routes.py | 194 - .../a76/layouts_csv/pedmientos/schemas.py | 25 - .../a76/layouts_csv/pedmientos/tasks.py | 468 - .../layouts_csv/pedmientos/template_config.py | 251 - .../pedmientos/validators/__init__.py | 3 - .../pedmientos/validators/common.py | 521 - .../pedmientos/validators/create.py | 144 - .../a76/layouts_csv/trailers/__init__.py | 1 - .../layouts_csv/trailers/common/__init__.py | 1 - .../trailers/common/common_validators.py | 183 - .../layouts_csv/trailers/common/fk_loader.py | 92 - .../layouts_csv/trailers/common/mappers.py | 94 - .../trailers/csv_max_chars_for_headers.py | 16 - .../a76/layouts_csv/trailers/routes.py | 204 - .../a76/layouts_csv/trailers/schemas.py | 23 - .../modules/a76/layouts_csv/trailers/tasks.py | 409 - .../layouts_csv/trailers/template_config.py | 58 - .../trailers/validators/__init__.py | 3 - .../layouts_csv/trailers/validators/common.py | 131 - .../layouts_csv/trailers/validators/create.py | 73 - .../layouts_csv/transportistas/__init__.py | 1 - .../transportistas/common/__init__.py | 1 - .../common/common_validators.py | 143 - .../transportistas/common/fk_loader.py | 96 - .../transportistas/common/mappers.py | 95 - .../csv_max_chars_for_headers.py | 25 - .../a76/layouts_csv/transportistas/routes.py | 204 - .../a76/layouts_csv/transportistas/schemas.py | 22 - .../a76/layouts_csv/transportistas/tasks.py | 367 - .../transportistas/template_config.py | 69 - .../transportistas/validators/__init__.py | 3 - .../transportistas/validators/common.py | 97 - .../transportistas/validators/create.py | 72 - .../us_tariff_fractions/__init__.py | 1 - .../us_tariff_fractions/common/__init__.py | 1 - .../common/common_validators.py | 83 - .../us_tariff_fractions/common/fk_loader.py | 39 - .../us_tariff_fractions/common/mappers.py | 90 - .../csv_max_chars_for_headers.py | 14 - .../layouts_csv/us_tariff_fractions/routes.py | 247 - .../us_tariff_fractions/schemas.py | 25 - .../layouts_csv/us_tariff_fractions/tasks.py | 281 - .../us_tariff_fractions/template_config.py | 69 - .../validators/__init__.py | 3 - .../us_tariff_fractions/validators/common.py | 180 - .../us_tariff_fractions/validators/create.py | 10 - .../a76/layouts_csv/vehicles/__init__.py | 1 - .../layouts_csv/vehicles/common/__init__.py | 1 - .../vehicles/common/common_validators.py | 248 - .../layouts_csv/vehicles/common/fk_loader.py | 80 - .../layouts_csv/vehicles/common/mappers.py | 117 - .../vehicles/csv_max_chars_for_headers.py | 24 - .../a76/layouts_csv/vehicles/routes.py | 206 - .../a76/layouts_csv/vehicles/schemas.py | 22 - .../modules/a76/layouts_csv/vehicles/tasks.py | 404 - .../layouts_csv/vehicles/template_config.py | 66 - .../vehicles/validators/__init__.py | 3 - .../layouts_csv/vehicles/validators/common.py | 136 - .../layouts_csv/vehicles/validators/create.py | 68 - .../api/v1/modules/a76/manifests/__init__.py | 0 .../concept_manifestation/__init__.py | 0 .../manifests/concept_manifestation/dtos.py | 48 - .../manifests/concept_manifestation/models.py | 45 - .../manifests/concept_manifestation/routes.py | 112 - .../concept_manifestation/service.py | 143 - .../modules/a76/manifests/driver/__init__.py | 0 .../v1/modules/a76/manifests/driver/dtos.py | 42 - .../v1/modules/a76/manifests/driver/models.py | 30 - .../v1/modules/a76/manifests/driver/routes.py | 98 - .../modules/a76/manifests/driver/service.py | 124 - .../a76/manifests/manifest/debug_db.py | 41 - .../v1/modules/a76/manifests/manifest/dtos.py | 66 - .../modules/a76/manifests/manifest/models.py | 52 - .../modules/a76/manifests/manifest/routes.py | 127 - .../modules/a76/manifests/manifest/service.py | 192 - .../manifests/manifiesto_anexo/__init__.py | 0 .../a76/manifests/manifiesto_anexo/dtos.py | 37 - .../a76/manifests/manifiesto_anexo/models.py | 26 - .../a76/manifests/manifiesto_anexo/routes.py | 98 - .../a76/manifests/manifiesto_anexo/service.py | 124 - .../manifests/value_manifestation/__init__.py | 0 .../a76/manifests/value_manifestation/dtos.py | 75 - .../manifests/value_manifestation/models.py | 66 - .../manifests/value_manifestation/service.py | 136 - backend/api/v1/modules/a76/parts/__init__.py | 7 - backend/api/v1/modules/a76/parts/dto.py | 222 - backend/api/v1/modules/a76/parts/models.py | 146 - backend/api/v1/modules/a76/parts/routes.py | 38 - backend/api/v1/modules/a76/parts/service.py | 546 - .../api/v1/modules/a76/parts/test_parts.py | 36 - .../modules/a76/pedmientos/catalog_service.py | 158 - .../dtos/pedimento_config_additional.py | 53 - .../dtos/pedimento_config_calculations.py | 61 - .../dtos/pedimento_config_parameters.py | 66 - .../dtos/pedimento_config_surcharges.py | 43 - .../pedimento_config_update_rectification.py | 37 - .../dtos/pedimento_config_updates.py | 32 - .../dtos/pedimento_contributions.py | 49 - .../dtos/pedimento_customs_offices.py | 39 - .../a76/pedmientos/dtos/pedimento_dates.py | 62 - .../dtos/pedimento_decrementables.py | 54 - .../dtos/pedimento_incrementables.py | 56 - .../a76/pedmientos/dtos/pedimento_indexes.py | 40 - .../dtos/pedimento_packages_transport.py | 178 - .../a76/pedmientos/dtos/pedimento_payments.py | 69 - .../pedimento_rectification_destination.py | 45 - .../dtos/pedimento_rectification_origin.py | 72 - .../dtos/pedimento_transport_means.py | 42 - .../pedmientos/dtos/pedimento_validation.py | 62 - .../modules/a76/pedmientos/dtos/pedimentos.py | 227 - .../modules/a76/pedmientos/models/__init__.py | 84 - .../models/pedimento_config_additional.py | 50 - .../models/pedimento_config_calculations.py | 55 - .../models/pedimento_config_parameters.py | 57 - .../models/pedimento_config_surcharges.py | 50 - .../pedimento_config_update_rectification.py | 49 - .../models/pedimento_config_updates.py | 48 - .../pedmientos/models/pedimento_containers.py | 39 - .../models/pedimento_contributions.py | 46 - .../models/pedimento_customs_offices.py | 45 - .../a76/pedmientos/models/pedimento_dates.py | 60 - .../models/pedimento_decrementables.py | 55 - .../a76/pedmientos/models/pedimento_guides.py | 38 - .../models/pedimento_incrementables.py | 56 - .../pedmientos/models/pedimento_indexes.py | 48 - .../pedmientos/models/pedimento_packages.py | 48 - .../pedmientos/models/pedimento_payments.py | 61 - .../pedimento_rectification_destination.py | 47 - .../models/pedimento_rectification_origin.py | 61 - .../a76/pedmientos/models/pedimento_seals.py | 38 - .../models/pedimento_transport_carriers.py | 47 - .../models/pedimento_transport_means.py | 48 - .../pedmientos/models/pedimento_validation.py | 51 - .../a76/pedmientos/models/pedimentos.py | 265 - .../api/v1/modules/a76/pedmientos/router.py | 125 - .../routes/pedimento_config_additional.py | 30 - .../routes/pedimento_config_calculations.py | 30 - .../routes/pedimento_config_parameters.py | 30 - .../routes/pedimento_config_surcharges.py | 30 - .../pedimento_config_update_rectification.py | 32 - .../routes/pedimento_config_updates.py | 30 - .../routes/pedimento_customs_offices.py | 125 - .../a76/pedmientos/routes/pedimento_dates.py | 30 - .../routes/pedimento_decrementables.py | 127 - .../routes/pedimento_incrementables.py | 127 - .../pedmientos/routes/pedimento_indexes.py | 30 - .../pedmientos/routes/pedimento_payments.py | 125 - .../pedimento_rectification_destination.py | 32 - .../routes/pedimento_rectification_origin.py | 32 - .../routes/pedimento_transport_means.py | 127 - .../pedmientos/routes/pedimento_validation.py | 30 - .../a76/pedmientos/routes/pedimentos.py | 104 - .../api/v1/modules/a76/pedmientos/schemas.py | 46 - .../services/pedimento_config_additional.py | 85 - .../services/pedimento_config_calculations.py | 85 - .../services/pedimento_config_parameters.py | 77 - .../services/pedimento_config_surcharges.py | 77 - .../pedimento_config_update_rectification.py | 79 - .../services/pedimento_config_updates.py | 77 - .../services/pedimento_customs_offices.py | 77 - .../pedmientos/services/pedimento_dates.py | 68 - .../services/pedimento_decrementables.py | 77 - .../services/pedimento_incrementables.py | 77 - .../pedmientos/services/pedimento_indexes.py | 69 - .../pedmientos/services/pedimento_payments.py | 69 - .../pedimento_rectification_destination.py | 80 - .../pedimento_rectification_origin.py | 78 - .../services/pedimento_transport_means.py | 77 - .../services/pedimento_validation.py | 72 - .../a76/pedmientos/services/pedimentos.py | 654 - .../v1/modules/a76/permission_rule_oct/dto.py | 31 - .../modules/a76/permission_rule_oct/models.py | 33 - .../modules/a76/permission_rule_oct/routes.py | 24 - .../a76/permission_rule_oct/services.py | 114 - .../test_permission_rule_oct.py | 36 - .../a76/reports/exportacion/__init__.py | 0 .../exportacion/aviso_consolidado/__init__.py | 0 .../exportacion/aviso_consolidado/routes.py | 57 - .../exportacion/aviso_consolidado/service.py | 651 - .../exportacion/aviso_consolidado/task.py | 50 - .../templates/avcon_exp.html | 357 - .../reports/exportacion/descargo/routes.py | 89 - .../reports/exportacion/descargo/service.py | 398 - .../a76/reports/exportacion/descargo/task.py | 50 - .../descargo/templates/descarga.html | 184 - .../partes_descargadas/__init__.py | 1 - .../exportacion/partes_descargadas/routes.py | 43 - .../exportacion/partes_descargadas/schemas.py | 56 - .../exportacion/partes_descargadas/service.py | 959 - .../transmission/MAINX30/__init__.py | 0 .../transmission/MAINX30/processors.py | 347 - .../transmission/MAINX30/routes.py | 56 - .../transmission/MAINX30/schemas.py | 71 - .../transmission/MAINX30/service.py | 300 - .../exportacion/transmission/MAINX30/task.py | 47 - .../importacion/consolidados/mex/schemas.py | 98 - .../importacion/consolidados/mex/service.py | 920 - .../importacion/consolidados/routes.py | 59 - .../reports/importacion/consolidados/task.py | 55 - .../consolidados/templates/cons_mex_ver.html | 540 - .../consolidados/temporary/mex/service.py | 664 - .../importacion/facturas/mex/schemas.py | 98 - .../importacion/facturas/mex/service.py | 764 - .../reports/importacion/facturas/routes.py | 61 - .../a76/reports/importacion/facturas/task.py | 53 - .../facturas/templates/factura_mex_ver.html | 647 - .../facturas/templates/factura_usa_ver.html | 598 - .../facturas/temporary/__init__.py | 0 .../facturas/temporary/mex/service.py | 428 - .../importacion/facturas/usa/service.py | 750 - .../importacion/packing_list/routes.py | 59 - .../importacion/packing_list/schemas.py | 93 - .../importacion/packing_list/service.py | 466 - .../reports/importacion/packing_list/task.py | 52 - .../packing_list/templates/packing_list.html | 519 - .../definitive/MAINX30/processors.py | 370 - .../transmission/definitive/MAINX30/routes.py | 55 - .../definitive/MAINX30/schemas.py | 72 - .../definitive/MAINX30/service.py | 175 - .../transmission/definitive/MAINX30/task.py | 46 - .../temporal/MAINX30/processors.py | 428 - .../transmission/temporal/MAINX30/routes.py | 56 - .../transmission/temporal/MAINX30/schemas.py | 77 - .../transmission/temporal/MAINX30/service.py | 177 - .../transmission/temporal/MAINX30/task.py | 47 - .../reports/importacion/winsaai/__init__.py | 0 .../importacion/winsaai/invoices/__init__.py | 0 .../importacion/winsaai/invoices/routes.py | 91 - .../importacion/winsaai/invoices/schemas.py | 20 - .../importacion/winsaai/invoices/service.py | 340 - .../importacion/winsaai/invoices/task.py | 63 - .../winsaai/pedimentos/__init__.py | 0 .../importacion/winsaai/pedimentos/routes.py | 84 - .../importacion/winsaai/pedimentos/schemas.py | 20 - .../importacion/winsaai/pedimentos/service.py | 460 - .../importacion/winsaai/pedimentos/task.py | 54 - .../a76/reports/importacion/winsaai/router.py | 9 - .../modules/a76/reports/movements/__init__.py | 0 .../reports/movements/invoices/__init__.py | 0 .../reports/movements/invoices/csv_utils.py | 151 - .../movements/invoices/movement_service.py | 316 - .../a76/reports/movements/invoices/routes.py | 815 - .../a76/reports/movements/invoices/schemas.py | 609 - .../reports/movements/invoices/services.py | 112 - .../movements/invoices/services/__init__.py | 26 - .../movements/invoices/services/base.py | 85 - .../invoices/services/database_helpers.py | 522 - .../movements/invoices/services/definitive.py | 383 - .../invoices/services/exchange_rate.py | 193 - .../movements/invoices/services/export.py | 408 - .../invoices/services/export_repair.py | 409 - .../invoices/services/query_builders.py | 1071 - .../movements/invoices/services/repair.py | 400 - .../movements/invoices/services/temporary.py | 459 - .../a76/reports/movements/invoices/tasks.py | 108 - .../a76/reports/movements/saldos/__init__.py | 0 .../a76/reports/movements/saldos/csv_utils.py | 971 - .../a76/reports/movements/saldos/routes.py | 92 - .../a76/reports/movements/saldos/schemas.py | 52 - .../a76/reports/movements/saldos/tasks.py | 103 - .../reports/movements/vencimiento/__init__.py | 0 .../reports/movements/vencimiento/routes.py | 36 - .../reports/movements/vencimiento/schemas.py | 36 - .../reports/movements/vencimiento/service.py | 412 - .../reports/movements/vencimiento/tasks.py | 107 - .../a76/reports/xml_optima/__init__.py | 0 .../modules/a76/reports/xml_optima/routes.py | 31 - .../modules/a76/reports/xml_optima/schemas.py | 17 - .../modules/a76/reports/xml_optima/service.py | 764 - .../a76/reports/xml_rb_systems/__init__.py | 0 .../a76/reports/xml_rb_systems/routes.py | 90 - .../a76/reports/xml_rb_systems/schemas.py | 25 - .../a76/reports/xml_rb_systems/service.py | 1469 - .../a76/reports/xml_rb_systems/task.py | 62 - backend/api/v1/modules/a76/router.py | 216 - .../a76/rule_octave/balances/__init__.py | 0 .../a76/rule_octave/balances/models.py | 56 - .../v1/modules/a76/rule_octave/country/dto.py | 21 - .../modules/a76/rule_octave/country/models.py | 46 - .../modules/a76/rule_octave/country/routes.py | 98 - .../a76/rule_octave/country/services.py | 44 - .../country/test_country_rule_oct.py | 36 - .../modules/a76/rule_octave/fractions/dto.py | 28 - .../a76/rule_octave/fractions/models.py | 40 - .../a76/rule_octave/fractions/routes.py | 112 - .../a76/rule_octave/fractions/services.py | 41 - .../fractions/test_fraction_rule_octave.py | 36 - .../a76/rule_octave/permissions/models.py | 32 - .../a76/transportation/catalog_parity.py | 380 - .../modules/a76/transportation/drivers/dto.py | 72 - .../a76/transportation/drivers/models.py | 42 - .../a76/transportation/drivers/routes.py | 160 - .../a76/transportation/drivers/services.py | 136 - .../a76/transportation/trailers/dto.py | 73 - .../a76/transportation/trailers/models.py | 24 - .../a76/transportation/trailers/routes.py | 36 - .../a76/transportation/trailers/services.py | 153 - .../a76/transportation/transporters/dto.py | 46 - .../a76/transportation/transporters/models.py | 33 - .../a76/transportation/transporters/routes.py | 31 - .../transportation/transporters/services.py | 194 - .../a76/transportation/vehicles/dto.py | 52 - .../a76/transportation/vehicles/models.py | 39 - .../a76/transportation/vehicles/routes.py | 36 - .../a76/transportation/vehicles/services.py | 172 - backend/api/v1/modules/core/auth/routes.py | 61 +- backend/api/v1/modules/core/auth/service.py | 12 +- .../api/v1/modules/core/dashboard/service.py | 437 +- .../v1/modules/core/invite_codes/models.py | 2 +- .../v1/modules/core/invite_codes/service.py | 16 +- backend/api/v1/modules/core/invites/models.py | 2 +- .../api/v1/modules/core/invites/service.py | 8 +- .../v1/modules/core/permissions/__init__.py | 4 - .../core/permissions/integration_example.py | 153 - .../api/v1/modules/core/permissions/seed.py | 74 - .../v1/modules/core/permissions/seed_v2.py | 236 - .../v1/modules/core/permissions/service.py | 35 +- .../core/permissions/test_permissions.py | 424 - .../v1/modules/core/tasks_tracking/models.py | 2 +- backend/api/v1/modules/core/users/service.py | 11 +- .../{a24/inv/bom => example}/__init__.py | 0 backend/api/v1/modules/example/dto.py | 21 + backend/api/v1/modules/example/models.py | 16 + backend/api/v1/modules/example/routes.py | 65 + backend/api/v1/modules/example/service.py | 48 + backend/api/v1/modules/public/__init__.py | 0 .../reference_data/agency_tariff_codes/dto.py | 12 - .../agency_tariff_codes/models.py | 21 - .../agency_tariff_codes/routes.py | 126 - .../agency_tariff_codes/seed.py | 118 - .../carta_porte_codes/cartaPorte.csv | 28695 ---------------- .../reference_data/carta_porte_codes/dto.py | 14 - .../carta_porte_codes/models.py | 21 - .../carta_porte_codes/routes.py | 76 - .../reference_data/carta_porte_codes/seed.py | 48 - .../code_pedimento_regimens/dto.py | 12 - .../code_pedimento_regimens/models.py | 44 - .../code_pedimento_regimens/routes.py | 116 - .../code_pedimento_regimens/seed.py | 123 - .../test_code_pedimento_regimens.py | 44 - .../modules/public/reference_data/conftest.py | 20 - .../public/reference_data/containers/dto.py | 8 - .../reference_data/containers/models.py | 21 - .../reference_data/containers/routes.py | 106 - .../public/reference_data/containers/seed.py | 74 - .../containers/test_containers.py | 40 - .../public/reference_data/countries/dto.py | 11 - .../public/reference_data/countries/models.py | 29 - .../public/reference_data/countries/routes.py | 108 - .../public/reference_data/countries/seed.py | 647 - .../countries/test_countries.py | 46 - .../reference_data/currency_types/dto.py | 9 - .../reference_data/currency_types/models.py | 24 - .../reference_data/currency_types/routes.py | 107 - .../reference_data/currency_types/seed.py | 96 - .../currency_types/test_currency_types.py | 44 - .../reference_data/customs_sections/dto.py | 8 - .../reference_data/customs_sections/models.py | 17 - .../reference_data/customs_sections/routes.py | 118 - .../reference_data/customs_sections/seed.py | 170 - .../customs_sections/test_customs_sections.py | 44 - .../reference_data/customs_warehouses/dto.py | 9 - .../customs_warehouses/models.py | 22 - .../customs_warehouses/routes.py | 122 - .../reference_data/customs_warehouses/seed.py | 251 - .../test_customs_warehouses.py | 48 - .../public/reference_data/identifiers/dto.py | 10 - .../reference_data/identifiers/models.py | 26 - .../reference_data/identifiers/routes.py | 129 - .../public/reference_data/identifiers/seed.py | 186 - .../public/reference_data/incoterms/dto.py | 9 - .../public/reference_data/incoterms/models.py | 18 - .../public/reference_data/incoterms/routes.py | 118 - .../public/reference_data/incoterms/seed.py | 14 - .../incoterms/test_incoterms.py | 40 - .../reference_data/invoice_types/dto.py | 13 - .../reference_data/invoice_types/models.py | 20 - .../reference_data/invoice_types/routes.py | 120 - .../reference_data/invoice_types/seed.py | 83 - .../invoice_types/test_invoice_types.py | 44 - .../reference_data/license_exceptions/dto.py | 8 - .../license_exceptions/models.py | 20 - .../license_exceptions/routes.py | 122 - .../reference_data/license_exceptions/seed.py | 41 - .../reference_data/material_types/dto.py | 9 - .../reference_data/material_types/models.py | 21 - .../reference_data/material_types/routes.py | 113 - .../reference_data/material_types/seed.py | 36 - .../material_types/test_material_types.py | 44 - .../reference_data/payment_methods/dto.py | 8 - .../reference_data/payment_methods/models.py | 17 - .../reference_data/payment_methods/routes.py | 106 - .../reference_data/payment_methods/seed.py | 25 - .../payment_methods/test_payment_methods.py | 44 - .../reference_data/pedimento_codes/dto.py | 8 - .../reference_data/pedimento_codes/models.py | 28 - .../reference_data/pedimento_codes/routes.py | 106 - .../reference_data/pedimento_codes/seed.py | 220 - .../pedimento_codes/test_pedimento_codes.py | 44 - .../reference_data/pedimento_regimens/dto.py | 8 - .../pedimento_regimens/models.py | 32 - .../pedimento_regimens/routes.py | 106 - .../reference_data/pedimento_regimens/seed.py | 18 - .../test_pedimento_regimens.py | 44 - .../pedimento_transport_catalog/dto.py | 10 - .../pedimento_transport_catalog/models.py | 26 - .../pedimento_transport_catalog/routes.py | 115 - .../pedimento_transport_catalog/seed.py | 16 - .../v1/modules/public/reference_data/rbac.py | 25 - .../modules/public/reference_data/router.py | 142 - .../public/reference_data/states/dto.py | 12 - .../public/reference_data/states/models.py | 22 - .../public/reference_data/states/routes.py | 113 - .../public/reference_data/states/seed.py | 97 - .../reference_data/states/test_states.py | 40 - .../reference_data/trailer_types/dto.py | 28 - .../reference_data/trailer_types/models.py | 13 - .../reference_data/trailer_types/routes.py | 62 - .../reference_data/trailer_types/seed.py | 45 - .../reference_data/trailer_types/services.py | 51 - .../reference_data/transport_modes/dto.py | 8 - .../reference_data/transport_modes/models.py | 17 - .../reference_data/transport_modes/routes.py | 99 - .../reference_data/transport_modes/seed.py | 12 - .../transport_modes/test_transport_modes.py | 44 - .../reference_data/transport_types/dto.py | 8 - .../reference_data/transport_types/models.py | 21 - .../reference_data/transport_types/routes.py | 113 - .../reference_data/transport_types/seed.py | 22 - .../transport_types/test_transport_types.py | 44 - .../reference_data/valuation_methods/dto.py | 8 - .../valuation_methods/models.py | 17 - .../valuation_methods/routes.py | 106 - .../reference_data/valuation_methods/seed.py | 9 - .../test_valuation_methods.py | 44 - backend/api/v1/modules/public/router.py | 14 - backend/api/v1/modules/sitar/README.md | 320 - backend/api/v1/modules/sitar/__init__.py | 90 - .../api/v1/modules/sitar/aladi2/__init__.py | 7 - backend/api/v1/modules/sitar/aladi2/router.py | 37 - .../api/v1/modules/sitar/aladi2/schemas.py | 23 - .../api/v1/modules/sitar/aladi2/service.py | 37 - .../api/v1/modules/sitar/common/__init__.py | 5 - .../v1/modules/sitar/common/base_service.py | 185 - .../api/v1/modules/sitar/cuotas2/__init__.py | 7 - .../api/v1/modules/sitar/cuotas2/router.py | 35 - .../api/v1/modules/sitar/cuotas2/schemas.py | 29 - .../api/v1/modules/sitar/cuotas2/service.py | 37 - .../api/v1/modules/sitar/cupos/__init__.py | 7 - backend/api/v1/modules/sitar/cupos/router.py | 34 - backend/api/v1/modules/sitar/cupos/schemas.py | 24 - backend/api/v1/modules/sitar/cupos/service.py | 34 - .../v1/modules/sitar/fracciones/__init__.py | 7 - .../api/v1/modules/sitar/fracciones/router.py | 43 - .../v1/modules/sitar/fracciones/schemas.py | 38 - .../v1/modules/sitar/fracciones/service.py | 91 - .../sitar/fracciones_anteriores/__init__.py | 7 - .../sitar/fracciones_anteriores/router.py | 37 - .../sitar/fracciones_anteriores/schemas.py | 14 - .../sitar/fracciones_anteriores/service.py | 56 - .../modules/sitar/fracciones_usa/__init__.py | 7 - .../sitar/fracciones_usa/catalog_resolve.py | 124 - .../v1/modules/sitar/fracciones_usa/router.py | 41 - .../modules/sitar/fracciones_usa/schemas.py | 23 - .../modules/sitar/fracciones_usa/service.py | 93 - .../modules/sitar/fundamentos_tlc/__init__.py | 7 - .../modules/sitar/fundamentos_tlc/router.py | 37 - .../modules/sitar/fundamentos_tlc/schemas.py | 26 - .../modules/sitar/fundamentos_tlc/service.py | 40 - backend/api/v1/modules/sitar/ieps/__init__.py | 7 - backend/api/v1/modules/sitar/ieps/router.py | 34 - backend/api/v1/modules/sitar/ieps/schemas.py | 19 - backend/api/v1/modules/sitar/ieps/service.py | 34 - .../sitar/informacion_general/__init__.py | 7 - .../sitar/informacion_general/router.py | 34 - .../sitar/informacion_general/schemas.py | 20 - .../sitar/informacion_general/service.py | 36 - backend/api/v1/modules/sitar/noms/__init__.py | 7 - backend/api/v1/modules/sitar/noms/router.py | 35 - backend/api/v1/modules/sitar/noms/schemas.py | 26 - backend/api/v1/modules/sitar/noms/service.py | 37 - .../sitar/precios_estimados/__init__.py | 7 - .../modules/sitar/precios_estimados/router.py | 34 - .../sitar/precios_estimados/schemas.py | 17 - .../sitar/precios_estimados/service.py | 36 - .../api/v1/modules/sitar/prosec/__init__.py | 7 - backend/api/v1/modules/sitar/prosec/router.py | 42 - .../api/v1/modules/sitar/prosec/schemas.py | 21 - .../api/v1/modules/sitar/prosec/service.py | 43 - backend/api/v1/modules/sitar/rcg2/__init__.py | 7 - backend/api/v1/modules/sitar/rcg2/router.py | 34 - backend/api/v1/modules/sitar/rcg2/schemas.py | 25 - backend/api/v1/modules/sitar/rcg2/service.py | 34 - .../v1/modules/sitar/regulaciones/__init__.py | 7 - .../v1/modules/sitar/regulaciones/router.py | 37 - .../v1/modules/sitar/regulaciones/schemas.py | 18 - .../v1/modules/sitar/regulaciones/service.py | 37 - backend/api/v1/modules/sitar/reit/__init__.py | 7 - backend/api/v1/modules/sitar/reit/router.py | 34 - backend/api/v1/modules/sitar/reit/schemas.py | 24 - backend/api/v1/modules/sitar/reit/service.py | 34 - .../sitar/requisito_previo/__init__.py | 7 - .../modules/sitar/requisito_previo/router.py | 34 - .../modules/sitar/requisito_previo/schemas.py | 27 - .../modules/sitar/requisito_previo/service.py | 36 - backend/api/v1/modules/sitar/router.py | 90 - backend/api/v1/modules/sitar/tlcs/__init__.py | 7 - backend/api/v1/modules/sitar/tlcs/router.py | 44 - backend/api/v1/modules/sitar/tlcs/schemas.py | 24 - backend/api/v1/modules/sitar/tlcs/service.py | 55 - .../sitar/vehiculos_marcas/__init__.py | 7 - .../modules/sitar/vehiculos_marcas/router.py | 34 - .../modules/sitar/vehiculos_marcas/schemas.py | 14 - .../modules/sitar/vehiculos_marcas/service.py | 36 - .../sitar/vehiculos_modelos/__init__.py | 7 - .../modules/sitar/vehiculos_modelos/router.py | 35 - .../sitar/vehiculos_modelos/schemas.py | 37 - .../sitar/vehiculos_modelos/service.py | 39 - backend/api/v1/router.py | 19 +- backend/core/celery_app.py | 65 +- backend/core/config.py | 18 +- backend/core/email.py | 2 +- backend/core/exceptions.py | 2 +- backend/core/middleware.py | 4 + backend/core/security.py | 196 +- backend/main.py | 14 +- backend/tests/README.md | 158 - backend/tests/__init__.py | 1 - backend/tests/conftest.py | 157 - .../tests/e2e/test_inventory_flow_anexo24.py | 111 - backend/tests/fixtures/__init__.py | 1 - backend/tests/fixtures/builders.py | 457 - .../integration/test_export_process_api.py | 133 - .../integration/test_import_process_api.py | 63 - .../integration/test_rls_tenant_company.py | 202 - .../a76/layouts_csv/test_csv_header_hints.py | 143 - .../unit/core/test_license_middleware.py | 21 - .../doda/test_doda_alta_payloads.py | 69 - .../general_catalogs/doda/test_doda_export.py | 72 - .../doda/test_doda_external_service.py | 65 - .../general_catalogs/doda/test_doda_print.py | 130 - .../fractions/test_tariff_fraction_mapper.py | 57 - .../unit/invoices/test_balance_algorithm.py | 120 - .../test_currency_conversion_imports.py | 72 - .../tests/unit/test_csv_reader_encoding.py | 150 - docker-compose.e2e.yml | 12 +- docker-compose.prod.yml | 34 +- docker-compose.yml | 200 +- frontend/.env.example | 12 +- frontend/src/app.html | 2 +- frontend/src/lib/api.ts | 377 +- .../src/lib/api/dashboard/a24/fa_classes.ts | 110 - frontend/src/lib/api/dashboard/a24/inv.ts | 35 - .../lib/api/dashboard/a76/aphis-catalog.ts | 59 - .../src/lib/api/dashboard/a76/app-settings.ts | 27 - .../src/lib/api/dashboard/a76/audit_files.ts | 59 - .../src/lib/api/dashboard/a76/audit_log.ts | 79 - frontend/src/lib/api/dashboard/a76/classes.ts | 150 - .../api/dashboard/a76/clients-providers.ts | 109 - .../lib/api/dashboard/a76/customs-brokers.ts | 232 - .../lib/api/dashboard/a76/doda-alta-log.ts | 86 - frontend/src/lib/api/dashboard/a76/drivers.ts | 105 - .../lib/api/dashboard/a76/exchange-rate.ts | 43 - .../api/dashboard/a76/expediente-archivos.ts | 219 - .../classification-concepts.ts | 80 - .../dashboard/a76/general_catalogs/company.ts | 516 - .../a76/general_catalogs/concepts.ts | 92 - .../customs-broker-concepts.ts | 87 - .../general_catalogs/depreciation-catalog.ts | 41 - .../dashboard/a76/general_catalogs/doda.ts | 543 - .../general_catalogs/electronic-notices.ts | 132 - .../a76/general_catalogs/equivalencies.ts | 160 - .../a76/general_catalogs/error-catalogs.ts | 145 - .../a76/general_catalogs/exchange-rate.ts | 115 - .../a76/general_catalogs/fda-catalog.ts | 41 - .../historical-tariff-fractions.ts | 103 - .../a76/general_catalogs/identifiers.ts | 154 - .../dashboard/a76/general_catalogs/index.ts | 21 - .../dashboard/a76/general_catalogs/inpc.ts | 90 - .../dashboard/a76/general_catalogs/legends.ts | 82 - .../a76/general_catalogs/locations.ts | 111 - .../general_catalogs/multi-currency-types.ts | 89 - .../a76/general_catalogs/packages.ts | 98 - .../dashboard/a76/general_catalogs/ports.ts | 107 - .../a76/general_catalogs/prevalidators.ts | 74 - .../dashboard/a76/general_catalogs/seals.ts | 106 - .../a76/general_catalogs/signatures.ts | 167 - .../a76/general_catalogs/tariff-fractions.ts | 101 - .../dashboard/a76/general_catalogs/um-ace.ts | 75 - .../a76/general_catalogs/um-customs-ame.ts | 75 - .../a76/general_catalogs/um-customs-mex.ts | 78 - .../dashboard/a76/general_catalogs/um-oma.ts | 75 - .../a76/general_catalogs/unit-conversions.ts | 99 - .../a76/general_catalogs/unit-measures.ts | 75 - .../a76/general_catalogs/units-of-measure.ts | 355 - .../general_catalogs/us-tariff-fractions.ts | 43 - frontend/src/lib/api/dashboard/a76/index.ts | 19 - .../api/dashboard/a76/invoice-movements.ts | 203 - .../src/lib/api/dashboard/a76/invoices.ts | 646 - .../src/lib/api/dashboard/a76/item-presets.ts | 77 - frontend/src/lib/api/dashboard/a76/items.ts | 374 - .../lib/api/dashboard/a76/manifest-drivers.ts | 88 - .../src/lib/api/dashboard/a76/manifests.ts | 117 - .../lib/api/dashboard/a76/material-types.ts | 22 - frontend/src/lib/api/dashboard/a76/parts.ts | 196 - .../lib/api/dashboard/a76/pedimento-dates.ts | 61 - .../api/dashboard/a76/pedimento-payments.ts | 64 - .../api/dashboard/a76/pedimento-transport.ts | 43 - .../api/dashboard/a76/pedimento-validation.ts | 55 - .../src/lib/api/dashboard/a76/pedimentos.ts | 341 - .../a76/reports/reports-aviso-consolidado.ts | 59 - .../a76/reports/reports-consolidated.ts | 59 - .../dashboard/a76/reports/reports-descargo.ts | 85 - .../dashboard/a76/reports/reports-invoices.ts | 40 - .../a76/reports/reports-partes-descargadas.ts | 76 - .../a76/reports/reports-transmission.ts | 64 - .../a76/reports/reports-vencimiento.ts | 35 - .../dashboard/a76/reports/reports-winsaai.ts | 134 - .../lib/api/dashboard/a76/saldos-report.ts | 59 - frontend/src/lib/api/dashboard/a76/sitar.ts | 120 - frontend/src/lib/api/dashboard/a76/tasks.ts | 77 - .../src/lib/api/dashboard/a76/trailers.ts | 69 - .../src/lib/api/dashboard/a76/transporters.ts | 85 - .../lib/api/dashboard/a76/units_of_measure.ts | 101 - .../src/lib/api/dashboard/a76/vehicles.ts | 85 - .../dashboard/general_catalogs/canadian.ts | 77 - .../api/dashboard/general_catalogs/sectors.ts | 39 - frontend/src/lib/api/dashboard/index.ts | 43 +- .../reference_data/agency_tariff_codes.ts | 26 - .../dashboard/reference_data/carta_porte.ts | 27 - .../reference_data/code_pedimento_regimens.ts | 79 - .../dashboard/reference_data/containers.ts | 80 - .../api/dashboard/reference_data/countries.ts | 85 - .../reference_data/currency_types.ts | 83 - .../reference_data/customs_sections.ts | 81 - .../reference_data/customs_warehouses.ts | 86 - .../document_types_digitization.ts | 52 - .../dashboard/reference_data/identifiers.ts | 25 - .../api/dashboard/reference_data/incoterms.ts | 83 - .../dashboard/reference_data/invoice_types.ts | 97 - .../reference_data/license_exceptions.ts | 23 - .../reference_data/material_types.ts | 93 - .../reference_data/payment_methods.ts | 74 - .../reference_data/pedimento_codes.ts | 80 - .../reference_data/pedimento_regimens.ts | 77 - .../api/dashboard/reference_data/sectors.ts | 68 - .../api/dashboard/reference_data/states.ts | 81 - .../dashboard/reference_data/trailer_types.ts | 29 - .../reference_data/transport_modes.ts | 80 - .../reference_data/transport_types.ts | 71 - .../reference_data/valuation_methods.ts | 80 - frontend/src/lib/api/dashboard/types.ts | 38 - frontend/src/lib/auth.ts | 4 +- .../dashboard/PrerequisitesModal.svelte | 75 - .../components/dashboard/activity-feed.svelte | 127 - .../clients_and_providers/columns.ts | 169 - .../create-edit-dialog.svelte | 558 - .../data-table-actions.svelte | 111 - .../clients_and_providers/data-table.svelte | 194 - .../delete-dialog.svelte | 110 - .../details-dialog.svelte | 241 - .../dashboard/csv-upload/ConfigFooter.svelte | 106 - .../dashboard/csv-upload/CsvParamsBar.svelte | 141 - .../csv-upload/CsvPendingImportsSheet.svelte | 201 - .../csv-upload/ProcessingResultModal.svelte | 622 - .../csv-upload/UniversalUploadModal.svelte | 123 - .../csv-upload/UploadLauncherGrid.svelte | 307 - .../dashboard/customs_brokers/columns.ts | 204 - .../customs_brokers/create-dialog.svelte | 422 - .../customs_brokers/data-table-actions.svelte | 73 - .../customs_brokers/data-table.svelte | 80 - .../customs_brokers/delete-dialog.svelte | 104 - .../customs_brokers/details-dialog.svelte | 192 - .../customs_brokers/edit-dialog.svelte | 485 - .../despacho/doda/doda-alta-log-columns.ts | 119 - .../despacho/doda/doda-alta-log-dialog.svelte | 203 - .../doda/doda-export-excel-dialog.svelte | 184 - .../despacho/doda/doda-progress-dialog.svelte | 224 - .../dashboard/digitalizacion/columns.ts | 149 - .../digitalizacion/create-edit-dialog.svelte | 447 - .../digitalizacion/data-table-actions.svelte | 151 - .../digitalizacion/digitalizar-dialog.svelte | 176 - .../digitalizacion/e-document-cell.svelte | 19 - .../digitalizacion/progress-dialog.svelte | 304 - .../dashboard/exchange_rate/columns.ts | 52 - .../exchange_rate/create-edit-dialog.svelte | 335 - .../exchange_rate/data-table-actions.svelte | 105 - .../dashboard/exchange_rate/data-table.svelte | 106 - .../exchange_rate/exchange-rate-guard.svelte | 75 - .../dashboard/export/manifest/columns.ts | 101 - .../export/manifest/create-dialog.svelte | 171 - .../export/manifest/data-table.svelte | 140 - .../export/manifest/delete-dialog.svelte | 77 - .../export/manifest/details-dialog.svelte | 164 - .../export/manifest/edit-dialog.svelte | 173 - .../manifest/invoice-selection-table.svelte | 125 - .../manifest/manifest-driver-subform.svelte | 455 - .../export/manifest/manifest-form.svelte | 1398 - .../modals/broker-selector-dialog.svelte | 150 - .../modals/driver-selector-dialog.svelte | 135 - .../modals/port-selector-dialog.svelte | 155 - .../modals/trailer-selector-dialog.svelte | 146 - .../transport-mode-selector-dialog.svelte | 129 - .../transport-type-selector-dialog.svelte | 135 - .../modals/transporter-selector-dialog.svelte | 156 - .../modals/vehicle-selector-dialog.svelte | 141 - .../classification_concepts/columns.ts | 35 - .../create-edit-dialog.svelte | 140 - .../data-table-actions.svelte | 98 - .../classification_concepts/data-table.svelte | 106 - .../general_catalogs/company/columns.ts | 41 - .../company/create-edit-dialog.svelte | 352 - .../company/data-table-actions.svelte | 87 - .../company/data-table.svelte | 109 - .../general_catalogs/concepts/columns.ts | 45 - .../concepts/create-edit-dialog.svelte | 232 - .../concepts/data-table-actions.svelte | 93 - .../concepts/data-table.svelte | 106 - .../customs_broker_concepts/columns.ts | 45 - .../create-edit-dialog.svelte | 237 - .../data-table-actions.svelte | 99 - .../customs_broker_concepts/data-table.svelte | 106 - .../doda/child-detail-table.svelte | 173 - .../general_catalogs/doda/columns.ts | 182 - .../doda/data-table-actions.svelte | 83 - .../general_catalogs/doda/data-table.svelte | 144 - .../doda/delete-list-state.ts | 12 - .../doda/doda-form-helpers.ts | 105 - .../doda/doda-form-modal.svelte | 1810 - .../electronic_notices/columns.ts | 49 - .../create-edit-dialog.svelte | 250 - .../data-table-actions.svelte | 90 - .../electronic_notices/data-table.svelte | 104 - .../equivalencies/catalog-columns.ts | 46 - .../catalog-create-edit-dialog.svelte | 146 - .../catalog-data-table-actions.svelte | 79 - .../general_catalogs/equivalencies/columns.ts | 39 - .../equivalencies/create-edit-dialog.svelte | 202 - .../data-equivalencies-dialog.svelte | 395 - .../equivalencies/data-table-actions.svelte | 95 - .../equivalencies/data-table.svelte | 104 - .../error_catalogs/columns.ts | 37 - .../error_catalogs/create-edit-dialog.svelte | 203 - .../error_catalogs/data-table-actions.svelte | 102 - .../error_catalogs/data-table.svelte | 106 - .../general_catalogs/identifiers/columns.ts | 46 - .../identifiers/create-edit-dialog.svelte | 150 - .../identifiers/data-table-actions.svelte | 90 - .../identifiers/data-table.svelte | 104 - .../general_catalogs/inpc/columns.ts | 35 - .../inpc/create-edit-dialog.svelte | 146 - .../inpc/data-table-actions.svelte | 99 - .../general_catalogs/inpc/data-table.svelte | 106 - .../general_catalogs/legends/columns.ts | 33 - .../legends/create-edit-dialog.svelte | 161 - .../legends/data-table-actions.svelte | 94 - .../legends/data-table.svelte | 108 - .../locations/create-edit-dialog.svelte | 189 - .../create-edit-dialog.svelte | 247 - .../general_catalogs/prevalidators/columns.ts | 44 - .../prevalidators/create-edit-dialog.svelte | 154 - .../prevalidators/data-table-actions.svelte | 90 - .../prevalidators/data-table.svelte | 102 - .../general_catalogs/seals/columns.ts | 30 - .../seals/create-edit-dialog.svelte | 133 - .../seals/data-table-actions.svelte | 106 - .../dashboard/general_catalogs/seals/index.ts | 7 - .../sectors/SectorsList.svelte | 238 - .../general_catalogs/sectors/columns.ts | 39 - .../sectors/create-edit-dialog.svelte | 110 - .../sectors/data-table-actions.svelte | 75 - .../general_catalogs/signatures/columns.ts | 38 - .../signatures/create-edit-dialog.svelte | 309 - .../signatures/data-table-actions.svelte | 98 - .../signatures/data-table.svelte | 106 - .../general_catalogs/simple-data-table.svelte | 93 - .../unit_conversion/create-edit-dialog.svelte | 164 - .../unit_conversions/columns.ts | 35 - .../create-edit-dialog.svelte | 227 - .../data-table-actions.svelte | 90 - .../unit_conversions/data-table.svelte | 106 - .../units_of_measure/ace/columns.ts | 35 - .../ace/create-edit-dialog.svelte | 133 - .../ace/data-table-actions.svelte | 90 - .../units_of_measure/ace/data-table.svelte | 106 - .../units_of_measure/american/columns.ts | 33 - .../american/create-edit-dialog.svelte | 119 - .../american/data-table-actions.svelte | 63 - .../american/data-table.svelte | 106 - .../units_of_measure/customs/columns.ts | 37 - .../customs/create-edit-dialog.svelte | 124 - .../customs/data-table-actions.svelte | 57 - .../customs/data-table.svelte | 106 - .../units_of_measure/general/columns.ts | 35 - .../general/create-edit-dialog.svelte | 119 - .../general/data-table-actions.svelte | 59 - .../general/data-table.svelte | 107 - .../units_of_measure/main/data-table.svelte | 107 - .../units_of_measure/oma/columns.ts | 33 - .../oma/create-edit-dialog.svelte | 119 - .../oma/data-table-actions.svelte | 57 - .../units_of_measure/oma/data-table.svelte | 106 - .../dashboard/goods/classes/columns.ts | 190 - .../goods/classes/create-edit-dialog.svelte | 490 - .../dashboard/goods/classes/data-table.svelte | 201 - .../classes/forms/FixedAssetClassForm.svelte | 1447 - .../fractions/CanadianFractionDialog.svelte | 180 - .../fractions/CanadianFractionList.svelte | 358 - .../fractions/HistoricalFractionDialog.svelte | 270 - .../fractions/HistoricalFractionList.svelte | 368 - .../goods/fractions/SitarFractionTabs.svelte | 434 - .../fractions/TariffFractionFormDialog.svelte | 234 - .../goods/fractions/TariffFractionList.svelte | 425 - .../modales/TariffFractionSelector.svelte | 192 - .../modales/client-selector-dialog.svelte | 162 - .../modales/country-selector-dialog.svelte | 235 - .../modales/currency-selector-dialog.svelte | 156 - .../modales/fraction-selector-dialog.svelte | 16 - .../material-type-selector-dialog.svelte | 135 - .../goods/modales/unit-measure-dialog.svelte | 156 - .../us-fraction-selector-dialog.svelte | 196 - .../parts/AgencyTariffCodeSelector.svelte | 154 - .../goods/parts/CartaPorteSelector.svelte | 177 - .../parts/Fraction9801SelectorDialog.svelte | 17 - .../goods/parts/IdentifierSelector.svelte | 153 - .../parts/LicenseExceptionSelector.svelte | 147 - .../dashboard/goods/parts/PartSelector.svelte | 161 - .../parts/SubstitutePartSelectorDialog.svelte | 16 - .../TextileProviderSelectorDialog.svelte | 140 - .../goods/parts/class-selector-dialog.svelte | 227 - .../dashboard/goods/parts/columns.ts | 302 - .../goods/parts/create-edit-dialog.svelte | 520 - .../goods/parts/data-table-actions.svelte | 110 - .../dashboard/goods/parts/data-table.svelte | 224 - .../dashboard/goods/parts/partForm.svelte | 3785 -- .../dashboard/identifiers/columns.ts | 39 - .../identifiers/create-edit-dialog.svelte | 178 - .../identifiers/data-table-actions.svelte | 79 - .../dashboard/identifiers/data-table.svelte | 106 - .../components/dashboard/invoices/columns.ts | 372 - .../invoices/create-edit-dialog.svelte | 822 - .../invoices/data-table-actions.svelte | 73 - .../dashboard/invoices/data-table.svelte | 269 - .../dashboard/invoices/delete-dialog.svelte | 143 - .../dashboard/invoices/details-dialog.svelte | 449 - .../invoices/download-invoice-button.svelte | 23 - .../invoices/edit/InvoiceSelectorModal.svelte | 207 - .../edit/ManifestSelectorModal.svelte | 135 - .../invoices/edit/PortSelectorModal.svelte | 113 - .../edit/continuation-tab-form.svelte | 381 - .../invoices/edit/focus-invoice-field.ts | 124 - .../invoices/edit/general-tab-form.svelte | 1072 - .../invoices/edit/invoice-top-fields.svelte | 440 - .../edit/items/fa/class-dialog.svelte | 236 - .../edit/items/fa/country-dialog.svelte | 242 - .../fa/identifier-catalog-selector.svelte | 124 - .../edit/items/fa/item-configuration.svelte | 192 - .../edit/items/fa/item-sheet-fa.svelte | 1055 - .../items/fa/location-selector-dialog.svelte | 324 - .../invoices/edit/items/fa/main-data.svelte | 516 - .../edit/items/fa/package-dialog.svelte | 178 - .../edit/items/fa/packages-section.svelte | 278 - .../edit/items/fa/part-number-dialog.svelte | 243 - .../items/fa/payment-method-dialog.svelte | 166 - .../edit/items/fa/state-dialog.svelte | 170 - .../edit/items/fa/summary-section.svelte | 126 - .../edit/items/fa/tab-continuation.svelte | 362 - .../edit/items/fa/tab-identifiers.svelte | 395 - .../edit/items/fa/tab-labeling.svelte | 285 - .../invoices/edit/items/fa/tab-series.svelte | 465 - .../items/fa/tariff-fraction-dialog.svelte | 16 - .../items/fa/unit-of-measure-dialog.svelte | 197 - .../items/fa/valuation-method-selector.svelte | 117 - .../edit/items/focus-item-sheet-field.ts | 75 - .../edit/items/inv/item-sheet-inv.svelte | 745 - .../invoices/edit/items/items-tab-form.svelte | 1502 - .../edit/observations-tab-form.svelte | 699 - .../invoices/edit/others-tab-form.svelte | 402 - .../dashboard/invoices/edit/save-invoice.ts | 358 - .../invoices/invoice-download-modal.svelte | 184 - .../invoices/invoice-list-navigation.ts | 148 - .../invoices/pdf-progress-dialog.svelte | 393 - .../settings/sscr-settings-form.svelte | 1257 - .../settings/sscr-settings-form.svelte.patch | 28 - .../settings/ssicm-settings-form.svelte | 448 - .../settings/ssimp-settings-form.svelte | 392 - .../settings/ssisdef-settings-form.svelte | 535 - .../transferencia-electronica-modal.svelte | 712 - .../components/dashboard/locations/columns.ts | 44 - .../locations/data-table-actions.svelte | 104 - .../dashboard/locations/data-table.svelte | 76 - .../locations/locations-catalog.svelte | 180 - .../components/dashboard/packages/columns.ts | 77 - .../packages/create-edit-dialog.svelte | 274 - .../packages/data-table-actions.svelte | 123 - .../dashboard/packages/data-table.svelte | 110 - .../dashboard/pedimentos/columns.ts | 411 - .../pedimentos/data-table-actions.svelte | 89 - .../dashboard/pedimentos/data-table.svelte | 245 - .../accounts-compensation-tab-form.svelte | 872 - .../edit/contributions-tab-form.svelte | 828 - .../pedimentos/edit/dates-tab-form.svelte | 94 - .../edit/digitization-tab-form.svelte | 642 - .../pedimentos/edit/discharge-tab-form.svelte | 495 - .../pedimentos/edit/general-tab-form.svelte | 1420 - .../edit/identifiers-tab-form.svelte | 174 - .../pedimentos/edit/invoices-tab-form.svelte | 530 - .../pedimentos/edit/items-tab-form.svelte | 543 - .../edit/other-data-tab-form.svelte | 2350 -- .../package-transportation-tab-form.svelte | 1942 -- .../pedimentos/edit/payments-tab-form.svelte | 188 - .../edit/pedimento-selector-dialog.svelte | 227 - .../dashboard/pedimentos/edit/temp.svelte | 771 - .../pedimentos/edit/transport-tab-form.svelte | 98 - .../edit/validation-tab-form.svelte | 151 - .../lib/components/dashboard/ports/columns.ts | 54 - .../dashboard/ports/create-edit-dialog.svelte | 229 - .../dashboard/ports/data-table-actions.svelte | 68 - .../dashboard/ports/delete-dialog.svelte | 117 - .../code_pedimento_regimens/columns.ts | 86 - .../create-edit-dialog.svelte | 200 - .../data-table-actions.svelte | 82 - .../code_pedimento_regimens/data-table.svelte | 125 - .../delete-dialog.svelte | 119 - .../details-dialog.svelte | 84 - .../reference_data/containers/columns.ts | 58 - .../containers/create-edit-dialog.svelte | 184 - .../containers/data-table-actions.svelte | 78 - .../containers/data-table.svelte | 125 - .../containers/delete-dialog.svelte | 94 - .../containers/details-dialog.svelte | 57 - .../reference_data/countries/columns.ts | 102 - .../countries/create-edit-dialog.svelte | 239 - .../countries/data-table-actions.svelte | 74 - .../countries/data-table.svelte | 127 - .../countries/delete-dialog.svelte | 116 - .../countries/details-dialog.svelte | 79 - .../reference_data/currency_types/columns.ts | 72 - .../currency_types/create-edit-dialog.svelte | 206 - .../currency_types/data-table-actions.svelte | 74 - .../currency_types/data-table.svelte | 125 - .../currency_types/delete-dialog.svelte | 116 - .../currency_types/details-dialog.svelte | 65 - .../customs_sections/columns.ts | 58 - .../create-edit-dialog.svelte | 188 - .../data-table-actions.svelte | 86 - .../customs_sections/data-table.svelte | 125 - .../customs_sections/delete-dialog.svelte | 113 - .../customs_sections/details-dialog.svelte | 57 - .../customs_warehouses/columns.ts | 73 - .../create-edit-dialog.svelte | 209 - .../data-table-actions.svelte | 78 - .../customs_warehouses/data-table.svelte | 125 - .../customs_warehouses/delete-dialog.svelte | 117 - .../customs_warehouses/details-dialog.svelte | 62 - .../document_types_digitization/columns.ts | 41 - .../reference_data/incoterms/columns.ts | 73 - .../incoterms/create-edit-dialog.svelte | 207 - .../incoterms/data-table-actions.svelte | 76 - .../incoterms/data-table.svelte | 116 - .../incoterms/delete-dialog.svelte | 116 - .../incoterms/details-dialog.svelte | 65 - .../reference_data/invoice_types/columns.ts | 87 - .../invoice_types/create-edit-dialog.svelte | 222 - .../invoice_types/data-table-actions.svelte | 78 - .../invoice_types/data-table.svelte | 125 - .../invoice_types/delete-dialog.svelte | 124 - .../invoice_types/details-dialog.svelte | 77 - .../reference_data/material_types/columns.ts | 73 - .../material_types/create-edit-dialog.svelte | 206 - .../material_types/data-table-actions.svelte | 74 - .../material_types/data-table.svelte | 125 - .../material_types/delete-dialog.svelte | 98 - .../material_types/details-dialog.svelte | 65 - .../reference_data/payment_methods/columns.ts | 59 - .../payment_methods/create-edit-dialog.svelte | 186 - .../payment_methods/data-table-actions.svelte | 74 - .../payment_methods/data-table.svelte | 125 - .../payment_methods/delete-dialog.svelte | 94 - .../payment_methods/details-dialog.svelte | 57 - .../reference_data/pedimento_codes/columns.ts | 59 - .../pedimento_codes/create-edit-dialog.svelte | 188 - .../pedimento_codes/data-table-actions.svelte | 74 - .../pedimento_codes/data-table.svelte | 125 - .../pedimento_codes/delete-dialog.svelte | 112 - .../pedimento_codes/details-dialog.svelte | 57 - .../pedimento_regimens/columns.ts | 58 - .../create-edit-dialog.svelte | 186 - .../data-table-actions.svelte | 74 - .../pedimento_regimens/data-table.svelte | 125 - .../pedimento_regimens/delete-dialog.svelte | 94 - .../pedimento_regimens/details-dialog.svelte | 57 - .../reference_data/sectors/columns.ts | 82 - .../sectors/create-edit-dialog.svelte | 242 - .../sectors/data-table-actions.svelte | 78 - .../reference_data/sectors/data-table.svelte | 125 - .../sectors/delete-dialog.svelte | 139 - .../sectors/details-dialog.svelte | 68 - .../reference_data/states/columns.ts | 93 - .../states/create-edit-dialog.svelte | 206 - .../states/data-table-actions.svelte | 74 - .../reference_data/states/data-table.svelte | 125 - .../states/delete-dialog.svelte | 106 - .../states/details-dialog.svelte | 85 - .../reference_data/transport_modes/columns.ts | 58 - .../transport_modes/create-edit-dialog.svelte | 184 - .../transport_modes/data-table-actions.svelte | 74 - .../transport_modes/data-table.svelte | 125 - .../transport_modes/delete-dialog.svelte | 94 - .../transport_modes/details-dialog.svelte | 57 - .../reference_data/transport_types/columns.ts | 59 - .../transport_types/create-edit-dialog.svelte | 184 - .../transport_types/data-table-actions.svelte | 74 - .../transport_types/data-table.svelte | 125 - .../transport_types/delete-dialog.svelte | 112 - .../transport_types/details-dialog.svelte | 57 - .../valuation_methods/columns.ts | 59 - .../create-edit-dialog.svelte | 184 - .../data-table-actions.svelte | 78 - .../valuation_methods/data-table.svelte | 126 - .../valuation_methods/delete-dialog.svelte | 112 - .../valuation_methods/details-dialog.svelte | 57 - .../reports/downloaded-parts-form.svelte | 865 - .../transportation/drivers/columns.ts | 61 - .../drivers/create-edit-dialog.svelte | 563 - .../drivers/data-table-actions.svelte | 124 - .../transportation/drivers/data-table.svelte | 99 - .../transportation/trailers/columns.ts | 73 - .../trailers/create-edit-dialog.svelte | 366 - .../trailers/data-table-actions.svelte | 119 - .../transportation/trailers/data-table.svelte | 99 - .../transporters/create-edit-dialog.svelte | 639 - .../transporters/data-table-actions.svelte | 122 - .../transporters/data-table.svelte | 99 - .../transporters/transporter-columns.ts | 72 - .../transportation/vehicles/columns.ts | 74 - .../vehicles/create-edit-dialog.svelte | 538 - .../vehicles/data-table-actions.svelte | 120 - .../transportation/vehicles/data-table.svelte | 99 - .../keyboard/KeyboardManager.svelte | 2 +- .../lib/components/sidebar/app-sidebar.svelte | 60 +- .../src/lib/components/sidebar/modules.ts | 682 +- .../lib/components/sidebar/nav-main.svelte | 2 +- .../components/sidebar/team-switcher.svelte | 21 +- .../alert-dialog/alert-dialog-content.svelte | 4 +- .../ui/dialog/dialog-content.svelte | 4 +- .../components/ui/sheet/sheet-content.svelte | 3 +- frontend/src/lib/config/csv-upload.ts | 456 - .../src/lib/config/invoice-item-visibility.ts | 250 - frontend/src/lib/config/shortcuts.ts | 75 +- .../dashboard/a76/digitalizacion/list.ts | 59 - .../dashboard/clients_and_providers/edit.ts | 41 - .../dashboard/clients_and_providers/list.ts | 38 - .../dashboard/customs_brokers/edit.ts | 47 - .../dashboard/customs_brokers/list.ts | 31 - .../general_catalogs/classification/edit.ts | 4 - .../general_catalogs/classification/list.ts | 4 - .../general_catalogs/common/factory.ts | 81 - .../company_information/edit.ts | 29 - .../company_information/list.ts | 4 - .../general_catalogs/concepts/edit.ts | 4 - .../general_catalogs/concepts/list.ts | 4 - .../customs_broker_concepts/edit.ts | 4 - .../customs_broker_concepts/list.ts | 4 - .../dashboard/general_catalogs/doda/edit.ts | 46 - .../dashboard/general_catalogs/doda/list.ts | 4 - .../general_catalogs/drivers/edit.ts | 6 - .../general_catalogs/drivers/list.ts | 8 - .../electronic_notices/edit.ts | 4 - .../electronic_notices/list.ts | 4 - .../general_catalogs/equivalencies/edit.ts | 4 - .../general_catalogs/equivalencies/list.ts | 4 - .../general_catalogs/error_catalogs/edit.ts | 4 - .../general_catalogs/error_catalogs/list.ts | 4 - .../general_catalogs/exchange_rate/edit.ts | 4 - .../general_catalogs/exchange_rate/list.ts | 4 - .../general_catalogs/identifiers/edit.ts | 4 - .../general_catalogs/identifiers/list.ts | 4 - .../dashboard/general_catalogs/inpc/edit.ts | 4 - .../dashboard/general_catalogs/inpc/list.ts | 4 - .../general_catalogs/legends/edit.ts | 4 - .../general_catalogs/legends/list.ts | 4 - .../general_catalogs/locations/edit.ts | 4 - .../general_catalogs/locations/list.ts | 4 - .../multi_currency_types/edit.ts | 4 - .../multi_currency_types/list.ts | 4 - .../general_catalogs/packages/edit.ts | 7 - .../general_catalogs/packages/list.ts | 9 - .../dashboard/general_catalogs/ports/edit.ts | 4 - .../dashboard/general_catalogs/ports/list.ts | 4 - .../general_catalogs/prevalidators/edit.ts | 4 - .../general_catalogs/prevalidators/list.ts | 4 - .../dashboard/general_catalogs/seal/edit.ts | 4 - .../dashboard/general_catalogs/seal/list.ts | 4 - .../general_catalogs/signatures/edit.ts | 4 - .../general_catalogs/signatures/list.ts | 4 - .../general_catalogs/tariff_fractions/list.ts | 4 - .../general_catalogs/unit_conversions/edit.ts | 4 - .../general_catalogs/unit_conversions/list.ts | 4 - .../units_of_measure/ace/edit.ts | 4 - .../units_of_measure/ace/list.ts | 4 - .../units_of_measure/american/edit.ts | 4 - .../units_of_measure/american/list.ts | 4 - .../units_of_measure/commercial/edit.ts | 4 - .../units_of_measure/commercial/list.ts | 4 - .../units_of_measure/customs/edit.ts | 4 - .../units_of_measure/customs/list.ts | 4 - .../units_of_measure/general/edit.ts | 4 - .../units_of_measure/general/list.ts | 4 - .../units_of_measure/oma/edit.ts | 4 - .../units_of_measure/oma/list.ts | 4 - .../config/shortcuts/dashboard/goods/edit.ts | 16 - .../config/shortcuts/dashboard/goods/list.ts | 13 - .../shortcuts/dashboard/invoices/edit.ts | 15 - .../dashboard/invoices/item/fixed_asset.ts | 40 - .../dashboard/invoices/item/inventory.ts | 34 - .../dashboard/invoices/item/items_tab.ts | 12 - .../shortcuts/dashboard/invoices/item/list.ts | 17 - .../shortcuts/dashboard/invoices/list.ts | 113 - .../shortcuts/dashboard/pedimentos/edit.ts | 25 - .../shortcuts/dashboard/pedimentos/list.ts | 43 - .../pedimentos/tabs/contributions.ts | 11 - .../dashboard/pedimentos/tabs/general.ts | 36 - .../dashboard/pedimentos/tabs/other_data.ts | 27 - .../code_pedimento_regimens/list.ts | 4 - .../reference_data/common/factory.ts | 27 - .../reference_data/containers/list.ts | 4 - .../reference_data/countries/list.ts | 4 - .../reference_data/currency_types/list.ts | 4 - .../reference_data/customs_sections/list.ts | 4 - .../reference_data/customs_warehouses/list.ts | 4 - .../reference_data/incoterms/list.ts | 4 - .../reference_data/invoice_types/list.ts | 4 - .../reference_data/material_types/list.ts | 4 - .../reference_data/payment_methods/list.ts | 4 - .../reference_data/pedimento_codes/list.ts | 4 - .../reference_data/pedimento_regimens/list.ts | 4 - .../dashboard/reference_data/sectors/list.ts | 4 - .../dashboard/reference_data/states/list.ts | 4 - .../reference_data/transport_modes/list.ts | 4 - .../reference_data/transport_types/list.ts | 4 - .../reference_data/valuation_methods/list.ts | 4 - .../src/lib/csv-import-commit-metrics.test.ts | 46 - frontend/src/lib/csv-import-commit-metrics.ts | 36 - frontend/src/lib/csv-import-pending.ts | 150 - frontend/src/lib/csv-import-session.ts | 129 - .../src/lib/csv-import-status-api.test.ts | 18 - frontend/src/lib/csv-import-status-api.ts | 47 - frontend/src/lib/i18n/doda-form-strings.ts | 60 - .../catalogs/broker-concepts-permissions.ts | 12 - .../classification-concepts-permissions.ts | 12 - .../catalogs/company-catalog-permissions.ts | 12 - .../catalogs/concepts-permissions.ts | 12 - .../depreciation-catalog-permissions.ts | 15 - .../permissions/catalogs/doda-permissions.ts | 12 - .../catalogs/drivers-permissions.ts | 11 - .../electronic-notices-permissions.ts | 12 - .../catalogs/equivalencies-permissions.ts | 12 - .../catalogs/error-catalogs-permissions.ts | 12 - .../catalogs/exchange-rates-permissions.ts | 12 - .../catalogs/identifiers-permissions.ts | 12 - .../permissions/catalogs/inpc-permissions.ts | 12 - .../catalogs/legends-permissions.ts | 12 - .../catalogs/locations-catalog-permissions.ts | 12 - .../multi-currency-types-permissions.ts | 12 - .../catalogs/packages-permissions.ts | 12 - .../permissions/catalogs/ports-permissions.ts | 12 - .../catalogs/prevalidators-permissions.ts | 12 - .../permissions/catalogs/seals-permissions.ts | 12 - .../catalogs/sectors-catalog-permissions.ts | 12 - .../catalogs/signatures-permissions.ts | 12 - .../catalogs/trailers-permissions.ts | 11 - .../catalogs/transporters-permissions.ts | 14 - .../catalogs/um-ace-permissions.ts | 12 - .../catalogs/um-american-permissions.ts | 12 - .../catalogs/um-customs-permissions.ts | 12 - .../catalogs/um-general-permissions.ts | 12 - .../catalogs/um-oma-permissions.ts | 12 - .../catalogs/unit-conversions-permissions.ts | 12 - .../catalogs/vehicles-permissions.ts | 11 - .../clients-providers-permissions.ts | 18 - .../permissions/customs-broker-permissions.ts | 18 - .../permissions/goods-classes-permissions.ts | 18 - .../permissions/goods-parts-permissions.ts | 18 - .../lib/permissions/invoice-permissions.ts | 114 - .../lib/permissions/pedimento-permissions.ts | 37 - .../containers-ref-permissions.ts | 13 - .../countries-ref-permissions.ts | 13 - .../currency-types-ref-permissions.ts | 13 - .../customs-sections-ref-permissions.ts | 13 - .../customs-warehouses-ref-permissions.ts | 13 - .../incoterms-ref-permissions.ts | 13 - .../invoice-types-ref-permissions.ts | 13 - .../material-types-ref-permissions.ts | 13 - .../payment-methods-ref-permissions.ts | 13 - .../pedimento-codes-ref-permissions.ts | 13 - .../pedimento-regimens-ref-permissions.ts | 13 - .../reference-data/sectors-ref-permissions.ts | 20 - .../reference-data/states-ref-permissions.ts | 13 - .../transport-modes-ref-permissions.ts | 13 - .../transport-types-ref-permissions.ts | 13 - .../valuation-methods-ref-permissions.ts | 13 - .../tariff-fraction-permissions.ts | 39 - frontend/src/lib/server/api.ts | 4 +- frontend/src/lib/server/workspace-auth.ts | 12 +- frontend/src/lib/stores/company.svelte.ts | 2 +- frontend/src/lib/stores/system.svelte.ts | 62 +- .../src/lib/stores/workspace-apps.svelte.ts | 2 +- .../src/lib/utils.getInvoiceTypeColor.test.ts | 46 - frontend/src/lib/utils.ts | 35 - frontend/src/lib/utils/audit-log-i18n.ts | 282 - frontend/src/lib/utils/items-logic.ts | 527 - .../lib/utils/mexican-tariff-fraction.test.ts | 67 - .../src/lib/utils/mexican-tariff-fraction.ts | 64 - .../utils/partida-notice-interact-outside.ts | 17 - .../lib/utils/tariff-fraction-display.test.ts | 43 - .../src/lib/utils/tariff-fraction-display.ts | 9 - frontend/src/routes/+page.svelte | 1093 +- .../routes/api-sveltekit/classes/+server.ts | 91 - .../api-sveltekit/classes/[id]/+server.ts | 87 - .../company/my-companies/+server.ts | 2 +- .../routes/api-sveltekit/countries/+server.ts | 60 - .../api-sveltekit/fda-catalog/+server.ts | 283 - .../api-sveltekit/fda-catalog/[id]/+server.ts | 31 - .../[id]/affirmation-codes/+server.ts | 31 - .../[id]/constituent-elements/+server.ts | 31 - .../[id]/lot-productions/+server.ts | 31 - .../[id]/specifications/+server.ts | 31 - .../rate/+server.ts | 72 - .../routes/api-sveltekit/packages/+server.ts | 81 - .../src/routes/api-sveltekit/parts/+server.ts | 93 - .../api-sveltekit/parts/[id]/+server.ts | 87 - .../api-sveltekit/payment-methods/+server.ts | 65 - .../api-sveltekit/tariff-fractions/+server.ts | 65 - .../api-sveltekit/units-of-measure/+server.ts | 81 - .../units-of-measure/[id]/+server.ts | 87 - .../src/routes/auth/callback/+page.server.ts | 2 +- .../src/routes/auth/post-logout/+server.ts | 2 +- frontend/src/routes/auth/sso/+page.server.ts | 4 +- .../src/routes/dashboard/+layout.server.ts | 94 +- frontend/src/routes/dashboard/+layout.svelte | 54 - frontend/src/routes/dashboard/+page.svelte | 302 +- .../routes/dashboard/account/+page.server.ts | 70 +- .../src/routes/dashboard/account/+page.svelte | 259 +- .../src/routes/dashboard/admin/+layout.svelte | 36 - .../routes/dashboard/admin/roles/+page.svelte | 540 - .../routes/dashboard/audit_logs/+page.svelte | 102 - .../src/routes/dashboard/audit_logs/+page.ts | 7 - .../dashboard/audit_logs/bitacora-tab.svelte | 344 - .../dashboard/audit_logs/files-tab.svelte | 231 - .../dashboard/audit_logs/tasks-tab.svelte | 356 - .../clients_and_providers/+page.server.ts | 104 - .../clients_and_providers/+page.svelte | 586 - .../edit/[[id]]/+page.svelte | 884 - .../routes/dashboard/csv-upload/+page.svelte | 1395 - .../dashboard/customs_brokers/+page.server.ts | 84 - .../dashboard/customs_brokers/+page.svelte | 534 - .../customs_brokers/edit/[[id]]/+page.svelte | 1168 - .../despacho/digitalizacion/+page.svelte | 665 - .../dashboard/despacho/doda/+page.server.ts | 81 - .../dashboard/despacho/doda/+page.svelte | 600 - .../despacho/doda/edit/[[id]]/+page.ts | 15 - .../dashboard/export/catalog/+page.svelte | 5 - .../dashboard/export/destruction/+page.svelte | 5 - .../dashboard/export/manifest/+page.server.ts | 94 - .../dashboard/export/manifest/+page.svelte | 270 - .../export/manifest/edit/[[id]]/+page.svelte | 9 - .../dashboard/export/proforma/+page.svelte | 5 - .../dashboard/export/repair/+page.svelte | 5 - .../dashboard/export/reports/+page.svelte | 5 - .../export/special-processes/+page.svelte | 7 - .../export/used-materials/+page.svelte | 5 - .../dashboard/general_catalogs/+page.svelte | 0 .../classification_concepts/+page.server.ts | 70 - .../classification_concepts/+page.svelte | 244 - .../company_information/+page.server.ts | 89 - .../company_information/+page.svelte | 296 - .../edit/[[id]]/+page.svelte | 2106 -- .../general_catalogs/concepts/+page.server.ts | 110 - .../general_catalogs/concepts/+page.svelte | 285 - .../customs_broker_concepts/+page.server.ts | 70 - .../customs_broker_concepts/+page.svelte | 272 - .../general_catalogs/doda/+page.server.ts | 14 - .../general_catalogs/doda/+page.svelte | 288 - .../doda/edit/[[id]]/+page.svelte | 797 - .../doda/edit/[[id]]/+page.ts | 15 - .../general_catalogs/drivers/+page.server.ts | 57 - .../general_catalogs/drivers/+page.svelte | 245 - .../electronic_notices/+page.server.ts | 67 - .../electronic_notices/+page.svelte | 240 - .../equivalencies/+page.server.ts | 72 - .../equivalencies/+page.svelte | 359 - .../error_catalogs/+page.server.ts | 61 - .../error_catalogs/+page.svelte | 240 - .../exchange-rate/+page.server.ts | 76 - .../exchange-rate/+page.svelte | 313 - .../exchange-rate/new/+page.svelte | 175 - .../identifiers/+page.server.ts | 74 - .../general_catalogs/identifiers/+page.svelte | 286 - .../general_catalogs/inpc/+page.server.ts | 73 - .../general_catalogs/inpc/+page.svelte | 287 - .../general_catalogs/legends/+page.server.ts | 70 - .../general_catalogs/legends/+page.svelte | 283 - .../general_catalogs/locations/+page.svelte | 5 - .../multi_currency_types/+page.server.ts | 58 - .../multi_currency_types/+page.svelte | 376 - .../general_catalogs/packages/+page.server.ts | 111 - .../general_catalogs/packages/+page.svelte | 237 - .../general_catalogs/ports/+page.server.ts | 83 - .../general_catalogs/ports/+page.svelte | 283 - .../prevalidators/+page.server.ts | 61 - .../prevalidators/+page.svelte | 322 - .../general_catalogs/seal/+page.server.ts | 84 - .../general_catalogs/seal/+page.svelte | 275 - .../general_catalogs/sectors/+page.svelte | 5 - .../signatures/+page.server.ts | 54 - .../general_catalogs/signatures/+page.svelte | 240 - .../tariff-fractions/+page.svelte | 0 .../tariff-fractions/american/+page.svelte | 6 - .../tariff-fractions/american/+page.ts | 7 - .../tariff-fractions/canadian/+page.svelte | 6 - .../tariff-fractions/historical/+page.svelte | 6 - .../seventh-amendment/+page.svelte | 13 - .../tariff-fractions/sitar/+page.svelte | 12 - .../tariff-fractions/us/+page.svelte | 6 - .../general_catalogs/trailers/+page.svelte | 264 - .../transporters/+page.svelte | 264 - .../unit_conversions/+page.server.ts | 75 - .../unit_conversions/+page.svelte | 293 - .../units_of_measure/ace/+page.server.ts | 65 - .../units_of_measure/ace/+page.svelte | 268 - .../units_of_measure/american/+page.server.ts | 57 - .../units_of_measure/american/+page.svelte | 268 - .../units_of_measure/customs/+page.server.ts | 57 - .../units_of_measure/customs/+page.svelte | 236 - .../units_of_measure/general/+page.server.ts | 68 - .../units_of_measure/general/+page.svelte | 351 - .../units_of_measure/oma/+page.server.ts | 57 - .../units_of_measure/oma/+page.svelte | 268 - .../general_catalogs/vehicles/+page.svelte | 266 - frontend/src/routes/dashboard/goods/+page.ts | 5 - .../dashboard/goods/fda-codes/+page.svelte | 472 - .../goods/fda-codes/edit/[id]/+page.svelte | 1071 - .../goods/fixed-asset-classes/+page.svelte | 998 - .../dashboard/goods/parts/+page.server.ts | 60 - .../routes/dashboard/goods/parts/+page.svelte | 523 - .../goods/parts/edit/[[id]]/+page.server.ts | 56 - .../goods/parts/edit/[[id]]/+page.svelte | 61 - .../goods/parts/extension/[[id]]/+page.svelte | 2252 -- .../routes/dashboard/help-center/+page.svelte | 293 - .../dashboard/help-center/[uuid]/+page.svelte | 277 - .../help-center/editor/[uuid]/+page.svelte | 533 - .../routes/dashboard/invoices/+page.server.ts | 139 - .../routes/dashboard/invoices/+page.svelte | 2677 -- .../invoices/edit/[id]/+page.server.ts | 172 - .../dashboard/invoices/edit/[id]/+page.svelte | 1316 - .../invoices/items/presets/+page.svelte | 542 - .../invoices/settings/+page.server.ts | 265 - .../dashboard/invoices/settings/+page.svelte | 866 - .../dashboard/pedimentos/+page.server.ts | 90 - .../routes/dashboard/pedimentos/+page.svelte | 711 - .../pedimentos/edit/[id]/+page.server.ts | 118 - .../pedimentos/edit/[id]/+page.svelte | 1345 - .../dashboard/reference_data/+page.svelte | 0 .../code_pedimento_regimens/+page.server.ts | 86 - .../code_pedimento_regimens/+page.svelte | 192 - .../reference_data/containers/+page.server.ts | 87 - .../reference_data/containers/+page.svelte | 172 - .../reference_data/countries/+page.server.ts | 87 - .../reference_data/countries/+page.svelte | 222 - .../currency_types/+page.server.ts | 87 - .../currency_types/+page.svelte | 208 - .../customs_sections/+page.server.ts | 88 - .../customs_sections/+page.svelte | 195 - .../customs_warehouses/+page.server.ts | 87 - .../customs_warehouses/+page.svelte | 183 - .../+page.server.ts | 98 - .../document_types_digitization/+page.svelte | 197 - .../reference_data/incoterms/+page.server.ts | 94 - .../reference_data/incoterms/+page.svelte | 174 - .../invoice_types/+page.server.ts | 87 - .../reference_data/invoice_types/+page.svelte | 185 - .../material_types/+page.server.ts | 86 - .../material_types/+page.svelte | 165 - .../payment_methods/+page.server.ts | 87 - .../payment_methods/+page.svelte | 163 - .../pedimento_codes/+page.server.ts | 87 - .../pedimento_codes/+page.svelte | 175 - .../pedimento_regimens/+page.server.ts | 88 - .../pedimento_regimens/+page.svelte | 194 - .../reference_data/sectors/+page.server.ts | 89 - .../reference_data/sectors/+page.svelte | 243 - .../reference_data/states/+page.server.ts | 90 - .../reference_data/states/+page.svelte | 186 - .../transport_modes/+page.server.ts | 92 - .../transport_modes/+page.svelte | 202 - .../transport_types/+page.server.ts | 90 - .../transport_types/+page.svelte | 163 - .../valuation_methods/+page.server.ts | 91 - .../valuation_methods/+page.svelte | 174 - .../reports/invoices/+page.server.ts | 15 - .../dashboard/reports/invoices/+page.svelte | 2951 -- .../reports/vencimiento/+page.server.ts | 15 - .../reports/vencimiento/+page.svelte | 313 - .../routes/dashboard/roles/+page.server.ts | 5 + .../src/routes/dashboard/roles/+page.svelte | 26 + .../dashboard/settings/general/+page.svelte | 226 +- frontend/src/routes/dashboard/tasks/+page.ts | 2 +- .../routes/dashboard/users/+page.server.ts | 3 +- .../src/routes/dashboard/users/+page.svelte | 3601 +- frontend/src/routes/join/+page.svelte | 2 +- frontend/src/routes/login/+page.server.ts | 58 +- frontend/src/routes/login/+page.svelte | 38 +- frontend/src/routes/logout/+server.ts | 8 +- reinicio.sh | 2 - scripts/auth-mode.sh | 214 + scripts/backend-entrypoint.sh | 65 - scripts/health-check.sh | 186 - scripts/init_first_time.sh | 584 - scripts/keycloak-entrypoint.sh | 50 - scripts/run_layout_import_janitor.sh | 8 - scripts/wait-for-jenkins-stack.sh | 34 - start.sh | 225 - 1979 files changed, 1085 insertions(+), 359149 deletions(-) delete mode 100644 backend/alembic/versions/a7b8c9d0e1f2_add_system_column_to_classes_and_parts.py delete mode 100644 backend/alembic/versions/b3c4d5e6f7a8_split_rfc_tax_id_clients_and_providers.py delete mode 100644 backend/alembic/versions/c8d9e0f1a2b3_add_stock_unit_of_measure_to_classes.py delete mode 100644 backend/alembic/versions/ca7d3c4e8b2a_add_transportation_id_sequences.py delete mode 100644 backend/alembic/versions/d2e3f4a5b6c7_seed_document_types_digitization.py delete mode 100644 backend/alembic/versions/e1f2a3b4c5d6_seed_sectors_for_existing_companies.py delete mode 100644 backend/alembic/versions/e4f5a6b7c8d9_add_incoterm_daf_legacy.py delete mode 100644 backend/alembic/versions/f1a2b3c4d5e6_isolate_equivalency_items.py delete mode 100644 backend/api/v1/modules/a24/balance_movements/models.py delete mode 100644 backend/api/v1/modules/a24/discharges/models.py delete mode 100644 backend/api/v1/modules/a24/fa/fa_classes/dto.py delete mode 100644 backend/api/v1/modules/a24/fa/fa_classes/models.py delete mode 100644 backend/api/v1/modules/a24/fa/fa_classes/routes.py delete mode 100644 backend/api/v1/modules/a24/fa/fa_classes/service.py delete mode 100644 backend/api/v1/modules/a24/fa/fa_item_lines/__init__.py delete mode 100644 backend/api/v1/modules/a24/fa/fa_item_lines/dto.py delete mode 100644 backend/api/v1/modules/a24/fa/fa_item_lines/models.py delete mode 100644 backend/api/v1/modules/a24/fa/fa_item_lines/routes.py delete mode 100644 backend/api/v1/modules/a24/fa/fa_item_lines/service.py delete mode 100644 backend/api/v1/modules/a24/fa/fa_parts/models.py delete mode 100644 backend/api/v1/modules/a24/inv/bom/models.py delete mode 100644 backend/api/v1/modules/a24/inv/inv_aphis/__init__.py delete mode 100644 backend/api/v1/modules/a24/inv/inv_aphis/dto.py delete mode 100644 backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_catalog/__init__.py delete mode 100644 backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_catalog/dto.py delete mode 100644 backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_catalog/models.py delete mode 100644 backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_catalog/router.py delete mode 100644 backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_characteristic/__init__.py delete mode 100644 backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_characteristic/dto.py delete mode 100644 backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_characteristic/models.py delete mode 100644 backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_containers/__init__.py delete mode 100644 backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_containers/dto.py delete mode 100644 backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_containers/models.py delete mode 100644 backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_entities/__init__.py delete mode 100644 backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_entities/dto.py delete mode 100644 backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_entities/models.py delete mode 100644 backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_general/__init__.py delete mode 100644 backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_general/dto.py delete mode 100644 backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_general/models.py delete mode 100644 backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_lpcos/__init__.py delete mode 100644 backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_lpcos/dto.py delete mode 100644 backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_lpcos/models.py delete mode 100644 backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_routing/__init__.py delete mode 100644 backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_routing/dto.py delete mode 100644 backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_routing/models.py delete mode 100644 backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_stype_pitems/__init__.py delete mode 100644 backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_stype_pitems/dto.py delete mode 100644 backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_stype_pitems/models.py delete mode 100644 backend/api/v1/modules/a24/inv/inv_aphis/models.py delete mode 100644 backend/api/v1/modules/a24/inv/inv_classes/models.py delete mode 100644 backend/api/v1/modules/a24/inv/inv_parts/models.py delete mode 100644 backend/api/v1/modules/a24/inv/part_countries/__init__.py delete mode 100644 backend/api/v1/modules/a24/inv/part_countries/dto.py delete mode 100644 backend/api/v1/modules/a24/inv/part_countries/models.py delete mode 100644 backend/api/v1/modules/a24/inv/part_countries/routes.py delete mode 100644 backend/api/v1/modules/a24/inv/part_countries/service.py delete mode 100644 backend/api/v1/modules/a24/router.py delete mode 100644 backend/api/v1/modules/a76/__init__.py delete mode 100644 backend/api/v1/modules/a76/app_settings/__init__.py delete mode 100644 backend/api/v1/modules/a76/app_settings/models.py delete mode 100644 backend/api/v1/modules/a76/app_settings/routes.py delete mode 100644 backend/api/v1/modules/a76/app_settings/schemas.py delete mode 100644 backend/api/v1/modules/a76/app_settings/service.py delete mode 100644 backend/api/v1/modules/a76/audit_log/events.py delete mode 100644 backend/api/v1/modules/a76/audit_log/middleware.py delete mode 100644 backend/api/v1/modules/a76/audit_log/models.py delete mode 100644 backend/api/v1/modules/a76/audit_log/register.py delete mode 100644 backend/api/v1/modules/a76/audit_log/router.py delete mode 100644 backend/api/v1/modules/a76/audit_log/schemas.py delete mode 100644 backend/api/v1/modules/a76/audit_log/services/core.py delete mode 100644 backend/api/v1/modules/a76/audit_log/services/service.py delete mode 100644 backend/api/v1/modules/a76/audit_log/utils/__init__.py delete mode 100644 backend/api/v1/modules/a76/audit_log/utils/serialization.py delete mode 100644 backend/api/v1/modules/a76/boms/__init__.py delete mode 100644 backend/api/v1/modules/a76/boms/routes.py delete mode 100644 backend/api/v1/modules/a76/classes/__init__.py delete mode 100644 backend/api/v1/modules/a76/classes/dto.py delete mode 100644 backend/api/v1/modules/a76/classes/models.py delete mode 100644 backend/api/v1/modules/a76/classes/routes.py delete mode 100644 backend/api/v1/modules/a76/classes/service.py delete mode 100644 backend/api/v1/modules/a76/classes/test_classes.py delete mode 100644 backend/api/v1/modules/a76/clients_and_providers/__init__.py delete mode 100644 backend/api/v1/modules/a76/clients_and_providers/dto.py delete mode 100644 backend/api/v1/modules/a76/clients_and_providers/models.py delete mode 100644 backend/api/v1/modules/a76/clients_and_providers/routes.py delete mode 100644 backend/api/v1/modules/a76/clients_and_providers/service.py delete mode 100644 backend/api/v1/modules/a76/clients_and_providers/validators.py delete mode 100644 backend/api/v1/modules/a76/common/sorting.py delete mode 100644 backend/api/v1/modules/a76/csv_templates/__init__.py delete mode 100644 backend/api/v1/modules/a76/csv_templates/registry.py delete mode 100644 backend/api/v1/modules/a76/csv_templates/routes.py delete mode 100644 backend/api/v1/modules/a76/customs_brokers/dto.py delete mode 100644 backend/api/v1/modules/a76/customs_brokers/models.py delete mode 100644 backend/api/v1/modules/a76/customs_brokers/routes.py delete mode 100644 backend/api/v1/modules/a76/customs_brokers/services.py delete mode 100644 backend/api/v1/modules/a76/doc_types_dig/__init__.py delete mode 100644 backend/api/v1/modules/a76/doc_types_dig/dto.py delete mode 100644 backend/api/v1/modules/a76/doc_types_dig/models.py delete mode 100644 backend/api/v1/modules/a76/doc_types_dig/routes.py delete mode 100644 backend/api/v1/modules/a76/doc_types_dig/seed.py delete mode 100644 backend/api/v1/modules/a76/doc_types_dig/service.py delete mode 100644 backend/api/v1/modules/a76/expediente_archivos/__init__.py delete mode 100644 backend/api/v1/modules/a76/expediente_archivos/dto.py delete mode 100644 backend/api/v1/modules/a76/expediente_archivos/external_service.py delete mode 100644 backend/api/v1/modules/a76/expediente_archivos/models.py delete mode 100644 backend/api/v1/modules/a76/expediente_archivos/routes.py delete mode 100644 backend/api/v1/modules/a76/expediente_archivos/service.py delete mode 100644 backend/api/v1/modules/a76/expediente_archivos/tasks.py delete mode 100644 backend/api/v1/modules/a76/factura_cove/external_service.py delete mode 100644 backend/api/v1/modules/a76/factura_cove/report_service.py delete mode 100644 backend/api/v1/modules/a76/factura_cove/routes.py delete mode 100644 backend/api/v1/modules/a76/factura_cove/schemas.py delete mode 100644 backend/api/v1/modules/a76/factura_cove/service.py delete mode 100644 backend/api/v1/modules/a76/factura_cove/tasks.py delete mode 100644 backend/api/v1/modules/a76/factura_cove/templates/FooterCOVE.png delete mode 100644 backend/api/v1/modules/a76/factura_cove/templates/HeaderCOVE.png delete mode 100644 backend/api/v1/modules/a76/factura_cove/templates/acuse_cove.html delete mode 100644 backend/api/v1/modules/a76/general_catalogs/classification_concepts/__init__.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/classification_concepts/dto.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/classification_concepts/models.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/classification_concepts/routes.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/classification_concepts/service.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/company/__init__.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/company/dto.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/company/models.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/company/routes.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/company/service.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/company/submodels/__init__.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/company/submodels/address.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/company/submodels/certification.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/company/submodels/cfdi.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/company/submodels/digital_certificate.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/company/submodels/electronic_agent.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/company/submodels/prevalidator.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/company/submodels/vu.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/company/test_company.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/concepts/__init__.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/concepts/dto.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/concepts/models.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/concepts/routes.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/concepts/service.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/customs_broker_concepts/__init__.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/customs_broker_concepts/dto.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/customs_broker_concepts/models.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/customs_broker_concepts/routes.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/customs_broker_concepts/service.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/depreciation_catalog/__init__.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/depreciation_catalog/dto.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/depreciation_catalog/models.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/depreciation_catalog/routes.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/depreciation_catalog/seed.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/depreciation_catalog/service.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/doda/__init__.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/doda/alta_log_dto.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/doda/alta_log_models.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/doda/alta_log_service.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/doda/alta_service.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/doda/dto.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/doda/export_service.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/doda/external_service.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/doda/fingerprint.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/doda/models.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/doda/payload_normalizer.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/doda/print_cache.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/doda/report_service.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/doda/routes.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/doda/service.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/doda/templates/doda_report.html delete mode 100644 backend/api/v1/modules/a76/general_catalogs/electronic_notices/__init__.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/electronic_notices/dto.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/electronic_notices/models.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/electronic_notices/routes.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/electronic_notices/service.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/equivalencies/__init__.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/equivalencies/dto.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/equivalencies/models.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/equivalencies/routes.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/equivalencies/service.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/error_catalogs/__init__.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/error_catalogs/dto.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/error_catalogs/models.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/error_catalogs/routes.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/error_catalogs/service.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/exchange_rate/dto.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/exchange_rate/models.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/exchange_rate/routes.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/exchange_rate/services.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/exchange_rate/test_exchange_rate.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/fda_catalog/__init__.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/fda_catalog/dto.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/fda_catalog/models.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/fda_catalog/routes.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/fda_catalog/seed.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/fda_catalog/service.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/fractions/canadian_tariff_fractions/__init__.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/fractions/canadian_tariff_fractions/models.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/fractions/canadian_tariff_fractions/routes.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/fractions/canadian_tariff_fractions/schemas.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/fractions/canadian_tariff_fractions/service.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/fractions/historical_tariff_fractions/dto.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/fractions/historical_tariff_fractions/models.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/fractions/historical_tariff_fractions/routes.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/fractions/historical_tariff_fractions/schemas.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/fractions/historical_tariff_fractions/seed.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/fractions/historical_tariff_fractions/service.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/fractions/previous_fractions/models.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/fractions/previous_fractions/seed.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/fractions/tariff_fractions/__init__.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/fractions/tariff_fractions/dto.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/fractions/tariff_fractions/models.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/fractions/tariff_fractions/routes.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/fractions/tariff_fractions/seed.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/fractions/tariff_fractions/service.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/fractions/us_tariff_fractions/__init__.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/fractions/us_tariff_fractions/dto.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/fractions/us_tariff_fractions/models.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/fractions/us_tariff_fractions/routes.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/fractions/us_tariff_fractions/seed.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/fractions/us_tariff_fractions/service.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/fractions/warning_fractions/models.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/fractions/warning_fractions/seed.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/identifiers/__init__.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/identifiers/dto.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/identifiers/models.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/identifiers/routes.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/identifiers/service.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/inpc/__init__.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/inpc/dto.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/inpc/models.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/inpc/routes.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/inpc/service.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/legends/__init__.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/legends/dto.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/legends/models.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/legends/routes.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/legends/service.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/location/__init__.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/location/dto.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/location/models.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/location/routes.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/location/service.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/locations/__init__.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/multi_currency_types/__init__.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/multi_currency_types/dto.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/multi_currency_types/models.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/multi_currency_types/routes.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/multi_currency_types/service.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/packages/dto.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/packages/models.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/packages/routes.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/packages/services.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/packages/test_package.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/ports/__init__.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/ports/dto.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/ports/models.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/ports/routes.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/ports/service.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/prevalidators/__init__.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/prevalidators/dto.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/prevalidators/models.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/prevalidators/routes.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/prevalidators/service.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/router.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/seal/dto.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/seal/models.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/seal/routes.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/seal/services.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/seal/test_seal.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/sectors/__init__.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/sectors/dto.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/sectors/models.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/sectors/routes.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/sectors/seed.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/sectors/service.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/signatures/__init__.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/signatures/dto.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/signatures/models.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/signatures/routes.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/signatures/service.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/unit_conversions/__init__.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/unit_conversions/dto.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/unit_conversions/models.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/unit_conversions/routes.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/unit_conversions/service.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/units_of_measure/__init__.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/units_of_measure/dto.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/units_of_measure/models.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/units_of_measure/routes.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/units_of_measure/seed.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/units_of_measure/seed_ace.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/units_of_measure/seed_adua.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/units_of_measure/seed_ame.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/units_of_measure/seed_oma.py delete mode 100644 backend/api/v1/modules/a76/general_catalogs/units_of_measure/service.py delete mode 100644 backend/api/v1/modules/a76/invoice_settings/dto.py delete mode 100644 backend/api/v1/modules/a76/invoice_settings/models.py delete mode 100644 backend/api/v1/modules/a76/invoice_settings/routes.py delete mode 100644 backend/api/v1/modules/a76/invoice_settings/services.py delete mode 100644 backend/api/v1/modules/a76/invoices/catalog_service.py delete mode 100644 backend/api/v1/modules/a76/invoices/common/calculations.py delete mode 100644 backend/api/v1/modules/a76/invoices/common/common_validators.py delete mode 100644 backend/api/v1/modules/a76/invoices/common/mappers.py delete mode 100644 backend/api/v1/modules/a76/invoices/common/process/review_equivalence.py delete mode 100644 backend/api/v1/modules/a76/invoices/common/process/review_uma.py delete mode 100644 backend/api/v1/modules/a76/invoices/docs/README.MD delete mode 100644 backend/api/v1/modules/a76/invoices/exports/docs/REPO_exports_casos_de_uso.md delete mode 100644 backend/api/v1/modules/a76/invoices/exports/docs/REPO_exports_diagramas.md delete mode 100644 backend/api/v1/modules/a76/invoices/exports/process/main_process.py delete mode 100644 backend/api/v1/modules/a76/invoices/exports/process/pre_validators.py delete mode 100644 backend/api/v1/modules/a76/invoices/exports/process/routes.py delete mode 100644 backend/api/v1/modules/a76/invoices/exports/process/sub_process/__init__.py delete mode 100644 backend/api/v1/modules/a76/invoices/exports/process/sub_process/assign_no_discharges.py delete mode 100644 backend/api/v1/modules/a76/invoices/exports/process/sub_process/assign_values.py delete mode 100644 backend/api/v1/modules/a76/invoices/exports/process/sub_process/compare_balances.py delete mode 100644 backend/api/v1/modules/a76/invoices/exports/process/sub_process/discharge_types.py delete mode 100644 backend/api/v1/modules/a76/invoices/exports/process/sub_process/download_balance_collector.py delete mode 100644 backend/api/v1/modules/a76/invoices/exports/process/sub_process/fill_available_balances.py delete mode 100644 backend/api/v1/modules/a76/invoices/exports/process/sub_process/finalize_invoice.py delete mode 100644 backend/api/v1/modules/a76/invoices/exports/process/sub_process/generate_definitive_import.py delete mode 100644 backend/api/v1/modules/a76/invoices/exports/process/sub_process/register_discharge_ledger.py delete mode 100644 backend/api/v1/modules/a76/invoices/exports/process/sub_process/register_discharge_series.py delete mode 100644 backend/api/v1/modules/a76/invoices/exports/process/sub_process/register_import_discharge.py delete mode 100644 backend/api/v1/modules/a76/invoices/exports/process/sub_process/review_class.py delete mode 100644 backend/api/v1/modules/a76/invoices/exports/process/sub_process/review_exchange_rate.py delete mode 100644 backend/api/v1/modules/a76/invoices/exports/process/sub_process/review_limits.py delete mode 100644 backend/api/v1/modules/a76/invoices/exports/process/sub_process/review_origin_procedure.py delete mode 100644 backend/api/v1/modules/a76/invoices/exports/process/sub_process/review_qty_vs_weight.py delete mode 100644 backend/api/v1/modules/a76/invoices/exports/process/sub_process/review_unit_cost.py delete mode 100644 backend/api/v1/modules/a76/invoices/exports/process/sub_process/series/__init__.py delete mode 100644 backend/api/v1/modules/a76/invoices/exports/process/sub_process/series/review_qty_series.py delete mode 100644 backend/api/v1/modules/a76/invoices/exports/process/sub_process/series/review_series_exist.py delete mode 100644 backend/api/v1/modules/a76/invoices/exports/process/sub_process/series/review_series_other_lines.py delete mode 100644 backend/api/v1/modules/a76/invoices/exports/process/sub_process/verify_consolidated.py delete mode 100644 backend/api/v1/modules/a76/invoices/exports/process/task.py delete mode 100644 backend/api/v1/modules/a76/invoices/exports/revert/main_process.py delete mode 100644 backend/api/v1/modules/a76/invoices/exports/revert/pre_validators.py delete mode 100644 backend/api/v1/modules/a76/invoices/exports/revert/routes.py delete mode 100644 backend/api/v1/modules/a76/invoices/exports/revert/task.py delete mode 100644 backend/api/v1/modules/a76/invoices/exports/validators/create.py delete mode 100644 backend/api/v1/modules/a76/invoices/exports/validators/update.py delete mode 100644 backend/api/v1/modules/a76/invoices/imports/balance/__init__.py delete mode 100644 backend/api/v1/modules/a76/invoices/imports/balance/create_balance_entries.py delete mode 100644 backend/api/v1/modules/a76/invoices/imports/balance/void_balance_entries.py delete mode 100644 backend/api/v1/modules/a76/invoices/imports/docs/REPO_imports_casos_de_uso.md delete mode 100644 backend/api/v1/modules/a76/invoices/imports/docs/REPO_imports_diagramas.md delete mode 100644 backend/api/v1/modules/a76/invoices/imports/process/main_process.py delete mode 100644 backend/api/v1/modules/a76/invoices/imports/process/pre_validators.py delete mode 100644 backend/api/v1/modules/a76/invoices/imports/process/routes.py delete mode 100644 backend/api/v1/modules/a76/invoices/imports/process/sub_process/assing_values.py delete mode 100644 backend/api/v1/modules/a76/invoices/imports/process/sub_process/assing_values_def_mex.py delete mode 100644 backend/api/v1/modules/a76/invoices/imports/process/sub_process/review_classes.py delete mode 100644 backend/api/v1/modules/a76/invoices/imports/process/sub_process/review_exchange_rate.py delete mode 100644 backend/api/v1/modules/a76/invoices/imports/process/sub_process/review_limits.py delete mode 100644 backend/api/v1/modules/a76/invoices/imports/process/sub_process/review_rule_octave.py delete mode 100644 backend/api/v1/modules/a76/invoices/imports/process/sub_process/review_series.py delete mode 100644 backend/api/v1/modules/a76/invoices/imports/process/sub_process/review_weights.py delete mode 100644 backend/api/v1/modules/a76/invoices/imports/process/task.py delete mode 100644 backend/api/v1/modules/a76/invoices/imports/revert/main_process.py delete mode 100644 backend/api/v1/modules/a76/invoices/imports/revert/pre_validators.py delete mode 100644 backend/api/v1/modules/a76/invoices/imports/revert/routes.py delete mode 100644 backend/api/v1/modules/a76/invoices/imports/revert/sub_process/review_rule_octave.py delete mode 100644 backend/api/v1/modules/a76/invoices/imports/revert/task.py delete mode 100644 backend/api/v1/modules/a76/invoices/imports/validators/create.py delete mode 100644 backend/api/v1/modules/a76/invoices/imports/validators/update.py delete mode 100644 backend/api/v1/modules/a76/invoices/models.py delete mode 100644 backend/api/v1/modules/a76/invoices/permission_map.py delete mode 100644 backend/api/v1/modules/a76/invoices/routes.py delete mode 100644 backend/api/v1/modules/a76/invoices/schemas.py delete mode 100644 backend/api/v1/modules/a76/invoices/services.py delete mode 100644 backend/api/v1/modules/a76/item_presets/dto.py delete mode 100644 backend/api/v1/modules/a76/item_presets/models.py delete mode 100644 backend/api/v1/modules/a76/item_presets/routes.py delete mode 100644 backend/api/v1/modules/a76/item_presets/services.py delete mode 100644 backend/api/v1/modules/a76/items/__init__.py delete mode 100644 backend/api/v1/modules/a76/items/common/common_validators.py delete mode 100644 backend/api/v1/modules/a76/items/common/fractions.py delete mode 100644 backend/api/v1/modules/a76/items/exports/validators/calculations.py delete mode 100644 backend/api/v1/modules/a76/items/exports/validators/common.py delete mode 100644 backend/api/v1/modules/a76/items/exports/validators/create.py delete mode 100644 backend/api/v1/modules/a76/items/exports/validators/update.py delete mode 100644 backend/api/v1/modules/a76/items/imports/validators/calculations.py delete mode 100644 backend/api/v1/modules/a76/items/imports/validators/common.py delete mode 100644 backend/api/v1/modules/a76/items/imports/validators/create.py delete mode 100644 backend/api/v1/modules/a76/items/imports/validators/update.py delete mode 100644 backend/api/v1/modules/a76/items/line_customs/__init__.py delete mode 100644 backend/api/v1/modules/a76/items/line_customs/models.py delete mode 100644 backend/api/v1/modules/a76/items/line_customs/schemas.py delete mode 100644 backend/api/v1/modules/a76/items/line_descriptions/__init__.py delete mode 100644 backend/api/v1/modules/a76/items/line_descriptions/models.py delete mode 100644 backend/api/v1/modules/a76/items/line_descriptions/schemas.py delete mode 100644 backend/api/v1/modules/a76/items/line_financials/__init__.py delete mode 100644 backend/api/v1/modules/a76/items/line_financials/models.py delete mode 100644 backend/api/v1/modules/a76/items/line_financials/schemas.py delete mode 100644 backend/api/v1/modules/a76/items/line_quantities/__init__.py delete mode 100644 backend/api/v1/modules/a76/items/line_quantities/models.py delete mode 100644 backend/api/v1/modules/a76/items/line_quantities/schemas.py delete mode 100644 backend/api/v1/modules/a76/items/line_references/__init__.py delete mode 100644 backend/api/v1/modules/a76/items/line_references/models.py delete mode 100644 backend/api/v1/modules/a76/items/line_references/schemas.py delete mode 100644 backend/api/v1/modules/a76/items/models.py delete mode 100644 backend/api/v1/modules/a76/items/routes.py delete mode 100644 backend/api/v1/modules/a76/items/schemas.py delete mode 100644 backend/api/v1/modules/a76/items/series/models.py delete mode 100644 backend/api/v1/modules/a76/items/series/schemas.py delete mode 100644 backend/api/v1/modules/a76/items/service.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/__init__.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/boms/__init__.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/boms/common/__init__.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/boms/common/common_validators.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/boms/common/fk_loader.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/boms/common/mappers.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/boms/csv_max_chars_for_headers.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/boms/routes.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/boms/schemas.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/boms/tasks.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/boms/template_config.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/boms/validators/__init__.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/boms/validators/common.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/boms/validators/create.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/cambio_regimen_regularizacion/__init__.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/cambio_regimen_regularizacion/routes.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/cambio_regimen_regularizacion/schemas.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/cambio_regimen_regularizacion/tasks.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/cambio_regimen_regularizacion/template_config.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/classes/__init__.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/classes/common/__init__.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/classes/common/common_validators.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/classes/common/fk_loader.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/classes/common/mappers.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/classes/csv_max_chars_for_headers.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/classes/routes.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/classes/schemas.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/classes/tasks.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/classes/template_config.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/classes/validators/__init__.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/classes/validators/common.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/classes/validators/create.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/clients_and_providers/__init__.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/clients_and_providers/common/__init__.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/clients_and_providers/common/common_validators.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/clients_and_providers/common/csv_max_chars_for_headers.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/clients_and_providers/common/mappers.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/clients_and_providers/routes.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/clients_and_providers/schemas.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/clients_and_providers/tasks.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/clients_and_providers/template_config.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/clients_and_providers/validators/__init__.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/clients_and_providers/validators/common.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/clients_and_providers/validators/create.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/common/__init__.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/common/cell_value.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/common/csv_headers_en.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/common/csv_hint_inject.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/common/csv_reader.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/common/error_csv.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/common/import_redis_keys.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/common/inject_csv_labels_en.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/common/meta.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/common/normalize.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/common/responses.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/common/storage.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/common/template_locale.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/common/track_commit_dispatch.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/common/victor.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/customs_brokers/__init__.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/customs_brokers/common/__init__.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/customs_brokers/common/common_validators.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/customs_brokers/common/fk_loader.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/customs_brokers/common/mappers.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/customs_brokers/csv_max_chars_for_headers.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/customs_brokers/routes.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/customs_brokers/schemas.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/customs_brokers/tasks.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/customs_brokers/template_config.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/customs_brokers/validators/__init__.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/customs_brokers/validators/common.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/customs_brokers/validators/create.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/drivers/__init__.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/drivers/common/__init__.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/drivers/common/common_validators.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/drivers/common/fk_loader.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/drivers/common/mappers.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/drivers/csv_max_chars_for_headers.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/drivers/routes.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/drivers/schemas.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/drivers/tasks.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/drivers/template_config.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/drivers/validators/__init__.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/drivers/validators/common.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/drivers/validators/create.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/exchange_rate/__init__.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/exchange_rate/common/__init__.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/exchange_rate/common/common_validators.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/exchange_rate/common/mappers.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/exchange_rate/csv_max_chars_for_headers.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/exchange_rate/routes.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/exchange_rate/schemas.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/exchange_rate/tasks.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/exchange_rate/template_config.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/exchange_rate/validators/__init__.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/exchange_rate/validators/common.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/exchange_rate/validators/create.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/exportacion/__init__.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/exportacion/routes.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/exportacion/schemas.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/exportacion/tasks.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/exportacion/template_config.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/facturas/__init__.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/facturas/invoice_csv_column_hints.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/facturas/line_item_enrichment/__init__.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/facturas/line_item_enrichment/export_enrichment.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/facturas/line_item_enrichment/import_enrichment.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/facturas/routes.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/facturas/schemas.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/facturas/tasks.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/facturas/template_config.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/facturas/validators/__init__.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/facturas/validators/consolidated_invoice_dates.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/facturas/validators/encabezados_cmex.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/facturas/validators/encabezados_expo.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/facturas/validators/encabezados_impo_def.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/facturas/validators/encabezados_impo_temp.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/facturas/validators/partidas_expo.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/facturas/validators/partidas_impo_def.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/facturas/validators/partidas_impo_temp.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/facturas/validators/pedimento_resolution.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/facturas/validators/series_expo.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/facturas/validators/series_impo_def.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/facturas/validators/series_impo_temp.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/facturas/validators/transport_catalog.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/parts/__init__.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/parts/common/__init__.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/parts/common/common_validators.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/parts/common/fk_loader.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/parts/common/mappers.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/parts/csv_max_chars_for_headers.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/parts/routes.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/parts/schemas.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/parts/tasks.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/parts/template_config.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/parts/validators/__init__.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/parts/validators/common.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/parts/validators/create.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/pedmientos/__init__.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/pedmientos/common/__init__.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/pedmientos/common/common_validators.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/pedmientos/common/fk_loader.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/pedmientos/common/mappers.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/pedmientos/pedimentos_csv_hints.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/pedmientos/routes.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/pedmientos/schemas.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/pedmientos/tasks.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/pedmientos/template_config.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/pedmientos/validators/__init__.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/pedmientos/validators/common.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/pedmientos/validators/create.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/trailers/__init__.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/trailers/common/__init__.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/trailers/common/common_validators.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/trailers/common/fk_loader.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/trailers/common/mappers.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/trailers/csv_max_chars_for_headers.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/trailers/routes.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/trailers/schemas.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/trailers/tasks.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/trailers/template_config.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/trailers/validators/__init__.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/trailers/validators/common.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/trailers/validators/create.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/transportistas/__init__.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/transportistas/common/__init__.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/transportistas/common/common_validators.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/transportistas/common/fk_loader.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/transportistas/common/mappers.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/transportistas/csv_max_chars_for_headers.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/transportistas/routes.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/transportistas/schemas.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/transportistas/tasks.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/transportistas/template_config.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/transportistas/validators/__init__.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/transportistas/validators/common.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/transportistas/validators/create.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/us_tariff_fractions/__init__.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/us_tariff_fractions/common/__init__.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/us_tariff_fractions/common/common_validators.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/us_tariff_fractions/common/fk_loader.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/us_tariff_fractions/common/mappers.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/us_tariff_fractions/csv_max_chars_for_headers.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/us_tariff_fractions/routes.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/us_tariff_fractions/schemas.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/us_tariff_fractions/tasks.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/us_tariff_fractions/template_config.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/us_tariff_fractions/validators/__init__.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/us_tariff_fractions/validators/common.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/us_tariff_fractions/validators/create.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/vehicles/__init__.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/vehicles/common/__init__.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/vehicles/common/common_validators.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/vehicles/common/fk_loader.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/vehicles/common/mappers.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/vehicles/csv_max_chars_for_headers.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/vehicles/routes.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/vehicles/schemas.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/vehicles/tasks.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/vehicles/template_config.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/vehicles/validators/__init__.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/vehicles/validators/common.py delete mode 100644 backend/api/v1/modules/a76/layouts_csv/vehicles/validators/create.py delete mode 100644 backend/api/v1/modules/a76/manifests/__init__.py delete mode 100644 backend/api/v1/modules/a76/manifests/concept_manifestation/__init__.py delete mode 100644 backend/api/v1/modules/a76/manifests/concept_manifestation/dtos.py delete mode 100644 backend/api/v1/modules/a76/manifests/concept_manifestation/models.py delete mode 100644 backend/api/v1/modules/a76/manifests/concept_manifestation/routes.py delete mode 100644 backend/api/v1/modules/a76/manifests/concept_manifestation/service.py delete mode 100644 backend/api/v1/modules/a76/manifests/driver/__init__.py delete mode 100644 backend/api/v1/modules/a76/manifests/driver/dtos.py delete mode 100644 backend/api/v1/modules/a76/manifests/driver/models.py delete mode 100644 backend/api/v1/modules/a76/manifests/driver/routes.py delete mode 100644 backend/api/v1/modules/a76/manifests/driver/service.py delete mode 100644 backend/api/v1/modules/a76/manifests/manifest/debug_db.py delete mode 100644 backend/api/v1/modules/a76/manifests/manifest/dtos.py delete mode 100644 backend/api/v1/modules/a76/manifests/manifest/models.py delete mode 100644 backend/api/v1/modules/a76/manifests/manifest/routes.py delete mode 100644 backend/api/v1/modules/a76/manifests/manifest/service.py delete mode 100644 backend/api/v1/modules/a76/manifests/manifiesto_anexo/__init__.py delete mode 100644 backend/api/v1/modules/a76/manifests/manifiesto_anexo/dtos.py delete mode 100644 backend/api/v1/modules/a76/manifests/manifiesto_anexo/models.py delete mode 100644 backend/api/v1/modules/a76/manifests/manifiesto_anexo/routes.py delete mode 100644 backend/api/v1/modules/a76/manifests/manifiesto_anexo/service.py delete mode 100644 backend/api/v1/modules/a76/manifests/value_manifestation/__init__.py delete mode 100644 backend/api/v1/modules/a76/manifests/value_manifestation/dtos.py delete mode 100644 backend/api/v1/modules/a76/manifests/value_manifestation/models.py delete mode 100644 backend/api/v1/modules/a76/manifests/value_manifestation/service.py delete mode 100644 backend/api/v1/modules/a76/parts/__init__.py delete mode 100644 backend/api/v1/modules/a76/parts/dto.py delete mode 100644 backend/api/v1/modules/a76/parts/models.py delete mode 100644 backend/api/v1/modules/a76/parts/routes.py delete mode 100644 backend/api/v1/modules/a76/parts/service.py delete mode 100644 backend/api/v1/modules/a76/parts/test_parts.py delete mode 100644 backend/api/v1/modules/a76/pedmientos/catalog_service.py delete mode 100644 backend/api/v1/modules/a76/pedmientos/dtos/pedimento_config_additional.py delete mode 100644 backend/api/v1/modules/a76/pedmientos/dtos/pedimento_config_calculations.py delete mode 100644 backend/api/v1/modules/a76/pedmientos/dtos/pedimento_config_parameters.py delete mode 100644 backend/api/v1/modules/a76/pedmientos/dtos/pedimento_config_surcharges.py delete mode 100644 backend/api/v1/modules/a76/pedmientos/dtos/pedimento_config_update_rectification.py delete mode 100644 backend/api/v1/modules/a76/pedmientos/dtos/pedimento_config_updates.py delete mode 100644 backend/api/v1/modules/a76/pedmientos/dtos/pedimento_contributions.py delete mode 100644 backend/api/v1/modules/a76/pedmientos/dtos/pedimento_customs_offices.py delete mode 100644 backend/api/v1/modules/a76/pedmientos/dtos/pedimento_dates.py delete mode 100644 backend/api/v1/modules/a76/pedmientos/dtos/pedimento_decrementables.py delete mode 100644 backend/api/v1/modules/a76/pedmientos/dtos/pedimento_incrementables.py delete mode 100644 backend/api/v1/modules/a76/pedmientos/dtos/pedimento_indexes.py delete mode 100644 backend/api/v1/modules/a76/pedmientos/dtos/pedimento_packages_transport.py delete mode 100644 backend/api/v1/modules/a76/pedmientos/dtos/pedimento_payments.py delete mode 100644 backend/api/v1/modules/a76/pedmientos/dtos/pedimento_rectification_destination.py delete mode 100644 backend/api/v1/modules/a76/pedmientos/dtos/pedimento_rectification_origin.py delete mode 100644 backend/api/v1/modules/a76/pedmientos/dtos/pedimento_transport_means.py delete mode 100644 backend/api/v1/modules/a76/pedmientos/dtos/pedimento_validation.py delete mode 100644 backend/api/v1/modules/a76/pedmientos/dtos/pedimentos.py delete mode 100644 backend/api/v1/modules/a76/pedmientos/models/__init__.py delete mode 100644 backend/api/v1/modules/a76/pedmientos/models/pedimento_config_additional.py delete mode 100644 backend/api/v1/modules/a76/pedmientos/models/pedimento_config_calculations.py delete mode 100644 backend/api/v1/modules/a76/pedmientos/models/pedimento_config_parameters.py delete mode 100644 backend/api/v1/modules/a76/pedmientos/models/pedimento_config_surcharges.py delete mode 100644 backend/api/v1/modules/a76/pedmientos/models/pedimento_config_update_rectification.py delete mode 100644 backend/api/v1/modules/a76/pedmientos/models/pedimento_config_updates.py delete mode 100644 backend/api/v1/modules/a76/pedmientos/models/pedimento_containers.py delete mode 100644 backend/api/v1/modules/a76/pedmientos/models/pedimento_contributions.py delete mode 100644 backend/api/v1/modules/a76/pedmientos/models/pedimento_customs_offices.py delete mode 100644 backend/api/v1/modules/a76/pedmientos/models/pedimento_dates.py delete mode 100644 backend/api/v1/modules/a76/pedmientos/models/pedimento_decrementables.py delete mode 100644 backend/api/v1/modules/a76/pedmientos/models/pedimento_guides.py delete mode 100644 backend/api/v1/modules/a76/pedmientos/models/pedimento_incrementables.py delete mode 100644 backend/api/v1/modules/a76/pedmientos/models/pedimento_indexes.py delete mode 100644 backend/api/v1/modules/a76/pedmientos/models/pedimento_packages.py delete mode 100644 backend/api/v1/modules/a76/pedmientos/models/pedimento_payments.py delete mode 100644 backend/api/v1/modules/a76/pedmientos/models/pedimento_rectification_destination.py delete mode 100644 backend/api/v1/modules/a76/pedmientos/models/pedimento_rectification_origin.py delete mode 100644 backend/api/v1/modules/a76/pedmientos/models/pedimento_seals.py delete mode 100644 backend/api/v1/modules/a76/pedmientos/models/pedimento_transport_carriers.py delete mode 100644 backend/api/v1/modules/a76/pedmientos/models/pedimento_transport_means.py delete mode 100644 backend/api/v1/modules/a76/pedmientos/models/pedimento_validation.py delete mode 100644 backend/api/v1/modules/a76/pedmientos/models/pedimentos.py delete mode 100644 backend/api/v1/modules/a76/pedmientos/router.py delete mode 100644 backend/api/v1/modules/a76/pedmientos/routes/pedimento_config_additional.py delete mode 100644 backend/api/v1/modules/a76/pedmientos/routes/pedimento_config_calculations.py delete mode 100644 backend/api/v1/modules/a76/pedmientos/routes/pedimento_config_parameters.py delete mode 100644 backend/api/v1/modules/a76/pedmientos/routes/pedimento_config_surcharges.py delete mode 100644 backend/api/v1/modules/a76/pedmientos/routes/pedimento_config_update_rectification.py delete mode 100644 backend/api/v1/modules/a76/pedmientos/routes/pedimento_config_updates.py delete mode 100644 backend/api/v1/modules/a76/pedmientos/routes/pedimento_customs_offices.py delete mode 100644 backend/api/v1/modules/a76/pedmientos/routes/pedimento_dates.py delete mode 100644 backend/api/v1/modules/a76/pedmientos/routes/pedimento_decrementables.py delete mode 100644 backend/api/v1/modules/a76/pedmientos/routes/pedimento_incrementables.py delete mode 100644 backend/api/v1/modules/a76/pedmientos/routes/pedimento_indexes.py delete mode 100644 backend/api/v1/modules/a76/pedmientos/routes/pedimento_payments.py delete mode 100644 backend/api/v1/modules/a76/pedmientos/routes/pedimento_rectification_destination.py delete mode 100644 backend/api/v1/modules/a76/pedmientos/routes/pedimento_rectification_origin.py delete mode 100644 backend/api/v1/modules/a76/pedmientos/routes/pedimento_transport_means.py delete mode 100644 backend/api/v1/modules/a76/pedmientos/routes/pedimento_validation.py delete mode 100644 backend/api/v1/modules/a76/pedmientos/routes/pedimentos.py delete mode 100644 backend/api/v1/modules/a76/pedmientos/schemas.py delete mode 100644 backend/api/v1/modules/a76/pedmientos/services/pedimento_config_additional.py delete mode 100644 backend/api/v1/modules/a76/pedmientos/services/pedimento_config_calculations.py delete mode 100644 backend/api/v1/modules/a76/pedmientos/services/pedimento_config_parameters.py delete mode 100644 backend/api/v1/modules/a76/pedmientos/services/pedimento_config_surcharges.py delete mode 100644 backend/api/v1/modules/a76/pedmientos/services/pedimento_config_update_rectification.py delete mode 100644 backend/api/v1/modules/a76/pedmientos/services/pedimento_config_updates.py delete mode 100644 backend/api/v1/modules/a76/pedmientos/services/pedimento_customs_offices.py delete mode 100644 backend/api/v1/modules/a76/pedmientos/services/pedimento_dates.py delete mode 100644 backend/api/v1/modules/a76/pedmientos/services/pedimento_decrementables.py delete mode 100644 backend/api/v1/modules/a76/pedmientos/services/pedimento_incrementables.py delete mode 100644 backend/api/v1/modules/a76/pedmientos/services/pedimento_indexes.py delete mode 100644 backend/api/v1/modules/a76/pedmientos/services/pedimento_payments.py delete mode 100644 backend/api/v1/modules/a76/pedmientos/services/pedimento_rectification_destination.py delete mode 100644 backend/api/v1/modules/a76/pedmientos/services/pedimento_rectification_origin.py delete mode 100644 backend/api/v1/modules/a76/pedmientos/services/pedimento_transport_means.py delete mode 100644 backend/api/v1/modules/a76/pedmientos/services/pedimento_validation.py delete mode 100644 backend/api/v1/modules/a76/pedmientos/services/pedimentos.py delete mode 100644 backend/api/v1/modules/a76/permission_rule_oct/dto.py delete mode 100644 backend/api/v1/modules/a76/permission_rule_oct/models.py delete mode 100644 backend/api/v1/modules/a76/permission_rule_oct/routes.py delete mode 100644 backend/api/v1/modules/a76/permission_rule_oct/services.py delete mode 100644 backend/api/v1/modules/a76/permission_rule_oct/test_permission_rule_oct.py delete mode 100644 backend/api/v1/modules/a76/reports/exportacion/__init__.py delete mode 100644 backend/api/v1/modules/a76/reports/exportacion/aviso_consolidado/__init__.py delete mode 100644 backend/api/v1/modules/a76/reports/exportacion/aviso_consolidado/routes.py delete mode 100644 backend/api/v1/modules/a76/reports/exportacion/aviso_consolidado/service.py delete mode 100644 backend/api/v1/modules/a76/reports/exportacion/aviso_consolidado/task.py delete mode 100644 backend/api/v1/modules/a76/reports/exportacion/aviso_consolidado/templates/avcon_exp.html delete mode 100644 backend/api/v1/modules/a76/reports/exportacion/descargo/routes.py delete mode 100644 backend/api/v1/modules/a76/reports/exportacion/descargo/service.py delete mode 100644 backend/api/v1/modules/a76/reports/exportacion/descargo/task.py delete mode 100644 backend/api/v1/modules/a76/reports/exportacion/descargo/templates/descarga.html delete mode 100644 backend/api/v1/modules/a76/reports/exportacion/partes_descargadas/__init__.py delete mode 100644 backend/api/v1/modules/a76/reports/exportacion/partes_descargadas/routes.py delete mode 100644 backend/api/v1/modules/a76/reports/exportacion/partes_descargadas/schemas.py delete mode 100644 backend/api/v1/modules/a76/reports/exportacion/partes_descargadas/service.py delete mode 100644 backend/api/v1/modules/a76/reports/exportacion/transmission/MAINX30/__init__.py delete mode 100644 backend/api/v1/modules/a76/reports/exportacion/transmission/MAINX30/processors.py delete mode 100644 backend/api/v1/modules/a76/reports/exportacion/transmission/MAINX30/routes.py delete mode 100644 backend/api/v1/modules/a76/reports/exportacion/transmission/MAINX30/schemas.py delete mode 100644 backend/api/v1/modules/a76/reports/exportacion/transmission/MAINX30/service.py delete mode 100644 backend/api/v1/modules/a76/reports/exportacion/transmission/MAINX30/task.py delete mode 100644 backend/api/v1/modules/a76/reports/importacion/consolidados/mex/schemas.py delete mode 100644 backend/api/v1/modules/a76/reports/importacion/consolidados/mex/service.py delete mode 100644 backend/api/v1/modules/a76/reports/importacion/consolidados/routes.py delete mode 100644 backend/api/v1/modules/a76/reports/importacion/consolidados/task.py delete mode 100644 backend/api/v1/modules/a76/reports/importacion/consolidados/templates/cons_mex_ver.html delete mode 100644 backend/api/v1/modules/a76/reports/importacion/consolidados/temporary/mex/service.py delete mode 100644 backend/api/v1/modules/a76/reports/importacion/facturas/mex/schemas.py delete mode 100644 backend/api/v1/modules/a76/reports/importacion/facturas/mex/service.py delete mode 100644 backend/api/v1/modules/a76/reports/importacion/facturas/routes.py delete mode 100644 backend/api/v1/modules/a76/reports/importacion/facturas/task.py delete mode 100644 backend/api/v1/modules/a76/reports/importacion/facturas/templates/factura_mex_ver.html delete mode 100644 backend/api/v1/modules/a76/reports/importacion/facturas/templates/factura_usa_ver.html delete mode 100644 backend/api/v1/modules/a76/reports/importacion/facturas/temporary/__init__.py delete mode 100644 backend/api/v1/modules/a76/reports/importacion/facturas/temporary/mex/service.py delete mode 100644 backend/api/v1/modules/a76/reports/importacion/facturas/usa/service.py delete mode 100644 backend/api/v1/modules/a76/reports/importacion/packing_list/routes.py delete mode 100644 backend/api/v1/modules/a76/reports/importacion/packing_list/schemas.py delete mode 100644 backend/api/v1/modules/a76/reports/importacion/packing_list/service.py delete mode 100644 backend/api/v1/modules/a76/reports/importacion/packing_list/task.py delete mode 100644 backend/api/v1/modules/a76/reports/importacion/packing_list/templates/packing_list.html delete mode 100644 backend/api/v1/modules/a76/reports/importacion/transmission/definitive/MAINX30/processors.py delete mode 100644 backend/api/v1/modules/a76/reports/importacion/transmission/definitive/MAINX30/routes.py delete mode 100644 backend/api/v1/modules/a76/reports/importacion/transmission/definitive/MAINX30/schemas.py delete mode 100644 backend/api/v1/modules/a76/reports/importacion/transmission/definitive/MAINX30/service.py delete mode 100644 backend/api/v1/modules/a76/reports/importacion/transmission/definitive/MAINX30/task.py delete mode 100644 backend/api/v1/modules/a76/reports/importacion/transmission/temporal/MAINX30/processors.py delete mode 100644 backend/api/v1/modules/a76/reports/importacion/transmission/temporal/MAINX30/routes.py delete mode 100644 backend/api/v1/modules/a76/reports/importacion/transmission/temporal/MAINX30/schemas.py delete mode 100644 backend/api/v1/modules/a76/reports/importacion/transmission/temporal/MAINX30/service.py delete mode 100644 backend/api/v1/modules/a76/reports/importacion/transmission/temporal/MAINX30/task.py delete mode 100644 backend/api/v1/modules/a76/reports/importacion/winsaai/__init__.py delete mode 100644 backend/api/v1/modules/a76/reports/importacion/winsaai/invoices/__init__.py delete mode 100644 backend/api/v1/modules/a76/reports/importacion/winsaai/invoices/routes.py delete mode 100644 backend/api/v1/modules/a76/reports/importacion/winsaai/invoices/schemas.py delete mode 100644 backend/api/v1/modules/a76/reports/importacion/winsaai/invoices/service.py delete mode 100644 backend/api/v1/modules/a76/reports/importacion/winsaai/invoices/task.py delete mode 100644 backend/api/v1/modules/a76/reports/importacion/winsaai/pedimentos/__init__.py delete mode 100644 backend/api/v1/modules/a76/reports/importacion/winsaai/pedimentos/routes.py delete mode 100644 backend/api/v1/modules/a76/reports/importacion/winsaai/pedimentos/schemas.py delete mode 100644 backend/api/v1/modules/a76/reports/importacion/winsaai/pedimentos/service.py delete mode 100644 backend/api/v1/modules/a76/reports/importacion/winsaai/pedimentos/task.py delete mode 100644 backend/api/v1/modules/a76/reports/importacion/winsaai/router.py delete mode 100644 backend/api/v1/modules/a76/reports/movements/__init__.py delete mode 100644 backend/api/v1/modules/a76/reports/movements/invoices/__init__.py delete mode 100644 backend/api/v1/modules/a76/reports/movements/invoices/csv_utils.py delete mode 100644 backend/api/v1/modules/a76/reports/movements/invoices/movement_service.py delete mode 100644 backend/api/v1/modules/a76/reports/movements/invoices/routes.py delete mode 100644 backend/api/v1/modules/a76/reports/movements/invoices/schemas.py delete mode 100644 backend/api/v1/modules/a76/reports/movements/invoices/services.py delete mode 100644 backend/api/v1/modules/a76/reports/movements/invoices/services/__init__.py delete mode 100644 backend/api/v1/modules/a76/reports/movements/invoices/services/base.py delete mode 100644 backend/api/v1/modules/a76/reports/movements/invoices/services/database_helpers.py delete mode 100644 backend/api/v1/modules/a76/reports/movements/invoices/services/definitive.py delete mode 100644 backend/api/v1/modules/a76/reports/movements/invoices/services/exchange_rate.py delete mode 100644 backend/api/v1/modules/a76/reports/movements/invoices/services/export.py delete mode 100644 backend/api/v1/modules/a76/reports/movements/invoices/services/export_repair.py delete mode 100644 backend/api/v1/modules/a76/reports/movements/invoices/services/query_builders.py delete mode 100644 backend/api/v1/modules/a76/reports/movements/invoices/services/repair.py delete mode 100644 backend/api/v1/modules/a76/reports/movements/invoices/services/temporary.py delete mode 100644 backend/api/v1/modules/a76/reports/movements/invoices/tasks.py delete mode 100644 backend/api/v1/modules/a76/reports/movements/saldos/__init__.py delete mode 100644 backend/api/v1/modules/a76/reports/movements/saldos/csv_utils.py delete mode 100644 backend/api/v1/modules/a76/reports/movements/saldos/routes.py delete mode 100644 backend/api/v1/modules/a76/reports/movements/saldos/schemas.py delete mode 100644 backend/api/v1/modules/a76/reports/movements/saldos/tasks.py delete mode 100644 backend/api/v1/modules/a76/reports/movements/vencimiento/__init__.py delete mode 100644 backend/api/v1/modules/a76/reports/movements/vencimiento/routes.py delete mode 100644 backend/api/v1/modules/a76/reports/movements/vencimiento/schemas.py delete mode 100644 backend/api/v1/modules/a76/reports/movements/vencimiento/service.py delete mode 100644 backend/api/v1/modules/a76/reports/movements/vencimiento/tasks.py delete mode 100644 backend/api/v1/modules/a76/reports/xml_optima/__init__.py delete mode 100644 backend/api/v1/modules/a76/reports/xml_optima/routes.py delete mode 100644 backend/api/v1/modules/a76/reports/xml_optima/schemas.py delete mode 100644 backend/api/v1/modules/a76/reports/xml_optima/service.py delete mode 100644 backend/api/v1/modules/a76/reports/xml_rb_systems/__init__.py delete mode 100644 backend/api/v1/modules/a76/reports/xml_rb_systems/routes.py delete mode 100644 backend/api/v1/modules/a76/reports/xml_rb_systems/schemas.py delete mode 100644 backend/api/v1/modules/a76/reports/xml_rb_systems/service.py delete mode 100644 backend/api/v1/modules/a76/reports/xml_rb_systems/task.py delete mode 100644 backend/api/v1/modules/a76/router.py delete mode 100644 backend/api/v1/modules/a76/rule_octave/balances/__init__.py delete mode 100644 backend/api/v1/modules/a76/rule_octave/balances/models.py delete mode 100644 backend/api/v1/modules/a76/rule_octave/country/dto.py delete mode 100644 backend/api/v1/modules/a76/rule_octave/country/models.py delete mode 100644 backend/api/v1/modules/a76/rule_octave/country/routes.py delete mode 100644 backend/api/v1/modules/a76/rule_octave/country/services.py delete mode 100644 backend/api/v1/modules/a76/rule_octave/country/test_country_rule_oct.py delete mode 100644 backend/api/v1/modules/a76/rule_octave/fractions/dto.py delete mode 100644 backend/api/v1/modules/a76/rule_octave/fractions/models.py delete mode 100644 backend/api/v1/modules/a76/rule_octave/fractions/routes.py delete mode 100644 backend/api/v1/modules/a76/rule_octave/fractions/services.py delete mode 100644 backend/api/v1/modules/a76/rule_octave/fractions/test_fraction_rule_octave.py delete mode 100644 backend/api/v1/modules/a76/rule_octave/permissions/models.py delete mode 100644 backend/api/v1/modules/a76/transportation/catalog_parity.py delete mode 100644 backend/api/v1/modules/a76/transportation/drivers/dto.py delete mode 100644 backend/api/v1/modules/a76/transportation/drivers/models.py delete mode 100644 backend/api/v1/modules/a76/transportation/drivers/routes.py delete mode 100644 backend/api/v1/modules/a76/transportation/drivers/services.py delete mode 100644 backend/api/v1/modules/a76/transportation/trailers/dto.py delete mode 100644 backend/api/v1/modules/a76/transportation/trailers/models.py delete mode 100644 backend/api/v1/modules/a76/transportation/trailers/routes.py delete mode 100644 backend/api/v1/modules/a76/transportation/trailers/services.py delete mode 100644 backend/api/v1/modules/a76/transportation/transporters/dto.py delete mode 100644 backend/api/v1/modules/a76/transportation/transporters/models.py delete mode 100644 backend/api/v1/modules/a76/transportation/transporters/routes.py delete mode 100644 backend/api/v1/modules/a76/transportation/transporters/services.py delete mode 100644 backend/api/v1/modules/a76/transportation/vehicles/dto.py delete mode 100644 backend/api/v1/modules/a76/transportation/vehicles/models.py delete mode 100644 backend/api/v1/modules/a76/transportation/vehicles/routes.py delete mode 100644 backend/api/v1/modules/a76/transportation/vehicles/services.py delete mode 100644 backend/api/v1/modules/core/permissions/integration_example.py delete mode 100644 backend/api/v1/modules/core/permissions/seed.py delete mode 100644 backend/api/v1/modules/core/permissions/seed_v2.py delete mode 100644 backend/api/v1/modules/core/permissions/test_permissions.py rename backend/api/v1/modules/{a24/inv/bom => example}/__init__.py (100%) create mode 100644 backend/api/v1/modules/example/dto.py create mode 100644 backend/api/v1/modules/example/models.py create mode 100644 backend/api/v1/modules/example/routes.py create mode 100644 backend/api/v1/modules/example/service.py delete mode 100644 backend/api/v1/modules/public/__init__.py delete mode 100644 backend/api/v1/modules/public/reference_data/agency_tariff_codes/dto.py delete mode 100644 backend/api/v1/modules/public/reference_data/agency_tariff_codes/models.py delete mode 100644 backend/api/v1/modules/public/reference_data/agency_tariff_codes/routes.py delete mode 100644 backend/api/v1/modules/public/reference_data/agency_tariff_codes/seed.py delete mode 100644 backend/api/v1/modules/public/reference_data/carta_porte_codes/cartaPorte.csv delete mode 100644 backend/api/v1/modules/public/reference_data/carta_porte_codes/dto.py delete mode 100644 backend/api/v1/modules/public/reference_data/carta_porte_codes/models.py delete mode 100644 backend/api/v1/modules/public/reference_data/carta_porte_codes/routes.py delete mode 100644 backend/api/v1/modules/public/reference_data/carta_porte_codes/seed.py delete mode 100644 backend/api/v1/modules/public/reference_data/code_pedimento_regimens/dto.py delete mode 100644 backend/api/v1/modules/public/reference_data/code_pedimento_regimens/models.py delete mode 100644 backend/api/v1/modules/public/reference_data/code_pedimento_regimens/routes.py delete mode 100644 backend/api/v1/modules/public/reference_data/code_pedimento_regimens/seed.py delete mode 100644 backend/api/v1/modules/public/reference_data/code_pedimento_regimens/test_code_pedimento_regimens.py delete mode 100644 backend/api/v1/modules/public/reference_data/conftest.py delete mode 100644 backend/api/v1/modules/public/reference_data/containers/dto.py delete mode 100644 backend/api/v1/modules/public/reference_data/containers/models.py delete mode 100644 backend/api/v1/modules/public/reference_data/containers/routes.py delete mode 100644 backend/api/v1/modules/public/reference_data/containers/seed.py delete mode 100644 backend/api/v1/modules/public/reference_data/containers/test_containers.py delete mode 100644 backend/api/v1/modules/public/reference_data/countries/dto.py delete mode 100644 backend/api/v1/modules/public/reference_data/countries/models.py delete mode 100644 backend/api/v1/modules/public/reference_data/countries/routes.py delete mode 100644 backend/api/v1/modules/public/reference_data/countries/seed.py delete mode 100644 backend/api/v1/modules/public/reference_data/countries/test_countries.py delete mode 100644 backend/api/v1/modules/public/reference_data/currency_types/dto.py delete mode 100644 backend/api/v1/modules/public/reference_data/currency_types/models.py delete mode 100644 backend/api/v1/modules/public/reference_data/currency_types/routes.py delete mode 100644 backend/api/v1/modules/public/reference_data/currency_types/seed.py delete mode 100644 backend/api/v1/modules/public/reference_data/currency_types/test_currency_types.py delete mode 100644 backend/api/v1/modules/public/reference_data/customs_sections/dto.py delete mode 100644 backend/api/v1/modules/public/reference_data/customs_sections/models.py delete mode 100644 backend/api/v1/modules/public/reference_data/customs_sections/routes.py delete mode 100644 backend/api/v1/modules/public/reference_data/customs_sections/seed.py delete mode 100644 backend/api/v1/modules/public/reference_data/customs_sections/test_customs_sections.py delete mode 100644 backend/api/v1/modules/public/reference_data/customs_warehouses/dto.py delete mode 100644 backend/api/v1/modules/public/reference_data/customs_warehouses/models.py delete mode 100644 backend/api/v1/modules/public/reference_data/customs_warehouses/routes.py delete mode 100644 backend/api/v1/modules/public/reference_data/customs_warehouses/seed.py delete mode 100644 backend/api/v1/modules/public/reference_data/customs_warehouses/test_customs_warehouses.py delete mode 100644 backend/api/v1/modules/public/reference_data/identifiers/dto.py delete mode 100644 backend/api/v1/modules/public/reference_data/identifiers/models.py delete mode 100644 backend/api/v1/modules/public/reference_data/identifiers/routes.py delete mode 100644 backend/api/v1/modules/public/reference_data/identifiers/seed.py delete mode 100644 backend/api/v1/modules/public/reference_data/incoterms/dto.py delete mode 100644 backend/api/v1/modules/public/reference_data/incoterms/models.py delete mode 100644 backend/api/v1/modules/public/reference_data/incoterms/routes.py delete mode 100644 backend/api/v1/modules/public/reference_data/incoterms/seed.py delete mode 100644 backend/api/v1/modules/public/reference_data/incoterms/test_incoterms.py delete mode 100644 backend/api/v1/modules/public/reference_data/invoice_types/dto.py delete mode 100644 backend/api/v1/modules/public/reference_data/invoice_types/models.py delete mode 100644 backend/api/v1/modules/public/reference_data/invoice_types/routes.py delete mode 100644 backend/api/v1/modules/public/reference_data/invoice_types/seed.py delete mode 100644 backend/api/v1/modules/public/reference_data/invoice_types/test_invoice_types.py delete mode 100644 backend/api/v1/modules/public/reference_data/license_exceptions/dto.py delete mode 100644 backend/api/v1/modules/public/reference_data/license_exceptions/models.py delete mode 100644 backend/api/v1/modules/public/reference_data/license_exceptions/routes.py delete mode 100644 backend/api/v1/modules/public/reference_data/license_exceptions/seed.py delete mode 100644 backend/api/v1/modules/public/reference_data/material_types/dto.py delete mode 100644 backend/api/v1/modules/public/reference_data/material_types/models.py delete mode 100644 backend/api/v1/modules/public/reference_data/material_types/routes.py delete mode 100644 backend/api/v1/modules/public/reference_data/material_types/seed.py delete mode 100644 backend/api/v1/modules/public/reference_data/material_types/test_material_types.py delete mode 100644 backend/api/v1/modules/public/reference_data/payment_methods/dto.py delete mode 100644 backend/api/v1/modules/public/reference_data/payment_methods/models.py delete mode 100644 backend/api/v1/modules/public/reference_data/payment_methods/routes.py delete mode 100644 backend/api/v1/modules/public/reference_data/payment_methods/seed.py delete mode 100644 backend/api/v1/modules/public/reference_data/payment_methods/test_payment_methods.py delete mode 100644 backend/api/v1/modules/public/reference_data/pedimento_codes/dto.py delete mode 100644 backend/api/v1/modules/public/reference_data/pedimento_codes/models.py delete mode 100644 backend/api/v1/modules/public/reference_data/pedimento_codes/routes.py delete mode 100644 backend/api/v1/modules/public/reference_data/pedimento_codes/seed.py delete mode 100644 backend/api/v1/modules/public/reference_data/pedimento_codes/test_pedimento_codes.py delete mode 100644 backend/api/v1/modules/public/reference_data/pedimento_regimens/dto.py delete mode 100644 backend/api/v1/modules/public/reference_data/pedimento_regimens/models.py delete mode 100644 backend/api/v1/modules/public/reference_data/pedimento_regimens/routes.py delete mode 100644 backend/api/v1/modules/public/reference_data/pedimento_regimens/seed.py delete mode 100644 backend/api/v1/modules/public/reference_data/pedimento_regimens/test_pedimento_regimens.py delete mode 100644 backend/api/v1/modules/public/reference_data/pedimento_transport_catalog/dto.py delete mode 100644 backend/api/v1/modules/public/reference_data/pedimento_transport_catalog/models.py delete mode 100644 backend/api/v1/modules/public/reference_data/pedimento_transport_catalog/routes.py delete mode 100644 backend/api/v1/modules/public/reference_data/pedimento_transport_catalog/seed.py delete mode 100644 backend/api/v1/modules/public/reference_data/rbac.py delete mode 100644 backend/api/v1/modules/public/reference_data/router.py delete mode 100644 backend/api/v1/modules/public/reference_data/states/dto.py delete mode 100644 backend/api/v1/modules/public/reference_data/states/models.py delete mode 100644 backend/api/v1/modules/public/reference_data/states/routes.py delete mode 100644 backend/api/v1/modules/public/reference_data/states/seed.py delete mode 100644 backend/api/v1/modules/public/reference_data/states/test_states.py delete mode 100644 backend/api/v1/modules/public/reference_data/trailer_types/dto.py delete mode 100644 backend/api/v1/modules/public/reference_data/trailer_types/models.py delete mode 100644 backend/api/v1/modules/public/reference_data/trailer_types/routes.py delete mode 100644 backend/api/v1/modules/public/reference_data/trailer_types/seed.py delete mode 100644 backend/api/v1/modules/public/reference_data/trailer_types/services.py delete mode 100644 backend/api/v1/modules/public/reference_data/transport_modes/dto.py delete mode 100644 backend/api/v1/modules/public/reference_data/transport_modes/models.py delete mode 100644 backend/api/v1/modules/public/reference_data/transport_modes/routes.py delete mode 100644 backend/api/v1/modules/public/reference_data/transport_modes/seed.py delete mode 100644 backend/api/v1/modules/public/reference_data/transport_modes/test_transport_modes.py delete mode 100644 backend/api/v1/modules/public/reference_data/transport_types/dto.py delete mode 100644 backend/api/v1/modules/public/reference_data/transport_types/models.py delete mode 100644 backend/api/v1/modules/public/reference_data/transport_types/routes.py delete mode 100644 backend/api/v1/modules/public/reference_data/transport_types/seed.py delete mode 100644 backend/api/v1/modules/public/reference_data/transport_types/test_transport_types.py delete mode 100644 backend/api/v1/modules/public/reference_data/valuation_methods/dto.py delete mode 100644 backend/api/v1/modules/public/reference_data/valuation_methods/models.py delete mode 100644 backend/api/v1/modules/public/reference_data/valuation_methods/routes.py delete mode 100644 backend/api/v1/modules/public/reference_data/valuation_methods/seed.py delete mode 100644 backend/api/v1/modules/public/reference_data/valuation_methods/test_valuation_methods.py delete mode 100644 backend/api/v1/modules/public/router.py delete mode 100644 backend/api/v1/modules/sitar/README.md delete mode 100644 backend/api/v1/modules/sitar/__init__.py delete mode 100644 backend/api/v1/modules/sitar/aladi2/__init__.py delete mode 100644 backend/api/v1/modules/sitar/aladi2/router.py delete mode 100644 backend/api/v1/modules/sitar/aladi2/schemas.py delete mode 100644 backend/api/v1/modules/sitar/aladi2/service.py delete mode 100644 backend/api/v1/modules/sitar/common/__init__.py delete mode 100644 backend/api/v1/modules/sitar/common/base_service.py delete mode 100644 backend/api/v1/modules/sitar/cuotas2/__init__.py delete mode 100644 backend/api/v1/modules/sitar/cuotas2/router.py delete mode 100644 backend/api/v1/modules/sitar/cuotas2/schemas.py delete mode 100644 backend/api/v1/modules/sitar/cuotas2/service.py delete mode 100644 backend/api/v1/modules/sitar/cupos/__init__.py delete mode 100644 backend/api/v1/modules/sitar/cupos/router.py delete mode 100644 backend/api/v1/modules/sitar/cupos/schemas.py delete mode 100644 backend/api/v1/modules/sitar/cupos/service.py delete mode 100644 backend/api/v1/modules/sitar/fracciones/__init__.py delete mode 100644 backend/api/v1/modules/sitar/fracciones/router.py delete mode 100644 backend/api/v1/modules/sitar/fracciones/schemas.py delete mode 100644 backend/api/v1/modules/sitar/fracciones/service.py delete mode 100644 backend/api/v1/modules/sitar/fracciones_anteriores/__init__.py delete mode 100644 backend/api/v1/modules/sitar/fracciones_anteriores/router.py delete mode 100644 backend/api/v1/modules/sitar/fracciones_anteriores/schemas.py delete mode 100644 backend/api/v1/modules/sitar/fracciones_anteriores/service.py delete mode 100644 backend/api/v1/modules/sitar/fracciones_usa/__init__.py delete mode 100644 backend/api/v1/modules/sitar/fracciones_usa/catalog_resolve.py delete mode 100644 backend/api/v1/modules/sitar/fracciones_usa/router.py delete mode 100644 backend/api/v1/modules/sitar/fracciones_usa/schemas.py delete mode 100644 backend/api/v1/modules/sitar/fracciones_usa/service.py delete mode 100644 backend/api/v1/modules/sitar/fundamentos_tlc/__init__.py delete mode 100644 backend/api/v1/modules/sitar/fundamentos_tlc/router.py delete mode 100644 backend/api/v1/modules/sitar/fundamentos_tlc/schemas.py delete mode 100644 backend/api/v1/modules/sitar/fundamentos_tlc/service.py delete mode 100644 backend/api/v1/modules/sitar/ieps/__init__.py delete mode 100644 backend/api/v1/modules/sitar/ieps/router.py delete mode 100644 backend/api/v1/modules/sitar/ieps/schemas.py delete mode 100644 backend/api/v1/modules/sitar/ieps/service.py delete mode 100644 backend/api/v1/modules/sitar/informacion_general/__init__.py delete mode 100644 backend/api/v1/modules/sitar/informacion_general/router.py delete mode 100644 backend/api/v1/modules/sitar/informacion_general/schemas.py delete mode 100644 backend/api/v1/modules/sitar/informacion_general/service.py delete mode 100644 backend/api/v1/modules/sitar/noms/__init__.py delete mode 100644 backend/api/v1/modules/sitar/noms/router.py delete mode 100644 backend/api/v1/modules/sitar/noms/schemas.py delete mode 100644 backend/api/v1/modules/sitar/noms/service.py delete mode 100644 backend/api/v1/modules/sitar/precios_estimados/__init__.py delete mode 100644 backend/api/v1/modules/sitar/precios_estimados/router.py delete mode 100644 backend/api/v1/modules/sitar/precios_estimados/schemas.py delete mode 100644 backend/api/v1/modules/sitar/precios_estimados/service.py delete mode 100644 backend/api/v1/modules/sitar/prosec/__init__.py delete mode 100644 backend/api/v1/modules/sitar/prosec/router.py delete mode 100644 backend/api/v1/modules/sitar/prosec/schemas.py delete mode 100644 backend/api/v1/modules/sitar/prosec/service.py delete mode 100644 backend/api/v1/modules/sitar/rcg2/__init__.py delete mode 100644 backend/api/v1/modules/sitar/rcg2/router.py delete mode 100644 backend/api/v1/modules/sitar/rcg2/schemas.py delete mode 100644 backend/api/v1/modules/sitar/rcg2/service.py delete mode 100644 backend/api/v1/modules/sitar/regulaciones/__init__.py delete mode 100644 backend/api/v1/modules/sitar/regulaciones/router.py delete mode 100644 backend/api/v1/modules/sitar/regulaciones/schemas.py delete mode 100644 backend/api/v1/modules/sitar/regulaciones/service.py delete mode 100644 backend/api/v1/modules/sitar/reit/__init__.py delete mode 100644 backend/api/v1/modules/sitar/reit/router.py delete mode 100644 backend/api/v1/modules/sitar/reit/schemas.py delete mode 100644 backend/api/v1/modules/sitar/reit/service.py delete mode 100644 backend/api/v1/modules/sitar/requisito_previo/__init__.py delete mode 100644 backend/api/v1/modules/sitar/requisito_previo/router.py delete mode 100644 backend/api/v1/modules/sitar/requisito_previo/schemas.py delete mode 100644 backend/api/v1/modules/sitar/requisito_previo/service.py delete mode 100644 backend/api/v1/modules/sitar/router.py delete mode 100644 backend/api/v1/modules/sitar/tlcs/__init__.py delete mode 100644 backend/api/v1/modules/sitar/tlcs/router.py delete mode 100644 backend/api/v1/modules/sitar/tlcs/schemas.py delete mode 100644 backend/api/v1/modules/sitar/tlcs/service.py delete mode 100644 backend/api/v1/modules/sitar/vehiculos_marcas/__init__.py delete mode 100644 backend/api/v1/modules/sitar/vehiculos_marcas/router.py delete mode 100644 backend/api/v1/modules/sitar/vehiculos_marcas/schemas.py delete mode 100644 backend/api/v1/modules/sitar/vehiculos_marcas/service.py delete mode 100644 backend/api/v1/modules/sitar/vehiculos_modelos/__init__.py delete mode 100644 backend/api/v1/modules/sitar/vehiculos_modelos/router.py delete mode 100644 backend/api/v1/modules/sitar/vehiculos_modelos/schemas.py delete mode 100644 backend/api/v1/modules/sitar/vehiculos_modelos/service.py delete mode 100644 backend/tests/README.md delete mode 100644 backend/tests/__init__.py delete mode 100644 backend/tests/conftest.py delete mode 100644 backend/tests/e2e/test_inventory_flow_anexo24.py delete mode 100644 backend/tests/fixtures/__init__.py delete mode 100644 backend/tests/fixtures/builders.py delete mode 100644 backend/tests/integration/test_export_process_api.py delete mode 100644 backend/tests/integration/test_import_process_api.py delete mode 100644 backend/tests/integration/test_rls_tenant_company.py delete mode 100644 backend/tests/unit/a76/layouts_csv/test_csv_header_hints.py delete mode 100644 backend/tests/unit/core/test_license_middleware.py delete mode 100644 backend/tests/unit/general_catalogs/doda/test_doda_alta_payloads.py delete mode 100644 backend/tests/unit/general_catalogs/doda/test_doda_export.py delete mode 100644 backend/tests/unit/general_catalogs/doda/test_doda_external_service.py delete mode 100644 backend/tests/unit/general_catalogs/doda/test_doda_print.py delete mode 100644 backend/tests/unit/general_catalogs/fractions/test_tariff_fraction_mapper.py delete mode 100644 backend/tests/unit/invoices/test_balance_algorithm.py delete mode 100644 backend/tests/unit/invoices/test_currency_conversion_imports.py delete mode 100644 backend/tests/unit/test_csv_reader_encoding.py delete mode 100644 frontend/src/lib/api/dashboard/a24/fa_classes.ts delete mode 100644 frontend/src/lib/api/dashboard/a24/inv.ts delete mode 100644 frontend/src/lib/api/dashboard/a76/aphis-catalog.ts delete mode 100644 frontend/src/lib/api/dashboard/a76/app-settings.ts delete mode 100644 frontend/src/lib/api/dashboard/a76/audit_files.ts delete mode 100644 frontend/src/lib/api/dashboard/a76/audit_log.ts delete mode 100644 frontend/src/lib/api/dashboard/a76/classes.ts delete mode 100644 frontend/src/lib/api/dashboard/a76/clients-providers.ts delete mode 100644 frontend/src/lib/api/dashboard/a76/customs-brokers.ts delete mode 100644 frontend/src/lib/api/dashboard/a76/doda-alta-log.ts delete mode 100644 frontend/src/lib/api/dashboard/a76/drivers.ts delete mode 100644 frontend/src/lib/api/dashboard/a76/exchange-rate.ts delete mode 100644 frontend/src/lib/api/dashboard/a76/expediente-archivos.ts delete mode 100644 frontend/src/lib/api/dashboard/a76/general_catalogs/classification-concepts.ts delete mode 100644 frontend/src/lib/api/dashboard/a76/general_catalogs/company.ts delete mode 100644 frontend/src/lib/api/dashboard/a76/general_catalogs/concepts.ts delete mode 100644 frontend/src/lib/api/dashboard/a76/general_catalogs/customs-broker-concepts.ts delete mode 100644 frontend/src/lib/api/dashboard/a76/general_catalogs/depreciation-catalog.ts delete mode 100644 frontend/src/lib/api/dashboard/a76/general_catalogs/doda.ts delete mode 100644 frontend/src/lib/api/dashboard/a76/general_catalogs/electronic-notices.ts delete mode 100644 frontend/src/lib/api/dashboard/a76/general_catalogs/equivalencies.ts delete mode 100644 frontend/src/lib/api/dashboard/a76/general_catalogs/error-catalogs.ts delete mode 100644 frontend/src/lib/api/dashboard/a76/general_catalogs/exchange-rate.ts delete mode 100644 frontend/src/lib/api/dashboard/a76/general_catalogs/fda-catalog.ts delete mode 100644 frontend/src/lib/api/dashboard/a76/general_catalogs/historical-tariff-fractions.ts delete mode 100644 frontend/src/lib/api/dashboard/a76/general_catalogs/identifiers.ts delete mode 100644 frontend/src/lib/api/dashboard/a76/general_catalogs/index.ts delete mode 100644 frontend/src/lib/api/dashboard/a76/general_catalogs/inpc.ts delete mode 100644 frontend/src/lib/api/dashboard/a76/general_catalogs/legends.ts delete mode 100644 frontend/src/lib/api/dashboard/a76/general_catalogs/locations.ts delete mode 100644 frontend/src/lib/api/dashboard/a76/general_catalogs/multi-currency-types.ts delete mode 100644 frontend/src/lib/api/dashboard/a76/general_catalogs/packages.ts delete mode 100644 frontend/src/lib/api/dashboard/a76/general_catalogs/ports.ts delete mode 100644 frontend/src/lib/api/dashboard/a76/general_catalogs/prevalidators.ts delete mode 100644 frontend/src/lib/api/dashboard/a76/general_catalogs/seals.ts delete mode 100644 frontend/src/lib/api/dashboard/a76/general_catalogs/signatures.ts delete mode 100644 frontend/src/lib/api/dashboard/a76/general_catalogs/tariff-fractions.ts delete mode 100644 frontend/src/lib/api/dashboard/a76/general_catalogs/um-ace.ts delete mode 100644 frontend/src/lib/api/dashboard/a76/general_catalogs/um-customs-ame.ts delete mode 100644 frontend/src/lib/api/dashboard/a76/general_catalogs/um-customs-mex.ts delete mode 100644 frontend/src/lib/api/dashboard/a76/general_catalogs/um-oma.ts delete mode 100644 frontend/src/lib/api/dashboard/a76/general_catalogs/unit-conversions.ts delete mode 100644 frontend/src/lib/api/dashboard/a76/general_catalogs/unit-measures.ts delete mode 100644 frontend/src/lib/api/dashboard/a76/general_catalogs/units-of-measure.ts delete mode 100644 frontend/src/lib/api/dashboard/a76/general_catalogs/us-tariff-fractions.ts delete mode 100644 frontend/src/lib/api/dashboard/a76/index.ts delete mode 100644 frontend/src/lib/api/dashboard/a76/invoice-movements.ts delete mode 100644 frontend/src/lib/api/dashboard/a76/invoices.ts delete mode 100644 frontend/src/lib/api/dashboard/a76/item-presets.ts delete mode 100644 frontend/src/lib/api/dashboard/a76/items.ts delete mode 100644 frontend/src/lib/api/dashboard/a76/manifest-drivers.ts delete mode 100644 frontend/src/lib/api/dashboard/a76/manifests.ts delete mode 100644 frontend/src/lib/api/dashboard/a76/material-types.ts delete mode 100644 frontend/src/lib/api/dashboard/a76/parts.ts delete mode 100644 frontend/src/lib/api/dashboard/a76/pedimento-dates.ts delete mode 100644 frontend/src/lib/api/dashboard/a76/pedimento-payments.ts delete mode 100644 frontend/src/lib/api/dashboard/a76/pedimento-transport.ts delete mode 100644 frontend/src/lib/api/dashboard/a76/pedimento-validation.ts delete mode 100644 frontend/src/lib/api/dashboard/a76/pedimentos.ts delete mode 100644 frontend/src/lib/api/dashboard/a76/reports/reports-aviso-consolidado.ts delete mode 100644 frontend/src/lib/api/dashboard/a76/reports/reports-consolidated.ts delete mode 100644 frontend/src/lib/api/dashboard/a76/reports/reports-descargo.ts delete mode 100644 frontend/src/lib/api/dashboard/a76/reports/reports-invoices.ts delete mode 100644 frontend/src/lib/api/dashboard/a76/reports/reports-partes-descargadas.ts delete mode 100644 frontend/src/lib/api/dashboard/a76/reports/reports-transmission.ts delete mode 100644 frontend/src/lib/api/dashboard/a76/reports/reports-vencimiento.ts delete mode 100644 frontend/src/lib/api/dashboard/a76/reports/reports-winsaai.ts delete mode 100644 frontend/src/lib/api/dashboard/a76/saldos-report.ts delete mode 100644 frontend/src/lib/api/dashboard/a76/sitar.ts delete mode 100644 frontend/src/lib/api/dashboard/a76/tasks.ts delete mode 100644 frontend/src/lib/api/dashboard/a76/trailers.ts delete mode 100644 frontend/src/lib/api/dashboard/a76/transporters.ts delete mode 100644 frontend/src/lib/api/dashboard/a76/units_of_measure.ts delete mode 100644 frontend/src/lib/api/dashboard/a76/vehicles.ts delete mode 100644 frontend/src/lib/api/dashboard/general_catalogs/canadian.ts delete mode 100644 frontend/src/lib/api/dashboard/general_catalogs/sectors.ts delete mode 100644 frontend/src/lib/api/dashboard/reference_data/agency_tariff_codes.ts delete mode 100644 frontend/src/lib/api/dashboard/reference_data/carta_porte.ts delete mode 100644 frontend/src/lib/api/dashboard/reference_data/code_pedimento_regimens.ts delete mode 100644 frontend/src/lib/api/dashboard/reference_data/containers.ts delete mode 100644 frontend/src/lib/api/dashboard/reference_data/countries.ts delete mode 100644 frontend/src/lib/api/dashboard/reference_data/currency_types.ts delete mode 100644 frontend/src/lib/api/dashboard/reference_data/customs_sections.ts delete mode 100644 frontend/src/lib/api/dashboard/reference_data/customs_warehouses.ts delete mode 100644 frontend/src/lib/api/dashboard/reference_data/document_types_digitization.ts delete mode 100644 frontend/src/lib/api/dashboard/reference_data/identifiers.ts delete mode 100644 frontend/src/lib/api/dashboard/reference_data/incoterms.ts delete mode 100644 frontend/src/lib/api/dashboard/reference_data/invoice_types.ts delete mode 100644 frontend/src/lib/api/dashboard/reference_data/license_exceptions.ts delete mode 100644 frontend/src/lib/api/dashboard/reference_data/material_types.ts delete mode 100644 frontend/src/lib/api/dashboard/reference_data/payment_methods.ts delete mode 100644 frontend/src/lib/api/dashboard/reference_data/pedimento_codes.ts delete mode 100644 frontend/src/lib/api/dashboard/reference_data/pedimento_regimens.ts delete mode 100644 frontend/src/lib/api/dashboard/reference_data/sectors.ts delete mode 100644 frontend/src/lib/api/dashboard/reference_data/states.ts delete mode 100644 frontend/src/lib/api/dashboard/reference_data/trailer_types.ts delete mode 100644 frontend/src/lib/api/dashboard/reference_data/transport_modes.ts delete mode 100644 frontend/src/lib/api/dashboard/reference_data/transport_types.ts delete mode 100644 frontend/src/lib/api/dashboard/reference_data/valuation_methods.ts delete mode 100644 frontend/src/lib/components/dashboard/PrerequisitesModal.svelte delete mode 100644 frontend/src/lib/components/dashboard/activity-feed.svelte delete mode 100644 frontend/src/lib/components/dashboard/clients_and_providers/columns.ts delete mode 100644 frontend/src/lib/components/dashboard/clients_and_providers/create-edit-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/clients_and_providers/data-table-actions.svelte delete mode 100644 frontend/src/lib/components/dashboard/clients_and_providers/data-table.svelte delete mode 100644 frontend/src/lib/components/dashboard/clients_and_providers/delete-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/clients_and_providers/details-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/csv-upload/ConfigFooter.svelte delete mode 100644 frontend/src/lib/components/dashboard/csv-upload/CsvParamsBar.svelte delete mode 100644 frontend/src/lib/components/dashboard/csv-upload/CsvPendingImportsSheet.svelte delete mode 100644 frontend/src/lib/components/dashboard/csv-upload/ProcessingResultModal.svelte delete mode 100644 frontend/src/lib/components/dashboard/csv-upload/UniversalUploadModal.svelte delete mode 100644 frontend/src/lib/components/dashboard/csv-upload/UploadLauncherGrid.svelte delete mode 100644 frontend/src/lib/components/dashboard/customs_brokers/columns.ts delete mode 100644 frontend/src/lib/components/dashboard/customs_brokers/create-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/customs_brokers/data-table-actions.svelte delete mode 100644 frontend/src/lib/components/dashboard/customs_brokers/data-table.svelte delete mode 100644 frontend/src/lib/components/dashboard/customs_brokers/delete-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/customs_brokers/details-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/customs_brokers/edit-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/despacho/doda/doda-alta-log-columns.ts delete mode 100644 frontend/src/lib/components/dashboard/despacho/doda/doda-alta-log-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/despacho/doda/doda-export-excel-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/despacho/doda/doda-progress-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/digitalizacion/columns.ts delete mode 100644 frontend/src/lib/components/dashboard/digitalizacion/create-edit-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/digitalizacion/data-table-actions.svelte delete mode 100644 frontend/src/lib/components/dashboard/digitalizacion/digitalizar-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/digitalizacion/e-document-cell.svelte delete mode 100644 frontend/src/lib/components/dashboard/digitalizacion/progress-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/exchange_rate/columns.ts delete mode 100644 frontend/src/lib/components/dashboard/exchange_rate/create-edit-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/exchange_rate/data-table-actions.svelte delete mode 100644 frontend/src/lib/components/dashboard/exchange_rate/data-table.svelte delete mode 100644 frontend/src/lib/components/dashboard/exchange_rate/exchange-rate-guard.svelte delete mode 100644 frontend/src/lib/components/dashboard/export/manifest/columns.ts delete mode 100644 frontend/src/lib/components/dashboard/export/manifest/create-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/export/manifest/data-table.svelte delete mode 100644 frontend/src/lib/components/dashboard/export/manifest/delete-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/export/manifest/details-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/export/manifest/edit-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/export/manifest/invoice-selection-table.svelte delete mode 100644 frontend/src/lib/components/dashboard/export/manifest/manifest-driver-subform.svelte delete mode 100644 frontend/src/lib/components/dashboard/export/manifest/manifest-form.svelte delete mode 100644 frontend/src/lib/components/dashboard/export/manifest/modals/broker-selector-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/export/manifest/modals/driver-selector-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/export/manifest/modals/port-selector-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/export/manifest/modals/trailer-selector-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/export/manifest/modals/transport-mode-selector-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/export/manifest/modals/transport-type-selector-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/export/manifest/modals/transporter-selector-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/export/manifest/modals/vehicle-selector-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/classification_concepts/columns.ts delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/classification_concepts/create-edit-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/classification_concepts/data-table-actions.svelte delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/classification_concepts/data-table.svelte delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/company/columns.ts delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/company/create-edit-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/company/data-table-actions.svelte delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/company/data-table.svelte delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/concepts/columns.ts delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/concepts/create-edit-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/concepts/data-table-actions.svelte delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/concepts/data-table.svelte delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/customs_broker_concepts/columns.ts delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/customs_broker_concepts/create-edit-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/customs_broker_concepts/data-table-actions.svelte delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/customs_broker_concepts/data-table.svelte delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/doda/child-detail-table.svelte delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/doda/columns.ts delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/doda/data-table-actions.svelte delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/doda/data-table.svelte delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/doda/delete-list-state.ts delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/doda/doda-form-helpers.ts delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/doda/doda-form-modal.svelte delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/electronic_notices/columns.ts delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/electronic_notices/create-edit-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/electronic_notices/data-table-actions.svelte delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/electronic_notices/data-table.svelte delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/equivalencies/catalog-columns.ts delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/equivalencies/catalog-create-edit-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/equivalencies/catalog-data-table-actions.svelte delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/equivalencies/columns.ts delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/equivalencies/create-edit-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/equivalencies/data-equivalencies-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/equivalencies/data-table-actions.svelte delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/equivalencies/data-table.svelte delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/error_catalogs/columns.ts delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/error_catalogs/create-edit-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/error_catalogs/data-table-actions.svelte delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/error_catalogs/data-table.svelte delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/identifiers/columns.ts delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/identifiers/create-edit-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/identifiers/data-table-actions.svelte delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/identifiers/data-table.svelte delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/inpc/columns.ts delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/inpc/create-edit-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/inpc/data-table-actions.svelte delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/inpc/data-table.svelte delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/legends/columns.ts delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/legends/create-edit-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/legends/data-table-actions.svelte delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/legends/data-table.svelte delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/locations/create-edit-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/multi_currency_types/create-edit-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/prevalidators/columns.ts delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/prevalidators/create-edit-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/prevalidators/data-table-actions.svelte delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/prevalidators/data-table.svelte delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/seals/columns.ts delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/seals/create-edit-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/seals/data-table-actions.svelte delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/seals/index.ts delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/sectors/SectorsList.svelte delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/sectors/columns.ts delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/sectors/create-edit-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/sectors/data-table-actions.svelte delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/signatures/columns.ts delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/signatures/create-edit-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/signatures/data-table-actions.svelte delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/signatures/data-table.svelte delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/simple-data-table.svelte delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/unit_conversion/create-edit-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/unit_conversions/columns.ts delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/unit_conversions/create-edit-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/unit_conversions/data-table-actions.svelte delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/unit_conversions/data-table.svelte delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/units_of_measure/ace/columns.ts delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/units_of_measure/ace/create-edit-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/units_of_measure/ace/data-table-actions.svelte delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/units_of_measure/ace/data-table.svelte delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/units_of_measure/american/columns.ts delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/units_of_measure/american/create-edit-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/units_of_measure/american/data-table-actions.svelte delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/units_of_measure/american/data-table.svelte delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/units_of_measure/customs/columns.ts delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/units_of_measure/customs/create-edit-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/units_of_measure/customs/data-table-actions.svelte delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/units_of_measure/customs/data-table.svelte delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/units_of_measure/general/columns.ts delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/units_of_measure/general/create-edit-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/units_of_measure/general/data-table-actions.svelte delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/units_of_measure/general/data-table.svelte delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/units_of_measure/main/data-table.svelte delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/units_of_measure/oma/columns.ts delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/units_of_measure/oma/create-edit-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/units_of_measure/oma/data-table-actions.svelte delete mode 100644 frontend/src/lib/components/dashboard/general_catalogs/units_of_measure/oma/data-table.svelte delete mode 100644 frontend/src/lib/components/dashboard/goods/classes/columns.ts delete mode 100644 frontend/src/lib/components/dashboard/goods/classes/create-edit-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/goods/classes/data-table.svelte delete mode 100644 frontend/src/lib/components/dashboard/goods/classes/forms/FixedAssetClassForm.svelte delete mode 100644 frontend/src/lib/components/dashboard/goods/fractions/CanadianFractionDialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/goods/fractions/CanadianFractionList.svelte delete mode 100644 frontend/src/lib/components/dashboard/goods/fractions/HistoricalFractionDialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/goods/fractions/HistoricalFractionList.svelte delete mode 100644 frontend/src/lib/components/dashboard/goods/fractions/SitarFractionTabs.svelte delete mode 100644 frontend/src/lib/components/dashboard/goods/fractions/TariffFractionFormDialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/goods/fractions/TariffFractionList.svelte delete mode 100644 frontend/src/lib/components/dashboard/goods/modales/TariffFractionSelector.svelte delete mode 100644 frontend/src/lib/components/dashboard/goods/modales/client-selector-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/goods/modales/country-selector-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/goods/modales/currency-selector-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/goods/modales/fraction-selector-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/goods/modales/material-type-selector-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/goods/modales/unit-measure-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/goods/modales/us-fraction-selector-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/goods/parts/AgencyTariffCodeSelector.svelte delete mode 100644 frontend/src/lib/components/dashboard/goods/parts/CartaPorteSelector.svelte delete mode 100644 frontend/src/lib/components/dashboard/goods/parts/Fraction9801SelectorDialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/goods/parts/IdentifierSelector.svelte delete mode 100644 frontend/src/lib/components/dashboard/goods/parts/LicenseExceptionSelector.svelte delete mode 100644 frontend/src/lib/components/dashboard/goods/parts/PartSelector.svelte delete mode 100644 frontend/src/lib/components/dashboard/goods/parts/SubstitutePartSelectorDialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/goods/parts/TextileProviderSelectorDialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/goods/parts/class-selector-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/goods/parts/columns.ts delete mode 100644 frontend/src/lib/components/dashboard/goods/parts/create-edit-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/goods/parts/data-table-actions.svelte delete mode 100644 frontend/src/lib/components/dashboard/goods/parts/data-table.svelte delete mode 100644 frontend/src/lib/components/dashboard/goods/parts/partForm.svelte delete mode 100644 frontend/src/lib/components/dashboard/identifiers/columns.ts delete mode 100644 frontend/src/lib/components/dashboard/identifiers/create-edit-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/identifiers/data-table-actions.svelte delete mode 100644 frontend/src/lib/components/dashboard/identifiers/data-table.svelte delete mode 100644 frontend/src/lib/components/dashboard/invoices/columns.ts delete mode 100644 frontend/src/lib/components/dashboard/invoices/create-edit-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/invoices/data-table-actions.svelte delete mode 100644 frontend/src/lib/components/dashboard/invoices/data-table.svelte delete mode 100644 frontend/src/lib/components/dashboard/invoices/delete-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/invoices/details-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/invoices/download-invoice-button.svelte delete mode 100644 frontend/src/lib/components/dashboard/invoices/edit/InvoiceSelectorModal.svelte delete mode 100644 frontend/src/lib/components/dashboard/invoices/edit/ManifestSelectorModal.svelte delete mode 100644 frontend/src/lib/components/dashboard/invoices/edit/PortSelectorModal.svelte delete mode 100644 frontend/src/lib/components/dashboard/invoices/edit/continuation-tab-form.svelte delete mode 100644 frontend/src/lib/components/dashboard/invoices/edit/focus-invoice-field.ts delete mode 100644 frontend/src/lib/components/dashboard/invoices/edit/general-tab-form.svelte delete mode 100644 frontend/src/lib/components/dashboard/invoices/edit/invoice-top-fields.svelte delete mode 100644 frontend/src/lib/components/dashboard/invoices/edit/items/fa/class-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/invoices/edit/items/fa/country-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/invoices/edit/items/fa/identifier-catalog-selector.svelte delete mode 100644 frontend/src/lib/components/dashboard/invoices/edit/items/fa/item-configuration.svelte delete mode 100644 frontend/src/lib/components/dashboard/invoices/edit/items/fa/item-sheet-fa.svelte delete mode 100644 frontend/src/lib/components/dashboard/invoices/edit/items/fa/location-selector-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/invoices/edit/items/fa/main-data.svelte delete mode 100644 frontend/src/lib/components/dashboard/invoices/edit/items/fa/package-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/invoices/edit/items/fa/packages-section.svelte delete mode 100644 frontend/src/lib/components/dashboard/invoices/edit/items/fa/part-number-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/invoices/edit/items/fa/payment-method-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/invoices/edit/items/fa/state-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/invoices/edit/items/fa/summary-section.svelte delete mode 100644 frontend/src/lib/components/dashboard/invoices/edit/items/fa/tab-continuation.svelte delete mode 100644 frontend/src/lib/components/dashboard/invoices/edit/items/fa/tab-identifiers.svelte delete mode 100644 frontend/src/lib/components/dashboard/invoices/edit/items/fa/tab-labeling.svelte delete mode 100644 frontend/src/lib/components/dashboard/invoices/edit/items/fa/tab-series.svelte delete mode 100644 frontend/src/lib/components/dashboard/invoices/edit/items/fa/tariff-fraction-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/invoices/edit/items/fa/unit-of-measure-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/invoices/edit/items/fa/valuation-method-selector.svelte delete mode 100644 frontend/src/lib/components/dashboard/invoices/edit/items/focus-item-sheet-field.ts delete mode 100644 frontend/src/lib/components/dashboard/invoices/edit/items/inv/item-sheet-inv.svelte delete mode 100644 frontend/src/lib/components/dashboard/invoices/edit/items/items-tab-form.svelte delete mode 100644 frontend/src/lib/components/dashboard/invoices/edit/observations-tab-form.svelte delete mode 100644 frontend/src/lib/components/dashboard/invoices/edit/others-tab-form.svelte delete mode 100644 frontend/src/lib/components/dashboard/invoices/edit/save-invoice.ts delete mode 100644 frontend/src/lib/components/dashboard/invoices/invoice-download-modal.svelte delete mode 100644 frontend/src/lib/components/dashboard/invoices/invoice-list-navigation.ts delete mode 100644 frontend/src/lib/components/dashboard/invoices/pdf-progress-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/invoices/settings/sscr-settings-form.svelte delete mode 100644 frontend/src/lib/components/dashboard/invoices/settings/sscr-settings-form.svelte.patch delete mode 100644 frontend/src/lib/components/dashboard/invoices/settings/ssicm-settings-form.svelte delete mode 100644 frontend/src/lib/components/dashboard/invoices/settings/ssimp-settings-form.svelte delete mode 100644 frontend/src/lib/components/dashboard/invoices/settings/ssisdef-settings-form.svelte delete mode 100644 frontend/src/lib/components/dashboard/invoices/transferencia-electronica-modal.svelte delete mode 100644 frontend/src/lib/components/dashboard/locations/columns.ts delete mode 100644 frontend/src/lib/components/dashboard/locations/data-table-actions.svelte delete mode 100644 frontend/src/lib/components/dashboard/locations/data-table.svelte delete mode 100644 frontend/src/lib/components/dashboard/locations/locations-catalog.svelte delete mode 100644 frontend/src/lib/components/dashboard/packages/columns.ts delete mode 100644 frontend/src/lib/components/dashboard/packages/create-edit-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/packages/data-table-actions.svelte delete mode 100644 frontend/src/lib/components/dashboard/packages/data-table.svelte delete mode 100644 frontend/src/lib/components/dashboard/pedimentos/columns.ts delete mode 100644 frontend/src/lib/components/dashboard/pedimentos/data-table-actions.svelte delete mode 100644 frontend/src/lib/components/dashboard/pedimentos/data-table.svelte delete mode 100644 frontend/src/lib/components/dashboard/pedimentos/edit/accounts-compensation-tab-form.svelte delete mode 100644 frontend/src/lib/components/dashboard/pedimentos/edit/contributions-tab-form.svelte delete mode 100644 frontend/src/lib/components/dashboard/pedimentos/edit/dates-tab-form.svelte delete mode 100644 frontend/src/lib/components/dashboard/pedimentos/edit/digitization-tab-form.svelte delete mode 100644 frontend/src/lib/components/dashboard/pedimentos/edit/discharge-tab-form.svelte delete mode 100644 frontend/src/lib/components/dashboard/pedimentos/edit/general-tab-form.svelte delete mode 100644 frontend/src/lib/components/dashboard/pedimentos/edit/identifiers-tab-form.svelte delete mode 100644 frontend/src/lib/components/dashboard/pedimentos/edit/invoices-tab-form.svelte delete mode 100644 frontend/src/lib/components/dashboard/pedimentos/edit/items-tab-form.svelte delete mode 100644 frontend/src/lib/components/dashboard/pedimentos/edit/other-data-tab-form.svelte delete mode 100644 frontend/src/lib/components/dashboard/pedimentos/edit/package-transportation-tab-form.svelte delete mode 100644 frontend/src/lib/components/dashboard/pedimentos/edit/payments-tab-form.svelte delete mode 100644 frontend/src/lib/components/dashboard/pedimentos/edit/pedimento-selector-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/pedimentos/edit/temp.svelte delete mode 100644 frontend/src/lib/components/dashboard/pedimentos/edit/transport-tab-form.svelte delete mode 100644 frontend/src/lib/components/dashboard/pedimentos/edit/validation-tab-form.svelte delete mode 100644 frontend/src/lib/components/dashboard/ports/columns.ts delete mode 100644 frontend/src/lib/components/dashboard/ports/create-edit-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/ports/data-table-actions.svelte delete mode 100644 frontend/src/lib/components/dashboard/ports/delete-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/code_pedimento_regimens/columns.ts delete mode 100644 frontend/src/lib/components/dashboard/reference_data/code_pedimento_regimens/create-edit-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/code_pedimento_regimens/data-table-actions.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/code_pedimento_regimens/data-table.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/code_pedimento_regimens/delete-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/code_pedimento_regimens/details-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/containers/columns.ts delete mode 100644 frontend/src/lib/components/dashboard/reference_data/containers/create-edit-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/containers/data-table-actions.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/containers/data-table.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/containers/delete-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/containers/details-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/countries/columns.ts delete mode 100644 frontend/src/lib/components/dashboard/reference_data/countries/create-edit-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/countries/data-table-actions.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/countries/data-table.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/countries/delete-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/countries/details-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/currency_types/columns.ts delete mode 100644 frontend/src/lib/components/dashboard/reference_data/currency_types/create-edit-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/currency_types/data-table-actions.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/currency_types/data-table.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/currency_types/delete-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/currency_types/details-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/customs_sections/columns.ts delete mode 100644 frontend/src/lib/components/dashboard/reference_data/customs_sections/create-edit-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/customs_sections/data-table-actions.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/customs_sections/data-table.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/customs_sections/delete-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/customs_sections/details-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/customs_warehouses/columns.ts delete mode 100644 frontend/src/lib/components/dashboard/reference_data/customs_warehouses/create-edit-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/customs_warehouses/data-table-actions.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/customs_warehouses/data-table.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/customs_warehouses/delete-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/customs_warehouses/details-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/document_types_digitization/columns.ts delete mode 100644 frontend/src/lib/components/dashboard/reference_data/incoterms/columns.ts delete mode 100644 frontend/src/lib/components/dashboard/reference_data/incoterms/create-edit-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/incoterms/data-table-actions.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/incoterms/data-table.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/incoterms/delete-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/incoterms/details-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/invoice_types/columns.ts delete mode 100644 frontend/src/lib/components/dashboard/reference_data/invoice_types/create-edit-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/invoice_types/data-table-actions.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/invoice_types/data-table.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/invoice_types/delete-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/invoice_types/details-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/material_types/columns.ts delete mode 100644 frontend/src/lib/components/dashboard/reference_data/material_types/create-edit-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/material_types/data-table-actions.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/material_types/data-table.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/material_types/delete-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/material_types/details-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/payment_methods/columns.ts delete mode 100644 frontend/src/lib/components/dashboard/reference_data/payment_methods/create-edit-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/payment_methods/data-table-actions.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/payment_methods/data-table.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/payment_methods/delete-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/payment_methods/details-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/pedimento_codes/columns.ts delete mode 100644 frontend/src/lib/components/dashboard/reference_data/pedimento_codes/create-edit-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/pedimento_codes/data-table-actions.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/pedimento_codes/data-table.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/pedimento_codes/delete-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/pedimento_codes/details-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/pedimento_regimens/columns.ts delete mode 100644 frontend/src/lib/components/dashboard/reference_data/pedimento_regimens/create-edit-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/pedimento_regimens/data-table-actions.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/pedimento_regimens/data-table.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/pedimento_regimens/delete-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/pedimento_regimens/details-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/sectors/columns.ts delete mode 100644 frontend/src/lib/components/dashboard/reference_data/sectors/create-edit-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/sectors/data-table-actions.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/sectors/data-table.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/sectors/delete-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/sectors/details-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/states/columns.ts delete mode 100644 frontend/src/lib/components/dashboard/reference_data/states/create-edit-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/states/data-table-actions.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/states/data-table.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/states/delete-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/states/details-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/transport_modes/columns.ts delete mode 100644 frontend/src/lib/components/dashboard/reference_data/transport_modes/create-edit-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/transport_modes/data-table-actions.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/transport_modes/data-table.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/transport_modes/delete-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/transport_modes/details-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/transport_types/columns.ts delete mode 100644 frontend/src/lib/components/dashboard/reference_data/transport_types/create-edit-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/transport_types/data-table-actions.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/transport_types/data-table.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/transport_types/delete-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/transport_types/details-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/valuation_methods/columns.ts delete mode 100644 frontend/src/lib/components/dashboard/reference_data/valuation_methods/create-edit-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/valuation_methods/data-table-actions.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/valuation_methods/data-table.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/valuation_methods/delete-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/reference_data/valuation_methods/details-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/reports/downloaded-parts-form.svelte delete mode 100644 frontend/src/lib/components/dashboard/transportation/drivers/columns.ts delete mode 100644 frontend/src/lib/components/dashboard/transportation/drivers/create-edit-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/transportation/drivers/data-table-actions.svelte delete mode 100644 frontend/src/lib/components/dashboard/transportation/drivers/data-table.svelte delete mode 100644 frontend/src/lib/components/dashboard/transportation/trailers/columns.ts delete mode 100644 frontend/src/lib/components/dashboard/transportation/trailers/create-edit-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/transportation/trailers/data-table-actions.svelte delete mode 100644 frontend/src/lib/components/dashboard/transportation/trailers/data-table.svelte delete mode 100644 frontend/src/lib/components/dashboard/transportation/transporters/create-edit-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/transportation/transporters/data-table-actions.svelte delete mode 100644 frontend/src/lib/components/dashboard/transportation/transporters/data-table.svelte delete mode 100644 frontend/src/lib/components/dashboard/transportation/transporters/transporter-columns.ts delete mode 100644 frontend/src/lib/components/dashboard/transportation/vehicles/columns.ts delete mode 100644 frontend/src/lib/components/dashboard/transportation/vehicles/create-edit-dialog.svelte delete mode 100644 frontend/src/lib/components/dashboard/transportation/vehicles/data-table-actions.svelte delete mode 100644 frontend/src/lib/components/dashboard/transportation/vehicles/data-table.svelte delete mode 100644 frontend/src/lib/config/csv-upload.ts delete mode 100644 frontend/src/lib/config/invoice-item-visibility.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/a76/digitalizacion/list.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/clients_and_providers/edit.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/clients_and_providers/list.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/customs_brokers/edit.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/customs_brokers/list.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/general_catalogs/classification/edit.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/general_catalogs/classification/list.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/general_catalogs/common/factory.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/general_catalogs/company_information/edit.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/general_catalogs/company_information/list.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/general_catalogs/concepts/edit.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/general_catalogs/concepts/list.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/general_catalogs/customs_broker_concepts/edit.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/general_catalogs/customs_broker_concepts/list.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/general_catalogs/doda/edit.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/general_catalogs/doda/list.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/general_catalogs/drivers/edit.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/general_catalogs/drivers/list.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/general_catalogs/electronic_notices/edit.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/general_catalogs/electronic_notices/list.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/general_catalogs/equivalencies/edit.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/general_catalogs/equivalencies/list.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/general_catalogs/error_catalogs/edit.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/general_catalogs/error_catalogs/list.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/general_catalogs/exchange_rate/edit.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/general_catalogs/exchange_rate/list.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/general_catalogs/identifiers/edit.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/general_catalogs/identifiers/list.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/general_catalogs/inpc/edit.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/general_catalogs/inpc/list.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/general_catalogs/legends/edit.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/general_catalogs/legends/list.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/general_catalogs/locations/edit.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/general_catalogs/locations/list.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/general_catalogs/multi_currency_types/edit.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/general_catalogs/multi_currency_types/list.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/general_catalogs/packages/edit.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/general_catalogs/packages/list.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/general_catalogs/ports/edit.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/general_catalogs/ports/list.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/general_catalogs/prevalidators/edit.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/general_catalogs/prevalidators/list.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/general_catalogs/seal/edit.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/general_catalogs/seal/list.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/general_catalogs/signatures/edit.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/general_catalogs/signatures/list.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/general_catalogs/tariff_fractions/list.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/general_catalogs/unit_conversions/edit.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/general_catalogs/unit_conversions/list.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/general_catalogs/units_of_measure/ace/edit.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/general_catalogs/units_of_measure/ace/list.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/general_catalogs/units_of_measure/american/edit.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/general_catalogs/units_of_measure/american/list.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/general_catalogs/units_of_measure/commercial/edit.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/general_catalogs/units_of_measure/commercial/list.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/general_catalogs/units_of_measure/customs/edit.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/general_catalogs/units_of_measure/customs/list.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/general_catalogs/units_of_measure/general/edit.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/general_catalogs/units_of_measure/general/list.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/general_catalogs/units_of_measure/oma/edit.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/general_catalogs/units_of_measure/oma/list.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/goods/edit.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/goods/list.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/invoices/edit.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/invoices/item/fixed_asset.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/invoices/item/inventory.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/invoices/item/items_tab.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/invoices/item/list.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/invoices/list.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/pedimentos/edit.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/pedimentos/list.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/pedimentos/tabs/contributions.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/pedimentos/tabs/general.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/pedimentos/tabs/other_data.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/reference_data/code_pedimento_regimens/list.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/reference_data/common/factory.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/reference_data/containers/list.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/reference_data/countries/list.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/reference_data/currency_types/list.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/reference_data/customs_sections/list.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/reference_data/customs_warehouses/list.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/reference_data/incoterms/list.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/reference_data/invoice_types/list.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/reference_data/material_types/list.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/reference_data/payment_methods/list.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/reference_data/pedimento_codes/list.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/reference_data/pedimento_regimens/list.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/reference_data/sectors/list.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/reference_data/states/list.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/reference_data/transport_modes/list.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/reference_data/transport_types/list.ts delete mode 100644 frontend/src/lib/config/shortcuts/dashboard/reference_data/valuation_methods/list.ts delete mode 100644 frontend/src/lib/csv-import-commit-metrics.test.ts delete mode 100644 frontend/src/lib/csv-import-commit-metrics.ts delete mode 100644 frontend/src/lib/csv-import-pending.ts delete mode 100644 frontend/src/lib/csv-import-session.ts delete mode 100644 frontend/src/lib/csv-import-status-api.test.ts delete mode 100644 frontend/src/lib/csv-import-status-api.ts delete mode 100644 frontend/src/lib/i18n/doda-form-strings.ts delete mode 100644 frontend/src/lib/permissions/catalogs/broker-concepts-permissions.ts delete mode 100644 frontend/src/lib/permissions/catalogs/classification-concepts-permissions.ts delete mode 100644 frontend/src/lib/permissions/catalogs/company-catalog-permissions.ts delete mode 100644 frontend/src/lib/permissions/catalogs/concepts-permissions.ts delete mode 100644 frontend/src/lib/permissions/catalogs/depreciation-catalog-permissions.ts delete mode 100644 frontend/src/lib/permissions/catalogs/doda-permissions.ts delete mode 100644 frontend/src/lib/permissions/catalogs/drivers-permissions.ts delete mode 100644 frontend/src/lib/permissions/catalogs/electronic-notices-permissions.ts delete mode 100644 frontend/src/lib/permissions/catalogs/equivalencies-permissions.ts delete mode 100644 frontend/src/lib/permissions/catalogs/error-catalogs-permissions.ts delete mode 100644 frontend/src/lib/permissions/catalogs/exchange-rates-permissions.ts delete mode 100644 frontend/src/lib/permissions/catalogs/identifiers-permissions.ts delete mode 100644 frontend/src/lib/permissions/catalogs/inpc-permissions.ts delete mode 100644 frontend/src/lib/permissions/catalogs/legends-permissions.ts delete mode 100644 frontend/src/lib/permissions/catalogs/locations-catalog-permissions.ts delete mode 100644 frontend/src/lib/permissions/catalogs/multi-currency-types-permissions.ts delete mode 100644 frontend/src/lib/permissions/catalogs/packages-permissions.ts delete mode 100644 frontend/src/lib/permissions/catalogs/ports-permissions.ts delete mode 100644 frontend/src/lib/permissions/catalogs/prevalidators-permissions.ts delete mode 100644 frontend/src/lib/permissions/catalogs/seals-permissions.ts delete mode 100644 frontend/src/lib/permissions/catalogs/sectors-catalog-permissions.ts delete mode 100644 frontend/src/lib/permissions/catalogs/signatures-permissions.ts delete mode 100644 frontend/src/lib/permissions/catalogs/trailers-permissions.ts delete mode 100644 frontend/src/lib/permissions/catalogs/transporters-permissions.ts delete mode 100644 frontend/src/lib/permissions/catalogs/um-ace-permissions.ts delete mode 100644 frontend/src/lib/permissions/catalogs/um-american-permissions.ts delete mode 100644 frontend/src/lib/permissions/catalogs/um-customs-permissions.ts delete mode 100644 frontend/src/lib/permissions/catalogs/um-general-permissions.ts delete mode 100644 frontend/src/lib/permissions/catalogs/um-oma-permissions.ts delete mode 100644 frontend/src/lib/permissions/catalogs/unit-conversions-permissions.ts delete mode 100644 frontend/src/lib/permissions/catalogs/vehicles-permissions.ts delete mode 100644 frontend/src/lib/permissions/clients-providers-permissions.ts delete mode 100644 frontend/src/lib/permissions/customs-broker-permissions.ts delete mode 100644 frontend/src/lib/permissions/goods-classes-permissions.ts delete mode 100644 frontend/src/lib/permissions/goods-parts-permissions.ts delete mode 100644 frontend/src/lib/permissions/invoice-permissions.ts delete mode 100644 frontend/src/lib/permissions/pedimento-permissions.ts delete mode 100644 frontend/src/lib/permissions/reference-data/containers-ref-permissions.ts delete mode 100644 frontend/src/lib/permissions/reference-data/countries-ref-permissions.ts delete mode 100644 frontend/src/lib/permissions/reference-data/currency-types-ref-permissions.ts delete mode 100644 frontend/src/lib/permissions/reference-data/customs-sections-ref-permissions.ts delete mode 100644 frontend/src/lib/permissions/reference-data/customs-warehouses-ref-permissions.ts delete mode 100644 frontend/src/lib/permissions/reference-data/incoterms-ref-permissions.ts delete mode 100644 frontend/src/lib/permissions/reference-data/invoice-types-ref-permissions.ts delete mode 100644 frontend/src/lib/permissions/reference-data/material-types-ref-permissions.ts delete mode 100644 frontend/src/lib/permissions/reference-data/payment-methods-ref-permissions.ts delete mode 100644 frontend/src/lib/permissions/reference-data/pedimento-codes-ref-permissions.ts delete mode 100644 frontend/src/lib/permissions/reference-data/pedimento-regimens-ref-permissions.ts delete mode 100644 frontend/src/lib/permissions/reference-data/sectors-ref-permissions.ts delete mode 100644 frontend/src/lib/permissions/reference-data/states-ref-permissions.ts delete mode 100644 frontend/src/lib/permissions/reference-data/transport-modes-ref-permissions.ts delete mode 100644 frontend/src/lib/permissions/reference-data/transport-types-ref-permissions.ts delete mode 100644 frontend/src/lib/permissions/reference-data/valuation-methods-ref-permissions.ts delete mode 100644 frontend/src/lib/permissions/tariff-fraction-permissions.ts delete mode 100644 frontend/src/lib/utils.getInvoiceTypeColor.test.ts delete mode 100644 frontend/src/lib/utils/audit-log-i18n.ts delete mode 100644 frontend/src/lib/utils/items-logic.ts delete mode 100644 frontend/src/lib/utils/mexican-tariff-fraction.test.ts delete mode 100644 frontend/src/lib/utils/mexican-tariff-fraction.ts delete mode 100644 frontend/src/lib/utils/partida-notice-interact-outside.ts delete mode 100644 frontend/src/lib/utils/tariff-fraction-display.test.ts delete mode 100644 frontend/src/lib/utils/tariff-fraction-display.ts delete mode 100644 frontend/src/routes/api-sveltekit/classes/+server.ts delete mode 100644 frontend/src/routes/api-sveltekit/classes/[id]/+server.ts delete mode 100644 frontend/src/routes/api-sveltekit/countries/+server.ts delete mode 100644 frontend/src/routes/api-sveltekit/fda-catalog/+server.ts delete mode 100644 frontend/src/routes/api-sveltekit/fda-catalog/[id]/+server.ts delete mode 100644 frontend/src/routes/api-sveltekit/fda-catalog/[id]/affirmation-codes/+server.ts delete mode 100644 frontend/src/routes/api-sveltekit/fda-catalog/[id]/constituent-elements/+server.ts delete mode 100644 frontend/src/routes/api-sveltekit/fda-catalog/[id]/lot-productions/+server.ts delete mode 100644 frontend/src/routes/api-sveltekit/fda-catalog/[id]/specifications/+server.ts delete mode 100644 frontend/src/routes/api-sveltekit/historical-tariff-fractions/rate/+server.ts delete mode 100644 frontend/src/routes/api-sveltekit/packages/+server.ts delete mode 100644 frontend/src/routes/api-sveltekit/parts/+server.ts delete mode 100644 frontend/src/routes/api-sveltekit/parts/[id]/+server.ts delete mode 100644 frontend/src/routes/api-sveltekit/payment-methods/+server.ts delete mode 100644 frontend/src/routes/api-sveltekit/tariff-fractions/+server.ts delete mode 100644 frontend/src/routes/api-sveltekit/units-of-measure/+server.ts delete mode 100644 frontend/src/routes/api-sveltekit/units-of-measure/[id]/+server.ts delete mode 100644 frontend/src/routes/dashboard/admin/+layout.svelte delete mode 100644 frontend/src/routes/dashboard/admin/roles/+page.svelte delete mode 100644 frontend/src/routes/dashboard/audit_logs/+page.svelte delete mode 100644 frontend/src/routes/dashboard/audit_logs/+page.ts delete mode 100644 frontend/src/routes/dashboard/audit_logs/bitacora-tab.svelte delete mode 100644 frontend/src/routes/dashboard/audit_logs/files-tab.svelte delete mode 100644 frontend/src/routes/dashboard/audit_logs/tasks-tab.svelte delete mode 100644 frontend/src/routes/dashboard/clients_and_providers/+page.server.ts delete mode 100644 frontend/src/routes/dashboard/clients_and_providers/+page.svelte delete mode 100644 frontend/src/routes/dashboard/clients_and_providers/edit/[[id]]/+page.svelte delete mode 100644 frontend/src/routes/dashboard/csv-upload/+page.svelte delete mode 100644 frontend/src/routes/dashboard/customs_brokers/+page.server.ts delete mode 100644 frontend/src/routes/dashboard/customs_brokers/+page.svelte delete mode 100644 frontend/src/routes/dashboard/customs_brokers/edit/[[id]]/+page.svelte delete mode 100644 frontend/src/routes/dashboard/despacho/digitalizacion/+page.svelte delete mode 100644 frontend/src/routes/dashboard/despacho/doda/+page.server.ts delete mode 100644 frontend/src/routes/dashboard/despacho/doda/+page.svelte delete mode 100644 frontend/src/routes/dashboard/despacho/doda/edit/[[id]]/+page.ts delete mode 100644 frontend/src/routes/dashboard/export/catalog/+page.svelte delete mode 100644 frontend/src/routes/dashboard/export/destruction/+page.svelte delete mode 100644 frontend/src/routes/dashboard/export/manifest/+page.server.ts delete mode 100644 frontend/src/routes/dashboard/export/manifest/+page.svelte delete mode 100644 frontend/src/routes/dashboard/export/manifest/edit/[[id]]/+page.svelte delete mode 100644 frontend/src/routes/dashboard/export/proforma/+page.svelte delete mode 100644 frontend/src/routes/dashboard/export/repair/+page.svelte delete mode 100644 frontend/src/routes/dashboard/export/reports/+page.svelte delete mode 100644 frontend/src/routes/dashboard/export/special-processes/+page.svelte delete mode 100644 frontend/src/routes/dashboard/export/used-materials/+page.svelte delete mode 100644 frontend/src/routes/dashboard/general_catalogs/+page.svelte delete mode 100644 frontend/src/routes/dashboard/general_catalogs/classification_concepts/+page.server.ts delete mode 100644 frontend/src/routes/dashboard/general_catalogs/classification_concepts/+page.svelte delete mode 100644 frontend/src/routes/dashboard/general_catalogs/company_information/+page.server.ts delete mode 100644 frontend/src/routes/dashboard/general_catalogs/company_information/+page.svelte delete mode 100644 frontend/src/routes/dashboard/general_catalogs/company_information/edit/[[id]]/+page.svelte delete mode 100644 frontend/src/routes/dashboard/general_catalogs/concepts/+page.server.ts delete mode 100644 frontend/src/routes/dashboard/general_catalogs/concepts/+page.svelte delete mode 100644 frontend/src/routes/dashboard/general_catalogs/customs_broker_concepts/+page.server.ts delete mode 100644 frontend/src/routes/dashboard/general_catalogs/customs_broker_concepts/+page.svelte delete mode 100644 frontend/src/routes/dashboard/general_catalogs/doda/+page.server.ts delete mode 100644 frontend/src/routes/dashboard/general_catalogs/doda/+page.svelte delete mode 100644 frontend/src/routes/dashboard/general_catalogs/doda/edit/[[id]]/+page.svelte delete mode 100644 frontend/src/routes/dashboard/general_catalogs/doda/edit/[[id]]/+page.ts delete mode 100644 frontend/src/routes/dashboard/general_catalogs/drivers/+page.server.ts delete mode 100644 frontend/src/routes/dashboard/general_catalogs/drivers/+page.svelte delete mode 100644 frontend/src/routes/dashboard/general_catalogs/electronic_notices/+page.server.ts delete mode 100644 frontend/src/routes/dashboard/general_catalogs/electronic_notices/+page.svelte delete mode 100644 frontend/src/routes/dashboard/general_catalogs/equivalencies/+page.server.ts delete mode 100644 frontend/src/routes/dashboard/general_catalogs/equivalencies/+page.svelte delete mode 100644 frontend/src/routes/dashboard/general_catalogs/error_catalogs/+page.server.ts delete mode 100644 frontend/src/routes/dashboard/general_catalogs/error_catalogs/+page.svelte delete mode 100644 frontend/src/routes/dashboard/general_catalogs/exchange-rate/+page.server.ts delete mode 100644 frontend/src/routes/dashboard/general_catalogs/exchange-rate/+page.svelte delete mode 100644 frontend/src/routes/dashboard/general_catalogs/exchange-rate/new/+page.svelte delete mode 100644 frontend/src/routes/dashboard/general_catalogs/identifiers/+page.server.ts delete mode 100644 frontend/src/routes/dashboard/general_catalogs/identifiers/+page.svelte delete mode 100644 frontend/src/routes/dashboard/general_catalogs/inpc/+page.server.ts delete mode 100644 frontend/src/routes/dashboard/general_catalogs/inpc/+page.svelte delete mode 100644 frontend/src/routes/dashboard/general_catalogs/legends/+page.server.ts delete mode 100644 frontend/src/routes/dashboard/general_catalogs/legends/+page.svelte delete mode 100644 frontend/src/routes/dashboard/general_catalogs/locations/+page.svelte delete mode 100644 frontend/src/routes/dashboard/general_catalogs/multi_currency_types/+page.server.ts delete mode 100644 frontend/src/routes/dashboard/general_catalogs/multi_currency_types/+page.svelte delete mode 100644 frontend/src/routes/dashboard/general_catalogs/packages/+page.server.ts delete mode 100644 frontend/src/routes/dashboard/general_catalogs/packages/+page.svelte delete mode 100644 frontend/src/routes/dashboard/general_catalogs/ports/+page.server.ts delete mode 100644 frontend/src/routes/dashboard/general_catalogs/ports/+page.svelte delete mode 100644 frontend/src/routes/dashboard/general_catalogs/prevalidators/+page.server.ts delete mode 100644 frontend/src/routes/dashboard/general_catalogs/prevalidators/+page.svelte delete mode 100644 frontend/src/routes/dashboard/general_catalogs/seal/+page.server.ts delete mode 100644 frontend/src/routes/dashboard/general_catalogs/seal/+page.svelte delete mode 100644 frontend/src/routes/dashboard/general_catalogs/sectors/+page.svelte delete mode 100644 frontend/src/routes/dashboard/general_catalogs/signatures/+page.server.ts delete mode 100644 frontend/src/routes/dashboard/general_catalogs/signatures/+page.svelte delete mode 100644 frontend/src/routes/dashboard/general_catalogs/tariff-fractions/+page.svelte delete mode 100644 frontend/src/routes/dashboard/general_catalogs/tariff-fractions/american/+page.svelte delete mode 100644 frontend/src/routes/dashboard/general_catalogs/tariff-fractions/american/+page.ts delete mode 100644 frontend/src/routes/dashboard/general_catalogs/tariff-fractions/canadian/+page.svelte delete mode 100644 frontend/src/routes/dashboard/general_catalogs/tariff-fractions/historical/+page.svelte delete mode 100644 frontend/src/routes/dashboard/general_catalogs/tariff-fractions/seventh-amendment/+page.svelte delete mode 100644 frontend/src/routes/dashboard/general_catalogs/tariff-fractions/sitar/+page.svelte delete mode 100644 frontend/src/routes/dashboard/general_catalogs/tariff-fractions/us/+page.svelte delete mode 100644 frontend/src/routes/dashboard/general_catalogs/trailers/+page.svelte delete mode 100644 frontend/src/routes/dashboard/general_catalogs/transporters/+page.svelte delete mode 100644 frontend/src/routes/dashboard/general_catalogs/unit_conversions/+page.server.ts delete mode 100644 frontend/src/routes/dashboard/general_catalogs/unit_conversions/+page.svelte delete mode 100644 frontend/src/routes/dashboard/general_catalogs/units_of_measure/ace/+page.server.ts delete mode 100644 frontend/src/routes/dashboard/general_catalogs/units_of_measure/ace/+page.svelte delete mode 100644 frontend/src/routes/dashboard/general_catalogs/units_of_measure/american/+page.server.ts delete mode 100644 frontend/src/routes/dashboard/general_catalogs/units_of_measure/american/+page.svelte delete mode 100644 frontend/src/routes/dashboard/general_catalogs/units_of_measure/customs/+page.server.ts delete mode 100644 frontend/src/routes/dashboard/general_catalogs/units_of_measure/customs/+page.svelte delete mode 100644 frontend/src/routes/dashboard/general_catalogs/units_of_measure/general/+page.server.ts delete mode 100644 frontend/src/routes/dashboard/general_catalogs/units_of_measure/general/+page.svelte delete mode 100644 frontend/src/routes/dashboard/general_catalogs/units_of_measure/oma/+page.server.ts delete mode 100644 frontend/src/routes/dashboard/general_catalogs/units_of_measure/oma/+page.svelte delete mode 100644 frontend/src/routes/dashboard/general_catalogs/vehicles/+page.svelte delete mode 100644 frontend/src/routes/dashboard/goods/+page.ts delete mode 100644 frontend/src/routes/dashboard/goods/fda-codes/+page.svelte delete mode 100644 frontend/src/routes/dashboard/goods/fda-codes/edit/[id]/+page.svelte delete mode 100644 frontend/src/routes/dashboard/goods/fixed-asset-classes/+page.svelte delete mode 100644 frontend/src/routes/dashboard/goods/parts/+page.server.ts delete mode 100644 frontend/src/routes/dashboard/goods/parts/+page.svelte delete mode 100644 frontend/src/routes/dashboard/goods/parts/edit/[[id]]/+page.server.ts delete mode 100644 frontend/src/routes/dashboard/goods/parts/edit/[[id]]/+page.svelte delete mode 100644 frontend/src/routes/dashboard/goods/parts/extension/[[id]]/+page.svelte delete mode 100644 frontend/src/routes/dashboard/help-center/+page.svelte delete mode 100644 frontend/src/routes/dashboard/help-center/[uuid]/+page.svelte delete mode 100644 frontend/src/routes/dashboard/help-center/editor/[uuid]/+page.svelte delete mode 100644 frontend/src/routes/dashboard/invoices/+page.server.ts delete mode 100644 frontend/src/routes/dashboard/invoices/+page.svelte delete mode 100644 frontend/src/routes/dashboard/invoices/edit/[id]/+page.server.ts delete mode 100644 frontend/src/routes/dashboard/invoices/edit/[id]/+page.svelte delete mode 100644 frontend/src/routes/dashboard/invoices/items/presets/+page.svelte delete mode 100644 frontend/src/routes/dashboard/invoices/settings/+page.server.ts delete mode 100644 frontend/src/routes/dashboard/invoices/settings/+page.svelte delete mode 100644 frontend/src/routes/dashboard/pedimentos/+page.server.ts delete mode 100644 frontend/src/routes/dashboard/pedimentos/+page.svelte delete mode 100644 frontend/src/routes/dashboard/pedimentos/edit/[id]/+page.server.ts delete mode 100644 frontend/src/routes/dashboard/pedimentos/edit/[id]/+page.svelte delete mode 100644 frontend/src/routes/dashboard/reference_data/+page.svelte delete mode 100644 frontend/src/routes/dashboard/reference_data/code_pedimento_regimens/+page.server.ts delete mode 100644 frontend/src/routes/dashboard/reference_data/code_pedimento_regimens/+page.svelte delete mode 100644 frontend/src/routes/dashboard/reference_data/containers/+page.server.ts delete mode 100644 frontend/src/routes/dashboard/reference_data/containers/+page.svelte delete mode 100644 frontend/src/routes/dashboard/reference_data/countries/+page.server.ts delete mode 100644 frontend/src/routes/dashboard/reference_data/countries/+page.svelte delete mode 100644 frontend/src/routes/dashboard/reference_data/currency_types/+page.server.ts delete mode 100644 frontend/src/routes/dashboard/reference_data/currency_types/+page.svelte delete mode 100644 frontend/src/routes/dashboard/reference_data/customs_sections/+page.server.ts delete mode 100644 frontend/src/routes/dashboard/reference_data/customs_sections/+page.svelte delete mode 100644 frontend/src/routes/dashboard/reference_data/customs_warehouses/+page.server.ts delete mode 100644 frontend/src/routes/dashboard/reference_data/customs_warehouses/+page.svelte delete mode 100644 frontend/src/routes/dashboard/reference_data/document_types_digitization/+page.server.ts delete mode 100644 frontend/src/routes/dashboard/reference_data/document_types_digitization/+page.svelte delete mode 100644 frontend/src/routes/dashboard/reference_data/incoterms/+page.server.ts delete mode 100644 frontend/src/routes/dashboard/reference_data/incoterms/+page.svelte delete mode 100644 frontend/src/routes/dashboard/reference_data/invoice_types/+page.server.ts delete mode 100644 frontend/src/routes/dashboard/reference_data/invoice_types/+page.svelte delete mode 100644 frontend/src/routes/dashboard/reference_data/material_types/+page.server.ts delete mode 100644 frontend/src/routes/dashboard/reference_data/material_types/+page.svelte delete mode 100644 frontend/src/routes/dashboard/reference_data/payment_methods/+page.server.ts delete mode 100644 frontend/src/routes/dashboard/reference_data/payment_methods/+page.svelte delete mode 100644 frontend/src/routes/dashboard/reference_data/pedimento_codes/+page.server.ts delete mode 100644 frontend/src/routes/dashboard/reference_data/pedimento_codes/+page.svelte delete mode 100644 frontend/src/routes/dashboard/reference_data/pedimento_regimens/+page.server.ts delete mode 100644 frontend/src/routes/dashboard/reference_data/pedimento_regimens/+page.svelte delete mode 100644 frontend/src/routes/dashboard/reference_data/sectors/+page.server.ts delete mode 100644 frontend/src/routes/dashboard/reference_data/sectors/+page.svelte delete mode 100644 frontend/src/routes/dashboard/reference_data/states/+page.server.ts delete mode 100644 frontend/src/routes/dashboard/reference_data/states/+page.svelte delete mode 100644 frontend/src/routes/dashboard/reference_data/transport_modes/+page.server.ts delete mode 100644 frontend/src/routes/dashboard/reference_data/transport_modes/+page.svelte delete mode 100644 frontend/src/routes/dashboard/reference_data/transport_types/+page.server.ts delete mode 100644 frontend/src/routes/dashboard/reference_data/transport_types/+page.svelte delete mode 100644 frontend/src/routes/dashboard/reference_data/valuation_methods/+page.server.ts delete mode 100644 frontend/src/routes/dashboard/reference_data/valuation_methods/+page.svelte delete mode 100644 frontend/src/routes/dashboard/reports/invoices/+page.server.ts delete mode 100644 frontend/src/routes/dashboard/reports/invoices/+page.svelte delete mode 100644 frontend/src/routes/dashboard/reports/vencimiento/+page.server.ts delete mode 100644 frontend/src/routes/dashboard/reports/vencimiento/+page.svelte create mode 100644 frontend/src/routes/dashboard/roles/+page.server.ts create mode 100644 frontend/src/routes/dashboard/roles/+page.svelte delete mode 100644 reinicio.sh create mode 100755 scripts/auth-mode.sh delete mode 100755 scripts/backend-entrypoint.sh delete mode 100755 scripts/health-check.sh delete mode 100755 scripts/init_first_time.sh delete mode 100755 scripts/keycloak-entrypoint.sh delete mode 100644 scripts/run_layout_import_janitor.sh delete mode 100755 scripts/wait-for-jenkins-stack.sh delete mode 100755 start.sh diff --git a/.env.example b/.env.example index 2a99235c..e3663b4c 100644 --- a/.env.example +++ b/.env.example @@ -14,9 +14,9 @@ KEYCLOAK_ADMIN_PASSWORD=admin # ----- Keycloak Configuración ----- KEYCLOAK_REALM=master -KEYCLOAK_CLIENT_ID=anexo76-backend +KEYCLOAK_CLIENT_ID=app-backend KEYCLOAK_CLIENT_SECRET=dev-secret -KEYCLOAK_FRONTEND_CLIENT_ID=anexo76-frontend +KEYCLOAK_FRONTEND_CLIENT_ID=app-frontend # ----- Backend ----- DEBUG=True @@ -59,7 +59,7 @@ VITE_HUB_URL=http://localhost:3001 APP_PUBLIC_URL=http://localhost:5173 VITE_KEYCLOAK_REALM=master VITE_KEYCLOAK_URL=http://localhost:8080/kcauth -VITE_KEYCLOAK_CLIENT_ID=anexo76-frontend +VITE_KEYCLOAK_CLIENT_ID=app-frontend #------ Celery / Valkey ---------- VALKEY_URL=redis://valkey:6379/0 diff --git a/backend/.env.example b/backend/.env.example index e73a096e..43973d96 100644 --- a/backend/.env.example +++ b/backend/.env.example @@ -1,9 +1,17 @@ # Application -APP_NAME=Anexo76 +APP_NAME=Mi Aplicación APP_VERSION=1.0.0 DEBUG=True ENVIRONMENT=development +# Auth local para desarrollo (sin Keycloak/Hub) +# Cambia a True para entrar sin workspace. NUNCA en producción. +DEV_LOCAL_AUTH=False +DEV_LOCAL_AUTH_EMAIL=dev@local.test +DEV_LOCAL_AUTH_NAME=Dev User +DEV_LOCAL_AUTH_TENANT_ID=1 +DEV_LOCAL_AUTH_COMPANY_ID=1 + # Database - Core CORE_DB_HOST=localhost CORE_DB_PORT=5432 diff --git a/backend/alembic/env.py b/backend/alembic/env.py index c0314e02..f9dd3b18 100644 --- a/backend/alembic/env.py +++ b/backend/alembic/env.py @@ -301,7 +301,7 @@ modules_dir = os.path.join(BASE_DIR, "api", "v1", "modules") import_models_from_dir(modules_dir) # Tablas declaradas fuera de models.py / carpeta models/ (autogenerate) -import api.v1.modules.a76.general_catalogs.doda.alta_log_models # noqa: F401 +# Agrega aquí imports de models que no estén en archivos models.py estándar. def run_migrations_offline() -> None: diff --git a/backend/alembic/versions/8c9bad3da37f_seed_initial_data.py b/backend/alembic/versions/8c9bad3da37f_seed_initial_data.py index 2fb74df0..94ca24a6 100644 --- a/backend/alembic/versions/8c9bad3da37f_seed_initial_data.py +++ b/backend/alembic/versions/8c9bad3da37f_seed_initial_data.py @@ -4,477 +4,21 @@ Revision ID: 8c9bad3da37f Revises: 9db46c604463 Create Date: 2026-05-01 22:01:50.174319 +Nota: la migración original sembraba catálogos de referencia de Anexo 76. +En la plantilla este paso es un no-op — agrega tus seeds aquí si los necesitas. """ -# pylint: disable=no-member from typing import Sequence, Union -from alembic import op -from api.v1.modules.public.reference_data.code_pedimento_regimens.seed import ( - seed as code_pedimento_regimens_seed, -) -from api.v1.modules.public.reference_data.containers.seed import ( - seed as container_types_seed, -) -from api.v1.modules.public.reference_data.countries.seed import seed as countries_seed -from api.v1.modules.public.reference_data.currency_types.seed import ( - seed as currency_types_seed, -) -from api.v1.modules.public.reference_data.customs_sections.seed import ( - seed as customs_sections_seed, -) -from api.v1.modules.public.reference_data.customs_warehouses.seed import ( - seed as customs_warehouses_seed, -) -from api.v1.modules.public.reference_data.incoterms.seed import seed as incoterms_seed -from api.v1.modules.public.reference_data.invoice_types.seed import ( - seed as invoice_types_seed, -) -from api.v1.modules.public.reference_data.material_types.seed import ( - seed as material_types_seed, -) -from api.v1.modules.public.reference_data.payment_methods.seed import ( - seed as payment_methods_seed, -) -from api.v1.modules.public.reference_data.pedimento_codes.seed import ( - seed as pedimento_codes_seed, -) -from api.v1.modules.public.reference_data.pedimento_regimens.seed import ( - seed as pedimento_regimens_seed, -) -from api.v1.modules.public.reference_data.states.seed import seed as states_seed -from api.v1.modules.public.reference_data.transport_modes.seed import ( - seed as transport_modes_seed, -) -from api.v1.modules.public.reference_data.pedimento_transport_catalog.seed import ( - seed as pedimento_transport_catalog_seed, -) -from api.v1.modules.public.reference_data.transport_types.seed import ( - seed as transport_types_seed, -) -from api.v1.modules.public.reference_data.valuation_methods.seed import ( - seed as valuation_methods_seed, -) -from api.v1.modules.a76.general_catalogs.units_of_measure.seed import ( - seed as units_of_measure_seed, -) -from api.v1.modules.a76.general_catalogs.units_of_measure.seed_ace import ( - seed as ace_seed, -) -from api.v1.modules.a76.general_catalogs.units_of_measure.seed_oma import ( - seed as oma_seed, -) -from api.v1.modules.a76.general_catalogs.units_of_measure.seed_ame import ( - seed as ame_seed, -) -from api.v1.modules.a76.general_catalogs.units_of_measure.seed_adua import ( - seed as adua_seed, -) -from api.v1.modules.a76.general_catalogs.fractions.tariff_fractions.seed import ( - seed as tariff_fractions_seed, -) -from api.v1.modules.public.reference_data.trailer_types.seed import ( - seed as trailer_types_seed, -) -from api.v1.modules.core.permissions.seed_v2 import registry - -from api.v1.modules.public.reference_data.license_exceptions.seed import seed_license_exceptions -from api.v1.modules.public.reference_data.agency_tariff_codes.seed import seed_agency_tariff_codes -from api.v1.modules.public.reference_data.identifiers.seed import seed_identifiers -from api.v1.modules.public.reference_data.carta_porte_codes.seed import seed_carta_porte -from sqlalchemy.orm import Session - -# revision identifiers, used by Alembic. revision: str = "8c9bad3da37f" -down_revision: Union[str, Sequence[str], None] = "9db46c604463" +down_revision: Union[str, None] = "9db46c604463" branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None def upgrade() -> None: - """Catálogos de referencia y permisos base (datos).""" - - def format_value(val): - if val is None or str(val).strip() == "" or str(val).upper() == "NONE": - return "NULL" - return f"'{str(val).replace(chr(39), chr(39)*2)}'" - - # --- SEEDS PUBLIC (Tablas base) --- - values_pc = ", ".join( - [ - f"('{code}', '{desc.replace(chr(39), chr(39)*2)}')" - for code, desc in pedimento_codes_seed - ] - ) - op.execute( - f""" - INSERT INTO pedimento_codes (code, description) VALUES - {values_pc} - ON CONFLICT (code) DO NOTHING; - """ - ) - - values_pr = ", ".join( - [ - f"('{code}', '{desc.replace(chr(39), chr(39)*2)}')" - for code, desc in pedimento_regimens_seed - ] - ) - op.execute( - f""" - INSERT INTO public.pedimento_regimens (code, description) VALUES - {values_pr} - ON CONFLICT (code) DO NOTHING; - """ - ) - - values_cpr = ", ".join( - [ - f"('{ped_code}', '{reg_code}', '{type_code}')" - for ped_code, reg_code, type_code in code_pedimento_regimens_seed - ] - ) - op.execute( - f""" - INSERT INTO public.code_pedimento_regimens (pedimento_code, regimen_code, type_code) VALUES - {values_cpr} - """ - ) - - values_c = ", ".join( - [ - f"('{key}', '{desc.replace(chr(39), chr(39)*2)}')" - for key, desc in container_types_seed - ] - ) - op.execute( - f""" - INSERT INTO public.containers (key, description) VALUES - {values_c} - ON CONFLICT (key) DO NOTHING; - """ - ) - - values_country = ", ".join( - [ - f"('{m3_key}', '{mex_key}', '{ame_key}', '{desc_es.replace(chr(39), chr(39)*2)}', '{desc_en.replace(chr(39), chr(39)*2)}')" - for m3_key, mex_key, ame_key, desc_es, desc_en in countries_seed - ] - ) - op.execute( - f""" - INSERT INTO public.countries (m3_key, mex_key, ame_key, description_es, description_en) VALUES - {values_country} - ON CONFLICT (m3_key) DO NOTHING; - """ - ) - - values_ct = ", ".join( - [ - f"('{code}', '{currency_name.replace(chr(39), chr(39)*2)}', '{country_desc.replace(chr(39), chr(39)*2)}')" - for code, currency_name, country_desc in currency_types_seed - ] - ) - op.execute( - f""" - INSERT INTO public.currency_types (code, currency_name, country_description) VALUES - {values_ct} - ON CONFLICT (code) DO NOTHING; - """ - ) - - values_cs = ", ".join( - [ - f"('{code}', '{name.replace(chr(39), chr(39)*2)}')" - for code, name in customs_sections_seed - ] - ) - op.execute( - f""" - INSERT INTO public.customs_sections (customs_code, section_name) VALUES - {values_cs} - ON CONFLICT (customs_code) DO NOTHING; - """ - ) - - values_cw = ", ".join( - [ - f"('{key}', '{customs.replace(chr(39), chr(39)*2)}', '{fiscalized_warehouse.replace(chr(39), chr(39)*2)}')" - for key, customs, fiscalized_warehouse in customs_warehouses_seed - ] - ) - op.execute( - f""" - INSERT INTO public.customs_warehouses (key, customs, fiscalized_warehouse) - VALUES - {values_cw} - ON CONFLICT (key, customs) DO NOTHING; - """ - ) - - values_incoterms = ", ".join( - [ - f"('{code}', '{desc_es.replace(chr(39), chr(39)*2)}', '{desc_en.replace(chr(39), chr(39)*2)}')" - for code, desc_es, desc_en in incoterms_seed - ] - ) - op.execute( - f""" - INSERT INTO public.incoterms (code, description_es, description_en) VALUES - {values_incoterms} - ON CONFLICT (code) DO NOTHING; - """ - ) - - values_it = ", ".join( - [ - f"('{key}', '{desc.replace(chr(39), chr(39)*2)}', '{note.replace(chr(39), chr(39)*2)}', '{type.replace(chr(39), chr(39)*2)}', '{operation.replace(chr(39), chr(39)*2)}')" - for key, desc, note, type, operation in invoice_types_seed - ] - ) - op.execute( - f""" - INSERT INTO public.invoice_types (key, description, note, type, operation) VALUES - {values_it} - ON CONFLICT (key) DO NOTHING; - """ - ) - - values_mt = ", ".join( - [ - f"('{key}', '{desc.replace(chr(39), chr(39)*2)}', '{category.replace(chr(39), chr(39)*2)}')" - for key, desc, category in material_types_seed - ] - ) - op.execute( - f""" - INSERT INTO public.material_types (key, description, type) VALUES - {values_mt} - ON CONFLICT (key) DO NOTHING; - """ - ) - - values_pm = ", ".join( - [ - f"('{key}', '{desc.replace(chr(39), chr(39)*2)}')" - for key, desc in payment_methods_seed - ] - ) - op.execute( - f""" - INSERT INTO public.payment_methods (key, description) VALUES - {values_pm} - ON CONFLICT (key) DO NOTHING; - """ - ) - - values_tm = ", ".join( - [ - f"('{key}', '{name.replace(chr(39), chr(39)*2)}')" - for key, name in transport_modes_seed - ] - ) - op.execute( - f""" - INSERT INTO public.transport_modes (key, name) VALUES - {values_tm} - ON CONFLICT (key) DO NOTHING; - """ - ) - - values_ptc = ", ".join( - [ - f"('{code}', '{en.replace(chr(39), chr(39)*2)}', '{es.replace(chr(39), chr(39)*2)}', '{pdc}')" - for code, en, es, pdc in pedimento_transport_catalog_seed - ] - ) - op.execute( - f""" - INSERT INTO public.pedimento_transport_catalog (code, transport_en, transport_es, payment_date_code) VALUES - {values_ptc} - ON CONFLICT (code) DO NOTHING; - """ - ) - - values_tt = ", ".join( - [ - f"('{code}', '{desc.replace(chr(39), chr(39)*2)}')" - for code, desc in transport_types_seed - ] - ) - op.execute( - f""" - INSERT INTO public.transport_types (transport_code, description) VALUES - {values_tt} - ON CONFLICT (transport_code) DO NOTHING; - """ - ) - - values_trailer = ", ".join( - [ - f"('{code}', '{desc.replace(chr(39), chr(39)*2)}')" - for code, desc in trailer_types_seed - ] - ) - op.execute( - f""" - INSERT INTO public.trailer_type (trailer_type_key, description) VALUES - {values_trailer} - ON CONFLICT (trailer_type_key) DO NOTHING; - """ - ) - - values_vm = ", ".join( - [ - f"('{key}', '{desc.replace(chr(39), chr(39)*2)}')" - for key, desc in valuation_methods_seed - ] - ) - op.execute( - f""" - INSERT INTO public.valuation_methods (key, description) VALUES - {values_vm} - ON CONFLICT (key) DO NOTHING; - """ - ) - - # --- SEEDS A76 (Unidades de Medida) --- - val_ace = ", ".join( - [f"({format_value(c)}, {format_value(d)})" for c, d in ace_seed] - ) - op.execute( - f"INSERT INTO a76.unit_of_measure_ace (code, description) VALUES {val_ace} ON CONFLICT ON CONSTRAINT uq_uom_ace_code DO NOTHING;" - ) - - val_oma = ", ".join( - [f"({format_value(c)}, {format_value(d)})" for c, d in oma_seed] - ) - op.execute( - f"INSERT INTO a76.unit_of_measure_oma (code, description) VALUES {val_oma} ON CONFLICT ON CONSTRAINT uq_uom_oma_code DO NOTHING;" - ) - - val_ame = ", ".join( - [f"({format_value(c)}, {format_value(d)})" for c, d in ame_seed] - ) - op.execute( - f"INSERT INTO a76.unit_of_measure_american (code, description) VALUES {val_ame} ON CONFLICT ON CONSTRAINT uq_uom_american_code DO NOTHING;" - ) - - val_adua = ", ".join( - [f"({format_value(code)}, {format_value(desc)}, {format_value(a76_code)})" for code, desc, a76_code in adua_seed] - ) - op.execute( - f"INSERT INTO a76.unit_of_measure_customs (code, description, a76_unit_code) VALUES {val_adua} ON CONFLICT ON CONSTRAINT uq_uom_customs_code DO NOTHING;" - ) - - additional_customs = set() - additional_american = set() - additional_ace = set() - additional_oma = set() - - existing_customs = {code for code, desc, a76_code in adua_seed} - existing_american = {c for c, d in ame_seed} - existing_ace = {c for c, d in ace_seed} - existing_oma = {c for c, d in oma_seed} - - for code, desc, desc_en, customs, american, ace, oma in units_of_measure_seed: - if customs and customs.strip() and customs not in existing_customs: - additional_customs.add((customs, f"Auto-generated from {code}")) - if american and american.strip() and american not in existing_american: - additional_american.add((american, f"Auto-generated from {code}")) - if ace and ace.strip() and ace not in existing_ace: - additional_ace.add((ace, f"Auto-generated from {code}")) - if oma and oma.strip() and oma not in existing_oma: - additional_oma.add((oma, f"Auto-generated from {code}")) - - if additional_customs: - val_add_customs = ", ".join( - [f"({format_value(c)}, {format_value(d)}, NULL)" for c, d in additional_customs] - ) - op.execute( - f"INSERT INTO a76.unit_of_measure_customs (code, description, a76_unit_code) VALUES {val_add_customs} ON CONFLICT ON CONSTRAINT uq_uom_customs_code DO NOTHING;" - ) - - if additional_american: - val_add_american = ", ".join( - [f"({format_value(c)}, {format_value(d)})" for c, d in additional_american] - ) - op.execute( - f"INSERT INTO a76.unit_of_measure_american (code, description) VALUES {val_add_american} ON CONFLICT ON CONSTRAINT uq_uom_american_code DO NOTHING;" - ) - - if additional_ace: - val_add_ace = ", ".join( - [f"({format_value(c)}, {format_value(d)})" for c, d in additional_ace] - ) - op.execute( - f"INSERT INTO a76.unit_of_measure_ace (code, description) VALUES {val_add_ace} ON CONFLICT ON CONSTRAINT uq_uom_ace_code DO NOTHING;" - ) - - if additional_oma: - val_add_oma = ", ".join( - [f"({format_value(c)}, {format_value(d)})" for c, d in additional_oma] - ) - op.execute( - f"INSERT INTO a76.unit_of_measure_oma (code, description) VALUES {val_add_oma} ON CONFLICT ON CONSTRAINT uq_uom_oma_code DO NOTHING;" - ) - - # --- SEEDS CORE (Permissions) --- - all_permissions = registry.get_all() - - values_permissions = ", ".join( - [ - f"({format_value(p.code)}, {format_value(p.description)}, {format_value(p.module)}, {format_value(p.action)})" - for p in all_permissions - ] - ) - - if values_permissions: - op.execute( - f""" - INSERT INTO core.permissions (code, description, module, action) - VALUES {values_permissions} - ON CONFLICT (code) DO NOTHING; - """ - ) - - values_states = ", ".join( - [ - f"('{m3_key}', '{description.replace(chr(39), chr(39)*2)}', {format_value(mex_key)})" - for m3_key, description, mex_key in states_seed - ] - ) - op.execute( - f""" - INSERT INTO public.states (m3_key, description, mex_key) - VALUES {values_states} - ON CONFLICT (m3_key, description) DO NOTHING; - """ - ) - - values_tariff_fractions = ", ".join( - [ - f"({format_value(code)}, {format_value(fraction)}, {format_value(description)}, " - f"{format_value(nico)}, {format_value(umt)}, {format_value(adv_impo)}, {format_value(adv_expo)})" - for code, fraction, description, nico, umt, adv_impo, adv_expo in tariff_fractions_seed - ] - ) - op.execute( - f""" - INSERT INTO a76.tariff_fractions (code, fraction, description, nico, umt, adv_impo, adv_expo) - VALUES {values_tariff_fractions} - ON CONFLICT (code) DO NOTHING; - """ - ) - - bind = op.get_bind() - session = Session(bind=bind) - seed_license_exceptions(session) - seed_agency_tariff_codes(session) - seed_identifiers(session) - seed_carta_porte(session) + pass def downgrade() -> None: - """Los datos de catálogo no se revierten automáticamente.""" pass diff --git a/backend/alembic/versions/a7b8c9d0e1f2_add_system_column_to_classes_and_parts.py b/backend/alembic/versions/a7b8c9d0e1f2_add_system_column_to_classes_and_parts.py deleted file mode 100644 index 2cb668fb..00000000 --- a/backend/alembic/versions/a7b8c9d0e1f2_add_system_column_to_classes_and_parts.py +++ /dev/null @@ -1,36 +0,0 @@ -"""add system column to classes and parts - -Revision ID: a7b8c9d0e1f2 -Revises: d2e3f4a5b6c7 -Create Date: 2026-05-26 10:00:00.000000 - -""" -from typing import Sequence, Union - -import sqlalchemy as sa -from alembic import op - -revision: str = "a7b8c9d0e1f2" -down_revision: Union[str, None] = "d2e3f4a5b6c7" -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -def upgrade() -> None: - # Columna system en a76.classes — indica si la clase pertenece a SCAF (fixed_asset) o SCAII (inventory) - op.add_column( - "classes", - sa.Column("system", sa.String(length=12), nullable=False, server_default="fixed_asset"), - schema="a76", - ) - # Columna system en a76.parts — misma discriminación por sistema - op.add_column( - "parts", - sa.Column("system", sa.String(length=12), nullable=False, server_default="fixed_asset"), - schema="a76", - ) - - -def downgrade() -> None: - op.drop_column("classes", "system", schema="a76") - op.drop_column("parts", "system", schema="a76") diff --git a/backend/alembic/versions/b3c4d5e6f7a8_split_rfc_tax_id_clients_and_providers.py b/backend/alembic/versions/b3c4d5e6f7a8_split_rfc_tax_id_clients_and_providers.py deleted file mode 100644 index 22044ece..00000000 --- a/backend/alembic/versions/b3c4d5e6f7a8_split_rfc_tax_id_clients_and_providers.py +++ /dev/null @@ -1,79 +0,0 @@ -"""split rfc and tax_id in clients_and_providers - -Separa el identificador fiscal en dos columnas: -- rfc: RFC mexicano (nacional) -- tax_id: identificador fiscal extranjero - -Antes, ambos compartían la columna `rfc` discriminados por `type_nat_foreign`. -La migración mueve el valor de los extranjeros (type_nat_foreign='E') de `rfc` a -`tax_id` y deja `rfc` en NULL para esos registros. Los nacionales no se tocan. - -Revision ID: b3c4d5e6f7a8 -Revises: a7b8c9d0e1f2 -Create Date: 2026-06-02 10:00:00.000000 - -""" -import logging -from typing import Sequence, Union - -import sqlalchemy as sa -from alembic import op - -revision: str = "b3c4d5e6f7a8" -down_revision: Union[str, None] = "a7b8c9d0e1f2" -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - -logger = logging.getLogger("alembic.runtime.migration") - -# Extranjero ⟺ type_nat_foreign empieza con 'E' (misma convención que DTOs/modelo/frontend). -_FOREIGN_PREDICATE = "UPPER(COALESCE(type_nat_foreign, '')) LIKE 'E%'" - - -def upgrade() -> None: - # 1) Nueva columna tax_id (nullable: un registro puede no tener identificador extranjero). - op.add_column( - "clients_and_providers", - sa.Column("tax_id", sa.String(length=30), nullable=True), - schema="a76", - ) - - conn = op.get_bind() - - # 2) Conteo previo (visibilidad antes de mover datos; UPDATE precedido de SELECT COUNT). - to_move = conn.execute( - sa.text( - "SELECT COUNT(*) FROM a76.clients_and_providers " - f"WHERE {_FOREIGN_PREDICATE} AND rfc IS NOT NULL" - ) - ).scalar() - logger.info( - "split_rfc_tax_id: %s registros extranjeros con rfc serán movidos a tax_id", - to_move, - ) - - # 3) Mover rfc -> tax_id para extranjeros (idempotente por el guard tax_id IS NULL). - conn.execute( - sa.text( - "UPDATE a76.clients_and_providers SET tax_id = rfc " - f"WHERE {_FOREIGN_PREDICATE} AND tax_id IS NULL AND rfc IS NOT NULL" - ) - ) - # 4) Limpiar rfc en extranjeros (la columna rfc queda solo para RFC nacional). - conn.execute( - sa.text( - f"UPDATE a76.clients_and_providers SET rfc = NULL WHERE {_FOREIGN_PREDICATE}" - ) - ) - - -def downgrade() -> None: - # Reconsolidar: regresar el identificador extranjero a rfc antes de eliminar la columna. - conn = op.get_bind() - conn.execute( - sa.text( - "UPDATE a76.clients_and_providers SET rfc = tax_id " - f"WHERE {_FOREIGN_PREDICATE} AND rfc IS NULL AND tax_id IS NOT NULL" - ) - ) - op.drop_column("clients_and_providers", "tax_id", schema="a76") diff --git a/backend/alembic/versions/c3d4e5f6a7b_add_workspace_profile_fields_to_user_tenants.py b/backend/alembic/versions/c3d4e5f6a7b_add_workspace_profile_fields_to_user_tenants.py index e9141079..7608f783 100644 --- a/backend/alembic/versions/c3d4e5f6a7b_add_workspace_profile_fields_to_user_tenants.py +++ b/backend/alembic/versions/c3d4e5f6a7b_add_workspace_profile_fields_to_user_tenants.py @@ -1,7 +1,7 @@ """add workspace profile fields to user_tenants Revision ID: c3d4e5f6a7b -Revises: ca7d3c4e8b2a +Revises: b2c3d4e5f6a7 Create Date: 2026-05-08 00:00:00.000000 """ @@ -12,7 +12,7 @@ import sqlalchemy as sa from alembic import op revision: str = "c3d4e5f6a7b" -down_revision: Union[str, None] = "ca7d3c4e8b2a" +down_revision: Union[str, None] = "b2c3d4e5f6a7" branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None diff --git a/backend/alembic/versions/c8d9e0f1a2b3_add_stock_unit_of_measure_to_classes.py b/backend/alembic/versions/c8d9e0f1a2b3_add_stock_unit_of_measure_to_classes.py deleted file mode 100644 index 8971bbd2..00000000 --- a/backend/alembic/versions/c8d9e0f1a2b3_add_stock_unit_of_measure_to_classes.py +++ /dev/null @@ -1,43 +0,0 @@ -"""add stock_unit_of_measure to classes - -Revision ID: c8d9e0f1a2b3 -Revises: b3c4d5e6f7a8 -Create Date: 2026-06-02 12:00:00.000000 - -""" -from typing import Sequence, Union - -import sqlalchemy as sa -from alembic import op - -revision: str = "c8d9e0f1a2b3" -down_revision: Union[str, None] = "b3c4d5e6f7a8" -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -def upgrade() -> None: - op.add_column( - "classes", - sa.Column("stock_unit_of_measure", sa.String(length=5), nullable=True), - schema="a76", - ) - op.create_foreign_key( - "fk_classes_stock_uom", - "classes", - "units_of_measure", - ["stock_unit_of_measure", "tenant_id", "company_id"], - ["code", "tenant_id", "company_id"], - source_schema="a76", - referent_schema="a76", - ) - - -def downgrade() -> None: - op.drop_constraint( - "fk_classes_stock_uom", - "classes", - schema="a76", - type_="foreignkey", - ) - op.drop_column("classes", "stock_unit_of_measure", schema="a76") diff --git a/backend/alembic/versions/ca7d3c4e8b2a_add_transportation_id_sequences.py b/backend/alembic/versions/ca7d3c4e8b2a_add_transportation_id_sequences.py deleted file mode 100644 index d50716db..00000000 --- a/backend/alembic/versions/ca7d3c4e8b2a_add_transportation_id_sequences.py +++ /dev/null @@ -1,67 +0,0 @@ -"""add transportation id sequences - -Revision ID: ca7d3c4e8b2a -Revises: b2c3d4e5f6a7 -Create Date: 2026-05-08 08:34:00.000000 - -Crea las secuencias `a76.__seq` que los servicios de catálogos de -transporte (transporter, vehicle, driver, trailer) referencian vía -`SELECT nextval(...)`. La migración es idempotente y reposiciona cada -secuencia a `MAX()` para no chocar con filas pre-existentes. -""" -from typing import Sequence, Union - -from alembic import op - - -revision: str = "ca7d3c4e8b2a" -down_revision: Union[str, None] = "b2c3d4e5f6a7" -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -# (tabla, columna_id_sustituto) en el esquema a76. El nombre de secuencia -# resultante es `a76.__seq`, que es el patrón que los -# servicios ya consumen (ver transporters/vehicles/drivers/trailers services). -_SEQUENCE_TARGETS = ( - ("transporter", "transporter_id"), - ("vehicle", "vehicle_id"), - ("driver", "driver_id"), - ("trailer", "trailer_id"), -) - - -def _seq_name(table: str, column: str) -> str: - return f"{table}_{column}_seq" - - -def upgrade() -> None: - for table, column in _SEQUENCE_TARGETS: - seq = _seq_name(table, column) - op.execute( - f'CREATE SEQUENCE IF NOT EXISTS a76."{seq}" ' - f"AS BIGINT START WITH 1 INCREMENT BY 1" - ) - # Reposicionar la secuencia al MAX(id) actual para evitar colisiones - # con datos ya cargados antes de existir la secuencia. - op.execute( - f"SELECT setval('a76.\"{seq}\"', " - f"GREATEST((SELECT COALESCE(MAX({column}), 0) FROM a76.{table}), 1), " - f"true)" - ) - op.execute( - f'ALTER SEQUENCE a76."{seq}" OWNED BY a76.{table}.{column}' - ) - op.execute( - f"ALTER TABLE a76.{table} " - f"ALTER COLUMN {column} SET DEFAULT nextval('a76.\"{seq}\"')" - ) - - -def downgrade() -> None: - for table, column in _SEQUENCE_TARGETS: - seq = _seq_name(table, column) - op.execute( - f"ALTER TABLE a76.{table} ALTER COLUMN {column} DROP DEFAULT" - ) - op.execute(f'DROP SEQUENCE IF EXISTS a76."{seq}"') diff --git a/backend/alembic/versions/d2e3f4a5b6c7_seed_document_types_digitization.py b/backend/alembic/versions/d2e3f4a5b6c7_seed_document_types_digitization.py deleted file mode 100644 index 87f7c247..00000000 --- a/backend/alembic/versions/d2e3f4a5b6c7_seed_document_types_digitization.py +++ /dev/null @@ -1,57 +0,0 @@ -"""seed document_types_digitization - -Revision ID: d2e3f4a5b6c7 -Revises: f1a2b3c4d5e6 -Create Date: 2026-05-25 - -""" -from typing import Sequence, Union - -import sqlalchemy as sa -from alembic import op -from sqlalchemy import text -from sqlalchemy.orm import Session - -from api.v1.modules.a76.doc_types_dig.seed import seed as doc_types_dig_seed - -revision: str = "d2e3f4a5b6c7" -down_revision: Union[str, None] = "f1a2b3c4d5e6" -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -def upgrade() -> None: - # Quitar columna active (no se usa en ningún flujo) - op.drop_column('document_types_digitization', 'active', schema='a76') - - def format_value(val): - if val is None or str(val).strip() == "" or str(val).upper() == "NONE": - return "NULL" - return f"'{str(val).replace(chr(39), chr(39)*2)}'" - - bind = op.get_bind() - session = Session(bind=bind) - companies = session.execute( - text("SELECT tenant_id, id FROM a76.company WHERE deleted_at IS NULL") - ).fetchall() - - for tenant_id, company_id in companies: - values = ", ".join([ - f"({format_value(code)}, {format_value(description)}, {tenant_id}, {company_id})" - for code, description in doc_types_dig_seed - ]) - if values: - session.execute(text(f""" - INSERT INTO a76.document_types_digitization (code, description, tenant_id, company_id) - VALUES {values} - ON CONFLICT (tenant_id, company_id, code) DO NOTHING; - """)) - session.commit() - - -def downgrade() -> None: - op.add_column( - 'document_types_digitization', - sa.Column('active', sa.Boolean(), nullable=False, server_default=sa.text('true')), - schema='a76', - ) diff --git a/backend/alembic/versions/e1f2a3b4c5d6_seed_sectors_for_existing_companies.py b/backend/alembic/versions/e1f2a3b4c5d6_seed_sectors_for_existing_companies.py deleted file mode 100644 index 1fee5b1b..00000000 --- a/backend/alembic/versions/e1f2a3b4c5d6_seed_sectors_for_existing_companies.py +++ /dev/null @@ -1,63 +0,0 @@ -"""seed sectors for existing companies - -Siembra el catálogo de sectores PROSEC (a76.sectors) para todas las empresas -existentes. Los sectores son por empresa y solo se sembraban dentro de -_seed_company_data() al crear una empresa; las empresas que no pasaron por ese -flujo (BD recreada o creadas por otra vía) quedaban sin sectores y el selector -del frontend aparecía vacío. Idempotente: ON CONFLICT DO NOTHING. - -Revision ID: e1f2a3b4c5d6 -Revises: c8d9e0f1a2b3 -Create Date: 2026-06-02 14:00:00.000000 - -""" -from typing import Sequence, Union - -from alembic import op -from sqlalchemy import text -from sqlalchemy.orm import Session - -from api.v1.modules.a76.general_catalogs.sectors.seed import seed as sectors_seed - -revision: str = "e1f2a3b4c5d6" -down_revision: Union[str, None] = "c8d9e0f1a2b3" -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -def upgrade() -> None: - def format_value(val): - if val is None or str(val).strip() == "" or str(val).upper() == "NONE": - return "NULL" - return f"'{str(val).replace(chr(39), chr(39) * 2)}'" - - bind = op.get_bind() - session = Session(bind=bind) - companies = session.execute( - text("SELECT tenant_id, id FROM a76.company WHERE deleted_at IS NULL") - ).fetchall() - - for tenant_id, company_id in companies: - values = ", ".join( - [ - f"({format_value(key)}, {format_value(description)}, {str(authorized).upper()}, {tenant_id}, {company_id})" - for key, description, authorized in sectors_seed - ] - ) - if values: - session.execute( - text( - f""" - INSERT INTO a76.sectors (key, description, authorized, tenant_id, company_id) - VALUES {values} - ON CONFLICT (key, tenant_id, company_id) DO NOTHING; - """ - ) - ) - session.commit() - - -def downgrade() -> None: - # Backfill de datos: no se eliminan sectores en el downgrade porque podrían - # haberse editado/agregado manualmente tras la siembra. No-op intencional. - pass diff --git a/backend/alembic/versions/e4f5a6b7c8d9_add_incoterm_daf_legacy.py b/backend/alembic/versions/e4f5a6b7c8d9_add_incoterm_daf_legacy.py deleted file mode 100644 index cde28814..00000000 --- a/backend/alembic/versions/e4f5a6b7c8d9_add_incoterm_daf_legacy.py +++ /dev/null @@ -1,30 +0,0 @@ -"""add incoterm DAF legacy catalog row - -Revision ID: e4f5a6b7c8d9 -Revises: c3d4e5f6a7b -Create Date: 2026-05-08 - -Incoterm histórico DAF (Delivered At Frontier) para CSV y referencias legacy. -""" -from typing import Sequence, Union - -from alembic import op - -revision: str = "e4f5a6b7c8d9" -down_revision: Union[str, None] = "c3d4e5f6a7b" -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -def upgrade() -> None: - op.execute( - """ - INSERT INTO public.incoterms (code, description_es, description_en) VALUES - ('DAF', 'ENTREGADO EN FRONTERA', 'DELIVERED AT FRONTIER') - ON CONFLICT (code) DO NOTHING; - """ - ) - - -def downgrade() -> None: - op.execute("DELETE FROM public.incoterms WHERE code = 'DAF';") diff --git a/backend/alembic/versions/f1a2b3c4d5e6_isolate_equivalency_items.py b/backend/alembic/versions/f1a2b3c4d5e6_isolate_equivalency_items.py deleted file mode 100644 index f825826c..00000000 --- a/backend/alembic/versions/f1a2b3c4d5e6_isolate_equivalency_items.py +++ /dev/null @@ -1,75 +0,0 @@ -"""isolate equivalency items - -Revision ID: f1a2b3c4d5e6 -Revises: e4f5a6b7c8d9 -Create Date: 2026-05-12 17:18:00.000000 - -""" -from typing import Sequence, Union - -import sqlalchemy as sa -from alembic import op - -revision: str = "f1a2b3c4d5e6" -down_revision: Union[str, None] = "e4f5a6b7c8d9" -branch_labels: Union[str, Sequence[str], None] = None -depends_on: Union[str, Sequence[str], None] = None - - -def upgrade() -> None: - # 1. Add equivalency_id column - op.add_column( - 'equivalency_items', - sa.Column( - 'equivalency_id', - sa.Integer(), - sa.ForeignKey('a76.equivalencies.id', ondelete='CASCADE'), - nullable=True - ), - schema='a76' - ) - - # 2. Drop old unique constraint - # Note: In some DBs we might need to specify the type, but for Postgres 'unique' or 'foreignkey' works. - op.drop_constraint('uq_equivalency_item_fields', 'equivalency_items', schema='a76', type_='unique') - - # 3. Create new unique constraint including equivalency_id - op.create_unique_constraint( - 'uq_equivalency_item_fields', - 'equivalency_items', - ['equivalency_id', 'original_field', 'external_field', 'tenant_id', 'company_id'], - schema='a76' - ) - - # 4. Increase identifier length in equivalencies table - op.alter_column( - 'equivalencies', - 'identifier', - type_=sa.String(50), - existing_type=sa.String(10), - schema='a76' - ) - - -def downgrade() -> None: - # 1. Drop new unique constraint - op.drop_constraint('uq_equivalency_item_fields', 'equivalency_items', schema='a76', type_='unique') - - # 2. Re-create old unique constraint - op.create_unique_constraint( - 'uq_equivalency_item_fields', - 'equivalency_items', - ['original_field', 'external_field', 'tenant_id', 'company_id'], - schema='a76' - ) - - # 4. Revert identifier length in equivalencies table - op.alter_column( - 'equivalencies', - 'identifier', - type_=sa.String(10), - existing_type=sa.String(50), - schema='a76' - ) - # 5. Drop equivalency_id column - op.drop_column('equivalency_items', 'equivalency_id', schema='a76') diff --git a/backend/alembic/versions/g2h3i4j5k6l7_add_invite_codes.py b/backend/alembic/versions/g2h3i4j5k6l7_add_invite_codes.py index 3fe6e712..a6da3631 100644 --- a/backend/alembic/versions/g2h3i4j5k6l7_add_invite_codes.py +++ b/backend/alembic/versions/g2h3i4j5k6l7_add_invite_codes.py @@ -1,7 +1,7 @@ """add invite codes Revision ID: g2h3i4j5k6l7 -Revises: e1f2a3b4c5d6 +Revises: c3d4e5f6a7b Create Date: 2026-06-02 00:00:00.000000 """ @@ -11,7 +11,7 @@ import sqlalchemy as sa from alembic import op revision: str = "g2h3i4j5k6l7" -down_revision: str = "e1f2a3b4c5d6" +down_revision: str = "c3d4e5f6a7b" branch_labels: Union[str, Sequence[str], None] = None depends_on: Union[str, Sequence[str], None] = None diff --git a/backend/api/v1/common/base_models.py b/backend/api/v1/common/base_models.py index c58d8ffe..31db7e78 100644 --- a/backend/api/v1/common/base_models.py +++ b/backend/api/v1/common/base_models.py @@ -23,13 +23,11 @@ class TimestampMixin(BaseTimestampMixin): class TenantScopedMixin: - """Mixin for tenant and company scoped entities""" + """Mixin para entidades multi-tenant. + + company_id no tiene FK declarada aquí — agrégala en cada modelo + apuntando a la tabla de compañías de tu proyecto. + """ tenant_id: Mapped[int] = mapped_column(Integer, ForeignKey("core.tenants.id"), nullable=False, index=True) - company_id: Mapped[int] = mapped_column(Integer, ForeignKey("a76.company.id"), nullable=False, index=True) - - -class PedimentoRelatedMixin(TenantScopedMixin): - """Mixin for entities related to pedimentos""" - - pedimento_id: Mapped[int] = mapped_column(Integer, nullable=False) + company_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True) diff --git a/backend/api/v1/modules/a24/balance_movements/models.py b/backend/api/v1/modules/a24/balance_movements/models.py deleted file mode 100644 index d609d4c4..00000000 --- a/backend/api/v1/modules/a24/balance_movements/models.py +++ /dev/null @@ -1,352 +0,0 @@ -""" -Annex 24 - Balance Management Core -SQLAlchemy v2 - -Plugs into the existing schema: - invoice_header (a76.invoice_header) ← already exists - item_lines (a76.item_lines) ← already exists - -These 4 tables are ALL you need for balances: - - a24.balance_movement ← the ledger (append-only, never UPDATE) - a24.discharge_header ← one discharge per export/SM/CTM event - a24.discharge_detail ← one row per (export line x import lot consumed) - a24.discharge_scrap ← mermas, desperdicios, destrucciones - -Design rules: - 1. NEVER update balance_movement rows — only INSERT - 2. Balance = SUM of movements. No cached balance columns. - 3. Every discharge_detail row MUST reference a balance_movement row - 4. PEPS order is enforced via order_peps (monotonic, set on INSERT) -""" - -from __future__ import annotations - -import datetime -from decimal import Decimal -from enum import Enum -from typing import TYPE_CHECKING, List, Optional -from ..discharges.models import DischargeDetail - -from sqlalchemy import ( - BigInteger, - CheckConstraint, - Date, - ForeignKey, - Index, - Integer, - Numeric, - String, - UniqueConstraint, - text, -) -from sqlalchemy.orm import Mapped, mapped_column, relationship - -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base - -if TYPE_CHECKING: - from api.v1.modules.a76.invoices.models import InvoiceHeader - from api.v1.modules.a76.items.models import LineItem - from api.v1.modules.a76.parts.models import Part - - -# --------------------------------------------------------------------------- -# Enums -# --------------------------------------------------------------------------- - - -class MovementType(str, Enum): - # ── Positive (add to balance) ────────────────────────────────────────── - ENTRY = "entry" # Normal import entry - RETURN = "return" # Material returned to balance - POSITIVE_ADJUSTMENT = "pos_adj" # Physical inventory adjustment (+) - TRANSFER_IN = "transfer_in" # CTM/SM received (the receiving side) - REGIME_CHANGE_IN = "regime_chg_in" # Eg. temporary → definitive (entry side) - - # ── Negative (consume from balance) ─────────────────────────────────── - CONSUMPTION = "consumption" # Consumed in export (most common) - WASTE = "waste" # Merma de proceso - SCRAP = "scrap" # Desperdicio / scrap - DESTRUCTION = "destruction" # Destrucción oficial ante aduana - NEGATIVE_ADJUSTMENT = "neg_adj" # Physical inventory adjustment (-) - TRANSFER_OUT = "transfer_out" # CTM/SM sent (the sending side) - EXPIRATION = "expiration" # Balance cancelled due to deadline - REGIME_CHANGE_OUT = "regime_chg_out" # Eg. temporary → definitive (exit side) - - # ── Reversal (annuls a prior ENTRY — used when un-processing an invoice) ─ - # Inserting ENTRY_VOID with the same quantity as the original ENTRY leaves - # the net balance at zero, preventing any further discharges against that - # lot. A fresh ENTRY is created when the invoice is re-processed. - ENTRY_VOID = "entry_void" - - -# Which movement types reduce the balance (sign = -1) -NEGATIVE_MOVEMENTS = { - MovementType.CONSUMPTION, - MovementType.WASTE, - MovementType.SCRAP, - MovementType.DESTRUCTION, - MovementType.NEGATIVE_ADJUSTMENT, - MovementType.TRANSFER_OUT, - MovementType.EXPIRATION, - MovementType.REGIME_CHANGE_OUT, - MovementType.ENTRY_VOID, -} - -# Which types count toward "used" (CANTUSADA in Anexo 24 report) -USED_MOVEMENTS = { - MovementType.CONSUMPTION, - MovementType.WASTE, - MovementType.SCRAP, - MovementType.DESTRUCTION, -} - - - - -# --------------------------------------------------------------------------- -# 1. BalanceMovement (the ledger — APPEND ONLY) -# -# One row per atomic change to a specific import lot. -# Current balance of any lot = SUM of (signed quantity) over its rows. -# -# import_item_line_id → a76.item_lines.id (the import line = the "lot") -# import_invoice_id → a76.invoice_header.id (the import invoice) -# part_number_id → a76.parts.id (denormalized for PEPS index) -# source_item_line_id → a76.item_lines.id (export/SM/CTM line, if any) -# source_invoice_id → a76.invoice_header.id (export/SM/CTM invoice, if any) -# -# NOTE: No discharge_detail_id column. Navigate the other direction via -# DischargeDetail.movement_id to avoid a circular FK and keep this -# table truly append-only (no UPDATE ever needed). -# --------------------------------------------------------------------------- - -class BalanceMovement(Base, TenantScopedMixin, TimestampMixin): - """ - Core ledger table. NEVER update existing rows — only INSERT. - - Balance of a lot = SUM(quantity) WHERE sign=+1 (entries) - - SUM(quantity) WHERE sign=-1 (exits) - - Append-only rule is enforced at the application layer. - """ - - __tablename__ = "balance_movement" - __table_args__ = ( - UniqueConstraint( - "import_item_line_id", "order_peps", - name="uq_balance_movement_lot_peps", - ), - CheckConstraint("quantity > 0", name="ck_balance_movement_qty_positive"), - # PEPS lookup: "give me available lots for this part+regime, oldest first" - # part_number_id is denormalized here so this index is self-contained. - Index( - "ix_balmov_peps_lookup", - "tenant_id", "part_number_id", "movement_type", "order_peps", - postgresql_include=["import_item_line_id", "quantity", "value_me", "value_mn"], - ), - # Balance calculation per lot - Index("ix_balmov_lot", "import_item_line_id"), - # "What did this export consume?" - Index("ix_balmov_source", "source_invoice_id", "source_item_line_id"), - # Anexo 24 period reports - Index("ix_balmov_operation_date", "tenant_id", "operation_date", "movement_type"), - {"schema": "a24"}, - ) - - id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=True) - - # ── The import lot this movement belongs to ──────────────────────────── - import_invoice_id: Mapped[int] = mapped_column( - ForeignKey("a76.invoice_header.id"), - comment="Import invoice (cabecera de importación)", - ) - import_item_line_id: Mapped[int] = mapped_column( - ForeignKey("a76.item_lines.id"), - comment="Import line item = the PEPS lot", - ) - - # ── Denormalized part reference — enables efficient PEPS index ───────── - # Must equal import_line.part_number_id. Set on INSERT, never changed. - part_number_id: Mapped[Optional[int]] = mapped_column( - ForeignKey("a76.parts.id"), - comment="Denormalized from item_lines.part_number_id. Enables PEPS index without joins.", - ) - - # ── What kind of movement ───────────────────────────────────────────── - movement_type: Mapped[MovementType] = mapped_column( - String(20), - comment="See MovementType enum. Determines sign and whether qty counts as used.", - ) - - # ── Quantity and value (always stored positive) ─────────────────────── - quantity: Mapped[Decimal] = mapped_column( - Numeric(19, 8), - comment="Always positive. Sign is inferred from movement_type via NEGATIVE_MOVEMENTS.", - ) - value_me: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8), comment="USD") - value_mn: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8), comment="MXN") - net_weight: Mapped[Optional[Decimal]] = mapped_column(Numeric(19, 8)) - - # ── Document that caused this movement ──────────────────────────────── - # NULL for ENTRY movements (the import invoice itself is the cause) - source_invoice_id: Mapped[Optional[int]] = mapped_column( - ForeignKey("a76.invoice_header.id"), - comment="Export / SM / CTM invoice. NULL for entries.", - ) - source_item_line_id: Mapped[Optional[int]] = mapped_column( - ForeignKey("a76.item_lines.id"), - comment="Specific line in the export / SM / CTM invoice.", - ) - - # ── PEPS ordering ───────────────────────────────────────────────────── - # Simplest approach: set order_peps = id (globally monotonic). - # Finer approach: use a per-part sequence or epoch-based value. - order_peps: Mapped[int] = mapped_column( - BigInteger, - comment="PEPS order within this lot. Lower = older = consumed first.", - ) - - # ── Business date of the operation ─────────────────────────────────── - operation_date: Mapped[datetime.date] = mapped_column( - Date, comment="Date of the actual business event, not DB insert." - ) - - notes: Mapped[Optional[str]] = mapped_column(String(300)) - - # ── Relationships ───────────────────────────────────────────────────── - import_invoice: Mapped["InvoiceHeader"] = relationship( - foreign_keys=[import_invoice_id], - ) - import_line: Mapped["LineItem"] = relationship( - foreign_keys=[import_item_line_id], - ) - part: Mapped[Optional["Part"]] = relationship( - foreign_keys=[part_number_id], - ) - source_invoice: Mapped[Optional["InvoiceHeader"]] = relationship( - foreign_keys=[source_invoice_id], - ) - source_line: Mapped[Optional["LineItem"]] = relationship( - foreign_keys=[source_item_line_id], - ) - # Back-reference: navigate to the detail that consumed this movement. - # Use viewonly=True — ownership lives on DischargeDetail.movement_id. - discharge_detail: Mapped[Optional["DischargeDetail"]] = relationship( - back_populates="movement", - foreign_keys="[DischargeDetail.movement_id]", - primaryjoin="BalanceMovement.id == DischargeDetail.movement_id", - viewonly=True, - ) - - - - - -# --------------------------------------------------------------------------- -# Repository helpers (copy to your service/repository layer) -# --------------------------------------------------------------------------- -# -# -# ── Current balance of a lot ───────────────────────────────────────────── -# -# from sqlalchemy import case, func, select -# -# def current_balance(session, import_item_line_id: int): -# sign = case( -# (BalanceMovement.movement_type.in_(NEGATIVE_MOVEMENTS), -1), -# else_=1, -# ) -# affects_used = BalanceMovement.movement_type.in_(USED_MOVEMENTS) -# return session.execute( -# select( -# func.sum(sign * BalanceMovement.quantity).label("current_balance"), -# func.sum( -# case((affects_used, BalanceMovement.quantity), else_=0) -# ).label("quantity_used"), -# func.sum( -# case((affects_used, BalanceMovement.value_me), else_=0) -# ).label("value_used_me"), -# ).where(BalanceMovement.import_item_line_id == import_item_line_id) -# ).one() -# -# -# ── PEPS resolver — call BEFORE inserting a CONSUMPTION movement ───────── -# -# def peps_lots_for(session, tenant_id, part_number_id, operation_type, qty_needed): -# """ -# Returns import lots in FIFO order with their available balance. -# Walk the list and consume until qty_needed is satisfied. -# """ -# sign = case( -# (BalanceMovement.movement_type.in_(NEGATIVE_MOVEMENTS), -1), -# else_=1, -# ) -# lot_balances = ( -# select( -# BalanceMovement.import_item_line_id, -# func.sum(sign * BalanceMovement.quantity).label("available"), -# func.min(BalanceMovement.order_peps).label("oldest_peps"), -# ) -# .where( -# BalanceMovement.tenant_id == tenant_id, -# BalanceMovement.part_number_id == part_number_id, -# ) -# .group_by(BalanceMovement.import_item_line_id) -# .having(func.sum(sign * BalanceMovement.quantity) > 0) -# .order_by("oldest_peps") -# .subquery() -# ) -# return session.execute(select(lot_balances)).all() -# -# -# ── Transaction flow for a new discharge ───────────────────────────────── -# -# def apply_discharge(session, export_invoice_id, lines): -# """ -# lines = [{"export_line_id": X, "part_number_id": Y, "quantity": Z}, ...] -# -# Two-step INSERT: movement first, then detail referencing it. -# No UPDATE on balance_movement — design rule 1 is preserved. -# """ -# header = DischargeHeader(source_invoice_id=export_invoice_id, ...) -# session.add(header) -# session.flush() # get header.id -# -# for line in lines: -# lots = peps_lots_for(session, ..., line["part_number_id"], qty_needed=line["quantity"]) -# remaining = line["quantity"] -# -# for lot in lots: -# consume = min(lot.available, remaining) -# -# # Step 1: insert movement -# mov = BalanceMovement( -# import_item_line_id = lot.import_item_line_id, -# part_number_id = line["part_number_id"], -# movement_type = MovementType.CONSUMPTION, -# quantity = consume, -# source_invoice_id = export_invoice_id, -# source_item_line_id = line["export_line_id"], -# order_peps = , -# operation_date = datetime.date.today(), -# ) -# session.add(mov) -# session.flush() # get mov.id -# -# # Step 2: insert detail referencing the movement -# det = DischargeDetail( -# discharge_header_id = header.id, -# export_item_line_id = line["export_line_id"], -# import_item_line_id = lot.import_item_line_id, -# movement_id = mov.id, # NOT NULL — set immediately -# quantity_discharged = consume, -# ) -# session.add(det) -# -# remaining -= consume -# if remaining <= 0: -# break -# -# session.commit() diff --git a/backend/api/v1/modules/a24/discharges/models.py b/backend/api/v1/modules/a24/discharges/models.py deleted file mode 100644 index 13549411..00000000 --- a/backend/api/v1/modules/a24/discharges/models.py +++ /dev/null @@ -1,369 +0,0 @@ -""" -Annex 24 - Balance Management Core -SQLAlchemy v2 - -Plugs into the existing schema: - invoice_header (a76.invoice_header) ← already exists - item_lines (a76.item_lines) ← already exists - -These 4 tables are ALL you need for balances: - - a24.balance_movement ← the ledger (append-only, never UPDATE) - a24.discharge_header ← one discharge per export/SM/CTM event - a24.discharge_detail ← one row per (export line x import lot consumed) - a24.discharge_scrap ← mermas, desperdicios, destrucciones - -Design rules: - 1. NEVER update balance_movement rows — only INSERT - 2. Balance = SUM of movements. No cached balance columns. - 3. Every discharge_detail row MUST reference a balance_movement row - 4. PEPS order is enforced via order_peps (monotonic, set on INSERT) -""" - -from __future__ import annotations - -import datetime -from decimal import Decimal -from enum import Enum -from typing import TYPE_CHECKING, List, Optional - -from sqlalchemy import ( - BigInteger, - CheckConstraint, - Date, - ForeignKey, - Index, - Integer, - Numeric, - String, - text, -) -from sqlalchemy.orm import Mapped, mapped_column, relationship - -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base - -if TYPE_CHECKING: - from api.v1.modules.a76.invoices.models import InvoiceHeader - from api.v1.modules.a76.items.models import LineItem - from ..balance_movements.models import BalanceMovement - - -class DischargeType(str, Enum): - TEMPORARY = "temporary" # SDescargaT — export against temp import - DEFINITIVE = "definitive" # SDescargaD — export against def import - REPAIR = "repair" # SDescargaR — repair / return - DIRECTED = "directed" # SDescargaM — Directed discharge - CTM = "ctm" # SDescargaCTM — between plants (same group) - SUBASSEMBLY = "subassembly" # SDescargaSM — to external maquiladora - WASTE_SCRAP = "waste_scrap" # SDescargaMerDes — merma / desperdicio - - -class DischargeStatus(str, Enum): - PENDING = "pending" - APPLIED = "applied" - PARTIAL = "partial" - CANCELLED = "cancelled" - -# --------------------------------------------------------------------------- -# 2. DischargeHeader -# -# One row per discharge event (export, SM batch, CTM transfer…). -# Groups all DischargeDetail rows for the same business event. -# -# source_invoice_id → a76.invoice_header.id (the export/SM/CTM invoice) -# def_import_invoice_id → a76.invoice_header.id (DEF discharges only) -# --------------------------------------------------------------------------- - -class DischargeHeader(Base, TenantScopedMixin, TimestampMixin): - """ - One discharge per export event. - Groups DischargeDetail rows (one per import lot consumed). - """ - - __tablename__ = "discharge_header" - __table_args__ = ( - Index("ix_dischdr_source", "source_invoice_id", "status"), - Index("ix_dischdr_date", "tenant_id", "discharge_date", "discharge_type"), - {"schema": "a24"}, - ) - - id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=True) - - # ── Export / SM / CTM document that triggers this discharge ─────────── - source_invoice_id: Mapped[int] = mapped_column( - ForeignKey("a76.invoice_header.id"), - comment="Export, SM-out or CTM-send invoice that owns this discharge.", - ) - - # ── For DEFINITIVE discharges only ──────────────────────────────────── - def_import_invoice_id: Mapped[Optional[int]] = mapped_column( - ForeignKey("a76.invoice_header.id"), - comment="Populated only for discharge_type=DEFINITIVE.", - ) - - discharge_type: Mapped[DischargeType] = mapped_column(String(15)) - status: Mapped[DischargeStatus] = mapped_column( - String(15), server_default=text("'applied'") - ) - discharge_date: Mapped[datetime.date] = mapped_column(Date) - - # ── Control fields from legacy SDescarga* tables ────────────────────── - reference_invoice: Mapped[Optional[str]] = mapped_column( - String(19), comment="FACREFERENCIA — for rectifications" - ) - discharge_subtype: Mapped[Optional[str]] = mapped_column( - String(10), comment="TIPODESC: NORMAL, PARCIAL, REPARACION, UTILERIA" - ) - partial_sequence: Mapped[Optional[int]] = mapped_column( - Integer, comment="CONSECPARCIAL — for partial discharges" - ) - sales_order: Mapped[Optional[str]] = mapped_column(String(20)) # ORDENVENTA - ctm_section: Mapped[Optional[str]] = mapped_column(String(3)) # APARTADOCTM - is_tooling: Mapped[bool] = mapped_column( - server_default=text("false"), comment="PORUTILERIA" - ) - discharge_sm: Mapped[Optional[str]] = mapped_column(String(4)) # DESCARGASM - is_repair_update: Mapped[bool] = mapped_column( - server_default=text("false"), comment="ACTUALREPARACION" - ) - material_type_expo: Mapped[Optional[str]] = mapped_column( - String(10), comment="TIPOMATEXPO" - ) - - # ── Cancellation trail ──────────────────────────────────────────────── - cancelled_by: Mapped[Optional[str]] = mapped_column(String(100)) - cancellation_reason: Mapped[Optional[str]] = mapped_column(String(300)) - - # ── Relationships ───────────────────────────────────────────────────── - source_invoice: Mapped["InvoiceHeader"] = relationship( - foreign_keys=[source_invoice_id], - ) - def_import_invoice: Mapped[Optional["InvoiceHeader"]] = relationship( - foreign_keys=[def_import_invoice_id], - ) - details: Mapped[List["DischargeDetail"]] = relationship( - back_populates="header", cascade="all, delete-orphan" - ) - scraps: Mapped[List["DischargeScrap"]] = relationship( - back_populates="header", cascade="all, delete-orphan" - ) - - -# --------------------------------------------------------------------------- -# 3. DischargeDetail -# -# The critical traceability link: -# "Export line X consumed Y units from import lot Z" -# -# One row per (export_line x import_lot) pair. -# A single export line can span multiple rows when PEPS pulls from -# more than one import lot. -# -# discharge_header_id → a24.discharge_header.id -# export_item_line_id → a76.item_lines.id (the export line) -# import_item_line_id → a76.item_lines.id (the import lot consumed) -# movement_id → a24.balance_movement.id (the ledger row) -# --------------------------------------------------------------------------- - -class DischargeDetail(Base, TenantScopedMixin, TimestampMixin): - """ - One row per (export line x import lot consumed). - - This is the traceability record the SAT asks for: - "Show me which import pedimento covered this export line." - """ - - __tablename__ = "discharge_detail" - __table_args__ = ( - CheckConstraint("movement_id IS NOT NULL", name="ck_dischdet_movement_required"), - Index("ix_dischdet_header", "discharge_header_id"), - Index("ix_dischdet_import_lot", "import_item_line_id"), - Index("ix_dischdet_export_line", "export_item_line_id"), - Index("ix_dischdet_part", "tenant_id", "part_number"), - {"schema": "a24"}, - ) - - id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=True) - - discharge_header_id: Mapped[int] = mapped_column( - ForeignKey("a24.discharge_header.id"), - ) - - # ── Export side ─────────────────────────────────────────────────────── - export_item_line_id: Mapped[Optional[int]] = mapped_column( - ForeignKey("a76.item_lines.id"), - comment="NULL for waste-only discharges.", - ) - # Denormalized for report performance - part_number: Mapped[Optional[str]] = mapped_column( - String(70), comment="NUMPARTE of the export line (denormalized)" - ) - export_part_number: Mapped[Optional[str]] = mapped_column( - String(70), comment="NUMPARTEEXPO — as it appears in the pedimento" - ) - export_line_ref: Mapped[Optional[int]] = mapped_column( - Integer, comment="LINEAEXPOREF — for rectification references" - ) - - # ── Import lot side (PEPS lot consumed) ─────────────────────────────── - import_item_line_id: Mapped[int] = mapped_column( - ForeignKey("a76.item_lines.id"), - ) - - # ── Ledger row created for this consumption (NOT NULL — design rule 3) ─ - movement_id: Mapped[int] = mapped_column( - BigInteger, - ForeignKey("a24.balance_movement.id"), - comment="The BalanceMovement that records this consumption. Required.", - ) - - # ── Consumed quantities and values ──────────────────────────────────── - quantity_discharged: Mapped[Decimal] = mapped_column(Numeric(19, 8)) # CANTDESC - unit_of_measure: Mapped[Optional[str]] = mapped_column(String(5)) - value_mn: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) - value_me: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) - net_weight: Mapped[Optional[Decimal]] = mapped_column(Numeric(19, 8)) - gross_weight: Mapped[Optional[Decimal]] = mapped_column(Numeric(19, 8)) - - # ── Tariff classification of the consumed input ─────────────────────── - tariff_fraction: Mapped[Optional[str]] = mapped_column(String(10)) # FRACCIONIMPO - fraction_type: Mapped[Optional[str]] = mapped_column(String(7)) # TIPOFRACCION - ad_valorem: Mapped[Optional[str]] = mapped_column(String(10)) # ADVALOREMIMPO - country_of_origin: Mapped[Optional[str]] = mapped_column(String(3)) # PAISMERCANCIA - sector: Mapped[Optional[str]] = mapped_column(String(8)) # SECTOR - - # ── Repair-specific ─────────────────────────────────────────────────── - original_part: Mapped[Optional[str]] = mapped_column( - String(70), comment="PARTEORIGINAL" - ) - equivalent_quantity: Mapped[Optional[Decimal]] = mapped_column( - Numeric(19, 8), comment="CANTEQUIVALENTE" - ) - equivalent_unit: Mapped[Optional[str]] = mapped_column(String(5)) - returned_quantity_sm: Mapped[Optional[Decimal]] = mapped_column( - Numeric(19, 8), comment="CANTRETORNADASAM" - ) - - # ── Waste / scrap ───────────────────────────────────────────────────── - waste_type: Mapped[Optional[str]] = mapped_column( - String(1), comment="M=merma, D=desperdicio, S=scrap" - ) - take_balance_base_pt: Mapped[Optional[str]] = mapped_column( - String(2), comment="TOMARSALDOBASEALPT" - ) - - # ── Tax fields (from SDescargaT) ────────────────────────────────────── - igi_amount: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # MONTOIGI - tax_payment: Mapped[Optional[str]] = mapped_column(String(1)) # PAGOIMPUESTO - has_certificate: Mapped[Optional[str]] = mapped_column(String(1)) # TIENECERT - iva_mn: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # VALORIVAMN - iva_me: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # VALORIVAME - - # ── SM origin traceability ──────────────────────────────────────────── - origin_import_invoice: Mapped[Optional[str]] = mapped_column( - String(15), comment="FACTURAIMPO original (denorm for SM)" - ) - procedence: Mapped[Optional[str]] = mapped_column(String(3)) # PROCEDENCIA - - # ── Relationships ───────────────────────────────────────────────────── - header: Mapped["DischargeHeader"] = relationship(back_populates="details") - export_line: Mapped[Optional["LineItem"]] = relationship( - foreign_keys=[export_item_line_id], - ) - import_line: Mapped["LineItem"] = relationship( - foreign_keys=[import_item_line_id], - ) - movement: Mapped["BalanceMovement"] = relationship( - foreign_keys=[movement_id], - back_populates="discharge_detail", - ) - - -# --------------------------------------------------------------------------- -# 4. DischargeScrap -# -# Mermas, desperdicios and destrucciones. -# Can be standalone (no export invoice) or linked to a DischargeHeader. -# Always generates a BalanceMovement of type WASTE / SCRAP / DESTRUCTION. -# -# Replaces: SDescargaS + SDescargaMerDes -# --------------------------------------------------------------------------- - -class DischargeScrap(Base, TenantScopedMixin, TimestampMixin): - """ - Waste / scrap / destruction records. - Replaces SDescargaS and the waste portion of SDescargaMerDes. - """ - - __tablename__ = "discharge_scrap" - __table_args__ = ( - Index("ix_dischscrap_header", "discharge_header_id"), - Index("ix_dischscrap_import_lot", "import_item_line_id"), - Index("ix_dischscrap_date", "tenant_id", "scrap_date"), - {"schema": "a24"}, - ) - - id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=True) - - # ── Optional parent discharge ───────────────────────────────────────── - discharge_header_id: Mapped[Optional[int]] = mapped_column( - ForeignKey("a24.discharge_header.id"), - comment="NULL when scrap is registered independently (not tied to an export).", - ) - - # ── The import lot being scrapped ────────────────────────────────────── - import_item_line_id: Mapped[int] = mapped_column( - ForeignKey("a76.item_lines.id"), - ) - - # ── Ledger row recording this scrap ─────────────────────────────────── - movement_id: Mapped[Optional[int]] = mapped_column( - BigInteger, ForeignKey("a24.balance_movement.id"), - ) - - # ── Type ────────────────────────────────────────────────────────────── - scrap_type: Mapped[str] = mapped_column( - String(1), comment="M=merma, D=desperdicio, S=scrap, X=destrucción" - ) - - # ── Finished-good export line that generated this scrap ─────────────── - finished_good_line_id: Mapped[Optional[int]] = mapped_column( - ForeignKey("a76.item_lines.id"), - comment="Export line of the product whose manufacture created this scrap.", - ) - finished_good_part: Mapped[Optional[str]] = mapped_column(String(70)) - - # ── Scrap export pedimento (if scrap is exported separately) ────────── - scrap_export_invoice_id: Mapped[Optional[int]] = mapped_column( - ForeignKey("a76.invoice_header.id"), - comment="If desperdicio has its own export pedimento.", - ) - - # ── The scrapped material ───────────────────────────────────────────── - part_number: Mapped[str] = mapped_column(String(70)) - item_class: Mapped[Optional[str]] = mapped_column(String(8)) - quantity: Mapped[Decimal] = mapped_column(Numeric(19, 8)) - unit_of_measure: Mapped[str] = mapped_column(String(5)) - value_mn: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) - value_me: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) - net_weight: Mapped[Optional[Decimal]] = mapped_column(Numeric(19, 8)) - procedence: Mapped[Optional[str]] = mapped_column(String(3)) - scrap_date: Mapped[datetime.date] = mapped_column(Date) - - # ── Relationships ───────────────────────────────────────────────────── - header: Mapped[Optional["DischargeHeader"]] = relationship( - back_populates="scraps", - ) - import_line: Mapped["LineItem"] = relationship( - foreign_keys=[import_item_line_id], - ) - finished_good_line: Mapped[Optional["LineItem"]] = relationship( - foreign_keys=[finished_good_line_id], - ) - scrap_export_invoice: Mapped[Optional["InvoiceHeader"]] = relationship( - foreign_keys=[scrap_export_invoice_id], - ) - movement: Mapped[Optional["BalanceMovement"]] = relationship( - foreign_keys=[movement_id], - ) \ No newline at end of file diff --git a/backend/api/v1/modules/a24/fa/fa_classes/dto.py b/backend/api/v1/modules/a24/fa/fa_classes/dto.py deleted file mode 100644 index 671a010d..00000000 --- a/backend/api/v1/modules/a24/fa/fa_classes/dto.py +++ /dev/null @@ -1,107 +0,0 @@ -""" -DTOs (Data Transfer Objects) para módulo de clases de activos fijos (FA) -""" - -from datetime import datetime -from decimal import Decimal -from typing import Optional - -from pydantic import BaseModel, ConfigDict, Field - - -class FAClassCreateDTO(BaseModel): - """DTO para crear una clase de activo fijo""" - - class_id: int = Field(..., description="ID de la clase base en a76.classes") - - import_tariff_code: Optional[str] = Field( - None, max_length=10, description="Código de fracción de importación" - ) - import_tariff_type: Optional[str] = Field( - None, max_length=6, description="Tipo de fracción de importación" - ) - export_tariff_code: Optional[str] = Field( - None, max_length=10, description="Código de fracción de exportación" - ) - export_tariff_type: Optional[str] = Field( - None, max_length=6, description="Tipo de fracción de exportación" - ) - depreciation_rate: Optional[Decimal] = Field( - None, description="Tasa de depreciación anual", ge=0, le=100 - ) - fda_code: Optional[str] = Field( - None, max_length=20, description="Código FDA" - ) - eccn_code: Optional[str] = Field( - None, max_length=20, description="Código ECCN (Export Control Classification Number)" - ) - class_enabled: Optional[bool] = Field( - True, description="Indica si la clase está habilitada" - ) - - class Config: - from_attributes = True - - -class FAClassUpdateDTO(BaseModel): - """DTO para actualizar una clase de activo fijo""" - - import_tariff_code: Optional[str] = Field( - None, max_length=10, description="Código de fracción de importación" - ) - import_tariff_type: Optional[str] = Field( - None, max_length=6, description="Tipo de fracción de importación" - ) - export_tariff_code: Optional[str] = Field( - None, max_length=10, description="Código de fracción de exportación" - ) - export_tariff_type: Optional[str] = Field( - None, max_length=6, description="Tipo de fracción de exportación" - ) - depreciation_rate: Optional[Decimal] = Field( - None, description="Tasa de depreciación anual", ge=0, le=100 - ) - fda_code: Optional[str] = Field( - None, max_length=20, description="Código FDA" - ) - eccn_code: Optional[str] = Field( - None, max_length=20, description="Código ECCN" - ) - class_enabled: Optional[bool] = Field( - None, description="Indica si la clase está habilitada" - ) - - class Config: - from_attributes = True - - -class FAClassResponseDTO(BaseModel): - """DTO para respuesta de clase de activo fijo""" - - id: int - tenant_id: int - company_id: int - class_id: int - import_tariff_code: Optional[str] = None - import_tariff_type: Optional[str] = None - export_tariff_code: Optional[str] = None - export_tariff_type: Optional[str] = None - depreciation_rate: Optional[Decimal] = None - fda_code: Optional[str] = None - eccn_code: Optional[str] = None - class_enabled: Optional[bool] = None - created_at: datetime - updated_at: datetime - - model_config = ConfigDict(from_attributes=True) - - -class FAClassListResponseDTO(BaseModel): - """DTO para respuesta de lista paginada de clases de activos fijos""" - - items: list[FAClassResponseDTO] - total: int - page: int - page_size: int - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/a24/fa/fa_classes/models.py b/backend/api/v1/modules/a24/fa/fa_classes/models.py deleted file mode 100644 index 776baf54..00000000 --- a/backend/api/v1/modules/a24/fa/fa_classes/models.py +++ /dev/null @@ -1,49 +0,0 @@ -from decimal import Decimal -from typing import Optional - -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base -from sqlalchemy import ( - Boolean, - ForeignKeyConstraint, - Integer, - Numeric, - PrimaryKeyConstraint, - String, -) -from sqlalchemy.orm import Mapped, mapped_column - - -class QClasses(Base, TenantScopedMixin, TimestampMixin): - __tablename__ = "fa_classes" # QClases - __table_args__ = ( - PrimaryKeyConstraint("id", name="qclases_pk"), - ForeignKeyConstraint( - ["class_id"], ["a76.classes.id"], name="fk_qclasses_classes" - ), - {"schema": "a24"}, - ) - - id: Mapped[int] = mapped_column(Integer, primary_key=True) - class_id: Mapped[int] = mapped_column(Integer, nullable=False) - - import_tariff_code: Mapped[Optional[str]] = mapped_column( - String(10), nullable=True - ) # FRACCIONIMPO - import_tariff_type: Mapped[Optional[str]] = mapped_column( - String(6), nullable=True - ) # TIPOFRACIMPO - export_tariff_code: Mapped[Optional[str]] = mapped_column( - String(10), nullable=True - ) # FRACCIONEXPO - export_tariff_type: Mapped[Optional[str]] = mapped_column( - String(6), nullable=True - ) # TIPOFRACEXPO - depreciation_rate: Mapped[Optional[Decimal]] = mapped_column( - Numeric(5, 2), nullable=True - ) # TASADEPRECIA - fda_code: Mapped[Optional[str]] = mapped_column(String(20), nullable=True) # FDA - eccn_code: Mapped[Optional[str]] = mapped_column(String(20), nullable=True) # ECCN - class_enabled: Mapped[bool] = mapped_column( - Boolean, default=True, server_default="true", nullable=False - ) # HABILITADESHABILITACLASE diff --git a/backend/api/v1/modules/a24/fa/fa_classes/routes.py b/backend/api/v1/modules/a24/fa/fa_classes/routes.py deleted file mode 100644 index 9e57a5d6..00000000 --- a/backend/api/v1/modules/a24/fa/fa_classes/routes.py +++ /dev/null @@ -1,32 +0,0 @@ -""" -Endpoints API para gestión de clases de activos fijos (FA) -""" - -from typing import Any, Dict -from fastapi import Depends, Query -from sqlalchemy.orm import Session - -from core.database import get_core_db -from core.security import get_current_user -from api.v1.common.tenant_crud_routes import TenantCRUDRoutes, validate_access_to_resource - -from .dto import FAClassCreateDTO, FAClassResponseDTO, FAClassUpdateDTO -from .service import FAClassService - -# Create router with generic CRUD routes -crud_routes = TenantCRUDRoutes( - service=FAClassService, - create_schema=FAClassCreateDTO, - update_schema=FAClassUpdateDTO, - response_schema=FAClassResponseDTO, - prefix="/fa/classes", - tags=["a24 / fa / classes"], - resource_name="Fixed Asset Class", - id_name="fa_class_id", - enable_list=True, - enable_filters=True, - default_page_size=50, - max_page_size=100, -) - -router = crud_routes.router diff --git a/backend/api/v1/modules/a24/fa/fa_classes/service.py b/backend/api/v1/modules/a24/fa/fa_classes/service.py deleted file mode 100644 index 59362ff9..00000000 --- a/backend/api/v1/modules/a24/fa/fa_classes/service.py +++ /dev/null @@ -1,215 +0,0 @@ -""" -Capa de servicio para lógica de negocio de clases de activos fijos (FA) -""" - -import logging -from typing import Any, Dict, List, Optional - -from fastapi import HTTPException -from sqlalchemy import and_ -from sqlalchemy.exc import IntegrityError -from sqlalchemy.orm import Session - -from .dto import FAClassCreateDTO, FAClassResponseDTO, FAClassUpdateDTO -from .models import QClasses - -logger = logging.getLogger(__name__) - - -class FAClassService: - """Servicio para gestión de clases de activos fijos""" - - @staticmethod - def get_all( - db: Session, - tenant_id: int, - company_id: int, - skip: int = 0, - limit: int = 100, - filters: Optional[Dict[str, Any]] = None, - ) -> tuple[List[QClasses], int]: - """ - Obtener todas las clases de activos fijos con paginación y filtros - """ - query = db.query(QClasses).filter( - QClasses.tenant_id == tenant_id, QClasses.company_id == company_id - ) - - if filters: - if filters.get("class_id"): - query = query.filter(QClasses.class_id == filters["class_id"]) - if filters.get("fda_code"): - query = query.filter( - QClasses.fda_code.ilike(f"%{filters['fda_code']}%") - ) - if filters.get("class_enabled") is not None: - query = query.filter( - QClasses.class_enabled == filters["class_enabled"] - ) - - total = query.count() - items = query.offset(skip).limit(limit).all() - - return items, total - - @staticmethod - def get_by_id( - db: Session, fa_class_id: int, tenant_id: int, company_id: int - ) -> Optional[QClasses]: - """Obtener una clase de activo fijo por ID""" - return ( - db.query(QClasses) - .filter( - QClasses.id == fa_class_id, - QClasses.tenant_id == tenant_id, - QClasses.company_id == company_id, - ) - .first() - ) - - @staticmethod - def get_by_class_id( - db: Session, class_id: int, tenant_id: int, company_id: int - ) -> Optional[QClasses]: - """Obtener una clase de activo fijo por class_id de a76""" - return ( - db.query(QClasses) - .filter( - QClasses.class_id == class_id, - QClasses.tenant_id == tenant_id, - QClasses.company_id == company_id, - ) - .first() - ) - - @staticmethod - def create( - db: Session, fa_class_data: FAClassCreateDTO, tenant_id: int, company_id: int - ) -> QClasses: - """Crear una nueva clase de activo fijo""" - try: - # Verificar que la clase base existe en a76.classes - from api.v1.modules.a76.classes.models import Class - - base_class = ( - db.query(Class) - .filter( - Class.id == fa_class_data.class_id, - Class.tenant_id == tenant_id, - Class.company_id == company_id, - ) - .first() - ) - - if not base_class: - raise HTTPException( - status_code=404, - detail=f"Base class with id {fa_class_data.class_id} not found" - ) - - # Verificar que no exista ya una clase de activo fijo para esta clase base - existing = FAClassService.get_by_class_id( - db, fa_class_data.class_id, tenant_id, company_id - ) - if existing: - raise HTTPException( - status_code=400, - detail=f"Fixed asset class already exists for class_id {fa_class_data.class_id}" - ) - - data_dict = fa_class_data.model_dump() - - new_fa_class = QClasses( - **data_dict, - tenant_id=tenant_id, - company_id=company_id, - ) - - db.add(new_fa_class) - db.commit() - db.refresh(new_fa_class) - - return new_fa_class - - except IntegrityError as e: - db.rollback() - logger.error(f"IntegrityError creating fixed asset class: {str(e)}") - raise HTTPException( - status_code=400, - detail=f"Database constraint violation: {str(e.orig)}" - ) - except HTTPException: - raise - except Exception as e: - db.rollback() - logger.error(f"Error creating fixed asset class: {str(e)}") - raise HTTPException(status_code=500, detail=str(e)) - - @staticmethod - def update( - db: Session, - fa_class_id: int, - tenant_id: int, - fa_class_data: FAClassUpdateDTO, - company_id: int, - ) -> QClasses: - """Actualizar una clase de activo fijo""" - fa_class = FAClassService.get_by_id(db, fa_class_id, tenant_id, company_id) - - if not fa_class: - raise HTTPException( - status_code=404, - detail=f"Fixed asset class {fa_class_id} not found" - ) - - try: - update_data = fa_class_data.model_dump(exclude_unset=True) - - for key, value in update_data.items(): - setattr(fa_class, key, value) - - db.commit() - db.refresh(fa_class) - - return fa_class - - except IntegrityError as e: - db.rollback() - logger.error(f"IntegrityError updating fixed asset class: {str(e)}") - raise HTTPException( - status_code=400, - detail=f"Database constraint violation: {str(e.orig)}" - ) - except Exception as e: - db.rollback() - logger.error(f"Error updating fixed asset class: {str(e)}") - raise HTTPException(status_code=500, detail=str(e)) - - @staticmethod - def delete( - db: Session, fa_class_id: int, tenant_id: int, company_id: int - ) -> None: - """Eliminar una clase de activo fijo""" - fa_class = FAClassService.get_by_id(db, fa_class_id, tenant_id, company_id) - - if not fa_class: - raise HTTPException( - status_code=404, - detail=f"Fixed asset class {fa_class_id} not found" - ) - - try: - db.delete(fa_class) - db.commit() - - except IntegrityError as e: - db.rollback() - logger.error(f"IntegrityError deleting fixed asset class: {str(e)}") - raise HTTPException( - status_code=400, - detail="Cannot delete: Fixed asset class is referenced by other records" - ) - except Exception as e: - db.rollback() - logger.error(f"Error deleting fixed asset class: {str(e)}") - raise HTTPException(status_code=500, detail=str(e)) diff --git a/backend/api/v1/modules/a24/fa/fa_item_lines/__init__.py b/backend/api/v1/modules/a24/fa/fa_item_lines/__init__.py deleted file mode 100644 index 73edc52d..00000000 --- a/backend/api/v1/modules/a24/fa/fa_item_lines/__init__.py +++ /dev/null @@ -1,17 +0,0 @@ -""" -Módulo de líneas de activos fijos (FA Item Lines) para Anexo 24 -""" - -from .models import FaLineItem -from .dto import FaLineItemCreateDTO, FaLineItemUpdateDTO, FaLineItemResponseDTO -from .service import FaLineItemService -from .routes import router - -__all__ = [ - "FaLineItem", - "FaLineItemCreateDTO", - "FaLineItemUpdateDTO", - "FaLineItemResponseDTO", - "FaLineItemService", - "router", -] diff --git a/backend/api/v1/modules/a24/fa/fa_item_lines/dto.py b/backend/api/v1/modules/a24/fa/fa_item_lines/dto.py deleted file mode 100644 index 34b8f35b..00000000 --- a/backend/api/v1/modules/a24/fa/fa_item_lines/dto.py +++ /dev/null @@ -1,165 +0,0 @@ -""" -DTOs (Data Transfer Objects) para líneas de activos fijos (FA Item Lines) -""" - -from datetime import datetime -from typing import Optional - -from pydantic import BaseModel, ConfigDict, Field - - -class FaLineItemCreateDTO(BaseModel): - """DTO para crear una línea de activo fijo""" - - # line_item_id references the id in a76.item_lines - # Not required on creation - will be set when the LineItem is created - line_item_id: Optional[int] = Field(None, description="ID de la línea base en a76.item_lines") - - # Asset information (SCAF specific) - asset_number: Optional[str] = Field( - None, max_length=25, description="Número de activo" - ) - asset_photo: Optional[str] = Field( - None, max_length=255, description="Foto del activo fijo" - ) - equipment_message: Optional[str] = Field( - None, max_length=40, description="Mensaje del equipo" - ) - invoice_type_asset: Optional[str] = Field( - None, max_length=6, description="Tipo de factura para activo" - ) - return_import_invoice: Optional[str] = Field( - None, max_length=15, description="Factura de importación de retorno" - ) - return_import_date: Optional[int] = Field( - None, description="Fecha de factura de importación de retorno" - ) - movement_type_import: Optional[str] = Field( - None, max_length=3, description="Tipo de movimiento de importación" - ) - - # Cross-references for import repair - search_invoice: Optional[str] = Field( - None, max_length=15, description="Factura de búsqueda cruzada" - ) - search_line: Optional[int] = Field(None, description="Línea de búsqueda cruzada") - - # Search type - search_type: Optional[str] = Field( - None, max_length=10, description="Tipo de búsqueda" - ) - - # Subitems - is_subitem: Optional[bool] = Field(False, description="Es subpartida") - contains_subitems: Optional[bool] = Field(False, description="Contiene subpartidas") - includes_subitems: Optional[bool] = Field(False, description="Incluye subpartidas") - subitem_number: Optional[int] = Field(0, description="Número de subpartida") - - # Special flags - discharge: Optional[bool] = Field(None, description="Indicador de descarga") - own_equipment: Optional[bool] = Field(None, description="Equipo propio") - omit_annex31: Optional[bool] = Field(None, description="Omitir en Anexo 31") - - model_config = ConfigDict(from_attributes=True) - - -class FaLineItemUpdateDTO(BaseModel): - """DTO para actualizar una línea de activo fijo""" - - # Asset information (SCAF specific) - asset_number: Optional[str] = Field( - None, max_length=25, description="Número de activo" - ) - asset_photo: Optional[str] = Field( - None, max_length=255, description="Foto del activo fijo" - ) - equipment_message: Optional[str] = Field( - None, max_length=40, description="Mensaje del equipo" - ) - invoice_type_asset: Optional[str] = Field( - None, max_length=6, description="Tipo de factura para activo" - ) - return_import_invoice: Optional[str] = Field( - None, max_length=15, description="Factura de importación de retorno" - ) - return_import_date: Optional[int] = Field( - None, description="Fecha de factura de importación de retorno" - ) - movement_type_import: Optional[str] = Field( - None, max_length=3, description="Tipo de movimiento de importación" - ) - - # Cross-references for import repair - search_invoice: Optional[str] = Field( - None, max_length=15, description="Factura de búsqueda cruzada" - ) - search_line: Optional[int] = Field(None, description="Línea de búsqueda cruzada") - - # Search type - search_type: Optional[str] = Field( - None, max_length=10, description="Tipo de búsqueda" - ) - - # Subitems - is_subitem: Optional[bool] = Field(False, description="Es subpartida") - contains_subitems: Optional[bool] = Field(False, description="Contiene subpartidas") - includes_subitems: Optional[bool] = Field(False, description="Incluye subpartidas") - subitem_number: Optional[int] = Field(None, description="Número de subpartida") - - # Special flags - discharge: Optional[bool] = Field(None, description="Indicador de descarga") - own_equipment: Optional[bool] = Field(None, description="Equipo propio") - omit_annex31: Optional[bool] = Field(None, description="Omitir en Anexo 31") - - model_config = ConfigDict(from_attributes=True) - - -class FaLineItemResponseDTO(BaseModel): - """DTO para respuesta de línea de activo fijo""" - - id: int = Field(..., description="ID de la línea de activo fijo") - tenant_id: int = Field(..., description="ID del tenant") - company_id: int = Field(..., description="ID de la empresa") - - # Asset information (SCAF specific) - asset_number: Optional[str] = Field(None, description="Número de activo") - asset_photo: Optional[str] = Field(None, description="Foto del activo fijo") - equipment_message: Optional[str] = Field(None, description="Mensaje del equipo") - invoice_type_asset: Optional[str] = Field( - None, description="Tipo de factura para activo" - ) - return_import_invoice: Optional[str] = Field( - None, description="Factura de importación de retorno" - ) - return_import_date: Optional[int] = Field( - None, description="Fecha de factura de importación de retorno" - ) - movement_type_import: Optional[str] = Field( - None, description="Tipo de movimiento de importación" - ) - - # Cross-references for import repair - search_invoice: Optional[str] = Field( - None, description="Factura de búsqueda cruzada" - ) - search_line: Optional[int] = Field(None, description="Línea de búsqueda cruzada") - - # Search type - search_type: Optional[str] = Field(None, description="Tipo de búsqueda") - - # Subitems - is_subitem: Optional[bool] = Field(False, description="Es subpartida") - contains_subitems: Optional[bool] = Field(False, description="Contiene subpartidas") - includes_subitems: Optional[bool] = Field(False, description="Incluye subpartidas") - subitem_number: Optional[int] = Field(None, description="Número de subpartida") - - # Special flags - discharge: Optional[bool] = Field(None, description="Indicador de descarga") - own_equipment: Optional[bool] = Field(None, description="Equipo propio") - omit_annex31: Optional[bool] = Field(None, description="Omitir en Anexo 31") - - # Timestamps - created_at: datetime = Field(..., description="Fecha de creación") - updated_at: datetime = Field(..., description="Fecha de actualización") - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/a24/fa/fa_item_lines/models.py b/backend/api/v1/modules/a24/fa/fa_item_lines/models.py deleted file mode 100644 index f07b73c8..00000000 --- a/backend/api/v1/modules/a24/fa/fa_item_lines/models.py +++ /dev/null @@ -1,81 +0,0 @@ -""" -Modelo ORM para datos específicos de líneas de Activos Fijos - Anexo 24 -Extensión de a76.item_lines para SCAF (Sistema de Control de Activo Fijo) -""" - -from typing import TYPE_CHECKING, Optional -from sqlalchemy import ( - Boolean, - ForeignKeyConstraint, - Integer, - PrimaryKeyConstraint, - String, -) -from sqlalchemy.orm import Mapped, mapped_column, relationship -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base - -if TYPE_CHECKING: - from api.v1.modules.a76.items.models import LineItem - - -class FaLineItem(Base, TenantScopedMixin, TimestampMixin): - """ - Tabla fa_item_lines: Extensión de Anexo 24 para líneas de Activos Fijos. - Hereda el ID de la tabla item_lines en a76. - """ - - __tablename__ = "fa_item_lines" - __table_args__ = ( - PrimaryKeyConstraint("id", name="fa_item_lines_pkey"), - ForeignKeyConstraint( - ["id"], ["a76.item_lines.id"], name="fk_fa_item_lines_master" - ), - {"schema": "a24"}, - ) - - id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=False) - - # Asset information (SCAF specific) - asset_number: Mapped[Optional[str]] = mapped_column(String(25)) # ASSETNUMBER - asset_photo: Mapped[Optional[str]] = mapped_column(String(255)) # FOTOACTIVOFIJO - equipment_message: Mapped[Optional[str]] = mapped_column(String(40)) # EQI_MENSAJE - invoice_type_asset: Mapped[Optional[str]] = mapped_column( - String(6) - ) # TIPOFACTURAASSET - return_import_invoice: Mapped[Optional[str]] = mapped_column( - String(15) - ) # FACTURAIMPORET - return_import_date: Mapped[Optional[int]] = mapped_column( - Integer - ) # FECHAFACIMPORET - movement_type_import: Mapped[Optional[str]] = mapped_column( - String(3) - ) # TIPOMOVIMPO - - # Cross-references IN CASE OF IMPORT REPAIR - search_invoice: Mapped[Optional[str]] = mapped_column( - String(15) - ) # FACTURAEXPO (when line is import) / FACTURAIMPO (when line is export) - search_line: Mapped[Optional[int]] = mapped_column( - Integer - ) # LINEAEXPO (when line is import) / LINEAIMPO (when line is export) - - # Search type - search_type: Mapped[Optional[str]] = mapped_column(String(10)) # TIPOBUSQUEDA - - # Subitems - is_subitem: Mapped[Optional[bool]] = mapped_column(Boolean) # ESSUBPARTIDA - contains_subitems: Mapped[Optional[bool]] = mapped_column(Boolean) # CONTIENESUBP - subitem_number: Mapped[Optional[int]] = mapped_column(Integer) # SUBPARTIDA - - # Special flags - discharge: Mapped[Optional[bool]] = mapped_column(Boolean) # DESCARGA - own_equipment: Mapped[Optional[bool]] = mapped_column(Boolean) # EQUIPOPROPIO - omit_annex31: Mapped[Optional[bool]] = mapped_column(Boolean) # OMITITENANEXO31 - - # --- RELACIÓN --- - master_info: Mapped["LineItem"] = relationship("LineItem", back_populates="fa_data") - - def __repr__(self) -> str: - return f"" diff --git a/backend/api/v1/modules/a24/fa/fa_item_lines/routes.py b/backend/api/v1/modules/a24/fa/fa_item_lines/routes.py deleted file mode 100644 index eed5e34d..00000000 --- a/backend/api/v1/modules/a24/fa/fa_item_lines/routes.py +++ /dev/null @@ -1,35 +0,0 @@ -""" -Endpoints API para gestión de líneas de activos fijos (FA Item Lines) -""" - -from typing import Any, Dict -from fastapi import Depends, Query -from sqlalchemy.orm import Session - -from core.database import get_core_db -from core.security import get_current_user -from api.v1.common.tenant_crud_routes import ( - TenantCRUDRoutes, - validate_access_to_resource, -) - -from .dto import FaLineItemCreateDTO, FaLineItemResponseDTO, FaLineItemUpdateDTO -from .service import FaLineItemService - -# Create router with generic CRUD routes -crud_routes = TenantCRUDRoutes( - service=FaLineItemService, - create_schema=FaLineItemCreateDTO, - update_schema=FaLineItemUpdateDTO, - response_schema=FaLineItemResponseDTO, - prefix="/fa/item-lines", - tags=["a24 / fa / item-lines"], - resource_name="Fixed Asset Line Item", - id_name="fa_line_item_id", - enable_list=True, - enable_filters=True, - default_page_size=50, - max_page_size=100, -) - -router = crud_routes.router diff --git a/backend/api/v1/modules/a24/fa/fa_item_lines/service.py b/backend/api/v1/modules/a24/fa/fa_item_lines/service.py deleted file mode 100644 index aaa8e851..00000000 --- a/backend/api/v1/modules/a24/fa/fa_item_lines/service.py +++ /dev/null @@ -1,258 +0,0 @@ -""" -Capa de servicio para lógica de negocio de líneas de activos fijos (FA Item Lines) -""" - -import logging -from typing import Any, Dict, List, Optional - -from fastapi import HTTPException -from sqlalchemy import and_ -from sqlalchemy.exc import IntegrityError -from sqlalchemy.orm import Session - -from .dto import FaLineItemCreateDTO, FaLineItemResponseDTO, FaLineItemUpdateDTO -from .models import FaLineItem - -logger = logging.getLogger(__name__) - - -class FaLineItemService: - """Servicio para gestión de líneas de activos fijos""" - - @staticmethod - def get_all( - db: Session, - tenant_id: int, - company_id: int, - skip: int = 0, - limit: int = 100, - filters: Optional[Dict[str, Any]] = None, - ) -> tuple[List[FaLineItem], int]: - """ - Obtener todas las líneas de activos fijos con paginación y filtros - """ - query = db.query(FaLineItem).filter( - FaLineItem.tenant_id == tenant_id, FaLineItem.company_id == company_id - ) - - if filters: - if filters.get("asset_number"): - query = query.filter( - FaLineItem.asset_number.ilike(f"%{filters['asset_number']}%") - ) - if filters.get("invoice_type_asset"): - query = query.filter( - FaLineItem.invoice_type_asset == filters["invoice_type_asset"] - ) - if filters.get("own_equipment") is not None: - query = query.filter( - FaLineItem.own_equipment == filters["own_equipment"] - ) - if filters.get("discharge") is not None: - query = query.filter(FaLineItem.discharge == filters["discharge"]) - - total = query.count() - items = query.offset(skip).limit(limit).all() - - return items, total - - @staticmethod - def get_by_id( - db: Session, fa_line_item_id: int, tenant_id: int, company_id: int - ) -> Optional[FaLineItem]: - """Obtener una línea de activo fijo por ID""" - return ( - db.query(FaLineItem) - .filter( - FaLineItem.id == fa_line_item_id, - FaLineItem.tenant_id == tenant_id, - FaLineItem.company_id == company_id, - ) - .first() - ) - - @staticmethod - def get_by_line_item_id( - db: Session, line_item_id: int, tenant_id: int, company_id: int - ) -> Optional[FaLineItem]: - """Obtener una línea de activo fijo por line_item_id de a76""" - return ( - db.query(FaLineItem) - .filter( - FaLineItem.id == line_item_id, - FaLineItem.tenant_id == tenant_id, - FaLineItem.company_id == company_id, - ) - .first() - ) - - @staticmethod - def create( - db: Session, - fa_line_item_data: FaLineItemCreateDTO, - tenant_id: int, - company_id: int, - ) -> FaLineItem: - """Crear una nueva línea de activo fijo""" - try: - # Verificar que la línea base existe en a76.item_lines - from api.v1.modules.a76.items.models import LineItem - - base_line_item = ( - db.query(LineItem) - .filter( - LineItem.id == fa_line_item_data.line_item_id, - LineItem.tenant_id == tenant_id, - LineItem.company_id == company_id, - ) - .first() - ) - - if not base_line_item: - raise HTTPException( - status_code=404, - detail=f"Base line item with id {fa_line_item_data.line_item_id} not found", - ) - - # Verificar si ya existe una línea FA para esta línea base - existing_fa = ( - db.query(FaLineItem) - .filter( - FaLineItem.id == fa_line_item_data.line_item_id, - FaLineItem.tenant_id == tenant_id, - FaLineItem.company_id == company_id, - ) - .first() - ) - - if existing_fa: - raise HTTPException( - status_code=400, - detail=f"FA line item already exists for line_item_id {fa_line_item_data.line_item_id}", - ) - - # Crear nueva línea FA - fa_line_item = FaLineItem( - id=fa_line_item_data.line_item_id, # Usa el mismo ID que la línea base - tenant_id=tenant_id, - company_id=company_id, - asset_number=fa_line_item_data.asset_number, - asset_photo=fa_line_item_data.asset_photo, - equipment_message=fa_line_item_data.equipment_message, - invoice_type_asset=fa_line_item_data.invoice_type_asset, - return_import_invoice=fa_line_item_data.return_import_invoice, - return_import_date=fa_line_item_data.return_import_date, - movement_type_import=fa_line_item_data.movement_type_import, - search_invoice=fa_line_item_data.search_invoice, - search_line=fa_line_item_data.search_line, - search_type=fa_line_item_data.search_type, - discharge=fa_line_item_data.discharge, - own_equipment=fa_line_item_data.own_equipment, - omit_annex31=fa_line_item_data.omit_annex31, - ) - - db.add(fa_line_item) - db.commit() - db.refresh(fa_line_item) - - logger.info( - f"FA line item created: id={fa_line_item.id}, tenant={tenant_id}, company={company_id}" - ) - - return fa_line_item - - except IntegrityError as e: - db.rollback() - logger.error(f"IntegrityError creating FA line item: {str(e)}") - raise HTTPException( - status_code=400, - detail="Error creating FA line item. Constraint violation.", - ) - except HTTPException: - db.rollback() - raise - except Exception as e: - db.rollback() - logger.error(f"Error creating FA line item: {str(e)}") - raise HTTPException( - status_code=500, detail="Internal server error creating FA line item" - ) - - @staticmethod - def update( - db: Session, - fa_line_item_id: int, - fa_line_item_data: FaLineItemUpdateDTO, - tenant_id: int, - company_id: int, - ) -> FaLineItem: - """Actualizar una línea de activo fijo existente""" - try: - fa_line_item = FaLineItemService.get_by_id( - db, fa_line_item_id, tenant_id, company_id - ) - - if not fa_line_item: - raise HTTPException( - status_code=404, - detail=f"FA line item with id {fa_line_item_id} not found", - ) - - # Actualizar solo los campos proporcionados - update_data = fa_line_item_data.model_dump(exclude_unset=True) - for field, value in update_data.items(): - setattr(fa_line_item, field, value) - - db.commit() - db.refresh(fa_line_item) - - logger.info( - f"FA line item updated: id={fa_line_item.id}, tenant={tenant_id}, company={company_id}" - ) - - return fa_line_item - - except HTTPException: - db.rollback() - raise - except Exception as e: - db.rollback() - logger.error(f"Error updating FA line item: {str(e)}") - raise HTTPException( - status_code=500, detail="Internal server error updating FA line item" - ) - - @staticmethod - def delete( - db: Session, fa_line_item_id: int, tenant_id: int, company_id: int - ) -> bool: - """Eliminar una línea de activo fijo""" - try: - fa_line_item = FaLineItemService.get_by_id( - db, fa_line_item_id, tenant_id, company_id - ) - - if not fa_line_item: - raise HTTPException( - status_code=404, - detail=f"FA line item with id {fa_line_item_id} not found", - ) - - db.delete(fa_line_item) - db.commit() - - logger.info( - f"FA line item deleted: id={fa_line_item_id}, tenant={tenant_id}, company={company_id}" - ) - - return True - - except HTTPException: - db.rollback() - raise - except Exception as e: - db.rollback() - logger.error(f"Error deleting FA line item: {str(e)}") - raise HTTPException( - status_code=500, detail="Internal server error deleting FA line item" - ) diff --git a/backend/api/v1/modules/a24/fa/fa_parts/models.py b/backend/api/v1/modules/a24/fa/fa_parts/models.py deleted file mode 100644 index ad813d8e..00000000 --- a/backend/api/v1/modules/a24/fa/fa_parts/models.py +++ /dev/null @@ -1,48 +0,0 @@ -""" -Modelo ORM para datos específicos de Activos Fijos (Q-Partes) - Anexo 24 -""" - -from typing import TYPE_CHECKING, Optional -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base -from sqlalchemy import ( - Integer, - PrimaryKeyConstraint, - String, - ForeignKeyConstraint -) -from sqlalchemy.orm import Mapped, mapped_column, relationship - -if TYPE_CHECKING: - - from api.v1.modules.a76.parts.models import Part - - -class FaPart(Base, TenantScopedMixin, TimestampMixin): - """ - Tabla fa_partes: Extensión de Anexo 24 para Activos Fijos. - """ - - __tablename__ = "fa_partes" - __table_args__ = ( - PrimaryKeyConstraint("id", name="fa_partes_pkey"), - ForeignKeyConstraint( - ["id"], ["a76.parts.id"], name="fk_fa_partes_master" - ), - {"schema": "a24", "extend_existing": True}, - ) - - # El ID hereda el valor de la tabla parts - id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=False) - - # --- CAMPOS ESPECÍFICOS FISCALES (Q-PARTES) --- - origin_country: Mapped[Optional[str]] = mapped_column(String(3)) # PAIS - sector: Mapped[Optional[str]] = mapped_column(String(8)) # SECTOR - fraction_type: Mapped[Optional[str]] = mapped_column(String(7)) # TIPOFRACCION - - # --- RELACIÓN --- - # Usamos string "Part" para evitar que truene al inicializar los mappers - master_info: Mapped["Part"] = relationship("Part", back_populates="fa_data") - - def __repr__(self) -> str: - return f"" \ No newline at end of file diff --git a/backend/api/v1/modules/a24/inv/bom/models.py b/backend/api/v1/modules/a24/inv/bom/models.py deleted file mode 100644 index 932a4564..00000000 --- a/backend/api/v1/modules/a24/inv/bom/models.py +++ /dev/null @@ -1,56 +0,0 @@ -""" -Modelo ORM para la lista de materiales (BOM) de una parte - Anexo 24 -""" - -from typing import TYPE_CHECKING, Optional -from decimal import Decimal -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base -from sqlalchemy import ( - Integer, - Numeric, - String, - PrimaryKeyConstraint, - ForeignKeyConstraint -) -from sqlalchemy.orm import Mapped, mapped_column, relationship - -if TYPE_CHECKING: - from api.v1.modules.a76.parts.models import Part - - -class BillOfMaterial(Base, TenantScopedMixin, TimestampMixin): - """ - Tabla inv_bom: Lista de materiales para una parte. - """ - __tablename__ = "inv_bom" - __table_args__ = ( - PrimaryKeyConstraint("id", name="inv_bom_pkey"), - ForeignKeyConstraint( - ["parent_part_id"], ["a76.parts.id"], name="fk_inv_bom_parent" - ), - ForeignKeyConstraint( - ["component_part_id"], ["a76.parts.id"], name="fk_inv_bom_component" - ), - {"schema": "a24", "extend_existing": True}, - ) - - id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) - parent_part_id: Mapped[int] = mapped_column(Integer, nullable=False) - component_part_id: Mapped[int] = mapped_column(Integer, nullable=False) - quantity: Mapped[Decimal] = mapped_column(Numeric(19, 8), default=Decimal('1.0')) - - # Nuevos campos Legacy - uom_code: Mapped[str] = mapped_column(String(5), nullable=False) - procedure_type: Mapped[Optional[str]] = mapped_column(String(10), nullable=True) # TEMPORAL, DEFINITIVA, CTM - is_percentage: Mapped[bool] = mapped_column(default=True) - raw_material: Mapped[Optional[Decimal]] = mapped_column(Numeric(19, 8), nullable=True) - waste: Mapped[Optional[Decimal]] = mapped_column(Numeric(19, 8), nullable=True) - merma: Mapped[Optional[Decimal]] = mapped_column(Numeric(19, 8), nullable=True) - - # Relaciones - parent_part: Mapped["Part"] = relationship("Part", foreign_keys=[parent_part_id], back_populates="bom_items") - component_part: Mapped["Part"] = relationship("Part", foreign_keys=[component_part_id]) - - def __repr__(self) -> str: - return f"" diff --git a/backend/api/v1/modules/a24/inv/inv_aphis/__init__.py b/backend/api/v1/modules/a24/inv/inv_aphis/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/backend/api/v1/modules/a24/inv/inv_aphis/dto.py b/backend/api/v1/modules/a24/inv/inv_aphis/dto.py deleted file mode 100644 index 1631ba3c..00000000 --- a/backend/api/v1/modules/a24/inv/inv_aphis/dto.py +++ /dev/null @@ -1,17 +0,0 @@ -from .inv_aphis_general.dto import InvPartAphisGeneralDTO -from .inv_aphis_characteristic.dto import InvPartAphisCharacteristicDTO -from .inv_aphis_stype_pitems.dto import InvPartAphisStypePitemsDTO -from .inv_aphis_lpcos.dto import InvPartAphisLpcosDTO -from .inv_aphis_entities.dto import InvPartAphisEntitiesDTO -from .inv_aphis_containers.dto import InvPartAphisContainersDTO -from .inv_aphis_routing.dto import InvPartAphisRoutingDTO - -__all__ = [ - "InvPartAphisGeneralDTO", - "InvPartAphisCharacteristicDTO", - "InvPartAphisStypePitemsDTO", - "InvPartAphisLpcosDTO", - "InvPartAphisEntitiesDTO", - "InvPartAphisContainersDTO", - "InvPartAphisRoutingDTO" -] diff --git a/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_catalog/__init__.py b/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_catalog/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_catalog/dto.py b/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_catalog/dto.py deleted file mode 100644 index 7ece8ee1..00000000 --- a/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_catalog/dto.py +++ /dev/null @@ -1,57 +0,0 @@ -from pydantic import BaseModel, ConfigDict, field_validator -from typing import Optional, List, Dict, Any -from datetime import date - - -class AphisCatalogDTO(BaseModel): - id: Optional[int] = None - - # --- Pestaña 1: General --- - program_code: Optional[str] = None - processing_code: Optional[str] = None - aphis_type: Optional[str] = None - disclaimer: Optional[str] = None - electronic_image: Optional[str] = None - confidential: Optional[str] = None - global_product_id: Optional[str] = None - intended_use_code: Optional[str] = None - intended_use_description: Optional[str] = None - item_type: Optional[str] = None - product_code: Optional[str] = None - product_code_2: Optional[str] = None - product_code_3: Optional[str] = None - scientific_genus_name: Optional[str] = None - scientific_species_name: Optional[str] = None - scientific_sub_species_name: Optional[str] = None - common_name_specific: Optional[str] = None - common_name_general: Optional[str] = None - signed_doc: Optional[str] = None - signed_doc_date: Optional[date] = None - signed_doc_id: Optional[str] = None - invoice_number: Optional[str] = None - quantity_1: Optional[str] = None - quantity_2: Optional[str] = None - quantity_3: Optional[str] = None - inspection: Optional[str] = None - inspection_date: Optional[date] = None - inspection_loc_date: Optional[date] = None - inspection_location: Optional[str] = None - country_production: Optional[str] = None - country_source: Optional[str] = None - - # --- Pestañas 2-7: Detalles (Listas de objetos) --- - characteristics: Optional[List[Dict[str, Any]]] = [] - pitems: Optional[List[Dict[str, Any]]] = [] - lpcos: Optional[List[Dict[str, Any]]] = [] - entities: Optional[List[Dict[str, Any]]] = [] - containers: Optional[List[Dict[str, Any]]] = [] - routing: Optional[List[Dict[str, Any]]] = [] - - model_config = ConfigDict(from_attributes=True) - - @field_validator("signed_doc_date", "inspection_date", "inspection_loc_date", mode="before") - @classmethod - def empty_to_none(cls, v): - if v == "": - return None - return v diff --git a/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_catalog/models.py b/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_catalog/models.py deleted file mode 100644 index ef494b44..00000000 --- a/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_catalog/models.py +++ /dev/null @@ -1,61 +0,0 @@ -from typing import Optional, List, Dict, Any -from datetime import date -from sqlalchemy import Integer, String, Date, PrimaryKeyConstraint, JSON -from sqlalchemy.orm import Mapped, mapped_column -from core.database import Base - - -class AphisCatalog(Base): - """ - Catálogo global de registros APHIS por empresa. - Soporta las 7 pestañas de información (General + 6 detalles via JSON). - """ - __tablename__ = "inv_aphis_catalog" - __table_args__ = ( - PrimaryKeyConstraint("id", name="inv_aphis_catalog_pkey"), - {"schema": "a24", "extend_existing": True}, - ) - - id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) - company_id: Mapped[int] = mapped_column(Integer, nullable=False, index=True) - - # --- Pestaña 1: General (Campos principales) --- - program_code: Mapped[Optional[str]] = mapped_column(String(10)) - processing_code: Mapped[Optional[str]] = mapped_column(String(10)) - aphis_type: Mapped[Optional[str]] = mapped_column(String(10)) - disclaimer: Mapped[Optional[str]] = mapped_column(String(10)) - electronic_image: Mapped[Optional[str]] = mapped_column(String(50)) - confidential: Mapped[Optional[str]] = mapped_column(String(1)) - global_product_id: Mapped[Optional[str]] = mapped_column(String(100)) - intended_use_code: Mapped[Optional[str]] = mapped_column(String(10)) - intended_use_description: Mapped[Optional[str]] = mapped_column(String(200)) - item_type: Mapped[Optional[str]] = mapped_column(String(20)) - product_code: Mapped[Optional[str]] = mapped_column(String(20)) - product_code_2: Mapped[Optional[str]] = mapped_column(String(20)) - product_code_3: Mapped[Optional[str]] = mapped_column(String(20)) - scientific_genus_name: Mapped[Optional[str]] = mapped_column(String(100)) - scientific_species_name: Mapped[Optional[str]] = mapped_column(String(100)) - scientific_sub_species_name: Mapped[Optional[str]] = mapped_column(String(100)) - common_name_specific: Mapped[Optional[str]] = mapped_column(String(200)) - common_name_general: Mapped[Optional[str]] = mapped_column(String(200)) - signed_doc: Mapped[Optional[str]] = mapped_column(String(100)) - signed_doc_date: Mapped[Optional[date]] = mapped_column(Date) - signed_doc_id: Mapped[Optional[str]] = mapped_column(String(50)) - invoice_number: Mapped[Optional[str]] = mapped_column(String(50)) - quantity_1: Mapped[Optional[str]] = mapped_column(String(50)) - quantity_2: Mapped[Optional[str]] = mapped_column(String(50)) - quantity_3: Mapped[Optional[str]] = mapped_column(String(50)) - inspection: Mapped[Optional[str]] = mapped_column(String(200)) - inspection_date: Mapped[Optional[date]] = mapped_column(Date) - inspection_loc_date: Mapped[Optional[date]] = mapped_column(Date) - inspection_location: Mapped[Optional[str]] = mapped_column(String(200)) - country_production: Mapped[Optional[str]] = mapped_column(String(3)) - country_source: Mapped[Optional[str]] = mapped_column(String(3)) - - # --- Pestañas 2-7: Detalles (Almacenados como JSON por flexibilidad) --- - characteristics: Mapped[Optional[List[Dict[str, Any]]]] = mapped_column(JSON, default=list) - pitems: Mapped[Optional[List[Dict[str, Any]]]] = mapped_column(JSON, default=list) - lpcos: Mapped[Optional[List[Dict[str, Any]]]] = mapped_column(JSON, default=list) - entities: Mapped[Optional[List[Dict[str, Any]]]] = mapped_column(JSON, default=list) - containers: Mapped[Optional[List[Dict[str, Any]]]] = mapped_column(JSON, default=list) - routing: Mapped[Optional[List[Dict[str, Any]]]] = mapped_column(JSON, default=list) diff --git a/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_catalog/router.py b/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_catalog/router.py deleted file mode 100644 index 3e5e21d5..00000000 --- a/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_catalog/router.py +++ /dev/null @@ -1,62 +0,0 @@ -from fastapi import APIRouter, Depends, HTTPException -from sqlalchemy.orm import Session -from typing import List -from core.database import get_core_db -from .models import AphisCatalog -from .dto import AphisCatalogDTO - -router = APIRouter(prefix="/aphis-catalog", tags=["APHIS Catalog"]) - - -@router.get("/", response_model=List[AphisCatalogDTO]) -def list_aphis_catalog(company_id: int, db: Session = Depends(get_core_db)): - records = ( - db.query(AphisCatalog) - .filter(AphisCatalog.company_id == company_id) - .order_by(AphisCatalog.id) - .all() - ) - return records - - -@router.post("/", response_model=AphisCatalogDTO) -def create_aphis_catalog(data: AphisCatalogDTO, company_id: int, db: Session = Depends(get_core_db)): - payload = data.model_dump(exclude={"id"}) - record = AphisCatalog(**payload, company_id=company_id) - db.add(record) - db.commit() - db.refresh(record) - return record - - -@router.put("/{record_id}", response_model=AphisCatalogDTO) -def update_aphis_catalog( - record_id: int, data: AphisCatalogDTO, company_id: int, db: Session = Depends(get_core_db) -): - record = ( - db.query(AphisCatalog) - .filter(AphisCatalog.id == record_id, AphisCatalog.company_id == company_id) - .first() - ) - if not record: - raise HTTPException(status_code=404, detail="Registro no encontrado") - - for field, value in data.model_dump(exclude={"id"}).items(): - setattr(record, field, value) - - db.commit() - db.refresh(record) - return record - - -@router.delete("/{record_id}", status_code=204) -def delete_aphis_catalog(record_id: int, company_id: int, db: Session = Depends(get_core_db)): - record = ( - db.query(AphisCatalog) - .filter(AphisCatalog.id == record_id, AphisCatalog.company_id == company_id) - .first() - ) - if not record: - raise HTTPException(status_code=404, detail="Registro no encontrado") - db.delete(record) - db.commit() diff --git a/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_characteristic/__init__.py b/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_characteristic/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_characteristic/dto.py b/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_characteristic/dto.py deleted file mode 100644 index 232669d7..00000000 --- a/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_characteristic/dto.py +++ /dev/null @@ -1,17 +0,0 @@ -from pydantic import BaseModel, ConfigDict -from typing import Optional, List -from datetime import date - -class InvPartAphisCharacteristicDTO(BaseModel): - id: Optional[int] = None - aphis_general_id: Optional[int] = None - item_id: Optional[str] = None - number_from: Optional[str] = None - number_to: Optional[str] = None - category_type: Optional[str] = None - commodity_qua: Optional[str] = None - commodity_char_qua: Optional[str] = None - description: Optional[str] = None - category_code: Optional[str] = None - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_characteristic/models.py b/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_characteristic/models.py deleted file mode 100644 index 49eb6394..00000000 --- a/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_characteristic/models.py +++ /dev/null @@ -1,35 +0,0 @@ -from typing import TYPE_CHECKING, Optional, List -from datetime import date -from sqlalchemy import ( - Integer, String, Date, Numeric, Boolean, - PrimaryKeyConstraint, ForeignKeyConstraint -) -from sqlalchemy.orm import Mapped, mapped_column, relationship -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base - -if TYPE_CHECKING: - from api.v1.modules.a24.inv.inv_parts.models import InvPart - -class InvPartAphisCharacteristic(Base, TenantScopedMixin, TimestampMixin): - """Módulo: Item characteristic""" - __tablename__ = "inv_aphis_characteristic" - __table_args__ = ( - PrimaryKeyConstraint("id", name="inv_aphis_characteristic_pkey"), - ForeignKeyConstraint(["aphis_general_id"], ["a24.inv_aphis_general.id"]), - {"schema": "a24", "extend_existing": True}, - ) - id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) - aphis_general_id: Mapped[int] = mapped_column(Integer, nullable=False) - - # --- CAMPOS CARACTERISTICAS --- - item_id: Mapped[Optional[str]] = mapped_column(String(50)) - number_from: Mapped[Optional[str]] = mapped_column(String(50)) - number_to: Mapped[Optional[str]] = mapped_column(String(50)) - category_type: Mapped[Optional[str]] = mapped_column(String(50)) - commodity_qua: Mapped[Optional[str]] = mapped_column(String(50)) - commodity_char_qua: Mapped[Optional[str]] = mapped_column(String(50)) - description: Mapped[Optional[str]] = mapped_column(String(200)) - category_code: Mapped[Optional[str]] = mapped_column(String(50)) - - aphis_general: Mapped["InvPartAphisGeneral"] = relationship(back_populates="characteristics") diff --git a/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_containers/__init__.py b/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_containers/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_containers/dto.py b/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_containers/dto.py deleted file mode 100644 index 91a309fb..00000000 --- a/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_containers/dto.py +++ /dev/null @@ -1,12 +0,0 @@ -from pydantic import BaseModel, ConfigDict -from typing import Optional, List -from datetime import date - -class InvPartAphisContainersDTO(BaseModel): - id: Optional[int] = None - aphis_general_id: Optional[int] = None - container_number: Optional[str] = None - length: Optional[str] = None - type: Optional[str] = None - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_containers/models.py b/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_containers/models.py deleted file mode 100644 index aca9c3a7..00000000 --- a/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_containers/models.py +++ /dev/null @@ -1,30 +0,0 @@ -from typing import TYPE_CHECKING, Optional, List -from datetime import date -from sqlalchemy import ( - Integer, String, Date, Numeric, Boolean, - PrimaryKeyConstraint, ForeignKeyConstraint -) -from sqlalchemy.orm import Mapped, mapped_column, relationship -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base - -if TYPE_CHECKING: - from api.v1.modules.a24.inv.inv_parts.models import InvPart - -class InvPartAphisContainers(Base, TenantScopedMixin, TimestampMixin): - """Módulo: containers""" - __tablename__ = "inv_aphis_containers" - __table_args__ = ( - PrimaryKeyConstraint("id", name="inv_aphis_containers_pkey"), - ForeignKeyConstraint(["aphis_general_id"], ["a24.inv_aphis_general.id"]), - {"schema": "a24", "extend_existing": True}, - ) - id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) - aphis_general_id: Mapped[int] = mapped_column(Integer, nullable=False) - - # --- CAMPOS CONTAINERS --- - container_number: Mapped[Optional[str]] = mapped_column(String(50)) - length: Mapped[Optional[str]] = mapped_column(String(20)) - type: Mapped[Optional[str]] = mapped_column(String(50)) - - aphis_general: Mapped["InvPartAphisGeneral"] = relationship(back_populates="containers") diff --git a/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_entities/__init__.py b/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_entities/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_entities/dto.py b/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_entities/dto.py deleted file mode 100644 index c65f66f1..00000000 --- a/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_entities/dto.py +++ /dev/null @@ -1,13 +0,0 @@ -from pydantic import BaseModel, ConfigDict -from typing import Optional, List -from datetime import date - -class InvPartAphisEntitiesDTO(BaseModel): - id: Optional[int] = None - aphis_general_id: Optional[int] = None - consignee_key: Optional[str] = None - broker_key: Optional[str] = None - lpco_auth_party_key: Optional[str] = None - grower_key: Optional[str] = None - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_entities/models.py b/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_entities/models.py deleted file mode 100644 index d9c202f6..00000000 --- a/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_entities/models.py +++ /dev/null @@ -1,31 +0,0 @@ -from typing import TYPE_CHECKING, Optional, List -from datetime import date -from sqlalchemy import ( - Integer, String, Date, Numeric, Boolean, - PrimaryKeyConstraint, ForeignKeyConstraint -) -from sqlalchemy.orm import Mapped, mapped_column, relationship -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base - -if TYPE_CHECKING: - from api.v1.modules.a24.inv.inv_parts.models import InvPart - -class InvPartAphisEntities(Base, TenantScopedMixin, TimestampMixin): - """Módulo: entities""" - __tablename__ = "inv_aphis_entities" - __table_args__ = ( - PrimaryKeyConstraint("id", name="inv_aphis_entities_pkey"), - ForeignKeyConstraint(["aphis_general_id"], ["a24.inv_aphis_general.id"]), - {"schema": "a24", "extend_existing": True}, - ) - id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) - aphis_general_id: Mapped[int] = mapped_column(Integer, nullable=False) - - # --- CAMPOS ENTITIES --- - consignee_key: Mapped[Optional[str]] = mapped_column(String(50)) - broker_key: Mapped[Optional[str]] = mapped_column(String(50)) - lpco_auth_party_key: Mapped[Optional[str]] = mapped_column(String(50)) - grower_key: Mapped[Optional[str]] = mapped_column(String(50)) - - aphis_general: Mapped["InvPartAphisGeneral"] = relationship(back_populates="entities") diff --git a/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_general/__init__.py b/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_general/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_general/dto.py b/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_general/dto.py deleted file mode 100644 index ebae2ef6..00000000 --- a/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_general/dto.py +++ /dev/null @@ -1,64 +0,0 @@ -from pydantic import BaseModel, field_validator, ConfigDict -from typing import Optional, List, Any -from datetime import date - -from ..inv_aphis_characteristic.dto import InvPartAphisCharacteristicDTO -from ..inv_aphis_stype_pitems.dto import InvPartAphisStypePitemsDTO -from ..inv_aphis_lpcos.dto import InvPartAphisLpcosDTO -from ..inv_aphis_entities.dto import InvPartAphisEntitiesDTO -from ..inv_aphis_containers.dto import InvPartAphisContainersDTO -from ..inv_aphis_routing.dto import InvPartAphisRoutingDTO - -# NOTA: Importar primero los sub-dtos para resolver dependencias - -class InvPartAphisGeneralDTO(BaseModel): - id: Optional[int] = None - program_code: Optional[str] = None - processing_code: Optional[str] = None - aphis_type: Optional[str] = None - disclaimer: Optional[str] = None - electronic_image: Optional[str] = None - confidential: Optional[str] = None - global_product_id: Optional[str] = None - intended_use_code: Optional[str] = None - intended_use_description: Optional[str] = None - item_type: Optional[str] = None - product_code: Optional[str] = None - product_code_2: Optional[str] = None - product_code_3: Optional[str] = None - scientific_genus_name: Optional[str] = None - scientific_species_name: Optional[str] = None - scientific_sub_species_name: Optional[str] = None - common_name_specific: Optional[str] = None - common_name_general: Optional[str] = None - signed_doc: Optional[str] = None - signed_doc_date: Optional[date] = None - signed_doc_id: Optional[str] = None - invoice_number: Optional[str] = None - quantity_1: Optional[str] = None - quantity_2: Optional[str] = None - quantity_3: Optional[str] = None - inspection: Optional[str] = None - inspection_date: Optional[date] = None - inspection_loc_date: Optional[date] = None - inspection_location: Optional[str] = None - country_production: Optional[str] = None - country_source: Optional[str] = None - - # Relaciones - characteristics: List[InvPartAphisCharacteristicDTO] = [] - stype_pitems: List[InvPartAphisStypePitemsDTO] = [] - lpcos: List[InvPartAphisLpcosDTO] = [] - entities: List[InvPartAphisEntitiesDTO] = [] - containers: List[InvPartAphisContainersDTO] = [] - routing: List[InvPartAphisRoutingDTO] = [] - - model_config = ConfigDict(from_attributes=True) - - @field_validator('signed_doc_date', 'inspection_date', 'inspection_loc_date', mode='before') - @classmethod - def empty_str_to_none(cls, v: Any) -> Any: - if v == "": - return None - return v - diff --git a/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_general/models.py b/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_general/models.py deleted file mode 100644 index d7aba57e..00000000 --- a/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_general/models.py +++ /dev/null @@ -1,72 +0,0 @@ -from typing import TYPE_CHECKING, Optional, List -from datetime import date -from sqlalchemy import ( - Integer, String, Date, Numeric, Boolean, - PrimaryKeyConstraint, ForeignKeyConstraint -) -from sqlalchemy.orm import Mapped, mapped_column, relationship -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base - -if TYPE_CHECKING: - from api.v1.modules.a24.inv.inv_parts.models import InvPart - -class InvPartAphisGeneral(Base, TenantScopedMixin, TimestampMixin): - """ - Tabla principal de Aphis vinculada a la parte. - Pestaña: Información general de aphis - """ - __tablename__ = "inv_aphis_general" - __table_args__ = ( - PrimaryKeyConstraint("id", name="inv_aphis_general_pkey"), - ForeignKeyConstraint( - ["inv_part_id"], ["a24.inv_partes.id"], name="fk_aphis_general_inv_part" - ), - {"schema": "a24", "extend_existing": True}, - ) - - id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) - inv_part_id: Mapped[int] = mapped_column(Integer, nullable=False) - - # --- 31 CAMPOS INICIALES --- - program_code: Mapped[Optional[str]] = mapped_column(String(10)) - processing_code: Mapped[Optional[str]] = mapped_column(String(10)) - aphis_type: Mapped[Optional[str]] = mapped_column(String(10)) - disclaimer: Mapped[Optional[str]] = mapped_column(String(10)) - electronic_image: Mapped[Optional[str]] = mapped_column(String(50)) - confidential: Mapped[Optional[str]] = mapped_column(String(1)) - global_product_id: Mapped[Optional[str]] = mapped_column(String(100)) - intended_use_code: Mapped[Optional[str]] = mapped_column(String(10)) - intended_use_description: Mapped[Optional[str]] = mapped_column(String(200)) - item_type: Mapped[Optional[str]] = mapped_column(String(20)) - product_code: Mapped[Optional[str]] = mapped_column(String(20)) - product_code_2: Mapped[Optional[str]] = mapped_column(String(20)) - product_code_3: Mapped[Optional[str]] = mapped_column(String(20)) - scientific_genus_name: Mapped[Optional[str]] = mapped_column(String(100)) - scientific_species_name: Mapped[Optional[str]] = mapped_column(String(100)) - scientific_sub_species_name: Mapped[Optional[str]] = mapped_column(String(100)) - common_name_specific: Mapped[Optional[str]] = mapped_column(String(200)) - common_name_general: Mapped[Optional[str]] = mapped_column(String(200)) - signed_doc: Mapped[Optional[str]] = mapped_column(String(100)) - signed_doc_date: Mapped[Optional[date]] = mapped_column(Date) - signed_doc_id: Mapped[Optional[str]] = mapped_column(String(50)) - invoice_number: Mapped[Optional[str]] = mapped_column(String(50)) - quantity_1: Mapped[Optional[str]] = mapped_column(String(50)) - quantity_2: Mapped[Optional[str]] = mapped_column(String(50)) - quantity_3: Mapped[Optional[str]] = mapped_column(String(50)) - inspection: Mapped[Optional[str]] = mapped_column(String(200)) - inspection_date: Mapped[Optional[date]] = mapped_column(Date) - inspection_loc_date: Mapped[Optional[date]] = mapped_column(Date) - inspection_location: Mapped[Optional[str]] = mapped_column(String(200)) - country_production: Mapped[Optional[str]] = mapped_column(String(3)) - country_source: Mapped[Optional[str]] = mapped_column(String(3)) - - # Relaciones - inv_part: Mapped["InvPart"] = relationship("InvPart", back_populates="aphis_records") - - characteristics: Mapped[List["InvPartAphisCharacteristic"]] = relationship(back_populates="aphis_general", cascade="all, delete-orphan") - stype_pitems: Mapped[List["InvPartAphisStypePitems"]] = relationship(back_populates="aphis_general", cascade="all, delete-orphan") - lpcos: Mapped[List["InvPartAphisLpcos"]] = relationship(back_populates="aphis_general", cascade="all, delete-orphan") - entities: Mapped[List["InvPartAphisEntities"]] = relationship(back_populates="aphis_general", cascade="all, delete-orphan") - containers: Mapped[List["InvPartAphisContainers"]] = relationship(back_populates="aphis_general", cascade="all, delete-orphan") - routing: Mapped[List["InvPartAphisRouting"]] = relationship(back_populates="aphis_general", cascade="all, delete-orphan") diff --git a/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_lpcos/__init__.py b/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_lpcos/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_lpcos/dto.py b/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_lpcos/dto.py deleted file mode 100644 index 6d9db159..00000000 --- a/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_lpcos/dto.py +++ /dev/null @@ -1,27 +0,0 @@ -from pydantic import BaseModel, field_validator, ConfigDict -from typing import Optional, List, Any -from datetime import date - -class InvPartAphisLpcosDTO(BaseModel): - id: Optional[int] = None - aphis_general_id: Optional[int] = None - issuer: Optional[str] = None - issuer_loc_qua: Optional[str] = None - issuer_loc: Optional[str] = None - issuer_loc_desc: Optional[str] = None - uom: Optional[str] = None - txn_type: Optional[str] = None - type: Optional[str] = None - number: Optional[str] = None - date_qual: Optional[str] = None - date: Optional[date] = None - qty: Optional[str] = None - - model_config = ConfigDict(from_attributes=True) - - @field_validator('date', mode='before') - @classmethod - def empty_str_to_none(cls, v: Any) -> Any: - if v == "": - return None - return v diff --git a/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_lpcos/models.py b/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_lpcos/models.py deleted file mode 100644 index 7dddd2b4..00000000 --- a/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_lpcos/models.py +++ /dev/null @@ -1,38 +0,0 @@ -from typing import TYPE_CHECKING, Optional, List -from datetime import date -from sqlalchemy import ( - Integer, String, Date, Numeric, Boolean, - PrimaryKeyConstraint, ForeignKeyConstraint -) -from sqlalchemy.orm import Mapped, mapped_column, relationship -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base - -if TYPE_CHECKING: - from api.v1.modules.a24.inv.inv_parts.models import InvPart - -class InvPartAphisLpcos(Base, TenantScopedMixin, TimestampMixin): - """Módulo: lpcos""" - __tablename__ = "inv_aphis_lpcos" - __table_args__ = ( - PrimaryKeyConstraint("id", name="inv_aphis_lpcos_pkey"), - ForeignKeyConstraint(["aphis_general_id"], ["a24.inv_aphis_general.id"]), - {"schema": "a24", "extend_existing": True}, - ) - id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) - aphis_general_id: Mapped[int] = mapped_column(Integer, nullable=False) - - # --- CAMPOS LPCO --- - issuer: Mapped[Optional[str]] = mapped_column(String(100)) - issuer_loc_qua: Mapped[Optional[str]] = mapped_column(String(50)) - issuer_loc: Mapped[Optional[str]] = mapped_column(String(50)) - issuer_loc_desc: Mapped[Optional[str]] = mapped_column(String(200)) - uom: Mapped[Optional[str]] = mapped_column(String(20)) - txn_type: Mapped[Optional[str]] = mapped_column(String(50)) - type: Mapped[Optional[str]] = mapped_column(String(50)) - number: Mapped[Optional[str]] = mapped_column(String(50)) - date_qual: Mapped[Optional[str]] = mapped_column(String(50)) - date: Mapped[Optional[date]] = mapped_column(Date) - qty: Mapped[Optional[str]] = mapped_column(String(50)) - - aphis_general: Mapped["InvPartAphisGeneral"] = relationship(back_populates="lpcos") diff --git a/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_routing/__init__.py b/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_routing/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_routing/dto.py b/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_routing/dto.py deleted file mode 100644 index ff714cc2..00000000 --- a/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_routing/dto.py +++ /dev/null @@ -1,12 +0,0 @@ -from pydantic import BaseModel, ConfigDict -from typing import Optional, List -from datetime import date - -class InvPartAphisRoutingDTO(BaseModel): - id: Optional[int] = None - aphis_general_id: Optional[int] = None - type: Optional[str] = None - country: Optional[str] = None - name: Optional[str] = None - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_routing/models.py b/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_routing/models.py deleted file mode 100644 index 83ecaeb2..00000000 --- a/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_routing/models.py +++ /dev/null @@ -1,30 +0,0 @@ -from typing import TYPE_CHECKING, Optional, List -from datetime import date -from sqlalchemy import ( - Integer, String, Date, Numeric, Boolean, - PrimaryKeyConstraint, ForeignKeyConstraint -) -from sqlalchemy.orm import Mapped, mapped_column, relationship -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base - -if TYPE_CHECKING: - from api.v1.modules.a24.inv.inv_parts.models import InvPart - -class InvPartAphisRouting(Base, TenantScopedMixin, TimestampMixin): - """Módulo: routing""" - __tablename__ = "inv_aphis_routing" - __table_args__ = ( - PrimaryKeyConstraint("id", name="inv_aphis_routing_pkey"), - ForeignKeyConstraint(["aphis_general_id"], ["a24.inv_aphis_general.id"]), - {"schema": "a24", "extend_existing": True}, - ) - id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) - aphis_general_id: Mapped[int] = mapped_column(Integer, nullable=False) - - # --- CAMPOS ROUTING --- - type: Mapped[Optional[str]] = mapped_column(String(50)) - country: Mapped[Optional[str]] = mapped_column(String(50)) - name: Mapped[Optional[str]] = mapped_column(String(100)) - - aphis_general: Mapped["InvPartAphisGeneral"] = relationship(back_populates="routing") diff --git a/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_stype_pitems/__init__.py b/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_stype_pitems/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_stype_pitems/dto.py b/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_stype_pitems/dto.py deleted file mode 100644 index a7417444..00000000 --- a/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_stype_pitems/dto.py +++ /dev/null @@ -1,23 +0,0 @@ -from pydantic import BaseModel, field_validator, ConfigDict -from typing import Optional, List, Any -from datetime import date - -class InvPartAphisStypePitemsDTO(BaseModel): - id: Optional[int] = None - aphis_general_id: Optional[int] = None - source_type_code: Optional[str] = None - country_code: Optional[str] = None - geo_location: Optional[str] = None - processing_start: Optional[date] = None - processing_end: Optional[date] = None - processing_type: Optional[str] = None - processing_desc: Optional[str] = None - - model_config = ConfigDict(from_attributes=True) - - @field_validator('processing_start', 'processing_end', mode='before') - @classmethod - def empty_str_to_none(cls, v: Any) -> Any: - if v == "": - return None - return v diff --git a/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_stype_pitems/models.py b/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_stype_pitems/models.py deleted file mode 100644 index d8a0ff5b..00000000 --- a/backend/api/v1/modules/a24/inv/inv_aphis/inv_aphis_stype_pitems/models.py +++ /dev/null @@ -1,34 +0,0 @@ -from typing import TYPE_CHECKING, Optional, List -from datetime import date -from sqlalchemy import ( - Integer, String, Date, Numeric, Boolean, - PrimaryKeyConstraint, ForeignKeyConstraint -) -from sqlalchemy.orm import Mapped, mapped_column, relationship -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base - -if TYPE_CHECKING: - from api.v1.modules.a24.inv.inv_parts.models import InvPart - -class InvPartAphisStypePitems(Base, TenantScopedMixin, TimestampMixin): - """Módulo: stype_Pitems""" - __tablename__ = "inv_aphis_stype_pitems" - __table_args__ = ( - PrimaryKeyConstraint("id", name="inv_aphis_stype_pitems_pkey"), - ForeignKeyConstraint(["aphis_general_id"], ["a24.inv_aphis_general.id"]), - {"schema": "a24", "extend_existing": True}, - ) - id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) - aphis_general_id: Mapped[int] = mapped_column(Integer, nullable=False) - - # --- CAMPOS STYPE PITEMS --- - source_type_code: Mapped[Optional[str]] = mapped_column(String(50)) - country_code: Mapped[Optional[str]] = mapped_column(String(3)) - geo_location: Mapped[Optional[str]] = mapped_column(String(100)) - processing_start: Mapped[Optional[date]] = mapped_column(Date) - processing_end: Mapped[Optional[date]] = mapped_column(Date) - processing_type: Mapped[Optional[str]] = mapped_column(String(50)) - processing_desc: Mapped[Optional[str]] = mapped_column(String(200)) - - aphis_general: Mapped["InvPartAphisGeneral"] = relationship(back_populates="stype_pitems") diff --git a/backend/api/v1/modules/a24/inv/inv_aphis/models.py b/backend/api/v1/modules/a24/inv/inv_aphis/models.py deleted file mode 100644 index 26ca531b..00000000 --- a/backend/api/v1/modules/a24/inv/inv_aphis/models.py +++ /dev/null @@ -1,17 +0,0 @@ -from .inv_aphis_general.models import InvPartAphisGeneral -from .inv_aphis_characteristic.models import InvPartAphisCharacteristic -from .inv_aphis_stype_pitems.models import InvPartAphisStypePitems -from .inv_aphis_lpcos.models import InvPartAphisLpcos -from .inv_aphis_entities.models import InvPartAphisEntities -from .inv_aphis_containers.models import InvPartAphisContainers -from .inv_aphis_routing.models import InvPartAphisRouting - -__all__ = [ - "InvPartAphisGeneral", - "InvPartAphisCharacteristic", - "InvPartAphisStypePitems", - "InvPartAphisLpcos", - "InvPartAphisEntities", - "InvPartAphisContainers", - "InvPartAphisRouting" -] diff --git a/backend/api/v1/modules/a24/inv/inv_classes/models.py b/backend/api/v1/modules/a24/inv/inv_classes/models.py deleted file mode 100644 index 47fc679f..00000000 --- a/backend/api/v1/modules/a24/inv/inv_classes/models.py +++ /dev/null @@ -1,20 +0,0 @@ -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base -from sqlalchemy import ForeignKeyConstraint, Integer, PrimaryKeyConstraint, String -from sqlalchemy.orm import Mapped, mapped_column - - -class SClasses(Base, TenantScopedMixin, TimestampMixin): - __tablename__ = "inv_classes" # SClases - __table_args__ = ( - ForeignKeyConstraint( - ["class_id"], ["a76.classes.id"], name="fk_sclasses_classes" - ), - PrimaryKeyConstraint("id", name="sclases_pk"), - {"schema": "a24"}, - ) - id: Mapped[int] = mapped_column(Integer, primary_key=True) - class_id: Mapped[int] = mapped_column(Integer, nullable=False) # Id de clase - - stock_um: Mapped[str] = mapped_column(String(5)) # Unidad de medida para existencia - us_tariff_code: Mapped[str] = mapped_column(String(19)) # Fracción americana (USA) diff --git a/backend/api/v1/modules/a24/inv/inv_parts/models.py b/backend/api/v1/modules/a24/inv/inv_parts/models.py deleted file mode 100644 index 48bd6002..00000000 --- a/backend/api/v1/modules/a24/inv/inv_parts/models.py +++ /dev/null @@ -1,174 +0,0 @@ -""" -Modelo ORM para datos específicos de Inventario y Manufactura (S-Partes) - Anexo 24 -""" - -from typing import TYPE_CHECKING, Optional, List -from decimal import Decimal -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base -from sqlalchemy import ( - Integer, - Numeric, - PrimaryKeyConstraint, - String, - Boolean, - ForeignKeyConstraint -) -from sqlalchemy.dialects.postgresql import JSONB -from sqlalchemy.orm import Mapped, mapped_column, relationship - -if TYPE_CHECKING: - from api.v1.modules.a76.parts.models import Part - -# Importar los modelos Aphis para asegurar su registro en SQLAlchemy -import api.v1.modules.a24.inv.inv_aphis.models as aphis_models -class InvPart(Base, TenantScopedMixin, TimestampMixin): - """ - Tabla inv_partes: Extensión de Anexo 24 para Inventarios (SPartes). - """ - - __tablename__ = "inv_partes" - __table_args__ = ( - PrimaryKeyConstraint("id", name="inv_partes_pkey"), - ForeignKeyConstraint( - ["id"], ["a76.parts.id"], name="fk_inv_partes_master" - ), - {"schema": "a24", "extend_existing": True}, - ) - - # Relación 1:1 - El ID es el mismo de la tabla maestra - id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=False) - - # --- 1. ATRIBUTOS PRINCIPALES DE INVENTARIO --- - part_type: Mapped[Optional[str]] = mapped_column(String(10)) # TIPOPARTE - material_type: Mapped[Optional[str]] = mapped_column(String(10)) # TIPOMAT - - reference_number: Mapped[Optional[str]] = mapped_column(String(70)) # NUMPARTEREF - flex_reference_number: Mapped[Optional[str]] = mapped_column(String(120)) # NUMPARTEREFFLEX - - # Conversiones - equivalent_uom: Mapped[Optional[str]] = mapped_column(String(5)) # UNIMEDEQUIV - conversion_factor: Mapped[Optional[Decimal]] = mapped_column(Numeric(19, 8)) # FACTORCONV - - stock_uom: Mapped[Optional[str]] = mapped_column(String(5)) # UMEXISTENCIA - alternate_uom: Mapped[Optional[str]] = mapped_column(String(5)) # UNIMEDALTERNA - conversion_uom: Mapped[Optional[str]] = mapped_column(String(9)) # UMCONVERSION - - # Valor Agregado - added_value: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # VALORAGREGADO - added_value_type: Mapped[Optional[str]] = mapped_column(String(2)) # TIPOVA - - assigned_client: Mapped[Optional[str]] = mapped_column(String(50)) # CLIENTEASIGNADO - supplier_code: Mapped[Optional[str]] = mapped_column(String(8)) # PROVEEDOR - is_textile: Mapped[Optional[str]] = mapped_column(String(2)) # ESTEXTIL - - # --- 2. MANUFACTURA Y PELIGROSIDAD --- - bom_version: Mapped[Optional[int]] = mapped_column(Integer) # VERSIONBOM / VERSIONBILL - is_repair: Mapped[Optional[str]] = mapped_column(String(3)) # ESREPARACION - is_hazardous: Mapped[Optional[str]] = mapped_column(String(1)) # ESMATPELIGROSO - - emergency_number: Mapped[Optional[str]] = mapped_column(String(30)) # NUMEMERGENCIA - danger_class: Mapped[Optional[str]] = mapped_column(String(4)) # CLASEDEPELIGRO - packaging_group: Mapped[Optional[str]] = mapped_column(String(3)) # GRUPOEMBALAJE - - # Dimensiones - width: Mapped[Optional[str]] = mapped_column(String(50)) # ANCHURA - thickness: Mapped[Optional[str]] = mapped_column(String(50)) # ESPESOR - specification: Mapped[Optional[str]] = mapped_column(String(50)) # SPEC - - # --- 3. COSTOS DETALLADOS Y ADUANA US --- - total_value: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # VALORTOTAL - direct_labor: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # TRABAJODIREC - general_expenses: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # GASTOGRALES - total_expenses: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # TOTALGASTOS - - depreciation: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # DEPRECIACION - tooling: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # TOOLING - material_consumed: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # MATCONSUMED - profit: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # GANANCIA - - # Fracciones Internacionales - us_fraction_alt: Mapped[Optional[str]] = mapped_column(String(13)) # FRACEUA - ca_fraction: Mapped[Optional[str]] = mapped_column(String(13)) # FRACCANADA - ad_valorem_us: Mapped[Optional[Decimal]] = mapped_column(Numeric(5, 2)) # ADVALOREMAME - - # Nafta / USMCA - nafta_result: Mapped[Optional[str]] = mapped_column(String(19)) # RESULTADOCALCULONAFTA - nafta_percentage: Mapped[Optional[Decimal]] = mapped_column(Numeric(5, 2)) # PORCENTAJECALCULONAFTA - - # Impuestos Específicos (Derechos de Trámite Admon) - dta: Mapped[Optional[str]] = mapped_column(String(19)) # DTA - dtb: Mapped[Optional[str]] = mapped_column(String(19)) # DTB - dtg: Mapped[Optional[str]] = mapped_column(String(19)) # DTG - - # --- CAMPOS ADICIONALES FRONTEND --- - substitute_part: Mapped[Optional[str]] = mapped_column(String(70)) - complementary_part: Mapped[Optional[str]] = mapped_column(String(70)) - preference_part: Mapped[Optional[str]] = mapped_column(String(70)) - use_alternate_quantity: Mapped[Optional[bool]] = mapped_column(Boolean, default=False) - un_number: Mapped[Optional[str]] = mapped_column(String(30)) - shipping_name: Mapped[Optional[str]] = mapped_column(String(200)) - hazard_notes: Mapped[Optional[str]] = mapped_column(String(500)) - repair_unit_cost: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) - repair_added_value: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) - fraction_9801: Mapped[Optional[str]] = mapped_column(String(10)) - immex_type: Mapped[Optional[str]] = mapped_column(String(10)) - disable_movements: Mapped[Optional[bool]] = mapped_column(Boolean, default=False) - pga_program_code: Mapped[Optional[str]] = mapped_column(String(10)) - usmca_fraction: Mapped[Optional[str]] = mapped_column(String(10)) - scrap_part_number: Mapped[Optional[str]] = mapped_column(String(70)) - waste_part_number: Mapped[Optional[str]] = mapped_column(String(70)) - scrap_description_en: Mapped[Optional[str]] = mapped_column(String(500)) - scrap_description_es: Mapped[Optional[str]] = mapped_column(String(500)) - scrap_export_fraction: Mapped[Optional[str]] = mapped_column(String(10)) - scrap_us_fraction: Mapped[Optional[str]] = mapped_column(String(10)) - equivalent_uom_2: Mapped[Optional[str]] = mapped_column(String(5)) - conversion_factor_2: Mapped[Optional[Decimal]] = mapped_column(Numeric(19, 8)) - has_auxiliary: Mapped[Optional[bool]] = mapped_column(Boolean, default=False) - auxiliary_uom: Mapped[Optional[str]] = mapped_column(String(5)) - auxiliary_conversion: Mapped[Optional[Decimal]] = mapped_column(Numeric(19, 8)) - auxiliary_unit_cost: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) - mex_packing: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) - sales_order: Mapped[Optional[str]] = mapped_column(String(50)) - use_rule_8: Mapped[Optional[bool]] = mapped_column(Boolean, default=False) - sector: Mapped[Optional[str]] = mapped_column(String(150)) - origin_country: Mapped[Optional[str]] = mapped_column(String(3), default='MEX') - fraction_type: Mapped[Optional[str]] = mapped_column(String(10)) - - # --- NUEVOS CAMPOS EXTENSION --- - agency_code_definition: Mapped[Optional[str]] = mapped_column(String(50)) # Fila 7 - carta_porte: Mapped[Optional[str]] = mapped_column(String(100)) # Fila 10 - - client_part_names: Mapped[Optional[list]] = mapped_column(JSONB, nullable=True, default=[]) # Fila 5 - part_identifiers: Mapped[Optional[list]] = mapped_column(JSONB, nullable=True, default=[]) # Fila 9 - substitute_parts: Mapped[Optional[list]] = mapped_column(JSONB, nullable=True, default=[]) # Pestaña Continuación - aphis_data: Mapped[Optional[dict]] = mapped_column(JSONB, nullable=True, default={}) # Sección Aphis en Continuación - - # JSONB para almacenar clientes con restricción de no descarga - non_discharge_clients: Mapped[Optional[list]] = mapped_column(JSONB, nullable=True, default=[]) - - # --- RELACIÓN --- - aphis_records: Mapped[List["InvPartAphisGeneral"]] = relationship( - "InvPartAphisGeneral", - back_populates="inv_part", - cascade="all, delete-orphan" - ) - # Usamos string "Part" para evitar problemas de carga - master_info: Mapped["Part"] = relationship("Part", back_populates="inv_data") - - @property - def bom_items(self): - """Exponer los BOM items de la parte maestra para Pydantic""" - if self.master_info: - return self.master_info.bom_items - return [] - - @property - def countries(self): - """Exponer los países de la parte maestra para Pydantic""" - if self.master_info: - return self.master_info.inv_countries - return [] - - def __repr__(self) -> str: - return f"" \ No newline at end of file diff --git a/backend/api/v1/modules/a24/inv/part_countries/__init__.py b/backend/api/v1/modules/a24/inv/part_countries/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/backend/api/v1/modules/a24/inv/part_countries/dto.py b/backend/api/v1/modules/a24/inv/part_countries/dto.py deleted file mode 100644 index a79938ef..00000000 --- a/backend/api/v1/modules/a24/inv/part_countries/dto.py +++ /dev/null @@ -1,81 +0,0 @@ -""" -DTOs (Data Transfer Objects) para relación entre partes y países - Anexo 24 -""" - -from typing import Optional -from datetime import datetime -from decimal import Decimal - -from pydantic import BaseModel, Field - - -class PartCountryCreateDTO(BaseModel): - """DTO para crear una relación entre parte y país""" - - part_id: int = Field(..., description="Part ID") - country_code: str = Field(..., max_length=3, description="Country code (ISO 3166-1 alpha-3)") - fraction: Optional[str] = Field(None, max_length=20, description="Tariff fraction") - preference: str = Field(default="GENERAL", description="Trade preference: GENERAL, PROSEC, ALADI, TLCS") - has_certificate: bool = Field(default=False, description="Has certificate of origin") - certificate_number: Optional[str] = Field(None, max_length=50, description="Certificate of origin number") - end_date: Optional[datetime] = Field(None, description="Certificate expiration date") - previous_fractions_7m: bool = Field(default=False, description="Has fractions older than 7 months") - omission_import: bool = Field(default=False, description="Import omission flag") - omission_export: bool = Field(default=False, description="Export omission flag") - import_percentage: Optional[Decimal] = Field(None, description="Import tariff percentage") - export_percentage: Optional[Decimal] = Field(None, description="Export tariff percentage") - sector: Optional[str] = Field(None, max_length=10, description="Sector reference") - - class Config: - from_attributes = True - - -class PartCountryUpdateDTO(BaseModel): - """DTO para actualizar una relación entre parte y país""" - - country_code: Optional[str] = Field(None, max_length=3, description="Country code") - fraction: Optional[str] = Field(None, max_length=20, description="Tariff fraction") - preference: Optional[str] = Field(None, description="Trade preference") - has_certificate: Optional[bool] = Field(None, description="Has certificate of origin") - certificate_number: Optional[str] = Field(None, max_length=50, description="Certificate number") - end_date: Optional[datetime] = Field(None, description="Certificate expiration date") - previous_fractions_7m: Optional[bool] = Field(None, description="Previous fractions flag") - omission_import: Optional[bool] = Field(None, description="Import omission flag") - omission_export: Optional[bool] = Field(None, description="Export omission flag") - import_percentage: Optional[Decimal] = Field(None, description="Import percentage") - export_percentage: Optional[Decimal] = Field(None, description="Export percentage") - sector: Optional[str] = Field(None, max_length=10, description="Sector reference") - - class Config: - from_attributes = True - - -class PartCountryResponseDTO(BaseModel): - """DTO para responder con datos de relación entre parte y país""" - - id: int - part_id: int - country_code: str - fraction: Optional[str] = None - preference: str - has_certificate: bool - certificate_number: Optional[str] = None - end_date: Optional[datetime] = None - previous_fractions_7m: bool - omission_import: bool - omission_export: bool - import_percentage: Optional[Decimal] = None - export_percentage: Optional[Decimal] = None - sector: Optional[str] = None - - class Config: - from_attributes = True - - -class PartCountriesBulkResponseDTO(BaseModel): - """DTO para responder con lista de relaciones entre partes y países""" - - data: list[PartCountryResponseDTO] - total: int - skip: int - limit: int diff --git a/backend/api/v1/modules/a24/inv/part_countries/models.py b/backend/api/v1/modules/a24/inv/part_countries/models.py deleted file mode 100644 index 4e38d566..00000000 --- a/backend/api/v1/modules/a24/inv/part_countries/models.py +++ /dev/null @@ -1,73 +0,0 @@ -""" -Modelo ORM para la relación entre partes y países - Anexo 24 -""" - -from datetime import datetime -from typing import TYPE_CHECKING, Optional -from decimal import Decimal -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base -from sqlalchemy import ( - Integer, - PrimaryKeyConstraint, - String, - Boolean, - ForeignKeyConstraint, - DateTime, - Numeric, -) -from sqlalchemy.orm import Mapped, mapped_column, relationship - -if TYPE_CHECKING: - from api.v1.modules.a76.parts.models import Part - - -class PartCountry(Base, TenantScopedMixin, TimestampMixin): - """ - Tabla inv_parte_paises: Relación entre partes y países. - Almacena información de países de origen, preferencias, certificados y omisiones. - """ - __tablename__ = "inv_parte_paises" - __table_args__ = ( - PrimaryKeyConstraint("id", name="inv_parte_paises_pkey"), - ForeignKeyConstraint( - ["part_id"], ["a76.parts.id"], name="fk_inv_parte_paises_part" - ), - {"schema": "a24", "extend_existing": True}, - ) - - id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) - part_id: Mapped[int] = mapped_column(Integer, nullable=False) - country_code: Mapped[str] = mapped_column(String(3), nullable=False) - - # Información de fracciones - fraction: Mapped[Optional[str]] = mapped_column(String(20)) - is_origin: Mapped[bool] = mapped_column(Boolean, default=False) - - # Preferencia comercial - preference: Mapped[str] = mapped_column(String(15), default="GENERAL") # GENERAL, PROSEC, ALADI, TLCS - - # Certificado de origen - has_certificate: Mapped[bool] = mapped_column(Boolean, default=False) - certificate_number: Mapped[Optional[str]] = mapped_column(String(50)) - end_date: Mapped[Optional[datetime]] = mapped_column(DateTime) - - # Fracciones anteriores a 7 meses - previous_fractions_7m: Mapped[bool] = mapped_column(Boolean, default=False) - - # Omisiones (importación/exportación) - omission_import: Mapped[bool] = mapped_column(Boolean, default=False) - omission_export: Mapped[bool] = mapped_column(Boolean, default=False) - - # Porcentajes - import_percentage: Mapped[Optional[Decimal]] = mapped_column(Numeric(5, 2)) - export_percentage: Mapped[Optional[Decimal]] = mapped_column(Numeric(5, 2)) - - # Sector (para referencia) - sector: Mapped[Optional[str]] = mapped_column(String(10)) - - # Relación - part: Mapped["Part"] = relationship("Part", back_populates="inv_countries") - - def __repr__(self) -> str: - return f"" diff --git a/backend/api/v1/modules/a24/inv/part_countries/routes.py b/backend/api/v1/modules/a24/inv/part_countries/routes.py deleted file mode 100644 index 6256aa92..00000000 --- a/backend/api/v1/modules/a24/inv/part_countries/routes.py +++ /dev/null @@ -1,162 +0,0 @@ -""" -Rutas para gestión de relaciones entre partes y países - Anexo 24 -""" - -from typing import List - -from fastapi import APIRouter, Depends, HTTPException, Query, status -from sqlalchemy.orm import Session - -from core.database import get_core_db -from .dto import ( - PartCountryCreateDTO, - PartCountryResponseDTO, - PartCountryUpdateDTO, - PartCountriesBulkResponseDTO, -) -from .models import PartCountry -from .service import PartCountryService - -router = APIRouter(prefix="/part-countries", tags=["part-countries"]) - - -@router.get( - "", - response_model=dict, - summary="Get all part-country relationships", -) -async def get_all_part_countries( - skip: int = Query(0, ge=0), - limit: int = Query(50, ge=1, le=100), - part_id: int = Query(None), - country_code: str = Query(None), - preference: str = Query(None), - db: Session = Depends(get_core_db), -): - """Get all part-country relationships with optional filtering and pagination""" - filters = {} - if part_id: - filters["part_id"] = part_id - if country_code: - filters["country_code"] = country_code - if preference: - filters["preference"] = preference - - part_countries, total = PartCountryService.get_all(db, skip, limit, filters) - - return { - "data": [PartCountryResponseDTO.model_validate(pc) for pc in part_countries], - "total": total, - "skip": skip, - "limit": limit, - } - - -@router.get( - "/{part_country_id}", - response_model=PartCountryResponseDTO, - summary="Get part-country relationship by ID", -) -async def get_part_country( - part_country_id: int, - db: Session = Depends(get_core_db), -): - """Get a part-country relationship by its ID""" - part_country = PartCountryService.get_by_id(db, part_country_id) - if not part_country: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="Part-country relationship not found", - ) - return PartCountryResponseDTO.model_validate(part_country) - - -@router.get( - "/part/{part_id}", - response_model=dict, - summary="Get all countries for a part", -) -async def get_part_countries( - part_id: int, - db: Session = Depends(get_core_db), -): - """Get all countries associated with a specific part""" - part_countries = PartCountryService.get_by_part_id(db, part_id) - - return { - "data": [PartCountryResponseDTO.model_validate(pc) for pc in part_countries], - "total": len(part_countries), - } - - -@router.post( - "", - response_model=PartCountryResponseDTO, - status_code=status.HTTP_201_CREATED, - summary="Create part-country relationship", -) -async def create_part_country( - part_country_data: PartCountryCreateDTO, - db: Session = Depends(get_core_db), -): - """Create a new part-country relationship""" - part_country = PartCountryService.create(db, part_country_data) - return PartCountryResponseDTO.model_validate(part_country) - - -@router.post( - "/part/{part_id}/bulk", - response_model=dict, - status_code=status.HTTP_201_CREATED, - summary="Bulk create/update part-country relationships", -) -async def bulk_create_part_countries( - part_id: int, - countries_data: List[PartCountryCreateDTO], - db: Session = Depends(get_core_db), -): - """Create or replace all part-country relationships for a part""" - part_countries = PartCountryService.bulk_create(db, part_id, countries_data) - - return { - "data": [PartCountryResponseDTO.model_validate(pc) for pc in part_countries], - "total": len(part_countries), - } - - -@router.put( - "/{part_country_id}", - response_model=PartCountryResponseDTO, - summary="Update part-country relationship", -) -async def update_part_country( - part_country_id: int, - part_country_data: PartCountryUpdateDTO, - db: Session = Depends(get_core_db), -): - """Update a part-country relationship""" - part_country = PartCountryService.update(db, part_country_id, part_country_data) - if not part_country: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="Part-country relationship not found", - ) - return PartCountryResponseDTO.model_validate(part_country) - - -@router.delete( - "/{part_country_id}", - status_code=status.HTTP_204_NO_CONTENT, - summary="Delete part-country relationship", -) -async def delete_part_country( - part_country_id: int, - db: Session = Depends(get_core_db), -): - """Delete a part-country relationship""" - success = PartCountryService.delete(db, part_country_id) - if not success: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="Part-country relationship not found", - ) diff --git a/backend/api/v1/modules/a24/inv/part_countries/service.py b/backend/api/v1/modules/a24/inv/part_countries/service.py deleted file mode 100644 index fb8bb1d7..00000000 --- a/backend/api/v1/modules/a24/inv/part_countries/service.py +++ /dev/null @@ -1,186 +0,0 @@ -""" -Capa de servicio para lógica de negocio de relación entre partes y países -""" - -import logging -from typing import Any, Dict, List, Optional, Tuple - -from fastapi import HTTPException -from sqlalchemy.exc import IntegrityError -from sqlalchemy.orm import Session - -from .dto import PartCountryCreateDTO, PartCountryResponseDTO, PartCountryUpdateDTO -from .models import PartCountry - -logger = logging.getLogger(__name__) - - -class PartCountryService: - """Servicio para gestión de relaciones entre partes y países""" - - @staticmethod - def get_all( - db: Session, - skip: int = 0, - limit: int = 50, - filters: Optional[Dict[str, Any]] = None, - ) -> Tuple[List[PartCountry], int]: - """Get all part-country relationships with pagination""" - query = db.query(PartCountry) - - if filters: - if filters.get("part_id"): - query = query.filter(PartCountry.part_id == filters["part_id"]) - if filters.get("country_code"): - query = query.filter( - PartCountry.country_code.ilike(f"%{filters['country_code']}%") - ) - if filters.get("preference"): - query = query.filter(PartCountry.preference == filters["preference"]) - - total = query.count() - part_countries = query.offset(skip).limit(limit).all() - - return part_countries, total - - @staticmethod - def get_by_id(db: Session, part_country_id: int) -> Optional[PartCountry]: - """Get part-country relationship by ID""" - return db.query(PartCountry).filter(PartCountry.id == part_country_id).first() - - @staticmethod - def get_by_part_id(db: Session, part_id: int) -> List[PartCountry]: - """Get all countries for a specific part""" - return db.query(PartCountry).filter(PartCountry.part_id == part_id).all() - - @staticmethod - def get_by_part_and_country(db: Session, part_id: int, country_code: str) -> Optional[PartCountry]: - """Get specific part-country relationship""" - return db.query(PartCountry).filter( - PartCountry.part_id == part_id, - PartCountry.country_code == country_code - ).first() - - @staticmethod - def create(db: Session, part_country_data: PartCountryCreateDTO) -> PartCountry: - """Create a new part-country relationship""" - try: - db_part_country = PartCountry( - **part_country_data.model_dump(exclude_unset=True) - ) - - db.add(db_part_country) - db.commit() - db.refresh(db_part_country) - - return db_part_country - - except IntegrityError as e: - db.rollback() - logger.error(f"IntegrityError creating part-country relationship: {str(e)}") - raise HTTPException( - status_code=400, - detail="Relationship already exists or invalid foreign key", - ) - except Exception as e: - db.rollback() - logger.error(f"Error creating part-country relationship: {str(e)}") - raise HTTPException( - status_code=500, - detail=f"Error creating relationship: {str(e)}", - ) - - @staticmethod - def update( - db: Session, part_country_id: int, part_country_data: PartCountryUpdateDTO - ) -> Optional[PartCountry]: - """Update part-country relationship""" - try: - db_part_country = db.query(PartCountry).filter( - PartCountry.id == part_country_id - ).first() - - if not db_part_country: - return None - - update_data = part_country_data.model_dump(exclude_unset=True) - for field, value in update_data.items(): - setattr(db_part_country, field, value) - - db.add(db_part_country) - db.commit() - db.refresh(db_part_country) - - return db_part_country - - except IntegrityError as e: - db.rollback() - logger.error(f"IntegrityError updating part-country relationship: {str(e)}") - raise HTTPException( - status_code=400, - detail="Cannot update: constraint violation", - ) - except Exception as e: - db.rollback() - logger.error(f"Error updating part-country relationship: {str(e)}") - raise HTTPException( - status_code=500, - detail=f"Error updating relationship: {str(e)}", - ) - - @staticmethod - def delete(db: Session, part_country_id: int) -> bool: - """Delete part-country relationship""" - try: - db_part_country = db.query(PartCountry).filter( - PartCountry.id == part_country_id - ).first() - - if not db_part_country: - return False - - db.delete(db_part_country) - db.commit() - - return True - - except Exception as e: - db.rollback() - logger.error(f"Error deleting part-country relationship: {str(e)}") - raise HTTPException( - status_code=500, - detail=f"Error deleting relationship: {str(e)}", - ) - - @staticmethod - def bulk_create(db: Session, part_id: int, countries_data: List[PartCountryCreateDTO]) -> List[PartCountry]: - """Create multiple part-country relationships for a part""" - try: - # Delete existing relationships for this part - db.query(PartCountry).filter(PartCountry.part_id == part_id).delete() - db.commit() - - # Create new relationships - db_part_countries = [] - for country_data in countries_data: - country_data.part_id = part_id - db_part_country = PartCountry( - **country_data.model_dump(exclude_unset=True) - ) - db_part_countries.append(db_part_country) - - db.add_all(db_part_countries) - db.commit() - - for pc in db_part_countries: - db.refresh(pc) - - return db_part_countries - - except Exception as e: - db.rollback() - logger.error(f"Error bulk creating part-country relationships: {str(e)}") - raise HTTPException( - status_code=500, - detail=f"Error creating relationships: {str(e)}", - ) diff --git a/backend/api/v1/modules/a24/router.py b/backend/api/v1/modules/a24/router.py deleted file mode 100644 index dca3877f..00000000 --- a/backend/api/v1/modules/a24/router.py +++ /dev/null @@ -1,29 +0,0 @@ -""" -Router principal del módulo A24 (SCAF - Sistema de Control de Activo Fijo) -""" - -from fastapi import APIRouter - -# Importar routers de submódulos -from .fa.fa_classes.routes import router as fa_classes_router -from .fa.fa_item_lines.routes import router as fa_item_lines_router -from .inv.part_countries.routes import router as part_countries_router -from .inv.inv_aphis.inv_aphis_catalog.router import router as aphis_catalog_router - -# Importar modelo para que SQLAlchemy cree la tabla automáticamente -import api.v1.modules.a24.inv.inv_aphis.inv_aphis_catalog.models # noqa: F401 - - -# Router principal de A24 -router = APIRouter() - -# Registrar routers de FA (Fixed Assets) -router.include_router(fa_classes_router, prefix="/a24", tags=["a24 / fa / classes"]) -router.include_router( - fa_item_lines_router, prefix="/a24", tags=["a24 / fa / item-lines"] -) - -# Registrar routers de INV (Inventory) -router.include_router(part_countries_router, prefix="/a24", tags=["a24 / inv / part-countries"]) -router.include_router(aphis_catalog_router, prefix="/a24", tags=["a24 / inv / aphis-catalog"]) - diff --git a/backend/api/v1/modules/a76/__init__.py b/backend/api/v1/modules/a76/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/backend/api/v1/modules/a76/app_settings/__init__.py b/backend/api/v1/modules/a76/app_settings/__init__.py deleted file mode 100644 index 2af0782c..00000000 --- a/backend/api/v1/modules/a76/app_settings/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# Module initialization for app_settings diff --git a/backend/api/v1/modules/a76/app_settings/models.py b/backend/api/v1/modules/a76/app_settings/models.py deleted file mode 100644 index 6e0b0e7a..00000000 --- a/backend/api/v1/modules/a76/app_settings/models.py +++ /dev/null @@ -1,33 +0,0 @@ -from typing import Optional -from sqlalchemy import Integer, ForeignKey, UniqueConstraint -from sqlalchemy.dialects.postgresql import JSONB -from sqlalchemy.orm import Mapped, mapped_column -from core.database import Base -from api.v1.common.base_models import BaseTimestampMixin - -class AppSetting(Base, BaseTimestampMixin): - """ - Unified configuration table for Anexo 76. - Replaces 14 legacy tables using a hierarchical JSONB override system. - """ - __tablename__ = "app_settings" - __table_args__ = ( - UniqueConstraint("tenant_id", "company_id", name="uq_app_settings_tenant_company"), - {"schema": "a76"}, - ) - - id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) - - # Hierarchy levels (Nullable to allow Global/Tenant/Company scoping) - tenant_id: Mapped[Optional[int]] = mapped_column( - Integer, ForeignKey("core.tenants.id"), nullable=True, index=True - ) - company_id: Mapped[Optional[int]] = mapped_column( - Integer, ForeignKey("a76.company.id"), nullable=True, index=True - ) - - # The actual configuration payload - settings: Mapped[dict] = mapped_column(JSONB, nullable=False, default={}) - - def __repr__(self): - return f"" diff --git a/backend/api/v1/modules/a76/app_settings/routes.py b/backend/api/v1/modules/a76/app_settings/routes.py deleted file mode 100644 index 77ee7cab..00000000 --- a/backend/api/v1/modules/a76/app_settings/routes.py +++ /dev/null @@ -1,73 +0,0 @@ -from fastapi import APIRouter, Depends, Query, HTTPException -from sqlalchemy.orm import Session -from typing import Optional, Dict, Any -from core.database import get_core_db -from .service import AppSettingsService -from .schemas import AppSettingRequest, AppSettingResponse -from core.security import get_current_user, validate_access_to_resource - -router = APIRouter(prefix="/a76/app-settings", tags=["a76 / app_settings"]) - -import logging -import traceback - -logger = logging.getLogger(__name__) - -@router.get("/resolved") -def get_resolved_settings( - tenant_id: int = Query(...), - company_id: int = Query(...), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user) -): - """ - Returns the final merged configuration for a company. - Merges Global -> Tenant -> Company levels. - """ - try: - # Validar permisos - validate_access_to_resource(db, company_id, current_user, ["settings_general.view"]) - - return AppSettingsService.get_resolved_settings(db, tenant_id, company_id) - except HTTPException: - raise - except Exception as e: - logger.error(f"RESOLVE ERROR: {str(e)}", exc_info=True) - raise HTTPException(status_code=500, detail=str(e)) - -@router.post("/upsert") -def upsert_settings( - payload: AppSettingRequest, - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user) -): - """ - Creates or updates an override for a specific level (Global, Tenant, or Company). - """ - try: - # Validar permisos - validate_access_to_resource(db, payload.company_id, current_user, ["settings_general.edit"]) - - data = payload.settings.model_dump(exclude_unset=True) - return AppSettingsService.upsert_settings( - db, - payload.tenant_id, - payload.company_id, - data - ) - except HTTPException: - raise - except Exception as e: - logger.error(f"UPSERT ERROR: {str(e)}", exc_info=True) - raise HTTPException(status_code=500, detail=str(e)) - -@router.put("/upsert") -def update_settings( - payload: AppSettingRequest, - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user) -): - """ - Alias for upsert_settings. - """ - return upsert_settings(payload, db, current_user) diff --git a/backend/api/v1/modules/a76/app_settings/schemas.py b/backend/api/v1/modules/a76/app_settings/schemas.py deleted file mode 100644 index 4bd2118b..00000000 --- a/backend/api/v1/modules/a76/app_settings/schemas.py +++ /dev/null @@ -1,1042 +0,0 @@ -from typing import Optional, Any, Dict -from pydantic import BaseModel, Field -from decimal import Decimal - -# --- Legacy Table Domains (Tarea 1) --- - -class SSisGenSettings(BaseModel): - """ - Legacy Table: SSisGen - General system parameters migrated from Clarion/WinDev. - """ - consecutivo: Optional[int] = None - dta: Optional[int] = None - dtaexpo: Optional[int] = None - subempresa: Optional[str] = None - patharch: Optional[str] = None - patharchtransmision: Optional[str] = None - pathtransexpo: Optional[str] = None - pathrespuesta: Optional[str] = None - patharchped: Optional[str] = None - patharchpedconsm: Optional[str] = None - pathgenimpotemp: Optional[str] = None - pathgenexpo: Optional[str] = None - actseguridad: Optional[int] = None - controldes: Optional[int] = None - diadesactual: Optional[int] = None - diavencimiento: Optional[int] = None - mensajevenc: Optional[int] = None - fechades: Optional[int] = None - factoriva: Optional[Decimal] = None - validasifra: Optional[int] = None - decimalespeso: Optional[int] = None - decimalescant: Optional[int] = None - decimalesvalor: Optional[int] = None - calvalbasetcped: Optional[int] = None - calvalbasetcpedexpo: Optional[int] = None - filtrocantidad: Optional[Decimal] = None - muestraarchcodbarras: Optional[int] = None - datoshist: Optional[int] = None - tipovenro: Optional[str] = None - cantvenro: Optional[int] = None - costoplanta: Optional[Decimal] = None - firmapacking: Optional[int] = None - advertenciatm: Optional[int] = None - tomarsaldosvenc: Optional[int] = None - costoimpofijo: Optional[int] = None - valparteexiste: Optional[int] = None - valmanifusado: Optional[int] = None - temporalfechapago: Optional[int] = None - asignadiasantdesc: Optional[int] = None - diasantdesc: Optional[int] = None - deshabilitardescparte: Optional[int] = None - deshabilitardescparteing: Optional[int] = None - asignafracameparte: Optional[int] = None - validadecencant: Optional[int] = None - usartranspamedocame: Optional[int] = None - mostraradvertenciaro: Optional[int] = None - escondaamexpacking: Optional[int] = None - calcdutypacking: Optional[int] = None - parammultiples: Optional[int] = None - fraccnivelpais: Optional[int] = None - covefechaemision: Optional[int] = None - valordllstcfacturaexpo: Optional[int] = None - interfaceaaconsolidada: Optional[int] = None - interfaceaatcfpff: Optional[str] = None - incluirobscoveobsimpo: Optional[int] = None - agregarincreimpo: Optional[int] = None - componentebom: Optional[int] = None - limitesubensamble: Optional[int] = None - actpdfreportes: Optional[int] = None - patharchpdfimpo: Optional[str] = None - patharchpdfexpo: Optional[str] = None - noimprimircons: Optional[int] = None - mostraradvertenciarovalor: Optional[int] = None - partesypedimentosporcliente: Optional[str] = None - mensajesvurfc: Optional[int] = None - mostrarpackinglistingles: Optional[int] = None - omitirempaqueencodigobarras: Optional[int] = None - restringepaisimpo: Optional[int] = None - bloqueoaldesactivarnumerodeparte: Optional[int] = None - restringpaisexpo: Optional[int] = None - geninformeanexo31: Optional[str] = None - utilizarfechapagopeddeundiaanterior: Optional[int] = None - utilizarequivalenciasdeumpornumerodeparte: Optional[int] = None - utilizartitulosalternativosimpresionfactura: Optional[int] = None - usarfactorconversionpornumerodeparte: Optional[int] = None - usarvude128o256: Optional[int] = None - usartcdelafechapagopedimpoendescarga: Optional[int] = None - solicitarcontrasenaadministrador: Optional[int] = None - agregarnumeroembarque: Optional[int] = None - utilizarumdeexistenciaentransmisionvu: Optional[int] = None - utilizarcodigodebrokerdeclienteenmainx30: Optional[int] = None - hojacalculosepararincrementablesanexo3: Optional[int] = None - hojacalculodesglosefacturaanexo3: Optional[int] = None - usarvaloragregadoenfacturaamericana: Optional[int] = None - ocultarinformacionfraccion: Optional[int] = None - resaltarsaldostempconcolor: Optional[int] = None - valoragregadoenfacturamexicana: Optional[int] = None - validarsectorprosecr8: Optional[int] = None - agregarsubtotalinterfazaa: Optional[int] = None - utilizarnombregenericomainx30: Optional[int] = None - utilizartcrespectoatipoped: Optional[int] = None - tomardecimalescompletos: Optional[int] = None - incluirremesaeninterfazaawinsaai: Optional[int] = None - cambiarpesosporcostounitario: Optional[int] = None - utilizarsolopartesnaftaenco: Optional[int] = None - bloqueodeediciondefacturas: Optional[int] = None - reasignafraccionclase: Optional[int] = None - calcularcostounitarioenbaseavalortotal: Optional[int] = None - parametroauxiliar: Optional[int] = None - usarcontroldefechasdeversion: Optional[int] = None - desactivaciondemodulos: Optional[int] = None - imprimirfacturaalterna: Optional[int] = None - activarexpedienteelectronico: Optional[int] = None - activarcatalogofraccionesamericanassifra: Optional[int] = None - informacionamericanasubtotal: Optional[int] = None - mostrarprogramaimmexprosec: Optional[int] = None - costounitarioporempaquefac: Optional[int] = None - transmitirfacalterna: Optional[int] = None - muestra_copias_codbarras: Optional[int] = None - activar_revision_fracciones: Optional[int] = None - usarcoveenarchsaaim3: Optional[int] = None - downloadftp: Optional[str] = None - minsdownlftp: Optional[int] = None - downloadftppath: Optional[str] = None - descargarftpolocal: Optional[str] = None - serverftp: Optional[str] = None - userftp: Optional[str] = None - passwordftp: Optional[str] = None - directorioftp: Optional[str] = None - pathlocalparadescde: Optional[str] = None - agregarremplazarautomatico: Optional[str] = None - activarprocesovequipment: Optional[int] = None - activarprocesodesperdiciojdedwards: Optional[int] = None - usarfechaemisionfactura: Optional[int] = None - campo18valsaaim3: Optional[int] = None - actvaloragre: Optional[int] = None - valoragregadogen: Optional[Decimal] = None - validarseries: Optional[int] = None - emailas: Optional[int] = None - afostrofe: Optional[int] = None - codigobarrasesp: Optional[int] = None - asignainfoparte: Optional[int] = None - pathnaftaccs: Optional[str] = None - - - -class SSisGen2Settings(BaseModel): - """ - Legacy Table: SSisGen2 - Extended system parameters. - """ - consecutivo: Optional[int] = None - actvaloragre: Optional[int] = None - valoragregadogen: Optional[Decimal] = None - - -class SSisGen3Settings(BaseModel): - """ - Legacy Table: SSisGen3 - Additional name-value parameters. - """ - parametro: Optional[str] = None - valorparametro: Optional[int] = None - - -class SSisMexSettings(BaseModel): - """Legacy Table: SSisMex (Mexican Purchases)""" - consecutivo: Optional[int] = None - prefijocm: Optional[str] = None - consecutivocm: Optional[str] = None - porparteclasemex: Optional[str] = None - porparteclaseame: Optional[str] = None - proveedor: Optional[str] = None - vendidoconsignado: Optional[str] = None - vendidoa: Optional[str] = None - enviadotransferido: Optional[str] = None - enviadoa: Optional[str] = None - flete: Optional[Decimal] = None - paisorigenmex: Optional[int] = None - numpartemex: Optional[int] = None - firmafmex: Optional[str] = None - fraccionimp: Optional[int] = None - tipofraccmex: Optional[int] = None - tasafraccmex: Optional[int] = None - umequivalentemex: Optional[int] = None - numparteame: Optional[int] = None - fraccioname: Optional[int] = None - paisorigename: Optional[int] = None - umequivalenteame: Optional[int] = None - firmafame: Optional[str] = None - impordencomp: Optional[int] = None - decimalespeso: Optional[int] = None - decimalescant: Optional[int] = None - decimalesvalor: Optional[int] = None - decimalescosto: Optional[int] = None - tipomoneda: Optional[str] = None - clavemoneda: Optional[str] = None - umauxiliarmex: Optional[int] = None - umalternamex: Optional[int] = None - transportista: Optional[str] = None - conductor: Optional[str] = None - transporte: Optional[str] = None - numtransporte: Optional[str] = None - observacione: Optional[str] = None - observacioni: Optional[str] = None - clavebultos: Optional[str] = None - packingordencompra: Optional[int] = None - firmaelectronica: Optional[str] = None - incluirlineadelpo: Optional[int] = None - lineapersonaaa: Optional[int] = None - agregarvaenprodterminados: Optional[int] = None - ocultarfechahora: Optional[int] = None - - -class SSisDefSettings(BaseModel): - """Legacy Table: SSisDef (Definitive Import)""" - consecutivo: Optional[int] = None - prefijoimpo: Optional[str] = None - consecutivoimpo: Optional[str] = None - porparteclasemex: Optional[str] = None - porparteclaseame: Optional[str] = None - pedimento: Optional[str] = None - tipo: Optional[str] = None - proveedor: Optional[str] = None - vendidoconsignado: Optional[str] = None - vendidoa: Optional[str] = None - enviadotransferido: Optional[str] = None - enviadoa: Optional[str] = None - aaduanal: Optional[str] = None - incoterm: Optional[str] = None - flete: Optional[Decimal] = None - identificador: Optional[str] = None - firmafmex: Optional[str] = None - firmafame: Optional[str] = None - codigobarras: Optional[int] = None - numpartemex: Optional[int] = None - numparteame: Optional[int] = None - fraccionimp: Optional[int] = None - tasafraccmex: Optional[int] = None - tipofraccmex: Optional[int] = None - paisorigenmex: Optional[int] = None - umequivalentemex: Optional[int] = None - umauxiliarmex: Optional[int] = None - fraccioname: Optional[int] = None - paisorigename: Optional[int] = None - umequivalenteame: Optional[int] = None - impordencomp: Optional[int] = None - decimalespeso: Optional[int] = None - decimalescant: Optional[int] = None - decimalesvalor: Optional[int] = None - decimalescosto: Optional[int] = None - tipomoneda: Optional[str] = None - clavemoneda: Optional[str] = None - cantlimite: Optional[Decimal] = None - pesolimite: Optional[Decimal] = None - valorlimite: Optional[Decimal] = None - umalternamex: Optional[int] = None - restringeimpopt: Optional[int] = None - partecomplementariamex: Optional[int] = None - pagoimpuesto: Optional[str] = None - formapago: Optional[str] = None - metvalor: Optional[str] = None - transportista: Optional[str] = None - aaduanalame: Optional[str] = None - conductor: Optional[str] = None - transporte: Optional[str] = None - numtransporte: Optional[str] = None - packingpedclave: Optional[int] = None - porordenporocpack: Optional[str] = None - actualizarpartepartida: Optional[int] = None - observacione: Optional[str] = None - observacioni: Optional[str] = None - clavebultos: Optional[str] = None - partecomplemexpartida: Optional[int] = None - cantlimitemin: Optional[Decimal] = None - pesolimitemin: Optional[Decimal] = None - valorlimitemin: Optional[Decimal] = None - packingordencompra: Optional[int] = None - firmaelectronica: Optional[str] = None - solicitarcontrasenaadministrador: Optional[int] = None - incluirlineadelpo: Optional[int] = None - lineapersonaaa: Optional[int] = None - agregarvaenprodterminados: Optional[int] = None - imprimirlote: Optional[int] = None - imprimirnumentrada: Optional[int] = None - ocultarfechahora: Optional[int] = None - - -class SSisExpoSettings(BaseModel): - """Legacy Table: SSisExpo (Exports)""" - tipofactura: Optional[str] = None - porparteclasemex: Optional[str] = None - porparteclaseame: Optional[str] = None - prefijoexpo: Optional[str] = None - consecutivoexpo: Optional[str] = None - pedimento: Optional[str] = None - proveedor: Optional[str] = None - vendidoconsignado: Optional[str] = None - vendidoa: Optional[str] = None - enviadotransferido: Optional[str] = None - enviadoa: Optional[str] = None - vendidopor: Optional[str] = None - aaduanal: Optional[str] = None - tipo: Optional[str] = None - incoterm: Optional[str] = None - flete: Optional[Decimal] = None - identificador: Optional[str] = None - tipomoneda: Optional[str] = None - moneda: Optional[str] = None - decimalespeso: Optional[int] = None - decimalescant: Optional[int] = None - decimalesvalor: Optional[int] = None - decimalescosto: Optional[int] = None - numpartemex: Optional[int] = None - paisorigenmex: Optional[int] = None - fraccionexp: Optional[int] = None - tasafraccmex: Optional[int] = None - costounitmex: Optional[int] = None - ordencompmex: Optional[int] = None - consfacmex: Optional[int] = None - umequivalentemex: Optional[int] = None - ocultarempaquemex: Optional[int] = None - costounitmpmex: Optional[int] = None - codigobarras: Optional[int] = None - partecomplementariamex: Optional[int] = None - valordllsmexpesos: Optional[int] = None - cantpncodbar: Optional[int] = None - dtacerocodbar: Optional[int] = None - firmafmex: Optional[str] = None - numparteame: Optional[int] = None - firmafame: Optional[str] = None - fraccioname: Optional[int] = None - paisorigename: Optional[int] = None - ordencompame: Optional[int] = None - umequivalenteame: Optional[int] = None - fdainformacioname: Optional[int] = None - paisopcame: Optional[int] = None - muestrafree: Optional[int] = None - fraccmexdes: Optional[int] = None - tipofraccmexdes: Optional[int] = None - colvalordes: Optional[int] = None - colpesodes: Optional[int] = None - descargaclase: Optional[int] = None - descargasust: Optional[int] = None - descargadef: Optional[int] = None - ventqueueact: Optional[int] = None - codigoscac: Optional[int] = None - formadesperdicio: Optional[str] = None - calvalorbasecosto: Optional[int] = None - calvabasecapt: Optional[int] = None - calpesobasedescarga: Optional[int] = None - calempaquebasecapt: Optional[int] = None - ocultcantcons: Optional[int] = None - prefijofraccmult: Optional[int] = None - cantlimite: Optional[Decimal] = None - pesolimite: Optional[Decimal] = None - valorlimite: Optional[Decimal] = None - costamepaisparte: Optional[int] = None - basecostompototal: Optional[str] = None - umequivalentemex2: Optional[int] = None - umequivalenteame2: Optional[int] = None - totalequivmex: Optional[int] = None - totalequivame: Optional[int] = None - infoadicional: Optional[str] = None - restringeexpomp: Optional[int] = None - calvatotagremp: Optional[str] = None - activaadvpedrtv1: Optional[int] = None - asignafrac9801: Optional[int] = None - valorexcfrac9801: Optional[Decimal] = None - pagoimpuesto: Optional[str] = None - formapago: Optional[str] = None - scrapsolonoduty: Optional[int] = None - descextraamepartes: Optional[int] = None - incambioregdesc: Optional[int] = None - escambioregimen: Optional[str] = None - descnocontemcant: Optional[int] = None - metvalor: Optional[str] = None - ocultarempaqueame: Optional[int] = None - transportista: Optional[str] = None - aaduanalame: Optional[str] = None - pesonetodesdiffac: Optional[Decimal] = None - enviadoporvendidopor: Optional[str] = None - conductor: Optional[str] = None - transporte: Optional[str] = None - numtransporte: Optional[str] = None - observacione: Optional[str] = None - observacioni: Optional[str] = None - clavebultos: Optional[str] = None - ocultacolva: Optional[int] = None - calccostosamebasemex: Optional[int] = None - proveedorexportador: Optional[str] = None - tipofraccmex: Optional[int] = None - partecomplemexpartida: Optional[int] = None - validacantparcandes: Optional[int] = None - cantlimitemin: Optional[Decimal] = None - pesolimitemin: Optional[Decimal] = None - valorlimitemin: Optional[Decimal] = None - costosamepais: Optional[str] = None - incluirfracamebil: Optional[int] = None - solicitarpswdactual: Optional[int] = None - solicitarpswddesactual: Optional[int] = None - buscarsaldosrecientes: Optional[int] = None - packingordenventa: Optional[int] = None - imprimirordenventa: Optional[int] = None - firmaelectronica: Optional[str] = None - mpf: Optional[int] = None - valormpf: Optional[Decimal] = None - colcantporpeso: Optional[int] = None - packingpedclave: Optional[int] = None - generafacturaimd: Optional[str] = None - generafacturaimdenbasea: Optional[str] = None - solicitarcontrasenaadministrador: Optional[int] = None - incluirlineadelpo: Optional[int] = None - valorpacking1dlls: Optional[int] = None - tomarvalormexenfacame: Optional[int] = None - lineapersonaaa: Optional[int] = None - calamebasemexmp: Optional[int] = None - calamebasemexva: Optional[int] = None - calamebasemexempaque: Optional[int] = None - usarvaenfacturaamericana: Optional[int] = None - activarfechacortesaldos: Optional[int] = None - fechacortesaldos: Optional[int] = None - imprimirlote: Optional[int] = None - imprimirnumentrada: Optional[int] = None - respaldardescargaendescargam: Optional[int] = None - ocultarfechahora: Optional[int] = None - calcularvaloresamericanosenbaseapartida: Optional[int] = None - validarestatusped: Optional[int] = None - descapais: Optional[int] = None - - -class SSisImpoSettings(BaseModel): - """Legacy Table: SSisImpo (General Import)""" - consecutivo: Optional[int] = None - prefijoimpo: Optional[str] = None - prefijocr: Optional[str] = None - consecutivoimpo: Optional[str] = None - consecutivocr: Optional[int] = None - porparteclasemex: Optional[str] = None - porparteclaseame: Optional[str] = None - pedimento: Optional[str] = None - tipo: Optional[str] = None - proveedor: Optional[str] = None - vendidoconsignado: Optional[str] = None - vendidoa: Optional[str] = None - enviadotransferido: Optional[str] = None - enviadoa: Optional[str] = None - aaduanal: Optional[str] = None - incoterm: Optional[str] = None - flete: Optional[Decimal] = None - identificador: Optional[str] = None - numpartemex: Optional[int] = None - numparteame: Optional[int] = None - firmafmex: Optional[str] = None - firmafame: Optional[str] = None - codigobarras: Optional[int] = None - fraccionimp: Optional[int] = None - tasafraccmex: Optional[int] = None - tipofraccmex: Optional[int] = None - paisorigenmex: Optional[int] = None - umequivalentemex: Optional[int] = None - umauxiliarmex: Optional[int] = None - perpagrenglon: Optional[int] = None - fraccioname: Optional[int] = None - paisorigename: Optional[int] = None - umequivalenteame: Optional[int] = None - impordencomp: Optional[int] = None - decimalespeso: Optional[int] = None - decimalescant: Optional[int] = None - decimalesvalor: Optional[int] = None - decimalescosto: Optional[int] = None - tipomoneda: Optional[str] = None - clavemoneda: Optional[str] = None - cantlimite: Optional[Decimal] = None - pesolimite: Optional[Decimal] = None - valorlimite: Optional[Decimal] = None - valorlimitepar: Optional[Decimal] = None - umalternamex: Optional[int] = None - restringeimpopt: Optional[int] = None - controlremesa: Optional[str] = None - remesainicio: Optional[int] = None - remesafinal: Optional[int] = None - leyendafacame: Optional[str] = None - calcigibasecapt: Optional[int] = None - umequivalentemex2: Optional[int] = None - umequivalenteame2: Optional[int] = None - partecomplementariamex: Optional[int] = None - pagoimpuesto: Optional[str] = None - formapago: Optional[str] = None - metvalor: Optional[str] = None - transportista: Optional[str] = None - aaduanalame: Optional[str] = None - conductor: Optional[str] = None - transporte: Optional[str] = None - numtransporte: Optional[str] = None - packingpedclave: Optional[int] = None - porordenporocpack: Optional[str] = None - actualizarpartepartida: Optional[int] = None - observacione: Optional[str] = None - observacioni: Optional[str] = None - clavebultos: Optional[str] = None - partecomplemexpartida: Optional[int] = None - cantlimitemin: Optional[Decimal] = None - pesolimitemin: Optional[Decimal] = None - valorlimitemin: Optional[Decimal] = None - valorlimiteparmin: Optional[Decimal] = None - packingordencompra: Optional[int] = None - firmaelectronica: Optional[str] = None - solicitarcontrasenaadministrador: Optional[int] = None - incluirlineadelpo: Optional[int] = None - lineapersonaaa: Optional[int] = None - agregarvaenprodterminados: Optional[int] = None - imprimirlote: Optional[int] = None - imprimirnumentrada: Optional[int] = None - ocultarfechahora: Optional[int] = None - - - - - -class QSisCMexSettings(BaseModel): - """Legacy Table: QSisCMex (Fixed Asset Mexican Purchase)""" - consecutivo: Optional[int] = None - prefijocm: Optional[str] = None - consecutivocm: Optional[str] = None - proveedor: Optional[str] = None - vendidoconsignado: Optional[str] = None - vendidoa: Optional[str] = None - enviadotransferido: Optional[str] = None - enviadoa: Optional[str] = None - flete: Optional[Decimal] = None - paisorigenmex: Optional[int] = None - firmafmex: Optional[str] = None - fraccionimp: Optional[int] = None - tipofraccmex: Optional[int] = None - tasafraccmex: Optional[int] = None - impordencomp: Optional[int] = None - decimalespeso: Optional[int] = None - decimalescant: Optional[int] = None - decimalesvalor: Optional[int] = None - decimalescosto: Optional[int] = None - tipomoneda: Optional[str] = None - clavemoneda: Optional[str] = None - transportista: Optional[str] = None - conductor: Optional[str] = None - transporte: Optional[str] = None - numtransporte: Optional[str] = None - observacione: Optional[str] = None - observacioni: Optional[str] = None - packingordencompra: Optional[int] = None - imprimirlote: Optional[int] = None - imprimirnumentrada: Optional[int] = None - - -class QSisDefSettings(BaseModel): - """Legacy Table: QSisDef (Fixed Asset Definitive Import)""" - consecutivo: Optional[int] = None - prefijoimpo: Optional[str] = None - consecutivoimpo: Optional[str] = None - pedimento: Optional[str] = None - tipo: Optional[str] = None - proveedor: Optional[str] = None - vendidoconsignado: Optional[str] = None - vendidoa: Optional[str] = None - enviadotransferido: Optional[str] = None - enviadoa: Optional[str] = None - aaduanal: Optional[str] = None - incoterm: Optional[str] = None - flete: Optional[Decimal] = None - identificador: Optional[str] = None - firmafmex: Optional[str] = None - firmafame: Optional[str] = None - generarassettag: Optional[int] = None - codigobarras: Optional[int] = None - fraccionimp: Optional[int] = None - tasafraccmex: Optional[int] = None - tipofraccmex: Optional[int] = None - fraccioname: Optional[int] = None - paisorigenmex: Optional[int] = None - paisorigename: Optional[int] = None - impordencomp: Optional[int] = None - decimalespeso: Optional[int] = None - decimalescant: Optional[int] = None - decimalesvalor: Optional[int] = None - decimalescosto: Optional[int] = None - tipomoneda: Optional[str] = None - tipomn: Optional[str] = None - tipome: Optional[str] = None - clavemoneda: Optional[str] = None - controlremesa: Optional[str] = None - remesainicio: Optional[int] = None - remesafinal: Optional[int] = None - cantlimite: Optional[Decimal] = None - pesolimite: Optional[Decimal] = None - valorlimite: Optional[Decimal] = None - metvalor: Optional[str] = None - pagoimpuesto: Optional[str] = None - formapago: Optional[str] = None - transportista: Optional[str] = None - aaduanalame: Optional[str] = None - conductor: Optional[str] = None - transporte: Optional[str] = None - numtransporte: Optional[str] = None - observacione: Optional[str] = None - observacioni: Optional[str] = None - numpartemex: Optional[int] = None - numparteame: Optional[int] = None - cantlimitemin: Optional[Decimal] = None - pesolimitemin: Optional[Decimal] = None - valorlimitemin: Optional[Decimal] = None - actualizarpartepartida: Optional[int] = None - packingordencompra: Optional[int] = None - solicitarcontrasenaadministrador: Optional[int] = None - imprimirlote: Optional[int] = None - imprimirnumentrada: Optional[int] = None - - -class QSisGenSettings(BaseModel): - """Legacy Table: QSisGen (Fixed Asset General)""" - consecutivo: Optional[int] = None - dta: Optional[int] = None - dtaexpo: Optional[int] = None - subempresa: Optional[str] = None - patharch: Optional[str] = None - patharchtransmision: Optional[str] = None - pathrespuesta: Optional[str] = None - patharchped: Optional[str] = None - patharchpedconsm: Optional[str] = None - pathgenimpotemp: Optional[str] = None - pathgenexpo: Optional[str] = None - aplicapermat: Optional[int] = None - actseguridad: Optional[int] = None - controldes: Optional[int] = None - diadesactual: Optional[int] = None - fechades: Optional[int] = None - ubiplanta: Optional[str] = None - datoshistoricos: Optional[int] = None - tipovenro: Optional[str] = None - cantvenro: Optional[int] = None - omitirimposubpcodbarras: Optional[str] = None - muestraarchcodbarras: Optional[int] = None - calvalbasetcped: Optional[int] = None - calvalbasetcpedexpo: Optional[int] = None - decimalespeso: Optional[int] = None - decimalescant: Optional[int] = None - decimalesvalor: Optional[int] = None - factoriva: Optional[Decimal] = None - filtrocantidad: Optional[Decimal] = None - firmapacking: Optional[int] = None - advertenciatm: Optional[int] = None - calcdepreciacion: Optional[str] = None - valmanifusado: Optional[int] = None - validadecencant: Optional[int] = None - mostraradvertenciaro: Optional[int] = None - usartranspamedocame: Optional[int] = None - escondaamexpacking: Optional[int] = None - cantvscantseries: Optional[int] = None - covefechaemision: Optional[int] = None - interfaceaaconsolidada: Optional[int] = None - interfaceaatcfpff: Optional[str] = None - incluirobscoveobsimpo: Optional[int] = None - agregarincreimpo: Optional[int] = None - repdescargolineal: Optional[int] = None - identificadornodoseriecove: Optional[int] = None - actpdfreportes: Optional[int] = None - patharchpdfimpo: Optional[str] = None - patharchpdfexpo: Optional[str] = None - mensajesvurfc: Optional[int] = None - mostrarpackinglistingles: Optional[int] = None - enviarsubpartidascove: Optional[int] = None - restringepaisimpo: Optional[int] = None - restringpaisexpo: Optional[int] = None - utilizarfechapagopeddeundiaanterior: Optional[int] = None - utilizartitulosalternativosimpresionfactura: Optional[int] = None - usartcdelafechapagopedimpoendescarga: Optional[int] = None - usarvude128o256: Optional[int] = None - agregarnumeroembarque: Optional[int] = None - utilizarumdeexistenciaentransmisionvu: Optional[int] = None - utilizarcodigodebrokerdeclienteenmainx30: Optional[int] = None - hojacalculosepararincrementablesanexo3: Optional[int] = None - hojacalculodesglosefacturaanexo3: Optional[int] = None - utilizarnombregenericomainx30: Optional[int] = None - utilizartcrespectoatipoped: Optional[int] = None - utilizarsolopartesnaftaenco: Optional[int] = None - cambiarpesosporcostounitario: Optional[int] = None - bloqueodeediciondefacturas: Optional[int] = None - calcularcostounitarioenbaseavalortotalscaf: Optional[int] = None - impresionfacturaalterna: Optional[int] = None - transmitirfacalterna: Optional[int] = None - validarseries: Optional[int] = None - emailas: Optional[int] = None - afostrofe: Optional[int] = None - codigobarrasesp: Optional[int] = None - muestra_copias_codbarras: Optional[int] = None - asignainfoparte: Optional[int] = None - pathnaftaccs: Optional[str] = None - - -class QSisExpoRepSettings(BaseModel): - """Legacy Table: QSisExpoRep (Fixed Asset Export Repeat?)""" - tipofactura: Optional[str] = None - prefijoexpo: Optional[str] = None - consecutivoexpo: Optional[str] = None - pedimento: Optional[str] = None - tipo: Optional[str] = None - proveedor: Optional[str] = None - vendidoconsignado: Optional[str] = None - vendidoa: Optional[str] = None - enviadotransferido: Optional[str] = None - enviadoa: Optional[str] = None - vendidopor: Optional[str] = None - aaduanal: Optional[str] = None - incoterm: Optional[str] = None - flete: Optional[Decimal] = None - identificador: Optional[str] = None - codigobarras: Optional[int] = None - cantpncodbar: Optional[int] = None - dtacerocodbar: Optional[int] = None - cbarrasvalor: Optional[str] = None - firmafmex: Optional[str] = None - fraccionexp: Optional[int] = None - tasafraccmex: Optional[int] = None - tipofraccmex: Optional[int] = None - numpartemex: Optional[int] = None - paisorigenmex: Optional[int] = None - firmafame: Optional[str] = None - fraccioname: Optional[int] = None - paisorigename: Optional[int] = None - numparteame: Optional[int] = None - codigoscac: Optional[int] = None - impordencomp: Optional[int] = None - decimalespeso: Optional[int] = None - decimalescant: Optional[int] = None - decimalesvalor: Optional[int] = None - decimalescosto: Optional[int] = None - tipomoneda: Optional[str] = None - clavemoneda: Optional[str] = None - tipomn: Optional[str] = None - tipome: Optional[str] = None - mostrarvalact: Optional[int] = None - pepsporclase: Optional[int] = None - pepsporfraccion: Optional[int] = None - pepspordescripcion: Optional[int] = None - cantlimite: Optional[Decimal] = None - pesolimite: Optional[Decimal] = None - valorlimite: Optional[Decimal] = None - ventqueueact: Optional[int] = None - valfacttc: Optional[str] = None - metvalor: Optional[str] = None - pagoimpuesto: Optional[str] = None - formapago: Optional[str] = None - transportista: Optional[str] = None - aaduanalame: Optional[str] = None - enviadoporvendidopor: Optional[str] = None - conductor: Optional[str] = None - transporte: Optional[str] = None - numtransporte: Optional[str] = None - observacione: Optional[str] = None - observacioni: Optional[str] = None - proveedorexportador: Optional[str] = None - cantlimitemin: Optional[Decimal] = None - pesolimitemin: Optional[Decimal] = None - valorlimitemin: Optional[Decimal] = None - solicitarpswdactual: Optional[int] = None - solicitarpswddesactual: Optional[int] = None - packingordenventa: Optional[int] = None - imprimirpo: Optional[int] = None - fdainformacioname: Optional[int] = None - packingpedclave: Optional[int] = None - solicitarcontrasenaadministrador: Optional[int] = None - imprimirlote: Optional[int] = None - imprimirnumentrada: Optional[int] = None - - -class QSisImpoSettings(BaseModel): - """Legacy Table: QSisImpo (Fixed Asset Import)""" - consecutivo: Optional[int] = None - prefijoimpo: Optional[str] = None - consecutivoimpo: Optional[str] = None - pedimento: Optional[str] = None - tipo: Optional[str] = None - proveedor: Optional[str] = None - vendidoconsignado: Optional[str] = None - vendidoa: Optional[str] = None - enviadotransferido: Optional[str] = None - enviadoa: Optional[str] = None - aaduanal: Optional[str] = None - incoterm: Optional[str] = None - flete: Optional[Decimal] = None - identificador: Optional[str] = None - firmafmex: Optional[str] = None - firmafame: Optional[str] = None - codigobarras: Optional[int] = None - fraccionimp: Optional[int] = None - tasafraccmex: Optional[int] = None - tipofraccmex: Optional[int] = None - fraccioname: Optional[int] = None - paisorigenmex: Optional[int] = None - paisorigename: Optional[int] = None - impordencomp: Optional[int] = None - generarassettag: Optional[int] = None - decimalespeso: Optional[int] = None - decimalescant: Optional[int] = None - decimalesvalor: Optional[int] = None - decimalescosto: Optional[int] = None - tipomoneda: Optional[str] = None - tipomn: Optional[str] = None - tipome: Optional[str] = None - clavemoneda: Optional[str] = None - controlremesa: Optional[str] = None - remesainicio: Optional[int] = None - remesafinal: Optional[int] = None - perpagrenglon: Optional[int] = None - cantlimite: Optional[Decimal] = None - pesolimite: Optional[Decimal] = None - valorlimite: Optional[Decimal] = None - metvalor: Optional[str] = None - pagoimpuesto: Optional[str] = None - formapago: Optional[str] = None - transportista: Optional[str] = None - aaduanalame: Optional[str] = None - conductor: Optional[str] = None - transporte: Optional[str] = None - numtransporte: Optional[str] = None - observacione: Optional[str] = None - observacioni: Optional[str] = None - numpartemex: Optional[int] = None - numparteame: Optional[int] = None - cantlimitemin: Optional[Decimal] = None - pesolimitemin: Optional[Decimal] = None - valorlimitemin: Optional[Decimal] = None - actualizarpartepartida: Optional[int] = None - packingordencompra: Optional[int] = None - solicitarcontrasenaadministrador: Optional[int] = None - imprimirlote: Optional[int] = None - imprimirnumentrada: Optional[int] = None - - -class QSisImpoRepSettings(BaseModel): - """Legacy Table: QSisImpoRep (Fixed Asset Import Repeat?)""" - tipofactura: Optional[str] = None - consecutivo: Optional[int] = None - prefijoimpo: Optional[str] = None - consecutivoimpo: Optional[str] = None - pedimento: Optional[str] = None - tipo: Optional[str] = None - proveedor: Optional[str] = None - vendidoconsignado: Optional[str] = None - vendidoa: Optional[str] = None - enviadotransferido: Optional[str] = None - enviadoa: Optional[str] = None - aaduanal: Optional[str] = None - incoterm: Optional[str] = None - flete: Optional[Decimal] = None - identificador: Optional[str] = None - firmafmex: Optional[str] = None - firmafame: Optional[str] = None - codigobarras: Optional[int] = None - fraccionimp: Optional[int] = None - tasafraccmex: Optional[int] = None - tipofraccmex: Optional[int] = None - fraccioname: Optional[int] = None - paisorigenmex: Optional[int] = None - paisorigename: Optional[int] = None - impordencomp: Optional[int] = None - generarassettag: Optional[int] = None - decimalespeso: Optional[int] = None - decimalescant: Optional[int] = None - decimalesvalor: Optional[int] = None - decimalescosto: Optional[int] = None - tipomoneda: Optional[str] = None - tipomn: Optional[str] = None - tipome: Optional[str] = None - clavemoneda: Optional[str] = None - controlremesa: Optional[str] = None - remesainicio: Optional[int] = None - remesafinal: Optional[int] = None - perpagrenglon: Optional[int] = None - cantlimite: Optional[Decimal] = None - pesolimite: Optional[Decimal] = None - valorlimite: Optional[Decimal] = None - ventqueueact: Optional[int] = None - metvalor: Optional[str] = None - pagoimpuesto: Optional[str] = None - formapago: Optional[str] = None - transportista: Optional[str] = None - aaduanalame: Optional[str] = None - conductor: Optional[str] = None - transporte: Optional[str] = None - numtransporte: Optional[str] = None - observacione: Optional[str] = None - observacioni: Optional[str] = None - numpartemex: Optional[int] = None - numparteame: Optional[int] = None - cantlimitemin: Optional[Decimal] = None - pesolimitemin: Optional[Decimal] = None - valorlimitemin: Optional[Decimal] = None - packingordencompra: Optional[int] = None - solicitarcontrasenaadministrador: Optional[int] = None - - -class QSisExpoSettings(BaseModel): - """Legacy Table: QSisExpo (Fixed Asset Export)""" - tipofactura: Optional[str] = None - escambioregimen: Optional[str] = None - prefijoexpo: Optional[str] = None - consecutivoexpo: Optional[str] = None - pedimento: Optional[str] = None - tipo: Optional[str] = None - proveedor: Optional[str] = None - vendidoconsignado: Optional[str] = None - vendidoa: Optional[str] = None - enviadotransferido: Optional[str] = None - enviadoa: Optional[str] = None - vendidopor: Optional[str] = None - aaduanal: Optional[str] = None - incoterm: Optional[str] = None - flete: Optional[Decimal] = None - identificador: Optional[str] = None - codigobarras: Optional[int] = None - cantpncodbar: Optional[int] = None - dtacerocodbar: Optional[int] = None - cbarrasvalor: Optional[str] = None - firmafmex: Optional[str] = None - fraccionexp: Optional[int] = None - tasafraccmex: Optional[int] = None - tipofraccmex: Optional[int] = None - numpartemex: Optional[int] = None - paisorigenmex: Optional[int] = None - costounitmex: Optional[int] = None - ocultarempaquemex: Optional[int] = None - firmafame: Optional[str] = None - fraccioname: Optional[int] = None - paisorigename: Optional[int] = None - fdainformacioname: Optional[int] = None - numparteame: Optional[int] = None - codigoscac: Optional[int] = None - impordencomp: Optional[int] = None - decimalespeso: Optional[int] = None - decimalescant: Optional[int] = None - decimalesvalor: Optional[int] = None - decimalescosto: Optional[int] = None - tipomoneda: Optional[str] = None - clavemoneda: Optional[str] = None - tipomn: Optional[str] = None - tipome: Optional[str] = None - mostrarvalact: Optional[int] = None - pepsporclase: Optional[int] = None - pepsporfraccion: Optional[int] = None - pepspordescripcion: Optional[int] = None - pepsporfraccalterna: Optional[int] = None - cantlimite: Optional[Decimal] = None - pesolimite: Optional[Decimal] = None - valorlimite: Optional[Decimal] = None - ventqueueact: Optional[int] = None - valfacttc: Optional[str] = None - metvalor: Optional[str] = None - pagoimpuesto: Optional[str] = None - formapago: Optional[str] = None - colcantporpeso: Optional[int] = None - transportista: Optional[str] = None - aaduanalame: Optional[str] = None - enviadoporvendidopor: Optional[str] = None - conductor: Optional[str] = None - transporte: Optional[str] = None - numtransporte: Optional[str] = None - observacione: Optional[str] = None - observacioni: Optional[str] = None - proveedorexportador: Optional[str] = None - cantlimitemin: Optional[Decimal] = None - pesolimitemin: Optional[Decimal] = None - valorlimitemin: Optional[Decimal] = None - solicitarpswdactual: Optional[int] = None - solicitarpswddesactual: Optional[int] = None - packingordenventa: Optional[int] = None - imprimirpo: Optional[int] = None - packingpedclave: Optional[int] = None - generafacturaimd: Optional[str] = None - generafacturaimdenbasea: Optional[str] = None - solicitarcontrasenaadministrador: Optional[int] = None - imprimirlote: Optional[int] = None - imprimirnumentrada: Optional[int] = None - - -class SettingsPayload(BaseModel): - """ - Master container for all possible overrides. - Using Optional fields so only the delta is required in JSONB. - """ - # SCAII (Invoices / Inventory) - ssisgen: Optional[SSisGenSettings] = None - ssisgen2: Optional[SSisGen2Settings] = None - ssisgen3: Optional[SSisGen3Settings] = None - ssismex: Optional[SSisMexSettings] = None - ssisdef: Optional[SSisDefSettings] = None - ssisexpo: Optional[SSisExpoSettings] = None - ssisimpo: Optional[SSisImpoSettings] = None - - # SCAF (Fixed Assets) - qsiscmex: Optional[QSisCMexSettings] = None - qsisdef: Optional[QSisDefSettings] = None - qsisgen: Optional[QSisGenSettings] = None - qsisexporep: Optional[QSisExpoRepSettings] = None - qsisimpo: Optional[QSisImpoSettings] = None - qsisimporep: Optional[QSisImpoRepSettings] = None - qsisexpo: Optional[QSisExpoSettings] = None - - # Unified Invoice Settings - invoices: Optional["InvoiceSettingsMap"] = None - -class InvoiceSettingsData(BaseModel): - """Container for form-specific invoice settings""" - InvoiceTopFieldsFormData: Optional[Dict[str, Any]] = None - generalFormData: Optional[Dict[str, Any]] = None - observationFormData: Optional[Dict[str, Any]] = None - itemsFormData: Optional[Dict[str, Any]] = None - othersFormData: Optional[Dict[str, Any]] = None - continuationFormData: Optional[Dict[str, Any]] = None - -class InvoiceSettingsMap(BaseModel): - """ - Map of invoice settings indexed by operation_type (imp/exp) - and then by invoice_type. - Example: {"imp": {"factura_importacion": {...}}} - """ - types: Optional[Dict[str, Dict[str, InvoiceSettingsData]]] = None - -class AppSettingRequest(BaseModel): - tenant_id: Optional[int] = None - company_id: Optional[int] = None - settings: SettingsPayload - -class AppSettingResponse(BaseModel): - id: int - tenant_id: Optional[int] - company_id: Optional[int] - settings: Dict[str, Any] - - class Config: - from_attributes = True - diff --git a/backend/api/v1/modules/a76/app_settings/service.py b/backend/api/v1/modules/a76/app_settings/service.py deleted file mode 100644 index 60d4e19a..00000000 --- a/backend/api/v1/modules/a76/app_settings/service.py +++ /dev/null @@ -1,153 +0,0 @@ -from typing import Optional, List, Dict, Any -from sqlalchemy import or_, and_, select, case, nulls_first -from sqlalchemy.orm import Session -from .models import AppSetting - -import logging -logger = logging.getLogger(__name__) - -from decimal import Decimal -def convert_decimals(obj: Any) -> Any: - """ - Recursively converts Decimal objects to floats for JSON serialization. - Handles nested structures and None values. - """ - if obj is None: - return None - if isinstance(obj, list): - return [convert_decimals(i) for i in obj] - elif isinstance(obj, dict): - return {k: convert_decimals(v) for k, v in obj.items()} - elif isinstance(obj, Decimal): - return float(obj) - return obj - -def deep_merge(dict1: Dict[str, Any], dict2: Dict[str, Any]) -> Dict[str, Any]: - """ - Recursively merges dict2 into dict1. - """ - for key, value in dict2.items(): - if key in dict1 and isinstance(dict1[key], dict) and isinstance(value, dict): - deep_merge(dict1[key], value) - else: - dict1[key] = value - return dict1 - -class AppSettingsService: - """ - Service to manage hierarchical configuration overrides. - Hierarchy: System (Global) -> Tenant -> Company. - """ - - @staticmethod - def get_resolved_settings(db: Session, tenant_id: int, company_id: int) -> Dict[str, Any]: - """ - Retrieves settings from all levels (Global -> Tenant -> Company) and merges them. - Treats 0 as None for hierarchy resolution. - """ - # Normalize 0 to None for context-less lookups - t_id = tenant_id if tenant_id and tenant_id > 0 else None - c_id = company_id if company_id and company_id > 0 else None - - stmt = ( - select(AppSetting) - .where( - or_( - and_(AppSetting.tenant_id.is_(None), AppSetting.company_id.is_(None)), - and_(AppSetting.tenant_id == t_id, AppSetting.company_id.is_(None)) if t_id else False, - and_(AppSetting.tenant_id == t_id, AppSetting.company_id == c_id) if t_id and c_id else False, - ) - ) - .order_by( - # Ensure the order is: Global (1) -> Tenant (2) -> Company (3) - case( - (and_(AppSetting.tenant_id.is_(None), AppSetting.company_id.is_(None)), 1), - (and_(AppSetting.tenant_id.is_not(None), AppSetting.company_id.is_(None)), 2), - (and_(AppSetting.tenant_id.is_not(None), AppSetting.company_id.is_not(None)), 3), - else_=4 - ).asc() - ) - ) - - results = db.execute(stmt).scalars().all() - logger.info(f"RESOLVE: Found {len(results)} rows for Hierarchy") - - resolved_settings = {} - for row in results: - level_name = "GLOBAL" if not row.tenant_id else ("TENANT" if not row.company_id else "COMPANY") - - # Use safe data logging to avoid crashes - settings_data = row.settings if row.settings else {} - keys = list(settings_data.keys()) if isinstance(settings_data, dict) else "not-a-dict" - logger.info(f"RESOLVE: Merging {level_name} layer with keys: {keys}") - - if isinstance(settings_data, dict): - deep_merge(resolved_settings, settings_data) - - return resolved_settings - - @staticmethod - def upsert_settings(db: Session, tenant_id: Optional[int], company_id: Optional[int], settings: Dict[str, Any]) -> AppSetting: - """ - Inserts or updates settings for a specific level. - Treats 0 as None. - """ - # Normalize IDs: 0 or None means Global context at that level - tenant_id = tenant_id if tenant_id and tenant_id > 0 else None - company_id = company_id if company_id and company_id > 0 else None - - level_label = f"level(tenant={tenant_id}, company={company_id})" - logger.info(f"UPSERT Settings START: {level_label}, keys_to_update={list(settings.keys())}") - - # Ensure all Decimals are converted to floats before deep merge and save - settings = convert_decimals(settings) - - stmt = select(AppSetting).where( - and_( - AppSetting.tenant_id == tenant_id if tenant_id is not None else AppSetting.tenant_id.is_(None), - AppSetting.company_id == company_id if company_id is not None else AppSetting.company_id.is_(None) - ) - ) - - existing = db.execute(stmt).scalar_one_or_none() - - if existing: - # Deep merge at the root level (merging categories like ssisgen, ssismex, etc.) - logger.info(f"UPSERT: Updating existing row ID={existing.id}") - # Create a shallow copy of the top-level dict to ensure SQLAlchemy sees a new reference - new_settings = dict(existing.settings) if existing.settings else {} - - # Detailed logging of what's changing - for cat, data in settings.items(): - old_keys = list(new_settings.get(cat, {}).keys()) - new_keys = list(data.keys()) if isinstance(data, dict) else [] - logger.info(f"UPSERT: Merging category [{cat}]. Old keys: {old_keys}, New keys to merge/overwrite: {new_keys}") - - logger.info(f"UPSERT: Merging {len(settings)} top-level categories into existing row.") - deep_merge(new_settings, settings) - - # Second pass of conversion (merged results might still have Decimals if original row had them) - existing.settings = convert_decimals(new_settings) - - from sqlalchemy.orm.attributes import flag_modified - flag_modified(existing, "settings") - logger.info(f"UPSERT: Row updated and flagged as modified. Fields in ssisgen root: {list(new_settings.get('ssisgen', {}).keys())[:10]}...") - else: - logger.info(f"UPSERT: Creating NEW row for {level_label}") - existing = AppSetting( - tenant_id=tenant_id, - company_id=company_id, - settings=settings - ) - db.add(existing) - - try: - db.commit() - db.refresh(existing) - logger.info(f"UPSERT SUCCESS: Row ID={existing.id}, Final Settings Hash Keys={list(existing.settings.keys())}") - except Exception as e: - db.rollback() - logger.error(f"UPSERT FAILED: {str(e)}") - raise e - - return existing diff --git a/backend/api/v1/modules/a76/audit_log/events.py b/backend/api/v1/modules/a76/audit_log/events.py deleted file mode 100644 index 28f55422..00000000 --- a/backend/api/v1/modules/a76/audit_log/events.py +++ /dev/null @@ -1,242 +0,0 @@ -""" -Audit Log Events -""" - -import logging - -from sqlalchemy import event, inspect -from sqlalchemy.orm import Session - -from api.v1.modules.a76.general_catalogs.company.models import Company -from api.v1.modules.a76.invoices.models import InvoiceHeader -from core.database import ( - RLS_COMPANY_KEY, - RLS_TENANT_KEY, - rls_company_var, - rls_tenant_var, -) - -from .services.service import AuditService -from .utils.serialization import serialize_for_json -from core.context import get_user_context - -logger = logging.getLogger(__name__) - - -def register_audit_listeners(models_to_audit): - """ - Register SQLAlchemy listeners for given models - """ - for model in models_to_audit: - event.listen(model, "after_insert", after_insert_listener) - event.listen(model, "after_update", after_update_listener) - event.listen(model, "after_delete", after_delete_listener) - - -def _get_current_username(): - try: - context = get_user_context() - if context: - # Token usually has 'preferred_username' or 'name' or 'sub' - return ( - context.get("preferred_username") - or context.get("email") - or context.get("sub") - or "System" - ) - except Exception: - pass - return "System" - - -def _resolve_audit_company_tenant(session: Session, target) -> tuple: - """ - company_id / tenant_id desde la fila ORM, ContextVars RLS (petición HTTP), - o ``Company.tenant_id`` por ``company_id``. - """ - resolution_source = "target" - company_id = getattr(target, "company_id", None) - if company_id is None and getattr(target, "__tablename__", None) == "company": - company_id = getattr(target, "id", None) - if company_id is not None: - resolution_source = "company_self_id" - if company_id is None: - company_id = session.info.get(RLS_COMPANY_KEY) - if company_id is None: - company_id = rls_company_var.get() - if company_id is not None: - resolution_source = "rls_context" - - tenant_id = getattr(target, "tenant_id", None) - if tenant_id is None: - tenant_id = session.info.get(RLS_TENANT_KEY) - if tenant_id is None: - tenant_id = rls_tenant_var.get() - if tenant_id is not None and resolution_source == "target": - resolution_source = "rls_context" - - # Common fallback for invoice child tables where invoice_id points to header scope. - if (company_id is None or tenant_id is None) and hasattr(target, "invoice_id"): - invoice_id = getattr(target, "invoice_id", None) - if invoice_id is not None: - invoice_scope = ( - session.query(InvoiceHeader.company_id, InvoiceHeader.tenant_id) - .filter(InvoiceHeader.id == invoice_id, InvoiceHeader.deleted_at.is_(None)) - .first() - ) - if invoice_scope: - if company_id is None: - company_id = invoice_scope[0] - if tenant_id is None: - tenant_id = invoice_scope[1] - resolution_source = "invoice_header_lookup" - - if tenant_id is None and company_id is not None: - row = ( - session.query(Company.tenant_id) - .filter(Company.id == company_id, Company.deleted_at.is_(None)) - .first() - ) - if row: - tenant_id = int(row[0]) - resolution_source = "company_lookup" - - return company_id, tenant_id, resolution_source - - -def after_insert_listener(mapper, connection, target): - """ - Listener for INSERT operations - """ - table_name = target.__tablename__ - record_data = {c.name: getattr(target, c.name) for c in mapper.columns} - username = _get_current_username() - - session = Session(bind=connection) - try: - company_id, tenant_id, resolution_source = _resolve_audit_company_tenant( - session, target - ) - if company_id is None or tenant_id is None: - logger.warning( - "Audit skip INSERT table=%s record_id=%s company_id=%s tenant_id=%s source=%s", - table_name, - getattr(target, "id", None), - company_id, - tenant_id, - resolution_source, - ) - return - - AuditService.log_crud_operation( - db=session, - table_name=table_name, - operation_type="CREATE", - record_data=record_data, - username=username, - record_id=str(getattr(target, "id", "")), - company_id=company_id, - tenant_id=tenant_id, - ) - except Exception as e: - logger.warning("Error logging insert for %s: %s", table_name, e) - finally: - session.close() - - -def after_update_listener(mapper, connection, target): - """ - Listener for UPDATE operations - """ - table_name = target.__tablename__ - - state = inspect(target) - changes = {} - old_values = {} - new_values = {} - - for attr in state.attrs: - hist = attr.history - if hist.has_changes(): - changes[attr.key] = hist.added[0] if hist.added else None - old_values[attr.key] = hist.deleted[0] if hist.deleted else None - new_values[attr.key] = hist.added[0] if hist.added else None - - if not changes: - return - - record_data = {c.name: getattr(target, c.name) for c in mapper.columns} - username = _get_current_username() - - session = Session(bind=connection) - try: - company_id, tenant_id, resolution_source = _resolve_audit_company_tenant( - session, target - ) - if company_id is None or tenant_id is None: - logger.warning( - "Audit skip UPDATE table=%s record_id=%s company_id=%s tenant_id=%s source=%s", - table_name, - getattr(target, "id", None), - company_id, - tenant_id, - resolution_source, - ) - return - - AuditService.log_crud_operation( - db=session, - table_name=table_name, - operation_type="UPDATE", - record_data=record_data, - username=username, - record_id=str(getattr(target, "id", "")), - old_values=serialize_for_json(old_values), - new_values=serialize_for_json(new_values), - company_id=company_id, - tenant_id=tenant_id, - ) - except Exception as e: - logger.warning("Error logging update for %s: %s", table_name, e) - finally: - session.close() - - -def after_delete_listener(mapper, connection, target): - """ - Listener for DELETE operations - """ - table_name = target.__tablename__ - record_data = {c.name: getattr(target, c.name) for c in mapper.columns} - username = _get_current_username() - - session = Session(bind=connection) - try: - company_id, tenant_id, resolution_source = _resolve_audit_company_tenant( - session, target - ) - if company_id is None or tenant_id is None: - logger.warning( - "Audit skip DELETE table=%s record_id=%s company_id=%s tenant_id=%s source=%s", - table_name, - getattr(target, "id", None), - company_id, - tenant_id, - resolution_source, - ) - return - - AuditService.log_crud_operation( - db=session, - table_name=table_name, - operation_type="DELETE", - record_data=record_data, - username=username, - record_id=str(getattr(target, "id", "")), - company_id=company_id, - tenant_id=tenant_id, - ) - except Exception as e: - logger.warning("Error logging delete for %s: %s", table_name, e) - finally: - session.close() diff --git a/backend/api/v1/modules/a76/audit_log/middleware.py b/backend/api/v1/modules/a76/audit_log/middleware.py deleted file mode 100644 index b5f4ed68..00000000 --- a/backend/api/v1/modules/a76/audit_log/middleware.py +++ /dev/null @@ -1,27 +0,0 @@ -from starlette.middleware.base import BaseHTTPMiddleware -from starlette.requests import Request -from starlette.responses import Response -from core.security import verify_token -from core.context import set_user_context - -class UserContextMiddleware(BaseHTTPMiddleware): - async def dispatch(self, request: Request, call_next) -> Response: - auth_header = request.headers.get("Authorization") - if auth_header and auth_header.startswith("Bearer "): - token = auth_header.split(" ")[1] - try: - # verify_token might raise exception if invalid, we catch it to not block request - # but we won't have user context - user_info = await verify_token(token) - set_user_context(user_info) - except Exception: - # Log error or ignore - pass - - try: - response = await call_next(request) - except Exception: - # Re-raise the exception to let other middleware and handlers deal with it - raise - - return response diff --git a/backend/api/v1/modules/a76/audit_log/models.py b/backend/api/v1/modules/a76/audit_log/models.py deleted file mode 100644 index 31b51f03..00000000 --- a/backend/api/v1/modules/a76/audit_log/models.py +++ /dev/null @@ -1,51 +0,0 @@ -""" -Audit Log Models -""" - -from sqlalchemy import Column, Integer, String, Date, Time, DateTime, Text, Index, func -from sqlalchemy.dialects.postgresql import JSONB, ARRAY -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base - -class AuditLog(Base, TenantScopedMixin, TimestampMixin): - __tablename__ = "audit_logs" - __table_args__ = ( - Index('idx_audit_username_date', 'username', 'date'), - Index('idx_audit_procedure_date', 'procedure', 'date'), - Index('idx_audit_system_timestamp', 'system', 'timestamp'), - Index('idx_audit_table_record', 'table_name', 'record_id'), - {"schema": "a76"} # Use the a76 schema for audit logs - ) - - # Primary Key - spec_id = Column(Integer, primary_key=True, autoincrement=True) - - # Legacy Display Columns (English names as requested) - reference = Column(String(100), nullable=False, index=True) # Legacy: Referencia - procedure = Column(String(100), nullable=False, index=True) # Legacy: Procedimiento - movement = Column(String(255), nullable=False) # Legacy: Movimiento - username = Column(String(100), nullable=False, index=True) # Legacy: Usuario - date = Column(Date, nullable=False, index=True) # Legacy: Fecha - time = Column(Time, nullable=False) # Legacy: Hora - - # Technical Columns - timestamp = Column(DateTime(timezone=True), nullable=False, index=True) # Combined for queries - system = Column(String(50), nullable=False, index=True, default="fixed_asset") - - # Traceability - table_name = Column(String(100), nullable=True, index=True) - record_id = Column(String(255), nullable=True, index=True) - operation_type = Column(String(50), nullable=True, index=True) # CREATE, UPDATE, DELETE, LOGIN - - # Data Changes - old_values = Column(JSONB, nullable=True) - new_values = Column(JSONB, nullable=True) - changed_fields = Column(ARRAY(String), nullable=True) - - # Request Context - ip_address = Column(String(45), nullable=True) - user_agent = Column(Text, nullable=True) - endpoint = Column(String(500), nullable=True) - request_method = Column(String(10), nullable=True) - session_id = Column(String(50), nullable=True, index=True) - execution_time_ms = Column(Integer, nullable=True) diff --git a/backend/api/v1/modules/a76/audit_log/register.py b/backend/api/v1/modules/a76/audit_log/register.py deleted file mode 100644 index 3d86eaa2..00000000 --- a/backend/api/v1/modules/a76/audit_log/register.py +++ /dev/null @@ -1,169 +0,0 @@ -# Importar modelos para Audit Log -from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos -from api.v1.modules.a76.invoices.models import ( - InvoiceHeader, - InvoiceSalesDetails, - InvoiceComplianceMx, - InvoiceFinancials, - InvoiceLogistics, - InvoiceCollections, -) -from api.v1.modules.a76.audit_log.events import register_audit_listeners - -# Core Modules -from api.v1.modules.a76.clients_and_providers.models import ClientProvider -from api.v1.modules.a76.customs_brokers.models import ( - CustomsBroker, - CustomsBrokerVU, - CustomsBrokerPersonnel, -) -from api.v1.modules.a76.parts.models import Part -from api.v1.modules.a76.items.models import LineItem -from api.v1.modules.a76.items.series.models import Serie -from api.v1.modules.a76.general_catalogs.company.models import Company - -# Reference Data -from api.v1.modules.public.reference_data.countries.models import Country -from api.v1.modules.public.reference_data.currency_types.models import CurrencyType -from api.v1.modules.public.reference_data.customs_sections.models import CustomsSection -from api.v1.modules.public.reference_data.customs_warehouses.models import ( - CustomsWarehouse, -) -from api.v1.modules.public.reference_data.incoterms.models import Incoterm -from api.v1.modules.public.reference_data.invoice_types.models import InvoiceType -from api.v1.modules.public.reference_data.material_types.models import MaterialType -from api.v1.modules.public.reference_data.payment_methods.models import PaymentMethod -from api.v1.modules.public.reference_data.pedimento_transport_catalog.models import ( - PedimentoTransportCatalog, -) -from api.v1.modules.public.reference_data.pedimento_codes.models import PedimentoCode -from api.v1.modules.public.reference_data.pedimento_regimens.models import ( - RegimenPedimento, -) -from api.v1.modules.public.reference_data.states.models import State -from api.v1.modules.public.reference_data.transport_modes.models import TransportMode -from api.v1.modules.public.reference_data.transport_types.models import TransportType -from api.v1.modules.public.reference_data.valuation_methods.models import ( - ValuationMethod, -) -from api.v1.modules.public.reference_data.license_exceptions.models import LicenseException -from api.v1.modules.public.reference_data.agency_tariff_codes.models import AgencyTariffCode -from api.v1.modules.public.reference_data.identifiers.models import IdentifierCatalog -from api.v1.modules.public.reference_data.carta_porte_codes.models import CartaPorte -from api.v1.modules.a76.general_catalogs.units_of_measure.models import UnitOfMeasure -from api.v1.modules.a76.general_catalogs.exchange_rate.models import ExchangeRate -from api.v1.modules.a76.general_catalogs.identifiers.models import Identifier -from api.v1.modules.a76.classes.models import Class -from api.v1.modules.a76.general_catalogs.classification_concepts.models import ( - ClassificationConcept, -) -from api.v1.modules.a76.general_catalogs.concepts.models import Concept -from api.v1.modules.a76.general_catalogs.customs_broker_concepts.models import ( - CustomsBrokerConcept, -) -from api.v1.modules.a76.general_catalogs.depreciation_catalog.models import ( - DepreciationCatalog, -) -from api.v1.modules.a76.general_catalogs.doda.models import Doda -from api.v1.modules.a76.general_catalogs.electronic_notices.models import ( - ElectronicNotice, -) -from api.v1.modules.a76.general_catalogs.equivalencies.models import Equivalency -from api.v1.modules.a76.general_catalogs.error_catalogs.models import ErrorCatalog -from api.v1.modules.a76.general_catalogs.fda_catalog.models import FDACatalog -from api.v1.modules.a76.general_catalogs.inpc.models import INPC -from api.v1.modules.a76.general_catalogs.legends.models import Legend -from api.v1.modules.a76.general_catalogs.multi_currency_types.models import ( - MultiCurrencyType, -) -from api.v1.modules.a76.general_catalogs.packages.models import Package -from api.v1.modules.a76.general_catalogs.ports.models import Port -from api.v1.modules.a76.general_catalogs.location.models import Location, FaLocationExt -from api.v1.modules.a76.general_catalogs.prevalidators.models import Prevalidator -from api.v1.modules.a76.general_catalogs.seal.models import Seal -from api.v1.modules.a76.general_catalogs.signatures.models import Signature -from api.v1.modules.a76.general_catalogs.fractions.tariff_fractions.models import ( - TariffFraction, -) -from api.v1.modules.a76.general_catalogs.unit_conversions.models import UnitConversion -from api.v1.modules.a76.general_catalogs.fractions.us_tariff_fractions.models import ( - USTariffFraction, -) -from api.v1.modules.a76.general_catalogs.sectors.models import Sector -from api.v1.modules.a76.transportation.trailers.models import Trailer -from api.v1.modules.a76.transportation.transporters.models import Transporter -from api.v1.modules.a76.transportation.vehicles.models import Vehicle - - -# Registrar Listeners de Auditoría -def register_audit(): - register_audit_listeners( - [ - # Core Transactions - Pedimentos, - InvoiceHeader, - InvoiceSalesDetails, - InvoiceComplianceMx, - InvoiceFinancials, - InvoiceLogistics, - InvoiceCollections, - LineItem, - Serie, - # Sidebar Core Modules - ClientProvider, - CustomsBroker, - CustomsBrokerVU, - CustomsBrokerPersonnel, - Part, - Company, - # Transportation Modules - Trailer, - Transporter, - Vehicle, - # Reference Data - Country, - CurrencyType, - CustomsSection, - CustomsWarehouse, - Incoterm, - InvoiceType, - MaterialType, - PaymentMethod, - PedimentoTransportCatalog, - PedimentoCode, - RegimenPedimento, - Sector, - State, - TransportMode, - TransportType, - ValuationMethod, - LicenseException, - AgencyTariffCode, - IdentifierCatalog, - CartaPorte, - UnitOfMeasure, - ExchangeRate, - Identifier, - Class, - ClassificationConcept, - Concept, - CustomsBrokerConcept, - DepreciationCatalog, - Doda, - ElectronicNotice, - Equivalency, - ErrorCatalog, - FDACatalog, - INPC, - Legend, - MultiCurrencyType, - Package, - Port, - Prevalidator, - Seal, - Signature, - TariffFraction, - UnitConversion, - USTariffFraction, - ] - ) \ No newline at end of file diff --git a/backend/api/v1/modules/a76/audit_log/router.py b/backend/api/v1/modules/a76/audit_log/router.py deleted file mode 100644 index 58462cfd..00000000 --- a/backend/api/v1/modules/a76/audit_log/router.py +++ /dev/null @@ -1,462 +0,0 @@ -""" -Audit Log Router -""" -from datetime import date -from typing import Any, Dict, List, Optional - -from fastapi import APIRouter, Depends, HTTPException, Query -from fastapi.responses import StreamingResponse -from sqlalchemy.orm import Session -from sqlalchemy import or_, desc - -from core.database import get_core_db, set_rls_context -from core.security import get_current_user, validate_access_to_resource -from core.storage_s3 import get_object_bytes, list_objects_tree, should_ensure_s3_bucket -from .models import AuditLog -from .schemas import ( - AuditFileBreadcrumb, - AuditFileBrowserResponse, - AuditFileFolderItem, - AuditFileObjectItem, - AuditLogDetailResponse, - AuditLogListResponse, -) -from api.v1.modules.a76.general_catalogs.company.models import Company - -router = APIRouter() - - -def _audit_scope_tenant_id(db: Session, company_id: int) -> int: - """Tenant_id de la fila ``Company`` para filtrar ``audit_logs`` (alineado con lo persistido).""" - row = ( - db.query(Company.tenant_id) - .filter(Company.id == company_id, Company.deleted_at.is_(None)) - .first() - ) - if not row: - raise HTTPException(status_code=404, detail="Company not found") - return int(row[0]) - - -_SEGMENT_LABELS = { - "tenants": "Espacio", - "companies": "Companias", - "users": "Usuarios", - "imports": "Importaciones", - "csv": "Archivos CSV", - "branding": "Logotipos", - "certificates": "Certificados", - "customs_brokers": "Agentes aduanales", - "keys": "Llaves", - "cove": "COVE", - "doda": "DODA", - "system": "Sistema", - "help": "Ayuda", -} - - -def _normalize_relative_path(raw: Optional[str]) -> str: - if not raw: - return "" - val = raw.strip().strip("/") - if not val: - return "" - if ".." in val or "\\" in val: - raise HTTPException(status_code=400, detail="Invalid path") - parts = [p for p in val.split("/") if p] - for part in parts: - if part in (".", ".."): - raise HTTPException(status_code=400, detail="Invalid path segment") - return "/".join(parts) - - -def _tenant_prefix(tenant_id: int) -> str: - return f"tenants/{tenant_id}/" - - -def _relative_from_tenant_prefix(key: str, tenant_prefix: str) -> str: - if not key.startswith(tenant_prefix): - raise HTTPException(status_code=403, detail="Access denied to object key") - return key[len(tenant_prefix) :].strip("/") - - -def _companies_map(db: Session, tenant_id: int) -> Dict[str, str]: - rows = ( - db.query(Company.id, Company.name) - .filter(Company.tenant_id == tenant_id, Company.deleted_at.is_(None)) - .all() - ) - out: Dict[str, str] = {} - for company_id, company_name in rows: - if company_id is None: - continue - safe_name = (company_name or "").strip() - out[str(company_id)] = safe_name or "Compania" - return out - - -def _display_segment( - part: str, - prev_part: Optional[str], - company_names: Dict[str, str], - current_user_id: Optional[str] = None, - current_user_label: Optional[str] = None, -) -> str: - if prev_part == "companies": - return company_names.get(part, "Compania") - if prev_part == "users": - # Para carpetas de usuarios, mostrar un nombre amigable: - # - Si es el propio usuario actual, usar preferred_username/email/nombre. - # - Para otros IDs (UUIDs) mostrar un label genérico. - if current_user_id and part == str(current_user_id): - return (current_user_label or "").strip() or "Usuario" - return "Usuario" - if part in _SEGMENT_LABELS: - return _SEGMENT_LABELS[part] - # Evita exponer IDs puros en UI. - if part.isdigit(): - return "Elemento" - return part.replace("_", " ").strip().title() or "Elemento" - - -def _display_path( - rel_path: str, - company_names: Dict[str, str], - current_user_id: Optional[str] = None, - current_user_label: Optional[str] = None, -) -> str: - if not rel_path: - return "Raiz de archivos" - parts = [p for p in rel_path.split("/") if p] - labels: List[str] = [] - prev: Optional[str] = None - for part in parts: - labels.append( - _display_segment( - part, - prev, - company_names, - current_user_id=current_user_id, - current_user_label=current_user_label, - ) - ) - prev = part - return " / ".join(labels) - - -def _display_file_name(filename: str) -> str: - stem, dot, ext = filename.rpartition(".") - if not dot: - stem = filename - ext = "" - if stem.isdigit(): - return f"Archivo{f'.{ext}' if ext else ''}" - return filename - - -def _build_breadcrumbs( - rel_path: str, - company_names: Dict[str, str], - current_user_id: Optional[str] = None, - current_user_label: Optional[str] = None, -) -> List[AuditFileBreadcrumb]: - breadcrumbs: List[AuditFileBreadcrumb] = [ - AuditFileBreadcrumb(path="", display_name="Raiz de archivos") - ] - if not rel_path: - return breadcrumbs - parts = [p for p in rel_path.split("/") if p] - prev: Optional[str] = None - acc: List[str] = [] - for part in parts: - acc.append(part) - breadcrumbs.append( - AuditFileBreadcrumb( - path="/".join(acc), - display_name=_display_segment( - part, - prev, - company_names, - current_user_id=current_user_id, - current_user_label=current_user_label, - ), - ) - ) - prev = part - return breadcrumbs - -@router.get("/bitacora", response_model=AuditLogListResponse) -async def get_bitacora( - company_id: int = Query(..., description="Company ID"), - page: int = Query(1, ge=1), - page_size: int = Query(50, ge=1, le=100), - search: Optional[str] = None, - username: Optional[str] = None, - procedure: Optional[str] = None, - reference: Optional[str] = None, - date_from: Optional[date] = None, - date_to: Optional[date] = None, - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """ - Bitácora por compañía. Requiere permiso ``audit_logs.view``. - """ - validate_access_to_resource( - db, - company_id, - current_user, - required_permissions=["audit_logs.view"], - ) - scope_tenant_id = _audit_scope_tenant_id(db, company_id) - set_rls_context(db, tenant_id=scope_tenant_id, company_id=company_id) - - query = db.query(AuditLog).filter( - AuditLog.company_id == company_id, - AuditLog.tenant_id == scope_tenant_id, - ) - - # Filters - if date_from: - query = query.filter(AuditLog.date >= date_from) - if date_to: - query = query.filter(AuditLog.date <= date_to) - - if username: - query = query.filter(AuditLog.username.ilike(f"%{username}%")) - if procedure: - # Exact match for dropdown filter usually better, but let's allow partial if manual - # Legacy UI sends exact strings usually - query = query.filter(AuditLog.procedure == procedure) - if reference: - query = query.filter(AuditLog.reference.ilike(f"%{reference}%")) - - if search: - # General search across main columns - search_filter = or_( - AuditLog.reference.ilike(f"%{search}%"), - AuditLog.procedure.ilike(f"%{search}%"), - AuditLog.movement.ilike(f"%{search}%"), - AuditLog.username.ilike(f"%{search}%") - ) - query = query.filter(search_filter) - - total = query.count() - - # Sort by ID desc (newest first) -> Legacy usually shows newest first or spec_id desc - logs = query.order_by(desc(AuditLog.spec_id))\ - .offset((page - 1) * page_size)\ - .limit(page_size)\ - .all() - - return { - "data": logs, - "total": total, - "page": page, - "page_size": page_size - } - -@router.get("/bitacora/procedimientos", response_model=List[str]) -async def get_procedures( - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """ - Lista de procedimientos para filtros (alcance compañía). Requiere ``audit_logs.view``. - """ - validate_access_to_resource( - db, - company_id, - current_user, - required_permissions=["audit_logs.view"], - ) - scope_tenant_id = _audit_scope_tenant_id(db, company_id) - set_rls_context(db, tenant_id=scope_tenant_id, company_id=company_id) - - results = ( - db.query(AuditLog.procedure) - .filter( - AuditLog.company_id == company_id, - AuditLog.tenant_id == scope_tenant_id, - ) - .distinct() - .order_by(AuditLog.procedure) - .all() - ) - return [r[0] for r in results if r[0]] - -@router.get("/bitacora/{spec_id}/detalle", response_model=AuditLogDetailResponse) -async def get_audit_detail( - spec_id: int, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """ - Detalle de un registro de bitácora. Requiere ``audit_logs.view``. - """ - validate_access_to_resource( - db, - company_id, - current_user, - required_permissions=["audit_logs.view"], - ) - scope_tenant_id = _audit_scope_tenant_id(db, company_id) - set_rls_context(db, tenant_id=scope_tenant_id, company_id=company_id) - - log = ( - db.query(AuditLog) - .filter( - AuditLog.spec_id == spec_id, - AuditLog.company_id == company_id, - AuditLog.tenant_id == scope_tenant_id, - ) - .first() - ) - if not log: - raise HTTPException(status_code=404, detail="Log entry not found") - return log - - -@router.get("/files", response_model=AuditFileBrowserResponse) -async def list_tenant_files( - company_id: int = Query(..., description="Company ID"), - path: Optional[str] = Query(default="", description="Ruta relativa de navegación."), - continuation_token: Optional[str] = Query(default=None), - max_keys: int = Query(default=100, ge=1, le=500), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """ - Explorador de archivos de solo lectura para Auditoría. - Requiere permiso ``audit_logs.view``; el prefijo S3 sigue al tenant de la compañía. - """ - if not should_ensure_s3_bucket(): - raise HTTPException(status_code=400, detail="S3 storage is disabled") - - validate_access_to_resource( - db, - company_id, - current_user, - required_permissions=["audit_logs.view"], - ) - scope_tenant_id = _audit_scope_tenant_id(db, company_id) - set_rls_context(db, tenant_id=scope_tenant_id, company_id=company_id) - tenant_prefix = _tenant_prefix(scope_tenant_id) - rel_path = _normalize_relative_path(path) - list_prefix = f"{tenant_prefix}{rel_path}/" if rel_path else tenant_prefix - - # Datos del usuario actual para etiquetas amigables bajo /users/{id}/... - current_user_id = str(current_user.get("sub") or "") - current_user_label = ( - (current_user.get("preferred_username") or "").strip() - or (current_user.get("name") or "").strip() - or (current_user.get("email") or "").strip() - or "Usuario" - ) - - data = list_objects_tree( - prefix=list_prefix, - delimiter="/", - max_keys=max_keys, - continuation_token=continuation_token, - ) - - company_names = _companies_map(db, scope_tenant_id) - folders: List[AuditFileFolderItem] = [] - for prefix in data.get("prefixes", []): - rel = _relative_from_tenant_prefix(prefix, tenant_prefix) - folders.append( - AuditFileFolderItem( - path=rel, - # Para el gestor de archivos mostramos el nombre amigable del último segmento - # (empresa, usuario actual, etc.), no el ID bruto. - display_name=_display_path( - rel, - company_names, - current_user_id=current_user_id, - current_user_label=current_user_label, - ).split(" / ")[-1], - ) - ) - - files: List[AuditFileObjectItem] = [] - for obj in data.get("objects", []): - key = obj.get("key") - if not key: - continue - rel = _relative_from_tenant_prefix(key, tenant_prefix) - name = rel.rsplit("/", 1)[-1] - files.append( - AuditFileObjectItem( - path=rel, - display_name=_display_file_name(name), - size=int(obj.get("size", 0) or 0), - last_modified=obj.get("last_modified"), - ) - ) - - return AuditFileBrowserResponse( - current_path=rel_path, - display_path=_display_path( - rel_path, - company_names, - current_user_id=current_user_id, - current_user_label=current_user_label, - ), - breadcrumbs=_build_breadcrumbs( - rel_path, - company_names, - current_user_id=current_user_id, - current_user_label=current_user_label, - ), - folders=sorted(folders, key=lambda x: x.display_name.lower()), - files=sorted(files, key=lambda x: x.display_name.lower()), - next_token=data.get("next_continuation_token"), - ) - - -@router.get("/files/download") -async def download_tenant_file( - company_id: int = Query(..., description="Company ID"), - path: str = Query(..., description="Ruta relativa del archivo a descargar."), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """ - Descarga segura (backend streaming) de archivos autorizados. - Requiere ``audit_logs.view``. - """ - if not should_ensure_s3_bucket(): - raise HTTPException(status_code=400, detail="S3 storage is disabled") - - validate_access_to_resource( - db, - company_id, - current_user, - required_permissions=["audit_logs.view"], - ) - scope_tenant_id = _audit_scope_tenant_id(db, company_id) - set_rls_context(db, tenant_id=scope_tenant_id, company_id=company_id) - tenant_prefix = _tenant_prefix(scope_tenant_id) - rel_path = _normalize_relative_path(path) - if not rel_path or rel_path.endswith("/"): - raise HTTPException(status_code=400, detail="A file path is required") - - object_key = f"{tenant_prefix}{rel_path}" - if not object_key.startswith(tenant_prefix): - raise HTTPException(status_code=403, detail="Access denied to object key") - - try: - body = get_object_bytes(object_key) - except Exception as e: - raise HTTPException(status_code=404, detail=f"File not found: {e}") from e - - filename = rel_path.rsplit("/", 1)[-1] - headers = {"Content-Disposition": f'attachment; filename="{filename}"'} - return StreamingResponse( - iter([body]), - media_type="application/octet-stream", - headers=headers, - ) diff --git a/backend/api/v1/modules/a76/audit_log/schemas.py b/backend/api/v1/modules/a76/audit_log/schemas.py deleted file mode 100644 index ae1ad0e0..00000000 --- a/backend/api/v1/modules/a76/audit_log/schemas.py +++ /dev/null @@ -1,78 +0,0 @@ -""" -Audit Log Schemas -""" - -from typing import Optional, List, Any, Dict -from datetime import date as date_type, time as time_type, datetime -from pydantic import BaseModel, Field - -# --- Response Schemas --- - -class AuditLogResponse(BaseModel): - """ - Standard response showing the Legacy columns - """ - spec_id: int - reference: str - procedure: str - movement: str - username: str - date: date_type - time: time_type - - # Modern extras - timestamp: datetime - system: str - operation_type: Optional[str] = None - table_name: Optional[str] = None - record_id: Optional[str] = None - - class Config: - from_attributes = True - -class AuditLogDetailResponse(AuditLogResponse): - """ - Detailed response including changed values - """ - old_values: Optional[Dict[str, Any]] = None - new_values: Optional[Dict[str, Any]] = None - changed_fields: Optional[List[str]] = None - ip_address: Optional[str] = None - execution_time_ms: Optional[int] = None - -# --- List Response --- - -class AuditLogListResponse(BaseModel): - """ - Paginated response - """ - data: List[AuditLogResponse] - total: int - page: int - page_size: int - - -class AuditFileBreadcrumb(BaseModel): - path: str = Field(default="") - display_name: str - - -class AuditFileFolderItem(BaseModel): - path: str = Field(description="Ruta relativa interna del archivo, para navegación.") - display_name: str - - -class AuditFileObjectItem(BaseModel): - path: str = Field(description="Ruta relativa interna del archivo, para descarga.") - display_name: str - size: int - last_modified: Optional[datetime] = None - - -class AuditFileBrowserResponse(BaseModel): - current_path: str = Field(default="") - display_path: str = Field(default="") - breadcrumbs: List[AuditFileBreadcrumb] - folders: List[AuditFileFolderItem] - files: List[AuditFileObjectItem] - next_token: Optional[str] = None diff --git a/backend/api/v1/modules/a76/audit_log/services/core.py b/backend/api/v1/modules/a76/audit_log/services/core.py deleted file mode 100644 index 9fc1e1de..00000000 --- a/backend/api/v1/modules/a76/audit_log/services/core.py +++ /dev/null @@ -1,209 +0,0 @@ -""" -Audit Log Core Logic: Reference Generation and Mapping -""" -from typing import Optional, Dict, Any, Tuple - -class ReferenceGenerator: - """ - Generates legacy-style references (e.g., FUSE0-040-10) - """ - - @staticmethod - def generate_invoice_reference(invoice_data: Dict[str, Any]) -> str: - """ - Format: {SYSTEM}-{CUSTOMS}-{YEAR} - Example: FUSE0-040-10 - """ - # Default values - system = "FUSE0" - customs = "000" - year = "00" - - # Try to extract system (invoice_type usually holds this key) - if invoice_data.get("invoice_type"): - system = str(invoice_data["invoice_type"]) - - # Try to extract customs (need to look into nested compliance_mx if available, or just use default) - # Since this receives a dictionary from the mapper, we might not have deep nested relations resolved - # We'll try to do our best with available data - - # Try to get year from invoice_date - if invoice_data.get("invoice_date"): - try: - # invoice_date can be a date object or string - d = invoice_data["invoice_date"] - if hasattr(d, "year"): - y = d.year - else: - # Assume string YYYY-MM-DD - y = int(str(d)[:4]) - year = str(y)[-2:] - except: - pass - - return f"{system}-{customs}-{year}" - - @staticmethod - def generate_invoice_item_reference(invoice_ref: str, item_data: Dict[str, Any]) -> str: - """ - Format: {INVOICE_REF}-{ITEM_PART} - Example: FUSE0-040-10-FUS035 - """ - part_number = item_data.get("part_number", "ITEM") - return f"{invoice_ref}-{part_number}" - - @staticmethod - def generate_pedimento_reference(pedimento_data: Dict[str, Any]) -> str: - """ - Format: {LICENSE}-{CUSTOMS}{YEAR}{NUMBER} - Example: 0756C-040010315 - """ - license = str(pedimento_data.get("license", "0000")).strip() - customs = str(pedimento_data.get("customs_office", "000")).zfill(3) - year = str(pedimento_data.get("year", "00")).zfill(2) - number = str(pedimento_data.get("pedimento_number", "0000000")).zfill(7) - - return f"{license}-{customs}{year}{number}" - - -class AuditMapper: - """ - Maps table names and operations to English Procedures and Movements - """ - - # Map table names to Legacy Procedures (English) - TABLE_TO_PROCEDURE = { - # Invoices - "invoice_header": "IMPORT INVOICE BROWSE", # BROWSEOFACIMP - "invoice_sales_details": "IMPORT INVOICE UPDATE", # UPDATEOFACIMP - "invoice_compliance_mx": "IMPORT INVOICE UPDATE", - "invoice_financials": "IMPORT INVOICE UPDATE", - "invoice_logistics": "IMPORT INVOICE UPDATE", - "invoice_collections": "IMPORT INVOICE UPDATE", - - # Exports would be similar but we start with general - - # Pedimentos - "pedimentos": "PEDIMENTO BROWSE", # BROWSEPEDIMEN - - # System - "users": "SYSTEM SCAF", - "sessions": "SYSTEM SCAF", - - # General fallbacks - "clients_and_providers": "CATALOGS", - "items": "CATALOGS", - "parts": "CATALOGS", - "customs_brokers": "CATALOGS", - "customs_brokers_vu": "CATALOGS", - "customs_brokers_personnel": "CATALOGS", - "classes": "CATALOGS", - "classification_concepts": "CATALOGS", - "concepts": "CATALOGS", - "customs_broker_concepts": "CATALOGS", - "depreciation_catalog": "CATALOGS", - "electronic_notices": "ELECTRONIC NOTICES", - "equivalencies": "CATALOGS", - "error_catalogs": "CATALOGS", - "fda_catalog": "CATALOGS", - "inpc": "CATALOGS", - "legends": "CATALOGS", - "multi_currency_types": "CATALOGS", - "packages": "CATALOGS", - "ports": "CATALOGS", - "prevalidators": "CATALOGS", - "seal": "CATALOGS", - "seals": "CATALOGS", - "signatures": "CATALOGS", - "tariff_fractions": "CATALOGS", - "unit_conversions": "CATALOGS", - "us_tariff_fractions": "CATALOGS", - - # DODA - "doda": "DODA", - "doda_containers": "DODA", - "doda_pedimentos": "DODA", - } - - # Map (Table, Operation) to Legacy Movements (English) - OPERATION_TO_MOVEMENT = { - ("invoice_header", "CREATE"): "ADD IMPORT_INVOICE", - ("invoice_header", "UPDATE"): "EDIT IMPORT_INVOICE", - ("invoice_header", "DELETE"): "DELETE IMPORT_INVOICE", - - ("invoice_sales_details", "CREATE"): "ADD IMPORT_INVOICE_ITEM", - ("invoice_sales_details", "UPDATE"): "EDIT IMPORT_INVOICE_ITEM", - ("invoice_sales_details", "DELETE"): "DELETE IMPORT_INVOICE_ITEM", - ("invoice_compliance_mx", "CREATE"): "ADD IMPORT_INVOICE", - ("invoice_compliance_mx", "UPDATE"): "EDIT IMPORT_INVOICE", - ("invoice_compliance_mx", "DELETE"): "DELETE IMPORT_INVOICE", - ("invoice_financials", "CREATE"): "ADD IMPORT_INVOICE", - ("invoice_financials", "UPDATE"): "EDIT IMPORT_INVOICE", - ("invoice_financials", "DELETE"): "DELETE IMPORT_INVOICE", - ("invoice_logistics", "CREATE"): "ADD IMPORT_INVOICE", - ("invoice_logistics", "UPDATE"): "EDIT IMPORT_INVOICE", - ("invoice_logistics", "DELETE"): "DELETE IMPORT_INVOICE", - ("invoice_collections", "CREATE"): "ADD IMPORT_INVOICE", - ("invoice_collections", "UPDATE"): "EDIT IMPORT_INVOICE", - ("invoice_collections", "DELETE"): "DELETE IMPORT_INVOICE", - - ("pedimentos", "CREATE"): "ADD PEDIMENTO", - ("pedimentos", "UPDATE"): "EDIT PEDIMENTO", - ("pedimentos", "DELETE"): "DELETE PEDIMENTO", - - ("auth", "LOGIN"): "SYSTEM LOGIN", - ("auth", "LOGOUT"): "SYSTEM LOGOUT", - - ("classes", "CREATE"): "ADD CLASS", - ("classes", "UPDATE"): "EDIT CLASS", - ("classes", "DELETE"): "DELETE CLASS", - - ("doda", "CREATE"): "ADD DODA", - ("doda", "UPDATE"): "EDIT DODA", - ("doda", "DELETE"): "DELETE DODA", - - ("company", "CREATE"): "ADD COMPANY", - ("company", "UPDATE"): "EDIT COMPANY", - ("company", "DELETE"): "DELETE COMPANY", - ("companies", "CREATE"): "ADD COMPANY", - ("companies", "UPDATE"): "EDIT COMPANY", - ("companies", "DELETE"): "DELETE COMPANY", - } - - @staticmethod - def map_to_legacy_format( - table_name: str, - record_id: str, - operation_type: str, - username: str, - system: str = "SCAF" - ) -> Dict[str, Any]: - """ - Returns dictionary with keys: reference, procedure, movement, username, system - """ - # Determine Procedure - procedure = AuditMapper.TABLE_TO_PROCEDURE.get( - table_name, - table_name.upper().replace("_", " ") # Fallback - ) - - # Determine Movement - movement_key = (table_name, operation_type) - movement = AuditMapper.OPERATION_TO_MOVEMENT.get( - movement_key, - f"{operation_type} {table_name.upper()}" - ) - - # Determine Reference Base - if operation_type in ["LOGIN", "LOGOUT"]: - reference = operation_type - else: - reference = record_id or "NO-REF" - - return { - "reference": reference, - "procedure": procedure, - "movement": movement, - "username": username, - "system": system - } diff --git a/backend/api/v1/modules/a76/audit_log/services/service.py b/backend/api/v1/modules/a76/audit_log/services/service.py deleted file mode 100644 index 2d834aa2..00000000 --- a/backend/api/v1/modules/a76/audit_log/services/service.py +++ /dev/null @@ -1,261 +0,0 @@ -""" -Audit Log Service -""" -import logging -from datetime import datetime, timedelta, date, time -from decimal import Decimal -import uuid -import pytz -from typing import Optional, List, Dict, Any -from sqlalchemy.orm import Session -from ..models import AuditLog -from .core import AuditMapper, ReferenceGenerator - -logger = logging.getLogger(__name__) - - -def _make_json_safe(obj: Any) -> Any: - """Recursively convert non-JSON-serializable types to serializable equivalents.""" - if obj is None: - return None - if isinstance(obj, dict): - return {k: _make_json_safe(v) for k, v in obj.items()} - if isinstance(obj, (list, tuple)): - return [_make_json_safe(v) for v in obj] - if isinstance(obj, datetime): - return obj.isoformat() - if isinstance(obj, date): - return obj.isoformat() - if isinstance(obj, time): - return obj.isoformat() - if isinstance(obj, Decimal): - return float(obj) - if isinstance(obj, uuid.UUID): - return str(obj) - if isinstance(obj, bytes): - return obj.decode("utf-8", errors="replace") - - if hasattr(obj, "__dict__"): - d = dict(obj.__dict__) - d.pop("_sa_instance_state", None) - return {k: _make_json_safe(v) for k, v in d.items()} - - if not isinstance(obj, (int, float, str, bool)): - return str(obj) - - return obj - - -class AuditService: - - @staticmethod - def create_audit_log( - db: Session, - reference: str, - procedure: str, - movement: str, - username: str, - system: str = "SCAF", - # Extra context - table_name: Optional[str] = None, - record_id: Optional[str] = None, - operation_type: Optional[str] = None, - old_values: Optional[Dict] = None, - new_values: Optional[Dict] = None, - changed_fields: Optional[List[str]] = None, - # HTTP Context - ip_address: Optional[str] = None, - user_agent: Optional[str] = None, - endpoint: Optional[str] = None, - request_method: Optional[str] = None, - session_id: Optional[str] = None, - company_id: Optional[int] = None, - tenant_id: Optional[int] = None, - ) -> AuditLog: - """ - Low-level creation of an Audit Log entry - """ - # Timezone handling: Use UTC for consistency across regions. - # Frontend will convert to user's local time. - now = datetime.now(pytz.UTC) - - log = AuditLog( - reference=reference, - procedure=procedure, - movement=movement, - username=username, - date=now.date(), - time=now.time(), - timestamp=now, - system=system, - table_name=table_name, - record_id=record_id, - operation_type=operation_type, - old_values=_make_json_safe(old_values), - new_values=_make_json_safe(new_values), - changed_fields=changed_fields, - ip_address=ip_address, - user_agent=user_agent, - endpoint=endpoint, - request_method=request_method, - session_id=session_id, - company_id=company_id, - tenant_id=tenant_id - ) - - db.add(log) - db.flush() - return log - - @staticmethod - def log_crud_operation( - db: Session, - table_name: str, - operation_type: str, - record_data: Dict[str, Any], - username: str, - record_id: Optional[str] = None, - old_values: Optional[Dict] = None, - new_values: Optional[Dict] = None, - # Context - ip_address: Optional[str] = None, - user_agent: Optional[str] = None, - company_id: Optional[int] = None, - tenant_id: Optional[int] = None - ): - """ - High-level wrapper to log CRUD operations automatically mapping to Legacy format - """ - - # 1. Map to Legacy Base Format - legacy_data = AuditMapper.map_to_legacy_format( - table_name=table_name, - record_id=record_id, - operation_type=operation_type, - username=username - ) - - # 2. Refine Reference based on specific table logic - reference = legacy_data["reference"] - - if table_name == "invoice_header": - generated_ref = ReferenceGenerator.generate_invoice_reference(record_data) - # Use generated ref only if meaningful, else keep default - if generated_ref != "FUSE0-000-00": - reference = generated_ref - - elif table_name == "pedimentos": - reference = ReferenceGenerator.generate_pedimento_reference(record_data) - - elif table_name == "clients_and_providers": - reference = record_data.get("rfc") or reference - - elif table_name == "parts": - reference = record_data.get("part_number") or reference - - elif table_name in {"companies", "company"}: - reference = record_data.get("rfc") or reference - - elif table_name == "classes": - reference = record_data.get("class_code") or reference - - - # 3. Detect Changed Fields (for Update) - changed_fields = None - if operation_type == "UPDATE" and old_values and new_values: - changed_fields = [ - k for k in new_values.keys() - if old_values.get(k) != new_values.get(k) - ] - - # 4. Create Log - return AuditService.create_audit_log( - db=db, - reference=reference, - procedure=legacy_data["procedure"], - movement=legacy_data["movement"], - username=username, - system=legacy_data["system"], - table_name=table_name, - record_id=record_id, - operation_type=operation_type, - old_values=old_values, - new_values=new_values, - changed_fields=changed_fields, - ip_address=ip_address, - user_agent=user_agent, - company_id=company_id, - tenant_id=tenant_id - ) - - @staticmethod - def log_login( - db: Session, - username: str, - ip_address: str = None, - user_agent: str = None, - company_id: Optional[int] = None, - tenant_id: Optional[int] = None, - ): - """ - ``audit_logs`` exige ``tenant_id`` y ``company_id``. El login vía Hub no - define compañía activa; sin ambos argumentos no se inserta fila (antes fallaba NOT NULL). - """ - if company_id is None or tenant_id is None: - return None - - try: - now = datetime.now(pytz.UTC) - five_seconds_ago = now - timedelta(seconds=5) - existing = db.query(AuditLog).filter( - AuditLog.username == username, - AuditLog.operation_type == "LOGIN", - AuditLog.company_id == company_id, - AuditLog.tenant_id == tenant_id, - AuditLog.timestamp >= five_seconds_ago, - ).first() - if existing: - return existing - except Exception as e: - logger.warning("Login audit debounce query failed: %s", e) - - return AuditService.create_audit_log( - db=db, - reference="LOGIN", - procedure="SYSTEM SCAF", - movement="SYSTEM LOGIN", - username=username, - operation_type="LOGIN", - ip_address=ip_address, - user_agent=user_agent, - company_id=company_id, - tenant_id=tenant_id, - ) - - @staticmethod - def log_logout( - db: Session, - username: str, - ip_address: str = None, - user_agent: str = None, - company_id: Optional[int] = None, - tenant_id: Optional[int] = None, - ): - """Misma condición que ``log_login``: sin alcance compañía/tenant no se escribe.""" - if company_id is None or tenant_id is None: - return None - try: - AuditService.create_audit_log( - db=db, - reference="LOGOUT", - procedure="SYSTEM AUTH", - movement="SYSTEM LOGOUT", - username=username, - operation_type="LOGOUT", - ip_address=ip_address, - user_agent=user_agent, - company_id=company_id, - tenant_id=tenant_id, - ) - except Exception as e: - logger.warning("Logout audit insert failed: %s", e) diff --git a/backend/api/v1/modules/a76/audit_log/utils/__init__.py b/backend/api/v1/modules/a76/audit_log/utils/__init__.py deleted file mode 100644 index 57f53044..00000000 --- a/backend/api/v1/modules/a76/audit_log/utils/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -""" -Audit Log Utilities -""" - -from .serialization import serialize_for_json - -__all__ = ["serialize_for_json"] diff --git a/backend/api/v1/modules/a76/audit_log/utils/serialization.py b/backend/api/v1/modules/a76/audit_log/utils/serialization.py deleted file mode 100644 index dedfdc19..00000000 --- a/backend/api/v1/modules/a76/audit_log/utils/serialization.py +++ /dev/null @@ -1,50 +0,0 @@ -""" -Serialization utilities for audit logs -""" - -from datetime import date, datetime, time -from decimal import Decimal -from uuid import UUID -from typing import Any, Dict - - -def serialize_value(value: Any) -> Any: - """ - Convert a Python value to a JSON-serializable type - """ - if value is None: - return None - elif isinstance(value, (date, datetime)): - return value.isoformat() - elif isinstance(value, time): - return value.isoformat() - elif isinstance(value, Decimal): - return float(value) - elif isinstance(value, UUID): - return str(value) - elif isinstance(value, bytes): - return value.decode("utf-8", errors="replace") - elif isinstance(value, (list, tuple)): - return [serialize_value(item) for item in value] - elif isinstance(value, dict): - return {key: serialize_value(val) for key, val in value.items()} - else: - if hasattr(value, "__dict__"): - d = dict(value.__dict__) - d.pop("_sa_instance_state", None) - return {k: serialize_value(v) for k, v in d.items()} - - if not isinstance(value, (int, float, str, bool)): - return str(value) - - return value - - -def serialize_for_json(data: Dict[str, Any]) -> Dict[str, Any]: - """ - Recursively serialize a dictionary for JSON storage - """ - if not data: - return data - - return {key: serialize_value(value) for key, value in data.items()} diff --git a/backend/api/v1/modules/a76/boms/__init__.py b/backend/api/v1/modules/a76/boms/__init__.py deleted file mode 100644 index ea1f7540..00000000 --- a/backend/api/v1/modules/a76/boms/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -""" -Módulo de importación CSV para BOMs (Bills of Materials). -""" - -from .routes import router - -__all__ = ["router"] diff --git a/backend/api/v1/modules/a76/boms/routes.py b/backend/api/v1/modules/a76/boms/routes.py deleted file mode 100644 index 98f5a529..00000000 --- a/backend/api/v1/modules/a76/boms/routes.py +++ /dev/null @@ -1,13 +0,0 @@ -""" -Endpoints para importación CSV de BOMs. -Mismo patrón que parts y classes: upload → scan → status → commit. -""" - -from fastapi import APIRouter - -from api.v1.modules.a76.layouts_csv.boms.routes import router as imports_router - -router = APIRouter() - -# CSV import (upload → scan → status → commit) -router.include_router(imports_router, prefix="/imports", tags=["a76 / boms / csv_import"]) diff --git a/backend/api/v1/modules/a76/classes/__init__.py b/backend/api/v1/modules/a76/classes/__init__.py deleted file mode 100644 index fa36b353..00000000 --- a/backend/api/v1/modules/a76/classes/__init__.py +++ /dev/null @@ -1,21 +0,0 @@ -""" -Módulo de Class -""" - -from typing import Any - -__all__ = ["router"] - - -def __getattr__(name: str) -> Any: - """ - Lazy export to avoid circular imports during Celery init. - - This package is imported when models are loaded (e.g. a76.items.models), - so importing FastAPI routes at import-time can break Celery startup. - """ - if name == "router": - from .routes import router - - return router - raise AttributeError(f"module {__name__!r} has no attribute {name!r}") diff --git a/backend/api/v1/modules/a76/classes/dto.py b/backend/api/v1/modules/a76/classes/dto.py deleted file mode 100644 index 39ffdd7d..00000000 --- a/backend/api/v1/modules/a76/classes/dto.py +++ /dev/null @@ -1,276 +0,0 @@ -""" -DTOs (Data Transfer Objects) para módulo de clases SCAII y SCAF -Reemplaza schemas.py siguiendo enfoque DDD y estilo NestJS -""" - -from datetime import datetime -from decimal import Decimal -from typing import Optional - -from pydantic import BaseModel, ConfigDict, Field - - -class ClassCreateDTO(BaseModel): - """DTO para crear una clase""" - - class_code: str = Field(..., max_length=8, description="Class code") - description_es: str = Field( - ..., max_length=500, description="Description in Spanish (required)" - ) - description_en: Optional[str] = Field( - None, max_length=500, description="Description in English" - ) - material_key: str = Field( - ..., - max_length=10, - description="Material key - Fixed Asset Type (required)", - ) - unit_of_measure: str = Field( - ..., max_length=5, description="Unit of measure - U.M. comercial (required)" - ) - stock_unit_of_measure: Optional[str] = Field( - None, - max_length=5, - description="UM Existencia - solo SCAII/inventory (UMEXISTENCIA)", - ) - fraction: str = Field( - ..., max_length=20, description="Mexican tariff fraction (required)" - ) - us_fraction: Optional[str] = Field( - None, max_length=16, description="US tariff fraction" - ) - sub_key: Optional[str] = Field( - None, max_length=5, description="Sub classification key" - ) - physical_review: Optional[int] = Field( - None, description="Physical review indicator" - ) - iva_exempt_fraction: Optional[str] = Field( - None, max_length=4, description="IVA exempt fraction" - ) - is_active: Optional[bool] = Field( - True, description="Indicates if the class is active (default: true)" - ) - system: str = Field( - default="fixed_asset", max_length=12, - description="Sistema: 'fixed_asset' (SCAF) o 'inventory' (SCAII)" - ) - - class Config: - from_attributes = True - - -class ClassCreateDTOFA(ClassCreateDTO): - """DTO para crear una clase de activo fijo (clase base + extensión FA)""" - - # Campos específicos de activos fijos (a24.fa_classes) - import_tariff_code: Optional[str] = Field( - None, max_length=10, description="Código de fracción de importación" - ) - import_tariff_type: Optional[str] = Field( - None, max_length=6, description="Tipo de fracción de importación" - ) - export_tariff_code: Optional[str] = Field( - None, max_length=10, description="Código de fracción de exportación" - ) - export_tariff_type: Optional[str] = Field( - None, max_length=6, description="Tipo de fracción de exportación" - ) - depreciation_rate: Optional[Decimal] = Field( - None, ge=0, le=100, description="Tasa de depreciación anual (%)" - ) - fda_code: Optional[str] = Field( - None, max_length=20, description="Código FDA" - ) - eccn_code: Optional[str] = Field( - None, max_length=20, description="Código ECCN" - ) - class_enabled: Optional[bool] = Field( - True, description="Indica si la clase está habilitada" - ) - is_active: Optional[bool] = Field( - True, description="Indicates if the class is active (default: true)" - ) - - class Config: - from_attributes = True - - -class ClassUpdateDTO(BaseModel): - """DTO para actualizar una clase""" - - class_code: Optional[str] = Field( - None, max_length=8, description="Class code" - ) - description_es: Optional[str] = Field( - None, max_length=500, description="Description in Spanish" - ) - description_en: Optional[str] = Field( - None, max_length=500, description="Description in English" - ) - material_key: Optional[str] = Field( - None, - max_length=10, - description="Material key (homologated TIPOMAT/TIPOMATEQUIPO)", - ) - unit_of_measure: Optional[str] = Field( - None, max_length=5, description="Unit of measure (homologated UNIMEDIDA)" - ) - stock_unit_of_measure: Optional[str] = Field( - None, - max_length=5, - description="UM Existencia - solo SCAII/inventory (UMEXISTENCIA)", - ) - fraction: Optional[str] = Field( - None, max_length=20, description="Mexican tariff fraction" - ) - us_fraction: Optional[str] = Field( - None, max_length=16, description="US tariff fraction" - ) - sub_key: Optional[str] = Field( - None, max_length=5, description="Sub classification key" - ) - physical_review: Optional[int] = Field( - None, description="Physical review indicator" - ) - iva_exempt_fraction: Optional[str] = Field( - None, max_length=4, description="IVA exempt fraction" - ) - is_active: Optional[bool] = Field( - True, description="Indicates if the class is active (default: true)" - ) - system: Optional[str] = Field( - None, max_length=12, - description="Sistema: 'fixed_asset' (SCAF) o 'inventory' (SCAII)" - ) - - model_config = ConfigDict(from_attributes=True, extra='forbid') # Explicitly forbid extra fields - - -class ClassResponseDTO(BaseModel): - """DTO para respuesta de clase""" - - id: int - tenant_id: int - company_id: int - class_code: str - description_es: Optional[str] = None - description_en: Optional[str] = None - material_key: Optional[str] = None - unit_of_measure: Optional[str] = None - stock_unit_of_measure: Optional[str] = None - fraction: Optional[str] = None - us_fraction: Optional[str] = None - sub_key: Optional[str] = None - physical_review: Optional[int] = None - iva_exempt_fraction: Optional[str] = None - is_active: Optional[bool] = None - system: str = "fixed_asset" - created_at: datetime - updated_at: datetime - - model_config = ConfigDict(from_attributes=True) - - -class ClassResponseDTOFA(ClassResponseDTO): - """DTO para respuesta de clase de activo fijo (incluye campos FA)""" - - # Campos de a24.fa_classes - fa_id: Optional[int] = None - import_tariff_code: Optional[str] = None - import_tariff_type: Optional[str] = None - export_tariff_code: Optional[str] = None - export_tariff_type: Optional[str] = None - depreciation_rate: Optional[Decimal] = None - fda_code: Optional[str] = None - eccn_code: Optional[str] = None - class_enabled: Optional[bool] = None - - model_config = ConfigDict(from_attributes=True) - - -class ClassBasicDTO(BaseModel): - """DTO para información básica de clase""" - - class_code: str - description_es: Optional[str] = None - description_en: Optional[str] = None - material_key: Optional[str] = None - fraction: Optional[str] = None - - class Config: - from_attributes = True - - -class ClassListDTO(BaseModel): - """DTO para lista de clases""" - - classes: list[ClassBasicDTO] - total: int - page: int - size: int - - class Config: - from_attributes = True - - -class ClassSearchDTO(BaseModel): - """DTO para búsqueda de clases""" - - class_code: Optional[str] = Field(None, description="Search by class code") - description: Optional[str] = Field(None, description="Search in descriptions") - material_key: Optional[str] = Field(None, description="Filter by material key") - fraction: Optional[str] = Field(None, description="Filter by tariff fraction") - physical_review: Optional[int] = Field( - None, description="Filter by physical review indicator" - ) - - class Config: - from_attributes = True - - -class ClassWithFADataResponse(BaseModel): - """DTO para respuesta de clase con datos FA embebidos (para fixed-asset-classes)""" - - # Base class fields - id: int - tenant_id: int - company_id: int - class_code: str - description_es: Optional[str] = None - description_en: Optional[str] = None - material_key: Optional[str] = None - unit_of_measure: Optional[str] = None - stock_unit_of_measure: Optional[str] = None - fraction: Optional[str] = None - us_fraction: Optional[str] = None - sub_key: Optional[str] = None - physical_review: Optional[int] = None - iva_exempt_fraction: Optional[str] = None - system: str = "fixed_asset" - created_at: datetime - updated_at: datetime - - # FA-specific fields (embedded from a24.fa_classes) - fa_class_id: Optional[int] = None - import_tariff_code: Optional[str] = None - import_tariff_type: Optional[str] = None - export_tariff_code: Optional[str] = None - export_tariff_type: Optional[str] = None - depreciation_rate: Optional[Decimal] = None - fda_code: Optional[str] = None - eccn_code: Optional[str] = None - class_enabled: Optional[bool] = None - - model_config = ConfigDict(from_attributes=True) - - -class ClassWithFADataPaginatedResponse(BaseModel): - """Lista paginada de clases con datos FA (fixed-asset-classes)""" - - items: list[ClassWithFADataResponse] - total: int - page: int - page_size: int - - model_config = ConfigDict(from_attributes=True) \ No newline at end of file diff --git a/backend/api/v1/modules/a76/classes/models.py b/backend/api/v1/modules/a76/classes/models.py deleted file mode 100644 index 93694b9a..00000000 --- a/backend/api/v1/modules/a76/classes/models.py +++ /dev/null @@ -1,124 +0,0 @@ -""" -Modelos ORM para gestión de clases SCAII y SCAF -""" - -from typing import TYPE_CHECKING, Optional - -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base -from sqlalchemy import ( - ForeignKey, - ForeignKeyConstraint, - Integer, - PrimaryKeyConstraint, - SmallInteger, - String, - UniqueConstraint, -) -from sqlalchemy.orm import Mapped, mapped_column, relationship - -from api.v1.modules.public.reference_data.material_types.models import MaterialType -from api.v1.modules.a76.general_catalogs.units_of_measure.models import UnitOfMeasure - -if TYPE_CHECKING: - from api.v1.modules.a76.parts.models import Part - - -class Class(Base, TenantScopedMixin, TimestampMixin): - """ - Modelo para la tabla GClases - Información de clases en sistemas SCAII y SCAF - """ - - __tablename__ = "classes" - __table_args__ = ( - PrimaryKeyConstraint("id", name="classes_pkey"), - ForeignKeyConstraint( - ["material_key"], - ["public.material_types.key"], - name="fk_classes_material_type", - ), - ForeignKeyConstraint( - ["unit_of_measure", "tenant_id", "company_id"], - ["a76.units_of_measure.code", "a76.units_of_measure.tenant_id", - "a76.units_of_measure.company_id"], - ), - ForeignKeyConstraint( - ["stock_unit_of_measure", "tenant_id", "company_id"], - ["a76.units_of_measure.code", "a76.units_of_measure.tenant_id", - "a76.units_of_measure.company_id"], - name="fk_classes_stock_uom", - ), - UniqueConstraint( - "tenant_id", - "company_id", - "class_code", - name="uq_classes_tenant_company_code", - ), - {"schema": "a76", "extend_existing": True}, - ) - - id: Mapped[int] = mapped_column(Integer, primary_key=True) - - # Unique constraint compuesta - class_code: Mapped[str] = mapped_column(String(8)) # CLASE - - # Basic information - description_es: Mapped[Optional[str]] = mapped_column( - String(500)) # DESCRIPCIONE - description_en: Mapped[Optional[str]] = mapped_column( - String(500)) # DESCRIPCIONI - - # Material and measurement - material_key: Mapped[Optional[str]] = mapped_column( - String(10) - ) # CLAVEMAT - homologated from TIPOMAT/TIPOMATEQUIPO - unit_of_measure: Mapped[Optional[str]] = mapped_column( - String(5) - ) # UNIMED - homologated from UNIMEDIDA - stock_unit_of_measure: Mapped[Optional[str]] = mapped_column( - String(5) - ) # UMEXISTENCIA - UM Existencia (SCAII/inventory) - - # Tariff fractions - fraction: Mapped[Optional[str]] = mapped_column(String(20)) # FRACCION - us_fraction: Mapped[Optional[str]] = mapped_column( - String(16) - ) # FRACCIONAME - US tariff fraction - - # Additional classification - sub_key: Mapped[Optional[str]] = mapped_column(String(5)) # CLAVESUB - physical_review: Mapped[Optional[int]] = mapped_column( - SmallInteger) # REVFISICA - iva_exempt_fraction: Mapped[Optional[str]] = mapped_column( - String(4) - ) # FRACCIONEXENTAIVA - - is_active: Mapped[bool] = mapped_column( - default=True, server_default="true", nullable=False - ) # Campo para habilitar/deshabilitar clases sin eliminarlas - - # Sistema al que pertenece la clase: 'fixed_asset' (SCAF) o 'inventory' (SCAII) - system: Mapped[str] = mapped_column(String(12), nullable=False, server_default="fixed_asset") - - # Relationships - material_type: Mapped[Optional["MaterialType"]] = relationship( - foreign_keys=[material_key] - ) - unit_of_measure_info: Mapped[Optional["UnitOfMeasure"]] = relationship( - foreign_keys=[unit_of_measure] - ) - stock_unit_of_measure_info: Mapped[Optional["UnitOfMeasure"]] = relationship( - foreign_keys=[stock_unit_of_measure], - viewonly=True, - ) - - # Inverse relationship with GParts that have this class - parts: Mapped[list["Part"]] = relationship( - primaryjoin="and_(Class.class_code == Part.part_class)", - foreign_keys="[Part.part_class]", - viewonly=True, - back_populates="part_class_info", - ) - - def __repr__(self) -> str: - return f"" diff --git a/backend/api/v1/modules/a76/classes/routes.py b/backend/api/v1/modules/a76/classes/routes.py deleted file mode 100644 index 490c7a16..00000000 --- a/backend/api/v1/modules/a76/classes/routes.py +++ /dev/null @@ -1,151 +0,0 @@ -""" -Endpoints API para gestión de clases SCAII y SCAF -""" - -from typing import Any, Dict, Optional -from fastapi import APIRouter, Depends, HTTPException, Query, Request -from sqlalchemy.orm import Session - -from core.database import get_core_db -from core.security import get_current_user, get_active_system -from api.v1.common.tenant_crud_routes import TenantCRUDRoutes, validate_access_to_resource - -from .dto import ( - ClassCreateDTO, - ClassCreateDTOFA, - ClassResponseDTO, - ClassResponseDTOFA, - ClassUpdateDTO, - ClassWithFADataPaginatedResponse, -) -from .service import ClassService -from api.v1.modules.a76.layouts_csv.classes.routes import router as imports_router - -# Create a new router for custom endpoints -router = APIRouter() - -# CSV import (upload → scan → status → commit) -router.include_router(imports_router, prefix="/imports", tags=["a76 / classes / csv_import"]) - -# Add consolidated catalog endpoints FIRST (before generic CRUD routes) -# This ensures they have priority over the generic /{id} route -@router.get( - "/with-fa-data", - response_model=ClassWithFADataPaginatedResponse, - summary="Get Classes with FA Data", - description="Clases con datos FA en una sola consulta (JOIN); respuesta paginada", - tags=["a76 / classes"], -) -async def get_classes_with_fa_data( - request: Request, - company_id: int = Query(..., description="Company ID"), - page: int = Query(1, ge=1, description="Page number"), - page_size: int = Query(50, ge=1, le=1000, description="Page size"), - sort_by: Optional[str] = Query(None, description="Column to sort by"), - sort_order: Optional[str] = Query("asc", description="Sort order (asc/desc)"), - class_code: Optional[str] = Query(None, description="Filter by class code (contains)"), - description: Optional[str] = Query(None, description="Filter by ES/EN description (contains)"), - material_key: Optional[str] = Query(None, description="Filter by material key (contains)"), - fraction: Optional[str] = Query(None, description="Filter by tariff fraction (contains)"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """ - Get all classes with their FA data using a single LEFT JOIN query. - This endpoint is optimized for the fixed-asset-classes view. - """ - tenant_id = validate_access_to_resource(db, company_id, current_user, ["goods_classes.view"]) - - skip = (page - 1) * page_size - - filters: Dict[str, Any] = {} - if class_code: - filters["class_code"] = class_code - if description: - filters["description"] = description - if material_key: - filters["material_key"] = material_key - if fraction: - filters["fraction"] = fraction - - # Sistema activo desde header/cookie (TenantMiddleware no setea request.state en /api/) - active_system = get_active_system(request) - if active_system: - filters["system"] = active_system - - classes_with_fa, total = ClassService.get_all_with_fa_data( - db=db, - tenant_id=tenant_id, - company_id=company_id, - skip=skip, - limit=page_size, - filters=filters if filters else None, - sort_by=sort_by, - sort_order=sort_order, - ) - - return ClassWithFADataPaginatedResponse( - items=classes_with_fa, - total=total, - page=page, - page_size=page_size, - ) - -@router.post( - "/fa", - response_model=ClassResponseDTOFA, - status_code=201, - summary="Create Fixed Asset Class", - description="Create a class with FA extension in a single transaction", - tags=["a76 / classes"], -) -async def create_fa_class( - request: Request, - class_data: ClassCreateDTOFA, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """Create a fixed asset class (both base class and FA extension)""" - active_system = get_active_system(request) - if active_system == "inventory": - raise HTTPException( - status_code=403, - detail=( - "Solo se pueden crear clases de activo fijo con el módulo de Activo Fijo (SCAF) activo. " - "Cambie de aplicación e intente de nuevo." - ), - ) - - tenant_id = validate_access_to_resource(db, company_id, current_user, ["goods_classes.create"]) - - # Siempre persistir como activo fijo; no depender del default del DTO ni del body del cliente - class_data = class_data.model_copy(update={"system": "fixed_asset"}) - - result = ClassService.create_fa_class(db, class_data, tenant_id, company_id) - - return result - -# Now include generic CRUD routes -# These will be registered AFTER the custom endpoints above -crud_router = TenantCRUDRoutes( - service=ClassService, - create_schema=ClassCreateDTO, - update_schema=ClassUpdateDTO, - response_schema=ClassResponseDTO, - prefix="", # No prefix here, will be added in main router - tags=["a76 / classes"], - resource_name="Class", - id_name="id", - enable_list=True, - enable_filters=True, - default_page_size=50, - max_page_size=1000, - list_permissions=["goods_classes.view"], - create_permissions=["goods_classes.create"], - update_permissions=["goods_classes.edit"], - delete_permissions=["goods_classes.delete"], -).router - -# Include the CRUD routes into our main router -router.include_router(crud_router) \ No newline at end of file diff --git a/backend/api/v1/modules/a76/classes/service.py b/backend/api/v1/modules/a76/classes/service.py deleted file mode 100644 index d03888ab..00000000 --- a/backend/api/v1/modules/a76/classes/service.py +++ /dev/null @@ -1,873 +0,0 @@ -""" -Capa de servicio para lógica de negocio de clases SCAII y SCAF -""" - -import logging -from typing import Any, Dict, List, Optional - -from fastapi import HTTPException -from sqlalchemy import and_, or_ -from sqlalchemy.exc import IntegrityError -from sqlalchemy.orm import Session - -from .dto import ( - ClassBasicDTO, - ClassCreateDTO, - ClassCreateDTOFA, - ClassListDTO, - ClassResponseDTO, - ClassResponseDTOFA, - ClassSearchDTO, - ClassUpdateDTO, -) -from .models import Class - -logger = logging.getLogger(__name__) - - -def _validate_stock_uom_conversion( - db: Session, - tenant_id: int, - company_id: int, - system: str, - stock_unit_of_measure: Optional[str], - unit_of_measure: Optional[str], -) -> None: - """SCAII: si UM Existencia ≠ UM Comercial, debe existir conversión en catálogo.""" - if system != "inventory": - return - - stock_um = (stock_unit_of_measure or "").strip().upper() - commercial_um = (unit_of_measure or "").strip().upper() - if not stock_um or not commercial_um or stock_um == commercial_um: - return - - from api.v1.modules.a76.general_catalogs.unit_conversions.models import ( - UnitConversion, - ) - - conversion_exists = ( - db.query(UnitConversion) - .filter( - UnitConversion.tenant_id == tenant_id, - UnitConversion.company_id == company_id, - or_( - (UnitConversion.from_unit_code == stock_um) - & (UnitConversion.to_unit_code == commercial_um), - (UnitConversion.from_unit_code == commercial_um) - & (UnitConversion.to_unit_code == stock_um), - ), - ) - .first() - ) - if not conversion_exists: - raise HTTPException( - status_code=422, - detail=( - f"No existe un factor de conversión para '{stock_um}' ↔ '{commercial_um}'. " - "Configúrelo primero en el catálogo de Conversiones de UM." - ), - ) - - -class ClassService: - """Servicio para gestión de clases SCAII y SCAF""" - - @staticmethod - def get_all( - db: Session, - tenant_id: int, - company_id: Optional[int], - skip: int = 0, - limit: int = 100, - filters: Optional[Dict[str, Any]] = None, - sort_by: Optional[str] = None, - sort_order: Optional[str] = "asc", - ) -> tuple[List[Class], int]: - """ - Get all classes for a tenant with pagination and filters - """ - query = db.query(Class).filter(Class.tenant_id == tenant_id) - - if company_id is not None: - query = query.filter(Class.company_id == company_id) - - if filters: - # Búsqueda libre: OR en clave y descripciones (selectores / listados) - search_raw = filters.get("search") or filters.get("q") - if search_raw and str(search_raw).strip(): - pattern = f"%{str(search_raw).strip()}%" - query = query.filter( - or_( - Class.class_code.ilike(pattern), - Class.description_es.ilike(pattern), - Class.description_en.ilike(pattern), - ) - ) - else: - if filters.get("class_code"): - query = query.filter( - Class.class_code.ilike(f"%{filters['class_code']}%") - ) - if filters.get("description"): - description_pattern = f"%{filters['description']}%" - query = query.filter( - or_( - Class.description_es.ilike(description_pattern), - Class.description_en.ilike(description_pattern), - ) - ) - if filters.get("material_key"): - query = query.filter( - Class.material_key.ilike(f"%{filters['material_key']}%") - ) - if filters.get("fraction"): - query = query.filter(Class.fraction.ilike(f"%{filters['fraction']}%")) - if filters.get("physical_review") is not None: - query = query.filter( - Class.physical_review == filters["physical_review"] - ) - if filters.get("system"): - query = query.filter(Class.system == filters["system"]) - - # Apply sorting - if sort_by: - column = getattr(Class, sort_by, None) - if column: - if sort_order == "desc": - query = query.order_by(column.desc()) - else: - query = query.order_by(column.asc()) - else: - # Default sorting - query = query.order_by(Class.class_code.asc()) - - total = query.count() - items = query.offset(skip).limit(limit).all() - - return items, total - - @staticmethod - def get_all_with_fa_data( - db: Session, - tenant_id: int, - company_id: Optional[int], - skip: int = 0, - limit: int = 1000, - filters: Optional[Dict[str, Any]] = None, - sort_by: Optional[str] = None, - sort_order: Optional[str] = "asc", - ) -> tuple[List[Dict[str, Any]], int]: - """ - Get all classes with their FA data in a single query using LEFT JOIN. - This eliminates the N+1 query problem. - - Returns a list of dicts with combined base class + FA data. - """ - from api.v1.modules.a24.fa.fa_classes.models import QClasses - - # Build query with LEFT JOIN - query = ( - db.query(Class, QClasses) - .outerjoin(QClasses, and_( - Class.id == QClasses.class_id, - QClasses.tenant_id == tenant_id - )) - .filter(Class.tenant_id == tenant_id) - ) - - if company_id is not None: - query = query.filter(Class.company_id == company_id) - - # Apply filters if provided - if filters: - if filters.get("q"): - search = f"%{filters['q']}%" - query = query.filter( - or_( - Class.class_code.ilike(search), - Class.description_es.ilike(search), - Class.description_en.ilike(search) - ) - ) - if filters.get("class_code"): - query = query.filter( - Class.class_code.ilike(f"%{filters['class_code']}%") - ) - if filters.get("description"): - description_pattern = f"%{filters['description']}%" - query = query.filter( - or_( - Class.description_es.ilike(description_pattern), - Class.description_en.ilike(description_pattern), - ) - ) - if filters.get("material_key"): - query = query.filter( - Class.material_key.ilike(f"%{filters['material_key']}%") - ) - if filters.get("fraction"): - query = query.filter(Class.fraction.ilike(f"%{filters['fraction']}%")) - if filters.get("system"): - query = query.filter(Class.system == filters["system"]) - - # Apply sorting - if sort_by: - # Check if sort_by belongs to Class or QClasses - column = getattr(Class, sort_by, None) - if column: - if sort_order == "desc": - query = query.order_by(column.desc()) - else: - query = query.order_by(column.asc()) - else: - # Handle FA extension fields if sort_by is one of them - # (Simple approach for now, assuming base class fields are prioritized) - pass - else: - # Default sorting - query = query.order_by(Class.class_code.asc()) - - # Count total before pagination - total = query.count() - - # Apply pagination - results = query.offset(skip).limit(limit).all() - - # Combine base class + FA data into dicts - combined = [] - for base_class, fa_class in results: - class_dict = { - # Base class fields - "id": base_class.id, - "tenant_id": base_class.tenant_id, - "company_id": base_class.company_id, - "class_code": base_class.class_code, - "description_es": base_class.description_es, - "description_en": base_class.description_en, - "material_key": base_class.material_key, - "unit_of_measure": base_class.unit_of_measure, - "stock_unit_of_measure": base_class.stock_unit_of_measure, - "fraction": base_class.fraction, - "us_fraction": base_class.us_fraction, - "sub_key": base_class.sub_key, - "physical_review": base_class.physical_review, - "iva_exempt_fraction": base_class.iva_exempt_fraction, - "system": base_class.system, - "created_at": base_class.created_at, - "updated_at": base_class.updated_at, - # FA extension fields (None if no FA record exists) - "fa_class_id": fa_class.id if fa_class else None, - "import_tariff_code": fa_class.import_tariff_code if fa_class else None, - "import_tariff_type": fa_class.import_tariff_type if fa_class else None, - "export_tariff_code": fa_class.export_tariff_code if fa_class else None, - "export_tariff_type": fa_class.export_tariff_type if fa_class else None, - "depreciation_rate": fa_class.depreciation_rate if fa_class else None, - "fda_code": fa_class.fda_code if fa_class else None, - "eccn_code": fa_class.eccn_code if fa_class else None, - "class_enabled": fa_class.class_enabled if fa_class else None, - } - combined.append(class_dict) - - return combined, total - - @staticmethod - def get_by_id( - db: Session, class_id: int, tenant_id: int, company_id: int - ) -> Optional[Class]: - """Get a class by ID""" - return ( - db.query(Class) - .filter( - Class.id == class_id, - Class.tenant_id == tenant_id, - Class.company_id == company_id, - ) - .first() - ) - - @staticmethod - def create( - db: Session, class_data: ClassCreateDTO, tenant_id: int, company_id: int - ) -> Class: - """Create a new class""" - from fastapi import HTTPException - from sqlalchemy.exc import IntegrityError - - data_dict = class_data.model_dump() - - # Check if class_code already exists for this tenant and company - existing = db.query(Class).filter( - Class.tenant_id == tenant_id, - Class.company_id == company_id, - Class.class_code == data_dict["class_code"] - ).first() - - if existing: - raise HTTPException( - status_code=400, - detail=f" El código de clase '{data_dict['class_code']}' ya existe. Por favor use un código diferente." - ) - - # Validate material_key exists (now required) - from api.v1.modules.public.reference_data.material_types.models import MaterialType - material_exists = db.query(MaterialType).filter( - MaterialType.key == data_dict["material_key"] - ).first() - if not material_exists: - raise HTTPException( - status_code=400, - detail=f"Material type '{data_dict['material_key']}' does not exist" - ) - - _validate_stock_uom_conversion( - db, - tenant_id, - company_id, - data_dict.get("system", "fixed_asset"), - data_dict.get("stock_unit_of_measure"), - data_dict.get("unit_of_measure"), - ) - - class_obj = Class(**data_dict) - class_obj.tenant_id = tenant_id - class_obj.company_id = company_id - - try: - db.add(class_obj) - db.commit() - db.refresh(class_obj) - return class_obj - except IntegrityError as e: - db.rollback() - raise HTTPException( - status_code=400, - detail=f"Failed to create class: {str(e.orig)}" - ) - - @staticmethod - def update( - db: Session, - class_id: int, - tenant_id: int, - class_data: ClassUpdateDTO, - company_id: int, - ) -> Optional[Class]: - """Update a class""" - - class_obj = ClassService.get_by_id(db, class_id, tenant_id, company_id) - if not class_obj: - logger.warning(f"Class {class_id} not found for tenant {tenant_id}, company {company_id}") - return None - - update_data = class_data.model_dump(exclude_unset=True) - - effective_system = update_data.get("system", class_obj.system) - effective_stock_um = update_data.get( - "stock_unit_of_measure", class_obj.stock_unit_of_measure - ) - effective_commercial_um = update_data.get( - "unit_of_measure", class_obj.unit_of_measure - ) - _validate_stock_uom_conversion( - db, - tenant_id, - company_id, - effective_system, - effective_stock_um, - effective_commercial_um, - ) - - # Validate material_key exists if provided - if "material_key" in update_data and update_data["material_key"]: - from api.v1.modules.public.reference_data.material_types.models import MaterialType - material_exists = db.query(MaterialType).filter( - MaterialType.key == update_data["material_key"] - ).first() - if not material_exists: - # Set to None if material_key doesn't exist - update_data["material_key"] = None - - # Validate class_code is unique if being changed - if "class_code" in update_data and update_data["class_code"]: - new_code = update_data["class_code"] - # Check if another class with this code exists (excluding current class) - # The unique constraint is on (tenant_id, company_id, class_code) - existing_class = db.query(Class).filter( - Class.class_code == new_code, - Class.tenant_id == tenant_id, - Class.company_id == company_id, - Class.id != class_id # Exclude current class - ).first() - - if existing_class: - logger.warning(f"Duplicate class_code found: {existing_class.id}") - raise HTTPException( - status_code=400, - detail=f"El código '{new_code}' ya está en uso para este cliente. Por favor ingrese un código diferente." - ) - - for field, value in update_data.items(): - setattr(class_obj, field, value) - - try: - db.commit() - db.refresh(class_obj) - return class_obj - except IntegrityError as e: - db.rollback() - error_msg = str(e.orig) - logger.error(f"IntegrityError updating class {class_id}: {error_msg}") - - # Check if it's a duplicate class_code error - if "already exists" in error_msg.lower() or "duplicate" in error_msg.lower(): - # Extract the code from update_data if it was changed - code = update_data.get("class_code", class_obj.class_code) - raise HTTPException( - status_code=400, - detail=f"El código '{code}' ya está en uso. Por favor ingrese un código diferente." - ) - - raise HTTPException( - status_code=400, - detail=f"Error al actualizar la clase: {error_msg}" - ) - except Exception as e: - db.rollback() - logger.error(f"Unexpected error updating class {class_id}: {type(e).__name__}: {str(e)}") - raise - - @staticmethod - def delete(db: Session, class_id: int, tenant_id: int, company_id: int) -> bool: - """Delete a class (and its FA extension if exists)""" - from api.v1.modules.a24.fa.fa_classes.models import QClasses - from api.v1.modules.a76.items.models import LineItem - from api.v1.modules.a76.parts.models import Part - - class_obj = ClassService.get_by_id(db, class_id, tenant_id, company_id) - if not class_obj: - return False - - used_in_parts = ( - db.query(Part.id) - .filter( - Part.part_class == class_obj.class_code, - Part.tenant_id == tenant_id, - Part.company_id == company_id, - ) - .first() - is not None - ) - used_in_invoices = ( - db.query(LineItem.id) - .filter( - LineItem.class_id == class_id, - LineItem.tenant_id == tenant_id, - LineItem.company_id == company_id, - ) - .first() - is not None - ) - - if used_in_parts or used_in_invoices: - reasons: list[str] = [] - if used_in_parts: - reasons.append("partes") - if used_in_invoices: - reasons.append("facturas") - usage = " y ".join(reasons) - raise HTTPException( - status_code=409, - detail=f"No se puede eliminar la clase porque ya fue utilizada en {usage}.", - ) - - # Delete FA extension first (if exists) to avoid FK constraint violation - fa_extension = db.query(QClasses).filter( - QClasses.class_id == class_id, - QClasses.tenant_id == tenant_id - ).first() - - if fa_extension: - db.delete(fa_extension) - - # Now delete the base class - db.delete(class_obj) - db.commit() - return True - - @staticmethod - def create_fa_class( - db: Session, class_data: ClassCreateDTOFA, tenant_id: int, company_id: int - ) -> Dict[str, Any]: - """ - Create a fixed asset class (both a76.classes and a24.fa_classes) - Returns a dict with both records combined - """ - - from api.v1.modules.a24.fa.fa_classes.models import QClasses - - # Extract base class fields - base_fields = { - "class_code", "description_es", "description_en", - "material_key", "unit_of_measure", "stock_unit_of_measure", - "fraction", "us_fraction", - "sub_key", "physical_review", "iva_exempt_fraction", "system" - } - base_data = {k: v for k, v in class_data.model_dump().items() if k in base_fields} - - # Extract FA-specific fields - fa_fields = { - "import_tariff_code", "import_tariff_type", "export_tariff_code", - "export_tariff_type", "depreciation_rate", "fda_code", "eccn_code", - "class_enabled" - } - fa_data = {k: v for k, v in class_data.model_dump().items() if k in fa_fields} - - try: - # 1. Create base class - base_dto = ClassCreateDTO(**base_data) - base_class = ClassService.create(db, base_dto, tenant_id, company_id) - - # 2. Create FA extension - fa_obj = QClasses(**fa_data) - fa_obj.class_id = base_class.id - fa_obj.tenant_id = tenant_id - fa_obj.company_id = company_id - - db.add(fa_obj) - db.commit() - db.refresh(fa_obj) - - # 3. Combine response - build dict manually to avoid SQLAlchemy internals - combined_response = { - # Base class fields - "id": base_class.id, - "tenant_id": base_class.tenant_id, - "company_id": base_class.company_id, - "class_code": base_class.class_code, - "description_es": base_class.description_es, - "description_en": base_class.description_en, - "material_key": base_class.material_key, - "unit_of_measure": base_class.unit_of_measure, - "stock_unit_of_measure": base_class.stock_unit_of_measure, - "fraction": base_class.fraction, - "us_fraction": base_class.us_fraction, - "sub_key": base_class.sub_key, - "physical_review": base_class.physical_review, - "iva_exempt_fraction": base_class.iva_exempt_fraction, - "system": base_class.system, - "created_at": base_class.created_at, - "updated_at": base_class.updated_at, - # FA extension fields - "fa_id": fa_obj.id, - "import_tariff_code": fa_obj.import_tariff_code, - "import_tariff_type": fa_obj.import_tariff_type, - "export_tariff_code": fa_obj.export_tariff_code, - "export_tariff_type": fa_obj.export_tariff_type, - "depreciation_rate": fa_obj.depreciation_rate, - "fda_code": fa_obj.fda_code, - "eccn_code": fa_obj.eccn_code, - "class_enabled": fa_obj.class_enabled, - } - - return combined_response - - except Exception as e: - db.rollback() - # If FA creation fails, rollback base class too - if 'base_class' in locals(): - try: - db.delete(base_class) - db.commit() - except: - pass - - # Extract and improve error message - error_msg = str(e) - if "already exists" in error_msg.lower() or "duplicad" in error_msg.lower(): - # Extract code from error if possible - code = class_data.class_code - raise HTTPException( - status_code=400, - detail=f"El código '{code}' ya está en uso. Por favor ingrese un código diferente." - ) - - raise HTTPException( - status_code=400, - detail=f"Error al crear clase de activo fijo: {error_msg}" - ) - - def __init__(self, db: Session): - self.db = db - - def create_class(self, class_data: ClassCreateDTO) -> ClassResponseDTO: - """ - Crea una nueva clase en el sistema - - Args: - class_data: Datos de la clase a crear - - Returns: - ClassResponseDTO con información de la clase creada - - Raises: - HTTPException: Si la clase ya existe o error en la creación - """ - try: - # Verificar que no exista la clase - existing = ( - self.db.query(Class) - .filter( - and_( - Class.class_code == class_data.class_code, - ) - ) - .first() - ) - - if existing: - raise HTTPException( - status_code=400, - detail=f"Class with class_code '{class_data.class_code}' already exists", - ) - - # Crear clase - db_class = Class( - class_code=class_data.class_code, - description_spanish=class_data.description_spanish, - description_english=class_data.description_english, - material_key=class_data.material_key, - unit_of_measure=class_data.unit_of_measure, - fraction=class_data.fraction, - us_fraction=class_data.us_fraction, - sub_key=class_data.sub_key, - physical_review=class_data.physical_review, - iva_exempt_fraction=class_data.iva_exempt_fraction, - ) - - self.db.add(db_class) - self.db.commit() - self.db.refresh(db_class) - - return ClassResponseDTO.model_validate(db_class) - - except IntegrityError as e: - self.db.rollback() - logger.error(f"IntegrityError creating class: {str(e)}") - raise HTTPException( - status_code=400, - detail="Class with this class_code already exists", - ) - except HTTPException: - raise - except Exception as e: - self.db.rollback() - logger.error(f"Error creating class: {str(e)}") - raise HTTPException(status_code=500, detail="Error creating class") - - def get_class(self, class_code: str) -> Optional[ClassResponseDTO]: - """ - Obtiene una clase por clave compuesta - - Args: - class_code: Código de clase - - Returns: - ClassResponseDTO o None si no existe - """ - class_obj = ( - self.db.query(Class) - .filter(and_(Class.class_code == class_code)) - .first() - ) - - if not class_obj: - return None - return ClassResponseDTO.model_validate(class_obj) - - def list_classes( - self, - skip: int = 0, - limit: int = 100, - search_params: Optional[ClassSearchDTO] = None, - ) -> ClassListDTO: - """ - Lista clases con filtros - - Args: - skip: Número de registros a omitir - limit: Número máximo de registros a retornar - search_params: Parámetros de búsqueda - - Returns: - ClassListDTO con la lista paginada - """ - query = self.db.query(Class) - - # Aplicar filtros si se proporcionan - if search_params: - if search_params.class_code: - query = query.filter( - Class.class_code.ilike(f"%{search_params.class_code}%") - ) - - if search_params.description: - description_pattern = f"%{search_params.description}%" - query = query.filter( - or_( - Class.description_spanish.ilike(description_pattern), - Class.description_english.ilike(description_pattern), - ) - ) - - if search_params.material_key: - query = query.filter( - Class.material_key.ilike(f"%{search_params.material_key}%") - ) - - if search_params.fraction: - query = query.filter( - Class.fraction.ilike(f"%{search_params.fraction}%") - ) - - if search_params.physical_review is not None: - query = query.filter( - Class.physical_review == search_params.physical_review - ) - - # Contar total - total = query.count() - - # Aplicar paginación - classes = query.offset(skip).limit(limit).all() - - # Convertir a DTOs básicos - class_dtos = [ClassBasicDTO.model_validate(class_obj) for class_obj in classes] - - return ClassListDTO( - classes=class_dtos, - total=total, - page=(skip // limit) + 1 if limit > 0 else 1, - size=len(class_dtos), - ) - - def update_class( - self, class_code: str, class_data: ClassUpdateDTO - ) -> Optional[ClassResponseDTO]: - """ - Actualiza una clase - - Args: - class_code: Código de clase - class_data: Datos a actualizar - - Returns: - ClassResponseDTO actualizado o None si no existe - """ - class_obj = ( - self.db.query(Class) - .filter(and_(Class.class_code == class_code)) - .first() - ) - - if not class_obj: - return None - - try: - # Actualizar solo campos proporcionados - update_data = class_data.model_dump(exclude_unset=True) - for field, value in update_data.items(): - setattr(class_obj, field, value) - - self.db.commit() - self.db.refresh(class_obj) - - return ClassResponseDTO.model_validate(class_obj) - - except Exception as e: - self.db.rollback() - logger.error(f"Error updating class {class_code}: {str(e)}") - raise HTTPException(status_code=500, detail="Error updating class") - - def delete_class(self, class_code: str) -> bool: - """ - Elimina una clase - - Args: - class_code: Código de clase - - Returns: - True si se eliminó, False si no existe - """ - class_obj = ( - self.db.query(Class) - .filter(and_(Class.class_code == class_code)) - .first() - ) - - if not class_obj: - return False - - try: - self.db.delete(class_obj) - self.db.commit() - return True - except Exception as e: - self.db.rollback() - logger.error(f"Error deleting class {class_code}: {str(e)}") - raise HTTPException(status_code=500, detail="Error deleting class") - - def search_by_fraction(self, fraction: str) -> List[ClassBasicDTO]: - """Busca clases por fracción arancelaria""" - classes = ( - self.db.query(Class).filter(Class.fraction.ilike(f"%{fraction}%")).all() - ) - return [ClassBasicDTO.model_validate(class_obj) for class_obj in classes] - - def search_by_material(self, material_key: str) -> List[ClassBasicDTO]: - """Busca clases por clave de material""" - classes = ( - self.db.query(Class) - .filter(Class.material_key.ilike(f"%{material_key}%")) - .all() - ) - return [ClassBasicDTO.model_validate(class_obj) for class_obj in classes] - - def get_classes_by_physical_review( - self, physical_review: int - ) -> List[ClassBasicDTO]: - """Obtiene clases por indicador de revisión física""" - classes = ( - self.db.query(Class).filter(Class.physical_review == physical_review).all() - ) - return [ClassBasicDTO.model_validate(class_obj) for class_obj in classes] - - def get_classes_statistics(self) -> dict: - """Obtiene estadísticas básicas de clases""" - total_classes = self.db.query(Class).count() - - # Contar por revisión física - physical_review_stats = {} - for i in range(3): # Asumiendo valores 0, 1, 2 - count = self.db.query(Class).filter(Class.physical_review == i).count() - physical_review_stats[f"physical_review_{i}"] = count - - # Contar clases con fracciones - with_fraction = self.db.query(Class).filter(Class.fraction.isnot(None)).count() - with_us_fraction = ( - self.db.query(Class).filter(Class.us_fraction.isnot(None)).count() - ) - - return { - "total_classes": total_classes, - "classes_with_fraction": with_fraction, - "classes_with_us_fraction": with_us_fraction, - **physical_review_stats, - } - - def get_classes_by_unit_measure(self, unit_of_measure: str) -> List[ClassBasicDTO]: - """Obtiene clases por unidad de medida""" - classes = ( - self.db.query(Class).filter(Class.unit_of_measure == unit_of_measure).all() - ) - return [ClassBasicDTO.model_validate(class_obj) for class_obj in classes] diff --git a/backend/api/v1/modules/a76/classes/test_classes.py b/backend/api/v1/modules/a76/classes/test_classes.py deleted file mode 100644 index f899748c..00000000 --- a/backend/api/v1/modules/a76/classes/test_classes.py +++ /dev/null @@ -1,36 +0,0 @@ -import pytest -from fastapi import FastAPI -from fastapi.testclient import TestClient - -from .routes import router - -app = FastAPI() -app.include_router(router) -client = TestClient(app) - - -@pytest.mark.usefixtures("client", "access_token") -def test_list_classes(client, access_token): - headers = {"Authorization": f"Bearer {access_token}"} - response = client.get("/classes/", headers=headers) - assert response.status_code == 200 - assert "items" in response.json() - assert "page" in response.json() - assert "page_size" in response.json() - - -@pytest.mark.usefixtures("client", "access_token") -def test_get_class_not_found(client, access_token): - headers = {"Authorization": f"Bearer {access_token}"} - response = client.get("/classes/invalid_id", headers=headers) - assert response.status_code == 404 - - -def test_create_class_forbidden(): - response = client.post("/classes/", json={"name": "Test Class"}) - assert response.status_code in (403, 405, 404) - - -def test_update_class_forbidden(): - response = client.put("/classes/1", json={"name": "Updated Class"}) - assert response.status_code in (403, 405, 404) diff --git a/backend/api/v1/modules/a76/clients_and_providers/__init__.py b/backend/api/v1/modules/a76/clients_and_providers/__init__.py deleted file mode 100644 index bdcba0ad..00000000 --- a/backend/api/v1/modules/a76/clients_and_providers/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -""" -Módulo de Client & Provider -""" - -from .routes import router - -__all__ = ["router"] diff --git a/backend/api/v1/modules/a76/clients_and_providers/dto.py b/backend/api/v1/modules/a76/clients_and_providers/dto.py deleted file mode 100644 index 8e7af628..00000000 --- a/backend/api/v1/modules/a76/clients_and_providers/dto.py +++ /dev/null @@ -1,280 +0,0 @@ -""" -DTOs (Data Transfer Objects) para módulo de clientes y proveedores -Reemplaza schemas.py siguiendo enfoque DDD y estilo NestJS -""" - -from decimal import Decimal -from typing import List, Literal, Optional - -from pydantic import BaseModel, ConfigDict, Field, model_validator - -from .validators import is_valid_rfc, is_valid_tax_id - -# Mensajes de error reutilizados por las validaciones de identificador fiscal -_RFC_FORMAT_ERROR = "El RFC no tiene el formato correcto. Ejemplo: XAXX010101000." -_TAX_ID_FORMAT_ERROR = ( - "El TAX-ID debe tener formato: 2 dígitos, guión y resto (ej. 12-3456789). Máx 30 caracteres." -) - - -def _validate_fiscal_format(rfc: Optional[str], tax_id: Optional[str]) -> None: - """ - Valida el formato de cada identificador fiscal de forma independiente (no excluyente): - un registro puede traer RFC y TAX-ID a la vez; cada uno valida solo si tiene valor. - """ - if (rfc or "").strip() and not is_valid_rfc(rfc): - raise ValueError(_RFC_FORMAT_ERROR) - if (tax_id or "").strip() and not is_valid_tax_id(tax_id): - raise ValueError(_TAX_ID_FORMAT_ERROR) - - -def _require_fiscal_id_by_procedencia( - rfc: Optional[str], tax_id: Optional[str], type_nat_foreign: Optional[str] -) -> None: - """Exige el identificador que corresponde a la procedencia: E ⇒ TAX-ID, otro ⇒ RFC.""" - is_foreign = (type_nat_foreign or "").strip().upper().startswith("E") - if is_foreign and not (tax_id or "").strip(): - raise ValueError("El TAX-ID es obligatorio para registros extranjeros.") - if not is_foreign and not (rfc or "").strip(): - raise ValueError("El RFC es obligatorio para registros nacionales.") - - -# DTOs para dirección -class ClientProviderAddressDTO(BaseModel): - """DTO para dirección de cliente/proveedor""" - - municipality: Optional[str] = Field( - None, max_length=150, description="Municipality" - ) - streets: Optional[str] = Field(None, max_length=100, description="Streets") - neighborhood: Optional[str] = Field(None, max_length=40, description="Neighborhood") - interior_number: Optional[str] = Field( - None, max_length=20, description="Interior number" - ) - exterior_number: Optional[str] = Field( - None, max_length=20, description="Exterior number" - ) - postal_code: Optional[str] = Field(None, max_length=15, description="Postal code") - city: Optional[str] = Field(None, max_length=30, description="City") - state: Optional[str] = Field(None, max_length=30, description="State") - country: Optional[str] = Field(None, max_length=3, description="Country code") - phone: Optional[str] = Field(None, max_length=30, description="Phone number") - fax_number: Optional[str] = Field(None, max_length=30, description="Fax number") - email: Optional[str] = Field(None, max_length=100, description="Email address") - contact: Optional[str] = Field(None, max_length=50, description="Contact person") - reference: Optional[str] = Field(None, max_length=250, description="Reference") - - model_config = ConfigDict(from_attributes=True) - - -# DTOs para programas -class ClientProviderProgramsDTO(BaseModel): - """DTO para programas de cliente/proveedor""" - - program: Optional[str] = Field(None, max_length=7, description="Program") - program_number: Optional[str] = Field( - None, max_length=40, description="Program number" - ) - prosec: Optional[str] = Field(None, max_length=8, description="PROSEC") - prosec_authorization: Optional[str] = Field( - None, max_length=20, description="PROSEC authorization" - ) - secon_auth_date: Optional[int] = Field(None, description="SECON authorization date") - manufacturer_id: Optional[str] = Field( - None, max_length=25, description="Manufacturer ID" - ) - broker: Optional[str] = Field(None, max_length=6, description="Broker") - import_broker: Optional[str] = Field( - None, max_length=6, description="Import broker" - ) - transfer_key: Optional[str] = Field(None, max_length=8, description="Transfer key") - secon_authorization: Optional[str] = Field( - None, max_length=20, description="SECON authorization" - ) - applied_proportion: Optional[Decimal] = Field( - None, description="Applied proportion" - ) - is_certified_company: Optional[str] = Field( - None, max_length=1, description="Is certified company" - ) - certified_company_registry: Optional[str] = Field( - None, max_length=40, description="Certified company registry" - ) - donation_auth_number: Optional[str] = Field( - None, max_length=50, description="Donation authorization number" - ) - ctpat_svi: Optional[str] = Field(None, max_length=100, description="CTPAT SVI") - tax_registry_number: Optional[str] = Field( - None, max_length=40, description="Tax registry number" - ) - subassembly_service: Optional[int] = Field(None, description="Subassembly service") - autse_dates: Optional[int] = Field(None, description="AUTSE dates") - autse_number: Optional[str] = Field( - None, max_length=300, description="AUTSE number" - ) - - model_config = ConfigDict(from_attributes=True) - - -# DTOs principales -class ClientProviderCreateDTO(BaseModel): - """DTO para crear cliente/proveedor""" - - type_nat_foreign: Optional[str] = Field( - None, max_length=1, description="Type national/foreign" - ) - name: Optional[str] = Field(None, max_length=256, description="Name") - short_name: Optional[str] = Field(None, max_length=10, description="Short name") - rfc: Optional[str] = Field(None, max_length=30, description="RFC (nacional)") - tax_id: Optional[str] = Field( - None, max_length=30, description="TAX-ID (identificador fiscal extranjero)" - ) - curp: Optional[str] = Field(None, max_length=19, description="CURP") - client_or_provider: Optional[Literal["client", "provider", "both"]] = Field( - None, description="Client or provider" - ) - linking: Optional[str] = Field(None, max_length=1, description="Linking") - transform_subassembly: Optional[str] = Field( - None, max_length=1, description="Transform subassembly" - ) - extra_information: Optional[str] = Field( - None, max_length=399, description="Extra information" - ) - web_key: Optional[str] = Field(None, max_length=40, description="Web key") - responsible: Optional[str] = Field( - None, max_length=80, description="Responsible person" - ) - position: Optional[str] = Field(None, max_length=30, description="Position") - incoterm: Optional[str] = Field(None, max_length=19, description="Incoterm") - is_active: Optional[bool] = Field(None, description="Enabled/Disabled status") - - # Nested DTOs - address: Optional[ClientProviderAddressDTO] = Field( - None, description="Address information" - ) - programs: Optional[ClientProviderProgramsDTO] = Field( - None, description="Programs information" - ) - - @model_validator(mode="after") - def validate_rfc_or_tax_id_format(self): - # Valida formato de ambos campos y exige el que corresponda a la procedencia - _validate_fiscal_format(self.rfc, self.tax_id) - _require_fiscal_id_by_procedencia(self.rfc, self.tax_id, self.type_nat_foreign) - return self - - model_config = ConfigDict(from_attributes=True) - - -class ClientProviderUpdateDTO(BaseModel): - """DTO para actualizar cliente/proveedor""" - - type_nat_foreign: Optional[str] = Field( - None, max_length=1, description="Type national/foreign" - ) - name: Optional[str] = Field(None, max_length=256, description="Name") - short_name: Optional[str] = Field(None, max_length=10, description="Short name") - rfc: Optional[str] = Field(None, max_length=30, description="RFC (nacional)") - tax_id: Optional[str] = Field( - None, max_length=30, description="TAX-ID (identificador fiscal extranjero)" - ) - curp: Optional[str] = Field(None, max_length=19, description="CURP") - client_or_provider: Optional[Literal["client", "provider", "both"]] = Field( - None, description="Client or provider" - ) - linking: Optional[str] = Field(None, max_length=1, description="Linking") - transform_subassembly: Optional[str] = Field( - None, max_length=1, description="Transform subassembly" - ) - extra_information: Optional[str] = Field( - None, max_length=399, description="Extra information" - ) - web_key: Optional[str] = Field(None, max_length=40, description="Web key") - responsible: Optional[str] = Field( - None, max_length=80, description="Responsible person" - ) - position: Optional[str] = Field(None, max_length=30, description="Position") - incoterm: Optional[str] = Field(None, max_length=19, description="Incoterm") - is_active: Optional[bool] = Field(None, description="Enabled/Disabled status") - - # Nested DTOs - address: Optional[ClientProviderAddressDTO] = Field( - None, description="Address information" - ) - programs: Optional[ClientProviderProgramsDTO] = Field( - None, description="Programs information" - ) - - @model_validator(mode="after") - def validate_rfc_or_tax_id_format(self): - # Update es parcial (PATCH): solo se valida formato de lo que venga, sin exigir requerido - _validate_fiscal_format(self.rfc, self.tax_id) - return self - - model_config = ConfigDict(from_attributes=True) - - -class ClientProviderResponseDTO(BaseModel): - """DTO para respuesta de cliente/proveedor""" - - id: int - type_nat_foreign: Optional[str] = None - name: Optional[str] = None - short_name: Optional[str] = None - rfc: Optional[str] = None - tax_id: Optional[str] = None - curp: Optional[str] = None - client_or_provider: Optional[str] = None - linking: Optional[str] = None - transform_subassembly: Optional[str] = None - extra_information: Optional[str] = None - web_key: Optional[str] = None - responsible: Optional[str] = None - position: Optional[str] = None - incoterm: Optional[str] = None - is_active: Optional[bool] = None - tenant_id: int - company_id: int - - # Nested DTOs - address: Optional[ClientProviderAddressDTO] = None - programs: Optional[ClientProviderProgramsDTO] = None - - model_config = ConfigDict(from_attributes=True) - - -# DTOs para respuestas específicas -class ClientProviderBasicDTO(BaseModel): - """DTO para información básica de cliente/proveedor""" - - client_id: str - name: Optional[str] = None - short_name: Optional[str] = None - rfc: Optional[str] = None - tax_id: Optional[str] = None - client_or_provider: Optional[str] = None - is_active: Optional[bool] = None - - model_config = ConfigDict(from_attributes=True) - - -class ClientProviderListDTO(BaseModel): - """DTO para lista de clientes/proveedores""" - - clients: list[ClientProviderBasicDTO] - total: int - page: int - size: int - - model_config = ConfigDict(from_attributes=True) - - -class ClientProviderPaginatedResponseDTO(BaseModel): - """DTO para respuesta paginada de clientes/proveedores""" - - items: List[ClientProviderResponseDTO] - total: int - page: int - page_size: int - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/a76/clients_and_providers/models.py b/backend/api/v1/modules/a76/clients_and_providers/models.py deleted file mode 100644 index 37f45358..00000000 --- a/backend/api/v1/modules/a76/clients_and_providers/models.py +++ /dev/null @@ -1,172 +0,0 @@ -""" -Modelos ORM para gestión de clientes y proveedores -""" - -from decimal import Decimal -from typing import Optional - -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base -from sqlalchemy import ( - ForeignKey, - ForeignKeyConstraint, - Integer, - Numeric, - PrimaryKeyConstraint, - SmallInteger, - String, - Enum as PgEnum, - Boolean -) -from sqlalchemy.orm import Mapped, mapped_column, relationship -from enum import Enum - -class ClientOrProviderEnum(str, Enum): - CLIENT = "client" - PROVIDER = "provider" - BOTH = "both" - - -class ClientProvider(Base, TenantScopedMixin, TimestampMixin): - """ - Modelo para la tabla GClientesPro - Información de clientes y proveedores - """ - - __tablename__ = "clients_and_providers" - __table_args__ = ( - PrimaryKeyConstraint("id", name="clients_and_providers_pkey"), - {"schema": "a76"}, - ) - - # Primary key - id: Mapped[int] = mapped_column(Integer, primary_key=True) - - # Basic information - type_nat_foreign: Mapped[Optional[str]] = mapped_column(String(1)) # TIPO NACIONAL/EXTRANJERO - name: Mapped[Optional[str]] = mapped_column(String(256)) - short_name: Mapped[Optional[str]] = mapped_column(String(10)) - # Identificadores fiscales separados: rfc = RFC mexicano (nacional); tax_id = identificador fiscal extranjero. - # La procedencia (type_nat_foreign) rige cuál es obligatorio, pero un registro puede traer ambos. - rfc: Mapped[Optional[str]] = mapped_column(String(30)) - tax_id: Mapped[Optional[str]] = mapped_column(String(30)) - curp: Mapped[Optional[str]] = mapped_column(String(19)) - client_or_provider: Mapped[ClientOrProviderEnum] = mapped_column(PgEnum(ClientOrProviderEnum, name="entity_client_or_provider", create_type=True, native_enum=True),nullable=False) - linking: Mapped[Optional[str]] = mapped_column(String(1)) - transform_subassembly: Mapped[Optional[str]] = mapped_column(String(1)) - extra_information: Mapped[Optional[str]] = mapped_column(String(399)) - web_key: Mapped[Optional[str]] = mapped_column(String(40)) - responsible: Mapped[Optional[str]] = mapped_column(String(80)) - position: Mapped[Optional[str]] = mapped_column(String(30)) - incoterm: Mapped[Optional[str]] = mapped_column(String(19)) - is_active: Mapped[Optional[bool]] = mapped_column(Boolean) - - # Relationships - address: Mapped[Optional["ClientProviderAddress"]] = relationship( - back_populates="clients_and_providers", uselist=False, cascade="all, delete-orphan" - ) - programs: Mapped[Optional["ClientProviderPrograms"]] = relationship( - back_populates="clients_and_providers", uselist=False, cascade="all, delete-orphan" - ) - - @property - def fiscal_id(self) -> Optional[str]: - """ - Identificador fiscal efectivo según procedencia: RFC para nacionales, - TAX-ID para extranjeros. Punto único de verdad para consumidores downstream - (facturas, reportes, transmisiones) que antes leían solo `rfc`. - Extranjero ⟺ type_nat_foreign == 'E' (misma convención que DTOs/frontend); - cualquier otro valor (N, NULL, vacío) se trata como nacional. - """ - is_foreign = (self.type_nat_foreign or "").strip().upper().startswith("E") - return self.tax_id if is_foreign else self.rfc - - -class ClientProviderAddress(Base, TenantScopedMixin, TimestampMixin): - """ - Modelo para la tabla GClientesPro_Direccion - Dirección de clientes y proveedores - """ - - __tablename__ = "clients_and_providers_address" - __table_args__ = ( - PrimaryKeyConstraint("id", name="clients_and_providers_address_pkey"), - ForeignKeyConstraint( - ["client_id"], - ["a76.clients_and_providers.id"], - ondelete="CASCADE", - name="fk_clients_and_providers_address_client", - ), - {"schema": "a76"}, - ) - - # Primary key (foreign key) - id: Mapped[int] = mapped_column(Integer, primary_key=True) - client_id: Mapped[int] = mapped_column( - Integer, ForeignKey("a76.clients_and_providers.id", ondelete="CASCADE") - ) - - # Address information - municipality: Mapped[Optional[str]] = mapped_column(String(150)) - streets: Mapped[Optional[str]] = mapped_column(String(100)) - neighborhood: Mapped[Optional[str]] = mapped_column(String(40)) - interior_number: Mapped[Optional[str]] = mapped_column(String(20)) - exterior_number: Mapped[Optional[str]] = mapped_column(String(20)) - postal_code: Mapped[Optional[str]] = mapped_column(String(15)) - city: Mapped[Optional[str]] = mapped_column(String(30)) - state: Mapped[Optional[str]] = mapped_column(String(30)) - country: Mapped[Optional[str]] = mapped_column(String(3)) - phone: Mapped[Optional[str]] = mapped_column(String(30)) - fax_number: Mapped[Optional[str]] = mapped_column(String(30)) - email: Mapped[Optional[str]] = mapped_column(String(100)) - contact: Mapped[Optional[str]] = mapped_column(String(50)) - reference: Mapped[Optional[str]] = mapped_column(String(250)) - - # Relationship - clients_and_providers: Mapped["ClientProvider"] = relationship(back_populates="address") - - -class ClientProviderPrograms(Base, TenantScopedMixin, TimestampMixin): - """ - Modelo para la tabla GClientesPro_Programas - Programas de clientes y proveedores - """ - - __tablename__ = "clients_and_providers_programs" - __table_args__ = ( - PrimaryKeyConstraint("id", name="clients_and_providers_programs_pkey"), - ForeignKeyConstraint( - ["client_id"], - ["a76.clients_and_providers.id"], - ondelete="CASCADE", - name="fk_clients_and_providers_programs_client", - ), - {"schema": "a76"}, - ) - - # Primary key (foreign key) - id: Mapped[int] = mapped_column(Integer, primary_key=True) - client_id: Mapped[int] = mapped_column( - Integer, ForeignKey("a76.clients_and_providers.id", ondelete="CASCADE") - ) - - # Program information - program: Mapped[Optional[str]] = mapped_column(String(7)) - program_number: Mapped[Optional[str]] = mapped_column(String(40)) - prosec: Mapped[Optional[str]] = mapped_column(String(8)) - prosec_authorization: Mapped[Optional[str]] = mapped_column(String(20)) - secon_auth_date: Mapped[Optional[int]] = mapped_column(Integer) - manufacturer_id: Mapped[Optional[str]] = mapped_column(String(25)) - broker: Mapped[Optional[str]] = mapped_column(String(6)) - import_broker: Mapped[Optional[str]] = mapped_column(String(6)) - transfer_key: Mapped[Optional[str]] = mapped_column(String(8)) - secon_authorization: Mapped[Optional[str]] = mapped_column(String(20)) - applied_proportion: Mapped[Optional[Decimal]] = mapped_column(Numeric(7, 2)) - is_certified_company: Mapped[Optional[str]] = mapped_column(String(1)) - certified_company_registry: Mapped[Optional[str]] = mapped_column(String(40)) - donation_auth_number: Mapped[Optional[str]] = mapped_column(String(50)) - ctpat_svi: Mapped[Optional[str]] = mapped_column(String(100)) - tax_registry_number: Mapped[Optional[str]] = mapped_column(String(40)) - subassembly_service: Mapped[Optional[int]] = mapped_column(SmallInteger) - autse_dates: Mapped[Optional[int]] = mapped_column() - autse_number: Mapped[Optional[str]] = mapped_column(String(300)) - - # Relationship - clients_and_providers: Mapped["ClientProvider"] = relationship(back_populates="programs") diff --git a/backend/api/v1/modules/a76/clients_and_providers/routes.py b/backend/api/v1/modules/a76/clients_and_providers/routes.py deleted file mode 100644 index 9eb49246..00000000 --- a/backend/api/v1/modules/a76/clients_and_providers/routes.py +++ /dev/null @@ -1,188 +0,0 @@ -""" -Endpoints API para gestión de clientes y proveedores -""" - -from typing import List, Optional - -from core.database import get_core_db -from core.security import get_current_user, validate_access_to_resource -from fastapi import APIRouter, Depends, HTTPException, Query -from sqlalchemy import func, or_ -from sqlalchemy.orm import Session, joinedload -from api.v1.common.tenant_crud_routes import TenantCRUDRoutes -from .models import ClientOrProviderEnum - -from .dto import ( - ClientProviderBasicDTO, - ClientProviderCreateDTO, - ClientProviderResponseDTO, - ClientProviderUpdateDTO, - ClientProviderPaginatedResponseDTO, -) -from .service import ClientProviderService -from .models import ClientProvider -from api.v1.modules.a76.layouts_csv.clients_and_providers.routes import router as imports_router - -# Create main router to add custom endpoints -router = APIRouter(prefix="/clients-providers") - -# CSV import (mismo flujo que customs_brokers/imports: upload → scan → commit) -router.include_router(imports_router, prefix="/imports", tags=["clients_and_providers / csv_import"]) - - -@router.get("/", response_model=ClientProviderPaginatedResponseDTO) -async def get_clients_and_providers( - company_id: int = Query(..., description="Company ID"), - name: Optional[str] = Query(None, description="Filter by name (contains)"), - rfc: Optional[str] = Query(None, description="Filter by RFC (contains)"), - tax_id: Optional[str] = Query(None, description="Filter by TAX-ID (contains)"), - short_name: Optional[str] = Query( - None, description="Filter by short name / clave (exact match, case-insensitive)" - ), - type: Optional[ClientOrProviderEnum] = Query( - None, description="Type of entity (client or provider)" - ), - active: Optional[bool] = Query(None, description="Active status"), - page: Optional[int] = Query(None, ge=1, description="Page number (1-based)"), - page_size: Optional[int] = Query( - None, ge=1, le=1000, description="Page size when using page-based pagination" - ), - skip: int = Query(0, ge=0), - limit: int = Query(100, ge=1, le=1000), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - """Get clients and providers""" - tenant_id = validate_access_to_resource(db, company_id, current_user, ["partners_mgmt.view"]) - - resolved_limit = page_size if page_size is not None else limit - resolved_skip = ((page - 1) * resolved_limit) if page is not None else skip - - query = db.query(ClientProvider).options( - joinedload(ClientProvider.address), - joinedload(ClientProvider.programs) - ).filter( - ClientProvider.tenant_id == tenant_id, - ClientProvider.company_id == company_id, - ) - - if name: - query = query.filter(ClientProvider.name.ilike(f"%{name.strip()}%")) - - if rfc: - query = query.filter(ClientProvider.rfc.ilike(f"%{rfc.strip()}%")) - - if tax_id: - query = query.filter(ClientProvider.tax_id.ilike(f"%{tax_id.strip()}%")) - - if short_name: - sn = short_name.strip().upper() - query = query.filter(func.upper(ClientProvider.short_name) == sn) - - if type is not None: - # Include 'both' type when filtering by client or provider - query = query.filter( - or_( - ClientProvider.client_or_provider == type, - ClientProvider.client_or_provider == ClientOrProviderEnum.BOTH - ) - ) - - if active is not None: - query = query.filter(ClientProvider.is_active == active) - - total = query.count() - clients = query.offset(resolved_skip).limit(resolved_limit).all() - - return { - "items": [ClientProviderResponseDTO.model_validate(c) for c in clients], - "total": total, - "page": (resolved_skip // resolved_limit) + 1, - "page_size": resolved_limit, - } - -@router.get("/{client_id}/basic", response_model=ClientProviderBasicDTO) -async def get_clients_and_providers_basic_info( - client_id: int, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - """Get basic information for a client/provider (without address and programs)""" - tenant_id = validate_access_to_resource(db, company_id, current_user, ["partners_mgmt.view"]) - - client = ClientProviderService.get_by_id(db, client_id, tenant_id, company_id) - if not client: - raise HTTPException(status_code=404, detail="Client/Provider not found") - - return ClientProviderBasicDTO.model_validate(client) - - -@router.post("/", response_model=ClientProviderResponseDTO) -async def create_client_provider( - client_data: ClientProviderCreateDTO, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - """Create a new client/provider""" - tenant_id = validate_access_to_resource(db, company_id, current_user, ["partners_mgmt.create"]) - - return ClientProviderService.create(db, client_data, tenant_id, company_id) - - -@router.patch("/{client_id}", response_model=ClientProviderResponseDTO) -async def update_client_provider( - client_id: int, - client_data: ClientProviderUpdateDTO, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - """Update a client/provider""" - tenant_id = validate_access_to_resource(db, company_id, current_user, ["partners_mgmt.edit"]) - - client = ClientProviderService.update( - db, client_id, tenant_id, company_id, client_data - ) - if not client: - raise HTTPException(status_code=404, detail="Client/Provider not found") - - return client - -@router.get("/{client_id}", response_model=ClientProviderResponseDTO) -async def get_client_provider_detail( - client_id: int, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - """ - Obtener un cliente/proveedor completo por ID. - Esta es la ruta que tu formulario necesita para cargar los datos. - """ - tenant_id = validate_access_to_resource(db, company_id, current_user, ["partners_mgmt.view"]) - - # Usamos el servicio para buscar por ID - client = ClientProviderService.get_by_id(db, client_id, tenant_id, company_id) - - if not client: - raise HTTPException(status_code=404, detail="Client/Provider not found") - - return client - -@router.delete("/{client_id}", response_model=bool) -async def delete_client_provider( - client_id: int, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - """Delete a client/provider""" - tenant_id = validate_access_to_resource(db, company_id, current_user, ["partners_mgmt.delete"]) - - success = ClientProviderService.delete(db, client_id, tenant_id, company_id) - if not success: - raise HTTPException(status_code=404, detail="Client/Provider not found") - - return success diff --git a/backend/api/v1/modules/a76/clients_and_providers/service.py b/backend/api/v1/modules/a76/clients_and_providers/service.py deleted file mode 100644 index 745c407d..00000000 --- a/backend/api/v1/modules/a76/clients_and_providers/service.py +++ /dev/null @@ -1,505 +0,0 @@ -""" -Capa de servicio para lógica de negocio de clientes y proveedores -""" - -import logging -from typing import List, Optional, Tuple, Dict, Any - -from fastapi import HTTPException -from sqlalchemy import or_ -from sqlalchemy.exc import IntegrityError -from sqlalchemy.orm import Session, joinedload -from .models import ClientOrProviderEnum - -from .dto import ( - ClientProviderBasicDTO, - ClientProviderCreateDTO, - ClientProviderListDTO, - ClientProviderResponseDTO, - ClientProviderUpdateDTO, -) -from .models import ClientProvider, ClientProviderAddress, ClientProviderPrograms - -logger = logging.getLogger(__name__) - - -class ClientProviderService: - """Servicio para gestión de clientes y proveedores""" - - def __init__(self, db: Session): - self.db = db - - # Métodos para TenantCRUDRoutes - @staticmethod - def get_all( - db: Session, - tenant_id: int, - company_id: Optional[int], - skip: int = 0, - limit: int = 50, - filters: Optional[Dict[str, Any]] = None, - sort_by: Optional[str] = None, - sort_order: Optional[str] = "asc", - ) -> Tuple[List[ClientProvider], int]: - """Get all clients/providers for a tenant with pagination""" - query = db.query(ClientProvider).filter(ClientProvider.tenant_id == tenant_id) - - if company_id is not None: - query = query.filter(ClientProvider.company_id == company_id) - - # Apply filters if provided - if filters: - if filters.get("search"): - search_pattern = f"%{filters['search']}%" - query = query.filter( - or_( - ClientProvider.name.ilike(search_pattern), - ClientProvider.short_name.ilike(search_pattern), - ClientProvider.rfc.ilike(search_pattern), - ClientProvider.tax_id.ilike(search_pattern), - ) - ) - if filters.get("client_or_provider"): - - query = query.filter( - or_( - ClientProvider.client_or_provider == filters["client_or_provider"], - ClientProvider.client_or_provider == ClientOrProviderEnum.BOTH - ) - ) - if filters.get("status"): - enabled = 1 if filters["status"] == "enabled" else 0 - query = query.filter(ClientProvider.is_active == enabled) - - # Apply sorting - if sort_by: - column = getattr(ClientProvider, sort_by, None) - if column: - if sort_order == "desc": - query = query.order_by(column.desc()) - else: - query = query.order_by(column.asc()) - else: - query = query.order_by(ClientProvider.id.desc()) - - total = query.count() - clients = ( - query.options( - joinedload(ClientProvider.address), joinedload(ClientProvider.programs) - ) - .offset(skip) - .limit(limit) - .all() - ) - - return clients, total - - @staticmethod - def get_by_id( - db: Session, client_id: int, tenant_id: int, company_id: int - ) -> Optional[ClientProvider]: - """Get client/provider by ID""" - return ( - db.query(ClientProvider) - .options( - joinedload(ClientProvider.address), joinedload(ClientProvider.programs) - ) - .filter( - ClientProvider.id == client_id, - ClientProvider.tenant_id == tenant_id, - ClientProvider.company_id == company_id, - ) - .first() - ) - - @staticmethod - def create( - db: Session, - client_data: ClientProviderCreateDTO, - tenant_id: int, - company_id: int, - ) -> ClientProvider: - """Create a new client/provider""" - try: - # Create main client/provider - data_dict = client_data.model_dump(exclude={"address", "programs"}) - db_client = ClientProvider( - **data_dict, tenant_id=tenant_id, company_id=company_id - ) - - db.add(db_client) - db.flush() - - # Create address if provided - if client_data.address: - db_address = ClientProviderAddress( - tenant_id=tenant_id, - company_id=company_id, - client_id=db_client.id, - **client_data.address.model_dump(exclude_unset=True), - ) - db.add(db_address) - - # Create programs if provided - if client_data.programs: - db_programs = ClientProviderPrograms( - tenant_id=tenant_id, - company_id=company_id, - client_id=db_client.id, - **client_data.programs.model_dump(exclude_unset=True), - ) - db.add(db_programs) - - db.commit() - db.refresh(db_client) - - return db_client - - except IntegrityError as e: - db.rollback() - logger.error(f"IntegrityError creating client/provider: {str(e)}") - raise HTTPException( - status_code=400, detail="Client/Provider already exists" - ) - except Exception as e: - db.rollback() - logger.error(f"Error creating client/provider: {str(e)}") - raise HTTPException( - status_code=500, detail="Error creating client/provider" - ) - - @staticmethod - def update( - db: Session, - client_id: int, - tenant_id: int, - company_id: int, - client_data: ClientProviderUpdateDTO, - ) -> Optional[ClientProvider]: - """Update a client/provider""" - client = ClientProviderService.get_by_id(db, client_id, tenant_id, company_id) - if not client: - return None - - try: - # Update main fields - update_data = client_data.model_dump( - exclude_unset=True, exclude={"address", "programs"} - ) - for field, value in update_data.items(): - setattr(client, field, value) - - # Update address - if client_data.address: - if client.address: - address_data = client_data.address.model_dump(exclude_unset=True) - for field, value in address_data.items(): - setattr(client.address, field, value) - else: - db_address = ClientProviderAddress( - client_id=client.id, - tenant_id=tenant_id, - company_id=company_id, - **client_data.address.model_dump(exclude_unset=True), - ) - db.add(db_address) - - # Update programs - if client_data.programs: - if client.programs: - programs_data = client_data.programs.model_dump(exclude_unset=True) - for field, value in programs_data.items(): - setattr(client.programs, field, value) - else: - db_programs = ClientProviderPrograms( - client_id=client.id, - tenant_id=tenant_id, - company_id=company_id, - **client_data.programs.model_dump(exclude_unset=True), - ) - db.add(db_programs) - - db.commit() - db.refresh(client) - - return client - - except Exception as e: - db.rollback() - logger.error(f"Error updating client/provider {client_id}: {str(e)}") - raise HTTPException( - status_code=500, detail="Error updating client/provider" - ) - - @staticmethod - def delete(db: Session, client_id: int, tenant_id: int, company_id: int) -> bool: - """Delete a client/provider""" - client = ClientProviderService.get_by_id(db, client_id, tenant_id, company_id) - if not client: - return False - - try: - db.delete(client) - db.commit() - return True - except Exception as e: - db.rollback() - logger.error(f"Error deleting client/provider {client_id}: {str(e)}") - raise HTTPException( - status_code=500, detail="Error deleting client/provider" - ) - - # Legacy methods for custom endpoints - - def create_clients_and_providers_legacy( - self, company_id: int, client_data: ClientProviderCreateDTO - ) -> ClientProviderResponseDTO: - """Legacy method for creating client/provider""" - try: - # Create main client/provider - data_dict = client_data.model_dump(exclude={"address", "programs"}) - db_client = ClientProvider(**data_dict) - self.db.add(db_client) - self.db.flush() - - # Create address if provided - if client_data.address: - db_address = ClientProviderAddress( - company_id=company_id, - client_id=db_client.id, - **client_data.address.model_dump(exclude_unset=True), - ) - self.db.add(db_address) - - # Create programs if provided - if client_data.programs: - db_programs = ClientProviderPrograms( - company_id=company_id, - client_id=db_client.id, - **client_data.programs.model_dump(exclude_unset=True), - ) - self.db.add(db_programs) - - self.db.commit() - - return self._get_client_with_relations(db_client.id) - - except IntegrityError as e: - self.db.rollback() - logger.error(f"IntegrityError creating client/provider: {str(e)}") - raise HTTPException( - status_code=400, detail="Client/Provider with this ID already exists" - ) - except HTTPException: - raise - except Exception as e: - self.db.rollback() - logger.error(f"Error creating client/provider: {str(e)}") - raise HTTPException( - status_code=500, detail="Error creating client/provider" - ) - - def get_clients_and_providers( - self, client_id: str - ) -> Optional[ClientProviderResponseDTO]: - """ - Obtiene un cliente/proveedor por ID - - Args: - client_id: ID del cliente/proveedor - - Returns: - ClientProviderResponseDTO o None si no existe - """ - return self._get_client_with_relations(client_id) - - def _get_client_with_relations( - self, client_id: str - ) -> Optional[ClientProviderResponseDTO]: - """Método privado para obtener cliente con relaciones""" - client = ( - self.db.query(ClientProvider) - .options( - joinedload(ClientProvider.address), joinedload(ClientProvider.programs) - ) - .filter(ClientProvider.client_id == client_id) - .first() - ) - - if not client: - return None - return ClientProviderResponseDTO.model_validate(client) - - def list_clients_providers( - self, - skip: int = 0, - limit: int = 100, - search: Optional[str] = None, - client_or_provider: Optional[str] = None, - enabled_only: bool = False, - ) -> ClientProviderListDTO: - """ - Lista clientes/proveedores con filtros - - Args: - skip: Número de registros a omitir - limit: Número máximo de registros a retornar - search: Texto de búsqueda (nombre, RFC, ID) - client_or_provider: Filtrar por tipo (client=Cliente, provider=Proveedor) - enabled_only: Si True, solo retorna activos - - Returns: - ClientProviderListDTO con la lista paginada - """ - query = self.db.query(ClientProvider) - - # Aplicar filtros - if search: - search_pattern = f"%{search}%" - query = query.filter( - or_( - ClientProvider.name.ilike(search_pattern), - ClientProvider.short_name.ilike(search_pattern), - ClientProvider.rfc.ilike(search_pattern), - ClientProvider.tax_id.ilike(search_pattern), - ClientProvider.client_id.ilike(search_pattern), - ) - ) - - if client_or_provider: - query = query.filter( - or_( - ClientProvider.client_or_provider == client_or_provider, - ClientProvider.client_or_provider == ClientOrProviderEnum.BOTH - ) - ) - - if enabled_only: - query = query.filter(ClientProvider.is_active == 1) - - # Contar total - total = query.count() - - # Aplicar paginación - clients = query.offset(skip).limit(limit).all() - - # Convertir a DTOs básicos - client_dtos = [ - ClientProviderBasicDTO.model_validate(client) for client in clients - ] - - return ClientProviderListDTO( - clients=client_dtos, - total=total, - page=(skip // limit) + 1 if limit > 0 else 1, - size=len(client_dtos), - ) - - def update_clients_and_providers( - self, client_id: str, client_data: ClientProviderUpdateDTO - ) -> Optional[ClientProviderResponseDTO]: - """ - Actualiza un cliente/proveedor - - Args: - client_id: ID del cliente/proveedor a actualizar - client_data: Datos a actualizar - - Returns: - ClientProviderResponseDTO actualizado o None si no existe - """ - client = ( - self.db.query(ClientProvider) - .filter(ClientProvider.client_id == client_id) - .first() - ) - if not client: - return None - - try: - # Actualizar campos del cliente principal - update_data = client_data.model_dump( - exclude_unset=True, exclude={"address", "programs"} - ) - for field, value in update_data.items(): - setattr(client, field, value) - - # Actualizar dirección - if client_data.address: - address = ( - self.db.query(ClientProviderAddress) - .filter(ClientProviderAddress.client_id == client_id) - .first() - ) - if address: - # Actualizar dirección existente - address_data = client_data.address.model_dump(exclude_unset=True) - for field, value in address_data.items(): - setattr(address, field, value) - else: - # Crear nueva dirección - address = ClientProviderAddress( - client_id=client_id, - **client_data.address.model_dump(exclude_unset=True), - ) - self.db.add(address) - - # Actualizar programas - if client_data.programs: - programs = ( - self.db.query(ClientProviderPrograms) - .filter(ClientProviderPrograms.client_id == client_id) - .first() - ) - if programs: - # Actualizar programas existentes - programs_data = client_data.programs.model_dump(exclude_unset=True) - for field, value in programs_data.items(): - setattr(programs, field, value) - else: - # Crear nuevos programas - programs = ClientProviderPrograms( - client_id=client_id, - **client_data.programs.model_dump(exclude_unset=True), - ) - self.db.add(programs) - - self.db.commit() - - return self._get_client_with_relations(client_id) - - except Exception as e: - self.db.rollback() - logger.error(f"Error updating client/provider {client_id}: {str(e)}") - raise HTTPException( - status_code=500, detail="Error updating client/provider" - ) - - def delete_clients_and_providers(self, client_id: str) -> bool: - """ - Elimina un cliente/proveedor - - Args: - client_id: ID del cliente/proveedor a eliminar - - Returns: - True si se eliminó, False si no existe - """ - client = ( - self.db.query(ClientProvider) - .filter(ClientProvider.client_id == client_id) - .first() - ) - if not client: - return False - - try: - self.db.delete(client) # Las relaciones se eliminan en cascada - self.db.commit() - return True - except Exception as e: - self.db.rollback() - logger.error(f"Error deleting client/provider {client_id}: {str(e)}") - raise HTTPException( - status_code=500, detail="Error deleting client/provider" - ) \ No newline at end of file diff --git a/backend/api/v1/modules/a76/clients_and_providers/validators.py b/backend/api/v1/modules/a76/clients_and_providers/validators.py deleted file mode 100644 index 523bec74..00000000 --- a/backend/api/v1/modules/a76/clients_and_providers/validators.py +++ /dev/null @@ -1,27 +0,0 @@ -""" -Validadores de formato para RFC (Nacional) y TAX-ID (Extranjero) en clientes y proveedores. -""" - -import re - -# Formato RFC México: 3-4 letras (A-Z, &, Ñ), 6 dígitos (fecha), 3 caracteres homoclave. Ej: XAXX010101000 -RFC_PATTERN = re.compile(r"^[A-Z&Ñ]{3,4}\d{6}[A-Z0-9]{3}$", re.IGNORECASE) - -# TAX-ID extranjero: 2 dígitos, guión, resto alfanumérico. Ej: 12-3456789 (EIN US). Total máx 30. -TAX_ID_PATTERN = re.compile(r"^\d{2}-[A-Z0-9]{1,27}$", re.IGNORECASE) - - -def is_valid_rfc(value: str) -> bool: - """Valida formato RFC mexicano. Acepta cadena vacía/None como inválida (no opcional aquí).""" - if not value or not isinstance(value, str): - return False - normalized = value.strip().upper() - return bool(normalized and RFC_PATTERN.match(normalized)) - - -def is_valid_tax_id(value: str) -> bool: - """Valida formato TAX-ID (extranjero): 2 dígitos, guión y resto alfanumérico. Ej: 12-3456789.""" - if not value or not isinstance(value, str): - return False - normalized = value.strip() - return bool(normalized and len(normalized) <= 30 and TAX_ID_PATTERN.match(normalized)) diff --git a/backend/api/v1/modules/a76/common/sorting.py b/backend/api/v1/modules/a76/common/sorting.py deleted file mode 100644 index 519cf888..00000000 --- a/backend/api/v1/modules/a76/common/sorting.py +++ /dev/null @@ -1,61 +0,0 @@ -from typing import Any, Optional, Type -from sqlalchemy import asc, desc, inspect -from sqlalchemy.orm import Query, RelationshipProperty - -def apply_sorting( - query: Query, - model: Type[Any], - sort_by: Optional[str] = None, - sort_desc: bool = True, - default_sort_col: str = "created_at" -) -> Query: - """ - Applies dynamic sorting to a SQLAlchemy query. - Supports nested attributes via dot notation (e.g., 'compliance_mx.remesa'). - Automatically handles joins if necessary. - """ - if not sort_by: - if hasattr(model, default_sort_col): - col = getattr(model, default_sort_col) - return query.order_by(desc(col)) - return query - - try: - parts = sort_by.split('.') - current_model = model - - # Traverse relationships if dot notation is used - for i, part in enumerate(parts[:-1]): - # Check if relationship exists - mapper = inspect(current_model) - if part in mapper.relationships: - rel = mapper.relationships[part] - # Join the relationship - query = query.join(rel.entity.class_) - current_model = rel.entity.class_ - else: - # If part is not a relationship, we can't go deeper - # Fallback to default sorting - if hasattr(model, default_sort_col): - return query.order_by(desc(getattr(model, default_sort_col))) - return query - - # The last part is the actual column - last_part = parts[-1] - if hasattr(current_model, last_part): - col = getattr(current_model, last_part) - if sort_desc: - query = query.order_by(desc(col)) - else: - query = query.order_by(asc(col)) - else: - # Fallback to default sorting on the original model - if hasattr(model, default_sort_col): - query = query.order_by(desc(getattr(model, default_sort_col))) - except Exception as e: - # Log error or handle gracefully - print(f"Error applying sort for {sort_by}: {e}") - if hasattr(model, default_sort_col): - query = query.order_by(desc(getattr(model, default_sort_col))) - - return query diff --git a/backend/api/v1/modules/a76/csv_templates/__init__.py b/backend/api/v1/modules/a76/csv_templates/__init__.py deleted file mode 100644 index 0bb939f9..00000000 --- a/backend/api/v1/modules/a76/csv_templates/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# CSV templates: generate CSV from code (no physical XLS/XLSX files) diff --git a/backend/api/v1/modules/a76/csv_templates/registry.py b/backend/api/v1/modules/a76/csv_templates/registry.py deleted file mode 100644 index 42224cc6..00000000 --- a/backend/api/v1/modules/a76/csv_templates/registry.py +++ /dev/null @@ -1,423 +0,0 @@ -""" -Registro central de plantillas CSV: template_id -> lista de cabeceras canónicas. -Construido a partir de los TEMPLATE_COLUMNS de cada módulo de imports. -""" -import csv -import io -from typing import Dict, List, Optional, Set - -# Importar configs de cada módulo -from api.v1.modules.a76.layouts_csv.facturas.template_config import ( - _resolve_template_columns as resolve_imports_template, -) -from api.v1.modules.a76.layouts_csv.parts.template_config import ( - TEMPLATE_COLUMNS as PARTS_TEMPLATE_COLUMNS, - TEMPLATE_DOWNLOAD_HEADERS as PARTS_TEMPLATE_DOWNLOAD_HEADERS, -) -from api.v1.modules.a76.layouts_csv.boms.template_config import TEMPLATE_COLUMNS as BOMS_TEMPLATE_COLUMNS -from api.v1.modules.a76.layouts_csv.classes.template_config import ( - TEMPLATE_COLUMNS as CLASSES_TEMPLATE_COLUMNS, - TEMPLATE_DOWNLOAD_HEADERS as CLASSES_TEMPLATE_DOWNLOAD_HEADERS, -) -from api.v1.modules.a76.layouts_csv.customs_brokers.template_config import ( - TEMPLATE_COLUMNS as CUSTOMS_BROKERS_TEMPLATE_COLUMNS, -) -from api.v1.modules.a76.layouts_csv.clients_and_providers.template_config import ( - TEMPLATE_COLUMNS as CLIENTS_PROVIDERS_TEMPLATE_COLUMNS, -) -from api.v1.modules.a76.layouts_csv.exchange_rate.template_config import ( - TEMPLATE_COLUMNS as EXCHANGE_RATE_TEMPLATE_COLUMNS, -) -from api.v1.modules.a76.layouts_csv.us_tariff_fractions.template_config import ( - TEMPLATE_COLUMNS as US_TARIFF_FRACTIONS_TEMPLATE_COLUMNS, -) -from api.v1.modules.a76.layouts_csv.pedmientos.template_config import ( - TEMPLATE_COLUMNS as PEDIMENTOS_TEMPLATE_COLUMNS, -) -from api.v1.modules.a76.layouts_csv.vehicles.template_config import ( - TEMPLATE_COLUMNS as VEHICLES_TEMPLATE_COLUMNS, -) -from api.v1.modules.a76.layouts_csv.drivers.template_config import ( - TEMPLATE_COLUMNS as DRIVERS_TEMPLATE_COLUMNS, -) -from api.v1.modules.a76.layouts_csv.trailers.template_config import ( - TEMPLATE_COLUMNS as TRAILERS_TEMPLATE_COLUMNS, -) -from api.v1.modules.a76.layouts_csv.transportistas.template_config import ( - TEMPLATE_COLUMNS as TRANSPORTERS_TEMPLATE_COLUMNS, -) - - -def _normalize_header_for_match(header: str) -> str: - """Normaliza cabeceras para comparación interna (sin alterar salida).""" - if not header: - return "" - cleaned = header.strip() - if cleaned.startswith("* "): - cleaned = cleaned[2:] - return cleaned.strip().upper() - - -ALWAYS_REQUIRED_HEADERS_BY_TEMPLATE: Dict[str, set[str]] = { - # Facturas encabezados - "imp_temp_header": { - "NUMERO FACTURA", - "FECHA FACTURA", - "REGIMEN", - "CLAVE PROVEEDOR", - "CLAVE VENDIDO A", - "CLAVE ENVIADO A", - "AGENTE ADUANAL", - "ADUANA DE CRUCE", - }, - "imp_def_header": { - "NUMERO FACTURA", - "FECHA FACTURA", - "REGIMEN", - "CLAVE PROVEEDOR", - "CLAVE VENDIDO A", - "CLAVE ENVIADO A", - "AGENTE ADUANAL", - }, - "exp_def_header": { - "NUMERO FACTURA", - "FECHA FACTURA", - "REGIMEN", - "CLAVE PROVEEDOR", - "CLAVE VENDIDO A", - "CLAVE ENVIADO A", - "AGENTE ADUANAL", - "ADUANA DE CRUCE", - }, - "cmex_header": { - "NUMERO FACTURA", - "FECHA FACTURA", - "CLAVE PROVEEDOR", - "CLAVE VENDIDO A", - "CLAVE ENVIADO A", - }, - # Facturas partidas (plantillas del registry) - "imp_temp_details": { - "NUMERO FACTURA", - "CLASE", - "CANTIDAD IMPORTADA", - "COSTO UNITARIO", - "PESO NETO", - "PAIS ORIGEN", - "PREFERENCIA ARANCELARIA", - }, - "imp_def_details": { - "NUMERO FACTURA", - "CLASE", - "CANTIDAD IMPORTADA", - "COSTO UNITARIO", - "PESO NETO", - "PAIS ORIGEN", - "PREFERENCIA ARANCELARIA", - "NUM. PARTE", - }, - "exp_def_details": { - "NUMERO FACTURA", - "CLASE", - "CANTIDAD IMPORTADA", - "COSTO UNITARIO", - "PESO NETO", - "PAIS ORIGEN", - "PREFERENCIA ARANCELARIA", - }, - "cmex_details": { - "NUMERO FACTURA", - "CLASE", - "CANTIDAD IMPORTADA", - "COSTO UNITARIO", - "PESO NETO", - "PAIS ORIGEN", - "PREFERENCIA ARANCELARIA", - "NUM. PARTE", - }, - # Facturas series (plantillas del registry) - "imp_temp_series": {"NUMERO FACTURA", "LINEA FACTURA"}, - "imp_def_series": {"NUMERO FACTURA", "LINEA FACTURA"}, - "cmex_series": {"NUMERO FACTURA", "LINEA FACTURA"}, - "exp_def_series": {"NUMERO FACTURA", "LINEA FACTURA"}, - # Catálogos y transportes - "customs_brokers": {"TIPO", "CLAVE", "NOMBRE"}, - "clients_providers": {"PROCEDENCIA", "SHORT_NAME", "NOMBRE", "RFC"}, - "exchange_rates": {"FECHA", "VALOR"}, - "american_fractions": {"FRACCION_ARANCELARIA", "DESCRIPCION"}, - "material_classes": { - "CLAVE CLASE", - "CLASE", - "DESCRIPCION ESPAÑOL", - "DESCRIPCIONE", - "TIPO DE MATERIAL", - "CLAVEMAT", - "U.M. COMERCIAL", - "UNIMED", - "FRACCION ARANCELARIA", - "FRACCION", - }, - "part_numbers": { - "NUMERO DE PARTE", - "NUMPARTE", - "DESCRIPCION EN ESPAÑOL", - "DESCRIPCIONE", - "UNIDAD DE MEDIDA COMERCIAL", - "UNIMED", - }, - "items": { - "NUMERO DE PARTE", - "NUMPARTE", - "DESCRIPCION EN ESPAÑOL", - "DESCRIPCIONE", - "UNIDAD DE MEDIDA COMERCIAL", - "UNIMED", - }, - "boms": {"NUMPARTE_PADRE", "NUMPARTE_COMPONENTE", "CANTIDAD"}, - "pedimentos": { - "AÑO", "PATENTE", "NUMERO", "PEDIMENTO", - "TIPO_OPERACION", - "CLAVE_PEDIMENTO", - "REGIMEN", - "FECHA_INICIO", - "FECHA_FINAL", - "FECHA_PAGO", - "ADUANA_SECCION_CRUCE", - }, - "transports": {"CLAVE", "CODIGO DE ENTIDAD"}, - "drivers": {"TRANSPORTISTA", "LINEA", "CLAVE CONDUCTOR"}, - "trailers": {"NUMERO TRAILER"}, - "transporters": {"CLAVE TRANSPORTISTA", "NOMBRE"}, -} - - -def _compute_required_indices_map(base_rows: Dict[str, List[str]]) -> Dict[str, Set[int]]: - """Índices de columnas obligatorias (según plantilla ES/base), para marcar * en cualquier idioma.""" - out: Dict[str, Set[int]] = {} - for tid, headers in base_rows.items(): - req = ALWAYS_REQUIRED_HEADERS_BY_TEMPLATE.get(tid) - if not req: - continue - req_norm = {_normalize_header_for_match(h) for h in req} - idx_set: Set[int] = set() - for i, h in enumerate(headers): - if not h: - continue - if _normalize_header_for_match(h) in req_norm: - idx_set.add(i) - out[tid] = idx_set - return out - - -def _apply_required_prefix_indices(template_id: str, headers: List[str]) -> List[str]: - """Prefija '* ' usando índices fijos de la plantilla base (independiente del idioma de cabecera).""" - idx_set = _REQUIRED_INDICES.get(template_id) - if not idx_set: - return headers - out: List[str] = [] - for i, h in enumerate(headers): - if i in idx_set and h and not str(h).startswith("* "): - out.append(f"* {h}") - else: - out.append(h) - return out - - -def _canonicals_from_columns(cols: Optional[List[Dict]]) -> List[str]: - """Extrae la lista de nombres canónicos en orden a partir de una lista de columnas.""" - if not cols: - return [] - return [item["canonical"] for item in cols] - - -def _build_registry() -> Dict[str, List[str]]: - registry: Dict[str, List[str]] = {} - - # a76/imports (facturas): imp_temp_header, imp_temp_details, imp_def_*, exp_def_*, cmex_*, series - for tid in ( - "imp_temp_header", - "imp_temp_details", - "imp_temp_series", - "imp_def_header", - "imp_def_details", - "imp_def_series", - "exp_def_header", - "exp_def_details", - "exp_def_series", - "cmex_header", - "cmex_details", - "cmex_series", - ): - cols = resolve_imports_template(tid) - registry[tid] = _canonicals_from_columns(cols) - - # part_numbers (parts); "items" usa la misma plantilla - registry["part_numbers"] = ( - PARTS_TEMPLATE_DOWNLOAD_HEADERS - if PARTS_TEMPLATE_DOWNLOAD_HEADERS - else _canonicals_from_columns(PARTS_TEMPLATE_COLUMNS.get("part_numbers")) - ) - registry["items"] = ( - PARTS_TEMPLATE_DOWNLOAD_HEADERS - if PARTS_TEMPLATE_DOWNLOAD_HEADERS - else _canonicals_from_columns(PARTS_TEMPLATE_COLUMNS.get("part_numbers")) - ) - - # boms - registry["boms"] = _canonicals_from_columns(BOMS_TEMPLATE_COLUMNS.get("boms")) - - # material_classes: cabeceras de descarga según plantilla usuario (CLAVE, CLASE, DESCRIPCION, etc.) - registry["material_classes"] = ( - CLASSES_TEMPLATE_DOWNLOAD_HEADERS - if CLASSES_TEMPLATE_DOWNLOAD_HEADERS - else _canonicals_from_columns(CLASSES_TEMPLATE_COLUMNS.get("material_classes")) - ) - - # customs_brokers - registry["customs_brokers"] = _canonicals_from_columns(CUSTOMS_BROKERS_TEMPLATE_COLUMNS.get("customs_brokers")) - - # clients_providers - registry["clients_providers"] = _canonicals_from_columns(CLIENTS_PROVIDERS_TEMPLATE_COLUMNS.get("client_providers")) - - # exchange_rates - registry["exchange_rates"] = _canonicals_from_columns(EXCHANGE_RATE_TEMPLATE_COLUMNS.get("exchange_rates")) - - # american_fractions - registry["american_fractions"] = _canonicals_from_columns( - US_TARIFF_FRACTIONS_TEMPLATE_COLUMNS.get("us_tariff_fractions") - ) - - # pedimentos - registry["pedimentos"] = _canonicals_from_columns(PEDIMENTOS_TEMPLATE_COLUMNS.get("pedimentos")) - - # transports (vehicles) - registry["transports"] = _canonicals_from_columns(VEHICLES_TEMPLATE_COLUMNS.get("vehicles")) - - # drivers - registry["drivers"] = _canonicals_from_columns(DRIVERS_TEMPLATE_COLUMNS.get("drivers")) - - # trailers - registry["trailers"] = _canonicals_from_columns(TRAILERS_TEMPLATE_COLUMNS.get("trailers")) - - # transporters (transportistas) - registry["transporters"] = _canonicals_from_columns(TRANSPORTERS_TEMPLATE_COLUMNS.get("transporters")) - - return registry - - -_REGISTRY_BASE_ES: Dict[str, List[str]] = _build_registry() -_REQUIRED_INDICES: Dict[str, Set[int]] = _compute_required_indices_map(_REGISTRY_BASE_ES) -_TEMPLATE_HEADERS: Dict[str, List[str]] = _REGISTRY_BASE_ES - -# Nombre de archivo sugerido para descarga (sin path) -TEMPLATE_FILENAMES: Dict[str, str] = { - "customs_brokers": "EstructuraCatAgenteAduanal.csv", - "clients_providers": "EstructuraCatClienteProv.csv", - "exchange_rates": "EstructuraCatTiposCambio.csv", - "american_fractions": "EstructuraCatFraccAme.csv", - "material_classes": "EstructuraCatClasesAF.csv", - "part_numbers": "EstructuraCatPartesAF.csv", - "items": "EstructuraCatPartesAF.csv", - "boms": "EstructuraBOMS.csv", - "pedimentos": "EstructuraCatPedimentos.csv", - "transports": "EstructuraCatTransportes.csv", - "drivers": "EstructuraCatConductor.csv", - "trailers": "EstructuraCatTrailers.csv", - "transporters": "EstructuraCatTransportistas.csv", - "imp_temp_header": "EstructuraEncFacImpoTemp.csv", - "imp_temp_details": "EstructuraParFacImpoTempAF.csv", - "imp_temp_series": "EstructuraSeriesFacImpoTemp.csv", - "imp_def_header": "EstructuraEncFacImpoDef.csv", - "imp_def_details": "EstructuraParFacImpoDefAF.csv", - "imp_def_series": "EstructuraSeriesFacImpoDef.csv", - "cmex_header": "EstructuraEncFacComprasMex.csv", - "cmex_details": "EstructuraParFacComprasMex.csv", - "cmex_series": "EstructuraSeriesFacComprasMex.csv", - "exp_def_header": "EstructuraEncFacExpoCamReg.csv", - "exp_def_details": "EstructuraParExpoCamReg.csv", - "exp_def_series": "EstructuraSeriesFacExpoCamReg.csv", -} - - -def get_download_headers(template_id: str, locale: Optional[str] = "es") -> Optional[List[str]]: - """ - Cabeceras de descarga para la plantilla (sin prefijo *). - locale: es | en (default es). - """ - from api.v1.modules.a76.layouts_csv.common.template_locale import ( - download_header_cell, - normalize_locale, - ) - - loc = normalize_locale(locale) - - cols = resolve_imports_template(template_id) - if cols is not None: - return [download_header_cell(c, loc) for c in cols] - - if template_id in ("part_numbers", "items"): - from api.v1.modules.a76.layouts_csv.parts import template_config as parts_tc - - return parts_tc.download_headers_for_locale(loc) - - if template_id == "material_classes": - from api.v1.modules.a76.layouts_csv.classes import template_config as classes_tc - - return classes_tc.download_headers_for_locale(loc) - - # template_id del registry → (module_columns, clave interna del dict de columnas) - catalog_resolvers: list[tuple[str, dict, str]] = [ - ("boms", BOMS_TEMPLATE_COLUMNS, "boms"), - ("customs_brokers", CUSTOMS_BROKERS_TEMPLATE_COLUMNS, "customs_brokers"), - ("clients_providers", CLIENTS_PROVIDERS_TEMPLATE_COLUMNS, "client_providers"), - ("exchange_rates", EXCHANGE_RATE_TEMPLATE_COLUMNS, "exchange_rates"), - ("american_fractions", US_TARIFF_FRACTIONS_TEMPLATE_COLUMNS, "us_tariff_fractions"), - ("pedimentos", PEDIMENTOS_TEMPLATE_COLUMNS, "pedimentos"), - ("transports", VEHICLES_TEMPLATE_COLUMNS, "vehicles"), - ("drivers", DRIVERS_TEMPLATE_COLUMNS, "drivers"), - ("trailers", TRAILERS_TEMPLATE_COLUMNS, "trailers"), - ("transporters", TRANSPORTERS_TEMPLATE_COLUMNS, "transporters"), - ] - for tid, mapping, inner_key in catalog_resolvers: - if tid != template_id: - continue - cols = mapping.get(inner_key) - if cols: - return [download_header_cell(col, loc) for col in cols] - - return None - - -def get_template_headers(template_id: str) -> Optional[List[str]]: - """Cabeceras como en descarga ES histórica, con prefijo * en obligatorias.""" - raw = get_download_headers(template_id, "es") - if raw is None: - return None - return _apply_required_prefix_indices(template_id, raw) - - -def get_template_filename(template_id: str) -> str: - """Nombre de archivo sugerido para la descarga.""" - return TEMPLATE_FILENAMES.get(template_id, f"plantilla_{template_id}.csv") - - -def generate_csv_content( - template_id: str, - locale: str = "es", - include_bom: bool = True, -) -> Optional[bytes]: - """ - Genera el contenido CSV (solo fila de cabeceras) para el template_id. - UTF-8, opcionalmente con BOM para Excel. - """ - headers = get_download_headers(template_id, locale) - if not headers: - return None - headers = _apply_required_prefix_indices(template_id, headers) - buf = io.StringIO() - writer = csv.writer(buf, lineterminator="\n") - writer.writerow(headers) - content = buf.getvalue().encode("utf-8") - if include_bom: - content = b"\xef\xbb\xbf" + content - return content diff --git a/backend/api/v1/modules/a76/csv_templates/routes.py b/backend/api/v1/modules/a76/csv_templates/routes.py deleted file mode 100644 index 78e56983..00000000 --- a/backend/api/v1/modules/a76/csv_templates/routes.py +++ /dev/null @@ -1,43 +0,0 @@ -""" -Rutas para descargar plantillas CSV generadas desde código (sin archivos XLS/XLSX). -""" -from typing import Any, Dict, Optional - -from fastapi import APIRouter, Depends, HTTPException, Query -from fastapi.responses import Response - -from core.security import get_current_user - -from .registry import generate_csv_content, get_template_filename - -router = APIRouter() - -_LOCALE_ALLOWED = frozenset({"es", "en"}) - - -@router.get("/{template_id}", response_class=Response) -async def download_csv_template( - template_id: str, - locale: Optional[str] = Query("es", description="es | en: idioma de las cabeceras del CSV"), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """ - Devuelve un CSV con solo la fila de cabeceras para la plantilla indicada. - Query `locale`: es (defecto) o en — cabeceras localizadas cuando estén definidas. - """ - loc = (locale or "es").lower().strip() - if loc not in _LOCALE_ALLOWED: - raise HTTPException(status_code=400, detail="locale must be 'es' or 'en'") - content = generate_csv_content(template_id, locale=loc, include_bom=True) - if content is None: - raise HTTPException(status_code=404, detail=f"Plantilla desconocida: {template_id}") - filename = get_template_filename(template_id) - return Response( - content=content, - media_type="text/csv; charset=utf-8", - headers={ - "Content-Disposition": f'attachment; filename="{filename}"', - "Cache-Control": "private, no-store, max-age=0, must-revalidate", - "Pragma": "no-cache", - }, - ) diff --git a/backend/api/v1/modules/a76/customs_brokers/dto.py b/backend/api/v1/modules/a76/customs_brokers/dto.py deleted file mode 100644 index 64bbf65c..00000000 --- a/backend/api/v1/modules/a76/customs_brokers/dto.py +++ /dev/null @@ -1,124 +0,0 @@ -from typing import Optional - -from pydantic import BaseModel, Field - - -class CustomsBrokerBaseDTO(BaseModel): - """Base fields for CustomsBroker""" - - type: Optional[str] = None - name: Optional[str] = None - address: Optional[str] = None - postal_code: Optional[str] = None - city: Optional[str] = None - state: Optional[str] = None - phone: Optional[str] = Field(None, pattern=r"^$|^[\d\s\-\+\(\)]+$") - fax: Optional[str] = None - email: Optional[str] = Field(None, pattern=r"^$|^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$") - country: Optional[str] = None - tax_id: Optional[str] = Field(None, pattern=r"^$|^[A-Z&Ñ]{3,4}\d{6}[A-Z0-9]{3}$") - personal_id: Optional[str] = Field(None, pattern=r"^$|^[A-Z][AEIOUX][A-Z]{2}\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])[HM](AS|BC|BS|CC|CS|CH|CL|CM|DF|DG|GT|GR|HG|JC|MC|MN|MS|NT|NL|OC|PL|QT|QR|SP|SL|SR|TC|TS|TL|VZ|YN|ZS|NE)[B-DF-HJ-NP-TV-Z]{3}[0-9A-Z]\d$") - position: Optional[str] = None - license: Optional[str] = Field(None, max_length=4, pattern=r"^$|^[0-9]*[1-9][0-9]*$") - company: Optional[str] = None - contact: Optional[str] = Field(None, pattern=r"^$|^[a-zA-Z0-9\sñÑáéíóúÁÉÍÓÚ\-\.,]+$") - - -class CustomsBrokerCreateDTO(CustomsBrokerBaseDTO): - """Schema for creating a new CustomsBroker""" - - broker_key: str = Field(..., max_length=5, pattern=r"^[a-zA-Z0-9]+$", description="Clave única del agente aduanal (máx 5 caracteres)") - - -class CustomsBrokerUpdateDTO(CustomsBrokerBaseDTO): - """Schema for updating an existing CustomsBroker""" - - pass - - -class CustomsBrokerResponseDTO(CustomsBrokerBaseDTO): - """Schema for CustomsBroker response""" - - id: int - broker_key: str - tenant_id: int - company_id: int - vu: Optional["CustomsBrokerVUResponseDTO"] = None - - class Config: - from_attributes = True - - -# Legacy DTO for backwards compatibility (if needed elsewhere) -class CustomsBrokerDTO(BaseModel): - type: Optional[str] = None - broker_key: str = Field(..., max_length=5, pattern=r"^[a-zA-Z0-9]+$") - name: Optional[str] = None - address: Optional[str] = None - postal_code: Optional[str] = None - city: Optional[str] = None - state: Optional[str] = None - phone: Optional[str] = Field(None, pattern=r"^$|^[\d\s\-\+\(\)]+$") - fax: Optional[str] = None - email: Optional[str] = Field(None, pattern=r"^$|^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$") - country: Optional[str] = None - tax_id: Optional[str] = Field(None, pattern=r"^$|^[A-Z&Ñ]{3,4}\d{6}[A-Z0-9]{3}$") - personal_id: Optional[str] = Field(None, pattern=r"^$|^[A-Z][AEIOUX][A-Z]{2}\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])[HM](AS|BC|BS|CC|CS|CH|CL|CM|DF|DG|GT|GR|HG|JC|MC|MN|MS|NT|NL|OC|PL|QT|QR|SP|SL|SR|TC|TS|TL|VZ|YN|ZS|NE)[B-DF-HJ-NP-TV-Z]{3}[0-9A-Z]\d$") - position: Optional[str] = None - license: Optional[str] = Field(None, max_length=4, pattern=r"^$|^[0-9]*[1-9][0-9]*$") - company: Optional[str] = None - contact: Optional[str] = Field(None, pattern=r"^$|^[a-zA-Z0-9\sñÑáéíóúÁÉÍÓÚ\-\.,]+$") - tenant_id: str - company_id: str - - class Config: - from_attributes = True - - -class CustomsBrokerVUCreateDTO(BaseModel): - certificate_path: Optional[str] = None - key_path: Optional[str] = None - access_key: Optional[str] = None - fiel_format: Optional[str] = None - signature_read_path: Optional[str] = None - archive_path: Optional[str] = None - fiel_access_key: Optional[str] = None - web_service_user: Optional[str] = None - web_service_access_key: Optional[str] = None - vu_email: Optional[str] = None - vu_figure_type: Optional[str] = None - xml_files_path: Optional[str] = None - query_tax_id: Optional[str] = None - doda_certificate_path: Optional[str] = None - doda_key_path: Optional[str] = None - doda_web_service_user: Optional[str] = None - doda_web_service_access_key: Optional[str] = None - doda_fiel_access_key: Optional[str] = None - doda_xml_files_path: Optional[str] = None - tenant_id: Optional[int] = None - company_id: Optional[int] = None - -class CustomsBrokerVUResponseDTO(CustomsBrokerVUCreateDTO): - customs_broker_id: int - - class Config: - from_attributes = True - - -class CustomsBrokerPersonnelDTO(BaseModel): - broker_key: str = Field(..., max_length=5, pattern=r"^[a-zA-Z0-9]+$") - line: int - name: Optional[str] = None - tax_id: Optional[str] = Field(None, pattern=r"^$|^[A-Z&Ñ]{3,4}\d{6}[A-Z0-9]{3}$") - personal_id: Optional[str] = Field(None, pattern=r"^$|^[A-Z][AEIOUX][A-Z]{2}\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])[HM](AS|BC|BS|CC|CS|CH|CL|CM|DF|DG|GT|GR|HG|JC|MC|MN|MS|NT|NL|OC|PL|QT|QR|SP|SL|SR|TC|TS|TL|VZ|YN|ZS|NE)[B-DF-HJ-NP-TV-Z]{3}[0-9A-Z]\d$") - position: Optional[str] = None - license: Optional[str] = Field(None, max_length=4, pattern=r"^$|^[0-9]*[1-9][0-9]*$") - first_name: Optional[str] = None - last_name: Optional[str] = None - middle_name: Optional[str] = None - email: Optional[str] = Field(None, pattern=r"^$|^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$") - tenant_id: Optional[int] = None - company_id: Optional[int] = None - - class Config: - from_attributes = True diff --git a/backend/api/v1/modules/a76/customs_brokers/models.py b/backend/api/v1/modules/a76/customs_brokers/models.py deleted file mode 100644 index 11c29580..00000000 --- a/backend/api/v1/modules/a76/customs_brokers/models.py +++ /dev/null @@ -1,94 +0,0 @@ -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base -from sqlalchemy import Column, ForeignKey, ForeignKeyConstraint, Integer, String, UniqueConstraint, PrimaryKeyConstraint -from sqlalchemy.orm import relationship - - -class CustomsBroker(Base, TenantScopedMixin, TimestampMixin): - __tablename__ = "customs_brokers" - __table_args__ = ( - PrimaryKeyConstraint("id", name="customs_brokers_pkey"), - UniqueConstraint("broker_key", "tenant_id", "company_id", name="uq_broker_key_tenant_company"), - {"schema": "a76"}, - ) - - id = Column(Integer, primary_key=True) - type = Column(String(9), nullable=True) - broker_key = Column(String(5), nullable=False) - name = Column(String(80), nullable=True) - address = Column(String(1500), nullable=True) - postal_code = Column(String(15), nullable=True) - city = Column(String(30), nullable=True) - state = Column(String(30), nullable=True) - phone = Column(String(30), nullable=True) - fax = Column(String(30), nullable=True) - email = Column(String(100), nullable=True) - country = Column(String(3), nullable=True) - tax_id = Column(String(30), nullable=True) - personal_id = Column(String(20), nullable=True) - position = Column(String(30), nullable=True) - license = Column(String(4), nullable=True) - company = Column(String(200), nullable=True) - contact = Column(String(80), nullable=True) - - vu = relationship( - "CustomsBrokerVU", back_populates="customs_broker", cascade="all, delete", uselist=False - ) - personnel = relationship( - "CustomsBrokerPersonnel", back_populates="customs_broker", cascade="all, delete" - ) - - -class CustomsBrokerVU(Base, TenantScopedMixin, TimestampMixin): - __tablename__ = "customs_brokers_vu" - __table_args__ = {"schema": "a76"} - - customs_broker_id = Column( - Integer, - ForeignKey("a76.customs_brokers.id", ondelete="CASCADE"), - primary_key=True, - ) - certificate_path = Column(String(1499), nullable=True) - key_path = Column(String(1499), nullable=True) - access_key = Column(String(50), nullable=True) - fiel_format = Column(String(19), nullable=True) - signature_read_path = Column(String(1499), nullable=True) - archive_path = Column(String(1499), nullable=True) - fiel_access_key = Column(String(50), nullable=True) - web_service_user = Column(String(100), nullable=True) - web_service_access_key = Column(String(100), nullable=True) - vu_email = Column(String(800), nullable=True) - vu_figure_type = Column(String(29), nullable=True) - xml_files_path = Column(String(1499), nullable=True) - query_tax_id = Column(String(30), nullable=True) - doda_certificate_path = Column(String(1499), nullable=True) - doda_key_path = Column(String(1499), nullable=True) - doda_web_service_user = Column(String(100), nullable=True) - doda_web_service_access_key = Column(String(100), nullable=True) - doda_fiel_access_key = Column(String(50), nullable=True) - doda_xml_files_path = Column(String(1499), nullable=True) - - customs_broker = relationship("CustomsBroker", back_populates="vu") - - -class CustomsBrokerPersonnel(Base, TenantScopedMixin, TimestampMixin): - __tablename__ = "customs_brokers_personnel" - __table_args__ = {"schema": "a76"} - - customs_broker_id = Column( - Integer, - ForeignKey("a76.customs_brokers.id", ondelete="CASCADE"), - primary_key=True, - ) - line = Column(Integer, primary_key=True, nullable=False) - name = Column(String(80), nullable=True) - tax_id = Column(String(30), nullable=True) - personal_id = Column(String(20), nullable=True) - position = Column(String(30), nullable=True) - license = Column(String(4), nullable=True) - first_name = Column(String(80), nullable=True) - last_name = Column(String(80), nullable=True) - middle_name = Column(String(80), nullable=True) - email = Column(String(100), nullable=True) - - customs_broker = relationship("CustomsBroker", back_populates="personnel") diff --git a/backend/api/v1/modules/a76/customs_brokers/routes.py b/backend/api/v1/modules/a76/customs_brokers/routes.py deleted file mode 100644 index 74da7465..00000000 --- a/backend/api/v1/modules/a76/customs_brokers/routes.py +++ /dev/null @@ -1,403 +0,0 @@ -import logging -import mimetypes -import os -from datetime import datetime -from typing import Any, Dict, Optional - -from fastapi import APIRouter, Depends, File, HTTPException, Query, UploadFile, status -from sqlalchemy.orm import Session - -from core.config import settings -from core.database import get_core_db -from core.s3_keys import ( - customs_broker_vu_certificate_key, - customs_broker_vu_cove_key, - customs_broker_vu_doda_certificate_key, - customs_broker_vu_doda_cove_key, - customs_broker_vu_doda_private_key_key, - customs_broker_vu_private_key_key, -) -from core.security import get_current_user, get_tenant_from_token, resolve_tenant_id_required, validate_access_to_resource -from core.storage_s3 import delete_object_if_exists, put_object_bytes - -from api.v1.common.tenant_crud_routes import TenantCRUDRoutes -from . import dto, services -from ..layouts_csv.customs_brokers.routes import router as imports_router - -logger = logging.getLogger(__name__) - -router = APIRouter() - -MAX_VU_CER_KEY_BYTES = 5 * 1024 * 1024 # 5 MB -MAX_COVE_BYTES = 15 * 1024 * 1024 # 15 MB (xml/zip) - - -def _resolve_tenant_id_int( - current_user: dict, - db=None, - company_id: int = None, -): - """Delega a resolve_tenant_id_required. Hub admin resuelve tenant desde la empresa.""" - return resolve_tenant_id_required(current_user, db=db, company_id=company_id) - - -def _remove_stored_vu_path(ref: Optional[str]) -> None: - if not ref: - return - if ref.startswith("tenants/"): - delete_object_if_exists(ref) - elif os.path.isfile(ref): - try: - os.remove(ref) - except OSError: - pass - -# CSV import (mismo flujo que a76.imports: upload → scan → commit) -router.include_router(imports_router, prefix="/customs-brokers/imports", tags=["customs_brokers / csv_import"]) - -customs_broker_crud = TenantCRUDRoutes( - service=services.CustomsBrokerService, - create_schema=dto.CustomsBrokerCreateDTO, - update_schema=dto.CustomsBrokerUpdateDTO, - response_schema=dto.CustomsBrokerResponseDTO, - prefix="/customs-brokers", - tags=[], - resource_name="Customs Broker", - id_name="broker_key", - id_type=str, - enable_list=True, - list_permissions=["customs_brokers.view"], - get_permissions=["customs_brokers.view"], - create_permissions=["customs_brokers.create"], - update_permissions=["customs_brokers.edit"], - delete_permissions=["customs_brokers.delete"], -) - -router.include_router(customs_broker_crud.router) - - -@router.patch( - "/customs-brokers/{broker_key}", - response_model=dto.CustomsBrokerResponseDTO, -) -def update_customs_broker( - broker_key: str, - broker_data: dto.CustomsBrokerUpdateDTO, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """ - Actualización parcial (PATCH). - """ - tenant_id = validate_access_to_resource(db, company_id, current_user, ["customs_brokers.edit"]) - - broker = services.CustomsBrokerService.get_by_id(db, broker_key, tenant_id, company_id) - if not broker: - raise HTTPException(status_code=404, detail="Customs Broker not found") - - updated_broker = services.CustomsBrokerService.update( - db=db, - broker_key=broker_key, - tenant_id=tenant_id, - broker_data=broker_data, - company_id=company_id - ) - - if not updated_broker: - raise HTTPException(status_code=400, detail="Error updating Customs Broker") - - return updated_broker - - -@router.put( - "/customs-broker-vu/{broker_key}", - response_model=dto.CustomsBrokerVUResponseDTO, -) -def update_customs_broker_vu( - broker_key: str, - vu_data: dto.CustomsBrokerVUCreateDTO, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - tenant_id = validate_access_to_resource(db, company_id, current_user, ["customs_brokers.edit"]) - - broker = services.CustomsBrokerService.get_by_id(db, broker_key, tenant_id, company_id) - if not broker: - raise HTTPException(status_code=404, detail="Customs Broker not found") - - updated_vu = services.CustomsBrokerVUService.update_vu(db, broker_key, vu_data, tenant_id, company_id) - if not updated_vu: - raise HTTPException(status_code=404, detail="Customs Broker VU not found") - return updated_vu - - -@router.put( - "/customs-broker-personnel/{broker_key}/{line}", - response_model=dto.CustomsBrokerPersonnelDTO, -) -def update_customs_broker_personnel( - broker_key: str, - line: int, - personnel_data: dto.CustomsBrokerPersonnelDTO, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - tenant_id = validate_access_to_resource(db, company_id, current_user, ["customs_brokers.edit"]) - - broker = services.CustomsBrokerService.get_by_id(db, broker_key, tenant_id, company_id) - if not broker: - raise HTTPException(status_code=404, detail="Customs Broker not found") - - updated_personnel = services.CustomsBrokerPersonnelService.update_personnel( - db, broker_key, line, personnel_data, tenant_id, company_id - ) - if not updated_personnel: - raise HTTPException( - status_code=404, detail="Customs Broker Personnel not found" - ) - return updated_personnel - - -@router.post( - "/customs-brokers/{broker_key}/vu/upload", - summary="Sube VU/DODA (CER, KEY, COVE) al bucket bajo tenants/.../customs_brokers/{id}/...", -) -async def upload_customs_broker_vu_file( - broker_key: str, - file_kind: str = Query( - ..., - description=( - "certificate (.cer), key (.key), cove (xml/zip/txt/pdf/json), " - "doda_certificate (.cer), doda_key (.key), doda_cove (xml/zip/txt/pdf/json)" - ), - ), - company_id: int = Query(..., description="Company ID"), - file: UploadFile = File(...), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """ - Persiste el archivo bajo la misma jerarquía que logos/avatares (tenant/company/...). - Guarda la clave S3 o ruta local en: - - VU: certificate_path, key_path, xml_files_path - - DODA: doda_certificate_path, doda_key_path, doda_xml_files_path - """ - validate_access_to_resource(db, company_id, current_user, ["customs_brokers.create"]) - tenant_id = _resolve_tenant_id_int(current_user, db=db, company_id=company_id) - - broker = services.CustomsBrokerService.get_by_id(db, broker_key, tenant_id, company_id) - if not broker: - raise HTTPException(status_code=404, detail="Customs Broker not found") - - fk = file_kind.lower().strip() - if fk not in ( - "certificate", - "key", - "cove", - "doda_certificate", - "doda_key", - "doda_cove", - ): - raise HTTPException( - status_code=400, - detail=( - "file_kind must be certificate, key, cove, " - "doda_certificate, doda_key, or doda_cove" - ), - ) - - content = await file.read() - max_bytes = MAX_COVE_BYTES if fk in ("cove", "doda_cove") else MAX_VU_CER_KEY_BYTES - if len(content) > max_bytes: - raise HTTPException( - status_code=400, - detail=f"File too large (max {max_bytes // (1024 * 1024)} MB)", - ) - - file_ext = os.path.splitext(file.filename or "")[1].lower() - timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") - vu = services.CustomsBrokerVUService.ensure_vu_for_broker(db, broker) - broker_id = broker.id - - field_name: str - stored: str - - try: - if settings.use_s3_object_storage: - if fk == "certificate": - if file_ext != ".cer": - raise HTTPException(status_code=400, detail="certificate must be .cer") - key = customs_broker_vu_certificate_key( - tenant_id, company_id, broker_id, timestamp, file_ext - ) - ct = "application/x-x509-ca-cert" - field_name = "certificate_path" - elif fk == "key": - if file_ext != ".key": - raise HTTPException(status_code=400, detail="key must be .key") - key = customs_broker_vu_private_key_key( - tenant_id, company_id, broker_id, timestamp, file_ext - ) - ct = "application/pkcs8" - field_name = "key_path" - elif fk == "cove": - key = customs_broker_vu_cove_key( - tenant_id, - company_id, - broker_id, - timestamp, - file.filename or "cove.xml", - ) - ct = ( - file.content_type - or mimetypes.guess_type(file.filename or "")[0] - or "application/octet-stream" - ) - field_name = "xml_files_path" - elif fk == "doda_certificate": - if file_ext != ".cer": - raise HTTPException(status_code=400, detail="doda_certificate must be .cer") - key = customs_broker_vu_doda_certificate_key( - tenant_id, company_id, broker_id, timestamp, file_ext - ) - ct = "application/x-x509-ca-cert" - field_name = "doda_certificate_path" - elif fk == "doda_key": - if file_ext != ".key": - raise HTTPException(status_code=400, detail="doda_key must be .key") - key = customs_broker_vu_doda_private_key_key( - tenant_id, company_id, broker_id, timestamp, file_ext - ) - ct = "application/pkcs8" - field_name = "doda_key_path" - else: - key = customs_broker_vu_doda_cove_key( - tenant_id, - company_id, - broker_id, - timestamp, - file.filename or "doda.xml", - ) - ct = ( - file.content_type - or mimetypes.guess_type(file.filename or "")[0] - or "application/octet-stream" - ) - field_name = "doda_xml_files_path" - - old = getattr(vu, field_name) - _remove_stored_vu_path(old) - put_object_bytes(key, content, content_type=ct) - logger.info( - "Customs broker VU upload kind=%s key=%s bytes=%s", - fk, - key, - len(content), - ) - stored = key - else: - base = os.path.join( - "uploads", "customs_brokers", str(company_id), str(broker_id) - ) - if fk == "certificate": - if file_ext != ".cer": - raise HTTPException(status_code=400, detail="certificate must be .cer") - key = customs_broker_vu_certificate_key( - tenant_id, company_id, broker_id, timestamp, file_ext - ) - field_name = "certificate_path" - subdir = "certificates" - elif fk == "key": - if file_ext != ".key": - raise HTTPException(status_code=400, detail="key must be .key") - key = customs_broker_vu_private_key_key( - tenant_id, company_id, broker_id, timestamp, file_ext - ) - field_name = "key_path" - subdir = "keys" - elif fk == "cove": - try: - key = customs_broker_vu_cove_key( - tenant_id, - company_id, - broker_id, - timestamp, - file.filename or "cove.xml", - ) - except ValueError as e: - raise HTTPException(status_code=400, detail=str(e)) from e - field_name = "xml_files_path" - subdir = "cove" - elif fk == "doda_certificate": - if file_ext != ".cer": - raise HTTPException(status_code=400, detail="doda_certificate must be .cer") - key = customs_broker_vu_doda_certificate_key( - tenant_id, company_id, broker_id, timestamp, file_ext - ) - field_name = "doda_certificate_path" - subdir = "doda/certificates" - elif fk == "doda_key": - if file_ext != ".key": - raise HTTPException(status_code=400, detail="doda_key must be .key") - key = customs_broker_vu_doda_private_key_key( - tenant_id, company_id, broker_id, timestamp, file_ext - ) - field_name = "doda_key_path" - subdir = "doda/keys" - else: - try: - key = customs_broker_vu_doda_cove_key( - tenant_id, - company_id, - broker_id, - timestamp, - file.filename or "doda.xml", - ) - except ValueError as e: - raise HTTPException(status_code=400, detail=str(e)) from e - field_name = "doda_xml_files_path" - subdir = "doda/cove" - - fname = key.rsplit("/", 1)[-1] - dest_dir = os.path.join(base, subdir) - os.makedirs(dest_dir, exist_ok=True) - path = os.path.join(dest_dir, fname) - old = getattr(vu, field_name) - _remove_stored_vu_path(old) - with open(path, "wb") as f: - f.write(content) - logger.info( - "Customs broker VU upload kind=%s path=%s bytes=%s", - fk, - path, - len(content), - ) - stored = path - - setattr(vu, field_name, stored) - db.add(vu) - db.commit() - db.refresh(vu) - except HTTPException: - db.rollback() - raise - except ValueError as e: - db.rollback() - raise HTTPException(status_code=400, detail=str(e)) from e - except Exception as e: - db.rollback() - raise HTTPException( - status_code=500, detail=f"Error saving file: {str(e)}" - ) from e - - return { - "message": "File uploaded successfully", - "file_kind": fk, - "field": field_name, - "path": stored, - "broker_key": broker_key, - "company_id": company_id, - } \ No newline at end of file diff --git a/backend/api/v1/modules/a76/customs_brokers/services.py b/backend/api/v1/modules/a76/customs_brokers/services.py deleted file mode 100644 index 31bce8ee..00000000 --- a/backend/api/v1/modules/a76/customs_brokers/services.py +++ /dev/null @@ -1,257 +0,0 @@ -from sqlalchemy.orm import Session -from sqlalchemy.exc import IntegrityError - -from . import dto, models - - -class CustomsBrokerService: - @staticmethod - def get_by_id(db: Session, broker_key: str, tenant_id: int, company_id: int): - """Get a customs broker by broker_key with tenant/company validation""" - return ( - db.query(models.CustomsBroker) - .filter( - models.CustomsBroker.broker_key == broker_key, - models.CustomsBroker.tenant_id == tenant_id, - models.CustomsBroker.company_id == company_id, - ) - .first() - ) - - @staticmethod - def get_all( - db: Session, - tenant_id: int, - company_id: int, - skip: int = 0, - limit: int = 100, - filters: dict = None, - sort_by: str = None, - sort_order: str = "asc", - ): - """Get all customs brokers for a tenant/company with pagination""" - query = db.query(models.CustomsBroker).filter( - models.CustomsBroker.tenant_id == tenant_id, - models.CustomsBroker.company_id == company_id, - ) - - if filters: - # Implement filters if needed in the future - pass - - # Apply sorting - if sort_by: - column = getattr(models.CustomsBroker, sort_by, None) - if column: - if sort_order == "desc": - query = query.order_by(column.desc()) - else: - query = query.order_by(column.asc()) - else: - query = query.order_by(models.CustomsBroker.id.desc()) - else: - query = query.order_by(models.CustomsBroker.id.desc()) - - total = query.count() - items = query.offset(skip).limit(limit).all() - return items, total - - @staticmethod - def create(db: Session, broker_data: dto.CustomsBrokerCreateDTO, tenant_id: int, company_id: int): - """Create a new customs broker""" - broker_dict = broker_data.model_dump() - broker_dict["tenant_id"] = tenant_id - broker_dict["company_id"] = company_id - - new_broker = models.CustomsBroker(**broker_dict) - db.add(new_broker) - try: - db.commit() - db.refresh(new_broker) - return new_broker - except IntegrityError: - db.rollback() - raise ValueError("La clave del agente ya existe o hay datos duplicados.") - - @staticmethod - def update(db: Session, broker_key: str, tenant_id: int, broker_data: dto.CustomsBrokerUpdateDTO, company_id: int): - """Update an existing customs broker""" - broker = CustomsBrokerService.get_by_id(db, broker_key, tenant_id, company_id) - if broker: - for key, value in broker_data.model_dump(exclude_unset=True).items(): - setattr(broker, key, value) - try: - db.commit() - db.refresh(broker) - except IntegrityError: - db.rollback() - raise ValueError("Los datos duplicados no pueden ser guardados o hay un conflicto de integridad.") - return broker - - @staticmethod - def delete(db: Session, broker_key: str, tenant_id: int, company_id: int): - """Delete a customs broker""" - broker = CustomsBrokerService.get_by_id(db, broker_key, tenant_id, company_id) - if broker: - db.delete(broker) - db.commit() - return True - return False - - -class CustomsBrokerVUService: - @staticmethod - def get_by_broker_key(db: Session, broker_key: str): - return ( - db.query(models.CustomsBrokerVU) - .join(models.CustomsBroker) - .filter(models.CustomsBroker.broker_key == broker_key) - .first() - ) - - @staticmethod - def create_vu(db: Session, vu_data: dto.CustomsBrokerVUCreateDTO): - new_vu = models.CustomsBrokerVU(**vu_data.dict()) - db.add(new_vu) - db.commit() - db.refresh(new_vu) - return new_vu - - @staticmethod - def update_vu(db: Session, broker_key: str, vu_data: dto.CustomsBrokerVUCreateDTO, tenant_id: int, company_id: int): - # We need the custom broker ID to insert a new VU - broker = ( - db.query(models.CustomsBroker) - .filter( - models.CustomsBroker.broker_key == broker_key, - models.CustomsBroker.tenant_id == tenant_id, - models.CustomsBroker.company_id == company_id, - ) - .first() - ) - if not broker: - return None - - vu = db.query(models.CustomsBrokerVU).filter(models.CustomsBrokerVU.customs_broker_id == broker.id).first() - - if vu: - # Update existing - safe_updates = vu_data.model_dump( - exclude_unset=True, - exclude={"tenant_id", "company_id"}, - ) - for key, value in safe_updates.items(): - setattr(vu, key, value) - db.commit() - db.refresh(vu) - return vu - else: - # Create new - new_vu_data = vu_data.model_dump() - new_vu_data["tenant_id"] = tenant_id - new_vu_data["company_id"] = company_id - new_vu = models.CustomsBrokerVU(customs_broker_id=broker.id, **new_vu_data) - db.add(new_vu) - db.commit() - db.refresh(new_vu) - return new_vu - - @staticmethod - def delete_vu(db: Session, broker_key: str): - vu = CustomsBrokerVUService.get_by_broker_key(db, broker_key) - if vu: - db.delete(vu) - db.commit() - return vu - - @staticmethod - def ensure_vu_for_broker(db: Session, broker: models.CustomsBroker) -> models.CustomsBrokerVU: - """Crea fila VU vacía si no existe (p. ej. antes de subir CER/KEY/COVE al bucket).""" - vu = ( - db.query(models.CustomsBrokerVU) - .filter(models.CustomsBrokerVU.customs_broker_id == broker.id) - .first() - ) - if vu: - return vu - vu = models.CustomsBrokerVU( - customs_broker_id=broker.id, - tenant_id=broker.tenant_id, - company_id=broker.company_id, - ) - db.add(vu) - db.commit() - db.refresh(vu) - return vu - - -class CustomsBrokerPersonnelService: - @staticmethod - def get_by_broker_key_and_line(db: Session, broker_key: str, line: int, tenant_id: int, company_id: int): - return ( - db.query(models.CustomsBrokerPersonnel) - .join(models.CustomsBroker) - .filter( - models.CustomsBroker.broker_key == broker_key, - models.CustomsBrokerPersonnel.line == line, - models.CustomsBroker.tenant_id == tenant_id, - models.CustomsBroker.company_id == company_id, - ) - .first() - ) - - @staticmethod - def create_personnel(db: Session, broker_key: str, personnel_data: dto.CustomsBrokerPersonnelDTO, tenant_id: int, company_id: int): - broker = ( - db.query(models.CustomsBroker) - .filter( - models.CustomsBroker.broker_key == broker_key, - models.CustomsBroker.tenant_id == tenant_id, - models.CustomsBroker.company_id == company_id, - ) - .first() - ) - if not broker: - return None - - new_personnel_data = personnel_data.model_dump() - new_personnel_data["tenant_id"] = tenant_id - new_personnel_data["company_id"] = company_id - new_personnel = models.CustomsBrokerPersonnel(customs_broker_id=broker.id, **new_personnel_data) - db.add(new_personnel) - db.commit() - db.refresh(new_personnel) - return new_personnel - - @staticmethod - def update_personnel( - db: Session, - broker_key: str, - line: int, - personnel_data: dto.CustomsBrokerPersonnelDTO, - tenant_id: int, - company_id: int, - ): - personnel = CustomsBrokerPersonnelService.get_by_broker_key_and_line( - db, broker_key, line, tenant_id, company_id - ) - if personnel: - for key, value in personnel_data.model_dump(exclude_unset=True).items(): - setattr(personnel, key, value) - db.commit() - db.refresh(personnel) - return personnel - else: - return CustomsBrokerPersonnelService.create_personnel( - db, broker_key, personnel_data, tenant_id, company_id - ) - - @staticmethod - def delete_personnel(db: Session, broker_key: str, line: int): - personnel = CustomsBrokerPersonnelService.get_by_broker_key_and_line( - db, broker_key, line - ) - if personnel: - db.delete(personnel) - db.commit() - return personnel diff --git a/backend/api/v1/modules/a76/doc_types_dig/__init__.py b/backend/api/v1/modules/a76/doc_types_dig/__init__.py deleted file mode 100644 index e65cae83..00000000 --- a/backend/api/v1/modules/a76/doc_types_dig/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# Document Types for Digitization module diff --git a/backend/api/v1/modules/a76/doc_types_dig/dto.py b/backend/api/v1/modules/a76/doc_types_dig/dto.py deleted file mode 100644 index b8a334da..00000000 --- a/backend/api/v1/modules/a76/doc_types_dig/dto.py +++ /dev/null @@ -1,30 +0,0 @@ -from pydantic import BaseModel, Field - - -class DocumentTypeDigitizationBase(BaseModel): - """Base schema for Document Type Digitization""" - - code: str = Field(..., max_length=10, description="Código del tipo de documento") - description: str = Field(..., description="Descripción del tipo de documento") - - -class DocumentTypeDigitizationCreate(DocumentTypeDigitizationBase): - """Schema for creating a Document Type Digitization""" - - pass - - -class DocumentTypeDigitizationUpdate(BaseModel): - """Schema for updating a Document Type Digitization""" - - code: str | None = Field(None, max_length=10) - description: str | None = None - - -class DocumentTypeDigitizationResponse(DocumentTypeDigitizationBase): - """Schema for Document Type Digitization response""" - - id: int - - class Config: - from_attributes = True diff --git a/backend/api/v1/modules/a76/doc_types_dig/models.py b/backend/api/v1/modules/a76/doc_types_dig/models.py deleted file mode 100644 index 8c522dfb..00000000 --- a/backend/api/v1/modules/a76/doc_types_dig/models.py +++ /dev/null @@ -1,30 +0,0 @@ -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base -from sqlalchemy import ( - Integer, - PrimaryKeyConstraint, - String, - Text, - UniqueConstraint, -) -from sqlalchemy.orm import Mapped, mapped_column - - -class DocumentTypeDigitization(Base, TenantScopedMixin, TimestampMixin): - """Tipos de documentos para digitalización de pedimentos""" - - __tablename__ = "document_types_digitization" - __table_args__ = ( - PrimaryKeyConstraint("id", name="document_types_digitization_pkey"), - UniqueConstraint( - "tenant_id", - "company_id", - "code", - name="document_types_digitization_code_key", - ), - {"schema": "a76"}, - ) - - id: Mapped[int] = mapped_column(Integer) - code: Mapped[str] = mapped_column(String(10), nullable=False, index=True) - description: Mapped[str] = mapped_column(Text, nullable=False) diff --git a/backend/api/v1/modules/a76/doc_types_dig/routes.py b/backend/api/v1/modules/a76/doc_types_dig/routes.py deleted file mode 100644 index 688db40f..00000000 --- a/backend/api/v1/modules/a76/doc_types_dig/routes.py +++ /dev/null @@ -1,95 +0,0 @@ -from typing import Any, Dict, Optional - -from fastapi import APIRouter, Depends, HTTPException, Query -from sqlalchemy.orm import Session - -from core.database import get_core_db -from core.security import get_current_user, validate_access_to_resource - -from .dto import DocumentTypeDigitizationResponse -from .service import DocumentTypeDigitizationService - -router = APIRouter(prefix='/document-types-digitization', tags=['Document Types Digitization']) -VIEW_PERMISSION = ['ref_invoice_types.view'] - - -@router.get('/', response_model=Dict[str, Any]) -async def list_document_types( - company_id: int = Query(..., description='Company ID'), - page: int = Query(1, ge=1, description='Page number'), - page_size: int = Query(50, ge=1, le=2000, description='Page size'), - search: Optional[str] = Query(None, description='Search by code or description'), - sort_by: Optional[str] = Query('code', description='Column to sort by'), - sort_order: str = Query('asc', pattern='^(asc|desc)$', description='Sort order'), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - tenant_id = validate_access_to_resource(db, company_id, current_user, VIEW_PERMISSION) - skip = (page - 1) * page_size - filters: Dict[str, Any] = {} - - if search: - filters['search'] = search - - items, total = DocumentTypeDigitizationService.get_all( - db, - tenant_id, - company_id, - skip, - page_size, - filters, - sort_by, - sort_order, - ) - - return { - 'items': [DocumentTypeDigitizationResponse.model_validate(item) for item in items], - 'total': total, - 'page': page, - 'page_size': page_size, - } - - -@router.get('/{document_type_id}/', response_model=DocumentTypeDigitizationResponse) -async def get_document_type( - document_type_id: int, - company_id: int = Query(..., description='Company ID'), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - tenant_id = validate_access_to_resource(db, company_id, current_user, VIEW_PERMISSION) - document_type = DocumentTypeDigitizationService.get_by_id( - db, - document_type_id, - tenant_id, - company_id, - ) - - if not document_type: - raise HTTPException( - status_code=404, - detail=f'Tipo de documento con ID {document_type_id} no encontrado', - ) - - return document_type - - -@router.get('/by-code/{code}/', response_model=DocumentTypeDigitizationResponse) -async def get_document_type_by_code( - code: str, - company_id: int = Query(..., description='Company ID'), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - tenant_id = validate_access_to_resource(db, company_id, current_user, VIEW_PERMISSION) - document_type = DocumentTypeDigitizationService.get_by_code( - db, - code, - tenant_id, - company_id, - ) - - if not document_type: - raise HTTPException(status_code=404, detail=f'Tipo de documento con codigo {code} no encontrado') - - return document_type diff --git a/backend/api/v1/modules/a76/doc_types_dig/seed.py b/backend/api/v1/modules/a76/doc_types_dig/seed.py deleted file mode 100644 index 02cde648..00000000 --- a/backend/api/v1/modules/a76/doc_types_dig/seed.py +++ /dev/null @@ -1,40 +0,0 @@ -# Catálogo SAT — tipos de documentos para digitalización (Ventanilla Única) -seed = [ - ('168', 'Calca o fotografía digital del NIV del vehículo.'), - ('169', 'Aviso.'), - ('170', 'Factura.'), - ('171', 'Documento con el que se acredite la propiedad de la mercancía.'), - ('172', 'Contratos.'), - ('176', 'Documentación relacionada con la garantía otorgada en términos de los artículos 84.'), - ('177', 'Identificación Oficial.'), - ('179', 'Comprobante de domicilio.'), - ('184', 'Documento que ampara el avaluó de las mercancías.'), - ('185', 'Documentos de adjudicación judicial de las mercancías.'), - ('187', 'Solicitud de retiro de mercancías que causaron abandono.'), - ('189', 'Actas.'), - ('192', 'Escritos.'), - ('420', 'Certificado de peso o volumen.'), - ('421', 'Comprobante de la importación temporal de la embarcación debidamente formalizado.'), - ('422', 'Comprobante expedido por donataria.'), - ('423', 'Consulta en la que conste que el vehículo no se encuentra reportado como robado,'), - ('424', 'Clave Unica del Registro de Población.'), - ('425', 'Declaración de internación o extracción de cantidades en efectivo y/o documentos p'), - ('426', 'Declaración de operaciones que no confieren origen en países no parte de acuerdo'), - ('427', 'Declaración en la que se señalen los motivos por los que efectúa la devolución de m'), - ('428', 'Documentación con información que permita la identificación, análisis y control en tér'), - ('429', 'Documentación que acredite que acepta y subsana la irregularidad.'), - ('430', 'Documentación que ampara la importación temporal del vehículo de que se trate.'), - ('431', 'Documentación que compruebe que la adquisición de las mercancías fue efectuada '), - ('433', 'Documento con base en el cual se determine la procedencia y el origen de las merca'), - ('434', 'Documento con que se acredite el reintegro del IVA, en caso de que el contribuyente '), - ('435', 'Documentos previstos en la regla 8.7., fracciones I a IV de la Resolución del TLCAN.'), - ('436', 'El Documento que compruebe el cumplimiento de las regulaciones y restricciones no '), - ('438', 'Guía aérea, conocimiento de embarque o carta de porte.'), - ('439', 'Hoja con los datos de la matrícula y nombre del barco, el lugar donde se localiza y se '), - ('440', 'Manifiesto de carga.'), - ('441', 'Oficios emitidos por autoridad.'), - ('442', 'Pedimentos.'), - ('443', 'Programa IMMEX.'), - ('444', 'Relación de candados.'), - ('445', 'Relación de certificados de origen.'), -] diff --git a/backend/api/v1/modules/a76/doc_types_dig/service.py b/backend/api/v1/modules/a76/doc_types_dig/service.py deleted file mode 100644 index 74d9fe6c..00000000 --- a/backend/api/v1/modules/a76/doc_types_dig/service.py +++ /dev/null @@ -1,181 +0,0 @@ -from __future__ import annotations - -from typing import Any, Dict, Optional, Tuple - -from sqlalchemy import or_ -from sqlalchemy.orm import Session - -from .dto import DocumentTypeDigitizationCreate, DocumentTypeDigitizationUpdate -from .models import DocumentTypeDigitization - - -class DocumentTypeDigitizationService: - @staticmethod - def _normalize_code(code: str) -> str: - return (code or '').strip().upper() - - @staticmethod - def _normalize_description(description: str) -> str: - return (description or '').strip() - - @staticmethod - def get_all( - db: Session, - tenant_id: int, - company_id: Optional[int], - skip: int = 0, - limit: int = 100, - filters: Optional[Dict[str, Any]] = None, - sort_by: Optional[str] = None, - sort_order: str = 'asc', - ) -> Tuple[list[DocumentTypeDigitization], int]: - query = db.query(DocumentTypeDigitization).filter( - DocumentTypeDigitization.tenant_id == tenant_id, - ) - - if company_id is not None: - query = query.filter(DocumentTypeDigitization.company_id == company_id) - - filters = filters or {} - search = (filters.get('search') or '').strip() - - if search: - like = f'%{search}%' - query = query.filter( - or_( - DocumentTypeDigitization.code.ilike(like), - DocumentTypeDigitization.description.ilike(like), - ) - ) - - total = query.count() - - sort_column = { - 'id': DocumentTypeDigitization.id, - 'code': DocumentTypeDigitization.code, - 'description': DocumentTypeDigitization.description, - }.get(sort_by or 'code', DocumentTypeDigitization.code) - - if sort_order == 'desc': - query = query.order_by(sort_column.desc()) - else: - query = query.order_by(sort_column.asc()) - - items = query.offset(skip).limit(limit).all() - return items, total - - @staticmethod - def get_by_id( - db: Session, - id: int, - tenant_id: int, - company_id: int, - ) -> Optional[DocumentTypeDigitization]: - return ( - db.query(DocumentTypeDigitization) - .filter( - DocumentTypeDigitization.id == id, - DocumentTypeDigitization.tenant_id == tenant_id, - DocumentTypeDigitization.company_id == company_id, - ) - .first() - ) - - @staticmethod - def get_by_code( - db: Session, - code: str, - tenant_id: int, - company_id: int, - ) -> Optional[DocumentTypeDigitization]: - normalized_code = DocumentTypeDigitizationService._normalize_code(code) - return ( - db.query(DocumentTypeDigitization) - .filter( - DocumentTypeDigitization.code == normalized_code, - DocumentTypeDigitization.tenant_id == tenant_id, - DocumentTypeDigitization.company_id == company_id, - ) - .first() - ) - - @staticmethod - def create( - db: Session, - data: DocumentTypeDigitizationCreate, - tenant_id: int, - company_id: int, - ) -> DocumentTypeDigitization: - payload = data.model_dump() - payload['code'] = DocumentTypeDigitizationService._normalize_code(payload['code']) - payload['description'] = DocumentTypeDigitizationService._normalize_description( - payload['description'] - ) - - if not payload['code']: - raise ValueError('El codigo es obligatorio') - if not payload['description']: - raise ValueError('La descripcion es obligatoria') - - existing = DocumentTypeDigitizationService.get_by_code( - db, payload['code'], tenant_id, company_id - ) - if existing: - raise ValueError( - f'Ya existe un tipo de documento con el codigo {payload["code"]}' - ) - - db_obj = DocumentTypeDigitization( - **payload, - tenant_id=tenant_id, - company_id=company_id, - ) - db.add(db_obj) - db.commit() - db.refresh(db_obj) - return db_obj - - @staticmethod - def update( - db: Session, - id: int, - tenant_id: int, - data: DocumentTypeDigitizationUpdate, - company_id: int, - ) -> Optional[DocumentTypeDigitization]: - db_obj = DocumentTypeDigitizationService.get_by_id(db, id, tenant_id, company_id) - if not db_obj: - return None - - update_dict = data.model_dump(exclude_unset=True) - - if 'code' in update_dict: - update_dict['code'] = DocumentTypeDigitizationService._normalize_code(update_dict['code']) - if not update_dict['code']: - raise ValueError('El codigo es obligatorio') - if update_dict['code'] != db_obj.code: - existing = DocumentTypeDigitizationService.get_by_code( - db, - update_dict['code'], - tenant_id, - company_id, - ) - if existing: - raise ValueError( - f'Ya existe un tipo de documento con el codigo {update_dict["code"]}' - ) - - if 'description' in update_dict: - update_dict['description'] = DocumentTypeDigitizationService._normalize_description( - update_dict['description'] - ) - if not update_dict['description']: - raise ValueError('La descripcion es obligatoria') - - for key, value in update_dict.items(): - setattr(db_obj, key, value) - - db.commit() - db.refresh(db_obj) - return db_obj - diff --git a/backend/api/v1/modules/a76/expediente_archivos/__init__.py b/backend/api/v1/modules/a76/expediente_archivos/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/backend/api/v1/modules/a76/expediente_archivos/dto.py b/backend/api/v1/modules/a76/expediente_archivos/dto.py deleted file mode 100644 index e73132fb..00000000 --- a/backend/api/v1/modules/a76/expediente_archivos/dto.py +++ /dev/null @@ -1,138 +0,0 @@ -from __future__ import annotations - -from datetime import date -from typing import List, Optional - -from pydantic import BaseModel, Field - - -# --------------------------------------------------------------------------- -# CRUD DTOs -# --------------------------------------------------------------------------- - -class ExpedienteArchivoCreateDTO(BaseModel): - e_document: Optional[str] = Field(None, max_length=50) - num_operacion: Optional[str] = Field(None, max_length=50) - tipo_documento: Optional[str] = Field(None, max_length=10) - archivo_digitalizado_en: Optional[str] = Field(None, max_length=500) - fecha_digitalizacion: Optional[date] = None - agente_aduanal: Optional[str] = Field(None, max_length=50) - pedimento: Optional[str] = Field(None, max_length=21) - rfc_consulta: Optional[str] = Field(None, max_length=13) - nombre_archivo: Optional[str] = Field(None, max_length=255) - - -class ExpedienteArchivoUpdateDTO(BaseModel): - e_document: Optional[str] = Field(None, max_length=50) - num_operacion: Optional[str] = Field(None, max_length=50) - tipo_documento: Optional[str] = Field(None, max_length=10) - archivo_digitalizado_en: Optional[str] = Field(None, max_length=500) - fecha_digitalizacion: Optional[date] = None - agente_aduanal: Optional[str] = Field(None, max_length=50) - pedimento: Optional[str] = Field(None, max_length=21) - rfc_consulta: Optional[str] = Field(None, max_length=13) - nombre_archivo: Optional[str] = Field(None, max_length=255) - - -class ExpedienteArchivoResponseDTO(BaseModel): - id: int - e_document: Optional[str] = None - num_operacion: Optional[str] = None - tipo_documento: Optional[str] = None - archivo_digitalizado_en: Optional[str] = None - fecha_digitalizacion: Optional[date] = None - agente_aduanal: Optional[str] = None - pedimento: Optional[str] = None - rfc_consulta: Optional[str] = None - nombre_archivo: Optional[str] = None - status: Optional[str] = None - task_id: Optional[str] = None - external_task_id: Optional[str] = None - acuse_pdf_path: Optional[str] = None - envio_xml_path: Optional[str] = None - respuesta_xml_path: Optional[str] = None - consulta_envio_xml_path: Optional[str] = None - consulta_respuesta_xml_path: Optional[str] = None - company_id: int - tenant_id: int - - model_config = {"from_attributes": True} - - -class ExpedienteArchivoListResponse(BaseModel): - items: List[ExpedienteArchivoResponseDTO] - total: int - page: int - page_size: int - - -# --------------------------------------------------------------------------- -# Digitalización DTOs -# --------------------------------------------------------------------------- - -class DigitalizarRequest(BaseModel): - """ - Solicitud de digitalización enviada por el frontend. - La configuracion_vu se ensambla server-side desde CustomsBrokerVU / company. - """ - rfc_consulta: Optional[str] = Field(None, max_length=13) - clave_documento: Optional[str] = Field(None, max_length=10) - nombre_archivo: Optional[str] = Field(None, max_length=255) - archivo_base64: Optional[str] = None # contenido del archivo en base64; opcional si el expediente ya tiene archivo almacenado - - -class RegistrarDigitalizacionRequest(BaseModel): - """Digitalizar múltiples expedientes existentes por ID.""" - rfc_consulta: Optional[str] = Field(None, max_length=13) - ids_archivos: List[int] - - -class DigitalizarResponse(BaseModel): - task_id: str - message: str - status: str - - -# --------------------------------------------------------------------------- -# Task status DTOs (espejo del schema del servicio externo) -# --------------------------------------------------------------------------- - -class DigitalizacionResult(BaseModel): - status: Optional[str] = None - message: Optional[str] = None - request_id: Optional[str] = None - response_code: Optional[int] = None - xml_path: Optional[str] = None - response_path: Optional[str] = None - acuese_digitalizacion_pdf_base64: Optional[str] = None - nombre_archivo: Optional[str] = None - timestamp: Optional[str] = None - numero_operacion: Optional[str] = None - e_document: Optional[str] = None - envio_xml_base64: Optional[str] = None - respuesta_xml_base64: Optional[str] = None - consulta_envio_xml_base64: Optional[str] = None - consulta_respuesta_xml_base64: Optional[str] = None - - -class DigitalizacionErrorDetail(BaseModel): - codigo: Optional[str] = None - descripcion: Optional[str] = None - paso: Optional[str] = None - sugerencias: Optional[List[str]] = None - - -class DigitalizacionTaskDetailResponse(BaseModel): - task_id: str - external_task_id: Optional[str] = None - state: str - status: Optional[str] = None - current_step: Optional[str] = None - progress: Optional[int] = None - total_steps: Optional[int] = None - request_id: Optional[str] = None - result: Optional[DigitalizacionResult] = None - error: Optional[str] = None - error_type: Optional[str] = None - error_detail: Optional[DigitalizacionErrorDetail] = None - info: Optional[dict] = None diff --git a/backend/api/v1/modules/a76/expediente_archivos/external_service.py b/backend/api/v1/modules/a76/expediente_archivos/external_service.py deleted file mode 100644 index e55dcf6a..00000000 --- a/backend/api/v1/modules/a76/expediente_archivos/external_service.py +++ /dev/null @@ -1,62 +0,0 @@ -from __future__ import annotations - -import logging -from typing import Any, Dict, Optional - -import httpx - -from core.config import settings - -logger = logging.getLogger(__name__) - - -class ExpedienteExternalService: - """ - Cliente HTTP real para el API externo de digitalización de archivos en Ventanilla Única. - - Endpoints utilizados: - POST {base_url}/api/v1/expediente-archivos/digitalizar-archivo-json - GET {base_url}/api/v1/expediente-archivos/status-digitalizacion-task/{task_id} - """ - - def __init__(self) -> None: - self.base_url = settings.COVE_API_URL.strip() - self.verify_ssl = settings.COVE_API_VERIFY_SSL - - def digitalizar_archivo_json(self, payload: Dict[str, Any]) -> Dict[str, Any]: - """ - Envía un JSON con el documento + configuracion_vu al endpoint de digitalización. - Retorna la respuesta JSON tal como viene del API externo. - """ - url = f"{self.base_url.rstrip('/')}/api/v1/expediente-archivos/digitalizar-archivo-json" - - configuracion_vu = payload.get("configuracion_vu") or {} - logger.info( - "Sending digitalizar-archivo-json: nombre_archivo=%s rfc_vu=%s clave_fiel_len=%s clave_ws_len=%s cer_len=%s key_len=%s", - payload.get("nombre_archivo"), - configuracion_vu.get("rfc_usuario_vu"), - len(configuracion_vu.get("clave_fiel") or ""), - len(configuracion_vu.get("clave_webservice") or ""), - len(configuracion_vu.get("archivo_cer_base64") or ""), - len(configuracion_vu.get("archivo_key_base64") or ""), - ) - - # connect=10s, read=120s: la subida del PDF puede tomar tiempo en el servidor VU - with httpx.Client(timeout=httpx.Timeout(120.0, connect=10.0), verify=self.verify_ssl) as client: - response = client.post(url, json=payload) - response.raise_for_status() - return response.json() - - def get_status(self, task_id: str) -> Optional[Dict[str, Any]]: - """ - Consulta el estado de una tarea de digitalización en el API externo. - """ - url = f"{self.base_url.rstrip('/')}/api/v1/expediente-archivos/status-digitalizacion-task/{task_id}" - logger.debug("Consulting expediente task status: task_id=%s url=%s", task_id, url) - - # read=None: sin límite de lectura — VU mantiene la conexión abierta mientras procesa. - # El timeout global del polling loop (300 s) actúa como cota máxima real. - with httpx.Client(timeout=httpx.Timeout(None, connect=10.0), verify=self.verify_ssl) as client: - response = client.get(url) - response.raise_for_status() - return response.json() diff --git a/backend/api/v1/modules/a76/expediente_archivos/models.py b/backend/api/v1/modules/a76/expediente_archivos/models.py deleted file mode 100644 index a1631791..00000000 --- a/backend/api/v1/modules/a76/expediente_archivos/models.py +++ /dev/null @@ -1,39 +0,0 @@ -from __future__ import annotations - -from datetime import date - -from sqlalchemy import Date, Integer, String -from sqlalchemy.orm import Mapped, mapped_column - -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base - - -class ExpedienteArchivo(Base, TenantScopedMixin, TimestampMixin): - """Registro de documentos digitalizados en Ventanilla Única.""" - - __tablename__ = "expediente_archivo" - __table_args__ = ({"schema": "a76"},) - - id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) - - # Datos del documento (campos del catálogo legacy) - e_document: Mapped[str | None] = mapped_column(String(50), nullable=True) - num_operacion: Mapped[str | None] = mapped_column(String(50), nullable=True) - tipo_documento: Mapped[str | None] = mapped_column(String(10), nullable=True) - archivo_digitalizado_en: Mapped[str | None] = mapped_column(String(500), nullable=True) - fecha_digitalizacion: Mapped[date | None] = mapped_column(Date, nullable=True) - agente_aduanal: Mapped[str | None] = mapped_column(String(50), nullable=True) - pedimento: Mapped[str | None] = mapped_column(String(21), nullable=True) - rfc_consulta: Mapped[str | None] = mapped_column(String(13), nullable=True) - nombre_archivo: Mapped[str | None] = mapped_column(String(255), nullable=True) - - # Estado de la tarea de digitalización - status: Mapped[str | None] = mapped_column(String(20), nullable=True) # pending/processing/success/failed - task_id: Mapped[str | None] = mapped_column(String(255), nullable=True, index=True) - external_task_id: Mapped[str | None] = mapped_column(String(255), nullable=True, index=True) - acuse_pdf_path: Mapped[str | None] = mapped_column(String(500), nullable=True) - envio_xml_path: Mapped[str | None] = mapped_column(String(500), nullable=True) - respuesta_xml_path: Mapped[str | None] = mapped_column(String(500), nullable=True) - consulta_envio_xml_path: Mapped[str | None] = mapped_column(String(500), nullable=True) - consulta_respuesta_xml_path: Mapped[str | None] = mapped_column(String(500), nullable=True) diff --git a/backend/api/v1/modules/a76/expediente_archivos/routes.py b/backend/api/v1/modules/a76/expediente_archivos/routes.py deleted file mode 100644 index 5e753b8e..00000000 --- a/backend/api/v1/modules/a76/expediente_archivos/routes.py +++ /dev/null @@ -1,399 +0,0 @@ -from __future__ import annotations - -from datetime import datetime -import io -import mimetypes -import os -import zipfile -from typing import Any, Dict - -from fastapi import APIRouter, Depends, File, HTTPException, Query, Response, UploadFile, status -from sqlalchemy.orm import Session - -from core.config import settings -from core.database import get_core_db -from core.security import get_current_user, validate_access_to_resource -from core.s3_keys import expediente_archivo_document_key -from core.storage_s3 import delete_object_if_exists, get_object_bytes, object_exists, put_object_bytes - -from .dto import ( - DigitalizacionTaskDetailResponse, - DigitalizarRequest, - DigitalizarResponse, - ExpedienteArchivoCreateDTO, - ExpedienteArchivoListResponse, - ExpedienteArchivoResponseDTO, - ExpedienteArchivoUpdateDTO, - RegistrarDigitalizacionRequest, -) -from .models import ExpedienteArchivo -from .service import ExpedienteArchivoService -from .tasks import digitalizar_task - -import logging -logger = logging.getLogger(__name__) - -router = APIRouter(prefix="/expediente-archivos") - - -def _remove_stored_document_path(path: str | None) -> None: - raw = (path or "").strip() - if not raw: - return - try: - if settings.use_s3_object_storage and not os.path.isabs(raw): - delete_object_if_exists(raw) - return - if os.path.exists(raw): - os.remove(raw) - except Exception: - logger.warning("No se pudo eliminar archivo previo de expediente: %s", raw, exc_info=True) - - -# ──────────────────────────────────────────────────────────────────────────── # -# CRUD # -# ──────────────────────────────────────────────────────────────────────────── # - -@router.get("/", response_model=ExpedienteArchivoListResponse) -def list_expediente_archivos( - company_id: int = Query(...), - page: int = Query(1, ge=1), - page_size: int = Query(50, ge=1, le=200), - search: str = Query(None), - status: str = Query(None), - rfc_consulta: str = Query(None), - e_document: str = Query(None), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - tenant_id = validate_access_to_resource(db, company_id, current_user) - return ExpedienteArchivoService.list( - db, company_id, tenant_id, page, page_size, - search=search, status=status, rfc_consulta=rfc_consulta, e_document=e_document - ) - - -@router.get("/{record_id}", response_model=ExpedienteArchivoResponseDTO) -def get_expediente_archivo( - record_id: int, - company_id: int = Query(...), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - tenant_id = validate_access_to_resource(db, company_id, current_user) - record = ExpedienteArchivoService.get(db, record_id, company_id, tenant_id) - if not record: - raise HTTPException(status_code=404, detail="Expediente archivo no encontrado.") - return ExpedienteArchivoResponseDTO.model_validate(record) - - -@router.post("/", response_model=ExpedienteArchivoResponseDTO, status_code=status.HTTP_201_CREATED) -def create_expediente_archivo( - dto: ExpedienteArchivoCreateDTO, - company_id: int = Query(...), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - tenant_id = validate_access_to_resource(db, company_id, current_user) - record = ExpedienteArchivoService.create(db, dto, company_id, tenant_id) - return ExpedienteArchivoResponseDTO.model_validate(record) - - -@router.put("/{record_id}", response_model=ExpedienteArchivoResponseDTO) -def update_expediente_archivo( - record_id: int, - dto: ExpedienteArchivoUpdateDTO, - company_id: int = Query(...), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - tenant_id = validate_access_to_resource(db, company_id, current_user) - record = ExpedienteArchivoService.get(db, record_id, company_id, tenant_id) - if not record: - raise HTTPException(status_code=404, detail="Expediente archivo no encontrado.") - record = ExpedienteArchivoService.update(db, record, dto) - return ExpedienteArchivoResponseDTO.model_validate(record) - - -@router.delete("/{record_id}", status_code=status.HTTP_204_NO_CONTENT) -def delete_expediente_archivo( - record_id: int, - company_id: int = Query(...), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - tenant_id = validate_access_to_resource(db, company_id, current_user) - record = ExpedienteArchivoService.get(db, record_id, company_id, tenant_id) - if not record: - raise HTTPException(status_code=404, detail="Expediente archivo no encontrado.") - _remove_stored_document_path(record.archivo_digitalizado_en) - ExpedienteArchivoService.delete(db, record) - return None - - -_ARTIFACT_TYPE_MAP = { - "acuse": ("acuse_pdf_path", "application/pdf"), - "envio-xml": ("envio_xml_path", "application/xml"), - "respuesta-xml": ("respuesta_xml_path", "application/xml"), - "consulta-envio-xml": ("consulta_envio_xml_path", "application/xml"), - "consulta-respuesta-xml": ("consulta_respuesta_xml_path", "application/xml"), -} - - -@router.get("/{record_id}/artifacts/{artifact_type}", response_class=Response) -def download_artifact( - record_id: int, - artifact_type: str, - company_id: int = Query(...), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """Descarga un artefacto de digitalización (acuse PDF o XMLs) desde S3.""" - tenant_id = validate_access_to_resource(db, company_id, current_user) - if artifact_type not in _ARTIFACT_TYPE_MAP: - raise HTTPException(status_code=400, detail=f"Tipo de artefacto no válido: {artifact_type}") - record = ExpedienteArchivoService.get(db, record_id, company_id, tenant_id) - if not record: - raise HTTPException(status_code=404, detail="Expediente archivo no encontrado.") - field_name, content_type = _ARTIFACT_TYPE_MAP[artifact_type] - key = (getattr(record, field_name, None) or "").strip() - if not key or key == "inline": - raise HTTPException(status_code=404, detail="Artefacto no disponible para este expediente.") - if not object_exists(key): - raise HTTPException(status_code=404, detail="El archivo no se encontró en el almacenamiento.") - ext = ".pdf" if content_type == "application/pdf" else ".xml" - filename = f"{artifact_type}_{record.e_document or record_id}{ext}" - return Response( - content=get_object_bytes(key), - media_type=content_type, - headers={"Content-Disposition": f'attachment; filename="{filename}"'}, - ) - - -@router.get("/{record_id}/artifacts-zip", response_class=Response) -def download_artifacts_zip( - record_id: int, - company_id: int = Query(...), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """Descarga todos los artefactos disponibles de un expediente en un archivo ZIP.""" - tenant_id = validate_access_to_resource(db, company_id, current_user) - record = ExpedienteArchivoService.get(db, record_id, company_id, tenant_id) - if not record: - raise HTTPException(status_code=404, detail="Expediente archivo no encontrado.") - - base_name = record.e_document or str(record_id) - artifact_files = [ - ("acuse", "acuse_pdf_path", f"acuse_{base_name}.pdf"), - ("envio-xml", "envio_xml_path", f"envio_{base_name}.xml"), - ("respuesta-xml", "respuesta_xml_path", f"respuesta_{base_name}.xml"), - ("consulta-envio-xml", "consulta_envio_xml_path", f"consulta_envio_{base_name}.xml"), - ("consulta-respuesta-xml", "consulta_respuesta_xml_path", f"consulta_respuesta_{base_name}.xml"), - ] - - buf = io.BytesIO() - added = 0 - with zipfile.ZipFile(buf, mode="w", compression=zipfile.ZIP_DEFLATED) as zf: - for _type, field_name, filename in artifact_files: - key = (getattr(record, field_name, None) or "").strip() - if not key or key == "inline": - continue - if not object_exists(key): - continue - zf.writestr(filename, get_object_bytes(key)) - added += 1 - - if added == 0: - raise HTTPException(status_code=404, detail="No hay artefactos disponibles para este expediente.") - - buf.seek(0) - zip_filename = f"expediente_{base_name}.zip" - return Response( - content=buf.getvalue(), - media_type="application/zip", - headers={"Content-Disposition": f'attachment; filename="{zip_filename}"'}, - ) - - -@router.post("/{record_id}/upload", response_model=Dict[str, Any]) -async def upload_expediente_archivo_file( - record_id: int, - company_id: int = Query(...), - file: UploadFile = File(...), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - tenant_id = validate_access_to_resource(db, company_id, current_user) - record = ExpedienteArchivoService.get(db, record_id, company_id, tenant_id) - if not record: - raise HTTPException(status_code=404, detail="Expediente archivo no encontrado.") - - content = await file.read() - if not content: - raise HTTPException(status_code=400, detail="El archivo está vacío.") - - timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") - - # Borrar artefactos de digitalización previa al subir documento nuevo - _ARTIFACT_FIELDS = [ - "acuse_pdf_path", - "envio_xml_path", - "respuesta_xml_path", - "consulta_envio_xml_path", - "consulta_respuesta_xml_path", - ] - for _field in _ARTIFACT_FIELDS: - _old_key = (getattr(record, _field, None) or "").strip() - if _old_key and _old_key != "inline": - try: - delete_object_if_exists(_old_key) - except Exception: - logger.warning("No se pudo eliminar artefacto previo %s=%s", _field, _old_key, exc_info=True) - setattr(record, _field, None) - - # El documento fuente cambió — la digitalización anterior ya no aplica - record.status = "pending" - record.e_document = None - record.num_operacion = None - record.task_id = None - record.external_task_id = None - - try: - if settings.use_s3_object_storage: - key = expediente_archivo_document_key( - tenant_id, - company_id, - record.id, - timestamp, - file.filename or "documento.pdf", - ) - ct = file.content_type or mimetypes.guess_type(file.filename or "")[0] or "application/octet-stream" - _remove_stored_document_path(record.archivo_digitalizado_en) - put_object_bytes(key, content, content_type=ct) - stored = key - else: - key = expediente_archivo_document_key( - tenant_id, - company_id, - record.id, - timestamp, - file.filename or "documento.pdf", - ) - base = os.path.join("uploads", "expediente_archivos", str(company_id), str(record.id)) - os.makedirs(base, exist_ok=True) - filename = key.rsplit("/", 1)[-1] - stored = os.path.join(base, filename) - _remove_stored_document_path(record.archivo_digitalizado_en) - with open(stored, "wb") as destination: - destination.write(content) - - record.archivo_digitalizado_en = stored - record.nombre_archivo = file.filename or record.nombre_archivo - db.add(record) - db.commit() - db.refresh(record) - except HTTPException: - db.rollback() - raise - except ValueError as exc: - db.rollback() - raise HTTPException(status_code=400, detail=str(exc)) from exc - except Exception as exc: - db.rollback() - raise HTTPException(status_code=500, detail=f"Error saving file: {str(exc)}") from exc - - return { - "message": "Archivo cargado correctamente", - "record_id": record.id, - "path": stored, - "nombre_archivo": record.nombre_archivo, - } - - -# ──────────────────────────────────────────────────────────────────────────── # -# Digitalización # -# ──────────────────────────────────────────────────────────────────────────── # - -@router.post("/digitalizar/{record_id}", response_model=DigitalizarResponse) -def digitalizar_expediente_archivo( - record_id: int, - body: DigitalizarRequest, - company_id: int = Query(...), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """ - Lanza la tarea Celery de digitalización para un expediente existente. - """ - tenant_id = validate_access_to_resource(db, company_id, current_user) - record = ExpedienteArchivoService.get(db, record_id, company_id, tenant_id) - if not record: - raise HTTPException(status_code=404, detail="Expediente archivo no encontrado.") - - task = digitalizar_task.apply_async( - kwargs={ - "expediente_id": record_id, - "request_data": body.model_dump(), - "company_id": company_id, - "tenant_id": tenant_id, - }, - headers={ - "rls_tenant_id": str(int(tenant_id)), - "rls_company_id": str(int(company_id)), - }, - ) - - return DigitalizarResponse( - task_id=task.id, - message="Tarea de digitalización iniciada.", - status="pending", - ) - - -@router.post("/registrar-digitalizacion/", response_model=Dict[str, Any]) -def registrar_digitalizacion( - body: RegistrarDigitalizacionRequest, - company_id: int = Query(...), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """ - Lanza tareas de digitalización en batch para múltiples expedientes. - """ - tenant_id = validate_access_to_resource(db, company_id, current_user) - launched = [] - errors_list = [] - - for record_id in body.ids_archivos: - record = ExpedienteArchivoService.get(db, record_id, company_id, tenant_id) - if not record: - errors_list.append({"id": record_id, "error": "No encontrado"}) - continue - - task = digitalizar_task.apply_async( - kwargs={ - "expediente_id": record_id, - "request_data": {"rfc_consulta": body.rfc_consulta}, - "company_id": company_id, - "tenant_id": tenant_id, - }, - headers={ - "rls_tenant_id": str(int(tenant_id)), - "rls_company_id": str(int(company_id)), - }, - ) - launched.append({"id": record_id, "task_id": task.id}) - - return {"launched": launched, "errors": errors_list} - - -@router.get("/status-digitalizacion-task/{task_id}", response_model=DigitalizacionTaskDetailResponse) -def get_digitalizacion_task_status( - task_id: str, - current_user: Dict[str, Any] = Depends(get_current_user), -): - """ - Consulta el estado de una tarea Celery de digitalización. - """ - return ExpedienteArchivoService.get_task_status(task_id) diff --git a/backend/api/v1/modules/a76/expediente_archivos/service.py b/backend/api/v1/modules/a76/expediente_archivos/service.py deleted file mode 100644 index 7af953cc..00000000 --- a/backend/api/v1/modules/a76/expediente_archivos/service.py +++ /dev/null @@ -1,560 +0,0 @@ -from __future__ import annotations - -import base64 -import logging -from datetime import datetime -from typing import List, Optional - -from cryptography.hazmat.primitives import padding as crypto_padding -from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes -from sqlalchemy import or_ -from sqlalchemy.orm import Session - -from core.celery_app import celery_app -from core.config import settings -from core.database import CoreSessionLocal -from core.exceptions import ErrorCollector, ValidationException -from core.storage_s3 import get_object_bytes, object_exists - -from api.v1.modules.a76.customs_brokers import models as cb_models -from api.v1.modules.a76.general_catalogs.company.models import Company - -from .dto import ( - DigitalizacionErrorDetail, - DigitalizacionResult, - DigitalizacionTaskDetailResponse, - ExpedienteArchivoCreateDTO, - ExpedienteArchivoListResponse, - ExpedienteArchivoResponseDTO, - ExpedienteArchivoUpdateDTO, -) -from .models import ExpedienteArchivo - -logger = logging.getLogger(__name__) - - -# --------------------------------------------------------------------------- -# CRUD service -# --------------------------------------------------------------------------- - -class ExpedienteArchivoService: - - @staticmethod - def _get_task_record_metadata(task_id: str) -> dict: - db = CoreSessionLocal() - try: - record = ( - db.query(ExpedienteArchivo) - .filter( - ExpedienteArchivo.task_id == task_id, - ExpedienteArchivo.deleted_at.is_(None), - ) - .order_by(ExpedienteArchivo.id.desc()) - .first() - ) - if not record: - return {"external_task_id": None} - return { - "external_task_id": record.external_task_id, - "db_status": record.status, - "e_document": record.e_document, - "num_operacion": record.num_operacion, - "nombre_archivo": record.nombre_archivo, - } - except Exception: - logger.exception("No se pudo obtener metadata del expediente para task_id=%s", task_id) - return {"external_task_id": None} - finally: - db.close() - - @staticmethod - def list( - db: Session, - company_id: int, - tenant_id: int, - page: int = 1, - page_size: int = 50, - search: Optional[str] = None, - status: Optional[str] = None, - rfc_consulta: Optional[str] = None, - e_document: Optional[str] = None, - ) -> ExpedienteArchivoListResponse: - query = ( - db.query(ExpedienteArchivo) - .filter( - ExpedienteArchivo.company_id == company_id, - ExpedienteArchivo.tenant_id == tenant_id, - ExpedienteArchivo.deleted_at.is_(None), - ) - ) - if status: - query = query.filter(ExpedienteArchivo.status == status) - if rfc_consulta: - query = query.filter(ExpedienteArchivo.rfc_consulta.ilike(f"%{rfc_consulta}%")) - if e_document: - query = query.filter(ExpedienteArchivo.e_document.ilike(f"%{e_document}%")) - if search: - like = f"%{search}%" - query = query.filter( - or_( - ExpedienteArchivo.e_document.ilike(like), - ExpedienteArchivo.tipo_documento.ilike(like), - ExpedienteArchivo.rfc_consulta.ilike(like), - ExpedienteArchivo.num_operacion.ilike(like), - ExpedienteArchivo.nombre_archivo.ilike(like), - ) - ) - total = query.count() - items = query.order_by(ExpedienteArchivo.id.desc()).offset((page - 1) * page_size).limit(page_size).all() - return ExpedienteArchivoListResponse( - items=[ExpedienteArchivoResponseDTO.model_validate(r) for r in items], - total=total, - page=page, - page_size=page_size, - ) - - @staticmethod - def get(db: Session, record_id: int, company_id: int, tenant_id: int) -> Optional[ExpedienteArchivo]: - return ( - db.query(ExpedienteArchivo) - .filter( - ExpedienteArchivo.id == record_id, - ExpedienteArchivo.company_id == company_id, - ExpedienteArchivo.tenant_id == tenant_id, - ExpedienteArchivo.deleted_at.is_(None), - ) - .first() - ) - - @staticmethod - def create(db: Session, dto: ExpedienteArchivoCreateDTO, company_id: int, tenant_id: int) -> ExpedienteArchivo: - record = ExpedienteArchivo( - company_id=company_id, - tenant_id=tenant_id, - **dto.model_dump(exclude_none=False), - ) - db.add(record) - db.commit() - db.refresh(record) - return record - - @staticmethod - def update( - db: Session, - record: ExpedienteArchivo, - dto: ExpedienteArchivoUpdateDTO, - ) -> ExpedienteArchivo: - for field, value in dto.model_dump(exclude_unset=True).items(): - setattr(record, field, value) - db.commit() - db.refresh(record) - return record - - @staticmethod - def delete(db: Session, record: ExpedienteArchivo) -> None: - from datetime import datetime - record.deleted_at = datetime.utcnow() - db.commit() - - @staticmethod - def get_task_status(task_id: str) -> DigitalizacionTaskDetailResponse: - task_metadata = ExpedienteArchivoService._get_task_record_metadata(task_id) - try: - result = celery_app.AsyncResult(task_id) - state = result.state or "PENDING" - info = result.info or {} - except Exception as exc: - logger.exception("No se pudo consultar el estado de la tarea de digitalización task_id=%s", task_id) - return DigitalizacionTaskDetailResponse( - task_id=task_id, - external_task_id=task_metadata.get("external_task_id"), - state="FAILURE", - status="failed", - error="No se pudo consultar el estado de la digitalización.", - error_type=type(exc).__name__, - error_detail=DigitalizacionErrorDetail( - codigo="TASK_STATUS_ERROR", - descripcion=str(exc), - paso="Consulta de estado", - sugerencias=["Cierra el diálogo y vuelve a intentar la digitalización."], - ), - ) - - if state == "SUCCESS": - raw = result.result or {} - return DigitalizacionTaskDetailResponse( - task_id=task_id, - external_task_id=task_metadata.get("external_task_id"), - state="SUCCESS", - status="success", - request_id=raw.get("request_id"), - result=DigitalizacionResult(**{k: raw.get(k) for k in DigitalizacionResult.model_fields}), - progress=100, - total_steps=4, - ) - - # Fallback: Celery puede tardar en propagar SUCCESS a Redis. - # Si la DB ya tiene status=success, retornamos SUCCESS inmediatamente. - if task_metadata.get("db_status") == "success": - logger.info( - "get_task_status: Celery state=%s but DB status=success — returning SUCCESS from DB task_id=%s", - state, task_id, - ) - return DigitalizacionTaskDetailResponse( - task_id=task_id, - external_task_id=task_metadata.get("external_task_id"), - state="SUCCESS", - status="success", - result=DigitalizacionResult( - status="success", - message="Digitalización completada exitosamente.", - e_document=task_metadata.get("e_document"), - numero_operacion=task_metadata.get("num_operacion"), - nombre_archivo=task_metadata.get("nombre_archivo"), - ), - progress=100, - total_steps=4, - ) - - if state in {"FAILURE", "FAILED"}: - err = info if not isinstance(info, dict) else None - error_detail_raw = info.get("error_detail") if isinstance(info, dict) else None - error_text = str(err or info.get("error", "")) if isinstance(info, dict) else str(err or "") - error_type = info.get("error_type") if isinstance(info, dict) else None - if not error_type and isinstance(info, BaseException): - error_type = type(info).__name__ - if error_type == "Ignore" and not error_text: - error_text = "La digitalización no pudo completarse." - if not error_text and isinstance(info, BaseException): - error_text = "La digitalización no pudo completarse." - if isinstance(info, BaseException) and not error_detail_raw: - error_detail_raw = { - "codigo": "TASK_FAILED", - "descripcion": "La tarea terminó con error antes de completar la digitalización.", - "paso": "Proceso de digitalización", - "sugerencias": ["Revisa la configuración VU y vuelve a intentarlo."], - } - return DigitalizacionTaskDetailResponse( - task_id=task_id, - external_task_id=task_metadata.get("external_task_id"), - state="FAILURE", - status="failed", - request_id=info.get("request_id") if isinstance(info, dict) else None, - error=error_text, - error_type=error_type, - error_detail=DigitalizacionErrorDetail(**(error_detail_raw or {})) if error_detail_raw else None, - ) - - # PROGRESS / PENDING / STARTED - if isinstance(info, dict): - return DigitalizacionTaskDetailResponse( - task_id=task_id, - external_task_id=task_metadata.get("external_task_id"), - state=state, - status="processing", - current_step=info.get("current_step") or info.get("status"), - progress=info.get("progress") or info.get("current"), - total_steps=info.get("total_steps") or 4, - request_id=info.get("request_id"), - ) - - return DigitalizacionTaskDetailResponse( - task_id=task_id, - external_task_id=task_metadata.get("external_task_id"), - state=state, - status="pending", - ) - - -# --------------------------------------------------------------------------- -# VU config builder (mirror of factura_cove/service.py _build_configuracion_vu) -# --------------------------------------------------------------------------- - -def _encrypt_fiel(raw_fiel: str) -> str: - """AES-256-CBC + PKCS7 + base64 — mismo esquema que factura_cove.""" - normalized = (raw_fiel or "").strip() - if not normalized: - return "" - key_bytes = (settings.COVE_FIEL_HASH_KEY or "").encode("utf-8") - iv_bytes = (settings.COVE_FIEL_HASH_IV or "").encode("utf-8") - if not key_bytes or not iv_bytes: - return "" - key32 = key_bytes[:32].ljust(32, b"\0") - iv16 = iv_bytes[:16].ljust(16, b"\0") - padder = crypto_padding.PKCS7(algorithms.AES.block_size).padder() - padded = padder.update(normalized.encode("utf-8")) + padder.finalize() - cipher = Cipher(algorithms.AES(key32), modes.CBC(iv16)) - enc = cipher.encryptor() - encrypted = enc.update(padded) + enc.finalize() - return base64.b64encode(encrypted).decode("ascii") - - -def _resolve_broker_for_vu( - db: Session, - company_id: int, - tenant_id: int, - agente_aduanal_key: str, -) -> Optional[cb_models.CustomsBroker]: - normalized_key = (agente_aduanal_key or "").strip() - if not normalized_key: - return None - - brokers = ( - db.query(cb_models.CustomsBroker) - .filter( - or_( - cb_models.CustomsBroker.broker_key == normalized_key, - cb_models.CustomsBroker.license == normalized_key, - ), - cb_models.CustomsBroker.company_id == company_id, - cb_models.CustomsBroker.tenant_id == tenant_id, - cb_models.CustomsBroker.deleted_at.is_(None), - ) - .order_by(cb_models.CustomsBroker.id.desc()) - .all() - ) - if not brokers: - return None - - exact_broker_key = next( - (broker for broker in brokers if (broker.broker_key or "").strip() == normalized_key), - None, - ) - if exact_broker_key: - return exact_broker_key - - if len(brokers) > 1: - logger.warning( - "Multiple customs brokers matched agente_aduanal=%s; falling back to first license match ids=%s broker_keys=%s", - normalized_key, - [broker.id for broker in brokers], - [broker.broker_key for broker in brokers], - ) - - return brokers[0] - - -def resolve_rfc_consulta_value( - db: Session, - company_id: int, - tenant_id: int, - agente_aduanal_key: Optional[str], - request_rfc_consulta: Optional[str], - record_rfc_consulta: Optional[str], - config_vu_rfc: Optional[str], -) -> str: - explicit_rfc = (request_rfc_consulta or "").strip().upper() - if explicit_rfc: - return explicit_rfc - - broker_tax_id = "" - if agente_aduanal_key: - broker = _resolve_broker_for_vu(db, company_id, tenant_id, agente_aduanal_key) - broker_tax_id = (getattr(broker, "tax_id", None) or "").strip().upper() if broker else "" - if broker_tax_id: - return broker_tax_id - - stored_rfc = (record_rfc_consulta or "").strip().upper() - if stored_rfc: - return stored_rfc - - config_rfc = (config_vu_rfc or "").strip().upper() - if config_rfc: - return config_rfc - - raise ValidationException( - "RFC Consulta no disponible", - errors=[ - { - "field": "rfc_consulta", - "message": "No se pudo resolver el RFC Consulta desde el expediente ni desde el customs broker.", - "code": "MISSING_RFC_CONSULTA", - "solution": [ - "Configura el RFC del agente aduanal en el customs broker o captura el RFC directamente en el expediente." - ], - } - ], - ) - - -def build_configuracion_vu( - db: Session, - company_id: int, - tenant_id: int, - agente_aduanal_key: Optional[str], - errors: ErrorCollector, -) -> Optional[dict]: - """ - Construye el dict de configuracion_vu para el servicio externo de digitalización. - Prioridad: CustomsBrokerVU (agente) → company.ventanilla_unica → company_fiel_certificate. - """ - vu: Optional[cb_models.CustomsBrokerVU] = None - if agente_aduanal_key: - broker = _resolve_broker_for_vu(db, company_id, tenant_id, agente_aduanal_key) - if broker: - vu = broker.vu - else: - broker = ( - db.query(cb_models.CustomsBroker) - .filter( - cb_models.CustomsBroker.company_id == company_id, - cb_models.CustomsBroker.tenant_id == tenant_id, - cb_models.CustomsBroker.deleted_at.is_(None), - ) - .first() - ) - if broker: - vu = broker.vu - - company = db.get(Company, company_id) - company_vu = company.ventanilla_unica if company else None - - company_fiel_certificate = None - if company: - for cert in (company.digital_certificates or []): - if (cert.certificate_type or "").strip().lower() == "fiel": - company_fiel_certificate = cert - break - - if not vu and not company_vu and not company_fiel_certificate: - errors.add_error( - field="vu", - message="La empresa no tiene configuración VU ni certificado FIEL.", - solution=["Configura los datos VU del agente aduanal o de la empresa y sube certificado (.cer) y llave (.key)."], - code="MISSING_VU_CONFIGURATION", - ) - return None - - clave_fiel_value = "" - if vu and getattr(vu, "fiel_access_key", None): - clave_fiel_value = _encrypt_fiel(vu.fiel_access_key or "") - elif company_fiel_certificate: - secret = ( - getattr(company_fiel_certificate, "access_key", None) - or getattr(company_fiel_certificate, "password", None) - or "" - ) - clave_fiel_value = _encrypt_fiel(str(secret)) - - hardcoded_ws_key = ( - "RZGd+CB4R6PfSrstOyN8Is9FXL9AK9NPFisyGGaEWa0vVHoVOl8v2SBcHBoGbt3T/4uHTGcsFQO3b7EonWVfugQjBooywbz74K+jM68j8/Y=" - ) - vu_ws_key = (getattr(vu, "web_service_access_key", None) or "").strip() if vu else "" - vu_access_key_raw = (getattr(vu, "access_key", None) or "").strip() if vu else "" - vu_access_key_encrypted = _encrypt_fiel(vu_access_key_raw) if vu_access_key_raw else "" - company_ws_key = (getattr(company_vu, "webservice_password", None) or "").strip() if company_vu else "" - if vu_ws_key: - ws_key_source = "web_service_access_key" - elif vu_access_key_encrypted: - ws_key_source = "access_key_encrypted" - elif company_ws_key: - ws_key_source = "company" - else: - ws_key_source = "fallback" - clave_webservice = vu_ws_key or vu_access_key_encrypted or company_ws_key or hardcoded_ws_key - - if not clave_webservice: - errors.add_error( - field="vu.clave_webservice", - message="La clave de web service no está configurada en VU ni en la empresa.", - solution=["Captura la clave de web service en la pestaña VU o completa la configuración VU de la empresa."], - code="MISSING_VU_WS_KEY", - ) - - if not clave_fiel_value: - errors.add_error( - field="vu.clave_fiel", - message="La clave FIEL no está configurada en VU ni en la empresa.", - solution=["Captura la clave FIEL en la configuración VU del agente aduanal o en los certificados de la empresa."], - code="MISSING_FIEL_PASSWORD", - ) - - certificate_path = ( - (getattr(vu, "certificate_path", None) or "").strip() if vu else "" - ) or ( - (getattr(company_fiel_certificate, "cer_file_path", None) or "").strip() - if company_fiel_certificate - else "" - ) - key_path = ( - (getattr(vu, "key_path", None) or "").strip() if vu else "" - ) or ( - (getattr(company_fiel_certificate, "key_file_path", None) or "").strip() - if company_fiel_certificate - else "" - ) - - if not (certificate_path and key_path): - errors.add_error( - field="vu", - message="No hay rutas de certificado o llave en la configuración VU.", - solution=["Sube el certificado (.cer) y la llave (.key) en la configuración VU del agente aduanal."], - code="MISSING_VU_CERT_KEY", - ) - return None - - cer_b64 = None - key_b64 = None - try: - if not object_exists(certificate_path): - errors.add_error( - field="vu.certificate_path", - message="El certificado VU no existe en el almacenamiento.", - solution=["Vuelve a subir el certificado en la configuración VU."], - code="VU_CERT_NOT_FOUND", - ) - else: - cer_b64 = base64.b64encode(get_object_bytes(certificate_path)).decode("ascii") - - if not object_exists(key_path): - errors.add_error( - field="vu.key_path", - message="La llave VU no existe en el almacenamiento.", - solution=["Vuelve a subir la llave en la configuración VU."], - code="VU_KEY_NOT_FOUND", - ) - else: - key_b64 = base64.b64encode(get_object_bytes(key_path)).decode("ascii") - except Exception: - logger.exception("Error leyendo certificados VU desde almacenamiento") - errors.add_error( - field="vu", - message="Error leyendo certificados VU.", - solution=["Verifica la configuración de MinIO/S3."], - code="VU_STORAGE_ERROR", - ) - - if errors.has_errors(): - return None - - rfc_usuario_vu = ( - (getattr(vu, "query_tax_id", None) or "").strip() if vu else "" - ) or ( - (getattr(company_vu, "query_rfc", None) or "").strip() if company_vu else "" - ) - - email = ( - (getattr(vu, "vu_email", None) or "").strip() if vu else "" - ) or ( - (getattr(company_vu, "email", None) or "").strip() if company_vu else "" - ) or ( - (getattr(getattr(company, "address", None), "email", None) or "").strip() if company else "" - ) - - if ws_key_source == "fallback": - logger.warning( - "Expediente digitalization is using fallback web service key for agente_aduanal=%s company_id=%s tenant_id=%s", - agente_aduanal_key, - company_id, - tenant_id, - ) - - return { - "rfc_usuario_vu": rfc_usuario_vu, - "clave_webservice": clave_webservice, - "archivo_cer_base64": cer_b64 or "", - "archivo_key_base64": key_b64 or "", - "clave_fiel": clave_fiel_value, - "email": email, - "_ws_key_source": ws_key_source, - } diff --git a/backend/api/v1/modules/a76/expediente_archivos/tasks.py b/backend/api/v1/modules/a76/expediente_archivos/tasks.py deleted file mode 100644 index 2e35c3c7..00000000 --- a/backend/api/v1/modules/a76/expediente_archivos/tasks.py +++ /dev/null @@ -1,569 +0,0 @@ -from __future__ import annotations - -import base64 -import logging -import os -import time -from typing import Any, Dict - -from celery import Task -from celery.exceptions import Ignore -import httpx - -from core.celery_app import celery_app -from core.database import CoreSessionLocal -from core.exceptions import ErrorCollector, ValidationException -from core.storage_s3 import delete_object_if_exists, get_object_bytes, object_exists, put_object_bytes -from core.s3_keys import expediente_archivo_artifact_key - -from .models import ExpedienteArchivo -from .service import ExpedienteArchivoService, build_configuracion_vu, resolve_rfc_consulta_value -from .external_service import ExpedienteExternalService - -logger = logging.getLogger(__name__) - -TOTAL_STEPS = 4 - - -def _fail_task( - task: Task, - *, - error: str, - error_type: str, - codigo: str, - descripcion: str, - paso: str, - sugerencias: list[str] | None = None, -) -> None: - task.update_state( - state="FAILED", - meta={ - "error": error, - "error_type": error_type, - "error_detail": { - "codigo": codigo, - "descripcion": descripcion, - "paso": paso, - "sugerencias": sugerencias or [], - }, - }, - ) - raise Ignore() - - -def _load_record_file_base64(record: ExpedienteArchivo) -> str: - stored_path = (record.archivo_digitalizado_en or "").strip() - if not stored_path: - raise ValidationException( - "El expediente no tiene archivo cargado", - errors=[ - { - "field": "archivo_digitalizado_en", - "message": "El expediente no tiene archivo almacenado para digitalizar.", - "code": "MISSING_FILE", - "solution": ["Edita el expediente y vuelve a seleccionar el archivo antes de digitalizar."], - } - ], - ) - - load_started_at = time.perf_counter() - source = "unknown" - try: - if os.path.exists(stored_path): - source = "local" - with open(stored_path, "rb") as file_handle: - raw = file_handle.read() - elif object_exists(stored_path): - source = "s3" - raw = get_object_bytes(stored_path) - else: - raise ValidationException( - "Archivo del expediente no encontrado", - errors=[ - { - "field": "archivo_digitalizado_en", - "message": "No se encontró el archivo almacenado del expediente.", - "code": "FILE_NOT_FOUND", - "solution": ["Edita el expediente y vuelve a cargar el documento."], - } - ], - ) - except ValidationException: - raise - except Exception as exc: - raise ValidationException( - "No se pudo leer el archivo del expediente", - errors=[ - { - "field": "archivo_digitalizado_en", - "message": "Ocurrió un error leyendo el archivo almacenado del expediente.", - "code": "FILE_READ_ERROR", - "solution": ["Vuelve a cargar el archivo del expediente e inténtalo nuevamente."], - } - ], - ) from exc - - logger.info( - "Expediente file loaded expediente_id=%s source=%s bytes=%s elapsed_ms=%.1f", - record.id, - source, - len(raw), - (time.perf_counter() - load_started_at) * 1000, - ) - - return base64.b64encode(raw).decode("ascii") - - -def _progress(task: Task, current: int, status: str) -> None: - task.update_state( - state="PROGRESS", - meta={"current": current, "status": status, "current_step": status, "progress": current, "total_steps": TOTAL_STEPS}, - ) - - -def _poll_external( - task: Task, - external: ExpedienteExternalService, - external_task_id: str, - timeout_seconds: int = 300, -) -> Dict[str, Any]: - """ - Hace polling al API externo hasta obtener un estado final o agotar el timeout. - Retorna el payload final tal como lo devuelve el API externo. - """ - start = time.perf_counter() - last_payload: Dict[str, Any] = {} - attempts = 0 - - while True: - attempts += 1 - elapsed = time.perf_counter() - start - if elapsed > timeout_seconds: - logger.error( - "Timeout en polling externo de digitalización: task_id=%s attempts=%s elapsed_s=%.2f", - external_task_id, - attempts, - elapsed, - ) - raise TimeoutError(f"Timeout consultando estado de digitalización (task_id={external_task_id}).") - - try: - status_payload = external.get_status(external_task_id) or {} - except httpx.ReadTimeout: - # VU mantiene la conexión abierta mientras procesa; si httpx corta antes, - # lo tratamos como "sigue en proceso" y reintentamos. - logger.warning( - "ReadTimeout consultando estado externo, reintentando task_id=%s attempts=%s elapsed_s=%.2f", - external_task_id, - attempts, - elapsed, - ) - time.sleep(5) - continue - except Exception: - logger.exception( - "Error consultando estado externo de digitalización task_id=%s attempts=%s elapsed_s=%.2f", - external_task_id, - attempts, - elapsed, - ) - raise - - last_payload = status_payload - state = str(status_payload.get("state") or "").upper() - progress_info = status_payload.get("progress") - percent = 0.0 - current_step = str( - status_payload.get("current_step") - or status_payload.get("status") - or "Consultando estado en Ventanilla Única..." - ) - - if isinstance(progress_info, dict): - raw_percent = progress_info.get("progress", progress_info.get("current", 0.0)) - try: - percent = float(raw_percent) - except (TypeError, ValueError): - percent = 0.0 - - current_step = str( - progress_info.get("current_step") - or progress_info.get("status") - or current_step - ) - elif isinstance(progress_info, (int, float, str)): - try: - percent = float(progress_info) - except (TypeError, ValueError): - percent = 0.0 - - _progress(task, int(percent), str(current_step)) - - if state in {"PENDING", "STARTED", "PROGRESS"} or not state: - time.sleep(5) - continue - - logger.info( - "Digitalization external polling finished task_id=%s final_state=%s attempts=%s elapsed_s=%.2f", - external_task_id, - state or "UNKNOWN", - attempts, - elapsed, - ) - return last_payload - - -@celery_app.task(bind=True, name="expediente_archivos_digitalizar") -def digitalizar_task( - self: Task, - expediente_id: int, - request_data: Dict[str, Any], - company_id: int, - tenant_id: int, -) -> dict: - """ - Celery task: digitaliza un ExpedienteArchivo en Ventanilla Única. - - Pasos: - 1. Cargar el registro y construir configuracion_vu (server-side). - 2. Construir y enviar el payload al API externo. - 3. Hacer polling del estado del task externo. - 4. Persistir resultado (e_document, num_operacion, acuse_pdf_path) en DB. - """ - db = CoreSessionLocal() - task_started_at = time.perf_counter() - try: - logger.info( - "Digitalization task started task_id=%s expediente_id=%s company_id=%s tenant_id=%s", - self.request.id, - expediente_id, - company_id, - tenant_id, - ) - - # ------------------------------------------------------------------ # - # Paso 1 – cargar registro y construir configuracion_vu # - # ------------------------------------------------------------------ # - _progress(self, 5, "Construyendo configuración VU...") - - record: ExpedienteArchivo | None = db.get(ExpedienteArchivo, expediente_id) - if not record: - raise ValidationException( - "Expediente no encontrado", - errors=[{"field": "expediente_id", "message": f"No existe expediente {expediente_id}"}], - ) - - errors = ErrorCollector() - agente_key = request_data.get("agente_aduanal") or record.agente_aduanal - config_started_at = time.perf_counter() - config_vu = build_configuracion_vu(db, company_id, tenant_id, agente_key, errors) - logger.info( - "Digitalization VU config resolved task_id=%s expediente_id=%s agente_aduanal=%s elapsed_ms=%.1f", - self.request.id, - expediente_id, - agente_key, - (time.perf_counter() - config_started_at) * 1000, - ) - - if errors.has_errors(): - error_list = errors._errors # type: ignore[attr-defined] - first = error_list[0] if error_list else {} - _fail_task( - self, - error=first.get("message", "Error de configuración VU"), - error_type="VALIDATION_ERROR", - codigo=first.get("code", "VALIDATION_ERROR"), - descripcion=first.get("message", ""), - paso="Construcción de configuración VU", - sugerencias=first.get("solution") or [], - ) - - # Actualizar status en DB - resolved_rfc_consulta = resolve_rfc_consulta_value( - db, - company_id, - tenant_id, - agente_key, - request_data.get("rfc_consulta"), - record.rfc_consulta, - config_vu.get("rfc_usuario_vu"), - ) - current_record_rfc = (record.rfc_consulta or "").strip().upper() - config_vu_rfc = (config_vu.get("rfc_usuario_vu") or "").strip().upper() - if not current_record_rfc or current_record_rfc == config_vu_rfc: - record.rfc_consulta = resolved_rfc_consulta - # Limpiar artefactos previos de S3 antes de iniciar nueva digitalización - _ARTIFACT_PATH_FIELDS = [ - "acuse_pdf_path", - "envio_xml_path", - "respuesta_xml_path", - "consulta_envio_xml_path", - "consulta_respuesta_xml_path", - ] - for _field in _ARTIFACT_PATH_FIELDS: - _old_key = (getattr(record, _field, None) or "").strip() - if _old_key and _old_key != "inline": - try: - delete_object_if_exists(_old_key) - logger.info( - "Digitalization old artifact deleted task_id=%s expediente_id=%s field=%s key=%s", - self.request.id, expediente_id, _field, _old_key, - ) - except Exception: - logger.warning( - "Could not delete old artifact task_id=%s expediente_id=%s field=%s key=%s", - self.request.id, expediente_id, _field, _old_key, exc_info=True, - ) - - record.status = "processing" - record.task_id = self.request.id - record.external_task_id = None - record.e_document = None - record.num_operacion = None - record.acuse_pdf_path = None - record.envio_xml_path = None - record.respuesta_xml_path = None - record.consulta_envio_xml_path = None - record.consulta_respuesta_xml_path = None - db.commit() - - # ------------------------------------------------------------------ # - # Paso 2 – construir payload y enviar al API externo # - # ------------------------------------------------------------------ # - _progress(self, 30, "Enviando documento a Ventanilla Única...") - - file_started_at = time.perf_counter() - archivo_base64 = request_data.get("archivo_base64") or _load_record_file_base64(record) - logger.info( - "Digitalization payload document ready task_id=%s expediente_id=%s provided_inline=%s base64_len=%s elapsed_ms=%.1f", - self.request.id, - expediente_id, - bool(request_data.get("archivo_base64")), - len(archivo_base64), - (time.perf_counter() - file_started_at) * 1000, - ) - - payload = { - "rfc_consulta": resolved_rfc_consulta, - "clave_documento": request_data.get("clave_documento") or record.tipo_documento or "", - "nombre_archivo": request_data.get("nombre_archivo") or record.nombre_archivo or "", - "archivo_base64": archivo_base64, - "configuracion_vu": { - key: value for key, value in config_vu.items() if not key.startswith("_") - }, - } - - external = ExpedienteExternalService() - external_submit_started_at = time.perf_counter() - response = external.digitalizar_archivo_json(payload) - logger.info( - "Digitalization external submission finished task_id=%s expediente_id=%s external_task_id=%s response_state=%s elapsed_ms=%.1f", - self.request.id, - expediente_id, - response.get("task_id") or response.get("id"), - response.get("state") or response.get("status"), - (time.perf_counter() - external_submit_started_at) * 1000, - ) - - # Chequear si el API externo devolvió un error inmediato - resp_state = str(response.get("state") or response.get("status") or "").upper() - if resp_state in {"ERROR", "FAILURE", "FAILED"}: - error_msg = response.get("message") or response.get("error") or "Error en API externo" - record.status = "failed" - db.commit() - _fail_task( - self, - error=error_msg, - error_type="EXTERNAL_API_ERROR", - codigo="EXTERNAL_API_ERROR", - descripcion=error_msg, - paso="Envío a Ventanilla Única", - sugerencias=["Verifica las credenciales VU y vuelve a intentarlo."], - ) - - # Extraer task_id externo si el API lo devolvió de inmediato en PENDING/PROCESSING - external_task_id = response.get("task_id") or response.get("id") - - # ------------------------------------------------------------------ # - # Paso 3 – polling del estado externo # - # ------------------------------------------------------------------ # - if external_task_id: - _progress(self, 50, "Esperando respuesta de Ventanilla Única...") - record.external_task_id = str(external_task_id) - db.commit() - polling_started_at = time.perf_counter() - try: - final_response = _poll_external(self, external, str(external_task_id)) - except TimeoutError as exc: - record.status = "failed" - db.commit() - _fail_task( - self, - error=str(exc), - error_type="TIMEOUT", - codigo="TIMEOUT", - descripcion=str(exc), - paso="Polling Ventanilla Única", - sugerencias=["Vuelve a intentarlo o consulta el estado manualmente."], - ) - logger.info( - "Digitalization external wait completed task_id=%s expediente_id=%s external_task_id=%s elapsed_s=%.2f", - self.request.id, - expediente_id, - external_task_id, - time.perf_counter() - polling_started_at, - ) - else: - # El API devolvió resultado directo - final_response = response - - final_state = str(final_response.get("state") or final_response.get("status") or "").upper() - if final_state in {"ERROR", "FAILURE", "FAILED"}: - error_detail = final_response.get("error_detail") or {} - suggestions = error_detail.get("sugerencias") or [] - if config_vu.get("_ws_key_source") == "fallback": - suggestions = [ - "No hay clave real de web service configurada en VU ni en la empresa; se usó la clave fallback del sistema.", - *suggestions, - ] - error_msg = ( - final_response.get("error") - or final_response.get("message") - or error_detail.get("descripcion") - or final_response.get("status") - or "Error en Ventanilla Única" - ) - record.status = "failed" - db.commit() - _fail_task( - self, - error=str(error_msg), - error_type=str(final_response.get("error_type") or "EXTERNAL_API_ERROR"), - codigo=str(error_detail.get("codigo") or "EXTERNAL_TASK_FAILURE"), - descripcion=str(error_detail.get("descripcion") or error_msg), - paso=str(error_detail.get("paso") or "Respuesta final de Ventanilla Única"), - sugerencias=suggestions or ["Revisa el detalle devuelto por Ventanilla Única y vuelve a intentarlo."], - ) - - # ------------------------------------------------------------------ # - # Paso 4 – persistir resultado # - # ------------------------------------------------------------------ # - _progress(self, 90, "Guardando resultado...") - - result_payload = final_response.get("result") or final_response - e_doc = result_payload.get("e_document") or result_payload.get("eDocument") - num_op = result_payload.get("numero_operacion") or result_payload.get("numeroOperacion") - - record.status = "success" - if e_doc: - record.e_document = str(e_doc) - if num_op: - record.num_operacion = str(num_op) - - # Guardar todos los artefactos base64 en S3 - _ARTIFACT_FIELDS = { - "acuse": ("acuese_digitalizacion_pdf_base64", "application/pdf", "acuse_pdf_path"), - "envio_xml": ("envio_xml_base64", "application/xml", "envio_xml_path"), - "respuesta_xml": ("respuesta_xml_base64", "application/xml", "respuesta_xml_path"), - "consulta_envio_xml": ("consulta_envio_xml_base64", "application/xml", "consulta_envio_xml_path"), - "consulta_respuesta_xml": ("consulta_respuesta_xml_base64", "application/xml", "consulta_respuesta_xml_path"), - } - artifact_ts = time.strftime("%Y%m%d_%H%M%S", time.gmtime()) - for artifact_type, (result_field, content_type, record_field) in _ARTIFACT_FIELDS.items(): - b64 = result_payload.get(result_field) - if not b64: - continue - try: - key = expediente_archivo_artifact_key( - tenant_id, company_id, expediente_id, artifact_type, artifact_ts - ) - put_object_bytes(key, base64.b64decode(b64), content_type=content_type) - setattr(record, record_field, key) - logger.info( - "Digitalization artifact saved task_id=%s expediente_id=%s type=%s key=%s", - self.request.id, expediente_id, artifact_type, key, - ) - except Exception: - logger.exception( - "Failed to save artifact %s to S3 task_id=%s expediente_id=%s", - artifact_type, self.request.id, expediente_id, - ) - setattr(record, record_field, None) - - db.commit() - - logger.info( - "Digitalization task finished task_id=%s expediente_id=%s status=success total_elapsed_s=%.2f", - self.request.id, - expediente_id, - time.perf_counter() - task_started_at, - ) - - return { - "status": "success", - "message": "Digitalización completada exitosamente.", - "e_document": e_doc, - "numero_operacion": num_op, - "nombre_archivo": payload["nombre_archivo"], - "timestamp": result_payload.get("timestamp"), - "request_id": result_payload.get("request_id"), - "response_code": result_payload.get("response_code"), - } - - except Ignore: - raise - except ValidationException as exc: - if db: - try: - record = db.get(ExpedienteArchivo, expediente_id) # type: ignore - if record: - record.status = "failed" - db.commit() - except Exception: - pass - first_error = (exc.errors or [{}])[0] - self.update_state( - state="PROGRESS", - meta={"current": 0, "status": "Preparando error de validación..."}, - ) - logger.info( - "Digitalization task failed by validation task_id=%s expediente_id=%s elapsed_s=%.2f", - self.request.id, - expediente_id, - time.perf_counter() - task_started_at, - ) - _fail_task( - self, - error=first_error.get("message", str(exc)), - error_type="VALIDATION_ERROR", - codigo=first_error.get("code", "VALIDATION_ERROR"), - descripcion=first_error.get("message", ""), - paso="Validación", - sugerencias=first_error.get("solution") or [], - ) - except Exception as exc: - logger.exception( - "Error inesperado en digitalizar_task task_id=%s expediente_id=%s elapsed_s=%.2f", - self.request.id, - expediente_id, - time.perf_counter() - task_started_at, - ) - if db: - try: - record = db.get(ExpedienteArchivo, expediente_id) # type: ignore - if record: - record.status = "failed" - db.commit() - except Exception: - pass - _fail_task( - self, - error=str(exc), - error_type=type(exc).__name__, - codigo="UNEXPECTED_ERROR", - descripcion=str(exc), - paso="Proceso de digitalización", - sugerencias=["Contacta al soporte técnico."], - ) - finally: - db.close() diff --git a/backend/api/v1/modules/a76/factura_cove/external_service.py b/backend/api/v1/modules/a76/factura_cove/external_service.py deleted file mode 100644 index 61e9985b..00000000 --- a/backend/api/v1/modules/a76/factura_cove/external_service.py +++ /dev/null @@ -1,213 +0,0 @@ -from __future__ import annotations - -from dataclasses import dataclass -from typing import Any, Dict -import logging -from urllib.parse import urljoin - -import httpx - -from core.config import settings -from .schemas import FacturaCoveRequest - - -logger = logging.getLogger(__name__) - - -@dataclass -class CoveExternalResult: - """Resultado normalizado de POST /generar-factura-cove contra el API de VU.""" - - status: str - message: str | None = None - cove_number: str | None = None - vucem_operation_num: str | None = None - # XML de COVE devuelto por VU codificado en base64 (campo envio_base64 del result) - cove_xml_base64: str | None = None - raw_response: Dict[str, Any] | None = None - - -class CoveExternalService: - """ - Cliente del API externo de COVE. - - Implementación real: - - POST /api/v1/factura-cove/generar-factura-cove para iniciar proceso. - - GET /api/v1/factura-cove/status/{task_id} para consultar estado. - """ - - def __init__(self) -> None: - self.base_url = settings.COVE_API_URL.strip() - self.verify_ssl = settings.COVE_API_VERIFY_SSL - - def generate_cove(self, payload: FacturaCoveRequest) -> CoveExternalResult: - """ - Llama al endpoint externo /api/v1/factura-cove/generar-factura-cove - con el FacturaCoveRequest completo y retorna el resultado tal como - lo reporta el servicio remoto. - """ - url = f"{self.base_url.rstrip('/')}/api/v1/factura-cove/generar-factura-cove" - json_payload = payload.model_dump(mode="json") - configuracion_vu = json_payload.get("configuracion_vu") or {} - logger.info( - "Sending COVE payload: invoice=%s rfc_vu=%s clave_fiel_len=%s clave_ws_len=%s cer_len=%s key_len=%s", - json_payload.get("numero_factura"), - configuracion_vu.get("rfc_usuario_vu"), - len(configuracion_vu.get("clave_fiel") or ""), - len(configuracion_vu.get("clave_webservice") or ""), - len(configuracion_vu.get("archivo_cer_base64") or ""), - len(configuracion_vu.get("archivo_key_base64") or ""), - ) - - with httpx.Client(timeout=30.0, verify=self.verify_ssl, follow_redirects=False) as client: - resp = client.post(url, json=json_payload) - if resp.status_code in {301, 302, 307, 308} and resp.headers.get("location"): - redirected_url = urljoin(url, resp.headers["location"]) - logger.info("COVE generate redirect: %s -> %s", url, redirected_url) - resp = client.post(redirected_url, json=json_payload) - - # Intentar parsear JSON siempre, incluso en errores 4xx/5xx - try: - data: Dict[str, Any] = resp.json() - except Exception: - data = {} - - # Si el servicio externo respondió con error (por ejemplo 422 Validation Error), - # devolvemos un resultado de error rico en información para que la UI pueda - # mostrar el detalle completo. - if resp.status_code >= 400: - # Intentar construir un mensaje amigable - message = None - if isinstance(data, dict): - message = data.get("message") or data.get("mensaje") - if not message and "detail" in data: - # FastAPI ValidationError-style: detail: [{loc, msg, type}, ...] - try: - parts = [str(d.get("msg")) for d in data["detail"] if isinstance(d, dict)] - message = "; ".join([p for p in parts if p]) - except Exception: - pass - if not message and isinstance(data.get("errores"), list): - # Formato API VU: { mensaje, errores: [{ campo, mensaje, tipo_error }] } - parts: list[str] = [] - for e in data["errores"]: - if not isinstance(e, dict): - continue - campo = e.get("campo") or "" - msg = e.get("mensaje") or "" - if campo and msg: - parts.append(f"{campo}: {msg}") - elif msg: - parts.append(msg) - if parts: - message = "; ".join(parts) - if not message: - message = resp.text or f"HTTP {resp.status_code}" - - status = "validation_error" if resp.status_code == 422 else "error" - - return CoveExternalResult( - status=status, - message=message, - cove_number=None, - vucem_operation_num=None, - raw_response={ - "status_code": resp.status_code, - "body": data, - }, - ) - - # 2xx: según la especificación del servicio externo, al menos devuelve: - # { "task_id": "...", "status": "...", "message": "..." } - raw_status = str(data.get("status") or "queued") - message = data.get("message") - external_task_id = data.get("task_id") - - # Si VU respondió "queued/external_queued/success" pero no task_id, la respuesta - # no es utilizable para polling; tratarlo como error explícito. - if raw_status.lower() in {"queued", "external_queued", "success"} and not external_task_id: - return CoveExternalResult( - status="error", - message=( - "Respuesta inválida del servicio COVE: no se recibió task_id " - "para consultar el estado." - ), - cove_number=None, - vucem_operation_num=None, - raw_response={ - "status_code": resp.status_code, - "body": data, - }, - ) - - # Normalizar el status de VU a un valor interno que active el polling cuando procede. - # El API de VU ha devuelto distintos valores según la versión: - # - "PENDING" / "STARTED" con task_id → tarea encolada, se debe hacer polling. - # - "error" con mensaje "Factura COVE iniciada para" → mismo caso (versión anterior). - # - cualquier otro valor → se pasa sin modificar. - normalized_status = raw_status.lower() - if normalized_status in {"pending", "started"} and external_task_id: - status = "external_queued" - elif ( - normalized_status == "error" - and isinstance(message, str) - and "Factura COVE iniciada para" in message - ): - status = "external_queued" - else: - status = raw_status - - # El número de COVE normalmente se obtendrá vía /status/{task_id}; por ahora - # lo dejamos en None y exponemos la respuesta completa para inspección en UI. - return CoveExternalResult( - status=status, - message=message, - cove_number=None, - vucem_operation_num=None, - raw_response={ - "task_id": external_task_id, - "status": status, - "message": message, - "raw": data, - }, - ) - - def get_status(self, task_id: str) -> Dict[str, Any]: - """ - Consulta el endpoint externo /api/v1/factura-cove/status/{task_id} - y devuelve el JSON de progreso/resultado tal cual lo envía el servicio. - - Ejemplo de respuesta esperada (simplificada): - { - "task_id": "...", - "state": "PROGRESS" | "SUCCESS" | "FAILURE", - "result": null | {...}, - "error": null | "...", - "progress": { - "current_step": "Consultando respuesta COVE con número de operación", - "progress": 10.5, - "total_steps": 12, - "task_id": "...", - "numero_operacion": "306658625" - } - } - """ - url = f"{self.base_url.rstrip('/')}/api/v1/factura-cove/status/{task_id}" - - with httpx.Client(timeout=30.0, verify=self.verify_ssl, follow_redirects=False) as client: - resp = client.get(url) - if resp.status_code in {301, 302, 307, 308} and resp.headers.get("location"): - redirected_url = urljoin(url, resp.headers["location"]) - logger.info("COVE status redirect: %s -> %s", url, redirected_url) - resp = client.get(redirected_url) - - try: - data: Dict[str, Any] = resp.json() - except Exception: - data = {} - - # Adjuntar metadatos mínimos de respuesta HTTP - data.setdefault("status_code", resp.status_code) - - return data - diff --git a/backend/api/v1/modules/a76/factura_cove/report_service.py b/backend/api/v1/modules/a76/factura_cove/report_service.py deleted file mode 100644 index f00188df..00000000 --- a/backend/api/v1/modules/a76/factura_cove/report_service.py +++ /dev/null @@ -1,780 +0,0 @@ -""" -Generación de PDF de Acuse de COVE (Jinja2 + pdfkit / wkhtmltopdf). - -El contexto se construye preferentemente a partir del XML de COVE -almacenado en S3 (cuando está disponible y coincide el e-document), -y en su defecto directamente desde la base de datos reutilizando -la lógica ya existente en FacturaCoveDomainService (_build_personas, -_build_mercancias). - -Secciones del reporte (derivadas del Report REPORT legacy en Clarion): - - Cabecera: tipo_operacion, numero_factura, tipo_figura, fecha_expedicion - - Observaciones - - RFC de consulta / patente aduanal - - Datos del proveedor/emisor (nombre, identificación, domicilio completo) - - Datos del destinatario (nombre, identificación, domicilio completo) - - Por cada partida: descripción genérica de la mercancía, clave UMC, - cantidad, tipo moneda, valor unitario, valor total, - valor en dólares - - Por cada partida: marca, modelo, serie, submodelo (DescripcionEspecifica) - - Footer -""" - -from __future__ import annotations - -import base64 -import logging -import os -import shutil -import tempfile -from dataclasses import dataclass, field -from decimal import Decimal -from pathlib import Path -from typing import Any, List, Optional -from xml.etree import ElementTree as ET - -from core import storage_s3 -from core.s3_keys import cove_xml_key -import pdfkit -from jinja2 import Environment, FileSystemLoader, select_autoescape -from sqlalchemy.orm import Session - -from core.exceptions import ValidationException - -from .schemas import MercanciaCove, PersonaCove -from .service import FacturaCoveDomainService - -logger = logging.getLogger(__name__) - - -# --------------------------------------------------------------------------- -# Modelos de vista para el template (uno por sección del reporte legacy) -# --------------------------------------------------------------------------- - -@dataclass -class CoveItemView: - """Corresponde a DetallePartidasFactura + DetallePartidaMarcaModelo del legacy.""" - # DetallePartidasFactura - descripcion_generica: str - clave_unidad_medida: str - cantidad: str - tipo_moneda: str - valor_unitario: str - valor_total: str - valor_dolares: str - # DetallePartidaMarcaModelo (DescripcionEspecifica) - descripciones_especificas: List["CoveDescEspecificaView"] = field(default_factory=list) - - -@dataclass -class CoveDescEspecificaView: - marca: str = "" - modelo: str = "" - submodelo: str = "" - numero_serie: str = "" - - -@dataclass -class CovePersonaView: - """Corresponde al bloque Emisor/Destinatario del reporte legacy.""" - # Loc:TipoIdentificador / Loc:Des_TipoIdentificador - tipo_identificador: str = "" - # Loc:identificacion / Loc:Des_identificacion - identificacion: str = "" - # Loc:Emisor_Nombre / Loc:Des_Nombre - nombre: str = "" - apellido_paterno: str = "" - apellido_materno: str = "" - # Domicilio - calle: str = "" - numero_exterior: str = "" - numero_interior: str = "" - colonia: str = "" - codigo_postal: str = "" - localidad: str = "" - municipio: str = "" - entidad_federativa: str = "" - pais: str = "" - - -@dataclass -class CoveAcuseContext: - """ - Contexto completo para el template acuse_cove.html. - - Mapeo de campos Clarion → atributos de este dataclass: - Loc:tipoOperacion → tipo_operacion - Loc:numeroFacturaOriginal → numero_factura_relacionada - Loc:numeroFactura → numero_factura - Loc:tipoFigura → tipo_figura - Loc:fechaExpedicion → fecha_expedicion - Loc:ObservacionesCove → observaciones - Loc:rfcConsulta → rfc_consulta - Loc:NombreRazonSocial_Consulta → nombre_razon_social_consulta - Loc:patenteAduanal → patente_aduanal - Loc:subdivision → subdivision - Loc:certificadoOrigen → certificado_origen - Loc:exportadorAutorizado → exportador_autorizado - Emisor.* → emisor (CovePersonaView) - Destinatario.* → destinatario (CovePersonaView) - DetallePartidasFactura.* → items (lista de CoveItemView) - """ - # --- Sección "Datos" (cabecera) --- - tipo_operacion: str = "" - numero_factura_relacionada: str = "" - numero_factura: str = "" - tipo_figura: str = "" - fecha_expedicion: str = "" - observaciones: str = "" - rfc_consulta: str = "" - nombre_razon_social_consulta: str = "" - patente_aduanal: str = "" - subdivision: str = "" - certificado_origen: str = "" - exportador_autorizado: str = "" - edocument: str = "" - vucem_operation_num: str = "" - # --- Personas --- - emisor: CovePersonaView = field(default_factory=CovePersonaView) - destinatario: CovePersonaView = field(default_factory=CovePersonaView) - # --- Partidas --- - items: List[CoveItemView] = field(default_factory=list) - - -# --------------------------------------------------------------------------- -# Helpers de conversión -# --------------------------------------------------------------------------- - -def _fmt_decimal(value: Decimal | None) -> str: - if value is None: - return "0.00" - return f"{value:,.2f}" - - -def _persona_to_view(p: PersonaCove) -> CovePersonaView: - return CovePersonaView( - tipo_identificador=p.tipo_identificador or "", - identificacion=p.identificacion or "", - nombre=p.nombre or "", - apellido_paterno=p.apellido_paterno or "", - apellido_materno=p.apellido_materno or "", - calle=p.calle or "", - numero_exterior=p.numero_exterior or "", - numero_interior=p.numero_interior or "", - colonia=p.colonia or "", - codigo_postal=p.codigo_postal or "", - localidad=p.localidad or "", - municipio=p.municipio or "", - entidad_federativa=p.entidad_federativa or "", - pais=p.pais or "", - ) - - -def _mercancia_to_view(m: MercanciaCove) -> CoveItemView: - descripciones = [ - CoveDescEspecificaView( - marca=d.marca or "", - modelo=d.modelo or "", - submodelo=d.submodelo or "", - numero_serie=d.numero_serie or "", - ) - for d in (m.descripcion_especifica or []) - ] - return CoveItemView( - descripcion_generica=m.descripcion_generica or "", - clave_unidad_medida=m.clave_unidad_medida or "", - cantidad=_fmt_decimal(m.cantidad), - tipo_moneda=m.tipo_moneda or "", - valor_unitario=_fmt_decimal(m.valor_unitario), - valor_total=_fmt_decimal(m.valor_total), - valor_dolares=_fmt_decimal(m.valor_dolares), - descripciones_especificas=descripciones, - ) - - -def _strip_tag(tag: str) -> str: - """ - Normaliza un tag XML removiendo el namespace ({...}) y llevándolo a minúsculas. - """ - if "}" in tag: - tag = tag.split("}", 1)[1] - return tag.strip().lower() - - -def _find_first_text(elem: ET.Element, candidates: list[str]) -> str: - """ - Busca recursivamente el primer elemento cuyo nombre coincida con alguno - de los candidatos (ignorando namespace y mayúsculas) y devuelve su texto. - """ - wanted = {c.lower() for c in candidates} - for node in elem.iter(): - if _strip_tag(node.tag) in wanted: - return (node.text or "").strip() - return "" - - -def _find_child_text(elem: ET.Element, candidates: list[str]) -> str: - """ - Busca solo entre los hijos directos de elem. - """ - wanted = {c.lower() for c in candidates} - for node in list(elem): - if _strip_tag(node.tag) in wanted: - return (node.text or "").strip() - return "" - - -def _build_persona_from_xml(root: ET.Element, role_candidates: list[str]) -> CovePersonaView: - """ - Intenta construir una CovePersonaView desde un bloque o - (o etiquetas equivalentes) en el XML de COVE. - - Estrategia de búsqueda: - - Identidad/nombre: _find_child_text (hijos directos) para evitar que campos - de otros bloques hermanos contaminen el resultado. - - Domicilio: _find_first_text dentro del elemento de persona, porque el XML - de VUCEM puede anidar las direcciones en un sub-bloque . - """ - wanted_roles = {r.lower() for r in role_candidates} - persona_elem: Optional[ET.Element] = None - for node in root.iter(): - if _strip_tag(node.tag) in wanted_roles: - persona_elem = node - break - - if persona_elem is None: - return CovePersonaView() - - e = persona_elem - return CovePersonaView( - # Campos de identificación — busca solo hijos directos para no mezclar - # datos entre emisor y destinatario cuando ambos están en el mismo árbol. - tipo_identificador=_find_child_text(e, [ - "tipoIdentificador", "tipo_identificador", - ]), - identificacion=_find_child_text(e, [ - # El campo del schema Pydantic se serializa como "identificacion" - "identificacion", - # Variantes legacy / VUCEM camelCase - "identificador", "taxId", "tax_id", "rfc", "sinTaxId", "sin_tax_id", - ]), - nombre=_find_child_text(e, [ - "nombre", "razonSocial", "razon_social", "name", - ]), - apellido_paterno=_find_child_text(e, ["apellidoPaterno", "apellido_paterno"]), - apellido_materno=_find_child_text(e, ["apellidoMaterno", "apellido_materno"]), - # Campos de domicilio — búsqueda recursiva dentro de la persona para - # manejar el wrapper que usa el esquema VUCEM. - calle=_find_first_text(e, ["calle", "street"]), - numero_exterior=_find_first_text(e, ["numeroExterior", "numero_exterior", "numExterior"]), - numero_interior=_find_first_text(e, ["numeroInterior", "numero_interior", "numInterior"]), - colonia=_find_first_text(e, ["colonia", "neighborhood"]), - codigo_postal=_find_first_text(e, ["codigoPostal", "codigo_postal", "cp", "zipCode", "zip_code"]), - localidad=_find_first_text(e, ["localidad", "ciudad", "city"]), - municipio=_find_first_text(e, ["municipio", "delegacion"]), - entidad_federativa=_find_first_text(e, [ - "entidadFederativa", "entidad_federativa", "estado", "state", - ]), - pais=_find_first_text(e, ["pais", "country", "pais_code", "paisCode"]), - ) - - -def _build_items_from_xml(root: ET.Element) -> list[CoveItemView]: - """ - Construye una lista de CoveItemView a partir de los nodos - (o equivalentes) del XML COVE. - """ - items: list[CoveItemView] = [] - - # Encontrar todos los nodos tipo "mercancia" - for node in root.iter(): - if _strip_tag(node.tag) not in {"mercancia", "mercancias", "item", "partida"}: - continue - - # Evitar interpretar el contenedor como ítem - # (si tiene hijos lo tratamos como contenedor). - child_tags = {_strip_tag(c.tag) for c in list(node)} - if "mercancia" in child_tags: - continue - - desc_generica = _find_child_text(node, ["descripcionGenerica", "descripcion_generica", "descripcion"]) - clave_uom = _find_child_text(node, ["claveUnidadMedida", "clave_unidad_medida", "claveUmed"]) - cantidad_txt = _find_child_text(node, ["cantidad"]) - tipo_moneda = _find_child_text(node, ["tipoMoneda", "tipo_moneda", "moneda"]) - valor_unit_txt = _find_child_text(node, ["valorUnitario", "valor_unitario"]) - valor_total_txt = _find_child_text(node, ["valorTotal", "valor_total"]) - valor_usd_txt = _find_child_text(node, ["valorDolares", "valor_dolares", "valorUsd", "valor_usd"]) - - def _to_decimal(text: str) -> Decimal: - try: - return Decimal(text.replace(",", "")) if text else Decimal("0") - except Exception: - return Decimal("0") - - cantidad = _to_decimal(cantidad_txt).quantize(Decimal("0.01")) - valor_unitario = _to_decimal(valor_unit_txt).quantize(Decimal("0.01")) - valor_total = _to_decimal(valor_total_txt).quantize(Decimal("0.01")) - valor_dolares = _to_decimal(valor_usd_txt).quantize(Decimal("0.01")) - - # Descripciones específicas (marca/modelo/serie/submodelo) - desc_especificas: list[CoveDescEspecificaView] = [] - for de in node.iter(): - # _strip_tag ya devuelve minúsculas, así que los candidatos deben - # estar también en minúsculas para que el `in` funcione. - if _strip_tag(de.tag) not in {"descripcionespecifica", "descripcion_especifica"}: - continue - desc_especificas.append( - CoveDescEspecificaView( - marca=_find_child_text(de, ["marca"]), - modelo=_find_child_text(de, ["modelo"]), - submodelo=_find_child_text(de, ["submodelo"]), - numero_serie=_find_child_text(de, ["numeroSerie", "numero_serie", "serie"]), - ) - ) - - items.append( - CoveItemView( - descripcion_generica=desc_generica, - clave_unidad_medida=clave_uom, - cantidad=_fmt_decimal(cantidad), - tipo_moneda=tipo_moneda, - valor_unitario=_fmt_decimal(valor_unitario), - valor_total=_fmt_decimal(valor_total), - valor_dolares=_fmt_decimal(valor_dolares), - descripciones_especificas=desc_especificas, - ) - ) - - return items - - -# --------------------------------------------------------------------------- -# Servicio principal -# --------------------------------------------------------------------------- - -class CoveAcuseReportService: - """ - Genera el PDF de Acuse de COVE a partir de los datos de la factura en BD. - - Patrón idéntico al de DodaReportPdfService: - 1. build_context() → CoveAcuseContext (desde BD vía FacturaCoveDomainService) - 2. render_pdf_bytes() → bytes (Jinja2 → HTML → pdfkit/wkhtmltopdf) - """ - - def __init__(self) -> None: - self.template_dir = Path(__file__).parent / "templates" - self.jinja_env = Environment( - loader=FileSystemLoader(str(self.template_dir)), - autoescape=select_autoescape(["html"]), - ) - self._tpl = self.jinja_env.get_template("acuse_cove.html") - - # ------------------------------------------------------------------ - # Helpers de imagen - # ------------------------------------------------------------------ - - def _image_data_uri(self, filename: str) -> str: - """Carga una imagen del directorio de templates y la devuelve como data URI base64. - - Detecta el tipo real desde magic bytes en lugar de confiar en la extensión, - porque las imágenes del template son JPEGs renombrados como .png. - """ - img_path = self.template_dir / filename - try: - with open(img_path, "rb") as fh: - raw = fh.read() - data = base64.b64encode(raw).decode("ascii") - if raw[:2] == b"\xff\xd8": - mime = "image/jpeg" - elif raw[:8] == b"\x89PNG\r\n\x1a\n": - mime = "image/png" - elif raw[:6] in (b"GIF87a", b"GIF89a"): - mime = "image/gif" - else: - ext = img_path.suffix.lstrip(".").lower() - mime = f"image/{ext}" - return f"data:{mime};base64,{data}" - except Exception: - logger.warning("No se pudo cargar imagen del template: %s", img_path) - return "" - - # ------------------------------------------------------------------ - # wkhtmltopdf config (mismo helper que DodaReportPdfService) - # ------------------------------------------------------------------ - - def _get_wkhtmltopdf_config(self): - for path in ( - shutil.which("wkhtmltopdf"), - "/usr/local/bin/wkhtmltopdf", - "/usr/bin/wkhtmltopdf", - ): - if path: - return pdfkit.configuration(wkhtmltopdf=path) - raise RuntimeError("wkhtmltopdf binary not found.") - - # ------------------------------------------------------------------ - # Construcción del contexto desde BD - # ------------------------------------------------------------------ - - def _load_cove_xml_bytes( - self, - domain: FacturaCoveDomainService, - ctx: Any, - ) -> Optional[bytes]: - """ - Intenta obtener el XML de COVE asociado a la factura. - - Orden de búsqueda: - 1. Clave S3 específica por factura (guardada al recibir el XML de VU): - ``.../invoices/{invoice_id}/cove/cove.xml`` - 2. CustomsBrokerVU.xml_files_path (ruta compartida legacy). - 3. Company.ventanilla_unica.xml_files_path (ruta compartida legacy). - - Devuelve None si no encuentra o no puede leer ningún XML. - """ - # 1. Clave S3 específica por factura (prioridad máxima) - invoice = getattr(ctx, "invoice", None) - if invoice is not None: - try: - invoice_xml_key = cove_xml_key(invoice.tenant_id, invoice.company_id, invoice.id) - xml_bytes = storage_s3.get_object_bytes(invoice_xml_key) - if xml_bytes: - logger.info("XML de COVE cargado desde S3 por factura: %s", invoice_xml_key) - return xml_bytes - except Exception: - logger.debug("XML de COVE no encontrado en S3 por factura %s", getattr(invoice, "id", None)) - - # 2 & 3. Rutas configuradas en VU / empresa (modo legacy) - candidates: list[str] = [] - - vu = getattr(ctx, "vu", None) - if vu is not None: - ref = getattr(vu, "xml_files_path", None) - if isinstance(ref, str) and ref.strip(): - candidates.append(ref.strip()) - - company = domain._get_company(ctx) - company_vu = getattr(company, "ventanilla_unica", None) if company else None - if company_vu is not None: - ref = getattr(company_vu, "xml_files_path", None) - if isinstance(ref, str) and ref.strip(): - candidates.append(ref.strip()) - - for ref in candidates: - try: - if ref.startswith("tenants/"): - return storage_s3.get_object_bytes(ref) - if os.path.isfile(ref): - with open(ref, "rb") as fh: - return fh.read() - except Exception: - logger.warning("No se pudo leer XML de COVE desde %s", ref, exc_info=True) - - return None - - def _build_context_from_xml_if_possible( - self, - xml_bytes: bytes, - invoice: Any, - compliance: Any, - invoice_specific: bool = False, - ) -> Optional[CoveAcuseContext]: - """ - Intenta construir un CoveAcuseContext en base al XML de COVE. - - - Si ``invoice_specific=True`` el XML fue cargado desde la clave S3 - específica de la factura; en ese caso se omite la validación cruzada - por e-document (ya sabemos que pertenece a esta factura) y se usa - el primer nodo COVE válido. - - Si ``invoice_specific=False`` (modo legacy / ruta compartida) se - requiere que el e-document de la factura coincida con el XML. - """ - try: - root = ET.fromstring(xml_bytes) - except Exception: - logger.warning("No se pudo parsear XML de COVE para factura %s", getattr(invoice, "id", None)) - return None - - edoc_invoice = (getattr(compliance, "edocument", None) or "").strip() if compliance else "" - - cove_node: Optional[ET.Element] = None - - if invoice_specific: - # Modo sin validación cruzada: tomamos el primer nodo con datos de factura. - for node in root.iter(): - if _find_first_text(node, ["numeroFactura", "numero_factura", "factura"]): - cove_node = node - break - # Si no encontramos nodo con numero_factura, usamos root directamente. - if cove_node is None: - cove_node = root - else: - if not edoc_invoice: - # Sin e-document en la factura no podemos ligar el XML de ruta compartida. - return None - - # Buscar el bloque cuyo e-document coincida exactamente con el de la factura. - for node in root.iter(): - edoc_xml = _find_first_text(node, ["e-document", "edocument", "edocumento"]) - if edoc_xml and edoc_xml.strip().upper() == edoc_invoice.upper(): - cove_node = node - break - - if cove_node is None: - logger.info( - "XML de COVE no contiene bloque con e-document=%s para factura %s", - edoc_invoice, - getattr(invoice, "id", None), - ) - return None - - # Cabecera / datos generales - # Cada lista incluye: camelCase VUCEM, snake_case del schema Pydantic, - # y variantes alternativas observadas en XMLs de VUCEM. - tipo_operacion = _find_first_text(cove_node, [ - "tipoOperacion", "tipo_operacion", - ]) - numero_factura_relacionada = _find_first_text(cove_node, [ - "numeroFacturaOriginal", "numero_factura_original", - "relacionFactura", "relacionFacturas", "numero_factura_relacionada", - "facturaRelacionada", - ]) - numero_factura = _find_first_text(cove_node, [ - "numeroFactura", "numero_factura", "factura", "invoice_number", - ]) - tipo_figura = _find_first_text(cove_node, [ - "tipoFigura", "tipo_figura", - ]) - fecha_expedicion = _find_first_text(cove_node, [ - "fechaExpedicion", "fecha_expedicion", "fechaEmision", "fecha_emision", - ]) - observaciones = _find_first_text(cove_node, [ - "observaciones", "observacionesCove", "observaciones_cove", - ]) - rfc_consulta = _find_first_text(cove_node, [ - "rfcConsulta", "rfc_consulta", "rfcDeConsulta", - ]) - nombre_razon_social_consulta = _find_first_text(cove_node, [ - "nombreRazonSocialConsulta", "nombre_razon_social_consulta", - "nombreConsulta", "razonSocialConsulta", - ]) - patente_aduanal = _find_first_text(cove_node, [ - "patenteAduanal", "patente_aduanal", "patente", - ]) - subdivision = _find_first_text(cove_node, [ - "subdivision", "tieneSubdivision", "tiene_subdivision", - ]) - certificado_origen = _find_first_text(cove_node, [ - "certificadoOrigen", "certificado_origen", - ]) - exportador_autorizado = _find_first_text(cove_node, [ - "exportadorAutorizado", "exportador_autorizado", - "numExportadorAutorizado", "numero_exportador_autorizado", - ]) - - if not invoice_specific and not numero_factura: - logger.info( - "Bloque XML de COVE con e-document=%s no tiene numeroFactura legible; se descarta.", - edoc_invoice, - ) - return None - - # Si no hay e-document en la factura, intentamos extraerlo del XML. - edoc_final = edoc_invoice or _find_first_text(cove_node, ["e-document", "edocument", "edocumento"]) - - emisor_view = _build_persona_from_xml(cove_node, ["emisor", "exportador"]) - destinatario_view = _build_persona_from_xml(cove_node, ["destinatario", "importador"]) - items = _build_items_from_xml(cove_node) - - return CoveAcuseContext( - tipo_operacion=tipo_operacion or "", - numero_factura_relacionada=numero_factura_relacionada or "", - numero_factura=numero_factura or "", - tipo_figura=tipo_figura or "", - fecha_expedicion=fecha_expedicion or "", - observaciones=observaciones or "", - rfc_consulta=rfc_consulta or "", - nombre_razon_social_consulta=nombre_razon_social_consulta or "", - patente_aduanal=patente_aduanal or "", - subdivision=subdivision or "", - certificado_origen=certificado_origen or "", - exportador_autorizado=exportador_autorizado or "", - edocument=edoc_final, - vucem_operation_num=(getattr(compliance, "vucem_operation_num", None) or "") if compliance else "", - emisor=emisor_view, - destinatario=destinatario_view, - items=items, - ) - - def build_context( - self, - db: Session, - invoice_id: int, - tenant_id: int, - company_id: int, - ) -> CoveAcuseContext: - """ - Construye el CoveAcuseContext reutilizando FacturaCoveDomainService. - - Flujo: - 1. Intenta leer el XML de COVE específico de la factura desde S3 - (cargado al recibir la respuesta de VU). - 2. Si no existe, intenta desde xml_files_path configurado en VU / empresa. - 3. Si no hay XML disponible, lanza ValidationException. - """ - domain = FacturaCoveDomainService(db) - ctx = domain._load_context(invoice_id, tenant_id, company_id) - invoice = ctx.invoice - compliance = invoice.compliance_mx - - # Intentar primero la clave S3 específica por factura. - invoice_specific = False - xml_bytes: Optional[bytes] = None - try: - invoice_xml_key = cove_xml_key(tenant_id, company_id, invoice_id) - xml_bytes = storage_s3.get_object_bytes(invoice_xml_key) - if xml_bytes: - invoice_specific = True - except Exception: - logger.debug("XML de COVE no encontrado en S3 por factura %s, intentando ruta configurada.", invoice_id) - - # Fallback: rutas configuradas en VU / empresa. - if not xml_bytes: - try: - xml_bytes = self._load_cove_xml_bytes(domain, ctx) - except Exception: - logger.warning("Error leyendo XML de COVE para factura %s", invoice.id, exc_info=True) - xml_bytes = None - - if not xml_bytes: - raise ValidationException( - "La factura aún no cuenta con XML de COVE consultado en Ventanilla Única.", - errors=[ - { - "field": "vu.xml_files_path", - "message": "No se encontró ningún archivo XML de COVE asociado a la configuración VU.", - } - ], - ) - - xml_ctx = self._build_context_from_xml_if_possible( - xml_bytes, invoice, compliance, invoice_specific=invoice_specific - ) - if not xml_ctx: - raise ValidationException( - "No se pudo interpretar el XML de COVE asociado a la factura.", - errors=[ - { - "field": "cove_xml", - "message": "El XML de COVE no coincide con el e-document de la factura o no tiene la estructura esperada.", - } - ], - ) - - return xml_ctx - - # ------------------------------------------------------------------ - # Renderizado PDF - # ------------------------------------------------------------------ - - # Dimensiones de las imágenes de cabecera/pie (JPEG 1024px de ancho): - # HeaderCOVE: 149px tall → ~31mm a ancho Letter (215.9mm) - # FooterCOVE: 126px tall → ~27mm a ancho Letter - _HEADER_MARGIN_MM = "34" # 31mm imagen + 3mm buffer - _FOOTER_MARGIN_MM = "29" # 27mm imagen + 2mm buffer - - @staticmethod - def _make_page_frame_html(img_data_uri: str) -> str: - """Genera el HTML mínimo para usar como header/footer de wkhtmltopdf. - - Usa resets explícitos en html/body y overflow:hidden para evitar que - wkhtmltopdf añada scrollbars al viewport del header/footer, lo que - reduciría el ancho efectivo y cortaría la imagen. - """ - return ( - "" - "" - "" - f"" - "" - ) - - def render_pdf_bytes(self, context: CoveAcuseContext) -> bytes: - header_uri = self._image_data_uri("HeaderCOVE.png") - footer_uri = self._image_data_uri("FooterCOVE.png") - - html = self._tpl.render( - header_img=header_uri, - footer_img=footer_uri, - tipo_operacion=context.tipo_operacion, - numero_factura_relacionada=context.numero_factura_relacionada, - numero_factura=context.numero_factura, - tipo_figura=context.tipo_figura, - fecha_expedicion=context.fecha_expedicion, - observaciones=context.observaciones, - rfc_consulta=context.rfc_consulta, - nombre_razon_social_consulta=context.nombre_razon_social_consulta, - patente_aduanal=context.patente_aduanal, - subdivision=context.subdivision, - certificado_origen=context.certificado_origen, - exportador_autorizado=context.exportador_autorizado, - edocument=context.edocument, - vucem_operation_num=context.vucem_operation_num, - emisor=context.emisor, - destinatario=context.destinatario, - items=context.items, - ) - - # Escribir los HTML de cabecera y pie en archivos temporales. - # wkhtmltopdf los renderiza en el área de margen de CADA página. - header_tmp = tempfile.NamedTemporaryFile( - suffix=".html", delete=False, mode="w", encoding="utf-8" - ) - header_tmp.write(self._make_page_frame_html(header_uri)) - header_tmp.close() - - footer_tmp = tempfile.NamedTemporaryFile( - suffix=".html", delete=False, mode="w", encoding="utf-8" - ) - footer_tmp.write(self._make_page_frame_html(footer_uri)) - footer_tmp.close() - - try: - options = { - "page-size": "Letter", - "encoding": "UTF-8", - "margin-top": self._HEADER_MARGIN_MM, - "margin-bottom": self._FOOTER_MARGIN_MM, - "margin-left": "0", - "margin-right": "0", - "header-html": header_tmp.name, - "footer-html": footer_tmp.name, - "header-spacing": "0", - "footer-spacing": "0", - "print-media-type": "", - "enable-local-file-access": "", - } - return pdfkit.from_string( - html, - False, - options=options, - configuration=self._get_wkhtmltopdf_config(), - ) - finally: - os.unlink(header_tmp.name) - os.unlink(footer_tmp.name) - - def build_pdf( - self, - db: Session, - invoice_id: int, - tenant_id: int, - company_id: int, - ) -> bytes: - ctx = self.build_context(db, invoice_id, tenant_id, company_id) - return self.render_pdf_bytes(ctx) diff --git a/backend/api/v1/modules/a76/factura_cove/routes.py b/backend/api/v1/modules/a76/factura_cove/routes.py deleted file mode 100644 index e9f16786..00000000 --- a/backend/api/v1/modules/a76/factura_cove/routes.py +++ /dev/null @@ -1,247 +0,0 @@ -from __future__ import annotations - -import io -import logging -from typing import Any, Dict - -from fastapi import APIRouter, Depends, HTTPException, Query, status -from fastapi.responses import StreamingResponse -from sqlalchemy.orm import Session - -from core import storage_s3 -from core.celery_app import celery_app -from core.config import settings -from core.database import get_core_db -from core.exceptions import ValidationException -from core.s3_keys import cove_acuse_pdf_key -from core.security import get_current_user, get_tenant_from_token, resolve_tenant_id_required, validate_access_to_resource - -from api.v1.modules.core.tasks_tracking import track_and_dispatch - -from .schemas import ( - CoveEligibilityResponse, - FacturaCoveResponse, - GenerateCoveFromInvoiceRequest, -) -from .service import FacturaCoveDomainService -from .tasks import factura_cove_generate - -logger = logging.getLogger(__name__) - -router = APIRouter() - - -@router.post( - "/invoices/{invoice_id}/cove", - response_model=FacturaCoveResponse, - summary="Generar COVE a partir de una factura (asíncrono)", -) -def trigger_cove_for_invoice( - invoice_id: int, - body: GenerateCoveFromInvoiceRequest, - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """ - Dispara la tarea Celery `factura_cove_generate` para una factura específica. - - - Valida acceso a la compañía. - - Registra la tarea en el tracker de tareas. - - Retorna el `task_id` para hacer polling de estado desde el frontend. - """ - tenant_id = validate_access_to_resource(db, body.company_id, current_user) - - # Validación mínima de existencia/propiedad de la factura (el dominio hará validaciones más profundas). - from api.v1.modules.a76.invoices.models import InvoiceHeader - - invoice: InvoiceHeader | None = db.get(InvoiceHeader, invoice_id) - if not invoice: - raise HTTPException(status_code=404, detail=f"Factura {invoice_id} no encontrada.") - if invoice.company_id != body.company_id or invoice.tenant_id != tenant_id: - raise HTTPException( - status_code=400, - detail="La factura no pertenece a la compañía o tenant actuales.", - ) - - task = track_and_dispatch( - db=db, - task=factura_cove_generate, - tenant_id=tenant_id, - company_id=body.company_id, - requested_by_user=( - current_user.get("email") - or current_user.get("preferred_username") - or current_user.get("username") - or "system" - ), - task_name="factura_cove_generate", - task_group="factura_cove", - task_origin="a76/factura_cove/invoices/cove", - args=[invoice_id, int(tenant_id), body.company_id, body.recipient_email], - ) - - return FacturaCoveResponse( - task_id=task.id, - status="queued", - message="Tarea de validación/generación de COVE encolada.", - ) - - -@router.get( - "/invoices/cove/{task_id}/status", - summary="Estado de tarea de COVE para factura", -) -def get_cove_status(task_id: str) -> Dict[str, Any]: - """ - Consulta el estado de una tarea Celery de generación de COVE. - - Retorna: - - state: 'PROCESSING' | 'SUCCESS' | 'FAILURE' - - info: { current: int, status: str } (cuando state == 'PROCESSING') - - result: dict (cuando state == 'SUCCESS' o 'FAILURE') - """ - task_result = celery_app.AsyncResult(task_id) - - if task_result.state in ("PENDING", "STARTED"): - return { - "state": "PROCESSING", - "info": {"current": 0, "status": "Iniciando generación de COVE..."}, - } - - if task_result.state == "PROGRESS": - return { - "state": "PROCESSING", - "info": task_result.info or {"current": 0, "status": "Procesando COVE..."}, - } - - if task_result.state == "SUCCESS": - return { - "state": "SUCCESS", - "result": task_result.result, - } - - error_info = task_result.result - if isinstance(error_info, Exception): - error_msg = str(error_info) - else: - error_msg = str(error_info) if error_info else "Error desconocido" - - return { - "state": "FAILURE", - "result": error_msg, - } - - -@router.get( - "/invoices/{invoice_id}/cove/eligibility", - response_model=CoveEligibilityResponse, - summary="Verifica si una factura puede generar COVE", -) -def check_cove_eligibility( - invoice_id: int, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """ - Evalúa si la factura tiene todos los datos necesarios (VU, factura, partidas) - para poder generar un COVE. No dispara la tarea Celery. - """ - tenant_id = resolve_tenant_id_required(current_user, db=db, company_id=company_id) - - service = FacturaCoveDomainService(db) - eligibility = service.check_eligibility(invoice_id=invoice_id, tenant_id=tenant_id, company_id=company_id) - return eligibility - - -@router.get( - "/invoices/{invoice_id}/cove/acuse", - summary="Generar o descargar el Acuse de COVE en PDF", - responses={ - 200: {"content": {"application/pdf": {}}, "description": "PDF de Acuse de COVE"}, - 422: {"description": "La factura aún no tiene XML de COVE asociado"}, - 404: {"description": "Factura no encontrada"}, - }, -) -def get_cove_acuse_pdf( - invoice_id: int, - company_id: int = Query(..., description="Company ID"), - force_refresh: bool = Query(False, description="Forzar regeneración del PDF ignorando la caché S3"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """ - Genera el PDF de Acuse de COVE para una factura. - - - Construye el contexto del reporte a partir del XML de COVE almacenado - en la configuración VU (agente / empresa). - - Renderiza el PDF con Jinja2 + pdfkit/wkhtmltopdf (mismo patrón que DODA). - - Cachea el PDF en S3 con clave estable; en la siguiente llamada lo reutiliza. - - Usa ``force_refresh=true`` para saltar la caché y regenerar el PDF. - - Devuelve StreamingResponse con Content-Type application/pdf. - - Si aún no existe un XML de COVE asociado (consulta a VU no realizada), - el servicio devolverá un error 422. - """ - from api.v1.modules.a76.invoices.models import InvoiceHeader - from .report_service import CoveAcuseReportService - - tenant_id = int(validate_access_to_resource(db, company_id, current_user)) - - invoice: InvoiceHeader | None = db.get(InvoiceHeader, invoice_id) - if not invoice: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail=f"Factura {invoice_id} no encontrada.", - ) - if invoice.company_id != company_id or invoice.tenant_id != tenant_id: - raise HTTPException( - status_code=status.HTTP_403_FORBIDDEN, - detail="La factura no pertenece a la compañía o tenant actuales.", - ) - - pdf_key = cove_acuse_pdf_key(tenant_id, company_id, invoice_id) - filename = f"acuse_cove_{invoice.invoice_number or invoice_id}.pdf" - - # ── Caché S3: reutilizar si el PDF ya existe y no se fuerza regeneración ─ - if not force_refresh and settings.use_s3_object_storage and storage_s3.object_exists(pdf_key): - try: - cached_pdf = storage_s3.get_object_bytes(pdf_key) - if cached_pdf: - return StreamingResponse( - io.BytesIO(cached_pdf), - media_type="application/pdf", - headers={"Content-Disposition": f'inline; filename="{filename}"'}, - ) - except Exception: - logger.warning("No se pudo leer el PDF cacheado de S3 para factura %s; regenerando.", invoice_id) - - # ── Generar PDF ───────────────────────────────────────────── - try: - svc = CoveAcuseReportService() - pdf_bytes = svc.build_pdf(db, invoice_id, tenant_id, company_id) - except ValidationException as exc: - raise HTTPException( - status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, - detail=exc.message, - ) - except Exception as exc: - logger.exception("Error generando PDF de Acuse de COVE para factura %s", invoice_id) - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail=f"Error al generar el Acuse de COVE: {exc}", - ) - - # ── Guardar en S3 ─────────────────────────────────────────── - if settings.use_s3_object_storage: - try: - storage_s3.put_object_bytes(pdf_key, pdf_bytes, content_type="application/pdf") - except Exception: - logger.warning("No se pudo guardar el PDF de Acuse de COVE en S3 para factura %s.", invoice_id) - - return StreamingResponse( - io.BytesIO(pdf_bytes), - media_type="application/pdf", - headers={"Content-Disposition": f'inline; filename="{filename}"'}, - ) - diff --git a/backend/api/v1/modules/a76/factura_cove/schemas.py b/backend/api/v1/modules/a76/factura_cove/schemas.py deleted file mode 100644 index 3abb34c3..00000000 --- a/backend/api/v1/modules/a76/factura_cove/schemas.py +++ /dev/null @@ -1,136 +0,0 @@ -from __future__ import annotations - -from datetime import datetime -from decimal import Decimal -from typing import List, Optional, Dict, Any - -from pydantic import BaseModel, EmailStr, Field - - -class ConfiguracionVU(BaseModel): - """ - Configuración de Ventanilla Única / VUCEM para generación de COVE. - - Nota: estos campos se pueden poblar desde CustomsBrokerVU (web_service_user, - web_service_access_key, fiel_access_key, query_tax_id, etc.) o desde el - propio request de la API pública, según el flujo que se implemente. - """ - - rfc_usuario_vu: str = Field(..., max_length=30) - # Clave encriptada/token del webservice; puede ser larga (base64) - clave_webservice: str = Field(..., max_length=512) - archivo_cer_base64: str - archivo_key_base64: str - clave_fiel: str = Field(..., max_length=100) - - -class PersonaCove(BaseModel): - tipo_identificador: str = Field(..., max_length=10) - identificacion: str = Field(..., max_length=30) - apellido_paterno: str = Field(default="", max_length=80) - apellido_materno: str = Field(default="", max_length=80) - nombre: str = Field(default="", max_length=80) - calle: str = Field(default="", max_length=120) - numero_exterior: str = Field(default="", max_length=20) - numero_interior: str = Field(default="", max_length=20) - colonia: str = Field(default="", max_length=120) - localidad: str = Field(default="", max_length=120) - municipio: str = Field(default="", max_length=120) - entidad_federativa: str = Field(default="", max_length=120) - pais: str = Field(..., max_length=3, description="País en formato ISO o catálogo VU") - codigo_postal: str = Field(default="", max_length=15) - - -class DescripcionEspecifica(BaseModel): - marca: str = Field(default="", max_length=80) - modelo: str = Field(default="", max_length=80) - submodelo: str = Field(default="", max_length=80) - numero_serie: str = Field(default="", max_length=80) - - -class MercanciaCove(BaseModel): - descripcion_generica: str = Field(..., max_length=500) - clave_unidad_medida: str = Field(..., max_length=10) - tipo_moneda: str = Field(..., max_length=5) - cantidad: Decimal = Field(..., gt=0) - valor_unitario: Decimal = Field(..., ge=0) - valor_total: Decimal = Field(..., ge=0) - valor_dolares: Decimal = Field(default=Decimal("0"), ge=0) - descripcion_especifica: List[DescripcionEspecifica] = Field(default_factory=list) - - -class FacturaCoveRequest(BaseModel): - """ - Payload completo para generación de COVE. - - Este modelo replica el contrato del servicio externo de COVE que se - mostró en la documentación compartida por el usuario. - """ - - configuracion_vu: ConfiguracionVU - rfc_consulta: str = Field(..., max_length=30) - tipo_figura: str = Field(..., max_length=10) - numero_factura: str = Field(..., max_length=50) - tipo_operacion: str = Field(..., max_length=10) - patente_aduanal: str = Field(..., max_length=10) - fecha_expedicion: datetime - observaciones: Optional[str] = Field(None, max_length=500) - correo_electronico: Optional[EmailStr] = None - tiene_subdivision: bool = False - certificado_origen: bool = False - numero_exportador_autorizado: Optional[str] = Field(None, max_length=50) - emisor: PersonaCove - destinatario: PersonaCove - mercancias: List[MercanciaCove] = Field(default_factory=list, min_length=1) - - -class FacturaCoveResponse(BaseModel): - """ - Respuesta base del endpoint público de generación de COVE. - Para el flujo asíncrono interno, solo usamos task_id/status/message. - """ - - task_id: Optional[str] = None - status: str - message: Optional[str] = None - - -class GenerateCoveFromInvoiceRequest(BaseModel): - """ - Request minimalista desde la vista de facturas. - - Solo necesita el company_id porque el invoice_id viene en la URL y el - tenant_id se resuelve desde el token. - """ - - company_id: int - force_regen: Optional[bool] = False - recipient_email: Optional[EmailStr] = None - - -class GenerateCoveResult(BaseModel): - """ - Resultado estándar que produce la tarea Celery factura_cove_generate. - """ - - status: str - message: Optional[str] = None - invoice_id: Optional[int] = None - cove_number: Optional[str] = None - vucem_operation_num: Optional[str] = None - # ID de tarea devuelto por el servicio externo de COVE (si aplica) - external_task_id: Optional[str] = None - # Respuesta cruda devuelta por el servicio externo (POST generar-factura-cove) - external_response: Optional[Dict[str, Any]] = None - errors: Optional[list[dict]] = None - - -class CoveEligibilityIssue(BaseModel): - field: str - message: str - - -class CoveEligibilityResponse(BaseModel): - can_generate: bool - reasons: list[CoveEligibilityIssue] = Field(default_factory=list) - diff --git a/backend/api/v1/modules/a76/factura_cove/service.py b/backend/api/v1/modules/a76/factura_cove/service.py deleted file mode 100644 index 5c9a472e..00000000 --- a/backend/api/v1/modules/a76/factura_cove/service.py +++ /dev/null @@ -1,899 +0,0 @@ -from __future__ import annotations - -import base64 -from dataclasses import dataclass -from decimal import Decimal -from typing import List, Tuple - -from cryptography.hazmat.primitives import padding -from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes -from sqlalchemy.orm import Session - -from core.config import settings -from core.database import CoreSessionLocal -from core.exceptions import ValidationException, ErrorCollector -from core.storage_s3 import get_object_bytes, object_exists - -from api.v1.modules.a76.invoices.models import InvoiceHeader -from api.v1.modules.a76.customs_brokers import models as cb_models -from api.v1.modules.a76.clients_and_providers.models import ClientProvider -from api.v1.modules.a76.general_catalogs.company.models import Company -from api.v1.modules.a76.items.models import LineItem - -from .schemas import ( - ConfiguracionVU, - CoveEligibilityIssue, - CoveEligibilityResponse, - DescripcionEspecifica, - FacturaCoveRequest, - MercanciaCove, - PersonaCove, -) - -@dataclass -class InvoiceContext: - invoice: InvoiceHeader - broker: cb_models.CustomsBroker | None - vu: cb_models.CustomsBrokerVU | None - - -class FacturaCoveDomainService: - """ - Servicio de dominio para validar y construir el payload de COVE a partir de una factura. - - NOTA IMPORTANTE: - ---------------- - Este servicio prepara la estructura de datos y realiza validaciones de negocio, - pero **no** realiza todavía la llamada HTTP al webservice de COVE. Eso se puede - implementar posteriormente en un servicio dedicado (p. ej. CoveExternalService). - """ - - def __init__(self, db: Session): - self.db = db - - def _load_context(self, invoice_id: int, tenant_id: int, company_id: int) -> InvoiceContext: - invoice: InvoiceHeader | None = self.db.get(InvoiceHeader, invoice_id) - if not invoice: - raise ValidationException( - "Factura no encontrada", - errors=[{"field": "invoice_id", "message": f"Factura {invoice_id} no encontrada"}], - ) - - if invoice.company_id != company_id or invoice.tenant_id != tenant_id: - raise ValidationException( - "Factura no pertenece a la compañía/tenant actual", - errors=[ - { - "field": "invoice_id", - "message": "La factura no pertenece a la compañía o tenant actuales", - } - ], - ) - - compliance = invoice.compliance_mx - broker = None - vu = None - - if compliance and compliance.customs_broker_id: - broker = self.db.get(cb_models.CustomsBroker, compliance.customs_broker_id) - if broker: - vu = broker.vu - - return InvoiceContext(invoice=invoice, broker=broker, vu=vu) - - def _get_company(self, ctx: InvoiceContext) -> Company | None: - company_id = getattr(ctx.invoice, "company_id", None) - if not company_id: - return None - return self.db.get(Company, company_id) - - def _get_company_fiel_certificate(self, company: Company | None): - if not company: - return None - - for certificate in company.digital_certificates or []: - if (certificate.certificate_type or "").strip().lower() == "fiel": - return certificate - - return None - - def _encrypt_fiel(self, raw_fiel: str) -> str: - """ - Cifra la clave FIEL con el mismo esquema del sistema legado PHP: - AES-256-CBC + PKCS7 + base64. - """ - normalized_fiel = (raw_fiel or "").strip() - if not normalized_fiel: - return "" - - encryption_key = (settings.COVE_FIEL_HASH_KEY or "").encode("utf-8") - encryption_iv = (settings.COVE_FIEL_HASH_IV or "").encode("utf-8") - if not encryption_key or not encryption_iv: - return "" - - key_bytes = encryption_key[:32].ljust(32, b"\0") - iv_bytes = encryption_iv[:16].ljust(16, b"\0") - - padder = padding.PKCS7(algorithms.AES.block_size).padder() - padded_data = padder.update(normalized_fiel.encode("utf-8")) + padder.finalize() - - cipher = Cipher(algorithms.AES(key_bytes), modes.CBC(iv_bytes)) - encryptor = cipher.encryptor() - encrypted = encryptor.update(padded_data) + encryptor.finalize() - return base64.b64encode(encrypted).decode("ascii") - - def _build_configuracion_vu(self, ctx: InvoiceContext, errors: ErrorCollector) -> ConfiguracionVU | None: - """ - Construye la sección configuracion_vu usando CustomsBrokerVU + S3. - - Lee los archivos .cer y .key desde almacenamiento de objetos, los - convierte a base64 y construye un ConfiguracionVU listo para enviar - al API externo de COVE. - """ - vu = ctx.vu - company = self._get_company(ctx) - company_vu = company.ventanilla_unica if company else None - company_fiel_certificate = self._get_company_fiel_certificate(company) - - if not vu and not company_vu and not company_fiel_certificate: - errors.add_error( - field="vu", - message="La factura no tiene configuración VU asociada ni configuración VU/certificado FIEL en la empresa", - solution=[ - "Configura los datos VU del agente aduanal o de la empresa y sube certificado (.cer) y llave (.key) antes de generar COVE." - ], - code="MISSING_VU_CONFIGURATION", - ) - return None - - # Determinar clave FIEL efectiva desde la configuración persistida. - # Se envía cifrada con el mismo esquema AES-256-CBC del sistema legado. - clave_fiel_value = "" - - if vu and getattr(vu, "fiel_access_key", None): - clave_fiel_value = self._encrypt_fiel(vu.fiel_access_key or "") - elif company_fiel_certificate: - # Fallback en base de datos: certificado FIEL de la empresa - company_fiel_secret = ( - getattr(company_fiel_certificate, "access_key", None) - or getattr(company_fiel_certificate, "password", None) - or "" - ) - clave_fiel_value = self._encrypt_fiel(str(company_fiel_secret)) - - # La clave de webservice se almacena en texto plano en la DB y debe cifrarse - # con el mismo esquema AES-256-CBC que la clave FIEL antes de enviarla a VUCEM. - vu_ws_key_raw = (getattr(vu, "web_service_access_key", None) or "").strip() if vu else "" - vu_ws_key_encrypted = self._encrypt_fiel(vu_ws_key_raw) if vu_ws_key_raw else "" - vu_access_key_raw = (getattr(vu, "access_key", None) or "").strip() if vu else "" - vu_access_key_encrypted = self._encrypt_fiel(vu_access_key_raw) if vu_access_key_raw else "" - clave_webservice = ( - vu_ws_key_encrypted - or vu_access_key_encrypted - or (getattr(company_vu, "webservice_password", None) or "").strip() - ) - - if not clave_webservice: - errors.add_error( - field="vu.clave_webservice", - message="La clave de web service no está configurada en VU ni en la empresa.", - solution=[ - "Captura la clave de web service en la pestaña VU o DODA del agente, " - "o completa la configuración VU de la empresa." - ], - code="MISSING_VU_WS_KEY", - ) - - if not clave_fiel_value: - errors.add_error( - field="vu.clave_fiel", - message="La clave FIEL para COVE no está configurada ni en VU ni en la empresa.", - solution=[ - "Captura la clave FIEL en la configuración VU del agente aduanal o en el certificado FIEL de la empresa." - ], - code="MISSING_FIEL_PASSWORD", - ) - - certificate_path = ( - (getattr(vu, "certificate_path", None) or "").strip() if vu else "" - ) or ( - (getattr(company_fiel_certificate, "cer_file_path", None) or "").strip() - if company_fiel_certificate - else "" - ) - key_path = ( - (getattr(vu, "key_path", None) or "").strip() if vu else "" - ) or ( - (getattr(company_fiel_certificate, "key_file_path", None) or "").strip() - if company_fiel_certificate - else "" - ) - - if not (certificate_path and key_path): - errors.add_error( - field="vu", - message="No hay rutas de certificado o llave en VU", - solution=[ - "Sube el certificado (.cer) y la llave (.key) en la configuración VU del agente aduanal o en los certificados digitales de la empresa." - ], - code="MISSING_VU_CERT_KEY", - ) - return None - - # Convertir archivos .cer y .key de S3 a base64 - cer_b64 = None - key_b64 = None - - try: - if not object_exists(certificate_path): - errors.add_error( - field="vu.certificate_path", - message="El certificado VU no existe en el almacenamiento de objetos", - solution=["Vuelve a subir el certificado en la configuración VU del agente aduanal o en los certificados digitales de la empresa."], - code="VU_CERT_NOT_FOUND", - ) - else: - cer_bytes = get_object_bytes(certificate_path) - cer_b64 = base64.b64encode(cer_bytes).decode("ascii") - - if not object_exists(key_path): - errors.add_error( - field="vu.key_path", - message="La llave VU no existe en el almacenamiento de objetos", - solution=["Vuelve a subir la llave en la configuración VU del agente aduanal o en los certificados digitales de la empresa."], - code="VU_KEY_NOT_FOUND", - ) - else: - key_bytes = get_object_bytes(key_path) - key_b64 = base64.b64encode(key_bytes).decode("ascii") - except Exception as exc: # pragma: no cover - errores de IO externos - errors.add_error( - field="vu", - message="Error leyendo certificados VU desde almacenamiento de objetos.", - solution=["Verifica la configuración de MinIO/S3 y las rutas de certificados/llaves en VU o en los certificados digitales de la empresa."], - code="VU_STORAGE_ERROR", - ) - - if errors.has_errors(): - return None - - # rfc_usuario_vu = RFC del importador/empresa registrada en VUCEM (PMI, la empresa - # cuyo FIEL se usa para autenticar). En el sistema legado viene de query_tax_id. - rfc_usuario_vu = ( - (getattr(vu, "query_tax_id", None) or "").strip() if vu else "" - ) or ( - (getattr(company_vu, "query_rfc", None) or "").strip() if company_vu else "" - ) - - return ConfiguracionVU( - rfc_usuario_vu=rfc_usuario_vu, - clave_webservice=clave_webservice, - archivo_cer_base64=cer_b64 or "", - archivo_key_base64=key_b64 or "", - clave_fiel=clave_fiel_value, - ) - - def _normalize_country_to_m3(self, raw_country: str) -> str: - """ - Normaliza el código de país al m3_key del catálogo (GPaises / public.countries). - - VUCEM requiere el código m3_key de 3 caracteres. La DB puede almacenar - códigos de 2 letras (mex_key / ame_key) o el m3_key directamente. - La búsqueda se hace en orden: - 1. Coincidencia exacta en m3_key (3 chars). - 2. Coincidencia en mex_key o ame_key (2 chars). - 3. Fallback: el valor tal cual, truncado a 3 chars. - """ - if not raw_country: - return "" - code = raw_country.strip().upper() - try: - from api.v1.modules.public.reference_data.countries.models import Country - if len(code) == 3: - country = self.db.query(Country).filter(Country.m3_key == code).first() - if country: - return country.m3_key - if len(code) == 2: - country = ( - self.db.query(Country) - .filter((Country.mex_key == code) | (Country.ame_key == code)) - .first() - ) - if country: - return country.m3_key - # For any length that didn't match, try m3_key with a 3-char prefix - country = self.db.query(Country).filter(Country.m3_key == code[:3]).first() - if country: - return country.m3_key - except Exception: - pass - return code[:3] - - def _clientprovider_to_persona(self, cp: ClientProvider) -> PersonaCove: - """ - Construye una PersonaCove a partir de un ClientProvider + su dirección. - No expone IDs internos; solo valores normalizados. - """ - addr = cp.address - - tipo_nat = (cp.type_nat_foreign or "").strip().upper() - tipo_identificador = "0" if tipo_nat == "E" else "1" - # Identificador fiscal efectivo: RFC (nacional) o TAX-ID (extranjero) - identificacion = (cp.fiscal_id or "").strip().upper() - - raw_country = (addr.country or "") if addr and getattr(addr, "country", None) else "" - country_code = self._normalize_country_to_m3(raw_country) - - return PersonaCove( - tipo_identificador=tipo_identificador, - identificacion=identificacion, - apellido_paterno="", - apellido_materno="", - nombre=(cp.name or cp.short_name or "").strip(), - calle=(addr.streets or "").strip() if addr and addr.streets else "", - numero_exterior=(addr.exterior_number or "").strip() - if addr and addr.exterior_number - else "", - numero_interior=(addr.interior_number or "").strip() - if addr - else "", - colonia=(addr.neighborhood or "").strip() - if addr and addr.neighborhood - else "", - localidad=(addr.city or "").strip() if addr and addr.city else "", - municipio=(addr.municipality or "").strip() - if addr - else "", - entidad_federativa=(addr.state or "").strip() - if addr and addr.state - else "", - pais=country_code, - codigo_postal=(addr.postal_code or "").strip() - if addr and addr.postal_code - else "", - ) - - def _company_to_persona(self, company: Company) -> PersonaCove: - """ - Construye una PersonaCove a partir de Company + su dirección principal. - """ - # Tomar dirección 'main' si existe; si no, la primera. - addr = None - for a in company.addresses or []: - if getattr(a, "address_type", None) == "main": - addr = a - break - if addr is None and company.addresses: - addr = company.addresses[0] - - raw_country = (addr.country or "") if addr and getattr(addr, "country", None) else "" - country_code = self._normalize_country_to_m3(raw_country) - - return PersonaCove( - tipo_identificador="1", # Empresa mexicana por defecto - identificacion=(company.rfc or "").strip().upper(), - apellido_paterno="", - apellido_materno="", - nombre=(company.name or "").strip(), - calle=(addr.street or "").strip() if addr and addr.street else "", - numero_exterior=(addr.exterior_number or "").strip() - if addr and addr.exterior_number - else "", - numero_interior=(addr.interior_number or "").strip() - if addr - else "", - colonia=(addr.neighborhood or "").strip() - if addr and addr.neighborhood - else "", - localidad=(addr.city or "").strip() if addr and addr.city else "", - municipio=(addr.municipality or "").strip() - if addr - else "", - entidad_federativa=(addr.state or "").strip() - if addr and addr.state - else "", - pais=country_code, - codigo_postal=(addr.postal_code or "").strip() - if addr and addr.postal_code - else "", - ) - - def _build_personas( - self, ctx: InvoiceContext, errors: ErrorCollector - ) -> Tuple[PersonaCove | None, PersonaCove | None]: - """ - Construye emisor (exportador) y destinatario (importador) a partir de: - - InvoiceComplianceMx.provider_id / sold_to_id / shipped_to_id - - Catálogo de clientes/proveedores - - Company (datos de la propia empresa) como último recurso - """ - compliance = ctx.invoice.compliance_mx - tenant_id = getattr(ctx.invoice, "tenant_id", None) - company_id = getattr(ctx.invoice, "company_id", None) - - emisor_persona: PersonaCove | None = None - destinatario_persona: PersonaCove | None = None - - # --- Emisor: proveedor/exportador --- - if compliance and compliance.provider_id: - provider = ( - self.db.query(ClientProvider) - .filter( - ClientProvider.id == compliance.provider_id, - ClientProvider.tenant_id == tenant_id, - ClientProvider.company_id == company_id, - ) - .first() - ) - if provider: - emisor_persona = self._clientprovider_to_persona(provider) - if not emisor_persona.calle: - errors.add_error( - field="emisor.calle", - message="El proveedor/exportador no tiene calle configurada (requerida por VUCEM)", - solution=[ - "Completa la dirección del proveedor/exportador con la calle en el catálogo de clientes y proveedores." - ], - code="EMISOR_CALLE_MISSING", - ) - if not emisor_persona.pais: - errors.add_error( - field="emisor.pais", - message="El proveedor/exportador no tiene país configurado (requerido por VUCEM)", - solution=[ - "Configura el código de país en la dirección del proveedor/exportador usando el catálogo de países." - ], - code="EMISOR_PAIS_MISSING", - ) - else: - errors.add_error( - field="emisor", - message="No se encontró el proveedor/exportador asociado a la factura", - solution=[ - "Verifica que el proveedor/exportador exista en el catálogo y que el invoice_compliance_mx.provider_id sea válido." - ], - code="EMISOR_PROVIDER_NOT_FOUND", - ) - else: - errors.add_error( - field="emisor", - message="La factura no tiene proveedor/exportador configurado en cumplimiento (provider_id)", - solution=[ - "Configura el proveedor/exportador (provider_id) en los datos de cumplimiento de la factura." - ], - code="EMISOR_PROVIDER_MISSING", - ) - - # --- Destinatario: importador mexicano --- - dest_client: ClientProvider | None = None - if compliance and compliance.sold_to_id: - dest_client = ( - self.db.query(ClientProvider) - .filter( - ClientProvider.id == compliance.sold_to_id, - ClientProvider.tenant_id == tenant_id, - ClientProvider.company_id == company_id, - ) - .first() - ) - elif compliance and compliance.shipped_to_id: - dest_client = ( - self.db.query(ClientProvider) - .filter( - ClientProvider.id == compliance.shipped_to_id, - ClientProvider.tenant_id == tenant_id, - ClientProvider.company_id == company_id, - ) - .first() - ) - - if dest_client: - destinatario_persona = self._clientprovider_to_persona(dest_client) - else: - # Fallback: usar la empresa de A76 como importador/destinatario - if company_id is not None: - company = ( - self.db.query(Company) - .filter(Company.id == company_id, Company.tenant_id == tenant_id) - .first() - ) - else: - company = None - - if company: - destinatario_persona = self._company_to_persona(company) - else: - errors.add_error( - field="destinatario", - message="No se pudo determinar el destinatario (cliente/importador) para COVE", - solution=[ - "Configura sold_to_id o shipped_to_id en los datos de cumplimiento de la factura, " - "o asegura que la compañía tenga datos de dirección configurados." - ], - code="DESTINATARIO_NOT_FOUND", - ) - - if destinatario_persona and not destinatario_persona.calle: - errors.add_error( - field="destinatario.calle", - message="El destinatario/importador no tiene calle configurada (requerida por VUCEM)", - solution=[ - "Completa la dirección del destinatario con la calle en el catálogo de clientes/proveedores o en los datos de la empresa." - ], - code="DESTINATARIO_CALLE_MISSING", - ) - if destinatario_persona and not destinatario_persona.pais: - errors.add_error( - field="destinatario.pais", - message="El destinatario/importador no tiene país configurado (requerido por VUCEM)", - solution=[ - "Configura el código de país en la dirección del destinatario usando el catálogo de países." - ], - code="DESTINATARIO_PAIS_MISSING", - ) - - return emisor_persona, destinatario_persona - - def _build_mercancias(self, ctx: InvoiceContext, errors: ErrorCollector) -> list[MercanciaCove]: - """ - Construye la lista de mercancías COVE a partir de las partidas (LineItem) - asociadas a la factura. - """ - # Obtener todas las partidas de la factura - lines: list[LineItem] = ( - self.db.query(LineItem) - .filter(LineItem.invoice_id == ctx.invoice.id) - .all() - ) - - if not lines: - errors.add_error( - field="mercancias", - message="La factura no tiene partidas (LineItem) asociadas", - solution=[ - "Verifica que la factura tenga partidas capturadas antes de generar COVE." - ], - code="NO_LINE_ITEMS_FOR_COVE", - ) - return [] - - mercancias: list[MercanciaCove] = [] - - # Determinar moneda base: usamos la moneda de la factura tal como - # la maneja el módulo de invoices. En financials se normaliza: - # - currency: 'foreign' | 'local' | 'manual' - # - currency_type: código de catálogo (ej. 'USD', 'MXN'), upper. - fin = getattr(ctx.invoice, "financials", None) - raw_currency_type = getattr(fin, "currency_type", None) - invoice_currency = (raw_currency_type or "").strip().upper() or "USD" - - for line in lines: - qty_model = line.quantity - fin_model = line.financial - desc_model = line.description - - if not qty_model or qty_model.quantity is None or qty_model.quantity <= 0: - # Saltar partidas sin cantidad válida - continue - - # Cantidad: normalizar a EXACTAMENTE 2 decimales (ej. 12.23) - cantidad = Decimal(str(qty_model.quantity)).quantize(Decimal("0.01")) - - # Descripción genérica: priorizar descripción de parte / inglés / español - descripcion = "" - if desc_model: - descripcion = ( - desc_model.part_description - or desc_model.description_english - or desc_model.description_spanish - or "" - ).strip() - if not descripcion: - descripcion = (line.line_concept or "").strip() - if not descripcion: - descripcion = "SIN DESCRIPCION" - - # Clave unidad de medida: usar OMA/customs si están disponibles - clave_unidad = "" - uom = line.unit_of_measure_info - if uom: - if uom.oma_unit and uom.oma_unit.code: - clave_unidad = uom.oma_unit.code - elif uom.customs_unit and uom.customs_unit.code: - clave_unidad = uom.customs_unit.code - elif uom.code: - clave_unidad = uom.code - - if not clave_unidad: - errors.add_error( - field="mercancias", - message="No se pudo determinar la unidad de medida para una partida de la factura", - solution=[ - "Asegúrate de que la partida tenga una unidad de medida configurada en el catálogo " - "y que esté ligada a una clave OMA/aduana válida." - ], - code="MERCANCIA_UOM_MISSING", - ) - continue - - # Moneda y valores: usamos la moneda de la factura (3 caracteres) - tipo_moneda = invoice_currency - - valor_total = Decimal("0") - valor_dolares = Decimal("0") - valor_unitario = Decimal("0") - - if fin_model: - if tipo_moneda == "USD": - base_total = ( - fin_model.value_total_usd - or fin_model.value_usd - or Decimal("0") - ) - else: - # Para otras monedas, usamos el total en MXN/MC como respaldo - base_total = ( - fin_model.value_total_mxn - or fin_model.value_mxn - or fin_model.value_total_mc - or fin_model.value_mc - or Decimal("0") - ) - - # Valor total: normalizar a EXACTAMENTE 2 decimales - valor_total = Decimal(str(base_total or 0)).quantize(Decimal("0.01")) - if cantidad > 0: - # Valor unitario también a EXACTAMENTE 2 decimales - valor_unitario = (valor_total / cantidad).quantize(Decimal("0.01")) - else: - valor_unitario = Decimal("0") - - # Valor en dólares: si ya existe, lo usamos; si no, asumimos que los totales ya están en USD. - if fin_model.value_total_usd: - valor_dolares = Decimal(str(fin_model.value_total_usd)) - elif tipo_moneda == "USD": - valor_dolares = valor_total - else: - valor_dolares = Decimal("0") - - # Normalizar valor en dólares a EXACTAMENTE 2 decimales - valor_dolares = valor_dolares.quantize(Decimal("0.01")) - else: - errors.add_error( - field="mercancias", - message="La partida de la factura no tiene información financiera asociada", - solution=[ - "Verifica que las partidas tengan datos financieros (LineFinancial) antes de generar COVE." - ], - code="MERCANCIA_FINANCIAL_MISSING", - ) - continue - - # Build descripcion_especifica from series entries (marca/modelo/submodelo/numero_serie) - desc_esp: list[DescripcionEspecifica] = [] - for serie in (line.series or []): - desc_esp.append( - DescripcionEspecifica( - marca=(serie.brand or "").strip()[:80], - modelo=(serie.model or "").strip()[:80], - submodelo=(serie.sub_model or "").strip()[:80], - numero_serie=(serie.serial_numbers or "").strip()[:80], - ) - ) - # Fallback: if no series rows, try building one entry from line description fields - if not desc_esp and line.description: - d = line.description - if d.brand or d.model: - desc_esp.append( - DescripcionEspecifica( - marca=(d.brand or "").strip()[:80], - modelo=(d.model or "").strip()[:80], - submodelo="", - numero_serie="", - ) - ) - - mercancia = MercanciaCove( - descripcion_generica=descripcion[:500], - clave_unidad_medida=clave_unidad, - tipo_moneda=tipo_moneda, - cantidad=cantidad, - valor_unitario=valor_unitario, - valor_total=valor_total, - valor_dolares=valor_dolares, - descripcion_especifica=desc_esp, - ) - mercancias.append(mercancia) - - if not mercancias and not errors.has_errors(): - errors.add_error( - field="mercancias", - message="No se generó ninguna mercancía COVE a partir de las partidas de la factura", - solution=[ - "Verifica que las partidas tengan cantidad y datos financieros válidos antes de generar COVE." - ], - code="NO_MERCANCIAS_GENERATED", - ) - - return mercancias - - def build_factura_cove_request( - self, - invoice_id: int, - tenant_id: int, - company_id: int, - recipient_email: str | None = None, - ) -> FacturaCoveRequest: - """ - Construye el FacturaCoveRequest completo a partir de una factura, - validando prerrequisitos de VU, factura y mapeos. - """ - ctx = self._load_context(invoice_id, tenant_id, company_id) - errors = ErrorCollector() - - # Validaciones básicas de factura - if not ctx.invoice.invoice_number: - errors.add_error( - field="invoice.invoice_number", - message="La factura no tiene número de factura", - solution=["Captura el número de factura antes de generar COVE."], - code="MISSING_INVOICE_NUMBER", - ) - - # Construir configuración VU (puede agregar errores) - configuracion_vu = self._build_configuracion_vu(ctx, errors) - - # Personas y mercancías (por ahora placeholders con errores explícitos) - emisor, destinatario = self._build_personas(ctx, errors) - mercancias = self._build_mercancias(ctx, errors) - - if errors.has_errors(): - # Levantamos ValidationException con todos los errores - raise ValidationException("No se puede generar COVE desde la factura", errors=errors.get_errors()) - - # Campos genéricos que se pueden poblar de forma segura - raw_tipo_operacion = (ctx.invoice.operation_type or "").strip().lower() - # Mapear tipo_operacion al código esperado por el API de COVE - # Ejemplos: - # - IMP / importación -> "TOCE.IMP" - # - EXP / exportación -> "TOCE.EXP" - if raw_tipo_operacion in {"imp", "import", "importacion", "importación"}: - tipo_operacion = "TOCE.IMP" - elif raw_tipo_operacion in {"exp", "export", "exportacion", "exportación"}: - tipo_operacion = "TOCE.EXP" - else: - # Fallback seguro: usar valor por defecto de importación - tipo_operacion = "TOCE.IMP" - numero_factura = (ctx.invoice.invoice_number or "").strip()[:50] - fecha_expedicion = ctx.invoice.invoice_date or ctx.invoice.emission_date or ctx.invoice.capture_date - - if not fecha_expedicion: - raise ValidationException( - "Falta fecha de expedición de factura", - errors=[ - { - "field": "invoice.invoice_date", - "message": "La factura no tiene fecha de expedición/emisión/captura", - "solution": ["Captura la fecha de la factura antes de generar COVE."], - } - ], - ) - - # Patente aduanal en mayúsculas y acotada a 10 caracteres - raw_patente = (ctx.broker.license if ctx.broker else "") or "" - patente_aduanal = raw_patente.strip().upper()[:10] - - # Normalizar tipo_figura desde VU: el API externo espera un código corto - # (en el ejemplo: "5" para agente aduanal). Hacemos un mapeo simple - # desde el texto configurado en la UI. - raw_figura = ( - (ctx.vu.vu_figure_type or "").strip().upper() - if ctx.vu and ctx.vu.vu_figure_type - else "" - ) - if "AGENTE" in raw_figura: - tipo_figura = "5" - elif "APODERADO" in raw_figura: - tipo_figura = "6" - elif "MANDATARIO" in raw_figura: - tipo_figura = "7" - else: - # Fallback: recortar a máximo 10 caracteres para cumplir el esquema - tipo_figura = raw_figura[:10] - - correo_destino = (recipient_email or (ctx.vu.vu_email if ctx.vu else None) or "").strip() or None - - # rfc_consulta = RFC del agente aduanal / representante que opera en VUCEM. - # VUCEM exige que sea distinto del RFC del que registra (rfc_usuario_vu / FIEL). - # En el sistema legado viene del campo "Usuario Web Service VU" (web_service_user), - # que contiene el RFC personal del agente (p. ej. MABL620809BY7). - # Fallback: tax_id del broker → identificación del destinatario. - rfc_consulta_value = ( - (ctx.vu.web_service_user or "").strip().upper() if ctx.vu and ctx.vu.web_service_user else "" - ) or ( - (ctx.broker.tax_id or "").strip().upper() if ctx.broker and ctx.broker.tax_id else "" - ) or ( - (destinatario.identificacion or "").strip().upper() if destinatario else "" - ) - - if ( - rfc_consulta_value - and configuracion_vu - and rfc_consulta_value == (configuracion_vu.rfc_usuario_vu or "").strip().upper() - ): - raise ValidationException( - "Conflicto de RFC en COVE", - errors=[ - { - "field": "rfc_consulta", - "message": ( - f"El RFC de consulta ({rfc_consulta_value}) es igual al RFC del usuario VU " - f"({configuracion_vu.rfc_usuario_vu}). VUCEM no permite que ambos sean iguales." - ), - "solution": [ - "Captura el RFC del agente aduanal o representante en el campo " - "'Usuario Web Service VU' (web_service_user) de la configuración VU. " - "Ese RFC debe ser distinto del RFC del importador que aparece en 'R.F.C de consulta' (query_tax_id)." - ], - "code": "RFC_CONSULTA_EQUALS_RFC_USUARIO_VU", - } - ], - ) - - return FacturaCoveRequest( - configuracion_vu=configuracion_vu, - rfc_consulta=rfc_consulta_value, - tipo_figura=tipo_figura, - numero_factura=numero_factura, - tipo_operacion=tipo_operacion, - patente_aduanal=patente_aduanal, - fecha_expedicion=fecha_expedicion, - observaciones=ctx.invoice.vu_observations or None, - correo_electronico=correo_destino, - tiene_subdivision=bool(ctx.invoice.logistics and ctx.invoice.logistics.is_subdivision), - certificado_origen=False, - numero_exportador_autorizado=None, - emisor=emisor, # type: ignore[arg-type] - destinatario=destinatario, # type: ignore[arg-type] - mercancias=mercancias, - ) - - def check_eligibility(self, invoice_id: int, tenant_id: int, company_id: int) -> CoveEligibilityResponse: - """ - Versión "ligera" para frontend: evalúa si la factura puede generar COVE - e informa por qué no, sin disparar la tarea Celery. - """ - errors = ErrorCollector() - - try: - ctx = self._load_context(invoice_id, tenant_id, company_id) - # Reutilizamos solo las validaciones, sin necesidad de devolver el request completo - if not ctx.invoice.invoice_number: - errors.add_error( - field="invoice.invoice_number", - message="La factura no tiene número de factura", - solution=["Captura el número de factura antes de generar COVE."], - code="MISSING_INVOICE_NUMBER", - ) - - self._build_configuracion_vu(ctx, errors) - self._build_personas(ctx, errors) - self._build_mercancias(ctx, errors) - except ValidationException as exc: - # Errores de load_context (factura no existe, compañía distinta, etc.) - return CoveEligibilityResponse( - can_generate=False, - reasons=[CoveEligibilityIssue(field=e.get("field", ""), message=e.get("message", "")) for e in exc.errors], - ) - - if not errors.has_errors(): - return CoveEligibilityResponse(can_generate=True, reasons=[]) - - return CoveEligibilityResponse( - can_generate=False, - reasons=[ - CoveEligibilityIssue(field=e.get("field", ""), message=e.get("message", "")) - for e in errors.get_errors() - ], - ) - diff --git a/backend/api/v1/modules/a76/factura_cove/tasks.py b/backend/api/v1/modules/a76/factura_cove/tasks.py deleted file mode 100644 index 7d7a65f6..00000000 --- a/backend/api/v1/modules/a76/factura_cove/tasks.py +++ /dev/null @@ -1,407 +0,0 @@ -from __future__ import annotations - -import base64 -import logging -import time -from typing import Any, Dict -from xml.etree import ElementTree as ET - -from celery import Task - -from core.celery_app import celery_app -from core.database import CoreSessionLocal -from core.exceptions import ValidationException -from core import storage_s3 -from core.s3_keys import cove_xml_key -from api.v1.modules.a76.invoices.models import InvoiceHeader, InvoiceComplianceMx - -from .service import FacturaCoveDomainService -from .schemas import GenerateCoveResult -from .external_service import CoveExternalService, CoveExternalResult - -logger = logging.getLogger(__name__) - - -def _progress(task: Task, current: int, status: str) -> None: - task.update_state(state="PROGRESS", meta={"current": current, "status": status}) - - -def _strip_ns(tag: str) -> str: - """Elimina el namespace {uri} de un tag XML y lo devuelve en minúsculas.""" - if "}" in tag: - tag = tag.split("}", 1)[1] - return tag.strip().lower() - - -def _extract_edocument_from_xml(xml_bytes: bytes) -> str | None: - """ - Extrae el número de e-document (COVE) de un XML de VUCEM. - - Busca los tags más comunes que VUCEM utiliza para el identificador - del documento COVE: , , , . - """ - try: - root = ET.fromstring(xml_bytes) - except Exception: - return None - - for node in root.iter(): - tag = _strip_ns(node.tag) - if tag in {"edocumento", "e-document", "edocument", "cove", "numerocove", "numero_cove"}: - value = (node.text or "").strip() - if value: - return value - return None - - -def _parse_consulta_respuesta(b64_str: str) -> tuple[bool, str | None, list[str]]: - """ - Parsea el XML de la respuesta de VUCEM a la consulta de estado (campo - ``consulta_respuesta_base64``). - - Devuelve una tupla (tiene_error, edocumento, mensajes_error) donde: - - tiene_error: True si VUCEM reportó true - - edocumento: el número de COVE asignado por VUCEM () si no hay error - - mensajes_error: lista de textos de error si tiene_error es True - """ - tiene_error = False - edocumento: str | None = None - mensajes_error: list[str] = [] - - if not b64_str: - return tiene_error, edocumento, mensajes_error - - try: - xml_bytes = base64.b64decode(b64_str) - root = ET.fromstring(xml_bytes) - except Exception: - logger.warning("No se pudo decodificar/parsear consulta_respuesta_base64", exc_info=True) - return tiene_error, edocumento, mensajes_error - - for node in root.iter(): - tag = _strip_ns(node.tag) - - if tag == "contieneerror": - tiene_error = (node.text or "").strip().lower() == "true" - - elif tag in {"edocument", "edocumento"}: - # VUCEM usa (sin 'o') en la respuesta de consulta - value = (node.text or "").strip() - if value: - edocumento = value - - elif tag in {"mensaje", "leyenda", "descripcionerror", "mensajeerror"}: - # VUCEM puede usar como mensaje general o / para errores - text = (node.text or "").strip() - if text and tiene_error: - mensajes_error.append(text) - - return tiene_error, edocumento, mensajes_error - - -def _save_cove_result( - db: "Session", invoice_id: int, final_external: CoveExternalResult -) -> None: - """ - Persiste en la factura el número de COVE, el número de operación VUCEM - y el XML de COVE (en S3) cuando el servicio externo reporta SUCCESS. - """ - if final_external.status != "success": - return - - invoice = db.get(InvoiceHeader, invoice_id) - if not invoice: - logger.error("No se encontró la factura %s para guardar COVE", invoice_id) - return - - compliance = invoice.compliance_mx - if not compliance: - compliance = InvoiceComplianceMx( - invoice_id=invoice.id, - tenant_id=invoice.tenant_id, - company_id=invoice.company_id, - ) - db.add(compliance) - - # Guardar XML de COVE en S3 y extraer e-document si no está en la factura. - if final_external.cove_xml_base64: - try: - xml_bytes = base64.b64decode(final_external.cove_xml_base64) - s3_key = cove_xml_key(invoice.tenant_id, invoice.company_id, invoice_id) - storage_s3.put_object_bytes(s3_key, xml_bytes, content_type="application/xml") - logger.info("XML de COVE guardado en S3: %s", s3_key) - - # Extraer e-document del XML si la factura aún no lo tiene. - if not (compliance.edocument or "").strip(): - edoc_from_xml = _extract_edocument_from_xml(xml_bytes) - if edoc_from_xml: - compliance.edocument = edoc_from_xml - logger.info("e-document extraído del XML y guardado: %s", edoc_from_xml) - except Exception: - logger.exception("Error guardando XML de COVE en S3 para factura %s", invoice_id) - - # Idempotencia básica: solo sobrescribir si está vacío o coincide. - if final_external.cove_number: - current_cove = compliance.edocument or "" - new_cove = final_external.cove_number or "" - if not current_cove or current_cove == new_cove: - compliance.edocument = new_cove - - if final_external.vucem_operation_num: - current_op = compliance.vucem_operation_num or "" - new_op = final_external.vucem_operation_num or "" - if not current_op or current_op == new_op: - compliance.vucem_operation_num = new_op - - db.commit() - - -def _poll_external_status( - task: Task, external: CoveExternalService, external_task_id: str, timeout_seconds: int = 300 -) -> CoveExternalResult: - """ - Realiza polling al endpoint externo de status de COVE hasta obtener un estado final - o agotar el timeout. - """ - start = time.time() - last_payload: Dict[str, Any] = {} - - while True: - if time.time() - start > timeout_seconds: - logger.error("Timeout consultando estado de COVE para external_task_id=%s", external_task_id) - return CoveExternalResult( - status="error", - message="Timeout consultando estado de COVE en Ventanilla Única.", - cove_number=None, - vucem_operation_num=None, - raw_response={"last_status": last_payload, "external_task_id": external_task_id}, - ) - - try: - status_payload = external.get_status(external_task_id) - except Exception as exc: # pragma: no cover - errores HTTP inesperados - logger.exception("Error consultando estado externo de COVE") - return CoveExternalResult( - status="error", - message=f"Error consultando estado de COVE en Ventanilla Única: {exc}", - cove_number=None, - vucem_operation_num=None, - raw_response={"last_status": last_payload, "external_task_id": external_task_id}, - ) - - last_payload = status_payload or {} - state = str(last_payload.get("state") or "").upper() - progress = last_payload.get("progress") or {} - # En el ejemplo: progress.progress (float 0-100), progress.current_step (texto), numero_operacion - try: - percent = float(progress.get("progress", 0.0)) - except (TypeError, ValueError): - percent = 0.0 - current_step = progress.get("current_step") or "Consultando estado de COVE en Ventanilla Única..." - numero_operacion = progress.get("numero_operacion") or last_payload.get("numero_operacion") - if numero_operacion: - current_step = f"{current_step} (Operación: {numero_operacion})" - - # Actualizar progreso para que el frontend lo vea en el diálogo - _progress(task, int(percent), str(current_step)) - - # Estados intermedios: seguimos pollendo - if state in {"PENDING", "STARTED", "PROGRESS"} or not state: - time.sleep(5) - continue - - # Estado final: SUCCESS / FAILURE u otros - result_payload = last_payload.get("result") or {} - error_text = last_payload.get("error") - - if state in {"SUCCESS", "COMPLETED"}: - vucem_operation_num = result_payload.get("vucem_operation_num") or result_payload.get( - "numero_operacion" - ) - # SOAP request del COVE enviado a VUCEM (para el acuse PDF) - cove_xml_base64 = result_payload.get("envio_base64") or result_payload.get("cove_xml_base64") - - # Extraer el e-document (COVE number) del XML de respuesta de VUCEM. - # Solo se usa para capturar el número; la detección de errores sigue - # basándose en el campo tiene_errores del VU API. - consulta_b64 = result_payload.get("consulta_respuesta_base64", "") or "" - _, vucem_edocumento, _ = _parse_consulta_respuesta(consulta_b64) - - cove_number = ( - vucem_edocumento - or result_payload.get("cove_number") - or result_payload.get("cove") - ) - - tiene_errores = bool(result_payload.get("tiene_errores")) - mensajes = result_payload.get("mensajes") - first_error_message = None - if isinstance(mensajes, list) and mensajes: - first_item = mensajes[0] or {} - if isinstance(first_item, dict): - first_error_message = ( - first_item.get("descripcion") - or first_item.get("mensaje") - or first_item.get("codigo") - ) - - if tiene_errores: - message = ( - first_error_message - or result_payload.get("message") - or last_payload.get("message") - or "Factura COVE transmitida con errores." - ) - return CoveExternalResult( - status="error", - message=str(message), - cove_number=cove_number, - vucem_operation_num=vucem_operation_num, - cove_xml_base64=cove_xml_base64, - raw_response={**last_payload, "external_task_id": external_task_id}, - ) - - message = result_payload.get("message") or last_payload.get("message") or "COVE generado correctamente." - - return CoveExternalResult( - status="success", - message=message, - cove_number=cove_number, - vucem_operation_num=vucem_operation_num, - cove_xml_base64=cove_xml_base64, - raw_response={**last_payload, "external_task_id": external_task_id}, - ) - - # Cualquier otro estado lo tratamos como error - message = error_text or result_payload.get("message") or last_payload.get("message") or state - - return CoveExternalResult( - status="error", - message=str(message), - cove_number=None, - vucem_operation_num=None, - raw_response={**last_payload, "external_task_id": external_task_id}, - ) - - -@celery_app.task(bind=True, name="factura_cove_generate") -def factura_cove_generate( - self: Task, - invoice_id: int, - tenant_id: int, - company_id: int, - recipient_email: str | None = None, -) -> dict: - """ - Tarea Celery para generar COVE a partir de una factura. - - Flujo actual: - - Valida prerrequisitos de factura y configuración VU. - - Construye el payload FacturaCoveRequest. - - Invoca el servicio externo de COVE. - - Si el externo devuelve task_id, hace polling hasta estado final. - - Persiste número de COVE / operación VUCEM cuando aplica. - """ - db = CoreSessionLocal() - - try: - _progress(self, 5, "Validando factura para COVE...") - service = FacturaCoveDomainService(db) - - # Esta llamada valida todo y construye el payload; si algo falla, lanza ValidationException - request_payload = service.build_factura_cove_request( - invoice_id=invoice_id, - tenant_id=tenant_id, - company_id=company_id, - recipient_email=recipient_email, - ) - - _progress(self, 80, "Enviando solicitud al servicio COVE...") - - # Integración externa que encola la generación de COVE en Ventanilla Única - external = CoveExternalService() - external_result = external.generate_cove(request_payload) - - # Si el servicio externo devolvió un error inmediato (por ejemplo 422), - # devolvemos ese resultado tal cual sin hacer polling adicional. - if external_result.status in {"error", "validation_error"}: - result = GenerateCoveResult( - status=external_result.status, - message=external_result.message, - invoice_id=invoice_id, - cove_number=external_result.cove_number, - vucem_operation_num=external_result.vucem_operation_num, - external_task_id=( - external_result.raw_response.get("task_id") if external_result.raw_response else None - ), - external_response=external_result.raw_response, - errors=None, - ) - return result.model_dump() - - external_task_id = ( - external_result.raw_response.get("task_id") if external_result.raw_response else None - ) - - # Si la factura quedó encolada en VU y tenemos un task_id externo, hacemos polling - # al endpoint de status para acompañar el progreso completo hasta obtener COVE. - if external_task_id and external_result.status in {"external_queued", "queued", "success"}: - _progress( - self, - 85, - "Factura enviada a Ventanilla Única, consultando estado de COVE...", - ) - final_external = _poll_external_status(self, external, external_task_id) - else: - # Fallback: usamos el resultado tal cual devolvió el endpoint de generación - final_external = external_result - - # Intentar persistir COVE / número de operación en la factura cuando sea éxito. - try: - _save_cove_result(db, invoice_id, final_external) - except Exception: - # No fallamos la tarea por errores de persistencia; solo los registramos. - logger.exception("Error guardando COVE en la factura %s", invoice_id) - - _progress(self, 100, "Proceso de COVE finalizado.") - - result = GenerateCoveResult( - status=final_external.status, - message=final_external.message, - invoice_id=invoice_id, - cove_number=final_external.cove_number, - vucem_operation_num=final_external.vucem_operation_num, - external_task_id=( - final_external.raw_response.get("external_task_id") - or final_external.raw_response.get("task_id") - if final_external.raw_response - else None - ), - external_response=final_external.raw_response, - errors=None, - ) - return result.model_dump() - - except ValidationException as exc: - db.rollback() - logger.info("Validation error in factura_cove_generate: %s", exc.message) - result = GenerateCoveResult( - status="validation_error", - message=exc.message, - invoice_id=invoice_id, - errors=exc.errors, - ) - return result.model_dump() - except Exception as exc: # pragma: no cover - errores inesperados de runtime - db.rollback() - logger.exception("Unexpected error in factura_cove_generate") - result = GenerateCoveResult( - status="error", - message=str(exc), - invoice_id=invoice_id, - errors=None, - ) - return result.model_dump() - finally: - db.close() - diff --git a/backend/api/v1/modules/a76/factura_cove/templates/FooterCOVE.png b/backend/api/v1/modules/a76/factura_cove/templates/FooterCOVE.png deleted file mode 100644 index e2d614c125bb5006ba7d7318589fc1705284641c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 8903 zcmch62UJsA`tLzSk)j5PAmCL)5rgH@1q2&GkpPAyM7j_V2|WZ*s(LRfeJBCx8YGnj zDFF!ribx5)OA`@kp{Phug2Ib)Xa09)&6@Ytyjkykr|jR^-}n8#Z|}3$K4-7J`DXJS zu;;R=nJFM50stby1K4~o^2Y4K1?MZ)R;Fff*dK~!Kq%XO1ppsk0^a)K`9oK2?GEjD z_lL(%oeMV5|7ZDcM98~3@RJsSKd{Ua0I2Bv zv+mD6@pr+y{FQE-@V@2xbpTkV0DzP&0PO7o0CD@j>V&s{liMMoN>P}XpYZSiya9LM z5MT!Q0$AXL5NQIx0vdqs<_K^B*t%uQ&r>Mdgr}&OsOYwBqT)MtY!{Ohmz0zcmynR! zCA~*#7igD+#GZY7Kzn7tV6fzFS-E{Oa?&zjnV%#gTZKN`M0bjc?v#;|kdpb2W%C69 z5(6T)0b4~Nz!s3mR*=YM190Hya1s^yITHUaTST^rZr`z0Y^P9dyax~wroCnBc2SWX zTSb3biEI%z0kmEE@D7c==MZ8tV2DeAruCiV?(_SUT=9=fUdk$Kp|N*UOFs+!6@<(p z!ifJs|I>fF=nkRs1V|WrtB9zm*w(+t7Ais8M5PaFoZE}APVU|=6K-+tx67Ta@a5Fq`E zyVz}&wEcgg^M`i+RekozwV%|Y_yXt^5l1d2V9&3r;zwM`XMfiIzm!-SM1Az^BcSV~ zt8zxko_`LdoO|2yg^0s8hoca;i{iQ|TOrw|J&!VFhINTs5B;<0fBGbar=V@3ZqS(J zHsS(KP_jKTAekn)M^!d$JUTTeQl_cz&5Qrx1p8+KVvVcZ#u`R?m1gWEfD0i(Com;m zfmJVW6a-nAcPEnyZlU0G7D$sG!!6DvzqCjC7rEEh((Qwe;W?^I(*f_O2M-c_`{5)K zJ_*)1I(0>oAF*2ea_r*e6sS?#)&AxbC>epMnpC6aoFq6+%d?o@0TI2AX+$R(tAoXJerURsL4)G07r%2=> zG-flo_#BekjcLkr%NCDBIVb|r)v1u&0)^^?U@J*S!b9(rLP%s}+V%q1h@QcKtgM@k z%EvESKubaxIcs1<%ZOMG38IP-XC!J>{!1P&So|`z1yPw#~YIgaeXAV#on~z2YG7%FwFrbD4-TW^iS z+MKGF<5(C2-5mE-a3F-*zX?dpZ4@#(_9+Bi=#H#aDA}MK;3edS271p8)D?Oq6Bbab z>v331w}LSF*Ul`VJ%2@KYtwKXYWxth;+zL7>+XnX+A>Ga$RpJ)VCqc$Abk_a z+|^!m19JVh>WlI%(&u2>$;SE)gC#8rMDT%}nIm9cO>J0y**$Wfj)sBwe7gyTkTCZJ zCL7x+634$Z-1Y-)*&WXGX(d;RH=eLX+(@x0?(Pz@HZLjWlLY9X@$*z)ox!kdKXeI7 zM}1&Y3!P-8?V>Q;|!X%(h2dav%X+$S>@k+j3Q2VV22<_b~&6>qvw41u+hPh z9-|tlkf9$^NKh*Mpz(BHb9^h*;hBx^NVLhymzEJ6Tc;w)()h=5qyo<8Oift*r?_9n znm?>lTuwLb>~(~3ycV}}ptWY#c-mk~Hhgw;+E~W*d%tfzMXcfDkmK)G4IzfQ8BfZt z4taYM!5Rgi;w(}~tu1)(XLVvAQE<@MIX`qRy{4S+TdR^d5*bzXR&BUp6DXxb_E9iU z)ewkI?36Q)8;mkezk8GSDDj5VkZUq^)rgUXhq{BeB`Ph{VUYf1xUANM2sELu^41(S zg&uO|Rm`(`RTO{j$J@GMS;p8jy@G!KVi(%r-rcc_T955A^b`t-i?1>`E>vnv{^>xE zg{mz!o<@G#7j9PaD%?+(KKO0Onz3%=)fWnRpEn;~?l7Ia@^PXw=G*WJAJ!BFv$F0) zRC-0GEtOYL%xy+}^g=zc>1Vc7z}(s|l2?sbc+ynW%%CX6hCMcX+@4&%FPnN$W`*POF3x`=xdM-CJ_a!#@}^(H;T{HPPB|Z9fK769F>Y zlfMaU%b#*y)%?{?eCo50I?NbfrzN|BfBaN&{t@4j-bH6QEHH?)bkVhGl?!ruPO35Xrd?(`|E(B7{r=QGZw)hvNr+Pj2 z%ob;SKQ(!ENSX!KTV}yf9boQTg|B*aigi=L5an@2-<1KBIs)f)bP9X$%7V^5n2qWp zmzv|TAFjG*Dix_&*doc6=BQ2BT$C}t@AE2)A#7MkU{mYwA9Kj>`~;EA(h742`d3EY zjK*ve`D&>WaXqyHlwblgY0aHJA)h#fIBx&g%QN@#jCy)VR4^0Ph-akLt7G(M&bY(x z-6P%mY$at>FK1n0>lW_)X`hPM{&)!8#XpwScxrih=A;qaX1FuNaV7O8i_+2O>TB{j z1VUj1kdyR){AqBN=!TKl^>_h*G)~pJrq9R50TdLAKLTsB^3F4rE{Qh-oRC=$LU*-}$QUHDDix(f&(bdH*PDZ)tjLx}Ldq zr;~gioSrWcls(*fWj=OpmM~>9ps*EfH@-8t4|QZZEH$09f%<_b6#F_o>bo+sAUK}_ zOCUaA6Jz_o!*LVw>mK;xq1}nQqn-vkRVGjNbxNZEZyI> zMj?K=%x`^?%$?DXq?0dN@vdD1gFeB?+F#zj!Vt)F1?83x`}l##7Fbk*+uc({^k*^%8S=}Gs(1Y4 zC@XvxRzt(a)%euR!QRf`5Vg!4I6Po2s#K6V)Hz%@+)lDTlJl+GQuv&L_L2OxzF5NX-6Pd%w-V(3a3Ny~PXrwrS zalqX7IG-P0sCDGiVZT)2WcFzQf*6^Nc=VA$@|ES#4NRL`vPHENa*SHJGb@_u zOGRF%%3oWOs)uPueU5JbrVMl7@+YVPKb*I!scAQ6g){FYBtUU_&y}1u0iE^3`8+Gm zo0aH%%V9sww~tvw6&%KjOeP z2Esy;g)8YX9b{lykLDayTbY439l`hvkSPsEsac6c)v2Oj&1XNXit@ibTW~ERT)hIg zJV`Cl-1#O`8Th#Z{cl9#a_eg;&dtLNCWV|8{teNtZa4QUHI5CwwUo~h@uHR2)M19N zSBxp8;Pz)R)Azx@^_Ui?R?yvzqg8uWxeSSge!|8NpD<~?mo{JFiMU`G21=)hVHrLg zo3)S9mb}ZwqU)+C>i1ov%vVpBWbsgJKUgxHBVpp=Ah_)2+ZSo`c=q9B{_73ZjX9pj z+W`3J9B4*xoo|^?K5uw|8FJ$w!Y@?G8QzkwpcoTvg2Iu*4$G}L(vNFqMKG!6*06WV z#$QiZ8&wDUj#c}fYJ^J@Hi2`sw3Qa~OKeFzyITeMRVi7MmN3+zzb4-zjm$*;wJxGR&{3kxa6%)lFd@Qr#bBL09_u(HTSB#p01E^0qgK} z;^4Gfam}fxIP>GjNws;l#|dqTr{8HAjOiUpK4w;R6>mDas=FA2S#fk^LO~6A@ zM)<_<>ffG!uQ9vO>$n%8D1GsqmC_OPrJUjW^h?xOdcs=O1o-9(A}fCEQ&WyWSA0>! zhcmafKzzy!s<=6D6hcexQO~NqJcV}Vzbo%G*f&_PaMYv1pj3W)rI%7lh3!7=6>QFw z4P|6UE~%YNaChrQsOZ#^uj@E9Sytza()I2e4yrji9S=5gxy-Yhe&sBsP5-K)g8KA! zlpV-Mrg6y z{+114{Ji~iER+tlyLEjNXl%jd^+&1YxPM=sri>x`GHD~Dy*GIR?6NbGQ2$CQNX7kv z*NGsR6y@@zXM+}3l8p@YnPEf_vLzt(%&pQ9KDfdwq9a9Ia5Po=oFSq;WR{$(XH_nG z8eeN?cWFqX=cuB9Lek(-`*5rcFdCu3qw&x+hFyO3P3J6>G#eR1s3|Y>x1s!eLFq$2 zSF+F7^LbE&#+uRL*^aEc`C|p&Qg3J^;mw&=caki!KPOSJe&xezMK<4V`1F+^5cOjv zFC`F6Tm(qUAmjY3c!o~-$_i4y=jNs4{wi*9J!`yaTnaz=U47PVmQYKqxV%EXo2T!Z z`YO;~i`X977u*m3I!t}GfT#);#4VpR@?X($J;2(#xnpPdZ1XI)L(boUp;!ooo&NoZ`&To&~?gIMK2 zCk6bzMwFQgQJdrIJWjf+#(PZldPn%oO&}nuz{P7HWZEKXN;Ju?L}jk0oYeYT9A zOq3j}PY=DPOjwAmx*qIM*7z6wc5D`HZ+^~+kC~bYO{d>|u2V5V1sRK0*x9~~Z8RdP zHG@YhbZfe6WPR86mLbbBl$4O9r@D=;uRO0)(%Jkb8rq2nhxK82yjZ1=)(L4P$%l z?=4~nr(Kooicw)}OV7}bl7Q zENq0w%;M-3jG)h9&LM1u3W9}eNcR{dZbreehL`X9>O5jhnJ3wgM&mE+rOAIJ?$OUNO|>3*Z|mhv{g9_iMz1W@(CA=LxwoZWjz`_U!6=Ap>&JiFFv7IiJ}PVV=bZLOL{9 z;vOX3mkj;YV?45PrFRqPYA%B`P1>M}PMW~pWSUNaZhpTsIpJt@joB~DQMcg8qA)g> z-?%2wa_GhDw`hVpy)_4{dVhef_pVU0)jUTO-sTS;38~uzQi-jC{4>M$E3adZ@iRL= z?^%%i!RZ>;OsY8iL&kW-&UL?4Qj8rnP)5O_5bWt|yhl=g zXw^rlu;z+;jTwtit6!mUI&Z@E*+8lj?Q1}hG;WBFVa*N%oe(W=?`7}({Iw_{5o^7p zV9KKP;@?NC6J&=vs=qI9!;hoH1&De)?Oa($&OGfMfF!=KCXd zF}vHodOzG<(xxxe^$zEmN7Qx@ zcfud6(f6s~t2Brv4hm#zyM0xZfqo;^d*-d~w7gQ<0cG!`=~!(`%@`h;d85RPwxp{@ zrlj9}>5YwePb#E0&gkR6@Pd6V_A}Tgt*E|j3do6^wNw&JMO%esC-@X_O+3!$%tLS6n45 zJ&w(L*I4qUU3w{sQ5TqFucJa{wrrWjg>Z9@vAnV}tzPGM4J~91C^+4F?zAAltGgqw zUk)WC#4;eo`9qSEplEl#+p3ffb#4qzp>PIDb3y!eSYY+F!Lg_w>2n7RZqN%UG4o1x zCDr!@d4!0N8tWdt_xp4^l_2BCI$X=@d}l3cBC0H;y?dFhLscwOD5X10(k!EzWq?7U z(5U_!oEYeE+fp*i;?2aKWyf<*a*^j*-RR4_V3J0-UQ|eu@?ncXiSfI$HqC?Z2An~Z zXmc$6`RAnbylm!R=tOBwwBRu}n5GM?cE$ZVZvFBQRh?idz_||OX z=~Z~{{;>|vo{fT0e#BclafThTR4&{M8mx~0c%iVmDpUCP>~3w@(0s&A)$ETwuK(iq zx!y^;L60$2T2bKrm+mI5a9PNAE8T;fun@IRNe6s91z)h;q6 z1y?nuVt#tZVBt%qmu%z*R~=@@O?ahxlTmEX+=2AV5`^BF(Ycg;R=7fqGid;_B` za;pQs>Ym*$-|d#yGgJIrv*5LxnR;H^iD?aa`hz_4_{rv{*W*zI6&n3wY~6k@TA>Nb zv>LyJhJQ(Cjh7bF$^o7EfsB-o_I8`VO*elVYt0msp$r{R2c8}nPd7YhIn_^UTz5cs ze`6-IrCZc7IhA<&#I0q`cT&C#GlL22lZrmeY)^=*$+zWxv#Sr{lW#4f&`l#<{5sG3 z<50C3!lGMww?1u|&hEf8Y#>ipYPcZhKTthh$oE?&N1%1Qcjs0^eL*&K@&}Zuxng$M z=ltFKt(+ed#xx|Qn}>tEwTmN8=P4jR6+vVBtVoNv$T*&`$KCVKn%|&Na*6SQq+C@S zb=h4g@0OeywP%@z0n0z&xw@HI>0gn7J{JyX_i~)2?5KSgFUa8X!yDrAS;piSK)s<= z^ZKGr|8*P+_K*ky%Rgn@x~>)fajk6SWftBSN1=lOyAic)-^!Qpx80SBvS&qH!b`e& zhTi4i1wwi0gfiAGP){`s%0~FtdQJxjq%PpQvj$lAW;9z}Xu0Kn_kx0(l^mO2CnzR3 zgwNHqq%V45pw%TKt@gyyym_m4eOh4%L`$bTjC*o0U*(;f>+w%b_EKQSk`kC7;Zjk; z`ok%Q33(b9yV8NIy7%nL6I55dz|2-lHR{1YPt9VOjm2W{w5nzf6rb-*W=G9QeeOS* zH6CpDm9!MY=Sw%t_S34KpD*&z4E%P}1RiWOe@sx?k1F2;_SZPPzmyZBezoBhF%fMN zh|qKSwF>VM45HUXmwSJ*&sp<4;P)hbT;ix(qU~E&l2@*>uY)fNU%VTIh6m1Y_w8n; zTFedk64Ew-ebpUJrhWI^s0n47dfq0(4;WQQiQhXgEs`o<&3Hy3WE+F9Tz9Sgsw;&o zY=I9ly!69}QY7`+)G5=1>Ne=J4G-k0D}l0+toyih;85F~4&KQ2&^GvcVU*uG5ZSNf*3HqE zPr@eKO$+y&;>N(c(>U!5*w5zCu<;9KQtE~1@bt{F3hj{m-#;olbDtkcA^GQHeye#h zFy4)+yB@HbfkYIt>6GAw(7Ip!YW59=`aX6k?g_EcmsOY`6WW~1hSH0dJj$cW^seyZ z+di#)U*&j&(5fiaY}!0~Tyx_=CQ)>~=r zaIi*v$?3S1n)N&H(opRkN8iH*8as7UwqKm6kQ;8B!RctE4jz*5TuciJ;ONs_k`tE( zHT0oe@vFlFrCF*4NU;YE6B0>~+qVi;&YT%F&D;dGZ0|WcFmWhhZ@2t?+F2z<(1rY6 zx*C|;fk_p|(PY_>Ew=&X+!*cHU}!hwEcuGBCiK6UcK;(+5sMe7q-%_n=-W;WW+z=$ z{@GdD^2N{1T2Z{2tnyCn6sT|m{{Mp%RD)EJp4UvIhjrs06uTZNgV;L7!|IC_^EQE< z8DF%%I=>?|%hvr3hO0Wab&f^djXUJ@{SSEOQ)ZObnV=1^V|T*-26xoEe{cIS_-)I- z_}%_jBehb|kAJg2__rVu|4M#*?^ET~V}5~{YK$TX5JAE3pe)62yZtx)6SXMrWENQy~E9WzIE0)cddKwy5Ifv{rKi7d$RYQd1gm>|{YxFa1Gb^dR-{eQz99{c=VKbl_WzK6U2-_QCR{+9TP zlc%XMJ-tM~cmciuBtR3O`gi;3zvSu`2|*vJO}m@NPRw(kG{BkDiO=(qntw%ha~Ubf1h+XPd_hQx^UtA`3sB}FEU)Z z!g%Eh6C)E7^HtXC%vV{iGBI7}yw1YL&cVTP!wPWBtD>>TWWGdXjPPILakv;Wg@`USvp36OLiaPABcaF*rFIhHf0EdV~cy3d{Y5BSgKUc7Xf;lkPT=l(8M zx()zbIDh8s`3vXHpE-Z#+y(mIbC%u&hKnq$mjrG|nEvAh8{_|Y0G*s(z|Hc%Ka*Z`hLuJ7!RZL#%DFRi@y@XTQ~*VP02J!~ zQ>=g4RHO3DN&Zp9#ltVBfagELzGN(4jofI`_|RV5SHRqoPkwOkYZCWmJN?{4JU(G9 z=aB#H?|KCo#VkWE$FN|OVeAI8W;<=Oa84(A5%`opz zvqf-|6nF7gm5vf=c4DW1=`I`RBhL25#DfPNb#EeO(bXCr2RgR9#J)+9-k#7U;41z^ zx9VJ6_zNG9>gzPxGlkMwkWr^4I=BK$gkolNK)2GK?oKY>)4zDcEHIxpU$=2hx3){n zuYHm7oRT==z|@oOaa0rE`z$b!#oY|^nE`!+bEJsr!=0Z3&5;FqI;y!ix3C|YW2b1C-)Zx;+|EJz$K&Tx;0>6qoEFHp$o6q1BSx}99|+bEyBrFjbY(f--R&pOE~Jv7xSx5q(Ir{H7i6)me@@$hg? zGBpMzwPn{AAx>v?Zib?nCF0POu~uAUTKuI3bHOkme(o>AT)XN`)p~9h0$M$6D%95Q zuW_@PCFSXY9x)d+(@WP173V$vvP4#8Mq@RGP61ozDE~??FMmckkXMm_Kh|iDGH^xd z;3sN3vMqQhwjJ()ueOmG{F3Rtsyzva$*?|=VBuavEYZvcN{vy!VDcXhjqG>J+`O6} zmCvF5X87Jl0;4xHoTn~WL~BPq2xt_#FuCE7ytKM(UHRPonJN)wJeU&|O={(TfX`I( zPJBC1At*`I=kX^uzCYlb(5d%6QEr!G&JVEaA45T9m6$Xj<7rh5FNIjylR0~C0cYTj z&cB|iC`B+qwrbHfDUsz#<>PCnbthVh%gqC3Uf>OQeX=Zzq&QG%cjBlqFuH9~uhXA9 z<;g*)<*gA`KH_J?DE)nGJk73M`Pm;D@kP0-=eN;%h0NU4PUp3%KpNbhWCZW~5=ni@UakOWQg@el(-ktVNz zLaAQmT!h9`z_aGUW#>p~Q)I_qCaDEMMbunVVWO6B2V#oIB{);PR2~47Qa*XmjUDv* z9$Z`S^rAT8vNA|(b{6==mY1h+QURjjN_xwy4cz;dFQXA#)2U|eHz9W#^I zDwY$=YBZBnjCBK^0(6iOJ}Cs99%x8G4IfJU7%A=J0Z-_u%OH@oGE`=x6ynkrpLm+( zo&vmE3$oQZW!rQUYr*Z8!&vl0x3UMJhfBDC!{q*q5PNjxvCm?uk?Z#rc8XP}k{mvf zb;RPHwmTtHT|I;4J*#qi_6Cpo?y;ti)3DnN$~6IAS7?$J*cXXRc?EJOdLEf_fxPKm zIyq(KV5wu!H1&=2W}?{EqIzT&P6u~+8Z)SsqTSr3VDtp0+&=ce8aX#+K)cZ6(w~C$ zZu8-Ntw}0rOMmc=--@7zlhjyO%&g7k?>5QMoq9JJxv(W>;)YkUkIws+M!F(Hh^~1p z@eXQ{dJ1UM9*8=McoR4^7?xPL>2+YF>M?fiK)?+z2&RSS7Q%3 z$w-CP!aF@JBsBtvUgnnG9lH4q_9d5KB5}*=K|;6II#kN9uQ>a&y(qMnhqp>~)1XcGdw!suSU7(Pbq!;d?ho_)lnWy?leQ0V)oPXmXbrfy= z$)P_K64%%GbV)$79&*t!Yc0e43#KJY$8X^M>&nN`b(x89rwa4m;PL4LkzGB5Lzmf% zi_=9jO~H7BBdMdn^%cr76YHnP0FHm1ag6`-dtYirX_{VWCT4%coiGcd)+8Ve9O1UL zb~6B`e(NQvi)i6T)Q*(4EMPTv*=zll9~)Y=l|p4xa;1vH^OTx)rj!H9hkohAXy2%k zYb!TxjCdRy_H#45g`Xi2mtnx!(nVQOYGVM${qs}XyVO^y*Pe6S3MR|P?*aa&B)DM5 z;pksSM`slFL@3H{xyBwIz4}wxV(VVhRQrrfF@r3Miq3x-TsiL@(i%s;&o=vBEoxOF zR89MftZtj@)Bh9AIg@{r{99xHeqsLIU;aH@{Cl+g&&lEofPua%^iRP>P%@gFHCLU;KS=&Jp@ zd}44!h|<=kDHkXu|H{!>m!yd6RuMf0V+kCB$S-gUZ zeBp1ISbP0(Vd5#^1vuYEDx7cYLt>5)TdZ6TtS7fI^-^=rmG0;}Z}m){wGowndb)MD zTYGmVxcM8qUIn2wu1B9VX$*CTd88h%H@OEr6M;w?|lr~)`EnC2r--CJ2&#W}ZsfAEpgUaBzccBQJ z%PGL_(#TM)qT~JcSDuZXojId^sN3(~YqeOol+e|C z?e)p|62njas98R68nw51!sEI+FqT5dd_Ec**3}{G=qdx!%-(=O+MYqrOxPWM`(EWG zvHLJ|zfMe=#34Lc46{)=&^C+zxgImNlK||Qj|w)lT$bn0I4G)?if%WHk^A+@Cdu!9 z^(bzM)*MgdbqxD?9ov7T5wKdl4pRXd#Ggp|QU*p7DHZ4z`*?5xt^D17a{dt01X;N)laxxJ`|YY8LES5XpK} z;9p=a$@$(qkF4I{x3|(6BXDbKS_c7Ul=?v?w<;#}>FupbF1kjisof$?Y!9M)ST;Sv zXizsn1j(u@uGz_@HT6%zXL!e7JD|_zPST52g}nEWBibE~Z&6N{ELbC`ad6GCMnKW$ zhyM9!Q||#}`O0P;huI76$EJ@}-<0*G5_EujDtD4R#D_&As1ebWa_re}i#8K2Pq~=> zDy~G;uNKah7zD}kzr8sp(gBPZu3Nw-;s6GJAIN>R_r9E1Q7U)%+fW(*JM@ zXw^9BK91m7Z(8M*A2hAzA)-i#j~mRghAFSa9AY)zDt*T2$>mJFGQwZl!+A}mrmO@h zwwT^Gf7EY|s>!yjPV3j`=)J8`&?v92IV`*sg{=AYi5jOlH&?#bF#8urbkAFqq7kuh zr*^K=Bu6p{&%1|J7S}dGTaM^d7~26hhfwh}kf(><)r|%f=FTa?u}qFx8&wv-!gPEB3LW2<>}5SbuO~Qld*w2#)q1 z&@qR!1WnZLuu%BG*1LR_v~kAVQJKWaqQe2Pzr5F7)Kk4k_Vo92H(r?+0Mlp# zH};}GE}jC~r!pxo4Vpu4;Ud~|mu3B?8V6-fO7AhN#raxGnJmN#?2O;dA$f9(b_k<0 zJ0YDtyR6rc1naY>fIrQNz+c`hBoX}p)!E|aL#HZRmaEAdNkVa_0L#PYTWo2P?G^hD z4;PalGh$wgWx)(sUCl?`h`p%EUSdFx%Wj2=!0cRR_6j6p-cXF|ZZ{<~=XUm=dB}$% zvz@*>s>}$%O(~cZR0?+8YwJh5|C{nz1Cen&Ux8I^?xnBR5z=Kt7qki5&_ld#IHo;=Z{|vp zixCv$rD!8VSuNsgc)=?#wtug~hCo16SnBrW0HK1~!G}zC>FY|0_of;n$%P69>xDfh zJdU?(@~pM79(5YKQ&qX!y8}&AdBbbl*qC`k@3EWuqs1Ee0@A>7DTKi(z%!xO#L<(5 z8?Qs|q3qpZv$0;X`SSJdvw^C;g|i z%>n+laS}&in2Cx)C~1y2 zyjA?Rx<{?P`|GNPXEOtZq)u(zErW$CH=6#vR2o%%gzaO z$s_Lsn(^dCF(p+b702>KDG(6Pd-CDNe)U*SSnV3D1a;`6$6nBoQlk|EKR~+mqJXRD zsd%S~m9YTR&=PkZ8;P2=nv8<}+g7olNIkjtTvOdC>pODs1C&KULBZkq<}iW3kdLh= zxe7mZJX6$nDH)9&){sjV`Gt~I*q7_o6OP$HcrdOyY7D7ygvM^L26=rg=TOs7oi8tB zozbtgEKGFLi9Jk5qVU71AV-9LblhraD4bKWw8neHd!^m~n`Y2mr z$)Emf5GE}Nz9#0ItNb;EY%izL*ed5qN9$S{fAGt) zpjmO|@g4O`{yJrR67P-~9dh3g1w1N}S(Ci1@a}}hWCxhwFz44L0jG3laO+od-n5MU zxKQ-6?!(0JulweCT98QwRB*Rm=BJ&~PIJ%WhWjrD_2t^lSJ3@w{Xsdv$ewzW0wk#& z$a`*}<7#mtJn0rLL}Aha%A0ZQI2*j8|5yLWcsl2Jv7y&_Q?bN>xtfX?WOo3Nj})3d zRGzm=)?DaZsrp3&_7o-O`XEGUe~r7>=k`u%9TIv&;v5wlAxpI@(M#_NC9RVEzbYs5 zut&x;GdFh`I>EEECm3BBFLy1zn%$)-xz`EObs-gYbpJwKx5FC@S{`QXUZ& z(y&%lO`Qgg%Pm;618V7(bM$5?yf&RwZW{%dmtY?-^yPOV;)p2oBPTKiT@o-+b8fi( zGuonFTo7KqP<69`@2^cw0i(r4Cksk^+tN{IX#pP#^h0$pZd1*O8-G`AH=F7`re zzUk7%-}gTJ=v`!PmZ;G@ zA(+*fV&#`TyaQvxBq~tLrF~Wk%2dn7{L^2u`QPw!3(7?11vm6+gGv~x=yt;>Y&oRy z+F7MZPwNch>9m|Dvg5i%rYp`O>eV)GKOcC9XX(J=Gi`OSn=&pZxgyPWd=vMKC8pk$ zIn1K2j=p%>9fxVF6|0g*_Vx~0-h99_YP023mF$q3dXz(S z2EoV^DG)A@6*eoJeP5KV(`SFg3P}Mp~oIfns3OWp- zk{cu6tmy@g419!)F~MsJ`o)3G<|Z|3Tttu<8v0I^N}JfFVlS`c^sJ#IZ+!H9%-qgY z_({`WH3A!uROF$8z$vwV*9Ub!uj$$1-cVX@#D!4L7rla2SJL>>8Y73n#doMlEeTh1 zJ7k4n9h+)ckZ8mqZU-g+jGXQv@HBkf;*IR@J5wiKY}M;(h^=RC>N*>a6RRz5-XFZ zS_LW$QF{AHl1aCdJH%!XJs@?os|+smhlnBjDCuhbNL6{L^rz*EiN^{H1uS*-cS6C5 zX8o-@)cOzzz6LM(-bWf}qvSh{5?jT3c4!0y_p5ql$&dKU8dPgLxqyhs=s1@H6L=8F zdol#@@xyHkJ~s!Kl)@3Rmy(I+Xq^~S-dEq5=y4HbwE(<|WjUcryx!~hTKhH9p_Ghe zp#Ce4rOa=nQ5qIPQ+B=8X(HGzXA%bEyC1!yF|Ih3t*i(f27LS?8e-oRu`{UEJX&iS zP_32jD>FK>U=I7V06()v_y&1v_3w*2bo*k)u5;ootD_)R~6rYVIbf zjUTZGFetEBr+)sql_5tdd1;q>(v;$Z;|Ec63#?$;9MXjuqQl_xX!T@#DLlc2x?KT- z4*Ed;3jPFH)C+f`t@13l{E=(?&?FUVUMVC1nk;_;J7nSVg#phj1G<-S{Uz5FX00wo zX&&O=Oz!GW*ps*oJN=v<3)RJn$utb7atAKQQKRf#E7ls7+q*2Lstub;FN@|>v+CKT zn+o1zZ+UEfivWbpxb_etqL!_{SUl#)?mGGKv~80#y0v3BM(gn3Z)Hr0OcEW@Ey={r zh$dp>c7lfa$w%ZGRprkxT`MqrQG2e&lk&3J$9OS*#8kQJ&(Dm>0^ihMf6TNVg-!~t zWUDF$3^9E?TlhU_tIDO$m)9JE7U$3@oqatqzoqTfg5M+~K)Z%L1rcd;6zm$>Mm2i9suirf_69}KR7Lt7PkzF(mBhl}kBy=|7 zZUuB=C(Qm1=biIx82|J*srw1=%122~$xH6w0}V?jCEYItIF?jmGJ9}hNL+m`K4{sw zVlrL4wAh1ZE+|uXa57u^KFQ0vxUb$QEs31)n|2D2+|R6M)l(reT|dj;&`|tQ0#)&& z))Rp*fX#3974ghLwOk!TCrzAisR|+Fy5z9w@Eh;uyKe@$$@df{%#S&ysr7YZ|kU6pxLya=QIih@(w zhspNWe;F=`kz0q^4h8%u_=P%4HBY@b593ISKUB9c#_e7Ab+iRnDuK}B_Bk@2m%}^X z&_>KR&w9i6tWYPD+`ngjB}qmESOuDG)-yyQM@NNho%PS{wfm0o23O5@slH&zE$4@U zr{1I^FknPTL`1~frRzdXHPThQ&Mvlbie`Fe7t)p)gA&fu)>=JU$>hRoM95 zXe0T)a3QSEARm#8kb&K&mEK}W!c$|;m$rWvZCc>8FEhnHAnK92Ls%ZE+W816|6Gu) zd7YM+qcRi`{U!7xcMmP!f^!FxKg8l*ZAEj?o4k&LI-0iF=l&_*>gv=|(BUH3!(*DW zHWr>v&cF;k{IyzS$p7l`pt-tGhG&*q-Vs$BAzmN+*FFm&sveJkTb}|P&L{(p*m;(j zGO-M0I*DT=?gPfa`)}ef-qoDF*{6V>2zb&!Qq1Go%8xFIjf8q|eXAZ0mERW%g37#7 zdhEUGi0Rpx|mT4YkNHJS2O4;iO!P*09-J^`Dr z%nkVuwws#qy;bhPg7&xXdXi)Ha!wMMk)fDN2yuka?o6ph_Tb_7%g--ZsVs^|GY%wJ ziCK^2{z32-``D*7x|K8Ak}R_H<8|tCl(sB?bVLnYh!@wdmmHf`dwo>at%BsO0p9A= zsJ>3r!QJSiOg~1_o7%Jp-B1d=aXI2Vm(h|U53*eMN;uGc>$?yw5mWtlM#44Wyb`-D&Uwd?IGG1Z3?dj?s z3FR|XCl;1_-)ONv$n@^?m%KXXQYpHI;+{zn#a7`qj%V8eM}Co?#I}X`^fSPu#Qo@O z^+1UtJf8;!1j4A)b93K#!8NFVSC3j4^sr1`RreIo+y4akz5(jwGyVHSMRI>B|@|j9Tuv(D@gsiGg0W=Z_{>De!7!Jn> z%~KZ5sx*!W)aUxQ#bd=AJ%achzR7Bqq2Ac_otRmhy|-Fn#K*mK13J|2OwVOYxx+OgFlI-MQrEs&OB4vIlw9%2JW13ppO%w#qN!- zpgL_bL)G=Q67of_Bu1V)^GFk`CC0obEVY$0g+kFp7B_0A%m3G!<^P@N%um~ZXvKrh z(oz5OAhM~8fvUflWHQ%vj0(M%={HAj1bie$V*!OJ+e zDhRP~{g%28pr-rgTzRFM(*M@5t;@=!#mrRjFk6GRDKq=^t~|>-b$_%7jTXv&C3WJwuMf?lG}P2@ZOD_IN)1`y2Zds)sn8~d*3S5D%3(R!NN zDM6j4m*2072P~TE=WxxXOi_Ikr{7_N_loF~6doON^j6Q;?uZf{6iwc4JUf(YZ6bl? zmMqLdCh7?jn(mvgv;E4Z+p7^B>KG{~l*=7pXvu}x7Z%a0gMUwW;ILifx-M323GT8( zxJq@Ed^Hk(&vDEsIF$f9bkX8ieeZ=GSDm8}(~MoStqJx8TgfLL*>`7o;iEq=vmF;Y zgsl?QZOOHNoK5aI@NAxXXnm)TFIOg&pn<7Rq6JS28>I=ZItEuYZ^fh>?W!Q2l&qL8 zhP-`fpU$%qoa|GP^Cm*H~*$4X5TXjPH0TsL?0rd%+I()>8$i zi0V}!xD5h^V0l0KjMW9uO?h^0ZuD2>iyq&h#Pm>XtVqSzSQwq>EzD&Pv0w_JZXx#= z43p(fU9#kp(_9!S2a@_ZZ03?!2dhhm46ml6k_5zE68{vXj%!~Tc#V${LSr^y(24Nz zTWwZwk=7uYJX8Yr%uUaVjmuh=x>XpXTMmL!<=Y)WM*!#d6ZobM@!~YyD$}hwU8i znBqN{&>TIsRVxp7RxBrR(#%I9mtz^K>YK!Xvc>dCAC6a^K|AeZ_@J{LL$w8a)gu?= zqtcGjXhSTd&9r-~1vH`rKF7!#DC_3d^FF2N$GNmN6N`WexvB*YhK|(VU}+%PJyBbz z?u}s*C#8Izd>CI?x-}kJ*s>U#W)cwVvH36o@;WZv`GiO;=n{6O=aF#q8g6^U@z?aTX5?Z*l=1WfjHM4pjiPO2(ek6`? z<*1BN;yi6u_3mZc4fO}OBUPZ_@$QaUMB6(6qXA7thilFF?q-+nTOL}~3ZrFOjjey1 zkcvtA*8DgS8FOXj)>P9$PpUgjW7+A*f#B;Y(xPW7d5Kn3F|sP=kQ zW!qGh)5u5E7u17adUrwvag|~m?nOTBm7iKJR8TX_uNq_TYAPCa8D<)u2Jgq=p*D`TWnjPL zupa0)O`P_}!LXh+u&peEo4I)6Kvo+01PFpAakU!IoaSXw}cQ*1#lk+{y3VBTTG3mR;)0Z_PaWETy095N!7wUR)*?b zWhGPN7C`)s+$=b5cPbfSl9^JPt^(ScnUPB?G?K7XC!4y)wW~f=n07==h10!TuEU&B z2X0ia>PVXzZ8f8#v zhlGI5i@2Dz6Z@@Z*xYktgsU4iw({@c16g%j9f1$yLAc(rFoFIy1ze6X?&?@j6y!D zTQq9DJW?~a_ug+=-8|sxH#u0nFaPjPxbSRMg`~wng83B^24jfW9L4$zKT}rL7&@v! zO+@|i{32VVbc1)PKuvMDu1RlhZ(6>*L=?6Tqsbu}wG4mO6B4DSA~}Iwqj)hiU$cX=gF zpK64ZOjD~;!7Ds55~(>y=9BdSt0m9s&2P*G_j^7Xu69$E6`QVE<(wU~uU1w_Rnr0r z23CURdrQ1tJb}!X2`YvHV2zSnIUz=y3>xpXK2DI?qrTaF3ztk!>U1--@pNo7#U_!tiffpo5+bi80RnqSf`o%)S=lO=t*PDhtQw~P07qZ`J4L^T@^g2swi|rC zF?VH+>SgLLht#rfI#JLVL~1^@Q8eP=$N%?*&OcrA%ys`KRwR_QjH5OYm?f5E9Hs3| z-n6@)XXB*qL=?b4W!`VVLF+gC+zqKaw^|d`#+J3|IVV?0jE)`p^78?&a=N03K3{WY>{DBIn4I zo4cOeA(EShu#Q393Jfdx6!>Nh%=u|=d-2$k4H23f6`F~}>A91mlj+-5>-0}Ame3kI ztEzkneZp!sw-@lQBX8OkTA1!_7DXQ|@kDppf=KG6LAIU;4^rcHj$7-~ul@S;-nu;? zMBymg5slOi&`p$?m7RW&rT$Ztg1_{)OQwfE`uRhqQ$XSJlh}2C&f{d`AfiUeZ!u$& zwor-GPILTCcWP7iS`*xo9)qnRuL8B)_Z)3CzgeBWd9!N!M~mohWu4jFPCOhh?0B2t zsP`UtP?O#B-htO?uxEB3{pb@g%IHq1V)spxs|71!YPke7xV>xldbqg8R>2l(fZX_` z=W4#laG-C2qAi7e8vkuMITTcz6bev|%lf67YY~YC8HNAi>7pfUwW$9}(x6(WvjJhLuEwYK18snH?gk#mB zZhr=R+|wb68!L9zk+vT8yXx6L->`VEQ>rf7ivoE??%|WRGUL`N4sq^piRqPHr00@v z#LabjO;QaKA3lBrTPNx~J&Ny@L#gaI+LEJx5AZdt@E2EJSTV`VmaTnU$<4mtBv75+ z7UMI*nuGMuIRyX(PPW4%r4;{yD}F8(m3Di=7iVUw(IC-&`BK_Qhfp?%8^;>;!mU0q zvvK~S|I3EN%7d42CVEY#JRX*bRoNk&wOq4#t+P$SRUR5SJ-dP`b83B5i>6oMkBr8O z)JgrW9I4@F(-AqEBD2(|lwiXoFnHP4*N7{5RiG)kT>p%kxP_b%@pIum zxw~GR+BEsm|E_^CXLuA=a7H{St@>=LfFs3Z@im)g&;$%BjLq0JT2~PqhCi$`#BHMf_ICmgj<~`SCAtFmY+PXutgs@$4-P9r9z0cdP!!E;U z%dA2v!#n%OE1e>W&SKze(GyPnxzsKM@gOBO!qD!l1DlOIqu-t=2 z02AnfMD-FxD&A$urN_Psf)|?rk2V?fG}K~AI-t#|64r#RTijIbZd_=i0B_9XO%C)K(`UDfl~iGYb&@2gxkkm&5rAf)$Q8m?GK!&dRhb0U;iK+uALLyqh;*)N*n80q7)IM zm)jgh(#-DhiqHFL^Fb=*p>mu}`&I);@FvYc)9Ov(mGlh@_xOY`_-AjSnhbi_hql?) zxd#yOHyN1k`2(&Sqpx(IcjhAS3-mokx?lJdFt55dSiE#i*qTNOj4;2zpkc@869xIK zzwgrThjaf1-MZY^uihDEx%C^| zZtz*!#(l<(>9B|kg5~KID%A_h^8YLiQ6ABGIdvzA$52~_&>M!}JS0Rs*XQEeKGPPk zR6SG3+^tl|Y-Cbc6=9Nqx?`5iT6YRn53Vy$YocTLG5QQi$Aty`xDlj$9?dK2I zk*&S#*!*^#*P;Ngx3R3*q`8BLC6JfS{@GmN$Hbo*Ne$e-7n!R4UOBVi_o4?;n8@p2 zurH3#jzi-GflAyF0!=ACz{k!=&V}!3$M)-WeD5_J<{r`r`l@EyvcA2_cvvqrI$-lR zXjVkp@28a>_yVGGKJL$wCS;mm^i){ zmUA+*>(d)zpnGXwH)x*%l-5u{#Qc`?kZ(P##A_w?!|&rpUA3huNSe37`_4?a_}r#& zcH1|>jTlFG#ai~NfQ8b%xgztMPF670iUp1+pU=htCH%9Yz8)nrU=NaHmY5O7`FFyJ zrO34a9D_3`%7swzbJ~Qzq8n(y3(T1YVDoD zn^x%Mrf}ecT>Ko=h($oC0+|>}571F!>AR}{CChEw|EM&N)&EWG7mrNm|7qR3tnICi z?9CC$&QeEL4D6Pyk(08eoxOiuu3Le8TpCOxc~m_?9e7S6!VO`-sNVZ+Vf6S9;F$&8Sn6!)g2cc*}vUmxFda)1LL~ z-}Dqwe$NvQMl8+m%Jcw1IKi{DSu^hhE0bqbbfr-6T~)PQb(OY^XpAtNzI+T* zdObd{!&2&8%qI8BBUnVdoc`5s6!$Ei1*Dg{(xf>Q-JzMIJPI5p>;T=k_|TuQv|q{{ zgIze?YOqP+{YZym)pvcdu&uI4LMG}Uv9i~=0>Kq_hxPSpgLk#^LTJjgX}^&}_p$V0 zIr~aaYSQPMu3>~j?)m<9Z0G^!S>5^HS&GuTqi8|B^l!@5ECQmy;eu|8y7dlB`J%?| z3WIy&RsFk)c`3(EJ~!)gt-auR??6(7vB+1GEs$e!ZS#=*}LyH zIeH2ZieDFfPh;aSFLR+WT&<&C_Ej;6^3}?JoJ+=iW^)N6>^Ser;KBrB7D_2px$xXi zf=`Bie;Dpr*!++&eb$F#-Eh%N3wd**OS79w39G-3&Gs9Aw<_WNVBw$~(@Pp~5-UwL zu%Y{4(s9V2Wgaz}lqvr1m1dyQlRKQH`@@MM7Xz#{Y0U4y+ob<2PG6qMKL@CE zd!NNUVkbB{A|ehg@A_o>d}|Wi1M7bGafWcC!1c)D2D>~VZpKppr{g`>9;s8nFg1$W zt#;7z)!aD5&1(2Ur>jb}@Cb%jE;A$NY7$dh6|8JUsW2U*8xF5X86u8Dy(h( z@gB%)lZo)no-M-VO{20Y-ydO!eU`PoGY-(OhdauU#zM~J2rlMOCvdLH!=M-X`*|3> zf*S4kQpEI7^cLP|X<20~d>~Y=UBjIhD=Rj*E30J6qdEUE7C~|UgvSdC{&F6|hlMFI z_NObLnilrU1O`Z)LhoK^e(I{tR$`s8mcC>217j&vL*KE{3~TlXp`cYC5dG@?P!>`7 zi%ANTZ+XV6Fi`Hoqv8$!0&6zZ10v0i+M6V5bzJ7;{1VXY-H))PURMbFkQ#N z%{ud?7DEJ+B}F4T3|LC;KjU_f!VxI+*!I4X(e;DcW?sdC9|0e_YFbEUN0WI;%rA|d z*W=Mazij8LbZmWxV!b57QtrGp=E_rV4{*E^j`-|wXLPxl?vH)Bi4nqa7m)Y(YeyYJ zYD=y!nm&i5za<41J*L@nC=WKcw|t+b#UHvEX_=oj3Wl1i#eA!>sUYR_`p$nd~WaBL=)X>9I~Pz3{QvAgB9ZU>6SXMo|zR&#KQKBXmz<$LM7W{AZ=xbu9=?gc%74s=hk z%u^FJOE{0hZT_+bPu?jV<;3~f_qRp4H5usEIgQ6Q^Yyv!^x*_SL8@`;Rt5`elh^=0 zVfBS{soCfR3u><@t&`*D9~n1RP}aK+%Gh!Xqtqh`$td@ok+>teESF*(1%Ghc@ub|9 z)7hl1iR{XoQqv!BS*sA9U@$8w9>}!sgTOasNZ_cA>ypdtu7pcMTCfgz}r!aV7H|NvK z2i_Lx?=8t#tzj2(xAnH=k1gwX$&P*BxDS!ZE*Id68 z6^fd~iK{Kf^t$^%$hh+nTZ - - - - - Reporte COVE - - - - -
- - -
- Encabezado COVE -
- - - - - - {% if edocument %} - - {% endif %} - -
-
Datos del Acuse de Valor:
-
COMPROBANTE DE VALOR ELECTRÓNICO
-
- {{ edocument }} -
- - - - - - - - - - - - - - - - - - -
Tipo de operaciónRelación de facturasNo. de factura
{{ tipo_operacion }}{{ numero_factura_relacionada }}{{ numero_factura }}
- - - - - - - - - - - - - - -
Tipo de figuraFecha Exp.
{{ tipo_figura }}{{ fecha_expedicion }}
- - - - - - - - - - - -
Observaciones
{{ observaciones }}
- -
RFC Con permiso de consulta
- - - - - - - - - - - - - -
RFC de ConsultaNombre o Razón Social
{{ rfc_consulta }}{{ nombre_razon_social_consulta }}
- -
Número de patente aduanal
- - - - - - - - - - -
Número de autorización aduanal
{{ patente_aduanal }}
- -
Datos de la factura
- - - - - - - - - - - - - - - - -
SubdivisiónCertificado de origenNo. de exportador autorizado
{{ subdivision }}{{ certificado_origen }}{{ exportador_autorizado }}
- - -
Datos generales del proveedor
- - - - - - - - - - - - - -
Tipo de identificadorTax ID/Sin Tax/RFC/CURP
{{ emisor.tipo_identificador }}{{ emisor.identificacion }}
- - - - - - - - - - - - - - - - - -
Nombre(s) o Razón SocialApellido paternoApellido materno
{{ emisor.nombre }}{{ emisor.apellido_paterno }}{{ emisor.apellido_materno }}
- -
Domicilio del proveedor
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
CalleNo. exteriorNo. interiorCódigo postal
{{ emisor.calle }}{{ emisor.numero_exterior }}{{ emisor.numero_interior }}{{ emisor.codigo_postal }}
ColoniaLocalidad
{{ emisor.colonia }}{{ emisor.localidad }}
Entidad federativaMunicipio
{{ emisor.entidad_federativa }}{{ emisor.municipio }}
País
{{ emisor.pais }}
- - -
Datos generales del destinatario
- - - - - - - - - - - - - -
Tipo de IdentificadorTax ID/Sin Tax/RFC/CURP
{{ destinatario.tipo_identificador }}{{ destinatario.identificacion }}
- - - - - - - - - - - - - - - - - -
Nombre(s) o Razón SocialApellido paternoApellido materno
{{ destinatario.nombre }}{{ destinatario.apellido_paterno }}{{ destinatario.apellido_materno }}
- -
Domicilio del destinatario
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
CalleNo. exteriorNo. interiorCódigo postal
{{ destinatario.calle }}{{ destinatario.numero_exterior }}{{ destinatario.numero_interior }}{{ destinatario.codigo_postal }}
ColoniaLocalidad
{{ destinatario.colonia }}{{ destinatario.localidad }}
Entidad federativaMunicipio
{{ destinatario.entidad_federativa }}{{ destinatario.municipio }}
País
{{ destinatario.pais }}
- - - {% if items %} - {% for item in items %} -
Datos de la mercancía
- - - - - - - - - - - - - - - - -
Descripción genérica de la mercancíaClave UMCCantidad UMC
{{ item.descripcion_generica }}{{ item.clave_unidad_medida }}{{ item.cantidad }}
- - - - - - - - - - - - - - - - - - - - -
Tipo monedaValor unitarioValor totalValor total en dólares
{{ item.tipo_moneda }}$ {{ item.valor_unitario }}$ {{ item.valor_total }}$ {{ item.valor_dolares }}
- - {% if item.descripciones_especificas %} -
Descripción de la mercancía
- - - - - - - - - - - - - - {% for desc in item.descripciones_especificas %} - - - - - - - {% endfor %} -
MarcaModeloSerieSubmodelo
{{ desc.marca }}{{ desc.modelo }}{{ desc.numero_serie }}{{ desc.submodelo }}
- {% endif %} - - {% endfor %} - {% else %} - - - - -
Sin partidas registradas.
- {% endif %} - - -
- Pie de página COVE -
- -
- - - \ No newline at end of file diff --git a/backend/api/v1/modules/a76/general_catalogs/classification_concepts/__init__.py b/backend/api/v1/modules/a76/general_catalogs/classification_concepts/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/backend/api/v1/modules/a76/general_catalogs/classification_concepts/dto.py b/backend/api/v1/modules/a76/general_catalogs/classification_concepts/dto.py deleted file mode 100644 index 1e166e2f..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/classification_concepts/dto.py +++ /dev/null @@ -1,23 +0,0 @@ -from typing import Optional -from pydantic import BaseModel, Field, ConfigDict - - -class ClassificationConceptBase(BaseModel): - classification: str = Field(..., max_length=30, - description="Classification") - description: Optional[str] = Field(None, max_length=255, description="Description") - - -class ClassificationConceptCreate(ClassificationConceptBase): - pass - - -class ClassificationConceptUpdate(BaseModel): - classification: Optional[str] = Field(None, max_length=30) - description: Optional[str] = Field(None, max_length=255) - - -class ClassificationConceptResponse(ClassificationConceptBase): - id: int - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/a76/general_catalogs/classification_concepts/models.py b/backend/api/v1/modules/a76/general_catalogs/classification_concepts/models.py deleted file mode 100644 index 32c6c621..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/classification_concepts/models.py +++ /dev/null @@ -1,22 +0,0 @@ -from typing import Optional -from sqlalchemy import Integer, String, UniqueConstraint -from sqlalchemy.orm import Mapped, mapped_column -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base - - -class ClassificationConcept(Base, TenantScopedMixin, TimestampMixin): - __tablename__ = "classification_concepts" - __table_args__ = ( - UniqueConstraint("classification", name="uq_classification_concept"), - {"schema": "a76", "extend_existing": True}, - ) - - id: Mapped[int] = mapped_column( - Integer, primary_key=True, autoincrement=True) - - classification: Mapped[str] = mapped_column( - String(30), nullable=False) # CLASIFICACION - - description: Mapped[Optional[str]] = mapped_column( - String(255), nullable=True) # DESCRIPCION diff --git a/backend/api/v1/modules/a76/general_catalogs/classification_concepts/routes.py b/backend/api/v1/modules/a76/general_catalogs/classification_concepts/routes.py deleted file mode 100644 index 2af35c49..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/classification_concepts/routes.py +++ /dev/null @@ -1,20 +0,0 @@ -from api.v1.common.tenant_crud_routes import TenantCRUDRoutes -from .dto import ClassificationConceptCreate, ClassificationConceptResponse, ClassificationConceptUpdate -from .service import ClassificationConceptService - -router = TenantCRUDRoutes( - service=ClassificationConceptService, - create_schema=ClassificationConceptCreate, - update_schema=ClassificationConceptUpdate, - response_schema=ClassificationConceptResponse, - prefix="/classification-concepts", - tags=["a76.general_catalogs.classification_concepts"], - resource_name="Classification Concept", - enable_list=True, - list_permissions=["cat_classification.view"], - get_permissions=["cat_classification.view"], - create_permissions=["cat_classification.create"], - update_permissions=["cat_classification.edit"], - delete_permissions=["cat_classification.delete"], - enable_filters=True, -).router diff --git a/backend/api/v1/modules/a76/general_catalogs/classification_concepts/service.py b/backend/api/v1/modules/a76/general_catalogs/classification_concepts/service.py deleted file mode 100644 index ea19216f..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/classification_concepts/service.py +++ /dev/null @@ -1,100 +0,0 @@ -from typing import List, Optional, Tuple, Dict, Any -from sqlalchemy.orm import Session -from sqlalchemy import select -from sqlalchemy.exc import IntegrityError -from fastapi import HTTPException -import logging - -from .models import ClassificationConcept -from .dto import ClassificationConceptCreate, ClassificationConceptUpdate - -logger = logging.getLogger(__name__) - - -class ClassificationConceptService: - @staticmethod - def get_all( - db: Session, - tenant_id: int, - company_id: int, - skip: int = 0, - limit: int = 100, - filters: Optional[Dict[str, Any]] = None, - ) -> Tuple[List[ClassificationConcept], int]: - query = db.query(ClassificationConcept).filter( - ClassificationConcept.tenant_id == tenant_id, - ClassificationConcept.company_id == company_id - ) - - if filters: - if filters.get("classification"): - query = query.filter(ClassificationConcept.classification.ilike(f"%{filters['classification']}%")) - if filters.get("description"): - query = query.filter(ClassificationConcept.description.ilike(f"%{filters['description']}%")) - - total = query.count() - items = query.offset(skip).limit(limit).all() - - return items, total - - @staticmethod - def get_by_id( - db: Session, id: int, tenant_id: int, company_id: int - ) -> Optional[ClassificationConcept]: - return db.query(ClassificationConcept).filter( - ClassificationConcept.id == id, - ClassificationConcept.tenant_id == tenant_id, - ClassificationConcept.company_id == company_id - ).first() - - @staticmethod - def create( - db: Session, data: ClassificationConceptCreate, tenant_id: int, company_id: int - ) -> ClassificationConcept: - db_obj = ClassificationConcept( - **data.model_dump(), - tenant_id=tenant_id, - company_id=company_id - ) - db.add(db_obj) - db.commit() - db.refresh(db_obj) - return db_obj - - @staticmethod - def update( - db: Session, id: int, tenant_id: int, data: ClassificationConceptUpdate, company_id: int - ) -> Optional[ClassificationConcept]: - db_obj = ClassificationConceptService.get_by_id( - db, id, tenant_id, company_id) - if not db_obj: - return None - - update_dict = data.model_dump(exclude_unset=True) - for key, value in update_dict.items(): - setattr(db_obj, key, value) - - db.commit() - db.refresh(db_obj) - return db_obj - - @staticmethod - def delete( - db: Session, id: int, tenant_id: int, company_id: int - ) -> bool: - db_obj = ClassificationConceptService.get_by_id( - db, id, tenant_id, company_id) - if not db_obj: - return False - - try: - db.delete(db_obj) - db.commit() - return True - except IntegrityError as e: - db.rollback() - logger.error(f"Error de integridad al eliminar clasificación de concepto {id}: {str(e)}") - raise HTTPException( - status_code=400, - detail="No se puede eliminar esta clasificación porque tiene registros relacionados (pedimentos, facturas, etc.). Primero debe eliminar o reasignar esos registros." - ) diff --git a/backend/api/v1/modules/a76/general_catalogs/company/__init__.py b/backend/api/v1/modules/a76/general_catalogs/company/__init__.py deleted file mode 100644 index 8078325f..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/company/__init__.py +++ /dev/null @@ -1,26 +0,0 @@ -""" -Módulo de Company -""" -from .models import Company -from .routes import router -from .submodels import ( - CompanyAddress, - CompanyCertification, - CompanyDigitalCertificate, - CompanyVU, - CompanyElectronicAgent, - CompanyPrevalidator, - CompanyCFDI, -) - -__all__ = [ - "Company", - "router", - "CompanyAddress", - "CompanyCertification", - "CompanyDigitalCertificate", - "CompanyVU", - "CompanyElectronicAgent", - "CompanyPrevalidator", - "CompanyCFDI", -] \ No newline at end of file diff --git a/backend/api/v1/modules/a76/general_catalogs/company/dto.py b/backend/api/v1/modules/a76/general_catalogs/company/dto.py deleted file mode 100644 index 16485abf..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/company/dto.py +++ /dev/null @@ -1,489 +0,0 @@ -""" -DTOs (Data Transfer Objects) para módulo de empresa -Reemplaza schemas.py siguiendo enfoque DDD y estilo NestJS -""" - -from datetime import date, datetime -from typing import Optional - -from pydantic import BaseModel, ConfigDict, Field, field_validator - - -class CompanyCreateDTO(BaseModel): - """DTO para crear una empresa""" - - # Solo requerido cuando hub_admin crea una empresa (no tiene tenant_id en token) - tenant_id: Optional[int] = Field(None, description="Tenant ID (requerido para hub_admin)") - - name: Optional[str] = Field(None, max_length=255, description="Company name") - rfc: Optional[str] = Field(None, max_length=30, description="Company RFC") - main_activity: Optional[str] = Field( - None, max_length=255, description="Main activity" - ) - - # Program information - program: Optional[str] = Field(None, max_length=10, description="Program") - program_number: Optional[str] = Field( - None, max_length=40, description="Program number" - ) - prosec: Optional[bool] = Field(False, description="Empresa con programa PROSEC") - prosec_authorization: Optional[str] = Field( - None, max_length=20, description="Clave del sector PROSEC" - ) - - # Identifiers - manufacturer_id: Optional[str] = Field( - None, max_length=25, description="Manufacturer ID" - ) - broker_company: Optional[str] = Field( - None, max_length=10, description="Broker company" - ) - - # Responsible person - responsible: Optional[str] = Field( - None, max_length=80, description="Responsible person" - ) - responsible_name: Optional[str] = Field( - None, max_length=20, description="Responsible first name" - ) - responsible_last_name: Optional[str] = Field( - None, max_length=20, description="Responsible last name" - ) - responsible_mother_last_name: Optional[str] = Field( - None, max_length=20, description="Responsible mother's last name" - ) - responsible_rfc: Optional[str] = Field( - None, max_length=30, description="Responsible RFC" - ) - position: Optional[str] = Field( - None, max_length=30, description="Responsible position" - ) - - # Configuration - logo: Optional[str] = Field(None, max_length=512, description="Company logo (path local o clave S3)") - order_format_type: Optional[str] = Field( - None, max_length=19, description="Order format type" - ) - previous_code: Optional[int] = Field(None, description="Previous code") - is_service_company: Optional[bool] = Field(None, description="Is service company") - fiscal_deposit: Optional[bool] = Field(None, description="Fiscal deposit") - generate_barcodes_with_fiel: Optional[bool] = Field(None, description="Generate barcodes with FIEL") - - # Client and subassembly - client_name: Optional[str] = Field(None, max_length=300, description="Client name") - subassembly_mode: Optional[str] = Field( - None, max_length=7, description="Subassembly mode" - ) - - # Additional information - curp: Optional[str] = Field(None, max_length=19, description="CURP") - inter_db_name: Optional[str] = Field( - None, max_length=100, description="Inter DB name" - ) - ctpat_svi: Optional[str] = Field(None, max_length=100, description="CTPAT SVI") - trusted_exporter_number: Optional[str] = Field( - None, max_length=50, description="Trusted exporter number" - ) - prevalidator_key: Optional[str] = Field( - None, max_length=20, description="Prevalidator key" - ) - seventh_amendment: Optional[bool] = Field(None, description="Seventh amendment") - - # Sectors - sector1: Optional[str] = Field(None, max_length=150) - sector2: Optional[str] = Field(None, max_length=150) - sector3: Optional[str] = Field(None, max_length=5) - - # Certification (CompanyCertification flattened) - is_certified_company: Optional[bool] = None - certified_company_registration: Optional[str] = Field(None, max_length=40) - certified_company_start_date: Optional[date] = None - certified_company_end_date: Optional[date] = None - annex30_certification_date: Optional[date] = None - annex30_certification_number: Optional[str] = Field(None, max_length=50) - annex30_modality: Optional[str] = Field(None, max_length=3) - annex30_company_type: Optional[str] = Field(None, max_length=50) - annex30_renewal_date: Optional[date] = None - annex30_final_certification_date: Optional[date] = None - is_oea_company: Optional[bool] = None - neec_company: Optional[bool] = None - is_seciit_company: Optional[bool] = None - - # Addresses (Flattened) - # Main - main_street: Optional[str] = Field(None, max_length=100) - main_exterior_number: Optional[str] = Field(None, max_length=10) - main_interior_number: Optional[str] = Field(None, max_length=10) - main_postal_code: Optional[str] = Field(None, max_length=5) - main_neighborhood: Optional[str] = Field(None, max_length=40) - main_city: Optional[str] = Field(None, max_length=40) - main_municipality: Optional[str] = Field(None, max_length=50) - main_state: Optional[str] = Field(None, max_length=30) - main_country: Optional[str] = Field(None, max_length=4) - main_phone: Optional[str] = Field(None, max_length=20) - main_fax: Optional[str] = Field(None, max_length=20) - main_email: Optional[str] = Field(None, max_length=100) - # Industrial 1 - ind1_street: Optional[str] = Field(None, max_length=100) - ind1_exterior_number: Optional[str] = Field(None, max_length=10) - ind1_interior_number: Optional[str] = Field(None, max_length=10) - ind1_postal_code: Optional[str] = Field(None, max_length=5) - ind1_neighborhood: Optional[str] = Field(None, max_length=40) - ind1_city: Optional[str] = Field(None, max_length=40) - ind1_municipality: Optional[str] = Field(None, max_length=50) - ind1_state: Optional[str] = Field(None, max_length=30) - ind1_country: Optional[str] = Field(None, max_length=4) - ind1_phone: Optional[str] = Field(None, max_length=20) - ind1_fax: Optional[str] = Field(None, max_length=20) - ind1_email: Optional[str] = Field(None, max_length=100) - # Industrial 2 - ind2_street: Optional[str] = Field(None, max_length=100) - ind2_exterior_number: Optional[str] = Field(None, max_length=10) - ind2_interior_number: Optional[str] = Field(None, max_length=10) - ind2_postal_code: Optional[str] = Field(None, max_length=5) - ind2_neighborhood: Optional[str] = Field(None, max_length=40) - ind2_city: Optional[str] = Field(None, max_length=40) - ind2_municipality: Optional[str] = Field(None, max_length=50) - ind2_state: Optional[str] = Field(None, max_length=30) - ind2_country: Optional[str] = Field(None, max_length=4) - ind2_phone: Optional[str] = Field(None, max_length=20) - ind2_fax: Optional[str] = Field(None, max_length=20) - ind2_email: Optional[str] = Field(None, max_length=100) - - # Technical flags - active_labels: Optional[int] = None - active_fractions: Optional[int] = None - activate_caat: Optional[int] = None - trans_interface: Optional[int] = None - american_costs: Optional[int] = None - scaf_readonly: Optional[int] = None - parts_replacement: Optional[int] = None - activate_facmexame: Optional[int] = None - part_reference: Optional[int] = None - international_firm: Optional[int] = None - - # Advanced Config - ftp_key: Optional[str] = Field(None, max_length=10) - sifra_path: Optional[str] = Field(None, max_length=255) - version_type: Optional[str] = Field(None, max_length=20) - sql_language: Optional[str] = Field(None, max_length=19) - balance_operation_mode: Optional[str] = Field(None, max_length=50) - - # Prevalidator (detailed) - prev_customs: Optional[str] = Field(None, max_length=20) - prev_key: Optional[str] = Field(None, max_length=20) - prev_patent: Optional[str] = Field(None, max_length=4) - prev_description: Optional[str] = Field(None, max_length=100) - - # Ventanilla Única (VU) - vu_webservice_user: Optional[str] = Field(None, max_length=100) - vu_webservice_password: Optional[str] = Field(None, max_length=100) - vu_email: Optional[str] = Field(None, max_length=800) - vu_figure_type: Optional[str] = Field(None, max_length=29) - vu_central_path: Optional[str] = Field(None, max_length=1499) - vu_xml_files_path: Optional[str] = Field(None, max_length=1499) - vu_query_rfc: Optional[str] = Field(None, max_length=30) - vu_validation_rfc: Optional[str] = Field(None, max_length=30) - vu_configuration_source: Optional[str] = Field(None, max_length=30) - vu_measurement_units: Optional[str] = Field(None, max_length=3) - - # Electronic Agent - ea_input_folder: Optional[str] = Field(None, max_length=1000) - ea_output_folder: Optional[str] = Field(None, max_length=1000) - ea_send_mask: Optional[str] = Field(None, max_length=20) - ea_response_mask: Optional[str] = Field(None, max_length=20) - ea_response_extension: Optional[str] = Field(None, max_length=20) - ea_counter_start: Optional[int] = None - ea_counter_end: Optional[int] = None - ea_counter_next: Optional[int] = None - - # CFDI - cfdi_xml_save_path: Optional[str] = Field(None, max_length=5000) - cfdi_app_path: Optional[str] = Field(None, max_length=5000) - cfdi_pac_app_path: Optional[str] = Field(None, max_length=5000) - - # Digital Certificates (CompanyDigitalCertificate flattened) - # FIEL - fiel_cer: Optional[str] = Field(None, max_length=5000) - fiel_key: Optional[str] = Field(None, max_length=5000) - fiel_pass: Optional[str] = Field(None, max_length=200) - fiel_access: Optional[str] = Field(None, max_length=50) - fiel_cer_exp: Optional[int] = None - fiel_key_exp: Optional[int] = None - # CFDI (Sello) - cfdi_cert_cer: Optional[str] = Field(None, max_length=5000) - cfdi_cert_key: Optional[str] = Field(None, max_length=5000) - cfdi_cert_pass: Optional[str] = Field(None, max_length=200) - cfdi_cert_access: Optional[str] = Field(None, max_length=50) - cfdi_cert_cer_exp: Optional[int] = None - cfdi_cert_key_exp: Optional[int] = None - # Cancellation - cancel_cer: Optional[str] = Field(None, max_length=5000) - cancel_key: Optional[str] = Field(None, max_length=5000) - cancel_pass: Optional[str] = Field(None, max_length=200) - cancel_access: Optional[str] = Field(None, max_length=50) - cancel_cer_exp: Optional[int] = None - cancel_key_exp: Optional[int] = None - - - model_config = ConfigDict(from_attributes=True) - - @field_validator( - "certified_company_start_date", - "certified_company_end_date", - "annex30_certification_date", - "annex30_renewal_date", - "annex30_final_certification_date", - mode="before", - ) - @classmethod - def validate_dates(cls, value): - """Acepta exclusivamente DD/MM/YYYY para payload string.""" - if value in (None, ""): - return None - if isinstance(value, date): - return value - if isinstance(value, str): - try: - return datetime.strptime(value, "%d/%m/%Y").date() - except ValueError as exc: - raise ValueError("Date must be in DD/MM/YYYY format") from exc - raise ValueError("Date must be in DD/MM/YYYY format") - - @field_validator("prosec", mode="before") - @classmethod - def coerce_prosec_to_bool(cls, value): - """Coacciona prosec a booleano. Tolera valores heredados cuando la columna - era entero (ID de sector): null/0/vacío → False, cualquier otro → True. - Misma regla que el downgrade de la migración previa.""" - if value in (None, ""): - return False - if isinstance(value, bool): - return value - if isinstance(value, (int, float)): - return value != 0 - if isinstance(value, str): - return value.strip().lower() not in {"0", "false", "no", "off", ""} - return bool(value) - - @field_validator("annex30_modality", mode="before") - @classmethod - def validate_annex30_modality(cls, value): - if value in (None, ""): - return None - normalized = str(value).strip().upper() - if normalized not in {"A", "AA", "AAA"}: - raise ValueError("Annex 30 modality must be one of: A, AA, AAA") - return normalized - - -class CompanyUpdateDTO(CompanyCreateDTO): - """DTO para actualizar una empresa""" - pass - - -class CompanyResponseDTO(BaseModel): - """DTO para respuesta de empresa""" - - id: int - tenant_id: int - name: Optional[str] = None - rfc: Optional[str] = None - main_activity: Optional[str] = None - - # Program information - program: Optional[str] = None - program_number: Optional[str] = None - prosec: bool = False - prosec_authorization: Optional[str] = None - - # Identifiers - manufacturer_id: Optional[str] = None - broker_company: Optional[str] = None - - # Responsible person - responsible: Optional[str] = None - responsible_name: Optional[str] = None - responsible_last_name: Optional[str] = None - responsible_mother_last_name: Optional[str] = None - responsible_rfc: Optional[str] = None - position: Optional[str] = None - - # Configuration - logo: Optional[str] = None - fiscal_deposit: Optional[bool] = None - generate_barcodes_with_fiel: Optional[bool] = None - order_format_type: Optional[str] = None - previous_code: Optional[int] = None - is_service_company: Optional[bool] = None - - # Client and subassembly - client_name: Optional[str] = None - subassembly_mode: Optional[str] = None - - # Additional information - curp: Optional[str] = None - inter_db_name: Optional[str] = None - ctpat_svi: Optional[str] = None - trusted_exporter_number: Optional[str] = None - prevalidator_key: Optional[str] = None - seventh_amendment: Optional[bool] = None - - # Timestamps - created_at: datetime - updated_at: Optional[datetime] = None - - @field_validator("prosec", mode="before") - @classmethod - def coerce_prosec_to_bool(cls, value): - """Tolera datos heredados cuando la columna prosec era entero (ID de sector): - null/0/vacío → False, cualquier otro → True.""" - if value in (None, ""): - return False - if isinstance(value, bool): - return value - if isinstance(value, (int, float)): - return value != 0 - if isinstance(value, str): - return value.strip().lower() not in {"0", "false", "no", "off", ""} - return bool(value) - - # --- Flattened Fields for Response --- - # Sectores - sector1: Optional[str] = None - sector2: Optional[str] = None - sector3: Optional[str] = None - - # Certification - is_certified_company: Optional[bool] = None - certified_company_registration: Optional[str] = None - certified_company_start_date: Optional[date] = None - certified_company_end_date: Optional[date] = None - annex30_certification_date: Optional[date] = None - annex30_certification_number: Optional[str] = None - annex30_modality: Optional[str] = None - annex30_company_type: Optional[str] = None - annex30_renewal_date: Optional[date] = None - annex30_final_certification_date: Optional[date] = None - is_seciit_company: Optional[bool] = None - is_oea_company: Optional[bool] = None - neec_company: Optional[bool] = None - - # Addresses - # ... (Main, Ind1, Ind2 can be added here if needed for flattened response) - main_street: Optional[str] = None - main_exterior_number: Optional[str] = None - main_interior_number: Optional[str] = None - main_postal_code: Optional[str] = None - main_neighborhood: Optional[str] = None - main_city: Optional[str] = None - main_municipality: Optional[str] = None - main_state: Optional[str] = None - main_country: Optional[str] = None - main_phone: Optional[str] = None - main_fax: Optional[str] = None - main_email: Optional[str] = None - - ind1_street: Optional[str] = None - ind1_exterior_number: Optional[str] = None - ind1_interior_number: Optional[str] = None - ind1_postal_code: Optional[str] = None - ind1_neighborhood: Optional[str] = None - ind1_city: Optional[str] = None - ind1_municipality: Optional[str] = None - ind1_state: Optional[str] = None - ind1_country: Optional[str] = None - ind1_phone: Optional[str] = None - ind1_fax: Optional[str] = None - ind1_email: Optional[str] = None - - ind2_street: Optional[str] = None - ind2_exterior_number: Optional[str] = None - ind2_interior_number: Optional[str] = None - ind2_postal_code: Optional[str] = None - ind2_neighborhood: Optional[str] = None - ind2_city: Optional[str] = None - ind2_municipality: Optional[str] = None - ind2_state: Optional[str] = None - ind2_country: Optional[str] = None - ind2_phone: Optional[str] = None - ind2_fax: Optional[str] = None - ind2_email: Optional[str] = None - - # Technical flags - active_labels: Optional[int] = None - active_fractions: Optional[int] = None - activate_caat: Optional[int] = None - trans_interface: Optional[int] = None - american_costs: Optional[int] = None - scaf_readonly: Optional[int] = None - parts_replacement: Optional[int] = None - activate_facmexame: Optional[int] = None - part_reference: Optional[int] = None - international_firm: Optional[int] = None - - # Advanced Config - ftp_key: Optional[str] = None - sifra_path: Optional[str] = None - version_type: Optional[str] = None - sql_language: Optional[str] = None - balance_operation_mode: Optional[str] = None - - # Prevalidator - prev_customs: Optional[str] = None - prev_key: Optional[str] = None - prev_patent: Optional[str] = None - prev_description: Optional[str] = None - - # VU - vu_webservice_user: Optional[str] = None - vu_webservice_password: Optional[str] = None - vu_email: Optional[str] = None - vu_figure_type: Optional[str] = None - vu_central_path: Optional[str] = None - vu_xml_files_path: Optional[str] = None - vu_query_rfc: Optional[str] = None - vu_validation_rfc: Optional[str] = None - vu_configuration_source: Optional[str] = None - vu_measurement_units: Optional[str] = None - - # Electronic Agent - ea_input_folder: Optional[str] = None - ea_output_folder: Optional[str] = None - ea_send_mask: Optional[str] = None - ea_response_mask: Optional[str] = None - ea_response_extension: Optional[str] = None - ea_counter_start: Optional[int] = None - ea_counter_end: Optional[int] = None - ea_counter_next: Optional[int] = None - - # CFDI - cfdi_xml_save_path: Optional[str] = None - cfdi_app_path: Optional[str] = None - cfdi_pac_app_path: Optional[str] = None - - # Digital Certificates (Flattened) - fiel_cer: Optional[str] = None - fiel_key: Optional[str] = None - fiel_pass: Optional[str] = None - fiel_access: Optional[str] = None - fiel_cer_exp: Optional[int] = None - fiel_key_exp: Optional[int] = None - - cfdi_cert_cer: Optional[str] = None - cfdi_cert_key: Optional[str] = None - cfdi_cert_pass: Optional[str] = None - cfdi_cert_access: Optional[str] = None - cfdi_cert_cer_exp: Optional[int] = None - cfdi_cert_key_exp: Optional[int] = None - - cancel_cer: Optional[str] = None - cancel_key: Optional[str] = None - cancel_pass: Optional[str] = None - cancel_access: Optional[str] = None - cancel_cer_exp: Optional[int] = None - cancel_key_exp: Optional[int] = None - - - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/a76/general_catalogs/company/models.py b/backend/api/v1/modules/a76/general_catalogs/company/models.py deleted file mode 100644 index 7edb419b..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/company/models.py +++ /dev/null @@ -1,153 +0,0 @@ -""" -Modelo principal de Company -""" - -from typing import Optional, TYPE_CHECKING -from sqlalchemy import Integer, String, SmallInteger, ForeignKey, Boolean -from sqlalchemy.orm import Mapped, mapped_column, relationship -from core.database import Base -from api.v1.common.base_models import TimestampMixin - -if TYPE_CHECKING: - from .submodels.address import CompanyAddress - from .submodels.certification import CompanyCertification - from .submodels.digital_certificate import CompanyDigitalCertificate - from .submodels.vu import CompanyVU - from .submodels.electronic_agent import CompanyElectronicAgent - from .submodels.prevalidator import CompanyPrevalidator - from .submodels.cfdi import CompanyCFDI - - -class Company(Base, TimestampMixin): - """Información principal de la empresa""" - - __tablename__ = "company" - __table_args__ = {"schema": "a76", "extend_existing": True} - - # Primary key - id: Mapped[int] = mapped_column(Integer, primary_key=True) - tenant_id: Mapped[int] = mapped_column( - ForeignKey("core.tenants.id"), nullable=False, index=True - ) - - # Información básica - name: Mapped[Optional[str]] = mapped_column(String(256)) - rfc: Mapped[Optional[str]] = mapped_column(String(30)) - curp: Mapped[Optional[str]] = mapped_column(String(19)) - main_activity: Mapped[Optional[str]] = mapped_column(String(80)) - - # Programa - program: Mapped[Optional[str]] = mapped_column(String(7)) - program_number: Mapped[Optional[str]] = mapped_column(String(40)) - # prosec: indica si la empresa cuenta con programa PROSEC (sí/no) - prosec: Mapped[bool] = mapped_column( - Boolean, nullable=False, default=False, server_default="false" - ) - # prosec_authorization: clave del sector PROSEC (catálogo de sectores) - prosec_authorization: Mapped[Optional[str]] = mapped_column(String(20)) - - # Sectores - sector1: Mapped[Optional[str]] = mapped_column(String(150)) - sector2: Mapped[Optional[str]] = mapped_column(String(150)) - sector3: Mapped[Optional[str]] = mapped_column(String(5)) - - # Identificadores - manufacturer_id: Mapped[Optional[str]] = mapped_column(String(25)) - broker_company: Mapped[Optional[str]] = mapped_column(String(6)) - - # Responsable - responsible: Mapped[Optional[str]] = mapped_column(String(80)) - responsible_name: Mapped[Optional[str]] = mapped_column(String(20)) - responsible_last_name: Mapped[Optional[str]] = mapped_column(String(20)) - responsible_mother_last_name: Mapped[Optional[str]] = mapped_column(String(20)) - responsible_rfc: Mapped[Optional[str]] = mapped_column(String(30)) - position: Mapped[Optional[str]] = mapped_column(String(30)) - - # Configuración básica - logo: Mapped[Optional[str]] = mapped_column(String(512)) - fiscal_deposit: Mapped[Optional[bool]] = mapped_column(Boolean, default=False, server_default="false") - generate_barcodes_with_fiel: Mapped[Optional[bool]] = mapped_column(Boolean, default=False, server_default="false") - order_format_type: Mapped[Optional[str]] = mapped_column(String(19)) - is_service_company: Mapped[Optional[bool]] = mapped_column(Boolean, default=False, server_default="false") - client_name: Mapped[Optional[str]] = mapped_column(String(300)) - subassembly_mode: Mapped[Optional[str]] = mapped_column(String(7)) - - # Configuraciones técnicas (flags) - previous_code: Mapped[Optional[int]] = mapped_column(SmallInteger) - active_labels: Mapped[Optional[int]] = mapped_column(SmallInteger) - active_fractions: Mapped[Optional[int]] = mapped_column(SmallInteger) - activate_caat: Mapped[Optional[int]] = mapped_column(SmallInteger) - trans_interface: Mapped[Optional[int]] = mapped_column(SmallInteger) - american_costs: Mapped[Optional[int]] = mapped_column(SmallInteger) - scaf_readonly: Mapped[Optional[int]] = mapped_column(SmallInteger) - parts_replacement: Mapped[Optional[int]] = mapped_column(SmallInteger) - activate_facmexame: Mapped[Optional[int]] = mapped_column(SmallInteger) - part_reference: Mapped[Optional[int]] = mapped_column(SmallInteger) - international_firm: Mapped[Optional[int]] = mapped_column(SmallInteger) - seventh_amendment: Mapped[Optional[bool]] = mapped_column( - Boolean - ) # Septima enimenda (FinalContadorAElectronico) - - # Configuraciones simples - ftp_key: Mapped[Optional[str]] = mapped_column(String(10)) - sifra_path: Mapped[Optional[str]] = mapped_column(String(255)) - version_type: Mapped[Optional[str]] = mapped_column(String(20)) - sql_language: Mapped[Optional[str]] = mapped_column(String(19)) - balance_operation_mode: Mapped[Optional[str]] = mapped_column(String(50)) - inter_db_name: Mapped[Optional[str]] = mapped_column(String(100)) - - # ==================== RELACIONES CON SUBTABLAS ==================== - - addresses: Mapped[list["CompanyAddress"]] = relationship( - "CompanyAddress", - back_populates="company", - cascade="all, delete-orphan", - lazy="selectin", - ) - - certification: Mapped[Optional["CompanyCertification"]] = relationship( - "CompanyCertification", - back_populates="company", - uselist=False, - cascade="all, delete-orphan", - lazy="selectin", - ) - - digital_certificates: Mapped[list["CompanyDigitalCertificate"]] = relationship( - "CompanyDigitalCertificate", - back_populates="company", - cascade="all, delete-orphan", - lazy="selectin", - ) - - ventanilla_unica: Mapped[Optional["CompanyVU"]] = relationship( - "CompanyVU", - back_populates="company", - uselist=False, - cascade="all, delete-orphan", - lazy="selectin", - ) - - electronic_agent: Mapped[Optional["CompanyElectronicAgent"]] = relationship( - "CompanyElectronicAgent", - back_populates="company", - uselist=False, - cascade="all, delete-orphan", - lazy="selectin", - ) - - prevalidator: Mapped[Optional["CompanyPrevalidator"]] = relationship( - "CompanyPrevalidator", - back_populates="company", - uselist=False, - cascade="all, delete-orphan", - lazy="selectin", - ) - - cfdi: Mapped[Optional["CompanyCFDI"]] = relationship( - "CompanyCFDI", - back_populates="company", - uselist=False, - cascade="all, delete-orphan", - lazy="selectin", - ) diff --git a/backend/api/v1/modules/a76/general_catalogs/company/routes.py b/backend/api/v1/modules/a76/general_catalogs/company/routes.py deleted file mode 100644 index 021aa717..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/company/routes.py +++ /dev/null @@ -1,567 +0,0 @@ -""" -Rutas para gestión de empresa -""" - -import logging -import mimetypes -import os -import shutil -from typing import List, Optional -from pathlib import Path -from datetime import datetime - -from fastapi import APIRouter, Depends, HTTPException, Request, status, File, UploadFile -from fastapi.responses import FileResponse, Response -from sqlalchemy.orm import Session - -from core.config import settings -from core.database import get_core_db -from core.s3_keys import company_certificate_key, company_logo_key -from core.storage_s3 import delete_object_if_exists, get_object_bytes, put_object_bytes -from core.security import ( - collect_company_ids_from_app_membership, - get_current_user, - get_tenant_from_token, - is_hub_admin, - resolve_effective_tenant_id_from_user, - resolve_tenant_id_required, - validate_access_to_resource, -) -from .....common.tenant_crud_routes import TenantCRUDRoutes -from .dto import CompanyCreateDTO, CompanyResponseDTO, CompanyUpdateDTO -from .models import Company -from .service import CompanyService - -# Configuración de directorios -UPLOAD_DIR = "uploads/companies" -ALLOWED_EXTENSIONS = {".jpg", ".jpeg", ".png", ".gif", ".webp"} -MAX_FILE_SIZE = 5 * 1024 * 1024 # 5MB - -logger = logging.getLogger(__name__) - - -def _user_is_admin(current_user: dict) -> bool: - """ - Bypass cross-tenant para endpoints sin company_id en la firma. La única - fuente válida es hub_admin atestado por el Hub; el antiguo bypass por rol - "admin" del realm Keycloak se eliminó para no depender de claims del JWT. - Los super_admin locales operan a nivel de compañía (validate_access_to_resource). - """ - return is_hub_admin(current_user) - - -def _assert_permission_any_company( - db: Session, - current_user: dict, - permission_code: str, -) -> None: - """ - Enforces a permission when endpoint has no explicit company_id param. - """ - if _user_is_admin(current_user): - return - - user_id = current_user.get("sub") or current_user.get("id") - if not user_id: - raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Permission denied") - - company_ids = collect_company_ids_from_app_membership(db, str(user_id)) - if not company_ids: - raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Permission denied") - - from api.v1.modules.core.permissions.service import PermissionService - - permission_service = PermissionService(db) - for company_id in company_ids: - if permission_service.has_any_permission(str(user_id), int(company_id), [permission_code]): - return - - raise HTTPException(status_code=status.HTTP_403_FORBIDDEN, detail="Permission denied") - - -def _assert_permission_for_company( - db: Session, - company_id: int, - current_user: dict, - permission_code: str, -) -> int: - return validate_access_to_resource(db, company_id, current_user, [permission_code]) - - -def _resolve_tenant_id_int( - current_user: dict, - db: Session = None, - company_id: int = None, -) -> Optional[int]: - """Delega a resolve_tenant_id_required. Hub admin resuelve tenant desde la empresa.""" - return resolve_tenant_id_required(current_user, db=db, company_id=company_id) - - -def _is_s3_object_key(ref: Optional[str]) -> bool: - return bool(ref and ref.startswith("tenants/")) - - -def _remove_stored_file(ref: str) -> None: - if _is_s3_object_key(ref): - delete_object_if_exists(ref) - elif ref and os.path.isfile(ref): - try: - os.remove(ref) - except OSError: - pass - -# Main router that includes base CRUD -router = APIRouter(prefix="/company") - - -@router.post( - "", # Se suma al prefix, queda POST /api/v1/a76/company - response_model=CompanyResponseDTO, - status_code=status.HTTP_201_CREATED, - summary="Create a new company", -) -async def create_company( - request: Request, - data: CompanyCreateDTO, - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - _assert_permission_any_company(db, current_user, "cat_company.create") - - tenant_id = resolve_tenant_id_required(current_user, db=db) - if tenant_id is None: - # Prioridad: X-Tenant-Override del header (tenant seleccionado en el sidebar) - override = request.headers.get("X-Tenant-Override") - if override and str(override).strip().isdigit(): - tenant_id = int(override) - # Fallback: campo tenant_id en el body (enviado por el frontend como respaldo) - elif data.tenant_id: - tenant_id = data.tenant_id - else: - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail="Hub admin: selecciona un tenant en el sidebar antes de crear una empresa", - ) - - service = CompanyService(db) - new_company = service.create_company_manually(data, tenant_id=tenant_id) - return CompanyResponseDTO.model_validate(service.flatten_company_dto(new_company)) - - -@router.get( - "", # GET /api/v1/a76/company with pagination - response_model=dict, - summary="Get companies with pagination", -) -async def list_companies( - request: Request, - page: int = 1, - page_size: int = 50, - name: Optional[str] = None, - rfc: Optional[str] = None, - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - """Get paginated list of companies for current tenant with optional filters""" - _assert_permission_any_company(db, current_user, "cat_company.view") - - tenant_id = resolve_tenant_id_required(current_user, db=db) - # Hub admin: si hay X-Tenant-Override y aún no se resolvió tenant, filtra por ese tenant - if tenant_id is None and is_hub_admin(current_user): - override = request.headers.get("X-Tenant-Override") - if override and str(override).strip().isdigit(): - tenant_id = int(override) - - skip = (page - 1) * page_size - filters = {} - if name: - filters["name"] = name - if rfc: - filters["rfc"] = rfc - - service = CompanyService(db) - items, total = service.get_all( - db, - tenant_id, - company_id=0, # Not used for companies - skip=skip, - limit=page_size, - filters=filters if filters else None, - ) - - total_pages = (total + page_size - 1) // page_size - - return { - "items": [ - CompanyResponseDTO.model_validate(service.flatten_company_dto(item)) - for item in items - ], - "total": total, - "page": page, - "page_size": page_size, - "pages": total_pages, - } - - -@router.get( - "/my-companies", - response_model=List[CompanyResponseDTO], - summary="Get all companies for current tenant", -) -async def get_my_companies( - request: Request, - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - """ - Compañías accesibles: tenant del token/Hub (si existe) unión con membership en BD - (``user_company_roles``, ``user_company_permissions``, ``user_tenants``). - Hub admin: si hay X-Tenant-Override muestra solo ese tenant; si no, muestra todos. - """ - # Hub admin: respeta X-Tenant-Override para filtrar por tenant seleccionado; - # sin override devuelve todo (visibilidad global). Se consulta el booleano - # normalizado por el Hub (no se lee del JWT) para no acoplar a claims. - if is_hub_admin(current_user): - service = CompanyService(db) - override = request.headers.get("X-Tenant-Override") - query = db.query(Company).filter(Company.deleted_at.is_(None)) - if override and str(override).strip().isdigit(): - query = query.filter(Company.tenant_id == int(override)) - all_companies = query.order_by(Company.name).all() - return [ - CompanyResponseDTO.model_validate(service.flatten_company_dto(c)) - for c in all_companies - ] - - _assert_permission_any_company(db, current_user, "cat_company.view") - - user_id = current_user.get("sub") or current_user.get("id") - tenant_id = resolve_effective_tenant_id_from_user(current_user) - - service = CompanyService(db) - companies = service.get_companies_for_session_user( - keycloak_user_id=str(user_id) if user_id else None, - tenant_id_from_token=tenant_id, - ) - - return [ - CompanyResponseDTO.model_validate(service.flatten_company_dto(company)) - for company in companies - ] - -# ... existing code ... - -@router.get( - "/{company_id}", - response_model=CompanyResponseDTO, - summary="Get company by ID", -) -async def get_company( - company_id: int, - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - """Get a specific company by ID""" - _assert_permission_for_company(db, company_id, current_user, "cat_company.view") - - tenant_id = resolve_tenant_id_required(current_user, db=db, company_id=company_id) - - service = CompanyService(db) - company = CompanyService.get_by_id(db, company_id, tenant_id, 0) - if not company: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="Company not found", - ) - - return CompanyResponseDTO.model_validate(service.flatten_company_dto(company)) - - -@router.put( - "/{company_id}", - response_model=CompanyResponseDTO, - summary="Update company", -) -async def update_company( - company_id: int, - data: CompanyUpdateDTO, - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - """Update a company""" - _assert_permission_for_company(db, company_id, current_user, "cat_company.edit") - - tenant_id = resolve_tenant_id_required(current_user, db=db, company_id=company_id) - - service = CompanyService(db) - updated_company = service.update(db, company_id, tenant_id, 0, data) - if not updated_company: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="Company not found", - ) - - return CompanyResponseDTO.model_validate(service.flatten_company_dto(updated_company)) - - - - - - - - -@router.get( - "/{company_id}/logo/image", - summary="Get company logo image", -) - -async def get_company_logo_image( - company_id: int, - db: Session = Depends(get_core_db), - # Public endpoint to allow tags to load the image without custom headers -): - """Serve the company logo image file""" - # Security: In a stricter environment, we would use a signed short-lived URL - # or cookie-based auth. For now, checking if company exists is sufficient. - - # We find the company ignoring tenant checks for the image serving - # (Logos are generally considered semi-public assets in this context) - company = db.query(Company).filter(Company.id == company_id).first() - - if not company or not company.logo: - raise HTTPException(status_code=404, detail="Logo not found") - - if _is_s3_object_key(company.logo): - try: - data = get_object_bytes(company.logo) - except Exception: - raise HTTPException(status_code=404, detail="Logo file not found on server") - media = mimetypes.guess_type(company.logo)[0] or "image/jpeg" - return Response(content=data, media_type=media) - - file_path = Path(company.logo) - if not file_path.exists(): - standard_path = Path(f"app_data/logos/{company_id}") / file_path.name - if standard_path.exists(): - return FileResponse(standard_path) - - raise HTTPException(status_code=404, detail="Logo file not found on server") - - return FileResponse(file_path) - - -@router.delete( - "/{company_id}", - status_code=status.HTTP_204_NO_CONTENT, - summary="Delete a company", -) -async def delete_company( - company_id: int, - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - """Delete a company""" - _assert_permission_for_company(db, company_id, current_user, "cat_company.delete") - - tenant_id = resolve_tenant_id_required(current_user, db=db, company_id=company_id) - - success = CompanyService.delete(db, company_id, tenant_id, 0) - if not success: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="Company not found", - ) - - return None - - -@router.post( - "/{company_id}/upload-logo", - response_model=dict, - summary="Upload company logo", -) -async def upload_company_logo( - company_id: int, - file: UploadFile = File(...), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - """Upload a logo for a company""" - _assert_permission_for_company(db, company_id, current_user, "cat_company.edit") - - tenant_id = _resolve_tenant_id_int(current_user, db=db, company_id=company_id) - - # Validar que la empresa existe - company = CompanyService.get_by_id(db, company_id, tenant_id, 0) - if not company: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="Company not found", - ) - - # Validar extensión - file_ext = os.path.splitext(file.filename)[1].lower() - if file_ext not in ALLOWED_EXTENSIONS: - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail=f"File type not allowed. Allowed: {', '.join(ALLOWED_EXTENSIONS)}", - ) - - # Validar tamaño - content = await file.read() - if len(content) > MAX_FILE_SIZE: - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail=f"File too large. Maximum size: {MAX_FILE_SIZE / 1024 / 1024}MB", - ) - - if company.logo: - _remove_stored_file(company.logo) - - timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") - filename = f"logo_{company_id}_{timestamp}{file_ext}" - - try: - if settings.use_s3_object_storage: - ct = mimetypes.guess_type(filename)[0] or "image/jpeg" - key = company_logo_key(tenant_id, company_id, filename) - put_object_bytes(key, content, content_type=ct) - logger.info( - "Company logo stored in S3 key=%s bytes=%s", - key, - len(content), - ) - file_path = key - else: - os.makedirs(UPLOAD_DIR, exist_ok=True) - file_path = os.path.join(UPLOAD_DIR, filename) - with open(file_path, "wb") as f: - f.write(content) - except Exception as e: - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail=f"Error saving file: {str(e)}", - ) - - update_data = CompanyUpdateDTO(logo=file_path) - service = CompanyService(db) - updated_company = service.update(db, company_id, tenant_id, 0, update_data) - - return { - "message": "Logo uploaded successfully", - "logo_path": file_path, - "company_id": company_id, - } - - -@router.post( - "/{company_id}/upload-certificate", - response_model=dict, - summary="Upload company certificate", -) -async def upload_company_certificate( - company_id: int, - certificate_type: str, - file: UploadFile = File(...), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - """ - Upload a certificate for a company - certificate_type: fiel_cer, fiel_key, cfdi_cert_cer, cfdi_cert_key, cancel_cer, cancel_key - """ - _assert_permission_for_company(db, company_id, current_user, "cat_company.edit") - - tenant_id = _resolve_tenant_id_int(current_user, db=db, company_id=company_id) - - # Validar que la empresa existe - service = CompanyService(db) - company = service.get_by_id(db, company_id, tenant_id, 0) - if not company: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="Company not found", - ) - - # Validar tipo de certificado - valid_types = [ - "fiel_cer", "fiel_key", - "cfdi_cert_cer", "cfdi_cert_key", - "cancel_cer", "cancel_key" - ] - if certificate_type not in valid_types: - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail=f"Invalid certificate type. Allowed: {', '.join(valid_types)}", - ) - - # Validar extensión - file_ext = os.path.splitext(file.filename)[1].lower() - allowed_exts = {".cer", ".key"} - if file_ext not in allowed_exts: - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail=f"File type not allowed. Allowed: {', '.join(allowed_exts)}", - ) - - # Validar correspondencia extensión vs tipo (simple check, respetando sufijo) - ctype = (certificate_type or "").lower() - if ctype.endswith("_cer") and file_ext != ".cer": - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail="For this certificate type, file must be .cer", - ) - if ctype.endswith("_key") and file_ext != ".key": - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail="For this certificate type, file must be .key", - ) - - # Validar tamaño - content = await file.read() - if len(content) > MAX_FILE_SIZE: - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail=f"File too large. Maximum size: {MAX_FILE_SIZE / 1024 / 1024}MB", - ) - - timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") - - try: - if settings.use_s3_object_storage: - key = company_certificate_key( - tenant_id, company_id, certificate_type, timestamp, file_ext - ) - ct = ( - "application/x-x509-ca-cert" - if file_ext == ".cer" - else "application/pkcs8" - ) - put_object_bytes(key, content, content_type=ct) - logger.info( - "Company certificate stored in S3 key=%s bytes=%s", - key, - len(content), - ) - file_path = key - else: - certs_dir = os.path.join(UPLOAD_DIR, str(company_id), "certificates") - os.makedirs(certs_dir, exist_ok=True) - filename = f"{certificate_type}_{timestamp}{file_ext}" - file_path = os.path.join(certs_dir, filename) - with open(file_path, "wb") as f: - f.write(content) - except Exception as e: - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail=f"Error saving file: {str(e)}", - ) - - service.upload_certificate(company_id, certificate_type, file_path, tenant_id) - - return { - "message": "Certificate uploaded successfully", - "file_path": file_path, - "certificate_type": certificate_type, - "company_id": company_id, - } diff --git a/backend/api/v1/modules/a76/general_catalogs/company/service.py b/backend/api/v1/modules/a76/general_catalogs/company/service.py deleted file mode 100644 index 1d758980..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/company/service.py +++ /dev/null @@ -1,876 +0,0 @@ -""" -Capa de servicio para lógica de negocio de empresa -""" - -import logging -from datetime import datetime -from typing import List, Optional, Tuple, Dict, Any, Set - -from fastapi import HTTPException -from sqlalchemy.exc import IntegrityError -from sqlalchemy.orm import Session - -from core.config import settings -from core.s3_keys import tenant_company_prefix -from core.storage_s3 import delete_objects_with_prefix - -from .dto import CompanyCreateDTO, CompanyResponseDTO, CompanyUpdateDTO -from .models import Company -from ...audit_log.services.service import AuditService -from ..units_of_measure.seed import seed as units_of_measure_seed -from ..fractions.historical_tariff_fractions.seed import seed as historical_tariff_fractions_seed -from ..fractions.warning_fractions.seed import seed as warning_fractions_seed -from ..sectors.seed import seed as sectors_seed -from ...doc_types_dig.seed import seed as doc_types_dig_seed -from core.context import get_user_context -from sqlalchemy import text - -logger = logging.getLogger(__name__) - - -class CompanyService: - """Servicio para gestión de empresa""" - - def __init__(self, db: Session): - self.db = db - - # Métodos para TenantCRUDRoutes - @staticmethod - def get_all( - db: Session, - tenant_id: int, - company_id: int, - skip: int = 0, - limit: int = 50, - filters: Optional[Dict[str, Any]] = None, - ) -> Tuple[List[Company], int]: - """Get all companies for a tenant with pagination""" - query = db.query(Company).filter(Company.deleted_at.is_(None)) - if tenant_id is not None: - query = query.filter(Company.tenant_id == tenant_id) - - # Apply filters if provided - if filters: - if filters.get("name"): - query = query.filter( - Company.name.ilike(f"%{filters['name']}%") - ) - if filters.get("rfc"): - query = query.filter( - Company.rfc.ilike(f"%{filters['rfc']}%") - ) - - total = query.count() - companies = query.offset(skip).limit(limit).all() - - return companies, total - - @staticmethod - def get_by_id( - db: Session, company_id: int, tenant_id: int, company_id_unused: int - ) -> Optional[Company]: - """Get company by ID""" - query = db.query(Company).filter(Company.id == company_id, Company.deleted_at.is_(None)) - if tenant_id is not None: - query = query.filter(Company.tenant_id == tenant_id) - return query.first() - - # ESTE ES EL MÉTODO VIEJO QUE CAUSABA PROBLEMAS (Lo dejamos por si acaso) - @staticmethod - def create( - db: Session, - company_data: CompanyCreateDTO, - tenant_id: int, - company_id: int, - ) -> Company: - """Create a new company (MÉTODO GENÉRICO - NO USAR PARA CREACIÓN MANUAL)""" - try: - db_company = Company( - **company_data.model_dump(exclude_unset=True), - tenant_id=tenant_id - ) - - db.add(db_company) - db.commit() - db.refresh(db_company) - - return db_company - - except IntegrityError as e: - db.rollback() - logger.error(f"IntegrityError creating company: {str(e)}") - raise HTTPException( - status_code=400, - detail="Company already exists", - ) - except Exception as e: - db.rollback() - logger.error(f"Error creating company: {str(e)}") - raise HTTPException(status_code=500, detail="Error creating company") - - - # ==================== HELPERS FOR FIELD MAPPING ==================== - def _extract_company_fields(self, data: Dict[str, Any]) -> Dict[str, Any]: - """Extrae campos que pertenecen a la tabla Company principal""" - company_fields = [ - "name", "rfc", "curp", "main_activity", "program", "program_number", - "prosec", "prosec_authorization", "sector1", "sector2", "sector3", - "manufacturer_id", "broker_company", "responsible", "responsible_name", - "responsible_last_name", "responsible_mother_last_name", "responsible_rfc", - "position", "logo", "fiscal_deposit", - "generate_barcodes_with_fiel", "order_format_type", - "is_service_company", "client_name", "subassembly_mode", "previous_code", - "active_labels", "active_fractions", "activate_caat", "trans_interface", - "american_costs", "scaf_readonly", "parts_replacement", "activate_facmexame", - "part_reference", "international_firm", "ftp_key", "sifra_path", - "version_type", "sql_language", "balance_operation_mode", "inter_db_name", - "seventh_amendment" - ] - return {k: v for k, v in data.items() if k in company_fields} - - def _extract_certification_fields(self, data: Dict[str, Any]) -> Dict[str, Any]: - """Extrae campos que pertenecen a CompanyCertification""" - cert_fields = [ - "is_certified_company", "certified_company_registration", - "certified_company_start_date", "certified_company_end_date", - "annex30_certification_date", "annex30_certification_number", - "annex30_modality", "annex30_company_type", "annex30_renewal_date", - "annex30_final_certification_date", "is_seciit_company", "is_oea_company", "ctpat_svi", - "trusted_exporter_number", "neec_company" - ] - return {k: v for k, v in data.items() if k in cert_fields} - - def _extract_address_fields(self, data: Dict[str, Any], type_prefix: str) -> Dict[str, Any]: - """Extrae campos de dirección con base en un prefijo (main_, ind1_, ind2_)""" - fields = ["street", "exterior_number", "interior_number", "postal_code", - "neighborhood", "city", "municipality", "state", "country", - "phone", "fax", "email"] - - extracted = {} - for f in fields: - key = f"{type_prefix}_{f}" - if key in data: - extracted[f] = data[key] - return extracted - - def _extract_prevalidator_fields(self, data: Dict[str, Any]) -> Dict[str, Any]: - """Extrae campos que pertenecen a CompanyPrevalidator""" - # Se mapean campos 'prev_*' a los nombres del modelo - mapping = { - "prev_customs": "customs", - "prev_key": "key", - "prev_patent": "patent", - "prev_description": "description" - } - extracted = {} - for dto_key, model_key in mapping.items(): - if dto_key in data: - extracted[model_key] = data[dto_key] - - # Retrocompatibilidad con el campo prevalidator_key que ya estaba en el DTO - if "prevalidator_key" in data and "key" not in extracted: - extracted["key"] = data["prevalidator_key"] - - return extracted - - def _extract_vu_fields(self, data: Dict[str, Any]) -> Dict[str, Any]: - """Extrae campos que pertenecen a CompanyVU (prefijo vu_)""" - vu_fields = [ - "webservice_user", "webservice_password", "email", "figure_type", - "central_path", "xml_files_path", "query_rfc", "validation_rfc", - "configuration_source", "measurement_units" - ] - extracted = {} - for f in vu_fields: - key = f"vu_{f}" - if key in data: - extracted[f] = data[key] - return extracted - - def _extract_electronic_agent_fields(self, data: Dict[str, Any]) -> Dict[str, Any]: - """Extrae campos que pertenecen a CompanyElectronicAgent (prefijo ea_)""" - ea_fields = [ - "input_folder", "output_folder", "send_mask", "response_mask", - "response_extension", "counter_start", "counter_end", "counter_next" - ] - extracted = {} - for f in ea_fields: - key = f"ea_{f}" - if key in data: - extracted[f] = data[key] - return extracted - - def _extract_cfdi_fields(self, data: Dict[str, Any]) -> Dict[str, Any]: - """Extrae campos que pertenecen a CompanyCFDI (prefijo cfdi_)""" - cfdi_fields = ["xml_save_path", "cfdi_app_path", "pac_app_path"] - extracted = {} - for f in cfdi_fields: - key = f"cfdi_{f}" - if key in data: - extracted[f] = data[key] - return extracted - - def _extract_digital_certificate_fields(self, data: Dict[str, Any], cert_prefix: str) -> Dict[str, Any]: - """Extrae campos para un tipo específico de certificado (fiel, cfdi_cert, cancel)""" - # Mapeo de prefijos DTO a nombres de modelo - fields_map = { - f"{cert_prefix}_cer": "cer_file_path", - f"{cert_prefix}_key": "key_file_path", - f"{cert_prefix}_pass": "password", - f"{cert_prefix}_access": "access_key", - f"{cert_prefix}_cer_exp": "cer_expiration_date", - f"{cert_prefix}_key_exp": "key_expiration_date" - } - - extracted = {} - for dto_key, model_key in fields_map.items(): - if dto_key in data: - extracted[model_key] = data[dto_key] - - if extracted: - # Mapear prefijo al tipo real en base de datos - model_type_map = {'fiel': 'fiel', 'cfdi_cert': 'cfdi', 'cancel': 'cancellation'} - extracted['certificate_type'] = model_type_map.get(cert_prefix, cert_prefix) - - return extracted - - - def flatten_company_dto(self, company: Company) -> Dict[str, Any]: - """Flattens Company and its submodels into a single dict for DTO validation""" - # 1. Base Company fields - result = { - k: getattr(company, k) - for k in company.__mapper__.c.keys() - } - - # 2. Certification fields - if company.certification: - cert_fields = [ - "is_certified_company", "certified_company_registration", - "certified_company_start_date", "certified_company_end_date", - "annex30_certification_date", "annex30_certification_number", - "annex30_modality", "annex30_company_type", "annex30_renewal_date", - "annex30_final_certification_date", "is_seciit_company", "is_oea_company", "ctpat_svi", - "trusted_exporter_number", "neec_company" - ] - for field in cert_fields: - val = getattr(company.certification, field, None) - if val is not None: - result[field] = val - - # 3. Addresses - for addr in company.addresses: - prefix = "" - if addr.address_type == 'main': prefix = "main_" - elif addr.address_type == 'industrial': prefix = "ind1_" - elif addr.address_type == 'industrial2': prefix = "ind2_" - - if prefix: - addr_fields = ["street", "exterior_number", "interior_number", "postal_code", - "neighborhood", "city", "municipality", "state", "country", - "phone", "fax", "email"] - for f in addr_fields: - val = getattr(addr, f, None) - if val is not None: - result[f"{prefix}{f}"] = val - - # 4. Prevalidator fields - if company.prevalidator: - mapping = {"customs": "prev_customs", "key": "prev_key", - "patent": "prev_patent", "description": "prev_description"} - for model_f, dto_f in mapping.items(): - val = getattr(company.prevalidator, model_f, None) - if val is not None: - result[dto_f] = val - # Retrocompatibilidad - if company.prevalidator.key: - result["prevalidator_key"] = company.prevalidator.key - - # 5. VU fields - if company.ventanilla_unica: - f_list = ["webservice_user", "webservice_password", "email", "figure_type", - "central_path", "xml_files_path", "query_rfc", "validation_rfc", - "configuration_source", "measurement_units"] - for f in f_list: - val = getattr(company.ventanilla_unica, f, None) - if val is not None: - result[f"vu_{f}"] = val - - # 6. Electronic Agent fields - if company.electronic_agent: - f_list = ["input_folder", "output_folder", "send_mask", "response_mask", - "response_extension", "counter_start", "counter_end", "counter_next"] - for f in f_list: - val = getattr(company.electronic_agent, f, None) - if val is not None: - result[f"ea_{f}"] = val - - # 7. CFDI fields - if company.cfdi: - f_list = ["xml_save_path", "cfdi_app_path", "pac_app_path"] - for f in f_list: - val = getattr(company.cfdi, f, None) - if val is not None: - result[f"cfdi_{f}"] = val - - # 8. Digital Certificates - cert_type_map = {'fiel': 'fiel', 'cfdi': 'cfdi_cert', 'cancellation': 'cancel'} - for dc in company.digital_certificates: - prefix = cert_type_map.get(dc.certificate_type) - if prefix: - result[f"{prefix}_cer"] = dc.cer_file_path - result[f"{prefix}_key"] = dc.key_file_path - result[f"{prefix}_pass"] = dc.password - result[f"{prefix}_access"] = dc.access_key - result[f"{prefix}_cer_exp"] = dc.cer_expiration_date - result[f"{prefix}_key_exp"] = dc.key_expiration_date - - return result - - - # ==================== CRUD METHODS ==================== - - def create_company_manually(self, data: CompanyCreateDTO, tenant_id: int, username: str = "System") -> Company: - from .submodels.certification import CompanyCertification - from .submodels.prevalidator import CompanyPrevalidator - from .submodels.address import CompanyAddress - from .submodels.vu import CompanyVU - from .submodels.electronic_agent import CompanyElectronicAgent - from .submodels.cfdi import CompanyCFDI - - try: - # 1. Preparar datos - obj_data = data.model_dump(exclude_unset=True) - - # 2. Extract fields - company_data = self._extract_company_fields(obj_data) - cert_data = self._extract_certification_fields(obj_data) - preval_data = self._extract_prevalidator_fields(obj_data) - vu_data = self._extract_vu_fields(obj_data) - ea_data = self._extract_electronic_agent_fields(obj_data) - cfdi_data = self._extract_cfdi_fields(obj_data) - - addr_main = self._extract_address_fields(obj_data, "main") - addr_ind1 = self._extract_address_fields(obj_data, "ind1") - addr_ind2 = self._extract_address_fields(obj_data, "ind2") - - fiel_data = self._extract_digital_certificate_fields(obj_data, "fiel") - cfdi_cert_data = self._extract_digital_certificate_fields(obj_data, "cfdi_cert") - cancel_cert_data = self._extract_digital_certificate_fields(obj_data, "cancel") - - - # 3. Create Company - db_company = Company(**company_data, tenant_id=tenant_id) - self.db.add(db_company) - self.db.flush() # Generate ID - - # 4. Create submodels - if cert_data: - self.db.add(CompanyCertification(**cert_data, company_id=db_company.id)) - if preval_data: - self.db.add(CompanyPrevalidator(**preval_data, company_id=db_company.id)) - if vu_data: - self.db.add(CompanyVU(**vu_data, company_id=db_company.id)) - if ea_data: - self.db.add(CompanyElectronicAgent(**ea_data, company_id=db_company.id)) - if cfdi_data: - self.db.add(CompanyCFDI(**cfdi_data, company_id=db_company.id)) - - # 5. Create Digital Certificates - from .submodels.digital_certificate import CompanyDigitalCertificate - for dc_data in [fiel_data, cfdi_cert_data, cancel_cert_data]: - if dc_data: - self.db.add(CompanyDigitalCertificate(**dc_data, company_id=db_company.id)) - - # 6. Create Addresses - - if addr_main: - self.db.add(CompanyAddress(**addr_main, address_type='main', company_id=db_company.id)) - if addr_ind1: - self.db.add(CompanyAddress(**addr_ind1, address_type='industrial', company_id=db_company.id)) - if addr_ind2: - self.db.add(CompanyAddress(**addr_ind2, address_type='industrial2', company_id=db_company.id)) - - # 7. Seed company data (tenant/company dependent) - self._seed_company_data(self.db, tenant_id, db_company.id) - - # 8. Commit - self.db.commit() - - self.db.refresh(db_company) - - # --- Audit Log --- - try: - # Si no se pasó un username explícito, intentar obtenerlo del contexto - if username == "System": - ctx = get_user_context() - if ctx: - username = ctx.get("preferred_username") or ctx.get("email") or "System" - - # Preparamos la data para el log (aplanada) - log_data = self.flatten_company_dto(db_company) - - AuditService.log_crud_operation( - db=self.db, - table_name="company", - operation_type="CREATE", - record_data=log_data, - username=username, - record_id=str(db_company.id), - company_id=db_company.id, - tenant_id=tenant_id - ) - except Exception as e: - logger.error(f"Error creating audit log for company creation: {e}") - # ----------------- - - return db_company - - except IntegrityError as e: - self.db.rollback() - logger.error(f"IntegrityError creating company manually: {str(e)}") - raise HTTPException(status_code=400, detail="Error de integridad: Es posible que esta empresa ya exista.") - except Exception as e: - self.db.rollback() - logger.error(f"Error creating company manually: {str(e)}") - raise HTTPException(status_code=500, detail=f"Error creando empresa: {str(e)}") - - def update( - self, - db: Session, - company_id: int, - tenant_id: int, - company_id_unused: int, - company_data: CompanyUpdateDTO, - username: str = "System", - ) -> Optional[Company]: - """Update a company""" - from .submodels.certification import CompanyCertification - from .submodels.prevalidator import CompanyPrevalidator - from .submodels.address import CompanyAddress - from .submodels.vu import CompanyVU - from .submodels.electronic_agent import CompanyElectronicAgent - from .submodels.cfdi import CompanyCFDI - - session = db if db else self.db - company = self.get_by_id(session, company_id, tenant_id, company_id_unused) - if not company: return None - - # --- Audit Log Prep --- - old_values = {} - try: - # Capturamos estado actual para comparar - # Usamos flatten_company_dto para tener una representación completa - old_values = self.flatten_company_dto(company) - except Exception as e: - logger.error(f"Error prepping audit log (old values): {e}") - # ---------------------- - - update_data = company_data.model_dump(exclude_unset=True) - - # 1. Update Company fields - company_fields = self._extract_company_fields(update_data) - for field, value in company_fields.items(): - setattr(company, field, value) - - # 2. Update Certification - cert_fields = self._extract_certification_fields(update_data) - if cert_fields: - if company.certification: - for field, value in cert_fields.items(): setattr(company.certification, field, value) - else: - session.add(CompanyCertification(**cert_fields, company_id=company.id)) - - # 3. Update Prevalidator - preval_fields = self._extract_prevalidator_fields(update_data) - if preval_fields: - if company.prevalidator: - for field, value in preval_fields.items(): setattr(company.prevalidator, field, value) - else: - session.add(CompanyPrevalidator(**preval_fields, company_id=company.id)) - - # 4. Update VU - vu_fields = self._extract_vu_fields(update_data) - if vu_fields: - if company.ventanilla_unica: - for field, value in vu_fields.items(): setattr(company.ventanilla_unica, field, value) - else: - session.add(CompanyVU(**vu_fields, company_id=company.id)) - - # 5. Update Electronic Agent - ea_fields = self._extract_electronic_agent_fields(update_data) - if ea_fields: - if company.electronic_agent: - for field, value in ea_fields.items(): setattr(company.electronic_agent, field, value) - else: - session.add(CompanyElectronicAgent(**ea_fields, company_id=company.id)) - - # 6. Update CFDI - cfdi_fields = self._extract_cfdi_fields(update_data) - if cfdi_fields: - if company.cfdi: - for field, value in cfdi_fields.items(): setattr(company.cfdi, field, value) - else: - session.add(CompanyCFDI(**cfdi_fields, company_id=company.id)) - - # 7. Update Digital Certificates - from .submodels.digital_certificate import CompanyDigitalCertificate - for p in ["fiel", "cfdi_cert", "cancel"]: - dc_data = self._extract_digital_certificate_fields(update_data, p) - if dc_data: - m_type = dc_data['certificate_type'] - target = next((c for c in company.digital_certificates if c.certificate_type == m_type), None) - if target: - for field, value in dc_data.items(): setattr(target, field, value) - else: - session.add(CompanyDigitalCertificate(**dc_data, company_id=company.id)) - - # 8. Update Addresses - - for prefix, addr_type in [("main", "main"), ("ind1", "industrial"), ("ind2", "industrial2")]: - addr_data = self._extract_address_fields(update_data, prefix) - if addr_data: - # Buscar dirección existente de ese tipo - target_addr = next((a for a in company.addresses if a.address_type == addr_type), None) - if target_addr: - for field, value in addr_data.items(): setattr(target_addr, field, value) - else: - session.add(CompanyAddress(**addr_data, address_type=addr_type, company_id=company.id)) - - try: - session.commit() - session.refresh(company) - - # --- Audit Log --- - try: - # Si no se pasó un username explícito, intentar obtenerlo del contexto - if username == "System": - ctx = get_user_context() - if ctx: - username = ctx.get("preferred_username") or ctx.get("email") or "System" - - new_values = self.flatten_company_dto(company) - - AuditService.log_crud_operation( - db=session, - table_name="company", - operation_type="UPDATE", - record_data=new_values, # Data más reciente - username=username, - record_id=str(company.id), - old_values=old_values, - new_values=new_values, - company_id=company.id, - tenant_id=tenant_id - ) - except Exception as e: - logger.error(f"Error creating audit log for company update: {e}") - # ----------------- - - return company - - except Exception as e: - session.rollback() - logger.error(f"Error updating company {company_id}: {str(e)}") - raise HTTPException(status_code=500, detail=f"Error al actualizar la empresa: {str(e)}") - - @staticmethod - def delete( - db: Session, company_id: int, tenant_id: int, company_id_unused: int, username: str = "System" - ) -> bool: - """Delete a company""" - company = CompanyService.get_by_id(db, company_id, tenant_id, company_id_unused) - if not company: - return False - - # --- Audit Log Prep --- - record_data = {} - try: - service = CompanyService(db) # Instancia para usar métodos de instancia si fuera necesario, o usar estático si flatten lo fuera - # flatten_company_dto es método de instancia en la definición actual, pero se está llamando aquí - # Deberíamos instanciar el servicio o mover flatten a estático. - # Como flatten usa self solo para acceder a nada realmente del estado, podría ser estático, - # pero para no romper, instanciamos. - record_data = service.flatten_company_dto(company) - except Exception: - pass - # ---------------------- - - try: - # 1) Borrado lógico en base de datos - company.deleted_at = datetime.utcnow() - db.flush() - - # 2) Limpieza de objetos S3/MinIO asociados a la compañía - if settings.use_s3_object_storage: - try: - prefix = tenant_company_prefix(tenant_id, company_id) - delete_objects_with_prefix(prefix) - except Exception as e: - # No bloquear la eliminación lógica si falla la limpieza de objetos - logger.error( - "Error deleting S3 objects for company %s (tenant %s): %s", - company_id, - tenant_id, - e, - ) - - db.commit() - - # --- Audit Log --- - try: - # Context check - if username == "System": - ctx = get_user_context() - if ctx: - username = ctx.get("preferred_username") or ctx.get("email") or "System" - - AuditService.log_crud_operation( - db=db, - table_name="company", - operation_type="DELETE", - record_data=record_data, - username=username, - record_id=str(company_id), - company_id=company_id, - tenant_id=tenant_id - ) - except Exception as e: - logger.error(f"Error creating audit log for company delete: {e}") - # ----------------- - - return True - except IntegrityError as e: - db.rollback() - logger.error(f"IntegrityError deleting company {company_id}: {str(e)}") - raise HTTPException(status_code=400, detail="No se puede eliminar la empresa porque tiene registros relacionados.") - except Exception as e: - db.rollback() - logger.error(f"Error deleting company {company_id}: {str(e)}") - raise HTTPException(status_code=500, detail="Error al eliminar la empresa") - - def upload_certificate( - self, - company_id: int, - certificate_type: str, - file_path: str, - tenant_id: int - ) -> Company: - """ - Update a certificate path for a company - certificate_type: fiel_cer, fiel_key, cfdi_cert_cer, cfdi_cert_key, cancel_cer, cancel_key - """ - from .submodels.digital_certificate import CompanyDigitalCertificate - - company = self.get_by_id(self.db, company_id, tenant_id, 0) - if not company: - return None - - # Determinar el tipo de certificado (fiel, cfdi, cancellation) y el campo a actualizar (cer_file_path, key_file_path) - cert_model_type = "" - field_to_update = "" - - if certificate_type == "fiel_cer": - cert_model_type = "fiel" - field_to_update = "cer_file_path" - elif certificate_type == "fiel_key": - cert_model_type = "fiel" - field_to_update = "key_file_path" - elif certificate_type == "cfdi_cert_cer": - cert_model_type = "cfdi" - field_to_update = "cer_file_path" - elif certificate_type == "cfdi_cert_key": - cert_model_type = "cfdi" - field_to_update = "key_file_path" - elif certificate_type == "cancel_cer": - cert_model_type = "cancellation" - field_to_update = "cer_file_path" - elif certificate_type == "cancel_key": - cert_model_type = "cancellation" - field_to_update = "key_file_path" - else: - raise ValueError(f"Invalid certificate type: {certificate_type}") - - # Buscar el registro de certificado existente - target_cert = next((c for c in company.digital_certificates if c.certificate_type == cert_model_type), None) - - try: - if target_cert: - old_path = getattr(target_cert, field_to_update, None) - if old_path and str(old_path).startswith("tenants/"): - from core.storage_s3 import delete_object_if_exists - - delete_object_if_exists(str(old_path)) - # Si existe, actualizamos - setattr(target_cert, field_to_update, file_path) - else: - # Si no existe, creamos uno nuevo - new_cert_data = { - "certificate_type": cert_model_type, - "company_id": company.id, - field_to_update: file_path - } - new_cert = CompanyDigitalCertificate(**new_cert_data) - self.db.add(new_cert) - - self.db.commit() - self.db.refresh(company) - return company - except Exception as e: - self.db.rollback() - logger.error(f"Error uploading certificate: {str(e)}") - raise HTTPException(status_code=500, detail=f"Error al guardar la referencia del certificado: {str(e)}") - - - # Custom methods - def _seed_company_data(self, db: Session, tenant_id: int, company_id: int): - """Seeds tenant/company dependent data for a new company""" - def format_value(val): - if val is None or str(val).strip() == "" or str(val).upper() == "NONE": - return "NULL" - return f"'{str(val).replace(chr(39), chr(39)*2)}'" - - # 1. Units of Measure - val_uom = ", ".join( - [ - f"({format_value(code)}, {format_value(desc)}, {format_value(desc_en)}, " - f"{format_value(customs)}, {format_value(american)}, {format_value(ace)}, {format_value(oma)}, {tenant_id}, {company_id})" - for code, desc, desc_en, customs, american, ace, oma in units_of_measure_seed - ] - ) - - db.execute(text("ALTER TABLE a76.units_of_measure DISABLE TRIGGER ALL;")) - db.execute(text(f"INSERT INTO a76.units_of_measure (code, description, description_en, customs_code, american_code, ace_code, oma_code, tenant_id, company_id) VALUES {val_uom} ON CONFLICT (code, tenant_id, company_id) DO NOTHING;")) - db.execute(text("ALTER TABLE a76.units_of_measure ENABLE TRIGGER ALL;")) - - # 2. Historical Tariff Fractions - def format_bool(val): - if val is None or str(val).strip() == "" or str(val).upper() == "NONE": - return "NULL" - return "TRUE" if str(val).upper() == "TRUE" else "FALSE" - - def format_timestamp(val): - if val is None or str(val).strip() == "" or str(val).upper() == "NONE": - return "NULL" - return f"'{str(val)}'" - - values_historical = ", ".join( - [ - f"({format_value(historical_fraction)}, {format_value(nico)}, {format_value(unit_measure)}, {format_value(country)}, " - f"{format_value(fraction_type)}, {format_value(sector)}, {format_value(import_tax)}, " - f"{format_value(export_tax)}, {format_timestamp(pub_date)}, {format_bool(is_immex)}, " - f"{format_bool(normal_temp)}, {format_bool(services_temp)}, {format_bool(certified_temp)}, " - f"{format_bool(by_log)}, {format_timestamp(end_date)}, " - f"{tenant_id}, {company_id})" - for (historical_fraction, nico, unit_measure, country, fraction_type, sector, import_tax, - export_tax, pub_date, is_immex, normal_temp, services_temp, certified_temp, by_log, end_date) in historical_tariff_fractions_seed - ] - ) - - if values_historical: - db.execute(text("SET session_replication_role = replica;")) - db.execute(text(f""" - INSERT INTO a76.historical_tariff_fractions - (historical_fraction, nico, unit_of_measure_code, country, fraction_type, sector, - import_tax_rate, export_tax_rate, publication_date, is_immex, - normal_temporality, services_temporality, certified_temporality, by_log, end_date, - tenant_id, company_id) - VALUES {values_historical} - ON CONFLICT DO NOTHING; - """)) - db.execute(text("SET session_replication_role = DEFAULT;")) - - # 3. Warning Fractions - values_warning = ", ".join( - [ - f"({format_value(fraction)}, {format_value(description)}, {format_value(warning_type)}, {tenant_id}, {company_id})" - for fraction, description, warning_type in warning_fractions_seed - ] - ) - - if values_warning: - db.execute(text("ALTER TABLE public.warning_fractions DISABLE TRIGGER ALL;")) - db.execute(text(f""" - INSERT INTO public.warning_fractions - (fraction, description, warning_type, tenant_id, company_id) - VALUES {values_warning} - ON CONFLICT (fraction, company_id) DO NOTHING; - """)) - db.execute(text("ALTER TABLE public.warning_fractions ENABLE TRIGGER ALL;")) - - # 4. Sectors - values_sectors = ", ".join( - [ - f"({format_value(key)}, {format_value(description)}, {str(authorized).upper()}, {tenant_id}, {company_id})" - for key, description, authorized in sectors_seed - ] - ) - - if values_sectors: - db.execute(text(f""" - INSERT INTO a76.sectors (key, description, authorized, tenant_id, company_id) - VALUES {values_sectors} - ON CONFLICT (key, tenant_id, company_id) DO NOTHING; - """)) - - # 5. Document Types for Digitization - values_doc_types = ", ".join( - [ - f"({format_value(code)}, {format_value(description)}, {tenant_id}, {company_id})" - for code, description in doc_types_dig_seed - ] - ) - if values_doc_types: - db.execute(text(f""" - INSERT INTO a76.document_types_digitization (code, description, tenant_id, company_id) - VALUES {values_doc_types} - ON CONFLICT (tenant_id, company_id, code) DO NOTHING; - """)) - - def get_companies_by_tenant(self, tenant_id: int) -> List[Company]: - """Get all companies for a tenant""" - return ( - self.db.query(Company) - .filter(Company.tenant_id == tenant_id, Company.deleted_at.is_(None)) - .order_by(Company.name) - .all() - ) - - def get_companies_for_session_user( - self, - keycloak_user_id: Optional[str], - tenant_id_from_token: Optional[int], - ) -> List[Company]: - """ - Compañías visibles para el usuario: unión de (a) todas las del tenant si el - token/Hub aporta tenant_id, y (b) compañías con membership RBAC o user_tenants. - - Permite usuarios sin tenant_id en el JWT pero con roles asignados en la app. - """ - from core.security import collect_company_ids_from_app_membership - - company_ids: Set[int] = set( - collect_company_ids_from_app_membership( - self.db, keycloak_user_id or "" - ) - ) - if tenant_id_from_token is not None: - for c in self.get_companies_by_tenant(int(tenant_id_from_token)): - company_ids.add(c.id) - if not company_ids: - return [] - return ( - self.db.query(Company) - .filter(Company.id.in_(company_ids), Company.deleted_at.is_(None)) - .order_by(Company.name) - .all() - ) - - def exists_company(self, tenant_id: int) -> bool: - """Check if a company exists for a tenant""" - return ( - self.db.query(Company) - .filter(Company.tenant_id == tenant_id, Company.deleted_at.is_(None)) - .first() - is not None - ) \ No newline at end of file diff --git a/backend/api/v1/modules/a76/general_catalogs/company/submodels/__init__.py b/backend/api/v1/modules/a76/general_catalogs/company/submodels/__init__.py deleted file mode 100644 index 8afa2f2b..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/company/submodels/__init__.py +++ /dev/null @@ -1,20 +0,0 @@ -""" -Submodelos de Company -""" -from .address import CompanyAddress -from .certification import CompanyCertification -from .digital_certificate import CompanyDigitalCertificate -from .vu import CompanyVU -from .electronic_agent import CompanyElectronicAgent -from .prevalidator import CompanyPrevalidator -from .cfdi import CompanyCFDI - -__all__ = [ - "CompanyAddress", - "CompanyCertification", - "CompanyDigitalCertificate", - "CompanyVU", - "CompanyElectronicAgent", - "CompanyPrevalidator", - "CompanyCFDI", -] \ No newline at end of file diff --git a/backend/api/v1/modules/a76/general_catalogs/company/submodels/address.py b/backend/api/v1/modules/a76/general_catalogs/company/submodels/address.py deleted file mode 100644 index 3ecfe266..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/company/submodels/address.py +++ /dev/null @@ -1,46 +0,0 @@ -""" -Modelo de direcciones de empresa -""" -from typing import Optional, TYPE_CHECKING -from sqlalchemy import Integer, String, ForeignKey -from sqlalchemy.orm import Mapped, mapped_column, relationship -from core.database import Base -from api.v1.common.base_models import TimestampMixin - -if TYPE_CHECKING: - from ..models import Company - - -class CompanyAddress(Base, TimestampMixin): - """Direcciones de la empresa (principal, industrial 1 y 2)""" - - __tablename__ = "company_address" - __table_args__ = {"schema": "a76"} - - id: Mapped[int] = mapped_column(Integer, primary_key=True) - company_id: Mapped[int] = mapped_column( - ForeignKey("a76.company.id", ondelete="CASCADE"), - nullable=False, - index=True - ) - address_type: Mapped[str] = mapped_column(String(20)) # 'main', 'industrial', 'industrial2' - - # Campos de dirección - street: Mapped[Optional[str]] = mapped_column(String(100)) - exterior_number: Mapped[Optional[str]] = mapped_column(String(20)) - interior_number: Mapped[Optional[str]] = mapped_column(String(20)) - postal_code: Mapped[Optional[str]] = mapped_column(String(15)) - neighborhood: Mapped[Optional[str]] = mapped_column(String(40)) - city: Mapped[Optional[str]] = mapped_column(String(40)) - municipality: Mapped[Optional[str]] = mapped_column(String(50)) - state: Mapped[Optional[str]] = mapped_column(String(30)) - country: Mapped[Optional[str]] = mapped_column(String(4)) - phone: Mapped[Optional[str]] = mapped_column(String(30)) - fax: Mapped[Optional[str]] = mapped_column(String(30)) - email: Mapped[Optional[str]] = mapped_column(String(100)) - - # Relación inversa - company: Mapped["Company"] = relationship( - "Company", - back_populates="addresses" - ) \ No newline at end of file diff --git a/backend/api/v1/modules/a76/general_catalogs/company/submodels/certification.py b/backend/api/v1/modules/a76/general_catalogs/company/submodels/certification.py deleted file mode 100644 index 088071e5..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/company/submodels/certification.py +++ /dev/null @@ -1,55 +0,0 @@ -""" -Modelo de certificaciones de empresa -""" -from typing import Optional, TYPE_CHECKING -from datetime import date - -from sqlalchemy import String, ForeignKey, Boolean, Date, Integer -from sqlalchemy.orm import Mapped, mapped_column, relationship -from core.database import Base -from api.v1.common.base_models import TimestampMixin - -if TYPE_CHECKING: - from ..models import Company - - -class CompanyCertification(Base, TimestampMixin): - """Certificaciones de la empresa""" - - __tablename__ = "company_certification" - __table_args__ = {"schema": "a76"} - - id: Mapped[int] = mapped_column(Integer, primary_key=True) - company_id: Mapped[int] = mapped_column( - ForeignKey("a76.company.id", ondelete="CASCADE"), - nullable=False, - unique=True, - index=True - ) - - # Certificación general - is_certified_company: Mapped[Optional[bool]] = mapped_column(Boolean) - certified_company_registration: Mapped[Optional[str]] = mapped_column(String(40)) - certified_company_start_date: Mapped[Optional[date]] = mapped_column(Date) - certified_company_end_date: Mapped[Optional[date]] = mapped_column(Date) - - # Anexo 30 - annex30_certification_date: Mapped[Optional[date]] = mapped_column(Date) - annex30_certification_number: Mapped[Optional[str]] = mapped_column(String(50)) - annex30_modality: Mapped[Optional[str]] = mapped_column(String(3)) - annex30_company_type: Mapped[Optional[str]] = mapped_column(String(50)) - annex30_renewal_date: Mapped[Optional[date]] = mapped_column(Date) - annex30_final_certification_date: Mapped[Optional[date]] = mapped_column(Date) - - # Otras certificaciones - is_seciit_company: Mapped[Optional[bool]] = mapped_column(Boolean) - is_oea_company: Mapped[Optional[bool]] = mapped_column(Boolean) - ctpat_svi: Mapped[Optional[str]] = mapped_column(String(100)) - trusted_exporter_number: Mapped[Optional[str]] = mapped_column(String(50)) - neec_company: Mapped[Optional[bool]] = mapped_column(Boolean) - - # Relación inversa - company: Mapped["Company"] = relationship( - "Company", - back_populates="certification" - ) \ No newline at end of file diff --git a/backend/api/v1/modules/a76/general_catalogs/company/submodels/cfdi.py b/backend/api/v1/modules/a76/general_catalogs/company/submodels/cfdi.py deleted file mode 100644 index f5e9d48f..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/company/submodels/cfdi.py +++ /dev/null @@ -1,37 +0,0 @@ -""" -Modelo de configuración de CFDI (Facturación Electrónica) -""" -from typing import Optional, TYPE_CHECKING -from sqlalchemy import Integer, String, ForeignKey -from sqlalchemy.orm import Mapped, mapped_column, relationship -from core.database import Base -from api.v1.common.base_models import TimestampMixin - -if TYPE_CHECKING: - from ..models import Company - - -class CompanyCFDI(Base, TimestampMixin): - """Configuración de Facturación Electrónica (CFDI)""" - - __tablename__ = "company_cfdi" - __table_args__ = {"schema": "a76"} - - id: Mapped[int] = mapped_column(Integer, primary_key=True) - company_id: Mapped[int] = mapped_column( - ForeignKey("a76.company.id", ondelete="CASCADE"), - nullable=False, - unique=True, - index=True - ) - - # Rutas de aplicaciones y archivos - xml_save_path: Mapped[Optional[str]] = mapped_column(String(5000)) - cfdi_app_path: Mapped[Optional[str]] = mapped_column(String(5000)) - pac_app_path: Mapped[Optional[str]] = mapped_column(String(5000)) - - # Relación inversa - company: Mapped["Company"] = relationship( - "Company", - back_populates="cfdi" - ) \ No newline at end of file diff --git a/backend/api/v1/modules/a76/general_catalogs/company/submodels/digital_certificate.py b/backend/api/v1/modules/a76/general_catalogs/company/submodels/digital_certificate.py deleted file mode 100644 index 6de413b9..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/company/submodels/digital_certificate.py +++ /dev/null @@ -1,44 +0,0 @@ -""" -Modelo de certificados digitales (FIEL, CFDI, Cancelación) -""" -from typing import Optional, TYPE_CHECKING -from sqlalchemy import Integer, String, ForeignKey -from sqlalchemy.orm import Mapped, mapped_column, relationship -from core.database import Base -from api.v1.common.base_models import TimestampMixin - -if TYPE_CHECKING: - from ..models import Company - - -class CompanyDigitalCertificate(Base, TimestampMixin): - """Certificados digitales (FIEL, CFDI)""" - - __tablename__ = "company_digital_certificate" - __table_args__ = {"schema": "a76"} - - id: Mapped[int] = mapped_column(Integer, primary_key=True) - company_id: Mapped[int] = mapped_column( - ForeignKey("a76.company.id", ondelete="CASCADE"), - nullable=False, - index=True - ) - certificate_type: Mapped[str] = mapped_column(String(20)) # 'fiel', 'cfdi', 'cancellation' - - # Rutas de archivos - cer_file_path: Mapped[Optional[str]] = mapped_column(String(5000)) - key_file_path: Mapped[Optional[str]] = mapped_column(String(5000)) - - # Contraseñas y claves - password: Mapped[Optional[str]] = mapped_column(String(200)) - access_key: Mapped[Optional[str]] = mapped_column(String(50)) - - # Fechas de expiración - cer_expiration_date: Mapped[Optional[int]] = mapped_column(Integer) - key_expiration_date: Mapped[Optional[int]] = mapped_column(Integer) - - # Relación inversa - company: Mapped["Company"] = relationship( - "Company", - back_populates="digital_certificates" - ) \ No newline at end of file diff --git a/backend/api/v1/modules/a76/general_catalogs/company/submodels/electronic_agent.py b/backend/api/v1/modules/a76/general_catalogs/company/submodels/electronic_agent.py deleted file mode 100644 index d57c9c0c..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/company/submodels/electronic_agent.py +++ /dev/null @@ -1,46 +0,0 @@ -""" -Modelo de configuración del Agente Aduanal Electrónico -""" -from typing import Optional, TYPE_CHECKING -from sqlalchemy import Integer, String, ForeignKey -from sqlalchemy.orm import Mapped, mapped_column, relationship -from core.database import Base -from api.v1.common.base_models import TimestampMixin - -if TYPE_CHECKING: - from ..models import Company - - -class CompanyElectronicAgent(Base, TimestampMixin): - """Configuración de Agente Aduanal Electrónico""" - - __tablename__ = "company_electronic_agent" - __table_args__ = {"schema": "a76"} - - id: Mapped[int] = mapped_column(Integer, primary_key=True) - company_id: Mapped[int] = mapped_column( - ForeignKey("a76.company.id", ondelete="CASCADE"), - nullable=False, - unique=True, - index=True - ) - - # Carpetas - input_folder: Mapped[Optional[str]] = mapped_column(String(1000)) - output_folder: Mapped[Optional[str]] = mapped_column(String(1000)) - - # Máscaras y extensiones - send_mask: Mapped[Optional[str]] = mapped_column(String(20)) - response_mask: Mapped[Optional[str]] = mapped_column(String(20)) - response_extension: Mapped[Optional[str]] = mapped_column(String(20)) - - # Contadores - counter_start: Mapped[Optional[int]] = mapped_column(Integer) - counter_end: Mapped[Optional[int]] = mapped_column(Integer) - counter_next: Mapped[Optional[int]] = mapped_column(Integer) - - # Relación inversa - company: Mapped["Company"] = relationship( - "Company", - back_populates="electronic_agent" - ) \ No newline at end of file diff --git a/backend/api/v1/modules/a76/general_catalogs/company/submodels/prevalidator.py b/backend/api/v1/modules/a76/general_catalogs/company/submodels/prevalidator.py deleted file mode 100644 index 05da674b..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/company/submodels/prevalidator.py +++ /dev/null @@ -1,38 +0,0 @@ -""" -Modelo de configuración del Prevalidador Aduanal -""" -from typing import Optional, TYPE_CHECKING -from sqlalchemy import Integer, String, ForeignKey -from sqlalchemy.orm import Mapped, mapped_column, relationship -from core.database import Base -from api.v1.common.base_models import TimestampMixin - -if TYPE_CHECKING: - from ..models import Company - - -class CompanyPrevalidator(Base, TimestampMixin): - """Configuración de Prevalidador Aduanal""" - - __tablename__ = "company_prevalidator" - __table_args__ = {"schema": "a76"} - - id: Mapped[int] = mapped_column(Integer, primary_key=True) - company_id: Mapped[int] = mapped_column( - ForeignKey("a76.company.id", ondelete="CASCADE"), - nullable=False, - unique=True, - index=True - ) - - # Información del prevalidador - customs: Mapped[Optional[str]] = mapped_column(String(20)) - key: Mapped[Optional[str]] = mapped_column(String(20)) - patent: Mapped[Optional[str]] = mapped_column(String(4)) - description: Mapped[Optional[str]] = mapped_column(String(100)) - - # Relación inversa - company: Mapped["Company"] = relationship( - "Company", - back_populates="prevalidator" - ) \ No newline at end of file diff --git a/backend/api/v1/modules/a76/general_catalogs/company/submodels/vu.py b/backend/api/v1/modules/a76/general_catalogs/company/submodels/vu.py deleted file mode 100644 index d094e598..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/company/submodels/vu.py +++ /dev/null @@ -1,50 +0,0 @@ -""" -Modelo de configuración de Ventanilla Única -""" -from typing import Optional, TYPE_CHECKING -from sqlalchemy import Integer, String, ForeignKey -from sqlalchemy.orm import Mapped, mapped_column, relationship -from core.database import Base -from api.v1.common.base_models import TimestampMixin - -if TYPE_CHECKING: - from ..models import Company - - -class CompanyVU(Base, TimestampMixin): - """Configuración de Ventanilla Única""" - - __tablename__ = "CompanyVU" - __table_args__ = {"schema": "a76"} - - id: Mapped[int] = mapped_column(Integer, primary_key=True) - company_id: Mapped[int] = mapped_column( - ForeignKey("a76.company.id", ondelete="CASCADE"), - nullable=False, - unique=True, - index=True - ) - - # Credenciales - webservice_user: Mapped[Optional[str]] = mapped_column(String(100)) - webservice_password: Mapped[Optional[str]] = mapped_column(String(100)) - email: Mapped[Optional[str]] = mapped_column(String(800)) - - # Configuración - figure_type: Mapped[Optional[str]] = mapped_column(String(29)) - central_path: Mapped[Optional[str]] = mapped_column(String(1499)) - xml_files_path: Mapped[Optional[str]] = mapped_column(String(1499)) - - # RFC y validación - query_rfc: Mapped[Optional[str]] = mapped_column(String(30)) - validation_rfc: Mapped[Optional[str]] = mapped_column(String(30)) - - # Otras configuraciones - configuration_source: Mapped[Optional[str]] = mapped_column(String(30)) - measurement_units: Mapped[Optional[str]] = mapped_column(String(3)) - - # Relación inversa - company: Mapped["Company"] = relationship( - "Company", - back_populates="ventanilla_unica" - ) \ No newline at end of file diff --git a/backend/api/v1/modules/a76/general_catalogs/company/test_company.py b/backend/api/v1/modules/a76/general_catalogs/company/test_company.py deleted file mode 100644 index 0b718bbd..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/company/test_company.py +++ /dev/null @@ -1,36 +0,0 @@ -import pytest -from fastapi import FastAPI -from fastapi.testclient import TestClient - -from .routes import router - -app = FastAPI() -app.include_router(router) -client = TestClient(app) - - -@pytest.mark.usefixtures("client", "access_token") -def test_list_companies(client, access_token): - headers = {"Authorization": f"Bearer {access_token}"} - response = client.get("/company/", headers=headers) - assert response.status_code == 200 - assert "items" in response.json() - assert "page" in response.json() - assert "page_size" in response.json() - - -@pytest.mark.usefixtures("client", "access_token") -def test_get_company_not_found(client, access_token): - headers = {"Authorization": f"Bearer {access_token}"} - response = client.get("/company/invalid_id", headers=headers) - assert response.status_code == 404 - - -def test_create_company_forbidden(): - response = client.post("/company/", json={"name": "Test Company"}) - assert response.status_code in (403, 405, 404) - - -def test_update_company_forbidden(): - response = client.put("/company/1", json={"name": "Updated Company"}) - assert response.status_code in (403, 405, 404) diff --git a/backend/api/v1/modules/a76/general_catalogs/concepts/__init__.py b/backend/api/v1/modules/a76/general_catalogs/concepts/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/backend/api/v1/modules/a76/general_catalogs/concepts/dto.py b/backend/api/v1/modules/a76/general_catalogs/concepts/dto.py deleted file mode 100644 index e8c030e0..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/concepts/dto.py +++ /dev/null @@ -1,46 +0,0 @@ -from typing import Optional -from pydantic import BaseModel, Field, ConfigDict - - -class ConceptBase(BaseModel): - code: str = Field(..., max_length=15, description="Concept Code (CLAVE)") - company_id: int = Field(..., description="Company ID") - description: Optional[str] = Field( - None, max_length=120, description="Description") - description_en: Optional[str] = Field( - None, max_length=120, description="English Description") - detailed_description: Optional[str] = Field( - None, max_length=1000, description="Detailed Description") - priority: Optional[int] = Field(None, description="Priority") - priority_ame: Optional[int] = Field(None, description="American Priority") - first_total: Optional[bool] = Field(None, description="First Total") - type: Optional[str] = Field(None, max_length=9, description="Type") - is_printed: Optional[bool] = Field(None, description="Is Printed") - section: Optional[int] = Field(None, description="Section") - classification: Optional[str] = Field( - None, max_length=30, description="Classification") - - -class ConceptCreate(ConceptBase): - pass - - -class ConceptUpdate(BaseModel): - code: Optional[str] = Field(None, max_length=15) - description: Optional[str] = Field(None, max_length=120) - description_en: Optional[str] = Field(None, max_length=120) - detailed_description: Optional[str] = Field(None, max_length=1000) - priority: Optional[int] = None - priority_ame: Optional[int] = None - first_total: Optional[bool] = None - type: Optional[str] = Field(None, max_length=9) - is_printed: Optional[bool] = None - section: Optional[int] = None - classification: Optional[str] = Field(None, max_length=30) - - -class ConceptResponse(ConceptBase): - id: int - tenant_id: int - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/a76/general_catalogs/concepts/models.py b/backend/api/v1/modules/a76/general_catalogs/concepts/models.py deleted file mode 100644 index d3824d1d..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/concepts/models.py +++ /dev/null @@ -1,58 +0,0 @@ -from typing import Optional -from sqlalchemy import Integer, String, UniqueConstraint, Boolean, ForeignKey -from sqlalchemy.orm import Mapped, mapped_column, relationship -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base -from typing import TYPE_CHECKING - -if TYPE_CHECKING: - from api.v1.modules.a76.general_catalogs.classification_concepts.models import ClassificationConcept - - -class Concept(Base, TenantScopedMixin, TimestampMixin): - __tablename__ = "concepts" - __table_args__ = ( - UniqueConstraint("code", name="uq_concept_code"), - {"schema": "a76"} - ) - - id: Mapped[int] = mapped_column( - Integer, primary_key=True, autoincrement=True) - - company_id: Mapped[int] = mapped_column( - Integer, nullable=False) # IDEMPRESA - - code: Mapped[str] = mapped_column(String(15), nullable=False) # CLAVE - - description: Mapped[Optional[str]] = mapped_column( - String(120), nullable=True) # DESCRIPCION - - description_en: Mapped[Optional[str]] = mapped_column( - String(120), nullable=True) # DESCRIPCIONINGLES - - detailed_description: Mapped[Optional[str]] = mapped_column( - String(1000), nullable=True) # DESCRIPCIONDETALLADA - - priority: Mapped[Optional[int]] = mapped_column( - Integer, nullable=True) # PRIORIDAD - - priority_ame: Mapped[Optional[int]] = mapped_column( - Integer, nullable=True) # PRIORIDADAME - - first_total: Mapped[Optional[bool]] = mapped_column( - Boolean, nullable=True) # PRIMERTOTAL - - type: Mapped[Optional[str]] = mapped_column( - String(9), nullable=True) # TIPO - - is_printed: Mapped[Optional[bool]] = mapped_column( - Boolean, nullable=True) # SEIMPRIME - - section: Mapped[Optional[int]] = mapped_column( - Integer, nullable=True) # SECCION - - classification: Mapped[Optional[str]] = mapped_column(String(30), ForeignKey( - "a76.classification_concepts.classification"), nullable=True) # CLASIFICACION - - classification_info: Mapped[Optional["ClassificationConcept"]] = relationship( - foreign_keys=[classification]) diff --git a/backend/api/v1/modules/a76/general_catalogs/concepts/routes.py b/backend/api/v1/modules/a76/general_catalogs/concepts/routes.py deleted file mode 100644 index dda46e4b..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/concepts/routes.py +++ /dev/null @@ -1,20 +0,0 @@ -from api.v1.common.tenant_crud_routes import TenantCRUDRoutes -from .dto import ConceptCreate, ConceptResponse, ConceptUpdate -from .service import ConceptService - -router = TenantCRUDRoutes( - service=ConceptService, - create_schema=ConceptCreate, - update_schema=ConceptUpdate, - response_schema=ConceptResponse, - prefix="/concepts", - tags=["a76.general_catalogs.concepts"], - resource_name="Concept", - enable_list=True, - enable_filters=True, - list_permissions=["cat_concepts.view"], - get_permissions=["cat_concepts.view"], - create_permissions=["cat_concepts.create"], - update_permissions=["cat_concepts.edit"], - delete_permissions=["cat_concepts.delete"], -).router diff --git a/backend/api/v1/modules/a76/general_catalogs/concepts/service.py b/backend/api/v1/modules/a76/general_catalogs/concepts/service.py deleted file mode 100644 index 4e50fc62..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/concepts/service.py +++ /dev/null @@ -1,107 +0,0 @@ -from typing import List, Optional, Tuple, Dict, Any -import logging - -from sqlalchemy.orm import Session -from sqlalchemy import select -from sqlalchemy.exc import IntegrityError -from fastapi import HTTPException - -from .models import Concept -from .dto import ConceptCreate, ConceptUpdate - -logger = logging.getLogger(__name__) - - -class ConceptService: - @staticmethod - def get_all( - db: Session, - tenant_id: int, - company_id: int, - skip: int = 0, - limit: int = 100, - filters: Optional[Dict[str, Any]] = None, - ) -> Tuple[List[Concept], int]: - query = db.query(Concept).filter( - Concept.tenant_id == tenant_id, - Concept.company_id == company_id - ) - - if filters: - if filters.get("code"): - query = query.filter(Concept.code.ilike(f"%{filters['code']}%")) - if filters.get("description"): - query = query.filter( - Concept.description.ilike(f"%{filters['description']}%") - ) - - total = query.count() - items = query.offset(skip).limit(limit).all() - - return items, total - - @staticmethod - def get_by_id( - db: Session, id: int, tenant_id: int, company_id: int - ) -> Optional[Concept]: - return db.query(Concept).filter( - Concept.id == id, - Concept.tenant_id == tenant_id, - Concept.company_id == company_id - ).first() - - @staticmethod - def create( - db: Session, data: ConceptCreate, tenant_id: int, company_id: int - ) -> Concept: - data_dict = data.model_dump() - data_dict['company_id'] = company_id - data_dict['tenant_id'] = tenant_id - - db_obj = Concept(**data_dict) - db.add(db_obj) - db.commit() - db.refresh(db_obj) - return db_obj - - @staticmethod - def update( - db: Session, id: int, tenant_id: int, data: ConceptUpdate, company_id: int - ) -> Optional[Concept]: - db_obj = ConceptService.get_by_id(db, id, tenant_id, company_id) - if not db_obj: - return None - - update_dict = data.model_dump(exclude_unset=True) - for key, value in update_dict.items(): - setattr(db_obj, key, value) - - db.commit() - db.refresh(db_obj) - return db_obj - - @staticmethod - def delete( - db: Session, id: int, tenant_id: int, company_id: int - ) -> bool: - db_obj = ConceptService.get_by_id(db, id, tenant_id, company_id) - if not db_obj: - return False - - try: - db.delete(db_obj) - db.commit() - return True - except IntegrityError as e: - db.rollback() - logger.error(f"IntegrityError deleting concept {id}: {str(e)}") - if "foreign key constraint" in str(e).lower(): - raise HTTPException( - status_code=400, - detail="No se puede eliminar el concepto porque tiene registros relacionados" - ) - raise HTTPException(status_code=400, detail="Error al eliminar el concepto") - except Exception as e: - db.rollback() - logger.error(f"Error deleting concept {id}: {str(e)}") - raise HTTPException(status_code=500, detail="Error al eliminar el concepto") diff --git a/backend/api/v1/modules/a76/general_catalogs/customs_broker_concepts/__init__.py b/backend/api/v1/modules/a76/general_catalogs/customs_broker_concepts/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/backend/api/v1/modules/a76/general_catalogs/customs_broker_concepts/dto.py b/backend/api/v1/modules/a76/general_catalogs/customs_broker_concepts/dto.py deleted file mode 100644 index 3c545d44..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/customs_broker_concepts/dto.py +++ /dev/null @@ -1,40 +0,0 @@ -""" -DTOs for Customs Broker Concepts. -""" - -from datetime import datetime -from typing import Optional - -from pydantic import BaseModel, Field, ConfigDict - - -class CustomsBrokerConceptBase(BaseModel): - broker_key: str = Field(..., max_length=5, description="Customs Broker Key (CLAVEAA)") - concept: str = Field(..., max_length=15, description="Concept/Code") - amount: Optional[float] = Field(None, description="Amount (IMPORTE)") - priority: Optional[int] = Field(None, description="Priority (PRIORIDAD)") - - -class CustomsBrokerConceptCreate(CustomsBrokerConceptBase): - """Schema for creating a concept""" - pass - - -class CustomsBrokerConceptUpdate(BaseModel): - """Schema for updating a concept""" - broker_key: Optional[str] = Field(None, max_length=5, description="Customs Broker Key (CLAVEAA)") - concept: Optional[str] = Field(None, max_length=15, description="Concept/Code") - amount: Optional[float] = Field(None, description="Amount (IMPORTE)") - priority: Optional[int] = Field(None, description="Priority (PRIORIDAD)") - - -class CustomsBrokerConceptResponse(CustomsBrokerConceptBase): - """Schema for concept response""" - id: int - company_id: int - tenant_id: int - created_at: Optional[datetime] = None - updated_at: Optional[datetime] = None - - model_config = ConfigDict(from_attributes=True) - diff --git a/backend/api/v1/modules/a76/general_catalogs/customs_broker_concepts/models.py b/backend/api/v1/modules/a76/general_catalogs/customs_broker_concepts/models.py deleted file mode 100644 index 301e8d48..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/customs_broker_concepts/models.py +++ /dev/null @@ -1,31 +0,0 @@ -from typing import Optional -from decimal import Decimal -from sqlalchemy import Integer, String, UniqueConstraint, Numeric -from sqlalchemy.orm import Mapped, mapped_column -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base - - -class CustomsBrokerConcept(Base, TenantScopedMixin, TimestampMixin): - __tablename__ = "customs_broker_concepts" - __table_args__ = ( - UniqueConstraint("broker_key", "concept", "company_id", name="uq_broker_concept"), - {"schema": "a76"} - ) - - id: Mapped[int] = mapped_column( - Integer, primary_key=True, autoincrement=True) - - company_id: Mapped[int] = mapped_column(Integer, nullable=False) - - broker_key: Mapped[str] = mapped_column( - String(5), nullable=False) # CLAVEAA - - concept: Mapped[str] = mapped_column( - String(15), nullable=False) # CONCEPTO - - amount: Mapped[Optional[Decimal]] = mapped_column( - Numeric(11, 2), nullable=True) # IMPORTE - - priority: Mapped[Optional[int]] = mapped_column( - Integer, nullable=True) # PRIORIDAD diff --git a/backend/api/v1/modules/a76/general_catalogs/customs_broker_concepts/routes.py b/backend/api/v1/modules/a76/general_catalogs/customs_broker_concepts/routes.py deleted file mode 100644 index 06396629..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/customs_broker_concepts/routes.py +++ /dev/null @@ -1,31 +0,0 @@ -""" -Routes for managing Customs Broker Concepts. -""" - -from api.v1.common.tenant_crud_routes import TenantCRUDRoutes - -from .dto import CustomsBrokerConceptCreate, CustomsBrokerConceptResponse, CustomsBrokerConceptUpdate -from .service import CustomsBrokerConceptService - -# Create router using TenantCRUDRoutes factory -router = TenantCRUDRoutes( - service=CustomsBrokerConceptService, - create_schema=CustomsBrokerConceptCreate, - update_schema=CustomsBrokerConceptUpdate, - response_schema=CustomsBrokerConceptResponse, - prefix="/customs-broker-concepts", - tags=["a76 / customs_broker_concepts"], - resource_name="Customs Broker Concept", - id_name="concept_id", - enable_list=True, # Enable GET /customs-broker-concepts with pagination - enable_filters=True, # Enable filtering - default_page_size=50, - max_page_size=100, - list_permissions=["cat_broker_concepts.view"], - get_permissions=["cat_broker_concepts.view"], - create_permissions=["cat_broker_concepts.create"], - update_permissions=["cat_broker_concepts.edit"], - delete_permissions=["cat_broker_concepts.delete"], -).router - - diff --git a/backend/api/v1/modules/a76/general_catalogs/customs_broker_concepts/service.py b/backend/api/v1/modules/a76/general_catalogs/customs_broker_concepts/service.py deleted file mode 100644 index 4e60f61a..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/customs_broker_concepts/service.py +++ /dev/null @@ -1,132 +0,0 @@ -""" -Service layer for Customs Broker Concepts. -""" - -from typing import Optional, Tuple, List, Dict, Any -import logging - -from sqlalchemy.orm import Session -from sqlalchemy.exc import IntegrityError -from fastapi import HTTPException - -from . import dto, models - -logger = logging.getLogger(__name__) - - -class CustomsBrokerConceptService: - """Service for Customs Broker Concept CRUD operations with tenant support""" - - @staticmethod - def get_all( - db: Session, - tenant_id: int, - company_id: int, - skip: int = 0, - limit: int = 50, - filters: Optional[Dict[str, Any]] = None, - ) -> Tuple[List[models.CustomsBrokerConcept], int]: - """Get all customs broker concepts for a tenant/company with pagination""" - query = db.query(models.CustomsBrokerConcept).filter( - models.CustomsBrokerConcept.tenant_id == tenant_id, - models.CustomsBrokerConcept.company_id == company_id, - ) - - # Apply filters if provided - if filters: - if filters.get("broker_key"): - query = query.filter( - models.CustomsBrokerConcept.broker_key.ilike(f"%{filters['broker_key']}%") - ) - if filters.get("concept"): - query = query.filter( - models.CustomsBrokerConcept.concept.ilike( - f"%{filters['concept']}%") - ) - - total = query.count() - concepts = query.offset(skip).limit(limit).all() - - return concepts, total - - @staticmethod - def get_by_id( - db: Session, concept_id: int, tenant_id: int, company_id: int - ) -> Optional[models.CustomsBrokerConcept]: - """Get customs broker concept by ID""" - return ( - db.query(models.CustomsBrokerConcept) - .filter( - models.CustomsBrokerConcept.id == concept_id, - models.CustomsBrokerConcept.tenant_id == tenant_id, - models.CustomsBrokerConcept.company_id == company_id, - ) - .first() - ) - - @staticmethod - def create( - db: Session, - concept_data: dto.CustomsBrokerConceptCreate, - tenant_id: int, - company_id: int, - ) -> models.CustomsBrokerConcept: - """Create a new customs broker concept""" - new_concept = models.CustomsBrokerConcept( - **concept_data.model_dump(), tenant_id=tenant_id, company_id=company_id - ) - db.add(new_concept) - db.commit() - db.refresh(new_concept) - return new_concept - - @staticmethod - def update( - db: Session, - concept_id: int, - tenant_id: int, - concept_data: dto.CustomsBrokerConceptUpdate, - company_id: int, - ) -> Optional[models.CustomsBrokerConcept]: - """Update a customs broker concept""" - concept = CustomsBrokerConceptService.get_by_id( - db, concept_id, tenant_id, company_id) - if not concept: - return None - - # Update fields - update_data = concept_data.model_dump(exclude_unset=True) - for field, value in update_data.items(): - setattr(concept, field, value) - - db.commit() - db.refresh(concept) - return concept - - @staticmethod - def delete( - db: Session, concept_id: int, tenant_id: int, company_id: int - ) -> bool: - """Delete a customs broker concept""" - concept = CustomsBrokerConceptService.get_by_id( - db, concept_id, tenant_id, company_id) - if not concept: - return False - - try: - db.delete(concept) - db.commit() - return True - except IntegrityError as e: - db.rollback() - logger.error(f"IntegrityError deleting concept {concept_id}: {str(e)}") - if "foreign key constraint" in str(e).lower(): - raise HTTPException( - status_code=400, - detail="No se puede eliminar el concepto porque tiene registros relacionados" - ) - raise HTTPException(status_code=400, detail="Error al eliminar el concepto") - except Exception as e: - db.rollback() - logger.error(f"Error deleting concept {concept_id}: {str(e)}") - raise HTTPException(status_code=500, detail="Error al eliminar el concepto") diff --git a/backend/api/v1/modules/a76/general_catalogs/depreciation_catalog/__init__.py b/backend/api/v1/modules/a76/general_catalogs/depreciation_catalog/__init__.py deleted file mode 100644 index e8582b1e..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/depreciation_catalog/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -""" -Depreciation Catalog Module -""" diff --git a/backend/api/v1/modules/a76/general_catalogs/depreciation_catalog/dto.py b/backend/api/v1/modules/a76/general_catalogs/depreciation_catalog/dto.py deleted file mode 100644 index 6885588e..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/depreciation_catalog/dto.py +++ /dev/null @@ -1,38 +0,0 @@ -""" -Depreciation Catalog DTOs -""" - -from pydantic import BaseModel, Field, ConfigDict -from datetime import datetime - - -class DepreciationCatalogCreate(BaseModel): - """DTO for creating a depreciation catalog entry""" - fraction: str = Field(..., max_length=10) - description: str = Field(..., max_length=500) - depreciation_rate: float = Field(..., ge=0, le=100) - - model_config = ConfigDict(from_attributes=True) - - -class DepreciationCatalogUpdate(BaseModel): - """DTO for updating a depreciation catalog entry""" - fraction: str | None = Field(None, max_length=10) - description: str | None = Field(None, max_length=500) - depreciation_rate: float | None = Field(None, ge=0, le=100) - - model_config = ConfigDict(from_attributes=True) - - -class DepreciationCatalogResponse(BaseModel): - """DTO for depreciation catalog response""" - id: int - tenant_id: int - company_id: int - fraction: str - description: str - depreciation_rate: float - created_at: datetime - updated_at: datetime - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/a76/general_catalogs/depreciation_catalog/models.py b/backend/api/v1/modules/a76/general_catalogs/depreciation_catalog/models.py deleted file mode 100644 index d682636a..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/depreciation_catalog/models.py +++ /dev/null @@ -1,25 +0,0 @@ -""" -Depreciation Catalog Model -""" - -from sqlalchemy import Column, Integer, String, Numeric, ForeignKey, PrimaryKeyConstraint -from sqlalchemy.orm import Mapped, mapped_column, relationship -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base - - -class DepreciationCatalog(Base, TenantScopedMixin, TimestampMixin): - """Depreciation Catalog Model""" - - __tablename__ = 'depreciation_catalog' - __table_args__ = ( - PrimaryKeyConstraint("id", name="depreciation_catalog_pkey"), - {'schema': 'a76'} - ) - - id: Mapped[int] = mapped_column(Integer, primary_key=True) - - # Fields - fraction: Mapped[str] = mapped_column(String(10), nullable=False, index=True) - description: Mapped[str] = mapped_column(String(500), nullable=False, index=True) - depreciation_rate: Mapped[float] = mapped_column(Numeric(5, 2), nullable=False) diff --git a/backend/api/v1/modules/a76/general_catalogs/depreciation_catalog/routes.py b/backend/api/v1/modules/a76/general_catalogs/depreciation_catalog/routes.py deleted file mode 100644 index 56f5086a..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/depreciation_catalog/routes.py +++ /dev/null @@ -1,73 +0,0 @@ -""" -Depreciation Catalog Routes -""" - -from fastapi import APIRouter, Depends, Query -from sqlalchemy.orm import Session -from typing import Any, Dict, Optional - -from core.database import get_core_db -from core.security import get_current_user -from api.v1.common.tenant_crud_routes import TenantCRUDRoutes, validate_access_to_resource -from api.v1.modules.a76.general_catalogs.depreciation_catalog.service import DepreciationCatalogService -from api.v1.modules.a76.general_catalogs.depreciation_catalog.dto import ( - DepreciationCatalogCreate, - DepreciationCatalogUpdate, - DepreciationCatalogResponse -) - -# Create base router with generic CRUD routes (disabled list because we'll create a custom one) -base_router = TenantCRUDRoutes( - service=DepreciationCatalogService, - create_schema=DepreciationCatalogCreate, - update_schema=DepreciationCatalogUpdate, - response_schema=DepreciationCatalogResponse, - prefix="/depreciation-catalog", - tags=["a76 / general catalogs / depreciation catalog"], - resource_name="DepreciationCatalog", - id_name="depreciation_catalog_id", - enable_list=False, # Disable default list, we'll add custom one - enable_filters=False, - default_page_size=100, - max_page_size=1000, - get_permissions=["cat_depreciation_catalog.view"], - create_permissions=["cat_depreciation_catalog.create"], - update_permissions=["cat_depreciation_catalog.edit"], - delete_permissions=["cat_depreciation_catalog.delete"], -) - -router = APIRouter(prefix="/depreciation-catalog", tags=["a76 / general catalogs / depreciation catalog"]) - -# Custom list endpoint with search filter -@router.get( - "/", - response_model=Dict[str, Any], - summary="List Depreciation Catalog", - description="Get paginated list of Depreciation Catalog entries with optional search filter", -) -async def list_depreciation_catalog( - company_id: int = Query(..., description="Company ID"), - page: int = Query(1, ge=1, description="Page number"), - page_size: int = Query(100, ge=1, le=1000, description="Page size"), - search: Optional[str] = Query(None, description="Search in fraction or description"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - tenant_id = validate_access_to_resource( - db, company_id, current_user, ["cat_depreciation_catalog.view"] - ) - - items, total = DepreciationCatalogService.get_all( - db, tenant_id, company_id, page, page_size, search - ) - - return { - "items": [DepreciationCatalogResponse.model_validate(item) for item in items], - "total": total, - "page": page, - "page_size": page_size, - "pages": (total + page_size - 1) // page_size, - } - -# Include other CRUD routes from base router -router.include_router(base_router.router) diff --git a/backend/api/v1/modules/a76/general_catalogs/depreciation_catalog/seed.py b/backend/api/v1/modules/a76/general_catalogs/depreciation_catalog/seed.py deleted file mode 100644 index 40f098f9..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/depreciation_catalog/seed.py +++ /dev/null @@ -1,34 +0,0 @@ -""" -Datos semilla para Catálogo de Depreciación -Basado en artículo 34 de la Ley del Impuesto Sobre la Renta -""" - -from decimal import Decimal - -# Formato: (fraction, description, depreciation_rate) -seed = [ - ("I", "a) Para inmuebles declarados como monumentos arqueológicos, artísticos, históricos o patrimoniales, conforme a la Ley Federal sobre Monumentos y Zonas Arqueológicos, Artísticos e Históricos, que cuenten con el certificado de restauración expedido por el Instituto Nacional de Antropología e Historia o el Instituto Nacional de Bellas Artes.", Decimal("10.00")), - ("I", "b) En los demás casos.", Decimal("5.00")), - ("II", "a) Para bombas de suministro de combustible a trenes.", Decimal("3.00")), - ("II", "b) Para vías férreas.", Decimal("5.00")), - ("II", "c) Para carros de ferrocarril, locomotoras, armones y autoarmones.", Decimal("6.00")), - ("II", "d) Para maquinaria niveladora de vías, desclavadoras, esmeriles para vías, gatos de motor para levantar la vía, removedora, insertadora y taladradora de durmientes.", Decimal("7.00")), - ("II", "e) Para el equipo de comunicación, señalización y telemando.", Decimal("10.00")), - ("III", "Para mobiliario y equipo de oficina.", Decimal("10.00")), - ("IV", "Para embarcaciones.", Decimal("6.00")), - ("IX", "Para semovientes y vegetales.", Decimal("100.00")), - ("V", "a) Para los dedicados a la aerofumigación agrícola.", Decimal("25.00")), - ("V", "b) Para los demás.", Decimal("10.00")), - ("VI", "Para automóviles, autobuses, camiones de carga, tractocamiones, montacargas y remolques.", Decimal("25.00")), - ("VII", "Para computadoras personales de escritorio y portátiles; servidores; impresoras, lectores ópticos, graficadores, lectores de código de barras, digitalizadores, unidades de almacenamiento externo y concentradores de redes de cómputo.", Decimal("30.00")), - ("VIII", "Para dados, troqueles, moldes, matrices y herramental.", Decimal("35.00")), - ("X", "a) Para torres de transmisión y cables, excepto los de fibra óptica.", Decimal("5.00")), - ("X", "b) Para sistemas de radio, incluyendo equipo de transmisión y manejo que utiliza el espectro radioeléctrico, tales como el de radiotransmisión de microonda digital o analógica, torres de microondas y guías de onda.", Decimal("8.00")), - ("X", "c) Para equipo utilizado en la transmisión, tales como circuitos de la planta interna que no forman parte de la conmutación y cuyas funciones se enfocan hacia las troncales que llegan a la central telefónica, incluye multiplexores, equipos concentradores y ruteadores.", Decimal("10.00")), - ("X", "d) Para equipo de la central telefónica destinado a la conmutación de llamadas de tecnología distinta a la electromecánica.", Decimal("25.00")), - ("X", "e) Para los demás.", Decimal("10.00")), - ("XI", "a) Para el segmento satelital en el espacio, incluyendo el cuerpo principal del satélite, los transpondedores, las antenas para la transmisión y recepción de comunicaciones digitales y análogas, y el equipo de monitoreo en el satélite.", Decimal("8.00")), - ("XI", "b) Para el equipo satelital en tierra, incluyendo las antenas para la transmisión y recepción de comunicaciones digitales y análogas y el equipo para el monitoreo del satélite.", Decimal("10.00")), - ("XII", "Para adaptaciones que se realicen a instalaciones que impliquen adiciones o mejoras al activo fijo, siempre que dichas adaptaciones tengan como finalidad facilitar a las personas con discapacidad a que se refiere el artículo 186 de esta Ley, el acceso y uso de las instalaciones del contribuyente.", Decimal("100.00")), - ("XIII", "Para maquinaria y equipo para la generación de energía proveniente de fuentes renovables o de sistemas de cogeneración de electricidad eficiente.", Decimal("100.00")), -] diff --git a/backend/api/v1/modules/a76/general_catalogs/depreciation_catalog/service.py b/backend/api/v1/modules/a76/general_catalogs/depreciation_catalog/service.py deleted file mode 100644 index 20d19429..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/depreciation_catalog/service.py +++ /dev/null @@ -1,91 +0,0 @@ -""" -Depreciation Catalog Service -""" - -from sqlalchemy.orm import Session -from sqlalchemy import or_, func -from api.v1.modules.a76.general_catalogs.depreciation_catalog.models import DepreciationCatalog - - -class DepreciationCatalogService: - """Service for depreciation catalog operations""" - - @staticmethod - def get_all( - db: Session, - tenant_id: int, - company_id: int, - page: int = 1, - page_size: int = 100, - search: str | None = None - ): - """Get all depreciation catalog entries with optional search""" - query = db.query(DepreciationCatalog).filter( - DepreciationCatalog.tenant_id == tenant_id, - DepreciationCatalog.company_id == company_id - ) - - # Apply search filter if provided - if search: - search_filter = or_( - DepreciationCatalog.fraction.ilike(f"%{search}%"), - DepreciationCatalog.description.ilike(f"%{search}%"), - func.cast(DepreciationCatalog.depreciation_rate, db.String).ilike(f"%{search}%") - ) - query = query.filter(search_filter) - - # Get total count before pagination - total = query.count() - - # Apply pagination - offset = (page - 1) * page_size - items = query.order_by( - DepreciationCatalog.fraction, - DepreciationCatalog.depreciation_rate - ).offset(offset).limit(page_size).all() - - return items, total - - @staticmethod - def get_by_id(db: Session, id: int, tenant_id: int, company_id: int): - """Get depreciation catalog entry by ID""" - return db.query(DepreciationCatalog).filter( - DepreciationCatalog.id == id, - DepreciationCatalog.tenant_id == tenant_id, - DepreciationCatalog.company_id == company_id - ).first() - - @staticmethod - def create(db: Session, data: dict): - """Create new depreciation catalog entry""" - entry = DepreciationCatalog(**data) - db.add(entry) - db.commit() - db.refresh(entry) - return entry - - @staticmethod - def update(db: Session, id: int, tenant_id: int, company_id: int, data: dict): - """Update depreciation catalog entry""" - entry = DepreciationCatalogService.get_by_id(db, id, tenant_id, company_id) - if not entry: - return None - - for key, value in data.items(): - if value is not None: - setattr(entry, key, value) - - db.commit() - db.refresh(entry) - return entry - - @staticmethod - def delete(db: Session, id: int, tenant_id: int, company_id: int): - """Delete depreciation catalog entry""" - entry = DepreciationCatalogService.get_by_id(db, id, tenant_id, company_id) - if not entry: - return False - - db.delete(entry) - db.commit() - return True diff --git a/backend/api/v1/modules/a76/general_catalogs/doda/__init__.py b/backend/api/v1/modules/a76/general_catalogs/doda/__init__.py deleted file mode 100644 index 9c5e22f4..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/doda/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -""" -Módulo de DODA (Documentos de Operación de Aduana) -""" diff --git a/backend/api/v1/modules/a76/general_catalogs/doda/alta_log_dto.py b/backend/api/v1/modules/a76/general_catalogs/doda/alta_log_dto.py deleted file mode 100644 index 3ad314d6..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/doda/alta_log_dto.py +++ /dev/null @@ -1,55 +0,0 @@ -from __future__ import annotations - -from typing import List, Optional - -from pydantic import BaseModel, Field - - -class DodaAltaLogCreateDTO(BaseModel): - doda_id: Optional[int] = None - variant: Optional[str] = Field(None, max_length=10) - action: Optional[str] = Field(None, max_length=20) - responsible: Optional[str] = Field(None, max_length=20) - patent: Optional[str] = Field(None, max_length=10) - dispatch_customs: Optional[str] = Field(None, max_length=10) - operation_type: Optional[str] = Field(None, max_length=5) - integration_number: Optional[str] = Field(None, max_length=50) - task_id: Optional[str] = Field(None, max_length=255) - status: Optional[str] = Field(None, max_length=30) - message: Optional[str] = Field(None, max_length=2000) - result_json: Optional[str] = None - - -class DodaAltaLogUpdateDTO(BaseModel): - status: Optional[str] = Field(None, max_length=30) - message: Optional[str] = Field(None, max_length=2000) - result_json: Optional[str] = None - - -class DodaAltaLogResponseDTO(BaseModel): - id: int - doda_id: Optional[int] = None - variant: Optional[str] = None - action: Optional[str] = None - responsible: Optional[str] = None - patent: Optional[str] = None - dispatch_customs: Optional[str] = None - operation_type: Optional[str] = None - integration_number: Optional[str] = None - task_id: Optional[str] = None - status: Optional[str] = None - message: Optional[str] = None - result_json: Optional[str] = None - company_id: int - tenant_id: int - created_at: Optional[str] = None - updated_at: Optional[str] = None - - model_config = {"from_attributes": True} - - -class DodaAltaLogListResponse(BaseModel): - items: List[DodaAltaLogResponseDTO] - total: int - page: int - page_size: int diff --git a/backend/api/v1/modules/a76/general_catalogs/doda/alta_log_models.py b/backend/api/v1/modules/a76/general_catalogs/doda/alta_log_models.py deleted file mode 100644 index 085213ed..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/doda/alta_log_models.py +++ /dev/null @@ -1,39 +0,0 @@ -from __future__ import annotations - -from sqlalchemy import Integer, String, Text -from sqlalchemy.orm import Mapped, mapped_column - -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base - - -class DodaAltaLog(Base, TenantScopedMixin, TimestampMixin): - """ - Registro histórico de envíos de alta DODA/PITA al servicio externo. - Cada fila corresponde a un intento de alta para un DODA específico. - """ - - __tablename__ = "doda_alta_log" - __table_args__ = ({"schema": "a76"},) - - id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) - - # Referencia al DODA origen - doda_id: Mapped[int | None] = mapped_column(Integer, nullable=True, index=True) - - # Tipo de alta (doda / pita) - variant: Mapped[str | None] = mapped_column(String(10), nullable=True) - action: Mapped[str | None] = mapped_column(String(20), nullable=True) - - # Datos copiados del DODA al momento del envío (para historial) - responsible: Mapped[str | None] = mapped_column(String(20), nullable=True) - patent: Mapped[str | None] = mapped_column(String(10), nullable=True) - dispatch_customs: Mapped[str | None] = mapped_column(String(10), nullable=True) - operation_type: Mapped[str | None] = mapped_column(String(5), nullable=True) - integration_number: Mapped[str | None] = mapped_column(String(50), nullable=True) - - # Respuesta del servicio externo - task_id: Mapped[str | None] = mapped_column(String(255), nullable=True, index=True) - status: Mapped[str | None] = mapped_column(String(30), nullable=True) - message: Mapped[str | None] = mapped_column(String(2000), nullable=True) - result_json: Mapped[str | None] = mapped_column(Text, nullable=True) diff --git a/backend/api/v1/modules/a76/general_catalogs/doda/alta_log_service.py b/backend/api/v1/modules/a76/general_catalogs/doda/alta_log_service.py deleted file mode 100644 index ef325ab8..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/doda/alta_log_service.py +++ /dev/null @@ -1,142 +0,0 @@ -from __future__ import annotations - -import json -import logging -from typing import Optional - -from sqlalchemy.orm import Session - -from .alta_log_dto import ( - DodaAltaLogCreateDTO, - DodaAltaLogListResponse, - DodaAltaLogResponseDTO, - DodaAltaLogUpdateDTO, -) -from .alta_log_models import DodaAltaLog -from .models import Doda - -logger = logging.getLogger(__name__) - - -class DodaAltaLogService: - - @staticmethod - def list( - db: Session, - company_id: int, - tenant_id: int, - page: int = 1, - page_size: int = 50, - doda_id: Optional[int] = None, - search: Optional[str] = None, - ) -> DodaAltaLogListResponse: - query = ( - db.query(DodaAltaLog) - .filter( - DodaAltaLog.company_id == company_id, - DodaAltaLog.tenant_id == tenant_id, - DodaAltaLog.deleted_at.is_(None), - ) - ) - if doda_id: - query = query.filter(DodaAltaLog.doda_id == doda_id) - if search: - like = f"%{search}%" - query = query.filter( - DodaAltaLog.task_id.ilike(like) - | DodaAltaLog.integration_number.ilike(like) - | DodaAltaLog.patent.ilike(like) - | DodaAltaLog.status.ilike(like) - ) - total = query.count() - items = ( - query.order_by(DodaAltaLog.id.desc()) - .offset((page - 1) * page_size) - .limit(page_size) - .all() - ) - return DodaAltaLogListResponse( - items=[DodaAltaLogResponseDTO.model_validate(r) for r in items], - total=total, - page=page, - page_size=page_size, - ) - - @staticmethod - def get( - db: Session, record_id: int, company_id: int, tenant_id: int - ) -> Optional[DodaAltaLog]: - return ( - db.query(DodaAltaLog) - .filter( - DodaAltaLog.id == record_id, - DodaAltaLog.company_id == company_id, - DodaAltaLog.tenant_id == tenant_id, - DodaAltaLog.deleted_at.is_(None), - ) - .first() - ) - - @staticmethod - def create( - db: Session, dto: DodaAltaLogCreateDTO, company_id: int, tenant_id: int - ) -> DodaAltaLog: - record = DodaAltaLog( - company_id=company_id, - tenant_id=tenant_id, - **dto.model_dump(exclude_none=False), - ) - db.add(record) - db.commit() - db.refresh(record) - return record - - @staticmethod - def update( - db: Session, record: DodaAltaLog, dto: DodaAltaLogUpdateDTO - ) -> DodaAltaLog: - for field, value in dto.model_dump(exclude_unset=True).items(): - setattr(record, field, value) - db.commit() - db.refresh(record) - return record - - @staticmethod - def delete(db: Session, record: DodaAltaLog) -> None: - from datetime import datetime - record.deleted_at = datetime.utcnow() - db.commit() - - @staticmethod - def create_from_alta_result( - db: Session, - doda: Doda, - company_id: int, - tenant_id: int, - variant: str, - ext_result: dict, - action: str = "alta", - ) -> DodaAltaLog: - """ - Crea un registro de log a partir de la respuesta del servicio externo de alta. - Llamado automáticamente al completar `POST /{doda_id}/alta`. - """ - task_id = ext_result.get("task_id") or ext_result.get("id") or "" - status = ext_result.get("status") or "pending" - message = ext_result.get("message") or "" - - dto = DodaAltaLogCreateDTO( - doda_id=doda.id, - variant=variant, - action=action, - responsible=doda.responsible, - patent=doda.patent, - dispatch_customs=doda.dispatch_customs, - operation_type=doda.operation_type, - integration_number=doda.integration_number, - task_id=task_id, - status=status, - message=message, - result_json=json.dumps(ext_result), - ) - return DodaAltaLogService.create(db, dto, company_id, tenant_id) diff --git a/backend/api/v1/modules/a76/general_catalogs/doda/alta_service.py b/backend/api/v1/modules/a76/general_catalogs/doda/alta_service.py deleted file mode 100644 index 2a539bdb..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/doda/alta_service.py +++ /dev/null @@ -1,643 +0,0 @@ -from __future__ import annotations - -import base64 -import json -import logging -from dataclasses import dataclass, field -from typing import Any, Dict, List, Optional - -from cryptography.hazmat.primitives import padding as crypto_padding -from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes -from sqlalchemy import or_ -from sqlalchemy.orm import Session - -from core.config import settings -from core.storage_s3 import get_object_bytes, object_exists - -from api.v1.modules.a76.customs_brokers import models as cb_models - -from .models import Doda, DodaContainer, DodaAmericanPedimento, DodaPedimento -from .alta_log_models import DodaAltaLog -from .payload_normalizer import ( - normalize_aduana_despacho, - normalize_aduana_seccion, - normalize_caat, - normalize_doda_pedimento_row, - normalize_fast_id, - normalize_id_transporte, - normalize_numero_gafete, - normalize_patente, - normalize_tipo_operacion, -) - -logger = logging.getLogger(__name__) - - -# --------------------------------------------------------------------------- -# Response schemas (inline para no añadir dependencias externas) -# --------------------------------------------------------------------------- - -@dataclass -class ElegibilidadReason: - field: str - message: str - solution: str = "" - - -@dataclass -class ElegibilidadResponse: - can_alta: bool - reasons: List[ElegibilidadReason] = field(default_factory=list) - - -# --------------------------------------------------------------------------- -# FIEL encryption (mismo esquema AES-256-CBC que COVE / expediente_archivos) -# --------------------------------------------------------------------------- - -def _encrypt_fiel(raw_fiel: str) -> str: - normalized = (raw_fiel or "").strip() - if not normalized: - return "" - key_bytes = (settings.COVE_FIEL_HASH_KEY or "").encode("utf-8") - iv_bytes = (settings.COVE_FIEL_HASH_IV or "").encode("utf-8") - if not key_bytes or not iv_bytes: - return normalized - key32 = key_bytes[:32].ljust(32, b"\0") - iv16 = iv_bytes[:16].ljust(16, b"\0") - padder = crypto_padding.PKCS7(algorithms.AES.block_size).padder() - padded = padder.update(normalized.encode("utf-8")) + padder.finalize() - cipher = Cipher(algorithms.AES(key32), modes.CBC(iv16)) - enc = cipher.encryptor() - encrypted = enc.update(padded) + enc.finalize() - return base64.b64encode(encrypted).decode("ascii") - - -# --------------------------------------------------------------------------- -# Main service -# --------------------------------------------------------------------------- - -class DodaAltaService: - """ - Servicio de dominio para construir el payload de alta DODA y verificar - elegibilidad antes de enviarlo al servicio externo. - """ - - def __init__(self, db: Session) -> None: - self.db = db - - # ------------------------------------------------------------------ - # Broker resolution - # ------------------------------------------------------------------ - - def _resolve_broker( - self, - responsible_key: Optional[str], - company_id: int, - tenant_id: int, - ) -> Optional[cb_models.CustomsBroker]: - """ - Resuelve el agente aduanal a partir de Doda.responsible (ClaveAA del legacy). - Prioridad: broker_key exacto → license exacto. - """ - normalized = (responsible_key or "").strip() - if not normalized: - return None - - brokers = ( - self.db.query(cb_models.CustomsBroker) - .filter( - or_( - cb_models.CustomsBroker.broker_key == normalized, - cb_models.CustomsBroker.license == normalized, - ), - cb_models.CustomsBroker.company_id == company_id, - cb_models.CustomsBroker.tenant_id == tenant_id, - cb_models.CustomsBroker.deleted_at.is_(None), - ) - .order_by(cb_models.CustomsBroker.id.desc()) - .all() - ) - if not brokers: - return None - - exact = next( - (b for b in brokers if (b.broker_key or "").strip() == normalized), - None, - ) - return exact or brokers[0] - - # ------------------------------------------------------------------ - # configuracion_vu DODA - # ------------------------------------------------------------------ - - def _build_configuracion_vu_doda( - self, - broker: cb_models.CustomsBroker, - errors: List[ElegibilidadReason], - ) -> Optional[Dict[str, Any]]: - """ - Construye configuracion_vu usando los campos DODA del CustomsBrokerVU: - doda_certificate_path, doda_key_path, doda_fiel_access_key. - """ - vu = broker.vu if broker else None - - if not vu: - errors.append(ElegibilidadReason( - field="vu", - message="El agente aduanal no tiene configuración VU.", - solution="Configura la sección VU/DODA del agente aduanal.", - )) - return None - - doda_cert_path = (getattr(vu, "doda_certificate_path", None) or "").strip() - doda_key_path = (getattr(vu, "doda_key_path", None) or "").strip() - doda_fiel = (getattr(vu, "doda_fiel_access_key", None) or "").strip() - - if not doda_cert_path or not doda_key_path: - errors.append(ElegibilidadReason( - field="vu.doda_certificate_path", - message="Faltan rutas de certificado o llave DODA en la configuración VU del agente.", - solution="Sube el .cer y .key DODA en la pestaña DODA del agente aduanal.", - )) - return None - - if not doda_fiel: - errors.append(ElegibilidadReason( - field="vu.doda_fiel_access_key", - message="La clave FIEL DODA no está configurada en la VU del agente.", - solution="Captura la clave FIEL DODA en la configuración VU del agente aduanal.", - )) - return None - - cer_b64: Optional[str] = None - key_b64: Optional[str] = None - try: - if not object_exists(doda_cert_path): - errors.append(ElegibilidadReason( - field="vu.doda_certificate_path", - message="El certificado DODA no existe en el almacenamiento.", - solution="Vuelve a subir el .cer DODA en la configuración VU del agente.", - )) - else: - cer_b64 = base64.b64encode(get_object_bytes(doda_cert_path)).decode("ascii") - - if not object_exists(doda_key_path): - errors.append(ElegibilidadReason( - field="vu.doda_key_path", - message="La llave DODA no existe en el almacenamiento.", - solution="Vuelve a subir el .key DODA en la configuración VU del agente.", - )) - else: - key_b64 = base64.b64encode(get_object_bytes(doda_key_path)).decode("ascii") - except Exception: - logger.exception("Error leyendo certificados DODA desde S3") - errors.append(ElegibilidadReason( - field="vu", - message="Error leyendo certificados DODA desde el almacenamiento.", - solution="Verifica la configuración de S3/MinIO y las rutas de los certificados.", - )) - return None - - if errors: - return None - - rfc_ciec = ( - (getattr(vu, "query_tax_id", None) or "").strip() - or (getattr(broker, "tax_id", None) or "").strip() - ) - - clave_fiel = _encrypt_fiel(doda_fiel) - - return { - "rfc_ciec": rfc_ciec, - "archivo_cer_base64": cer_b64 or "", - "archivo_key_base64": key_b64 or "", - "clave_fiel": clave_fiel, - } - - def _attach_user_email( - self, configuracion_vu: Dict[str, Any], user_email: str - ) -> Dict[str, Any]: - """Agrega el email del usuario autenticado al bloque configuracion_vu.""" - configuracion_vu["email"] = (user_email or "").strip() - return configuracion_vu - - # ------------------------------------------------------------------ - # Payload builders for child collections - # ------------------------------------------------------------------ - - def _build_contenedores( - self, containers: List[DodaContainer] - ) -> List[Dict[str, Any]]: - result = [] - for c in containers: - candados = [] - for seal in (c.seals_detail or []): - if seal.seal_value: - candados.append({"candado": seal.seal_value}) - # Fallback: si no hay filas en seals_detail pero hay string legado en seals - if not candados and c.seals: - for raw in c.seals.split(","): - val = raw.strip() - if val: - candados.append({"candado": val}) - val = (c.container_value or "").strip() - result.append({ - "valor_contenedor": val, - "candados": candados, - }) - return result - - def _build_pedimentos_americanos( - self, american_pedimentos: List[DodaAmericanPedimento] - ) -> List[Dict[str, Any]]: - return [ - { - "tipo_pedimento_americano": p.american_pedimento_type or "", - "valor_pedimento_americano": p.american_pedimento_value or "", - } - for p in american_pedimentos - ] - - def _build_pedimentos( - self, pedimentos_detail: List[DodaPedimento] - ) -> List[Dict[str, Any]]: - out: List[Dict[str, Any]] = [] - for p in pedimentos_detail: - row = normalize_doda_pedimento_row( - document=p.document, - authorization_patent=p.authorization_patent, - shipment=p.shipment, - cove=p.cove, - umc=p.umc, - dta_niu=p.dta_niu, - pedimento_type=p.pedimento_type, - effective=p.effective_amount_usd, - diff=p.difference_amount_usd, - ) - out.append(row) - return out - - # ------------------------------------------------------------------ - # Elegibilidad (validaciones del legacy Clarion activas) - # ------------------------------------------------------------------ - - def check_elegibilidad( - self, - doda_id: int, - tenant_id: int, - company_id: int, - variant: str = "doda", - user_email: Optional[str] = None, - ) -> ElegibilidadResponse: - """ - Verifica si el DODA cumple los requisitos para enviar el alta. - Porta las validaciones activas del código Clarion legacy. - """ - reasons: List[ElegibilidadReason] = [] - - # --- Email del usuario autenticado --- - if not (user_email or "").strip(): - reasons.append(ElegibilidadReason( - field="user_email", - message="El usuario no tiene correo electrónico registrado.", - solution="Configura un correo electrónico en tu perfil de Keycloak.", - )) - - doda = self.db.query(Doda).filter( - Doda.id == doda_id, - Doda.tenant_id == tenant_id, - Doda.company_id == company_id, - ).first() - - if not doda: - reasons.append(ElegibilidadReason( - field="doda_id", - message=f"DODA {doda_id} no encontrado.", - )) - return ElegibilidadResponse(can_alta=False, reasons=reasons) - - # --- Campos obligatorios (ramas activas del legacy) --- - if not (doda.responsible or "").strip(): - reasons.append(ElegibilidadReason( - field="responsible", - message="El campo Responsable se encuentra vacío.", - solution="Captura la clave del agente aduanal responsable.", - )) - - if not (doda.dispatch_customs or "").strip(): - reasons.append(ElegibilidadReason( - field="dispatch_customs", - message="El campo Aduana de Despacho se encuentra vacío.", - solution="Captura la clave de la aduana de despacho.", - )) - - if not (doda.customs_sections or "").strip(): - reasons.append(ElegibilidadReason( - field="customs_sections", - message="El campo Aduana Sección (E/S) se encuentra vacío.", - solution="Captura la sección aduanera.", - )) - - if not (doda.operation_type or "").strip(): - reasons.append(ElegibilidadReason( - field="operation_type", - message="El campo Tipo de Operación se encuentra vacío.", - solution="Selecciona el tipo de operación.", - )) - - if not (doda.caat or "").strip(): - reasons.append(ElegibilidadReason( - field="caat", - message="El campo CAAT se encuentra vacío.", - solution="Captura el código CAAT del transportista.", - )) - - if not (doda.transport_identification or "").strip(): - reasons.append(ElegibilidadReason( - field="transport_identification", - message="El campo Identificación de Transporte se encuentra vacío.", - solution="Captura el número de identificación del transporte.", - )) - - if not (doda.patent or "").strip(): - reasons.append(ElegibilidadReason( - field="patent", - message="El campo Patente se encuentra vacío.", - solution="La patente se llena automáticamente al seleccionar el responsable.", - )) - - # --- Gafete único: obligatorio si variant=doda --- - if variant.lower() == "doda": - if not (doda.unique_badge_number or "").strip(): - reasons.append(ElegibilidadReason( - field="unique_badge_number", - message="El campo Número de Gafete Único es obligatorio para el Alta DODA.", - solution="Captura el número de gafete único del conductor.", - )) - - # --- Contenedores máximo 4 (legacy: IF SQL2:C2 = 4 THEN MESSAGE) --- - containers = doda.containers or [] - if len(containers) > 4: - reasons.append(ElegibilidadReason( - field="containers", - message=f"El DODA tiene {len(containers)} contenedores. El máximo permitido es 4.", - solution="Elimina los contenedores sobrantes antes de enviar el alta.", - )) - - # --- Precintos: máximo 8 en todo el DODA (legacy gDoda_Contenedores_Candados) --- - seal_count = 0 - for c in containers: - details = getattr(c, "seals_detail", None) or [] - if details: - seal_count += len(details) - else: - legacy = (getattr(c, "seals", None) or "").strip() - if legacy: - seal_count += len([s for s in legacy.split(",") if s.strip()]) - if seal_count > 8: - reasons.append(ElegibilidadReason( - field="containers", - message=f"El DODA tiene {seal_count} precintos. El máximo permitido es 8.", - solution="Elimina precintos hasta quedar en 8 o menos.", - )) - - # --- Pedimentos americanos: tipo obligatorio y rango según operación (legacy, salvo PITA) --- - clearance = getattr(doda, "customs_clearance", None) - if clearance != 1: - op = (doda.operation_type or "").strip().upper() - if op in ("I", "1"): - allowed_tipo = {"1", "2", "3", "4", "5"} - elif op in ("E", "2"): - allowed_tipo = {"6", "7", "8"} - else: - allowed_tipo = set() - for idx, ap in enumerate(doda.american_pedimentos or [], 1): - tipo = (ap.american_pedimento_type or "").strip() - if not tipo: - reasons.append(ElegibilidadReason( - field="american_pedimentos", - message=f"Pedimento americano (línea {idx}): el tipo es obligatorio para este tipo de despacho.", - solution="Captura el tipo de pedimento americano (1–5 importación, 6–8 exportación).", - )) - elif allowed_tipo and tipo not in allowed_tipo: - reasons.append(ElegibilidadReason( - field="american_pedimentos", - message=( - f"Pedimento americano (línea {idx}): el tipo '{tipo}' no corresponde al tipo de operación." - ), - solution="Corrige el tipo según importación (1–5) o exportación (6–8).", - )) - - # --- Patente vs. patente del agente aduanal (legacy: DODA:Patente <> AgeAdu:Patente) --- - broker = self._resolve_broker(doda.responsible, company_id, tenant_id) - - if (doda.responsible or "").strip() and not broker: - reasons.append(ElegibilidadReason( - field="responsible", - message=f"La clave de Responsable '{doda.responsible}' no existe en el catálogo de agentes aduanales.", - solution="Selecciona un agente aduanal válido del catálogo.", - )) - elif broker and (doda.patent or "").strip(): - broker_patent = (broker.license or "").strip() - doda_patent = (doda.patent or "").strip() - if broker_patent and doda_patent and doda_patent != broker_patent: - reasons.append(ElegibilidadReason( - field="patent", - message=( - f"La patente declarada en el DODA '{doda_patent}' es distinta " - f"a la patente del responsable '{broker_patent}'." - ), - solution="Verifica o actualiza la patente del DODA para que coincida con la del agente.", - )) - - # --- Certificados DODA en VU del agente (equivalente a RutaArchivosXMLDODA) --- - if broker: - vu = broker.vu - if not vu: - reasons.append(ElegibilidadReason( - field="vu", - message="El agente aduanal no tiene configuración VU.", - solution="Configura la sección VU/DODA del agente aduanal.", - )) - else: - if not (getattr(vu, "doda_certificate_path", None) or "").strip(): - reasons.append(ElegibilidadReason( - field="vu.doda_certificate_path", - message="No hay certificado DODA (.cer) configurado en la VU del agente.", - solution="Sube el certificado .cer DODA en la pestaña DODA del agente aduanal.", - )) - if not (getattr(vu, "doda_key_path", None) or "").strip(): - reasons.append(ElegibilidadReason( - field="vu.doda_key_path", - message="No hay llave DODA (.key) configurada en la VU del agente.", - solution="Sube la llave .key DODA en la pestaña DODA del agente aduanal.", - )) - if not (getattr(vu, "doda_fiel_access_key", None) or "").strip(): - reasons.append(ElegibilidadReason( - field="vu.doda_fiel_access_key", - message="La clave FIEL DODA no está configurada en la VU del agente.", - solution="Captura la clave FIEL DODA en la configuración VU del agente.", - )) - - return ElegibilidadResponse( - can_alta=len(reasons) == 0, - reasons=reasons, - ) - - # ------------------------------------------------------------------ - # Build full payload - # ------------------------------------------------------------------ - - def _latest_alta_log( - self, - doda_id: int, - tenant_id: int, - company_id: int, - variant: str, - ) -> Optional[DodaAltaLog]: - return ( - self.db.query(DodaAltaLog) - .filter( - DodaAltaLog.doda_id == doda_id, - DodaAltaLog.tenant_id == tenant_id, - DodaAltaLog.company_id == company_id, - DodaAltaLog.variant == variant, - DodaAltaLog.deleted_at.is_(None), - ) - .order_by(DodaAltaLog.id.desc()) - .first() - ) - - @staticmethod - def _extract_numero_transaccion(log_record: DodaAltaLog) -> str: - raw_json = (log_record.result_json or "").strip() - if raw_json: - try: - parsed = json.loads(raw_json) - for key in ("numero_transaccion", "transaction_number"): - value = parsed.get(key) - if value: - return str(value).strip() - except Exception: - logger.warning("No se pudo parsear result_json de DodaAltaLog id=%s", log_record.id) - return "" - - def build_alta_payload( - self, - doda_id: int, - tenant_id: int, - company_id: int, - variant: str = "doda", - user_email: str = "", - ) -> Dict[str, Any]: - """ - Construye el payload completo para POST /api/v1/doda/alta. - Lanza ValueError si hay problemas de configuración críticos. - """ - doda = self.db.query(Doda).filter( - Doda.id == doda_id, - Doda.tenant_id == tenant_id, - Doda.company_id == company_id, - ).first() - - if not doda: - raise ValueError(f"DODA {doda_id} no encontrado.") - - broker = self._resolve_broker(doda.responsible, company_id, tenant_id) - if not broker: - raise ValueError( - f"No se encontró el agente aduanal con clave '{doda.responsible}'." - ) - - errors: List[ElegibilidadReason] = [] - configuracion_vu = self._build_configuracion_vu_doda(broker, errors) - if errors or not configuracion_vu: - msgs = "; ".join(r.message for r in errors) - raise ValueError(f"Error en configuración VU DODA: {msgs}") - - self._attach_user_email(configuracion_vu, user_email) - - containers = doda.containers or [] - american_pedimentos = doda.american_pedimentos or [] - pedimentos_detail = doda.pedimentos_detail or [] - - # El API externo espera "1" para DODA y "2" para PITA (ver DODARequest.despacho_aduanero) - despacho_aduanero = "1" if variant.lower() == "doda" else "2" - - payload: Dict[str, Any] = { - "configuracion_vu": configuracion_vu, - "despacho_aduanero": despacho_aduanero, - "numero_gafete_unico": normalize_numero_gafete( - doda.unique_badge_number - ), - "aduana_despacho": normalize_aduana_despacho(doda.dispatch_customs), - "aduana_seccion": normalize_aduana_seccion(doda.customs_sections), - "patente": normalize_patente(doda.patent), - "caat": normalize_caat(doda.caat), - "id_transporte": normalize_id_transporte(doda.transport_identification), - "fast_id": normalize_fast_id(doda.fast_id), - "tipo_operacion": normalize_tipo_operacion(doda.operation_type), - "contenedores": self._build_contenedores(containers), - "pedimentos_americanos": self._build_pedimentos_americanos(american_pedimentos), - "cfdi_carta_porte": {"cfdi_carta_porte": ""}, - "pedimentos": self._build_pedimentos(pedimentos_detail), - } - - return payload - - def build_consulta_payload( - self, - doda_id: int, - tenant_id: int, - company_id: int, - variant: str = "doda", - user_email: str = "", - ) -> Dict[str, Any]: - payload = self.build_alta_payload( - doda_id=doda_id, - tenant_id=tenant_id, - company_id=company_id, - variant=variant, - user_email=user_email, - ) - latest_log = self._latest_alta_log(doda_id, tenant_id, company_id, variant) - if not latest_log: - raise ValueError( - "No existe un alta DODA previa para construir la consulta (falta task/log)." - ) - numero_transaccion = self._extract_numero_transaccion(latest_log) - if not numero_transaccion: - raise ValueError( - "No se encontro numero_transaccion en el ultimo resultado de alta DODA." - ) - payload["numero_transaccion"] = numero_transaccion - return payload - - def build_eliminar_payload( - self, - doda_id: int, - tenant_id: int, - company_id: int, - variant: str = "doda", - user_email: str = "", - ) -> Dict[str, Any]: - payload = self.build_alta_payload( - doda_id=doda_id, - tenant_id=tenant_id, - company_id=company_id, - variant=variant, - user_email=user_email, - ) - latest_log = self._latest_alta_log(doda_id, tenant_id, company_id, variant) - if not latest_log: - raise ValueError( - "No existe un alta DODA previa para construir la eliminacion." - ) - numero_integracion = (latest_log.integration_number or "").strip() - if not numero_integracion: - raise ValueError( - "No se encontro numero_integracion en el historial de alta DODA." - ) - payload["numero_integracion"] = numero_integracion - return payload diff --git a/backend/api/v1/modules/a76/general_catalogs/doda/dto.py b/backend/api/v1/modules/a76/general_catalogs/doda/dto.py deleted file mode 100644 index 5e7b1822..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/doda/dto.py +++ /dev/null @@ -1,437 +0,0 @@ -""" -DTOs (Data Transfer Objects) para módulo de DODA -""" - -from datetime import datetime -from decimal import Decimal -from typing import Optional, List - -from pydantic import AliasChoices, BaseModel, Field - - -# ============ DODA CONTAINER SEAL DTOS ============ -class DodaContainerSealCreateDTO(BaseModel): - """DTO para crear un candado de contenedor""" - - seal_value: Optional[str] = Field( - None, max_length=21, description="Seal value") - - class Config: - from_attributes = True - - -class DodaContainerSealResponseDTO(BaseModel): - """DTO para responder con datos de un candado""" - - id: int - doda_sys_id: int = Field( - validation_alias=AliasChoices("doda_sys_id", "doda_id") - ) - seal_line: int - seal_value: Optional[str] = None - - class Config: - from_attributes = True - - -# ============ DODA CONTAINER DTOS ============ -class DodaContainerCreateDTO(BaseModel): - """DTO para crear un contenedor""" - - container_value: Optional[str] = Field( - None, max_length=20, description="Container value") - seals: Optional[str] = Field(None, max_length=254, description="Seals") - seals_detail: Optional[List[DodaContainerSealCreateDTO]] = Field( - None, description="Container seals" - ) - - class Config: - from_attributes = True - - -class DodaContainerUpdateDTO(BaseModel): - """DTO para actualizar un contenedor""" - - container_value: Optional[str] = Field( - None, max_length=20, description="Container value") - seals: Optional[str] = Field(None, max_length=254, description="Seals") - - class Config: - from_attributes = True - - -class DodaContainerResponseDTO(BaseModel): - """DTO para responder con datos de un contenedor""" - - id: int - doda_sys_id: int = Field( - validation_alias=AliasChoices("doda_sys_id", "doda_id") - ) - container_line: int - container_value: Optional[str] = None - seals: Optional[str] = None - seals_detail: Optional[List[DodaContainerSealResponseDTO]] = None - - class Config: - from_attributes = True - - -# ============ DODA AMERICAN PEDIMENTO DTOS ============ -class DodaAmericanPedimentoCreateDTO(BaseModel): - """DTO para crear un pedimento americano""" - - american_pedimento_type: Optional[str] = Field( - None, max_length=2, description="American pedimento type" - ) - american_pedimento_value: Optional[str] = Field( - None, max_length=20, description="American pedimento value" - ) - - class Config: - from_attributes = True - - -class DodaAmericanPedimentoUpdateDTO(BaseModel): - """DTO para actualizar un pedimento americano""" - - american_pedimento_type: Optional[str] = Field( - None, max_length=2, description="American pedimento type" - ) - american_pedimento_value: Optional[str] = Field( - None, max_length=20, description="American pedimento value" - ) - - class Config: - from_attributes = True - - -class DodaAmericanPedimentoResponseDTO(BaseModel): - """DTO para responder con datos de un pedimento americano""" - - id: int - doda_sys_id: int = Field( - validation_alias=AliasChoices("doda_sys_id", "doda_id") - ) - american_pedimento_line: int - american_pedimento_type: Optional[str] = None - american_pedimento_value: Optional[str] = None - - class Config: - from_attributes = True - - -# ============ DODA PEDIMENTO DTOS ============ -class DodaPedimentoCreateDTO(BaseModel): - """DTO para crear un pedimento DODA""" - - authorization_patent: Optional[str] = Field( - None, max_length=10, description="Authorization patent" - ) - document: Optional[str] = Field( - None, max_length=50, description="Document") - shipment: Optional[str] = Field( - None, max_length=11, description="Shipment") - cove: Optional[str] = Field(None, max_length=50, description="COVE") - umc: Optional[str] = Field(None, max_length=20, description="UMC") - effective_amount_usd: Optional[Decimal] = Field( - None, description="Effective amount USD") - difference_amount_usd: Optional[Decimal] = Field( - None, description="Difference amount USD") - dta_niu: Optional[str] = Field(None, max_length=20, description="DTA NIU") - article_7: Optional[bool] = Field(None, description="Article 7") - pedimento_sys_id: Optional[int] = Field( - None, description="Pedimento system ID") - invoice_line: Optional[int] = Field(None, description="Invoice line") - part_ii_line: Optional[int] = Field(None, description="Part II line") - pedimento_type: Optional[str] = Field( - None, max_length=20, description="Pedimento type") - zero_packaging_validation: Optional[bool] = Field( - None, description="Zero packaging validation" - ) - - class Config: - from_attributes = True - - -class DodaPedimentoUpdateDTO(BaseModel): - """DTO para actualizar un pedimento DODA""" - - authorization_patent: Optional[str] = Field( - None, max_length=10, description="Authorization patent" - ) - document: Optional[str] = Field( - None, max_length=50, description="Document") - shipment: Optional[str] = Field( - None, max_length=11, description="Shipment") - cove: Optional[str] = Field(None, max_length=50, description="COVE") - umc: Optional[str] = Field(None, max_length=20, description="UMC") - effective_amount_usd: Optional[Decimal] = Field( - None, description="Effective amount USD") - difference_amount_usd: Optional[Decimal] = Field( - None, description="Difference amount USD") - dta_niu: Optional[str] = Field(None, max_length=20, description="DTA NIU") - article_7: Optional[bool] = Field(None, description="Article 7") - pedimento_sys_id: Optional[int] = Field( - None, description="Pedimento system ID") - invoice_line: Optional[int] = Field(None, description="Invoice line") - part_ii_line: Optional[int] = Field(None, description="Part II line") - pedimento_type: Optional[str] = Field( - None, max_length=20, description="Pedimento type") - zero_packaging_validation: Optional[bool] = Field( - None, description="Zero packaging validation" - ) - - class Config: - from_attributes = True - - -class DodaPedimentoResponseDTO(BaseModel): - """DTO para responder con datos de un pedimento DODA""" - - id: int - doda_sys_id: int = Field( - validation_alias=AliasChoices("doda_sys_id", "doda_id") - ) - pedimento_line: int - authorization_patent: Optional[str] = None - document: Optional[str] = None - shipment: Optional[str] = None - cove: Optional[str] = None - umc: Optional[str] = None - effective_amount_usd: Optional[Decimal] = None - difference_amount_usd: Optional[Decimal] = None - dta_niu: Optional[str] = None - article_7: Optional[bool] = None - pedimento_sys_id: Optional[int] = None - invoice_line: Optional[int] = None - part_ii_line: Optional[int] = None - pedimento_type: Optional[str] = None - zero_packaging_validation: Optional[bool] = None - - class Config: - from_attributes = True - - -# ============ MAIN DODA DTOS ============ -class DodaCreateDTO(BaseModel): - """DTO para crear un DODA""" - - integration_number: Optional[str] = Field( - None, max_length=30, description="Integration number") - doda_date: Optional[int] = Field(None, description="DODA date") - doda_time: Optional[int] = Field(None, description="DODA time") - dispatch_customs: Optional[str] = Field( - None, max_length=3, description="Dispatch customs") - customs_sections: Optional[str] = Field( - None, max_length=3, description="Customs sections") - patent: Optional[str] = Field(None, max_length=4, description="Patent") - pedimentos: Optional[str] = Field( - None, max_length=80, description="Pedimentos") - caat: Optional[str] = Field(None, max_length=10, description="CAAT") - transport_identification: Optional[str] = Field( - None, max_length=20, description="Transport identification" - ) - fast_id: Optional[str] = Field(None, max_length=20, description="FAST ID") - operation_type: Optional[str] = Field( - None, max_length=1, description="Operation type") - selected: Optional[bool] = Field(None, description="Selected") - user_selected: Optional[str] = Field( - None, max_length=30, description="User selected") - last_user: Optional[str] = Field( - None, max_length=30, description="Last user") - responsible: Optional[str] = Field( - None, max_length=14, description="Responsible") - carrier: Optional[str] = Field(None, max_length=8, description="Carrier") - shipments: Optional[str] = Field( - None, max_length=80, description="Shipments") - pedimento_type: Optional[str] = Field( - None, max_length=30, description="Pedimento type") - original_chain: Optional[str] = Field( - None, max_length=5000, description="Original chain") - serial_number: Optional[str] = Field( - None, max_length=21, description="Serial number") - electronic_signature: Optional[str] = Field( - None, max_length=2000, description="Electronic signature" - ) - transaction_number: Optional[str] = Field( - None, max_length=30, description="Transaction number") - status: Optional[str] = Field(None, max_length=30, description="Status") - linq_sat_qr: Optional[str] = Field( - None, max_length=1000, description="LINQ SAT QR") - sat_certificate: Optional[str] = Field( - None, max_length=2001, description="SAT certificate") - sat_digital_seal: Optional[str] = Field( - None, description="SAT digital seal") - xml_doda_sent_path: Optional[str] = Field( - None, max_length=1000, description="XML DODA sent path") - xml_doda_response_path: Optional[str] = Field( - None, max_length=1000, description="XML DODA response path" - ) - sat_original_chain: Optional[str] = Field( - None, description="SAT original chain") - customs_clearance: Optional[int] = Field( - None, description="Customs clearance") - unique_badge_number: Optional[str] = Field( - None, max_length=250, description="Unique badge number" - ) - - class Config: - from_attributes = True - - -class DodaUpdateDTO(BaseModel): - """DTO para actualizar un DODA""" - - integration_number: Optional[str] = Field( - None, max_length=30, description="Integration number") - doda_date: Optional[int] = Field(None, description="DODA date") - doda_time: Optional[int] = Field(None, description="DODA time") - dispatch_customs: Optional[str] = Field( - None, max_length=3, description="Dispatch customs") - customs_sections: Optional[str] = Field( - None, max_length=3, description="Customs sections") - patent: Optional[str] = Field(None, max_length=4, description="Patent") - pedimentos: Optional[str] = Field( - None, max_length=80, description="Pedimentos") - caat: Optional[str] = Field(None, max_length=10, description="CAAT") - transport_identification: Optional[str] = Field( - None, max_length=20, description="Transport identification" - ) - fast_id: Optional[str] = Field(None, max_length=20, description="FAST ID") - operation_type: Optional[str] = Field( - None, max_length=1, description="Operation type") - selected: Optional[bool] = Field(None, description="Selected") - user_selected: Optional[str] = Field( - None, max_length=30, description="User selected") - last_user: Optional[str] = Field( - None, max_length=30, description="Last user") - responsible: Optional[str] = Field( - None, max_length=14, description="Responsible") - carrier: Optional[str] = Field(None, max_length=8, description="Carrier") - shipments: Optional[str] = Field( - None, max_length=80, description="Shipments") - pedimento_type: Optional[str] = Field( - None, max_length=30, description="Pedimento type") - original_chain: Optional[str] = Field( - None, max_length=5000, description="Original chain") - serial_number: Optional[str] = Field( - None, max_length=21, description="Serial number") - electronic_signature: Optional[str] = Field( - None, max_length=2000, description="Electronic signature" - ) - transaction_number: Optional[str] = Field( - None, max_length=30, description="Transaction number") - status: Optional[str] = Field(None, max_length=30, description="Status") - linq_sat_qr: Optional[str] = Field( - None, max_length=1000, description="LINQ SAT QR") - sat_certificate: Optional[str] = Field( - None, max_length=2001, description="SAT certificate") - sat_digital_seal: Optional[str] = Field( - None, description="SAT digital seal") - xml_doda_sent_path: Optional[str] = Field( - None, max_length=1000, description="XML DODA sent path") - xml_doda_response_path: Optional[str] = Field( - None, max_length=1000, description="XML DODA response path" - ) - sat_original_chain: Optional[str] = Field( - None, description="SAT original chain") - customs_clearance: Optional[int] = Field( - None, description="Customs clearance") - unique_badge_number: Optional[str] = Field( - None, max_length=250, description="Unique badge number" - ) - - class Config: - from_attributes = True - - -class DodaResponseDTO(BaseModel): - """DTO para responder con datos de un DODA""" - - id: int - integration_number: Optional[str] = None - doda_date: Optional[int] = None - doda_time: Optional[int] = None - dispatch_customs: Optional[str] = None - customs_sections: Optional[str] = None - patent: Optional[str] = None - pedimentos: Optional[str] = None - caat: Optional[str] = None - transport_identification: Optional[str] = None - fast_id: Optional[str] = None - operation_type: Optional[str] = None - selected: Optional[bool] = None - user_selected: Optional[str] = None - last_user: Optional[str] = None - responsible: Optional[str] = None - carrier: Optional[str] = None - shipments: Optional[str] = None - pedimento_type: Optional[str] = None - original_chain: Optional[str] = None - serial_number: Optional[str] = None - electronic_signature: Optional[str] = None - transaction_number: Optional[str] = None - status: Optional[str] = None - linq_sat_qr: Optional[str] = None - sat_certificate: Optional[str] = None - sat_digital_seal: Optional[str] = None - xml_doda_sent_path: Optional[str] = None - xml_doda_response_path: Optional[str] = None - doda_report_pdf_path: Optional[str] = None - doda_report_pdf_generated_at: Optional[datetime] = None - doda_report_source_fingerprint: Optional[str] = None - sat_original_chain: Optional[str] = None - customs_clearance: Optional[int] = None - unique_badge_number: Optional[str] = None - containers: Optional[List[DodaContainerResponseDTO]] = None - american_pedimentos: Optional[List[DodaAmericanPedimentoResponseDTO]] = None - pedimentos_detail: Optional[List[DodaPedimentoResponseDTO]] = None - - class Config: - from_attributes = True - - -class DodaDetailResponseDTO(BaseModel): - """DTO detallado para responder con todos los datos de un DODA""" - - id: int - integration_number: Optional[str] = None - doda_date: Optional[int] = None - doda_time: Optional[int] = None - dispatch_customs: Optional[str] = None - customs_sections: Optional[str] = None - patent: Optional[str] = None - pedimentos: Optional[str] = None - caat: Optional[str] = None - transport_identification: Optional[str] = None - fast_id: Optional[str] = None - operation_type: Optional[str] = None - selected: Optional[bool] = None - user_selected: Optional[str] = None - last_user: Optional[str] = None - responsible: Optional[str] = None - carrier: Optional[str] = None - shipments: Optional[str] = None - pedimento_type: Optional[str] = None - original_chain: Optional[str] = None - serial_number: Optional[str] = None - electronic_signature: Optional[str] = None - transaction_number: Optional[str] = None - status: Optional[str] = None - linq_sat_qr: Optional[str] = None - sat_certificate: Optional[str] = None - sat_digital_seal: Optional[str] = None - xml_doda_sent_path: Optional[str] = None - xml_doda_response_path: Optional[str] = None - doda_report_pdf_path: Optional[str] = None - doda_report_pdf_generated_at: Optional[datetime] = None - doda_report_source_fingerprint: Optional[str] = None - sat_original_chain: Optional[str] = None - customs_clearance: Optional[int] = None - unique_badge_number: Optional[str] = None - containers: List[DodaContainerResponseDTO] = [] - american_pedimentos: List[DodaAmericanPedimentoResponseDTO] = [] - pedimentos_detail: List[DodaPedimentoResponseDTO] = [] - - class Config: - from_attributes = True diff --git a/backend/api/v1/modules/a76/general_catalogs/doda/export_service.py b/backend/api/v1/modules/a76/general_catalogs/doda/export_service.py deleted file mode 100644 index feffe1d3..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/doda/export_service.py +++ /dev/null @@ -1,318 +0,0 @@ -""" -Exportación de listado DODA a CSV / TSV (xls) / pipe, alineada al reporte legacy GDoda. -""" - -from __future__ import annotations - -import csv -import io -from datetime import date, datetime -from enum import Enum -from typing import Any, List, Optional - -from sqlalchemy import and_ -from sqlalchemy.orm import Session - -from .models import Doda, DodaPedimento - -# Encabezados (orden legacy Clarion) -EXPORT_HEADERS: List[str] = [ - "SYSID", - "NUM INTEGRACIÓN", - "FECHA", - "HORA", - "ADUANA", - "ADUANA ES", - "PATENTE", - "PEDIMENTOS", - "CAAT", - "IDEN. TRANSPORTE", - "FAST_ID", - "TIPO OPERACIÓN", - "RESPONSABLE", - "TRANSPORTISTA", - "REMESAS", - "TIPO PEDIMENTO", - "CADENA ORIGINIAL", - "NUMERO SERIE", - "FIRMA ELECTRÓNICA", - "NO TRANSACCIÓN", - "ESTATUS", - "LINQSQTQR", - "SAT_CERTIFICADO", - "SELLO DIGITAL", - "PATH XML ENVÍO", - "PATH XML RESPUESTA", - "SAT_CADENA ORIGINAL", - "DESPACHO ADUANERO", - "GAFETE ÚNICO", - "USUARIO", -] - - -class DodaExportFormat(str, Enum): - csv = "csv" - xls = "xls" - txt = "txt" - - -def _parse_iso_date(s: str) -> date: - s = (s or "").strip() - for fmt in ("%Y-%m-%d", "%Y/%m/%d", "%d/%m/%Y", "%d-%m-%Y"): - try: - return datetime.strptime(s, fmt).date() - except ValueError: - continue - raise ValueError(f"Fecha inválida: {s!r} (use YYYY-MM-DD)") - - -def _date_to_yyyymmdd(d: date) -> int: - return d.year * 10000 + d.month * 100 + d.day - - -def _format_doda_date_formatted(doda_date: Optional[int]) -> str: - if doda_date is None: - return "" - s = str(doda_date) - if len(s) == 8 and s.isdigit(): - y, m, d = s[:4], s[4:6], s[6:8] - return f"{d}/{m}/{y}" - return s - - -def _format_doda_time(doda_time: Optional[int]) -> str: - if doda_time is None: - return "" - t = int(doda_time) - s = str(t) - if len(s) <= 2: - return s - if len(s) == 4: - return f"{s[:2]}:{s[2:4]}" - if len(s) == 6: - return f"{s[:2]}:{s[2:4]}:{s[4:6]}" - if len(s) > 6: - return s[:2] + ":" + s[2:4] + ":" + s[4:6] - return s - - -def _as_text(value: Any) -> str: - if value is None: - return "" - if isinstance(value, bool): - return "1" if value else "0" - s = str(value) - s = s.replace("\r\n", " ").replace("\n", " ").replace("\r", " ") - return s - - -def doda_row_values( - row: Doda, *, date_mode: str -) -> List[str]: - """date_mode: 'raw' | 'formatted' (legacy FechaJul branch).""" - if date_mode == "raw": - fecha = _as_text(row.doda_date) - hora = _as_text(row.doda_time) - else: - fecha = _format_doda_date_formatted(row.doda_date) - hora = _format_doda_time(row.doda_time) - - return [ - _as_text(row.id), - _as_text(row.integration_number), - fecha, - hora, - _as_text(row.dispatch_customs), - _as_text(row.customs_sections), - _as_text(row.patent), - _as_text(row.pedimentos), - _as_text(row.caat), - _as_text(row.transport_identification), - _as_text(row.fast_id), - _as_text(row.operation_type), - _as_text(row.responsible), - _as_text(row.carrier), - _as_text(row.shipments), - _as_text(row.pedimento_type), - _as_text(row.original_chain), - _as_text(row.serial_number), - _as_text(row.electronic_signature), - _as_text(row.transaction_number), - _as_text(row.status), - _as_text(row.linq_sat_qr), - _as_text(row.sat_certificate), - _as_text(row.sat_digital_seal), - _as_text(row.xml_doda_sent_path), - _as_text(row.xml_doda_response_path), - _as_text(row.sat_original_chain), - _as_text(row.customs_clearance), - _as_text(row.unique_badge_number), - _as_text(row.last_user), - ] - - -def _delimiter_for_format(fmt: DodaExportFormat) -> str: - if fmt == DodaExportFormat.csv: - return "," - if fmt == DodaExportFormat.xls: - return "\t" - if fmt == DodaExportFormat.txt: - return "|" - return "," - - -def _content_type_and_filename(fmt: DodaExportFormat) -> tuple[str, str]: - if fmt == DodaExportFormat.csv: - return "text/csv; charset=utf-8", "doda_export.csv" - if fmt == DodaExportFormat.xls: - return "application/vnd.ms-excel; charset=utf-8", "doda_export.xls" - return "text/plain; charset=utf-8", "doda_export.txt" - - -def list_dodas_in_date_range( - db: Session, - *, - tenant_id: int, - company_id: int, - date_start: int, - date_end: int, -) -> List[Doda]: - return ( - db.query(Doda) - .filter( - and_( - Doda.tenant_id == tenant_id, - Doda.company_id == company_id, - Doda.doda_date.isnot(None), - Doda.doda_date >= date_start, - Doda.doda_date <= date_end, - ) - ) - .order_by(Doda.doda_date.asc(), Doda.id.asc()) - .all() - ) - - -def build_export_text( - rows: List[Doda], - *, - export_format: DodaExportFormat, - date_mode: str = "formatted", -) -> str: - delim = _delimiter_for_format(export_format) - out = io.StringIO() - w = csv.writer( - out, - delimiter=delim, - quoting=csv.QUOTE_MINIMAL, - lineterminator="\r\n", - ) - w.writerow(EXPORT_HEADERS) - for r in rows: - w.writerow(doda_row_values(r, date_mode=date_mode)) - return out.getvalue() - - -def parse_export_params( - date_from: str, - date_to: str, - format_str: str, - date_mode: str, -) -> tuple[int, int, DodaExportFormat, str]: - d0 = _date_to_yyyymmdd(_parse_iso_date(date_from)) - d1 = _date_to_yyyymmdd(_parse_iso_date(date_to)) - if d0 > d1: - raise ValueError("date_from no puede ser posterior a date_to") - try: - fmt = DodaExportFormat(format_str.lower().strip()) - except ValueError: - raise ValueError("format debe ser csv, xls o txt") - mode = (date_mode or "formatted").lower().strip() - if mode not in ("raw", "formatted"): - raise ValueError("date_mode debe ser raw o formatted") - return d0, d1, fmt, mode - - -# --- Exportación de pedimentos (líneas) de un DODA específico (legacy / pantalla) --- - -PEDIMENTO_EXPORT_HEADERS: List[str] = [ - "PATENTE", - "DOCUMENTO", - "ACUSE_VA", - "REMESA", - "CANTIDAD", - "IMPORTE_USD", - "IMPORTE_DIF_USD", - "NIU", - "ARTICULO", -] - - -def _as_decimal_text(value: Any) -> str: - if value is None: - return "" - return _as_text(value) - - -def pedimento_row_values(row: DodaPedimento) -> List[str]: - """ - ACUSE_VA = COVE; CANTIDAD = UMC (captura típica en listado); - IMPORTE_USD / IMPORTE_DIF_USD = montos en USD; ARTICULO = art. 7 (0/1). - """ - return [ - _as_text(row.authorization_patent), - _as_text(row.document), - _as_text(row.cove), - _as_text(row.shipment), - _as_text(row.umc), - _as_decimal_text(row.effective_amount_usd), - _as_decimal_text(row.difference_amount_usd), - _as_text(row.dta_niu), - _as_text(row.article_7), - ] - - -def list_pedimentos_for_doda_export( - db: Session, - *, - tenant_id: int, - company_id: int, - doda_id: int, -) -> List[DodaPedimento]: - return ( - db.query(DodaPedimento) - .join(Doda, DodaPedimento.doda_id == Doda.id) - .filter( - Doda.id == doda_id, - Doda.tenant_id == tenant_id, - Doda.company_id == company_id, - ) - .order_by(DodaPedimento.pedimento_line.asc()) - .all() - ) - - -def build_pedimentos_export_text( - rows: List[DodaPedimento], - *, - export_format: DodaExportFormat, -) -> str: - delim = _delimiter_for_format(export_format) - out = io.StringIO() - w = csv.writer( - out, - delimiter=delim, - quoting=csv.QUOTE_MINIMAL, - lineterminator="\r\n", - ) - w.writerow(PEDIMENTO_EXPORT_HEADERS) - for r in rows: - w.writerow(pedimento_row_values(r)) - return out.getvalue() - - -def parse_pedimento_export_format(format_str: str) -> DodaExportFormat: - try: - return DodaExportFormat(format_str.lower().strip()) - except ValueError as e: - raise ValueError("format debe ser csv, xls o txt") from e diff --git a/backend/api/v1/modules/a76/general_catalogs/doda/external_service.py b/backend/api/v1/modules/a76/general_catalogs/doda/external_service.py deleted file mode 100644 index 85970890..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/doda/external_service.py +++ /dev/null @@ -1,107 +0,0 @@ -from __future__ import annotations - -import logging -from typing import Any, Dict - -import httpx - -from core.config import settings - -logger = logging.getLogger(__name__) - - -class DodaExternalService: - """ - Cliente HTTP para el servicio externo de alta DODA (API de Ventanilla Única). - - Endpoints: - POST {base_url}/api/v1/doda/alta - GET {base_url}/api/v1/doda/alta-status/{task_id} - POST {base_url}/api/v1/doda/consulta - GET {base_url}/api/v1/doda/consulta-status/{task_id} - POST {base_url}/api/v1/doda/eliminar - GET {base_url}/api/v1/doda/eliminar-status/{task_id} - - Usa COVE_API_URL como URL base (la misma variable que COVE y Expediente). - """ - - def __init__(self) -> None: - self.base_url = settings.COVE_API_URL.strip() - self.verify_ssl = settings.COVE_API_VERIFY_SSL - - def post_alta(self, payload: Dict[str, Any]) -> Dict[str, Any]: - """ - Envía el payload de alta DODA al servicio externo. - Retorna {task_id, status, message}. - """ - url = f"{self.base_url.rstrip('/')}/api/v1/doda/alta" - - configuracion_vu = payload.get("configuracion_vu") or {} - logger.info( - "Enviando alta DODA: rfc_ciec=%s cer_len=%s key_len=%s clave_fiel_len=%s", - configuracion_vu.get("rfc_ciec"), - len(configuracion_vu.get("archivo_cer_base64") or ""), - len(configuracion_vu.get("archivo_key_base64") or ""), - len(configuracion_vu.get("clave_fiel") or ""), - ) - - with httpx.Client( - timeout=httpx.Timeout(60.0, connect=10.0), verify=self.verify_ssl - ) as client: - response = client.post(url, json=payload) - response.raise_for_status() - return response.json() - - def get_status(self, task_id: str) -> Dict[str, Any]: - """ - Consulta el estado de una tarea de alta DODA en el servicio externo. - """ - url = f"{self.base_url.rstrip('/')}/api/v1/doda/alta-status/{task_id}" - logger.debug("Consultando estado tarea DODA: task_id=%s url=%s", task_id, url) - - with httpx.Client( - timeout=httpx.Timeout(30.0, connect=10.0), verify=self.verify_ssl - ) as client: - response = client.get(url) - response.raise_for_status() - return response.json() - - def post_consulta(self, payload: Dict[str, Any]) -> Dict[str, Any]: - """Envia consulta DODA y retorna {task_id, status, message}.""" - url = f"{self.base_url.rstrip('/')}/api/v1/doda/consulta" - with httpx.Client( - timeout=httpx.Timeout(60.0, connect=10.0), verify=self.verify_ssl - ) as client: - response = client.post(url, json=payload) - response.raise_for_status() - return response.json() - - def get_consulta_status(self, task_id: str) -> Dict[str, Any]: - """Consulta el estado de una tarea de consulta DODA.""" - url = f"{self.base_url.rstrip('/')}/api/v1/doda/consulta-status/{task_id}" - with httpx.Client( - timeout=httpx.Timeout(30.0, connect=10.0), verify=self.verify_ssl - ) as client: - response = client.get(url) - response.raise_for_status() - return response.json() - - def post_eliminar(self, payload: Dict[str, Any]) -> Dict[str, Any]: - """Envia eliminacion DODA y retorna {task_id, status, message}.""" - url = f"{self.base_url.rstrip('/')}/api/v1/doda/eliminar" - with httpx.Client( - timeout=httpx.Timeout(60.0, connect=10.0), verify=self.verify_ssl - ) as client: - response = client.post(url, json=payload) - response.raise_for_status() - return response.json() - - def get_eliminar_status(self, task_id: str) -> Dict[str, Any]: - """Consulta el estado de una tarea de eliminacion DODA.""" - url = f"{self.base_url.rstrip('/')}/api/v1/doda/eliminar-status/{task_id}" - with httpx.Client( - timeout=httpx.Timeout(30.0, connect=10.0), verify=self.verify_ssl - ) as client: - response = client.get(url) - response.raise_for_status() - return response.json() diff --git a/backend/api/v1/modules/a76/general_catalogs/doda/fingerprint.py b/backend/api/v1/modules/a76/general_catalogs/doda/fingerprint.py deleted file mode 100644 index 0687b871..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/doda/fingerprint.py +++ /dev/null @@ -1,132 +0,0 @@ -""" -Huella de contenido (fingerprint) para invalidar el PDF de reporte DODA. -""" - -from __future__ import annotations - -import hashlib -import json -from datetime import date, datetime -from decimal import Decimal -from typing import Any, Dict - -from sqlalchemy import inspect as sa_inspect -from sqlalchemy.orm import Session - -from .models import ( - Doda, - DodaAmericanPedimento, - DodaContainer, - DodaContainerSeal, - DodaPedimento, -) - -_DODA_FINGERPRINT_EXCLUDE = frozenset( - { - "doda_report_pdf_path", - "doda_report_pdf_generated_at", - "doda_report_source_fingerprint", - "created_at", - "updated_at", - "deleted_at", - } -) - - -def _json_default(obj: Any) -> Any: - if isinstance(obj, Decimal): - return str(obj) - if isinstance(obj, (datetime, date)): - return obj.isoformat() - if isinstance(obj, (bytes, bytearray)): - return obj.hex() - raise TypeError(f"Type {type(obj)} not serializable") - - -def _instance_payload(instance: object, *, exclude: frozenset[str]) -> Dict[str, Any]: - insp = sa_inspect(instance) - d: Dict[str, Any] = {} - for col in insp.mapper.column_attrs: - name = col.key - if name in exclude or name in _DODA_FINGERPRINT_EXCLUDE: - continue - d[name] = getattr(instance, name) - return d - - -def build_doda_fingerprint(db: Session, doda_id: int) -> str: - doda = db.get(Doda, doda_id) - if doda is None: - raise ValueError("DODA no encontrado") - - doda_block = _instance_payload(doda, exclude=frozenset()) - - containers_rows = ( - db.query(DodaContainer) - .filter(DodaContainer.doda_id == doda_id) - .order_by(DodaContainer.container_line.asc()) - .all() - ) - container_blocks: list[Dict[str, Any]] = [] - for c in containers_rows: - c_block = _instance_payload( - c, - exclude=frozenset( - { - "id", - "doda_id", - } - ), - ) - seals = ( - db.query(DodaContainerSeal) - .filter(DodaContainerSeal.container_id == c.id) - .order_by(DodaContainerSeal.seal_line.asc()) - .all() - ) - c_block["seals"] = [ - _instance_payload( - s, - exclude=frozenset({"id", "container_id", "doda_id"}), - ) - for s in seals - ] - container_blocks.append(c_block) - - american = ( - db.query(DodaAmericanPedimento) - .filter(DodaAmericanPedimento.doda_id == doda_id) - .order_by(DodaAmericanPedimento.american_pedimento_line.asc()) - .all() - ) - american_blocks = [ - _instance_payload( - p, exclude=frozenset({"id", "doda_id"}) - ) - for p in american - ] - - pedimentos = ( - db.query(DodaPedimento) - .filter(DodaPedimento.doda_id == doda_id) - .order_by(DodaPedimento.pedimento_line.asc()) - .all() - ) - pedimento_blocks = [ - _instance_payload(p, exclude=frozenset({"id", "doda_id"})) for p in pedimentos - ] - - snapshot: Dict[str, Any] = { - "doda": doda_block, - "containers": container_blocks, - "american_pedimentos": american_blocks, - "pedimentos_detail": pedimento_blocks, - } - raw = json.dumps( - snapshot, - sort_keys=True, - ensure_ascii=True, - separators=(",", ":"), - default=_json_default, - ) - return hashlib.sha256(raw.encode("utf-8")).hexdigest() diff --git a/backend/api/v1/modules/a76/general_catalogs/doda/models.py b/backend/api/v1/modules/a76/general_catalogs/doda/models.py deleted file mode 100644 index 18bc0f9a..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/doda/models.py +++ /dev/null @@ -1,269 +0,0 @@ -""" -Modelos ORM para gestión de DODA (Documentos de Operación de Aduana) -""" - -from datetime import datetime -from typing import Optional - -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base -from sqlalchemy import ( - ForeignKeyConstraint, - DateTime, - Integer, - PrimaryKeyConstraint, - String, - Text, - Numeric, - Boolean, -) -from sqlalchemy.orm import Mapped, mapped_column, relationship - - -class Doda(Base, TenantScopedMixin, TimestampMixin): - """ - Modelo para la tabla Doda - Documentos de Operación de Aduana - """ - - __tablename__ = "doda" # gDODA - __table_args__ = ( - PrimaryKeyConstraint("id", name="doda_pkey"), - {"schema": "a76"}, - ) - - # Primary key - id: Mapped[int] = mapped_column( - Integer, primary_key=True, autoincrement=True) - - # Integration and timestamps - integration_number: Mapped[Optional[str]] = mapped_column(String(30)) - doda_date: Mapped[Optional[int]] = mapped_column(Integer) - doda_time: Mapped[Optional[int]] = mapped_column(Integer) - - # Customs information - dispatch_customs: Mapped[Optional[str]] = mapped_column(String(3)) - customs_sections: Mapped[Optional[str]] = mapped_column(String(3)) - patent: Mapped[Optional[str]] = mapped_column(String(4)) - pedimentos: Mapped[Optional[str]] = mapped_column(String(80)) - caat: Mapped[Optional[str]] = mapped_column(String(10)) - - # Transport and identifiers - transport_identification: Mapped[Optional[str]] = mapped_column(String(20)) - fast_id: Mapped[Optional[str]] = mapped_column(String(20)) - operation_type: Mapped[Optional[str]] = mapped_column(String(1)) - - # Selection info - selected: Mapped[Optional[bool]] = mapped_column(Boolean) - user_selected: Mapped[Optional[str]] = mapped_column(String(30)) - last_user: Mapped[Optional[str]] = mapped_column(String(30)) - - # Responsible parties - responsible: Mapped[Optional[str]] = mapped_column(String(14)) - carrier: Mapped[Optional[str]] = mapped_column(String(8)) - shipments: Mapped[Optional[str]] = mapped_column(String(80)) - pedimento_type: Mapped[Optional[str]] = mapped_column(String(30)) - - # Digital signatures and certificates - original_chain: Mapped[Optional[str]] = mapped_column(String(5000)) - serial_number: Mapped[Optional[str]] = mapped_column(String(21)) - electronic_signature: Mapped[Optional[str]] = mapped_column(String(2000)) - - # Transaction info - transaction_number: Mapped[Optional[str]] = mapped_column(String(30)) - status: Mapped[Optional[str]] = mapped_column(String(30)) - - # SAT information - linq_sat_qr: Mapped[Optional[str]] = mapped_column(String(1000)) - sat_certificate: Mapped[Optional[str]] = mapped_column(String(2001)) - sat_digital_seal: Mapped[Optional[Text]] = mapped_column(Text) - - # XML Paths - xml_doda_sent_path: Mapped[Optional[str]] = mapped_column(String(1000)) - xml_doda_response_path: Mapped[Optional[str]] = mapped_column(String(1000)) - - # Reporte PDF (S3) + caché por huella de contenido - doda_report_pdf_path: Mapped[Optional[str]] = mapped_column(String(1000)) - doda_report_pdf_generated_at: Mapped[Optional[datetime]] = mapped_column(DateTime) - doda_report_source_fingerprint: Mapped[Optional[str]] = mapped_column(String(64)) - - # SAT original chain - sat_original_chain: Mapped[Optional[Text]] = mapped_column(Text) - - # Customs clearance - customs_clearance: Mapped[Optional[int]] = mapped_column(Integer) - unique_badge_number: Mapped[Optional[str]] = mapped_column(String(250)) - - # Relationships - containers: Mapped[list["DodaContainer"]] = relationship( - "DodaContainer", back_populates="doda", cascade="all, delete-orphan" - ) - american_pedimentos: Mapped[list["DodaAmericanPedimento"]] = relationship( - "DodaAmericanPedimento", back_populates="doda", cascade="all, delete-orphan" - ) - pedimentos_detail: Mapped[list["DodaPedimento"]] = relationship( - "DodaPedimento", back_populates="doda", cascade="all, delete-orphan" - ) - - def __repr__(self): - return f"" - - -class DodaContainer(Base, TenantScopedMixin, TimestampMixin): - """ - Modelo para la tabla DodaContainer - Contenedores en DODA - """ - - __tablename__ = "doda_containers" # gDoda_Contenedores - __table_args__ = ( - PrimaryKeyConstraint("id", name="doda_containers_pkey"), - ForeignKeyConstraint( - ["doda_id"], ["a76.doda.id"], name="fk_doda_containers_doda" - ), - {"schema": "a76"}, - ) - - # Primary key - id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) - - # Foreign key and line number - doda_id: Mapped[int] = mapped_column(Integer, nullable=False) - container_line: Mapped[int] = mapped_column(Integer, nullable=False) - - # Container information - container_value: Mapped[Optional[str]] = mapped_column(String(20)) - seals: Mapped[Optional[str]] = mapped_column(String(254)) - - # Relationships - doda: Mapped["Doda"] = relationship("Doda", back_populates="containers") - seals_detail: Mapped[list["DodaContainerSeal"]] = relationship( - "DodaContainerSeal", back_populates="container", cascade="all, delete-orphan" - ) - - def __repr__(self): - return f"" - - -class DodaContainerSeal(Base, TenantScopedMixin, TimestampMixin): - """ - Modelo para la tabla DodaContainerSeal - Candados de Contenedores - """ - - __tablename__ = "doda_container_seals" # gDoda_Contenedores_Candados - __table_args__ = ( - PrimaryKeyConstraint("id", name="doda_container_seals_pkey"), - ForeignKeyConstraint( - ["container_id"], ["a76.doda_containers.id"], name="fk_doda_container_seals_container" - ), - {"schema": "a76"}, - ) - - - id: Mapped[int] = mapped_column( - Integer, primary_key=True, autoincrement=True) - - - container_id: Mapped[int] = mapped_column(Integer, nullable=False) - doda_id: Mapped[int] = mapped_column(Integer, nullable=False) - seal_line: Mapped[int] = mapped_column(Integer, nullable=False) - - - seal_value: Mapped[Optional[str]] = mapped_column(String(21)) - - - container: Mapped["DodaContainer"] = relationship( - "DodaContainer", back_populates="seals_detail" - ) - - def __repr__(self): - return f"" - - -class DodaAmericanPedimento(Base, TenantScopedMixin, TimestampMixin): - """ - Modelo para la tabla DodaAmericanPedimento - Pedimentos Americanos en DODA - """ - - __tablename__ = "doda_american_pedimentos" # gDoda_PedimentoAmericano - __table_args__ = ( - PrimaryKeyConstraint("id", name="doda_american_pedimentos_pkey"), - ForeignKeyConstraint( - ["doda_id"], ["a76.doda.id"], name="fk_doda_american_pedimentos_doda" - ), - {"schema": "a76"}, - ) - - # Primary key - id: Mapped[int] = mapped_column( - Integer, primary_key=True, autoincrement=True) - - # Foreign key and line number - doda_id: Mapped[int] = mapped_column(Integer, nullable=False) - american_pedimento_line: Mapped[int] = mapped_column( - Integer, nullable=False) - - # American pedimento information - american_pedimento_type: Mapped[Optional[str]] = mapped_column(String(2)) - american_pedimento_value: Mapped[Optional[str]] = mapped_column(String(20)) - - # Relationships - doda: Mapped["Doda"] = relationship( - "Doda", back_populates="american_pedimentos") - - def __repr__(self): - return f"" - - -class DodaPedimento(Base, TenantScopedMixin, TimestampMixin): - """ - Modelo para la tabla DodaPedimento - Pedimentos en DODA - """ - - __tablename__ = "doda_pedimentos" # gDoda_Pedimentos - __table_args__ = ( - PrimaryKeyConstraint("id", name="doda_pedimentos_pkey"), - ForeignKeyConstraint( - ["doda_id"], ["a76.doda.id"], name="fk_doda_pedimentos_doda" - ), - {"schema": "a76"}, - ) - - # Primary key - id: Mapped[int] = mapped_column( - Integer, primary_key=True, autoincrement=True) - - # Foreign key and line number - doda_id: Mapped[int] = mapped_column(Integer, nullable=False) - pedimento_line: Mapped[int] = mapped_column(Integer, nullable=False) - - # Authorization and document info - authorization_patent: Mapped[Optional[str]] = mapped_column(String(10)) - document: Mapped[Optional[str]] = mapped_column(String(50)) - shipment: Mapped[Optional[str]] = mapped_column(String(11)) - - # Commercial information - cove: Mapped[Optional[str]] = mapped_column(String(50)) - umc: Mapped[Optional[str]] = mapped_column(String(20)) - - # Financial information - effective_amount_usd: Mapped[Optional[Numeric] - ] = mapped_column(Numeric(15, 2)) - difference_amount_usd: Mapped[Optional[Numeric] - ] = mapped_column(Numeric(15, 2)) - - # Additional identifiers - dta_niu: Mapped[Optional[str]] = mapped_column(String(20)) - article_7: Mapped[Optional[bool]] = mapped_column(Boolean) - pedimento_id: Mapped[Optional[int]] = mapped_column(Integer) - invoice_line: Mapped[Optional[int]] = mapped_column(Integer) - part_ii_line: Mapped[Optional[int]] = mapped_column(Integer) - - # Type and validation - pedimento_type: Mapped[Optional[str]] = mapped_column(String(20)) - zero_packaging_validation: Mapped[Optional[bool]] = mapped_column(Boolean) - - # Relationships - doda: Mapped["Doda"] = relationship( - "Doda", back_populates="pedimentos_detail") - - def __repr__(self): - return f"" diff --git a/backend/api/v1/modules/a76/general_catalogs/doda/payload_normalizer.py b/backend/api/v1/modules/a76/general_catalogs/doda/payload_normalizer.py deleted file mode 100644 index 88dd91d3..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/doda/payload_normalizer.py +++ /dev/null @@ -1,128 +0,0 @@ -""" -Normaliza valores de negocio hacia el JSON del servicio DODA externo. -Solo cadenas limpias: sin mezclar claves de catálogo (broker_key, vehicle_key) como -sustituto de patente, CAAT o id de transporte cuando deban ser otros datos. -""" -from __future__ import annotations - -import re -from typing import Any, Dict, Optional - -# Aduana-Patente-Pedimento (3 segmentos; patente 4, pedimento alfanum limpio a dígitos) -_PEDIMENTO_DOC_SPLIT = re.compile(r"^[\s\-–—]*([^\s\-–—]+)[\s\-–—]+([^\s\-–—]+)[\s\-–—]+([^\s\-–—]+)[\s\-–—]*$") - - -def _digits_only(s: str, max_len: int) -> str: - d = re.sub(r"\D", "", s or "") - if max_len and len(d) > max_len: - return d[-max_len:] - return d - - -def normalize_aduana_despacho(value: Optional[str]) -> str: - s = (value or "").strip() - if not s: - return "" - return _digits_only(s, 3).zfill(3) if _digits_only(s, 3) else s[:3] - - -def normalize_aduana_seccion(value: Optional[str]) -> str: - s = (value or "").strip() - if not s: - return "" - d = _digits_only(s, 3) - if d: - return d.zfill(3) - return s[:3] - - -def normalize_patente(value: Optional[str]) -> str: - s = (re.sub(r"[\s\u00A0]+", " ", (value or "").strip())) - d = re.sub(r"\D", "", s) - if len(d) >= 4: - return d[-4:] - return s[:4] if s else "" - - -def normalize_caat(value: Optional[str]) -> str: - return (value or "").strip()[:20] - - -def normalize_id_transporte(value: Optional[str]) -> str: - return (re.sub(r"\s+", " ", (value or "").strip()))[:20] - - -def normalize_tipo_operacion(value: Optional[str]) -> str: - v = (value or "").strip().upper()[:1] - return v if v in ("I", "E") else v - - -def normalize_numero_gafete(value: Optional[str]) -> str: - return (value or "").strip()[:250] - - -def normalize_fast_id(value: Optional[str]) -> str: - return (value or "").strip()[:20] - - -def _parse_document_pedimento( - document: Optional[str], authorization_patent: Optional[str] -) -> tuple[str, str]: - """ - Retorna (patente_4, número_pedimento) a partir de documento o patente de autorización. - """ - doc = (document or "").strip() - m = _PEDIMENTO_DOC_SPLIT.match(doc) - if m: - b, c = m.group(2), m.group(3) - pat = re.sub(r"\D", "", b) - if len(pat) > 4: - pat = pat[-4:] - else: - pat = pat.zfill(4) if pat else "" - if not pat: - ap = (authorization_patent or "").strip() - pat = re.sub(r"\D", "", ap)[-4:].zfill(4) if ap else "" - ped = re.sub(r"[^\d\w]", "", c) or re.sub(r"\D", "", c) - return (pat[:4], ped) - ped = re.sub(r"[^\d]", "", doc) if doc else "" - ap = (authorization_patent or "").strip() - pat4 = re.sub(r"\D", "", ap)[:4] if ap else "" - if len(pat4) < 4 and ap and ap.isalnum(): - pat4 = (re.sub(r"\D", "", ap) + "0000")[:4] if re.sub(r"\D", "", ap) else (ap + "0")[:4] - return (pat4, ped) - - -def normalize_doda_pedimento_row( - document: Optional[str], - authorization_patent: Optional[str], - shipment: Optional[str], - cove: Optional[str], - umc: Optional[str], - dta_niu: Optional[str], - pedimento_type: Optional[str], - effective: Any, - diff: Any, -) -> Dict[str, Any]: - ap_raw = (authorization_patent or "").strip() - pat, ped = _parse_document_pedimento(document, ap_raw) - if not pat and ap_raw: - d = re.sub(r"\D", "", ap_raw) - pat = d[-4:].zfill(4) if d else ap_raw[:4] - - rem = (shipment or "").strip()[:11] if (shipment or "").strip() else "" - if not rem and ped: - rem = ped - - return { - "patente": pat, - "pedimento": ped, - "numero_remesa": rem, - "tipo_pedimento": (pedimento_type or "")[:20], - "dta_niu": (dta_niu or "")[:20], - "importe_efectivo_dolares": float(effective or 0) if effective is not None else 0.0, - "importe_diferencia_dolares": float(diff or 0) if diff is not None else 0.0, - "campo_12_apendice_17": 0, - "cove": (cove or "")[:50], - "umc": (umc or "")[:20], - } diff --git a/backend/api/v1/modules/a76/general_catalogs/doda/print_cache.py b/backend/api/v1/modules/a76/general_catalogs/doda/print_cache.py deleted file mode 100644 index 7d1df83d..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/doda/print_cache.py +++ /dev/null @@ -1,75 +0,0 @@ -""" -Caché del PDF de reporte DODA (S3 + columnas en a76.doda) e invalidación. -""" - -from __future__ import annotations - -import logging -from typing import Optional - -from sqlalchemy.orm import Session - -from core.config import settings -from core.s3_keys import doda_report_pdf_key -from core import storage_s3 - -from .models import Doda - -logger = logging.getLogger(__name__) - - -def _delete_stored_s3_key(key: Optional[str]) -> None: - if not key or not settings.use_s3_object_storage: - return - storage_s3.delete_object_if_exists(key) - - -def clear_doda_report_fields(doda: Doda) -> None: - doda.doda_report_pdf_path = None - doda.doda_report_pdf_generated_at = None - doda.doda_report_source_fingerprint = None - - -def touch_invalidate_doda_report( - db: Session, - *, - tenant_id: int, - company_id: int, - doda_id: int, - doda: Optional[Doda] = None, -) -> None: - """ - Borra el PDF previo en S3 (si aplica) y limpia columnas de caché en el DODA. - Llamar tras mutaciones de DODA o de tablas hijas. - """ - if doda is None: - doda = ( - db.query(Doda) - .filter( - Doda.id == doda_id, - Doda.tenant_id == tenant_id, - Doda.company_id == company_id, - ) - .first() - ) - if not doda: - return - - keys: set[str] = set() - if doda.doda_report_pdf_path: - keys.add(doda.doda_report_pdf_path) - try: - keys.add(doda_report_pdf_key(tenant_id, company_id, doda_id)) - except ValueError: - pass - - for k in keys: - _delete_stored_s3_key(k) - - clear_doda_report_fields(doda) - try: - db.add(doda) - db.commit() - except Exception: - db.rollback() - logger.exception("Error invalidating DODA report cache doda_id=%s", doda_id) diff --git a/backend/api/v1/modules/a76/general_catalogs/doda/report_service.py b/backend/api/v1/modules/a76/general_catalogs/doda/report_service.py deleted file mode 100644 index 6aaa2696..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/doda/report_service.py +++ /dev/null @@ -1,205 +0,0 @@ -""" -Generación de PDF de reporte DODA (Jinja2 + pdfkit / wkhtmltopdf). -""" - -from __future__ import annotations - -import json -import logging -import shutil -from dataclasses import asdict, dataclass -from pathlib import Path -from typing import Any, List, Optional - -import pdfkit -from jinja2 import Environment, FileSystemLoader, select_autoescape -from sqlalchemy.orm import Session - -from .fingerprint import build_doda_fingerprint -from .models import ( - Doda, - DodaAmericanPedimento, - DodaContainer, - DodaContainerSeal, - DodaPedimento, -) - -logger = logging.getLogger(__name__) - - -@dataclass -class _SealView: - seal_line: int - seal_value: str - - -@dataclass -class _ContainerView: - container_line: int - container_value: str - seals: List[_SealView] - - -@dataclass -class _AmericanView: - american_pedimento_line: int - american_pedimento_type: str - american_pedimento_value: str - - -@dataclass -class _PedimentoView: - pedimento_line: int - authorization_patent: str - document: str - shipment: str - cove: str - umc: str - pedimento_type: str - - -def _s(v: Optional[str]) -> str: - if v is None: - return "" - return str(v).strip() - - -class DodaReportPdfService: - def __init__(self) -> None: - self.template_dir = Path(__file__).parent / "templates" - self.jinja_env = Environment( - loader=FileSystemLoader(self.template_dir), - autoescape=select_autoescape(["html", "xml"]), - ) - self._tpl = self.jinja_env.get_template("doda_report.html") - - def _get_wkhtmltopdf_config(self): - for path in ( - shutil.which("wkhtmltopdf"), - "/usr/local/bin/wkhtmltopdf", - "/usr/bin/wkhtmltopdf", - ): - if path: - return pdfkit.configuration(wkhtmltopdf=path) - raise RuntimeError("wkhtmltopdf binary not found.") - - @staticmethod - def _doda_header_block(d: Doda) -> dict[str, str]: - return { - "integration_number": _s(d.integration_number), - "patent": _s(d.patent), - "dispatch_customs": _s(d.dispatch_customs), - "customs_sections": _s(d.customs_sections), - "caat": _s(d.caat), - "transport_identification": _s(d.transport_identification), - "fast_id": _s(d.fast_id), - "operation_type": _s(d.operation_type), - "status": _s(d.status), - "transaction_number": _s(d.transaction_number), - } - - def _load_children(self, db: Session, doda_id: int) -> dict[str, Any]: - containers = ( - db.query(DodaContainer) - .filter(DodaContainer.doda_id == doda_id) - .order_by(DodaContainer.container_line.asc()) - .all() - ) - cviews: list[_ContainerView] = [] - for c in containers: - seals = ( - db.query(DodaContainerSeal) - .filter(DodaContainerSeal.container_id == c.id) - .order_by(DodaContainerSeal.seal_line.asc()) - .all() - ) - cviews.append( - _ContainerView( - container_line=c.container_line, - container_value=_s(c.container_value), - seals=[ - _SealView( - seal_line=s.seal_line, seal_value=_s(s.seal_value) - ) - for s in seals - ], - ) - ) - - american = ( - db.query(DodaAmericanPedimento) - .filter(DodaAmericanPedimento.doda_id == doda_id) - .order_by(DodaAmericanPedimento.american_pedimento_line.asc()) - .all() - ) - amer_views = [ - _AmericanView( - american_pedimento_line=p.american_pedimento_line, - american_pedimento_type=_s(p.american_pedimento_type), - american_pedimento_value=_s(p.american_pedimento_value), - ) - for p in american - ] - - peds = ( - db.query(DodaPedimento) - .filter(DodaPedimento.doda_id == doda_id) - .order_by(DodaPedimento.pedimento_line.asc()) - .all() - ) - ped_views = [ - _PedimentoView( - pedimento_line=p.pedimento_line, - authorization_patent=_s(p.authorization_patent), - document=_s(p.document), - shipment=_s(p.shipment), - cove=_s(p.cove), - umc=_s(p.umc), - pedimento_type=_s(p.pedimento_type), - ) - for p in peds - ] - - return { - "containers": [asdict(x) for x in cviews], - "american_pedimentos": [asdict(x) for x in amer_views], - "pedimentos_detail": [asdict(x) for x in ped_views], - } - - def build_context(self, db: Session, doda: Doda) -> dict[str, Any]: - fp = build_doda_fingerprint(db, doda.id) - children = self._load_children(db, doda.id) - return { - "doda": self._doda_header_block(doda), - "linq_sat_qr": _s(d.linq_sat_qr), - "sat_chain_preview": _s((d.sat_original_chain or d.original_chain) or "")[:2000], - "sat_digital_seal_preview": _s(d.sat_digital_seal)[:2000], - "fingerprint_sha256": fp, - **children, - } - - def render_pdf_bytes(self, context: dict[str, Any]) -> bytes: - html = self._tpl.render(**context) - options = { - "page-size": "A4", - "encoding": "UTF-8", - "margin-top": "12mm", - "margin-bottom": "12mm", - "margin-left": "10mm", - "margin-right": "10mm", - } - return pdfkit.from_string( - html, - False, - options=options, - configuration=self._get_wkhtmltopdf_config(), - ) - - def build_pdf_for_doda(self, db: Session, doda: Doda) -> bytes: - ctx = self.build_context(db, doda) - return self.render_pdf_bytes(ctx) - - @staticmethod - def debug_json_snapshot(context: dict[str, Any]) -> str: - """Para depuración: snapshot legible (no contiene el sello completo).""" - return json.dumps(context, ensure_ascii=True, indent=2) diff --git a/backend/api/v1/modules/a76/general_catalogs/doda/routes.py b/backend/api/v1/modules/a76/general_catalogs/doda/routes.py deleted file mode 100644 index bec0c7c5..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/doda/routes.py +++ /dev/null @@ -1,1132 +0,0 @@ -""" -Rutas para gestión de DODA (Documentos de Operación de Aduana) -""" - -import io -import logging -from datetime import datetime, timezone -from typing import Any, Dict, List - -from fastapi import APIRouter, Depends, HTTPException, Query, status -from fastapi.responses import StreamingResponse -from sqlalchemy.orm import Session - -from core import storage_s3 -from core.config import settings -from core.database import get_core_db -from core.s3_keys import doda_report_pdf_key -from api.v1.common.tenant_crud_routes import TenantCRUDRoutes -from .dto import ( - DodaCreateDTO, - DodaResponseDTO, - DodaUpdateDTO, - DodaDetailResponseDTO, - DodaContainerCreateDTO, - DodaContainerResponseDTO, - DodaContainerUpdateDTO, - DodaContainerSealCreateDTO, - DodaContainerSealResponseDTO, - DodaAmericanPedimentoCreateDTO, - DodaAmericanPedimentoResponseDTO, - DodaAmericanPedimentoUpdateDTO, - DodaPedimentoCreateDTO, - DodaPedimentoResponseDTO, - DodaPedimentoUpdateDTO, -) -from .models import Doda -from .service import DodaService -from .alta_service import DodaAltaService -from .external_service import DodaExternalService -from .alta_log_dto import ( - DodaAltaLogCreateDTO, - DodaAltaLogListResponse, - DodaAltaLogResponseDTO, - DodaAltaLogUpdateDTO, -) -from .alta_log_service import DodaAltaLogService -from .fingerprint import build_doda_fingerprint -from .print_cache import touch_invalidate_doda_report -from .report_service import DodaReportPdfService -from .export_service import ( - build_export_text, - build_pedimentos_export_text, - list_dodas_in_date_range, - list_pedimentos_for_doda_export, - parse_export_params, - parse_pedimento_export_format, - _content_type_and_filename, -) -from core.security import get_current_user, validate_access_to_resource - -logger = logging.getLogger(__name__) - - -def _coalesce_external_result_payload(payload: Dict[str, Any]) -> Dict[str, Any]: - result = payload.get("result") - if isinstance(result, dict): - return result - return payload - - -def _apply_consulta_success_to_doda( - doda: Doda, - payload: Dict[str, Any], -) -> None: - source = _coalesce_external_result_payload(payload) - mapping = { - "integration_number": "integration_number", - "numero_integracion": "integration_number", - "transaction_number": "transaction_number", - "numero_transaccion": "transaction_number", - "sat_digital_seal": "sat_digital_seal", - "sello_digital_sat": "sat_digital_seal", - "sat_certificate": "sat_certificate", - "certificado_sat": "sat_certificate", - "serial_number": "serial_number", - "numero_serie": "serial_number", - "electronic_signature": "electronic_signature", - "firma_electronica": "electronic_signature", - "original_chain": "original_chain", - "cadena_original": "original_chain", - "sat_original_chain": "sat_original_chain", - "cadena_original_sat": "sat_original_chain", - "linq_sat_qr": "linq_sat_qr", - "link_sat_qr": "linq_sat_qr", - "xml_doda_sent_path": "xml_doda_sent_path", - "xml_doda_response_path": "xml_doda_response_path", - "status": "status", - } - for src_key, dst_attr in mapping.items(): - value = source.get(src_key) - if value is not None and value != "": - setattr(doda, dst_attr, str(value)) - -# Router independiente para rutas literales (deben registrarse antes que /{id}) -router = APIRouter(prefix="/doda", tags=["doda"]) - -# ============ RUTAS LITERALES (antes del CRUD /{id}) ============ - - -@router.get( - "/export", - summary="Exportar DODA por rango de fechas (CSV, TSV como XLS, o TXT con |)", -) -async def export_doda_list( - company_id: int = Query(..., description="Company ID"), - date_from: str = Query(..., description="Fecha inicio (YYYY-MM-DD)"), - date_to: str = Query(..., description="Fecha fin (YYYY-MM-DD)"), - file_format: str = Query("csv", alias="format", description="csv, xls o txt"), - date_mode: str = Query("formatted", description="raw o formatted (fechas/horas)"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - """ - Listado al estilo legacy: filtra `doda_date` (YYYYMMDD) entre inicio y fin. - """ - tenant_id = int(validate_access_to_resource(db, company_id, current_user, ["cat_doda.view"])) - try: - d0, d1, fmt, mode = parse_export_params( - date_from, date_to, file_format, date_mode - ) - except ValueError as e: - raise HTTPException(status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=str(e)) - - rows = list_dodas_in_date_range( - db, tenant_id=tenant_id, company_id=company_id, date_start=d0, date_end=d1 - ) - if not rows: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="No existen DODA en el rango de fechas seleccionado.", - ) - - text = build_export_text(rows, export_format=fmt, date_mode=mode) - content_type, default_name = _content_type_and_filename(fmt) - data = ("\ufeff" + text).encode("utf-8") - return StreamingResponse( - io.BytesIO(data), - media_type=content_type, - headers={ - "Content-Disposition": f'attachment; filename="{default_name}"', - }, - ) - - -@router.get( - "/export/pedimentos/{doda_id}", - summary="Exportar líneas de pedimento de un DODA (CSV, TSV como XLS, TXT con |)", -) -async def export_doda_pedimentos( - doda_id: int, - company_id: int = Query(..., description="Company ID"), - file_format: str = Query("xls", alias="format", description="csv, xls o txt"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - """ - Reporte por DODA seleccionado: columnas alineadas al listado de pedimentos (PATENTE, DOCUMENTO, COVE, etc.). - Si no hay líneas, se devuelve el archivo solo con encabezados. - """ - tenant_id = int(validate_access_to_resource(db, company_id, current_user, ["cat_doda.view"])) - try: - fmt = parse_pedimento_export_format(file_format) - except ValueError as e: - raise HTTPException( - status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, detail=str(e) - ) from e - - doda = DodaService.get_by_id(db, doda_id, tenant_id, company_id) - if not doda: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, detail="DODA no encontrado." - ) - - rows = list_pedimentos_for_doda_export( - db, tenant_id=tenant_id, company_id=company_id, doda_id=doda_id - ) - text = build_pedimentos_export_text(rows, export_format=fmt) - content_type, _ = _content_type_and_filename(fmt) - fname = f"doda_pedimentos_{doda_id}.{fmt.value}" - data = ("\ufeff" + text).encode("utf-8") - return StreamingResponse( - io.BytesIO(data), - media_type=content_type, - headers={"Content-Disposition": f'attachment; filename="{fname}"'}, - ) - - -# Incluir rutas CRUD (contiene GET /{id}, POST /, PUT /{id}, DELETE /{id}). -# Se registra DESPUÉS de los endpoints literales para que /export, /alta-logs, -# /alta-status no sean capturados por el parámetro /{id}. -_crud_router = TenantCRUDRoutes( - service=DodaService, - create_schema=DodaCreateDTO, - update_schema=DodaUpdateDTO, - response_schema=DodaResponseDTO, - prefix="", - tags=["doda"], - resource_name="DODA", - id_name="doda_id", - enable_list=True, - enable_filters=True, - list_permissions=["cat_doda.view"], - get_permissions=["cat_doda.view"], - create_permissions=["cat_doda.create"], - update_permissions=["cat_doda.edit"], - delete_permissions=["cat_doda.delete"], -).router -router.include_router(_crud_router) - - -@router.get( - "/{doda_id}/detail", - response_model=DodaDetailResponseDTO, - summary="Get DODA by ID with all details", -) -async def get_doda_detail( - doda_id: int, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - tenant_id = validate_access_to_resource(db, company_id, current_user, ["cat_doda.view"]) - doda = DodaService.get_by_id(db, doda_id, tenant_id, company_id) - if not doda: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="DODA not found", - ) - return DodaDetailResponseDTO.model_validate(doda) - - -@router.get( - "/{doda_id}/print", - summary="Imprimir DODA (PDF)", - responses={422: {"description": "Validación (p. ej. falta sello digital SAT)."}}, -) -async def print_doda_pdf( - doda_id: int, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - """ - Genera o reutiliza el PDF almacenado en S3 cuando el contenido no ha cambiado - (huella SHA-256 de DODA + hijos). - """ - tenant_id = int( - validate_access_to_resource(db, company_id, current_user, ["cat_doda.view"]) - ) - doda = DodaService.get_by_id(db, doda_id, tenant_id, company_id) - if not doda: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="DODA not found", - ) - - if not (doda.sat_digital_seal and str(doda.sat_digital_seal).strip()): - raise HTTPException( - status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, - detail="Falta el sello digital SAT requerido para imprimir el DODA.", - ) - - content_fp = build_doda_fingerprint(db, doda_id) - expected_key = doda_report_pdf_key(tenant_id, company_id, doda_id) - - can_reuse = ( - doda.doda_report_source_fingerprint == content_fp - and doda.doda_report_pdf_path == expected_key - and bool(doda.doda_report_pdf_path) - ) - if can_reuse and settings.use_s3_object_storage and storage_s3.object_exists(expected_key): - data = storage_s3.get_object_bytes(expected_key) - return StreamingResponse( - io.BytesIO(data), - media_type="application/pdf", - headers={"Content-Disposition": f'inline; filename="doda_{doda_id}.pdf"'}, - ) - - # Re-generar: eliminar caché previa (S3 + columnas) y volver a guardar - if settings.use_s3_object_storage: - touch_invalidate_doda_report( - db, - tenant_id=tenant_id, - company_id=company_id, - doda_id=doda_id, - doda=None, - ) - - doda_fresh = DodaService.get_by_id(db, doda_id, tenant_id, company_id) - if not doda_fresh: - raise HTTPException(status_code=404, detail="DODA not found") - - service = DodaReportPdfService() - pdf = service.build_pdf_for_doda(db, doda_fresh) - if settings.use_s3_object_storage: - storage_s3.put_object_bytes(expected_key, pdf, content_type="application/pdf") - - now = datetime.now(timezone.utc) - doda_fresh.doda_report_pdf_path = expected_key if settings.use_s3_object_storage else None - doda_fresh.doda_report_pdf_generated_at = now - doda_fresh.doda_report_source_fingerprint = content_fp - db.add(doda_fresh) - db.commit() - if settings.use_s3_object_storage: - pdf = storage_s3.get_object_bytes(expected_key) - - return StreamingResponse( - io.BytesIO(pdf), - media_type="application/pdf", - headers={"Content-Disposition": f'inline; filename="doda_{doda_id}.pdf"'}, - ) - - -# ============ CONTAINERS ENDPOINTS ============ -@router.get( - "/{doda_id}/containers", - response_model=List[DodaContainerResponseDTO], - summary="Get containers for DODA", -) -async def get_doda_containers( - doda_id: int, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - """Get all containers for a specific DODA""" - validate_access_to_resource(db, company_id, current_user, ["cat_doda.view"]) - containers = DodaService.get_containers(db, doda_id) - return [DodaContainerResponseDTO.model_validate(c) for c in containers] - - -@router.post( - "/{doda_id}/containers", - response_model=DodaContainerResponseDTO, - status_code=status.HTTP_201_CREATED, - summary="Add container to DODA", -) -async def add_container( - doda_id: int, - container_data: DodaContainerCreateDTO, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - """Add a new container to a DODA""" - validate_access_to_resource(db, company_id, current_user, ["cat_doda.edit"]) - container = DodaService.add_container(db, doda_id, container_data) - if not container: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="DODA not found", - ) - return DodaContainerResponseDTO.model_validate(container) - - -@router.put( - "/{doda_id}/containers/{container_line}", - response_model=DodaContainerResponseDTO, - summary="Update container", -) -async def update_container( - doda_id: int, - container_line: int, - container_data: DodaContainerUpdateDTO, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - """Update a container""" - validate_access_to_resource(db, company_id, current_user, ["cat_doda.edit"]) - container = DodaService.update_container( - db, doda_id, container_line, container_data - ) - if not container: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="Container not found", - ) - return DodaContainerResponseDTO.model_validate(container) - - -@router.delete( - "/{doda_id}/containers/{container_line}", - status_code=status.HTTP_204_NO_CONTENT, - summary="Delete container from DODA", -) -async def delete_container( - doda_id: int, - container_line: int, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - """ - Elimina un contenedor del DODA. - Devuelve 409 si el contenedor tiene precintos (candados) asignados. - """ - validate_access_to_resource(db, company_id, current_user, ["cat_doda.delete"]) - DodaService.delete_container(db, doda_id, container_line) - return None - - -def _seal_to_response(seal) -> DodaContainerSealResponseDTO: - """ORM usa doda_id; DTO expone doda_sys_id.""" - return DodaContainerSealResponseDTO( - id=seal.id, - doda_sys_id=seal.doda_id, - seal_line=seal.seal_line, - seal_value=seal.seal_value, - ) - - -# ============ CONTAINER SEALS (PRECINTOS) ============ -@router.get( - "/{doda_id}/containers/{container_line}/seals", - response_model=List[DodaContainerSealResponseDTO], - summary="Listar precintos de un contenedor", -) -async def get_container_seals( - doda_id: int, - container_line: int, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - validate_access_to_resource(db, company_id, current_user, ["cat_doda.view"]) - seals = DodaService.get_seals_for_container(db, doda_id, container_line) - return [_seal_to_response(s) for s in seals] - - -@router.post( - "/{doda_id}/containers/{container_line}/seals", - response_model=DodaContainerSealResponseDTO, - status_code=status.HTTP_201_CREATED, - summary="Agregar precinto a un contenedor", -) -async def add_container_seal( - doda_id: int, - container_line: int, - seal_data: DodaContainerSealCreateDTO, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - validate_access_to_resource(db, company_id, current_user, ["cat_doda.edit"]) - seal = DodaService.add_seal(db, doda_id, container_line, seal_data) - return _seal_to_response(seal) - - -@router.delete( - "/{doda_id}/containers/{container_line}/seals/{seal_line}", - status_code=status.HTTP_204_NO_CONTENT, - summary="Eliminar precinto de un contenedor", -) -async def delete_container_seal( - doda_id: int, - container_line: int, - seal_line: int, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - validate_access_to_resource(db, company_id, current_user, ["cat_doda.delete"]) - DodaService.delete_seal(db, doda_id, container_line, seal_line) - return None - - -# ============ AMERICAN PEDIMENTOS ENDPOINTS ============ -@router.get( - "/{doda_id}/american-pedimentos", - response_model=List[DodaAmericanPedimentoResponseDTO], - summary="Get American pedimentos for DODA", -) -async def get_american_pedimentos( - doda_id: int, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - """Get all American pedimentos for a specific DODA""" - validate_access_to_resource(db, company_id, current_user, ["cat_doda.view"]) - pedimentos = DodaService.get_american_pedimentos(db, doda_id) - return [DodaAmericanPedimentoResponseDTO.model_validate(p) for p in pedimentos] - - -@router.post( - "/{doda_id}/american-pedimentos", - response_model=DodaAmericanPedimentoResponseDTO, - status_code=status.HTTP_201_CREATED, - summary="Add American pedimento to DODA", -) -async def add_american_pedimento( - doda_id: int, - pedimento_data: DodaAmericanPedimentoCreateDTO, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - """Add a new American pedimento to a DODA""" - validate_access_to_resource(db, company_id, current_user, ["cat_doda.edit"]) - pedimento = DodaService.add_american_pedimento(db, doda_id, pedimento_data) - if not pedimento: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="DODA not found", - ) - return DodaAmericanPedimentoResponseDTO.model_validate(pedimento) - - -# ============ AMERICAN PEDIMENTOS (DELETE) ============ -@router.delete( - "/{doda_id}/american-pedimentos/{pedimento_line}", - status_code=status.HTTP_204_NO_CONTENT, - summary="Delete American pedimento from DODA", -) -async def delete_american_pedimento( - doda_id: int, - pedimento_line: int, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - """Elimina un pedimento americano del DODA.""" - validate_access_to_resource(db, company_id, current_user, ["cat_doda.delete"]) - DodaService.delete_american_pedimento(db, doda_id, pedimento_line) - return None - - -# ============ PEDIMENTOS ENDPOINTS ============ -@router.get( - "/{doda_id}/pedimentos", - response_model=List[DodaPedimentoResponseDTO], - summary="Get pedimentos for DODA", -) -async def get_doda_pedimentos( - doda_id: int, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - """Get all pedimentos for a specific DODA""" - validate_access_to_resource(db, company_id, current_user, ["cat_doda.view"]) - pedimentos = DodaService.get_pedimentos(db, doda_id) - return [DodaPedimentoResponseDTO.model_validate(p) for p in pedimentos] - - -@router.post( - "/{doda_id}/pedimentos", - response_model=DodaPedimentoResponseDTO, - status_code=status.HTTP_201_CREATED, - summary="Add pedimento to DODA", -) -async def add_pedimento( - doda_id: int, - pedimento_data: DodaPedimentoCreateDTO, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - """Add a new pedimento to a DODA""" - validate_access_to_resource(db, company_id, current_user, ["cat_doda.edit"]) - pedimento = DodaService.add_pedimento(db, doda_id, pedimento_data) - if not pedimento: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="DODA not found", - ) - return DodaPedimentoResponseDTO.model_validate(pedimento) - - -@router.delete( - "/{doda_id}/pedimentos/{pedimento_line}", - status_code=status.HTTP_204_NO_CONTENT, - summary="Delete pedimento from DODA", -) -async def delete_pedimento( - doda_id: int, - pedimento_line: int, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - """Elimina un pedimento del DODA.""" - validate_access_to_resource(db, company_id, current_user, ["cat_doda.delete"]) - DodaService.delete_pedimento(db, doda_id, pedimento_line) - return None - - -# ============ ALTA DODA ENDPOINTS ============ - - -@router.get( - "/{doda_id}/alta/elegibilidad", - summary="Verificar elegibilidad para Alta DODA", - tags=["doda-alta"], -) -async def get_doda_elegibilidad( - doda_id: int, - company_id: int = Query(..., description="Company ID"), - variant: str = Query("doda", description="Tipo de alta: 'doda' o 'pita'"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -) -> Dict[str, Any]: - """ - Verifica si el DODA cumple los requisitos para enviar el alta al servicio externo. - Porta las validaciones del sistema legacy (campos requeridos, max 4 contenedores, - gafete si DODA, patente vs agente, certificados DODA en VU). - """ - tenant_id = validate_access_to_resource(db, company_id, current_user, ["cat_doda.view"]) - user_email = ( - current_user.get("email") - or current_user.get("preferred_username") - or "" - ) - service = DodaAltaService(db) - result = service.check_elegibilidad( - doda_id=doda_id, - tenant_id=int(tenant_id), - company_id=company_id, - variant=variant, - user_email=user_email, - ) - return { - "can_alta": result.can_alta, - "reasons": [ - {"field": r.field, "message": r.message, "solution": r.solution} - for r in result.reasons - ], - } - - -@router.post( - "/{doda_id}/alta", - summary="Enviar Alta DODA al servicio externo (asíncrono)", - tags=["doda-alta"], -) -async def post_doda_alta( - doda_id: int, - company_id: int = Query(..., description="Company ID"), - variant: str = Query("doda", description="Tipo de alta: 'doda' o 'pita'"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -) -> Dict[str, Any]: - """ - Verifica elegibilidad, construye el payload desde los datos del DODA y su VU, - y envía el alta al servicio externo. Retorna {task_id, status, message} para polling. - """ - tenant_id = validate_access_to_resource(db, company_id, current_user, ["cat_doda.edit"]) - user_email = ( - current_user.get("email") - or current_user.get("preferred_username") - or "" - ) - service = DodaAltaService(db) - - elegibilidad = service.check_elegibilidad( - doda_id=doda_id, - tenant_id=int(tenant_id), - company_id=company_id, - variant=variant, - user_email=user_email, - ) - if not elegibilidad.can_alta: - reasons = [ - {"field": r.field, "message": r.message, "solution": r.solution} - for r in elegibilidad.reasons - ] - raise HTTPException( - status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, - detail={"message": "El DODA no cumple los requisitos para el alta.", "reasons": reasons}, - ) - - try: - payload = service.build_alta_payload( - doda_id=doda_id, - tenant_id=int(tenant_id), - company_id=company_id, - variant=variant, - user_email=user_email, - ) - except ValueError as exc: - raise HTTPException( - status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, - detail=str(exc), - ) from exc - - try: - ext = DodaExternalService() - result = ext.post_alta(payload) - except ValueError as exc: - raise HTTPException( - status_code=status.HTTP_503_SERVICE_UNAVAILABLE, - detail=str(exc), - ) from exc - except Exception as exc: - logger.exception("Error al enviar alta DODA al servicio externo: doda_id=%s", doda_id) - raise HTTPException( - status_code=status.HTTP_502_BAD_GATEWAY, - detail=f"Error al contactar el servicio DODA externo: {exc}", - ) from exc - - # Persistir el log del alta - doda_record = DodaService.get_by_id(db, doda_id, tenant_id, company_id) - if doda_record: - try: - DodaAltaLogService.create_from_alta_result( - db=db, - doda=doda_record, - company_id=company_id, - tenant_id=int(tenant_id), - variant=variant, - ext_result=result, - action="alta", - ) - except Exception: - logger.exception("Error persistiendo DodaAltaLog para doda_id=%s", doda_id) - - return result - - -@router.get( - "/alta-status/{task_id}", - summary="Consultar estado de tarea de Alta DODA", - tags=["doda-alta"], -) -async def get_doda_alta_status( - task_id: str, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -) -> Any: - """ - Proxy transparente al servicio externo para consultar el estado de una tarea de alta DODA. - """ - validate_access_to_resource(db, company_id, current_user, ["cat_doda.view"]) - try: - ext = DodaExternalService() - return ext.get_status(task_id) - except ValueError as exc: - raise HTTPException( - status_code=status.HTTP_503_SERVICE_UNAVAILABLE, - detail=str(exc), - ) from exc - except Exception as exc: - logger.exception("Error consultando estado DODA task_id=%s", task_id) - raise HTTPException( - status_code=status.HTTP_502_BAD_GATEWAY, - detail=f"Error al consultar el estado de la tarea DODA: {exc}", - ) from exc - - -@router.post( - "/{doda_id}/consulta", - summary="Enviar Consulta DODA al servicio externo (asíncrono)", - tags=["doda-alta"], -) -async def post_doda_consulta( - doda_id: int, - company_id: int = Query(..., description="Company ID"), - variant: str = Query("doda", description="Tipo de alta: 'doda' o 'pita'"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -) -> Dict[str, Any]: - tenant_id = validate_access_to_resource(db, company_id, current_user, ["cat_doda.edit"]) - user_email = ( - current_user.get("email") - or current_user.get("preferred_username") - or "" - ) - service = DodaAltaService(db) - try: - payload = service.build_consulta_payload( - doda_id=doda_id, - tenant_id=int(tenant_id), - company_id=company_id, - variant=variant, - user_email=user_email, - ) - except ValueError as exc: - raise HTTPException( - status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, - detail=str(exc), - ) from exc - - ext_result: Dict[str, Any] - try: - ext = DodaExternalService() - ext_result = ext.post_consulta(payload) - except ValueError as exc: - raise HTTPException( - status_code=status.HTTP_503_SERVICE_UNAVAILABLE, - detail=str(exc), - ) from exc - except Exception as exc: - logger.exception("Error al enviar consulta DODA al servicio externo: doda_id=%s", doda_id) - raise HTTPException( - status_code=status.HTTP_502_BAD_GATEWAY, - detail=f"Error al contactar el servicio DODA externo (consulta): {exc}", - ) from exc - - doda_record = DodaService.get_by_id(db, doda_id, int(tenant_id), company_id) - if doda_record: - try: - DodaAltaLogService.create_from_alta_result( - db=db, - doda=doda_record, - company_id=company_id, - tenant_id=int(tenant_id), - variant=variant, - ext_result=ext_result, - action="consulta", - ) - except Exception: - logger.exception("Error persistiendo DodaAltaLog(consulta) para doda_id=%s", doda_id) - return ext_result - - -@router.get( - "/consulta-status/{task_id}", - summary="Consultar estado de tarea de Consulta DODA", - tags=["doda-alta"], -) -async def get_doda_consulta_status( - task_id: str, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -) -> Any: - validate_access_to_resource(db, company_id, current_user, ["cat_doda.view"]) - try: - ext = DodaExternalService() - return ext.get_consulta_status(task_id) - except ValueError as exc: - raise HTTPException( - status_code=status.HTTP_503_SERVICE_UNAVAILABLE, - detail=str(exc), - ) from exc - except Exception as exc: - logger.exception("Error consultando consulta-status DODA task_id=%s", task_id) - raise HTTPException( - status_code=status.HTTP_502_BAD_GATEWAY, - detail=f"Error al consultar el estado de la consulta DODA: {exc}", - ) from exc - - -@router.post( - "/{doda_id}/consulta-apply/{task_id}", - summary="Aplicar resultado exitoso de consulta DODA al registro local", - tags=["doda-alta"], -) -async def post_doda_consulta_apply( - doda_id: int, - task_id: str, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -) -> Dict[str, Any]: - tenant_id = int( - validate_access_to_resource(db, company_id, current_user, ["cat_doda.edit"]) - ) - doda_record = DodaService.get_by_id(db, doda_id, tenant_id, company_id) - if not doda_record: - raise HTTPException(status_code=404, detail="DODA no encontrado.") - - try: - ext = DodaExternalService() - status_payload = ext.get_consulta_status(task_id) - except ValueError as exc: - raise HTTPException( - status_code=status.HTTP_503_SERVICE_UNAVAILABLE, - detail=str(exc), - ) from exc - except Exception as exc: - logger.exception("Error al consultar consulta-status para aplicar: task_id=%s", task_id) - raise HTTPException( - status_code=status.HTTP_502_BAD_GATEWAY, - detail=f"Error al consultar el estado de la consulta DODA: {exc}", - ) from exc - - task_state = str(status_payload.get("state") or status_payload.get("status") or "").upper() - if task_state != "SUCCESS": - raise HTTPException( - status_code=status.HTTP_409_CONFLICT, - detail="La tarea de consulta aún no está en estado SUCCESS.", - ) - - try: - _apply_consulta_success_to_doda(doda_record, status_payload) - if not (doda_record.status or "").strip(): - doda_record.status = "VALIDADO" - db.add(doda_record) - db.commit() - db.refresh(doda_record) - except Exception as exc: - db.rollback() - logger.exception("Error aplicando consulta-status al DODA id=%s", doda_id) - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail=f"No se pudo aplicar el resultado de consulta al DODA: {exc}", - ) from exc - - return { - "message": "Resultado de consulta aplicado correctamente.", - "doda_id": doda_id, - "task_id": task_id, - "state": task_state, - } - - -@router.post( - "/{doda_id}/eliminar", - summary="Enviar Eliminación DODA al servicio externo (asíncrono)", - tags=["doda-alta"], -) -async def post_doda_eliminar( - doda_id: int, - company_id: int = Query(..., description="Company ID"), - variant: str = Query("doda", description="Tipo de alta: 'doda' o 'pita'"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -) -> Dict[str, Any]: - tenant_id = validate_access_to_resource(db, company_id, current_user, ["cat_doda.delete"]) - user_email = ( - current_user.get("email") - or current_user.get("preferred_username") - or "" - ) - service = DodaAltaService(db) - try: - payload = service.build_eliminar_payload( - doda_id=doda_id, - tenant_id=int(tenant_id), - company_id=company_id, - variant=variant, - user_email=user_email, - ) - except ValueError as exc: - raise HTTPException( - status_code=status.HTTP_422_UNPROCESSABLE_ENTITY, - detail=str(exc), - ) from exc - - ext_result: Dict[str, Any] - try: - ext = DodaExternalService() - ext_result = ext.post_eliminar(payload) - except ValueError as exc: - raise HTTPException( - status_code=status.HTTP_503_SERVICE_UNAVAILABLE, - detail=str(exc), - ) from exc - except Exception as exc: - logger.exception("Error al enviar eliminación DODA al servicio externo: doda_id=%s", doda_id) - raise HTTPException( - status_code=status.HTTP_502_BAD_GATEWAY, - detail=f"Error al contactar el servicio DODA externo (eliminación): {exc}", - ) from exc - - doda_record = DodaService.get_by_id(db, doda_id, int(tenant_id), company_id) - if doda_record: - try: - DodaAltaLogService.create_from_alta_result( - db=db, - doda=doda_record, - company_id=company_id, - tenant_id=int(tenant_id), - variant=variant, - ext_result=ext_result, - action="eliminar", - ) - except Exception: - logger.exception("Error persistiendo DodaAltaLog(eliminar) para doda_id=%s", doda_id) - - try: - doda_record.integration_number = None - doda_record.transaction_number = None - doda_record.status = "PENDIENTE" - doda_record.sat_digital_seal = None - doda_record.sat_certificate = None - doda_record.serial_number = None - doda_record.electronic_signature = None - doda_record.original_chain = None - doda_record.sat_original_chain = None - doda_record.linq_sat_qr = None - doda_record.xml_doda_sent_path = None - doda_record.xml_doda_response_path = None - db.add(doda_record) - db.commit() - except Exception: - db.rollback() - logger.exception("Error desprocesando DODA local tras eliminación id=%s", doda_id) - return ext_result - - -@router.get( - "/eliminar-status/{task_id}", - summary="Consultar estado de tarea de Eliminación DODA", - tags=["doda-alta"], -) -async def get_doda_eliminar_status( - task_id: str, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -) -> Any: - validate_access_to_resource(db, company_id, current_user, ["cat_doda.view"]) - try: - ext = DodaExternalService() - return ext.get_eliminar_status(task_id) - except ValueError as exc: - raise HTTPException( - status_code=status.HTTP_503_SERVICE_UNAVAILABLE, - detail=str(exc), - ) from exc - except Exception as exc: - logger.exception("Error consultando eliminar-status DODA task_id=%s", task_id) - raise HTTPException( - status_code=status.HTTP_502_BAD_GATEWAY, - detail=f"Error al consultar el estado de la eliminación DODA: {exc}", - ) from exc - - -# ============ DODA ALTA LOG CRUD ============ - - -@router.get( - "/alta-logs", - response_model=DodaAltaLogListResponse, - summary="Listar registros de alta DODA", - tags=["doda-alta"], -) -async def list_doda_alta_logs( - company_id: int = Query(...), - page: int = Query(1, ge=1), - page_size: int = Query(50, ge=1, le=200), - doda_id: int = Query(None), - search: str = Query(None), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - tenant_id = validate_access_to_resource(db, company_id, current_user, ["cat_doda.view"]) - return DodaAltaLogService.list( - db, company_id, int(tenant_id), page, page_size, doda_id, search - ) - - -@router.get( - "/alta-logs/{log_id}", - response_model=DodaAltaLogResponseDTO, - summary="Obtener registro de alta DODA", - tags=["doda-alta"], -) -async def get_doda_alta_log( - log_id: int, - company_id: int = Query(...), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - tenant_id = validate_access_to_resource(db, company_id, current_user, ["cat_doda.view"]) - record = DodaAltaLogService.get(db, log_id, company_id, int(tenant_id)) - if not record: - raise HTTPException(status_code=404, detail="Registro de alta DODA no encontrado.") - return DodaAltaLogResponseDTO.model_validate(record) - - -@router.post( - "/alta-logs", - response_model=DodaAltaLogResponseDTO, - status_code=status.HTTP_201_CREATED, - summary="Crear registro de alta DODA manualmente", - tags=["doda-alta"], -) -async def create_doda_alta_log( - dto: DodaAltaLogCreateDTO, - company_id: int = Query(...), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - tenant_id = validate_access_to_resource(db, company_id, current_user, ["cat_doda.create"]) - record = DodaAltaLogService.create(db, dto, company_id, int(tenant_id)) - return DodaAltaLogResponseDTO.model_validate(record) - - -@router.put( - "/alta-logs/{log_id}", - response_model=DodaAltaLogResponseDTO, - summary="Actualizar registro de alta DODA", - tags=["doda-alta"], -) -async def update_doda_alta_log( - log_id: int, - dto: DodaAltaLogUpdateDTO, - company_id: int = Query(...), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - tenant_id = validate_access_to_resource(db, company_id, current_user, ["cat_doda.edit"]) - record = DodaAltaLogService.get(db, log_id, company_id, int(tenant_id)) - if not record: - raise HTTPException(status_code=404, detail="Registro de alta DODA no encontrado.") - record = DodaAltaLogService.update(db, record, dto) - return DodaAltaLogResponseDTO.model_validate(record) - - -@router.delete( - "/alta-logs/{log_id}", - status_code=status.HTTP_204_NO_CONTENT, - summary="Eliminar registro de alta DODA", - tags=["doda-alta"], -) -async def delete_doda_alta_log( - log_id: int, - company_id: int = Query(...), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - tenant_id = validate_access_to_resource(db, company_id, current_user, ["cat_doda.delete"]) - record = DodaAltaLogService.get(db, log_id, company_id, int(tenant_id)) - if not record: - raise HTTPException(status_code=404, detail="Registro de alta DODA no encontrado.") - DodaAltaLogService.delete(db, record) - return None diff --git a/backend/api/v1/modules/a76/general_catalogs/doda/service.py b/backend/api/v1/modules/a76/general_catalogs/doda/service.py deleted file mode 100644 index 33172fd7..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/doda/service.py +++ /dev/null @@ -1,726 +0,0 @@ -""" -Capa de servicio para lógica de negocio de DODA -""" - -import logging -from typing import Any, Dict, List, Optional, Tuple - -from fastapi import HTTPException -from sqlalchemy import func -from sqlalchemy.exc import IntegrityError -from sqlalchemy.orm import Session - -from .dto import ( - DodaCreateDTO, - DodaResponseDTO, - DodaUpdateDTO, - DodaContainerCreateDTO, - DodaContainerUpdateDTO, - DodaContainerSealCreateDTO, - DodaAmericanPedimentoCreateDTO, - DodaAmericanPedimentoUpdateDTO, - DodaPedimentoCreateDTO, - DodaPedimentoUpdateDTO, -) -from .models import ( - Doda, - DodaContainer, - DodaContainerSeal, - DodaAmericanPedimento, - DodaPedimento, -) -from .print_cache import touch_invalidate_doda_report - -logger = logging.getLogger(__name__) - - -class DodaService: - """Servicio para gestión de DODA""" - - @staticmethod - def _ensure_editable_doda(doda: Optional[Doda]) -> None: - if not doda: - return - if (doda.integration_number or "").strip(): - raise HTTPException( - status_code=422, - detail=( - "El DODA ya fue generado (tiene número de integración). " - "Elimínelo primero para poder editarlo." - ), - ) - - @staticmethod - def _invalidate_report_after_mutation( - db: Session, - *, - doda_id: int, - tenant_id: int, - company_id: int, - ) -> None: - """Limpia PDF de reporte y objeto S3 previo (patrón artefactos).""" - touch_invalidate_doda_report( - db, tenant_id=tenant_id, company_id=company_id, doda_id=doda_id - ) - - @staticmethod - def get_all( - db: Session, - tenant_id: int, - company_id: int, - skip: int = 0, - limit: int = 50, - filters: Optional[Dict[str, Any]] = None, - ) -> Tuple[List[Doda], int]: - """Get all DODAs with pagination""" - query = db.query(Doda).filter( - Doda.tenant_id == tenant_id, - Doda.company_id == company_id - ) - - if filters: - if filters.get("integration_number"): - query = query.filter( - Doda.integration_number.ilike( - f"%{filters['integration_number']}%") - ) - if filters.get("status"): - query = query.filter( - Doda.status.ilike(f"%{filters['status']}%")) - if filters.get("patent"): - query = query.filter( - Doda.patent.ilike(f"%{filters['patent']}%")) - if filters.get("operation_type"): - query = query.filter( - Doda.operation_type == filters["operation_type"]) - - total = query.count() - dodas = query.offset(skip).limit(limit).all() - return dodas, total - - @staticmethod - def get_by_id( - db: Session, id: int, tenant_id: int, company_id: int - ) -> Optional[Doda]: - """Get DODA by ID""" - return db.query(Doda).filter( - Doda.id == id, - Doda.tenant_id == tenant_id, - Doda.company_id == company_id - ).first() - - @staticmethod - def create( - db: Session, doda_data: DodaCreateDTO, tenant_id: int, company_id: int - ) -> Doda: - """Create a new DODA""" - try: - db_doda = Doda( - **doda_data.model_dump(exclude_unset=True), - tenant_id=tenant_id, - company_id=company_id - ) - db.add(db_doda) - db.commit() - db.refresh(db_doda) - DodaService._invalidate_report_after_mutation( - db, doda_id=db_doda.id, tenant_id=tenant_id, company_id=company_id - ) - return db_doda - except IntegrityError as e: - db.rollback() - logger.error(f"IntegrityError creating DODA: {str(e)}") - raise HTTPException(status_code=400, detail="Error creating DODA") - except Exception as e: - db.rollback() - logger.error(f"Error creating DODA: {str(e)}") - raise HTTPException(status_code=500, detail="Error creating DODA") - - @staticmethod - def update( - db: Session, id: int, tenant_id: int, doda_data: DodaUpdateDTO, company_id: int - ) -> Optional[Doda]: - """Update a DODA""" - try: - db_doda = DodaService.get_by_id(db, id, tenant_id, company_id) - if not db_doda: - return None - DodaService._ensure_editable_doda(db_doda) - - for key, value in doda_data.model_dump(exclude_unset=True).items(): - setattr(db_doda, key, value) - - db.commit() - db.refresh(db_doda) - DodaService._invalidate_report_after_mutation( - db, doda_id=db_doda.id, tenant_id=tenant_id, company_id=company_id - ) - return db_doda - except IntegrityError as e: - db.rollback() - logger.error(f"IntegrityError updating DODA: {str(e)}") - raise HTTPException(status_code=400, detail="Error updating DODA") - except Exception as e: - db.rollback() - logger.error(f"Error updating DODA: {str(e)}") - raise HTTPException(status_code=500, detail="Error updating DODA") - - @staticmethod - def delete( - db: Session, id: int, tenant_id: int, company_id: int - ) -> bool: - """Delete a DODA""" - db_doda = DodaService.get_by_id(db, id, tenant_id, company_id) - if not db_doda: - return False - - try: - tid = int(db_doda.tenant_id) - cid = int(db_doda.company_id) - did = int(db_doda.id) - DodaService._invalidate_report_after_mutation( - db, doda_id=did, tenant_id=tid, company_id=cid - ) - to_delete = DodaService.get_by_id(db, id, tenant_id, company_id) - if not to_delete: - return True - db.delete(to_delete) - db.commit() - return True - except IntegrityError as e: - db.rollback() - logger.error(f"Error de integridad al eliminar DODA {id}: {str(e)}") - raise HTTPException( - status_code=400, - detail="No se puede eliminar este DODA porque tiene registros relacionados. Primero debe eliminar o reasignar esos registros." - ) - except Exception as e: - db.rollback() - logger.error(f"Error deleting DODA: {str(e)}") - raise HTTPException(status_code=500, detail="Error deleting DODA") - - # ============ CONTAINERS ============ - @staticmethod - def add_container( - db: Session, doda_id: int, container_data: DodaContainerCreateDTO - ) -> Optional[DodaContainer]: - """Add a container to a DODA""" - try: - doda = db.query(Doda).filter(Doda.id == doda_id).first() - if not doda: - return None - DodaService._ensure_editable_doda(doda) - - cv = (container_data.container_value or "").strip() - if not cv: - raise HTTPException( - status_code=400, - detail="El valor del contenedor no puede estar vacío.", - ) - - # Get max line number - max_line = ( - db.query(DodaContainer) - .filter(DodaContainer.doda_id == doda_id) - .count() - ) - - dump = container_data.model_dump(exclude_unset=True) - dump.pop("seals_detail", None) - dump["container_value"] = cv - - db_container = DodaContainer( - doda_id=doda_id, - container_line=max_line + 1, - tenant_id=doda.tenant_id, - company_id=doda.company_id, - **{k: v for k, v in dump.items() if k not in ("doda_id", "container_line")}, - ) - db.add(db_container) - db.commit() - db.refresh(db_container) - DodaService._invalidate_report_after_mutation( - db, - doda_id=doda_id, - tenant_id=int(doda.tenant_id), - company_id=int(doda.company_id), - ) - return db_container - except HTTPException: - db.rollback() - raise - except Exception as e: - db.rollback() - logger.error(f"Error adding container: {str(e)}") - raise HTTPException( - status_code=500, detail="Error adding container") - - @staticmethod - def update_container( - db: Session, - doda_id: int, - container_line: int, - container_data: DodaContainerUpdateDTO, - ) -> Optional[DodaContainer]: - """Update a container""" - try: - db_container = ( - db.query(DodaContainer) - .filter( - DodaContainer.doda_id == doda_id, - DodaContainer.container_line == container_line, - ) - .first() - ) - if not db_container: - return None - doda = db.get(Doda, doda_id) - DodaService._ensure_editable_doda(doda) - - for key, value in container_data.model_dump(exclude_unset=True).items(): - setattr(db_container, key, value) - - db.commit() - db.refresh(db_container) - doda = db.get(Doda, doda_id) - if doda: - DodaService._invalidate_report_after_mutation( - db, - doda_id=doda_id, - tenant_id=int(doda.tenant_id), - company_id=int(doda.company_id), - ) - return db_container - except Exception as e: - db.rollback() - logger.error(f"Error updating container: {str(e)}") - raise HTTPException( - status_code=500, detail="Error updating container") - - @staticmethod - def get_containers(db: Session, doda_id: int) -> List[DodaContainer]: - """Get all containers for a DODA""" - return ( - db.query(DodaContainer) - .filter(DodaContainer.doda_id == doda_id) - .all() - ) - - @staticmethod - def delete_container( - db: Session, doda_id: int, container_line: int - ) -> None: - """ - Delete a container from a DODA. - Raises HTTP 409 if the container has seals assigned (Clarion: validación precintos). - Raises HTTP 404 if not found. - """ - db_container = ( - db.query(DodaContainer) - .filter( - DodaContainer.doda_id == doda_id, - DodaContainer.container_line == container_line, - ) - .first() - ) - if not db_container: - raise HTTPException(status_code=404, detail="Contenedor no encontrado.") - doda = db.get(Doda, doda_id) - DodaService._ensure_editable_doda(doda) - - has_seals = bool(db_container.seals_detail) - if not has_seals and db_container.seals: - has_seals = any(s.strip() for s in db_container.seals.split(",")) - - if has_seals: - raise HTTPException( - status_code=409, - detail=( - "El contenedor tiene uno o más precintos asignados. " - "No se puede borrar el contenedor." - ), - ) - - try: - doda = db.get(Doda, doda_id) - tid = int(doda.tenant_id) if doda else 0 - cid = int(doda.company_id) if doda else 0 - db.delete(db_container) - db.commit() - if doda: - DodaService._invalidate_report_after_mutation( - db, doda_id=doda_id, tenant_id=tid, company_id=cid, doda=None - ) - except Exception as e: - db.rollback() - logger.error(f"Error deleting container doda_id={doda_id} line={container_line}: {e}") - raise HTTPException(status_code=500, detail="Error al eliminar el contenedor.") - - # ============ CONTAINER SEALS (PRECINTOS) ============ - MAX_SEALS_PER_DODA = 8 - - @staticmethod - def get_seals_for_container( - db: Session, doda_id: int, container_line: int - ) -> List[DodaContainerSeal]: - """Precintos de un contenedor (por línea de contenedor dentro del DODA).""" - db_container = ( - db.query(DodaContainer) - .filter( - DodaContainer.doda_id == doda_id, - DodaContainer.container_line == container_line, - ) - .first() - ) - if not db_container: - return [] - return ( - db.query(DodaContainerSeal) - .filter(DodaContainerSeal.container_id == db_container.id) - .order_by(DodaContainerSeal.seal_line.asc()) - .all() - ) - - @staticmethod - def add_seal( - db: Session, - doda_id: int, - container_line: int, - seal_data: DodaContainerSealCreateDTO, - ) -> DodaContainerSeal: - """ - Añade un precinto (candado) a un contenedor. - Máximo 8 precintos en total por DODA (legacy Clarion: gDoda_Contenedores_Candados). - """ - doda = db.query(Doda).filter(Doda.id == doda_id).first() - if not doda: - raise HTTPException(status_code=404, detail="DODA no encontrado.") - DodaService._ensure_editable_doda(doda) - - container = ( - db.query(DodaContainer) - .filter( - DodaContainer.doda_id == doda_id, - DodaContainer.container_line == container_line, - ) - .first() - ) - if not container: - raise HTTPException(status_code=404, detail="Contenedor no encontrado.") - doda = db.get(Doda, doda_id) - DodaService._ensure_editable_doda(doda) - - raw_value = (seal_data.seal_value or "").strip() - if not raw_value: - raise HTTPException( - status_code=400, - detail="El campo precinto no puede estar vacío.", - ) - - total_seals = ( - db.query(DodaContainerSeal) - .filter(DodaContainerSeal.doda_id == doda_id) - .count() - ) - if total_seals >= DodaService.MAX_SEALS_PER_DODA: - raise HTTPException( - status_code=400, - detail=( - "El DODA supera el máximo de precintos (8). " - "Revise los precintos registrados." - ), - ) - - max_line = ( - db.query(func.max(DodaContainerSeal.seal_line)) - .filter(DodaContainerSeal.container_id == container.id) - .scalar() - ) - next_line = (max_line or 0) + 1 - - try: - db_seal = DodaContainerSeal( - doda_id=doda_id, - container_id=container.id, - seal_line=next_line, - seal_value=raw_value, - tenant_id=doda.tenant_id, - company_id=doda.company_id, - ) - db.add(db_seal) - db.commit() - db.refresh(db_seal) - DodaService._invalidate_report_after_mutation( - db, - doda_id=doda_id, - tenant_id=int(doda.tenant_id), - company_id=int(doda.company_id), - ) - return db_seal - except Exception as e: - db.rollback() - logger.error( - "Error adding seal doda_id=%s container_line=%s: %s", - doda_id, - container_line, - e, - ) - raise HTTPException(status_code=500, detail="Error al agregar el precinto.") - - @staticmethod - def delete_seal( - db: Session, doda_id: int, container_line: int, seal_line: int - ) -> None: - """Elimina un precinto por línea de contenedor y línea de candado.""" - container = ( - db.query(DodaContainer) - .filter( - DodaContainer.doda_id == doda_id, - DodaContainer.container_line == container_line, - ) - .first() - ) - if not container: - raise HTTPException(status_code=404, detail="Contenedor no encontrado.") - - seal = ( - db.query(DodaContainerSeal) - .filter( - DodaContainerSeal.container_id == container.id, - DodaContainerSeal.seal_line == seal_line, - ) - .first() - ) - if not seal: - raise HTTPException(status_code=404, detail="Precinto no encontrado.") - - try: - doda = db.get(Doda, doda_id) - db.delete(seal) - db.commit() - if doda: - DodaService._invalidate_report_after_mutation( - db, - doda_id=doda_id, - tenant_id=int(doda.tenant_id), - company_id=int(doda.company_id), - ) - except Exception as e: - db.rollback() - logger.error( - "Error deleting seal doda_id=%s line=%s seal_line=%s: %s", - doda_id, - container_line, - seal_line, - e, - ) - raise HTTPException(status_code=500, detail="Error al eliminar el precinto.") - - # ============ AMERICAN PEDIMENTOS ============ - @staticmethod - def add_american_pedimento( - db: Session, doda_id: int, pedimento_data: DodaAmericanPedimentoCreateDTO - ) -> Optional[DodaAmericanPedimento]: - """Add an American pedimento to a DODA""" - try: - doda = db.query(Doda).filter(Doda.id == doda_id).first() - if not doda: - return None - DodaService._ensure_editable_doda(doda) - - tipo = (pedimento_data.american_pedimento_type or "").strip() - valor = (pedimento_data.american_pedimento_value or "").strip() - # Legacy: IF DODA:DespachoAduanero = '3' (PITA) → sin validación de tipo; web usa customs_clearance=1 para PITA - clearance = getattr(doda, "customs_clearance", None) - if clearance != 1: - if not tipo: - raise HTTPException( - status_code=400, - detail="El tipo de pedimento americano es obligatorio.", - ) - op = (doda.operation_type or "").strip().upper() - if op in ("I", "1"): - allowed = {"1", "2", "3", "4", "5"} - elif op in ("E", "2"): - allowed = {"6", "7", "8"} - else: - raise HTTPException( - status_code=400, - detail="El tipo de operación del DODA no permite validar el pedimento americano.", - ) - if tipo not in allowed: - raise HTTPException( - status_code=400, - detail="El tipo de pedimento americano no es correcto para el tipo de operación.", - ) - - max_line = ( - db.query(DodaAmericanPedimento) - .filter(DodaAmericanPedimento.doda_id == doda_id) - .count() - ) - - dump = pedimento_data.model_dump(exclude_unset=True) - if clearance == 1: - dump["american_pedimento_type"] = tipo or None - - db_pedimento = DodaAmericanPedimento( - doda_id=doda_id, - american_pedimento_line=max_line + 1, - tenant_id=doda.tenant_id, - company_id=doda.company_id, - **dump, - ) - db.add(db_pedimento) - db.commit() - db.refresh(db_pedimento) - DodaService._invalidate_report_after_mutation( - db, - doda_id=doda_id, - tenant_id=int(doda.tenant_id), - company_id=int(doda.company_id), - ) - return db_pedimento - except HTTPException: - db.rollback() - raise - except Exception as e: - db.rollback() - logger.error(f"Error adding American pedimento: {str(e)}") - raise HTTPException( - status_code=500, detail="Error adding American pedimento" - ) - - @staticmethod - def get_american_pedimentos(db: Session, doda_id: int) -> List[DodaAmericanPedimento]: - """Get all American pedimentos for a DODA""" - return ( - db.query(DodaAmericanPedimento) - .filter(DodaAmericanPedimento.doda_id == doda_id) - .all() - ) - - # ============ AMERICAN PEDIMENTOS (DELETE) ============ - @staticmethod - def delete_american_pedimento( - db: Session, doda_id: int, pedimento_line: int - ) -> None: - """Delete an American pedimento from a DODA.""" - db_pedimento = ( - db.query(DodaAmericanPedimento) - .filter( - DodaAmericanPedimento.doda_id == doda_id, - DodaAmericanPedimento.american_pedimento_line == pedimento_line, - ) - .first() - ) - if not db_pedimento: - raise HTTPException( - status_code=404, detail="Pedimento americano no encontrado." - ) - doda = db.get(Doda, doda_id) - DodaService._ensure_editable_doda(doda) - try: - db.delete(db_pedimento) - db.commit() - if doda: - DodaService._invalidate_report_after_mutation( - db, - doda_id=doda_id, - tenant_id=int(doda.tenant_id), - company_id=int(doda.company_id), - ) - except Exception as e: - db.rollback() - logger.error( - f"Error deleting american pedimento doda_id={doda_id} line={pedimento_line}: {e}" - ) - raise HTTPException( - status_code=500, detail="Error al eliminar el pedimento americano." - ) - - # ============ PEDIMENTOS ============ - @staticmethod - def add_pedimento( - db: Session, doda_id: int, pedimento_data: DodaPedimentoCreateDTO - ) -> Optional[DodaPedimento]: - """Add a pedimento to a DODA""" - try: - doda = db.query(Doda).filter(Doda.id == doda_id).first() - if not doda: - return None - DodaService._ensure_editable_doda(doda) - - max_line = ( - db.query(DodaPedimento) - .filter(DodaPedimento.doda_id == doda_id) - .count() - ) - - db_pedimento = DodaPedimento( - doda_id=doda_id, - pedimento_line=max_line + 1, - tenant_id=doda.tenant_id, - company_id=doda.company_id, - **pedimento_data.model_dump(exclude_unset=True), - ) - db.add(db_pedimento) - db.commit() - db.refresh(db_pedimento) - DodaService._invalidate_report_after_mutation( - db, - doda_id=doda_id, - tenant_id=int(doda.tenant_id), - company_id=int(doda.company_id), - ) - return db_pedimento - except HTTPException: - db.rollback() - raise - except Exception as e: - db.rollback() - logger.error(f"Error adding pedimento: {str(e)}") - raise HTTPException( - status_code=500, detail="Error adding pedimento") - - @staticmethod - def get_pedimentos(db: Session, doda_id: int) -> List[DodaPedimento]: - """Get all pedimentos for a DODA""" - return ( - db.query(DodaPedimento).filter( - DodaPedimento.doda_id == doda_id).all() - ) - - @staticmethod - def delete_pedimento( - db: Session, doda_id: int, pedimento_line: int - ) -> None: - """Delete a pedimento from a DODA.""" - db_pedimento = ( - db.query(DodaPedimento) - .filter( - DodaPedimento.doda_id == doda_id, - DodaPedimento.pedimento_line == pedimento_line, - ) - .first() - ) - if not db_pedimento: - raise HTTPException(status_code=404, detail="Pedimento no encontrado.") - doda = db.get(Doda, doda_id) - DodaService._ensure_editable_doda(doda) - try: - db.delete(db_pedimento) - db.commit() - if doda: - DodaService._invalidate_report_after_mutation( - db, - doda_id=doda_id, - tenant_id=int(doda.tenant_id), - company_id=int(doda.company_id), - ) - except Exception as e: - db.rollback() - logger.error( - f"Error deleting pedimento doda_id={doda_id} line={pedimento_line}: {e}" - ) - raise HTTPException(status_code=500, detail="Error al eliminar el pedimento.") diff --git a/backend/api/v1/modules/a76/general_catalogs/doda/templates/doda_report.html b/backend/api/v1/modules/a76/general_catalogs/doda/templates/doda_report.html deleted file mode 100644 index bc92e065..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/doda/templates/doda_report.html +++ /dev/null @@ -1,160 +0,0 @@ - - - - - Reporte DODA - - - -

Documento de operación (DODA)

-

Huella de contenido (SHA-256): {{ fingerprint_sha256 }}

- -

Datos generales

- - - - - - - - - - - - - - - - - - - - - -
Folio de integración{{ doda.integration_number }}Patente{{ doda.patent }}
Aduana despacho{{ doda.dispatch_customs }}Secciones aduaneras{{ doda.customs_sections }}
Operación{{ doda.operation_type }}Estado{{ doda.status }}
Ident. transporte{{ doda.transport_identification }}ID rápida{{ doda.fast_id }}
CAAT{{ doda.caat }}Transacción / folio{{ doda.transaction_number }}
- - {% if linq_sat_qr %} -

QR (LINQ / SAT)

-

{{ linq_sat_qr }}

- {% endif %} - - {% if sat_chain_preview %} -

Cadena original / SAT (extracto)

-

{{ sat_chain_preview }}

- {% endif %} - - {% if sat_digital_seal_preview %} -

Sello digital (SAT) — extracto

-

{{ sat_digital_seal_preview }}

- {% endif %} - -

Contenedores y precintos

- {% if containers|length == 0 %} -

Sin contenedores registrados.

- {% else %} - - - - - - - - - - {% for c in containers %} - - - - - - {% endfor %} - -
LíneaContenedorPrecintos (línea / valor)
{{ c.container_line }}{{ c.container_value }} - {% if c.seals|length == 0 %} - - {% else %} - - {% for s in c.seals %} - - - - - {% endfor %} -
{{ s.seal_line }}{{ s.seal_value }}
- {% endif %} -
- {% endif %} - -

Pedimentos nacionales

- {% if pedimentos_detail|length == 0 %} -

Sin partidas de pedimentos nacionales.

- {% else %} - - - - - - - - - - - - - - {% for p in pedimentos_detail %} - - - - - - - - - - {% endfor %} - -
LíneaPatente auth.Documento / ped.EmbarqueCOVEUMCTipo
{{ p.pedimento_line }}{{ p.authorization_patent }}{{ p.document }}{{ p.shipment }}{{ p.cove }}{{ p.umc }}{{ p.pedimento_type }}
- {% endif %} - -

Pedimentos USA

- {% if american_pedimentos|length == 0 %} -

Sin pedimentos americanos.

- {% else %} - - - - - - - - - - {% for a in american_pedimentos %} - - - - - - {% endfor %} - -
LíneaTipoValor
{{ a.american_pedimento_line }}{{ a.american_pedimento_type }}{{ a.american_pedimento_value }}
- {% endif %} - -

- Documento generado automáticamente. Los extractos de cadena / sello se truncan en este reporte; - los datos de huella (SHA-256) reflejan el contenido completo persistido. -

- - diff --git a/backend/api/v1/modules/a76/general_catalogs/electronic_notices/__init__.py b/backend/api/v1/modules/a76/general_catalogs/electronic_notices/__init__.py deleted file mode 100644 index 12753c03..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/electronic_notices/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -""" -Módulo de avisos electrónicos -""" diff --git a/backend/api/v1/modules/a76/general_catalogs/electronic_notices/dto.py b/backend/api/v1/modules/a76/general_catalogs/electronic_notices/dto.py deleted file mode 100644 index 97edc4a5..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/electronic_notices/dto.py +++ /dev/null @@ -1,85 +0,0 @@ -""" -DTOs (Data Transfer Objects) para módulo de avisos electrónicos -""" - -from typing import Optional - -from pydantic import BaseModel, Field - - -class ElectronicNoticeCreateDTO(BaseModel): - """DTO para crear un aviso electrónico""" - - notice_number: Optional[str] = Field( - None, max_length=500, description="Notice number" - ) - year: Optional[str] = Field(None, max_length=20, description="Year") - patent: Optional[str] = Field(None, max_length=4, description="Patent") - pedimento: Optional[str] = Field( - None, max_length=15, description="Pedimento") - file_sent: Optional[str] = Field( - None, max_length=1000, description="File sent") - file_response: Optional[str] = Field( - None, max_length=1000, description="File response" - ) - status: Optional[str] = Field(None, max_length=100, description="Status") - invoice: Optional[str] = Field(None, max_length=50, description="Invoice") - validation_acknowledgment: Optional[str] = Field( - None, max_length=20, description="Validation acknowledgment" - ) - fea: Optional[str] = Field(None, max_length=1000, description="FEA") - certificate_number: Optional[str] = Field( - None, max_length=50, description="Certificate number" - ) - - class Config: - from_attributes = True - - -class ElectronicNoticeUpdateDTO(BaseModel): - """DTO para actualizar un aviso electrónico""" - - notice_number: Optional[str] = Field( - None, max_length=500, description="Notice number" - ) - year: Optional[str] = Field(None, max_length=20, description="Year") - patent: Optional[str] = Field(None, max_length=4, description="Patent") - pedimento: Optional[str] = Field( - None, max_length=15, description="Pedimento") - file_sent: Optional[str] = Field( - None, max_length=1000, description="File sent") - file_response: Optional[str] = Field( - None, max_length=1000, description="File response" - ) - status: Optional[str] = Field(None, max_length=100, description="Status") - invoice: Optional[str] = Field(None, max_length=50, description="Invoice") - validation_acknowledgment: Optional[str] = Field( - None, max_length=20, description="Validation acknowledgment" - ) - fea: Optional[str] = Field(None, max_length=1000, description="FEA") - certificate_number: Optional[str] = Field( - None, max_length=50, description="Certificate number" - ) - - class Config: - from_attributes = True - - -class ElectronicNoticeResponseDTO(BaseModel): - """DTO para responder con datos de un aviso electrónico""" - - id: int - notice_number: Optional[str] = None - year: Optional[str] = None - patent: Optional[str] = None - pedimento: Optional[str] = None - file_sent: Optional[str] = None - file_response: Optional[str] = None - status: Optional[str] = None - invoice: Optional[str] = None - validation_acknowledgment: Optional[str] = None - fea: Optional[str] = None - certificate_number: Optional[str] = None - - class Config: - from_attributes = True diff --git a/backend/api/v1/modules/a76/general_catalogs/electronic_notices/models.py b/backend/api/v1/modules/a76/general_catalogs/electronic_notices/models.py deleted file mode 100644 index e2722d47..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/electronic_notices/models.py +++ /dev/null @@ -1,48 +0,0 @@ -""" -Modelos ORM para gestión de avisos electrónicos -""" - -from typing import Optional - -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base -from sqlalchemy import Integer, PrimaryKeyConstraint, String -from sqlalchemy.orm import Mapped, mapped_column - - -class ElectronicNotice(Base, TenantScopedMixin, TimestampMixin): - """ - Modelo para la tabla ElectronicNotice - Avisos Electrónicos - """ - - __tablename__ = "electronic_notices" # GAvisosElectronicos - __table_args__ = ( - PrimaryKeyConstraint("id", name="electronic_notices_pkey"), - {"schema": "a76"}, - ) - - # Primary key - id: Mapped[int] = mapped_column(Integer, primary_key=True) - - # Notice identification - notice_number: Mapped[Optional[str]] = mapped_column(String(500)) - year: Mapped[Optional[str]] = mapped_column(String(20)) - patent: Mapped[Optional[str]] = mapped_column(String(4)) - pedimento: Mapped[Optional[str]] = mapped_column(String(15)) - - # Files - file_sent: Mapped[Optional[str]] = mapped_column(String(1000)) - file_response: Mapped[Optional[str]] = mapped_column(String(1000)) - - # Status and validation - status: Mapped[Optional[str]] = mapped_column(String(100)) - invoice: Mapped[Optional[str]] = mapped_column(String(50)) - validation_acknowledgment: Mapped[Optional[str]] = mapped_column( - String(20)) - - # Certificate information - fea: Mapped[Optional[str]] = mapped_column(String(1000)) - certificate_number: Mapped[Optional[str]] = mapped_column(String(50)) - - def __repr__(self): - return f"" diff --git a/backend/api/v1/modules/a76/general_catalogs/electronic_notices/routes.py b/backend/api/v1/modules/a76/general_catalogs/electronic_notices/routes.py deleted file mode 100644 index 16c7629d..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/electronic_notices/routes.py +++ /dev/null @@ -1,72 +0,0 @@ -""" -Rutas para gestión de avisos electrónicos -""" - -from core.security import get_current_user, validate_access_to_resource -from typing import List - -from fastapi import APIRouter, Depends, HTTPException, Query, status -from sqlalchemy.orm import Session - -from core.database import get_core_db -from api.v1.common.tenant_crud_routes import TenantCRUDRoutes -from .dto import ( - ElectronicNoticeCreateDTO, - ElectronicNoticeResponseDTO, - ElectronicNoticeUpdateDTO, -) -from .models import ElectronicNotice -from .service import ElectronicNoticeService - -router = TenantCRUDRoutes( - service=ElectronicNoticeService, - create_schema=ElectronicNoticeCreateDTO, - update_schema=ElectronicNoticeUpdateDTO, - response_schema=ElectronicNoticeResponseDTO, - prefix="/electronic-notices", - tags=["electronic-notices"], - resource_name="Electronic Notice", - enable_list=True, - enable_filters=True, - list_permissions=["cat_notices.view"], - get_permissions=["cat_notices.view"], - create_permissions=["cat_notices.create"], - update_permissions=["cat_notices.edit"], - delete_permissions=["cat_notices.delete"], -).router - - -@router.get( - "/by-pedimento/{pedimento}", - response_model=List[ElectronicNoticeResponseDTO], - summary="Get notices by pedimento", -) -async def get_notices_by_pedimento( - pedimento: str, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - """Get all electronic notices for a specific pedimento""" - tenant_id = validate_access_to_resource(db, company_id, current_user, ["cat_notices.view"]) - notices = ElectronicNoticeService.get_by_pedimento( - db, pedimento, tenant_id, company_id) - return [ElectronicNoticeResponseDTO.model_validate(notice) for notice in notices] - - -@router.get( - "/by-status/{status}", - response_model=List[ElectronicNoticeResponseDTO], - summary="Get notices by status", -) -async def get_notices_by_status( - status: str, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - """Get all electronic notices with a specific status""" - tenant_id = validate_access_to_resource(db, company_id, current_user, ["cat_notices.view"]) - notices = ElectronicNoticeService.get_by_status( - db, status, tenant_id, company_id) - return [ElectronicNoticeResponseDTO.model_validate(notice) for notice in notices] diff --git a/backend/api/v1/modules/a76/general_catalogs/electronic_notices/service.py b/backend/api/v1/modules/a76/general_catalogs/electronic_notices/service.py deleted file mode 100644 index 0fadd40f..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/electronic_notices/service.py +++ /dev/null @@ -1,190 +0,0 @@ -""" -Capa de servicio para lógica de negocio de avisos electrónicos -""" - -import logging -from typing import Any, Dict, List, Optional, Tuple - -from fastapi import HTTPException -from sqlalchemy.exc import IntegrityError -from sqlalchemy.orm import Session - -from .dto import ( - ElectronicNoticeCreateDTO, - ElectronicNoticeResponseDTO, - ElectronicNoticeUpdateDTO, -) -from .models import ElectronicNotice - -logger = logging.getLogger(__name__) - - -class ElectronicNoticeService: - """Servicio para gestión de avisos electrónicos""" - - @staticmethod - def get_all( - db: Session, - tenant_id: int, - company_id: int, - skip: int = 0, - limit: int = 50, - filters: Optional[Dict[str, Any]] = None, - ) -> Tuple[List[ElectronicNotice], int]: - """Get all electronic notices with pagination""" - query = db.query(ElectronicNotice).filter( - ElectronicNotice.tenant_id == tenant_id, - ElectronicNotice.company_id == company_id - ) - - # Apply filters if provided - if filters: - if filters.get("notice_number"): - query = query.filter( - ElectronicNotice.notice_number.ilike( - f"%{filters['notice_number']}%" - ) - ) - if filters.get("status"): - query = query.filter( - ElectronicNotice.status.ilike(f"%{filters['status']}%") - ) - if filters.get("pedimento"): - query = query.filter( - ElectronicNotice.pedimento.ilike( - f"%{filters['pedimento']}%") - ) - - total = query.count() - notices = query.offset(skip).limit(limit).all() - - return notices, total - - @staticmethod - def get_by_id( - db: Session, id: int, tenant_id: int, company_id: int - ) -> Optional[ElectronicNotice]: - """Get electronic notice by ID""" - return db.query(ElectronicNotice).filter( - ElectronicNotice.id == id, - ElectronicNotice.tenant_id == tenant_id, - ElectronicNotice.company_id == company_id - ).first() - - @staticmethod - def create( - db: Session, notice_data: ElectronicNoticeCreateDTO, tenant_id: int, company_id: int - ) -> ElectronicNotice: - """Create a new electronic notice""" - try: - db_notice = ElectronicNotice( - **notice_data.model_dump(exclude_unset=True), - tenant_id=tenant_id, - company_id=company_id - ) - - db.add(db_notice) - db.commit() - db.refresh(db_notice) - - return db_notice - - except IntegrityError as e: - db.rollback() - logger.error( - f"IntegrityError creating electronic notice: {str(e)}") - raise HTTPException( - status_code=400, - detail="Electronic notice already exists", - ) - except Exception as e: - db.rollback() - logger.error(f"Error creating electronic notice: {str(e)}") - raise HTTPException( - status_code=500, detail="Error creating electronic notice" - ) - - @staticmethod - def update( - db: Session, id: int, tenant_id: int, notice_data: ElectronicNoticeUpdateDTO, company_id: int - ) -> Optional[ElectronicNotice]: - """Update an electronic notice""" - try: - db_notice = ElectronicNoticeService.get_by_id( - db, id, tenant_id, company_id) - if not db_notice: - return None - - for key, value in notice_data.model_dump(exclude_unset=True).items(): - setattr(db_notice, key, value) - - db.commit() - db.refresh(db_notice) - return db_notice - - except IntegrityError as e: - db.rollback() - logger.error( - f"IntegrityError updating electronic notice: {str(e)}") - raise HTTPException( - status_code=400, - detail="Error updating electronic notice", - ) - except Exception as e: - db.rollback() - logger.error(f"Error updating electronic notice: {str(e)}") - raise HTTPException( - status_code=500, detail="Error updating electronic notice" - ) - - @staticmethod - def delete( - db: Session, id: int, tenant_id: int, company_id: int - ) -> bool: - """Delete an electronic notice""" - try: - db_notice = ElectronicNoticeService.get_by_id( - db, id, tenant_id, company_id) - if not db_notice: - return False - - db.delete(db_notice) - db.commit() - return True - - except Exception as e: - db.rollback() - logger.error(f"Error deleting electronic notice: {str(e)}") - raise HTTPException( - status_code=500, detail="Error deleting electronic notice" - ) - - @staticmethod - def get_by_pedimento( - db: Session, pedimento: str, tenant_id: int, company_id: int - ) -> List[ElectronicNotice]: - """Get all electronic notices by pedimento""" - return ( - db.query(ElectronicNotice) - .filter( - ElectronicNotice.pedimento == pedimento, - ElectronicNotice.tenant_id == tenant_id, - ElectronicNotice.company_id == company_id - ) - .all() - ) - - @staticmethod - def get_by_status( - db: Session, status: str, tenant_id: int, company_id: int - ) -> List[ElectronicNotice]: - """Get all electronic notices by status""" - return ( - db.query(ElectronicNotice) - .filter( - ElectronicNotice.status == status, - ElectronicNotice.tenant_id == tenant_id, - ElectronicNotice.company_id == company_id - ) - .all() - ) diff --git a/backend/api/v1/modules/a76/general_catalogs/equivalencies/__init__.py b/backend/api/v1/modules/a76/general_catalogs/equivalencies/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/backend/api/v1/modules/a76/general_catalogs/equivalencies/dto.py b/backend/api/v1/modules/a76/general_catalogs/equivalencies/dto.py deleted file mode 100644 index 8dd1d318..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/equivalencies/dto.py +++ /dev/null @@ -1,60 +0,0 @@ -from typing import Optional -from decimal import Decimal - -from pydantic import BaseModel, Field, ConfigDict - -# EquivalencyItem DTOs (pool global independiente) - - -class EquivalencyItemBase(BaseModel): - original_field: str = Field(..., max_length=100, description="Campo original (from)") - external_field: str = Field(..., max_length=100, description="Campo externo (to)") - conversion_factor: Optional[Decimal] = Field(None, description="Factor de conversión") - equivalency_id: Optional[int] = Field(None, description="ID de la equivalencia padre") - - -class EquivalencyItemCreate(EquivalencyItemBase): - conversion_factor: Optional[Decimal] = Field(None, description="Factor de conversión") - - -class EquivalencyItemUpdate(BaseModel): - original_field: Optional[str] = Field(None, max_length=100) - external_field: Optional[str] = Field(None, max_length=100) - conversion_factor: Optional[Decimal] = None - equivalency_id: Optional[int] = None - - -class EquivalencyItemResponse(EquivalencyItemBase): - id: int - tenant_id: int - company_id: int - - model_config = ConfigDict(from_attributes=True) - - -# Equivalency DTOs (catálogo — referencia un item del pool) - - -class EquivalencyBase(BaseModel): - identifier: str = Field(..., max_length=50, description="Identificador único") - description: Optional[str] = Field(None, max_length=200, description="Descripción (opcional)") - item_id: Optional[int] = Field(None, description="ID del EquivalencyItem seleccionado") - - -class EquivalencyCreate(EquivalencyBase): - pass - - -class EquivalencyUpdate(BaseModel): - identifier: Optional[str] = Field(None, max_length=50) - description: Optional[str] = Field(None, max_length=200) - item_id: Optional[int] = Field(None, description="Re-asociar a otro EquivalencyItem") - - -class EquivalencyResponse(EquivalencyBase): - id: int - tenant_id: int - company_id: int - item: Optional[EquivalencyItemResponse] = None - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/a76/general_catalogs/equivalencies/models.py b/backend/api/v1/modules/a76/general_catalogs/equivalencies/models.py deleted file mode 100644 index cfcd69c2..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/equivalencies/models.py +++ /dev/null @@ -1,68 +0,0 @@ -from typing import Optional, List -from decimal import Decimal -from sqlalchemy import Integer, String, ForeignKey, UniqueConstraint, Numeric -from sqlalchemy.orm import Mapped, mapped_column, relationship -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base - - -class EquivalencyItem(Base, TenantScopedMixin, TimestampMixin): - """Pool global de mapeos campo_original → campo_externo. - Un mismo item puede ser referenciado por múltiples Equivalency.""" - - __tablename__ = "equivalency_items" - __table_args__ = ( - UniqueConstraint( - "equivalency_id", "original_field", "external_field", "tenant_id", "company_id", - name="uq_equivalency_item_fields" - ), - {"schema": "a76"} - ) - - id: Mapped[int] = mapped_column( - Integer, primary_key=True, autoincrement=True) - - original_field: Mapped[str] = mapped_column(String(100), nullable=False) - external_field: Mapped[str] = mapped_column(String(100), nullable=False) - conversion_factor: Mapped[Optional[Decimal]] = mapped_column( - Numeric(13, 6), nullable=True) - - equivalency_id: Mapped[Optional[int]] = mapped_column( - Integer, - ForeignKey("a76.equivalencies.id", ondelete="CASCADE"), - nullable=True) - - equivalency: Mapped[Optional["Equivalency"]] = relationship( - back_populates="items", foreign_keys=[equivalency_id]) - - equivalencies: Mapped[List["Equivalency"]] = relationship( - back_populates="item", foreign_keys="[Equivalency.item_id]") - - -class Equivalency(Base, TenantScopedMixin, TimestampMixin): - """Catálogo de equivalencias. Cada registro selecciona UN item del pool.""" - - __tablename__ = "equivalencies" - __table_args__ = ( - UniqueConstraint("identifier", "tenant_id", "company_id", - name="uq_equivalency_identifier"), - {"schema": "a76"} - ) - - id: Mapped[int] = mapped_column( - Integer, primary_key=True, autoincrement=True) - - identifier: Mapped[str] = mapped_column(String(50), nullable=False) - description: Mapped[Optional[str]] = mapped_column( - String(200), nullable=True) - - item_id: Mapped[Optional[int]] = mapped_column( - Integer, - ForeignKey("a76.equivalency_items.id", ondelete="SET NULL"), - nullable=True) - - item: Mapped[Optional["EquivalencyItem"]] = relationship( - back_populates="equivalencies", foreign_keys=[item_id]) - - items: Mapped[List["EquivalencyItem"]] = relationship( - back_populates="equivalency", foreign_keys="[EquivalencyItem.equivalency_id]") diff --git a/backend/api/v1/modules/a76/general_catalogs/equivalencies/routes.py b/backend/api/v1/modules/a76/general_catalogs/equivalencies/routes.py deleted file mode 100644 index f184f917..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/equivalencies/routes.py +++ /dev/null @@ -1,47 +0,0 @@ -from fastapi import APIRouter -from api.v1.common.tenant_crud_routes import TenantCRUDRoutes -from .dto import ( - EquivalencyCreate, EquivalencyResponse, EquivalencyUpdate, - EquivalencyItemCreate, EquivalencyItemResponse, EquivalencyItemUpdate -) -from .service import EquivalencyService, EquivalencyItemService - -router = APIRouter(prefix="/equivalencies", - tags=["a76.general_catalogs.equivalencies"]) - -item_crud = TenantCRUDRoutes( - service=EquivalencyItemService, - create_schema=EquivalencyItemCreate, - update_schema=EquivalencyItemUpdate, - response_schema=EquivalencyItemResponse, - prefix="/items", - tags=["Equivalency Items"], - resource_name="EquivalencyItem", - enable_list=True, - enable_filters=True, - list_permissions=["cat_equivalencies.view"], - get_permissions=["cat_equivalencies.view"], - create_permissions=["cat_equivalencies.create"], - update_permissions=["cat_equivalencies.edit"], - delete_permissions=["cat_equivalencies.delete"], -) - -equivalency_crud = TenantCRUDRoutes( - service=EquivalencyService, - create_schema=EquivalencyCreate, - update_schema=EquivalencyUpdate, - response_schema=EquivalencyResponse, - prefix="", - tags=["Equivalencies"], - resource_name="Equivalency", - enable_list=True, - enable_filters=True, - list_permissions=["cat_equivalencies.view"], - get_permissions=["cat_equivalencies.view"], - create_permissions=["cat_equivalencies.create"], - update_permissions=["cat_equivalencies.edit"], - delete_permissions=["cat_equivalencies.delete"], -) - -router.include_router(item_crud.router) -router.include_router(equivalency_crud.router) \ No newline at end of file diff --git a/backend/api/v1/modules/a76/general_catalogs/equivalencies/service.py b/backend/api/v1/modules/a76/general_catalogs/equivalencies/service.py deleted file mode 100644 index 2713e0de..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/equivalencies/service.py +++ /dev/null @@ -1,240 +0,0 @@ -from typing import List, Optional, Tuple, Dict, Any -from sqlalchemy.orm import Session -from sqlalchemy.exc import IntegrityError -from fastapi import HTTPException - -from .models import Equivalency, EquivalencyItem -from .dto import EquivalencyCreate, EquivalencyUpdate, EquivalencyItemCreate, EquivalencyItemUpdate - - -class EquivalencyItemService: - """CRUD simple sobre el pool global de items.""" - - @staticmethod - def get_all( - db: Session, - tenant_id: int, - company_id: int, - skip: int = 0, - limit: int = 100, - filters: Optional[Dict[str, Any]] = None - ) -> Tuple[List[EquivalencyItem], int]: - query = db.query(EquivalencyItem).filter( - EquivalencyItem.tenant_id == tenant_id, - EquivalencyItem.company_id == company_id, - ) - - if filters: - if "original_field" in filters: - query = query.filter( - EquivalencyItem.original_field.ilike(f"%{filters['original_field']}%") - ) - if "external_field" in filters: - query = query.filter( - EquivalencyItem.external_field.ilike(f"%{filters['external_field']}%") - ) - if "equivalency_id" in filters: - query = query.filter( - EquivalencyItem.equivalency_id == filters["equivalency_id"] - ) - - total = query.count() - items = query.offset(skip).limit(limit).all() - return items, total - - @staticmethod - def get_by_id( - db: Session, - id: int, - tenant_id: int, - company_id: int - ) -> Optional[EquivalencyItem]: - return db.query(EquivalencyItem).filter( - EquivalencyItem.id == id, - EquivalencyItem.tenant_id == tenant_id, - EquivalencyItem.company_id == company_id - ).first() - - @staticmethod - def create( - db: Session, - data: EquivalencyItemCreate, - tenant_id: int, - company_id: int - ) -> EquivalencyItem: - try: - db_obj = EquivalencyItem( - original_field=data.original_field, - external_field=data.external_field, - conversion_factor=data.conversion_factor, - equivalency_id=data.equivalency_id, - tenant_id=tenant_id, - company_id=company_id, - ) - db.add(db_obj) - db.commit() - db.refresh(db_obj) - return db_obj - except IntegrityError as e: - db.rollback() - error_msg = str(e.orig) if hasattr(e, "orig") else str(e) - if "uq_equivalency_item_fields" in error_msg: - raise HTTPException( - status_code=400, - detail=f"Ya existe un item con los mismos campos '{data.original_field}' → '{data.external_field}'." - ) from e - raise HTTPException(status_code=400, detail=f"Error al guardar: {error_msg}") from e - - @staticmethod - def update( - db: Session, - id: int, - tenant_id: int, - data: EquivalencyItemUpdate, - company_id: int - ) -> Optional[EquivalencyItem]: - db_obj = EquivalencyItemService.get_by_id(db, id, tenant_id, company_id) - if not db_obj: - return None - - try: - update_dict = data.model_dump(exclude_unset=True) - for key, value in update_dict.items(): - setattr(db_obj, key, value) - db.commit() - db.refresh(db_obj) - return db_obj - except IntegrityError as e: - db.rollback() - error_msg = str(e.orig) if hasattr(e, "orig") else str(e) - raise HTTPException(status_code=400, detail=f"Error al actualizar: {error_msg}") from e - - @staticmethod - def delete( - db: Session, - id: int, - tenant_id: int, - company_id: int - ) -> bool: - db_obj = EquivalencyItemService.get_by_id(db, id, tenant_id, company_id) - if not db_obj: - return False - db.delete(db_obj) - db.commit() - return True - - -class EquivalencyService: - """CRUD del catálogo de equivalencias. Cada registro referencia un EquivalencyItem.""" - - @staticmethod - def get_all( - db: Session, - tenant_id: int, - company_id: int, - skip: int = 0, - limit: int = 100, - filters: Optional[Dict[str, Any]] = None - ) -> Tuple[List[Equivalency], int]: - query = db.query(Equivalency).filter( - Equivalency.tenant_id == tenant_id, - Equivalency.company_id == company_id, - ) - - if filters: - if "from_unit_code" in filters: - query = query.filter( - Equivalency.identifier.ilike(f"%{filters['from_unit_code']}%") - ) - if "identifier" in filters: - query = query.filter( - Equivalency.identifier.ilike(f"%{filters['identifier']}%") - ) - if "description" in filters: - query = query.filter( - Equivalency.description.ilike(f"%{filters['description']}%") - ) - - total = query.count() - items = query.offset(skip).limit(limit).all() - return items, total - - @staticmethod - def get_by_id( - db: Session, - id: int, - tenant_id: int, - company_id: int - ) -> Optional[Equivalency]: - return db.query(Equivalency).filter( - Equivalency.id == id, - Equivalency.tenant_id == tenant_id, - Equivalency.company_id == company_id - ).first() - - @staticmethod - def create( - db: Session, - data: EquivalencyCreate, - tenant_id: int, - company_id: int - ) -> Equivalency: - try: - db_obj = Equivalency( - identifier=data.identifier, - description=data.description, - item_id=data.item_id, - tenant_id=tenant_id, - company_id=company_id, - ) - db.add(db_obj) - db.commit() - db.refresh(db_obj) - return db_obj - except IntegrityError as e: - db.rollback() - error_msg = str(e.orig) if hasattr(e, "orig") else str(e) - if "uq_equivalency_identifier" in error_msg: - raise HTTPException( - status_code=400, - detail=f"Ya existe un registro para el identificador '{data.identifier}'.", - ) from e - raise HTTPException(status_code=400, detail=f"Error al guardar: {error_msg}") from e - - @staticmethod - def update( - db: Session, - id: int, - tenant_id: int, - data: EquivalencyUpdate, - company_id: int - ) -> Optional[Equivalency]: - db_obj = EquivalencyService.get_by_id(db, id, tenant_id, company_id) - if not db_obj: - return None - - try: - update_dict = data.model_dump(exclude_unset=True) - for key, value in update_dict.items(): - setattr(db_obj, key, value) - db.commit() - db.refresh(db_obj) - return db_obj - except IntegrityError as e: - db.rollback() - error_msg = str(e.orig) if hasattr(e, "orig") else str(e) - raise HTTPException(status_code=400, detail=f"Error al actualizar: {error_msg}") from e - - @staticmethod - def delete( - db: Session, - id: int, - tenant_id: int, - company_id: int - ) -> bool: - db_obj = EquivalencyService.get_by_id(db, id, tenant_id, company_id) - if not db_obj: - return False - db.delete(db_obj) - db.commit() - return True diff --git a/backend/api/v1/modules/a76/general_catalogs/error_catalogs/__init__.py b/backend/api/v1/modules/a76/general_catalogs/error_catalogs/__init__.py deleted file mode 100644 index 4537d3f3..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/error_catalogs/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -""" -Módulo de catálogos de errores -""" diff --git a/backend/api/v1/modules/a76/general_catalogs/error_catalogs/dto.py b/backend/api/v1/modules/a76/general_catalogs/error_catalogs/dto.py deleted file mode 100644 index fd8e5033..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/error_catalogs/dto.py +++ /dev/null @@ -1,105 +0,0 @@ -""" -DTOs (Data Transfer Objects) para módulo de catálogos de errores -""" - -from typing import Optional, List - -from pydantic import BaseModel, Field - - -# ============ ERROR CLASSIFICATION DTOS ============ -class ErrorClassificationCreateDTO(BaseModel): - """DTO para crear una clasificación de error""" - - code: str = Field(..., max_length=100, description="Classification code") - level: Optional[str] = Field(None, max_length=3, description="Level") - - class Config: - from_attributes = True - - -class ErrorClassificationUpdateDTO(BaseModel): - """DTO para actualizar una clasificación de error""" - - level: Optional[str] = Field(None, max_length=3, description="Level") - - class Config: - from_attributes = True - - -class ErrorClassificationResponseDTO(BaseModel): - """DTO para responder con datos de una clasificación de error""" - - id: int - code: str - level: Optional[str] = None - - class Config: - from_attributes = True - - -class ErrorClassificationDetailResponseDTO(BaseModel): - """DTO detallado para responder con datos de una clasificación y sus errores""" - - id: int - code: str - level: Optional[str] = None - errors: List["ErrorCatalogResponseDTO"] = [] - - class Config: - from_attributes = True - - -# ============ ERROR CATALOG DTOS ============ -class ErrorCatalogCreateDTO(BaseModel): - """DTO para crear un error en el catálogo""" - - code: str = Field(..., max_length=15, description="Error code") - description: Optional[str] = Field( - None, max_length=255, description="Error description" - ) - classification_id: Optional[int] = Field( - None, description="Classification ID" - ) - - class Config: - from_attributes = True - - -class ErrorCatalogUpdateDTO(BaseModel): - """DTO para actualizar un error en el catálogo""" - - description: Optional[str] = Field( - None, max_length=255, description="Error description" - ) - classification_id: Optional[int] = Field( - None, description="Classification ID" - ) - - class Config: - from_attributes = True - - -class ErrorCatalogResponseDTO(BaseModel): - """DTO para responder con datos de un error en el catálogo""" - - id: int - code: str - description: Optional[str] = None - classification_id: Optional[int] = None - - class Config: - from_attributes = True - - -class ErrorCatalogDetailResponseDTO(BaseModel): - """DTO detallado para responder con datos de un error y su clasificación""" - - id: int - code: str - description: Optional[str] = None - classification_id: Optional[int] = None - classification: Optional[ErrorClassificationResponseDTO] = None - - class Config: - from_attributes = True diff --git a/backend/api/v1/modules/a76/general_catalogs/error_catalogs/models.py b/backend/api/v1/modules/a76/general_catalogs/error_catalogs/models.py deleted file mode 100644 index 147770c3..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/error_catalogs/models.py +++ /dev/null @@ -1,82 +0,0 @@ -""" -Modelos ORM para gestión de catálogos de errores -""" - -from typing import Optional - -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base -from sqlalchemy import ForeignKeyConstraint, Integer, PrimaryKeyConstraint, String, UniqueConstraint -from sqlalchemy.orm import Mapped, mapped_column, relationship - - -class ErrorClassification(Base, TenantScopedMixin, TimestampMixin): - """ - Modelo para la tabla ErrorClassification - Clasificación de Errores - """ - - __tablename__ = "error_classifications" # GCatErroresClas - __table_args__ = ( - PrimaryKeyConstraint("id", name="error_classifications_pkey"), - UniqueConstraint("code", name="error_classifications_code_unique"), - {"schema": "a76"}, - ) - - # Primary key - id: Mapped[int] = mapped_column( - Integer, primary_key=True, autoincrement=True - ) - - # Classification code (unique) - code: Mapped[str] = mapped_column(String(100), nullable=False, unique=True) - - # Classification information - level: Mapped[Optional[str]] = mapped_column(String(3)) - - # Relationships - errors: Mapped[list["ErrorCatalog"]] = relationship( - "ErrorCatalog", back_populates="classification", cascade="all, delete-orphan" - ) - - def __repr__(self): - return f"" - - -class ErrorCatalog(Base, TenantScopedMixin, TimestampMixin): - """ - Modelo para la tabla ErrorCatalog - Catálogo de Errores - """ - - __tablename__ = "error_catalogs" # GCatErrores - __table_args__ = ( - PrimaryKeyConstraint("id", name="error_catalogs_pkey"), - UniqueConstraint("code", name="error_catalogs_code_unique"), - ForeignKeyConstraint( - ["classification_id"], - ["a76.error_classifications.id"], - name="fk_error_catalogs_classification", - ), - {"schema": "a76"}, - ) - - # Primary key - id: Mapped[int] = mapped_column( - Integer, primary_key=True, autoincrement=True - ) - - # Error code (unique) - code: Mapped[str] = mapped_column(String(15), nullable=False, unique=True) - - # Error information - description: Mapped[Optional[str]] = mapped_column(String(255)) - - # Foreign key to classification - classification_id: Mapped[Optional[int]] = mapped_column(Integer) - - # Relationships - classification: Mapped[Optional["ErrorClassification"]] = relationship( - "ErrorClassification", back_populates="errors" - ) - - def __repr__(self): - return f"" \ No newline at end of file diff --git a/backend/api/v1/modules/a76/general_catalogs/error_catalogs/routes.py b/backend/api/v1/modules/a76/general_catalogs/error_catalogs/routes.py deleted file mode 100644 index 6af9fea3..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/error_catalogs/routes.py +++ /dev/null @@ -1,203 +0,0 @@ -""" -Rutas para gestión de catálogos de errores -""" - -from typing import List - -from fastapi import APIRouter, Depends, HTTPException, Query, status -from sqlalchemy.orm import Session - -from core.database import get_core_db -from core.security import get_current_user -from api.v1.common.tenant_crud_routes import TenantCRUDRoutes, validate_access_to_resource -from .dto import ( - ErrorClassificationCreateDTO, - ErrorClassificationResponseDTO, - ErrorClassificationUpdateDTO, - ErrorClassificationDetailResponseDTO, - ErrorCatalogCreateDTO, - ErrorCatalogResponseDTO, - ErrorCatalogUpdateDTO, - ErrorCatalogDetailResponseDTO, -) -from .models import ErrorClassification, ErrorCatalog -from .service import ErrorClassificationService, ErrorCatalogService - -router = APIRouter(prefix="/error-catalogs", tags=["error-catalogs"]) - - -classification_crud = TenantCRUDRoutes( - service=ErrorClassificationService, - create_schema=ErrorClassificationCreateDTO, - update_schema=ErrorClassificationUpdateDTO, - response_schema=ErrorClassificationResponseDTO, - prefix="/classifications", - tags=["error-classifications"], - resource_name="ErrorClassification", - enable_list=True, - list_permissions=["cat_errors.view"], - get_permissions=["cat_errors.view"], - create_permissions=["cat_errors.create"], - update_permissions=["cat_errors.edit"], - delete_permissions=["cat_errors.delete"], -) - -# Add custom endpoints for classifications - - -@classification_crud.router.get( - "/code/{code}", - response_model=ErrorClassificationDetailResponseDTO, - summary="Get error classification by code with errors", -) -async def get_classification_by_code( - code: str, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - """Get an error classification by its code with all related errors""" - tenant_id = validate_access_to_resource(db, company_id, current_user, ["cat_errors.view"]) - classification = ErrorClassificationService.get_by_code( - db, - code, - tenant_id=tenant_id, - company_id=company_id, - ) - if not classification: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="Classification not found", - ) - return ErrorClassificationDetailResponseDTO.model_validate(classification) - -# Override get_by_id to return detail DTO - - -@classification_crud.router.get( - "/{id}", - response_model=ErrorClassificationDetailResponseDTO, - summary="Get error classification by ID with errors", -) -async def get_classification( - id: int, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - """Get an error classification by its ID with all related errors""" - tenant_id = validate_access_to_resource(db, company_id, current_user, ["cat_errors.view"]) - classification = ErrorClassificationService.get_by_id( - db, - id, - tenant_id=tenant_id, - company_id=company_id, - ) - if not classification: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="Classification not found", - ) - return ErrorClassificationDetailResponseDTO.model_validate(classification) - -router.include_router(classification_crud.router) - - - -catalog_crud = TenantCRUDRoutes( - service=ErrorCatalogService, - create_schema=ErrorCatalogCreateDTO, - update_schema=ErrorCatalogUpdateDTO, - response_schema=ErrorCatalogResponseDTO, - prefix="", - tags=["error-catalogs"], - resource_name="ErrorCatalog", - enable_list=True, - list_permissions=["cat_errors.view"], - get_permissions=["cat_errors.view"], - create_permissions=["cat_errors.create"], - update_permissions=["cat_errors.edit"], - delete_permissions=["cat_errors.delete"], -) - -# Add custom endpoints for catalogs - - -@catalog_crud.router.get( - "/code/{code}", - response_model=ErrorCatalogDetailResponseDTO, - summary="Get error by code", -) -async def get_error_by_code( - code: str, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - """Get an error by its code with classification details""" - tenant_id = validate_access_to_resource(db, company_id, current_user, ["cat_errors.view"]) - error = ErrorCatalogService.get_by_code( - db, - code, - tenant_id=tenant_id, - company_id=company_id, - ) - if not error: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="Error not found", - ) - return ErrorCatalogDetailResponseDTO.model_validate(error) - - -@catalog_crud.router.get( - "/classification/{classification_id}", - response_model=List[ErrorCatalogResponseDTO], - summary="Get errors by classification", -) -async def get_errors_by_classification( - classification_id: int, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - """Get all errors for a specific classification""" - tenant_id = validate_access_to_resource(db, company_id, current_user, ["cat_errors.view"]) - errors = ErrorCatalogService.get_by_classification( - db, - classification_id, - tenant_id=tenant_id, - company_id=company_id, - ) - return [ErrorCatalogResponseDTO.model_validate(error) for error in errors] - -# Override get_by_id to return detail DTO - - -@catalog_crud.router.get( - "/{id}", - response_model=ErrorCatalogDetailResponseDTO, - summary="Get error by ID", -) -async def get_error( - id: int, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - """Get an error by its ID with classification details""" - tenant_id = validate_access_to_resource(db, company_id, current_user, ["cat_errors.view"]) - error = ErrorCatalogService.get_by_id( - db, - id, - tenant_id=tenant_id, - company_id=company_id, - ) - if not error: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="Error not found", - ) - return ErrorCatalogDetailResponseDTO.model_validate(error) - -router.include_router(catalog_crud.router) diff --git a/backend/api/v1/modules/a76/general_catalogs/error_catalogs/service.py b/backend/api/v1/modules/a76/general_catalogs/error_catalogs/service.py deleted file mode 100644 index 06ebf664..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/error_catalogs/service.py +++ /dev/null @@ -1,354 +0,0 @@ -""" -Capa de servicio para lógica de negocio de catálogos de errores -""" - -import logging -from typing import Any, Dict, List, Optional, Tuple - -from fastapi import HTTPException -from sqlalchemy.exc import IntegrityError -from sqlalchemy.orm import Session - -from .dto import ( - ErrorClassificationCreateDTO, - ErrorClassificationResponseDTO, - ErrorClassificationUpdateDTO, - ErrorCatalogCreateDTO, - ErrorCatalogResponseDTO, - ErrorCatalogUpdateDTO, -) -from .models import ErrorClassification, ErrorCatalog - -logger = logging.getLogger(__name__) - - -class ErrorClassificationService: - """Servicio para gestión de clasificaciones de errores""" - - @staticmethod - def get_all( - db: Session, - tenant_id: int, - company_id: int, - skip: int = 0, - limit: int = 50, - filters: Optional[Dict[str, Any]] = None, - ) -> Tuple[List[ErrorClassification], int]: - """Get all error classifications with pagination""" - query = db.query(ErrorClassification).filter( - ErrorClassification.tenant_id == tenant_id, - ErrorClassification.company_id == company_id - ) - - if filters: - if filters.get("code"): - query = query.filter( - ErrorClassification.code.ilike(f"%{filters['code']}%") - ) - if filters.get("level"): - query = query.filter( - ErrorClassification.level.ilike(f"%{filters['level']}%") - ) - - total = query.count() - classifications = query.offset(skip).limit(limit).all() - - return classifications, total - - @staticmethod - def get_by_code( - db: Session, code: str, tenant_id: int, company_id: int - ) -> Optional[ErrorClassification]: - """Get error classification by code""" - return ( - db.query(ErrorClassification) - .filter( - ErrorClassification.code == code, - ErrorClassification.tenant_id == tenant_id, - ErrorClassification.company_id == company_id - ) - .first() - ) - - @staticmethod - def get_by_id( - db: Session, id: int, tenant_id: int, company_id: int - ) -> Optional[ErrorClassification]: - """Get error classification by ID""" - return ( - db.query(ErrorClassification) - .filter( - ErrorClassification.id == id, - ErrorClassification.tenant_id == tenant_id, - ErrorClassification.company_id == company_id - ) - .first() - ) - - @staticmethod - def create( - db: Session, classification_data: ErrorClassificationCreateDTO, tenant_id: int, company_id: int - ) -> ErrorClassification: - """Create a new error classification""" - try: - db_classification = ErrorClassification( - **classification_data.model_dump(exclude_unset=True), - tenant_id=tenant_id, - company_id=company_id - ) - - db.add(db_classification) - db.commit() - db.refresh(db_classification) - - return db_classification - - except IntegrityError as e: - db.rollback() - logger.error( - f"IntegrityError creating error classification: {str(e)}") - raise HTTPException( - status_code=400, - detail="Error classification already exists", - ) - except Exception as e: - db.rollback() - logger.error(f"Error creating error classification: {str(e)}") - raise HTTPException( - status_code=500, detail="Error creating error classification" - ) - - @staticmethod - def update( - db: Session, id: int, tenant_id: int, classification_data: ErrorClassificationUpdateDTO, company_id: int - ) -> Optional[ErrorClassification]: - """Update an error classification""" - try: - db_classification = ErrorClassificationService.get_by_id( - db, id, tenant_id, company_id) - if not db_classification: - return None - - for key, value in classification_data.model_dump(exclude_unset=True).items(): - setattr(db_classification, key, value) - - db.commit() - db.refresh(db_classification) - return db_classification - - except IntegrityError as e: - db.rollback() - logger.error( - f"IntegrityError updating error classification: {str(e)}") - raise HTTPException( - status_code=400, - detail="Error updating error classification", - ) - except Exception as e: - db.rollback() - logger.error(f"Error updating error classification: {str(e)}") - raise HTTPException( - status_code=500, detail="Error updating error classification" - ) - - @staticmethod - def delete( - db: Session, id: int, tenant_id: int, company_id: int - ) -> bool: - """Delete an error classification""" - try: - db_classification = ErrorClassificationService.get_by_id( - db, id, tenant_id, company_id) - if not db_classification: - return False - - db.delete(db_classification) - db.commit() - return True - - except Exception as e: - db.rollback() - logger.error(f"Error deleting error classification: {str(e)}") - raise HTTPException( - status_code=500, detail="Error deleting error classification" - ) - - -class ErrorCatalogService: - """Servicio para gestión de catálogos de errores""" - - @staticmethod - def get_all( - db: Session, - tenant_id: int, - company_id: int, - skip: int = 0, - limit: int = 50, - filters: Optional[Dict[str, Any]] = None, - ) -> Tuple[List[ErrorCatalog], int]: - """Get all error catalogs with pagination""" - query = db.query(ErrorCatalog).filter( - ErrorCatalog.tenant_id == tenant_id, - ErrorCatalog.company_id == company_id - ) - - if filters: - if filters.get("code"): - query = query.filter( - ErrorCatalog.code.ilike(f"%{filters['code']}%")) - if filters.get("description"): - query = query.filter( - ErrorCatalog.description.ilike( - f"%{filters['description']}%") - ) - if filters.get("classification_id"): - query = query.filter( - ErrorCatalog.classification_id == filters['classification_id'] - ) - - total = query.count() - catalogs = query.offset(skip).limit(limit).all() - - return catalogs, total - - @staticmethod - def get_by_code( - db: Session, code: str, tenant_id: int, company_id: int - ) -> Optional[ErrorCatalog]: - """Get error catalog by code""" - return db.query(ErrorCatalog).filter( - ErrorCatalog.code == code, - ErrorCatalog.tenant_id == tenant_id, - ErrorCatalog.company_id == company_id - ).first() - - @staticmethod - def get_by_id( - db: Session, id: int, tenant_id: int, company_id: int - ) -> Optional[ErrorCatalog]: - """Get error catalog by ID""" - return db.query(ErrorCatalog).filter( - ErrorCatalog.id == id, - ErrorCatalog.tenant_id == tenant_id, - ErrorCatalog.company_id == company_id - ).first() - - @staticmethod - def get_by_classification( - db: Session, classification_id: int, tenant_id: int, company_id: int - ) -> List[ErrorCatalog]: - """Get all errors by classification""" - return ( - db.query(ErrorCatalog) - .filter( - ErrorCatalog.classification_id == classification_id, - ErrorCatalog.tenant_id == tenant_id, - ErrorCatalog.company_id == company_id - ) - .all() - ) - - @staticmethod - def create( - db: Session, error_data: ErrorCatalogCreateDTO, tenant_id: int, company_id: int - ) -> ErrorCatalog: - """Create a new error catalog""" - try: - # Validate classification exists if provided - if error_data.classification_id: - classification = ( - db.query(ErrorClassification) - .filter( - ErrorClassification.id == error_data.classification_id, - ErrorClassification.tenant_id == tenant_id, - ErrorClassification.company_id == company_id - ) - .first() - ) - if not classification: - raise HTTPException( - status_code=400, - detail="Classification not found", - ) - - db_error = ErrorCatalog( - **error_data.model_dump(exclude_unset=True), - tenant_id=tenant_id, - company_id=company_id - ) - - db.add(db_error) - db.commit() - db.refresh(db_error) - - return db_error - - except HTTPException: - raise - except IntegrityError as e: - db.rollback() - logger.error(f"IntegrityError creating error catalog: {str(e)}") - raise HTTPException( - status_code=400, - detail="Error already exists", - ) - except Exception as e: - db.rollback() - logger.error(f"Error creating error catalog: {str(e)}") - raise HTTPException( - status_code=500, detail="Error creating error catalog" - ) - - @staticmethod - def update( - db: Session, id: int, tenant_id: int, error_data: ErrorCatalogUpdateDTO, company_id: int - ) -> Optional[ErrorCatalog]: - """Update an error catalog""" - try: - db_error = ErrorCatalogService.get_by_id( - db, id, tenant_id, company_id) - if not db_error: - return None - - for key, value in error_data.model_dump(exclude_unset=True).items(): - setattr(db_error, key, value) - - db.commit() - db.refresh(db_error) - return db_error - - except IntegrityError as e: - db.rollback() - logger.error(f"IntegrityError updating error catalog: {str(e)}") - raise HTTPException( - status_code=400, - detail="Error updating error catalog", - ) - except Exception as e: - db.rollback() - logger.error(f"Error updating error catalog: {str(e)}") - raise HTTPException( - status_code=500, detail="Error updating error catalog" - ) - - @staticmethod - def delete( - db: Session, id: int, tenant_id: int, company_id: int - ) -> bool: - """Delete an error catalog""" - try: - db_error = ErrorCatalogService.get_by_id( - db, id, tenant_id, company_id) - if not db_error: - return False - - db.delete(db_error) - db.commit() - return True - - except Exception as e: - db.rollback() - logger.error(f"Error deleting error catalog: {str(e)}") - raise HTTPException( - status_code=500, detail="Error deleting error catalog" - ) diff --git a/backend/api/v1/modules/a76/general_catalogs/exchange_rate/dto.py b/backend/api/v1/modules/a76/general_catalogs/exchange_rate/dto.py deleted file mode 100644 index ce0ddea8..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/exchange_rate/dto.py +++ /dev/null @@ -1,31 +0,0 @@ -from datetime import datetime -from typing import Optional - -from pydantic import BaseModel, Field - - -class ExchangeRateBaseDTO(BaseModel): - date: datetime = Field(..., description="Exchange rate date") - value: Optional[float] = Field(None, description="Exchange rate value") - local_currency: Optional[str] = Field(None, max_length=7, description="Local currency code") - foreign_currency: Optional[str] = Field(None, max_length=7, description="Foreign currency code") - - -class ExchangeRateCreateDTO(ExchangeRateBaseDTO): - """Schema for creating an exchange rate""" - pass - - -class ExchangeRateUpdateDTO(ExchangeRateBaseDTO): - """Schema for updating an exchange rate""" - date: Optional[datetime] = Field(None, description="Exchange rate date") - - -class ExchangeRateResponseDTO(ExchangeRateBaseDTO): - """Schema for exchange rate response""" - id: int - company_id: int - tenant_id: int - - class Config: - from_attributes = True diff --git a/backend/api/v1/modules/a76/general_catalogs/exchange_rate/models.py b/backend/api/v1/modules/a76/general_catalogs/exchange_rate/models.py deleted file mode 100644 index 92f8e4f7..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/exchange_rate/models.py +++ /dev/null @@ -1,38 +0,0 @@ -from datetime import datetime -from decimal import Decimal -from typing import Optional - -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base -from sqlalchemy import ( - DECIMAL, - DateTime, - ForeignKeyConstraint, - Integer, - PrimaryKeyConstraint, - String, - UniqueConstraint, -) -from sqlalchemy.orm import Mapped, mapped_column - - -class ExchangeRate(Base, TenantScopedMixin, TimestampMixin): - __tablename__ = "exchange_rate" - __table_args__ = ( - PrimaryKeyConstraint("id", name="exchange_rate_pkey"), - - UniqueConstraint( - "tenant_id", "company_id", "date", name="uq_exchange_rate_date_tenant" - ), - {"schema": "a76"}, - ) - - id: Mapped[int] = mapped_column(Integer, primary_key=True) - - date: Mapped[datetime] = mapped_column(DateTime) - - value: Mapped[Optional[Decimal]] = mapped_column(DECIMAL(13, 6)) - - local_currency: Mapped[Optional[str]] = mapped_column(String(7)) - - foreign_currency: Mapped[Optional[str]] = mapped_column(String(7)) diff --git a/backend/api/v1/modules/a76/general_catalogs/exchange_rate/routes.py b/backend/api/v1/modules/a76/general_catalogs/exchange_rate/routes.py deleted file mode 100644 index 0fdccb7e..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/exchange_rate/routes.py +++ /dev/null @@ -1,127 +0,0 @@ -from typing import Any, Dict, Optional -from fastapi import Depends, Query -from sqlalchemy.orm import Session -from api.v1.common.tenant_crud_routes import ( - TenantCRUDRoutes, - validate_access_to_resource, - get_core_db, - get_current_user, -) - -from .dto import ExchangeRateCreateDTO, ExchangeRateResponseDTO, ExchangeRateUpdateDTO -from .services import ExchangeRateService - -# Create router using TenantCRUDRoutes factory -route_handler = TenantCRUDRoutes( - service=ExchangeRateService, - create_schema=ExchangeRateCreateDTO, - update_schema=ExchangeRateUpdateDTO, - response_schema=ExchangeRateResponseDTO, - prefix="/exchange-rate", - tags=[], - resource_name="Exchange Rate", - id_name="id", # Using numeric ID - enable_list=False, # Disable default list to provide custom one with filters - enable_filters=False, - default_page_size=50, - max_page_size=100, - # Permisos - list_permissions=["cat_exchange_rates.view"], - get_permissions=["cat_exchange_rates.view"], - create_permissions=["cat_exchange_rates.create"], - update_permissions=["cat_exchange_rates.edit"], - delete_permissions=["cat_exchange_rates.delete"], -) - -crud_router = route_handler.router - -# Create a custom router for specific endpoints that must be matched BEFORE generic CRUD routes -# We use the same prefix so they are grouped together -from fastapi import APIRouter -custom_router = APIRouter(prefix="/exchange-rate", tags=[]) - -# CSV import: add to custom_router BEFORE including it in master, so /exchange-rate/imports/* is registered -from api.v1.modules.a76.layouts_csv.exchange_rate.routes import router as imports_router -custom_router.include_router(imports_router, prefix="/imports", tags=["exchange_rate / csv_import"]) - -@custom_router.get("/test-ping") -async def test_ping(): - return {"message": "pong"} - -# Master router to export -router = APIRouter() -# Include custom routes FIRST to avoid shadowing by /{id} -router.include_router(custom_router) -# router.include_router(crud_router) - - -@custom_router.get( - "/", - response_model=Dict[str, Any], - summary="List Exchange Rates", - description="Get paginated list of exchange rates with optional date filter", -) -async def list_exchange_rates( - company_id: int = Query(..., description="Company ID"), - date: Optional[str] = Query(None, description="Filter by date (YYYY-MM-DD)"), - page: int = Query(1, ge=1, description="Page number"), - page_size: int = Query( - 50, - ge=1, - le=100, - description="Page size", - ), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - tenant_id = validate_access_to_resource(db, company_id, current_user, ["cat_exchange_rates.view"]) - - skip = (page - 1) * page_size - filters = {} - if date: - filters["date"] = date - - items, total = ExchangeRateService.get_all( - db, tenant_id, company_id, skip, page_size, filters - ) - - return { - "items": [ExchangeRateResponseDTO.model_validate(item) for item in items], - "total": total, - "page": page, - "page_size": page_size, - } - -@custom_router.get( - "/dof-search", - response_model=Dict[str, Any], - summary="Fetch Exchange Rate from DOF", - description="Fetches the exchange rate from the Official Journal of the Federation (DOF) for a specific date.", -) -async def fetch_exchange_rate_dof( - date: str = Query(..., description="Date in YYYY-MM-DD format"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - # This endpoint can be public or protected. Assuming protected for now. - # No specific tenant validation needed since it's an external query, - # but good to ensure user is authenticated. - - try: - print(f"DEBUG: Route called with date={date}") - rate = ExchangeRateService.fetch_from_dof(date) - - if rate is None: - return {"success": False, "message": "No se encontró el tipo de cambio en el DOF para la fecha especificada o el servicio no está disponible.", "value": None} - - return {"success": True, "value": rate} - except Exception as e: - print(f"DEBUG: Error in route: {e}") - import traceback - traceback.print_exc() - return {"success": False, "message": f"Error interno: {str(e)}", "value": None} - -# Include routers at the end to ensure all routes are registered -# Include custom routes FIRST to avoid shadowing by /{id} of crud_router -router.include_router(custom_router) -router.include_router(crud_router) diff --git a/backend/api/v1/modules/a76/general_catalogs/exchange_rate/services.py b/backend/api/v1/modules/a76/general_catalogs/exchange_rate/services.py deleted file mode 100644 index ee33f82a..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/exchange_rate/services.py +++ /dev/null @@ -1,234 +0,0 @@ -from typing import Optional, Tuple, List, Dict, Any -from datetime import datetime, time -import requests -import re - -from sqlalchemy.orm import Session -from sqlalchemy import cast, Date - -from . import dto, models -import urllib3 -from core.config import settings - -urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) - - - - -class ExchangeRateService: - """Service for ExchangeRate CRUD operations with tenant support""" - - @staticmethod - def get_all( - db: Session, - tenant_id: int, - company_id: int, - skip: int = 0, - limit: int = 50, - filters: Optional[Dict[str, Any]] = None, - ) -> Tuple[List[models.ExchangeRate], int]: - """Get all exchange rates for a tenant/company with pagination""" - query = db.query(models.ExchangeRate).filter( - models.ExchangeRate.tenant_id == tenant_id, - models.ExchangeRate.company_id == company_id, - ) - - # Apply filters if provided - if filters: - if filters.get("date"): - # Use range query to utilize index on (tenant_id, company_id, date) efficiently - # filters["date"] is expected to be 'YYYY-MM-DD' - try: - date_str = filters["date"] - date_val = datetime.strptime(date_str, "%Y-%m-%d").date() - start_date = datetime.combine(date_val, time.min) - end_date = datetime.combine(date_val, time.max) - - query = query.filter( - models.ExchangeRate.date >= start_date, - models.ExchangeRate.date <= end_date, - ) - except (ValueError, TypeError): - # Fallback to cast if date format is invalid or logic fails, though validation should catch this - query = query.filter( - cast(models.ExchangeRate.date, Date) == filters["date"] - ) - if filters.get("local_currency"): - query = query.filter( - models.ExchangeRate.local_currency == filters["local_currency"] - ) - if filters.get("foreign_currency"): - query = query.filter( - models.ExchangeRate.foreign_currency == filters["foreign_currency"] - ) - - total = query.count() - exchange_rates = ( - query.order_by(models.ExchangeRate.date.desc()) - .offset(skip) - .limit(limit) - .all() - ) - - return exchange_rates, total - - @staticmethod - def get_by_id( - db: Session, exchange_rate_id: int, tenant_id: int, company_id: int - ) -> Optional[models.ExchangeRate]: - """Get exchange rate by ID""" - return ( - db.query(models.ExchangeRate) - .filter( - models.ExchangeRate.id == exchange_rate_id, - models.ExchangeRate.tenant_id == tenant_id, - models.ExchangeRate.company_id == company_id, - ) - .first() - ) - - @staticmethod - def create( - db: Session, - exchange_rate_data: dto.ExchangeRateCreateDTO, - tenant_id: int, - company_id: int, - ) -> models.ExchangeRate: - """Create a new exchange rate""" - new_exchange_rate = models.ExchangeRate( - **exchange_rate_data.model_dump(), - tenant_id=tenant_id, - company_id=company_id - ) - db.add(new_exchange_rate) - db.commit() - db.refresh(new_exchange_rate) - return new_exchange_rate - - @staticmethod - def update( - db: Session, - exchange_rate_id: int, - tenant_id: int, - exchange_rate_data: dto.ExchangeRateUpdateDTO, - company_id: int, - ) -> Optional[models.ExchangeRate]: - """Update an exchange rate""" - exchange_rate = ExchangeRateService.get_by_id( - db, exchange_rate_id, tenant_id, company_id - ) - if not exchange_rate: - return None - - # Update fields - update_data = exchange_rate_data.model_dump(exclude_unset=True) - for field, value in update_data.items(): - setattr(exchange_rate, field, value) - - db.commit() - db.refresh(exchange_rate) - return exchange_rate - - @staticmethod - def delete( - db: Session, exchange_rate_id: int, tenant_id: int, company_id: int - ) -> bool: - """Delete an exchange rate""" - exchange_rate = ExchangeRateService.get_by_id( - db, exchange_rate_id, tenant_id, company_id - ) - if not exchange_rate: - return False - - db.delete(exchange_rate) - db.commit() - return True - - @staticmethod - def _get_sitar_api_token(base_url, username, password) -> Optional[str]: - """Helper to get authentication token from external API""" - try: - print(base_url) - login_url = f"{base_url}/exchange-rate/auth/login" - payload = {"username": username, "password": password} - headers = {"Content-Type": "application/json"} - - response = requests.post(login_url, json=payload, headers=headers, timeout=5) - if response.status_code not in [200, 201]: - print(f"External API Login Failed: {response.status_code} - {response.text}") - return None - - data = response.json() - return data.get("token") or data.get("access_token") - except Exception as e: - print(f"External API Login Error: {e}") - return None - - @staticmethod - def fetch_from_dof(date_str: str) -> Optional[float]: - """ - Fetches the exchange rate from an external API (replacing direct DOF scraping). - The API handles date logic (holidays, weekends) automatically. - - Args: - date_str (str): Date in 'YYYY-MM-DD' format. - - Returns: - Optional[float]: The exchange rate value if found, None otherwise. - """ - # API Credentials - API_BASE_URL = settings.SITAR_API_URL - API_USER = settings.SITAR_API_USER - API_PASS = settings.SITAR_API_PASSWORD - - if not API_USER or not API_PASS: - print("ERROR: External API credentials not properly configured in settings") - return None - - try: - print(f"DEBUG: Fetching External API for date: {date_str}") - - # 1. Get Token - token = ExchangeRateService._get_sitar_api_token(API_BASE_URL, API_USER, API_PASS) - if not token: - print("Failed to obtain external API token") - return None - - # 2. Fetch Exchange Rate - # The API endpoint is /tipoCambio/{YYYY-MM-DD} - tc_endpoint = f"{API_BASE_URL}/exchange-rate/tipoCambio/{date_str}" - - # Auth header: The API expects just the token string in common usage, but we try standard first - # based on user feedback/code: 'Authorization:' . $token - headers = { - "Authorization": token, - "Content-Type": "application/json" - } - - response = requests.get(tc_endpoint, headers=headers, timeout=5) - - # Retry logic as per PHP reference (if 401, maybe formatting issue, but requests handles headers well) - if response.status_code == 401: - # Try with Bearer prefix just in case, though PHP code suggested raw token - print("DEBUG: 401 received, retrying with Bearer prefix...") - headers["Authorization"] = f"Bearer {token}" - response = requests.get(tc_endpoint, headers=headers, timeout=5) - - if response.status_code != 200: - print(f"External API TC Error: {response.status_code} - {response.text}") - return None - - data = response.json() - # Expected response: {"Id":..., "Fecha":"...", "TipoCambio":17.452, "Mov":"..."} - - if "TipoCambio" in data: - val = float(data["TipoCambio"]) - print(f"DEBUG: External API returned value: {val}") - return val - - print(f"DEBUG: 'TipoCambio' key not found in response: {data}") - return None - - except Exception as e: - print(f"Error fetching from External API: {e}") - return None diff --git a/backend/api/v1/modules/a76/general_catalogs/exchange_rate/test_exchange_rate.py b/backend/api/v1/modules/a76/general_catalogs/exchange_rate/test_exchange_rate.py deleted file mode 100644 index 3bf1cb12..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/exchange_rate/test_exchange_rate.py +++ /dev/null @@ -1,36 +0,0 @@ -import pytest -from fastapi import FastAPI -from fastapi.testclient import TestClient - -from .routes import router - -app = FastAPI() -app.include_router(router) -client = TestClient(app) - - -@pytest.mark.usefixtures("client", "access_token") -def test_list_exchange_rates(client, access_token): - headers = {"Authorization": f"Bearer {access_token}"} - response = client.get("/exchange-rate/", headers=headers) - assert response.status_code == 200 - assert "items" in response.json() - assert "page" in response.json() - assert "page_size" in response.json() - - -@pytest.mark.usefixtures("client", "access_token") -def test_get_exchange_rate_not_found(client, access_token): - headers = {"Authorization": f"Bearer {access_token}"} - response = client.get("/exchange-rate/invalid_id", headers=headers) - assert response.status_code == 404 - - -def test_create_exchange_rate_forbidden(): - response = client.post("/exchange-rate/", json={"rate": 1.23}) - assert response.status_code in (403, 405, 404) - - -def test_update_exchange_rate_forbidden(): - response = client.put("/exchange-rate/1", json={"rate": 1.45}) - assert response.status_code in (403, 405, 404) diff --git a/backend/api/v1/modules/a76/general_catalogs/fda_catalog/__init__.py b/backend/api/v1/modules/a76/general_catalogs/fda_catalog/__init__.py deleted file mode 100644 index b8dc52ba..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/fda_catalog/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -""" -__init__.py para módulo de catálogo FDA -""" diff --git a/backend/api/v1/modules/a76/general_catalogs/fda_catalog/dto.py b/backend/api/v1/modules/a76/general_catalogs/fda_catalog/dto.py deleted file mode 100644 index 0735b6e3..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/fda_catalog/dto.py +++ /dev/null @@ -1,172 +0,0 @@ -""" -DTOs para el catálogo de claves FDA -""" -from pydantic import BaseModel, Field -from typing import Optional - - -class FDACatalogCreate(BaseModel): - """DTO para crear una entrada en el catálogo FDA""" - fda_key: str = Field(..., max_length=20, description="Clave FDA") - description: str = Field(..., max_length=500, description="Descripción") - fda_code: Optional[str] = Field(None, max_length=50, description="Código FDA") - requirements: Optional[str] = Field(None, max_length=500, description="Requerimientos") - manufacturer_number: Optional[str] = Field(None, max_length=50, description="Número de fabricante") - country_of_production: Optional[str] = Field(None, max_length=100, description="País de producción") - storage_status: Optional[str] = Field(None, max_length=100, description="Estatus de almacenaje") - warehouse_code: Optional[str] = Field(None, max_length=20, description="Código de almacén") - call_atl: Optional[str] = Field(None, max_length=20, description="CallAtl") - - model_config = { - "from_attributes": True - } - - -class FDACatalogUpdate(BaseModel): - """DTO para actualizar una entrada en el catálogo FDA""" - fda_key: Optional[str] = Field(None, max_length=20, description="Clave FDA") - description: Optional[str] = Field(None, max_length=500, description="Descripción") - fda_code: Optional[str] = Field(None, max_length=50, description="Código FDA") - requirements: Optional[str] = Field(None, max_length=500, description="Requerimientos") - manufacturer_number: Optional[str] = Field(None, max_length=50, description="Número de fabricante") - country_of_production: Optional[str] = Field(None, max_length=100, description="País de producción") - storage_status: Optional[str] = Field(None, max_length=100, description="Estatus de almacenaje") - warehouse_code: Optional[str] = Field(None, max_length=20, description="Código de almacén") - call_atl: Optional[str] = Field(None, max_length=20, description="CallAtl") - - model_config = { - "from_attributes": True - } - - - -class FDACatalogResponse(BaseModel): - """DTO para respuesta del catálogo FDA""" - id: int - fda_key: str - description: str - fda_code: Optional[str] - requirements: Optional[str] - manufacturer_number: Optional[str] - country_of_production: Optional[str] - storage_status: Optional[str] - warehouse_code: Optional[str] - call_atl: Optional[str] - - model_config = { - "from_attributes": True - } - - -# DTOs para FDA01 - Especificaciones -class FDASpecificationsBase(BaseModel): - prod_code: Optional[str] = None - commodity_desc: Optional[str] = None - brand_name: Optional[str] = None - disclaimer: Optional[str] = None - pgm_code: Optional[str] = None - proc_code: Optional[str] = None - intnd_use_code: Optional[str] = None - intnd_use_desc: Optional[str] = None - temp_qual: Optional[str] = None - temp_type: Optional[str] = None - temp_degrees: Optional[float] = None - temp_negative: Optional[float] = None - temp_location: Optional[str] = None - quantity_1: Optional[float] = None - qty_uom_1: Optional[str] = None - quantity_2: Optional[float] = None - qty_uom_2: Optional[str] = None - quantity_3: Optional[float] = None - qty_uom_3: Optional[str] = None - ctry_prod: Optional[str] = None - ctry_source: Optional[str] = None - ctry_growth: Optional[str] = None - ctry_refusal: Optional[str] = None - ctry_shipping: Optional[str] = None - manuf_key: Optional[str] = None - shipper_key: Optional[str] = None - ult_cons_key: Optional[str] = None - fda_imp_key: Optional[str] = None - pn_subm_key: Optional[str] = None - consol_key: Optional[str] = None - producer_key: Optional[str] = None - owner_key: Optional[str] = None - deli_party_key: Optional[str] = None - grower_key: Optional[str] = None - dev_ini_imp_key: Optional[str] = None - lacf_cont_1: Optional[str] = None - lacf_cont_2: Optional[str] = None - lacf_cont_3: Optional[str] = None - pn_transmitter_key: Optional[str] = None - -class FDASpecificationsCreate(FDASpecificationsBase): - pass - -class FDASpecificationsUpdate(FDASpecificationsBase): - pass - -class FDASpecificationsResponse(FDASpecificationsBase): - id: int - fda_catalog_id: int - model_config = {"from_attributes": True} - - -# DTOs para FDA04 - Elementos Constitutivos -class FDAConstituentElementsBase(BaseModel): - line: int - ele_name: str - ele_qty: Optional[float] = None - ele_qty_uom: Optional[str] = None - ele_pctg: Optional[float] = None - -class FDAConstituentElementsCreate(FDAConstituentElementsBase): - pass - -class FDAConstituentElementsUpdate(FDAConstituentElementsBase): - line: Optional[int] = None - ele_name: Optional[str] = None - -class FDAConstituentElementsResponse(FDAConstituentElementsBase): - id: int - fda_catalog_id: int - model_config = {"from_attributes": True} - - -# DTOs para FDA23 - Códigos de Afirmación -class FDAAffirmationCodesBase(BaseModel): - line: int - aoc_code: str - aoc_qual: Optional[str] = None - -class FDAAffirmationCodesCreate(FDAAffirmationCodesBase): - pass - -class FDAAffirmationCodesUpdate(FDAAffirmationCodesBase): - line: Optional[int] = None - aoc_code: Optional[str] = None - -class FDAAffirmationCodesResponse(FDAAffirmationCodesBase): - id: int - fda_catalog_id: int - model_config = {"from_attributes": True} - - -# DTOs para FDA25 - Lotes de Producción -class FDALotProductionBase(BaseModel): - line: int - lot_number: str - production_start_date: Optional[str] = None - production_end_date: Optional[str] = None - -class FDALotProductionCreate(FDALotProductionBase): - pass - -class FDALotProductionUpdate(FDALotProductionBase): - line: Optional[int] = None - lot_number: Optional[str] = None - -class FDALotProductionResponse(FDALotProductionBase): - id: int - fda_catalog_id: int - model_config = {"from_attributes": True} diff --git a/backend/api/v1/modules/a76/general_catalogs/fda_catalog/models.py b/backend/api/v1/modules/a76/general_catalogs/fda_catalog/models.py deleted file mode 100644 index f408df88..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/fda_catalog/models.py +++ /dev/null @@ -1,150 +0,0 @@ -""" -Modelo para el catálogo de claves FDA -""" -from typing import Optional, List -from sqlalchemy import String, Integer, Numeric, Date, ForeignKey, UniqueConstraint, PrimaryKeyConstraint -from sqlalchemy.orm import Mapped, mapped_column, relationship -from core.database import Base -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin - - -class FDACatalog(Base, TenantScopedMixin, TimestampMixin): - """Catálogo de claves FDA""" - __tablename__ = "fda_catalog" - __table_args__ = ( - PrimaryKeyConstraint("id", name="fda_catalog_pkey"), - UniqueConstraint('tenant_id', 'company_id', 'fda_key', name='idx_fda_catalog_unique'), - {'schema': 'a76'} - ) - - id: Mapped[int] = mapped_column(Integer, primary_key=True) - fda_key: Mapped[str] = mapped_column(String(20), nullable=False, index=True) - description: Mapped[str] = mapped_column(String(500), nullable=False, index=True) - fda_code: Mapped[Optional[str]] = mapped_column(String(50)) - requirements: Mapped[Optional[str]] = mapped_column(String(500)) - manufacturer_number: Mapped[Optional[str]] = mapped_column(String(50)) - country_of_production: Mapped[Optional[str]] = mapped_column(String(100)) - storage_status: Mapped[Optional[str]] = mapped_column(String(100)) - warehouse_code: Mapped[Optional[str]] = mapped_column(String(20)) - call_atl: Mapped[Optional[str]] = mapped_column(String(20)) - - # Relationships - specifications: Mapped[Optional["FDASpecifications"]] = relationship("FDASpecifications", back_populates="fda_catalog", cascade="all, delete-orphan", uselist=False) - constituent_elements: Mapped[List["FDAConstituentElements"]] = relationship("FDAConstituentElements", back_populates="fda_catalog", cascade="all, delete-orphan") - affirmation_codes: Mapped[List["FDAAffirmationCodes"]] = relationship("FDAAffirmationCodes", back_populates="fda_catalog", cascade="all, delete-orphan") - lot_productions: Mapped[List["FDALotProduction"]] = relationship("FDALotProduction", back_populates="fda_catalog", cascade="all, delete-orphan") - - -class FDASpecifications(Base, TenantScopedMixin, TimestampMixin): - """FDA01 - Especificaciones del Producto FDA""" - __tablename__ = "fda_specifications" - __table_args__ = ( - PrimaryKeyConstraint("id", name="fda_specifications_pkey"), - {'schema': 'a76'} - ) - - id: Mapped[int] = mapped_column(Integer, primary_key=True) - fda_catalog_id: Mapped[int] = mapped_column(ForeignKey("a76.fda_catalog.id", ondelete="CASCADE"), nullable=False, index=True) - - prod_code: Mapped[Optional[str]] = mapped_column(String(50)) - commodity_desc: Mapped[Optional[str]] = mapped_column(String(200)) - brand_name: Mapped[Optional[str]] = mapped_column(String(100)) - disclaimer: Mapped[Optional[str]] = mapped_column(String(100)) - pgm_code: Mapped[Optional[str]] = mapped_column(String(50)) - proc_code: Mapped[Optional[str]] = mapped_column(String(50)) - intnd_use_code: Mapped[Optional[str]] = mapped_column(String(50)) - intnd_use_desc: Mapped[Optional[str]] = mapped_column(String(200)) - temp_qual: Mapped[Optional[str]] = mapped_column(String(50)) - temp_type: Mapped[Optional[str]] = mapped_column(String(50)) - temp_degrees: Mapped[Optional[float]] = mapped_column(Numeric(10, 2)) - temp_negative: Mapped[Optional[float]] = mapped_column(Numeric(10, 2)) - temp_location: Mapped[Optional[str]] = mapped_column(String(100)) - - quantity_1: Mapped[Optional[float]] = mapped_column(Numeric(15, 2)) - qty_uom_1: Mapped[Optional[str]] = mapped_column(String(20)) - quantity_2: Mapped[Optional[float]] = mapped_column(Numeric(15, 2)) - qty_uom_2: Mapped[Optional[str]] = mapped_column(String(20)) - quantity_3: Mapped[Optional[float]] = mapped_column(Numeric(15, 2)) - qty_uom_3: Mapped[Optional[str]] = mapped_column(String(20)) - - ctry_prod: Mapped[Optional[str]] = mapped_column(String(50)) - ctry_source: Mapped[Optional[str]] = mapped_column(String(50)) - ctry_growth: Mapped[Optional[str]] = mapped_column(String(50)) - ctry_refusal: Mapped[Optional[str]] = mapped_column(String(50)) - ctry_shipping: Mapped[Optional[str]] = mapped_column(String(50)) - - manuf_key: Mapped[Optional[str]] = mapped_column(String(50)) - shipper_key: Mapped[Optional[str]] = mapped_column(String(50)) - ult_cons_key: Mapped[Optional[str]] = mapped_column(String(50)) - fda_imp_key: Mapped[Optional[str]] = mapped_column(String(50)) - pn_subm_key: Mapped[Optional[str]] = mapped_column(String(50)) - consol_key: Mapped[Optional[str]] = mapped_column(String(50)) - producer_key: Mapped[Optional[str]] = mapped_column(String(50)) - owner_key: Mapped[Optional[str]] = mapped_column(String(50)) - deli_party_key: Mapped[Optional[str]] = mapped_column(String(50)) - grower_key: Mapped[Optional[str]] = mapped_column(String(50)) - dev_ini_imp_key: Mapped[Optional[str]] = mapped_column(String(50)) - - lacf_cont_1: Mapped[Optional[str]] = mapped_column(String(100)) - lacf_cont_2: Mapped[Optional[str]] = mapped_column(String(100)) - lacf_cont_3: Mapped[Optional[str]] = mapped_column(String(100)) - pn_transmitter_key: Mapped[Optional[str]] = mapped_column(String(50)) - - fda_catalog: Mapped["FDACatalog"] = relationship("FDACatalog", back_populates="specifications") - - -class FDAConstituentElements(Base, TenantScopedMixin, TimestampMixin): - """FDA04 - Constituent Elements""" - __tablename__ = "fda_constituent_elements" - __table_args__ = ( - PrimaryKeyConstraint("id", name="fda_constituent_elements_pkey"), - {'schema': 'a76'} - ) - - id: Mapped[int] = mapped_column(Integer, primary_key=True) - fda_catalog_id: Mapped[int] = mapped_column(ForeignKey("a76.fda_catalog.id", ondelete="CASCADE"), nullable=False, index=True) - - line: Mapped[int] = mapped_column(Integer, nullable=False, default=1) - ele_name: Mapped[str] = mapped_column(String(200), nullable=False) - ele_qty: Mapped[Optional[float]] = mapped_column(Numeric(15, 2)) - ele_qty_uom: Mapped[Optional[str]] = mapped_column(String(20)) - ele_pctg: Mapped[Optional[float]] = mapped_column(Numeric(15, 2)) - - fda_catalog: Mapped["FDACatalog"] = relationship("FDACatalog", back_populates="constituent_elements") - - -class FDAAffirmationCodes(Base, TenantScopedMixin, TimestampMixin): - """FDA23 - Affirmation of Compliance""" - __tablename__ = "fda_affirmation_codes" - __table_args__ = ( - PrimaryKeyConstraint("id", name="fda_affirmation_codes_pkey"), - {'schema': 'a76'} - ) - - id: Mapped[int] = mapped_column(Integer, primary_key=True) - fda_catalog_id: Mapped[int] = mapped_column(ForeignKey("a76.fda_catalog.id", ondelete="CASCADE"), nullable=False, index=True) - - line: Mapped[int] = mapped_column(Integer, nullable=False, default=1) - aoc_code: Mapped[str] = mapped_column(String(50), nullable=False) - aoc_qual: Mapped[Optional[str]] = mapped_column(String(100)) - - fda_catalog: Mapped["FDACatalog"] = relationship("FDACatalog", back_populates="affirmation_codes") - - -class FDALotProduction(Base, TenantScopedMixin, TimestampMixin): - """FDA25 - Lot and Production Dates""" - __tablename__ = "fda_lot_production" - __table_args__ = ( - PrimaryKeyConstraint("id", name="fda_lot_production_pkey"), - {'schema': 'a76'} - ) - - id: Mapped[int] = mapped_column(Integer, primary_key=True) - fda_catalog_id: Mapped[int] = mapped_column(ForeignKey("a76.fda_catalog.id", ondelete="CASCADE"), nullable=False, index=True) - - line: Mapped[int] = mapped_column(Integer, nullable=False, default=1) - lot_number: Mapped[str] = mapped_column(String(100), nullable=False) - production_start_date: Mapped[Optional[str]] = mapped_column(String(50)) - production_end_date: Mapped[Optional[str]] = mapped_column(String(50)) - - fda_catalog: Mapped["FDACatalog"] = relationship("FDACatalog", back_populates="lot_productions") diff --git a/backend/api/v1/modules/a76/general_catalogs/fda_catalog/routes.py b/backend/api/v1/modules/a76/general_catalogs/fda_catalog/routes.py deleted file mode 100644 index 41005234..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/fda_catalog/routes.py +++ /dev/null @@ -1,278 +0,0 @@ -""" -Rutas para el catálogo de claves FDA -""" -from fastapi import APIRouter, Depends, HTTPException, status, Query -from sqlalchemy.orm import Session -from typing import Any, Dict, Optional -from api.v1.modules.a76.general_catalogs.fda_catalog.service import FDACatalogService -from api.v1.modules.a76.general_catalogs.fda_catalog.dto import FDACatalogResponse -from core.database import get_core_db -from core.security import get_current_user -from api.v1.common.tenant_crud_routes import validate_access_to_resource - - -router = APIRouter( - prefix="/fda-catalog", - tags=["a76 / general catalogs / fda catalog"] -) - - -@router.get("/") -async def list_fda_catalog( - company_id: int = Query(..., description="Company ID"), - page: int = Query(1, ge=1, description="Page number"), - page_size: int = Query(50, ge=1, le=1000, description="Page size"), - search: Optional[str] = Query(None, description="Search in FDA key, description or code"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user) -): - """Listar entradas del catálogo FDA con búsqueda y paginación""" - # 👇 PERMISO DE LECTURA 👇 - tenant_id = validate_access_to_resource(db, company_id, current_user, required_permissions=["goods_fda.view"]) - - filters = {"search": search} if search else {} - result = FDACatalogService.get_all(db, tenant_id, company_id, page, page_size, filters) - - return { - "items": result["items"], - "total": result["total"], - "page": result["page"], - "page_size": result["page_size"], - "pages": result["pages"] - } - - -@router.get("/{id}") -async def get_fda_catalog( - id: int, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user) -): - """Obtener una entrada del catálogo FDA por ID""" - # 👇 PERMISO DE LECTURA 👇 - tenant_id = validate_access_to_resource(db, company_id, current_user, required_permissions=["goods_fda.view"]) - - entry = FDACatalogService.get_by_id(db, tenant_id, company_id, id) - if not entry: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Entrada no encontrada") - - return { - "id": entry.id, - "fda_key": entry.fda_key, - "description": entry.description, - "fda_code": entry.fda_code, - "requirements": entry.requirements, - "manufacturer_number": entry.manufacturer_number, - "country_of_production": entry.country_of_production, - "storage_status": entry.storage_status, - "warehouse_code": entry.warehouse_code, - "call_atl": entry.call_atl - } - - -from api.v1.modules.a76.general_catalogs.fda_catalog.dto import FDACatalogCreate, FDACatalogUpdate -from sqlalchemy.exc import IntegrityError - -@router.post("/", response_model=FDACatalogResponse, status_code=status.HTTP_201_CREATED) -async def create_fda_catalog( - data: FDACatalogCreate, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user) -): - """Crear nueva entrada en el catálogo FDA""" - # 👇 PERMISO DE CREACIÓN 👇 - tenant_id = validate_access_to_resource(db, company_id, current_user, required_permissions=["goods_fda.create"]) - try: - return FDACatalogService.create(db, tenant_id, company_id, data) - except IntegrityError: - db.rollback() - raise HTTPException(status_code=400, detail="La Clave FDA ya existe o hay un conflicto de datos.") - - -@router.put("/{id}", response_model=FDACatalogResponse) -async def update_fda_catalog( - id: int, - data: FDACatalogUpdate, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user) -): - """Actualizar entrada del catálogo FDA""" - # 👇 PERMISO DE EDICIÓN 👇 - tenant_id = validate_access_to_resource(db, company_id, current_user, required_permissions=["goods_fda.edit"]) - - try: - entry = FDACatalogService.update(db, tenant_id, company_id, id, data) - if not entry: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Entrada no encontrada") - return entry - except IntegrityError: - db.rollback() - raise HTTPException(status_code=400, detail="Error de integridad de datos al actualizar la Clave FDA.") - - -@router.delete("/{id}") -async def delete_fda_catalog( - id: int, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user) -): - """Eliminar entrada del catálogo FDA""" - # 👇 PERMISO DE ELIMINACIÓN 👇 - tenant_id = validate_access_to_resource(db, company_id, current_user, required_permissions=["goods_fda.delete"]) - success = FDACatalogService.delete(db, tenant_id, company_id, id) - if not success: - raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Entrada no encontrada") - return {"message": "Eliminado correctamente"} - - -from typing import List -from api.v1.modules.a76.general_catalogs.fda_catalog.service import FDADetailsService -from api.v1.modules.a76.general_catalogs.fda_catalog.dto import ( - FDASpecificationsCreate, FDASpecificationsResponse, - FDAConstituentElementsCreate, FDAConstituentElementsUpdate, FDAConstituentElementsResponse, - FDAAffirmationCodesCreate, FDAAffirmationCodesUpdate, FDAAffirmationCodesResponse, - FDALotProductionCreate, FDALotProductionUpdate, FDALotProductionResponse -) - -# === FDA01: Especificaciones del Producto === -@router.get("/{id}/specifications", response_model=Optional[FDASpecificationsResponse]) -async def get_fda_specifications( - id: int, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user) -): - tenant_id = validate_access_to_resource(db, company_id, current_user, required_permissions=["goods_fda.view"]) - catalog_entry = FDACatalogService.get_by_id(db, tenant_id, company_id, id) - if not catalog_entry: - raise HTTPException(status_code=404, detail="Catálogo FDA no encontrado") - - return FDADetailsService.get_specifications(db, tenant_id, company_id, id) - -@router.put("/{id}/specifications", response_model=FDASpecificationsResponse) -async def save_fda_specifications( - id: int, - data: FDASpecificationsCreate, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user) -): - # Aquí usamos .edit porque estamos modificando un catálogo existente - tenant_id = validate_access_to_resource(db, company_id, current_user, required_permissions=["goods_fda.edit"]) - catalog_entry = FDACatalogService.get_by_id(db, tenant_id, company_id, id) - if not catalog_entry: - raise HTTPException(status_code=404, detail="Catálogo FDA no encontrado") - - return FDADetailsService.save_specifications(db, tenant_id, company_id, id, data.model_dump(exclude_unset=True)) - -# === FDA04: Elementos Constitutivos === -@router.get("/{id}/constituent-elements", response_model=List[FDAConstituentElementsResponse]) -async def list_fda_constituent_elements( - id: int, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user) -): - tenant_id = validate_access_to_resource(db, company_id, current_user, required_permissions=["goods_fda.view"]) - return FDADetailsService.get_constituent_elements(db, tenant_id, company_id, id) - -@router.post("/{id}/constituent-elements", response_model=FDAConstituentElementsResponse) -async def create_fda_constituent_element( - id: int, - data: FDAConstituentElementsCreate, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user) -): - tenant_id = validate_access_to_resource(db, company_id, current_user, required_permissions=["goods_fda.create"]) - return FDADetailsService.create_constituent_element(db, tenant_id, company_id, id, data.model_dump()) - -@router.delete("/{id}/constituent-elements/{element_id}") -async def delete_fda_constituent_element( - id: int, - element_id: int, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user) -): - tenant_id = validate_access_to_resource(db, company_id, current_user, required_permissions=["goods_fda.delete"]) - success = FDADetailsService.delete_constituent_element(db, tenant_id, company_id, element_id) - if not success: - raise HTTPException(status_code=404, detail="Elemento no encontrado") - return {"message": "Eliminado correctamente"} - -# === FDA23: Códigos de Afirmación === -@router.get("/{id}/affirmation-codes", response_model=List[FDAAffirmationCodesResponse]) -async def list_fda_affirmation_codes( - id: int, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user) -): - tenant_id = validate_access_to_resource(db, company_id, current_user, required_permissions=["goods_fda.view"]) - return FDADetailsService.get_affirmation_codes(db, tenant_id, company_id, id) - -@router.post("/{id}/affirmation-codes", response_model=FDAAffirmationCodesResponse) -async def create_fda_affirmation_code( - id: int, - data: FDAAffirmationCodesCreate, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user) -): - tenant_id = validate_access_to_resource(db, company_id, current_user, required_permissions=["goods_fda.create"]) - return FDADetailsService.create_affirmation_code(db, tenant_id, company_id, id, data.model_dump()) - -@router.delete("/{id}/affirmation-codes/{code_id}") -async def delete_fda_affirmation_code( - id: int, - code_id: int, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user) -): - tenant_id = validate_access_to_resource(db, company_id, current_user, required_permissions=["goods_fda.delete"]) - success = FDADetailsService.delete_affirmation_code(db, tenant_id, company_id, code_id) - if not success: - raise HTTPException(status_code=404, detail="Código no encontrado") - return {"message": "Eliminado correctamente"} - -# === FDA25: Lotes de Producción === -@router.get("/{id}/lot-productions", response_model=List[FDALotProductionResponse]) -async def list_fda_lot_productions( - id: int, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user) -): - tenant_id = validate_access_to_resource(db, company_id, current_user, required_permissions=["goods_fda.view"]) - return FDADetailsService.get_lot_productions(db, tenant_id, company_id, id) - -@router.post("/{id}/lot-productions", response_model=FDALotProductionResponse) -async def create_fda_lot_production( - id: int, - data: FDALotProductionCreate, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user) -): - tenant_id = validate_access_to_resource(db, company_id, current_user, required_permissions=["goods_fda.create"]) - return FDADetailsService.create_lot_production(db, tenant_id, company_id, id, data.model_dump()) - -@router.delete("/{id}/lot-productions/{lot_id}") -async def delete_fda_lot_production( - id: int, - lot_id: int, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user) -): - tenant_id = validate_access_to_resource(db, company_id, current_user, required_permissions=["goods_fda.delete"]) - success = FDADetailsService.delete_lot_production(db, tenant_id, company_id, lot_id) - if not success: - raise HTTPException(status_code=404, detail="Lote no encontrado") - return {"message": "Eliminado correctamente"} \ No newline at end of file diff --git a/backend/api/v1/modules/a76/general_catalogs/fda_catalog/seed.py b/backend/api/v1/modules/a76/general_catalogs/fda_catalog/seed.py deleted file mode 100644 index 7bd31be8..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/fda_catalog/seed.py +++ /dev/null @@ -1,15 +0,0 @@ -""" -Datos semilla para Catálogo FDA - -Catálogo de clasificación de la Food and Drug Administration (FDA) -para importaciones/exportaciones de productos regulados. -""" - -seed = [ - # (fda_key, description, fda_code, requirements, manufacturer_number, country_of_production) - ("3012", "Dispositivos médicos clase I", "MED-I", "Registro FDA", "MFR001", "US"), - ("3013", "Dispositivos médicos clase II", "MED-II", "Registro FDA + 510(k)", "MFR002", "US"), - ("3014", "Suplementos alimenticios", "SUP-01", "Registro Establecimiento", "MFR003", "MX"), - ("3015", "Medicamentos de venta libre", "OTC-01", "Monografía FDA", "MFR004", "US"), - ("3016", "Cosméticos", "COS-01", "Registro Voluntario", "MFR005", "MX"), -] diff --git a/backend/api/v1/modules/a76/general_catalogs/fda_catalog/service.py b/backend/api/v1/modules/a76/general_catalogs/fda_catalog/service.py deleted file mode 100644 index 15921208..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/fda_catalog/service.py +++ /dev/null @@ -1,267 +0,0 @@ -""" -Servicio para el catálogo de claves FDA -""" -from sqlalchemy import select -from sqlalchemy.orm import Session -from api.v1.modules.a76.general_catalogs.fda_catalog.models import FDACatalog -from api.v1.modules.a76.general_catalogs.fda_catalog.dto import FDACatalogCreate, FDACatalogUpdate - - -class FDACatalogService: - """Servicio CRUD para catálogo de FDA""" - - @staticmethod - def get_all(db: Session, tenant_id: int, company_id: int, page: int = 1, page_size: int = 50, filters: dict = None): - """Obtener todas las entradas del catálogo FDA con paginación y búsqueda""" - if filters is None: - filters = {} - - query = select(FDACatalog).where( - (FDACatalog.tenant_id == tenant_id) & - (FDACatalog.company_id == company_id) - ) - - # Búsqueda multi-campo - search = filters.get('search', '').strip() - if search: - search_pattern = f"%{search}%" - query = query.where( - (FDACatalog.fda_key.ilike(search_pattern)) | - (FDACatalog.description.ilike(search_pattern)) | - (FDACatalog.fda_code.ilike(search_pattern)) - ) - - # Contar total - total_query = select(FDACatalog).where( - (FDACatalog.tenant_id == tenant_id) & - (FDACatalog.company_id == company_id) - ) - if search: - total_query = total_query.where( - (FDACatalog.fda_key.ilike(search_pattern)) | - (FDACatalog.description.ilike(search_pattern)) | - (FDACatalog.fda_code.ilike(search_pattern)) - ) - total = db.execute(select(FDACatalog).distinct()).scalars().all().__len__() - - # Paginación - offset = (page - 1) * page_size - query = query.offset(offset).limit(page_size) - - items = db.execute(query).scalars().all() - - return { - "items": items, - "total": len(items), - "page": page, - "page_size": page_size, - "pages": (len(items) + page_size - 1) // page_size - } - - @staticmethod - def get_by_id(db: Session, tenant_id: int, company_id: int, id: int): - """Obtener una entrada por ID""" - return db.execute( - select(FDACatalog).where( - (FDACatalog.id == id) & - (FDACatalog.tenant_id == tenant_id) & - (FDACatalog.company_id == company_id) - ) - ).scalar_one_or_none() - - @staticmethod - def create(db: Session, tenant_id: int, company_id: int, data: FDACatalogCreate): - """Crear una nueva entrada""" - entry = FDACatalog( - tenant_id=tenant_id, - company_id=company_id, - **data.model_dump() - ) - db.add(entry) - db.commit() - db.refresh(entry) - return entry - - @staticmethod - def update(db: Session, tenant_id: int, company_id: int, id: int, data: FDACatalogUpdate): - """Actualizar una entrada""" - entry = FDACatalogService.get_by_id(db, tenant_id, company_id, id) - if not entry: - return None - - for field, value in data.model_dump(exclude_unset=True).items(): - setattr(entry, field, value) - - db.commit() - db.refresh(entry) - return entry - - @staticmethod - def delete(db: Session, tenant_id: int, company_id: int, id: int): - """Eliminar una entrada""" - entry = FDACatalogService.get_by_id(db, tenant_id, company_id, id) - if not entry: - return False - - db.delete(entry) - db.commit() - return True - - -class FDADetailsService: - """Servicio CRUD para los detalles adicionales de FDA (Especificaciones, Lotes, etc)""" - - # FDA01 - Especificaciones - @staticmethod - def get_specifications(db: Session, tenant_id: int, company_id: int, fda_catalog_id: int): - from api.v1.modules.a76.general_catalogs.fda_catalog.models import FDASpecifications - return db.execute( - select(FDASpecifications).where( - (FDASpecifications.fda_catalog_id == fda_catalog_id) & - (FDASpecifications.tenant_id == tenant_id) & - (FDASpecifications.company_id == company_id) - ) - ).scalar_one_or_none() - - @staticmethod - def save_specifications(db: Session, tenant_id: int, company_id: int, fda_catalog_id: int, data: dict): - from api.v1.modules.a76.general_catalogs.fda_catalog.models import FDASpecifications - existing = FDADetailsService.get_specifications(db, tenant_id, company_id, fda_catalog_id) - if existing: - for field, value in data.items(): - setattr(existing, field, value) - entry = existing - else: - entry = FDASpecifications( - tenant_id=tenant_id, - company_id=company_id, - fda_catalog_id=fda_catalog_id, - **data - ) - db.add(entry) - db.commit() - db.refresh(entry) - return entry - - # FDA04 - Elementos Constitutivos - @staticmethod - def get_constituent_elements(db: Session, tenant_id: int, company_id: int, fda_catalog_id: int): - from api.v1.modules.a76.general_catalogs.fda_catalog.models import FDAConstituentElements - items = db.execute( - select(FDAConstituentElements).where( - (FDAConstituentElements.fda_catalog_id == fda_catalog_id) & - (FDAConstituentElements.tenant_id == tenant_id) & - (FDAConstituentElements.company_id == company_id) - ).order_by(FDAConstituentElements.line.asc()) - ).scalars().all() - return items - - @staticmethod - def create_constituent_element(db: Session, tenant_id: int, company_id: int, fda_catalog_id: int, data: dict): - from api.v1.modules.a76.general_catalogs.fda_catalog.models import FDAConstituentElements - entry = FDAConstituentElements( - tenant_id=tenant_id, - company_id=company_id, - fda_catalog_id=fda_catalog_id, - **data - ) - db.add(entry) - db.commit() - db.refresh(entry) - return entry - - @staticmethod - def delete_constituent_element(db: Session, tenant_id: int, company_id: int, element_id: int): - from api.v1.modules.a76.general_catalogs.fda_catalog.models import FDAConstituentElements - entry = db.execute(select(FDAConstituentElements).where( - (FDAConstituentElements.id == element_id) & - (FDAConstituentElements.tenant_id == tenant_id) & - (FDAConstituentElements.company_id == company_id) - )).scalar_one_or_none() - if entry: - db.delete(entry) - db.commit() - return True - return False - - # FDA23 - Códigos de Afirmación - @staticmethod - def get_affirmation_codes(db: Session, tenant_id: int, company_id: int, fda_catalog_id: int): - from api.v1.modules.a76.general_catalogs.fda_catalog.models import FDAAffirmationCodes - items = db.execute( - select(FDAAffirmationCodes).where( - (FDAAffirmationCodes.fda_catalog_id == fda_catalog_id) & - (FDAAffirmationCodes.tenant_id == tenant_id) & - (FDAAffirmationCodes.company_id == company_id) - ).order_by(FDAAffirmationCodes.line.asc()) - ).scalars().all() - return items - - @staticmethod - def create_affirmation_code(db: Session, tenant_id: int, company_id: int, fda_catalog_id: int, data: dict): - from api.v1.modules.a76.general_catalogs.fda_catalog.models import FDAAffirmationCodes - entry = FDAAffirmationCodes( - tenant_id=tenant_id, - company_id=company_id, - fda_catalog_id=fda_catalog_id, - **data - ) - db.add(entry) - db.commit() - db.refresh(entry) - return entry - - @staticmethod - def delete_affirmation_code(db: Session, tenant_id: int, company_id: int, code_id: int): - from api.v1.modules.a76.general_catalogs.fda_catalog.models import FDAAffirmationCodes - entry = db.execute(select(FDAAffirmationCodes).where( - (FDAAffirmationCodes.id == code_id) & - (FDAAffirmationCodes.tenant_id == tenant_id) & - (FDAAffirmationCodes.company_id == company_id) - )).scalar_one_or_none() - if entry: - db.delete(entry) - db.commit() - return True - return False - - # FDA25 - Lotes de Producción - @staticmethod - def get_lot_productions(db: Session, tenant_id: int, company_id: int, fda_catalog_id: int): - from api.v1.modules.a76.general_catalogs.fda_catalog.models import FDALotProduction - items = db.execute( - select(FDALotProduction).where( - (FDALotProduction.fda_catalog_id == fda_catalog_id) & - (FDALotProduction.tenant_id == tenant_id) & - (FDALotProduction.company_id == company_id) - ).order_by(FDALotProduction.line.asc()) - ).scalars().all() - return items - - @staticmethod - def create_lot_production(db: Session, tenant_id: int, company_id: int, fda_catalog_id: int, data: dict): - from api.v1.modules.a76.general_catalogs.fda_catalog.models import FDALotProduction - entry = FDALotProduction( - tenant_id=tenant_id, - company_id=company_id, - fda_catalog_id=fda_catalog_id, - **data - ) - db.add(entry) - db.commit() - db.refresh(entry) - return entry - - @staticmethod - def delete_lot_production(db: Session, tenant_id: int, company_id: int, lot_id: int): - from api.v1.modules.a76.general_catalogs.fda_catalog.models import FDALotProduction - entry = db.execute(select(FDALotProduction).where( - (FDALotProduction.id == lot_id) & - (FDALotProduction.tenant_id == tenant_id) & - (FDALotProduction.company_id == company_id) - )).scalar_one_or_none() - if entry: - db.delete(entry) - db.commit() - return True - return False diff --git a/backend/api/v1/modules/a76/general_catalogs/fractions/canadian_tariff_fractions/__init__.py b/backend/api/v1/modules/a76/general_catalogs/fractions/canadian_tariff_fractions/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/backend/api/v1/modules/a76/general_catalogs/fractions/canadian_tariff_fractions/models.py b/backend/api/v1/modules/a76/general_catalogs/fractions/canadian_tariff_fractions/models.py deleted file mode 100644 index f67d68bd..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/fractions/canadian_tariff_fractions/models.py +++ /dev/null @@ -1,29 +0,0 @@ - -from datetime import datetime -from typing import Optional -from sqlalchemy import String, Numeric, TIMESTAMP, func, UniqueConstraint -from sqlalchemy.orm import Mapped, mapped_column -from core.database import Base - -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin - -class CanadianTariffFraction(Base, TenantScopedMixin, TimestampMixin): - """Model for Canadian Tariff Fractions (GFracEUACan)""" - __tablename__ = "canadian_tariff_fractions" - __table_args__ = ( - UniqueConstraint('fraction', 'country_code', 'company_id', name='uq_canadian_fraction_country_company'), - {"schema": "a76"} - ) - - id: Mapped[int] = mapped_column(primary_key=True, index=True) - - # FRACCION - fraction: Mapped[str] = mapped_column(String(13), nullable=False, index=True) - # ADV - ad_valorem: Mapped[Optional[float]] = mapped_column(Numeric(5, 2)) - # UNIDAD - unit_of_measure: Mapped[Optional[str]] = mapped_column(String(5)) - # CLAVEM3 (Part of original PK) - country_code: Mapped[str] = mapped_column(String(3), nullable=False, index=True) - # DESCRIPCION - description: Mapped[Optional[str]] = mapped_column(String(1000)) diff --git a/backend/api/v1/modules/a76/general_catalogs/fractions/canadian_tariff_fractions/routes.py b/backend/api/v1/modules/a76/general_catalogs/fractions/canadian_tariff_fractions/routes.py deleted file mode 100644 index 757e0bec..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/fractions/canadian_tariff_fractions/routes.py +++ /dev/null @@ -1,108 +0,0 @@ - -from typing import Optional -from fastapi import APIRouter, Depends, HTTPException, Query -from sqlalchemy.orm import Session -from core.database import get_core_db -from core.security import get_current_user -from .service import CanadianTariffFractionService -from .schemas import ( - CanadianTariffFractionResponse, - CanadianTariffFractionCreate, - CanadianTariffFractionUpdate, - CanadianTariffFractionListResponse -) - -from api.v1.common.tenant_crud_routes import validate_access_to_resource - -router = APIRouter() - -@router.get("/", response_model=CanadianTariffFractionListResponse) -def list_canadian_fractions( - company_id: int = Query(..., description="Company ID"), - page: int = Query(1, ge=1), - page_size: int = Query(50, ge=1, le=1000), - search: Optional[str] = None, - db: Session = Depends(get_core_db), - current_user = Depends(get_current_user) -): - tenant_id = validate_access_to_resource( - db, company_id, current_user, ["frac_canadian.view"] - ) - skip = (page - 1) * page_size - service = CanadianTariffFractionService(db) - items, total = service.get_multi( - tenant_id=tenant_id, - company_id=company_id, - skip=skip, - limit=page_size, - search=search - ) - return { - "items": items, - "total": total, - "page": page, - "page_size": page_size, - "pages": (total + page_size - 1) // page_size if page_size > 0 else 1 - } - -@router.get("/{id}", response_model=CanadianTariffFractionResponse) -def get_canadian_fraction( - id: int, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user = Depends(get_current_user) -): - tenant_id = validate_access_to_resource( - db, company_id, current_user, ["frac_canadian.view"] - ) - service = CanadianTariffFractionService(db) - item = service.get(id, tenant_id, company_id) - if not item: - raise HTTPException(status_code=404, detail="Not found") - return item - -@router.post("/", response_model=CanadianTariffFractionResponse) -def create_canadian_fraction( - item_in: CanadianTariffFractionCreate, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user = Depends(get_current_user) -): - tenant_id = validate_access_to_resource( - db, company_id, current_user, ["frac_canadian.create"] - ) - service = CanadianTariffFractionService(db) - return service.create(item_in, tenant_id, company_id) - -@router.put("/{id}", response_model=CanadianTariffFractionResponse) -def update_canadian_fraction( - id: int, - item_in: CanadianTariffFractionUpdate, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user = Depends(get_current_user) -): - tenant_id = validate_access_to_resource( - db, company_id, current_user, ["frac_canadian.edit"] - ) - service = CanadianTariffFractionService(db) - item = service.get(id, tenant_id, company_id) - if not item: - raise HTTPException(status_code=404, detail="Not found") - return service.update(item, item_in) - -@router.delete("/{id}", response_model=CanadianTariffFractionResponse) -def delete_canadian_fraction( - id: int, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user = Depends(get_current_user) -): - tenant_id = validate_access_to_resource( - db, company_id, current_user, ["frac_canadian.delete"] - ) - service = CanadianTariffFractionService(db) - item = service.get(id, tenant_id, company_id) - if not item: - raise HTTPException(status_code=404, detail="Not found") - return service.delete(id, tenant_id, company_id) diff --git a/backend/api/v1/modules/a76/general_catalogs/fractions/canadian_tariff_fractions/schemas.py b/backend/api/v1/modules/a76/general_catalogs/fractions/canadian_tariff_fractions/schemas.py deleted file mode 100644 index 4748e4ca..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/fractions/canadian_tariff_fractions/schemas.py +++ /dev/null @@ -1,32 +0,0 @@ - -from typing import Optional, List -from pydantic import BaseModel, Field, ConfigDict -from datetime import datetime -from decimal import Decimal - -class CanadianTariffFractionBase(BaseModel): - fraction: str = Field(..., max_length=13) - ad_valorem: Optional[Decimal] = Field(None, max_digits=5, decimal_places=2) - unit_of_measure: Optional[str] = Field(None, max_length=5) - country_code: str = Field(..., max_length=3) - description: Optional[str] = Field(None, max_length=1000) - -class CanadianTariffFractionCreate(CanadianTariffFractionBase): - pass - -class CanadianTariffFractionUpdate(CanadianTariffFractionBase): - pass - -class CanadianTariffFractionResponse(CanadianTariffFractionBase): - id: int - created_at: datetime - updated_at: datetime - - model_config = ConfigDict(from_attributes=True) - -class CanadianTariffFractionListResponse(BaseModel): - items: List[CanadianTariffFractionResponse] - total: int - page: int - page_size: int - pages: int diff --git a/backend/api/v1/modules/a76/general_catalogs/fractions/canadian_tariff_fractions/service.py b/backend/api/v1/modules/a76/general_catalogs/fractions/canadian_tariff_fractions/service.py deleted file mode 100644 index 8fec1468..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/fractions/canadian_tariff_fractions/service.py +++ /dev/null @@ -1,73 +0,0 @@ - -from typing import Optional, List, Tuple -from sqlalchemy import select, func -from sqlalchemy.orm import Session -from .models import CanadianTariffFraction -from .schemas import CanadianTariffFractionCreate, CanadianTariffFractionUpdate - -class CanadianTariffFractionService: - def __init__(self, db: Session): - self.db = db - - def get(self, id: int, tenant_id: int, company_id: int) -> Optional[CanadianTariffFraction]: - return self.db.query(CanadianTariffFraction).filter( - CanadianTariffFraction.id == id, - CanadianTariffFraction.tenant_id == tenant_id, - CanadianTariffFraction.company_id == company_id - ).first() - - def get_multi( - self, - tenant_id: int, - company_id: int, - skip: int = 0, - limit: int = 100, - search: Optional[str] = None - ) -> Tuple[List[CanadianTariffFraction], int]: - query = select(CanadianTariffFraction).where( - CanadianTariffFraction.tenant_id == tenant_id, - CanadianTariffFraction.company_id == company_id - ) - - if search: - query = query.where( - (CanadianTariffFraction.fraction.ilike(f"%{search}%")) | - (CanadianTariffFraction.description.ilike(f"%{search}%")) - ) - - total = self.db.execute(select(func.count()).select_from(query.subquery())).scalar_one() - # Add deterministic sort order - query = query.order_by(CanadianTariffFraction.fraction) - items = self.db.scalars(query.offset(skip).limit(limit)).all() - return items, total - - def create(self, obj_in: CanadianTariffFractionCreate, tenant_id: int, company_id: int) -> CanadianTariffFraction: - db_obj = CanadianTariffFraction( - **obj_in.model_dump(), - tenant_id=tenant_id, - company_id=company_id - ) - self.db.add(db_obj) - self.db.commit() - self.db.refresh(db_obj) - return db_obj - - def update( - self, - db_obj: CanadianTariffFraction, - obj_in: CanadianTariffFractionUpdate - ) -> CanadianTariffFraction: - update_data = obj_in.model_dump(exclude_unset=True) - for field, value in update_data.items(): - setattr(db_obj, field, value) - self.db.add(db_obj) - self.db.commit() - self.db.refresh(db_obj) - return db_obj - - def delete(self, id: int, tenant_id: int, company_id: int) -> Optional[CanadianTariffFraction]: - obj = self.get(id, tenant_id, company_id) - if obj: - self.db.delete(obj) - self.db.commit() - return obj diff --git a/backend/api/v1/modules/a76/general_catalogs/fractions/historical_tariff_fractions/dto.py b/backend/api/v1/modules/a76/general_catalogs/fractions/historical_tariff_fractions/dto.py deleted file mode 100644 index 3bf70308..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/fractions/historical_tariff_fractions/dto.py +++ /dev/null @@ -1,29 +0,0 @@ -""" -DTOs for historical tariff fractions. -""" - -from datetime import datetime -from decimal import Decimal -from typing import Optional - -from pydantic import BaseModel, ConfigDict - - -class HistoricalTariffFractionResponseDTO(BaseModel): - id: int - historical_fraction: Optional[str] = None - unit_of_measure_code: Optional[str] = None - country: Optional[str] = None - fraction_type: Optional[str] = None - sector: Optional[str] = None - import_tax_rate: Optional[Decimal] = None - export_tax_rate: Optional[Decimal] = None - publication_date: Optional[datetime] = None - is_immex: Optional[bool] = None - normal_temporality: Optional[bool] = None - services_temporality: Optional[bool] = None - certified_temporality: Optional[bool] = None - by_log: Optional[bool] = None - end_date: Optional[datetime] = None - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/a76/general_catalogs/fractions/historical_tariff_fractions/models.py b/backend/api/v1/modules/a76/general_catalogs/fractions/historical_tariff_fractions/models.py deleted file mode 100644 index 4f2fc3e6..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/fractions/historical_tariff_fractions/models.py +++ /dev/null @@ -1,34 +0,0 @@ -from datetime import datetime -from typing import Optional -from decimal import Decimal -from sqlalchemy import DateTime, ForeignKey, String, Integer, Numeric, Boolean -from sqlalchemy.orm import Mapped, mapped_column -from core.database import Base -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin - - -class HistoricalTariffFraction(Base, TenantScopedMixin, TimestampMixin): - """ - Historical tariff fractions catalog. - Maps to SQL Server table: GFraccionesHistorico - """ - - __tablename__ = "historical_tariff_fractions" - __table_args__ = {"schema": "a76"} - - id: Mapped[int] = mapped_column(Integer, primary_key=True, nullable=False) - historical_fraction: Mapped[Optional[str]] = mapped_column(String(8), nullable=True) - nico: Mapped[Optional[str]] = mapped_column(String(2), nullable=True) - unit_of_measure_code: Mapped[Optional[str]] = mapped_column(ForeignKey("a76.unit_of_measure_customs.code"), nullable=True) - country: Mapped[Optional[str]] = mapped_column(ForeignKey("public.countries.m3_key"), nullable=True) - fraction_type: Mapped[Optional[str]] = mapped_column(String(7), nullable=True) - sector: Mapped[Optional[str]] = mapped_column(String(5), nullable=True) - import_tax_rate: Mapped[Optional[Decimal]] = mapped_column(Numeric(7, 2), nullable=True) - export_tax_rate: Mapped[Optional[Decimal]] = mapped_column(Numeric(7, 2), nullable=True) - publication_date: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True) - is_immex: Mapped[Optional[bool]] = mapped_column(Boolean, nullable=True) - normal_temporality: Mapped[Optional[bool]] = mapped_column(Boolean, nullable=True) - services_temporality: Mapped[Optional[bool]] = mapped_column(Boolean, nullable=True) - certified_temporality: Mapped[Optional[bool]] = mapped_column(Boolean, nullable=True) - by_log: Mapped[Optional[bool]] = mapped_column(Boolean, nullable=True) - end_date: Mapped[Optional[datetime]] = mapped_column(DateTime, nullable=True) diff --git a/backend/api/v1/modules/a76/general_catalogs/fractions/historical_tariff_fractions/routes.py b/backend/api/v1/modules/a76/general_catalogs/fractions/historical_tariff_fractions/routes.py deleted file mode 100644 index 5dca0eb2..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/fractions/historical_tariff_fractions/routes.py +++ /dev/null @@ -1,136 +0,0 @@ -from typing import List, Optional -from datetime import datetime -from fastapi import APIRouter, Depends, HTTPException, Query -from sqlalchemy.orm import Session -from core.database import get_core_db -from core.security import get_current_user -from .service import HistoricalTariffFractionService -from .schemas import HistoricalTariffFractionResponse, HistoricalTariffFractionCreate, HistoricalTariffFractionUpdate, HistoricalTariffFractionListResponse - -from api.v1.common.tenant_crud_routes import validate_access_to_resource - -router = APIRouter() - -@router.get("/", response_model=HistoricalTariffFractionListResponse) -def get_historical_fractions( - company_id: int = Query(..., description="Company ID"), - page: int = Query(1, ge=1, description="Page number"), - page_size: int = Query(50, ge=1, le=1000, description="Page size"), - historical_fraction: Optional[str] = Query(None, description="Search by historical fraction code"), - db: Session = Depends(get_core_db), - current_user = Depends(get_current_user) -): - tenant_id = validate_access_to_resource( - db, company_id, current_user, ["frac_historical.view"] - ) - skip = (page - 1) * page_size - service = HistoricalTariffFractionService(db) - items, total = service.get_multi(tenant_id, company_id, skip=skip, limit=page_size, historical_fraction=historical_fraction) - return { - "items": items, - "total": total, - "page": page, - "page_size": page_size, - "pages": (total + page_size - 1) // page_size if page_size > 0 else 1 - } - -@router.get("/rate") -async def get_rate( - company_id: int = Query(..., description="Company ID"), - historical_fraction: str = Query(..., description="8-character fraction code"), - nico: str = Query(..., description="2-character NICO code"), - direction: str = Query("export", description="Movement direction: 'import' or 'export'"), - tariff_type: str = Query("GENERAL", description="Tariff regimen: 'GENERAL', 'PROSEC', etc."), - invoice_date: str = Query(..., description="ISO Date (YYYY-MM-DD)"), - is_regime_change: bool = Query(False, description="Whether the invoice is a regime change (Cambio de Régimen)"), - db: Session = Depends(get_core_db), - current_user = Depends(get_current_user) -): - tenant_id = validate_access_to_resource( - db, company_id, current_user, ["frac_historical.view"] - ) - - # Parse date - try: - parsed_date = datetime.fromisoformat(invoice_date.split('T')[0]) - except ValueError: - raise HTTPException(status_code=400, detail="Invalid date format. Use YYYY-MM-DD") - - service = HistoricalTariffFractionService(db) - rate = await service.get_historical_rate( - tenant_id=int(tenant_id), - company_id=company_id, - historical_fraction=historical_fraction, - nico=nico, - direction=direction, - tariff_type=tariff_type, - invoice_date=parsed_date, - is_regime_change=is_regime_change - ) - - if rate is None: - return {"found": False, "rate": 0} - - return {"found": True, "rate": float(rate)} - -@router.get("/{id}", response_model=HistoricalTariffFractionResponse) -def get_historical_fraction( - id: int, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user = Depends(get_current_user) -): - tenant_id = validate_access_to_resource( - db, company_id, current_user, ["frac_historical.view"] - ) - service = HistoricalTariffFractionService(db) - fraction = service.get(id, tenant_id, company_id) - if not fraction: - raise HTTPException(status_code=404, detail="Historical tariff fraction not found") - return fraction - -@router.post("/", response_model=HistoricalTariffFractionResponse) -def create_historical_fraction( - fraction_in: HistoricalTariffFractionCreate, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user = Depends(get_current_user) -): - tenant_id = validate_access_to_resource( - db, company_id, current_user, ["frac_historical.create"] - ) - service = HistoricalTariffFractionService(db) - return service.create(fraction_in, tenant_id, company_id) - -@router.put("/{id}", response_model=HistoricalTariffFractionResponse) -def update_historical_fraction( - id: int, - fraction_in: HistoricalTariffFractionUpdate, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user = Depends(get_current_user) -): - tenant_id = validate_access_to_resource( - db, company_id, current_user, ["frac_historical.edit"] - ) - service = HistoricalTariffFractionService(db) - fraction = service.get(id, tenant_id, company_id) - if not fraction: - raise HTTPException(status_code=404, detail="Historical tariff fraction not found") - return service.update(fraction, fraction_in) - -@router.delete("/{id}", response_model=HistoricalTariffFractionResponse) -def delete_historical_fraction( - id: int, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user = Depends(get_current_user) -): - tenant_id = validate_access_to_resource( - db, company_id, current_user, ["frac_historical.delete"] - ) - service = HistoricalTariffFractionService(db) - fraction = service.get(id, tenant_id, company_id) - if not fraction: - raise HTTPException(status_code=404, detail="Historical tariff fraction not found") - return service.delete(id, tenant_id, company_id) diff --git a/backend/api/v1/modules/a76/general_catalogs/fractions/historical_tariff_fractions/schemas.py b/backend/api/v1/modules/a76/general_catalogs/fractions/historical_tariff_fractions/schemas.py deleted file mode 100644 index e6cc1c53..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/fractions/historical_tariff_fractions/schemas.py +++ /dev/null @@ -1,45 +0,0 @@ - -from typing import Optional, List -from datetime import datetime -from decimal import Decimal -from pydantic import BaseModel, Field - -class HistoricalTariffFractionBase(BaseModel): - """Base schema for Historical Tariff Fraction""" - historical_fraction: Optional[str] = Field(None, max_length=8) - unit_of_measure_code: Optional[str] = None - country: Optional[str] = None - fraction_type: Optional[str] = Field(None, max_length=7) - sector: Optional[str] = Field(None, max_length=5) - import_tax_rate: Optional[Decimal] = None - export_tax_rate: Optional[Decimal] = None - publication_date: Optional[datetime] = None - is_immex: Optional[bool] = None - normal_temporality: Optional[bool] = None - services_temporality: Optional[bool] = None - certified_temporality: Optional[bool] = None - by_log: Optional[bool] = None - end_date: Optional[datetime] = None - -class HistoricalTariffFractionCreate(HistoricalTariffFractionBase): - """Schema for creating a Historical Tariff Fraction""" - pass - -class HistoricalTariffFractionUpdate(HistoricalTariffFractionBase): - """Schema for updating a Historical Tariff Fraction""" - pass - -class HistoricalTariffFractionResponse(HistoricalTariffFractionBase): - """Schema for reading a Historical Tariff Fraction""" - id: int - - class Config: - from_attributes = True - -class HistoricalTariffFractionListResponse(BaseModel): - """Schema for paginated list of Historical Tariff Fractions""" - items: List[HistoricalTariffFractionResponse] - total: int - page: int - page_size: int - pages: int diff --git a/backend/api/v1/modules/a76/general_catalogs/fractions/historical_tariff_fractions/seed.py b/backend/api/v1/modules/a76/general_catalogs/fractions/historical_tariff_fractions/seed.py deleted file mode 100644 index 4bc9e7cf..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/fractions/historical_tariff_fractions/seed.py +++ /dev/null @@ -1,6592 +0,0 @@ -seed = [ - ('1012901', 'None', '7', 'None', 'GENERAL', 'None', '20.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('1039102', 'None', '7', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('1039202', 'None', '7', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('1039203', 'None', '7', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('1041002', 'None', '7', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('1051101', 'None', '7', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('1051102', 'None', '7', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('1061901', 'None', '7', 'None', 'GENERAL', 'None', '20.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('1061903', 'None', '7', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('1062001', 'None', '7', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('1062002', 'None', '7', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('1062003', 'None', '7', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('1062099', 'None', '7', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('1063901', 'None', '7', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('1063902', 'None', '7', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('2071301', 'None', '1', 'None', 'GENERAL', 'None', '75.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('2071302', 'None', '1', 'None', 'GENERAL', 'None', '75.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('2071303', 'None', '1', 'None', 'GENERAL', 'None', '75.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('2071399', 'None', '1', 'None', 'GENERAL', 'None', '75.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('2071401', 'None', '1', 'None', 'GENERAL', 'None', '75.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('2071403', 'None', '1', 'None', 'GENERAL', 'None', '75.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('2071404', 'None', '1', 'None', 'GENERAL', 'None', '75.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('2072601', 'None', '1', 'None', 'GENERAL', 'None', '75.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('2072602', 'None', '1', 'None', 'GENERAL', 'None', '75.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('2072699', 'None', '1', 'None', 'GENERAL', 'None', '75.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('2072701', 'None', '1', 'None', 'GENERAL', 'None', '75.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('2072703', 'None', '1', 'None', 'GENERAL', 'None', '75.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('2076001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('2076002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('2076099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('2089001', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('2089002', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('2109901', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('2109903', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('3029001', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('3039001', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('3044402', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-11-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('3044499', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-11-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('3044902', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-11-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('3044903', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-11-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('3044904', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-11-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('3045202', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-11-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('3045299', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-11-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('3045302', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-11-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('3045399', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-11-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('3045901', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-11-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('3045902', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-11-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('3045903', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-11-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('3047402', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-11-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('3047499', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-11-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('3048902', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-11-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('3048903', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-11-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('3049502', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-11-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('3049599', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-11-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('3049901', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-11-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('3049902', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-11-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('3049903', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-11-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('3049904', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-11-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('3053202', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-11-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('3053299', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-11-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('3053901', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-11-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('3053902', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-11-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('3053903', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-11-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('3053904', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-11-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('3053905', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-11-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('3054901', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('3054902', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-11-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('3054903', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-11-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('3054904', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-11-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('3054905', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-11-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('3054906', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-11-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('3055101', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('3055199', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('3055901', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('3055902', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-11-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('3055903', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-11-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('3055904', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-11-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('3055905', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-11-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('3055906', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-11-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('3055907', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-11-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('3056901', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-11-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('3056902', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-11-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('3056903', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-11-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('3056904', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-11-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('3056905', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-11-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('3057203', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-11-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('3057204', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-11-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('3057205', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-11-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('3057206', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-11-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('3057207', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-11-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('3057208', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-11-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('3057299', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-11-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('3057902', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-11-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('3057903', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-11-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('3057904', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-11-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('3057905', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-11-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('3057906', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-11-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('3057907', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-11-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('3062101', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('3062201', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('3062401', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('3062501', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('3062601', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('3062699', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('3062701', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('3062799', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('3062901', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('3074101', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('3074199', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('3074901', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('3078999', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('4011001', 'None', '8', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('4011099', 'None', '8', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('4012001', 'None', '8', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('4012099', 'None', '8', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('4014001', 'None', '8', 'None', 'GENERAL', 'None', '10.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('4014099', 'None', '8', 'None', 'GENERAL', 'None', '10.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('4015001', 'None', '8', 'None', 'GENERAL', 'None', '10.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('4015099', 'None', '8', 'None', 'GENERAL', 'None', '10.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('4051001', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('4051099', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('4063001', 'None', '1', 'None', 'GENERAL', 'None', '45.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('4063099', 'None', '1', 'None', 'GENERAL', 'None', '45.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('4069003', 'None', '1', 'None', 'GENERAL', 'None', '45.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('4069005', 'None', '1', 'None', 'GENERAL', 'None', '45.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('4069006', 'None', '1', 'None', 'GENERAL', 'None', '45.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('4072101', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('4072199', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('4079001', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('4089101', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('4089199', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('4089901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('5079001', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('5080001', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('5080099', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('5100001', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('5100002', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('5100003', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('5100099', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('5119101', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('5119902', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('5119906', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('5119907', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('6011001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('6011002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('6011003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('6011004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('6011005', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('6011006', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('6011007', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('6011008', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('6011099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('6012001', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('6012002', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('6012003', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('6012004', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('6012005', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('6012006', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('6012007', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('6012008', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('6012009', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('6012099', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('6021001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('6021002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('6021003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('6021004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('6021005', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('6021006', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('6021099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('6022001', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('6022002', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('6022003', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('6022099', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('6029001', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('6029002', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('6029003', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('6029004', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('6029005', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('6029008', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('6029009', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('6029010', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('6029011', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('6029012', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('6029013', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('6031401', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('6031499', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('6031901', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('6031902', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('6031903', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('6031904', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('6031905', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('6031906', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('6031907', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('6031908', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('6042002', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('6042003', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('6049002', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('6049003', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('6049004', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('7020001', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('7020002', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2013-05-15 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('7020099', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('7031001', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('7031099', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('7032001', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('7032099', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('7041001', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('7041002', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('7041099', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('7049001', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('7092001', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('7092099', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('7094001', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('7094099', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('7095901', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('7096001', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('7096099', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('7099901', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('7108002', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('7108003', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('7108004', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('7109001', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('7109099', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('7119001', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('7119002', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('7129002', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('7129003', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('7133302', 'None', '1', 'None', 'GENERAL', 'None', '45.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('7133303', 'None', '1', 'None', 'GENERAL', 'None', '45.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('7149001', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('8029001', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('8041001', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('8041099', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('8042001', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('8042099', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('8045001', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('8045002', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('8045003', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('8052001', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('8055001', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('8055002', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('8055099', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('8071901', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('8093001', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('8093002', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('8129001', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('8129002', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('8131001', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('8131099', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('8132001', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('8132099', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('8134001', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('8134002', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('8134003', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('8134099', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('9011101', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('9011199', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('9019001', 'None', '1', 'None', 'GENERAL', 'None', '60.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('9042101', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('9042199', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('9042201', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('9042299', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('9109901', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('9109902', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('10039001', 'None', '1', 'None', 'GENERAL', 'None', '60.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('10059001', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('10059002', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('10059003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('10063001', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('10063099', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('11029001', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('11029002', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('11031901', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('11031902', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('11031999', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('11032001', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('11032099', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('11041901', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('11041999', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('11042901', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('11042999', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('11062001', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('11062099', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('11063001', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('11063099', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('11081901', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('11081999', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('12060001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('12060099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('12076001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('12076002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('12079901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('12079902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('12089001', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('12089002', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('12092901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('12092902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('12092903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('12092905', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('12099101', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('12099102', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('12099103', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('12099104', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('12099105', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('12099106', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('12099107', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('12099108', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('12099109', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('12099110', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('12099111', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('12099112', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('12099113', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('12099114', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('12099199', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('12099901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('12099902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('12099903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('12099904', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('12099905', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('12099906', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('12119001', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('12119003', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('12119004', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('12119005', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('12119006', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('12119007', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2014-12-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('12129201', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('12129299', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('12129401', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('12129499', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('12129904', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('13019001', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('13019002', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('13021101', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('13021103', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('13021201', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('13021299', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('13021910', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('13021911', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('13021912', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('13022001', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('13022099', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('13023903', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('14049001', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('14049002', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('14049003', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('14049004', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('15030001', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('15030099', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('15042001', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('15042099', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('15050001', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('15050002', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('15050003', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('15050099', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('15060002', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('15091001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('15091099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('15099001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('15099002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('15159001', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('15159002', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('15159003', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('15159004', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('15159005', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('15179001', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('15179002', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('15219001', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('15219002', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('15219003', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('16010001', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('16010002', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2018-06-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('16010099', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('16021001', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('16021099', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('16022001', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('16022099', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('16024901', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('16025001', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('16025099', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('16041301', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('16041303', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2019-11-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('16041399', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('16041401', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('16041402', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('16041403', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('16041601', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('16041603', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2019-11-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('16041699', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('16041901', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('16041902', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('16041903', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2019-11-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('16042001', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('16042002', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('16042004', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2019-11-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('16042005', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2019-11-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('16042006', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2019-11-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('16042007', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2019-11-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('16042008', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2019-11-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('16042009', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2019-11-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('16042099', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('16051001', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('16051099', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('17011201', 'None', '1', 'None', 'GENERAL', 'None', '0.36', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('17011204', 'None', '1', 'None', 'GENERAL', 'None', '0.36', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('17011401', 'None', '1', 'None', 'GENERAL', 'None', '0.34', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('17011404', 'None', '1', 'None', 'GENERAL', 'None', '0.34', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('17019102', 'None', '1', 'None', 'GENERAL', 'None', '0.36', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('17019103', 'None', '1', 'None', 'GENERAL', 'None', '0.36', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('17019901', 'None', '1', 'None', 'GENERAL', 'None', '0.36', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('17019902', 'None', '1', 'None', 'GENERAL', 'None', '0.36', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('17026001', 'None', '1', 'None', 'GENERAL', 'None', '75.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('17026002', 'None', '1', 'None', 'GENERAL', 'None', '75.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('17026099', 'None', '1', 'None', 'GENERAL', 'None', '75.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('17031002', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('18069001', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('18069002', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('19011001', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('19011099', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('19012001', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('19019001', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('19059001', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('20019001', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('20019002', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('20019003', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('20049001', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('20049002', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('20049099', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('20059901', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('20059902', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('20079904', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('20081101', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('20081199', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('20081901', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('20081999', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('20083001', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('20083002', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('20083003', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('20083004', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('20083005', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('20083006', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('20083007', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('20083008', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('20083099', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('20089901', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('20091201', 'None', '8', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('20091299', 'None', '8', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('20091901', 'None', '8', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('20093101', 'None', '8', 'None', 'GENERAL', 'None', '20.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('20093199', 'None', '8', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('20093901', 'None', '8', 'None', 'GENERAL', 'None', '20.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('20094101', 'None', '8', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('20094199', 'None', '8', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('20094901', 'None', '8', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('20099001', 'None', '8', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('20099099', 'None', '8', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('21011101', 'None', '1', 'None', 'GENERAL', 'None', '45.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('21021001', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('21032001', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('21032099', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('21033001', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('21033099', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('21061003', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('21061004', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('21069002', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('21069006', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('22011001', 'None', '8', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('22011099', 'None', '8', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('22029001', 'None', '8', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('22029002', 'None', '8', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('22029003', 'None', '8', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('22029004', 'None', '8', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('22029005', 'None', '8', 'None', 'GENERAL', 'None', '20.0', '0.0', '2009-12-16 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('22029099', 'None', '8', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('22041001', 'None', '8', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('22041099', 'None', '8', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('22042101', 'None', '8', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('22042102', 'None', '8', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('22042103', 'None', '8', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('22042199', 'None', '8', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('22051001', 'None', '8', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('22051099', 'None', '8', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('22059001', 'None', '8', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('22060001', 'None', '8', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('22060099', 'None', '8', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('22083001', 'None', '8', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('22083002', 'None', '8', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('22083003', 'None', '8', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('22083004', 'None', '8', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('22083099', 'None', '8', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('22084001', 'None', '8', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('22084099', 'None', '8', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('22087001', 'None', '8', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('22087002', 'None', '8', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('22087099', 'None', '8', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('22089002', 'None', '8', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('22089003', 'None', '8', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('22089004', 'None', '8', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('22089005', 'None', '8', 'None', 'GENERAL', 'None', '20.0', '0.0', '2012-11-23 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('23011001', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('23011099', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('23024001', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('23024099', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('23069001', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('23080001', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('23080099', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('23099001', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('23099002', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('23099003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('23099005', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('23099006', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('23099007', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('23099008', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('23099009', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('23099010', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('23099011', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('24011001', 'None', '1', 'None', 'GENERAL', 'None', '45.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('24011099', 'None', '1', 'None', 'GENERAL', 'None', '45.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('24012001', 'None', '1', 'None', 'GENERAL', 'None', '45.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('24012002', 'None', '1', 'None', 'GENERAL', 'None', '45.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('24012099', 'None', '1', 'None', 'GENERAL', 'None', '45.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('24039101', 'None', '1', 'None', 'GENERAL', 'None', '45.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('24039199', 'None', '1', 'None', 'GENERAL', 'None', '45.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('25010001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('25010099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('25030001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('25030099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('25062001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('25062099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('25084001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('25084099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('25101001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('25101099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('25102001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('25102099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('25131001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('25131099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('25241001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('25241002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('25249001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('25249002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('25309002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('25309003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('25309004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('25309005', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('25309006', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('25309007', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('25309008', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('25309009', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('26020001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('26020099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('26060001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('26060099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('26100001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('26100099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('26140001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('26140002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2008-12-24 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('26140099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('26151001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('26151099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('26190001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('26190099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('26204001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('26204099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('26209101', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('26209199', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('26209901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('26209902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('26209903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('27030001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('27030099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('27040001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('27040002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('27079901', 'None', '8', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('27079902', 'None', '8', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('27079903', 'None', '8', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('27090002', 'None', '15', 'None', 'GENERAL', 'None', '0.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('27090003', 'None', '15', 'None', 'GENERAL', 'None', '0.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('27090004', 'None', '15', 'None', 'GENERAL', 'None', '0.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('27101201', 'None', '8', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('27101202', 'None', '8', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('27101203', 'None', '8', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('27101205', 'None', '8', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('27101207', 'None', '8', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('27101208', 'None', '8', 'None', 'GENERAL', 'None', '0.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('27101209', 'None', '8', 'None', 'GENERAL', 'None', '0.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('27101210', 'None', '8', 'None', 'GENERAL', 'None', '0.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('27101291', 'None', '8', 'None', 'GENERAL', 'None', '0.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('27101901', 'None', '8', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('27101903', 'None', '8', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('27101905', 'None', '8', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('27101906', 'None', '8', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('27101907', 'None', '8', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('27101908', 'None', '8', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('27101909', 'None', '8', 'None', 'GENERAL', 'None', '0.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('27101910', 'None', '8', 'None', 'GENERAL', 'None', '0.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('27101991', 'None', '8', 'None', 'GENERAL', 'None', '0.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('27111902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('27111903', 'None', '8', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('27129004', 'None', '1', 'None', 'GENERAL', 'None', '7.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28030001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28042901', 'None', '8', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28047001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28047002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28047003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28047099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28051901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28100001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28100099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28111901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28112101', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28112102', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28112201', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28112202', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28112901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28112902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28121001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28121002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28121003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28121099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28152001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28152002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28164001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28164002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28181001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28181002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28181099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28182001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28182099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28183001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28183002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28201001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28201099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28211001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28211002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28211003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28249001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28251001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28251099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28254001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28254099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28255001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28255002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28255099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28259001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28261901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28261902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28269001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28273901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28273902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28273903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28273904', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28273905', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28273906', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28274101', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28274199', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28291101', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28291102', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28291901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28309001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28309002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28319001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28321001', 'None', '1', 'None', 'GENERAL', 'None', '7.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28321099', 'None', '1', 'None', 'GENERAL', 'None', '7.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28322001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28332901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28332902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28332904', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28334001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28334002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28334099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28341001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28341099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28352501', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28352599', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28352901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28352902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28352903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28364001', 'None', '1', 'None', 'GENERAL', 'None', '7.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28364002', 'None', '1', 'None', 'GENERAL', 'None', '7.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28369901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28369902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28369903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28369904', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28371101', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28371199', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28371901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28372001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28372099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28399001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28399002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28399003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28399099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28415001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28415002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28415099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28419001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28421001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28421099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28429001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28439001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28444001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28444002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28444099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28469001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28469002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28480001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28480002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28480003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28480099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28492001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28500001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28500002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28500099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28521001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28521002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28521003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28521099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28529001', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('28530001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29011001', 'None', '8', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29011002', 'None', '8', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29011003', 'None', '8', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29011004', 'None', '8', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29011099', 'None', '8', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29021901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29021902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29021903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29029001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29029002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29029003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29029004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29029005', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29029006', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29029007', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29031301', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29031399', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29031901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29031902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29031903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29031904', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29032901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29033901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29033902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29037701', 'None', '8', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29037702', 'None', '8', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29037703', 'None', '8', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29037704', 'None', '8', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29037799', 'None', '8', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29037901', 'None', '8', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29037904', 'None', '8', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29037905', 'None', '8', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29038203', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29038901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29038902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29039902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29039903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29039904', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29039906', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29042001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29042002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29042003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29042004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29042005', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29042006', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29042007', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29049001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29049002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29049003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29049004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29049005', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29049006', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29049007', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29049099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29051201', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29051299', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29051401', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29051402', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29051403', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29051499', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29051601', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29051602', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29051699', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29051901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29051902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29051903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29051904', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29051905', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29051907', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29051908', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29052201', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29052202', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29052203', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29052204', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29052205', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29052299', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29052901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29052902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29052903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29053903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29053904', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29054901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29054902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29055901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29055902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29055903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29055904', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29055905', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29061201', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29061299', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29061301', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29061302', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29061901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29061902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29061903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29062901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29062902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29062903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29062904', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29062906', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29071101', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29071199', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29071201', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29071299', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29071301', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29071302', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29071399', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29071501', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29071502', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29071599', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29071901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29071902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29071903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29071904', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29071905', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29071906', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29071907', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29071908', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29071909', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29072301', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29072302', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29072901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29072902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29072903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29072904', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29072905', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29081901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29081902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29081903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29081904', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29081905', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29081906', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29081907', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29089902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29089903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29089904', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29089905', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29089906', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29089907', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29089908', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29089909', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29089910', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29089911', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29089912', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29091901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29091902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29091903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29093001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29093002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29093003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29093004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29093005', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29093006', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29093007', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29093008', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29093009', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29093099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29094402', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29095002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29095003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29095004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29095005', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29109002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29110001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29110002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29110003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29110099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29121901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29121902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29121903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29121904', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29121906', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29121907', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29121908', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29121909', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29121910', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29121911', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29121912', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29122901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29122903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29122904', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29122905', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29124901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29124902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29124903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29124906', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29130001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29130002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29130099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29141901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29141903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29141904', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29141905', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29141906', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29142301', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29142302', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29143901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29143902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29143903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29143904', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29143905', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29143906', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29143907', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29145001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29145002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29145003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29145099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29146901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29146902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29147001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29147002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29147003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29147099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29151201', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29151202', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29151299', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29152901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29152902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29153901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29153906', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29153907', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29153908', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29153910', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29153911', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29153912', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29153914', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29153916', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29154002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29154003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29154004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29155001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29155002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29155004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29155005', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29156002', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29157008', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29157009', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29159002', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29159013', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29159014', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29159017', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29159021', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29159028', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29161501', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29161502', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29161503', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29161504', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29161901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29161902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29161903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29161904', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29161905', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29162001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29162002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29162003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29162004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29162099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29163104', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29163105', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29163201', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29163202', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29163901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29163902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29163903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29163904', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29163905', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29163906', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29163907', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29171101', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29171199', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29171301', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29171302', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29171399', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29171901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29171902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29171903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29171905', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29171906', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29171907', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29172001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29172002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29172003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29172099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29181301', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29181302', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29181303', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29181304', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29181399', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29181503', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29181504', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29181601', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29181602', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29181699', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29181901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29181902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29181903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29181904', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29181905', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29181907', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29181908', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29181909', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29181910', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29182101', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29182201', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29182299', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29182301', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29182302', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29182303', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29182399', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29182902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29182903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29182905', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29182906', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29182907', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29182908', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29182909', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29182910', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29182911', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29182912', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29182913', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29182914', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29182915', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29182916', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29183001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29183002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29183003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29183004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29183005', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29183006', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29183007', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29183008', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29183009', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29183099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29189903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29189905', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29189906', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29189907', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29189908', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29189909', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29189910', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29189911', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29189912', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29189913', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29189914', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29189915', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29189916', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29189917', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29189918', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29189919', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29189920', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29199001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29199002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29199003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29199004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29199005', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29199006', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29199007', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29199008', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29199009', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29199011', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29199012', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29201901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29201902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29209001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29209002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29209004', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29209005', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29209006', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29209007', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29209008', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29209010', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29209011', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29209012', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29209013', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29211101', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29211102', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29211103', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29211104', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2013-12-13 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29211901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29211903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29211906', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29211907', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29211908', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29211909', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29211910', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29211913', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29211914', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29211915', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29211916', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29211917', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29212101', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29212199', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29212901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29212902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29212903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29212904', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29212905', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29212906', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29212907', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29212908', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29212909', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29212911', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29213001', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29214201', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29214202', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29214203', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29214204', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29214205', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29214206', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29214207', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29214208', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29214209', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29214210', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29214211', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29214212', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29214213', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29214215', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29214216', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29214218', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29214301', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29214302', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29214303', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29214304', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29214305', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29214306', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29214307', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29214308', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29214309', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29214310', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29214311', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29214312', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29214399', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29214401', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29214499', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29214501', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29214502', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29214503', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29214504', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29214505', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29214506', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29214507', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29214901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29214902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29214903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29214904', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29214905', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29214906', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29214907', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29214908', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29214909', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29214910', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29214911', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29214912', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29214913', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29214915', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29215901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29215902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29215903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29215904', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29215905', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29215906', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29215907', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29215908', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29215909', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29215910', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29221301', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29221399', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29221401', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29221402', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29221499', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29221901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29221902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29221903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29221904', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29221905', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29221906', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29221907', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29221908', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29221909', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29221911', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29221912', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29221913', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29221915', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29221916', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29221917', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29221918', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29221919', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29221921', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29221922', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29221923', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29221924', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29221925', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29221926', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29221927', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29221928', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29221929', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29221930', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29221931', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29221932', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29221933', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29221934', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29221935', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29221936', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29221937', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29221938', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29221939', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29221941', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29222101', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29222102', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29222199', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29222901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29222902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29222903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29222904', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29222905', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29222906', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29222907', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29222908', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29222909', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29222910', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29222911', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29222912', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29222913', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29222914', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29222915', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29222916', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29222917', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29222918', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29222919', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29222920', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29222921', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29222922', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29222923', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29222924', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29223901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29223902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29223903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29223904', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29223905', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29223906', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29223907', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29223908', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29223910', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29223911', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29224101', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29224199', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29225002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29225003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29225004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29225005', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29225006', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29225007', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29225008', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29225009', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29225010', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29225011', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29225012', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29225013', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29225014', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29225015', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29225016', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29225017', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29225018', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29225019', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29225020', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29225021', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29225022', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29232099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29239001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29239004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29239005', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29239006', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29239007', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29239008', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29239009', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29239010', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29239011', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29239012', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29241201', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29241903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29241914', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29242101', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29242102', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29242103', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29242104', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29242105', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29242106', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29242107', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29242199', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29242901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29242902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29242903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29242904', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29242905', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29242906', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29242907', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29242908', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29242910', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29242911', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29242912', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29242913', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29242914', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29242915', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29242916', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29242917', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29242918', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29242919', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29242920', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29242921', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29242922', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29242923', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29242924', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29242925', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29242926', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29242927', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29242928', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29242929', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29242930', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29242931', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29242933', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29242934', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29242935', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29242936', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29242937', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29242938', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29242939', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29242940', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29242941', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29242942', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29242943', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29242944', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29242945', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29242946', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29242947', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29242948', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29251901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29251902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29251903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29252901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29252902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29252903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29252904', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29269001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29269002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29269003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29269004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29269005', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29269006', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29269007', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29269008', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29269009', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29270001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29270002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29270003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29270004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29270005', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29270099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29280004', 'None', '1', 'None', 'GENERAL', 'None', '7.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29291003', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29299001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29299002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29302001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29302002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29302003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29302004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29302008', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29302009', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29303001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29303099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29305002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29309001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29309002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29309003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29309004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29309005', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29309006', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29309007', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29309008', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29309009', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29309010', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29309012', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29309013', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29309014', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29309015', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29309016', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29309017', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29309018', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29309019', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29309020', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29309021', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29309022', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29309023', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29309024', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29309025', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29309026', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29309028', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29309029', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29309030', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29309032', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29309033', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29309034', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29309035', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29309036', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29309037', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29309038', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29309039', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29309040', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29309041', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29309042', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29309043', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29309044', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29309045', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29309046', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29309047', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29309048', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29309049', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29309050', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29309051', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29309052', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29309053', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29309054', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29309055', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29309056', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29309057', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29309058', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29309059', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29309060', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29309061', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29309062', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29309063', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29309064', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29309065', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29309066', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29309067', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29309068', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29309069', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29309070', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29309071', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29319001', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29319002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29319004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29319006', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29319007', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29319008', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29319010', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29319011', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29319012', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29319013', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29319014', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29319015', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29319016', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29319017', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29319018', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29319019', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29319020', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29321901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29321903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29321904', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29321905', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29322001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29322002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29322003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29322004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29322005', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29322006', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29322007', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29322008', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29322009', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29322010', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29322099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29329901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29329902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29329903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29329904', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29329905', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29329906', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29329907', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29329908', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29329909', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29329911', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29331101', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29331102', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29331199', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29331901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29331902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29331903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29331905', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29331906', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29331907', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29331909', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29332901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29332902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29332903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29332904', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29332905', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29332906', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29332907', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29332908', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29332909', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29332910', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29332911', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29332912', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29332913', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29332914', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29332915', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29332916', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29333101', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29333102', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29333301', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29333302', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29333901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29333903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29333904', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29333905', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29333907', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29333908', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29333909', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29333910', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29333911', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29333912', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29333913', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29333914', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29333915', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29333916', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29333918', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29333919', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29333920', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29333921', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29333923', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29333925', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29333926', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29333927', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29333928', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29333929', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29333930', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29333931', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29334902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29334903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29334905', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29334906', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29334907', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29334908', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29334909', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29334911', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29334912', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29334913', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29335501', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29335502', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29335599', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29335901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29335902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29335903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29335904', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29335905', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29335906', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29335908', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29335909', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29335910', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29335911', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29335912', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29335913', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29335914', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29335915', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29335916', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29335917', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29335918', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29335919', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29335920', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29336901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29336902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29336903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29336905', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29336906', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29336907', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29336908', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29336909', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29336910', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29336911', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29336913', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29336914', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29336916', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29337901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29337902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29337903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29337904', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29337999', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29339101', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29339102', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29339901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29339902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29339903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29339905', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29339906', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29339907', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29339908', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29339909', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29339911', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29339912', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29339914', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29339915', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29339916', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29339917', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29339918', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29339919', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29339920', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29339921', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29339922', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29339923', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29339924', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29339925', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29339927', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29339928', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29339929', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29339930', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29339931', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29339932', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29339933', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29339934', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29339935', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29339936', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29339937', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29339938', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29339939', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29339940', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29339941', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29339942', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29339943', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29339944', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29339945', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29339946', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29339947', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29339948', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29341001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29341002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29341003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29341004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29341005', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29341006', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29341007', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29341008', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29341099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29342001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29342002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29342003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29342004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29342006', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29343001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29343099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29349902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29349903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29349904', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29349905', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29349906', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29349907', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29349908', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29349909', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29349911', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29349912', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29349913', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29349915', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29349916', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29349917', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29349918', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29349919', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29349920', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29349921', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29349922', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29349923', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29349925', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29349926', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29349927', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29349928', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29349929', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29349930', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29349931', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29349932', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29349933', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29349934', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29349935', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29349936', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29349937', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29349938', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29349939', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29349940', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29349942', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29349943', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29349944', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29349945', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29349946', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29349947', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29349948', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29349949', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29350001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29350002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29350003', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29350004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29350005', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29350006', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29350007', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29350008', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29350009', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29350010', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29350011', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29350012', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29350013', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29350014', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29350015', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29350016', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29350017', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29350018', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29350019', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29350020', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29350021', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29350022', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29350023', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29350024', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29350025', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29350026', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29350027', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29350028', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29350029', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29350030', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29350031', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29350032', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29350033', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29350034', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29350035', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29350036', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29350038', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29350099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29362102', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29362103', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29362199', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29362201', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29362202', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29362203', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29362299', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29362301', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29362302', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29362399', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29362401', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29362402', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29362499', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29362501', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29362599', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29362602', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29362701', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29362799', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29362802', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29362899', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29362901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29362902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29362905', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29362906', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29362907', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29362908', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29369001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29369002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29369004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29369005', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29371201', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29371299', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29371901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29371902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29371903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29371904', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29371905', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29372101', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29372102', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29372103', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29372104', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29372201', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29372202', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29372203', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29372204', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29372205', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29372206', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29372207', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29372208', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29372209', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29372210', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29372301', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29372302', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29372303', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29372306', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29372309', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29372312', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29372313', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29372314', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29372315', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29372316', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29372319', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29372320', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29372323', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29372901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29372902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29372903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29372904', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29372905', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29372906', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29372907', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29372908', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29372910', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29372911', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29372912', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29372913', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29372914', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29372915', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29372916', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29372918', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29372920', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29372921', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29372922', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29372923', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29372925', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29372926', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29372927', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29372928', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29372930', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29372931', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29372934', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29372935', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29372937', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29372938', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29372939', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29372940', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29372941', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29372942', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29375001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29375099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29379001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29379002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29379003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29379004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29389001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29389002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29389003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29389004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29389005', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29389006', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29389007', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29391102', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29391103', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29391104', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29391105', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29391106', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29391107', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29391108', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29391109', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29391110', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29391111', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29391112', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29391113', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29391114', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29391115', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29391116', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29391117', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29391901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29391902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29391903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29391904', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29391905', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29391906', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29392001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29392099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29393001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29393002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29393003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29395901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29395902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29396901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29399101', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29399199', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29399901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29399902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29399903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29399904', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29399905', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29399906', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29399907', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29399908', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29399909', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29399910', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29399911', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29399912', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29399913', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29399914', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29399915', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29399917', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29399918', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29399919', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29399999', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29400001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29400002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29400003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29400004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29400099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29411001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29411002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29411004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29411005', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29411006', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29411009', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29411010', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29411011', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29411013', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29413001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29413002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29413003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29413099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29414001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29414002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29414003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29415001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29415099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29419001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29419002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29419003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29419004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29419005', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29419006', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29419007', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29419008', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29419009', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29419010', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29419011', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29419012', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29419013', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29419014', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29419015', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('29419016', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('30012001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('30012002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('30019002', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('30019007', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('30021001', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('30021002', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('30021003', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('30021004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('30021005', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('30021006', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('30021007', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('30021008', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('30021009', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('30021010', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('30021011', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('30021012', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('30021013', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('30021014', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('30021015', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('30021016', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('30021017', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('30021018', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('30021019', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('30021020', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('30021021', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('30021022', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('30021099', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('30022001', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('30022003', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('30022004', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('30022005', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('30023001', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('30023002', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('30023099', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('30029002', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('30029003', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('30029004', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('30033101', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('30033199', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('30033902', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('30034003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('30034004', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('30034099', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('30039006', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('30039007', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('30043101', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('30043199', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('30043906', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('30044003', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('30044004', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('30044005', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('30044099', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('30045003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('30045004', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('30049003', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('30049010', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('30059002', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('30061001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('30061002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('30064001', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('31023001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('31023099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('31029001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('31029099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('31031001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('31039001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('31043001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('31043099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('31049001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('31059001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('32019001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('32030001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('32030002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('32030099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('32041102', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('32041103', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('32041104', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('32041201', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('32041202', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('32041203', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('32041204', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('32041205', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('32041206', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('32041299', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('32041301', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('32041302', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('32041303', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('32041304', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('32041399', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('32041401', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('32041402', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('32041403', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('32041404', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('32041499', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('32041501', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('32041599', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('32041601', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('32041602', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('32041603', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('32041604', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('32041699', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('32041709', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('32041901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('32041902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('32041903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('32041905', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('32041907', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('32041908', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('32041909', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('32049001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('32049002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('32050001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('32064101', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('32064199', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('32064201', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('32064299', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('32064906', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('32065002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('32071001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('32071099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('32074001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('32074002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('32074099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('32081001', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('32081099', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('32082001', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('32082002', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('32082099', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('32089001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('32091001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('32091099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('32100001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('32100002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('32100003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('32100099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('32110001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('32110099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('32129001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('32129002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('32151101', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('32151102', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('32151199', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('32151901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('32151903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('32159001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('33011301', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('33011399', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('33011901', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('33011902', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('33011903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('33011904', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('33011905', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('33011906', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('33012901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('33012902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('33012903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('33012904', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('33012905', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('33012906', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('33012907', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('33019002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('33019003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('33062001', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('33062099', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('34021101', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('34021103', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('34021201', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('34021202', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('34052001', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('34052099', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('34070001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('34070002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('35019002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('35030002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('35030003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('35030004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('35040001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('35040002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('35040003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('35040004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('35040005', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('35040006', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('35040099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('35061001', 'None', '1', 'None', 'GENERAL', 'None', '6.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('35069102', 'None', '1', 'None', 'GENERAL', 'None', '6.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('35069103', 'None', '1', 'None', 'GENERAL', 'None', '6.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('35069104', 'None', '1', 'None', 'GENERAL', 'None', '6.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('35079001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('35079003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('35079004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('35079005', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('35079007', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('35079010', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('35079011', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('35079012', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('36020001', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('37011001', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('37019902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('37019903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('37019905', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('37023101', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('37023199', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('37023901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('37031001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('37031099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('37051001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('37051099', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('37059001', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('37059002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('37059099', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('37079001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('37079099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38013001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38013099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38019002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38019003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38019004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38029001', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38029002', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38029003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38029004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38029005', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38040001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38040099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38051001', 'None', '8', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38051099', 'None', '8', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38063001', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38063002', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38063099', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38085099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2019-11-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38089102', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38089103', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38089105', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2019-11-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38089201', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38089202', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38089299', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38089301', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38089302', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38089303', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38089399', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38089901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38089902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38099203', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38109001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38111101', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38111199', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38112901', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38112902', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38112903', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38123001', 'None', '1', 'None', 'GENERAL', 'None', '6.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38123002', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38123003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38123004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38123005', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38123099', 'None', '1', 'None', 'GENERAL', 'None', '6.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38151101', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38151102', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38151199', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38151201', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38151202', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38151299', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38159001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38159003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38160001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38160003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38160004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38160005', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38170004', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38190001', 'None', '8', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38190002', 'None', '8', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38220001', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38220002', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38220004', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38231201', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38231202', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38231901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38231902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38237001', 'None', '8', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38237099', 'None', '8', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38243001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38243099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38249001', 'None', '1', 'None', 'GENERAL', 'None', '6.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38249002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38249003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38249004', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38249005', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38249006', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38249007', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38249008', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38249009', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38249010', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38249011', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38249012', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38249013', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38249014', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38249015', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38249016', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38249017', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38249018', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38249019', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38249020', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38249021', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38249022', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38249023', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38249024', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38249025', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38249026', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38249027', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38249028', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38249029', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38249030', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38249031', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38249032', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38249033', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38249034', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38249035', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38249036', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38249037', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38249038', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38249039', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38249040', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38249041', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38249042', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38249043', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38249044', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38249045', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38249046', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38249047', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38249048', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38249049', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38249050', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38249051', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38249052', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38249053', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38249054', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38249055', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38249056', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38249057', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38249058', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38249059', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38249060', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38249061', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38249062', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38249063', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38249064', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38249065', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38249066', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38249067', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38249068', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38249069', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38249070', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38249071', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38249072', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38249073', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38249074', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38249075', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38249076', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38249077', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38249099', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38256101', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38256102', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38256199', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('38256901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39011001', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39011002', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39019002', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39019003', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39022001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39022099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39031901', 'None', '1', 'None', 'GENERAL', 'None', '7.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39031902', 'None', '1', 'None', 'GENERAL', 'None', '6.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39039004', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39042102', 'None', '1', 'None', 'GENERAL', 'None', '7.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39059901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39059902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39061001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39061099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39071001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39071002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39071003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39071004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39071099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39073001', 'None', '1', 'None', 'GENERAL', 'None', '6.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39074001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39074002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39074003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39074099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39075001', 'None', '1', 'None', 'GENERAL', 'None', '6.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39076001', 'None', '1', 'None', 'GENERAL', 'None', '7.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39076099', 'None', '1', 'None', 'GENERAL', 'None', '9.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39092001', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39093001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39093099', 'None', '1', 'None', 'GENERAL', 'None', '3.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39094002', 'None', '1', 'None', 'GENERAL', 'None', '6.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39094004', 'None', '1', 'None', 'GENERAL', 'None', '6.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39095001', 'None', '1', 'None', 'GENERAL', 'None', '6.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39095002', 'None', '1', 'None', 'GENERAL', 'None', '6.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39095003', 'None', '1', 'None', 'GENERAL', 'None', '6.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39122001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39122099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39123904', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39123905', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39131001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39131002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39131003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39131004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39131005', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39131099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39139001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39139003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39139004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39139007', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39139008', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39159002', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2013-09-02 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39161001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39161099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39162001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39162002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39162099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39169001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39169002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39169003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39169004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39169099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39171001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39171002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39171003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39171004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39171099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39172101', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39172102', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39172199', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39172201', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39172202', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39172299', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39172301', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39172302', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39172303', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39172399', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39172901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39172902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39172903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39172904', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39172905', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39172999', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39173201', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39173202', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39173299', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39181001', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39181099', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39201001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39201002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39201003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39201004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39201099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39202001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39202002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39202003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39202004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39202099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39203001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39203002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39203003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39203099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39204301', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39204302', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39204399', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39204901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39204902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39205901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39206201', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39206301', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39206302', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39206399', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39207301', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39207399', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39207901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39207999', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39209201', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39209299', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39209901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39209999', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39211301', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39211399', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39211901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39211999', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39219001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39219002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39219003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39219004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39219005', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39219006', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39219007', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39219008', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39231001', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39231002', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39232901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39232902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39232999', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39233001', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39233099', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39262002', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39263001', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39263099', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39269002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39269003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39269009', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39269010', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39269012', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39269016', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39269017', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39269021', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39269022', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39269023', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39269025', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39269026', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39269028', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39269030', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39269031', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39269032', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39269033', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39269034', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2008-12-24 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('39269035', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2008-12-24 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40012901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40013001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40013002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40013099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40021101', 'None', '1', 'None', 'GENERAL', 'None', '6.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40024901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40026001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40026099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40029102', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40052001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40052099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40059101', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40059102', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40059103', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40059199', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40081901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40082101', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40082199', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40082901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40082902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40091101', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40091199', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40091201', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40091202', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40091299', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40092101', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40092102', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40092103', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40092104', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40092201', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40092202', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40092203', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40092204', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40092299', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40093101', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40093102', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40093103', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40093104', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40093105', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40093201', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40093202', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40093203', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40093204', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40093299', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40094101', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40094102', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40094103', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40094199', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40094201', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40094202', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40094299', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40101101', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40101199', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40101201', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40101202', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40101299', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40101901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40101902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40101903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40101904', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40103501', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40103599', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40103601', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40103699', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40103901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40103902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40103903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40103904', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40111002', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40111003', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40111004', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40111005', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40111006', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40111007', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40111008', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40111009', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40111099', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40112002', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40112003', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40116101', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40116102', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40116199', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40116201', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40116299', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40116301', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40116302', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40116303', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40116399', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40116999', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40119201', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40119299', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40119301', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40119399', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40119401', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40119499', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40119901', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40119902', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40119999', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40122002', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40129001', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40129003', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2008-12-24 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40159004', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40169301', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40169302', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40169303', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40169399', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40169401', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40169902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40169903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40169905', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40169906', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40169907', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40169908', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40169909', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40169910', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40170001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('40170002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('41012001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('41012002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('41015001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('41015002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('41015099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('41019001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('41019002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('41019099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('41039001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('41039002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('41039003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('41041101', 'None', '4', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('41041102', 'None', '4', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('41041103', 'None', '4', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('41041199', 'None', '4', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('41041901', 'None', '4', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('41041902', 'None', '4', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('41041903', 'None', '4', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('41044101', 'None', '4', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('41044199', 'None', '4', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('41044901', 'None', '4', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('41051001', 'None', '4', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('41051002', 'None', '4', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('41051003', 'None', '4', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('41051099', 'None', '4', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('41062101', 'None', '4', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('41062102', 'None', '4', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('41062103', 'None', '4', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('41062199', 'None', '4', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('41064001', 'None', '4', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('41064099', 'None', '4', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('41071101', 'None', '4', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('41071199', 'None', '4', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('41071201', 'None', '4', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('41071299', 'None', '4', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('41071901', 'None', '4', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('41079901', 'None', '4', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('41139001', 'None', '4', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-30 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('42021201', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('42021202', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('42022201', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('42022202', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('42023201', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('42023202', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('42029201', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('42029202', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('42029203', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('42033001', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('42033099', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('42050001', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('42060002', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('43018001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('43018002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('43018003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('43018004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('43018005', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('43018006', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('43018007', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('43018099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('43021901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('43021902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('43021903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('43022001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('43022099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('43023001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('43023099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44011001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44031001', 'None', '5', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44032099', 'None', '5', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44034901', 'None', '5', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44034902', 'None', '5', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44039201', 'None', '5', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44041001', 'None', '5', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44041099', 'None', '5', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44050001', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44050002', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44061001', 'None', '6', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44069099', 'None', '6', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44071001', 'None', '5', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44071002', 'None', '5', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44071003', 'None', '5', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44071004', 'None', '5', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44071099', 'None', '5', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44072101', 'None', '5', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44072102', 'None', '5', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44072199', 'None', '5', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44072201', 'None', '5', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44072299', 'None', '5', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44072901', 'None', '5', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44072902', 'None', '5', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44072903', 'None', '5', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44079201', 'None', '5', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44079299', 'None', '5', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44079501', 'None', '5', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44079502', 'None', '5', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44079599', 'None', '5', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44079901', 'None', '5', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44079902', 'None', '5', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44079903', 'None', '5', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44081001', 'None', '5', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44081002', 'None', '5', 'None', 'GENERAL', 'None', '0.0', '0.0', '2009-12-16 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44083901', 'None', '5', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44089001', 'None', '5', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44091001', 'None', '4', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44092101', 'None', '4', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44092102', 'None', '4', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44092199', 'None', '4', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44092901', 'None', '4', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44092902', 'None', '4', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44101101', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44101102', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44101103', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44101199', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44101201', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44101299', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44101901', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44101902', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44101903', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44111202', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44111203', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44111204', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44111205', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44111302', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44111303', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44111304', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44111305', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44111402', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44111404', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44111405', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44119201', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44119299', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44119301', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44119302', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44119303', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44119399', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44119401', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44119402', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44119403', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44119499', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44123201', 'None', '1', 'None', 'GENERAL', 'None', '7.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44123299', 'None', '1', 'None', 'GENERAL', 'None', '7.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44123901', 'None', '1', 'None', 'GENERAL', 'None', '7.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44123999', 'None', '1', 'None', 'GENERAL', 'None', '7.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44129402', 'None', '1', 'None', 'GENERAL', 'None', '7.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44152001', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44152099', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44160002', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44160003', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44160004', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44187101', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44187202', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44187299', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44187901', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44189001', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44189099', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44190001', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44219001', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44219002', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44219003', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44219004', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('44219099', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('45041001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('45041003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('46012901', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('46019301', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('46019399', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('47020001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('47020099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('47031101', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('47031102', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('47031199', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('47031901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('47031902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('47032101', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('47032102', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('47032901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('47032902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('47063001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('47063002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('47063003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48022001', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48022002', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48022099', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48025401', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48025402', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48025403', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48025405', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48025406', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48025407', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48025501', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48025601', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48025701', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48025799', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48025801', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48025802', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48025803', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48025899', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48026101', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48026102', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48026199', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48026201', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48026299', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48026901', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48030001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48030002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48030003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48030099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48041901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48041902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48043101', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48043102', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48043103', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48043104', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48043199', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48043901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48043902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48043903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48044901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48044902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48045101', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48045199', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48045901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48045902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48052401', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48052499', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48052501', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48052599', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48070001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48070002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48070099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48084001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48084002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48084003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48084099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48089001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48099001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48101301', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48101302', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48101303', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48101304', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48101305', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48101306', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48101399', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48101401', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48101402', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48101403', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48101404', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48101405', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48101406', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48101499', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48102201', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48102299', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48102901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48103104', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48109901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48111001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48115103', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48115106', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48115901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48115902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48115903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48115904', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48115905', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48115906', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48116001', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48116002', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48116099', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48119001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48119002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48119003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48119004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48119005', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48119006', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48119007', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48119008', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48119009', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48149001', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48169001', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48169002', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48192001', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48192099', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48201001', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48201099', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48232001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48232099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48237001', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48237002', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48239003', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48239004', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48239006', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48239007', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48239008', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48239009', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48239013', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48239014', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48239015', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('48239016', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('49019101', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('49019102', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('49019103', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('49019199', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('49021001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('49021099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('49029001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('49070001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('49070002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('49089001', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('49089004', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('49119102', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('49119104', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('49119902', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('49119903', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('49119904', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('49119905', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('50030001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('50030099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('51011101', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('51011199', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('51011901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('51012101', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('51012199', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('51012901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('51013001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('51013099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('51021901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('51021902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('51022001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('51022099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('51031001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('51031002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('51031099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('51032001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('51032002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('51032099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('51052901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('51053901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('51053902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('51111101', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('51111199', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('51111901', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('51112001', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('51112099', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('51113001', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('51113099', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('51121101', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('51121199', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('51121901', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('51121902', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('51122001', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('51122099', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('51123001', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('51123002', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('51123099', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('51130001', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('51130099', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('52010001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('52010002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('52010099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('52029901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('52081901', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('52081902', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('52081999', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('52082901', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('52082999', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('52083901', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('52083999', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('52085901', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('52085902', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('52085999', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('52091901', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('52091999', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('52092901', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('52092999', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('52093901', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('52093999', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('52094202', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('52094203', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('52094291', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('52094292', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('52095901', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('52095999', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('52101101', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('52101199', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('52101901', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('52101902', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('52101999', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('52102901', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('52102902', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('52102999', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('52103901', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('52103999', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('52104901', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('52104999', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('52105901', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('52105902', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('52105999', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('52111101', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('52111199', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('52111901', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('52111999', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('52112001', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('52112002', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('52112003', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('52112099', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('52113901', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('52113999', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('52114202', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('52114203', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('52114291', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('52114292', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('52115901', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('52115999', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('52122401', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('52122499', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('53050001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('53050002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('53050003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('53050004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('53050005', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('53050006', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('53050007', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('53050099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('53089001', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('53089002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('53110001', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('53110099', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54021901', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54022001', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54022099', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54023901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54024501', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54024502', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54024504', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54024701', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54024702', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54024799', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54024801', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54024802', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54024899', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54024901', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54024902', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54024903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54024904', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54024905', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54025101', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54025199', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54025201', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54025202', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54025299', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54025901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54025902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54025903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54025904', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54025905', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54026101', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54026199', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54026201', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54026299', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54026901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54026902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54026903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54026904', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54026905', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54033101', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54033102', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54033201', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54033202', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54033901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54034101', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54034102', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54034901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54041201', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54041299', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54041901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54041902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54041903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54050001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54050002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54050003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54050004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54050099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54060003', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54071001', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54071002', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54071099', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54072001', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54072099', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54073001', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54073002', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54073003', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54073099', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54074102', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54074103', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54074104', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54074202', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54074203', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54074204', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54074301', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54074302', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54074303', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54074399', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54075102', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54075103', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54075104', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54075202', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54075203', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54075204', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54075301', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54075302', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54075303', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54075391', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54075392', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54075393', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54075402', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54075403', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54075404', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54076101', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54076103', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54076104', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54076105', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54076191', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54076192', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54076193', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54076901', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54076991', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54076992', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54076993', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54077301', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54077302', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54077303', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54077399', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54078201', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54078202', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54078203', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54078299', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54079101', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54079102', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54079103', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54079104', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54079105', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54079106', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54079107', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54079199', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54079201', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54079202', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54079203', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54079204', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54079205', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54079206', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54079299', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54079301', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54079302', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54079303', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54079304', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54079305', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54079306', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54079307', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54079399', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54079401', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54079402', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54079403', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54079404', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54079405', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54079406', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54079407', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54079499', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54081001', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54081002', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54081003', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54081004', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54081099', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54082101', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54082102', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54082103', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54082199', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54082201', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54082202', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54082203', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54082204', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54082299', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54082301', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54082302', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54082303', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54082304', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54082305', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54082399', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54082401', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54082499', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54083101', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54083102', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54083103', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54083104', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54083199', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54083201', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54083202', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54083203', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54083204', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54083205', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54083299', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54083301', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54083302', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54083303', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54083304', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54083399', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54083401', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54083402', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54083403', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('54083499', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55012001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55012003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55020001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55020099', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55032001', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55032002', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55041001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55041099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55121102', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55121103', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55121104', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55121901', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55121991', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55121992', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55121993', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55131102', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55131103', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55131202', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55131299', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55131302', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55131399', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55131902', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55131999', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55132102', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55132103', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55132303', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55132304', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55132391', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55132399', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55132902', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55132999', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55133102', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55133199', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55133904', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55133905', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55133906', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55133991', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55133999', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55134102', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55134199', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55134902', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55134904', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55134905', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55134991', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55134999', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55141901', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55141999', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55143001', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55143002', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55143003', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55143004', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55143099', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55151102', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55151103', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55151199', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55151202', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55151203', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55151299', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55151301', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55151399', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55151901', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55151902', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55152102', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55152103', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55152199', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55152201', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55152299', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55152901', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55152902', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55159102', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55159103', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55159199', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55159901', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55159902', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55159903', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55159904', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55163101', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55163199', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55163201', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55163299', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55163301', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55163399', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55163401', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('55163499', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('56012101', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('56012199', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('56013001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('56013099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('56021001', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('56021099', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('56022101', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('56022102', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('56022199', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('56031201', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('56031299', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('56031301', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('56031399', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('56049003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('56060001', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('56060002', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('56060099', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('56079001', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('56081101', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('56081199', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('56090001', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('56090099', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('57025001', 'None', '4', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('57025002', 'None', '4', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('57025099', 'None', '4', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('57032001', 'None', '4', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('57032099', 'None', '4', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('57033001', 'None', '4', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('57033099', 'None', '4', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('57050001', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('57050099', 'None', '4', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('58030001', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('58030002', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('58030003', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('58030004', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('58030099', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('59019001', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('59019002', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('59031001', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('59031099', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('59032001', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('59032099', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('59039001', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('59039003', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('59039004', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('59039005', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('59049001', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('59049002', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('59069101', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('59069199', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('59069901', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('59069902', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('59069903', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('59070002', 'None', '3', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('59070003', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('59070004', 'None', '4', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('59080001', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('59080002', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('59119002', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('59119003', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('60011002', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('60011099', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('60012102', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('60012199', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('60012202', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('60012203', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('60012299', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('60012902', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('60019102', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('60019199', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('60019202', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('60019203', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('60019299', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('60024002', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('60024003', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('60024004', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('60029002', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('60029003', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('60029004', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('60041002', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('60041003', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('60041004', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('60041005', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('60041006', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('60053102', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('60053199', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('60053201', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('60053299', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('60053301', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('60053402', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('60053499', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('60059001', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('60062101', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('60062199', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('60062201', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('60062299', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('60062301', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('60062399', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('60062401', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('60062499', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('60063102', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('60063199', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('60063202', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('60063299', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('60063302', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('60063399', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('60063402', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('60063499', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61012002', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61012099', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61013003', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61013091', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61013092', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61019001', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61019099', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61022002', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61022099', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61023002', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61023003', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61031001', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61031002', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61031003', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61031004', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61031099', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61032901', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61032999', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61033301', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61033399', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61033901', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61033902', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61033999', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61034201', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61034202', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61034204', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61034291', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61034292', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61034302', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61034303', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61034304', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61034305', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61034391', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61034392', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61034901', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61034902', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61034999', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61041301', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61041399', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61041901', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61041902', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61041903', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61041904', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61041905', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61041999', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61042901', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61042999', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61043301', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61043399', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61043901', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61043902', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61043999', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61044202', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61044299', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61044301', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61044391', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61044392', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61044401', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61044403', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61044499', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61044901', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61044999', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61045202', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61045299', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61045301', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61045391', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61045392', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61045901', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61045902', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61045999', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61046201', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61046202', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61046206', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61046291', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61046292', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61046302', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61046303', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61046304', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61046391', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61046392', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61046901', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61046902', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61046999', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61051003', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61051004', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61051099', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61052002', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61052099', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61059001', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61059099', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61061003', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61061004', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61061091', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61061092', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61062091', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61062092', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61069001', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61069002', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61069099', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61071102', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61071199', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61071202', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61071299', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61072102', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61072199', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61072202', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61072299', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61072901', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61072999', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61079901', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61079902', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61079999', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61082102', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61082199', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61082202', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61082299', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61083102', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61083199', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61083202', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61083299', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61083901', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61083999', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61089101', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61089199', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61089201', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61089299', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61089901', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61089999', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61091002', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61091099', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61099002', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61099003', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61099091', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61099092', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61099093', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61101102', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61101199', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61101202', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61101299', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61101901', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61102002', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61102003', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61102004', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61102091', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61102092', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61102093', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61102094', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61102099', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61103003', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61103004', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61103005', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61103006', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61103007', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61103091', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61103092', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61103093', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61109001', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61109003', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61109004', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61109005', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61109006', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61109091', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61109099', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61112004', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61112005', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61112007', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61112008', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61112009', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61112011', 'None', '9', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61112013', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61112014', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61112015', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61112016', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61112017', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61112018', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61112019', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61112020', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61112091', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61112092', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61112099', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61113005', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61113008', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61113009', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61113010', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61113011', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61113012', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61113013', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61113014', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61113015', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61113016', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61113017', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61113018', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61113019', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61113020', 'None', '9', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61113091', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61113092', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61113093', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61113099', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61119001', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61119002', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61119003', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61119004', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61119099', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61121901', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61121902', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61121999', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61122001', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61122099', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61130001', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61130099', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61143001', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61143099', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61149001', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61149099', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61161001', 'None', '9', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61161099', 'None', '9', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61171001', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61171099', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('61178001', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62011291', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62011292', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62011391', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62011392', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62019291', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62019292', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62021291', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62021292', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62021391', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62021392', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62029291', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62029292', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62029391', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62029392', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62031901', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62031902', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62031999', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62032901', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62032999', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62033202', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62033299', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62033391', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62033392', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62033901', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62033902', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62033903', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62033999', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62034207', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62034208', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62034209', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62034210', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62034293', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62034294', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62034295', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62034296', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62034304', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62034305', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62034308', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62034309', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62034310', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62034311', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62034393', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62034394', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62034395', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62034396', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62041301', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62041399', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62041901', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62041902', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62041903', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62041999', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62043202', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62043299', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62043391', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62043392', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62043393', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62043901', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62043902', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62043903', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62043999', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62044291', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62044292', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62044303', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62044391', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62044392', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62044491', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62044492', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62044901', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62044999', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62045202', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62045299', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62045391', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62045392', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62045901', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62045902', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62045903', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62045904', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62045905', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62045999', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62046206', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62046207', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62046208', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62046210', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62046291', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62046292', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62046293', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62046294', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62046295', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62046296', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62046304', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62046305', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62046307', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62046308', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62046309', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62046310', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62046393', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62046394', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62046395', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62046904', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62046905', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62062001', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62062099', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62063002', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62063003', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62071102', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62071199', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62072102', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62072199', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62072901', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62072999', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62079901', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62079902', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62079999', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62082102', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62082199', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62082202', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62082299', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62082901', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62082999', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62089201', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62089203', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62089291', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62089901', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62089902', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62089999', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62092004', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62092005', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62092008', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62092009', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62092010', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62092011', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62092012', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62092013', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62092014', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62092015', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62092091', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62092092', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62092099', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62093004', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62093006', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62093007', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62093008', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62093009', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62093010', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62093091', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62093092', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62093099', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62099001', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62099002', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62099003', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62099004', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62099099', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62112001', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62112099', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62113201', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62113299', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62113301', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62113399', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62113901', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62113902', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62113999', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62114201', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62114299', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62114301', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62114399', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62121002', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62121003', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62121004', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62121005', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62121006', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62121008', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62129001', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62129002', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62139001', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62139003', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62139099', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62171002', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('62171099', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('63022102', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('63022103', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('63022104', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('63022105', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('63022106', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('63022107', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('63022108', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('63022109', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('63022199', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('63022202', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('63022203', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('63022204', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('63022205', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('63022206', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('63022207', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('63022208', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('63022209', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('63022299', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('63023107', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('63023108', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('63023109', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('63023110', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('63023111', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('63023112', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('63023113', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('63023114', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('63023199', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('63023207', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('63023208', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('63023209', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('63023210', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('63023211', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('63023212', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('63023213', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('63023214', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('63023299', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('63025901', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('63025999', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('63026002', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('63026003', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('63026004', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('63026005', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('63026099', 'None', '6', 'None', 'GENERAL', 'None', '25.0', '0.0', '2016-05-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('63029901', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('63029999', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('63031901', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('63031999', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('63039201', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('63039299', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('63061901', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('63061999', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('63062901', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('63062999', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('63063001', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('63063099', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('63064001', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('63064099', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('63069001', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('63101001', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('63101099', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('63109001', 'None', '1', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64011002', 'None', '9', 'None', 'GENERAL', 'None', '10.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64011099', 'None', '9', 'None', 'GENERAL', 'None', '10.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64019202', 'None', '9', 'None', 'GENERAL', 'None', '10.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64019203', 'None', '9', 'None', 'GENERAL', 'None', '10.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64019204', 'None', '9', 'None', 'GENERAL', 'None', '10.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64019205', 'None', '9', 'None', 'GENERAL', 'None', '20.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64019206', 'None', '9', 'None', 'GENERAL', 'None', '20.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64019207', 'None', '9', 'None', 'GENERAL', 'None', '20.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64019208', 'None', '9', 'None', 'GENERAL', 'None', '20.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64019209', 'None', '9', 'None', 'GENERAL', 'None', '20.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64019210', 'None', '9', 'None', 'GENERAL', 'None', '20.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64019901', 'None', '9', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64019902', 'None', '9', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64019903', 'None', '9', 'None', 'GENERAL', 'None', '10.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64019904', 'None', '9', 'None', 'GENERAL', 'None', '10.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64019905', 'None', '9', 'None', 'GENERAL', 'None', '10.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64019906', 'None', '9', 'None', 'GENERAL', 'None', '10.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64019907', 'None', '9', 'None', 'GENERAL', 'None', '10.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64019908', 'None', '9', 'None', 'GENERAL', 'None', '10.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64021902', 'None', '9', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64021903', 'None', '9', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64021904', 'None', '9', 'None', 'GENERAL', 'None', '20.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64021905', 'None', '9', 'None', 'GENERAL', 'None', '20.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64021906', 'None', '9', 'None', 'GENERAL', 'None', '20.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64021907', 'None', '9', 'None', 'GENERAL', 'None', '20.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64021908', 'None', '9', 'None', 'GENERAL', 'None', '20.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64021909', 'None', '9', 'None', 'GENERAL', 'None', '20.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64022002', 'None', '9', 'None', 'GENERAL', 'None', '30.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64022003', 'None', '9', 'None', 'GENERAL', 'None', '30.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64029103', 'None', '9', 'None', 'GENERAL', 'None', '25.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64029104', 'None', '9', 'None', 'GENERAL', 'None', '25.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64029105', 'None', '9', 'None', 'GENERAL', 'None', '25.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64029107', 'None', '9', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64029903', 'None', '9', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64029904', 'None', '9', 'None', 'GENERAL', 'None', '30.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64029907', 'None', '9', 'None', 'GENERAL', 'None', '30.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64029908', 'None', '9', 'None', 'GENERAL', 'None', '30.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64029909', 'None', '9', 'None', 'GENERAL', 'None', '30.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64029910', 'None', '9', 'None', 'GENERAL', 'None', '30.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64029911', 'None', '9', 'None', 'GENERAL', 'None', '30.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64029912', 'None', '9', 'None', 'GENERAL', 'None', '30.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64029913', 'None', '9', 'None', 'GENERAL', 'None', '10.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64029914', 'None', '9', 'None', 'GENERAL', 'None', '10.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64029916', 'None', '9', 'None', 'GENERAL', 'None', '10.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64029917', 'None', '9', 'None', 'GENERAL', 'None', '10.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64029918', 'None', '9', 'None', 'GENERAL', 'None', '10.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64029922', 'None', '9', 'None', 'GENERAL', 'None', '20.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64029923', 'None', '9', 'None', 'GENERAL', 'None', '20.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64029924', 'None', '9', 'None', 'GENERAL', 'None', '10.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64029925', 'None', '9', 'None', 'GENERAL', 'None', '10.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64029926', 'None', '9', 'None', 'GENERAL', 'None', '30.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64029927', 'None', '9', 'None', 'GENERAL', 'None', '30.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64029928', 'None', '9', 'None', 'GENERAL', 'None', '30.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64029929', 'None', '9', 'None', 'GENERAL', 'None', '30.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64029930', 'None', '9', 'None', 'GENERAL', 'None', '10.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64029931', 'None', '9', 'None', 'GENERAL', 'None', '10.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64029932', 'None', '9', 'None', 'GENERAL', 'None', '10.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64031901', 'None', '9', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64031903', 'None', '9', 'None', 'GENERAL', 'None', '10.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64031904', 'None', '9', 'None', 'GENERAL', 'None', '10.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64031991', 'None', '9', 'None', 'GENERAL', 'None', '10.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64034002', 'None', '9', 'None', 'GENERAL', 'None', '20.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64034003', 'None', '9', 'None', 'GENERAL', 'None', '20.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64034004', 'None', '9', 'None', 'GENERAL', 'None', '20.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64035101', 'None', '9', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64035102', 'None', '9', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64035103', 'None', '9', 'None', 'GENERAL', 'None', '20.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64035104', 'None', '9', 'None', 'GENERAL', 'None', '20.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64035901', 'None', '9', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64035902', 'None', '9', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64035903', 'None', '9', 'None', 'GENERAL', 'None', '20.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64035904', 'None', '9', 'None', 'GENERAL', 'None', '20.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64035905', 'None', '9', 'None', 'GENERAL', 'None', '20.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64035906', 'None', '9', 'None', 'GENERAL', 'None', '20.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64035907', 'None', '9', 'None', 'GENERAL', 'None', '20.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64039101', 'None', '9', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64039105', 'None', '9', 'None', 'GENERAL', 'None', '20.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64039106', 'None', '9', 'None', 'GENERAL', 'None', '20.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64039107', 'None', '9', 'None', 'GENERAL', 'None', '20.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64039108', 'None', '9', 'None', 'GENERAL', 'None', '20.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64039109', 'None', '9', 'None', 'GENERAL', 'None', '30.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64039110', 'None', '9', 'None', 'GENERAL', 'None', '30.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64039111', 'None', '9', 'None', 'GENERAL', 'None', '30.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64039903', 'None', '9', 'None', 'GENERAL', 'None', '30.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64039904', 'None', '9', 'None', 'GENERAL', 'None', '30.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64039905', 'None', '9', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64039907', 'None', '9', 'None', 'GENERAL', 'None', '30.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64039908', 'None', '9', 'None', 'GENERAL', 'None', '30.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64039909', 'None', '9', 'None', 'GENERAL', 'None', '30.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64039910', 'None', '9', 'None', 'GENERAL', 'None', '30.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64039911', 'None', '9', 'None', 'GENERAL', 'None', '30.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64041104', 'None', '9', 'None', 'GENERAL', 'None', '30.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64041105', 'None', '9', 'None', 'GENERAL', 'None', '30.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64041107', 'None', '9', 'None', 'GENERAL', 'None', '25.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64041108', 'None', '9', 'None', 'GENERAL', 'None', '25.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64041110', 'None', '9', 'None', 'GENERAL', 'None', '25.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64041111', 'None', '9', 'None', 'GENERAL', 'None', '25.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64041113', 'None', '9', 'None', 'GENERAL', 'None', '30.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64041114', 'None', '9', 'None', 'GENERAL', 'None', '30.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64041115', 'None', '9', 'None', 'GENERAL', 'None', '30.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64041118', 'None', '9', 'None', 'GENERAL', 'None', '30.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64041119', 'None', '9', 'None', 'GENERAL', 'None', '30.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64041120', 'None', '9', 'None', 'GENERAL', 'None', '20.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64041121', 'None', '9', 'None', 'GENERAL', 'None', '20.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64041122', 'None', '9', 'None', 'GENERAL', 'None', '30.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64041901', 'None', '9', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64041904', 'None', '9', 'None', 'GENERAL', 'None', '20.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64041905', 'None', '9', 'None', 'GENERAL', 'None', '20.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64041906', 'None', '9', 'None', 'GENERAL', 'None', '20.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64041907', 'None', '9', 'None', 'GENERAL', 'None', '20.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64041909', 'None', '9', 'None', 'GENERAL', 'None', '20.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64041910', 'None', '9', 'None', 'GENERAL', 'None', '20.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64041911', 'None', '9', 'None', 'GENERAL', 'None', '20.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64041912', 'None', '9', 'None', 'GENERAL', 'None', '20.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64041913', 'None', '9', 'None', 'GENERAL', 'None', '20.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64041914', 'None', '9', 'None', 'GENERAL', 'None', '20.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64041915', 'None', '9', 'None', 'GENERAL', 'None', '20.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64041916', 'None', '9', 'None', 'GENERAL', 'None', '25.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64041917', 'None', '9', 'None', 'GENERAL', 'None', '20.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64041991', 'None', '9', 'None', 'GENERAL', 'None', '20.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64042002', 'None', '9', 'None', 'GENERAL', 'None', '10.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64042003', 'None', '9', 'None', 'GENERAL', 'None', '10.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64042099', 'None', '9', 'None', 'GENERAL', 'None', '10.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64052003', 'None', '9', 'None', 'GENERAL', 'None', '20.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64052004', 'None', '9', 'None', 'GENERAL', 'None', '20.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64052005', 'None', '9', 'None', 'GENERAL', 'None', '20.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64052091', 'None', '9', 'None', 'GENERAL', 'None', '20.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64059001', 'None', '9', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64059002', 'None', '9', 'None', 'GENERAL', 'None', '0.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64061001', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64061002', 'None', '9', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64061003', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64061004', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64061005', 'None', '9', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64061006', 'None', '9', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64061007', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('64061099', 'None', '9', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('65050001', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('65050002', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('65050003', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('65050099', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('65069901', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('65069999', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('66039001', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('68022301', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('68022399', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('68022901', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('68022999', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('68041001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('68041099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('68042101', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('68042199', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('68042201', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('68042202', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('68042203', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('68042299', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('68042301', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('68042399', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('68051099', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('68109901', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('68114001', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('68114002', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('68128002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('68128003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('68128004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('68128007', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('68129301', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('68129399', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('68129901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('68129904', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('68132001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('68132002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('68132003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('68132004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('68132099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('68138901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('68138902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('68141001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('68141099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('68159101', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('68159199', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('69029001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('69029002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('69029003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('69031001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('69031002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('69031099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('69032001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('69032002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('69032003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('69032004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('69032005', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('69032099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('69039001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('69071001', 'None', '4', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('69079001', 'None', '4', 'None', 'GENERAL', 'None', '15.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('69079002', 'None', '4', 'None', 'GENERAL', 'None', '15.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('69079099', 'None', '4', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('69081001', 'None', '4', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('69089002', 'None', '4', 'None', 'GENERAL', 'None', '15.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('69089003', 'None', '4', 'None', 'GENERAL', 'None', '15.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('69089099', 'None', '4', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('69091101', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('69091102', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('69091103', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('69091104', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('69091105', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('69091199', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('69120001', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('69120099', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70022001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70022002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70022003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70022004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70022099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70023101', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70023102', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70023199', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70031201', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70031299', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70031901', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70031999', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70042001', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70042002', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70042099', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70049001', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70049099', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70051001', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70051099', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70052101', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70052102', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70052199', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70052902', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70052903', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70052904', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70060001', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70060002', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70060003', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70060099', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70071102', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70071103', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70071901', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70072101', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70072102', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70091001', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70091002', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70091003', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70109001', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70109002', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70109003', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70111001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70111002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70111003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70111004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70111099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70112001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70112002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70112003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70112004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70112099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70119001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70132801', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70133701', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70133702', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70133703', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70134901', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70134902', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70134903', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70134904', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70171001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70171002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70171003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70171005', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70171011', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70171012', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70171013', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70172002', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70179001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70179002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70179005', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70189001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70193901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70193902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70193903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70193904', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70194001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70194099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70195101', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70195199', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70195901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70199001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70199002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70199003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70199004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70199005', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70199006', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70199007', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70199008', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70200001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70200002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70200003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70200004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70200005', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('70200099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('71011001', 'None', '2', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('71011099', 'None', '2', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('71129101', 'None', '2', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('71129199', 'None', '2', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('71131901', 'None', '2', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('71131902', 'None', '2', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('71159001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72015001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72015099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72022101', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72022199', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72029101', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72029102', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72029103', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72029201', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72029299', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72029901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72029902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72029903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72071201', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72071299', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72072001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72072099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72081001', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72081002', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72081004', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-09-20 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72081099', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72082501', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72082599', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72084001', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72084099', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72085101', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72085102', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72085103', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72091501', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72091502', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72091503', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72091599', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72091802', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-09-20 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72091899', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-09-20 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72101201', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72101202', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72101203', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72101299', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72103001', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72103099', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72104903', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72105001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72105002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72105099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72106901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72107003', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-09-20 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72107004', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-09-20 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72107005', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-09-20 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72107091', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-09-20 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72107099', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72109001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72109091', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2019-09-20 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72111401', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72111402', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72111404', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-09-20 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72111499', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72111901', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72111902', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72111903', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72111904', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72112301', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72112302', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72112399', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72112901', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72112902', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72112903', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72121001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72121002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72121099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72122001', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72122002', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72122099', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72123001', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72123002', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72123099', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72124001', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72124002', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72124003', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72124099', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72126001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72126002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72126003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72126099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72139101', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72139102', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72139901', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72139902', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-09-20 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72149101', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72149102', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72149199', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72149901', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72149902', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72155001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72155099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72159001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2019-09-20 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72163101', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72163102', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72163199', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72163201', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72163202', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72166901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72166902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72171001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72171099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72172001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72172099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72173001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72173099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72191201', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72191299', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72193102', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2019-09-20 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72193199', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2019-09-20 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72193201', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72193299', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72193501', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72193599', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72202001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72202002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72202099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72210002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2019-09-20 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72210099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2019-09-20 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72221101', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72221199', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72223001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72223099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72224002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2019-09-20 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72224099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2019-09-20 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72230001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72230099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72241001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72241002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72241003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72241004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72241005', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72241091', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2019-09-20 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72241099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72249001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72249003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2019-09-20 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72253002', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72253003', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72253004', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72253005', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72253006', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72253008', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-09-20 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72253091', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-09-20 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72253099', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72254001', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72254002', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72254003', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72254004', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72254005', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72254007', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-09-20 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72254091', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-09-20 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72254099', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72255002', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72255003', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72255004', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72255006', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72255008', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-09-20 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72255009', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-09-20 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72255010', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-09-20 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72255011', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-09-20 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72255012', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-09-20 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72255091', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-09-20 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72255099', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72269103', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72269104', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72269105', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72269108', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-09-20 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72269109', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-09-20 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72269110', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-09-20 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72269111', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-09-20 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72269112', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-09-20 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72269191', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-09-20 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72269199', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72269201', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72269202', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72269203', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72269204', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72269205', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72269207', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-09-20 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72269299', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72269901', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72269902', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72272002', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-09-20 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72272099', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-09-20 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72279001', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72279002', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-09-20 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72279003', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-09-20 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72281001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72281099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72282001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72282099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72283003', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-09-20 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72284001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72284099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72285001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72285099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72286001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72286003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2019-09-20 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72286099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72287002', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-09-20 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72287099', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-09-20 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72299001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72299002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72299003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('72299004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73021001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73021003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2019-09-20 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73021099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73024001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73024099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73029001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73029002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73030001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73030099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73041105', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73041991', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-09-20 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73042201', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73042202', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73042203', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73042299', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73042301', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73042302', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73042303', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73042401', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73042402', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73042403', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73042404', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73042405', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73042406', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73042499', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73042901', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73042902', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73042903', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73042904', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73042905', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73042906', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73043102', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73043103', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73043104', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73043105', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73043106', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73043107', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73043108', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73043109', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73043191', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2019-09-20 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73044101', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73044102', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73044199', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73044901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73045101', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73045102', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73045103', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73045104', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73045105', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73045106', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73045107', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73045108', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73045109', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73045110', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73045111', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73045191', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2019-09-20 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73045199', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73045901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73045902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73045903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73045904', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73045905', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73045910', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73045911', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2019-09-20 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73045912', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2019-09-20 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73045913', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2019-09-20 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73045914', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2019-09-20 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73045915', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2019-09-20 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73045916', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2019-09-20 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73045917', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2019-09-20 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73045991', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2019-09-20 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73045992', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2019-09-20 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73051101', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73051199', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73051201', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73051299', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73051901', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73059001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73061901', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-09-20 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73063005', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-09-20 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73063006', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-09-20 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73063091', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-09-20 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73065003', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-09-20 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73065004', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-09-20 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73066102', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-09-20 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73066103', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-09-20 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73066199', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-09-20 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73066901', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-09-20 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73066902', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-09-20 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73071101', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73071199', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73071901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73071902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73071903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73071904', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73071905', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73071906', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73072201', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73072299', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73079201', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73079299', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73079901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73079902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73079903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73079904', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73079905', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73082001', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73082099', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73083001', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73083099', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73090001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73090002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73090003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73090099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73101001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73101002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73101003', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73101004', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73101099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73102902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73102903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73102904', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73110001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73110002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73110003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73110004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73110099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73121002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73121003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73121004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73121006', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73121009', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73121010', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73141901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73141902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73151101', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73151102', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73151103', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73151104', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73151105', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73151199', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73151201', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73151202', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73151299', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73151901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73151902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73151903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73151999', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73158101', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73158102', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73158199', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73158201', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73158202', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73158299', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73158901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73158902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73181501', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73181502', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73181503', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73181505', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73181506', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73181507', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73181508', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73181509', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73181510', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73181601', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73181602', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73181603', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73181604', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73181605', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73181901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73181902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73182101', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73182199', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73182201', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73182299', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73182301', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73182399', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73182401', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73182402', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73182499', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73182901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73201001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73201099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73202001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73202002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73202003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73202004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73202099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73209001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73209002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73209003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73218101', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73218199', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73218201', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73218299', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73221101', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73221199', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73239101', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73239102', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73239199', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73239201', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73239202', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73239203', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73239299', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73239301', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73239302', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73239303', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73239304', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73239399', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73239401', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73239402', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73239403', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73239404', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73239499', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73249001', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73249002', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73249003', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73249099', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73251001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73251002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73251003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73251004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73251099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73259101', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73259102', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73259199', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73259901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73259902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73259903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73259904', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73259905', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73259906', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73261101', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73261102', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73261199', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73261901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73261902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73261903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73261904', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73261905', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73261906', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73261907', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73261908', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73261909', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73261910', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73261911', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73261912', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73261913', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73261914', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73262001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73262002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73262003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73262004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73262005', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73262099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73269001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73269002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73269003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73269004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73269005', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73269006', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73269007', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73269008', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73269009', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73269010', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73269011', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73269012', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73269013', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73269014', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73269015', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73269016', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73269017', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('73269018', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-30 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('74010001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('74010002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('74032901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('74032999', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('74040001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('74040002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('74040099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('74071002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('74072101', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('74072901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('74072902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('74072903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('74072904', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('74072905', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('74081101', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('74081199', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('74102101', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('74102102', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('74102103', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('74102199', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('74111002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('74111003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('74111004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('74112102', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('74112103', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('74112104', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('74112203', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('74112204', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('74112902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('74112903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('74112904', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('74130001', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('74130099', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('74151001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('74151099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('74152901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('74153301', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('74153302', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('74153399', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('74153901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('74199903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('74199904', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('74199905', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('74199906', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('74199907', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('74199908', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('74199909', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('74199910', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('74199911', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('74199912', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('74199913', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('74199914', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('74199915', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('75061001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('75061099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('75062001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('75062099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('75089001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('75089002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('75089003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('76011001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('76011099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('76012001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('76012099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('76020001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('76020099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('76072001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('76072099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('76081001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('76081002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('76090001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('76090099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('76151001', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('76151099', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('76169991', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-09-20 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('76169992', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-09-20 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('78060001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('78060002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('78060099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('79070001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('79070099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('81019601', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('81019602', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('81019699', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('81019901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('81029501', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('81029599', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('81049001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('81049002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('81110001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('81110099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('81130001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('81130099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82011001', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82011099', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82013001', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82013099', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82019001', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82019004', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82021002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82021003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82024001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82024099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82029101', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82029102', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82029103', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82029199', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82031001', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82034002', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82041202', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82051002', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82051003', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82055101', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82055103', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82055901', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82055902', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82055903', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82055904', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82055906', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82055907', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82055911', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82055912', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82055917', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82055918', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82055919', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82056001', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82056099', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82057001', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82057002', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82059002', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82059003', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82059004', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82071301', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82071302', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82071303', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82071304', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82071305', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82071306', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82071307', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82071399', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82071901', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82071902', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82071903', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82071904', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82071905', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82071906', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82071907', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82071908', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82071909', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82071999', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82073001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82073002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82074001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82074002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82074003', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82074099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82075001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82075002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82075003', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82075004', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82075005', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82075006', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82075099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82076001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82076002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82076003', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82076004', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82076005', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82076099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82077001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82077002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82077099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82081001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82081099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82082001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82082099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82083001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82083099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82084001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82084002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82084099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82141001', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82141099', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82142001', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82142099', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82149002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82149003', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82149004', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('82159901', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('83012001', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('83012099', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('83016001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('83016099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('83017001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('83017099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('83022001', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('83022099', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('83024101', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('83024102', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('83024103', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('83024104', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('83024105', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('83024199', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('83024201', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('83024202', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('83024299', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('83040001', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('83040002', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('83040099', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('83099002', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('83099004', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('83099005', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('83111001', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('83112001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('83112002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('83112003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('83112004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('83112099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('83113002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('83113003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('83113004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84069003', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84072101', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84072199', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84073201', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84073203', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84073301', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84073302', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84073303', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84073399', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84073401', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84073402', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84073499', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84079001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84079099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84099101', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84099102', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84099103', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84099104', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84099106', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84099107', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84099108', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84099109', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84099110', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84099112', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84099113', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84099114', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84099115', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84099116', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84099117', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84099118', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84099119', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84099906', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84099907', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84101201', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84101299', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84101301', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84101399', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84123901', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84131902', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84131904', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84133001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84133002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84133004', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84133005', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84133006', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84134001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84134099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84135001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84136002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84136003', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84136005', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84137003', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84137004', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84137006', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84139101', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84139102', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84139103', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84139104', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84139105', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84139106', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84139107', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84139108', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84139109', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84139110', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84139111', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84139112', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84139199', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84141001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84141002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84141003', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84141004', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84141005', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84141099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84143009', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84148001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84148002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84148003', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84148004', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84148005', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84148009', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84148011', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84148012', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84148013', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84148014', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84148015', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84148016', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84149001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84149002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84149003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84149004', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84149005', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84149006', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84149007', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84149008', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84149009', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84149099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84159001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84159099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84169001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84169099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84171001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84171002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84171004', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84171005', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84172001', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84172099', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84183003', 'None', '6', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84184003', 'None', '6', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84185002', 'None', '6', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84186102', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84186902', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84186903', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84186911', 'None', '6', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84186916', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84186917', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84189901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84189902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84191902', 'None', '6', 'None', 'GENERAL', 'None', '5.0', '0.0', '2012-11-23 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84191903', 'None', '6', 'None', 'GENERAL', 'None', '5.0', '0.0', '2012-11-23 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84192001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84192099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84193201', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84193299', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84193901', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84193902', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84193903', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84193904', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84193905', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84194001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84194002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84194003', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84195001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84195002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84195004', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84195005', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84198102', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84198901', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84198904', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84198905', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84198906', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84198907', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84198908', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84198912', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84198913', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84198914', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84198916', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84198920', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84198921', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84199001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84199002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84199003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84199099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84209101', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84209199', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84211101', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84211199', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84211201', 'None', '6', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84211299', 'None', '6', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84211902', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84211904', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84212103', 'None', '6', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84212904', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84212905', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84212907', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84213901', 'None', '6', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84213904', 'None', '6', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84219101', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84219102', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84219103', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84219199', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84219901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84219902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84219903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84222003', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84223001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84223002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84223003', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84223004', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84223005', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84223006', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84223007', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84223009', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84223010', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84224001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84224003', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84224004', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84224005', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84224006', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84229001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84229002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84229003', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84229004', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84229099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84231001', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84231099', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84233001', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84233099', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84238101', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84238102', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84238201', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84238202', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84239001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84239099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84241001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84241002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84241099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84242002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84242003', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84243001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84243002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84243003', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84248101', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84248102', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84248103', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84248104', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84248105', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84248106', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84248199', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84248901', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84248902', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84264103', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84264104', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84264901', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84264902', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84269101', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84269102', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84269901', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84269902', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84271002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84279001', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84279099', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84282001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84283901', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84283902', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84284001', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84289004', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84289005', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84294001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84294099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84295101', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84295102', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84295201', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84295202', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84295299', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84295903', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84295904', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84303101', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84303102', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84303199', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84304101', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84304102', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84304199', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84304901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84304902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84312001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84312099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84313101', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84313199', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84314101', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84314102', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84314103', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84314199', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84314301', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84314302', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84314399', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84314901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84314902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84314903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84322901', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84323001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84323002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84323003', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84323099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84324001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84328001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84328002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84328003', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84328099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84332001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84332002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84332099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84334001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84334002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84334099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84335901', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84335902', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84335903', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84335904', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84336001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84336002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84336003', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84336099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84339001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84339002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84339003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84339099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84362901', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84368001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84368002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84368003', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84368099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84371001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84371002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84371003', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84371099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84378001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84378002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84378099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84379001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84379099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84381002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84381005', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84381006', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84381007', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84382001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84382002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84382099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84384001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84384099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84385001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84385002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84385003', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84385004', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84385005', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84385007', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84385008', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84386001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84386002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84386003', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84386005', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84388001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84388002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84388003', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84388099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84389001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84389002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84389003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84389004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84389005', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84389099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84391001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84391002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84391003', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84391004', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84391005', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84391099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84401001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84401099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84411001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84411002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84411003', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84411099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84414001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84414099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84423001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84423002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84423099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84425002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84431101', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84431199', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84431302', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84431401', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84431499', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84431501', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84431599', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84431901', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84431902', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84431903', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84433201', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84433202', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84433203', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84433204', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84433205', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84433206', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84433207', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84433208', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84433299', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84433901', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84433903', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84433904', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84433907', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84433908', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84439101', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84439901', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84439902', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84439903', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84439904', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84439907', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84439908', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84439909', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84451901', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84453001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84453099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84459001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84472001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84472099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84479001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84484901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84501201', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84501299', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84501901', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84509001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84509002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84509099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84512101', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84512199', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84512901', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84512902', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84512903', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84518001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84518099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84519001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84519002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84519099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84522101', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84522102', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84522103', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84522104', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84522105', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84522199', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84522901', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84522902', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84522903', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84522904', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84522905', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84522906', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84522907', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84529002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84542001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84542099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84543001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84543099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84549001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84549099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84552101', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84552102', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84552199', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84552201', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84552202', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84552299', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84553001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84553002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84553099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84561001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84561099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84562001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84562099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84573001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84573004', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84581102', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84581902', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84589101', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84589199', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84589901', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84591001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84591099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84592101', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84592199', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84592901', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84594001', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84594099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84597001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84597002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84597099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84601101', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84601199', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84601901', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84602101', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84602102', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84602103', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84602104', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84602199', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84602901', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84602902', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84602903', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84602904', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84603101', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84603199', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84603901', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84604001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84604002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84604099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84609001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84609002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84609003', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84612001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84612099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84613001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84613099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84615001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84619002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84619004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84619005', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84622102', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84622103', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84622104', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84622105', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84622902', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84622904', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84623102', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84623902', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84624101', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84624102', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84624103', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84624199', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84624903', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84629901', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84629902', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84629903', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84633001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84633002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84633003', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84633099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84639001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84649001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84659201', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84659202', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84659203', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84659299', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84659301', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84659399', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84659401', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84659402', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84659499', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84659901', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84659903', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84661001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84661002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84662001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84662099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84663001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84663002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84663099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84669301', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84669302', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84669402', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84671101', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84671199', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84671901', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84672201', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84678901', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84679101', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84679199', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84679901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84679902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84682001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84682099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84690001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84690002', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84690003', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84690004', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84690099', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84701001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84701002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84701099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84702901', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84709001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84709002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84716001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84716002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84716003', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84716099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84718001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84718002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84718003', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2008-12-24 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84718099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84729002', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84729003', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84729004', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84729006', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84729007', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84729008', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84729009', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84729011', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84729015', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84731001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84731099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84733001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84733002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84733003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84734001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84734002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84734099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84735001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84735002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84735099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84741002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84742001', 'None', '6', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84742002', 'None', '6', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84742003', 'None', '6', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84742005', 'None', '6', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84742006', 'None', '6', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84743901', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84743902', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84748002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84748003', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84748005', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84748006', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84748007', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84748008', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84748009', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84748010', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84749001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84749002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84749099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84769001', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84769099', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84772002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84778001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84778002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84778003', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84778004', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84778005', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84778007', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84779001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84779002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84779003', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84779099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84781005', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84791002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84791005', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84791006', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84791008', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84794001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84794099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84798101', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84798105', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84798106', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84798108', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84798109', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84798203', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84798901', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84798902', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84798905', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84798909', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84798911', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84798915', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84798916', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84798917', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84798918', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84798923', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84798924', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84798925', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84799001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84799002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84799004', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84799005', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84799006', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84799007', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84799008', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84799009', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84799010', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84799011', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84799012', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84799013', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84799014', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84799015', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84799016', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84799017', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84799099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84803001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84803002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84803099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84804101', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84804199', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84804901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84805001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84805002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84805099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84807101', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84807102', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84807199', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84807901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84807902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84807903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84807904', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84811002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84812001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84812002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84812003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84812004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84812005', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84812006', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84812008', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84812009', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84812011', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84813002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84813003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84814001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84814002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84814003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84819001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84819002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84819003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84819004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84819099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84821001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84821003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84822002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84829101', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84829199', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84829902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84829903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84831001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84831002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84831003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84831004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84831005', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84831006', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84831007', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84831099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84833001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84833002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84833003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84833099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84834001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84834002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84834003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84834004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84834005', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84834006', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84834007', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84834008', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84834099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84835001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84835002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84835099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84839001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84839002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84839099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84849001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84849002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84862002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84862099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84869001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84869002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84869003', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84869004', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84871001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('84871099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85011001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85011002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85011003', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85011004', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85011005', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85011006', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85011007', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85011008', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85011009', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85011099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85012001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85012002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85012003', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85012004', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85012099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85013402', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85013403', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85013404', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85014001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85014002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85014003', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85014004', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85014006', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85014007', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85015101', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85015103', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85015201', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85015202', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85015203', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85015205', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85015301', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85015302', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85015303', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85015306', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85015307', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85016402', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85022002', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85030001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85030002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85030003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85030004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85030006', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85042104', 'None', '6', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85043101', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85043102', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85043105', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85043106', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85043202', 'None', '6', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85043301', 'None', '6', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85043399', 'None', '6', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85044003', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85044004', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85044010', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85044012', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85045001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85045002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85045099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85049001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85049003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85049004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85049005', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85049006', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85059001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85059002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85059003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85059004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85059005', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85059099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85061002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2013-12-13 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85061099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2013-12-13 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85072003', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85081901', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85094001', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85098001', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85098002', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85098005', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85098006', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85098008', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85098010', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85098011', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85098012', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85099001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85111001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85111002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85112001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85112002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85112003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85112099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85113001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85113002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85113003', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85113004', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85113099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85114001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85114002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85114003', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85114099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85115001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85115002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85115003', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85115004', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85115099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85119001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85119002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85119003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85119004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85119005', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85119099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85121001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85121002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85121099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85129001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85129002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85129003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85129004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85129005', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85129006', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85129099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85143002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85143003', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85143004', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85143005', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85143006', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85144001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85144099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85149001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85149002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85149003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85149099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85152901', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85158001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85158002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85158099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85161001', 'None', '6', 'None', 'GENERAL', 'None', '10.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85161099', 'None', '6', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85162901', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85166002', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85168001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85168002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85168003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85168099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85169001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85169002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85169003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85169004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85169005', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85169006', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85169008', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85169010', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85171201', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85171299', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85171801', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85176201', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85176202', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85176203', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85176204', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85176205', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85176206', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85176207', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85176208', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85176209', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85176210', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85176211', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85176212', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85176213', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85176214', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85176215', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85176216', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85176299', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85176901', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85176902', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85176903', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85176904', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85176906', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85176908', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85176909', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85176910', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85176911', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85176912', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85176918', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85176920', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2008-12-24 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85177001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85177002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85177003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85177004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85177005', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85177006', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85177007', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85177008', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85177013', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85181001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85181002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85181003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85181099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85182101', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85182199', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85182201', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85182299', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85182901', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85183001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85183002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85184003', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85184005', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85184006', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85189003', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85193001', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85193099', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85198101', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85198102', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85198111', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2008-12-24 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85198902', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85198903', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85198904', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85211001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85211099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85229001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85229002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85229003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85229004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85229005', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85229006', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85229008', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85229009', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85229010', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85229011', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85229012', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85229013', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85229014', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85232101', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85232199', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85232901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85232902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85232903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85232906', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85232910', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85234901', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85235201', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85235202', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85255001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85255002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85255003', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85255004', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85255099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85256001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85256002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85256003', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85256004', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85256006', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85256007', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85256009', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85256010', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85258001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85258002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85258003', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85258004', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85258099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85279101', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85279199', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85279901', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85284101', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85284199', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85284901', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85284902', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85284903', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85284904', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85284905', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85284908', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85284909', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85285101', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85285199', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85286101', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85286901', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85286902', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85286903', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85286904', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85291001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85291002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85291003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85291004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85291005', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85291006', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85291007', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85291008', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85291099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85299001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85299002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85299003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85299004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85299005', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85299006', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85299007', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85299008', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85299010', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85299011', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85299012', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85299014', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85299017', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85299022', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85299023', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85308001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85311005', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85318002', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85319001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85322201', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85322202', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85322203', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85322299', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85322301', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85322302', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85322303', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85322304', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85322305', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85322399', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85322401', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85322402', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85322403', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85322499', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85322501', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85322503', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85322504', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85322901', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85322902', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85322903', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85322904', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85322905', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85323001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85323002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85323003', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85323004', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85323099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85331001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85331002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85332901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85333101', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85333199', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85333901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85333902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85334001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85334002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85334003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85334004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85334005', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85334006', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85334007', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85334099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85339001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85339002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85339099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85340001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85340002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85340003', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85340099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85353001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85353002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85353003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85353004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85353005', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85353006', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85353099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85354002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85359001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85359002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85359003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85359004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85359005', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85359006', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85359007', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85359009', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85359010', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85359011', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85359012', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85359013', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85359014', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85359015', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85359016', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85359017', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85359018', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85359019', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85359021', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85359022', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85361001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85361002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85361005', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85363001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85363002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85363003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85363004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85364101', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85364102', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85364104', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85364105', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85364106', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85364107', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85364108', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85364109', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85364110', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85364111', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85364901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85364902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85364903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85364904', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85364905', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85365001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85365002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85365003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85365004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85365005', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85365006', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85365008', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85365009', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85365010', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85365011', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85365012', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85365013', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85365014', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85365015', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85365016', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85365017', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85366101', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85366102', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85366103', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85366199', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85366901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85369001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85369002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85369003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85369004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85369005', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85369006', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85369007', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85369008', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85369009', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85369010', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85369011', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85369012', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85369013', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85369014', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85369015', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85369016', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85369017', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85369018', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85369019', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85369020', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85369021', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85369022', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85369023', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85369024', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85369025', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85369026', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85369027', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85371001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85371002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85372001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85389002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85389003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85389004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85391001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85391002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85391003', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85392901', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85392902', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85392903', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85392904', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85392905', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85392906', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85392907', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85392908', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85393201', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85393202', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85393203', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85393299', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85394901', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85399001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85399002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85399003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85399004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85399005', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85399006', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85399099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85401101', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85401102', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85401103', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85401104', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85401105', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85401199', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85401201', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85401299', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85402001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85402099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85406001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85406099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85408101', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85408102', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85408199', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85408901', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85408902', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85408903', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85409101', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85409102', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85409103', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85409104', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85409199', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85409901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85409902', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85409903', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85409904', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85411099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85413099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85414001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85414002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-11-23 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85414003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-11-23 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85419001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85419099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85423101', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85423102', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85423199', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85423201', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85423299', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85423301', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85423399', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85423901', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85432001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85432002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85432003', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85432004', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85432005', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85432099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85437002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85437003', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85437004', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85437005', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85437006', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85437007', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85437009', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85437010', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85437011', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85437012', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85437013', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85437014', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85437015', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85437016', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85441901', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85443002', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85444201', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85444202', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85444203', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85444204', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85444901', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85444902', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85444903', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85444904', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85444906', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85446001', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85446099', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85459001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85459002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85471001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85471002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85471003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85471004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85471005', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85472001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85472002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85472003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85472099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85479001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85479002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85479003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85479004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85479005', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85479006', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85479007', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85479008', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85479009', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85479010', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85479011', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85479012', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85489001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85489002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('85489003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('86040001', 'None', '6', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('86040002', 'None', '6', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('86040099', 'None', '6', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('86069101', 'None', '6', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('86069199', 'None', '6', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('86071901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('86071904', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('86071905', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('86071906', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('86072101', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('86072199', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('86079101', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('86079199', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('86080001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('86080002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('86080003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('86080099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87019001', 'None', '6', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87019002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87019003', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87019004', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87019005', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87019006', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87019007', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87019008', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2008-12-24 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87019099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87021001', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87021002', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87021003', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87021004', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87029002', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87029003', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87029004', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87029005', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87029007', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2019-09-20 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87031001', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87031002', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87031099', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87039001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2019-09-20 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87039003', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-09-20 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87041001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87041099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87042102', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87042103', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87042202', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87042203', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87042204', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87042205', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87042206', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87043103', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87043202', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87043203', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87043204', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87043205', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87043206', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87060002', 'None', '6', 'None', 'GENERAL', 'None', '20.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87071001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87071099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87079001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87082914', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87082915', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87082921', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87083003', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87083005', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87083007', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87083012', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87083013', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87083014', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87084006', 'None', '6', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87085011', 'None', '6', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87085012', 'None', '6', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87085017', 'None', '6', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87085019', 'None', '6', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87085021', 'None', '6', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87087001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87087002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87087003', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87087004', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87087005', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87087007', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87089201', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87089202', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87089299', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87089301', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87089302', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87089303', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87089304', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87089399', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87089401', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87089402', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87089403', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87089404', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87089405', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87089406', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87089407', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87089408', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87089409', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87089410', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87089411', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87089499', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87089501', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87089599', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87089901', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87089902', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87089903', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87089905', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87089906', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87089910', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87089911', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87089912', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87089913', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87089914', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87091901', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87091902', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87111001', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87111002', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-30 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87111099', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87112002', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87112003', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87112004', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-30 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87112099', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-30 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87113001', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87113002', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87113003', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-30 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87113099', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87114003', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-30 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87115001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87115002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-30 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87115099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87120001', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87120002', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87120003', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87120004', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87120099', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87162001', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87162002', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87162003', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87162099', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87163101', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87163102', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87163199', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87163901', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87163902', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87163905', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87163906', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87163907', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87168001', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87168002', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87169001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('87169002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('88010001', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('88010099', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('89020001', 'None', '6', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('89020002', 'None', '6', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('89020099', 'None', '6', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90011001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90011099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90013001', 'None', '9', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90014001', 'None', '9', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90014002', 'None', '9', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90014099', 'None', '9', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90015001', 'None', '9', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90015099', 'None', '9', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90019001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90039001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90039099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90061001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90065901', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90065902', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90066901', 'None', '6', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90069101', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90069102', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90069199', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90089001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90089099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90138001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90138099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90148001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90148002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90148099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90149001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90149099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90158002', 'None', '6', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90158006', 'None', '6', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90158007', 'None', '6', 'None', 'GENERAL', 'None', '5.0', '0.0', '2016-01-06 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90160001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90160099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90171002', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90173001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90173099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90178001', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90178002', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90179001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90179099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90181901', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90181903', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90181904', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90181907', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90181908', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90181909', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90181910', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90183201', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90183901', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90183905', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90184901', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90184903', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90189021', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90189022', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90189023', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90189024', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90189026', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90189029', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90189030', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90191001', 'None', '6', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90200001', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90211001', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90211002', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90211003', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90211004', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90211005', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90211099', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90212101', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90212199', 'None', '1', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90213903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90221401', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90221499', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90222101', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90222199', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90229001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90229002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90258002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90258003', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90261001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90261002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90261003', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90261004', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90261005', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90261006', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90261099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90262001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90262002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90262005', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90262006', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90268001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90268002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90268099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90278001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90278002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90278003', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90278099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90282001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90289001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90289002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90289099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90291001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90291002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90291004', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90292001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90292002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90292003', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90292004', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90292005', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90292099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90302001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90302099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90303301', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90303302', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90303303', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90303304', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90303305', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90303306', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90303399', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90304001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90304099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90308901', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90308902', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90308903', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90308904', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90308999', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90309001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90309002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90309003', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90309004', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90309099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90312001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90312099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90314901', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90314902', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90318003', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90318004', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90318005', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90318006', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90319002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90321002', 'None', '6', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90328101', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90328199', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90329001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('90329099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('91011901', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('91040001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('91040002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('91040003', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('91040099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('91051101', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('91051199', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('91059101', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('91059199', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('91059901', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('91061001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('91061099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('91069001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('91111001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('91111099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('91122001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('91122099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('91131001', 'None', '2', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('91131099', 'None', '2', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('91132001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('91132099', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('91149001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('91149002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('92019002', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('92059001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('92099901', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('92099902', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('92099903', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('93011001', 'None', '6', 'None', 'GENERAL', 'None', '5.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('93011099', 'None', '6', 'None', 'GENERAL', 'None', '5.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('93020001', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('93020099', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('93052001', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('93052099', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('93062901', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('93063001', 'None', '11', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('93063002', 'None', '11', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('93063003', 'None', '11', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('93069001', 'None', '6', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('93069002', 'None', '6', 'None', 'GENERAL', 'None', '5.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('94015101', 'None', '6', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('94019001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('94031001', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('94031002', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('94031099', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('94032001', 'None', '6', 'None', 'GENERAL', 'None', '7.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('94032002', 'None', '6', 'None', 'GENERAL', 'None', '7.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('94032003', 'None', '6', 'None', 'GENERAL', 'None', '7.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('94032004', 'None', '6', 'None', 'GENERAL', 'None', '7.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('94032099', 'None', '6', 'None', 'GENERAL', 'None', '7.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('94037001', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('94037002', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('94037099', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('94038101', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('94042101', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('94042199', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('94049001', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('94049002', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2017-10-05 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('94049003', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('94049004', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('94049005', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('94049006', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2019-04-10 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('94051001', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('94051002', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('94051003', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('94051099', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('94052001', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('94052099', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('94055001', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('94055099', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('94060001', 'None', '1', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('95030019', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('95030036', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('95045001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('95045002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2012-06-29 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('95062901', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('95069101', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('95069102', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('95069199', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('95089001', 'None', '6', 'None', 'GENERAL', 'None', '10.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('96062901', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('96081001', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('96081099', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('96084001', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('96085001', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('96085099', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('96089101', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('96089199', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('96089901', 'None', '2', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('96089902', 'None', '1', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('96089904', 'None', '2', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('96089905', 'None', '2', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('96121001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('96121002', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('96138001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('96140002', 'None', '6', 'None', 'GENERAL', 'None', '15.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('97040001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('97040099', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('97050001', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('97050002', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('97050003', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('97050004', 'None', '6', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('99010006', 'None', '1', 'None', 'GENERAL', 'None', '16.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('99010011', 'None', '8', 'None', 'GENERAL', 'None', '77.26', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('99010012', 'None', '8', 'None', 'GENERAL', 'None', '82.17', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('99010013', 'None', '8', 'None', 'GENERAL', 'None', '114.4', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('99010015', 'None', '6', 'None', 'GENERAL', 'None', '573.48', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('99010016', 'None', '6', 'None', 'GENERAL', 'None', '373.56', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('99010017', 'None', '6', 'None', 'GENERAL', 'None', '51.73', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('99010018', 'None', '6', 'None', 'GENERAL', 'None', '51.73', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), - ('99020001', 'None', '1', 'None', 'GENERAL', 'None', '0.0', '0.0', '2007-06-18 00:00:00', 'False', 'False', 'False', 'False', 'True', '2020-12-27 00:00:00'), -] diff --git a/backend/api/v1/modules/a76/general_catalogs/fractions/historical_tariff_fractions/service.py b/backend/api/v1/modules/a76/general_catalogs/fractions/historical_tariff_fractions/service.py deleted file mode 100644 index afa14ec5..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/fractions/historical_tariff_fractions/service.py +++ /dev/null @@ -1,177 +0,0 @@ -from datetime import datetime -from decimal import Decimal -from typing import Optional, List, Tuple -from sqlalchemy import select, or_, func -from sqlalchemy.orm import Session -from .models import HistoricalTariffFraction -from .schemas import HistoricalTariffFractionResponse, HistoricalTariffFractionCreate, HistoricalTariffFractionUpdate -from api.v1.modules.sitar.fracciones.service import FraccionesService - -class HistoricalTariffFractionService: - def __init__(self, db: Session): - self.db = db - - def get(self, id: int, tenant_id: int, company_id: int) -> Optional[HistoricalTariffFraction]: - return self.db.query(HistoricalTariffFraction).filter( - HistoricalTariffFraction.id == id, - HistoricalTariffFraction.tenant_id == tenant_id, - HistoricalTariffFraction.company_id == company_id - ).first() - - def get_multi( - self, - tenant_id: int, - company_id: int, - skip: int = 0, - limit: int = 100, - historical_fraction: Optional[str] = None - ) -> Tuple[List[HistoricalTariffFraction], int]: - query = select(HistoricalTariffFraction).where( - HistoricalTariffFraction.tenant_id == tenant_id, - HistoricalTariffFraction.company_id == company_id - ) - - if historical_fraction: - query = query.where(HistoricalTariffFraction.historical_fraction.ilike(f"%{historical_fraction}%")) - - total = self.db.execute(select(func.count()).select_from(query.subquery())).scalar_one() - # Add deterministic sort order - query = query.order_by(HistoricalTariffFraction.historical_fraction) - items = self.db.scalars(query.offset(skip).limit(limit)).all() - return items, total - - def create(self, obj_in: HistoricalTariffFractionCreate, tenant_id: int, company_id: int) -> HistoricalTariffFraction: - db_obj = HistoricalTariffFraction(**obj_in.model_dump()) - db_obj.tenant_id = tenant_id - db_obj.company_id = company_id - self.db.add(db_obj) - self.db.commit() - self.db.refresh(db_obj) - return db_obj - - def update( - self, - db_obj: HistoricalTariffFraction, - obj_in: HistoricalTariffFractionUpdate - ) -> HistoricalTariffFraction: - # db_obj already validated for tenant/company in get() - update_data = obj_in.model_dump(exclude_unset=True) - for field, value in update_data.items(): - setattr(db_obj, field, value) - self.db.add(db_obj) - self.db.commit() - self.db.refresh(db_obj) - return db_obj - - def delete(self, id: int, tenant_id: int, company_id: int) -> Optional[HistoricalTariffFraction]: - obj = self.get(id, tenant_id, company_id) - if obj: - self.db.delete(obj) - self.db.commit() - return obj - async def get_historical_rate( - self, - tenant_id: int, - company_id: int, - historical_fraction: str, - nico: str, - direction: str, - tariff_type: str, - invoice_date: datetime, - is_regime_change: bool = False - ) -> Optional[Decimal]: - """ - Gets the historical tax rate based on the fraction, nico, and date. - Equivalent to Clarion BUSCA_FRACCION_HISTORICA. - """ - # Normalize input fraction to handle both 8-digit and unpadded (7-digit) versions - fraction_variants = [historical_fraction] - unpadded = historical_fraction.lstrip('0') - if unpadded and unpadded != historical_fraction: - fraction_variants.append(unpadded) - - query = select(HistoricalTariffFraction).where( - HistoricalTariffFraction.tenant_id == tenant_id, - HistoricalTariffFraction.company_id == company_id, - HistoricalTariffFraction.historical_fraction.in_(fraction_variants), - HistoricalTariffFraction.fraction_type.ilike(tariff_type), # Match GENERAL, PROSEC, etc. - or_( - HistoricalTariffFraction.nico == nico, - HistoricalTariffFraction.nico.is_(None), - HistoricalTariffFraction.nico == '' - ), - HistoricalTariffFraction.publication_date <= invoice_date - ).order_by(HistoricalTariffFraction.publication_date.desc()) - - result = self.db.execute(query).scalars().first() - - if result: - # Special rule: If it's a regime change, always return the import rate (TasaImNum) - # as per Clarion logic ASIGNA_FRACCION_HISTORICA - if is_regime_change: - return result.import_tax_rate - - # Otherwise return based on direction - if direction.lower() == 'import': - return result.import_tax_rate - else: - return result.export_tax_rate - - # 2. Priority 2: Try SITAR API (Modern source of truth) - try: - sitar_service = FraccionesService.get_instance() - # Search by 8-digit fraction and 2-digit NICO - sitar_data = await sitar_service.search( - fraccion=historical_fraction, - nico=nico, - limit=1 - ) - - if sitar_data: - first_record = sitar_data[0] - # Special rule: If it's a regime change, always return the import rate (TasaImNum) - if is_regime_change: - return first_record.ADVIMPONUM - - # Otherwise return based on direction - if direction.lower() == 'import': - return first_record.ADVIMPONUM - else: - return first_record.ADVEXPONUM - except Exception as e: - # Log error but continue to fallback - print(f"Error fetching data from SITAR API: {e}") - - # 3. Priority 3: Fallback to main TariffFraction catalog if not found in historical or SITAR - from ..tariff_fractions.models import TariffFraction - - # In the main catalog, fractions might be stored with dots (e.g. 0101.90.99) - # or as code (e.g. 01019099) - # We search primarily by fraction code (8 digits) and take the first one found. - # This handles cases where NICO doesn't match perfectly. - fallback_query = select(TariffFraction).where( - or_( - TariffFraction.code == historical_fraction, - TariffFraction.fraction == f"{historical_fraction[:4]}.{historical_fraction[4:6]}.{historical_fraction[6:8]}" - ) - ).order_by(TariffFraction.nico) # Order so we get a consistent result if multiple NICOs exist - - main_result = self.db.execute(fallback_query).scalars().first() - - if main_result: - # Apply same regime change rule to fallback if found - if is_regime_change: - rate_str = main_result.adv_impo - else: - rate_str = main_result.adv_impo if direction.lower() == 'import' else main_result.adv_expo - - if rate_str: - try: - # Remove non-numeric characters (like % or text) - import re - clean_rate = re.sub(r'[^\d.]', '', rate_str) - return Decimal(clean_rate) if clean_rate else Decimal(0) - except: - return Decimal(0) - - return None diff --git a/backend/api/v1/modules/a76/general_catalogs/fractions/previous_fractions/models.py b/backend/api/v1/modules/a76/general_catalogs/fractions/previous_fractions/models.py deleted file mode 100644 index 20bc89cf..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/fractions/previous_fractions/models.py +++ /dev/null @@ -1,24 +0,0 @@ -from typing import Optional - -from sqlalchemy import Integer, String -from sqlalchemy.orm import Mapped, mapped_column - -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base - - -class PreviousFraction(Base, TenantScopedMixin, TimestampMixin): - """ - Tabla de correlación entre fracciones anteriores y actuales. - Paridad: SFraccionesAnterioresN (Clarion SCAII). - Se usa en REVISA_UM_REGLA_OCTAVA para aceptar fracciones cambiadas - cuando el permiso de RO tiene fecha de inicio anterior al corte (~2010-05-31). - """ - - __tablename__ = "previous_fractions" - __table_args__ = {"schema": "a76"} - - id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) - - current_fraction: Mapped[Optional[str]] = mapped_column(String(50)) # FraccionActual - previous_fraction: Mapped[Optional[str]] = mapped_column(String(50)) # FraccionAnterior diff --git a/backend/api/v1/modules/a76/general_catalogs/fractions/previous_fractions/seed.py b/backend/api/v1/modules/a76/general_catalogs/fractions/previous_fractions/seed.py deleted file mode 100644 index 9c2aaa4b..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/fractions/previous_fractions/seed.py +++ /dev/null @@ -1,27112 +0,0 @@ -seed = [ - -("0101210100", 01012101 -01012902 -01012903 -01012901 -01012999 -01013001 -01019099 -01022101 -01022901 -01022902 -01022999 -01023101 -01023999 -01029099 -01031001 -01039101 -01039199 -01039102 -01039201 -01039299 -01039202 -01039203 -01041001 -01041002 -01041099 -01042001 -01042002 -01042099 -01051101 -01051101 -01051101 -01051102 -01051101 -01051102 -01051101 -01051102 -01051199 -01051201 -01051301 -01051401 -01051501 -01059401 -01059499 -01059999 -01061101 -01061199 -01061201 -01061301 -01061401 -01061902 -01061903 -01061999 -01061901 -01062001 -01062002 -01062003 -01062099 -01063101 -01063201 -01063301 -01063999 -01063901 -01063902 -01064101 -01064999 -01069002 -01069003 -01069004 -01069099 -02011001 -02012099 -02013001 -02021001 -02022099 -02023001 -02031101 -02031201 -02031201 -02031999 -02032101 -02032201 -02032201 -02032999 -02041001 -02042101 -02042299 -02042301 -02043001 -02044101 -02044299 -02044301 -02045001 -02050001 -02061001 -02062101 -02062201 -02062999 -02063001 -02063099 -02064101 -02064901 -02064999 -02068099 -02069099 -02071101 -02071201 -02071301 -02071302 -02071303 -02071399 -02071399 -02071399 -02071399 -02071402 -02071401 -02071404 -02071499 -02071499 -02071499 -02071499 -02071403 -02072401 -02072501 -02072601 -02072699 -02072602 -02072702 -02072701 -02072799 -02072703 -02074101 -02074201 -02074301 -02074401 -02074501 -02074599 -02075101 -02075201 -02075301 -02075401 -02075501 -02075599 -02076001 -02076002 -02076099 -02081001 -02083001 -02084001 -02085001 -02086001 -02089099 -02089001 -02089002 -02091001 -02099001 -02099099 -02101101 -02101201 -02101999 -02102001 -02109101 -02109201 -02109301 -02109902 -02109999 -02109901 -02109903 -03011101 -03011999 -03019101 -03019201 -03019301 -03019999 -03019401 -03019501 -03019999 -03021101 -03021301 -03021401 -03021999 -03022101 -03022201 -03022301 -03022401 -03022999 -03023101 -03023201 -03023301 -03023401 -03023501 -03023601 -03023999 -03024101 -03024201 -03024301 -03024401 -03024501 -03024601 -03024701 -03028999 -03025101 -03025201 -03025301 -03025401 -03025501 -03025601 -03025999 -03027101 -03027201 -03027301 -03028999 -03027401 -03027999 -03028101 -03028201 -03028301 -03028401 -03028501 -03028901 -03028999 -03029001 -03028101 -03023101 -03023201 -03023301 -03023401 -03023501 -03023601 -03023999 -03025401 -03028901 -03021101 -03021301 -03021401 -03021999 -03022101 -03022201 -03022301 -03022401 -03022999 -03024101 -03024201 -03024301 -03024401 -03024501 -03024601 -03024701 -03025101 -03025201 -03025301 -03025501 -03025601 -03025999 -03027101 -03027201 -03027301 -03027401 -03027999 -03028101 -03028201 -03028301 -03028401 -03028501 -03028999 -03031101 -03031201 -03031301 -03031401 -03031999 -03032301 -03032401 -03032501 -03038999 -03032601 -03032999 -03033101 -03033201 -03033301 -03033401 -03033999 -03034101 -03034201 -03034301 -03034401 -03034501 -03034601 -03034999 -03035101 -03035301 -03035401 -03035501 -03035601 -03035701 -03038999 -03036301 -03036401 -03036501 -03036601 -03036701 -03036801 -03036999 -03038101 -03038201 -03038301 -03038401 -03038901 -03038999 -03039001 -03038101 -03034101 -03034201 -03034301 -03034401 -03034501 -03034601 -03034999 -03032601 -03036601 -03038901 -03031101 -03031201 -03031301 -03031401 -03031999 -03032301 -03032401 -03032501 -03032999 -03033101 -03033201 -03033301 -03033401 -03033999 -03035101 -03035301 -03035401 -03035501 -03035601 -03035701 -03036301 -03036401 -03036501 -03036701 -03036801 -03036999 -03038101 -03038201 -03038301 -03038401 -03038999 -03043101 -03043201 -03043301 -03043999 -03044999 -03044101 -03044201 -03044301 -03044402 -03044499 -03044501 -03044601 -03044999 -03044999 -03044902 -03044903 -03044904 -03044999 -03045101 -03045999 -03045202 -03045299 -03045302 -03045399 -03045401 -03045501 -03045999 -03045999 -03045901 -03045902 -03045903 -03045999 -03046101 -03046201 -03046301 -03046999 -03048999 -03047101 -03047201 -03047301 -03047402 -03047499 -03047501 -03047999 -03048101 -03048201 -03048301 -03048401 -03048501 -03048601 -03048701 -03048999 -03048902 -03048903 -03048999 -03049101 -03049201 -03049301 -03049999 -03049401 -03049502 -03049599 -03049999 -03049999 -03049901 -03049902 -03049903 -03049904 -03049999 -03051001 -03052001 -03053101 -03053999 -03053202 -03053299 -03053901 -03053902 -03053903 -03053904 -03053905 -03053999 -03054101 -03054201 -03054301 -03054401 -03054999 -03054901 -03054902 -03054903 -03054904 -03054905 -03054906 -03054999 -03055101 -03055199 -03055999 -03055999 -03055999 -03055901 -03055902 -03055903 -03055904 -03055905 -03055906 -03055907 -03055999 -03056101 -03056201 -03056301 -03056401 -03056999 -03056901 -03056902 -03056903 -03056904 -03056905 -03056999 -03057101 -03057203 -03057204 -03057205 -03057206 -03057207 -03057208 -03057299 -03057902 -03057903 -03057904 -03057905 -03057906 -03057907 -03057999 -03061101 -03061201 -03061401 -03061501 -03061601 -03061701 -03061999 -03062101 -03062201 -03062401 -03062501 -03062601 -03062699 -03062701 -03062799 -03062901 -03062101 -03062201 -03062401 -03062501 -03062601 -03062701 -03062699 -03062799 -03062901 -03071101 -03071999 -03071999 -03072101 -03072999 -03072999 -03073101 -03073999 -03073999 -03074101 -03074199 -03079101 -03074901 -03074999 -03079999 -03074901 -03074999 -03079999 -03075101 -03075999 -03075999 -03076001 -03077101 -03077999 -03077999 -03078101 -03079101 -03078999 -03079999 -03078999 -03079999 -03079101 -03079999 -03079999 -03081101 -03081999 -03081999 -03082101 -03082999 -03082999 -03083001 -03089099 -04011001 -04011099 -04012001 -04012099 -04014001 -04014099 -04015001 -04015099 -04021001 -04021099 -04022101 -04022199 -04022999 -04029101 -04029199 -04029901 -04029999 -04031001 -04039099 -04041001 -04041099 -04049099 -04051001 -04051099 -04052001 -04059001 -04059099 -04061001 -04062001 -04063001 -04063099 -04064001 -04069001 -04069002 -04069004 -04069005 -04069003 -04069006 -04069099 -04071101 -04071101 -04071101 -04071999 -04072101 -04072199 -04072901 -04072999 -04079001 -04079099 -04081101 -04081999 -04089101 -04089199 -04089902 -04089901 -04089999 -04090001 -04100001 -04100099 -05010001 -05021001 -05029099 -05040001 -05051001 -05059099 -05061001 -05069099 -05071001 -05079099 -05079001 -05080001 -05080099 -05100001 -05100002 -05100003 -05100099 -05111001 -05119102 -05119101 -05119199 -05119901 -05119903 -05119904 -05119905 -05119908 -05119902 -05119907 -05119999 -05119906 -06011001 -06011002 -06011004 -06011099 -06011003 -06011005 -06011006 -06011007 -06011008 -06012001 -06012002 -06012003 -06012004 -06012005 -06012006 -06012007 -06012008 -06012009 -06012099 -06021001 -06021002 -06021003 -06021004 -06021005 -06021006 -06021099 -06022001 -06022002 -06022099 -06022003 -06023001 -06024001 -06024099 -06029006 -06029007 -06029001 -06029004 -06029013 -06029002 -06029003 -06029005 -06029008 -06029009 -06029010 -06029011 -06029012 -06029099 -06031101 -06031201 -06031301 -06031401 -06031499 -06031501 -06031901 -06031902 -06031903 -06031904 -06031905 -06031906 -06031907 -06031908 -06031902 -06031903 -06031904 -06031905 -06031906 -06031907 -06031999 -06039099 -06042001 -06042002 -06042003 -06042099 -06049001 -06049002 -06049003 -06049004 -06049099 -07011001 -07019099 -07020001 -07020002 -07020099 -07020099 -07020099 -07020099 -07031001 -07031099 -07032001 -07032099 -07039001 -07041001 -07041002 -07041099 -07042001 -07049001 -07049099 -07051101 -07051999 -07052101 -07052999 -07061001 -07069099 -07070001 -07081001 -07082001 -07089099 -07092001 -07092099 -07093001 -07094001 -07094099 -07095101 -07095999 -07095901 -07096001 -07096099 -07096099 -07096099 -07096099 -07096099 -07096099 -07096099 -07096099 -07097001 -07099101 -07099201 -07099301 -07099301 -07099901 -07099999 -07101001 -07102101 -07102201 -07102999 -07103001 -07104001 -07108001 -07108003 -07108004 -07108099 -07108002 -07109001 -07109099 -07112001 -07114001 -07115101 -07115999 -07119003 -07119099 -07119001 -07119002 -07122001 -07123101 -07123201 -07123301 -07123999 -07129001 -07129002 -07129003 -07129099 -07131001 -07131099 -07132001 -07133101 -07133201 -07133301 -07133302 -07133303 -07133399 -07133401 -07133501 -07133999 -07134001 -07135001 -07136001 -07139099 -07141001 -07141099 -07142001 -07142099 -07143001 -07143099 -07144001 -07144099 -07145001 -07145099 -07149002 -07149099 -07149001 -08011101 -08011201 -08011999 -08012101 -08012201 -08013101 -08013201 -08021101 -08021201 -08022101 -08022201 -08023101 -08023201 -08024101 -08024201 -08025101 -08025201 -08026101 -08026201 -08027001 -08028001 -08029001 -08029099 -08031001 -08039099 -08041001 -08041099 -08042001 -08042099 -08043001 -08044001 -08045001 -08045002 -08045003 -08051001 -08052001 -08052001 -08052001 -08054001 -08055001 -08055002 -08055099 -08059099 -08061001 -08062001 -08071101 -08071101 -08071901 -08071999 -08072001 -08081001 -08083001 -08084001 -08091001 -08092101 -08092999 -08093001 -08093002 -08094001 -08101001 -08102001 -08102001 -08102001 -08103001 -08104001 -08104001 -08105001 -08106001 -08107001 -08109099 -08111001 -08112001 -08112001 -08112001 -08119099 -08119099 -08121001 -08129001 -08129002 -08129099 -08131001 -08131099 -08132001 -08132099 -08133001 -08134001 -08134002 -08134003 -08134099 -08135001 -08140001 -09011101 -09011199 -09011201 -09012101 -09012201 -09019001 -09019099 -09021001 -09022001 -09023001 -09024001 -09030001 -09041101 -09041201 -09042101 -09042199 -09042201 -09042299 -09051001 -09052001 -09061101 -09061999 -09062001 -09071001 -09072001 -09081101 -09081201 -09082101 -09082201 -09083101 -09083201 -09092101 -09092201 -09093101 -09093201 -09096101 -09096102 -09096199 -09096201 -09096202 -09096299 -09101101 -09101201 -09102001 -09103001 -09109101 -09109901 -09109999 -09109902 -10011101 -10011999 -10019101 -10019199 -10019901 -10019999 -10021001 -10029099 -10031001 -10039001 -10039099 -10041001 -10049099 -10051001 -10059004 -10059001 -10059003 -10059099 -10059002 -10061001 -10062001 -10063001 -10063099 -10064001 -10071001 -10079001 -10079002 -10081001 -10082101 -10082999 -10083001 -10084001 -10085001 -10086001 -10089099 -11010001 -11022001 -11029001 -11029099 -11029002 -11031101 -11031301 -11031901 -11031999 -11031902 -11032001 -11032099 -11041201 -11041901 -11041999 -11042201 -11042301 -11042901 -11042999 -11043001 -11051001 -11052001 -11061001 -11062001 -11062099 -11063001 -11063099 -11071001 -11072001 -11081101 -11081201 -11081301 -11081401 -11081901 -11081999 -11082001 -11090001 -12011001 -12019001 -12019002 -12023001 -12024101 -12024201 -12030001 -12040001 -12051001 -12059099 -12060001 -12060099 -12071001 -12072101 -12072999 -12073001 -12074001 -12075001 -12076003 -12076001 -12076002 -12077001 -12079101 -12079901 -12079902 -12079999 -12081001 -12089001 -12089002 -12089099 -12091001 -12092101 -12092201 -12092301 -12092401 -12092501 -12092904 -12092902 -12092903 -12092901 -12092905 -12092999 -12093001 -12099101 -12099102 -12099103 -12099104 -12099105 -12099106 -12099107 -12099108 -12099109 -12099111 -12099112 -12099113 -12099110 -12099114 -12099199 -12099901 -12099902 -12099903 -12099904 -12099905 -12099906 -12099999 -12101001 -12102001 -12112001 -20089999 -12113001 -14049099 -12114001 -14049099 -12119099 -14049099 -20089999 -12119001 -12119003 -12119007 -12119004 -12119005 -12119006 -12119099 -14049099 -12122101 -12122102 -12122199 -12122901 -12122999 -12129101 -12129201 -12129299 -12129301 -12129401 -12129499 -12129904 -12129999 -12130001 -12141001 -12149001 -12149099 -13012001 -13019099 -13019001 -13019002 -13021199 -13021101 -13021103 -13021201 -13021299 -13021301 -13021999 -13021901 -13021903 -13021904 -13021905 -13021906 -13021907 -13021908 -13021909 -13021913 -13021914 -13021910 -13021911 -13021999 -13021912 -13022001 -13022099 -13023101 -13023201 -13023202 -13023299 -13023901 -13023902 -13023999 -13023903 -14011001 -14012001 -14019099 -14042001 -14049002 -14049003 -14049004 -14049001 -14049099 -15011001 -15012001 -15019099 -15021001 -15029099 -15030001 -15030099 -15041001 -15041099 -15042001 -15042099 -15043001 -15050002 -15050001 -15050003 -15050099 -15060001 -15060002 -15060099 -15071001 -15079099 -15081001 -15089099 -15091001 -15091099 -15099002 -15099001 -15099099 -15100099 -15111001 -15119099 -15121101 -15121999 -15122101 -15122999 -15131101 -15131999 -15132101 -15132999 -15141101 -15141999 -15149101 -15149999 -15151101 -15151999 -15152101 -15152999 -15153001 -15155001 -15159001 -15159002 -15159003 -15159004 -15159005 -15159099 -15161001 -15162001 -15171001 -15179001 -15179002 -15179099 -15180001 -15180002 -15180099 -15200001 -15211001 -15211099 -15219002 -15219003 -15219001 -15219099 -15220001 -16010001 -16010002 -16010099 -16021001 -16021099 -16022001 -16022099 -16023101 -16023201 -16023999 -16024101 -16024201 -16024901 -16024999 -16025001 -16025099 -16029099 -16030001 -16030099 -16041101 -16041201 -16041301 -16041303 -16041399 -16041404 -16041401 -16041402 -16041499 -16041403 -16041501 -16041601 -16041603 -16041699 -16041701 -16041999 -16041901 -16041902 -16041903 -16041999 -16042001 -16042002 -16042004 -16042005 -16042006 -16042007 -16042008 -16042009 -16042099 -16043101 -16043201 -16051001 -16051099 -16052101 -16052999 -16053001 -16054001 -16055101 -16055201 -16055301 -16055401 -16055999 -16055501 -16055601 -16055701 -16055801 -16055999 -16056101 -16056201 -16056301 -16056999 -17011201 -17011204 -17011301 -17011401 -17011404 -17019102 -17019103 -17019901 -17019902 -17019999 -17021101 -17021901 -17021999 -17022001 -17023001 -17024001 -17024099 -17025001 -17026001 -17026002 -17026001 -17026001 -17026002 -17026099 -17026001 -17026002 -17026099 -17029001 -17029099 -17031001 -17031002 -17039099 -17041001 -17049099 -18010001 -18020001 -18031001 -18032001 -18040001 -18050001 -18061001 -18061099 -18062001 -18063101 -18063201 -18069002 -18069099 -18069001 -19011001 -19011099 -19012002 -19012001 -19012099 -19019002 -19019003 -19019004 -19019005 -19019001 -19019099 -19021101 -19021999 -19022001 -19023099 -19024001 -19030001 -19041001 -19042001 -19043001 -19049099 -19051001 -19052001 -19053101 -19053201 -19054001 -19059001 -19059099 -19059099 -19059099 -20011001 -20019001 -20019002 -20019003 -20019099 -20021001 -20029099 -20031001 -20039099 -20041001 -20049001 -20049002 -20049099 -20051001 -20052001 -20054001 -20055101 -20055999 -20056001 -20057001 -20058001 -20059101 -20059901 -20059902 -20059999 -20060001 -20060002 -20060003 -20060099 -20071001 -20079101 -20079901 -20079902 -20079903 -20079904 -20079999 -20081101 -20081199 -20081901 -20081999 -20081999 -20081999 -20082001 -20083003 -20083007 -20083001 -20083002 -20083004 -20083005 -20083006 -20083008 -20083099 -20084001 -20085001 -20086001 -20087001 -20088001 -20089101 -20089301 -20089701 -20089901 -20089999 -20091101 -20091201 -20091299 -20091901 -20091999 -20092101 -20092999 -20093101 -20093199 -20093901 -20093999 -20094101 -20094199 -20094901 -20094999 -20095001 -20096101 -20096999 -20097101 -20097999 -20098101 -20098999 -20099001 -20099099 -21011102 -21011101 -21011199 -21011201 -21012001 -21013001 -21021002 -21021001 -21021099 -21022001 -21022099 -21023001 -21031001 -21032001 -21032099 -21033001 -21033099 -21039099 -21041001 -21042001 -21050001 -21061001 -21061099 -21061003 -21069001 -21069003 -21069004 -21069005 -21069007 -21069008 -21069009 -21069010 -21069011 -21069012 -21061004 -21069006 -21069099 -21069002 -22011001 -22011099 -22019001 -22019002 -22019099 -22021001 -22029005 -22029001 -22029002 -22029002 -22029003 -22029004 -22029099 -22030001 -22041001 -22041099 -22042101 -22042103 -22042102 -22042103 -22042199 -22042103 -22042999 -22042999 -22043099 -22051001 -22051099 -22059099 -22059001 -22060001 -22060099 -22071001 -22072001 -22082001 -22082002 -22082003 -22082099 -22083001 -22083002 -22083003 -22083004 -22083099 -22084001 -22084099 -22085001 -22086001 -22087001 -22087002 -22087099 -22089001 -22089003 -22089005 -22089004 -22089003 -22089099 -22089002 -22090001 -23011001 -23011099 -23012001 -23021001 -23023001 -23024001 -23024099 -23025001 -23031001 -23032001 -23033001 -23033099 -23040001 -23050001 -23061001 -23062001 -23063001 -23064101 -23064999 -23065001 -23066001 -23069001 -23069099 -23070001 -23080001 -23080099 -23091001 -23099004 -23099010 -23099011 -23099001 -23099002 -23099003 -23099005 -23099006 -23099007 -23099008 -23099009 -23099099 -24011001 -24011099 -24012001 -24012099 -24012002 -24013001 -24021001 -24022001 -24029099 -24031101 -24031999 -24039101 -24039199 -24039901 -24039999 -25010001 -25010099 -25010001 -25020001 -25030001 -25030099 -25041001 -25049099 -25051001 -25059099 -25061001 -25062001 -25062099 -25070001 -25081001 -25083001 -25084001 -25084099 -25085001 -25086001 -25087001 -25090001 -25101001 -25101099 -25102001 -25102099 -25111001 -25112001 -25120001 -25131001 -25131099 -25132001 -25140001 -25151101 -25151201 -25151299 -25152001 -25161101 -25161201 -25162001 -25169099 -25171001 -25172001 -25173001 -25174101 -25174999 -25181001 -25182001 -25183001 -25191001 -25199001 -25199002 -25199003 -25199099 -25201001 -25202001 -25210001 -25221001 -25222001 -25223001 -25231001 -25232101 -25232999 -25233001 -25239099 -25241001 -25241002 -25249001 -25249002 -25251001 -25252001 -25253001 -25261001 -25262001 -25280001 -25280099 -25291001 -25292101 -25292201 -25293001 -25301001 -25302001 -25309006 -25309099 -25309002 -25309003 -25309004 -25309005 -25309007 -25309008 -25309009 -26011101 -26011201 -26012001 -26020001 -26020099 -26030001 -26040001 -26050001 -26060001 -26060099 -26070001 -26080001 -26090001 -26100001 -26100099 -26110001 -26121001 -26122001 -26131001 -26139099 -26140001 -26140002 -26140099 -26151001 -26151099 -26159099 -26161001 -26169099 -26171001 -26179099 -26180001 -26190001 -26190099 -26201101 -26201999 -26202101 -26202999 -26203001 -26204001 -26204099 -26206001 -26209101 -26209199 -26209903 -26209999 -26209901 -26209902 -26211001 -26219099 -27011101 -27011201 -27011999 -27012001 -27021001 -27022001 -27030001 -27030099 -27040001 -27040002 -27050001 -27060001 -27071001 -27072001 -27073001 -27074001 -27075001 -27079101 -27079902 -27079901 -27079903 -27079999 -27081001 -27082001 -27090002 -27090003 -27090004 -27090099 -27101206 -27101201 -27101202 -27101203 -27101208 -27101209 -27101210 -27101205 -27101207 -27101291 -27101299 -27101902 -27101901 -27101903 -27101909 -27101910 -27101905 -27101906 -27101907 -27101908 -27101991 -27101999 -27102001 -27109101 -27109999 -27111101 -27111201 -27111301 -27111401 -27111901 -27111902 -27111999 -27111903 -27112101 -27112999 -27121001 -27122001 -27129001 -27129002 -27129003 -27129004 -27129099 -27131101 -27131201 -27132001 -27139099 -27141001 -27149001 -27149099 -27150001 -27150099 -27160001 -28011001 -28012001 -28013001 -28020001 -28030002 -28030099 -28030001 -28041001 -28042101 -28042901 -28042999 -28043001 -28044001 -28045001 -28046101 -28046999 -28047001 -28047002 -28047003 -28047099 -28048001 -28049001 -28051101 -28051201 -28051999 -28051901 -28053001 -28054001 -28061001 -28062001 -28070001 -28080001 -28091001 -28092001 -28092099 -28100001 -28100099 -28111101 -28111199 -28111999 -28111999 -28111901 -28112101 -28112102 -28112201 -28112202 -28112901 -28112999 -28112902 -28121099 -28121003 -28121003 -28121003 -28121002 -28121002 -28121002 -28121001 -28121002 -28121003 -28121099 -28129099 -28131001 -28139099 -28141001 -28142001 -28151101 -28151201 -28152001 -28152002 -28153001 -28161001 -28164001 -28164002 -28170001 -28170002 -28181001 -28181002 -28181099 -28182001 -28182099 -28183001 -28183002 -28191001 -28199099 -28201001 -28201099 -28209099 -28211001 -28211002 -28211003 -28212001 -28220001 -28230001 -28241001 -28249099 -28249001 -28251001 -28251099 -28252001 -28253001 -28254001 -28254099 -28255001 -28255002 -28255099 -28256001 -28257001 -28258001 -28259099 -28259001 -28261201 -28261901 -28261902 -28261999 -28263001 -28269001 -28269099 -28271001 -28272001 -28273101 -28273201 -28273501 -28273906 -28273999 -28273901 -28273902 -28273903 -28273904 -28273905 -28274101 -28274199 -28274999 -28275101 -28275999 -28276001 -28281001 -28289099 -28291101 -28291102 -28291901 -28291999 -28299001 -28299099 -28301001 -28309099 -28309001 -28309002 -28311001 -28319099 -28319001 -28321001 -28321099 -28322099 -28322001 -28323001 -28331101 -28331999 -28332101 -28332201 -28332401 -28332501 -28332502 -28332701 -28332905 -28332901 -28332904 -28332999 -28332902 -28333001 -28334001 -28334002 -28334099 -28341001 -28341099 -28342101 -28342901 -28342999 -28351001 -28352201 -28352401 -28352501 -28352599 -28352699 -28352999 -28352901 -28352902 -28352903 -28353101 -28353901 -28353902 -28353999 -28362001 -28363001 -28364001 -28364002 -28365001 -28366001 -28369101 -28369201 -28369903 -28369999 -28369901 -28369902 -28369904 -28371101 -28371199 -28371999 -28371901 -28372001 -28372099 -28391101 -28391999 -28399001 -28399099 -28399002 -28399003 -28401101 -28401999 -28402099 -28403001 -28413001 -28415001 -28415002 -28415099 -28416101 -28416999 -28417001 -28418001 -28419001 -28419099 -28421001 -28421099 -28429001 -28429099 -28431001 -28432101 -28432999 -28433001 -28439099 -28439001 -28441001 -28442001 -28443001 -28444001 -28444002 -28444099 -28445001 -28451001 -28459099 -28461001 -28469099 -28469001 -28469002 -28470001 -28491001 -28492099 -28492001 -28499099 -28500001 -28500002 -28500099 -28521001 -28521002 -28521003 -28521099 -28529099 -28529001 -28530001 -28480001 -28480002 -28480003 -28480099 -28530001 -29011001 -29011004 -29011099 -29011002 -29011003 -29012101 -29012201 -29012301 -29012401 -29012999 -29021101 -29021902 -29021999 -29021901 -29021903 -29022001 -29023001 -29024101 -29024201 -29024301 -29024401 -29025001 -29026001 -29027001 -29029099 -29029001 -29029002 -29029003 -29029004 -29029005 -29029006 -29029007 -29031101 -29031201 -29031301 -29031399 -29031401 -29031501 -29031999 -29031901 -29031902 -29031903 -29031904 -29032101 -29032201 -29032301 -29032999 -29032901 -29033101 -29033901 -29033902 -29033999 -29037101 -29037201 -29037301 -29037401 -29037501 -29037601 -29037701 -29037702 -29037703 -29037704 -29037799 -29037801 -29037999 -29037901 -29037904 -29037905 -29038203 -29038999 -29038902 -29038999 -29038901 -29039101 -29039201 -29039999 -29039999 -29039901 -29039906 -29039999 -29039902 -29039903 -29039904 -29041001 -29041002 -29041003 -29041004 -29041005 -29041099 -29042008 -29042099 -29042001 -29042002 -29042003 -29042004 -29042005 -29042006 -29042007 -29049099 -29049099 -29049099 -29049099 -29049099 -29049099 -29049007 -29049001 -29049002 -29049003 -29049004 -29049005 -29049006 -29049099 -29051101 -29051201 -29051299 -29051301 -29051401 -29051402 -29051499 -29051403 -29051602 -29051699 -29051601 -29051701 -29051906 -29051901 -29051903 -29051904 -29051908 -29051999 -29051902 -29051905 -29051907 -29052201 -29052202 -29052203 -29052299 -29052204 -29052205 -29052902 -29052999 -29052901 -29052903 -29053101 -29053201 -29053901 -29053902 -29053903 -29053999 -29053904 -29054101 -29054201 -29054301 -29054401 -29054501 -29054599 -29054901 -29054999 -29054902 -29055101 -29055999 -29055901 -29055902 -29055903 -29055904 -29055905 -29061101 -29061201 -29061299 -29061399 -29061301 -29061302 -29061903 -29061999 -29061901 -29061902 -29062101 -29062905 -29062999 -29062901 -29062902 -29062903 -29062904 -29062906 -29071101 -29071199 -29071201 -29071299 -29071302 -29071399 -29071301 -29071501 -29071502 -29071599 -29071901 -29071902 -29071906 -29071999 -29071903 -29071904 -29071905 -29071907 -29071908 -29071909 -29072101 -29072201 -29072202 -29072301 -29072302 -29072999 -29072901 -29072902 -29072903 -29072904 -29072905 -29081101 -29081999 -29081901 -29081902 -29081903 -29081904 -29081905 -29081906 -29081907 -29089101 -29089201 -29089901 -29089906 -29089908 -29089999 -29089902 -29089903 -29089904 -29089905 -29089907 -29089909 -29089910 -29089911 -29089912 -29091101 -29091901 -29091903 -29091999 -29091902 -29092001 -29093002 -29093099 -29093001 -29093003 -29093004 -29093005 -29093006 -29093007 -29093008 -29093009 -29094101 -29094301 -29094401 -29094403 -29094499 -29094402 -29094901 -29094902 -29094903 -29094904 -29094905 -29094906 -29094907 -29094908 -29094909 -29094999 -29095001 -29095004 -29095005 -29095099 -29095002 -29095003 -29096001 -29096002 -29096003 -29096004 -29096005 -29096099 -29101001 -29102001 -29103001 -29104001 -29109099 -29109002 -29110001 -29110002 -29110003 -29110099 -29121101 -29121201 -29121905 -29121902 -29121907 -29121908 -29121999 -29121901 -29121903 -29121904 -29121906 -29121909 -29121910 -29121911 -29121912 -29122101 -29122902 -29122901 -29122903 -29122904 -29122999 -29122905 -29124101 -29124201 -29124906 -29124999 -29124901 -29124902 -29124903 -29125001 -29126001 -29130001 -29130002 -29130099 -29141101 -29141201 -29141301 -29141902 -29141901 -29141999 -29141903 -29141904 -29141905 -29141906 -29142201 -29142301 -29142302 -29142901 -29142902 -29142903 -29142904 -29142999 -29143101 -29143906 -29143999 -29143901 -29143902 -29143903 -29143904 -29143905 -29143907 -29144001 -29144002 -29144099 -29145001 -29145002 -29145099 -29145003 -29146101 -29146999 -29146999 -29146901 -29146902 -29147099 -29147001 -29147002 -29147003 -29147099 -29151101 -29151201 -29151299 -29151202 -29151301 -29152101 -29152401 -29152902 -29152999 -29152901 -29153101 -29153201 -29153301 -29153601 -29153902 -29153903 -29153904 -29153905 -29153909 -29153913 -29153915 -29153917 -29153918 -29153901 -29153906 -29153999 -29153907 -29153908 -29153910 -29153911 -29153912 -29153914 -29153916 -29154001 -29154099 -29154002 -29154003 -29154004 -29155003 -29155001 -29155004 -29155099 -29155002 -29155005 -29156001 -29156003 -29156002 -29156099 -29157001 -29157002 -29157003 -29157004 -29157005 -29157006 -29157007 -29157099 -29157008 -29157009 -29159001 -29159003 -29159004 -29159005 -29159006 -29159007 -29159008 -29159009 -29159010 -29159011 -29159012 -29159015 -29159016 -29159018 -29159019 -29159020 -29159022 -29159023 -29159024 -29159025 -29159026 -29159027 -29159029 -29159030 -29159031 -29159032 -29159033 -29159014 -29159021 -29159099 -29159002 -29159013 -29159017 -29161101 -29161201 -29161202 -29161203 -29161299 -29161301 -29161401 -29161402 -29161403 -29161404 -29161499 -29161501 -29161503 -29161599 -29161502 -29161504 -29161601 -29161901 -29161903 -29161904 -29159028 -29161999 -29161902 -29161905 -29162003 -29162099 -29162001 -29162002 -29162004 -29163101 -29163102 -29163103 -29163105 -29163199 -29163104 -29163201 -29163202 -29163401 -29163908 -29163907 -29163999 -29163901 -29163902 -29163903 -29163904 -29163905 -29163906 -29171101 -29171199 -29171201 -29171301 -29171302 -29171399 -29171401 -29171904 -29171908 -29171901 -29171907 -29171999 -29171902 -29171903 -29171905 -29171906 -29172003 -29172099 -29172001 -29172002 -29173201 -29173301 -29173401 -29173499 -29173501 -29173601 -29173701 -29173901 -29173902 -29173903 -29173904 -29173905 -29173906 -29173999 -29181101 -29181201 -29181301 -29181302 -29181303 -29181304 -29181399 -29181401 -29181501 -29181502 -29181505 -29181599 -29181503 -29181504 -29181601 -29181699 -29181602 -29181999 -29181801 -29181906 -29181999 -29181901 -29181902 -29181903 -29181904 -29181905 -29181907 -29181908 -29181909 -29181910 -29182102 -29182101 -29182199 -29182201 -29182299 -29182301 -29182303 -29182399 -29182302 -29182901 -29182904 -29182908 -29182915 -29182999 -29182902 -29182903 -29182905 -29182906 -29182907 -29182909 -29182910 -29182911 -29182912 -29182913 -29182914 -29182916 -29183003 -29183006 -29183099 -29183001 -29183002 -29183004 -29183005 -29183007 -29183008 -29183009 -29189101 -29189901 -29189902 -29189904 -29189918 -29189919 -29189999 -29189903 -29189905 -29189906 -29189907 -29189908 -29189909 -29189910 -29189911 -29189912 -29189913 -29189914 -29189915 -29189916 -29189917 -29189920 -29191001 -29199010 -29199004 -29199011 -29199012 -29199099 -29199001 -29199002 -29199003 -29199005 -29199006 -29199007 -29199008 -29199009 -29201101 -29201999 -29201901 -29201902 -29209005 -29209013 -29209005 -29209005 -29209004 -29209011 -29209012 -29209099 -29209009 -29209002 -29209006 -29209007 -29209010 -29209099 -29209001 -29209008 -29211101 -29211102 -29211103 -29211199 -29211104 -29211999 -29211901 -29211999 -29211902 -29211904 -29211905 -29211911 -29211912 -29211908 -29211913 -29211999 -29211903 -29211906 -29211907 -29211909 -29211910 -29211914 -29211915 -29211916 -29211917 -29212101 -29212199 -29212201 -29212910 -29212901 -29212907 -29212999 -29212902 -29212903 -29212904 -29212905 -29212906 -29212908 -29212909 -29212911 -29213002 -29213099 -29213001 -29214101 -29214214 -29214217 -29214203 -29214204 -29214299 -29214201 -29214202 -29214205 -29214206 -29214207 -29214208 -29214209 -29214210 -29214211 -29214212 -29214213 -29214215 -29214216 -29214218 -29214301 -29214399 -29214302 -29214303 -29214304 -29214305 -29214306 -29214307 -29214308 -29214309 -29214310 -29214311 -29214312 -29214401 -29214499 -29214508 -29214599 -29214501 -29214502 -29214503 -29214504 -29214505 -29214506 -29214507 -29214601 -29214914 -29214999 -29214901 -29214902 -29214903 -29214904 -29214905 -29214906 -29214907 -29214908 -29214909 -29214910 -29214911 -29214912 -29214913 -29214915 -29215101 -29215102 -29215103 -29215104 -29215105 -29215106 -29215107 -29215108 -29215109 -29215199 -29215999 -29215901 -29215902 -29215903 -29215904 -29215905 -29215906 -29215907 -29215908 -29215909 -29215910 -29221101 -29221199 -29221201 -29221299 -29221401 -29221402 -29221499 -29221301 -29221999 -29221937 -29221999 -29221910 -29221914 -29221920 -29221940 -29221901 -29221903 -29221908 -29221921 -29221928 -29221937 -29221938 -29221399 -29221999 -29221902 -29221904 -29221905 -29221906 -29221907 -29221909 -29221911 -29221912 -29221913 -29221915 -29221916 -29221917 -29221918 -29221919 -29221922 -29221923 -29221924 -29221925 -29221926 -29221927 -29221929 -29221930 -29221931 -29221932 -29221933 -29221934 -29221935 -29221936 -29221939 -29221941 -29222101 -29222199 -29222102 -29222912 -29222999 -29222901 -29222902 -29222903 -29222904 -29222905 -29222906 -29222907 -29222908 -29222909 -29222910 -29222911 -29222913 -29222914 -29222915 -29222916 -29222917 -29222918 -29222919 -29222920 -29222921 -29222922 -29222923 -29222924 -29223101 -29223909 -29223903 -29223906 -29223999 -29223901 -29223902 -29223904 -29223905 -29223907 -29223908 -29223910 -29223911 -29224101 -29224199 -29224201 -29224299 -29224301 -29224401 -29224901 -29224902 -29224903 -29224904 -29224905 -29224906 -29224907 -29224908 -29224909 -29224910 -29224911 -29224912 -29224913 -29224914 -29224915 -29224916 -29224917 -29224918 -29224919 -29224920 -29224921 -29224922 -29224923 -29224999 -29225001 -29225005 -29225016 -29225018 -29225019 -29225022 -29225099 -29225002 -29225003 -29225004 -29225006 -29225007 -29225008 -29225009 -29225010 -29225011 -29225012 -29225013 -29225014 -29225015 -29225017 -29225020 -29225021 -29231001 -29231002 -29231099 -29232001 -29232099 -29239099 -29239099 -29239002 -29239003 -29239001 -29239012 -29239099 -29239004 -29239005 -29239006 -29239007 -29239008 -29239009 -29239010 -29239011 -29241101 -29241299 -29241201 -29241901 -29241902 -29241904 -29241905 -29241906 -29241907 -29241908 -29241909 -29241910 -29241911 -29241912 -29241913 -29241903 -29241914 -29241999 -29242106 -29242107 -29242199 -29242101 -29242102 -29242103 -29242104 -29242105 -29242301 -29242401 -29242909 -29242901 -29242913 -29242914 -29242931 -29242937 -29242947 -29242999 -29242902 -29242903 -29242904 -29242905 -29242906 -29242907 -29242908 -29242910 -29242911 -29242912 -29242915 -29242916 -29242917 -29242918 -29242919 -29242920 -29242921 -29242922 -29242923 -29242924 -29242925 -29242926 -29242927 -29242928 -29242929 -29242930 -29242933 -29242934 -29242935 -29242936 -29242938 -29242939 -29242940 -29242941 -29242942 -29242943 -29242944 -29242945 -29242946 -29242948 -29251101 -29251201 -29251903 -29251999 -29251901 -29251902 -29252101 -29252999 -29252901 -29252902 -29252903 -29252904 -29261001 -29262001 -29263001 -29269099 -29269004 -29269005 -29269009 -29269099 -29269001 -29269002 -29269003 -29269006 -29269007 -29269008 -29270003 -29270004 -29270099 -29270001 -29270002 -29270005 -29280001 -29280002 -29280003 -29280005 -29280006 -29280007 -29280099 -29280004 -29291001 -29291002 -29291004 -29291005 -29291006 -29291003 -29291099 -29299099 -29299001 -29299002 -29302005 -29302006 -29302007 -29302099 -29302001 -29302002 -29302003 -29302004 -29302008 -29302009 -29303001 -29303099 -29304001 -29309039 -29309015 -29305002 -29309011 -29309031 -29309004 -29309008 -29309012 -29309014 -29309026 -29309042 -29309045 -29309053 -29309054 -29309067 -29309068 -29309070 -29309099 -29309001 -29309002 -29309003 -29309005 -29309006 -29309007 -29309009 -29309010 -29309013 -29309016 -29309017 -29309018 -29309019 -29309020 -29309021 -29309022 -29309023 -29309024 -29309025 -29309028 -29309029 -29309030 -29309032 -29309033 -29309034 -29309035 -29309036 -29309037 -29309038 -29309039 -29309040 -29309041 -29309043 -29309044 -29309046 -29309047 -29309048 -29309049 -29309050 -29309051 -29309052 -29309055 -29309056 -29309057 -29309058 -29309059 -29309060 -29309061 -29309062 -29309063 -29309064 -29309065 -29309066 -29309069 -29309071 -29311001 -29312001 -29319002 -29319017 -29319017 -29319002 -29319017 -29319002 -29319002 -29319002 -29319001 -29319002 -29319012 -29319015 -29319016 -29319017 -29319018 -29319019 -29319020 -29319099 -29319003 -29319009 -29319099 -29319002 -29319004 -29319006 -29319007 -29319008 -29319010 -29319011 -29319012 -29319013 -29319014 -29321101 -29321201 -29321301 -29321901 -29321902 -29321901 -29321903 -29321904 -29321999 -29321905 -29322001 -29322004 -29322010 -29322099 -29322002 -29322003 -29322005 -29322006 -29322007 -29322008 -29322009 -29329101 -29329201 -29329301 -29329401 -29329501 -29329903 -29329905 -29329909 -29329911 -29329999 -29329901 -29329902 -29329904 -29329906 -29329907 -29329908 -29331101 -29331102 -29331199 -29331904 -29331908 -29331901 -29331999 -29331902 -29331903 -29331905 -29331906 -29331907 -29331909 -29332101 -29332901 -29332902 -29332909 -29332903 -29332906 -29332907 -29332910 -29332911 -29332908 -29332913 -29332915 -29332999 -29332904 -29332905 -29332912 -29332914 -29332916 -29333101 -29333102 -29333201 -29333299 -29333301 -29333302 -29333902 -29333906 -29333917 -29333922 -29333924 -29333926 -29333903 -29333904 -29333905 -29333912 -29333913 -29333925 -29333931 -29333999 -29333901 -29333907 -29333908 -29333909 -29333910 -29333911 -29333914 -29333915 -29333916 -29333918 -29333919 -29333920 -29333921 -29333923 -29333927 -29333928 -29333929 -29333930 -29334101 -29334901 -29334904 -29334910 -29334905 -29334907 -29334999 -29334902 -29334903 -29334906 -29334908 -29334909 -29334911 -29334912 -29334913 -29335201 -29335301 -29335401 -29335501 -29335502 -29335599 -29335907 -29335901 -29335914 -29335902 -29335910 -29335913 -29335903 -29335905 -29335906 -29335908 -29335918 -29335919 -29335999 -29335904 -29335909 -29335911 -29335912 -29335915 -29335916 -29335917 -29335920 -29336101 -29336912 -29336915 -29336903 -29336907 -29336909 -29336916 -29336901 -29336902 -29336906 -29336908 -29336910 -29336911 -29336999 -29336905 -29336913 -29336914 -29337101 -29337201 -29337903 -29337904 -29337999 -29337901 -29337902 -29339101 -29339102 -29339904 -29339910 -29339926 -29339908 -29339909 -29339942 -29339914 -29339918 -29339921 -29339925 -29339928 -29339930 -29339931 -29339927 -29339936 -29339919 -29339937 -29339938 -29339940 -29339946 -29339948 -29339999 -29339901 -29339902 -29339903 -29339905 -29339906 -29339907 -29339911 -29339912 -29339915 -29339916 -29339917 -29339920 -29339922 -29339923 -29339924 -29339929 -29339932 -29339933 -29339934 -29339935 -29339939 -29339941 -29339943 -29339944 -29339945 -29339947 -29341005 -29341099 -29341001 -29341002 -29341003 -29341004 -29341006 -29341007 -29341008 -29342005 -29342002 -29342003 -29342099 -29342001 -29342004 -29342006 -29343001 -29343099 -29349101 -29349901 -29349910 -29349914 -29349924 -29349941 -29349905 -29349945 -29349946 -29349999 -29349902 -29349903 -29349904 -29349906 -29349907 -29349908 -29349909 -29349911 -29349912 -29349913 -29349915 -29349916 -29349917 -29349918 -29349919 -29349920 -29349921 -29349922 -29349923 -29349925 -29349926 -29349927 -29349928 -29349929 -29349930 -29349931 -29349932 -29349933 -29349934 -29349935 -29349936 -29349937 -29349938 -29349939 -29349940 -29349942 -29349943 -29349944 -29349947 -29349948 -29349949 -29350099 -29350099 -29350099 -29350099 -29350099 -29350003 -29350007 -29350020 -29350031 -29350001 -29350002 -29350004 -29350005 -29350006 -29350008 -29350009 -29350010 -29350011 -29350012 -29350013 -29350014 -29350015 -29350016 -29350017 -29350018 -29350019 -29350021 -29350022 -29350023 -29350024 -29350025 -29350026 -29350027 -29350028 -29350029 -29350030 -29350032 -29350033 -29350034 -29350035 -29350036 -29350038 -29350099 -29362103 -29362199 -29362102 -29362201 -29362299 -29362202 -29362203 -29362301 -29362399 -29362302 -29362401 -29362402 -29362499 -29362501 -29362599 -29362601 -29362699 -29362602 -29362701 -29362799 -29362802 -29362899 -29362903 -29362904 -29362901 -29362905 -29362906 -29362999 -29362902 -29362907 -29362908 -29369099 -29369001 -29369002 -29369004 -29369005 -29371101 -29371201 -29371299 -29371905 -29371999 -29371901 -29371902 -29371903 -29371904 -29372103 -29372101 -29372102 -29372104 -29372201 -29372202 -29372204 -29372205 -29372299 -29372203 -29372206 -29372207 -29372208 -29372209 -29372210 -29372304 -29372305 -29372307 -29372308 -29372310 -29372311 -29372317 -29372318 -29372321 -29372322 -29372301 -29372302 -29372303 -29372313 -29372316 -29372319 -29372399 -29372306 -29372309 -29372312 -29372314 -29372315 -29372320 -29372323 -29372909 -29372917 -29372919 -29372924 -29372929 -29372932 -29372933 -29372936 -29372903 -29372905 -29372911 -29372916 -29372923 -29372926 -29372942 -29372999 -29372901 -29372902 -29372904 -29372906 -29372907 -29372908 -29372910 -29372912 -29372913 -29372914 -29372915 -29372918 -29372920 -29372921 -29372922 -29372925 -29372927 -29372928 -29372930 -29372931 -29372934 -29372935 -29372937 -29372938 -29372939 -29372940 -29372941 -29375001 -29375099 -29379002 -29379099 -29379001 -29379003 -29379004 -29381001 -29389007 -29389099 -29389099 -29389001 -29389002 -29389003 -29389004 -29389005 -29389006 -29391199 -29391102 -29391103 -29391104 -29391105 -29391106 -29391107 -29391108 -29391109 -29391110 -29391111 -29391112 -29391113 -29391114 -29391115 -29391116 -29391117 -29391903 -29391999 -29391901 -29391902 -29391904 -29391905 -29391906 -29392001 -29392099 -29393001 -29393003 -29393002 -29394101 -29394201 -29394301 -29394401 -29394499 -29394999 -29395101 -29395999 -29395901 -29395902 -29396101 -29396201 -29396301 -29396999 -29396901 -29399101 -29399199 -29399901 -29399902 -29399903 -29399904 -29399905 -29399906 -29399907 -29399908 -29399909 -29399910 -29399911 -29399912 -29399913 -29399914 -29399915 -29399917 -29399918 -29399919 -29399999 -29399999 -29400002 -29400099 -29400001 -29400003 -29400004 -29411003 -29411007 -29411008 -29411012 -29411002 -29411006 -29411099 -29411001 -29411004 -29411005 -29411009 -29411010 -29411011 -29411013 -29412001 -29413001 -29413099 -29413002 -29413003 -29414001 -29414002 -29414003 -29415001 -29415099 -29419017 -29419004 -29419006 -29419007 -29419009 -29419010 -29419011 -29419012 -29419013 -29419014 -29419015 -29419016 -29419099 -29419001 -29419002 -29419003 -29419005 -29419008 -29420099 -30012003 -30012004 -30012005 -30012001 -30012002 -30012099 -30019001 -30019003 -30019004 -30019005 -30019006 -30019008 -30019002 -30019099 -30019007 -30021019 -30021001 -30021004 -30021007 -30021008 -30021009 -30021002 -30021003 -30021005 -30021006 -30021012 -30021013 -30021014 -30021015 -30021016 -30021017 -30021099 -30021010 -30021018 -30021011 -30021099 -30021019 -30021099 -30021021 -30021022 -30021020 -30021099 -30021099 -30022002 -30022006 -30022007 -30022008 -30022009 -30022001 -30022003 -30022005 -30022099 -30022004 -30023002 -30023099 -30023001 -30029001 -30029099 -30029002 -30029003 -30029004 -30031001 -30032001 -30032099 -30033101 -30033199 -30033901 -30033902 -30033999 -30034099 -30034099 -30034099 -30034003 -30034004 -30034099 -30039099 -30039001 -30039002 -30039003 -30039004 -30039008 -30039009 -30039010 -30039011 -30039012 -30039013 -30039014 -30039015 -30039016 -30039017 -30039018 -30039019 -30039020 -30039021 -30039006 -30039007 -30039099 -30041001 -30041099 -30042001 -30042002 -30042003 -30042099 -30043101 -30043199 -30043201 -30043299 -30043901 -30043902 -30043903 -30043904 -30043905 -30043906 -30043999 -30044099 -30044099 -30044099 -30044003 -30044005 -30044004 -30044099 -30045001 -30045002 -30045004 -30045099 -30049099 -30049001 -30049002 -30049004 -30049005 -30049006 -30049007 -30049008 -30049009 -30049011 -30049012 -30049013 -30049014 -30049015 -30049016 -30049017 -30049018 -30049019 -30049020 -30049021 -30049022 -30049023 -30049024 -30049025 -30049026 -30049027 -30049028 -30049029 -30049030 -30049031 -30049032 -30049034 -30049035 -30049036 -30049037 -30049038 -30049039 -30049040 -30049041 -30049043 -30049044 -30049045 -30049046 -30049047 -30049048 -30049049 -30049050 -30045003 -30049003 -30049010 -30049099 -30051001 -30051002 -30051099 -30059001 -30059003 -30059002 -30059099 -30061001 -30061002 -30061099 -30062001 -30062099 -30063001 -30063002 -30063099 -30064002 -30064003 -30064001 -30064099 -30065001 -30066001 -30067001 -30069101 -30069201 -31010001 -31021001 -31022101 -31022999 -31023001 -31023099 -31024001 -31025001 -31026001 -31028001 -31029001 -31029099 -31031001 -31031001 -31039001 -31039099 -31042001 -31043001 -31043099 -31049001 -31049099 -31051001 -31052001 -31053001 -31054001 -31055101 -31055999 -31056001 -31059001 -31059099 -32011001 -32012001 -32019001 -32019099 -32021001 -32029099 -32030002 -32030001 -32030099 -32041101 -32041102 -32041104 -32041103 -32041199 -32041201 -32041202 -32041203 -32041204 -32041205 -32041206 -32041299 -32041302 -32041303 -32041304 -32041301 -32041399 -32041402 -32041403 -32041404 -32041401 -32041499 -32041501 -32041599 -32041601 -32041602 -32041603 -32041604 -32041699 -32041701 -32041702 -32041703 -32041704 -32041705 -32041706 -32041707 -32041708 -32041710 -32041709 -32041799 -32041904 -32041906 -32041901 -32041903 -32041905 -32041907 -32041909 -32041902 -32041908 -32041999 -32042001 -32042002 -32042099 -32049001 -32049002 -32049099 -32050002 -32050001 -32050099 -32061101 -32061999 -32062001 -32062002 -32062003 -32064101 -32064199 -32064201 -32064299 -32064901 -32064902 -32064903 -32064904 -32064905 -32064907 -32064908 -32064906 -32064999 -32065001 -32065002 -32065099 -32071001 -32071099 -32072001 -32072099 -32073001 -32074001 -32074002 -32074099 -32081001 -32081099 -32082001 -32082002 -32082099 -32089001 -32089099 -32091001 -32091099 -32099099 -32100001 -32100002 -32100003 -32100099 -32110001 -32110099 -32121001 -32129001 -32129002 -32129099 -32131001 -32139099 -32141001 -32141002 -32149099 -32151101 -32151102 -32151199 -32151902 -32151903 -32151901 -32151999 -32159002 -32159003 -32159001 -32159099 -33011201 -33011301 -33011399 -33011901 -33011902 -33011904 -33011905 -33011906 -33011999 -33011903 -33012401 -33012599 -33012902 -33012903 -33012906 -33012999 -33012901 -33012904 -33012905 -33012907 -33013001 -33019001 -33019004 -33019005 -33019006 -33019002 -33019003 -33019099 -33021001 -33021002 -33021099 -33029099 -33030001 -33030099 -33041001 -33042001 -33043001 -33049101 -33049901 -33049999 -33051001 -33052001 -33053001 -33059099 -33061001 -33062001 -33062099 -33069099 -33071001 -33072001 -33073001 -33074101 -33074999 -33079099 -34011101 -34011999 -34012001 -34013001 -34021102 -34021101 -34021103 -34021199 -34021203 -34021202 -34021299 -34021201 -34021301 -34021302 -34021303 -34021399 -34021999 -34022001 -34022002 -34022003 -34022004 -34022005 -34022099 -34029001 -34029002 -34029099 -34031101 -34031901 -34031999 -34039101 -34039999 -34042001 -34049001 -34049002 -34049099 -34051001 -34052001 -34052099 -34053001 -34054001 -34059001 -34059099 -34060001 -34070003 -34070004 -34070001 -34070002 -34070099 -35011001 -35019001 -35019003 -35019002 -35019099 -35021101 -35021999 -35022001 -35029099 -35030001 -35030002 -35030004 -35030003 -35030099 -35040001 -35040006 -35040002 -35040003 -35040004 -35040005 -35040099 -35051001 -35052001 -35061002 -35061001 -35061099 -35069101 -35069102 -35069103 -35069104 -35069199 -35069901 -35069999 -35071001 -35079002 -35079006 -35079008 -35079009 -35079005 -35079007 -35079012 -35079001 -35079003 -35079004 -35079010 -35079011 -35079099 -36010001 -36010099 -36020002 -36020003 -36020099 -36020001 -36030001 -36030002 -36030099 -36041001 -36049001 -36050001 -36061001 -36069001 -36069002 -36069099 -37011002 -37011001 -37011099 -37012001 -37013001 -37019101 -37019901 -37019904 -37019902 -37019905 -37019999 -37019903 -37021001 -37021099 -37023101 -37023199 -37023201 -37023299 -37023999 -37023901 -37024101 -37024199 -37024201 -37024202 -37024299 -37024301 -37024401 -37024499 -37025201 -37025301 -37025401 -37025501 -37025601 -37029601 -37029701 -37029801 -37031001 -37031099 -37032001 -37032099 -37039001 -37039002 -37039003 -37039099 -37040001 -37051001 -37059001 -37059002 -37051099 -37059099 -37061001 -37061002 -37069001 -37069099 -37071001 -37079001 -37079099 -38011001 -38011099 -38012001 -38013001 -38013099 -38019001 -38019099 -38019002 -38019003 -38019004 -38021001 -38029001 -38029002 -38029004 -38029099 -38029003 -38029005 -38030001 -38040001 -38040099 -38051001 -38051099 -38059001 -38059099 -38061001 -38061099 -38062001 -38063002 -38063099 -38063001 -38069001 -38069099 -38070001 -38089499 -38089301 -38089901 -38085099 -38089199 -38089299 -38089399 -38089999 -38089199 -38089199 -38089199 -38089105 -38089102 -38089103 -38089199 -38089201 -38089202 -38089299 -38089302 -38089303 -38089399 -38089401 -38089402 -38089499 -38089999 -38089902 -38091001 -38099101 -38099199 -38099201 -38099202 -38099203 -38099299 -38099301 -38101001 -38109001 -38109099 -38111101 -38111199 -38111999 -38112101 -38112102 -38112103 -38112104 -38112105 -38112106 -38112107 -38112199 -38112904 -38112905 -38112906 -38112999 -38112901 -38112902 -38112903 -38119001 -38119099 -38121001 -38122001 -38123004 -38123002 -38123003 -38123004 -38123005 -38123001 -38123099 -38130001 -38140001 -38151101 -38151199 -38151102 -38151202 -38151299 -38151201 -38151901 -38151999 -38159002 -38159001 -38159003 -38159099 -38160002 -38160006 -38160007 -38160001 -38160004 -38160099 -38160003 -38160005 -38170001 -38170002 -38170003 -38170099 -38170004 -38180001 -38190003 -38190001 -38190002 -38190099 -38200001 -38210001 -38220003 -38220005 -38220001 -38220004 -38220099 -38220002 -38231101 -38231201 -38231202 -38231301 -38231903 -38231901 -38231999 -38231902 -38237001 -38237099 -38241001 -38243001 -38243099 -38244001 -38244099 -38245001 -38245002 -38245003 -38245099 -38246001 -38247101 -38247201 -38247301 -38247401 -38247501 -38247601 -38247701 -38247801 -38247999 -38248101 -38248201 -38248301 -38249099 -38249099 -38249099 -38249099 -38249099 -38249099 -38249001 -38249002 -38249003 -38249006 -38249008 -38249009 -38249010 -38249011 -38249013 -38249014 -38249016 -38249018 -38249019 -38249020 -38249022 -38249023 -38249025 -38249026 -38249028 -38249029 -38249030 -38249031 -38249032 -38249033 -38249035 -38249037 -38249038 -38249040 -38249041 -38249043 -38249044 -38249045 -38249046 -38249047 -38249048 -38249049 -38249050 -38249051 -38249052 -38249053 -38249054 -38249055 -38249056 -38249058 -38249059 -38249060 -38249061 -38249062 -38249063 -38249064 -38249065 -38249066 -38249067 -38249068 -38249069 -38249070 -38249072 -38249073 -38249074 -38249075 -38249076 -38249012 -38249017 -38249057 -38249071 -38249099 -38249004 -38249005 -38249007 -38249015 -38249021 -38249024 -38249027 -38249034 -38249036 -38249039 -38249042 -38249077 -38251001 -38252001 -38253001 -38254101 -38254999 -38255001 -38256101 -38256102 -38256199 -38256999 -38256901 -38259099 -38260001 -39011001 -39011002 -39012001 -39013001 -39019099 -39019001 -39019002 -39019003 -39019099 -39021001 -39021099 -39022001 -39022099 -39023001 -39023099 -39029001 -39029099 -39031101 -39031902 -39031999 -39032001 -39033001 -39039001 -39039002 -39039003 -39039005 -39039004 -39039099 -39041001 -39041002 -39041003 -39041004 -39041099 -39042101 -39042102 -39042199 -39042201 -39043001 -39043099 -39044001 -39044099 -39045001 -39046101 -39046999 -39049099 -39051201 -39051901 -39051902 -39051903 -39051999 -39052101 -39052901 -39052902 -39052999 -39053001 -39059101 -39059901 -39059902 -39059999 -39061001 -39061099 -39069001 -39069002 -39069003 -39069004 -39069005 -39069006 -39069007 -39069008 -39069009 -39069010 -39069099 -39071001 -39071003 -39071002 -39071004 -39071099 -39072001 -39072002 -39072003 -39072004 -39072005 -39072006 -39072007 -39072099 -39073002 -39073001 -39073099 -39074001 -39074002 -39074003 -39074099 -39075002 -39075001 -39075099 -39076099 -39076001 -39076099 -39077001 -39079101 -39079199 -39079901 -39079902 -39079903 -39079904 -39079905 -39079906 -39079907 -39079908 -39079909 -39079910 -39079911 -39079999 -39081001 -39081003 -39081004 -39081005 -39081006 -39081007 -39081008 -39089001 -39089002 -39089003 -39089099 -39091001 -39092002 -39092001 -39092099 -39093099 -39093001 -39093099 -39094001 -39094003 -39094002 -39094004 -39094099 -39095004 -39095005 -39095001 -39095002 -39095003 -39095099 -39100001 -39100002 -39100003 -39100004 -39100005 -39100099 -39111001 -39119001 -39119002 -39119003 -39119004 -39119005 -39119006 -39031901 -39119099 -39121101 -39121201 -39122001 -39122099 -39123101 -39123901 -39123902 -39123903 -39123904 -39123905 -39123999 -39129001 -39129002 -39129099 -39131002 -39131004 -39131005 -39131001 -39131003 -39131099 -39139002 -39139005 -39139006 -39139004 -39139007 -39139008 -39139001 -39139003 -39139099 -39140001 -39140099 -39151001 -39152001 -39153001 -39159001 -39159002 -39159099 -39161001 -39161099 -39162001 -39162002 -39162099 -39169001 -39169002 -39169003 -39169004 -39169099 -39171002 -39171003 -39171004 -39171001 -39171099 -39172101 -39172102 -39172199 -39172201 -39172202 -39172299 -39172302 -39172303 -39172301 -39172399 -39172903 -39172901 -39172902 -39172904 -39172905 -39172999 -39173101 -39173201 -39173202 -39173299 -39173301 -39173399 -39173999 -39174001 -39181001 -39181099 -39189099 -39191001 -39199099 -39201001 -39201002 -39201003 -39201004 -39201099 -39202001 -39202002 -39202003 -39202004 -39202099 -39203001 -39203002 -39203003 -39203099 -39204301 -39204302 -39204399 -39204901 -39204902 -39204999 -39205101 -39205901 -39205999 -39206101 -39206202 -39206201 -39206299 -39206302 -39206301 -39206399 -39206999 -39207101 -39207301 -39207399 -39207901 -39207999 -39209101 -39209201 -39209299 -39209301 -39209401 -39209901 -39209999 -39211101 -39211201 -39211301 -39211399 -39211401 -39211901 -39211999 -39219009 -39219003 -39219005 -39219007 -39219008 -39219001 -39219002 -39219004 -39219006 -39219099 -39221001 -39222001 -39229099 -39231001 -39231002 -39232101 -39232901 -39232902 -39232999 -39233001 -39233099 -39234001 -39234002 -39234099 -39235001 -39239099 -39241001 -39249001 -39249099 -39251001 -39252001 -39253001 -39259099 -39261001 -39262001 -39262002 -39262099 -39263001 -39263099 -39264001 -39269001 -39269004 -39269005 -39269006 -39269007 -39269008 -39269011 -39269013 -39269014 -39269015 -39269018 -39269019 -39269020 -39269024 -39269027 -39269029 -39269002 -39269003 -39269009 -39269010 -39269016 -39269021 -39269025 -39269026 -39269031 -39269033 -39269034 -39269035 -39269012 -39269017 -39269022 -39269023 -39269028 -39269030 -39269032 -39269099 -40011001 -40012101 -40012201 -40012901 -40013001 -40013002 -40013099 -40021102 -40021101 -40021199 -40021901 -40021902 -40021903 -40021999 -40022001 -40023101 -40023199 -40023901 -40023999 -40024101 -40024901 -40024999 -40025101 -40025901 -40025902 -40025903 -40025999 -40026001 -40026099 -40027001 -40028001 -40029101 -40029102 -40029199 -40029901 -40029999 -40030001 -40040001 -40040002 -40040099 -40051001 -40052001 -40052099 -40059101 -40059102 -40059199 -40059103 -40059999 -40061001 -40069001 -40069002 -40069003 -40069004 -40069099 -40070001 -40081101 -40081901 -40081999 -40082101 -40082199 -40082901 -40082999 -40082902 -40091101 -40091199 -40091201 -40091202 -40091299 -40092101 -40092103 -40092104 -40092102 -40092202 -40092299 -40092201 -40092202 -40092203 -40092204 -40093101 -40093103 -40093102 -40093104 -40093105 -40093202 -40093203 -40093299 -40093201 -40093204 -40094101 -40094102 -40094103 -40094199 -40094201 -40094202 -40094299 -40101101 -40101199 -40101201 -40101202 -40101299 -40101902 -40101999 -40101901 -40101903 -40101904 -40103101 -40103201 -40103301 -40103401 -40103501 -40103599 -40103601 -40103699 -40103901 -40103902 -40103904 -40103999 -40103903 -40111002 -40111003 -40111004 -40111005 -40111006 -40111007 -40111008 -40111009 -40111099 -40112004 -40112005 -40112002 -40112003 -40113001 -40114001 -40115001 -40116101 -40116102 -40119201 -40116199 -40119299 -40116201 -40116301 -40116302 -40116303 -40119301 -40119401 -40116299 -40119399 -40116399 -40119499 -40119901 -40119902 -40116999 -40119999 -40121101 -40121201 -40121301 -40121999 -40122001 -40122002 -40122099 -40129002 -40129003 -40129099 -40129001 -40131001 -40132001 -40139001 -40139002 -40139003 -40139099 -40141001 -40149001 -40149002 -40149099 -40151101 -40151999 -40159001 -40159002 -40159003 -40159099 -40159004 -40161001 -40169101 -40169201 -40169299 -40169301 -40169302 -40169303 -40169399 -40169402 -40169401 -40169499 -40169501 -40169502 -40169599 -40169901 -40169904 -40169902 -40169903 -40169906 -40169907 -40169908 -40169909 -40169910 -40169999 -40169905 -40170003 -40170002 -40170099 -40170001 -41012001 -41012002 -41015001 -41015099 -41015002 -41019001 -41019099 -41019002 -41021001 -41022101 -41022999 -41032001 -41032099 -41033001 -41039001 -41039099 -41039002 -41039003 -41041101 -41041103 -41041199 -41041102 -41041903 -41041999 -41041901 -41041902 -41044101 -41044199 -41044901 -41044999 -41051003 -41051099 -41051001 -41051002 -41053001 -41062103 -41062199 -41062101 -41062102 -41062201 -41063101 -41063201 -41064001 -41064099 -41069101 -41069201 -41071101 -41071199 -41071201 -41071299 -41071999 -41071901 -41079101 -41079201 -41079999 -41079901 -41120001 -41131001 -41132001 -41133001 -41139001 -41139099 -41141001 -41142001 -41151001 -41152001 -42010001 -42021101 -42021201 -42021202 -42021999 -42022101 -42022201 -42022202 -42022999 -42023101 -42023201 -42023202 -42023999 -42029101 -42029201 -42029202 -42029203 -42029999 -42031001 -42031099 -42032101 -42032901 -42032999 -42033001 -42033099 -42034001 -42034099 -42050002 -42050001 -42050099 -42060001 -42060099 -42060002 -43011001 -43013001 -43016001 -43018001 -43018002 -43018003 -43018004 -43018005 -43018006 -43018007 -43018099 -43019001 -43021101 -43021999 -43021901 -43021902 -43021903 -43022001 -43022099 -43023001 -43023099 -43031001 -43039099 -43040001 -44011001 -44011001 -44012101 -44012201 -44013101 -44013999 -44013999 -44021001 -44029099 -44031001 -44031001 -44032099 -44032099 -44032099 -44032099 -44032099 -44032099 -44034101 -44034999 -44034901 -44034902 -44039999 -44039101 -44039201 -44039201 -44039999 -44039999 -44039999 -44039999 -44039999 -44041001 -44041099 -44042001 -44042002 -44042003 -44042004 -44042099 -44050001 -44050002 -44061001 -44061001 -44069099 -44069099 -44071001 -44071003 -44071002 -44071099 -44071001 -44071003 -44071002 -44071099 -44071003 -44071004 -44071002 -44071099 -44072102 -44072199 -44072101 -44072201 -44072299 -44072501 -44072601 -44072701 -44072801 -44072901 -44072902 -44072903 -44072999 -44079999 -44079101 -44079201 -44079299 -44079301 -44079401 -44079501 -44079599 -44079502 -44079902 -44079999 -44079901 -44079902 -44079999 -44079903 -44081001 -44081002 -44083101 -44083999 -44083901 -44089099 -44089001 -44089099 -44091002 -44091001 -44091099 -44092101 -44092102 -44092199 -44092999 -44092901 -44092999 -44092902 -44101101 -44101102 -44101103 -44101199 -44101201 -44101299 -44101903 -44101999 -44101901 -44101902 -44109001 -44109099 -44111201 -44111202 -44111203 -44111204 -44111205 -44111299 -44111301 -44111302 -44111303 -44111399 -44111304 -44111305 -44111401 -44111403 -44111402 -44111404 -44111499 -44111405 -44119201 -44119299 -44119301 -44119302 -44119303 -44119399 -44119401 -44119402 -44119403 -44119499 -44121001 -44123101 -44123199 -44123299 -44123201 -44123299 -44123901 -44123999 -44129401 -44129499 -44129402 -44129901 -44129902 -44129999 -44130001 -44130002 -44130099 -44140001 -44151001 -44152001 -44152099 -44160001 -44160005 -44160099 -44160002 -44160003 -44160004 -44170001 -44170099 -44181001 -44182001 -44184001 -44185001 -44186001 -44187202 -44187101 -44187999 -44187101 -44187299 -44187999 -44187901 -44189099 -44189001 -44189099 -44190001 -44190001 -44190001 -44190001 -44201001 -44209099 -44211001 -44219001 -44219002 -44219004 -44219003 -44219099 -44219001 -44219002 -44219004 -44219003 -44219099 -45011001 -45019099 -45020001 -45031001 -45039099 -45041002 -45041099 -45041001 -45041003 -45049099 -46012101 -46012201 -46012999 -46012901 -46019201 -46019299 -46019301 -46019399 -46019401 -46019499 -46019901 -46019999 -46021101 -46021201 -46021999 -46029099 -47010001 -47020001 -47020099 -47031102 -47031101 -47031199 -47031901 -47031902 -47032101 -47032102 -47032901 -47032902 -47041101 -47041901 -47042101 -47042901 -47050001 -47061001 -47062001 -47063001 -47063002 -47063003 -47069101 -47069201 -47069301 -47071001 -47072001 -47073001 -47079001 -48010001 -48026199 -48026299 -48021001 -48022001 -48022002 -48022099 -48024001 -48025404 -48025401 -48025402 -48025403 -48025405 -48025406 -48025407 -48025499 -48025502 -48025503 -48025501 -48025599 -48025602 -48025601 -48025699 -48025701 -48025799 -48025801 -48025802 -48025803 -48025899 -48026101 -48026199 -48026102 -48026201 -48026299 -48026999 -48026901 -48030001 -48030099 -48030002 -48030003 -48041101 -48041901 -48041902 -48041999 -48042101 -48042999 -48043104 -48043199 -48043101 -48043102 -48043103 -48043901 -48043999 -48043902 -48043903 -48044101 -48044201 -48044999 -48044901 -48044902 -48045101 -48045199 -48045201 -48045901 -48045999 -48045902 -48051101 -48051201 -48051999 -48052401 -48052499 -48052501 -48052599 -48053001 -48054001 -48055001 -48059101 -48059201 -48059301 -48061001 -48062001 -48063001 -48064001 -48070001 -48070002 -48070099 -48081001 -48084001 -48084002 -48084003 -48084099 -48089001 -48089099 -48092001 -48099002 -48099001 -48099099 -48101301 -48101399 -48101302 -48101303 -48101304 -48101305 -48101306 -48101401 -48101499 -48101402 -48101403 -48101404 -48101405 -48101406 -48101999 -48102201 -48102299 -48102901 -48102999 -48103101 -48103102 -48103103 -48103199 -48103104 -48103201 -48103999 -48109201 -48109999 -48109901 -48111002 -48111001 -48111099 -48114101 -48114102 -48114901 -48114999 -48115101 -48115102 -48115104 -48115105 -48115199 -48115103 -48115106 -48115907 -48115901 -48115904 -48115906 -48115999 -48115902 -48115903 -48115905 -48116001 -48116099 -48116002 -48119010 -48119002 -48119005 -48119007 -48119099 -48119001 -48119003 -48119004 -48119006 -48119008 -48119009 -48120001 -48131001 -48132001 -48139099 -48142001 -48149002 -48149099 -48149001 -48162001 -48169001 -48169099 -48169002 -48171001 -48172001 -48173001 -48181001 -48182001 -48183001 -48185001 -48189099 -48191001 -48192001 -48192099 -48193001 -48194001 -48195001 -48196001 -48201001 -48201099 -48202001 -48203001 -48204001 -48205001 -48209099 -48211001 -48219099 -48221001 -48229099 -48232001 -48232099 -48234001 -48236101 -48236999 -48237003 -48237001 -48237099 -48237002 -48239001 -48239002 -48239005 -48239010 -48239011 -48239012 -48239017 -48239007 -48239015 -48239016 -48239099 -48239003 -48239004 -48239006 -48239008 -48239009 -48239013 -48239014 -49011001 -49011099 -49019101 -49019102 -49019199 -49019103 -49019901 -49019902 -49019903 -49019904 -49019905 -49019906 -49019999 -49021001 -49021099 -49029001 -49029099 -49030001 -49030099 -49040001 -49051001 -49059101 -49059199 -49059901 -49059999 -49060001 -49070001 -49070002 -49070099 -49081001 -49081099 -49089002 -49089003 -49089001 -49089004 -49089099 -49090001 -49100001 -49111001 -49111002 -49111003 -49111004 -49111099 -49119101 -49119103 -49119102 -49119104 -49119199 -49119901 -49119906 -49119902 -49119903 -49119905 -49119999 -49119904 -50010001 -50020001 -50030001 -50030099 -50040001 -50050001 -50060001 -50071001 -50072001 -50079001 -51011101 -51011199 -51011901 -51011999 -51012101 -51012199 -51012901 -51012999 -51013001 -51013099 -51021101 -51021901 -51021902 -51021999 -51022001 -51022099 -51031001 -51031002 -51031099 -51032001 -51032002 -51032099 -51033001 -51040001 -51051001 -51052101 -51052901 -51052999 -51053101 -51053901 -51053902 -51053999 -51054001 -51061001 -51062001 -51071001 -51072001 -51081001 -51082001 -51091001 -51099099 -51100001 -51111101 -51111199 -51111901 -51111999 -51112001 -51112099 -51113001 -51113099 -51119099 -51121101 -51121199 -51121901 -51121902 -51121999 -51122001 -51122099 -51123001 -51123002 -51123099 -51129099 -51130001 -51130099 -52010001 -52010002 -52010099 -52021001 -52029101 -52029901 -52029999 -52030001 -52041101 -52041999 -52042001 -52051101 -52051201 -52051301 -52051401 -52051501 -52052101 -52052201 -52052301 -52052401 -52052601 -52052701 -52052801 -52053101 -52053201 -52053301 -52053401 -52053501 -52054101 -52054201 -52054301 -52054401 -52054601 -52054701 -52054801 -52061101 -52061201 -52061301 -52061401 -52061501 -52062101 -52062201 -52062301 -52062401 -52062501 -52063101 -52063201 -52063301 -52063401 -52063501 -52064101 -52064201 -52064301 -52064401 -52064501 -52071001 -52079099 -52081101 -52081201 -52081301 -52081901 -52081902 -52081999 -52082101 -52082201 -52082301 -52082901 -52082999 -52083101 -52083201 -52083301 -52083901 -52083999 -52084101 -52084201 -52084301 -52084901 -52085101 -52085201 -52085901 -52085902 -52085999 -52091101 -52091201 -52091901 -52091999 -52092101 -52092201 -52092901 -52092999 -52093101 -52093201 -52093901 -52093999 -52094101 -52094202 -52094203 -52094291 -52094292 -52094301 -52094901 -52095101 -52095201 -52095901 -52095999 -52101101 -52101199 -52101901 -52101902 -52101999 -52102101 -52102902 -52102901 -52102999 -52103101 -52103201 -52103901 -52103999 -52104101 -52104901 -52104999 -52105101 -52105901 -52105902 -52105999 -52111101 -52111199 -52111201 -52111901 -52111999 -52112001 -52112002 -52112003 -52112099 -52113101 -52113201 -52113901 -52113999 -52114101 -52114202 -52114203 -52114291 -52114292 -52114301 -52114901 -52115101 -52115201 -52115901 -52115999 -52121101 -52121201 -52121301 -52121401 -52121501 -52122101 -52122201 -52122301 -52122401 -52122499 -52122501 -53011001 -53012101 -53012999 -53013001 -53021001 -53029099 -53031001 -53039099 -53050001 -53050002 -53050005 -53050003 -53050004 -53050006 -53050007 -53050099 -53061001 -53062001 -53071001 -53072001 -53081001 -53082001 -53089001 -53089002 -53089099 -53091101 -53091999 -53092101 -53092999 -53101001 -53109099 -53110001 -53110099 -54011001 -54012001 -54021101 -54021901 -54021999 -54022001 -54022099 -54023101 -54023201 -54023301 -54023401 -54023999 -54023901 -54024401 -54024499 -54024503 -54024501 -54024502 -54024504 -54024599 -54024601 -54024702 -54024799 -54024701 -54024801 -54024802 -54024899 -54024901 -54024902 -54024999 -54024903 -54024904 -54024905 -54025101 -54025199 -54025201 -54025202 -54025299 -54025999 -54025999 -54025901 -54025902 -54025903 -54025904 -54025905 -54026101 -54026199 -54026201 -54026299 -54026999 -54026901 -54026999 -54026902 -54026903 -54026904 -54026905 -54031001 -54033101 -54033102 -54033201 -54033202 -54033301 -54033999 -54033901 -54034101 -54034102 -54034201 -54034999 -54034901 -54041101 -54041199 -54041201 -54041299 -54041901 -54041902 -54041999 -54041903 -54049099 -54050001 -54050002 -54050003 -54050004 -54050099 -54060001 -54060002 -54060004 -54060003 -54060005 -54071001 -54071002 -54071099 -54072001 -54072099 -54073001 -54073002 -54073003 -54073099 -54074102 -54074103 -54074104 -54074202 -54074203 -54074204 -54074301 -54074302 -54074303 -54074399 -54074401 -54075102 -54075103 -54075104 -54075202 -54075203 -54075204 -54075301 -54075302 -54075303 -54075391 -54075392 -54075393 -54075402 -54075403 -54075404 -54076101 -54076103 -54076104 -54076105 -54076191 -54076192 -54076193 -54076901 -54076991 -54076992 -54076993 -54077101 -54077201 -54077301 -54077399 -54077302 -54077303 -54077401 -54078101 -54078201 -54078299 -54078202 -54078203 -54078301 -54078401 -54079101 -54079102 -54079103 -54079104 -54079105 -54079106 -54079107 -54079199 -54079201 -54079202 -54079203 -54079204 -54079205 -54079206 -54079299 -54079301 -54079302 -54079303 -54079304 -54079305 -54079306 -54079307 -54079399 -54079401 -54079402 -54079403 -54079404 -54079405 -54079406 -54079407 -54079499 -54081001 -54081002 -54081003 -54081004 -54081099 -54082101 -54082102 -54082103 -54082199 -54082204 -54082299 -54082201 -54082202 -54082203 -54082301 -54082302 -54082303 -54082304 -54082305 -54082399 -54082401 -54082499 -54083101 -54083102 -54083103 -54083104 -54083199 -54083201 -54083202 -54083203 -54083204 -54083205 -54083299 -54083301 -54083302 -54083303 -54083304 -54083399 -54083401 -54083402 -54083403 -54083499 -55011001 -55012002 -55012099 -55012001 -55012003 -55013001 -55014001 -55019099 -55020099 -55020001 -55020099 -55031101 -55031999 -55032003 -55032001 -55032002 -55032099 -55033001 -55034001 -55034099 -55039001 -55039099 -55041001 -55041099 -55049099 -55051001 -55052001 -55061001 -55062001 -55063001 -55069099 -55069099 -55070001 -55081001 -55082001 -55091101 -55091201 -55092101 -55092201 -55093101 -55093201 -55094101 -55094201 -55095101 -55095201 -55095301 -55095999 -55096101 -55096201 -55096999 -55099101 -55099201 -55099999 -55101101 -55101201 -55102001 -55103001 -55109001 -55111001 -55112001 -55113001 -55121102 -55121103 -55121104 -55121901 -55121991 -55121992 -55121993 -55122101 -55122999 -55129101 -55129999 -55131102 -55131103 -55131202 -55131299 -55131302 -55131399 -55131902 -55131999 -55132102 -55132103 -55132303 -55132304 -55132391 -55132399 -55132902 -55132999 -55133102 -55133199 -55133904 -55133905 -55133906 -55133991 -55133999 -55134102 -55134199 -55134904 -55134905 -55134991 -55134902 -55134999 -55141101 -55141201 -55141901 -55141999 -55142101 -55142201 -55142301 -55142901 -55143004 -55143099 -55143001 -55143002 -55143003 -55144101 -55144201 -55144301 -55144901 -55151102 -55151103 -55151199 -55151202 -55151203 -55151299 -55151301 -55151399 -55151901 -55151902 -55151999 -55152102 -55152103 -55152199 -55152201 -55152299 -55152901 -55152902 -55152999 -55159102 -55159103 -55159199 -55159901 -55159902 -55159903 -55159904 -55159999 -55161101 -55161201 -55161301 -55161401 -55162101 -55162201 -55162301 -55162401 -55163101 -55163199 -55163201 -55163299 -55163301 -55163399 -55163401 -55163499 -55164101 -55164201 -55164301 -55164401 -55169101 -55169201 -55169301 -55169401 -56012101 -56012199 -56012201 -56012299 -56012999 -56013001 -56013099 -56021001 -56021099 -56022101 -56022102 -56022199 -56022901 -56029099 -56031101 -56031201 -56031299 -56031301 -56031399 -56031401 -56039101 -56039201 -56039301 -56039401 -56041001 -56049001 -56049002 -56049004 -56049005 -56049006 -56049007 -56049008 -56049009 -56049010 -56049011 -56049012 -56049013 -56049014 -56049003 -56049099 -56050001 -56060001 -56060002 -56060099 -56072101 -56072999 -56074101 -56074999 -56075001 -56079002 -56079001 -56079099 -56081101 -56081199 -56081999 -56089099 -56090001 -56090099 -57011001 -57019001 -57021001 -57022001 -57023101 -57023201 -57023901 -57024101 -57024201 -57024901 -57025001 -57025002 -57025099 -57029101 -57029201 -57029901 -57031001 -57032001 -57032099 -57033001 -57033099 -57039001 -57041001 -57049099 -57049099 -57050001 -57050099 -58011001 -58012101 -58012201 -58012301 -58012601 -58012701 -58013101 -58013201 -58013301 -58013601 -58013701 -58019001 -58021101 -58021999 -58022001 -58023001 -58030001 -58030002 -58030003 -58030004 -58030099 -58041001 -58042101 -58042901 -58043001 -58050001 -58061001 -58061099 -58062001 -58062099 -58063101 -58063201 -58063901 -58063999 -58064001 -58064099 -58071001 -58079099 -58081001 -58089099 -58090001 -58101001 -58109101 -58109201 -58109901 -58110001 -59011001 -59019001 -59019002 -59019099 -59021001 -59022001 -59029099 -59031001 -59031099 -59032001 -59032099 -59039001 -59039003 -59039004 -59039005 -59039099 -59041001 -59049001 -59049002 -59050001 -59061001 -59069101 -59069199 -59069901 -59069902 -59069903 -59069999 -59070001 -59070005 -59070006 -59070002 -59070003 -59070004 -59070099 -59080003 -59080001 -59080002 -59080099 -59090001 -59100001 -59111001 -59111099 -59112001 -59113101 -59113201 -59114001 -59119001 -59119002 -59119003 -59119099 -60011002 -60011099 -60012102 -60012199 -60012202 -60012203 -60012299 -60012901 -60012902 -60012999 -60019102 -60019199 -60019202 -60019203 -60019299 -60019901 -60024001 -60024002 -60024003 -60024004 -60024099 -60029001 -60029002 -60029003 -60029004 -60029099 -60031001 -60032001 -60033001 -60034001 -60039001 -60039099 -60041001 -60041002 -60041003 -60041004 -60041005 -60041006 -60041099 -60049001 -60049099 -60052101 -60052201 -60052301 -60052401 -60053199 -60053299 -60053301 -60053499 -60053102 -60053199 -60053201 -60053299 -60053301 -60053402 -60053499 -60054101 -60054201 -60054301 -60054401 -60059001 -60059099 -60061001 -60062101 -60062199 -60062201 -60062299 -60062301 -60062399 -60062401 -60062499 -60063102 -60063199 -60063202 -60063299 -60063302 -60063399 -60063402 -60063499 -60064101 -60064201 -60064301 -60064401 -60069099 -61012002 -61012099 -61013001 -61013003 -61013091 -61013092 -61019001 -61019099 -61021001 -61022002 -61022099 -61023001 -61023002 -61023003 -61023099 -61029001 -61031001 -61031002 -61031003 -61031004 -61031099 -61032201 -61032301 -61032901 -61032999 -61033101 -61033201 -61033301 -61033399 -61033901 -61033902 -61033999 -61034101 -61034201 -61034202 -61034204 -61034291 -61034292 -61034301 -61034302 -61034303 -61034304 -61034305 -61034391 -61034392 -61034901 -61034902 -61034999 -61041301 -61041399 -61041901 -61041902 -61041903 -61041904 -61041905 -61041999 -61042201 -61042301 -61042901 -61042999 -61043101 -61043201 -61043301 -61043399 -61043901 -61043902 -61043999 -61044101 -61044202 -61044299 -61044301 -61044391 -61044392 -61044401 -61044403 -61044499 -61044901 -61044999 -61045101 -61045202 -61045299 -61045301 -61045391 -61045392 -61045901 -61045902 -61045999 -61046101 -61046201 -61046202 -61046206 -61046291 -61046292 -61046301 -61046302 -61046303 -61046304 -61046391 -61046392 -61046901 -61046902 -61046999 -61051003 -61051004 -61051099 -61052002 -61052099 -61059001 -61059099 -61061003 -61061004 -61061091 -61061092 -61062001 -61062091 -61062092 -61069001 -61069002 -61069099 -61071102 -61071199 -61071202 -61071299 -61071901 -61072102 -61072199 -61072202 -61072299 -61072901 -61072999 -61079101 -61079901 -61079902 -61079999 -61081101 -61081901 -61082102 -61082199 -61082202 -61082299 -61082901 -61083102 -61083199 -61083202 -61083299 -61083901 -61083999 -61089101 -61089199 -61089201 -61089299 -61089901 -61089999 -61091002 -61091099 -61099003 -61099091 -61099002 -61099092 -61099093 -61101102 -61101199 -61101202 -61101299 -61101901 -61101999 -61102002 -61102003 -61102004 -61102091 -61102092 -61102093 -61102094 -61102099 -61103004 -61103005 -61103003 -61103006 -61103007 -61103091 -61103092 -61103093 -61103099 -61109001 -61109003 -61109004 -61109005 -61109006 -61109091 -61109099 -61112004 -61112005 -61112007 -61112008 -61112009 -61112011 -61112013 -61112014 -61112015 -61112016 -61112017 -61112018 -61112019 -61112020 -61112091 -61112092 -61112099 -61113005 -61113008 -61113009 -61113010 -61113011 -61113012 -61113013 -61113014 -61113015 -61113016 -61113017 -61113018 -61113019 -61113020 -61113091 -61113092 -61113093 -61113099 -61119001 -61119002 -61119003 -61119004 -61119099 -61121101 -61121201 -61121901 -61121902 -61121999 -61122001 -61122099 -61123101 -61123901 -61124101 -61124901 -61130001 -61130099 -61142001 -61143001 -61143099 -61149001 -61149099 -61151001 -61152101 -61152201 -61152901 -61153001 -61159401 -61159501 -61159601 -61159901 -61161001 -61161099 -61169101 -61169201 -61169301 -61169901 -61171001 -61171099 -61178002 -61178001 -61178099 -61179001 -62011101 -62011201 -62011291 -62011292 -62011301 -62011302 -62011391 -62011392 -62011901 -62019101 -62019201 -62019291 -62019292 -62019301 -62019399 -62019901 -62021101 -62021201 -62021291 -62021292 -62021301 -62021302 -62021391 -62021392 -62021901 -62029101 -62029201 -62029291 -62029292 -62029301 -62029391 -62029392 -62029901 -62031101 -62031201 -62031901 -62031902 -62031999 -62032201 -62032301 -62032901 -62032999 -62033101 -62033202 -62033299 -62033301 -62033391 -62033392 -62033901 -62033902 -62033903 -62033999 -62034101 -62034201 -62034202 -62034210 -62034208 -62034294 -62034296 -62034291 -62034207 -62034209 -62034293 -62034295 -62034292 -62034301 -62034304 -62034308 -62034309 -62034393 -62034394 -62034391 -62034305 -62034310 -62034311 -62034395 -62034396 -62034392 -62034901 -62041101 -62041201 -62041301 -62041399 -62041901 -62041902 -62041903 -62041999 -62042101 -62042201 -62042301 -62042901 -62043101 -62043202 -62043299 -62043301 -62043302 -62043391 -62043392 -62043393 -62043901 -62043902 -62043903 -62043999 -62044101 -62044201 -62044291 -62044292 -62044301 -62044302 -62044303 -62044391 -62044392 -62044401 -62044402 -62044491 -62044492 -62044901 -62044999 -62045101 -62045202 -62045299 -62045301 -62045302 -62045391 -62045392 -62045901 -62045902 -62045903 -62045905 -62045904 -62045999 -62046101 -62046206 -62046207 -62046208 -62046210 -62046293 -62046294 -62046295 -62046296 -62046291 -62046292 -62046301 -62046304 -62046308 -62046310 -62046393 -62046395 -62046391 -62046309 -62046305 -62046307 -62046394 -62046392 -62046902 -62046903 -62046904 -62046905 -62046999 -62052001 -62052091 -62052092 -62053001 -62053091 -62053092 -62059001 -62059002 -62059099 -62061001 -62062001 -62062099 -62063002 -62063003 -62064001 -62064002 -62064091 -62064092 -62069001 -62069099 -62071102 -62071199 -62071901 -62072102 -62072199 -62072201 -62072901 -62072999 -62079101 -62079901 -62079902 -62079999 -62081101 -62081901 -62082102 -62082199 -62082202 -62082299 -62082901 -62082999 -62089101 -62089201 -62089203 -62089291 -62089901 -62089902 -62089999 -62092004 -62092005 -62092012 -62092008 -62092009 -62092010 -62092011 -62092013 -62092014 -62092015 -62092091 -62092092 -62092099 -62093004 -62093006 -62093007 -62093008 -62093009 -62093010 -62093091 -62093092 -62093099 -62099001 -62099002 -62099003 -62099004 -62099099 -62101001 -62102001 -62103001 -62104001 -62105001 -62111101 -62111201 -62112001 -62112099 -62113201 -62113299 -62113301 -62113399 -62113901 -62113902 -62113999 -62114201 -62114299 -62114301 -62114399 -62114901 -62121002 -62121004 -62121008 -62121003 -62121005 -62121006 -62122001 -62123001 -62129001 -62129002 -62129099 -62132001 -62139001 -62139003 -62139099 -62141001 -62142001 -62143001 -62144001 -62149001 -62151001 -62152001 -62159001 -62160001 -62171002 -62171099 -62179001 -63011001 -63012001 -63013001 -63014001 -63019001 -63021001 -63022102 -63022103 -63022104 -63022105 -63022106 -63022107 -63022108 -63022109 -63022199 -63022202 -63022203 -63022204 -63022205 -63022206 -63022207 -63022208 -63022209 -63022299 -63022901 -63023107 -63023108 -63023109 -63023110 -63023111 -63023112 -63023113 -63023114 -63023199 -63023207 -63023208 -63023209 -63023210 -63023211 -63023212 -63023213 -63023214 -63023299 -63023901 -63024001 -63025101 -63025301 -63025901 -63025999 -63026002 -63026003 -63026004 -63026005 -63026099 -63029101 -63029301 -63029901 -63029999 -63031201 -63031901 -63031999 -63039101 -63039201 -63039299 -63039901 -63041101 -63041999 -63049101 -63049101 -63049201 -63049301 -63049901 -63051001 -63052001 -63053201 -63053301 -63053999 -63059001 -63061201 -63061901 -63061999 -63062201 -63062901 -63062999 -63063001 -63063099 -63064001 -63064099 -63069001 -63069099 -63071001 -63072001 -63079001 -63079099 -63080001 -63090001 -63101001 -63101099 -63109001 -63109099 -64011002 -64011099 -64019202 -64019203 -64019204 -64019205 -64019206 -64019207 -64019208 -64019209 -64019210 -64019901 -64019902 -64019903 -64019904 -64019905 -64019906 -64019907 -64019908 -64021201 -64021901 -64021902 -64021903 -64021904 -64021907 -64021905 -64021908 -64021906 -64021909 -64022002 -64022003 -64029102 -64029103 -64029104 -64029105 -64029107 -64029924 -64029925 -64029926 -64029927 -64029928 -64029929 -64029907 -64029908 -64029909 -64029910 -64029911 -64029912 -64029913 -64029914 -64029930 -64029931 -64029916 -64029917 -64029918 -64029903 -64029904 -64029922 -64029923 -64029932 -64031201 -64031902 -64031901 -64031903 -64031904 -64031991 -64031999 -64032001 -64034002 -64034003 -64034004 -64035101 -64035102 -64035103 -64035104 -64035901 -64035905 -64035906 -64035907 -64035902 -64035903 -64035904 -64039104 -64039101 -64039108 -64039105 -64039106 -64039107 -64039109 -64039110 -64039111 -64039901 -64039906 -64039912 -64039907 -64039908 -64039909 -64039910 -64039911 -64039905 -64039903 -64039904 -64041109 -64041120 -64041121 -64041107 -64041108 -64041110 -64041111 -64041104 -64041105 -64041118 -64041119 -64041113 -64041114 -64041115 -64041122 -64041902 -64041908 -64041916 -64041901 -64041913 -64041914 -64041904 -64041905 -64041906 -64041907 -64041909 -64041915 -64041917 -64041910 -64041911 -64041912 -64041991 -64042002 -64042003 -64042099 -64051001 -64052001 -64052002 -64052003 -64052004 -64052005 -64052091 -64059001 -64059002 -64059099 -64061001 -64061002 -64061005 -64061004 -64061006 -64061099 -64061003 -64061007 -64061099 -64062001 -64062002 -64069001 -64069002 -64069099 -65010001 -65020001 -65040001 -65050001 -65050002 -65050003 -65050099 -65061001 -65069101 -65069199 -65069901 -65069999 -65070001 -66011001 -66019101 -66019999 -66020001 -66032001 -66032099 -66039099 -66039001 -67010001 -67010099 -67021001 -67029001 -67030001 -67041101 -67041999 -67042001 -67049001 -68010001 -68021001 -68021099 -68022101 -68022301 -68022399 -68022901 -68022999 -68029101 -68029201 -68029301 -68029901 -68030001 -68030099 -68041001 -68041099 -68042101 -68042199 -68042201 -68042202 -68042203 -68042299 -68042301 -68042399 -68043001 -68051001 -68051099 -68052001 -68053001 -68061001 -68062001 -68069099 -68071001 -68079099 -68080001 -68091101 -68091999 -68099001 -68101101 -68101999 -68109101 -68109199 -68109999 -68109901 -68114003 -68114004 -68114001 -68114002 -68114099 -68118101 -68118201 -68118902 -68118999 -68128001 -68128005 -68128006 -68128008 -68128099 -68128002 -68128003 -68128004 -68128007 -68129101 -68129201 -68129301 -68129399 -68129902 -68129903 -68129905 -68129999 -68129901 -68129904 -68132002 -68132099 -68132001 -68132003 -68132004 -68138101 -68138199 -68138901 -68138999 -68138902 -68141001 -68141099 -68149099 -68151001 -68151002 -68151003 -68151004 -68151099 -68152001 -68159101 -68159199 -68159901 -68159999 -69010001 -69021001 -69022001 -69029099 -69029001 -69029002 -69029003 -69031002 -69031099 -69031001 -69032002 -69032004 -69032005 -69032099 -69032001 -69032002 -69032003 -69039099 -69039001 -69041001 -69049099 -69051001 -69059001 -69059099 -69060001 -69079001 -69089002 -69071001 -69081001 -69079099 -69089099 -69079002 -69089003 -69071001 -69081001 -69079099 -69089099 -69079002 -69089003 -69071001 -69081001 -69079099 -69089099 -69071001 -69081001 -69079099 -69089099 -69071001 -69081001 -69079099 -69089099 -69091101 -69091102 -69091103 -69091104 -69091105 -69091199 -69091201 -69091999 -69099099 -69101001 -69109001 -69109099 -69111001 -69119099 -69120001 -69120099 -69131001 -69139099 -69141001 -69149099 -70010001 -70021001 -70022001 -70022002 -70022003 -70022004 -70022099 -70023101 -70023102 -70023199 -70023201 -70023999 -70031201 -70031299 -70031901 -70031999 -70032001 -70033001 -70042001 -70042002 -70042099 -70049001 -70049099 -70051001 -70051099 -70052101 -70052102 -70052199 -70052901 -70052902 -70052903 -70052904 -70052999 -70053001 -70060001 -70060003 -70060002 -70060099 -70071101 -70071102 -70071103 -70071199 -70071901 -70071999 -70072103 -70072101 -70072102 -70072199 -70072999 -70080001 -70091004 -70091001 -70091002 -70091003 -70091099 -70099101 -70099199 -70099201 -70101001 -70101099 -70102001 -70109001 -70109002 -70109003 -70109099 -70111001 -70111002 -70111003 -70111004 -70111099 -70112001 -70112002 -70112003 -70112004 -70112099 -70119001 -70119099 -70131001 -70132201 -70132801 -70132899 -70133301 -70133703 -70133703 -70133701 -70133702 -70133799 -70134101 -70134201 -70134902 -70134903 -70134903 -70134903 -70134903 -70134904 -70134903 -70134901 -70134999 -70139101 -70139999 -70139999 -70139999 -70139999 -70139999 -70140001 -70140002 -70140003 -70140004 -70140099 -70151001 -70159099 -70161001 -70169099 -70171004 -70171006 -70171007 -70171008 -70171009 -70171010 -70171001 -70171002 -70171003 -70171005 -70171011 -70171012 -70171013 -70171099 -70172001 -70172003 -70172002 -70172099 -70179003 -70179004 -70179001 -70179002 -70179005 -70179099 -70181001 -70182001 -70182099 -70189001 -70189099 -70191101 -70191201 -70191999 -70193101 -70193201 -70193901 -70193902 -70193903 -70193904 -70193999 -70194001 -70194099 -70195101 -70195199 -70195201 -70195299 -70195901 -70195999 -70199001 -70199002 -70199005 -70199006 -70199007 -70199008 -70199003 -70199004 -70199099 -70200004 -70200001 -70200002 -70200003 -70200005 -70200099 -71011001 -71011099 -71012101 -71012201 -71012299 -71021001 -71022101 -71022999 -71023101 -71023999 -71031001 -71039101 -71039999 -71041001 -71042001 -71049099 -71051001 -71059099 -71061001 -71069101 -71069201 -71070001 -71081101 -71081201 -71081301 -71082001 -71082099 -71090001 -71101101 -71101999 -71102101 -71102999 -71103101 -71103999 -71104101 -71104999 -71110001 -71123001 -71129101 -71129199 -71129201 -71129999 -71131101 -71131102 -71131199 -71131903 -71131901 -71131902 -71131999 -71132001 -71141101 -71141999 -71142001 -71151001 -71159001 -71159099 -71161001 -71162001 -71171101 -71171901 -71171999 -71179001 -71179099 -71181001 -71189099 -72011001 -72012001 -72015001 -72015099 -72021101 -72021999 -72021999 -72022101 -72022199 -72022999 -72023001 -72024101 -72024999 -72025001 -72026001 -72027001 -72028001 -72029101 -72029102 -72029103 -72029201 -72029299 -72029301 -72029901 -72029902 -72029903 -72029999 -72031001 -72039099 -72041001 -72042101 -72042999 -72043001 -72044101 -72044999 -72045001 -72051001 -72052101 -72052999 -72061001 -72069099 -72071101 -72071201 -72071299 -72071999 -72072001 -72072099 -72081001 -72081002 -72081004 -72081099 -72082501 -72082599 -72082601 -72082601 -72082701 -72082701 -72083601 -72083601 -72083601 -72083701 -72083701 -72083701 -72083801 -72083801 -72083901 -72083901 -72084001 -72084099 -72085101 -72085102 -72085103 -72085101 -72085102 -72085103 -72085101 -72085102 -72085103 -72085201 -72085301 -72085401 -72089099 -72091501 -72091502 -72091503 -72091599 -72091601 -72091601 -72091701 -72091701 -72091802 -72091899 -72092501 -72092601 -72092701 -72092801 -72099099 -72101101 -72101201 -72101202 -72101203 -72101299 -72102001 -72103001 -72103099 -72104101 -72104199 -72104903 -72104903 -72104999 -72105001 -72105002 -72105099 -72106101 -72106901 -72106999 -72107003 -72107004 -72107005 -72107091 -72107099 -72109001 -72109091 -72109099 -72111301 -72111401 -72111402 -72111404 -72111499 -72111901 -72111902 -72111903 -72111904 -72111999 -72112301 -72112302 -72112399 -72112901 -72112902 -72112903 -72112999 -72119099 -72121001 -72121002 -72121099 -72122001 -72122002 -72122099 -72123001 -72123002 -72123099 -72124001 -72124002 -72124003 -72124099 -72125001 -72126001 -72126002 -72126003 -72126099 -72131001 -72132001 -72139101 -72139102 -72139901 -72139902 -72139999 -72141001 -72142001 -72142099 -72143001 -72149101 -72149102 -72149101 -72149199 -72149901 -72149901 -72149902 -72149999 -72149901 -72149902 -72149999 -72151001 -72155001 -72155099 -72159001 -72159099 -72161001 -72161001 -72162101 -72162101 -72162201 -72163101 -72163102 -72163199 -72163204 -72163201 -72163202 -72163299 -72163301 -72163301 -72163301 -72163301 -72163301 -72163302 -72163302 -72163302 -72163302 -72163302 -72164001 -72164001 -72164001 -72164001 -72165001 -72165099 -72166101 -72166102 -72166199 -72166901 -72166902 -72166999 -72169101 -72169999 -72171001 -72171099 -72171099 -72171099 -72171099 -72171099 -72171099 -72171099 -72171099 -72172001 -72172099 -72173001 -72173099 -72179099 -72179099 -72181001 -72189101 -72189999 -72191101 -72191201 -72191299 -72191301 -72191401 -72192101 -72192201 -72192301 -72192401 -72193102 -72193199 -72193201 -72193299 -72193301 -72193401 -72193501 -72193599 -72199099 -72201101 -72201201 -72202001 -72202002 -72202099 -72209099 -72210002 -72210099 -72221101 -72221199 -72221999 -72222001 -72223001 -72223099 -72224002 -72224099 -72230001 -72230099 -72241001 -72241002 -72241003 -72241004 -72241005 -72241091 -72241099 -72249002 -72249001 -72249003 -72249099 -72251101 -72251999 -72253002 -72253003 -72253004 -72253005 -72253006 -72253008 -72253002 -72253003 -72253004 -72253005 -72253099 -72253091 -72253099 -72254001 -72254002 -72254003 -72254004 -72254005 -72254099 -72254007 -72254099 -72254091 -72254099 -72254005 -72255002 -72255003 -72255004 -72255008 -72255009 -72255010 -72255011 -72255006 -72255012 -72255002 -72255003 -72255004 -72255008 -72255009 -72255010 -72255011 -72255099 -72255099 -72255091 -72255099 -72259101 -72259201 -72259201 -72259999 -72259999 -72259999 -72259999 -72261101 -72261999 -72262001 -72269103 -72269104 -72269105 -72269108 -72269109 -72269110 -72269111 -72269112 -72269191 -72269199 -72269201 -72269202 -72269203 -72269204 -72269205 -72269207 -72269299 -72269901 -72269902 -72269999 -72271001 -72272002 -72272099 -72279001 -72279002 -72279003 -72279099 -72279099 -72281001 -72281099 -72282001 -72282099 -72283001 -72283003 -72283099 -72284001 -72284099 -72285001 -72285099 -72286001 -72286003 -72286099 -72287002 -72287099 -72288001 -72292001 -72299001 -72299002 -72299003 -72299004 -72299099 -73011001 -73012001 -73021001 -73021003 -73021099 -73023001 -73024001 -73024099 -73029001 -73029002 -73029099 -73030001 -73030099 -73041101 -73041102 -73041103 -73041104 -73041105 -73041199 -73041901 -73041901 -73041901 -73041901 -73041902 -73041902 -73041903 -73041903 -73041904 -73041904 -73041904 -73041905 -73041999 -73041991 -73041999 -73042201 -73042202 -73042203 -73042299 -73042301 -73042302 -73042303 -73042399 -73042401 -73042402 -73042403 -73042404 -73042405 -73042406 -73042499 -73042901 -73042902 -73042903 -73042904 -73042905 -73042906 -73042999 -73042999 -73043101 -73043101 -73043110 -73043110 -73043102 -73043103 -73043104 -73043105 -73043106 -73043107 -73043108 -73043109 -73043199 -73043191 -73043199 -73043901 -73043901 -73043902 -73043902 -73043903 -73043904 -73043908 -73043909 -73043910 -73043911 -73043912 -73043913 -73043914 -73043915 -73043916 -73043991 -73043992 -73043999 -73043999 -73043999 -73043999 -73043999 -73044101 -73044102 -73044199 -73044901 -73044999 -73045101 -73045102 -73045103 -73045104 -73045105 -73045106 -73045107 -73045108 -73045109 -73045110 -73045111 -73045191 -73045199 -73045909 -73045901 -73045902 -73045903 -73045904 -73045905 -73045910 -73045911 -73045912 -73045913 -73045914 -73045915 -73045916 -73045917 -73045991 -73045992 -73045999 -73049099 -73051101 -73051101 -73051199 -73051201 -73051201 -73051299 -73051901 -73051999 -73052001 -73052099 -73053101 -73053102 -73053103 -73053104 -73053105 -73053106 -73053191 -73053199 -73053901 -73053902 -73053903 -73053904 -73053905 -73053999 -73059001 -73059099 -73061101 -73061901 -73061999 -73061999 -73062101 -73062999 -73063002 -73063003 -73063004 -73063005 -73063006 -73063099 -73063099 -73063099 -73063099 -73063091 -73063099 -73064001 -73064099 -73065001 -73065003 -73065004 -73065099 -73066102 -73066103 -73066102 -73066103 -73066199 -73066199 -73066901 -73066902 -73066999 -73069099 -73071101 -73071199 -73071902 -73071903 -73071904 -73071901 -73071905 -73071906 -73071999 -73072101 -73072201 -73072299 -73072301 -73072399 -73072999 -73079101 -73079201 -73079299 -73079301 -73079301 -73079902 -73079903 -73079904 -73079901 -73079905 -73079999 -73081001 -73082001 -73082099 -73083001 -73083099 -73084001 -73089001 -73089002 -73089099 -73089099 -73090001 -73090002 -73090003 -73090099 -73101001 -73101002 -73101003 -73101004 -73101099 -73102101 -73102901 -73102905 -73102902 -73102903 -73102904 -73102999 -73110001 -73110002 -73110003 -73110004 -73110099 -73121001 -73121005 -73121005 -73121005 -73121007 -73121008 -73121008 -73121003 -73121004 -73121009 -73121010 -73121099 -73121099 -73121099 -73121002 -73121006 -73121099 -73129099 -73130001 -73141201 -73141401 -73141903 -73141903 -73141903 -73141902 -73141902 -73141901 -73141999 -73142001 -73143101 -73141903 -73143101 -73143101 -73143999 -73144101 -73144101 -73144101 -73144201 -73144999 -73144999 -73145001 -73151102 -73151101 -73151102 -73151103 -73151104 -73151102 -73151105 -73151102 -73151199 -73151201 -73151202 -73151299 -73151901 -73151902 -73151903 -73151999 -73152001 -73158102 -73158101 -73158199 -73158201 -73158202 -73158299 -73158902 -73158901 -73158999 -73159001 -73160001 -73170001 -73170002 -73170003 -73170004 -73170099 -73170099 -73170099 -73170099 -73170099 -73170099 -73170099 -73170099 -73181101 -73181201 -73181301 -73181401 -73181504 -73181501 -73181502 -73181503 -73181505 -73181506 -73181507 -73181508 -73181509 -73181510 -73181599 -73181601 -73181602 -73181603 -73181604 -73181605 -73181901 -73181902 -73181901 -73181999 -73182101 -73182199 -73182201 -73182299 -73182301 -73182399 -73182402 -73182401 -73182402 -73182499 -73182901 -73182999 -73194001 -73199001 -73199099 -73201001 -73201099 -73202001 -73202003 -73202004 -73202002 -73202099 -73209003 -73209001 -73209002 -73209099 -73211101 -73211102 -73211199 -73211201 -73211901 -73218101 -73218199 -73218201 -73218299 -73218901 -73219001 -73219002 -73219003 -73219004 -73219005 -73219006 -73219007 -73219099 -73221101 -73221199 -73221901 -73221999 -73229001 -73229099 -73231001 -73239101 -73239102 -73239199 -73239202 -73239201 -73239203 -73239299 -73239301 -73239302 -73239303 -73239304 -73239399 -73239401 -73239403 -73239402 -73239404 -73239499 -73239999 -73241001 -73242101 -73242999 -73249003 -73249001 -73249002 -73249099 -73251002 -73251001 -73251003 -73251004 -73251099 -73259101 -73259102 -73259199 -73259903 -73259902 -73259903 -73259905 -73259901 -73259903 -73259904 -73259906 -73259999 -73261101 -73261102 -73261199 -73261901 -73261902 -73261903 -73261905 -73261907 -73261909 -73261910 -73261911 -73261912 -73261914 -73261904 -73261906 -73261908 -73261913 -73261999 -73262001 -73262002 -73262003 -73262004 -73262005 -73262099 -73269002 -73269005 -73269006 -73269009 -73269010 -73269012 -73269013 -73269014 -73269015 -73269017 -73269018 -73269001 -73269003 -73269004 -73269007 -73269008 -73269011 -73269016 -73269099 -74010001 -74010002 -74020001 -74031101 -74031201 -74031301 -74031999 -74032101 -74032201 -74032901 -74032999 -74040001 -74040002 -74040099 -74050001 -74061001 -74062001 -74071001 -74071002 -74071099 -74072102 -74072101 -74072199 -74072901 -74072904 -74072905 -74072902 -74072903 -74072999 -74081101 -74081199 -74081901 -74081902 -74081999 -74082101 -74082201 -74082299 -74082999 -74091101 -74091999 -74092101 -74092999 -74093101 -74093999 -74094001 -74099001 -74101101 -74101201 -74102101 -74102102 -74102103 -74102199 -74102201 -74111001 -74111002 -74111003 -74111004 -74111099 -74112101 -74112105 -74112102 -74112103 -74112104 -74112199 -74112201 -74112202 -74112203 -74112204 -74112299 -74112901 -74112902 -74112903 -74112904 -74112999 -74121001 -74122001 -74130001 -74130099 -74151001 -74151099 -74152101 -74152999 -74152901 -74153301 -74153302 -74153399 -74153999 -74153901 -74181001 -74182001 -74191001 -74199101 -74199102 -74199103 -74199104 -74199105 -74199106 -74199199 -74199901 -74199902 -74199905 -74199906 -74199908 -74199913 -74199915 -74199999 -74199903 -74199904 -74199907 -74199909 -74199910 -74199911 -74199912 -74199914 -75011001 -75012001 -75021001 -75022001 -75030001 -75040001 -75051101 -75051201 -75052101 -75052201 -75061001 -75061099 -75062001 -75062099 -75071101 -75071201 -75072001 -75081001 -75089001 -75089099 -75089002 -75089003 -76011001 -76011099 -76012001 -76012099 -76020001 -76020099 -76031001 -76032001 -76041001 -76041002 -76041099 -76042101 -76042901 -76042902 -76042999 -76051101 -76051199 -76051999 -76052101 -76052102 -76052199 -76052901 -76052902 -76052999 -76061101 -76061199 -76061201 -76061202 -76061203 -76061299 -76069101 -76069199 -76069201 -76069299 -76071101 -76071901 -76071902 -76071903 -76071999 -76072001 -76072099 -76081003 -76081001 -76081002 -76081099 -76082001 -76082002 -76082003 -76082099 -76090001 -76090099 -76101001 -76109001 -76109099 -76110001 -76121001 -76129001 -76129099 -76130001 -76141001 -76141001 -76141001 -76149099 -76149099 -76151001 -76151099 -76151099 -76152001 -76161001 -76161002 -76161003 -76161099 -76169101 -76169901 -76169902 -76169903 -76169904 -76169905 -76169906 -76169907 -76169908 -76169909 -76169910 -76169911 -76169912 -76169913 -76169914 -76169915 -76169991 -76169992 -76169999 -78011001 -78019101 -78019999 -78020001 -78041101 -78041999 -78042001 -78060001 -78060099 -78060002 -79011101 -79011201 -79012001 -79020001 -79031001 -79039099 -79040001 -79050001 -79070001 -79070099 -80011001 -80012001 -80020001 -80030001 -80070001 -80070002 -80070003 -80070099 -81011001 -81019401 -81019602 -81019699 -81019601 -81019701 -81019901 -81019999 -81021001 -81029401 -81029501 -81029599 -81029601 -81029701 -81029999 -81032001 -81033001 -81039099 -81041101 -81041999 -81042001 -81043001 -81049001 -81049002 -81049099 -81052001 -81053001 -81059099 -81060001 -81072001 -81073001 -81079099 -81082001 -81083001 -81089001 -81089099 -81092001 -81093001 -81099099 -81101001 -81102001 -81109099 -81110001 -81110099 -81121201 -81121301 -81121999 -81122101 -81122201 -81122999 -81125101 -81125201 -81125999 -81129201 -81129999 -81130001 -81130099 -82011001 -82011099 -82013001 -82013099 -82014001 -82015001 -82016001 -82016002 -82019002 -82019003 -82019004 -82019001 -82019099 -82021001 -82021004 -82021002 -82021003 -82021099 -82022001 -82023101 -82023199 -82023901 -82023902 -82023903 -82023904 -82023905 -82023906 -82023907 -82023999 -82024001 -82024099 -82029101 -82029102 -82029103 -82029199 -82029901 -82029999 -82031002 -82031001 -82031099 -82032001 -82032099 -82033001 -82034001 -82034003 -82034002 -82034099 -82041101 -82041102 -82041199 -82041201 -82041202 -82041299 -82042001 -82042099 -82051001 -82051002 -82051003 -82051099 -82052001 -82053001 -82053099 -82054001 -82054099 -82055102 -82055101 -82055103 -82055199 -82055905 -82055908 -82055909 -82055910 -82055913 -82055914 -82055915 -82055916 -82055901 -82055902 -82055903 -82055907 -82055911 -82055912 -82055917 -82055918 -82055919 -82055904 -82055906 -82055999 -82056001 -82056099 -82057003 -82057001 -82057002 -82057099 -82059002 -82059003 -82059004 -82060001 -82071302 -82071305 -82071306 -82071301 -82071303 -82071304 -82071307 -82071399 -82071902 -82071903 -82071904 -82071905 -82071906 -82071907 -82071908 -82071901 -82071909 -82071999 -82072001 -82073001 -82073002 -82074002 -82074001 -82074003 -82074099 -82075001 -82075002 -82075003 -82075005 -82075006 -82075004 -82075099 -82076002 -82076003 -82076004 -82076005 -82076001 -82076099 -82077001 -82077002 -82077099 -82078001 -82079001 -82079099 -82081001 -82081099 -82082001 -82082099 -82083001 -82083099 -82084001 -82084002 -82084099 -82089099 -82090001 -82100001 -82111001 -82119101 -82119201 -82119299 -82119301 -82119401 -82119501 -82121001 -82121099 -82122001 -82129001 -82129099 -82130001 -82141001 -82141099 -82142001 -82142099 -82149001 -82149002 -82149003 -82149004 -82149099 -82151001 -82152001 -82159101 -82159901 -82159999 -83011001 -83012001 -83012099 -83013001 -83014001 -83015001 -83015099 -83016001 -83016099 -83017001 -83017099 -83021001 -83021002 -83021099 -83022001 -83022099 -83023001 -83024101 -83024102 -83024104 -83024105 -83024199 -83024103 -83024201 -83024202 -83024299 -83024299 -83024999 -83025001 -83026001 -83030001 -83040001 -83040002 -83040099 -83051001 -83052001 -83059001 -83059002 -83059099 -83061001 -83062101 -83062999 -83063001 -83071001 -83071099 -83079001 -83081001 -83081099 -83082001 -83089001 -83091001 -83099001 -83099003 -83099006 -83099007 -83099008 -83099002 -83099004 -83099099 -83099005 -83100001 -83100099 -83111002 -83111003 -83111004 -83111001 -83111099 -83112001 -83112002 -83112003 -83112004 -83112099 -83113001 -83113002 -83113003 -83113099 -83113004 -83119001 -83119002 -83119003 -83119004 -83119005 -83119099 -84011001 -84012001 -84013001 -84014001 -84021101 -84021201 -84021901 -84021999 -84022001 -84029001 -84031001 -84039001 -84041001 -84042001 -84049001 -84051001 -84051002 -84051099 -84059001 -84061001 -84061099 -84068101 -84068201 -84068299 -84069001 -84069002 -84069099 -84069003 -84071001 -84072101 -84072199 -84072901 -84072999 -84073101 -84073199 -84073202 -84073201 -84073203 -84073299 -84073301 -84073302 -84073303 -84073399 -84073402 -84073499 -84073401 -84079001 -84079099 -84081001 -84081099 -84082001 -84089001 -84089099 -84091001 -84099105 -84099111 -84099101 -84099102 -84099103 -84099104 -84099106 -84099107 -84099108 -84099110 -84099112 -84099114 -84099115 -84099116 -84099117 -84099118 -84099119 -84099199 -84099199 -84099109 -84099113 -84099901 -84099902 -84099903 -84099906 -84099904 -84099905 -84099908 -84099909 -84099910 -84099911 -84099912 -84099913 -84099914 -84099999 -84101101 -84101201 -84101299 -84101301 -84101399 -84109001 -84111101 -84111201 -84112101 -84112201 -84118101 -84118201 -84119101 -84119999 -84121001 -84122101 -84122999 -84123101 -84123199 -84123999 -84123901 -84128001 -84128099 -84129001 -84131101 -84131199 -84131901 -84131903 -84131902 -84131999 -84131904 -84132001 -84133003 -84133001 -84133002 -84133004 -84133005 -84133006 -84133099 -84134001 -84134099 -84135002 -84135001 -84135099 -84136001 -84136004 -84136006 -84136002 -84136003 -84136005 -84136099 -84137001 -84137002 -84137005 -84137007 -84137003 -84137004 -84137006 -84137099 -84138101 -84138102 -84138103 -84138199 -84138201 -84139101 -84139102 -84139104 -84139105 -84139106 -84139109 -84139111 -84139112 -84139199 -84139103 -84139107 -84139108 -84139110 -84139201 -84141001 -84141002 -84141003 -84141099 -84141004 -84141005 -84142001 -84143001 -84143009 -84143002 -84143003 -84143004 -84143005 -84143006 -84143007 -84143008 -84143010 -84143099 -84144001 -84144002 -84144099 -84145101 -84145102 -84145199 -84145901 -84145999 -84146001 -84146099 -84148006 -84148007 -84148008 -84148010 -84148001 -84148002 -84148016 -84148003 -84148004 -84148011 -84148012 -84148013 -84148014 -84148015 -84148099 -84148005 -84148009 -84149001 -84149002 -84149003 -84149004 -84149006 -84149099 -84149005 -84149007 -84149008 -84149009 -84151001 -84151001 -84151001 -84152001 -84158101 -84158201 -84158299 -84158301 -84159001 -84159099 -84161001 -84161099 -84162001 -84162099 -84163001 -84163099 -84169001 -84169099 -84171003 -84171001 -84171002 -84171004 -84171099 -84171005 -84172001 -84172099 -84178001 -84178002 -84178003 -84178004 -84178005 -84178099 -84179001 -84181001 -84181099 -84182101 -84182901 -84182999 -84183001 -84183002 -84183004 -84183003 -84183099 -84184001 -84184002 -84184004 -84184003 -84184099 -84185001 -84185003 -84185002 -84185099 -84186101 -84186199 -84186102 -84186901 -84186902 -84186903 -84186904 -84186905 -84186906 -84186907 -84186908 -84186909 -84186910 -84186912 -84186913 -84186914 -84186916 -84186917 -84186915 -84186911 -85161001 -84186999 -84189101 -84189903 -84189904 -84189999 -84189901 -84189902 -84191101 -84191901 -84191902 -84191999 -84191903 -84192001 -84192099 -84193101 -84193102 -84193103 -84193104 -84193105 -84193199 -84193201 -84193299 -84193901 -84193904 -84193999 -84193902 -84193903 -84193905 -84194004 -84194001 -84194002 -84194003 -84194099 -84195003 -84195001 -84195002 -84195004 -84195005 -84195099 -84196001 -84198101 -84198103 -84198102 -84198199 -84198902 -84198903 -84198909 -84198910 -84198911 -84198915 -84198917 -84198918 -84198919 -84198901 -84198905 -84198907 -84198908 -84198912 -84198913 -84198914 -84198916 -84198904 -84198906 -84198920 -84198921 -84198999 -84199001 -84199003 -84199099 -84199002 -84201001 -84209101 -84209199 -84209999 -84211101 -84211199 -84211201 -84211299 -84211901 -84211903 -84211902 -84211904 -84211999 -84212101 -84212102 -84212104 -84212103 -84212199 -84212201 -84212301 -84212901 -84212902 -84212903 -84212906 -84212908 -84212904 -84212905 -84212999 -84212907 -84213101 -84213102 -84213199 -84213902 -84213903 -84213905 -84213906 -84213907 -84213908 -84213901 -84213904 -84213999 -84219101 -84219199 -84219102 -84219103 -84219901 -84219999 -84219902 -84219903 -84221101 -84221999 -84222001 -84222002 -84222003 -84222099 -84223008 -84223001 -84223002 -84223003 -84223004 -84223006 -84223007 -84223009 -84223010 -84223099 -84223005 -84224002 -84224001 -84224003 -84224004 -84224099 -84224005 -84224006 -84229001 -84229002 -84229003 -84229004 -84229099 -84231001 -84231099 -84232001 -84232099 -84233001 -84233099 -84238101 -84238102 -84238201 -84238202 -84238999 -84239001 -84239099 -84241002 -84241099 -84241001 -84242001 -84242002 -84242099 -84242003 -84243004 -84243001 -84243002 -84243099 -84243003 -84248102 -84248199 -84248104 -84248199 -84248101 -84248102 -84248103 -84248104 -84248105 -84248106 -84248199 -84248902 -84248999 -84248901 -84249001 -84251101 -84251199 -84251999 -84253101 -84253102 -84253199 -84253901 -84253902 -84253903 -84253999 -84254101 -84254201 -84254202 -84254299 -84254999 -84261101 -84261201 -84261999 -84262001 -84263001 -84264101 -84264102 -84264103 -84264104 -84264199 -84264901 -84264902 -84264999 -84269103 -84269101 -84269102 -84269199 -84269903 -84269904 -84269901 -84269902 -84269999 -84271001 -84271002 -84271099 -84272001 -84272002 -84272003 -84272004 -84272005 -84272099 -84279001 -84279099 -84281001 -84282002 -84282003 -84282099 -84282001 -84283101 -84283299 -84283399 -84283901 -84283902 -84283999 -84289005 -84284001 -84284099 -84286001 -84289001 -84289002 -84289003 -84289006 -84289004 -84289099 -84291101 -84291999 -84292001 -84293001 -84294001 -84294099 -84295103 -84295101 -84295102 -84295199 -84295201 -84295202 -84295299 -84295901 -84295902 -84295905 -84295903 -84295904 -84295999 -84301001 -84302001 -84303101 -84303102 -84303199 -84303901 -84303999 -84304101 -84304102 -84304199 -84304999 -84304901 -84304902 -84305001 -84305002 -84305099 -84306101 -84306102 -84306199 -84306901 -84306902 -84306903 -84306904 -84306999 -84311001 -84312001 -84312099 -84313101 -84313199 -84313999 -84314101 -84314102 -84314103 -84314199 -84314201 -84314302 -84314399 -84314301 -84314902 -84314903 -84314999 -84314901 -84321001 -84322101 -84322901 -84322999 -84323001 -84323002 -84323003 -84323099 -84323002 -84323003 -84323099 -84324001 -84324001 -84328002 -84328003 -84328099 -84328001 -84329001 -84331101 -84331999 -84332001 -84332002 -84332099 -84333001 -84334001 -84334099 -84334002 -84335101 -84335201 -84335301 -84335901 -84335902 -84335903 -84335904 -84335999 -84336003 -84336099 -84336001 -84336002 -84339001 -84339003 -84339099 -84339002 -84341001 -84342001 -84349001 -84351001 -84359001 -84361001 -84362101 -84362901 -84362999 -84368001 -84368003 -84368099 -84368002 -84369101 -84369999 -84371003 -84371099 -84371001 -84371002 -84378001 -84378099 -84378002 -84379001 -84379099 -84381001 -84381003 -84381004 -84381002 -84381005 -84381006 -84381007 -84381099 -84382001 -84382099 -84382002 -84383001 -84383099 -84384001 -84384099 -84385006 -84385001 -84385002 -84385003 -84385005 -84385007 -84385099 -84385004 -84385008 -84386004 -84386001 -84386002 -84386003 -84386005 -84386099 -84388001 -84388002 -84388099 -84388003 -84389001 -84389002 -84389005 -84389099 -84389003 -84389004 -84391001 -84391002 -84391003 -84391004 -84391005 -84391099 -84392001 -84393001 -84399101 -84399999 -84401001 -84401099 -84409001 -84411001 -84411002 -84411003 -84411099 -84412001 -84413001 -84414001 -84414099 -84418001 -84419001 -84423001 -84423002 -84423099 -84424001 -84425001 -84425003 -84425099 -84425002 -84431101 -84431199 -84431201 -84431301 -84431399 -84431302 -84431401 -84431499 -84431501 -84431599 -84431601 -84431701 -84431901 -84431903 -84431999 -84431902 -84433101 -84433202 -84433203 -84433201 -84433204 -84433205 -84433208 -84433207 -84433299 -84433206 -84433902 -84433905 -84433906 -84433901 -84433908 -84433904 -84433999 -84433903 -84433907 -84439102 -84439101 -84439199 -84439905 -84439906 -84439910 -84439901 -84439903 -84439909 -84439999 -84439902 -84439904 -84439907 -84439908 -84440001 -84451101 -84451201 -84451301 -84451901 -84451999 -84452001 -84453001 -84453099 -84454001 -84459001 -84459099 -84461001 -84462101 -84462999 -84463001 -84471101 -84471201 -84472001 -84472099 -84479001 -84479099 -84481101 -84481999 -84482001 -84483101 -84483201 -84483301 -84483999 -84484201 -84484999 -84484901 -84485101 -84485999 -84490001 -84501101 -84501199 -84501201 -84501299 -84501999 -84501901 -84502001 -84509002 -84509099 -84509001 -84511001 -84512101 -84512199 -84512901 -84512999 -84512902 -84512903 -84513001 -84514001 -84515001 -84518001 -84518099 -84519001 -84519099 -84519002 -84521001 -84522101 -84522102 -84522104 -84522105 -84522199 -84522103 -84522901 -84522902 -84522904 -84522905 -84522906 -84522907 -84522999 -84522903 -84523001 -84529001 -84529002 -84529099 -84531001 -84532001 -84538001 -84539001 -84541001 -84542001 -84542099 -84543001 -84543099 -84549001 -84549099 -84551001 -84552101 -84552199 -84552102 -84552201 -84552202 -84552299 -84553001 -84553002 -84553099 -84559001 -84559099 -84561001 -84561099 -84561001 -84561099 -84562001 -84562099 -84563001 -84569099 -84569099 -84569099 -84571001 -84572001 -84573002 -84573003 -84573001 -84573004 -84573099 -84581101 -84581102 -84581199 -84581901 -84581999 -84581902 -84589101 -84589199 -84589999 -84589901 -84591001 -84591099 -84592101 -84592199 -84592901 -84592999 -84593101 -84593999 -84594001 -84594099 -84594001 -84594099 -84595101 -84595999 -84596101 -84596999 -84597001 -84597002 -84597099 -84601101 -84609001 -84601199 -84609001 -84601999 -84601901 -84609099 -84602199 -84609001 -84602102 -84602103 -84602104 -84602199 -84602101 -84602199 -84609001 -84602999 -84602901 -84602902 -84602903 -84602904 -84609099 -84603101 -84603199 -84603999 -84603901 -84604002 -84604099 -84604001 -84609001 -84609002 -84609003 -84609099 -84612001 -84612099 -84613001 -84613099 -84614001 -84615002 -84615003 -84615001 -84615099 -84619003 -84619002 -84619099 -84619004 -84619005 -84621001 -84621099 -84622101 -84622106 -84622107 -84622102 -84622103 -84622104 -84622105 -84622199 -84622901 -84622903 -84622905 -84622906 -84622907 -84622902 -84622904 -84622999 -84623101 -84623103 -84623102 -84623199 -84623901 -84623903 -84623902 -84623999 -84624101 -84624102 -84624103 -84624199 -84624901 -84624902 -84624903 -84624999 -84629101 -84629102 -84629103 -84629199 -84629904 -84629901 -84629999 -84629902 -84629903 -84631001 -84632001 -84633001 -84633003 -84633099 -84633002 -84639099 -84639001 -84641001 -84642001 -84649001 -84649099 -84651001 -84659199 -84659299 -84659399 -84659499 -84659599 -84659601 -84659999 -84659101 -84659199 -84659201 -84659202 -84659299 -84659203 -84659301 -84659399 -84659401 -84659402 -84659499 -84659501 -84659599 -84659601 -84659902 -84659999 -84659901 -84659903 -84661003 -84661001 -84661002 -84661099 -84662001 -84662099 -84663002 -84663099 -84663001 -84669101 -84669201 -84669303 -84669304 -84669301 -84669302 -84669399 -84669401 -84669499 -84669402 -84671101 -84671199 -84671999 -84671901 -84672101 -84672102 -84672103 -84672199 -84672202 -84672203 -84672299 -84672201 -84672901 -84672902 -84672903 -84672999 -84678101 -84678902 -84678903 -84678901 -84678999 -84679101 -84679199 -84679201 -84679901 -84679902 -84679999 -84681001 -84682001 -84682099 -84688099 -84689001 -84701001 -84701002 -84701099 -84702101 -84702999 -84702901 -84703001 -84705001 -84709003 -84709002 -84709099 -84709001 -84713001 -84713001 -84714101 -84714901 -84715001 -84716001 -84716002 -84716003 -84716099 -84717001 -84718001 -84718002 -84718003 -84718099 -84719099 -84721001 -84721099 -84723001 -84723002 -84723099 -84729001 -84729005 -84729010 -84729012 -84729013 -84729014 -84690001 -84729002 -84729009 -84690002 -84690003 -84690004 -84690099 -84729099 -84729003 -84729004 -84729006 -84729007 -84729008 -84729011 -84729015 -84732101 -84732999 -84733002 -84733003 -84733001 -84734002 -84731001 -84731099 -84731099 -84734001 -84734099 -84735099 -84735001 -84735099 -84735002 -84741001 -84741002 -84741099 -84742004 -84742007 -84742008 -84742001 -84742002 -84742005 -84742006 -84742099 -84742003 -84743101 -84743201 -84743901 -84743902 -84743999 -84748001 -84748004 -84748002 -84748005 -84748008 -84748010 -84748003 -84748006 -84748007 -84748009 -84748099 -84749001 -84749002 -84749099 -84751001 -84752101 -84752901 -84752999 -84759001 -84762101 -84762999 -84768101 -84768999 -84769001 -84769099 -84771001 -84771099 -84772001 -84772002 -84772099 -84773001 -84774001 -84775101 -84775999 -84778006 -84778001 -84778003 -84778004 -84778005 -84778007 -84778002 -84778099 -84779001 -84779002 -84779003 -84779099 -84781001 -84781002 -84781003 -84781004 -84781005 -84781099 -84789001 -84791001 -84791003 -84791004 -84791007 -84791005 -84791006 -84791008 -84791099 -84791002 -84792001 -84793001 -84793002 -84793099 -84794001 -84794099 -84795001 -84796001 -84797101 -84797999 -84798102 -84798103 -84798104 -84798107 -84798101 -84798105 -84798108 -84798109 -84798106 -84798199 -84798201 -84798202 -84798204 -84798205 -84798203 -84798299 -84798903 -84798904 -84798906 -84798907 -84798908 -84798910 -84798912 -84798913 -84798914 -84798919 -84798920 -84798921 -84798922 -84798926 -84798927 -84798901 -84798902 -84798905 -84798915 -84798916 -84798917 -84798923 -84798925 -84798909 -84798911 -84798918 -84798924 -84798999 -84799001 -84799006 -84799009 -84799010 -84799013 -84799014 -84799002 -84799004 -84799005 -84799007 -84799008 -84799011 -84799012 -84799015 -84799016 -84799017 -84799099 -84801001 -84802001 -84803001 -84803002 -84803099 -84804101 -84804199 -84804901 -84804999 -84805001 -84805002 -84805099 -84806001 -84806099 -84807101 -84807102 -84807199 -84807902 -84807904 -84807901 -84807903 -84807999 -84811001 -84811002 -84811099 -84812007 -84812010 -84812012 -84812001 -84812002 -84812003 -84812004 -84812006 -84812009 -84812011 -84812005 -84812008 -84812099 -84813001 -84813002 -84813003 -84813099 -84814004 -84814002 -84814003 -84814001 -84814099 -84818001 -84818002 -84818003 -84818004 -84818005 -84818006 -84818007 -84818008 -84818009 -84818010 -84818011 -84818012 -84818013 -84818014 -84818015 -84818016 -84818017 -84818018 -84818019 -84818020 -84818021 -84818022 -84818023 -84818024 -84818025 -84818099 -84819001 -84819003 -84819004 -84819002 -84819099 -84821002 -84821001 -84821003 -84821099 -84822001 -84822003 -84822002 -84822099 -84823001 -84824001 -84825001 -84828001 -84829101 -84829199 -84829901 -84829902 -84829903 -84829999 -84831001 -84831002 -84831003 -84831004 -84831005 -84831006 -84831007 -84831099 -84832001 -84833001 -84833002 -84833003 -84833099 -84834001 -84834002 -84834003 -84834005 -84834006 -84834007 -84834008 -84834004 -84834099 -84835001 -84835002 -84835099 -84836001 -84836002 -84836099 -84839001 -84839002 -84839099 -84841001 -84842001 -84849001 -84849002 -84849099 -84861001 -84862002 -84862099 -84863001 -84864001 -84869001 -84869002 -84869003 -84869004 -84871001 -84871099 -84879001 -84879002 -84879003 -84879004 -84879005 -84879006 -84879007 -84879099 -85011001 -85011002 -85011003 -85011004 -85011005 -85011008 -85011009 -85011006 -85011007 -85011099 -85012002 -85012004 -85012001 -85012003 -85012099 -85013101 -85013102 -85013103 -85013104 -85013105 -85013199 -85013201 -85013202 -85013203 -85013204 -85013205 -85013206 -85013299 -85013301 -85013302 -85013303 -85013304 -85013305 -85013399 -85013401 -85013405 -85013402 -85013403 -85013404 -85013499 -85014005 -85014008 -85014001 -85014007 -85014002 -85014003 -85014004 -85014006 -85014099 -85015102 -85015103 -85015101 -85015199 -85015204 -85015202 -85015205 -85015201 -85015203 -85015299 -85015304 -85015305 -85015302 -85015306 -85015307 -85015301 -85015303 -85015399 -85016101 -85016201 -85016301 -85016401 -85016403 -85016402 -85016499 -85021101 -85021201 -85021301 -85021302 -85021399 -85022001 -85022002 -85022099 -85023101 -85023199 -85023901 -85023902 -85023903 -85023904 -85023999 -85024001 -85030005 -85030001 -85030002 -85030003 -85030004 -85030006 -85030099 -85041001 -85041099 -85042101 -85042102 -85042103 -85042104 -85042199 -85042201 -85042301 -85043103 -85043104 -85043101 -85043102 -85043105 -85043106 -85043199 -85043201 -85043202 -85043299 -85043301 -85043399 -85043401 -85044001 -85044002 -85044005 -85044006 -85044007 -85044008 -85044009 -85044011 -85044013 -85044012 -85044014 -85044015 -85044010 -85044003 -85044004 -85044099 -85045001 -85045002 -85045099 -85049002 -85049007 -85049008 -85049001 -85049005 -85049003 -85049004 -85049006 -85049099 -85051101 -85051999 -85052001 -85059005 -85059001 -85059002 -85059003 -85059004 -85059099 -85061002 -85061099 -85063001 -85064001 -85065001 -85066001 -85068001 -85069001 -85071001 -85071099 -85072001 -85072002 -85072004 -85072003 -85072099 -85073001 -85074001 -85075001 -85076001 -85078001 -85079001 -85081101 -85081901 -85081999 -85086001 -85087001 -85087002 -85087099 -85094002 -85094003 -85094001 -85094099 -85098003 -85098004 -85098007 -85098009 -85098010 -85098012 -85098001 -85098002 -85098005 -85098006 -85098008 -85098011 -85098099 -85099002 -85099099 -85099001 -85099099 -85101001 -85102001 -85103001 -85109001 -85109002 -85109003 -85109004 -85109099 -85111003 -85111002 -85111001 -85111099 -85112003 -85112001 -85112002 -85112099 -85113002 -85113003 -85113001 -85113004 -85113099 -85114001 -85114002 -85114003 -85114099 -85115001 -85115002 -85115003 -85115004 -85115099 -85118001 -85118002 -85118003 -85118004 -85118099 -85119001 -85119003 -85119005 -85119002 -85119004 -85119099 -85121001 -85121002 -85121099 -85122001 -85122002 -85122099 -85123001 -85123099 -85124001 -85129001 -85129002 -85129003 -85129004 -85129005 -85129006 -85129099 -85131001 -85131099 -85139001 -85141001 -85141002 -85141003 -85141004 -85141099 -85142001 -85142002 -85142003 -85142004 -85142005 -85142099 -85143001 -85143002 -85143003 -85143004 -85143005 -85143006 -85143099 -85144001 -85144099 -85149002 -85149003 -85149001 -85149099 -85151101 -85151199 -85151999 -85152101 -85152199 -85152901 -85152999 -85153101 -85153102 -85153199 -85153901 -85153902 -85153999 -85158001 -85158002 -85158099 -85159001 -85159099 -85161099 -85162101 -85162901 -85162999 -85163101 -85163201 -85163301 -85164001 -85165001 -85166001 -85166003 -85166002 -85166099 -85167101 -85167201 -85167901 -85167999 -85168002 -85168003 -85168001 -85168099 -85169007 -85169009 -85169001 -85169002 -85169003 -85169004 -85169005 -85169006 -85169008 -85169010 -85169099 -85171101 -85171201 -85171299 -85171802 -85171801 -85171899 -85176101 -85176201 -85176202 -85176203 -85176204 -85176205 -85176206 -85176209 -85176210 -85176213 -85176214 -85176215 -85176299 -85176207 -85176208 -85176211 -85176212 -85176216 -85176299 -85176905 -85176907 -85176913 -85176914 -85176915 -85176916 -85176917 -85176919 -85176901 -85176903 -85176904 -85176999 -85176902 -85176906 -85176908 -85176909 -85176910 -85176911 -85176912 -85176918 -85176920 -85176999 -85177009 -85177010 -85177011 -85177012 -85177001 -85177002 -85177013 -85177003 -85177004 -85177005 -85177006 -85177007 -85177008 -85177099 -85181001 -85181002 -85181003 -85181099 -85182101 -85182199 -85182199 -85182201 -85182299 -85182299 -85182901 -85182999 -85183003 -85183004 -85183001 -85183002 -85183099 -85184001 -85184002 -85184004 -85184006 -85184003 -85184005 -85184099 -85185001 -85189001 -85189002 -85189003 -85189099 -85192001 -85193001 -85193099 -85195001 -85198103 -85198104 -85198105 -85198106 -85198107 -85198108 -85198109 -85198110 -85198101 -85198102 -85198111 -85198199 -85198901 -85198902 -85198903 -85198904 -85198999 -85211001 -85211099 -85219001 -85219002 -85219003 -85219004 -85219005 -85219099 -85221001 -85229007 -85229001 -85229008 -85229014 -85229002 -85229003 -85229004 -85229005 -85229006 -85229009 -85229010 -85229011 -85229012 -85229013 -85229099 -85232101 -85232199 -85232905 -85232907 -85232908 -85232906 -85232909 -85232901 -85232902 -85232903 -85232910 -85232999 -85234101 -85234199 -85234901 -85234999 -85235101 -85235199 -85235201 -85235202 -85235901 -85235999 -85238099 -85255001 -85255002 -85255004 -85255003 -85255099 -85256005 -85256001 -85256003 -85256004 -85256006 -85256009 -85256010 -85256002 -85256007 -85256099 -85258001 -85258002 -85258003 -85258004 -85258099 -85261001 -85261099 -85269101 -85269199 -85269201 -85269299 -85271201 -85271301 -85271302 -85271901 -85271999 -85272101 -85272199 -85272901 -85272999 -85279101 -85279199 -85279199 -85279201 -85279901 -85279999 -85284101 -85284901 -85284902 -85284903 -85284904 -85284905 -85284908 -85284199 -85284906 -85284908 -85284906 -85284907 -85284901 -85284902 -85284903 -85284904 -85284905 -85284908 -85284909 -85284999 -85285101 -85285199 -85285901 -85285902 -85285903 -85285904 -85285999 -85286101 -85286901 -85286903 -85286902 -85286904 -85286999 -85287101 -85287102 -85287103 -85287104 -85287199 -85287201 -85287202 -85287203 -85287204 -85287205 -85287206 -85287207 -85287299 -85287301 -85291001 -85291002 -85291004 -85291005 -85291006 -85291007 -85291008 -85291003 -85291099 -85299009 -85299013 -85299015 -85299016 -85299018 -85299019 -85299020 -85299021 -85299001 -85299002 -85299003 -85299005 -85299006 -85299007 -85299010 -85299011 -85299023 -85299012 -85299004 -85299008 -85299013 -85299014 -85299017 -85299022 -85299099 -85301001 -85308002 -85308001 -85308099 -85309001 -85311001 -85311002 -85311003 -85311004 -85311005 -85311099 -85312001 -85318001 -85318003 -85318002 -85318099 -85319002 -85319001 -85319099 -85321001 -85321099 -85322101 -85322202 -85322203 -85322201 -85322299 -85322304 -85322305 -85322399 -85322301 -85322302 -85322303 -85322401 -85322402 -85322403 -85322499 -85322502 -85322503 -85322504 -85322501 -85322599 -85322904 -85322999 -85322901 -85322902 -85322903 -85322905 -85323001 -85323002 -85323003 -85323004 -85323099 -85329001 -85331001 -85331002 -85332101 -85332901 -85332999 -85333101 -85333199 -85333901 -85333902 -85333999 -85334001 -85334002 -85334004 -85334005 -85334006 -85334099 -85334003 -85334007 -85339002 -85339099 -85339001 -85340001 -85340002 -85340003 -85340099 -85351001 -85351002 -85351003 -85351099 -85352101 -85352999 -85353001 -85353003 -85353005 -85353006 -85353099 -85353002 -85353004 -85354001 -85354002 -85354099 -85359008 -85359020 -85359001 -85359002 -85359005 -85359010 -85359015 -85359016 -85359099 -85359003 -85359004 -85359006 -85359007 -85359009 -85359011 -85359012 -85359013 -85359014 -85359017 -85359018 -85359019 -85359021 -85359022 -85361003 -85361004 -85361002 -85361005 -85361001 -85361099 -85362001 -85362002 -85362099 -85363005 -85363002 -85363003 -85363099 -85363001 -85363004 -85364103 -85364102 -85364104 -85364108 -85364109 -85364110 -85364111 -85364101 -85364105 -85364106 -85364107 -85364199 -85364901 -85364902 -85364903 -85364905 -85364999 -85364904 -85365007 -85365001 -85365005 -85365008 -85365009 -85365010 -85365011 -85365013 -85365015 -85365016 -85365017 -85365002 -85365003 -85365004 -85365006 -85365012 -85365014 -85365099 -85366101 -85366102 -85366103 -85366199 -85366902 -85366901 -85366999 -85367001 -85369028 -85369001 -85369002 -85369003 -85369005 -85369006 -85369007 -85369010 -85369011 -85369012 -85369013 -85369014 -85369015 -85369016 -85369017 -85369019 -85369021 -85369022 -85369023 -85369024 -85369026 -85369027 -85369004 -85369008 -85369009 -85369018 -85369020 -85369025 -85369099 -85371003 -85371004 -85371005 -85371006 -85371001 -85371002 -85371099 -85372002 -85372001 -85372099 -85381001 -85389001 -85389005 -85389002 -85389004 -85389099 -85389003 -85391001 -85391003 -85391099 -85391002 -85392101 -85392199 -85392201 -85392202 -85392203 -85392204 -85392205 -85392299 -85392908 -85392999 -85392901 -85392902 -85392903 -85392904 -85392905 -85392906 -85392907 -85393101 -85393199 -85393201 -85393202 -85393203 -85393299 -85393901 -85393902 -85393903 -85393904 -85393905 -85393906 -85393999 -85394101 -85394901 -85394999 -85437099 -85399001 -85399003 -85399004 -85399005 -85399099 -85399002 -85399006 -85401102 -85401105 -85401199 -85401101 -85401103 -85401104 -85401201 -85401299 -85402001 -85402099 -85404002 -85406001 -85406099 -85407101 -85407999 -85408101 -85408102 -85408199 -85408901 -85408999 -85408902 -85408903 -85409101 -85409102 -85409103 -85409104 -85409199 -85409901 -85409902 -85409903 -85409904 -85409999 -85411001 -85411099 -85412101 -85412999 -85413001 -85413099 -85414001 -85414002 -85414003 -85415001 -85416001 -85419001 -85419099 -85423101 -85423102 -85423199 -85423201 -85423299 -85423301 -85423399 -85423901 -85423999 -85429001 -85431002 -85432005 -85432099 -85432001 -85432002 -85432003 -85432004 -85433001 -85437001 -85437008 -85437017 -85437002 -85437005 -85437006 -85437007 -85437009 -85437010 -85437012 -85437014 -85437015 -85437016 -85437003 -85437004 -85437011 -85437013 -85437099 -85439001 -85439002 -85439099 -85441101 -85441901 -85441999 -85442001 -85442002 -85442003 -85442099 -85443001 -85443002 -85443099 -85444205 -85444201 -85444202 -85444203 -85444204 -85444204 -85444204 -85444299 -85444905 -85444901 -85444902 -85444903 -85444904 -85444901 -85444903 -85444904 -85444901 -85444903 -85444904 -85444901 -85444903 -85444904 -85444906 -85444999 -85446001 -85446099 -85446099 -85447001 -85451101 -85451999 -85452001 -85459001 -85459002 -85459099 -85461001 -85461002 -85461003 -85461099 -85462001 -85462002 -85462003 -85462004 -85462005 -85462099 -85469001 -85469002 -85469003 -85469099 -85471006 -85471001 -85471005 -85471099 -85471002 -85471003 -85471004 -85472001 -85472002 -85472003 -85472099 -85479008 -85479012 -85479099 -85479001 -85479002 -85479003 -85479004 -85479005 -85479006 -85479007 -85479009 -85479010 -85479011 -85481001 -85489004 -85489001 -85489002 -85489003 -85489099 -86011001 -86012001 -86021001 -86029099 -86031001 -86039099 -86040001 -86040099 -86040002 -86050001 -86050099 -86061001 -86063001 -86069101 -86069199 -86069201 -86069999 -86071101 -86071299 -86071902 -86071903 -86071906 -86071901 -86071904 -86071905 -86071999 -86072101 -86072199 -86072999 -86073001 -86079101 -86079199 -86079999 -86080001 -86080002 -86080099 -86080003 -86090001 -87011001 -87012001 -87012002 -87013001 -87013099 -87019001 -87019007 -87019003 -87019002 -87019004 -87019099 -87019001 -87019008 -87019007 -87019002 -87019003 -87019004 -87019099 -87019001 -87019007 -87019002 -87019003 -87019004 -87019099 -87019001 -87019003 -87019005 -87019006 -87019007 -87019002 -87019004 -87019099 -87019001 -87019007 -87019002 -87019003 -87019004 -87019099 -87021005 -87021001 -87021002 -87021003 -87021004 -87021005 -87029006 -87021001 -87029002 -87021002 -87029003 -87021003 -87029004 -87021004 -87029005 -87029006 -87029002 -87029003 -87029004 -87029005 -87029001 -87029007 -87029007 -87029007 -87029007 -87029006 -87029008 -87029001 -87029006 -87029008 -87029002 -87029003 -87029004 -87029005 -87031001 -87031002 -87031099 -87032101 -87032102 -87032199 -87032201 -87032202 -87032301 -87032302 -87032401 -87032402 -87033101 -87033102 -87033201 -87033202 -87033301 -87033302 -87032199 -87032201 -87032301 -87032401 -87039099 -87032102 -87032202 -87032302 -87032402 -87039002 -87032101 -87033101 -87033201 -87033301 -87039099 -87033102 -87033202 -87033302 -87039002 -87032199 -87032201 -87032301 -87032401 -87039099 -87032102 -87032202 -87032302 -87032402 -87039002 -87032101 -87033101 -87033201 -87033301 -87039099 -87033102 -87033202 -87033302 -87039002 -87039001 -87039003 -87039002 -87039099 -87041001 -87041099 -87042101 -87042104 -87042102 -87042103 -87042199 -87042201 -87042207 -87042202 -87042203 -87042204 -87042205 -87042206 -87042299 -87042301 -87042302 -87042399 -87043101 -87043102 -87043105 -87043103 -87043199 -87043201 -87043207 -87043202 -87043203 -87043204 -87043206 -87043205 -87043299 -87049001 -87049099 -87051001 -87052001 -87052099 -87053001 -87054001 -87054002 -87059001 -87059002 -87059099 -87060001 -87060002 -87060099 -87071001 -87071099 -87079002 -87082914 -87082915 -87079001 -87079099 -87081001 -87081002 -87081003 -87081099 -87082101 -87082901 -87082902 -87082903 -87082904 -87082905 -87082906 -87082907 -87082908 -87082909 -87082910 -87082911 -87082912 -87082913 -87082916 -87082917 -87082918 -87082919 -87082920 -87082922 -87082923 -87082924 -87082999 -87082999 -87082999 -87082999 -87082999 -87082999 -87082999 -87082999 -87082999 -87083001 -87083002 -87083004 -87083006 -87083008 -87083009 -87083010 -87083011 -87083005 -87083007 -87083012 -87083013 -87083003 -87083014 -87083099 -87084001 -87084002 -87084003 -87084004 -87084005 -87084007 -87084008 -87084006 -87084099 -87085001 -87085002 -87085003 -87085004 -87085005 -87085006 -87085007 -87085008 -87085009 -87085010 -87085013 -87085014 -87085015 -87085016 -87085018 -87085020 -87085022 -87085023 -87085024 -87085025 -87085026 -87085027 -87085028 -87085029 -87085030 -87085011 -87085017 -87085019 -87085021 -87085012 -87085099 -87087006 -87087002 -87087003 -87087004 -87087007 -87087001 -87087005 -87087099 -87088001 -87088002 -87088003 -87088004 -87088005 -87088006 -87088007 -87088008 -87088009 -87088010 -87088011 -87088012 -87088099 -87089101 -87089102 -84099907 -87089199 -87089201 -87089202 -87089299 -87089302 -87089304 -87089301 -87089303 -87089399 -87089402 -87089401 -87089408 -87089403 -87089401 -87089408 -87089404 -87089401 -87089408 -87089406 -87089401 -87089408 -87089407 -87089401 -87089408 -87089409 -87089401 -87089408 -87089410 -87089401 -87089408 -87089411 -87089401 -87089408 -87089405 -87089499 -87089501 -87082921 -87089599 -87089904 -87089907 -87089908 -87089909 -87089902 -87089912 -87089901 -87089903 -87089905 -87089902 -87089912 -87089906 -87089902 -87089912 -87089910 -87089902 -87089912 -87089911 -87089902 -87089912 -87089913 -87089902 -87089912 -87089914 -87089902 -87089912 -87089999 -87091101 -87091901 -87091902 -87091999 -87099001 -87100001 -87111001 -87111002 -87111099 -87112003 -87112004 -87112002 -87112099 -87113003 -87113001 -87113002 -87113099 -87114001 -87114002 -87114003 -87114099 -87115001 -87115002 -87115099 -87119099 -87119001 -87119099 -87120001 -87120002 -87120004 -87120003 -87120099 -87131001 -87139099 -87141001 -87141002 -87141003 -87141099 -87142001 -87149101 -87149101 -87149101 -87149201 -87149301 -87149401 -87149499 -87149501 -87149601 -87149999 -87150001 -87150002 -87161001 -87162001 -87162003 -87162002 -87162099 -87163101 -87163102 -87163199 -87163903 -87163904 -87163908 -87163901 -87163902 -87163905 -87163906 -87163907 -87163999 -87164099 -87168003 -87168001 -87168002 -87168099 -87169003 -87169001 -87169002 -87169099 -88010001 -88010099 -88021101 -88021199 -88021201 -88021299 -88022001 -88022099 -88023001 -88023002 -88023099 -88024001 -88026001 -88031001 -88032001 -88033099 -88039099 -88040001 -88051001 -88052101 -88052901 -89011001 -89011099 -89012001 -89012099 -89013001 -89013099 -89019001 -89019002 -89019003 -89019099 -89020001 -89020002 -89020099 -89031001 -89039101 -89039201 -89039999 -89040001 -89051001 -89052001 -89059001 -89059099 -89061001 -89069099 -89071001 -89079099 -89080001 -90011001 -90011099 -90012001 -90013002 -90013001 -90013099 -90014001 -90014002 -90014099 -90015001 -90015099 -90019001 -90019099 -90021101 -90021999 -90022001 -90029099 -90031101 -90031901 -90039001 -90039099 -90041001 -90049099 -90051001 -90058099 -90059001 -90059002 -90059099 -90063001 -90064001 -90065101 -90065201 -90065299 -90065301 -90065399 -90061001 -90065901 -90065902 -90065999 -90066101 -90066901 -90066999 -90069102 -90069199 -90069999 -90071001 -90071002 -90071099 -90072001 -90079101 -90079201 -90085001 -90089001 -90089099 -90101001 -90105001 -90106001 -90109001 -90111001 -90111099 -90112099 -90118001 -90119001 -90121001 -90129001 -90131001 -90132001 -90138001 -90138099 -90139001 -90141001 -90141002 -90141003 -90141099 -90142001 -90148001 -90148002 -90148099 -90149001 -90149099 -90151001 -90152001 -90152099 -90153001 -90154001 -90158001 -90158003 -90158004 -90158005 -90158007 -90158002 -90158006 -90158099 -90159001 -90160001 -90160099 -90171001 -90171002 -90171099 -90172001 -90172099 -90173001 -90173099 -90178003 -90178004 -90178001 -90178002 -90178099 -90179001 -90179099 -90181101 -90181102 -90181201 -90181301 -90181401 -90181902 -90181905 -90181906 -90181904 -90181910 -90181901 -90181903 -90181907 -90181909 -90181908 -90181999 -90182001 -90183101 -90183199 -90183202 -90183203 -90183204 -90183201 -90183299 -90183902 -90183903 -90183904 -90189007 -90183901 -90183905 -90183999 -90184101 -90184199 -90184902 -90184904 -90184905 -90184906 -90184901 -90184903 -90184999 -90185001 -90189001 -90189002 -90189003 -90189004 -90189005 -90189006 -90189007 -90189008 -90189009 -90189010 -90189011 -90189012 -90189013 -90189014 -90189015 -90189016 -90189017 -90189018 -90189019 -90189020 -90189025 -90189027 -90189028 -90189031 -90189021 -90189022 -90189023 -90189024 -90189026 -90189029 -90189030 -90189099 -90191002 -90191001 -90191099 -90192001 -90200002 -90200003 -90200001 -90200099 -90211001 -90211002 -90211004 -90211005 -90211003 -90211099 -90212101 -90212199 -90212999 -90213101 -90213901 -90213902 -90213904 -90213903 -90213999 -90214001 -90215001 -90219099 -90221201 -90221301 -90221401 -90221499 -90221901 -90222101 -90222199 -90222901 -90223001 -90229003 -90229001 -90229002 -90229099 -90230001 -90241001 -90248001 -90249001 -90251101 -90251199 -90251901 -90251902 -90251903 -90251904 -90251999 -90258001 -90258002 -90258003 -90258099 -90259001 -90261002 -90261003 -90261004 -90261005 -90261001 -90261006 -90261099 -90262003 -90262004 -90262001 -90262002 -90262005 -90262006 -90262099 -90268001 -90268002 -90268099 -90269001 -90271001 -90272001 -90273001 -90275099 -90278001 -90278002 -90278003 -90278099 -90279001 -90279002 -90279003 -90279099 -90281001 -90282002 -90282003 -90282001 -90282099 -90283001 -90283099 -90289001 -90289002 -90289099 -90291003 -90291002 -90291001 -90291004 -90291099 -90292001 -90292002 -90292004 -90292003 -90292005 -90292099 -90299001 -90301001 -90302001 -90302099 -90303101 -90303201 -90303301 -90303302 -90303304 -90303303 -90303305 -90303306 -90303399 -90303901 -90304001 -90304099 -90308201 -90308401 -90308903 -90308901 -90308902 -90308904 -90308999 -90309002 -90309004 -90309001 -90309003 -90309099 -90311001 -90312001 -90312099 -90314101 -90314901 -90314902 -90314999 -90318001 -90318002 -90318007 -90318003 -90318004 -90318005 -90318006 -90318099 -90319001 -90319002 -90319099 -90321001 -90321003 -90321004 -90321002 -90321099 -90322001 -90328101 -90328199 -90328901 -90328902 -90328903 -90328904 -90328905 -90328906 -90328999 -90329001 -90329099 -90330001 -91011101 -91011999 -91011901 -91012101 -91012999 -91019101 -91019999 -91021101 -91021201 -91021999 -91022101 -91022999 -91029101 -91029999 -91031001 -91039099 -91040002 -91040099 -91040001 -91040003 -91051101 -91051199 -91051999 -91052101 -91052999 -91059101 -91059199 -91059999 -91059901 -91061001 -91061099 -91069001 -91069099 -91070001 -91081101 -91081201 -91081999 -91082001 -91089099 -91091001 -91099099 -91101101 -91101201 -91101901 -91109099 -91111001 -91111099 -91112001 -91118099 -91119001 -91122001 -91122099 -91129001 -91131001 -91131099 -91132001 -91132099 -91139001 -91139099 -91141001 -91143001 -91144001 -91149099 -91149001 -91149002 -92011001 -92012001 -92019001 -92019002 -92019099 -92021001 -92029001 -92029002 -92029099 -92051001 -92059002 -92059003 -92059004 -92059001 -92059099 -92060001 -92071001 -92071002 -92071099 -92079099 -92081001 -92089099 -92093001 -92099101 -92099199 -92099201 -92099401 -92099499 -92099901 -92099902 -92099903 -92099999 -93011001 -93011099 -93012001 -93019099 -93020001 -93020099 -93031001 -93031099 -93032001 -93033001 -93039001 -93039099 -93040001 -93040099 -93051001 -93051099 -93052001 -93052099 -93059101 -93059999 -93062101 -93062199 -93062901 -93062999 -93063004 -93063002 -93063003 -93063099 -93063001 -93069001 -93069002 -93069099 -93070001 -94011001 -94012001 -94013001 -94014001 -94015101 -94015101 -94015999 -94016101 -94016999 -94017101 -94017999 -94017999 -94018001 -94019002 -94019001 -94019099 -94021001 -94021099 -94029001 -94029002 -94029099 -94031002 -94031099 -94031099 -94031099 -94031001 -94032003 -94032004 -94032099 -94032099 -94032099 -94032099 -94032001 -94032002 -94033001 -94033002 -94034001 -94035001 -94036001 -94036002 -94036003 -94036099 -94037001 -94037002 -94037099 -94038101 -94038101 -94038999 -94039001 -94039001 -94041001 -94042101 -94042199 -94042999 -94043001 -94049002 -94049003 -94049004 -94049005 -94049006 -94049001 -94049099 -94051001 -94051002 -94051003 -94051099 -94052001 -94052099 -94053001 -94054001 -94055001 -94055099 -94056001 -94059101 -94059102 -94059103 -94059104 -94059199 -94059201 -94059999 -94060001 -94060001 -95030001 -95030002 -95030003 -95030004 -95030005 -95030006 -95030007 -95030008 -95030009 -95030010 -95030011 -95030012 -95030013 -95030014 -95030015 -95030016 -95030017 -95030018 -95030020 -95030021 -95030022 -95030023 -95030024 -95030025 -95030026 -95030027 -95030028 -95030029 -95030030 -95030031 -95030032 -95030033 -95030034 -95030035 -95030019 -95030036 -95030099 -95042001 -95042099 -95043001 -95043002 -95043099 -95044001 -95045003 -95045001 -95045002 -95049001 -95049002 -95049003 -95049004 -95049005 -95049099 -95051001 -95051099 -95059099 -95061101 -95061201 -95061999 -95062101 -95062901 -95062999 -95063101 -95063199 -95063201 -95063901 -95063999 -95064001 -95065101 -95065199 -95065999 -95066101 -95066201 -95066999 -95067001 -95069102 -95069199 -95069101 -95069901 -95069902 -95069903 -95069904 -95069905 -95069906 -95069999 -95071001 -95072001 -95073001 -95079099 -95081001 -95089002 -95089001 -95089099 -96011001 -96019099 -96020001 -96020099 -96031001 -96032101 -96032999 -96033001 -96034001 -96035001 -96039001 -96039099 -96040001 -96050001 -96061001 -96062101 -96062201 -96062901 -96062999 -96063001 -96071101 -96071999 -96072001 -96081001 -96081099 -96082001 -96083001 -96083099 -96084002 -96084001 -96084099 -96085001 -96085099 -96086001 -96089101 -96089199 -96089903 -96089906 -96089907 -96089908 -96089909 -96089910 -96089911 -96089901 -96089904 -96089902 -96089905 -96089999 -96091001 -96092001 -96092099 -96099001 -96099099 -96100001 -96110001 -96110099 -96121003 -96121001 -96121002 -96121099 -96122001 -96131001 -96132001 -96138002 -96138001 -96138099 -96139001 -96139002 -96139099 -96140001 -96140003 -96140002 -96140099 -96151101 -96151999 -96159099 -96161001 -96162001 -96170001 -96180001 -96180099 -96190001 -96190002 -96190003 -96190004 -96190099 -90069101 -44219099 -76169999 -68151099 -84879099 -39269099 -73269099 -84313999 -84733001 -85229099 -85299099 -90059001 -90059099 -90069199 -90079101 -90159001 -90330001 -92099999 -97011001 -97019099 -97020001 -97030001 -97040001 -97040099 -97050005 -97050006 -97050001 -97050002 -97050003 -97050004 -97050099 -97060001 -98010001 -98020001 -98020002 -98020003 -98020004 -98020005 -98020006 -98020007 -98020008 -98020009 -98020010 -98020011 -98020012 -98020013 -98020014 -98020015 -98020016 -98020017 -98020018 -98020019 -98020020 -98020021 -98020022 -98020023 -98020024 -98020025 -98020026 -98020027 -98020028 -98020029 -98020030 -98020031 -98020032 -98020033 -98020034 -98020035 -98020036 -98020037 -98020038 -98030001 -98030002 -98040001 -98040099 -98050001 -98060001 -98060002 -98060003 -98060004 -98060005 -98060006 -98060007 -98060008 -98060009 -98060010 -98070001 -("0101290200", -("0101290300", -("0101299901", -("0101299999", -("0101300100", -("0101909900", -("0102210100", -("0102290100", -("0102290200", -("0102299900", -("0102310100", -("0102399900", -("0102909900", -("0103100100", -("0103910100", -("0103919900", -("0103919900", -("0103920100", -("0103929900", -("0103929900", -("0103929900", -("0104100100", -("0104109901", -("0104109999", -("0104200100", -("0104200200", -("0104209900", -("0105110301", -("0105110302", -("0105110303", -("0105110303", -("0105110391", -("0105110391", -("0105110392", -("0105110392", -("0105119900", -("0105120100", -("0105130100", -("0105140100", -("0105150100", -("0105940100", -("0105949900", -("0105999900", -("0106110100", -("0106119900", -("0106120100", -("0106130100", -("0106140100", -("0106190200", -("0106199901", -("0106199999", -("0106199999", -("0106200400", -("0106200400", -("0106200400", -("0106200400", -("0106310100", -("0106320100", -("0106330100", -("0106399900", -("0106399900", -("0106399900", -("0106410100", -("0106499900", -("0106900200", -("0106900300", -("0106900400", -("0106909900", -("0201100100", -("0201209900", -("0201300100", -("0202100100", -("0202209900", -("0202300100", -("0203110100", -("0203120101", -("0203120102", -("0203199900", -("0203210100", -("0203220101", -("0203220102", -("0203299900", -("0204100100", -("0204210100", -("0204229900", -("0204230100", -("0204300100", -("0204410100", -("0204429900", -("0204430100", -("0204500100", -("0205000100", -("0206100100", -("0206210100", -("0206220100", -("0206299900", -("0206300100", -("0206309900", -("0206410100", -("0206490100", -("0206499900", -("0206809900", -("0206909900", -("0207110100", -("0207120100", -("0207130401", -("0207130402", -("0207130403", -("0207130404", -("0207130405", -("0207130406", -("0207130499", -("0207140200", -("0207149901", -("0207149902", -("0207149903", -("0207149904", -("0207149905", -("0207149999", -("0207149999", -("0207240100", -("0207250100", -("0207260301", -("0207260399", -("0207260399", -("0207270200", -("0207279901", -("0207279999", -("0207279999", -("0207410100", -("0207420100", -("0207430100", -("0207440100", -("0207450100", -("0207459900", -("0207510100", -("0207520100", -("0207530100", -("0207540100", -("0207550100", -("0207559900", -("0207600300", -("0207600300", -("0207600300", -("0208100100", -("0208300100", -("0208400100", -("0208500100", -("0208600100", -("0208909900", -("0208909900", -("0208909900", -("0209100100", -("0209900100", -("0209909900", -("0210110100", -("0210120100", -("0210199900", -("0210200100", -("0210910100", -("0210920100", -("0210930100", -("0210990200", -("0210999900", -("0210999900", -("0210999900", -("0301110100", -("0301199900", -("0301910100", -("0301920100", -("0301930100", -("0301930100", -("0301940100", -("0301950100", -("0301999900", -("0302110100", -("0302130100", -("0302140100", -("0302199900", -("0302210100", -("0302220100", -("0302230100", -("0302240100", -("0302299900", -("0302310100", -("0302320100", -("0302330100", -("0302340100", -("0302350100", -("0302360100", -("0302399900", -("0302410100", -("0302420100", -("0302430100", -("0302440100", -("0302450100", -("0302460100", -("0302470100", -("0302499900", -("0302510100", -("0302520100", -("0302530100", -("0302540100", -("0302550100", -("0302560100", -("0302599900", -("0302710100", -("0302720100", -("0302730100", -("0302730100", -("0302740100", -("0302799900", -("0302810100", -("0302820100", -("0302830100", -("0302840100", -("0302850100", -("0302890100", -("0302899900", -("0302910100", -("0302920100", -("0302990100", -("0302990100", -("0302990100", -("0302990100", -("0302990100", -("0302990100", -("0302990100", -("0302990200", -("0302990300", -("0302999900", -("0302999900", -("0302999900", -("0302999900", -("0302999900", -("0302999900", -("0302999900", -("0302999900", -("0302999900", -("0302999900", -("0302999900", -("0302999900", -("0302999900", -("0302999900", -("0302999900", -("0302999900", -("0302999900", -("0302999900", -("0302999900", -("0302999900", -("0302999900", -("0302999900", -("0302999900", -("0302999900", -("0302999900", -("0302999900", -("0302999900", -("0302999900", -("0302999900", -("0302999900", -("0302999900", -("0302999900", -("0302999900", -("0303110100", -("0303120100", -("0303130100", -("0303140100", -("0303199900", -("0303230100", -("0303240100", -("0303250100", -("0303250100", -("0303260100", -("0303299900", -("0303310100", -("0303320100", -("0303330100", -("0303340100", -("0303399900", -("0303410100", -("0303420100", -("0303430100", -("0303440100", -("0303450100", -("0303460100", -("0303499900", -("0303510100", -("0303530100", -("0303540100", -("0303550100", -("0303560100", -("0303570100", -("0303599900", -("0303630100", -("0303640100", -("0303650100", -("0303660100", -("0303670100", -("0303680100", -("0303699900", -("0303810100", -("0303820100", -("0303830100", -("0303840100", -("0303890100", -("0303899900", -("0303910100", -("0303920100", -("0303990100", -("0303990100", -("0303990100", -("0303990100", -("0303990100", -("0303990100", -("0303990100", -("0303990200", -("0303990200", -("0303990300", -("0303999900", -("0303999900", -("0303999900", -("0303999900", -("0303999900", -("0303999900", -("0303999900", -("0303999900", -("0303999900", -("0303999900", -("0303999900", -("0303999900", -("0303999900", -("0303999900", -("0303999900", -("0303999900", -("0303999900", -("0303999900", -("0303999900", -("0303999900", -("0303999900", -("0303999900", -("0303999900", -("0303999900", -("0303999900", -("0303999900", -("0303999900", -("0303999900", -("0303999900", -("0303999900", -("0303999900", -("0304310100", -("0304320100", -("0304330100", -("0304399900", -("0304399900", -("0304410100", -("0304420100", -("0304430100", -("0304440101", -("0304440199", -("0304450100", -("0304460100", -("0304470100", -("0304480100", -("0304499901", -("0304499902", -("0304499903", -("0304499999", -("0304510100", -("0304510100", -("0304520101", -("0304520199", -("0304530101", -("0304530199", -("0304540100", -("0304550100", -("0304560100", -("0304570100", -("0304599901", -("0304599902", -("0304599903", -("0304599999", -("0304610100", -("0304620100", -("0304630100", -("0304699900", -("0304699900", -("0304710100", -("0304720100", -("0304730100", -("0304740101", -("0304740199", -("0304750100", -("0304799900", -("0304810100", -("0304820100", -("0304830100", -("0304840100", -("0304850100", -("0304860100", -("0304870100", -("0304880100", -("0304899901", -("0304899902", -("0304899999", -("0304910100", -("0304920100", -("0304930100", -("0304930100", -("0304940100", -("0304950101", -("0304950199", -("0304960100", -("0304970100", -("0304999901", -("0304999902", -("0304999903", -("0304999904", -("0304999999", -("0305100100", -("0305200100", -("0305310100", -("0305310100", -("0305320101", -("0305320199", -("0305399901", -("0305399902", -("0305399903", -("0305399904", -("0305399905", -("0305399999", -("0305410100", -("0305420100", -("0305430100", -("0305440100", -("0305440100", -("0305499901", -("0305499902", -("0305499903", -("0305499904", -("0305499905", -("0305499906", -("0305499999", -("0305510201", -("0305510299", -("0305520100", -("0305530100", -("0305540100", -("0305599901", -("0305599902", -("0305599903", -("0305599904", -("0305599905", -("0305599906", -("0305599907", -("0305599999", -("0305610100", -("0305620100", -("0305630100", -("0305640100", -("0305640100", -("0305699901", -("0305699902", -("0305699903", -("0305699904", -("0305699905", -("0305699999", -("0305710100", -("0305720101", -("0305720102", -("0305720103", -("0305720104", -("0305720105", -("0305720106", -("0305720199", -("0305799901", -("0305799902", -("0305799903", -("0305799904", -("0305799905", -("0305799906", -("0305799999", -("0306110100", -("0306120100", -("0306140100", -("0306150100", -("0306160100", -("0306170100", -("0306199900", -("0306310100", -("0306320100", -("0306330100", -("0306340100", -("0306350100", -("0306359900", -("0306360100", -("0306369900", -("0306390100", -("0306910100", -("0306920100", -("0306930100", -("0306940100", -("0306950100", -("0306950100", -("0306959900", -("0306959900", -("0306999900", -("0307110100", -("0307120100", -("0307199900", -("0307210100", -("0307220100", -("0307299900", -("0307310100", -("0307320100", -("0307399900", -("0307420201", -("0307420299", -("0307420299", -("0307430201", -("0307430299", -("0307430299", -("0307499901", -("0307499999", -("0307499999", -("0307510100", -("0307520100", -("0307599900", -("0307600100", -("0307710100", -("0307720100", -("0307799900", -("0307810100", -("0307820100", -("0307830100", -("0307840100", -("0307870100", -("0307880100", -("0307910100", -("0307920100", -("0307999900", -("0308110100", -("0308120100", -("0308199900", -("0308210100", -("0308220100", -("0308299900", -("0308300100", -("0308909900", -("0401100201", -("0401100299", -("0401200201", -("0401200299", -("0401400201", -("0401400299", -("0401500201", -("0401500299", -("0402100100", -("0402109900", -("0402210100", -("0402219900", -("0402299900", -("0402910100", -("0402919900", -("0402990100", -("0402999900", -("0403100100", -("0403909900", -("0404100100", -("0404109900", -("0404909900", -("0405100201", -("0405100299", -("0405200100", -("0405900100", -("0405909900", -("0406100100", -("0406200100", -("0406300200", -("0406300200", -("0406400100", -("0406900100", -("0406900200", -("0406900400", -("0406909901", -("0406909999", -("0406909999", -("0406909999", -("0407110101", -("0407110191", -("0407110199", -("0407199900", -("0407210201", -("0407210299", -("0407290100", -("0407299900", -("0407909900", -("0407909900", -("0408110100", -("0408199900", -("0408910201", -("0408910299", -("0408990200", -("0408999900", -("0408999900", -("0409000100", -("0410000100", -("0410009900", -("0501000100", -("0502100100", -("0502909900", -("0504000100", -("0505100100", -("0505909900", -("0506100100", -("0506909900", -("0507100100", -("0507909900", -("0507909900", -("0508000200", -("0508000200", -("0510000400", -("0510000400", -("0510000400", -("0510000400", -("0511100100", -("0511910200", -("0511919901", -("0511919999", -("0511990100", -("0511990300", -("0511990400", -("0511990500", -("0511990800", -("0511999901", -("0511999902", -("0511999999", -("0511999999", -("0601100901", -("0601100902", -("0601100903", -("0601100999", -("0601100999", -("0601100999", -("0601100999", -("0601100999", -("0601100999", -("0601201000", -("0601201000", -("0601201000", -("0601201000", -("0601201000", -("0601201000", -("0601201000", -("0601201000", -("0601201000", -("0601201000", -("0602100700", -("0602100700", -("0602100700", -("0602100700", -("0602100700", -("0602100700", -("0602100700", -("0602200401", -("0602200402", -("0602200499", -("0602200499", -("0602300100", -("0602400100", -("0602409900", -("0602900600", -("0602900700", -("0602909901", -("0602909902", -("0602909903", -("0602909999", -("0602909999", -("0602909999", -("0602909999", -("0602909999", -("0602909999", -("0602909999", -("0602909999", -("0602909999", -("0603110100", -("0603120100", -("0603130100", -("0603140200", -("0603140200", -("0603150100", -("0603199901", -("0603199991", -("0603199991", -("0603199991", -("0603199991", -("0603199991", -("0603199991", -("0603199991", -("0603199999", -("0603199999", -("0603199999", -("0603199999", -("0603199999", -("0603199999", -("0603199999", -("0603909900", -("0604200100", -("0604209901", -("0604209902", -("0604209999", -("0604900100", -("0604909901", -("0604909999", -("0604909999", -("0604909999", -("0701100100", -("0701909900", -("0702000301", -("0702000302", -("0702000303", -("0702000304", -("0702000305", -("0702000399", -("0703100201", -("0703100299", -("0703200201", -("0703200299", -("0703900100", -("0704100301", -("0704100302", -("0704100399", -("0704200100", -("0704909901", -("0704909999", -("0705110100", -("0705199900", -("0705210100", -("0705299900", -("0706100100", -("0706909900", -("0707000100", -("0708100100", -("0708200100", -("0708909900", -("0709200201", -("0709200299", -("0709300100", -("0709400201", -("0709400299", -("0709510100", -("0709599900", -("0709599900", -("0709600201", -("0709600202", -("0709600203", -("0709600204", -("0709600205", -("0709600206", -("0709600207", -("0709600208", -("0709600299", -("0709700100", -("0709910100", -("0709920100", -("0709930101", -("0709930199", -("0709999901", -("0709999999", -("0710100100", -("0710210100", -("0710220100", -("0710299900", -("0710300100", -("0710400100", -("0710800100", -("0710809901", -("0710809902", -("0710809999", -("0710809999", -("0710900200", -("0710900200", -("0711200100", -("0711400100", -("0711510100", -("0711599900", -("0711900300", -("0711909900", -("0711909900", -("0711909900", -("0712200100", -("0712310100", -("0712320100", -("0712330100", -("0712399900", -("0712900100", -("0712909901", -("0712909902", -("0712909999", -("0713100100", -("0713109900", -("0713200100", -("0713310100", -("0713320100", -("0713330100", -("0713339901", -("0713339902", -("0713339999", -("0713340100", -("0713350100", -("0713399900", -("0713400100", -("0713500100", -("0713600100", -("0713909900", -("0714100100", -("0714109900", -("0714200100", -("0714209900", -("0714300100", -("0714309900", -("0714400100", -("0714409900", -("0714500100", -("0714509900", -("0714900200", -("0714909900", -("0714909900", -("0801110100", -("0801120100", -("0801199900", -("0801210100", -("0801220100", -("0801310100", -("0801320100", -("0802110100", -("0802120100", -("0802210100", -("0802220100", -("0802310100", -("0802320100", -("0802410100", -("0802420100", -("0802510100", -("0802520100", -("0802610100", -("0802620100", -("0802700100", -("0802800100", -("0802909901", -("0802909999", -("0803100100", -("0803909900", -("0804100201", -("0804100299", -("0804200200", -("0804200200", -("0804300100", -("0804400100", -("0804500401", -("0804500402", -("0804500403", -("0805100100", -("0805210100", -("0805220100", -("0805299900", -("0805400100", -("0805500301", -("0805500302", -("0805500399", -("0805909900", -("0806100100", -("0806200100", -("0807110101", -("0807110199", -("0807199901", -("0807199999", -("0807200100", -("0808100100", -("0808300100", -("0808400100", -("0809100100", -("0809210100", -("0809299900", -("0809300301", -("0809300302", -("0809400100", -("0810100100", -("0810200101", -("0810200102", -("0810200199", -("0810300100", -("0810400101", -("0810400199", -("0810500100", -("0810600100", -("0810700100", -("0810909900", -("0811100100", -("0811200101", -("0811200102", -("0811200199", -("0811909901", -("0811909999", -("0812100100", -("0812909900", -("0812909900", -("0812909900", -("0813100200", -("0813100200", -("0813200201", -("0813200299", -("0813300100", -("0813400400", -("0813400400", -("0813400400", -("0813400400", -("0813500100", -("0814000100", -("0901110201", -("0901110299", -("0901120100", -("0901210100", -("0901220100", -("0901909900", -("0901909900", -("0902100100", -("0902200100", -("0902300100", -("0902400100", -("0903000100", -("0904110100", -("0904120100", -("0904210201", -("0904210299", -("0904220201", -("0904220299", -("0905100100", -("0905200100", -("0906110100", -("0906199900", -("0906200100", -("0907100100", -("0907200100", -("0908110100", -("0908120100", -("0908210100", -("0908220100", -("0908310100", -("0908320100", -("0909210100", -("0909220100", -("0909310100", -("0909320100", -("0909610100", -("0909610200", -("0909619900", -("0909620100", -("0909620200", -("0909629900", -("0910110100", -("0910120100", -("0910200100", -("0910300100", -("0910910100", -("0910999901", -("0910999999", -("0910999999", -("1001110100", -("1001199900", -("1001910100", -("1001919900", -("1001990100", -("1001999900", -("1002100100", -("1002909900", -("1003100100", -("1003909901", -("1003909999", -("1004100100", -("1004909900", -("1005100100", -("1005900400", -("1005909901", -("1005909902", -("1005909999", -("1005909999", -("1006100100", -("1006200100", -("1006300201", -("1006300299", -("1006400100", -("1007100100", -("1007900100", -("1007900200", -("1008100100", -("1008210100", -("1008299900", -("1008300100", -("1008400100", -("1008500100", -("1008600100", -("1008909900", -("1101000100", -("1102200100", -("1102909901", -("1102909999", -("1102909999", -("1103110100", -("1103130100", -("1103190301", -("1103190399", -("1103190399", -("1103200201", -("1103200299", -("1104120100", -("1104190200", -("1104190200", -("1104220100", -("1104230100", -("1104290200", -("1104290200", -("1104300100", -("1105100100", -("1105200100", -("1106100100", -("1106200200", -("1106200200", -("1106300200", -("1106300200", -("1107100100", -("1107200100", -("1108110100", -("1108120100", -("1108130100", -("1108140100", -("1108190200", -("1108190200", -("1108200100", -("1109000100", -("1201100100", -("1201900100", -("1201900200", -("1202300100", -("1202410100", -("1202420100", -("1203000100", -("1204000100", -("1205100100", -("1205909900", -("1206000201", -("1206000299", -("1207100100", -("1207210100", -("1207299900", -("1207300100", -("1207400100", -("1207500100", -("1207600300", -("1207609900", -("1207609900", -("1207700100", -("1207910100", -("1207999900", -("1207999900", -("1207999900", -("1208100100", -("1208909900", -("1208909900", -("1208909900", -("1209100100", -("1209210100", -("1209220100", -("1209230100", -("1209240100", -("1209250100", -("1209290400", -("1209299901", -("1209299902", -("1209299999", -("1209299999", -("1209299999", -("1209300100", -("1209911501", -("1209911502", -("1209911503", -("1209911504", -("1209911505", -("1209911506", -("1209911507", -("1209911508", -("1209911509", -("1209911510", -("1209911511", -("1209911512", -("1209911599", -("1209911599", -("1209911599", -("1209999901", -("1209999902", -("1209999999", -("1209999999", -("1209999999", -("1209999999", -("1209999999", -("1210100100", -("1210200100", -("1211200100", -("1211200200", -("1211300100", -("1211300200", -("1211400100", -("1211400100", -("1211500100", -("1211500100", -("1211909100", -("1211909901", -("1211909902", -("1211909903", -("1211909999", -("1211909999", -("1211909999", -("1211909999", -("1211909999", -("1212210100", -("1212210200", -("1212219900", -("1212290100", -("1212299900", -("1212910100", -("1212920200", -("1212920200", -("1212930100", -("1212940200", -("1212940200", -("1212999900", -("1212999900", -("1213000100", -("1214100100", -("1214900100", -("1214909900", -("1301200100", -("1301909900", -("1301909900", -("1301909900", -("1302119900", -("1302119900", -("1302119900", -("1302120201", -("1302120299", -("1302130100", -("1302140100", -("1302190100", -("1302190300", -("1302190400", -("1302190500", -("1302190600", -("1302190700", -("1302190800", -("1302190900", -("1302191300", -("1302191400", -("1302191500", -("1302191500", -("1302199900", -("1302199900", -("1302200201", -("1302200299", -("1302310100", -("1302320100", -("1302320200", -("1302329900", -("1302390100", -("1302390200", -("1302399900", -("1302399900", -("1401100100", -("1401200100", -("1401909900", -("1404200100", -("1404909901", -("1404909902", -("1404909991", -("1404909999", -("1404909999", -("1501100100", -("1501200100", -("1501909900", -("1502100100", -("1502909900", -("1503000200", -("1503000200", -("1504100100", -("1504109900", -("1504200201", -("1504200299", -("1504300100", -("1505000401", -("1505000499", -("1505000499", -("1505000499", -("1506000100", -("1506009900", -("1506009900", -("1507100100", -("1507909900", -("1508100100", -("1508909900", -("1509100200", -("1509100200", -("1509909901", -("1509909999", -("1509909999", -("1510009900", -("1511100100", -("1511909900", -("1512110100", -("1512199900", -("1512210100", -("1512299900", -("1513110100", -("1513199900", -("1513210100", -("1513299900", -("1514110100", -("1514199900", -("1514910100", -("1514999900", -("1515110100", -("1515199900", -("1515210100", -("1515299900", -("1515300100", -("1515500100", -("1515909900", -("1515909900", -("1515909900", -("1515909900", -("1515909900", -("1515909900", -("1516100100", -("1516200100", -("1517100100", -("1517909900", -("1517909900", -("1517909900", -("1518000100", -("1518000200", -("1518009900", -("1520000100", -("1521100100", -("1521109900", -("1521900400", -("1521900400", -("1521909901", -("1521909999", -("1522000100", -("1601000301", -("1601000302", -("1601000399", -("1602100200", -("1602100200", -("1602200200", -("1602200200", -("1602310100", -("1602320100", -("1602399900", -("1602410100", -("1602420100", -("1602499901", -("1602499999", -("1602500200", -("1602500200", -("1602909900", -("1603000100", -("1603009900", -("1604110100", -("1604120100", -("1604130201", -("1604130202", -("1604130299", -("1604140400", -("1604149901", -("1604149902", -("1604149999", -("1604149999", -("1604150100", -("1604160201", -("1604160202", -("1604160299", -("1604170100", -("1604180100", -("1604199901", -("1604199902", -("1604199903", -("1604199999", -("1604200301", -("1604200302", -("1604200303", -("1604200304", -("1604200305", -("1604200306", -("1604200307", -("1604200391", -("1604200399", -("1604310100", -("1604320100", -("1605100201", -("1605100299", -("1605210100", -("1605299900", -("1605300100", -("1605400100", -("1605510100", -("1605520100", -("1605530100", -("1605540100", -("1605540100", -("1605550100", -("1605560100", -("1605570100", -("1605580100", -("1605599900", -("1605610100", -("1605620100", -("1605630100", -("1605699900", -("1701120501", -("1701120502", -("1701130100", -("1701140501", -("1701140502", -("1701910401", -("1701910402", -("1701999901", -("1701999902", -("1701999999", -("1702110100", -("1702190100", -("1702199900", -("1702200100", -("1702300100", -("1702400100", -("1702409900", -("1702500100", -("1702600301", -("1702600302", -("1702600303", -("1702600304", -("1702600305", -("1702600306", -("1702600391", -("1702600392", -("1702600399", -("1702900100", -("1702909900", -("1703100100", -("1703100100", -("1703909900", -("1704100100", -("1704909900", -("1801000100", -("1802000100", -("1803100100", -("1803200100", -("1804000100", -("1805000100", -("1806100100", -("1806109900", -("1806200100", -("1806310100", -("1806320100", -("1806909901", -("1806909999", -("1806909999", -("1901100201", -("1901100299", -("1901200200", -("1901209901", -("1901209999", -("1901900200", -("1901900300", -("1901900400", -("1901900500", -("1901909901", -("1901909999", -("1902110100", -("1902199900", -("1902200100", -("1902309900", -("1902400100", -("1903000100", -("1904100100", -("1904200100", -("1904300100", -("1904909900", -("1905100100", -("1905200100", -("1905310100", -("1905320100", -("1905400100", -("1905909901", -("1905909902", -("1905909991", -("1905909999", -("2001100100", -("2001909901", -("2001909991", -("2001909991", -("2001909999", -("2002100100", -("2002909900", -("2003100100", -("2003909900", -("2004100100", -("2004900300", -("2004900300", -("2004900300", -("2005100100", -("2005200100", -("2005400100", -("2005510100", -("2005599900", -("2005600100", -("2005700100", -("2005800100", -("2005910100", -("2005999901", -("2005999999", -("2005999999", -("2006000100", -("2006000200", -("2006000300", -("2006009900", -("2007100100", -("2007910100", -("2007990100", -("2007990200", -("2007990300", -("2007999901", -("2007999999", -("2008110201", -("2008110299", -("2008190201", -("2008190202", -("2008190203", -("2008190299", -("2008200100", -("2008300901", -("2008300902", -("2008300999", -("2008300999", -("2008300999", -("2008300999", -("2008300999", -("2008300999", -("2008300999", -("2008400100", -("2008500100", -("2008600100", -("2008700100", -("2008800100", -("2008910100", -("2008930100", -("2008970100", -("2008999900", -("2008999900", -("2009110100", -("2009120201", -("2009120299", -("2009199900", -("2009199900", -("2009210100", -("2009299900", -("2009310201", -("2009310299", -("2009399901", -("2009399999", -("2009410201", -("2009410299", -("2009499900", -("2009499900", -("2009500100", -("2009610100", -("2009699900", -("2009710100", -("2009799900", -("2009810100", -("2009899900", -("2009900200", -("2009900200", -("2101110200", -("2101119901", -("2101119999", -("2101120100", -("2101200100", -("2101300100", -("2102100200", -("2102109901", -("2102109999", -("2102200100", -("2102209900", -("2102300100", -("2103100100", -("2103200201", -("2103200299", -("2103300201", -("2103300299", -("2103909900", -("2104100100", -("2104200100", -("2105000100", -("2106100100", -("2106109900", -("2106109900", -("2106900100", -("2106900300", -("2106900400", -("2106900500", -("2106900700", -("2106900800", -("2106900900", -("2106901000", -("2106901100", -("2106901200", -("2106901300", -("2106909901", -("2106909999", -("2106909999", -("2201100201", -("2201100299", -("2201900100", -("2201900200", -("2201909900", -("2202100100", -("2202910100", -("2202990100", -("2202990201", -("2202990299", -("2202990300", -("2202990400", -("2202999900", -("2203000100", -("2204100201", -("2204100299", -("2204210401", -("2204210401", -("2204210402", -("2204210402", -("2204210499", -("2204210499", -("2204220100", -("2204299900", -("2204309900", -("2205100200", -("2205100200", -("2205909900", -("2205909900", -("2206000201", -("2206000299", -("2207100100", -("2207200100", -("2208200100", -("2208200200", -("2208200300", -("2208209900", -("2208300501", -("2208300502", -("2208300503", -("2208300504", -("2208300599", -("2208400201", -("2208400299", -("2208500100", -("2208600100", -("2208700301", -("2208700302", -("2208700399", -("2208900100", -("2208909901", -("2208909902", -("2208909991", -("2208909992", -("2208909999", -("2208909999", -("2209000100", -("2301100201", -("2301100299", -("2301200100", -("2302100100", -("2302300100", -("2302400201", -("2302400299", -("2302500100", -("2303100100", -("2303200100", -("2303300100", -("2303309900", -("2304000100", -("2305000100", -("2306100100", -("2306200100", -("2306300100", -("2306410100", -("2306499900", -("2306500100", -("2306600100", -("2306909901", -("2306909999", -("2307000100", -("2308000200", -("2308000200", -("2309100100", -("2309900400", -("2309909901", -("2309909902", -("2309909999", -("2309909999", -("2309909999", -("2309909999", -("2309909999", -("2309909999", -("2309909999", -("2309909999", -("2309909999", -("2401100201", -("2401100299", -("2401200301", -("2401200399", -("2401200399", -("2401300100", -("2402100100", -("2402200100", -("2402909900", -("2403110100", -("2403199900", -("2403910201", -("2403910299", -("2403990100", -("2403999900", -("2501000201", -("2501000299", -("2501000299", -("2502000100", -("2503000201", -("2503000299", -("2504100100", -("2504909900", -("2505100100", -("2505909900", -("2506100100", -("2506200200", -("2506200200", -("2507000100", -("2508100100", -("2508300100", -("2508400201", -("2508400299", -("2508500100", -("2508600100", -("2508700100", -("2509000100", -("2510100201", -("2510100299", -("2510200201", -("2510200299", -("2511100100", -("2511200100", -("2512000100", -("2513100200", -("2513100200", -("2513200100", -("2514000100", -("2515110100", -("2515120100", -("2515129900", -("2515200100", -("2516110100", -("2516120100", -("2516200100", -("2516909900", -("2517100100", -("2517200100", -("2517300100", -("2517410100", -("2517499900", -("2518100100", -("2518200100", -("2518300100", -("2519100100", -("2519900100", -("2519900200", -("2519900300", -("2519909900", -("2520100100", -("2520200100", -("2521000100", -("2522100100", -("2522200100", -("2522300100", -("2523100100", -("2523210100", -("2523299900", -("2523300100", -("2523909900", -("2524100300", -("2524100300", -("2524909901", -("2524909902", -("2525100100", -("2525200100", -("2525300100", -("2526100100", -("2526200100", -("2528000100", -("2528009900", -("2529100100", -("2529210100", -("2529220100", -("2529300100", -("2530100100", -("2530200100", -("2530909901", -("2530909999", -("2530909999", -("2530909999", -("2530909999", -("2530909999", -("2530909999", -("2530909999", -("2530909999", -("2601110100", -("2601120100", -("2601200100", -("2602000201", -("2602000299", -("2603000100", -("2604000100", -("2605000100", -("2606000201", -("2606000299", -("2607000100", -("2608000100", -("2609000100", -("2610000201", -("2610000299", -("2611000100", -("2612100100", -("2612200100", -("2613100100", -("2613909900", -("2614000301", -("2614000302", -("2614000399", -("2615100201", -("2615100299", -("2615909900", -("2616100100", -("2616909900", -("2617100100", -("2617909900", -("2618000100", -("2619000201", -("2619000299", -("2620110100", -("2620199900", -("2620210100", -("2620299900", -("2620300100", -("2620400200", -("2620400200", -("2620600100", -("2620910200", -("2620910200", -("2620999901", -("2620999999", -("2620999999", -("2620999999", -("2621100100", -("2621909900", -("2701110100", -("2701120100", -("2701199900", -("2701200100", -("2702100100", -("2702200100", -("2703000201", -("2703000299", -("2704000301", -("2704000302", -("2705000100", -("2706000100", -("2707100100", -("2707200100", -("2707300100", -("2707400100", -("2707500100", -("2707910100", -("2707999901", -("2707999999", -("2707999999", -("2707999999", -("2708100100", -("2708200100", -("2709000501", -("2709000502", -("2709000503", -("2709009900", -("2710120600", -("2710129901", -("2710129902", -("2710129903", -("2710129904", -("2710129905", -("2710129906", -("2710129907", -("2710129908", -("2710129991", -("2710129999", -("2710190200", -("2710199901", -("2710199902", -("2710199903", -("2710199904", -("2710199905", -("2710199906", -("2710199907", -("2710199908", -("2710199991", -("2710199999", -("2710200100", -("2710910100", -("2710999900", -("2711110100", -("2711120100", -("2711130100", -("2711140100", -("2711190100", -("2711199901", -("2711199999", -("2711199999", -("2711210100", -("2711299900", -("2712100100", -("2712200100", -("2712900100", -("2712900200", -("2712900300", -("2712909901", -("2712909999", -("2713110100", -("2713120100", -("2713200100", -("2713909900", -("2714100100", -("2714900100", -("2714909900", -("2715000100", -("2715009900", -("2716000100", -("2801100100", -("2801200100", -("2801300100", -("2802000100", -("2803000200", -("2803009900", -("2803009900", -("2804100100", -("2804210100", -("2804299901", -("2804299999", -("2804300100", -("2804400100", -("2804500100", -("2804610100", -("2804699900", -("2804700400", -("2804700400", -("2804700400", -("2804700400", -("2804800100", -("2804900100", -("2805110100", -("2805120100", -("2805199900", -("2805199900", -("2805300100", -("2805400100", -("2806100100", -("2806200100", -("2807000100", -("2808000100", -("2809100100", -("2809200100", -("2809209900", -("2810000200", -("2810000200", -("2811110100", -("2811119900", -("2811120100", -("2811199900", -("2811199900", -("2811210301", -("2811210302", -("2811220301", -("2811220302", -("2811299901", -("2811299999", -("2811299999", -("2812110100", -("2812120100", -("2812130100", -("2812140100", -("2812150100", -("2812160100", -("2812170100", -("2812199901", -("2812199999", -("2812199999", -("2812199999", -("2812909900", -("2813100100", -("2813909900", -("2814100100", -("2814200100", -("2815110100", -("2815120100", -("2815200301", -("2815200302", -("2815300100", -("2816100100", -("2816400300", -("2816400300", -("2817000100", -("2817000200", -("2818100301", -("2818100302", -("2818100399", -("2818200201", -("2818200299", -("2818300301", -("2818300302", -("2819100100", -("2819909900", -("2820100200", -("2820100200", -("2820909900", -("2821100401", -("2821100402", -("2821100403", -("2821200100", -("2822000100", -("2823000100", -("2824100100", -("2824909900", -("2824909900", -("2825100201", -("2825100299", -("2825200100", -("2825300100", -("2825400201", -("2825400299", -("2825500301", -("2825500302", -("2825500399", -("2825600100", -("2825700100", -("2825800100", -("2825909900", -("2825909900", -("2826120100", -("2826199901", -("2826199902", -("2826199999", -("2826300100", -("2826909901", -("2826909999", -("2827100100", -("2827200100", -("2827310100", -("2827320100", -("2827350100", -("2827399901", -("2827399999", -("2827399999", -("2827399999", -("2827399999", -("2827399999", -("2827399999", -("2827410200", -("2827410200", -("2827499900", -("2827510100", -("2827599900", -("2827600100", -("2828100100", -("2828909900", -("2829110301", -("2829110302", -("2829199901", -("2829199999", -("2829900100", -("2829909900", -("2830100100", -("2830909900", -("2830909900", -("2830909900", -("2831100100", -("2831909900", -("2831909900", -("2832100201", -("2832100299", -("2832209900", -("2832209900", -("2832300100", -("2833110100", -("2833199900", -("2833210100", -("2833220100", -("2833240100", -("2833250100", -("2833250200", -("2833270100", -("2833290500", -("2833299901", -("2833299902", -("2833299999", -("2833299999", -("2833300100", -("2833400301", -("2833400302", -("2833400399", -("2834100201", -("2834100299", -("2834210100", -("2834290100", -("2834299900", -("2835100100", -("2835220100", -("2835240100", -("2835250201", -("2835250299", -("2835269900", -("2835299900", -("2835299900", -("2835299900", -("2835299900", -("2835310100", -("2835390100", -("2835390200", -("2835399900", -("2836200100", -("2836300100", -("2836400301", -("2836400302", -("2836500100", -("2836600100", -("2836910100", -("2836920100", -("2836999901", -("2836999999", -("2836999999", -("2836999999", -("2836999999", -("2837110201", -("2837110299", -("2837199900", -("2837199900", -("2837200201", -("2837200299", -("2839110100", -("2839199900", -("2839900401", -("2839900499", -("2839900499", -("2839900499", -("2840110100", -("2840199900", -("2840209900", -("2840300100", -("2841300100", -("2841500300", -("2841500300", -("2841500300", -("2841610100", -("2841699900", -("2841700100", -("2841800100", -("2841909901", -("2841909999", -("2842100201", -("2842100299", -("2842909901", -("2842909999", -("2843100100", -("2843210100", -("2843299900", -("2843300100", -("2843909900", -("2843909900", -("2844100100", -("2844200100", -("2844300100", -("2844400301", -("2844400302", -("2844400399", -("2844500100", -("2845100100", -("2845909900", -("2846100100", -("2846909900", -("2846909900", -("2846909900", -("2847000100", -("2849100100", -("2849209900", -("2849209900", -("2849909900", -("2850000300", -("2850000300", -("2850000300", -("2852100400", -("2852100400", -("2852100400", -("2852100400", -("2852909900", -("2852909900", -("2853100100", -("2853909901", -("2853909902", -("2853909903", -("2853909999", -("2853909999", -("2901100501", -("2901100502", -("2901100599", -("2901100599", -("2901100599", -("2901210100", -("2901220100", -("2901230100", -("2901240100", -("2901299900", -("2902110100", -("2902199901", -("2902199999", -("2902199999", -("2902199999", -("2902200100", -("2902300100", -("2902410100", -("2902420100", -("2902430100", -("2902440100", -("2902500100", -("2902600100", -("2902700100", -("2902909900", -("2902909900", -("2902909900", -("2902909900", -("2902909900", -("2902909900", -("2902909900", -("2902909900", -("2903110100", -("2903120100", -("2903130201", -("2903130299", -("2903140100", -("2903150100", -("2903199900", -("2903199900", -("2903199900", -("2903199900", -("2903199900", -("2903210100", -("2903220100", -("2903230100", -("2903299900", -("2903299900", -("2903310100", -("2903399901", -("2903399902", -("2903399999", -("2903710100", -("2903720100", -("2903730100", -("2903740100", -("2903750100", -("2903760100", -("2903770500", -("2903770500", -("2903770500", -("2903770500", -("2903770500", -("2903780100", -("2903799900", -("2903799900", -("2903799900", -("2903799900", -("2903829900", -("2903830100", -("2903899901", -("2903899999", -("2903899999", -("2903910100", -("2903920100", -("2903930100", -("2903940100", -("2903990100", -("2903999901", -("2903999999", -("2903999999", -("2903999999", -("2903999999", -("2904100100", -("2904100200", -("2904100300", -("2904100400", -("2904100500", -("2904109900", -("2904200800", -("2904209900", -("2904209900", -("2904209900", -("2904209900", -("2904209900", -("2904209900", -("2904209900", -("2904209900", -("2904310100", -("2904320100", -("2904330100", -("2904340100", -("2904350100", -("2904360100", -("2904910100", -("2904999901", -("2904999902", -("2904999903", -("2904999904", -("2904999905", -("2904999906", -("2904999999", -("2905110100", -("2905120201", -("2905120299", -("2905130100", -("2905140401", -("2905140402", -("2905140499", -("2905140499", -("2905160301", -("2905160399", -("2905160399", -("2905170100", -("2905190600", -("2905199901", -("2905199902", -("2905199903", -("2905199904", -("2905199999", -("2905199999", -("2905199999", -("2905199999", -("2905220601", -("2905220602", -("2905220603", -("2905220699", -("2905220699", -("2905220699", -("2905299901", -("2905299999", -("2905299999", -("2905299999", -("2905310100", -("2905320100", -("2905390100", -("2905390200", -("2905399901", -("2905399999", -("2905399999", -("2905410100", -("2905420100", -("2905430100", -("2905440100", -("2905450100", -("2905459900", -("2905499901", -("2905499999", -("2905499999", -("2905510100", -("2905599900", -("2905599900", -("2905599900", -("2905599900", -("2905599900", -("2905599900", -("2906110100", -("2906120200", -("2906120200", -("2906139900", -("2906139900", -("2906139900", -("2906199901", -("2906199999", -("2906199999", -("2906199999", -("2906210100", -("2906290500", -("2906299900", -("2906299900", -("2906299900", -("2906299900", -("2906299900", -("2906299900", -("2907110201", -("2907110299", -("2907120201", -("2907120299", -("2907130301", -("2907130399", -("2907130399", -("2907150300", -("2907150300", -("2907150300", -("2907199901", -("2907199902", -("2907199903", -("2907199999", -("2907199999", -("2907199999", -("2907199999", -("2907199999", -("2907199999", -("2907199999", -("2907210100", -("2907220100", -("2907220200", -("2907230301", -("2907230302", -("2907299900", -("2907299900", -("2907299900", -("2907299900", -("2907299900", -("2907299900", -("2908110100", -("2908199900", -("2908199900", -("2908199900", -("2908199900", -("2908199900", -("2908199900", -("2908199900", -("2908199900", -("2908910100", -("2908920100", -("2908990100", -("2908999901", -("2908999902", -("2908999999", -("2908999999", -("2908999999", -("2908999999", -("2908999999", -("2908999999", -("2908999999", -("2908999999", -("2908999999", -("2908999999", -("2909110100", -("2909199901", -("2909199902", -("2909199999", -("2909199999", -("2909200100", -("2909301001", -("2909301099", -("2909301099", -("2909301099", -("2909301099", -("2909301099", -("2909301099", -("2909301099", -("2909301099", -("2909301099", -("2909410100", -("2909430100", -("2909440100", -("2909440300", -("2909449900", -("2909449900", -("2909490100", -("2909490200", -("2909490300", -("2909490400", -("2909490500", -("2909490600", -("2909490700", -("2909490800", -("2909490900", -("2909499900", -("2909500100", -("2909509901", -("2909509901", -("2909509999", -("2909509999", -("2909509999", -("2909600100", -("2909600200", -("2909600300", -("2909600400", -("2909600500", -("2909609900", -("2910100100", -("2910200100", -("2910300100", -("2910400100", -("2910909900", -("2910909900", -("2911000400", -("2911000400", -("2911000400", -("2911000400", -("2912110100", -("2912120100", -("2912190500", -("2912199901", -("2912199902", -("2912199903", -("2912199999", -("2912199999", -("2912199999", -("2912199999", -("2912199999", -("2912199999", -("2912199999", -("2912199999", -("2912199999", -("2912210100", -("2912290200", -("2912299901", -("2912299902", -("2912299903", -("2912299999", -("2912299999", -("2912410100", -("2912420100", -("2912499901", -("2912499999", -("2912499999", -("2912499999", -("2912499999", -("2912500100", -("2912600100", -("2913000300", -("2913000300", -("2913000300", -("2914110100", -("2914120100", -("2914130100", -("2914190200", -("2914199901", -("2914199999", -("2914199999", -("2914199999", -("2914199999", -("2914199999", -("2914220100", -("2914230301", -("2914230302", -("2914290100", -("2914290200", -("2914290300", -("2914290400", -("2914299900", -("2914310100", -("2914399901", -("2914399999", -("2914399999", -("2914399999", -("2914399999", -("2914399999", -("2914399999", -("2914399999", -("2914400100", -("2914400200", -("2914409900", -("2914500401", -("2914500402", -("2914500499", -("2914500499", -("2914610100", -("2914620100", -("2914699900", -("2914699900", -("2914699900", -("2914710100", -("2914799901", -("2914799902", -("2914799903", -("2914799999", -("2915110100", -("2915120301", -("2915120399", -("2915120399", -("2915130100", -("2915210100", -("2915240100", -("2915299901", -("2915299999", -("2915299999", -("2915310100", -("2915320100", -("2915330100", -("2915360100", -("2915390200", -("2915390300", -("2915390400", -("2915390500", -("2915390900", -("2915391300", -("2915391500", -("2915391700", -("2915391800", -("2915399901", -("2915399902", -("2915399999", -("2915399999", -("2915399999", -("2915399999", -("2915399999", -("2915399999", -("2915399999", -("2915399999", -("2915400100", -("2915409900", -("2915409900", -("2915409900", -("2915409900", -("2915500300", -("2915509901", -("2915509902", -("2915509999", -("2915509999", -("2915509999", -("2915600100", -("2915600300", -("2915609901", -("2915609999", -("2915700100", -("2915700200", -("2915700300", -("2915700400", -("2915700500", -("2915700600", -("2915700700", -("2915709900", -("2915709900", -("2915709900", -("2915900100", -("2915900300", -("2915900400", -("2915900500", -("2915900600", -("2915900700", -("2915900800", -("2915900900", -("2915901000", -("2915901100", -("2915901200", -("2915901500", -("2915901600", -("2915901800", -("2915901900", -("2915902000", -("2915902200", -("2915902300", -("2915902400", -("2915902500", -("2915902600", -("2915902700", -("2915902900", -("2915903000", -("2915903100", -("2915903200", -("2915903300", -("2915909901", -("2915909902", -("2915909999", -("2915909999", -("2915909999", -("2915909999", -("2916110100", -("2916120100", -("2916120200", -("2916120300", -("2916129900", -("2916130100", -("2916140100", -("2916140200", -("2916140300", -("2916140400", -("2916149900", -("2916150500", -("2916150500", -("2916159900", -("2916159900", -("2916159900", -("2916160100", -("2916199901", -("2916199902", -("2916199903", -("2916199904", -("2916199999", -("2916199999", -("2916199999", -("2916200501", -("2916200599", -("2916200599", -("2916200599", -("2916200599", -("2916310100", -("2916310200", -("2916310300", -("2916319901", -("2916319999", -("2916319999", -("2916320301", -("2916320302", -("2916340100", -("2916390800", -("2916399901", -("2916399999", -("2916399999", -("2916399999", -("2916399999", -("2916399999", -("2916399999", -("2916399999", -("2917110200", -("2917110200", -("2917120100", -("2917130300", -("2917130300", -("2917130300", -("2917140100", -("2917190400", -("2917190800", -("2917199901", -("2917199902", -("2917199999", -("2917199999", -("2917199999", -("2917199999", -("2917199999", -("2917200401", -("2917200499", -("2917200499", -("2917200499", -("2917320100", -("2917330100", -("2917340100", -("2917349900", -("2917350100", -("2917360100", -("2917370100", -("2917390100", -("2917390200", -("2917390300", -("2917390400", -("2917390500", -("2917390600", -("2917399900", -("2918110100", -("2918120100", -("2918130500", -("2918130500", -("2918130500", -("2918130500", -("2918130500", -("2918140100", -("2918150100", -("2918150200", -("2918150500", -("2918159900", -("2918159900", -("2918159900", -("2918160301", -("2918160399", -("2918160399", -("2918170100", -("2918180100", -("2918190600", -("2918199900", -("2918199900", -("2918199900", -("2918199900", -("2918199900", -("2918199900", -("2918199900", -("2918199900", -("2918199900", -("2918199900", -("2918210200", -("2918219901", -("2918219999", -("2918220201", -("2918220299", -("2918230401", -("2918230402", -("2918230499", -("2918230499", -("2918290100", -("2918290400", -("2918299901", -("2918299902", -("2918299999", -("2918299999", -("2918299999", -("2918299999", -("2918299999", -("2918299999", -("2918299999", -("2918299999", -("2918299999", -("2918299999", -("2918299999", -("2918299999", -("2918299999", -("2918301001", -("2918301002", -("2918301099", -("2918301099", -("2918301099", -("2918301099", -("2918301099", -("2918301099", -("2918301099", -("2918301099", -("2918910100", -("2918990100", -("2918990200", -("2918990400", -("2918999901", -("2918999902", -("2918999999", -("2918999999", -("2918999999", -("2918999999", -("2918999999", -("2918999999", -("2918999999", -("2918999999", -("2918999999", -("2918999999", -("2918999999", -("2918999999", -("2918999999", -("2918999999", -("2918999999", -("2918999999", -("2919100100", -("2919901000", -("2919909901", -("2919909902", -("2919909903", -("2919909999", -("2919909999", -("2919909999", -("2919909999", -("2919909999", -("2919909999", -("2919909999", -("2919909999", -("2919909999", -("2920110100", -("2920199900", -("2920199900", -("2920199900", -("2920210100", -("2920220100", -("2920230100", -("2920240100", -("2920290100", -("2920299901", -("2920299902", -("2920299999", -("2920900900", -("2920909901", -("2920909902", -("2920909903", -("2920909904", -("2920909999", -("2920909999", -("2920909999", -("2921110501", -("2921110502", -("2921110503", -("2921119900", -("2921119900", -("2921120100", -("2921130100", -("2921140100", -("2921190200", -("2921190400", -("2921190500", -("2921191100", -("2921191200", -("2921199901", -("2921199902", -("2921199999", -("2921199999", -("2921199999", -("2921199999", -("2921199999", -("2921199999", -("2921199999", -("2921199999", -("2921199999", -("2921199999", -("2921210201", -("2921210299", -("2921220100", -("2921291000", -("2921299901", -("2921299902", -("2921299999", -("2921299999", -("2921299999", -("2921299999", -("2921299999", -("2921299999", -("2921299999", -("2921299999", -("2921299999", -("2921300200", -("2921309900", -("2921309900", -("2921410100", -("2921421400", -("2921421700", -("2921429901", -("2921429902", -("2921429999", -("2921429999", -("2921429999", -("2921429999", -("2921429999", -("2921429999", -("2921429999", -("2921429999", -("2921429999", -("2921429999", -("2921429999", -("2921429999", -("2921429999", -("2921429999", -("2921429999", -("2921431301", -("2921431399", -("2921431399", -("2921431399", -("2921431399", -("2921431399", -("2921431399", -("2921431399", -("2921431399", -("2921431399", -("2921431399", -("2921431399", -("2921431399", -("2921440201", -("2921440299", -("2921450800", -("2921459900", -("2921459900", -("2921459900", -("2921459900", -("2921459900", -("2921459900", -("2921459900", -("2921459900", -("2921460100", -("2921491400", -("2921499900", -("2921499900", -("2921499900", -("2921499900", -("2921499900", -("2921499900", -("2921499900", -("2921499900", -("2921499900", -("2921499900", -("2921499900", -("2921499900", -("2921499900", -("2921499900", -("2921499900", -("2921510100", -("2921510200", -("2921510300", -("2921510400", -("2921510500", -("2921510600", -("2921510700", -("2921510800", -("2921510900", -("2921519900", -("2921599900", -("2921599900", -("2921599900", -("2921599900", -("2921599900", -("2921599900", -("2921599900", -("2921599900", -("2921599900", -("2921599900", -("2921599900", -("2922110100", -("2922119900", -("2922120100", -("2922129900", -("2922140300", -("2922140300", -("2922140300", -("2922150100", -("2922160100", -("2922170100", -("2922180100", -("2922191000", -("2922191400", -("2922192000", -("2922194000", -("2922199901", -("2922199902", -("2922199903", -("2922199904", -("2922199905", -("2922199906", -("2922199907", -("2922199999", -("2922199999", -("2922199999", -("2922199999", -("2922199999", -("2922199999", -("2922199999", -("2922199999", -("2922199999", -("2922199999", -("2922199999", -("2922199999", -("2922199999", -("2922199999", -("2922199999", -("2922199999", -("2922199999", -("2922199999", -("2922199999", -("2922199999", -("2922199999", -("2922199999", -("2922199999", -("2922199999", -("2922199999", -("2922199999", -("2922199999", -("2922199999", -("2922199999", -("2922199999", -("2922199999", -("2922199999", -("2922210301", -("2922210399", -("2922210399", -("2922299901", -("2922299999", -("2922299999", -("2922299999", -("2922299999", -("2922299999", -("2922299999", -("2922299999", -("2922299999", -("2922299999", -("2922299999", -("2922299999", -("2922299999", -("2922299999", -("2922299999", -("2922299999", -("2922299999", -("2922299999", -("2922299999", -("2922299999", -("2922299999", -("2922299999", -("2922299999", -("2922299999", -("2922299999", -("2922310100", -("2922390900", -("2922399901", -("2922399902", -("2922399999", -("2922399999", -("2922399999", -("2922399999", -("2922399999", -("2922399999", -("2922399999", -("2922399999", -("2922399999", -("2922410201", -("2922410299", -("2922420100", -("2922429900", -("2922430100", -("2922440100", -("2922490100", -("2922490200", -("2922490300", -("2922490400", -("2922490500", -("2922490600", -("2922490700", -("2922490800", -("2922490900", -("2922491000", -("2922491100", -("2922491200", -("2922491300", -("2922491400", -("2922491500", -("2922491600", -("2922491700", -("2922491800", -("2922491900", -("2922492000", -("2922492100", -("2922492200", -("2922492300", -("2922499900", -("2922500100", -("2922509901", -("2922509902", -("2922509903", -("2922509904", -("2922509905", -("2922509999", -("2922509999", -("2922509999", -("2922509999", -("2922509999", -("2922509999", -("2922509999", -("2922509999", -("2922509999", -("2922509999", -("2922509999", -("2922509999", -("2922509999", -("2922509999", -("2922509999", -("2922509999", -("2922509999", -("2923100100", -("2923100200", -("2923109900", -("2923200101", -("2923200199", -("2923300100", -("2923400100", -("2923900200", -("2923900300", -("2923909901", -("2923909902", -("2923909999", -("2923909999", -("2923909999", -("2923909999", -("2923909999", -("2923909999", -("2923909999", -("2923909999", -("2923909999", -("2924110100", -("2924129900", -("2924129900", -("2924190100", -("2924190200", -("2924190400", -("2924190500", -("2924190600", -("2924190700", -("2924190800", -("2924190900", -("2924191000", -("2924191100", -("2924191200", -("2924191300", -("2924191500", -("2924191500", -("2924199900", -("2924210801", -("2924210802", -("2924210899", -("2924210899", -("2924210899", -("2924210899", -("2924210899", -("2924210899", -("2924230100", -("2924240100", -("2924290900", -("2924299901", -("2924299902", -("2924299903", -("2924299904", -("2924299905", -("2924299906", -("2924299999", -("2924299999", -("2924299999", -("2924299999", -("2924299999", -("2924299999", -("2924299999", -("2924299999", -("2924299999", -("2924299999", -("2924299999", -("2924299999", -("2924299999", -("2924299999", -("2924299999", -("2924299999", -("2924299999", -("2924299999", -("2924299999", -("2924299999", -("2924299999", -("2924299999", -("2924299999", -("2924299999", -("2924299999", -("2924299999", -("2924299999", -("2924299999", -("2924299999", -("2924299999", -("2924299999", -("2924299999", -("2924299999", -("2924299999", -("2924299999", -("2924299999", -("2924299999", -("2924299999", -("2924299999", -("2924299999", -("2924299999", -("2925110100", -("2925120100", -("2925199901", -("2925199999", -("2925199999", -("2925199999", -("2925210100", -("2925299900", -("2925299900", -("2925299900", -("2925299900", -("2925299900", -("2926100100", -("2926200100", -("2926300100", -("2926400100", -("2926909901", -("2926909902", -("2926909903", -("2926909999", -("2926909999", -("2926909999", -("2926909999", -("2926909999", -("2926909999", -("2926909999", -("2927000601", -("2927000602", -("2927000699", -("2927000699", -("2927000699", -("2927000699", -("2928000100", -("2928000200", -("2928000300", -("2928000500", -("2928000600", -("2928000700", -("2928009900", -("2928009900", -("2929100100", -("2929100200", -("2929100400", -("2929100500", -("2929100600", -("2929109901", -("2929109999", -("2929909900", -("2929909900", -("2929909900", -("2930200500", -("2930200600", -("2930200700", -("2930209900", -("2930209900", -("2930209900", -("2930209900", -("2930209900", -("2930209900", -("2930209900", -("2930300201", -("2930300299", -("2930400100", -("2930600100", -("2930700100", -("2930809900", -("2930901100", -("2930903100", -("2930909901", -("2930909902", -("2930909903", -("2930909904", -("2930909905", -("2930909906", -("2930909907", -("2930909908", -("2930909909", -("2930909910", -("2930909911", -("2930909912", -("2930909999", -("2930909999", -("2930909999", -("2930909999", -("2930909999", -("2930909999", -("2930909999", -("2930909999", -("2930909999", -("2930909999", -("2930909999", -("2930909999", -("2930909999", -("2930909999", -("2930909999", -("2930909999", -("2930909999", -("2930909999", -("2930909999", -("2930909999", -("2930909999", -("2930909999", -("2930909999", -("2930909999", -("2930909999", -("2930909999", -("2930909999", -("2930909999", -("2930909999", -("2930909999", -("2930909999", -("2930909999", -("2930909999", -("2930909999", -("2930909999", -("2930909999", -("2930909999", -("2930909999", -("2930909999", -("2930909999", -("2930909999", -("2930909999", -("2930909999", -("2930909999", -("2930909999", -("2930909999", -("2930909999", -("2930909999", -("2930909999", -("2930909999", -("2930909999", -("2930909999", -("2930909999", -("2930909999", -("2930909999", -("2930909999", -("2931100100", -("2931200100", -("2931310100", -("2931320100", -("2931330100", -("2931340100", -("2931350100", -("2931360100", -("2931370100", -("2931380100", -("2931390100", -("2931399901", -("2931399902", -("2931399903", -("2931399904", -("2931399905", -("2931399906", -("2931399907", -("2931399908", -("2931399999", -("2931900300", -("2931900900", -("2931909900", -("2931909900", -("2931909900", -("2931909900", -("2931909900", -("2931909900", -("2931909900", -("2931909900", -("2931909900", -("2931909900", -("2931909900", -("2932110100", -("2932120100", -("2932130100", -("2932140100", -("2932190200", -("2932199901", -("2932199901", -("2932199902", -("2932199999", -("2932199999", -("2932201101", -("2932201102", -("2932201103", -("2932201199", -("2932201199", -("2932201199", -("2932201199", -("2932201199", -("2932201199", -("2932201199", -("2932201199", -("2932910100", -("2932920100", -("2932930100", -("2932940100", -("2932950100", -("2932999901", -("2932999902", -("2932999903", -("2932999904", -("2932999999", -("2932999999", -("2932999999", -("2932999999", -("2932999999", -("2932999999", -("2932999999", -("2933110300", -("2933110300", -("2933110300", -("2933190400", -("2933190800", -("2933199991", -("2933199999", -("2933199999", -("2933199999", -("2933199999", -("2933199999", -("2933199999", -("2933199999", -("2933210100", -("2933299901", -("2933299901", -("2933299901", -("2933299902", -("2933299902", -("2933299902", -("2933299902", -("2933299902", -("2933299903", -("2933299904", -("2933299905", -("2933299999", -("2933299999", -("2933299999", -("2933299999", -("2933299999", -("2933299999", -("2933310300", -("2933310300", -("2933320100", -("2933329900", -("2933330301", -("2933330302", -("2933390200", -("2933390600", -("2933391700", -("2933392200", -("2933392400", -("2933392400", -("2933399901", -("2933399902", -("2933399903", -("2933399904", -("2933399905", -("2933399906", -("2933399907", -("2933399999", -("2933399999", -("2933399999", -("2933399999", -("2933399999", -("2933399999", -("2933399999", -("2933399999", -("2933399999", -("2933399999", -("2933399999", -("2933399999", -("2933399999", -("2933399999", -("2933399999", -("2933399999", -("2933399999", -("2933399999", -("2933399999", -("2933410100", -("2933490100", -("2933490400", -("2933491000", -("2933499901", -("2933499902", -("2933499999", -("2933499999", -("2933499999", -("2933499999", -("2933499999", -("2933499999", -("2933499999", -("2933499999", -("2933499999", -("2933520100", -("2933530100", -("2933540100", -("2933550300", -("2933550300", -("2933550300", -("2933590700", -("2933599901", -("2933599901", -("2933599902", -("2933599902", -("2933599902", -("2933599903", -("2933599904", -("2933599905", -("2933599906", -("2933599907", -("2933599908", -("2933599999", -("2933599999", -("2933599999", -("2933599999", -("2933599999", -("2933599999", -("2933599999", -("2933599999", -("2933599999", -("2933610100", -("2933691200", -("2933691500", -("2933699901", -("2933699902", -("2933699903", -("2933699904", -("2933699991", -("2933699991", -("2933699991", -("2933699991", -("2933699991", -("2933699991", -("2933699999", -("2933699999", -("2933699999", -("2933699999", -("2933710100", -("2933720100", -("2933790501", -("2933790502", -("2933790599", -("2933790599", -("2933790599", -("2933910301", -("2933910302", -("2933990400", -("2933991000", -("2933992600", -("2933999901", -("2933999902", -("2933999902", -("2933999903", -("2933999904", -("2933999905", -("2933999906", -("2933999907", -("2933999908", -("2933999909", -("2933999909", -("2933999910", -("2933999910", -("2933999911", -("2933999912", -("2933999913", -("2933999914", -("2933999915", -("2933999999", -("2933999999", -("2933999999", -("2933999999", -("2933999999", -("2933999999", -("2933999999", -("2933999999", -("2933999999", -("2933999999", -("2933999999", -("2933999999", -("2933999999", -("2933999999", -("2933999999", -("2933999999", -("2933999999", -("2933999999", -("2933999999", -("2933999999", -("2933999999", -("2933999999", -("2933999999", -("2933999999", -("2933999999", -("2933999999", -("2933999999", -("2934100901", -("2934100999", -("2934100999", -("2934100999", -("2934100999", -("2934100999", -("2934100999", -("2934100999", -("2934100999", -("2934200500", -("2934209901", -("2934209902", -("2934209999", -("2934209999", -("2934209999", -("2934209999", -("2934300200", -("2934300200", -("2934910100", -("2934990100", -("2934991000", -("2934991400", -("2934992400", -("2934994100", -("2934999901", -("2934999902", -("2934999903", -("2934999999", -("2934999999", -("2934999999", -("2934999999", -("2934999999", -("2934999999", -("2934999999", -("2934999999", -("2934999999", -("2934999999", -("2934999999", -("2934999999", -("2934999999", -("2934999999", -("2934999999", -("2934999999", -("2934999999", -("2934999999", -("2934999999", -("2934999999", -("2934999999", -("2934999999", -("2934999999", -("2934999999", -("2934999999", -("2934999999", -("2934999999", -("2934999999", -("2934999999", -("2934999999", -("2934999999", -("2934999999", -("2934999999", -("2934999999", -("2934999999", -("2934999999", -("2934999999", -("2934999999", -("2934999999", -("2934999999", -("2934999999", -("2934999999", -("2935100100", -("2935200100", -("2935300100", -("2935400100", -("2935500100", -("2935900300", -("2935900700", -("2935902000", -("2935903100", -("2935909901", -("2935909902", -("2935909903", -("2935909904", -("2935909905", -("2935909906", -("2935909907", -("2935909908", -("2935909909", -("2935909910", -("2935909911", -("2935909912", -("2935909913", -("2935909914", -("2935909915", -("2935909916", -("2935909917", -("2935909918", -("2935909919", -("2935909920", -("2935909921", -("2935909922", -("2935909923", -("2935909924", -("2935909925", -("2935909926", -("2935909927", -("2935909928", -("2935909929", -("2935909930", -("2935909931", -("2935909932", -("2935909933", -("2935909999", -("2936210401", -("2936210499", -("2936210499", -("2936220401", -("2936220499", -("2936220499", -("2936220499", -("2936230301", -("2936230399", -("2936230399", -("2936240301", -("2936240302", -("2936240399", -("2936250201", -("2936250299", -("2936260100", -("2936269900", -("2936269900", -("2936270201", -("2936270299", -("2936280301", -("2936280399", -("2936290300", -("2936290400", -("2936299901", -("2936299902", -("2936299903", -("2936299999", -("2936299999", -("2936299999", -("2936299999", -("2936909900", -("2936909900", -("2936909900", -("2936909900", -("2936909900", -("2937110100", -("2937120201", -("2937120299", -("2937199901", -("2937199999", -("2937199999", -("2937199999", -("2937199999", -("2937199999", -("2937210501", -("2937210599", -("2937210599", -("2937210599", -("2937221100", -("2937221100", -("2937229901", -("2937229902", -("2937229999", -("2937229999", -("2937229999", -("2937229999", -("2937229999", -("2937229999", -("2937229999", -("2937230400", -("2937230500", -("2937230700", -("2937230800", -("2937231000", -("2937231100", -("2937231700", -("2937231800", -("2937232100", -("2937232200", -("2937239901", -("2937239902", -("2937239903", -("2937239904", -("2937239905", -("2937239906", -("2937239999", -("2937239999", -("2937239999", -("2937239999", -("2937239999", -("2937239999", -("2937239999", -("2937239999", -("2937290900", -("2937291700", -("2937291900", -("2937292400", -("2937292900", -("2937293200", -("2937293300", -("2937293600", -("2937299901", -("2937299902", -("2937299903", -("2937299904", -("2937299905", -("2937299906", -("2937299907", -("2937299999", -("2937299999", -("2937299999", -("2937299999", -("2937299999", -("2937299999", -("2937299999", -("2937299999", -("2937299999", -("2937299999", -("2937299999", -("2937299999", -("2937299999", -("2937299999", -("2937299999", -("2937299999", -("2937299999", -("2937299999", -("2937299999", -("2937299999", -("2937299999", -("2937299999", -("2937299999", -("2937299999", -("2937299999", -("2937299999", -("2937299999", -("2937299999", -("2937500200", -("2937500200", -("2937909901", -("2937909999", -("2937909999", -("2937909999", -("2937909999", -("2938100100", -("2938909901", -("2938909902", -("2938909999", -("2938909999", -("2938909999", -("2938909999", -("2938909999", -("2938909999", -("2938909999", -("2939119900", -("2939119900", -("2939119900", -("2939119900", -("2939119900", -("2939119900", -("2939119900", -("2939119900", -("2939119900", -("2939119900", -("2939119900", -("2939119900", -("2939119900", -("2939119900", -("2939119900", -("2939119900", -("2939119900", -("2939199991", -("2939199999", -("2939199999", -("2939199999", -("2939199999", -("2939199999", -("2939199999", -("2939200200", -("2939200200", -("2939300401", -("2939300402", -("2939300499", -("2939410100", -("2939420100", -("2939430100", -("2939440100", -("2939449900", -("2939499900", -("2939510100", -("2939599900", -("2939599900", -("2939599900", -("2939610100", -("2939620100", -("2939630100", -("2939699900", -("2939699900", -("2939710201", -("2939710299", -("2939799901", -("2939799902", -("2939799903", -("2939799904", -("2939799905", -("2939799906", -("2939799907", -("2939799908", -("2939799909", -("2939799910", -("2939799911", -("2939799912", -("2939799913", -("2939799914", -("2939799915", -("2939799916", -("2939799917", -("2939799918", -("2939799999", -("2939809900", -("2940000501", -("2940000599", -("2940000599", -("2940000599", -("2940000599", -("2941100300", -("2941100700", -("2941100800", -("2941101200", -("2941109901", -("2941109902", -("2941109999", -("2941109999", -("2941109999", -("2941109999", -("2941109999", -("2941109999", -("2941109999", -("2941109999", -("2941200100", -("2941300401", -("2941300499", -("2941300499", -("2941300499", -("2941400401", -("2941400402", -("2941400403", -("2941500200", -("2941500200", -("2941901700", -("2941909901", -("2941909902", -("2941909903", -("2941909904", -("2941909905", -("2941909906", -("2941909907", -("2941909908", -("2941909909", -("2941909910", -("2941909911", -("2941909999", -("2941909999", -("2941909999", -("2941909999", -("2941909999", -("2941909999", -("2942009900", -("3001200300", -("3001200400", -("3001200500", -("3001200600", -("3001200600", -("3001209900", -("3001900100", -("3001900300", -("3001900400", -("3001900500", -("3001900600", -("3001900800", -("3001909901", -("3001909999", -("3001909999", -("3002110100", -("3002120100", -("3002120400", -("3002120700", -("3002120800", -("3002120900", -("3002129901", -("3002129902", -("3002129903", -("3002129904", -("3002129905", -("3002129906", -("3002129907", -("3002129908", -("3002129909", -("3002129910", -("3002129999", -("3002130100", -("3002130500", -("3002139901", -("3002139999", -("3002140100", -("3002149900", -("3002150200", -("3002150300", -("3002159901", -("3002159999", -("3002199900", -("3002200200", -("3002200600", -("3002200700", -("3002200800", -("3002200900", -("3002209901", -("3002209902", -("3002209903", -("3002209999", -("3002209999", -("3002300301", -("3002300399", -("3002300399", -("3002900100", -("3002909900", -("3002909900", -("3002909900", -("3002909900", -("3003100100", -("3003200100", -("3003209900", -("3003310200", -("3003310200", -("3003390100", -("3003399901", -("3003399999", -("3003410100", -("3003420100", -("3003430100", -("3003490300", -("3003499901", -("3003499999", -("3003600100", -("3003900100", -("3003900200", -("3003900300", -("3003900400", -("3003900800", -("3003900900", -("3003901000", -("3003901100", -("3003901200", -("3003901300", -("3003901400", -("3003901500", -("3003901600", -("3003901700", -("3003901800", -("3003901900", -("3003902000", -("3003902100", -("3003902200", -("3003902200", -("3003909900", -("3004100100", -("3004109900", -("3004200100", -("3004200200", -("3004200300", -("3004209900", -("3004310201", -("3004310299", -("3004320100", -("3004329900", -("3004390100", -("3004390200", -("3004390300", -("3004390400", -("3004390500", -("3004399901", -("3004399999", -("3004410100", -("3004420100", -("3004430100", -("3004490300", -("3004490500", -("3004499901", -("3004499999", -("3004500100", -("3004500200", -("3004509901", -("3004509999", -("3004609900", -("3004900100", -("3004900200", -("3004900400", -("3004900500", -("3004900600", -("3004900700", -("3004900800", -("3004900900", -("3004901100", -("3004901200", -("3004901300", -("3004901400", -("3004901500", -("3004901600", -("3004901700", -("3004901800", -("3004901900", -("3004902000", -("3004902100", -("3004902200", -("3004902300", -("3004902400", -("3004902500", -("3004902600", -("3004902700", -("3004902800", -("3004902900", -("3004903000", -("3004903100", -("3004903200", -("3004903400", -("3004903500", -("3004903600", -("3004903700", -("3004903800", -("3004903900", -("3004904000", -("3004904100", -("3004904300", -("3004904400", -("3004904500", -("3004904600", -("3004904700", -("3004904800", -("3004904900", -("3004905000", -("3004905200", -("3004909901", -("3004909902", -("3004909999", -("3005100100", -("3005100200", -("3005109900", -("3005900100", -("3005900300", -("3005909901", -("3005909999", -("3006100301", -("3006100302", -("3006109900", -("3006200100", -("3006209900", -("3006300100", -("3006300200", -("3006309900", -("3006400200", -("3006400300", -("3006409901", -("3006409999", -("3006500100", -("3006600100", -("3006700100", -("3006910100", -("3006920100", -("3101000100", -("3102100100", -("3102210100", -("3102299900", -("3102300201", -("3102300299", -("3102400100", -("3102500100", -("3102600100", -("3102800100", -("3102900200", -("3102900200", -("3103110100", -("3103199900", -("3103909900", -("3103909900", -("3104200100", -("3104300201", -("3104300299", -("3104909900", -("3104909900", -("3105100100", -("3105200100", -("3105300100", -("3105400100", -("3105510100", -("3105599900", -("3105600100", -("3105909901", -("3105909999", -("3201100100", -("3201200100", -("3201909901", -("3201909999", -("3202100100", -("3202909900", -("3203000301", -("3203000399", -("3203000399", -("3204110100", -("3204119901", -("3204119902", -("3204119991", -("3204119999", -("3204120701", -("3204120702", -("3204120703", -("3204120704", -("3204120799", -("3204120799", -("3204120799", -("3204130501", -("3204130502", -("3204130503", -("3204130599", -("3204130599", -("3204140501", -("3204140502", -("3204140503", -("3204140599", -("3204140599", -("3204150201", -("3204150299", -("3204160501", -("3204160502", -("3204160503", -("3204160504", -("3204160599", -("3204170100", -("3204170200", -("3204170300", -("3204170400", -("3204170500", -("3204170600", -("3204170700", -("3204170800", -("3204171000", -("3204179901", -("3204179999", -("3204190400", -("3204190600", -("3204199901", -("3204199902", -("3204199903", -("3204199904", -("3204199905", -("3204199999", -("3204199999", -("3204199999", -("3204200100", -("3204200200", -("3204209900", -("3204909901", -("3204909999", -("3204909999", -("3205000200", -("3205009900", -("3205009900", -("3206110100", -("3206199900", -("3206200100", -("3206200200", -("3206200300", -("3206410201", -("3206410299", -("3206420200", -("3206420200", -("3206490100", -("3206490200", -("3206490300", -("3206490400", -("3206490500", -("3206490700", -("3206490800", -("3206499901", -("3206499999", -("3206500100", -("3206509900", -("3206509900", -("3207100200", -("3207100200", -("3207200100", -("3207209900", -("3207300100", -("3207400301", -("3207400302", -("3207400399", -("3208100201", -("3208100299", -("3208200301", -("3208200399", -("3208200399", -("3208909900", -("3208909900", -("3209100201", -("3209100299", -("3209909900", -("3210000400", -("3210000400", -("3210000400", -("3210000400", -("3211000200", -("3211000200", -("3212100100", -("3212909901", -("3212909902", -("3212909999", -("3213100100", -("3213909900", -("3214100100", -("3214100200", -("3214909900", -("3215110301", -("3215110302", -("3215110399", -("3215190200", -("3215199901", -("3215199999", -("3215199999", -("3215900200", -("3215900300", -("3215909901", -("3215909999", -("3301120100", -("3301130201", -("3301130299", -("3301190701", -("3301190702", -("3301190703", -("3301190704", -("3301199901", -("3301199999", -("3301199999", -("3301240100", -("3301259900", -("3301299901", -("3301299902", -("3301299903", -("3301299999", -("3301299999", -("3301299999", -("3301299999", -("3301299999", -("3301300100", -("3301900100", -("3301900400", -("3301900500", -("3301900600", -("3301900701", -("3301900702", -("3301909900", -("3302100100", -("3302100200", -("3302109900", -("3302909900", -("3303000100", -("3303009900", -("3304100100", -("3304200100", -("3304300100", -("3304910100", -("3304990100", -("3304999900", -("3305100100", -("3305200100", -("3305300100", -("3305909900", -("3306100100", -("3306200201", -("3306200299", -("3306909900", -("3307100100", -("3307200100", -("3307300100", -("3307410100", -("3307499900", -("3307909900", -("3401110100", -("3401199900", -("3401200100", -("3401300100", -("3402110200", -("3402119901", -("3402119902", -("3402119999", -("3402120300", -("3402129901", -("3402129999", -("3402129999", -("3402130100", -("3402130200", -("3402130300", -("3402139900", -("3402199900", -("3402200100", -("3402200200", -("3402200300", -("3402200400", -("3402200500", -("3402209900", -("3402900100", -("3402900200", -("3402909900", -("3403110100", -("3403190100", -("3403199900", -("3403910100", -("3403999900", -("3404200100", -("3404900100", -("3404900200", -("3404909900", -("3405100100", -("3405200200", -("3405200200", -("3405300100", -("3405400100", -("3405900100", -("3405909900", -("3406000100", -("3407000300", -("3407000400", -("3407000501", -("3407000502", -("3407009900", -("3501100100", -("3501900100", -("3501900300", -("3501909901", -("3501909999", -("3502110100", -("3502199900", -("3502200100", -("3502909900", -("3503000100", -("3503009901", -("3503009902", -("3503009999", -("3503009999", -("3504000701", -("3504000702", -("3504000799", -("3504000799", -("3504000799", -("3504000799", -("3504000799", -("3505100100", -("3505200100", -("3506100200", -("3506109901", -("3506109999", -("3506910100", -("3506919901", -("3506919902", -("3506919903", -("3506919999", -("3506990100", -("3506999900", -("3507100100", -("3507900200", -("3507900600", -("3507900800", -("3507900900", -("3507909901", -("3507909902", -("3507909903", -("3507909999", -("3507909999", -("3507909999", -("3507909999", -("3507909999", -("3507909999", -("3601000100", -("3601009900", -("3602000200", -("3602000300", -("3602009900", -("3602009900", -("3603000100", -("3603000200", -("3603009900", -("3604100100", -("3604900100", -("3605000100", -("3606100100", -("3606900100", -("3606900200", -("3606909900", -("3701100200", -("3701109901", -("3701109999", -("3701200100", -("3701300100", -("3701910100", -("3701990100", -("3701990400", -("3701999901", -("3701999902", -("3701999999", -("3701999999", -("3702100100", -("3702109900", -("3702310200", -("3702310200", -("3702320100", -("3702329900", -("3702399900", -("3702399900", -("3702410100", -("3702419900", -("3702420100", -("3702420200", -("3702429900", -("3702430100", -("3702440100", -("3702449900", -("3702520100", -("3702530100", -("3702540100", -("3702550100", -("3702560100", -("3702960100", -("3702970100", -("3702980100", -("3703100200", -("3703100200", -("3703200100", -("3703209900", -("3703900100", -("3703900200", -("3703900300", -("3703909900", -("3704000100", -("3705000100", -("3705000200", -("3705000300", -("3705000400", -("3705009900", -("3706100100", -("3706100200", -("3706900100", -("3706909900", -("3707100100", -("3707900201", -("3707900299", -("3801100100", -("3801109900", -("3801200100", -("3801300201", -("3801300299", -("3801900100", -("3801909900", -("3801909900", -("3801909900", -("3801909900", -("3802100100", -("3802900601", -("3802900602", -("3802909901", -("3802909999", -("3802909999", -("3802909999", -("3803000100", -("3804000201", -("3804000299", -("3805100201", -("3805100299", -("3805900100", -("3805909900", -("3806100100", -("3806109900", -("3806200100", -("3806300301", -("3806300399", -("3806300399", -("3806900100", -("3806909900", -("3807000100", -("3808590100", -("3808599901", -("3808599902", -("3808599999", -("3808599999", -("3808599999", -("3808599999", -("3808599999", -("3808610100", -("3808620100", -("3808699900", -("3808919901", -("3808919999", -("3808919999", -("3808919999", -("3808920301", -("3808920302", -("3808920399", -("3808930401", -("3808930402", -("3808930499", -("3808940100", -("3808940200", -("3808949900", -("3808999900", -("3808999900", -("3809100100", -("3809910100", -("3809919900", -("3809920100", -("3809920200", -("3809929901", -("3809929999", -("3809930100", -("3810100100", -("3810909901", -("3810909999", -("3811110200", -("3811110200", -("3811199900", -("3811210100", -("3811210200", -("3811210300", -("3811210400", -("3811210500", -("3811210600", -("3811210700", -("3811219900", -("3811290400", -("3811290500", -("3811290600", -("3811299900", -("3811299900", -("3811299900", -("3811299900", -("3811900100", -("3811909900", -("3812100100", -("3812200100", -("3812310100", -("3812390200", -("3812390300", -("3812390400", -("3812390500", -("3812399901", -("3812399999", -("3813000100", -("3814000100", -("3815110301", -("3815110399", -("3815110399", -("3815120301", -("3815120399", -("3815120399", -("3815190100", -("3815199900", -("3815900200", -("3815909901", -("3815909902", -("3815909999", -("3816000200", -("3816000600", -("3816000700", -("3816009901", -("3816009902", -("3816009999", -("3816009999", -("3816009999", -("3817000100", -("3817000200", -("3817000300", -("3817009900", -("3817009900", -("3818000100", -("3819000300", -("3819000401", -("3819000402", -("3819009900", -("3820000100", -("3821000100", -("3822000300", -("3822000500", -("3822009901", -("3822009902", -("3822009999", -("3822009999", -("3823110100", -("3823120301", -("3823120302", -("3823130100", -("3823190300", -("3823199901", -("3823199999", -("3823199999", -("3823700201", -("3823700299", -("3824100100", -("3824300201", -("3824300299", -("3824400100", -("3824409900", -("3824500100", -("3824500200", -("3824500300", -("3824509900", -("3824600100", -("3824710100", -("3824720100", -("3824730100", -("3824740100", -("3824750100", -("3824760100", -("3824770100", -("3824780100", -("3824799900", -("3824810100", -("3824820100", -("3824830100", -("3824840100", -("3824850100", -("3824860100", -("3824870100", -("3824880100", -("3824910100", -("3824990100", -("3824990200", -("3824990300", -("3824990600", -("3824990800", -("3824990900", -("3824991000", -("3824991100", -("3824991300", -("3824991400", -("3824991600", -("3824991800", -("3824991900", -("3824992000", -("3824992200", -("3824992300", -("3824992500", -("3824992600", -("3824992800", -("3824992900", -("3824993000", -("3824993100", -("3824993200", -("3824993300", -("3824993500", -("3824993700", -("3824993800", -("3824994000", -("3824994100", -("3824994300", -("3824994400", -("3824994500", -("3824994600", -("3824994700", -("3824994800", -("3824994900", -("3824995000", -("3824995100", -("3824995200", -("3824995300", -("3824995400", -("3824995500", -("3824995600", -("3824995800", -("3824995900", -("3824996000", -("3824996100", -("3824996200", -("3824996300", -("3824996400", -("3824996500", -("3824996600", -("3824996700", -("3824996800", -("3824996900", -("3824997000", -("3824997200", -("3824997300", -("3824997400", -("3824997500", -("3824997600", -("3824999901", -("3824999902", -("3824999903", -("3824999904", -("3824999999", -("3824999999", -("3824999999", -("3824999999", -("3824999999", -("3824999999", -("3824999999", -("3824999999", -("3824999999", -("3824999999", -("3824999999", -("3824999999", -("3824999999", -("3825100100", -("3825200100", -("3825300100", -("3825410100", -("3825499900", -("3825500100", -("3825610300", -("3825610300", -("3825610300", -("3825699900", -("3825699900", -("3825909900", -("3826000100", -("3901100301", -("3901100302", -("3901200100", -("3901300100", -("3901400100", -("3901900100", -("3901909901", -("3901909902", -("3901909999", -("3902100100", -("3902109900", -("3902200201", -("3902200299", -("3902300100", -("3902309900", -("3902900100", -("3902909900", -("3903110100", -("3903199901", -("3903199999", -("3903200100", -("3903300100", -("3903900100", -("3903900200", -("3903900300", -("3903900500", -("3903909901", -("3903909999", -("3904100100", -("3904100200", -("3904100300", -("3904100400", -("3904109900", -("3904210100", -("3904219901", -("3904219999", -("3904220100", -("3904300100", -("3904309900", -("3904400100", -("3904409900", -("3904500100", -("3904610100", -("3904699900", -("3904909900", -("3905120100", -("3905190100", -("3905190200", -("3905190300", -("3905199900", -("3905210100", -("3905290100", -("3905290200", -("3905299900", -("3905300100", -("3905910100", -("3905999901", -("3905999902", -("3905999999", -("3906100201", -("3906100299", -("3906900100", -("3906900200", -("3906900300", -("3906900400", -("3906900500", -("3906900600", -("3906900700", -("3906900800", -("3906900900", -("3906901000", -("3906909900", -("3907100501", -("3907100502", -("3907100599", -("3907100599", -("3907100599", -("3907200100", -("3907200200", -("3907200300", -("3907200400", -("3907200500", -("3907200600", -("3907200700", -("3907209900", -("3907300200", -("3907309901", -("3907309999", -("3907400401", -("3907400402", -("3907400403", -("3907400499", -("3907500200", -("3907509901", -("3907509999", -("3907610100", -("3907690100", -("3907699900", -("3907700100", -("3907910100", -("3907919900", -("3907990100", -("3907990200", -("3907990300", -("3907990400", -("3907990500", -("3907990600", -("3907990700", -("3907990800", -("3907990900", -("3907991000", -("3907991100", -("3907999900", -("3908100100", -("3908100300", -("3908100400", -("3908100500", -("3908100600", -("3908100700", -("3908100800", -("3908900100", -("3908900200", -("3908900300", -("3908909900", -("3909100100", -("3909200200", -("3909209901", -("3909209999", -("3909310100", -("3909390100", -("3909399900", -("3909400100", -("3909400300", -("3909409901", -("3909409902", -("3909409999", -("3909500400", -("3909500500", -("3909509901", -("3909509902", -("3909509903", -("3909509999", -("3910000100", -("3910000200", -("3910000300", -("3910000400", -("3910000500", -("3910009900", -("3911100100", -("3911900100", -("3911900200", -("3911900300", -("3911900400", -("3911900500", -("3911900600", -("3911900700", -("3911909900", -("3912110100", -("3912120100", -("3912200201", -("3912200299", -("3912310100", -("3912390100", -("3912390200", -("3912390300", -("3912399901", -("3912399902", -("3912399999", -("3912900100", -("3912900200", -("3912909900", -("3913100601", -("3913100602", -("3913100603", -("3913100699", -("3913100699", -("3913100699", -("3913900200", -("3913900500", -("3913900600", -("3913909901", -("3913909902", -("3913909903", -("3913909999", -("3913909999", -("3913909999", -("3914000100", -("3914009900", -("3915100100", -("3915200100", -("3915300100", -("3915900100", -("3915909901", -("3915909999", -("3916100201", -("3916100299", -("3916200300", -("3916200300", -("3916200300", -("3916900500", -("3916900500", -("3916900500", -("3916900500", -("3916900500", -("3917100501", -("3917100502", -("3917100503", -("3917100599", -("3917100599", -("3917210301", -("3917210302", -("3917210399", -("3917220301", -("3917220399", -("3917220399", -("3917230401", -("3917230402", -("3917230499", -("3917230499", -("3917290601", -("3917290699", -("3917290699", -("3917290699", -("3917290699", -("3917290699", -("3917310100", -("3917320300", -("3917320300", -("3917320300", -("3917330100", -("3917339900", -("3917399900", -("3917400100", -("3918100201", -("3918100299", -("3918909900", -("3919100100", -("3919909900", -("3920100501", -("3920100502", -("3920100503", -("3920100504", -("3920100599", -("3920200501", -("3920200502", -("3920200503", -("3920200504", -("3920200599", -("3920300401", -("3920300402", -("3920300403", -("3920300499", -("3920430301", -("3920430302", -("3920430399", -("3920499901", -("3920499902", -("3920499999", -("3920510100", -("3920599901", -("3920599999", -("3920610100", -("3920620200", -("3920629901", -("3920629999", -("3920630301", -("3920630399", -("3920630399", -("3920699900", -("3920710100", -("3920730201", -("3920730299", -("3920790201", -("3920790299", -("3920910100", -("3920920201", -("3920920299", -("3920930100", -("3920940100", -("3920990200", -("3920990200", -("3921110100", -("3921120100", -("3921130201", -("3921130299", -("3921140100", -("3921190201", -("3921190299", -("3921900900", -("3921909901", -("3921909902", -("3921909903", -("3921909904", -("3921909999", -("3921909999", -("3921909999", -("3921909999", -("3921909999", -("3922100100", -("3922200100", -("3922909900", -("3923100301", -("3923100302", -("3923210100", -("3923290301", -("3923290302", -("3923290399", -("3923300201", -("3923300299", -("3923400100", -("3923400200", -("3923409900", -("3923500100", -("3923909900", -("3924100100", -("3924900100", -("3924909900", -("3925100100", -("3925200100", -("3925300100", -("3925909900", -("3926100100", -("3926200100", -("3926209900", -("3926209900", -("3926300201", -("3926300299", -("3926400100", -("3926900100", -("3926900400", -("3926900500", -("3926900600", -("3926900700", -("3926900800", -("3926901100", -("3926901300", -("3926901400", -("3926901500", -("3926901800", -("3926901900", -("3926902000", -("3926902400", -("3926902700", -("3926902900", -("3926909901", -("3926909902", -("3926909903", -("3926909904", -("3926909905", -("3926909906", -("3926909907", -("3926909908", -("3926909909", -("3926909910", -("3926909911", -("3926909912", -("3926909999", -("3926909999", -("3926909999", -("3926909999", -("3926909999", -("3926909999", -("3926909999", -("3926909999", -("4001100100", -("4001210100", -("4001220100", -("4001299900", -("4001300300", -("4001300300", -("4001300300", -("4002110200", -("4002119901", -("4002119999", -("4002190100", -("4002190200", -("4002190300", -("4002199900", -("4002200100", -("4002310100", -("4002319900", -("4002390100", -("4002399900", -("4002410100", -("4002499901", -("4002499999", -("4002510100", -("4002590100", -("4002590200", -("4002590300", -("4002599900", -("4002600200", -("4002600200", -("4002700100", -("4002800100", -("4002910100", -("4002919901", -("4002919999", -("4002990100", -("4002999900", -("4003000100", -("4004000100", -("4004000200", -("4004009900", -("4005100100", -("4005200200", -("4005200200", -("4005910401", -("4005910402", -("4005910499", -("4005910499", -("4005999900", -("4006100100", -("4006900100", -("4006900200", -("4006900300", -("4006900400", -("4006909900", -("4007000100", -("4008110100", -("4008199901", -("4008199999", -("4008210201", -("4008210299", -("4008299901", -("4008299999", -("4008299999", -("4009110200", -("4009110200", -("4009120300", -("4009120300", -("4009120300", -("4009210501", -("4009210502", -("4009210502", -("4009210599", -("4009220501", -("4009220599", -("4009220599", -("4009220599", -("4009220599", -("4009220599", -("4009310601", -("4009310602", -("4009310699", -("4009310699", -("4009310699", -("4009320501", -("4009320502", -("4009320599", -("4009320599", -("4009320599", -("4009410400", -("4009410400", -("4009410400", -("4009410400", -("4009420300", -("4009420300", -("4009420300", -("4010110201", -("4010110299", -("4010120301", -("4010120302", -("4010120399", -("4010199901", -("4010199999", -("4010199999", -("4010199999", -("4010199999", -("4010310100", -("4010320100", -("4010330100", -("4010340100", -("4010350200", -("4010350200", -("4010360200", -("4010360200", -("4010399901", -("4010399902", -("4010399903", -("4010399999", -("4010399999", -("4011101001", -("4011101002", -("4011101003", -("4011101004", -("4011101005", -("4011101006", -("4011101007", -("4011101008", -("4011101099", -("4011200400", -("4011200500", -("4011200601", -("4011200602", -("4011300100", -("4011400100", -("4011500100", -("4011700100", -("4011700200", -("4011700300", -("4011709900", -("4011709900", -("4011800100", -("4011800200", -("4011800300", -("4011800400", -("4011800500", -("4011800600", -("4011809391", -("4011809391", -("4011809392", -("4011809392", -("4011900100", -("4011900200", -("4011909900", -("4011909900", -("4012110100", -("4012120100", -("4012130100", -("4012199900", -("4012200100", -("4012209901", -("4012209999", -("4012900200", -("4012909901", -("4012909999", -("4012909999", -("4013100100", -("4013200100", -("4013900100", -("4013900200", -("4013900300", -("4013909900", -("4014100100", -("4014900100", -("4014900200", -("4014909900", -("4015110100", -("4015199900", -("4015900100", -("4015900200", -("4015900300", -("4015909900", -("4015909900", -("4016100100", -("4016910100", -("4016920100", -("4016929900", -("4016930401", -("4016930402", -("4016930403", -("4016930499", -("4016940200", -("4016949901", -("4016949999", -("4016950100", -("4016950200", -("4016959900", -("4016990100", -("4016990400", -("4016999901", -("4016999902", -("4016999903", -("4016999904", -("4016999905", -("4016999906", -("4016999907", -("4016999999", -("4016999999", -("4017000300", -("4017009901", -("4017009999", -("4017009999", -("4101200301", -("4101200302", -("4101500301", -("4101500399", -("4101500399", -("4101900301", -("4101900399", -("4101900399", -("4102100100", -("4102210100", -("4102299900", -("4103200100", -("4103209900", -("4103300100", -("4103909901", -("4103909999", -("4103909999", -("4103909999", -("4104110401", -("4104110402", -("4104110499", -("4104110499", -("4104199901", -("4104199999", -("4104199999", -("4104199999", -("4104410201", -("4104410299", -("4104499901", -("4104499999", -("4105100401", -("4105100499", -("4105100499", -("4105100499", -("4105300100", -("4106210401", -("4106210499", -("4106210499", -("4106210499", -("4106220100", -("4106310100", -("4106320100", -("4106400200", -("4106400200", -("4106910100", -("4106920100", -("4107110201", -("4107110299", -("4107120201", -("4107120299", -("4107199900", -("4107199900", -("4107910100", -("4107920100", -("4107999900", -("4107999900", -("4112000100", -("4113100100", -("4113200100", -("4113300100", -("4113909901", -("4113909999", -("4114100100", -("4114200100", -("4115100100", -("4115200100", -("4201000100", -("4202110100", -("4202120301", -("4202120302", -("4202199900", -("4202210100", -("4202220301", -("4202220302", -("4202299900", -("4202310100", -("4202320301", -("4202320302", -("4202399900", -("4202910100", -("4202920401", -("4202920402", -("4202920403", -("4202999900", -("4203100100", -("4203109900", -("4203210100", -("4203290100", -("4203299900", -("4203300200", -("4203300200", -("4203400100", -("4203409900", -("4205000200", -("4205009901", -("4205009999", -("4206000100", -("4206009900", -("4206009900", -("4301100100", -("4301300100", -("4301600100", -("4301800800", -("4301800800", -("4301800800", -("4301800800", -("4301800800", -("4301800800", -("4301800800", -("4301800800", -("4301900100", -("4302110100", -("4302199900", -("4302199900", -("4302199900", -("4302199900", -("4302200200", -("4302200200", -("4302300200", -("4302300200", -("4303100100", -("4303909900", -("4304000100", -("4401110100", -("4401120100", -("4401210100", -("4401220100", -("4401310100", -("4401399900", -("4401400100", -("4402100100", -("4402909900", -("4403110100", -("4403120100", -("4403210100", -("4403220100", -("4403230100", -("4403240100", -("4403250100", -("4403269900", -("4403410100", -("4403499900", -("4403499900", -("4403499900", -("4403499900", -("4403910100", -("4403930100", -("4403940100", -("4403950100", -("4403960100", -("4403970100", -("4403980100", -("4403999900", -("4404100200", -("4404100200", -("4404200100", -("4404200200", -("4404200300", -("4404200400", -("4404209900", -("4405000301", -("4405000302", -("4406110100", -("4406120100", -("4406910100", -("4406920100", -("4407110100", -("4407110300", -("4407119901", -("4407119999", -("4407120100", -("4407120300", -("4407129901", -("4407129999", -("4407190200", -("4407190300", -("4407199901", -("4407199999", -("4407210301", -("4407210399", -("4407210399", -("4407220201", -("4407220299", -("4407250100", -("4407260100", -("4407270100", -("4407280100", -("4407299901", -("4407299901", -("4407299902", -("4407299999", -("4407299999", -("4407910100", -("4407920200", -("4407920200", -("4407930100", -("4407940100", -("4407950301", -("4407950399", -("4407950399", -("4407960100", -("4407970100", -("4407999901", -("4407999902", -("4407999999", -("4407999999", -("4408100301", -("4408100302", -("4408310100", -("4408399900", -("4408399900", -("4408399900", -("4408909901", -("4408909999", -("4409100200", -("4409109901", -("4409109999", -("4409210300", -("4409210300", -("4409210300", -("4409220100", -("4409299901", -("4409299999", -("4409299999", -("4410110401", -("4410110402", -("4410110403", -("4410110499", -("4410120201", -("4410120299", -("4410199901", -("4410199999", -("4410199999", -("4410199999", -("4410900100", -("4410909900", -("4411120100", -("4411129901", -("4411129902", -("4411129903", -("4411129904", -("4411129999", -("4411130100", -("4411139901", -("4411139902", -("4411139999", -("4411139999", -("4411139999", -("4411140100", -("4411140300", -("4411149901", -("4411149902", -("4411149999", -("4411149999", -("4411920201", -("4411920299", -("4411930400", -("4411930400", -("4411930400", -("4411930400", -("4411940400", -("4411940400", -("4411940400", -("4411940400", -("4412100100", -("4412310100", -("4412319900", -("4412319900", -("4412330100", -("4412340100", -("4412390201", -("4412390299", -("4412940100", -("4412949900", -("4412949900", -("4412990100", -("4412990200", -("4412999900", -("4413000100", -("4413000200", -("4413009900", -("4414000100", -("4415100100", -("4415200201", -("4415200299", -("4416000100", -("4416000500", -("4416009900", -("4416009900", -("4416009900", -("4416009900", -("4417000100", -("4417009900", -("4418100100", -("4418200100", -("4418400100", -("4418500100", -("4418600100", -("4418730100", -("4418739900", -("4418739900", -("4418740100", -("4418750100", -("4418799900", -("4418799900", -("4418910100", -("4418999901", -("4418999999", -("4419110100", -("4419120100", -("4419199900", -("4419909900", -("4420100100", -("4420909900", -("4421100100", -("4421910100", -("4421910200", -("4421910400", -("4421919901", -("4421919999", -("4421990100", -("4421990200", -("4421990400", -("4421999901", -("4421999999", -("4501100100", -("4501909900", -("4502000100", -("4503100100", -("4503909900", -("4504100200", -("4504109900", -("4504109900", -("4504109900", -("4504909900", -("4601210100", -("4601220100", -("4601299900", -("4601299900", -("4601920100", -("4601929900", -("4601930200", -("4601930200", -("4601940100", -("4601949900", -("4601990100", -("4601999900", -("4602110100", -("4602120100", -("4602199900", -("4602909900", -("4701000100", -("4702000201", -("4702000299", -("4703110301", -("4703110399", -("4703110399", -("4703190300", -("4703190300", -("4703210301", -("4703210302", -("4703290301", -("4703290302", -("4704110100", -("4704190100", -("4704210100", -("4704290100", -("4705000100", -("4706100100", -("4706200100", -("4706300400", -("4706300400", -("4706300400", -("4706910100", -("4706920100", -("4706930100", -("4707100100", -("4707200100", -("4707300100", -("4707900100", -("4801000100", -("4801000100", -("4801000100", -("4802100100", -("4802200300", -("4802200300", -("4802200300", -("4802400100", -("4802540400", -("4802549901", -("4802549999", -("4802549999", -("4802549999", -("4802549999", -("4802549999", -("4802549999", -("4802550200", -("4802550300", -("4802559901", -("4802559999", -("4802560200", -("4802569901", -("4802569999", -("4802570200", -("4802570200", -("4802580400", -("4802580400", -("4802580400", -("4802580400", -("4802610301", -("4802610399", -("4802610399", -("4802620300", -("4802620300", -("4802699900", -("4802699900", -("4803000401", -("4803000499", -("4803000499", -("4803000499", -("4804110100", -("4804199901", -("4804199902", -("4804199999", -("4804210100", -("4804299900", -("4804310501", -("4804310599", -("4804310599", -("4804310599", -("4804310599", -("4804399901", -("4804399999", -("4804399999", -("4804399999", -("4804410100", -("4804420100", -("4804499900", -("4804499900", -("4804499900", -("4804510201", -("4804510299", -("4804520100", -("4804599901", -("4804599999", -("4804599999", -("4805110100", -("4805120100", -("4805199900", -("4805240201", -("4805240299", -("4805250201", -("4805250299", -("4805300100", -("4805400100", -("4805500100", -("4805910100", -("4805920100", -("4805930100", -("4806100100", -("4806200100", -("4806300100", -("4806400100", -("4807000300", -("4807000300", -("4807000300", -("4808100100", -("4808400400", -("4808400400", -("4808400400", -("4808400400", -("4808909901", -("4808909999", -("4809200100", -("4809900200", -("4809909901", -("4809909999", -("4810130701", -("4810130799", -("4810130799", -("4810130799", -("4810130799", -("4810130799", -("4810130799", -("4810140701", -("4810140799", -("4810140799", -("4810140799", -("4810140799", -("4810140799", -("4810140799", -("4810199900", -("4810220201", -("4810220299", -("4810299901", -("4810299999", -("4810310100", -("4810310200", -("4810310300", -("4810319900", -("4810319900", -("4810320100", -("4810399900", -("4810920100", -("4810999900", -("4810999900", -("4811100200", -("4811109901", -("4811109999", -("4811410100", -("4811410200", -("4811490100", -("4811499900", -("4811510100", -("4811510200", -("4811510400", -("4811510500", -("4811519900", -("4811519900", -("4811519900", -("4811590700", -("4811599901", -("4811599902", -("4811599903", -("4811599999", -("4811599999", -("4811599999", -("4811599999", -("4811600301", -("4811600399", -("4811600399", -("4811901000", -("4811909901", -("4811909902", -("4811909903", -("4811909999", -("4811909999", -("4811909999", -("4811909999", -("4811909999", -("4811909999", -("4811909999", -("4812000100", -("4813100100", -("4813200100", -("4813909900", -("4814200100", -("4814900200", -("4814909900", -("4814909900", -("4816200100", -("4816909901", -("4816909999", -("4816909999", -("4817100100", -("4817200100", -("4817300100", -("4818100100", -("4818200100", -("4818300100", -("4818500100", -("4818909900", -("4819100100", -("4819200201", -("4819200299", -("4819300100", -("4819400100", -("4819500100", -("4819600100", -("4820100201", -("4820100299", -("4820200100", -("4820300100", -("4820400100", -("4820500100", -("4820909900", -("4821100100", -("4821909900", -("4822100100", -("4822909900", -("4823200201", -("4823200299", -("4823400100", -("4823610100", -("4823699900", -("4823700300", -("4823709901", -("4823709999", -("4823709999", -("4823900100", -("4823900200", -("4823900500", -("4823901000", -("4823901100", -("4823901200", -("4823901700", -("4823909901", -("4823909902", -("4823909903", -("4823909999", -("4823909999", -("4823909999", -("4823909999", -("4823909999", -("4823909999", -("4823909999", -("4823909999", -("4901100100", -("4901109900", -("4901910401", -("4901910402", -("4901910499", -("4901910499", -("4901990100", -("4901990200", -("4901990300", -("4901990400", -("4901990500", -("4901990600", -("4901999900", -("4902100200", -("4902100200", -("4902909901", -("4902909999", -("4903000100", -("4903009900", -("4904000100", -("4905100100", -("4905910100", -("4905919900", -("4905990100", -("4905999900", -("4906000100", -("4907000301", -("4907000302", -("4907009900", -("4908100100", -("4908109900", -("4908900200", -("4908900300", -("4908909901", -("4908909902", -("4908909999", -("4909000100", -("4910000100", -("4911100100", -("4911100200", -("4911100300", -("4911100400", -("4911109900", -("4911910100", -("4911910300", -("4911919901", -("4911919902", -("4911919999", -("4911990100", -("4911990600", -("4911999901", -("4911999902", -("4911999903", -("4911999999", -("4911999999", -("5001000100", -("5002000100", -("5003000200", -("5003000200", -("5004000100", -("5005000100", -("5006000100", -("5007100100", -("5007200100", -("5007900100", -("5101110200", -("5101110200", -("5101199900", -("5101199900", -("5101210200", -("5101210200", -("5101299900", -("5101299900", -("5101300200", -("5101300200", -("5102110100", -("5102199900", -("5102199900", -("5102199900", -("5102200200", -("5102200200", -("5103100300", -("5103100300", -("5103100300", -("5103200300", -("5103200300", -("5103200300", -("5103300100", -("5104000100", -("5105100100", -("5105210100", -("5105299901", -("5105299999", -("5105310100", -("5105399900", -("5105399900", -("5105399900", -("5105400100", -("5106100100", -("5106200100", -("5107100100", -("5107200100", -("5108100100", -("5108200100", -("5109100100", -("5109909900", -("5110000100", -("5111110200", -("5111110200", -("5111199900", -("5111199900", -("5111200200", -("5111200200", -("5111300200", -("5111300200", -("5111909900", -("5112110200", -("5112110200", -("5112199900", -("5112199900", -("5112199900", -("5112200200", -("5112200200", -("5112300300", -("5112300300", -("5112300300", -("5112909900", -("5113000200", -("5113000200", -("5201000301", -("5201000302", -("5201000399", -("5202100100", -("5202910100", -("5202999901", -("5202999999", -("5203000100", -("5204110100", -("5204199900", -("5204200100", -("5205110100", -("5205120100", -("5205130100", -("5205140100", -("5205150100", -("5205210100", -("5205220100", -("5205230100", -("5205240100", -("5205260100", -("5205270100", -("5205280100", -("5205310100", -("5205320100", -("5205330100", -("5205340100", -("5205350100", -("5205410100", -("5205420100", -("5205430100", -("5205440100", -("5205460100", -("5205470100", -("5205480100", -("5206110100", -("5206120100", -("5206130100", -("5206140100", -("5206150100", -("5206210100", -("5206220100", -("5206230100", -("5206240100", -("5206250100", -("5206310100", -("5206320100", -("5206330100", -("5206340100", -("5206350100", -("5206410100", -("5206420100", -("5206430100", -("5206440100", -("5206450100", -("5207100100", -("5207909900", -("5208110100", -("5208120100", -("5208130100", -("5208190301", -("5208190302", -("5208190399", -("5208210100", -("5208220100", -("5208230100", -("5208290200", -("5208290200", -("5208310100", -("5208320100", -("5208330100", -("5208390201", -("5208390299", -("5208410100", -("5208420100", -("5208430100", -("5208490100", -("5208510100", -("5208520100", -("5208590300", -("5208590300", -("5208590300", -("5209110100", -("5209120100", -("5209190200", -("5209190200", -("5209210100", -("5209220100", -("5209290201", -("5209290299", -("5209310100", -("5209320100", -("5209390201", -("5209390299", -("5209410100", -("5209420401", -("5209420402", -("5209420491", -("5209420492", -("5209430100", -("5209490100", -("5209510100", -("5209520100", -("5209590300", -("5209590300", -("5210110201", -("5210110299", -("5210190300", -("5210190300", -("5210190300", -("5210210100", -("5210290301", -("5210290399", -("5210290399", -("5210310100", -("5210320100", -("5210390201", -("5210390299", -("5210410100", -("5210490201", -("5210490299", -("5210510100", -("5210590300", -("5210590300", -("5210590300", -("5211110201", -("5211110299", -("5211120100", -("5211190200", -("5211190200", -("5211200400", -("5211200400", -("5211200400", -("5211200400", -("5211310100", -("5211320100", -("5211390200", -("5211390200", -("5211410100", -("5211420401", -("5211420402", -("5211420491", -("5211420492", -("5211430100", -("5211490100", -("5211510100", -("5211520100", -("5211590200", -("5211590200", -("5212110100", -("5212120100", -("5212130100", -("5212140100", -("5212150100", -("5212210100", -("5212220100", -("5212230100", -("5212240201", -("5212240299", -("5212250100", -("5301100100", -("5301210100", -("5301299900", -("5301300100", -("5302100100", -("5302909900", -("5303100100", -("5303909900", -("5305000801", -("5305000802", -("5305000803", -("5305000899", -("5305000899", -("5305000899", -("5305000899", -("5305000899", -("5306100100", -("5306200100", -("5307100100", -("5307200100", -("5308100100", -("5308200100", -("5308900300", -("5308909901", -("5308909999", -("5309110100", -("5309199900", -("5309210100", -("5309299900", -("5310100100", -("5310909900", -("5311000200", -("5311000200", -("5401100100", -("5401200100", -("5402110100", -("5402199901", -("5402199999", -("5402200201", -("5402200299", -("5402310100", -("5402320100", -("5402330100", -("5402340100", -("5402399900", -("5402399900", -("5402440100", -("5402449900", -("5402450300", -("5402459901", -("5402459902", -("5402459903", -("5402459999", -("5402460100", -("5402470301", -("5402470399", -("5402470399", -("5402480301", -("5402480302", -("5402480399", -("5402490600", -("5402490600", -("5402499900", -("5402499900", -("5402499900", -("5402499900", -("5402510200", -("5402510200", -("5402520300", -("5402520300", -("5402520300", -("5402530100", -("5402599900", -("5402599900", -("5402599900", -("5402599900", -("5402599900", -("5402599900", -("5402610201", -("5402610299", -("5402620201", -("5402620299", -("5402630100", -("5402699901", -("5402699999", -("5402699999", -("5402699999", -("5402699999", -("5402699999", -("5403100100", -("5403310301", -("5403310302", -("5403320300", -("5403320300", -("5403330100", -("5403399900", -("5403399900", -("5403410301", -("5403410302", -("5403420100", -("5403499900", -("5403499900", -("5404110100", -("5404119900", -("5404120200", -("5404120200", -("5404199901", -("5404199902", -("5404199999", -("5404199999", -("5404909900", -("5405000500", -("5405000500", -("5405000500", -("5405000500", -("5405000500", -("5406000100", -("5406000200", -("5406000400", -("5406000400", -("5406000500", -("5407100300", -("5407100300", -("5407100300", -("5407200201", -("5407200299", -("5407300401", -("5407300402", -("5407300403", -("5407300499", -("5407410501", -("5407410502", -("5407410503", -("5407420501", -("5407420502", -("5407420503", -("5407430400", -("5407430400", -("5407430400", -("5407430400", -("5407440100", -("5407510501", -("5407510502", -("5407510503", -("5407520501", -("5407520502", -("5407520503", -("5407530401", -("5407530402", -("5407530403", -("5407530491", -("5407530492", -("5407530493", -("5407540501", -("5407540502", -("5407540503", -("5407610601", -("5407610602", -("5407610603", -("5407610604", -("5407610691", -("5407610692", -("5407610693", -("5407699901", -("5407699991", -("5407699992", -("5407699993", -("5407710100", -("5407720100", -("5407730401", -("5407730499", -("5407730499", -("5407730499", -("5407740100", -("5407810100", -("5407820401", -("5407820499", -("5407820499", -("5407820499", -("5407830100", -("5407840100", -("5407910801", -("5407910802", -("5407910803", -("5407910804", -("5407910805", -("5407910806", -("5407910807", -("5407910899", -("5407920701", -("5407920702", -("5407920703", -("5407920704", -("5407920705", -("5407920706", -("5407920799", -("5407930801", -("5407930802", -("5407930803", -("5407930804", -("5407930805", -("5407930806", -("5407930807", -("5407930899", -("5407940801", -("5407940802", -("5407940803", -("5407940804", -("5407940805", -("5407940806", -("5407940807", -("5407940899", -("5408100501", -("5408100502", -("5408100503", -("5408100504", -("5408100599", -("5408210400", -("5408210400", -("5408210400", -("5408210400", -("5408220501", -("5408220599", -("5408220599", -("5408220599", -("5408220599", -("5408230600", -("5408230600", -("5408230600", -("5408230600", -("5408230600", -("5408230600", -("5408240200", -("5408240200", -("5408310501", -("5408310502", -("5408310503", -("5408310504", -("5408310599", -("5408320601", -("5408320602", -("5408320603", -("5408320604", -("5408320605", -("5408320699", -("5408330501", -("5408330502", -("5408330503", -("5408330504", -("5408330599", -("5408340401", -("5408340402", -("5408340403", -("5408340499", -("5501100100", -("5501200200", -("5501209900", -("5501209900", -("5501209900", -("5501300100", -("5501400100", -("5501909900", -("5502100100", -("5502900100", -("5502909900", -("5503110100", -("5503199900", -("5503200300", -("5503209901", -("5503209902", -("5503209999", -("5503300100", -("5503400100", -("5503409900", -("5503900100", -("5503909900", -("5504100201", -("5504100299", -("5504909900", -("5505100100", -("5505200100", -("5506100100", -("5506200100", -("5506300100", -("5506400100", -("5506909900", -("5507000100", -("5508100100", -("5508200100", -("5509110100", -("5509120100", -("5509210100", -("5509220100", -("5509310100", -("5509320100", -("5509410100", -("5509420100", -("5509510100", -("5509520100", -("5509530100", -("5509599900", -("5509610100", -("5509620100", -("5509699900", -("5509910100", -("5509920100", -("5509999900", -("5510110100", -("5510120100", -("5510200100", -("5510300100", -("5510900100", -("5511100100", -("5511200100", -("5511300100", -("5512110501", -("5512110502", -("5512110503", -("5512199901", -("5512199991", -("5512199992", -("5512199993", -("5512210100", -("5512299900", -("5512910100", -("5512999900", -("5513110401", -("5513110402", -("5513120101", -("5513120199", -("5513130101", -("5513130199", -("5513190101", -("5513190199", -("5513210401", -("5513210402", -("5513230201", -("5513230202", -("5513230291", -("5513230299", -("5513290101", -("5513290199", -("5513310101", -("5513310199", -("5513390301", -("5513390302", -("5513390303", -("5513390391", -("5513390399", -("5513410101", -("5513410199", -("5513490301", -("5513490302", -("5513490391", -("5513490392", -("5513490399", -("5514110100", -("5514120100", -("5514190201", -("5514190299", -("5514210100", -("5514220100", -("5514230100", -("5514290100", -("5514300591", -("5514300599", -("5514300599", -("5514300599", -("5514300599", -("5514410100", -("5514420100", -("5514430100", -("5514490100", -("5515110101", -("5515110102", -("5515110199", -("5515120101", -("5515120102", -("5515120199", -("5515130201", -("5515130299", -("5515199901", -("5515199902", -("5515199999", -("5515210101", -("5515210102", -("5515210199", -("5515220200", -("5515220200", -("5515299901", -("5515299902", -("5515299999", -("5515910101", -("5515910102", -("5515910199", -("5515999901", -("5515999902", -("5515999903", -("5515999904", -("5515999999", -("5516110100", -("5516120100", -("5516130100", -("5516140100", -("5516210100", -("5516220100", -("5516230100", -("5516240100", -("5516310200", -("5516310200", -("5516320200", -("5516320200", -("5516330200", -("5516330200", -("5516340200", -("5516340200", -("5516410100", -("5516420100", -("5516430100", -("5516440100", -("5516910100", -("5516920100", -("5516930100", -("5516940100", -("5601210201", -("5601210299", -("5601220100", -("5601229900", -("5601299900", -("5601300201", -("5601300299", -("5602100200", -("5602100200", -("5602210301", -("5602210302", -("5602210399", -("5602290100", -("5602909900", -("5603110100", -("5603120201", -("5603120299", -("5603130201", -("5603130299", -("5603140100", -("5603910100", -("5603920100", -("5603930100", -("5603940100", -("5604100100", -("5604900100", -("5604900200", -("5604900400", -("5604900500", -("5604900600", -("5604900700", -("5604900800", -("5604900900", -("5604901000", -("5604901100", -("5604901200", -("5604901300", -("5604901400", -("5604909901", -("5604909999", -("5605000100", -("5606000301", -("5606000302", -("5606000399", -("5607210100", -("5607299900", -("5607410100", -("5607499900", -("5607500100", -("5607900200", -("5607909901", -("5607909999", -("5608110201", -("5608110299", -("5608199900", -("5608909900", -("5609000201", -("5609000299", -("5701100100", -("5701900100", -("5702100100", -("5702200100", -("5702310100", -("5702320100", -("5702390100", -("5702410100", -("5702420100", -("5702490100", -("5702500301", -("5702500302", -("5702500399", -("5702910100", -("5702920100", -("5702990100", -("5703100100", -("5703200201", -("5703200299", -("5703300201", -("5703300299", -("5703900100", -("5704100100", -("5704200100", -("5704909900", -("5705000201", -("5705000299", -("5801100100", -("5801210100", -("5801220100", -("5801230100", -("5801260100", -("5801270100", -("5801310100", -("5801320100", -("5801330100", -("5801360100", -("5801370100", -("5801900100", -("5802110100", -("5802199900", -("5802200100", -("5802300100", -("5803000501", -("5803000502", -("5803000503", -("5803000504", -("5803000599", -("5804100100", -("5804210100", -("5804290100", -("5804300100", -("5805000100", -("5806100100", -("5806109900", -("5806200100", -("5806209900", -("5806310100", -("5806320100", -("5806390100", -("5806399900", -("5806400100", -("5806409900", -("5807100100", -("5807909900", -("5808100100", -("5808909900", -("5809000100", -("5810100100", -("5810910100", -("5810920100", -("5810990100", -("5811000100", -("5901100100", -("5901909901", -("5901909902", -("5901909999", -("5902100100", -("5902200100", -("5902909900", -("5903100201", -("5903100299", -("5903200201", -("5903200299", -("5903909901", -("5903909902", -("5903909903", -("5903909904", -("5903909999", -("5904100100", -("5904909900", -("5904909900", -("5905000100", -("5906100100", -("5906910200", -("5906910200", -("5906999901", -("5906999902", -("5906999903", -("5906999999", -("5907000100", -("5907000500", -("5907000600", -("5907009901", -("5907009902", -("5907009903", -("5907009999", -("5908000300", -("5908009901", -("5908009902", -("5908009999", -("5909000100", -("5910000100", -("5911100100", -("5911109900", -("5911200100", -("5911310100", -("5911320100", -("5911400100", -("5911900100", -("5911909901", -("5911909902", -("5911909999", -("6001100301", -("6001100399", -("6001210101", -("6001210199", -("6001220101", -("6001220102", -("6001220199", -("6001290100", -("6001299901", -("6001299999", -("6001910101", -("6001910199", -("6001920101", -("6001920102", -("6001920199", -("6001990100", -("6002400100", -("6002409901", -("6002409902", -("6002409903", -("6002409999", -("6002900100", -("6002909901", -("6002909902", -("6002909903", -("6002909999", -("6003100100", -("6003200100", -("6003300100", -("6003400100", -("6003900100", -("6003909900", -("6004100100", -("6004109901", -("6004109902", -("6004109903", -("6004109904", -("6004109905", -("6004109999", -("6004900100", -("6004909900", -("6005210100", -("6005220100", -("6005230100", -("6005240100", -("6005350100", -("6005350100", -("6005350100", -("6005350100", -("6005360101", -("6005360199", -("6005370201", -("6005370299", -("6005380100", -("6005390100", -("6005390100", -("6005410100", -("6005420100", -("6005430100", -("6005440100", -("6005909901", -("6005909999", -("6006100100", -("6006210201", -("6006210299", -("6006220201", -("6006220299", -("6006230201", -("6006230299", -("6006240201", -("6006240299", -("6006310301", -("6006310399", -("6006320301", -("6006320399", -("6006330301", -("6006330399", -("6006340301", -("6006340399", -("6006410100", -("6006420100", -("6006430100", -("6006440100", -("6006909900", -("6101200301", -("6101200399", -("6101300100", -("6101309901", -("6101309991", -("6101309992", -("6101900201", -("6101900299", -("6102100100", -("6102200301", -("6102200399", -("6102300100", -("6102309901", -("6102309902", -("6102309999", -("6102900100", -("6103100501", -("6103100502", -("6103100503", -("6103100504", -("6103100599", -("6103220100", -("6103230100", -("6103290201", -("6103290299", -("6103310100", -("6103320100", -("6103330201", -("6103330299", -("6103390301", -("6103390302", -("6103390399", -("6103410100", -("6103420301", -("6103420302", -("6103420303", -("6103420391", -("6103420392", -("6103430100", -("6103439901", -("6103439902", -("6103439903", -("6103439904", -("6103439991", -("6103439992", -("6103490301", -("6103490302", -("6103490399", -("6104130201", -("6104130299", -("6104190601", -("6104190602", -("6104190603", -("6104190604", -("6104190605", -("6104190699", -("6104220100", -("6104230100", -("6104290201", -("6104290299", -("6104310100", -("6104320100", -("6104330201", -("6104330299", -("6104390301", -("6104390302", -("6104390399", -("6104410100", -("6104420301", -("6104420399", -("6104430201", -("6104430291", -("6104430292", -("6104440201", -("6104440202", -("6104440299", -("6104490201", -("6104490299", -("6104510100", -("6104520101", -("6104520199", -("6104530201", -("6104530291", -("6104530292", -("6104590301", -("6104590302", -("6104590399", -("6104610100", -("6104620301", -("6104620302", -("6104620303", -("6104620391", -("6104620392", -("6104630100", -("6104639901", -("6104639902", -("6104639903", -("6104639991", -("6104639992", -("6104690301", -("6104690302", -("6104690399", -("6105100201", -("6105100202", -("6105100299", -("6105200301", -("6105200399", -("6105900201", -("6105900299", -("6106100201", -("6106100202", -("6106100291", -("6106100292", -("6106200100", -("6106209991", -("6106209992", -("6106900301", -("6106900302", -("6106900399", -("6107110301", -("6107110399", -("6107120301", -("6107120399", -("6107190100", -("6107210101", -("6107210199", -("6107220101", -("6107220199", -("6107290201", -("6107290299", -("6107910100", -("6107990301", -("6107990302", -("6107990399", -("6108110100", -("6108190100", -("6108210301", -("6108210399", -("6108220301", -("6108220399", -("6108290100", -("6108310301", -("6108310399", -("6108320301", -("6108320399", -("6108390201", -("6108390299", -("6108910201", -("6108910299", -("6108920201", -("6108920299", -("6108990201", -("6108990299", -("6109100301", -("6109100399", -("6109900401", -("6109900491", -("6109909901", -("6109909991", -("6109909992", -("6110110301", -("6110110399", -("6110120101", -("6110120199", -("6110199901", -("6110199999", -("6110200501", -("6110200502", -("6110200503", -("6110200591", -("6110200592", -("6110200593", -("6110200594", -("6110200599", -("6110300101", -("6110300199", -("6110309901", -("6110309902", -("6110309903", -("6110309991", -("6110309992", -("6110309993", -("6110309999", -("6110900201", -("6110900202", -("6110900203", -("6110900204", -("6110900205", -("6110900291", -("6110900299", -("6111201201", -("6111201202", -("6111201203", -("6111201204", -("6111201205", -("6111201206", -("6111201207", -("6111201208", -("6111201209", -("6111201210", -("6111201211", -("6111201212", -("6111201213", -("6111201214", -("6111201291", -("6111201292", -("6111201299", -("6111300701", -("6111300702", -("6111300703", -("6111300704", -("6111300705", -("6111300706", -("6111300707", -("6111300708", -("6111300709", -("6111300710", -("6111300711", -("6111300712", -("6111300713", -("6111300714", -("6111300791", -("6111300792", -("6111300793", -("6111300799", -("6111900501", -("6111900502", -("6111900503", -("6111900504", -("6111900599", -("6112110100", -("6112120100", -("6112190301", -("6112190302", -("6112190399", -("6112200200", -("6112200200", -("6112310100", -("6112390100", -("6112410100", -("6112490100", -("6113000201", -("6113000299", -("6114200100", -("6114300201", -("6114300299", -("6114900201", -("6114900299", -("6115100100", -("6115210100", -("6115220100", -("6115290100", -("6115300100", -("6115940100", -("6115950100", -("6115960100", -("6115990100", -("6116100201", -("6116100299", -("6116910100", -("6116920100", -("6116930100", -("6116990100", -("6117100201", -("6117100299", -("6117800200", -("6117809901", -("6117809999", -("6117900100", -("6201110100", -("6201120100", -("6201129901", -("6201129902", -("6201130100", -("6201130200", -("6201139901", -("6201139902", -("6201190100", -("6201910100", -("6201920100", -("6201929901", -("6201929902", -("6201930100", -("6201939900", -("6201990100", -("6202110100", -("6202120100", -("6202129901", -("6202129902", -("6202130100", -("6202130200", -("6202139901", -("6202139902", -("6202190100", -("6202910100", -("6202920100", -("6202929901", -("6202929902", -("6202930100", -("6202939901", -("6202939902", -("6202990100", -("6203110100", -("6203120100", -("6203190301", -("6203190302", -("6203190399", -("6203220100", -("6203230100", -("6203290201", -("6203290299", -("6203310100", -("6203320301", -("6203320399", -("6203330100", -("6203339901", -("6203339902", -("6203390401", -("6203390402", -("6203390403", -("6203390499", -("6203410100", -("6203420100", -("6203420200", -("6203429101", -("6203429102", -("6203429191", -("6203429192", -("6203429199", -("6203429201", -("6203429202", -("6203429291", -("6203429292", -("6203429299", -("6203430100", -("6203439101", -("6203439102", -("6203439103", -("6203439191", -("6203439192", -("6203439199", -("6203439201", -("6203439202", -("6203439203", -("6203439291", -("6203439292", -("6203439299", -("6203490100", -("6204110100", -("6204120100", -("6204130201", -("6204130299", -("6204190401", -("6204190402", -("6204190403", -("6204190499", -("6204210100", -("6204220100", -("6204230100", -("6204290100", -("6204310100", -("6204320301", -("6204320399", -("6204330100", -("6204330200", -("6204339901", -("6204339902", -("6204339991", -("6204390401", -("6204390402", -("6204390403", -("6204390499", -("6204410100", -("6204420100", -("6204429991", -("6204429992", -("6204430100", -("6204430200", -("6204439901", -("6204439991", -("6204439992", -("6204440100", -("6204440200", -("6204449991", -("6204449992", -("6204490201", -("6204490299", -("6204510100", -("6204520301", -("6204520399", -("6204530100", -("6204530200", -("6204539991", -("6204539992", -("6204590601", -("6204590602", -("6204590603", -("6204590604", -("6204590691", -("6204590699", -("6204610100", -("6204620901", -("6204620902", -("6204620903", -("6204620904", -("6204620991", -("6204620992", -("6204620993", -("6204620994", -("6204620995", -("6204620996", -("6204630100", -("6204639101", -("6204639102", -("6204639103", -("6204639191", -("6204639192", -("6204639199", -("6204639201", -("6204639202", -("6204639203", -("6204639291", -("6204639299", -("6204690200", -("6204690300", -("6204699901", -("6204699902", -("6204699999", -("6205200100", -("6205209100", -("6205209200", -("6205300100", -("6205309100", -("6205309200", -("6205900100", -("6205900200", -("6205909900", -("6206100100", -("6206200201", -("6206200299", -("6206300401", -("6206300402", -("6206400100", -("6206400200", -("6206409100", -("6206409200", -("6206900100", -("6206909900", -("6207110101", -("6207110199", -("6207190100", -("6207210101", -("6207210199", -("6207220100", -("6207290201", -("6207290299", -("6207910100", -("6207990301", -("6207990302", -("6207990399", -("6208110100", -("6208190100", -("6208210101", -("6208210199", -("6208220101", -("6208220199", -("6208290201", -("6208290299", -("6208910100", -("6208920201", -("6208920202", -("6208920299", -("6208990301", -("6208990302", -("6208990399", -("6209200701", -("6209200702", -("6209200702", -("6209200703", -("6209200704", -("6209200705", -("6209200706", -("6209200707", -("6209200708", -("6209200709", -("6209200791", -("6209200792", -("6209200799", -("6209300501", -("6209300502", -("6209300503", -("6209300504", -("6209300505", -("6209300506", -("6209300591", -("6209300592", -("6209300599", -("6209900501", -("6209900502", -("6209900503", -("6209900504", -("6209900599", -("6210100100", -("6210200100", -("6210300100", -("6210400100", -("6210500100", -("6211110100", -("6211120100", -("6211200201", -("6211200299", -("6211320201", -("6211320299", -("6211330201", -("6211330299", -("6211390301", -("6211390302", -("6211390399", -("6211420201", -("6211420299", -("6211430201", -("6211430299", -("6211490100", -("6212100701", -("6212100702", -("6212100703", -("6212100791", -("6212100792", -("6212100793", -("6212200100", -("6212300100", -("6212909901", -("6212909902", -("6212909999", -("6213200100", -("6213900201", -("6213900202", -("6213900299", -("6214100100", -("6214200100", -("6214300100", -("6214400100", -("6214900100", -("6215100100", -("6215200100", -("6215900100", -("6216000100", -("6217100101", -("6217100199", -("6217900100", -("6301100100", -("6301200100", -("6301300100", -("6301400100", -("6301900100", -("6302100100", -("6302210101", -("6302210102", -("6302210103", -("6302210104", -("6302210105", -("6302210106", -("6302210107", -("6302210108", -("6302210199", -("6302220101", -("6302220102", -("6302220103", -("6302220104", -("6302220105", -("6302220106", -("6302220107", -("6302220108", -("6302220199", -("6302290100", -("6302310601", -("6302310602", -("6302310603", -("6302310604", -("6302310605", -("6302310606", -("6302310607", -("6302310608", -("6302310699", -("6302320601", -("6302320602", -("6302320603", -("6302320604", -("6302320605", -("6302320606", -("6302320607", -("6302320608", -("6302320699", -("6302390100", -("6302400100", -("6302510100", -("6302530100", -("6302590201", -("6302590299", -("6302600601", -("6302600602", -("6302600603", -("6302600604", -("6302600699", -("6302910100", -("6302930100", -("6302990201", -("6302990299", -("6303120100", -("6303190201", -("6303190299", -("6303910100", -("6303920201", -("6303920299", -("6303990100", -("6304110100", -("6304199900", -("6304200100", -("6304910100", -("6304920100", -("6304930100", -("6304990100", -("6305100100", -("6305200100", -("6305320100", -("6305330100", -("6305399900", -("6305900100", -("6306120100", -("6306190201", -("6306190299", -("6306220100", -("6306290201", -("6306290299", -("6306300201", -("6306300299", -("6306400201", -("6306400299", -("6306909901", -("6306909999", -("6307100100", -("6307200100", -("6307900100", -("6307909900", -("6308000100", -("6309000100", -("6310100201", -("6310100299", -("6310909901", -("6310909999", -("6401100101", -("6401100199", -("6401921101", -("6401921102", -("6401921103", -("6401929901", -("6401929902", -("6401929903", -("6401929991", -("6401929992", -("6401929993", -("6401999901", -("6401999902", -("6401999903", -("6401999904", -("6401999905", -("6401999991", -("6401999992", -("6401999993", -("6402120100", -("6402190100", -("6402199901", -("6402199902", -("6402199903", -("6402199903", -("6402199904", -("6402199904", -("6402199905", -("6402199905", -("6402200401", -("6402200402", -("6402910200", -("6402910601", -("6402910602", -("6402910603", -("6402910691", -("6402990601", -("6402990699", -("6402991901", -("6402991902", -("6402991903", -("6402991904", -("6402991991", -("6402991992", -("6402991993", -("6402992001", -("6402992002", -("6402992003", -("6402992101", -("6402992102", -("6402992103", -("6402992104", -("6402992191", -("6402992192", -("6402992193", -("6402999100", -("6402999200", -("6402999300", -("6402999300", -("6402999400", -("6403120100", -("6403190200", -("6403199901", -("6403199902", -("6403199903", -("6403199991", -("6403199999", -("6403200100", -("6403400501", -("6403400502", -("6403400503", -("6403510501", -("6403510502", -("6403510503", -("6403510504", -("6403599901", -("6403599902", -("6403599903", -("6403599904", -("6403599991", -("6403599992", -("6403599993", -("6403910400", -("6403911201", -("6403911202", -("6403911301", -("6403911302", -("6403911303", -("6403919901", -("6403919902", -("6403919903", -("6403990100", -("6403990600", -("6403991200", -("6403991301", -("6403991302", -("6403991303", -("6403991401", -("6403991402", -("6403991500", -("6403999901", -("6403999902", -("6404110900", -("6404111201", -("6404111202", -("6404111601", -("6404111602", -("6404111701", -("6404111702", -("6404119901", -("6404119902", -("6404119903", -("6404119904", -("6404119991", -("6404119992", -("6404119993", -("6404119994", -("6404190200", -("6404190801", -("6404190802", -("6404199901", -("6404199902", -("6404199903", -("6404199904", -("6404199905", -("6404199906", -("6404199907", -("6404199908", -("6404199909", -("6404199910", -("6404199991", -("6404199992", -("6404199993", -("6404199994", -("6404200101", -("6404200102", -("6404200199", -("6405100100", -("6405200100", -("6405200200", -("6405209991", -("6405209992", -("6405209993", -("6405209994", -("6405909901", -("6405909902", -("6405909999", -("6406100801", -("6406100801", -("6406100801", -("6406100802", -("6406100802", -("6406100899", -("6406100901", -("6406100902", -("6406100999", -("6406200100", -("6406200200", -("6406900100", -("6406900200", -("6406909900", -("6501000100", -("6502000100", -("6504000100", -("6505000401", -("6505000402", -("6505000403", -("6505000499", -("6506100100", -("6506910100", -("6506919900", -("6506990200", -("6506990200", -("6507000100", -("6601100100", -("6601910100", -("6601999900", -("6602000100", -("6603200100", -("6603209900", -("6603909900", -("6603909900", -("6701000100", -("6701009900", -("6702100100", -("6702900100", -("6703000100", -("6704110100", -("6704199900", -("6704200100", -("6704900100", -("6801000100", -("6802100100", -("6802109900", -("6802210100", -("6802230201", -("6802230299", -("6802290201", -("6802290299", -("6802910100", -("6802920100", -("6802930100", -("6802990100", -("6803000100", -("6803009900", -("6804100201", -("6804100299", -("6804210201", -("6804210299", -("6804220401", -("6804220402", -("6804220403", -("6804220499", -("6804230201", -("6804230299", -("6804300100", -("6805100101", -("6805100199", -("6805200100", -("6805300100", -("6806100100", -("6806200100", -("6806909900", -("6807100100", -("6807909900", -("6808000100", -("6809110100", -("6809199900", -("6809900100", -("6810110100", -("6810199900", -("6810910100", -("6810919900", -("6810999900", -("6810999900", -("6811400300", -("6811400400", -("6811400500", -("6811400500", -("6811409900", -("6811810100", -("6811820100", -("6811890200", -("6811899900", -("6812800100", -("6812800500", -("6812800600", -("6812800800", -("6812809900", -("6812809900", -("6812809900", -("6812809900", -("6812809900", -("6812910100", -("6812920100", -("6812930200", -("6812930200", -("6812990200", -("6812990300", -("6812990500", -("6812999900", -("6812999900", -("6812999900", -("6813200501", -("6813200599", -("6813200599", -("6813200599", -("6813200599", -("6813810100", -("6813819900", -("6813899901", -("6813899999", -("6813899999", -("6814100200", -("6814100200", -("6814909900", -("6815100100", -("6815100200", -("6815100300", -("6815100400", -("6815109900", -("6815200100", -("6815910201", -("6815910299", -("6815990100", -("6815999900", -("6901000100", -("6902100100", -("6902200100", -("6902909900", -("6902909900", -("6902909900", -("6902909900", -("6903100301", -("6903100399", -("6903100399", -("6903200601", -("6903200602", -("6903200603", -("6903200699", -("6903200699", -("6903200699", -("6903200699", -("6903909900", -("6903909900", -("6904100100", -("6904909900", -("6905100100", -("6905900100", -("6905909900", -("6906000100", -("6907210201", -("6907210202", -("6907210299", -("6907210299", -("6907210299", -("6907210299", -("6907220201", -("6907220201", -("6907220299", -("6907220299", -("6907220299", -("6907220299", -("6907230201", -("6907230201", -("6907230299", -("6907230299", -("6907230299", -("6907230299", -("6907300100", -("6907300100", -("6907300100", -("6907300100", -("6907400100", -("6907400100", -("6907400100", -("6907400100", -("6909110600", -("6909110600", -("6909110600", -("6909110600", -("6909110600", -("6909110600", -("6909120100", -("6909199900", -("6909909900", -("6910100100", -("6910900100", -("6910909900", -("6911100100", -("6911909900", -("6912000201", -("6912000299", -("6913100100", -("6913909900", -("6914100100", -("6914909900", -("7001000100", -("7002100100", -("7002200500", -("7002200500", -("7002200500", -("7002200500", -("7002200500", -("7002310301", -("7002310399", -("7002310399", -("7002320100", -("7002399900", -("7003120200", -("7003120200", -("7003190201", -("7003190299", -("7003200100", -("7003300100", -("7004200300", -("7004200300", -("7004200300", -("7004900200", -("7004900200", -("7005100201", -("7005100299", -("7005210301", -("7005210302", -("7005210399", -("7005290100", -("7005299901", -("7005299902", -("7005299903", -("7005299999", -("7005300100", -("7006000401", -("7006000402", -("7006000499", -("7006000499", -("7007110100", -("7007119901", -("7007119902", -("7007119999", -("7007199901", -("7007199999", -("7007210300", -("7007219901", -("7007219902", -("7007219999", -("7007299900", -("7008000100", -("7009100400", -("7009109901", -("7009109902", -("7009109999", -("7009109999", -("7009910100", -("7009919900", -("7009920100", -("7010100100", -("7010109900", -("7010200100", -("7010909901", -("7010909902", -("7010909903", -("7010909999", -("7011100501", -("7011100502", -("7011100503", -("7011100599", -("7011100599", -("7011200500", -("7011200500", -("7011200500", -("7011200500", -("7011200500", -("7011909900", -("7011909900", -("7013100100", -("7013220100", -("7013289901", -("7013289999", -("7013330100", -("7013379901", -("7013379991", -("7013379999", -("7013379999", -("7013379999", -("7013410100", -("7013420100", -("7013499901", -("7013499902", -("7013499903", -("7013499904", -("7013499905", -("7013499906", -("7013499991", -("7013499999", -("7013499999", -("7013910100", -("7013999901", -("7013999902", -("7013999903", -("7013999904", -("7013999999", -("7014000100", -("7014000200", -("7014000300", -("7014000400", -("7014009900", -("7015100100", -("7015909900", -("7016100100", -("7016909900", -("7017100400", -("7017100600", -("7017100700", -("7017100800", -("7017100900", -("7017101000", -("7017109901", -("7017109902", -("7017109999", -("7017109999", -("7017109999", -("7017109999", -("7017109999", -("7017109999", -("7017200100", -("7017200300", -("7017209901", -("7017209999", -("7017900300", -("7017900400", -("7017909900", -("7017909900", -("7017909900", -("7017909900", -("7018100100", -("7018200100", -("7018209900", -("7018909900", -("7018909900", -("7019110100", -("7019120100", -("7019199900", -("7019310100", -("7019320100", -("7019399900", -("7019399900", -("7019399900", -("7019399900", -("7019399900", -("7019400201", -("7019400299", -("7019510201", -("7019510299", -("7019520100", -("7019529900", -("7019599901", -("7019599999", -("7019909901", -("7019909902", -("7019909903", -("7019909904", -("7019909905", -("7019909906", -("7019909999", -("7019909999", -("7019909999", -("7020000601", -("7020000699", -("7020000699", -("7020000699", -("7020000699", -("7020000699", -("7101100200", -("7101100200", -("7101210100", -("7101220100", -("7101229900", -("7102100100", -("7102210100", -("7102299900", -("7102310100", -("7102399900", -("7103100100", -("7103910100", -("7103999900", -("7104100100", -("7104200100", -("7104909900", -("7105100100", -("7105909900", -("7106100100", -("7106910100", -("7106920100", -("7107000100", -("7108110100", -("7108120100", -("7108130100", -("7108200100", -("7108209900", -("7109000100", -("7110110100", -("7110199900", -("7110210100", -("7110299900", -("7110310100", -("7110399900", -("7110410100", -("7110499900", -("7111000100", -("7112300100", -("7112910201", -("7112910299", -("7112920100", -("7112999900", -("7113110100", -("7113110200", -("7113119900", -("7113190300", -("7113199901", -("7113199901", -("7113199999", -("7113200100", -("7114110100", -("7114199900", -("7114200100", -("7115100100", -("7115909900", -("7115909900", -("7116100100", -("7116200100", -("7117110100", -("7117190100", -("7117199900", -("7117900100", -("7117909900", -("7118100100", -("7118909900", -("7201100100", -("7201200100", -("7201500200", -("7201500200", -("7202110100", -("7202199901", -("7202199999", -("7202210200", -("7202210200", -("7202299900", -("7202300100", -("7202410100", -("7202499900", -("7202500100", -("7202600100", -("7202700100", -("7202800100", -("7202910401", -("7202910402", -("7202910403", -("7202920200", -("7202920200", -("7202930100", -("7202999901", -("7202999902", -("7202999903", -("7202999999", -("7203100100", -("7203909900", -("7204100100", -("7204210100", -("7204299900", -("7204300100", -("7204410100", -("7204499900", -("7204500100", -("7205100100", -("7205210100", -("7205299900", -("7206100100", -("7206909900", -("7207110100", -("7207120201", -("7207120299", -("7207199900", -("7207200201", -("7207200299", -("7208100301", -("7208100302", -("7208100303", -("7208100399", -("7208250201", -("7208250299", -("7208260101", -("7208260199", -("7208270101", -("7208270199", -("7208360101", -("7208360102", -("7208360199", -("7208370101", -("7208370102", -("7208370199", -("7208380101", -("7208380199", -("7208390101", -("7208390199", -("7208400201", -("7208400299", -("7208510401", -("7208510402", -("7208510403", -("7208510404", -("7208510404", -("7208510404", -("7208510405", -("7208510405", -("7208510405", -("7208520100", -("7208530100", -("7208540100", -("7208909900", -("7209150401", -("7209150402", -("7209150403", -("7209150499", -("7209160101", -("7209160199", -("7209170101", -("7209170199", -("7209180101", -("7209180199", -("7209250100", -("7209260100", -("7209270100", -("7209280100", -("7209909900", -("7210110100", -("7210120401", -("7210120402", -("7210120403", -("7210120499", -("7210200100", -("7210300201", -("7210300299", -("7210410100", -("7210419900", -("7210499901", -("7210499902", -("7210499999", -("7210500301", -("7210500302", -("7210500399", -("7210610100", -("7210699901", -("7210699999", -("7210700201", -("7210700202", -("7210700203", -("7210700291", -("7210700299", -("7210909901", -("7210909991", -("7210909999", -("7211130100", -("7211140301", -("7211140302", -("7211140303", -("7211140399", -("7211199901", -("7211199902", -("7211199903", -("7211199904", -("7211199999", -("7211230301", -("7211230302", -("7211230399", -("7211299901", -("7211299902", -("7211299903", -("7211299999", -("7211909900", -("7212100301", -("7212100302", -("7212100399", -("7212200301", -("7212200302", -("7212200399", -("7212300301", -("7212300302", -("7212300399", -("7212400401", -("7212400402", -("7212400403", -("7212400499", -("7212500100", -("7212600401", -("7212600402", -("7212600403", -("7212600499", -("7213100100", -("7213200100", -("7213910301", -("7213910302", -("7213999901", -("7213999902", -("7213999999", -("7214100100", -("7214200100", -("7214209900", -("7214300100", -("7214910301", -("7214910302", -("7214910391", -("7214910399", -("7214999901", -("7214999902", -("7214999903", -("7214999904", -("7214999991", -("7214999992", -("7214999999", -("7215100100", -("7215500201", -("7215500299", -("7215909901", -("7215909999", -("7216100101", -("7216100199", -("7216210101", -("7216210199", -("7216220100", -("7216310301", -("7216310302", -("7216310399", -("7216320400", -("7216329901", -("7216329902", -("7216329999", -("7216330101", -("7216330102", -("7216330103", -("7216330104", -("7216330199", -("7216330201", -("7216330202", -("7216330203", -("7216330204", -("7216330299", -("7216400101", -("7216400102", -("7216400191", -("7216400199", -("7216500100", -("7216509900", -("7216610100", -("7216610200", -("7216619900", -("7216699901", -("7216699902", -("7216699999", -("7216910100", -("7216999900", -("7217100201", -("7217100202", -("7217100203", -("7217100204", -("7217100205", -("7217100206", -("7217100291", -("7217100292", -("7217100293", -("7217200201", -("7217200299", -("7217300201", -("7217300299", -("7217909901", -("7217909999", -("7218100100", -("7218910100", -("7218999900", -("7219110100", -("7219120201", -("7219120299", -("7219130100", -("7219140100", -("7219210100", -("7219220100", -("7219230100", -("7219240100", -("7219310101", -("7219310199", -("7219320201", -("7219320299", -("7219330100", -("7219340100", -("7219350201", -("7219350299", -("7219909900", -("7220110100", -("7220120100", -("7220200301", -("7220200302", -("7220200399", -("7220909900", -("7221000101", -("7221000199", -("7222110201", -("7222110299", -("7222199900", -("7222200100", -("7222300201", -("7222300299", -("7222400101", -("7222400199", -("7223000201", -("7223000299", -("7224100601", -("7224100602", -("7224100603", -("7224100604", -("7224100605", -("7224100691", -("7224100699", -("7224900200", -("7224909901", -("7224909902", -("7224909999", -("7225110100", -("7225199900", -("7225300701", -("7225300702", -("7225300703", -("7225300704", -("7225300705", -("7225300706", -("7225300707", -("7225300707", -("7225300707", -("7225300707", -("7225300791", -("7225300792", -("7225300799", -("7225400601", -("7225400602", -("7225400603", -("7225400604", -("7225400605", -("7225400606", -("7225400607", -("7225400608", -("7225400691", -("7225400699", -("7225400699", -("7225500701", -("7225500702", -("7225500703", -("7225500704", -("7225500705", -("7225500706", -("7225500707", -("7225500708", -("7225500709", -("7225500710", -("7225500710", -("7225500710", -("7225500710", -("7225500710", -("7225500710", -("7225500710", -("7225500710", -("7225500711", -("7225500791", -("7225500799", -("7225910100", -("7225920101", -("7225920199", -("7225999901", -("7225999902", -("7225999903", -("7225999999", -("7226110100", -("7226199900", -("7226200100", -("7226910701", -("7226910702", -("7226910703", -("7226910704", -("7226910705", -("7226910706", -("7226910707", -("7226910708", -("7226910791", -("7226910799", -("7226920601", -("7226920602", -("7226920603", -("7226920604", -("7226920605", -("7226920606", -("7226920699", -("7226999901", -("7226999902", -("7226999999", -("7227100100", -("7227200101", -("7227200199", -("7227909901", -("7227909902", -("7227909903", -("7227909904", -("7227909999", -("7228100201", -("7228100299", -("7228200201", -("7228200299", -("7228300100", -("7228309901", -("7228309999", -("7228400201", -("7228400299", -("7228500201", -("7228500299", -("7228600201", -("7228600202", -("7228600299", -("7228700101", -("7228700199", -("7228800100", -("7229200100", -("7229909901", -("7229909902", -("7229909903", -("7229909904", -("7229909999", -("7301100100", -("7301200100", -("7302100201", -("7302100202", -("7302100299", -("7302300100", -("7302400201", -("7302400299", -("7302909901", -("7302909902", -("7302909999", -("7303000201", -("7303000299", -("7304110100", -("7304110200", -("7304110300", -("7304110400", -("7304119901", -("7304119999", -("7304190101", -("7304190102", -("7304190103", -("7304190104", -("7304190201", -("7304190299", -("7304190301", -("7304190399", -("7304190401", -("7304190491", -("7304190499", -("7304190500", -("7304199901", -("7304199991", -("7304199999", -("7304220401", -("7304220402", -("7304220403", -("7304220499", -("7304230400", -("7304239901", -("7304239902", -("7304239999", -("7304240701", -("7304240702", -("7304240703", -("7304240704", -("7304240705", -("7304240706", -("7304240799", -("7304299901", -("7304299902", -("7304299903", -("7304299904", -("7304299905", -("7304299906", -("7304299991", -("7304299999", -("7304310101", -("7304310199", -("7304311001", -("7304311099", -("7304319901", -("7304319902", -("7304319903", -("7304319904", -("7304319905", -("7304319906", -("7304319907", -("7304319908", -("7304319909", -("7304319991", -("7304319999", -("7304390101", -("7304390199", -("7304390201", -("7304390299", -("7304390300", -("7304390400", -("7304390800", -("7304390900", -("7304391000", -("7304391100", -("7304391200", -("7304391300", -("7304391400", -("7304391500", -("7304391600", -("7304399100", -("7304399200", -("7304399901", -("7304399902", -("7304399991", -("7304399992", -("7304399999", -("7304410301", -("7304410302", -("7304410399", -("7304499901", -("7304499999", -("7304511201", -("7304511202", -("7304511203", -("7304511204", -("7304511205", -("7304511206", -("7304511207", -("7304511208", -("7304511209", -("7304511210", -("7304511211", -("7304511291", -("7304511299", -("7304590900", -("7304599901", -("7304599902", -("7304599903", -("7304599904", -("7304599905", -("7304599906", -("7304599907", -("7304599908", -("7304599909", -("7304599910", -("7304599911", -("7304599912", -("7304599913", -("7304599991", -("7304599992", -("7304599999", -("7304909900", -("7305110201", -("7305110202", -("7305110299", -("7305120201", -("7305120202", -("7305120299", -("7305199901", -("7305199999", -("7305200100", -("7305209900", -("7305310100", -("7305310200", -("7305310300", -("7305310400", -("7305310500", -("7305310600", -("7305319100", -("7305319900", -("7305390100", -("7305390200", -("7305390300", -("7305390400", -("7305390500", -("7305399900", -("7305909901", -("7305909999", -("7306110100", -("7306199901", -("7306199902", -("7306199999", -("7306210100", -("7306299900", -("7306300200", -("7306300300", -("7306300400", -("7306309901", -("7306309902", -("7306309903", -("7306309904", -("7306309905", -("7306309906", -("7306309991", -("7306309999", -("7306400100", -("7306409900", -("7306500100", -("7306509901", -("7306509902", -("7306509999", -("7306610101", -("7306610102", -("7306610103", -("7306610103", -("7306610103", -("7306610199", -("7306699901", -("7306699902", -("7306699999", -("7306909900", -("7307110200", -("7307110200", -("7307199901", -("7307199902", -("7307199903", -("7307199999", -("7307199999", -("7307199999", -("7307199999", -("7307210100", -("7307220200", -("7307220200", -("7307230100", -("7307239900", -("7307299900", -("7307910100", -("7307920200", -("7307920200", -("7307930101", -("7307930199", -("7307999901", -("7307999902", -("7307999903", -("7307999999", -("7307999999", -("7307999999", -("7308100100", -("7308200201", -("7308200299", -("7308300201", -("7308300299", -("7308400100", -("7308900100", -("7308900200", -("7308909901", -("7308909999", -("7309000401", -("7309000499", -("7309000499", -("7309000499", -("7310100501", -("7310100501", -("7310100502", -("7310100599", -("7310100599", -("7310210100", -("7310290100", -("7310290500", -("7310299901", -("7310299999", -("7310299999", -("7310299999", -("7311000501", -("7311000502", -("7311000599", -("7311000599", -("7311000599", -("7312100100", -("7312100501", -("7312100502", -("7312100599", -("7312100700", -("7312100801", -("7312100899", -("7312109901", -("7312109902", -("7312109903", -("7312109904", -("7312109905", -("7312109906", -("7312109907", -("7312109999", -("7312109999", -("7312109999", -("7312909900", -("7313000100", -("7314120100", -("7314140100", -("7314190301", -("7314190302", -("7314190399", -("7314199901", -("7314199902", -("7314199999", -("7314199999", -("7314200100", -("7314310101", -("7314310102", -("7314310103", -("7314310199", -("7314399900", -("7314410101", -("7314410102", -("7314410199", -("7314420100", -("7314499901", -("7314499999", -("7314500100", -("7315110601", -("7315110601", -("7315110602", -("7315110602", -("7315110603", -("7315110604", -("7315110604", -("7315110699", -("7315110699", -("7315120301", -("7315120399", -("7315120399", -("7315190401", -("7315190402", -("7315190403", -("7315190499", -("7315200100", -("7315810301", -("7315810399", -("7315810399", -("7315820301", -("7315820302", -("7315820399", -("7315899901", -("7315899999", -("7315899999", -("7315900100", -("7316000100", -("7317000100", -("7317000200", -("7317000300", -("7317000400", -("7317009901", -("7317009902", -("7317009903", -("7317009904", -("7317009905", -("7317009906", -("7317009991", -("7317009999", -("7318110100", -("7318120100", -("7318130100", -("7318140100", -("7318150400", -("7318159901", -("7318159902", -("7318159903", -("7318159904", -("7318159905", -("7318159906", -("7318159907", -("7318159908", -("7318159909", -("7318159999", -("7318160601", -("7318160602", -("7318160603", -("7318160604", -("7318160605", -("7318199901", -("7318199901", -("7318199999", -("7318199999", -("7318210200", -("7318210200", -("7318220201", -("7318220299", -("7318230201", -("7318230299", -("7318240301", -("7318240301", -("7318240399", -("7318240399", -("7318299901", -("7318299999", -("7319400100", -("7319900100", -("7319909900", -("7320100200", -("7320100200", -("7320200501", -("7320200502", -("7320200503", -("7320200599", -("7320200599", -("7320909901", -("7320909999", -("7320909999", -("7320909999", -("7321110100", -("7321110200", -("7321119900", -("7321120100", -("7321190100", -("7321810201", -("7321810299", -("7321820200", -("7321820200", -("7321890100", -("7321900100", -("7321900200", -("7321900300", -("7321900400", -("7321900500", -("7321900600", -("7321900700", -("7321909900", -("7322110200", -("7322110200", -("7322190100", -("7322199900", -("7322900100", -("7322909900", -("7323100100", -("7323910300", -("7323910300", -("7323910300", -("7323920401", -("7323920499", -("7323920499", -("7323920499", -("7323930500", -("7323930500", -("7323930500", -("7323930500", -("7323930500", -("7323940501", -("7323940502", -("7323940599", -("7323940599", -("7323940599", -("7323999900", -("7324100100", -("7324210100", -("7324299900", -("7324900401", -("7324900499", -("7324900499", -("7324900499", -("7325100501", -("7325100599", -("7325100599", -("7325100599", -("7325100599", -("7325910301", -("7325910399", -("7325910399", -("7325999901", -("7325999901", -("7325999902", -("7325999902", -("7325999999", -("7325999999", -("7325999999", -("7325999999", -("7325999999", -("7326110301", -("7326110399", -("7326110399", -("7326199901", -("7326199902", -("7326199903", -("7326199904", -("7326199905", -("7326199906", -("7326199907", -("7326199908", -("7326199909", -("7326199910", -("7326199999", -("7326199999", -("7326199999", -("7326199999", -("7326199999", -("7326200601", -("7326200602", -("7326200699", -("7326200699", -("7326200699", -("7326200699", -("7326909901", -("7326909902", -("7326909903", -("7326909904", -("7326909905", -("7326909906", -("7326909907", -("7326909908", -("7326909908", -("7326909909", -("7326909910", -("7326909999", -("7326909999", -("7326909999", -("7326909999", -("7326909999", -("7326909999", -("7326909999", -("7326909999", -("7401000301", -("7401000302", -("7402000100", -("7403110100", -("7403120100", -("7403130100", -("7403199900", -("7403210100", -("7403220100", -("7403290201", -("7403290299", -("7404000301", -("7404000302", -("7404000399", -("7405000100", -("7406100100", -("7406200100", -("7407100100", -("7407109901", -("7407109999", -("7407210200", -("7407219901", -("7407219999", -("7407299901", -("7407299902", -("7407299903", -("7407299999", -("7407299999", -("7407299999", -("7408110201", -("7408110299", -("7408190100", -("7408190200", -("7408199900", -("7408210100", -("7408220100", -("7408229900", -("7408299900", -("7409110100", -("7409199900", -("7409210100", -("7409299900", -("7409310100", -("7409399900", -("7409400100", -("7409900100", -("7410110100", -("7410120100", -("7410210400", -("7410210400", -("7410210400", -("7410210400", -("7410220100", -("7411100100", -("7411109901", -("7411109902", -("7411109903", -("7411109999", -("7411210100", -("7411210500", -("7411219901", -("7411219999", -("7411219999", -("7411219999", -("7411220100", -("7411220200", -("7411229900", -("7411229900", -("7411229900", -("7411290100", -("7411299900", -("7411299900", -("7411299900", -("7411299900", -("7412100100", -("7412200100", -("7413000200", -("7413000200", -("7415100200", -("7415100200", -("7415210100", -("7415299900", -("7415299900", -("7415330300", -("7415330300", -("7415330300", -("7415399900", -("7415399900", -("7418100100", -("7418200100", -("7419100100", -("7419910100", -("7419910200", -("7419910300", -("7419910400", -("7419910500", -("7419910600", -("7419919900", -("7419990100", -("7419990200", -("7419999901", -("7419999902", -("7419999903", -("7419999904", -("7419999905", -("7419999999", -("7419999999", -("7419999999", -("7419999999", -("7419999999", -("7419999999", -("7419999999", -("7419999999", -("7419999999", -("7501100100", -("7501200100", -("7502100100", -("7502200100", -("7503000100", -("7504000100", -("7505110100", -("7505120100", -("7505210100", -("7505220100", -("7506100201", -("7506100299", -("7506200200", -("7506200200", -("7507110100", -("7507120100", -("7507200100", -("7508100100", -("7508909901", -("7508909999", -("7508909999", -("7508909999", -("7601100201", -("7601100299", -("7601200201", -("7601200299", -("7602000201", -("7602000299", -("7603100100", -("7603200100", -("7604100100", -("7604100200", -("7604109900", -("7604210100", -("7604290100", -("7604290200", -("7604299900", -("7605110100", -("7605119900", -("7605199900", -("7605210100", -("7605210200", -("7605219900", -("7605290100", -("7605290200", -("7605299900", -("7606110100", -("7606119900", -("7606120100", -("7606120200", -("7606120300", -("7606129900", -("7606910100", -("7606919900", -("7606920100", -("7606929900", -("7607110100", -("7607190100", -("7607190200", -("7607190300", -("7607199900", -("7607200200", -("7607200200", -("7608100300", -("7608109901", -("7608109901", -("7608109999", -("7608200100", -("7608200200", -("7608200300", -("7608209900", -("7609000201", -("7609000299", -("7610100100", -("7610900100", -("7610909900", -("7611000100", -("7612100100", -("7612900100", -("7612909900", -("7613000100", -("7614100101", -("7614100191", -("7614100199", -("7614909901", -("7614909999", -("7615100201", -("7615100202", -("7615100299", -("7615200100", -("7616100100", -("7616100200", -("7616100300", -("7616109900", -("7616910100", -("7616990100", -("7616990200", -("7616990300", -("7616990400", -("7616990500", -("7616990600", -("7616990700", -("7616990800", -("7616990900", -("7616991000", -("7616991100", -("7616991200", -("7616991300", -("7616991400", -("7616991500", -("7616999991", -("7616999992", -("7616999999", -("7801100100", -("7801910100", -("7801999900", -("7802000100", -("7804110100", -("7804199900", -("7804200100", -("7806000301", -("7806000399", -("7806000399", -("7901110100", -("7901120100", -("7901200100", -("7902000100", -("7903100100", -("7903909900", -("7904000100", -("7905000100", -("7907000201", -("7907000299", -("8001100100", -("8001200100", -("8002000100", -("8003000100", -("8007000100", -("8007000200", -("8007000300", -("8007009900", -("8101100100", -("8101940100", -("8101960301", -("8101960399", -("8101960399", -("8101970100", -("8101999901", -("8101999999", -("8102100100", -("8102940100", -("8102950200", -("8102950200", -("8102960100", -("8102970100", -("8102999900", -("8103200100", -("8103300100", -("8103909900", -("8104110100", -("8104199900", -("8104200100", -("8104300100", -("8104909901", -("8104909902", -("8104909999", -("8105200100", -("8105300100", -("8105909900", -("8106000100", -("8107200100", -("8107300100", -("8107909900", -("8108200100", -("8108300100", -("8108900100", -("8108909900", -("8109200100", -("8109300100", -("8109909900", -("8110100100", -("8110200100", -("8110909900", -("8111000201", -("8111000299", -("8112120100", -("8112130100", -("8112199900", -("8112210100", -("8112220100", -("8112299900", -("8112510100", -("8112520100", -("8112599900", -("8112920100", -("8112999900", -("8113000201", -("8113000299", -("8201100200", -("8201100200", -("8201300201", -("8201300299", -("8201400100", -("8201500100", -("8201600100", -("8201600200", -("8201900200", -("8201900300", -("8201909901", -("8201909999", -("8201909999", -("8202100100", -("8202100400", -("8202100500", -("8202100500", -("8202109900", -("8202200100", -("8202310100", -("8202319900", -("8202390100", -("8202390200", -("8202390300", -("8202390400", -("8202390500", -("8202390600", -("8202390700", -("8202399900", -("8202400201", -("8202400299", -("8202910401", -("8202910499", -("8202910499", -("8202910499", -("8202990100", -("8202999900", -("8203100200", -("8203109901", -("8203109999", -("8203200100", -("8203209900", -("8203300100", -("8203400100", -("8203400300", -("8203409901", -("8203409999", -("8204110100", -("8204110200", -("8204119900", -("8204120100", -("8204129901", -("8204129999", -("8204200100", -("8204209900", -("8205100100", -("8205109901", -("8205109999", -("8205109999", -("8205200100", -("8205300100", -("8205309900", -("8205400100", -("8205409900", -("8205510200", -("8205519901", -("8205519999", -("8205519999", -("8205590500", -("8205590800", -("8205590900", -("8205591000", -("8205591300", -("8205591400", -("8205591500", -("8205591600", -("8205599901", -("8205599902", -("8205599903", -("8205599904", -("8205599905", -("8205599906", -("8205599907", -("8205599908", -("8205599909", -("8205599999", -("8205599999", -("8205599999", -("8205600200", -("8205600200", -("8205700300", -("8205709901", -("8205709902", -("8205709999", -("8205900500", -("8205900500", -("8205900500", -("8206000100", -("8207130801", -("8207130802", -("8207130803", -("8207130899", -("8207130899", -("8207130899", -("8207130899", -("8207130899", -("8207191001", -("8207191002", -("8207191003", -("8207191004", -("8207191005", -("8207191006", -("8207191007", -("8207191099", -("8207191099", -("8207191099", -("8207200100", -("8207300301", -("8207300302", -("8207400401", -("8207400499", -("8207400499", -("8207400499", -("8207500701", -("8207500702", -("8207500703", -("8207500704", -("8207500705", -("8207500799", -("8207500799", -("8207600601", -("8207600602", -("8207600603", -("8207600604", -("8207600699", -("8207600699", -("8207700301", -("8207700302", -("8207700399", -("8207800100", -("8207900100", -("8207909900", -("8208100201", -("8208100299", -("8208200200", -("8208200200", -("8208300201", -("8208300299", -("8208400301", -("8208400302", -("8208400399", -("8208909900", -("8209000100", -("8210000100", -("8211100100", -("8211910100", -("8211920100", -("8211929900", -("8211930100", -("8211940100", -("8211950100", -("8212100100", -("8212109900", -("8212200100", -("8212900100", -("8212909900", -("8213000100", -("8214100201", -("8214100299", -("8214200201", -("8214200299", -("8214900100", -("8214900500", -("8214900500", -("8214909900", -("8214909900", -("8215100100", -("8215200100", -("8215910100", -("8215999901", -("8215999999", -("8301100100", -("8301200201", -("8301200299", -("8301300100", -("8301400100", -("8301500100", -("8301509900", -("8301600201", -("8301600299", -("8301700201", -("8301700299", -("8302100100", -("8302100200", -("8302109900", -("8302200201", -("8302200299", -("8302300100", -("8302410601", -("8302410602", -("8302410603", -("8302410604", -("8302410699", -("8302410699", -("8302420301", -("8302420302", -("8302420303", -("8302420399", -("8302499900", -("8302500100", -("8302600100", -("8303000100", -("8304000300", -("8304000300", -("8304000300", -("8305100100", -("8305200100", -("8305900100", -("8305900200", -("8305909900", -("8306100100", -("8306210100", -("8306299900", -("8306300100", -("8307100100", -("8307109900", -("8307900100", -("8308100100", -("8308109900", -("8308200100", -("8308900100", -("8309100100", -("8309900100", -("8309900300", -("8309900600", -("8309900700", -("8309900800", -("8309909901", -("8309909902", -("8309909999", -("8309909999", -("8310000100", -("8310009900", -("8311100200", -("8311100300", -("8311100400", -("8311109901", -("8311109999", -("8311200501", -("8311200502", -("8311200503", -("8311200504", -("8311200599", -("8311300100", -("8311309901", -("8311309902", -("8311309999", -("8311309999", -("8311900100", -("8311900200", -("8311900300", -("8311900400", -("8311900500", -("8311909900", -("8401100100", -("8401200100", -("8401300100", -("8401400100", -("8402110100", -("8402120100", -("8402190100", -("8402199900", -("8402200100", -("8402900100", -("8403100100", -("8403900100", -("8404100100", -("8404200100", -("8404900100", -("8405100100", -("8405100200", -("8405109900", -("8405900100", -("8406100100", -("8406109900", -("8406810100", -("8406820100", -("8406829900", -("8406900100", -("8406900200", -("8406909900", -("8406909900", -("8407100100", -("8407210201", -("8407210299", -("8407290100", -("8407299900", -("8407310100", -("8407319900", -("8407320200", -("8407329901", -("8407329999", -("8407329999", -("8407330400", -("8407330400", -("8407330400", -("8407330400", -("8407340301", -("8407340399", -("8407340399", -("8407900201", -("8407900299", -("8408100100", -("8408109900", -("8408200100", -("8408900100", -("8408909900", -("8409100100", -("8409910500", -("8409911100", -("8409919901", -("8409919902", -("8409919903", -("8409919904", -("8409919905", -("8409919906", -("8409919907", -("8409919908", -("8409919909", -("8409919910", -("8409919911", -("8409919912", -("8409919913", -("8409919914", -("8409919915", -("8409919916", -("8409919999", -("8409919999", -("8409919999", -("8409990100", -("8409990200", -("8409990300", -("8409990300", -("8409990400", -("8409990500", -("8409990800", -("8409990900", -("8409991000", -("8409991100", -("8409991200", -("8409991300", -("8409991400", -("8409999900", -("8410110100", -("8410120200", -("8410120200", -("8410130200", -("8410130200", -("8410900100", -("8411110100", -("8411120100", -("8411210100", -("8411220100", -("8411810100", -("8411820100", -("8411910100", -("8411999900", -("8412100100", -("8412210100", -("8412299900", -("8412310100", -("8412319900", -("8412399900", -("8412399900", -("8412800100", -("8412809900", -("8412900100", -("8413110100", -("8413119900", -("8413190100", -("8413190300", -("8413199901", -("8413199999", -("8413199999", -("8413200100", -("8413300300", -("8413309901", -("8413309902", -("8413309903", -("8413309904", -("8413309905", -("8413309999", -("8413400200", -("8413400200", -("8413500200", -("8413509901", -("8413509999", -("8413600100", -("8413600400", -("8413600600", -("8413609901", -("8413609902", -("8413609903", -("8413609999", -("8413700100", -("8413700200", -("8413700500", -("8413700700", -("8413709901", -("8413709902", -("8413709903", -("8413709999", -("8413810100", -("8413810200", -("8413810300", -("8413819900", -("8413820100", -("8413911301", -("8413911302", -("8413911303", -("8413911304", -("8413911305", -("8413911306", -("8413911307", -("8413911308", -("8413911399", -("8413911399", -("8413911399", -("8413911399", -("8413911399", -("8413920100", -("8414100601", -("8414100602", -("8414100603", -("8414100699", -("8414100699", -("8414100699", -("8414200100", -("8414300100", -("8414300100", -("8414300200", -("8414300300", -("8414300400", -("8414300500", -("8414300600", -("8414300700", -("8414300800", -("8414301000", -("8414309900", -("8414400100", -("8414400200", -("8414409900", -("8414510100", -("8414510200", -("8414519900", -("8414590100", -("8414599900", -("8414600100", -("8414609900", -("8414800600", -("8414800700", -("8414800800", -("8414801000", -("8414809901", -("8414809902", -("8414809902", -("8414809903", -("8414809904", -("8414809905", -("8414809906", -("8414809907", -("8414809908", -("8414809909", -("8414809999", -("8414809999", -("8414809999", -("8414901001", -("8414901002", -("8414901003", -("8414901004", -("8414901005", -("8414901099", -("8414901099", -("8414901099", -("8414901099", -("8414901099", -("8415100101", -("8415100102", -("8415100199", -("8415200100", -("8415810100", -("8415820100", -("8415829900", -("8415830100", -("8415900201", -("8415900299", -("8416100100", -("8416109900", -("8416200100", -("8416209900", -("8416300100", -("8416309900", -("8416900201", -("8416900299", -("8417100300", -("8417109901", -("8417109902", -("8417109903", -("8417109999", -("8417109999", -("8417200200", -("8417200200", -("8417800100", -("8417800200", -("8417800300", -("8417800400", -("8417800500", -("8417809900", -("8417900100", -("8418100100", -("8418109900", -("8418210100", -("8418290100", -("8418299900", -("8418300100", -("8418300200", -("8418300400", -("8418309901", -("8418309999", -("8418400100", -("8418400200", -("8418400400", -("8418409901", -("8418409999", -("8418500100", -("8418500300", -("8418509901", -("8418509999", -("8418610100", -("8418619900", -("8418619900", -("8418690100", -("8418690100", -("8418690100", -("8418690400", -("8418690500", -("8418690600", -("8418690700", -("8418690800", -("8418690900", -("8418691000", -("8418691200", -("8418691300", -("8418691400", -("8418691400", -("8418691400", -("8418691500", -("8418699901", -("8418699902", -("8418699999", -("8418910100", -("8418990300", -("8418990400", -("8418999900", -("8418999900", -("8418999900", -("8419110100", -("8419190100", -("8419199901", -("8419199999", -("8419199999", -("8419200201", -("8419200299", -("8419310100", -("8419310200", -("8419310300", -("8419310400", -("8419310500", -("8419319900", -("8419320200", -("8419320200", -("8419399901", -("8419399902", -("8419399999", -("8419399999", -("8419399999", -("8419399999", -("8419400400", -("8419409901", -("8419409902", -("8419409903", -("8419409999", -("8419500300", -("8419509901", -("8419509902", -("8419509903", -("8419509999", -("8419509999", -("8419600100", -("8419810100", -("8419810300", -("8419819901", -("8419819999", -("8419890200", -("8419890300", -("8419890900", -("8419891000", -("8419891100", -("8419891500", -("8419891700", -("8419891800", -("8419891900", -("8419899901", -("8419899902", -("8419899903", -("8419899904", -("8419899905", -("8419899906", -("8419899907", -("8419899908", -("8419899999", -("8419899999", -("8419899999", -("8419899999", -("8419899999", -("8419900401", -("8419900402", -("8419900499", -("8419900499", -("8420100100", -("8420910201", -("8420910299", -("8420999900", -("8421110201", -("8421110299", -("8421120201", -("8421120299", -("8421190100", -("8421190300", -("8421199901", -("8421199902", -("8421199999", -("8421210100", -("8421210200", -("8421210400", -("8421219901", -("8421219999", -("8421220100", -("8421230100", -("8421290100", -("8421290200", -("8421290300", -("8421290600", -("8421290800", -("8421299901", -("8421299902", -("8421299999", -("8421299999", -("8421310100", -("8421310200", -("8421319900", -("8421390200", -("8421390300", -("8421390500", -("8421390600", -("8421390700", -("8421390800", -("8421399901", -("8421399902", -("8421399999", -("8421910401", -("8421910499", -("8421910499", -("8421910499", -("8421999901", -("8421999999", -("8421999999", -("8421999999", -("8422110100", -("8422199900", -("8422200100", -("8422200200", -("8422209901", -("8422209999", -("8422300800", -("8422309901", -("8422309902", -("8422309903", -("8422309904", -("8422309905", -("8422309906", -("8422309907", -("8422309908", -("8422309999", -("8422309999", -("8422400200", -("8422409901", -("8422409902", -("8422409903", -("8422409999", -("8422409999", -("8422409999", -("8422900501", -("8422900502", -("8422900599", -("8422900599", -("8422900599", -("8423100201", -("8423100299", -("8423200100", -("8423209900", -("8423300201", -("8423300299", -("8423810301", -("8423810302", -("8423820301", -("8423820302", -("8423899900", -("8423900200", -("8423900200", -("8424100301", -("8424100399", -("8424100399", -("8424200100", -("8424209901", -("8424209999", -("8424209999", -("8424300400", -("8424309901", -("8424309902", -("8424309999", -("8424309999", -("8424410201", -("8424410299", -("8424499901", -("8424499999", -("8424820701", -("8424820702", -("8424820703", -("8424820704", -("8424820705", -("8424820706", -("8424820799", -("8424899901", -("8424899999", -("8424899999", -("8424900100", -("8425110100", -("8425119900", -("8425199900", -("8425310100", -("8425310200", -("8425319900", -("8425390100", -("8425390200", -("8425390300", -("8425399900", -("8425410100", -("8425420100", -("8425420200", -("8425429900", -("8425499900", -("8426110100", -("8426120100", -("8426199900", -("8426200100", -("8426300100", -("8426410100", -("8426410200", -("8426419901", -("8426419902", -("8426419999", -("8426499901", -("8426499999", -("8426499999", -("8426910300", -("8426919901", -("8426919902", -("8426919999", -("8426990300", -("8426990400", -("8426999901", -("8426999902", -("8426999999", -("8427100100", -("8427109901", -("8427109999", -("8427200100", -("8427200200", -("8427200300", -("8427200400", -("8427200500", -("8427209900", -("8427900200", -("8427900200", -("8428100100", -("8428200200", -("8428200300", -("8428209900", -("8428209900", -("8428310100", -("8428329900", -("8428339900", -("8428399901", -("8428399902", -("8428399999", -("8428400200", -("8428409901", -("8428409999", -("8428600100", -("8428900100", -("8428900200", -("8428900300", -("8428900600", -("8428909901", -("8428909999", -("8429110100", -("8429199900", -("8429200100", -("8429300100", -("8429400201", -("8429400299", -("8429510300", -("8429519901", -("8429519902", -("8429519999", -("8429520301", -("8429520302", -("8429520399", -("8429590100", -("8429590200", -("8429590500", -("8429599901", -("8429599902", -("8429599999", -("8430100100", -("8430200100", -("8430310301", -("8430310302", -("8430310399", -("8430390100", -("8430399900", -("8430410301", -("8430410302", -("8430410399", -("8430499900", -("8430499900", -("8430499900", -("8430500100", -("8430500200", -("8430509900", -("8430610100", -("8430610200", -("8430619900", -("8430690100", -("8430690200", -("8430690300", -("8430690400", -("8430699900", -("8431100100", -("8431200201", -("8431200299", -("8431310201", -("8431310299", -("8431399900", -("8431410401", -("8431410402", -("8431410403", -("8431410499", -("8431420100", -("8431430301", -("8431430399", -("8431430399", -("8431499901", -("8431499902", -("8431499999", -("8431499999", -("8432100100", -("8432210100", -("8432299901", -("8432299999", -("8432310401", -("8432310402", -("8432310403", -("8432310499", -("8432399900", -("8432399900", -("8432399900", -("8432410100", -("8432420100", -("8432800401", -("8432800402", -("8432800499", -("8432800499", -("8432900100", -("8433110100", -("8433199900", -("8433200301", -("8433200302", -("8433200399", -("8433300100", -("8433400301", -("8433400399", -("8433400399", -("8433510100", -("8433520100", -("8433530100", -("8433599901", -("8433599902", -("8433599903", -("8433599904", -("8433599999", -("8433600401", -("8433600499", -("8433600499", -("8433600499", -("8433900401", -("8433900402", -("8433900499", -("8433900499", -("8434100100", -("8434200100", -("8434900100", -("8435100100", -("8435900100", -("8436100100", -("8436210100", -("8436299901", -("8436299999", -("8436800401", -("8436800402", -("8436800499", -("8436800499", -("8436910100", -("8436999900", -("8437100401", -("8437100499", -("8437100499", -("8437100499", -("8437800301", -("8437800399", -("8437800399", -("8437900201", -("8437900299", -("8438100100", -("8438100300", -("8438100400", -("8438109901", -("8438109902", -("8438109903", -("8438109904", -("8438109999", -("8438200301", -("8438200399", -("8438200399", -("8438300100", -("8438309900", -("8438400201", -("8438400299", -("8438500600", -("8438509901", -("8438509902", -("8438509903", -("8438509904", -("8438509905", -("8438509999", -("8438509999", -("8438509999", -("8438600400", -("8438609901", -("8438609902", -("8438609999", -("8438609999", -("8438609999", -("8438800401", -("8438800402", -("8438800499", -("8438800499", -("8438900601", -("8438900602", -("8438900603", -("8438900699", -("8438900699", -("8438900699", -("8439100600", -("8439100600", -("8439100600", -("8439100600", -("8439100600", -("8439100600", -("8439200100", -("8439300100", -("8439910100", -("8439999900", -("8440100201", -("8440100299", -("8440900100", -("8441100401", -("8441100402", -("8441100403", -("8441100499", -("8441200100", -("8441300100", -("8441400200", -("8441400200", -("8441800100", -("8441900100", -("8442300301", -("8442300302", -("8442300399", -("8442400100", -("8442500100", -("8442500300", -("8442509900", -("8442509900", -("8443110201", -("8443110299", -("8443120100", -("8443130100", -("8443139900", -("8443139900", -("8443140201", -("8443140299", -("8443150200", -("8443150200", -("8443160100", -("8443170100", -("8443199901", -("8443199902", -("8443199999", -("8443199999", -("8443310100", -("8443320901", -("8443320902", -("8443320903", -("8443320903", -("8443320904", -("8443320905", -("8443320991", -("8443320999", -("8443320999", -("8443390200", -("8443390500", -("8443390600", -("8443399901", -("8443399902", -("8443399991", -("8443399999", -("8443399999", -("8443399999", -("8443910200", -("8443919901", -("8443919999", -("8443990500", -("8443990600", -("8443991000", -("8443999901", -("8443999902", -("8443999903", -("8443999999", -("8443999999", -("8443999999", -("8443999999", -("8443999999", -("8444000100", -("8445110100", -("8445120100", -("8445130100", -("8445199901", -("8445199999", -("8445200100", -("8445300201", -("8445300299", -("8445400100", -("8445909901", -("8445909999", -("8446100100", -("8446210100", -("8446299900", -("8446300100", -("8447110100", -("8447120100", -("8447200201", -("8447200299", -("8447909901", -("8447909999", -("8448110100", -("8448199900", -("8448200100", -("8448310100", -("8448320100", -("8448330100", -("8448399900", -("8448420100", -("8448499900", -("8448499900", -("8448510100", -("8448599900", -("8449000100", -("8450110100", -("8450119900", -("8450120201", -("8450120299", -("8450199900", -("8450199900", -("8450200100", -("8450900301", -("8450900399", -("8450900399", -("8451100100", -("8451210201", -("8451210299", -("8451299901", -("8451299999", -("8451299999", -("8451299999", -("8451300100", -("8451400100", -("8451500100", -("8451800201", -("8451800299", -("8451900301", -("8451900399", -("8451900399", -("8452100100", -("8452210601", -("8452210602", -("8452210603", -("8452210604", -("8452210699", -("8452210699", -("8452299901", -("8452299902", -("8452299903", -("8452299904", -("8452299905", -("8452299906", -("8452299999", -("8452299999", -("8452300100", -("8452900100", -("8452909901", -("8452909999", -("8453100100", -("8453200100", -("8453800100", -("8453900100", -("8454100100", -("8454200200", -("8454200200", -("8454300201", -("8454300299", -("8454900201", -("8454900299", -("8455100100", -("8455210301", -("8455210399", -("8455210399", -("8455220301", -("8455220302", -("8455220399", -("8455300301", -("8455300302", -("8455300399", -("8455900100", -("8455909900", -("8456110201", -("8456110299", -("8456120201", -("8456120299", -("8456200200", -("8456200200", -("8456300100", -("8456400100", -("8456500100", -("8456909900", -("8457100100", -("8457200100", -("8457300200", -("8457300300", -("8457309901", -("8457309902", -("8457309999", -("8458110100", -("8458119901", -("8458119999", -("8458190100", -("8458199900", -("8458199900", -("8458910201", -("8458910299", -("8458999900", -("8458999900", -("8459100201", -("8459100299", -("8459210200", -("8459210200", -("8459299901", -("8459299999", -("8459310100", -("8459399900", -("8459410100", -("8459419900", -("8459490100", -("8459499900", -("8459510100", -("8459599900", -("8459610100", -("8459699900", -("8459700301", -("8459700302", -("8459700399", -("8460120201", -("8460120201", -("8460120299", -("8460120299", -("8460199900", -("8460199900", -("8460199900", -("8460220100", -("8460220100", -("8460230501", -("8460230502", -("8460230503", -("8460230599", -("8460240201", -("8460240299", -("8460240299", -("8460299900", -("8460299900", -("8460299900", -("8460299900", -("8460299900", -("8460299900", -("8460310200", -("8460310200", -("8460399900", -("8460399900", -("8460400301", -("8460400399", -("8460400399", -("8460909901", -("8460909902", -("8460909903", -("8460909999", -("8461200200", -("8461200200", -("8461300201", -("8461300299", -("8461400100", -("8461500200", -("8461500300", -("8461509901", -("8461509999", -("8461900300", -("8461909901", -("8461909999", -("8461909999", -("8461909999", -("8462100100", -("8462109900", -("8462210100", -("8462210600", -("8462210700", -("8462219901", -("8462219902", -("8462219903", -("8462219904", -("8462219999", -("8462290100", -("8462290300", -("8462290500", -("8462290600", -("8462290700", -("8462299901", -("8462299902", -("8462299999", -("8462310100", -("8462310300", -("8462319901", -("8462319999", -("8462390100", -("8462390300", -("8462399901", -("8462399999", -("8462410401", -("8462410402", -("8462410403", -("8462410499", -("8462490100", -("8462490200", -("8462499901", -("8462499999", -("8462910100", -("8462910200", -("8462910300", -("8462919900", -("8462990400", -("8462999901", -("8462999999", -("8462999999", -("8462999999", -("8463100100", -("8463200100", -("8463300401", -("8463300402", -("8463300499", -("8463300499", -("8463909900", -("8463909900", -("8464100100", -("8464200100", -("8464909901", -("8464909999", -("8465100100", -("8465200100", -("8465200100", -("8465200100", -("8465200100", -("8465200100", -("8465200100", -("8465200100", -("8465910100", -("8465919900", -("8465920401", -("8465920402", -("8465920499", -("8465920499", -("8465930201", -("8465930299", -("8465940300", -("8465940300", -("8465940300", -("8465950100", -("8465959900", -("8465960100", -("8465990200", -("8465999900", -("8465999900", -("8465999900", -("8466100300", -("8466109901", -("8466109902", -("8466109999", -("8466200201", -("8466200299", -("8466300301", -("8466300399", -("8466300399", -("8466910100", -("8466920100", -("8466930300", -("8466930400", -("8466939901", -("8466939902", -("8466939999", -("8466940100", -("8466949900", -("8466949900", -("8467110201", -("8467110299", -("8467199900", -("8467199900", -("8467210100", -("8467210200", -("8467210300", -("8467219900", -("8467220200", -("8467220300", -("8467229900", -("8467229900", -("8467290100", -("8467290200", -("8467290300", -("8467299900", -("8467810100", -("8467890200", -("8467890300", -("8467899901", -("8467899999", -("8467910201", -("8467910299", -("8467920100", -("8467999901", -("8467999991", -("8467999999", -("8468100100", -("8468200201", -("8468200299", -("8468809900", -("8468900100", -("8470100301", -("8470100302", -("8470100399", -("8470210100", -("8470299900", -("8470299900", -("8470300100", -("8470500100", -("8470900300", -("8470909901", -("8470909999", -("8470909999", -("8471300101", -("8471300199", -("8471410100", -("8471490100", -("8471500100", -("8471600401", -("8471600402", -("8471600403", -("8471600499", -("8471700100", -("8471800401", -("8471800402", -("8471800403", -("8471800499", -("8471909900", -("8472100100", -("8472109900", -("8472300100", -("8472300200", -("8472309900", -("8472900100", -("8472900500", -("8472901000", -("8472901200", -("8472901300", -("8472901400", -("8472901600", -("8472909901", -("8472909902", -("8472909903", -("8472909904", -("8472909991", -("8472909999", -("8472909999", -("8472909999", -("8472909999", -("8472909999", -("8472909999", -("8472909999", -("8472909999", -("8472909999", -("8473210100", -("8473299900", -("8473300401", -("8473300402", -("8473300499", -("8473400401", -("8473400402", -("8473400402", -("8473400499", -("8473400499", -("8473400499", -("8473400499", -("8473500301", -("8473500399", -("8473500399", -("8474100100", -("8474109900", -("8474109900", -("8474200400", -("8474200700", -("8474200800", -("8474209901", -("8474209902", -("8474209903", -("8474209904", -("8474209999", -("8474209999", -("8474310100", -("8474320100", -("8474399901", -("8474399999", -("8474399999", -("8474800100", -("8474800400", -("8474809901", -("8474809902", -("8474809903", -("8474809904", -("8474809999", -("8474809999", -("8474809999", -("8474809999", -("8474809999", -("8474900301", -("8474900302", -("8474900399", -("8475100100", -("8475210100", -("8475290100", -("8475299900", -("8475900100", -("8476210100", -("8476299900", -("8476810100", -("8476899900", -("8476900200", -("8476900200", -("8477100100", -("8477109900", -("8477200100", -("8477209901", -("8477209999", -("8477300100", -("8477400100", -("8477510100", -("8477599900", -("8477800600", -("8477809901", -("8477809902", -("8477809903", -("8477809904", -("8477809905", -("8477809999", -("8477809999", -("8477900401", -("8477900402", -("8477900499", -("8477900499", -("8478100100", -("8478100200", -("8478100300", -("8478100400", -("8478109900", -("8478109900", -("8478900100", -("8479100100", -("8479100300", -("8479100400", -("8479100700", -("8479109901", -("8479109902", -("8479109903", -("8479109999", -("8479109999", -("8479200100", -("8479300100", -("8479300200", -("8479309900", -("8479400201", -("8479400299", -("8479500100", -("8479600100", -("8479710100", -("8479799900", -("8479810200", -("8479810300", -("8479810400", -("8479810700", -("8479819901", -("8479819902", -("8479819903", -("8479819904", -("8479819999", -("8479819999", -("8479820100", -("8479820200", -("8479820400", -("8479820500", -("8479829900", -("8479829900", -("8479890300", -("8479890400", -("8479890600", -("8479890700", -("8479890800", -("8479891000", -("8479891200", -("8479891300", -("8479891400", -("8479891900", -("8479892000", -("8479892100", -("8479892200", -("8479892600", -("8479892700", -("8479899901", -("8479899902", -("8479899903", -("8479899904", -("8479899905", -("8479899906", -("8479899907", -("8479899908", -("8479899999", -("8479899999", -("8479899999", -("8479899999", -("8479899999", -("8479901801", -("8479901802", -("8479901803", -("8479901804", -("8479901805", -("8479901806", -("8479901899", -("8479901899", -("8479901899", -("8479901899", -("8479901899", -("8479901899", -("8479901899", -("8479901899", -("8479901899", -("8479901899", -("8479901899", -("8480100100", -("8480200100", -("8480300301", -("8480300399", -("8480300399", -("8480410201", -("8480410299", -("8480499901", -("8480499999", -("8480500301", -("8480500399", -("8480500399", -("8480600100", -("8480609900", -("8480710301", -("8480710302", -("8480710399", -("8480799901", -("8480799902", -("8480799999", -("8480799999", -("8480799999", -("8481100100", -("8481109901", -("8481109999", -("8481200700", -("8481201000", -("8481201200", -("8481209901", -("8481209902", -("8481209903", -("8481209904", -("8481209905", -("8481209906", -("8481209907", -("8481209999", -("8481209999", -("8481209999", -("8481300100", -("8481309901", -("8481309902", -("8481309999", -("8481400400", -("8481409901", -("8481409902", -("8481409999", -("8481409999", -("8481800100", -("8481800200", -("8481800300", -("8481800400", -("8481800500", -("8481800600", -("8481800700", -("8481800800", -("8481800900", -("8481801000", -("8481801100", -("8481801200", -("8481801300", -("8481801400", -("8481801500", -("8481801600", -("8481801700", -("8481801800", -("8481801900", -("8481802000", -("8481802100", -("8481802200", -("8481802300", -("8481802400", -("8481802500", -("8481809900", -("8481900501", -("8481900502", -("8481900503", -("8481900599", -("8481900599", -("8482100200", -("8482109901", -("8482109902", -("8482109999", -("8482200100", -("8482200300", -("8482209900", -("8482209900", -("8482300100", -("8482400100", -("8482500100", -("8482800100", -("8482910201", -("8482910299", -("8482990100", -("8482999901", -("8482999902", -("8482999999", -("8483100801", -("8483100802", -("8483100803", -("8483100804", -("8483100805", -("8483100806", -("8483100807", -("8483100899", -("8483200100", -("8483300401", -("8483300402", -("8483300403", -("8483300499", -("8483400901", -("8483400902", -("8483400903", -("8483400904", -("8483400905", -("8483400906", -("8483400907", -("8483400999", -("8483400999", -("8483500301", -("8483500399", -("8483500399", -("8483600100", -("8483600200", -("8483609900", -("8483900301", -("8483900302", -("8483900399", -("8484100100", -("8484200100", -("8484909901", -("8484909902", -("8484909999", -("8486100100", -("8486200300", -("8486200300", -("8486300100", -("8486400100", -("8486900501", -("8486900502", -("8486900503", -("8486900504", -("8487100201", -("8487100299", -("8487900100", -("8487900200", -("8487900300", -("8487900400", -("8487900500", -("8487900600", -("8487900700", -("8487909900", -("8501101001", -("8501101002", -("8501101003", -("8501101004", -("8501101005", -("8501101006", -("8501101007", -("8501101099", -("8501101099", -("8501101099", -("8501200501", -("8501200502", -("8501200599", -("8501200599", -("8501200599", -("8501310100", -("8501310200", -("8501310300", -("8501310400", -("8501310500", -("8501319900", -("8501320100", -("8501320200", -("8501320300", -("8501320400", -("8501320500", -("8501320600", -("8501329900", -("8501330100", -("8501330200", -("8501330300", -("8501330400", -("8501330500", -("8501339900", -("8501340100", -("8501340500", -("8501349900", -("8501349900", -("8501349900", -("8501349900", -("8501400500", -("8501400800", -("8501409901", -("8501409902", -("8501409999", -("8501409999", -("8501409999", -("8501409999", -("8501409999", -("8501510200", -("8501519901", -("8501519999", -("8501519999", -("8501520400", -("8501529901", -("8501529902", -("8501529999", -("8501529999", -("8501529999", -("8501530400", -("8501530500", -("8501539901", -("8501539902", -("8501539903", -("8501539999", -("8501539999", -("8501539999", -("8501610100", -("8501620100", -("8501630100", -("8501640100", -("8501640300", -("8501649901", -("8501649999", -("8502110100", -("8502120100", -("8502130100", -("8502130200", -("8502139900", -("8502200100", -("8502209901", -("8502209999", -("8502310100", -("8502319900", -("8502390100", -("8502390200", -("8502390300", -("8502390400", -("8502399900", -("8502400100", -("8503000500", -("8503009901", -("8503009902", -("8503009903", -("8503009904", -("8503009905", -("8503009999", -("8504100100", -("8504109900", -("8504210100", -("8504210200", -("8504210300", -("8504219901", -("8504219999", -("8504220100", -("8504230100", -("8504310300", -("8504310400", -("8504319901", -("8504319902", -("8504319903", -("8504319904", -("8504319999", -("8504320100", -("8504329901", -("8504329999", -("8504330201", -("8504330299", -("8504340100", -("8504400100", -("8504400200", -("8504400500", -("8504400600", -("8504400700", -("8504400800", -("8504400900", -("8504401100", -("8504401300", -("8504401400", -("8504401400", -("8504401500", -("8504401600", -("8504401700", -("8504401700", -("8504409900", -("8504500301", -("8504500302", -("8504500399", -("8504900200", -("8504900700", -("8504900800", -("8504909901", -("8504909902", -("8504909999", -("8504909999", -("8504909999", -("8504909999", -("8505110100", -("8505199900", -("8505200100", -("8505900601", -("8505900699", -("8505900699", -("8505900699", -("8505900699", -("8505900699", -("8506100301", -("8506100399", -("8506300100", -("8506400100", -("8506500100", -("8506600100", -("8506800100", -("8506900100", -("8507100100", -("8507109900", -("8507200100", -("8507200200", -("8507200400", -("8507209901", -("8507209999", -("8507300100", -("8507400100", -("8507500100", -("8507600100", -("8507800100", -("8507900100", -("8508110100", -("8508199901", -("8508199999", -("8508600100", -("8508700100", -("8508700200", -("8508709900", -("8509400200", -("8509400300", -("8509409901", -("8509409999", -("8509800300", -("8509800400", -("8509800700", -("8509800900", -("8509809901", -("8509809902", -("8509809999", -("8509809999", -("8509809999", -("8509809999", -("8509809999", -("8509809999", -("8509809999", -("8509900200", -("8509909901", -("8509909999", -("8509909999", -("8510100100", -("8510200100", -("8510300100", -("8510900100", -("8510900200", -("8510900300", -("8510900400", -("8510909900", -("8511100300", -("8511109901", -("8511109999", -("8511109999", -("8511200401", -("8511200499", -("8511200499", -("8511200499", -("8511300501", -("8511300502", -("8511300599", -("8511300599", -("8511300599", -("8511400401", -("8511400402", -("8511400403", -("8511400499", -("8511500501", -("8511500502", -("8511500503", -("8511500504", -("8511500599", -("8511800100", -("8511800200", -("8511800300", -("8511800400", -("8511809900", -("8511900601", -("8511900602", -("8511900603", -("8511900699", -("8511900699", -("8511900699", -("8512100300", -("8512100300", -("8512100300", -("8512200100", -("8512200200", -("8512209900", -("8512300100", -("8512309900", -("8512400100", -("8512900701", -("8512900702", -("8512900703", -("8512900704", -("8512900705", -("8512900799", -("8512900799", -("8513100100", -("8513109900", -("8513900100", -("8514100100", -("8514100200", -("8514100300", -("8514100400", -("8514109900", -("8514200100", -("8514200200", -("8514200300", -("8514200400", -("8514200500", -("8514209900", -("8514300100", -("8514309901", -("8514309902", -("8514309903", -("8514309904", -("8514309905", -("8514309999", -("8514400201", -("8514400299", -("8514900401", -("8514900402", -("8514900499", -("8514900499", -("8515110100", -("8515119900", -("8515199900", -("8515210100", -("8515219900", -("8515299901", -("8515299999", -("8515310100", -("8515310200", -("8515319900", -("8515390100", -("8515390200", -("8515399900", -("8515800301", -("8515800302", -("8515800399", -("8515900100", -("8515909900", -("8516100200", -("8516210100", -("8516299901", -("8516299999", -("8516310100", -("8516320100", -("8516330100", -("8516400100", -("8516500100", -("8516600100", -("8516600300", -("8516609901", -("8516609999", -("8516710100", -("8516720100", -("8516790100", -("8516799900", -("8516800401", -("8516800402", -("8516800499", -("8516800499", -("8516900700", -("8516900900", -("8516909900", -("8516909900", -("8516909900", -("8516909900", -("8516909900", -("8516909900", -("8516909900", -("8516909900", -("8516909900", -("8517110100", -("8517120201", -("8517120299", -("8517180200", -("8517189901", -("8517189999", -("8517610100", -("8517621701", -("8517621702", -("8517621703", -("8517621704", -("8517621705", -("8517621706", -("8517621707", -("8517621708", -("8517621709", -("8517621710", -("8517621711", -("8517621791", -("8517621799", -("8517621799", -("8517621799", -("8517621799", -("8517621799", -("8517621799", -("8517690500", -("8517690700", -("8517691300", -("8517691400", -("8517691500", -("8517691600", -("8517691700", -("8517691900", -("8517699901", -("8517699902", -("8517699903", -("8517699904", -("8517699999", -("8517699999", -("8517699999", -("8517699999", -("8517699999", -("8517699999", -("8517699999", -("8517699999", -("8517699999", -("8517699999", -("8517700900", -("8517701000", -("8517701100", -("8517701200", -("8517709901", -("8517709902", -("8517709991", -("8517709999", -("8517709999", -("8517709999", -("8517709999", -("8517709999", -("8517709999", -("8517709999", -("8518100401", -("8518100402", -("8518100499", -("8518100499", -("8518210201", -("8518210202", -("8518210299", -("8518220201", -("8518220202", -("8518220299", -("8518299900", -("8518299900", -("8518300300", -("8518300400", -("8518309901", -("8518309902", -("8518309999", -("8518400100", -("8518400200", -("8518400400", -("8518409901", -("8518409999", -("8518409999", -("8518409999", -("8518500100", -("8518900100", -("8518900200", -("8518909901", -("8518909999", -("8519200100", -("8519300200", -("8519300200", -("8519500100", -("8519810300", -("8519810400", -("8519810500", -("8519810600", -("8519810700", -("8519810800", -("8519810900", -("8519811000", -("8519811200", -("8519811200", -("8519819900", -("8519819900", -("8519890100", -("8519899900", -("8519899900", -("8519899900", -("8519899900", -("8521100201", -("8521100299", -("8521900100", -("8521900200", -("8521900300", -("8521900400", -("8521900500", -("8521909900", -("8522100100", -("8522900700", -("8522909901", -("8522909902", -("8522909903", -("8522909999", -("8522909999", -("8522909999", -("8522909999", -("8522909999", -("8522909999", -("8522909999", -("8522909999", -("8522909999", -("8522909999", -("8522909999", -("8523210201", -("8523210299", -("8523290500", -("8523290700", -("8523290800", -("8523290900", -("8523290900", -("8523299901", -("8523299902", -("8523299903", -("8523299904", -("8523299999", -("8523410100", -("8523419900", -("8523499901", -("8523499999", -("8523510100", -("8523519900", -("8523520301", -("8523520302", -("8523590100", -("8523599900", -("8523809900", -("8525500501", -("8525500502", -("8525500503", -("8525500599", -("8525500599", -("8525600500", -("8525609901", -("8525609902", -("8525609903", -("8525609904", -("8525609905", -("8525609906", -("8525609999", -("8525609999", -("8525609999", -("8525800501", -("8525800502", -("8525800503", -("8525800504", -("8525800599", -("8526100100", -("8526109900", -("8526910100", -("8526919900", -("8526920100", -("8526929900", -("8527120100", -("8527130100", -("8527130200", -("8527190100", -("8527199900", -("8527210100", -("8527219900", -("8527290100", -("8527299900", -("8527910201", -("8527910202", -("8527910299", -("8527920100", -("8527999901", -("8527999999", -("8528420201", -("8528420201", -("8528420201", -("8528420201", -("8528420201", -("8528420201", -("8528420201", -("8528420299", -("8528420299", -("8528420299", -("8528490600", -("8528490700", -("8528491000", -("8528491000", -("8528491000", -("8528491000", -("8528491000", -("8528491100", -("8528491100", -("8528499900", -("8528520201", -("8528520299", -("8528590100", -("8528590200", -("8528590300", -("8528590400", -("8528599900", -("8528620100", -("8528699901", -("8528699902", -("8528699999", -("8528699999", -("8528699999", -("8528710100", -("8528710200", -("8528710300", -("8528710400", -("8528719900", -("8528720100", -("8528720200", -("8528720300", -("8528720400", -("8528720500", -("8528720600", -("8528720700", -("8528729900", -("8528730100", -("8529100901", -("8529100902", -("8529100903", -("8529100904", -("8529100905", -("8529100906", -("8529100907", -("8529100999", -("8529100999", -("8529900900", -("8529901300", -("8529901500", -("8529901600", -("8529901800", -("8529901900", -("8529902000", -("8529902100", -("8529909901", -("8529909902", -("8529909903", -("8529909904", -("8529909905", -("8529909906", -("8529909907", -("8529909908", -("8529909909", -("8529909991", -("8529909999", -("8529909999", -("8529909999", -("8529909999", -("8529909999", -("8529909999", -("8529909999", -("8530100100", -("8530800200", -("8530809900", -("8530809900", -("8530900100", -("8531100100", -("8531100200", -("8531100300", -("8531100400", -("8531109901", -("8531109999", -("8531200100", -("8531800100", -("8531800300", -("8531809901", -("8531809999", -("8531900200", -("8531909900", -("8531909900", -("8532100100", -("8532109900", -("8532210100", -("8532220401", -("8532220402", -("8532220499", -("8532220499", -("8532230601", -("8532230602", -("8532230699", -("8532230699", -("8532230699", -("8532230699", -("8532240400", -("8532240400", -("8532240400", -("8532240400", -("8532250200", -("8532259901", -("8532259902", -("8532259999", -("8532259999", -("8532299901", -("8532299999", -("8532299999", -("8532299999", -("8532299999", -("8532299999", -("8532300500", -("8532300500", -("8532300500", -("8532300500", -("8532300500", -("8532900100", -("8533100301", -("8533100302", -("8533210100", -("8533299900", -("8533299900", -("8533310200", -("8533310200", -("8533399900", -("8533399900", -("8533399900", -("8533400801", -("8533400802", -("8533400803", -("8533400804", -("8533400805", -("8533400899", -("8533400899", -("8533400899", -("8533900301", -("8533900399", -("8533900399", -("8534000401", -("8534000402", -("8534000403", -("8534000499", -("8535100100", -("8535100200", -("8535100300", -("8535109900", -("8535210100", -("8535299900", -("8535300701", -("8535300702", -("8535300703", -("8535300704", -("8535300799", -("8535300799", -("8535300799", -("8535400100", -("8535409901", -("8535409999", -("8535900800", -("8535902000", -("8535909901", -("8535909902", -("8535909903", -("8535909904", -("8535909905", -("8535909906", -("8535909999", -("8535909999", -("8535909999", -("8535909999", -("8535909999", -("8535909999", -("8535909999", -("8535909999", -("8535909999", -("8535909999", -("8535909999", -("8535909999", -("8535909999", -("8535909999", -("8535909999", -("8536100300", -("8536100400", -("8536109901", -("8536109902", -("8536109999", -("8536109999", -("8536200100", -("8536200200", -("8536209900", -("8536300500", -("8536309901", -("8536309902", -("8536309999", -("8536309999", -("8536309999", -("8536410300", -("8536419901", -("8536419902", -("8536419903", -("8536419904", -("8536419905", -("8536419906", -("8536419999", -("8536419999", -("8536419999", -("8536419999", -("8536419999", -("8536499901", -("8536499902", -("8536499903", -("8536499904", -("8536499999", -("8536499999", -("8536500700", -("8536509901", -("8536509902", -("8536509903", -("8536509904", -("8536509905", -("8536509906", -("8536509907", -("8536509908", -("8536509909", -("8536509910", -("8536509999", -("8536509999", -("8536509999", -("8536509999", -("8536509999", -("8536509999", -("8536509999", -("8536610400", -("8536610400", -("8536610400", -("8536610400", -("8536690200", -("8536699901", -("8536699999", -("8536700100", -("8536902800", -("8536909901", -("8536909902", -("8536909903", -("8536909904", -("8536909905", -("8536909906", -("8536909907", -("8536909908", -("8536909909", -("8536909910", -("8536909911", -("8536909912", -("8536909913", -("8536909914", -("8536909915", -("8536909916", -("8536909917", -("8536909918", -("8536909919", -("8536909920", -("8536909921", -("8536909999", -("8536909999", -("8536909999", -("8536909999", -("8536909999", -("8536909999", -("8536909999", -("8537100300", -("8537100400", -("8537100500", -("8537100600", -("8537109901", -("8537109902", -("8537109999", -("8537200200", -("8537209901", -("8537209999", -("8538100100", -("8538900100", -("8538900500", -("8538909901", -("8538909902", -("8538909999", -("8538909999", -("8539109901", -("8539109902", -("8539109999", -("8539109999", -("8539210100", -("8539219900", -("8539220100", -("8539220200", -("8539220300", -("8539220400", -("8539220500", -("8539229900", -("8539299901", -("8539299999", -("8539299999", -("8539299999", -("8539299999", -("8539299999", -("8539299999", -("8539299999", -("8539299999", -("8539310100", -("8539319900", -("8539320401", -("8539320402", -("8539320403", -("8539320499", -("8539390100", -("8539390200", -("8539390300", -("8539390400", -("8539390500", -("8539390600", -("8539399900", -("8539410100", -("8539499901", -("8539499999", -("8539500100", -("8539900701", -("8539900702", -("8539900703", -("8539900704", -("8539900799", -("8539900799", -("8539900799", -("8540110601", -("8540110602", -("8540110699", -("8540110699", -("8540110699", -("8540110699", -("8540120200", -("8540120200", -("8540200200", -("8540200200", -("8540400200", -("8540600200", -("8540600200", -("8540710100", -("8540799900", -("8540810300", -("8540810300", -("8540810300", -("8540899901", -("8540899999", -("8540899999", -("8540899999", -("8540910500", -("8540910500", -("8540910500", -("8540910500", -("8540910500", -("8540999900", -("8540999900", -("8540999900", -("8540999900", -("8540999900", -("8541100101", -("8541100199", -("8541210100", -("8541299900", -("8541300101", -("8541300199", -("8541400401", -("8541400402", -("8541400403", -("8541500100", -("8541600100", -("8541900201", -("8541900299", -("8542310301", -("8542310302", -("8542310399", -("8542320201", -("8542320299", -("8542330201", -("8542330299", -("8542399901", -("8542399999", -("8542900100", -("8543100200", -("8543200601", -("8543200699", -("8543200699", -("8543200699", -("8543200699", -("8543200699", -("8543300100", -("8543700100", -("8543700800", -("8543701700", -("8543709901", -("8543709902", -("8543709903", -("8543709904", -("8543709905", -("8543709906", -("8543709907", -("8543709908", -("8543709909", -("8543709910", -("8543709999", -("8543709999", -("8543709999", -("8543709999", -("8543709999", -("8543900100", -("8543900200", -("8543909900", -("8544110100", -("8544199901", -("8544199999", -("8544200100", -("8544200200", -("8544200300", -("8544209900", -("8544300100", -("8544309901", -("8544309999", -("8544420500", -("8544429901", -("8544429902", -("8544429903", -("8544429904", -("8544429905", -("8544429906", -("8544429999", -("8544490500", -("8544499901", -("8544499902", -("8544499903", -("8544499904", -("8544499905", -("8544499905", -("8544499905", -("8544499991", -("8544499991", -("8544499991", -("8544499992", -("8544499992", -("8544499992", -("8544499999", -("8544499999", -("8544600201", -("8544600202", -("8544600299", -("8544700100", -("8545110100", -("8545199900", -("8545200100", -("8545909900", -("8545909900", -("8545909900", -("8546100100", -("8546100200", -("8546100300", -("8546109900", -("8546200100", -("8546200200", -("8546200300", -("8546200400", -("8546200500", -("8546209900", -("8546900100", -("8546900200", -("8546900300", -("8546909900", -("8547100600", -("8547109901", -("8547109902", -("8547109999", -("8547109999", -("8547109999", -("8547109999", -("8547200400", -("8547200400", -("8547200400", -("8547200400", -("8547909901", -("8547909902", -("8547909999", -("8547909999", -("8547909999", -("8547909999", -("8547909999", -("8547909999", -("8547909999", -("8547909999", -("8547909999", -("8547909999", -("8547909999", -("8548100100", -("8548900400", -("8548909901", -("8548909902", -("8548909903", -("8548909999", -("8601100100", -("8601200100", -("8602100100", -("8602909900", -("8603100100", -("8603909900", -("8604000301", -("8604000399", -("8604000399", -("8605000100", -("8605009900", -("8606100100", -("8606300100", -("8606910200", -("8606910200", -("8606920100", -("8606999900", -("8607110100", -("8607129900", -("8607190200", -("8607190300", -("8607199901", -("8607199999", -("8607199999", -("8607199999", -("8607199999", -("8607210200", -("8607210200", -("8607299900", -("8607300100", -("8607910201", -("8607910299", -("8607999900", -("8608000401", -("8608000402", -("8608000499", -("8608000499", -("8609000100", -("8701100100", -("8701200100", -("8701200200", -("8701300100", -("8701309900", -("8701910100", -("8701910200", -("8701919901", -("8701919902", -("8701919903", -("8701919999", -("8701920100", -("8701920200", -("8701920300", -("8701929901", -("8701929902", -("8701929903", -("8701929999", -("8701930100", -("8701930200", -("8701939901", -("8701939902", -("8701939903", -("8701939999", -("8701940100", -("8701940200", -("8701940300", -("8701940400", -("8701940500", -("8701949901", -("8701949902", -("8701949999", -("8701950100", -("8701950200", -("8701959901", -("8701959902", -("8701959903", -("8701959999", -("8702100500", -("8702109901", -("8702109902", -("8702109903", -("8702109904", -("8702200500", -("8702200500", -("8702209901", -("8702209901", -("8702209902", -("8702209902", -("8702209903", -("8702209903", -("8702209904", -("8702209904", -("8702300500", -("8702309901", -("8702309902", -("8702309903", -("8702309904", -("8702400100", -("8702400201", -("8702400202", -("8702400203", -("8702400204", -("8702400600", -("8702400700", -("8702900100", -("8702900600", -("8702900800", -("8702909901", -("8702909902", -("8702909903", -("8702909904", -("8703100401", -("8703100402", -("8703100499", -("8703210100", -("8703210200", -("8703219900", -("8703220100", -("8703220200", -("8703230100", -("8703230200", -("8703240100", -("8703240200", -("8703310100", -("8703310200", -("8703320100", -("8703320200", -("8703330100", -("8703330200", -("8703400100", -("8703400100", -("8703400100", -("8703400100", -("8703400100", -("8703400200", -("8703400200", -("8703400200", -("8703400200", -("8703400200", -("8703400300", -("8703500100", -("8703500100", -("8703500100", -("8703500100", -("8703500200", -("8703500200", -("8703500200", -("8703500200", -("8703600100", -("8703600100", -("8703600100", -("8703600100", -("8703600100", -("8703600200", -("8703600200", -("8703600200", -("8703600200", -("8703600200", -("8703600300", -("8703700100", -("8703700100", -("8703700100", -("8703700100", -("8703700200", -("8703700200", -("8703700200", -("8703700200", -("8703800100", -("8703800200", -("8703900200", -("8703909900", -("8704100201", -("8704100299", -("8704210100", -("8704210400", -("8704219901", -("8704219902", -("8704219999", -("8704220100", -("8704220700", -("8704229901", -("8704229902", -("8704229903", -("8704229904", -("8704229905", -("8704229999", -("8704230100", -("8704230200", -("8704239900", -("8704310100", -("8704310200", -("8704310500", -("8704319901", -("8704319999", -("8704320100", -("8704320700", -("8704329901", -("8704329902", -("8704329903", -("8704329904", -("8704329999", -("8704329999", -("8704900100", -("8704909900", -("8705100100", -("8705200100", -("8705209900", -("8705300100", -("8705400100", -("8705400200", -("8705900100", -("8705900200", -("8705909900", -("8706000100", -("8706009900", -("8706009900", -("8707100200", -("8707100200", -("8707900200", -("8707909901", -("8707909902", -("8707909999", -("8707909999", -("8708100100", -("8708100200", -("8708100300", -("8708109900", -("8708210100", -("8708290100", -("8708290200", -("8708290300", -("8708290400", -("8708290500", -("8708290600", -("8708290700", -("8708290800", -("8708290900", -("8708291000", -("8708291100", -("8708291200", -("8708291300", -("8708291600", -("8708291700", -("8708291800", -("8708291900", -("8708292000", -("8708292200", -("8708292300", -("8708292400", -("8708299901", -("8708299902", -("8708299903", -("8708299904", -("8708299905", -("8708299906", -("8708299907", -("8708299908", -("8708299999", -("8708300100", -("8708300200", -("8708300400", -("8708300600", -("8708300800", -("8708300900", -("8708301000", -("8708301100", -("8708309901", -("8708309902", -("8708309903", -("8708309904", -("8708309999", -("8708309999", -("8708309999", -("8708400100", -("8708400200", -("8708400300", -("8708400400", -("8708400500", -("8708400700", -("8708400800", -("8708409901", -("8708409999", -("8708500100", -("8708500200", -("8708500300", -("8708500400", -("8708500500", -("8708500600", -("8708500700", -("8708500800", -("8708500900", -("8708501000", -("8708501300", -("8708501400", -("8708501500", -("8708501600", -("8708501800", -("8708502000", -("8708502200", -("8708502300", -("8708502400", -("8708502500", -("8708502600", -("8708502700", -("8708502800", -("8708502900", -("8708503000", -("8708509901", -("8708509902", -("8708509903", -("8708509904", -("8708509999", -("8708509999", -("8708700600", -("8708709901", -("8708709902", -("8708709903", -("8708709904", -("8708709999", -("8708709999", -("8708709999", -("8708800100", -("8708800200", -("8708800300", -("8708800400", -("8708800500", -("8708800600", -("8708800700", -("8708800800", -("8708800900", -("8708801000", -("8708801100", -("8708801200", -("8708809900", -("8708910100", -("8708910200", -("8708910300", -("8708919900", -("8708920301", -("8708920399", -("8708920399", -("8708930501", -("8708930502", -("8708930599", -("8708930599", -("8708930599", -("8708941201", -("8708941202", -("8708941202", -("8708941202", -("8708941203", -("8708941203", -("8708941203", -("8708941204", -("8708941204", -("8708941204", -("8708941205", -("8708941205", -("8708941205", -("8708941206", -("8708941206", -("8708941206", -("8708941207", -("8708941207", -("8708941207", -("8708941291", -("8708941291", -("8708941291", -("8708941299", -("8708941299", -("8708941299", -("8708941299", -("8708950201", -("8708950202", -("8708950299", -("8708990400", -("8708990700", -("8708990800", -("8708990900", -("8708999901", -("8708999901", -("8708999901", -("8708999902", -("8708999903", -("8708999904", -("8708999904", -("8708999904", -("8708999905", -("8708999905", -("8708999905", -("8708999906", -("8708999906", -("8708999906", -("8708999907", -("8708999907", -("8708999907", -("8708999908", -("8708999908", -("8708999908", -("8708999999", -("8708999999", -("8708999999", -("8709110100", -("8709199900", -("8709199900", -("8709199900", -("8709900100", -("8710000100", -("8711100300", -("8711100300", -("8711100300", -("8711200501", -("8711200502", -("8711200599", -("8711200599", -("8711300401", -("8711300499", -("8711300499", -("8711300499", -("8711400100", -("8711400200", -("8711409901", -("8711409999", -("8711500301", -("8711500302", -("8711500399", -("8711600100", -("8711900100", -("8711909900", -("8712000501", -("8712000502", -("8712000503", -("8712000599", -("8712000599", -("8713100100", -("8713909900", -("8714100100", -("8714100200", -("8714100300", -("8714109900", -("8714200100", -("8714910101", -("8714910102", -("8714910103", -("8714920100", -("8714930100", -("8714940100", -("8714949900", -("8714950100", -("8714960100", -("8714999900", -("8715000100", -("8715000200", -("8716100100", -("8716200401", -("8716200402", -("8716200499", -("8716200499", -("8716310301", -("8716310302", -("8716310399", -("8716390300", -("8716390400", -("8716390800", -("8716399901", -("8716399902", -("8716399903", -("8716399904", -("8716399905", -("8716399999", -("8716409900", -("8716800300", -("8716809901", -("8716809902", -("8716809999", -("8716900300", -("8716909901", -("8716909902", -("8716909999", -("8801000200", -("8801000200", -("8802110100", -("8802119900", -("8802120100", -("8802129900", -("8802200100", -("8802209900", -("8802300100", -("8802300200", -("8802309900", -("8802400100", -("8802600100", -("8803100100", -("8803200100", -("8803309900", -("8803909900", -("8804000100", -("8805100100", -("8805210100", -("8805290100", -("8901100100", -("8901109900", -("8901200100", -("8901209900", -("8901300100", -("8901309900", -("8901900100", -("8901900200", -("8901900300", -("8901909900", -("8902000301", -("8902000399", -("8902000399", -("8903100100", -("8903910100", -("8903920100", -("8903999900", -("8904000100", -("8905100100", -("8905200100", -("8905900100", -("8905909900", -("8906100100", -("8906909900", -("8907100100", -("8907909900", -("8908000100", -("9001100201", -("9001100299", -("9001200100", -("9001300200", -("9001309901", -("9001309999", -("9001400300", -("9001400300", -("9001400300", -("9001500201", -("9001500299", -("9001909900", -("9001909900", -("9002110100", -("9002199900", -("9002200100", -("9002909900", -("9003110100", -("9003190100", -("9003900200", -("9003900200", -("9004100100", -("9004909900", -("9005100100", -("9005809900", -("9005900100", -("9005900200", -("9005909900", -("9006300100", -("9006400100", -("9006510100", -("9006520100", -("9006529900", -("9006530100", -("9006539900", -("9006599901", -("9006599999", -("9006599999", -("9006599999", -("9006610100", -("9006699900", -("9006699900", -("9006910300", -("9006910300", -("9006999900", -("9007100100", -("9007100200", -("9007109900", -("9007200100", -("9007910100", -("9007920100", -("9008500100", -("9008900200", -("9008900200", -("9010100100", -("9010500100", -("9010600100", -("9010900100", -("9011100100", -("9011109900", -("9011209900", -("9011800100", -("9011900100", -("9012100100", -("9012900100", -("9013100100", -("9013200100", -("9013800200", -("9013800200", -("9013900100", -("9014100100", -("9014100200", -("9014100300", -("9014109900", -("9014200100", -("9014800301", -("9014800399", -("9014800399", -("9014900201", -("9014900299", -("9015100100", -("9015200100", -("9015209900", -("9015300100", -("9015400100", -("9015800100", -("9015800300", -("9015800400", -("9015800500", -("9015809901", -("9015809999", -("9015809999", -("9015809999", -("9015900100", -("9016000201", -("9016000299", -("9017100100", -("9017109900", -("9017109900", -("9017200100", -("9017209900", -("9017300201", -("9017300299", -("9017800300", -("9017800400", -("9017809901", -("9017809991", -("9017809999", -("9017900200", -("9017900200", -("9018110100", -("9018110200", -("9018120100", -("9018130100", -("9018140100", -("9018190200", -("9018190500", -("9018190600", -("9018199901", -("9018199902", -("9018199999", -("9018199999", -("9018199999", -("9018199999", -("9018199999", -("9018199999", -("9018200100", -("9018310100", -("9018319900", -("9018320200", -("9018320300", -("9018320400", -("9018329901", -("9018329999", -("9018390200", -("9018390300", -("9018390400", -("9018390600", -("9018399901", -("9018399902", -("9018399999", -("9018410100", -("9018419900", -("9018490200", -("9018490400", -("9018490500", -("9018490600", -("9018499901", -("9018499902", -("9018499999", -("9018500100", -("9018900100", -("9018900200", -("9018900300", -("9018900400", -("9018900500", -("9018900600", -("9018900700", -("9018900800", -("9018900900", -("9018901000", -("9018901100", -("9018901200", -("9018901300", -("9018901400", -("9018901500", -("9018901600", -("9018901700", -("9018901800", -("9018901900", -("9018902000", -("9018902500", -("9018902700", -("9018902800", -("9018903100", -("9018909901", -("9018909902", -("9018909999", -("9018909999", -("9018909999", -("9018909999", -("9018909999", -("9018909999", -("9019100200", -("9019109901", -("9019109999", -("9019200100", -("9020000200", -("9020000300", -("9020009901", -("9020009999", -("9021100601", -("9021100602", -("9021100603", -("9021100604", -("9021100699", -("9021100699", -("9021210201", -("9021210299", -("9021299900", -("9021310100", -("9021390100", -("9021390200", -("9021390400", -("9021399900", -("9021399900", -("9021400100", -("9021500100", -("9021909900", -("9022120100", -("9022130100", -("9022140201", -("9022140299", -("9022190100", -("9022210200", -("9022210200", -("9022290100", -("9022300100", -("9022900300", -("9022909901", -("9022909999", -("9022909999", -("9023000100", -("9024100100", -("9024800100", -("9024900100", -("9025110100", -("9025119900", -("9025190100", -("9025190200", -("9025190300", -("9025190400", -("9025199900", -("9025800100", -("9025809901", -("9025809902", -("9025809999", -("9025900100", -("9026100701", -("9026100702", -("9026100703", -("9026100704", -("9026100799", -("9026100799", -("9026100799", -("9026200300", -("9026200400", -("9026209901", -("9026209902", -("9026209903", -("9026209904", -("9026209999", -("9026800301", -("9026800399", -("9026800399", -("9026900100", -("9027100100", -("9027200100", -("9027300100", -("9027509900", -("9027800401", -("9027800402", -("9027800499", -("9027800499", -("9027900100", -("9027900200", -("9027900300", -("9027909900", -("9028100100", -("9028200200", -("9028200300", -("9028209901", -("9028209999", -("9028300100", -("9028309900", -("9028900301", -("9028900302", -("9028900399", -("9029100300", -("9029109901", -("9029109999", -("9029109999", -("9029109999", -("9029200601", -("9029200602", -("9029200603", -("9029200699", -("9029200699", -("9029200699", -("9029900100", -("9030100100", -("9030200200", -("9030200200", -("9030310100", -("9030320100", -("9030330701", -("9030330702", -("9030330703", -("9030330799", -("9030330799", -("9030330799", -("9030330799", -("9030390100", -("9030400201", -("9030400299", -("9030820100", -("9030840100", -("9030890501", -("9030890599", -("9030890599", -("9030890599", -("9030890599", -("9030900501", -("9030900502", -("9030900599", -("9030900599", -("9030900599", -("9031100100", -("9031200200", -("9031200200", -("9031410100", -("9031499901", -("9031499902", -("9031499999", -("9031800100", -("9031800200", -("9031800700", -("9031809901", -("9031809902", -("9031809903", -("9031809999", -("9031809999", -("9031900100", -("9031909900", -("9031909900", -("9032100100", -("9032100300", -("9032100400", -("9032109901", -("9032109999", -("9032200100", -("9032810200", -("9032810200", -("9032890100", -("9032890200", -("9032890300", -("9032890400", -("9032890500", -("9032890600", -("9032899900", -("9032900201", -("9032900299", -("9033000100", -("9101110100", -("9101199900", -("9101199900", -("9101210100", -("9101299900", -("9101910100", -("9101999900", -("9102110100", -("9102120100", -("9102199900", -("9102210100", -("9102299900", -("9102910100", -("9102999900", -("9103100100", -("9103909900", -("9104000401", -("9104000499", -("9104000499", -("9104000499", -("9105110201", -("9105110299", -("9105199900", -("9105210100", -("9105299900", -("9105910200", -("9105910200", -("9105999900", -("9105999900", -("9106100200", -("9106100200", -("9106909901", -("9106909999", -("9107000100", -("9108110100", -("9108120100", -("9108199900", -("9108200100", -("9108909900", -("9109100100", -("9109909900", -("9110110100", -("9110120100", -("9110190100", -("9110909900", -("9111100200", -("9111100200", -("9111200100", -("9111809900", -("9111900100", -("9112200201", -("9112200299", -("9112900100", -("9113100200", -("9113100200", -("9113200200", -("9113200200", -("9113900100", -("9113909900", -("9114100100", -("9114300100", -("9114400100", -("9114909900", -("9114909900", -("9114909900", -("9201100100", -("9201200100", -("9201900100", -("9201909900", -("9201909900", -("9202100100", -("9202900100", -("9202900200", -("9202909900", -("9205100100", -("9205900200", -("9205900300", -("9205900400", -("9205909900", -("9205909900", -("9206000100", -("9207100100", -("9207100200", -("9207109900", -("9207909900", -("9208100100", -("9208909900", -("9209300100", -("9209910100", -("9209919900", -("9209920100", -("9209940100", -("9209949900", -("9209999900", -("9209999900", -("9209999900", -("9209999900", -("9301100200", -("9301100200", -("9301200100", -("9301909900", -("9302000200", -("9302000200", -("9303100100", -("9303109900", -("9303200100", -("9303300100", -("9303900100", -("9303909900", -("9304000100", -("9304009900", -("9305100100", -("9305109900", -("9305200200", -("9305200200", -("9305910100", -("9305999900", -("9306210100", -("9306219900", -("9306299900", -("9306299900", -("9306300400", -("9306309901", -("9306309902", -("9306309999", -("9306309999", -("9306900301", -("9306900302", -("9306909900", -("9307000100", -("9401100100", -("9401200100", -("9401300100", -("9401400100", -("9401520100", -("9401530100", -("9401599900", -("9401610100", -("9401699900", -("9401710100", -("9401799901", -("9401799999", -("9401800100", -("9401900200", -("9401909901", -("9401909999", -("9402100100", -("9402109900", -("9402900100", -("9402900200", -("9402909900", -("9403100301", -("9403100302", -("9403100303", -("9403100399", -("9403100399", -("9403200501", -("9403200502", -("9403200503", -("9403200504", -("9403200505", -("9403200599", -("9403200599", -("9403200599", -("9403300100", -("9403300200", -("9403400100", -("9403500100", -("9403600100", -("9403600200", -("9403600300", -("9403609900", -("9403700300", -("9403700300", -("9403700300", -("9403820100", -("9403830100", -("9403899900", -("9403900101", -("9403900199", -("9404100100", -("9404210201", -("9404210299", -("9404299900", -("9404300100", -("9404909901", -("9404909902", -("9404909903", -("9404909904", -("9404909905", -("9404909906", -("9404909999", -("9405100401", -("9405100402", -("9405100403", -("9405100499", -("9405200201", -("9405200299", -("9405300100", -("9405400100", -("9405500200", -("9405500200", -("9405600100", -("9405910100", -("9405910200", -("9405910300", -("9405910400", -("9405919900", -("9405920100", -("9405999900", -("9406100100", -("9406909900", -("9503000100", -("9503000200", -("9503000300", -("9503000400", -("9503000500", -("9503000600", -("9503000700", -("9503000800", -("9503000900", -("9503001000", -("9503001100", -("9503001200", -("9503001300", -("9503001400", -("9503001500", -("9503001600", -("9503001700", -("9503001800", -("9503002000", -("9503002100", -("9503002200", -("9503002300", -("9503002400", -("9503002500", -("9503002600", -("9503002700", -("9503002800", -("9503002900", -("9503003000", -("9503003100", -("9503003200", -("9503003300", -("9503003400", -("9503003500", -("9503009901", -("9503009991", -("9503009999", -("9504200100", -("9504209900", -("9504300100", -("9504300200", -("9504309900", -("9504400100", -("9504500300", -("9504500401", -("9504500402", -("9504900100", -("9504900200", -("9504900300", -("9504900400", -("9504900500", -("9504909900", -("9505100100", -("9505109900", -("9505909900", -("9506110100", -("9506120100", -("9506199900", -("9506210100", -("9506299901", -("9506299999", -("9506310100", -("9506319900", -("9506320100", -("9506390100", -("9506399900", -("9506400100", -("9506510100", -("9506519900", -("9506599900", -("9506610100", -("9506620100", -("9506699900", -("9506700100", -("9506910301", -("9506910399", -("9506910399", -("9506990100", -("9506990200", -("9506990300", -("9506990400", -("9506990500", -("9506990600", -("9506999900", -("9507100100", -("9507200100", -("9507300100", -("9507909900", -("9508100100", -("9508900200", -("9508909900", -("9508909900", -("9601100100", -("9601909900", -("9602000100", -("9602009900", -("9603100100", -("9603210100", -("9603299900", -("9603300100", -("9603400100", -("9603500100", -("9603900100", -("9603909900", -("9604000100", -("9605000100", -("9606100100", -("9606210100", -("9606220100", -("9606299900", -("9606299900", -("9606300100", -("9607110100", -("9607199900", -("9607200100", -("9608100201", -("9608100299", -("9608200100", -("9608300100", -("9608309900", -("9608400200", -("9608409901", -("9608409999", -("9608500201", -("9608500299", -("9608600100", -("9608910200", -("9608910200", -("9608990300", -("9608990600", -("9608990700", -("9608990800", -("9608990900", -("9608991000", -("9608991100", -("9608991200", -("9608991200", -("9608999900", -("9608999900", -("9608999900", -("9609100100", -("9609200100", -("9609209900", -("9609900100", -("9609909900", -("9610000100", -("9611000100", -("9611009900", -("9612100300", -("9612109901", -("9612109902", -("9612109999", -("9612200100", -("9613100100", -("9613200100", -("9613800200", -("9613809900", -("9613809900", -("9613900100", -("9613900200", -("9613909900", -("9614000100", -("9614000300", -("9614009900", -("9614009900", -("9615110100", -("9615199900", -("9615909900", -("9616100100", -("9616200100", -("9617000100", -("9618000100", -("9618009900", -("9619000100", -("9619000200", -("9619000300", -("9619000400", -("9619009900", -("9620000100", -("9620000200", -("9620000200", -("9620000300", -("9620000400", -("9620009900", -("9620009900", -("9620009900", -("9620009900", -("9620009900", -("9620009900", -("9620009900", -("9620009900", -("9620009900", -("9620009900", -("9620009900", -("9620009900", -("9620009900", -("9701100100", -("9701909900", -("9702000100", -("9703000100", -("9704000200", -("9704000200", -("9705000500", -("9705000600", -("9705009900", -("9705009900", -("9705009900", -("9705009900", -("9705009900", -("9706000100", -("9801000100", -("9802000100", -("9802000200", -("9802000300", -("9802000400", -("9802000500", -("9802000600", -("9802000700", -("9802000800", -("9802000900", -("9802001000", -("9802001100", -("9802001200", -("9802001300", -("9802001400", -("9802001500", -("9802001600", -("9802001700", -("9802001800", -("9802001900", -("9802002000", -("9802002100", -("9802002200", -("9802002300", -("9802002400", -("9802002500", -("9802002600", -("9802002700", -("9802002800", -("9802002900", -("9802003000", -("9802003100", -("9802003200", -("9802003300", -("9802003400", -("9802003500", -("9802003600", -("9802003700", -("9802003800", -("9803000100", -("9803000200", -("9804000100", -("9804009900", -("9805000100", -("9806000100", -("9806000200", -("9806000300", -("9806000400", -("9806000500", -("9806000600", -("9806000700", -("9806000800", -("9806000900", -("9806001000", -("9807000100", -] \ No newline at end of file diff --git a/backend/api/v1/modules/a76/general_catalogs/fractions/tariff_fractions/__init__.py b/backend/api/v1/modules/a76/general_catalogs/fractions/tariff_fractions/__init__.py deleted file mode 100644 index f4610052..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/fractions/tariff_fractions/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -""" -Módulo de fracciones arancelarias (Tariff Fractions) -""" - -from .models import TariffFraction -from .routes import router - -__all__ = ["TariffFraction", "router"] diff --git a/backend/api/v1/modules/a76/general_catalogs/fractions/tariff_fractions/dto.py b/backend/api/v1/modules/a76/general_catalogs/fractions/tariff_fractions/dto.py deleted file mode 100644 index 16463397..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/fractions/tariff_fractions/dto.py +++ /dev/null @@ -1,91 +0,0 @@ -""" -DTOs (Data Transfer Objects) para fracciones arancelarias -""" - -from datetime import datetime -from typing import Optional - -from pydantic import BaseModel, ConfigDict, Field - - -class TariffFractionCreateDTO(BaseModel): - """DTO para crear una fracción arancelaria""" - - code: str = Field(..., max_length=10, description="Código completo de la fracción") - fraction: str = Field(..., max_length=15, description="Fracción formateada") - description: Optional[str] = Field(None, max_length=1000, description="Descripción") - nico: Optional[str] = Field(None, max_length=10, description="Código NICO") - umt: Optional[str] = Field(None, max_length=10, description="Unidad de medida de tarifa") - adv_impo: Optional[str] = Field(None, max_length=20, description="Ad valorem importación") - adv_expo: Optional[str] = Field(None, max_length=20, description="Ad valorem exportación") - - model_config = ConfigDict(from_attributes=True) - - -class TariffFractionUpdateDTO(BaseModel): - """DTO para actualizar una fracción arancelaria""" - - fraction: Optional[str] = Field(None, max_length=15, description="Fracción formateada") - description: Optional[str] = Field(None, max_length=1000, description="Descripción") - nico: Optional[str] = Field(None, max_length=10, description="Código NICO") - umt: Optional[str] = Field(None, max_length=10, description="Unidad de medida de tarifa") - adv_impo: Optional[str] = Field(None, max_length=20, description="Ad valorem importación") - adv_expo: Optional[str] = Field(None, max_length=20, description="Ad valorem exportación") - - model_config = ConfigDict(from_attributes=True) - - -class TariffFractionResponseDTO(BaseModel): - """DTO para respuesta de fracción arancelaria (catálogo global)""" - - id: int - code: str - fraction: str - description: Optional[str] = None - nico: Optional[str] = None - umt: Optional[str] = None - adv_impo: Optional[str] = None - adv_expo: Optional[str] = None - dof: Optional[str] = None - aplica_ieps: Optional[str] = None - um_code: Optional[str] = None - - model_config = ConfigDict(from_attributes=True) - - -class TariffFractionBasicDTO(BaseModel): - """DTO para información básica de fracción arancelaria""" - - code: str - fraction: str - description: Optional[str] = None - nico: Optional[str] = None - umt: Optional[str] = None - adv_impo: Optional[str] = None - adv_expo: Optional[str] = None - - model_config = ConfigDict(from_attributes=True) - - -class TariffFractionListDTO(BaseModel): - """DTO para lista de fracciones arancelarias""" - - items: list[TariffFractionBasicDTO] - total: int - page: int - page_size: int - pages: int - - model_config = ConfigDict(from_attributes=True) - - -class TariffFractionSearchDTO(BaseModel): - """DTO para búsqueda de fracciones arancelarias""" - - code: Optional[str] = Field(None, description="Buscar por código") - fraction: Optional[str] = Field(None, description="Buscar por fracción") - description: Optional[str] = Field(None, description="Buscar en descripción") - nico: Optional[str] = Field(None, description="Filtrar por NICO") - umt: Optional[str] = Field(None, description="Filtrar por UMT") - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/a76/general_catalogs/fractions/tariff_fractions/models.py b/backend/api/v1/modules/a76/general_catalogs/fractions/tariff_fractions/models.py deleted file mode 100644 index 71256e0e..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/fractions/tariff_fractions/models.py +++ /dev/null @@ -1,49 +0,0 @@ -""" -Modelos ORM para fracciones arancelarias (SITAR-SCAII) -""" - -from typing import Optional - -from core.database import Base -from sqlalchemy import Integer, PrimaryKeyConstraint, String, Numeric -from sqlalchemy.orm import Mapped, mapped_column - - -class TariffFraction(Base): - """ - Modelo para fracciones arancelarias mexicanas (SITAR-SCAII) - Catálogo de referencia global (no tenant-scoped) - Corresponde a la tabla sFracciones - """ - - __tablename__ = "tariff_fractions" - __table_args__ = ( - PrimaryKeyConstraint("id", name="tariff_fractions_pkey"), - {"schema": "a76", "extend_existing": True}, - ) - - id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) - - # Código completo de la fracción (ej: 01012101) - code: Mapped[str] = mapped_column(String(10), unique=True, index=True, nullable=False) - - # Fracción formateada (ej: 0101.21.01) - fraction: Mapped[str] = mapped_column(String(15), index=True) - - # Descripción de la fracción - description: Mapped[Optional[str]] = mapped_column(String(1000)) - - # Código NICO - nico: Mapped[Optional[str]] = mapped_column(String(10)) - - # Unidad de medida de tarifa (UMT) - umt: Mapped[Optional[str]] = mapped_column(String(10)) - - # Ad valorem de importación - adv_impo: Mapped[Optional[str]] = mapped_column(String(20)) - - # Ad valorem de exportación - adv_expo: Mapped[Optional[str]] = mapped_column(String(20)) - - def __repr__(self) -> str: - return f"" diff --git a/backend/api/v1/modules/a76/general_catalogs/fractions/tariff_fractions/routes.py b/backend/api/v1/modules/a76/general_catalogs/fractions/tariff_fractions/routes.py deleted file mode 100644 index 52c5e05b..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/fractions/tariff_fractions/routes.py +++ /dev/null @@ -1,251 +0,0 @@ -""" -Endpoints API para fracciones arancelarias -Catálogo de referencia global (no tenant-scoped) -""" - -from typing import Any, Dict, Optional -from fastapi import APIRouter, Depends, HTTPException, Query, status -from sqlalchemy.orm import Session - -from core.database import get_core_db -from core.security import get_current_user, get_tenant_from_token, validate_access_to_resource - -from .dto import ( - TariffFractionCreateDTO, - TariffFractionResponseDTO, - TariffFractionUpdateDTO, -) -from .service import TariffFractionService - -router = APIRouter(prefix="/tariff-fractions", tags=["a76 / general catalogs / tariff fractions"]) - -# Custom list endpoint with search filter -@router.get( - "/", - response_model=Dict[str, Any], - summary="List Tariff Fractions", - description="Get paginated list of Tariff Fractions with optional search filter (global catalog)", -) -async def list_tariff_fractions( - company_id: int = Query(..., description="Company ID"), - page: int = Query(1, ge=1, description="Page number"), - page_size: int = Query(50, ge=1, le=10000, description="Page size"), - search: Optional[str] = Query(None, description="Search in code, fraction, description, nico, or umt"), - level: Optional[int] = Query(None, description="Filter by hierarchy level (e.g. 5)"), - catalog: Optional[str] = Query( - "mex", - description="Catalog: 'mex' (default), 'usa', or 'american' (both US read-only via SITAR)", - ), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - # Validar permisos según el catálogo solicitado - if catalog == "mex": - validate_access_to_resource(db, company_id, current_user, ["frac_sitar.view"]) - elif catalog == "usa": - validate_access_to_resource(db, company_id, current_user, ["frac_sitar_us.view"]) - elif catalog == "american": - validate_access_to_resource(db, company_id, current_user, ["frac_american.view"]) - - skip = (page - 1) * page_size - filters = {} - if search: - filters["search"] = search - if level is not None: - filters["level"] = level - - # Updated to async call with Sitar integration - # WARNING: Using async def with blocking DB dependency (Session) run in threadpool by FastAPI. - # Service.get_all calls Sitar (async) or DB (sync). - # This should be fine. - - tenant_id = get_tenant_from_token(current_user) - if tenant_id is None: - tenant_id = current_user.get("tenant_id") - # If using headers for selected company, it might be in current_user context if middleware sets it. - - items, total = await TariffFractionService.get_all( - db, skip, page_size, filters, catalog, tenant_id, company_id - ) - - return { - "items": [TariffFractionResponseDTO.model_validate(item) for item in items], - "total": total, - "page": page, - "page_size": page_size, - "pages": (total + page_size - 1) // page_size, - } - - -@router.get( - "/{tariff_fraction_id}", - response_model=TariffFractionResponseDTO, - summary="Get Tariff Fraction by ID", - description="Get a specific tariff fraction by ID (Lookups in Local DB for legacy compatibility)", -) -async def get_tariff_fraction( - tariff_fraction_id: int, - catalog: str = Query("mex", description="Catalog source: 'mex', 'usa', or 'american'"), - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - if catalog == "mex": - validate_access_to_resource(db, company_id, current_user, ["frac_sitar.view"]) - elif catalog == "usa": - validate_access_to_resource(db, company_id, current_user, ["frac_sitar_us.view"]) - elif catalog == "american": - validate_access_to_resource(db, company_id, current_user, ["frac_american.view"]) - else: - raise HTTPException(status_code=400, detail=f"Unsupported catalog '{catalog}'") - item = TariffFractionService.get_by_id(db, tariff_fraction_id) - if not item: - from fastapi import HTTPException - raise HTTPException(status_code=404, detail="Tariff fraction not found") - return TariffFractionResponseDTO.model_validate(item) - - -@router.post( - "/", - response_model=TariffFractionResponseDTO, - summary="Create Tariff Fraction", - description="Not supported: all catalogs are read-only (SITAR-backed for US).", -) -async def create_tariff_fraction( - fraction_data: TariffFractionCreateDTO, - catalog: str = Query("mex", description="Catalog source: 'mex', 'usa', or 'american'"), - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """ - Crea una nueva fracción. - - MEX/USA: No permitido (Read-Only) - - AMERICAN: Permitido (Local DB) - """ - from fastapi import HTTPException - - tenant_id = current_user.get("tenant_id") - - if catalog == "american": - validate_access_to_resource(db, company_id, current_user, ["frac_american.create"]) - from api.v1.modules.a76.general_catalogs.fractions.us_tariff_fractions.service import USTariffFractionService - from api.v1.modules.a76.general_catalogs.fractions.us_tariff_fractions.dto import USTariffFractionCreateDTO - import re - - # Map generic DTO to US DTO - ad_valorem = None - if fraction_data.adv_impo: - try: - # remove non-numeric chars except dot - clean = re.sub(r'[^\d.]', '', fraction_data.adv_impo) - if clean: - ad_valorem = float(clean) - except: - pass - - us_dto = USTariffFractionCreateDTO( - code=fraction_data.fraction, # Store the punctuated fraction in the DB - description=fraction_data.description, - unit_of_measure=fraction_data.umt, - ad_valorem=ad_valorem, - # Defaults for others - prefix=None, - type_code=None, - fixed_cost=None - ) - - created = USTariffFractionService.create(db, us_dto, tenant_id, company_id) - return TariffFractionService.to_domain_usa_local(created) - - else: - raise HTTPException(status_code=400, detail=f"Creation not allowed for '{catalog}' catalog (Read-Only)") - raise HTTPException( - status_code=status.HTTP_405_METHOD_NOT_ALLOWED, - detail=f"Creation not allowed for '{catalog}' catalog (read-only; US data from SITAR).", - ) - - -@router.put( - "/{tariff_fraction_id}", - response_model=TariffFractionResponseDTO, - summary="Update Tariff Fraction", - description="Not supported: all catalogs are read-only (SITAR-backed for US).", -) -async def update_tariff_fraction( - tariff_fraction_id: int, - fraction_data: TariffFractionUpdateDTO, - catalog: str = Query("mex", description="Catalog source: 'mex', 'usa', or 'american'"), - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - from fastapi import HTTPException - - tenant_id = current_user.get("tenant_id") - - if catalog == "american": - validate_access_to_resource(db, company_id, current_user, ["frac_american.edit"]) - from api.v1.modules.a76.general_catalogs.fractions.us_tariff_fractions.service import USTariffFractionService - from api.v1.modules.a76.general_catalogs.fractions.us_tariff_fractions.dto import USTariffFractionUpdateDTO - import re - - ad_valorem = None - if fraction_data.adv_impo: - try: - clean = re.sub(r'[^\d.]', '', fraction_data.adv_impo) - if clean: - ad_valorem = float(clean) - except: - pass - - us_dto = USTariffFractionUpdateDTO( - code=fraction_data.fraction, - description=fraction_data.description, - unit_of_measure=fraction_data.umt, - ad_valorem=ad_valorem - ) - - updated = USTariffFractionService.update(db, tariff_fraction_id, tenant_id, us_dto, company_id) - if not updated: - raise HTTPException(status_code=404, detail="US Tariff fraction not found") - return TariffFractionService.to_domain_usa_local(updated) - - else: - raise HTTPException(status_code=400, detail=f"Update not allowed for '{catalog}' catalog (Read-Only)") - raise HTTPException( - status_code=status.HTTP_405_METHOD_NOT_ALLOWED, - detail=f"Update not allowed for '{catalog}' catalog (read-only; US data from SITAR).", - ) - - -@router.delete( - "/{tariff_fraction_id}", - summary="Delete Tariff Fraction", - description="Not supported: all catalogs are read-only (SITAR-backed for US).", -) -async def delete_tariff_fraction( - tariff_fraction_id: int, - catalog: str = Query("mex", description="Catalog source: 'mex', 'usa', or 'american'"), - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - from fastapi import HTTPException - tenant_id = current_user.get("tenant_id") - - if catalog == "american": - validate_access_to_resource(db, company_id, current_user, ["frac_american.delete"]) - from api.v1.modules.a76.general_catalogs.fractions.us_tariff_fractions.service import USTariffFractionService - success = USTariffFractionService.delete(db, tariff_fraction_id, tenant_id, company_id) - if not success: - raise HTTPException(status_code=404, detail="US Tariff fraction not found") - return {"ok": True} - - else: - raise HTTPException(status_code=400, detail=f"Delete not allowed for '{catalog}' catalog (Read-Only)") - raise HTTPException( - status_code=status.HTTP_405_METHOD_NOT_ALLOWED, - detail=f"Delete not allowed for '{catalog}' catalog (read-only; US data from SITAR).", - ) - diff --git a/backend/api/v1/modules/a76/general_catalogs/fractions/tariff_fractions/seed.py b/backend/api/v1/modules/a76/general_catalogs/fractions/tariff_fractions/seed.py deleted file mode 100644 index 87bc79dc..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/fractions/tariff_fractions/seed.py +++ /dev/null @@ -1,8189 +0,0 @@ -""" -Datos semilla para Fracciones Arancelarias Mexicanas - -Catálogo oficial de fracciones arancelarias del SAT. -Total: 8178 registros - -Estructura: (code, fraction, description, nico, umt, adv_impo, adv_expo) -""" - -seed = [ - ('01012101', '0101.21.01', 'Reproductores de raza pura.', '7', 'Cbza', '10.00', '0.00') - ,('01012902', '0101.29.02', 'Sin pedigree, para reproducción.', '6', 'Pza', '10.00', '0.00') - ,('01012903', '0101.29.03', 'Para abasto, cuando la importación la realicen empacadoras Tipo Inspección Federal.', '6', 'Pza', '10.00', '0.00') - ,('01012999', '0101.29.99', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('01013001', '0101.30.01', 'Asnos.', '7', 'Cbza', '20.00', '0.00') - ,('01019099', '0101.90.99', 'Los demás.', '7', 'Cbza', '20.00', '0.00') - ,('01022101', '0102.21.01', 'Reproductores de raza pura.', '7', 'Cbza', '0.00', '0.00') - ,('01022901', '0102.29.01', 'Vacas lecheras.', '6', 'Pza', '0.00', '0.00') - ,('01022902', '0102.29.02', 'Con pedigree o certificado de alto registro, excepto lo comprendido en la fracción arancelaria 0102.29.01.', '6', 'Pza', '0.00', '0.00') - ,('01022999', '0102.29.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('01023101', '0102.31.01', 'Reproductores de raza pura.', '7', 'Cbza', '0.00', '0.00') - ,('01023999', '0102.39.99', 'Los demás.', '7', 'Cbza', '15.00', '0.00') - ,('01029099', '0102.90.99', 'Los demás.', '7', 'Cbza', '15.00', '0.00') - ,('01031001', '0103.10.01', 'Reproductores de raza pura.', '7', 'Cbza', '0.00', '0.00') - ,('01039101', '0103.91.01', 'Con pedigree o certificado de alto registro.', '7', 'Cbza', '9.00', '0.00') - ,('01039199', '0103.91.99', 'Los demás.', '7', 'Cbza', '0.00', '0.00') - ,('01039201', '0103.92.01', 'Con pedigree o certificado de alto registro.', '7', 'Cbza', '9.00', '0.00') - ,('01039299', '0103.92.99', 'Los demás.', '7', 'Cbza', '0.00', '0.00') - ,('01041001', '0104.10.01', 'Con pedigree o certificado de alto registro.', '6', 'Pza', '0.00', '0.00') - ,('01041099', '0104.10.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('01042001', '0104.20.01', 'Con pedigree o certificado de alto registro.', '7', 'Cbza', '0.00', '0.00') - ,('01042002', '0104.20.02', 'Borrego cimarrón.', '7', 'Cbza', '20.00', '0.00') - ,('01042099', '0104.20.99', 'Los demás.', '7', 'Cbza', '0.00', '0.00') - ,('01051103', '0105.11.03', 'Las demás de engorda.', '6', 'Pza', '0.00', '0.00') - ,('01051199', '0105.11.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('01051201', '0105.12.01', 'Pavos (gallipavos).', '6', 'Pza', '10.00', '0.00') - ,('01051301', '0105.13.01', 'Patos.', '7', 'Cbza', '10.00', '0.00') - ,('01051401', '0105.14.01', 'Gansos.', '7', 'Cbza', '10.00', '0.00') - ,('01051501', '0105.15.01', 'Pintadas.', '7', 'Cbza', '10.00', '0.00') - ,('01059401', '0105.94.01', 'Gallos de pelea.', '7', 'Cbza', '20.00', '0.00') - ,('01059499', '0105.94.99', 'Los demás.', '7', 'Cbza', '10.00', '0.00') - ,('01059999', '0105.99.99', 'Los demás.', '7', 'Cbza', '10.00', '0.00') - ,('01061101', '0106.11.01', 'Monos (simios) de las variedades Macacus rhesus o Macacus cercophitecus.', '7', 'Cbza', '10.00', '0.00') - ,('01061199', '0106.11.99', 'Los demás.', '7', 'Cbza', '20.00', '0.00') - ,('01061201', '0106.12.01', 'Ballenas, delfines y marsopas (mamíferos del orden Cetacea); manatíes y dugones o dugongos (mamíferos del orden Sirenia); otarios y focas, leones marinos y morsas (mamíferos del suborden Pinnipedia).', '7', 'Cbza', '20.00', '0.00') - ,('01061301', '0106.13.01', 'Camellos y demás camélidos (Camelidae).', '7', 'Cbza', '20.00', '0.00') - ,('01061401', '0106.14.01', 'Conejos y liebres.', '7', 'Cbza', '20.00', '0.00') - ,('01061902', '0106.19.02', 'Venado rojo (Cervus elaphus); gamo (Dama dama).', '7', 'Cbza', '0.00', '0.00') - ,('01061999', '0106.19.99', 'Los demás.', '7', 'Cbza', '20.00', '0.00') - ,('01062004', '0106.20.04', 'Reptiles (incluidas las serpientes y tortugas de mar).', '7', 'Cbza', '20.00', '0.00') - ,('01063101', '0106.31.01', 'Aves de rapiña.', '6', 'Pza', '20.00', '0.00') - ,('01063201', '0106.32.01', 'Psitaciformes (incluidos los loros, guacamayos, cacatúas y demás papagayos).', '7', 'Cbza', '20.00', '0.00') - ,('01063301', '0106.33.01', 'Avestruces; emúes (Dromaius novaehollandiae).', '7', 'Cbza', '20.00', '0.00') - ,('01063999', '0106.39.99', 'Las demás.', '7', 'Cbza', '20.00', '0.00') - ,('01064101', '0106.41.01', 'Abejas.', '1', 'Kg', '10.00', '0.00') - ,('01064999', '0106.49.99', 'Los demás.', '1', 'Kg', '20.00', '0.00') - ,('01069002', '0106.90.02', 'Lombriz Rebellus (Lumbricus rubellus).', '1', 'Kg', '10.00', '0.00') - ,('01069003', '0106.90.03', 'Lombriz acuática.', '1', 'Kg', '0.00', '0.00') - ,('01069004', '0106.90.04', 'Ácaros Phytoseiulus persimilis.', '1', 'Kg', '0.00', '0.00') - ,('01069099', '0106.90.99', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('02011001', '0201.10.01', 'En canales o medias canales.', '1', 'Kg', '20.00', '0.00') - ,('02012091', '0201.20.91', 'Los demás cortes (trozos) sin deshuesar.', '1', 'Kg', '20.00', '0.00') - ,('02013001', '0201.30.01', 'Deshuesada.', '1', 'Kg', '20.00', '0.00') - ,('02021001', '0202.10.01', 'En canales o medias canales.', '1', 'Kg', '25.00', '0.00') - ,('02022091', '0202.20.91', 'Los demás cortes (trozos) sin deshuesar.', '1', 'Kg', '25.00', '0.00') - ,('02023001', '0202.30.01', 'Deshuesada.', '1', 'Kg', '25.00', '0.00') - ,('02031101', '0203.11.01', 'En canales o medias canales.', '1', 'Kg', '20.00', '0.00') - ,('02031201', '0203.12.01', 'Piernas y sus trozos.', '1', 'Kg', '20.00', '0.00') - ,('02031999', '0203.19.99', 'Las demás.', '1', 'Kg', '20.00', '0.00') - ,('02032101', '0203.21.01', 'En canales o medias canales.', '1', 'Kg', '20.00', '0.00') - ,('02032201', '0203.22.01', 'Piernas y sus trozos.', '1', 'Kg', '20.00', '0.00') - ,('02032999', '0203.29.99', 'Las demás.', '1', 'Kg', '20.00', '0.00') - ,('02041001', '0204.10.01', 'Canales o medias canales de cordero, frescas o refrigeradas.', '1', 'Kg', '10.00', '0.00') - ,('02042101', '0204.21.01', 'En canales o medias canales.', '1', 'Kg', '10.00', '0.00') - ,('02042291', '0204.22.91', 'Los demás cortes (trozos) sin deshuesar.', '1', 'Kg', '10.00', '0.00') - ,('02042301', '0204.23.01', 'Deshuesadas.', '1', 'Kg', '10.00', '0.00') - ,('02043001', '0204.30.01', 'Canales o medias canales de cordero, congeladas.', '1', 'Kg', '10.00', '0.00') - ,('02044101', '0204.41.01', 'En canales o medias canales.', '1', 'Kg', '10.00', '0.00') - ,('02044291', '0204.42.91', 'Los demás cortes (trozos) sin deshuesar.', '1', 'Kg', '10.00', '0.00') - ,('02044301', '0204.43.01', 'Deshuesadas.', '1', 'Kg', '10.00', '0.00') - ,('02045001', '0204.50.01', 'Carne de animales de la especie caprina.', '1', 'Kg', '10.00', '0.00') - ,('02050001', '0205.00.01', 'Carne de animales de las especies caballar, asnal o mular, fresca, refrigerada o congelada.', '1', 'Kg', '10.00', '0.00') - ,('02061001', '0206.10.01', 'De la especie bovina, frescos o refrigerados.', '1', 'Kg', '20.00', '0.00') - ,('02062101', '0206.21.01', 'Lenguas.', '1', 'Kg', '20.00', '0.00') - ,('02062201', '0206.22.01', 'Hígados.', '1', 'Kg', '20.00', '0.00') - ,('02062999', '0206.29.99', 'Los demás.', '1', 'Kg', '20.00', '0.00') - ,('02063001', '0206.30.01', 'Pieles de cerdo enteras o en recortes, refrigerados, excepto el cuero precocido en trozos ("pellets").', '1', 'Kg', '10.00', '0.00') - ,('02063099', '0206.30.99', 'Los demás.', '1', 'Kg', '20.00', '0.00') - ,('02064101', '0206.41.01', 'Hígados.', '1', 'Kg', '10.00', '0.00') - ,('02064901', '0206.49.01', 'Pieles de cerdo enteras o en recortes, excepto el cuero precocido en trozos ("pellets").', '1', 'Kg', '0.00', '0.00') - ,('02064999', '0206.49.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('02068091', '0206.80.91', 'Los demás, frescos o refrigerados.', '1', 'Kg', '10.00', '0.00') - ,('02069091', '0206.90.91', 'Los demás, congelados.', '1', 'Kg', '10.00', '0.00') - ,('02071101', '0207.11.01', 'Sin trocear, frescos o refrigerados.', '1', 'Kg', '75.00', '0.00') - ,('02071201', '0207.12.01', 'Sin trocear, congelados.', '1', 'Kg', '75.00', '0.00') - ,('02071304', '0207.13.04', 'Los demás.', '1', 'Kg', '75.00', '0.00') - ,('02071402', '0207.14.02', 'Hígados.', '1', 'Kg', '10.00', '0.00') - ,('02071499', '0207.14.99', 'Los demás.', '1', 'Kg', '75.00', '0.00') - ,('02072401', '0207.24.01', 'Sin trocear, frescos o refrigerados.', '1', 'Kg', '45.00', '0.00') - ,('02072501', '0207.25.01', 'Sin trocear, congelados.', '1', 'Kg', '45.00', '0.00') - ,('02072603', '0207.26.03', 'Los demás.', '1', 'Kg', '75.00', '0.00') - ,('02072702', '0207.27.02', 'Hígados.', '1', 'Kg', '10.00', '0.00') - ,('02072799', '0207.27.99', 'Los demás.', '1', 'Kg', '75.00', '0.00') - ,('02074101', '0207.41.01', 'Sin trocear, frescos o refrigerados.', '1', 'Kg', '0.00', '0.00') - ,('02074201', '0207.42.01', 'Sin trocear, congelados.', '1', 'Kg', '0.00', '0.00') - ,('02074301', '0207.43.01', 'Hígados grasos, frescos o refrigerados.', '1', 'Kg', '10.00', '0.00') - ,('02074491', '0207.44.91', 'Los demás, frescos o refrigerados.', '1', 'Kg', '0.00', '0.00') - ,('02074501', '0207.45.01', 'Hígados.', '1', 'Kg', '10.00', '0.00') - ,('02074599', '0207.45.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('02075101', '0207.51.01', 'Sin trocear, frescos o refrigerados.', '1', 'Kg', '0.00', '0.00') - ,('02075201', '0207.52.01', 'Sin trocear, congelados.', '1', 'Kg', '0.00', '0.00') - ,('02075301', '0207.53.01', 'Hígados grasos, frescos o refrigerados.', '1', 'Kg', '10.00', '0.00') - ,('02075491', '0207.54.91', 'Los demás, frescos o refrigerados.', '1', 'Kg', '0.00', '0.00') - ,('02075501', '0207.55.01', 'Hígados.', '1', 'Kg', '10.00', '0.00') - ,('02075599', '0207.55.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('02076003', '0207.60.03', 'De pintada.', '1', 'Kg', '0.00', '0.00') - ,('02081001', '0208.10.01', 'De conejo o liebre.', '1', 'Kg', '10.00', '0.00') - ,('02083001', '0208.30.01', 'De primates.', '1', 'Kg', '10.00', '0.00') - ,('02084001', '0208.40.01', 'De ballenas, delfines y marsopas (mamíferos del orden Cetacea); de manatíes y dugones o dugongos (mamíferos del orden Sirenia); de otarios y focas, leones marinos y morsas (mamíferos del suborden Pinnipedia).', '1', 'Kg', '10.00', '0.00') - ,('02085001', '0208.50.01', 'De reptiles (incluidas las serpientes y tortugas de mar).', '1', 'Kg', '10.00', '0.00') - ,('02086001', '0208.60.01', 'De camellos y demás camélidos (Camelidae).', '1', 'Kg', '10.00', '0.00') - ,('02089099', '0208.90.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('02091001', '0209.10.01', 'De cerdo.', '1', 'Kg', '15.00', '0.00') - ,('02099001', '0209.90.01', 'De gallo, gallina o pavo (gallipavo).', '1', 'Kg', '0.00', '0.00') - ,('02099099', '0209.90.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('02101101', '0210.11.01', 'Jamones, paletas, y sus trozos, sin deshuesar.', '1', 'Kg', '10.00', '0.00') - ,('02101201', '0210.12.01', 'Tocino entreverado de panza (panceta) y sus trozos.', '1', 'Kg', '10.00', '0.00') - ,('02101999', '0210.19.99', 'Las demás.', '1', 'Kg', '10.00', '0.00') - ,('02102001', '0210.20.01', 'Carne de la especie bovina.', '1', 'Kg', '10.00', '0.00') - ,('02109101', '0210.91.01', 'De primates.', '1', 'Kg', '10.00', '0.00') - ,('02109201', '0210.92.01', 'De ballenas, delfines y marsopas (mamíferos del orden Cetacea); de manatíes y dugones o dugongos (mamíferos del orden Sirenia); de otarios y focas, leones marinos y morsas (mamíferos del suborden Pinnipedia).', '1', 'Kg', '10.00', '0.00') - ,('02109301', '0210.93.01', 'De reptiles (incluidas las serpientes y tortugas de mar).', '1', 'Kg', '10.00', '0.00') - ,('02109902', '0210.99.02', 'Pieles de cerdo ahumadas, enteras o en recortes.', '1', 'Kg', '15.00', '0.00') - ,('02109999', '0210.99.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('03011101', '0301.11.01', 'De agua dulce.', '7', 'Cbza', '10.00', '0.00') - ,('03011999', '0301.19.99', 'Los demás.', '7', 'Cbza', '10.00', '0.00') - ,('03019101', '0301.91.01', 'Truchas (Salmo trutta, Oncorhynchus mykiss, Oncorhynchus clarki, Oncorhynchus aguabonita, Oncorhynchus gilae, Oncorhynchus apache y Oncorhynchus chrysogaster).', '7', 'Cbza', '15.00', '0.00') - ,('03019201', '0301.92.01', 'Anguilas (Anguilla spp.).', '7', 'Cbza', '15.00', '0.00') - ,('03019301', '0301.93.01', 'Carpas (Cyprinus spp., Carassius spp., Ctenopharyngodon idellus, Hypophthalmichthys spp., Cirrhinus spp., Mylopharyngodon piceus, Catla catla, Labeo spp., Osteochilus hasselti, Leptobarbus hoeveni, Megalobrama spp.).', '7', 'Cbza', '15.00', '0.00') - ,('03019401', '0301.94.01', 'Atunes comunes o de aleta azul, del Atlántico y del Pacífico (Thunnus thynnus, Thunnus orientalis).', '7', 'Cbza', '0.00', '0.00') - ,('03019501', '0301.95.01', 'Atunes del sur (Thunnus maccoyii).', '7', 'Cbza', '0.00', '0.00') - ,('03019901', '0301.99.01', 'Depredadores, en sus estados de alevines, juveniles y adultos.', '22', 'PROH', '0.00', '0.00') - ,('03019999', '0301.99.99', 'Los demás.', '7', 'Cbza', '15.00', '0.00') - ,('03021101', '0302.11.01', 'Truchas (Salmo trutta, Oncorhynchus mykiss, Oncorhynchus clarki, Oncorhynchus aguabonita, Oncorhynchus gilae, Oncorhynchus apache y Oncorhynchus chrysogaster).', '1', 'Kg', '15.00', '0.00') - ,('03021301', '0302.13.01', 'Salmones del Pacífico (Oncorhynchus nerka, Oncorhynchus gorbuscha, Oncorhynchus keta, Oncorhynchus tschawytscha, Oncorhynchus kisutch, Oncorhynchus masou y Oncorhynchus rhodurus).', '1', 'Kg', '15.00', '0.00') - ,('03021401', '0302.14.01', 'Salmones del Atlántico (Salmo salar) y salmones del Danubio (Hucho hucho).', '1', 'Kg', '15.00', '0.00') - ,('03021999', '0302.19.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('03022101', '0302.21.01', 'Fletanes (halibut) (Reinhardtius hippoglossoides, Hippoglossus hippoglossus, Hippoglossus stenolepis).', '1', 'Kg', '15.00', '0.00') - ,('03022201', '0302.22.01', 'Sollas (Pleuronectes platessa).', '1', 'Kg', '15.00', '0.00') - ,('03022301', '0302.23.01', 'Lenguados (Solea spp.).', '1', 'Kg', '15.00', '0.00') - ,('03022401', '0302.24.01', 'Rodaballos (turbots) (Psetta maxima).', '1', 'Kg', '15.00', '0.00') - ,('03022999', '0302.29.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('03023101', '0302.31.01', 'Albacoras o atunes blancos (Thunnus alalunga).', '1', 'Kg', '0.00', '0.00') - ,('03023201', '0302.32.01', 'Atunes de aleta amarilla (rabiles) (Thunnus albacares).', '1', 'Kg', '0.00', '0.00') - ,('03023301', '0302.33.01', 'Listados (bonitos de vientre rayado) (Katsuwonus pelamis).', '1', 'Kg', '0.00', '0.00') - ,('03023401', '0302.34.01', 'Patudos o atunes ojo grande (Thunnus obesus).', '1', 'Kg', '0.00', '0.00') - ,('03023501', '0302.35.01', 'Atunes comunes o de aleta azul, del Atlántico y del Pacífico (Thunnus thynnus, Thunnus orientalis).', '1', 'Kg', '0.00', '0.00') - ,('03023601', '0302.36.01', 'Atunes del sur (Thunnus maccoyii).', '1', 'Kg', '0.00', '0.00') - ,('03023999', '0302.39.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('03024101', '0302.41.01', 'Arenques (Clupea harengus, Clupea pallasii).', '1', 'Kg', '15.00', '0.00') - ,('03024201', '0302.42.01', 'Anchoas (Engraulis spp.).', '1', 'Kg', '15.00', '0.00') - ,('03024301', '0302.43.01', 'Sardinas (Sardina pilchardus, Sardinops spp.), sardinelas (Sardinella spp.) y espadines (Sprattus sprattus).', '1', 'Kg', '15.00', '0.00') - ,('03024401', '0302.44.01', 'Caballas (Scomber scombrus, Scomber australasicus, Scomber japonicus).', '1', 'Kg', '15.00', '0.00') - ,('03024501', '0302.45.01', 'Jureles (Trachurus spp.).', '1', 'Kg', '15.00', '0.00') - ,('03024601', '0302.46.01', 'Cobias (Rachycentron canadum).', '1', 'Kg', '15.00', '0.00') - ,('03024701', '0302.47.01', 'Peces espada (Xiphias gladius).', '1', 'Kg', '15.00', '0.00') - ,('03024999', '0302.49.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('03025101', '0302.51.01', 'Bacalaos (Gadus morhua, Gadus ogac, Gadus macrocephalus).', '1', 'Kg', '15.00', '0.00') - ,('03025201', '0302.52.01', 'Eglefinos (Melanogrammus aeglefinus).', '1', 'Kg', '15.00', '0.00') - ,('03025301', '0302.53.01', 'Carboneros (Pollachius virens).', '1', 'Kg', '15.00', '0.00') - ,('03025401', '0302.54.01', 'Merluzas (Merluccius spp., Urophycis spp.).', '1', 'Kg', '10.00', '0.00') - ,('03025501', '0302.55.01', 'Abadejos de Alaska (Theragra chalcogramma).', '1', 'Kg', '15.00', '0.00') - ,('03025601', '0302.56.01', 'Bacaladillas (Micromesistius poutassou, Micromesistius australis).', '1', 'Kg', '15.00', '0.00') - ,('03025999', '0302.59.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('03027101', '0302.71.01', 'Tilapias (Oreochromis spp.).', '1', 'Kg', '15.00', '0.00') - ,('03027201', '0302.72.01', 'Bagres o peces gato (Pangasius spp., Silurus spp., Clarias spp., Ictalurus spp.).', '1', 'Kg', '15.00', '0.00') - ,('03027301', '0302.73.01', 'Carpas (Cyprinus spp., Carassius spp., Ctenopharyngodon idellus, Hypophthalmichthys spp., Cirrhinus spp., Mylopharyngodon piceus, Catla catla, Labeo spp., Osteochilus hasselti, Leptobarbus hoeveni, Megalobrama spp.).', '1', 'Kg', '15.00', '0.00') - ,('03027401', '0302.74.01', 'Anguilas (Anguilla spp.).', '1', 'Kg', '15.00', '0.00') - ,('03027999', '0302.79.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('03028101', '0302.81.01', 'Cazones y demás escualos.', '1', 'Kg', '15.00', '0.00') - ,('03028201', '0302.82.01', 'Rayas (Rajidae).', '1', 'Kg', '15.00', '0.00') - ,('03028301', '0302.83.01', 'Austromerluzas antárticas y austromerluzas negras (merluzas negras, bacalaos de profundidad, nototenias negras) (Dissostichus spp.).', '1', 'Kg', '15.00', '0.00') - ,('03028401', '0302.84.01', 'Róbalos (Dicentrarchus spp.).', '1', 'Kg', '15.00', '0.00') - ,('03028501', '0302.85.01', 'Sargos (Doradas, Espáridos) (Sparidae).', '1', 'Kg', '15.00', '0.00') - ,('03028901', '0302.89.01', 'Totoabas.', '1', 'Kg', '15.00', '0.00') - ,('03028999', '0302.89.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('03029101', '0302.91.01', 'Hígados, huevas y lechas.', '1', 'Kg', '15.00', '0.00') - ,('03029201', '0302.92.01', 'Aletas de tiburón.', '1', 'Kg', '15.00', '0.00') - ,('03029901', '0302.99.01', 'De Atunes (del género Thunnus), listados o bonitos de vientre rayado (Euthynnus (Katsuwonus) pelamis).', '1', 'Kg', '0.00', '0.00') - ,('03029902', '0302.99.02', 'De Merluzas (Merluccius spp., Urophycis spp.).', '1', 'Kg', '10.00', '0.00') - ,('03029903', '0302.99.03', 'De Totoabas.', '1', 'Kg', '15.00', '0.00') - ,('03029999', '0302.99.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('03031101', '0303.11.01', 'Salmones rojos (Oncorhynchus nerka).', '1', 'Kg', '15.00', '0.00') - ,('03031291', '0303.12.91', 'Los demás salmones del Pacífico (Oncorhynchus gorbuscha, Oncorhynchus keta, Oncorhynchus tschawytscha, Oncorhynchus kisutch, Oncorhynchus masou y Oncorhynchus rhodurus).', '1', 'Kg', '15.00', '0.00') - ,('03031301', '0303.13.01', 'Salmones del Atlántico (Salmo salar) y salmones del Danubio (Hucho hucho).', '1', 'Kg', '15.00', '0.00') - ,('03031401', '0303.14.01', 'Truchas (Salmo trutta, Oncorhynchus mykiss, Oncorhynchus clarki, Oncorhynchus aguabonita, Oncorhynchus gilae, Oncorhynchus apache y Oncorhynchus chrysogaster).', '1', 'Kg', '15.00', '0.00') - ,('03031999', '0303.19.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('03032301', '0303.23.01', 'Tilapias (Oreochromis spp.).', '1', 'Kg', '15.00', '0.00') - ,('03032401', '0303.24.01', 'Bagres o peces gato (Pangasius spp., Silurus spp., Clarias spp., Ictalurus spp.).', '1', 'Kg', '15.00', '0.00') - ,('03032501', '0303.25.01', 'Carpas (Cyprinus spp., Carassius spp., Ctenopharyngodon idellus, Hypophthalmichthys spp., Cirrhinus spp., Mylopharyngodon piceus, Catla catla, Labeo spp., Osteochilus hasselti, Leptobarbus hoeveni, Megalobrama spp.).', '1', 'Kg', '15.00', '0.00') - ,('03032601', '0303.26.01', 'Anguilas (Anguilla spp.).', '1', 'Kg', '10.00', '0.00') - ,('03032999', '0303.29.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('03033101', '0303.31.01', 'Fletanes (halibut) (Reinhardtius hippoglossoides, Hippoglossus hippoglossus, Hippoglossus stenolepis).', '1', 'Kg', '15.00', '0.00') - ,('03033201', '0303.32.01', 'Sollas (Pleuronectes platessa).', '1', 'Kg', '15.00', '0.00') - ,('03033301', '0303.33.01', 'Lenguados (Solea spp.).', '1', 'Kg', '15.00', '0.00') - ,('03033401', '0303.34.01', 'Rodaballos (turbots) (Psetta maxima).', '1', 'Kg', '15.00', '0.00') - ,('03033999', '0303.39.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('03034101', '0303.41.01', 'Albacoras o atunes blancos (Thunnus alalunga).', '1', 'Kg', '0.00', '0.00') - ,('03034201', '0303.42.01', 'Atunes de aleta amarilla (rabiles) (Thunnus albacares).', '1', 'Kg', '0.00', '0.00') - ,('03034301', '0303.43.01', 'Listados (bonitos de vientre rayado) (Katsuwonus pelamis).', '1', 'Kg', '0.00', '0.00') - ,('03034401', '0303.44.01', 'Patudos o atunes ojo grande (Thunnus obesus).', '1', 'Kg', '0.00', '0.00') - ,('03034501', '0303.45.01', 'Atunes comunes o de aleta azul, del Atlántico y del Pacífico (Thunnus thynnus, Thunnus orientalis).', '1', 'Kg', '0.00', '0.00') - ,('03034601', '0303.46.01', 'Atunes del sur (Thunnus maccoyii).', '1', 'Kg', '0.00', '0.00') - ,('03034999', '0303.49.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('03035101', '0303.51.01', 'Arenques (Clupea harengus, Clupea pallasii).', '1', 'Kg', '15.00', '0.00') - ,('03035301', '0303.53.01', 'Sardinas (Sardina pilchardus, Sardinops spp.), sardinelas (Sardinella spp.) y espadines (Sprattus sprattus).', '1', 'Kg', '15.00', '0.00') - ,('03035401', '0303.54.01', 'Caballas (Scomber scombrus, Scomber australasicus, Scomber japonicus).', '1', 'Kg', '15.00', '0.00') - ,('03035501', '0303.55.01', 'Jureles (Trachurus spp.).', '1', 'Kg', '15.00', '0.00') - ,('03035601', '0303.56.01', 'Cobias (Rachycentron canadum).', '1', 'Kg', '15.00', '0.00') - ,('03035701', '0303.57.01', 'Peces espada (Xiphias gladius).', '1', 'Kg', '15.00', '0.00') - ,('03035999', '0303.59.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('03036301', '0303.63.01', 'Bacalaos (Gadus morhua, Gadus ogac, Gadus macrocephalus).', '1', 'Kg', '15.00', '0.00') - ,('03036401', '0303.64.01', 'Eglefinos (Melanogrammus aeglefinus).', '1', 'Kg', '15.00', '0.00') - ,('03036501', '0303.65.01', 'Carboneros (Pollachius virens).', '1', 'Kg', '15.00', '0.00') - ,('03036601', '0303.66.01', 'Merluzas (Merluccius spp., Urophycis spp.).', '1', 'Kg', '10.00', '0.00') - ,('03036701', '0303.67.01', 'Abadejos de Alaska (Theragra chalcogramma).', '1', 'Kg', '15.00', '0.00') - ,('03036801', '0303.68.01', 'Bacaladillas (Micromesistius poutassou, Micromesistius australis).', '1', 'Kg', '15.00', '0.00') - ,('03036999', '0303.69.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('03038101', '0303.81.01', 'Cazones y demás escualos.', '1', 'Kg', '15.00', '0.00') - ,('03038201', '0303.82.01', 'Rayas (Rajidae).', '1', 'Kg', '15.00', '0.00') - ,('03038301', '0303.83.01', 'Austromerluzas antárticas y austromerluzas negras (merluzas negras, bacalaos de profundidad, nototenias negras) (Dissostichus spp.).', '1', 'Kg', '15.00', '0.00') - ,('03038401', '0303.84.01', 'Róbalos (Dicentrarchus spp.).', '1', 'Kg', '15.00', '0.00') - ,('03038901', '0303.89.01', 'Totoabas.', '1', 'Kg', '15.00', '0.00') - ,('03038999', '0303.89.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('03039101', '0303.91.01', 'Hígados, huevas y lechas.', '1', 'Kg', '15.00', '0.00') - ,('03039201', '0303.92.01', 'Aletas de tiburón.', '1', 'Kg', '15.00', '0.00') - ,('03039901', '0303.99.01', 'De Atunes (del género Thunnus), listados o bonitos de vientre rayado (Euthynnus (Katsuwonus) pelamis).', '1', 'Kg', '0.00', '0.00') - ,('03039902', '0303.99.02', 'De Merluzas (Merluccius spp., Urophycis spp.) y de Anguilas.', '1', 'Kg', '10.00', '0.00') - ,('03039903', '0303.99.03', 'De Totoabas.', '1', 'Kg', '15.00', '0.00') - ,('03039999', '0303.99.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('03043101', '0304.31.01', 'Tilapias (Oreochromis spp.).', '1', 'Kg', '15.00', '0.00') - ,('03043201', '0304.32.01', 'Bagres o peces gato (Pangasius spp., Silurus spp., Clarias spp., Ictalurus spp.).', '1', 'Kg', '15.00', '0.00') - ,('03043301', '0304.33.01', 'Percas del Nilo (Lates niloticus).', '1', 'Kg', '15.00', '0.00') - ,('03043999', '0304.39.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('03044101', '0304.41.01', 'Salmones del Pacífico (Oncorhynchus nerka, Oncorhynchus gorbuscha, Oncorhynchus keta, Oncorhynchus tschawytscha, Oncorhynchus kisutch, Oncorhynchus masou y Oncorhynchus rhodurus), salmones del Atlántico (Salmo salar) y salmones del Danubio (Hucho hucho).', '1', 'Kg', '15.00', '0.00') - ,('03044201', '0304.42.01', 'Truchas (Salmo trutta, Oncorhynchus mykiss, Oncorhynchus clarki, Oncorhynchus aguabonita, Oncorhynchus gilae, Oncorhynchus apache y Oncorhynchus chrysogaster).', '1', 'Kg', '15.00', '0.00') - ,('03044301', '0304.43.01', 'Pescados planos (Pleuronectidae, Bothidae, Cynoglossidae, Soleidae, Scophthalmidae y Citharidae).', '1', 'Kg', '15.00', '0.00') - ,('03044401', '0304.44.01', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('03044501', '0304.45.01', 'Peces espada (Xiphias gladius).', '1', 'Kg', '15.00', '0.00') - ,('03044601', '0304.46.01', 'Austromerluzas antárticas y austromerluzas negras (merluzas negras, bacalaos de profundidad, nototenias negras) (Dissostichus spp.).', '1', 'Kg', '15.00', '0.00') - ,('03044701', '0304.47.01', 'Cazones y demás escualos.', '1', 'Kg', '15.00', '0.00') - ,('03044801', '0304.48.01', 'Rayas (Rajidae).', '1', 'Kg', '15.00', '0.00') - ,('03044999', '0304.49.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('03045101', '0304.51.01', 'Tilapias (Oreochromis spp.), bagres o peces gato (Pangasius spp., Silurus spp., Clarias spp., Ictalurus spp.), carpas (Cyprinus spp., Carassius spp., Ctenopharyngodon idellus, Hypophthalmichthys spp., Cirrhinus spp., Mylopharyngodon piceus, Catla catla, Labeo spp., Osteochilus hasselti, Leptobarbus hoeveni, Megalobrama spp.), anguilas (Anguilla spp.), percas del Nilo (Lates niloticus) y peces cabeza de serpiente (Channa spp.).', '1', 'Kg', '15.00', '0.00') - ,('03045201', '0304.52.01', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('03045301', '0304.53.01', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('03045401', '0304.54.01', 'Peces espada (Xiphias gladius).', '1', 'Kg', '15.00', '0.00') - ,('03045501', '0304.55.01', 'Austromerluzas antárticas y austromerluzas negras (merluzas negras, bacalaos de profundidad, nototenias negras) (Dissostichus spp.).', '1', 'Kg', '15.00', '0.00') - ,('03045601', '0304.56.01', 'Cazones y demás escualos.', '1', 'Kg', '15.00', '0.00') - ,('03045701', '0304.57.01', 'Rayas (Rajidae).', '1', 'Kg', '15.00', '0.00') - ,('03045999', '0304.59.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('03046101', '0304.61.01', 'Tilapias (Oreochromis spp.).', '1', 'Kg', '15.00', '0.00') - ,('03046201', '0304.62.01', 'Bagres o peces gato (Pangasius spp., Silurus spp., Clarias spp., Ictalurus spp.).', '1', 'Kg', '15.00', '0.00') - ,('03046301', '0304.63.01', 'Percas del Nilo (Lates niloticus).', '1', 'Kg', '15.00', '0.00') - ,('03046999', '0304.69.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('03047101', '0304.71.01', 'Bacalaos (Gadus morhua, Gadus ogac, Gadus macrocephalus).', '1', 'Kg', '15.00', '0.00') - ,('03047201', '0304.72.01', 'Eglefinos (Melanogrammus aeglefinus).', '1', 'Kg', '15.00', '0.00') - ,('03047301', '0304.73.01', 'Carboneros (Pollachius virens).', '1', 'Kg', '15.00', '0.00') - ,('03047401', '0304.74.01', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('03047501', '0304.75.01', 'Abadejos de Alaska (Theragra chalcogramma).', '1', 'Kg', '15.00', '0.00') - ,('03047999', '0304.79.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('03048101', '0304.81.01', 'Salmones del Pacífico (Oncorhynchus nerka, Oncorhynchus gorbuscha, Oncorhynchus keta, Oncorhynchus tschawytscha, Oncorhynchus kisutch, Oncorhynchus masou y Oncorhynchus rhodurus), salmones del Atlántico (Salmo salar) y salmones del Danubio (Hucho hucho).', '1', 'Kg', '15.00', '0.00') - ,('03048201', '0304.82.01', 'Truchas (Salmo trutta, Oncorhynchus mykiss, Oncorhynchus clarki, Oncorhynchus aguabonita, Oncorhynchus gilae, Oncorhynchus apache y Oncorhynchus chrysogaster).', '1', 'Kg', '15.00', '0.00') - ,('03048301', '0304.83.01', 'Pescados planos (Pleuronectidae, Bothidae, Cynoglossidae, Soleidae, Scophthalmidae y Citharidae).', '1', 'Kg', '15.00', '0.00') - ,('03048401', '0304.84.01', 'Peces espada (Xiphias gladius).', '1', 'Kg', '15.00', '0.00') - ,('03048501', '0304.85.01', 'Austromerluzas antárticas y austromerluzas negras (merluzas negras, bacalaos de profundidad, nototenias negras) (Dissostichus spp.).', '1', 'Kg', '15.00', '0.00') - ,('03048601', '0304.86.01', 'Arenques (Clupea harengus, Clupea pallasii).', '1', 'Kg', '15.00', '0.00') - ,('03048701', '0304.87.01', 'Atunes (del género Thunnus), listados (bonitos de vientre rayado) (Katsuwonus pelamis).', '1', 'Kg', '0.00', '0.00') - ,('03048801', '0304.88.01', 'Cazones, demás escualos y rayas (Rajidae).', '1', 'Kg', '15.00', '0.00') - ,('03048999', '0304.89.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('03049101', '0304.91.01', 'Peces espada (Xiphias gladius).', '1', 'Kg', '15.00', '0.00') - ,('03049201', '0304.92.01', 'Austromerluzas antárticas y austromerluzas negras (merluzas negras, bacalaos de profundidad, nototenias negras) (Dissostichus spp.).', '1', 'Kg', '15.00', '0.00') - ,('03049301', '0304.93.01', 'Tilapias (Oreochromis spp.), bagres o peces gato (Pangasius spp., Silurus spp., Clarias spp., Ictalurus spp.), carpas (Cyprinus spp., Carassius spp., Ctenopharyngodon idellus, Hypophthalmichthys spp., Cirrhinus spp., Mylopharyngodon piceus, Catla catla, Labeo spp., Osteochilus hasselti, Leptobarbus hoeveni, Megalobrama spp.), anguilas (Anguilla spp.), percas del Nilo (Lates niloticus) y peces cabeza de serpiente (Channa spp.).', '1', 'Kg', '15.00', '0.00') - ,('03049401', '0304.94.01', 'Abadejos de Alaska (Theragra chalcogramma).', '1', 'Kg', '15.00', '0.00') - ,('03049501', '0304.95.01', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('03049601', '0304.96.01', 'Cazones y demás escualos.', '1', 'Kg', '15.00', '0.00') - ,('03049701', '0304.97.01', 'Rayas (Rajidae).', '1', 'Kg', '15.00', '0.00') - ,('03049999', '0304.99.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('03052001', '0305.20.01', 'Hígados, huevas y lechas, de pescado, secos, ahumados, salados o en salmuera.', '1', 'Kg', '15.00', '0.00') - ,('03053101', '0305.31.01', 'Tilapias (Oreochromis spp.), bagres o peces gato (Pangasius spp., Silurus spp., Clarias spp., Ictalurus spp.), carpas (Cyprinus spp., Carassius spp., Ctenopharyngodon idellus, Hypophthalmichthys spp., Cirrhinus spp., Mylopharyngodon piceus, Catla catla, Labeo spp., Osteochilus hasselti, Leptobarbus hoeveni, Megalobrama spp.), anguilas (Anguilla spp.), percas del Nilo (Lates niloticus) y peces cabeza de serpiente (Channa spp.).', '1', 'Kg', '15.00', '0.00') - ,('03053201', '0305.32.01', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('03053999', '0305.39.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('03054101', '0305.41.01', 'Salmones del Pacífico (Oncorhynchus nerka, Oncorhynchus gorbuscha, Oncorhynchus keta, Oncorhynchus tschawytscha, Oncorhynchus kisutch, Oncorhynchus masou y Oncorhynchus rhodurus), salmones del Atlántico (Salmo salar) y salmones del Danubio (Hucho hucho).', '1', 'Kg', '15.00', '0.00') - ,('03054201', '0305.42.01', 'Arenques (Clupea harengus, Clupea pallasii).', '1', 'Kg', '15.00', '0.00') - ,('03054301', '0305.43.01', 'Truchas (Salmo trutta, Oncorhynchus mykiss, Oncorhynchus clarki, Oncorhynchus aguabonita, Oncorhynchus gilae, Oncorhynchus apache y Oncorhynchus chrysogaster).', '1', 'Kg', '15.00', '0.00') - ,('03054401', '0305.44.01', 'Tilapias (Oreochromis spp.), bagres o peces gato (Pangasius spp., Silurus spp., Clarias spp., Ictalurus spp.), carpas (Cyprinus spp., Carassius spp., Ctenopharyngodon idellus, Hypophthalmichthys spp., Cirrhinus spp., Mylopharyngodon piceus, Catla catla, Labeo spp., Osteochilus hasselti, Leptobarbus hoeveni, Megalobrama spp.), anguilas (Anguilla spp.), percas del Nilo (Lates niloticus) y peces cabeza de serpiente (Channa spp.).', '1', 'Kg', '15.00', '0.00') - ,('03054999', '0305.49.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('03055102', '0305.51.02', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('03055201', '0305.52.01', 'Tilapias (Oreochromis spp.), bagres o peces gato (Pangasius spp., Silurus spp., Clarias spp., Ictalurus spp.), carpas (Cyprinus spp., Carassius spp., Ctenopharyngodon idellus, Hypophthalmichthys spp., Cirrhinus spp., Mylopharyngodon piceus, Catla catla, Labeo spp., Osteochilus hasselti, Leptobarbus hoeveni, Megalobrama spp.), anguilas (Anguilla spp.), percas del Nilo (Lates niloticus) y peces cabeza de serpiente (Channa spp.).', '1', 'Kg', '15.00', '0.00') - ,('03055301', '0305.53.01', 'Pescados de las familias Bregmacerotidae, Euclichthyidae, Gadidae, Macrouridae, Melanonidae, Merlucciidae, Moridae y Muraenolepididae, excepto los bacalaos (Gadus morhua, Gadus ogac, Gadus macrocephalus).', '1', 'Kg', '15.00', '0.00') - ,('03055401', '0305.54.01', 'Arenques (Clupea harengus, Clupea pallasii), anchoas (Engraulis spp.), sardinas (Sardina pilchardus, Sardinops spp.), sardinelas (Sardinella spp.), espadines (Sprattus sprattus), caballas (Scomber scombrus, Scomber australasicus, Scomber japonicus), caballas de la India (Rastrelliger spp.), carites (Scomberomorus spp.), jureles (Trachurus spp.), pámpanos (Caranx spp.), cobias (Rachycentron canadum), palometones plateados (Pampus spp.), papardas del Pacífico (Cololabis saira), macarelas (Decapte', '1', 'Kg', '15.00', '0.00') - ,('03055999', '0305.59.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('03056101', '0305.61.01', 'Arenques (Clupea harengus, Clupea pallasii).', '1', 'Kg', '15.00', '0.00') - ,('03056201', '0305.62.01', 'Bacalaos (Gadus morhua, Gadus ogac, Gadus macrocephalus).', '1', 'Kg', '15.00', '0.00') - ,('03056301', '0305.63.01', 'Anchoas (Engraulis spp.).', '1', 'Kg', '15.00', '0.00') - ,('03056401', '0305.64.01', 'Tilapias (Oreochromis spp.), bagres o peces gato (Pangasius spp., Silurus spp., Clarias spp., Ictalurus spp.), carpas (Cyprinus spp., Carassius spp., Ctenopharyngodon idellus, Hypophthalmichthys spp., Cirrhinus spp., Mylopharyngodon piceus, Catla catla, Labeo spp., Osteochilus hasselti, Leptobarbus hoeveni, Megalobrama spp.), anguilas (Anguilla spp.), percas del Nilo (Lates niloticus) y peces cabeza de serpiente (Channa spp.).', '1', 'Kg', '15.00', '0.00') - ,('03056999', '0305.69.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('03057101', '0305.71.01', 'Aletas de tiburón.', '1', 'Kg', '15.00', '0.00') - ,('03057201', '0305.72.01', 'Las demás.', '1', 'Kg', '15.00', '0.00') - ,('03057202', '0305.72.01', 'De merluza panameña (Merluccius angustimanus) o merluza del Pacífico Norte (Merluccius productus).', '1', 'Kg', '15.00', '0.00') - ,('03057999', '0305.79.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('03061101', '0306.11.01', 'Langostas (Palinurus spp., Panulirus spp., Jasus spp.).', '1', 'Kg', '15.00', '0.00') - ,('03061201', '0306.12.01', 'Bogavantes (Homarus spp.).', '1', 'Kg', '15.00', '0.00') - ,('03061401', '0306.14.01', 'Cangrejos (excepto macruros).', '1', 'Kg', '15.00', '0.00') - ,('03061501', '0306.15.01', 'Cigalas (Nephrops norvegicus).', '1', 'Kg', '15.00', '0.00') - ,('03061601', '0306.16.01', 'Camarones, langostinos y demás decápodos Natantia, de agua fría (Pandalus spp., Crangon crangon).', '1', 'Kg', '15.00', '0.00') - ,('03061791', '0306.17.91', 'Los demás camarones, langostinos y demás decápodos Natantia.', '1', 'Kg', '15.00', '0.00') - ,('03061999', '0306.19.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('03063101', '0306.31.01', 'Langostas (Palinurus spp., Panulirus spp., Jasus spp.).', '1', 'Kg', '15.00', '0.00') - ,('03063201', '0306.32.01', 'Bogavantes (Homarus spp.).', '1', 'Kg', '15.00', '0.00') - ,('03063301', '0306.33.01', 'Cangrejos (excepto macruros).', '1', 'Kg', '15.00', '0.00') - ,('03063401', '0306.34.01', 'Cigalas (Nephrops norvegicus).', '1', 'Kg', '15.00', '0.00') - ,('03063501', '0306.35.01', 'Reproductores y postlarvas de camarones peneidos y langostinos para acuacultura.', '1', 'Kg', '0.00', '0.00') - ,('03063599', '0306.35.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('03063601', '0306.36.01', 'Reproductores y postlarvas de camarones peneidos y langostinos para acuacultura.', '1', 'Kg', '0.00', '0.00') - ,('03063699', '0306.36.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('03063999', '0306.39.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('03069101', '0306.91.01', 'Langostas (Palinurus spp., Panulirus spp., Jasus spp.).', '1', 'Kg', '15.00', '0.00') - ,('03069201', '0306.92.01', 'Bogavantes (Homarus spp.).', '1', 'Kg', '15.00', '0.00') - ,('03069301', '0306.93.01', 'Cangrejos (excepto macruros).', '1', 'Kg', '15.00', '0.00') - ,('03069401', '0306.94.01', 'Cigalas (Nephrops norvegicus).', '1', 'Kg', '15.00', '0.00') - ,('03069501', '0306.95.01', 'Reproductores y postlarvas de camarones peneidos y langostinos para acuacultura.', '1', 'Kg', '0.00', '0.00') - ,('03069599', '0306.95.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('03069999', '0306.99.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('03071101', '0307.11.01', 'Vivas, frescas o refrigeradas.', '1', 'Kg', '15.00', '0.00') - ,('03071201', '0307.12.01', 'Congeladas.', '1', 'Kg', '15.00', '0.00') - ,('03071999', '0307.19.99', 'Las demás.', '1', 'Kg', '15.00', '0.00') - ,('03072101', '0307.21.01', 'Vivos, frescos o refrigerados.', '1', 'Kg', '15.00', '0.00') - ,('03072201', '0307.22.01', 'Congelados.', '1', 'Kg', '15.00', '0.00') - ,('03072999', '0307.29.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('03073101', '0307.31.01', 'Vivos, frescos o refrigerados.', '1', 'Kg', '15.00', '0.00') - ,('03073201', '0307.32.01', 'Congelados.', '1', 'Kg', '15.00', '0.00') - ,('03073999', '0307.39.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('03074202', '0307.42.02', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('03074302', '0307.43.02', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('03074999', '0307.49.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('03075101', '0307.51.01', 'Vivos, frescos o refrigerados.', '1', 'Kg', '15.00', '0.00') - ,('03075201', '0307.52.01', 'Congelados.', '1', 'Kg', '15.00', '0.00') - ,('03075999', '0307.59.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('03076001', '0307.60.01', 'Caracoles, excepto los de mar.', '1', 'Kg', '15.00', '0.00') - ,('03077101', '0307.71.01', 'Vivos, frescos o refrigerados.', '1', 'Kg', '15.00', '0.00') - ,('03077201', '0307.72.01', 'Congelados.', '1', 'Kg', '15.00', '0.00') - ,('03077999', '0307.79.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('03078101', '0307.81.01', 'Abulones u orejas de mar (Haliotis spp.), vivos, frescos o refrigerados.', '1', 'Kg', '15.00', '0.00') - ,('03078201', '0307.82.01', 'Cobos (caracoles de mar) (Strombus spp.), vivos, frescos o refrigerados.', '1', 'Kg', '15.00', '0.00') - ,('03078301', '0307.83.01', 'Abulones u orejas de mar (Haliotis spp.), congelados.', '1', 'Kg', '15.00', '0.00') - ,('03078401', '0307.84.01', 'Cobos (caracoles de mar) (Strombus spp.), congelados.', '1', 'Kg', '15.00', '0.00') - ,('03078791', '0307.87.91', 'Los demás abulones u orejas de mar (Haliotis spp.).', '1', 'Kg', '15.00', '0.00') - ,('03078891', '0307.88.91', 'Los demás cobos (caracoles de mar) (Strombus spp.).', '1', 'Kg', '15.00', '0.00') - ,('03079101', '0307.91.01', 'Vivos, frescos o refrigerados.', '1', 'Kg', '15.00', '0.00') - ,('03079201', '0307.92.01', 'Congelados.', '1', 'Kg', '15.00', '0.00') - ,('03079999', '0307.99.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('03081101', '0308.11.01', 'Vivos, frescos o refrigerados.', '1', 'Kg', '15.00', '0.00') - ,('03081201', '0308.12.01', 'Congelados.', '1', 'Kg', '15.00', '0.00') - ,('03081999', '0308.19.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('03082101', '0308.21.01', 'Vivos, frescos o refrigerados.', '1', 'Kg', '15.00', '0.00') - ,('03082201', '0308.22.01', 'Congelados.', '1', 'Kg', '15.00', '0.00') - ,('03082999', '0308.29.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('03083001', '0308.30.01', 'Medusas (Rhopilema spp.).', '1', 'Kg', '15.00', '0.00') - ,('03089099', '0308.90.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('03091001', '0309.10.01', 'De pescado.', '1', 'Kg', '15.00', '0.00') - ,('03099099', '0309.90.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('04011002', '0401.10.02', 'Las demás.', '8', 'L', '10.00', '0.00') - ,('04012002', '0401.20.02', 'Las demás.', '8', 'L', '10.00', '0.00') - ,('04014002', '0401.40.02', 'Las demás.', '8', 'L', '10.00', '0.00') - ,('04015002', '0401.50.02', 'Las demás.', '8', 'L', '10.00', '0.00') - ,('04021001', '0402.10.01', 'Leche en polvo o en pastillas.', '1', 'Kg', '45.00', '0.00') - ,('04021099', '0402.10.99', 'Las demás.', '1', 'Kg', '10.00', '0.00') - ,('04022101', '0402.21.01', 'Leche en polvo o en pastillas.', '1', 'Kg', '45.00', '0.00') - ,('04022199', '0402.21.99', 'Las demás.', '1', 'Kg', '10.00', '0.00') - ,('04022999', '0402.29.99', 'Las demás.', '1', 'Kg', '20.00', '0.00') - ,('04029101', '0402.91.01', 'Leche evaporada.', '1', 'Kg', '45.00', '0.00') - ,('04029199', '0402.91.99', 'Las demás.', '1', 'Kg', '20.00', '0.00') - ,('04029901', '0402.99.01', 'Leche condensada.', '1', 'Kg', '15.00', '0.00') - ,('04029999', '0402.99.99', 'Las demás.', '1', 'Kg', '20.00', '0.00') - ,('04032001', '0403.20.01', 'Preparado, mezclado o combinado con chocolate, especias, café o extractos de café, plantas, partes de plantas, cereales o productos de panadería.', '1', 'Kg', '0.00', '0.00') - ,('04032099', '0403.20.99', 'Los demás.', '1', 'Kg', '20.00', '0.00') - ,('04039099', '0403.90.99', 'Los demás.', '1', 'Kg', '20.00', '0.00') - ,('04041001', '0404.10.01', 'Suero de leche en polvo, con contenido de proteínas inferior o igual a 12.5%.', '1', 'Kg', '10.00', '0.00') - ,('04041099', '0404.10.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('04049099', '0404.90.99', 'Los demás.', '1', 'Kg', '20.00', '0.00') - ,('04051002', '0405.10.02', 'Las demás.', '1', 'Kg', '20.00', '0.00') - ,('04052001', '0405.20.01', 'Pastas lácteas para untar.', '1', 'Kg', '20.00', '0.00') - ,('04059001', '0405.90.01', 'Grasa butírica deshidratada.', '1', 'Kg', '0.00', '0.00') - ,('04059099', '0405.90.99', 'Las demás.', '1', 'Kg', '20.00', '0.00') - ,('04061001', '0406.10.01', 'Queso fresco (sin madurar), incluido el del lactosuero, y requesón.', '1', 'Kg', '45.00', '0.00') - ,('04062001', '0406.20.01', 'Queso de cualquier tipo, rallado o en polvo.', '1', 'Kg', '20.00', '0.00') - ,('04063002', '0406.30.02', 'Queso fundido, excepto el rallado o en polvo.', '1', 'Kg', '45.00', '0.00') - ,('04064001', '0406.40.01', 'Queso de pasta azul y demás quesos que presenten vetas producidas por Penicillium roqueforti.', '1', 'Kg', '20.00', '0.00') - ,('04069001', '0406.90.01', 'De pasta dura, denominado Sardo, cuando su presentación así lo indique.', '1', 'Kg', '20.00', '0.00') - ,('04069002', '0406.90.02', 'De pasta dura, denominado Reggiano o Reggianito, cuando su presentación así lo indique.', '1', 'Kg', '0.00', '0.00') - ,('04069004', '0406.90.04', 'Grana o Parmegiano-reggiano, con un contenido de materias grasas inferior o igual al 40% en peso, con un contenido de agua, en la materia no grasa, inferior o igual al 47% en peso; Danbo, Edam, Fontal, Fontina, Fynbo, Gouda, Havarti, Maribo, Samsoe, Esrom, Itálico, Kernhem, Saint-Nectaire, Saint-Paulin o Taleggio, con un contenido de materias grasas inferior o igual al 40% en peso, con un contenido de agua, en la materia no grasa, superior al 47%, pero inferior o igual al 72% en peso.', '1', 'Kg', '20.00', '0.00') - ,('04069099', '0406.90.99', 'Los demás.', '1', 'Kg', '45.00', '0.00') - ,('04071101', '0407.11.01', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('04071999', '0407.19.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('04072102', '0407.21.02', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('04072901', '0407.29.01', 'Para consumo humano.', '1', 'Kg', '45.00', '0.00') - ,('04072999', '0407.29.99', 'Los demás.', '1', 'Kg', '20.00', '0.00') - ,('04079099', '0407.90.99', 'Los demás.', '1', 'Kg', '20.00', '0.00') - ,('04081101', '0408.11.01', 'Secas.', '1', 'Kg', '0.00', '0.00') - ,('04081999', '0408.19.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('04089102', '0408.91.02', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('04089902', '0408.99.02', 'Huevos de aves marinas guaneras.', '1', 'Kg', '20.00', '0.00') - ,('04089999', '0408.99.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('04090001', '0409.00.01', 'Miel natural.', '1', 'Kg', '20.00', '0.00') - ,('04101001', '0410.10.01', 'Insectos.', '1', 'Kg', '10.00', '0.00') - ,('04109001', '0410.90.01', 'Huevos de tortuga de cualquier clase.', '1', 'Kg', '20.00', '0.00') - ,('04109099', '0410.90.99', 'Los demás.', '1', 'Kg', '20.00', '0.00') - ,('05010001', '0501.00.01', 'Cabello en bruto, incluso lavado o desgrasado; desperdicios de cabello.', '1', 'Kg', '20.00', '0.00') - ,('05021001', '0502.10.01', 'Cerdas de cerdo o de jabalí y sus desperdicios.', '1', 'Kg', '0.00', '0.00') - ,('05029099', '0502.90.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('05040001', '0504.00.01', 'Tripas, vejigas y estómagos de animales, excepto los de pescado, enteros o en trozos, frescos, refrigerados, congelados, salados o en salmuera, secos o ahumados.', '1', 'Kg', '10.00', '0.00') - ,('05051001', '0505.10.01', 'Plumas de las utilizadas para relleno; plumón.', '1', 'Kg', '10.00', '0.00') - ,('05059099', '0505.90.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('05061001', '0506.10.01', 'Oseína y huesos acidulados.', '1', 'Kg', '10.00', '0.00') - ,('05069099', '0506.90.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('05071001', '0507.10.01', 'Marfil; polvo y desperdicios de marfil.', '1', 'Kg', '10.00', '0.00') - ,('05079099', '0507.90.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('05080002', '0508.00.02', 'Coral y materias similares, en bruto o simplemente preparados, pero sin otro trabajo; valvas y caparazones de moluscos, crustáceos o equinodermos, y jibiones, en bruto o simplemente preparados, pero sin cortar en forma determinada, incluso en polvo y desperdicios.', '1', 'Kg', '10.00', '0.00') - ,('05100004', '0510.00.04', 'Ámbar gris, castóreo, algalia y almizcle; cantáridas; bilis, incluso desecada; glándulas y demás sustancias de origen animal utilizadas para la preparación de productos farmacéuticos, frescas, refrigeradas, congeladas o conservadas provisionalmente de otra forma.', '1', 'Kg', '10.00', '0.00') - ,('05111001', '0511.10.01', 'Semen de bovino.', '1', 'Kg', '0.00', '0.00') - ,('05119102', '0511.91.02', 'Quistes de artemia (incluso enlatados al vacío), poliquetos y krill para acuacultura.', '1', 'Kg', '0.00', '0.00') - ,('05119199', '0511.91.99', 'Los demás.', '1', 'Kg', '20.00', '0.00') - ,('05119901', '0511.99.01', 'Cochinillas, enteras o en polvo.', '1', 'Kg', '0.00', '0.00') - ,('05119903', '0511.99.03', 'Semen.', '1', 'Kg', '0.00', '0.00') - ,('05119904', '0511.99.04', 'Huevas fecundadas, semillas, larvas y embriones de especies acuáticas para acuacultura.', '1', 'Kg', '0.00', '0.00') - ,('05119905', '0511.99.05', 'Embriones de las especies de ganado bovino, equino, porcino, ovino y caprino.', '6', 'Pza', '0.00', '0.00') - ,('05119908', '0511.99.08', 'Esponjas naturales de origen animal.', '1', 'Kg', '20.00', '0.00') - ,('05119999', '0511.99.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('06011009', '0601.10.09', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('06012010', '0601.20.10', 'Bulbos, cebollas, tubérculos, raíces y bulbos tuberosos, turiones y rizomas, en vegetación o en flor; plantas y raíces de achicoria.', '1', 'Kg', '10.00', '0.00') - ,('06021007', '0602.10.07', 'Esquejes sin enraizar e injertos.', '1', 'Kg', '0.00', '0.00') - ,('06022004', '0602.20.04', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('06023001', '0602.30.01', 'Rododendros y azaleas, incluso injertados.', '1', 'Kg', '10.00', '0.00') - ,('06024001', '0602.40.01', 'Estacas, plantas o plántulas de rosales, con o sin raíz, incluso injertados.', '1', 'Kg', '0.00', '0.00') - ,('06024099', '0602.40.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('06029006', '0602.90.06', 'Esquejes con raíz.', '1', 'Kg', '0.00', '0.00') - ,('06029007', '0602.90.07', 'Plantas vivas acuáticas, incluidos sus bulbos y sus partes, para acuacultura.', '1', 'Kg', '0.00', '0.00') - ,('06029099', '0602.90.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('06031101', '0603.11.01', 'Rosas.', '1', 'Kg', '20.00', '0.00') - ,('06031201', '0603.12.01', 'Claveles.', '1', 'Kg', '20.00', '0.00') - ,('06031301', '0603.13.01', 'Orquídeas.', '1', 'Kg', '20.00', '0.00') - ,('06031402', '0603.14.02', 'Crisantemos.', '1', 'Kg', '20.00', '0.00') - ,('06031501', '0603.15.01', 'Azucenas (Lilium spp.).', '1', 'Kg', '20.00', '0.00') - ,('06031999', '0603.19.99', 'Los demás.', '1', 'Kg', '20.00', '0.00') - ,('06039099', '0603.90.99', 'Los demás.', '1', 'Kg', '20.00', '0.00') - ,('06042001', '0604.20.01', 'Musgo del género Sphagnum.', '1', 'Kg', '0.00', '0.00') - ,('06042099', '0604.20.99', 'Los demás.', '1', 'Kg', '20.00', '0.00') - ,('06049001', '0604.90.01', 'Musgo del género Sphagnum.', '1', 'Kg', '0.00', '0.00') - ,('06049099', '0604.90.99', 'Los demás.', '1', 'Kg', '20.00', '0.00') - ,('07011001', '0701.10.01', 'Para siembra.', '1', 'Kg', '0.00', '0.00') - ,('07019099', '0701.90.99', 'Las demás.', '1', 'Kg', '75.00', '0.00') - ,('07020003', '0702.00.03', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('07031002', '0703.10.02', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('07032002', '0703.20.02', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('07039001', '0703.90.01', 'Puerros y demás hortalizas aliáceas.', '1', 'Kg', '10.00', '0.00') - ,('07041003', '0704.10.03', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('07042001', '0704.20.01', 'Coles (repollitos) de Bruselas.', '1', 'Kg', '10.00', '0.00') - ,('07049099', '0704.90.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('07051101', '0705.11.01', 'Repolladas.', '1', 'Kg', '10.00', '0.00') - ,('07051999', '0705.19.99', 'Las demás.', '1', 'Kg', '10.00', '0.00') - ,('07052101', '0705.21.01', 'Endibia "witloof" (Cichorium intybus var. foliosum).', '1', 'Kg', '10.00', '0.00') - ,('07052999', '0705.29.99', 'Las demás.', '1', 'Kg', '10.00', '0.00') - ,('07061001', '0706.10.01', 'Zanahorias y nabos.', '1', 'Kg', '10.00', '0.00') - ,('07069099', '0706.90.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('07070001', '0707.00.01', 'Pepinos y pepinillos, frescos o refrigerados.', '1', 'Kg', '10.00', '0.00') - ,('07081001', '0708.10.01', 'Chícharos (guisantes, arvejas) (Pisum sativum).', '1', 'Kg', '10.00', '0.00') - ,('07082001', '0708.20.01', 'Frijoles (porotos, alubias, judías, fréjoles) (Vigna spp., Phaseolus spp.).', '1', 'Kg', '10.00', '0.00') - ,('07089099', '0708.90.99', 'Las demás.', '1', 'Kg', '10.00', '0.00') - ,('07092002', '0709.20.02', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('07093001', '0709.30.01', 'Berenjenas.', '1', 'Kg', '10.00', '0.00') - ,('07094002', '0709.40.02', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('07095101', '0709.51.01', 'Hongos del género Agaricus.', '1', 'Kg', '10.00', '0.00') - ,('07095201', '0709.52.01', 'Hongos del género Boletus.', '1', 'Kg', '10.00', '0.00') - ,('07095301', '0709.53.01', 'Hongos del género Cantharellus.', '1', 'Kg', '10.00', '0.00') - ,('07095401', '0709.54.01', 'Shiitake (Lentinus edodes).', '1', 'Kg', '10.00', '0.00') - ,('07095501', '0709.55.01', 'Matsutake (Tricholoma matsutake, Tricholoma magnivelare, Tricholoma anatolicum, Tricholoma dulciolens, Tricholoma caligatum).', '1', 'Kg', '10.00', '0.00') - ,('07095601', '0709.56.01', 'Trufas (Tuber spp.).', '1', 'Kg', '10.00', '0.00') - ,('07095999', '0709.59.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('07096003', '0709.60.03', 'Chile Habanero de la Península de Yucatán.', '1', 'Kg', '10.00', '0.00') - ,('07096004', '0709.60.04', 'Chile Yahualica.', '1', 'Kg', '10.00', '0.00') - ,('07096099', '0709.60.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('07097001', '0709.70.01', 'Espinacas (incluida la de Nueva Zelanda) y armuelles.', '1', 'Kg', '10.00', '0.00') - ,('07099101', '0709.91.01', 'Alcachofas (alcauciles).', '1', 'Kg', '10.00', '0.00') - ,('07099201', '0709.92.01', 'Aceitunas.', '1', 'Kg', '10.00', '0.00') - ,('07099301', '0709.93.01', 'Las demás.', '1', 'Kg', '10.00', '0.00') - ,('07099999', '0709.99.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('07101001', '0710.10.01', 'Papas (patatas).', '1', 'Kg', '15.00', '0.00') - ,('07102101', '0710.21.01', 'Chícharos (guisantes, arvejas) (Pisum sativum).', '1', 'Kg', '15.00', '0.00') - ,('07102201', '0710.22.01', 'Frijoles (porotos, alubias, judías, fréjoles) (Vigna spp., Phaseolus spp.).', '1', 'Kg', '15.00', '0.00') - ,('07102999', '0710.29.99', 'Las demás.', '1', 'Kg', '15.00', '0.00') - ,('07103001', '0710.30.01', 'Espinacas (incluida la de Nueva Zelanda) y armuelles.', '1', 'Kg', '15.00', '0.00') - ,('07104001', '0710.40.01', 'Maíz dulce.', '1', 'Kg', '15.00', '0.00') - ,('07108001', '0710.80.01', 'Cebollas.', '1', 'Kg', '20.00', '0.00') - ,('07108099', '0710.80.99', 'Las demás.', '1', 'Kg', '15.00', '0.00') - ,('07109002', '0710.90.02', 'Mezclas de hortalizas.', '1', 'Kg', '15.00', '0.00') - ,('07112001', '0711.20.01', 'Aceitunas.', '1', 'Kg', '15.00', '0.00') - ,('07114001', '0711.40.01', 'Pepinos y pepinillos.', '1', 'Kg', '15.00', '0.00') - ,('07115101', '0711.51.01', 'Hongos del género Agaricus.', '1', 'Kg', '15.00', '0.00') - ,('07115999', '0711.59.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('07119003', '0711.90.03', 'Alcaparras.', '1', 'Kg', '10.00', '0.00') - ,('07119099', '0711.90.99', 'Las demás.', '1', 'Kg', '15.00', '0.00') - ,('07122001', '0712.20.01', 'Cebollas.', '1', 'Kg', '20.00', '0.00') - ,('07123101', '0712.31.01', 'Hongos del género Agaricus.', '1', 'Kg', '20.00', '0.00') - ,('07123201', '0712.32.01', 'Orejas de Judas (Auricularia spp.).', '1', 'Kg', '20.00', '0.00') - ,('07123301', '0712.33.01', 'Hongos gelatinosos (Tremella spp.).', '1', 'Kg', '20.00', '0.00') - ,('07123401', '0712.34.01', 'Shiitake (Lentinus edodes).', '1', 'Kg', '20.00', '0.00') - ,('07123999', '0712.39.99', 'Los demás.', '1', 'Kg', '20.00', '0.00') - ,('07129001', '0712.90.01', 'Aceitunas deshidratadas.', '1', 'Kg', '15.00', '0.00') - ,('07129099', '0712.90.99', 'Las demás.', '1', 'Kg', '20.00', '0.00') - ,('07131001', '0713.10.01', 'Para siembra.', '1', 'Kg', '0.00', '0.00') - ,('07131099', '0713.10.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('07132001', '0713.20.01', 'Garbanzos.', '1', 'Kg', '10.00', '0.00') - ,('07133101', '0713.31.01', 'Frijoles (porotos, alubias, judías, fréjoles) de las especies Vigna mungo (L) Hepper o Vigna radiata (L) Wilczek.', '1', 'Kg', '10.00', '0.00') - ,('07133201', '0713.32.01', 'Frijoles (porotos, alubias, judías, fréjoles) adzuki (Phaseolus o Vigna angularis).', '1', 'Kg', '10.00', '0.00') - ,('07133301', '0713.33.01', 'Frijol para siembra.', '1', 'Kg', '0.00', '0.00') - ,('07133399', '0713.33.99', 'Los demás.', '1', 'Kg', '45.00', '0.00') - ,('07133401', '0713.34.01', 'Frijoles (porotos, alubias, judías, fréjoles) bambara (Vigna subterránea o Voandzeia subterránea).', '1', 'Kg', '10.00', '0.00') - ,('07133501', '0713.35.01', 'Frijoles (porotos, alubias, judías, fréjoles) salvajes o caupí (Vigna unguiculata).', '1', 'Kg', '10.00', '0.00') - ,('07133999', '0713.39.99', 'Las demás.', '1', 'Kg', '10.00', '0.00') - ,('07134001', '0713.40.01', 'Lentejas.', '1', 'Kg', '10.00', '0.00') - ,('07135001', '0713.50.01', 'Habas (Vicia faba var. major), haba caballar (Vicia faba var. equina) y haba menor (Vicia faba var. minor).', '1', 'Kg', '10.00', '0.00') - ,('07136001', '0713.60.01', 'Chícharos (guisantes, arvejas) de palo, gandú o gandul (Cajanus cajan).', '1', 'Kg', '10.00', '0.00') - ,('07139099', '0713.90.99', 'Las demás.', '1', 'Kg', '10.00', '0.00') - ,('07141001', '0714.10.01', 'Congeladas.', '1', 'Kg', '20.00', '0.00') - ,('07141099', '0714.10.99', 'Las demás.', '1', 'Kg', '10.00', '0.00') - ,('07142001', '0714.20.01', 'Congelados.', '1', 'Kg', '20.00', '0.00') - ,('07142099', '0714.20.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('07143001', '0714.30.01', 'Congelados.', '1', 'Kg', '20.00', '0.00') - ,('07143099', '0714.30.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('07144001', '0714.40.01', 'Congelados.', '1', 'Kg', '20.00', '0.00') - ,('07144099', '0714.40.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('07145001', '0714.50.01', 'Congelados.', '1', 'Kg', '20.00', '0.00') - ,('07145099', '0714.50.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('07149002', '0714.90.02', 'Congelados.', '1', 'Kg', '20.00', '0.00') - ,('07149099', '0714.90.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('08011101', '0801.11.01', 'Secos.', '1', 'Kg', '20.00', '0.00') - ,('08011201', '0801.12.01', 'Con la cáscara interna (endocarpio).', '1', 'Kg', '20.00', '0.00') - ,('08011999', '0801.19.99', 'Los demás.', '1', 'Kg', '20.00', '0.00') - ,('08012101', '0801.21.01', 'Con cáscara.', '1', 'Kg', '20.00', '0.00') - ,('08012201', '0801.22.01', 'Sin cáscara.', '1', 'Kg', '20.00', '0.00') - ,('08013101', '0801.31.01', 'Con cáscara.', '1', 'Kg', '20.00', '0.00') - ,('08013201', '0801.32.01', 'Sin cáscara.', '1', 'Kg', '20.00', '0.00') - ,('08021101', '0802.11.01', 'Con cáscara.', '1', 'Kg', '15.00', '0.00') - ,('08021201', '0802.12.01', 'Sin cáscara.', '1', 'Kg', '20.00', '0.00') - ,('08022101', '0802.21.01', 'Con cáscara.', '1', 'Kg', '0.00', '0.00') - ,('08022201', '0802.22.01', 'Sin cáscara.', '1', 'Kg', '0.00', '0.00') - ,('08023101', '0802.31.01', 'Con cáscara.', '1', 'Kg', '20.00', '0.00') - ,('08023201', '0802.32.01', 'Sin cáscara.', '1', 'Kg', '20.00', '0.00') - ,('08024101', '0802.41.01', 'Con cáscara.', '1', 'Kg', '20.00', '0.00') - ,('08024201', '0802.42.01', 'Sin cáscara.', '1', 'Kg', '20.00', '0.00') - ,('08025101', '0802.51.01', 'Con cáscara.', '1', 'Kg', '20.00', '0.00') - ,('08025201', '0802.52.01', 'Sin cáscara.', '1', 'Kg', '20.00', '0.00') - ,('08026101', '0802.61.01', 'Con cáscara.', '1', 'Kg', '20.00', '0.00') - ,('08026201', '0802.62.01', 'Sin cáscara.', '1', 'Kg', '20.00', '0.00') - ,('08027001', '0802.70.01', 'Nueces de cola (Cola spp.).', '1', 'Kg', '20.00', '0.00') - ,('08028001', '0802.80.01', 'Nueces de areca.', '1', 'Kg', '20.00', '0.00') - ,('08029101', '0802.91.01', 'Piñones con cáscara.', '1', 'Kg', '20.00', '0.00') - ,('08029201', '0802.92.01', 'Piñones sin cáscara.', '1', 'Kg', '20.00', '0.00') - ,('08029999', '0802.99.99', 'Los demás.', '1', 'Kg', '20.00', '0.00') - ,('08031001', '0803.10.01', 'Plátanos "plantains".', '1', 'Kg', '20.00', '0.00') - ,('08039099', '0803.90.99', 'Los demás.', '1', 'Kg', '20.00', '0.00') - ,('08041002', '0804.10.02', 'Los demás.', '1', 'Kg', '20.00', '0.00') - ,('08042002', '0804.20.02', 'Higos.', '1', 'Kg', '20.00', '0.00') - ,('08043001', '0804.30.01', 'Piñas (ananás).', '1', 'Kg', '20.00', '0.00') - ,('08044001', '0804.40.01', 'Aguacates (paltas).', '1', 'Kg', '20.00', '0.00') - ,('08045005', '0804.50.05', 'Mango Ataúlfo del Soconusco Chiapas.', '1', 'Kg', '20.00', '0.00') - ,('08045099', '0804.50.99', 'Mangos.', '1', 'Kg', '20.00', '0.00') - ,('08051001', '0805.10.01', 'Naranjas.', '1', 'Kg', '20.00', '0.00') - ,('08052101', '0805.21.01', 'Mandarinas (incluidas las tangerinas y satsumas).', '1', 'Kg', '20.00', '0.00') - ,('08052201', '0805.22.01', 'Clementinas.', '1', 'Kg', '20.00', '0.00') - ,('08052999', '0805.29.99', 'Los demás.', '1', 'Kg', '20.00', '0.00') - ,('08054001', '0805.40.01', 'Toronjas y pomelos.', '1', 'Kg', '20.00', '0.00') - ,('08055003', '0805.50.03', 'Los demás.', '1', 'Kg', '20.00', '0.00') - ,('08059099', '0805.90.99', 'Los demás.', '1', 'Kg', '20.00', '0.00') - ,('08061001', '0806.10.01', 'Frescas.', '1', 'Kg', '15.00', '0.00') - ,('08062001', '0806.20.01', 'Secas, incluidas las pasas.', '1', 'Kg', '20.00', '0.00') - ,('08071101', '0807.11.01', 'Las demás.', '1', 'Kg', '20.00', '0.00') - ,('08071999', '0807.19.99', 'Los demás.', '1', 'Kg', '20.00', '0.00') - ,('08072001', '0807.20.01', 'Papayas.', '1', 'Kg', '20.00', '0.00') - ,('08081001', '0808.10.01', 'Manzanas.', '1', 'Kg', '20.00', '0.00') - ,('08083001', '0808.30.01', 'Peras.', '1', 'Kg', '20.00', '0.00') - ,('08084001', '0808.40.01', 'Membrillos.', '1', 'Kg', '20.00', '0.00') - ,('08091001', '0809.10.01', 'Chabacanos (damascos, albaricoques).', '1', 'Kg', '20.00', '0.00') - ,('08092101', '0809.21.01', 'Guindas (cerezas ácidas) (Prunus cerasus).', '1', 'Kg', '20.00', '0.00') - ,('08092999', '0809.29.99', 'Las demás.', '1', 'Kg', '20.00', '0.00') - ,('08093003', '0809.30.03', 'Duraznos (melocotones).', '1', 'Kg', '20.00', '0.00') - ,('08094001', '0809.40.01', 'Ciruelas y endrinas.', '1', 'Kg', '20.00', '0.00') - ,('08101001', '0810.10.01', 'Las demás.', '1', 'Kg', '20.00', '0.00') - ,('08102001', '0810.20.01', 'Las demás.', '1', 'Kg', '20.00', '0.00') - ,('08103001', '0810.30.01', 'Grosellas negras, blancas o rojas y grosellas espinosas.', '1', 'Kg', '20.00', '0.00') - ,('08104001', '0810.40.01', 'Los demás.', '1', 'Kg', '20.00', '0.00') - ,('08105001', '0810.50.01', 'Kiwis.', '1', 'Kg', '0.00', '0.00') - ,('08106001', '0810.60.01', 'Duriones.', '1', 'Kg', '20.00', '0.00') - ,('08107001', '0810.70.01', 'Persimonios (caquis).', '1', 'Kg', '20.00', '0.00') - ,('08109099', '0810.90.99', 'Los demás.', '1', 'Kg', '20.00', '0.00') - ,('08111001', '0811.10.01', 'Fresas (frutillas).', '1', 'Kg', '20.00', '0.00') - ,('08112001', '0811.20.01', 'Las demás.', '1', 'Kg', '20.00', '0.00') - ,('08119099', '0811.90.99', 'Los demás.', '1', 'Kg', '20.00', '0.00') - ,('08121001', '0812.10.01', 'Cerezas.', '1', 'Kg', '0.00', '0.00') - ,('08129099', '0812.90.99', 'Los demás.', '1', 'Kg', '20.00', '0.00') - ,('08131002', '0813.10.02', 'Chabacanos (damascos, albaricoques).', '1', 'Kg', '20.00', '0.00') - ,('08132002', '0813.20.02', 'Las demás.', '1', 'Kg', '20.00', '0.00') - ,('08133001', '0813.30.01', 'Manzanas.', '1', 'Kg', '20.00', '0.00') - ,('08134091', '0813.40.91', 'Las demás frutas u otros frutos.', '1', 'Kg', '20.00', '0.00') - ,('08135001', '0813.50.01', 'Mezclas de frutas u otros frutos, secos, o de frutos de cáscara de este Capítulo.', '1', 'Kg', '20.00', '0.00') - ,('08140001', '0814.00.01', 'Cortezas de agrios (cítricos), melones o sandías, frescas, congeladas, secas o presentadas en agua salada, sulfurosa o adicionada de otras sustancias para su conservación provisional.', '1', 'Kg', '15.00', '0.00') - ,('09011103', '0901.11.03', 'Café Veracruz.', '1', 'Kg', '20.00', '0.00') - ,('09011104', '0901.11.04', 'Café Chiapas.', '1', 'Kg', '20.00', '0.00') - ,('09011105', '0901.11.05', 'Café Pluma.', '1', 'Kg', '20.00', '0.00') - ,('09011199', '0901.11.99', 'Los demás.', '1', 'Kg', '20.00', '0.00') - ,('09011202', '0901.12.02', 'Café Veracruz.', '1', 'Kg', '20.00', '0.00') - ,('09011203', '0901.12.03', 'Café Chiapas.', '1', 'Kg', '20.00', '0.00') - ,('09011204', '0901.12.04', 'Café Pluma.', '1', 'Kg', '20.00', '0.00') - ,('09011299', '0901.12.99', 'Los demás.', '1', 'Kg', '20.00', '0.00') - ,('09012102', '0901.21.02', 'Café Veracruz.', '1', 'Kg', '45.00', '0.00') - ,('09012103', '0901.21.03', 'Café Chiapas.', '1', 'Kg', '45.00', '0.00') - ,('09012104', '0901.21.04', 'Café Pluma.', '1', 'Kg', '45.00', '0.00') - ,('09012199', '0901.21.99', 'Los demás.', '1', 'Kg', '45.00', '0.00') - ,('09012202', '0901.22.02', 'Café Veracruz.', '1', 'Kg', '45.00', '0.00') - ,('09012203', '0901.22.03', 'Café Chiapas.', '1', 'Kg', '45.00', '0.00') - ,('09012204', '0901.22.04', 'Café Pluma.', '1', 'Kg', '45.00', '0.00') - ,('09012299', '0901.22.99', 'Los demás.', '1', 'Kg', '45.00', '0.00') - ,('09019099', '0901.90.99', 'Los demás.', '1', 'Kg', '45.00', '0.00') - ,('09021001', '0902.10.01', 'Té verde (sin fermentar) presentado en envases inmediatos con un contenido inferior o igual a 3 kg.', '1', 'Kg', '20.00', '0.00') - ,('09022001', '0902.20.01', 'Té verde (sin fermentar) presentado de otra forma.', '1', 'Kg', '20.00', '0.00') - ,('09023001', '0902.30.01', 'Té negro (fermentado) y té parcialmente fermentado, presentados en envases inmediatos con un contenido inferior o igual a 3 kg.', '1', 'Kg', '20.00', '0.00') - ,('09024001', '0902.40.01', 'Té negro (fermentado) y té parcialmente fermentado, presentados de otra forma.', '1', 'Kg', '20.00', '0.00') - ,('09030001', '0903.00.01', 'Yerba mate.', '1', 'Kg', '20.00', '0.00') - ,('09041101', '0904.11.01', 'Sin triturar ni pulverizar.', '1', 'Kg', '20.00', '0.00') - ,('09041201', '0904.12.01', 'Triturada o pulverizada.', '1', 'Kg', '20.00', '0.00') - ,('09042102', '0904.21.02', 'Los demás.', '1', 'Kg', '20.00', '0.00') - ,('09042202', '0904.22.02', 'Los demás.', '1', 'Kg', '20.00', '0.00') - ,('09051002', '0905.10.02', 'Vainilla de Papantla.', '1', 'Kg', '20.00', '0.00') - ,('09051099', '0905.10.99', 'Las demás.', '1', 'Kg', '20.00', '0.00') - ,('09052002', '0905.20.02', 'Vainilla de Papantla.', '1', 'Kg', '20.00', '0.00') - ,('09052099', '0905.20.99', 'Las demás.', '1', 'Kg', '20.00', '0.00') - ,('09061101', '0906.11.01', 'Canela (Cinnamomum zeylanicum Blume).', '1', 'Kg', '0.00', '0.00') - ,('09061999', '0906.19.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('09062001', '0906.20.01', 'Trituradas o pulverizadas.', '1', 'Kg', '10.00', '0.00') - ,('09071001', '0907.10.01', 'Sin triturar ni pulverizar.', '1', 'Kg', '10.00', '0.00') - ,('09072001', '0907.20.01', 'Triturados o pulverizados.', '1', 'Kg', '10.00', '0.00') - ,('09081101', '0908.11.01', 'Sin triturar ni pulverizar.', '1', 'Kg', '20.00', '0.00') - ,('09081201', '0908.12.01', 'Triturada o pulverizada.', '1', 'Kg', '20.00', '0.00') - ,('09082101', '0908.21.01', 'Sin triturar ni pulverizar.', '1', 'Kg', '20.00', '0.00') - ,('09082201', '0908.22.01', 'Triturado o pulverizado.', '1', 'Kg', '20.00', '0.00') - ,('09083101', '0908.31.01', 'Sin triturar ni pulverizar.', '1', 'Kg', '20.00', '0.00') - ,('09083201', '0908.32.01', 'Triturados o pulverizados.', '1', 'Kg', '20.00', '0.00') - ,('09092101', '0909.21.01', 'Sin triturar ni pulverizar.', '1', 'Kg', '10.00', '0.00') - ,('09092201', '0909.22.01', 'Trituradas o pulverizadas.', '1', 'Kg', '10.00', '0.00') - ,('09093101', '0909.31.01', 'Sin triturar ni pulverizar.', '1', 'Kg', '10.00', '0.00') - ,('09093201', '0909.32.01', 'Trituradas o pulverizadas.', '1', 'Kg', '10.00', '0.00') - ,('09096101', '0909.61.01', 'Semillas de alcaravea.', '1', 'Kg', '20.00', '0.00') - ,('09096102', '0909.61.02', 'Semillas de anís o badiana.', '1', 'Kg', '10.00', '0.00') - ,('09096199', '0909.61.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('09096201', '0909.62.01', 'Semillas de alcaravea.', '1', 'Kg', '20.00', '0.00') - ,('09096202', '0909.62.02', 'Semillas de anís o badiana.', '1', 'Kg', '10.00', '0.00') - ,('09096299', '0909.62.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('09101101', '0910.11.01', 'Sin triturar ni pulverizar.', '1', 'Kg', '10.00', '0.00') - ,('09101201', '0910.12.01', 'Triturado o pulverizado.', '1', 'Kg', '10.00', '0.00') - ,('09102001', '0910.20.01', 'Azafrán.', '1', 'Kg', '10.00', '0.00') - ,('09103001', '0910.30.01', 'Cúrcuma.', '1', 'Kg', '20.00', '0.00') - ,('09109101', '0910.91.01', 'Mezclas previstas en la Nota 1 b) de este Capítulo.', '1', 'Kg', '20.00', '0.00') - ,('09109999', '0910.99.99', 'Las demás.', '1', 'Kg', '20.00', '0.00') - ,('10011101', '1001.11.01', 'Para siembra.', '1', 'Kg', '15.00', '0.00') - ,('10011999', '1001.19.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('10019101', '1001.91.01', 'Triticum Aestivum o Trigo Común Panificable.', '1', 'Kg', '0.00', '0.00') - ,('10019199', '1001.91.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('10019901', '1001.99.01', 'Triticum Aestivum o Trigo Común Panificable.', '1', 'Kg', '0.00', '0.00') - ,('10019999', '1001.99.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('10021001', '1002.10.01', 'Para siembra.', '1', 'Kg', '0.00', '0.00') - ,('10029099', '1002.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('10031001', '1003.10.01', 'Para siembra.', '1', 'Kg', '0.00', '0.00') - ,('10039099', '1003.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('10041001', '1004.10.01', 'Para siembra.', '1', 'Kg', '0.00', '0.00') - ,('10049099', '1004.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('10051001', '1005.10.01', 'Para siembra.', '1', 'Kg', '0.00', '0.00') - ,('10059004', '1005.90.04', 'Maíz blanco (harinero).', '1', 'Kg', '50.00', '50.00') - ,('10059099', '1005.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('10061002', '1006.10.02', 'Arroz del Estado de Morelos.', '1', 'Kg', '9.00', '0.00') - ,('10061099', '1006.10.99', 'Los demás.', '1', 'Kg', '9.00', '0.00') - ,('10062002', '1006.20.02', 'Arroz del Estado de Morelos.', '1', 'Kg', '20.00', '0.00') - ,('10062099', '1006.20.99', 'Los demás.', '1', 'Kg', '20.00', '0.00') - ,('10063003', '1006.30.03', 'Arroz del Estado de Morelos.', '1', 'Kg', '20.00', '0.00') - ,('10063099', '1006.30.99', 'Los demás.', '1', 'Kg', '20.00', '0.00') - ,('10064002', '1006.40.02', 'Arroz del Estado de Morelos.', '1', 'Kg', '20.00', '0.00') - ,('10064099', '1006.40.99', 'Los demás.', '1', 'Kg', '20.00', '0.00') - ,('10071001', '1007.10.01', 'Para siembra.', '1', 'Kg', '0.00', '0.00') - ,('10079001', '1007.90.01', 'Cuando la operación se realice dentro del periodo comprendido entre el 16 de diciembre y el 15 de mayo.', '1', 'Kg', '0.00', '0.00') - ,('10079002', '1007.90.02', 'Cuando la operación se realice dentro del periodo comprendido entre el 16 de mayo y el 15 de diciembre.', '1', 'Kg', '15.00', '0.00') - ,('10081001', '1008.10.01', 'Alforfón.', '1', 'Kg', '0.00', '0.00') - ,('10082101', '1008.21.01', 'Para siembra.', '1', 'Kg', '15.00', '0.00') - ,('10082999', '1008.29.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('10083001', '1008.30.01', 'Alpiste.', '1', 'Kg', '0.00', '0.00') - ,('10084001', '1008.40.01', 'Fonio (Digitaria spp.).', '1', 'Kg', '0.00', '0.00') - ,('10085001', '1008.50.01', 'Quinua (quinoa) (Chenopodium quinoa).', '1', 'Kg', '0.00', '0.00') - ,('10086001', '1008.60.01', 'Triticale.', '1', 'Kg', '0.00', '0.00') - ,('10089091', '1008.90.91', 'Los demás cereales.', '1', 'Kg', '0.00', '0.00') - ,('11010001', '1101.00.01', 'Harina de trigo o de morcajo (tranquillón).', '1', 'Kg', '10.00', '0.00') - ,('11022001', '1102.20.01', 'Harina de maíz.', '1', 'Kg', '10.00', '0.00') - ,('11029099', '1102.90.99', 'Las demás.', '1', 'Kg', '10.00', '0.00') - ,('11031101', '1103.11.01', 'De trigo.', '1', 'Kg', '5.00', '0.00') - ,('11031301', '1103.13.01', 'De maíz.', '1', 'Kg', '5.00', '0.00') - ,('11031991', '1103.19.91', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('11032002', '1103.20.02', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('11041201', '1104.12.01', 'De avena.', '1', 'Kg', '5.00', '0.00') - ,('11041991', '1104.19.91', 'De los demás cereales.', '1', 'Kg', '5.00', '0.00') - ,('11042201', '1104.22.01', 'De avena.', '1', 'Kg', '5.00', '0.00') - ,('11042301', '1104.23.01', 'De maíz.', '1', 'Kg', '0.00', '0.00') - ,('11042991', '1104.29.91', 'De los demás cereales.', '1', 'Kg', '5.00', '0.00') - ,('11043001', '1104.30.01', 'Germen de cereales entero, aplastado, en copos o molido.', '1', 'Kg', '5.00', '0.00') - ,('11051001', '1105.10.01', 'Harina, sémola y polvo.', '1', 'Kg', '10.00', '0.00') - ,('11052001', '1105.20.01', 'Copos, gránulos y "pellets".', '1', 'Kg', '10.00', '0.00') - ,('11061001', '1106.10.01', 'De las hortalizas de la partida 07.13.', '1', 'Kg', '10.00', '0.00') - ,('11062002', '1106.20.02', 'De sagú o de las raíces o tubérculos de la partida 07.14.', '1', 'Kg', '10.00', '0.00') - ,('11063002', '1106.30.02', 'De los productos del Capítulo 08.', '1', 'Kg', '10.00', '0.00') - ,('11071001', '1107.10.01', 'Sin tostar.', '1', 'Kg', '0.00', '0.00') - ,('11072001', '1107.20.01', 'Tostada.', '1', 'Kg', '0.00', '0.00') - ,('11081101', '1108.11.01', 'Almidón de trigo.', '1', 'Kg', '10.00', '0.00') - ,('11081201', '1108.12.01', 'Almidón de maíz.', '1', 'Kg', '10.00', '0.00') - ,('11081301', '1108.13.01', 'Fécula de papa (patata).', '1', 'Kg', '10.00', '0.00') - ,('11081401', '1108.14.01', 'Fécula de yuca (mandioca).', '1', 'Kg', '10.00', '0.00') - ,('11081991', '1108.19.91', 'Los demás almidones y féculas.', '1', 'Kg', '10.00', '0.00') - ,('11082001', '1108.20.01', 'Inulina.', '1', 'Kg', '10.00', '0.00') - ,('11090001', '1109.00.01', 'Gluten de trigo, incluso seco.', '1', 'Kg', '10.00', '0.00') - ,('12011001', '1201.10.01', 'Para siembra.', '1', 'Kg', '0.00', '0.00') - ,('12019001', '1201.90.01', 'Cuando la operación se realice dentro del periodo comprendido entre el 1o. de enero y el 30 de septiembre.', '1', 'Kg', '0.00', '0.00') - ,('12019002', '1201.90.02', 'Cuando la operación se realice dentro del periodo comprendido entre el 1o. de octubre y el 31 de diciembre.', '1', 'Kg', '15.00', '0.00') - ,('12023001', '1202.30.01', 'Para siembra.', '1', 'Kg', '0.00', '0.00') - ,('12024101', '1202.41.01', 'Con cáscara.', '1', 'Kg', '0.00', '0.00') - ,('12024201', '1202.42.01', 'Sin cáscara, incluso quebrantados.', '1', 'Kg', '0.00', '0.00') - ,('12030001', '1203.00.01', 'Copra.', '1', 'Kg', '45.00', '0.00') - ,('12040001', '1204.00.01', 'Semillas de lino, incluso quebrantadas.', '1', 'Kg', '0.00', '0.00') - ,('12051001', '1205.10.01', 'Semillas de nabo (nabina) o de colza con bajo contenido de ácido erúcico.', '1', 'Kg', '0.00', '0.00') - ,('12059099', '1205.90.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('12060002', '1206.00.02', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('12071001', '1207.10.01', 'Nueces y almendras de palma (palmiste).', '1', 'Kg', '0.00', '0.00') - ,('12072101', '1207.21.01', 'Para siembra.', '1', 'Kg', '0.00', '0.00') - ,('12072999', '1207.29.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('12073001', '1207.30.01', 'Semillas de ricino.', '1', 'Kg', '0.00', '0.00') - ,('12074001', '1207.40.01', 'Semillas de sésamo (ajonjolí).', '1', 'Kg', '0.00', '0.00') - ,('12075001', '1207.50.01', 'Semillas de mostaza.', '1', 'Kg', '0.00', '0.00') - ,('12076003', '1207.60.03', 'Semillas de cártamo, excepto para siembra, cuando la operación se realice dentro del periodo comprendido entre el 1o. de octubre y el 31 de diciembre.', '1', 'Kg', '10.00', '0.00') - ,('12076099', '1207.60.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('12077001', '1207.70.01', 'Semillas de melón.', '1', 'Kg', '0.00', '0.00') - ,('12079101', '1207.91.01', 'Semillas de amapola (adormidera).', '1', 'Kg', '10.00', '0.00') - ,('12079999', '1207.99.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('12081001', '1208.10.01', 'De habas (porotos, frijoles, fréjoles) de soja (soya).', '1', 'Kg', '15.00', '0.00') - ,('12089003', '1208.90.03', 'De amapola (adormidera).', '22', 'PROH', '0.00', '0.00') - ,('12089099', '1208.90.99', 'Las demás.', '1', 'Kg', '15.00', '0.00') - ,('12091001', '1209.10.01', 'Semillas de remolacha azucarera.', '1', 'Kg', '9.00', '0.00') - ,('12092101', '1209.21.01', 'De alfalfa.', '1', 'Kg', '0.00', '0.00') - ,('12092201', '1209.22.01', 'De trébol (Trifolium spp.).', '1', 'Kg', '0.00', '0.00') - ,('12092301', '1209.23.01', 'De festucas.', '1', 'Kg', '0.00', '0.00') - ,('12092401', '1209.24.01', 'De pasto azul de Kentucky (Poa pratensis L.).', '1', 'Kg', '0.00', '0.00') - ,('12092501', '1209.25.01', 'De ballico (Lolium multiflorum Lam., Lolium perenne L.).', '1', 'Kg', '0.00', '0.00') - ,('12092904', '1209.29.04', 'De remolacha, excepto azucarera.', '1', 'Kg', '9.00', '0.00') - ,('12092999', '1209.29.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('12093001', '1209.30.01', 'Semillas de plantas herbáceas utilizadas principalmente por sus flores.', '1', 'Kg', '0.00', '0.00') - ,('12099115', '1209.91.15', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('12099907', '1209.99.07', 'De marihuana (Género Cannabis), aun cuando esté mezclada con otras semillas.', '22', 'PROH', '0.00', '0.00') - ,('12099999', '1209.99.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('12101001', '1210.10.01', 'Conos de lúpulo sin triturar ni moler ni en "pellets".', '1', 'Kg', '0.00', '0.00') - ,('12102001', '1210.20.01', 'Conos de lúpulo triturados, molidos o en "pellets"; lupulino.', '1', 'Kg', '0.00', '0.00') - ,('12112001', '1211.20.01', 'Raíces de ginseng, excepto lo comprendido en la fracción arancelaria 1211.20.02.', '1', 'Kg', '10.00', '0.00') - ,('12112002', '1211.20.02', 'Raíces de ginseng, refrigeradas o congeladas.', '1', 'Kg', '20.00', '0.00') - ,('12113001', '1211.30.01', 'Hojas de coca, excepto lo comprendido en la fracción arancelaria 1211.30.02.', '1', 'Kg', '5.00', '0.00') - ,('12113002', '1211.30.02', 'Hojas de coca, refrigeradas o congeladas.', '1', 'Kg', '10.00', '0.00') - ,('12114001', '1211.40.01', 'Paja de adormidera.', '1', 'Kg', '10.00', '0.00') - ,('12115001', '1211.50.01', 'Efedra.', '1', 'Kg', '10.00', '0.00') - ,('12116001', '1211.60.01', 'Corteza de cerezo africano (Prunus africana).', '1', 'Kg', '10.00', '0.00') - ,('12119002', '1211.90.02', 'De marihuana (Género Cannabis).', '22', 'PROH', '0.00', '0.00') - ,('12119091', '1211.90.91', 'Los demás, preparados o conservados conforme a lo indicado en la partida 20.08, refrigerados o congelados.', '1', 'Kg', '20.00', '0.00') - ,('12119099', '1211.90.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('12122101', '1212.21.01', 'Congeladas.', '1', 'Kg', '20.00', '0.00') - ,('12122102', '1212.21.02', 'Algas secas de las especies Porphyra Yezoensis o Porphyra Tenera (alga nori).', '1', 'Kg', '0.00', '0.00') - ,('12122199', '1212.21.99', 'Las demás.', '1', 'Kg', '10.00', '0.00') - ,('12122901', '1212.29.01', 'Congeladas.', '1', 'Kg', '20.00', '0.00') - ,('12122999', '1212.29.99', 'Las demás.', '1', 'Kg', '10.00', '0.00') - ,('12129101', '1212.91.01', 'Remolacha azucarera.', '1', 'Kg', '10.00', '0.00') - ,('12129202', '1212.92.02', 'Algarrobas.', '1', 'Kg', '10.00', '0.00') - ,('12129301', '1212.93.01', 'Caña de azúcar.', '1', 'Kg', '36.00', '0.00') - ,('12129402', '1212.94.02', 'Raíces de achicoria.', '1', 'Kg', '10.00', '0.00') - ,('12129999', '1212.99.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('12130001', '1213.00.01', 'Paja y cascabillo de cereales, en bruto, incluso picados, molidos, prensados o en "pellets".', '1', 'Kg', '10.00', '0.00') - ,('12141001', '1214.10.01', 'Harina y "pellets" de alfalfa.', '1', 'Kg', '15.00', '0.00') - ,('12149001', '1214.90.01', 'Alfalfa.', '1', 'Kg', '10.00', '0.00') - ,('12149099', '1214.90.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('13012001', '1301.20.01', 'Goma arábiga.', '1', 'Kg', '10.00', '0.00') - ,('13019099', '1301.90.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('13021102', '1302.11.02', 'Preparado para fumar.', '22', 'PROH', '0.00', '0.00') - ,('13021199', '1302.11.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('13021202', '1302.12.02', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('13021301', '1302.13.01', 'De lúpulo.', '1', 'Kg', '0.00', '0.00') - ,('13021401', '1302.14.01', 'De efedra.', '1', 'Kg', '15.00', '0.00') - ,('13021901', '1302.19.01', 'De helecho macho.', '1', 'Kg', '10.00', '0.00') - ,('13021902', '1302.19.02', 'De marihuana (Género Cannabis).', '22', 'PROH', '0.00', '0.00') - ,('13021903', '1302.19.03', 'De Ginko-Biloba.', '1', 'Kg', '0.00', '0.00') - ,('13021904', '1302.19.04', 'De haba tonka.', '1', 'Kg', '10.00', '0.00') - ,('13021905', '1302.19.05', 'De belladona, con un contenido de alcaloides inferior o igual al 60%.', '1', 'Kg', '10.00', '0.00') - ,('13021906', '1302.19.06', 'De Pygeum Africanum (Prunus Africana).', '1', 'Kg', '10.00', '0.00') - ,('13021907', '1302.19.07', 'Podofilina.', '1', 'Kg', '10.00', '0.00') - ,('13021908', '1302.19.08', 'Maná.', '1', 'Kg', '10.00', '0.00') - ,('13021909', '1302.19.09', 'Alcoholados, extractos fluidos o sólidos o tinturas, de coca.', '1', 'Kg', '10.00', '0.00') - ,('13021913', '1302.19.13', 'De piretro (pelitre).', '1', 'Kg', '10.00', '0.00') - ,('13021914', '1302.19.14', 'De raíces que contengan rotenona.', '1', 'Kg', '10.00', '0.00') - ,('13021915', '1302.19.15', 'De cáscara de nuez de cajú.', '1', 'Kg', '10.00', '0.00') - ,('13021916', '1302.19.16', 'De Vainilla de Papantla.', '1', 'Kg', '15.00', '0.00') - ,('13021999', '1302.19.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('13022002', '1302.20.02', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('13023101', '1302.31.01', 'Agar-agar.', '1', 'Kg', '15.00', '0.00') - ,('13023201', '1302.32.01', 'Harina o mucílago de algarroba.', '1', 'Kg', '0.00', '0.00') - ,('13023202', '1302.32.02', 'Goma guar.', '1', 'Kg', '10.00', '0.00') - ,('13023299', '1302.32.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('13023901', '1302.39.01', 'Mucílago de zaragatona.', '1', 'Kg', '10.00', '0.00') - ,('13023902', '1302.39.02', 'Carragenina.', '1', 'Kg', '10.00', '0.00') - ,('13023904', '1302.39.04', 'Derivados de la marihuana (Género Cannabis).', '22', 'PROH', '0.00', '0.00') - ,('13023999', '1302.39.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('14011001', '1401.10.01', 'Bambú.', '1', 'Kg', '10.00', '0.00') - ,('14012001', '1401.20.01', 'Ratán (roten).', '1', 'Kg', '0.00', '0.00') - ,('14019099', '1401.90.99', 'Las demás.', '1', 'Kg', '10.00', '0.00') - ,('14042001', '1404.20.01', 'Línteres de algodón.', '1', 'Kg', '10.00', '0.00') - ,('14049099', '1404.90.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('15011001', '1501.10.01', 'Manteca de cerdo.', '1', 'Kg', '15.00', '0.00') - ,('15012091', '1501.20.91', 'Las demás grasas de cerdo.', '1', 'Kg', '15.00', '0.00') - ,('15019099', '1501.90.99', 'Las demás.', '1', 'Kg', '15.00', '0.00') - ,('15021001', '1502.10.01', 'Sebo.', '1', 'Kg', '10.00', '0.00') - ,('15029099', '1502.90.99', 'Las demás.', '1', 'Kg', '10.00', '0.00') - ,('15030002', '1503.00.02', 'Estearina solar, aceite de manteca de cerdo, oleoestearina, oleomargarina y aceite de sebo, sin emulsionar, mezclar ni preparar de otro modo.', '1', 'Kg', '10.00', '0.00') - ,('15041001', '1504.10.01', 'De bacalao.', '1', 'Kg', '0.00', '0.00') - ,('15041099', '1504.10.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('15042002', '1504.20.02', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('15043001', '1504.30.01', 'Grasas y aceites de mamíferos marinos y sus fracciones.', '1', 'Kg', '10.00', '0.00') - ,('15050004', '1505.00.04', 'Las demás.', '1', 'Kg', '10.00', '0.00') - ,('15060001', '1506.00.01', 'De pie de buey, refinado.', '1', 'Kg', '20.00', '0.00') - ,('15060099', '1506.00.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('15071001', '1507.10.01', 'Aceite en bruto, incluso desgomado.', '1', 'Kg', '5.00', '0.00') - ,('15079099', '1507.90.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('15081001', '1508.10.01', 'Aceite en bruto.', '1', 'Kg', '10.00', '0.00') - ,('15089099', '1508.90.99', 'Los demás.', '1', 'Kg', '20.00', '0.00') - ,('15092001', '1509.20.01', 'Aceite de oliva virgen extra.', '1', 'Kg', '0.00', '0.00') - ,('15093001', '1509.30.01', 'Aceite de oliva virgen.', '1', 'Kg', '0.00', '0.00') - ,('15094091', '1509.40.91', 'Los demás aceites de oliva vírgenes.', '1', 'Kg', '0.00', '0.00') - ,('15099099', '1509.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('15101001', '1510.10.01', 'Aceite de orujo de oliva en bruto.', '1', 'Kg', '10.00', '0.00') - ,('15109099', '1510.90.99', 'Las demás.', '1', 'Kg', '10.00', '0.00') - ,('15111001', '1511.10.01', 'Aceite en bruto.', '1', 'Kg', '3.00', '0.00') - ,('15119099', '1511.90.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('15121101', '1512.11.01', 'Aceites en bruto.', '1', 'Kg', '5.00', '0.00') - ,('15121999', '1512.19.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('15122101', '1512.21.01', 'Aceite en bruto, incluso sin gosipol.', '1', 'Kg', '0.00', '0.00') - ,('15122999', '1512.29.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('15131101', '1513.11.01', 'Aceite en bruto.', '1', 'Kg', '0.00', '0.00') - ,('15131999', '1513.19.99', 'Los demás.', '1', 'Kg', '3.00', '0.00') - ,('15132101', '1513.21.01', 'Aceites en bruto.', '1', 'Kg', '0.00', '0.00') - ,('15132999', '1513.29.99', 'Los demás.', '1', 'Kg', '3.00', '0.00') - ,('15141101', '1514.11.01', 'Aceites en bruto.', '1', 'Kg', '0.00', '0.00') - ,('15141999', '1514.19.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('15149101', '1514.91.01', 'Aceites en bruto.', '1', 'Kg', '0.00', '0.00') - ,('15149999', '1514.99.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('15151101', '1515.11.01', 'Aceite en bruto.', '1', 'Kg', '10.00', '0.00') - ,('15151999', '1515.19.99', 'Los demás.', '1', 'Kg', '20.00', '0.00') - ,('15152101', '1515.21.01', 'Aceite en bruto.', '1', 'Kg', '10.00', '0.00') - ,('15152999', '1515.29.99', 'Los demás.', '1', 'Kg', '20.00', '0.00') - ,('15153001', '1515.30.01', 'Aceite de ricino y sus fracciones.', '1', 'Kg', '0.00', '0.00') - ,('15155001', '1515.50.01', 'Aceite de sésamo (ajonjolí) y sus fracciones.', '1', 'Kg', '10.00', '0.00') - ,('15156001', '1515.60.01', 'Grasas y aceites, de origen microbiano, y sus fracciones.', '1', 'Kg', '10.00', '0.00') - ,('15159099', '1515.90.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('15161001', '1516.10.01', 'Grasas y aceites, animales, y sus fracciones.', '1', 'Kg', '15.00', '0.00') - ,('15162001', '1516.20.01', 'Grasas y aceites, vegetales, y sus fracciones.', '1', 'Kg', '5.00', '0.00') - ,('15163001', '1516.30.01', 'Grasas y aceites, de origen microbiano, y sus fracciones.', '1', 'Kg', '5.00', '0.00') - ,('15171001', '1517.10.01', 'Margarina, excepto la margarina líquida.', '1', 'Kg', '20.00', '0.00') - ,('15179099', '1517.90.99', 'Las demás.', '1', 'Kg', '20.00', '0.00') - ,('15180001', '1518.00.01', 'Mezcla de aceites de girasol y oliva, bromados, calidad farmacéutica.', '1', 'Kg', '0.00', '0.00') - ,('15180002', '1518.00.02', 'Aceites animales o vegetales epoxidados.', '1', 'Kg', '15.00', '0.00') - ,('15180099', '1518.00.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('15200001', '1520.00.01', 'Glicerol en bruto; aguas y lejías glicerinosas.', '1', 'Kg', '10.00', '0.00') - ,('15211001', '1521.10.01', 'Carnauba.', '1', 'Kg', '0.00', '0.00') - ,('15211099', '1521.10.99', 'Las demás.', '1', 'Kg', '10.00', '0.00') - ,('15219004', '1521.90.04', 'Esperma de ballena y de otros cetáceos.', '1', 'Kg', '10.00', '0.00') - ,('15219099', '1521.90.99', 'Las demás.', '1', 'Kg', '15.00', '0.00') - ,('15220001', '1522.00.01', 'Degrás; residuos procedentes del tratamiento de grasas o ceras, animales o vegetales.', '1', 'Kg', '10.00', '0.00') - ,('16010003', '1601.00.03', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('16021002', '1602.10.02', 'Los demás.', '1', 'Kg', '20.00', '0.00') - ,('16022002', '1602.20.02', 'De hígado de cualquier animal.', '1', 'Kg', '20.00', '0.00') - ,('16023101', '1602.31.01', 'De pavo (gallipavo).', '1', 'Kg', '20.00', '0.00') - ,('16023201', '1602.32.01', 'De aves de la especie Gallus domesticus.', '1', 'Kg', '20.00', '0.00') - ,('16023999', '1602.39.99', 'Las demás.', '1', 'Kg', '20.00', '0.00') - ,('16024101', '1602.41.01', 'Jamones y trozos de jamón.', '1', 'Kg', '20.00', '0.00') - ,('16024201', '1602.42.01', 'Paletas y trozos de paleta.', '1', 'Kg', '20.00', '0.00') - ,('16024991', '1602.49.91', 'Las demás.', '1', 'Kg', '20.00', '0.00') - ,('16025002', '1602.50.02', 'De la especie bovina.', '1', 'Kg', '20.00', '0.00') - ,('16029091', '1602.90.91', 'Las demás.', '1', 'Kg', '20.00', '0.00') - ,('16030001', '1603.00.01', 'Extractos de carne.', '1', 'Kg', '20.00', '0.00') - ,('16030099', '1603.00.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('16041101', '1604.11.01', 'Salmones.', '1', 'Kg', '0.00', '0.00') - ,('16041201', '1604.12.01', 'Arenques.', '1', 'Kg', '0.00', '0.00') - ,('16041302', '1604.13.02', 'Las demás.', '1', 'Kg', '20.00', '0.00') - ,('16041404', '1604.14.04', 'Filetes ("lomos") de atunes aleta amarilla ("Yellowfin Tuna"), de barrilete ("Skip Jask") o de patudo ("Big Eye"), de peso superior o igual a 0.5 kg, pero inferior o igual a 7.5 kg, precocidos, congelados y empacados al vacío en fundas de plástico, libres de escamas, espinas, hueso, piel y carne negra.', '1', 'Kg', '0.00', '0.00') - ,('16041499', '1604.14.99', 'Las demás.', '1', 'Kg', '20.00', '0.00') - ,('16041501', '1604.15.01', 'Caballas.', '1', 'Kg', '20.00', '0.00') - ,('16041602', '1604.16.02', 'Las demás.', '1', 'Kg', '20.00', '0.00') - ,('16041701', '1604.17.01', 'Anguilas.', '1', 'Kg', '20.00', '0.00') - ,('16041801', '1604.18.01', 'Aletas de tiburón.', '1', 'Kg', '20.00', '0.00') - ,('16041999', '1604.19.99', 'Las demás.', '1', 'Kg', '20.00', '0.00') - ,('16042091', '1604.20.91', 'Las demás.', '1', 'Kg', '20.00', '0.00') - ,('16043101', '1604.31.01', 'Caviar.', '1', 'Kg', '0.00', '0.00') - ,('16043201', '1604.32.01', 'Sucedáneos del caviar.', '1', 'Kg', '20.00', '0.00') - ,('16051002', '1605.10.02', 'Los demás.', '1', 'Kg', '20.00', '0.00') - ,('16052101', '1605.21.01', 'Presentados en envases no herméticos.', '1', 'Kg', '20.00', '0.00') - ,('16052999', '1605.29.99', 'Los demás.', '1', 'Kg', '20.00', '0.00') - ,('16053001', '1605.30.01', 'Bogavantes.', '1', 'Kg', '20.00', '0.00') - ,('16054091', '1605.40.91', 'Los demás crustáceos.', '1', 'Kg', '20.00', '0.00') - ,('16055101', '1605.51.01', 'Ostras.', '1', 'Kg', '20.00', '0.00') - ,('16055201', '1605.52.01', 'Vieiras, volandeiras y demás moluscos.', '1', 'Kg', '20.00', '0.00') - ,('16055301', '1605.53.01', 'Mejillones.', '1', 'Kg', '20.00', '0.00') - ,('16055401', '1605.54.01', 'Sepias (jibias), globitos, calamares y potas.', '1', 'Kg', '20.00', '0.00') - ,('16055501', '1605.55.01', 'Pulpos.', '1', 'Kg', '20.00', '0.00') - ,('16055601', '1605.56.01', 'Almejas, berberechos y arcas.', '1', 'Kg', '20.00', '0.00') - ,('16055701', '1605.57.01', 'Abulones u orejas de mar.', '1', 'Kg', '20.00', '0.00') - ,('16055801', '1605.58.01', 'Caracoles, excepto los de mar.', '1', 'Kg', '20.00', '0.00') - ,('16055999', '1605.59.99', 'Los demás.', '1', 'Kg', '20.00', '0.00') - ,('16056101', '1605.61.01', 'Pepinos de mar.', '1', 'Kg', '20.00', '0.00') - ,('16056201', '1605.62.01', 'Erizos de mar.', '1', 'Kg', '20.00', '0.00') - ,('16056301', '1605.63.01', 'Medusas.', '1', 'Kg', '20.00', '0.00') - ,('16056999', '1605.69.99', 'Los demás.', '1', 'Kg', '20.00', '0.00') - ,('17011205', '1701.12.05', 'Azúcar cuyo contenido en peso de sacarosa, en estado seco, tenga una polarización inferior a 99.2 grados.', '1', 'Kg', '0.00', '0.00') - ,('17011301', '1701.13.01', 'Azúcar de caña mencionado en la Nota 2 de subpartida de este Capítulo.', '1', 'Kg', '0.00', '0.00') - ,('17011491', '1701.14.91', 'Azúcar cuyo contenido en peso de sacarosa, en estado seco, tenga una polarización inferior a 99.2 grados.', '1', 'Kg', '0.00', '0.00') - ,('17019104', '1701.91.04', 'Azúcar cuyo contenido en peso de sacarosa, en estado seco, tenga una polarización inferior a 99.2 grados.', '1', 'Kg', '0.00', '0.00') - ,('17019999', '1701.99.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('17021101', '1702.11.01', 'Con un contenido de lactosa superior o igual al 99% en peso, expresado en lactosa anhidra, calculado sobre producto seco.', '1', 'Kg', '10.00', '0.00') - ,('17021901', '1702.19.01', 'Lactosa.', '1', 'Kg', '10.00', '0.00') - ,('17021999', '1702.19.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('17022001', '1702.20.01', 'Azúcar y jarabe de arce ("maple").', '1', 'Kg', '15.00', '0.00') - ,('17023001', '1702.30.01', 'Glucosa y jarabe de glucosa, sin fructosa o con un contenido de fructosa sobre producto seco, inferior al 20% en peso.', '1', 'Kg', '15.00', '0.00') - ,('17024001', '1702.40.01', 'Glucosa.', '1', 'Kg', '15.00', '0.00') - ,('17024099', '1702.40.99', 'Los demás.', '1', 'Kg', '50.00', '0.00') - ,('17025001', '1702.50.01', 'Fructosa químicamente pura.', '1', 'Kg', '75.00', '0.00') - ,('17026091', '1702.60.91', 'Los demás.', '1', 'Kg', '75.00', '0.00') - ,('17029001', '1702.90.01', 'Azúcar líquida refinada y azúcar invertido.', '1', 'Kg', '0.00', '0.00') - ,('17029099', '1702.90.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('17031001', '1703.10.01', 'Melaza de caña.', '1', 'Kg', '10.00', '0.00') - ,('17039099', '1703.90.99', 'Las demás.', '1', 'Kg', '10.00', '0.00') - ,('17041001', '1704.10.01', 'Chicles y demás gomas de mascar, incluso recubiertos de azúcar.', '1', 'Kg', '20.00', '0.00') - ,('17049099', '1704.90.99', 'Los demás.', '1', 'Kg', '20.00', '0.00') - ,('18010002', '1801.00.02', 'Cacao Grijalva.', '1', 'Kg', '0.00', '0.00') - ,('18010099', '1801.00.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('18020001', '1802.00.01', 'Cáscara, películas y demás residuos de cacao.', '1', 'Kg', '0.00', '0.00') - ,('18031001', '1803.10.01', 'Sin desgrasar.', '1', 'Kg', '0.00', '0.00') - ,('18032001', '1803.20.01', 'Desgrasada total o parcialmente.', '1', 'Kg', '0.00', '0.00') - ,('18040001', '1804.00.01', 'Manteca, grasa y aceite de cacao.', '1', 'Kg', '0.00', '0.00') - ,('18050001', '1805.00.01', 'Cacao en polvo sin adición de azúcar ni otro edulcorante.', '1', 'Kg', '5.00', '0.00') - ,('18061001', '1806.10.01', 'Con un contenido de azúcar superior o igual al 90%, en peso.', '1', 'Kg', '0.00', '0.00') - ,('18061099', '1806.10.99', 'Los demás.', '1', 'Kg', '20.00', '0.00') - ,('18062091', '1806.20.91', 'Las demás preparaciones, en bloques, tabletas o barras con peso superior a 2 kg o en forma líquida, pastosa o en polvo, gránulos o formas similares, en recipientes o en envases inmediatos con un contenido superior a 2 kg.', '1', 'Kg', '20.00', '0.00') - ,('18063101', '1806.31.01', 'Rellenos.', '1', 'Kg', '20.00', '0.00') - ,('18063201', '1806.32.01', 'Sin rellenar.', '1', 'Kg', '20.00', '0.00') - ,('18069099', '1806.90.99', 'Los demás.', '1', 'Kg', '20.00', '0.00') - ,('19011002', '1901.10.02', 'Las demás.', '1', 'Kg', '10.00', '0.00') - ,('19012002', '1901.20.02', 'Con un contenido de grasa butírica superior al 25%, en peso, sin acondicionar para la venta al por menor, excepto a base de harinas, almidones o fécula, de avena, maíz o trigo.', '1', 'Kg', '10.00', '0.00') - ,('19012099', '1901.20.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('19019002', '1901.90.02', 'Productos alimenticios vegetales, dietéticos, para diabéticos.', '1', 'Kg', '10.00', '0.00') - ,('19019003', '1901.90.03', 'Preparaciones a base de productos lácteos con un contenido de sólidos lácteos superior al 10%, pero inferior o igual a 50%, en peso, excepto las comprendidas en la fracción arancelaria 1901.90.04.', '1', 'Kg', '10.00', '0.00') - ,('19019004', '1901.90.04', 'Preparaciones a base de productos lácteos con un contenido de sólidos lácteos superior al 10%, acondicionadas en envases para la venta al por menor cuya etiqueta contenga indicaciones para la utilización directa del producto en la preparación de alimentos o postres, por ejemplo.', '1', 'Kg', '10.00', '0.00') - ,('19019005', '1901.90.05', 'Preparaciones a base de productos lácteos con un contenido de sólidos lácteos superior al 50%, en peso, excepto las comprendidas en la fracción arancelaria 1901.90.04.', '1', 'Kg', '45.00', '0.00') - ,('19019099', '1901.90.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('19021101', '1902.11.01', 'Que contengan huevo.', '1', 'Kg', '20.00', '0.00') - ,('19021999', '1902.19.99', 'Las demás.', '1', 'Kg', '10.00', '0.00') - ,('19022001', '1902.20.01', 'Pastas alimenticias rellenas, incluso cocidas o preparadas de otra forma.', '1', 'Kg', '10.00', '0.00') - ,('19023091', '1902.30.91', 'Las demás pastas alimenticias.', '1', 'Kg', '10.00', '0.00') - ,('19024001', '1902.40.01', 'Cuscús.', '1', 'Kg', '10.00', '0.00') - ,('19030001', '1903.00.01', 'Tapioca y sus sucedáneos preparados con fécula, en copos, grumos, granos perlados, cerniduras o formas similares.', '1', 'Kg', '10.00', '0.00') - ,('19041001', '1904.10.01', 'Productos a base de cereales obtenidos por inflado o tostado.', '1', 'Kg', '10.00', '0.00') - ,('19042001', '1904.20.01', 'Preparaciones alimenticias obtenidas con copos de cereales sin tostar o con mezclas de copos de cereales sin tostar y copos de cereales tostados o cereales inflados.', '1', 'Kg', '10.00', '0.00') - ,('19043001', '1904.30.01', 'Trigo bulgur.', '1', 'Kg', '10.00', '0.00') - ,('19049099', '1904.90.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('19051001', '1905.10.01', 'Pan crujiente llamado "Knäckebrot".', '1', 'Kg', '10.00', '0.00') - ,('19052001', '1905.20.01', 'Pan de especias.', '1', 'Kg', '10.00', '0.00') - ,('19053101', '1905.31.01', 'Galletas dulces (con adición de edulcorante).', '1', 'Kg', '10.00', '0.00') - ,('19053201', '1905.32.01', 'Barquillos y obleas, incluso rellenos ("gaufrettes", "wafers") y "waffles" ("gaufres").', '1', 'Kg', '10.00', '0.00') - ,('19054001', '1905.40.01', 'Pan tostado y productos similares tostados.', '1', 'Kg', '10.00', '0.00') - ,('19059099', '1905.90.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('20011001', '2001.10.01', 'Pepinos y pepinillos.', '1', 'Kg', '20.00', '0.00') - ,('20019099', '2001.90.99', 'Los demás.', '1', 'Kg', '20.00', '0.00') - ,('20021001', '2002.10.01', 'Tomates enteros o en trozos.', '1', 'Kg', '20.00', '0.00') - ,('20029099', '2002.90.99', 'Los demás.', '1', 'Kg', '20.00', '0.00') - ,('20031001', '2003.10.01', 'Hongos del género Agaricus.', '1', 'Kg', '20.00', '0.00') - ,('20039099', '2003.90.99', 'Los demás.', '1', 'Kg', '20.00', '0.00') - ,('20041001', '2004.10.01', 'Papas (patatas).', '1', 'Kg', '20.00', '0.00') - ,('20049091', '2004.90.91', 'Las demás hortalizas y las mezclas de hortalizas.', '1', 'Kg', '20.00', '0.00') - ,('20051001', '2005.10.01', 'Hortalizas homogeneizadas.', '1', 'Kg', '20.00', '0.00') - ,('20052001', '2005.20.01', 'Papas (patatas).', '1', 'Kg', '20.00', '0.00') - ,('20054001', '2005.40.01', 'Chícharos (arvejas, guisantes) (Pisum sativum).', '1', 'Kg', '20.00', '0.00') - ,('20055101', '2005.51.01', 'Desvainadas.', '1', 'Kg', '20.00', '0.00') - ,('20055999', '2005.59.99', 'Las demás.', '1', 'Kg', '20.00', '0.00') - ,('20056001', '2005.60.01', 'Espárragos.', '1', 'Kg', '20.00', '0.00') - ,('20057001', '2005.70.01', 'Aceitunas.', '1', 'Kg', '20.00', '0.00') - ,('20058001', '2005.80.01', 'Maíz dulce (Zea mays var. saccharata).', '1', 'Kg', '20.00', '0.00') - ,('20059101', '2005.91.01', 'Brotes de bambú.', '1', 'Kg', '20.00', '0.00') - ,('20059999', '2005.99.99', 'Las demás.', '1', 'Kg', '20.00', '0.00') - ,('20060001', '2006.00.01', 'Frijoles (porotos, alubias, judías, fréjoles) (Vigna spp., Phaseolus spp.).', '1', 'Kg', '20.00', '0.00') - ,('20060002', '2006.00.02', 'Espárragos.', '1', 'Kg', '20.00', '0.00') - ,('20060091', '2006.00.91', 'Las demás hortalizas congeladas, excepto lo comprendido en las fracciones arancelarias 2006.00.01 y 2006.00.02.', '1', 'Kg', '20.00', '0.00') - ,('20060099', '2006.00.99', 'Los demás.', '1', 'Kg', '20.00', '0.00') - ,('20071001', '2007.10.01', 'Preparaciones homogeneizadas.', '1', 'Kg', '20.00', '0.00') - ,('20079101', '2007.91.01', 'De agrios (cítricos).', '1', 'Kg', '20.00', '0.00') - ,('20079901', '2007.99.01', 'Compotas o mermeladas destinadas a diabéticos.', '1', 'Kg', '20.00', '0.00') - ,('20079902', '2007.99.02', 'Jaleas, destinadas a diabéticos.', '1', 'Kg', '20.00', '0.00') - ,('20079903', '2007.99.03', 'Purés o pastas destinadas a diabéticos.', '1', 'Kg', '20.00', '0.00') - ,('20079999', '2007.99.99', 'Los demás.', '1', 'Kg', '20.00', '0.00') - ,('20081102', '2008.11.02', 'Los demás.', '1', 'Kg', '20.00', '0.00') - ,('20081991', '2008.19.91', 'Los demás.', '1', 'Kg', '20.00', '0.00') - ,('20082001', '2008.20.01', 'Piñas (ananás).', '1', 'Kg', '20.00', '0.00') - ,('20083009', '2008.30.09', 'Los demás.', '1', 'Kg', '20.00', '0.00') - ,('20084001', '2008.40.01', 'Peras.', '1', 'Kg', '20.00', '0.00') - ,('20085001', '2008.50.01', 'Chabacanos (damascos, albaricoques).', '1', 'Kg', '20.00', '0.00') - ,('20086001', '2008.60.01', 'Cerezas.', '1', 'Kg', '20.00', '0.00') - ,('20087001', '2008.70.01', 'Duraznos (melocotones), incluidos los griñones y nectarinas.', '1', 'Kg', '20.00', '0.00') - ,('20088001', '2008.80.01', 'Fresas (frutillas).', '1', 'Kg', '20.00', '0.00') - ,('20089101', '2008.91.01', 'Palmitos.', '1', 'Kg', '20.00', '0.00') - ,('20089301', '2008.93.01', 'Arándanos agrios, trepadores o palustres (Vaccinium macrocarpon, Vaccinium oxycoccos); arándanos rojos o encarnados (Vaccinium vitis-idaea).', '1', 'Kg', '20.00', '0.00') - ,('20089701', '2008.97.01', 'Mezclas.', '1', 'Kg', '20.00', '0.00') - ,('20089999', '2008.99.99', 'Los demás.', '1', 'Kg', '20.00', '0.00') - ,('20091101', '2009.11.01', 'Congelado.', '8', 'L', '20.00', '0.00') - ,('20091202', '2009.12.02', 'Los demás.', '8', 'L', '20.00', '0.00') - ,('20091999', '2009.19.99', 'Los demás.', '8', 'L', '20.00', '0.00') - ,('20092101', '2009.21.01', 'De valor Brix inferior o igual a 20.', '8', 'L', '20.00', '0.00') - ,('20092999', '2009.29.99', 'Los demás.', '8', 'L', '20.00', '0.00') - ,('20093102', '2009.31.02', 'Los demás.', '8', 'L', '20.00', '0.00') - ,('20093999', '2009.39.99', 'Los demás.', '8', 'L', '20.00', '0.00') - ,('20094102', '2009.41.02', 'Los demás.', '8', 'L', '20.00', '0.00') - ,('20094999', '2009.49.99', 'Los demás.', '8', 'L', '20.00', '0.00') - ,('20095001', '2009.50.01', 'Jugo de tomate.', '8', 'L', '20.00', '0.00') - ,('20096101', '2009.61.01', 'De valor Brix inferior o igual a 30.', '8', 'L', '20.00', '0.00') - ,('20096999', '2009.69.99', 'Los demás.', '8', 'L', '20.00', '0.00') - ,('20097101', '2009.71.01', 'De valor Brix inferior o igual a 20.', '8', 'L', '20.00', '0.00') - ,('20097999', '2009.79.99', 'Los demás.', '8', 'L', '20.00', '0.00') - ,('20098101', '2009.81.01', 'Jugo de arándanos agrios, trepadores o palustres (Vaccinium macrocarpon, Vaccinium oxycoccos); jugo de arándanos rojos o encarnados (Vaccinium vitis-idaea).', '8', 'L', '20.00', '0.00') - ,('20098999', '2009.89.99', 'Los demás.', '8', 'L', '20.00', '0.00') - ,('20099002', '2009.90.02', 'Mezclas de jugos.', '8', 'L', '20.00', '0.00') - ,('21011102', '2101.11.02', 'Extracto de café líquido concentrado, aunque se presente congelado.', '8', 'L', '45.00', '0.00') - ,('21011199', '2101.11.99', 'Los demás.', '1', 'Kg', '45.00', '0.00') - ,('21011201', '2101.12.01', 'Preparaciones a base de extractos, esencias o concentrados o a base de café.', '1', 'Kg', '45.00', '0.00') - ,('21012001', '2101.20.01', 'Extractos, esencias y concentrados de té o de yerba mate y preparaciones a base de estos extractos, esencias o concentrados o a base de té o de yerba mate.', '1', 'Kg', '0.00', '0.00') - ,('21013001', '2101.30.01', 'Achicoria tostada y demás sucedáneos del café tostados y sus extractos, esencias y concentrados.', '1', 'Kg', '20.00', '0.00') - ,('21021002', '2102.10.02', 'De tórula.', '1', 'Kg', '10.00', '0.00') - ,('21021099', '2102.10.99', 'Las demás.', '1', 'Kg', '15.00', '0.00') - ,('21022001', '2102.20.01', 'Levaduras de tórula.', '1', 'Kg', '10.00', '0.00') - ,('21022099', '2102.20.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('21023001', '2102.30.01', 'Polvos preparados para esponjar masas.', '1', 'Kg', '15.00', '0.00') - ,('21031001', '2103.10.01', 'Salsa de soja (soya).', '1', 'Kg', '20.00', '0.00') - ,('21032002', '2103.20.02', 'Las demás.', '1', 'Kg', '20.00', '0.00') - ,('21033002', '2103.30.02', 'Las demás.', '1', 'Kg', '20.00', '0.00') - ,('21039099', '2103.90.99', 'Los demás.', '1', 'Kg', '20.00', '0.00') - ,('21041001', '2104.10.01', 'Preparaciones para sopas, potajes o caldos; sopas, potajes o caldos, preparados.', '1', 'Kg', '10.00', '0.00') - ,('21042001', '2104.20.01', 'Preparaciones alimenticias compuestas homogeneizadas.', '1', 'Kg', '10.00', '0.00') - ,('21050001', '2105.00.01', 'Helados, incluso con cacao.', '1', 'Kg', '20.00', '0.00') - ,('21061001', '2106.10.01', 'Concentrados de proteína de soja (soya).', '1', 'Kg', '0.00', '0.00') - ,('21061099', '2106.10.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('21069001', '2106.90.01', 'Polvos para la elaboración de budines y gelatinas destinadas a diabéticos.', '1', 'Kg', '15.00', '0.00') - ,('21069003', '2106.90.03', 'Autolizados o extractos de levadura.', '1', 'Kg', '15.00', '0.00') - ,('21069004', '2106.90.04', 'A base de corazón de res pulverizado, aceite de ajonjolí; almidón de tapioca, azúcar, vitaminas y minerales.', '1', 'Kg', '10.00', '0.00') - ,('21069005', '2106.90.05', 'Jarabes aromatizados o con adición de colorantes.', '1', 'Kg', '0.00', '0.00') - ,('21069007', '2106.90.07', 'Mezclas de jugos concentrados de frutas, legumbres u hortalizas, enriquecidos con minerales o vitaminas.', '1', 'Kg', '15.00', '0.00') - ,('21069008', '2106.90.08', 'Con un contenido de sólidos lácteos superior al 10%, en peso.', '1', 'Kg', '15.00', '0.00') - ,('21069009', '2106.90.09', 'Preparaciones a base de huevo.', '1', 'Kg', '15.00', '0.00') - ,('21069010', '2106.90.10', 'Extractos y concentrados del tipo de los utilizados en la elaboración de bebidas que contengan alcohol, excepto las preparaciones a base de sustancias odoríferas de la partida 33.02.', '8', 'L', '10.00', '0.00') - ,('21069012', '2106.90.12', 'Derivados de proteína de leche, cuya composición sea: manteca de coco hidrogenada 44%, glucosa anhidra 38%, caseinato de sodio 10%, emulsificantes 6%, estabilizador 2%, en peso.', '1', 'Kg', '15.00', '0.00') - ,('21069091', '2106.90.91', 'Las demás preparaciones del tipo de las utilizadas en la elaboración de bebidas que contengan alcohol, excepto las preparaciones a base de sustancias odoríferas de la partida 33.02.', '8', 'L', '20.00', '0.00') - ,('21069092', '2106.90.92', 'Las demás de proteína de soja (soya), cuyo contenido de proteína sea inferior o igual al 50%, en peso.', '1', 'Kg', '10.00', '0.00') - ,('21069099', '2106.90.99', 'Las demás.', '1', 'Kg', '15.00', '0.00') - ,('22011002', '2201.10.02', 'Las demás.', '8', 'L', '20.00', '0.00') - ,('22019001', '2201.90.01', 'Agua potable.', '8', 'L', '10.00', '0.00') - ,('22019002', '2201.90.02', 'Hielo.', '1', 'Kg', '10.00', '0.00') - ,('22019099', '2201.90.99', 'Los demás.', '8', 'L', '20.00', '0.00') - ,('22021001', '2202.10.01', 'Agua, incluidas el agua mineral y la gaseada, con adición de azúcar u otro edulcorante o aromatizada.', '8', 'L', '20.00', '0.00') - ,('22029101', '2202.91.01', 'Cerveza sin alcohol.', '8', 'L', '20.00', '0.00') - ,('22029901', '2202.99.01', 'A base de ginseng y jalea real.', '8', 'L', '10.00', '0.00') - ,('22029902', '2202.99.02', 'Los demás.', '8', 'L', '20.00', '0.00') - ,('22029903', '2202.99.03', 'A base de mezclas de jugos de frutas, legumbres u hortalizas, enriquecidos con minerales o vitaminas.', '8', 'L', '20.00', '0.00') - ,('22029904', '2202.99.04', 'Que contengan leche.', '8', 'L', '20.00', '0.00') - ,('22029999', '2202.99.99', 'Las demás.', '8', 'L', '20.00', '0.00') - ,('22030001', '2203.00.01', 'Cerveza de malta.', '8', 'L', '20.00', '0.00') - ,('22041002', '2204.10.02', 'Los demás.', '8', 'L', '20.00', '0.00') - ,('22042104', '2204.21.04', 'Los demás.', '8', 'L', '20.00', '0.00') - ,('22042201', '2204.22.01', 'En recipientes con capacidad superior a 2 l pero inferior o igual a 10 l.', '8', 'L', '20.00', '0.00') - ,('22042999', '2204.29.99', 'Los demás.', '8', 'L', '20.00', '0.00') - ,('22043091', '2204.30.91', 'Los demás mostos de uva.', '8', 'L', '20.00', '0.00') - ,('22051002', '2205.10.02', 'En recipientes con capacidad inferior o igual a 2 l.', '8', 'L', '20.00', '0.00') - ,('22059099', '2205.90.99', 'Los demás.', '8', 'L', '20.00', '0.00') - ,('22060091', '2206.00.91', 'Las demás.', '8', 'L', '20.00', '0.00') - ,('22071001', '2207.10.01', 'Alcohol etílico sin desnaturalizar con grado alcohólico volumétrico superior o igual al 80% vol.', '8', 'L', '10.00', '0.00') - ,('22072001', '2207.20.01', 'Alcohol etílico y aguardiente desnaturalizados, de cualquier graduación.', '8', 'L', '10.00', '0.00') - ,('22082001', '2208.20.01', 'Cogñac.', '8', 'L', '0.00', '0.00') - ,('22082002', '2208.20.02', 'Brandy o "Wainbrand" cuya graduación alcohólica sea superior o igual a 37.5 grados centesimales Gay-Lussac, con una cantidad total de sustancias volátiles que no sean los alcoholes etílico y metílico superior a 200 g/hl de alcohol a 100% vol.', '8', 'L', '20.00', '0.00') - ,('22082003', '2208.20.03', 'Destilados puros de uva, cuya graduación alcohólica sea superior o igual a 80 grados centesimales Gay-Lussac, a la temperatura de 15°C, a granel.', '8', 'L', '10.00', '0.00') - ,('22082099', '2208.20.99', 'Los demás.', '8', 'L', '20.00', '0.00') - ,('22083005', '2208.30.05', 'Los demás.', '8', 'L', '0.00', '0.00') - ,('22084002', '2208.40.02', 'Los demás.', '8', 'L', '20.00', '0.00') - ,('22085001', '2208.50.01', 'Gin y ginebra.', '8', 'L', '20.00', '0.00') - ,('22086001', '2208.60.01', 'Vodka.', '8', 'L', '20.00', '0.00') - ,('22087003', '2208.70.03', 'Los demás.', '8', 'L', '20.00', '0.00') - ,('22089001', '2208.90.01', 'Alcohol etílico.', '8', 'L', '10.00', '0.00') - ,('22089002', '2208.90.02', 'Bacanora.', '8', 'L', '20.00', '0.00') - ,('22089003', '2208.90.03', 'Los demás tequilas.', '8', 'L', '20.00', '0.00') - ,('22089004', '2208.90.04', 'Sotol.', '8', 'L', '20.00', '0.00') - ,('22089005', '2208.90.05', 'Mezcal.', '8', 'L', '20.00', '0.00') - ,('22089006', '2208.90.06', 'Charanda.', '8', 'L', '20.00', '0.00') - ,('22089007', '2208.90.07', 'Raicilla.', '8', 'L', '20.00', '0.00') - ,('22089099', '2208.90.99', 'Los demás.', '8', 'L', '20.00', '0.00') - ,('22090001', '2209.00.01', 'Vinagre y sucedáneos del vinagre obtenidos a partir del ácido acético.', '8', 'L', '20.00', '0.00') - ,('23011002', '2301.10.02', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('23012001', '2301.20.01', 'Harina, polvo y "pellets", de pescado o de crustáceos, moluscos o demás invertebrados acuáticos.', '1', 'Kg', '15.00', '0.00') - ,('23021001', '2302.10.01', 'De maíz.', '1', 'Kg', '10.00', '0.00') - ,('23023001', '2302.30.01', 'De trigo.', '1', 'Kg', '10.00', '0.00') - ,('23024091', '2302.40.91', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('23025001', '2302.50.01', 'De leguminosas.', '1', 'Kg', '10.00', '0.00') - ,('23031001', '2303.10.01', 'Residuos de la industria del almidón y residuos similares.', '1', 'Kg', '15.00', '0.00') - ,('23032001', '2303.20.01', 'Pulpa de remolacha, bagazo de caña de azúcar y demás desperdicios de la industria azucarera.', '1', 'Kg', '10.00', '0.00') - ,('23033001', '2303.30.01', 'Solubles y granos desecados de la destilación del maíz.', '1', 'Kg', '0.00', '0.00') - ,('23033099', '2303.30.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('23040001', '2304.00.01', 'Tortas y demás residuos sólidos de la extracción del aceite de soja (soya), incluso molidos o en "pellets".', '1', 'Kg', '0.00', '0.00') - ,('23050001', '2305.00.01', 'Tortas y demás residuos sólidos de la extracción del aceite de cacahuate (cacahuete, maní), incluso molidos o en "pellets".', '1', 'Kg', '15.00', '0.00') - ,('23061001', '2306.10.01', 'De semillas de algodón.', '1', 'Kg', '15.00', '0.00') - ,('23062001', '2306.20.01', 'De semillas de lino.', '1', 'Kg', '15.00', '0.00') - ,('23063001', '2306.30.01', 'De semillas de girasol.', '1', 'Kg', '15.00', '0.00') - ,('23064101', '2306.41.01', 'Con bajo contenido de ácido erúcico.', '1', 'Kg', '15.00', '0.00') - ,('23064999', '2306.49.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('23065001', '2306.50.01', 'De coco o de copra.', '1', 'Kg', '15.00', '0.00') - ,('23066001', '2306.60.01', 'De nuez o de almendra de palma (palmiste).', '1', 'Kg', '15.00', '0.00') - ,('23069099', '2306.90.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('23070001', '2307.00.01', 'Lías o heces de vino; tártaro bruto.', '1', 'Kg', '10.00', '0.00') - ,('23080002', '2308.00.02', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('23091001', '2309.10.01', 'Alimentos para perros o gatos, acondicionados para la venta al por menor.', '1', 'Kg', '10.00', '0.00') - ,('23099004', '2309.90.04', 'Mezclas, preparaciones o productos de origen orgánico para la alimentación de peces de ornato.', '1', 'Kg', '20.00', '0.00') - ,('23099099', '2309.90.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('24011002', '2401.10.02', 'Los demás.', '1', 'Kg', '45.00', '0.00') - ,('24012003', '2401.20.03', 'Los demás.', '1', 'Kg', '45.00', '0.00') - ,('24013001', '2401.30.01', 'Desperdicios de tabaco.', '1', 'Kg', '45.00', '0.00') - ,('24021001', '2402.10.01', 'Cigarros (puros) (incluso despuntados) y cigarritos (puritos), que contengan tabaco.', '6', 'Pza', '45.00', '0.00') - ,('24022001', '2402.20.01', 'Cigarrillos que contengan tabaco.', '1', 'Kg', '67.00', '0.00') - ,('24029099', '2402.90.99', 'Los demás.', '1', 'Kg', '67.00', '0.00') - ,('24031101', '2403.11.01', 'Tabaco para pipa de agua mencionado en la Nota 1 de subpartida de este Capítulo.', '1', 'Kg', '67.00', '0.00') - ,('24031999', '2403.19.99', 'Los demás.', '1', 'Kg', '67.00', '0.00') - ,('24039102', '2403.91.02', 'Los demás.', '1', 'Kg', '45.00', '0.00') - ,('24039901', '2403.99.01', 'Rapé húmedo oral.', '1', 'Kg', '20.00', '0.00') - ,('24039999', '2403.99.99', 'Los demás.', '1', 'Kg', '45.00', '0.00') - ,('24041101', '2404.11.01', 'Que contengan tabaco o tabaco reconstituido.', '22', 'PROH', '0.00', '0.00') - ,('24041201', '2404.12.01', 'Los demás, que contengan nicotina.', '22', 'PROH', '0.00', '0.00') - ,('24041901', '2404.19.01', 'De los diseñados para lo comprendido en la fracción arancelaria 8543.40.01.', '22', 'PROH', '0.00', '0.00') - ,('24041999', '2404.19.99', 'Los demás.', '1', 'Kg', '45.00', '0.00') - ,('24049101', '2404.91.01', 'Para administrarse por vía oral.', '1', 'Kg', '15.00', '0.00') - ,('24049201', '2404.92.01', 'Para administrarse por vía transdérmica.', '1', 'Kg', '5.00', '0.00') - ,('24049999', '2404.99.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('25010002', '2501.00.02', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('25020001', '2502.00.01', 'Piritas de hierro sin tostar.', '1', 'Kg', '0.00', '0.00') - ,('25030002', '2503.00.02', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('25041001', '2504.10.01', 'En polvo o en escamas.', '1', 'Kg', '0.00', '0.00') - ,('25049099', '2504.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('25051001', '2505.10.01', 'Arenas silíceas y arenas cuarzosas.', '1', 'Kg', '0.00', '0.00') - ,('25059099', '2505.90.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('25061001', '2506.10.01', 'Cuarzo.', '1', 'Kg', '0.00', '0.00') - ,('25062002', '2506.20.02', 'Cuarcita.', '1', 'Kg', '0.00', '0.00') - ,('25070001', '2507.00.01', 'Caolín y demás arcillas caolínicas, incluso calcinados.', '1', 'Kg', '0.00', '0.00') - ,('25081001', '2508.10.01', 'Bentonita.', '1', 'Kg', '0.00', '0.00') - ,('25083001', '2508.30.01', 'Arcillas refractarias.', '1', 'Kg', '0.00', '0.00') - ,('25084091', '2508.40.91', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('25085001', '2508.50.01', 'Andalucita, cianita y silimanita.', '1', 'Kg', '0.00', '0.00') - ,('25086001', '2508.60.01', 'Mullita.', '1', 'Kg', '0.00', '0.00') - ,('25087001', '2508.70.01', 'Tierras de chamota o de dinas.', '1', 'Kg', '0.00', '0.00') - ,('25090001', '2509.00.01', 'Creta.', '1', 'Kg', '0.00', '0.00') - ,('25101002', '2510.10.02', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('25102002', '2510.20.02', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('25111001', '2511.10.01', 'Sulfato de bario natural (baritina).', '1', 'Kg', '0.00', '0.00') - ,('25112001', '2511.20.01', 'Carbonato de bario natural (witherita).', '1', 'Kg', '0.00', '0.00') - ,('25120001', '2512.00.01', 'Harinas silíceas fósiles (por ejemplo: "kieselguhr", tripolita, diatomita) y demás tierras silíceas análogas, de densidad aparente inferior o igual a 1, incluso calcinadas.', '1', 'Kg', '0.00', '0.00') - ,('25131002', '2513.10.02', 'Piedra pómez.', '1', 'Kg', '0.00', '0.00') - ,('25132001', '2513.20.01', 'Esmeril, corindón natural, granate natural y demás abrasivos naturales.', '1', 'Kg', '0.00', '0.00') - ,('25140001', '2514.00.01', 'Pizarra, incluso desbastada o simplemente troceada, por aserrado o de otro modo, en bloques o en placas cuadradas o rectangulares.', '1', 'Kg', '0.00', '0.00') - ,('25151101', '2515.11.01', 'En bruto o desbastados.', '1', 'Kg', '0.00', '0.00') - ,('25151201', '2515.12.01', 'Mármol aserrado en hojas, de espesor superior a 5 cm.', '1', 'Kg', '0.00', '0.00') - ,('25151299', '2515.12.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('25152001', '2515.20.01', 'Ecaussines y demás piedras calizas de talla o de construcción; alabastro.', '1', 'Kg', '0.00', '0.00') - ,('25161101', '2516.11.01', 'En bruto o desbastado.', '1', 'Kg', '0.00', '0.00') - ,('25161201', '2516.12.01', 'Simplemente troceado, por aserrado o de otro modo, en bloques o en placas cuadradas o rectangulares.', '1', 'Kg', '0.00', '0.00') - ,('25162001', '2516.20.01', 'Arenisca.', '1', 'Kg', '0.00', '0.00') - ,('25169091', '2516.90.91', 'Las demás piedras de talla o de construcción.', '1', 'Kg', '0.00', '0.00') - ,('25171001', '2517.10.01', 'Cantos, grava, piedras machacadas, de los tipos generalmente utilizados para hacer hormigón, o para firmes de carreteras, vías férreas u otros balastos, guijarros y pedernal, incluso tratados térmicamente.', '1', 'Kg', '0.00', '0.00') - ,('25172001', '2517.20.01', 'Macadán de escorias o de desechos industriales similares, incluso con materiales citados en la subpartida 2517.10.', '1', 'Kg', '0.00', '0.00') - ,('25173001', '2517.30.01', 'Macadán alquitranado.', '1', 'Kg', '0.00', '0.00') - ,('25174101', '2517.41.01', 'De mármol.', '1', 'Kg', '0.00', '0.00') - ,('25174999', '2517.49.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('25181001', '2518.10.01', 'Dolomita sin calcinar ni sinterizar, llamada "cruda".', '1', 'Kg', '0.00', '0.00') - ,('25182001', '2518.20.01', 'Dolomita calcinada o sinterizada.', '1', 'Kg', '0.00', '0.00') - ,('25191001', '2519.10.01', 'Carbonato de magnesio natural (magnesita).', '1', 'Kg', '0.00', '0.00') - ,('25199001', '2519.90.01', 'Óxido de magnesio, excepto la magnesia electrofundida y la magnesia calcinada a muerte (sinterizada).', '1', 'Kg', '6.00', '0.00') - ,('25199002', '2519.90.02', 'Magnesia calcinada a muerte (sinterizada) con un contenido de óxido de magnesio inferior o igual al 94%, en peso.', '1', 'Kg', '0.00', '0.00') - ,('25199003', '2519.90.03', 'Óxido de magnesio electrofundido, con un contenido de óxido de magnesio inferior o igual a 98%, en peso.', '1', 'Kg', '5.00', '0.00') - ,('25199099', '2519.90.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('25201001', '2520.10.01', 'Yeso natural; anhidrita.', '1', 'Kg', '0.00', '0.00') - ,('25202001', '2520.20.01', 'Yeso fraguable.', '1', 'Kg', '0.00', '0.00') - ,('25210001', '2521.00.01', 'Castinas; piedras para la fabricación de cal o de cemento.', '1', 'Kg', '0.00', '0.00') - ,('25221001', '2522.10.01', 'Cal viva.', '1', 'Kg', '0.00', '0.00') - ,('25222001', '2522.20.01', 'Cal apagada.', '1', 'Kg', '0.00', '0.00') - ,('25223001', '2522.30.01', 'Cal hidráulica.', '1', 'Kg', '0.00', '0.00') - ,('25231001', '2523.10.01', 'Cementos sin pulverizar o clínker.', '1', 'Kg', '0.00', '0.00') - ,('25232101', '2523.21.01', 'Cemento blanco, incluso coloreado artificialmente.', '1', 'Kg', '0.00', '0.00') - ,('25232999', '2523.29.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('25233001', '2523.30.01', 'Cementos aluminosos.', '1', 'Kg', '0.00', '0.00') - ,('25239091', '2523.90.91', 'Los demás cementos hidráulicos.', '1', 'Kg', '0.00', '0.00') - ,('25241003', '2524.10.03', 'Crocidolita.', '1', 'Kg', '0.00', '0.00') - ,('25249099', '2524.90.99', 'En polvo o en copos, incluidos los desperdicios.', '1', 'Kg', '0.00', '0.00') - ,('25251001', '2525.10.01', 'Mica en bruto o exfoliada en hojas o en laminillas irregulares ("splittings").', '1', 'Kg', '0.00', '0.00') - ,('25252001', '2525.20.01', 'Mica en polvo.', '1', 'Kg', '0.00', '0.00') - ,('25253001', '2525.30.01', 'Desperdicios de mica.', '1', 'Kg', '0.00', '0.00') - ,('25261001', '2526.10.01', 'Sin triturar ni pulverizar.', '1', 'Kg', '0.00', '0.00') - ,('25262001', '2526.20.01', 'Triturados o pulverizados.', '1', 'Kg', '0.00', '0.00') - ,('25280001', '2528.00.01', 'Boratos de sodio naturales y sus concentrados (incluso calcinados).', '1', 'Kg', '0.00', '0.00') - ,('25280099', '2528.00.99', 'Los demás.', '1', 'Kg', '7.00', '0.00') - ,('25291001', '2529.10.01', 'Feldespato.', '1', 'Kg', '0.00', '0.00') - ,('25292101', '2529.21.01', 'Con un contenido de fluoruro de calcio inferior o igual al 97% en peso.', '1', 'Kg', '0.00', '0.00') - ,('25292201', '2529.22.01', 'Con un contenido de fluoruro de calcio superior al 97% en peso.', '1', 'Kg', '0.00', '0.00') - ,('25293001', '2529.30.01', 'Leucita; nefelina y nefelina sienita.', '1', 'Kg', '0.00', '0.00') - ,('25301001', '2530.10.01', 'Vermiculita, perlita y cloritas, sin dilatar.', '1', 'Kg', '0.00', '0.00') - ,('25302001', '2530.20.01', 'Kieserita, epsomita (sulfatos de magnesio naturales).', '1', 'Kg', '0.00', '0.00') - ,('25309099', '2530.90.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('26011101', '2601.11.01', 'Sin aglomerar.', '1', 'Kg', '0.00', '0.00') - ,('26011201', '2601.12.01', 'Aglomerados.', '1', 'Kg', '0.00', '0.00') - ,('26012001', '2601.20.01', 'Piritas de hierro tostadas (cenizas de piritas).', '1', 'Kg', '0.00', '0.00') - ,('26020002', '2602.00.02', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('26030001', '2603.00.01', 'Minerales de cobre y sus concentrados.', '1', 'Kg', '0.00', '0.00') - ,('26040001', '2604.00.01', 'Minerales de níquel y sus concentrados.', '1', 'Kg', '0.00', '0.00') - ,('26050001', '2605.00.01', 'Minerales de cobalto y sus concentrados.', '1', 'Kg', '0.00', '0.00') - ,('26060002', '2606.00.02', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('26070001', '2607.00.01', 'Minerales de plomo y sus concentrados.', '1', 'Kg', '0.00', '0.00') - ,('26080001', '2608.00.01', 'Minerales de cinc y sus concentrados.', '1', 'Kg', '0.00', '0.00') - ,('26090001', '2609.00.01', 'Minerales de estaño y sus concentrados.', '1', 'Kg', '0.00', '0.00') - ,('26100002', '2610.00.02', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('26110001', '2611.00.01', 'Minerales de volframio (tungsteno) y sus concentrados.', '1', 'Kg', '0.00', '0.00') - ,('26121001', '2612.10.01', 'Minerales de uranio y sus concentrados.', '1', 'Kg', '0.00', '0.00') - ,('26122001', '2612.20.01', 'Minerales de torio y sus concentrados.', '1', 'Kg', '0.00', '0.00') - ,('26131001', '2613.10.01', 'Tostados.', '1', 'Kg', '0.00', '0.00') - ,('26139099', '2613.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('26140003', '2614.00.03', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('26151002', '2615.10.02', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('26159099', '2615.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('26161001', '2616.10.01', 'Minerales de plata y sus concentrados.', '1', 'Kg', '0.00', '0.00') - ,('26169099', '2616.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('26171001', '2617.10.01', 'Minerales de antimonio y sus concentrados.', '1', 'Kg', '0.00', '0.00') - ,('26179099', '2617.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('26180001', '2618.00.01', 'Escorias granuladas (arena de escoria) de la siderurgia.', '1', 'Kg', '0.00', '0.00') - ,('26190002', '2619.00.02', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('26201101', '2620.11.01', 'Matas de galvanización.', '1', 'Kg', '0.00', '0.00') - ,('26201999', '2620.19.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('26202101', '2620.21.01', 'Lodos de gasolina con plomo y lodos de compuestos antidetonantes con plomo.', '1', 'Kg', '0.00', '0.00') - ,('26202999', '2620.29.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('26203001', '2620.30.01', 'Que contengan principalmente cobre.', '1', 'Kg', '0.00', '0.00') - ,('26204002', '2620.40.02', 'Que contengan principalmente aluminio.', '1', 'Kg', '0.00', '0.00') - ,('26206001', '2620.60.01', 'Que contengan arsénico, mercurio, talio o sus mezclas, de los tipos utilizados para la extracción de arsénico o de estos metales o para la elaboración de sus compuestos químicos.', '1', 'Kg', '0.00', '0.00') - ,('26209102', '2620.91.02', 'Que contengan antimonio, berilio, cadmio, cromo, o sus mezclas.', '1', 'Kg', '0.00', '0.00') - ,('26209999', '2620.99.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('26211001', '2621.10.01', 'Cenizas y residuos procedentes de la incineración de desechos municipales.', '1', 'Kg', '0.00', '0.00') - ,('26219099', '2621.90.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('27011101', '2701.11.01', 'Antracitas.', '1', 'Kg', '0.00', '0.00') - ,('27011201', '2701.12.01', 'Hulla bituminosa.', '1', 'Kg', '0.00', '0.00') - ,('27011991', '2701.19.91', 'Las demás hullas.', '1', 'Kg', '0.00', '0.00') - ,('27012001', '2701.20.01', 'Briquetas, ovoides y combustibles sólidos similares, obtenidos de la hulla.', '1', 'Kg', '0.00', '0.00') - ,('27021001', '2702.10.01', 'Lignitos, incluso pulverizados, pero sin aglomerar.', '1', 'Kg', '0.00', '0.00') - ,('27022001', '2702.20.01', 'Lignitos aglomerados.', '1', 'Kg', '0.00', '0.00') - ,('27030002', '2703.00.02', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('27040003', '2704.00.03', 'Carbón de retorta.', '1', 'Kg', '0.00', '0.00') - ,('27050001', '2705.00.01', 'Gas de hulla, gas de agua, gas pobre y gases similares, excepto el gas de petróleo y demás hidrocarburos gaseosos.', '8', 'L', '0.00', '0.00') - ,('27060001', '2706.00.01', 'Alquitranes de hulla, lignito o turba y demás alquitranes minerales, aunque estén deshidratados o descabezados, incluidos los alquitranes reconstituidos.', '8', 'L', '0.00', '0.00') - ,('27071001', '2707.10.01', 'Benzol (benceno).', '8', 'L', '0.00', '0.00') - ,('27072001', '2707.20.01', 'Toluol (tolueno).', '8', 'L', '0.00', '0.00') - ,('27073001', '2707.30.01', 'Xilol (xilenos).', '8', 'L', '0.00', '0.00') - ,('27074001', '2707.40.01', 'Naftaleno.', '8', 'L', '0.00', '0.00') - ,('27075091', '2707.50.91', 'Las demás mezclas de hidrocarburos aromáticos que destilen, incluidas las pérdidas, una proporción superior o igual al 65% en volumen a 250°C, según el método ISO 3405 (equivalente al método ASTM D 86).', '8', 'L', '0.00', '0.00') - ,('27079101', '2707.91.01', 'Aceites de creosota.', '8', 'L', '0.00', '0.00') - ,('27079999', '2707.99.99', 'Los demás.', '8', 'L', '0.00', '0.00') - ,('27081001', '2708.10.01', 'Brea.', '1', 'Kg', '0.00', '0.00') - ,('27082001', '2708.20.01', 'Coque de brea.', '1', 'Kg', '0.00', '0.00') - ,('27090005', '2709.00.05', 'Ligeros.', '15', 'Barr', '0.00', '0.00') - ,('27090099', '2709.00.99', 'Los demás.', '5', 'M³', '0.00', '0.00') - ,('27101206', '2710.12.06', 'Mezcla isomérica de trimetil penteno y dimetil hexeno (Diisobutileno).', '1', 'Kg', '0.00', '0.00') - ,('27101299', '2710.12.99', 'Los demás.', '8', 'L', '0.00', '0.00') - ,('27101902', '2710.19.02', 'Aceites de engrase o preparaciones lubricantes a base de aceites minerales derivados del petróleo, con aditivos (aceites lubricantes terminados).', '8', 'L', '3.00', '0.00') - ,('27101999', '2710.19.99', 'Los demás.', '8', 'L', '0.00', '0.00') - ,('27102001', '2710.20.01', 'Aceites de petróleo o de mineral bituminoso (excepto los aceites crudos) y preparaciones no expresadas ni comprendidas en otra parte, con un contenido de aceites de petróleo o de mineral bituminoso superior o igual al 70% en peso, en las que estos aceites constituyan el elemento base, que contengan biodiésel, excepto los desechos de aceites.', '8', 'L', '0.00', '0.00') - ,('27109101', '2710.91.01', 'Que contengan bifenilos policlorados (PCB), terfenilos policlorados (PCT) o bifenilos polibromados (PBB).', '8', 'L', '0.00', '0.00') - ,('27109999', '2710.99.99', 'Los demás.', '8', 'L', '0.00', '0.00') - ,('27111101', '2711.11.01', 'Gas natural.', '8', 'L', '0.00', '0.00') - ,('27111201', '2711.12.01', 'Propano.', '8', 'L', '0.00', '0.00') - ,('27111301', '2711.13.01', 'Butanos.', '8', 'L', '0.00', '0.00') - ,('27111401', '2711.14.01', 'Etileno, propileno, butileno y butadieno.', '8', 'L', '0.00', '0.00') - ,('27111901', '2711.19.01', 'Butano y propano, mezclados entre sí, licuados.', '8', 'L', '0.00', '0.00') - ,('27111999', '2711.19.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('27112101', '2711.21.01', 'Gas natural.', '8', 'L', '0.00', '0.00') - ,('27112999', '2711.29.99', 'Los demás.', '5', 'M³', '0.00', '0.00') - ,('27121001', '2712.10.01', 'Vaselina.', '1', 'Kg', '0.00', '0.00') - ,('27122001', '2712.20.01', 'Parafina con un contenido de aceite inferior al 0.75% en peso.', '1', 'Kg', '0.00', '0.00') - ,('27129001', '2712.90.01', 'Cera de lignito.', '1', 'Kg', '0.00', '0.00') - ,('27129002', '2712.90.02', 'Ceras microcristalinas.', '1', 'Kg', '0.00', '0.00') - ,('27129003', '2712.90.03', 'Residuos parafínicos ("slack wax"), con un contenido de aceite superior o igual a 8%, en peso.', '1', 'Kg', '0.00', '0.00') - ,('27129099', '2712.90.99', 'Los demás.', '1', 'Kg', '7.00', '0.00') - ,('27131101', '2713.11.01', 'Sin calcinar.', '1', 'Kg', '0.00', '0.00') - ,('27131201', '2713.12.01', 'Calcinado.', '1', 'Kg', '0.00', '0.00') - ,('27132001', '2713.20.01', 'Betún de petróleo.', '1', 'Kg', '0.00', '0.00') - ,('27139091', '2713.90.91', 'Los demás residuos de los aceites de petróleo o de mineral bituminoso.', '1', 'Kg', '0.00', '0.00') - ,('27141001', '2714.10.01', 'Pizarras y arenas bituminosas.', '1', 'Kg', '0.00', '0.00') - ,('27149001', '2714.90.01', 'Betunes y asfaltos naturales.', '1', 'Kg', '0.00', '0.00') - ,('27149099', '2714.90.99', 'Los demás.', '1', 'Kg', '0.00', '25.00') - ,('27150001', '2715.00.01', 'Betunes fluidificados; mezclas bituminosas a base de asfalto acondicionadas para su venta en envases con capacidad inferior o igual a 200 l.', '1', 'Kg', '0.00', '0.00') - ,('27150099', '2715.00.99', 'Los demás.', '1', 'Kg', '0.00', '25.00') - ,('27160001', '2716.00.01', 'Energía eléctrica.', '13', 'KWH', '0.00', '0.00') - ,('28011001', '2801.10.01', 'Cloro.', '1', 'Kg', '0.00', '0.00') - ,('28012001', '2801.20.01', 'Yodo.', '1', 'Kg', '0.00', '0.00') - ,('28013001', '2801.30.01', 'Flúor; bromo.', '1', 'Kg', '0.00', '0.00') - ,('28020001', '2802.00.01', 'Azufre sublimado o precipitado; azufre coloidal.', '1', 'Kg', '0.00', '0.00') - ,('28030002', '2803.00.02', 'Negro de humo de hornos.', '1', 'Kg', '25.00', '0.00') - ,('28030099', '2803.00.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('28041001', '2804.10.01', 'Hidrógeno.', '8', 'L', '0.00', '0.00') - ,('28042101', '2804.21.01', 'Argón.', '8', 'L', '0.00', '0.00') - ,('28042999', '2804.29.99', 'Los demás.', '8', 'L', '0.00', '0.00') - ,('28043001', '2804.30.01', 'Nitrógeno.', '8', 'L', '0.00', '0.00') - ,('28044001', '2804.40.01', 'Oxígeno.', '8', 'L', '0.00', '0.00') - ,('28045001', '2804.50.01', 'Boro; telurio.', '1', 'Kg', '0.00', '0.00') - ,('28046101', '2804.61.01', 'Con un contenido de silicio superior o igual al 99.99% en peso.', '1', 'Kg', '0.00', '0.00') - ,('28046999', '2804.69.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('28047004', '2804.70.04', 'Fósforo.', '1', 'Kg', '0.00', '0.00') - ,('28048001', '2804.80.01', 'Arsénico.', '1', 'Kg', '0.00', '0.00') - ,('28049001', '2804.90.01', 'Selenio.', '1', 'Kg', '0.00', '0.00') - ,('28051101', '2805.11.01', 'Sodio.', '1', 'Kg', '0.00', '0.00') - ,('28051201', '2805.12.01', 'Calcio.', '1', 'Kg', '0.00', '0.00') - ,('28051999', '2805.19.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('28053001', '2805.30.01', 'Metales de las tierras raras, escandio e itrio, incluso mezclados o aleados entre sí.', '1', 'Kg', '0.00', '0.00') - ,('28054001', '2805.40.01', 'Mercurio.', '1', 'Kg', '0.00', '0.00') - ,('28061001', '2806.10.01', 'Cloruro de hidrógeno (ácido clorhídrico).', '1', 'Kg', '0.00', '0.00') - ,('28062001', '2806.20.01', 'Ácido clorosulfúrico.', '1', 'Kg', '0.00', '0.00') - ,('28070001', '2807.00.01', 'Ácido sulfúrico; oleum.', '1', 'Kg', '5.00', '0.00') - ,('28080001', '2808.00.01', 'Ácido nítrico; ácidos sulfonítricos.', '1', 'Kg', '0.00', '0.00') - ,('28091001', '2809.10.01', 'Pentóxido de difósforo.', '1', 'Kg', '0.00', '0.00') - ,('28092001', '2809.20.01', 'Ácido fosfórico (ácido ortofosfórico).', '1', 'Kg', '7.00', '0.00') - ,('28092099', '2809.20.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('28100002', '2810.00.02', 'Óxidos de boro; ácidos bóricos.', '1', 'Kg', '0.00', '0.00') - ,('28111101', '2811.11.01', 'Fluoruro de hidrógeno (ácido fluorhídrico), grado técnico.', '1', 'Kg', '7.00', '0.00') - ,('28111199', '2811.11.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('28111201', '2811.12.01', 'Cianuro de hidrógeno (ácido cianhídrico).', '1', 'Kg', '0.00', '0.00') - ,('28111999', '2811.19.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('28112103', '2811.21.03', 'Dióxido de carbono.', '1', 'Kg', '0.00', '0.00') - ,('28112203', '2811.22.03', 'Dióxido de silicio, incluso la gel de sílice (sílica gel), grado farmacéutico.', '1', 'Kg', '0.00', '0.00') - ,('28112999', '2811.29.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('28121101', '2812.11.01', 'Dicloruro de carbonilo (fosgeno).', '1', 'Kg', '0.00', '0.00') - ,('28121201', '2812.12.01', 'Oxicloruro de fósforo.', '1', 'Kg', '0.00', '0.00') - ,('28121301', '2812.13.01', 'Tricloruro de fósforo.', '1', 'Kg', '0.00', '0.00') - ,('28121401', '2812.14.01', 'Pentacloruro de fósforo.', '1', 'Kg', '0.00', '0.00') - ,('28121501', '2812.15.01', 'Monocloruro de azufre.', '1', 'Kg', '0.00', '0.00') - ,('28121601', '2812.16.01', 'Dicloruro de azufre.', '1', 'Kg', '0.00', '0.00') - ,('28121701', '2812.17.01', 'Cloruro de tionilo.', '1', 'Kg', '0.00', '0.00') - ,('28121999', '2812.19.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('28129099', '2812.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('28131001', '2813.10.01', 'Disulfuro de carbono.', '1', 'Kg', '0.00', '0.00') - ,('28139099', '2813.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('28141001', '2814.10.01', 'Amoníaco anhidro.', '8', 'L', '0.00', '0.00') - ,('28142001', '2814.20.01', 'Amoníaco en disolución acuosa.', '1', 'Kg', '0.00', '0.00') - ,('28151101', '2815.11.01', 'Sólido.', '1', 'Kg', '5.00', '0.00') - ,('28151201', '2815.12.01', 'En disolución acuosa (lejía de sosa o soda cáustica).', '1', 'Kg', '0.00', '0.00') - ,('28152003', '2815.20.03', 'Hidróxido de potasio sólido.', '1', 'Kg', '0.00', '0.00') - ,('28153001', '2815.30.01', 'Peróxidos de sodio o de potasio.', '1', 'Kg', '0.00', '0.00') - ,('28161001', '2816.10.01', 'Hidróxido y peróxido de magnesio.', '1', 'Kg', '7.00', '0.00') - ,('28164003', '2816.40.03', 'Óxidos, hidróxidos y peróxidos, de estroncio o de bario.', '1', 'Kg', '0.00', '0.00') - ,('28170001', '2817.00.01', 'Óxido de cinc.', '1', 'Kg', '5.00', '0.00') - ,('28170002', '2817.00.02', 'Peróxido de cinc.', '1', 'Kg', '0.00', '0.00') - ,('28181003', '2818.10.03', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('28182002', '2818.20.02', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('28183003', '2818.30.03', 'Hidróxido de aluminio, grado farmacéutico.', '1', 'Kg', '0.00', '0.00') - ,('28191001', '2819.10.01', 'Trióxido de cromo.', '1', 'Kg', '0.00', '0.00') - ,('28199099', '2819.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('28201002', '2820.10.02', 'Dióxido de manganeso.', '1', 'Kg', '0.00', '0.00') - ,('28209099', '2820.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('28211004', '2821.10.04', 'Óxido férrico magnético, con coercitividad superior a 300 oersteds.', '1', 'Kg', '0.00', '0.00') - ,('28212001', '2821.20.01', 'Tierras colorantes.', '1', 'Kg', '0.00', '0.00') - ,('28220001', '2822.00.01', 'Óxidos e hidróxidos de cobalto; óxidos de cobalto comerciales.', '1', 'Kg', '0.00', '0.00') - ,('28230001', '2823.00.01', 'Óxidos de titanio.', '1', 'Kg', '7.00', '0.00') - ,('28241001', '2824.10.01', 'Monóxido de plomo (litargirio, masicote).', '1', 'Kg', '0.00', '0.00') - ,('28249099', '2824.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('28251002', '2825.10.02', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('28252001', '2825.20.01', 'Óxido e hidróxido de litio.', '1', 'Kg', '0.00', '0.00') - ,('28253001', '2825.30.01', 'Óxidos e hidróxidos de vanadio.', '1', 'Kg', '0.00', '0.00') - ,('28254002', '2825.40.02', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('28255003', '2825.50.03', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('28256001', '2825.60.01', 'Óxidos de germanio y dióxido de circonio.', '1', 'Kg', '0.00', '0.00') - ,('28257001', '2825.70.01', 'Óxidos e hidróxidos de molibdeno.', '1', 'Kg', '0.00', '0.00') - ,('28258001', '2825.80.01', 'Óxidos de antimonio.', '1', 'Kg', '0.00', '0.00') - ,('28259099', '2825.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('28261201', '2826.12.01', 'De aluminio.', '1', 'Kg', '0.00', '0.00') - ,('28261999', '2826.19.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('28263001', '2826.30.01', 'Hexafluoroaluminato de sodio (criolita sintética).', '1', 'Kg', '0.00', '0.00') - ,('28269099', '2826.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('28271001', '2827.10.01', 'Cloruro de amonio.', '1', 'Kg', '0.00', '0.00') - ,('28272001', '2827.20.01', 'Cloruro de calcio.', '1', 'Kg', '35.00', '0.00') - ,('28273101', '2827.31.01', 'De magnesio.', '1', 'Kg', '0.00', '0.00') - ,('28273201', '2827.32.01', 'De aluminio.', '1', 'Kg', '0.00', '0.00') - ,('28273501', '2827.35.01', 'De níquel.', '1', 'Kg', '0.00', '0.00') - ,('28273999', '2827.39.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('28274102', '2827.41.02', 'De cobre.', '1', 'Kg', '0.00', '0.00') - ,('28274999', '2827.49.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('28275101', '2827.51.01', 'Bromuros de sodio o potasio.', '1', 'Kg', '0.00', '0.00') - ,('28275999', '2827.59.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('28276001', '2827.60.01', 'Yoduros y oxiyoduros.', '1', 'Kg', '0.00', '0.00') - ,('28281001', '2828.10.01', 'Hipoclorito de calcio comercial y demás hipocloritos de calcio.', '1', 'Kg', '0.00', '0.00') - ,('28289099', '2828.90.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('28291103', '2829.11.03', 'Clorato de sodio, grado reactivo.', '1', 'Kg', '0.00', '0.00') - ,('28291999', '2829.19.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('28299001', '2829.90.01', 'Percloratos de hierro o de potasio.', '1', 'Kg', '7.00', '0.00') - ,('28299099', '2829.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('28301001', '2830.10.01', 'Sulfuros de sodio.', '1', 'Kg', '0.00', '0.00') - ,('28309099', '2830.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('28311001', '2831.10.01', 'De sodio.', '1', 'Kg', '0.00', '0.00') - ,('28319099', '2831.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('28321002', '2832.10.02', 'Los demás.', '1', 'Kg', '7.00', '0.00') - ,('28322091', '2832.20.91', 'Los demás sulfitos.', '1', 'Kg', '0.00', '0.00') - ,('28323001', '2832.30.01', 'Tiosulfatos.', '1', 'Kg', '7.00', '0.00') - ,('28331101', '2833.11.01', 'Sulfato de disodio.', '1', 'Kg', '5.00', '0.00') - ,('28331999', '2833.19.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('28332101', '2833.21.01', 'De magnesio.', '1', 'Kg', '7.00', '0.00') - ,('28332201', '2833.22.01', 'De aluminio.', '1', 'Kg', '0.00', '0.00') - ,('28332401', '2833.24.01', 'De níquel.', '1', 'Kg', '0.00', '0.00') - ,('28332501', '2833.25.01', 'De cobre, excepto lo comprendido en la fracción arancelaria 2833.25.02.', '1', 'Kg', '5.00', '0.00') - ,('28332502', '2833.25.02', 'Sulfato tetraminocúprico.', '1', 'Kg', '0.00', '0.00') - ,('28332701', '2833.27.01', 'De bario.', '1', 'Kg', '0.00', '0.00') - ,('28332903', '2833.29.03', 'Sulfato de talio.', '22', 'PROH', '0.00', '0.00') - ,('28332905', '2833.29.05', 'De cinc.', '1', 'Kg', '7.00', '0.00') - ,('28332999', '2833.29.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('28333001', '2833.30.01', 'Alumbres.', '1', 'Kg', '0.00', '0.00') - ,('28334003', '2833.40.03', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('28341002', '2834.10.02', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('28342101', '2834.21.01', 'De potasio.', '1', 'Kg', '0.00', '0.00') - ,('28342901', '2834.29.01', 'Subnitrato de bismuto.', '1', 'Kg', '0.00', '0.00') - ,('28342999', '2834.29.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('28351001', '2835.10.01', 'Fosfinatos (hipofosfitos) y fosfonatos (fosfitos).', '1', 'Kg', '0.00', '0.00') - ,('28352201', '2835.22.01', 'De monosodio o de disodio.', '1', 'Kg', '0.00', '0.00') - ,('28352401', '2835.24.01', 'De potasio.', '1', 'Kg', '0.00', '0.00') - ,('28352502', '2835.25.02', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('28352691', '2835.26.91', 'Los demás fosfatos de calcio.', '1', 'Kg', '0.00', '0.00') - ,('28352999', '2835.29.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('28353101', '2835.31.01', 'Trifosfato de sodio (tripolifosfato de sodio).', '1', 'Kg', '7.00', '0.00') - ,('28353901', '2835.39.01', 'Pirofosfato tetrasódico.', '1', 'Kg', '7.00', '0.00') - ,('28353902', '2835.39.02', 'Hexametafosfato de sodio.', '1', 'Kg', '7.00', '0.00') - ,('28353999', '2835.39.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('28362001', '2836.20.01', 'Carbonato de disodio.', '1', 'Kg', '35.00', '0.00') - ,('28363001', '2836.30.01', 'Hidrogenocarbonato (bicarbonato) de sodio.', '1', 'Kg', '35.00', '0.00') - ,('28364003', '2836.40.03', 'Bicarbonato de potasio.', '1', 'Kg', '7.00', '0.00') - ,('28365001', '2836.50.01', 'Carbonato de calcio.', '1', 'Kg', '5.00', '0.00') - ,('28366001', '2836.60.01', 'Carbonato de bario.', '1', 'Kg', '0.00', '0.00') - ,('28369101', '2836.91.01', 'Carbonatos de litio.', '1', 'Kg', '0.00', '0.00') - ,('28369201', '2836.92.01', 'Carbonato de estroncio.', '1', 'Kg', '0.00', '0.00') - ,('28369999', '2836.99.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('28371102', '2837.11.02', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('28371999', '2837.19.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('28372002', '2837.20.02', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('28391101', '2839.11.01', 'Metasilicatos.', '1', 'Kg', '7.00', '0.00') - ,('28391999', '2839.19.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('28399004', '2839.90.04', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('28401101', '2840.11.01', 'Anhidro.', '1', 'Kg', '0.00', '0.00') - ,('28401999', '2840.19.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('28402091', '2840.20.91', 'Los demás boratos.', '1', 'Kg', '0.00', '0.00') - ,('28403001', '2840.30.01', 'Peroxoboratos (perboratos).', '1', 'Kg', '0.00', '0.00') - ,('28413001', '2841.30.01', 'Dicromato de sodio.', '1', 'Kg', '0.00', '0.00') - ,('28415091', '2841.50.91', 'Los demás cromatos y dicromatos; peroxocromatos.', '1', 'Kg', '0.00', '0.00') - ,('28416101', '2841.61.01', 'Permanganato de potasio.', '1', 'Kg', '0.00', '0.00') - ,('28416999', '2841.69.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('28417001', '2841.70.01', 'Molibdatos.', '1', 'Kg', '0.00', '0.00') - ,('28418001', '2841.80.01', 'Volframatos (tungstatos).', '1', 'Kg', '0.00', '0.00') - ,('28419099', '2841.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('28421002', '2842.10.02', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('28429099', '2842.90.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('28431001', '2843.10.01', 'Metal precioso en estado coloidal.', '1', 'Kg', '0.00', '0.00') - ,('28432101', '2843.21.01', 'Nitrato de plata.', '1', 'Kg', '0.00', '0.00') - ,('28432999', '2843.29.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('28433001', '2843.30.01', 'Compuestos de oro.', '1', 'Kg', '0.00', '0.00') - ,('28439091', '2843.90.91', 'Los demás compuestos; amalgamas.', '1', 'Kg', '0.00', '0.00') - ,('28441001', '2844.10.01', 'Uranio natural y sus compuestos; aleaciones, dispersiones (incluido el cermet), productos cerámicos y mezclas, que contengan uranio natural o compuestos de uranio natural.', '1', 'Kg', '0.00', '0.00') - ,('28442001', '2844.20.01', 'Uranio enriquecido en U 235 y sus compuestos; plutonio y sus compuestos; aleaciones, dispersiones (incluido el cermet), productos cerámicos y mezclas, que contengan uranio enriquecido en U 235, plutonio o compuestos de estos productos.', '1', 'Kg', '0.00', '0.00') - ,('28443001', '2844.30.01', 'Uranio empobrecido en U 235 y sus compuestos; torio y sus compuestos; aleaciones, dispersiones (incluido el cermet), productos cerámicos y mezclas, que contengan uranio empobrecido en U 235, torio o compuestos de estos productos.', '1', 'Kg', '0.00', '0.00') - ,('28444101', '2844.41.01', 'Tritio y sus compuestos; aleaciones, dispersiones (incluido el cermet), productos cerámicos y mezclas, que contengan tritio o sus compuestos.', '1', 'Kg', '0.00', '0.00') - ,('28444201', '2844.42.01', 'Actinio-225, actinio-227, californio-253, curio-240, curio-241, curio-242, curio-243, curio-244, einstenio-253, einstenio-254, gadolinio-148, polonio-208, polonio-209, polonio-210, radio-223, uranio-230 o uranio-232, y sus compuestos; aleaciones, dispersiones (incluido el cermet), productos cerámicos y mezclas, que contengan estos elementos o compuestos.', '1', 'Kg', '0.00', '0.00') - ,('28444391', '2844.43.91', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('28444401', '2844.44.01', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('28445001', '2844.50.01', 'Elementos combustibles (cartuchos) agotados (irradiados) de reactores nucleares.', '1', 'Kg', '0.00', '0.00') - ,('28451001', '2845.10.01', 'Agua pesada (óxido de deuterio).', '1', 'Kg', '0.00', '0.00') - ,('28452001', '2845.20.01', 'Boro enriquecido en boro-10 y sus compuestos.', '1', 'Kg', '0.00', '0.00') - ,('28453001', '2845.30.01', 'Litio enriquecido en litio-6 y sus compuestos.', '1', 'Kg', '0.00', '0.00') - ,('28454001', '2845.40.01', 'Helio-3.', '1', 'Kg', '0.00', '0.00') - ,('28459099', '2845.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('28461001', '2846.10.01', 'Compuestos de cerio.', '1', 'Kg', '0.00', '0.00') - ,('28469099', '2846.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('28470001', '2847.00.01', 'Peróxido de hidrógeno (agua oxigenada), incluso solidificado con urea.', '1', 'Kg', '0.00', '0.00') - ,('28491001', '2849.10.01', 'De calcio.', '1', 'Kg', '0.00', '0.00') - ,('28492099', '2849.20.99', 'De silicio.', '1', 'Kg', '0.00', '0.00') - ,('28499099', '2849.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('28500003', '2850.00.03', 'Hidruros, nitruros, aziduros (azidas), siliciuros y boruros, aunque no sean de constitución química definida, excepto los compuestos que consistan igualmente en carburos de la partida 28.49.', '1', 'Kg', '0.00', '0.00') - ,('28521004', '2852.10.04', 'De constitución química definida.', '1', 'Kg', '0.00', '0.00') - ,('28529099', '2852.90.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('28531001', '2853.10.01', 'Cloruro de cianógeno ("chlorcyan").', '1', 'Kg', '0.00', '0.00') - ,('28539099', '2853.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29011005', '2901.10.05', 'Los demás.', '8', 'L', '0.00', '0.00') - ,('29012101', '2901.21.01', 'Etileno.', '1', 'Kg', '0.00', '0.00') - ,('29012201', '2901.22.01', 'Propeno (propileno).', '1', 'Kg', '0.00', '0.00') - ,('29012301', '2901.23.01', 'Buteno (butileno) y sus isómeros.', '1', 'Kg', '0.00', '0.00') - ,('29012401', '2901.24.01', 'Buta-1,3-dieno e isopreno.', '1', 'Kg', '0.00', '0.00') - ,('29012999', '2901.29.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29021101', '2902.11.01', 'Ciclohexano.', '1', 'Kg', '0.00', '0.00') - ,('29021999', '2902.19.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29022001', '2902.20.01', 'Benceno.', '8', 'L', '0.00', '0.00') - ,('29023001', '2902.30.01', 'Tolueno.', '8', 'L', '0.00', '0.00') - ,('29024101', '2902.41.01', 'o-Xileno.', '8', 'L', '0.00', '0.00') - ,('29024201', '2902.42.01', 'm-Xileno.', '8', 'L', '0.00', '0.00') - ,('29024301', '2902.43.01', 'p-Xileno.', '8', 'L', '0.00', '0.00') - ,('29024401', '2902.44.01', 'Mezclas de isómeros del xileno.', '8', 'L', '0.00', '0.00') - ,('29025001', '2902.50.01', 'Estireno.', '1', 'Kg', '0.00', '0.00') - ,('29026001', '2902.60.01', 'Etilbenceno.', '1', 'Kg', '0.00', '0.00') - ,('29027001', '2902.70.01', 'Cumeno.', '1', 'Kg', '0.00', '0.00') - ,('29029099', '2902.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29031101', '2903.11.01', 'Clorometano (cloruro de metilo) y cloroetano (cloruro de etilo).', '1', 'Kg', '0.00', '0.00') - ,('29031201', '2903.12.01', 'Diclorometano (cloruro de metileno).', '1', 'Kg', '0.00', '0.00') - ,('29031302', '2903.13.02', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29031401', '2903.14.01', 'Tetracloruro de carbono.', '1', 'Kg', '0.00', '0.00') - ,('29031501', '2903.15.01', 'Dicloruro de etileno (ISO) (1,2-dicloroetano).', '1', 'Kg', '0.00', '0.00') - ,('29031999', '2903.19.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29032101', '2903.21.01', 'Cloruro de vinilo (cloroetileno).', '1', 'Kg', '0.00', '0.00') - ,('29032201', '2903.22.01', 'Tricloroetileno.', '1', 'Kg', '0.00', '0.00') - ,('29032301', '2903.23.01', 'Tetracloroetileno (percloroetileno).', '1', 'Kg', '0.00', '0.00') - ,('29032999', '2903.29.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29034101', '2903.41.01', 'Trifluorometano (HFC-23).', '1', 'Kg', '0.00', '0.00') - ,('29034201', '2903.42.01', 'Difluorometano (HFC-32).', '1', 'Kg', '0.00', '0.00') - ,('29034301', '2903.43.01', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29034401', '2903.44.01', 'Pentafluoroetano (HFC-125), 1,1,1-trifluoroetano (HFC-143a) y 1,1,2-trifluoroetano (HFC-143).', '1', 'Kg', '0.00', '0.00') - ,('29034501', '2903.45.01', '1,1,1,2-Tetrafluoroetano (HFC-134a) y 1,1,2,2-tetrafluoroetano (HFC-134).', '1', 'Kg', '0.00', '0.00') - ,('29034601', '2903.46.01', '1,1,1,2,3,3,3-Heptafluoropropano (HFC-227ea), 1,1,1,2,2,3-hexafluoropropano (HFC-236cb), 1,1,1,2,3,3-hexafluoropropano (HFC-236ea) y 1,1,1,3,3,3-hexafluoropropano (HFC-236fa).', '1', 'Kg', '0.00', '0.00') - ,('29034701', '2903.47.01', '1,1,1,3,3-Pentafluoropropano (HFC-245fa) y 1,1,2,2,3-pentafluoropropano (HFC-245ca).', '1', 'Kg', '0.00', '0.00') - ,('29034801', '2903.48.01', '1,1,1,3,3-Pentafluorobutano (HFC-365mfc) y 1,1,1,2,2,3,4,5,5,5-decafluoropentano (HFC-43-10mee).', '1', 'Kg', '0.00', '0.00') - ,('29034999', '2903.49.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29035101', '2903.51.01', '2,3,3,3-Tetrafluoropropeno (HFO-1234yf), 1,3,3,3-tetrafluoropropeno (HFO-1234ze) y (Z)-1,1,1,4,4,4-hexafluoro-2-buteno (HFO-1336mzz).', '1', 'Kg', '0.00', '0.00') - ,('29035999', '2903.59.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29036101', '2903.61.01', 'Bromuro de metilo (bromometano).', '1', 'Kg', '0.00', '0.00') - ,('29036201', '2903.62.01', 'Dibromuro de etileno (ISO) (1,2-dibromoetano).', '1', 'Kg', '0.00', '0.00') - ,('29036999', '2903.69.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29037101', '2903.71.01', 'Clorodifluorometano (HCFC-22).', '8', 'L', '0.00', '0.00') - ,('29037201', '2903.72.01', 'Diclorotrifluoroetanos (HCFC-123).', '8', 'L', '0.00', '0.00') - ,('29037301', '2903.73.01', 'Diclorofluoroetanos (HCFC-141, 141b).', '8', 'L', '0.00', '0.00') - ,('29037401', '2903.74.01', 'Clorodifluoroetanos (HCFC-142, 142b).', '8', 'L', '0.00', '0.00') - ,('29037501', '2903.75.01', 'Dicloropentafluoropropanos (HCFC-225, 225ca, 225cb).', '8', 'L', '0.00', '0.00') - ,('29037601', '2903.76.01', 'Bromoclorodifluorometano (Halón-1211), bromotrifluorometano (Halón-1301) y dibromotetrafluoroetanos (Halón-2402).', '8', 'L', '0.00', '0.00') - ,('29037791', '2903.77.91', 'Los demás, perhalogenados solamente con flúor y cloro.', '8', 'L', '0.00', '0.00') - ,('29037891', '2903.78.91', 'Los demás derivados perhalogenados.', '8', 'L', '0.00', '0.00') - ,('29037999', '2903.79.99', 'Los demás.', '8', 'L', '0.00', '0.00') - ,('29038104', '2903.81.04', '1,2,3,4,5,6-Hexaclorociclohexano (HCH (ISO)), incluido el lindano (ISO, DCI).', '22', 'PROH', '0.00', '0.00') - ,('29038202', '2903.82.02', '1,4,5,6,7,8,8-Heptacloro-3a,4,7,7atetrahidro-4,7-metanoindeno (Heptacloro).', '22', 'PROH', '0.00', '0.00') - ,('29038204', '2903.82.04', '1,2,4,5,6,7,8,8-Octacloro-3-alfa,4,7,7-alfa tetrahidro-4,7- metanoindeno (Clordano).', '22', 'PROH', '0.00', '0.00') - ,('29038299', '2903.82.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29038301', '2903.83.01', 'Mirex (ISO).', '1', 'Kg', '0.00', '0.00') - ,('29038903', '2903.89.03', '1,2,3,4,10,10-Hexacloro-1,4,4a,5,8,8a-hexahidro-endo-endo-1,4:5,8-dimetanonaftaleno (Isodrin).', '22', 'PROH', '0.00', '0.00') - ,('29038999', '2903.89.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29039101', '2903.91.01', 'Clorobenceno, o-diclorobenceno y p-diclorobenceno.', '1', 'Kg', '0.00', '0.00') - ,('29039201', '2903.92.01', 'Hexaclorobenceno (ISO).', '1', 'Kg', '0.00', '0.00') - ,('29039204', '2903.92.04', '1,1,1-tricloro-2,2-bis(p-clorofenil)etano, DDT (ISO), clofenotano (DCI)).', '22', 'PROH', '0.00', '0.00') - ,('29039301', '2903.93.01', 'Pentaclorobenceno (ISO).', '1', 'Kg', '0.00', '0.00') - ,('29039401', '2903.94.01', 'Hexabromobifenilos.', '1', 'Kg', '0.00', '0.00') - ,('29039901', '2903.99.01', 'Cloruro de bencilo.', '1', 'Kg', '0.00', '0.00') - ,('29039999', '2903.99.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29041001', '2904.10.01', 'Butilnaftalensulfonato de sodio.', '1', 'Kg', '0.00', '0.00') - ,('29041002', '2904.10.02', '1,3,6-Naftalentrisulfonato trisódico.', '1', 'Kg', '0.00', '0.00') - ,('29041003', '2904.10.03', 'Alil o metalilsulfonato de sodio.', '1', 'Kg', '0.00', '0.00') - ,('29041004', '2904.10.04', '6,7-Dihidro-2-naftalensulfonato de sodio.', '1', 'Kg', '0.00', '0.00') - ,('29041005', '2904.10.05', 'Ácido p-toluensulfónico.', '1', 'Kg', '0.00', '0.00') - ,('29041099', '2904.10.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('29042008', '2904.20.08', 'Nitroetano.', '1', 'Kg', '0.00', '0.00') - ,('29042099', '2904.20.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29043101', '2904.31.01', 'Ácido perfluorooctano sulfónico.', '1', 'Kg', '0.00', '0.00') - ,('29043201', '2904.32.01', 'Perfluorooctano sulfonato de amonio.', '1', 'Kg', '0.00', '0.00') - ,('29043301', '2904.33.01', 'Perfluorooctano sulfonato de litio.', '1', 'Kg', '0.00', '0.00') - ,('29043401', '2904.34.01', 'Perfluorooctano sulfonato de potasio.', '1', 'Kg', '0.00', '0.00') - ,('29043591', '2904.35.91', 'Las demás sales del ácido perfluorooctano sulfónico.', '1', 'Kg', '0.00', '0.00') - ,('29043601', '2904.36.01', 'Fluoruro de perfluorooctano sulfonilo.', '1', 'Kg', '0.00', '0.00') - ,('29049101', '2904.91.01', 'Tricloronitrometano (cloropicrina).', '1', 'Kg', '0.00', '0.00') - ,('29049999', '2904.99.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29051101', '2905.11.01', 'Metanol (alcohol metílico).', '8', 'L', '0.00', '0.00') - ,('29051202', '2905.12.02', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29051301', '2905.13.01', 'Butan-1-ol (alcohol n-butílico).', '1', 'Kg', '0.00', '0.00') - ,('29051491', '2905.14.91', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29051603', '2905.16.03', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29051701', '2905.17.01', 'Dodecan-1-ol (alcohol laurílico), hexadecan-1-ol (alcohol cetílico) y octadecan-1-ol (alcohol estearílico).', '1', 'Kg', '0.00', '0.00') - ,('29051906', '2905.19.06', 'Metil isobutil carbinol.', '1', 'Kg', '7.00', '0.00') - ,('29051999', '2905.19.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29052206', '2905.22.06', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29052999', '2905.29.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29053101', '2905.31.01', 'Etilenglicol (etanodiol).', '1', 'Kg', '5.00', '0.00') - ,('29053201', '2905.32.01', 'Propilenglicol (propano-1,2-diol).', '1', 'Kg', '7.00', '0.00') - ,('29053901', '2905.39.01', 'Butilenglicol.', '1', 'Kg', '5.00', '0.00') - ,('29053902', '2905.39.02', '2-Metil-2,4-pentanodiol.', '1', 'Kg', '5.00', '0.00') - ,('29053999', '2905.39.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29054101', '2905.41.01', '2-Etil-2-(hidroximetil)propano-1,3-diol (trimetilolpropano).', '1', 'Kg', '0.00', '0.00') - ,('29054201', '2905.42.01', 'Pentaeritritol (pentaeritrita).', '1', 'Kg', '0.00', '0.00') - ,('29054301', '2905.43.01', 'Manitol.', '1', 'Kg', '0.00', '0.00') - ,('29054401', '2905.44.01', 'D-glucitol (sorbitol).', '1', 'Kg', '5.00', '0.00') - ,('29054501', '2905.45.01', 'Glicerol, excepto grado dinamita.', '1', 'Kg', '15.00', '0.00') - ,('29054599', '2905.45.99', 'Los demás.', '1', 'Kg', '7.00', '0.00') - ,('29054999', '2905.49.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29055101', '2905.51.01', 'Etclorvinol (DCI).', '1', 'Kg', '0.00', '0.00') - ,('29055999', '2905.59.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29061101', '2906.11.01', 'Mentol.', '1', 'Kg', '0.00', '0.00') - ,('29061202', '2906.12.02', 'Ciclohexanol, metilciclohexanoles y dimetilciclohexanoles.', '1', 'Kg', '0.00', '0.00') - ,('29061399', '2906.13.99', 'Esteroles e inositoles.', '1', 'Kg', '0.00', '0.00') - ,('29061999', '2906.19.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29062101', '2906.21.01', 'Alcohol bencílico.', '1', 'Kg', '0.00', '0.00') - ,('29062905', '2906.29.05', 'Feniletanol.', '1', 'Kg', '7.00', '0.00') - ,('29062999', '2906.29.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29071102', '2907.11.02', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29071202', '2907.12.02', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29071303', '2907.13.03', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29071503', '2907.15.03', 'Naftoles y sus sales.', '1', 'Kg', '0.00', '0.00') - ,('29071999', '2907.19.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29072101', '2907.21.01', 'Resorcinol y sus sales.', '1', 'Kg', '0.00', '0.00') - ,('29072201', '2907.22.01', 'Hidroquinona.', '1', 'Kg', '7.00', '0.00') - ,('29072202', '2907.22.02', 'Sales de la hidroquinona.', '1', 'Kg', '0.00', '0.00') - ,('29072999', '2907.29.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29081101', '2908.11.01', 'Pentaclorofenol (ISO).', '1', 'Kg', '0.00', '0.00') - ,('29081999', '2908.19.99', 'Éter isopropílico.', '1', 'Kg', '0.00', '0.00') - ,('29089101', '2908.91.01', 'Dinoseb (ISO) y sus sales.', '1', 'Kg', '0.00', '0.00') - ,('29089201', '2908.92.01', '4,6-Dinitro-o-cresol (DNOC (ISO)) y sus sales.', '1', 'Kg', '0.00', '0.00') - ,('29089901', '2908.99.01', 'p-Nitrofenol y su sal de sodio.', '1', 'Kg', '7.00', '0.00') - ,('29089999', '2908.99.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29091101', '2909.11.01', 'Éter dietílico (óxido de dietilo).', '1', 'Kg', '0.00', '0.00') - ,('29091999', '2909.19.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29092001', '2909.20.01', 'Éteres ciclánicos, ciclénicos, cicloterpénicos, y sus derivados halogenados, sulfonados, nitrados o nitrosados.', '1', 'Kg', '0.00', '0.00') - ,('29093010', '2909.30.10', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29094301', '2909.43.01', 'Éteres monobutílicos del etilenglicol o del dietilenglicol.', '1', 'Kg', '7.00', '0.00') - ,('29094401', '2909.44.01', 'Éter monoetílico del etilenglicol o del dietilenglicol.', '1', 'Kg', '7.00', '0.00') - ,('29094403', '2909.44.03', 'Éteres monometílicos del etilenglicol o del dietilenglicol.', '1', 'Kg', '7.00', '0.00') - ,('29094499', '2909.44.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29094901', '2909.49.01', 'Éter monometílico, monoetílico o monobutílico del trietilenglicol.', '1', 'Kg', '7.00', '0.00') - ,('29094902', '2909.49.02', '3-(2-Metilfenoxi)-1,2-propanodiol (mefenesina).', '1', 'Kg', '0.00', '0.00') - ,('29094903', '2909.49.03', 'Trietilenglicol.', '1', 'Kg', '7.00', '0.00') - ,('29094904', '2909.49.04', 'Éter monofenílico del etilenglicol (Fenoxietanol).', '1', 'Kg', '0.00', '0.00') - ,('29094905', '2909.49.05', 'Alcohol 3-fenoxibencílico.', '1', 'Kg', '0.00', '0.00') - ,('29094906', '2909.49.06', 'Dipropilenglicol.', '1', 'Kg', '7.00', '0.00') - ,('29094907', '2909.49.07', 'Tetraetilenglicol.', '1', 'Kg', '0.00', '0.00') - ,('29094908', '2909.49.08', 'Tripropilenglicol.', '1', 'Kg', '7.00', '0.00') - ,('29094909', '2909.49.09', '1,2-Dihidroxi- 3-(2-metoxifenoxi)propano (Guayacolato de glicerilo).', '1', 'Kg', '0.00', '0.00') - ,('29094999', '2909.49.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('29095001', '2909.50.01', 'Éter monometílico de la hidroquinona.', '1', 'Kg', '7.00', '0.00') - ,('29095099', '2909.50.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29096001', '2909.60.01', 'Peróxido de laurilo.', '1', 'Kg', '0.00', '0.00') - ,('29096002', '2909.60.02', 'Hidroperóxido de cumeno.', '1', 'Kg', '0.00', '0.00') - ,('29096003', '2909.60.03', 'Hidroxioxianisol.', '1', 'Kg', '0.00', '0.00') - ,('29096004', '2909.60.04', 'Peróxido de dicumilo.', '1', 'Kg', '0.00', '0.00') - ,('29096005', '2909.60.05', 'Hidroperóxido de paramentano.', '1', 'Kg', '0.00', '0.00') - ,('29096006', '2909.60.06', 'Peróxidos de acetales y hemiacetales.', '1', 'Kg', '0.00', '0.00') - ,('29096099', '2909.60.99', 'Los demás.', '1', 'Kg', '7.00', '0.00') - ,('29101001', '2910.10.01', 'Oxirano (óxido de etileno).', '1', 'Kg', '0.00', '0.00') - ,('29102001', '2910.20.01', 'Metiloxirano (óxido de propileno).', '1', 'Kg', '0.00', '0.00') - ,('29103001', '2910.30.01', '1-Cloro-2,3-epoxipropano (epiclorhidrina).', '1', 'Kg', '0.00', '0.00') - ,('29104001', '2910.40.01', 'Dieldrina (ISO, DCI).', '1', 'Kg', '0.00', '0.00') - ,('29105001', '2910.50.01', 'Endrina (ISO).', '22', 'PROH', '0.00', '0.00') - ,('29109099', '2910.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29110004', '2911.00.04', 'Acetales y hemiacetales, incluso con otras funciones oxigenadas, y sus derivados halogenados, sulfonados, nitrados o nitrosados.', '1', 'Kg', '0.00', '0.00') - ,('29121101', '2912.11.01', 'Metanal (formaldehído).', '1', 'Kg', '7.00', '0.00') - ,('29121201', '2912.12.01', 'Etanal (acetaldehído).', '1', 'Kg', '0.00', '0.00') - ,('29121905', '2912.19.05', 'Glutaraldehído.', '1', 'Kg', '7.00', '0.00') - ,('29121999', '2912.19.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29122101', '2912.21.01', 'Benzaldehído (aldehído benzoico).', '1', 'Kg', '0.00', '0.00') - ,('29122902', '2912.29.02', 'Fenilacetaldehído.', '1', 'Kg', '0.00', '0.00') - ,('29122999', '2912.29.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29124101', '2912.41.01', 'Vainillina (aldehído metilprotocatéquico).', '1', 'Kg', '0.00', '0.00') - ,('29124201', '2912.42.01', 'Etilvainillina (aldehído etilprotocatéquico).', '1', 'Kg', '0.00', '0.00') - ,('29124999', '2912.49.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29125001', '2912.50.01', 'Polímeros cíclicos de los aldehídos.', '1', 'Kg', '0.00', '0.00') - ,('29126001', '2912.60.01', 'Paraformaldehído.', '1', 'Kg', '0.00', '0.00') - ,('29130003', '2913.00.03', 'Derivados halogenados, sulfonados, nitrados o nitrosados de los productos de la partida 29.12.', '1', 'Kg', '0.00', '0.00') - ,('29141101', '2914.11.01', 'Acetona.', '1', 'Kg', '0.00', '0.00') - ,('29141201', '2914.12.01', 'Butanona (metiletilcetona).', '1', 'Kg', '0.00', '0.00') - ,('29141301', '2914.13.01', '4-Metilpentan-2-ona (metilisobutilcetona).', '1', 'Kg', '7.00', '0.00') - ,('29141902', '2914.19.02', 'Óxido de mesitilo.', '1', 'Kg', '7.00', '0.00') - ,('29141999', '2914.19.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('29142201', '2914.22.01', 'Ciclohexanona y metilciclohexanonas.', '1', 'Kg', '0.00', '0.00') - ,('29142303', '2914.23.03', 'Metiliononas.', '1', 'Kg', '0.00', '0.00') - ,('29142901', '2914.29.01', '2-Metil-5-(1-metiletenil)-2-ciclohexen-1-ona (Carvona).', '1', 'Kg', '0.00', '0.00') - ,('29142902', '2914.29.02', '3,5,5-Trimetil-2-ciclohexen-1-ona (Isoforona).', '1', 'Kg', '0.00', '0.00') - ,('29142903', '2914.29.03', '1-p-Menten-3-ona (Piperitona).', '1', 'Kg', '0.00', '0.00') - ,('29142904', '2914.29.04', 'Alcanfor.', '1', 'Kg', '0.00', '0.00') - ,('29142999', '2914.29.99', 'Las demás.', '1', 'Kg', '5.00', '0.00') - ,('29143101', '2914.31.01', 'Fenilacetona (fenilpropan-2-ona).', '1', 'Kg', '0.00', '0.00') - ,('29143999', '2914.39.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29144001', '2914.40.01', 'Acetilmetilcarbinol.', '1', 'Kg', '0.00', '0.00') - ,('29144002', '2914.40.02', 'Fenil acetil carbinol.', '1', 'Kg', '7.00', '0.00') - ,('29144099', '2914.40.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('29145004', '2914.50.04', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29146101', '2914.61.01', 'Antraquinona.', '1', 'Kg', '0.00', '0.00') - ,('29146201', '2914.62.01', 'Coenzima Q10 (ubidecarenona (DCI)).', '1', 'Kg', '0.00', '0.00') - ,('29146999', '2914.69.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('29147101', '2914.71.01', 'Clordecona (ISO).', '1', 'Kg', '0.00', '0.00') - ,('29147999', '2914.79.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29151101', '2915.11.01', 'Ácido fórmico.', '1', 'Kg', '0.00', '0.00') - ,('29151203', '2915.12.03', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29151301', '2915.13.01', 'Ésteres del ácido fórmico.', '1', 'Kg', '0.00', '0.00') - ,('29152101', '2915.21.01', 'Ácido acético.', '1', 'Kg', '0.00', '0.00') - ,('29152401', '2915.24.01', 'Anhídrido acético.', '1', 'Kg', '10.00', '0.00') - ,('29152999', '2915.29.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('29153101', '2915.31.01', 'Acetato de etilo.', '1', 'Kg', '10.00', '0.00') - ,('29153201', '2915.32.01', 'Acetato de vinilo.', '1', 'Kg', '0.00', '0.00') - ,('29153301', '2915.33.01', 'Acetato de n-butilo.', '1', 'Kg', '0.00', '0.00') - ,('29153601', '2915.36.01', 'Acetato de dinoseb (ISO).', '1', 'Kg', '0.00', '0.00') - ,('29153902', '2915.39.02', 'Acetato del éter monometílico del etilenglicol.', '1', 'Kg', '5.00', '0.00') - ,('29153903', '2915.39.03', 'Acetato de isopropilo o de metilamilo.', '1', 'Kg', '10.00', '0.00') - ,('29153904', '2915.39.04', 'Acetato de isobornilo.', '1', 'Kg', '5.00', '0.00') - ,('29153905', '2915.39.05', 'Acetato de cedrilo.', '1', 'Kg', '5.00', '0.00') - ,('29153909', '2915.39.09', 'Acetato de mentanilo.', '1', 'Kg', '5.00', '0.00') - ,('29153913', '2915.39.13', 'Acetato del éter monobutílico del etilenglicol.', '1', 'Kg', '5.00', '0.00') - ,('29153915', '2915.39.15', 'Acetato de terpenilo.', '1', 'Kg', '5.00', '0.00') - ,('29153917', '2915.39.17', 'Acetato de isobutilo.', '1', 'Kg', '5.00', '0.00') - ,('29153918', '2915.39.18', 'Acetato de 2-etoxietilo.', '1', 'Kg', '7.00', '0.00') - ,('29153999', '2915.39.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29154001', '2915.40.01', 'Ácidos mono- o dicloroacéticos y sus sales de sodio.', '1', 'Kg', '9.00', '0.00') - ,('29154099', '2915.40.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29155003', '2915.50.03', 'Propionato de terpenilo.', '1', 'Kg', '5.00', '0.00') - ,('29155099', '2915.50.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29156001', '2915.60.01', 'Ácido butanoico (Ácido butírico).', '1', 'Kg', '0.00', '0.00') - ,('29156003', '2915.60.03', 'Diisobutanoato de 2,2,4-trimetilpentanodiol.', '1', 'Kg', '0.00', '0.00') - ,('29156099', '2915.60.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('29157001', '2915.70.01', 'Estearato de isopropilo.', '1', 'Kg', '5.00', '0.00') - ,('29157002', '2915.70.02', 'Monoestearato de etilenglicol o de propilenglicol.', '1', 'Kg', '5.00', '0.00') - ,('29157003', '2915.70.03', 'Estearato de butilo.', '1', 'Kg', '5.00', '0.00') - ,('29157004', '2915.70.04', 'Monoestearato de sorbitan.', '1', 'Kg', '5.00', '0.00') - ,('29157005', '2915.70.05', 'Monoestearato de glicerilo.', '1', 'Kg', '5.00', '0.00') - ,('29157006', '2915.70.06', 'Palmitato de metilo.', '1', 'Kg', '5.00', '0.00') - ,('29157007', '2915.70.07', 'Palmitato de isopropilo.', '1', 'Kg', '5.00', '0.00') - ,('29157099', '2915.70.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29159001', '2915.90.01', 'Ácido 2-etilhexanoico.', '1', 'Kg', '0.00', '0.00') - ,('29159003', '2915.90.03', 'Ácido cáprico.', '1', 'Kg', '0.00', '0.00') - ,('29159004', '2915.90.04', 'Ácido láurico.', '1', 'Kg', '0.00', '0.00') - ,('29159005', '2915.90.05', 'Ácido mirístico.', '1', 'Kg', '0.00', '0.00') - ,('29159006', '2915.90.06', 'Ácido caproico.', '1', 'Kg', '0.00', '0.00') - ,('29159007', '2915.90.07', 'Ácido 2-propilpentanoico (Ácido valproico).', '1', 'Kg', '0.00', '0.00') - ,('29159008', '2915.90.08', 'Heptanoato de etilo.', '1', 'Kg', '0.00', '0.00') - ,('29159009', '2915.90.09', 'Laurato de metilo.', '1', 'Kg', '0.00', '0.00') - ,('29159010', '2915.90.10', 'Ácido nonanoico o isononanoico.', '1', 'Kg', '0.00', '0.00') - ,('29159011', '2915.90.11', 'Sal sódica del ácido 2-propilpentanoico (Valproato de sodio).', '1', 'Kg', '0.00', '0.00') - ,('29159012', '2915.90.12', 'Peróxido de lauroilo o de decanoilo.', '1', 'Kg', '7.00', '0.00') - ,('29159015', '2915.90.15', 'Peroxi-2-etilhexanoato de terbutilo; peroxibenzoato de terbutilo.', '1', 'Kg', '10.00', '0.00') - ,('29159016', '2915.90.16', 'Cloruro de lauroilo, de decanoilo o de isononanoilo.', '1', 'Kg', '0.00', '0.00') - ,('29159018', '2915.90.18', 'Anhídrido propiónico.', '1', 'Kg', '0.00', '0.00') - ,('29159019', '2915.90.19', 'Ácido 3-(3,5-diterbutil-4-hidroxifenil)-propiónico.', '1', 'Kg', '0.00', '0.00') - ,('29159020', '2915.90.20', 'Tetraésteres del pentaeritritol.', '1', 'Kg', '0.00', '0.00') - ,('29159022', '2915.90.22', 'Cloruro de palmitoilo.', '1', 'Kg', '0.00', '0.00') - ,('29159023', '2915.90.23', 'Di-(2-etilbutanoato) de trietilenglicol.', '1', 'Kg', '0.00', '0.00') - ,('29159024', '2915.90.24', 'Cloruro de n-pentanoilo.', '1', 'Kg', '0.00', '0.00') - ,('29159025', '2915.90.25', 'Ácido caprílico.', '1', 'Kg', '0.00', '0.00') - ,('29159026', '2915.90.26', 'Ácido behénico.', '1', 'Kg', '0.00', '0.00') - ,('29159027', '2915.90.27', 'Cloruro de pivaloilo.', '1', 'Kg', '0.00', '0.00') - ,('29159029', '2915.90.29', 'Cloropropionato de metilo.', '1', 'Kg', '0.00', '0.00') - ,('29159030', '2915.90.30', 'Ortoformiato de trietilo.', '1', 'Kg', '0.00', '0.00') - ,('29159031', '2915.90.31', 'Cloroformiato de 2-etilhexilo.', '1', 'Kg', '0.00', '0.00') - ,('29159032', '2915.90.32', 'Cloroformiato de metilo.', '1', 'Kg', '0.00', '0.00') - ,('29159033', '2915.90.33', 'Cloroformiato de bencilo.', '1', 'Kg', '0.00', '0.00') - ,('29159099', '2915.90.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('29161101', '2916.11.01', 'Ácido acrílico y sus sales.', '1', 'Kg', '5.00', '0.00') - ,('29161201', '2916.12.01', 'Acrilato de metilo o de etilo.', '1', 'Kg', '6.00', '0.00') - ,('29161202', '2916.12.02', 'Acrilato de butilo.', '1', 'Kg', '0.00', '0.00') - ,('29161203', '2916.12.03', 'Acrilato de 2-etilhexilo.', '1', 'Kg', '3.00', '0.00') - ,('29161299', '2916.12.99', 'Los demás.', '1', 'Kg', '7.00', '0.00') - ,('29161301', '2916.13.01', 'Ácido metacrílico y sus sales.', '1', 'Kg', '0.00', '0.00') - ,('29161401', '2916.14.01', 'Metacrilato de metilo.', '1', 'Kg', '0.00', '0.00') - ,('29161402', '2916.14.02', 'Metacrilato de etilo o de butilo.', '1', 'Kg', '7.00', '0.00') - ,('29161403', '2916.14.03', 'Metacrilato de 2-hidroxipropilo.', '1', 'Kg', '7.00', '0.00') - ,('29161404', '2916.14.04', 'Dimetacrilato de etilenglicol.', '1', 'Kg', '7.00', '0.00') - ,('29161499', '2916.14.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('29161505', '2916.15.05', 'Monooleato de sorbitan; monooleato de propanotriol.', '1', 'Kg', '0.00', '0.00') - ,('29161599', '2916.15.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('29161601', '2916.16.01', 'Binapacril (ISO).', '1', 'Kg', '0.00', '0.00') - ,('29161999', '2916.19.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29162005', '2916.20.05', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29163101', '2916.31.01', 'Sal de sodio del ácido benzoico.', '1', 'Kg', '5.00', '0.00') - ,('29163102', '2916.31.02', 'Dibenzoato de dipropilenglicol.', '1', 'Kg', '5.00', '0.00') - ,('29163103', '2916.31.03', 'Benzoato de etilo o de bencilo.', '1', 'Kg', '5.00', '0.00') - ,('29163199', '2916.31.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29163203', '2916.32.03', 'Cloruro de benzoilo.', '1', 'Kg', '0.00', '0.00') - ,('29163401', '2916.34.01', 'Ácido fenilacético y sus sales.', '1', 'Kg', '0.00', '0.00') - ,('29163908', '2916.39.08', 'Esteres del ácido fenilacético.', '1', 'Kg', '0.00', '0.00') - ,('29163999', '2916.39.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29171102', '2917.11.02', 'Ácido oxálico, sus sales y sus ésteres.', '1', 'Kg', '0.00', '0.00') - ,('29171201', '2917.12.01', 'Ácido adípico, sus sales y sus ésteres.', '1', 'Kg', '0.00', '0.00') - ,('29171303', '2917.13.03', 'Ácido azelaico, ácido sebácico, sus sales y sus ésteres.', '1', 'Kg', '0.00', '0.00') - ,('29171401', '2917.14.01', 'Anhídrido maleico.', '1', 'Kg', '10.00', '0.00') - ,('29171904', '2917.19.04', 'Ácido maleico y sus sales.', '1', 'Kg', '7.00', '0.00') - ,('29171908', '2917.19.08', 'Ácido fumárico.', '1', 'Kg', '5.00', '0.00') - ,('29171999', '2917.19.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29172004', '2917.20.04', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29173201', '2917.32.01', 'Ortoftalatos de dioctilo.', '1', 'Kg', '35.00', '0.00') - ,('29173301', '2917.33.01', 'Ortoftalatos de dinonilo o de didecilo.', '1', 'Kg', '7.00', '0.00') - ,('29173401', '2917.34.01', 'Ortoftalatos de dibutilo.', '1', 'Kg', '7.00', '0.00') - ,('29173499', '2917.34.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29173501', '2917.35.01', 'Anhídrido ftálico.', '1', 'Kg', '25.00', '0.00') - ,('29173601', '2917.36.01', 'Ácido tereftálico y sus sales.', '1', 'Kg', '10.00', '0.00') - ,('29173701', '2917.37.01', 'Tereftalato de dimetilo.', '1', 'Kg', '10.00', '0.00') - ,('29173901', '2917.39.01', 'Sal sódica del sulfoisoftalato de dimetilo.', '1', 'Kg', '0.00', '0.00') - ,('29173902', '2917.39.02', 'Tetracloro tereftalato de dimetilo.', '1', 'Kg', '0.00', '0.00') - ,('29173903', '2917.39.03', 'Ftalato dibásico de plomo.', '1', 'Kg', '0.00', '0.00') - ,('29173904', '2917.39.04', 'Trimelitato de trioctilo.', '1', 'Kg', '7.00', '0.00') - ,('29173905', '2917.39.05', 'Ácido isoftálico.', '1', 'Kg', '0.00', '0.00') - ,('29173906', '2917.39.06', 'Anhídrido trimelítico.', '1', 'Kg', '0.00', '0.00') - ,('29173999', '2917.39.99', 'Los demás.', '1', 'Kg', '35.00', '0.00') - ,('29181101', '2918.11.01', 'Ácido láctico, sus sales y sus ésteres.', '1', 'Kg', '0.00', '0.00') - ,('29181201', '2918.12.01', 'Ácido tartárico.', '1', 'Kg', '5.00', '0.00') - ,('29181305', '2918.13.05', 'Sales y ésteres del ácido tartárico.', '1', 'Kg', '0.00', '0.00') - ,('29181401', '2918.14.01', 'Ácido cítrico.', '1', 'Kg', '0.00', '0.00') - ,('29181501', '2918.15.01', 'Citrato de sodio.', '1', 'Kg', '7.00', '0.00') - ,('29181502', '2918.15.02', 'Citrato férrico amónico.', '1', 'Kg', '7.00', '0.00') - ,('29181505', '2918.15.05', 'Sales del ácido cítrico, excepto citratro de litio; ferrocitrato de calcio; y lo comprendido en las fracciones arancelarias 2918.15.01 y 2918.15.02.', '1', 'Kg', '5.00', '0.00') - ,('29181599', '2918.15.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29181603', '2918.16.03', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29181701', '2918.17.01', 'Ácido 2,2-difenil-2-hidroxiacético (ácido bencílico).', '1', 'Kg', '0.00', '0.00') - ,('29181801', '2918.18.01', 'Clorobencilato (ISO).', '1', 'Kg', '0.00', '0.00') - ,('29181906', '2918.19.06', 'Ácido málico.', '1', 'Kg', '5.00', '0.00') - ,('29181999', '2918.19.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29182102', '2918.21.02', 'Subsalicilato de bismuto.', '1', 'Kg', '5.00', '0.00') - ,('29182199', '2918.21.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29182202', '2918.22.02', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29182391', '2918.23.91', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29182901', '2918.29.01', 'p-Hidroxibenzoato de metilo o propilo.', '1', 'Kg', '5.00', '0.00') - ,('29182904', '2918.29.04', 'p-Hidroxibenzoato de etilo.', '1', 'Kg', '5.00', '0.00') - ,('29182999', '2918.29.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29183010', '2918.30.10', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29189101', '2918.91.01', '2,4,5-T (ISO) (ácido 2,4,5-triclorofenoxiacético), sus sales y sus ésteres.', '1', 'Kg', '0.00', '0.00') - ,('29189901', '2918.99.01', 'Ácido 2,4-diclorofenoxiacético.', '1', 'Kg', '35.00', '0.00') - ,('29189902', '2918.99.02', 'Estearoil 2-lactilato de calcio o de sodio.', '1', 'Kg', '10.00', '0.00') - ,('29189904', '2918.99.04', 'Ácido d-2-(6-metoxi-2-naftil) propiónico (Naproxeno) o su sal de sodio, excepto Ácido dl-2-(6-metoxi-2-naftil) propiónico.', '1', 'Kg', '5.00', '0.00') - ,('29189999', '2918.99.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29191001', '2919.10.01', 'Fosfato de tris(2,3-dibromopropilo).', '1', 'Kg', '0.00', '0.00') - ,('29199010', '2919.90.10', 'Fosfato de dimetil 1,2-dibromo-2,2-dicloroetilo (Naled).', '1', 'Kg', '5.00', '0.00') - ,('29199099', '2919.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29201101', '2920.11.01', 'Fósforotioato de O,O-dietil-O-p-nitrofenilo (Paratión).', '1', 'Kg', '5.00', '0.00') - ,('29201103', '2920.11.03', 'Fósforotioato de O,O-dimetil-O-p-nitrofenilo (Paratión metílico).', '22', 'PROH', '0.00', '0.00') - ,('29201999', '2920.19.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29202101', '2920.21.01', 'Fosfito de dimetilo.', '1', 'Kg', '0.00', '0.00') - ,('29202201', '2920.22.01', 'Fosfito de dietilo.', '1', 'Kg', '0.00', '0.00') - ,('29202301', '2920.23.01', 'Fosfito de trimetilo.', '1', 'Kg', '0.00', '0.00') - ,('29202401', '2920.24.01', 'Fosfito de trietilo.', '1', 'Kg', '0.00', '0.00') - ,('29202901', '2920.29.01', 'Fosfito del Trisnonilfenilo.', '1', 'Kg', '5.00', '0.00') - ,('29202999', '2920.29.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29209009', '2920.90.09', 'Peroxidicarbonatos de: di-secbutilo; di-n-butilo; diisopropilo; di(2-etilhexilo); bis(4- ter-butilciclohexilo); di-cetilo; di- ciclohexilo; dimiristilo.', '1', 'Kg', '10.00', '0.00') - ,('29209099', '2920.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29211105', '2921.11.05', 'Trimetilamina.', '1', 'Kg', '10.00', '0.00') - ,('29211199', '2921.11.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29211201', '2921.12.01', 'Clorhidrato de 2-cloroetil (N,N-dimetilamina).', '1', 'Kg', '0.00', '0.00') - ,('29211301', '2921.13.01', 'Clorhidrato de 2-cloroetil (N,N-dietilamina).', '1', 'Kg', '0.00', '0.00') - ,('29211401', '2921.14.01', 'Clorhidrato de 2-cloroetil (N,N-diisopropilamina).', '1', 'Kg', '0.00', '0.00') - ,('29211902', '2921.19.02', 'Trietilamina.', '1', 'Kg', '5.00', '0.00') - ,('29211904', '2921.19.04', 'Monoetilamina.', '1', 'Kg', '5.00', '0.00') - ,('29211905', '2921.19.05', '2-Aminopropano.', '1', 'Kg', '5.00', '0.00') - ,('29211911', '2921.19.11', 'Dimetiletilamina.', '1', 'Kg', '5.00', '0.00') - ,('29211912', '2921.19.12', 'n-Oleilamina.', '1', 'Kg', '5.00', '0.00') - ,('29211999', '2921.19.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29212102', '2921.21.02', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29212201', '2921.22.01', 'Hexametilendiamina y sus sales.', '1', 'Kg', '0.00', '0.00') - ,('29212910', '2921.29.10', 'Pentametildietilentriamina.', '1', 'Kg', '7.00', '0.00') - ,('29212999', '2921.29.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29213002', '2921.30.02', 'Tetrametil ciclohexil diamina.', '1', 'Kg', '0.00', '0.00') - ,('29213099', '2921.30.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('29214101', '2921.41.01', 'Anilina y sus sales.', '1', 'Kg', '0.00', '0.00') - ,('29214214', '2921.42.14', 'm-Cloroanilina.', '1', 'Kg', '5.00', '0.00') - ,('29214217', '2921.42.17', 'p-Nitroanilina.', '1', 'Kg', '5.00', '0.00') - ,('29214299', '2921.42.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29214313', '2921.43.13', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29214402', '2921.44.02', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29214508', '2921.45.08', 'Ácido naftiónico.', '1', 'Kg', '5.00', '0.00') - ,('29214599', '2921.45.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29214601', '2921.46.01', 'Anfetamina (DCI), benzfetamina (DCI), dexanfetamina (DCI), etilanfetamina (DCI), fencanfamina (DCI), fentermina (DCI), lefetamina (DCI), levanfetamina (DCI) y mefenorex (DCI); sales de estos productos.', '1', 'Kg', '0.00', '0.00') - ,('29214999', '2921.49.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29215101', '2921.51.01', 'p- o m-Fenilendiamina.', '1', 'Kg', '0.00', '0.00') - ,('29215102', '2921.51.02', 'Diaminotoluenos.', '1', 'Kg', '0.00', '0.00') - ,('29215103', '2921.51.03', 'Difenil-p-fenilendiamina.', '1', 'Kg', '0.00', '0.00') - ,('29215104', '2921.51.04', 'N,N-Diheptil-p-fenilendiamina.', '1', 'Kg', '0.00', '0.00') - ,('29215105', '2921.51.05', 'N,N-Di-beta-naftil-p-fenilendiamina.', '1', 'Kg', '0.00', '0.00') - ,('29215106', '2921.51.06', 'N-fenil-p-fenilendiamina.', '1', 'Kg', '0.00', '0.00') - ,('29215107', '2921.51.07', 'N-1,3-Dimetilbutil-N-fenil-p-fenilendiamina.', '1', 'Kg', '0.00', '0.00') - ,('29215108', '2921.51.08', 'Ácido nitro-amino difenilamino-sulfónico.', '1', 'Kg', '0.00', '0.00') - ,('29215109', '2921.51.09', 'Ácido 4-amino difenil amino 2-sulfónico.', '1', 'Kg', '0.00', '0.00') - ,('29215199', '2921.51.99', 'Los demás.', '1', 'Kg', '7.00', '0.00') - ,('29215999', '2921.59.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29221101', '2922.11.01', 'Monoetanolamina.', '1', 'Kg', '35.00', '0.00') - ,('29221199', '2922.11.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29221201', '2922.12.01', 'Dietanolamina.', '1', 'Kg', '35.00', '0.00') - ,('29221299', '2922.12.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29221403', '2922.14.03', 'Dextropropoxifeno (DCI) y sus sales.', '1', 'Kg', '0.00', '0.00') - ,('29221501', '2922.15.01', 'Trietanolamina.', '1', 'Kg', '10.00', '0.00') - ,('29221601', '2922.16.01', 'Perfluorooctano sulfonato de dietanolamonio.', '1', 'Kg', '0.00', '0.00') - ,('29221701', '2922.17.01', 'Metildietanolamina y etildietanolamina.', '1', 'Kg', '0.00', '0.00') - ,('29221801', '2922.18.01', '2-(N,N-Diisopropilamino)etanol.', '1', 'Kg', '0.00', '0.00') - ,('29221910', '2922.19.10', 'Tri-isopropanolamina.', '1', 'Kg', '5.00', '0.00') - ,('29221914', '2922.19.14', 'Fenildietanolamina.', '1', 'Kg', '5.00', '0.00') - ,('29221920', '2922.19.20', 'Laurato de dietanolamina.', '1', 'Kg', '5.00', '0.00') - ,('29221940', '2922.19.40', 'Dioxietil-meta-cloroanilina; Dioxietil-meta- toluidina.', '1', 'Kg', '5.00', '0.00') - ,('29221999', '2922.19.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29222103', '2922.21.03', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29222999', '2922.29.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29223101', '2922.31.01', 'Anfepramona (DCI), metadona (DCI) y normetadona (DCI); sales de estos productos.', '1', 'Kg', '0.00', '0.00') - ,('29223909', '2922.39.09', 'Difenil amino acetona.', '1', 'Kg', '5.00', '0.00') - ,('29223999', '2922.39.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29224102', '2922.41.02', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29224201', '2922.42.01', 'Glutamato de sodio.', '1', 'Kg', '5.00', '0.00') - ,('29224299', '2922.42.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29224301', '2922.43.01', 'Ácido antranílico y sus sales.', '1', 'Kg', '0.00', '0.00') - ,('29224401', '2922.44.01', 'Tilidina (DCI) y sus sales.', '1', 'Kg', '0.00', '0.00') - ,('29224901', '2922.49.01', 'Ácido aminoacético (Glicina).', '1', 'Kg', '0.00', '0.00') - ,('29224902', '2922.49.02', 'Clorhidrato de p-amino- benzoato de 2-dietilaminoetanol (Clorhidrato de procaína).', '1', 'Kg', '0.00', '0.00') - ,('29224903', '2922.49.03', 'Fenilalanina.', '1', 'Kg', '0.00', '0.00') - ,('29224904', '2922.49.04', 'Beta-Alanina y sus sales.', '1', 'Kg', '0.00', '0.00') - ,('29224905', '2922.49.05', 'Ácido yodopanoico y sus sales sódicas.', '1', 'Kg', '0.00', '0.00') - ,('29224906', '2922.49.06', 'Sal doble monocálcica disódica del ácido etilendiaminotetracético.', '1', 'Kg', '0.00', '0.00') - ,('29224907', '2922.49.07', 'Ácido aspártico.', '1', 'Kg', '0.00', '0.00') - ,('29224908', '2922.49.08', 'Ácidos etilendiamino tri o tetra acético y sus sales, excepto lo comprendido en la fracción arancelaria 2922.49.06.', '1', 'Kg', '10.00', '0.00') - ,('29224909', '2922.49.09', 'Ácido dietilentriaminopentacético y sus sales.', '1', 'Kg', '5.00', '0.00') - ,('29224910', '2922.49.10', 'Ácido N-(2,3-xilil)antranílico (Ácido mefenámico).', '1', 'Kg', '0.00', '0.00') - ,('29224911', '2922.49.11', 'Acamilofenina.', '1', 'Kg', '0.00', '0.00') - ,('29224912', '2922.49.12', 'N-(Alcoxi-carbonil-alquil) fenilglicinato de sodio.', '1', 'Kg', '0.00', '0.00') - ,('29224913', '2922.49.13', 'N-(1-Metoxicarbonil-propen-2-il) alfa-amino-p- hidroxifenil acetato de sodio.', '1', 'Kg', '0.00', '0.00') - ,('29224914', '2922.49.14', 'Clorhidrato de cloruro alfa-amino fenilacético.', '1', 'Kg', '0.00', '0.00') - ,('29224915', '2922.49.15', 'Alfa-Fenilglicina.', '1', 'Kg', '0.00', '0.00') - ,('29224917', '2922.49.17', 'Diclorhidrato del éster isopentílico de N-(2- dietilaminoetil)-2-fenilglicina (Diclorhidrato de acamilofenina).', '1', 'Kg', '0.00', '0.00') - ,('29224918', '2922.49.18', 'Sal sódica o potásica del ácido 2-((2,6-diclorofenil)amino) bencenacético (Diclofenac sódico o pótásico).', '1', 'Kg', '0.00', '0.00') - ,('29224919', '2922.49.19', 'p-n-Butilaminobenzoato del éter monometílico del nonaetilenglicol (Benzonatato).', '1', 'Kg', '0.00', '0.00') - ,('29224920', '2922.49.20', 'Sal de sodio del ácido aminonitrobenzoico.', '1', 'Kg', '0.00', '0.00') - ,('29224921', '2922.49.21', 'Ester etílico del ácido p-aminobenzoico (Benzocaina).', '1', 'Kg', '0.00', '0.00') - ,('29224922', '2922.49.22', 'N,N-bis(2-Metoxicarbonil etil anilina).', '1', 'Kg', '0.00', '0.00') - ,('29224923', '2922.49.23', 'Ester 2-(2-hidroxietoxi) etílico del ácido (2-((3-(trifluorometil)fenil)amino) benzoico (Etofenamato).', '1', 'Kg', '0.00', '0.00') - ,('29224999', '2922.49.99', 'Los demás.', '1', 'Kg', '7.00', '0.00') - ,('29225001', '2922.50.01', 'Ácido N-(hidroxietil)etilen diamino triacético y su sal trisódica.', '1', 'Kg', '5.00', '0.00') - ,('29225099', '2922.50.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29231001', '2923.10.01', 'Dicloruro de trimetiletilamonio (Dicloruro de colina).', '1', 'Kg', '0.00', '0.00') - ,('29231002', '2923.10.02', 'Quelato de ferrocitrato de colina (Ferrocolinato).', '1', 'Kg', '0.00', '0.00') - ,('29231099', '2923.10.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('29232001', '2923.20.01', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29233001', '2923.30.01', 'Perfluorooctano sulfonato de tetraetilamonio.', '1', 'Kg', '0.00', '0.00') - ,('29234001', '2923.40.01', 'Perfluorooctano sulfonato de didecildimetilamonio.', '1', 'Kg', '0.00', '0.00') - ,('29239002', '2923.90.02', 'Haluros de trimetilalquil amonio o de dialquil dimetilamonio, excepto bromuro de cetiltrimetilamonio.', '1', 'Kg', '5.00', '0.00') - ,('29239003', '2923.90.03', 'Cloruro de benzalconio.', '1', 'Kg', '5.00', '0.00') - ,('29239099', '2923.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29241101', '2924.11.01', 'Meprobamato (DCI).', '1', 'Kg', '0.00', '0.00') - ,('29241203', '2924.12.03', 'Dimetilfosfato de 2-cloro-2-dietilcarbamoil-1-metilvinilo (Fosfamidón).', '22', 'PROH', '0.00', '0.00') - ,('29241299', '2924.12.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29241901', '2924.19.01', 'Oleamida.', '1', 'Kg', '0.00', '0.00') - ,('29241902', '2924.19.02', 'Acrilamida.', '1', 'Kg', '0.00', '0.00') - ,('29241904', '2924.19.04', 'Erucamida.', '1', 'Kg', '0.00', '0.00') - ,('29241905', '2924.19.05', 'Metacrilamida.', '1', 'Kg', '0.00', '0.00') - ,('29241906', '2924.19.06', 'Formamida y sus derivados de sustitución.', '1', 'Kg', '0.00', '0.00') - ,('29241907', '2924.19.07', 'Estearamida, sus sales y otros derivados de sustitución, excepto lo comprendido en la fracción arancelaria 2924.19.10.', '1', 'Kg', '0.00', '0.00') - ,('29241908', '2924.19.08', 'N,N-Dimetilacetamida.', '1', 'Kg', '0.00', '0.00') - ,('29241909', '2924.19.09', 'N-Metil-2-cloroacetoacetamida.', '1', 'Kg', '0.00', '0.00') - ,('29241911', '2924.19.11', 'Dimetilformamida.', '1', 'Kg', '10.00', '0.00') - ,('29241912', '2924.19.12', 'N-lauroil sarcosinato de sodio.', '1', 'Kg', '0.00', '0.00') - ,('29241913', '2924.19.13', 'Dimetilfosfato de 3-hidroxi-N,N-dimetil-cis- crotonamida (dicrotofós).', '1', 'Kg', '0.00', '0.00') - ,('29241991', '2924.19.91', 'Los demás carbamatos y dicarbamatos, acíclicos.', '1', 'Kg', '0.00', '0.00') - ,('29241999', '2924.19.99', 'Los demás.', '1', 'Kg', '7.00', '0.00') - ,('29242108', '2924.21.08', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29242301', '2924.23.01', 'Ácido 2-acetamidobenzoico (ácido N-acetilantranílico) y sus sales.', '1', 'Kg', '0.00', '0.00') - ,('29242401', '2924.24.01', 'Etinamato (DCI).', '1', 'Kg', '0.00', '0.00') - ,('29242501', '2924.25.01', 'Alaclor (ISO).', '22', 'PROH', '0.00', '0.00') - ,('29242909', '2924.29.09', 'Acetotoluidina.', '1', 'Kg', '5.00', '0.00') - ,('29242999', '2924.29.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29251101', '2925.11.01', 'Sacarina y sus sales.', '1', 'Kg', '0.00', '0.00') - ,('29251201', '2925.12.01', 'Glutetimida (DCI).', '1', 'Kg', '0.00', '0.00') - ,('29251999', '2925.19.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29252101', '2925.21.01', 'Clordimeformo (ISO).', '1', 'Kg', '0.00', '0.00') - ,('29252999', '2925.29.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29261001', '2926.10.01', 'Acrilonitrilo.', '1', 'Kg', '0.00', '0.00') - ,('29262001', '2926.20.01', '1-Cianoguanidina (diciandiamida).', '1', 'Kg', '0.00', '0.00') - ,('29263001', '2926.30.01', 'Fenproporex (DCI) y sus sales; intermedio de la metadona (DCI) (4- ciano-2-dimetilamino-4,4-difenilbutano).', '1', 'Kg', '0.00', '0.00') - ,('29264001', '2926.40.01', 'alfa-Fenilacetoacetonitrilo.', '1', 'Kg', '0.00', '0.00') - ,('29269099', '2926.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29270006', '2927.00.06', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29280001', '2928.00.01', 'Metil-etil-cetoxima.', '1', 'Kg', '0.00', '0.00') - ,('29280002', '2928.00.02', 'Hidrazobenceno.', '1', 'Kg', '0.00', '0.00') - ,('29280003', '2928.00.03', 'Fosforotioato de O,O-dietil O-iminofenil acetonitrilo.', '1', 'Kg', '0.00', '0.00') - ,('29280005', '2928.00.05', '1,2-Naftoquinona-monosemicarbazona.', '1', 'Kg', '0.00', '0.00') - ,('29280006', '2928.00.06', 'Ácido (-)L-alfa-hidrazino-3,4-dihidroxi-alfa-metilhidrocinámico monohidratado (Carbidopa).', '1', 'Kg', '0.00', '0.00') - ,('29280007', '2928.00.07', 'Clorhidrato de 1,3-bis-((p-clorobenciliden)-amino)guanidina.', '1', 'Kg', '0.00', '0.00') - ,('29280099', '2928.00.99', 'Los demás.', '1', 'Kg', '7.00', '0.00') - ,('29291001', '2929.10.01', 'Mono o diclorofenilisocianato.', '1', 'Kg', '0.00', '0.00') - ,('29291002', '2929.10.02', '3-Trifluorometilfenilisocianato.', '1', 'Kg', '0.00', '0.00') - ,('29291004', '2929.10.04', 'Toluen diisocianato.', '1', 'Kg', '0.00', '0.00') - ,('29291005', '2929.10.05', 'Hexametilen diisocianato.', '1', 'Kg', '0.00', '0.00') - ,('29291006', '2929.10.06', 'Diisocianato de isoforona.', '1', 'Kg', '0.00', '0.00') - ,('29291099', '2929.10.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('29299099', '2929.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29301001', '2930.10.01', '2-(N,N-Dimetilamino)etanotiol.', '1', 'Kg', '0.00', '0.00') - ,('29302005', '2930.20.05', 'Etilen bis ditiocarbamato de manganeso (Maneb); Etilen bis ditiocarbamato de cinc (Zineb).', '1', 'Kg', '5.00', '0.00') - ,('29302006', '2930.20.06', 'N-Metilditiocarbamato de sodio, dihidratado.', '1', 'Kg', '5.00', '0.00') - ,('29302007', '2930.20.07', 'Dipropiltiocarbamato de S-Propilo (Vernolato).', '1', 'Kg', '5.00', '0.00') - ,('29302099', '2930.20.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29303002', '2930.30.02', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29304001', '2930.40.01', 'Metionina.', '1', 'Kg', '0.00', '0.00') - ,('29306001', '2930.60.01', '2-(N,N-Dietilamino)etanotiol.', '1', 'Kg', '0.00', '0.00') - ,('29307001', '2930.70.01', 'Sulfuro de bis(2-hidroxietilo) (tiodiglicol (DCI)).', '1', 'Kg', '0.00', '0.00') - ,('29308001', '2930.80.01', 'N-(1,1,2,2-Tetracloroetilmercapto)-4-ciclohexen-1,2-dicarboximida (Captafol).', '22', 'PROH', '0.00', '0.00') - ,('29308002', '2930.80.02', '2-metil-2- (metiltio) propionaldehido-O-metil carbamoil-oxima (Aldicarb).', '22', 'PROH', '0.00', '0.00') - ,('29308099', '2930.80.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29309011', '2930.90.11', 'Amilxantato de potasio; secbutilxantato de sodio; isopropilxantato de sodio; isobutilxantato de sodio.', '1', 'Kg', '5.00', '0.00') - ,('29309031', '2930.90.31', 'Hexa-sulfuro de dipentametilen tiourama.', '1', 'Kg', '5.00', '0.00') - ,('29309074', '2930.90.74', 'Ester etílico del ácido O,O-dimetilditiofosforil-fenilacético (Fentoato).', '22', 'PROH', '0.00', '0.00') - ,('29309099', '2930.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29311001', '2931.10.01', 'Tetrametilplomo y tetraetilplomo.', '1', 'Kg', '0.00', '0.00') - ,('29312001', '2931.20.01', 'Compuestos de tributilestaño.', '1', 'Kg', '0.00', '0.00') - ,('29314101', '2931.41.01', 'Metilfosfonato de dimetilo.', '1', 'Kg', '0.00', '0.00') - ,('29314201', '2931.42.01', 'Propilfosfonato de dimetilo.', '1', 'Kg', '0.00', '0.00') - ,('29314301', '2931.43.01', 'Etilfosfonato de dietilo.', '1', 'Kg', '0.00', '0.00') - ,('29314401', '2931.44.01', 'Ácido metilfosfónico.', '1', 'Kg', '0.00', '0.00') - ,('29314501', '2931.45.01', 'Sal del ácido metilfosfónico y de (aminoiminometil)urea (1 : 1).', '1', 'Kg', '0.00', '0.00') - ,('29314601', '2931.46.01', '2,4,6-Trióxido de 2,4,6-tripropil-1,3,5,2,4,6-trioxatrifosfinano.', '1', 'Kg', '0.00', '0.00') - ,('29314701', '2931.47.01', 'Metilfosfonato de (5-etil-2-metil-2-óxido-1,3,2-dioxafosfinan-5-il)metil metilo.', '1', 'Kg', '0.00', '0.00') - ,('29314801', '2931.48.01', '3,9-Dióxido de 3,9-dimetil-2,4,8,10-tetraoxa-3,9-difosfaspiro [5.5]undecano.', '1', 'Kg', '0.00', '0.00') - ,('29314901', '2931.49.01', 'Ácido amino tri(metilenfosfónico) y sus sales sódicas.', '1', 'Kg', '5.00', '0.00') - ,('29314999', '2931.49.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29315101', '2931.51.01', 'Dicloruro metilfosfónico.', '1', 'Kg', '0.00', '0.00') - ,('29315201', '2931.52.01', 'Dicloruro propilfosfónico.', '1', 'Kg', '0.00', '0.00') - ,('29315301', '2931.53.01', 'Metilfosfonotionato de O-(3-cloropropil) O-[4-nitro-3-(trifluorometil)fenilo].', '1', 'Kg', '0.00', '0.00') - ,('29315401', '2931.54.01', 'Triclorfón (ISO).', '22', 'PROH', '0.00', '0.00') - ,('29315901', '2931.59.01', 'Feniltiofosfonato de O-(2,5-dicloro-4-bromofenil)-O-metilo (Leptofos).', '22', 'PROH', '0.00', '0.00') - ,('29315999', '2931.59.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29319003', '2931.90.03', 'Óxidos o cloruros de dialquil estaño.', '1', 'Kg', '5.00', '0.00') - ,('29319009', '2931.90.09', 'Ácido arsanílico.', '1', 'Kg', '5.00', '0.00') - ,('29319099', '2931.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29321101', '2932.11.01', 'Tetrahidrofurano.', '1', 'Kg', '0.00', '0.00') - ,('29321201', '2932.12.01', '2-Furaldehído (furfural).', '1', 'Kg', '0.00', '0.00') - ,('29321301', '2932.13.01', 'Alcohol furfurílico y alcohol tetrahidrofurfurílico.', '1', 'Kg', '0.00', '0.00') - ,('29321401', '2932.14.01', 'Sucralosa.', '1', 'Kg', '0.00', '0.00') - ,('29321902', '2932.19.02', 'Nitrofurazona.', '1', 'Kg', '5.00', '0.00') - ,('29321999', '2932.19.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29322011', '2932.20.11', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29329101', '2932.91.01', 'Isosafrol.', '1', 'Kg', '0.00', '0.00') - ,('29329201', '2932.92.01', '1-(1,3-Benzodioxol-5-il)propan-2-ona.', '1', 'Kg', '0.00', '0.00') - ,('29329301', '2932.93.01', 'Piperonal.', '1', 'Kg', '0.00', '0.00') - ,('29329401', '2932.94.01', 'Safrol.', '1', 'Kg', '0.00', '0.00') - ,('29329501', '2932.95.01', 'Tetrahidrocannabinoles (todos los isómeros).', '1', 'Kg', '0.00', '0.00') - ,('29329601', '2932.96.01', 'Carbofurano (carbofurán) (ISO).', '22', 'PROH', '0.00', '0.00') - ,('29329999', '2932.99.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29331103', '2933.11.03', 'Fenazona (antipirina) y sus derivados.', '1', 'Kg', '0.00', '0.00') - ,('29331904', '2933.19.04', 'Fenilbutazona base.', '1', 'Kg', '5.00', '0.00') - ,('29331908', '2933.19.08', '2,3-Dimetil-1-fenil-5-pirazolón-4- metilaminometasulfonato sódico o de magnesio (Dipirona sódica o magnésica).', '1', 'Kg', '5.00', '0.00') - ,('29331999', '2933.19.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29332101', '2933.21.01', 'Hidantoína y sus derivados.', '1', 'Kg', '0.00', '0.00') - ,('29332999', '2933.29.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29333103', '2933.31.03', 'Piridina y sus sales.', '1', 'Kg', '0.00', '0.00') - ,('29333201', '2933.32.01', 'Pentametilenditiocarbamato de piperidina.', '1', 'Kg', '5.00', '0.00') - ,('29333299', '2933.32.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29333303', '2933.33.03', 'Sales de los productos del número de identificación comercial 2933.33.03.01.', '1', 'Kg', '0.00', '0.00') - ,('29333491', '2933.34.91', 'Los demás fentanilos y sus derivados.', '1', 'Kg', '0.00', '0.00') - ,('29333501', '2933.35.01', 'Quinuclidin-3-ol.', '1', 'Kg', '0.00', '0.00') - ,('29333601', '2933.36.01', '4-Anilino-N-fenetilpiperidina (ANPP).', '1', 'Kg', '0.00', '0.00') - ,('29333701', '2933.37.01', 'N-Fenetil-4-piperidona (NPP).', '1', 'Kg', '0.00', '0.00') - ,('29333902', '2933.39.02', 'Feniramina, bromofeniramina y sus maleatos.', '1', 'Kg', '5.00', '0.00') - ,('29333917', '2933.39.17', 'Ester dimetílico del ácido 1,4-dihidro-2,6-dimetil-4-(2-nitrofenil)-3,5-piridindicarboxílico (Nifedipina).', '1', 'Kg', '5.00', '0.00') - ,('29333991', '2933.39.91', 'Los demás derivados de la piperidina y sales de estos productos.', '1', 'Kg', '0.00', '0.00') - ,('29333999', '2933.39.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29334101', '2933.41.01', '', '1', 'Kg', '0.00', '0.00') - ,('29334901', '2933.49.01', 'Diyodo oxiquinolina (Yodoquinol).', '1', 'Kg', '5.00', '0.00') - ,('29334904', '2933.49.04', '6-Dodecil-1,2-dihidro-2,2,4-trimetil quinolina.', '1', 'Kg', '5.00', '0.00') - ,('29334910', '2933.49.10', 'Yodo cloro-8-hidroxiquinolina (Clioquinol).', '1', 'Kg', '5.00', '0.00') - ,('29334999', '2933.49.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29335201', '2933.52.01', 'Malonilurea (ácido barbitúrico) y sus sales.', '1', 'Kg', '0.00', '0.00') - ,('29335301', '2933.53.01', 'Alobarbital (DCI), amobarbital (DCI), barbital (DCI), butalbital (DCI), butobarbital, ciclobarbital (DCI), fenobarbital (DCI), metilfenobarbital (DCI), pentobarbital (DCI), secbutabarbital (DCI), secobarbital (DCI) y vinilbital (DCI); sales de estos productos.', '1', 'Kg', '0.00', '0.00') - ,('29335491', '2933.54.91', 'Los demás derivados de la malonilurea (ácido barbitúrico); sales de estos productos.', '1', 'Kg', '0.00', '0.00') - ,('29335503', '2933.55.03', 'Loprazolam (DCI), meclocualona (DCI), metacualona (DCI) y zipeprol (DCI); sales de estos productos.', '1', 'Kg', '0.00', '0.00') - ,('29335907', '2933.59.07', 'Sales de la piperazina.', '1', 'Kg', '5.00', '0.00') - ,('29335999', '2933.59.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29336101', '2933.61.01', 'Melamina.', '1', 'Kg', '0.00', '0.00') - ,('29336912', '2933.69.12', 'Hexametilentetramina (Metenamina).', '1', 'Kg', '5.00', '0.00') - ,('29336915', '2933.69.15', 'Dinitroso pentametilen tetramina.', '1', 'Kg', '5.00', '0.00') - ,('29336999', '2933.69.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29337101', '2933.71.01', '6-Hexanolactama (épsilon-caprolactama).', '1', 'Kg', '7.00', '0.00') - ,('29337201', '2933.72.01', 'Clobazam (DCI) y metiprilona (DCI).', '1', 'Kg', '0.00', '0.00') - ,('29337991', '2933.79.91', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29339103', '2933.91.03', 'Mazindol (DCI), pirovalerona (DCI); sales de estos productos.', '1', 'Kg', '0.00', '0.00') - ,('29339201', '2933.92.01', 'Azinfos-metil (ISO).', '22', 'PROH', '0.00', '0.00') - ,('29339904', '2933.99.04', '1-((4-Clorofenil)metil)-2-(1-pirrolidinil metil)-1H-bencimidazol (Clemizol).', '1', 'Kg', '5.00', '0.00') - ,('29339910', '2933.99.10', 'Dimetilacridan.', '1', 'Kg', '5.00', '0.00') - ,('29339926', '2933.99.26', 'Fosforoditioato de O,O-dietil-S-(4-oxo-1,2,3- benzotriazin-3(4H)-il)metilo (Azinfos etílico).', '1', 'Kg', '5.00', '0.00') - ,('29339999', '2933.99.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29341009', '2934.10.09', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29342005', '2934.20.05', 'Disulfuro de benzotiazilo sulfenamida.', '1', 'Kg', '5.00', '0.00') - ,('29342099', '2934.20.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29343002', '2934.30.02', 'Compuestos cuya estructura contenga ciclos fenotiazina (incluso hidrogenados), sin otras condensaciones.', '1', 'Kg', '0.00', '0.00') - ,('29349101', '2934.91.01', 'Aminorex (DCI), brotizolam (DCI), clotiazepam (DCI), cloxazolam (DCI), dextromoramida (DCI), fendimetrazina (DCI), fenmetrazina (DCI), haloxazolam (DCI), ketazolam (DCI), mesocarb (DCI), oxazolam (DCI), pemolina (DCI) y sufentanil (DCI); sales de estos productos.', '1', 'Kg', '0.00', '0.00') - ,('29349291', '2934.92.91', 'Los demás fentanilos y sus derivados.', '1', 'Kg', '0.00', '0.00') - ,('29349901', '2934.99.01', 'Furazolidona.', '1', 'Kg', '5.00', '0.00') - ,('29349910', '2934.99.10', '3,5-Dimetil-1,3,5-(2H)-tetrahidrotiadiazin-2-tiona (Dazomet).', '1', 'Kg', '5.00', '0.00') - ,('29349914', '2934.99.14', 'Furaltadona.', '1', 'Kg', '5.00', '0.00') - ,('29349924', '2934.99.24', 'Clorhidrato de levo-2,3,5,6-tetrahidro-6-fenilimidazo (2,1-b) tiazol (Clorhidrato de levamisol).', '1', 'Kg', '5.00', '0.00') - ,('29349999', '2934.99.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29351001', '2935.10.01', 'N-Metilperfluorooctano sulfonamida.', '1', 'Kg', '0.00', '0.00') - ,('29352001', '2935.20.01', 'N-Etilperfluorooctano sulfonamida.', '1', 'Kg', '0.00', '0.00') - ,('29353001', '2935.30.01', 'N-Etil-N-(2-hidroxietil)perfluorooctano sulfonamida.', '1', 'Kg', '0.00', '0.00') - ,('29354001', '2935.40.01', 'N-(2-Hidroxietil)-N-metilperfluorooctano sulfonamida.', '1', 'Kg', '0.00', '0.00') - ,('29355091', '2935.50.91', 'Las demás perfluorooctano sulfonamidas.', '1', 'Kg', '0.00', '0.00') - ,('29359003', '2935.90.03', 'Clorpropamida.', '1', 'Kg', '5.00', '0.00') - ,('29359007', '2935.90.07', 'Ácido 4-cloro-N-(2-furilmetil)-5-sulfamoilantranílico (Furosemida).', '1', 'Kg', '5.00', '0.00') - ,('29359020', '2935.90.20', 'Tolilsulfonilbutilurea (Tolbutamida).', '1', 'Kg', '5.00', '0.00') - ,('29359031', '2935.90.31', 'Ftalilsulfacetamida.', '1', 'Kg', '5.00', '0.00') - ,('29359099', '2935.90.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('29362104', '2936.21.04', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29362204', '2936.22.04', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29362303', '2936.23.03', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29362403', '2936.24.03', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29362502', '2936.25.02', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29362601', '2936.26.01', 'Vitamina B12; las demás cobalaminas.', '1', 'Kg', '5.00', '0.00') - ,('29362699', '2936.26.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29362702', '2936.27.02', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29362803', '2936.28.03', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29362903', '2936.29.03', 'Ácido nicotínico.', '1', 'Kg', '5.00', '0.00') - ,('29362904', '2936.29.04', 'Nicotinamida (Niacinamida).', '1', 'Kg', '5.00', '0.00') - ,('29362999', '2936.29.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29369091', '2936.90.91', 'Los demás, incluidos los concentrados naturales.', '1', 'Kg', '0.00', '0.00') - ,('29371101', '2937.11.01', 'Somatotropina, sus derivados y análogos estructurales.', '1', 'Kg', '0.00', '0.00') - ,('29371202', '2937.12.02', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29371999', '2937.19.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29372105', '2937.21.05', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29372211', '2937.22.11', 'Flumetasona, parametasona; sales o ésteres de estos productos.', '1', 'Kg', '5.00', '0.00') - ,('29372299', '2937.22.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29372304', '2937.23.04', 'Progesterona.', '1', 'Kg', '5.00', '0.00') - ,('29372305', '2937.23.05', 'Estriol, sus sales o sus ésteres.', '1', 'Kg', '5.00', '0.00') - ,('29372307', '2937.23.07', 'Acetato de medroxiprogesterona.', '1', 'Kg', '5.00', '0.00') - ,('29372308', '2937.23.08', 'Acetato de clormadinona.', '1', 'Kg', '5.00', '0.00') - ,('29372310', '2937.23.10', 'Acetato de megestrol.', '1', 'Kg', '5.00', '0.00') - ,('29372311', '2937.23.11', '17-alfa-Etinil-17-beta hidroxiestra-4-eno (Linestrenol).', '1', 'Kg', '5.00', '0.00') - ,('29372317', '2937.23.17', 'Etinilestradiol, sus ésteres o sus sales.', '1', 'Kg', '5.00', '0.00') - ,('29372318', '2937.23.18', 'Mestranol.', '1', 'Kg', '5.00', '0.00') - ,('29372321', '2937.23.21', 'Delmadinona, sus sales o sus ésteres.', '1', 'Kg', '5.00', '0.00') - ,('29372322', '2937.23.22', 'Acetofénido de dihidroxiprogesterona (Algestona acetofénido).', '1', 'Kg', '5.00', '0.00') - ,('29372399', '2937.23.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29372909', '2937.29.09', 'Pregnenolona, sus sales o sus ésteres.', '1', 'Kg', '5.00', '0.00') - ,('29372917', '2937.29.17', 'Diosgenina.', '1', 'Kg', '15.00', '0.00') - ,('29372919', '2937.29.19', 'Metiltestosterona.', '1', 'Kg', '5.00', '0.00') - ,('29372924', '2937.29.24', 'Metilandrostendiol.', '1', 'Kg', '5.00', '0.00') - ,('29372929', '2937.29.29', '', '1', 'Kg', '5.00', '0.00') - ,('29372932', '2937.29.32', '17-alfa-Pregna-2,4-dien-20-ino (2,3-d)- isoxazol-17-ol (Danazol).', '1', 'Kg', '5.00', '0.00') - ,('29372933', '2937.29.33', 'Clostebol, sus sales o sus ésteres.', '1', 'Kg', '5.00', '0.00') - ,('29372936', '2937.29.36', 'Dehidroisoandrosterona (Prasterona), sus sales o sus ésteres, excepto enantato de prasterona.', '1', 'Kg', '5.00', '0.00') - ,('29372999', '2937.29.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29375002', '2937.50.02', 'Prostaglandinas, tromboxanos y leucotrienos, sus derivados y análogos estructurales.', '1', 'Kg', '0.00', '0.00') - ,('29379099', '2937.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29381001', '2938.10.01', 'Rutósido (rutina) y sus derivados.', '1', 'Kg', '0.00', '0.00') - ,('29389099', '2938.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29391101', '2939.11.01', 'Diacetilmorfina (Heroína), base o clorhidrato.', '22', 'PROH', '0.00', '0.00') - ,('29391199', '2939.11.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29391999', '2939.19.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29392002', '2939.20.02', 'Alcaloides de la quina (chinchona) y sus derivados; sales de estos productos.', '1', 'Kg', '0.00', '0.00') - ,('29393004', '2939.30.04', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('29394101', '2939.41.01', 'Efedrina y sus sales.', '1', 'Kg', '0.00', '0.00') - ,('29394201', '2939.42.01', 'Seudoefedrina (DCI) y sus sales.', '1', 'Kg', '0.00', '0.00') - ,('29394301', '2939.43.01', 'Catina (DCI) y sus sales.', '1', 'Kg', '0.00', '0.00') - ,('29394401', '2939.44.01', 'Clorhidrato de 2-amino-1-fenil-1-propanol (Clorhidrato de norefedrina).', '1', 'Kg', '0.00', '0.00') - ,('29394499', '2939.44.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('29394501', '2939.45.01', 'Levometanfetamina, metanfetamina (DCI), racemato de metanfetamina, y sus sales.', '1', 'Kg', '0.00', '0.00') - ,('29394999', '2939.49.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29395101', '2939.51.01', 'Fenetilina (DCI) y sus sales.', '1', 'Kg', '0.00', '0.00') - ,('29395999', '2939.59.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29396101', '2939.61.01', 'Ergometrina (DCI) y sus sales.', '1', 'Kg', '0.00', '0.00') - ,('29396201', '2939.62.01', 'Ergotamina (DCI) y sus sales.', '1', 'Kg', '0.00', '0.00') - ,('29396301', '2939.63.01', 'Ácido lisérgico y sus sales.', '1', 'Kg', '0.00', '0.00') - ,('29396999', '2939.69.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29397201', '2939.72.01', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29397999', '2939.79.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29398099', '2939.80.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29400005', '2940.00.05', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29411003', '2941.10.03', 'Bencilpenicilina procaína.', '1', 'Kg', '5.00', '0.00') - ,('29411007', '2941.10.07', '3-Fenil-5-metil-4-isoxazolil penicilina sódica (Oxacilina sódica).', '1', 'Kg', '5.00', '0.00') - ,('29411008', '2941.10.08', '3-(2,6-diclorofenil)-5-metil-4-isoxazolil penicilina sódica (Dicloxacilina sódica).', '1', 'Kg', '5.00', '0.00') - ,('29411012', '2941.10.12', 'Amoxicilina trihidratada.', '1', 'Kg', '5.00', '0.00') - ,('29411099', '2941.10.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29412001', '2941.20.01', 'Estreptomicinas y sus derivados; sales de estos productos.', '1', 'Kg', '0.00', '0.00') - ,('29413004', '2941.30.04', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29414004', '2941.40.04', 'Florfenicol y sus sales.', '1', 'Kg', '0.00', '0.00') - ,('29415002', '2941.50.02', 'Eritromicina y sus derivados; sales de estos productos.', '1', 'Kg', '0.00', '0.00') - ,('29419017', '2941.90.17', 'Lincomicina.', '1', 'Kg', '5.00', '0.00') - ,('29419099', '2941.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('29420091', '2942.00.91', 'Los demás compuestos orgánicos.', '1', 'Kg', '0.00', '0.00') - ,('30012003', '3001.20.03', 'Mucina gástrica en polvo.', '1', 'Kg', '0.00', '0.00') - ,('30012004', '3001.20.04', 'Sales biliares.', '1', 'Kg', '0.00', '0.00') - ,('30012005', '3001.20.05', 'Ácidos biliares.', '1', 'Kg', '0.00', '0.00') - ,('30012006', '3001.20.06', 'Extractos de glándulas o de otros órganos.', '1', 'Kg', '0.00', '0.00') - ,('30012099', '3001.20.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('30019001', '3001.90.01', 'Órganos y tejidos de seres humanos para fines terapéuticos, de docencia o investigación.', '1', 'Kg', '0.00', '0.00') - ,('30019003', '3001.90.03', 'Sustancias oseas.', '1', 'Kg', '0.00', '0.00') - ,('30019004', '3001.90.04', 'Fosfolípidos de materia gris cerebral en polvo.', '1', 'Kg', '0.00', '0.00') - ,('30019005', '3001.90.05', 'Heparina o heparina sódica.', '1', 'Kg', '0.00', '0.00') - ,('30019006', '3001.90.06', 'Heparinoide.', '1', 'Kg', '0.00', '0.00') - ,('30019008', '3001.90.08', 'Glándulas y demás órganos, desecados, incluso pulverizados.', '1', 'Kg', '0.00', '0.00') - ,('30019099', '3001.90.99', 'Las demás.', '1', 'Kg', '5.00', '0.00') - ,('30021201', '3002.12.01', 'Sueros, excepto suero antiofídico polivalente y suero humano.', '1', 'Kg', '15.00', '0.00') - ,('30021204', '3002.12.04', 'Inmunoglobulina-humana anti Rh.', '1', 'Kg', '0.00', '0.00') - ,('30021207', '3002.12.07', 'Extracto desproteinizado de sangre de res.', '1', 'Kg', '0.00', '0.00') - ,('30021208', '3002.12.08', 'Preparaciones de albúmina de sangre humana, no acondicionadas para la venta al por menor.', '1', 'Kg', '0.00', '0.00') - ,('30021209', '3002.12.09', 'Gamma globulina de origen humano liofilizada para administración intravenosa.', '1', 'Kg', '0.00', '0.00') - ,('30021299', '3002.12.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('30021301', '3002.13.01', 'Interferón beta recombinante de células de mamífero, o de fibroblastos humanos.', '1', 'Kg', '0.00', '0.00') - ,('30021305', '3002.13.05', 'Molgramostim.', '1', 'Kg', '0.00', '0.00') - ,('30021399', '3002.13.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('30021402', '3002.14.02', 'Productos inmunológicos mezclados, sin dosificar ni acondicionar para la venta al por menor.', '1', 'Kg', '5.00', '0.00') - ,('30021502', '3002.15.02', 'Medicamentos a base de rituximab.', '1', 'Kg', '0.00', '0.00') - ,('30021503', '3002.15.03', 'Medicamentos que contengan molgramostim.', '1', 'Kg', '10.00', '0.00') - ,('30021599', '3002.15.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('30024101', '3002.41.01', 'Vacuna antiestafilocócica.', '1', 'Kg', '0.00', '0.00') - ,('30024102', '3002.41.02', 'Vacuna antihepatitis tipo "A" o "B".', '1', 'Kg', '0.00', '0.00') - ,('30024103', '3002.41.03', 'Vacuna contra la haemophilus tipo "B".', '1', 'Kg', '0.00', '0.00') - ,('30024104', '3002.41.04', 'Vacuna antineumococica polivalente.', '1', 'Kg', '0.00', '0.00') - ,('30024105', '3002.41.05', 'Vacuna contra el sarampión, paroditis y rubeóla.', '1', 'Kg', '0.00', '0.00') - ,('30024106', '3002.41.06', 'Vacuna contra el virus SARS-CoV-2.', '1', 'Kg', '0.00', '0.00') - ,('30024199', '3002.41.99', 'Las demás.', '1', 'Kg', '5.00', '0.00') - ,('30024201', '3002.42.01', 'Las demás.', '1', 'Kg', '5.00', '0.00') - ,('30024901', '3002.49.01', 'Cultivos bacteriológicos para inyecciones hipodérmicas o intravenosas; bacilos lácticos liofilizados.', '1', 'Kg', '0.00', '0.00') - ,('30024999', '3002.49.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('30025101', '3002.51.01', 'Productos de terapia celular.', '1', 'Kg', '5.00', '0.00') - ,('30025999', '3002.59.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('30029099', '3002.90.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('30031001', '3003.10.01', 'Que contengan penicilinas o derivados de estos productos con la estructura del ácido penicilánico, o estreptomicinas o derivados de estos productos.', '1', 'Kg', '15.00', '0.00') - ,('30032001', '3003.20.01', 'Medicamentos a base de dos o más antibióticos, aún cuando contengan vitaminas u otros productos.', '1', 'Kg', '0.00', '0.00') - ,('30032099', '3003.20.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('30033102', '3003.31.02', 'Que contengan insulina.', '1', 'Kg', '5.00', '0.00') - ,('30033901', '3003.39.01', 'Anestésicos a base de 2-dietilamino- 2,6-acetoxilidida (Lidocaína) al 2% con 1-noradrenalina.', '1', 'Kg', '0.00', '0.00') - ,('30033999', '3003.39.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('30034101', '3003.41.01', 'Que contengan efedrina o sus sales.', '1', 'Kg', '5.00', '0.00') - ,('30034201', '3003.42.01', 'Que contengan seudoefedrina (DCI) o sus sales.', '1', 'Kg', '5.00', '0.00') - ,('30034301', '3003.43.01', 'Que contengan norefedrina o sus sales.', '1', 'Kg', '5.00', '0.00') - ,('30034901', '3003.49.01', 'Preparaciones a base de Cannabis indica.', '22', 'PROH', '0.00', '0.00') - ,('30034902', '3003.49.02', 'Preparaciones a base de acetil morfina o de sus sales o derivados.', '22', 'PROH', '0.00', '0.00') - ,('30034903', '3003.49.03', 'Preparaciones a base de sulfato de vincristina.', '1', 'Kg', '0.00', '0.00') - ,('30034999', '3003.49.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('30036091', '3003.60.91', 'Los demás, que contengan los principios activos contra la malaria (paludismo) descritos en la Nota 2 de subpartida del presente Capítulo.', '1', 'Kg', '10.00', '0.00') - ,('30039001', '3003.90.01', 'Preparaciones a base de cal sodada.', '1', 'Kg', '0.00', '0.00') - ,('30039002', '3003.90.02', 'Solución isotónica glucosada.', '1', 'Kg', '0.00', '0.00') - ,('30039003', '3003.90.03', 'Preparaciones a base de proteínas hidrolizadas.', '1', 'Kg', '5.00', '0.00') - ,('30039004', '3003.90.04', 'Tioleico RV 100.', '1', 'Kg', '0.00', '0.00') - ,('30039008', '3003.90.08', 'Insaponificable de aceite de germen de maíz.', '1', 'Kg', '0.00', '0.00') - ,('30039009', '3003.90.09', 'Preparación a base de polipéptido inhibidor de calicreína.', '1', 'Kg', '0.00', '0.00') - ,('30039010', '3003.90.10', 'Preparación liofilizada a base de 5-Etil-5(1-metilbutil)-2-tiobarbiturato de sodio (Tiopental sódico).', '1', 'Kg', '0.00', '0.00') - ,('30039011', '3003.90.11', 'Solución coloidal de polimerizado de gelatinas desintegradas, conteniendo además cloruros de sodio, de potasio y de calcio.', '1', 'Kg', '0.00', '0.00') - ,('30039012', '3003.90.12', 'Medicamentos homeopáticos.', '1', 'Kg', '5.00', '0.00') - ,('30039013', '3003.90.13', 'Preparación de hidroxialuminato de sodio o de magnesio y sorbitol.', '1', 'Kg', '0.00', '0.00') - ,('30039014', '3003.90.14', 'Polvo formado con leche descremada y dimetil polisiloxano.', '1', 'Kg', '0.00', '0.00') - ,('30039015', '3003.90.15', 'Mezcla de glucósidos de adonis, convallaria, oleander y scila.', '1', 'Kg', '0.00', '0.00') - ,('30039016', '3003.90.16', 'Preparación a base de clostridiopeptidasa.', '1', 'Kg', '0.00', '0.00') - ,('30039017', '3003.90.17', 'Poli(vinilpirrolidona)-Yodo, en polvo, destinada para uso humano o veterinario.', '1', 'Kg', '0.00', '0.00') - ,('30039018', '3003.90.18', 'Preparación hidromiscible de vitamina A, D y E.', '1', 'Kg', '0.00', '0.00') - ,('30039019', '3003.90.19', 'Premezcla granulada a base de nimodipina (Nimotop).', '1', 'Kg', '0.00', '0.00') - ,('30039020', '3003.90.20', 'Premezcla granulada a base de acarbosa (Glucobay).', '1', 'Kg', '0.00', '0.00') - ,('30039021', '3003.90.21', 'Desinfectantes para boca, oídos, nariz o garganta.', '1', 'Kg', '0.00', '0.00') - ,('30039022', '3003.90.22', 'Antineurítico a base de enzima proteolítica inyectable, incluso asociada con vitamina B1 y B12.', '1', 'Kg', '0.00', '0.00') - ,('30039099', '3003.90.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('30041001', '3004.10.01', 'Antibiótico a base de piperacilina sódica.', '1', 'Kg', '5.00', '0.00') - ,('30041099', '3004.10.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('30042001', '3004.20.01', 'A base de ciclosporina.', '1', 'Kg', '0.00', '0.00') - ,('30042002', '3004.20.02', 'Medicamento de amplio espectro a base de meropenem.', '1', 'Kg', '0.00', '0.00') - ,('30042003', '3004.20.03', 'Antibiótico de amplio espectro a base de imipenem y cilastatina sódica (Tienam).', '1', 'Kg', '0.00', '0.00') - ,('30042099', '3004.20.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('30043102', '3004.31.02', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('30043201', '3004.32.01', 'Medicamentos a base de budesonida.', '1', 'Kg', '0.00', '0.00') - ,('30043299', '3004.32.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('30043902', '3004.39.02', 'Que contengan somatotropina (somatropina).', '1', 'Kg', '0.00', '0.00') - ,('30043903', '3004.39.03', 'A base de octreotida.', '1', 'Kg', '0.00', '0.00') - ,('30043904', '3004.39.04', 'Antineoplásico constituido por 6-[O-(1,1-dimetiletil)-D-serina]-10 deglicinamida-FLHL-2 (amino carbonil) hidrazina (Goserelina), en excipiente biodegradable.', '1', 'Kg', '0.00', '0.00') - ,('30043905', '3004.39.05', 'Óvulos a base de dinoprostona o prostaglandina E2.', '1', 'Kg', '0.00', '0.00') - ,('30043999', '3004.39.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('30044101', '3004.41.01', 'Que contengan efedrina o sus sales.', '1', 'Kg', '10.00', '0.00') - ,('30044201', '3004.42.01', 'Que contengan seudoefedrina (DCI) o sus sales.', '1', 'Kg', '10.00', '0.00') - ,('30044301', '3004.43.01', 'Que contengan norefedrina o sus sales.', '1', 'Kg', '10.00', '0.00') - ,('30044901', '3004.49.01', 'Preparaciones a base de acetil morfina o de sus sales o derivados.', '22', 'PROH', '0.00', '0.00') - ,('30044902', '3004.49.02', 'Preparaciones a base de Cannabis indica.', '22', 'PROH', '0.00', '0.00') - ,('30044903', '3004.49.03', 'Preparaciones a base de sulfato de vincristina.', '1', 'Kg', '5.00', '0.00') - ,('30044905', '3004.49.05', 'Soluciones oftálmicas a base de maleato de timolol y clorhidrato de pilocarpina.', '1', 'Kg', '0.00', '0.00') - ,('30044999', '3004.49.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('30045001', '3004.50.01', 'Medicamentos en tabletas de núcleos múltiples y desintegración retardada.', '1', 'Kg', '0.00', '0.00') - ,('30045002', '3004.50.02', 'Antineuríticos a base de enzima proteolítica asociada con vitaminas B1 y B12, inyectable.', '1', 'Kg', '0.00', '0.00') - ,('30045099', '3004.50.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('30046091', '3004.60.91', 'Los demás, que contengan los principios activos contra la malaria (paludismo) descritos en la Nota 2 de subpartida del presente Capítulo.', '1', 'Kg', '10.00', '0.00') - ,('30049001', '3004.90.01', 'Preparaciones a base de cal sodada.', '1', 'Kg', '0.00', '0.00') - ,('30049002', '3004.90.02', 'Solución isotónica glucosada.', '1', 'Kg', '5.00', '0.00') - ,('30049004', '3004.90.04', 'Tioleico RV 100.', '1', 'Kg', '5.00', '0.00') - ,('30049005', '3004.90.05', 'Emulsión de aceite de soya al 10% o al 20%, conteniendo 1.2% de lecitina de huevo, con un pH de 5.5 a 9.0, grasa de 9.0% a 11.0% y glicerol de 19.5 mg/ml a 24.5 mg/ml.', '1', 'Kg', '0.00', '0.00') - ,('30049006', '3004.90.06', 'Antineurítico a base de enzima proteolítica inyectable.', '1', 'Kg', '0.00', '0.00') - ,('30049007', '3004.90.07', 'Insaponificable de aceite de germen de maíz.', '1', 'Kg', '5.00', '0.00') - ,('30049008', '3004.90.08', 'Liofilizados a base de 5-etil-5-(1-metilbutil)-2-tiobarbiturato de sodio (Tiopental sódico).', '1', 'Kg', '0.00', '0.00') - ,('30049009', '3004.90.09', 'Solución coloidal de polimerizado de gelatinas desintegradas, conteniendo además cloruros de sodio, de potasio y de calcio.', '1', 'Kg', '5.00', '0.00') - ,('30049011', '3004.90.11', 'Preparación de hidroxialuminato de sodio o de magnesio y sorbitol.', '1', 'Kg', '0.00', '0.00') - ,('30049012', '3004.90.12', 'Medicamentos a base de triyodometano, aminobenzoato de butilo, aceite esencial de menta y eugenol.', '1', 'Kg', '5.00', '0.00') - ,('30049013', '3004.90.13', 'Medicamentos a base de fluoruro de sodio y glicerina.', '1', 'Kg', '0.00', '0.00') - ,('30049014', '3004.90.14', 'Medicamentos en aerosol a base de clorhidrato de tetracaína y amino benzoato de etilo.', '1', 'Kg', '0.00', '0.00') - ,('30049015', '3004.90.15', 'Preparación a base de cloruro de etilo.', '1', 'Kg', '0.00', '0.00') - ,('30049016', '3004.90.16', 'Medicamentos a base de 1-(4-hidroxi-3-hidroximetilfenil)-2-(terbutilamino)etanol, en envase aerosol.', '1', 'Kg', '0.00', '0.00') - ,('30049017', '3004.90.17', 'Mezcla de glucósidos de adonis, convallaria, oleander y scila.', '1', 'Kg', '5.00', '0.00') - ,('30049018', '3004.90.18', 'Medicamentos en tabletas a base de azatioprina o de clorambucil o de melfalan o de busulfan o de 6-mercaptopurina.', '1', 'Kg', '5.00', '0.00') - ,('30049019', '3004.90.19', 'Soluciones inyectables a base de besilato de atracurio o de acyclovir.', '1', 'Kg', '5.00', '0.00') - ,('30049020', '3004.90.20', 'Medicamentos a base de mesilato de imatinib.', '1', 'Kg', '0.00', '0.00') - ,('30049021', '3004.90.21', 'Trinitrato de 1,2,3 propanotriol (nitroglicerina) absorbido en lactosa.', '1', 'Kg', '5.00', '0.00') - ,('30049022', '3004.90.22', 'Medicamentos que contengan azidotimidina (Zidovudina).', '1', 'Kg', '0.00', '0.00') - ,('30049023', '3004.90.23', 'Solución inyectable a base de aprotinina.', '1', 'Kg', '0.00', '0.00') - ,('30049024', '3004.90.24', 'Solución inyectable a base de nimodipina.', '1', 'Kg', '0.00', '0.00') - ,('30049025', '3004.90.25', 'Solución inyectable a base de ciprofloxacina al 0.2%.', '1', 'Kg', '0.00', '0.00') - ,('30049026', '3004.90.26', 'Grageas de liberación prolongada o tabletas de liberación instantánea, ambas a base de nisoldipina.', '1', 'Kg', '0.00', '0.00') - ,('30049027', '3004.90.27', 'A base de saquinavir.', '1', 'Kg', '0.00', '0.00') - ,('30049028', '3004.90.28', 'Tabletas a base de anastrazol.', '1', 'Kg', '0.00', '0.00') - ,('30049029', '3004.90.29', 'Tabletas a base de bicalutamida.', '1', 'Kg', '0.00', '0.00') - ,('30049030', '3004.90.30', 'Tabletas a base de quetiapina.', '1', 'Kg', '0.00', '0.00') - ,('30049031', '3004.90.31', 'Anestésico a base de desflurano.', '1', 'Kg', '0.00', '0.00') - ,('30049032', '3004.90.32', 'Tabletas a base de zafirlukast.', '1', 'Kg', '0.00', '0.00') - ,('30049034', '3004.90.34', 'Tabletas a base de zolmitriptan.', '1', 'Kg', '0.00', '0.00') - ,('30049035', '3004.90.35', 'A base de sulfato de indinavir, o de amprenavir.', '1', 'Kg', '0.00', '0.00') - ,('30049036', '3004.90.36', 'A base de finasteride.', '1', 'Kg', '0.00', '0.00') - ,('30049037', '3004.90.37', 'Tabletas de liberación prolongada, a base de nifedipina.', '1', 'Kg', '0.00', '0.00') - ,('30049038', '3004.90.38', 'A base de octacosanol.', '1', 'Kg', '0.00', '0.00') - ,('30049039', '3004.90.39', 'Medicamentos a base de minerales, en cápsulas de gelatina blanda, aún cuando se presenten en sobres tropicalizados.', '1', 'Kg', '15.00', '0.00') - ,('30049040', '3004.90.40', 'A base de orlistat.', '1', 'Kg', '0.00', '0.00') - ,('30049041', '3004.90.41', 'A base de zalcitabina, en comprimidos.', '1', 'Kg', '0.00', '0.00') - ,('30049043', '3004.90.43', 'Soluciones oftálmicas a base de: norfloxacina; clorhidrato de dorzolamida; o de maleato de timolol con gelán.', '1', 'Kg', '0.00', '0.00') - ,('30049044', '3004.90.44', 'A base de famotidina, en tabletas u obleas liofilizadas.', '1', 'Kg', '0.00', '0.00') - ,('30049045', '3004.90.45', 'A base de montelukast sódico o de benzoato de rizatriptan, en tabletas.', '1', 'Kg', '0.00', '0.00') - ,('30049046', '3004.90.46', 'A base de etofenamato, en solución inyectable.', '1', 'Kg', '0.00', '0.00') - ,('30049047', '3004.90.47', 'Anestésico a base de 2,6-bis-(1-metiletil)-fenol (Propofol), emulsión inyectable estéril.', '1', 'Kg', '0.00', '0.00') - ,('30049048', '3004.90.48', 'Medicamentos a base de cerivastatina, o a base de moxifloxacino.', '1', 'Kg', '0.00', '0.00') - ,('30049049', '3004.90.49', 'Medicamentos a base de: mesilato de nelfinavir; de ganciclovir o de sal sódica de ganciclovir.', '1', 'Kg', '0.00', '0.00') - ,('30049050', '3004.90.50', 'Medicamentos a base de: succinato de metoprolol incluso con hidroclorotiazida; de formoterol; de candesartan cilexetilo incluso con hidroclorotiazida; de omeprazol, sus derivados o sales, o su isómero.', '1', 'Kg', '0.00', '0.00') - ,('30049052', '3004.90.52', 'A base de isotretinoina, cápsulas.', '1', 'Kg', '0.00', '0.00') - ,('30049099', '3004.90.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('30051001', '3005.10.01', 'Tafetán engomado o venditas adhesivas.', '1', 'Kg', '20.00', '0.00') - ,('30051002', '3005.10.02', 'Apósitos de tejido cutáneo de porcino, liofilizados.', '1', 'Kg', '5.00', '0.00') - ,('30051099', '3005.10.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('30059001', '3005.90.01', 'Algodón absorbente o gasas, con sustancias medicinales.', '1', 'Kg', '15.00', '0.00') - ,('30059003', '3005.90.03', 'Hojas o bandas de materias plásticas artificiales esterilizadas para el tratamiento de quemaduras.', '1', 'Kg', '15.00', '0.00') - ,('30059099', '3005.90.99', 'Los demás.', '1', 'Kg', '20.00', '0.00') - ,('30061003', '3006.10.03', 'Catguts u otras ligaduras estériles, excepto lo comprendido en el número de identificación comercial 3006.10.03.01.', '1', 'Kg', '0.00', '0.00') - ,('30061099', '3006.10.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('30063001', '3006.30.01', 'Reactivos de diagnóstico concebidos para usar en el paciente.', '1', 'Kg', '5.00', '0.00') - ,('30063002', '3006.30.02', 'Complejos estanosos liofilizados a base de: pirofosfato de sodio, fitato de calcio, albúmina humana, calcio trisódico, difosfonato de metileno y glucoheptonato de calcio, solución con un contenido de yodo, de la sal meglumina del ácido iocármico al 28%.', '1', 'Kg', '0.00', '0.00') - ,('30063099', '3006.30.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('30064002', '3006.40.02', 'Preparaciones de metales preciosos para obturación dental.', '1', 'Kg', '15.00', '0.00') - ,('30064003', '3006.40.03', 'Cera para cirugía de huesos, a base de cera natural de abeja.', '1', 'Kg', '0.00', '0.00') - ,('30064099', '3006.40.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('30065001', '3006.50.01', 'Botiquines equipados para primeros auxilios.', '1', 'Kg', '15.00', '0.00') - ,('30066001', '3006.60.01', 'Preparaciones químicas anticonceptivas a base de hormonas, de otros productos de la partida 29.37 o de espermicidas.', '1', 'Kg', '15.00', '0.00') - ,('30067001', '3006.70.01', 'Preparaciones en forma de gel, concebidas para ser utilizadas en medicina o veterinaria como lubricante para ciertas partes del cuerpo en operaciones quirúrgicas o exámenes médicos o como nexo entre el cuerpo y los instrumentos médicos.', '1', 'Kg', '0.00', '0.00') - ,('30069101', '3006.91.01', 'Dispositivos identificables para uso en estomas.', '1', 'Kg', '0.00', '0.00') - ,('30069201', '3006.92.01', 'Desechos farmacéuticos.', '1', 'Kg', '15.00', '0.00') - ,('30069301', '3006.93.01', 'Hechos a base de azúcar.', '1', 'Kg', '20.00', '0.00') - ,('30069302', '3006.93.02', 'Hechos a base de almidón u otros productos alimenticios, excepto a base de azúcar.', '1', 'Kg', '15.00', '0.00') - ,('30069303', '3006.93.03', 'En forma líquida para ingestión oral.', '8', 'L', '20.00', '0.00') - ,('30069304', '3006.93.04', 'En forma de kit que contengan medicamentos.', '1', 'Kg', '10.00', '0.00') - ,('30069399', '3006.93.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('31010001', '3101.00.01', 'Abonos de origen animal o vegetal, incluso mezclados entre sí o tratados químicamente; abonos procedentes de la mezcla o del tratamiento químico de productos de origen animal o vegetal.', '1', 'Kg', '0.00', '0.00') - ,('31021001', '3102.10.01', 'Urea, incluso en disolución acuosa.', '1', 'Kg', '0.00', '0.00') - ,('31022101', '3102.21.01', 'Sulfato de amonio.', '1', 'Kg', '0.00', '0.00') - ,('31022999', '3102.29.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('31023002', '3102.30.02', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('31024001', '3102.40.01', 'Mezclas de nitrato de amonio con carbonato de calcio u otras materias inorgánicas sin poder fertilizante.', '1', 'Kg', '0.00', '0.00') - ,('31025001', '3102.50.01', 'Nitrato de sodio.', '1', 'Kg', '0.00', '0.00') - ,('31026001', '3102.60.01', 'Sales dobles y mezclas entre sí de nitrato de calcio y nitrato de amonio.', '1', 'Kg', '0.00', '0.00') - ,('31028001', '3102.80.01', 'Mezclas de urea con nitrato de amonio en disolución acuosa o amoniacal.', '1', 'Kg', '0.00', '0.00') - ,('31029091', '3102.90.91', 'Los demás, incluidas las mezclas no comprendidas en las subpartidas precedentes.', '1', 'Kg', '0.00', '0.00') - ,('31031101', '3103.11.01', 'Con un contenido de pentóxido de difósforo (P2O5) superior o igual al 35% en peso.', '1', 'Kg', '0.00', '0.00') - ,('31031999', '3103.19.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('31039099', '3103.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('31042001', '3104.20.01', 'Cloruro de potasio.', '1', 'Kg', '0.00', '0.00') - ,('31043002', '3104.30.02', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('31049099', '3104.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('31051001', '3105.10.01', 'Productos de este capítulo en tabletas o formas similares o en envases de un peso bruto inferior o igual a 10 kg.', '1', 'Kg', '0.00', '0.00') - ,('31052001', '3105.20.01', 'Abonos minerales o químicos con los tres elementos fertilizantes: nitrógeno, fósforo y potasio.', '1', 'Kg', '0.00', '0.00') - ,('31053001', '3105.30.01', 'Hidrogenoortofosfato de diamonio (fosfato diamónico).', '1', 'Kg', '0.00', '0.00') - ,('31054001', '3105.40.01', 'Dihidrogenoortofosfato de amonio (fosfato monoamónico), incluso mezclado con el hidrogenoortofosfato de diamonio (fosfato diamónico).', '1', 'Kg', '0.00', '0.00') - ,('31055101', '3105.51.01', 'Que contengan nitratos y fosfatos.', '1', 'Kg', '0.00', '0.00') - ,('31055999', '3105.59.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('31056001', '3105.60.01', 'Abonos minerales o químicos con los dos elementos fertilizantes: fósforo y potasio.', '1', 'Kg', '0.00', '0.00') - ,('31059099', '3105.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('32011001', '3201.10.01', 'Extracto de quebracho.', '1', 'Kg', '0.00', '0.00') - ,('32012001', '3201.20.01', 'Extracto de mimosa (acacia).', '1', 'Kg', '0.00', '0.00') - ,('32019099', '3201.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('32021001', '3202.10.01', 'Productos curtientes orgánicos sintéticos.', '1', 'Kg', '7.00', '0.00') - ,('32029099', '3202.90.99', 'Los demás.', '1', 'Kg', '7.00', '0.00') - ,('32030003', '3203.00.03', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('32041101', '3204.11.01', 'Colorantes dispersos con la siguiente clasificación de "Colour Index": Amarillo: 3, 23, 42, 54, 163; Azul: 3, 56, 60, 64, 79, 183, 291, 321; Café: 1; Naranja: 25:1, 29, 30, 37, 44, 89, 90; Negro: 9, 25, 33; Rojo: 1, 5, 17, 30, 54, 55, 55:1, 60, 167:1, 277, 366; Violeta: 1, 27, 93.', '1', 'Kg', '6.00', '0.00') - ,('32041199', '3204.11.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('32041207', '3204.12.07', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('32041305', '3204.13.05', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('32041405', '3204.14.05', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('32041502', '3204.15.02', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('32041605', '3204.16.05', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('32041701', '3204.17.01', 'Colorantes pigmentarios con la siguiente clasificación de "Colour Index": Pigmento amarillo: 24(70600), 93, 94, 95, 96, 97, 108(68420), 109, 110, 112, 120, 128, 129, 138, 139, 147, 151, 154, 155, 156, 173, 179, 183; Pigmento azul: 18(42770:1), 19(42750:1), 60(69800), 61(42765:1), 66(73000); Pigmento café: 22, 23, 25; Pigmento naranja: 31, 34(21115), 36, 38, 43(71105), 48, 49, 60, 61, 65, 66; Pigmento rojo: 37(1205), 38(21120), 88(73312), 122, 123(71140), 144, 148, 149, 166, 170, 175, 176, 177,', '1', 'Kg', '0.00', '0.00') - ,('32041702', '3204.17.02', 'Colorantes pigmentarios: Diarilidas con la siguiente clasificación de "Colour Index": Amarillo: 12, 13, 14, 17, 83; Naranja: 13.', '1', 'Kg', '6.00', '0.00') - ,('32041703', '3204.17.03', 'Colorantes pigmentarios: Ariles o toluidinas con la siguiente clasificación de "Colour Index": Amarillo: 1, 3, 65, 73, 74, 75; Naranja: 5; Rojo: 3.', '1', 'Kg', '6.00', '0.00') - ,('32041704', '3204.17.04', 'Colorantes pigmentarios: Naftoles con la siguiente clasificación de "Colour Index": rojo: 2, 8, 112, 146.', '1', 'Kg', '6.00', '0.00') - ,('32041705', '3204.17.05', 'Colorantes pigmentarios: Laqueados o metalizados con la siguiente clasificación de "Colour Index": Rojo: 48:1, 48:2, 48:3, 48:4, 49:1, 49:2, 52:1, 52:2, 53:1, 57:1, 58:4, 63:1, 63:2.', '1', 'Kg', '6.00', '0.00') - ,('32041706', '3204.17.06', 'Colorantes pigmentarios: Ftalocianinas con la siguiente clasificación de "Colour Index": Azul: 15, 15:1, 15:2, 15:3, 15:4; Verde: 7, 36.', '1', 'Kg', '6.00', '0.00') - ,('32041707', '3204.17.07', 'Colorantes pigmentarios: básicos precipitados con la siguiente clasificación de "Colour Index": Violeta: 1, 3; Rojo: 81.', '1', 'Kg', '6.00', '0.00') - ,('32041708', '3204.17.08', 'Pigmentos, excepto lo comprendido en las fracciones arancelarias 3204.17.01, 3204.17.02, 3204.17.03, 3204.17.04, 3204.17.05, 3204.17.06 y 3204.17.07.', '1', 'Kg', '6.00', '0.00') - ,('32041710', '3204.17.10', 'Pigmentos orgánicos dispersos en polipropileno en una concentración de 25% a 50%, con índice de fluidez de 30 g/10 minutos a 45 g/10 minutos y tamaño de pigmento de 3 micras a 5 micras.', '1', 'Kg', '5.00', '0.00') - ,('32041799', '3204.17.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('32041801', '3204.18.01', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('32041904', '3204.19.04', 'Colorantes al azufre con la siguiente clasificación de "Colour Index": Azul al azufre: 1(53235), 3(53235), 7(53440), 13(53450); Azul al azufre leuco: 1(53235), 3(53235), 7(53440), 13(53450), 19; Café al azufre: 10(53055), 14(53246); Negro al azufre: 1(53185); Negro al azufre leuco: 1(53185), 2(53195), 18; Rojo al azufre: 5(53830); Rojo al azufre leuco: 5(53830), 10(53228).', '1', 'Kg', '3.00', '0.00') - ,('32041906', '3204.19.06', 'Colorante para alimentos con la siguiente clasificación de "Colour Index": Amarillo: 3(15985), 4(19140); Rojo: 3(14720), 7(16255), 9(16185), 17.', '1', 'Kg', '5.00', '0.00') - ,('32041999', '3204.19.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('32042001', '3204.20.01', 'Agentes de blanqueo óptico fijables sobre fibra, correspondientes al grupo de "Colour Index" de los abrillantadores fluorescentes: 135, 140, 162:1, 179, 179+371, 184, 185, 236, 238, 291, 311, 313, 315, 329, 330, 340, 352, 363, 367+372, 373, 374, 374:1, 376, 378, 381, 393.', '1', 'Kg', '0.00', '0.00') - ,('32042002', '3204.20.02', 'Agentes de blanqueo óptico fijables sobre fibra, derivados del ácido diaminoestilbendisulfónico, excepto lo comprendido en la fracción arancelaria 3204.20.01.', '1', 'Kg', '0.00', '0.00') - ,('32042099', '3204.20.99', 'Los demás.', '1', 'Kg', '6.00', '0.00') - ,('32049099', '3204.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('32050002', '3205.00.02', 'Lacas de aluminio en polvo o en dispersión con la siguiente clasificación de "Colour Index": Amarillo 3(15985), 4(19140); Azul 1(73015), 2(42090); Rojo 7(16255), 9: 1(16135:1), 14:1(45430:1).', '1', 'Kg', '5.00', '0.00') - ,('32050099', '3205.00.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('32061101', '3206.11.01', 'Con un contenido de dióxido de titanio superior o igual al 80% en peso, calculado sobre materia seca.', '1', 'Kg', '9.00', '0.00') - ,('32061999', '3206.19.99', 'Los demás.', '1', 'Kg', '9.00', '0.00') - ,('32062001', '3206.20.01', 'Pigmentos inorgánicos con la siguiente clasificación de "Colour Index": Pigmento amarillo 34; Pigmento rojo 104.', '1', 'Kg', '10.00', '0.00') - ,('32062002', '3206.20.02', 'Pigmentos inorgánicos con la siguiente clasificación de "Colour Index": Pigmento amarillo 36; Pigmento verde 15, 17; Pigmento naranja 21.', '1', 'Kg', '10.00', '0.00') - ,('32062003', '3206.20.03', 'Pigmentos y preparaciones a base de compuestos de cromo, excepto lo comprendido en las fracciones arancelarias 3206.20.01 y 3206.20.02.', '1', 'Kg', '6.00', '0.00') - ,('32064102', '3206.41.02', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('32064202', '3206.42.02', 'Litopón y demás pigmentos y preparaciones a base de sulfuro de cinc.', '1', 'Kg', '0.00', '0.00') - ,('32064901', '3206.49.01', 'En forma de dispersiones concentradas en acetato de celulosa, utilizables para colorear en la masa.', '1', 'Kg', '7.00', '0.00') - ,('32064902', '3206.49.02', 'En forma de dispersiones concentradas de polietileno, utilizables para colorear en la masa.', '1', 'Kg', '7.00', '0.00') - ,('32064903', '3206.49.03', 'Pigmentos inorgánicos dispersos en polipropileno en una concentración de 25% a 50%, con índice de fluidez de 30 g/10 minutos a 45 g/10 minutos y tamaño del pigmento de 3 micras a 5 micras.', '1', 'Kg', '7.00', '0.00') - ,('32064904', '3206.49.04', 'Dispersiones de pigmentos nacarados o perlescentes a base de cristales de carbonato de plomo, con un contenido de sólidos fijos inferior o igual al 65%.', '1', 'Kg', '10.00', '0.00') - ,('32064905', '3206.49.05', 'Dispersiones de pigmentos nacarados o perlescentes a base de cristales de carbonato de plomo, con un contenido de sólidos fijos superior al 85% a 100 grados centígrados.', '1', 'Kg', '7.00', '0.00') - ,('32064907', '3206.49.07', 'Pigmento azul 27.', '1', 'Kg', '10.00', '0.00') - ,('32064908', '3206.49.08', 'Pigmentos y preparaciones a base de hexacianoferratos (ferrocianuros o ferricianuros), excepto lo comprendido en la fracción arancelaria 3206.49.07.', '1', 'Kg', '5.00', '0.00') - ,('32064999', '3206.49.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('32065001', '3206.50.01', 'Polvos fluorescentes para tubos, excepto para los tubos de rayos catódicos, anuncios luminosos, lámparas de vapor de mercurio o luz mixta.', '1', 'Kg', '5.00', '0.00') - ,('32065099', '3206.50.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('32071002', '3207.10.02', 'Pigmentos, opacificantes y colores preparados y preparaciones similares.', '1', 'Kg', '0.00', '0.00') - ,('32072001', '3207.20.01', 'Esmaltes cerámicos a base de borosilicatos metálicos.', '1', 'Kg', '0.00', '0.00') - ,('32072099', '3207.20.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('32073001', '3207.30.01', 'Abrillantadores (lustres) líquidos y preparaciones similares.', '1', 'Kg', '0.00', '0.00') - ,('32074003', '3207.40.03', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('32081002', '3208.10.02', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('32082003', '3208.20.03', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('32089099', '3208.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('32091002', '3209.10.02', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('32099099', '3209.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('32100091', '3210.00.91', 'Las demás pinturas y barnices; pigmentos al agua preparados de los tipos utilizados para el acabado del cuero.', '1', 'Kg', '0.00', '0.00') - ,('32110002', '3211.00.02', 'Secativos preparados.', '1', 'Kg', '0.00', '0.00') - ,('32121001', '3212.10.01', 'Hojas para el marcado a fuego.', '1', 'Kg', '0.00', '0.00') - ,('32129099', '3212.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('32131001', '3213.10.01', 'Colores en surtidos.', '1', 'Kg', '0.00', '0.00') - ,('32139099', '3213.90.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('32141001', '3214.10.01', 'Masilla, cementos de resina y demás mástiques, excepto lo comprendido en la fracción arancelaria 3214.10.02; plastes (enduidos) utilizados en pintura.', '1', 'Kg', '3.00', '0.00') - ,('32141002', '3214.10.02', 'Mástique para soldaduras por puntos.', '1', 'Kg', '5.00', '0.00') - ,('32149099', '3214.90.99', 'Los demás.', '1', 'Kg', '3.00', '0.00') - ,('32151103', '3215.11.03', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('32151902', '3215.19.02', 'Para litografía u offset.', '1', 'Kg', '5.00', '0.00') - ,('32151999', '3215.19.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('32159002', '3215.90.02', 'Tintas a base de mezclas de parafinas o grasas con materias colorantes.', '1', 'Kg', '5.00', '0.00') - ,('32159003', '3215.90.03', 'Pastas a base de gelatina.', '1', 'Kg', '5.00', '0.00') - ,('32159099', '3215.90.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('33011201', '3301.12.01', 'De naranja.', '1', 'Kg', '0.00', '0.00') - ,('33011302', '3301.13.02', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('33011907', '3301.19.07', 'De lima, de la variedad Citrus aurantifolia-Christmann Swingle (limón "mexicano").', '1', 'Kg', '5.00', '0.00') - ,('33011999', '3301.19.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('33012401', '3301.24.01', 'De menta piperita (Mentha piperita).', '1', 'Kg', '0.00', '0.00') - ,('33012591', '3301.25.91', 'De las demás mentas.', '1', 'Kg', '0.00', '0.00') - ,('33012999', '3301.29.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('33013001', '3301.30.01', 'Resinoides.', '1', 'Kg', '0.00', '0.00') - ,('33019001', '3301.90.01', 'Oleorresinas de extracción, excepto las comprendidas en la fracción arancelaria 3301.90.06.', '1', 'Kg', '0.00', '0.00') - ,('33019004', '3301.90.04', 'Aguas destiladas aromáticas y soluciones acuosas de aceites esenciales.', '1', 'Kg', '15.00', '0.00') - ,('33019005', '3301.90.05', 'Alcoholados, extractos o tinturas derivados de la raíz de Rawolfia heterophila que contenga el alcaloide llamado reserpina.', '1', 'Kg', '0.00', '0.00') - ,('33019006', '3301.90.06', 'Oleorresinas de extracción, de opio.', '1', 'Kg', '0.00', '0.00') - ,('33019007', '3301.90.07', 'Terpenos de toronja.', '1', 'Kg', '0.00', '0.00') - ,('33019099', '3301.90.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('33021001', '3302.10.01', 'Extractos y concentrados de los tipos utilizados en la elaboración de bebidas que contengan alcohol, a base de sustancias odoríferas.', '8', 'L', '0.00', '0.00') - ,('33021091', '3302.10.91', 'Las demás preparaciones de los tipos utilizados en la elaboración de bebidas que contengan alcohol, a base de sustancias odoríferas.', '8', 'L', '15.00', '0.00') - ,('33021099', '3302.10.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('33029099', '3302.90.99', 'Las demás.', '1', 'Kg', '5.00', '0.00') - ,('33030001', '3303.00.01', 'Aguas de tocador.', '8', 'L', '15.00', '0.00') - ,('33030099', '3303.00.99', 'Los demás.', '8', 'L', '10.00', '0.00') - ,('33041001', '3304.10.01', 'Preparaciones para el maquillaje de los labios.', '1', 'Kg', '10.00', '0.00') - ,('33042001', '3304.20.01', 'Preparaciones para el maquillaje de los ojos.', '1', 'Kg', '10.00', '0.00') - ,('33043001', '3304.30.01', 'Preparaciones para manicuras o pedicuros.', '1', 'Kg', '10.00', '0.00') - ,('33049101', '3304.91.01', 'Polvos, incluidos los compactos.', '1', 'Kg', '10.00', '0.00') - ,('33049901', '3304.99.01', 'Leches cutáneas.', '1', 'Kg', '15.00', '0.00') - ,('33049999', '3304.99.99', 'Las demás.', '1', 'Kg', '10.00', '0.00') - ,('33051001', '3305.10.01', 'Champúes.', '1', 'Kg', '10.00', '0.00') - ,('33052001', '3305.20.01', 'Preparaciones para ondulación o desrizado permanentes.', '1', 'Kg', '10.00', '0.00') - ,('33053001', '3305.30.01', 'Lacas para el cabello.', '1', 'Kg', '10.00', '0.00') - ,('33059099', '3305.90.99', 'Las demás.', '1', 'Kg', '10.00', '0.00') - ,('33061001', '3306.10.01', 'Dentífricos.', '1', 'Kg', '10.00', '0.00') - ,('33062002', '3306.20.02', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('33069099', '3306.90.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('33071001', '3307.10.01', 'Preparaciones para afeitar o para antes o después del afeitado.', '1', 'Kg', '10.00', '0.00') - ,('33072001', '3307.20.01', 'Desodorantes corporales y antitraspirantes.', '1', 'Kg', '10.00', '0.00') - ,('33073001', '3307.30.01', 'Sales perfumadas y demás preparaciones para el baño.', '1', 'Kg', '10.00', '0.00') - ,('33074101', '3307.41.01', 'Agarbatti y demás preparaciones odoríferas que actúan por combustión.', '1', 'Kg', '10.00', '0.00') - ,('33074999', '3307.49.99', 'Las demás.', '1', 'Kg', '10.00', '0.00') - ,('33079099', '3307.90.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('34011101', '3401.11.01', 'De tocador (incluso los medicinales).', '1', 'Kg', '15.00', '0.00') - ,('34011999', '3401.19.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('34012001', '3401.20.01', 'Jabón en otras formas.', '1', 'Kg', '15.00', '0.00') - ,('34013001', '3401.30.01', 'Productos y preparaciones orgánicos tensoactivos para el lavado de la piel, líquidos o en crema, acondicionados para la venta al por menor, aunque contengan jabón.', '1', 'Kg', '10.00', '0.00') - ,('34023101', '3402.31.01', 'Ácidos alquilbenceno sulfónicos lineales y sus sales.', '1', 'Kg', '0.00', '0.00') - ,('34023901', '3402.39.01', 'Sulfonatos sódicos de octenos y de isooctenos.', '1', 'Kg', '5.00', '0.00') - ,('34023999', '3402.39.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('34024101', '3402.41.01', 'Dimetil amidas de los ácidos grasos del tall-oil.', '1', 'Kg', '0.00', '0.00') - ,('34024199', '3402.41.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('34024201', '3402.42.01', 'Productos de la condensación del óxido de etileno o del óxido de propileno con alquilfenoles o alcoholes grasos.', '1', 'Kg', '25.00', '0.00') - ,('34024202', '3402.42.02', 'Polisorbato (Ésteres grasos de sorbitán polioxietilado).', '1', 'Kg', '10.00', '0.00') - ,('34024203', '3402.42.03', 'Poliéter Polisiloxano.', '1', 'Kg', '0.00', '0.00') - ,('34024299', '3402.42.99', 'Los demás.', '1', 'Kg', '6.00', '0.00') - ,('34024999', '3402.49.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('34025001', '3402.50.01', 'Preparaciones a base de N-metil-N-oleoil-taurato de sodio, oleato de sodio y cloruro de sodio.', '1', 'Kg', '0.00', '0.00') - ,('34025002', '3402.50.02', 'Preparación que contenga aceite de ricino, un solvente aromático y un máximo de 80% de aceite de ricino sulfonado.', '1', 'Kg', '0.00', '0.00') - ,('34025003', '3402.50.03', 'Preparaciones tensoactivas a base de lauril sulfato de amonio, de monoetanolamina, de trietanolamina, de potasio o de sodio; lauril éter sulfatos de amonio o sodio.', '1', 'Kg', '0.00', '0.00') - ,('34025004', '3402.50.04', 'Mezclas (limpiadoras, humectantes o emulsificantes) o preparaciones de productos orgánicos sulfonados, adicionadas de carbonatos, hidróxido o fosfatos de potasio o de sodio.', '1', 'Kg', '7.00', '0.00') - ,('34025005', '3402.50.05', 'Tableta limpiadora que contenga papaína estabilizada en lactosa, con clorhidrato de cisteina, edetato disódico y polietilenglicol.', '1', 'Kg', '0.00', '0.00') - ,('34025099', '3402.50.99', 'Los demás.', '1', 'Kg', '6.00', '0.00') - ,('34029001', '3402.90.01', 'Preparaciones a base de nonanoil oxibencen sulfonato.', '1', 'Kg', '0.00', '0.00') - ,('34029002', '3402.90.02', 'Composiciones constituidas por polialquifenol- formaldehído oxietilado y/o polioxipropileno oxietilado, aunque contengan solventes orgánicos, para la fabricación de desmulsificantes para la industria petrolera.', '1', 'Kg', '0.00', '0.00') - ,('34029099', '3402.90.99', 'Las demás.', '1', 'Kg', '3.00', '0.00') - ,('34031101', '3403.11.01', 'Preparaciones para el tratamiento de materias textiles, cueros y pieles, peletería u otras materias.', '1', 'Kg', '6.00', '0.00') - ,('34031901', '3403.19.01', 'Preparación a base de sulfonatos sódicos de los hidrocarburos clorados.', '1', 'Kg', '0.00', '0.00') - ,('34031999', '3403.19.99', 'Las demás.', '1', 'Kg', '3.00', '0.00') - ,('34039101', '3403.91.01', 'Preparaciones para el tratamiento de materias textiles, cueros y pieles, peletería u otras materias.', '1', 'Kg', '6.00', '0.00') - ,('34039999', '3403.99.99', 'Las demás.', '1', 'Kg', '6.00', '0.00') - ,('34042001', '3404.20.01', 'De poli(oxietileno) (polietilenglicol).', '1', 'Kg', '10.00', '0.00') - ,('34049001', '3404.90.01', 'Ceras polietilénicas.', '1', 'Kg', '5.00', '0.00') - ,('34049002', '3404.90.02', 'De lignito modificado químicamente.', '1', 'Kg', '0.00', '0.00') - ,('34049099', '3404.90.99', 'Las demás.', '1', 'Kg', '6.00', '0.00') - ,('34051001', '3405.10.01', 'Betunes, cremas y preparaciones similares para el calzado o para cueros y pieles.', '1', 'Kg', '0.00', '0.00') - ,('34052002', '3405.20.02', 'Encáusticos y preparaciones similares para la conservación de muebles de madera, parqués u otras manufacturas de madera.', '1', 'Kg', '35.00', '0.00') - ,('34053001', '3405.30.01', 'Abrillantadores (lustres) y preparaciones similares para carrocerías, excepto las preparaciones para lustrar metal.', '1', 'Kg', '15.00', '0.00') - ,('34054001', '3405.40.01', 'Pastas, polvos y demás preparaciones para fregar.', '1', 'Kg', '15.00', '0.00') - ,('34059001', '3405.90.01', 'Lustres para metal.', '1', 'Kg', '15.00', '0.00') - ,('34059099', '3405.90.99', 'Las demás.', '1', 'Kg', '15.00', '0.00') - ,('34060001', '3406.00.01', 'Velas, cirios y artículos similares.', '1', 'Kg', '20.00', '0.00') - ,('34070003', '3407.00.03', 'Estuco o yeso preparado en polvo o en pasta para trabajos dentales.', '1', 'Kg', '0.00', '0.00') - ,('34070004', '3407.00.04', 'Conjunto para impresiones dentales, conteniendo principalmente una pasta a base de hule o materias plásticas artificiales o sintéticas, un acelerador y un adhesivo, acondicionados para su venta al por menor.', '1', 'Kg', '0.00', '0.00') - ,('34070005', '3407.00.05', 'Preparaciones denominadas "ceras para odontología", excepto lo comprendido en el número de identificación comercial 3407.00.05.01.', '1', 'Kg', '0.00', '0.00') - ,('34070099', '3407.00.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('35011001', '3501.10.01', 'Caseína.', '1', 'Kg', '0.00', '0.00') - ,('35019001', '3501.90.01', 'Colas de caseína.', '1', 'Kg', '10.00', '0.00') - ,('35019003', '3501.90.03', 'Carboximetil caseina, grado fotográfico, en solución.', '1', 'Kg', '5.00', '0.00') - ,('35019099', '3501.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('35021101', '3502.11.01', 'Seca.', '1', 'Kg', '0.00', '0.00') - ,('35021999', '3502.19.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('35022001', '3502.20.01', 'Lactoalbúmina, incluidos los concentrados de dos o más proteínas del lactosuero.', '1', 'Kg', '5.00', '0.00') - ,('35029099', '3502.90.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('35030001', '3503.00.01', 'Gelatina, excepto de grado fotográfico y farmacéutico.', '1', 'Kg', '5.00', '0.00') - ,('35030099', '3503.00.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('35040007', '3504.00.07', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('35051001', '3505.10.01', 'Dextrina y demás almidones y féculas modificados.', '1', 'Kg', '5.00', '0.00') - ,('35052001', '3505.20.01', 'Colas.', '1', 'Kg', '0.00', '0.00') - ,('35061002', '3506.10.02', 'Adhesivos a base de dextrinas, almidones y féculas.', '1', 'Kg', '10.00', '0.00') - ,('35061099', '3506.10.99', 'Los demás.', '1', 'Kg', '6.00', '0.00') - ,('35069101', '3506.91.01', 'Adhesivos anaeróbicos.', '1', 'Kg', '7.00', '0.00') - ,('35069102', '3506.91.02', 'Pegamentos a base de nitrocelulosa.', '1', 'Kg', '10.00', '0.00') - ,('35069199', '3506.91.99', 'Los demás.', '1', 'Kg', '30.00', '0.00') - ,('35069999', '3506.99.99', 'Los demás.', '1', 'Kg', '6.00', '0.00') - ,('35071001', '3507.10.01', 'Cuajo y sus concentrados.', '1', 'Kg', '5.00', '0.00') - ,('35079002', '3507.90.02', 'Papaína.', '1', 'Kg', '5.00', '0.00') - ,('35079006', '3507.90.06', 'Diastasa de Aspergillus oryzae.', '1', 'Kg', '5.00', '0.00') - ,('35079008', '3507.90.08', 'Amilasas; proteasas; mezcla de proteasas y amilasas.', '1', 'Kg', '5.00', '0.00') - ,('35079009', '3507.90.09', 'Preparación de enzima proteolítica obtenida por fermentación sumergida del Bacillus subtilis y/o Bacillus licheniformis.', '1', 'Kg', '5.00', '0.00') - ,('35079099', '3507.90.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('36010001', '3601.00.01', 'Pólvora sin humo o negra.', '1', 'Kg', '0.00', '0.00') - ,('36010099', '3601.00.99', 'Las demás.', '1', 'Kg', '5.00', '0.00') - ,('36020002', '3602.00.02', 'Dinamita gelatina.', '1', 'Kg', '0.00', '0.00') - ,('36020003', '3602.00.03', 'Cartuchos o cápsulas microgeneradores de gas utilizados en la fabricación de cinturones de seguridad para vehículos automotores.', '6', 'Pza', '0.00', '0.00') - ,('36020099', '3602.00.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('36031001', '3603.10.01', 'Para minas con núcleo de pólvora negra.', '1', 'Kg', '5.00', '0.00') - ,('36031099', '3603.10.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('36032001', '3603.20.01', 'Cordones detonantes.', '1', 'Kg', '5.00', '0.00') - ,('36033001', '3603.30.01', 'Cebos fulminantes.', '1', 'Kg', '0.00', '0.00') - ,('36034001', '3603.40.01', 'Cápsulas fulminantes.', '1', 'Kg', '0.00', '0.00') - ,('36035001', '3603.50.01', 'Inflamadores.', '1', 'Kg', '0.00', '0.00') - ,('36036001', '3603.60.01', 'Detonadores eléctricos.', '1', 'Kg', '0.00', '0.00') - ,('36041001', '3604.10.01', 'Artículos para fuegos artificiales.', '1', 'Kg', '15.00', '0.00') - ,('36049099', '3604.90.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('36050001', '3605.00.01', 'Fósforos (cerillas), excepto los artículos de pirotecnia de la partida 36.04.', '1', 'Kg', '15.00', '0.00') - ,('36061001', '3606.10.01', 'Combustibles líquidos y gases combustibles licuados en recipientes de los tipos utilizados para cargar o recargar encendedores o mecheros, de capacidad inferior o igual a 300 cm3.', '1', 'Kg', '5.00', '0.00') - ,('36069001', '3606.90.01', 'Combustibles sólidos a base de alcohol.', '1', 'Kg', '15.00', '0.00') - ,('36069002', '3606.90.02', 'Ferrocerio y otras aleaciones pirofóricas, cualquiera que sea su forma de presentación.', '1', 'Kg', '0.00', '0.00') - ,('36069099', '3606.90.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('37011002', '3701.10.02', 'Placas fotográficas para radiografías, de uso dental.', '1', 'Kg', '0.00', '0.00') - ,('37011099', '3701.10.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('37012001', '3701.20.01', 'Películas autorrevelables.', '1', 'Kg', '5.00', '0.00') - ,('37013091', '3701.30.91', 'Las demás placas y películas planas en las que por lo menos un lado sea superior a 255 mm.', '1', 'Kg', '0.00', '0.00') - ,('37019101', '3701.91.01', 'Para fotografía en colores (policroma).', '1', 'Kg', '10.00', '0.00') - ,('37019901', '3701.99.01', 'Placas secas para fotografía.', '1', 'Kg', '5.00', '0.00') - ,('37019904', '3701.99.04', 'Chapas sensibilizadas litoplanográficas trimetálicas para fotolitografía (offset).', '1', 'Kg', '5.00', '0.00') - ,('37019999', '3701.99.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('37021001', '3702.10.01', 'En rollos maestros para radiografías, con peso unitario superior o igual a 100 kg.', '1', 'Kg', '0.00', '0.00') - ,('37021099', '3702.10.99', 'Las demás.', '1', 'Kg', '5.00', '0.00') - ,('37023102', '3702.31.02', 'Para fotografía en colores (policroma).', '1', 'Kg', '10.00', '0.00') - ,('37023201', '3702.32.01', 'Películas autorrevelables.', '1', 'Kg', '10.00', '0.00') - ,('37023299', '3702.32.99', 'Las demás.', '1', 'Kg', '5.00', '0.00') - ,('37023999', '3702.39.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('37024101', '3702.41.01', 'En rollos maestros, para cinematógrafos o exposiciones fotográficas, sin movimiento, con peso unitario superior o igual a 100 kg.', '1', 'Kg', '0.00', '0.00') - ,('37024199', '3702.41.99', 'Las demás.', '1', 'Kg', '5.00', '0.00') - ,('37024201', '3702.42.01', 'En rollos maestros, para exposiciones fotográficas, no perforadas, de uso en las artes gráficas, con peso unitario superior o igual a 100 kg.', '1', 'Kg', '0.00', '0.00') - ,('37024202', '3702.42.02', 'En rollos maestros, para cinematógrafos o exposiciones fotográficas, sin movimiento, con peso unitario superior o igual a 100 kg.', '1', 'Kg', '0.00', '0.00') - ,('37024299', '3702.42.99', 'Las demás.', '1', 'Kg', '5.00', '0.00') - ,('37024301', '3702.43.01', 'De anchura superior a 610 mm y de longitud inferior o igual a 200 m.', '1', 'Kg', '5.00', '0.00') - ,('37024401', '3702.44.01', 'Películas autorrevelables.', '1', 'Kg', '10.00', '0.00') - ,('37024499', '3702.44.99', 'Las demás.', '1', 'Kg', '5.00', '0.00') - ,('37025201', '3702.52.01', 'De anchura inferior o igual a 16 mm.', '1', 'Kg', '5.00', '0.00') - ,('37025301', '3702.53.01', 'De anchura superior a 16 mm pero inferior o igual a 35 mm y longitud inferior o igual a 30 m, para diapositivas.', '1', 'Kg', '5.00', '0.00') - ,('37025401', '3702.54.01', 'De anchura superior a 16 mm pero inferior o igual a 35 mm y longitud inferior o igual a 30 m, excepto para diapositivas.', '1', 'Kg', '0.00', '0.00') - ,('37025501', '3702.55.01', 'De anchura superior a 16 mm pero inferior o igual a 35 mm y longitud superior a 30 m.', '1', 'Kg', '0.00', '0.00') - ,('37025601', '3702.56.01', 'De anchura superior a 35 mm.', '1', 'Kg', '5.00', '0.00') - ,('37029601', '3702.96.01', 'De anchura inferior o igual a 35 mm y longitud inferior o igual a 30 m.', '1', 'Kg', '5.00', '0.00') - ,('37029701', '3702.97.01', 'De anchura inferior o igual a 35 mm y longitud superior a 30 m.', '1', 'Kg', '5.00', '0.00') - ,('37029801', '3702.98.01', 'De anchura superior a 35 mm.', '1', 'Kg', '5.00', '0.00') - ,('37031002', '3703.10.02', 'En rollos de anchura superior a 610 mm.', '1', 'Kg', '0.00', '0.00') - ,('37032001', '3703.20.01', 'Papeles para fotografía.', '1', 'Kg', '0.00', '0.00') - ,('37032099', '3703.20.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('37039001', '3703.90.01', 'Papel positivo a base de óxido de cinc y colorante estabilizador.', '1', 'Kg', '0.00', '0.00') - ,('37039002', '3703.90.02', 'Papeles para fotografía.', '1', 'Kg', '0.00', '0.00') - ,('37039003', '3703.90.03', 'Papel para heliografía, excepto al ferrocianuro.', '1', 'Kg', '0.00', '0.00') - ,('37039099', '3703.90.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('37040001', '3704.00.01', 'Placas, películas, papel, cartón y textiles, fotográficos, impresionados pero sin revelar.', '1', 'Kg', '5.00', '0.00') - ,('37050001', '3705.00.01', 'Placas y películas ortocromáticas reveladas, para la impresión de periódicos, libros, fascículos, revistas, material didáctico o colecciones.', '1', 'Kg', '0.00', '0.00') - ,('37050002', '3705.00.02', 'Películas fotográficas en rollos, con anchura de 35 mm, sin sonido, con reproducción de documentos, de textos o de ilustraciones, técnicas o científicas, para enseñanza o con fines culturales, reconocibles como diseñadas exclusivamente para instituciones de educación o similares.', '1', 'Kg', '5.00', '0.00') - ,('37050003', '3705.00.03', 'Microfilmes.', '1', 'Kg', '0.00', '0.00') - ,('37050004', '3705.00.04', 'Para la reproducción offset, excepto lo comprendido en la fracción arancelaria 3705.00.01.', '1', 'Kg', '5.00', '0.00') - ,('37050099', '3705.00.99', 'Las demás.', '1', 'Kg', '10.00', '0.00') - ,('37061001', '3706.10.01', 'Positivas.', '1', 'Kg', '10.00', '0.00') - ,('37061002', '3706.10.02', 'Negativas, incluso las denominadas internegativas o "master".', '1', 'Kg', '5.00', '0.00') - ,('37069001', '3706.90.01', 'Películas cinematográficas educativas, aun cuando tengan impresión directa de sonido.', '1', 'Kg', '5.00', '0.00') - ,('37069099', '3706.90.99', 'Las demás.', '1', 'Kg', '10.00', '0.00') - ,('37071001', '3707.10.01', 'Emulsiones para sensibilizar superficies.', '1', 'Kg', '0.00', '0.00') - ,('37079002', '3707.90.02', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('38011001', '3801.10.01', 'Barras o bloques.', '1', 'Kg', '5.00', '0.00') - ,('38011099', '3801.10.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('38012001', '3801.20.01', 'Grafito coloidal o semicoloidal.', '1', 'Kg', '0.00', '0.00') - ,('38013002', '3801.30.02', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('38019001', '3801.90.01', 'Monofilamentos constituidos por grafito soportado en fibra de vidrio, aun cuando se presenten formando mechas.', '1', 'Kg', '5.00', '0.00') - ,('38019099', '3801.90.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('38021001', '3802.10.01', 'Carbón activado.', '1', 'Kg', '6.00', '0.00') - ,('38029006', '3802.90.06', 'Tierras de fuller, activadas.', '1', 'Kg', '10.00', '0.00') - ,('38029099', '3802.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('38030001', '3803.00.01', 'Tall oil, incluso refinado.', '1', 'Kg', '0.00', '0.00') - ,('38040002', '3804.00.02', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('38051002', '3805.10.02', 'Los demás.', '8', 'L', '0.00', '0.00') - ,('38059001', '3805.90.01', 'Aceite de pino.', '8', 'L', '5.00', '0.00') - ,('38059099', '3805.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('38061001', '3806.10.01', 'Colofonias.', '1', 'Kg', '0.00', '0.00') - ,('38061099', '3806.10.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('38062001', '3806.20.01', 'Sales de colofonias, de ácidos resínicos o de derivados de colofonias o de ácidos resínicos, excepto las sales de aductos de colofonias.', '1', 'Kg', '10.00', '0.00') - ,('38063003', '3806.30.03', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('38069001', '3806.90.01', 'Mezcla de alcohol tetrahidroabietílico, alcohol dihidroabietílico y alcohol dehidroabietílico.', '1', 'Kg', '5.00', '0.00') - ,('38069099', '3806.90.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('38070001', '3807.00.01', 'Alquitranes de madera; aceites de alquitrán de madera; creosota de madera; metileno (nafta de madera); pez vegetal; pez de cervecería y preparaciones similares a base de colofonia, de ácidos resínicos o de pez vegetal.', '1', 'Kg', '0.00', '0.00') - ,('38085201', '3808.52.01', 'DDT (ISO) (clofenotano (DCI)), acondicionado en envases con un contenido en peso neto inferior o igual a 300 g.', '22', 'PROH', '0.00', '0.00') - ,('38085901', '3808.59.01', 'Desinfectantes.', '1', 'Kg', '5.00', '0.00') - ,('38085902', '3808.59.02', 'Formulados a base de: Aldicarb; Alaclor; Azinfos metílico; Captafol; Carbofurano (ISO); Clordano; DDT; Endosulfan; Fosfamidón; Hexaclorociclohexano (HCH (ISO)), incluido el lindano (ISO, DCI); Paratión metílico; Triclorfón (ISO).', '22', 'PROH', '0.00', '0.00') - ,('38085999', '3808.59.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('38086101', '3808.61.01', 'Acondicionados en envases con un contenido en peso neto inferior o igual a 300 g.', '1', 'Kg', '0.00', '0.00') - ,('38086201', '3808.62.01', 'Acondicionados en envases con un contenido en peso neto superior a 300 g pero inferior o igual a 7.5 kg.', '1', 'Kg', '0.00', '0.00') - ,('38086999', '3808.69.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('38089106', '3808.91.06', 'Formulados a base de fentoato.', '22', 'PROH', '0.00', '0.00') - ,('38089199', '3808.91.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('38089203', '3808.92.03', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('38089304', '3808.93.04', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('38089401', '3808.94.01', 'Poli (dicloruro de oxietileno-(dimetilamonio)- etileno-(dimetilamonio)-etileno), en solución acuosa.', '1', 'Kg', '0.00', '0.00') - ,('38089402', '3808.94.02', 'Formulados a base de derivados de la isotiazolinona.', '1', 'Kg', '0.00', '0.00') - ,('38089499', '3808.94.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('38089903', '3808.99.03', 'Acaricidas a base de ester etílico del ácido O,O-dimetilditiofosforil-fenilacético (fentoato).', '22', 'PROH', '0.00', '0.00') - ,('38089999', '3808.99.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('38091001', '3809.10.01', 'A base de materias amiláceas.', '1', 'Kg', '5.00', '0.00') - ,('38099101', '3809.91.01', 'Preparaciones suavizantes de telas a base de aminas cuaternarias, acondicionadas para la venta al por menor.', '1', 'Kg', '6.00', '0.00') - ,('38099199', '3809.91.99', 'Los demás.', '1', 'Kg', '6.00', '0.00') - ,('38099201', '3809.92.01', 'Preparaciones de polietileno con cera, con un contenido de sólidos superior o igual al 40%, con una viscosidad de 200 a 250 centipoises a 25°C, y con un pH de 7.0 a 8.5.', '1', 'Kg', '5.00', '0.00') - ,('38099202', '3809.92.02', 'Preparación constituida por fibrilas de polipropileno y pasta de celulosa, en placas.', '1', 'Kg', '0.00', '0.00') - ,('38099299', '3809.92.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('38099301', '3809.93.01', 'De los tipos utilizados en la industria del cuero o industrias similares.', '1', 'Kg', '6.00', '0.00') - ,('38101001', '3810.10.01', 'Preparaciones para el decapado de metal; pastas y polvos para soldar, constituidos por metal y otros productos.', '1', 'Kg', '3.00', '0.00') - ,('38109099', '3810.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('38111102', '3811.11.02', 'A base de compuestos de plomo.', '1', 'Kg', '0.00', '0.00') - ,('38111999', '3811.19.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('38112101', '3811.21.01', 'Sulfonatos y/o fenatos de calcio de los hidrocarburos y sus derivados.', '1', 'Kg', '5.00', '0.00') - ,('38112102', '3811.21.02', 'Derivados de ácido y/o anhídrido poliisobutenil succínico, incluyendo amida, imida o ésteres.', '1', 'Kg', '5.00', '0.00') - ,('38112103', '3811.21.03', 'Ditiofosfato de cinc disubstituidos con radicales de C3 a C18, y sus derivados.', '1', 'Kg', '5.00', '0.00') - ,('38112104', '3811.21.04', 'Mezclas a base de poliisobutileno o diisobutileno sulfurizados.', '1', 'Kg', '0.00', '0.00') - ,('38112105', '3811.21.05', 'Mezclas a base de ácido dodecilsuccínico y dodecil succinato de alquilo.', '1', 'Kg', '0.00', '0.00') - ,('38112106', '3811.21.06', 'Sales de O,O-dihexil ditiofosfato de alquilaminas primarias con radicales alquilo de C10 a C14.', '1', 'Kg', '0.00', '0.00') - ,('38112107', '3811.21.07', 'Aditivos para aceites lubricantes cuando se presenten a granel, excepto lo comprendido en las fracciones arancelarias 3811.21.01, 3811.21.02, 3811.21.03, 3811.21.04, 3811.21.05 y 3811.21.06.', '1', 'Kg', '5.00', '0.00') - ,('38112199', '3811.21.99', 'Los demás.', '8', 'L', '15.00', '0.00') - ,('38112904', '3811.29.04', 'Mezclas a base de poliisobutileno o diisobutileno sulfurizados.', '1', 'Kg', '0.00', '0.00') - ,('38112905', '3811.29.05', 'Mezclas a base de ácido dodecilsuccínico y dodecil succinato de alquilo.', '1', 'Kg', '0.00', '0.00') - ,('38112906', '3811.29.06', 'Sales de O,O-dihexil ditiofosfato de alquilaminas primarias con radicales alquilo de C10 a C14.', '1', 'Kg', '0.00', '0.00') - ,('38112999', '3811.29.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('38119001', '3811.90.01', 'Aditivos para combustóleo a base de óxido de magnesio; aditivos para combustóleo a base de agentes emulsionantes.', '1', 'Kg', '0.00', '0.00') - ,('38119099', '3811.90.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('38121001', '3812.10.01', 'Aceleradores de vulcanización preparados.', '1', 'Kg', '3.00', '0.00') - ,('38122001', '3812.20.01', 'Plastificantes compuestos para caucho o plástico.', '1', 'Kg', '5.00', '0.00') - ,('38123101', '3812.31.01', 'Mezclas de oligómeros de 2,2,4-trimetil-1,2-dihidroquinoleína (TMQ).', '1', 'Kg', '0.00', '0.00') - ,('38123902', '3812.39.02', 'Mezcla antiozonante a base de o-tolil-o-anilina-p-fenilen-diamina, difenil-p-fenilendiamina y di-o-tolil-p-fenilendiamina.', '1', 'Kg', '5.00', '0.00') - ,('38123903', '3812.39.03', 'Mezcla de nitrilos de ácidos grasos.', '1', 'Kg', '0.00', '0.00') - ,('38123904', '3812.39.04', '2,2,4-Trimetil-1,2-dihidro-quinolina polimerizada.', '1', 'Kg', '0.00', '0.00') - ,('38123905', '3812.39.05', 'Poli ((6-((1,1,3,3 tetrametil butil)-imino)-1,3,5-triazina-2,4-diil)(2-(2,2,6,6-tetrametil-piperidil)-imino)-hexametilén- (4-(2,2,6,6-tetrametil-piperidil)-imino)).', '1', 'Kg', '0.00', '0.00') - ,('38123999', '3812.39.99', 'Los demás.', '1', 'Kg', '6.00', '0.00') - ,('38130001', '3813.00.01', 'Preparaciones y cargas para aparatos extintores; granadas y bombas extintoras.', '1', 'Kg', '5.00', '0.00') - ,('38140001', '3814.00.01', 'Disolventes y diluyentes orgánicos compuestos, no expresados ni comprendidos en otra parte; preparaciones para quitar pinturas o barnices.', '1', 'Kg', '6.00', '0.00') - ,('38151103', '3815.11.03', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('38151203', '3815.12.03', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('38151901', '3815.19.01', 'A base de pentóxido de vanadio.', '1', 'Kg', '10.00', '0.00') - ,('38151999', '3815.19.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('38159002', '3815.90.02', 'Iniciadores de polimerización a base de peróxidos o peroxidicarbonatos orgánicos.', '1', 'Kg', '5.00', '0.00') - ,('38159099', '3815.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('38160002', '3816.00.02', 'Mortero refractario, a base de dióxido de silicio, óxido de aluminio y óxido de calcio.', '1', 'Kg', '5.00', '0.00') - ,('38160006', '3816.00.06', 'Compuestos con un contenido de: arenas silíceas superior o igual al 44% pero inferior o igual al 46%, carbono superior o igual al 24% pero inferior o igual al 26% y de carburo de silicio superior o igual al 29% pero inferior o igual al 31%.', '1', 'Kg', '5.00', '0.00') - ,('38160007', '3816.00.07', 'A base de dolomita calcinada, aun cuando contenga magnesita calcinada.', '1', 'Kg', '5.00', '0.00') - ,('38160099', '3816.00.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('38170001', '3817.00.01', 'Mezcla a base de dodecilbenceno.', '1', 'Kg', '0.00', '0.00') - ,('38170002', '3817.00.02', 'Mezcla de dialquilbencenos.', '1', 'Kg', '0.00', '0.00') - ,('38170003', '3817.00.03', 'Alquilbenceno lineal (LAB).', '1', 'Kg', '0.00', '0.00') - ,('38170099', '3817.00.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('38180001', '3818.00.01', 'Elementos químicos dopados para uso en electrónica, en discos, obleas ("wafers") o formas análogas; compuestos químicos dopados para uso en electrónica.', '1', 'Kg', '0.00', '0.00') - ,('38190003', '3819.00.03', 'Líquidos para transmisiones hidráulicas a base de ésteres fosfóricos o hidrocarburos clorados y aceites minerales.', '8', 'L', '5.00', '0.00') - ,('38190004', '3819.00.04', 'Líquidos para frenos hidráulicos, a base de alcoholes y aceites fijos, excepto los presentados para la venta al por menor.', '8', 'L', '7.00', '0.00') - ,('38190099', '3819.00.99', 'Los demás.', '8', 'L', '6.00', '0.00') - ,('38200001', '3820.00.01', 'Preparaciones anticongelantes y líquidos preparados para descongelar.', '8', 'L', '6.00', '0.00') - ,('38210001', '3821.00.01', 'Medios de cultivo preparados para el desarrollo o mantenimiento de microorganismos (incluidos los virus y organismos similares) o de células vegetales, humanas o animales.', '1', 'Kg', '5.00', '0.00') - ,('38221101', '3822.11.01', 'Presentados en forma de Kit.', '1', 'Kg', '0.00', '0.00') - ,('38221199', '3822.11.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('38221201', '3822.12.01', 'Reactivos de diagnóstico para determinación de pruebas inmunológicas por medio de anticuerpos monoclonales, mezclados, incluso en forma de juegos (Kit).', '1', 'Kg', '0.00', '0.00') - ,('38221299', '3822.12.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('38221301', '3822.13.01', 'Reactivos hemoclasificadores.', '1', 'Kg', '5.00', '0.00') - ,('38221399', '3822.13.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('38221901', '3822.19.01', 'Reactivos de diagnóstico para determinación de pruebas inmunológicas por medio de anticuerpos monoclonales, mezclados, incluso en forma de juegos (Kit).', '1', 'Kg', '0.00', '0.00') - ,('38221902', '3822.19.02', 'Placas, hojas, láminas, hojas o tiras, de papel, impregnadas o recubiertas con reactivos para diagnóstico o laboratorio.', '1', 'Kg', '0.00', '0.00') - ,('38221903', '3822.19.03', 'Juegos o surtidos de reactivos de diagnósticos, para las determinaciones en el laboratorio de hematología.', '1', 'Kg', '0.00', '0.00') - ,('38221999', '3822.19.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('38229099', '3822.90.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('38231101', '3823.11.01', 'Ácido esteárico.', '1', 'Kg', '0.00', '0.00') - ,('38231203', '3823.12.03', 'Ácido oléico, cuyas características sean "Titer" menor de 25°C índice de yodo de 80 a 100; color Gardner 12 máximo, índice de acidez de 195 a 204, índice de saponificación de 197 a 206, composición de ácido oléico 70% mínimo.', '1', 'Kg', '0.00', '0.00') - ,('38231301', '3823.13.01', 'Ácidos grasos del "tall oil".', '1', 'Kg', '0.00', '0.00') - ,('38231903', '3823.19.03', 'Ácido palmítico, cuyas características sean: "Titer" de 53°C a 62°C, índice de yodo inferior o igual a 2, color Gardner de 1 a 12, índice de acidez de 206 a 220, índice de saponificación de 207 a 221, composición de ácido palmítico de superior o igual al 64%.', '1', 'Kg', '5.00', '0.00') - ,('38231999', '3823.19.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('38237002', '3823.70.02', 'Los demás.', '8', 'L', '0.00', '0.00') - ,('38241001', '3824.10.01', 'Preparaciones aglutinantes para moldes o núcleos de fundición.', '1', 'Kg', '0.00', '0.00') - ,('38243002', '3824.30.02', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('38244001', '3824.40.01', 'Para cemento.', '1', 'Kg', '10.00', '0.00') - ,('38244099', '3824.40.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('38245001', '3824.50.01', 'Preparaciones a base de hierro molido, arena silícea y cemento hidráulico.', '1', 'Kg', '5.00', '0.00') - ,('38245002', '3824.50.02', 'Mezclas de arena de circonio, arena silícea y resina.', '1', 'Kg', '0.00', '0.00') - ,('38245003', '3824.50.03', 'Aglutinantes vitrificadores o ligas cerámicas.', '1', 'Kg', '0.00', '0.00') - ,('38245099', '3824.50.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('38246001', '3824.60.01', 'Sorbitol, excepto el de la subpartida 2905.44.', '1', 'Kg', '5.00', '0.00') - ,('38248101', '3824.81.01', 'Que contengan oxirano (óxido de etileno).', '1', 'Kg', '0.00', '0.00') - ,('38248201', '3824.82.01', 'Que contengan bifenilos polibromados (PBB), bifenilos policlorados (PCB) o terfenilos policlorados (PCT).', '1', 'Kg', '0.00', '0.00') - ,('38248301', '3824.83.01', 'Que contengan fosfato de tris (2,3-dibromopropilo).', '1', 'Kg', '0.00', '0.00') - ,('38248401', '3824.84.01', 'Que contengan aldrina (ISO), canfecloro (ISO) (toxafeno), clordano (ISO), clordecona (ISO), DDT (ISO) (clofenotano (DCI), 1,1,1-tricloro-2,2-bis (p-clorofenil)etano), dieldrina (ISO, DCI), endosulfán (ISO), endrina (ISO), heptacloro (ISO) o mirex (ISO).', '1', 'Kg', '5.00', '0.00') - ,('38248501', '3824.85.01', 'Que contengan 1,2,3,4,5,6-hexaclorociclohexano (HCH (ISO)), incluido el lindano (ISO, DCI).', '1', 'Kg', '5.00', '0.00') - ,('38248601', '3824.86.01', 'Que contengan pentaclorobenceno (ISO) o hexaclorobenceno (ISO).', '1', 'Kg', '5.00', '0.00') - ,('38248701', '3824.87.01', 'Que contengan ácido perfluorooctano sulfónico o sus sales, perfluorooctano sulfonamidas o fluoruro de perfluorooctano sulfonilo.', '1', 'Kg', '5.00', '0.00') - ,('38248801', '3824.88.01', 'Que contengan éteres tetra-, penta-, hexa-, hepta- u octabromodifenílicos.', '1', 'Kg', '5.00', '0.00') - ,('38248901', '3824.89.01', 'Que contengan parafinas cloradas de cadena corta.', '1', 'Kg', '5.00', '0.00') - ,('38249101', '3824.91.01', 'Mezclas y preparaciones constituidas esencialmente de metilfosfonato de (5-etil-2-metil-2-óxido-1,3,2-dioxafosfinan-5-il)metil metilo y metilfosfonato de bis[(5-etil-2-metil-2-óxido-1,3,2-dioxafosfinan-5-il) metilo].', '1', 'Kg', '5.00', '0.00') - ,('38249201', '3824.92.01', 'Ésteres de poliglicol del ácido metilfosfónico.', '1', 'Kg', '5.00', '0.00') - ,('38249901', '3824.99.01', 'Preparaciones borra tinta.', '1', 'Kg', '6.00', '0.00') - ,('38249902', '3824.99.02', 'Composiciones a base de materias vegetales para desincrustar calderas.', '1', 'Kg', '0.00', '0.00') - ,('38249903', '3824.99.03', 'Desincrustantes para calderas, a base de materias minerales, aun cuando contengan productos orgánicos.', '1', 'Kg', '0.00', '0.00') - ,('38249906', '3824.99.06', 'Soluciones anticoagulantes para sangre humana en envases iguales o menores a 500 cm³.', '1', 'Kg', '0.00', '0.00') - ,('38249908', '3824.99.08', 'Aceites minerales sulfonados, insolubles en agua.', '1', 'Kg', '0.00', '0.00') - ,('38249909', '3824.99.09', 'Indicadores de temperatura por fusión.', '1', 'Kg', '0.00', '0.00') - ,('38249910', '3824.99.10', 'Conservadores de forrajes a base de formiato de calcio y nitrato de sodio.', '1', 'Kg', '0.00', '0.00') - ,('38249911', '3824.99.11', 'Mezclas a base de poliéter-alcohol alifático.', '1', 'Kg', '0.00', '0.00') - ,('38249913', '3824.99.13', 'Composiciones a base de materias minerales para el sellado y limpieza de radiadores.', '1', 'Kg', '0.00', '0.00') - ,('38249914', '3824.99.14', 'Sulfato de manganeso, con un contenido de otros sulfatos inferior o igual al 25%.', '1', 'Kg', '0.00', '0.00') - ,('38249916', '3824.99.16', 'Mezclas de fosfato de cresilo y derivados sulfurados.', '1', 'Kg', '0.00', '0.00') - ,('38249918', '3824.99.18', 'Polvo desecado proveniente de la fermentación bacteriana, con un conteniendo de kanamicina superior o igual al 30% pero inferior o igual al 45%.', '1', 'Kg', '0.00', '0.00') - ,('38249919', '3824.99.19', 'Mezclas a base de compuestos cíclicos polimetil siloxánicos, con un contenido de alfa, omega-dihidroxi-dimetil polisiloxano inferior o igual al 60%.', '1', 'Kg', '0.00', '0.00') - ,('38249920', '3824.99.20', 'Mezcla de difenilo y óxido de difenilo.', '1', 'Kg', '0.00', '0.00') - ,('38249922', '3824.99.22', 'Sílice en solución coloidal.', '1', 'Kg', '0.00', '0.00') - ,('38249923', '3824.99.23', 'Pentaclorotiofenol con aditivos de efecto activador y dispersante.', '1', 'Kg', '0.00', '0.00') - ,('38249925', '3824.99.25', 'Mezclas de N,N-dimetil-alquilaminas o N,N- dialquil-metilaminas.', '1', 'Kg', '0.00', '0.00') - ,('38249926', '3824.99.26', 'Mezcla de éteres monoalílicos de mono-,di-, y trimetilolfenoles.', '1', 'Kg', '0.00', '0.00') - ,('38249928', '3824.99.28', 'Mezcla constituida por: ácidos resínicos de la colofonía con un contenido superior o igual al 27% pero inferior o igual 29%, compuestos fenólicos de alto peso molecular con un contenido superior o igual al 56% pero inferior o igual al 58%, tales como flobafenos e hidroxometoxiestilbeno, y de "compuestos neutros" formados por cera, terpenos polimerizados y dimetoxiestilbeno con un contenido superior o igual al 14% pero inferior o igual al 16%.', '1', 'Kg', '0.00', '0.00') - ,('38249929', '3824.99.29', 'Pasta de coque de petróleo con azufre.', '1', 'Kg', '0.00', '0.00') - ,('38249930', '3824.99.30', '1,1,1-Tri(4-metil-3-isocianfenilcarbamoilmetil) propano, en acetato de etilo.', '1', 'Kg', '0.00', '0.00') - ,('38249933', '3824.99.33', 'Mezcla a base de dos o más siliciuros.', '1', 'Kg', '0.00', '0.00') - ,('38249935', '3824.99.35', 'Preparación a base de carbón activado y óxido de cobre.', '1', 'Kg', '0.00', '0.00') - ,('38249937', '3824.99.37', 'Preparación a base de vermiculita y turba, con o sin perlita.', '1', 'Kg', '0.00', '0.00') - ,('38249938', '3824.99.38', 'Preparación a base de alúmina, silicatos y carbonatos alcalinos y carbono.', '1', 'Kg', '0.00', '0.00') - ,('38249940', '3824.99.40', 'Preparaciones para detectar fallas en materiales, incluso los polvos magnetizables coloreados.', '1', 'Kg', '0.00', '0.00') - ,('38249941', '3824.99.41', 'Cartuchos con preparados químicos que al reaccionar producen luz fría.', '1', 'Kg', '0.00', '0.00') - ,('38249943', '3824.99.43', 'Mezcla de alquil-amina, donde el radical alquilo sea de 16 a 22 átomos de carbono.', '1', 'Kg', '0.00', '0.00') - ,('38249944', '3824.99.44', 'Aglutinantes vitrificadores o ligas cerámicas.', '1', 'Kg', '0.00', '0.00') - ,('38249945', '3824.99.45', 'Mezcla a base de politetrafluoroetileno y sílica gel.', '1', 'Kg', '0.00', '0.00') - ,('38249946', '3824.99.46', 'Residuales provenientes de la fermentación de la clorotetraciclina cuyo contenido de clorotetraciclina sea inferior o igual al 30%.', '1', 'Kg', '0.00', '0.00') - ,('38249947', '3824.99.47', 'Residuales provenientes de la fermentación de la estreptomicina cuyo contenido de estreptomicina sea inferior o igual al 30%.', '1', 'Kg', '0.00', '0.00') - ,('38249948', '3824.99.48', 'Preparación para producir niebla artificial ("Humo líquido").', '1', 'Kg', '0.00', '0.00') - ,('38249949', '3824.99.49', 'Anhídrido poliisobutenil succínico, diluido en aceite mineral.', '1', 'Kg', '0.00', '0.00') - ,('38249950', '3824.99.50', 'Monooleato de glicerol adicionado de estearato de sorbitan etoxilado.', '1', 'Kg', '0.00', '0.00') - ,('38249951', '3824.99.51', 'Mezclas de ésteres dimetílicos de los ácidos adípico, glutárico y succínico.', '1', 'Kg', '0.00', '0.00') - ,('38249952', '3824.99.52', 'Mezclas de N,N-dimetilamidas de ácidos grasos de aceite de soya.', '1', 'Kg', '0.00', '0.00') - ,('38249953', '3824.99.53', 'Dialquenil hidrógeno fosfito con radicales alquenilos de 12 a 20 átomos de carbono.', '1', 'Kg', '0.00', '0.00') - ,('38249954', '3824.99.54', 'Resinas de guayaco, modificada con sustancias nitrogenadas.', '1', 'Kg', '0.00', '0.00') - ,('38249955', '3824.99.55', 'Disolución de metilato de sodio con una concentración al 25%.', '1', 'Kg', '0.00', '0.00') - ,('38249958', '3824.99.58', 'Mezcla de mono, di y triglicéridos de ácidos saturados de longitud de cadena C12 a C18.', '1', 'Kg', '0.00', '0.00') - ,('38249959', '3824.99.59', 'Mezclas orgánicas, extractantes a base de dodecilsalicilaldoxima, con alcohol tridecílico y keroseno.', '1', 'Kg', '0.00', '0.00') - ,('38249960', '3824.99.60', 'Sólidos de fermentación de la nistatina.', '1', 'Kg', '0.00', '0.00') - ,('38249961', '3824.99.61', 'Ácidos bencensulfónicos mono o polisubstituidos por radicales alquilo de C10 a C28.', '1', 'Kg', '0.00', '0.00') - ,('38249962', '3824.99.62', 'Mezcla de alcoholes constituida, en promedio, por: isobutanol 61%, n-pentanol 24%, metil-2-butanol 12% y metil-3-butanol con un contenido superior o igual al 1% pero inferior o igual al 3%.', '1', 'Kg', '0.00', '0.00') - ,('38249963', '3824.99.63', 'Mezcla de polietilen poliaminas con un contenido de nitrógeno superior o igual al 32% pero inferior o igual al 38%.', '1', 'Kg', '0.00', '0.00') - ,('38249964', '3824.99.64', 'Mezclas de poliésteres derivados de benzotriazol.', '1', 'Kg', '0.00', '0.00') - ,('38249965', '3824.99.65', 'Mezcla de ceras de polietileno, parafina y dioctil estanato ditioglicólico, en resina fenólica y estearato de calcio como vehículos.', '1', 'Kg', '0.00', '0.00') - ,('38249966', '3824.99.66', 'Mezcla de alcoholato de sodio y alcohol alifático polivalente.', '1', 'Kg', '0.00', '0.00') - ,('38249967', '3824.99.67', 'Mezcla de éteres glicidílicos alifáticos donde los grupos alquílicos son predominantemente C12 y C14.', '1', 'Kg', '0.00', '0.00') - ,('38249968', '3824.99.68', 'Mezcla de 3,5-diterbutil-4-hidroxibencil monoetil fosfonato de calcio y cera de polietileno.', '1', 'Kg', '0.00', '0.00') - ,('38249969', '3824.99.69', 'Mezcla de dimetil metil fosfonato y fosfonato de triarilo.', '1', 'Kg', '0.00', '0.00') - ,('38249970', '3824.99.70', 'Mezcla de metilen y anilina y polimetilen polianilinas.', '1', 'Kg', '0.00', '0.00') - ,('38249972', '3824.99.72', 'Preparación selladora de ponchaduras de neumáticos automotrices, a base de etilenglicol.', '1', 'Kg', '0.00', '0.00') - ,('38249973', '3824.99.73', 'Preparación a base de borodecanoato de cobalto y silicato de calcio.', '1', 'Kg', '0.00', '0.00') - ,('38249974', '3824.99.74', 'Aluminato de magnesio ("espinela") enriquecido con óxido de magnesio.', '1', 'Kg', '0.00', '0.00') - ,('38249975', '3824.99.75', 'Preparación a base de óxido de magnesio y sílice, electrofundido, y de partículas recubiertas con silicón, llamado "Óxido de magnesio grado eléctrico".', '1', 'Kg', '0.00', '0.00') - ,('38249976', '3824.99.76', 'Ácidos nafténicos; Dinonilnaftalen sulfonato de plomo.', '1', 'Kg', '0.00', '0.00') - ,('38249983', '3824.99.83', 'Soluciones y mezclas, de las utilizadas para lo comprendido en la fracción arancelaria 8543.40.01.', '22', 'PROH', '0.00', '0.00') - ,('38249999', '3824.99.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('38251001', '3825.10.01', 'Desechos municipales.', '1', 'Kg', '0.00', '0.00') - ,('38252001', '3825.20.01', 'Lodos de depuración.', '1', 'Kg', '0.00', '0.00') - ,('38253001', '3825.30.01', 'Desechos clínicos.', '1', 'Kg', '0.00', '0.00') - ,('38254101', '3825.41.01', 'Halogenados.', '1', 'Kg', '0.00', '0.00') - ,('38254999', '3825.49.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('38255001', '3825.50.01', 'Desechos de soluciones decapantes, fluidos hidráulicos, líquidos para frenos y líquidos anticongelantes.', '1', 'Kg', '0.00', '0.00') - ,('38256103', '3825.61.03', 'Que contengan principalmente componentes orgánicos.', '1', 'Kg', '0.00', '0.00') - ,('38256999', '3825.69.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('38259099', '3825.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('38260001', '3826.00.01', 'Biodiésel y sus mezclas, sin aceites de petróleo o de mineral bituminoso o con un contenido inferior al 70% en peso de estos aceites.', '8', 'L', '5.00', '0.00') - ,('38271101', '3827.11.01', 'Que contengan clorofluorocarburos (CFC), incluso con hidroclorofluorocarburos (HCFC), perfluorocarburos (PFC) o hidrofluorocarburos (HFC).', '1', 'Kg', '0.00', '0.00') - ,('38271201', '3827.12.01', 'Que contengan hidrobromofluorocarburos (HBFC).', '1', 'Kg', '0.00', '0.00') - ,('38271301', '3827.13.01', 'Que contengan tetracloruro de carbono.', '1', 'Kg', '0.00', '0.00') - ,('38271401', '3827.14.01', 'Que contengan 1,1,1-tricloroetano (metilcloroformo).', '1', 'Kg', '0.00', '0.00') - ,('38272001', '3827.20.01', 'Que contengan bromoclorodifluorometano (Halón-1211), bromotrifluorometano (Halón-1301) o dibromotetrafluoroetanos (Halón-2402).', '1', 'Kg', '0.00', '0.00') - ,('38273101', '3827.31.01', 'Que contengan sustancias de las subpartidas 2903.41 a 2903.48.', '1', 'Kg', '0.00', '0.00') - ,('38273291', '3827.32.91', 'Las demás, que contengan sustancias de las subpartidas 2903.71 a 2903.75.', '1', 'Kg', '0.00', '0.00') - ,('38273999', '3827.39.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('38274001', '3827.40.01', 'Que contengan bromuro de metilo (bromometano) o bromoclorometano.', '1', 'Kg', '0.00', '0.00') - ,('38275101', '3827.51.01', 'Que contengan trifluorometano (HFC-23).', '1', 'Kg', '0.00', '0.00') - ,('38275999', '3827.59.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('38276101', '3827.61.01', 'Con un contenido de 1,1,1-trifluoroetano (HFC-143a), superior o igual al 15% en masa.', '1', 'Kg', '0.00', '0.00') - ,('38276291', '3827.62.91', 'Las demás, no comprendidas en subpartidas anteriores, con un contenido de pentafluoroetano (HFC-125) superior o igual al 55% en masa, pero que no contengan derivados fluorados de los hidrocarburos acíclicos no saturados (HFO).', '1', 'Kg', '0.00', '0.00') - ,('38276391', '3827.63.91', 'Las demás, no comprendidas en subpartidas anteriores, con un contenido de pentafluoroetano (HFC-125) superior o igual al 40% en masa.', '1', 'Kg', '0.00', '0.00') - ,('38276491', '3827.64.91', 'Las demás, no comprendidas en subpartidas anteriores, con un contenido de 1,1,1,2-tetrafluoroetano (HFC-134a) superior o igual al 30% en masa, pero que no contengan derivados fluorados de los hidrocarburos acíclicos no saturados (HFO).', '1', 'Kg', '0.00', '0.00') - ,('38276591', '3827.65.91', 'Las demás, no comprendidas en subpartidas anteriores, con un contenido de difluorometano (HFC-32) superior o igual al 20% en masa y de pentafluoroetano (HFC-125) superior o igual al 20% en masa.', '1', 'Kg', '0.00', '0.00') - ,('38276891', '3827.68.91', 'Las demás, no comprendidas en subpartidas anteriores, que contengan sustancias de las subpartidas 2903.41 a 2903.48.', '1', 'Kg', '0.00', '0.00') - ,('38276999', '3827.69.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('38279099', '3827.90.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('39011003', '3901.10.03', 'Polietileno de baja densidad lineal (LLDPE).', '1', 'Kg', '5.00', '0.00') - ,('39012001', '3901.20.01', 'Polietileno de densidad superior o igual a 0.94.', '1', 'Kg', '5.00', '0.00') - ,('39013001', '3901.30.01', 'Copolímeros de etileno y acetato de vinilo.', '1', 'Kg', '5.00', '0.00') - ,('39014001', '3901.40.01', 'Copolímeros de etileno y alfa-olefina de densidad inferior a 0.94.', '1', 'Kg', '5.00', '0.00') - ,('39019001', '3901.90.01', 'Copolímero de etileno-anhídrido maléico.', '1', 'Kg', '0.00', '0.00') - ,('39019099', '3901.90.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('39021001', '3902.10.01', 'Sin adición de negro de humo.', '1', 'Kg', '6.00', '0.00') - ,('39021099', '3902.10.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('39022002', '3902.20.02', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('39023001', '3902.30.01', 'Copolímeros de propileno, sin adición de negro de humo.', '1', 'Kg', '6.00', '0.00') - ,('39023099', '3902.30.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('39029001', '3902.90.01', 'Terpolímero de metacrilato de metilo-butadieno-estireno.', '1', 'Kg', '0.00', '0.00') - ,('39029099', '3902.90.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('39031101', '3903.11.01', 'Expandible.', '1', 'Kg', '35.00', '0.00') - ,('39031999', '3903.19.99', 'Los demás.', '1', 'Kg', '35.00', '0.00') - ,('39032001', '3903.20.01', 'Copolímeros de estireno-acrilonitrilo (SAN).', '1', 'Kg', '6.00', '0.00') - ,('39033001', '3903.30.01', 'Copolímeros de acrilonitrilo-butadieno-estireno (ABS).', '1', 'Kg', '5.00', '0.00') - ,('39039001', '3903.90.01', 'Copolímeros de estireno-vinilo.', '1', 'Kg', '6.00', '0.00') - ,('39039002', '3903.90.02', 'Copolímeros de estireno-maléico.', '1', 'Kg', '6.00', '0.00') - ,('39039003', '3903.90.03', 'Copolímero clorometilado de estireno-divinil- benceno.', '1', 'Kg', '7.00', '0.00') - ,('39039005', '3903.90.05', 'Copolímeros del estireno, excepto los elastoméricos termoplásticos y lo comprendido en las fracciones arancelarias 3903.90.01 a la 3903.90.03.', '1', 'Kg', '6.00', '0.00') - ,('39039099', '3903.90.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('39041001', '3904.10.01', 'Poli(cloruro de vinilo) (P.V.C.) obtenido por el proceso de polimerización en emulsión que, en dispersión (50% resina y 50% dioctilftalato), tenga una finura de 7 Hegman mínimo.', '1', 'Kg', '7.00', '0.00') - ,('39041002', '3904.10.02', 'Poli(cloruro de vinilo) (P.V.C.) obtenido por el proceso de polimerización en emulsión, cuyo tamaño de partícula sea de 30 micras, que al sinterizarse en una hoja de 0.65 mm de espesor se humecte uniformemente en un segundo (en electrolito de 1.280 de gravedad específica) y con un tamaño de poro de 14 a 18 micras con una porosidad Gurley mayor de 35 segundos (con un Gurley No. 4110).', '1', 'Kg', '5.00', '0.00') - ,('39041003', '3904.10.03', 'Poli(cloruro de vinilo) (P.V.C.) obtenido por los procesos de polimerización en masa o suspensión.', '1', 'Kg', '3.00', '0.00') - ,('39041004', '3904.10.04', 'Poli(cloruro de vinilo) (P.V.C.) obtenido por el proceso de polimerización en emulsión o dispersión, excepto lo comprendido en las fracciones arancelarias 3904.10.01 y 3904.10.02.', '1', 'Kg', '7.00', '0.00') - ,('39041099', '3904.10.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('39042101', '3904.21.01', 'Dispersiones acuosas de poli(cloruro de vinilo) (P.V.C.), de viscosidades menores de 200 centipoises.', '1', 'Kg', '5.00', '0.00') - ,('39042199', '3904.21.99', 'Los demás.', '1', 'Kg', '7.00', '0.00') - ,('39042201', '3904.22.01', 'Plastificados.', '1', 'Kg', '5.00', '0.00') - ,('39043001', '3904.30.01', 'Copolímero de cloruro de vinilo-acetato de vinilo, sin cargas ni modificantes, cuyo tiempo de disolución total a 25°C en una solución de una parte de copolímero, y 4 partes de metil-etilcetona, sea menor de 30 minutos.', '1', 'Kg', '0.00', '0.00') - ,('39043099', '3904.30.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('39044001', '3904.40.01', 'Copolímeros de cloruro de vinilo-vinil isobutil éter.', '1', 'Kg', '0.00', '0.00') - ,('39044099', '3904.40.99', 'Los demás.', '1', 'Kg', '7.00', '0.00') - ,('39045001', '3904.50.01', 'Polímeros de cloruro de vinilideno.', '1', 'Kg', '7.00', '0.00') - ,('39046101', '3904.61.01', 'Politetrafluoroetileno.', '1', 'Kg', '0.00', '0.00') - ,('39046999', '3904.69.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('39049099', '3904.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('39051201', '3905.12.01', 'En dispersión acuosa.', '1', 'Kg', '6.00', '0.00') - ,('39051901', '3905.19.01', 'Emulsiones y soluciones a base de poli(acetato de vinilo), excepto lo comprendido en las fracciones arancelarias 3905.19.02 y 3905.19.03.', '1', 'Kg', '10.00', '0.00') - ,('39051902', '3905.19.02', 'Poli(acetato de vinilo) resina termoplástica grado alimenticio, con peso molecular de 30,000 máximo y punto de ablandamiento de 90°C máximo.', '1', 'Kg', '5.00', '0.00') - ,('39051903', '3905.19.03', 'Poli(acetato de vinilo), resina termoplástica, con peso molecular mayor de 30,000 y punto de ablandamiento mayor a 90°C.', '1', 'Kg', '0.00', '0.00') - ,('39051999', '3905.19.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('39052101', '3905.21.01', 'En dispersión acuosa.', '1', 'Kg', '10.00', '0.00') - ,('39052901', '3905.29.01', 'Emulsiones y soluciones a base de copolímeros de acetato de vinilo, excepto lo comprendido en la fracción arancelaria 3905.29.02.', '1', 'Kg', '10.00', '0.00') - ,('39052902', '3905.29.02', 'Copolímero de acetato de vinilo-vinil pirrolidona.', '1', 'Kg', '0.00', '0.00') - ,('39052999', '3905.29.99', 'Los demás.', '1', 'Kg', '7.00', '0.00') - ,('39053001', '3905.30.01', 'Poli(alcohol vinílico), incluso con grupos acetato sin hidrolizar.', '1', 'Kg', '0.00', '0.00') - ,('39059101', '3905.91.01', 'Copolímeros.', '1', 'Kg', '0.00', '0.00') - ,('39059999', '3905.99.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('39061002', '3906.10.02', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('39069001', '3906.90.01', 'Poli(acrilato de sodio) al 12% en solución acuosa.', '1', 'Kg', '10.00', '0.00') - ,('39069002', '3906.90.02', 'Resinas acrílicas hidroxiladas, sus copolímeros y terpolímeros.', '1', 'Kg', '6.00', '0.00') - ,('39069003', '3906.90.03', 'Poliacrilatos, excepto lo comprendido en las fracciones arancelarias 3906.90.01, 3906.90.05, 3906.90.06, 3906.90.08, 3906.90.09.', '1', 'Kg', '6.00', '0.00') - ,('39069004', '3906.90.04', 'Poli(acrilonitrilo), sin pigmentar.', '1', 'Kg', '10.00', '0.00') - ,('39069005', '3906.90.05', 'Poli(acrilato de n-butilo).', '1', 'Kg', '7.00', '0.00') - ,('39069006', '3906.90.06', 'Terpolímero de (etileno-ácido acrilico-éster del ácido acrílico).', '1', 'Kg', '7.00', '0.00') - ,('39069007', '3906.90.07', 'Copolímero de (acrilamida-cloruro de metacriloil oxietil trimetil amonio).', '1', 'Kg', '7.00', '0.00') - ,('39069008', '3906.90.08', 'Poli(acrilato de sodio) en polvo, con granulometría de 90 a 850 micras y absorción mínima de 200 ml por g.', '1', 'Kg', '0.00', '0.00') - ,('39069009', '3906.90.09', 'Copolímero de metacrilato de metilo-acrilato de etilo, en polvo.', '1', 'Kg', '6.00', '0.00') - ,('39069010', '3906.90.10', 'Poli(metacrilato de sodio).', '1', 'Kg', '7.00', '0.00') - ,('39069099', '3906.90.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('39071005', '3907.10.05', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('39072101', '3907.21.01', 'Metilfosfonato de bis(polioxietileno).', '1', 'Kg', '5.00', '0.00') - ,('39072901', '3907.29.01', 'Resinas de poli(2,2-bis(3,4-dicarboxifenoxi)) fenil propanol-2-fenilen bis imida.', '1', 'Kg', '0.00', '0.00') - ,('39072902', '3907.29.02', 'Poli(óxido de 2,6-dimetilfenileno) en polvo, sin cargas, refuerzos, pigmentos, estabilizadores y desmoldantes (Poli(óxido de fenileno)).', '1', 'Kg', '0.00', '0.00') - ,('39072903', '3907.29.03', 'Resinas poliéter modificadas, con función oxazolina o uretano-amina o uretano- poliolefinas, solubles en agua después de neutralizar con ácidos orgánicos débiles.', '1', 'Kg', '0.00', '0.00') - ,('39072904', '3907.29.04', 'Poli(tetrametilen éter glicol).', '1', 'Kg', '0.00', '0.00') - ,('39072905', '3907.29.05', 'Poli(etilenglicol).', '1', 'Kg', '6.00', '0.00') - ,('39072906', '3907.29.06', 'Poli(propilenglicol).', '1', 'Kg', '6.00', '0.00') - ,('39072907', '3907.29.07', 'Poli(oxipropilenetilenglicol)-alcohol, de peso molecular hasta 8,000.', '1', 'Kg', '6.00', '0.00') - ,('39072999', '3907.29.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('39073002', '3907.30.02', 'Resinas epóxidas tipo etoxilinas cicloalifaticas o novolacas.', '1', 'Kg', '5.00', '0.00') - ,('39073099', '3907.30.99', 'Los demás.', '1', 'Kg', '6.00', '0.00') - ,('39074004', '3907.40.04', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('39075002', '3907.50.02', 'Resinas alcídicas, modificadas con uretanos.', '1', 'Kg', '7.00', '0.00') - ,('39075099', '3907.50.99', 'Los demás.', '1', 'Kg', '6.00', '0.00') - ,('39076101', '3907.61.01', 'Con un índice de viscosidad superior o igual a 78 ml/g.', '1', 'Kg', '35.00', '0.00') - ,('39076901', '3907.69.01', 'Resinas de poli(tereftalato de etileno) solubles en cloruro de metileno.', '1', 'Kg', '7.00', '0.00') - ,('39076999', '3907.69.99', 'Los demás.', '1', 'Kg', '35.00', '0.00') - ,('39077001', '3907.70.01', 'Poli(ácido láctico).', '1', 'Kg', '0.00', '0.00') - ,('39079101', '3907.91.01', 'Elastómero termoplástico a base de copolímeros de poli(ester-éter) en aleación con polibutilentereftalato, con o sin cargas, refuerzos, pigmentos, aditivos y modificadores en gránulos.', '1', 'Kg', '0.00', '0.00') - ,('39079199', '3907.91.99', 'Los demás.', '1', 'Kg', '35.00', '0.00') - ,('39079901', '3907.99.01', 'Poliésteres del ácido adípico, modificados.', '1', 'Kg', '5.00', '0.00') - ,('39079902', '3907.99.02', 'Resina poliéster derivada del ácido adípico y glicoles.', '1', 'Kg', '5.00', '0.00') - ,('39079903', '3907.99.03', 'Resina termoplástica derivada de la policondensación del 1,4-butanodiol y dimetil tereftalato (polibutilen tereftalato) con cargas, refuerzos, pigmentos y aditivos, en gránulos.', '1', 'Kg', '0.00', '0.00') - ,('39079904', '3907.99.04', 'Resina termoplástica derivada de la policondensación del 1,4-butanodiol y dimetil tereftalato (polibutilen tereftalato) sin cargas, refuerzos, pigmentos y aditivos.', '1', 'Kg', '0.00', '0.00') - ,('39079905', '3907.99.05', 'Polibutilen tereftalato, excepto lo comprendido en las fracciones arancelarias 3907.99.03 y 3907.99.04.', '1', 'Kg', '5.00', '0.00') - ,('39079906', '3907.99.06', 'Resinas de poliéster con función oxirano.', '1', 'Kg', '0.00', '0.00') - ,('39079907', '3907.99.07', 'Polímero de éster de ácido carbónico con glicoles.', '1', 'Kg', '0.00', '0.00') - ,('39079908', '3907.99.08', 'Resina para electrodepositación catódica poliéster-epoxiaminada, con un contenido de sólidos superior o igual al 60% pero inferior o igual al 80%.', '1', 'Kg', '0.00', '0.00') - ,('39079909', '3907.99.09', 'Resina poliéster a base de ácido p-hidroxibenzoico, ácido tereftálico y p-dihidroxibifenilo, con aditivos, reforzantes y catalizadores.', '1', 'Kg', '0.00', '0.00') - ,('39079910', '3907.99.10', 'Poliésteres del ácido tereftálico-ácido isoftálico-butanodiol-polietilenglicol, excepto lo comprendido en la fracción arancelaria 3907.99.07.', '1', 'Kg', '0.00', '0.00') - ,('39079911', '3907.99.11', 'Resina del ácido carbanílico.', '1', 'Kg', '0.00', '0.00') - ,('39079999', '3907.99.99', 'Los demás.', '1', 'Kg', '6.00', '0.00') - ,('39081001', '3908.10.01', 'Polímeros de la hexametilendiamina y ácido dodecandioico.', '1', 'Kg', '5.00', '0.00') - ,('39081003', '3908.10.03', 'Superpoliamida del ácido 11-aminoundecanoico.', '1', 'Kg', '0.00', '0.00') - ,('39081004', '3908.10.04', 'Polímeros de la caprolactama sin pigmentar, ni contener materias colorantes.', '1', 'Kg', '10.00', '0.00') - ,('39081005', '3908.10.05', 'Poliamidas o superpoliamidas, excepto lo comprendido en las fracciones arancelarias 3908.10.01 a la 3908.10.04, 3908.10.06, 3908.10.07 y 3908.10.08.', '1', 'Kg', '6.00', '0.00') - ,('39081006', '3908.10.06', 'Poliamida 12.', '1', 'Kg', '3.00', '0.00') - ,('39081007', '3908.10.07', 'Poliamida del adipato de hexametilendiamina con cargas, pigmentos o modificantes.', '1', 'Kg', '7.00', '0.00') - ,('39081008', '3908.10.08', 'Poliamida del adipato de hexametilendiamina sin cargas, pigmentos o modificantes.', '1', 'Kg', '0.00', '0.00') - ,('39089001', '3908.90.01', 'Poli(epsilón-caprolactama), en forma líquida o pastosa, incluidas las emulsiones, dispersiones y soluciones.', '1', 'Kg', '0.00', '0.00') - ,('39089002', '3908.90.02', 'Terpoliamidas cuyo contenido de la lactama del ácido dodecandióico o de la dodelactama y ácido undecanoico sea superior o igual al 20% pero inferior o igual al 60%, adipato de hexametilendiamina superior o igual al 10% pero inferior o igual al 50% y de caprolactama superior o igual al 10% pero inferior o igual al 45%, en peso.', '1', 'Kg', '0.00', '0.00') - ,('39089003', '3908.90.03', 'Resina de poliamida obtenida a partir del meta-xileno de amida (MXD6).', '1', 'Kg', '5.00', '0.00') - ,('39089099', '3908.90.99', 'Las demás.', '1', 'Kg', '6.00', '0.00') - ,('39091001', '3909.10.01', 'Resinas ureicas; resinas de tiourea.', '1', 'Kg', '35.00', '0.00') - ,('39092002', '3909.20.02', 'Melamina formaldehído, en forma líquida o pastosa incluidas las emulsiones, aun cuando estén pigmentadas, excepto con negro de humo.', '1', 'Kg', '0.00', '0.00') - ,('39092099', '3909.20.99', 'Las demás.', '1', 'Kg', '35.00', '0.00') - ,('39093101', '3909.31.01', 'Poli(metilenfenilisocianato) (MDI en bruto, MDI polimérico).', '1', 'Kg', '3.00', '0.00') - ,('39093901', '3909.39.01', 'Aminoplastos en solución incolora.', '1', 'Kg', '0.00', '0.00') - ,('39093999', '3909.39.99', 'Las demás.', '1', 'Kg', '3.00', '0.00') - ,('39094001', '3909.40.01', 'Resinas de fenol-formaldehído, modificadas con vinil formal, con disolventes y anticomburentes.', '1', 'Kg', '10.00', '0.00') - ,('39094003', '3909.40.03', 'Resinas de fenol-formaldehído éterificadas sin modificar.', '1', 'Kg', '5.00', '0.00') - ,('39094099', '3909.40.99', 'Los demás.', '1', 'Kg', '6.00', '0.00') - ,('39095004', '3909.50.04', 'Prepolímeros y polímeros de difenilmetan diisocianato y polimetilen polifenil isocianato con un contenido de isocianato libre superior o igual al 17%, excepto lo comprendido en la fracción arancelaria 3909.50.05.', '1', 'Kg', '7.00', '0.00') - ,('39095005', '3909.50.05', 'Prepolímero obtenido a partir de difenilmetan 4,4´-diisocianato y polipropilenglicol.', '1', 'Kg', '7.00', '0.00') - ,('39095099', '3909.50.99', 'Los demás.', '1', 'Kg', '6.00', '0.00') - ,('39100001', '3910.00.01', 'Resinas de silicona ("potting compound") para empleo electrónico.', '1', 'Kg', '5.00', '0.00') - ,('39100002', '3910.00.02', 'Resinas de poli(metil-fenil-siloxano), aun cuando estén pigmentadas.', '1', 'Kg', '0.00', '0.00') - ,('39100003', '3910.00.03', 'Alfa-Omega-Dihidroxi-dimetil polisiloxano, excepto lo comprendido en la fracción arancelaria 3910.00.05.', '1', 'Kg', '0.00', '0.00') - ,('39100004', '3910.00.04', 'Elastómero de silicona reticulable en caliente ("Caucho de silicona").', '1', 'Kg', '0.00', '0.00') - ,('39100005', '3910.00.05', 'Alfa-Omega-Dihidroxi-dimetil siloxano con una viscosidad superior o igual a 50 cps, pero inferior a 100 cps, y tamaño de cadena de 50 a 120 monómeros, libre de cíclicos.', '1', 'Kg', '0.00', '0.00') - ,('39100099', '3910.00.99', 'Los demás.', '1', 'Kg', '6.00', '0.00') - ,('39111001', '3911.10.01', 'Resinas de petróleo, resinas de cumarona, resinas de indeno, resinas de cumarona-indeno y politerpenos.', '1', 'Kg', '0.00', '0.00') - ,('39112001', '3911.20.01', 'Poli(1,3-fenilen metilfosfonato).', '1', 'Kg', '6.00', '0.00') - ,('39119001', '3911.90.01', 'Resinas de policondensación de cetonas y aldehídos, modificadas.', '1', 'Kg', '0.00', '0.00') - ,('39119002', '3911.90.02', 'Resinas maléicas a base de butadieno o polibutadieno modificadas con ácido resolcarboxílico para terbutil fenol, solubles al agua sin pigmentar en una proporción superior o igual al 65% pero inferior o igual al 70%.', '1', 'Kg', '0.00', '0.00') - ,('39119003', '3911.90.03', 'Resinas de anacardo modificadas.', '1', 'Kg', '0.00', '0.00') - ,('39119004', '3911.90.04', 'Resinas provenientes de la condensación del alcohol furfurílico con el formaldehído incluso con modificantes.', '1', 'Kg', '5.00', '0.00') - ,('39119005', '3911.90.05', 'Resinas de poliamida-epiclorhidrina.', '1', 'Kg', '0.00', '0.00') - ,('39119006', '3911.90.06', 'Polihidantoinas.', '1', 'Kg', '0.00', '0.00') - ,('39119007', '3911.90.07', 'Homopolímero de alfa metilestireno.', '1', 'Kg', '7.00', '0.00') - ,('39119099', '3911.90.99', 'Los demás.', '1', 'Kg', '6.00', '0.00') - ,('39121101', '3912.11.01', 'Sin plastificar.', '1', 'Kg', '0.00', '0.00') - ,('39121201', '3912.12.01', 'Plastificados.', '1', 'Kg', '0.00', '0.00') - ,('39122002', '3912.20.02', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('39123101', '3912.31.01', 'Carboximetilcelulosa y sus sales.', '1', 'Kg', '10.00', '0.00') - ,('39123901', '3912.39.01', 'Metilcelulosa, en forma líquida o pastosa, incluidas las emulsiones, dispersiones o soluciones.', '1', 'Kg', '7.00', '0.00') - ,('39123902', '3912.39.02', 'Etilcelulosa.', '1', 'Kg', '7.00', '0.00') - ,('39123903', '3912.39.03', 'Metilcelulosa, excepto lo comprendido en la fracción arancelaria 3912.39.01.', '1', 'Kg', '7.00', '0.00') - ,('39123999', '3912.39.99', 'Las demás.', '1', 'Kg', '10.00', '0.00') - ,('39129001', '3912.90.01', 'Celulosa en polvo.', '1', 'Kg', '0.00', '0.00') - ,('39129002', '3912.90.02', 'Celulosa esponjosa.', '1', 'Kg', '0.00', '0.00') - ,('39129099', '3912.90.99', 'Las demás.', '1', 'Kg', '5.00', '0.00') - ,('39131006', '3913.10.06', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('39139002', '3913.90.02', 'Esponjas celulósicas.', '1', 'Kg', '3.00', '0.00') - ,('39139005', '3913.90.05', 'Producto de la reacción del líquido obtenido de la cáscara de nuez de anacardo y el formaldehído.', '1', 'Kg', '5.00', '0.00') - ,('39139006', '3913.90.06', 'Goma de Xantán.', '1', 'Kg', '7.00', '0.00') - ,('39139099', '3913.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('39140001', '3914.00.01', 'Intercambiadores de iones, del tipo catiónico.', '1', 'Kg', '6.00', '0.00') - ,('39140099', '3914.00.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('39151001', '3915.10.01', 'De polímeros de etileno.', '1', 'Kg', '15.00', '0.00') - ,('39152001', '3915.20.01', 'De polímeros de estireno.', '1', 'Kg', '7.00', '0.00') - ,('39153001', '3915.30.01', 'De polímeros de cloruro de vinilo.', '1', 'Kg', '15.00', '0.00') - ,('39159001', '3915.90.01', 'De manufacturas de polimetacrilato de metilo.', '1', 'Kg', '15.00', '0.00') - ,('39159099', '3915.90.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('39161002', '3916.10.02', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('39162003', '3916.20.03', 'De polímeros de cloruro de vinilo.', '1', 'Kg', '0.00', '0.00') - ,('39169091', '3916.90.91', 'De los demás plásticos.', '1', 'Kg', '0.00', '0.00') - ,('39171005', '3917.10.05', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('39172103', '3917.21.03', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('39172203', '3917.22.03', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('39172304', '3917.23.04', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('39172991', '3917.29.91', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('39173101', '3917.31.01', 'Tubos flexibles para una presión superior o igual a 27.6 MPa.', '1', 'Kg', '0.00', '0.00') - ,('39173291', '3917.32.91', 'Los demás, sin reforzar ni combinar con otras materias, sin accesorios.', '1', 'Kg', '0.00', '0.00') - ,('39173301', '3917.33.01', 'Tubería de materias plásticas artificiales, hasta de 20 mm de diámetro exterior, con goteras integradas, para riego agrícola.', '1', 'Kg', '15.00', '0.00') - ,('39173399', '3917.33.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('39173999', '3917.39.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('39174001', '3917.40.01', 'Accesorios.', '1', 'Kg', '0.00', '0.00') - ,('39181002', '3918.10.02', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('39189091', '3918.90.91', 'De los demás plásticos.', '1', 'Kg', '15.00', '0.00') - ,('39191001', '3919.10.01', 'En rollos de anchura inferior o igual a 20 cm.', '1', 'Kg', '0.00', '0.00') - ,('39199099', '3919.90.99', 'Las demás.', '1', 'Kg', '35.00', '0.00') - ,('39201005', '3920.10.05', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('39202005', '3920.20.05', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('39203004', '3920.30.04', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('39204303', '3920.43.03', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('39204999', '3920.49.99', 'Las demás.', '1', 'Kg', '35.00', '0.00') - ,('39205101', '3920.51.01', 'De poli(metacrilato de metilo).', '1', 'Kg', '0.00', '0.00') - ,('39205999', '3920.59.99', 'Las demás.', '1', 'Kg', '35.00', '0.00') - ,('39206101', '3920.61.01', 'De policarbonatos.', '1', 'Kg', '0.00', '0.00') - ,('39206202', '3920.62.02', 'Películas de poliéster biorientado.', '1', 'Kg', '7.00', '0.00') - ,('39206299', '3920.62.99', 'Las demás.', '1', 'Kg', '25.00', '0.00') - ,('39206303', '3920.63.03', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('39206991', '3920.69.91', 'De los demás poliésteres.', '1', 'Kg', '0.00', '0.00') - ,('39207101', '3920.71.01', 'De celulosa regenerada.', '1', 'Kg', '0.00', '0.00') - ,('39207302', '3920.73.02', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('39207991', '3920.79.91', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('39209101', '3920.91.01', 'De poli(vinilbutiral).', '1', 'Kg', '0.00', '0.00') - ,('39209202', '3920.92.02', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('39209301', '3920.93.01', 'De resinas amínicas.', '1', 'Kg', '0.00', '0.00') - ,('39209401', '3920.94.01', 'De resinas fenólicas.', '1', 'Kg', '0.00', '0.00') - ,('39209991', '3920.99.91', 'De los demás plásticos.', '1', 'Kg', '0.00', '0.00') - ,('39211101', '3921.11.01', 'De polímeros de estireno.', '1', 'Kg', '10.00', '0.00') - ,('39211201', '3921.12.01', 'De polímeros de cloruro de vinilo.', '1', 'Kg', '0.00', '0.00') - ,('39211302', '3921.13.02', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('39211401', '3921.14.01', 'De celulosa regenerada.', '1', 'Kg', '0.00', '0.00') - ,('39211991', '3921.19.91', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('39219009', '3921.90.09', 'De poliéster metalizados con anchura superior o igual a 35 mm, con un espesor inferior a 100 micrones.', '1', 'Kg', '7.00', '0.00') - ,('39219099', '3921.90.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('39221001', '3922.10.01', 'Bañeras, duchas, fregaderos y lavabos.', '1', 'Kg', '15.00', '0.00') - ,('39222001', '3922.20.01', 'Asientos y tapas de inodoros.', '1', 'Kg', '15.00', '0.00') - ,('39229099', '3922.90.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('39231003', '3923.10.03', 'A base de poliestireno expandible.', '1', 'Kg', '15.00', '0.00') - ,('39232101', '3923.21.01', 'De polímeros de etileno.', '1', 'Kg', '0.00', '0.00') - ,('39232991', '3923.29.91', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('39233002', '3923.30.02', 'Los demás.', '1', 'Kg', '35.00', '0.00') - ,('39234001', '3923.40.01', 'Casetes o cartuchos para embobinar cintas magnéticas o cintas para máquinas de escribir, excepto para cintas de sonido de anchura inferior a 13 mm.', '1', 'Kg', '0.00', '0.00') - ,('39234002', '3923.40.02', 'Casetes o cartuchos para embobinar cintas magnéticas de sonido de anchura inferior a 13 mm.', '1', 'Kg', '0.00', '0.00') - ,('39234099', '3923.40.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('39235001', '3923.50.01', 'Tapones, tapas, cápsulas y demás dispositivos de cierre.', '1', 'Kg', '15.00', '0.00') - ,('39239099', '3923.90.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('39241001', '3924.10.01', 'Vajilla y demás artículos para el servicio de mesa o de cocina.', '1', 'Kg', '15.00', '0.00') - ,('39249001', '3924.90.01', 'Mangos para maquinillas de afeitar.', '1', 'Kg', '0.00', '0.00') - ,('39249099', '3924.90.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('39251001', '3925.10.01', 'Depósitos, cisternas, cubas y recipientes análogos, de capacidad superior a 300 l.', '1', 'Kg', '15.00', '0.00') - ,('39252001', '3925.20.01', 'Puertas, ventanas, y sus marcos, contramarcos y umbrales.', '1', 'Kg', '15.00', '0.00') - ,('39253001', '3925.30.01', 'Contraventanas, persianas (incluidas las venecianas) y artículos similares, y sus partes.', '1', 'Kg', '15.00', '0.00') - ,('39259099', '3925.90.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('39261001', '3926.10.01', 'Artículos de oficina y artículos escolares.', '1', 'Kg', '15.00', '0.00') - ,('39262001', '3926.20.01', 'Prendas de vestir, sus accesorios y dispositivos, para protección contra radiaciones.', '1', 'Kg', '0.00', '0.00') - ,('39262099', '3926.20.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('39263002', '3926.30.02', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('39264001', '3926.40.01', 'Estatuillas y demás artículos de adorno.', '1', 'Kg', '15.00', '0.00') - ,('39269001', '3926.90.01', 'Mangos para herramientas de mano.', '1', 'Kg', '15.00', '0.00') - ,('39269004', '3926.90.04', 'Salvavidas.', '1', 'Kg', '15.00', '0.00') - ,('39269005', '3926.90.05', 'Flotadores o boyas para redes de pesca.', '1', 'Kg', '15.00', '0.00') - ,('39269006', '3926.90.06', 'Loncheras; cantimploras.', '1', 'Kg', '35.00', '0.00') - ,('39269007', '3926.90.07', 'Modelos o patrones.', '1', 'Kg', '15.00', '0.00') - ,('39269008', '3926.90.08', 'Hormas para calzado.', '1', 'Kg', '15.00', '0.00') - ,('39269011', '3926.90.11', 'Protectores para el sentido auditivo.', '1', 'Kg', '15.00', '0.00') - ,('39269013', '3926.90.13', 'Letras, números o signos.', '1', 'Kg', '15.00', '0.00') - ,('39269014', '3926.90.14', 'Cinchos fijadores o abrazaderas, excepto lo reconocible como diseñados exclusivamente para uso automotriz.', '1', 'Kg', '15.00', '0.00') - ,('39269015', '3926.90.15', 'Almácigas, con oquedades perforadas.', '1', 'Kg', '15.00', '0.00') - ,('39269018', '3926.90.18', 'Marcas para asfalto, postes reflejantes y/o dispositivos de advertencia (triángulos de seguridad), de resina plástica, para la señalización vial.', '1', 'Kg', '15.00', '0.00') - ,('39269019', '3926.90.19', 'Abanicos o sus partes.', '1', 'Kg', '15.00', '0.00') - ,('39269020', '3926.90.20', 'Emblemas, para vehículos automóviles.', '1', 'Kg', '15.00', '0.00') - ,('39269024', '3926.90.24', 'Diablos o tacos (pigs) de poliuretanos con diámetro hasta de 122 cm, para la limpieza interior de tuberías, aun cuando estén recubiertos con banda de caucho, con incrustaciones de carburo de tungsteno o cerdas de acero.', '1', 'Kg', '15.00', '0.00') - ,('39269027', '3926.90.27', 'Láminas perforadas o troqueladas de poli(etileno) y/o poli(propileno), aun cuando estén coloreadas, metalizadas o laqueadas.', '1', 'Kg', '15.00', '0.00') - ,('39269029', '3926.90.29', 'Embudos.', '1', 'Kg', '15.00', '0.00') - ,('39269099', '3926.90.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('40011001', '4001.10.01', 'Látex de caucho natural, incluso prevulcanizado.', '1', 'Kg', '0.00', '0.00') - ,('40012101', '4001.21.01', 'Hojas ahumadas.', '1', 'Kg', '0.00', '0.00') - ,('40012201', '4001.22.01', 'Cauchos técnicamente especificados (TSNR).', '1', 'Kg', '0.00', '0.00') - ,('40012999', '4001.29.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('40013003', '4001.30.03', 'Balata, gutapercha, guayule, chicle y gomas naturales análogas.', '1', 'Kg', '0.00', '0.00') - ,('40021102', '4002.11.02', 'Látex frío de poli(butadieno-estireno), con un contenido de sólidos superior o igual al 38% pero inferior o igual al 41% o superior o igual al 67% pero inferior al 69%, de estireno combinado superior o igual al 21.5% pero inferior o igual al 25.5%, de estireno residual inferior o igual 0.1%.', '1', 'Kg', '5.00', '0.00') - ,('40021199', '4002.11.99', 'Los demás.', '1', 'Kg', '6.00', '0.00') - ,('40021901', '4002.19.01', 'Poli(butadieno-estireno), con un contenido reaccionado de butadieno superior o igual al 90% pero inferior o igual al 97% y de 10% a 3% respectivamente, de estireno.', '1', 'Kg', '7.00', '0.00') - ,('40021902', '4002.19.02', 'Poli(butadieno-estireno), excepto lo comprendido en la fracción arancelaria 4002.19.01.', '1', 'Kg', '35.00', '0.00') - ,('40021903', '4002.19.03', 'Soluciones o dispersiones de poli(butadieno-estireno).', '1', 'Kg', '10.00', '0.00') - ,('40021999', '4002.19.99', 'Los demás.', '1', 'Kg', '35.00', '0.00') - ,('40022001', '4002.20.01', 'Caucho butadieno (BR).', '1', 'Kg', '0.00', '0.00') - ,('40023101', '4002.31.01', 'Caucho poli(isobuteno-isopreno).', '1', 'Kg', '0.00', '0.00') - ,('40023199', '4002.31.99', 'Los demás.', '1', 'Kg', '7.00', '0.00') - ,('40023901', '4002.39.01', 'Caucho poli(isobuteno-isopreno) halogenado.', '1', 'Kg', '0.00', '0.00') - ,('40023999', '4002.39.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('40024101', '4002.41.01', 'Látex.', '1', 'Kg', '0.00', '0.00') - ,('40024999', '4002.49.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('40025101', '4002.51.01', 'Látex.', '1', 'Kg', '0.00', '0.00') - ,('40025901', '4002.59.01', 'Poli(butadieno-acrilonitrilo) con un contenido de acrilonitrilo superior o igual al 45%.', '1', 'Kg', '5.00', '0.00') - ,('40025902', '4002.59.02', 'Poli(butadieno-acrilonitrilo), excepto lo comprendido en la fracción arancelaria 4002.59.01.', '1', 'Kg', '9.00', '0.00') - ,('40025903', '4002.59.03', 'Copolímero de (butadieno-acrilonitrilo) carboxilado, con un contenido de copolímero superior o igual al 73% pero inferior o igual al 84%.', '1', 'Kg', '0.00', '0.00') - ,('40025999', '4002.59.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('40026002', '4002.60.02', 'Caucho isopreno (IR).', '1', 'Kg', '0.00', '0.00') - ,('40027001', '4002.70.01', 'Caucho etileno-propileno-dieno no conjugado (EPDM).', '1', 'Kg', '0.00', '0.00') - ,('40028001', '4002.80.01', 'Mezclas de los productos de la partida 40.01 con los de esta partida.', '1', 'Kg', '0.00', '0.00') - ,('40029101', '4002.91.01', 'Tioplastos.', '1', 'Kg', '0.00', '0.00') - ,('40029199', '4002.91.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('40029901', '4002.99.01', 'Caucho facticio.', '1', 'Kg', '0.00', '0.00') - ,('40029999', '4002.99.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('40030001', '4003.00.01', 'Caucho regenerado en formas primarias o en placas, hojas o tiras.', '1', 'Kg', '0.00', '0.00') - ,('40040001', '4004.00.01', 'Recortes de neumáticos o de desperdicios, de hule o caucho vulcanizados, sin endurecer.', '1', 'Kg', '0.00', '0.00') - ,('40040002', '4004.00.02', 'Neumáticos o cubiertas gastados, inutilizables.', '1', 'Kg', '15.00', '0.00') - ,('40040099', '4004.00.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('40051001', '4005.10.01', 'Caucho con adición de negro de humo o de sílice.', '1', 'Kg', '0.00', '0.00') - ,('40052002', '4005.20.02', 'Disoluciones; dispersiones, excepto las de la subpartida 4005.10.', '1', 'Kg', '0.00', '0.00') - ,('40059104', '4005.91.04', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('40059999', '4005.99.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('40061001', '4006.10.01', 'Perfiles para recauchutar.', '1', 'Kg', '0.00', '0.00') - ,('40069001', '4006.90.01', 'Juntas.', '1', 'Kg', '0.00', '0.00') - ,('40069002', '4006.90.02', 'Parches.', '1', 'Kg', '15.00', '0.00') - ,('40069003', '4006.90.03', 'Copas para portabustos, aun cuando estén recubiertas de tejidos.', '1', 'Kg', '0.00', '0.00') - ,('40069004', '4006.90.04', 'Reconocibles para naves aéreas.', '1', 'Kg', '0.00', '0.00') - ,('40069099', '4006.90.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('40070001', '4007.00.01', 'Hilos y cuerdas, de caucho vulcanizado.', '1', 'Kg', '0.00', '0.00') - ,('40081101', '4008.11.01', 'Placas, hojas y tiras.', '1', 'Kg', '0.00', '0.00') - ,('40081999', '4008.19.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('40082102', '4008.21.02', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('40082999', '4008.29.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('40091102', '4009.11.02', 'Sin accesorios.', '1', 'Kg', '0.00', '0.00') - ,('40091203', '4009.12.03', 'Con accesorios.', '1', 'Kg', '0.00', '0.00') - ,('40092105', '4009.21.05', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('40092205', '4009.22.05', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('40093106', '4009.31.06', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('40093205', '4009.32.05', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('40094104', '4009.41.04', 'Sin accesorios.', '1', 'Kg', '0.00', '0.00') - ,('40094203', '4009.42.03', 'Con accesorios.', '1', 'Kg', '0.00', '0.00') - ,('40101102', '4010.11.02', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('40101203', '4010.12.03', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('40101999', '4010.19.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('40103101', '4010.31.01', 'Correas de transmisión sin fin, estriadas, de sección trapezoidal, de circunferencia exterior superior a 60 cm pero inferior o igual a 180 cm.', '1', 'Kg', '0.00', '0.00') - ,('40103201', '4010.32.01', 'Correas de transmisión sin fin, sin estriar, de sección trapezoidal, de circunferencia exterior superior a 60 cm pero inferior o igual a 180 cm.', '1', 'Kg', '0.00', '0.00') - ,('40103301', '4010.33.01', 'Correas de transmisión sin fin, estriadas, de sección trapezoidal, de circunferencia exterior superior a 180 cm pero inferior o igual a 240 cm.', '1', 'Kg', '0.00', '0.00') - ,('40103401', '4010.34.01', 'Correas de transmisión sin fin, sin estriar, de sección trapezoidal, de circunferencia exterior superior a 180 cm pero inferior o igual a 240 cm.', '1', 'Kg', '0.00', '0.00') - ,('40103502', '4010.35.02', 'Correas de transmisión sin fin, con muescas (sincrónicas), de circunferencia exterior superior a 60 cm pero inferior o igual a 150 cm.', '1', 'Kg', '0.00', '0.00') - ,('40103602', '4010.36.02', 'Correas de transmisión sin fin, con muescas (sincrónicas), de circunferencia exterior superior a 150 cm pero inferior o igual a 198 cm.', '1', 'Kg', '0.00', '0.00') - ,('40103999', '4010.39.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('40111010', '4011.10.10', 'Los demás.', '6', 'Pza', '35.00', '0.00') - ,('40112004', '4011.20.04', 'Con diámetro interior superior a 44.45 cm, de construcción radial.', '6', 'Pza', '0.00', '0.00') - ,('40112005', '4011.20.05', 'Con diámetro interior superior a 44.45 cm, de construcción diagonal.', '6', 'Pza', '15.00', '0.00') - ,('40112006', '4011.20.06', 'Con diámetro interior inferior o igual a 44.45 cm, de construcción diagonal.', '6', 'Pza', '35.00', '0.00') - ,('40113001', '4011.30.01', 'De los tipos utilizados en aeronaves.', '6', 'Pza', '0.00', '0.00') - ,('40114001', '4011.40.01', 'De los tipos utilizados en motocicletas.', '6', 'Pza', '0.00', '0.00') - ,('40115001', '4011.50.01', 'De los tipos utilizados en bicicletas.', '6', 'Pza', '0.00', '0.00') - ,('40117001', '4011.70.01', 'Con altos relieves en forma de taco, ángulo o similar, para maquinaria y tractores agrícolas, cuyos números de medida sean: 8.25-15; 10.00-15; 6.00-16; 6.50-16; 7.50-16; 5.00-16; 7.50-18; 6.00-19; 13.00-24; 16.00-25; 17.50-25; 18.00-25; 18.40-26; 23.1-26; 11.25-28; 13.6-28; 14.9-28; 16.9-30; 18.4-30; 24.5-32; 18.4-34; 20.8-34; 23.1-34; 12.4-36; 13.6-38; 14.9-38; 15.5-38; 18.4-38.', '6', 'Pza', '0.00', '0.00') - ,('40117002', '4011.70.02', 'Con altos relieves en forma de taco, ángulo o similar, para maquinaria y tractores agrícolas, excepto lo comprendido en la fracción arancelaria 4011.70.01.', '6', 'Pza', '0.00', '0.00') - ,('40117003', '4011.70.03', 'De diámetro interior superior a 35 cm.', '6', 'Pza', '0.00', '0.00') - ,('40117099', '4011.70.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('40118001', '4011.80.01', 'Para maquinaria y tractores industriales, con altos relieves en forma de taco, ángulo o similar, para rines de diámetro inferior o igual a 61 cm.', '6', 'Pza', '0.00', '0.00') - ,('40118002', '4011.80.02', 'Para maquinaria y tractores industriales, cuyos números de medida sean: 8.25-15; 10.00-15; 6.50-16; 7.50-16, con altos relieves en forma de taco, ángulo o similar, para rines de diámetro superior a 61 cm.', '6', 'Pza', '0.00', '0.00') - ,('40118003', '4011.80.03', 'Para vehículos fuera de carretera, con diámetro exterior superior a 2.20 m, con altos relieves en forma de taco, ángulo o similar, para rines de diámetro superior a 61 cm.', '6', 'Pza', '0.00', '0.00') - ,('40118004', '4011.80.04', 'Para maquinaria y tractores industriales, con altos relieves en forma de taco, ángulo o similar, para rines de diámetro superior a 61 cm, excepto lo comprendido en la fracción arancelaria 4011.80.02.', '6', 'Pza', '0.00', '0.00') - ,('40118005', '4011.80.05', 'De diámetro interior superior a 35 cm, para rines de diámetro inferior o igual a 61 cm.', '6', 'Pza', '0.00', '0.00') - ,('40118006', '4011.80.06', 'De diámetro interior superior a 35 cm, para rines de diámetro superior a 61 cm.', '6', 'Pza', '0.00', '0.00') - ,('40118093', '4011.80.93', 'Los demás para rines de diámetro superior a 61 cm.', '6', 'Pza', '15.00', '0.00') - ,('40119001', '4011.90.01', 'Reconocibles como diseñados exclusivamente para trenes metropolitanos (METRO).', '6', 'Pza', '0.00', '0.00') - ,('40119002', '4011.90.02', 'De diámetro interior superior a 35 cm.', '6', 'Pza', '0.00', '0.00') - ,('40119099', '4011.90.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('40121101', '4012.11.01', 'De los tipos utilizados en automóviles de turismo (incluidos los del tipo familiar ("break" o "station wagon") y los de carreras).', '6', 'Pza', '15.00', '0.00') - ,('40121201', '4012.12.01', 'De los tipos utilizados en autobuses o camiones.', '6', 'Pza', '15.00', '0.00') - ,('40121301', '4012.13.01', 'De los tipos utilizados en aeronaves.', '6', 'Pza', '15.00', '0.00') - ,('40121999', '4012.19.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('40122001', '4012.20.01', 'De los tipos utilizados en vehículos para el transporte en carretera de pasajeros o mercancía, incluyendo tractores, o en vehículos de la partida 87.05.', '6', 'Pza', '0.00', '0.00') - ,('40122099', '4012.20.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('40129002', '4012.90.02', 'Reconocibles para naves aéreas.', '6', 'Pza', '0.00', '0.00') - ,('40129099', '4012.90.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('40131001', '4013.10.01', 'De los tipos utilizados en automóviles de turismo (incluidos los del tipo familiar ("break" o "station wagon") y los de carreras), en autobuses o camiones.', '6', 'Pza', '15.00', '0.00') - ,('40132001', '4013.20.01', 'De los tipos utilizados en bicicletas.', '6', 'Pza', '0.00', '0.00') - ,('40139001', '4013.90.01', 'Reconocibles para naves aéreas.', '6', 'Pza', '0.00', '0.00') - ,('40139002', '4013.90.02', 'Para maquinaria y tractores agrícolas e industriales.', '6', 'Pza', '0.00', '0.00') - ,('40139003', '4013.90.03', 'De los tipos utilizados en motocicletas.', '6', 'Pza', '0.00', '0.00') - ,('40139099', '4013.90.99', 'Las demás.', '6', 'Pza', '15.00', '0.00') - ,('40141001', '4014.10.01', 'Preservativos.', '1', 'Kg', '5.00', '0.00') - ,('40149001', '4014.90.01', 'Cojines neumáticos.', '1', 'Kg', '0.00', '0.00') - ,('40149002', '4014.90.02', 'Orinales para incontinencia.', '1', 'Kg', '0.00', '0.00') - ,('40149099', '4014.90.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('40151201', '4015.12.01', 'Para cirugía.', '1', 'Kg', '0.00', '0.00') - ,('40151299', '4015.12.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('40151999', '4015.19.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('40159001', '4015.90.01', 'Prendas de vestir totalmente de caucho.', '1', 'Kg', '0.00', '0.00') - ,('40159002', '4015.90.02', 'Prendas de vestir impregnadas o recubiertas de caucho.', '1', 'Kg', '0.00', '0.00') - ,('40159003', '4015.90.03', 'Prendas de vestir y sus accesorios, para protección contra radiaciones.', '1', 'Kg', '0.00', '0.00') - ,('40159099', '4015.90.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('40161001', '4016.10.01', 'De caucho celular.', '1', 'Kg', '0.00', '0.00') - ,('40169101', '4016.91.01', 'Revestimientos para el suelo y alfombras.', '4', 'M²', '10.00', '0.00') - ,('40169201', '4016.92.01', 'Cilíndricas de diámetro inferior o igual a 1 cm.', '1', 'Kg', '0.00', '0.00') - ,('40169299', '4016.92.99', 'Las demás.', '1', 'Kg', '10.00', '0.00') - ,('40169304', '4016.93.04', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('40169402', '4016.94.02', 'Defensas para muelles portuarios, flotantes (rellenas de espuma flotante) o giratorias (llantas de caucho flexible no inflable).', '1', 'Kg', '0.00', '0.00') - ,('40169499', '4016.94.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('40169501', '4016.95.01', 'Salvavidas.', '1', 'Kg', '0.00', '0.00') - ,('40169502', '4016.95.02', 'Diques.', '6', 'Pza', '0.00', '0.00') - ,('40169599', '4016.95.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('40169901', '4016.99.01', 'Arandelas, válvulas u otras piezas de uso técnico, excepto artículos reconocibles como diseñados exclusivamente para ser utilizados en el moldeo de neumáticos nuevos ("Bladers").', '1', 'Kg', '5.00', '0.00') - ,('40169904', '4016.99.04', 'Dedales.', '1', 'Kg', '10.00', '0.00') - ,('40169999', '4016.99.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('40170003', '4017.00.03', 'Desperdicios y desechos.', '1', 'Kg', '5.00', '0.00') - ,('40170099', '4017.00.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('41012003', '4101.20.03', 'De equino.', '1', 'Kg', '0.00', '0.00') - ,('41015003', '4101.50.03', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('41019091', '4101.90.91', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('41021001', '4102.10.01', 'Con lana.', '1', 'Kg', '0.00', '0.00') - ,('41022101', '4102.21.01', 'Piquelados.', '1', 'Kg', '0.00', '0.00') - ,('41022999', '4102.29.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('41032001', '4103.20.01', 'De caimán, cocodrilo o lagarto.', '7', 'Cbza', '0.00', '0.00') - ,('41032002', '4103.20.02', 'De tortuga o caguama.', '22', 'PROH', '0.00', '0.00') - ,('41032099', '4103.20.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('41033001', '4103.30.01', 'De porcino.', '1', 'Kg', '0.00', '0.00') - ,('41039099', '4103.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('41041104', '4104.11.04', 'Los demás.', '4', 'M²', '0.00', '0.00') - ,('41041999', '4104.19.99', 'Los demás.', '4', 'M²', '0.00', '0.00') - ,('41044102', '4104.41.02', 'Los demás.', '4', 'M²', '0.00', '0.00') - ,('41044999', '4104.49.99', 'Los demás.', '4', 'M²', '0.00', '0.00') - ,('41051004', '4105.10.04', 'Las demás.', '4', 'M²', '0.00', '0.00') - ,('41053001', '4105.30.01', 'En estado seco ("crust").', '4', 'M²', '0.00', '0.00') - ,('41062104', '4106.21.04', 'Las demás.', '4', 'M²', '0.00', '0.00') - ,('41062201', '4106.22.01', 'En estado seco ("crust").', '4', 'M²', '0.00', '0.00') - ,('41063101', '4106.31.01', 'En estado húmedo (incluido el "wet-blue").', '4', 'M²', '0.00', '0.00') - ,('41063201', '4106.32.01', 'En estado seco ("crust").', '4', 'M²', '0.00', '0.00') - ,('41064002', '4106.40.02', 'De reptil.', '4', 'M²', '0.00', '0.00') - ,('41069101', '4106.91.01', 'En estado húmedo (incluido el "wet-blue").', '4', 'M²', '0.00', '0.00') - ,('41069201', '4106.92.01', 'En estado seco ("crust").', '4', 'M²', '0.00', '0.00') - ,('41071102', '4107.11.02', 'Los demás.', '4', 'M²', '0.00', '0.00') - ,('41071202', '4107.12.02', 'Los demás.', '4', 'M²', '0.00', '0.00') - ,('41071999', '4107.19.99', 'Los demás.', '4', 'M²', '0.00', '0.00') - ,('41079101', '4107.91.01', 'Plena flor sin dividir.', '4', 'M²', '0.00', '0.00') - ,('41079201', '4107.92.01', 'Divididos con la flor.', '4', 'M²', '0.00', '0.00') - ,('41079999', '4107.99.99', 'Los demás.', '4', 'M²', '0.00', '0.00') - ,('41120001', '4112.00.01', 'Cueros preparados después del curtido o secado y cueros y pieles apergaminados, de ovino, depilados, incluso divididos, excepto los de la partida 41.14.', '4', 'M²', '0.00', '0.00') - ,('41131001', '4113.10.01', 'De caprino.', '4', 'M²', '0.00', '0.00') - ,('41132001', '4113.20.01', 'De porcino.', '4', 'M²', '0.00', '0.00') - ,('41133001', '4113.30.01', 'De reptil.', '4', 'M²', '0.00', '0.00') - ,('41139099', '4113.90.99', 'Los demás.', '4', 'M²', '0.00', '0.00') - ,('41141001', '4114.10.01', 'Cueros y pieles agamuzados (incluido el agamuzado combinado al aceite).', '4', 'M²', '0.00', '0.00') - ,('41142001', '4114.20.01', 'Cueros y pieles charolados y sus imitaciones de cueros o pieles chapados; cueros y pieles metalizados.', '4', 'M²', '0.00', '0.00') - ,('41151001', '4115.10.01', 'Cuero regenerado a base de cuero o fibras de cuero, en placas, hojas o tiras, incluso enrolladas.', '1', 'Kg', '0.00', '0.00') - ,('41152001', '4115.20.01', 'Recortes y demás desperdicios de cuero o piel, preparados, o de cuero regenerado, no utilizables para la fabricación de manufacturas de cuero; aserrín, polvo y harina de cuero.', '1', 'Kg', '0.00', '0.00') - ,('42010001', '4201.00.01', 'Artículos de talabartería o guarnicionería para todos los animales (incluidos los tiros, traíllas, rodilleras, bozales, sudaderos, alforjas, abrigos para perros y artículos similares), de cualquier materia.', '6', 'Pza', '20.00', '0.00') - ,('42021101', '4202.11.01', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('42021203', '4202.12.03', 'Los demás con la superficie exterior de materia textil.', '6', 'Pza', '20.00', '0.00') - ,('42021999', '4202.19.99', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('42022101', '4202.21.01', 'Con la superficie exterior de cuero natural o cuero regenerado.', '6', 'Pza', '20.00', '0.00') - ,('42022203', '4202.22.03', 'Con la superficie exterior de materia textil.', '6', 'Pza', '20.00', '0.00') - ,('42022999', '4202.29.99', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('42023101', '4202.31.01', 'Con la superficie exterior de cuero natural o cuero regenerado.', '6', 'Pza', '20.00', '0.00') - ,('42023203', '4202.32.03', 'Con la superficie exterior de materia textil.', '6', 'Pza', '20.00', '0.00') - ,('42023999', '4202.39.99', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('42029101', '4202.91.01', 'Con la superficie exterior de cuero natural o cuero regenerado.', '6', 'Pza', '20.00', '0.00') - ,('42029204', '4202.92.04', 'Bolsas o fundas, utilizadas para contener llaves de cubo y/o un "gato", reconocibles como concebidas exclusivamente para uso automotriz.', '6', 'Pza', '20.00', '0.00') - ,('42029999', '4202.99.99', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('42031001', '4203.10.01', 'Para protección contra radiaciones.', '6', 'Pza', '0.00', '0.00') - ,('42031099', '4203.10.99', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('42032101', '4203.21.01', 'Diseñados especialmente para la práctica del deporte.', '6', 'Pza', '20.00', '0.00') - ,('42032901', '4203.29.01', 'Para protección contra radiaciones.', '6', 'Pza', '0.00', '0.00') - ,('42032999', '4203.29.99', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('42033002', '4203.30.02', 'Cintos, cinturones y bandoleras.', '6', 'Pza', '20.00', '0.00') - ,('42034001', '4203.40.01', 'Para protección contra radiaciones.', '6', 'Pza', '0.00', '0.00') - ,('42034099', '4203.40.99', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('42050002', '4205.00.02', 'Artículos para usos técnicos de cuero natural o cuero regenerado.', '1', 'Kg', '0.00', '0.00') - ,('42050099', '4205.00.99', 'Las demás.', '6', 'Pza', '20.00', '0.00') - ,('42060001', '4206.00.01', 'Catgut, incluso cromado, con diámetro superior o igual a 0.10 mm, sin exceder de 0.89 mm.', '1', 'Kg', '0.00', '0.00') - ,('42060099', '4206.00.99', 'Las demás.', '1', 'Kg', '5.00', '0.00') - ,('43011001', '4301.10.01', 'De visón, enteras, incluso sin la cabeza, cola o patas.', '1', 'Kg', '0.00', '0.00') - ,('43013001', '4301.30.01', 'De cordero llamadas astracán, "Breitschwanz", "caracul", "persa" o similares, de cordero de Indias, de China, de Mongolia o del Tíbet, enteras, incluso sin la cabeza, cola o patas.', '1', 'Kg', '0.00', '0.00') - ,('43016001', '4301.60.01', 'De zorro, enteras, incluso sin la cabeza, cola o patas.', '1', 'Kg', '0.00', '0.00') - ,('43018091', '4301.80.91', 'Las demás pieles, enteras, incluso sin la cabeza, cola o patas.', '1', 'Kg', '0.00', '0.00') - ,('43019001', '4301.90.01', 'Cabezas, colas, patas y demás trozos utilizables en peletería.', '1', 'Kg', '0.00', '0.00') - ,('43021101', '4302.11.01', 'De visón.', '1', 'Kg', '0.00', '0.00') - ,('43021999', '4302.19.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('43022002', '4302.20.02', 'Cabezas, colas, patas y demás trozos, desechos y recortes, sin ensamblar.', '1', 'Kg', '0.00', '0.00') - ,('43023002', '4302.30.02', 'Pieles enteras y trozos y recortes de pieles, ensamblados.', '1', 'Kg', '0.00', '0.00') - ,('43031001', '4303.10.01', 'Prendas y complementos (accesorios), de vestir.', '6', 'Pza', '20.00', '0.00') - ,('43039099', '4303.90.99', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('43040001', '4304.00.01', 'Peletería facticia o artificial y artículos de peletería facticia o artificial.', '1', 'Kg', '20.00', '0.00') - ,('44011101', '4401.11.01', 'De coníferas.', '1', 'Kg', '0.00', '0.00') - ,('44011201', '4401.12.01', 'Distinta de la de coníferas.', '1', 'Kg', '0.00', '0.00') - ,('44012101', '4401.21.01', 'De coníferas.', '1', 'Kg', '0.00', '0.00') - ,('44012201', '4401.22.01', 'Distinta de la de coníferas.', '1', 'Kg', '0.00', '0.00') - ,('44013101', '4401.31.01', 'Pellets de madera.', '1', 'Kg', '0.00', '0.00') - ,('44013201', '4401.32.01', 'Briquetas de madera.', '1', 'Kg', '0.00', '0.00') - ,('44013999', '4401.39.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('44014101', '4401.41.01', 'Aserrín.', '1', 'Kg', '0.00', '0.00') - ,('44014999', '4401.49.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('44021001', '4402.10.01', 'De bambú.', '1', 'Kg', '0.00', '0.00') - ,('44022001', '4402.20.01', 'De cáscaras o de huesos (carozos) de frutos.', '1', 'Kg', '0.00', '0.00') - ,('44029099', '4402.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('44031101', '4403.11.01', 'De coníferas.', '5', 'M³', '0.00', '0.00') - ,('44031201', '4403.12.01', 'Distinta de la de coníferas.', '5', 'M³', '0.00', '0.00') - ,('44032101', '4403.21.01', 'De pino (Pinus spp.), cuya menor dimensión de la sección transversal sea superior o igual a 15 cm.', '5', 'M³', '0.00', '0.00') - ,('44032291', '4403.22.91', 'Las demás, de pino (Pinus spp.).', '5', 'M³', '0.00', '0.00') - ,('44032301', '4403.23.01', 'De abeto (Abies spp.) y de pícea (Picea spp.), cuya menor dimensión de la sección transversal sea superior o igual a 15 cm.', '5', 'M³', '0.00', '0.00') - ,('44032491', '4403.24.91', 'Las demás, de abeto (Abies spp.) y de pícea (Picea spp.).', '5', 'M³', '0.00', '0.00') - ,('44032591', '4403.25.91', 'Las demás, cuya menor dimensión de la sección transversal sea superior o igual a 15 cm.', '5', 'M³', '0.00', '0.00') - ,('44032699', '4403.26.99', 'Las demás.', '5', 'M³', '0.00', '0.00') - ,('44034101', '4403.41.01', 'Dark Red Meranti, Light Red Meranti y Meranti Bakau.', '5', 'M³', '0.00', '0.00') - ,('44034201', '4403.42.01', 'Teca.', '5', 'M³', '0.00', '0.00') - ,('44034999', '4403.49.99', 'Las demás.', '5', 'M³', '0.00', '0.00') - ,('44039101', '4403.91.01', 'De encina, roble, alcornoque y demás belloteros (Quercus spp.).', '5', 'M³', '0.00', '0.00') - ,('44039301', '4403.93.01', 'De haya (Fagus spp.), cuya menor dimensión de la sección transversal sea superior o igual a 15 cm.', '5', 'M³', '0.00', '0.00') - ,('44039491', '4403.94.91', 'Las demás, de haya (Fagus spp.).', '5', 'M³', '0.00', '0.00') - ,('44039501', '4403.95.01', 'De abedul (Betula spp.), cuya menor dimensión de la sección transversal sea superior o igual a 15 cm.', '5', 'M³', '0.00', '0.00') - ,('44039691', '4403.96.91', 'Las demás, de abedul (Betula spp.).', '5', 'M³', '0.00', '0.00') - ,('44039701', '4403.97.01', 'De álamo (Populus spp.).', '5', 'M³', '0.00', '0.00') - ,('44039801', '4403.98.01', 'De eucalipto (Eucalyptus spp.).', '5', 'M³', '0.00', '0.00') - ,('44039999', '4403.99.99', 'Las demás.', '5', 'M³', '0.00', '0.00') - ,('44041002', '4404.10.02', 'De coníferas.', '5', 'M³', '5.00', '0.00') - ,('44042001', '4404.20.01', 'Varitas de bambú aún cuando estén redondeadas.', '5', 'M³', '15.00', '0.00') - ,('44042002', '4404.20.02', 'De fresno, simplemente desbastada o redondeada, para bastones, paraguas, mangos de herramientas y similares.', '5', 'M³', '5.00', '0.00') - ,('44042003', '4404.20.03', 'De haya o de maple, simplemente desbastada o redondeada, para bastones, paraguas, mangos de herramientas y similares.', '5', 'M³', '5.00', '0.00') - ,('44042004', '4404.20.04', 'Madera hilada.', '5', 'M³', '5.00', '0.00') - ,('44042099', '4404.20.99', 'Los demás.', '5', 'M³', '10.00', '0.00') - ,('44050003', '4405.00.03', 'Harina de madera.', '1', 'Kg', '5.00', '0.00') - ,('44061101', '4406.11.01', 'De coníferas.', '6', 'Pza', '5.00', '0.00') - ,('44061201', '4406.12.01', 'Distinta de la de coníferas.', '6', 'Pza', '5.00', '0.00') - ,('44069101', '4406.91.01', 'De coníferas.', '6', 'Pza', '5.00', '0.00') - ,('44069201', '4406.92.01', 'Distinta de la de coníferas.', '6', 'Pza', '5.00', '0.00') - ,('44071101', '4407.11.01', 'De ocote, en tablas, tablones o vigas.', '5', 'M³', '0.00', '0.00') - ,('44071103', '4407.11.03', 'Tablillas con ancho que no exceda de 10 cm y longitud inferior o igual a 20 cm, para la fabricación de lápices.', '5', 'M³', '0.00', '0.00') - ,('44071199', '4407.11.99', 'Las demás.', '5', 'M³', '5.00', '0.00') - ,('44071201', '4407.12.01', 'De pinabete o abeto (oyamel), en tablas, tablones o vigas.', '5', 'M³', '0.00', '0.00') - ,('44071203', '4407.12.03', 'Tablillas con ancho que no exceda de 10 cm y longitud inferior o igual a 20 cm, para la fabricación de lápices.', '5', 'M³', '0.00', '0.00') - ,('44071299', '4407.12.99', 'Las demás.', '5', 'M³', '5.00', '0.00') - ,('44071301', '4407.13.01', 'Las demás.', '5', 'M³', '5.00', '0.00') - ,('44071401', '4407.14.01', 'Las demás.', '5', 'M³', '5.00', '0.00') - ,('44071902', '4407.19.02', 'Tablillas con ancho que no exceda de 10 cm y longitud inferior o igual a 20 cm, para la fabricación de lápices, excepto lo comprendido en la fracción arancelaria 4407.19.03.', '5', 'M³', '0.00', '0.00') - ,('44071903', '4407.19.03', 'Tablillas con ancho que no exceda de 10 cm y longitud inferior o igual a 70 cm, de cedro rojo occidental (Thuja plicata).', '5', 'M³', '0.00', '0.00') - ,('44071999', '4407.19.99', 'Las demás.', '5', 'M³', '5.00', '0.00') - ,('44072103', '4407.21.03', 'Los demás.', '5', 'M³', '0.00', '0.00') - ,('44072202', '4407.22.02', 'Los demás.', '5', 'M³', '0.00', '0.00') - ,('44072301', '4407.23.01', 'Teca.', '5', 'M³', '0.00', '0.00') - ,('44072501', '4407.25.01', 'Dark Red Meranti, Light Red Meranti y Meranti Bakau.', '5', 'M³', '5.00', '0.00') - ,('44072601', '4407.26.01', 'White Lauan, White Meranti, White Seraya, Yellow Meranti y Alan.', '5', 'M³', '5.00', '0.00') - ,('44072701', '4407.27.01', 'Sapelli.', '5', 'M³', '0.00', '0.00') - ,('44072801', '4407.28.01', 'Iroko.', '5', 'M³', '0.00', '0.00') - ,('44072999', '4407.29.99', 'Las demás.', '5', 'M³', '0.00', '0.00') - ,('44079101', '4407.91.01', 'De encina, roble, alcornoque y demás belloteros (Quercus spp.).', '5', 'M³', '0.00', '0.00') - ,('44079202', '4407.92.02', 'De haya (Fagus spp.).', '5', 'M³', '0.00', '0.00') - ,('44079301', '4407.93.01', 'De arce (Acer spp.).', '5', 'M³', '0.00', '0.00') - ,('44079401', '4407.94.01', 'De cerezo (Prunus spp.).', '5', 'M³', '0.00', '0.00') - ,('44079503', '4407.95.03', 'Las demás.', '5', 'M³', '0.00', '0.00') - ,('44079601', '4407.96.01', 'De abedul (Betula spp.).', '5', 'M³', '0.00', '0.00') - ,('44079701', '4407.97.01', 'De álamo (Populus spp.).', '5', 'M³', '0.00', '0.00') - ,('44079999', '4407.99.99', 'Los demás.', '5', 'M³', '0.00', '0.00') - ,('44081003', '4408.10.03', 'De coníferas para la fabricación de lápices.', '5', 'M³', '0.00', '0.00') - ,('44083101', '4408.31.01', 'Dark Red Meranti, Light Red Meranti y Meranti Bakau.', '5', 'M³', '0.00', '0.00') - ,('44083999', '4408.39.99', 'Las demás.', '5', 'M³', '0.00', '0.00') - ,('44089099', '4408.90.99', 'Las demás.', '5', 'M³', '0.00', '0.00') - ,('44091002', '4409.10.02', 'Tablillas de Libocedrus decurrens con ancho que no exceda de 10 cm y longitud igual o inferior a 20 cm, para la fabricación de lápices.', '4', 'M²', '0.00', '0.00') - ,('44091099', '4409.10.99', 'Los demás.', '4', 'M²', '15.00', '0.00') - ,('44092103', '4409.21.03', 'De bambú.', '4', 'M²', '15.00', '0.00') - ,('44092201', '4409.22.01', 'De maderas tropicales.', '4', 'M²', '15.00', '0.00') - ,('44092999', '4409.29.99', 'Los demás.', '4', 'M²', '15.00', '0.00') - ,('44101104', '4410.11.04', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('44101202', '4410.12.02', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('44101999', '4410.19.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('44109001', '4410.90.01', 'Aglomerados sin recubrir ni acabar.', '1', 'Kg', '5.00', '0.00') - ,('44109099', '4410.90.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('44111201', '4411.12.01', 'De densidad superior a 0.8 g/cm³, sin trabajo mecánico ni recubrimiento de superficie.', '1', 'Kg', '0.00', '0.00') - ,('44111299', '4411.12.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('44111301', '4411.13.01', 'De densidad superior a 0.8 g/cm³, sin trabajo mecánico ni recubrimiento de superficie.', '1', 'Kg', '0.00', '0.00') - ,('44111399', '4411.13.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('44111401', '4411.14.01', 'De densidad superior a 0.8 g/cm³, sin trabajo mecánico ni recubrimiento de superficie.', '1', 'Kg', '0.00', '0.00') - ,('44111403', '4411.14.03', 'De densidad superior a 0.5 g/cm³ pero inferior o igual a 0.8 g/cm³, sin trabajo mecánico ni recubrimiento de superficie.', '1', 'Kg', '0.00', '0.00') - ,('44111499', '4411.14.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('44119202', '4411.92.02', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('44119304', '4411.93.04', 'De densidad superior a 0.5 g/cm³ pero inferior o igual a 0.8 g/cm³.', '1', 'Kg', '5.00', '0.00') - ,('44119404', '4411.94.04', 'De densidad inferior o igual a 0.5 g/cm³.', '1', 'Kg', '5.00', '0.00') - ,('44121001', '4412.10.01', 'De bambú.', '1', 'Kg', '7.00', '0.00') - ,('44123199', '4412.31.99', 'Las demás.', '1', 'Kg', '7.00', '0.00') - ,('44123391', '4412.33.91', 'Las demás, que tengan, por lo menos, una hoja externa de madera distinta de la de coníferas, de las especies: aliso (Alnus spp.), fresno (Fraxinus spp.), haya (Fagus spp.), abedul (Betula spp.), cerezo (Prunus spp.), castaño (Castanea spp.), olmo (Ulmus spp.), eucalipto (Eucalyptus spp.), caria o pacana (Carya spp.), castaño de Indias (Aesculus spp.), tilo (Tilia spp.), arce (Acer spp.), roble (Quercus spp.), plátano (Platanus spp.), álamo (Populus spp.), algarrobo negro (Robinia spp.), árbol d', '1', 'Kg', '7.00', '0.00') - ,('44123491', '4412.34.91', 'Las demás, que tengan, por lo menos, una hoja externa de madera distinta de la de coníferas, no especificadas en la subpartida 4412.33.', '1', 'Kg', '7.00', '0.00') - ,('44123991', '4412.39.91', 'Las demás.', '1', 'Kg', '7.00', '0.00') - ,('44124101', '4412.41.01', 'Los demás.', '1', 'Kg', '7.00', '0.00') - ,('44124291', '4412.42.91', 'Los demás.', '1', 'Kg', '7.00', '0.00') - ,('44124991', '4412.49.91', 'Los demás.', '1', 'Kg', '7.00', '0.00') - ,('44125101', '4412.51.01', 'Los demás.', '1', 'Kg', '7.00', '0.00') - ,('44125291', '4412.52.91', 'Los demás, que tengan, por lo menos, una hoja externa de madera distinta de la de coníferas.', '1', 'Kg', '7.00', '0.00') - ,('44125991', '4412.59.91', 'Los demás, con las dos hojas externas de madera de coníferas.', '1', 'Kg', '7.00', '0.00') - ,('44129101', '4412.91.01', 'Los demás.', '1', 'Kg', '7.00', '0.00') - ,('44129291', '4412.92.91', 'Los demás.', '1', 'Kg', '7.00', '0.00') - ,('44129991', '4412.99.91', 'Los demás.', '1', 'Kg', '7.00', '0.00') - ,('44130001', '4413.00.01', 'De corte rectangular o cilíndrico, cuya sección transversal sea inferior o igual a 5 cm y longitud superior a 25 cm sin exceder de 170 cm, de haya blanca.', '1', 'Kg', '0.00', '0.00') - ,('44130002', '4413.00.02', 'De “maple” (Acer spp.).', '1', 'Kg', '5.00', '0.00') - ,('44130099', '4413.00.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('44141001', '4414.10.01', 'De maderas tropicales.', '1', 'Kg', '15.00', '0.00') - ,('44149099', '4414.90.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('44151001', '4415.10.01', 'Cajones, cajas, jaulas, tambores y envases similares; carretes para cables.', '1', 'Kg', '15.00', '0.00') - ,('44152002', '4415.20.02', 'Las demás.', '1', 'Kg', '15.00', '0.00') - ,('44160001', '4416.00.01', 'Barriles, cubas, tinas, cubos o demás manufacturas de tonelería, con capacidad superior a 5,000 l.', '1', 'Kg', '0.00', '0.00') - ,('44160005', '4416.00.05', 'Barriles o manufacturas de tonelería con capacidad inferior a 5,000 litros, de roble o de encino.', '1', 'Kg', '0.00', '0.00') - ,('44160099', '4416.00.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('44170001', '4417.00.01', 'Esbozos para hormas.', '1', 'Kg', '5.00', '0.00') - ,('44170099', '4417.00.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('44181101', '4418.11.01', 'De maderas tropicales.', '1', 'Kg', '15.00', '0.00') - ,('44181999', '4418.19.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('44182101', '4418.21.01', 'De maderas tropicales.', '1', 'Kg', '15.00', '0.00') - ,('44182999', '4418.29.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('44183001', '4418.30.01', 'Postes y vigas, distintos de los productos de las subpartidas 4418.81 a 4418.89.', '1', 'Kg', '15.00', '0.00') - ,('44184001', '4418.40.01', 'Encofrados para hormigón.', '1', 'Kg', '15.00', '0.00') - ,('44185001', '4418.50.01', 'Tablillas para cubierta de tejados o fachadas ("shingles" y "shakes").', '1', 'Kg', '15.00', '0.00') - ,('44187301', '4418.73.01', 'Multicapas.', '1', 'Kg', '5.00', '0.00') - ,('44187399', '4418.73.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('44187491', '4418.74.91', 'Los demás, para suelos en mosaico.', '1', 'Kg', '15.00', '0.00') - ,('44187591', '4418.75.91', 'Los demás, multicapas.', '1', 'Kg', '15.00', '0.00') - ,('44187999', '4418.79.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('44188101', '4418.81.01', 'Madera laminada-encolada (llamada “glulam”).', '1', 'Kg', '15.00', '0.00') - ,('44188201', '4418.82.01', 'Madera laminada cruzada (contralaminada) (llamada “CLT” o “X-lam”).', '1', 'Kg', '15.00', '0.00') - ,('44188301', '4418.83.01', 'Vigas en I.', '1', 'Kg', '15.00', '0.00') - ,('44188999', '4418.89.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('44189101', '4418.91.01', 'De bambú.', '1', 'Kg', '15.00', '0.00') - ,('44189201', '4418.92.01', 'Tableros celulares de madera.', '1', 'Kg', '15.00', '0.00') - ,('44189999', '4418.99.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('44191101', '4419.11.01', 'Tablas para pan, tablas para cortar y artículos similares.', '1', 'Kg', '15.00', '0.00') - ,('44191201', '4419.12.01', 'Palillos.', '1', 'Kg', '15.00', '0.00') - ,('44191999', '4419.19.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('44192001', '4419.20.01', 'De maderas tropicales.', '1', 'Kg', '15.00', '0.00') - ,('44199099', '4419.90.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('44201101', '4420.11.01', 'De Olinalá.', '1', 'Kg', '15.00', '0.00') - ,('44201199', '4420.11.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('44201999', '4420.19.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('44209001', '4420.90.01', 'De Olinalá.', '1', 'Kg', '15.00', '0.00') - ,('44209099', '4420.90.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('44211001', '4421.10.01', 'Perchas para prendas de vestir.', '1', 'Kg', '15.00', '0.00') - ,('44212001', '4421.20.01', 'Ataúdes.', '1', 'Kg', '15.00', '0.00') - ,('44219101', '4421.91.01', 'Para fósforos; clavos para calzado.', '1', 'Kg', '5.00', '0.00') - ,('44219102', '4421.91.02', 'Tapones.', '1', 'Kg', '10.00', '0.00') - ,('44219104', '4421.91.04', 'Canillas, carretes y bobinas para la hilatura y el tejido, para hilo de coser y artículos similares de madera torneada.', '1', 'Kg', '5.00', '0.00') - ,('44219199', '4421.91.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('44219901', '4421.99.01', 'Para fósforos; clavos para calzado.', '1', 'Kg', '5.00', '0.00') - ,('44219902', '4421.99.02', 'Tapones.', '1', 'Kg', '10.00', '0.00') - ,('44219904', '4421.99.04', 'Canillas, carretes y bobinas para la hilatura y el tejido, para hilo de coser y artículos similares de madera torneada.', '1', 'Kg', '5.00', '0.00') - ,('44219999', '4421.99.99', 'Las demás.', '1', 'Kg', '15.00', '0.00') - ,('45011001', '4501.10.01', 'Corcho natural en bruto o simplemente preparado.', '1', 'Kg', '0.00', '0.00') - ,('45019099', '4501.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('45020001', '4502.00.01', 'Corcho natural, descortezado o simplemente escuadrado o en bloques, placas, hojas o tiras, cuadradas o rectangulares (incluidos los esbozos con aristas vivas para tapones).', '1', 'Kg', '0.00', '0.00') - ,('45031001', '4503.10.01', 'Tapones.', '1', 'Kg', '0.00', '0.00') - ,('45039099', '4503.90.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('45041002', '4504.10.02', 'Empaquetaduras.', '1', 'Kg', '5.00', '0.00') - ,('45041099', '4504.10.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('45049099', '4504.90.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('46012101', '4601.21.01', 'De bambú.', '1', 'Kg', '10.00', '0.00') - ,('46012201', '4601.22.01', 'De ratán (roten).', '1', 'Kg', '15.00', '0.00') - ,('46012999', '4601.29.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('46019201', '4601.92.01', 'Trenzas y artículos similares, incluso ensamblados en tiras.', '1', 'Kg', '15.00', '0.00') - ,('46019299', '4601.92.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('46019302', '4601.93.02', 'De ratán (roten).', '1', 'Kg', '15.00', '0.00') - ,('46019401', '4601.94.01', 'Trenzas y artículos similares, incluso ensamblados en tiras.', '1', 'Kg', '15.00', '0.00') - ,('46019499', '4601.94.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('46019901', '4601.99.01', 'Trenzas y artículos similares, incluso ensamblados en tiras.', '1', 'Kg', '15.00', '0.00') - ,('46019999', '4601.99.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('46021101', '4602.11.01', 'De bambú.', '1', 'Kg', '15.00', '0.00') - ,('46021201', '4602.12.01', 'De ratán (roten).', '1', 'Kg', '15.00', '0.00') - ,('46021999', '4602.19.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('46029099', '4602.90.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('47010001', '4701.00.01', 'Pasta mecánica de madera.', '1', 'Kg', '0.00', '0.00') - ,('47020002', '4702.00.02', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('47031103', '4703.11.03', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('47031903', '4703.19.03', 'Distinta de la de coníferas.', '1', 'Kg', '0.00', '0.00') - ,('47032103', '4703.21.03', 'A la sosa (soda).', '1', 'Kg', '0.00', '0.00') - ,('47032903', '4703.29.03', 'A la sosa (soda).', '1', 'Kg', '0.00', '0.00') - ,('47041101', '4704.11.01', 'De coníferas.', '1', 'Kg', '0.00', '0.00') - ,('47041901', '4704.19.01', 'Distinta de la de coníferas.', '1', 'Kg', '0.00', '0.00') - ,('47042101', '4704.21.01', 'De coníferas.', '1', 'Kg', '0.00', '0.00') - ,('47042901', '4704.29.01', 'Distinta de la de coníferas.', '1', 'Kg', '0.00', '0.00') - ,('47050001', '4705.00.01', 'Pasta de madera obtenida por la combinación de procedimientos mecánico y químico.', '1', 'Kg', '0.00', '0.00') - ,('47061001', '4706.10.01', 'Pasta de línter de algodón.', '1', 'Kg', '0.00', '0.00') - ,('47062001', '4706.20.01', 'Pasta de fibras obtenidas de papel o cartón reciclado (desperdicios y desechos).', '1', 'Kg', '0.00', '0.00') - ,('47063091', '4706.30.91', 'Las demás, de bambú.', '1', 'Kg', '0.00', '0.00') - ,('47069101', '4706.91.01', 'Mecánicas.', '1', 'Kg', '0.00', '0.00') - ,('47069201', '4706.92.01', 'Químicas.', '1', 'Kg', '0.00', '0.00') - ,('47069301', '4706.93.01', 'Obtenidas por la combinación de procedimientos mecánico y químico.', '1', 'Kg', '0.00', '0.00') - ,('47071001', '4707.10.01', 'Papel o cartón Kraft crudo o papel o cartón corrugado.', '1', 'Kg', '0.00', '0.00') - ,('47072091', '4707.20.91', 'Los demás papeles o cartones obtenidos principalmente a partir de pasta química blanqueada sin colorear en la masa.', '1', 'Kg', '0.00', '0.00') - ,('47073001', '4707.30.01', 'Papel o cartón obtenido principalmente a partir de pasta mecánica (por ejemplo: diarios, periódicos e impresos similares).', '1', 'Kg', '0.00', '0.00') - ,('47079091', '4707.90.91', 'Los demás, incluidos los desperdicios y desechos sin clasificar.', '1', 'Kg', '0.00', '0.00') - ,('48010001', '4801.00.01', 'Papel prensa en bobinas (rollos) o en hojas.', '1', 'Kg', '15.00', '0.00') - ,('48021001', '4802.10.01', 'Papel y cartón hechos a mano (hoja a hoja).', '1', 'Kg', '5.00', '0.00') - ,('48022003', '4802.20.03', 'Papel y cartón soporte para papel o cartón fotosensibles, termosensibles o electrosensibles.', '1', 'Kg', '5.00', '0.00') - ,('48024001', '4802.40.01', 'Papel soporte para papeles de decorar paredes.', '1', 'Kg', '5.00', '0.00') - ,('48025404', '4802.54.04', 'Para la impresión de billetes de banco, cuando se importe o exporte por el Banco de México.', '1', 'Kg', '0.00', '0.00') - ,('48025499', '4802.54.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('48025502', '4802.55.02', 'Papel utilizado en máquinas copiadoras o reproductoras y cuyo revelado se hace por la acción del calor.', '1', 'Kg', '15.00', '0.00') - ,('48025503', '4802.55.03', 'Para la impresión de billetes de banco cuando se importe o exporte por el Banco de México.', '1', 'Kg', '0.00', '0.00') - ,('48025599', '4802.55.99', 'Los demás.', '1', 'Kg', '30.00', '0.00') - ,('48025602', '4802.56.02', 'Para la impresión de billetes de banco cuando se importe o exporte por el Banco de México.', '1', 'Kg', '0.00', '0.00') - ,('48025699', '4802.56.99', 'Los demás.', '1', 'Kg', '30.00', '0.00') - ,('48025791', '4802.57.91', 'Los demás, de peso superior o igual a 40 g/m2 pero inferior o igual a 150 g/m2.', '1', 'Kg', '35.00', '0.00') - ,('48025804', '4802.58.04', 'De peso superior a 150 g/m2.', '1', 'Kg', '35.00', '0.00') - ,('48026103', '4802.61.03', 'Los demás.', '1', 'Kg', '35.00', '0.00') - ,('48026203', '4802.62.03', 'En hojas en las que un lado sea inferior o igual a 435 mm y el otro sea inferior o igual a 297 mm, medidos sin plegar.', '1', 'Kg', '35.00', '0.00') - ,('48026999', '4802.69.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('48030004', '4803.00.04', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('48041101', '4804.11.01', 'Crudos.', '1', 'Kg', '0.00', '0.00') - ,('48041999', '4804.19.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('48042101', '4804.21.01', 'Crudo.', '1', 'Kg', '0.00', '0.00') - ,('48042999', '4804.29.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('48043105', '4804.31.05', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('48043999', '4804.39.99', 'Los demás.', '1', 'Kg', '30.00', '0.00') - ,('48044101', '4804.41.01', 'Crudos.', '1', 'Kg', '0.00', '0.00') - ,('48044201', '4804.42.01', 'Blanqueados uniformemente en la masa y con un contenido de fibras de madera obtenidas por procedimiento químico superior al 95% en peso del contenido total de fibra.', '1', 'Kg', '35.00', '0.00') - ,('48044999', '4804.49.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('48045102', '4804.51.02', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('48045201', '4804.52.01', 'Blanqueados uniformemente en la masa y con un contenido de fibras de madera obtenidas por procedimiento químico superior al 95% en peso del contenido total de fibra.', '1', 'Kg', '0.00', '0.00') - ,('48045999', '4804.59.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('48051101', '4805.11.01', 'Papel semiquímico para acanalar.', '1', 'Kg', '0.00', '0.00') - ,('48051201', '4805.12.01', 'Papel paja para acanalar.', '1', 'Kg', '0.00', '0.00') - ,('48051999', '4805.19.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('48052402', '4805.24.02', 'De peso inferior o igual a 150 g/m2.', '1', 'Kg', '20.00', '0.00') - ,('48052502', '4805.25.02', 'Los demás.', '1', 'Kg', '35.00', '0.00') - ,('48053001', '4805.30.01', 'Papel sulfito para envolver.', '1', 'Kg', '0.00', '0.00') - ,('48054001', '4805.40.01', 'Papel y cartón filtro.', '1', 'Kg', '0.00', '0.00') - ,('48055001', '4805.50.01', 'Papel y cartón fieltro, papel y cartón lana.', '1', 'Kg', '0.00', '0.00') - ,('48059101', '4805.91.01', 'De peso inferior o igual a 150 g/m².', '1', 'Kg', '0.00', '0.00') - ,('48059201', '4805.92.01', 'De peso superior a 150 g/m² pero inferior a 225 g/m².', '1', 'Kg', '0.00', '0.00') - ,('48059301', '4805.93.01', 'De peso superior o igual a 225 g/m².', '1', 'Kg', '0.00', '0.00') - ,('48061001', '4806.10.01', 'Papel y cartón sulfurizados (pergamino vegetal).', '1', 'Kg', '0.00', '0.00') - ,('48062001', '4806.20.01', 'Papel resistente a las grasas ("greaseproof").', '1', 'Kg', '0.00', '0.00') - ,('48063001', '4806.30.01', 'Papel vegetal (papel calco).', '1', 'Kg', '0.00', '0.00') - ,('48064001', '4806.40.01', 'Papel cristal y demás papeles calandrados transparentes o traslúcidos.', '1', 'Kg', '0.00', '0.00') - ,('48070003', '4807.00.03', 'Papel y cartón obtenidos por pegado de hojas planas, sin estucar ni recubrir en la superficie y sin impregnar, incluso reforzados interiormente, en bobinas (rollos) o en hojas.', '1', 'Kg', '0.00', '0.00') - ,('48081001', '4808.10.01', 'Papel y cartón corrugados, incluso perforados.', '1', 'Kg', '0.00', '0.00') - ,('48084004', '4808.40.04', 'Papel Kraft rizado ("crepé") o plisado, incluso gofrado, estampado o perforado.', '1', 'Kg', '0.00', '0.00') - ,('48089099', '4808.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('48092001', '4809.20.01', 'Papel autocopia.', '1', 'Kg', '5.00', '0.00') - ,('48099002', '4809.90.02', 'Papel carbón (carbónico) y papeles similares.', '1', 'Kg', '5.00', '0.00') - ,('48099099', '4809.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('48101307', '4810.13.07', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('48101407', '4810.14.07', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('48101999', '4810.19.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('48102202', '4810.22.02', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('48102999', '4810.29.99', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('48103101', '4810.31.01', 'Estucados, pintados o recubiertos por una o ambas caras, satinados o abrillantados, con peso superior o igual a 74 g/m².', '1', 'Kg', '0.00', '0.00') - ,('48103102', '4810.31.02', 'Coloreados por ambas caras con peso inferior o igual a 120 g/m², excepto lo comprendido en la fracción arancelaria 4810.31.01.', '1', 'Kg', '0.00', '0.00') - ,('48103103', '4810.31.03', 'Coloreados o decorados por una cara, excepto lo comprendido en la fracción arancelaria 4810.31.01.', '1', 'Kg', '0.00', '0.00') - ,('48103199', '4810.31.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('48103201', '4810.32.01', 'Blanqueados uniformemente en la masa y con un contenido de fibras de madera obtenidas por procedimiento químico superior al 95% en peso del contenido total de fibra, de peso superior a 150 g/m².', '1', 'Kg', '25.00', '0.00') - ,('48103999', '4810.39.99', 'Los demás.', '1', 'Kg', '20.00', '0.00') - ,('48109201', '4810.92.01', 'Multicapas.', '1', 'Kg', '25.00', '0.00') - ,('48109999', '4810.99.99', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('48111002', '4811.10.02', 'Cubresuelos con soporte de papel o cartón, incluso recortados.', '1', 'Kg', '5.00', '0.00') - ,('48111099', '4811.10.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('48114101', '4811.41.01', 'Autoadhesivos, excepto los comprendidos en la fracción arancelaria 4811.41.02.', '1', 'Kg', '0.00', '0.00') - ,('48114102', '4811.41.02', 'En tiras o en bobinas (rollos).', '1', 'Kg', '5.00', '0.00') - ,('48114901', '4811.49.01', 'En tiras o en bobinas (rollos).', '1', 'Kg', '5.00', '0.00') - ,('48114999', '4811.49.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('48115101', '4811.51.01', 'Recubiertos por una de sus caras, con una película de materia plástica artificial, aun cuando también lleven recubrimiento de otros materiales, excepto papel milimétrico recubierto por una cara, con una película de materia plástica artificial “laminene-milimétrico”.', '1', 'Kg', '0.00', '0.00') - ,('48115102', '4811.51.02', 'Recubiertos con caolín y resinas sintéticas, repelentes a películas vinílicas, resistentes a la tensión así como a temperaturas iguales o superiores a 210°C, con peso superior a 100 g/m².', '1', 'Kg', '0.00', '0.00') - ,('48115104', '4811.51.04', 'Filtro, impregnados de resinas sintéticas, aun cuando estén coloreados o acanalados.', '1', 'Kg', '0.00', '0.00') - ,('48115105', '4811.51.05', 'De fibras de algodón, con un contenido de algodón "transparentizado" del 100%, impregnado con resinas sintéticas.', '1', 'Kg', '0.00', '0.00') - ,('48115199', '4811.51.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('48115907', '4811.59.07', 'Cubresuelos con soporte de papel o cartón, incluso recortados.', '1', 'Kg', '5.00', '0.00') - ,('48115999', '4811.59.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('48116003', '4811.60.03', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('48119010', '4811.90.10', 'Cubresuelos con soporte de papel o cartón, incluso recortados.', '1', 'Kg', '5.00', '0.00') - ,('48119099', '4811.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('48120001', '4812.00.01', 'Bloques y placas, filtrantes, de pasta de papel.', '1', 'Kg', '0.00', '0.00') - ,('48131001', '4813.10.01', 'En librillos o en tubos.', '1', 'Kg', '0.00', '0.00') - ,('48132001', '4813.20.01', 'En bobinas (rollos) de anchura inferior o igual a 5 cm.', '1', 'Kg', '0.00', '0.00') - ,('48139099', '4813.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('48142001', '4814.20.01', 'Papel para decorar y revestimientos similares de paredes, constituidos por papel recubierto o revestido, en la cara vista, con una capa de plástico graneada, gofrada, coloreada, impresa con motivos o decorada de otro modo.', '1', 'Kg', '5.00', '0.00') - ,('48149002', '4814.90.02', 'Papel granito ("ingrain").', '1', 'Kg', '0.00', '0.00') - ,('48149099', '4814.90.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('48162001', '4816.20.01', 'Papel autocopia.', '1', 'Kg', '5.00', '0.00') - ,('48169099', '4816.90.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('48171001', '4817.10.01', 'Sobres.', '1', 'Kg', '5.00', '0.00') - ,('48172001', '4817.20.01', 'Sobres carta, tarjetas postales sin ilustrar y tarjetas para correspondencia.', '1', 'Kg', '5.00', '0.00') - ,('48173001', '4817.30.01', 'Cajas, bolsas y presentaciones similares de papel o cartón, con un surtido de artículos de correspondencia.', '1', 'Kg', '35.00', '0.00') - ,('48181001', '4818.10.01', 'Papel higiénico.', '1', 'Kg', '5.00', '0.00') - ,('48182001', '4818.20.01', 'Pañuelos, toallitas de desmaquillar y toallas.', '1', 'Kg', '5.00', '0.00') - ,('48183001', '4818.30.01', 'Manteles y servilletas.', '1', 'Kg', '5.00', '0.00') - ,('48185001', '4818.50.01', 'Prendas y complementos (accesorios), de vestir.', '1', 'Kg', '5.00', '0.00') - ,('48189099', '4818.90.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('48191001', '4819.10.01', 'Cajas de papel o cartón corrugado.', '1', 'Kg', '0.00', '0.00') - ,('48192002', '4819.20.02', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('48193001', '4819.30.01', 'Sacos (bolsas) con una anchura en la base superior o igual a 40 cm.', '1', 'Kg', '0.00', '0.00') - ,('48194091', '4819.40.91', 'Los demás sacos (bolsas); bolsitas y cucuruchos.', '1', 'Kg', '5.00', '0.00') - ,('48195091', '4819.50.91', 'Los demás envases, incluidas las fundas para discos.', '1', 'Kg', '5.00', '0.00') - ,('48196001', '4819.60.01', 'Cartonajes de oficina, tienda o similares.', '1', 'Kg', '5.00', '0.00') - ,('48201002', '4820.10.02', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('48202001', '4820.20.01', 'Cuadernos.', '1', 'Kg', '35.00', '0.00') - ,('48203001', '4820.30.01', 'Clasificadores, encuadernaciones (excepto las cubiertas para libros), carpetas y cubiertas para documentos.', '1', 'Kg', '5.00', '0.00') - ,('48204001', '4820.40.01', 'Formularios en paquetes o plegados ("manifold"), aunque lleven papel carbón (carbónico).', '1', 'Kg', '5.00', '0.00') - ,('48205001', '4820.50.01', 'Álbumes para muestras o para colecciones.', '1', 'Kg', '5.00', '0.00') - ,('48209099', '4820.90.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('48211001', '4821.10.01', 'Impresas.', '1', 'Kg', '0.00', '0.00') - ,('48219099', '4821.90.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('48221001', '4822.10.01', 'De los tipos utilizados para el bobinado de hilados textiles.', '1', 'Kg', '5.00', '0.00') - ,('48229099', '4822.90.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('48232002', '4823.20.02', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('48234001', '4823.40.01', 'Papel diagrama para aparatos registradores, en bobinas (rollos), hojas o discos.', '1', 'Kg', '0.00', '0.00') - ,('48236101', '4823.61.01', 'De bambú.', '1', 'Kg', '5.00', '0.00') - ,('48236999', '4823.69.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('48237003', '4823.70.03', 'Empaquetaduras.', '1', 'Kg', '0.00', '0.00') - ,('48237099', '4823.70.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('48239001', '4823.90.01', 'Para usos dieléctricos.', '1', 'Kg', '0.00', '0.00') - ,('48239002', '4823.90.02', 'Crepé en bandas, excepto lo comprendido en la fracción arancelaria 4823.90.01.', '1', 'Kg', '0.00', '0.00') - ,('48239005', '4823.90.05', 'Recubierto con resinas plásticas, con ancho superior o igual a 9 mm.', '1', 'Kg', '0.00', '0.00') - ,('48239010', '4823.90.10', 'Semiconos de papel filtro.', '1', 'Kg', '0.00', '0.00') - ,('48239011', '4823.90.11', 'Para protección de películas fotográficas.', '1', 'Kg', '30.00', '0.00') - ,('48239012', '4823.90.12', 'Cartón de pasta teñida en la masa, superficie jaspeada con peso superior a 500 g/m² sin exceder de 900 g/m² ("pressboard").', '1', 'Kg', '0.00', '0.00') - ,('48239017', '4823.90.17', 'Cubresuelos con soporte de papel o cartón, incluso recortados.', '1', 'Kg', '7.00', '0.00') - ,('48239099', '4823.90.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('49011001', '4901.10.01', 'Obras de la literatura universal y libros técnicos, científicos o de arte, incluso los de carácter biográfico.', '1', 'Kg', '0.00', '0.00') - ,('49011099', '4901.10.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('49019104', '4901.91.04', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('49019901', '4901.99.01', 'Impresos y publicado en México.', '6', 'Pza', '0.00', '0.00') - ,('49019902', '4901.99.02', 'Para la enseñanza primaria.', '6', 'Pza', '0.00', '0.00') - ,('49019903', '4901.99.03', 'Anuarios científicos o técnicos, excepto lo comprendido en la fracción arancelaria 4901.99.01.', '6', 'Pza', '0.00', '0.00') - ,('49019904', '4901.99.04', 'Obras de la literatura universal, libros o fascículos técnicos, científicos o de arte, incluso los de carácter biográfico, impresos en español, aunque contengan otros idiomas, excepto lo comprendido en las fracciones arancelarias 4901.99.01, 4901.99.02 y 4901.99.05.', '6', 'Pza', '0.00', '0.00') - ,('49019905', '4901.99.05', 'Impresos en relieve para uso de ciegos.', '6', 'Pza', '0.00', '0.00') - ,('49019991', '4901.99.91', 'Las demás obras de la literatura universal, libros o fascículos técnicos, científicos o de arte, incluso los de carácter biográfico, excepto lo comprendido en las fracciones arancelarias 4901.99.01, 4901.99.02, 4901.99.04 y 4901.99.05.', '6', 'Pza', '25.00', '0.00') - ,('49019999', '4901.99.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('49021002', '4902.10.02', 'Que se publiquen cuatro veces por semana como mínimo.', '1', 'Kg', '0.00', '0.00') - ,('49029099', '4902.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('49030001', '4903.00.01', 'Álbumes o libros de estampas.', '6', 'Pza', '0.00', '0.00') - ,('49030099', '4903.00.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('49040001', '4904.00.01', 'Música manuscrita o impresa, incluso con ilustraciones o encuadernada.', '6', 'Pza', '0.00', '0.00') - ,('49052001', '4905.20.01', 'Cartas geográficas, topográficas o náuticas; mapas murales.', '6', 'Pza', '5.00', '0.00') - ,('49052099', '4905.20.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('49059001', '4905.90.01', 'Esferas.', '6', 'Pza', '35.00', '0.00') - ,('49059002', '4905.90.02', 'Cartas geográficas, topográficas o náuticas; mapas murales.', '6', 'Pza', '25.00', '0.00') - ,('49059099', '4905.90.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('49060001', '4906.00.01', 'Planos y dibujos originales hechos a mano, de arquitectura, ingeniería, industriales, comerciales, topográficos o similares; textos manuscritos; reproducciones fotográficas sobre papel sensibilizado y copias con papel carbón (carbónico), de los planos, dibujos o textos antes mencionados.', '1', 'Kg', '35.00', '0.00') - ,('49070003', '4907.00.03', 'Cheques de viajero.', '1', 'Kg', '0.00', '0.00') - ,('49070099', '4907.00.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('49081001', '4908.10.01', 'Calcomanías vitrificables policromas, elaboradas con pigmentos metálicos, sobre soportes de papel para ser fijadas a temperaturas mayores de 500°C, diseñadas exclusivamente para ser aplicadas a loza, cerámica, porcelana y vidrio.', '1', 'Kg', '0.00', '0.00') - ,('49081099', '4908.10.99', 'Las demás.', '1', 'Kg', '15.00', '0.00') - ,('49089002', '4908.90.02', 'Para estampar tejidos.', '1', 'Kg', '10.00', '0.00') - ,('49089003', '4908.90.03', 'Calcomanías adheribles por calor, diseñadas exclusivamente para ser aplicadas en materiales plásticos y caucho.', '1', 'Kg', '5.00', '0.00') - ,('49089005', '4908.90.05', 'Impresas a colores o en blanco y negro, presentadas para su venta en sobres o paquetes, aun cuando incluyan goma de mascar, dulces o cualquier otro tipo de artículos, conteniendo dibujos, figuras o ilustraciones que representen a la niñez de manera denigrante o ridícula, en actitudes de incitación a la violencia, a la autodestrucción o en cualquier otra forma de comportamiento antisocial, conocidas como "Garbage Pail Kids", por ejemplo, impresas por cualquier empresa o denominación comercial.', '22', 'PROH', '0.00', '0.00') - ,('49089099', '4908.90.99', 'Las demás.', '1', 'Kg', '35.00', '0.00') - ,('49090001', '4909.00.01', 'Tarjetas postales impresas o ilustradas; tarjetas impresas con felicitaciones o comunicaciones personales, incluso con ilustraciones, adornos o aplicaciones, o con sobres.', '1', 'Kg', '15.00', '0.00') - ,('49100001', '4910.00.01', 'Calendarios de cualquier clase impresos, incluidos los tacos de calendario.', '1', 'Kg', '15.00', '0.00') - ,('49111001', '4911.10.01', 'Catálogos en idioma distinto del español, cuando se importen asignados en cantidad no mayor de 3 ejemplares por destinatarios.', '1', 'Kg', '5.00', '0.00') - ,('49111002', '4911.10.02', 'Guías, horarios o demás impresos relativos a servicios de transporte de compañías que operen en el extranjero.', '1', 'Kg', '5.00', '0.00') - ,('49111003', '4911.10.03', 'Folletos o publicaciones turísticas.', '1', 'Kg', '5.00', '0.00') - ,('49111004', '4911.10.04', 'Figuras o paisajes, impresos o fotografiados sobre tejidos.', '1', 'Kg', '5.00', '0.00') - ,('49111099', '4911.10.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('49119101', '4911.91.01', 'Estampas, dibujos, fotografías, sobre papel o cartón para la edición de libros o colecciones de carácter educativo o cultural.', '1', 'Kg', '5.00', '0.00') - ,('49119103', '4911.91.03', 'Terapéutico-pedagógicos, reconocibles como diseñados exclusivamente para instituciones de educación especial o similares.', '1', 'Kg', '25.00', '0.00') - ,('49119105', '4911.91.05', 'Impresas a colores o en blanco y negro, presentadas para su venta en sobres o paquetes, aun cuando incluyan goma de mascar, dulces o cualquier otro tipo de artículos, conteniendo dibujos, figuras o ilustraciones que representen a la niñez de manera denigrante o ridícula, en actitudes de incitación a la violencia, a la autodestrucción o en cualquier otra forma de comportamiento antisocial, conocidas como "Garbage Pail Kids", por ejemplo, impresas por cualquier empresa o denominación comercial.', '22', 'PROH', '0.00', '0.00') - ,('49119199', '4911.91.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('49119901', '4911.99.01', 'Cuadros murales para escuelas.', '1', 'Kg', '5.00', '0.00') - ,('49119906', '4911.99.06', 'Terapéutico-pedagógicos, reconocibles como diseñados exclusivamente para instituciones de educación especial o similares.', '1', 'Kg', '5.00', '0.00') - ,('49119999', '4911.99.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('50010001', '5001.00.01', 'Capullos de seda aptos para el devanado.', '1', 'Kg', '0.00', '0.00') - ,('50020001', '5002.00.01', 'Seda cruda (sin torcer).', '1', 'Kg', '0.00', '0.00') - ,('50030002', '5003.00.02', 'Desperdicios de seda (incluidos los capullos no aptos para el devanado, desperdicios de hilados e hilachas).', '1', 'Kg', '0.00', '0.00') - ,('50040001', '5004.00.01', 'Hilados de seda (excepto los hilados de desperdicios de seda) sin acondicionar para la venta al por menor.', '1', 'Kg', '10.00', '0.00') - ,('50050001', '5005.00.01', 'Hilados de desperdicios de seda sin acondicionar para la venta al por menor.', '1', 'Kg', '10.00', '0.00') - ,('50060001', '5006.00.01', 'Hilados de seda o de desperdicios de seda, acondicionados para la venta al por menor; "pelo de Mesina" ("crin de Florencia").', '1', 'Kg', '10.00', '0.00') - ,('50071001', '5007.10.01', 'Tejidos de borrilla.', '4', 'M²', '10.00', '0.00') - ,('50072091', '5007.20.91', 'Los demás tejidos con un contenido de seda o de desperdicios de seda, distintos de la borrilla, superior o igual al 85% en peso.', '4', 'M²', '10.00', '0.00') - ,('50079091', '5007.90.91', 'Los demás tejidos.', '4', 'M²', '10.00', '0.00') - ,('51011102', '5101.11.02', 'Lana esquilada.', '1', 'Kg', '0.00', '0.00') - ,('51011999', '5101.19.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('51012102', '5101.21.02', 'Lana esquilada.', '1', 'Kg', '0.00', '0.00') - ,('51012999', '5101.29.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('51013002', '5101.30.02', 'Carbonizada.', '1', 'Kg', '0.00', '0.00') - ,('51021101', '5102.11.01', 'De cabra de Cachemira.', '1', 'Kg', '0.00', '0.00') - ,('51021999', '5102.19.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('51022002', '5102.20.02', 'Pelo ordinario.', '1', 'Kg', '0.00', '0.00') - ,('51031003', '5103.10.03', 'Borras del peinado de lana o pelo fino.', '1', 'Kg', '0.00', '0.00') - ,('51032091', '5103.20.91', 'Los demás desperdicios de lana o pelo fino.', '1', 'Kg', '0.00', '0.00') - ,('51033001', '5103.30.01', 'Desperdicios de pelo ordinario.', '1', 'Kg', '0.00', '0.00') - ,('51040001', '5104.00.01', 'Hilachas de lana o de pelo fino u ordinario.', '1', 'Kg', '0.00', '0.00') - ,('51051001', '5105.10.01', 'Lana cardada.', '1', 'Kg', '0.00', '0.00') - ,('51052101', '5105.21.01', 'Lana peinada a granel.', '1', 'Kg', '0.00', '0.00') - ,('51052999', '5105.29.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('51053101', '5105.31.01', 'De cabra de Cachemira.', '1', 'Kg', '0.00', '0.00') - ,('51053999', '5105.39.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('51054001', '5105.40.01', 'Pelo ordinario cardado o peinado.', '1', 'Kg', '0.00', '0.00') - ,('51061001', '5106.10.01', 'Con un contenido de lana superior o igual al 85% en peso.', '1', 'Kg', '10.00', '0.00') - ,('51062001', '5106.20.01', 'Con un contenido de lana inferior al 85% en peso.', '1', 'Kg', '10.00', '0.00') - ,('51071001', '5107.10.01', 'Con un contenido de lana superior o igual al 85% en peso.', '1', 'Kg', '10.00', '0.00') - ,('51072001', '5107.20.01', 'Con un contenido de lana inferior al 85% en peso.', '1', 'Kg', '10.00', '0.00') - ,('51081001', '5108.10.01', 'Cardado.', '1', 'Kg', '10.00', '0.00') - ,('51082001', '5108.20.01', 'Peinado.', '1', 'Kg', '10.00', '0.00') - ,('51091001', '5109.10.01', 'Con un contenido de lana o pelo fino superior o igual al 85% en peso.', '1', 'Kg', '10.00', '0.00') - ,('51099099', '5109.90.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('51100001', '5110.00.01', 'Hilados de pelo ordinario o de crin (incluidos los hilados de crin entorchados), aunque estén acondicionados para la venta al por menor.', '1', 'Kg', '10.00', '0.00') - ,('51111102', '5111.11.02', 'De peso inferior o igual a 300 g/m².', '4', 'M²', '10.00', '0.00') - ,('51111999', '5111.19.99', 'Los demás.', '4', 'M²', '10.00', '0.00') - ,('51112091', '5111.20.91', 'Los demás, mezclados exclusiva o principalmente con filamentos sintéticos o artificiales.', '4', 'M²', '10.00', '0.00') - ,('51113091', '5111.30.91', 'Los demás, mezclados exclusiva o principalmente con fibras sintéticas o artificiales discontinuas.', '4', 'M²', '10.00', '0.00') - ,('51119099', '5111.90.99', 'Los demás.', '4', 'M²', '10.00', '0.00') - ,('51121102', '5112.11.02', 'De peso inferior o igual a 200 g/m².', '4', 'M²', '10.00', '0.00') - ,('51121999', '5112.19.99', 'Los demás.', '4', 'M²', '10.00', '0.00') - ,('51122091', '5112.20.91', 'Los demás, mezclados exclusiva o principalmente con filamentos sintéticos o artificiales.', '4', 'M²', '10.00', '0.00') - ,('51123091', '5112.30.91', 'Los demás, mezclados exclusiva o principalmente con fibras sintéticas o artificiales discontinuas.', '4', 'M²', '10.00', '0.00') - ,('51129099', '5112.90.99', 'Los demás.', '4', 'M²', '10.00', '0.00') - ,('51130002', '5113.00.02', 'Tejidos de pelo ordinario o de crin.', '4', 'M²', '10.00', '0.00') - ,('52010003', '5201.00.03', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('52021001', '5202.10.01', 'Desperdicios de hilados.', '1', 'Kg', '0.00', '0.00') - ,('52029101', '5202.91.01', 'Hilachas.', '1', 'Kg', '0.00', '0.00') - ,('52029999', '5202.99.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('52030001', '5203.00.01', 'Algodón cardado o peinado.', '1', 'Kg', '0.00', '0.00') - ,('52041101', '5204.11.01', 'Con un contenido de algodón superior o igual al 85% en peso.', '1', 'Kg', '10.00', '0.00') - ,('52041999', '5204.19.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('52042001', '5204.20.01', 'Acondicionado para la venta al por menor.', '1', 'Kg', '10.00', '0.00') - ,('52051101', '5205.11.01', 'De título superior o igual a 714.29 decitex (inferior o igual al número métrico 14).', '1', 'Kg', '10.00', '0.00') - ,('52051201', '5205.12.01', 'De título inferior a 714.29 decitex pero superior o igual a 232.56 decitex (superior al número métrico 14 pero inferior o igual al número métrico 43).', '1', 'Kg', '10.00', '0.00') - ,('52051301', '5205.13.01', 'De título inferior a 232.56 decitex pero superior o igual a 192.31 decitex (superior al número métrico 43 pero inferior o igual al número métrico 52).', '1', 'Kg', '10.00', '0.00') - ,('52051401', '5205.14.01', 'De título inferior a 192.31 decitex pero superior o igual a 125 decitex (superior al número métrico 52 pero inferior o igual al número métrico 80).', '1', 'Kg', '10.00', '0.00') - ,('52051501', '5205.15.01', 'De título inferior a 125 decitex (superior al número métrico 80).', '1', 'Kg', '10.00', '0.00') - ,('52052101', '5205.21.01', 'De título superior o igual a 714.29 decitex (inferior o igual al número métrico 14).', '1', 'Kg', '10.00', '0.00') - ,('52052201', '5205.22.01', 'De título inferior a 714.29 decitex pero superior o igual a 232.56 decitex (superior al número métrico 14 pero inferior o igual al número métrico 43).', '1', 'Kg', '10.00', '0.00') - ,('52052301', '5205.23.01', 'De título inferior a 232.56 decitex pero superior o igual a 192.31 decitex (superior al número métrico 43 pero inferior o igual al número métrico 52).', '1', 'Kg', '10.00', '0.00') - ,('52052401', '5205.24.01', 'De título inferior a 192.31 decitex pero superior o igual a 125 decitex (superior al número métrico 52 pero inferior o igual al número métrico 80).', '1', 'Kg', '10.00', '0.00') - ,('52052601', '5205.26.01', 'De título inferior a 125 decitex pero superior o igual a 106.38 decitex (superior al número métrico 80 pero inferior o igual al número métrico 94).', '1', 'Kg', '10.00', '0.00') - ,('52052701', '5205.27.01', 'De título inferior a 106.38 decitex pero superior o igual a 83.33 decitex (superior al número métrico 94 pero inferior o igual al número métrico 120).', '1', 'Kg', '10.00', '0.00') - ,('52052801', '5205.28.01', 'De título inferior a 83.33 decitex (superior al número métrico 120).', '1', 'Kg', '10.00', '0.00') - ,('52053101', '5205.31.01', 'De título superior o igual a 714.29 decitex por hilo sencillo (inferior o igual al número métrico 14 por hilo sencillo).', '1', 'Kg', '10.00', '0.00') - ,('52053201', '5205.32.01', 'De título inferior a 714.29 decitex pero superior o igual a 232.56 decitex, por hilo sencillo (superior al número métrico 14 pero inferior o igual al número métrico 43, por hilo sencillo).', '1', 'Kg', '10.00', '0.00') - ,('52053301', '5205.33.01', 'De título inferior a 232.56 decitex pero superior o igual a 192.31 decitex, por hilo sencillo (superior al número métrico 43 pero inferior o igual al número métrico 52, por hilo sencillo).', '1', 'Kg', '10.00', '0.00') - ,('52053401', '5205.34.01', 'De título inferior a 192.31 decitex pero superior o igual a 125 decitex, por hilo sencillo (superior al número métrico 52 pero inferior o igual al número métrico 80, por hilo sencillo).', '1', 'Kg', '10.00', '0.00') - ,('52053501', '5205.35.01', 'De título inferior a 125 decitex por hilo sencillo (superior al número métrico 80 por hilo sencillo).', '1', 'Kg', '10.00', '0.00') - ,('52054101', '5205.41.01', 'De título superior o igual a 714.29 decitex por hilo sencillo (inferior o igual al número métrico 14 por hilo sencillo).', '1', 'Kg', '10.00', '0.00') - ,('52054201', '5205.42.01', 'De título inferior a 714.29 decitex pero superior o igual a 232.56 decitex, por hilo sencillo (superior al número métrico 14 pero inferior o igual al número métrico 43, por hilo sencillo).', '1', 'Kg', '10.00', '0.00') - ,('52054301', '5205.43.01', 'De título inferior a 232.56 decitex pero superior o igual a 192.31 decitex, por hilo sencillo (superior al número métrico 43 pero inferior o igual al número métrico 52, por hilo sencillo).', '1', 'Kg', '10.00', '0.00') - ,('52054401', '5205.44.01', 'De título inferior a 192.31 decitex pero superior o igual a 125 decitex, por hilo sencillo (superior al número métrico 52 pero inferior o igual al número métrico 80, por hilo sencillo).', '1', 'Kg', '10.00', '0.00') - ,('52054601', '5205.46.01', 'De título inferior a 125 decitex pero superior o igual a 106.38 decitex, por hilo sencillo (superior al número métrico 80 pero inferior o igual al número métrico 94, por hilo sencillo).', '1', 'Kg', '10.00', '0.00') - ,('52054701', '5205.47.01', 'De título inferior a 106.38 decitex pero superior o igual a 83.33 decitex, por hilo sencillo (superior al número métrico 94 pero inferior o igual al número métrico 120, por hilo sencillo).', '1', 'Kg', '10.00', '0.00') - ,('52054801', '5205.48.01', 'De título inferior a 83.33 decitex por hilo sencillo (superior al número métrico 120 por hilo sencillo).', '1', 'Kg', '10.00', '0.00') - ,('52061101', '5206.11.01', 'De título superior o igual a 714.29 decitex (inferior o igual al número métrico 14).', '1', 'Kg', '10.00', '0.00') - ,('52061201', '5206.12.01', 'De título inferior a 714.29 decitex pero superior o igual a 232.56 decitex (superior al número métrico 14 pero inferior o igual al número métrico 43).', '1', 'Kg', '10.00', '0.00') - ,('52061301', '5206.13.01', 'De título inferior a 232.56 decitex pero superior o igual a 192.31 decitex (superior al número métrico 43 pero inferior o igual al número métrico 52).', '1', 'Kg', '10.00', '0.00') - ,('52061401', '5206.14.01', 'De título inferior a 192.31 decitex pero superior o igual a 125 decitex (superior al número métrico 52 pero inferior o igual al número métrico 80).', '1', 'Kg', '10.00', '0.00') - ,('52061501', '5206.15.01', 'De título inferior a 125 decitex (superior al número métrico 80).', '1', 'Kg', '10.00', '0.00') - ,('52062101', '5206.21.01', 'De título superior o igual a 714.29 decitex (inferior o igual al número métrico 14).', '1', 'Kg', '10.00', '0.00') - ,('52062201', '5206.22.01', 'De título inferior a 714.29 decitex pero superior o igual a 232.56 decitex (superior al número métrico 14 pero inferior o igual al número métrico 43).', '1', 'Kg', '10.00', '0.00') - ,('52062301', '5206.23.01', 'De título inferior a 232.56 decitex pero superior o igual a 192.31 decitex (superior al número métrico 43 pero inferior o igual al número métrico 52).', '1', 'Kg', '10.00', '0.00') - ,('52062401', '5206.24.01', 'De título inferior a 192.31 decitex pero superior o igual a 125 decitex (superior al número métrico 52 pero inferior o igual al número métrico 80).', '1', 'Kg', '10.00', '0.00') - ,('52062501', '5206.25.01', 'De título inferior a 125 decitex (superior al número métrico 80).', '1', 'Kg', '10.00', '0.00') - ,('52063101', '5206.31.01', 'De título superior o igual a 714.29 decitex por hilo sencillo (inferior o igual al número métrico 14 por hilo sencillo).', '1', 'Kg', '10.00', '0.00') - ,('52063201', '5206.32.01', 'De título inferior a 714.29 decitex pero superior o igual a 232.56 decitex, por hilo sencillo (superior al número métrico 14 pero inferior o igual al número métrico 43, por hilo sencillo).', '1', 'Kg', '10.00', '0.00') - ,('52063301', '5206.33.01', 'De título inferior a 232.56 decitex pero superior o igual a 192.31 decitex, por hilo sencillo (superior al número métrico 43 pero inferior o igual al número métrico 52, por hilo sencillo).', '1', 'Kg', '10.00', '0.00') - ,('52063401', '5206.34.01', 'De título inferior a 192.31 decitex pero superior o igual a 125 decitex, por hilo sencillo (superior al número métrico 52 pero inferior o igual al número métrico 80, por hilo sencillo).', '1', 'Kg', '10.00', '0.00') - ,('52063501', '5206.35.01', 'De título inferior a 125 decitex por hilo sencillo (superior al número métrico 80 por hilo sencillo).', '1', 'Kg', '10.00', '0.00') - ,('52064101', '5206.41.01', 'De título superior o igual a 714.29 decitex por hilo sencillo (inferior o igual al número métrico 14 por hilo sencillo).', '1', 'Kg', '10.00', '0.00') - ,('52064201', '5206.42.01', 'De título inferior a 714.29 decitex pero superior o igual a 232.56 decitex, por hilo sencillo (superior al número métrico 14 pero inferior o igual al número métrico 43, por hilo sencillo).', '1', 'Kg', '10.00', '0.00') - ,('52064301', '5206.43.01', 'De título inferior a 232.56 decitex pero superior o igual a 192.31 decitex, por hilo sencillo (superior al número métrico 43 pero inferior o igual al número métrico 52, por hilo sencillo).', '1', 'Kg', '10.00', '0.00') - ,('52064401', '5206.44.01', 'De título inferior a 192.31 decitex pero superior o igual a 125 decitex, por hilo sencillo (superior al número métrico 52 pero inferior o igual al número métrico 80, por hilo sencillo).', '1', 'Kg', '10.00', '0.00') - ,('52064501', '5206.45.01', 'De título inferior a 125 decitex por hilo sencillo (superior al número métrico 80 por hilo sencillo).', '1', 'Kg', '10.00', '0.00') - ,('52071001', '5207.10.01', 'Con un contenido de algodón superior o igual al 85% en peso.', '1', 'Kg', '10.00', '0.00') - ,('52079099', '5207.90.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('52081101', '5208.11.01', 'De ligamento tafetán, de peso inferior o igual a 100 g/m².', '4', 'M²', '35.00', '0.00') - ,('52081201', '5208.12.01', 'De ligamento tafetán, de peso superior a 100 g/m².', '4', 'M²', '10.00', '0.00') - ,('52081301', '5208.13.01', 'De ligamento sarga, incluido el cruzado, de curso inferior o igual a 4.', '4', 'M²', '10.00', '0.00') - ,('52081991', '5208.19.91', 'Los demás.', '4', 'M²', '35.00', '0.00') - ,('52082101', '5208.21.01', 'De ligamento tafetán, de peso inferior o igual a 100 g/m².', '4', 'M²', '35.00', '0.00') - ,('52082201', '5208.22.01', 'De ligamento tafetán, de peso superior a 100 g/m².', '4', 'M²', '10.00', '0.00') - ,('52082301', '5208.23.01', 'De ligamento sarga, incluido el cruzado, de curso inferior o igual a 4.', '4', 'M²', '10.00', '0.00') - ,('52082991', '5208.29.91', 'Los demás tejidos.', '4', 'M²', '10.00', '0.00') - ,('52083101', '5208.31.01', 'De ligamento tafetán, de peso inferior o igual a 100 g/m².', '4', 'M²', '10.00', '0.00') - ,('52083201', '5208.32.01', 'De ligamento tafetán, de peso superior a 100 g/m².', '4', 'M²', '10.00', '0.00') - ,('52083301', '5208.33.01', 'De ligamento sarga, incluido el cruzado, de curso inferior o igual a 4.', '4', 'M²', '10.00', '0.00') - ,('52083991', '5208.39.91', 'Los demás.', '4', 'M²', '35.00', '0.00') - ,('52084101', '5208.41.01', 'De ligamento tafetán, de peso inferior o igual a 100 g/m².', '4', 'M²', '35.00', '0.00') - ,('52084201', '5208.42.01', 'De ligamento tafetán, de peso superior a 100 g/m².', '4', 'M²', '10.00', '0.00') - ,('52084301', '5208.43.01', 'De ligamento sarga, incluido el cruzado, de curso inferior o igual a 4.', '4', 'M²', '10.00', '0.00') - ,('52084991', '5208.49.91', 'Los demás tejidos.', '4', 'M²', '10.00', '0.00') - ,('52085101', '5208.51.01', 'De ligamento tafetán, de peso inferior o igual a 100 g/m².', '4', 'M²', '10.00', '0.00') - ,('52085201', '5208.52.01', 'De ligamento tafetán, de peso superior a 100 g/m².', '4', 'M²', '10.00', '0.00') - ,('52085991', '5208.59.91', 'Los demás tejidos.', '4', 'M²', '10.00', '0.00') - ,('52091101', '5209.11.01', 'De ligamento tafetán.', '4', 'M²', '10.00', '0.00') - ,('52091201', '5209.12.01', 'De ligamento sarga, incluido el cruzado, de curso inferior o igual a 4.', '4', 'M²', '10.00', '0.00') - ,('52091991', '5209.19.91', 'Los demás tejidos.', '4', 'M²', '35.00', '0.00') - ,('52092101', '5209.21.01', 'De ligamento tafetán.', '4', 'M²', '10.00', '0.00') - ,('52092201', '5209.22.01', 'De ligamento sarga, incluido el cruzado, de curso inferior o igual a 4.', '4', 'M²', '10.00', '0.00') - ,('52092991', '5209.29.91', 'Los demás.', '4', 'M²', '10.00', '0.00') - ,('52093101', '5209.31.01', 'De ligamento tafetán.', '4', 'M²', '10.00', '0.00') - ,('52093201', '5209.32.01', 'De ligamento sarga, incluido el cruzado, de curso inferior o igual a 4.', '4', 'M²', '10.00', '0.00') - ,('52093991', '5209.39.91', 'Los demás.', '4', 'M²', '35.00', '0.00') - ,('52094101', '5209.41.01', 'De ligamento tafetán.', '4', 'M²', '10.00', '0.00') - ,('52094204', '5209.42.04', 'Los demás de peso superior a 300 g/m².', '4', 'M²', '10.00', '0.00') - ,('52094391', '5209.43.91', 'Los demás tejidos de ligamento sarga, incluido el cruzado, de curso inferior o igual a 4.', '4', 'M²', '10.00', '0.00') - ,('52094991', '5209.49.91', 'Los demás tejidos.', '4', 'M²', '35.00', '0.00') - ,('52095101', '5209.51.01', 'De ligamento tafetán.', '4', 'M²', '10.00', '0.00') - ,('52095201', '5209.52.01', 'De ligamento sarga, incluido el cruzado, de curso inferior o igual a 4.', '4', 'M²', '10.00', '0.00') - ,('52095991', '5209.59.91', 'Los demás tejidos.', '4', 'M²', '10.00', '0.00') - ,('52101102', '5210.11.02', 'Los demás.', '4', 'M²', '10.00', '0.00') - ,('52101991', '5210.19.91', 'Los demás tejidos.', '4', 'M²', '10.00', '0.00') - ,('52102101', '5210.21.01', 'De ligamento tafetán.', '4', 'M²', '10.00', '0.00') - ,('52102991', '5210.29.91', 'Los demás.', '4', 'M²', '10.00', '0.00') - ,('52103101', '5210.31.01', 'De ligamento tafetán.', '4', 'M²', '10.00', '0.00') - ,('52103201', '5210.32.01', 'De ligamento sarga, incluido el cruzado, de curso inferior o igual a 4.', '4', 'M²', '10.00', '0.00') - ,('52103991', '5210.39.91', 'Los demás.', '4', 'M²', '10.00', '0.00') - ,('52104101', '5210.41.01', 'De ligamento tafetán.', '4', 'M²', '10.00', '0.00') - ,('52104991', '5210.49.91', 'Los demás.', '4', 'M²', '10.00', '0.00') - ,('52105101', '5210.51.01', 'De ligamento tafetán.', '4', 'M²', '10.00', '0.00') - ,('52105991', '5210.59.91', 'Los demás tejidos.', '4', 'M²', '35.00', '0.00') - ,('52111102', '5211.11.02', 'Los demás.', '4', 'M²', '10.00', '0.00') - ,('52111201', '5211.12.01', 'De ligamento sarga, incluido el cruzado, de curso inferior o igual a 4.', '4', 'M²', '10.00', '0.00') - ,('52111991', '5211.19.91', 'Los demás tejidos.', '4', 'M²', '35.00', '0.00') - ,('52112004', '5211.20.04', 'Blanqueados.', '4', 'M²', '10.00', '0.00') - ,('52113101', '5211.31.01', 'De ligamento tafetán.', '4', 'M²', '10.00', '0.00') - ,('52113201', '5211.32.01', 'De ligamento sarga, incluido el cruzado, de curso inferior o igual a 4.', '4', 'M²', '35.00', '0.00') - ,('52113991', '5211.39.91', 'Los demás tejidos.', '4', 'M²', '10.00', '0.00') - ,('52114101', '5211.41.01', 'De ligamento tafetán.', '4', 'M²', '10.00', '0.00') - ,('52114204', '5211.42.04', 'Los demás de peso superior a 300 g/m².', '4', 'M²', '35.00', '0.00') - ,('52114391', '5211.43.91', 'Los demás tejidos de ligamento sarga, incluido el cruzado, de curso inferior o igual a 4.', '4', 'M²', '35.00', '0.00') - ,('52114991', '5211.49.91', 'Los demás tejidos.', '4', 'M²', '10.00', '0.00') - ,('52115101', '5211.51.01', 'De ligamento tafetán.', '4', 'M²', '10.00', '0.00') - ,('52115201', '5211.52.01', 'De ligamento sarga, incluido el cruzado, de curso inferior o igual a 4.', '4', 'M²', '10.00', '0.00') - ,('52115991', '5211.59.91', 'Los demás tejidos.', '4', 'M²', '10.00', '0.00') - ,('52121101', '5212.11.01', 'Crudos.', '4', 'M²', '10.00', '0.00') - ,('52121201', '5212.12.01', 'Blanqueados.', '4', 'M²', '10.00', '0.00') - ,('52121301', '5212.13.01', 'Teñidos.', '4', 'M²', '10.00', '0.00') - ,('52121401', '5212.14.01', 'Con hilados de distintos colores.', '4', 'M²', '10.00', '0.00') - ,('52121501', '5212.15.01', 'Estampados.', '4', 'M²', '10.00', '0.00') - ,('52122101', '5212.21.01', 'Crudos.', '4', 'M²', '10.00', '0.00') - ,('52122201', '5212.22.01', 'Blanqueados.', '4', 'M²', '10.00', '0.00') - ,('52122301', '5212.23.01', 'Teñidos.', '4', 'M²', '10.00', '0.00') - ,('52122402', '5212.24.02', 'Los demás.', '4', 'M²', '10.00', '0.00') - ,('52122501', '5212.25.01', 'Estampados.', '4', 'M²', '10.00', '0.00') - ,('53011001', '5301.10.01', 'Lino en bruto o enriado.', '1', 'Kg', '0.00', '0.00') - ,('53012101', '5301.21.01', 'Agramado o espadado.', '1', 'Kg', '0.00', '0.00') - ,('53012999', '5301.29.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('53013001', '5301.30.01', 'Estopas y desperdicios de lino.', '1', 'Kg', '0.00', '0.00') - ,('53021001', '5302.10.01', 'Cáñamo en bruto o enriado.', '1', 'Kg', '0.00', '0.00') - ,('53029099', '5302.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('53031001', '5303.10.01', 'Yute y demás fibras textiles del líber, en bruto o enriados.', '1', 'Kg', '0.00', '0.00') - ,('53039099', '5303.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('53050008', '5305.00.08', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('53061001', '5306.10.01', 'Sencillos.', '1', 'Kg', '10.00', '0.00') - ,('53062001', '5306.20.01', 'Retorcidos o cableados.', '1', 'Kg', '10.00', '0.00') - ,('53071001', '5307.10.01', 'Sencillos.', '1', 'Kg', '0.00', '0.00') - ,('53072001', '5307.20.01', 'Retorcidos o cableados.', '1', 'Kg', '0.00', '0.00') - ,('53081001', '5308.10.01', 'Hilados de coco.', '1', 'Kg', '0.00', '0.00') - ,('53082001', '5308.20.01', 'Hilados de cáñamo.', '1', 'Kg', '10.00', '0.00') - ,('53089003', '5308.90.03', 'De ramio.', '1', 'Kg', '10.00', '0.00') - ,('53089099', '5308.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('53091101', '5309.11.01', 'Crudos o blanqueados.', '4', 'M²', '10.00', '0.00') - ,('53091999', '5309.19.99', 'Los demás.', '4', 'M²', '10.00', '0.00') - ,('53092101', '5309.21.01', 'Crudos o blanqueados.', '4', 'M²', '10.00', '0.00') - ,('53092999', '5309.29.99', 'Los demás.', '4', 'M²', '10.00', '0.00') - ,('53101001', '5310.10.01', 'Crudos.', '4', 'M²', '10.00', '0.00') - ,('53109099', '5310.90.99', 'Los demás.', '4', 'M²', '10.00', '0.00') - ,('53110002', '5311.00.02', 'Tejidos de las demás fibras textiles vegetales; tejidos de hilados de papel.', '4', 'M²', '10.00', '0.00') - ,('54011001', '5401.10.01', 'De filamentos sintéticos.', '1', 'Kg', '10.00', '0.00') - ,('54012001', '5401.20.01', 'De filamentos artificiales.', '1', 'Kg', '10.00', '0.00') - ,('54021101', '5402.11.01', 'De aramidas.', '1', 'Kg', '0.00', '0.00') - ,('54021999', '5402.19.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('54022002', '5402.20.02', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('54023101', '5402.31.01', 'De nailon o demás poliamidas, de título inferior o igual a 50 tex por hilo sencillo.', '1', 'Kg', '5.00', '0.00') - ,('54023201', '5402.32.01', 'De nailon o demás poliamidas, de título superior a 50 tex por hilo sencillo.', '1', 'Kg', '5.00', '0.00') - ,('54023301', '5402.33.01', 'De poliésteres.', '1', 'Kg', '5.00', '0.00') - ,('54023401', '5402.34.01', 'De polipropileno.', '1', 'Kg', '0.00', '0.00') - ,('54023999', '5402.39.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('54024401', '5402.44.01', 'De poliuretanos, del tipo de los denominados "elastanos", sin torsión, no en carretes de urdido (enjulios).', '1', 'Kg', '5.00', '0.00') - ,('54024499', '5402.44.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('54024503', '5402.45.03', 'De aramidas.', '1', 'Kg', '0.00', '0.00') - ,('54024599', '5402.45.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('54024691', '5402.46.91', 'Los demás, de poliésteres, parcialmente orientados.', '1', 'Kg', '5.00', '0.00') - ,('54024791', '5402.47.91', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('54024891', '5402.48.91', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('54024906', '5402.49.06', 'De poliuretanos.', '1', 'Kg', '5.00', '0.00') - ,('54024999', '5402.49.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('54025102', '5402.51.02', 'De nailon o demás poliamidas.', '1', 'Kg', '0.00', '0.00') - ,('54025203', '5402.52.03', 'De poliésteres.', '1', 'Kg', '5.00', '0.00') - ,('54025301', '5402.53.01', 'De polipropileno.', '1', 'Kg', '0.00', '0.00') - ,('54025999', '5402.59.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('54026102', '5402.61.02', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('54026202', '5402.62.02', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('54026301', '5402.63.01', 'De polipropileno.', '1', 'Kg', '0.00', '0.00') - ,('54026999', '5402.69.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('54031001', '5403.10.01', 'Hilados de alta tenacidad de rayón viscosa.', '1', 'Kg', '0.00', '0.00') - ,('54033103', '5403.31.03', 'De hilados texturados.', '1', 'Kg', '0.00', '0.00') - ,('54033203', '5403.32.03', 'De rayón viscosa, con una torsión superior a 120 vueltas por metro.', '1', 'Kg', '0.00', '0.00') - ,('54033301', '5403.33.01', 'De acetato de celulosa.', '1', 'Kg', '0.00', '0.00') - ,('54033999', '5403.39.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('54034103', '5403.41.03', 'De hilados texturados.', '1', 'Kg', '0.00', '0.00') - ,('54034201', '5403.42.01', 'De acetato de celulosa.', '1', 'Kg', '0.00', '0.00') - ,('54034999', '5403.49.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('54041101', '5404.11.01', 'De poliuretanos, del tipo de los denominados "elastanos".', '1', 'Kg', '5.00', '0.00') - ,('54041199', '5404.11.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('54041291', '5404.12.91', 'Los demás, de polipropileno.', '1', 'Kg', '0.00', '0.00') - ,('54041999', '5404.19.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('54049099', '5404.90.99', 'Las demás.', '1', 'Kg', '5.00', '0.00') - ,('54050005', '5405.00.05', 'Monofilamentos artificiales de título superior o igual a 67 decitex y cuya mayor dimensión de la sección transversal sea inferior o igual a 1 mm; tiras y formas similares (por ejemplo, paja artificial) de materia textil artificial, de anchura aparente inferior o igual a 5 mm.', '1', 'Kg', '0.00', '0.00') - ,('54060001', '5406.00.01', 'De poliamidas o superpoliamidas.', '1', 'Kg', '10.00', '0.00') - ,('54060002', '5406.00.02', 'De aramidas, retardantes a la flama.', '1', 'Kg', '0.00', '0.00') - ,('54060005', '5406.00.05', 'Hilados de filamentos artificiales.', '1', 'Kg', '0.00', '0.00') - ,('54060091', '5406.00.91', 'Los demás hilados de filamentos sintéticos.', '1', 'Kg', '10.00', '0.00') - ,('54071003', '5407.10.03', 'Tejidos fabricados con hilados de alta tenacidad de nailon o demás poliamidas o de poliésteres.', '4', 'M²', '10.00', '0.00') - ,('54072002', '5407.20.02', 'Los demás.', '4', 'M²', '10.00', '0.00') - ,('54073004', '5407.30.04', 'Los demás.', '4', 'M²', '10.00', '0.00') - ,('54074105', '5407.41.05', 'De peso superior a 100 g/m2.', '4', 'M²', '10.00', '0.00') - ,('54074205', '5407.42.05', 'De peso superior a 100 g/m2.', '4', 'M²', '10.00', '0.00') - ,('54074304', '5407.43.04', 'Con hilados de distintos colores.', '4', 'M²', '10.00', '0.00') - ,('54074401', '5407.44.01', 'Estampados.', '4', 'M²', '10.00', '0.00') - ,('54075105', '5407.51.05', 'De peso superior a 200 g/m².', '4', 'M²', '10.00', '0.00') - ,('54075205', '5407.52.05', 'De peso superior a 200 g/m2.', '4', 'M²', '10.00', '0.00') - ,('54075304', '5407.53.04', 'Los demás de peso superior a 200 g/m2.', '4', 'M²', '10.00', '0.00') - ,('54075405', '5407.54.05', 'De peso superior a 200 g/m2.', '4', 'M²', '10.00', '0.00') - ,('54076106', '5407.61.06', 'Los demás de peso superior a 200 g/m2.', '4', 'M²', '35.00', '0.00') - ,('54076999', '5407.69.99', 'Los demás de peso superior a 200 g/m2.', '4', 'M²', '10.00', '0.00') - ,('54077101', '5407.71.01', 'Crudos o blanqueados.', '4', 'M²', '10.00', '0.00') - ,('54077201', '5407.72.01', 'Teñidos.', '4', 'M²', '10.00', '0.00') - ,('54077304', '5407.73.04', 'Los demás.', '4', 'M²', '10.00', '0.00') - ,('54077401', '5407.74.01', 'Estampados.', '4', 'M²', '10.00', '0.00') - ,('54078101', '5407.81.01', 'Crudos o blanqueados.', '4', 'M²', '10.00', '0.00') - ,('54078204', '5407.82.04', 'Los demás.', '4', 'M²', '10.00', '0.00') - ,('54078301', '5407.83.01', 'Con hilados de distintos colores.', '4', 'M²', '10.00', '0.00') - ,('54078401', '5407.84.01', 'Estampados.', '4', 'M²', '10.00', '0.00') - ,('54079108', '5407.91.08', 'Los demás.', '4', 'M²', '10.00', '0.00') - ,('54079207', '5407.92.07', 'Los demás.', '4', 'M²', '10.00', '0.00') - ,('54079308', '5407.93.08', 'Los demás.', '4', 'M²', '10.00', '0.00') - ,('54079408', '5407.94.08', 'Los demás.', '4', 'M²', '10.00', '0.00') - ,('54081005', '5408.10.05', 'Los demás.', '4', 'M²', '10.00', '0.00') - ,('54082104', '5408.21.04', 'Crudos o blanqueados.', '4', 'M²', '10.00', '0.00') - ,('54082205', '5408.22.05', 'Los demás.', '4', 'M²', '10.00', '0.00') - ,('54082306', '5408.23.06', 'Con hilados de distintos colores.', '4', 'M²', '10.00', '0.00') - ,('54082402', '5408.24.02', 'Estampados.', '4', 'M²', '10.00', '0.00') - ,('54083105', '5408.31.05', 'Los demás.', '4', 'M²', '10.00', '0.00') - ,('54083206', '5408.32.06', 'Los demás.', '4', 'M²', '10.00', '0.00') - ,('54083305', '5408.33.05', 'Los demás.', '4', 'M²', '10.00', '0.00') - ,('54083404', '5408.34.04', 'Los demás.', '4', 'M²', '10.00', '0.00') - ,('55011101', '5501.11.01', 'De aramidas.', '1', 'Kg', '5.00', '0.00') - ,('55011999', '5501.19.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('55012002', '5501.20.02', 'De tereftalato de polietileno color negro, teñido en la masa.', '1', 'Kg', '5.00', '0.00') - ,('55012099', '5501.20.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('55013001', '5501.30.01', 'Acrílicos o modacrílicos.', '1', 'Kg', '5.00', '0.00') - ,('55014001', '5501.40.01', 'De polipropileno.', '1', 'Kg', '5.00', '0.00') - ,('55019099', '5501.90.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('55021001', '5502.10.01', 'De acetato de celulosa.', '1', 'Kg', '5.00', '0.00') - ,('55029001', '5502.90.01', 'Cables de rayón.', '1', 'Kg', '0.00', '0.00') - ,('55029099', '5502.90.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('55031101', '5503.11.01', 'De aramidas.', '1', 'Kg', '5.00', '0.00') - ,('55031999', '5503.19.99', 'Las demás.', '1', 'Kg', '5.00', '0.00') - ,('55032003', '5503.20.03', 'De tereftalato de polietileno color negro, teñidas en la masa.', '1', 'Kg', '0.00', '0.00') - ,('55032099', '5503.20.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('55033001', '5503.30.01', 'Acrílicas o modacrílicas.', '1', 'Kg', '5.00', '0.00') - ,('55034001', '5503.40.01', 'De polipropileno de 3 a 25 deniers.', '1', 'Kg', '5.00', '0.00') - ,('55034099', '5503.40.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('55039001', '5503.90.01', 'De alcohol polivinílico, de longitud inferior o igual a 12 mm.', '1', 'Kg', '0.00', '0.00') - ,('55039099', '5503.90.99', 'Las demás.', '1', 'Kg', '5.00', '0.00') - ,('55041002', '5504.10.02', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('55049099', '5504.90.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('55051001', '5505.10.01', 'De fibras sintéticas.', '1', 'Kg', '5.00', '0.00') - ,('55052001', '5505.20.01', 'De fibras artificiales.', '1', 'Kg', '0.00', '0.00') - ,('55061001', '5506.10.01', 'De nailon o demás poliamidas.', '1', 'Kg', '5.00', '0.00') - ,('55062001', '5506.20.01', 'De poliésteres.', '1', 'Kg', '5.00', '0.00') - ,('55063001', '5506.30.01', 'Acrílicas o modacrílicas.', '1', 'Kg', '5.00', '0.00') - ,('55064001', '5506.40.01', 'De polipropileno.', '1', 'Kg', '5.00', '0.00') - ,('55069099', '5506.90.99', 'Las demás.', '1', 'Kg', '5.00', '0.00') - ,('55070001', '5507.00.01', 'Fibras artificiales discontinuas, cardadas, peinadas o transformadas de otro modo para la hilatura.', '1', 'Kg', '5.00', '0.00') - ,('55081001', '5508.10.01', 'De fibras sintéticas discontinuas.', '1', 'Kg', '10.00', '0.00') - ,('55082001', '5508.20.01', 'De fibras artificiales discontinuas.', '1', 'Kg', '10.00', '0.00') - ,('55091101', '5509.11.01', 'Sencillos.', '1', 'Kg', '10.00', '0.00') - ,('55091201', '5509.12.01', 'Retorcidos o cableados.', '1', 'Kg', '10.00', '0.00') - ,('55092101', '5509.21.01', 'Sencillos.', '1', 'Kg', '10.00', '0.00') - ,('55092201', '5509.22.01', 'Retorcidos o cableados.', '1', 'Kg', '10.00', '0.00') - ,('55093101', '5509.31.01', 'Sencillos.', '1', 'Kg', '10.00', '0.00') - ,('55093201', '5509.32.01', 'Retorcidos o cableados.', '1', 'Kg', '10.00', '0.00') - ,('55094101', '5509.41.01', 'Sencillos.', '1', 'Kg', '10.00', '0.00') - ,('55094201', '5509.42.01', 'Retorcidos o cableados.', '1', 'Kg', '10.00', '0.00') - ,('55095101', '5509.51.01', 'Mezclados exclusiva o principalmente con fibras artificiales discontinuas.', '1', 'Kg', '10.00', '0.00') - ,('55095201', '5509.52.01', 'Mezclados exclusiva o principalmente con lana o pelo fino.', '1', 'Kg', '10.00', '0.00') - ,('55095301', '5509.53.01', 'Mezclados exclusiva o principalmente con algodón.', '1', 'Kg', '10.00', '0.00') - ,('55095999', '5509.59.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('55096101', '5509.61.01', 'Mezclados exclusiva o principalmente con lana o pelo fino.', '1', 'Kg', '10.00', '0.00') - ,('55096201', '5509.62.01', 'Mezclados exclusiva o principalmente con algodón.', '1', 'Kg', '10.00', '0.00') - ,('55096999', '5509.69.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('55099101', '5509.91.01', 'Mezclados exclusiva o principalmente con lana o pelo fino.', '1', 'Kg', '10.00', '0.00') - ,('55099201', '5509.92.01', 'Mezclados exclusiva o principalmente con algodón.', '1', 'Kg', '10.00', '0.00') - ,('55099999', '5509.99.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('55101101', '5510.11.01', 'Sencillos.', '1', 'Kg', '10.00', '0.00') - ,('55101201', '5510.12.01', 'Retorcidos o cableados.', '1', 'Kg', '10.00', '0.00') - ,('55102091', '5510.20.91', 'Los demás hilados mezclados exclusiva o principalmente con lana o pelo fino.', '1', 'Kg', '10.00', '0.00') - ,('55103091', '5510.30.91', 'Los demás hilados mezclados exclusiva o principalmente con algodón.', '1', 'Kg', '10.00', '0.00') - ,('55109091', '5510.90.91', 'Los demás hilados.', '1', 'Kg', '10.00', '0.00') - ,('55111001', '5511.10.01', 'De fibras sintéticas discontinuas con un contenido de estas fibras superior o igual al 85% en peso.', '1', 'Kg', '10.00', '0.00') - ,('55112001', '5511.20.01', 'De fibras sintéticas discontinuas con un contenido de estas fibras inferior al 85% en peso.', '1', 'Kg', '10.00', '0.00') - ,('55113001', '5511.30.01', 'De fibras artificiales discontinuas.', '1', 'Kg', '10.00', '0.00') - ,('55121105', '5512.11.05', 'De peso superior a 200 g/m2.', '4', 'M²', '10.00', '0.00') - ,('55121999', '5512.19.99', 'Los demás de peso superior a 200 g/m2.', '4', 'M²', '35.00', '0.00') - ,('55122101', '5512.21.01', 'Crudos o blanqueados.', '4', 'M²', '10.00', '0.00') - ,('55122999', '5512.29.99', 'Los demás.', '4', 'M²', '10.00', '0.00') - ,('55129101', '5512.91.01', 'Crudos o blanqueados.', '4', 'M²', '35.00', '0.00') - ,('55129999', '5512.99.99', 'Los demás.', '4', 'M²', '10.00', '0.00') - ,('55131104', '5513.11.04', 'De peso superior a 90 g/m2.', '4', 'M²', '10.00', '0.00') - ,('55131201', '5513.12.01', 'Los demás.', '4', 'M²', '10.00', '0.00') - ,('55131391', '5513.13.91', 'Los demás.', '4', 'M²', '35.00', '0.00') - ,('55131991', '5513.19.91', 'Los demás.', '4', 'M²', '10.00', '0.00') - ,('55132104', '5513.21.04', 'De peso superior a 90 g/m2.', '4', 'M²', '10.00', '0.00') - ,('55132391', '5513.23.91', 'Los demás.', '4', 'M²', '10.00', '0.00') - ,('55132991', '5513.29.91', 'Los demás.', '4', 'M²', '35.00', '0.00') - ,('55133101', '5513.31.01', 'Los demás.', '4', 'M²', '10.00', '0.00') - ,('55133991', '5513.39.91', 'Los demás.', '4', 'M²', '35.00', '0.00') - ,('55134101', '5513.41.01', 'Los demás.', '4', 'M²', '10.00', '0.00') - ,('55134991', '5513.49.91', 'Los demás.', '4', 'M²', '35.00', '0.00') - ,('55141101', '5514.11.01', 'De fibras discontinuas de poliéster, de ligamento tafetán.', '4', 'M²', '10.00', '0.00') - ,('55141201', '5514.12.01', 'De fibras discontinuas de poliéster, de ligamento sarga, incluido el cruzado, de curso inferior o igual a 4.', '4', 'M²', '10.00', '0.00') - ,('55141991', '5514.19.91', 'Los demás.', '4', 'M²', '10.00', '0.00') - ,('55142101', '5514.21.01', 'De fibras discontinuas de poliéster, de ligamento tafetán.', '4', 'M²', '10.00', '0.00') - ,('55142201', '5514.22.01', 'De fibras discontinuas de poliéster, de ligamento sarga, incluido el cruzado, de curso inferior o igual a 4.', '4', 'M²', '10.00', '0.00') - ,('55142391', '5514.23.91', 'Los demás tejidos de fibras discontinuas de poliéster.', '4', 'M²', '35.00', '0.00') - ,('55142991', '5514.29.91', 'Los demás tejidos.', '4', 'M²', '35.00', '0.00') - ,('55143005', '5514.30.05', 'Los demás.', '4', 'M²', '10.00', '0.00') - ,('55144101', '5514.41.01', 'De fibras discontinuas de poliéster, de ligamento tafetán.', '4', 'M²', '10.00', '0.00') - ,('55144201', '5514.42.01', 'De fibras discontinuas de poliéster, de ligamento sarga, incluido el cruzado, de curso inferior o igual a 4.', '4', 'M²', '10.00', '0.00') - ,('55144391', '5514.43.91', 'Los demás tejidos de fibras discontinuas de poliéster.', '4', 'M²', '10.00', '0.00') - ,('55144991', '5514.49.91', 'Los demás tejidos.', '4', 'M²', '10.00', '0.00') - ,('55151101', '5515.11.01', 'Los demás.', '4', 'M²', '10.00', '0.00') - ,('55151201', '5515.12.01', 'Los demás.', '4', 'M²', '10.00', '0.00') - ,('55151302', '5515.13.02', 'Los demás.', '4', 'M²', '10.00', '0.00') - ,('55151999', '5515.19.99', 'Los demás.', '4', 'M²', '35.00', '0.00') - ,('55152101', '5515.21.01', 'Los demás.', '4', 'M²', '35.00', '0.00') - ,('55152202', '5515.22.02', 'Mezcladas exclusiva o principalmente con lana o pelo fino.', '4', 'M²', '10.00', '0.00') - ,('55152999', '5515.29.99', 'Los demás.', '4', 'M²', '10.00', '0.00') - ,('55159101', '5515.91.01', 'Los demás.', '4', 'M²', '35.00', '0.00') - ,('55159999', '5515.99.99', 'Los demás.', '4', 'M²', '35.00', '0.00') - ,('55161101', '5516.11.01', 'Crudos o blanqueados.', '4', 'M²', '10.00', '0.00') - ,('55161201', '5516.12.01', 'Teñidos.', '4', 'M²', '10.00', '0.00') - ,('55161301', '5516.13.01', 'Con hilados de distintos colores.', '4', 'M²', '10.00', '0.00') - ,('55161401', '5516.14.01', 'Estampados.', '4', 'M²', '10.00', '0.00') - ,('55162101', '5516.21.01', 'Crudos o blanqueados.', '4', 'M²', '10.00', '0.00') - ,('55162201', '5516.22.01', 'Teñidos.', '4', 'M²', '10.00', '0.00') - ,('55162301', '5516.23.01', 'Con hilados de distintos colores.', '4', 'M²', '10.00', '0.00') - ,('55162401', '5516.24.01', 'Estampados.', '4', 'M²', '10.00', '0.00') - ,('55163102', '5516.31.02', 'Crudos o blanqueados.', '4', 'M²', '10.00', '0.00') - ,('55163202', '5516.32.02', 'Teñidos.', '4', 'M²', '10.00', '0.00') - ,('55163302', '5516.33.02', 'Con hilados de distintos colores.', '4', 'M²', '10.00', '0.00') - ,('55163402', '5516.34.02', 'Estampados.', '4', 'M²', '10.00', '0.00') - ,('55164101', '5516.41.01', 'Crudos o blanqueados.', '4', 'M²', '10.00', '0.00') - ,('55164201', '5516.42.01', 'Teñidos.', '4', 'M²', '10.00', '0.00') - ,('55164301', '5516.43.01', 'Con hilados de distintos colores.', '4', 'M²', '10.00', '0.00') - ,('55164401', '5516.44.01', 'Estampados.', '4', 'M²', '10.00', '0.00') - ,('55169101', '5516.91.01', 'Crudos o blanqueados.', '4', 'M²', '10.00', '0.00') - ,('55169201', '5516.92.01', 'Teñidos.', '4', 'M²', '10.00', '0.00') - ,('55169301', '5516.93.01', 'Con hilados de distintos colores.', '4', 'M²', '10.00', '0.00') - ,('55169401', '5516.94.01', 'Estampados.', '4', 'M²', '10.00', '0.00') - ,('56012102', '5601.21.02', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('56012201', '5601.22.01', 'Guata.', '1', 'Kg', '0.00', '0.00') - ,('56012299', '5601.22.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('56012999', '5601.29.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('56013002', '5601.30.02', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('56021002', '5602.10.02', 'Fieltro punzonado y productos obtenidos mediante costura por cadeneta.', '1', 'Kg', '10.00', '0.00') - ,('56022103', '5602.21.03', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('56022991', '5602.29.91', 'De las demás materias textiles.', '1', 'Kg', '10.00', '0.00') - ,('56029099', '5602.90.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('56031101', '5603.11.01', 'De peso inferior o igual a 25 g/m².', '1', 'Kg', '10.00', '0.00') - ,('56031202', '5603.12.02', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('56031302', '5603.13.02', 'Las demás.', '1', 'Kg', '35.00', '0.00') - ,('56031401', '5603.14.01', 'De peso superior a 150 g/m².', '1', 'Kg', '10.00', '0.00') - ,('56039101', '5603.91.01', 'De peso inferior o igual a 25 g/m².', '1', 'Kg', '10.00', '0.00') - ,('56039201', '5603.92.01', 'De peso superior a 25 g/m² pero inferior o igual a 70 g/m².', '1', 'Kg', '10.00', '0.00') - ,('56039301', '5603.93.01', 'De peso superior a 70 g/m² pero inferior o igual a 150 g/m².', '1', 'Kg', '10.00', '0.00') - ,('56039401', '5603.94.01', 'De peso superior a 150 g/m².', '1', 'Kg', '10.00', '0.00') - ,('56041001', '5604.10.01', 'Hilos y cuerdas de caucho revestidos de textiles.', '1', 'Kg', '10.00', '0.00') - ,('56049001', '5604.90.01', 'Impregnados o recubiertos de caucho vulcanizado.', '1', 'Kg', '10.00', '0.00') - ,('56049002', '5604.90.02', 'De seda o de desperdicios de seda, acondicionados para la venta al por menor, pelo de Mesina (crin de Florencia); imitaciones de catgut preparados con hilados de seda.', '1', 'Kg', '10.00', '0.00') - ,('56049004', '5604.90.04', 'Imitaciones de catgut, de materia textil sintética y artificial, continua, con diámetro superior o igual a 0.05 mm, sin exceder de 0.70 mm.', '1', 'Kg', '10.00', '0.00') - ,('56049005', '5604.90.05', 'De materia textil sintética y artificial, excepto lo comprendido en la fracción arancelaria 5604.90.01.', '1', 'Kg', '10.00', '0.00') - ,('56049006', '5604.90.06', 'De lana, de pelos (finos u ordinarios) o de crin incluso acondicionados para la venta al por menor.', '1', 'Kg', '10.00', '0.00') - ,('56049007', '5604.90.07', 'De lino o de ramio.', '1', 'Kg', '10.00', '0.00') - ,('56049008', '5604.90.08', 'De algodón, sin acondicionar para la venta al por menor.', '1', 'Kg', '10.00', '0.00') - ,('56049009', '5604.90.09', 'De algodón, acondicionados para la venta al por menor.', '1', 'Kg', '10.00', '0.00') - ,('56049010', '5604.90.10', 'Hilados de alta tenacidad de poliésteres, de nailon o demás poliamidas o de rayón viscosa, impregnados o recubiertos, reconocibles para naves aéreas.', '1', 'Kg', '0.00', '0.00') - ,('56049011', '5604.90.11', 'Hilados de alta tenacidad impregnados o recubiertos, de fibras aramídicas.', '1', 'Kg', '0.00', '0.00') - ,('56049012', '5604.90.12', 'Hilados de alta tenacidad impregnados o recubiertos, de poliamidas o superpoliamidas de 44.44 decitex (40 deniers) y 34 filamentos.', '1', 'Kg', '0.00', '0.00') - ,('56049013', '5604.90.13', 'Hilados de alta tenacidad impregnados o recubiertos, de rayón, de 1,333.33 decitex (1,200 deniers).', '1', 'Kg', '10.00', '0.00') - ,('56049014', '5604.90.14', 'Hilados de alta tenacidad de poliésteres, de nailon o demás poliamidas o de rayón viscosa, impregnados o recubiertos, excepto lo comprendido en las fracciones arancelarias 5604.90.10, 5604.90.11, 5604.90.12 y 5604.90.13.', '1', 'Kg', '10.00', '0.00') - ,('56049099', '5604.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('56050001', '5605.00.01', 'Hilados metálicos e hilados metalizados, incluso entorchados, constituidos por hilados textiles, tiras o formas similares de las partidas 54.04 o 54.05, combinados con metal en forma de hilos, tiras o polvo, o revestidos de metal.', '1', 'Kg', '10.00', '0.00') - ,('56060003', '5606.00.03', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('56072101', '5607.21.01', 'Cordeles para atar o engavillar.', '1', 'Kg', '10.00', '0.00') - ,('56072999', '5607.29.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('56074101', '5607.41.01', 'Cordeles para atar o engavillar.', '1', 'Kg', '10.00', '0.00') - ,('56074999', '5607.49.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('56075091', '5607.50.91', 'De las demás fibras sintéticas.', '1', 'Kg', '10.00', '0.00') - ,('56079002', '5607.90.02', 'De yute o demás fibras textiles del liber de la partida 53.03.', '1', 'Kg', '0.00', '0.00') - ,('56079099', '5607.90.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('56081102', '5608.11.02', 'Las demás.', '1', 'Kg', '10.00', '0.00') - ,('56081999', '5608.19.99', 'Las demás.', '1', 'Kg', '10.00', '0.00') - ,('56089099', '5608.90.99', 'Las demás.', '1', 'Kg', '10.00', '0.00') - ,('56090002', '5609.00.02', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('57011001', '5701.10.01', 'De lana o pelo fino.', '4', 'M²', '15.00', '0.00') - ,('57019091', '5701.90.91', 'De las demás materias textiles.', '4', 'M²', '15.00', '0.00') - ,('57021001', '5702.10.01', 'Alfombras llamadas “Kelim” o “Kilim”, “Schumacks” o “Soumak”, “Karamanie” y alfombras similares tejidas a mano.', '4', 'M²', '15.00', '0.00') - ,('57022001', '5702.20.01', 'Revestimientos para el suelo de fibras de coco.', '4', 'M²', '15.00', '0.00') - ,('57023101', '5702.31.01', 'De lana o pelo fino.', '4', 'M²', '15.00', '0.00') - ,('57023201', '5702.32.01', 'De materia textil sintética o artificial.', '4', 'M²', '15.00', '0.00') - ,('57023991', '5702.39.91', 'De las demás materias textiles.', '4', 'M²', '15.00', '0.00') - ,('57024101', '5702.41.01', 'De lana o pelo fino.', '4', 'M²', '15.00', '0.00') - ,('57024201', '5702.42.01', 'De materia textil sintética o artificial.', '4', 'M²', '15.00', '0.00') - ,('57024991', '5702.49.91', 'De las demás materias textiles.', '4', 'M²', '15.00', '0.00') - ,('57025091', '5702.50.91', 'Los demás.', '4', 'M²', '15.00', '0.00') - ,('57029101', '5702.91.01', 'De lana o pelo fino.', '4', 'M²', '15.00', '0.00') - ,('57029201', '5702.92.01', 'De materia textil sintética o artificial.', '4', 'M²', '15.00', '0.00') - ,('57029991', '5702.99.91', 'De las demás materias textiles.', '4', 'M²', '15.00', '0.00') - ,('57031001', '5703.10.01', 'De lana o pelo fino.', '4', 'M²', '15.00', '0.00') - ,('57032101', '5703.21.01', 'Los demás.', '4', 'M²', '15.00', '0.00') - ,('57032999', '5703.29.99', 'Los demás.', '4', 'M²', '15.00', '0.00') - ,('57033101', '5703.31.01', 'Los demás.', '4', 'M²', '15.00', '0.00') - ,('57033999', '5703.39.99', 'Los demás.', '4', 'M²', '15.00', '0.00') - ,('57039091', '5703.90.91', 'De las demás materias textiles.', '4', 'M²', '15.00', '0.00') - ,('57041001', '5704.10.01', 'De superficie inferior o igual a 0.3 m².', '4', 'M²', '15.00', '0.00') - ,('57042001', '5704.20.01', 'De superficie superior a 0.3 m2 pero inferior o igual a 1 m².', '4', 'M²', '15.00', '0.00') - ,('57049099', '5704.90.99', 'Los demás.', '4', 'M²', '15.00', '0.00') - ,('57050091', '5705.00.91', 'Los demás.', '4', 'M²', '15.00', '0.00') - ,('58011001', '5801.10.01', 'De lana o pelo fino.', '4', 'M²', '10.00', '0.00') - ,('58012101', '5801.21.01', 'Terciopelo y felpa por trama, sin cortar.', '4', 'M²', '10.00', '0.00') - ,('58012201', '5801.22.01', 'Terciopelo y felpa por trama, cortados, rayados (pana rayada, "corduroy").', '4', 'M²', '10.00', '0.00') - ,('58012391', '5801.23.91', 'Los demás terciopelos y felpas por trama.', '4', 'M²', '10.00', '0.00') - ,('58012601', '5801.26.01', 'Tejidos de chenilla.', '4', 'M²', '10.00', '0.00') - ,('58012701', '5801.27.01', 'Terciopelo y felpa por urdimbre.', '4', 'M²', '10.00', '0.00') - ,('58013101', '5801.31.01', 'Terciopelo y felpa por trama, sin cortar.', '4', 'M²', '10.00', '0.00') - ,('58013201', '5801.32.01', 'Terciopelo y felpa por trama, cortados, rayados (pana rayada, "corduroy").', '4', 'M²', '10.00', '0.00') - ,('58013391', '5801.33.91', 'Los demás terciopelos y felpas por trama.', '4', 'M²', '10.00', '0.00') - ,('58013601', '5801.36.01', 'Tejidos de chenilla.', '4', 'M²', '10.00', '0.00') - ,('58013701', '5801.37.01', 'Terciopelo y felpa por urdimbre.', '4', 'M²', '10.00', '0.00') - ,('58019091', '5801.90.91', 'De las demás materias textiles.', '4', 'M²', '10.00', '0.00') - ,('58021001', '5802.10.01', 'Crudos.', '4', 'M²', '10.00', '0.00') - ,('58021099', '5802.10.99', 'Los demás.', '4', 'M²', '20.00', '0.00') - ,('58022001', '5802.20.01', 'Tejidos con bucles del tipo toalla, de las demás materias textiles.', '4', 'M²', '10.00', '0.00') - ,('58023001', '5802.30.01', 'Superficies textiles con mechón editado.', '4', 'M²', '10.00', '0.00') - ,('58030005', '5803.00.05', 'Los demás.', '4', 'M²', '10.00', '0.00') - ,('58041001', '5804.10.01', 'Tul, tul-bobinot y tejidos de mallas anudadas.', '1', 'Kg', '10.00', '0.00') - ,('58042101', '5804.21.01', 'De fibras sintéticas o artificiales.', '1', 'Kg', '10.00', '0.00') - ,('58042991', '5804.29.91', 'De las demás materias textiles.', '1', 'Kg', '10.00', '0.00') - ,('58043001', '5804.30.01', 'Encajes hechos a mano.', '1', 'Kg', '10.00', '0.00') - ,('58050001', '5805.00.01', 'Tapicería tejida a mano (gobelinos, Flandes, Aubusson, Beauvais y similares) y tapicería de aguja (por ejemplo: de “petit point”, de punto de cruz), incluso confeccionadas.', '4', 'M²', '10.00', '0.00') - ,('58061001', '5806.10.01', 'De seda.', '1', 'Kg', '0.00', '0.00') - ,('58061099', '5806.10.99', 'Las demás.', '1', 'Kg', '10.00', '0.00') - ,('58062001', '5806.20.01', 'De seda.', '1', 'Kg', '0.00', '0.00') - ,('58062099', '5806.20.99', 'Las demás.', '1', 'Kg', '10.00', '0.00') - ,('58063101', '5806.31.01', 'De algodón.', '1', 'Kg', '10.00', '0.00') - ,('58063201', '5806.32.01', 'De fibras sintéticas o artificiales.', '1', 'Kg', '10.00', '0.00') - ,('58063901', '5806.39.01', 'De seda.', '1', 'Kg', '0.00', '0.00') - ,('58063999', '5806.39.99', 'Las demás.', '1', 'Kg', '10.00', '0.00') - ,('58064001', '5806.40.01', 'De seda.', '1', 'Kg', '0.00', '0.00') - ,('58064099', '5806.40.99', 'Las demás.', '1', 'Kg', '10.00', '0.00') - ,('58071001', '5807.10.01', 'Tejidos.', '1', 'Kg', '10.00', '0.00') - ,('58079099', '5807.90.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('58081001', '5808.10.01', 'Trenzas en pieza.', '1', 'Kg', '10.00', '0.00') - ,('58089099', '5808.90.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('58090001', '5809.00.01', 'Tejidos de hilos de metal y tejidos de hilados metálicos o de hilados textiles metalizados de la partida 56.05, de los tipos utilizados en prendas de vestir, tapicería o usos similares, no expresados ni comprendidos en otra parte.', '4', 'M²', '10.00', '0.00') - ,('58101001', '5810.10.01', 'Bordados químicos o aéreos y bordados con fondo recortado.', '1', 'Kg', '10.00', '0.00') - ,('58109101', '5810.91.01', 'De algodón.', '1', 'Kg', '10.00', '0.00') - ,('58109201', '5810.92.01', 'De fibras sintéticas o artificiales.', '1', 'Kg', '10.00', '0.00') - ,('58109991', '5810.99.91', 'De las demás materias textiles.', '1', 'Kg', '10.00', '0.00') - ,('58110001', '5811.00.01', 'Productos textiles acolchados en pieza, constituidos por una o varias capas de materia textil combinadas con una materia de relleno y mantenidas mediante puntadas u otro modo de sujeción, excepto los bordados de la partida 58.10.', '4', 'M²', '10.00', '0.00') - ,('59011001', '5901.10.01', 'Telas recubiertas de cola o materias amiláceas, de los tipos utilizados para encuadernación, cartonaje, estuchería o usos similares.', '4', 'M²', '10.00', '0.00') - ,('59019099', '5901.90.99', 'Los demás.', '4', 'M²', '10.00', '0.00') - ,('59021001', '5902.10.01', 'De nailon o demás poliamidas.', '1', 'Kg', '10.00', '0.00') - ,('59022001', '5902.20.01', 'De poliésteres.', '1', 'Kg', '10.00', '0.00') - ,('59029099', '5902.90.99', 'Las demás.', '1', 'Kg', '10.00', '0.00') - ,('59031002', '5903.10.02', 'Los demás.', '4', 'M²', '10.00', '0.00') - ,('59032002', '5903.20.02', 'Los demás.', '4', 'M²', '10.00', '0.00') - ,('59039099', '5903.90.99', 'Las demás.', '4', 'M²', '10.00', '0.00') - ,('59041001', '5904.10.01', 'Linóleo.', '4', 'M²', '10.00', '0.00') - ,('59049099', '5904.90.99', 'Los demás.', '4', 'M²', '10.00', '0.00') - ,('59050001', '5905.00.01', 'Revestimientos de materia textil para paredes.', '4', 'M²', '10.00', '0.00') - ,('59061001', '5906.10.01', 'Cintas adhesivas de anchura inferior o igual a 20 cm.', '3', 'M', '10.00', '0.00') - ,('59069102', '5906.91.02', 'De punto.', '4', 'M²', '10.00', '0.00') - ,('59069999', '5906.99.99', 'Los demás.', '4', 'M²', '10.00', '0.00') - ,('59070001', '5907.00.01', 'Tejidos impregnados con materias incombustibles.', '4', 'M²', '10.00', '0.00') - ,('59070005', '5907.00.05', 'Telas impregnadas o bañadas, con tundiznos cuya longitud sea hasta 2 mm.', '4', 'M²', '10.00', '0.00') - ,('59070006', '5907.00.06', 'De fibras sintéticas o artificiales, del tipo de los comprendidos en las fracciones arancelarias 5907.00.01 y 5907.00.05.', '4', 'M²', '10.00', '0.00') - ,('59070099', '5907.00.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('59080003', '5908.00.03', 'Tejidos tubulares.', '1', 'Kg', '0.00', '0.00') - ,('59080099', '5908.00.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('59090001', '5909.00.01', 'Mangueras para bombas y tubos similares, de materia textil, incluso con armadura o accesorios de otras materias.', '1', 'Kg', '10.00', '0.00') - ,('59100001', '5910.00.01', 'Correas transportadoras o de transmisión, de materia textil, incluso impregnadas, recubiertas, revestidas o estratificadas con plástico o reforzadas con metal u otra materia.', '1', 'Kg', '10.00', '0.00') - ,('59111001', '5911.10.01', 'Cintas de terciopelo impregnadas de caucho para forrar enjulios.', '3', 'M', '10.00', '0.00') - ,('59111099', '5911.10.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('59112001', '5911.20.01', 'Gasas y telas para cerner, incluso confeccionadas.', '4', 'M²', '0.00', '0.00') - ,('59113101', '5911.31.01', 'De peso inferior a 650 g/m².', '1', 'Kg', '10.00', '0.00') - ,('59113201', '5911.32.01', 'De peso superior o igual a 650 g/m².', '1', 'Kg', '10.00', '0.00') - ,('59114001', '5911.40.01', 'Telas filtrantes, tejidos gruesos y capachos de los tipos utilizados en las prensas de aceite o para usos técnicos análogos, incluidos los de cabello.', '1', 'Kg', '10.00', '0.00') - ,('59119001', '5911.90.01', 'Artículos textiles para usos técnicos u otras partes o piezas de máquinas o aparatos.', '1', 'Kg', '0.00', '0.00') - ,('59119099', '5911.90.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('60011003', '6001.10.03', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('60012101', '6001.21.01', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('60012201', '6001.22.01', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('60012901', '6001.29.01', 'De seda.', '1', 'Kg', '0.00', '0.00') - ,('60012999', '6001.29.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('60019101', '6001.91.01', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('60019201', '6001.92.01', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('60019991', '6001.99.91', 'De las demás materias textiles.', '1', 'Kg', '10.00', '0.00') - ,('60024001', '6002.40.01', 'De seda.', '1', 'Kg', '0.00', '0.00') - ,('60024099', '6002.40.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('60029001', '6002.90.01', 'De seda.', '1', 'Kg', '0.00', '0.00') - ,('60029099', '6002.90.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('60031001', '6003.10.01', 'De lana o pelo fino.', '1', 'Kg', '10.00', '0.00') - ,('60032001', '6003.20.01', 'De algodón.', '1', 'Kg', '10.00', '0.00') - ,('60033001', '6003.30.01', 'De fibras sintéticas.', '1', 'Kg', '10.00', '0.00') - ,('60034001', '6003.40.01', 'De fibras artificiales.', '1', 'Kg', '10.00', '0.00') - ,('60039001', '6003.90.01', 'De seda.', '1', 'Kg', '0.00', '0.00') - ,('60039099', '6003.90.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('60041001', '6004.10.01', 'De seda.', '1', 'Kg', '0.00', '0.00') - ,('60041099', '6004.10.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('60049001', '6004.90.01', 'De seda.', '1', 'Kg', '0.00', '0.00') - ,('60049099', '6004.90.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('60052101', '6005.21.01', 'Crudos o blanqueados.', '1', 'Kg', '10.00', '0.00') - ,('60052201', '6005.22.01', 'Teñidos.', '1', 'Kg', '10.00', '0.00') - ,('60052301', '6005.23.01', 'Con hilados de distintos colores.', '1', 'Kg', '35.00', '0.00') - ,('60052401', '6005.24.01', 'Estampados.', '1', 'Kg', '10.00', '0.00') - ,('60053501', '6005.35.01', 'Tejidos mencionados en la Nota 1 de subpartida de este Capítulo.', '1', 'Kg', '10.00', '0.00') - ,('60053691', '6005.36.91', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('60053791', '6005.37.91', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('60053891', '6005.38.91', 'Los demás, con hilados de distintos colores.', '1', 'Kg', '10.00', '0.00') - ,('60053991', '6005.39.91', 'Los demás, estampados.', '1', 'Kg', '10.00', '0.00') - ,('60054101', '6005.41.01', 'Crudos o blanqueados.', '1', 'Kg', '10.00', '0.00') - ,('60054201', '6005.42.01', 'Teñidos.', '1', 'Kg', '10.00', '0.00') - ,('60054301', '6005.43.01', 'Con hilados de distintos colores.', '1', 'Kg', '10.00', '0.00') - ,('60054401', '6005.44.01', 'Estampados.', '1', 'Kg', '10.00', '0.00') - ,('60059099', '6005.90.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('60061001', '6006.10.01', 'De lana o pelo fino.', '1', 'Kg', '10.00', '0.00') - ,('60062102', '6006.21.02', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('60062202', '6006.22.02', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('60062302', '6006.23.02', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('60062402', '6006.24.02', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('60063103', '6006.31.03', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('60063203', '6006.32.03', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('60063303', '6006.33.03', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('60063403', '6006.34.03', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('60064101', '6006.41.01', 'Crudos o blanqueados.', '1', 'Kg', '10.00', '0.00') - ,('60064201', '6006.42.01', 'Teñidos.', '1', 'Kg', '10.00', '0.00') - ,('60064301', '6006.43.01', 'Con hilados de distintos colores.', '1', 'Kg', '10.00', '0.00') - ,('60064401', '6006.44.01', 'Estampados.', '1', 'Kg', '10.00', '0.00') - ,('60069099', '6006.90.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('61012003', '6101.20.03', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('61013001', '6101.30.01', 'Con un contenido de lana o pelo fino superior o igual a 23% en peso.', '6', 'Pza', '20.00', '0.00') - ,('61013099', '6101.30.99', 'Los demás para niños.', '6', 'Pza', '25.00', '0.00') - ,('61019091', '6101.90.91', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('61021001', '6102.10.01', 'De lana o pelo fino.', '6', 'Pza', '20.00', '0.00') - ,('61022003', '6102.20.03', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('61023001', '6102.30.01', 'Con un contenido de lana o pelo fino superior o igual a 23% en peso.', '6', 'Pza', '20.00', '0.00') - ,('61023099', '6102.30.99', 'Los demás.', '6', 'Pza', '25.00', '0.00') - ,('61029091', '6102.90.91', 'De las demás materias textiles.', '6', 'Pza', '35.00', '0.00') - ,('61031005', '6103.10.05', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('61032201', '6103.22.01', 'De algodón.', '6', 'Pza', '20.00', '0.00') - ,('61032301', '6103.23.01', 'De fibras sintéticas.', '6', 'Pza', '20.00', '0.00') - ,('61032991', '6103.29.91', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('61033101', '6103.31.01', 'De lana o pelo fino.', '6', 'Pza', '35.00', '0.00') - ,('61033201', '6103.32.01', 'De algodón.', '6', 'Pza', '20.00', '0.00') - ,('61033302', '6103.33.02', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('61033991', '6103.39.91', 'Los demás.', '6', 'Pza', '35.00', '0.00') - ,('61034101', '6103.41.01', 'De lana o pelo fino.', '6', 'Pza', '20.00', '0.00') - ,('61034203', '6103.42.03', 'Los demás para niños.', '6', 'Pza', '20.00', '0.00') - ,('61034301', '6103.43.01', 'Con un contenido de lana o pelo fino superior o igual a 23% en peso.', '6', 'Pza', '20.00', '0.00') - ,('61034399', '6103.43.99', 'Los demás para niños.', '6', 'Pza', '25.00', '0.00') - ,('61034991', '6103.49.91', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('61041302', '6104.13.02', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('61041991', '6104.19.91', 'Los demás.', '6', 'Pza', '35.00', '0.00') - ,('61042201', '6104.22.01', 'De algodón.', '6', 'Pza', '20.00', '0.00') - ,('61042301', '6104.23.01', 'De fibras sintéticas.', '6', 'Pza', '20.00', '0.00') - ,('61042991', '6104.29.91', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('61043101', '6104.31.01', 'De lana o pelo fino.', '6', 'Pza', '20.00', '0.00') - ,('61043201', '6104.32.01', 'De algodón.', '6', 'Pza', '35.00', '0.00') - ,('61043302', '6104.33.02', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('61043991', '6104.39.91', 'Las demás.', '6', 'Pza', '20.00', '0.00') - ,('61044101', '6104.41.01', 'De lana o pelo fino.', '6', 'Pza', '35.00', '0.00') - ,('61044203', '6104.42.03', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('61044302', '6104.43.02', 'Los demás para niñas.', '6', 'Pza', '20.00', '0.00') - ,('61044402', '6104.44.02', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('61044991', '6104.49.91', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('61045101', '6104.51.01', 'De lana o pelo fino.', '6', 'Pza', '35.00', '0.00') - ,('61045201', '6104.52.01', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('61045302', '6104.53.02', 'Los demás para niñas.', '6', 'Pza', '20.00', '0.00') - ,('61045991', '6104.59.91', 'Las demás.', '6', 'Pza', '20.00', '0.00') - ,('61046101', '6104.61.01', 'De lana o pelo fino.', '6', 'Pza', '20.00', '0.00') - ,('61046203', '6104.62.03', 'Los demás para niñas.', '6', 'Pza', '20.00', '0.00') - ,('61046301', '6104.63.01', 'Con un contenido de lana o pelo fino superior o igual a 23% en peso.', '6', 'Pza', '20.00', '0.00') - ,('61046399', '6104.63.99', 'Los demás para mujeres.', '6', 'Pza', '25.00', '0.00') - ,('61046991', '6104.69.91', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('61051002', '6105.10.02', 'Las demás.', '6', 'Pza', '25.00', '0.00') - ,('61052003', '6105.20.03', 'Las demás.', '6', 'Pza', '25.00', '0.00') - ,('61059091', '6105.90.91', 'Las demás.', '6', 'Pza', '20.00', '0.00') - ,('61061002', '6106.10.02', 'Las demás para niñas.', '6', 'Pza', '25.00', '0.00') - ,('61062001', '6106.20.01', 'Con un contenido de lana o pelo fino superior o igual a 23% en peso.', '6', 'Pza', '35.00', '0.00') - ,('61062099', '6106.20.99', 'Las demás para niñas.', '6', 'Pza', '25.00', '0.00') - ,('61069091', '6106.90.91', 'Las demás.', '6', 'Pza', '35.00', '0.00') - ,('61071103', '6107.11.03', 'Los demás.', '6', 'Pza', '25.00', '0.00') - ,('61071203', '6107.12.03', 'Los demás.', '6', 'Pza', '25.00', '0.00') - ,('61071991', '6107.19.91', 'De las demás materias textiles.', '6', 'Pza', '20.00', '0.00') - ,('61072101', '6107.21.01', 'Los demás.', '6', 'Pza', '25.00', '0.00') - ,('61072201', '6107.22.01', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('61072991', '6107.29.91', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('61079101', '6107.91.01', 'De algodón.', '6', 'Pza', '20.00', '0.00') - ,('61079991', '6107.99.91', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('61081101', '6108.11.01', 'De fibras sintéticas o artificiales.', '6', 'Pza', '20.00', '0.00') - ,('61081991', '6108.19.91', 'De las demás materias textiles.', '6', 'Pza', '20.00', '0.00') - ,('61082103', '6108.21.03', 'Los demás.', '6', 'Pza', '25.00', '0.00') - ,('61082203', '6108.22.03', 'Los demás.', '6', 'Pza', '25.00', '0.00') - ,('61082991', '6108.29.91', 'De las demás materias textiles.', '6', 'Pza', '20.00', '0.00') - ,('61083103', '6108.31.03', 'Los demás.', '6', 'Pza', '25.00', '0.00') - ,('61083203', '6108.32.03', 'Los demás.', '6', 'Pza', '25.00', '0.00') - ,('61083991', '6108.39.91', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('61089102', '6108.91.02', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('61089202', '6108.92.02', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('61089991', '6108.99.91', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('61091003', '6109.10.03', 'Las demás.', '6', 'Pza', '25.00', '0.00') - ,('61099004', '6109.90.04', 'Las demás de fibras sintéticas o artificiales.', '6', 'Pza', '25.00', '0.00') - ,('61099099', '6109.90.99', 'Las demás para niños y niñas.', '6', 'Pza', '20.00', '0.00') - ,('61101103', '6110.11.03', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('61101201', '6110.12.01', 'Los demás.', '6', 'Pza', '35.00', '0.00') - ,('61101999', '6110.19.99', 'Los demás.', '6', 'Pza', '35.00', '0.00') - ,('61102005', '6110.20.05', 'Los demás.', '6', 'Pza', '25.00', '0.00') - ,('61103001', '6110.30.01', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('61103099', '6110.30.99', 'Los demás.', '6', 'Pza', '25.00', '0.00') - ,('61109091', '6110.90.91', 'Las demás.', '6', 'Pza', '35.00', '0.00') - ,('61112012', '6111.20.12', 'Los demás.', '6', 'Pza', '25.00', '0.00') - ,('61113007', '6111.30.07', 'Los demás.', '6', 'Pza', '25.00', '0.00') - ,('61119091', '6111.90.91', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('61121101', '6112.11.01', 'De algodón.', '6', 'Pza', '20.00', '0.00') - ,('61121201', '6112.12.01', 'De fibras sintéticas.', '6', 'Pza', '25.00', '0.00') - ,('61121991', '6112.19.91', 'Los demás.', '6', 'Pza', '35.00', '0.00') - ,('61122002', '6112.20.02', 'Monos (overoles) y conjuntos de esquí.', '6', 'Pza', '20.00', '0.00') - ,('61123101', '6112.31.01', 'De fibras sintéticas.', '6', 'Pza', '20.00', '0.00') - ,('61123991', '6112.39.91', 'De las demás materias textiles.', '6', 'Pza', '20.00', '0.00') - ,('61124101', '6112.41.01', 'Los demás.', '6', 'Pza', '25.00', '0.00') - ,('61124991', '6112.49.91', 'De las demás materias textiles.', '6', 'Pza', '20.00', '0.00') - ,('61130002', '6113.00.02', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('61142001', '6114.20.01', 'De algodón.', '6', 'Pza', '20.00', '0.00') - ,('61143002', '6114.30.02', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('61149091', '6114.90.91', 'Los demás.', '6', 'Pza', '35.00', '0.00') - ,('61151001', '6115.10.01', 'Calzas, panty-medias, leotardos y medias, de compresión progresiva (por ejemplo, medias para várices).', '6', 'Pza', '20.00', '0.00') - ,('61152101', '6115.21.01', 'De fibras sintéticas, de título inferior a 67 decitex por hilo sencillo.', '6', 'Pza', '20.00', '0.00') - ,('61152201', '6115.22.01', 'De fibras sintéticas, de título superior o igual a 67 decitex por hilo sencillo.', '6', 'Pza', '20.00', '0.00') - ,('61152991', '6115.29.91', 'De las demás materias textiles.', '6', 'Pza', '20.00', '0.00') - ,('61153091', '6115.30.91', 'Las demás medias de mujer, de título inferior a 67 decitex por hilo sencillo.', '9', 'Par', '35.00', '0.00') - ,('61159401', '6115.94.01', 'De lana o pelo fino.', '9', 'Par', '20.00', '0.00') - ,('61159501', '6115.95.01', 'Los demás.', '9', 'Par', '25.00', '0.00') - ,('61159601', '6115.96.01', 'Los demás.', '9', 'Par', '25.00', '0.00') - ,('61159991', '6115.99.91', 'De las demás materias textiles.', '9', 'Par', '20.00', '0.00') - ,('61161002', '6116.10.02', 'Los demás.', '9', 'Par', '20.00', '0.00') - ,('61169101', '6116.91.01', 'De lana o pelo fino.', '9', 'Par', '20.00', '0.00') - ,('61169201', '6116.92.01', 'De algodón.', '9', 'Par', '20.00', '0.00') - ,('61169301', '6116.93.01', 'De fibras sintéticas.', '9', 'Par', '20.00', '0.00') - ,('61169991', '6116.99.91', 'De las demás materias textiles.', '9', 'Par', '20.00', '0.00') - ,('61171002', '6117.10.02', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('61178002', '6117.80.02', 'Cintas, bandas o ligas, de sujeción para el cabello y artículos similares.', '1', 'Kg', '20.00', '0.00') - ,('61178099', '6117.80.99', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('61179001', '6117.90.01', 'Las demás.', '6', 'Pza', '20.00', '0.00') - ,('62012001', '6201.20.01', 'Abrigos, impermeables, chaquetones, capas y artículos similares.', '6', 'Pza', '25.00', '0.00') - ,('62012099', '6201.20.99', 'Los demás.', '6', 'Pza', '35.00', '0.00') - ,('62013001', '6201.30.01', 'Con un contenido de plumón y plumas de ave acuática superior o igual al 15%, en peso, siempre que el contenido de plumón sea superior o igual al 35%, en peso; con un contenido del plumaje superior o igual al 10%, en peso.', '6', 'Pza', '35.00', '0.00') - ,('62013099', '6201.30.99', 'Para niños.', '6', 'Pza', '25.00', '0.00') - ,('62014001', '6201.40.01', 'Con un contenido de plumón y plumas de ave acuática superior o igual al 15%, en peso, siempre que el contenido de plumón sea superior o igual al 35%, en peso; con un contenido del plumaje superior o igual al 10%, en peso.', '6', 'Pza', '20.00', '0.00') - ,('62014002', '6201.40.02', 'Con un contenido de lana o pelo fino superior o igual a 36% en peso, excepto lo comprendido en la fracción arancelaria 6201.40.01.', '6', 'Pza', '20.00', '0.00') - ,('62014099', '6201.40.99', 'Para niños.', '6', 'Pza', '25.00', '0.00') - ,('62019091', '6201.90.91', 'De las demás materias textiles.', '6', 'Pza', '35.00', '0.00') - ,('62022001', '6202.20.01', 'Abrigos, impermeables, chaquetones, capas y artículos similares.', '6', 'Pza', '25.00', '0.00') - ,('62022099', '6202.20.99', 'Los demás.', '6', 'Pza', '35.00', '0.00') - ,('62023001', '6202.30.01', 'Con un contenido de plumón y plumas de ave acuática superior o igual al 15%, en peso, siempre que el contenido de plumón sea superior o igual al 35%, en peso; con un contenido del plumaje superior o igual al 10%, en peso.', '6', 'Pza', '35.00', '0.00') - ,('62023099', '6202.30.99', 'Para niñas.', '6', 'Pza', '25.00', '0.00') - ,('62024001', '6202.40.01', 'Con un contenido de plumón y plumas de ave acuática superior o igual al 15%, en peso, siempre que el contenido de plumón sea superior o igual al 35%, en peso; con un contenido del plumaje superior o igual al 10%, en peso.', '6', 'Pza', '35.00', '0.00') - ,('62024002', '6202.40.02', 'Con un contenido de lana o pelo fino superior o igual a 36% en peso, excepto lo comprendido en la fracción arancelaria 6202.40.01.', '6', 'Pza', '35.00', '0.00') - ,('62024099', '6202.40.99', 'Para niñas.', '6', 'Pza', '25.00', '0.00') - ,('62029091', '6202.90.91', 'De las demás materias textiles.', '6', 'Pza', '35.00', '0.00') - ,('62031101', '6203.11.01', 'De lana o pelo fino.', '6', 'Pza', '25.00', '0.00') - ,('62031201', '6203.12.01', 'De fibras sintéticas.', '6', 'Pza', '25.00', '0.00') - ,('62031991', '6203.19.91', 'Las demás.', '6', 'Pza', '35.00', '0.00') - ,('62032201', '6203.22.01', 'De algodón.', '6', 'Pza', '20.00', '0.00') - ,('62032301', '6203.23.01', 'De fibras sintéticas.', '6', 'Pza', '20.00', '0.00') - ,('62032991', '6203.29.91', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('62033101', '6203.31.01', 'De lana o pelo fino.', '6', 'Pza', '25.00', '0.00') - ,('62033203', '6203.32.03', 'Los demás.', '6', 'Pza', '25.00', '0.00') - ,('62033301', '6203.33.01', 'Con un contenido de lana o pelo fino superior o igual a 36% en peso.', '6', 'Pza', '20.00', '0.00') - ,('62033399', '6203.33.99', 'Para niños.', '6', 'Pza', '25.00', '0.00') - ,('62033991', '6203.39.91', 'Los demás.', '6', 'Pza', '35.00', '0.00') - ,('62034101', '6203.41.01', 'De lana o pelo fino.', '6', 'Pza', '25.00', '0.00') - ,('62034201', '6203.42.01', 'Con un contenido de plumón y plumas de ave acuática superior o igual al 15%, en peso, siempre que el contenido de plumón sea superior o igual al 35%, en peso; con un contenido del plumaje superior o igual al 10%, en peso.', '6', 'Pza', '35.00', '0.00') - ,('62034202', '6203.42.02', 'Pantalones con peto y tirantes.', '6', 'Pza', '20.00', '0.00') - ,('62034291', '6203.42.91', 'Los demás.', '6', 'Pza', '25.00', '0.00') - ,('62034292', '6203.42.92', 'Los demás.', '6', 'Pza', '25.00', '0.00') - ,('62034301', '6203.43.01', 'Con un contenido de lana o pelo fino superior o igual a 36% en peso.', '6', 'Pza', '20.00', '0.00') - ,('62034391', '6203.43.91', 'Los demás.', '6', 'Pza', '25.00', '0.00') - ,('62034392', '6203.43.92', 'Los demás.', '6', 'Pza', '25.00', '0.00') - ,('62034991', '6203.49.91', 'De las demás materias textiles.', '6', 'Pza', '25.00', '0.00') - ,('62041101', '6204.11.01', 'De lana o pelo fino.', '6', 'Pza', '20.00', '0.00') - ,('62041201', '6204.12.01', 'De algodón.', '6', 'Pza', '20.00', '0.00') - ,('62041302', '6204.13.02', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('62041991', '6204.19.91', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('62042101', '6204.21.01', 'De lana o pelo fino.', '6', 'Pza', '20.00', '0.00') - ,('62042201', '6204.22.01', 'De algodón.', '6', 'Pza', '20.00', '0.00') - ,('62042301', '6204.23.01', 'De fibras sintéticas.', '6', 'Pza', '35.00', '0.00') - ,('62042991', '6204.29.91', 'De las demás materias textiles.', '6', 'Pza', '35.00', '0.00') - ,('62043101', '6204.31.01', 'De lana o pelo fino.', '6', 'Pza', '20.00', '0.00') - ,('62043203', '6204.32.03', 'Las demás.', '6', 'Pza', '25.00', '0.00') - ,('62043301', '6204.33.01', 'Con un contenido de lana o pelo fino superior o igual a 36% en peso.', '6', 'Pza', '20.00', '0.00') - ,('62043302', '6204.33.02', 'Con un contenido de lino superior o igual a 36% en peso.', '6', 'Pza', '20.00', '0.00') - ,('62043399', '6204.33.99', 'Los demás con forro o relleno para mujeres.', '6', 'Pza', '25.00', '0.00') - ,('62043991', '6204.39.91', 'Los demás.', '6', 'Pza', '35.00', '0.00') - ,('62044101', '6204.41.01', 'De lana o pelo fino.', '6', 'Pza', '35.00', '0.00') - ,('62044201', '6204.42.01', 'Hechos totalmente a mano.', '6', 'Pza', '35.00', '0.00') - ,('62044299', '6204.42.99', 'Para niñas.', '6', 'Pza', '25.00', '0.00') - ,('62044301', '6204.43.01', 'Hechos totalmente a mano.', '6', 'Pza', '20.00', '0.00') - ,('62044302', '6204.43.02', 'Con un contenido de lana o pelo fino superior o igual a 36% en peso, excepto lo comprendido en la fracción arancelaria 6204.43.01.', '6', 'Pza', '20.00', '0.00') - ,('62044399', '6204.43.99', 'Para niñas.', '6', 'Pza', '25.00', '0.00') - ,('62044401', '6204.44.01', 'Hechos totalmente a mano.', '6', 'Pza', '20.00', '0.00') - ,('62044402', '6204.44.02', 'Con un contenido de lana o pelo fino superior o igual a 36% en peso, excepto lo comprendido en la fracción arancelaria 6204.44.01.', '6', 'Pza', '20.00', '0.00') - ,('62044499', '6204.44.99', 'Para niñas.', '6', 'Pza', '25.00', '0.00') - ,('62044991', '6204.49.91', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('62045101', '6204.51.01', 'De lana o pelo fino.', '6', 'Pza', '20.00', '0.00') - ,('62045203', '6204.52.03', 'Las demás.', '6', 'Pza', '25.00', '0.00') - ,('62045301', '6204.53.01', 'Hechas totalmente a mano.', '6', 'Pza', '20.00', '0.00') - ,('62045302', '6204.53.02', 'Con un contenido de lana o pelo fino superior o igual a 36% en peso, excepto lo comprendido en la fracción arancelaria 6204.53.01.', '6', 'Pza', '20.00', '0.00') - ,('62045399', '6204.53.99', 'Para niñas.', '6', 'Pza', '25.00', '0.00') - ,('62045991', '6204.59.91', 'Los demás.', '6', 'Pza', '35.00', '0.00') - ,('62046101', '6204.61.01', 'De lana o pelo fino.', '6', 'Pza', '20.00', '0.00') - ,('62046209', '6204.62.09', 'Los demás para niñas.', '6', 'Pza', '25.00', '0.00') - ,('62046301', '6204.63.01', 'Con un contenido de lana o pelo fino superior o igual a 36% en peso.', '6', 'Pza', '20.00', '0.00') - ,('62046391', '6204.63.91', 'Los demás.', '6', 'Pza', '25.00', '0.00') - ,('62046392', '6204.63.92', 'Los demás.', '6', 'Pza', '25.00', '0.00') - ,('62046902', '6204.69.02', 'Con un contenido de seda superior o igual a 70% en peso.', '6', 'Pza', '20.00', '0.00') - ,('62046903', '6204.69.03', 'Con un contenido de lana o pelo fino superior o igual a 36% en peso.', '6', 'Pza', '20.00', '0.00') - ,('62046999', '6204.69.99', 'Los demás.', '6', 'Pza', '25.00', '0.00') - ,('62052001', '6205.20.01', 'Hechas totalmente a mano.', '6', 'Pza', '20.00', '0.00') - ,('62052091', '6205.20.91', 'Para hombres, excepto lo comprendido en la fracción arancelaria 6205.20.01.', '6', 'Pza', '25.00', '0.00') - ,('62052092', '6205.20.92', 'Para niños, excepto lo comprendido en la fracción arancelaria 6205.20.01.', '6', 'Pza', '25.00', '0.00') - ,('62053001', '6205.30.01', 'Hechas totalmente a mano.', '6', 'Pza', '20.00', '0.00') - ,('62053091', '6205.30.91', 'Para hombres, excepto lo comprendido en la fracción arancelaria 6205.30.01.', '6', 'Pza', '25.00', '0.00') - ,('62053092', '6205.30.92', 'Para niños, excepto lo comprendido en la fracción arancelaria 6205.30.01.', '6', 'Pza', '25.00', '0.00') - ,('62059001', '6205.90.01', 'Con un contenido en seda superior o igual a 70% en peso.', '6', 'Pza', '20.00', '0.00') - ,('62059002', '6205.90.02', 'De lana o pelo fino.', '6', 'Pza', '20.00', '0.00') - ,('62059099', '6205.90.99', 'Las demás.', '6', 'Pza', '25.00', '0.00') - ,('62061001', '6206.10.01', 'De seda o desperdicios de seda.', '6', 'Pza', '20.00', '0.00') - ,('62062002', '6206.20.02', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('62063004', '6206.30.04', 'Para niñas.', '6', 'Pza', '25.00', '0.00') - ,('62064001', '6206.40.01', 'Hechas totalmente a mano.', '6', 'Pza', '35.00', '0.00') - ,('62064002', '6206.40.02', 'Con un contenido de lana o pelo fino superior o igual a 36% en peso, excepto lo comprendido en la fracción arancelaria 6206.40.01.', '6', 'Pza', '20.00', '0.00') - ,('62064091', '6206.40.91', 'Para mujeres, excepto lo comprendido en las fracciones arancelarias 6206.40.01 y 6206.40.02.', '6', 'Pza', '25.00', '0.00') - ,('62064092', '6206.40.92', 'Para niñas, excepto lo comprendido en las fracciones arancelarias 6206.40.01 y 6206.40.02.', '6', 'Pza', '25.00', '0.00') - ,('62069001', '6206.90.01', 'Con mezclas de algodón.', '6', 'Pza', '20.00', '0.00') - ,('62069099', '6206.90.99', 'Los demás.', '6', 'Pza', '25.00', '0.00') - ,('62071101', '6207.11.01', 'Los demás.', '6', 'Pza', '25.00', '0.00') - ,('62071991', '6207.19.91', 'De las demás materias textiles.', '6', 'Pza', '20.00', '0.00') - ,('62072101', '6207.21.01', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('62072201', '6207.22.01', 'De fibras sintéticas o artificiales.', '6', 'Pza', '20.00', '0.00') - ,('62072991', '6207.29.91', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('62079101', '6207.91.01', 'De algodón.', '6', 'Pza', '20.00', '0.00') - ,('62079991', '6207.99.91', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('62081101', '6208.11.01', 'De fibras sintéticas o artificiales.', '6', 'Pza', '20.00', '0.00') - ,('62081991', '6208.19.91', 'De las demás materias textiles.', '6', 'Pza', '20.00', '0.00') - ,('62082101', '6208.21.01', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('62082201', '6208.22.01', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('62082991', '6208.29.91', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('62089101', '6208.91.01', 'De algodón.', '6', 'Pza', '20.00', '0.00') - ,('62089202', '6208.92.02', 'Los demás para mujeres.', '6', 'Pza', '20.00', '0.00') - ,('62089991', '6208.99.91', 'Las demás.', '6', 'Pza', '20.00', '0.00') - ,('62092007', '6209.20.07', 'Los demás.', '6', 'Pza', '25.00', '0.00') - ,('62093005', '6209.30.05', 'Los demás.', '6', 'Pza', '25.00', '0.00') - ,('62099091', '6209.90.91', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('62101001', '6210.10.01', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('62102091', '6210.20.91', 'Las demás prendas de vestir de los tipos citados en la partida 62.01.', '6', 'Pza', '35.00', '0.00') - ,('62103091', '6210.30.91', 'Las demás prendas de vestir de los tipos citados en la partida 62.02.', '6', 'Pza', '20.00', '0.00') - ,('62104091', '6210.40.91', 'Las demás prendas de vestir para hombres o niños.', '6', 'Pza', '20.00', '0.00') - ,('62105091', '6210.50.91', 'Las demás prendas de vestir para mujeres o niñas.', '6', 'Pza', '20.00', '0.00') - ,('62111101', '6211.11.01', 'Para hombres o niños.', '6', 'Pza', '25.00', '0.00') - ,('62111201', '6211.12.01', 'Para mujeres o niñas.', '6', 'Pza', '20.00', '0.00') - ,('62112002', '6211.20.02', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('62113202', '6211.32.02', 'Las demás.', '6', 'Pza', '20.00', '0.00') - ,('62113302', '6211.33.02', 'Las demás.', '6', 'Pza', '20.00', '0.00') - ,('62113991', '6211.39.91', 'Las demás.', '6', 'Pza', '20.00', '0.00') - ,('62114202', '6211.42.02', 'Las demás.', '6', 'Pza', '20.00', '0.00') - ,('62114302', '6211.43.02', 'Las demás.', '6', 'Pza', '20.00', '0.00') - ,('62114991', '6211.49.91', 'De las demás materias textiles.', '6', 'Pza', '35.00', '0.00') - ,('62121007', '6212.10.07', 'Los demás, de fibras sintéticas o artificiales.', '6', 'Pza', '25.00', '0.00') - ,('62122001', '6212.20.01', 'Fajas y fajas braga (fajas bombacha).', '6', 'Pza', '25.00', '0.00') - ,('62123001', '6212.30.01', 'Fajas sostén (fajas corpiño).', '6', 'Pza', '25.00', '0.00') - ,('62129099', '6212.90.99', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('62132001', '6213.20.01', 'De algodón.', '6', 'Pza', '20.00', '0.00') - ,('62139091', '6213.90.91', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('62141001', '6214.10.01', 'De seda o desperdicios de seda.', '6', 'Pza', '20.00', '0.00') - ,('62142001', '6214.20.01', 'De lana o pelo fino.', '6', 'Pza', '20.00', '0.00') - ,('62143001', '6214.30.01', 'De fibras sintéticas.', '6', 'Pza', '20.00', '0.00') - ,('62144001', '6214.40.01', 'De fibras artificiales.', '6', 'Pza', '20.00', '0.00') - ,('62149091', '6214.90.91', 'De las demás materias textiles.', '6', 'Pza', '20.00', '0.00') - ,('62151001', '6215.10.01', 'De seda o desperdicios de seda.', '6', 'Pza', '20.00', '0.00') - ,('62152001', '6215.20.01', 'De fibras sintéticas o artificiales.', '6', 'Pza', '20.00', '0.00') - ,('62159091', '6215.90.91', 'De las demás materias textiles.', '6', 'Pza', '20.00', '0.00') - ,('62160001', '6216.00.01', 'Guantes, mitones y manoplas.', '9', 'Par', '20.00', '0.00') - ,('62171001', '6217.10.01', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('62179001', '6217.90.01', 'Las demás.', '6', 'Pza', '20.00', '0.00') - ,('63011001', '6301.10.01', 'Mantas eléctricas.', '6', 'Pza', '15.00', '0.00') - ,('63012001', '6301.20.01', 'Mantas de lana o pelo fino (excepto las eléctricas).', '6', 'Pza', '35.00', '0.00') - ,('63013001', '6301.30.01', 'Mantas de algodón (excepto las eléctricas).', '6', 'Pza', '20.00', '0.00') - ,('63014001', '6301.40.01', 'Mantas de fibras sintéticas (excepto las eléctricas).', '6', 'Pza', '25.00', '0.00') - ,('63019091', '6301.90.91', 'Las demás mantas.', '6', 'Pza', '20.00', '0.00') - ,('63021001', '6302.10.01', 'Ropa de cama, de punto.', '6', 'Pza', '20.00', '0.00') - ,('63022101', '6302.21.01', 'Los demás.', '6', 'Pza', '25.00', '0.00') - ,('63022201', '6302.22.01', 'Los demás.', '6', 'Pza', '25.00', '0.00') - ,('63022991', '6302.29.91', 'De las demás materias textiles.', '6', 'Pza', '35.00', '0.00') - ,('63023106', '6302.31.06', 'Las demás.', '6', 'Pza', '25.00', '0.00') - ,('63023206', '6302.32.06', 'Las demás.', '6', 'Pza', '25.00', '0.00') - ,('63023991', '6302.39.91', 'De las demás materias textiles.', '6', 'Pza', '35.00', '0.00') - ,('63024001', '6302.40.01', 'Ropa de mesa, de punto.', '6', 'Pza', '35.00', '0.00') - ,('63025101', '6302.51.01', 'De algodón.', '6', 'Pza', '20.00', '0.00') - ,('63025301', '6302.53.01', 'De fibras sintéticas o artificiales.', '6', 'Pza', '20.00', '0.00') - ,('63025991', '6302.59.91', 'Las demás.', '6', 'Pza', '20.00', '0.00') - ,('63026006', '6302.60.06', 'Las demás.', '6', 'Pza', '25.00', '0.00') - ,('63029101', '6302.91.01', 'Los demás.', '6', 'Pza', '35.00', '0.00') - ,('63029301', '6302.93.01', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('63029991', '6302.99.91', 'Las demás.', '6', 'Pza', '20.00', '0.00') - ,('63031201', '6303.12.01', 'De fibras sintéticas.', '6', 'Pza', '35.00', '0.00') - ,('63031991', '6303.19.91', 'Las demás.', '6', 'Pza', '35.00', '0.00') - ,('63039101', '6303.91.01', 'De algodón.', '6', 'Pza', '35.00', '0.00') - ,('63039202', '6303.92.02', 'Las demás.', '6', 'Pza', '20.00', '0.00') - ,('63039991', '6303.99.91', 'De las demás materias textiles.', '6', 'Pza', '35.00', '0.00') - ,('63041101', '6304.11.01', 'De punto.', '6', 'Pza', '20.00', '0.00') - ,('63041999', '6304.19.99', 'Las demás.', '6', 'Pza', '25.00', '0.00') - ,('63042001', '6304.20.01', 'Mosquiteros para camas, especificados en la Nota 1 de subpartida de este Capítulo.', '6', 'Pza', '20.00', '0.00') - ,('63049101', '6304.91.01', 'De punto.', '6', 'Pza', '35.00', '0.00') - ,('63049201', '6304.92.01', 'De algodón, excepto de punto.', '6', 'Pza', '20.00', '0.00') - ,('63049301', '6304.93.01', 'De fibras sintéticas, excepto de punto.', '6', 'Pza', '20.00', '0.00') - ,('63049991', '6304.99.91', 'De las demás materias textiles, excepto de punto.', '6', 'Pza', '20.00', '0.00') - ,('63051001', '6305.10.01', 'De yute o demás fibras textiles del líber de la partida 53.03.', '6', 'Pza', '15.00', '0.00') - ,('63052001', '6305.20.01', 'De algodón.', '6', 'Pza', '15.00', '0.00') - ,('63053201', '6305.32.01', 'Continentes intermedios flexibles para productos a granel.', '6', 'Pza', '35.00', '0.00') - ,('63053391', '6305.33.91', 'Los demás, de tiras o formas similares, de polietileno o polipropileno.', '6', 'Pza', '15.00', '0.00') - ,('63053999', '6305.39.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('63059091', '6305.90.91', 'De las demás materias textiles.', '6', 'Pza', '15.00', '0.00') - ,('63061201', '6306.12.01', 'De fibras sintéticas.', '6', 'Pza', '15.00', '0.00') - ,('63061991', '6306.19.91', 'Los demás.', '6', 'Pza', '35.00', '0.00') - ,('63062201', '6306.22.01', 'De fibras sintéticas.', '6', 'Pza', '15.00', '0.00') - ,('63062991', '6306.29.91', 'Los demás.', '6', 'Pza', '35.00', '0.00') - ,('63063002', '6306.30.02', 'Las demás.', '6', 'Pza', '15.00', '0.00') - ,('63064002', '6306.40.02', 'Los demás.', '6', 'Pza', '35.00', '0.00') - ,('63069099', '6306.90.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('63071001', '6307.10.01', 'Paños para fregar o lavar (bayetas, paños rejilla), franelas y artículos similares para limpieza.', '1', 'Kg', '20.00', '0.00') - ,('63072001', '6307.20.01', 'Cinturones y chalecos salvavidas.', '1', 'Kg', '15.00', '0.00') - ,('63079001', '6307.90.01', 'Toallas quirúrgicas.', '6', 'Pza', '20.00', '0.00') - ,('63079099', '6307.90.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('63080001', '6308.00.01', 'Juegos constituidos por piezas de tejido e hilados, incluso con accesorios, para la confección de alfombras, tapicería, manteles o servilletas bordados o de artículos textiles similares, en envases para la venta al por menor.', '1', 'Kg', '15.00', '0.00') - ,('63090001', '6309.00.01', 'Artículos de prendería.', '1', 'Kg', '20.00', '0.00') - ,('63101002', '6310.10.02', 'Los demás.', '1', 'Kg', '20.00', '0.00') - ,('63109099', '6310.90.99', 'Los demás.', '1', 'Kg', '20.00', '0.00') - ,('64011001', '6401.10.01', 'Los demás.', '9', 'Par', '35.00', '0.00') - ,('64019211', '6401.92.11', 'Para niños, niñas o infantes.', '9', 'Par', '35.00', '0.00') - ,('64019299', '6401.92.99', 'Los demás para niños, niñas o infantes.', '9', 'Par', '35.00', '0.00') - ,('64019999', '6401.99.99', 'Los demás para niños, niñas o infantes.', '9', 'Par', '35.00', '0.00') - ,('64021201', '6402.12.01', 'Calzado de esquí y calzado para la práctica de "snowboard" (tabla para nieve).', '9', 'Par', '35.00', '0.00') - ,('64021901', '6402.19.01', 'Para hombres, adultos y jóvenes, con la parte superior (corte) con un contenido de caucho o plástico superior al 90%, excepto el que tenga una banda o aplicación similar pegada o moldeada a la suela y sobrepuesta al corte.', '9', 'Par', '35.00', '0.00') - ,('64021999', '6402.19.99', 'Para niños, niñas o infantes.', '9', 'Par', '35.00', '0.00') - ,('64022004', '6402.20.04', 'Para niños, niñas o infantes.', '9', 'Par', '35.00', '0.00') - ,('64029102', '6402.91.02', 'Con puntera metálica de protección.', '9', 'Par', '35.00', '0.00') - ,('64029106', '6402.91.06', 'Los demás sin puntera metálica.', '9', 'Par', '35.00', '0.00') - ,('64029906', '6402.99.06', 'Los demás.', '9', 'Par', '35.00', '0.00') - ,('64029919', '6402.99.19', 'Las demás para niños o niñas.', '9', 'Par', '35.00', '0.00') - ,('64029920', '6402.99.20', 'Para niños, niñas o infantes.', '9', 'Par', '35.00', '0.00') - ,('64029921', '6402.99.21', 'Los demás para niños o niñas.', '9', 'Par', '35.00', '0.00') - ,('64029991', '6402.99.91', 'Los demás para hombres, adultos y jóvenes.', '9', 'Par', '35.00', '0.00') - ,('64029992', '6402.99.92', 'Los demás para mujeres, adultas y jóvenes.', '9', 'Par', '35.00', '0.00') - ,('64029993', '6402.99.93', 'Los demás, para niños y niñas.', '9', 'Par', '35.00', '0.00') - ,('64029994', '6402.99.94', 'Los demás para infantes.', '9', 'Par', '35.00', '0.00') - ,('64031201', '6403.12.01', 'Calzado de esquí y calzado para la práctica de "snowboard" (tabla para nieve).', '9', 'Par', '0.00', '0.00') - ,('64031902', '6403.19.02', 'Para hombres, adultos y jóvenes, excepto de construcción "Welt".', '9', 'Par', '35.00', '0.00') - ,('64031999', '6403.19.99', 'Los demás.', '9', 'Par', '35.00', '0.00') - ,('64032001', '6403.20.01', 'Calzado con suela de cuero natural y parte superior de tiras de cuero natural que pasan por el empeine y rodean el dedo gordo.', '9', 'Par', '35.00', '0.00') - ,('64034091', '6403.40.91', 'Para niños, niñas o infantes.', '9', 'Par', '35.00', '0.00') - ,('64035105', '6403.51.05', 'Para niños, niñas o infantes.', '9', 'Par', '35.00', '0.00') - ,('64035999', '6403.59.99', 'Los demás para niños, niñas o infantes.', '9', 'Par', '35.00', '0.00') - ,('64039104', '6403.91.04', 'Con palmilla o plataforma de madera, sin plantillas ni puntera metálica de protección.', '9', 'Par', '0.00', '0.00') - ,('64039112', '6403.91.12', 'Para niños, niñas o infantes.', '9', 'Par', '35.00', '0.00') - ,('64039113', '6403.91.13', 'Para niños, niñas o infantes.', '9', 'Par', '35.00', '0.00') - ,('64039199', '6403.91.99', 'Para niños, niñas o infantes.', '9', 'Par', '35.00', '0.00') - ,('64039901', '6403.99.01', 'De construcción "Welt".', '9', 'Par', '35.00', '0.00') - ,('64039906', '6403.99.06', 'Con palmilla o plataforma de madera, sin plantillas ni puntera metálica de protección.', '9', 'Par', '0.00', '0.00') - ,('64039912', '6403.99.12', 'Sandalias para niños, niñas o infantes.', '9', 'Par', '35.00', '0.00') - ,('64039913', '6403.99.13', 'Para niños, niñas o infantes.', '9', 'Par', '35.00', '0.00') - ,('64039914', '6403.99.14', 'Para mujeres, adultas y jóvenes.', '9', 'Par', '35.00', '0.00') - ,('64039991', '6403.99.91', 'Los demás para niños, niñas o infantes.', '9', 'Par', '35.00', '0.00') - ,('64039999', '6403.99.99', 'Para mujeres, adultas y jóvenes.', '9', 'Par', '35.00', '0.00') - ,('64041109', '6404.11.09', 'De deporte para niños, niñas o infantes, excepto los que tengan una banda o aplicación similar pegada o moldeada a la suela y sobrepuesta al corte.', '9', 'Par', '35.00', '0.00') - ,('64041112', '6404.11.12', 'Para infantes.', '9', 'Par', '35.00', '0.00') - ,('64041116', '6404.11.16', 'Para mujeres, adultas y jóvenes.', '9', 'Par', '25.00', '0.00') - ,('64041117', '6404.11.17', 'Para mujeres, adultas y jóvenes.', '9', 'Par', '35.00', '0.00') - ,('64041199', '6404.11.99', 'Los demás para infantes.', '9', 'Par', '35.00', '0.00') - ,('64041902', '6404.19.02', 'Para mujeres, adultas y jóvenes, excepto el que tenga una banda o aplicación similar pegada o moldeada a la suela y sobrepuesta al corte y lo comprendido en la fracción arancelaria 6404.19.08.', '9', 'Par', '35.00', '0.00') - ,('64041908', '6404.19.08', 'Formal o de vestir.', '9', 'Par', '35.00', '0.00') - ,('64041999', '6404.19.99', 'Los demás para infantes.', '9', 'Par', '35.00', '0.00') - ,('64042001', '6404.20.01', 'Los demás.', '9', 'Par', '35.00', '0.00') - ,('64051001', '6405.10.01', 'Con la parte superior de cuero natural o regenerado.', '9', 'Par', '35.00', '0.00') - ,('64052001', '6405.20.01', 'Con la suela de madera o corcho.', '9', 'Par', '0.00', '0.00') - ,('64052002', '6405.20.02', 'Con suela y parte superior de fieltro de lana.', '9', 'Par', '35.00', '0.00') - ,('64052099', '6405.20.99', 'Los demás calzados para infantes.', '9', 'Par', '35.00', '0.00') - ,('64059099', '6405.90.99', 'Los demás.', '9', 'Par', '35.00', '0.00') - ,('64061008', '6406.10.08', 'Las demás.', '9', 'Par', '20.00', '0.00') - ,('64061009', '6406.10.09', 'Las demás.', '6', 'Pza', '20.00', '0.00') - ,('64062001', '6406.20.01', 'Suelas.', '6', 'Pza', '5.00', '0.00') - ,('64062002', '6406.20.02', 'Tacones (tacos).', '6', 'Pza', '0.00', '0.00') - ,('64069001', '6406.90.01', 'De madera.', '1', 'Kg', '0.00', '0.00') - ,('64069002', '6406.90.02', 'Botines, polainas y artículos similares y sus partes.', '1', 'Kg', '0.00', '0.00') - ,('64069099', '6406.90.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('65010001', '6501.00.01', 'Cascos sin ahormado ni perfilado del ala, platos (discos) y cilindros aunque estén cortados en el sentido de la altura, de fieltro, para sombreros.', '6', 'Pza', '0.00', '0.00') - ,('65020001', '6502.00.01', 'Cascos para sombreros, trenzados o fabricados por unión de tiras de cualquier materia, sin ahormado ni perfilado del ala y sin guarnecer.', '6', 'Pza', '0.00', '0.00') - ,('65040001', '6504.00.01', 'Sombreros y demás tocados, trenzados o fabricados por unión de tiras de cualquier materia, incluso guarnecidos.', '6', 'Pza', '35.00', '0.00') - ,('65050004', '6505.00.04', 'Los demás.', '6', 'Pza', '35.00', '0.00') - ,('65061001', '6506.10.01', 'Cascos de seguridad.', '6', 'Pza', '5.00', '0.00') - ,('65069101', '6506.91.01', 'Gorras.', '6', 'Pza', '15.00', '0.00') - ,('65069199', '6506.91.99', 'Los demás.', '6', 'Pza', '10.00', '0.00') - ,('65069991', '6506.99.91', 'De las demás materias.', '6', 'Pza', '15.00', '0.00') - ,('65070001', '6507.00.01', 'Desudadores, forros, fundas, armaduras, viseras y barboquejos (barbijos), para sombreros y demás tocados.', '1', 'Kg', '15.00', '0.00') - ,('66011001', '6601.10.01', 'Quitasoles toldo y artículos similares.', '6', 'Pza', '15.00', '0.00') - ,('66019101', '6601.91.01', 'Con astil o mango telescópico.', '6', 'Pza', '15.00', '0.00') - ,('66019999', '6601.99.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('66020001', '6602.00.01', 'Bastones, bastones asiento, látigos, fustas y artículos similares.', '6', 'Pza', '15.00', '0.00') - ,('66032001', '6603.20.01', 'Monturas ensambladas, sin puños.', '1', 'Kg', '0.00', '0.00') - ,('66032099', '6603.20.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('66039099', '6603.90.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('67010001', '6701.00.01', 'Artículos de pluma o plumón.', '1', 'Kg', '0.00', '0.00') - ,('67010099', '6701.00.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('67021001', '6702.10.01', 'De plástico.', '1', 'Kg', '15.00', '0.00') - ,('67029091', '6702.90.91', 'De las demás materias.', '1', 'Kg', '0.00', '0.00') - ,('67030001', '6703.00.01', 'Cabello peinado, afinado, blanqueado o preparado de otra forma; lana, pelo u otra materia textil, preparados para la fabricación de pelucas o artículos similares.', '1', 'Kg', '15.00', '0.00') - ,('67041101', '6704.11.01', 'Pelucas que cubran toda la cabeza.', '1', 'Kg', '0.00', '0.00') - ,('67041999', '6704.19.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('67042001', '6704.20.01', 'De cabello.', '1', 'Kg', '0.00', '0.00') - ,('67049091', '6704.90.91', 'De las demás materias.', '1', 'Kg', '15.00', '0.00') - ,('68010001', '6801.00.01', 'Adoquines, encintados (bordillos) y losas para pavimentos, de piedra natural (excepto la pizarra).', '1', 'Kg', '15.00', '0.00') - ,('68021001', '6802.10.01', 'Losetas, cubos, dados y artículos similares, incluso de forma distinta a la cuadrada o rectangular, en los que la superficie mayor pueda inscribirse en un cuadrado de lado inferior a 7 cm.', '1', 'Kg', '15.00', '0.00') - ,('68021099', '6802.10.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('68022101', '6802.21.01', 'Mármol, travertinos y alabastro.', '1', 'Kg', '15.00', '0.00') - ,('68022302', '6802.23.02', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('68022991', '6802.29.91', 'Las demás.', '1', 'Kg', '15.00', '0.00') - ,('68029101', '6802.91.01', 'Mármol, travertinos y alabastro.', '1', 'Kg', '15.00', '0.00') - ,('68029291', '6802.92.91', 'Las demás piedras calizas.', '1', 'Kg', '15.00', '0.00') - ,('68029301', '6802.93.01', 'Granito.', '1', 'Kg', '0.00', '0.00') - ,('68029991', '6802.99.91', 'Las demás piedras.', '1', 'Kg', '15.00', '0.00') - ,('68030001', '6803.00.01', 'En losas rectangulares, cuyas medidas en milímetros sean: longitud inferior o igual a 700, anchura inferior o igual a 350 y un espesor inferior o igual a 26.', '1', 'Kg', '5.00', '0.00') - ,('68030099', '6803.00.99', 'Las demás.', '1', 'Kg', '15.00', '0.00') - ,('68041002', '6804.10.02', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('68042102', '6804.21.02', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('68042291', '6804.22.91', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('68042302', '6804.23.02', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('68043001', '6804.30.01', 'Piedras de afilar o pulir a mano.', '1', 'Kg', '10.00', '0.00') - ,('68051001', '6805.10.01', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('68052001', '6805.20.01', 'Con soporte constituido solamente por papel o cartón.', '1', 'Kg', '5.00', '0.00') - ,('68053001', '6805.30.01', 'Con soporte de otras materias.', '1', 'Kg', '5.00', '0.00') - ,('68061001', '6806.10.01', 'Lana de escoria, de roca y lanas minerales similares, incluso mezcladas entre sí, en masa, hojas o enrolladas.', '1', 'Kg', '0.00', '0.00') - ,('68062001', '6806.20.01', 'Vermiculita dilatada, arcilla dilatada, espuma de escoria y productos minerales similares dilatados, incluso mezclados entre sí.', '1', 'Kg', '0.00', '0.00') - ,('68069099', '6806.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('68071001', '6807.10.01', 'En rollos.', '1', 'Kg', '0.00', '0.00') - ,('68079099', '6807.90.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('68080001', '6808.00.01', 'Paneles, placas, losetas, bloques y artículos similares, de fibra vegetal, paja o viruta, de plaquitas o partículas, o de aserrín o demás desperdicios de madera, aglomerados con cemento, yeso fraguable o demás aglutinantes minerales.', '1', 'Kg', '15.00', '0.00') - ,('68091101', '6809.11.01', 'Revestidos o reforzados exclusivamente con papel o cartón.', '1', 'Kg', '15.00', '0.00') - ,('68091999', '6809.19.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('68099091', '6809.90.91', 'Las demás manufacturas.', '1', 'Kg', '15.00', '0.00') - ,('68101101', '6810.11.01', 'Bloques y ladrillos para la construcción.', '1', 'Kg', '15.00', '0.00') - ,('68101999', '6810.19.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('68109101', '6810.91.01', 'Postes, no decorativos.', '1', 'Kg', '5.00', '0.00') - ,('68109199', '6810.91.99', 'Las demás.', '1', 'Kg', '15.00', '0.00') - ,('68109999', '6810.99.99', 'Las demás.', '1', 'Kg', '15.00', '0.00') - ,('68114003', '6811.40.03', 'Tubería de presión, tubería sanitaria o para ductos eléctricos.', '1', 'Kg', '5.00', '0.00') - ,('68114005', '6811.40.05', 'Placas, paneles, losetas, tejas y artículos similares.', '1', 'Kg', '5.00', '0.00') - ,('68114091', '6811.40.91', 'Los demás tubos, fundas y accesorios de tubería.', '1', 'Kg', '35.00', '0.00') - ,('68114099', '6811.40.99', 'Las demás.', '1', 'Kg', '35.00', '0.00') - ,('68118101', '6811.81.01', 'Placas onduladas.', '1', 'Kg', '5.00', '0.00') - ,('68118291', '6811.82.91', 'Las demás placas, paneles, losetas, tejas y artículos similares.', '1', 'Kg', '5.00', '0.00') - ,('68118902', '6811.89.02', 'Tubos, fundas y accesorios de tubería.', '1', 'Kg', '5.00', '0.00') - ,('68118999', '6811.89.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('68128001', '6812.80.01', 'Prendas y complementos (accesorios), de vestir, calzado, sombreros y demás tocados.', '1', 'Kg', '10.00', '0.00') - ,('68128005', '6812.80.05', 'Crocidolita en fibras, trabajada; mezclas a base de crocidolita o a base de crocidolita y carbonato de magnesio.', '1', 'Kg', '5.00', '0.00') - ,('68128006', '6812.80.06', 'Hilados.', '1', 'Kg', '5.00', '0.00') - ,('68128008', '6812.80.08', 'Tejidos, incluso de punto.', '1', 'Kg', '5.00', '0.00') - ,('68128099', '6812.80.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('68129101', '6812.91.01', 'Prendas y complementos (accesorios), de vestir, calzado, sombreros y demás tocados.', '1', 'Kg', '10.00', '0.00') - ,('68129902', '6812.99.02', 'Amianto (asbesto) en fibras trabajado; mezclas a base de amianto o a base de amianto y carbonato de magnesio.', '1', 'Kg', '35.00', '0.00') - ,('68129903', '6812.99.03', 'Hilados.', '1', 'Kg', '35.00', '0.00') - ,('68129905', '6812.99.05', 'Tejidos, incluso de punto.', '1', 'Kg', '35.00', '0.00') - ,('68129906', '6812.99.06', 'Amianto (asbesto) y elastómeros comprimidos, para juntas o empaquetaduras, en hojas o rollos, excepto lo comprendido en la fracción arancelaria 6812.99.02.', '1', 'Kg', '35.00', '0.00') - ,('68129999', '6812.99.99', 'Las demás.', '1', 'Kg', '35.00', '0.00') - ,('68132005', '6813.20.05', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('68138101', '6813.81.01', 'Reconocibles para naves aéreas.', '1', 'Kg', '0.00', '0.00') - ,('68138199', '6813.81.99', 'Las demás.', '1', 'Kg', '5.00', '0.00') - ,('68138999', '6813.89.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('68141002', '6814.10.02', 'Placas, hojas y tiras de mica aglomerada o reconstituida, incluso con soporte.', '1', 'Kg', '0.00', '0.00') - ,('68149099', '6814.90.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('68151101', '6815.11.01', 'Fibras de carbono.', '1', 'Kg', '5.00', '0.00') - ,('68151201', '6815.12.01', 'Textiles de fibras de carbono.', '1', 'Kg', '5.00', '0.00') - ,('68151391', '6815.13.91', 'Las demás manufacturas de fibras de carbono.', '1', 'Kg', '5.00', '0.00') - ,('68151901', '6815.19.01', 'Tapones o tapas de grafito.', '1', 'Kg', '0.00', '0.00') - ,('68151902', '6815.19.02', 'Bloques y ladrillos de carbón, con aglutinantes.', '1', 'Kg', '0.00', '0.00') - ,('68151903', '6815.19.03', 'Filtros, caños, codos o uniones de grafito, impermeabilizados con resinas polimerizadas, reconocibles como diseñadas exclusivamente para intercambiadores de calor.', '1', 'Kg', '0.00', '0.00') - ,('68151904', '6815.19.04', 'Láminas de grafito natural.', '1', 'Kg', '0.00', '0.00') - ,('68151999', '6815.19.99', 'Las demás.', '1', 'Kg', '5.00', '0.00') - ,('68152001', '6815.20.01', 'Manufacturas de turba.', '1', 'Kg', '5.00', '0.00') - ,('68159102', '6815.91.02', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('68159199', '6815.91.99', 'Las demás.', '1', 'Kg', '5.00', '0.00') - ,('68159901', '6815.99.01', 'A base de óxidos, fundidos eléctricamente.', '1', 'Kg', '0.00', '0.00') - ,('68159999', '6815.99.99', 'Las demás.', '1', 'Kg', '5.00', '0.00') - ,('69010001', '6901.00.01', 'Ladrillos, placas, baldosas y demás piezas cerámicas de harinas silíceas fósiles (por ejemplo: "kieselguhr", tripolita, diatomita) o de tierras silíceas análogas.', '1', 'Kg', '0.00', '0.00') - ,('69021001', '6902.10.01', 'Con un contenido de los elementos Mg (magnesio), Ca (calcio) o Cr (cromo), considerados aislada o conjuntamente, superior al 50% en peso, expresados en MgO (óxido de magnesio), CaO (óxido de calcio) o Cr2O3 (óxido crómico).', '1', 'Kg', '0.00', '0.00') - ,('69022001', '6902.20.01', 'Con un contenido de alúmina (Al2O3), de sílice (SiO2) o de una mezcla o combinación de estos productos, superior al 50% en peso.', '1', 'Kg', '0.00', '0.00') - ,('69029099', '6902.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('69031003', '6903.10.03', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('69032006', '6903.20.06', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('69039099', '6903.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('69041001', '6904.10.01', 'Ladrillos de construcción.', '1', 'Kg', '15.00', '0.00') - ,('69049099', '6904.90.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('69051001', '6905.10.01', 'Tejas.', '1', 'Kg', '15.00', '0.00') - ,('69059001', '6905.90.01', 'Ornamentos arquitectónicos (cornisas, frisos, etc.) y demás artículos cerámicos de construcción, (sombretes, cañones de chimenea, etc.).', '1', 'Kg', '0.00', '0.00') - ,('69059099', '6905.90.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('69060001', '6906.00.01', 'Tubos, canalones y accesorios de tubería, de cerámica.', '1', 'Kg', '15.00', '0.00') - ,('69072102', '6907.21.02', 'Los demás.', '4', 'M²', '35.00', '0.00') - ,('69072202', '6907.22.02', 'Los demás.', '4', 'M²', '35.00', '0.00') - ,('69072302', '6907.23.02', 'Los demás.', '4', 'M²', '35.00', '0.00') - ,('69073001', '6907.30.01', 'Cubos, dados y artículos similares para mosaicos, excepto los de la subpartida 6907.40.', '4', 'M²', '35.00', '0.00') - ,('69074001', '6907.40.01', 'Piezas de acabado.', '4', 'M²', '35.00', '0.00') - ,('69091106', '6909.11.06', 'De porcelana.', '1', 'Kg', '0.00', '0.00') - ,('69091201', '6909.12.01', 'Artículos con una dureza equivalente a 9 o superior en la escala de Mohs.', '1', 'Kg', '5.00', '0.00') - ,('69091999', '6909.19.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('69099099', '6909.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('69101001', '6910.10.01', 'De porcelana.', '1', 'Kg', '25.00', '0.00') - ,('69109001', '6910.90.01', 'Inodoros (retretes).', '1', 'Kg', '35.00', '0.00') - ,('69109099', '6910.90.99', 'Los demás.', '1', 'Kg', '35.00', '0.00') - ,('69111001', '6911.10.01', 'Artículos para el servicio de mesa o cocina.', '1', 'Kg', '15.00', '0.00') - ,('69119099', '6911.90.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('69120003', '6912.00.03', 'De Talavera.', '1', 'Kg', '15.00', '0.00') - ,('69120099', '6912.00.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('69131001', '6913.10.01', 'De porcelana.', '1', 'Kg', '15.00', '0.00') - ,('69139001', '6913.90.01', 'De Talavera.', '1', 'Kg', '15.00', '0.00') - ,('69139099', '6913.90.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('69141001', '6914.10.01', 'De porcelana.', '1', 'Kg', '15.00', '0.00') - ,('69149001', '6914.90.01', 'De Talavera.', '1', 'Kg', '15.00', '0.00') - ,('69149099', '6914.90.99', 'Las demás.', '1', 'Kg', '15.00', '0.00') - ,('70010001', '7001.00.01', 'Calcín y demás desperdicios y desechos de vidrio, excepto el vidrio de tubos de rayos catódicos y demás vidrios activados de la partida 85.49; vidrio en masa.', '1', 'Kg', '0.00', '0.00') - ,('70021001', '7002.10.01', 'Bolas.', '1', 'Kg', '5.00', '0.00') - ,('70022005', '7002.20.05', 'Barras o varillas.', '1', 'Kg', '0.00', '0.00') - ,('70023103', '7002.31.03', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('70023201', '7002.32.01', 'De otro vidrio con un coeficiente de dilatación lineal inferior o igual a 5x10-6 por Kelvin, entre 0°C y 300°C.', '1', 'Kg', '5.00', '0.00') - ,('70023999', '7002.39.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('70031202', '7003.12.02', 'Coloreadas en la masa, opacificadas, chapadas o con capa absorbente, reflectante o antirreflectante.', '1', 'Kg', '35.00', '0.00') - ,('70031902', '7003.19.02', 'Las demás.', '1', 'Kg', '35.00', '0.00') - ,('70032001', '7003.20.01', 'Placas y hojas, armadas.', '1', 'Kg', '35.00', '0.00') - ,('70033001', '7003.30.01', 'Perfiles.', '1', 'Kg', '15.00', '0.00') - ,('70042003', '7004.20.03', 'Vidrio coloreado en la masa, opacificado, chapado o con capa absorbente, reflectante o antirreflectante.', '1', 'Kg', '15.00', '0.00') - ,('70049091', '7004.90.91', 'Los demás vidrios.', '1', 'Kg', '15.00', '0.00') - ,('70051002', '7005.10.02', 'Los demás.', '1', 'Kg', '35.00', '0.00') - ,('70052103', '7005.21.03', 'Los demás.', '1', 'Kg', '35.00', '0.00') - ,('70052901', '7005.29.01', 'De vidrio flotado claro, con espesor inferior o igual a 1 mm y con una superficie que no exceda de 4.65 m².', '1', 'Kg', '35.00', '0.00') - ,('70052999', '7005.29.99', 'Los demás.', '1', 'Kg', '35.00', '0.00') - ,('70053001', '7005.30.01', 'Vidrio armado.', '1', 'Kg', '35.00', '0.00') - ,('70060004', '7006.00.04', 'Los demás.', '1', 'Kg', '35.00', '0.00') - ,('70071101', '7007.11.01', 'Reconocibles para naves aéreas.', '1', 'Kg', '0.00', '0.00') - ,('70071199', '7007.11.99', 'Los demás.', '1', 'Kg', '35.00', '0.00') - ,('70071999', '7007.19.99', 'Los demás.', '1', 'Kg', '35.00', '0.00') - ,('70072103', '7007.21.03', 'Reconocibles para naves aéreas.', '1', 'Kg', '0.00', '0.00') - ,('70072199', '7007.21.99', 'Los demás.', '1', 'Kg', '35.00', '0.00') - ,('70072999', '7007.29.99', 'Los demás.', '1', 'Kg', '35.00', '0.00') - ,('70080001', '7008.00.01', 'Vidrieras aislantes de paredes múltiples.', '1', 'Kg', '35.00', '0.00') - ,('70091004', '7009.10.04', 'Reconocibles para naves aéreas.', '1', 'Kg', '0.00', '0.00') - ,('70091099', '7009.10.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('70099101', '7009.91.01', 'Espejos semitransparentes, sin película reflejante de plata, sin montar, con espesor igual o inferior a 2 mm y con dimensiones máximas de 900 mm de largo y 320 mm de ancho.', '1', 'Kg', '0.00', '0.00') - ,('70099199', '7009.91.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('70099201', '7009.92.01', 'Enmarcados.', '1', 'Kg', '15.00', '0.00') - ,('70101001', '7010.10.01', 'Para inseminación artificial.', '1', 'Kg', '5.00', '0.00') - ,('70101099', '7010.10.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('70102001', '7010.20.01', 'Tapones, tapas y demás dispositivos de cierre.', '1', 'Kg', '5.00', '0.00') - ,('70109099', '7010.90.99', 'Los demás.', '1', 'Kg', '35.00', '0.00') - ,('70111005', '7011.10.05', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('70112005', '7011.20.05', 'Para tubos de rayos catódicos.', '1', 'Kg', '0.00', '0.00') - ,('70119099', '7011.90.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('70131001', '7013.10.01', 'Artículos de vitrocerámica.', '1', 'Kg', '25.00', '0.00') - ,('70132201', '7013.22.01', 'De cristal al plomo.', '1', 'Kg', '25.00', '0.00') - ,('70132899', '7013.28.99', 'Los demás.', '1', 'Kg', '30.00', '0.00') - ,('70133301', '7013.33.01', 'De cristal al plomo.', '1', 'Kg', '25.00', '0.00') - ,('70133799', '7013.37.99', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('70134101', '7013.41.01', 'De cristal al plomo.', '1', 'Kg', '25.00', '0.00') - ,('70134201', '7013.42.01', 'De vidrio con un coeficiente de dilatación lineal inferior o igual a 5x10-6 por Kelvin, entre 0°C y 300°C.', '1', 'Kg', '25.00', '0.00') - ,('70134999', '7013.49.99', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('70139101', '7013.91.01', 'De cristal al plomo.', '1', 'Kg', '25.00', '0.00') - ,('70139999', '7013.99.99', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('70140001', '7014.00.01', 'Elementos de óptica común.', '1', 'Kg', '0.00', '0.00') - ,('70140002', '7014.00.02', 'Elementos de vidrio para alumbrado y señalización.', '1', 'Kg', '0.00', '0.00') - ,('70140003', '7014.00.03', 'De borosilicato, refractivos, para alumbrado.', '1', 'Kg', '0.00', '0.00') - ,('70140004', '7014.00.04', 'Lentes o reflectores de borosilicato, reconocibles como diseñados exclusivamente para la fabricación de faros o proyectores sellados (unidades selladas) de uso automotriz.', '1', 'Kg', '0.00', '0.00') - ,('70140099', '7014.00.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('70151001', '7015.10.01', 'Cristales correctores para gafas (anteojos).', '1', 'Kg', '5.00', '0.00') - ,('70159099', '7015.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('70161001', '7016.10.01', 'Cubos, dados y demás artículos similares, de vidrio, incluso con soporte, para mosaicos o decoraciones similares.', '1', 'Kg', '15.00', '0.00') - ,('70169099', '7016.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('70171004', '7017.10.04', 'Embudos, buretas y probetas.', '1', 'Kg', '5.00', '0.00') - ,('70171006', '7017.10.06', 'Retortas.', '1', 'Kg', '5.00', '0.00') - ,('70171007', '7017.10.07', 'Frascos para el cultivo de microbios.', '1', 'Kg', '5.00', '0.00') - ,('70171008', '7017.10.08', 'Juntas.', '1', 'Kg', '5.00', '0.00') - ,('70171009', '7017.10.09', 'Tubos de viscosidad; tubos desecadores.', '1', 'Kg', '5.00', '0.00') - ,('70171010', '7017.10.10', 'Agitadores.', '1', 'Kg', '5.00', '0.00') - ,('70171099', '7017.10.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('70172001', '7017.20.01', 'Goteros y matraces, pipetas, vasos de precipitados y vasos graduados.', '1', 'Kg', '0.00', '0.00') - ,('70172003', '7017.20.03', 'Cápsulas y refrigerantes.', '1', 'Kg', '0.00', '0.00') - ,('70172099', '7017.20.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('70179003', '7017.90.03', 'Desecadores, tapas o tiras para el cultivo de microbios.', '1', 'Kg', '5.00', '0.00') - ,('70179004', '7017.90.04', 'Tubos de viscosidad; tubos desecadores.', '1', 'Kg', '5.00', '0.00') - ,('70179099', '7017.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('70181001', '7018.10.01', 'Cuentas de vidrio, imitaciones de perlas, de piedras preciosas o semipreciosas y artículos similares de abalorio.', '1', 'Kg', '0.00', '0.00') - ,('70182001', '7018.20.01', 'Microesferas de borosilicato-óxido de calcio-soda, unicelulares.', '1', 'Kg', '0.00', '0.00') - ,('70182099', '7018.20.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('70189099', '7018.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('70191101', '7019.11.01', 'Hilados cortados ("chopped strands"), de longitud inferior o igual a 50 mm.', '1', 'Kg', '0.00', '0.00') - ,('70191201', '7019.12.01', 'Rovings.', '1', 'Kg', '0.00', '0.00') - ,('70191391', '7019.13.91', 'Los demás hilados, mechas.', '1', 'Kg', '0.00', '0.00') - ,('70191401', '7019.14.01', '“Mats” unidos mecánicamente.', '1', 'Kg', '0.00', '0.00') - ,('70191501', '7019.15.01', '”Mats” unidos químicamente.', '1', 'Kg', '0.00', '0.00') - ,('70191999', '7019.19.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('70196101', '7019.61.01', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('70196291', '7019.62.91', 'Las demás telas de “rovings” de malla cerrada.', '1', 'Kg', '0.00', '0.00') - ,('70196301', '7019.63.01', 'De anchura superior a 30 cm, de ligamento tafetán, con peso inferior a 250 g/m², de filamentos de título inferior o igual a 136 tex por hilo sencillo, sin recubrir.', '1', 'Kg', '5.00', '0.00') - ,('70196399', '7019.63.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('70196401', '7019.64.01', 'Tejidos de hilados de malla cerrada, de ligamento tafetán, recubiertos o estratificados.', '1', 'Kg', '0.00', '0.00') - ,('70196501', '7019.65.01', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('70196601', '7019.66.01', 'De ligamento tafetán, con peso inferior a 250 g/m², de filamentos de título inferior o igual a 136 tex por hilo sencillo, sin recubrir.', '1', 'Kg', '5.00', '0.00') - ,('70196699', '7019.66.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('70196999', '7019.69.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('70197101', '7019.71.01', 'Velos (capas delgadas).', '1', 'Kg', '0.00', '0.00') - ,('70197291', '7019.72.91', 'Las demás telas de malla cerrada.', '1', 'Kg', '0.00', '0.00') - ,('70197391', '7019.73.91', 'Las demás telas de malla abierta.', '1', 'Kg', '0.00', '0.00') - ,('70198001', '7019.80.01', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('70199001', '7019.90.01', 'De anchura superior a 30 cm, de ligamento tafetán, con peso inferior a 250 g/m², de filamentos de título inferior o igual a 136 tex por hilo sencillo, sin recubrir.', '1', 'Kg', '5.00', '0.00') - ,('70199099', '7019.90.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('70200091', '7020.00.91', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('71011002', '7101.10.02', 'Perlas naturales (finas).', '1', 'Kg', '0.00', '0.00') - ,('71012101', '7101.21.01', 'En bruto.', '1', 'Kg', '0.00', '0.00') - ,('71012201', '7101.22.01', 'Graduadas y ensartadas temporalmente para facilitar su transporte.', '1', 'Kg', '5.00', '0.00') - ,('71012299', '7101.22.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('71021001', '7102.10.01', 'Sin clasificar.', '2', 'Gr', '0.00', '0.00') - ,('71022101', '7102.21.01', 'En bruto o simplemente aserrados, exfoliados o desbastados.', '2', 'Gr', '0.00', '0.00') - ,('71022999', '7102.29.99', 'Los demás.', '22', 'CARA', '0.00', '0.00') - ,('71023101', '7102.31.01', 'En bruto o simplemente aserrados, exfoliados o desbastados.', '2', 'Gr', '0.00', '0.00') - ,('71023999', '7102.39.99', 'Los demás.', '22', 'CARA', '0.00', '0.00') - ,('71031001', '7103.10.01', 'En bruto o simplemente aserradas o desbastadas.', '1', 'Kg', '0.00', '0.00') - ,('71039101', '7103.91.01', 'Rubíes, zafiros y esmeraldas.', '22', 'CARA', '0.00', '0.00') - ,('71039999', '7103.99.99', 'Las demás.', '22', 'CARA', '0.00', '0.00') - ,('71041001', '7104.10.01', 'Cuarzo piezoeléctrico.', '1', 'Kg', '25.00', '0.00') - ,('71042101', '7104.21.01', 'Diamantes.', '1', 'Kg', '0.00', '0.00') - ,('71042999', '7104.29.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('71049101', '7104.91.01', 'Diamantes.', '1', 'Kg', '25.00', '0.00') - ,('71049999', '7104.99.99', 'Las demás.', '1', 'Kg', '25.00', '0.00') - ,('71051001', '7105.10.01', 'De diamante.', '22', 'CARA', '0.00', '0.00') - ,('71059099', '7105.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('71061001', '7106.10.01', 'Polvo.', '1', 'Kg', '5.00', '0.00') - ,('71069101', '7106.91.01', 'En bruto.', '1', 'Kg', '0.00', '0.00') - ,('71069201', '7106.92.01', 'Semilabrada.', '1', 'Kg', '5.00', '0.00') - ,('71070001', '7107.00.01', 'Chapado (plaqué) de plata sobre metal común, en bruto o semilabrado.', '1', 'Kg', '15.00', '0.00') - ,('71081101', '7108.11.01', 'Polvo.', '2', 'Gr', '0.00', '0.00') - ,('71081291', '7108.12.91', 'Las demás formas en bruto.', '1', 'Kg', '0.00', '0.00') - ,('71081391', '7108.13.91', 'Las demás formas semilabradas.', '1', 'Kg', '0.00', '0.00') - ,('71082001', '7108.20.01', 'Oro en bruto o semilabrado.', '1', 'Kg', '0.00', '0.00') - ,('71082099', '7108.20.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('71090001', '7109.00.01', 'Chapado (plaqué) de oro sobre metal común o sobre plata, en bruto o semilabrado.', '1', 'Kg', '15.00', '0.00') - ,('71101101', '7110.11.01', 'En bruto o en polvo.', '2', 'Gr', '0.00', '0.00') - ,('71101999', '7110.19.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('71102101', '7110.21.01', 'En bruto o en polvo.', '2', 'Gr', '0.00', '0.00') - ,('71102999', '7110.29.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('71103101', '7110.31.01', 'En bruto o en polvo.', '2', 'Gr', '0.00', '0.00') - ,('71103999', '7110.39.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('71104101', '7110.41.01', 'En bruto o en polvo.', '2', 'Gr', '0.00', '0.00') - ,('71104999', '7110.49.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('71110001', '7111.00.01', 'Chapado (plaqué) de platino sobre metal común, plata u oro, en bruto o semilabrado.', '1', 'Kg', '15.00', '0.00') - ,('71123001', '7112.30.01', 'Cenizas que contengan metal precioso o compuestos de metal precioso.', '2', 'Gr', '0.00', '0.00') - ,('71129102', '7112.91.02', 'Los demás.', '2', 'Gr', '0.00', '0.00') - ,('71129201', '7112.92.01', 'De platino o de chapado (plaqué) de platino, excepto las barreduras que contengan otro metal precioso.', '1', 'Kg', '0.00', '0.00') - ,('71129999', '7112.99.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('71131101', '7113.11.01', 'Sujetadores ("broches") de plata, incluso revestida o chapada de otro metal precioso.', '2', 'Gr', '15.00', '0.00') - ,('71131102', '7113.11.02', 'Cadenas en rollo continuo, de longitud superior o igual a 10 m.', '2', 'Gr', '15.00', '0.00') - ,('71131199', '7113.11.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('71131903', '7113.19.03', 'Cadenas en rollo continuo, de longitud superior o igual a 10 m.', '2', 'Gr', '15.00', '0.00') - ,('71131999', '7113.19.99', 'Los demás.', '2', 'Gr', '0.00', '0.00') - ,('71132001', '7113.20.01', 'De chapado de metal precioso (plaqué) sobre metal común.', '1', 'Kg', '0.00', '0.00') - ,('71141101', '7114.11.01', 'De plata, incluso revestida o chapada de otro metal precioso (plaqué).', '1', 'Kg', '15.00', '0.00') - ,('71141991', '7114.19.91', 'De los demás metales preciosos, incluso revestidos o chapados de metal precioso (plaqué).', '1', 'Kg', '0.00', '0.00') - ,('71142001', '7114.20.01', 'De chapado de metal precioso (plaqué) sobre metal común.', '1', 'Kg', '15.00', '0.00') - ,('71151001', '7115.10.01', 'Catalizadores de platino en forma de tela o enrejado.', '1', 'Kg', '0.00', '0.00') - ,('71159099', '7115.90.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('71161001', '7116.10.01', 'De perlas naturales (finas) o cultivadas.', '1', 'Kg', '5.00', '0.00') - ,('71162001', '7116.20.01', 'De piedras preciosas o semipreciosas (naturales, sintéticas o reconstituidas).', '1', 'Kg', '15.00', '0.00') - ,('71171101', '7117.11.01', 'Gemelos y pasadores similares.', '1', 'Kg', '15.00', '0.00') - ,('71171901', '7117.19.01', 'Cadenas y cadenitas de metales comunes, sin dorar ni platear.', '1', 'Kg', '5.00', '0.00') - ,('71171999', '7117.19.99', 'Las demás.', '1', 'Kg', '15.00', '0.00') - ,('71179001', '7117.90.01', 'Partes o piezas sueltas, de metales comunes, sin dorar o platear, incluso broches.', '1', 'Kg', '0.00', '0.00') - ,('71179002', '7117.90.02', 'De Ámbar de Chiapas.', '1', 'Kg', '10.00', '0.00') - ,('71179099', '7117.90.99', 'Las demás.', '1', 'Kg', '10.00', '0.00') - ,('71181001', '7118.10.01', 'Monedas sin curso legal, excepto las de oro.', '6', 'Pza', '35.00', '0.00') - ,('71189099', '7118.90.99', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('72011001', '7201.10.01', 'Fundición en bruto sin alear con un contenido de fósforo inferior o igual al 0.5% en peso.', '1', 'Kg', '0.00', '0.00') - ,('72012001', '7201.20.01', 'Fundición en bruto sin alear con un contenido de fósforo superior al 0.5% en peso.', '1', 'Kg', '0.00', '0.00') - ,('72015002', '7201.50.02', 'Fundición en bruto aleada; fundición especular.', '1', 'Kg', '0.00', '0.00') - ,('72021101', '7202.11.01', 'Con un contenido de carbono superior al 2% en peso.', '1', 'Kg', '35.00', '0.00') - ,('72021999', '7202.19.99', 'Los demás.', '1', 'Kg', '35.00', '0.00') - ,('72022102', '7202.21.02', 'Con un contenido de silicio superior al 55% en peso.', '1', 'Kg', '0.00', '0.00') - ,('72022999', '7202.29.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('72023001', '7202.30.01', 'Ferro-sílico-manganeso.', '1', 'Kg', '35.00', '0.00') - ,('72024101', '7202.41.01', 'Con un contenido de carbono superior al 4% en peso.', '1', 'Kg', '0.00', '0.00') - ,('72024999', '7202.49.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('72025001', '7202.50.01', 'Ferro-sílico-cromo.', '1', 'Kg', '0.00', '0.00') - ,('72026001', '7202.60.01', 'Ferroníquel.', '1', 'Kg', '0.00', '0.00') - ,('72027001', '7202.70.01', 'Ferromolibdeno.', '1', 'Kg', '0.00', '0.00') - ,('72028001', '7202.80.01', 'Ferrovolframio y ferro-sílico-volframio.', '1', 'Kg', '0.00', '0.00') - ,('72029104', '7202.91.04', 'Ferrotitanio, excepto lo comprendido en el número de identificación comercial 7202.91.04.01.', '1', 'Kg', '0.00', '0.00') - ,('72029202', '7202.92.02', 'Ferrovanadio.', '1', 'Kg', '0.00', '0.00') - ,('72029301', '7202.93.01', 'Ferroniobio.', '1', 'Kg', '0.00', '0.00') - ,('72029999', '7202.99.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('72031001', '7203.10.01', 'Productos férreos obtenidos por reducción directa de minerales de hierro.', '1', 'Kg', '0.00', '0.00') - ,('72039099', '7203.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('72041001', '7204.10.01', 'Desperdicios y desechos, de fundición.', '1', 'Kg', '0.00', '0.00') - ,('72042101', '7204.21.01', 'De acero inoxidable.', '1', 'Kg', '0.00', '0.00') - ,('72042999', '7204.29.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('72043001', '7204.30.01', 'Desperdicios y desechos, de hierro o acero estañados.', '1', 'Kg', '0.00', '0.00') - ,('72044101', '7204.41.01', 'Torneaduras, virutas, esquirlas, limaduras (de amolado, aserrado, limado) y recortes de estampado o de corte, incluso en paquetes.', '1', 'Kg', '0.00', '0.00') - ,('72044999', '7204.49.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('72045001', '7204.50.01', 'Lingotes de chatarra.', '1', 'Kg', '0.00', '0.00') - ,('72051001', '7205.10.01', 'Granallas.', '1', 'Kg', '0.00', '0.00') - ,('72052101', '7205.21.01', 'De aceros aleados.', '1', 'Kg', '0.00', '0.00') - ,('72052999', '7205.29.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('72061001', '7206.10.01', 'Lingotes.', '1', 'Kg', '0.00', '0.00') - ,('72069099', '7206.90.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('72071101', '7207.11.01', 'De sección transversal cuadrada o rectangular, cuya anchura sea inferior al doble del espesor.', '1', 'Kg', '0.00', '0.00') - ,('72071291', '7207.12.91', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('72071999', '7207.19.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('72072002', '7207.20.02', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('72081003', '7208.10.03', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('72082502', '7208.25.02', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('72082601', '7208.26.01', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('72082701', '7208.27.01', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('72083601', '7208.36.01', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('72083701', '7208.37.01', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('72083801', '7208.38.01', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('72083901', '7208.39.01', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('72084002', '7208.40.02', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('72085104', '7208.51.04', 'Acero para la fabricación de tubos de los tipos utilizados en oleoductos o gasoductos.', '1', 'Kg', '25.00', '0.00') - ,('72085201', '7208.52.01', 'De espesor superior o igual a 4.75 mm pero inferior o igual a 10 mm.', '1', 'Kg', '25.00', '0.00') - ,('72085301', '7208.53.01', 'De espesor superior o igual a 3 mm pero inferior a 4.75 mm.', '1', 'Kg', '25.00', '0.00') - ,('72085401', '7208.54.01', 'De espesor inferior a 3 mm.', '1', 'Kg', '25.00', '0.00') - ,('72089099', '7208.90.99', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('72091504', '7209.15.04', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('72091601', '7209.16.01', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('72091701', '7209.17.01', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('72091801', '7209.18.01', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('72092501', '7209.25.01', 'De espesor superior o igual a 3 mm.', '1', 'Kg', '25.00', '0.00') - ,('72092601', '7209.26.01', 'De espesor superior a 1 mm pero inferior a 3 mm.', '1', 'Kg', '25.00', '0.00') - ,('72092701', '7209.27.01', 'De espesor superior o igual a 0.5 mm pero inferior o igual a 1 mm.', '1', 'Kg', '25.00', '0.00') - ,('72092801', '7209.28.01', 'De espesor inferior a 0.5 mm.', '1', 'Kg', '25.00', '0.00') - ,('72099099', '7209.90.99', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('72101101', '7210.11.01', 'De espesor superior o igual a 0.5 mm.', '1', 'Kg', '0.00', '0.00') - ,('72101204', '7210.12.04', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('72102001', '7210.20.01', 'Emplomados, incluidos los revestidos con una aleación de plomo y estaño.', '1', 'Kg', '0.00', '0.00') - ,('72103002', '7210.30.02', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('72104101', '7210.41.01', 'Láminas cincadas por las dos caras.', '1', 'Kg', '25.00', '0.00') - ,('72104199', '7210.41.99', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('72104999', '7210.49.99', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('72105003', '7210.50.03', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('72106101', '7210.61.01', 'Revestidos de aleaciones de aluminio y cinc.', '1', 'Kg', '35.00', '0.00') - ,('72106999', '7210.69.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('72107002', '7210.70.02', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('72109099', '7210.90.99', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('72111301', '7211.13.01', 'Laminados en las cuatro caras o en acanaladuras cerradas, de anchura superior a 150 mm y espesor superior o igual a 4 mm, sin enrollar y sin motivos en relieve.', '1', 'Kg', '25.00', '0.00') - ,('72111491', '7211.14.91', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('72111999', '7211.19.99', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('72112303', '7211.23.03', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('72112999', '7211.29.99', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('72119099', '7211.90.99', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('72121003', '7212.10.03', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('72122003', '7212.20.03', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('72123003', '7212.30.03', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('72124004', '7212.40.04', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('72125001', '7212.50.01', 'Revestidos de otro modo.', '1', 'Kg', '25.00', '0.00') - ,('72126004', '7212.60.04', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('72131001', '7213.10.01', 'Con muescas, cordones, surcos o relieves, producidos en el laminado.', '1', 'Kg', '35.00', '0.00') - ,('72132091', '7213.20.91', 'Los demás, de acero de fácil mecanización.', '1', 'Kg', '25.00', '0.00') - ,('72139103', '7213.91.03', 'Con un contenido de carbono igual o superior a 0.4% en peso.', '1', 'Kg', '50.00', '0.00') - ,('72139999', '7213.99.99', 'Los demás.', '1', 'Kg', '50.00', '0.00') - ,('72141001', '7214.10.01', 'Forjadas.', '1', 'Kg', '0.00', '0.00') - ,('72142001', '7214.20.01', 'Varillas corrugadas o barras para armadura, para cemento u hormigón.', '1', 'Kg', '35.00', '0.00') - ,('72142099', '7214.20.99', 'Los demás.', '1', 'Kg', '35.00', '0.00') - ,('72143091', '7214.30.91', 'Las demás, de acero de fácil mecanización.', '1', 'Kg', '25.00', '0.00') - ,('72149103', '7214.91.03', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('72149999', '7214.99.99', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('72151001', '7215.10.01', 'De acero de fácil mecanización, simplemente obtenidas o acabadas en frío.', '1', 'Kg', '25.00', '0.00') - ,('72155091', '7215.50.91', 'Las demás.', '1', 'Kg', '25.00', '0.00') - ,('72159099', '7215.90.99', 'Las demás.', '1', 'Kg', '25.00', '0.00') - ,('72161001', '7216.10.01', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('72162101', '7216.21.01', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('72162201', '7216.22.01', 'Perfiles en T.', '1', 'Kg', '35.00', '0.00') - ,('72163103', '7216.31.03', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('72163204', '7216.32.04', 'Cuyo patín (ancho de las secciones paralelas) sea superior a 270 mm y su peso sea superior a 190 kg por metro lineal.', '1', 'Kg', '0.00', '0.00') - ,('72163299', '7216.32.99', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('72163301', '7216.33.01', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('72163302', '7216.33.02', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('72164001', '7216.40.01', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('72165001', '7216.50.01', 'Perfiles en forma de Z, cuyo espesor no exceda de 23 cm.', '1', 'Kg', '0.00', '0.00') - ,('72165099', '7216.50.99', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('72166101', '7216.61.01', 'Perfiles en forma de H, I, L, T, U y Z, cuyo espesor no exceda a 23 cm, excepto lo comprendido en la fracción arancelaria 7216.61.02.', '1', 'Kg', '25.00', '0.00') - ,('72166102', '7216.61.02', 'En forma de U e I, cuyo espesor sea superior o igual a 13 cm, sin exceder de 20 cm.', '1', 'Kg', '35.00', '0.00') - ,('72166199', '7216.61.99', 'Los demás.', '1', 'Kg', '35.00', '0.00') - ,('72166999', '7216.69.99', 'Los demás.', '1', 'Kg', '35.00', '0.00') - ,('72169101', '7216.91.01', 'Obtenidos o acabados en frío, a partir de productos laminados planos.', '1', 'Kg', '25.00', '0.00') - ,('72169999', '7216.99.99', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('72171002', '7217.10.02', 'Los demás con un contenido de carbono superior a 0.6% en su peso.', '1', 'Kg', '35.00', '0.00') - ,('72172002', '7217.20.02', 'Los demás.', '1', 'Kg', '35.00', '0.00') - ,('72173002', '7217.30.02', 'Los demás.', '1', 'Kg', '35.00', '0.00') - ,('72179099', '7217.90.99', 'Los demás.', '1', 'Kg', '35.00', '0.00') - ,('72181001', '7218.10.01', 'Lingotes o demás formas primarias.', '1', 'Kg', '0.00', '0.00') - ,('72189101', '7218.91.01', 'De sección transversal rectangular.', '1', 'Kg', '0.00', '0.00') - ,('72189999', '7218.99.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('72191101', '7219.11.01', 'De espesor superior a 10 mm.', '1', 'Kg', '0.00', '0.00') - ,('72191202', '7219.12.02', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('72191301', '7219.13.01', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('72191401', '7219.14.01', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('72192101', '7219.21.01', 'De espesor superior a 10 mm.', '1', 'Kg', '0.00', '0.00') - ,('72192201', '7219.22.01', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('72192301', '7219.23.01', 'De espesor superior o igual a 3 mm pero inferior a 4.75 mm.', '1', 'Kg', '0.00', '0.00') - ,('72192401', '7219.24.01', 'De espesor inferior a 3 mm.', '1', 'Kg', '0.00', '0.00') - ,('72193101', '7219.31.01', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('72193202', '7219.32.02', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('72193301', '7219.33.01', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('72193401', '7219.34.01', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('72193502', '7219.35.02', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('72199099', '7219.90.99', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('72201101', '7220.11.01', 'De espesor superior o igual a 4.75 mm.', '1', 'Kg', '0.00', '0.00') - ,('72201201', '7220.12.01', 'De espesor inferior a 4.75 mm.', '1', 'Kg', '0.00', '0.00') - ,('72202003', '7220.20.03', 'Los demás.', '1', 'Kg', '20.00', '0.00') - ,('72209099', '7220.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('72210001', '7221.00.01', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('72221102', '7222.11.02', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('72221999', '7222.19.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('72222001', '7222.20.01', 'Barras simplemente obtenidas o acabadas en frío.', '1', 'Kg', '0.00', '0.00') - ,('72223091', '7222.30.91', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('72224001', '7222.40.01', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('72230002', '7223.00.02', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('72241006', '7224.10.06', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('72249002', '7224.90.02', 'Productos intermedios, con un contenido de carbono inferior o igual a 0.006% en peso, excepto de acero grado herramienta.', '1', 'Kg', '0.00', '0.00') - ,('72249099', '7224.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('72251101', '7225.11.01', 'De grano orientado.', '1', 'Kg', '0.00', '0.00') - ,('72251999', '7225.19.99', 'Los demás.', '1', 'Kg', '35.00', '0.00') - ,('72253091', '7225.30.91', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('72254091', '7225.40.91', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('72255091', '7225.50.91', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('72259101', '7225.91.01', 'Cincados electrolíticamente.', '1', 'Kg', '25.00', '0.00') - ,('72259201', '7225.92.01', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('72259999', '7225.99.99', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('72261101', '7226.11.01', 'De grano orientado.', '1', 'Kg', '0.00', '0.00') - ,('72261999', '7226.19.99', 'Los demás.', '1', 'Kg', '35.00', '0.00') - ,('72262001', '7226.20.01', 'De acero rápido.', '1', 'Kg', '0.00', '0.00') - ,('72269107', '7226.91.07', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('72269206', '7226.92.06', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('72269999', '7226.99.99', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('72271001', '7227.10.01', 'De acero rápido.', '1', 'Kg', '35.00', '0.00') - ,('72272001', '7227.20.01', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('72279099', '7227.90.99', 'Los demás.', '1', 'Kg', '35.00', '0.00') - ,('72281002', '7228.10.02', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('72282002', '7228.20.02', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('72283001', '7228.30.01', 'En aceros grado herramienta.', '1', 'Kg', '35.00', '0.00') - ,('72283099', '7228.30.99', 'Las demás.', '1', 'Kg', '25.00', '0.00') - ,('72284091', '7228.40.91', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('72285091', '7228.50.91', 'Las demás.', '1', 'Kg', '25.00', '0.00') - ,('72286091', '7228.60.91', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('72287001', '7228.70.01', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('72288001', '7228.80.01', 'Barras huecas para perforación.', '1', 'Kg', '0.00', '0.00') - ,('72292001', '7229.20.01', 'Los demás.', '1', 'Kg', '35.00', '0.00') - ,('72299099', '7229.90.99', 'Los demás.', '1', 'Kg', '35.00', '0.00') - ,('73011001', '7301.10.01', 'Tablestacas.', '1', 'Kg', '0.00', '0.00') - ,('73012001', '7301.20.01', 'Perfiles.', '1', 'Kg', '0.00', '0.00') - ,('73021002', '7302.10.02', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('73023001', '7302.30.01', 'Agujas, puntas de corazón, varillas para mando de agujas y otros elementos para cruce o cambio de vías.', '1', 'Kg', '0.00', '0.00') - ,('73024002', '7302.40.02', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('73029099', '7302.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('73030002', '7303.00.02', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('73041101', '7304.11.01', 'Tubos laminados en caliente, sin recubrimiento u otros trabajos de superficie, incluidos los tubos laminados en caliente barnizados o laqueados: de diámetro exterior inferior o igual a 114.3 mm y espesor de pared superior o igual a 4 mm sin exceder de 19.5 mm.', '1', 'Kg', '5.00', '0.00') - ,('73041102', '7304.11.02', 'Tubos laminados en caliente, sin recubrimiento u otros trabajos de superficie, incluidos los tubos laminados en caliente barnizados o laqueados: de diámetro exterior superior a 114.3 mm sin exceder de 406.4 mm y espesor de pared superior o igual a 6.35 mm sin exceder de 38.1 mm.', '1', 'Kg', '5.00', '0.00') - ,('73041103', '7304.11.03', 'Tubos laminados en caliente, sin recubrimiento u otros trabajos de superficie, incluidos los tubos laminados en caliente barnizados o laqueados: de diámetro exterior superior a 406.4 mm y espesor de pared superior o igual a 9.52 mm sin exceder de 31.75 mm.', '1', 'Kg', '35.00', '0.00') - ,('73041104', '7304.11.04', 'Tubos laminados en frío, sin recubrimiento u otros trabajos de superficie, incluidos los tubos laminados en frío barnizados o laqueados: de diámetro exterior inferior o igual a 114.3 mm y espesor de pared superior o igual a 1.27 mm sin exceder de 9.5 mm.', '1', 'Kg', '5.00', '0.00') - ,('73041199', '7304.11.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('73041901', '7304.19.01', 'De diámetro exterior igual o superior a 60.3mm pero inferior o igual de 114.3mm de acero aleado.', '1', 'Kg', '25.00', '0.00') - ,('73041902', '7304.19.02', 'Los demás.', '1', 'Kg', '35.00', '0.00') - ,('73041903', '7304.19.03', 'Los demás.', '1', 'Kg', '35.00', '0.00') - ,('73041904', '7304.19.04', 'Los demás.', '1', 'Kg', '35.00', '0.00') - ,('73041905', '7304.19.05', 'Tubos semiterminados o esbozos para uso exclusivo de empresas fabricantes de tubería estirada en frío.', '1', 'Kg', '25.00', '0.00') - ,('73041999', '7304.19.99', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('73042204', '7304.22.04', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('73042304', '7304.23.04', 'Tubos de perforación (“Drill pipe”), laminados en caliente, con diámetro exterior superior o igual a 60.3 mm sin exceder de 168.3 mm, con extremos roscados.', '1', 'Kg', '25.00', '0.00') - ,('73042399', '7304.23.99', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('73042491', '7304.24.91', 'Los demás.', '1', 'Kg', '35.00', '0.00') - ,('73042999', '7304.29.99', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('73043101', '7304.31.01', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('73043110', '7304.31.10', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('73043199', '7304.31.99', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('73043901', '7304.39.01', 'Los demás.', '1', 'Kg', '35.00', '0.00') - ,('73043902', '7304.39.02', 'Los demás.', '1', 'Kg', '35.00', '0.00') - ,('73043903', '7304.39.03', 'Barras huecas laminadas en caliente, con diámetro exterior superior a 30 mm sin exceder de 50 mm, así como las de diámetro exterior superior a 300 mm.', '1', 'Kg', '35.00', '0.00') - ,('73043904', '7304.39.04', 'Barras huecas laminadas en caliente, de diámetro exterior superior a 50 mm sin exceder de 300 mm.', '1', 'Kg', '35.00', '0.00') - ,('73043908', '7304.39.08', 'Tubos aletados o con birlos.', '1', 'Kg', '5.00', '0.00') - ,('73043909', '7304.39.09', 'Tubos semiterminados o esbozos, sin recubrimiento u otros trabajos de superficie, de diámetro exterior superior o igual a 20 mm sin exceder de 460 mm y espesor de pared superior o igual a 2.8 mm sin exceder de 35.4 mm, con extremos lisos, biselados, recalcados y/o con rosca y cople.', '1', 'Kg', '35.00', '0.00') - ,('73043910', '7304.39.10', 'Tubos llamados "térmicos", sin recubrimientos distintos de los obtenidos por laqueado y barnizado o sin trabajos de superficie, de diámetro exterior inferior o igual a 114.3 mm y espesor de pared superior o igual a 4 mm, sin exceder 19.5 mm.', '1', 'Kg', '25.00', '0.00') - ,('73043911', '7304.39.11', 'Tubos llamados de "conducción", sin recubrimientos distintos de los obtenidos por laqueado y barnizado o sin trabajos de superficie, de diámetro exterior inferior o igual a 114.3 mm y espesor de pared superior o igual a 4 mm, sin exceder 19.5 mm.', '1', 'Kg', '35.00', '0.00') - ,('73043912', '7304.39.12', 'Tubos llamados "térmicos", sin recubrimientos distintos de los obtenidos por laqueado y barnizado o sin trabajos de superficie, de diámetro exterior superior a 114.3 mm sin exceder de 406.4 mm y espesor de pared superior o igual a 6.35 mm sin exceder de 38.1 mm.', '1', 'Kg', '25.00', '0.00') - ,('73043913', '7304.39.13', 'Tubos llamados de "conducción", sin recubrimientos distintos de los obtenidos por laqueado y barnizado o sin trabajos de superficie, de diámetro exterior superior a 114.3 mm sin exceder de 406.4 mm y espesor de pared superior o igual a 6.35 mm sin exceder de 38.1 mm.', '1', 'Kg', '35.00', '0.00') - ,('73043914', '7304.39.14', 'Tubos llamados "térmicos", sin recubrimientos distintos de los obtenidos por laqueado y barnizado o sin trabajos de superficie, de diámetro exterior superior a 406.4 mm y espesor de pared superior o igual a 9.52 mm sin exceder de 31.75 mm.', '1', 'Kg', '25.00', '0.00') - ,('73043915', '7304.39.15', 'Tubos llamados de "conducción", sin recubrimientos distintos de los obtenidos por laqueado y barnizado o sin trabajos de superficie, de diámetro exterior superior a 406.4 mm y espesor de pared superior o igual a 9.52 mm sin exceder de 31.75 mm.', '1', 'Kg', '35.00', '0.00') - ,('73043916', '7304.39.16', 'Diseñados para su uso en calderas, sobrecalentadores, intercambiadores de calor, condensadores, hornos de refinación, calentadores de agua u otros similares, excepto lo comprendido en las fracciones arancelarias 7304.39.10, 7304.39.12 y 7304.39.14.', '1', 'Kg', '25.00', '0.00') - ,('73043991', '7304.39.91', 'Los demás de diámetro exterior superior o igual a 38.1 mm, pero inferior o igual a 406.4 mm, con un espesor de pared superior a 12.7 mm.', '1', 'Kg', '35.00', '0.00') - ,('73043992', '7304.39.92', 'Los demás de diámetro exterior superior o igual a 38.1 mm, pero inferior o igual a 114.3 mm, con un espesor de pared superior a 6.4 mm pero inferior o igual a 12.7 mm.', '1', 'Kg', '35.00', '0.00') - ,('73043999', '7304.39.99', 'Los demás.', '1', 'Kg', '35.00', '0.00') - ,('73044103', '7304.41.03', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('73044999', '7304.49.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('73045112', '7304.51.12', 'Los demás.', '1', 'Kg', '35.00', '0.00') - ,('73045909', '7304.59.09', 'Tubos aletados o con birlos.', '1', 'Kg', '35.00', '0.00') - ,('73045999', '7304.59.99', 'Los demás.', '1', 'Kg', '35.00', '0.00') - ,('73049099', '7304.90.99', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('73051102', '7305.11.02', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('73051291', '7305.12.91', 'Los demás.', '1', 'Kg', '35.00', '0.00') - ,('73051999', '7305.19.99', 'Los demás.', '1', 'Kg', '35.00', '0.00') - ,('73052001', '7305.20.01', 'Con espesor de pared inferior a 50.8 mm.', '1', 'Kg', '35.00', '0.00') - ,('73052099', '7305.20.99', 'Los demás.', '1', 'Kg', '35.00', '0.00') - ,('73053101', '7305.31.01', 'Galvanizados.', '1', 'Kg', '25.00', '0.00') - ,('73053102', '7305.31.02', 'De acero inoxidable con diámetro exterior superior a 1,220 mm.', '1', 'Kg', '0.00', '0.00') - ,('73053103', '7305.31.03', 'Tubos aletados o con birlos.', '1', 'Kg', '35.00', '0.00') - ,('73053104', '7305.31.04', 'Con paredes ranuradas de cualquier tipo o forma, aun cuando se presenten con recubrimientos anticorrosivos.', '1', 'Kg', '35.00', '0.00') - ,('73053105', '7305.31.05', 'Con espesor de pared superior a 50.8 mm.', '1', 'Kg', '25.00', '0.00') - ,('73053106', '7305.31.06', 'Conducciones forzadas de acero, incluso con zunchos, del tipo utilizado en instalaciones hidroeléctricas.', '1', 'Kg', '35.00', '0.00') - ,('73053191', '7305.31.91', 'Los demás de acero inoxidable.', '1', 'Kg', '25.00', '0.00') - ,('73053199', '7305.31.99', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('73053901', '7305.39.01', 'Galvanizados.', '1', 'Kg', '25.00', '0.00') - ,('73053902', '7305.39.02', 'De acero inoxidable con diámetro exterior superior a 1,220 mm.', '1', 'Kg', '0.00', '0.00') - ,('73053903', '7305.39.03', 'Tubos aletados o con birlos.', '1', 'Kg', '35.00', '0.00') - ,('73053904', '7305.39.04', 'Con espesor de pared superior a 50.8 mm.', '1', 'Kg', '35.00', '0.00') - ,('73053905', '7305.39.05', 'Conducciones forzadas de acero, incluso con zunchos, del tipo utilizado en instalaciones hidroeléctricas.', '1', 'Kg', '35.00', '0.00') - ,('73053999', '7305.39.99', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('73059099', '7305.90.99', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('73061101', '7306.11.01', 'Soldados, de acero inoxidable.', '1', 'Kg', '5.00', '0.00') - ,('73061999', '7306.19.99', 'Los demás.', '1', 'Kg', '35.00', '0.00') - ,('73062101', '7306.21.01', 'Soldados, de acero inoxidable.', '1', 'Kg', '5.00', '0.00') - ,('73062999', '7306.29.99', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('73063002', '7306.30.02', 'Tubo de acero, al bajo carbono, galvanizado por inmersión, con diámetro exterior superior o igual a 3.92 mm pero inferior o igual a 4.08 mm, y espesor de pared superior o igual a 0.51 mm pero inferior o igual a 0.77 mm.', '1', 'Kg', '35.00', '0.00') - ,('73063003', '7306.30.03', 'Galvanizados, con un espesor de pared inferior a 1.65 mm, excepto lo comprendido en la fracción arancelaria 7306.30.02.', '1', 'Kg', '35.00', '0.00') - ,('73063004', '7306.30.04', 'Galvanizados, con un espesor de pared superior o igual a 1.65 mm, excepto lo comprendido en la fracción arancelaria 7306.30.02.', '1', 'Kg', '35.00', '0.00') - ,('73063099', '7306.30.99', 'Los demás.', '1', 'Kg', '35.00', '0.00') - ,('73064001', '7306.40.01', 'Tubos de diámetro superior o igual a 0.20 mm pero inferior a 1.5 mm.', '1', 'Kg', '0.00', '0.00') - ,('73064099', '7306.40.99', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('73065001', '7306.50.01', 'De hierro o acero, cobrizados, de doble pared soldados por fusión (proceso “brazing”) con o sin recubrimiento anticorrosivo.', '1', 'Kg', '0.00', '0.00') - ,('73065099', '7306.50.99', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('73066101', '7306.61.01', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('73066999', '7306.69.99', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('73069099', '7306.90.99', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('73071102', '7307.11.02', 'De fundición no maleable.', '1', 'Kg', '0.00', '0.00') - ,('73071999', '7307.19.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('73072101', '7307.21.01', 'Bridas.', '1', 'Kg', '0.00', '0.00') - ,('73072202', '7307.22.02', 'Codos, curvas y manguitos, roscados.', '1', 'Kg', '35.00', '0.00') - ,('73072301', '7307.23.01', 'Mangas o sillas sin rosca.', '1', 'Kg', '0.00', '0.00') - ,('73072399', '7307.23.99', 'Los demás.', '1', 'Kg', '35.00', '0.00') - ,('73072999', '7307.29.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('73079101', '7307.91.01', 'Bridas.', '1', 'Kg', '0.00', '0.00') - ,('73079202', '7307.92.02', 'Codos, curvas y manguitos, roscados.', '1', 'Kg', '0.00', '0.00') - ,('73079301', '7307.93.01', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('73079999', '7307.99.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('73081001', '7308.10.01', 'Puentes y sus partes.', '1', 'Kg', '5.00', '0.00') - ,('73082002', '7308.20.02', 'Los demás.', '1', 'Kg', '35.00', '0.00') - ,('73083002', '7308.30.02', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('73084001', '7308.40.01', 'Material de andamiaje, encofrado, apeo o apuntalamiento.', '1', 'Kg', '5.00', '0.00') - ,('73089001', '7308.90.01', 'Barandales; balcones; escaleras.', '1', 'Kg', '7.00', '0.00') - ,('73089002', '7308.90.02', 'Estructuras desarmadas consistentes en armaduras, columnas y sus placas de asiento, ménsulas, planchas de unión, tensores y tirantes, aun cuando se presenten con tuercas y demás partes para la construcción.', '1', 'Kg', '25.00', '0.00') - ,('73089099', '7308.90.99', 'Los demás.', '1', 'Kg', '35.00', '0.00') - ,('73090004', '7309.00.04', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('73101005', '7310.10.05', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('73102101', '7310.21.01', 'Latas o botes para ser cerrados por soldadura o rebordeado.', '1', 'Kg', '35.00', '0.00') - ,('73102901', '7310.29.01', 'Barriles o tambores, excepto lo comprendido en la fracción arancelaria 7310.29.05.', '1', 'Kg', '0.00', '0.00') - ,('73102905', '7310.29.05', 'Barriles de acero inoxidable reconocibles como diseñados exclusivamente para cerveza.', '6', 'Pza', '0.00', '0.00') - ,('73102999', '7310.29.99', 'Los demás.', '1', 'Kg', '35.00', '0.00') - ,('73110005', '7311.00.05', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('73121001', '7312.10.01', 'Galvanizados, con diámetro mayor de 4 mm, constituidos por más de 5 alambres y con núcleos sin torcer de la misma materia, excepto los comprendidos en la fracción arancelaria 7312.10.07.', '1', 'Kg', '25.00', '0.00') - ,('73121005', '7312.10.05', 'Los demás.', '1', 'Kg', '35.00', '0.00') - ,('73121007', '7312.10.07', 'Galvanizados, con un diámetro mayor a 4 mm pero inferior a 19 mm, constituidos por 7 alambres, lubricados o sin lubricar.', '1', 'Kg', '25.00', '0.00') - ,('73121008', '7312.10.08', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('73121099', '7312.10.99', 'Los demás.', '1', 'Kg', '35.00', '0.00') - ,('73129099', '7312.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('73130001', '7313.00.01', 'Alambre de púas, de hierro o acero; alambre (simple o doble) y fleje, torcidos, incluso con púas, de hierro o acero, de los tipos utilizados para cercar.', '1', 'Kg', '35.00', '0.00') - ,('73141201', '7314.12.01', 'Telas metálicas continuas o sin fin, de acero inoxidable, para máquinas.', '1', 'Kg', '0.00', '0.00') - ,('73141491', '7314.14.91', 'Las demás telas metálicas tejidas, de acero inoxidable.', '1', 'Kg', '0.00', '0.00') - ,('73141903', '7314.19.03', 'Las demás.', '1', 'Kg', '35.00', '0.00') - ,('73141999', '7314.19.99', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('73142001', '7314.20.01', 'Redes y rejas, soldadas en los puntos de cruce, de alambre cuya mayor dimensión de la sección transversal sea superior o igual a 3 mm y con malla de superficie superior o igual a 100 cm².', '1', 'Kg', '35.00', '0.00') - ,('73143101', '7314.31.01', 'Las demás.', '1', 'Kg', '25.00', '0.00') - ,('73143999', '7314.39.99', 'Las demás.', '1', 'Kg', '25.00', '0.00') - ,('73144101', '7314.41.01', 'Las demás.', '1', 'Kg', '25.00', '0.00') - ,('73144201', '7314.42.01', 'Revestidas de plástico.', '1', 'Kg', '25.00', '0.00') - ,('73144999', '7314.49.99', 'Las demás.', '1', 'Kg', '35.00', '0.00') - ,('73145001', '7314.50.01', 'Chapas y tiras, extendidas (desplegadas).', '1', 'Kg', '35.00', '0.00') - ,('73151106', '7315.11.06', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('73151291', '7315.12.91', 'Las demás.', '1', 'Kg', '35.00', '0.00') - ,('73151904', '7315.19.04', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('73152001', '7315.20.01', 'Cadenas antideslizantes.', '1', 'Kg', '0.00', '0.00') - ,('73158103', '7315.81.03', 'Las demás.', '1', 'Kg', '25.00', '0.00') - ,('73158291', '7315.82.91', 'Las demás.', '1', 'Kg', '35.00', '0.00') - ,('73158999', '7315.89.99', 'Las demás.', '1', 'Kg', '35.00', '0.00') - ,('73159091', '7315.90.91', 'Las demás partes.', '1', 'Kg', '35.00', '0.00') - ,('73160001', '7316.00.01', 'Anclas, rezones y sus partes, de fundición, hierro o acero.', '1', 'Kg', '0.00', '0.00') - ,('73170001', '7317.00.01', 'Clavos para herrar.', '1', 'Kg', '30.00', '0.00') - ,('73170002', '7317.00.02', 'Púas o dientes para cardar.', '1', 'Kg', '35.00', '0.00') - ,('73170003', '7317.00.03', 'Placas con puntas en una de sus superficies, para ensamblar piezas de madera.', '1', 'Kg', '0.00', '0.00') - ,('73170004', '7317.00.04', 'Puntas o escarpias puntiagudas, reconocibles como diseñadas exclusivamente para preparar (raspar) llantas para su vulcanización.', '1', 'Kg', '35.00', '0.00') - ,('73170099', '7317.00.99', 'Los demás.', '1', 'Kg', '35.00', '0.00') - ,('73181101', '7318.11.01', 'Tirafondos.', '1', 'Kg', '35.00', '0.00') - ,('73181291', '7318.12.91', 'Los demás tornillos para madera.', '1', 'Kg', '35.00', '0.00') - ,('73181301', '7318.13.01', 'Escarpias y armellas, roscadas.', '1', 'Kg', '35.00', '0.00') - ,('73181401', '7318.14.01', 'Tornillos taladradores.', '1', 'Kg', '35.00', '0.00') - ,('73181504', '7318.15.04', 'Tornillos con diámetro inferior a 6.4 mm (¼ pulgada) y longitud inferior a 50.8 mm (2 pulgadas), excepto las de acero inoxidable y las reconocibles para naves aéreas o uso automotriz.', '1', 'Kg', '35.00', '0.00') - ,('73181599', '7318.15.99', 'Los demás.', '1', 'Kg', '35.00', '0.00') - ,('73181606', '7318.16.06', 'De diámetro superior a 38.1 mm (1½ pulgadas), excepto lo comprendido en los números de identificación comercial 7318.16.06.01 y 7318.16.06.02.', '1', 'Kg', '35.00', '0.00') - ,('73181999', '7318.19.99', 'Los demás.', '1', 'Kg', '35.00', '0.00') - ,('73182102', '7318.21.02', 'Arandelas de muelle (resorte) y las demás de seguridad.', '1', 'Kg', '0.00', '0.00') - ,('73182291', '7318.22.91', 'Las demás.', '1', 'Kg', '35.00', '0.00') - ,('73182302', '7318.23.02', 'Los demás.', '1', 'Kg', '35.00', '0.00') - ,('73182403', '7318.24.03', 'Pasadores y chavetas.', '1', 'Kg', '35.00', '0.00') - ,('73182999', '7318.29.99', 'Los demás.', '1', 'Kg', '35.00', '0.00') - ,('73194001', '7319.40.01', 'Alfileres de gancho (imperdibles) y demás alfileres.', '1', 'Kg', '10.00', '0.00') - ,('73199001', '7319.90.01', 'Agujas de coser, zurcir o bordar.', '1', 'Kg', '5.00', '0.00') - ,('73199099', '7319.90.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('73201002', '7320.10.02', 'Ballestas y sus hojas.', '1', 'Kg', '0.00', '0.00') - ,('73202005', '7320.20.05', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('73209099', '7320.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('73211101', '7321.11.01', 'Cocinas que consuman combustibles gaseosos.', '6', 'Pza', '35.00', '0.00') - ,('73211191', '7321.11.91', 'Las demás cocinas, excepto portátiles.', '6', 'Pza', '35.00', '0.00') - ,('73211199', '7321.11.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('73211201', '7321.12.01', 'De combustibles líquidos.', '6', 'Pza', '15.00', '0.00') - ,('73211991', '7321.19.91', 'Los demás, incluidos los aparatos de combustibles sólidos.', '6', 'Pza', '15.00', '0.00') - ,('73218102', '7321.81.02', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('73218202', '7321.82.02', 'De combustibles líquidos.', '6', 'Pza', '15.00', '0.00') - ,('73218991', '7321.89.91', 'Los demás, incluidos los aparatos de combustibles sólidos.', '6', 'Pza', '15.00', '0.00') - ,('73219001', '7321.90.01', 'Rosticeros accionados por motor eléctrico, reconocibles como diseñados exclusivamente para cocinas de uso doméstico.', '1', 'Kg', '15.00', '0.00') - ,('73219002', '7321.90.02', 'Reconocibles como diseñadas exclusivamente para cocinas que consuman combustibles gaseosos, excepto lo comprendido en las fracciones arancelarias 7321.90.01 y 7321.90.03.', '1', 'Kg', '0.00', '0.00') - ,('73219003', '7321.90.03', 'Quemadores troquelados o estampados, de lámina de acero, reconocibles como diseñados exclusivamente para cocinas y/o hornos que consuman combustibles gaseosos.', '1', 'Kg', '0.00', '0.00') - ,('73219004', '7321.90.04', 'Esparcidores de flama y galerías, reconocibles como diseñados exclusivamente para estufas o caloríferos portátiles, que consuman petróleo.', '1', 'Kg', '0.00', '0.00') - ,('73219005', '7321.90.05', 'Cámaras de cocción, incluso sin ensamblar, reconocibles como diseñadas exclusivamente para lo comprendido en la fracción arancelaria 7321.11.91.', '1', 'Kg', '0.00', '0.00') - ,('73219006', '7321.90.06', 'Panel superior con o sin controles, con o sin quemadores, reconocibles como diseñados exclusivamente para lo comprendido en la fracción arancelaria 7321.11.91.', '1', 'Kg', '0.00', '0.00') - ,('73219007', '7321.90.07', 'Ensambles de puertas, que incorporen más de uno de los siguientes componentes: paredes interiores, paredes exteriores, ventana, aislamiento, reconocibles como diseñados exclusivamente para lo comprendido en la fracción arancelaria 7321.11.91.', '1', 'Kg', '0.00', '0.00') - ,('73219099', '7321.90.99', 'Los demás.', '1', 'Kg', '35.00', '0.00') - ,('73221102', '7322.11.02', 'De fundición.', '1', 'Kg', '15.00', '0.00') - ,('73221901', '7322.19.01', 'Para naves aéreas.', '1', 'Kg', '25.00', '0.00') - ,('73221999', '7322.19.99', 'Los demás.', '1', 'Kg', '35.00', '0.00') - ,('73229001', '7322.90.01', 'Reconocibles para naves aéreas, incluidas sus partes.', '1', 'Kg', '0.00', '0.00') - ,('73229099', '7322.90.99', 'Los demás.', '1', 'Kg', '35.00', '0.00') - ,('73231001', '7323.10.01', 'Lana de hierro o acero; esponjas, estropajos, guantes y artículos similares para fregar, lustrar o usos análogos.', '1', 'Kg', '35.00', '0.00') - ,('73239103', '7323.91.03', 'De fundición, sin esmaltar.', '1', 'Kg', '15.00', '0.00') - ,('73239204', '7323.92.04', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('73239305', '7323.93.05', 'De acero inoxidable.', '1', 'Kg', '35.00', '0.00') - ,('73239405', '7323.94.05', 'Los demás.', '1', 'Kg', '35.00', '0.00') - ,('73239999', '7323.99.99', 'Los demás.', '1', 'Kg', '35.00', '0.00') - ,('73241001', '7324.10.01', 'Fregaderos (piletas de lavar) y lavabos, de acero inoxidable.', '1', 'Kg', '25.00', '0.00') - ,('73242101', '7324.21.01', 'De fundición, incluso esmaltadas.', '1', 'Kg', '25.00', '0.00') - ,('73242999', '7324.29.99', 'Las demás.', '1', 'Kg', '25.00', '0.00') - ,('73249091', '7324.90.91', 'Los demás.', '1', 'Kg', '35.00', '0.00') - ,('73251005', '7325.10.05', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('73259103', '7325.91.03', 'Las demás.', '1', 'Kg', '35.00', '0.00') - ,('73259999', '7325.99.99', 'Las demás.', '1', 'Kg', '35.00', '0.00') - ,('73261103', '7326.11.03', 'Los demás.', '1', 'Kg', '35.00', '0.00') - ,('73261999', '7326.19.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('73262006', '7326.20.06', 'Los demás.', '1', 'Kg', '30.00', '0.00') - ,('73269099', '7326.90.99', 'Las demás.', '1', 'Kg', '35.00', '0.00') - ,('74010003', '7401.00.03', 'Cobre de cementación (cobre precipitado).', '1', 'Kg', '0.00', '0.00') - ,('74020001', '7402.00.01', 'Cobre sin refinar; ánodos de cobre para refinado electrolítico.', '1', 'Kg', '0.00', '0.00') - ,('74031101', '7403.11.01', 'Cátodos y secciones de cátodos.', '1', 'Kg', '0.00', '0.00') - ,('74031201', '7403.12.01', 'Barras para alambrón ("wire-bars").', '1', 'Kg', '0.00', '0.00') - ,('74031301', '7403.13.01', 'Tochos.', '1', 'Kg', '0.00', '0.00') - ,('74031999', '7403.19.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('74032101', '7403.21.01', 'A base de cobre-cinc (latón).', '1', 'Kg', '0.00', '0.00') - ,('74032201', '7403.22.01', 'A base de cobre-estaño (bronce).', '1', 'Kg', '0.00', '0.00') - ,('74032991', '7403.29.91', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('74040003', '7404.00.03', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('74050001', '7405.00.01', 'Aleaciones madre de cobre.', '1', 'Kg', '0.00', '0.00') - ,('74061001', '7406.10.01', 'Polvo de estructura no laminar.', '1', 'Kg', '0.00', '0.00') - ,('74062001', '7406.20.01', 'Polvo de estructura laminar; escamillas.', '1', 'Kg', '0.00', '0.00') - ,('74071001', '7407.10.01', 'Barras.', '1', 'Kg', '5.00', '0.00') - ,('74071099', '7407.10.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('74072102', '7407.21.02', 'Perfiles huecos.', '1', 'Kg', '0.00', '0.00') - ,('74072199', '7407.21.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('74072999', '7407.29.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('74081102', '7408.11.02', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('74081901', '7408.19.01', 'De cobre libres de oxígeno, con pureza superior o igual al 99.22%, de diámetro inferior o igual a 1 mm, con o sin recubrimiento de níquel, reconocibles para la fabricación de electrodos para cátodos de encendido de focos, tubos de descarga o tubos de rayos catódicos.', '1', 'Kg', '0.00', '0.00') - ,('74081902', '7408.19.02', 'Con recubrimiento de plata inferior o igual al 2% (plateado), inclusive, con diámetro de 0.08 mm a 1 mm.', '1', 'Kg', '0.00', '0.00') - ,('74081999', '7408.19.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('74082101', '7408.21.01', 'A base de cobre-cinc (latón).', '1', 'Kg', '0.00', '0.00') - ,('74082201', '7408.22.01', 'A base de cobre-níquel (cuproníquel) de cualquier diámetro, o a base de cobre-níquel-cinc (alpaca) de diámetro superior o igual a 0.5 mm.', '1', 'Kg', '5.00', '0.00') - ,('74082299', '7408.22.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('74082999', '7408.29.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('74091101', '7409.11.01', 'Enrolladas.', '1', 'Kg', '0.00', '0.00') - ,('74091999', '7409.19.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('74092101', '7409.21.01', 'Enrolladas.', '1', 'Kg', '0.00', '0.00') - ,('74092999', '7409.29.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('74093101', '7409.31.01', 'Enrolladas.', '1', 'Kg', '0.00', '0.00') - ,('74093999', '7409.39.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('74094001', '7409.40.01', 'De aleaciones a base de cobre-níquel (cuproníquel) o de cobre-níquel-cinc (alpaca).', '1', 'Kg', '0.00', '0.00') - ,('74099091', '7409.90.91', 'De las demás aleaciones de cobre.', '1', 'Kg', '0.00', '0.00') - ,('74101101', '7410.11.01', 'De cobre refinado.', '1', 'Kg', '0.00', '0.00') - ,('74101201', '7410.12.01', 'De aleaciones de cobre.', '1', 'Kg', '25.00', '0.00') - ,('74102104', '7410.21.04', 'De cobre refinado.', '1', 'Kg', '0.00', '0.00') - ,('74102201', '7410.22.01', 'De aleaciones de cobre.', '1', 'Kg', '0.00', '0.00') - ,('74111001', '7411.10.01', 'Con espesor de pared inferior o igual a 3 mm, excepto serpentines.', '1', 'Kg', '5.00', '0.00') - ,('74111099', '7411.10.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('74112101', '7411.21.01', 'Con espesor de pared inferior o igual a 3 mm, excepto serpentines y lo comprendido en la fracción arancelaria 7411.21.05.', '1', 'Kg', '5.00', '0.00') - ,('74112105', '7411.21.05', 'Sin costura, con espesor de pared superior o igual a 0.20 mm pero inferior o igual a 0.40 mm, para instalaciones sanitarias.', '1', 'Kg', '35.00', '0.00') - ,('74112199', '7411.21.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('74112201', '7411.22.01', 'Con espesor de pared inferior o igual a 3 mm, excepto serpentines.', '1', 'Kg', '5.00', '0.00') - ,('74112202', '7411.22.02', 'Con espesor de pared superior a 3 mm, sin exceder de 15 mm, excepto serpentines.', '1', 'Kg', '5.00', '0.00') - ,('74112299', '7411.22.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('74112901', '7411.29.01', 'Con espesor de pared inferior o igual a 3 mm, excepto serpentines.', '1', 'Kg', '5.00', '0.00') - ,('74112999', '7411.29.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('74121001', '7412.10.01', 'De cobre refinado.', '1', 'Kg', '5.00', '0.00') - ,('74122001', '7412.20.01', 'De aleaciones de cobre.', '1', 'Kg', '5.00', '0.00') - ,('74130002', '7413.00.02', 'Cables, trenzas y artículos similares, de cobre, sin aislar para electricidad.', '1', 'Kg', '5.00', '0.00') - ,('74151002', '7415.10.02', 'Puntas y clavos, chinchetas (chinches), grapas apuntadas y artículos similares.', '1', 'Kg', '0.00', '0.00') - ,('74152101', '7415.21.01', 'Arandelas (incluidas las arandelas de muelle (resorte)).', '1', 'Kg', '0.00', '0.00') - ,('74152999', '7415.29.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('74153303', '7415.33.03', 'Tornillos; pernos y tuercas.', '1', 'Kg', '0.00', '0.00') - ,('74153999', '7415.39.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('74181001', '7418.10.01', 'Artículos de uso doméstico y sus partes; esponjas, estropajos, guantes y artículos similares para fregar, lustrar o usos análogos.', '1', 'Kg', '15.00', '0.00') - ,('74182001', '7418.20.01', 'Artículos de higiene o tocador, y sus partes.', '1', 'Kg', '15.00', '0.00') - ,('74192001', '7419.20.01', 'Cadenas y sus partes.', '1', 'Kg', '0.00', '0.00') - ,('74192002', '7419.20.02', 'Terminales para cables.', '1', 'Kg', '15.00', '0.00') - ,('74192003', '7419.20.03', 'Accesorios para tendidos aéreos eléctricos.', '1', 'Kg', '25.00', '0.00') - ,('74192004', '7419.20.04', 'Reconocibles para naves aéreas.', '1', 'Kg', '0.00', '0.00') - ,('74192005', '7419.20.05', 'Electrodos circulares para máquinas o aparatos de soldar.', '1', 'Kg', '0.00', '0.00') - ,('74192006', '7419.20.06', 'Terminales reconocibles como diseñadas exclusivamente para resistencias no calentadoras.', '1', 'Kg', '25.00', '0.00') - ,('74192007', '7419.20.07', 'Arillos o cospeles, reconocibles como diseñados exclusivamente para la acuñación de monedas.', '1', 'Kg', '0.00', '0.00') - ,('74192099', '7419.20.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('74198001', '7419.80.01', 'Cadenas y sus partes.', '1', 'Kg', '0.00', '0.00') - ,('74198002', '7419.80.02', 'Terminales para cables.', '1', 'Kg', '15.00', '0.00') - ,('74198003', '7419.80.03', 'Accesorios para tendidos aéreos eléctricos.', '1', 'Kg', '15.00', '0.00') - ,('74198099', '7419.80.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('75011001', '7501.10.01', 'Matas de níquel.', '1', 'Kg', '0.00', '0.00') - ,('75012001', '7501.20.01', 'Sinters de óxidos de níquel y demás productos intermedios de la metalurgia del níquel.', '1', 'Kg', '0.00', '0.00') - ,('75021001', '7502.10.01', 'Níquel sin alear.', '1', 'Kg', '0.00', '0.00') - ,('75022001', '7502.20.01', 'Aleaciones de níquel.', '1', 'Kg', '0.00', '0.00') - ,('75030001', '7503.00.01', 'Desperdicios y desechos, de níquel.', '1', 'Kg', '0.00', '0.00') - ,('75040001', '7504.00.01', 'Polvo y escamillas, de níquel.', '1', 'Kg', '0.00', '0.00') - ,('75051101', '7505.11.01', 'De níquel sin alear.', '1', 'Kg', '0.00', '0.00') - ,('75051201', '7505.12.01', 'De aleaciones de níquel.', '1', 'Kg', '0.00', '0.00') - ,('75052101', '7505.21.01', 'De níquel sin alear.', '1', 'Kg', '0.00', '0.00') - ,('75052201', '7505.22.01', 'De aleaciones de níquel.', '1', 'Kg', '0.00', '0.00') - ,('75061002', '7506.10.02', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('75062002', '7506.20.02', 'De aleaciones de níquel.', '1', 'Kg', '0.00', '0.00') - ,('75071101', '7507.11.01', 'De níquel sin alear.', '1', 'Kg', '0.00', '0.00') - ,('75071201', '7507.12.01', 'De aleaciones de níquel.', '1', 'Kg', '0.00', '0.00') - ,('75072001', '7507.20.01', 'Accesorios de tubería.', '1', 'Kg', '0.00', '0.00') - ,('75081001', '7508.10.01', 'Telas metálicas, redes y rejas, de alambre de níquel.', '1', 'Kg', '25.00', '0.00') - ,('75089099', '7508.90.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('76011002', '7601.10.02', 'Los demás.', '1', 'Kg', '35.00', '0.00') - ,('76012002', '7601.20.02', 'Las demás.', '1', 'Kg', '20.00', '0.00') - ,('76020002', '7602.00.02', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('76031001', '7603.10.01', 'Polvo de estructura no laminar.', '1', 'Kg', '0.00', '0.00') - ,('76032001', '7603.20.01', 'Polvo de estructura laminar; escamillas.', '1', 'Kg', '0.00', '0.00') - ,('76041001', '7604.10.01', 'Con pureza mínima de 99.5%, diámetro superior o igual a 9 mm, para la fabricación de conductores eléctricos.', '1', 'Kg', '0.00', '0.00') - ,('76041002', '7604.10.02', 'Perfiles.', '1', 'Kg', '30.00', '0.00') - ,('76041099', '7604.10.99', 'Los demás.', '1', 'Kg', '35.00', '0.00') - ,('76042101', '7604.21.01', 'Perfiles huecos.', '1', 'Kg', '25.00', '0.00') - ,('76042901', '7604.29.01', 'Barras de aluminio, con un contenido de: hierro de 0.7%, silicio superior o igual al 0.4% pero inferior o igual al 0.8%, cobre superior o igual 0.15% pero inferior o igual al 0.40%, magnesio superior o igual al 0.8% pero inferior o igual al 1.2%, cromo superior o igual al 0.04% pero inferior o igual al 0.35%, además de los otros elementos, en peso.', '1', 'Kg', '25.00', '0.00') - ,('76042902', '7604.29.02', 'Perfiles.', '1', 'Kg', '25.00', '0.00') - ,('76042999', '7604.29.99', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('76051101', '7605.11.01', 'De aluminio con pureza superior o igual de 99.5% y diámetro superior o igual a 9 mm, para la fabricación de conductores eléctricos.', '1', 'Kg', '3.00', '0.00') - ,('76051199', '7605.11.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('76051999', '7605.19.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('76052101', '7605.21.01', 'Con un contenido de: hierro de 0.7%, silicio superior o igual al 0.4% pero inferior o igual al 0.8%, cobre superior o igual 0.15% pero inferior o igual al 0.40%, magnesio superior o igual al 0.8% pero inferior o igual al 1.2%, cromo superior o igual al 0.04% pero inferior o igual al 0.35%, además de los otros elementos, en peso.', '1', 'Kg', '3.00', '0.00') - ,('76052102', '7605.21.02', 'De aleación 5056.', '1', 'Kg', '0.00', '0.00') - ,('76052199', '7605.21.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('76052901', '7605.29.01', 'Con un contenido de: hierro de 0.7%, silicio superior o igual al 0.4% pero inferior o igual al 0.8%, cobre superior o igual 0.15% pero inferior o igual al 0.40%, magnesio superior o igual al 0.8% pero inferior o igual al 1.2%, cromo superior o igual al 0.04% pero inferior o igual al 0.35%, además de los otros elementos, en peso.', '1', 'Kg', '0.00', '0.00') - ,('76052902', '7605.29.02', 'De la aleación A2011TD, según Norma JIS-H-4040, o sus equivalentes.', '1', 'Kg', '0.00', '0.00') - ,('76052999', '7605.29.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('76061101', '7606.11.01', 'Hojas o tiras o chapas en rollos, con un contenido de aluminio superior o igual al 93%, con resistencia a la tensión superior o igual a 2,812 kg/cm² y con elongación superior o igual al 1% en 5 cm de longitud, reconocibles exclusivamente para la elaboración de envases para bebidas o alimentos.', '1', 'Kg', '0.00', '0.00') - ,('76061199', '7606.11.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('76061201', '7606.12.01', 'Hojas o tiras o chapas en rollos, con un contenido de aluminio superior o igual al 93%, con resistencia a la tensión superior o igual a 2,812 kg/cm² y con elongación superior o igual al 1% en 5 cm de longitud, reconocibles exclusivamente para la elaboración de envases para bebidas o alimentos.', '1', 'Kg', '0.00', '0.00') - ,('76061202', '7606.12.02', 'Reconocibles para fuselaje de naves aéreas.', '1', 'Kg', '0.00', '0.00') - ,('76061203', '7606.12.03', 'Anodizadas, en rollos, excepto las comprendidas en la fracción arancelaria 7606.12.01.', '1', 'Kg', '5.00', '0.00') - ,('76061299', '7606.12.99', 'Los demás.', '1', 'Kg', '30.00', '0.00') - ,('76069101', '7606.91.01', 'Reconocibles para naves aéreas.', '1', 'Kg', '0.00', '0.00') - ,('76069199', '7606.91.99', 'Las demás.', '1', 'Kg', '3.00', '0.00') - ,('76069201', '7606.92.01', 'Reconocibles para naves aéreas.', '1', 'Kg', '0.00', '0.00') - ,('76069299', '7606.92.99', 'Las demás.', '1', 'Kg', '5.00', '0.00') - ,('76071101', '7607.11.01', 'Simplemente laminadas.', '1', 'Kg', '5.00', '0.00') - ,('76071901', '7607.19.01', 'Reconocibles para naves aéreas.', '1', 'Kg', '0.00', '0.00') - ,('76071902', '7607.19.02', 'Atacadas en su superficie, reconocibles para la fabricación de condensadores electrolíticos.', '1', 'Kg', '0.00', '0.00') - ,('76071903', '7607.19.03', 'De espesor inferior o igual a 0.02 mm y anchura inferior a 20 mm, o en espesor inferior a 0.006 mm, en cualquier anchura, en rollos, reconocibles como diseñadas exclusivamente para condensadores eléctricos.', '1', 'Kg', '0.00', '0.00') - ,('76071999', '7607.19.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('76072002', '7607.20.02', 'Con soporte.', '1', 'Kg', '0.00', '0.00') - ,('76081003', '7608.10.03', 'Serpentines.', '1', 'Kg', '5.00', '0.00') - ,('76081099', '7608.10.99', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('76082001', '7608.20.01', 'Con diámetro interior inferior o igual a 203.2 mm, excepto lo comprendido en las fracciones arancelarias 7608.20.02 y 7608.20.03.', '1', 'Kg', '25.00', '0.00') - ,('76082002', '7608.20.02', 'Con diámetro interior inferior o igual a 203.2 mm, incluso con órganos de acoplamiento y compuertas laterales de descarga reconocibles como diseñados exclusivamente para sistemas de riego agrícola a flor de tierra.', '1', 'Kg', '0.00', '0.00') - ,('76082003', '7608.20.03', 'Serpentines.', '1', 'Kg', '5.00', '0.00') - ,('76082099', '7608.20.99', 'Los demás.', '1', 'Kg', '30.00', '0.00') - ,('76090002', '7609.00.02', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('76101001', '7610.10.01', 'Puertas y ventanas, y sus marcos, contramarcos y umbrales.', '1', 'Kg', '30.00', '0.00') - ,('76109001', '7610.90.01', 'Mástiles para embarcaciones.', '1', 'Kg', '0.00', '0.00') - ,('76109099', '7610.90.99', 'Los demás.', '1', 'Kg', '30.00', '0.00') - ,('76110001', '7611.00.01', 'Depósitos, cisternas, cubas y recipientes similares para cualquier materia (excepto gas comprimido o licuado), de aluminio, de capacidad superior a 300 l, sin dispositivos mecánicos ni térmicos, incluso con revestimiento interior o calorífugo.', '6', 'Pza', '15.00', '0.00') - ,('76121001', '7612.10.01', 'Envases tubulares flexibles.', '6', 'Pza', '15.00', '0.00') - ,('76129001', '7612.90.01', 'Reconocibles como diseñados exclusivamente para el transporte y conservación de semen para animales y las demás muestras biológicas.', '6', 'Pza', '0.00', '0.00') - ,('76129099', '7612.90.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('76130002', '7613.00.02', 'Tanques para oxígeno de uso medicinal.', '6', 'Pza', '0.00', '0.00') - ,('76130099', '7613.00.99', 'Los demás.', '6', 'Pza', '35.00', '0.00') - ,('76141001', '7614.10.01', 'Los demás cables ACSR.', '1', 'Kg', '5.00', '0.00') - ,('76149099', '7614.90.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('76151002', '7615.10.02', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('76152001', '7615.20.01', 'Artículos de higiene o tocador, y sus partes.', '1', 'Kg', '15.00', '0.00') - ,('76161001', '7616.10.01', 'Clavos, puntillas, remaches, arandelas, tornillos o tuercas.', '1', 'Kg', '0.00', '0.00') - ,('76161003', '7616.10.03', 'Reconocibles para naves aéreas.', '1', 'Kg', '0.00', '0.00') - ,('76161099', '7616.10.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('76169101', '7616.91.01', 'Telas metálicas, redes y rejas, de alambre de aluminio.', '1', 'Kg', '15.00', '0.00') - ,('76169901', '7616.99.01', 'Accesorios para tendidos aéreos eléctricos.', '1', 'Kg', '5.00', '0.00') - ,('76169902', '7616.99.02', 'Carretes de urdido, seccionales.', '1', 'Kg', '0.00', '0.00') - ,('76169903', '7616.99.03', 'Bobinas o carretes reconocibles como diseñadas exclusivamente para la industria textil.', '1', 'Kg', '0.00', '0.00') - ,('76169904', '7616.99.04', 'Bobinas o carretes, excepto lo comprendido en la fracción arancelaria 7616.99.03.', '1', 'Kg', '0.00', '0.00') - ,('76169905', '7616.99.05', 'Quemadores de aluminio, para calentadoras de ambiente.', '1', 'Kg', '0.00', '0.00') - ,('76169906', '7616.99.06', 'Agujas para tejer a mano.', '1', 'Kg', '15.00', '0.00') - ,('76169907', '7616.99.07', 'Ganchillos para tejer a mano.', '1', 'Kg', '15.00', '0.00') - ,('76169908', '7616.99.08', 'Chapas o bandas extendidas.', '1', 'Kg', '0.00', '0.00') - ,('76169909', '7616.99.09', 'Portagomas (casquillos) o tapas (conteras) para lápices.', '1', 'Kg', '0.00', '0.00') - ,('76169910', '7616.99.10', 'Discos con un contenido de aluminio superior o igual a 97%, excepto lo comprendido en la fracción arancelaria 7616.99.14.', '1', 'Kg', '3.00', '0.00') - ,('76169911', '7616.99.11', 'Anodos.', '1', 'Kg', '0.00', '0.00') - ,('76169912', '7616.99.12', 'Reconocibles para naves aéreas.', '1', 'Kg', '0.00', '0.00') - ,('76169913', '7616.99.13', 'Escaleras.', '1', 'Kg', '35.00', '0.00') - ,('76169914', '7616.99.14', 'Manufacturas planas con un contenido de aluminio superior o igual a 99.7%, cuyas dimensiones se circunscriban en una circunferencia de un círculo cuyo diámetro sea superior o igual a 12 mm, pero inferior a 70 mm, y espesor superior o igual a 3 mm, pero inferior o igual a 16 mm.', '1', 'Kg', '3.00', '0.00') - ,('76169915', '7616.99.15', 'Casquillos troquelados, reconocibles como diseñados exclusivamente para esferas de navidad.', '1', 'Kg', '35.00', '0.00') - ,('76169999', '7616.99.99', 'Las demás piezas forjadas.', '1', 'Kg', '25.00', '0.00') - ,('78011001', '7801.10.01', 'Plomo refinado.', '1', 'Kg', '0.00', '0.00') - ,('78019101', '7801.91.01', 'Con antimonio como el otro elemento predominante en peso.', '1', 'Kg', '0.00', '0.00') - ,('78019999', '7801.99.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('78020001', '7802.00.01', 'Desperdicios y desechos, de plomo.', '1', 'Kg', '0.00', '0.00') - ,('78041101', '7804.11.01', 'Hojas y tiras, de espesor inferior o igual a 0.2 mm (sin incluir el soporte).', '1', 'Kg', '0.00', '0.00') - ,('78041999', '7804.19.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('78042001', '7804.20.01', 'Polvo y escamillas.', '1', 'Kg', '0.00', '0.00') - ,('78060091', '7806.00.91', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('79011101', '7901.11.01', 'Con un contenido de cinc superior o igual al 99.99% en peso.', '1', 'Kg', '0.00', '0.00') - ,('79011201', '7901.12.01', 'Con un contenido de cinc inferior al 99.99% en peso.', '1', 'Kg', '0.00', '0.00') - ,('79012001', '7901.20.01', 'Aleaciones de cinc.', '1', 'Kg', '0.00', '0.00') - ,('79020001', '7902.00.01', 'Desperdicios y desechos, de cinc.', '1', 'Kg', '0.00', '0.00') - ,('79031001', '7903.10.01', 'Polvo de condensación.', '1', 'Kg', '0.00', '0.00') - ,('79039099', '7903.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('79040001', '7904.00.01', 'Barras, perfiles y alambre, de cinc.', '1', 'Kg', '0.00', '0.00') - ,('79050001', '7905.00.01', 'Chapas, hojas y tiras, de cinc.', '1', 'Kg', '0.00', '0.00') - ,('79070091', '7907.00.91', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('80011001', '8001.10.01', 'Estaño sin alear.', '1', 'Kg', '0.00', '0.00') - ,('80012001', '8001.20.01', 'Aleaciones de estaño.', '1', 'Kg', '0.00', '0.00') - ,('80020001', '8002.00.01', 'Desperdicios y desechos, de estaño.', '1', 'Kg', '0.00', '0.00') - ,('80030001', '8003.00.01', 'Barras, perfiles y alambre, de estaño.', '1', 'Kg', '0.00', '0.00') - ,('80070001', '8007.00.01', 'Chapas, hojas y tiras, de estaño, de espesor superior a 0.2 mm.', '1', 'Kg', '0.00', '0.00') - ,('80070002', '8007.00.02', 'Hojas y tiras, delgadas, de estaño (incluso impresas o fijadas sobre papel, cartón, plástico o soportes similares), de espesor inferior o igual a 0.2 mm (sin incluir el soporte); polvo y escamillas, de estaño.', '1', 'Kg', '0.00', '0.00') - ,('80070003', '8007.00.03', 'Tubos y accesorios de tubería (por ejemplo: empalmes (racores), codos, manguitos), de estaño.', '1', 'Kg', '0.00', '0.00') - ,('80070099', '8007.00.99', 'Las demás.', '1', 'Kg', '15.00', '0.00') - ,('81011001', '8101.10.01', 'Polvo.', '1', 'Kg', '0.00', '0.00') - ,('81019401', '8101.94.01', 'Volframio (tungsteno) en bruto, incluidas las barras simplemente obtenidas por sinterizado.', '1', 'Kg', '0.00', '0.00') - ,('81019603', '8101.96.03', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('81019701', '8101.97.01', 'Desperdicios y desechos.', '1', 'Kg', '0.00', '0.00') - ,('81019999', '8101.99.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('81021001', '8102.10.01', 'Polvo.', '1', 'Kg', '0.00', '0.00') - ,('81029401', '8102.94.01', 'Molibdeno en bruto, incluidas las barras simplemente obtenidas por sinterizado.', '1', 'Kg', '0.00', '0.00') - ,('81029502', '8102.95.02', 'Barras, excepto las simplemente obtenidas por sinterizado, perfiles, chapas, hojas y tiras.', '1', 'Kg', '0.00', '0.00') - ,('81029601', '8102.96.01', 'Alambre.', '1', 'Kg', '0.00', '0.00') - ,('81029701', '8102.97.01', 'Desperdicios y desechos.', '1', 'Kg', '0.00', '0.00') - ,('81029999', '8102.99.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('81032001', '8103.20.01', 'Tantalio en bruto, incluidas las barras simplemente obtenidas por sinterizado; polvo.', '1', 'Kg', '0.00', '0.00') - ,('81033001', '8103.30.01', 'Desperdicios y desechos.', '1', 'Kg', '0.00', '0.00') - ,('81039101', '8103.91.01', 'Crisoles.', '1', 'Kg', '0.00', '0.00') - ,('81039999', '8103.99.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('81041101', '8104.11.01', 'Con un contenido de magnesio superior o igual al 99.8% en peso.', '1', 'Kg', '0.00', '0.00') - ,('81041999', '8104.19.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('81042001', '8104.20.01', 'Desperdicios y desechos.', '1', 'Kg', '0.00', '0.00') - ,('81043001', '8104.30.01', 'Virutas, torneaduras y gránulos calibrados; polvo.', '1', 'Kg', '0.00', '0.00') - ,('81049099', '8104.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('81052001', '8105.20.01', 'Matas de cobalto y demás productos intermedios de la metalurgia del cobalto; cobalto en bruto; polvo.', '1', 'Kg', '0.00', '0.00') - ,('81053001', '8105.30.01', 'Desperdicios y desechos.', '1', 'Kg', '0.00', '0.00') - ,('81059099', '8105.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('81061001', '8106.10.01', 'Con un contenido de bismuto superior al 99.99% en peso.', '1', 'Kg', '0.00', '0.00') - ,('81069099', '8106.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('81082001', '8108.20.01', 'Titanio en bruto; polvo.', '1', 'Kg', '0.00', '0.00') - ,('81083001', '8108.30.01', 'Desperdicios y desechos.', '1', 'Kg', '0.00', '0.00') - ,('81089001', '8108.90.01', 'Canastillas, bastidores ("Racks") y serpentines.', '1', 'Kg', '10.00', '0.00') - ,('81089099', '8108.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('81092101', '8109.21.01', 'Con un contenido inferior a 1 parte de hafnio (celtio) por 500 partes de circonio, en peso.', '1', 'Kg', '0.00', '0.00') - ,('81092999', '8109.29.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('81093101', '8109.31.01', 'Con un contenido inferior a 1 parte de hafnio (celtio) por 500 partes de circonio, en peso.', '1', 'Kg', '0.00', '0.00') - ,('81093999', '8109.39.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('81099101', '8109.91.01', 'Con un contenido inferior a 1 parte de hafnio (celtio) por 500 partes de circonio, en peso.', '1', 'Kg', '0.00', '0.00') - ,('81099999', '8109.99.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('81101001', '8110.10.01', 'Antimonio en bruto; polvo.', '1', 'Kg', '0.00', '0.00') - ,('81102001', '8110.20.01', 'Desperdicios y desechos.', '1', 'Kg', '0.00', '0.00') - ,('81109099', '8110.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('81110002', '8111.00.02', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('81121201', '8112.12.01', 'En bruto; polvo.', '1', 'Kg', '0.00', '0.00') - ,('81121301', '8112.13.01', 'Desperdicios y desechos.', '1', 'Kg', '0.00', '0.00') - ,('81121999', '8112.19.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('81122101', '8112.21.01', 'En bruto; polvo.', '1', 'Kg', '0.00', '0.00') - ,('81122201', '8112.22.01', 'Desperdicios y desechos.', '1', 'Kg', '0.00', '0.00') - ,('81122999', '8112.29.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('81123101', '8112.31.01', 'En bruto; desperdicios y desechos; polvo.', '1', 'Kg', '0.00', '0.00') - ,('81123999', '8112.39.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('81124101', '8112.41.01', 'En bruto; desperdicios y desechos; polvo.', '1', 'Kg', '0.00', '0.00') - ,('81124999', '8112.49.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('81125101', '8112.51.01', 'En bruto; polvo.', '1', 'Kg', '0.00', '0.00') - ,('81125201', '8112.52.01', 'Desperdicios y desechos.', '1', 'Kg', '0.00', '0.00') - ,('81125999', '8112.59.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('81126101', '8112.61.01', 'Desperdicios y desechos.', '1', 'Kg', '0.00', '0.00') - ,('81126999', '8112.69.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('81129201', '8112.92.01', 'En bruto; desperdicios y desechos; polvo.', '1', 'Kg', '0.00', '0.00') - ,('81129999', '8112.99.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('81130002', '8113.00.02', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('82011002', '8201.10.02', 'Layas y palas.', '6', 'Pza', '15.00', '0.00') - ,('82013002', '8201.30.02', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('82014001', '8201.40.01', 'Hachas, hocinos y herramientas similares con filo.', '6', 'Pza', '15.00', '0.00') - ,('82015001', '8201.50.01', 'Tijeras de podar (incluidas las de trinchar aves) para usar con una sola mano.', '6', 'Pza', '15.00', '0.00') - ,('82016001', '8201.60.01', 'Cizallas para setos, tijeras de podar y herramientas similares para usar con las dos manos, excepto lo comprendido en la fracción arancelaria 8201.60.02.', '6', 'Pza', '15.00', '0.00') - ,('82016002', '8201.60.02', 'Cabezas de tijeras de podar, sin mango.', '6', 'Pza', '0.00', '0.00') - ,('82019002', '8201.90.02', 'Guadañas.', '6', 'Pza', '0.00', '0.00') - ,('82019003', '8201.90.03', 'Bieldos de más de cinco dientes.', '6', 'Pza', '0.00', '0.00') - ,('82019099', '8201.90.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('82021001', '8202.10.01', 'Serruchos (serrotes).', '1', 'Kg', '15.00', '0.00') - ,('82021004', '8202.10.04', 'Sierras con marco o de arco (arcos con segueta, arcos para seguetas).', '1', 'Kg', '15.00', '0.00') - ,('82021005', '8202.10.05', 'Seguetas para ampolletas; tronzadores.', '1', 'Kg', '0.00', '0.00') - ,('82021099', '8202.10.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('82022001', '8202.20.01', 'Hojas de sierra de cinta.', '1', 'Kg', '0.00', '0.00') - ,('82023101', '8202.31.01', 'Con diámetro exterior inferior o igual a 800 mm.', '1', 'Kg', '15.00', '0.00') - ,('82023199', '8202.31.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('82023901', '8202.39.01', 'Con diámetro inferior o igual a 800 mm, excepto lo comprendido en la fracción arancelaria 8202.39.02.', '1', 'Kg', '15.00', '0.00') - ,('82023902', '8202.39.02', 'Guarnecidas de diamante.', '1', 'Kg', '0.00', '0.00') - ,('82023903', '8202.39.03', 'Con diámetro exterior superior a 800 mm, excepto lo comprendido en la fracción arancelaria 8202.39.02.', '1', 'Kg', '0.00', '0.00') - ,('82023904', '8202.39.04', 'Esbozos (corazas o centros) de acero, reconocibles como diseñados exclusivamente para sierras circulares guarnecidas de diamante.', '1', 'Kg', '0.00', '0.00') - ,('82023905', '8202.39.05', 'Segmentos o dientes sin polvo de diamante para sierras circulares, excepto lo comprendido en la fracción arancelaria 8202.39.07.', '1', 'Kg', '0.00', '0.00') - ,('82023906', '8202.39.06', 'Segmentos o dientes guarnecidos con polvo de diamante aglomerado.', '1', 'Kg', '0.00', '0.00') - ,('82023907', '8202.39.07', 'Segmentos o dientes con parte operante de acero.', '1', 'Kg', '0.00', '0.00') - ,('82023999', '8202.39.99', 'Las demás.', '1', 'Kg', '10.00', '0.00') - ,('82024002', '8202.40.02', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('82029104', '8202.91.04', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('82029901', '8202.99.01', 'Hojas de sierra de pelo.', '1', 'Kg', '0.00', '0.00') - ,('82029999', '8202.99.99', 'Las demás.', '1', 'Kg', '5.00', '0.00') - ,('82031002', '8203.10.02', 'Limas con longitud superior a 50 cm.', '6', 'Pza', '0.00', '0.00') - ,('82031099', '8203.10.99', 'Las demás.', '6', 'Pza', '15.00', '0.00') - ,('82032001', '8203.20.01', 'Trabadores para sierra.', '1', 'Kg', '0.00', '0.00') - ,('82032099', '8203.20.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('82033001', '8203.30.01', 'Cizallas para metales y herramientas similares.', '6', 'Pza', '15.00', '0.00') - ,('82034001', '8203.40.01', 'Sacabocados.', '6', 'Pza', '0.00', '0.00') - ,('82034003', '8203.40.03', 'Cortapernos y cortarremaches.', '6', 'Pza', '0.00', '0.00') - ,('82034099', '8203.40.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('82041101', '8204.11.01', 'Llaves de palanca con matraca, excepto lo comprendido en la fracción arancelaria 8204.11.02.', '1', 'Kg', '15.00', '0.00') - ,('82041102', '8204.11.02', 'Con longitud igual o inferior a 610 mm, para tubos.', '1', 'Kg', '15.00', '0.00') - ,('82041199', '8204.11.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('82041201', '8204.12.01', 'Llaves de cadena; llaves ajustables con longitud superior a 785 mm, para tubos.', '1', 'Kg', '0.00', '0.00') - ,('82041299', '8204.12.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('82042001', '8204.20.01', 'Con mango.', '1', 'Kg', '15.00', '0.00') - ,('82042099', '8204.20.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('82051001', '8205.10.01', 'Taladros, excepto los berbiquíes .', '6', 'Pza', '0.00', '0.00') - ,('82051099', '8205.10.99', 'Las demás.', '6', 'Pza', '15.00', '0.00') - ,('82052001', '8205.20.01', 'Martillos y mazas.', '6', 'Pza', '15.00', '0.00') - ,('82053001', '8205.30.01', 'Guillames, acanaladores o machihembradores.', '6', 'Pza', '0.00', '0.00') - ,('82053099', '8205.30.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('82054001', '8205.40.01', 'Con probador de corriente, incluso con su lámpara; de matraca; de cabeza giratoria.', '6', 'Pza', '0.00', '0.00') - ,('82054099', '8205.40.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('82055102', '8205.51.02', 'Destapadores mecánicos, para cañerías.', '6', 'Pza', '5.00', '0.00') - ,('82055199', '8205.51.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('82055905', '8205.59.05', 'Cuñas de hierro o acero.', '6', 'Pza', '0.00', '0.00') - ,('82055908', '8205.59.08', 'Engrapadoras.', '6', 'Pza', '0.00', '0.00') - ,('82055909', '8205.59.09', 'Tensores para cadenas.', '6', 'Pza', '0.00', '0.00') - ,('82055910', '8205.59.10', 'Tiradores o guías, para instalación de conductores eléctricos.', '6', 'Pza', '0.00', '0.00') - ,('82055913', '8205.59.13', 'Extractores de poleas o de rodamientos.', '6', 'Pza', '5.00', '0.00') - ,('82055914', '8205.59.14', 'Compresores para colocar anillos de pistones.', '6', 'Pza', '5.00', '0.00') - ,('82055915', '8205.59.15', 'Herramientas o utensilios para colocar válvulas de broche.', '6', 'Pza', '0.00', '0.00') - ,('82055916', '8205.59.16', 'Extractores de piezas de reloj.', '6', 'Pza', '0.00', '0.00') - ,('82055999', '8205.59.99', 'Las demás.', '6', 'Pza', '15.00', '0.00') - ,('82056002', '8205.60.02', 'Lámparas de soldar y similares.', '6', 'Pza', '15.00', '0.00') - ,('82057003', '8205.70.03', 'Sujetadores de piezas de reloj.', '6', 'Pza', '0.00', '0.00') - ,('82057099', '8205.70.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('82059091', '8205.90.91', 'Los demás, incluidos los juegos de artículos de dos o más de las subpartidas anteriores.', '6', 'Pza', '15.00', '0.00') - ,('82060001', '8206.00.01', 'Herramientas de dos o más de las partidas 82.02 a 82.05, acondicionadas en juegos para la venta al por menor.', '1', 'Kg', '15.00', '0.00') - ,('82071308', '8207.13.08', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('82071991', '8207.19.91', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('82072001', '8207.20.01', 'Hileras de extrudir o de estirar (trefilar) metal.', '6', 'Pza', '0.00', '0.00') - ,('82073003', '8207.30.03', 'Esbozos de matrices o troqueles, con peso igual o superior a 1,000 Kg, para el estampado de metales; y sus partes.', '6', 'Pza', '0.00', '0.00') - ,('82074004', '8207.40.04', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('82075007', '8207.50.07', 'Los demás.', '6', 'Pza', '35.00', '0.00') - ,('82076006', '8207.60.06', 'Los demás.', '6', 'Pza', '35.00', '0.00') - ,('82077003', '8207.70.03', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('82078001', '8207.80.01', 'Útiles de tornear.', '6', 'Pza', '35.00', '0.00') - ,('82079001', '8207.90.01', 'Extractores de tornillos.', '6', 'Pza', '35.00', '0.00') - ,('82079099', '8207.90.99', 'Los demás.', '6', 'Pza', '35.00', '0.00') - ,('82081002', '8208.10.02', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('82082002', '8208.20.02', 'Para trabajar madera.', '6', 'Pza', '25.00', '0.00') - ,('82083002', '8208.30.02', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('82084003', '8208.40.03', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('82089099', '8208.90.99', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('82090001', '8209.00.01', 'Plaquitas, varillas, puntas y artículos similares para útiles, sin montar, de cermet.', '1', 'Kg', '0.00', '0.00') - ,('82100001', '8210.00.01', 'Aparatos mecánicos accionados a mano, de peso inferior o igual a 10 kg, utilizados para preparar, acondicionar o servir alimentos o bebidas.', '6', 'Pza', '15.00', '0.00') - ,('82111001', '8211.10.01', 'Surtidos.', '1', 'Kg', '15.00', '0.00') - ,('82119101', '8211.91.01', 'Cuchillos de mesa de hoja fija.', '6', 'Pza', '15.00', '0.00') - ,('82119201', '8211.92.01', 'Chairas.', '6', 'Pza', '0.00', '0.00') - ,('82119299', '8211.92.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('82119301', '8211.93.01', 'Cuchillos, excepto los de hoja fija, incluidas las navajas de podar.', '6', 'Pza', '15.00', '0.00') - ,('82119401', '8211.94.01', 'Hojas.', '6', 'Pza', '15.00', '0.00') - ,('82119501', '8211.95.01', 'Mangos de metal común.', '6', 'Pza', '0.00', '0.00') - ,('82121001', '8212.10.01', 'Navajas de barbero.', '6', 'Pza', '5.00', '0.00') - ,('82121099', '8212.10.99', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('82122001', '8212.20.01', 'Hojas para maquinillas de afeitar, incluidos los esbozos en fleje.', '6', 'Pza', '0.00', '0.00') - ,('82129001', '8212.90.01', 'Para máquinas de afeitar.', '1', 'Kg', '0.00', '0.00') - ,('82129099', '8212.90.99', 'Las demás.', '1', 'Kg', '15.00', '0.00') - ,('82130001', '8213.00.01', 'Tijeras y sus hojas.', '6', 'Pza', '15.00', '0.00') - ,('82141002', '8214.10.02', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('82142002', '8214.20.02', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('82149001', '8214.90.01', 'Máquinas de cortar el pelo.', '6', 'Pza', '5.00', '0.00') - ,('82149005', '8214.90.05', 'Esquiladoras; peines para máquinas de cortar el pelo.', '6', 'Pza', '0.00', '0.00') - ,('82149099', '8214.90.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('82151001', '8215.10.01', 'Surtidos que contengan por lo menos un objeto plateado, dorado o platinado.', '1', 'Kg', '15.00', '0.00') - ,('82152091', '8215.20.91', 'Los demás surtidos.', '1', 'Kg', '15.00', '0.00') - ,('82159101', '8215.91.01', 'Plateados, dorados o platinados.', '6', 'Pza', '35.00', '0.00') - ,('82159999', '8215.99.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('83011001', '8301.10.01', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('83012002', '8301.20.02', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('83013001', '8301.30.01', 'Cerraduras de los tipos utilizados en muebles.', '1', 'Kg', '15.00', '0.00') - ,('83014091', '8301.40.91', 'Las demás.', '6', 'Pza', '15.00', '0.00') - ,('83015001', '8301.50.01', 'Monturas cierre de aleación de magnesio, con cerradura incorporada.', '1', 'Kg', '5.00', '0.00') - ,('83015099', '8301.50.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('83016002', '8301.60.02', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('83017002', '8301.70.02', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('83021001', '8302.10.01', 'Con mecanismos de resortes, excepto lo comprendido en la fracción arancelaria 8302.10.02.', '1', 'Kg', '15.00', '0.00') - ,('83021002', '8302.10.02', 'Para vehículos automóviles.', '1', 'Kg', '0.00', '0.00') - ,('83021099', '8302.10.99', 'Los demás.', '1', 'Kg', '35.00', '0.00') - ,('83022002', '8302.20.02', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('83023091', '8302.30.91', 'Las demás guarniciones, herrajes y artículos similares, para vehículos automóviles.', '1', 'Kg', '15.00', '0.00') - ,('83024106', '8302.41.06', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('83024291', '8302.42.91', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('83024999', '8302.49.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('83025001', '8302.50.01', 'Colgadores, perchas, soportes y artículos similares.', '1', 'Kg', '35.00', '0.00') - ,('83026001', '8302.60.01', 'Cierrapuertas automáticos.', '1', 'Kg', '15.00', '0.00') - ,('83030001', '8303.00.01', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('83040003', '8304.00.03', 'Clasificadores, ficheros, cajas de clasificación, bandejas de correspondencia, plumeros (vasos o cajas para plumas de escribir), portasellos y material similar de oficina, de metal común, excepto los muebles de oficina de la partida 94.03.', '1', 'Kg', '15.00', '0.00') - ,('83051001', '8305.10.01', 'Mecanismos para encuadernación de hojas intercambiables o para clasificadores.', '1', 'Kg', '15.00', '0.00') - ,('83052001', '8305.20.01', 'Grapas en tiras.', '1', 'Kg', '25.00', '0.00') - ,('83059001', '8305.90.01', 'Clips u otros sujetadores.', '1', 'Kg', '15.00', '0.00') - ,('83059002', '8305.90.02', 'Herrajes de 6 argollas con longitud de 13.0 cm o de 19.0 cm, con o sin gatillo, incluyendo las bases curvas para sujetarlos.', '1', 'Kg', '5.00', '0.00') - ,('83059099', '8305.90.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('83061001', '8306.10.01', 'Campanas, campanillas, gongos y artículos similares.', '6', 'Pza', '15.00', '0.00') - ,('83062101', '8306.21.01', 'Plateados, dorados o platinados.', '6', 'Pza', '15.00', '0.00') - ,('83062999', '8306.29.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('83063001', '8306.30.01', 'Marcos para fotografías, grabados o similares; espejos.', '6', 'Pza', '15.00', '0.00') - ,('83071001', '8307.10.01', 'Constituidos de dos o más armazones de hierro o de acero y de dos o más capas de materias termoplásticas.', '1', 'Kg', '0.00', '0.00') - ,('83071099', '8307.10.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('83079091', '8307.90.91', 'De los demás metales comunes.', '1', 'Kg', '5.00', '0.00') - ,('83081001', '8308.10.01', 'Broches.', '1', 'Kg', '0.00', '0.00') - ,('83081099', '8308.10.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('83082001', '8308.20.01', 'Remaches tubulares o con espiga hendida.', '1', 'Kg', '35.00', '0.00') - ,('83089091', '8308.90.91', 'Los demás, incluidas las partes.', '1', 'Kg', '5.00', '0.00') - ,('83091001', '8309.10.01', 'Tapas corona.', '1', 'Kg', '5.00', '0.00') - ,('83099001', '8309.90.01', 'Tapones o tapas, excepto los tapones sin cerradura para tanques de gasolina y lo comprendido en las fracciones arancelarias 8309.90.03, 8309.90.07 y 8309.90.08.', '1', 'Kg', '0.00', '0.00') - ,('83099003', '8309.90.03', 'Tapas de aluminio con rosca, para envases de uso farmacéutico.', '1', 'Kg', '0.00', '0.00') - ,('83099006', '8309.90.06', 'Cápsulas de aluminio para sobretaponar botellas.', '1', 'Kg', '0.00', '0.00') - ,('83099007', '8309.90.07', 'Tapas: con un precorte a menos de 10 mm de toda su orilla y una pieza de sujeción colocada sobre la tapa mediante un remache, llamadas tapas "abre fácil" ("full open"), de acero o aluminio, para envases destinados a contener bebidas o alimentos.', '1', 'Kg', '30.00', '0.00') - ,('83099008', '8309.90.08', 'Tapas de aluminio, con una pieza de sujeción colocada sobre la tapa mediante un remache, reconocibles como diseñadas exclusivamente para envases destinados a contener bebidas o alimentos.', '1', 'Kg', '25.00', '0.00') - ,('83099099', '8309.90.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('83100001', '8310.00.01', 'Emblemas o monogramas para vehículos automóviles.', '1', 'Kg', '15.00', '0.00') - ,('83100099', '8310.00.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('83111002', '8311.10.02', 'De cobre o sus aleaciones.', '1', 'Kg', '0.00', '0.00') - ,('83111003', '8311.10.03', 'De aluminio o sus aleaciones.', '1', 'Kg', '35.00', '0.00') - ,('83111004', '8311.10.04', 'De níquel o sus aleaciones.', '1', 'Kg', '30.00', '0.00') - ,('83111099', '8311.10.99', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('83112005', '8311.20.05', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('83113001', '8311.30.01', 'De hierro o acero.', '1', 'Kg', '25.00', '0.00') - ,('83113099', '8311.30.99', 'Los demás.', '1', 'Kg', '30.00', '0.00') - ,('83119001', '8311.90.01', 'Los demás.', '1', 'Kg', '35.00', '0.00') - ,('83119002', '8311.90.02', 'De cobre o sus aleaciones.', '1', 'Kg', '25.00', '0.00') - ,('83119003', '8311.90.03', 'De aluminio o sus aleaciones.', '1', 'Kg', '0.00', '0.00') - ,('83119004', '8311.90.04', 'De níquel o sus aleaciones.', '1', 'Kg', '0.00', '0.00') - ,('83119005', '8311.90.05', 'Hilos o varillas de polvo de metal común aglomerado, para la metalización por proyección.', '1', 'Kg', '0.00', '0.00') - ,('83119099', '8311.90.99', 'Los demás.', '1', 'Kg', '30.00', '0.00') - ,('84011001', '8401.10.01', 'Reactores nucleares.', '1', 'Kg', '0.00', '0.00') - ,('84012001', '8401.20.01', 'Máquinas y aparatos para la separación isotópica, y sus partes.', '1', 'Kg', '0.00', '0.00') - ,('84013001', '8401.30.01', 'Elementos combustibles (cartuchos) sin irradiar.', '1', 'Kg', '0.00', '0.00') - ,('84014001', '8401.40.01', 'Partes de reactores nucleares.', '1', 'Kg', '0.00', '0.00') - ,('84021101', '8402.11.01', 'Calderas acuotubulares con una producción de vapor superior a 45 t por hora.', '1', 'Kg', '15.00', '0.00') - ,('84021201', '8402.12.01', 'Calderas acuotubulares con una producción de vapor inferior o igual a 45 t por hora.', '1', 'Kg', '15.00', '0.00') - ,('84021901', '8402.19.01', 'Para generación de vapor de agua.', '1', 'Kg', '15.00', '0.00') - ,('84021999', '8402.19.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('84022001', '8402.20.01', 'Calderas denominadas "de agua sobrecalentada".', '1', 'Kg', '15.00', '0.00') - ,('84029001', '8402.90.01', 'Partes.', '1', 'Kg', '0.00', '0.00') - ,('84031001', '8403.10.01', 'Calderas.', '6', 'Pza', '15.00', '0.00') - ,('84039001', '8403.90.01', 'Partes.', '1', 'Kg', '0.00', '0.00') - ,('84041001', '8404.10.01', 'Aparatos auxiliares para las calderas de las partidas 84.02 u 84.03.', '1', 'Kg', '0.00', '0.00') - ,('84042001', '8404.20.01', 'Condensadores para máquinas de vapor.', '1', 'Kg', '5.00', '0.00') - ,('84049001', '8404.90.01', 'Partes.', '1', 'Kg', '0.00', '0.00') - ,('84051001', '8405.10.01', 'Celdas electrolíticas para producir cloro o hidrógeno y oxígeno.', '6', 'Pza', '0.00', '0.00') - ,('84051002', '8405.10.02', 'Generadores de atmósferas, sean oxidantes, reductoras o neutras, para tratamiento térmico de los metales.', '6', 'Pza', '15.00', '0.00') - ,('84051099', '8405.10.99', 'Los demás.', '6', 'Pza', '10.00', '0.00') - ,('84059001', '8405.90.01', 'Partes.', '1', 'Kg', '0.00', '0.00') - ,('84061001', '8406.10.01', 'Con potencia inferior o igual a 4,000 CP.', '6', 'Pza', '15.00', '0.00') - ,('84061099', '8406.10.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84068101', '8406.81.01', 'De potencia superior a 40 MW.', '6', 'Pza', '0.00', '0.00') - ,('84068201', '8406.82.01', 'De potencia inferior o igual a 2.95 MW (4,000 CP).', '6', 'Pza', '15.00', '0.00') - ,('84068299', '8406.82.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84069001', '8406.90.01', 'Rotores terminados para su ensamble final.', '6', 'Pza', '0.00', '0.00') - ,('84069002', '8406.90.02', 'Aspas rotativas o estacionarias.', '6', 'Pza', '0.00', '0.00') - ,('84069099', '8406.90.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('84071001', '8407.10.01', 'Motores de aviación.', '6', 'Pza', '0.00', '0.00') - ,('84072102', '8407.21.02', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84072901', '8407.29.01', 'Con potencia inferior o igual a 600 CP.', '6', 'Pza', '5.00', '0.00') - ,('84072999', '8407.29.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84073101', '8407.31.01', 'Para acoplarse en bicicletas, con potencia hasta 1.5 CP y con peso unitario igual o inferior a 6 kg.', '6', 'Pza', '5.00', '0.00') - ,('84073199', '8407.31.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84073202', '8407.32.02', 'Para acoplarse en bicicletas, con potencia hasta 1.5 CP y con peso unitario inferior a 6 kg.', '6', 'Pza', '5.00', '0.00') - ,('84073299', '8407.32.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84073304', '8407.33.04', 'De cilindrada superior a 250 cm3 pero inferior o igual a 1,000 cm3.', '6', 'Pza', '0.00', '0.00') - ,('84073403', '8407.34.03', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84079091', '8407.90.91', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84081001', '8408.10.01', 'Con potencia igual o inferior a 600 CP.', '6', 'Pza', '5.00', '0.00') - ,('84081099', '8408.10.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84082001', '8408.20.01', 'Motores de los tipos utilizados para la propulsión de vehículos del capítulo 87.', '6', 'Pza', '0.00', '0.00') - ,('84089001', '8408.90.01', 'Con potencia igual o inferior a 960 CP.', '6', 'Pza', '5.00', '0.00') - ,('84089099', '8408.90.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84091001', '8409.10.01', 'De motores de aviación.', '1', 'Kg', '0.00', '0.00') - ,('84099105', '8409.91.05', 'Pistones (émbolos) de aluminio, con diámetro exterior superior o igual a 58 mm, sin exceder de 140.0 mm, excepto los reconocibles para tractores agrícolas e industriales.', '1', 'Kg', '5.00', '0.00') - ,('84099111', '8409.91.11', 'Cárteres, excepto los reconocibles para tractores agrícolas e industriales.', '1', 'Kg', '5.00', '0.00') - ,('84099199', '8409.91.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('84099901', '8409.99.01', 'Culatas (cabezas) o monobloques.', '1', 'Kg', '0.00', '0.00') - ,('84099902', '8409.99.02', 'Pistones (émbolos) de aluminio, con diámetro exterior superior o igual a 58 mm, sin exceder de 140 mm.', '1', 'Kg', '0.00', '0.00') - ,('84099903', '8409.99.03', 'Pistones, camisas, anillos o válvulas, aun cuando se presenten en juegos ("kits") excepto lo comprendido en las fracciones arancelarias 8409.99.02 y 8409.99.04.', '1', 'Kg', '0.00', '0.00') - ,('84099904', '8409.99.04', 'Balancines, barras de balancines, punterías (buzos), válvulas con diámetro de cabeza superior o igual a 26 mm, sin exceder de 52 mm, incluso en juegos ("kits").', '1', 'Kg', '0.00', '0.00') - ,('84099905', '8409.99.05', 'Bielas o portabielas.', '1', 'Kg', '0.00', '0.00') - ,('84099908', '8409.99.08', 'Ejes ("pernos") para pistón (émbolo), excepto lo comprendido en las fracciones arancelarias 8409.99.09 y 8409.99.10.', '1', 'Kg', '0.00', '0.00') - ,('84099909', '8409.99.09', 'Reconocibles para motores marinos con potencia superior a 600 CP.', '1', 'Kg', '0.00', '0.00') - ,('84099910', '8409.99.10', 'Reconocibles como diseñadas exclusivamente para tractores agrícolas e industriales, excepto lo comprendido en la fracción arancelaria 8409.99.11.', '1', 'Kg', '0.00', '0.00') - ,('84099911', '8409.99.11', 'Reconocibles como diseñadas exclusivamente para inyectores de combustible diésel.', '1', 'Kg', '0.00', '0.00') - ,('84099912', '8409.99.12', 'Gobernadores de revoluciones.', '1', 'Kg', '0.00', '0.00') - ,('84099913', '8409.99.13', 'Varillas empujadoras de válvulas, no tubulares (sólidas) para uso en motores de combustión interna.', '1', 'Kg', '0.00', '0.00') - ,('84099914', '8409.99.14', 'Esbozos de émbolos de acero, hierro gris o aluminio, con diámetro exterior superior a 140 mm.', '1', 'Kg', '0.00', '0.00') - ,('84099999', '8409.99.99', 'Las demás.', '1', 'Kg', '5.00', '0.00') - ,('84101101', '8410.11.01', 'De potencia inferior o igual a 1,000 kW.', '6', 'Pza', '0.00', '0.00') - ,('84101202', '8410.12.02', 'De potencia superior a 1,000 kW pero inferior o igual a 10,000 kW.', '6', 'Pza', '0.00', '0.00') - ,('84101302', '8410.13.02', 'De potencia superior a 10,000 kW.', '6', 'Pza', '0.00', '0.00') - ,('84109001', '8410.90.01', 'Partes, incluidos los reguladores.', '1', 'Kg', '0.00', '0.00') - ,('84111101', '8411.11.01', 'De empuje inferior o igual a 25 kN.', '6', 'Pza', '0.00', '0.00') - ,('84111201', '8411.12.01', 'De empuje superior a 25 kN.', '6', 'Pza', '0.00', '0.00') - ,('84112101', '8411.21.01', 'De potencia inferior o igual a 1,100 kW.', '6', 'Pza', '0.00', '0.00') - ,('84112201', '8411.22.01', 'De potencia superior a 1,100 kW.', '6', 'Pza', '0.00', '0.00') - ,('84118101', '8411.81.01', 'De potencia inferior o igual a 5,000 kW.', '6', 'Pza', '0.00', '0.00') - ,('84118201', '8411.82.01', 'De potencia superior a 5,000 kW.', '6', 'Pza', '0.00', '0.00') - ,('84119101', '8411.91.01', 'De turborreactores o de turbopropulsores.', '1', 'Kg', '0.00', '0.00') - ,('84119999', '8411.99.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('84121001', '8412.10.01', 'Propulsores a reacción, excepto los turborreactores.', '6', 'Pza', '0.00', '0.00') - ,('84122101', '8412.21.01', 'Con movimiento rectilíneo (cilindros).', '6', 'Pza', '0.00', '0.00') - ,('84122999', '8412.29.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84123101', '8412.31.01', 'De aire, reconocibles como diseñados exclusivamente para bombas neumáticas.', '6', 'Pza', '15.00', '0.00') - ,('84123199', '8412.31.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84123999', '8412.39.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84128001', '8412.80.01', 'Motores de viento.', '6', 'Pza', '15.00', '0.00') - ,('84128099', '8412.80.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84129001', '8412.90.01', 'Partes.', '1', 'Kg', '0.00', '0.00') - ,('84131101', '8413.11.01', 'Distribuidoras con dispositivo medidor, aun cuando presenten mecanismo totalizador.', '6', 'Pza', '15.00', '0.00') - ,('84131199', '8413.11.99', 'Las demás.', '6', 'Pza', '10.00', '0.00') - ,('84131901', '8413.19.01', 'Distribuidoras con dispositivo medidor, aun cuando se presenten con mecanismo totalizador, excepto de émbolo, para el manejo de oxígeno líquido, a presión entre 29 y 301 kg/cm² (28 y 300 atmósferas), con dispositivos medidor y totalizador.', '6', 'Pza', '15.00', '0.00') - ,('84131903', '8413.19.03', 'Medidoras, de engranes.', '6', 'Pza', '15.00', '0.00') - ,('84131999', '8413.19.99', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('84132001', '8413.20.01', 'Bombas manuales, excepto las de las subpartidas 8413.11 u 8413.19.', '6', 'Pza', '15.00', '0.00') - ,('84133003', '8413.30.03', 'Para gasolina.', '6', 'Pza', '5.00', '0.00') - ,('84133099', '8413.30.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84134002', '8413.40.02', 'Bombas para hormigón.', '6', 'Pza', '0.00', '0.00') - ,('84135002', '8413.50.02', 'Para albercas.', '6', 'Pza', '5.00', '0.00') - ,('84135099', '8413.50.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84136001', '8413.60.01', 'Sumergibles, con tubería de descarga de diámetro interior superior o igual a 63 mm, sin exceder de 610 mm.', '6', 'Pza', '15.00', '0.00') - ,('84136004', '8413.60.04', 'Hidráulicas de paletas, para presión inferior o igual a 217 kg/cm² (210 atmósferas).', '6', 'Pza', '15.00', '0.00') - ,('84136006', '8413.60.06', 'Para albercas.', '6', 'Pza', '5.00', '0.00') - ,('84136099', '8413.60.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84137001', '8413.70.01', 'Portátiles, contra incendio.', '6', 'Pza', '0.00', '0.00') - ,('84137002', '8413.70.02', 'Extractoras o recirculatorias de agua, reconocibles como diseñadas exclusivamente para aparatos acondicionadores de aire.', '6', 'Pza', '5.00', '0.00') - ,('84137005', '8413.70.05', 'Para albercas.', '6', 'Pza', '0.00', '0.00') - ,('84137007', '8413.70.07', 'Bombas de agua, reconocibles como diseñadas exclusivamente para el sistema de limpiaparabrisas, para uso automotriz.', '6', 'Pza', '0.00', '0.00') - ,('84137099', '8413.70.99', 'Las demás.', '6', 'Pza', '15.00', '0.00') - ,('84138101', '8413.81.01', 'De caudal variable, aun cuando tengan servomotor.', '6', 'Pza', '15.00', '0.00') - ,('84138102', '8413.81.02', 'De accionamiento neumático, incluso con depósito, con o sin base rodante, para lubricantes.', '6', 'Pza', '15.00', '0.00') - ,('84138103', '8413.81.03', 'Para albercas.', '6', 'Pza', '5.00', '0.00') - ,('84138199', '8413.81.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84138201', '8413.82.01', 'Elevadores de líquidos.', '6', 'Pza', '0.00', '0.00') - ,('84139113', '8413.91.13', 'Los demás.', '1', 'Kg', '35.00', '0.00') - ,('84139201', '8413.92.01', 'De elevadores de líquidos.', '1', 'Kg', '0.00', '0.00') - ,('84141006', '8414.10.06', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84142001', '8414.20.01', 'Bombas de aire, de mano o pedal.', '6', 'Pza', '10.00', '0.00') - ,('84143001', '8414.30.01', 'Motocompresores, excepto lo comprendido en las fracciones arancelarias 8414.30.04, 8414.30.07, 8414.30.08 y 8414.30.10.', '6', 'Pza', '15.00', '0.00') - ,('84143002', '8414.30.02', 'Denominados abiertos, excepto lo comprendido en la fracción arancelaria 8414.30.06.', '6', 'Pza', '15.00', '0.00') - ,('84143003', '8414.30.03', 'Reconocibles para naves aéreas.', '6', 'Pza', '0.00', '0.00') - ,('84143004', '8414.30.04', 'Motocompresores herméticos, con potencia superior a 1½ CP, sin exceder de 5 CP.', '6', 'Pza', '0.00', '0.00') - ,('84143005', '8414.30.05', 'Rotativos, de segunda etapa de compresión, con un desplazamiento volumétrico de hasta 15 m³ por minuto, para ser utilizados en sistemas de refrigeración de baja temperatura.', '6', 'Pza', '15.00', '0.00') - ,('84143006', '8414.30.06', 'Abiertos, con capacidad de desplazamiento por revolución superior a 108 sin exceder de 161 cm³, sin bomba de aceite, accionados a platos magnéticos, para aire acondicionado de uso en automóviles.', '6', 'Pza', '0.00', '0.00') - ,('84143007', '8414.30.07', 'Motocompresores herméticos con potencia superior a ½ CP, sin exceder de 1½ CP, excepto lo comprendido en la fracción arancelaria 8414.30.10.', '6', 'Pza', '0.00', '0.00') - ,('84143008', '8414.30.08', 'Motocompresores herméticos reconocibles como diseñados exclusivamente para equipos de aire acondicionado, con potencia superior a ½ CP sin exceder de 5 CP.', '6', 'Pza', '0.00', '0.00') - ,('84143010', '8414.30.10', 'Motocompresores herméticos con potencia inferior a 1 CP, de un mínimo de 4.9 de eficiencia energética (BTU/Wh) y capacidad máxima de 750 BTU/h.', '6', 'Pza', '15.00', '0.00') - ,('84143099', '8414.30.99', 'Los demás.', '6', 'Pza', '10.00', '0.00') - ,('84144001', '8414.40.01', 'Compresores o motocompresores, con capacidad hasta 31.5 m³ por minuto y presión de aire hasta 17.6 kg/cm².', '6', 'Pza', '15.00', '0.00') - ,('84144002', '8414.40.02', 'Compresores o motocompresores, con capacidad superior a 31.5 m³ por minuto.', '6', 'Pza', '0.00', '0.00') - ,('84144099', '8414.40.99', 'Los demás.', '6', 'Pza', '10.00', '0.00') - ,('84145101', '8414.51.01', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('84145102', '8414.51.02', 'Ventiladores anticondensantes de 12 V, corriente directa.', '6', 'Pza', '15.00', '0.00') - ,('84145199', '8414.51.99', 'Los demás.', '6', 'Pza', '10.00', '0.00') - ,('84145901', '8414.59.01', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('84145999', '8414.59.99', 'Los demás.', '6', 'Pza', '10.00', '0.00') - ,('84146001', '8414.60.01', 'De uso doméstico.', '6', 'Pza', '0.00', '0.00') - ,('84146099', '8414.60.99', 'Los demás.', '6', 'Pza', '10.00', '0.00') - ,('84147001', '8414.70.01', 'Del tipo de campana aspirante.', '6', 'Pza', '10.00', '0.00') - ,('84147002', '8414.70.02', 'Filtros.', '1', 'Kg', '5.00', '0.00') - ,('84147099', '8414.70.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84148006', '8414.80.06', 'Motocompresores integrales, de 4 o más cilindros motrices, excepto los reconocibles exclusivamente para tractores agrícolas e industriales.', '6', 'Pza', '15.00', '0.00') - ,('84148007', '8414.80.07', 'Compresores de cloro.', '6', 'Pza', '5.00', '0.00') - ,('84148008', '8414.80.08', 'Bombas de aire, de dos impulsores rotativos, con capacidad de 2.0 a 48.5 m³, por minuto, excepto los reconocibles exclusivamente para tractores agrícolas e industriales.', '6', 'Pza', '15.00', '0.00') - ,('84148010', '8414.80.10', 'Enfriadores circulares para hornos de sinterización.', '6', 'Pza', '15.00', '0.00') - ,('84148099', '8414.80.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84149010', '8414.90.10', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('84151001', '8415.10.01', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84152001', '8415.20.01', 'De los tipos utilizados en vehículos automóviles para sus ocupantes.', '6', 'Pza', '5.00', '0.00') - ,('84158101', '8415.81.01', 'Con equipo de enfriamiento y válvula de inversión del ciclo térmico (bombas de calor reversibles).', '6', 'Pza', '15.00', '0.00') - ,('84158201', '8415.82.01', 'Equipos de aire acondicionado, de ciclo sencillo o reversible con compresor hermético cuya potencia sea inferior o igual a 5 CP.', '6', 'Pza', '10.00', '0.00') - ,('84158299', '8415.82.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84158301', '8415.83.01', 'Sin equipo de enfriamiento.', '6', 'Pza', '10.00', '0.00') - ,('84159002', '8415.90.02', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('84161001', '8416.10.01', 'Tipo cañón de tiro forzado, con capacidad superior o igual a 175 kW, pero inferior o igual a 18,500 kW, para calentadores de aceite o calderas.', '6', 'Pza', '0.00', '0.00') - ,('84161099', '8416.10.99', 'Las demás.', '6', 'Pza', '10.00', '0.00') - ,('84162001', '8416.20.01', 'De combustibles gaseosos o mixtos, tipo cañón de tiro forzado con capacidad superior o igual a 175 kW pero inferior o igual a 18,500 kW, para calentadores de aceite o calderas.', '6', 'Pza', '0.00', '0.00') - ,('84162099', '8416.20.99', 'Las demás.', '6', 'Pza', '10.00', '0.00') - ,('84163001', '8416.30.01', 'Trenes de gas de alimentación, reconocibles como diseñados exclusivamente para quemadores tipo cañón de tiro forzado.', '6', 'Pza', '0.00', '0.00') - ,('84163099', '8416.30.99', 'Los demás.', '6', 'Pza', '10.00', '0.00') - ,('84169002', '8416.90.02', 'Las demás.', '6', 'Pza', '35.00', '0.00') - ,('84171003', '8417.10.03', 'Para fusión, calentamiento, recalentamiento y/o tratamiento térmico de metales, excepto la oxicupola, con capacidad superior a 80 t/h y las industriales de operación continua.', '6', 'Pza', '15.00', '0.00') - ,('84171099', '8417.10.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84172002', '8417.20.02', 'Hornos de panadería, pastelería o galletería.', '6', 'Pza', '15.00', '0.00') - ,('84178001', '8417.80.01', 'Horno túnel, para temperaturas entre 900°C y 1,200°C, reconocibles para cocer ladrillos, tejas u otros elementos cerámicos.', '6', 'Pza', '0.00', '0.00') - ,('84178002', '8417.80.02', 'Hornos de rodillo para la cocción de losetas cerámicas a temperaturas entre 900°C y 1350°C, reconocibles como diseñados para líneas continuas para la fabricación de losetas o baldosas, de pasta cerámica, con capacidad de producción superior a 415 m² por hora, incluyendo al menos los siguientes elementos: molino continuo, horno continuo y moldeadora por prensado, con o sin atomizador.', '6', 'Pza', '0.00', '0.00') - ,('84178003', '8417.80.03', 'Incineradores de residuos, equipados con sistema de emisión de rayos infrarrojos.', '6', 'Pza', '0.00', '0.00') - ,('84178004', '8417.80.04', 'Calentadores e incineradores catalíticos, reconocibles como diseñados para la eliminación de residuos tóxicos contaminantes.', '6', 'Pza', '5.00', '0.00') - ,('84178005', '8417.80.05', 'Incineradores de desperdicios, excepto los comprendidos en la fracción arancelaria 8417.80.03.', '6', 'Pza', '5.00', '0.00') - ,('84178099', '8417.80.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('84179001', '8417.90.01', 'Partes.', '1', 'Kg', '0.00', '0.00') - ,('84181001', '8418.10.01', 'Con peso unitario inferior o igual a 200 kg, excepto con cajones exteriores separados.', '6', 'Pza', '15.00', '0.00') - ,('84181099', '8418.10.99', 'Los demás.', '6', 'Pza', '10.00', '0.00') - ,('84182101', '8418.21.01', 'De compresión.', '6', 'Pza', '15.00', '0.00') - ,('84182901', '8418.29.01', 'De absorción, eléctricos.', '6', 'Pza', '15.00', '0.00') - ,('84182999', '8418.29.99', 'Los demás.', '6', 'Pza', '10.00', '0.00') - ,('84183001', '8418.30.01', 'De absorción, eléctricos, con peso unitario inferior o igual a 200 kg.', '6', 'Pza', '15.00', '0.00') - ,('84183002', '8418.30.02', 'De absorción o compresión con peso unitario superior a 200 kg.', '6', 'Pza', '15.00', '0.00') - ,('84183004', '8418.30.04', 'De compresión, con peso unitario inferior o igual a 200 kg, excepto de uso doméstico.', '6', 'Pza', '15.00', '0.00') - ,('84183099', '8418.30.99', 'Los demás.', '6', 'Pza', '10.00', '0.00') - ,('84184001', '8418.40.01', 'De absorción, eléctricos, con peso unitario igual o inferior a 200 kg.', '6', 'Pza', '15.00', '0.00') - ,('84184002', '8418.40.02', 'De absorción, con peso unitario superior a 200 kg.', '6', 'Pza', '15.00', '0.00') - ,('84184004', '8418.40.04', 'De compresión, excepto de uso doméstico.', '1', 'Kg', '15.00', '0.00') - ,('84184099', '8418.40.99', 'Los demás.', '6', 'Pza', '10.00', '0.00') - ,('84185001', '8418.50.01', 'Vitrinas refrigeradoras, de compresión, con su equipo de refrigeración aun cuando no esté incorporado, de peso unitario superior a 200 kg, para autoservicio.', '6', 'Pza', '0.00', '0.00') - ,('84185003', '8418.50.03', 'Aparatos surtidores de agua refrigerada, incluso con gabinete de refrigeración incorporado, con o sin depósito (por ejemplo, un botellón), aunque puedan conectarse a una tubería, (por ejemplo, despachadores).', '6', 'Pza', '0.00', '0.00') - ,('84185099', '8418.50.99', 'Los demás.', '6', 'Pza', '10.00', '0.00') - ,('84186101', '8418.61.01', 'Reconocibles como diseñados exclusivamente para unidades de transporte terrestre de productos perecederos, comprendiendo: compresor, evaporador y condensador.', '6', 'Pza', '0.00', '0.00') - ,('84186199', '8418.61.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('84186901', '8418.69.01', 'Grupos frigoríficos de absorción.', '6', 'Pza', '15.00', '0.00') - ,('84186904', '8418.69.04', 'Grupos frigoríficos de compresión, excepto lo comprendido en las fracciones arancelarias 8418.69.05 y 8418.69.06.', '6', 'Pza', '15.00', '0.00') - ,('84186905', '8418.69.05', 'Grupos frigoríficos de compresión, reconocibles como diseñados exclusivamente para unidades de transporte terrestre de productos perecederos, comprendiendo: compresor, evaporador y condensador.', '6', 'Pza', '0.00', '0.00') - ,('84186906', '8418.69.06', 'Grupos frigoríficos de compresión, reconocibles como diseñados exclusivamente para unidades de transporte marítimo de productos perecederos, comprendiendo: compresor, evaporador y condensador.', '6', 'Pza', '15.00', '0.00') - ,('84186907', '8418.69.07', 'Grupos frigoríficos por expansión de nitrógeno líquido, con peso unitario igual o inferior a 500 kg, reconocibles como diseñados exclusivamente para unidades de transporte de productos perecederos.', '6', 'Pza', '0.00', '0.00') - ,('84186908', '8418.69.08', 'Unidades condensadoras con compresor abierto para gases halogenados, sin motor, montadas sobre una base común.', '6', 'Pza', '15.00', '0.00') - ,('84186909', '8418.69.09', 'Máquinas o aparatos para producir nieve carbónica.', '6', 'Pza', '15.00', '0.00') - ,('84186910', '8418.69.10', 'Gabinete evaporativo de placas de contacto de aluminio o acero, para congelación rápida de productos alimenticios, sin equipo de compresión y con operación hidráulica de apertura y cierre de las placas.', '6', 'Pza', '0.00', '0.00') - ,('84186912', '8418.69.12', 'Cámaras frigoríficas desarmadas en paneles, con grupo eléctrico de refrigeración para ser incorporado en las mismas.', '6', 'Pza', '15.00', '0.00') - ,('84186913', '8418.69.13', 'Instalaciones frigoríficas (unidades funcionales), excepto lo comprendido en las fracciones arancelarias 8418.69.14 y 8418.69.15.', '6', 'Pza', '15.00', '0.00') - ,('84186914', '8418.69.14', 'Plantas para la elaboración de hielo, excepto lo comprendido en la fracción arancelaria 8418.69.15.', '6', 'Pza', '15.00', '0.00') - ,('84186915', '8418.69.15', 'Plantas automáticas para la producción de hielo en cubos, escamas u otras formas, con capacidad superior a 4,000 kg, cada 24 horas.', '6', 'Pza', '15.00', '0.00') - ,('84186999', '8418.69.99', 'Los demás.', '6', 'Pza', '10.00', '0.00') - ,('84189101', '8418.91.01', 'Muebles diseñados para incorporarles un equipo para producción de frío.', '1', 'Kg', '0.00', '0.00') - ,('84189903', '8418.99.03', 'Condensadores de casco y tubo horizontal o vertical.', '1', 'Kg', '0.00', '0.00') - ,('84189904', '8418.99.04', 'Ensambles de puertas que incorporen más de uno de los siguientes componentes: panel interior, panel exterior, aislamiento, bisagras, agarraderas.', '6', 'Pza', '0.00', '0.00') - ,('84189999', '8418.99.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('84191101', '8419.11.01', 'De calentamiento instantáneo, de gas.', '6', 'Pza', '10.00', '0.00') - ,('84191201', '8419.12.01', 'De uso doméstico, excepto los de placas, metálicas o plásticas y de agua de tubos evacuados.', '6', 'Pza', '10.00', '0.00') - ,('84191299', '8419.12.99', 'Los demás.', '6', 'Pza', '5.00', '0.00') - ,('84191901', '8419.19.01', 'De uso doméstico.', '6', 'Pza', '10.00', '0.00') - ,('84191999', '8419.19.99', 'Los demás.', '6', 'Pza', '5.00', '0.00') - ,('84192002', '8419.20.02', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84193301', '8419.33.01', 'De los tipos utilizados para productos agrícolas.', '6', 'Pza', '10.00', '0.00') - ,('84193399', '8419.33.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84193401', '8419.34.01', 'De vacío.', '6', 'Pza', '0.00', '0.00') - ,('84193402', '8419.34.02', 'De granos.', '6', 'Pza', '15.00', '0.00') - ,('84193403', '8419.34.03', 'Discontinuos de charolas.', '6', 'Pza', '15.00', '0.00') - ,('84193404', '8419.34.04', 'Túneles, de banda continua.', '6', 'Pza', '15.00', '0.00') - ,('84193405', '8419.34.05', 'De tabaco.', '6', 'Pza', '0.00', '0.00') - ,('84193499', '8419.34.99', 'Los demás.', '6', 'Pza', '10.00', '0.00') - ,('84193591', '8419.35.91', 'Los demás, para madera, pasta para papel, papel o cartón.', '6', 'Pza', '0.00', '0.00') - ,('84193999', '8419.39.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84194004', '8419.40.04', 'Columnas para la destilación fraccionada del aire.', '6', 'Pza', '5.00', '0.00') - ,('84194099', '8419.40.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84195003', '8419.50.03', 'Cambiadores o intercambiadores de temperatura con serpentines tubulares, excepto los constituidos por tubos de grafito impermeabilizados con resinas polimerizadas.', '6', 'Pza', '35.00', '0.00') - ,('84195099', '8419.50.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84196001', '8419.60.01', 'Aparatos y dispositivos para licuefacción de aire u otros gases.', '6', 'Pza', '0.00', '0.00') - ,('84198101', '8419.81.01', 'Cafeteras.', '6', 'Pza', '15.00', '0.00') - ,('84198103', '8419.81.03', 'Cocedores a vacío, freidores o sartenes con capacidad superior o igual a 58 l, incluso con mecanismo de volteo.', '6', 'Pza', '15.00', '0.00') - ,('84198199', '8419.81.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84198902', '8419.89.02', 'Esterilizadores o enfriadores, rotativos o continuos tipo atmosférico, para frutas, hortalizas o alimentos envasados, con peso unitario superior a 100 kg.', '6', 'Pza', '15.00', '0.00') - ,('84198903', '8419.89.03', 'Torres de enfriamiento, excepto las reconocibles como diseñadas para la separación y eliminación de contaminantes.', '6', 'Pza', '5.00', '0.00') - ,('84198909', '8419.89.09', 'Para hacer helados.', '6', 'Pza', '15.00', '0.00') - ,('84198910', '8419.89.10', 'Cubas de fermentación.', '6', 'Pza', '15.00', '0.00') - ,('84198911', '8419.89.11', 'Desodorizadores semicontinuos.', '6', 'Pza', '5.00', '0.00') - ,('84198915', '8419.89.15', 'Aparatos de torrefacción.', '6', 'Pza', '5.00', '0.00') - ,('84198917', '8419.89.17', 'Calentador para líquido térmico con o sin bomba para la circulación de fluido, con un rango de temperatura de 150°C a 375°C.', '6', 'Pza', '15.00', '0.00') - ,('84198918', '8419.89.18', 'Máquinas para escaldar o enfriar aves.', '6', 'Pza', '15.00', '0.00') - ,('84198919', '8419.89.19', 'Marmitas.', '6', 'Pza', '15.00', '0.00') - ,('84198999', '8419.89.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84199004', '8419.90.04', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('84201001', '8420.10.01', 'Calandrias y laminadores.', '6', 'Pza', '0.00', '0.00') - ,('84209102', '8420.91.02', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84209999', '8420.99.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('84211102', '8421.11.02', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84211202', '8421.12.02', 'Los demás.', '6', 'Pza', '10.00', '0.00') - ,('84211901', '8421.19.01', 'Turbinadoras para el refinado del azúcar.', '6', 'Pza', '15.00', '0.00') - ,('84211903', '8421.19.03', 'Centrífugas con canasta de eje de rotación vertical.', '6', 'Pza', '15.00', '0.00') - ,('84211999', '8421.19.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84212101', '8421.21.01', 'Reconocibles para naves aéreas.', '6', 'Pza', '0.00', '0.00') - ,('84212102', '8421.21.02', 'Depuradores de acción química a base de cloro (cloradores); depuradores magnéticos anticalcáreos para agua.', '6', 'Pza', '0.00', '0.00') - ,('84212104', '8421.21.04', 'Módulos de ósmosis inversa.', '6', 'Pza', '0.00', '0.00') - ,('84212199', '8421.21.99', 'Los demás.', '6', 'Pza', '5.00', '0.00') - ,('84212201', '8421.22.01', 'Para filtrar o depurar las demás bebidas.', '6', 'Pza', '0.00', '0.00') - ,('84212301', '8421.23.01', 'Para filtrar lubricantes o carburantes en los motores de encendido por chispa o compresión.', '1', 'Kg', '5.00', '0.00') - ,('84212901', '8421.29.01', 'Purificadores de líquidos o desaereadores, excepto columnas depuradoras de líquidos, utilizadas para la fabricación de fructosa, dextrosa, glucosa y almidón; y máquinas para filtrar o depurar ácido sulfúrico.', '6', 'Pza', '5.00', '0.00') - ,('84212902', '8421.29.02', 'Columnas depuradoras de líquidos, utilizadas para la fabricación de fructosa, dextrosa, glucosa y almidón.', '6', 'Pza', '0.00', '0.00') - ,('84212903', '8421.29.03', 'Depuradores ciclón.', '1', 'Kg', '5.00', '0.00') - ,('84212906', '8421.29.06', 'Máquinas para filtrar o depurar ácido sulfúrico.', '6', 'Pza', '0.00', '0.00') - ,('84212908', '8421.29.08', 'Filtros (dializadores) de sangre para riñón artificial, desechables.', '6', 'Pza', '0.00', '0.00') - ,('84212999', '8421.29.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('84213101', '8421.31.01', 'Reconocibles como diseñados exclusivamente para tractores agrícolas e industriales.', '1', 'Kg', '0.00', '0.00') - ,('84213102', '8421.31.02', 'Reconocibles para naves aéreas.', '1', 'Kg', '0.00', '0.00') - ,('84213199', '8421.31.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('84213201', '8421.32.01', 'Convertidores catalíticos.', '6', 'Pza', '0.00', '0.00') - ,('84213299', '8421.32.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('84213902', '8421.39.02', 'Filtros secadores, aun cuando tengan deshidratante, reconocibles como diseñados exclusivamente para refrigeradores domésticos o comerciales.', '1', 'Kg', '0.00', '0.00') - ,('84213903', '8421.39.03', 'Filtros para máscaras antigás.', '1', 'Kg', '0.00', '0.00') - ,('84213905', '8421.39.05', 'Filtros de aire, reconocibles como diseñados exclusivamente para acondicionadores de aire.', '1', 'Kg', '0.00', '0.00') - ,('84213906', '8421.39.06', 'Separadores de aceite, aun cuando tengan sistema de retorno de aceite al carter, reconocibles como diseñados exclusivamente para compresores.', '1', 'Kg', '0.00', '0.00') - ,('84213907', '8421.39.07', 'Desgasificadores.', '1', 'Kg', '0.00', '0.00') - ,('84213999', '8421.39.99', 'Las demás.', '1', 'Kg', '5.00', '0.00') - ,('84219104', '8421.91.04', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('84219999', '8421.99.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('84221101', '8422.11.01', 'De tipo doméstico.', '6', 'Pza', '10.00', '0.00') - ,('84221999', '8422.19.99', 'Las demás.', '6', 'Pza', '15.00', '0.00') - ,('84222001', '8422.20.01', 'Para limpiar botellas y otros recipientes, excepto lavadoras tipo túnel, de banda continua para envases metálicos, con capacidad superior a 1,500 unidades por minuto y lo comprendido en la fracción arancelaria 8422.20.02.', '6', 'Pza', '15.00', '0.00') - ,('84222002', '8422.20.02', 'Para lavar botellas de vidrio, cuya capacidad sea de 3 ml a 20 l.', '6', 'Pza', '5.00', '0.00') - ,('84222099', '8422.20.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84223008', '8422.30.08', 'Envasadoras rotativas de frutas.', '6', 'Pza', '5.00', '0.00') - ,('84223099', '8422.30.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84224002', '8422.40.02', 'De peso unitario igual o inferior a 100 kg, para introducir en cajas envases metálicos.', '6', 'Pza', '15.00', '0.00') - ,('84224099', '8422.40.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84229005', '8422.90.05', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('84231002', '8423.10.02', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('84232001', '8423.20.01', 'Automáticas, con registro impresor, provistas con mecanismo automático de control de velocidad y con peso superior o igual a 740 kg.', '6', 'Pza', '0.00', '0.00') - ,('84232099', '8423.20.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('84233002', '8423.30.02', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('84238103', '8423.81.03', 'De funcionamiento electrónico.', '6', 'Pza', '15.00', '0.00') - ,('84238203', '8423.82.03', 'De funcionamiento electrónico.', '6', 'Pza', '15.00', '0.00') - ,('84238999', '8423.89.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('84239002', '8423.90.02', 'Pesas para toda clase de básculas o balanzas; partes de aparatos o instrumentos de pesar.', '1', 'Kg', '0.00', '0.00') - ,('84241003', '8424.10.03', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84242001', '8424.20.01', 'Pulverizadores, espolvoreadores, esparcidores o aspersores, excepto los reconocibles para naves aéreas.', '6', 'Pza', '15.00', '0.00') - ,('84242099', '8424.20.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84243004', '8424.30.04', 'Pistolas o inyectores de lodos, aun cuando se presenten con sus mangueras de cementación y circulación, reconocibles para remover residuos y sedimentaciones en presas o tanques almacenadores de lodos en pozos petroleros.', '6', 'Pza', '15.00', '0.00') - ,('84243099', '8424.30.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84244102', '8424.41.02', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84244999', '8424.49.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84248207', '8424.82.07', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84248999', '8424.89.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84249001', '8424.90.01', 'Partes.', '1', 'Kg', '0.00', '0.00') - ,('84251101', '8425.11.01', 'Con capacidad superior a 30 t.', '6', 'Pza', '0.00', '0.00') - ,('84251199', '8425.11.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('84251999', '8425.19.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('84253101', '8425.31.01', 'Con capacidad hasta 5,000 kg.', '6', 'Pza', '15.00', '0.00') - ,('84253102', '8425.31.02', 'Tornos para el ascenso y descenso de jaulas o montacargas en pozos de minas, o diseñados para el interior de las minas, con capacidad superior a 5,000 kg.', '6', 'Pza', '5.00', '0.00') - ,('84253199', '8425.31.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84253901', '8425.39.01', 'Para elevadores o montacargas, accionados sin engranajes.', '6', 'Pza', '0.00', '0.00') - ,('84253902', '8425.39.02', 'Para elevadores o montacargas accionados con engranajes.', '6', 'Pza', '5.00', '0.00') - ,('84253903', '8425.39.03', 'Reconocibles como diseñados exclusivamente para tractores agrícolas e industriales.', '6', 'Pza', '0.00', '0.00') - ,('84253999', '8425.39.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('84254101', '8425.41.01', 'Elevadores fijos para vehículos automóviles, de los tipos utilizados en talleres.', '6', 'Pza', '15.00', '0.00') - ,('84254201', '8425.42.01', 'Tipo patín, aun cuando se presenten sin ruedas, con una o más bombas integrales, de peso mayor a 12 kg y capacidad de carga hasta 12 t.', '6', 'Pza', '15.00', '0.00') - ,('84254202', '8425.42.02', 'Tipo botella con bomba integral, de peso unitario igual o inferior a 20 kg y capacidad máxima de carga de 20 t.', '6', 'Pza', '15.00', '0.00') - ,('84254299', '8425.42.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84254999', '8425.49.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('84261101', '8426.11.01', 'Puentes (incluidas las vigas) rodantes, sobre soporte fijo.', '6', 'Pza', '0.00', '0.00') - ,('84261201', '8426.12.01', 'Pórticos móviles sobre neumáticos y carretillas puente.', '6', 'Pza', '5.00', '0.00') - ,('84261999', '8426.19.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84262001', '8426.20.01', 'Grúas de torre.', '6', 'Pza', '0.00', '0.00') - ,('84263001', '8426.30.01', 'Grúas de pórtico.', '6', 'Pza', '0.00', '0.00') - ,('84264101', '8426.41.01', 'Grúas, con brazo de hierro estructural (celosía), de accionamiento mecánico, autopropulsadas con peso unitario hasta de 55 t, excepto grúas, con pluma tipo celosía y operación diésel-eléctrica o diésel-hidráulica, con capacidad de carga de 40 a 600 t, inclusive.', '6', 'Pza', '5.00', '0.00') - ,('84264102', '8426.41.02', 'Grúas, con brazo (aguilón) rígido, de accionamiento hidráulico, autopropulsadas, con capacidad de carga superior a 9.9 t sin exceder de 30 t.', '6', 'Pza', '5.00', '0.00') - ,('84264199', '8426.41.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84264999', '8426.49.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84269103', '8426.91.03', 'Grúas elevadoras aisladas del tipo canastilla, con capacidad de carga hasta 1 t y hasta 15 m de elevación.', '6', 'Pza', '5.00', '0.00') - ,('84269199', '8426.91.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84269903', '8426.99.03', 'Cargador frontal para montarse en tractores agrícolas (de rastrillo, cuchara de reja o de apilador).', '6', 'Pza', '5.00', '0.00') - ,('84269904', '8426.99.04', 'Ademes caminantes.', '6', 'Pza', '15.00', '0.00') - ,('84269999', '8426.99.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84271001', '8427.10.01', 'Montacargas de carga frontal y unidad motriz trasera (denominado “counterbalance”), con capacidad de carga hasta 3,500 kg.', '6', 'Pza', '15.00', '0.00') - ,('84271099', '8427.10.99', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('84272001', '8427.20.01', 'Carretilla con motor de explosión o combustión interna con capacidad de carga hasta 7,000 kg, medida a 620 mm de la cara frontal de las horquillas, excepto lo comprendido en la fracción arancelaria 8427.20.04.', '6', 'Pza', '15.00', '0.00') - ,('84272002', '8427.20.02', 'Con motor de explosión o combustión interna, con capacidad de carga superior a 7,000 kg, medida a 620 mm de la cara frontal de las horquillas, excepto lo comprendido en la fracción arancelaria 8427.20.05.', '6', 'Pza', '0.00', '0.00') - ,('84272003', '8427.20.03', 'Carretilla portadora con cabina, con motor de combustión interna (diésel), con plataforma, con ejes direccionables y capacidad de carga superior a 39 t.', '6', 'Pza', '0.00', '0.00') - ,('84272004', '8427.20.04', 'Montacargas de carga frontal y unidad motriz trasera (denominado "counterbalance"), con motor de explosión o combustión interna, con capacidad de carga hasta 7,000 kg.', '6', 'Pza', '15.00', '0.00') - ,('84272005', '8427.20.05', 'Montacargas de carga frontal y unidad motriz trasera (denominado "counterbalance"), con motor de explosión o combustión interna, con capacidad de carga superior a 7,000 kg.', '6', 'Pza', '0.00', '0.00') - ,('84272099', '8427.20.99', 'Las demás.', '6', 'Pza', '5.00', '0.00') - ,('84279091', '8427.90.91', 'Las demás carretillas.', '6', 'Pza', '15.00', '0.00') - ,('84281001', '8428.10.01', 'Ascensores y montacargas.', '6', 'Pza', '15.00', '0.00') - ,('84282002', '8428.20.02', 'Con dispositivo dosificador, excepto para carga o descarga de navíos.', '6', 'Pza', '15.00', '0.00') - ,('84282003', '8428.20.03', 'Para el manejo de documentos y valores.', '6', 'Pza', '15.00', '0.00') - ,('84282099', '8428.20.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84283101', '8428.31.01', 'Especialmente diseñados para el interior de minas u otros trabajos subterráneos.', '6', 'Pza', '0.00', '0.00') - ,('84283291', '8428.32.91', 'Los demás, de cangilones.', '6', 'Pza', '0.00', '0.00') - ,('84283391', '8428.33.91', 'Los demás, de banda o correa.', '6', 'Pza', '0.00', '0.00') - ,('84283999', '8428.39.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84284002', '8428.40.02', 'Escaleras electromecánicas.', '1', 'Kg', '15.00', '0.00') - ,('84284099', '8428.40.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('84286001', '8428.60.01', 'Teleféricos (incluidos las telesillas y los telesquís); mecanismos de tracción para funiculares.', '6', 'Pza', '0.00', '0.00') - ,('84287001', '8428.70.01', 'Robots industriales.', '6', 'Pza', '15.00', '0.00') - ,('84289001', '8428.90.01', 'Elevador de accionamiento hidráulico, de chapas.', '6', 'Pza', '0.00', '0.00') - ,('84289002', '8428.90.02', 'Paleadoras de accionamiento neumático o hidráulico, provistas para su desplazamiento de ruedas de ferrocarril.', '6', 'Pza', '0.00', '0.00') - ,('84289003', '8428.90.03', 'Transportadores-cargadores autopropulsados, de peso unitario superior o igual a 10,000 kg.', '6', 'Pza', '0.00', '0.00') - ,('84289006', '8428.90.06', 'Empujadores de vagonetas de minas, carros transbordadores, basculadores y volteadores, de vagones, de vagonetas, etc., e instalaciones similares para la manipulación de material móvil sobre carriles (rieles).', '6', 'Pza', '0.00', '0.00') - ,('84289099', '8428.90.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('84291101', '8429.11.01', 'De orugas.', '6', 'Pza', '0.00', '0.00') - ,('84291999', '8429.19.99', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('84292001', '8429.20.01', 'Niveladoras.', '6', 'Pza', '15.00', '0.00') - ,('84293001', '8429.30.01', 'Traíllas ("scrapers").', '6', 'Pza', '0.00', '0.00') - ,('84294002', '8429.40.02', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84295103', '8429.51.03', 'Palas mecánicas, excepto autopropulsadas sobre orugas, con peso unitario superior a 55,000 kg.', '6', 'Pza', '15.00', '0.00') - ,('84295199', '8429.51.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84295203', '8429.52.03', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84295901', '8429.59.01', 'Zanjadoras.', '6', 'Pza', '15.00', '0.00') - ,('84295902', '8429.59.02', 'Dragas, con capacidad de carga de arrastre hasta 4,000 kg.', '6', 'Pza', '15.00', '0.00') - ,('84295905', '8429.59.05', 'Dragas o excavadoras, excepto lo comprendido en la fracción arancelaria 8429.59.02.', '6', 'Pza', '15.00', '0.00') - ,('84295999', '8429.59.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84301001', '8430.10.01', 'Martinetes y máquinas para arrancar pilotes, estacas o similares.', '6', 'Pza', '5.00', '0.00') - ,('84302001', '8430.20.01', 'Quitanieves.', '6', 'Pza', '10.00', '0.00') - ,('84303103', '8430.31.03', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('84303901', '8430.39.01', 'Escudos de perforación.', '6', 'Pza', '5.00', '0.00') - ,('84303999', '8430.39.99', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('84304103', '8430.41.03', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('84304999', '8430.49.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('84305001', '8430.50.01', 'Excavadoras, cargadores frontales de accionamiento hidráulico, con capacidad igual o inferior a 335 CP.', '6', 'Pza', '15.00', '0.00') - ,('84305002', '8430.50.02', 'Desgarradores.', '6', 'Pza', '15.00', '0.00') - ,('84305099', '8430.50.99', 'Los demás.', '6', 'Pza', '10.00', '0.00') - ,('84306101', '8430.61.01', 'Explanadoras (empujadoras).', '6', 'Pza', '15.00', '0.00') - ,('84306102', '8430.61.02', 'Rodillos apisonadores o compactadores.', '6', 'Pza', '15.00', '0.00') - ,('84306199', '8430.61.99', 'Los demás.', '6', 'Pza', '10.00', '0.00') - ,('84306901', '8430.69.01', 'Retroexcavadoras de cucharón, de accionamiento hidráulico.', '6', 'Pza', '15.00', '0.00') - ,('84306902', '8430.69.02', 'Dragas, excavadoras y palas mecánicas, montadas sobre vagón de neumáticos.', '6', 'Pza', '15.00', '0.00') - ,('84306903', '8430.69.03', 'Zanjadoras, excepto lo comprendido en la fracción arancelaria 8430.69.01.', '6', 'Pza', '15.00', '0.00') - ,('84306904', '8430.69.04', 'Traíllas ("scrapers").', '6', 'Pza', '15.00', '0.00') - ,('84306999', '8430.69.99', 'Los demás.', '6', 'Pza', '10.00', '0.00') - ,('84311001', '8431.10.01', 'De máquinas o aparatos de la partida 84.25.', '1', 'Kg', '0.00', '0.00') - ,('84312002', '8431.20.02', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('84313102', '8431.31.02', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('84313999', '8431.39.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('84314104', '8431.41.04', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('84314201', '8431.42.01', 'Hojas de topadoras frontales (buldóceres) o de topadoras angulares ("angledozers").', '6', 'Pza', '0.00', '0.00') - ,('84314303', '8431.43.03', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('84314999', '8431.49.99', 'Las demás.', '1', 'Kg', '25.00', '0.00') - ,('84321001', '8432.10.01', 'Arados.', '6', 'Pza', '0.00', '0.00') - ,('84322101', '8432.21.01', 'Gradas (rastras) de discos.', '6', 'Pza', '0.00', '0.00') - ,('84322999', '8432.29.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84323104', '8432.31.04', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('84323999', '8432.39.99', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('84324101', '8432.41.01', 'Esparcidores de estiércol.', '6', 'Pza', '0.00', '0.00') - ,('84324201', '8432.42.01', 'Distribuidores de abonos.', '6', 'Pza', '0.00', '0.00') - ,('84328091', '8432.80.91', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84329001', '8432.90.01', 'Partes.', '1', 'Kg', '0.00', '0.00') - ,('84331101', '8433.11.01', 'Con motor, en las que el dispositivo de corte gire en un plano horizontal.', '6', 'Pza', '15.00', '0.00') - ,('84331999', '8433.19.99', 'Las demás.', '6', 'Pza', '15.00', '0.00') - ,('84332003', '8433.20.03', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84333091', '8433.30.91', 'Las demás máquinas y aparatos de henificar.', '6', 'Pza', '0.00', '0.00') - ,('84334003', '8433.40.03', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('84335101', '8433.51.01', 'Cosechadoras-trilladoras.', '6', 'Pza', '0.00', '0.00') - ,('84335291', '8433.52.91', 'Las demás máquinas y aparatos de trillar.', '6', 'Pza', '0.00', '0.00') - ,('84335301', '8433.53.01', 'Máquinas de cosechar raíces o tubérculos.', '6', 'Pza', '0.00', '0.00') - ,('84335999', '8433.59.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84336004', '8433.60.04', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84339004', '8433.90.04', 'Los demás.', '1', 'Kg', '25.00', '0.00') - ,('84341001', '8434.10.01', 'Máquinas de ordeñar.', '6', 'Pza', '0.00', '0.00') - ,('84342001', '8434.20.01', 'Máquinas y aparatos para la industria lechera.', '6', 'Pza', '0.00', '0.00') - ,('84349001', '8434.90.01', 'Partes.', '1', 'Kg', '0.00', '0.00') - ,('84351001', '8435.10.01', 'Máquinas y aparatos.', '6', 'Pza', '0.00', '0.00') - ,('84359001', '8435.90.01', 'Partes.', '1', 'Kg', '0.00', '0.00') - ,('84361001', '8436.10.01', 'Máquinas y aparatos para preparar alimentos o piensos para animales.', '6', 'Pza', '0.00', '0.00') - ,('84362101', '8436.21.01', 'Incubadoras y criadoras.', '6', 'Pza', '0.00', '0.00') - ,('84362999', '8436.29.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84368091', '8436.80.91', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84369101', '8436.91.01', 'De máquinas o aparatos para la avicultura.', '1', 'Kg', '0.00', '0.00') - ,('84369999', '8436.99.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('84371004', '8437.10.04', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84378091', '8437.80.91', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84379002', '8437.90.02', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('84381001', '8438.10.01', 'Artesas mecánicas, incluso con dispositivos de calentamiento o enfriamiento.', '6', 'Pza', '15.00', '0.00') - ,('84381003', '8438.10.03', 'Automáticas para fabricar galletas.', '6', 'Pza', '15.00', '0.00') - ,('84381004', '8438.10.04', 'Batidoras, reconocibles como diseñadas exclusivamente para la industria de la panificación.', '6', 'Pza', '15.00', '0.00') - ,('84381099', '8438.10.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84382003', '8438.20.03', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84383001', '8438.30.01', 'Cubas provistas de agitadores; desfibradores; trituradoras (desmenuzadoras), molinos.', '6', 'Pza', '15.00', '0.00') - ,('84383099', '8438.30.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84384002', '8438.40.02', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('84385006', '8438.50.06', 'Mezcladoras de carne, de peso unitario igual o inferior a 100 kg.', '6', 'Pza', '15.00', '0.00') - ,('84385099', '8438.50.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84386004', '8438.60.04', 'Peladoras de papas.', '6', 'Pza', '5.00', '0.00') - ,('84386099', '8438.60.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84388091', '8438.80.91', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84389006', '8438.90.06', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('84391006', '8439.10.06', 'Máquinas y aparatos para la fabricación de pasta de materias fibrosas celulósicas.', '6', 'Pza', '0.00', '0.00') - ,('84392001', '8439.20.01', 'Máquinas y aparatos para la fabricación de papel o cartón.', '6', 'Pza', '0.00', '0.00') - ,('84393001', '8439.30.01', 'Máquinas y aparatos para el acabado de papel o cartón.', '6', 'Pza', '0.00', '0.00') - ,('84399101', '8439.91.01', 'De máquinas o aparatos para la fabricación de pasta de materias fibrosas celulósicas.', '1', 'Kg', '0.00', '0.00') - ,('84399999', '8439.99.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('84401002', '8440.10.02', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84409001', '8440.90.01', 'Partes.', '1', 'Kg', '0.00', '0.00') - ,('84411004', '8441.10.04', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84412001', '8441.20.01', 'Máquinas para la fabricación de sacos (bolsas), bolsitas o sobres.', '6', 'Pza', '0.00', '0.00') - ,('84413001', '8441.30.01', 'Máquinas para la fabricación de cajas, tubos, tambores o continentes similares, excepto por moldeado.', '6', 'Pza', '0.00', '0.00') - ,('84414002', '8441.40.02', 'Máquinas para moldear artículos de pasta de papel, de papel o cartón.', '6', 'Pza', '0.00', '0.00') - ,('84418091', '8441.80.91', 'Las demás máquinas y aparatos.', '6', 'Pza', '0.00', '0.00') - ,('84419001', '8441.90.01', 'Partes.', '1', 'Kg', '0.00', '0.00') - ,('84423003', '8442.30.03', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('84424001', '8442.40.01', 'Partes de estas máquinas, aparatos o material.', '1', 'Kg', '0.00', '0.00') - ,('84425001', '8442.50.01', 'Planchas trimetálicas preparadas.', '1', 'Kg', '15.00', '0.00') - ,('84425003', '8442.50.03', 'Láminas (planchas flexibles) de aluminio, tratadas sin sensibilizar, para utilizarse en fotolitografía "offset".', '6', 'Pza', '5.00', '0.00') - ,('84425099', '8442.50.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('84431102', '8443.11.02', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84431201', '8443.12.01', 'Máquinas y aparatos de oficina para imprimir, offset, alimentados con hojas en las que un lado sea inferior o igual a 22 cm y el otro sea inferior o igual a 36 cm, medidas sin plegar.', '6', 'Pza', '0.00', '0.00') - ,('84431301', '8443.13.01', 'Para oficina.', '6', 'Pza', '5.00', '0.00') - ,('84431399', '8443.13.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84431402', '8443.14.02', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84431502', '8443.15.02', 'Máquinas y aparatos para imprimir, tipográficos, distintos de los alimentados con bobinas, excepto las máquinas y aparatos flexográficos.', '6', 'Pza', '0.00', '0.00') - ,('84431601', '8443.16.01', 'Máquinas y aparatos para imprimir, flexográficos.', '6', 'Pza', '0.00', '0.00') - ,('84431701', '8443.17.01', 'Máquinas y aparatos para imprimir, heliográficos (huecograbado).', '6', 'Pza', '0.00', '0.00') - ,('84431999', '8443.19.99', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('84433101', '8443.31.01', 'Máquinas que efectúen dos o más de las siguientes funciones: impresión, copia o fax, aptas para ser conectadas a una máquina automática para tratamiento o procesamiento de datos o a una red.', '6', 'Pza', '0.00', '0.00') - ,('84433291', '8443.32.91', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84433902', '8443.39.02', 'Aparatos de fotocopia electrostáticos, por procedimiento directo (reproducción directa del original) excepto aparatos de fotocopia por sistema óptico.', '6', 'Pza', '15.00', '0.00') - ,('84433906', '8443.39.06', 'Aparatos de termocopia.', '6', 'Pza', '15.00', '0.00') - ,('84433991', '8443.39.91', 'Los demás aparatos de fotocopia de contacto.', '6', 'Pza', '15.00', '0.00') - ,('84433999', '8443.39.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84439102', '8443.91.02', 'Reconocibles como diseñadas exclusivamente para máquinas para impresión por serigrafía.', '1', 'Kg', '0.00', '0.00') - ,('84439199', '8443.91.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84439905', '8443.99.05', 'Reconocibles como diseñadas exclusivamente para aparatos de fotocopia por sistema óptico y lo comprendido en la fracción arancelaria 8443.39.06.', '1', 'Kg', '0.00', '0.00') - ,('84439906', '8443.99.06', 'Circuitos modulares.', '1', 'Kg', '0.00', '0.00') - ,('84439910', '8443.99.10', 'Reconocibles como diseñadas para aparatos de fotocopia: alimentadores automáticos de documentos; alimentadores de papel; clasificadores.', '1', 'Kg', '0.00', '0.00') - ,('84439999', '8443.99.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84440001', '8444.00.01', 'Máquinas para extrudir, estirar, texturar o cortar materia textil sintética o artificial.', '6', 'Pza', '0.00', '0.00') - ,('84451101', '8445.11.01', 'Cardas.', '6', 'Pza', '0.00', '0.00') - ,('84451201', '8445.12.01', 'Peinadoras.', '6', 'Pza', '0.00', '0.00') - ,('84451301', '8445.13.01', 'Mecheras.', '6', 'Pza', '0.00', '0.00') - ,('84451999', '8445.19.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84452001', '8445.20.01', 'Máquinas para hilar materia textil.', '6', 'Pza', '0.00', '0.00') - ,('84453002', '8445.30.02', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84454001', '8445.40.01', 'Máquinas para bobinar (incluidas las canilleras) o devanar materia textil.', '6', 'Pza', '0.00', '0.00') - ,('84459099', '8445.90.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84461001', '8446.10.01', 'Para tejidos de anchura inferior o igual a 30 cm.', '6', 'Pza', '0.00', '0.00') - ,('84462101', '8446.21.01', 'De motor.', '6', 'Pza', '0.00', '0.00') - ,('84462999', '8446.29.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84463001', '8446.30.01', 'Para tejidos de anchura superior a 30 cm, sin lanzadera.', '6', 'Pza', '0.00', '0.00') - ,('84471101', '8447.11.01', 'Con cilindro de diámetro inferior o igual a 165 mm.', '6', 'Pza', '0.00', '0.00') - ,('84471201', '8447.12.01', 'Con cilindro de diámetro superior a 165 mm.', '6', 'Pza', '0.00', '0.00') - ,('84472002', '8447.20.02', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84479099', '8447.90.99', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('84481101', '8448.11.01', 'Maquinitas para lizos y mecanismos Jacquard; reductoras, perforadoras y copiadoras de cartones; máquinas para unir cartones después de perforados.', '6', 'Pza', '0.00', '0.00') - ,('84481999', '8448.19.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84482001', '8448.20.01', 'Partes y accesorios de las máquinas de la partida 84.44 o de sus máquinas o aparatos auxiliares.', '1', 'Kg', '0.00', '0.00') - ,('84483101', '8448.31.01', 'Guarniciones de cardas.', '1', 'Kg', '0.00', '0.00') - ,('84483201', '8448.32.01', 'De máquinas para la preparación de materia textil, excepto las guarniciones de cardas.', '1', 'Kg', '0.00', '0.00') - ,('84483301', '8448.33.01', 'Husos y sus aletas, anillos y cursores.', '6', 'Pza', '0.00', '0.00') - ,('84483999', '8448.39.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('84484201', '8448.42.01', 'Peines, lizos y cuadros de lizos.', '1', 'Kg', '0.00', '0.00') - ,('84484999', '8448.49.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('84485101', '8448.51.01', 'Platinas, agujas y demás artículos que participen en la formación de mallas.', '1', 'Kg', '0.00', '0.00') - ,('84485999', '8448.59.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('84490001', '8449.00.01', 'Máquinas y aparatos para la fabricación o acabado del fieltro o tela sin tejer, en pieza o con forma, incluidas las máquinas y aparatos para la fabricación de sombreros de fieltro; hormas de sombrerería.', '6', 'Pza', '0.00', '0.00') - ,('84501101', '8450.11.01', 'De uso doméstico.', '6', 'Pza', '15.00', '0.00') - ,('84501199', '8450.11.99', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('84501291', '8450.12.91', 'Las demás.', '6', 'Pza', '15.00', '0.00') - ,('84501999', '8450.19.99', 'Las demás.', '6', 'Pza', '15.00', '0.00') - ,('84502001', '8450.20.01', 'Máquinas de capacidad unitaria, expresada en peso de ropa seca, superior a 10 kg.', '6', 'Pza', '0.00', '0.00') - ,('84509003', '8450.90.03', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('84511001', '8451.10.01', 'Máquinas para limpieza en seco.', '6', 'Pza', '0.00', '0.00') - ,('84512102', '8451.21.02', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('84512999', '8451.29.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84513001', '8451.30.01', 'Máquinas y prensas para planchar, incluidas las prensas para fijar.', '6', 'Pza', '0.00', '0.00') - ,('84514001', '8451.40.01', 'Máquinas para lavar, blanquear o teñir.', '6', 'Pza', '0.00', '0.00') - ,('84515001', '8451.50.01', 'Máquinas para enrollar, desenrollar, plegar, cortar o dentar telas.', '6', 'Pza', '0.00', '0.00') - ,('84518091', '8451.80.91', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84519003', '8451.90.03', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('84521001', '8452.10.01', 'Máquinas de coser domésticas.', '6', 'Pza', '15.00', '0.00') - ,('84522106', '8452.21.06', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('84522999', '8452.29.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84523001', '8452.30.01', 'Agujas para máquinas de coser.', '1', 'Kg', '0.00', '0.00') - ,('84529001', '8452.90.01', 'Partes reconocibles como diseñadas exclusivamente para máquinas de coser domésticas, excepto muebles, basamentos y tapas o cubiertas para máquinas de coser, y sus partes.', '1', 'Kg', '5.00', '0.00') - ,('84529099', '8452.90.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('84531001', '8453.10.01', 'Máquinas y aparatos para la preparación, curtido o trabajo de cuero o piel.', '6', 'Pza', '0.00', '0.00') - ,('84532001', '8453.20.01', 'Máquinas y aparatos para la fabricación o reparación de calzado.', '6', 'Pza', '0.00', '0.00') - ,('84538091', '8453.80.91', 'Las demás máquinas y aparatos.', '6', 'Pza', '0.00', '0.00') - ,('84539001', '8453.90.01', 'Partes.', '1', 'Kg', '0.00', '0.00') - ,('84541001', '8454.10.01', 'Convertidores.', '6', 'Pza', '0.00', '0.00') - ,('84542002', '8454.20.02', 'Lingoteras y cucharas de colada.', '1', 'Kg', '0.00', '0.00') - ,('84543002', '8454.30.02', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84549002', '8454.90.02', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('84551001', '8455.10.01', 'Laminadores de tubos.', '6', 'Pza', '0.00', '0.00') - ,('84552103', '8455.21.03', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84552203', '8455.22.03', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84553003', '8455.30.03', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84559001', '8455.90.01', 'Obtenidas por fundición o por soldadura, con un peso individual inferior a 90 t, reconocibles como diseñadas exclusivamente para lo comprendido en la partida 84.55.', '1', 'Kg', '0.00', '0.00') - ,('84559099', '8455.90.99', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('84561102', '8456.11.02', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84561202', '8456.12.02', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84562002', '8456.20.02', 'Que operen por ultrasonido.', '6', 'Pza', '0.00', '0.00') - ,('84563001', '8456.30.01', 'Que operen por electroerosión.', '6', 'Pza', '0.00', '0.00') - ,('84564001', '8456.40.01', 'Que operen mediante chorro de plasma.', '6', 'Pza', '0.00', '0.00') - ,('84565001', '8456.50.01', 'Máquinas para cortar por chorro de agua.', '6', 'Pza', '0.00', '0.00') - ,('84569099', '8456.90.99', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('84571001', '8457.10.01', 'Centros de mecanizado.', '6', 'Pza', '0.00', '0.00') - ,('84572001', '8457.20.01', 'Máquinas de puesto fijo.', '6', 'Pza', '0.00', '0.00') - ,('84573002', '8457.30.02', 'Con mesa estática o pendular que realicen de manera alternativa o simultánea, dos o más operaciones, de peso unitario inferior o igual a 10,000 kg.', '6', 'Pza', '15.00', '0.00') - ,('84573003', '8457.30.03', 'Con mesa de transferencia lineal o rotativa (máquinas “transfer”) de peso unitario inferior o igual a 10,000 kg.', '6', 'Pza', '15.00', '0.00') - ,('84573099', '8457.30.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84581101', '8458.11.01', 'Paralelos universales, con distancia entre puntos hasta de 4.5 m y con capacidad de volteo hasta de 750 mm, de diámetro sobre la bancada.', '6', 'Pza', '15.00', '0.00') - ,('84581199', '8458.11.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84581901', '8458.19.01', 'Paralelos universales, con distancia entre puntos hasta de 4.5 m y con capacidad de volteo hasta de 750 mm de diámetro sobre la bancada.', '6', 'Pza', '15.00', '0.00') - ,('84581999', '8458.19.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84589102', '8458.91.02', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84589999', '8458.99.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84591002', '8459.10.02', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84592102', '8459.21.02', 'De control numérico.', '6', 'Pza', '0.00', '0.00') - ,('84592999', '8459.29.99', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('84593101', '8459.31.01', 'De control numérico.', '6', 'Pza', '0.00', '0.00') - ,('84593999', '8459.39.99', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('84594101', '8459.41.01', 'Máquinas para la reconstrucción de culatas de motor de explosión o de combustión interna, de un husillo, con cabezal flotante o de mesa neumática y portapiezas basculante en dos ejes.', '6', 'Pza', '15.00', '0.00') - ,('84594199', '8459.41.99', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('84594901', '8459.49.01', 'Máquinas para la reconstrucción de culatas de motor de explosión o de combustión interna, de un husillo, con cabezal flotante o de mesa neumática y portapiezas basculante en dos ejes.', '6', 'Pza', '15.00', '0.00') - ,('84594999', '8459.49.99', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('84595101', '8459.51.01', 'De control numérico.', '6', 'Pza', '15.00', '0.00') - ,('84595999', '8459.59.99', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('84596101', '8459.61.01', 'De control numérico.', '6', 'Pza', '0.00', '0.00') - ,('84596999', '8459.69.99', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('84597091', '8459.70.91', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('84601202', '8460.12.02', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('84601999', '8460.19.99', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('84602201', '8460.22.01', 'Máquinas de rectificar sin centro, de control numérico.', '6', 'Pza', '0.00', '0.00') - ,('84602391', '8460.23.91', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('84602491', '8460.24.91', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('84602999', '8460.29.99', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('84603102', '8460.31.02', 'De control numérico.', '6', 'Pza', '0.00', '0.00') - ,('84603999', '8460.39.99', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('84604003', '8460.40.03', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('84609099', '8460.90.99', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('84612002', '8461.20.02', 'Máquinas de limar o mortajar.', '6', 'Pza', '0.00', '0.00') - ,('84613002', '8461.30.02', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('84614001', '8461.40.01', 'Máquinas de tallar o acabar engranajes.', '6', 'Pza', '0.00', '0.00') - ,('84615002', '8461.50.02', 'Serradoras de disco o de cinta sinfín, excepto de control numérico.', '6', 'Pza', '15.00', '0.00') - ,('84615003', '8461.50.03', 'Serradoras hidráulicas alternativas, excepto de control numérico.', '6', 'Pza', '15.00', '0.00') - ,('84615099', '8461.50.99', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('84619003', '8461.90.03', 'Máquinas de cepillar, de codo para metales, con carrera máxima del carro hasta 350 mm, excepto de control numérico.', '6', 'Pza', '15.00', '0.00') - ,('84619099', '8461.90.99', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('84621101', '8462.11.01', 'Hidráulicas con capacidad (de presión de trabajo) hasta 1,000 t.', '6', 'Pza', '15.00', '0.00') - ,('84621199', '8462.11.99', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('84621901', '8462.19.01', 'Hidráulicas con capacidad (de presión de trabajo) hasta 1,000 t.', '6', 'Pza', '15.00', '0.00') - ,('84621999', '8462.19.99', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('84622201', '8462.22.01', 'Prensas mecánicas con capacidad inferior o igual a 200 t.', '6', 'Pza', '15.00', '0.00') - ,('84622299', '8462.22.99', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('84622301', '8462.23.01', 'Prensas mecánicas con capacidad inferior o igual a 200 t.', '6', 'Pza', '15.00', '0.00') - ,('84622399', '8462.23.99', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('84622401', '8462.24.01', 'Prensas para paneles, de control numérico.', '6', 'Pza', '0.00', '0.00') - ,('84622501', '8462.25.01', 'Máquinas de perfilar rodillos, de control numérico.', '6', 'Pza', '0.00', '0.00') - ,('84622601', '8462.26.01', 'Enderezadoras de alambre o alambrón.', '6', 'Pza', '15.00', '0.00') - ,('84622602', '8462.26.02', 'Dobladoras de tubos, accionadas con motor, para tubos con diámetro igual o inferior a 70 mm y espesor de pared igual o inferior a 6.5 mm.', '6', 'Pza', '15.00', '0.00') - ,('84622603', '8462.26.03', 'Prensas mecánicas con capacidad inferior o igual a 200 t.', '6', 'Pza', '15.00', '0.00') - ,('84622699', '8462.26.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84622901', '8462.29.01', 'Enderezadoras de alambre o alambrón.', '6', 'Pza', '15.00', '0.00') - ,('84622903', '8462.29.03', 'Prensas hidráulicas con capacidad (de presión de trabajo) hasta 1,000 t, excepto de control numérico.', '6', 'Pza', '15.00', '0.00') - ,('84622905', '8462.29.05', 'Dobladoras (plegadoras) de accionamiento mecánico, con motor, excepto de control numérico.', '6', 'Pza', '15.00', '0.00') - ,('84622906', '8462.29.06', 'Dobladoras de tubos accionadas con motor, para tubos con diámetro igual o inferior a 70 mm y espesor de pared igual o inferior a 6.5 mm.', '6', 'Pza', '15.00', '0.00') - ,('84622907', '8462.29.07', 'Prensas mecánicas con capacidad inferior o igual a 200 t.', '6', 'Pza', '15.00', '0.00') - ,('84622999', '8462.29.99', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('84623201', '8462.32.01', 'Líneas de hendido y líneas de corte longitudinal.', '6', 'Pza', '0.00', '0.00') - ,('84623301', '8462.33.01', 'Máquinas de cizallar, de control numérico.', '6', 'Pza', '15.00', '0.00') - ,('84623901', '8462.39.01', 'Cizallas o guillotinas; cortadoras de alambre o alambrón.', '6', 'Pza', '15.00', '0.00') - ,('84623903', '8462.39.03', 'Prensas mecánicas con capacidad inferior o igual a 200 t.', '6', 'Pza', '15.00', '0.00') - ,('84623999', '8462.39.99', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('84624201', '8462.42.01', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('84624901', '8462.49.01', 'Prensas hidráulicas con capacidad (de presión de trabajo) hasta 1,000 t.', '6', 'Pza', '15.00', '0.00') - ,('84624902', '8462.49.02', 'Prensas mecánicas con capacidad inferior o igual a 200 t.', '6', 'Pza', '15.00', '0.00') - ,('84624999', '8462.49.99', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('84625101', '8462.51.01', 'Dobladoras de tubos, accionadas con motor, para tubos con diámetro igual o inferior a 70 mm y espesor de pared igual o inferior a 6.5 mm.', '6', 'Pza', '15.00', '0.00') - ,('84625102', '8462.51.02', 'Cizallas o guillotinas.', '6', 'Pza', '15.00', '0.00') - ,('84625199', '8462.51.99', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('84625901', '8462.59.01', 'Dobladoras (plegadoras) de accionamiento mecánico, con motor.', '6', 'Pza', '15.00', '0.00') - ,('84625902', '8462.59.02', 'Dobladoras de tubos accionadas con motor, para tubos con diámetro igual o inferior a 70 mm y espesor de pared igual o inferior a 6.5 mm.', '6', 'Pza', '15.00', '0.00') - ,('84625903', '8462.59.03', 'Cizallas o guillotinas; cortadoras de alambre o alambrón.', '6', 'Pza', '15.00', '0.00') - ,('84625999', '8462.59.99', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('84626101', '8462.61.01', 'Prensas hidráulicas con capacidad (de presión de trabajo) hasta 1,000 t.', '6', 'Pza', '15.00', '0.00') - ,('84626199', '8462.61.99', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('84626201', '8462.62.01', 'Prensas mecánicas con capacidad inferior o igual a 200 t, excepto para punzonar o entallar.', '6', 'Pza', '15.00', '0.00') - ,('84626299', '8462.62.99', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('84626301', '8462.63.01', 'Servoprensas.', '6', 'Pza', '0.00', '0.00') - ,('84626999', '8462.69.99', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('84629099', '8462.90.99', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('84631001', '8463.10.01', 'Bancos de estirar barras, tubos, perfiles, alambres o similares.', '6', 'Pza', '0.00', '0.00') - ,('84632001', '8463.20.01', 'Máquinas laminadoras de hacer roscas.', '6', 'Pza', '0.00', '0.00') - ,('84633004', '8463.30.04', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('84639099', '8463.90.99', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('84641001', '8464.10.01', 'Máquinas de aserrar.', '6', 'Pza', '0.00', '0.00') - ,('84642001', '8464.20.01', 'Máquinas de amolar o pulir.', '6', 'Pza', '0.00', '0.00') - ,('84649099', '8464.90.99', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('84651001', '8465.10.01', 'Máquinas que efectúen distintas operaciones de mecanizado sin cambio de útil entre dichas operaciones.', '6', 'Pza', '0.00', '0.00') - ,('84652001', '8465.20.01', 'Centros de mecanizado.', '6', 'Pza', '0.00', '0.00') - ,('84659101', '8465.91.01', 'De cinta sinfín, de disco o alternativas.', '6', 'Pza', '5.00', '0.00') - ,('84659199', '8465.91.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84659204', '8465.92.04', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('84659302', '8465.93.02', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('84659403', '8465.94.03', 'Máquinas de curvar o ensamblar.', '6', 'Pza', '0.00', '0.00') - ,('84659501', '8465.95.01', 'Taladradoras o escopleadoras.', '6', 'Pza', '15.00', '0.00') - ,('84659599', '8465.95.99', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('84659601', '8465.96.01', 'Máquinas de hendir, rebanar o desenrollar.', '6', 'Pza', '0.00', '0.00') - ,('84659902', '8465.99.02', 'Descortezadoras de rollizos.', '6', 'Pza', '5.00', '0.00') - ,('84659999', '8465.99.99', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('84661003', '8466.10.03', 'Portatroqueles.', '1', 'Kg', '15.00', '0.00') - ,('84661099', '8466.10.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('84662002', '8466.20.02', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('84663003', '8466.30.03', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('84669101', '8466.91.01', 'Para máquinas de la partida 84.64.', '1', 'Kg', '0.00', '0.00') - ,('84669201', '8466.92.01', 'Para máquinas de la partida 84.65.', '1', 'Kg', '0.00', '0.00') - ,('84669303', '8466.93.03', 'Reconocibles como diseñadas exclusiva o principalmente para taladros de columna o banco con transmisión por bandas o engranados.', '1', 'Kg', '15.00', '0.00') - ,('84669304', '8466.93.04', 'Cama, base, mesa, cabezal, contrapunto, arnés, cuna, carros deslizantes, columna, brazo, brazo de sierra, cabezal de rueda, "carnero", armazón, montante, lunetas, husillo, obtenidos por fundición, soldadura o forjado.', '6', 'Pza', '0.00', '0.00') - ,('84669399', '8466.93.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('84669401', '8466.94.01', 'Cama, base, mesa, columna, cuna, armazón, corona, carro deslizante, flecha, bastidor, obtenidos por fundición, soldadura o forjado.', '6', 'Pza', '0.00', '0.00') - ,('84669499', '8466.94.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('84671102', '8467.11.02', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('84671999', '8467.19.99', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('84672101', '8467.21.01', 'Taladros, con capacidad de entrada de 6.35, 9.52 o 12.70 mm.', '6', 'Pza', '15.00', '0.00') - ,('84672102', '8467.21.02', 'Taladros, excepto lo comprendido en la fracción arancelaria 8467.21.01.', '6', 'Pza', '0.00', '0.00') - ,('84672103', '8467.21.03', 'Perforadoras por percusión y rotación (rotomartillos), con potencia inferior o igual a ½ CP.', '6', 'Pza', '15.00', '0.00') - ,('84672199', '8467.21.99', 'Los demás.', '6', 'Pza', '5.00', '0.00') - ,('84672202', '8467.22.02', 'Sierras de disco con potencia del motor igual o inferior a 2.33 CP.', '6', 'Pza', '5.00', '0.00') - ,('84672203', '8467.22.03', 'Sierra caladora, con potencia inferior o igual a 0.4 CP.', '6', 'Pza', '15.00', '0.00') - ,('84672299', '8467.22.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84672901', '8467.29.01', 'Esmeriladoras con un mínimo de 4,000 RPM a un máximo de 8,000 RPM, con capacidad de 8 a 25 amperes y de 100 a 150 V, con peso de 4 kg a 8 kg.', '6', 'Pza', '15.00', '0.00') - ,('84672902', '8467.29.02', 'Destornilladores o aprietatuercas de embrague o impacto.', '6', 'Pza', '0.00', '0.00') - ,('84672903', '8467.29.03', 'Pulidora-lijadora orbital con potencia inferior o igual a 0.2 CP.', '6', 'Pza', '15.00', '0.00') - ,('84672999', '8467.29.99', 'Los demás.', '6', 'Pza', '5.00', '0.00') - ,('84678101', '8467.81.01', 'Sierras o tronzadoras, de cadena.', '6', 'Pza', '0.00', '0.00') - ,('84678902', '8467.89.02', 'Perforadoras por rotación y percusión, excepto hidráulicas.', '6', 'Pza', '15.00', '0.00') - ,('84678903', '8467.89.03', 'Pizones vibradores de accionamiento por motor de explosión.', '6', 'Pza', '15.00', '0.00') - ,('84678999', '8467.89.99', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('84679102', '8467.91.02', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('84679201', '8467.92.01', 'De herramientas neumáticas.', '1', 'Kg', '0.00', '0.00') - ,('84679999', '8467.99.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('84681001', '8468.10.01', 'Sopletes manuales.', '6', 'Pza', '15.00', '0.00') - ,('84682091', '8468.20.91', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84688091', '8468.80.91', 'Las demás máquinas y aparatos.', '6', 'Pza', '0.00', '0.00') - ,('84689001', '8468.90.01', 'Partes.', '1', 'Kg', '0.00', '0.00') - ,('84701003', '8470.10.03', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('84702101', '8470.21.01', 'Con dispositivo de impresión incorporado.', '6', 'Pza', '0.00', '0.00') - ,('84702999', '8470.29.99', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('84703091', '8470.30.91', 'Las demás máquinas de calcular.', '6', 'Pza', '0.00', '0.00') - ,('84705001', '8470.50.01', 'Cajas registradoras.', '6', 'Pza', '0.00', '0.00') - ,('84709003', '8470.90.03', 'Máquinas de contabilidad.', '6', 'Pza', '5.00', '0.00') - ,('84709099', '8470.90.99', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('84713001', '8471.30.01', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('84714101', '8471.41.01', 'Que incluyan en la misma envoltura, al menos, una unidad central de proceso y, aunque estén combinadas, una unidad de entrada y una de salida.', '6', 'Pza', '0.00', '0.00') - ,('84714991', '8471.49.91', 'Las demás presentadas en forma de sistemas.', '6', 'Pza', '0.00', '0.00') - ,('84715001', '8471.50.01', 'Unidades de proceso, excepto las de las subpartidas 8471.41 u 8471.49, aunque incluyan en la misma envoltura uno o dos de los tipos siguientes de unidades: unidad de memoria, unidad de entrada y unidad de salida.', '6', 'Pza', '0.00', '0.00') - ,('84716004', '8471.60.04', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84717001', '8471.70.01', 'Unidades de memoria.', '6', 'Pza', '0.00', '0.00') - ,('84718091', '8471.80.91', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84719099', '8471.90.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84721001', '8472.10.01', 'Mimeógrafos.', '6', 'Pza', '0.00', '0.00') - ,('84721099', '8472.10.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('84723001', '8472.30.01', 'Plegadoras de papel y/o editadoras en sobres de correspondencia o documentos.', '6', 'Pza', '0.00', '0.00') - ,('84723002', '8472.30.02', 'Para obliterar.', '6', 'Pza', '5.00', '0.00') - ,('84723099', '8472.30.99', 'Las demás.', '6', 'Pza', '15.00', '0.00') - ,('84729001', '8472.90.01', 'Máquinas para autenticar cheques.', '6', 'Pza', '0.00', '0.00') - ,('84729005', '8472.90.05', 'Para perforar documentos, con letras u otros signos.', '6', 'Pza', '0.00', '0.00') - ,('84729010', '8472.90.10', 'Para destruir documentos.', '6', 'Pza', '0.00', '0.00') - ,('84729012', '8472.90.12', 'Para contar billetes de banco, incluso con mecanismo impresor.', '6', 'Pza', '0.00', '0.00') - ,('84729013', '8472.90.13', 'De clasificar, contar y encartuchar monedas, excepto lo comprendido en la fracción arancelaria 8472.90.12.', '6', 'Pza', '5.00', '0.00') - ,('84729014', '8472.90.14', 'Cajeros automáticos.', '6', 'Pza', '0.00', '0.00') - ,('84729016', '8472.90.16', 'Máquinas para tratamiento o procesamiento de textos.', '6', 'Pza', '0.00', '0.00') - ,('84729099', '8472.90.99', 'Las demás.', '6', 'Pza', '15.00', '0.00') - ,('84732101', '8473.21.01', 'De máquinas de calcular electrónicas de las subpartidas 8470.10, 8470.21 u 8470.29.', '1', 'Kg', '0.00', '0.00') - ,('84732999', '8473.29.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('84733004', '8473.30.04', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('84734004', '8473.40.04', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('84735003', '8473.50.03', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('84741001', '8474.10.01', 'Clasificadoras de tamiz, de minerales tipo espiral o tipo rastrillo.', '6', 'Pza', '15.00', '0.00') - ,('84741099', '8474.10.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84742004', '8474.20.04', 'Trituradores (molinos) de bolas o de barras.', '6', 'Pza', '0.00', '0.00') - ,('84742007', '8474.20.07', 'Quebrantadores giratorios de conos, con diámetro de tazón superior a 1,200 mm.', '6', 'Pza', '0.00', '0.00') - ,('84742008', '8474.20.08', 'Pulverizadora de carbón, para el sistema de inyección en un alto horno.', '6', 'Pza', '0.00', '0.00') - ,('84742099', '8474.20.99', 'Los demás.', '6', 'Pza', '5.00', '0.00') - ,('84743101', '8474.31.01', 'Hormigoneras y aparatos de amasar mortero.', '6', 'Pza', '15.00', '0.00') - ,('84743201', '8474.32.01', 'Máquinas de mezclar materia mineral con asfalto.', '6', 'Pza', '0.00', '0.00') - ,('84743999', '8474.39.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84748001', '8474.80.01', 'Prensas de accionamiento manual.', '6', 'Pza', '15.00', '0.00') - ,('84748004', '8474.80.04', 'Para la obtención de elementos prefabricados para construcción, de cemento o concreto.', '6', 'Pza', '15.00', '0.00') - ,('84748099', '8474.80.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84749003', '8474.90.03', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('84751001', '8475.10.01', 'Máquinas para montar lámparas, tubos o válvulas eléctricos o electrónicos o lámparas de destello, que tengan envoltura de vidrio.', '6', 'Pza', '0.00', '0.00') - ,('84752101', '8475.21.01', 'Máquinas para fabricar fibras ópticas y sus esbozos.', '6', 'Pza', '0.00', '0.00') - ,('84752901', '8475.29.01', 'Para moldear por soplado o prensado, excepto para fabricar ampollas para lámparas incandescentes.', '6', 'Pza', '15.00', '0.00') - ,('84752999', '8475.29.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84759001', '8475.90.01', 'Partes.', '1', 'Kg', '0.00', '0.00') - ,('84762101', '8476.21.01', 'Con dispositivo de calentamiento o refrigeración, incorporado.', '6', 'Pza', '15.00', '0.00') - ,('84762999', '8476.29.99', 'Las demás.', '6', 'Pza', '15.00', '0.00') - ,('84768101', '8476.81.01', 'Con dispositivo de calentamiento o refrigeración, incorporado.', '6', 'Pza', '15.00', '0.00') - ,('84768999', '8476.89.99', 'Las demás.', '6', 'Pza', '15.00', '0.00') - ,('84769002', '8476.90.02', 'Partes.', '1', 'Kg', '15.00', '0.00') - ,('84771001', '8477.10.01', 'Para materias termoplásticas, con capacidad de inyección hasta de 5 kg.', '6', 'Pza', '0.00', '0.00') - ,('84771099', '8477.10.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('84772001', '8477.20.01', 'De un husillo, para materias termoplásticas o de elastómeros granulados.', '6', 'Pza', '15.00', '0.00') - ,('84772099', '8477.20.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84773001', '8477.30.01', 'Máquinas de moldear por soplado.', '6', 'Pza', '0.00', '0.00') - ,('84774001', '8477.40.01', 'Máquinas de moldear en vacío y demás máquinas para termoformado.', '6', 'Pza', '0.00', '0.00') - ,('84775101', '8477.51.01', 'De moldear o recauchutar neumáticos (llantas neumáticas) o moldear o formar cámaras para neumáticos.', '6', 'Pza', '0.00', '0.00') - ,('84775999', '8477.59.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84778006', '8477.80.06', 'Sistemas automatizados para fabricar discos compactos ("Compact Disks"), que realicen las siguientes funciones: moldeo por inyección, aluminizado, laqueado, control de calidad e impresión del disco.', '1', 'Kg', '0.00', '0.00') - ,('84778099', '8477.80.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84779004', '8477.90.04', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('84781001', '8478.10.01', 'Para colocar filtros o boquillas a los cigarrillos.', '6', 'Pza', '5.00', '0.00') - ,('84781002', '8478.10.02', 'Automáticas para elaborar cigarrillos, incluso combinadas con alimentadoras de tabaco.', '6', 'Pza', '5.00', '0.00') - ,('84781003', '8478.10.03', 'Conjuntos de dispositivos para aumentar la eficiencia de las máquinas para empaquetar cigarrillos.', '6', 'Pza', '5.00', '0.00') - ,('84781004', '8478.10.04', 'Llenadores automáticos de bandejas, para máquinas de fabricar cigarrillos.', '6', 'Pza', '5.00', '0.00') - ,('84781099', '8478.10.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84789001', '8478.90.01', 'Partes.', '1', 'Kg', '0.00', '0.00') - ,('84791001', '8479.10.01', 'Distribuidoras vibradoras de concreto.', '6', 'Pza', '15.00', '0.00') - ,('84791003', '8479.10.03', 'Esparcidoras de asfalto remolcables, provistas de dispositivo calentador.', '6', 'Pza', '15.00', '0.00') - ,('84791004', '8479.10.04', 'Esparcidoras de asfalto autopropulsadas, incluso con equipo fundidor de asfalto.', '6', 'Pza', '5.00', '0.00') - ,('84791007', '8479.10.07', 'Esparcidoras de asfalto remolcables, excepto lo comprendido en la fracción arancelaria 8479.10.03.', '6', 'Pza', '5.00', '0.00') - ,('84791099', '8479.10.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84792001', '8479.20.01', 'Máquinas y aparatos para extracción o preparación de grasas o aceites, vegetales o de origen microbiano, fijos o animales.', '6', 'Pza', '0.00', '0.00') - ,('84793001', '8479.30.01', 'Clasificadoras vibratorias, de viruta o astillas de madera o materiales fibrosos, granulosos, en forma de copos u hojuelas, de peso específico similar al de la madera.', '6', 'Pza', '15.00', '0.00') - ,('84793002', '8479.30.02', 'Esparcidoras dosificadoras, diseñadas exclusivamente para la fabricación de tableros a base de astillas de madera aglomeradas.', '6', 'Pza', '15.00', '0.00') - ,('84793099', '8479.30.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84794002', '8479.40.02', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84795001', '8479.50.01', 'Robots industriales, no expresados ni comprendidos en otra parte.', '6', 'Pza', '0.00', '0.00') - ,('84796001', '8479.60.01', 'Aparatos de evaporación para refrigerar el aire.', '6', 'Pza', '0.00', '0.00') - ,('84797101', '8479.71.01', 'De los tipos utilizados en aeropuertos.', '6', 'Pza', '0.00', '0.00') - ,('84797999', '8479.79.99', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('84798102', '8479.81.02', 'Toneles giratorios para el decapado, limpieza, pulido o abrillantado de piezas metálicas.', '6', 'Pza', '15.00', '0.00') - ,('84798103', '8479.81.03', 'Máquinas de control numérico, para galvanizado continuo por inmersión, con velocidad superior a 300 pies por minuto, incluso cuando formen un solo cuerpo con hornos de atmósfera controlada y enfriadores por chorros de agua.', '6', 'Pza', '0.00', '0.00') - ,('84798104', '8479.81.04', 'Para limpieza, pulido o abrillantado de piezas metálicas con abrasivos en circulación, excepto lo comprendido en la fracción arancelaria 8479.81.02.', '6', 'Pza', '15.00', '0.00') - ,('84798107', '8479.81.07', 'Para galvanizar, estañar o recubrir metales, excepto líneas continuas para galvanizar alambre de acero, por inmersión y lo comprendido en la fracción arancelaria 8479.81.03.', '6', 'Pza', '15.00', '0.00') - ,('84798199', '8479.81.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84798201', '8479.82.01', 'Mezcladoras, de aspas horizontales, provistas de dispositivos de tornillo de Arquímedes para descarga continua.', '6', 'Pza', '0.00', '0.00') - ,('84798202', '8479.82.02', 'Cubas u otros recipientes provistos de agitadores, incluso con sistemas de vacío o vidriados interiormente, excepto lo comprendido en las fracciones arancelarias 8479.82.01 y 8479.82.05.', '6', 'Pza', '15.00', '0.00') - ,('84798204', '8479.82.04', 'Agitador-mezclador de hélice, excepto lo comprendido en la fracción arancelaria 8479.82.05.', '6', 'Pza', '15.00', '0.00') - ,('84798205', '8479.82.05', 'Agitadores, reconocibles como diseñados para el tratamiento de desperdicios, residuos y aguas residuales.', '6', 'Pza', '5.00', '0.00') - ,('84798299', '8479.82.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84798301', '8479.83.01', 'Prensas isostáticas en frío.', '6', 'Pza', '0.00', '0.00') - ,('84798903', '8479.89.03', 'Separadores o clasificadores de materiales o partes ferrosas, a base de dispositivos magnéticos o electromagnéticos.', '6', 'Pza', '5.00', '0.00') - ,('84798904', '8479.89.04', 'Aparatos para la generación de una corriente controlada de aire ("cortinas de aire") para impedir la entrada de insectos, polvo y mantener la temperatura de un recinto.', '6', 'Pza', '15.00', '0.00') - ,('84798906', '8479.89.06', 'Aparatos neumáticos o hidráulicos para automatizar máquinas, aparatos o artefactos mecánicos.', '6', 'Pza', '15.00', '0.00') - ,('84798907', '8479.89.07', 'Para fabricar cierres relámpago.', '6', 'Pza', '15.00', '0.00') - ,('84798908', '8479.89.08', 'Frenos de motor.', '6', 'Pza', '15.00', '0.00') - ,('84798910', '8479.89.10', 'Acumuladores hidráulicos.', '6', 'Pza', '15.00', '0.00') - ,('84798912', '8479.89.12', 'Sulfonadores de trióxido de azufre.', '6', 'Pza', '15.00', '0.00') - ,('84798913', '8479.89.13', 'Vibradores electromagnéticos, incluso con alimentador.', '6', 'Pza', '15.00', '0.00') - ,('84798914', '8479.89.14', 'Reactores o convertidores catalíticos tubulares.', '6', 'Pza', '15.00', '0.00') - ,('84798919', '8479.89.19', 'Deshumectadores, con sistema de refrigeración incorporado, para condensar la humedad atmosférica.', '6', 'Pza', '5.00', '0.00') - ,('84798920', '8479.89.20', 'Colectores de monedas y/o contraseñas, con torniquete, aun con dispositivo contador.', '6', 'Pza', '15.00', '0.00') - ,('84798921', '8479.89.21', 'Prensas hidráulicas para algodón.', '6', 'Pza', '15.00', '0.00') - ,('84798922', '8479.89.22', 'Para desmontar llantas.', '6', 'Pza', '15.00', '0.00') - ,('84798926', '8479.89.26', 'Mecanismos de apertura y cierre de puertas, incluso con sus rieles, para cocheras ("garajes"), ya sean operados o no a control remoto inalámbrico.', '6', 'Pza', '5.00', '0.00') - ,('84798927', '8479.89.27', 'Mecanismos de apertura y cierre de persianas, incluso con sus rieles, ya sean operados o no a control remoto inalámbrico.', '6', 'Pza', '5.00', '0.00') - ,('84798999', '8479.89.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84799018', '8479.90.18', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('84801001', '8480.10.01', 'Cajas de fundición.', '6', 'Pza', '0.00', '0.00') - ,('84802001', '8480.20.01', 'Placas de fondo para moldes.', '6', 'Pza', '0.00', '0.00') - ,('84803003', '8480.30.03', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('84804102', '8480.41.02', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('84804999', '8480.49.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('84805003', '8480.50.03', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('84806001', '8480.60.01', 'Moldes o formas de acero o aluminio, así como las partes de los mismos para el vaciado o colado en la industria de la construcción.', '1', 'Kg', '15.00', '0.00') - ,('84806099', '8480.60.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('84807103', '8480.71.03', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('84807999', '8480.79.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('84811001', '8481.10.01', 'De diafragma, con regulación manual.', '1', 'Kg', '5.00', '0.00') - ,('84811099', '8481.10.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('84812007', '8481.20.07', 'Tipo globo o tipo ángulo, de acero y/o hierro fundido, bridadas o roscadas, con diámetro de conexión hasta 152.4 mm inclusive, para amoniaco y/o gases halogenados, reconocibles como diseñadas exclusivamente para servicio de refrigeración.', '1', 'Kg', '5.00', '0.00') - ,('84812010', '8481.20.10', 'Tipo globo o tipo ángulo, de bronce o latón con diámetro de conexión hasta 76.2 mm para gases halogenados, reconocibles como diseñadas exclusivamente para servicios de refrigeración.', '1', 'Kg', '5.00', '0.00') - ,('84812012', '8481.20.12', 'Conjunto de válvulas (árboles de navidad o de Noel) reconocibles para la industria petrolera.', '1', 'Kg', '5.00', '0.00') - ,('84812099', '8481.20.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('84813001', '8481.30.01', 'Válvulas de retención, que operen automáticamente, excepto trampas de vapor; y las reconocibles como diseñadas exclusivamente para el funcionamiento de máquinas, aparatos o artefactos mecánicos para sistemas hidráulicos de aceite en circuitos cerrados.', '1', 'Kg', '5.00', '0.00') - ,('84813099', '8481.30.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('84814004', '8481.40.04', 'De seguridad, y contra escape de gas, de uso doméstico, excepto automáticas o semiautomáticas, reconocibles como diseñadas exclusivamente para calentadores no eléctricos.', '1', 'Kg', '5.00', '0.00') - ,('84814099', '8481.40.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('84818001', '8481.80.01', 'De bronce, reconocibles como diseñadas exclusivamente para laboratorio.', '1', 'Kg', '0.00', '0.00') - ,('84818002', '8481.80.02', 'Grifería sanitaria de uso doméstico.', '1', 'Kg', '15.00', '0.00') - ,('84818003', '8481.80.03', 'Reconocibles como diseñadas exclusivamente para tractores agrícolas e industriales.', '1', 'Kg', '0.00', '0.00') - ,('84818004', '8481.80.04', 'Válvulas de compuerta, excepto lo comprendido en la fracción arancelaria 8481.80.24.', '1', 'Kg', '5.00', '0.00') - ,('84818005', '8481.80.05', 'Bocas o válvulas regulables para riego agrícola o de jardín ("hidrantes") con diámetro igual o inferior a 203.2 mm.', '1', 'Kg', '0.00', '0.00') - ,('84818006', '8481.80.06', 'De comando, reconocibles como diseñadas exclusivamente para automatizar el funcionamiento de máquinas, aparatos o artefactos mecánicos, con diámetro de conexión hasta de 19.05 mm (¾ de pulgada) y presión de trabajo hasta 35.15 kg/cm² (500 PSI).', '1', 'Kg', '0.00', '0.00') - ,('84818007', '8481.80.07', 'Boquillas o espreas para aspersión.', '1', 'Kg', '10.00', '0.00') - ,('84818008', '8481.80.08', 'Reconocibles como diseñadas exclusivamente para contadores volumétricos automáticos para medir cerveza.', '1', 'Kg', '0.00', '0.00') - ,('84818009', '8481.80.09', 'Reconocibles para naves aéreas.', '1', 'Kg', '0.00', '0.00') - ,('84818010', '8481.80.10', 'De asiento, de tres o más vías, cuya sección giratoria de la vía sea a través de accionamiento manual por palanca.', '1', 'Kg', '0.00', '0.00') - ,('84818011', '8481.80.11', 'Operadas por electroimán, para máquinas de lavar ropa o vajilla.', '1', 'Kg', '0.00', '0.00') - ,('84818012', '8481.80.12', 'Operadas con electroimán, reconocibles como diseñadas exclusivamente para refrigeración.', '1', 'Kg', '0.00', '0.00') - ,('84818013', '8481.80.13', 'Válvulas de expansión, termostáticas y automáticas, reconocibles como diseñadas exclusivamente para refrigeración y aire acondicionado.', '1', 'Kg', '0.00', '0.00') - ,('84818014', '8481.80.14', 'Automáticas o semiautomáticas, reconocibles como diseñadas exclusivamente para calentadores no eléctricos.', '1', 'Kg', '0.00', '0.00') - ,('84818015', '8481.80.15', 'Reconocibles como diseñadas exclusivamente para el funcionamiento de máquinas, aparatos o artefactos mecánicos para sistemas hidráulicos de aceite en circuitos cerrados.', '1', 'Kg', '0.00', '0.00') - ,('84818016', '8481.80.16', 'De comando, reconocibles como diseñadas exclusivamente para automatizar el funcionamiento de máquinas, aparatos o artefactos mecánicos, con diámetro de conexión superior a 19.05 mm, (¾ de pulgada) y presión de trabajo superior a 35.15 kg/cm² (500 PSI).', '1', 'Kg', '0.00', '0.00') - ,('84818017', '8481.80.17', 'De acero inoxidable, con vástago, reconocibles como diseñadas exclusivamente para máquinas llenadoras de botellas.', '1', 'Kg', '0.00', '0.00') - ,('84818018', '8481.80.18', 'De hierro o acero con resistencia a la presión superior a 18 kg/cm², excepto lo comprendido en la fracción arancelaria 8481.80.04.', '1', 'Kg', '5.00', '0.00') - ,('84818019', '8481.80.19', 'De metal común, cromados, niquelados o con otro recubrimiento, excepto lo comprendido en las fracciones arancelarias 8481.80.01 y 8481.80.02.', '1', 'Kg', '0.00', '0.00') - ,('84818020', '8481.80.20', 'De hierro o acero, con resistencia a la presión inferior o igual a 18 kg/cm², excepto lo comprendido en la fracción arancelaria 8481.80.04.', '1', 'Kg', '0.00', '0.00') - ,('84818021', '8481.80.21', 'De cobre, bronce, latón o aluminio, sin recubrimiento en su superficie, excepto lo comprendido en la fracción arancelaria 8481.80.02.', '1', 'Kg', '5.00', '0.00') - ,('84818022', '8481.80.22', 'Válvulas de funcionamiento automático por medio de actuador, excepto lo comprendido en las fracciones arancelarias 8481.80.06, 8481.80.15 y 8481.80.24.', '1', 'Kg', '0.00', '0.00') - ,('84818023', '8481.80.23', 'De control hidráulico, excepto lo comprendido en la fracción arancelaria 8481.80.15.', '1', 'Kg', '0.00', '0.00') - ,('84818024', '8481.80.24', 'Válvulas de funcionamiento automático por medio de actuador, de apertura controlada, de cuchilla, bola o globo.', '1', 'Kg', '0.00', '0.00') - ,('84818025', '8481.80.25', 'Válvulas de aire para neumáticos y cámaras de aire.', '1', 'Kg', '0.00', '0.00') - ,('84818099', '8481.80.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('84819005', '8481.90.05', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('84821002', '8482.10.02', 'En fila sencilla con diámetro interior superior o igual a 12.7 mm, sin exceder de 50.8 mm y diámetro exterior superior o igual a 40 mm, sin exceder de 100 mm, con superficie exterior esférica, excepto los de centro de eje cuadrado.', '1', 'Kg', '5.00', '0.00') - ,('84821099', '8482.10.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('84822001', '8482.20.01', 'Ensambles de conos y rodillos cónicos con números de serie: 15101, 331274, L44643, L44649, L68149, M12649, LM11749, LM11949, LM12749, LM29748, LM29749, LM48548, LM67048 o LM501349; rodamientos con los siguientes códigos (SET): LM11749/710 (SET 1), LM11949/910 (SET 2), M12649/610 (SET 3), L44649/610 (SET 4), LM48548/510 (SET 5), LM67048/010 (SET 6), L45449/410 (SET 8), LM12749/710 (SET 12), L68149/110 (SET 13), L44643/610 (SET 14), LM12749/711 (SET 16), L68149/111 (SET 17) o 15101/15245, JL 2674', '1', 'Kg', '5.00', '0.00') - ,('84822003', '8482.20.03', 'Ensambles de conos y rodillos cónicos con números de serie: 387A, HM803146, HM803149, HM88542, JL26749, JL69349 o L45449; rodamientos con los siguientes códigos (SET): JL69349/310 (SET 11), LM501349/310 (SET 45), JL26749F/710 (SET 46), LM29748/710 (SET 56), 387A/382S (SET 75), HM88542/510 (SET 81) o HM803149/110 (SET 83); ensambles y rodamientos equivalentes a los comprendidos en esta fracción arancelaria, según las Normas Internacionales ANSI-ABMA Standard 19.1, ANSI-ABMA Standard 19.2 o ISO 3', '1', 'Kg', '0.00', '0.00') - ,('84822099', '8482.20.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('84823001', '8482.30.01', 'Rodamientos de rodillos en forma de tonel.', '1', 'Kg', '0.00', '0.00') - ,('84824001', '8482.40.01', 'Rodamientos de agujas, incluidos los ensamblajes de jaulas y rodillos de agujas.', '1', 'Kg', '0.00', '0.00') - ,('84825091', '8482.50.91', 'Los demás rodamientos de rodillos cilíndricos, incluidos los ensamblajes de jaulas y rodillos.', '1', 'Kg', '0.00', '0.00') - ,('84828091', '8482.80.91', 'Los demás, incluidos los rodamientos combinados.', '1', 'Kg', '0.00', '0.00') - ,('84829102', '8482.91.02', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('84829901', '8482.99.01', 'Pistas o tazas con números de serie: 15245, L68110, L68111, L45410, M12610, LM11710, LM11910, LM12710, LM12711, LM29710, LM48510, LM67010 o LM501314, incluyendo aquellas que contengan números y/o letras posteriores a esos números de serie, según las Normas Internacionales ANSI-ABMA Standard 19.1, ANSI-ABMA Standard 19.2 o ISO 355.', '1', 'Kg', '5.00', '0.00') - ,('84829999', '8482.99.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('84831008', '8483.10.08', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('84832001', '8483.20.01', 'Cajas de cojinetes con rodamientos incorporados.', '1', 'Kg', '0.00', '0.00') - ,('84833004', '8483.30.04', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('84834009', '8483.40.09', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('84835003', '8483.50.03', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('84836001', '8483.60.01', 'Embragues.', '1', 'Kg', '0.00', '0.00') - ,('84836002', '8483.60.02', 'Acoplamientos elásticos, acoplamientos de acero forjado con peso unitario superior o igual a 600 kg.', '1', 'Kg', '15.00', '0.00') - ,('84836099', '8483.60.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('84839003', '8483.90.03', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('84841001', '8484.10.01', 'Juntas metaloplásticas.', '1', 'Kg', '5.00', '0.00') - ,('84842001', '8484.20.01', 'Juntas mecánicas de estanqueidad.', '1', 'Kg', '0.00', '0.00') - ,('84849099', '8484.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('84851001', '8485.10.01', 'Por depósito de metal.', '6', 'Pza', '0.00', '0.00') - ,('84852001', '8485.20.01', 'Por depósito de plástico o caucho.', '6', 'Pza', '0.00', '0.00') - ,('84853001', '8485.30.01', 'Por depósito de yeso, cemento, cerámica o vidrio.', '6', 'Pza', '0.00', '0.00') - ,('84858099', '8485.80.99', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('84859001', '8485.90.01', 'Partes.', '1', 'Kg', '0.00', '0.00') - ,('84861001', '8486.10.01', 'Máquinas y aparatos para la fabricación de semiconductores en forma de monocristales periformes u obleas ("wafers").', '6', 'Pza', '0.00', '0.00') - ,('84862003', '8486.20.03', 'Máquinas y aparatos para la fabricación de dispositivos semiconductores o circuitos electrónicos integrados.', '6', 'Pza', '0.00', '0.00') - ,('84863001', '8486.30.01', 'Máquinas y aparatos para la fabricación de dispositivos de visualización (display) de pantalla plana.', '6', 'Pza', '0.00', '0.00') - ,('84864001', '8486.40.01', 'Máquinas y aparatos descritos en la Nota 11 C) de este Capítulo.', '6', 'Pza', '0.00', '0.00') - ,('84869005', '8486.90.05', 'Partes y accesorios reconocibles exclusivamente para lo comprendido en la subpartida 8486.40.', '6', 'Pza', '0.00', '0.00') - ,('84871002', '8487.10.02', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('84879001', '8487.90.01', 'Cilindros hidráulicos.', '1', 'Kg', '15.00', '0.00') - ,('84879002', '8487.90.02', 'Válvulas de engrase por inyección.', '1', 'Kg', '0.00', '0.00') - ,('84879003', '8487.90.03', 'Guarniciones montadas de frenos.', '1', 'Kg', '0.00', '0.00') - ,('84879004', '8487.90.04', 'Reconocibles como diseñadas exclusivamente para tractores agrícolas e industriales, excepto lo comprendido en la fracción arancelaria 8487.90.02.', '1', 'Kg', '0.00', '0.00') - ,('84879005', '8487.90.05', 'Reconocibles para naves aéreas.', '1', 'Kg', '0.00', '0.00') - ,('84879006', '8487.90.06', 'Engrasadoras de copa tipo "Stauffer".', '1', 'Kg', '0.00', '0.00') - ,('84879007', '8487.90.07', 'Portamoldes o sus partes, aun cuando se presenten sin la cavidad para recibir el molde.', '1', 'Kg', '0.00', '0.00') - ,('84879099', '8487.90.99', 'Las demás.', '1', 'Kg', '5.00', '0.00') - ,('85011010', '8501.10.10', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('85012005', '8501.20.05', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('85013101', '8501.31.01', 'Generadores.', '6', 'Pza', '15.00', '0.00') - ,('85013102', '8501.31.02', 'Reconocibles para naves aéreas.', '6', 'Pza', '0.00', '0.00') - ,('85013103', '8501.31.03', 'Motores con potencia inferior o igual a 264 W y voltaje de 12 V, de uso automotriz, para limpiaparabrisas, elevadores de cristales, radiadores y calefacción.', '6', 'Pza', '0.00', '0.00') - ,('85013104', '8501.31.04', 'Motores para máquinas esquiladoras.', '6', 'Pza', '0.00', '0.00') - ,('85013105', '8501.31.05', 'Motores con potencia superior o igual a 186 W (¼ CP), excepto lo comprendido en las fracciones arancelarias 8501.31.03 y 8501.31.04.', '6', 'Pza', '15.00', '0.00') - ,('85013199', '8501.31.99', 'Los demás.', '6', 'Pza', '5.00', '0.00') - ,('85013201', '8501.32.01', 'Generadores.', '6', 'Pza', '15.00', '0.00') - ,('85013202', '8501.32.02', 'Reconocibles para naves aéreas.', '6', 'Pza', '0.00', '0.00') - ,('85013203', '8501.32.03', 'Motores para ascensores o elevadores.', '6', 'Pza', '0.00', '0.00') - ,('85013204', '8501.32.04', 'Motores para trolebuses.', '6', 'Pza', '0.00', '0.00') - ,('85013205', '8501.32.05', 'Motores de potencia igual o inferior a 3.75 kW (5 CP).', '6', 'Pza', '15.00', '0.00') - ,('85013206', '8501.32.06', 'Motores reconocibles como diseñados exclusivamente para la propulsión de vehículos eléctricos de la subpartida 8703.80.', '6', 'Pza', '0.00', '0.00') - ,('85013299', '8501.32.99', 'Los demás.', '6', 'Pza', '10.00', '0.00') - ,('85013301', '8501.33.01', 'Generadores con capacidad hasta de 150 kW.', '6', 'Pza', '15.00', '0.00') - ,('85013302', '8501.33.02', 'Reconocibles para naves aéreas.', '6', 'Pza', '0.00', '0.00') - ,('85013303', '8501.33.03', 'Generadores, excepto lo comprendido en la fracción arancelaria 8501.33.01.', '6', 'Pza', '0.00', '0.00') - ,('85013304', '8501.33.04', 'Motores para ascensores o elevadores.', '6', 'Pza', '0.00', '0.00') - ,('85013305', '8501.33.05', 'Motores para trolebuses.', '6', 'Pza', '0.00', '0.00') - ,('85013399', '8501.33.99', 'Los demás.', '6', 'Pza', '10.00', '0.00') - ,('85013401', '8501.34.01', 'Generadores.', '6', 'Pza', '0.00', '0.00') - ,('85013405', '8501.34.05', 'Motores, con potencia inferior o igual a 2,611 kW (3,500 CP), excepto reconocibles para naves aéreas, para ascensores o elevadores, para trolebuses.', '6', 'Pza', '5.00', '0.00') - ,('85013499', '8501.34.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('85014005', '8501.40.05', 'Síncronos, con potencia igual o inferior a 4,475 kW (6,000 CP).', '6', 'Pza', '5.00', '0.00') - ,('85014008', '8501.40.08', 'Motores de corriente alterna, asíncronos monofásicos, según normas NMX-J-75 o NMX-J-226, o sus equivalentes, excepto los motores para ascensores o elevadores; para máquinas de afeitar o cortar el pelo, incluidas las esquiladoras y lo comprendido en la fracción arancelaria 8501.40.05.', '6', 'Pza', '5.00', '0.00') - ,('85014099', '8501.40.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('85015102', '8501.51.02', 'Asíncronos, trifásicos.', '6', 'Pza', '15.00', '0.00') - ,('85015199', '8501.51.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('85015204', '8501.52.04', 'Asíncronos, trifásicos, excepto los reconocibles para naves aéreas; para trolebuses; para ascensores o elevadores.', '6', 'Pza', '15.00', '0.00') - ,('85015299', '8501.52.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('85015304', '8501.53.04', 'Asíncronos, trifásicos, con potencia de salida inferior o igual a 8,952 kW (12,000 CP), excepto los reconocibles para naves aéreas; para trolebuses, ascensores o elevadores.', '6', 'Pza', '15.00', '0.00') - ,('85015305', '8501.53.05', 'Síncronos, con potencia de salida igual o inferior a 4,475 kW (6,000 CP), excepto los reconocibles para naves aéreas y para trolebuses.', '6', 'Pza', '5.00', '0.00') - ,('85015399', '8501.53.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('85016101', '8501.61.01', 'De potencia inferior o igual a 75 kVA.', '6', 'Pza', '15.00', '0.00') - ,('85016201', '8501.62.01', 'De potencia superior a 75 kVA pero inferior o igual a 375 kVA.', '6', 'Pza', '15.00', '0.00') - ,('85016301', '8501.63.01', 'De potencia superior a 375 kVA pero inferior o igual a 750 kVA.', '6', 'Pza', '15.00', '0.00') - ,('85016401', '8501.64.01', 'De potencia superior a 750 kVA, pero inferior o igual a 6,000 kVA, excepto lo comprendido en la fracción arancelaria 8501.64.03.', '6', 'Pza', '15.00', '0.00') - ,('85016403', '8501.64.03', 'De potencia superior a 750 kVA, pero inferior o igual a 6,000 kVA, para producir electricidad a partir de fuentes de energía renovable.', '6', 'Pza', '5.00', '0.00') - ,('85016499', '8501.64.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('85017101', '8501.71.01', 'De potencia inferior o igual a 50 W.', '6', 'Pza', '15.00', '0.00') - ,('85017201', '8501.72.01', 'De potencia superior a 150 kW pero inferior o igual a 375 kW.', '6', 'Pza', '10.00', '0.00') - ,('85017202', '8501.72.02', 'De potencia superior a 375 kW.', '6', 'Pza', '0.00', '0.00') - ,('85017299', '8501.72.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('85018001', '8501.80.01', 'De potencia superior a 750 kVA pero inferior o igual a 6,000 kVA.', '6', 'Pza', '5.00', '0.00') - ,('85018002', '8501.80.02', 'De potencia superior a 6,000 kVA.', '6', 'Pza', '0.00', '0.00') - ,('85018099', '8501.80.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('85021101', '8502.11.01', 'De potencia inferior o igual a 75 kVA.', '6', 'Pza', '15.00', '0.00') - ,('85021201', '8502.12.01', 'De potencia superior a 75 kVA pero inferior o igual a 375 kVA.', '6', 'Pza', '15.00', '0.00') - ,('85021301', '8502.13.01', 'De potencia superior a 375 kVA, pero inferior o igual a 1,500 kVA.', '6', 'Pza', '15.00', '0.00') - ,('85021302', '8502.13.02', 'De potencia superior a 1,500 kVA pero inferior o igual a 2,000 kVA.', '6', 'Pza', '15.00', '0.00') - ,('85021399', '8502.13.99', 'Los demás.', '6', 'Pza', '5.00', '0.00') - ,('85022001', '8502.20.01', 'Con potencia superior a 2,000 kVA.', '6', 'Pza', '0.00', '0.00') - ,('85022099', '8502.20.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('85023101', '8502.31.01', 'Aerogeneradores.', '6', 'Pza', '5.00', '0.00') - ,('85023199', '8502.31.99', 'Los demás.', '6', 'Pza', '5.00', '0.00') - ,('85023901', '8502.39.01', 'Turbogeneradores (turbodinamos o turboalternadores), excepto lo comprendido en la fracción arancelaria 8502.39.03.', '6', 'Pza', '0.00', '0.00') - ,('85023902', '8502.39.02', 'Sistemas de cogeneración de electricidad y vapor, presentados como unidades móviles que formen un solo cuerpo.', '6', 'Pza', '0.00', '0.00') - ,('85023903', '8502.39.03', 'Turbogeneradores accionados por turbina a gas, excepto los accionados por turbina de vapor de agua.', '6', 'Pza', '0.00', '0.00') - ,('85023991', '8502.39.91', 'Los demás grupos electrógenos, para producir electricidad a partir de fuentes de energía renovable.', '6', 'Pza', '5.00', '0.00') - ,('85023999', '8502.39.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('85024001', '8502.40.01', 'Convertidores rotativos eléctricos.', '6', 'Pza', '5.00', '0.00') - ,('85030005', '8503.00.05', 'Estatores o rotores con peso unitario superior a 1,000 kg, excepto los reconocibles como diseñados exclusivamente para motores de trolebuses.', '6', 'Pza', '0.00', '0.00') - ,('85030099', '8503.00.99', 'Las demás.', '1', 'Kg', '5.00', '0.00') - ,('85041001', '8504.10.01', 'Balastos para lámparas.', '6', 'Pza', '5.00', '0.00') - ,('85041099', '8504.10.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('85042101', '8504.21.01', 'Bobinas de inducción.', '6', 'Pza', '0.00', '0.00') - ,('85042102', '8504.21.02', 'Con peso unitario inferior o igual a 5 kg, para uso en electrónica.', '6', 'Pza', '0.00', '0.00') - ,('85042103', '8504.21.03', 'Con peso unitario inferior a 5 kg, excepto lo comprendido en la fracción arancelaria 8504.21.02.', '6', 'Pza', '0.00', '0.00') - ,('85042199', '8504.21.99', 'Los demás.', '6', 'Pza', '5.00', '0.00') - ,('85042201', '8504.22.01', 'De potencia superior a 650 kVA pero inferior o igual a 10,000 kVA.', '6', 'Pza', '5.00', '0.00') - ,('85042301', '8504.23.01', 'De potencia superior a 10,000 kVA.', '6', 'Pza', '5.00', '0.00') - ,('85043103', '8504.31.03', 'De distribución, monofásicos o trifásicos.', '6', 'Pza', '5.00', '0.00') - ,('85043104', '8504.31.04', 'Para instrumentos, para medición y/o protección.', '6', 'Pza', '5.00', '0.00') - ,('85043199', '8504.31.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('85043201', '8504.32.01', 'Para instrumentos de medición y/o protección.', '6', 'Pza', '0.00', '0.00') - ,('85043299', '8504.32.99', 'Los demás.', '6', 'Pza', '5.00', '0.00') - ,('85043302', '8504.33.02', 'Los demás.', '6', 'Pza', '5.00', '0.00') - ,('85043401', '8504.34.01', 'De potencia superior a 500 kVA.', '6', 'Pza', '5.00', '0.00') - ,('85044001', '8504.40.01', 'Para soldadura eléctrica, con capacidad nominal igual o inferior a 400 amperes.', '6', 'Pza', '5.00', '0.00') - ,('85044002', '8504.40.02', 'Equipos rectificadores de selenio.', '6', 'Pza', '0.00', '0.00') - ,('85044005', '8504.40.05', 'Equipos rectificadores de óxido de cobre, germanio o silicio.', '6', 'Pza', '0.00', '0.00') - ,('85044006', '8504.40.06', 'Convertidores de batería de corriente CC/CC para alimentación de equipos de telecomunicaciones.', '6', 'Pza', '0.00', '0.00') - ,('85044007', '8504.40.07', 'Para fuente de llamada, para centrales telefónicas.', '6', 'Pza', '0.00', '0.00') - ,('85044008', '8504.40.08', 'Eliminadores de baterías o pilas, con peso unitario inferior o igual a 1 kg, para grabadoras, radios o fonógrafos.', '6', 'Pza', '0.00', '0.00') - ,('85044009', '8504.40.09', 'Fuentes de poder reguladas, con regulación superior o igual al 0.1%, para la alimentación de amplificadores de distribución de audio y video, para sistemas de televisión por cables.', '6', 'Pza', '0.00', '0.00') - ,('85044011', '8504.40.11', 'Fuentes de alimentación de corriente continua, para mesa o bastidor ("Rack") inferior o igual a 500 voltios con precisión superior o igual al 0.1% e inferior o igual a 500 W de potencia con instrumentos indicadores de tensión y corriente con protección automática contra sobrecarga.', '6', 'Pza', '0.00', '0.00') - ,('85044013', '8504.40.13', 'Controladores de velocidad para motores eléctricos.', '6', 'Pza', '5.00', '0.00') - ,('85044014', '8504.40.14', 'Fuentes de poder y/o fuentes de alimentación estabilizada, reconocibles como diseñadas exclusivamente para incorporación en los aparatos y equipos comprendidos en la partida 84.71, excepto lo comprendido en la fracción arancelaria 8504.40.16.', '6', 'Pza', '0.00', '0.00') - ,('85044015', '8504.40.15', 'Cargadores para baterías que tengan dispositivo de control de carga.', '6', 'Pza', '0.00', '0.00') - ,('85044016', '8504.40.16', 'Fuentes de voltaje, con conversión de corriente CA/CC/CA, llamadas "no break" o "uninterruptible power supply" ("UPS"), excepto reguladores automáticos de voltaje y lo comprendido en la fracción arancelaria 8504.40.14.', '6', 'Pza', '15.00', '0.00') - ,('85044017', '8504.40.17', 'Convertidores estáticos para alimentar dispositivos electrónicos portátiles (teléfonos, reproductores de música, cámaras fotográficas, etc.).', '6', 'Pza', '0.00', '0.00') - ,('85044099', '8504.40.99', 'Los demás.', '6', 'Pza', '10.00', '0.00') - ,('85045091', '8504.50.91', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('85049002', '8504.90.02', 'Circuitos modulares reconocibles como diseñados exclusivamente para lo comprendido en la fracción arancelaria 8504.40.14.', '1', 'Kg', '0.00', '0.00') - ,('85049007', '8504.90.07', 'Circuitos modulares reconocibles como diseñados exclusivamente para lo comprendido en las subpartidas 8504.40 y 8504.90, excepto lo comprendido en la fracción arancelaria 8504.90.02.', '1', 'Kg', '0.00', '0.00') - ,('85049008', '8504.90.08', 'Reconocibles como diseñadas exclusivamente para lo comprendido en las fracciones arancelarias 8504.40.05, 8504.40.06, 8504.40.07, 8504.40.08, 8504.40.09, 8504.40.14, 8504.40.16, 8504.40.17, excepto lo comprendido en las fracciones arancelarias 8504.90.02 y 8504.90.07.', '6', 'Pza', '0.00', '0.00') - ,('85049099', '8504.90.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('85051101', '8505.11.01', 'De metal.', '1', 'Kg', '0.00', '0.00') - ,('85051999', '8505.19.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('85052001', '8505.20.01', 'Acoplamientos, embragues, variadores de velocidad y frenos, electromagnéticos.', '1', 'Kg', '5.00', '0.00') - ,('85059091', '8505.90.91', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('85061003', '8506.10.03', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('85063001', '8506.30.01', 'De óxido de mercurio.', '1', 'Kg', '0.00', '0.00') - ,('85064001', '8506.40.01', 'De óxido de plata.', '1', 'Kg', '0.00', '0.00') - ,('85065001', '8506.50.01', 'De litio.', '1', 'Kg', '0.00', '0.00') - ,('85066001', '8506.60.01', 'De aire-cinc.', '1', 'Kg', '0.00', '0.00') - ,('85068091', '8506.80.91', 'Las demás pilas y baterías de pilas.', '1', 'Kg', '0.00', '0.00') - ,('85069001', '8506.90.01', 'Partes.', '1', 'Kg', '0.00', '0.00') - ,('85071001', '8507.10.01', 'Reconocibles para naves aéreas.', '6', 'Pza', '0.00', '0.00') - ,('85071099', '8507.10.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('85072001', '8507.20.01', 'Reconocibles para naves aéreas.', '6', 'Pza', '0.00', '0.00') - ,('85072002', '8507.20.02', 'Recargables, de plomo-ácido, para "flash" electrónico hasta 6 voltios, con peso unitario igual o inferior a 1 kg.', '6', 'Pza', '0.00', '0.00') - ,('85072004', '8507.20.04', 'De plomo-ácido, con recombinación interna de gases, construcción sellada, con electrolito inmovilizado, para uso electrónico, con peso inferior a 9 kg y terminales de tornillo o tipo fastón.', '6', 'Pza', '0.00', '0.00') - ,('85072099', '8507.20.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('85073001', '8507.30.01', 'De níquel-cadmio.', '1', 'Kg', '0.00', '0.00') - ,('85075001', '8507.50.01', 'De níquel-hidruro metálico.', '1', 'Kg', '0.00', '0.00') - ,('85076001', '8507.60.01', 'De iones de litio.', '1', 'Kg', '0.00', '0.00') - ,('85078091', '8507.80.91', 'Los demás acumuladores.', '1', 'Kg', '0.00', '0.00') - ,('85079001', '8507.90.01', 'Partes.', '1', 'Kg', '0.00', '0.00') - ,('85081101', '8508.11.01', 'De potencia inferior o igual a 1,500 W y de capacidad del depósito o bolsa para el polvo inferior o igual a 20 l.', '6', 'Pza', '20.00', '0.00') - ,('85081999', '8508.19.99', 'Las demás.', '6', 'Pza', '20.00', '0.00') - ,('85086091', '8508.60.91', 'Las demás aspiradoras.', '6', 'Pza', '20.00', '0.00') - ,('85087001', '8508.70.01', 'Reconocibles como diseñadas exclusivamente para aspiradoras, con peso superior a 20 kg, para uso industrial.', '1', 'Kg', '7.00', '0.00') - ,('85087002', '8508.70.02', 'Carcasas.', '6', 'Pza', '10.00', '0.00') - ,('85087099', '8508.70.99', 'Las demás.', '1', 'Kg', '10.00', '0.00') - ,('85094002', '8509.40.02', 'Exprimidoras de frutas.', '6', 'Pza', '0.00', '0.00') - ,('85094003', '8509.40.03', 'Batidoras.', '6', 'Pza', '0.00', '0.00') - ,('85094099', '8509.40.99', 'Las demás.', '6', 'Pza', '15.00', '0.00') - ,('85098003', '8509.80.03', 'Cuchillos.', '6', 'Pza', '0.00', '0.00') - ,('85098004', '8509.80.04', 'Cepillos para dientes.', '6', 'Pza', '0.00', '0.00') - ,('85098007', '8509.80.07', 'De manicura, con accesorios intercambiables.', '6', 'Pza', '0.00', '0.00') - ,('85098009', '8509.80.09', 'Abridores de latas.', '6', 'Pza', '0.00', '0.00') - ,('85098099', '8509.80.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('85099002', '8509.90.02', 'Carcasas.', '6', 'Pza', '0.00', '0.00') - ,('85099099', '8509.90.99', 'Las demás.', '1', 'Kg', '25.00', '0.00') - ,('85101001', '8510.10.01', 'Afeitadoras.', '6', 'Pza', '15.00', '0.00') - ,('85102001', '8510.20.01', 'Máquinas de cortar el pelo o esquilar.', '6', 'Pza', '15.00', '0.00') - ,('85103001', '8510.30.01', 'Aparatos de depilar.', '6', 'Pza', '15.00', '0.00') - ,('85109001', '8510.90.01', 'Peines, para máquinas de cortar el pelo.', '1', 'Kg', '0.00', '0.00') - ,('85109002', '8510.90.02', 'Cabezales para máquinas de afeitar.', '1', 'Kg', '15.00', '0.00') - ,('85109003', '8510.90.03', 'Hojas con o sin filo.', '1', 'Kg', '15.00', '0.00') - ,('85109004', '8510.90.04', 'Partes reconocidas como diseñadas exclusivamente para máquinas rasuradoras o de afeitar, excepto lo comprendido en las fracciones arancelarias 8510.90.02 y 8510.90.03.', '1', 'Kg', '0.00', '0.00') - ,('85109099', '8510.90.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('85111003', '8511.10.03', 'Cuyo electrodo central sea de níquel, tungsteno; platino, iridio o de aleaciones de oro; o que contengan dos o más electrodos a tierra; excepto los reconocibles como diseñadas para naves aéreas, tractores agrícolas e industriales o motocicletas.', '1', 'Kg', '5.00', '0.00') - ,('85111099', '8511.10.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('85112004', '8511.20.04', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('85113005', '8511.30.05', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('85114004', '8511.40.04', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('85115091', '8511.50.91', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('85118001', '8511.80.01', 'Reguladores de arranque.', '6', 'Pza', '15.00', '0.00') - ,('85118002', '8511.80.02', 'Reconocibles para naves aéreas.', '6', 'Pza', '0.00', '0.00') - ,('85118003', '8511.80.03', 'Bujías de calentado (precalentadoras).', '6', 'Pza', '0.00', '0.00') - ,('85118004', '8511.80.04', 'Reconocibles como diseñados exclusivamente para tractores agrícolas e industriales o motocicletas.', '6', 'Pza', '0.00', '0.00') - ,('85118099', '8511.80.99', 'Los demás.', '6', 'Pza', '10.00', '0.00') - ,('85119006', '8511.90.06', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('85121003', '8512.10.03', 'Aparatos de alumbrado o señalización visual de los tipos utilizados en bicicletas.', '1', 'Kg', '0.00', '0.00') - ,('85122001', '8512.20.01', 'Faros, luces direccionales delanteras y traseras, reconocibles como diseñados exclusivamente para motocicletas.', '1', 'Kg', '0.00', '0.00') - ,('85122002', '8512.20.02', 'Luces direccionales y/o calaveras traseras, excepto lo comprendido en la fracción arancelaria 8512.20.01.', '1', 'Kg', '0.00', '0.00') - ,('85122099', '8512.20.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('85123001', '8512.30.01', 'Alarma electrónica contra robo, para vehículos automóviles.', '1', 'Kg', '5.00', '0.00') - ,('85123099', '8512.30.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('85124001', '8512.40.01', 'Limpiaparabrisas y eliminadores de escarcha o vaho.', '1', 'Kg', '0.00', '0.00') - ,('85129007', '8512.90.07', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('85131001', '8513.10.01', 'De seguridad, para mineros.', '6', 'Pza', '0.00', '0.00') - ,('85131099', '8513.10.99', 'Las demás.', '6', 'Pza', '15.00', '0.00') - ,('85139001', '8513.90.01', 'Partes.', '1', 'Kg', '0.00', '0.00') - ,('85141101', '8514.11.01', 'Prensas isostáticas en caliente.', '6', 'Pza', '10.00', '0.00') - ,('85141901', '8514.19.01', 'Hornos para panadería o industrias análogas.', '6', 'Pza', '15.00', '0.00') - ,('85141902', '8514.19.02', 'De resistencia para temple de metales.', '6', 'Pza', '0.00', '0.00') - ,('85141903', '8514.19.03', 'Hornos industriales, excepto lo comprendido en las fracciones arancelarias 8514.19.01, 8514.19.02 y 8514.19.04.', '6', 'Pza', '15.00', '0.00') - ,('85141904', '8514.19.04', 'Incineradores de residuos o desperdicios.', '6', 'Pza', '5.00', '0.00') - ,('85141999', '8514.19.99', 'Los demás.', '6', 'Pza', '10.00', '0.00') - ,('85142001', '8514.20.01', 'De inducción de baja frecuencia, para el recalentamiento de metales.', '6', 'Pza', '0.00', '0.00') - ,('85142002', '8514.20.02', 'De inducción de baja frecuencia, para fusión de metales.', '6', 'Pza', '0.00', '0.00') - ,('85142003', '8514.20.03', 'Hornos industriales, excepto lo comprendido en las fracciones arancelarias 8514.20.01, 8514.20.02, 8514.20.04 y 8514.20.05.', '6', 'Pza', '15.00', '0.00') - ,('85142004', '8514.20.04', 'Sistema de fundición reconocible como diseñado para la fabricación de monobloques y cabezas para motor, incluyendo al menos: horno de fusión, horno de precalentamiento (de espera y afinación) y escorificador de hornos de fundición.', '6', 'Pza', '0.00', '0.00') - ,('85142005', '8514.20.05', 'Incineradores de residuos o desperdicios.', '6', 'Pza', '5.00', '0.00') - ,('85142099', '8514.20.99', 'Los demás.', '6', 'Pza', '10.00', '0.00') - ,('85143101', '8514.31.01', 'Hornos de haces de electrones.', '6', 'Pza', '0.00', '0.00') - ,('85143201', '8514.32.01', 'Hornos de plasma y hornos de arco al vacío.', '6', 'Pza', '0.00', '0.00') - ,('85143901', '8514.39.01', 'Hornos para panadería o industrias análogas.', '6', 'Pza', '15.00', '0.00') - ,('85143999', '8514.39.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('85144091', '8514.40.91', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('85149004', '8514.90.04', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('85151101', '8515.11.01', 'Para soldar o cortar, portátiles ("cautines").', '6', 'Pza', '15.00', '0.00') - ,('85151199', '8515.11.99', 'Los demás.', '6', 'Pza', '10.00', '0.00') - ,('85151999', '8515.19.99', 'Los demás.', '6', 'Pza', '5.00', '0.00') - ,('85152101', '8515.21.01', 'Para soldar metales por costuras o proyección.', '6', 'Pza', '0.00', '0.00') - ,('85152199', '8515.21.99', 'Las demás.', '6', 'Pza', '10.00', '0.00') - ,('85152999', '8515.29.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('85153101', '8515.31.01', 'Para soldar o cortar, de arco, tipo generador o transformador, inferior o igual a 1,260 amperes.', '6', 'Pza', '15.00', '0.00') - ,('85153102', '8515.31.02', 'Para soldar o cortar, de arco, tipo generador o transformador, superior a 1,260 amperes.', '6', 'Pza', '0.00', '0.00') - ,('85153199', '8515.31.99', 'Los demás.', '6', 'Pza', '35.00', '0.00') - ,('85153901', '8515.39.01', 'Para soldar o cortar, de arco, tipo generador o transformador, inferior o igual a 1,260 amperes.', '6', 'Pza', '15.00', '0.00') - ,('85153902', '8515.39.02', 'Para soldar o cortar, de arco, tipo generador o transformador, superior a 1,260 amperes.', '6', 'Pza', '0.00', '0.00') - ,('85153999', '8515.39.99', 'Los demás.', '6', 'Pza', '35.00', '0.00') - ,('85158091', '8515.80.91', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('85159001', '8515.90.01', 'Pinzas portaelectrodos o sus partes, para soldadura por arco.', '1', 'Kg', '5.00', '0.00') - ,('85159099', '8515.90.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('85161002', '8516.10.02', 'Calentadores eléctricos de agua de calentamiento instantáneo o acumulación y calentadores eléctricos de inmersión.', '6', 'Pza', '10.00', '0.00') - ,('85162101', '8516.21.01', 'Radiadores de acumulación.', '6', 'Pza', '15.00', '0.00') - ,('85162999', '8516.29.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('85163101', '8516.31.01', 'Secadores para el cabello.', '6', 'Pza', '15.00', '0.00') - ,('85163291', '8516.32.91', 'Los demás aparatos para el cuidado del cabello.', '6', 'Pza', '15.00', '0.00') - ,('85163301', '8516.33.01', 'Aparatos para secar las manos.', '6', 'Pza', '0.00', '0.00') - ,('85164001', '8516.40.01', 'Planchas eléctricas.', '6', 'Pza', '15.00', '0.00') - ,('85165001', '8516.50.01', 'Hornos de microondas.', '6', 'Pza', '0.00', '0.00') - ,('85166001', '8516.60.01', 'Hornillos (incluidas las mesas de cocción), parrillas y asadores.', '6', 'Pza', '5.00', '0.00') - ,('85166003', '8516.60.03', 'Hornos.', '6', 'Pza', '10.00', '0.00') - ,('85166099', '8516.60.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('85167101', '8516.71.01', 'Aparatos para la preparación de café o té.', '6', 'Pza', '0.00', '0.00') - ,('85167201', '8516.72.01', 'Tostadoras de pan.', '6', 'Pza', '0.00', '0.00') - ,('85167901', '8516.79.01', 'Para calefacción de automóviles.', '6', 'Pza', '5.00', '0.00') - ,('85167999', '8516.79.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('85168004', '8516.80.04', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('85169007', '8516.90.07', 'Circuitos modulares reconocibles como diseñados exclusivamente para lo comprendido en la subpartida 8516.50.', '6', 'Pza', '0.00', '0.00') - ,('85169009', '8516.90.09', 'Panel superior con o sin elementos de calentamiento o control reconocibles como diseñados exclusivamente para cocinas y lo comprendido en las fracción arancelaria 8516.60.03.', '6', 'Pza', '0.00', '0.00') - ,('85169099', '8516.90.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('85171101', '8517.11.01', 'Teléfonos de auricular inalámbrico combinado con micrófono.', '6', 'Pza', '15.00', '0.00') - ,('85171301', '8517.13.01', 'Teléfonos inteligentes.', '6', 'Pza', '0.00', '0.00') - ,('85171491', '8517.14.91', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('85171891', '8517.18.91', 'Los demás teléfonos para servicio público.', '6', 'Pza', '0.00', '0.00') - ,('85171899', '8517.18.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('85176101', '8517.61.01', 'Estaciones base.', '6', 'Pza', '0.00', '0.00') - ,('85176217', '8517.62.17', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('85176907', '8517.69.07', 'Mesas de atención para operadora, para centrales telefónicas automáticas.', '6', 'Pza', '15.00', '0.00') - ,('85176913', '8517.69.13', 'Receptores de radiotelefonía o radiotelegrafía, fijos o móviles, en ultra alta frecuencia (UHF) de 300 a 470 MHz.', '1', 'Kg', '5.00', '0.00') - ,('85176914', '8517.69.14', 'Receptores de radiotelefonía o radiotelegrafía, fijos o móviles, en ultra alta frecuencia (UHF) de más de 470 MHz, a 1 GHz.', '1', 'Kg', '0.00', '0.00') - ,('85176915', '8517.69.15', 'Receptores de radiotelefonía o radiotelegrafía, fijos o móviles, en muy alta frecuencia modulada, excepto los receptores de radiotelefonía en muy alta frecuencia (VHF), de 243 a 250 MHz.', '6', 'Pza', '15.00', '0.00') - ,('85176916', '8517.69.16', 'Receptores de radiotelefonía o radiotelegrafía, fijos o móviles, en súper alta frecuencia (SHF) o de microondas de más de 1 GHz, con capacidad superior a 300 canales telefónicos o para un canal de televisión.', '6', 'Pza', '15.00', '0.00') - ,('85176917', '8517.69.17', 'Receptores de radiotelefonía, fijos o móviles en banda civil de 26.2 a 27.5 MHz.', '6', 'Pza', '15.00', '0.00') - ,('85176991', '8517.69.91', 'Los demás aparatos receptores.', '6', 'Pza', '10.00', '0.00') - ,('85176992', '8517.69.92', 'Los demás videófonos.', '6', 'Pza', '15.00', '0.00') - ,('85176999', '8517.69.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('85177101', '8517.71.01', 'Antenas y reflectores de antena de cualquier tipo; partes identificables para ser utilizadas con dichos artículos.', '6', 'Pza', '0.00', '0.00') - ,('85177901', '8517.79.01', 'Reconocibles como diseñadas exclusivamente para equipos telefónicos que incorporen al menos un circuito modular.', '6', 'Pza', '0.00', '0.00') - ,('85177902', '8517.79.02', 'Reconocibles como diseñadas exclusivamente para aparatos comprendidos en las subpartidas 8517.62 (excepto digitales), y 8517.69 (que incorporen al menos un circuito modular).', '6', 'Pza', '0.00', '0.00') - ,('85177904', '8517.79.04', 'Circuitos modulares.', '6', 'Pza', '0.00', '0.00') - ,('85177991', '8517.79.91', 'Las demás partes que incorporen al menos un circuito modular.', '6', 'Pza', '0.00', '0.00') - ,('85177999', '8517.79.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('85181004', '8518.10.04', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('85182102', '8518.21.02', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('85182202', '8518.22.02', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('85182999', '8518.29.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('85183003', '8518.30.03', 'Microteléfono.', '6', 'Pza', '15.00', '0.00') - ,('85183004', '8518.30.04', 'Auricular con cabezal combinado con micrófono (diadema), para operadora telefónica.', '1', 'Kg', '0.00', '0.00') - ,('85183099', '8518.30.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('85184001', '8518.40.01', 'Reconocibles para naves aéreas.', '6', 'Pza', '0.00', '0.00') - ,('85184002', '8518.40.02', 'Para operación sobre línea telefónica.', '6', 'Pza', '0.00', '0.00') - ,('85184004', '8518.40.04', 'Procesadores de audio o compresores, limitadores, expansores, controladores automáticos de ganancia, recortadores de pico con o sin ecualizadores, de uno o más canales con impedancia de entrada y salida de 600 ohms.', '6', 'Pza', '0.00', '0.00') - ,('85184099', '8518.40.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('85185001', '8518.50.01', 'Equipos eléctricos para amplificación de sonido.', '6', 'Pza', '15.00', '0.00') - ,('85189001', '8518.90.01', 'Reconocibles como diseñadas exclusivamente para micrófonos.', '1', 'Kg', '0.00', '0.00') - ,('85189002', '8518.90.02', 'Reconocibles como diseñadas exclusivamente para cápsulas transmisoras (micrófonos), para aparatos telefónicos.', '1', 'Kg', '0.00', '0.00') - ,('85189099', '8518.90.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('85192001', '8519.20.01', 'Aparatos activados con monedas, billetes, tarjetas, fichas o cualquier otro medio de pago.', '6', 'Pza', '15.00', '0.00') - ,('85193002', '8519.30.02', 'Giradiscos.', '6', 'Pza', '15.00', '0.00') - ,('85198103', '8519.81.03', 'Reproductores de casetes (tocacasetes) con potencia superior o igual a 60 W, excepto lo comprendido en la fracción arancelaria 8519.81.12.', '6', 'Pza', '5.00', '0.00') - ,('85198104', '8519.81.04', 'Reproductores con sistema de lectura óptica por haz de rayos láser (lectores de discos compactos), excepto los comprendidos en las fracciones arancelarias 8519.81.05 y 8519.81.06.', '6', 'Pza', '0.00', '0.00') - ,('85198105', '8519.81.05', 'Reproductores con sistema de lectura óptica por haz de rayos láser (lectores de discos compactos) reconocibles como diseñados exclusivamente para uso automotriz, excepto los comprendidos en la fracción arancelaria 8519.81.06.', '6', 'Pza', '0.00', '0.00') - ,('85198106', '8519.81.06', 'Reproductores con sistema de lectura óptica por haz de rayos láser (lectores de discos compactos), con cambiador automático incluido con capacidad de 6 o más discos, reconocibles como diseñados exclusivamente para uso automotriz.', '6', 'Pza', '0.00', '0.00') - ,('85198107', '8519.81.07', 'Reproductores de audio (en soportes de almacenamiento a base de semiconductores) digitales extraíbles, incluyendo los de tipo diadema.', '6', 'Pza', '0.00', '0.00') - ,('85198108', '8519.81.08', 'Aparatos para dictar, incluso con dispositivo de reproducción de sonido incorporado, que sólo funcionen con fuente de energía eléctrica exterior.', '6', 'Pza', '10.00', '0.00') - ,('85198109', '8519.81.09', 'Aparatos de grabación y reproducción de sonido, en cinta magnética de ancho superior o igual a 6 mm, para estaciones difusoras de radio o televisión y estudios de grabación.', '6', 'Pza', '5.00', '0.00') - ,('85198110', '8519.81.10', 'Aparatos de grabación con dispositivo de reproducción incorporado, portátiles, de sonido almacenado en soportes de tecnología digital.', '6', 'Pza', '0.00', '0.00') - ,('85198112', '8519.81.12', 'Reproductores de casetes (tocacasetes) de bolsillo; de tipo doméstico y/o para automóviles, con peso unitario igual o inferior a 3.5 kg.', '6', 'Pza', '0.00', '0.00') - ,('85198113', '8519.81.13', 'Contestadores telefónicos.', '6', 'Pza', '10.00', '0.00') - ,('85198199', '8519.81.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('85198901', '8519.89.01', 'Tornamesas profesionales ("turn-table"), incluso con altavoces (altoparlantes), sin cambiador automático ni mueble, reconocibles como diseñadas exclusivamente para el uso de radiodifusoras y estudios profesionales de grabación.', '6', 'Pza', '5.00', '0.00') - ,('85198999', '8519.89.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('85211002', '8521.10.02', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('85219001', '8521.90.01', 'Sistemas de grabación, archivo y reproducción de imágenes y sonido en televisión, compuestos por los siguientes elementos: a) unidad de manejo digital de audio y video, b) unidades de control automatizado de imágenes y sonido en medios magnéticos, c) ruteadores y, d) equipos de enlace.', '6', 'Pza', '0.00', '0.00') - ,('85219002', '8521.90.02', 'De disco o de elementos en estado sólido (chips), sin altavoces, excepto lo comprendido en las fracciones arancelarias 8521.90.03, 8521.90.04 y 8521.90.05.', '6', 'Pza', '0.00', '0.00') - ,('85219003', '8521.90.03', 'Unidades reproductoras de discos de video digitalizado (DVD), sin gabinete, reconocibles como diseñadas para su incorporación física en aparatos receptores de televisión.', '6', 'Pza', '0.00', '0.00') - ,('85219004', '8521.90.04', 'De disco, con amplificador incorporado, salidas para altavoces y procesador digital de audio, incluso con sintonizador de canales de televisión y/o sintonizador de bandas de radiodifusión, aun cuando se presenten con sus altavoces.', '6', 'Pza', '15.00', '0.00') - ,('85219005', '8521.90.05', 'Aparatos de grabación, almacenada en soportes de tecnología digital, incluso con dispositivo de reproducción incorporado, excepto lo comprendido en la fracción arancelaria 8521.90.04.', '6', 'Pza', '0.00', '0.00') - ,('85219099', '8521.90.99', 'Los demás.', '6', 'Pza', '5.00', '0.00') - ,('85221001', '8522.10.01', 'Cápsulas fonocaptoras.', '1', 'Kg', '0.00', '0.00') - ,('85229007', '8522.90.07', 'Circuitos modulares reconocibles como diseñados exclusivamente para lo comprendido en las partidas 85.19 y 85.21.', '6', 'Pza', '0.00', '0.00') - ,('85229099', '8522.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('85232102', '8523.21.02', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('85232905', '8523.29.05', 'Cintas magnéticas, grabadas, para reproducir fenómenos distintos del sonido o la imagen.', '1', 'Kg', '0.00', '0.00') - ,('85232907', '8523.29.07', 'Cintas magnéticas grabadas, para la enseñanza, con sonido o imágenes, técnicas, científicas o con fines culturales, reconocibles como diseñadas exclusivamente para instituciones de educación o similares.', '1', 'Kg', '0.00', '0.00') - ,('85232908', '8523.29.08', 'Cintas magnéticas grabadas, reconocibles como diseñadas exclusivamente para ser utilizadas en “video tape”, excepto cuando se presenten en cartuchos o casetes.', '1', 'Kg', '0.00', '0.00') - ,('85232991', '8523.29.91', 'Las demás cintas magnéticas grabadas.', '1', 'Kg', '10.00', '0.00') - ,('85232999', '8523.29.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('85234101', '8523.41.01', 'Discos de escritura (conocidos como CD-R; DVD-R, y demás formatos), para sistemas de lectura por rayo láser.', '6', 'Pza', '10.00', '0.00') - ,('85234199', '8523.41.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('85234999', '8523.49.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('85235101', '8523.51.01', 'Dispositivos de almacenamiento no volátil, regrabables, formados a base de elementos de estado sólido (semiconductores), por ejemplo: los llamados "tarjetas de memoria flash", "tarjeta de almacenamiento electrónico flash", "memory stick", "PC card", "secure digital", "compact flash", "smart media".', '6', 'Pza', '0.00', '0.00') - ,('85235199', '8523.51.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('85235203', '8523.52.03', 'Partes.', '1', 'Kg', '0.00', '0.00') - ,('85235901', '8523.59.01', 'Tarjetas y etiquetas de activación por proximidad.', '6', 'Pza', '0.00', '0.00') - ,('85235999', '8523.59.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('85238099', '8523.80.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('85241101', '8524.11.01', 'De cristal líquido.', '6', 'Pza', '0.00', '0.00') - ,('85241201', '8524.12.01', 'De diodos emisores de luz orgánicos (OLED).', '6', 'Pza', '0.00', '0.00') - ,('85241999', '8524.19.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('85249101', '8524.91.01', 'De cristal líquido.', '6', 'Pza', '0.00', '0.00') - ,('85249201', '8524.92.01', 'De diodos emisores de luz orgánicos (OLED).', '6', 'Pza', '0.00', '0.00') - ,('85249999', '8524.99.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('85255005', '8525.50.05', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('85256005', '8525.60.05', 'Fijos o móviles en ultra alta frecuencia (UHF) de 300 a 470 MHz.', '1', 'Kg', '0.00', '0.00') - ,('85256099', '8525.60.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('85258101', '8525.81.01', 'Ultrarrápidas, especificadas en la Nota 1 de subpartida de este Capítulo.', '6', 'Pza', '0.00', '0.00') - ,('85258291', '8525.82.91', 'Las demás, resistentes a radiaciones, especificadas en la Nota 2 de subpartida de este Capítulo.', '6', 'Pza', '0.00', '0.00') - ,('85258391', '8525.83.91', 'Las demás, de visión nocturna, especificadas en la Nota 3 de subpartida de este Capítulo.', '6', 'Pza', '0.00', '0.00') - ,('85258999', '8525.89.99', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('85261001', '8526.10.01', 'Radiosondas meteorológicas.', '6', 'Pza', '0.00', '0.00') - ,('85261099', '8526.10.99', 'Los demás.', '6', 'Pza', '5.00', '0.00') - ,('85269101', '8526.91.01', 'Radiogoniómetros con capacidad para sintonizar la banda de radiofaros (beacon), comprendida entre 180 y 420 kilociclos y sensibilidad superior o igual a 20 microvoltios por metro, con una relación señal a ruido de 6 decibeles.', '6', 'Pza', '0.00', '0.00') - ,('85269199', '8526.91.99', 'Los demás.', '6', 'Pza', '5.00', '0.00') - ,('85269201', '8526.92.01', 'Transmisores para el accionamiento de aparatos a control remoto mediante frecuencias ultrasónicas.', '6', 'Pza', '0.00', '0.00') - ,('85269299', '8526.92.99', 'Los demás.', '6', 'Pza', '10.00', '0.00') - ,('85271201', '8527.12.01', 'Radiocasetes de bolsillo.', '6', 'Pza', '15.00', '0.00') - ,('85271302', '8527.13.02', 'Portátiles, que utilicen tecnología digital con base a semiconductores.', '6', 'Pza', '0.00', '0.00') - ,('85271399', '8527.13.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('85271901', '8527.19.01', 'Portátiles, que utilicen tecnología digital con base a semiconductores.', '6', 'Pza', '0.00', '0.00') - ,('85271999', '8527.19.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('85272101', '8527.21.01', 'Receptores de radio AM-FM, aun cuando incluyan transmisores-receptores de radio banda civil o receptor de señal satelital, o entradas para "Bluethooth" o "USB".', '6', 'Pza', '15.00', '0.00') - ,('85272199', '8527.21.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('85272901', '8527.29.01', 'Receptores de radiodifusión, AM reconocibles como diseñados exclusivamente para uso automotriz.', '6', 'Pza', '15.00', '0.00') - ,('85272999', '8527.29.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('85279102', '8527.91.02', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('85279201', '8527.92.01', 'Sin combinar con grabador o reproductor de sonido, pero combinados con reloj.', '6', 'Pza', '15.00', '0.00') - ,('85279999', '8527.99.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('85284202', '8528.42.02', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('85284906', '8528.49.06', 'En blanco y negro o demás monocromos por cable coaxial.', '6', 'Pza', '0.00', '0.00') - ,('85284907', '8528.49.07', 'Incompletos o sin terminar (incluso los ensambles compuestos de las partes correspondientes entre las especificadas en los incisos a), b), c) y e) en la Nota Nacional 13 del Capítulo 85 más una fuente de poder), que no incorporen tubos de rayos catódicos.', '6', 'Pza', '0.00', '0.00') - ,('85284910', '8528.49.10', 'En colores.', '6', 'Pza', '0.00', '0.00') - ,('85284911', '8528.49.11', 'Sistemas audiovisuales integrados para control de accesos mediante múltiples pantallas (displays) de video de hasta 30 cm (12 pulgadas) con direccionamiento selectivo y automático.', '6', 'Pza', '0.00', '0.00') - ,('85284999', '8528.49.99', 'Los demás.', '6', 'Pza', '10.00', '0.00') - ,('85285202', '8528.52.02', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('85285901', '8528.59.01', 'Con pantalla inferior o igual a 35.56 cm (14 pulgadas), excepto los de alta definición.', '6', 'Pza', '0.00', '0.00') - ,('85285902', '8528.59.02', 'Con pantalla superior a 35.56 cm (14 pulgadas), excepto los de alta definición.', '6', 'Pza', '0.00', '0.00') - ,('85285903', '8528.59.03', 'De alta definición.', '6', 'Pza', '15.00', '0.00') - ,('85285904', '8528.59.04', 'Incompletos o sin terminar (incluso los ensambles compuestos de las partes correspondientes entre las especificadas en los incisos a), b), c) y e) en la Nota Nacional 13 del Capítulo 85 más una fuente de poder), que no incorporen pantalla plana o pantalla similar.', '6', 'Pza', '0.00', '0.00') - ,('85285999', '8528.59.99', 'Los demás.', '6', 'Pza', '10.00', '0.00') - ,('85286201', '8528.62.01', 'Aptos para ser conectados directamente y diseñados para ser utilizados con una máquina automática para tratamiento o procesamiento de datos de la partida 84.71.', '6', 'Pza', '0.00', '0.00') - ,('85286999', '8528.69.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('85287101', '8528.71.01', 'Incompletos o sin terminar (incluso los ensambles compuestos de todas las partes especificadas en la Nota Nacional 13 del Capítulo 85 más una fuente de poder).', '6', 'Pza', '0.00', '0.00') - ,('85287102', '8528.71.02', 'Receptor de microondas o de señales de vía satélite, cuya frecuencia de operación sea hasta de 4.2 GHz y máximo 999 canales de televisión.', '6', 'Pza', '15.00', '0.00') - ,('85287103', '8528.71.03', 'Sistema de recepción de microondas vía satélite, compuesto de localizador electrónico de satélites, convertidor de bajada, receptor cuya onda de frecuencia de operación sea de 3.7 a 4.2 GHz, amplificador de bajo ruido (LNA), guías de onda, polarrotor y corneta alimentadora.', '6', 'Pza', '0.00', '0.00') - ,('85287104', '8528.71.04', 'Sistema de recepción de microondas vía satélite, compuesto de un convertidor de bajada cuya frecuencia de operación sea de 11.7 a 14.5 GHz, y un receptor cuya frecuencia de operación sea de hasta 4.2 GHz.', '6', 'Pza', '0.00', '0.00') - ,('85287199', '8528.71.99', 'Los demás.', '6', 'Pza', '10.00', '0.00') - ,('85287201', '8528.72.01', 'Con pantalla inferior o igual a 35.56 cm (14 pulgadas), excepto los de alta definición, los tipo proyección y los comprendidos en la fracción arancelaria 8528.72.06.', '6', 'Pza', '15.00', '0.00') - ,('85287202', '8528.72.02', 'Con pantalla superior a 35.56 cm (14 pulgadas), excepto los de alta definición, los tipo proyección y los comprendidos en la fracción arancelaria 8528.72.06.', '6', 'Pza', '15.00', '0.00') - ,('85287203', '8528.72.03', 'De tipo proyección por tubos de rayos catódicos, excepto los de alta definición.', '6', 'Pza', '15.00', '0.00') - ,('85287204', '8528.72.04', 'De alta definición por tubo de rayo catódico, excepto los tipo proyección.', '6', 'Pza', '0.00', '0.00') - ,('85287205', '8528.72.05', 'De alta definición tipo proyección por tubo de rayos catódicos.', '6', 'Pza', '0.00', '0.00') - ,('85287206', '8528.72.06', 'Con pantalla plana, incluso las reconocibles como diseñadas para vehículos automóviles.', '6', 'Pza', '15.00', '0.00') - ,('85287207', '8528.72.07', 'Incompletos o sin terminar (incluso los ensambles compuestos de todas las partes especificadas en la Nota Nacional 13 del Capítulo 85 más una fuente de poder), que no incorporen tubos de rayos catódicos, pantalla plana o pantalla similar.', '6', 'Pza', '0.00', '0.00') - ,('85287299', '8528.72.99', 'Los demás.', '6', 'Pza', '10.00', '0.00') - ,('85287391', '8528.73.91', 'Los demás, monocromos.', '6', 'Pza', '15.00', '0.00') - ,('85291009', '8529.10.09', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('85299009', '8529.90.09', 'Combinaciones de las partes especificadas en la Nota Nacional 13 del Capítulo 85, excepto las comprendidas en la fracción arancelaria 8529.90.19.', '6', 'Pza', '0.00', '0.00') - ,('85299013', '8529.90.13', 'Amplificadores para transmisores de señales de televisión.', '6', 'Pza', '0.00', '0.00') - ,('85299015', '8529.90.15', 'Amplificadores-distribuidores, regeneradores de pulsos o de subportadora, para sistemas de televisión por cable.', '6', 'Pza', '5.00', '0.00') - ,('85299016', '8529.90.16', 'Amplificadores-distribuidores de video, con entrada diferencial, con compensación de cable o con restaurador de corriente continua, para sistemas de televisión, con o sin gabinete modular.', '6', 'Pza', '5.00', '0.00') - ,('85299018', '8529.90.18', 'Sintonizadores de canales para televisión y videocasetera; sintonizadores de doble convergencia.', '6', 'Pza', '0.00', '0.00') - ,('85299019', '8529.90.19', 'Sintonizadores de canal, combinados con otras partes especificadas en la Nota Nacional 13 del Capítulo 85.', '6', 'Pza', '0.00', '0.00') - ,('85299020', '8529.90.20', 'Amplificadores de radiofrecuencia, banda ancha y monocanales, para sistemas de distribución de señales de HF, TV y/o FM.', '6', 'Pza', '5.00', '0.00') - ,('85299021', '8529.90.21', 'Acoplador (combinador o defasador) para operar dos o más transmisores de radio o televisión a una salida común.', '6', 'Pza', '0.00', '0.00') - ,('85299099', '8529.90.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('85301001', '8530.10.01', 'Aparatos para vías férreas o similares.', '1', 'Kg', '0.00', '0.00') - ,('85308002', '8530.80.02', 'Equipos controladores de semáforos.', '1', 'Kg', '5.00', '0.00') - ,('85308099', '8530.80.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('85309001', '8530.90.01', 'Partes.', '1', 'Kg', '0.00', '0.00') - ,('85311001', '8531.10.01', 'Bocinas en o con caja tipo intemperie a prueba de humedad, gases, vapores, polvo y explosión.', '1', 'Kg', '0.00', '0.00') - ,('85311002', '8531.10.02', 'Campanas de alarma, con caja tipo intemperie a prueba de humedad, gases, vapores, polvos y explosión.', '1', 'Kg', '0.00', '0.00') - ,('85311003', '8531.10.03', 'Alarmas electrónicas contra robo o incendio, de uso doméstico o industrial, incluso en forma de sistema.', '6', 'Pza', '15.00', '0.00') - ,('85311004', '8531.10.04', 'Detectores acústicos, para sistemas de alarmas en bóvedas de seguridad; sistemas de seguridad a detección acústica y/o visual, con señalización local y remota, sin equipo de enlace por radiofrecuencia.', '1', 'Kg', '0.00', '0.00') - ,('85311099', '8531.10.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('85312001', '8531.20.01', 'Tableros indicadores con dispositivos de cristal líquido (LCD) o diodos emisores de luz (LED), incorporados.', '1', 'Kg', '0.00', '0.00') - ,('85318001', '8531.80.01', 'Sirenas.', '1', 'Kg', '5.00', '0.00') - ,('85318003', '8531.80.03', 'Zumbadores miniatura de corriente alterna, para aparatos telefónicos.', '1', 'Kg', '0.00', '0.00') - ,('85318099', '8531.80.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('85319002', '8531.90.02', 'Circuitos modulares.', '6', 'Pza', '0.00', '0.00') - ,('85319099', '8531.90.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('85321001', '8532.10.01', 'Fijos, monofásicos o trifásicos, con peso unitario superior a 1 kg.', '6', 'Pza', '5.00', '0.00') - ,('85321099', '8532.10.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('85322101', '8532.21.01', 'De tantalio.', '6', 'Pza', '0.00', '0.00') - ,('85322204', '8532.22.04', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('85322306', '8532.23.06', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('85322404', '8532.24.04', 'Con dieléctrico de cerámica, multicapas.', '6', 'Pza', '0.00', '0.00') - ,('85322502', '8532.25.02', 'Fijos de películas plásticas, excepto los reconocibles para naves aéreas y aquellos con dieléctrico de plástico, sin terminales, de montaje por contacto.', '6', 'Pza', '5.00', '0.00') - ,('85322599', '8532.25.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('85322999', '8532.29.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('85323005', '8532.30.05', 'Condensadores variables o ajustables.', '6', 'Pza', '0.00', '0.00') - ,('85329001', '8532.90.01', 'Partes.', '1', 'Kg', '0.00', '0.00') - ,('85331003', '8533.10.03', 'Resistencias de montaje por contacto; resistencias con terminales radiales.', '1', 'Kg', '0.00', '0.00') - ,('85332101', '8533.21.01', 'De potencia inferior o igual a 20 W.', '1', 'Kg', '0.00', '0.00') - ,('85332999', '8533.29.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('85333102', '8533.31.02', 'De potencia inferior o igual a 20 W.', '1', 'Kg', '0.00', '0.00') - ,('85333999', '8533.39.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('85334091', '8533.40.91', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('85339003', '8533.90.03', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('85340004', '8534.00.04', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('85351001', '8535.10.01', 'Reconocibles para naves aéreas.', '1', 'Kg', '0.00', '0.00') - ,('85351002', '8535.10.02', 'Cortacircuitos de fusibles de más de 46 kV.', '1', 'Kg', '0.00', '0.00') - ,('85351003', '8535.10.03', 'Fusibles.', '1', 'Kg', '0.00', '0.00') - ,('85351099', '8535.10.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('85352101', '8535.21.01', 'Para una tensión inferior a 72.5 kV.', '1', 'Kg', '0.00', '0.00') - ,('85352999', '8535.29.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('85353007', '8535.30.07', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('85354001', '8535.40.01', 'Pararrayos (apartarrayos) tipo distribución autovalvulares de 3 a 13 kV nominales, para sistemas con neutro a tierra hasta 37 kV.', '1', 'Kg', '5.00', '0.00') - ,('85354099', '8535.40.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('85359008', '8535.90.08', 'Llaves magnéticas (arrancadores magnéticos), con potencia nominal hasta 200 CP.', '6', 'Pza', '0.00', '0.00') - ,('85359020', '8535.90.20', 'Llaves magnéticas (arrancadores magnéticos), con potencia nominal superior a 200 CP.', '6', 'Pza', '0.00', '0.00') - ,('85359099', '8535.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('85361003', '8536.10.03', 'Fusibles de alta capacidad de ruptura, de 20,000 amperes eficaces o más y cualquier corriente nominal, hasta 600 voltios de tensión, inclusive; excepto los reconocibles para naves aéreas.', '1', 'Kg', '15.00', '0.00') - ,('85361004', '8536.10.04', 'Corta circuitos de fusible excepto los reconocibles para naves aéreas.', '1', 'Kg', '15.00', '0.00') - ,('85361099', '8536.10.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('85362001', '8536.20.01', 'Reconocibles para naves aéreas.', '1', 'Kg', '0.00', '0.00') - ,('85362002', '8536.20.02', 'Llaves disyuntoras, térmicas, reconocibles como diseñadas exclusivamente para radio o televisión.', '1', 'Kg', '0.00', '0.00') - ,('85362099', '8536.20.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('85363005', '8536.30.05', 'Protectores de sobrecarga para motores.', '6', 'Pza', '0.00', '0.00') - ,('85363099', '8536.30.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('85364103', '8536.41.03', 'Térmicos o por inducción.', '1', 'Kg', '5.00', '0.00') - ,('85364199', '8536.41.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('85364999', '8536.49.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('85365007', '8536.50.07', 'Interruptores automáticos, termoeléctricos, para el cebado de la descarga en las lámparas o tubos fluorescentes.', '1', 'Kg', '5.00', '0.00') - ,('85365099', '8536.50.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('85366104', '8536.61.04', 'Portalámparas.', '1', 'Kg', '0.00', '0.00') - ,('85366902', '8536.69.02', 'Tomas de corriente con peso unitario inferior o igual a 2 kg.', '1', 'Kg', '5.00', '0.00') - ,('85366999', '8536.69.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('85367001', '8536.70.01', 'Conectores de fibras ópticas, haces o cables de fibras ópticas.', '1', 'Kg', '0.00', '0.00') - ,('85369028', '8536.90.28', 'Cajas de conexión, de derivación, de corte, extremidad u otras cajas análogas.', '1', 'Kg', '5.00', '0.00') - ,('85369099', '8536.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('85371003', '8537.10.03', 'Cuadros de mando para máquinas de soldar por resistencia.', '1', 'Kg', '15.00', '0.00') - ,('85371004', '8537.10.04', 'Cuadros de mando o distribución, operados mediante botones (botoneras).', '1', 'Kg', '5.00', '0.00') - ,('85371005', '8537.10.05', 'Ensambles con la carcasa exterior o soporte, reconocibles como diseñados para lo comprendido en las partidas 84.21, 84.22, 84.50 y 85.16.', '6', 'Pza', '0.00', '0.00') - ,('85371006', '8537.10.06', 'Módulos reconocibles como diseñados exclusivamente para el control de señales de indicación en motores de vehículos automotrices.', '6', 'Pza', '0.00', '0.00') - ,('85371099', '8537.10.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('85372002', '8537.20.02', 'Cuadros de mando para máquinas de soldar por resistencia.', '1', 'Kg', '15.00', '0.00') - ,('85372099', '8537.20.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('85381001', '8538.10.01', 'Cuadros, paneles, consolas, armarios y demás soportes de la partida 85.37, sin sus aparatos.', '1', 'Kg', '0.00', '0.00') - ,('85389001', '8538.90.01', 'Partes moldeadas.', '6', 'Pza', '0.00', '0.00') - ,('85389005', '8538.90.05', 'Circuitos modulares.', '6', 'Pza', '0.00', '0.00') - ,('85389099', '8538.90.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('85391099', '8539.10.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('85392101', '8539.21.01', 'De incandescencia, de tubo de cuarzo ("halógenas" o "quartzline"), de 2,900°K (grados Kelvin) como mínimo.', '6', 'Pza', '0.00', '0.00') - ,('85392199', '8539.21.99', 'Los demás.', '6', 'Pza', '10.00', '0.00') - ,('85392201', '8539.22.01', 'Reconocibles como diseñadas exclusivamente para uso en aparatos de proyección cinematográfica y de vista fija.', '6', 'Pza', '0.00', '0.00') - ,('85392202', '8539.22.02', 'Con peso unitario inferior o igual a 20 g.', '6', 'Pza', '0.00', '0.00') - ,('85392203', '8539.22.03', 'Provistos de dos postes o espigas para su enchufe, con peso unitario superior a 120 g, sin exceder de 2 kg.', '6', 'Pza', '0.00', '0.00') - ,('85392204', '8539.22.04', 'Reconocibles para naves aéreas.', '6', 'Pza', '0.00', '0.00') - ,('85392205', '8539.22.05', 'De vidrio transparente azul natural, denominados "luz de día".', '6', 'Pza', '0.00', '0.00') - ,('85392299', '8539.22.99', 'Los demás.', '6', 'Pza', '10.00', '0.00') - ,('85392999', '8539.29.99', 'Los demás.', '6', 'Pza', '35.00', '0.00') - ,('85393101', '8539.31.01', 'Lámparas fluorescentes tubulares en forma de "O" o de "U".', '6', 'Pza', '0.00', '0.00') - ,('85393199', '8539.31.99', 'Las demás.', '6', 'Pza', '10.00', '0.00') - ,('85393204', '8539.32.04', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('85393901', '8539.39.01', 'Para luz relámpago.', '6', 'Pza', '5.00', '0.00') - ,('85393902', '8539.39.02', 'Reconocibles para naves aéreas.', '6', 'Pza', '0.00', '0.00') - ,('85393903', '8539.39.03', 'Lámparas fluorescentes tubulares en forma de "O" o de "U".', '6', 'Pza', '0.00', '0.00') - ,('85393904', '8539.39.04', 'De luz mixta (de descarga y filamento).', '6', 'Pza', '0.00', '0.00') - ,('85393905', '8539.39.05', 'Lámparas de neón.', '6', 'Pza', '0.00', '0.00') - ,('85393906', '8539.39.06', 'Lámparas de descarga de gases metálicos exclusivamente mezclados o combinados, tipo multivapor o similares.', '6', 'Pza', '0.00', '0.00') - ,('85393999', '8539.39.99', 'Los demás.', '6', 'Pza', '10.00', '0.00') - ,('85394101', '8539.41.01', 'Lámparas de arco.', '6', 'Pza', '0.00', '0.00') - ,('85394999', '8539.49.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('85395101', '8539.51.01', 'Para lo comprendido en la fracción arancelaria 8539.52.01 y para las máquinas de la partida 85.43.', '1', 'Kg', '0.00', '0.00') - ,('85395102', '8539.51.02', 'Para guirnaldas eléctricas de los tipos utilizados en árboles de Navidad.', '6', 'Pza', '0.00', '0.00') - ,('85395199', '8539.51.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('85395201', '8539.52.01', 'Lámparas y tubos de diodos emisores de luz (LED).', '6', 'Pza', '0.00', '0.00') - ,('85399007', '8539.90.07', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('85401106', '8540.11.06', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('85401202', '8540.12.02', 'Monocromos.', '6', 'Pza', '0.00', '0.00') - ,('85402002', '8540.20.02', 'Tubos para cámaras de televisión; tubos convertidores o intensificadores de imagen; los demás tubos de fotocátodo.', '6', 'Pza', '0.00', '0.00') - ,('85404002', '8540.40.02', 'Tubos para visualizar datos gráficos monocromos; tubos para visualizar datos gráficos en colores, con pantalla fosfórica de separación de puntos inferior a 0.4 mm.', '6', 'Pza', '0.00', '0.00') - ,('85406091', '8540.60.91', 'Los demás tubos de rayos catódicos.', '6', 'Pza', '0.00', '0.00') - ,('85407101', '8540.71.01', 'Magnetrones.', '6', 'Pza', '0.00', '0.00') - ,('85407999', '8540.79.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('85408103', '8540.81.03', 'Tubos receptores o amplificadores.', '6', 'Pza', '0.00', '0.00') - ,('85408999', '8540.89.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('85409105', '8540.91.05', 'De tubos de rayos catódicos.', '1', 'Kg', '0.00', '0.00') - ,('85409999', '8540.99.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('85411001', '8541.10.01', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('85412101', '8541.21.01', 'Con una capacidad de disipación inferior a 1 W.', '1', 'Kg', '0.00', '0.00') - ,('85412999', '8541.29.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('85413001', '8541.30.01', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('85414101', '8541.41.01', 'Diodos emisores de luz (LED).', '1', 'Kg', '0.00', '0.00') - ,('85414201', '8541.42.01', 'Células fotovoltaicas sin ensamblar en módulos ni paneles.', '1', 'Kg', '0.00', '0.00') - ,('85414301', '8541.43.01', 'Células fotovoltaicas ensambladas en módulos o paneles.', '1', 'Kg', '0.00', '0.00') - ,('85414999', '8541.49.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('85415101', '8541.51.01', 'Transductores basados en semiconductores.', '1', 'Kg', '0.00', '0.00') - ,('85415999', '8541.59.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('85416001', '8541.60.01', 'Cristales piezoeléctricos montados.', '1', 'Kg', '0.00', '0.00') - ,('85419002', '8541.90.02', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('85423103', '8542.31.03', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('85423202', '8542.32.02', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('85423302', '8542.33.02', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('85423999', '8542.39.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('85429001', '8542.90.01', 'Partes.', '1', 'Kg', '0.00', '0.00') - ,('85431002', '8543.10.02', 'Aceleradores de partículas.', '6', 'Pza', '0.00', '0.00') - ,('85432006', '8543.20.06', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('85433001', '8543.30.01', 'Máquinas y aparatos de galvanoplastia, electrólisis o electroforesis.', '6', 'Pza', '0.00', '0.00') - ,('85434001', '8543.40.01', 'Cigarrillos electrónicos y dispositivos personales de vaporización eléctricos o electrónicos similares, incluyendo aquellos novedosos y emergentes que utilicen tabaco calentado, los Sistemas electrónicos de administración de Nicotina (SEAN) Sistemas Similares Sin Nicotina (SSSN), y Sistemas Alternativos de Consumo de Nicotina (SACN) y similares.', '22', 'PROH', '0.00', '0.00') - ,('85437001', '8543.70.01', 'Electrificadores de cercas.', '6', 'Pza', '5.00', '0.00') - ,('85437008', '8543.70.08', 'Para electrocutar insectos voladores, mediante un sistema de rejillas electrizadas con voltaje elevado y que proyecte luz negra.', '6', 'Pza', '10.00', '0.00') - ,('85437017', '8543.70.17', 'Ecualizadores.', '6', 'Pza', '15.00', '0.00') - ,('85437099', '8543.70.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('85439001', '8543.90.01', 'Circuitos modulares.', '6', 'Pza', '0.00', '0.00') - ,('85439002', '8543.90.02', 'Microestructuras electrónicas.', '6', 'Pza', '0.00', '0.00') - ,('85439003', '8543.90.03', 'De las reconocidas para lo comprendido en la fracción arancelaria 8543.40.01.', '22', 'PROH', '0.00', '0.00') - ,('85439099', '8543.90.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('85441101', '8544.11.01', 'De cobre.', '1', 'Kg', '5.00', '0.00') - ,('85441999', '8544.19.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('85442001', '8544.20.01', 'Cables coaxiales, de uno o más conductores eléctricos, aislados y con funda de malla de metal, aun cuando vengan recubiertos de materias aislantes, con o sin mensajero de acero, con una impedancia de 50 a 75 ohms.', '1', 'Kg', '5.00', '0.00') - ,('85442002', '8544.20.02', 'Cables coaxiales de uno o más conductores concéntricos, aislados, aun cuando vengan recubiertos de materias aislantes, con o sin mensajero de acero, con una impedancia de 50 a 75 ohms, excepto lo comprendido en la fracción arancelaria 8544.20.01.', '1', 'Kg', '0.00', '0.00') - ,('85442003', '8544.20.03', 'Reconocibles para naves aéreas.', '1', 'Kg', '0.00', '0.00') - ,('85442099', '8544.20.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('85443001', '8544.30.01', 'Reconocibles para naves aéreas.', '1', 'Kg', '0.00', '0.00') - ,('85443099', '8544.30.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('85444205', '8544.42.05', 'Reconocibles para naves aéreas.', '1', 'Kg', '0.00', '0.00') - ,('85444299', '8544.42.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('85444905', '8544.49.05', 'Reconocibles para naves aéreas.', '1', 'Kg', '0.00', '0.00') - ,('85444999', '8544.49.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('85446091', '8544.60.91', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('85447001', '8544.70.01', 'Cables de fibras ópticas.', '1', 'Kg', '35.00', '0.00') - ,('85451101', '8545.11.01', 'De los tipos utilizados en hornos.', '1', 'Kg', '0.00', '0.00') - ,('85451999', '8545.19.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('85452001', '8545.20.01', 'Escobillas.', '1', 'Kg', '0.00', '0.00') - ,('85459099', '8545.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('85461001', '8546.10.01', 'Tubulares.', '1', 'Kg', '0.00', '0.00') - ,('85461002', '8546.10.02', 'Esbozos, en forma de campana, con diámetro superior o igual a 15 cm en la circunferencia mayor.', '1', 'Kg', '0.00', '0.00') - ,('85461003', '8546.10.03', 'Reconocibles para naves aéreas.', '1', 'Kg', '0.00', '0.00') - ,('85461099', '8546.10.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('85462001', '8546.20.01', 'Tubulares, excepto lo comprendido en la fracción arancelaria 8546.20.05.', '1', 'Kg', '0.00', '0.00') - ,('85462002', '8546.20.02', 'De suspensión.', '1', 'Kg', '0.00', '0.00') - ,('85462003', '8546.20.03', 'De porcelana o de esteatita para radio y televisión.', '1', 'Kg', '0.00', '0.00') - ,('85462004', '8546.20.04', 'Reconocibles para naves aéreas.', '1', 'Kg', '0.00', '0.00') - ,('85462005', '8546.20.05', 'Tubo cilíndrico exterior, de porcelana, para fusible limitador de corriente, liso, con una longitud superior o igual a 100 mm pero inferior o igual a 600 mm.', '6', 'Pza', '0.00', '0.00') - ,('85462099', '8546.20.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('85469001', '8546.90.01', 'Tubulares.', '1', 'Kg', '0.00', '0.00') - ,('85469002', '8546.90.02', 'Reconocibles para naves aéreas.', '1', 'Kg', '0.00', '0.00') - ,('85469003', '8546.90.03', 'De resina, epóxica, no rígidos.', '1', 'Kg', '0.00', '0.00') - ,('85469099', '8546.90.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('85471006', '8547.10.06', 'Elemento interno de fusible limitador de corriente, en forma cilíndrica o de estrella, de cerámica, con longitud superior o igual a 100 mm pero inferior o igual a 600 mm.', '6', 'Pza', '0.00', '0.00') - ,('85471099', '8547.10.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('85472004', '8547.20.04', 'Piezas aislantes de plástico.', '1', 'Kg', '0.00', '0.00') - ,('85479099', '8547.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('85480001', '8548.00.01', 'Microestructuras electrónicas.', '6', 'Pza', '0.00', '0.00') - ,('85480099', '8548.00.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('85491101', '8549.11.01', 'Desperdicios y desechos de acumuladores de plomo y ácido; acumuladores de plomo y ácido inservibles.', '1', 'Kg', '15.00', '0.00') - ,('85491291', '8549.12.91', 'Los demás, que contengan plomo, cadmio o mercurio.', '1', 'Kg', '15.00', '0.00') - ,('85491301', '8549.13.01', 'Clasificados por tipo de componente químico, que no contengan plomo, cadmio o mercurio.', '1', 'Kg', '15.00', '0.00') - ,('85491401', '8549.14.01', 'Sin clasificar, que no contengan plomo, cadmio o mercurio.', '1', 'Kg', '15.00', '0.00') - ,('85491999', '8549.19.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('85492101', '8549.21.01', 'Que contengan pilas, baterías de pilas, acumuladores eléctricos, interruptores de mercurio, vidrio de tubos de rayos catódicos u otros vidrios activados, o componentes eléctricos o electrónicos que contengan cadmio, mercurio, plomo o bifenilos policlorados (PCB).', '1', 'Kg', '15.00', '0.00') - ,('85492999', '8549.29.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('85493101', '8549.31.01', 'Que contengan pilas, baterías de pilas, acumuladores eléctricos, interruptores de mercurio, vidrio de tubos de rayos catódicos u otros vidrios activados, o componentes eléctricos o electrónicos que contengan cadmio, mercurio, plomo o bifenilos policlorados (PCB).', '1', 'Kg', '15.00', '0.00') - ,('85493999', '8549.39.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('85499101', '8549.91.01', 'Que contengan pilas, baterías de pilas, acumuladores eléctricos, interruptores de mercurio, vidrio de tubos de rayos catódicos u otros vidrios activados, o componentes eléctricos o electrónicos que contengan cadmio, mercurio, plomo o bifenilos policlorados (PCB).', '1', 'Kg', '15.00', '0.00') - ,('85499999', '8549.99.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('86011001', '8601.10.01', 'De fuente externa de electricidad.', '6', 'Pza', '0.00', '0.00') - ,('86012001', '8601.20.01', 'De acumuladores eléctricos.', '6', 'Pza', '0.00', '0.00') - ,('86021001', '8602.10.01', 'Locomotoras diésel-eléctricas.', '6', 'Pza', '0.00', '0.00') - ,('86029099', '8602.90.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('86031001', '8603.10.01', 'De fuente externa de electricidad.', '6', 'Pza', '0.00', '0.00') - ,('86039099', '8603.90.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('86040003', '8604.00.03', 'Los demás.', '6', 'Pza', '5.00', '0.00') - ,('86050001', '8605.00.01', 'Coches de viajeros.', '6', 'Pza', '10.00', '0.00') - ,('86050099', '8605.00.99', 'Los demás.', '6', 'Pza', '5.00', '0.00') - ,('86061001', '8606.10.01', 'Vagones cisterna y similares.', '6', 'Pza', '5.00', '0.00') - ,('86063001', '8606.30.01', 'Vagones de descarga automática, excepto los de la subpartida 8606.10.', '6', 'Pza', '0.00', '0.00') - ,('86069102', '8606.91.02', 'Cubiertos y cerrados.', '6', 'Pza', '5.00', '0.00') - ,('86069201', '8606.92.01', 'Abiertos, con pared fija de altura superior a 60 cm.', '6', 'Pza', '5.00', '0.00') - ,('86069999', '8606.99.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('86071101', '8607.11.01', 'Bojes y "bissels", de tracción.', '1', 'Kg', '0.00', '0.00') - ,('86071291', '8607.12.91', 'Los demás bojes y "bissels".', '1', 'Kg', '0.00', '0.00') - ,('86071902', '8607.19.02', 'Ejes montados con sus ruedas.', '1', 'Kg', '0.00', '0.00') - ,('86071903', '8607.19.03', 'Ruedas.', '1', 'Kg', '0.00', '0.00') - ,('86071999', '8607.19.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('86072102', '8607.21.02', 'Frenos de aire comprimido y sus partes.', '1', 'Kg', '0.00', '0.00') - ,('86072999', '8607.29.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('86073001', '8607.30.01', 'Ganchos y demás sistemas de enganche, topes, y sus partes.', '1', 'Kg', '0.00', '0.00') - ,('86079102', '8607.91.02', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('86079999', '8607.99.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('86080004', '8608.00.04', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('86090001', '8609.00.01', 'Contenedores (incluidos los contenedores cisterna y los contenedores depósito) especialmente diseñados y equipados para uno o varios medios de transporte.', '6', 'Pza', '15.00', '0.00') - ,('87011001', '8701.10.01', 'Tractores de un solo eje.', '6', 'Pza', '0.00', '0.00') - ,('87012101', '8701.21.01', 'Usados.', '6', 'Pza', '50.00', '0.00') - ,('87012199', '8701.21.99', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('87012201', '8701.22.01', 'Usados.', '6', 'Pza', '50.00', '0.00') - ,('87012299', '8701.22.99', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('87012301', '8701.23.01', 'Usados.', '6', 'Pza', '50.00', '0.00') - ,('87012399', '8701.23.99', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('87012401', '8701.24.01', 'Usados.', '6', 'Pza', '50.00', '0.00') - ,('87012499', '8701.24.99', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('87012901', '8701.29.01', 'Usados.', '6', 'Pza', '50.00', '0.00') - ,('87012999', '8701.29.99', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('87013001', '8701.30.01', 'Tractores de orugas con potencia al volante del motor superior o igual a 105 CP sin exceder de 380 CP, medida a 1900 RPM, incluso con hoja empujadora.', '6', 'Pza', '15.00', '0.00') - ,('87013099', '8701.30.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('87019101', '8701.91.01', 'Tractores de ruedas con toma de fuerza o enganche de tres puntos, para acoplamiento de implementos agrícolas, excepto aquellos cuyo número de serie o modelo sea al menos 2 años anterior al vigente.', '6', 'Pza', '5.00', '0.00') - ,('87019102', '8701.91.02', 'Tractores de ruedas con toma de fuerza o enganche de tres puntos, para acoplamiento de implementos agrícolas, incompletos o sin terminar, con transmisión manual sincronizada de 12 por 12 e inversor de marcha, que no incorpore al menos nueve de los siguientes elementos: el conjunto de rines, toldo, brazos de levante, contrapesos, tubo de escape, eje delantero, soporte frontal, radiador, salpicaduras, barra de tiro, horquilla barra de tiro, escalón y soportes, excepto aquellos cuyo número de seri', '6', 'Pza', '0.00', '0.00') - ,('87019199', '8701.91.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('87019201', '8701.92.01', 'Tractores de ruedas con toma de fuerza o enganche de tres puntos, para acoplamiento de implementos agrícolas, excepto aquellos cuyo número de serie o modelo sea al menos 2 años anterior al vigente y lo comprendido en la fracción arancelaria 8701.92.02.', '6', 'Pza', '5.00', '0.00') - ,('87019202', '8701.92.02', 'Tractores de ruedas con toma de fuerza o enganche de tres puntos, para acoplamiento de implementos agrícolas, con potencia superior o igual a 32 CP.', '6', 'Pza', '0.00', '0.00') - ,('87019203', '8701.92.03', 'Tractores de ruedas con toma de fuerza o enganche de tres puntos, para acoplamiento de implementos agrícolas, incompletos o sin terminar, con transmisión manual sincronizada de 12 por 12 e inversor de marcha, que no incorpore al menos nueve de los siguientes elementos: el conjunto de rines, toldo, brazos de levante, contrapesos, tubo de escape, eje delantero, soporte frontal, radiador, salpicaduras, barra de tiro, horquilla barra de tiro, escalón y soportes, excepto aquellos cuyo número de seri', '6', 'Pza', '0.00', '0.00') - ,('87019299', '8701.92.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('87019301', '8701.93.01', 'Tractores de ruedas con toma de fuerza o enganche de tres puntos, para acoplamiento de implementos agrícolas, con potencia superior a 53 CP, excepto aquellos cuyo número de serie o modelo sea al menos 2 años anterior al vigente.', '6', 'Pza', '5.00', '0.00') - ,('87019302', '8701.93.02', 'Tractores de ruedas con toma de fuerza o enganche de tres puntos, para acoplamiento de implementos agrícolas, incompletos o sin terminar, con transmisión manual sincronizada de 12 por 12 e inversor de marcha, que no incorpore al menos nueve de los siguientes elementos: el conjunto de rines, toldo, brazos de levante, contrapesos, tubo de escape, eje delantero, soporte frontal, radiador, salpicaduras, barra de tiro, horquilla barra de tiro, escalón y soportes, excepto aquellos cuyo número de seri', '6', 'Pza', '0.00', '0.00') - ,('87019399', '8701.93.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('87019401', '8701.94.01', 'Tractores de ruedas con toma de fuerza o enganche de tres puntos, para acoplamiento de implementos agrícolas, excepto lo comprendido en las fracciones arancelarias 8701.94.02, 8701.94.03 y 8701.94.04.', '6', 'Pza', '5.00', '0.00') - ,('87019402', '8701.94.02', 'Tractores de ruedas con toma de fuerza o enganche de tres puntos, para acoplamiento de implementos agrícolas cuyo número de serie o modelo sea al menos 2 años anterior al vigente.', '6', 'Pza', '0.00', '0.00') - ,('87019403', '8701.94.03', 'Tractores de ruedas con toma de fuerza o enganche de tres puntos, para acoplamiento de implementos agrícolas, con potencia superior o igual a 140 CP, transmisión manual y tablero de instrumentos analógico, excepto lo comprendido en la fracción arancelaria 8701.94.02.', '6', 'Pza', '0.00', '0.00') - ,('87019404', '8701.94.04', 'Tractores de ruedas con toma de fuerza o enganche de tres puntos, para acoplamiento de implementos agrícolas, con potencia superior o igual a 106 CP pero inferior a 140 CP, con cabina con aire acondicionado y transmisión syncroplus o powerquad, excepto lo comprendido en la fracción arancelaria 8701.94.02.', '6', 'Pza', '0.00', '0.00') - ,('87019405', '8701.94.05', 'Tractores de ruedas con toma de fuerza o enganche de tres puntos, para acoplamiento de implementos agrícolas, incompletos o sin terminar, con transmisión manual sincronizada de 12 por 12 e inversor de marcha, que no incorpore al menos nueve de los siguientes elementos: el conjunto de rines, toldo, brazos de levante, contrapesos, tubo de escape, eje delantero, soporte frontal, radiador, salpicaduras, barra de tiro, horquilla barra de tiro, escalón y soportes, excepto lo comprendido en las fracci', '6', 'Pza', '0.00', '0.00') - ,('87019499', '8701.94.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('87019501', '8701.95.01', 'Tractores de ruedas con toma de fuerza o enganche de tres puntos, para acoplamiento de implementos agrícolas, con potencia superior a 180 CP, excepto aquellos cuyo número de serie o modelo sea al menos 2 años anterior al vigente.', '6', 'Pza', '5.00', '0.00') - ,('87019502', '8701.95.02', 'Tractores de ruedas con toma de fuerza o enganche de tres puntos, para acoplamiento de implementos agrícolas, incompletos o sin terminar, con transmisión manual sincronizada de 12 por 12 e inversor de marcha, que no incorpore al menos nueve de los siguientes elementos: el conjunto de rines, toldo, brazos de levante, contrapesos, tubo de escape, eje delantero, soporte frontal, radiador, salpicaduras, barra de tiro, horquilla barra de tiro, escalón y soportes, excepto aquellos cuyo número de seri', '6', 'Pza', '0.00', '0.00') - ,('87019599', '8701.95.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('87021005', '8702.10.05', 'Usados.', '6', 'Pza', '50.00', '0.00') - ,('87021099', '8702.10.99', 'Para el transporte de 16 o más personas, incluyendo el conductor, con carrocería integral.', '6', 'Pza', '20.00', '0.00') - ,('87022005', '8702.20.05', 'Usados.', '6', 'Pza', '50.00', '0.00') - ,('87022099', '8702.20.99', 'Para el transporte de 16 o más personas, incluyendo el conductor, con carrocería integral.', '6', 'Pza', '20.00', '0.00') - ,('87023005', '8702.30.05', 'Usados.', '6', 'Pza', '50.00', '0.00') - ,('87023099', '8702.30.99', 'Para el transporte de 16 o más personas, incluyendo el conductor, con carrocería integral.', '6', 'Pza', '20.00', '0.00') - ,('87024001', '8702.40.01', 'Trolebuses, excepto usados.', '6', 'Pza', '0.00', '0.00') - ,('87024002', '8702.40.02', 'Para el transporte de 16 o más personas, incluyendo el conductor, con carrocería integral.', '6', 'Pza', '0.00', '0.00') - ,('87024006', '8702.40.06', 'Usados.', '6', 'Pza', '50.00', '0.00') - ,('87024007', '8702.40.07', 'Trolebuses usados.', '6', 'Pza', '15.00', '0.00') - ,('87029001', '8702.90.01', 'Trolebuses, excepto usados.', '6', 'Pza', '0.00', '0.00') - ,('87029006', '8702.90.06', 'Usados, excepto lo comprendido en la fracción arancelaria 8702.90.08.', '6', 'Pza', '50.00', '0.00') - ,('87029008', '8702.90.08', 'Trolebuses usados.', '6', 'Pza', '15.00', '0.00') - ,('87029099', '8702.90.99', 'Para el transporte de 16 o más personas, incluyendo el conductor, con carrocería integral.', '6', 'Pza', '20.00', '0.00') - ,('87031004', '8703.10.04', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('87032101', '8703.21.01', 'Motociclos de tres ruedas (trimotos) que presenten una dirección tipo automóvil o, al mismo tiempo, diferencial y reversa; motociclos de cuatro ruedas (cuadrimotos) con dirección tipo automóvil.', '6', 'Pza', '15.00', '0.00') - ,('87032102', '8703.21.02', 'Usados, excepto lo comprendido en la fracción arancelaria 8703.21.01.', '6', 'Pza', '50.00', '0.00') - ,('87032199', '8703.21.99', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('87032202', '8703.22.02', 'Usados.', '6', 'Pza', '50.00', '0.00') - ,('87032299', '8703.22.99', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('87032302', '8703.23.02', 'Usados.', '6', 'Pza', '50.00', '0.00') - ,('87032399', '8703.23.99', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('87032402', '8703.24.02', 'Usados.', '6', 'Pza', '50.00', '0.00') - ,('87032499', '8703.24.99', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('87033102', '8703.31.02', 'Usados.', '6', 'Pza', '50.00', '0.00') - ,('87033199', '8703.31.99', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('87033202', '8703.32.02', 'Usados.', '6', 'Pza', '50.00', '0.00') - ,('87033299', '8703.32.99', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('87033302', '8703.33.02', 'Usados.', '6', 'Pza', '50.00', '0.00') - ,('87033399', '8703.33.99', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('87034002', '8703.40.02', 'Usados, excepto lo comprendido en la fracción arancelaria 8703.40.03.', '6', 'Pza', '50.00', '0.00') - ,('87034003', '8703.40.03', 'Motociclos de tres ruedas (trimotos), de cilindrada inferior o igual a 1,000 cm³, que presenten una dirección tipo automóvil o, al mismo tiempo, diferencial y reversa; motociclos de cuatro ruedas (cuadrimotos) con dirección tipo automóvil.', '6', 'Pza', '15.00', '0.00') - ,('87034099', '8703.40.99', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('87035002', '8703.50.02', 'Usados.', '6', 'Pza', '50.00', '0.00') - ,('87035099', '8703.50.99', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('87036002', '8703.60.02', 'Usados, excepto lo comprendido en la fracción arancelaria 8703.60.03.', '6', 'Pza', '50.00', '0.00') - ,('87036003', '8703.60.03', 'Motociclos de tres ruedas (trimotos), de cilindrada inferior o igual a 1,000 cm³, que presenten una dirección tipo automóvil o, al mismo tiempo, diferencial y reversa; motociclos de cuatro ruedas (cuadrimotos) con dirección tipo automóvil.', '6', 'Pza', '15.00', '0.00') - ,('87036099', '8703.60.99', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('87037002', '8703.70.02', 'Usados.', '6', 'Pza', '50.00', '0.00') - ,('87037099', '8703.70.99', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('87038001', '8703.80.01', 'Eléctricos, excepto usados.', '6', 'Pza', '0.00', '0.00') - ,('87038002', '8703.80.02', 'Usados.', '6', 'Pza', '15.00', '0.00') - ,('87039002', '8703.90.02', 'Usados.', '6', 'Pza', '50.00', '0.00') - ,('87039099', '8703.90.99', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('87041002', '8704.10.02', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('87042101', '8704.21.01', 'Acarreadores de escoria, excepto para la recolección de basura doméstica.', '6', 'Pza', '5.00', '0.00') - ,('87042104', '8704.21.04', 'Usados, excepto lo comprendido en la fracción arancelaria 8704.21.01.', '6', 'Pza', '50.00', '0.00') - ,('87042199', '8704.21.99', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('87042201', '8704.22.01', 'Acarreadores de escoria, excepto para la recolección de basura doméstica.', '6', 'Pza', '5.00', '0.00') - ,('87042207', '8704.22.07', 'Usados, excepto lo comprendido en la fracción arancelaria 8704.22.01.', '6', 'Pza', '50.00', '0.00') - ,('87042299', '8704.22.99', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('87042301', '8704.23.01', 'Acarreadores de escoria.', '6', 'Pza', '5.00', '0.00') - ,('87042302', '8704.23.02', 'Usados, excepto lo comprendido en la fracción arancelaria 8704.23.01.', '6', 'Pza', '50.00', '0.00') - ,('87042399', '8704.23.99', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('87043101', '8704.31.01', 'Acarreadores de escoria, excepto para la recolección de basura doméstica.', '6', 'Pza', '5.00', '0.00') - ,('87043102', '8704.31.02', 'Motociclos de tres ruedas (trimotos) que presenten una dirección tipo automóvil o, al mismo tiempo, diferencial y reversa; motociclos de cuatro ruedas (cuadrimotos) con dirección tipo automóvil.', '6', 'Pza', '15.00', '0.00') - ,('87043105', '8704.31.05', 'Usados, excepto lo comprendido en las fracciones arancelarias 8704.31.01 y 8704.31.02.', '6', 'Pza', '50.00', '0.00') - ,('87043199', '8704.31.99', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('87043201', '8704.32.01', 'Acarreadores de escoria, excepto para la recolección de basura doméstica.', '6', 'Pza', '5.00', '0.00') - ,('87043207', '8704.32.07', 'Usados, excepto lo comprendido en la fracción arancelaria 8704.32.01.', '6', 'Pza', '50.00', '0.00') - ,('87043299', '8704.32.99', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('87044101', '8704.41.01', 'Acarreadores de escoria, excepto para la recolección de basura doméstica.', '6', 'Pza', '5.00', '0.00') - ,('87044102', '8704.41.02', 'Usados, excepto lo comprendido en la fracción arancelaria 8704.41.01.', '6', 'Pza', '50.00', '0.00') - ,('87044199', '8704.41.99', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('87044201', '8704.42.01', 'Acarreadores de escoria, excepto para la recolección de basura doméstica.', '6', 'Pza', '5.00', '0.00') - ,('87044202', '8704.42.02', 'Usados, excepto lo comprendido en la fracción arancelaria 8704.42.01.', '6', 'Pza', '50.00', '0.00') - ,('87044299', '8704.42.99', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('87044301', '8704.43.01', 'Acarreadores de escoria.', '6', 'Pza', '5.00', '0.00') - ,('87044302', '8704.43.02', 'Usados, excepto lo comprendido en la fracción arancelaria 8704.43.01.', '6', 'Pza', '50.00', '0.00') - ,('87044399', '8704.43.99', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('87045101', '8704.51.01', 'Acarreadores de escoria, excepto para la recolección de basura doméstica.', '6', 'Pza', '5.00', '0.00') - ,('87045102', '8704.51.02', 'Motociclos de tres ruedas (trimotos) que presenten una dirección tipo automóvil o, al mismo tiempo, diferencial y reversa; motociclos de cuatro ruedas (cuadrimotos) con dirección tipo automóvil.', '6', 'Pza', '15.00', '0.00') - ,('87045103', '8704.51.03', 'Usados, excepto lo comprendido en las fracciones arancelarias 8704.51.01 y 8704.51.02.', '6', 'Pza', '50.00', '0.00') - ,('87045199', '8704.51.99', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('87045201', '8704.52.01', 'Acarreadores de escoria, excepto para la recolección de basura doméstica.', '6', 'Pza', '5.00', '0.00') - ,('87045202', '8704.52.02', 'Usados, excepto lo comprendido en la fracción arancelaria 8704.52.01.', '6', 'Pza', '50.00', '0.00') - ,('87045299', '8704.52.99', 'Los demás.', '6', 'Pza', '20.00', '0.00') - ,('87046001', '8704.60.01', 'Usados.', '6', 'Pza', '15.00', '0.00') - ,('87046002', '8704.60.02', 'Eléctricos, excepto usados.', '6', 'Pza', '0.00', '0.00') - ,('87049099', '8704.90.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('87051001', '8705.10.01', 'Camiones-grúa.', '6', 'Pza', '15.00', '0.00') - ,('87052001', '8705.20.01', 'Con equipos hidráulicos de perforación destinados a programas de abastecimiento de agua potable en el medio rural.', '6', 'Pza', '5.00', '0.00') - ,('87052099', '8705.20.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('87053001', '8705.30.01', 'Camiones de bomberos.', '6', 'Pza', '5.00', '0.00') - ,('87054001', '8705.40.01', 'Camiones hormigonera, excepto lo comprendido en la fracción arancelaria 8705.40.02.', '6', 'Pza', '20.00', '0.00') - ,('87054002', '8705.40.02', 'Usados.', '6', 'Pza', '50.00', '0.00') - ,('87059001', '8705.90.01', 'Con equipos especiales para el aseo de calles.', '6', 'Pza', '0.00', '0.00') - ,('87059002', '8705.90.02', 'Para aplicación de fertilizantes fluidos.', '6', 'Pza', '0.00', '0.00') - ,('87059099', '8705.90.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('87060001', '8706.00.01', 'Chasis con motor de explosión, de dos cilindros de 700 cm³, de cuatro tiempos y con potencia inferior a 20 CP (15 kW).', '6', 'Pza', '0.00', '0.00') - ,('87060099', '8706.00.99', 'Los demás.', '6', 'Pza', '25.00', '0.00') - ,('87071002', '8707.10.02', 'De vehículos de la partida 87.03.', '6', 'Pza', '0.00', '0.00') - ,('87079002', '8707.90.02', 'Para transporte de más de 16 personas, para ser montadas sobre chasis de largueros completos, para vehículos de dos ejes.', '6', 'Pza', '35.00', '0.00') - ,('87079099', '8707.90.99', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('87081001', '8708.10.01', 'Defensas para trolebuses.', '1', 'Kg', '0.00', '0.00') - ,('87081002', '8708.10.02', 'Defensas reconocibles como diseñadas exclusivamente para tractores agrícolas.', '1', 'Kg', '0.00', '0.00') - ,('87081003', '8708.10.03', 'Defensas completas, reconocibles como diseñadas exclusivamente para vehículos automóviles de hasta diez plazas.', '6', 'Pza', '5.00', '0.00') - ,('87081099', '8708.10.99', 'Los demás.', '6', 'Pza', '10.00', '0.00') - ,('87082101', '8708.21.01', 'Cinturones de seguridad.', '6', 'Pza', '0.00', '0.00') - ,('87082201', '8708.22.01', 'Parabrisas, vidrios traseros (lunetas) y demás ventanillas especificados en la Nota 1 de subpartida de este Capítulo.', '6', 'Pza', '10.00', '0.00') - ,('87082901', '8708.29.01', 'Guardafangos.', '6', 'Pza', '0.00', '0.00') - ,('87082902', '8708.29.02', 'Capots (cofres).', '6', 'Pza', '0.00', '0.00') - ,('87082903', '8708.29.03', 'Estribos.', '6', 'Pza', '0.00', '0.00') - ,('87082904', '8708.29.04', 'Viseras, forros de tablero, paneles de puerta, coderas, sombrereras, incluso acojinadas.', '6', 'Pza', '0.00', '0.00') - ,('87082905', '8708.29.05', 'Para trolebuses.', '6', 'Pza', '0.00', '0.00') - ,('87082906', '8708.29.06', 'Para tractores de ruedas.', '6', 'Pza', '0.00', '0.00') - ,('87082907', '8708.29.07', 'Parrillas de adorno y protección para radiador.', '6', 'Pza', '0.00', '0.00') - ,('87082908', '8708.29.08', 'Biseles.', '6', 'Pza', '0.00', '0.00') - ,('87082909', '8708.29.09', 'Tapas de cajuelas portaequipajes.', '6', 'Pza', '0.00', '0.00') - ,('87082910', '8708.29.10', 'Marcos para cristales.', '6', 'Pza', '5.00', '0.00') - ,('87082911', '8708.29.11', 'Aletas, excepto de vidrio, aun cuando se presenten con marco.', '6', 'Pza', '0.00', '0.00') - ,('87082912', '8708.29.12', 'Soportes o armazones para acojinados.', '6', 'Pza', '0.00', '0.00') - ,('87082913', '8708.29.13', 'Reconocibles como diseñados exclusivamente para capots (cofres).', '6', 'Pza', '0.00', '0.00') - ,('87082916', '8708.29.16', 'Toldos exteriores acojinados, techos corredizos centrales o laterales y sus partes; de accionamiento manual o electrónico.', '6', 'Pza', '0.00', '0.00') - ,('87082917', '8708.29.17', 'Juntas preformadas para carrocería.', '6', 'Pza', '0.00', '0.00') - ,('87082918', '8708.29.18', 'Reconocibles como diseñados exclusivamente para lo comprendido en la fracción arancelaria 8701.30.01.', '6', 'Pza', '0.00', '0.00') - ,('87082919', '8708.29.19', 'Ensambles de puertas.', '6', 'Pza', '0.00', '0.00') - ,('87082920', '8708.29.20', 'Partes troqueladas para carrocería.', '6', 'Pza', '5.00', '0.00') - ,('87082922', '8708.29.22', 'Tableros de instrumentos, incluso con instrumentos de medida o control, para uso exclusivo en vehículos automóviles.', '6', 'Pza', '5.00', '0.00') - ,('87082923', '8708.29.23', 'Dispositivos retractores y sus partes o piezas sueltas, para cinturones de seguridad.', '6', 'Pza', '0.00', '0.00') - ,('87082924', '8708.29.24', 'Dispositivos interiores (consolas), reconocibles como diseñados exclusivamente para vehículos automóviles hasta de diez plazas, aún cuando se presenten con palancas al piso para cambios de velocidades.', '6', 'Pza', '0.00', '0.00') - ,('87082999', '8708.29.99', 'Los demás.', '6', 'Pza', '35.00', '0.00') - ,('87083001', '8708.30.01', 'Para trolebuses.', '6', 'Pza', '0.00', '0.00') - ,('87083002', '8708.30.02', 'Reconocibles como diseñadas exclusivamente para tractores de ruedas.', '1', 'Kg', '0.00', '0.00') - ,('87083004', '8708.30.04', 'Guarniciones de frenos montadas, excepto lo comprendido en las fracciones arancelarias 8708.30.01, 8708.30.02 y lo reconocido como exclusivamente para lo comprendido en la fracción arancelaria 8701.30.01.', '6', 'Pza', '0.00', '0.00') - ,('87083006', '8708.30.06', 'Cilindros de ruedas para mecanismos de frenos; juegos de repuesto para cilindros de rueda y para cilindros maestros, presentados en surtidos (kits) para su venta al por menor.', '6', 'Pza', '0.00', '0.00') - ,('87083008', '8708.30.08', 'Frenos de tambor accionados por leva o sus partes componentes.', '6', 'Pza', '5.00', '0.00') - ,('87083009', '8708.30.09', 'Cilindros maestros para mecanismos de frenos.', '6', 'Pza', '5.00', '0.00') - ,('87083010', '8708.30.10', 'Frenos de tambor accionados hidráulicamente o sus partes componentes, excepto lo comprendido en la fracción arancelaria 8708.30.06.', '6', 'Pza', '5.00', '0.00') - ,('87083011', '8708.30.11', 'Mangueras de frenos hidráulicos automotrices con conexiones.', '6', 'Pza', '5.00', '0.00') - ,('87083099', '8708.30.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('87084001', '8708.40.01', 'Reconocibles como diseñadas exclusivamente para lo comprendido en las fracciones arancelarias 8701.91.01, 8701.92.01, 8701.93.01, 8701.94.01, 8701.94.05 y 8701.95.01.', '6', 'Pza', '0.00', '0.00') - ,('87084002', '8708.40.02', 'Cajas de velocidades mecánicas, con peso inferior a 120 kg.', '6', 'Pza', '0.00', '0.00') - ,('87084003', '8708.40.03', 'Cajas de velocidades automáticas.', '6', 'Pza', '0.00', '0.00') - ,('87084004', '8708.40.04', 'Cajas de velocidades mecánicas con peso superior o igual a 120 kg.', '6', 'Pza', '0.00', '0.00') - ,('87084005', '8708.40.05', 'Reconocibles como diseñadas exclusivamente para lo comprendido en la fracción arancelaria 8701.30.01.', '6', 'Pza', '0.00', '0.00') - ,('87084007', '8708.40.07', 'Partes reconocibles como diseñadas exclusivamente para lo comprendido en las fracciones arancelarias 8708.40.02 y 8708.40.04, excepto engranes.', '6', 'Pza', '0.00', '0.00') - ,('87084008', '8708.40.08', 'Forja de flecha, para ser utilizada en cajas de velocidad de uso automotriz.', '6', 'Pza', '0.00', '0.00') - ,('87084099', '8708.40.99', 'Las demás.', '6', 'Pza', '5.00', '0.00') - ,('87085001', '8708.50.01', 'Para trolebuses.', '6', 'Pza', '0.00', '0.00') - ,('87085002', '8708.50.02', 'Reconocibles como diseñadas exclusivamente para lo comprendido en las fracciones arancelarias 8701.91.01, 8701.92.01, 8701.93.01, 8701.94.01, 8701.94.05 y 8701.95.01.', '6', 'Pza', '0.00', '0.00') - ,('87085003', '8708.50.03', 'Reconocibles como diseñados exclusivamente para lo comprendido en la fracción arancelaria 8701.30.01.', '6', 'Pza', '0.00', '0.00') - ,('87085004', '8708.50.04', 'Ejes con diferencial traseros sin acoplar a las masas, reconocibles como diseñados exclusivamente para lo comprendido en la partida 87.03.', '6', 'Pza', '0.00', '0.00') - ,('87085005', '8708.50.05', 'Acoplados a las masas, con o sin mecanismos de frenos y tambores, excepto lo comprendido en las fracciones arancelarias 8708.50.04 y 8708.50.91.', '6', 'Pza', '5.00', '0.00') - ,('87085006', '8708.50.06', 'Conjunto diferencial integral compuesto de caja de velocidades, diferencial con o sin flecha (semieje), y sus partes componentes, excepto lo comprendido en la fracción arancelaria 8708.50.91.', '6', 'Pza', '0.00', '0.00') - ,('87085007', '8708.50.07', 'Ejes con diferencial delanteros, excepto lo comprendido en las fracciones arancelarias 8708.50.06 y 8708.50.91.', '6', 'Pza', '5.00', '0.00') - ,('87085010', '8708.50.10', 'Ejes portadores delanteros, y sus partes, excepto lo comprendido en la fracción arancelaria 8708.50.13.', '6', 'Pza', '0.00', '0.00') - ,('87085013', '8708.50.13', 'Ejes portadores reconocibles como diseñados exclusivamente para lo comprendido en la partida 87.03.', '6', 'Pza', '5.00', '0.00') - ,('87085014', '8708.50.14', 'Vigas, muñones, brazos en forja para ejes delanteros de vehículos con capacidad de carga superior o igual a 2,724 kg (6,000 libras), pero inferior o igual a 8,172 kg (18,000 libras).', '6', 'Pza', '0.00', '0.00') - ,('87085015', '8708.50.15', 'Corona en forja para ejes traseros de vehículos con capacidad de carga superior o igual a 8,626 kg (19,000 libras), pero inferior o igual a 20,884 kg (46,000 libras).', '6', 'Pza', '0.00', '0.00') - ,('87085016', '8708.50.16', 'Fundiciones (esbozos) de mazas para eje delantero de vehículos con capacidad de carga superior a 2,724 kg (6,000 libras), pero inferior o igual a 8,172 kg (18,000 libras).', '6', 'Pza', '0.00', '0.00') - ,('87085018', '8708.50.18', 'Forjas para la fabricación de flechas de velocidad constante (homocinéticas).', '6', 'Pza', '0.00', '0.00') - ,('87085020', '8708.50.20', 'Reconocibles como diseñados exclusivamente para ejes cardán, excepto lo comprendido en las fracciones arancelarias 8708.50.22 y 8708.50.23.', '6', 'Pza', '5.00', '0.00') - ,('87085022', '8708.50.22', 'Ensamble de toma de fuerza para control de tracción delantera-trasera (PTU).', '6', 'Pza', '0.00', '0.00') - ,('87085023', '8708.50.23', 'Ensamble diferencial hidráulico para estabilización de revoluciones ("Geromatic").', '6', 'Pza', '0.00', '0.00') - ,('87085024', '8708.50.24', 'Forjas o fundiciones de yugos, con peso unitario superior a 6.4 kg pero inferior o igual a 14 kg, para utilizarse en el eje trasero motriz.', '6', 'Pza', '0.00', '0.00') - ,('87085025', '8708.50.25', 'Forjas de flechas de entrada y salida para diferencial de eje trasero; forjas de flechas semi-eje para vehículos con capacidad de carga superior a 8,626 kg (19,000 libras), pero inferior o igual a 20,884 kg (46,000 libras).', '6', 'Pza', '0.00', '0.00') - ,('87085026', '8708.50.26', 'Conjuntos compuestos de las siguientes fundiciones: de portadiferencial, de caja para baleros, de caja diferencial, de caja cambiador y de caja piñón, todos ellos para integrar portadiferenciales de eje trasero de vehículos de carga.', '6', 'Pza', '0.00', '0.00') - ,('87085027', '8708.50.27', 'Fundiciones de mazas para eje diferencial trasero para vehículos con capacidad de carga superior a 8,626 kg (19,000 libras), pero inferior o igual a 20,884 kg (46,000 libras).', '6', 'Pza', '0.00', '0.00') - ,('87085028', '8708.50.28', 'Placas troqueladas (preformas), de acero, para la fabricación de fundas para ejes traseros con diferencial, de vehículos con capacidad de carga superior a 8,626 kg (19,000 libras), pero inferior o igual a 20,884 kg (46,000 libras).', '6', 'Pza', '0.00', '0.00') - ,('87085029', '8708.50.29', 'Forjas de crucetas para eje trasero motriz para vehículos con capacidad de carga superior o igual a 8,846 kg (19,501 libras), pero inferior o igual a 20,884 kg (46,000 libras).', '6', 'Pza', '0.00', '0.00') - ,('87085030', '8708.50.30', 'Forjas de piñón para ejes traseros motriz, para vehículos con capacidad de carga superior o igual a 7,258 kg (16,000 libras), pero inferior o igual a 20,884 kg (46,000 libras).', '6', 'Pza', '0.00', '0.00') - ,('87085091', '8708.50.91', 'Los demás ejes con diferencial reconocibles como diseñados exclusivamente para lo comprendido en la partida 87.03, excepto los comprendidos en las fracciones arancelarias 8708.50.04 y 8708.50.05.', '6', 'Pza', '0.00', '0.00') - ,('87085092', '8708.50.92', 'Los demás ejes con diferencial, excepto los comprendidos en la fracción arancelaria 8708.50.91.', '6', 'Pza', '0.00', '0.00') - ,('87085099', '8708.50.99', 'Los demás.', '6', 'Pza', '5.00', '0.00') - ,('87087006', '8708.70.06', 'Tapones o polveras y arillos para ruedas, excepto las reconocidas como diseñadas exclusivamente para trolebuses y lo comprendido en la fracción arancelaria 8701.30.01.', '6', 'Pza', '5.00', '0.00') - ,('87087099', '8708.70.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('87088001', '8708.80.01', 'Para trolebuses.', '6', 'Pza', '0.00', '0.00') - ,('87088002', '8708.80.02', 'Reconocibles como diseñadas exclusivamente para lo comprendido en las fracciones arancelarias 8701.91.01, 8701.92.01, 8701.93.01, 8701.94.01, 8701.94.05 y 8701.95.01.', '6', 'Pza', '0.00', '0.00') - ,('87088003', '8708.80.03', 'Reconocibles como diseñados exclusivamente para lo comprendido en la fracción arancelaria 8701.30.01.', '6', 'Pza', '0.00', '0.00') - ,('87088004', '8708.80.04', 'Cartuchos para amortiguadores ("Mc Pherson Struts").', '6', 'Pza', '5.00', '0.00') - ,('87088005', '8708.80.05', 'Partes reconocibles como diseñados exclusivamente para sistemas de suspensión, excepto lo comprendido en las fracciones arancelarias 8708.80.07, 8708.80.10 y 8708.80.12.', '6', 'Pza', '0.00', '0.00') - ,('87088006', '8708.80.06', 'Horquillas de levante hidráulico.', '6', 'Pza', '0.00', '0.00') - ,('87088007', '8708.80.07', 'Horquillas, brazos, excéntricos o pernos, para el sistema de suspensión delantera.', '6', 'Pza', '5.00', '0.00') - ,('87088008', '8708.80.08', 'Partes componentes de barras de torsión.', '6', 'Pza', '5.00', '0.00') - ,('87088009', '8708.80.09', 'Barras de torsión.', '6', 'Pza', '5.00', '0.00') - ,('87088010', '8708.80.10', 'Rótulas, para el sistema de suspensión delantera.', '6', 'Pza', '5.00', '0.00') - ,('87088011', '8708.80.11', 'Partes reconocibles como diseñados exclusivamente para amortiguadores.', '6', 'Pza', '5.00', '0.00') - ,('87088012', '8708.80.12', 'Bujes para suspensión.', '6', 'Pza', '5.00', '0.00') - ,('87088099', '8708.80.99', 'Los demás.', '6', 'Pza', '5.00', '0.00') - ,('87089101', '8708.91.01', 'Reconocibles como diseñadas exclusivamente para lo comprendido en las fracciones arancelarias 8701.91.01, 8701.92.01, 8701.93.01, 8701.94.01, 8701.94.05 y 8701.95.01.', '6', 'Pza', '0.00', '0.00') - ,('87089102', '8708.91.02', 'Aspas para radiadores.', '6', 'Pza', '0.00', '0.00') - ,('87089103', '8708.91.03', 'Radiadores de aceites lubricantes para motores de émbolo (pistón) de encendido por compresión (motores Diesel o semi-Diesel).', '1', 'Kg', '0.00', '0.00') - ,('87089199', '8708.91.99', 'Los demás.', '6', 'Pza', '5.00', '0.00') - ,('87089203', '8708.92.03', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('87089305', '8708.93.05', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('87089412', '8708.94.12', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('87089502', '8708.95.02', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('87089904', '8708.99.04', 'Tanques de combustible, excepto los reconocidos como exclusivamente para trolebuses o lo comprendido en la fracción arancelaria 8701.30.01.', '6', 'Pza', '5.00', '0.00') - ,('87089907', '8708.99.07', 'Bastidores ("chasis"), excepto los reconocidos como exclusivamente para trolebuses o lo comprendido en la fracción arancelaria 8701.30.01.', '6', 'Pza', '5.00', '0.00') - ,('87089908', '8708.99.08', 'Perchas o columpios, excepto los reconocidos como exclusivamente para trolebuses o lo comprendido en la fracción arancelaria 8701.30.01.', '6', 'Pza', '5.00', '0.00') - ,('87089909', '8708.99.09', 'Uniones de ballestas (abrazaderas o soportes), excepto los reconocidos como exclusivamente para trolebuses o lo comprendido en la fracción arancelaria 8701.30.01.', '6', 'Pza', '5.00', '0.00') - ,('87089999', '8708.99.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('87091101', '8709.11.01', 'Eléctricas.', '6', 'Pza', '0.00', '0.00') - ,('87091999', '8709.19.99', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('87099001', '8709.90.01', 'Partes.', '1', 'Kg', '0.00', '0.00') - ,('87100001', '8710.00.01', 'Tanques y demás vehículos automóviles blindados de combate, incluso con su armamento; sus partes.', '6', 'Pza', '5.00', '0.00') - ,('87111003', '8711.10.03', 'Con motor de émbolo (pistón) de cilindrada inferior o igual a 50 cm³.', '6', 'Pza', '15.00', '0.00') - ,('87112005', '8711.20.05', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('87113004', '8711.30.04', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('87114001', '8711.40.01', 'Motociclos de tres ruedas (trimotos) que no presenten una dirección tipo automóvil o, al mismo tiempo, diferencial y reversa.', '6', 'Pza', '0.00', '0.00') - ,('87114002', '8711.40.02', 'Esbozo de motocicleta conteniendo conjunto formado con bastidor (cuadro) y motor de émbolo (pistón) de cilindrada superior a 500 cm³ pero inferior o igual a 550 cm³, ensamblado o sin ensamblar.', '6', 'Pza', '0.00', '0.00') - ,('87114099', '8711.40.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('87115003', '8711.50.03', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('87116001', '8711.60.01', 'Propulsados con motor eléctrico.', '6', 'Pza', '15.00', '0.00') - ,('87119001', '8711.90.01', 'Sidecares para motociclos y velocípedos de cualquier clase, presentados aisladamente.', '6', 'Pza', '0.00', '0.00') - ,('87119099', '8711.90.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('87120005', '8712.00.05', 'Los demás.', '6', 'Pza', '35.00', '0.00') - ,('87131001', '8713.10.01', 'Sin mecanismo de propulsión.', '6', 'Pza', '0.00', '0.00') - ,('87139099', '8713.90.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('87141001', '8714.10.01', 'Sillines (asientos).', '6', 'Pza', '0.00', '0.00') - ,('87141002', '8714.10.02', 'Reconocibles como diseñados exclusivamente para motocicletas con capacidad de cilindrada superior o igual a 550 cm³, excepto lo comprendido en la fracción arancelaria 8714.10.01.', '6', 'Pza', '0.00', '0.00') - ,('87141003', '8714.10.03', 'Bastidor (cuadro, armazón, chasis), excepto los comprendidos en la fracción arancelaria 8714.10.02.', '6', 'Pza', '5.00', '0.00') - ,('87141099', '8714.10.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('87142001', '8714.20.01', 'De sillones de ruedas y demás vehículos para personas con discapacidad.', '6', 'Pza', '0.00', '0.00') - ,('87149101', '8714.91.01', 'Horquillas (Tijera) y sus partes.', '6', 'Pza', '35.00', '0.00') - ,('87149201', '8714.92.01', 'Llantas y radios.', '6', 'Pza', '0.00', '0.00') - ,('87149301', '8714.93.01', 'Bujes sin frenos y piñones libres.', '6', 'Pza', '0.00', '0.00') - ,('87149401', '8714.94.01', 'Masas de freno de contra pedal.', '6', 'Pza', '5.00', '0.00') - ,('87149499', '8714.94.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('87149501', '8714.95.01', 'Sillines (asientos).', '6', 'Pza', '0.00', '0.00') - ,('87149601', '8714.96.01', 'Pedales y mecanismos de pedal, y sus partes.', '6', 'Pza', '0.00', '0.00') - ,('87149999', '8714.99.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('87150001', '8715.00.01', 'Coches, sillas y vehículos similares para transporte de niños.', '6', 'Pza', '10.00', '0.00') - ,('87150002', '8715.00.02', 'Partes reconocibles como diseñadas exclusivamente para lo comprendido en la fracción arancelaria 8715.00.01.', '6', 'Pza', '5.00', '0.00') - ,('87161001', '8716.10.01', 'Remolques y semirremolques para vivienda o acampar, del tipo caravana.', '6', 'Pza', '15.00', '0.00') - ,('87162004', '8716.20.04', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('87163103', '8716.31.03', 'Las demás.', '6', 'Pza', '15.00', '0.00') - ,('87163903', '8716.39.03', 'Reconocibles como diseñados exclusivamente para el transporte de lanchas, yates y veleros de más de 4.5 m de eslora.', '6', 'Pza', '0.00', '0.00') - ,('87163904', '8716.39.04', 'Remolques tipo plataformas modulares con ejes direccionales, incluso con sección de puente transportador, acoplamientos hidráulicos y/o cuello de ganso y/o motor de accionamiento hidráulico del equipo.', '6', 'Pza', '35.00', '0.00') - ,('87163908', '8716.39.08', 'Remolques o semirremolques de dos pisos, reconocibles como diseñados exclusivamente para transportar ganado bovino.', '6', 'Pza', '0.00', '0.00') - ,('87163999', '8716.39.99', 'Los demás.', '6', 'Pza', '35.00', '0.00') - ,('87164091', '8716.40.91', 'Los demás remolques y semirremolques.', '6', 'Pza', '35.00', '0.00') - ,('87168003', '8716.80.03', 'Carretillas, reconocibles como diseñadas para ser utilizadas en la construcción o en la albañilería.', '6', 'Pza', '15.00', '0.00') - ,('87168099', '8716.80.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('87169003', '8716.90.03', 'Conchas reconocibles como diseñadas exclusivamente para lo comprendido en la fracción arancelaria 8716.80.03.', '6', 'Pza', '5.00', '0.00') - ,('87169099', '8716.90.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('88010002', '8801.00.02', 'Globos y dirigibles; planeadores, alas planeadoras y demás aeronaves, no propulsados con motor.', '6', 'Pza', '15.00', '0.00') - ,('88021101', '8802.11.01', 'Helicópteros para fumigar, hasta de 3 plazas.', '6', 'Pza', '0.00', '0.00') - ,('88021199', '8802.11.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('88021201', '8802.12.01', 'Helicópteros para fumigar, hasta de 3 plazas.', '6', 'Pza', '5.00', '0.00') - ,('88021299', '8802.12.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('88022001', '8802.20.01', 'Aviones monomotores, de una plaza, reconocibles como diseñados exclusivamente para fumigar, rociar o esparcir líquidos o sólidos, con tolva de carga.', '6', 'Pza', '5.00', '0.00') - ,('88022099', '8802.20.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('88023001', '8802.30.01', 'Aviones monomotores, de una plaza, reconocibles como diseñados exclusivamente para fumigar, rociar o esparcir líquidos o sólidos, con tolva de carga.', '6', 'Pza', '5.00', '0.00') - ,('88023002', '8802.30.02', 'Aviones con motor a reacción, con peso en vacío superior o igual a 10,000 Kg.', '6', 'Pza', '0.00', '0.00') - ,('88023099', '8802.30.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('88024001', '8802.40.01', 'Aviones y demás aeronaves, de peso en vacío superior a 15,000 kg.', '6', 'Pza', '0.00', '0.00') - ,('88026001', '8802.60.01', 'Vehículos espaciales (incluidos los satélites) y sus vehículos de lanzamiento y vehículos suborbitales.', '6', 'Pza', '5.00', '0.00') - ,('88040001', '8804.00.01', 'Paracaídas, incluidos los dirigibles, planeadores (parapentes) o de aspas giratorias; sus partes y accesorios.', '1', 'Kg', '35.00', '0.00') - ,('88051001', '8805.10.01', 'Aparatos y dispositivos para lanzamiento de aeronaves y sus partes; aparatos y dispositivos para aterrizaje en portaaviones y aparatos y dispositivos similares, y sus partes.', '1', 'Kg', '5.00', '0.00') - ,('88052101', '8805.21.01', 'Simuladores de combate aéreo y sus partes.', '6', 'Pza', '0.00', '0.00') - ,('88052999', '8805.29.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('88061001', '8806.10.01', 'De peso en vacío superior a 15,000 kg.', '6', 'Pza', '0.00', '0.00') - ,('88061002', '8806.10.02', 'Aviones con motor a reacción, con peso en vacío superior o igual a 10,000 Kg.', '6', 'Pza', '0.00', '0.00') - ,('88061099', '8806.10.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('88062101', '8806.21.01', 'Drones con videocámara incluida y cuyo objetivo principal sea grabar, capturar o transmitir video y/o imágenes.', '6', 'Pza', '0.00', '0.00') - ,('88062199', '8806.21.99', 'Las demás.', '6', 'Pza', '15.00', '0.00') - ,('88062201', '8806.22.01', 'Drones con videocámara incluida y cuyo objetivo principal sea grabar, capturar o transmitir video y/o imágenes.', '6', 'Pza', '0.00', '0.00') - ,('88062299', '8806.22.99', 'Las demás.', '6', 'Pza', '15.00', '0.00') - ,('88062301', '8806.23.01', 'Drones con videocámara incluida y cuyo objetivo principal sea grabar, capturar o transmitir video y/o imágenes.', '6', 'Pza', '0.00', '0.00') - ,('88062399', '8806.23.99', 'Las demás.', '6', 'Pza', '15.00', '0.00') - ,('88062401', '8806.24.01', 'Drones con videocámara incluida y cuyo objetivo principal sea grabar, capturar o transmitir video y/o imágenes.', '6', 'Pza', '0.00', '0.00') - ,('88062499', '8806.24.99', 'Las demás.', '6', 'Pza', '15.00', '0.00') - ,('88062901', '8806.29.01', 'Drones con videocámara incluida y cuyo objetivo principal sea grabar, capturar o transmitir video y/o imágenes.', '6', 'Pza', '0.00', '0.00') - ,('88062902', '8806.29.02', 'Aviones y aeronaves similares, de peso en vacío superior a 15,000 kg.', '6', 'Pza', '0.00', '0.00') - ,('88062999', '8806.29.99', 'Las demás.', '6', 'Pza', '15.00', '0.00') - ,('88069101', '8806.91.01', 'Drones con videocámara incluida y cuyo objetivo principal sea grabar, capturar o transmitir video y/o imágenes.', '6', 'Pza', '0.00', '0.00') - ,('88069199', '8806.91.99', 'Las demás.', '6', 'Pza', '15.00', '0.00') - ,('88069201', '8806.92.01', 'Drones con videocámara incluida y cuyo objetivo principal sea grabar, capturar o transmitir video y/o imágenes.', '6', 'Pza', '0.00', '0.00') - ,('88069299', '8806.92.99', 'Las demás.', '6', 'Pza', '15.00', '0.00') - ,('88069301', '8806.93.01', 'Drones con videocámara incluida y cuyo objetivo principal sea grabar, capturar o transmitir video y/o imágenes.', '6', 'Pza', '0.00', '0.00') - ,('88069399', '8806.93.99', 'Las demás.', '6', 'Pza', '15.00', '0.00') - ,('88069401', '8806.94.01', 'Drones con videocámara incluida y cuyo objetivo principal sea grabar, capturar o transmitir video y/o imágenes.', '6', 'Pza', '0.00', '0.00') - ,('88069499', '8806.94.99', 'Las demás.', '6', 'Pza', '15.00', '0.00') - ,('88069901', '8806.99.01', 'Drones con videocámara incluida y cuyo objetivo principal sea grabar, capturar o transmitir video y/o imágenes.', '6', 'Pza', '0.00', '0.00') - ,('88069902', '8806.99.02', 'Aviones y aeronaves similares, de peso en vacío superior a 15,000 kg.', '6', 'Pza', '0.00', '0.00') - ,('88069999', '8806.99.99', 'Las demás.', '6', 'Pza', '15.00', '0.00') - ,('88071001', '8807.10.01', 'Hélices y rotores, y sus partes.', '6', 'Pza', '0.00', '0.00') - ,('88072001', '8807.20.01', 'Trenes de aterrizaje y sus partes.', '6', 'Pza', '0.00', '0.00') - ,('88073091', '8807.30.91', 'Las demás partes de aviones, helicópteros o aeronaves no tripuladas.', '6', 'Pza', '0.00', '0.00') - ,('88079099', '8807.90.99', 'Las demás.', '6', 'Pza', '5.00', '0.00') - ,('89011001', '8901.10.01', 'Con longitud inferior o igual a 35 m de eslora.', '6', 'Pza', '5.00', '0.00') - ,('89011099', '8901.10.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('89012001', '8901.20.01', 'Con longitud inferior o igual a 35 m de eslora.', '6', 'Pza', '15.00', '0.00') - ,('89012099', '8901.20.99', 'Los demás.', '6', 'Pza', '5.00', '0.00') - ,('89013001', '8901.30.01', 'Con longitud inferior o igual a 35 m de eslora.', '6', 'Pza', '15.00', '0.00') - ,('89013099', '8901.30.99', 'Los demás.', '6', 'Pza', '5.00', '0.00') - ,('89019001', '8901.90.01', 'Con motor fueraborda.', '6', 'Pza', '15.00', '0.00') - ,('89019002', '8901.90.02', 'Con longitud inferior o igual a 35 m de eslora.', '6', 'Pza', '15.00', '0.00') - ,('89019003', '8901.90.03', 'Con casco de aluminio, para transportar hasta 80 personas y con capacidad de carga, en cubierta hasta 36 t, sin cabinas de refrigeración ni bodegas de carga.', '6', 'Pza', '10.00', '0.00') - ,('89019099', '8901.90.99', 'Los demás.', '6', 'Pza', '5.00', '0.00') - ,('89020003', '8902.00.03', 'Los demás.', '6', 'Pza', '5.00', '0.00') - ,('89031101', '8903.11.01', 'Equipadas o diseñadas para equiparlas con motor, de peso en vacío, sin motor, inferior o igual a 100 kg.', '6', 'Pza', '15.00', '0.00') - ,('89031201', '8903.12.01', 'No diseñadas para ser utilizadas con motor y de peso en vacío inferior o igual a 100 kg.', '6', 'Pza', '15.00', '0.00') - ,('89031999', '8903.19.99', 'Las demás.', '6', 'Pza', '15.00', '0.00') - ,('89032101', '8903.21.01', 'De longitud inferior o igual a 7.5 m.', '6', 'Pza', '15.00', '0.00') - ,('89032201', '8903.22.01', 'De longitud superior a 7.5 m pero inferior o igual a 24 m.', '6', 'Pza', '15.00', '0.00') - ,('89032301', '8903.23.01', 'De longitud superior a 24 m.', '6', 'Pza', '15.00', '0.00') - ,('89033101', '8903.31.01', 'De longitud inferior o igual a 7.5 m.', '6', 'Pza', '15.00', '0.00') - ,('89033201', '8903.32.01', 'De longitud superior a 7.5 m pero inferior o igual a 24 m.', '6', 'Pza', '15.00', '0.00') - ,('89033301', '8903.33.01', 'De longitud superior a 24 m.', '6', 'Pza', '15.00', '0.00') - ,('89039301', '8903.93.01', 'De longitud inferior o igual a 7.5 m.', '6', 'Pza', '15.00', '0.00') - ,('89039999', '8903.99.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('89040001', '8904.00.01', 'Remolcadores y barcos empujadores.', '6', 'Pza', '5.00', '0.00') - ,('89051001', '8905.10.01', 'Dragas.', '6', 'Pza', '0.00', '0.00') - ,('89052001', '8905.20.01', 'Plataformas de perforación o explotación, flotantes o sumergibles.', '6', 'Pza', '0.00', '0.00') - ,('89059001', '8905.90.01', 'Unidades (artefactos) flotantes para el procesamiento o tratamiento de petróleo crudo o de gas.', '6', 'Pza', '0.00', '0.00') - ,('89059099', '8905.90.99', 'Los demás.', '6', 'Pza', '5.00', '0.00') - ,('89061001', '8906.10.01', 'Navíos de guerra.', '6', 'Pza', '10.00', '0.00') - ,('89069099', '8906.90.99', 'Los demás.', '6', 'Pza', '10.00', '0.00') - ,('89071001', '8907.10.01', 'Balsas inflables.', '6', 'Pza', '10.00', '0.00') - ,('89079099', '8907.90.99', 'Los demás.', '6', 'Pza', '10.00', '0.00') - ,('89080001', '8908.00.01', 'Barcos y demás artefactos flotantes para desguace.', '6', 'Pza', '0.00', '0.00') - ,('90011002', '9001.10.02', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('90012001', '9001.20.01', 'Hojas y placas de materia polarizante.', '1', 'Kg', '0.00', '0.00') - ,('90013002', '9001.30.02', 'Elementos de óptica (pastillas o botones) destinados a la fabricación de lentes de contacto, de materias plásticas, en bruto, con diámetro inferior o igual a 30 mm.', '6', 'Pza', '0.00', '0.00') - ,('90013099', '9001.30.99', 'Los demás.', '6', 'Pza', '10.00', '0.00') - ,('90014003', '9001.40.03', 'Lentes de vidrio para gafas (anteojos).', '6', 'Pza', '5.00', '0.00') - ,('90015002', '9001.50.02', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('90019099', '9001.90.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('90021101', '9002.11.01', 'Para cámaras, proyectores o aparatos fotográficos o cinematográficos de ampliación o reducción.', '6', 'Pza', '0.00', '0.00') - ,('90021999', '9002.19.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('90022001', '9002.20.01', 'Filtros.', '6', 'Pza', '0.00', '0.00') - ,('90029099', '9002.90.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('90031101', '9003.11.01', 'De plástico.', '6', 'Pza', '0.00', '0.00') - ,('90031901', '9003.19.01', 'De otras materias.', '6', 'Pza', '0.00', '0.00') - ,('90039002', '9003.90.02', 'Partes.', '6', 'Pza', '0.00', '0.00') - ,('90041001', '9004.10.01', 'Gafas (anteojos) de sol.', '6', 'Pza', '10.00', '0.00') - ,('90049099', '9004.90.99', 'Los demás.', '6', 'Pza', '10.00', '0.00') - ,('90051001', '9005.10.01', 'Binoculares (incluidos los prismáticos).', '6', 'Pza', '10.00', '0.00') - ,('90058091', '9005.80.91', 'Los demás instrumentos.', '6', 'Pza', '10.00', '0.00') - ,('90059001', '9005.90.01', 'Partes reconocibles como diseñadas exclusivamente para lo comprendido en las subpartidas 9005.10 a 9005.80, que incorporen lentes, prismas y espejos de las partidas 90.01 a 90.02.', '1', 'Kg', '0.00', '0.00') - ,('90059002', '9005.90.02', 'Partes y accesorios, reconocibles exclusivamente para lo comprendido en la fracción arancelaria 9005.10.01, excepto lo comprendido en la fracción arancelaria 9005.90.01.', '1', 'Kg', '5.00', '0.00') - ,('90059099', '9005.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('90063001', '9006.30.01', 'Cámaras especiales para fotografía submarina o aérea, examen médico de órganos internos o para laboratorios de medicina legal o identificación judicial.', '6', 'Pza', '0.00', '0.00') - ,('90064001', '9006.40.01', 'Cámaras fotográficas de autorrevelado.', '6', 'Pza', '10.00', '0.00') - ,('90065301', '9006.53.01', 'Cámaras fotográficas de los tipos utilizados para registrar documentos en microfilmes, microfichas u otros microformatos.', '6', 'Pza', '0.00', '0.00') - ,('90065399', '9006.53.99', 'Las demás.', '6', 'Pza', '10.00', '0.00') - ,('90065901', '9006.59.01', 'Para películas en rollo de anchura inferior a 35 mm, excepto las utilizados para registrar documentos en microfilmes, microfichas u otros microformatos.', '6', 'Pza', '10.00', '0.00') - ,('90065902', '9006.59.02', 'Con visor de reflexión a través del objetivo, para películas en rollo de anchura inferior o igual a 35 mm.', '6', 'Pza', '10.00', '0.00') - ,('90065999', '9006.59.99', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('90066101', '9006.61.01', 'Aparatos de tubo de descarga para producir destellos (flashes electrónicos).', '6', 'Pza', '5.00', '0.00') - ,('90066999', '9006.69.99', 'Los demás.', '6', 'Pza', '10.00', '0.00') - ,('90069103', '9006.91.03', 'De cámaras fotográficas.', '1', 'Kg', '0.00', '0.00') - ,('90069999', '9006.99.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('90071001', '9007.10.01', 'Para películas cinematográficas (filme) de anchura inferior a 16 mm o para la doble 8 mm.', '6', 'Pza', '10.00', '0.00') - ,('90071002', '9007.10.02', 'Giroestabilizadas.', '6', 'Pza', '5.00', '0.00') - ,('90071099', '9007.10.99', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('90072001', '9007.20.01', 'Proyectores.', '6', 'Pza', '0.00', '0.00') - ,('90079101', '9007.91.01', 'De cámaras.', '1', 'Kg', '0.00', '0.00') - ,('90079201', '9007.92.01', 'De proyectores.', '1', 'Kg', '0.00', '0.00') - ,('90085001', '9008.50.01', 'Proyectores, ampliadoras o reductoras.', '6', 'Pza', '0.00', '0.00') - ,('90089002', '9008.90.02', 'Partes y accesorios.', '1', 'Kg', '0.00', '0.00') - ,('90101001', '9010.10.01', 'Aparatos y material para revelado automático de película fotográfica, película cinematográfica (filme) o papel fotográfico en rollo o para impresión automática de películas reveladas en rollos de papel fotográfico.', '6', 'Pza', '0.00', '0.00') - ,('90105091', '9010.50.91', 'Los demás aparatos y material para laboratorios fotográficos o cinematográficos; negatoscopios.', '6', 'Pza', '0.00', '0.00') - ,('90106001', '9010.60.01', 'Pantallas de proyección.', '6', 'Pza', '15.00', '0.00') - ,('90109001', '9010.90.01', 'Partes y accesorios.', '1', 'Kg', '0.00', '0.00') - ,('90111001', '9011.10.01', 'Para cirugía.', '6', 'Pza', '0.00', '0.00') - ,('90111099', '9011.10.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('90112091', '9011.20.91', 'Los demás microscopios para fotomicrografía, cinefotomicrografía o microproyección.', '6', 'Pza', '15.00', '0.00') - ,('90118091', '9011.80.91', 'Los demás microscopios.', '6', 'Pza', '15.00', '0.00') - ,('90119001', '9011.90.01', 'Partes y accesorios.', '1', 'Kg', '0.00', '0.00') - ,('90121001', '9012.10.01', 'Microscopios, excepto los ópticos; difractógrafos.', '6', 'Pza', '0.00', '0.00') - ,('90129001', '9012.90.01', 'Partes y accesorios.', '1', 'Kg', '0.00', '0.00') - ,('90131001', '9013.10.01', 'Miras telescópicas para armas; periscopios; visores para máquinas, aparatos o instrumentos de este Capítulo o de la Sección XVI.', '6', 'Pza', '0.00', '0.00') - ,('90132001', '9013.20.01', 'Láseres, excepto los diodos láser.', '6', 'Pza', '0.00', '0.00') - ,('90138091', '9013.80.91', 'Los demás dispositivos, aparatos e instrumentos.', '6', 'Pza', '0.00', '0.00') - ,('90139001', '9013.90.01', 'Partes y accesorios.', '1', 'Kg', '0.00', '0.00') - ,('90141001', '9014.10.01', 'Brújulas, excepto lo comprendido en la fracción arancelaria 9014.10.03.', '6', 'Pza', '0.00', '0.00') - ,('90141002', '9014.10.02', 'Reconocibles para naves aéreas.', '6', 'Pza', '0.00', '0.00') - ,('90141003', '9014.10.03', 'Brújulas de funcionamiento electrónico, reconocibles como diseñadas exclusivamente para uso automotriz.', '6', 'Pza', '0.00', '0.00') - ,('90141099', '9014.10.99', 'Los demás.', '6', 'Pza', '10.00', '0.00') - ,('90142001', '9014.20.01', 'Instrumentos y aparatos para navegación aérea o espacial (excepto las brújulas).', '6', 'Pza', '0.00', '0.00') - ,('90148091', '9014.80.91', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('90149002', '9014.90.02', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('90151001', '9015.10.01', 'Telémetros.', '6', 'Pza', '0.00', '0.00') - ,('90152001', '9015.20.01', 'Teodolitos.', '6', 'Pza', '15.00', '0.00') - ,('90152099', '9015.20.99', 'Los demás.', '6', 'Pza', '10.00', '0.00') - ,('90153001', '9015.30.01', 'Niveles.', '6', 'Pza', '0.00', '0.00') - ,('90154001', '9015.40.01', 'Instrumentos y aparatos de fotogrametría.', '6', 'Pza', '0.00', '0.00') - ,('90158001', '9015.80.01', 'Aparatos para medir distancias geodésicas.', '6', 'Pza', '0.00', '0.00') - ,('90158003', '9015.80.03', 'Celdas de presión, piezómetros o extensómetros.', '6', 'Pza', '0.00', '0.00') - ,('90158004', '9015.80.04', 'Aparatos de control y medición de niveles del tipo eléctrico o electrónico.', '6', 'Pza', '0.00', '0.00') - ,('90158005', '9015.80.05', 'Inclinómetros y extensómetros, para medir deformaciones del suelo, roca o concreto, eléctricos o electrónicos.', '6', 'Pza', '0.00', '0.00') - ,('90158099', '9015.80.99', 'Los demás.', '6', 'Pza', '5.00', '0.00') - ,('90159001', '9015.90.01', 'Partes y accesorios.', '1', 'Kg', '0.00', '0.00') - ,('90160002', '9016.00.02', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('90171001', '9017.10.01', 'Mesas o mármoles.', '6', 'Pza', '0.00', '0.00') - ,('90171099', '9017.10.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('90172001', '9017.20.01', 'Transportadores de ángulos.', '6', 'Pza', '0.00', '0.00') - ,('90172099', '9017.20.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('90173002', '9017.30.02', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('90178003', '9017.80.03', 'Comprobadores de cuadrante.', '6', 'Pza', '0.00', '0.00') - ,('90178004', '9017.80.04', 'Metros de madera plegable.', '6', 'Pza', '0.00', '0.00') - ,('90178099', '9017.80.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('90179002', '9017.90.02', 'Partes y accesorios.', '1', 'Kg', '0.00', '0.00') - ,('90181101', '9018.11.01', 'Electrocardiógrafos.', '1', 'Kg', '0.00', '0.00') - ,('90181102', '9018.11.02', 'Circuitos modulares para electrocardiógrafos.', '1', 'Kg', '5.00', '0.00') - ,('90181201', '9018.12.01', 'Aparatos de diagnóstico por exploración ultrasónica.', '6', 'Pza', '5.00', '0.00') - ,('90181301', '9018.13.01', 'Aparatos de diagnóstico de visualización por resonancia magnética.', '6', 'Pza', '0.00', '0.00') - ,('90181401', '9018.14.01', 'Aparatos de centellografía.', '6', 'Pza', '0.00', '0.00') - ,('90181902', '9018.19.02', 'Electroencefalógrafos.', '6', 'Pza', '0.00', '0.00') - ,('90181905', '9018.19.05', 'Sistemas de monitoreo de pacientes.', '6', 'Pza', '0.00', '0.00') - ,('90181906', '9018.19.06', 'Audiómetros.', '6', 'Pza', '0.00', '0.00') - ,('90181999', '9018.19.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('90182001', '9018.20.01', 'Aparatos de rayos ultravioletas o infrarrojos.', '6', 'Pza', '0.00', '0.00') - ,('90183101', '9018.31.01', 'De vidrio o de plástico, con capacidad hasta 30 ml.', '1', 'Kg', '10.00', '0.00') - ,('90183199', '9018.31.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('90183202', '9018.32.02', 'Para sutura o ligadura, excepto lo comprendido en la fracción arancelaria 9018.32.04.', '1', 'Kg', '0.00', '0.00') - ,('90183203', '9018.32.03', 'Desechables, sin esterilizar, empacar o envasar ni acondicionar para su venta al por menor, reconocibles como diseñadas exclusivamente para jeringas hipodérmicas desechables.', '1', 'Kg', '0.00', '0.00') - ,('90183204', '9018.32.04', 'Material de sutura quirúrgica, constituido por aguja provista de hilo (catgut u otras ligaduras, con diámetro superior o igual a 0.10 mm sin exceder de 0.80 mm) esterilizado, presentado en sobres herméticamente cerrados, excepto a base de polímeros del ácido glicólico y/o ácido láctico.', '1', 'Kg', '5.00', '0.00') - ,('90183299', '9018.32.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('90183902', '9018.39.02', 'Catéteres inyectores para inseminación artificial.', '1', 'Kg', '0.00', '0.00') - ,('90183903', '9018.39.03', 'Cánulas.', '1', 'Kg', '0.00', '0.00') - ,('90183904', '9018.39.04', 'Sondas vaginales, rectales, uretrales, bucofaríngeas y epidulares.', '1', 'Kg', '15.00', '0.00') - ,('90183906', '9018.39.06', 'Lancetas.', '1', 'Kg', '10.00', '0.00') - ,('90183999', '9018.39.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('90184101', '9018.41.01', 'Tornos para dentista (transmisión flexible y colgante) eléctricos, con velocidad de hasta 30,000 RPM.', '1', 'Kg', '10.00', '0.00') - ,('90184199', '9018.41.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('90184902', '9018.49.02', 'Espéculos bucales.', '1', 'Kg', '5.00', '0.00') - ,('90184904', '9018.49.04', 'Fresas para odontología.', '1', 'Kg', '5.00', '0.00') - ,('90184905', '9018.49.05', 'Pinzas para la extracción de piezas dentales "Daviers".', '1', 'Kg', '5.00', '0.00') - ,('90184906', '9018.49.06', 'Pinzas gubia, sacabocado y hemostáticas, para cirugía, pinzas para biopsia.', '1', 'Kg', '5.00', '0.00') - ,('90184999', '9018.49.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('90185091', '9018.50.91', 'Los demás instrumentos y aparatos de oftalmología.', '1', 'Kg', '5.00', '0.00') - ,('90189001', '9018.90.01', 'Espejos.', '1', 'Kg', '5.00', '0.00') - ,('90189002', '9018.90.02', 'Tijeras.', '1', 'Kg', '10.00', '0.00') - ,('90189003', '9018.90.03', 'Aparatos para medir la presión arterial.', '1', 'Kg', '10.00', '0.00') - ,('90189004', '9018.90.04', 'Aparatos para anestesia.', '1', 'Kg', '10.00', '0.00') - ,('90189005', '9018.90.05', 'Equipos para derivación ventricular con reservorio para líquido cefalorraquídeo.', '1', 'Kg', '10.00', '0.00') - ,('90189006', '9018.90.06', 'Estuches de cirugía o disección.', '1', 'Kg', '10.00', '0.00') - ,('90189007', '9018.90.07', 'Bisturís.', '1', 'Kg', '10.00', '0.00') - ,('90189008', '9018.90.08', 'Valvas, portaagujas y escoplos para cirugía.', '1', 'Kg', '5.00', '0.00') - ,('90189009', '9018.90.09', 'Separadores para cirugía.', '1', 'Kg', '10.00', '0.00') - ,('90189010', '9018.90.10', 'Pinzas tipo disección para cirugía.', '1', 'Kg', '10.00', '0.00') - ,('90189011', '9018.90.11', 'Pinzas para descornar.', '1', 'Kg', '5.00', '0.00') - ,('90189012', '9018.90.12', 'Pinzas, excepto lo comprendido en las fracciones arancelarias 9018.90.10 y 9018.90.11.', '1', 'Kg', '10.00', '0.00') - ,('90189013', '9018.90.13', 'Castradores de seguridad.', '1', 'Kg', '5.00', '0.00') - ,('90189014', '9018.90.14', 'Bombas de aspiración pleural.', '1', 'Kg', '5.00', '0.00') - ,('90189015', '9018.90.15', 'Aparatos de succión, excepto lo comprendido en la fracción arancelaria 9018.90.14.', '1', 'Kg', '15.00', '0.00') - ,('90189016', '9018.90.16', 'Espátulas del tipo de abatelenguas.', '1', 'Kg', '10.00', '0.00') - ,('90189017', '9018.90.17', 'Dispositivos intrauterinos anticonceptivos.', '1', 'Kg', '10.00', '0.00') - ,('90189018', '9018.90.18', 'Desfibriladores.', '6', 'Pza', '5.00', '0.00') - ,('90189019', '9018.90.19', 'Estetoscopios.', '1', 'Kg', '15.00', '0.00') - ,('90189020', '9018.90.20', 'Aparatos de actinoterapia.', '1', 'Kg', '5.00', '0.00') - ,('90189025', '9018.90.25', 'Aparatos de diatermia de onda corta.', '6', 'Pza', '0.00', '0.00') - ,('90189027', '9018.90.27', 'Incubadoras para niños, partes y accesorios.', '6', 'Pza', '5.00', '0.00') - ,('90189028', '9018.90.28', 'Aparatos de electrocirugía.', '6', 'Pza', '0.00', '0.00') - ,('90189031', '9018.90.31', 'Equipos para hemodiálisis (riñón artificial).', '6', 'Pza', '0.00', '0.00') - ,('90189099', '9018.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('90191002', '9019.10.02', 'Aparatos de masaje, eléctricos.', '6', 'Pza', '10.00', '0.00') - ,('90191099', '9019.10.99', 'Los demás.', '6', 'Pza', '5.00', '0.00') - ,('90192001', '9019.20.01', 'Aparatos de ozonoterapia, oxigenoterapia o aerosolterapia, aparatos respiratorios de reanimación y demás aparatos de terapia respiratoria.', '1', 'Kg', '0.00', '0.00') - ,('90200002', '9020.00.02', 'Caretas protectoras.', '1', 'Kg', '0.00', '0.00') - ,('90200003', '9020.00.03', 'Equipo de buceo.', '1', 'Kg', '10.00', '0.00') - ,('90200099', '9020.00.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('90211006', '9021.10.06', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('90212102', '9021.21.02', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('90212999', '9021.29.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('90213101', '9021.31.01', 'Prótesis articulares.', '1', 'Kg', '5.00', '0.00') - ,('90213901', '9021.39.01', 'Ojos artificiales.', '1', 'Kg', '5.00', '0.00') - ,('90213902', '9021.39.02', 'Prótesis de arterias y venas.', '1', 'Kg', '5.00', '0.00') - ,('90213904', '9021.39.04', 'Manos o pies artificiales.', '1', 'Kg', '5.00', '0.00') - ,('90213999', '9021.39.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('90214001', '9021.40.01', 'Audífonos, excepto sus partes y accesorios.', '6', 'Pza', '5.00', '0.00') - ,('90215001', '9021.50.01', 'Estimuladores cardíacos, excepto sus partes y accesorios.', '6', 'Pza', '0.00', '0.00') - ,('90219099', '9021.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('90221201', '9022.12.01', 'Aparatos de tomografía regidos por una máquina automática para tratamiento o procesamiento de datos.', '6', 'Pza', '0.00', '0.00') - ,('90221391', '9022.13.91', 'Los demás, para uso odontológico.', '6', 'Pza', '0.00', '0.00') - ,('90221491', '9022.14.91', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('90221901', '9022.19.01', 'Para otros usos.', '1', 'Kg', '0.00', '0.00') - ,('90222103', '9022.21.03', 'A base de radiaciones ionizantes.', '1', 'Kg', '5.00', '0.00') - ,('90222199', '9022.21.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('90222901', '9022.29.01', 'Para otros usos.', '6', 'Pza', '0.00', '0.00') - ,('90223001', '9022.30.01', 'Tubos de rayos X.', '6', 'Pza', '0.00', '0.00') - ,('90229003', '9022.90.03', 'Partes y accesorios para aparatos de rayos X.', '1', 'Kg', '0.00', '0.00') - ,('90229099', '9022.90.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('90230001', '9023.00.01', 'Instrumentos, aparatos y modelos diseñados para demostraciones (por ejemplo: en la enseñanza o exposiciones), no susceptibles de otros usos.', '6', 'Pza', '0.00', '0.00') - ,('90241001', '9024.10.01', 'Máquinas y aparatos para ensayo de metal.', '6', 'Pza', '0.00', '0.00') - ,('90248091', '9024.80.91', 'Las demás máquinas y aparatos.', '6', 'Pza', '0.00', '0.00') - ,('90249001', '9024.90.01', 'Partes y accesorios.', '1', 'Kg', '0.00', '0.00') - ,('90251101', '9025.11.01', 'Esbozos para la elaboración de termómetros de vidrio, sin graduación, con o sin vacío, con o sin mercurio.', '6', 'Pza', '0.00', '0.00') - ,('90251199', '9025.11.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('90251901', '9025.19.01', 'De vehículos automóviles.', '6', 'Pza', '0.00', '0.00') - ,('90251902', '9025.19.02', 'Reconocibles para naves aéreas.', '6', 'Pza', '0.00', '0.00') - ,('90251903', '9025.19.03', 'Electrónicos, excepto lo comprendido en las fracciones arancelarias 9025.19.01 y 9025.19.02.', '6', 'Pza', '0.00', '0.00') - ,('90251904', '9025.19.04', 'Pirómetros.', '6', 'Pza', '0.00', '0.00') - ,('90251999', '9025.19.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('90258001', '9025.80.01', 'Aerómetros y densímetros.', '6', 'Pza', '15.00', '0.00') - ,('90258099', '9025.80.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('90259001', '9025.90.01', 'Partes y accesorios.', '1', 'Kg', '0.00', '0.00') - ,('90261007', '9026.10.07', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('90262003', '9026.20.03', 'Reguladores medidores de la presión de aire a inyectar en neumáticos de vehículos, incluso con distribuidores de agua.', '6', 'Pza', '15.00', '0.00') - ,('90262004', '9026.20.04', 'Reguladores de presión, acoplados a válvulas o manómetros.', '6', 'Pza', '5.00', '0.00') - ,('90262099', '9026.20.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('90268091', '9026.80.91', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('90269001', '9026.90.01', 'Partes y accesorios.', '1', 'Kg', '0.00', '0.00') - ,('90271001', '9027.10.01', 'Analizadores de gases o humos.', '6', 'Pza', '0.00', '0.00') - ,('90272001', '9027.20.01', 'Cromatógrafos e instrumentos de electroforesis.', '6', 'Pza', '0.00', '0.00') - ,('90273001', '9027.30.01', 'Espectrómetros, espectrofotómetros y espectrógrafos que utilicen radiaciones ópticas (UV, visibles, IR).', '6', 'Pza', '0.00', '0.00') - ,('90275091', '9027.50.91', 'Los demás instrumentos y aparatos que utilicen radiaciones ópticas (UV, visibles, IR).', '6', 'Pza', '0.00', '0.00') - ,('90278101', '9027.81.01', 'Espectrómetros de masa.', '6', 'Pza', '0.00', '0.00') - ,('90278999', '9027.89.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('90279001', '9027.90.01', 'Columnas de análisis para cromatógrafos de gases.', '6', 'Pza', '0.00', '0.00') - ,('90279002', '9027.90.02', 'Micrótomos.', '6', 'Pza', '0.00', '0.00') - ,('90279003', '9027.90.03', 'Circuitos modulares reconocibles como diseñados exclusivamente para lo comprendido en las subpartidas 9027.81 y 9027.89.', '6', 'Pza', '0.00', '0.00') - ,('90279099', '9027.90.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('90281001', '9028.10.01', 'Contadores de gas.', '6', 'Pza', '0.00', '0.00') - ,('90282002', '9028.20.02', 'Contadores volumétricos automáticos, para medir cerveza.', '6', 'Pza', '15.00', '0.00') - ,('90282003', '9028.20.03', 'Contadores de agua, excepto los equipados con dispositivos que permitan su lectura remota (lectura electrónica).', '6', 'Pza', '15.00', '0.00') - ,('90282099', '9028.20.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('90283001', '9028.30.01', 'Vatihorímetros.', '6', 'Pza', '5.00', '0.00') - ,('90283099', '9028.30.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('90289003', '9028.90.03', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('90291003', '9029.10.03', 'Taxímetros electrónicos o electromecánicos.', '6', 'Pza', '15.00', '0.00') - ,('90291099', '9029.10.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('90292006', '9029.20.06', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('90299001', '9029.90.01', 'Partes y accesorios.', '1', 'Kg', '0.00', '0.00') - ,('90301001', '9030.10.01', 'Instrumentos y aparatos para medida o detección de radiaciones ionizantes.', '6', 'Pza', '0.00', '0.00') - ,('90302002', '9030.20.02', 'Osciloscopios y oscilógrafos.', '6', 'Pza', '0.00', '0.00') - ,('90303101', '9030.31.01', 'Multímetros, sin dispositivo registrador.', '6', 'Pza', '0.00', '0.00') - ,('90303201', '9030.32.01', 'Multímetros, con dispositivo registrador.', '6', 'Pza', '0.00', '0.00') - ,('90303391', '9030.33.91', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('90303991', '9030.39.91', 'Los demás, con dispositivo registrador.', '6', 'Pza', '0.00', '0.00') - ,('90304091', '9030.40.91', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('90308201', '9030.82.01', 'Para medida o control de obleas ("wafers") o dispositivos semiconductores (incluidos los circuitos integrados).', '6', 'Pza', '0.00', '0.00') - ,('90308491', '9030.84.91', 'Los demás, con dispositivo registrador.', '6', 'Pza', '0.00', '0.00') - ,('90308999', '9030.89.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('90309005', '9030.90.05', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('90311001', '9031.10.01', 'Máquinas para equilibrar piezas mecánicas.', '6', 'Pza', '0.00', '0.00') - ,('90312002', '9031.20.02', 'Bancos de pruebas.', '6', 'Pza', '0.00', '0.00') - ,('90314101', '9031.41.01', 'Para control de obleas ("wafers") o dispositivos semiconductores (incluidos los circuitos integrados), o para control de máscaras fotográficas o retículas utilizadas en la fabricación de dispositivos semiconductores (incluidos los circuitos integrados).', '6', 'Pza', '0.00', '0.00') - ,('90314999', '9031.49.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('90318001', '9031.80.01', 'Controles fotoeléctricos.', '6', 'Pza', '5.00', '0.00') - ,('90318002', '9031.80.02', 'Para verificar la alineación, equilibrio o balanceo de neumáticos o ruedas.', '6', 'Pza', '0.00', '0.00') - ,('90318007', '9031.80.07', 'Niveles.', '6', 'Pza', '15.00', '0.00') - ,('90318099', '9031.80.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('90319001', '9031.90.01', 'Reconocibles para lo comprendido en la fracción arancelaria 9031.80.02.', '1', 'Kg', '0.00', '0.00') - ,('90319099', '9031.90.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('90321001', '9032.10.01', 'Para refrigeradores.', '6', 'Pza', '0.00', '0.00') - ,('90321003', '9032.10.03', 'Automáticos para el control de la temperatura del agua, con dispositivos de seguridad para fallas de flama.', '6', 'Pza', '15.00', '0.00') - ,('90321004', '9032.10.04', 'Para cocinas.', '6', 'Pza', '0.00', '0.00') - ,('90321099', '9032.10.99', 'Los demás.', '6', 'Pza', '5.00', '0.00') - ,('90322001', '9032.20.01', 'Manostatos (presostatos).', '6', 'Pza', '15.00', '0.00') - ,('90328102', '9032.81.02', 'Hidráulicos o neumáticos.', '6', 'Pza', '0.00', '0.00') - ,('90328901', '9032.89.01', 'Reconocibles para naves aéreas.', '6', 'Pza', '0.00', '0.00') - ,('90328902', '9032.89.02', 'Reguladores automáticos de voltaje, excepto para uso industrial, incluso combinados, en una misma envolvente o carcasa, con una fuente de voltaje con conversión de corriente CA/CC/CA, de las también llamadas "no break" o "uninterruptible power supply" ("UPS").', '6', 'Pza', '15.00', '0.00') - ,('90328903', '9032.89.03', 'Reguladores electrónicos de velocidad, para motores de corriente continua, giradiscos, grabadoras y tocacintas.', '6', 'Pza', '0.00', '0.00') - ,('90328904', '9032.89.04', 'Reguladores para operar sobre grupos generadores rotativos.', '6', 'Pza', '0.00', '0.00') - ,('90328905', '9032.89.05', 'Aparatos controladores de flama, reconocibles para calderas y hornos con capacidad inferior o igual a 100 CP.', '6', 'Pza', '0.00', '0.00') - ,('90328906', '9032.89.06', 'Reguladores tipo inducción, excepto lo comprendido en la fracción arancelaria 9032.89.02.', '6', 'Pza', '15.00', '0.00') - ,('90328999', '9032.89.99', 'Los demás.', '6', 'Pza', '5.00', '0.00') - ,('90329002', '9032.90.02', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('90330001', '9033.00.01', 'Partes y accesorios, no expresados ni comprendidos en otra parte de este Capítulo, para máquinas, aparatos, instrumentos o artículos del Capítulo 90.', '1', 'Kg', '0.00', '0.00') - ,('91011101', '9101.11.01', 'Con indicador mecánico solamente.', '6', 'Pza', '0.00', '0.00') - ,('91011999', '9101.19.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('91012101', '9101.21.01', 'Automáticos.', '6', 'Pza', '0.00', '0.00') - ,('91012999', '9101.29.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('91019101', '9101.91.01', 'Eléctricos.', '6', 'Pza', '0.00', '0.00') - ,('91019999', '9101.99.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('91021101', '9102.11.01', 'Con indicador mecánico solamente.', '6', 'Pza', '0.00', '0.00') - ,('91021201', '9102.12.01', 'Con indicador optoelectrónico solamente.', '6', 'Pza', '0.00', '0.00') - ,('91021999', '9102.19.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('91022101', '9102.21.01', 'Automáticos.', '6', 'Pza', '0.00', '0.00') - ,('91022999', '9102.29.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('91029101', '9102.91.01', 'Eléctricos.', '6', 'Pza', '0.00', '0.00') - ,('91029999', '9102.99.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('91031001', '9103.10.01', 'Eléctricos.', '6', 'Pza', '0.00', '0.00') - ,('91039099', '9103.90.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('91040004', '9104.00.04', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('91051102', '9105.11.02', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('91051999', '9105.19.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('91052101', '9105.21.01', 'Eléctricos.', '6', 'Pza', '0.00', '0.00') - ,('91052999', '9105.29.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('91059102', '9105.91.02', 'Eléctricos.', '6', 'Pza', '0.00', '0.00') - ,('91059999', '9105.99.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('91061002', '9106.10.02', 'Registradores de asistencia; registradores fechadores y registradores contadores.', '6', 'Pza', '0.00', '0.00') - ,('91069099', '9106.90.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('91070001', '9107.00.01', 'Interruptores horarios y demás aparatos que permitan accionar un dispositivo en un momento dado, con mecanismo de relojería o motor sincrónico.', '6', 'Pza', '0.00', '0.00') - ,('91081101', '9108.11.01', 'Con indicador mecánico solamente o con dispositivo que permita incorporarlo.', '6', 'Pza', '0.00', '0.00') - ,('91081201', '9108.12.01', 'Con indicador optoelectrónico solamente.', '6', 'Pza', '0.00', '0.00') - ,('91081999', '9108.19.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('91082001', '9108.20.01', 'Automáticos.', '6', 'Pza', '0.00', '0.00') - ,('91089099', '9108.90.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('91091001', '9109.10.01', 'Eléctricos.', '6', 'Pza', '0.00', '0.00') - ,('91099099', '9109.90.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('91101101', '9110.11.01', 'Mecanismos completos, sin montar o parcialmente montados ("chablons").', '6', 'Pza', '0.00', '0.00') - ,('91101201', '9110.12.01', 'Mecanismos incompletos, montados.', '6', 'Pza', '0.00', '0.00') - ,('91101901', '9110.19.01', 'Mecanismos "en blanco" ("ébauches").', '6', 'Pza', '0.00', '0.00') - ,('91109099', '9110.90.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('91111002', '9111.10.02', 'Cajas de metal precioso o chapado de metal precioso (plaqué).', '6', 'Pza', '0.00', '0.00') - ,('91112001', '9111.20.01', 'Cajas de metal común, incluso dorado o plateado.', '6', 'Pza', '0.00', '0.00') - ,('91118091', '9111.80.91', 'Las demás cajas.', '6', 'Pza', '0.00', '0.00') - ,('91119001', '9111.90.01', 'Partes.', '1', 'Kg', '0.00', '0.00') - ,('91122002', '9112.20.02', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('91129001', '9112.90.01', 'Partes.', '1', 'Kg', '0.00', '0.00') - ,('91131002', '9113.10.02', 'De metal precioso o chapado de metal precioso (plaqué).', '1', 'Kg', '0.00', '0.00') - ,('91132002', '9113.20.02', 'De metal común, incluso dorado o plateado.', '1', 'Kg', '0.00', '0.00') - ,('91139001', '9113.90.01', 'Pulseras.', '6', 'Pza', '0.00', '0.00') - ,('91139099', '9113.90.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('91143001', '9114.30.01', 'Esferas o cuadrantes.', '1', 'Kg', '0.00', '0.00') - ,('91144001', '9114.40.01', 'Platinas y puentes.', '1', 'Kg', '0.00', '0.00') - ,('91149099', '9114.90.99', 'Las demás.', '1', 'Kg', '0.00', '0.00') - ,('92011001', '9201.10.01', 'Pianos verticales.', '6', 'Pza', '0.00', '0.00') - ,('92012001', '9201.20.01', 'Pianos de cola.', '6', 'Pza', '0.00', '0.00') - ,('92019001', '9201.90.01', 'Pianos automáticos (pianolas).', '6', 'Pza', '10.00', '0.00') - ,('92019099', '9201.90.99', 'Los demás.', '6', 'Pza', '35.00', '0.00') - ,('92021001', '9202.10.01', 'De arco.', '6', 'Pza', '35.00', '0.00') - ,('92029001', '9202.90.01', 'Mandolinas o banjos.', '6', 'Pza', '10.00', '0.00') - ,('92029002', '9202.90.02', 'Guitarras.', '6', 'Pza', '35.00', '0.00') - ,('92029099', '9202.90.99', 'Los demás.', '6', 'Pza', '35.00', '0.00') - ,('92051001', '9205.10.01', 'Instrumentos llamados "metales".', '6', 'Pza', '35.00', '0.00') - ,('92059002', '9205.90.02', 'Órganos de tubo y teclado; armonios e instrumentos similares de teclado y lengüetas metálicas libres.', '6', 'Pza', '35.00', '0.00') - ,('92059003', '9205.90.03', 'Acordeones e instrumentos similares.', '6', 'Pza', '35.00', '0.00') - ,('92059004', '9205.90.04', 'Armónicas.', '1', 'Kg', '25.00', '0.00') - ,('92059099', '9205.90.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('92060001', '9206.00.01', 'Instrumentos musicales de percusión (por ejemplo: tambores, cajas, xilófonos, platillos, castañuelas, maracas).', '6', 'Pza', '10.00', '0.00') - ,('92071001', '9207.10.01', 'Órganos con "pedalier" de más de 24 pedales.', '6', 'Pza', '10.00', '0.00') - ,('92071002', '9207.10.02', 'Órganos, excepto lo comprendido en la fracción arancelaria 9207.10.01.', '6', 'Pza', '35.00', '0.00') - ,('92071099', '9207.10.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('92079099', '9207.90.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('92081001', '9208.10.01', 'Cajas de música.', '6', 'Pza', '35.00', '0.00') - ,('92089099', '9208.90.99', 'Los demás.', '6', 'Pza', '35.00', '0.00') - ,('92093001', '9209.30.01', 'Cuerdas armónicas.', '6', 'Pza', '0.00', '0.00') - ,('92099101', '9209.91.01', 'Gabinetes, muebles o sus partes.', '1', 'Kg', '5.00', '0.00') - ,('92099199', '9209.91.99', 'Los demás.', '1', 'Kg', '35.00', '0.00') - ,('92099201', '9209.92.01', 'Partes y accesorios de instrumentos musicales de la partida 92.02.', '1', 'Kg', '0.00', '0.00') - ,('92099401', '9209.94.01', 'Gabinetes, muebles o sus partes, para pianos.', '1', 'Kg', '5.00', '0.00') - ,('92099499', '9209.94.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('92099999', '9209.99.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('93011002', '9301.10.02', 'Piezas de artillería (por ejemplo: cañones, obuses y morteros).', '6', 'Pza', '5.00', '0.00') - ,('93012001', '9301.20.01', 'Lanzacohetes; lanzallamas; lanzagranadas; lanzatorpedos y lanzadores similares.', '6', 'Pza', '5.00', '0.00') - ,('93019099', '9301.90.99', 'Las demás.', '6', 'Pza', '5.00', '0.00') - ,('93020002', '9302.00.02', 'Revólveres y pistolas, excepto los de las partidas 93.03 o 93.04.', '6', 'Pza', '15.00', '0.00') - ,('93031001', '9303.10.01', 'Para lanzar cápsulas con sustancias asfixiantes, tóxicas o repelentes.', '6', 'Pza', '5.00', '0.00') - ,('93031099', '9303.10.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('93032091', '9303.20.91', 'Las demás armas largas de caza o tiro deportivo que tengan, por lo menos, un cañón de ánima lisa.', '6', 'Pza', '15.00', '0.00') - ,('93033091', '9303.30.91', 'Las demás armas largas de caza o tiro deportivo.', '6', 'Pza', '15.00', '0.00') - ,('93039001', '9303.90.01', 'Cañones industriales desincrustadores, mediante cartuchos especiales con proyectil blindado.', '6', 'Pza', '10.00', '0.00') - ,('93039099', '9303.90.99', 'Las demás.', '6', 'Pza', '15.00', '0.00') - ,('93040001', '9304.00.01', 'Pistolas de matarife de émbolo oculto.', '6', 'Pza', '0.00', '0.00') - ,('93040099', '9304.00.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('93051001', '9305.10.01', 'Reconocibles como diseñadas exclusivamente para lo comprendido en la fracción arancelaria 9304.00.01.', '6', 'Pza', '0.00', '0.00') - ,('93051099', '9305.10.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('93052002', '9305.20.02', 'De armas largas de la partida 93.03.', '6', 'Pza', '15.00', '0.00') - ,('93059101', '9305.91.01', 'De armas de guerra de la partida 93.01.', '6', 'Pza', '15.00', '0.00') - ,('93059999', '9305.99.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('93062101', '9306.21.01', 'Cartuchos cargados con gases lacrimosos o tóxicos.', '11', 'Mll', '5.00', '0.00') - ,('93062199', '9306.21.99', 'Los demás.', '11', 'Mll', '15.00', '0.00') - ,('93062999', '9306.29.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('93063004', '9306.30.04', 'Partes.', '6', 'Pza', '5.00', '0.00') - ,('93063099', '9306.30.99', 'Los demás.', '11', 'Mll', '15.00', '0.00') - ,('93069003', '9306.90.03', 'Partes.', '6', 'Pza', '5.00', '0.00') - ,('93069099', '9306.90.99', 'Los demás.', '11', 'Mll', '15.00', '0.00') - ,('93070001', '9307.00.01', 'Sables, espadas, bayonetas, lanzas y demás armas blancas, sus partes y fundas.', '6', 'Pza', '15.00', '0.00') - ,('94011001', '9401.10.01', 'Asientos de los tipos utilizados en aeronaves.', '6', 'Pza', '0.00', '0.00') - ,('94012001', '9401.20.01', 'Asientos de los tipos utilizados en vehículos automóviles.', '6', 'Pza', '0.00', '0.00') - ,('94013101', '9401.31.01', 'De madera.', '6', 'Pza', '10.00', '0.00') - ,('94013999', '9401.39.99', 'Los demás.', '6', 'Pza', '35.00', '0.00') - ,('94014101', '9401.41.01', 'De madera.', '6', 'Pza', '10.00', '0.00') - ,('94014999', '9401.49.99', 'Los demás.', '6', 'Pza', '10.00', '0.00') - ,('94015201', '9401.52.01', 'De bambú.', '6', 'Pza', '35.00', '0.00') - ,('94015301', '9401.53.01', 'De ratán (roten).', '6', 'Pza', '10.00', '0.00') - ,('94015999', '9401.59.99', 'Los demás.', '6', 'Pza', '10.00', '0.00') - ,('94016101', '9401.61.01', 'Con relleno.', '6', 'Pza', '10.00', '0.00') - ,('94016999', '9401.69.99', 'Los demás.', '6', 'Pza', '10.00', '0.00') - ,('94017101', '9401.71.01', 'Con relleno.', '6', 'Pza', '35.00', '0.00') - ,('94017999', '9401.79.99', 'Los demás.', '6', 'Pza', '10.00', '0.00') - ,('94018091', '9401.80.91', 'Los demás asientos.', '1', 'Kg', '10.00', '0.00') - ,('94019101', '9401.91.01', 'De madera.', '1', 'Kg', '0.00', '0.00') - ,('94019901', '9401.99.01', 'Reconocibles como diseñadas exclusivamente para tractores agrícolas e industriales.', '6', 'Pza', '0.00', '0.00') - ,('94019999', '9401.99.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('94021001', '9402.10.01', 'Partes.', '1', 'Kg', '15.00', '0.00') - ,('94021099', '9402.10.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('94029001', '9402.90.01', 'Mesas de operaciones.', '6', 'Pza', '10.00', '0.00') - ,('94029002', '9402.90.02', 'Parihuelas o camillas.', '6', 'Pza', '10.00', '0.00') - ,('94029099', '9402.90.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('94031003', '9403.10.03', 'Los demás.', '6', 'Pza', '35.00', '0.00') - ,('94032091', '9403.20.91', 'Los demás.', '6', 'Pza', '35.00', '0.00') - ,('94033001', '9403.30.01', 'Muebles de madera de los tipos utilizados en oficinas, excepto lo comprendido en la fracción arancelaria 9403.30.02.', '6', 'Pza', '10.00', '0.00') - ,('94033002', '9403.30.02', 'Llamados "estaciones de trabajo", reconocibles como diseñados para alojar un sistema de cómputo personal, conteniendo por lo menos: una cubierta para monitor, una cubierta para teclado y una cubierta para la unidad central de proceso.', '6', 'Pza', '15.00', '0.00') - ,('94034001', '9403.40.01', 'Muebles de madera de los tipos utilizados en cocinas.', '6', 'Pza', '10.00', '0.00') - ,('94035001', '9403.50.01', 'Muebles de madera de los tipos utilizados en dormitorios.', '6', 'Pza', '10.00', '0.00') - ,('94036001', '9403.60.01', 'Mesas, reconocibles como diseñadas exclusivamente para dibujo o trazado (restiradores), sin equipar.', '6', 'Pza', '35.00', '0.00') - ,('94036002', '9403.60.02', 'Atriles.', '6', 'Pza', '25.00', '0.00') - ,('94036003', '9403.60.03', 'Llamados "estaciones de trabajo", reconocibles como diseñados para alojar un sistema de cómputo personal, conteniendo por lo menos: una cubierta para monitor, una cubierta para teclado y una cubierta para la unidad central de proceso.', '6', 'Pza', '15.00', '0.00') - ,('94036099', '9403.60.99', 'Los demás.', '6', 'Pza', '10.00', '0.00') - ,('94037003', '9403.70.03', 'Muebles de plástico.', '6', 'Pza', '15.00', '0.00') - ,('94038201', '9403.82.01', 'De bambú.', '6', 'Pza', '15.00', '0.00') - ,('94038301', '9403.83.01', 'De ratán (roten).', '6', 'Pza', '15.00', '0.00') - ,('94038999', '9403.89.99', 'Los demás.', '6', 'Pza', '35.00', '0.00') - ,('94039101', '9403.91.01', 'De madera.', '1', 'Kg', '0.00', '0.00') - ,('94039999', '9403.99.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('94041001', '9404.10.01', 'Somieres.', '6', 'Pza', '35.00', '0.00') - ,('94042102', '9404.21.02', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('94042999', '9404.29.99', 'De otras materias.', '6', 'Pza', '25.00', '0.00') - ,('94043001', '9404.30.01', 'Sacos (bolsas) de dormir.', '6', 'Pza', '15.00', '0.00') - ,('94044001', '9404.40.01', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('94049099', '9404.90.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('94051101', '9405.11.01', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('94051999', '9405.19.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('94052101', '9405.21.01', 'Las demás.', '6', 'Pza', '15.00', '0.00') - ,('94052999', '9405.29.99', 'Las demás.', '6', 'Pza', '15.00', '0.00') - ,('94053101', '9405.31.01', 'Diseñadas para ser utilizadas únicamente con fuentes luminosas de diodos emisores de luz (LED).', '6', 'Pza', '0.00', '0.00') - ,('94053999', '9405.39.99', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('94054101', '9405.41.01', 'Fotovoltaicos, diseñados para ser utilizados únicamente con fuentes luminosas de diodos emisores de luz (LED).', '6', 'Pza', '15.00', '0.00') - ,('94054291', '9405.42.91', 'Los demás, diseñados para ser utilizados únicamente con fuentes luminosas de diodos emisores de luz (LED).', '6', 'Pza', '15.00', '0.00') - ,('94054999', '9405.49.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('94055002', '9405.50.02', 'Luminarias y aparatos de alumbrado, no eléctricos.', '6', 'Pza', '15.00', '0.00') - ,('94056101', '9405.61.01', 'Diseñados para ser utilizados únicamente con fuentes luminosas de diodos emisores de luz (LED).', '6', 'Pza', '25.00', '0.00') - ,('94056999', '9405.69.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('94059101', '9405.91.01', 'Bombillas de borosilicato, para lámparas o linternas de combustible líquido o gaseoso.', '1', 'Kg', '0.00', '0.00') - ,('94059102', '9405.91.02', 'Bombillas para lámparas o linternas de combustible líquido o gaseoso, excepto lo comprendido en la fracción arancelaria 9405.91.01.', '1', 'Kg', '5.00', '0.00') - ,('94059103', '9405.91.03', 'Elementos de vidrio para alumbrado y señalización.', '1', 'Kg', '5.00', '0.00') - ,('94059104', '9405.91.04', 'Piezas moldeadas semiterminadas (esbozos), de vidrio sin contenido de plomo, en formas poliédricas, reconocibles como diseñadas exclusivamente para la elaboración de candiles y lámparas.', '1', 'Kg', '0.00', '0.00') - ,('94059199', '9405.91.99', 'Las demás.', '1', 'Kg', '15.00', '0.00') - ,('94059201', '9405.92.01', 'De plástico.', '1', 'Kg', '5.00', '0.00') - ,('94059999', '9405.99.99', 'Las demás.', '1', 'Kg', '15.00', '0.00') - ,('94061001', '9406.10.01', 'De madera.', '1', 'Kg', '10.00', '0.00') - ,('94062001', '9406.20.01', 'Unidades de construcción modular, de acero.', '1', 'Kg', '10.00', '0.00') - ,('94069099', '9406.90.99', 'Las demás.', '1', 'Kg', '10.00', '0.00') - ,('95030001', '9503.00.01', 'Triciclos o cochecitos de pedal o palanca.', '6', 'Pza', '15.00', '0.00') - ,('95030002', '9503.00.02', 'Con ruedas, diseñados para que los conduzcan los niños, impulsados por ellos o por otra persona, o accionados por baterías recargables de hasta 12 V, excepto, en ambos casos, lo comprendido en la fracción arancelaria 9503.00.01.', '6', 'Pza', '15.00', '0.00') - ,('95030004', '9503.00.04', 'Muñecas y muñecos que representen solamente seres humanos, incluso vestidos, que contengan mecanismos operados eléctrica o electrónicamente, excepto lo comprendido en la fracción arancelaria 9503.00.05.', '6', 'Pza', '15.00', '0.00') - ,('95030005', '9503.00.05', 'Muñecas y muñecos que representen solamente seres humanos, de longitud inferior o igual a 30 cm, incluso vestidos, articulados o con mecanismos operados eléctrica o electrónicamente.', '6', 'Pza', '15.00', '0.00') - ,('95030007', '9503.00.07', 'Trenes eléctricos, incluidos los carriles (rieles), señales y demás accesorios.', '1', 'Kg', '0.00', '0.00') - ,('95030008', '9503.00.08', 'Juguetes terapéutico-pedagógicos, reconocibles como diseñados exclusivamente para usos clínicos, para corregir disfunciones psicomotrices o problemas de lento aprendizaje, en instituciones de educación especial o similares.', '1', 'Kg', '0.00', '0.00') - ,('95030009', '9503.00.09', 'Modelos reducidos "a escala" para ensamblar, de madera de balsa.', '1', 'Kg', '0.00', '0.00') - ,('95030010', '9503.00.10', 'Modelos reducidos "a escala" para ensamblar, incluso los que tengan componentes electrónicos o eléctricos, excepto lo comprendido en la fracción arancelaria 9503.00.07.', '1', 'Kg', '15.00', '0.00') - ,('95030011', '9503.00.11', 'Juegos o surtidos de construcción; los demás juguetes de construcción.', '1', 'Kg', '15.00', '0.00') - ,('95030012', '9503.00.12', 'Juguetes que representen animales o seres no humanos, rellenos.', '6', 'Pza', '15.00', '0.00') - ,('95030013', '9503.00.13', 'Juguetes que representen animales o seres no humanos, sin rellenar, de papel o cartón.', '1', 'Kg', '0.00', '0.00') - ,('95030015', '9503.00.15', 'Instrumentos y aparatos, de música, de juguete.', '1', 'Kg', '15.00', '0.00') - ,('95030016', '9503.00.16', 'Rompecabezas de papel o cartón.', '1', 'Kg', '15.00', '0.00') - ,('95030017', '9503.00.17', 'Rompecabezas de materias distintas a papel o cartón.', '1', 'Kg', '15.00', '0.00') - ,('95030018', '9503.00.18', 'Juegos o surtidos reconocibles como diseñados exclusivamente para que el niño o la niña, representen un personaje, profesión u oficio, excepto juegos que imiten preparaciones de belleza, de maquillaje o de manicura.', '1', 'Kg', '15.00', '0.00') - ,('95030020', '9503.00.20', 'Juguetes y modelos, con motor, excepto lo comprendido en las fracciones arancelarias 9503.00.02, 9503.00.04, 9503.00.05, 9503.00.07, 9503.00.09, 9503.00.10, 9503.00.11, 9503.00.12, 9503.00.15 y 9503.00.18.', '1', 'Kg', '15.00', '0.00') - ,('95030021', '9503.00.21', 'Ábacos.', '1', 'Kg', '10.00', '0.00') - ,('95030022', '9503.00.22', 'Preparaciones de materias plásticas, o caucho, reconocibles como diseñadas para formar globos por insuflado.', '1', 'Kg', '15.00', '0.00') - ,('95030023', '9503.00.23', 'Los demás', '1', 'Kg', '15.00', '0.00') - ,('95030024', '9503.00.24', 'Juguetes destinados a niños de hasta 36 meses de edad, excepto lo comprendido en las fracciones arancelarias 9503.00.01, 9503.00.02, 9503.00.04, 9503.00.05, 9503.00.11, 9503.00.12, 9503.00.13, 9503.00.15, 9503.00.16, 9503.00.17, 9503.00.20 y 9503.00.23.', '1', 'Kg', '15.00', '0.00') - ,('95030025', '9503.00.25', 'Juguetes réplica de armas de fuego, que tengan apariencia, forma y/o configuración de las armas de la subpartida 9301.90, o de las partidas 93.02 y 93.03, pero que no sean las armas comprendidas en la partida 93.04.', '1', 'Kg', '10.00', '0.00') - ,('95030026', '9503.00.26', 'Juguetes reconocibles como diseñados exclusivamente para lanzar agua, excepto los comprendidos en la fracción arancelaria 9503.00.25.', '1', 'Kg', '0.00', '0.00') - ,('95030027', '9503.00.27', 'Partes o piezas sueltas para triciclos, patinetes, coches de pedal y juguetes similares con ruedas.', '1', 'Kg', '0.00', '0.00') - ,('95030028', '9503.00.28', 'Prendas, complementos (accesorios) de vestir, calzado, sombreros y demás tocados, para muñecas y muñecos que representen solamente seres humanos.', '1', 'Kg', '0.00', '0.00') - ,('95030029', '9503.00.29', 'Subensambles eléctricos o electrónicos, reconocibles como diseñados exclusivamente para los productos de las fracciones arancelarias 9503.00.04 y 9503.00.05.', '1', 'Kg', '0.00', '0.00') - ,('95030030', '9503.00.30', 'Partes y accesorios de muñecas y muñecos que representen solamente seres humanos, excepto lo comprendido en las fracciones arancelarias 9503.00.28 y 9503.00.29.', '1', 'Kg', '0.00', '0.00') - ,('95030031', '9503.00.31', 'Partes y accesorios de instrumentos y aparatos, de música, de juguete.', '1', 'Kg', '0.00', '0.00') - ,('95030032', '9503.00.32', 'Partes y accesorios reconocibles como diseñados exclusivamente para lo comprendido en la fracción arancelaria 9503.00.20.', '1', 'Kg', '0.00', '0.00') - ,('95030033', '9503.00.33', 'Motores, excepto eléctricos reconocibles como diseñados exclusivamente para montarse en juguetes o en modelos, reducidos.', '1', 'Kg', '0.00', '0.00') - ,('95030034', '9503.00.34', 'Partes y accesorios reconocibles como diseñados exclusivamente para los productos comprendidos en la fracción arancelaria 9503.00.24.', '1', 'Kg', '0.00', '0.00') - ,('95030035', '9503.00.35', 'Partes, piezas y accesorios, reconocibles como diseñadas para coches y sillas de ruedas para muñecas o muñecos, para los demás juguetes de rueda, o para lo comprendido en las fracciones arancelarias 9503.00.21, 9503.00.25 o 9503.00.26.', '1', 'Kg', '0.00', '0.00') - ,('95030091', '9503.00.91', 'Los demás juguetes con ruedas diseñados para que los conduzcan los niños; coches y sillas de ruedas para muñecas o muñecos.', '6', 'Pza', '15.00', '0.00') - ,('95030092', '9503.00.92', 'Las demás muñecas y muñecos que representen solamente seres humanos, incluso vestidos, excepto lo comprendido en las fracciones arancelarias 9503.00.04.y 9503.00.05.', '6', 'Pza', '15.00', '0.00') - ,('95030093', '9503.00.93', 'Los demás juguetes que representen animales o seres no humanos, sin rellenar.', '1', 'Kg', '15.00', '0.00') - ,('95030099', '9503.00.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('95042001', '9504.20.01', 'Tizas y bolas de billar.', '1', 'Kg', '5.00', '0.00') - ,('95042099', '9504.20.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('95043001', '9504.30.01', 'Partes y accesorios.', '1', 'Kg', '0.00', '0.00') - ,('95043002', '9504.30.02', 'Máquinas de funcionamiento eléctrico, electrónico, mecánico o combinación de ellos, a través de las cuales se realicen sorteos con números o símbolos, que están sujetos al azar.', '6', 'Pza', '15.00', '0.00') - ,('95043099', '9504.30.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('95044001', '9504.40.01', 'Naipes.', '1', 'Kg', '15.00', '0.00') - ,('95045003', '9504.50.03', 'Partes y accesorios, para las videoconsolas y máquinas de videojuegos.', '1', 'Kg', '0.00', '0.00') - ,('95045004', '9504.50.04', 'Cartuchos conteniendo programas para videoconsolas y máquinas de videojuegos.', '6', 'Pza', '0.00', '0.00') - ,('95049001', '9504.90.01', 'Pelotas de celuloide.', '1', 'Kg', '0.00', '0.00') - ,('95049002', '9504.90.02', 'Bolas, excepto lo comprendido en la fracción arancelaria 9504.90.01.', '6', 'Pza', '0.00', '0.00') - ,('95049003', '9504.90.03', 'Bolos o pinos de madera; aparatos automáticos para acomodar los bolos o pinos y regresar las bolas a su lugar de lanzamiento ("bowlings"), sus partes o piezas sueltas.', '1', 'Kg', '10.00', '0.00') - ,('95049004', '9504.90.04', 'Autopistas eléctricas.', '1', 'Kg', '0.00', '0.00') - ,('95049005', '9504.90.05', 'Partes sueltas y accesorios reconocibles como diseñadas exclusivamente para lo comprendido en la fracción arancelaria 9504.90.04.', '1', 'Kg', '0.00', '0.00') - ,('95049099', '9504.90.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('95051001', '9505.10.01', 'Árboles artificiales para fiestas de Navidad.', '1', 'Kg', '15.00', '0.00') - ,('95051099', '9505.10.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('95059099', '9505.90.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('95061101', '9506.11.01', 'Esquís.', '9', 'Par', '0.00', '0.00') - ,('95061201', '9506.12.01', 'Fijadores de esquí.', '1', 'Kg', '0.00', '0.00') - ,('95061999', '9506.19.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('95062101', '9506.21.01', 'Deslizadores de vela.', '6', 'Pza', '0.00', '0.00') - ,('95062999', '9506.29.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('95063101', '9506.31.01', 'Palos de golf ("clubs"), completos, en juegos.', '6', 'Pza', '10.00', '0.00') - ,('95063199', '9506.31.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('95063201', '9506.32.01', 'Pelotas.', '1', 'Kg', '0.00', '0.00') - ,('95063901', '9506.39.01', 'Partes para palos de golf.', '6', 'Pza', '0.00', '0.00') - ,('95063999', '9506.39.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('95064001', '9506.40.01', 'Artículos y material para tenis de mesa.', '6', 'Pza', '10.00', '0.00') - ,('95065101', '9506.51.01', 'Esbozos.', '6', 'Pza', '0.00', '0.00') - ,('95065199', '9506.51.99', 'Las demás.', '6', 'Pza', '10.00', '0.00') - ,('95065999', '9506.59.99', 'Las demás.', '1', 'Kg', '10.00', '0.00') - ,('95066101', '9506.61.01', 'Pelotas de tenis.', '1', 'Kg', '10.00', '0.00') - ,('95066201', '9506.62.01', 'Inflables.', '1', 'Kg', '15.00', '0.00') - ,('95066999', '9506.69.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('95067001', '9506.70.01', 'Patines para hielo y patines de ruedas, incluido el calzado con patines fijos.', '9', 'Par', '0.00', '0.00') - ,('95069103', '9506.91.03', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('95069901', '9506.99.01', 'Espinilleras y tobilleras.', '1', 'Kg', '0.00', '0.00') - ,('95069902', '9506.99.02', 'Artículos para el tiro de arco, así como sus partes o accesorios reconocibles como destinados exclusiva o principalmente a dichos artículos.', '1', 'Kg', '10.00', '0.00') - ,('95069903', '9506.99.03', 'Redes para deportes de campo.', '6', 'Pza', '0.00', '0.00') - ,('95069904', '9506.99.04', 'Caretas.', '6', 'Pza', '10.00', '0.00') - ,('95069905', '9506.99.05', 'Trampolines.', '6', 'Pza', '0.00', '0.00') - ,('95069906', '9506.99.06', 'Piscinas, incluso infantiles.', '1', 'Kg', '15.00', '0.00') - ,('95069999', '9506.99.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('95071001', '9507.10.01', 'Cañas de pescar.', '6', 'Pza', '10.00', '0.00') - ,('95072001', '9507.20.01', 'Anzuelos, incluso montados en sedal (tanza).', '1', 'Kg', '10.00', '0.00') - ,('95073001', '9507.30.01', 'Carretes de pesca.', '1', 'Kg', '10.00', '0.00') - ,('95079099', '9507.90.99', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('95081001', '9508.10.01', 'Circos y zoológicos, ambulantes.', '6', 'Pza', '0.00', '0.00') - ,('95082101', '9508.21.01', 'Montañas rusas.', '6', 'Pza', '0.00', '0.00') - ,('95082201', '9508.22.01', 'Carruseles, columpios y tiovivos.', '6', 'Pza', '0.00', '0.00') - ,('95082301', '9508.23.01', 'Autos de choque.', '6', 'Pza', '10.00', '0.00') - ,('95082401', '9508.24.01', 'Simuladores de movimiento y cines dinámicos.', '6', 'Pza', '10.00', '0.00') - ,('95082501', '9508.25.01', 'Paseos acuáticos.', '6', 'Pza', '10.00', '0.00') - ,('95082601', '9508.26.01', 'Atracciones de parques acuáticos.', '6', 'Pza', '10.00', '0.00') - ,('95082999', '9508.29.99', 'Las demás.', '6', 'Pza', '10.00', '0.00') - ,('95083001', '9508.30.01', 'Atracciones de feria.', '6', 'Pza', '0.00', '0.00') - ,('95084001', '9508.40.01', 'Teatros ambulantes.', '6', 'Pza', '10.00', '0.00') - ,('96011001', '9601.10.01', 'Marfil trabajado y sus manufacturas.', '1', 'Kg', '15.00', '0.00') - ,('96019099', '9601.90.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('96020001', '9602.00.01', 'Cápsulas de gelatina.', '1', 'Kg', '5.00', '0.00') - ,('96020002', '9602.00.02', 'Ámbar de Chiapas.', '1', 'Kg', '15.00', '0.00') - ,('96020099', '9602.00.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('96031001', '9603.10.01', 'Escobas y escobillas de ramitas u otra materia vegetal atada en haces, incluso con mango.', '6', 'Pza', '15.00', '0.00') - ,('96032101', '9603.21.01', 'Cepillos de dientes, incluidos los cepillos para dentaduras postizas.', '6', 'Pza', '15.00', '0.00') - ,('96032999', '9603.29.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('96033001', '9603.30.01', 'Pinceles y brochas para pintura artística, pinceles para escribir y pinceles similares para aplicación de cosméticos.', '6', 'Pza', '15.00', '0.00') - ,('96034001', '9603.40.01', 'Pinceles y brochas para pintar, enlucir, barnizar o similares (excepto los de la subpartida 9603.30); almohadillas o muñequillas y rodillos, para pintar.', '6', 'Pza', '15.00', '0.00') - ,('96035091', '9603.50.91', 'Los demás cepillos que constituyan partes de máquinas, aparatos o vehículos.', '6', 'Pza', '0.00', '0.00') - ,('96039001', '9603.90.01', 'Plumeros.', '6', 'Pza', '35.00', '0.00') - ,('96039099', '9603.90.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('96040001', '9604.00.01', 'Tamices, cedazos y cribas, de mano.', '1', 'Kg', '0.00', '0.00') - ,('96050001', '9605.00.01', 'Juegos o surtidos de viaje para aseo personal, costura o limpieza del calzado o de prendas de vestir.', '1', 'Kg', '15.00', '0.00') - ,('96061001', '9606.10.01', 'Botones de presión y sus partes.', '1', 'Kg', '15.00', '0.00') - ,('96062101', '9606.21.01', 'De plástico, sin forrar con materia textil.', '1', 'Kg', '15.00', '0.00') - ,('96062201', '9606.22.01', 'De metal común, sin forrar con materia textil.', '1', 'Kg', '15.00', '0.00') - ,('96062999', '9606.29.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('96063001', '9606.30.01', 'Formas para botones y demás partes de botones; esbozos de botones.', '1', 'Kg', '15.00', '0.00') - ,('96071101', '9607.11.01', 'Con dientes de metal común.', '1', 'Kg', '15.00', '0.00') - ,('96071999', '9607.19.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('96072001', '9607.20.01', 'Partes.', '1', 'Kg', '15.00', '0.00') - ,('96081002', '9608.10.02', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('96082001', '9608.20.01', 'Rotuladores y marcadores con punta de fieltro u otra punta porosa.', '6', 'Pza', '15.00', '0.00') - ,('96083001', '9608.30.01', 'Para dibujar con tinta china.', '6', 'Pza', '0.00', '0.00') - ,('96083099', '9608.30.99', 'Las demás.', '6', 'Pza', '15.00', '0.00') - ,('96084002', '9608.40.02', 'Lapiceros o portaminas sin cuerpo ni sujetador o clip, y con tapón, punta metálica o cono y con o sin portagomas.', '1', 'Kg', '10.00', '0.00') - ,('96084099', '9608.40.99', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('96085002', '9608.50.02', 'Los demás.', '6', 'Pza', '15.00', '0.00') - ,('96086001', '9608.60.01', 'Cartuchos de repuesto con su punta para bolígrafo.', '1', 'Kg', '0.00', '0.00') - ,('96089102', '9608.91.02', 'Plumillas y puntos para plumillas.', '1', 'Kg', '0.00', '0.00') - ,('96089903', '9608.99.03', 'Partes de metal común, chapeadas de oro.', '1', 'Kg', '5.00', '0.00') - ,('96089906', '9608.99.06', 'Partes de metal común, chapeadas de plata.', '1', 'Kg', '0.00', '0.00') - ,('96089907', '9608.99.07', 'De metal, excepto partes.', '1', 'Kg', '0.00', '0.00') - ,('96089908', '9608.99.08', 'Barriles o cañones, tapas, tubos, de metal común.', '1', 'Kg', '0.00', '0.00') - ,('96089909', '9608.99.09', 'Depósitos de caucho, mecanismos, sujetadores de metal común, alimentadoras.', '1', 'Kg', '0.00', '0.00') - ,('96089910', '9608.99.10', 'Puntas, barras o contenedores para uso en plumones o marcadores, excepto lo comprendido en la fracción arancelaria 9608.99.11.', '1', 'Kg', '0.00', '0.00') - ,('96089911', '9608.99.11', 'Puntas de fibras acrílicas para uso en plumones o marcadores.', '1', 'Kg', '0.00', '0.00') - ,('96089912', '9608.99.12', 'De oro de plata o de platino, total o parcialmente, excepto partes.', '1', 'Kg', '0.00', '0.00') - ,('96089999', '9608.99.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('96091001', '9609.10.01', 'Lápices.', '6', 'Pza', '15.00', '0.00') - ,('96092001', '9609.20.01', 'Minas, cuyo diámetro no exceda de 0.7 mm.', '1', 'Kg', '10.00', '0.00') - ,('96092099', '9609.20.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('96099001', '9609.90.01', 'Pasteles.', '1', 'Kg', '10.00', '0.00') - ,('96099099', '9609.90.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('96100001', '9610.00.01', 'Pizarras y tableros para escribir o dibujar, incluso enmarcados.', '1', 'Kg', '15.00', '0.00') - ,('96110001', '9611.00.01', 'Partes.', '1', 'Kg', '0.00', '0.00') - ,('96110099', '9611.00.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('96121003', '9612.10.03', 'Bandas sin fin, entintadas, de nailon, reconocibles como diseñadas para incorporarse a cartuchos de plástico de unidades de impresión o impresoras, para cajas registradoras.', '6', 'Pza', '0.00', '0.00') - ,('96121099', '9612.10.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('96122001', '9612.20.01', 'Tampones.', '1', 'Kg', '0.00', '0.00') - ,('96131001', '9613.10.01', 'Encendedores de gas no recargables, de bolsillo.', '6', 'Pza', '15.00', '0.00') - ,('96132001', '9613.20.01', 'Encendedores de gas recargables, de bolsillo.', '6', 'Pza', '15.00', '0.00') - ,('96138002', '9613.80.02', 'Encendedores de mesa.', '6', 'Pza', '15.00', '0.00') - ,('96138099', '9613.80.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('96139001', '9613.90.01', 'Reconocibles como diseñadas exclusivamente para los encendedores de cigarrillos a base de resistencia para uso automotriz.', '1', 'Kg', '0.00', '0.00') - ,('96139002', '9613.90.02', 'Para encendedores a gas.', '1', 'Kg', '0.00', '0.00') - ,('96139099', '9613.90.99', 'Los demás.', '1', 'Kg', '5.00', '0.00') - ,('96140001', '9614.00.01', 'Escalabornes.', '1', 'Kg', '0.00', '0.00') - ,('96140003', '9614.00.03', 'Partes.', '1', 'Kg', '0.00', '0.00') - ,('96140099', '9614.00.99', 'Las demás.', '1', 'Kg', '15.00', '0.00') - ,('96151101', '9615.11.01', 'De caucho endurecido o plástico.', '1', 'Kg', '15.00', '0.00') - ,('96151999', '9615.19.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('96159099', '9615.90.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('96161001', '9616.10.01', 'Pulverizadores de tocador, sus monturas y cabezas de monturas.', '1', 'Kg', '15.00', '0.00') - ,('96162001', '9616.20.01', 'Borlas y similares para aplicación de polvos, otros cosméticos o productos de tocador.', '1', 'Kg', '15.00', '0.00') - ,('96170001', '9617.00.01', 'Termos y demás recipientes isotérmicos, montados y aislados por vacío, así como sus partes (excepto las ampollas de vidrio).', '1', 'Kg', '15.00', '0.00') - ,('96180001', '9618.00.01', 'Maniquíes con peso superior o igual a 25 kg.', '1', 'Kg', '10.00', '0.00') - ,('96180099', '9618.00.99', 'Los demás.', '1', 'Kg', '15.00', '0.00') - ,('96190001', '9619.00.01', 'De pasta de papel, papel, guata de celulosa o napa de fibras de celulosa.', '1', 'Kg', '5.00', '0.00') - ,('96190002', '9619.00.02', 'De guata de materia textil.', '1', 'Kg', '10.00', '0.00') - ,('96190003', '9619.00.03', 'Pañales y artículos similares, de otras materias textiles, excepto lo comprendido en la fracción arancelaria 9619.00.02.', '6', 'Pza', '20.00', '0.00') - ,('96190004', '9619.00.04', 'Los demás.', '1', 'Kg', '10.00', '0.00') - ,('96190099', '9619.00.99', 'Los demás.', '1', 'Kg', '20.00', '0.00') - ,('96200001', '9620.00.01', 'Tripies de cámaras fotográficas.', '1', 'Kg', '10.00', '0.00') - ,('96200002', '9620.00.02', 'De madera; de aluminio, excepto lo comprendido en la fracción arancelaria 9620.00.04.', '1', 'Kg', '15.00', '0.00') - ,('96200003', '9620.00.03', 'De las materias indicadas en la Partida 68.15.', '1', 'Kg', '35.00', '0.00') - ,('96200004', '9620.00.04', 'De máquinas o aparatos comprendidos en el Capítulo 84, excepto para las máquinas o aparatos de las partidas 84.28 u 84.71.', '1', 'Kg', '5.00', '0.00') - ,('96200099', '9620.00.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('97012101', '9701.21.01', 'Pinturas y dibujos.', '6', 'Pza', '0.00', '0.00') - ,('97012201', '9701.22.01', 'Mosaicos.', '6', 'Pza', '0.00', '0.00') - ,('97012999', '9701.29.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('97019101', '9701.91.01', 'Pinturas y dibujos.', '6', 'Pza', '0.00', '0.00') - ,('97019201', '9701.92.01', 'Mosaicos.', '6', 'Pza', '0.00', '0.00') - ,('97019999', '9701.99.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('97021001', '9702.10.01', 'De más de 100 años.', '6', 'Pza', '0.00', '0.00') - ,('97029099', '9702.90.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('97031001', '9703.10.01', 'De más de 100 años.', '6', 'Pza', '0.00', '0.00') - ,('97039099', '9703.90.99', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('97040002', '9704.00.02', 'Sellos (estampillas) de correo, timbres fiscales, marcas postales, sobres primer día, enteros postales, demás artículos franqueados y análogos, incluso obliterados, excepto los artículos de la partida 49.07.', '6', 'Pza', '0.00', '0.00') - ,('97051001', '9705.10.01', 'Bienes que hayan sido declarados monumentos arqueológicos de conformidad con la declaratoria que al respecto emita el Presidente de la República o el Secretario de Cultura, por conducto del titular del Instituto competente.', '6', 'Pza', '0.00', '0.00') - ,('97051002', '9705.10.02', 'Objetos de interés histórico o etnográfico, que no hayan sido declarados monumentos arqueológicos o históricos de conformidad con la declaratoria que al respecto emita el Presidente de la República o el Secretario de Cultura, por conducto del titular del Instituto competente.', '1', 'Kg', '0.00', '0.00') - ,('97051099', '9705.10.99', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('97052101', '9705.21.01', 'Objetos de interés paleontológico, que no hayan sido declarados monumentos arqueológicos o históricos de conformidad con la declaratoria que al respecto emita el Presidente de la República o el Secretario de Cultura, por conducto del titular del Instituto competente.', '1', 'Kg', '0.00', '0.00') - ,('97052199', '9705.21.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('97052201', '9705.22.01', 'Objetos de interés paleontológico, que no hayan sido declarados monumentos arqueológicos o históricos de conformidad con la declaratoria que al respecto emita el Presidente de la República o el Secretario de Cultura, por conducto del titular del Instituto competente.', '1', 'Kg', '0.00', '0.00') - ,('97052299', '9705.22.99', 'Los demás.', '6', 'Pza', '0.00', '0.00') - ,('97052901', '9705.29.01', 'Objetos de interés paleontológico, que no hayan sido declarados monumentos arqueológicos o históricos de conformidad con la declaratoria que al respecto emita el Presidente de la República o el Secretario de Cultura, por conducto del titular del Instituto competente.', '1', 'Kg', '0.00', '0.00') - ,('97052999', '9705.29.99', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('97053101', '9705.31.01', 'Objetos de interés histórico, que no hayan sido declarados monumentos arqueológicos o históricos de conformidad con la declaratoria que al respecto emita el Presidente de la República o el Secretario de Cultura, por conducto del titular del Instituto competente.', '1', 'Kg', '0.00', '0.00') - ,('97053199', '9705.31.99', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('97053901', '9705.39.01', 'Objetos de interés histórico, que no hayan sido declarados monumentos arqueológicos o históricos de conformidad con la declaratoria que al respecto emita el Presidente de la República o el Secretario de Cultura, por conducto del titular del Instituto competente.', '1', 'Kg', '0.00', '0.00') - ,('97053999', '9705.39.99', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('97061001', '9706.10.01', 'De más de 250 años.', '6', 'Pza', '0.00', '0.00') - ,('97069099', '9706.90.99', 'Las demás.', '6', 'Pza', '0.00', '0.00') - ,('98010001', '9801.00.01', 'Importaciones o exportaciones de muestras y muestrarios.', '1', 'Kg', '0.00', '0.00') - ,('98020001', '9802.00.01', 'Mercancías para el Programa de Promoción Sectorial de la Industria Eléctrica, cuando las empresas cuenten con la autorización a que se refiere la Regla 8a de las Complementarias, para la interpretación y aplicación de la Tarifa de la Ley de los Impuestos Generales de Importación y de Exportación, conforme a los criterios que establezca la Secretaría de Economía.', '1', 'Kg', '0.00', '0.00') - ,('98020002', '9802.00.02', 'Mercancías para el Programa de Promoción Sectorial de la Industria Electrónica, cuando las empresas cuenten con la autorización a que se refiere la Regla 8a de las Complementarias, para la interpretación y aplicación de la Tarifa de la Ley de los Impuestos Generales de Importación y de Exportación, conforme a los criterios que establezca la Secretaría de Economía.', '1', 'Kg', '0.00', '0.00') - ,('98020003', '9802.00.03', 'Mercancías para el Programa de Promoción Sectorial de la Industria del Mueble, cuando las empresas cuenten con la autorización a que se refiere la Regla 8a de las Complementarias, para la interpretación y aplicación de la Tarifa de la Ley de los Impuestos Generales de Importación y de Exportación, conforme a los criterios que establezca la Secretaría de Economía.', '1', 'Kg', '0.00', '0.00') - ,('98020004', '9802.00.04', 'Mercancías para el Programa de Promoción Sectorial de la Industria del Juguete, Juegos de Recreo y Artículos Deportivos, cuando las empresas cuenten con la autorización a que se refiere la Regla 8a de las Complementarias, para la interpretación y aplicación de la Tarifa de la Ley de los Impuestos Generales de Importación y de Exportación, conforme a los criterios que establezca la Secretaría de Economía.', '1', 'Kg', '0.00', '0.00') - ,('98020005', '9802.00.05', 'Mercancías para el Programa de Promoción Sectorial de la Industria del Calzado, cuando las empresas cuenten con la autorización a que se refiere la Regla 8a de las Complementarias, para la interpretación y aplicación de la Tarifa de la Ley de los Impuestos Generales de Importación y de Exportación, conforme a los criterios que establezca la Secretaría de Economía.', '1', 'Kg', '0.00', '0.00') - ,('98020006', '9802.00.06', 'Mercancías para el Programa de Promoción Sectorial de la Industria Minera y Metalúrgica, cuando las empresas cuenten con la autorización a que se refiere la Regla 8a de las Complementarias, para la interpretación y aplicación de la Tarifa de la Ley de los Impuestos Generales de Importación y de Exportación, conforme a los criterios que establezca la Secretaría de Economía.', '1', 'Kg', '0.00', '0.00') - ,('98020007', '9802.00.07', 'Mercancías para el Programa de Promoción Sectorial de la Industria Bienes de Capital, cuando las empresas cuenten con la autorización a que se refiere la Regla 8a de las Complementarias, para la interpretación y aplicación de la Tarifa de la Ley de los Impuestos Generales de Importación y de Exportación, conforme a los criterios que establezca la Secretaría de Economía.', '1', 'Kg', '0.00', '0.00') - ,('98020008', '9802.00.08', 'Mercancías para el Programa de Promoción Sectorial de la Industria Fotográfica, cuando las empresas cuenten con la autorización a que se refiere la Regla 8a de las Complementarias, para la interpretación y aplicación de la Tarifa de la Ley de los Impuestos Generales de Importación y de Exportación, conforme a los criterios que establezca la Secretaría de Economía.', '1', 'Kg', '0.00', '0.00') - ,('98020009', '9802.00.09', 'Mercancías para el Programa de Promoción Sectorial de la Industria de Maquinaria Agrícola, cuando las empresas cuenten con la autorización a que se refiere la Regla 8a de las Complementarias, para la interpretación y aplicación de la Tarifa de la Ley de los Impuestos Generales de Importación y de Exportación, conforme a los criterios que establezca la Secretaría de Economía.', '1', 'Kg', '0.00', '0.00') - ,('98020010', '9802.00.10', 'Mercancías para el Programa de Promoción Sectorial de las Industrias Diversas, cuando las empresas cuenten con la autorización a que se refiere la Regla 8a de las Complementarias, para la interpretación y aplicación de la Tarifa de la Ley de los Impuestos Generales de Importación y de Exportación, conforme a los criterios que establezca la Secretaría de Economía.', '1', 'Kg', '0.00', '0.00') - ,('98020011', '9802.00.11', 'Mercancías para el Programa de Promoción Sectorial de la Industria Química, cuando las empresas cuenten con la autorización a que se refiere la Regla 8a de las Complementarias, para la interpretación y aplicación de la Tarifa de la Ley de los Impuestos Generales de Importación y de Exportación, conforme a los criterios que establezca la Secretaría de Economía.', '1', 'Kg', '0.00', '0.00') - ,('98020012', '9802.00.12', 'Mercancías para el Programa de Promoción Sectorial de la Industria de Manufacturas del Caucho y Plástico cuando las empresas cuenten con la autorización a que se refiere la Regla 8ª de las Complementarias, para la interpretación y aplicación de la Tarifa de la Ley de los Impuestos Generales de Importación y de Exportación, conforme a los criterios que establezca la Secretaría de Economía, excepto las comprendidas en la fracción arancelaria 9802.00.38.', '1', 'Kg', '0.00', '0.00') - ,('98020013', '9802.00.13', 'Mercancías para el Programa de Promoción Sectorial de la Industria Siderúrgica cuando se trate de productores directos y las empresas cuenten con la autorización a que se refiere la Regla 8ª de las Complementarias, para la interpretación y aplicación de la Tarifa de la Ley de los Impuestos Generales de Importación y de Exportación, conforme a los criterios que establezca la Secretaría de Economía, excepto las comprendidas en el Anexo II del Decreto para el Fomento de la Industria Manufacturera,', '1', 'Kg', '0.00', '0.00') - ,('98020014', '9802.00.14', 'Mercancías para el Programa de Promoción Sectorial de la Industria de Productos Farmoquímicos, Medicamentos y Equipo Médico, cuando las empresas cuenten con la autorización a que se refiere la Regla 8a de las Complementarias, para la interpretación y aplicación de la Tarifa de la Ley de los Impuestos Generales de Importación y de Exportación, conforme a los criterios que establezca la Secretaría de Economía.', '1', 'Kg', '0.00', '0.00') - ,('98020015', '9802.00.15', 'Mercancías para el Programa de Promoción Sectorial de la Industria del Transporte, excepto el Sector de la Industria Automotriz y de Autopartes, cuando las empresas cuenten con la autorización a que se refiere la Regla 8a de las Complementarias, para la interpretación y aplicación de la Tarifa de la Ley de los Impuestos Generales de Importación y de Exportación, conforme a los criterios que establezca la Secretaría de Economía.', '1', 'Kg', '0.00', '0.00') - ,('98020016', '9802.00.16', 'Mercancías para el Programa de Promoción Sectorial de la Industria del Papel y Cartón, cuando las empresas cuenten con la autorización a que se refiere la Regla 8a de las Complementarias, para la interpretación y aplicación de la Tarifa de la Ley de los Impuestos Generales de Importación y de Exportación, conforme a los criterios que establezca la Secretaría de Economía.', '1', 'Kg', '0.00', '0.00') - ,('98020017', '9802.00.17', 'Mercancías para el Programa de Promoción Sectorial de la Industria de la Madera, cuando las empresas cuenten con la autorización a que se refiere la Regla 8a de las Complementarias, para la interpretación y aplicación de la Tarifa de la Ley de los Impuestos Generales de Importación y de Exportación, conforme a los criterios que establezca la Secretaría de Economía.', '1', 'Kg', '0.00', '0.00') - ,('98020018', '9802.00.18', 'Mercancías para el Programa de Promoción Sectorial de la Industria del Cuero y Pieles, cuando las empresas cuenten con la autorización a que se refiere la Regla 8a de las Complementarias, para la interpretación y aplicación de la Tarifa de la Ley de los Impuestos Generales de Importación y de Exportación, conforme a los criterios que establezca la Secretaría de Economía.', '1', 'Kg', '0.00', '0.00') - ,('98020019', '9802.00.19', 'Mercancías para el Programa de Promoción Sectorial de la Industria Automotriz y de Autopartes, cuando las empresas cuenten con la autorización a que se refiere la Regla 8a de las Complementarias, para la interpretación y aplicación de la Tarifa de la Ley de los Impuestos Generales de Importación y de Exportación, conforme a los criterios que establezca la Secretaría de Economía.', '1', 'Kg', '0.00', '0.00') - ,('98020020', '9802.00.20', 'Mercancías para el Programa de Promoción Sectorial de la Industria Textil y de la Confección excepto lo comprendido en la fracción 9802.00.24 y las comprendidas en el Anexo II, Apartado C del Decreto para el Fomento de la Industria Manufacturera, Maquiladora y de Servicios de Exportación, cuando las empresas cuenten con la autorización a que se refiere la Regla 8ª de las Complementarias, para la interpretación y aplicación de la Tarifa de la Ley de los Impuestos Generales de Importación y de Ex', '1', 'Kg', '0.00', '0.00') - ,('98020021', '9802.00.21', 'Mercancías para el Programa de Promoción Sectorial de la Industria de Chocolates, Dulces y Similares, cuando las empresas cuenten con la autorización a que se refiere la Regla 8a de las Complementarias, para la interpretación y aplicación de la Tarifa de la Ley de los Impuestos Generales de Importación y de Exportación, conforme a los criterios que establezca la Secretaría de Economía.', '1', 'Kg', '0.00', '0.00') - ,('98020022', '9802.00.22', 'Mercancías para el Programa de Promoción Sectorial de la Industria del Café, cuando las empresas cuenten con la autorización a que se refiere la Regla 8a de las Complementarias, para la interpretación y aplicación de la Tarifa de la Ley de los Impuestos Generales de Importación y de Exportación, conforme a los criterios que establezca la Secretaría de Economía.', '1', 'Kg', '0.00', '0.00') - ,('98020023', '9802.00.23', 'Mercancías para el Programa de Promoción Sectorial de la Industria Siderúrgica cuando se trate de productores indirectos y las empresas cuenten con la autorización a que se refiere la Regla 8ª de las Complementarias, para la interpretación y aplicación de la Tarifa de la Ley de los Impuestos Generales de Importación y de Exportación, conforme a los criterios que establezca la Secretaría de Economía, excepto las comprendidas en el Anexo II del Decreto para el Fomento de la Industria Manufacturer', '1', 'Kg', '5.00', '0.00') - ,('98020024', '9802.00.24', 'Hilados de filamento de nailon rígidos de título inferior o igual a 44.4 decitex (40 deniers) e inferior o igual a 1.33 decitex (1.2 deniers) por filamento e hilados de filamento de nailon rígidos de título igual a 44.4 decitex (40 deniers) y 34 filamentos de título igual a 1.30 decitex (1.17 deniers) por filamento, de la fracción arancelaria 5402.45.99; hilados rígidos, de filamentos de poliéster (no texturados), de título inferior o igual a 1.66 decitex (1.5 deniers) por filamento, de la frac', '1', 'Kg', '0.00', '0.00') - ,('98020025', '9802.00.25', 'Mercancías para el Programa de Promoción Sectorial de la Industria Alimentaria cuando las empresas cuenten con la autorización a que se refiere la Regla 8ª de las Complementarias, para la interpretación y aplicación de la Tarifa de la Ley de los Impuestos Generales de Importación y de Exportación, conforme a los criterios que establezca la Secretaría de Economía, excepto las comprendidas en el Anexo II del Decreto para el Fomento de la Industria Manufacturera, Maquiladora y de Servicios de Expo', '1', 'Kg', '0.00', '0.00') - ,('98020026', '9802.00.26', 'Mercancías al amparo de la autorización a que se refiere la Regla 8ª de las Complementarias, para la interpretación y aplicación de la Tarifa de la Ley de los Impuestos Generales de Importación y de Exportación (TIGIE), conforme a los criterios que establezca la Secretaría de Economía, clasificadas en las fracciones arancelarias cuya unidad de medida de la TIGIE se indique como Kg y listadas en el Anexo II, Apartado A del Decreto para el Fomento de la Industria Manufacturera, Maquiladora y de S', '1', 'Kg', '0.00', '0.00') - ,('98020027', '9802.00.27', 'Mercancías al amparo de la autorización a que se refiere la Regla 8ª de las Complementarias, para la interpretación y aplicación de la Tarifa de la Ley de los Impuestos Generales de Importación y de Exportación (TIGIE), conforme a los criterios que establezca la Secretaría de Economía, clasificadas en las fracciones arancelarias cuya unidad de medida de la TIGIE se indique como Kg y listadas en el Anexo II, Apartado B del Decreto para el Fomento de la Industria Manufacturera, Maquiladora y de S', '1', 'Kg', '0.00', '0.00') - ,('98020028', '9802.00.28', 'Mercancías al amparo de la autorización a que se refiere la Regla 8ª de las Complementarias, para la interpretación y aplicación de la Tarifa de la Ley de los Impuestos Generales de Importación y de Exportación (TIGIE), conforme a los criterios que establezca la Secretaría de Economía, clasificadas en las fracciones arancelarias cuya unidad de medida de la TIGIE se indique como Kg y listadas en el Anexo II, Apartado C del Decreto para el Fomento de la Industria Manufacturera, Maquiladora y de S', '1', 'Kg', '0.00', '0.00') - ,('98020029', '9802.00.29', 'Mercancías al amparo de la autorización a que se refiere la Regla 8ª de las Complementarias, para la interpretación y aplicación de la Tarifa de la Ley de los Impuestos Generales de Importación y de Exportación (TIGIE), conforme a los criterios que establezca la Secretaría de Economía, clasificadas en las fracciones arancelarias cuya unidad de medida de la TIGIE se indique como M y listadas en el Anexo II, Apartado C del Decreto para el Fomento de la Industria Manufacturera, Maquiladora y de Se', '3', 'M', '0.00', '0.00') - ,('98020030', '9802.00.30', 'Mercancías al amparo de la autorización a que se refiere la Regla 8ª de las Complementarias, para la interpretación y aplicación de la Tarifa de la Ley de los Impuestos Generales de Importación y de Exportación (TIGIE), conforme a los criterios que establezca la Secretaría de Economía, clasificadas en las fracciones arancelarias cuya unidad de medida de la TIGIE se indique como M² y listadas en el Anexo II, Apartado C del Decreto para el Fomento de la Industria Manufacturera, Maquiladora y de S', '4', 'M²', '0.00', '0.00') - ,('98020031', '9802.00.31', 'Mercancías al amparo de la autorización a que se refiere la Regla 8ª de las Complementarias, para la interpretación y aplicación de la Tarifa de la Ley de los Impuestos Generales de Importación y de Exportación (TIGIE), conforme a los criterios que establezca la Secretaría de Economía, clasificadas en las fracciones arancelarias cuya unidad de medida de la TIGIE se indique como Par y listadas en el Anexo II, Apartado C del Decreto para el Fomento de la Industria Manufacturera, Maquiladora y de', '9', 'Par', '0.00', '0.00') - ,('98020032', '9802.00.32', 'Mercancías al amparo de la autorización a que se refiere la Regla 8ª de las Complementarias, para la interpretación y aplicación de la Tarifa de la Ley de los Impuestos Generales de Importación y de Exportación (TIGIE), conforme a los criterios que establezca la Secretaría de Economía, clasificadas en las fracciones arancelarias cuya unidad de medida de la TIGIE se indique como Pza y listadas en el Anexo II, Apartado C del Decreto para el Fomento de la Industria Manufacturera, Maquiladora y de', '6', 'Pza', '0.00', '0.00') - ,('98020033', '9802.00.33', 'Mercancías al amparo de la autorización a que se refiere la Regla 8ª de las Complementarias, para la interpretación y aplicación de la Tarifa de la Ley de los Impuestos Generales de Importación y de Exportación (TIGIE), conforme a los criterios que establezca la Secretaría de Economía, clasificadas en las fracciones arancelarias cuya unidad de medida de la TIGIE se indique como Kg y listadas en el Anexo II, Apartado D del Decreto para el Fomento de la Industria Manufacturera, Maquiladora y de S', '1', 'Kg', '0.00', '0.00') - ,('98020034', '9802.00.34', 'Mercancías al amparo de la autorización a que se refiere la Regla 8ª de las Complementarias, para la interpretación y aplicación de la Tarifa de la Ley de los Impuestos Generales de Importación y de Exportación (TIGIE), conforme a los criterios que establezca la Secretaría de Economía, clasificadas en las fracciones arancelarias cuya unidad de medida de la TIGIE se indique como Pza y listadas en el Anexo II, Apartado D del Decreto para el Fomento de la Industria Manufacturera, Maquiladora y de', '6', 'Pza', '0.00', '0.00') - ,('98020035', '9802.00.35', 'Mercancías al amparo de la autorización a que se refiere la Regla 8ª de las Complementarias, para la interpretación y aplicación de la Tarifa de la Ley de los Impuestos Generales de Importación y de Exportación (TIGIE), conforme a los criterios que establezca la Secretaría de Economía, clasificadas en las fracciones arancelarias cuya unidad de medida de la TIGIE se indique como G y listadas en el Anexo II, Apartado E del Decreto para el Fomento de la Industria Manufacturera, Maquiladora y de Se', '2', 'Gr', '0.00', '0.00') - ,('98020036', '9802.00.36', 'Mercancías al amparo de la autorización a que se refiere la Regla 8ª de las Complementarias, para la interpretación y aplicación de la Tarifa de la Ley de los Impuestos Generales de Importación y de Exportación (TIGIE), conforme a los criterios que establezca la Secretaría de Economía, clasificadas en las fracciones arancelarias cuya unidad de medida de la TIGIE se indique como Kg y listadas en el Anexo II, Apartado E del Decreto para el Fomento de la Industria Manufacturera, Maquiladora y de S', '1', 'Kg', '0.00', '0.00') - ,('98020037', '9802.00.37', 'Mercancías al amparo de la autorización a que se refiere la Regla 8ª de las Complementarias, para la interpretación y aplicación de la Tarifa de la Ley de los Impuestos Generales de Importación y de Exportación (TIGIE), conforme a los criterios que establezca la Secretaría de Economía, clasificadas en las fracciones arancelarias cuya unidad de medida de la TIGIE se indique como Kg y listadas en el Anexo II, Apartado F del Decreto para el Fomento de la Industria Manufacturera, Maquiladora y de S', '1', 'Kg', '0.00', '0.00') - ,('98020038', '9802.00.38', 'Mercancías al amparo de la autorización a que se refiere la Regla 8ª de las Complementarias, para la interpretación y aplicación de la Tarifa de la Ley de los Impuestos Generales de Importación y de Exportación (TIGIE), conforme a los criterios que establezca la Secretaría de Economía, clasificadas en la partida arancelaria 40.04.', '1', 'Kg', '0.00', '0.00') - ,('98030001', '9803.00.01', 'Partes y componentes para el ensamble de automóviles cuando se trate de empresas de la industria automotriz terminal que cuenten con autorización de depósito fiscal para ensamble y fabricación de vehículos conforme a la Ley Aduanera, siempre que se ajusten a los lineamientos que establezca la Secretaría de Economía.', '1', 'Kg', '10.00', '0.00') - ,('98030002', '9803.00.02', 'Partes y componentes para el ensamble de camiones, autobuses integrales y tracto-camiones cuando se trate de empresas de la industria automotriz terminal que cuenten con autorización de depósito fiscal para ensamble y fabricación de vehículos conforme a la Ley Aduanera, siempre que se ajusten a los lineamientos que establezca la Secretaría de Economía.', '1', 'Kg', '10.00', '0.00') - ,('98040001', '9804.00.01', 'Menajes de casa.', '1', 'Kg', '0.00', '0.00') - ,('98040099', '9804.00.99', 'Los demás.', '1', 'Kg', '0.00', '0.00') - ,('98050001', '9805.00.01', 'Dispositivos excluidores de tortugas marinas y sus partes, para redes de arrastre camaroneras.', '6', 'Pza', '0.00', '0.00') - ,('98060001', '9806.00.01', 'Mercancías importadas por un organismo de certificación en cantidad no mayor a 3 unidades, o el número de muestras que determine la Norma Mexicana NMX-Z12 o, en su caso, el número de muestras determinado por la Norma Oficial Mexicana correspondiente, destinadas exclusivamente a obtener la certificación del cumplimiento de una Norma Oficial Mexicana.', '6', 'Pza', '0.00', '0.00') - ,('98060002', '9806.00.02', 'Equipos anticontaminantes y sus partes, cuando las empresas se ajusten a los lineamientos establecidos por las Secretarías de Medio Ambiente y Recursos Naturales, y de Economía.', '6', 'Pza', '0.00', '0.00') - ,('98060003', '9806.00.03', 'Maquinaria, equipo, instrumentos, materiales, animales, plantas y demás artículos para investigación o desarrollos tecnológicos, cuando los centros públicos de investigación, universidades públicas y privadas, instituciones de investigación científica y tecnológica, personas físicas y morales, inscritos en el Registro Nacional de Instituciones y Empresas Científicas y Tecnológicas, se sujeten a los lineamientos establecidos por la Secretaría de Economía y el Consejo Nacional de Ciencia y Tecnol', '1', 'Kg', '0.00', '0.00') - ,('98060004', '9806.00.04', 'Equipos y artículos reconocibles como destinados exclusivamente a compensar una disfunción corporal de personas con discapacidad, excepto lo comprendido en las fracciones arancelarias 8713.10.01 y 8713.90.99, y excepto los vehículos del Capítulo 87.', '6', 'Pza', '0.00', '0.00') - ,('98060005', '9806.00.05', 'Mercancías destinadas a la reparación o mantenimiento de naves aéreas o aeropartes.', '1', 'Kg', '0.00', '0.00') - ,('98060006', '9806.00.06', 'Mercancías, insumos, partes y componentes para el submontaje, montaje o producción de aeronaves o aeropartes destinadas a usos en materia de aviación civil, cuando las empresas cuenten con la aprobación de producción de productos aeronáuticos y sus artículos, en cualquiera de sus tipos, emitida por la Secretaría de Infraestructura, Comunicaciones y Transportes a través de la Agencia Federal de Aviación Civil.', '1', 'Kg', '0.00', '0.00') - ,('98060007', '9806.00.07', 'Archivos (expedientes) conteniendo, entre otros, diagnósticos médicos, radiografías, fluorangiografías, resultados de laboratorio, electrocardiogramas.', '1', 'Kg', '0.00', '0.00') - ,('98060008', '9806.00.08', 'Mercancías destinadas a procesos tales como reparación, reacondicionamiento o remanufactura, cuando las empresas cuenten con registro otorgado conforme a los lineamientos establecidos por la Secretaría de Economía.', '1', 'Kg', '0.00', '0.00') - ,('98060009', '9806.00.09', 'Insumos, materias primas, materiales auxiliares, envases y empaques que no estén sujetos al cumplimiento de regulaciones y restricciones no arancelarias en materia de sanidad animal y vegetal, salud pública, medio ambiente y seguridad nacional, que se destinen al régimen de recinto fiscalizado estratégico, por parte de las personas autorizadas para operar el régimen. Las mercancías objeto de almacenaje, consolidación de carga, así como las resultantes de los procesos de elaboración, transformac', '1', 'Kg', '0.00', '0.00') - ,('98060010', '9806.00.10', 'Maquinaria y equipo, que no estén sujetas al cumplimiento de regulaciones y restricciones no arancelarias en materia de sanidad animal y vegetal, salud pública, medio ambiente y seguridad nacional, que se destinen al régimen de recinto fiscalizado estratégico, por parte de personas autorizadas para operar el régimen. Cuando las mercancías se extraigan del recinto deberán ser clasificadas en términos de la Tarifa de esta Ley y cumplir, en su caso, con las regulaciones y restricciones no arancela', '1', 'Kg', '0.00', '0.00') - ,('98070001', '9807.00.01', 'Mercancías producidas en territorio nacional que retornen en el mismo estado en que fueron exportadas o retornadas del territorio nacional, de conformidad con los lineamientos establecidos por la Secretaría de Economía.', '1', 'Kg', '0.00', '0.00') - ,('99010001', '9901.00.01', 'Importación de mercancías realizadas por empresas de mensajería y paquetería, cuando la unidad de medida de la mercancía corresponda a piezas.', '6', 'Pza', '0.00', '0.00') - ,('99010002', '9901.00.02', 'Importación de mercancías realizadas por empresas de mensajería y paqueteria, cuando la unidad de medida de la mercancía corresponda a kilos.', '1', 'Kg', '0.00', '0.00') - ,('99010004', '9901.00.04', 'Mercancías de operaciones especiales a la importación.', '1', 'Kg', '0.00', '0.00') - ,('99010005', '9901.00.05', 'Importación de mercancías realizadas por empresas de mensajería y paqueteria, cuando la unidad de medida de la mercancía corresponda a litros.', '8', 'Lt', '0.00', '0.00') - ,('99010006', '9901.00.06', 'Importación o Exportación de mercancías por via postal, cuyo valor en aduana no exceda de 1,000 dólares o su equivalente en moneda nacional o extranjera.', '1', 'Kg', '16.00', '0.00') - ,('99010011', '9901.00.11', 'Bebidas con contenido alcohólico y cerveza con una graduación alcohólica de hasta 14° G.L.', '8', 'Lt', '77.26', '0.00') - ,('99010012', '9901.00.12', 'Bebidas con contenido alcohólico y cerveza con graduación alcohólica de más de 14° G.L. y hasta 20° G.L.', '8', 'Lt', '82.17', '0.00') - ,('99010013', '9901.00.13', 'Bebidas con contenido alcohólico y cerveza con una graduación alcohólica de más de 20° G.L., alcohol y alcohol desnaturalizado.', '8', 'Lt', '114.40', '0.00') - ,('99010015', '9901.00.15', 'Cigarros.', '6', 'Pza', '573.48', '0.00') - ,('99010016', '9901.00.16', 'Puros y tabacos labrados.', '6', 'Pza', '373.56', '0.00') - ,('99010017', '9901.00.17', 'Calzado, artículos de talabartería, peletería artificial.', '6', 'Pza', '51.73', '0.00') - ,('99010018', '9901.00.18', 'Prendas y accesorios de vestir y demás artículos textiles confeccionados.', '6', 'Pza', '51.73', '0.00') - ,('99020001', '9902.00.01', 'Exportación de mercancías realizadas por empresas de mensajería y paqueteria', '6', 'Pza', '0.00', '0.00') - ,('99999999', '9999.99.99', 'GLOBAL COMPLEMENTARIO', '1', 'Kg', '0.00', '0.00') -] diff --git a/backend/api/v1/modules/a76/general_catalogs/fractions/tariff_fractions/service.py b/backend/api/v1/modules/a76/general_catalogs/fractions/tariff_fractions/service.py deleted file mode 100644 index 80c95b2d..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/fractions/tariff_fractions/service.py +++ /dev/null @@ -1,412 +0,0 @@ -""" -Service para fracciones arancelarias -Catálogo de referencia global (no tenant-scoped) -""" - -from typing import List, Optional, Tuple, Dict, Any -from sqlalchemy.orm import Session -from sqlalchemy.exc import IntegrityError -from fastapi import HTTPException -import zlib -import logging -import re - -from .models import TariffFraction -from .dto import TariffFractionCreateDTO, TariffFractionUpdateDTO -from api.v1.modules.sitar.fracciones.service import FraccionesService -from api.v1.modules.sitar.fracciones.schemas import FraccionesResponse -from api.v1.modules.sitar.fracciones_usa.service import FraccionesUSAService -from api.v1.modules.sitar.fracciones_usa.schemas import FraccionesUSAResponse - -logger = logging.getLogger(__name__) - - -class TariffFractionMapper: - """Helper to map Sitar responses to Local domain objects""" - - @staticmethod - def _digits_only(value: Optional[str]) -> str: - return re.sub(r"\D", "", (value or "").strip()) - - @staticmethod - def _format_mx_fraction(code: str) -> str: - if code.isdigit() and len(code) == 8: - return f"{code[:2]}.{code[2:4]}.{code[4:6]}.{code[6:]}" - if code.isdigit() and len(code) == 6: - return f"{code[:2]}.{code[2:4]}.{code[4:]}" - return code - - @staticmethod - def _format_usa_fraction(code: str) -> str: - if code.isdigit() and len(code) == 10: - return f"{code[:4]}.{code[4:6]}.{code[6:8]}.{code[8:]}" - if code.isdigit() and len(code) == 8: - return f"{code[:4]}.{code[4:6]}.{code[6:]}" - return code - - @staticmethod - def _normalized_pair(raw_code: Optional[str], raw_fraction: Optional[str], formatter) -> Tuple[str, str]: - """Return (code_without_separators, formatted_fraction).""" - code = TariffFractionMapper._digits_only(raw_code) - fraction = (raw_fraction or "").strip() - if not code: - code = TariffFractionMapper._digits_only(fraction) - if not fraction: - fraction = formatter(code) - elif "." not in fraction and "-" not in fraction: - fraction = formatter(TariffFractionMapper._digits_only(fraction)) - if not fraction: - fraction = formatter(code) - return code, fraction - - @staticmethod - def to_domain(fraccion: FraccionesResponse) -> TariffFraction: - # Generate ID: Use SYSID if available, else composite hash of code + nico - if fraccion.SYSID: - fake_id = fraccion.SYSID - else: - # Composite key for uniqueness if SYSID missing - unique_str = f"{fraccion.FRACCION}-{fraccion.NICO}" - fake_id = zlib.crc32(unique_str.encode('utf-8')) - - code_val, fraction_val = TariffFractionMapper._normalized_pair( - fraccion.FRACCION, fraccion.FRACCIONPUNTO, TariffFractionMapper._format_mx_fraction - ) - description_val = fraccion.DESCRIPCION if fraccion.DESCRIPCION else "(Sin descripción)" - - tf = TariffFraction( - id=fake_id, - code=code_val, - fraction=fraction_val, - description=description_val, - nico=fraccion.NICO, - # MAP CHANGE: UMT now maps to Abbreviation (e.g., Pza, Kg) - umt=fraccion.UMABREVIACION, - adv_impo=fraccion.ADVIMPOTXT, - adv_expo=fraccion.ADVEXPOTXT - ) - # Dynamically attach non-model attributes for DTO - tf.dof = fraccion.DOF - tf.aplica_ieps = fraccion.APLICAIEPS - # MAP CHANGE: New field for the numeric code (e.g., 01, 06) - tf.um_code = fraccion.UMCLAVE - - return tf - - @staticmethod - def to_domain_usa(item: FraccionesUSAResponse) -> TariffFraction: - """Map US Fraction to Domain""" - code_val, fraction_val = TariffFractionMapper._normalized_pair( - item.FRACCION_SIN_PUNTO, - item.FRACCION_CON_PUNTO or item.FRACCION_MOSTRAR, - TariffFractionMapper._format_usa_fraction, - ) - return TariffFraction( - id=item.CONSECUTIVO, - code=code_val, - fraction=fraction_val, - description=item.DESCRIPCION or "(Sin descripción)", - nico=None, # Not applicable - umt=item.UNIDADCANTIDAD, - adv_impo=item.TARIFA1, - adv_expo=item.TARIFA2 - ) - - -class TariffFractionService: - """Service para gestionar fracciones arancelarias (catálogo global)""" - - - @staticmethod - def to_domain_usa_local(item: Any) -> TariffFraction: - """Map Local US Fraction (ORM) to Domain""" - # Formatter helper (simple logic: add dots every 2/4 chars? or just return as is?) - # US format: 1234.56.78.90. For now return as is or use helper if available. - # item is USTariffFraction (imported inside method to avoid circular import if needed, or assumed available) - - # Remove formatting (e.g. dots) for the 'code' property - code_str = str(item.code) - clean_code = code_str.replace(".", "").replace("-", "") - - return TariffFraction( - id=item.id, - code=clean_code, - fraction=code_str, - description=item.description or "(Sin descripción)", - nico=None, - umt=item.unit_of_measure, - adv_impo=str(item.ad_valorem) if item.ad_valorem is not None else None, - adv_expo=None - ) - - @staticmethod - async def _fetch_usa_catalog_from_sitar( - skip: int, - limit: int, - filters: Optional[Dict[str, Any]], - ) -> Tuple[List[TariffFraction], int]: - """SITAR fracciones-usa for catalog 'usa' and 'american' (read-only, same source).""" - try: - usa_service = FraccionesUSAService.get_instance() - except ValueError: - logger.warning("SITAR USA not configured (missing env)") - return [], 0 - - search_term = None - search_description = None - - if filters and filters.get("search"): - term = filters["search"] - clean_term = term.replace(".", "") - if clean_term.isdigit() and len(clean_term) >= 4: - search_term = term - else: - search_description = term - - try: - usa_items, total = await usa_service.search_with_total( - fraccion=search_term, - descripcion=search_description, - skip=skip, - limit=limit, - ) - items = [TariffFractionMapper.to_domain_usa(item) for item in usa_items] - return items, total - except Exception as e: - import traceback - - logger.error("Error fetching USA fractions from SITAR: %s", e) - logger.error(traceback.format_exc()) - return [], 0 - - @staticmethod - async def get_all( - db: Session, - skip: int = 0, - limit: int = 100, - filters: Optional[Dict[str, Any]] = None, - catalog: str = "mex", - tenant_id: Optional[int] = None, - company_id: Optional[int] = None, - ) -> Tuple[List[TariffFraction], int]: - """ - Obtiene fracciones arancelarias. - Estrategia: - - MEX: Sitar API (fracciones) - - USA / AMERICAN: Sitar API (fracciones-usa), solo lectura - """ - - if catalog in ("american", "usa"): - return await TariffFractionService._fetch_usa_catalog_from_sitar( - skip, limit, filters - ) - - # MEX (SITAR) CATALOG HANDLING - try: - sitar_service = FraccionesService.get_instance() - - # Map filters - sitar_fraccion = None - sitar_nico = None - sitar_description = None - - # Legacy parity: base query is always Nivel = 5 unless caller explicitly requests another level. - level_filter = 5 - if filters and filters.get("level") is not None: - level_filter = filters["level"] - # UI compatibility: level -1 means "sin filtro de nivel". - if level_filter == -1: - level_filter = None - - if filters: - if filters.get("search"): - term = str(filters["search"]).strip() - # Legacy-like behavior: - # - Numeric search targets fracción first. - # - Text search targets descripción. - clean_term = term.replace(".", "") - if clean_term.isdigit(): - sitar_fraccion = clean_term - else: - sitar_description = term - - if filters.get("code"): - sitar_fraccion = filters["code"] - if filters.get("fraction"): - sitar_fraccion = filters["fraction"] - if filters.get("nico"): - sitar_nico = filters["nico"] - if sitar_fraccion is not None: - sitar_fraccion = str(sitar_fraccion).replace(".", "").strip() - - # Note: Sitar search might not return total count. - # We fetch page items. Pagination might be tricky if Sitar doesn't return total. - # Assuming Sitar returns a list. - sitar_items, total = await sitar_service.search_with_total( - fraccion=sitar_fraccion, - nico=sitar_nico, - description=sitar_description, - nivel=level_filter, # Dynamic level - skip=skip, - limit=limit, - ) - - # STRICT API USAGE: - # We do NOT fallback to local DB on empty list, as user requested strict API consumption. - # We also do NOT attempt enrichment as codes mismatch (API uses '010191A' vs Local '01012101'). - - # Map items - items = [TariffFractionMapper.to_domain(item) for item in sitar_items] - # Legacy browse behavior: keep table in ascending fracción order. - items = sorted(items, key=lambda row: ((row.code or ""), (row.nico or ""))) - - # total comes from SITAR PaginatedFraccionesResponse (matches API-wide count for the query). - return items, total - - except Exception as e: - logger.error(f"Error fetching from Sitar API: {e}") - # STRICT API USAGE: Propagate error, do NOT fallback to local DB. - raise e - - @staticmethod - async def _get_all_local_async( - db: Session, - skip: int = 0, - limit: int = 100, - filters: Optional[Dict[str, Any]] = None, - ) -> Tuple[List[TariffFraction], int]: - """Lógica original de consulta local""" - query = db.query(TariffFraction) - - # Aplicar filtros - if filters: - # Si hay un filtro 'search', buscar en múltiples campos - if filters.get("search"): - search_term = f"%{filters['search']}%" - query = query.filter( - TariffFraction.code.ilike(search_term) | - TariffFraction.fraction.ilike(search_term) | - TariffFraction.description.ilike(search_term) | - TariffFraction.nico.ilike(search_term) | - TariffFraction.umt.ilike(search_term) - ) - else: - # Filtros individuales - if filters.get("code"): - query = query.filter(TariffFraction.code.ilike(f"%{filters['code']}%")) - if filters.get("fraction"): - query = query.filter(TariffFraction.fraction.ilike(f"%{filters['fraction']}%")) - if filters.get("description"): - query = query.filter(TariffFraction.description.ilike(f"%{filters['description']}%")) - if filters.get("nico"): - query = query.filter(TariffFraction.nico.ilike(f"%{filters['nico']}%")) - if filters.get("umt"): - query = query.filter(TariffFraction.umt.ilike(f"%{filters['umt']}%")) - - total = query.count() - items = query.offset(skip).limit(limit).all() - - return items, total - - @staticmethod - def get_by_id( - db: Session, - tariff_fraction_id: int, - ) -> Optional[TariffFraction]: - """ - Obtiene por ID. - Como Sitar no usa estos IDs, consultamos Local DB directamente para compatibilidad legacy. - Si se necesitara obtener detalle de Sitar, se requeriría otro identificador (Code). - """ - return ( - db.query(TariffFraction) - .filter(TariffFraction.id == tariff_fraction_id) - .first() - ) - - # WRITE OPERATIONS - DEPRECATED / LOCAL ONLY (Optional: Remove or Keep for Fallback Maintenance) - - @staticmethod - def get_by_code( - db: Session, - code: str, - ) -> Optional[TariffFraction]: - return ( - db.query(TariffFraction) - .filter(TariffFraction.code == code) - .first() - ) - - @staticmethod - def create( - db: Session, - tariff_fraction_data: TariffFractionCreateDTO, - ) -> TariffFraction: - try: - tariff_fraction = TariffFraction( - **tariff_fraction_data.model_dump(), - ) - db.add(tariff_fraction) - db.commit() - db.refresh(tariff_fraction) - return tariff_fraction - except IntegrityError as e: - db.rollback() - logger.error(f"Error creating tariff fraction: {e}") - raise HTTPException( - status_code=400, - detail="Tariff fraction with this code already exists", - ) - - @staticmethod - def update( - db: Session, - tariff_fraction_id: int, - tariff_fraction_data: TariffFractionUpdateDTO, - ) -> Optional[TariffFraction]: - tariff_fraction = TariffFractionService.get_by_id( - db, tariff_fraction_id - ) - - if not tariff_fraction: - return None - - try: - update_data = tariff_fraction_data.model_dump(exclude_unset=True) - for key, value in update_data.items(): - setattr(tariff_fraction, key, value) - - db.commit() - db.refresh(tariff_fraction) - return tariff_fraction - except IntegrityError as e: - db.rollback() - logger.error(f"Error updating tariff fraction: {e}") - raise HTTPException( - status_code=400, - detail="Error updating tariff fraction", - ) - - @staticmethod - def delete( - db: Session, - tariff_fraction_id: int, - ) -> bool: - tariff_fraction = TariffFractionService.get_by_id( - db, tariff_fraction_id - ) - - if not tariff_fraction: - return False - - try: - db.delete(tariff_fraction) - db.commit() - return True - except IntegrityError as e: - db.rollback() - logger.error(f"Error deleting tariff fraction: {e}") - raise HTTPException( - status_code=400, - detail="Cannot delete tariff fraction - may be in use", - ) diff --git a/backend/api/v1/modules/a76/general_catalogs/fractions/us_tariff_fractions/__init__.py b/backend/api/v1/modules/a76/general_catalogs/fractions/us_tariff_fractions/__init__.py deleted file mode 100644 index 7f9cc07e..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/fractions/us_tariff_fractions/__init__.py +++ /dev/null @@ -1,8 +0,0 @@ -""" -US Tariff Fractions Catalog Module -""" - -from .models import USTariffFraction -from .routes import router - -__all__ = ["USTariffFraction", "router"] diff --git a/backend/api/v1/modules/a76/general_catalogs/fractions/us_tariff_fractions/dto.py b/backend/api/v1/modules/a76/general_catalogs/fractions/us_tariff_fractions/dto.py deleted file mode 100644 index b30d2d59..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/fractions/us_tariff_fractions/dto.py +++ /dev/null @@ -1,94 +0,0 @@ -""" -DTOs para fracciones arancelarias americanas -""" - -from datetime import datetime -from typing import Optional, Any - -from pydantic import BaseModel, Field, ConfigDict, model_validator -import re - - -class USTariffFractionCreateDTO(BaseModel): - """DTO para crear fracción arancelaria americana""" - - code: str = Field(..., max_length=16, description="Código de fracción americana") - prefix: Optional[str] = Field(None, max_length=10, description="Prefijo") - type_code: Optional[str] = Field(None, max_length=10, description="Código de tipo") - ad_valorem: Optional[float] = Field(None, description="Porcentaje ad valorem") - fixed_cost: Optional[float] = Field(None, description="Tasa fija") - unit_of_measure: Optional[str] = Field(None, max_length=10, description="Unidad de medida") - description: Optional[str] = Field(None, description="Descripción") - - -class USTariffFractionUpdateDTO(BaseModel): - """DTO para actualizar fracción arancelaria americana""" - - code: Optional[str] = Field(None, max_length=16) - prefix: Optional[str] = Field(None, max_length=10) - type_code: Optional[str] = Field(None, max_length=10) - ad_valorem: Optional[float] = None - fixed_cost: Optional[float] = None - unit_of_measure: Optional[str] = Field(None, max_length=10) - description: Optional[str] = None - - -class USTariffFractionResponseDTO(BaseModel): - """DTO para respuesta de fracción arancelaria americana""" - - model_config = ConfigDict(from_attributes=True) - - id: int - code: str - fraction: Optional[str] = None - prefix: Optional[str] = None - type_code: Optional[str] = None - ad_valorem: Optional[float] = None - fixed_cost: Optional[float] = None - unit_of_measure: Optional[str] = None - description: Optional[str] = None - created_at: datetime - updated_at: datetime - - @model_validator(mode="before") - @classmethod - def format_code_and_fraction(cls, data: Any) -> Any: - # Check if data is an ORM model or dict - if hasattr(data, "code"): - raw_code = data.code - elif isinstance(data, dict): - raw_code = data.get("code") - else: - return data - - if raw_code: - code_str = str(raw_code) - fraction_raw = "" - if isinstance(data, dict): - fraction_raw = str(data.get("fraction") or "") - else: - fraction_raw = str(getattr(data, "fraction", "") or "") - - code = re.sub(r"[.\s-]", "", code_str) - fraction = fraction_raw.strip() or code_str - if "." not in fraction and "-" not in fraction: - only_digits = re.sub(r"[.\s-]", "", fraction) - if len(only_digits) == 10: - fraction = f"{only_digits[:4]}.{only_digits[4:6]}.{only_digits[6:8]}.{only_digits[8:]}" - elif len(only_digits) == 8: - fraction = f"{only_digits[:4]}.{only_digits[4:6]}.{only_digits[6:]}" - - if isinstance(data, dict): - data["code"] = code - data["fraction"] = fraction - else: - # If it's an ORM object, we can't easily modify the object's attribute - # cleanly without side effects for other things, so we convert it to dict - new_data = { - c.name: getattr(data, c.name) for c in data.__table__.columns - } - new_data["code"] = code - new_data["fraction"] = fraction - return new_data - - return data diff --git a/backend/api/v1/modules/a76/general_catalogs/fractions/us_tariff_fractions/models.py b/backend/api/v1/modules/a76/general_catalogs/fractions/us_tariff_fractions/models.py deleted file mode 100644 index cc9097c4..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/fractions/us_tariff_fractions/models.py +++ /dev/null @@ -1,48 +0,0 @@ -""" -Modelos para fracciones arancelarias americanas -""" - -from datetime import datetime -from typing import Optional - -from sqlalchemy import String, Numeric, TIMESTAMP, func -from sqlalchemy.orm import Mapped, mapped_column - -from core.database import Base -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin - - -class USTariffFraction(Base, TenantScopedMixin, TimestampMixin): - """Modelo para fracciones arancelarias americanas (US HTS codes)""" - - __tablename__ = "us_tariff_fractions" - __table_args__ = {"schema": "a76"} - - # Primary Key - id: Mapped[int] = mapped_column(primary_key=True, index=True) - - # Datos principales - code: Mapped[str] = mapped_column( - String(16), nullable=False, comment="Código de fracción americana" - ) - prefix: Mapped[Optional[str]] = mapped_column( - String(10), comment="Prefijo de clasificación" - ) - type_code: Mapped[Optional[str]] = mapped_column( - String(10), comment="Código de tipo" - ) - ad_valorem: Mapped[Optional[float]] = mapped_column( - Numeric(10, 2), comment="Porcentaje ad valorem" - ) - fixed_cost: Mapped[Optional[float]] = mapped_column( - Numeric(15, 8), comment="Tasa fija" - ) - unit_of_measure: Mapped[Optional[str]] = mapped_column( - String(10), comment="Unidad de medida" - ) - description: Mapped[Optional[str]] = mapped_column( - String, comment="Descripción de la fracción" - ) - - def __repr__(self): - return f"" diff --git a/backend/api/v1/modules/a76/general_catalogs/fractions/us_tariff_fractions/routes.py b/backend/api/v1/modules/a76/general_catalogs/fractions/us_tariff_fractions/routes.py deleted file mode 100644 index b5130e55..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/fractions/us_tariff_fractions/routes.py +++ /dev/null @@ -1,159 +0,0 @@ -""" -Endpoints API para fracciones arancelarias americanas (solo lectura; fuente SITAR). -""" - -from datetime import datetime, timezone -from typing import Any, Dict, Optional - -from fastapi import APIRouter, Depends, HTTPException, Query, status -from sqlalchemy.orm import Session - -from core.database import get_core_db -from core.security import get_current_user, validate_access_to_resource - -from api.v1.modules.sitar.fracciones_usa.catalog_resolve import ( - american_fraction_ad_valorem_from_row, - canonical_code_from_sitar_row, -) -from api.v1.modules.sitar.fracciones_usa.schemas import FraccionesUSAResponse -from api.v1.modules.sitar.fracciones_usa.service import FraccionesUSAService - -from .dto import USTariffFractionResponseDTO - - -def _sitar_row_to_us_response_payload(item: FraccionesUSAResponse) -> dict: - """Build payload for USTariffFractionResponseDTO.model_validate (SITAR row).""" - canon = canonical_code_from_sitar_row(item) - now = datetime.now(timezone.utc) - return { - "id": item.CONSECUTIVO, - "code": canon, - "fraction": item.FRACCION_CON_PUNTO or item.FRACCION_MOSTRAR or canon, - "prefix": item.FRACCION_SIN_PUNTO, - "type_code": str(item.NIVEL) if item.NIVEL is not None else None, - "ad_valorem": american_fraction_ad_valorem_from_row(item), - "fixed_cost": None, - "unit_of_measure": item.UNIDADCANTIDAD, - "description": item.DESCRIPCION, - "created_at": now, - "updated_at": now, - } - - -main_router = APIRouter( - prefix="/us-tariff-fractions", - tags=["a76 / general catalogs / us tariff fractions"], -) - - -@main_router.api_route( - "/imports", - methods=["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"], - include_in_schema=False, -) -@main_router.api_route( - "/imports/{path:path}", - methods=["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"], - include_in_schema=False, -) -async def us_tariff_imports_disabled(path: str = ""): - raise HTTPException( - status_code=status.HTTP_410_GONE, - detail="CSV import for US tariff fractions is disabled; catalog is read-only from SITAR.", - ) - - -@main_router.get( - "/", - response_model=Dict[str, Any], - summary="List US Tariff Fractions", - description="Paginated list from SITAR fracciones-usa (read-only).", -) -async def list_us_tariff_fractions( - company_id: int = Query(..., description="Company ID"), - page: int = Query(1, ge=1, description="Page number"), - page_size: int = Query(50, ge=1, le=10000, description="Page size"), - search: Optional[str] = Query(None, description="Search in code or description"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - tenant_id = validate_access_to_resource( - db, company_id, current_user, ["frac_sitar_us.view"] - ) - - skip = (page - 1) * page_size - try: - svc = FraccionesUSAService.get_instance() - except ValueError: - return { - "items": [], - "total": 0, - "page": page, - "page_size": page_size, - "pages": 0, - } - - search_term = None - search_description = None - if search: - clean = search.replace(".", "") - if clean.isdigit() and len(clean) >= 4: - search_term = search - else: - search_description = search - - try: - sitar_items, total = await svc.search_with_total( - fraccion=search_term, - descripcion=search_description, - skip=skip, - limit=page_size, - ) - except Exception: - return { - "items": [], - "total": 0, - "page": page, - "page_size": page_size, - "pages": 0, - } - - items = [ - USTariffFractionResponseDTO.model_validate(_sitar_row_to_us_response_payload(row)) - for row in sitar_items - ] - - return { - "items": items, - "total": total, - "page": page, - "page_size": page_size, - "pages": (total + page_size - 1) // page_size, - } - - -@main_router.post("/") -async def create_us_tariff_fraction_disabled(): - raise HTTPException( - status_code=status.HTTP_405_METHOD_NOT_ALLOWED, - detail="US tariff fractions are read-only (SITAR).", - ) - - -@main_router.put("/{fraction_id}") -async def update_us_tariff_fraction_disabled(fraction_id: int): - raise HTTPException( - status_code=status.HTTP_405_METHOD_NOT_ALLOWED, - detail="US tariff fractions are read-only (SITAR).", - ) - - -@main_router.delete("/{fraction_id}") -async def delete_us_tariff_fraction_disabled(fraction_id: int): - raise HTTPException( - status_code=status.HTTP_405_METHOD_NOT_ALLOWED, - detail="US tariff fractions are read-only (SITAR).", - ) - - -router = main_router diff --git a/backend/api/v1/modules/a76/general_catalogs/fractions/us_tariff_fractions/seed.py b/backend/api/v1/modules/a76/general_catalogs/fractions/us_tariff_fractions/seed.py deleted file mode 100644 index 1561288a..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/fractions/us_tariff_fractions/seed.py +++ /dev/null @@ -1,417 +0,0 @@ -""" -Datos semilla para Fracciones Arancelarias de Estados Unidos - -Catálogo de fracciones HTS/Schedule B para importaciones/exportaciones con EE.UU. -Total: 406 registros - -Estructura: (code, prefix, type_code, ad_valorem, fixed_cost, unit_of_measure, description) -""" - -seed = [ - ('0902300090', '', 'PO', '0.00', '0.00000000', 'PZA', 'BOLSA DE TE') - ,('2508400150', '', 'PO', '0.00', '0.00000000', '', '') - ,('2520200000', '', 'PO', '0.00', '0.00000000', '', '') - ,('2525200000', '', 'PO', '0.00', '0.00000000', 'KGS', 'PITMENT BASED ON TITANIUM DIOXIDE') - ,('2526200000', '', 'PO', '0.00', '0.00000000', '', '') - ,('2707999090', '', 'PO', '0.00', '0.00000000', '', '') - ,('2710121550', '', 'PO', '0.00', '0.00000000', '', '') - ,('2710129000', '', 'PO', '0.00', '0.00000000', 'LT', 'MOLD RELEASE') - ,('2710129050', '', 'PO', '0.00', '0.00000000', 'LT', '') - ,('2710190650', '', 'PO', '0.00', '0.00000000', 'LT', 'MOLD RELEASE') - ,('2710199000', '', 'PO', '0.00', '0.00000000', '', '') - ,('2712902000', '', 'PO', '0.00', '0.00000000', '', '') - ,('2839905000', '', 'PO', '0.00', '0.00000000', '', '') - ,('2905120050', '', 'PO', '0.00', '0.00000000', '', '') - ,('2905145050', '', 'PO', '0.00', '0.00000000', '', '') - ,('2909430000', '', 'PO', '0.00', '0.00000000', '', '') - ,('2909496000', '', 'PO', '0.00', '0.00000000', '', '') - ,('2914115000', '', 'PO', '0.00', '0.00000000', 'LT', 'BUTYL ACETATE') - ,('2914120000', '', 'PO', '0.00', '0.00000000', '', '') - ,('2915905050', '', 'PO', '0.00', '0.00000000', '', '') - ,('2924293600', '', 'PO', '0.00', '0.00000000', '', '') - ,('2929108090', '', 'PO', '0.00', '0.00000000', 'KGS', 'Urethane, Iso Side') - ,('3206110000', '', 'PO', '0.00', '0.00000000', '', '') - ,('3206190000', '', 'PO', '0.00', '0.00000000', '', '') - ,('3206495060', '', 'PO', '0.00', '0.00000000', 'KGS', 'RESIN') - ,('3206496050', '', 'PO', '0.00', '0.00000000', '', 'PIGMENTO COLORANTE') - ,('3208100000', '', 'PO', '0.00', '0.00000000', '', '') - ,('3208200000', '', 'PO', '0.00', '0.00000000', 'LT', 'LACA ALQUIDICA NITROCELULOSA') - ,('3208900000', '', 'PO', '0.00', '0.00000000', '', 'PAINT') - ,('3209100000', '', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('3209900000', 'S', 'PO', '0.00', '0.00000000', 'LT', 'LIQUID PAINT') - ,('3212900050', '', 'PO', '0.00', '0.00000000', '', '') - ,('3214100020', '', 'PO', '0.00', '0.00000000', '', '') - ,('3215905000', '', 'PO', '0.00', '0.00000000', '', '') - ,('3402205100', '', 'PO', '0.00', '0.00000000', '', '') - ,('3402905050', '', 'PO', '0.00', '0.00000000', '', '') - ,('3403195000', '', 'PO', '0.00', '0.00000000', 'LT', 'Mold Release, Stoner, GL') - ,('3403990000', '', 'PO', '0.00', '0.00000000', '', 'PASTE FOR POLISHING METALS') - ,('3404905150', '', 'PO', '0.00', '0.00000000', '', '') - ,('3405400000', '', 'PO', '0.00', '0.00000000', '', '') - ,('3405900000', '', 'PO', '0.00', '0.00000000', '', '') - ,('3506105000', 'US', '', '0.00', '0.00000000', 'PZA', 'ADHESIVO EN AEROSOL') - ,('3506915000', '', 'PO', '0.00', '0.00000000', 'LT', 'Adhesive') - ,('3506990000', '', 'PO', '0.00', '0.00000000', 'LT', 'Adhesive') - ,('3802.20.00.00', '', 'PO', '0.00', '0.00000000', 'KGS', '') - ,('3811900000', '', 'PO', '0.00', '0.00000000', '', '') - ,('3814005090', '', 'PO', '0.00', '0.00000000', 'LT', 'THINNER') - ,('3815901000', '', 'PO', '0.00', '0.00000000', '', '') - ,('3815903000', '', 'PO', '0.00', '0.00000000', '', '') - ,('3815905000', '', 'PO', '0.00', '0.00000000', 'PZA', 'CATALYST') - ,('3820000000', '', 'PO', '0.00', '0.00000000', '', '') - ,('3824910000', '', 'PO', '0.00', '0.00000000', 'LT', 'LIQUIDO PARA PAVONAR METAL A BASE DE ACIDOS') - ,('3824991900', '', 'PO', '0.00', '0.00000000', '', '') - ,('3824999297', '', 'PO', '0.00', '0.00000000', 'PZA', 'WELDING ANTI SPLATTER') - ,('3825900000', '', 'PO', '0.00', '0.00000000', '', '') - ,('3903190000', '', 'PO', '0.00', '0.00000000', 'KGS', '') - ,('3903905000', '', 'PO', '0.00', '0.00000000', '', '') - ,('3905300000', '', 'PO', '0.00', '0.00000000', '', '') - ,('3906905050', 'US', '', '0.00', '0.00000000', 'PZA', 'POLIMERO ACRILICO') - ,('3907200000', '', 'PO', '0.00', '0.00000000', 'KGS', 'POLYURETHANE BASED RESIN') - ,('3907210000', '', 'PO', '0.00', '0.00000000', 'PZA', 'POLYURETHANE-BASED RESIN') - ,('3907300000', 'US', '', '0.00', '0.00000000', 'KGS', 'RESINA EPOXICA A Y B') - ,('3907915000', '', 'PO', '0.00', '0.00000000', 'KGS', 'resin') - ,('3907995050', '', 'PO', '0.00', '0.00000000', '', '') - ,('3909100000', '', 'PO', '0.00', '0.00000000', 'KGS', 'GRANULATED PLASTIC (PLASTIC RESIN)') - ,('3909310000', '', 'PO', '0.00', '0.00000000', 'KGS', 'ISOCIANATE') - ,('3909390000', '', 'PO', '0.00', '0.00000000', '', '') - ,('3909505000', '', 'PO', '0.00', '0.00000000', 'LT', 'POLYURETHANE ELASTOMER PART A AND B') - ,('3909506000', '', 'PO', '0.00', '0.00000000', 'KGS', '') - ,('3909900000', '', 'PO', '0.00', '0.00000000', '', '') - ,('3910000000', '', 'PO', '0.00', '0.00000000', 'LT', 'SEALER CONDITIONER PART B') - ,('3911902500', '', 'PO', '0.00', '0.00000000', '', '') - ,('3911909050', '', 'PO', '0.00', '0.00000000', '', '') - ,('3915900090', '', 'PO', '0.00', '0.00000000', 'KGS', 'DESPERDICIO DE PLASTICO') - ,('3917230000', '', 'PO', '0.00', '0.00000000', 'PZA', 'PLASTIC HOSE') - ,('3917320050', '', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('39173299', '', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('3917330000', '', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('39173399', '', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('3917390050', '', 'PO', '0.00', '0.00000000', 'PZA', 'PLASTIC TUBE') - ,('3919101050', 'US', '', '0.00', '0.00000000', 'PZA', 'CINTA ADHESIVA') - ,('3919102055', 'US', '', '0.00', '0.00000000', 'PZA', 'CINTA ADHESIVA (PLASTICA)') - ,('3919905060', '', '', '0.00', '0.00000000', 'PZA', 'CINTA ADHESIVA (PLASTICO TRANSPARENTE)') - ,('3920100000', '', 'PO', '0.00', '0.00000000', 'PZA', 'burbuja') - ,('3920200055', '', 'PO', '0.00', '0.00000000', '', '') - ,('3920515000', '', 'PO', '0.00', '0.00000000', 'PZA', 'ACRYLIC POLYMER SHEET') - ,('3921135000', '', 'PO', '0.00', '0.00000000', '', '') - ,('3921905050', 'S', '', '0.00', '0.00000000', 'PZA', 'PELICULA DE PLASTICO PARA FLEJAR') - ,('3923109000', '', 'PO', '0.00', '0.00000000', 'PZA', 'CAJA DE PLASTICO') - ,('3923210095', '', 'PO', '0.00', '0.00000000', '', '') - ,('3923290000', '', 'PO', '0.00', '0.00000000', 'PZA', 'Bag') - ,('3923300090', '', 'PO', '0.00', '0.00000000', '', '') - ,('3923500000', '', 'PO', '0.00', '0.00000000', '', '') - ,('3923900080', 'S', 'PO', '0.00', '0.00000000', 'PZA', 'Plastic container') - ,('3926209050', '', 'PO', '0.00', '0.00000000', '', '') - ,('3926400090', 'S', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('3926902100', 'US', '', '0.00', '0.00000000', 'PZA', 'PROTECTORES DE PLASTICO P/OIDO') - ,('3926903500', 'S', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('3926909985', 'S', 'PO', '0.00', '0.00000000', 'PZA', 'PLASTIC') - ,('3926909987', 'S', 'PO', '0.00', '0.00000000', '', '') - ,('3926909989', 'S', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('3926909990', '', 'PO', '0.00', '0.00000000', 'PZA', 'PLACTIC BASE') - ,('3926909995', 'S', 'PO', '0.00', '0.00000000', '', 'PLASTIC DOME') - ,('3926909996', '', 'PO', '0.00', '0.00000000', 'PZA', 'Foam Plug, Hosiery Leg') - ,('4015190002', 'US', '', '0.00', '0.00000000', 'PZA', 'GUANTES DE CAUCHO') - ,('4016930000', '', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('4016935050', '', 'PO', '0.00', '0.00000000', '', '') - ,('4016992000', '', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('4016993510', '', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('4016996000', '', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('4016996050', '', 'PO', '0.00', '0.00000000', 'PZA', 'BALL') - ,('4201003000', 'S', 'PO', '0.00', '0.00000000', 'PZA', 'Dog leashes, collars, muzzles, harnesses and similar dog equipment') - ,('4201006000', '', 'PO', '0.00', '0.00000000', '', '') - ,('4410190060', '', 'PO', '0.00', '0.00000000', '', '') - ,('4415109000', '', 'PO', '0.00', '0.00000000', '', '') - ,('4415208000', 'S', 'PO', '0.00', '0.00000000', 'PZA', 'WOOD PALLETS') - ,('4417004000', '', 'PO', '0.00', '0.00000000', '', 'Paint brush and paint roller handles') - ,('4421904000', '', 'PO', '5.10', '0.00000000', '', 'NECKCAP WOOD') - ,('4421909750', 'US', '', '0.00', '0.00000000', 'PZA', 'PALILLO DE MADERA') - ,('4421914000', '', 'PO', '0.00', '0.00000000', '', '') - ,('4421994000', '', 'PO', '0.00', '0.00000000', '', '') - ,('4421999880', '', 'PO', '0.00', '0.00000000', '', '') - ,('4503106000', '', 'PO', '0.00', '0.00000000', '', '') - ,('4802693000', '', 'PO', '0.00', '0.00000000', '', '') - ,('4804394000', '', 'PO', '0.00', '0.00000000', '', 'Wrapping paper') - ,('4804590000', 'S', 'PO', '0.00', '0.00000000', '', '') - ,('4805400000', '', 'PO', '0.00', '0.00000000', '', '') - ,('4808100000', 'S', 'PO', '0.00', '0.00000000', 'PZA', 'CORRUGATED CARTON SEPARATOR') - ,('4808906000', '', 'PO', '0.00', '0.00000000', '', '') - ,('4810137040', '', 'PO', '0.00', '0.00000000', 'PZA', 'ROLLO DE CABLE DE ACER') - ,('4811412100', '', 'PO', '0.00', '0.00000000', '', '') - ,('4817100000', '', 'PO', '0.00', '0.00000000', '', '') - ,('4818200020', 'US', '', '0.00', '0.00000000', 'PZA', 'TOALLAS DESECHABLES DE PAPEL TISSUE') - ,('4819100040', 'S', 'PO', '0.00', '0.00000000', 'PZA', 'BOX') - ,('4819504060', '', 'PO', '0.00', '0.00000000', '', '') - ,('4821104000', '', 'PO', '0.00', '0.00000000', 'PZA', 'LABEL') - ,('4821904000', '', 'PO', '0.00', '0.00000000', '', '') - ,('4822900000', 'S', 'PO', '0.00', '0.00000000', 'PZA', 'CARTON BOX') - ,('4823700040', '', 'PO', '0.00', '0.00000000', '', '') - ,('4823901000', '', 'PO', '0.00', '0.00000000', 'PZA', 'PAPER CUP') - ,('4823908600', '', 'PO', '0.00', '0.00000000', 'PZA', 'BASES DE CARTON PRENSADO') - ,('4823908850', 'US', '', '0.00', '0.00000000', 'PZA', 'CINTA ADHESIVA DE PAPEL') - ,('4901990091', '', 'PO', '0.00', '0.00000000', 'PZA', 'MANUALES') - ,('4911100080', '', 'PO', '0.00', '0.00000000', '', '') - ,('4911998000', '', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('5407619975', '', 'PO', '0.00', '0.00000000', '', '') - ,('5508200000', '', 'PO', '0.00', '0.00000000', '', '') - ,('5703308085', '', 'PO', '0.00', '0.00000000', 'PZA', 'PASTO SINTETICO (MUESTRAS)') - ,('5806310000', '', 'PO', '0.00', '0.00000000', 'MT', 'CINTA TEXTIL') - ,('5806322000', '', 'PO', '0.00', '0.00000000', 'MT', 'VELCRO') - ,('59039001', '', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('5903903090', '', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('5911102000', '', 'PO', '0.00', '0.00000000', 'MT', '') - ,('5911900040', '', 'PO', '0.00', '0.00000000', 'PZA', 'Cords, braids and the like of a kind used in industry as packing or lubricating material') - ,('5911900080', '', 'PO', '0.00', '0.00000000', 'MT', 'COVER FORM 3/4 FEMALE FOAM LINEN') - ,('6116100000', 'US', '', '0.00', '0.00000000', 'PZA', 'GUANTES DE ALGOHODON CON RECUBRIMIENTO DE NITRILIO') - ,('6116920000', 'US', '', '0.00', '0.00000000', 'PZA', 'GUANTES DE ALGOHODON') - ,('6116929400', '', 'PO', '0.00', '0.00000000', '', '') - ,('6306192120', '', 'PO', '0.00', '0.00000000', '', '') - ,('6307100000', 'US', '', '0.00', '0.00000000', 'PZA', 'FIBRA SINTETICA P/LIMPIAR') - ,('6307909089', 'S', 'PO', '0.00', '0.00000000', '', 'BAG') - ,('6307909889', '', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('6307909891', '', 'PO', '0.00', '0.00000000', 'PZA', 'bolsa de tela') - ,('6307909995', 'US', '', '0.00', '0.00000000', 'PZA', 'MASCARILLA DE PROTECCION CONTRA EL POLVO') - ,('6403919015', '', 'PO', '0.00', '0.00000000', '', '') - ,('6403999031', '', 'PO', '10.00', '0.00000000', 'PAR', 'LEATHER SHOES') - ,('6406109090', '', 'PO', '0.00', '0.00000000', 'PZA', 'HUARACHE') - ,('6506106075', '', 'PO', '0.00', '0.00000000', 'PZA', 'PROTECTION HELMET') - ,('6804220000', 'US', '', '0.00', '0.00000000', 'PZA', 'DISCO ABRASIVO CIRCULARES') - ,('6805100000', 'US', '', '0.00', '0.00000000', 'PZA', 'DISCO ABRASIVO EN ROLLO') - ,('6805100199', '', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('6805300000', 'US', '', '0.00', '0.00000000', 'PZA', 'LIJA CON SOPORTE DE PLASTICO CELULAR') - ,('6805305000', '', 'PO', '0.00', '0.00000000', '', '') - ,('7007190000', '', 'PO', '0.00', '0.00000000', 'PZA', 'TEMPERED GLASS BASE') - ,('7009921000', '', 'PO', '0.00', '0.00000000', 'PZA', 'ESPEJO ENMARCADO') - ,('7010905055', '', 'PO', '0.00', '0.00000000', '', '') - ,('7019905050', '', 'PO', '0.00', '0.00000000', '', 'FIBRA DE VIDRIO') - ,('7019905150', '', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('7020006000', '', 'PO', '0.00', '0.00000000', 'PZA', 'BASE DE VIDRIO') - ,('7020009990', '', 'PO', '0.00', '0.00000000', '', 'GLASS BOARD') - ,('7206900000', '', 'PO', '0.00', '0.00000000', 'PZA', 'TUBO') - ,('7215100080', '', 'PO', '0.00', '0.00000000', '', '') - ,('7215905000', '', 'PO', '0.00', '0.00000000', '', '') - ,('7226928050', '', 'PO', '0.00', '0.00000000', '', '') - ,('7228400000', '', 'PO', '0.00', '0.00000000', 'PZA', 'METAL BAR') - ,('7228608000', '', 'PO', '0.00', '0.00000000', '', '') - ,('7304598080', '', 'PO', '0.00', '0.00000000', '', '') - ,('7306200000', '', 'PO', '0.00', '0.00000000', '', '') - ,('7306305090', '', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('7306905000', '', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('7307225000', '', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('7307929000', '', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('7308909590', '', 'PO', '0.00', '0.00000000', 'PZA', 'RACK') - ,('7309000090', '', 'PO', '0.00', '0.00000000', 'PZA', 'TANQUE HERMETICO DE ACERO') - ,('7310100050', '', 'PO', '0.00', '0.00000000', '', '') - ,('7310290050', '', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('7312109090', '', 'PO', '0.00', '0.00000000', '', '') - ,('7315827000', '', 'PO', '0.00', '0.00000000', '', 'CHAIN') - ,('7317007500', '', 'PO', '0.00', '0.00000000', '', '') - ,('7318130060', '', 'PO', '0.00', '0.00000000', '', 'EYE HOOK') - ,('73181504', '', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('7318150400', '', 'PO', '0.00', '0.00000000', '', '') - ,('7318152000', '', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('7318158066', '', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('7318158085', '', '', '0.00', '0.00000000', 'PZA', 'TORNILLO DE ACERO CON TUERCA') - ,('7318158688', '', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('7318159000', 'S', 'PO', '0.00', '0.00000000', 'PZA', 'TORNILLO DE ACERO MARIPOSA') - ,('7318160085', '', 'PO', '0.00', '0.00000000', '', '') - ,('7318190000', '', 'PO', '0.00', '0.00000000', 'PZA', 'REMACHE') - ,('7318210090', '', 'PO', '0.00', '0.00000000', '', '') - ,('7318220000', '', 'PO', '0.00', '0.00000000', '', '') - ,('7318230000', '', 'PO', '0.00', '0.00000000', '', '') - ,('7318240000', '', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('7318290000', '', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('7319909000', '', 'PO', '0.00', '0.00000000', '', '') - ,('7320205060', '', 'PO', '0.00', '0.00000000', '', '') - ,('7321811000', '', 'PO', '0.00', '0.00000000', '', '') - ,('7325995000', '', 'PO', '0.00', '0.00000000', '', '') - ,('7326901000', '', 'PO', '0.00', '0.00000000', '', 'HOOK') - ,('7326908605', '', 'PO', '0.00', '0.00000000', '', '') - ,('7326908688', 'S', 'PO', '0.00', '0.00000000', 'PZA', 'METAL INSERTS') - ,('7326908695', '', 'PO', '0.00', '0.00000000', 'PZA', 'STEEL FASTENER') - ,('73269099', '', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('7412200090', '', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('74122001', '', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('7415100000', '', 'PO', '0.00', '0.00000000', '', '') - ,('7415390000', '', 'PO', '0.00', '0.00000000', '', '') - ,('7419995050', '', 'PO', '0.00', '0.00000000', '', '') - ,('7607196000', '', 'PO', '0.00', '0.00000000', 'PZA', 'aluminium') - ,('7609000000', '', 'PO', '0.00', '0.00000000', '', '') - ,('7616109090', '', 'PO', '0.00', '0.00000000', 'PZA', 'tachuela') - ,('7616995090', 'S', 'PO', '0.00', '0.00000000', '', 'SOPORTE DE ALUMINIO') - ,('7616995190', 'S', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('7618000000', '', 'PO', '0.00', '0.00000000', '', '') - ,('7806008000', '', 'PO', '0.00', '0.00000000', '', '') - ,('7907006000', '', 'PO', '0.00', '0.00000000', '', '') - ,('8021230000', '', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('8202200060', '', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('8203109000', '', 'PO', '0.00', '0.00000000', 'PZA', 'ESTUCHE DE LIMAS') - ,('8203208000', '', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('82032099', '', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('8204110060', '', 'PO', '0.00', '0.00000000', '', 'WRENCH') - ,('8205511500', '', 'PO', '0.00', '0.00000000', '', 'CEPILLO DE ALAMBRE') - ,('8205599000', 'US', '', '0.00', '0.00000000', 'PZA', 'DESPACHADOR DE CINTA MANUAL') - ,('8205700060', '', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('8205700090', '', 'PO', '0.00', '0.00000000', '', '') - ,('8205906000', '', 'PO', '0.00', '0.00000000', '', '') - ,('8207502055', '', 'PO', '0.00', '0.00000000', '', 'DRILL BIT') - ,('8207907585', '', 'PO', '0.00', '0.00000000', '', '') - ,('8208906000', '', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('8302200000', '', 'PO', '0.00', '0.00000000', '', 'RUEDAS PARA BASE DE MANIQUI') - ,('8302426000', '', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('8302498090', 'S', 'PO', '0.00', '0.00000000', 'PZA', 'Kit, Knob,Hand') - ,('8302500000', '', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('8309900000', '', 'PO', '0.00', '0.00000000', 'PZA', 'TAPA METALICA') - ,('8309900090', 'S', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('8310000000', '', 'PO', '0.00', '0.00000000', '', '') - ,('8413600090', '', 'PO', '0.00', '0.00000000', 'PZA', 'PUMPS OF MACHINE MOLDING') - ,('8413919060', '', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('8413919080', '', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('8413919096', '', 'PO', '0.00', '0.00000000', 'PZA', 'DIAPHRAGM FOR PUMP') - ,('8414.59.6595', '', 'PO', '0.00', '0.00000000', '', '') - ,('8414100000', '', 'PO', '0.00', '0.00000000', 'PZA', 'VACUM PUMP') - ,('8414510090', '', 'PO', '0.00', '0.00000000', '', '') - ,('8414519090', '', 'PO', '0.00', '0.00000000', 'PZA', 'FAN') - ,('8414596595', '', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('8414809000', '', 'PO', '0.00', '0.00000000', 'PZA', 'DUST COLLECTOR') - ,('8414909080', '', 'PO', '0.00', '0.00000000', 'PZA', 'DISPOSITIVO DE FILTRACCION PARA BOMBA NEUMATICA') - ,('8419390180', '', 'PO', '0.00', '0.00000000', '', '') - ,('8421210000', '', 'PO', '0.00', '0.00000000', 'PZA', 'FEEDER-DISASSEMBLY DISASSEMBLED') - ,('8421230000', '', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('8421290065', '', 'PO', '0.00', '0.00000000', 'PZA', 'FILTER') - ,('8421390115', '', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('8421390190', '', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('8421398015', '', 'PO', '0.00', '0.00000000', 'PZA', 'FILTER') - ,('8421398040', '', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('8421398090', '', 'PO', '0.00', '0.00000000', '', '') - ,('8421990180', '', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('8422309191', '', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('8422401190', '', 'PO', '0.00', '0.00000000', '', '') - ,('8422409180', '', 'PO', '0.00', '0.00000000', 'PZA', 'STRAPPING MACHINE') - ,('8424209000', '', 'PO', '0.00', '0.00000000', 'PZA', 'PISTOLA AEROGRAFICA') - ,('8424890000', '', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('8424900100', '', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('8424900500', '', 'PO', '0.00', '0.00000000', '', '') - ,('8424902000', '', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('8424909080', '', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('8425110000', '', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('8427108095', '', 'PO', '0.00', '0.00000000', 'PZA', 'Pallet jack') - ,('8427900000', '', 'PO', '0.00', '0.00000000', 'PZA', 'PALLET JACK') - ,('8456111050', '', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('8459290090', '', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('8461500090', '', 'PO', '0.00', '0.00000000', 'PZA', 'BELT SAW') - ,('8461508090', '', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('8465910091', '', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('8465930012', '', 'PO', '0.00', '0.00000000', '', '') - ,('8466306085', '', 'PO', '0.00', '0.00000000', 'PZA', 'CILINDRO NEUMATICO') - ,('8466925090', '', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('8467.19.5090', '', 'PO', '0.00', '0.00000000', '', '') - ,('8467111080', 'US', '', '0.00', '0.00000000', 'PZA', 'PULIDOR NEUMATICO MANUAL') - ,('8467195090', '', 'PO', '0.00', '0.00000000', '', '') - ,('8467210070', '', 'PO', '0.00', '0.00000000', '', 'ROTARY') - ,('8467220090', '', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('8467290090', '', 'PO', '0.00', '0.00000000', '', '') - ,('8467895090', '', 'PO', '0.00', '0.00000000', 'PZA', 'Pneumatic screwdriver') - ,('8467920050', '', 'PO', '0.00', '0.00000000', '', 'pulidor') - ,('8467920090', 'US', '', '0.00', '0.00000000', 'PZA', 'DISCO DE URETANO P/ PULIDOR NEUMATICO') - ,('8471410150', '', 'PO', '0.00', '0.00000000', '', '') - ,('8471500150', '', 'PO', '0.00', '0.00000000', '', 'CPU') - ,('8471608000', '', 'PO', '0.00', '0.00000000', '', 'SCANNER') - ,('8471609050', '', 'PO', '0.00', '0.00000000', 'PZA', 'SCANNER') - ,('8477590100', '', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('8477800000', '', 'PO', '0.00', '0.00000000', 'PZA', 'MACHINE FOR FILLING AIR BAGS') - ,('8477800100', '', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('8477900000', '', 'PO', '0.00', '0.00000000', 'PZA', 'Presses setup the mold during blowing process') - ,('8477902580', '', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('8477908595', '', 'PO', '0.00', '0.00000000', 'PZA', 'DISC') - ,('8477908695', '', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('8479820040', '', 'PO', '0.00', '0.00000000', '', 'ROTARY MACHINE') - ,('8479820080', '', 'PO', '0.00', '0.00000000', 'PZA', 'APARATO AGITADOR') - ,('8479830100', '', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('8479899199', '', 'PO', '0.00', '0.00000000', 'PZA', 'PORTABLE DOCK PLATE') - ,('8479899499', '', 'PO', '0.00', '0.00000000', 'PZA', 'MAQUINA APLICADORA') - ,('8479899599', '', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('8479899797', '', 'PO', '0.00', '0.00000000', 'PZA', 'COLECTOR DE POLVO') - ,('8479909496', '', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('84799099', '', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('8480718045', 'S', 'PO', '0.00', '0.00000000', 'PZA', 'MOLDE') - ,('8480718060', '', 'PO', '0.00', '0.00000000', 'PZA', 'TIG TORCH') - ,('8480719090', '', 'PO', '0.00', '0.00000000', 'PZA', 'Semi-finished mold') - ,('8481100090', '', 'PO', '0.00', '0.00000000', '', '') - ,('8481200080', '', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('84812099', '', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('8481400000', '', 'PO', '0.00', '0.00000000', '', '') - ,('8481809020', '', 'PO', '0.00', '0.00000000', '', '') - ,('8481809050', '', 'PO', '0.00', '0.00000000', 'PZA', 'NOZZLE, CONE DUT') - ,('8481909085', '', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('8483308090', '', 'PO', '0.00', '0.00000000', 'PZA', 'BUJES') - ,('8484200000', '', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('8484900000', '', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('8501106080', '', 'PO', '0.00', '0.00000000', 'PZA', 'MOTOR ELECTRICO INCLUYE ACCESORIOS') - ,('8504404000', '', 'PO', '0.00', '0.00000000', 'PZA', 'CONTROLADOR DE VELOCIDAD') - ,('8504409580', '', 'PO', '0.00', '0.00000000', 'PZA', 'BATTERY CHARGER') - ,('8505110090', '', 'PO', '0.00', '0.00000000', 'PZA', 'INSERT') - ,('8505200000', '', 'PO', '0.00', '0.00000000', 'PZA', 'BARRIER') - ,('8507208091', '', 'PO', '0.00', '0.00000000', '', '') - ,('8512902000', 'S', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('8514908000', '', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('8515390040', '', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('8515800080', '', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('85160808000', '', 'PO', '0.00', '0.00000000', '', '') - ,('8516808000', '', 'PO', '0.00', '0.00000000', '', '') - ,('8517620010', '', 'PO', '0.00', '0.00000000', '', 'WIRELESS ACCESS POINT') - ,('8523520010', '', 'PO', '0.00', '0.00000000', 'PZA', 'RF Label') - ,('8525805050', '', 'PO', '0.00', '0.00000000', 'PZA', 'CAMARA DE CIRCUITO CERRADO') - ,('8533408070', '', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('8536100040', '', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('8536490050', '', 'PO', '0.00', '0.00000000', '', '') - ,('8536507000', '', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('8536908585', '', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('8537103000', 'S', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('8537109070', '', 'PO', '0.00', '0.00000000', '', 'DIGITAL PANEL CONTROL') - ,('8543908885', 'S', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('8544190000', '', 'PO', '0.00', '0.00000000', '', '') - ,('8544429090', '', 'PO', '0.00', '0.00000000', 'PZA', 'SENSOR CABLE') - ,('8547200000', '', 'PO', '0.00', '0.00000000', '', '') - ,('8713100000', '', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('8716805090', '', 'PO', '0.00', '0.00000000', 'PZA', 'UTILITY CART') - ,('9018390050', '', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('9023000000', '', 'PO', '0.00', '0.00000000', '', '') - ,('9024800000', '', 'PO', '0.00', '0.00000000', '', 'TESTING MACHINE') - ,('9026102010', '', 'PO', '0.00', '0.00000000', '', 'DIGITAL VACUUM GAUGE') - ,('9026106000', '', 'PO', '0.00', '0.00000000', 'PZA', 'FLOAT & GAUGE') - ,('9026204000', '', 'PO', '0.00', '0.00000000', '', '') - ,('9026208000', '', 'PO', '0.00', '0.00000000', 'PZA', 'REGULATOR') - ,('9027304040', '', 'PO', '0.00', '0.00000000', 'PZA', 'SPECTROPHOTOMETER') - ,('9031.80.8085', '', 'PO', '1.70', '0.00000000', '', 'FIXTURA') - ,('9031808085', '', 'PO', '1.70', '2.25000000', 'PZA', 'APARATO') - ,('9031907000', '', 'PO', '0.00', '0.00000000', 'PZA', 'FIXTURE') - ,('9032200000', '', 'PO', '0.00', '0.00000000', 'PZA', 'PRESOSTATO') - ,('9106100000', '', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('9403100040', '', 'PO', '0.00', '0.00000000', 'PZA', 'MESA DE METAL CON BASE DE MADERA') - ,('9403200030', '', 'PO', '0.00', '0.00000000', 'PZA', 'ESTANTE DE ACERO PARA TOTE') - ,('9506310000', '', 'PO', '0.00', '0.00000000', '', 'GOLF CLUB') - ,('9506320000', '', 'PO', '0.00', '0.00000000', 'PZA', 'BALL') - ,('9506620000', '', 'PO', '0.00', '0.00000000', 'PZA', 'PELOTA') - ,('9506628060', '', 'PO', '0.00', '0.00000000', '', '') - ,('9506996080', '', 'PO', '0.00', '0.00000000', '', '') - ,('9603404050', 'US', '', '0.00', '0.00000000', 'PZA', 'BROCHAS CON MANGO DE PLASTICO Y CERDAS DE FIBRA SINTETICA') - ,('9604000000', '', 'PO', '0.00', '0.00000000', '', '') - ,('9607190060', '', 'PO', '0.00', '0.00000000', '', '') - ,('9612101020', '', 'PO', '0.00', '0.00000000', 'PZA', 'RIBBON') - ,('9612109090', '', 'PO', '0.00', '0.00000000', 'PZA', 'RIBBON') - ,('9618000000', 'S', 'PO', '0.00', '79.00000000', 'PZA', 'MANIQUIES') - ,('9618009900', '', 'PO', '0.00', '0.00000000', 'PZA', '') - ,('9801001095', '', 'PO', '0.00', '4.35000000', '', 'RESINA') - ,('9801001097', 'S', 'PO', '0.00', '4.35000000', 'PZA', '') - ,('9801001098', '', 'PO', '0.00', '0.00000000', 'PZA', 'US RETURNS') - ,('9801002500', '', 'PO', '0.00', '0.00000000', 'PZA', 'CHINA ARTICLES') - ,('9802004040', 'US', 'PO', '0.00', '0.00000000', 'PZA', 'MANIQUIS REPARADOS') - ,('9802005060', '', 'PO', '0.00', '0.00000000', '', 'Repaired in Mexico') - ,('9861661098', 'M6363', 'PO', '0.00', '8.10000000', 'PZA', '') - ,('MX4415109000', '', 'PO', '0.00', '0.00000000', 'PZA', 'CAJON DE MADERA') - ,('MX4820400000', '', 'PO', '0.00', '0.50000000', '', 'FORMULARIOS DE PAPEL') - ,('MX7326909980', '', 'PO', '0.00', '0.00000000', '', 'STEEL PANEL') - ,('MX8309900000', '', 'PO', '0.00', '0.00000000', 'PZA', 'CAP') -] diff --git a/backend/api/v1/modules/a76/general_catalogs/fractions/us_tariff_fractions/service.py b/backend/api/v1/modules/a76/general_catalogs/fractions/us_tariff_fractions/service.py deleted file mode 100644 index e5f8cfbf..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/fractions/us_tariff_fractions/service.py +++ /dev/null @@ -1,211 +0,0 @@ -from typing import List, Optional, Tuple, Dict, Any -from datetime import datetime -from sqlalchemy.orm import Session -from sqlalchemy.exc import IntegrityError -from fastapi import HTTPException -import logging -from decimal import Decimal - -from .models import USTariffFraction -from .dto import USTariffFractionCreateDTO, USTariffFractionUpdateDTO -from api.v1.modules.sitar.fracciones_usa.service import FraccionesUSAService - -logger = logging.getLogger(__name__) - - -class USTariffFractionMapper: - """Maps Sitar FraccionesUSAResponse objects to USTariffFraction domain objects""" - - @staticmethod - def to_domain( - item: Any, tenant_id: int, company_id: int - ) -> USTariffFraction: - """Convert a FraccionesUSAResponse to a USTariffFraction instance (not persisted)""" - code = item.FRACCION_CON_PUNTO or item.FRACCION_MOSTRAR or item.FRACCION_SIN_PUNTO or "" - ad_valorem: Optional[float] = None - if item.TARIFA1: - try: - ad_valorem = float(str(item.TARIFA1).replace("%", "").strip()) - except (ValueError, TypeError): - ad_valorem = None - - fraction = USTariffFraction() - fraction.id = item.CONSECUTIVO - fraction.tenant_id = tenant_id - fraction.company_id = company_id - fraction.code = code - fraction.prefix = item.FRACCION_SIN_PUNTO - fraction.type_code = str(item.NIVEL) if item.NIVEL is not None else None - fraction.ad_valorem = ad_valorem - fraction.fixed_cost = None - fraction.unit_of_measure = item.UNIDADCANTIDAD - fraction.description = item.DESCRIPCION - now = datetime.now() - fraction.created_at = now - fraction.updated_at = now - return fraction - - -class USTariffFractionService: - """Service para gestionar fracciones arancelarias americanas""" - - @staticmethod - async def get_all( - db: Session, - tenant_id: int, - company_id: int, - skip: int = 0, - limit: int = 100, - filters: Optional[Dict[str, Any]] = None, - ) -> Tuple[List[USTariffFraction], int]: - """ - Obtiene todas las fracciones arancelarias americanas con filtros opcionales. - Estrategia: Sitar API -> Fallback Local DB - """ - - ## 1. Try Sitar API - #try: - # sitar_service = FraccionesUSAService.get_instance() - # - # sitar_fraccion = None - # has_filters = False - # - # if filters and filters.get("search"): - # term = filters["search"] - # # Sitar only filters by fraction code - # if term.replace(".", "").isdigit(): - # sitar_fraccion = term - # has_filters = True - # - # sitar_items = await sitar_service.search( - # fraccion=sitar_fraccion, - # skip=skip, - # limit=limit - # ) - # - # # If Sitar returns empty list, attempt fallback to local DB - # if not sitar_items: - # logger.info(f"Sitar returned no results for USA query (filters={has_filters}). Attempting fallback to local DB.") - # return USTariffFractionService._get_all_local(db, tenant_id, company_id, skip, limit, filters) - # - # # Map items - # items = [USTariffFractionMapper.to_domain(item, tenant_id, company_id) for item in sitar_items] - # - # # Estimate total - # total = len(items) + skip - # if len(items) == limit: - # total += 1 - # - # return items, total - - #except Exception as e: - return USTariffFractionService._get_all_local(db, tenant_id, company_id, skip, limit, filters) - - @staticmethod - def _get_all_local( - db: Session, - tenant_id: int, - company_id: int, - skip: int = 0, - limit: int = 100, - filters: Optional[Dict[str, Any]] = None, - ) -> Tuple[List[USTariffFraction], int]: - """Lógica original de consulta local""" - query = db.query(USTariffFraction).filter( - USTariffFraction.tenant_id == tenant_id, - USTariffFraction.company_id == company_id, - ) - - # Aplicar filtros - if filters: - # Si hay un filtro 'search', buscar en múltiples campos - if filters.get("search"): - search_term = f"%{filters['search']}%" - query = query.filter( - USTariffFraction.code.ilike(search_term) | - USTariffFraction.description.ilike(search_term) | - USTariffFraction.prefix.ilike(search_term) - ) - - total = query.count() - items = query.offset(skip).limit(limit).all() - - return items, total - - @staticmethod - def get_by_id( - db: Session, fraction_id: int, tenant_id: int, company_id: int - ) -> Optional[USTariffFraction]: - """ - Obtiene por ID local. - """ - return ( - db.query(USTariffFraction) - .filter( - USTariffFraction.id == fraction_id, - USTariffFraction.tenant_id == tenant_id, - USTariffFraction.company_id == company_id, - ) - .first() - ) - - @staticmethod - def create( - db: Session, - fraction_data: USTariffFractionCreateDTO, - tenant_id: int, - company_id: int, - ) -> USTariffFraction: - try: - db_fraction = USTariffFraction( - tenant_id=tenant_id, - company_id=company_id, - **fraction_data.model_dump(), - ) - db.add(db_fraction) - db.commit() - db.refresh(db_fraction) - return db_fraction - except IntegrityError as e: - db.rollback() - logger.error(f"Error creando fracción americana: {e}") - raise HTTPException( - status_code=400, - detail="Ya existe una fracción americana con este código", - ) - - @staticmethod - def update( - db: Session, - fraction_id: int, - tenant_id: int, - fraction_data: USTariffFractionUpdateDTO, - company_id: int, - ) -> Optional[USTariffFraction]: - db_fraction = USTariffFractionService.get_by_id( - db, fraction_id, tenant_id, company_id - ) - if not db_fraction: - return None - - update_data = fraction_data.model_dump(exclude_unset=True) - for field, value in update_data.items(): - setattr(db_fraction, field, value) - - db.commit() - db.refresh(db_fraction) - return db_fraction - - @staticmethod - def delete( - db: Session, fraction_id: int, tenant_id: int, company_id: int - ) -> bool: - db_fraction = USTariffFractionService.get_by_id( - db, fraction_id, tenant_id, company_id - ) - if not db_fraction: - return False - - db.delete(db_fraction) - db.commit() - return True diff --git a/backend/api/v1/modules/a76/general_catalogs/fractions/warning_fractions/models.py b/backend/api/v1/modules/a76/general_catalogs/fractions/warning_fractions/models.py deleted file mode 100644 index 664458fd..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/fractions/warning_fractions/models.py +++ /dev/null @@ -1,47 +0,0 @@ -""" -Modelos ORM para fracciones de alerta -""" - -from typing import Optional - -from sqlalchemy import Boolean, Integer, PrimaryKeyConstraint, String, UniqueConstraint -from sqlalchemy.orm import Mapped, mapped_column - -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base - - -class WarningFraction(Base, TenantScopedMixin, TimestampMixin): - """ - Modelo para fracciones arancelarias con alerta. - Fracciones que requieren atención especial o documentación adicional. - """ - - __tablename__ = "warning_fractions" - __table_args__ = ( - PrimaryKeyConstraint("id", name="warning_fractions_pkey"), - UniqueConstraint( - "fraction", "company_id", name="uq_warning_fractions_fraction_company" - ), - {"schema": "public"}, - ) - - id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) - - # Código de la fracción arancelaria (ej: 01012101) - fraction: Mapped[str] = mapped_column(String(15), nullable=False, index=True) - - # Descripción de la alerta - description: Mapped[Optional[str]] = mapped_column(String(1000)) - - # Tipo de alerta (ej: PERMISO, RESTRICCION, NOM, etc.) - warning_type: Mapped[Optional[str]] = mapped_column(String(50), index=True) - - # Indica si la alerta está activa - is_active: Mapped[bool] = mapped_column(Boolean, nullable=False, default=True, server_default="true") - - def __repr__(self) -> str: - return ( - f"" - ) diff --git a/backend/api/v1/modules/a76/general_catalogs/fractions/warning_fractions/seed.py b/backend/api/v1/modules/a76/general_catalogs/fractions/warning_fractions/seed.py deleted file mode 100644 index 501ae013..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/fractions/warning_fractions/seed.py +++ /dev/null @@ -1,141 +0,0 @@ -seed = [ - ("8701210100", "", "PROCESS"), - ("8702100500", "", "PROCESS"), - ("8702400600", "", "PROCESS"), - ("8703310200", "", "PROCESS"), - ("8703500200", "", "PROCESS"), - ("8704210400", "", "PROCESS"), - ("8704410200", "", "PROCESS"), - ("8705400200", "", "PROCESS"), - ("8711300401", "", "PROCESS"), - ("8711409999", "", "PROCESS"), - ("8711900100", "", "PROCESS"), - ("8716200499", "", "PROCESS"), - ("8716390400", "", "PROCESS"), - ("8716399903", "", "PROCESS"), - ("8716409100", "", "PROCESS"), - ("8716900300", "", "PROCESS"), - ("8716909901", "", "PROCESS"), - ("8716909902", "", "PROCESS"), - ("8716909999", "", "PROCESS"), - ("8711100300", "", "PROCESS"), - ("8711200501", "", "PROCESS"), - ("8711200502", "", "PROCESS"), - ("8711200599", "", "PROCESS"), - ("8711300499", "", "PROCESS"), - ("8711400100", "", "PROCESS"), - ("8711400200", "", "PROCESS"), - ("8711409901", "", "PROCESS"), - ("8711500301", "", "PROCESS"), - ("8711500302", "", "PROCESS"), - ("8711500399", "", "PROCESS"), - ("8711600100", "", "PROCESS"), - ("8711909900", "", "PROCESS"), - ("8716100100", "", "PROCESS"), - ("8716200401", "", "PROCESS"), - ("8716200402", "", "PROCESS"), - ("8703210200", "", "PROCESS"), - ("8703220200", "", "PROCESS"), - ("8703230200", "", "PROCESS"), - ("8703240200", "", "PROCESS"), - ("8716310301", "", "PROCESS"), - ("8716310302", "", "PROCESS"), - ("8716310399", "", "PROCESS"), - ("8716390300", "", "PROCESS"), - ("8703320200", "", "PROCESS"), - ("8703330200", "", "PROCESS"), - ("8703400200", "", "PROCESS"), - ("8703600200", "", "PROCESS"), - ("8703700200", "", "PROCESS"), - ("8703900200", "", "PROCESS"), - ("8716390800", "", "PROCESS"), - ("8716399901", "", "PROCESS"), - ("8716399902", "", "PROCESS"), - ("8701220100", "", "PROCESS"), - ("8701230100", "", "PROCESS"), - ("8701240100", "", "PROCESS"), - ("8701290100", "", "PROCESS"), - ("8704220700", "", "PROCESS"), - ("8704230200", "", "PROCESS"), - ("8704310500", "", "PROCESS"), - ("8704320700", "", "PROCESS"), - ("8704420200", "", "PROCESS"), - ("8704430200", "", "PROCESS"), - ("8704510300", "", "PROCESS"), - ("8704520200", "", "PROCESS"), - ("8716399904", "", "PROCESS"), - ("8716399905", "", "PROCESS"), - ("8716399999", "", "PROCESS"), - ("8702200500", "", "PROCESS"), - ("8702300500", "", "PROCESS") -] - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/backend/api/v1/modules/a76/general_catalogs/identifiers/__init__.py b/backend/api/v1/modules/a76/general_catalogs/identifiers/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/backend/api/v1/modules/a76/general_catalogs/identifiers/dto.py b/backend/api/v1/modules/a76/general_catalogs/identifiers/dto.py deleted file mode 100644 index e65af32e..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/identifiers/dto.py +++ /dev/null @@ -1,61 +0,0 @@ -from typing import Optional -from pydantic import BaseModel, Field, ConfigDict - -class IdentifierBase(BaseModel): - code: str = Field(..., max_length=2, description="Identifier Code (CLAVE)") - description: Optional[str] = Field( - None, max_length=1000, description="Description") - level: Optional[str] = Field(None, max_length=1, description="Level") - complement: Optional[str] = Field( - None, max_length=5000, description="Complement") - -class IdentifierCreate(IdentifierBase): - pass - -class IdentifierUpdate(BaseModel): - code: Optional[str] = Field(None, max_length=2) - description: Optional[str] = Field(None, max_length=1000) - level: Optional[str] = Field(None, max_length=1) - complement: Optional[str] = Field(None, max_length=5000) - -class IdentifierResponse(IdentifierBase): - id: int - tenant_id: int - company_id: int - - model_config = ConfigDict(from_attributes=True) - - -class IdentifierDetailBase(BaseModel): - invoice_consecutive: Optional[int] = Field( - None, description="Invoice Consecutive") - part_line: Optional[int] = Field(None, description="Part Line") - identifier_code: Optional[str] = Field( - None, max_length=2, description="Identifier Code") - item_line_id: Optional[int] = Field(None, description="Item Line ID") - module: Optional[str] = Field(None, max_length=20, description="Module") - complement1: Optional[str] = Field( - None, max_length=50, description="Complement 1") - complement2: Optional[str] = Field( - None, max_length=51, description="Complement 2") - complement3: Optional[str] = Field( - None, max_length=50, description="Complement 3") - -class IdentifierDetailCreate(IdentifierDetailBase): - pass - -class IdentifierDetailUpdate(BaseModel): - invoice_consecutive: Optional[int] = None - part_line: Optional[int] = None - identifier_code: Optional[str] = Field(None, max_length=2) - module: Optional[str] = Field(None, max_length=20) - complement1: Optional[str] = Field(None, max_length=50) - complement2: Optional[str] = Field(None, max_length=51) - complement3: Optional[str] = Field(None, max_length=50) - -class IdentifierDetailResponse(IdentifierDetailBase): - id: int - tenant_id: int - company_id: int - - model_config = ConfigDict(from_attributes=True) \ No newline at end of file diff --git a/backend/api/v1/modules/a76/general_catalogs/identifiers/models.py b/backend/api/v1/modules/a76/general_catalogs/identifiers/models.py deleted file mode 100644 index d81e700b..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/identifiers/models.py +++ /dev/null @@ -1,62 +0,0 @@ -from typing import Optional, TYPE_CHECKING -from sqlalchemy import Integer, String, UniqueConstraint, ForeignKey -from sqlalchemy.orm import Mapped, mapped_column, relationship -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base - - -if TYPE_CHECKING: - from api.v1.modules.a76.items.models import LineItem - -class Identifier(Base, TenantScopedMixin, TimestampMixin): - __tablename__ = "identifiers" - __table_args__ = ( - UniqueConstraint("code", name="uq_identifier_code"), - {"schema": "a76"} - ) - - id: Mapped[int] = mapped_column( - Integer, primary_key=True, autoincrement=True) - - code: Mapped[str] = mapped_column(String(2), nullable=False) # CLAVE - - description: Mapped[Optional[str]] = mapped_column( - String(1000), nullable=True) # DESCRIPCION - - level: Mapped[Optional[str]] = mapped_column( - String(1), nullable=True) # NIVEL - - complement: Mapped[Optional[str]] = mapped_column( - String(5000), nullable=True) # COMPLEMENTO - - details: Mapped[list["IdentifierDetail"]] = relationship( - back_populates="identifier") - - -class IdentifierDetail(Base, TenantScopedMixin, TimestampMixin): - __tablename__ = "identifier_details" - __table_args__ = ( - {"schema": "a76"} - ) - - id: Mapped[int] = mapped_column( - Integer, primary_key=True, autoincrement=True) - invoice_consecutive: Mapped[Optional[int]] = mapped_column( - Integer, nullable=True) # CONSECUTIVOFACTURA - part_line: Mapped[Optional[int]] = mapped_column( - Integer, nullable=True) # LINEAPARTIDA - identifier_code: Mapped[Optional[str]] = mapped_column( - String(2), ForeignKey("a76.identifiers.code"), nullable=True) # ID - item_line_id: Mapped[Optional[int]] = mapped_column( - ForeignKey("a76.item_lines.id"), nullable=True) - module: Mapped[Optional[str]] = mapped_column( - String(20), nullable=True) # MODULO - complement1: Mapped[Optional[str]] = mapped_column( - String(50), nullable=True) # COMPLEMENTO1 - complement2: Mapped[Optional[str]] = mapped_column( - String(51), nullable=True) # COMPLEMENTO2 - complement3: Mapped[Optional[str]] = mapped_column( - String(50), nullable=True) # COMPLEMENTO3 - - identifier: Mapped["Identifier"] = relationship(back_populates="details") - line: Mapped[Optional["LineItem"]] = relationship(back_populates="identifiers") diff --git a/backend/api/v1/modules/a76/general_catalogs/identifiers/routes.py b/backend/api/v1/modules/a76/general_catalogs/identifiers/routes.py deleted file mode 100644 index ed326704..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/identifiers/routes.py +++ /dev/null @@ -1,47 +0,0 @@ -from fastapi import APIRouter -from api.v1.common.tenant_crud_routes import TenantCRUDRoutes -from .dto import ( - IdentifierCreate, IdentifierResponse, IdentifierUpdate, - IdentifierDetailCreate, IdentifierDetailResponse, IdentifierDetailUpdate -) -from .service import IdentifierService, IdentifierDetailService - -router = APIRouter(prefix="/identifiers", - tags=["a76.general_catalogs.identifiers"]) - -# Identifier CRUD -identifier_crud = TenantCRUDRoutes( - create_schema=IdentifierCreate, - update_schema=IdentifierUpdate, - response_schema=IdentifierResponse, - service=IdentifierService, - prefix="", - tags=["Identifiers"], - resource_name="Identifier", - enable_list=True, - enable_filters=True, - list_permissions=["cat_identifiers.view"], - get_permissions=["cat_identifiers.view"], - create_permissions=["cat_identifiers.create"], - update_permissions=["cat_identifiers.edit"], - delete_permissions=["cat_identifiers.delete"], -) - -# Identifier Detail CRUD -detail_crud = TenantCRUDRoutes( - create_schema=IdentifierDetailCreate, - update_schema=IdentifierDetailUpdate, - response_schema=IdentifierDetailResponse, - service=IdentifierDetailService, - prefix="/details", - tags=["Identifier Details"], - resource_name="Identifier Detail", - list_permissions=["cat_identifiers.view"], - get_permissions=["cat_identifiers.view"], - create_permissions=["cat_identifiers.create"], - update_permissions=["cat_identifiers.edit"], - delete_permissions=["cat_identifiers.delete"], -) - -router.include_router(identifier_crud.router) -router.include_router(detail_crud.router) diff --git a/backend/api/v1/modules/a76/general_catalogs/identifiers/service.py b/backend/api/v1/modules/a76/general_catalogs/identifiers/service.py deleted file mode 100644 index 187371f4..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/identifiers/service.py +++ /dev/null @@ -1,200 +0,0 @@ -from typing import List, Optional, Tuple, Dict, Any -from sqlalchemy.orm import Session -from sqlalchemy import select - -from .models import Identifier, IdentifierDetail -from .dto import IdentifierCreate, IdentifierUpdate, IdentifierDetailCreate, IdentifierDetailUpdate - - -class IdentifierService: - @staticmethod - def get_all( - db: Session, - tenant_id: int, - company_id: int, - skip: int = 0, - limit: int = 100, - filters: Optional[Dict[str, Any]] = None - ) -> Tuple[List[Identifier], int]: - query = db.query(Identifier).filter( - Identifier.tenant_id == tenant_id, - Identifier.company_id == company_id - ) - - if filters: - if filters.get("code"): - query = query.filter(Identifier.code.ilike(f"%{filters['code']}%")) - if filters.get("description"): - query = query.filter( - Identifier.description.ilike(f"%{filters['description']}%") - ) - - total = query.count() - items = query.offset(skip).limit(limit).all() - return items, total - - @staticmethod - def get_by_id( - db: Session, - id: int, - tenant_id: int, - company_id: int - ) -> Optional[Identifier]: - return db.query(Identifier).filter( - Identifier.id == id, - Identifier.tenant_id == tenant_id, - Identifier.company_id == company_id - ).first() - - @staticmethod - def create( - db: Session, - data: IdentifierCreate, - tenant_id: int, - company_id: int - ) -> Identifier: - # Exclude company_id from data as it is passed separately - data_dict = data.model_dump() - if 'company_id' in data_dict: - del data_dict['company_id'] - - db_obj = Identifier( - **data_dict, - tenant_id=tenant_id, - company_id=company_id - ) - db.add(db_obj) - db.commit() - db.refresh(db_obj) - return db_obj - - @staticmethod - def update( - db: Session, - id: int, - tenant_id: int, - data: IdentifierUpdate, - company_id: int - ) -> Optional[Identifier]: - db_obj = IdentifierService.get_by_id(db, id, tenant_id, company_id) - if not db_obj: - return None - - update_dict = data.model_dump(exclude_unset=True) - for key, value in update_dict.items(): - setattr(db_obj, key, value) - - db.commit() - db.refresh(db_obj) - return db_obj - - @staticmethod - def delete( - db: Session, - id: int, - tenant_id: int, - company_id: int - ) -> bool: - db_obj = IdentifierService.get_by_id(db, id, tenant_id, company_id) - if not db_obj: - return False - - db.delete(db_obj) - db.commit() - return True - - -class IdentifierDetailService: - @staticmethod - def get_all( - db: Session, - tenant_id: int, - company_id: int, - skip: int = 0, - limit: int = 100, - filters: Optional[Dict[str, Any]] = None - ) -> Tuple[List[IdentifierDetail], int]: - query = db.query(IdentifierDetail).filter( - IdentifierDetail.tenant_id == tenant_id, - IdentifierDetail.company_id == company_id - ) - - if filters: - # Add filters here if needed - pass - - total = query.count() - items = query.offset(skip).limit(limit).all() - return items, total - - @staticmethod - def get_by_id( - db: Session, - id: int, - tenant_id: int, - company_id: int - ) -> Optional[IdentifierDetail]: - return db.query(IdentifierDetail).filter( - IdentifierDetail.id == id, - IdentifierDetail.tenant_id == tenant_id, - IdentifierDetail.company_id == company_id - ).first() - - @staticmethod - def create( - db: Session, - data: IdentifierDetailCreate, - tenant_id: int, - company_id: int - ) -> IdentifierDetail: - # Exclude company_id from data as it is passed separately - data_dict = data.model_dump() - if 'company_id' in data_dict: - del data_dict['company_id'] - - db_obj = IdentifierDetail( - **data_dict, - tenant_id=tenant_id, - company_id=company_id - ) - db.add(db_obj) - db.commit() - db.refresh(db_obj) - return db_obj - - @staticmethod - def update( - db: Session, - id: int, - data: IdentifierDetailUpdate, - tenant_id: int, - company_id: int - ) -> Optional[IdentifierDetail]: - db_obj = IdentifierDetailService.get_by_id( - db, id, tenant_id, company_id) - if not db_obj: - return None - - update_dict = data.model_dump(exclude_unset=True) - for key, value in update_dict.items(): - setattr(db_obj, key, value) - - db.commit() - db.refresh(db_obj) - return db_obj - - @staticmethod - def delete( - db: Session, - id: int, - tenant_id: int, - company_id: int - ) -> bool: - db_obj = IdentifierDetailService.get_by_id( - db, id, tenant_id, company_id) - if not db_obj: - return False - - db.delete(db_obj) - db.commit() - return True diff --git a/backend/api/v1/modules/a76/general_catalogs/inpc/__init__.py b/backend/api/v1/modules/a76/general_catalogs/inpc/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/backend/api/v1/modules/a76/general_catalogs/inpc/dto.py b/backend/api/v1/modules/a76/general_catalogs/inpc/dto.py deleted file mode 100644 index 72afc5c6..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/inpc/dto.py +++ /dev/null @@ -1,25 +0,0 @@ -from typing import Optional -from decimal import Decimal -from pydantic import BaseModel, Field, ConfigDict - -class INPCBase(BaseModel): - year: str = Field(..., max_length=4, description="Year (YYYY)") - month: str = Field(..., max_length=2, description="Month (MM)") - value: Optional[Decimal] = Field(None, description="INPC Value") - - -class INPCCreate(INPCBase): - pass - -class INPCUpdate(BaseModel): - year: Optional[str] = Field(None, max_length=4) - month: Optional[str] = Field(None, max_length=2) - value: Optional[Decimal] = None - - -class INPCResponse(INPCBase): - id: int - tenant_id: int - company_id: int - - model_config = ConfigDict(from_attributes=True) \ No newline at end of file diff --git a/backend/api/v1/modules/a76/general_catalogs/inpc/models.py b/backend/api/v1/modules/a76/general_catalogs/inpc/models.py deleted file mode 100644 index 65e79049..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/inpc/models.py +++ /dev/null @@ -1,25 +0,0 @@ -from typing import Optional -from decimal import Decimal -from sqlalchemy import Integer, String, UniqueConstraint, Numeric -from sqlalchemy.orm import Mapped, mapped_column -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base - - -class INPC(Base, TenantScopedMixin, TimestampMixin): - __tablename__ = "inpc" - __table_args__ = ( - UniqueConstraint("year", "month", "tenant_id", - "company_id", name="uq_inpc_year_month"), - {"schema": "a76"} - ) - - id: Mapped[int] = mapped_column( - Integer, primary_key=True, autoincrement=True) - - year: Mapped[str] = mapped_column(String(4), nullable=False) # ANIO - - month: Mapped[str] = mapped_column(String(2), nullable=False) # MES - - value: Mapped[Optional[Decimal]] = mapped_column( - Numeric(19, 8), nullable=True) # VALOR diff --git a/backend/api/v1/modules/a76/general_catalogs/inpc/routes.py b/backend/api/v1/modules/a76/general_catalogs/inpc/routes.py deleted file mode 100644 index e5b5b8e0..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/inpc/routes.py +++ /dev/null @@ -1,23 +0,0 @@ -from api.v1.common.tenant_crud_routes import TenantCRUDRoutes -from .models import INPC -from .dto import INPCCreate, INPCResponse, INPCUpdate -from .service import INPCService - -# Usamos TenantCRUDRoutes directamente -router = TenantCRUDRoutes( - service=INPCService, - create_schema=INPCCreate, - update_schema=INPCUpdate, - response_schema=INPCResponse, - prefix="/inpc", - tags=["a76.general_catalogs.inpc"], - resource_name="INPC", - enable_list=True, - enable_filters=True, - # Permisos - list_permissions=["cat_inpc.view"], - get_permissions=["cat_inpc.view"], - create_permissions=["cat_inpc.create"], - update_permissions=["cat_inpc.edit"], - delete_permissions=["cat_inpc.delete"], -).router \ No newline at end of file diff --git a/backend/api/v1/modules/a76/general_catalogs/inpc/service.py b/backend/api/v1/modules/a76/general_catalogs/inpc/service.py deleted file mode 100644 index de06ce0b..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/inpc/service.py +++ /dev/null @@ -1,97 +0,0 @@ -from typing import List, Optional, Tuple, Dict, Any -from sqlalchemy.orm import Session -from sqlalchemy import select - -from .models import INPC -from .dto import INPCCreate, INPCUpdate - - -class INPCService: - @staticmethod - def get_all( - db: Session, - tenant_id: int, - company_id: int, - skip: int = 0, - limit: int = 100, - filters: Optional[Dict[str, Any]] = None - ) -> Tuple[List[INPC], int]: - query = db.query(INPC).filter( - INPC.tenant_id == tenant_id, - INPC.company_id == company_id - ) - - if filters: - if filters.get("year"): - query = query.filter(INPC.year == filters["year"]) - if filters.get("month"): - query = query.filter(INPC.month == filters["month"]) - - total = query.count() - items = query.offset(skip).limit(limit).all() - return items, total - - @staticmethod - def get_by_id( - db: Session, - id: int, - tenant_id: int, - company_id: int - ) -> Optional[INPC]: - return db.query(INPC).filter( - INPC.id == id, - INPC.tenant_id == tenant_id, - INPC.company_id == company_id - ).first() - - @staticmethod - def create( - db: Session, - data: INPCCreate, - tenant_id: int, - company_id: int - ) -> INPC: - db_obj = INPC( - **data.model_dump(), - tenant_id=tenant_id, - company_id=company_id - ) - db.add(db_obj) - db.commit() - db.refresh(db_obj) - return db_obj - - @staticmethod - def update( - db: Session, - id: int, - tenant_id: int, - data: INPCUpdate, - company_id: int - ) -> Optional[INPC]: - db_obj = INPCService.get_by_id(db, id, tenant_id, company_id) - if not db_obj: - return None - - update_dict = data.model_dump(exclude_unset=True) - for key, value in update_dict.items(): - setattr(db_obj, key, value) - - db.commit() - db.refresh(db_obj) - return db_obj - - @staticmethod - def delete( - db: Session, - id: int, - tenant_id: int, - company_id: int - ) -> bool: - db_obj = INPCService.get_by_id(db, id, tenant_id, company_id) - if not db_obj: - return False - - db.delete(db_obj) - db.commit() - return True diff --git a/backend/api/v1/modules/a76/general_catalogs/legends/__init__.py b/backend/api/v1/modules/a76/general_catalogs/legends/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/backend/api/v1/modules/a76/general_catalogs/legends/dto.py b/backend/api/v1/modules/a76/general_catalogs/legends/dto.py deleted file mode 100644 index 2f3b9570..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/legends/dto.py +++ /dev/null @@ -1,25 +0,0 @@ -from typing import Optional -from pydantic import BaseModel, Field, ConfigDict - - -class LegendBase(BaseModel): - code: int = Field(..., description="Legend Code (CLAVELEY)") - description: Optional[str] = Field( - None, max_length=2000, description="Description") - - -class LegendCreate(LegendBase): - pass - - -class LegendUpdate(BaseModel): - code: Optional[int] = None - description: Optional[str] = Field(None, max_length=2000) - - -class LegendResponse(LegendBase): - id: int - - model_config = ConfigDict(from_attributes=True) - tenant_id : int - company_id : int diff --git a/backend/api/v1/modules/a76/general_catalogs/legends/models.py b/backend/api/v1/modules/a76/general_catalogs/legends/models.py deleted file mode 100644 index f6ea4591..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/legends/models.py +++ /dev/null @@ -1,22 +0,0 @@ -from typing import Optional -from sqlalchemy import Integer, String, UniqueConstraint -from sqlalchemy.orm import Mapped, mapped_column -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base - - -class Legend(Base, TenantScopedMixin, TimestampMixin): - __tablename__ = "legends" - __table_args__ = ( - UniqueConstraint("code", "tenant_id", "company_id", - name="uq_legend_code"), - {"schema": "a76"} - ) - - id: Mapped[int] = mapped_column( - Integer, primary_key=True, autoincrement=True) - - code: Mapped[int] = mapped_column(Integer, nullable=False) # CLAVELEY - - description: Mapped[Optional[str]] = mapped_column( - String(2000), nullable=True) # DESCLEYENDA diff --git a/backend/api/v1/modules/a76/general_catalogs/legends/routes.py b/backend/api/v1/modules/a76/general_catalogs/legends/routes.py deleted file mode 100644 index de2b1e5a..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/legends/routes.py +++ /dev/null @@ -1,22 +0,0 @@ -from fastapi import APIRouter -from api.v1.common.tenant_crud_routes import TenantCRUDRoutes -from .models import Legend -from .dto import LegendCreate, LegendResponse, LegendUpdate -from .service import LegendService - -router = TenantCRUDRoutes( - service=LegendService, - create_schema=LegendCreate, - update_schema=LegendUpdate, - response_schema=LegendResponse, - prefix="/legends", - tags=["a76.general_catalogs.legends"], - resource_name="Legend", - enable_list=True, - enable_filters=True, - list_permissions=["cat_legends.view"], - get_permissions=["cat_legends.view"], - create_permissions=["cat_legends.create"], - update_permissions=["cat_legends.edit"], - delete_permissions=["cat_legends.delete"], -).router diff --git a/backend/api/v1/modules/a76/general_catalogs/legends/service.py b/backend/api/v1/modules/a76/general_catalogs/legends/service.py deleted file mode 100644 index e4340e24..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/legends/service.py +++ /dev/null @@ -1,116 +0,0 @@ -from typing import List, Optional, Tuple, Dict, Any -from sqlalchemy.orm import Session -from sqlalchemy import cast, String, select -from sqlalchemy.exc import IntegrityError -from fastapi import HTTPException -import logging - -from .models import Legend -from .dto import LegendCreate, LegendUpdate - -logger = logging.getLogger(__name__) - - -class LegendService: - @staticmethod - def get_all( - db: Session, - tenant_id: int, - company_id: int, - skip: int = 0, - limit: int = 100, - filters: Optional[Dict[str, Any]] = None - ) -> Tuple[List[Legend], int]: - query = db.query(Legend).filter( - Legend.tenant_id == tenant_id, - Legend.company_id == company_id - ) - - if filters: - if filters.get("code"): - code_str = str(filters["code"]).strip() - if code_str: - query = query.filter( - cast(Legend.code, String).ilike(f"%{code_str}%") - ) - if filters.get("description"): - query = query.filter( - Legend.description.ilike(f"%{filters['description']}%") - ) - - total = query.count() - items = query.offset(skip).limit(limit).all() - return items, total - - @staticmethod - def get_by_id( - db: Session, - id: int, - tenant_id: int, - company_id: int - ) -> Optional[Legend]: - return db.query(Legend).filter( - Legend.id == id, - Legend.tenant_id == tenant_id, - Legend.company_id == company_id - ).first() - - @staticmethod - def create( - db: Session, - data: LegendCreate, - tenant_id: int, - company_id: int - ) -> Legend: - db_obj = Legend( - **data.model_dump(), - tenant_id=tenant_id, - company_id=company_id - ) - db.add(db_obj) - db.commit() - db.refresh(db_obj) - return db_obj - - @staticmethod - def update( - db: Session, - id: int, - tenant_id: int, - data: LegendUpdate, - company_id: int - ) -> Optional[Legend]: - db_obj = LegendService.get_by_id(db, id, tenant_id, company_id) - if not db_obj: - return None - - update_dict = data.model_dump(exclude_unset=True) - for key, value in update_dict.items(): - setattr(db_obj, key, value) - - db.commit() - db.refresh(db_obj) - return db_obj - - @staticmethod - def delete( - db: Session, - id: int, - tenant_id: int, - company_id: int - ) -> bool: - db_obj = LegendService.get_by_id(db, id, tenant_id, company_id) - if not db_obj: - return False - - try: - db.delete(db_obj) - db.commit() - return True - except IntegrityError as e: - db.rollback() - logger.error(f"Error de integridad al eliminar leyenda {id}: {str(e)}") - raise HTTPException( - status_code=400, - detail="No se puede eliminar esta leyenda porque tiene registros relacionados (pedimentos, facturas, etc.). Primero debe eliminar o reasignar esos registros." - ) diff --git a/backend/api/v1/modules/a76/general_catalogs/location/__init__.py b/backend/api/v1/modules/a76/general_catalogs/location/__init__.py deleted file mode 100644 index 5d2af8dd..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/location/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# a76 general_catalogs.location diff --git a/backend/api/v1/modules/a76/general_catalogs/location/dto.py b/backend/api/v1/modules/a76/general_catalogs/location/dto.py deleted file mode 100644 index b1be6e6b..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/location/dto.py +++ /dev/null @@ -1,36 +0,0 @@ -from typing import Optional - -from pydantic import BaseModel, ConfigDict, Field - -from .models import LocationSystem - - -class LocationBase(BaseModel): - clave_localizacion: str = Field( - ..., max_length=20, description="Clave/código de la localización" - ) - localizacion: Optional[str] = Field( - None, max_length=200, description="Nombre o descripción" - ) - system: LocationSystem = Field( - ..., description="Contexto: fixed_asset (FA) o inventory" - ) - - -class LocationCreate(LocationBase): - """Optional extra fields for fixed_asset; used only when system == FIXED_ASSET.""" - department: Optional[str] = Field(None, max_length=100) - responsible: Optional[str] = Field(None, max_length=200) - observations: Optional[str] = Field(None, description="Free text") - - -class LocationUpdate(BaseModel): - clave_localizacion: Optional[str] = Field(None, max_length=20) - localizacion: Optional[str] = Field(None, max_length=200) - system: Optional[LocationSystem] = None - - -class LocationResponse(LocationBase): - id: int - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/a76/general_catalogs/location/models.py b/backend/api/v1/modules/a76/general_catalogs/location/models.py deleted file mode 100644 index 47119425..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/location/models.py +++ /dev/null @@ -1,84 +0,0 @@ -""" -Modelo ORM para catálogo de localización (a76). -Tabla compartida para contexto Fixed Asset (FA) e inventory. -""" - -import enum -from typing import Optional - -from sqlalchemy import Integer, String, Text, UniqueConstraint, ForeignKey -from sqlalchemy.orm import Mapped, mapped_column - -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base - - -class LocationSystem(str, enum.Enum): - """Contexto de uso de la localización.""" - FIXED_ASSET = "fixed_asset" - INVENTORY = "inventory" - - -class Location(Base, TenantScopedMixin, TimestampMixin): - """ - Catálogo de localización (clave + localizacion), compartido por FA e inventory. - """ - - __tablename__ = "location" - __table_args__ = ( - UniqueConstraint( - "clave_localizacion", - "tenant_id", - "company_id", - "system", - name="uq_location_clave_tenant_company_system", - ), - {"schema": "a76"}, - ) - - id: Mapped[int] = mapped_column( - Integer, primary_key=True, autoincrement=True - ) - clave_localizacion: Mapped[str] = mapped_column(String(20), nullable=False) - localizacion: Mapped[Optional[str]] = mapped_column(String(200), nullable=True) - system: Mapped[str] = mapped_column( - String(20), nullable=False - ) # 'fixed_asset' | 'inventory' - - def __repr__(self) -> str: - return ( - f"" - ) - - -class FaLocationExt(Base, TenantScopedMixin, TimestampMixin): - """ - Extra info for Fixed Asset locations only (1:1 with Location). - Table: Fa_Location_Ext (fa_location_ext). - """ - - __tablename__ = "fa_location_ext" - __table_args__ = ( - UniqueConstraint("location_id", name="uq_fa_location_ext_location_id"), - {"schema": "a76"}, - ) - - id: Mapped[int] = mapped_column( - Integer, primary_key=True, autoincrement=True - ) - location_id: Mapped[int] = mapped_column( - Integer, - ForeignKey("a76.location.id", ondelete="CASCADE"), - nullable=False, - unique=True, - ) - department: Mapped[Optional[str]] = mapped_column(String(100), nullable=True) - responsible: Mapped[Optional[str]] = mapped_column(String(200), nullable=True) - observations: Mapped[Optional[str]] = mapped_column(Text, nullable=True) - - def __repr__(self) -> str: - return ( - f"" - ) diff --git a/backend/api/v1/modules/a76/general_catalogs/location/routes.py b/backend/api/v1/modules/a76/general_catalogs/location/routes.py deleted file mode 100644 index 529ad1bf..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/location/routes.py +++ /dev/null @@ -1,25 +0,0 @@ -from fastapi import APIRouter - -from api.v1.common.tenant_crud_routes import TenantCRUDRoutes - -from .dto import LocationCreate, LocationResponse, LocationUpdate -from .models import Location -from .service import LocationService - -router = TenantCRUDRoutes( - service=LocationService, - create_schema=LocationCreate, - update_schema=LocationUpdate, - response_schema=LocationResponse, - prefix="/locations", - tags=["a76.general_catalogs.locations"], - resource_name="Location", - enable_list=True, - enable_filters=True, - max_page_size=1000, - list_permissions=["cat_locations.view"], - get_permissions=["cat_locations.view"], - create_permissions=["cat_locations.create"], - update_permissions=["cat_locations.edit"], - delete_permissions=["cat_locations.delete"], -).router diff --git a/backend/api/v1/modules/a76/general_catalogs/location/service.py b/backend/api/v1/modules/a76/general_catalogs/location/service.py deleted file mode 100644 index fb48d4ba..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/location/service.py +++ /dev/null @@ -1,154 +0,0 @@ -from typing import Any, Dict, List, Optional, Tuple - -from sqlalchemy.orm import Session -from sqlalchemy.exc import IntegrityError - -from .models import Location, FaLocationExt -from .dto import LocationCreate, LocationUpdate - - -class LocationService: - @staticmethod - def get_all( - db: Session, - tenant_id: int, - company_id: Optional[int], - skip: int = 0, - limit: int = 100, - filters: Optional[Dict[str, Any]] = None, - ) -> Tuple[List[Location], int]: - query = db.query(Location).filter(Location.tenant_id == tenant_id) - - if company_id is not None: - query = query.filter(Location.company_id == company_id) - - if filters: - if filters.get("clave_localizacion"): - query = query.filter( - Location.clave_localizacion.ilike( - f"%{filters['clave_localizacion']}%" - ) - ) - if filters.get("localizacion"): - query = query.filter( - Location.localizacion.ilike(f"%{filters['localizacion']}%") - ) - if filters.get("system"): - query = query.filter(Location.system == filters["system"]) - - total = query.count() - items = query.offset(skip).limit(limit).all() - return items, total - - @staticmethod - def get_by_id( - db: Session, - id: int, - tenant_id: int, - company_id: int, - ) -> Optional[Location]: - return ( - db.query(Location) - .filter( - Location.id == id, - Location.tenant_id == tenant_id, - Location.company_id == company_id, - ) - .first() - ) - - @staticmethod - def get_by_clave( - db: Session, - clave_localizacion: str, - tenant_id: int, - company_id: int, - system: Optional[str] = None, - ) -> Optional[Location]: - query = db.query(Location).filter( - Location.clave_localizacion == clave_localizacion, - Location.tenant_id == tenant_id, - Location.company_id == company_id, - ) - if system is not None: - query = query.filter(Location.system == system) - return query.first() - - @staticmethod - def create( - db: Session, - data: LocationCreate, - tenant_id: int, - company_id: int, - ) -> Location: - db_obj = Location( - clave_localizacion=data.clave_localizacion, - localizacion=data.localizacion, - system=data.system.value, - tenant_id=tenant_id, - company_id=company_id, - ) - db.add(db_obj) - try: - db.flush() # get db_obj.id without committing - if data.system.value == "fixed_asset" and ( - data.department is not None - or data.responsible is not None - or data.observations is not None - ): - ext = FaLocationExt( - location_id=db_obj.id, - department=data.department, - responsible=data.responsible, - observations=data.observations, - tenant_id=tenant_id, - company_id=company_id, - ) - db.add(ext) - db.commit() - db.refresh(db_obj) - return db_obj - except IntegrityError: - db.rollback() - raise ValueError("Ya existe una localización con esa clave y system") - - @staticmethod - def update( - db: Session, - id: int, - tenant_id: int, - data: LocationUpdate, - company_id: int, - ) -> Optional[Location]: - db_obj = LocationService.get_by_id(db, id, tenant_id, company_id) - if not db_obj: - return None - - update_dict = data.model_dump(exclude_unset=True) - for key, value in update_dict.items(): - if key == "system" and value is not None: - setattr(db_obj, key, value.value) - else: - setattr(db_obj, key, value) - - try: - db.commit() - db.refresh(db_obj) - return db_obj - except IntegrityError: - db.rollback() - raise ValueError("Ya existe una localización con esa clave y system") - - @staticmethod - def delete( - db: Session, - id: int, - tenant_id: int, - company_id: int, - ) -> bool: - db_obj = LocationService.get_by_id(db, id, tenant_id, company_id) - if not db_obj: - return False - db.delete(db_obj) - db.commit() - return True diff --git a/backend/api/v1/modules/a76/general_catalogs/locations/__init__.py b/backend/api/v1/modules/a76/general_catalogs/locations/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/backend/api/v1/modules/a76/general_catalogs/multi_currency_types/__init__.py b/backend/api/v1/modules/a76/general_catalogs/multi_currency_types/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/backend/api/v1/modules/a76/general_catalogs/multi_currency_types/dto.py b/backend/api/v1/modules/a76/general_catalogs/multi_currency_types/dto.py deleted file mode 100644 index b76b798f..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/multi_currency_types/dto.py +++ /dev/null @@ -1,33 +0,0 @@ -from typing import Optional -from decimal import Decimal -from pydantic import BaseModel, Field, ConfigDict - - -class MultiCurrencyTypeBase(BaseModel): - currency_type_code: str = Field(..., max_length=3, - description="Currency Type Code") - country_key: Optional[str] = Field( - None, max_length=3, description="Country Key") - conversion_factor: Optional[Decimal] = Field( - None, description="Conversion Factor") - publication_date: int = Field(..., - description="Publication Date (YYYYMMDD)") - - -class MultiCurrencyTypeCreate(MultiCurrencyTypeBase): - pass - - -class MultiCurrencyTypeUpdate(BaseModel): - currency_type_code: Optional[str] = Field(None, max_length=3) - country_key: Optional[str] = Field(None, max_length=3) - conversion_factor: Optional[Decimal] = None - publication_date: Optional[int] = None - - -class MultiCurrencyTypeResponse(MultiCurrencyTypeBase): - id: int - company_id: int - tenant_id: int - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/a76/general_catalogs/multi_currency_types/models.py b/backend/api/v1/modules/a76/general_catalogs/multi_currency_types/models.py deleted file mode 100644 index e119b8b8..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/multi_currency_types/models.py +++ /dev/null @@ -1,35 +0,0 @@ -from typing import Optional -from decimal import Decimal -from sqlalchemy import Integer, String, ForeignKey, UniqueConstraint, Numeric -from sqlalchemy.orm import Mapped, mapped_column, relationship -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base -from api.v1.modules.public.reference_data.currency_types.models import CurrencyType -from api.v1.modules.public.reference_data.countries.models import Country - - -class MultiCurrencyType(Base, TenantScopedMixin, TimestampMixin): - __tablename__ = "multi_currency_types" - __table_args__ = ( - UniqueConstraint("currency_type_code", "publication_date", "tenant_id", "company_id", - name="uq_multi_currency_type_code_date"), - {"schema": "a76"} - ) - - id: Mapped[int] = mapped_column( - Integer, primary_key=True, autoincrement=True) - - currency_type_code: Mapped[str] = mapped_column( - String(3), ForeignKey("public.currency_types.code"), nullable=False) - - country_key: Mapped[Optional[str]] = mapped_column( - String(3), ForeignKey("public.countries.m3_key"), nullable=True) - - conversion_factor: Mapped[Optional[Decimal]] = mapped_column( - Numeric(13, 6), nullable=True) - - publication_date: Mapped[int] = mapped_column(Integer, nullable=False) - - currency_type: Mapped["CurrencyType"] = relationship() - - country: Mapped[Optional["Country"]] = relationship() diff --git a/backend/api/v1/modules/a76/general_catalogs/multi_currency_types/routes.py b/backend/api/v1/modules/a76/general_catalogs/multi_currency_types/routes.py deleted file mode 100644 index 1dd91120..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/multi_currency_types/routes.py +++ /dev/null @@ -1,27 +0,0 @@ -from fastapi import APIRouter -from api.v1.common.tenant_crud_routes import TenantCRUDRoutes -from .models import MultiCurrencyType -from .dto import MultiCurrencyTypeCreate, MultiCurrencyTypeResponse, MultiCurrencyTypeUpdate -from .service import MultiCurrencyTypeService - -router = APIRouter(prefix="/multi-currency-types", - tags=["a76.general_catalogs.multi_currency_types"]) - -multi_currency_type_crud = TenantCRUDRoutes( - service=MultiCurrencyTypeService, - create_schema=MultiCurrencyTypeCreate, - update_schema=MultiCurrencyTypeUpdate, - response_schema=MultiCurrencyTypeResponse, - prefix="", - tags=["Multi Currency Types"], - resource_name="MultiCurrencyType", - enable_list=True, - enable_filters=True, - list_permissions=["cat_multi_currency_types.view"], - get_permissions=["cat_multi_currency_types.view"], - create_permissions=["cat_multi_currency_types.create"], - update_permissions=["cat_multi_currency_types.edit"], - delete_permissions=["cat_multi_currency_types.delete"], -) - -router.include_router(multi_currency_type_crud.router) diff --git a/backend/api/v1/modules/a76/general_catalogs/multi_currency_types/service.py b/backend/api/v1/modules/a76/general_catalogs/multi_currency_types/service.py deleted file mode 100644 index f9e2523f..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/multi_currency_types/service.py +++ /dev/null @@ -1,103 +0,0 @@ -from typing import List, Optional, Tuple, Dict, Any -from sqlalchemy.orm import Session -from sqlalchemy import select - -from .models import MultiCurrencyType -from .dto import MultiCurrencyTypeCreate, MultiCurrencyTypeUpdate - - -class MultiCurrencyTypeService: - @staticmethod - def get_all( - db: Session, - tenant_id: int, - company_id: int, - skip: int = 0, - limit: int = 100, - filters: Optional[Dict[str, Any]] = None - ) -> Tuple[List[MultiCurrencyType], int]: - query = db.query(MultiCurrencyType).filter( - MultiCurrencyType.tenant_id == tenant_id, - MultiCurrencyType.company_id == company_id - ) - - if filters: - if filters.get('currency_type_code'): - query = query.filter( - MultiCurrencyType.currency_type_code.ilike(f"%{filters['currency_type_code']}%") - ) - if filters.get('country_key'): - query = query.filter( - MultiCurrencyType.country_key.ilike(f"%{filters['country_key']}%") - ) - - total = query.count() - items = query.offset(skip).limit(limit).all() - return items, total - - @staticmethod - def get_by_id( - db: Session, - id: int, - tenant_id: int, - company_id: int - ) -> Optional[MultiCurrencyType]: - return db.query(MultiCurrencyType).filter( - MultiCurrencyType.id == id, - MultiCurrencyType.tenant_id == tenant_id, - MultiCurrencyType.company_id == company_id - ).first() - - @staticmethod - def create( - db: Session, - data: MultiCurrencyTypeCreate, - tenant_id: int, - company_id: int - ) -> MultiCurrencyType: - db_obj = MultiCurrencyType( - **data.model_dump(), - tenant_id=tenant_id, - company_id=company_id - ) - db.add(db_obj) - db.commit() - db.refresh(db_obj) - return db_obj - - @staticmethod - def update( - db: Session, - id: int, - data: MultiCurrencyTypeUpdate, - tenant_id: int, - company_id: int - ) -> Optional[MultiCurrencyType]: - db_obj = MultiCurrencyTypeService.get_by_id( - db, id, tenant_id, company_id) - if not db_obj: - return None - - update_dict = data.model_dump(exclude_unset=True) - for key, value in update_dict.items(): - setattr(db_obj, key, value) - - db.commit() - db.refresh(db_obj) - return db_obj - - @staticmethod - def delete( - db: Session, - id: int, - tenant_id: int, - company_id: int - ) -> bool: - db_obj = MultiCurrencyTypeService.get_by_id( - db, id, tenant_id, company_id) - if not db_obj: - return False - - db.delete(db_obj) - db.commit() - return True diff --git a/backend/api/v1/modules/a76/general_catalogs/packages/dto.py b/backend/api/v1/modules/a76/general_catalogs/packages/dto.py deleted file mode 100644 index 09e421df..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/packages/dto.py +++ /dev/null @@ -1,41 +0,0 @@ -""" -DTOs for Packages (GBultos). -""" - -from datetime import datetime -from typing import Optional - -from pydantic import BaseModel, Field - - -class PackageBaseDTO(BaseModel): - key: str = Field(..., description="Package key (primary identifier)", max_length=5) - description_es: Optional[str] = Field(None, description="Description in Spanish", max_length=40) - description_en: Optional[str] = Field(None, description="Description in English", max_length=40) - weight_unit: Optional[float] = Field(None, description="Weight unit") - plurals: Optional[str] = Field(None, max_length=4) - plural_in: Optional[str] = Field(None, max_length=4) - code_ace: Optional[str] = Field(None, max_length=4) - code_aamex: Optional[str] = Field(None, max_length=9) - - -class PackageCreateDTO(PackageBaseDTO): - """Schema for creating a package""" - pass - - -class PackageUpdateDTO(PackageBaseDTO): - """Schema for updating a package""" - key: Optional[str] = Field(None, description="Package key (cannot be modified)", max_length=5) - - -class PackageResponseDTO(PackageBaseDTO): - """Schema for package response""" - id: int - company_id: int - tenant_id: int - created_at: Optional[datetime] = None - updated_at: Optional[datetime] = None - - class Config: - from_attributes = True diff --git a/backend/api/v1/modules/a76/general_catalogs/packages/models.py b/backend/api/v1/modules/a76/general_catalogs/packages/models.py deleted file mode 100644 index 92b07647..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/packages/models.py +++ /dev/null @@ -1,34 +0,0 @@ -from decimal import Decimal -from typing import Optional - -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base -from sqlalchemy import ( - DECIMAL, - ForeignKeyConstraint, - Integer, - PrimaryKeyConstraint, - String, - UniqueConstraint, -) -from sqlalchemy.orm import Mapped, mapped_column - - -class Package(Base, TenantScopedMixin, TimestampMixin): - __tablename__ = "packages" # GBultos - __table_args__ = ( - PrimaryKeyConstraint("id", name="packages_pkey"), - UniqueConstraint("tenant_id", "company_id", "key", name="packages_key_ukey"), - {"schema": "a76", "extend_existing": True}, - ) - - id: Mapped[int] = mapped_column(Integer, primary_key=True) - - key: Mapped[str] = mapped_column(String(5)) - description_es: Mapped[Optional[str]] = mapped_column(String(40)) - description_en: Mapped[Optional[str]] = mapped_column(String(40)) - weight_unit: Mapped[Optional[Decimal]] = mapped_column(DECIMAL(19, 8)) - plurals: Mapped[Optional[str]] = mapped_column(String(4)) - plural_in: Mapped[Optional[str]] = mapped_column(String(4)) - code_ace: Mapped[Optional[str]] = mapped_column(String(4)) - code_aamex: Mapped[Optional[str]] = mapped_column(String(9)) \ No newline at end of file diff --git a/backend/api/v1/modules/a76/general_catalogs/packages/routes.py b/backend/api/v1/modules/a76/general_catalogs/packages/routes.py deleted file mode 100644 index dd9d519c..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/packages/routes.py +++ /dev/null @@ -1,30 +0,0 @@ -""" -Routes for managing Packages (GBultos). -""" - -from api.v1.common.tenant_crud_routes import TenantCRUDRoutes - -from .dto import PackageCreateDTO, PackageResponseDTO, PackageUpdateDTO -from .services import PackageService - -# Create router using TenantCRUDRoutes factory -router = TenantCRUDRoutes( - service=PackageService, - create_schema=PackageCreateDTO, - update_schema=PackageUpdateDTO, - response_schema=PackageResponseDTO, - prefix="/packages", - tags=["a76 / packages"], - resource_name="Package", - id_name="package_id", - enable_list=True, # Enable GET /packages with pagination - enable_filters=True, # Enable filtering by key and description_es - default_page_size=50, - max_page_size=100, - # Permisos - list_permissions=["cat_packages.view"], - get_permissions=["cat_packages.view"], - create_permissions=["cat_packages.create"], - update_permissions=["cat_packages.edit"], - delete_permissions=["cat_packages.delete"], -).router diff --git a/backend/api/v1/modules/a76/general_catalogs/packages/services.py b/backend/api/v1/modules/a76/general_catalogs/packages/services.py deleted file mode 100644 index cea4d882..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/packages/services.py +++ /dev/null @@ -1,133 +0,0 @@ -""" -Service layer for Packages (GBultos). -""" - -from typing import Optional, Tuple, List, Dict, Any -import logging - -from sqlalchemy.orm import Session -from sqlalchemy.exc import IntegrityError -from fastapi import HTTPException - -from . import dto, models - -logger = logging.getLogger(__name__) - - -class PackageService: - """Service for Package CRUD operations with tenant support""" - - @staticmethod - def get_all( - db: Session, - tenant_id: int, - company_id: int, - skip: int = 0, - limit: int = 50, - filters: Optional[Dict[str, Any]] = None, - ) -> Tuple[List[models.Package], int]: - """Get all packages for a tenant/company with pagination""" - query = db.query(models.Package).filter( - models.Package.tenant_id == tenant_id, - models.Package.company_id == company_id, - ) - - # Apply filters if provided - if filters: - if filters.get("key"): - query = query.filter( - models.Package.key.ilike(f"%{filters['key']}%") - ) - if filters.get("description_es"): - query = query.filter( - models.Package.description_es.ilike( - f"%{filters['description_es']}%") - ) - - total = query.count() - packages = query.offset(skip).limit(limit).all() - - return packages, total - - @staticmethod - def get_by_id( - db: Session, package_id: int, tenant_id: int, company_id: int - ) -> Optional[models.Package]: - """Get package by ID""" - return ( - db.query(models.Package) - .filter( - models.Package.id == package_id, - models.Package.tenant_id == tenant_id, - models.Package.company_id == company_id, - ) - .first() - ) - - @staticmethod - def create( - db: Session, - package_data: dto.PackageCreateDTO, - tenant_id: int, - company_id: int, - ) -> models.Package: - """Create a new package""" - new_package = models.Package( - **package_data.model_dump(), tenant_id=tenant_id, company_id=company_id - ) - db.add(new_package) - db.commit() - db.refresh(new_package) - return new_package - - @staticmethod - def update( - db: Session, - package_id: int, - tenant_id: int, - package_data: dto.PackageUpdateDTO, - company_id: int, - ) -> Optional[models.Package]: - """Update a package""" - package = PackageService.get_by_id( - db, package_id, tenant_id, company_id) - if not package: - return None - - # Update fields (excluding key if it's meant to be immutable) - update_data = package_data.model_dump( - exclude_unset=True, exclude={"key"}) - for field, value in update_data.items(): - setattr(package, field, value) - - db.commit() - db.refresh(package) - return package - - @staticmethod - def delete( - db: Session, package_id: int, tenant_id: int, company_id: int - ) -> bool: - """Delete a package""" - package = PackageService.get_by_id( - db, package_id, tenant_id, company_id) - if not package: - return False - - try: - db.delete(package) - db.commit() - return True - except IntegrityError as e: - db.rollback() - logger.error(f"IntegrityError deleting package {package_id}: {str(e)}") - if "foreign key constraint" in str(e).lower(): - raise HTTPException( - status_code=400, - detail="No se puede eliminar el bulto porque tiene registros relacionados" - ) - raise HTTPException(status_code=400, detail="Error al eliminar el bulto") - except Exception as e: - db.rollback() - logger.error(f"Error deleting package {package_id}: {str(e)}") - raise HTTPException(status_code=500, detail="Error al eliminar el bulto") diff --git a/backend/api/v1/modules/a76/general_catalogs/packages/test_package.py b/backend/api/v1/modules/a76/general_catalogs/packages/test_package.py deleted file mode 100644 index 4bb4c007..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/packages/test_package.py +++ /dev/null @@ -1,36 +0,0 @@ -import pytest -from fastapi import FastAPI -from fastapi.testclient import TestClient - -from .routes import router - -app = FastAPI() -app.include_router(router) -client = TestClient(app) - - -@pytest.mark.usefixtures("client", "access_token") -def test_list_packages(client, access_token): - headers = {"Authorization": f"Bearer {access_token}"} - response = client.get("/bultos/", headers=headers) - assert response.status_code == 200 - assert "items" in response.json() - assert "page" in response.json() - assert "page_size" in response.json() - - -@pytest.mark.usefixtures("client", "access_token") -def test_get_package_not_found(client, access_token): - headers = {"Authorization": f"Bearer {access_token}"} - response = client.get("/bultos/invalid_id", headers=headers) - assert response.status_code == 404 - - -def test_create_package_forbidden(): - response = client.post("/bultos/", json={"name": "Test Package"}) - assert response.status_code in (403, 405, 404) - - -def test_update_package_forbidden(): - response = client.put("/bultos/1", json={"name": "Updated Package"}) - assert response.status_code in (403, 405, 404) diff --git a/backend/api/v1/modules/a76/general_catalogs/ports/__init__.py b/backend/api/v1/modules/a76/general_catalogs/ports/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/backend/api/v1/modules/a76/general_catalogs/ports/dto.py b/backend/api/v1/modules/a76/general_catalogs/ports/dto.py deleted file mode 100644 index d173d9ea..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/ports/dto.py +++ /dev/null @@ -1,32 +0,0 @@ -from typing import Optional -from pydantic import BaseModel, Field, ConfigDict -from .models import PortType - - -class PortBase(BaseModel): - port_code: str = Field(..., max_length=6, description="Port Code") - description: Optional[str] = Field( - None, max_length=20, description="Description") - location_code: str = Field(..., max_length=4, description="Location Code") - location_description: Optional[str] = Field( - None, max_length=20, description="Location Description") - port_type: PortType = Field( - server_default=PortType.ENTRY, description="Port Type (ENTRY, EXIT, BOTH)") - - -class PortCreate(PortBase): - pass - - -class PortUpdate(BaseModel): - port_code: Optional[str] = Field(None, max_length=6) - description: Optional[str] = Field(None, max_length=20) - location_code: Optional[str] = Field(None, max_length=4) - location_description: Optional[str] = Field(None, max_length=20) - port_type: Optional[PortType] = None - - -class PortResponse(PortBase): - id: int - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/a76/general_catalogs/ports/models.py b/backend/api/v1/modules/a76/general_catalogs/ports/models.py deleted file mode 100644 index 5507b50a..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/ports/models.py +++ /dev/null @@ -1,36 +0,0 @@ -from typing import Optional -from sqlalchemy import Integer, String, UniqueConstraint, Enum -from sqlalchemy.orm import Mapped, mapped_column -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base -import enum - - -class PortType(str, enum.Enum): - ENTRY = "ENTRY" - EXIT = "EXIT" - BOTH = "BOTH" - DESTINATION = "DESTINATION" - - -class Port(Base, TenantScopedMixin, TimestampMixin): - __tablename__ = "ports" - __table_args__ = ( - UniqueConstraint("port_code", "location_code", "tenant_id", "company_id", - name="uq_port_location"), - {"schema": "a76"} - ) - - id: Mapped[int] = mapped_column( - Integer, primary_key=True, autoincrement=True) - port_code: Mapped[str] = mapped_column(String(6), nullable=False) # PUERTO - description: Mapped[Optional[str]] = mapped_column( - String(20), nullable=True) # DESCRIPCION - location_code: Mapped[str] = mapped_column( - String(4), nullable=False) # LOCALIZACION - location_description: Mapped[Optional[str]] = mapped_column( - String(20), nullable=True) # DESCLOCALIZACION - - # New column requested - port_type: Mapped[PortType] = mapped_column( - String(15), nullable=False, server_default=PortType.ENTRY) diff --git a/backend/api/v1/modules/a76/general_catalogs/ports/routes.py b/backend/api/v1/modules/a76/general_catalogs/ports/routes.py deleted file mode 100644 index cd3f1a6a..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/ports/routes.py +++ /dev/null @@ -1,22 +0,0 @@ -from fastapi import APIRouter -from api.v1.common.tenant_crud_routes import TenantCRUDRoutes -from .models import Port -from .dto import PortCreate, PortResponse, PortUpdate -from .service import PortService - -router = TenantCRUDRoutes( - service=PortService, - create_schema=PortCreate, - update_schema=PortUpdate, - response_schema=PortResponse, - prefix="/ports", - tags=["a76.general_catalogs.ports"], - resource_name="Port", - enable_list=True, - max_page_size=1000, - list_permissions=["cat_ports.view"], - get_permissions=["cat_ports.view"], - create_permissions=["cat_ports.create"], - update_permissions=["cat_ports.edit"], - delete_permissions=["cat_ports.delete"], -).router diff --git a/backend/api/v1/modules/a76/general_catalogs/ports/service.py b/backend/api/v1/modules/a76/general_catalogs/ports/service.py deleted file mode 100644 index 7e5e1af2..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/ports/service.py +++ /dev/null @@ -1,94 +0,0 @@ -from typing import List, Optional, Tuple, Dict, Any -from sqlalchemy.orm import Session -from sqlalchemy import select - -from .models import Port -from .dto import PortCreate, PortUpdate - - -class PortService: - @staticmethod - def get_all( - db: Session, - tenant_id: int, - company_id: Optional[int], - skip: int = 0, - limit: int = 100, - filters: Optional[Dict[str, Any]] = None - ) -> Tuple[List[Port], int]: - query = db.query(Port).filter(Port.tenant_id == tenant_id) - - if company_id is not None: - query = query.filter(Port.company_id == company_id) - - if filters: - # Add filters here if needed - pass - - total = query.count() - items = query.offset(skip).limit(limit).all() - return items, total - - @staticmethod - def get_by_id( - db: Session, - id: int, - tenant_id: int, - company_id: int - ) -> Optional[Port]: - return db.query(Port).filter( - Port.id == id, - Port.tenant_id == tenant_id - ).first() - - @staticmethod - def create( - db: Session, - data: PortCreate, - tenant_id: int, - company_id: int - ) -> Port: - db_obj = Port( - **data.model_dump(), - tenant_id=tenant_id, - company_id=company_id - ) - db.add(db_obj) - db.commit() - db.refresh(db_obj) - return db_obj - - @staticmethod - def update( - db: Session, - id: int, - tenant_id: int, - data: PortUpdate, - company_id: int - ) -> Optional[Port]: - db_obj = PortService.get_by_id(db, id, tenant_id, company_id) - if not db_obj: - return None - - update_dict = data.model_dump(exclude_unset=True) - for key, value in update_dict.items(): - setattr(db_obj, key, value) - - db.commit() - db.refresh(db_obj) - return db_obj - - @staticmethod - def delete( - db: Session, - id: int, - tenant_id: int, - company_id: int - ) -> bool: - db_obj = PortService.get_by_id(db, id, tenant_id, company_id) - if not db_obj: - return False - - db.delete(db_obj) - db.commit() - return True diff --git a/backend/api/v1/modules/a76/general_catalogs/prevalidators/__init__.py b/backend/api/v1/modules/a76/general_catalogs/prevalidators/__init__.py deleted file mode 100644 index c55b2979..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/prevalidators/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -""" -Módulo de prevalidadores -""" diff --git a/backend/api/v1/modules/a76/general_catalogs/prevalidators/dto.py b/backend/api/v1/modules/a76/general_catalogs/prevalidators/dto.py deleted file mode 100644 index 1f974685..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/prevalidators/dto.py +++ /dev/null @@ -1,58 +0,0 @@ -""" -DTOs (Data Transfer Objects) para módulo de prevalidadores -""" - -from typing import Optional - -from pydantic import BaseModel, Field - - -class PrevalidatorCreateDTO(BaseModel): - """DTO para crear un prevalidador""" - - code: str = Field(..., max_length=20, description="Prevalidator code") - customs_prevalidator: Optional[str] = Field( - None, max_length=20, description="Customs prevalidator" - ) - patent_prevalidator: Optional[str] = Field( - None, max_length=20, description="Patent prevalidator" - ) - description: Optional[str] = Field( - None, max_length=50, description="Description" - ) - - class Config: - from_attributes = True - - -class PrevalidatorUpdateDTO(BaseModel): - """DTO para actualizar un prevalidador""" - - code: Optional[str] = Field( - None, max_length=20, description="Prevalidator code" - ) - customs_prevalidator: Optional[str] = Field( - None, max_length=20, description="Customs prevalidator" - ) - patent_prevalidator: Optional[str] = Field( - None, max_length=20, description="Patent prevalidator" - ) - description: Optional[str] = Field( - None, max_length=50, description="Description" - ) - - class Config: - from_attributes = True - - -class PrevalidatorResponseDTO(BaseModel): - """DTO para responder con datos de un prevalidador""" - - id: int - code: str - customs_prevalidator: Optional[str] = None - patent_prevalidator: Optional[str] = None - description: Optional[str] = None - - class Config: - from_attributes = True diff --git a/backend/api/v1/modules/a76/general_catalogs/prevalidators/models.py b/backend/api/v1/modules/a76/general_catalogs/prevalidators/models.py deleted file mode 100644 index a9d0f2b8..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/prevalidators/models.py +++ /dev/null @@ -1,40 +0,0 @@ -""" -Modelos ORM para gestión de prevalidadores -""" - -from typing import Optional - -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base -from sqlalchemy import Integer, PrimaryKeyConstraint, String, UniqueConstraint -from sqlalchemy.orm import Mapped, mapped_column - - -class Prevalidator(Base, TenantScopedMixin, TimestampMixin): - """ - Modelo para la tabla Prevalidator - Prevalidadores - """ - - __tablename__ = "prevalidators" # GPrevalidadores - __table_args__ = ( - PrimaryKeyConstraint("id", name="prevalidators_pkey"), - UniqueConstraint("code", "tenant_id", "company_id", - name="prevalidators_code_unique"), - {"schema": "a76"}, - ) - - # Primary key - id: Mapped[int] = mapped_column(Integer, primary_key=True) - - # Prevalidator code (unique) - code: Mapped[str] = mapped_column(String(20), nullable=False) - - # Prevalidator information - customs_prevalidator: Mapped[Optional[str]] = mapped_column(String(20)) - - patent_prevalidator: Mapped[Optional[str]] = mapped_column(String(20)) - - description: Mapped[Optional[str]] = mapped_column(String(50)) - - def __repr__(self): - return f"" diff --git a/backend/api/v1/modules/a76/general_catalogs/prevalidators/routes.py b/backend/api/v1/modules/a76/general_catalogs/prevalidators/routes.py deleted file mode 100644 index 605fdb09..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/prevalidators/routes.py +++ /dev/null @@ -1,148 +0,0 @@ -""" -Rutas para gestión de prevalidadores -""" - -from typing import List - -from fastapi import APIRouter, Depends, HTTPException, Query, status -from sqlalchemy.orm import Session - -from core.database import get_core_db -from core.security import get_current_user -from api.v1.common.tenant_crud_routes import TenantCRUDRoutes, validate_access_to_resource -from .dto import ( - PrevalidatorCreateDTO, - PrevalidatorResponseDTO, - PrevalidatorUpdateDTO, -) -from .models import Prevalidator -from .service import PrevalidatorService - -router = APIRouter(prefix="/prevalidators", tags=["prevalidators"]) - -prevalidator_crud = TenantCRUDRoutes( - service=PrevalidatorService, - create_schema=PrevalidatorCreateDTO, - update_schema=PrevalidatorUpdateDTO, - response_schema=PrevalidatorResponseDTO, - prefix="", - tags=["Prevalidators"], - resource_name="Prevalidator", - enable_list=True, - enable_filters=True, - list_permissions=["cat_prevalidators.view"], - get_permissions=["cat_prevalidators.view"], - create_permissions=["cat_prevalidators.create"], - update_permissions=["cat_prevalidators.edit"], - delete_permissions=["cat_prevalidators.delete"], -) - -# Add custom endpoints - - -@prevalidator_crud.router.get( - "/code/{code}", - response_model=PrevalidatorResponseDTO, - summary="Get prevalidator by code", -) -async def get_prevalidator_by_code( - code: str, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - """Get a prevalidator by its code""" - tenant_id = validate_access_to_resource( - db, company_id, current_user, ["cat_prevalidators.view"] - ) - prevalidator = PrevalidatorService.get_by_code( - db, - code, - tenant_id=tenant_id, - company_id=company_id, - ) - if not prevalidator: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="Prevalidator not found", - ) - return PrevalidatorResponseDTO.model_validate(prevalidator) - -router.include_router(prevalidator_crud.router) - - -async def create_prevalidator( - prevalidator_data: PrevalidatorCreateDTO, - db: Session = Depends(get_core_db), -): - """Create a new prevalidator""" - prevalidator = PrevalidatorService.create(db, prevalidator_data) - return PrevalidatorResponseDTO.model_validate(prevalidator) - - -@router.put( - "/{prevalidator_id}", - response_model=PrevalidatorResponseDTO, - summary="Update prevalidator", -) -async def update_prevalidator( - prevalidator_id: int, - prevalidator_data: PrevalidatorUpdateDTO, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - """Update a prevalidator""" - validate_access_to_resource(db, company_id, current_user, ["cat_prevalidators.edit"]) - prevalidator = PrevalidatorService.update( - db, prevalidator_id, prevalidator_data) - if not prevalidator: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="Prevalidator not found", - ) - return PrevalidatorResponseDTO.model_validate(prevalidator) - - -@router.delete( - "/{prevalidator_id}", - status_code=status.HTTP_204_NO_CONTENT, - summary="Delete prevalidator", -) -async def delete_prevalidator( - prevalidator_id: int, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - """Delete a prevalidator""" - validate_access_to_resource(db, company_id, current_user, ["cat_prevalidators.delete"]) - success = PrevalidatorService.delete(db, prevalidator_id) - if not success: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="Prevalidator not found", - ) - return None - - -@router.get( - "/by-customs/{customs}", - response_model=List[PrevalidatorResponseDTO], - summary="Get prevalidators by customs", -) -async def get_prevalidators_by_customs( - customs: str, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - """Get all prevalidators for a specific customs""" - tenant_id = validate_access_to_resource( - db, company_id, current_user, ["cat_prevalidators.view"] - ) - prevalidators = PrevalidatorService.get_by_customs(db, customs, tenant_id, company_id) - return [ - PrevalidatorResponseDTO.model_validate(prevalidator) - for prevalidator in prevalidators - ] diff --git a/backend/api/v1/modules/a76/general_catalogs/prevalidators/service.py b/backend/api/v1/modules/a76/general_catalogs/prevalidators/service.py deleted file mode 100644 index 4f51d8f0..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/prevalidators/service.py +++ /dev/null @@ -1,193 +0,0 @@ -""" -Capa de servicio para lógica de negocio de prevalidadores -""" - -import logging -from typing import Any, Dict, List, Optional, Tuple - -from fastapi import HTTPException -from sqlalchemy.exc import IntegrityError -from sqlalchemy.orm import Session - -from .dto import ( - PrevalidatorCreateDTO, - PrevalidatorResponseDTO, - PrevalidatorUpdateDTO, -) -from .models import Prevalidator - -logger = logging.getLogger(__name__) - - -class PrevalidatorService: - """Servicio para gestión de prevalidadores""" - - @staticmethod - def get_all( - db: Session, - tenant_id: int, - company_id: int, - skip: int = 0, - limit: int = 50, - filters: Optional[Dict[str, Any]] = None, - ) -> Tuple[List[Prevalidator], int]: - """Get all prevalidators with pagination""" - query = db.query(Prevalidator).filter( - Prevalidator.tenant_id == tenant_id, - Prevalidator.company_id == company_id - ) - - # Apply filters if provided - if filters: - if filters.get("code"): - query = query.filter( - Prevalidator.code.ilike(f"%{filters['code']}%") - ) - if filters.get("description"): - query = query.filter( - Prevalidator.description.ilike( - f"%{filters['description']}%") - ) - if filters.get("customs_prevalidator"): - query = query.filter( - Prevalidator.customs_prevalidator.ilike( - f"%{filters['customs_prevalidator']}%" - ) - ) - - total = query.count() - prevalidators = query.offset(skip).limit(limit).all() - - return prevalidators, total - - @staticmethod - def get_by_code( - db: Session, code: str, tenant_id: int, company_id: int - ) -> Optional[Prevalidator]: - """Get prevalidator by code""" - return db.query(Prevalidator).filter( - Prevalidator.code == code, - Prevalidator.tenant_id == tenant_id, - Prevalidator.company_id == company_id - ).first() - - @staticmethod - def get_by_id( - db: Session, prevalidator_id: int, tenant_id: int, company_id: int - ) -> Optional[Prevalidator]: - """Get prevalidator by ID""" - return db.query(Prevalidator).filter( - Prevalidator.id == prevalidator_id, - Prevalidator.tenant_id == tenant_id, - Prevalidator.company_id == company_id - ).first() - - @staticmethod - def create( - db: Session, - prevalidator_data: PrevalidatorCreateDTO, - tenant_id: int, - company_id: int - ) -> Prevalidator: - """Create a new prevalidator""" - try: - db_prevalidator = Prevalidator( - **prevalidator_data.model_dump(exclude_unset=True), - tenant_id=tenant_id, - company_id=company_id - ) - - db.add(db_prevalidator) - db.commit() - db.refresh(db_prevalidator) - - return db_prevalidator - - except IntegrityError as e: - db.rollback() - logger.error(f"IntegrityError creating prevalidator: {str(e)}") - raise HTTPException( - status_code=400, - detail="Prevalidator already exists", - ) - except Exception as e: - db.rollback() - logger.error(f"Error creating prevalidator: {str(e)}") - raise HTTPException( - status_code=500, detail="Error creating prevalidator") - - @staticmethod - def update( - db: Session, - prevalidator_id: int, - tenant_id: int, - prevalidator_data: PrevalidatorUpdateDTO, - company_id: int - ) -> Optional[Prevalidator]: - """Update a prevalidator""" - try: - db_prevalidator = PrevalidatorService.get_by_id( - db, prevalidator_id, tenant_id, company_id - ) - if not db_prevalidator: - return None - - for key, value in prevalidator_data.model_dump(exclude_unset=True).items(): - setattr(db_prevalidator, key, value) - - db.commit() - db.refresh(db_prevalidator) - - return db_prevalidator - - except IntegrityError as e: - db.rollback() - logger.error(f"IntegrityError updating prevalidator: {str(e)}") - raise HTTPException( - status_code=400, - detail="Error updating prevalidator", - ) - except Exception as e: - db.rollback() - logger.error(f"Error updating prevalidator: {str(e)}") - raise HTTPException( - status_code=500, detail="Error updating prevalidator" - ) - - @staticmethod - def delete( - db: Session, prevalidator_id: int, tenant_id: int, company_id: int - ) -> bool: - """Delete a prevalidator""" - try: - db_prevalidator = PrevalidatorService.get_by_id( - db, prevalidator_id, tenant_id, company_id - ) - if not db_prevalidator: - return False - - db.delete(db_prevalidator) - db.commit() - - return True - - except Exception as e: - db.rollback() - logger.error(f"Error deleting prevalidator: {str(e)}") - raise HTTPException( - status_code=500, detail="Error deleting prevalidator") - - @staticmethod - def get_by_customs( - db: Session, customs: str, tenant_id: int, company_id: int - ) -> List[Prevalidator]: - """Get all prevalidators by customs""" - return ( - db.query(Prevalidator) - .filter( - Prevalidator.customs_prevalidator == customs, - Prevalidator.tenant_id == tenant_id, - Prevalidator.company_id == company_id - ) - .all() - ) diff --git a/backend/api/v1/modules/a76/general_catalogs/router.py b/backend/api/v1/modules/a76/general_catalogs/router.py deleted file mode 100644 index 75975e40..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/router.py +++ /dev/null @@ -1,60 +0,0 @@ -from fastapi import APIRouter -from .company import router as company_router -from .exchange_rate.routes import router as exchange_rate_router -from .identifiers.routes import router as identifiers_router -from .packages.routes import router as package_router -from .ports.routes import router as ports_router -from .fractions.tariff_fractions.routes import router as tariff_fractions_router -from .fractions.us_tariff_fractions.routes import router as us_tariff_fractions_router -from .fractions.historical_tariff_fractions.routes import router as historical_tariff_fractions_router -from .fractions.canadian_tariff_fractions.routes import router as canadian_tariff_fractions_router -from .depreciation_catalog.routes import router as depreciation_catalog_router -from .fda_catalog.routes import router as fda_catalog_router -from .seal.routes import router as seal_router -from .units_of_measure.routes import router as units_of_measure_router -from .concepts.routes import router as concepts_router -from .customs_broker_concepts.routes import router as customs_broker_concepts_router -from .classification_concepts.routes import router as classification_concepts_router -from .unit_conversions.routes import router as unit_conversions_router -from .equivalencies.routes import router as equivalencies_router -from .multi_currency_types.routes import router as multi_currency_types_router -from .inpc.routes import router as inpc_router -from .legends.routes import router as legends_router -from .signatures.routes import router as signatures_router -from .error_catalogs.routes import router as error_catalogs_router -from .doda.routes import router as doda_router -from .prevalidators.routes import router as prevalidators_router -from .electronic_notices.routes import router as electronic_notices_router -from .location.routes import router as location_router -from .sectors.routes import router as sectors_router - -router = APIRouter() - -router.include_router(company_router, tags=["a76 / company"]) -router.include_router(package_router) -router.include_router(ports_router) -router.include_router(location_router) -router.include_router(tariff_fractions_router) -router.include_router(us_tariff_fractions_router) -router.include_router(historical_tariff_fractions_router, prefix="/fractions/historical-tariff-fractions", tags=["a76 / historical_tariff_fractions"]) -router.include_router(canadian_tariff_fractions_router, prefix="/fractions/canadian-tariff-fractions", tags=["a76 / canadian_tariff_fractions"]) -router.include_router(depreciation_catalog_router) -router.include_router(fda_catalog_router) -router.include_router(seal_router, tags=["a76 / seal"]) -router.include_router(units_of_measure_router) -router.include_router(identifiers_router) -router.include_router(exchange_rate_router, tags=["a76 / exchange_rate"]) -router.include_router(concepts_router) -router.include_router(customs_broker_concepts_router) -router.include_router(classification_concepts_router) -router.include_router(unit_conversions_router) -router.include_router(equivalencies_router) -router.include_router(multi_currency_types_router) -router.include_router(inpc_router) -router.include_router(legends_router) -router.include_router(signatures_router) -router.include_router(error_catalogs_router) -router.include_router(doda_router) -router.include_router(prevalidators_router) -router.include_router(electronic_notices_router) -router.include_router(sectors_router) diff --git a/backend/api/v1/modules/a76/general_catalogs/seal/dto.py b/backend/api/v1/modules/a76/general_catalogs/seal/dto.py deleted file mode 100644 index a0d919e8..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/seal/dto.py +++ /dev/null @@ -1,31 +0,0 @@ -""" -DTOs for Seal. -""" - -from typing import Optional - -from pydantic import BaseModel, Field - - -class SealBaseDTO(BaseModel): - seal: str = Field(..., description="Seal identifier", max_length=15) - - -class SealCreateDTO(SealBaseDTO): - """Schema for creating a seal""" - pass - - -class SealUpdateDTO(SealBaseDTO): - """Schema for updating a seal""" - seal: Optional[str] = Field(None, description="Seal identifier", max_length=15) - - -class SealResponseDTO(SealBaseDTO): - """Schema for seal response""" - id: int - company_id: int - tenant_id: int - - class Config: - from_attributes = True diff --git a/backend/api/v1/modules/a76/general_catalogs/seal/models.py b/backend/api/v1/modules/a76/general_catalogs/seal/models.py deleted file mode 100644 index 2e254b40..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/seal/models.py +++ /dev/null @@ -1,22 +0,0 @@ -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base -from sqlalchemy import ( - Integer, - PrimaryKeyConstraint, - String, - UniqueConstraint, -) -from sqlalchemy.orm import Mapped, mapped_column - - -class Seal(Base, TenantScopedMixin, TimestampMixin): - __tablename__ = "seal" - __table_args__ = ( - PrimaryKeyConstraint("id", name="seal_pkey"), - UniqueConstraint("tenant_id", "company_id", "seal", name="seal_ukey"), - {"schema": "a76"}, - ) - - id: Mapped[int] = mapped_column(Integer, primary_key=True) - - seal: Mapped[str] = mapped_column(String(15)) diff --git a/backend/api/v1/modules/a76/general_catalogs/seal/routes.py b/backend/api/v1/modules/a76/general_catalogs/seal/routes.py deleted file mode 100644 index 2a09ddf0..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/seal/routes.py +++ /dev/null @@ -1,29 +0,0 @@ -""" -Routes for managing Seal entries. -""" - -from api.v1.common.tenant_crud_routes import TenantCRUDRoutes - -from .dto import SealCreateDTO, SealResponseDTO, SealUpdateDTO -from .services import SealService - -# Create router using TenantCRUDRoutes factory -router = TenantCRUDRoutes( - service=SealService, - create_schema=SealCreateDTO, - update_schema=SealUpdateDTO, - response_schema=SealResponseDTO, - prefix="/seals", - tags=[], - resource_name="Seal", - id_name="id", # Using numeric ID - enable_list=True, # Enable GET /seals with pagination - enable_filters=True, # Enable filtering by seal - default_page_size=50, - max_page_size=100, - list_permissions=["cat_seals.view"], - get_permissions=["cat_seals.view"], - create_permissions=["cat_seals.create"], - update_permissions=["cat_seals.edit"], - delete_permissions=["cat_seals.delete"], -).router diff --git a/backend/api/v1/modules/a76/general_catalogs/seal/services.py b/backend/api/v1/modules/a76/general_catalogs/seal/services.py deleted file mode 100644 index f29fcb2f..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/seal/services.py +++ /dev/null @@ -1,106 +0,0 @@ -""" -Service layer for Seal. -""" - -from typing import Optional, Tuple, List, Dict, Any - -from sqlalchemy.orm import Session - -from . import dto, models - - -class SealService: - """Service for Seal CRUD operations with tenant support""" - - @staticmethod - def get_all( - db: Session, - tenant_id: int, - company_id: int, - skip: int = 0, - limit: int = 50, - filters: Optional[Dict[str, Any]] = None, - ) -> Tuple[List[models.Seal], int]: - """Get all seals for a tenant/company with pagination""" - query = db.query(models.Seal).filter( - models.Seal.tenant_id == tenant_id, - models.Seal.company_id == company_id, - ) - - # Apply filters if provided - if filters: - if filters.get("seal"): - query = query.filter( - models.Seal.seal.ilike(f"%{filters['seal']}%") - ) - - total = query.count() - seals = query.offset(skip).limit(limit).all() - - return seals, total - - @staticmethod - def get_by_id( - db: Session, seal_id: int, tenant_id: int, company_id: int - ) -> Optional[models.Seal]: - """Get seal by ID""" - return ( - db.query(models.Seal) - .filter( - models.Seal.id == seal_id, - models.Seal.tenant_id == tenant_id, - models.Seal.company_id == company_id, - ) - .first() - ) - - @staticmethod - def create( - db: Session, - seal_data: dto.SealCreateDTO, - tenant_id: int, - company_id: int, - ) -> models.Seal: - """Create a new seal""" - new_seal = models.Seal( - **seal_data.model_dump(), tenant_id=tenant_id, company_id=company_id - ) - db.add(new_seal) - db.commit() - db.refresh(new_seal) - return new_seal - - @staticmethod - def update( - db: Session, - seal_id: int, - tenant_id: int, - seal_data: dto.SealUpdateDTO, - company_id: int, - ) -> Optional[models.Seal]: - """Update a seal""" - seal = SealService.get_by_id(db, seal_id, tenant_id, company_id) - if not seal: - return None - - # Update fields - update_data = seal_data.model_dump(exclude_unset=True) - for field, value in update_data.items(): - setattr(seal, field, value) - - db.commit() - db.refresh(seal) - return seal - - @staticmethod - def delete( - db: Session, seal_id: int, tenant_id: int, company_id: int - ) -> bool: - """Delete a seal""" - seal = SealService.get_by_id(db, seal_id, tenant_id, company_id) - if not seal: - return False - - db.delete(seal) - db.commit() - return True diff --git a/backend/api/v1/modules/a76/general_catalogs/seal/test_seal.py b/backend/api/v1/modules/a76/general_catalogs/seal/test_seal.py deleted file mode 100644 index 22ac8ece..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/seal/test_seal.py +++ /dev/null @@ -1,36 +0,0 @@ -import pytest -from fastapi import FastAPI -from fastapi.testclient import TestClient - -from .routes import router - -app = FastAPI() -app.include_router(router) -client = TestClient(app) - - -@pytest.mark.usefixtures("client", "access_token") -def test_list_seals(client, access_token): - headers = {"Authorization": f"Bearer {access_token}"} - response = client.get("/seal/", headers=headers) - assert response.status_code == 200 - assert "items" in response.json() - assert "page" in response.json() - assert "page_size" in response.json() - - -@pytest.mark.usefixtures("client", "access_token") -def test_get_seal_not_found(client, access_token): - headers = {"Authorization": f"Bearer {access_token}"} - response = client.get("/seal/invalid_id", headers=headers) - assert response.status_code == 404 - - -def test_create_seal_forbidden(): - response = client.post("/seal/", json={"name": "Test Seal"}) - assert response.status_code in (403, 405, 404) - - -def test_update_seal_forbidden(): - response = client.put("/seal/1", json={"name": "Updated Seal"}) - assert response.status_code in (403, 405, 404) diff --git a/backend/api/v1/modules/a76/general_catalogs/sectors/__init__.py b/backend/api/v1/modules/a76/general_catalogs/sectors/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/backend/api/v1/modules/a76/general_catalogs/sectors/dto.py b/backend/api/v1/modules/a76/general_catalogs/sectors/dto.py deleted file mode 100644 index 557dce03..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/sectors/dto.py +++ /dev/null @@ -1,30 +0,0 @@ -from datetime import datetime -from typing import Optional - -from pydantic import BaseModel, ConfigDict, Field - - -class SectorBaseDTO(BaseModel): - key: str = Field(..., description="Clave del sector (ej: 'XIX', 'IIa')", max_length=8) - description: str = Field(..., description="Descripción del sector", max_length=150) - authorized: Optional[bool] = Field(False, description="True = autorizado para PROSEC") - - -class SectorCreateDTO(SectorBaseDTO): - pass - - -class SectorUpdateDTO(BaseModel): - key: Optional[str] = Field(None, max_length=8) - description: Optional[str] = Field(None, max_length=150) - authorized: Optional[bool] = None - - -class SectorResponseDTO(SectorBaseDTO): - id: int - company_id: int - tenant_id: int - created_at: Optional[datetime] = None - updated_at: Optional[datetime] = None - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/a76/general_catalogs/sectors/models.py b/backend/api/v1/modules/a76/general_catalogs/sectors/models.py deleted file mode 100644 index e210332f..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/sectors/models.py +++ /dev/null @@ -1,23 +0,0 @@ -from typing import Optional - -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base -from sqlalchemy import Boolean, Integer, PrimaryKeyConstraint, String, UniqueConstraint -from sqlalchemy.orm import Mapped, mapped_column - - -class Sector(Base, TenantScopedMixin, TimestampMixin): - __tablename__ = "sectors" # GSectores - __table_args__ = ( - PrimaryKeyConstraint("id", name="sectors_pkey"), - UniqueConstraint("tenant_id", "company_id", "key", name="sectors_key_ukey"), - {"schema": "a76", "extend_existing": True}, - ) - - id: Mapped[int] = mapped_column(Integer, primary_key=True) - key: Mapped[str] = mapped_column(String(8), nullable=False) - description: Mapped[str] = mapped_column(String(150), nullable=False) - authorized: Mapped[Optional[bool]] = mapped_column(Boolean, default=False, server_default="false") - - def __repr__(self): - return f"" diff --git a/backend/api/v1/modules/a76/general_catalogs/sectors/routes.py b/backend/api/v1/modules/a76/general_catalogs/sectors/routes.py deleted file mode 100644 index 331a7455..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/sectors/routes.py +++ /dev/null @@ -1,28 +0,0 @@ -""" -Routes for managing Sectors (GSectores) — a76 tenant-scoped catalog. -""" - -from api.v1.common.tenant_crud_routes import TenantCRUDRoutes - -from .dto import SectorCreateDTO, SectorResponseDTO, SectorUpdateDTO -from .service import SectorService - -router = TenantCRUDRoutes( - service=SectorService, - create_schema=SectorCreateDTO, - update_schema=SectorUpdateDTO, - response_schema=SectorResponseDTO, - prefix="/sectors", - tags=["a76 / sectors"], - resource_name="Sector", - id_name="sector_id", - enable_list=True, - enable_filters=True, - default_page_size=50, - max_page_size=100, - list_permissions=["ref_sectors.view"], - get_permissions=["ref_sectors.view"], - create_permissions=["cat_sectors.create"], - update_permissions=["cat_sectors.edit"], - delete_permissions=["cat_sectors.delete"], -).router diff --git a/backend/api/v1/modules/a76/general_catalogs/sectors/seed.py b/backend/api/v1/modules/a76/general_catalogs/sectors/seed.py deleted file mode 100644 index ee9bfd56..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/sectors/seed.py +++ /dev/null @@ -1,36 +0,0 @@ -# (key, description, authorized) -seed = [ - ("I", "INDUSTRIA ELECTRICA", False), - ("II", "INDUSTRIA ELECTRONICA", False), - ("IIa", "PARA LOS BIENES A QUE SE REFIERE LA FRACCION II, INCISO a) o b), DE ARTICULO 4to DE ESTE DECRETO.", False), - ("IIb", "PARA LOS BIENES A QUE SE REFIERE LA FRACCION II, INCISO b), DE ARTICULO 4to DE ESTE DECRETO.", False), - ("III", "INDUSTRIA DEL MUEBLE", False), - ("IV", "INDUSTRIA DEL JUGUETE, JUEGOS DE RECREO Y ARTICULOS DEPORTIVOS", False), - ("IX", "INDUSTRIA DE MAQUINARIA AGRICOLA", False), - ("V", "INDUSTRIA DEL CALZADO", False), - ("VI", "INDUSTRIA MINERA Y METALURGICA", False), - ("VII", "INDUSTRIA DE BIENES DE CAPITAL", False), - ("VIII", "INDUSTRIA FOTOGRAFICA", False), - ("X", "INDUSTRIAS DIVERSAS", False), - ("XI", "INDUSTRIA QUIMICA", False), - ("XII", "INDUSTRIAS DE MANUFACTURAS DEL CAUCHO Y PLASTICOS", False), - ("XIII", "INDUSTRIA SIDERURGICA", False), - ("XIV", "INDUSTRIA DE PRODUCTOS FARMOQUIMICOS, MEDICAMENTOS Y EQUIPO MEDICO", False), - ("XIX", "INDUSTRIA AUTOMOTRIZ Y DE AUTOPARTES", False), - ("XIXa", "INDUSTRIA AUTOMOTRIZ Y DE AUTOPARTES", False), - ("XIXb", "INDUSTRIA AUTOMOTRIZ Y DE AUTOPARTES", False), - ("XV", "INDUSTRIA DEL TRANSPORTE, EXCEPTO EL SECTOR DE LA INDUSTRIA AUTOMOTRIZ Y DE AUTOPARTES", False), - ("XVa", "INDUSTRIA DEL TRANSPORTE, EXCEPTO EL SECTOR DE LA INDUSTRIA AUTOMOTRIZ Y DE AUTOPARTES.", False), - ("XVb", "INDUSTRIA DEL TRANSPORTE, EXCEPTO EL SECTOR DE LA INDUSTRIA AUTOMOTRIZ Y DE AUTOPARTES.", False), - ("XVI", "INDUSTRIA DEL PAPEL Y CARTON", False), - ("XVII", "INDUSTRIA DE LA MADERA", False), - ("XVIII", "INDUSTRIA DEL CUERO Y PIELES", False), - ("XX", "INDUSTRIA TEXTIL Y DE LA CONFECCION", False), - ("XXa", "INDUSTRIA TEXTIL Y DE LA CONFECCION", False), - ("XXb", "INDUSTRIA TEXTIL Y DE LA CONFECCION", False), - ("XXc", "INDUSTRIA TEXTIL Y DE LA CONFECCION", False), - ("XXd", "INDUSTRIA TEXTIL Y DE LA CONFECCION", False), - ("XXe", "INDUSTRIA TEXTIL Y DE LA CONFECCION", False), - ("XXI", "INDUSTRIA DE CHOCOLATES, DULCES Y SIMILARES", False), - ("XXII", "INDUSTRIA DEL CAFE", False), -] diff --git a/backend/api/v1/modules/a76/general_catalogs/sectors/service.py b/backend/api/v1/modules/a76/general_catalogs/sectors/service.py deleted file mode 100644 index fea918cd..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/sectors/service.py +++ /dev/null @@ -1,152 +0,0 @@ -""" -Service layer for Sectors (GSectores) — a76 tenant-scoped catalog. -""" - -import logging -from typing import Any, Dict, List, Optional, Tuple - -from fastapi import HTTPException -from sqlalchemy.exc import IntegrityError -from sqlalchemy.orm import Session -from sqlalchemy import or_ - -from . import dto, models - -logger = logging.getLogger(__name__) - - -class SectorService: - """Service for Sector CRUD operations with tenant support""" - - @staticmethod - def get_all( - db: Session, - tenant_id: int, - company_id: int, - skip: int = 0, - limit: int = 50, - filters: Optional[Dict[str, Any]] = None, - ) -> Tuple[List[models.Sector], int]: - """Get all sectors for a tenant/company with pagination""" - query = db.query(models.Sector).filter( - models.Sector.tenant_id == tenant_id, - models.Sector.company_id == company_id, - ) - - if filters: - if filters.get("search"): - search_term = f"%{filters['search']}%" - query = query.filter( - or_( - models.Sector.key.ilike(search_term), - models.Sector.description.ilike(search_term) - ) - ) - if filters.get("key"): - query = query.filter( - models.Sector.key.ilike(f"%{filters['key']}%") - ) - if filters.get("description"): - query = query.filter( - models.Sector.description.ilike(f"%{filters['description']}%") - ) - - total = query.count() - sectors = query.order_by(models.Sector.key).offset(skip).limit(limit).all() - - return sectors, total - - @staticmethod - def get_by_id( - db: Session, sector_id: int, tenant_id: int, company_id: int - ) -> Optional[models.Sector]: - """Get sector by ID""" - return ( - db.query(models.Sector) - .filter( - models.Sector.id == sector_id, - models.Sector.tenant_id == tenant_id, - models.Sector.company_id == company_id, - ) - .first() - ) - - @staticmethod - def create( - db: Session, - sector_data: dto.SectorCreateDTO, - tenant_id: int, - company_id: int, - ) -> models.Sector: - """Create a new sector""" - new_sector = models.Sector( - **sector_data.model_dump(), tenant_id=tenant_id, company_id=company_id - ) - db.add(new_sector) - try: - db.commit() - db.refresh(new_sector) - return new_sector - except IntegrityError as e: - db.rollback() - logger.error(f"IntegrityError creating sector: {str(e)}") - raise HTTPException( - status_code=400, - detail="Ya existe un sector con esa clave para esta empresa.", - ) - - @staticmethod - def update( - db: Session, - sector_id: int, - tenant_id: int, - sector_data: dto.SectorUpdateDTO, - company_id: int, - ) -> Optional[models.Sector]: - """Update a sector""" - sector = SectorService.get_by_id(db, sector_id, tenant_id, company_id) - if not sector: - return None - - update_data = sector_data.model_dump(exclude_unset=True) - for field, value in update_data.items(): - setattr(sector, field, value) - - try: - db.commit() - db.refresh(sector) - return sector - except IntegrityError as e: - db.rollback() - logger.error(f"IntegrityError updating sector {sector_id}: {str(e)}") - raise HTTPException( - status_code=400, - detail="Ya existe un sector con esa clave para esta empresa.", - ) - - @staticmethod - def delete( - db: Session, sector_id: int, tenant_id: int, company_id: int - ) -> bool: - """Delete a sector""" - sector = SectorService.get_by_id(db, sector_id, tenant_id, company_id) - if not sector: - return False - - try: - db.delete(sector) - db.commit() - return True - except IntegrityError as e: - db.rollback() - logger.error(f"IntegrityError deleting sector {sector_id}: {str(e)}") - if "foreign key constraint" in str(e).lower(): - raise HTTPException( - status_code=400, - detail="No se puede eliminar el sector porque tiene registros relacionados.", - ) - raise HTTPException(status_code=400, detail="Error al eliminar el sector.") - except Exception as e: - db.rollback() - logger.error(f"Error deleting sector {sector_id}: {str(e)}") - raise HTTPException(status_code=500, detail="Error al eliminar el sector.") diff --git a/backend/api/v1/modules/a76/general_catalogs/signatures/__init__.py b/backend/api/v1/modules/a76/general_catalogs/signatures/__init__.py deleted file mode 100644 index 4ee29dba..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/signatures/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -""" -Módulo de firmas -""" diff --git a/backend/api/v1/modules/a76/general_catalogs/signatures/dto.py b/backend/api/v1/modules/a76/general_catalogs/signatures/dto.py deleted file mode 100644 index fc317714..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/signatures/dto.py +++ /dev/null @@ -1,47 +0,0 @@ -""" -DTOs (Data Transfer Objects) para módulo de firmas -""" - -from typing import Optional - -from pydantic import BaseModel, Field - - -class SignatureCreateDTO(BaseModel): - """DTO para crear una firma""" - - code: str = Field(..., max_length=10, description="Signature code") - - signature: Optional[str] = Field( - None, max_length=1000, description="Signature") - photo_path: Optional[str] = Field( - None, max_length=1000, description="Photo path") - - class Config: - from_attributes = True - - -class SignatureUpdateDTO(BaseModel): - """DTO para actualizar una firma""" - - signature: Optional[str] = Field( - None, max_length=1000, description="Signature") - photo_path: Optional[str] = Field( - None, max_length=1000, description="Photo path") - - class Config: - from_attributes = True - - -class SignatureResponseDTO(BaseModel): - """DTO para responder con datos de una firma""" - - id: int - code: str - signature: Optional[str] = None - photo_path: Optional[str] = None - tenant_id: int - company_id: int - - class Config: - from_attributes = True diff --git a/backend/api/v1/modules/a76/general_catalogs/signatures/models.py b/backend/api/v1/modules/a76/general_catalogs/signatures/models.py deleted file mode 100644 index 0885a276..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/signatures/models.py +++ /dev/null @@ -1,39 +0,0 @@ -""" -Modelos ORM para gestión de firmas -""" - -from typing import Optional - -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base -from sqlalchemy import Integer, PrimaryKeyConstraint, String, UniqueConstraint -from sqlalchemy.orm import Mapped, mapped_column - - -class Signature(Base, TenantScopedMixin, TimestampMixin): - """ - Modelo para la tabla Signature - Firmas - """ - - __tablename__ = "signatures" # GFirmas - __table_args__ = ( - PrimaryKeyConstraint("id", name="signatures_pkey"), - UniqueConstraint("code", "tenant_id", "company_id", - name="signatures_code_unique"), - {"schema": "a76"}, - ) - - # Primary key - id: Mapped[int] = mapped_column( - Integer, primary_key=True, autoincrement=True) - - # Signature code (unique) - code: Mapped[str] = mapped_column(String(10), nullable=False) - - # Signature information - signature: Mapped[Optional[str]] = mapped_column(String(1000)) - - photo_path: Mapped[Optional[str]] = mapped_column(String(1000)) - - def __repr__(self): - return f"" diff --git a/backend/api/v1/modules/a76/general_catalogs/signatures/routes.py b/backend/api/v1/modules/a76/general_catalogs/signatures/routes.py deleted file mode 100644 index 8ff84d9b..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/signatures/routes.py +++ /dev/null @@ -1,209 +0,0 @@ -""" -Rutas para gestión de firmas -""" - -import mimetypes -import os -from datetime import datetime -from typing import Any, Dict - -from api.v1.common.tenant_crud_routes import TenantCRUDRoutes, validate_access_to_resource -from fastapi import Depends, File, HTTPException, Query, Response, UploadFile, status -from sqlalchemy.orm import Session - -from core.config import settings -from core.database import get_core_db -from core.s3_keys import signature_photo_key -from core.storage_s3 import delete_object_if_exists, get_object_bytes, put_object_bytes - -from .dto import SignatureCreateDTO, SignatureResponseDTO, SignatureUpdateDTO -from .service import SignatureService - -# Constantes -ALLOWED_PHOTO_EXTENSIONS = {".jpg", ".jpeg", ".png", ".gif", ".webp"} -MAX_PHOTO_SIZE = 5 * 1024 * 1024 # 5 MB - -# Create router using TenantCRUDRoutes factory -signature_crud = TenantCRUDRoutes( - service=SignatureService, - create_schema=SignatureCreateDTO, - update_schema=SignatureUpdateDTO, - response_schema=SignatureResponseDTO, - prefix="/signatures", - tags=["signatures"], - resource_name="Signature", - id_name="id", # Using numeric ID - enable_list=True, # Enable GET /signatures with pagination - enable_filters=True, # Enable filtering by code - default_page_size=50, - max_page_size=100, - list_permissions=["cat_signatures.view"], - get_permissions=["cat_signatures.view"], - create_permissions=["cat_signatures.create"], - update_permissions=["cat_signatures.edit"], - delete_permissions=["cat_signatures.delete"], -) - -router = signature_crud.router - - -@router.get( - "/code/{code}", - response_model=SignatureResponseDTO, - summary="Get signature by code", -) -async def get_signature_by_code( - code: str, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(signature_crud.db_dependency), - current_user: Dict[str, Any] = Depends(signature_crud.auth_dependency), -): - """Get a signature by its code""" - tenant_id = validate_access_to_resource( - db, company_id, current_user, ["cat_signatures.view"] - ) - signature = SignatureService.get_by_code(db, code, tenant_id, company_id) - if not signature: - raise HTTPException( - status_code=404, - detail="Signature not found", - ) - return SignatureResponseDTO.model_validate(signature) - - -@router.delete( - "/{signature_id}", - status_code=status.HTTP_204_NO_CONTENT, - summary="Delete signature", -) -async def delete_signature( - signature_id: int, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(signature_crud.auth_dependency), -): - """Delete a signature""" - validate_access_to_resource(db, company_id, current_user, ["cat_signatures.delete"]) - success = SignatureService.delete(db, signature_id) - if not success: - raise HTTPException( - status_code=status.HTTP_404_NOT_FOUND, - detail="Signature not found", - ) - return None - - -@router.post( - "/{signature_id}/upload-photo", - response_model=dict, - summary="Upload signature photo", -) -async def upload_signature_photo( - signature_id: int, - company_id: int = Query(..., description="Company ID"), - file: UploadFile = File(...), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(signature_crud.auth_dependency), -): - """ - Sube la foto de la firma electrónica a MinIO/S3 y actualiza photo_path en la firma. - Extensiones permitidas: .jpg, .jpeg, .png, .gif, .webp — máx 5 MB. - """ - tenant_id = validate_access_to_resource( - db, company_id, current_user, ["cat_signatures.edit"] - ) - - # Verificar que la firma existe - sig = SignatureService.get_by_id(db, signature_id, tenant_id, company_id) - if not sig: - raise HTTPException(status_code=404, detail="Signature not found") - - # Validar extensión - file_ext = os.path.splitext(file.filename or "")[1].lower() - if file_ext not in ALLOWED_PHOTO_EXTENSIONS: - raise HTTPException( - status_code=400, - detail=f"Tipo de archivo no permitido. Permitidos: {', '.join(ALLOWED_PHOTO_EXTENSIONS)}", - ) - - # Leer y validar tamaño - content = await file.read() - if len(content) > MAX_PHOTO_SIZE: - raise HTTPException( - status_code=400, - detail=f"Archivo demasiado grande. Máximo: {MAX_PHOTO_SIZE // 1024 // 1024} MB", - ) - - timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") - - try: - if settings.use_s3_object_storage: - # Eliminar foto anterior si existe en S3 - if sig.photo_path and sig.photo_path.startswith("tenants/"): - delete_object_if_exists(sig.photo_path) - - key = signature_photo_key(tenant_id, company_id, signature_id, timestamp, file_ext) - ct = mimetypes.guess_type(file.filename or "photo")[0] or "image/jpeg" - put_object_bytes(key, content, content_type=ct) - file_path = key - else: - # Fallback: almacenamiento local - upload_dir = os.path.join("uploads", "signatures", str(signature_id)) - os.makedirs(upload_dir, exist_ok=True) - filename = f"photo_{timestamp}{file_ext}" - file_path = os.path.join(upload_dir, filename) - with open(file_path, "wb") as f: - f.write(content) - except Exception as exc: - raise HTTPException( - status_code=500, - detail=f"Error al guardar la foto: {str(exc)}", - ) from exc - - # Actualizar photo_path en la firma - from .dto import SignatureUpdateDTO as UpdateDTO - SignatureService.update( - db, signature_id, tenant_id, UpdateDTO(photo_path=file_path), company_id - ) - - return { - "message": "Foto subida correctamente", - "photo_path": file_path, - "signature_id": signature_id, - } - - -@router.get( - "/{signature_id}/photo", - summary="Get signature photo image", -) -async def get_signature_photo( - signature_id: int, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(signature_crud.auth_dependency), -): - """Devuelve los bytes de la foto de la firma desde MinIO/S3 para preview.""" - tenant_id = validate_access_to_resource( - db, company_id, current_user, ["cat_signatures.view"] - ) - - sig = SignatureService.get_by_id(db, signature_id, tenant_id, company_id) - if not sig or not sig.photo_path: - raise HTTPException(status_code=404, detail="Foto no encontrada") - - if sig.photo_path.startswith("tenants/"): - try: - data = get_object_bytes(sig.photo_path) - except Exception: - raise HTTPException(status_code=404, detail="Foto no encontrada en el almacenamiento") - media_type = mimetypes.guess_type(sig.photo_path)[0] or "image/jpeg" - return Response(content=data, media_type=media_type) - - # Fallback: archivo local - if not os.path.isfile(sig.photo_path): - raise HTTPException(status_code=404, detail="Foto no encontrada en el sistema de archivos") - with open(sig.photo_path, "rb") as f: - data = f.read() - media_type = mimetypes.guess_type(sig.photo_path)[0] or "image/jpeg" - return Response(content=data, media_type=media_type) diff --git a/backend/api/v1/modules/a76/general_catalogs/signatures/service.py b/backend/api/v1/modules/a76/general_catalogs/signatures/service.py deleted file mode 100644 index 80944e54..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/signatures/service.py +++ /dev/null @@ -1,135 +0,0 @@ -""" -Capa de servicio para lógica de negocio de firmas -""" - -import logging -from typing import Any, Dict, List, Optional, Tuple - -from fastapi import HTTPException, status -from sqlalchemy.exc import IntegrityError -from sqlalchemy.orm import Session - -from . import dto, models - -logger = logging.getLogger(__name__) - - -class SignatureService: - """Servicio para gestión de firmas""" - - @staticmethod - def get_all( - db: Session, - tenant_id: int, - company_id: int, - skip: int = 0, - limit: int = 50, - filters: Optional[Dict[str, Any]] = None, - ) -> Tuple[List[models.Signature], int]: - """Get all signatures with pagination""" - query = db.query(models.Signature).filter( - models.Signature.tenant_id == tenant_id, - models.Signature.company_id == company_id, - ) - - if filters: - if filters.get("code"): - query = query.filter( - models.Signature.code.ilike(f"%{filters['code']}%")) - if filters.get("signature"): - query = query.filter( - models.Signature.signature.ilike(f"%{filters['signature']}%")) - - total = query.count() - signatures = query.offset(skip).limit(limit).all() - - return signatures, total - - @staticmethod - def get_by_id( - db: Session, signature_id: int, tenant_id: int, company_id: int - ) -> Optional[models.Signature]: - """Get signature by ID""" - return ( - db.query(models.Signature) - .filter( - models.Signature.id == signature_id, - models.Signature.tenant_id == tenant_id, - models.Signature.company_id == company_id, - ) - .first() - ) - - @staticmethod - def get_by_code( - db: Session, code: str, tenant_id: int, company_id: int - ) -> Optional[models.Signature]: - """Get signature by code""" - return ( - db.query(models.Signature) - .filter( - models.Signature.code == code, - models.Signature.tenant_id == tenant_id, - models.Signature.company_id == company_id, - ) - .first() - ) - - @staticmethod - def create( - db: Session, - signature_data: dto.SignatureCreateDTO, - tenant_id: int, - company_id: int, - ) -> models.Signature: - """Create a new signature""" - new_signature = models.Signature( - **signature_data.model_dump(), tenant_id=tenant_id, company_id=company_id - ) - try: - db.add(new_signature) - db.commit() - db.refresh(new_signature) - return new_signature - except IntegrityError as exc: - db.rollback() - # Constraint names: signatures_code_unique (code, tenant_id, company_id) - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail="Ya existe una firma con ese código para esta compañía", - ) from exc - - @staticmethod - def update( - db: Session, - signature_id: int, - tenant_id: int, - signature_data: dto.SignatureUpdateDTO, - company_id: int, - ) -> Optional[models.Signature]: - """Update a signature""" - signature = SignatureService.get_by_id( - db, signature_id, tenant_id, company_id) - if not signature: - return None - - for key, value in signature_data.model_dump(exclude_unset=True).items(): - setattr(signature, key, value) - - db.commit() - db.refresh(signature) - return signature - - @staticmethod - def delete( - db: Session, signature_id: int, tenant_id: int, company_id: int - ) -> bool: - """Delete a signature""" - signature = SignatureService.get_by_id( - db, signature_id, tenant_id, company_id) - if not signature: - return False - - db.delete(signature) - db.commit() - return True diff --git a/backend/api/v1/modules/a76/general_catalogs/unit_conversions/__init__.py b/backend/api/v1/modules/a76/general_catalogs/unit_conversions/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/backend/api/v1/modules/a76/general_catalogs/unit_conversions/dto.py b/backend/api/v1/modules/a76/general_catalogs/unit_conversions/dto.py deleted file mode 100644 index 0bb41aed..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/unit_conversions/dto.py +++ /dev/null @@ -1,24 +0,0 @@ -from typing import Optional -from decimal import Decimal -from pydantic import BaseModel, Field, ConfigDict - -class UnitConversionBase(BaseModel): - # 👇 OJO: Son códigos (Strings), no IDs - from_unit_code: str = Field(..., max_length=5, description="Source Unit Code") - to_unit_code: str = Field(..., max_length=5, description="Target Unit Code") - conversion_factor: Optional[Decimal] = Field(None, description="Conversion Factor") - -class UnitConversionCreate(UnitConversionBase): - pass - -class UnitConversionUpdate(BaseModel): - from_unit_code: Optional[str] = Field(None, max_length=5) - to_unit_code: Optional[str] = Field(None, max_length=5) - conversion_factor: Optional[Decimal] = None - -class UnitConversionResponse(UnitConversionBase): - id: int - tenant_id: int - company_id: int - - model_config = ConfigDict(from_attributes=True) \ No newline at end of file diff --git a/backend/api/v1/modules/a76/general_catalogs/unit_conversions/models.py b/backend/api/v1/modules/a76/general_catalogs/unit_conversions/models.py deleted file mode 100644 index 26893749..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/unit_conversions/models.py +++ /dev/null @@ -1,39 +0,0 @@ -from typing import Optional -from decimal import Decimal -from sqlalchemy import Integer, String, ForeignKeyConstraint, UniqueConstraint, Numeric -from sqlalchemy.orm import Mapped, mapped_column, relationship -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base -from api.v1.modules.a76.general_catalogs.units_of_measure.models import UnitOfMeasure - - -class UnitConversion(Base, TenantScopedMixin, TimestampMixin): - __tablename__ = "unit_conversions" - __table_args__ = ( - UniqueConstraint("from_unit_code", "to_unit_code", "tenant_id", "company_id", - name="uq_unit_conversion_pair"), - ForeignKeyConstraint( - ["from_unit_code", "tenant_id", "company_id"], - ["a76.units_of_measure.code", "a76.units_of_measure.tenant_id", - "a76.units_of_measure.company_id"], - ), - ForeignKeyConstraint( - ["to_unit_code", "tenant_id", "company_id"], - ["a76.units_of_measure.code", "a76.units_of_measure.tenant_id", - "a76.units_of_measure.company_id"], - ), - {"schema": "a76"} - ) - - id: Mapped[int] = mapped_column( - Integer, primary_key=True, autoincrement=True) - - from_unit_code: Mapped[str] = mapped_column(String(5), nullable=False) - - to_unit_code: Mapped[str] = mapped_column(String(5), nullable=False) - - conversion_factor: Mapped[Optional[Decimal]] = mapped_column( - Numeric(13, 6), nullable=True) - - # Relationships - # Note: Complex composite foreign keys might require explicit primaryjoin if used diff --git a/backend/api/v1/modules/a76/general_catalogs/unit_conversions/routes.py b/backend/api/v1/modules/a76/general_catalogs/unit_conversions/routes.py deleted file mode 100644 index 9bc3cb52..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/unit_conversions/routes.py +++ /dev/null @@ -1,21 +0,0 @@ -from api.v1.common.tenant_crud_routes import TenantCRUDRoutes -from .dto import UnitConversionCreate, UnitConversionResponse, UnitConversionUpdate -from .service import UnitConversionService - -router = TenantCRUDRoutes( - service=UnitConversionService, - create_schema=UnitConversionCreate, - update_schema=UnitConversionUpdate, - response_schema=UnitConversionResponse, - prefix="/unit-conversions", - tags=["a76.general_catalogs.unit_conversions"], - resource_name="UnitConversion", - id_name="id", - enable_list=True, - enable_filters=True, - list_permissions=["cat_unit_conversions.view"], - get_permissions=["cat_unit_conversions.view"], - create_permissions=["cat_unit_conversions.create"], - update_permissions=["cat_unit_conversions.edit"], - delete_permissions=["cat_unit_conversions.delete"], -).router diff --git a/backend/api/v1/modules/a76/general_catalogs/unit_conversions/service.py b/backend/api/v1/modules/a76/general_catalogs/unit_conversions/service.py deleted file mode 100644 index c3a2a7d9..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/unit_conversions/service.py +++ /dev/null @@ -1,113 +0,0 @@ -from typing import List, Optional, Tuple, Dict, Any - -from fastapi import HTTPException, status -from sqlalchemy import select -from sqlalchemy.exc import IntegrityError -from sqlalchemy.orm import Session - -from .models import UnitConversion -from .dto import UnitConversionCreate, UnitConversionUpdate - - -class UnitConversionService: - @staticmethod - def get_all( - db: Session, - tenant_id: int, - company_id: int, - skip: int = 0, - limit: int = 100, - filters: Optional[Dict[str, Any]] = None, - ) -> Tuple[List[UnitConversion], int]: - query = db.query(UnitConversion).filter( - UnitConversion.tenant_id == tenant_id, - UnitConversion.company_id == company_id, - ) - - if filters: - if filters.get("from_unit_code"): - query = query.filter( - UnitConversion.from_unit_code.ilike(f"%{filters['from_unit_code']}%") - ) - if filters.get("to_unit_code"): - query = query.filter( - UnitConversion.to_unit_code.ilike(f"%{filters['to_unit_code']}%") - ) - - total = query.count() - items = query.offset(skip).limit(limit).all() - return items, total - - @staticmethod - def get_by_id( - db: Session, id: int, tenant_id: int, company_id: int - ) -> Optional[UnitConversion]: - return db.query(UnitConversion).filter( - UnitConversion.id == id, - UnitConversion.tenant_id == tenant_id, - UnitConversion.company_id == company_id, - ).first() - - @staticmethod - def create( - db: Session, - data: UnitConversionCreate, - tenant_id: int, - company_id: int, - ) -> UnitConversion: - db_obj = UnitConversion( - **data.model_dump(), - tenant_id=tenant_id, - company_id=company_id - ) - try: - db.add(db_obj) - db.commit() - db.refresh(db_obj) - return db_obj - except IntegrityError as exc: - db.rollback() - # Puede ser FK de unidades o duplicado de (from_unit_code, to_unit_code, tenant, company) - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail="Verifica que los códigos de unidad existan y que la conversión no esté duplicada", - ) from exc - - @staticmethod - def update( - db: Session, - id: int, - tenant_id: int, - data: UnitConversionUpdate, - company_id: int, - ) -> Optional[UnitConversion]: - db_obj = UnitConversionService.get_by_id(db, id, tenant_id, company_id) - if not db_obj: - return None - - update_dict = data.model_dump(exclude_unset=True) - for key, value in update_dict.items(): - setattr(db_obj, key, value) - - try: - db.commit() - db.refresh(db_obj) - return db_obj - except IntegrityError as exc: - db.rollback() - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail="Verifica que los códigos de unidad existan y que la conversión no esté duplicada", - ) from exc - - @staticmethod - def delete( - db: Session, id: int, tenant_id: int, company_id: int - ) -> bool: - db_obj = UnitConversionService.get_by_id(db, id, tenant_id, company_id) - if not db_obj: - return False - - db.delete(db_obj) - db.commit() - return True diff --git a/backend/api/v1/modules/a76/general_catalogs/units_of_measure/__init__.py b/backend/api/v1/modules/a76/general_catalogs/units_of_measure/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/backend/api/v1/modules/a76/general_catalogs/units_of_measure/dto.py b/backend/api/v1/modules/a76/general_catalogs/units_of_measure/dto.py deleted file mode 100644 index 0b593be6..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/units_of_measure/dto.py +++ /dev/null @@ -1,134 +0,0 @@ -from typing import Optional -from decimal import Decimal -from pydantic import BaseModel, Field, ConfigDict - -# --- Base DTOs --- - - -class UnitOfMeasureACEBase(BaseModel): - code: str = Field(..., max_length=4, description="ACE Code") - description: Optional[str] = Field(None, max_length=49) - - -class UnitOfMeasureOMABase(BaseModel): - code: str = Field(..., max_length=10, description="OMA Code") - description: Optional[str] = Field(None, max_length=200) - - -class UnitOfMeasureAmericanBase(BaseModel): - code: str = Field(..., max_length=3, description="American Code") - description: Optional[str] = Field(None, max_length=40) - - -class UnitOfMeasureCustomsBase(BaseModel): - code: str = Field(..., max_length=10, description="Customs Code") - description: Optional[str] = Field(None, max_length=50) - a76_unit_code: Optional[str] = Field(None, max_length=5, description="Unidad SCAII") - - -class UnitOfMeasureBase(BaseModel): - code: str = Field(..., max_length=20, description="Unit Code") - description: Optional[str] = Field(None, max_length=100) - description_en: Optional[str] = Field(None, max_length=100) - customs_code: Optional[int] = Field(None) - american_code: Optional[str] = Field(None, max_length=20) - ace_code: Optional[str] = Field(None, max_length=20) - oma_code: Optional[str] = Field(None, max_length=20) - - -class UnitOfMeasureGeneralBase(UnitOfMeasureBase): - pass - -# --- Create DTOs --- - - -class UnitOfMeasureACECreate(UnitOfMeasureACEBase): - pass - - -class UnitOfMeasureOMACreate(UnitOfMeasureOMABase): - pass - - -class UnitOfMeasureAmericanCreate(UnitOfMeasureAmericanBase): - pass - - -class UnitOfMeasureCustomsCreate(UnitOfMeasureCustomsBase): - pass - - -class UnitOfMeasureCreate(UnitOfMeasureBase): - pass - - -class UnitOfMeasureGeneralCreate(UnitOfMeasureGeneralBase): - pass - -# --- Update DTOs --- - - -class UnitOfMeasureACEUpdate(BaseModel): - code: Optional[str] = Field(None, max_length=4) - description: Optional[str] = Field(None, max_length=49) - - -class UnitOfMeasureOMAUpdate(BaseModel): - code: Optional[str] = Field(None, max_length=10) - description: Optional[str] = Field(None, max_length=200) - - -class UnitOfMeasureAmericanUpdate(BaseModel): - code: Optional[str] = Field(None, max_length=3) - description: Optional[str] = Field(None, max_length=40) - - -class UnitOfMeasureCustomsUpdate(BaseModel): - code: Optional[str] = Field(None, max_length=10) - description: Optional[str] = Field(None, max_length=50) - a76_unit_code: Optional[str] = Field(None, max_length=5) - - -class UnitOfMeasureUpdate(BaseModel): - code: Optional[str] = Field(None, max_length=20) - description: Optional[str] = Field(None, max_length=100) - description_en: Optional[str] = Field(None, max_length=100) - customs_code: Optional[int] = Field(None) - american_code: Optional[str] = Field(None, max_length=20) - ace_code: Optional[str] = Field(None, max_length=20) - oma_code: Optional[str] = Field(None, max_length=20) - - -class UnitOfMeasureGeneralUpdate(UnitOfMeasureUpdate): - pass - -# --- Response DTOs --- - - -class UnitOfMeasureACEResponse(UnitOfMeasureACEBase): - id: int - model_config = ConfigDict(from_attributes=True) - - -class UnitOfMeasureOMAResponse(UnitOfMeasureOMABase): - id: int - model_config = ConfigDict(from_attributes=True) - - -class UnitOfMeasureAmericanResponse(UnitOfMeasureAmericanBase): - id: int - model_config = ConfigDict(from_attributes=True) - - -class UnitOfMeasureCustomsResponse(UnitOfMeasureCustomsBase): - id: int - model_config = ConfigDict(from_attributes=True) - - -class UnitOfMeasureResponse(UnitOfMeasureBase): - id: int - model_config = ConfigDict(from_attributes=True) - - -class UnitOfMeasureGeneralResponse(UnitOfMeasureResponse): - pass diff --git a/backend/api/v1/modules/a76/general_catalogs/units_of_measure/models.py b/backend/api/v1/modules/a76/general_catalogs/units_of_measure/models.py deleted file mode 100644 index 3335753e..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/units_of_measure/models.py +++ /dev/null @@ -1,133 +0,0 @@ -from typing import Optional -from decimal import Decimal -from sqlalchemy import ( - ForeignKey, - Integer, - String, - ForeignKeyConstraint, - UniqueConstraint, - Numeric, -) -from sqlalchemy.orm import Mapped, mapped_column, relationship -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base - -# Import models referenced by TenantScopedMixin (Tenant, Company) to ensure they are loaded -from api.v1.modules.core.tenants.models import Tenant -from api.v1.modules.a76.general_catalogs.company.models import Company - -# 1. GUniMedACE -class UnitOfMeasureACE(Base, TimestampMixin): - __tablename__ = "unit_of_measure_ace" - __table_args__ = ( - UniqueConstraint("code", name="uq_uom_ace_code"), - {"schema": "a76", "extend_existing": True}, - ) - - id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) - code: Mapped[str] = mapped_column(String(4), nullable=False) # CLAVEACE - description: Mapped[Optional[str]] = mapped_column(String(49), nullable=True) - - -# 2. GUMOMA -class UnitOfMeasureOMA(Base, TimestampMixin): - __tablename__ = "unit_of_measure_oma" - __table_args__ = ( - UniqueConstraint("code", name="uq_uom_oma_code"), - {"schema": "a76", "extend_existing": True}, - ) - - id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) - code: Mapped[str] = mapped_column(String(10), nullable=False) # CLAVEUM - description: Mapped[Optional[str]] = mapped_column(String(200), nullable=True) - - -# 3. GUMAme -class UnitOfMeasureAmerican(Base, TimestampMixin): - __tablename__ = "unit_of_measure_american" - __table_args__ = ( - UniqueConstraint("code", name="uq_uom_american_code"), - {"schema": "a76", "extend_existing": True}, - ) - - id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) - code: Mapped[str] = mapped_column(String(3), nullable=False) # CLAVE - description: Mapped[Optional[str]] = mapped_column(String(40), nullable=True) - - -# 4. GUMAduana -class UnitOfMeasureCustoms(Base, TimestampMixin): - __tablename__ = "unit_of_measure_customs" - __table_args__ = ( - UniqueConstraint("code", name="uq_uom_customs_code"), - {"schema": "a76", "extend_existing": True}, - ) - - id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) - code: Mapped[str] = mapped_column(String(10), nullable=False) # CLAVE - description: Mapped[Optional[str]] = mapped_column(String(20), nullable=True) - a76_unit_code: Mapped[Optional[str]] = mapped_column(String(5), nullable=True) # UnidadSCAII - - -# 5. GUniMedida (Main) -class UnitOfMeasure(Base, TenantScopedMixin, TimestampMixin): - __tablename__ = "units_of_measure" - __table_args__ = ( - UniqueConstraint("code", "tenant_id", "company_id", name="uq_uom_code"), - {"schema": "a76", "extend_existing": True}, - ) - - id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) - code: Mapped[str] = mapped_column(String(5), nullable=False) # CLAVEUNI - description: Mapped[Optional[str]] = mapped_column(String(100), nullable=True) - description_en: Mapped[Optional[str]] = mapped_column(String(100), nullable=True) - - customs_code: Mapped[Optional[str]] = mapped_column(ForeignKey("a76.unit_of_measure_customs.code"), nullable=True) # CLAVE_AMEX - american_code: Mapped[Optional[str]] = mapped_column(ForeignKey("a76.unit_of_measure_american.code"), nullable=True) # CLAVE_AAMER - ace_code: Mapped[Optional[str]] = mapped_column(ForeignKey("a76.unit_of_measure_ace.code"), nullable=True) # CLAVEACE - oma_code: Mapped[Optional[str]] = mapped_column(ForeignKey("a76.unit_of_measure_oma.code"), nullable=True) # CLAVEOMA - - # Relationships omitted for simplicity or need explicit primaryjoin - customs_unit: Mapped[Optional["UnitOfMeasureCustoms"]] = relationship() - american_unit: Mapped[Optional["UnitOfMeasureAmerican"]] = relationship(overlaps="customs_unit") - ace_unit: Mapped[Optional["UnitOfMeasureACE"]] = relationship(overlaps="american_unit,customs_unit") - oma_unit: Mapped[Optional["UnitOfMeasureOMA"]] = relationship(overlaps="ace_unit,american_unit,customs_unit") - - -# 6. GUniMed (General/Conversion) -class UnitOfMeasureGeneral(Base, TenantScopedMixin, TimestampMixin): - __tablename__ = "units_of_measure_general" - __table_args__ = ( - UniqueConstraint("code", "tenant_id", "company_id", name="uq_uom_general_code"), - ForeignKeyConstraint( - ["customs_code"], - ["a76.unit_of_measure_customs.code"], - use_alter=True, - name="fk_uom_general_customs", - ), - ForeignKeyConstraint( - ["ace_code"], - ["a76.unit_of_measure_ace.code"], - use_alter=True, - name="fk_uom_general_ace", - ), - {"schema": "a76", "extend_existing": True}, - ) - - id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) - code: Mapped[str] = mapped_column(String(5), nullable=False) # UNIDAD - description: Mapped[Optional[str]] = mapped_column(String(100), nullable=True) - conversion_factor: Mapped[Optional[Decimal]] = mapped_column( - Numeric(13, 6), nullable=True - ) - mexico_unit: Mapped[Optional[str]] = mapped_column(String(5), nullable=True) - # UNIDAD_AME (Note: GUniMed has UNIDAD_AME varchar(5), but GUMAme has CLAVE varchar(3). Keeping as string for now) - american_unit_code: Mapped[Optional[str]] = mapped_column(ForeignKey("a76.unit_of_measure_american.code"), nullable=True) - - customs_code: Mapped[Optional[str]] = mapped_column(ForeignKey("a76.unit_of_measure_customs.code"), nullable=True) # CLAVE_ADUANA - ace_code: Mapped[Optional[str]] = mapped_column(ForeignKey("a76.unit_of_measure_ace.code"), nullable=True) # CLAVEACE - - customs_unit: Mapped[Optional["UnitOfMeasureCustoms"]] = relationship() - ace_unit: Mapped[Optional["UnitOfMeasureACE"]] = relationship( - overlaps="customs_unit" - ) diff --git a/backend/api/v1/modules/a76/general_catalogs/units_of_measure/routes.py b/backend/api/v1/modules/a76/general_catalogs/units_of_measure/routes.py deleted file mode 100644 index a1b7f090..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/units_of_measure/routes.py +++ /dev/null @@ -1,139 +0,0 @@ -from fastapi import APIRouter -from api.v1.common.tenant_crud_routes import TenantCRUDRoutes -from . import dto, service - -router = APIRouter(prefix="/units-of-measure", - tags=["a76.general_catalogs.units_of_measure"]) - -# ACE -ace_router = TenantCRUDRoutes( - service=service.UnitOfMeasureACEService, - create_schema=dto.UnitOfMeasureACECreate, - update_schema=dto.UnitOfMeasureACEUpdate, - response_schema=dto.UnitOfMeasureACEResponse, - prefix="/ace", - tags=["a76.general_catalogs.units_of_measure"], - resource_name="UnitOfMeasureACE", - id_name="id", - enable_list=True, - enable_filters=True, - max_page_size=10000, - # Permisos - list_permissions=["cat_um_ace.view"], - get_permissions=["cat_um_ace.view"], - create_permissions=["cat_um_ace.create"], - update_permissions=["cat_um_ace.edit"], - delete_permissions=["cat_um_ace.delete"], -).router -router.include_router(ace_router) - -# OMA -oma_router = TenantCRUDRoutes( - service=service.UnitOfMeasureOMAService, - create_schema=dto.UnitOfMeasureOMACreate, - update_schema=dto.UnitOfMeasureOMAUpdate, - response_schema=dto.UnitOfMeasureOMAResponse, - prefix="/oma", - tags=["a76.general_catalogs.units_of_measure"], - resource_name="UnitOfMeasureOMA", - id_name="id", - enable_list=True, - enable_filters=True, - max_page_size=10000, - # Permisos - list_permissions=["cat_um_oma.view"], - get_permissions=["cat_um_oma.view"], - create_permissions=["cat_um_oma.create"], - update_permissions=["cat_um_oma.edit"], - delete_permissions=["cat_um_oma.delete"], -).router -router.include_router(oma_router) - -# American -american_router = TenantCRUDRoutes( - service=service.UnitOfMeasureAmericanService, - create_schema=dto.UnitOfMeasureAmericanCreate, - update_schema=dto.UnitOfMeasureAmericanUpdate, - response_schema=dto.UnitOfMeasureAmericanResponse, - prefix="/american", - tags=["a76.general_catalogs.units_of_measure"], - resource_name="UnitOfMeasureAmerican", - id_name="id", - enable_list=True, - enable_filters=True, - max_page_size=10000, - # Permisos - list_permissions=["cat_um_american.view"], - get_permissions=["cat_um_american.view"], - create_permissions=["cat_um_american.create"], - update_permissions=["cat_um_american.edit"], - delete_permissions=["cat_um_american.delete"], -).router -router.include_router(american_router) - -# Customs -customs_router = TenantCRUDRoutes( - service=service.UnitOfMeasureCustomsService, - create_schema=dto.UnitOfMeasureCustomsCreate, - update_schema=dto.UnitOfMeasureCustomsUpdate, - response_schema=dto.UnitOfMeasureCustomsResponse, - prefix="/customs", - tags=["a76.general_catalogs.units_of_measure"], - resource_name="UnitOfMeasureCustoms", - id_name="id", - enable_list=True, - enable_filters=True, - max_page_size=10000, - # Permisos - list_permissions=["cat_um_customs.view"], - get_permissions=["cat_um_customs.view"], - create_permissions=["cat_um_customs.create"], - update_permissions=["cat_um_customs.edit"], - delete_permissions=["cat_um_customs.delete"], -).router -router.include_router(customs_router) - -# General -general_router = TenantCRUDRoutes( - service=service.UnitOfMeasureGeneralService, - create_schema=dto.UnitOfMeasureGeneralCreate, - update_schema=dto.UnitOfMeasureGeneralUpdate, - response_schema=dto.UnitOfMeasureGeneralResponse, - prefix="/general", - tags=["a76.general_catalogs.units_of_measure"], - resource_name="UnitOfMeasureGeneral", - id_name="id", - enable_list=True, - enable_filters=True, - max_page_size=10000, - # Permisos - list_permissions=["cat_um_general.view"], - get_permissions=["cat_um_general.view"], - create_permissions=["cat_um_general.create"], - update_permissions=["cat_um_general.edit"], - delete_permissions=["cat_um_general.delete"], -).router -router.include_router(general_router) - -# Main UnitOfMeasure -# Note: We use prefix="" to map to /units-of-measure/ -main_router = TenantCRUDRoutes( - service=service.UnitOfMeasureService, - create_schema=dto.UnitOfMeasureCreate, - update_schema=dto.UnitOfMeasureUpdate, - response_schema=dto.UnitOfMeasureResponse, - prefix="", - tags=["a76.general_catalogs.units_of_measure"], - resource_name="UnitOfMeasure", - id_name="id", - enable_list=True, - enable_filters=True, - max_page_size=10000, - # Permisos - list_permissions=["cat_um_general.view"], - get_permissions=["cat_um_general.view"], - create_permissions=["cat_um_general.create"], - update_permissions=["cat_um_general.edit"], - delete_permissions=["cat_um_general.delete"], -).router -router.include_router(main_router) diff --git a/backend/api/v1/modules/a76/general_catalogs/units_of_measure/seed.py b/backend/api/v1/modules/a76/general_catalogs/units_of_measure/seed.py deleted file mode 100644 index ef7d05be..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/units_of_measure/seed.py +++ /dev/null @@ -1,49 +0,0 @@ -""" -Datos semilla para Unidades de Medida Comerciales -Migrado desde frontend para centralizar en backend -""" - -# Formato: (code, description, description_en, customs_code, american_code, ace_code, oma_code) -seed = [ - ('BARR', 'BARRIL', 'BARREL', '8', 'BBL', '', 'BLL'), - ('BD FT', 'PIE TABLA', 'BD FEET', '5', 'FT', '', 'BFT'), - ('BOLS', 'BOLSA', 'BAG', '6', 'PCS', '', 'BG'), - ('BTL', 'BOTELLA', 'BOTTLE', '21', 'PCS', '', 'BO'), - ('BULT', 'BULTO', 'BULK', '6', 'PCS', '', 'VQ'), - ('CAJA', 'CAJA', 'BOX', '20', '', '', 'BX'), - ('CARAT', 'CARAT', 'CARAT', '22', '', '', 'HE'), - ('CBZA', 'CABEZA', 'HEAD', '7', 'PCS', '', 'Z4'), - ('CIEN', 'CIENTO', 'CIEN', '18', '', '', 'CEN'), - ('CM', 'CENTIMETRO', 'CM', '3', 'CM', '', 'CMT'), - ('CM2', 'CENTIMETRO CUADRADO', 'CM2', '4', 'CM2', '', 'CMK'), - ('DEC', 'DECENA', '', '17', '', '', 'DC'), - ('DM', 'DECIMETRO', 'DM', '3', '', '', 'DMT'), - ('DM2', 'DECIMETRO CUADRADO', 'SQ DM', '4', '', '', 'DMK'), - ('DOCE', 'DOCENA', 'DOZ', '19', 'DOZ', 'DZ', 'DZN'), - ('FOZ', 'ONZA LIQUIDA', 'FOZ', '8', 'FOZ', '', 'OZA'), - ('FT', 'PIES', 'FT', '3', 'FT', '', 'LF'), - ('FT2', 'PIE CUADRADO', 'FT2', '4', 'SFT', '', 'FTK'), - ('GAL', 'GALON', 'GAL', '8', 'GAL', '', 'GLL'), - ('GR', 'GRAMO', 'GRAM', '2', '', '', 'GRM'), - ('IN', 'PULGADA', 'IN', '3', '', '', 'LI'), - ('IN2', 'PULGADA CUADRADA', 'IN2', '4', '', '', 'INK'), - ('JGO', 'JUEGO', 'SET', '12', '', '', 'SET'), - ('KGS', 'KILOGRAMOS', 'KGS', '1', 'KG2', '', 'KGM'), - ('LB', 'LIBRAS', 'LB', '1', '', '', 'LBR'), - ('LT', 'LITRO', 'LT', '8', 'L', '', 'LTR'), - ('M2', 'METRO CUADRADO', 'M2', '4', 'M2', '', 'MTK'), - ('M3', 'METRO CUBICO', 'M3', '5', 'M3', '', 'MTQ'), - ('MI', 'MILLA', 'MILE', '3', 'KM', '', 'SMI'), - ('MILLR', 'MILLAR', 'MILLR', '11', '', '', 'MIL'), - ('MT', 'METROS', 'MT', '3', 'M', '', 'MTR'), - ('OZ', 'ONZA', 'OZ', '8', 'FOZ', '', 'OZ'), - ('PAR', 'PAR', 'PAIR', '9', '', '', 'PB'), - ('PQ', 'PAQUETE', 'PACKAGE', '6', 'PCS', '', 'PK_1'), - ('PZA', 'PIEZA', 'PCS', '6', 'PCS', '', 'C62_1'), - ('QGL', 'CUARTO DE GALON', 'QGL', '8', '', '', 'QT'), - ('ROLL', 'ROLLO', 'ROLL', '6', '', '', 'RO'), - ('TON', 'TONELADA', 'TON', '14', 'TON', '', 'TNE_1'), - ('TOZ', 'ONZA TROY', 'TOZ', '1', 'TOZ', '', 'APZ'), - ('YD', 'YARDA', 'YD', '3', 'YD', '', 'YRD'), - ('YD2', 'YARDA CUADRADA', 'YD2', '4', 'SYD', '', 'YDK'), -] diff --git a/backend/api/v1/modules/a76/general_catalogs/units_of_measure/seed_ace.py b/backend/api/v1/modules/a76/general_catalogs/units_of_measure/seed_ace.py deleted file mode 100644 index 37bf24ee..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/units_of_measure/seed_ace.py +++ /dev/null @@ -1,58 +0,0 @@ -seed = [ - ('BAG', 'Bag'), - ('BBL', 'Barrel'), - ('BDL', 'Bundle'), - ('BIC', 'Bing Chest'), - ('BIN', 'Bin'), - ('BKT', 'Bucket'), - ('BLE', 'Bale'), - ('BLK', 'Bulk'), - ('BOX', 'Box'), - ('BSK', 'Basket'), - ('CAN', 'Can'), - ('CAR', 'Carcass'), - ('CAS', 'Case'), - ('CBC', 'Container Bulk Cargo'), - ('CBY', 'Carboy'), - ('CCS', 'Can Case'), - ('CHS', 'Chest'), - ('COL', 'Coil'), - ('COR', 'Cord'), - ('CRT', 'Crate'), - ('CSK', 'Cask'), - ('CTN', 'Carton'), - ('CYL', 'Cylinder'), - ('DBK', 'Bry Bulk'), - ('DRM', 'Drum'), - ('DZ', 'Dozen'), - ('FT', 'Feet'), - ('GAL', 'Gallon'), - ('HED', 'Heads of Beef'), - ('HMP', 'Hamper'), - ('KEG', 'Keg'), - ('L', 'Liter'), - ('LBK', 'Liquid Bulk'), - ('LOG', 'Logs'), - ('LUG', 'Lugs'), - ('LVN', 'Lift Van'), - ('M', 'Meters'), - ('PAA', 'Pairs'), - ('PAL', 'Pail'), - ('PCL', 'Parcel'), - ('PCS', 'Pieces'), - ('PKG', 'Package'), - ('POV', 'Private Vehicle'), - ('QTR', 'Quarters of Beef'), - ('REL', 'Reel'), - ('ROL', 'Roll'), - ('SAK', 'Sack'), - ('SFT', 'Square Feet'), - ('SHT', 'Sheet'), - ('SID', 'Sides of Beef'), - ('SKD', 'Skid'), - ('TBE', 'Tube'), - ('TBN', 'Tote Bin'), - ('TIN', 'Tin'), - ('TNK', 'Tank'), - ('UNT', 'Unit'), -] diff --git a/backend/api/v1/modules/a76/general_catalogs/units_of_measure/seed_adua.py b/backend/api/v1/modules/a76/general_catalogs/units_of_measure/seed_adua.py deleted file mode 100644 index 62c67215..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/units_of_measure/seed_adua.py +++ /dev/null @@ -1,24 +0,0 @@ -seed = [ - ("1", "Kilo", "KGS"), - ("2", "Gramo", "GR"), - ("3", "Metro Lineal", "MT"), - ("4", "Metro Cuadrado", "M2"), - ("5", "Metro Cubico", "M3"), - ("6", "Pieza", "PZA"), - ("7", "Cabeza", "PZA"), - ("8", "Litro", "LT"), - ("9", "Par", "PAR "), - ("10", "Kilowatt", ""), - ("11", "Millar", "MILLR"), - ("12", "Juego", "JGO"), - ("13", "Kilowatt/Hora", ""), - ("14", "Tonelada", "TON"), - ("15", "Barril", "BARR"), - ("16", "Gramo Neto", ""), - ("17", "Decenas", "DEC"), - ("18", "Cientos", "CIEN"), - ("19", "Decenas", "DOCE"), - ("20", "Caja", "CAJA"), - ("21", "Botella", "PZA"), - ("22", "Carat", "CARAT"), -] \ No newline at end of file diff --git a/backend/api/v1/modules/a76/general_catalogs/units_of_measure/seed_ame.py b/backend/api/v1/modules/a76/general_catalogs/units_of_measure/seed_ame.py deleted file mode 100644 index c7d844af..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/units_of_measure/seed_ame.py +++ /dev/null @@ -1,48 +0,0 @@ - -seed = [ - ('BBL', 'Barrels (42 Gallons ea) (Volume)'), - ('BOL', 'Boluses (Dosage)'), - ('CAP', 'Capsules (Dosage)'), - ('CAR', 'Carats (Weight)'), - ('CFT', 'Cubic Feet (Volume)'), - ('CGI', 'Centigrams (Weight)'), - ('CM', 'Centimeters (Long)'), - ('CM3', 'Cubic Centimeters (Volume)'), - ('CYD', 'Cubic Yards (Volume)'), - ('DOZ', 'Dozen (Count)'), - ('DPC', 'Dozen Pieces (Count)'), - ('DPR', 'Dozen Pairs (Count)'), - ('FOZ', 'Ounces, fluid (Volume)'), - ('FT', 'Feet (Length)'), - ('G', 'Grams (Weight)'), - ('GAL', 'Gallons (US)(Volume)'), - ('GR', 'Gross (Count)'), - ('KG2', 'Kilograms (Weight)'), - ('KM', 'Kilometers (Length)'), - ('KM2', '1000 sq Meter Area'), - ('KM3', '1,000 Cubic Meters (Volume)'), - ('L', 'Liters (Volume)'), - ('LB', 'Pounds (Weight)'), - ('LNM', 'Linear Meters (Length)'), - ('M', 'Meters (Length)'), - ('M2', 'SQ Meter (Area)'), - ('M3', 'Cubic Meters (Volume)'), - ('MG', 'Miligrams (Weight)'), - ('ML', 'Milliliters (Volume)'), - ('NO', 'Number (Count)'), - ('OZ', 'Weight'), - ('PCS', 'Pieces (Count)'), - ('PRS', 'Pairs (Count)'), - ('PTL', 'Pints, liquid (US)(Volume)'), - ('QTL', 'Quarts, liquid (US)(Volume)'), - ('SFT', 'SQ Feet (Area)'), - ('SQI', 'Sq Inches (Area)'), - ('STN', 'Short Ion (2000 LB)(Weight)'), - ('SUP', 'Suppositories (Dosage)'), - ('SYD', 'Sq. Yards (Area)'), - ('T', 'Metric Ton (Weight)'), - ('TAB', 'Tablets (Dosage)'), - ('TON', 'Long Ton (2,240 LB) (Weight)'), - ('TOZ', 'Ounces, Troy or Apoth (Weight)'), - ('YD', 'Yards (Length)'), -] \ No newline at end of file diff --git a/backend/api/v1/modules/a76/general_catalogs/units_of_measure/seed_oma.py b/backend/api/v1/modules/a76/general_catalogs/units_of_measure/seed_oma.py deleted file mode 100644 index 3a783b2d..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/units_of_measure/seed_oma.py +++ /dev/null @@ -1,1205 +0,0 @@ -seed = [ - ('05', 'LIFT'), - ('06', 'SMALL SPRAY'), - ('08', 'HEAT LOT'), - ('10_1', 'GROUP'), - ('11_1', 'OUTFIT'), - ('13_1', 'RATION'), - ('14_1', 'SHOT'), - ('15_1', 'STICK, MILITARY'), - ('16_1', 'HUNDRED FIFTEEN KG DRUM'), - ('17_1', 'HUNDRED LB DRUM'), - ('18_1', 'FIFTYFIVE GALLON (US) DRUM'), - ('19_1', 'TANK TRUCK'), - ('1A', 'CAR MILE'), - ('1B', 'CAR COUNT'), - ('1C', 'LOCOMOTIVE COUNT'), - ('1D', 'CABOOSE COUNT'), - ('1E', 'EMPY CAR'), - ('1F', 'TRAIN MILE'), - ('1G', 'FUEL USAGE GALLON (US)'), - ('1H', 'CABOOSE MILE'), - ('1I', 'FIXED RATE'), - ('1J', 'TON MILE'), - ('1K', 'LOCOMOTIVE MILE'), - ('1L', 'TOTAL CAR COUNT'), - ('1X', 'QUARTER MILE'), - ('20_1', 'TWENTY FOOT CONTAINER'), - ('21_1', 'FORTY FOOT CONTAINER'), - ('22', 'DECILITRE PER GRAM'), - ('23', 'GRAM PER CUBIC CENTIMETRE'), - ('24', 'THEORETICAL POUND'), - ('25', 'GRAM PER SQUARE CENTIMETRE'), - ('26', 'ACUAL TON'), - ('27', 'THEORETICAL TON'), - ('28', 'KILOGRAM PER SQUARE METRE'), - ('29', 'PUND PER THOUSAND SQUARE FEET'), - ('2A', 'RADIAN PER SECOND'), - ('2B', 'RADIAN PER SECOND SQUARED'), - ('2C', 'ROENTGEN'), - ('2I', 'BRITISH THERMAL UNIT PER HOUR'), - ('2J', 'CUBIC CENTIMETRE PER SECOND'), - ('2K', 'CUBIC FOOT PER HOUR'), - ('2L', 'CUBIC FOOT PER MINUTE'), - ('2M', 'CENTIMETRE PER SECOND'), - ('2N', 'DECIBEL'), - ('2P', 'KILOBYTE'), - ('2Q', 'KILOBECQUEREL'), - ('2R', 'KILOCURIE'), - ('2U', 'MEGAGRAM'), - ('2V', 'MEGAGRAM PER HOUR'), - ('2W', 'BIN'), - ('2X', 'METRE PER MINUTE'), - ('2Y', 'MILLIRÖNTGEN'), - ('2Z', 'MILLIVOLT'), - ('30', 'HORSE POWER DAR PER AIR DRY METRIC TON'), - ('31', 'CATCH WEIGHT'), - ('32', 'KILOGRAM PER AIR DRY METRIC TON'), - ('33', 'KILOPASCAL SQUARE METRES PER GRAM'), - ('34', 'KILOPASCALS PER MILLIMETRE'), - ('35', 'MILLILITRES PER SQUARE CENTIMETRE SECOND'), - ('36', 'CUBIC FEET PER MINUTE PER SQUARE FOOT'), - ('37', 'OUNCE PER SQUARE FOOT'), - ('38', 'OUNCES PER SQUARE FOOT PER 0, 01 INCH'), - ('3B', 'MEGAJOULE'), - ('3C', 'MANMONTH'), - ('3E', 'PUND PER PUND OF PRODUCT'), - ('3G', 'POUND PER PIECE OF PRODUCT'), - ('3H', 'KILOGRAM PER KILOGRAM OF PRODUCT'), - ('3I', 'KILOGRAM PER PIECE OF PRODUCT'), - ('40', 'MILLILITRE PER SECOND'), - ('41', 'MILLILITRE PER MINUTE'), - ('43', 'SUPER BULK BAG'), - ('44', 'FIVEHUNDRED KG BULK BAG'), - ('45', 'THREEHUNDRED KG BULK BAG'), - ('46', 'FIFTY BULK BAG'), - ('47', 'FIFTY LB BAG'), - ('48', 'BULK CAR LOAD'), - ('4A', 'BOBBIN'), - ('4B', 'CAP'), - ('4C', 'CENTISTOKES'), - ('4E', 'TWENTY PACK'), - ('4G', 'MICROLITRE'), - ('4H', 'MICROMETRE (MICRON)'), - ('4K', 'MILLIAMPERE'), - ('4L', 'MEGABYTE'), - ('4M', 'MILLIGRAM PER HOUR'), - ('4N', 'MEGABECQUEREL'), - ('4O', 'MICROFARAD'), - ('4P', 'NEWTON PER METRE'), - ('4Q', 'OUNCE INCHE'), - ('4R', 'OUNCE FOOT'), - ('4T', 'PICOFARAD'), - ('4U', 'PUND PER HOUR'), - ('4W', 'TON (US) PER HOUR'), - ('4X', 'KILOLITRE PER HOUR'), - ('53', 'THEORETICAL KILOGRAMS'), - ('54', 'THEORETICAL TONNE'), - ('56', 'SITAS'), - ('57', 'MESH'), - ('58', 'NET KILOGRAM'), - ('59', 'PART PER MILLION'), - ('5A', 'BARREL (US) PER MINUTE'), - ('5B', 'BATCH'), - ('5C', 'GALLON (US) PER THOUSAND'), - ('5E', 'MMSCF/DAY'), - ('5F', 'POUNDS PER THOUSAND'), - ('5G', 'PUMP'), - ('5H', 'STAGE'), - ('5I', 'STANDARD CUBIC FOOT'), - ('5J', 'HYDRAULIC HORSE POWER'), - ('5K', 'COUNT PER MINUTE'), - ('5P', 'SEISMIC LEVEL'), - ('5Q', 'SEISMIC LINE'), - ('60', 'PERCEN WEIGHT'), - ('61', 'PART PER BILLION (US)'), - ('62', 'PERCENT PER 1000 HOUR'), - ('63', 'FAILURE RATE IN TIME'), - ('64', 'POUND PER SQUARE INCH, GAUGE'), - ('66', 'OERSTED'), - ('69', 'TEST SPECIFIC SCALE'), - ('71', 'VOLT AMPERE PER PUND'), - ('72', 'WATT PER POUND'), - ('73', 'AMPERE TUM PER CENTIMETRE'), - ('74', 'MILLIPASCAL'), - ('76', 'GAUSS'), - ('77', 'MILLI-INCH'), - ('78', 'KILOGAUSS'), - ('80', 'POUNDS PER SAUQRE INCHE ABSOULTE'), - ('81', 'HENRY'), - ('84', 'KILOPOUND PER SQUARE INCH'), - ('85', 'FOOT POUND-FORCE'), - ('87', 'POUND PER CUBIC FOOT'), - ('89', 'POISE'), - ('90', 'SAYBOLD UNIVERSAL SECOND'), - ('91', 'STOKES'), - ('92', 'CALORIE PER CUBIC CENTIMETRE'), - ('93', 'CALORIE PER GRAM'), - ('94', 'CURL UNIT'), - ('95', 'TWENTY THOUSAND GALLON (US) TANKCAR'), - ('96', 'TEN THOUSAND GALLON (US) TANKCAR'), - ('97', 'TEN KG DRUM'), - ('98', 'FIFTEEN KG DRUM'), - ('A1', '15° C CALORIE'), - ('A10', 'AMPERE SQUARE METRE PER JOULE SECOND'), - ('A11', 'ANGSTROM'), - ('A12', 'ASTRONOMICAL UNIT'), - ('A13', 'ATTOJOULE'), - ('A14', 'BARN'), - ('A15', 'BARN PER ELECTRON VOLT'), - ('A16', 'BARN PER STERADIAN ELECTRONVOLT'), - ('A17', 'BARN PER STERADIAN'), - ('A18', 'BECQUEREL PER KILOGRAM'), - ('A19', 'BECQUEREL PER METRE CUBED'), - ('A2', 'AMPERE PER CENTIMETRE'), - ('A20', 'BRITISH THERMAL UNIT PER SECOND SQUARE FOOT DEGREE RANKIN'), - ('A21', 'BRITISH THERMAL UNIT PER POUND DEGREE RANKIN'), - ('A22', 'BRITISH THERMAL UNIT PER SECOND FOOT DEGREE RANKIN'), - ('A23', 'BRITHIS THERMAL UNIT PER HOUR SQUARE FOOT DEGREE RANKIN'), - ('A24', 'CANDELA PER SQUARE METRE'), - ('A25', 'CHEVAL VAPEUR'), - ('A26', 'COULOMB METRE'), - ('A27', 'COULOMB METRE SQUARED PER VOLT'), - ('A28', 'COULOMB PER CUBIC CENTIMETRE'), - ('A29', 'COULOMB PER CUBIC METRE'), - ('A3', 'AMPERE PER MILLIMETRE'), - ('A30', 'COULOMB PER CUBIC MILLIMETRE'), - ('A31', 'COULOMB PER KILOGRAM SECOND'), - ('A32', 'COULOMB PER MOLE'), - ('A33', 'COULOMB PER SQUARE CENTIMETRE'), - ('A34', 'COULOMB PER SQUARE METRE'), - ('A35', 'COULOMB PER SQUARE MILLIMETRE'), - ('A36', 'CUBIC CENTIMETRE PER MOLE'), - ('A37', 'CUBIC DECIMETRE PER MOLE'), - ('A38', 'CUBIC METRE PER COULOMB'), - ('A39', 'CUBIC METRE PER KILOGRAM'), - ('A4', 'AMPERE PER SQURE CENTRIMETRE'), - ('A40', 'CUBIC METRE PER MOLE'), - ('A41', 'AMPERE PER SQUARE METRE'), - ('A42', 'CURIE PER KILOGRAM'), - ('A43', 'DEADWEIGHT TONNAGE'), - ('A44', 'DECALITRE'), - ('A45', 'DECAMETRE'), - ('A47', 'DECITEX'), - ('A48', 'DEGREE RANKIN'), - ('A49', 'DENIER'), - ('A5', 'AMPERE SQUARE METRE'), - ('A50', 'DYNE SECOND PER CUBIC CENTIMETRE'), - ('A51', 'DYNE SECOND PER CENTIMETRE'), - ('A52', 'DYNE SECOND PER CENTIMETRE TO THE FIFTH POWER'), - ('A53', 'ELECTRONVOLT'), - ('A54', 'ELECTRONVOLT PER METRE'), - ('A55', 'ELECTRONVOLT SQUARE METRE'), - ('A56', 'ELECTRONVOLT SQUARE METRE PER KILOGRAM'), - ('A57', 'ERG'), - ('A58', 'ERG PER CENTIMETRE'), - ('A59', '8-PART CLOUD COVER'), - ('A6', 'AMPERE PER SQUARE METRE KELVIN SQUARED'), - ('A60', 'ERG PER CUBIC CENTIMETRE'), - ('A61', 'ERG PER GRAM'), - ('A62', 'ERG PER GRAM SECOND'), - ('A63', 'ERG PER SECOND'), - ('A64', 'ERG PER SECOND SQUARE CENTIMETRE'), - ('A65', 'ERG PER SQUARE CENTIMETRE SECOND'), - ('A66', 'ERG SQUARE CENTIMETRE'), - ('A67', 'ERG SQTUARE CENTIMETRE PER GRAM'), - ('A68', 'EXAJOULE'), - ('A69', 'FARAD PER METRE'), - ('A7', 'AMPERE PER SQUARE MILLIMETRE'), - ('A70', 'FEMTOJOULE'), - ('A71', 'FEMTOMETRE'), - ('A73', 'FOOT PER SECOND SQUARED'), - ('A74', 'FOOT POUND-FORCE PER SECOND'), - ('A75', 'FREIGHT TON'), - ('A76', 'GALLON (US) PER THOUSAND'), - ('A77', 'GAUSSIAN CGS UNIT OF DISPLACEMENT'), - ('A78', 'GAUSSIAN CGS UNIT OF ELECTRIC CURRENT'), - ('A79', 'GAUSSIAN CGS UNIT OF ELECTRIC CHARGE'), - ('A8', 'AMPERE SECOND'), - ('A80', 'GAUSSIAN CGS UNIT OF ELECTRIC FIELD STRENGHT'), - ('A81', 'GAUSSIAN CGS UNIT OF ELECTRIC POLARIZATION'), - ('A82', 'GAUSSIAN CGS UNIT OF ELECTRIC POTENCIAL'), - ('A83', 'GAUSSIAN CGS UNIT OF MAGNETIZATION'), - ('A84', 'GIGACOULOMB PER CUBIC METRE'), - ('A85', 'GIGAELECTRONVOLT'), - ('A86', 'GIGAHERTZ'), - ('A87', 'GIGAOHM'), - ('A88', 'GIGAOHM METRE'), - ('A89', 'GIGAPASCAL'), - ('A9', 'RATE'), - ('A90', 'GIGAWATT'), - ('A91', 'GON'), - ('A92', 'GRADE'), - ('A93', 'GRAM PER CUBIC METRE'), - ('A94', 'GRAM PER MOLE'), - ('A95', 'GRAY'), - ('A96', 'GRAY PER SECOND'), - ('A97', 'HECTOPASCAL'), - ('A98', 'HENRY PER METRE'), - ('A99', 'BIT'), - ('AA', 'BALL'), - ('AB', 'BULK PACK'), - ('ACR', 'ACRE'), - ('ACT', 'ACTIVITY'), - ('AD', 'BYTE'), - ('AE', 'AMPERE PER METRE'), - ('AH', 'ADDITIONAL MINUTE'), - ('AI', 'AVERAGE MINUTE PER CALL'), - ('AJ', 'COP'), - ('AK', 'FATHOM'), - ('AL', 'ACCESS LINE'), - ('AM', 'AMPOULE'), - ('AMH', 'APERE HOUR'), - ('AMP', 'AMPERE'), - ('ANN', 'YEAR'), - ('AP', 'ALUMINUM POUND ONLY'), - ('APZ', 'TROY OUNCE OT APOTHECARY OUNCE'), - ('AQ', 'ANTI-HEMOPHILIC FACTOR (AHF) UNIT'), - ('AR', 'SUPPOSITORY'), - ('ARE', 'ARE'), - ('AS', 'ASSORTMENT'), - ('ASM', 'ALCOHOLIC STRENGHT BY MASS'), - ('ASU', 'ALCOHOLIC STRENGHT BY VOLUME'), - ('ATM', 'STANDARD ATMOSPHERE'), - ('ATT', 'TECHNICAL ATMOSPHERE'), - ('AV', 'CAPSULE'), - ('AW', 'POWDER FILLED VIAL'), - ('AY', 'ASSEMBLY'), - ('AZ', 'BRITHIS THERMAL UNIT PER POUND'), - ('B0', 'BTU PER CUBIC FOOT'), - ('B1', 'BARREL (US) PER DAY'), - ('B10', 'BIT PER SECOND'), - ('B11', 'JOULE PER KILOGRAM KELVIN'), - ('B12', 'JOULE PER METRE'), - ('B13', 'JOULE PER METRE SQUARED'), - ('B13_1', 'JOULE PER SQUARE METRE'), - ('B14', 'JOULE PER METRE TO THE FOURTH POWER'), - ('B15', 'JOULE PER MOLE'), - ('B16', 'JOULE PER MOLE KELVIN'), - ('B17', 'CREDIT'), - ('B18', 'JOULE SECOND'), - ('B19', 'DIGIT'), - ('B2', 'BUNK'), - ('B20', 'JOULE SQUARE METRE PER KILOGRAM'), - ('B21', 'KELVIN PER WATT'), - ('B22', 'KILOAMPERE'), - ('B23', 'KILOAMPERE PER SQUARE METRE'), - ('B24', 'KILOAMPERE PER METRE'), - ('B25', 'KILOBECQUEREL PER KILOGRAM'), - ('B26', 'KILOCOULOMB'), - ('B27', 'KILOCOULOMB PER CUBIC METRE'), - ('B28', 'KILOCOULOMB PER SQUARE METRE'), - ('B29', 'KILOELECTRONVOLT'), - ('B3', 'BATTIN POUND'), - ('B30', 'GIDIBIT'), - ('B31', 'KILOGRAM METRE PER SECOND'), - ('B32', 'KILOGRAM METRE SQUARED'), - ('B33', 'KILOGRAM METRE SQUARED PER SECOND'), - ('B34', 'KILOGRAM PER CUBIC DECIMETRE'), - ('B35', 'KILOGRAM PER LITRE'), - ('B35_1', 'KILOGRAM PER LITRE OF PRODUCT'), - ('B36', 'THERMOCHEMICAL CALORIE PER GRAM'), - ('B37', 'KILOGRAM-FORCE'), - ('B38', 'KILOGRAM-FORCE METRE'), - ('B39', 'KILOGRAM-FORCE METRE PER SECOND'), - ('B4', 'BARREL, IMPERIAL'), - ('B40', 'KILOGRAM-FORCE PER SQUARE METRE'), - ('B41', 'KILOJOULE PER KELVIN'), - ('B42', 'KILOJOULE PER KILOGRAM'), - ('B43', 'KILOJOULE PER KILOGRAM KELVIN'), - ('B44', 'KILOJOULE PER MOLE'), - ('B45', 'KILOMOLE'), - ('B46', 'KILOMOLE PER CUBIC METRE'), - ('B47', 'KILONEWTON'), - ('B48', 'KILONEWTON METRE'), - ('B49', 'KILOOHM'), - ('B5', 'BILLET'), - ('B50', 'KILOOHM METRE'), - ('B51', 'KILOPOND'), - ('B52', 'KILOSECOND'), - ('B53', 'KILOSIEMENS'), - ('B54', 'KILOSIEMENS PE RMETRE'), - ('B55', 'KILOVOLT PER METRE'), - ('B56', 'KILOWEBER PER METRE'), - ('B57', 'LIGHT YEAR'), - ('B58', 'LITRE PER MOLE'), - ('B59', 'LUMEN HOUR'), - ('B6', 'BUNK'), - ('B60', 'LUMEN PER SQUARE METRE'), - ('B61', 'LUMEN PER WATT'), - ('B62', 'LUMEN SECOND'), - ('B63', 'LUX HOUR'), - ('B64', 'LUX SECOND'), - ('B65', 'MAXWELL'), - ('B66', 'MEGAAMPERE PER SQUARE METRE'), - ('B67', 'MEGABECQUEREL PER KILOGRAM'), - ('B68', 'GIGABIT'), - ('B7', 'CYCLE'), - ('B70', 'MEGACOULOMB PER SQUARE METRE'), - ('B71', 'MEGAELECTRONVOLT'), - ('B72', 'MEGAGRAM PER CUBIC METRE'), - ('B73', 'MEGANEWTON'), - ('B74', 'MEGANEWTON METRE'), - ('B75', 'MEGAOHM'), - ('B76', 'MEGAOHM METRE'), - ('B77', 'MEGASIEMENS PER METRE'), - ('B78', 'MEGAVOLT'), - ('B79', 'MEGAVOLT PER METRE'), - ('B8', 'JOULE PER CUBIC METRE'), - ('B80', 'GIGABIT PER SECOND'), - ('B81', 'RECIPROCAL METRE SQUARED RECIPROCAL SECOND'), - ('B82', 'INCHE PER LINEAR FOOT'), - ('B83', 'METRE TO THE FOURTH POWER'), - ('B84', 'MICROAMPERE'), - ('B85', 'MICROBAR'), - ('B86', 'MICROCOULOMB'), - ('B87', 'MICROCOULOMB PER CUBIC METRE'), - ('B88', 'MICROCOULOMB PER SQUARE METRE'), - ('B89', 'MICROFARAD PER METRE'), - ('B9', 'BATTIN POUND'), - ('B90', 'MICROHENRY'), - ('B91', 'MICROHENRY PER METRE'), - ('B92', 'MICRONEWTON'), - ('B93', 'MICRONEWTON METRE'), - ('B94', 'MIOCROOHM'), - ('B95', 'MICROOHM METRE'), - ('B96', 'MICROPASCAL'), - ('B97', 'MICRORADIAN'), - ('B98', 'MICROSECOND'), - ('B99', 'MICROSIEMENS'), - ('BAR', 'BAR [UNIT OF PRESSURE]'), - ('BB', 'BASE BOX'), - ('BD', 'BOARD'), - ('BE', 'BUNDLE'), - ('BFT', 'BOARD FOOT'), - ('BG', 'BAG'), - ('BH', 'BRUSH'), - ('BHP', 'BRAKE HORSE POWER'), - ('BIL_EUR', 'BILLION (EUR)'), - ('BIL_US', 'TRILLION (US)'), - ('BJ', 'BUCKET'), - ('BK', 'BASKET'), - ('BL', 'BALE'), - ('BLD', 'DRY BARREL (S)'), - ('BLL', 'BARREL (US)'), - ('BO', 'BOTTLE'), - ('BP', 'HUNDRED BOARD FEET'), - ('BQL', 'BECQUEREL'), - ('BR', 'BAR [UNIT OF PACKAGING]'), - ('BT', 'BOLT'), - ('BTU', 'BRITISH THERMAL UNIT'), - ('BUA', 'BUSHEL (US)'), - ('BUI', 'BUSHEL (UK)'), - ('BW', 'BASE WEIGHT'), - ('BX', 'BOX'), - ('BZ', 'MILLION BTUS'), - ('C0', 'CALL'), - ('C1', 'COMPOSITE PRODUCT POUND (TOTAL WEIGHT)'), - ('C10', 'MILLIFARAD'), - ('C10_1', 'MILLIVOLT PER METRE'), - ('C11', 'MILLIGAL'), - ('C12', 'MILLIGRAM PER METRE'), - ('C13', 'MILLIGRAY'), - ('C14', 'MILLIHENRY'), - ('C15', 'MILLIJOULE'), - ('C16', 'MILLIMETRE PER SECOND'), - ('C17', 'MILLIMETRE SQUARED PER SECOND'), - ('C18', 'MILLIMOLE'), - ('C19', 'MOLE PER KILOGRAM'), - ('C2', 'CARSET'), - ('C20', 'MILLNEWTON'), - ('C21', 'KIBIBIT'), - ('C22', 'MILLINEWTON PER METRE'), - ('C23', 'MILLIOHM METRE'), - ('C24', 'MILLIPASCAL SECOND'), - ('C25', 'MILLIRADIAN'), - ('C26', 'MILLISECOND'), - ('C27', 'MILLISIEMENS'), - ('C28', 'MILLISIEVERT'), - ('C29', 'MILLITESLA'), - ('C3', 'MICROVOLT PER METRE'), - ('C31', 'MILLIWATT'), - ('C32', 'MILLIWATT PER SQUARE METRE'), - ('C33', 'MILLIWEBER'), - ('C34', 'MOLE'), - ('C35', 'MOLE PER CUBIC DECIMETRE'), - ('C36', 'MOLE PER CUBIC METRE'), - ('C37', 'KILOBIT'), - ('C38', 'MOLE PER LITRE'), - ('C39', 'NANOAMPERE'), - ('C4', 'CARLOAD'), - ('C40', 'NANOCOULOMB'), - ('C41', 'NANOFARAD'), - ('C42', 'NANOFARAD PER METRE'), - ('C43', 'NANOHENRY'), - ('C44', 'NANOHENRY PER METRE'), - ('C45', 'NANOMETRE'), - ('C46', 'NANOOHM METRE'), - ('C47', 'NANOSECOND'), - ('C48', 'NANOTESLA'), - ('C49', 'NANOWATT'), - ('C5', 'COST'), - ('C50', 'NEPER'), - ('C51', 'NEPER PER SECOND'), - ('C52', 'PICOMETRE'), - ('C53', 'NEWTON METRE SECOND'), - ('C54', 'NEWTON METRE SQUARED KILOGRAM SQUARED'), - ('C55', 'NEWTON PER SQUARE METRE'), - ('C56', 'NEWTON PER SQUARE MILLIMETRE'), - ('C57', 'NEWTON SECOND'), - ('C58', 'NEWTON SECOND PER METRE'), - ('C59', 'OCTAVE'), - ('C6', 'CELL'), - ('C60', 'OHM CENTIMETRE'), - ('C61', 'OHM METRE'), - ('C62', 'ONE'), - ('C62_1', 'PIECE'), - ('C62_2', 'UNIT'), - ('C63', 'PARSEC'), - ('C64', 'PASCAL PER KELVIN'), - ('C65', 'PASCAL PER SECOND'), - ('C66', 'PASCAL SECOND PER CUBIC METRE'), - ('C67', 'PASCAL SECOND PER METRE'), - ('C68', 'PETAJOULE'), - ('C69', 'PHON'), - ('C7', 'CENTIPOISE'), - ('C70', 'PICOAMPERE'), - ('C71', 'PICOCOULOMB'), - ('C72', 'PICOFARAD PER METRE'), - ('C73', 'PICOHENRY'), - ('C74', 'KILOBIT PER SECOND'), - ('C75', 'PICOWATT'), - ('C76', 'PICOWATT PER SQUARE METRE'), - ('C77', 'PUND PAGE'), - ('C78', 'PUND-FORCE'), - ('C79', 'KILOVOLT AMPERE HOUR'), - ('C8', 'MILLICOULOMB PER KILOGRAM'), - ('C80', 'RAD'), - ('C81', 'RADIAN'), - ('C82', 'RADIAN SQUARE METRE PER MOLE'), - ('C83', 'RADIAN SQUARE METRE PER KILOGRAM'), - ('C84', 'RADIAN PER METRE'), - ('C85', 'RECIPROCAL ANGSTROM'), - ('C86', 'RECIPROCAL CUBIC METRE'), - ('C87', 'RECIPROCAL CUBIC METRE PER SECOND'), - ('C88', 'RECIPROCAL ELECTRON VOLT PER CUBIC METRE'), - ('C89', 'RECIPROCAL HENRY'), - ('C9', 'COIL GROUP'), - ('C90', 'RECIPROCAL JOUL EPER CUBIC METRE'), - ('C91', 'RECIPROCAL KELVIN OR KELVIN TO THE POWER MINUS ONE'), - ('C92', 'RECIPROCAL METRE'), - ('C93', 'RECIPROCAL METRE SQUARED'), - ('C93_1', 'RECIPROCAL SQUARED METRE'), - ('C94', 'RECIPROCAL MINUTE'), - ('C95', 'RECIPROCAL MOLE'), - ('C96', 'RECIPROCAL PASCAL OR PASCAL TO THE POWER MINUS ONE'), - ('C97', 'RECIPROCAL SECOND'), - ('C98', 'RECIPROCAL SECOND PER CUBIC METRE'), - ('C99', 'RECIPROCAL SECOND PER METRE SQUARED'), - ('CA', 'CAN'), - ('CCT', 'CARRYING CAPACITY IN METRIC TON'), - ('CDL', 'CANDELA'), - ('CEL', 'DEGREE CELSIUS'), - ('CEN', 'HUNDRED'), - ('CG', 'CARD'), - ('CGM', 'CENTIGRAM'), - ('CH', 'CONTAINER'), - ('CJ', 'CONE'), - ('CK', 'CONNECTOR'), - ('CKG', 'COULOMB PER KILOGRAM'), - ('CL', 'COIL'), - ('CLF', 'HUNDRED LEAVE'), - ('CLT', 'CENTILITRE'), - ('CMK', 'SQUARE CENTIMETRE'), - ('CMQ', 'CUBIC CENTRIMETRE'), - ('CMT', 'CENTIMETRE'), - ('CNP', 'HUNDRED PACK'), - ('CNT', 'CENTAL (UK)'), - ('CO', 'CARBOY'), - ('COU', 'COULOMB'), - ('CQ', 'CARTRIDGE'), - ('CR', 'CRATE'), - ('CS', 'CASE'), - ('CT', 'CARTON'), - ('CTG', 'CONTENT GRAM'), - ('CTM', 'METRIC CARAT'), - ('CTN', 'CONTENT TON (METRIC)'), - ('CU', 'CUP'), - ('CUR', 'CURIE'), - ('CV', 'COVER'), - ('CWA', 'HUNDRED POUNDS (CWT)/HUNDRED WEIGHT (US)'), - ('CWI', 'HUNDRED WEIGHT (UK)'), - ('CY', 'CYLINDER'), - ('CZ', 'COMBO'), - ('D03', 'KILOWATT HOUR PER HOUR'), - ('D04', 'LOT [UNIT OF WEIGHT]'), - ('D1', 'RECIPROCAL SECOND PER STERADIAN'), - ('D10', 'SIEMENS PER METRE'), - ('D11', 'MEBIBIT'), - ('D12', 'SIEMENS SQUARE METRE PER MOLE'), - ('D13', 'SIEVERT'), - ('D14', 'THOUSAND LINEAR YARD'), - ('D15', 'SONE'), - ('D16', 'SQUARE CENTIMETRE PER ERG'), - ('D17', 'SQUARE CENTIMETRE PER STERADIAN ERG'), - ('D18', 'METRE KELVIN'), - ('D19', 'SQUARE METRE KELVIN PER WATT'), - ('D2', 'RECIPROCAL SECOND PER STERADIAN METRE SQUARED'), - ('D20', 'SQUARE METRE PER JOULE'), - ('D21', 'SQUARE METRE PER KILOGRAM'), - ('D22', 'SQUARE METRE PER MOLE'), - ('D23', 'PEN GRAM (PROTEIN)'), - ('D24', 'SQUARE METRE PER STERADIAN'), - ('D25', 'SQUARE METRE PER STERADIAN JOULE'), - ('D26', 'SQUARE METRE PER VOLT SECOND'), - ('D27', 'STERADIAN'), - ('D28', 'SYPHON'), - ('D29', 'TERAHERTZ'), - ('D30', 'TERAJOULE'), - ('D31', 'TERAWATT'), - ('D32', 'TERAWATT HOUR'), - ('D33', 'TESLA'), - ('D34', 'TEX'), - ('D35', 'THERMOCHEMICAL CALORIE'), - ('D36', 'MEGABIT'), - ('D37', 'THERMOCHEMICAL CALORIE PER GRAM KELVIN'), - ('D38', 'THERMOCHEMICAL CALORIE PER SECOND CENTIMETRE KELVIN'), - ('D39', 'THERMOCHEMICAL CALORIE PER SECOND SQAURE CENTIMETRE KELVIN'), - ('D40', 'THOUSAND LITRE'), - ('D41', 'TONNE PER CUBIC METRE'), - ('D42', 'TROPICAL YEAR'), - ('D43', 'UNIFIED ATOMIC MASS UNIT'), - ('D44', 'VAR'), - ('D45', 'VOLT SQUARED PER KELVIN SQUARED'), - ('D46', 'VOLT-AMPERE'), - ('D47', 'VOLT PER CENTIMETRE'), - ('D48', 'VOLT PER KELVIN'), - ('D49', 'MILIVOLT PER KELVIN'), - ('D5', 'KILOGRAM PER SQUARE CENTIMETRE'), - ('D50', 'VOLT PER METRE'), - ('D51', 'VOLT PER MILLIMETRE'), - ('D52', 'WATT PER KELVIN'), - ('D53', 'WAT PER METRE KELVIN'), - ('D54', 'WATT PER SQUARE METRE'), - ('D55', 'WATT PER SQUARE METRE KELVIN'), - ('D56', 'WATT PER SQUARE METRE KELVIN TO THE FOURTH POWER'), - ('D57', 'WATT PER STERADIAN'), - ('D58', 'WATT PER STERADIAN SQUARE METRE'), - ('D59', 'WEBER PER METRE'), - ('D6', 'ROENTGEN PER SECOND'), - ('D60', 'WEBER PER MILLIMETRE'), - ('D61', 'MINUTE [UNIT OF ANGLE]'), - ('D62', 'SECOND [UNIT OF ANGLE]'), - ('D63', 'BOOK'), - ('D64', 'BLOCK'), - ('D65', 'ROUND'), - ('D66', 'CASSETTE'), - ('D67', 'DOLLAR PER HOUR'), - ('D68', 'NUMBER OF WORDS'), - ('D69', 'INCH TO THE FOURTH POWER'), - ('D7', 'SANDWICH'), - ('D70', 'INTERNATIONAL TABLE (IT) CALORIE)'), - ('D71', 'INTERNATIONAL TABLE (IT) CALORIE PER SECOND CENTIMETRE KELVIN'), - ('D72', 'INTERNATIONAL TABLE (IT) CALORIE PER SECOND SQUARE CENTIMETRE KELVIN'), - ('D73', 'JOULE SQUARE METRE PER KILOGRAM'), - ('D74', 'KILOGRAM PER MOLE'), - ('D75', 'INTERNATIONAL TABLE (IT) CALORIE PER GRAM'), - ('D76', 'INTERNATIONAL TABLE (IT) CALORIE PER GRAM KELVIN'), - ('D77', 'MEGACOULOMB'), - ('D78', 'MEGAJOULE PER SECOND'), - ('D79', 'BEAM'), - ('D8', 'DRAIZE SCORE'), - ('D80', 'MICROWATT'), - ('D81', 'MICROTESLA'), - ('D82', 'MICROVOLT PER METRE'), - ('D83', 'MILLINEWTON METRE'), - ('D85', 'MICROWATT PER SQUARE METRE'), - ('D86', 'MILLICOULOMB PER KILOGRAM'), - ('D87', 'MILLIMOLE PER KILOGRAM'), - ('D88', 'MILLICOULOMB PER CUBIC METRE'), - ('D89', 'MILLICOULOMB PER SQUARE METRE'), - ('D9', 'DYNE PER SQUARE CENTIMETRE'), - ('D90', 'CUBIC METRE (NET'), - ('D91', 'REM'), - ('D92', 'BAND'), - ('D93', 'SECOND PER CUBIC METRE'), - ('D94', 'SECOND PER RADIAN CUBIC METRE'), - ('D95', 'JOULE PER GRAM'), - ('D96', 'POUND GROSS'), - ('D97', 'PALLET/UNIT LOAD'), - ('D98', 'MASS POUND'), - ('D99', 'SLEEVE'), - ('DAA', 'DECARE'), - ('DAD', 'TEN DAY'), - ('DAY', 'DAY'), - ('DB', 'DRY POUND'), - ('DC', 'DISK (DISC)'), - ('DD', 'DEGREE CELSIUS HUNDRED'), - ('DD_1', 'DEGREE [UNIT OF ANGLE]'), - ('DE', 'DEAL'), - ('DEC', 'DECADE'), - ('DG', 'DECIGRAM'), - ('DI', 'DISPENSE'), - ('DJ', 'DECAGRAM'), - ('DKW', 'KILOGRAM DRAINED NET WEIGHT'), - ('DLT', 'DECILITRE'), - ('DMK', 'SQUARE DECIMETRE'), - ('DMO', 'STANDARD KILOLITRE'), - ('DMQ', 'CUBIC DECIMETRE PER MOLE'), - ('DMT', 'DECIMETRE'), - ('DN', 'DECINEWTON METRE'), - ('DPC', 'DOZEN PIECE'), - ('DPR', 'DOZEN PAIR'), - ('DPT', 'DISPLACEMENT TONNAGE'), - ('DQ', 'DATA RECORD'), - ('DR', 'DRUM'), - ('DRA', 'DRAM (US)'), - ('DRI', 'DRAM (UK)'), - ('DRL', 'DOZEN ROLL'), - ('DRM', 'DRACHM (UK)'), - ('DS', 'DISPLAY'), - ('DT', 'DRY TON'), - ('DTN', 'CENTNER, METRIC 100KG'), - ('DTN_1', 'DECITONNE'), - ('DTN_2', 'QUINTAL, METRIC 100KG'), - ('DU', 'DYNE'), - ('DWT', 'PENNYWEIGHT'), - ('DX', 'DYNE PER CENTIMETRE'), - ('DY', 'DIRECTORY BOOK'), - ('DZN', 'DOZEN'), - ('DZP', 'DOZEN PACK'), - ('E01', 'NEWTON PER SQUARE CENTIMETRE'), - ('E07', 'MEGAWATT HOUR PER HOUR'), - ('E08', 'MEGAWATT PER HERTZ'), - ('E09', 'MILLIAMPERE HOUR'), - ('E10', 'DEGREE DAYS'), - ('E11', 'GIGACALORIE'), - ('E12', 'MILLE'), - ('E14', 'KILOCALORIE (IT)'), - ('E15', 'KILOCALORIE (TH) PER HOUR'), - ('E16', 'MILLION BTU (IT) PER HOUR'), - ('E17', 'CUBIC FOOT PER SECOND'), - ('E18', 'TONNE PER HOUR'), - ('E19', 'PING'), - ('E2', 'BELT'), - ('E20', 'MEGABIT PER SECOND'), - ('E21', 'SHARES'), - ('E22', 'TEU'), - ('E23', 'TYRE'), - ('E25', 'ACTIVE UNIT'), - ('E27', 'DOSE'), - ('E28', 'AIR DRY TON'), - ('E3', 'TRAILER'), - ('E30', 'STRAND'), - ('E31', 'SQUARE METRE PER LITRE'), - ('E32', 'LITRE PER HOUR'), - ('E33', 'FOOT PER THOUSAND'), - ('E34', 'GIGABYTE'), - ('E35', 'TERABYTE'), - ('E36', 'PETABYTE'), - ('E37', 'PIXEL'), - ('E38', 'MEGAPIXEL'), - ('E39', 'DOTS PER INCH'), - ('E4', 'GROSS KILOGRAM'), - ('E40', 'PART PER HUNDRED THOUSAND'), - ('E41', 'KILOGRAM FORCE PER SQUARE MILLIMETRE'), - ('E42', 'KILOGRAM FORCE PER SQUARE CENTIMETRE'), - ('E43', 'JOULE PER SQUARE CENTIMETRE'), - ('E44', 'KILOGRAM-FORCE METRE PER SQUARE CENTIMETRE'), - ('E5', 'METRIC LONG TON'), - ('EA', 'EACH'), - ('EB', 'ELECTRONICA MAIL BOX'), - ('EC', 'EACH PER MONTH'), - ('EP', 'ELEVEN PACK'), - ('EQ', 'EQUIVALEN GALLON'), - ('EV', 'ENVELOPE'), - ('F1', 'THOUSAND CUBIC FEET PER DAY'), - ('F9', 'FIBRE PER CUBIC CENTIMETRE OF AIR'), - ('FAH', 'DEGREE FAHRENHEIT'), - ('FAR', 'FARAD'), - ('FB', 'FIELD'), - ('FBM', 'FIBRE METRE'), - ('FC', 'THOUSAND CUBIC FEET'), - ('FD', 'MILLION PARTICLE PER CUBIC FOOT'), - ('FE', 'TRACK FOOT'), - ('FF', 'HUNDRED CUBIC METRE'), - ('FG', 'TRANSDERMAL PATCH'), - ('FH', 'MICROMOLE'), - ('FL', 'FLAKE TON'), - ('FM', 'MILLION CUBIC FEET'), - ('FOT', 'FOOT PER THOUSAND'), - ('FP', 'POUND PER SQUARE FOOT'), - ('FR', 'FOOT PER MINUTE'), - ('FS', 'FOOT PER SECOND SQUARED'), - ('FTK', 'SQUARE FOOT'), - ('FTQ', 'CUBIC FOOT'), - ('G2', 'US GALLON PER MINUTE'), - ('G3', 'IMPERIAL GALLON PER MINUTE'), - ('G7', 'MICROFICHE SHEET'), - ('GB', 'GALLON (US) PER DAY'), - ('GBQ', 'GIGABECQUEREL'), - ('GC', 'GRAM PER 100 GRAM'), - ('GD', 'GROSS BARREL'), - ('GDW', ' GRAM, DRY WEIGHT'), - ('GE', 'POUND PER GALLON (US)'), - ('GF', 'GRAM PER METRE (GRAM PER 100 CENTIMETRES'), - ('GFI', 'GRAM FISSILE ISOTOPE'), - ('GGR', 'GREAT GROSS'), - ('GH', 'HALF GALLON'), - ('GIA', 'GILL'), - ('GIC', 'GRAM, INCLUDING CONTAINER'), - ('GII', 'GILL (UK)'), - ('GIP', 'GRAM, INCLUDING INNER PACKAGING'), - ('GJ', 'GRAM PER MILLILITRE'), - ('GK', 'GRAM PER KILOGRAM'), - ('GL', 'GRAM PER LITRE'), - ('GLD', 'DRY GALLON (S)'), - ('GLI', 'GALLON (UK)'), - ('GLL', 'GALLON (US)'), - ('GM', 'GRAM PER SQUARE METRE'), - ('GN', 'GROSS GALLON'), - ('GO', 'MILLIGRAMS PER SQUARE METRE'), - ('GP', 'MILLIGRAMS PER CUBIC METRE'), - ('GQ', 'MICROGRAM PER CUBIC METRE'), - ('GRM', 'GRAM'), - ('GRN', 'GRAIN'), - ('GRO', 'GROSS'), - ('GRT', 'GROSS REGISTER TON'), - ('GT', 'GROSS TON'), - ('GT_1', 'METRIC GROSS TON'), - ('GV', 'GIGAJOULE'), - ('GW', 'GALLON PER THOUSAND CUBIC FEET'), - ('GWH', 'GIGAWATT HOUR'), - ('GY', 'GROSS YARD'), - ('GZ', 'GAGE-SYSTEM'), - ('H1', 'HALF PAGE- ELECTRONIC'), - ('H2', 'HALF LITR'), - ('HA', 'HANK'), - ('HAR', 'HECARE'), - ('HBA', 'HECTOBAR'), - ('HBX', 'HUNDRED BOXES'), - ('HC', 'HUNDRED COUNT'), - ('HD', 'HALF DOZEN'), - ('HDW', ' HUNDRED KILOGRAM, DRY WIEIGHT'), - ('HE', 'HUNDREDTH OF A CARAT'), - ('HF', 'HUNDRED FEET'), - ('HGM', 'HECTOGRAM'), - ('HH', 'HUNDRED CUBIC FEET'), - ('HI', 'HUNDRED SHEET'), - ('HIU', 'HUNDRED INTERNATIONAL UNIT'), - ('HJ', 'METRIC HORSE POWER'), - ('HK', 'HUNDRED KILOGRAM, DRY WIEIGHT'), - ('HKM', 'HUNDRED KILOGRAM, NET MASS'), - ('HL', 'HUNDRED FEET (LINEAR)'), - ('HLT', 'HECTOLITRE'), - ('HM', 'MILE PER HOUR'), - ('HMQ', 'MILLION CUBIC METRE'), - ('HMT', 'HECTOMETRE'), - ('HN', 'CONVENTIONAL MILLIMETRE MERCURY'), - ('HO', 'HUNDRED TROY OUNCE'), - ('HP', 'CONVENTIONAL MILLIMETRE OF WATER'), - ('HPA', 'HECTOLITRE OF PURE ALCOHOL'), - ('HS', 'HUNDRED SQUARE FEET'), - ('HT', 'HALF HOUR'), - ('HTZ', 'HERTZ'), - ('HUR', 'HOUR'), - ('HY', 'HUNDRED YARD'), - ('IA', 'INCHE POUND (POUND INCH)'), - ('IC', 'COUNT PER INCHE'), - ('IE', 'PERSON'), - ('IF', 'INCHES OF WATER'), - ('II', 'COLUMN INCH'), - ('IL', 'INCHE PER MINUTE'), - ('IM', 'IMPRESSION INCH'), - ('INH', 'INCH'), - ('INK', 'SQUARE INCH'), - ('INQ', 'CUBIC INCH'), - ('INQ_1', 'INCH CUBED'), - ('IP', 'INSURANCE POLICY'), - ('ISD', 'INTERNATIONAL SUGAR DEGREE'), - ('IT', 'COUNT PER CENTIMETRE'), - ('IU', 'INCHE PER SECOND'), - ('IU_1', 'INCH PER SECOND (VIBRATION)'), - ('IV', 'INCH PER SECOND SQUARED'), - ('J2', 'JOULE PER KILOGRAM'), - ('JB', 'JUMBO'), - ('JE', 'JOULE PER KELVIN'), - ('JG', 'JUG'), - ('JK', 'MEGAJOULE PER KILOGRAM'), - ('JM', 'MEGAJOULE PER CUBIC METRE'), - ('JO', 'JOINT'), - ('JOU', 'JOULE'), - ('JPS', 'HUNDRED METRE'), - ('JR', 'JAR'), - ('JWL', 'NUMBER OF JEWELS'), - ('K1', 'KILOWATT DEMAND'), - ('K2', 'KILOVOLT AMPERE REACTIVE DEMAND'), - ('K3', 'KILOVOLT AMPERE REACTIVE HOUR'), - ('K5', 'KILOVOLT AMPERE (REACTIVE)'), - ('K6', 'KILOLITRE'), - ('KA', 'CAKE'), - ('KB', 'KILOCHARACTER'), - ('KBA', 'KILOBAR'), - ('KCC', 'KILOGRAM OF CHOLINE CHLORIDE'), - ('KD', 'KILOGRAM DECIMAL'), - ('KEL', 'KELVIN'), - ('KF', 'KILOPACKET'), - ('KG', 'KEG'), - ('KGM', 'KILOGRAM'), - ('KGS', 'KILOGRAM PER SECOND'), - ('KHY', 'KILOGRAM OF HYDROGEN PEROXIDE'), - ('KHZ', 'KILOHERTZ'), - ('KI', 'KILOGRAM PER MILLIMETRE WIDHT'), - ('KIC', 'KILOGRAM, INCLUDING CONTAINER'), - ('KIP', 'KILOGRAM, INCLUDING INNER PACKAGING'), - ('KJ', 'KILOSEGMENT'), - ('KJO', 'KILOJOULE'), - ('KL', 'KILOGRAM PER METRE'), - ('KLK', 'LACTIC DRY MATERIAL PERCENTAGE'), - ('KMA', 'KILOGRAM OF METHYLAMINE'), - ('KMH', 'KILOMETRE PER HOUR'), - ('KMK', 'SQUARE KILOMETRE'), - ('KMQ', 'KILOGRAM PER CUBIC METRE'), - ('KMT', 'KILOMETRE PER HOUR'), - ('KNI', 'KILOGRAM OF NITROGEN'), - ('KNS', 'KILOGRAM NAMED SUBSTANCE'), - ('KNT', 'KNOT'), - ('KO', 'MILLIEQUIVALENCE CAUSTIC POTASH PER GRAM OF PRODUCT'), - ('KPA', 'KILOPASCAL'), - ('KPH', 'KILOGRAM OF POTASSIUM HYDROXIDE (CAUSTIC POTASH)'), - ('KPO', 'KILOGRAM OF POTASSIUM OXIDE'), - ('KPP', 'KILOGRAM OF PHOSPHORUS PENTOXIDE (PHOSPHORIC ANHYDRIDE)'), - ('KR', 'KILOROENTGEN'), - ('KS', 'THOUSAND POUND PER SQUARE INCH'), - ('KSD', 'KILOGRAM OF SUBSTANCE 90% DRY'), - ('KSH', 'KILOGRAM OF SODIUM HYDROXIDE (CAUSTIC SODA)'), - ('KT', 'KIT'), - ('KTM', 'KILOMETRE PER HOUR'), - ('KTN', 'KILOTONNE'), - ('KUR', 'KILOGRAM OF URANIUM'), - ('KVA', 'KILOVOLT-AMPERE'), - ('KVR', 'KILOVAR'), - ('KVT', 'KILOVOLT'), - ('KW', 'KILOGRAMS PER MILLIMETRE'), - ('KWH', 'KILOWATT HOUR'), - ('KWO', 'KILOGRAM OF TUNGSTEN TRIOXIDE'), - ('KWT', 'KILOWATT'), - ('KX', 'MILLILITRE PER KILOGRAM'), - ('L2', 'LITRE PER MINUTE'), - ('LA', 'POUND PER CUBIC INCH'), - ('LAC', 'LACTOSE EXCESS PERCENTAGE'), - ('LBR', 'POUND'), - ('LBR_1', 'POUND DECIMAL'), - ('LBT', 'TROY POUND (US)'), - ('LC', 'LINEAR CENTIMETRE'), - ('LD', 'LITRE PER DAY'), - ('LE', 'LITE'), - ('LEF', 'LEAF'), - ('LF', 'LINEAR FOOT'), - ('LH', 'LABOUR HOUR'), - ('LI', 'LINEAR INCH'), - ('LJ', 'LARGE SPRAY'), - ('LK', 'LINK'), - ('LM', 'LINEAR METRE'), - ('LN', 'LENGTH'), - ('LO', 'LOT [UNIT PROCUREMENT]'), - ('LP', 'LIQUID POUND'), - ('LPA', 'LITRE OF PURE ALCOHOL'), - ('LR', 'LAYER'), - ('LS', 'LUMP SUM'), - ('LTN', 'TON (UK) OR LONG TON (US)'), - ('LTR', 'LITRE'), - ('LUB', 'METRIC TON, LUBRICATING OIL'), - ('LUM', 'LUMEN'), - ('LUX', 'LUX'), - ('LX', 'LINEAR YARD PER POUND'), - ('LY', 'LINAR YARD'), - ('M1', 'MILLIGRAM PER LITRE'), - ('M4', 'MONETARY VALUE'), - ('M5', 'MICROCURIE'), - ('M7', 'MICRO-INCH'), - ('M9', 'MILLION BTU PER 1000 CUBIC FEET'), - ('MA', 'MACHINE PER UNIT'), - ('MAH', 'MEGAVOLT AMPERE REACTIVE HOURS'), - ('MAL', 'MEGA LITRE'), - ('MAM', 'MEGAMETRE'), - ('MAR', 'MEVAVOLT AMPERE REACTIVE'), - ('MAW', 'MEGAWATT'), - ('MBE', 'THOUSAND STANDARD BRICK EQUIVALENT'), - ('MBF', 'THOUSAND BOARD FEET'), - ('MBR', 'MILLIBAR'), - ('MC', 'MICROGRAM'), - ('MCU', 'MILLICURIE'), - ('MD', 'AIR DRY METRIC TONE'), - ('MF', 'MILLIGRAM PER SQUARE FOOT PER SIDE'), - ('MGM', 'MILLIGRAM'), - ('MHZ', 'MEGAHERTZ'), - ('MIK', 'SQUARE MILE'), - ('MIL', 'THOUSAND'), - ('MIN', 'MINUTE [UNIT OF TIME]'), - ('MIO', 'MILLION'), - ('MIU', 'MILLION INTERNATIONAL UNIT'), - ('MK', 'MILLIGRAM PER SQUARE INCH'), - ('MLD', 'BILLION (US)'), - ('MLD_1', 'MILLIARD'), - ('MLT', 'MILLILITRE PER KILOGRAM'), - ('MMK', 'SQUARE MILLIMETRE'), - ('MMQ', 'CUBIC MILLIMETRE'), - ('MMT', 'MILLIMETRE'), - ('MND', 'KILOGRA, DRY WEIGHT'), - ('MO', 'MAGNETIC TAPE'), - ('MON', 'MONTH'), - ('MPA', 'MEGAPASCAL'), - ('MQ', 'THOUSAND METRE'), - ('MQH', 'CUNIC METRE PER HOUR'), - ('MQS', 'CUBIC METRE PER SECOND'), - ('MSK', 'METRE PER SECOND SQUARED'), - ('MT', 'MAT'), - ('MTK', 'SQUARE METRE'), - ('MTQ', 'CUBIC METRE'), - ('MTQ_1', 'METRE CUBED'), - ('MTR', 'METRE'), - ('MTS', 'METRE PER SECOND'), - ('MV', 'NUMBER OF MULTS'), - ('MVA', 'MEGAVOLT-AMPERE'), - ('MWH', 'MEGAWATT HOUR (1000KW.H)'), - ('N1', 'PEN CALORIE'), - ('N2', 'NUMBER OF LINES'), - ('N3', 'PRINT POINT'), - ('NA', 'MILLIGRAM PER KILOGRAM'), - ('NAR', 'NUMBER OF ARTICLES'), - ('NB', 'BARGE'), - ('NBB', 'NUMBER OF BOBBINS'), - ('NC', 'CAR'), - ('NCL', 'NUMBER OF CELLS'), - ('ND', 'NET BARREL'), - ('NE', 'NET LITRE'), - ('NEW', 'NEWTON PER SQUARE CENTIMETRE'), - ('NF', 'MESSAGE'), - ('NG', 'NET GALLON (US)'), - ('NH', 'MESSAGE HOUR'), - ('NI', 'NET IMPERIAL GALLON'), - ('NIU', 'NUMBER OF INTERNATIONAL UNITS'), - ('NJ', 'NUMBER OF SCREENS'), - ('NL', 'LOAD'), - ('NMI', 'NAUTICAL MILE'), - ('NMP', 'NUMBER OF PACKS'), - ('NN', 'TRAIN'), - ('NPL', 'NUMBER OF PARCELS'), - ('NPR', 'NUMBER OF PAIRS'), - ('NPT', 'NUMBER OF PARTS'), - ('NQ', 'MHO'), - ('NR', 'MICROMHO'), - ('NRL', 'NUMBER OF ROLLS'), - ('NT', 'METRIC NET TON'), - ('NT_1', 'NET TON'), - ('NTT', 'NET REGISTER TON'), - ('NV', 'VEHICLE'), - ('NX', 'PART PER THOUSAND'), - ('NY', 'POUND PER AIR DRY METRIC TON'), - ('OA', 'PANEL'), - ('ODE', 'OZONE DEPLETION EQUIVALENT'), - ('OHM', 'OHM'), - ('ON', 'OUNCE PER SQUARE YARD'), - ('ONZ', 'OUNCE PER SQUARE YARD'), - ('OP', 'TWO PACK'), - ('OT', 'OVERTIME HOUR'), - ('OZ', 'OUNCE AV'), - ('OZA', 'FLUID OUNCE (US)'), - ('OZI', 'FLUID OUNCE (UK)'), - ('P0', 'PAGE -ELECTRONIC'), - ('P1', 'PERCENT'), - ('P2', 'POUND PER FOOT'), - ('P3', 'THREE PACK'), - ('P4', 'FOUR PACK'), - ('P5', 'FIVE PACK'), - ('P6', 'SIX PACK'), - ('P7', 'SEVEN PACK'), - ('P8', 'EIGHT PACK'), - ('P9', 'NINE PACK'), - ('PA', 'PACKET'), - ('PAL', 'PASCAL'), - ('PB', 'PAIR INCH'), - ('PD', 'PAD'), - ('PE', 'POUND EQUIVALENT'), - ('PF', 'PALLET (LIFT)'), - ('PFL', 'PROOF LITRE'), - ('PG', 'PLATE'), - ('PGL', 'PROOF GALLON'), - ('PI', 'PITCH'), - ('PK', 'PACK'), - ('PK_1', 'PACKAGE'), - ('PLA', 'PAIL'), - ('PLA_1', 'DEGREE PLATO'), - ('PM', 'POUND PERCENTAGE'), - ('PN', 'POUND NET'), - ('PO', 'POUND PER INCH OF LENGTT'), - ('PQ', 'PAGE PER INCH'), - ('PR', 'PAIR INCH'), - ('PS', 'POUND-FORCE PER SQUARE INCH'), - ('PT', 'PINT (US)'), - ('PTD', 'DRY PINT(US)'), - ('PTI', 'PINT (UK)'), - ('PTL', 'LIQUID PINT (US)'), - ('PU', 'TRAY/TRAY PACK'), - ('PV', 'HALF PINT (US)'), - ('PW', 'POUND PER INCH OF WIDTH'), - ('PY', 'PECK DRY (US)'), - ('PZ', 'PECK DRY (UK)'), - ('Q3', 'MEAL'), - ('QA', 'PAGE-FACSIMILE'), - ('QAN', 'QUARTER (OF A YEAR)'), - ('QB', 'PAGE-HARDCOPY'), - ('QD', 'QUARTER DOZEN'), - ('QH', 'QUARTER HOUR'), - ('QK', 'QUARTES KILOGRAM'), - ('QR', 'QUIRE'), - ('QT', 'QUART (US)'), - ('QTD', 'DRY QUART (US)'), - ('QTI', 'QUART (UK)'), - ('QTL', 'LIQUID QUART (US)'), - ('QTR', 'QUARTER (UK)'), - ('R1', 'PICA'), - ('R4', 'CALORIE'), - ('R9', 'THOUSAND CUBIC METRE'), - ('RA', 'RACK'), - ('RD', 'ROD'), - ('RG', 'RING'), - ('RH', 'RUNNING OR OPERATING HOUR'), - ('RK', 'ROLL METRIC MEASURE'), - ('RL', 'REEL'), - ('RM', 'REAM'), - ('RN', 'REAM METRIC MEASURE'), - ('RO', 'ROLL METRIC MEASURE'), - ('RP', 'POUND PER TEAM'), - ('RPM', 'REVOLUTIONS PER MINUTE'), - ('RPS', 'REVOLUTIONS PER SECOND'), - ('RS', 'RESET'), - ('RT', 'REVENUE TON MILE'), - ('RU', 'RUN'), - ('S3', 'FOOT WQUARE PER SECOND'), - ('S3_1', 'SQUARE FOOT PER SECOND'), - ('S4', 'METRE SQUARED PER SECOND (SQUARE METRES/SECONDS US)'), - ('S4_1', 'SQUARE METRE PER SECOND SIXTY FOURTHS OF AN INCH'), - ('S5', 'SIXTY FOURTHS OF AN INCH'), - ('S6', 'SESSION'), - ('S7', 'STORAGE UNIT'), - ('S8', 'STANDARD ADVERTISING UNIT'), - ('SA', 'SACK'), - ('SAN', 'HALF YEAR (6 MONTHS)'), - ('SCO', 'SCORE'), - ('SCR', 'SCRUPLE'), - ('SD', 'SOLID POUND'), - ('SE', 'SECTION'), - ('SEC', 'SECOND [UNIT OF TIME]'), - ('SET', 'SET'), - ('SG', 'SEGMENT'), - ('SHT', 'SHIPPING TON'), - ('SIE', 'SIEMENS'), - ('SK', 'SPLIT TANK TRUCK'), - ('SL', 'SLIPSHEET'), - ('SMI', 'MILE (STATUTE MILE)'), - ('SN', 'SQUARE ROD'), - ('SO', 'SPOOL'), - ('SP', 'SHELF PACKAGE'), - ('SQ', 'SQUARE'), - ('SQR', 'SQUARE, ROOFING'), - ('SR', 'STRIP'), - ('SS', 'SHEET METRIC MEASURE'), - ('SST', 'SHORT STANDARD (7200 MATCHES)'), - ('ST', 'SHEET'), - ('STI', 'STONE (UK)'), - ('STK', 'STICK, CIGARETTE'), - ('STL', 'STANDARD LITRE'), - ('STN', 'NET TON (2000 LB)'), - ('STN_1', 'TON (US) OR SHORT TON (UK/US)'), - ('SV', 'SKID'), - ('SW', 'SKEIN'), - ('SX', 'SHIPMENT'), - ('T0', 'TELECOMMUNICATION LINE SERVICE'), - ('T1', 'THOUSAND POUND GROSS'), - ('T3', 'THOUSAND PIECE'), - ('T4', 'THOUSAND BAG'), - ('T5', 'THOUSAND CASING'), - ('T6', 'THOUSAND GALLON (US)'), - ('T7', 'THOUSAND IMPRESSION'), - ('T8', 'THOUSAND LINEAR INCH'), - ('TA', 'TENTH CUBIC FOOT'), - ('TAH', 'KILOAMPERE HOUR (THOUSAND AMPERE HOUR'), - ('TC', 'TRUCKLOAD'), - ('TD', 'THERM'), - ('TE', 'TOTE'), - ('TF', 'TEN SQUARE YARD'), - ('TI', 'THOUSAND SQUARE INCH'), - ('TIC', 'METRIC TON, INCLUDING CONTAINER'), - ('TIP', 'METRIC TON, INCLUDING INNER PACKAGING'), - ('TJ', 'THOUSAND SQUARE CENTIMETRE'), - ('TK', 'TANK, RECTANGULAR'), - ('TL', 'THOUSAND FEET (LINEAR)'), - ('TMS', 'KILOGRAM OF IMPORTED META, LESS OFFAL'), - ('TN', 'TIN'), - ('TNE', 'METRIC TON'), - ('TNE_1', 'TONNE (METRIC TON)'), - ('TP', 'TEN PACK'), - ('TPR', 'TEN PAIR'), - ('TQ', 'THOUSAND FEET'), - ('TQD', 'THOUSAND CUBIC METRE PER DAY'), - ('TR', 'TEN SQUARE FEET'), - ('TRL', 'TRILLION (EUR)'), - ('TS', 'TONNE OF SUBSTANCE 90% DRY'), - ('TSD', 'THOUSAND SQUARE FEET'), - ('TSH', 'TON OF STEAM PER HOUR'), - ('TT', 'THOUSAND LINEAR METRE'), - ('TU', 'TUBE'), - ('TV', 'THOUSAND KILOGRAM'), - ('TW', 'THOUSAND SHEET'), - ('TY', 'TANK, CYLINDRICAL'), - ('U1', 'TRATMENT'), - ('U2', 'TABLET'), - ('UA', 'TORR'), - ('UB', 'TELECOMMUNICATION LINE IN SERVICE AVERAGE'), - ('UC', 'TELECOMMUNICATION PORT'), - ('UD', 'TENTH MINUTE'), - ('UE', 'TENTH HOUR'), - ('UF', 'USAGE PER TELECOMMUNICATION LINE AVERAGE'), - ('UH', 'TEN THOUSAND YARD'), - ('UM', 'MILLION UN IT'), - ('UN', 'NEWTON METRE'), - ('VA', 'VOLT AMPERE PER KILOGRAM'), - ('VI', 'VIAL'), - ('VLT', 'VOLT'), - ('VQ', 'BULK'), - ('VS', 'VISIT'), - ('W2', 'WET KILO'), - ('W4', 'TWO WEEK'), - ('WA', 'WATT PER KILOGRAM'), - ('WB', 'WET POUND'), - ('WCD', 'CORD'), - ('WE', 'WET TON'), - ('WE_1', 'WRAP'), - ('WEB', 'WEBER'), - ('WEE', 'WEEK'), - ('WG', 'WINE GALLON'), - ('WH', 'WHEEL'), - ('WHR', 'WATT HOUR'), - ('WI', 'WEIGHT PER SQUARE INCH'), - ('WM', 'WORKING MONTH'), - ('WSD', 'STANDARD'), - ('WTT', 'WATT'), - ('WW', 'MILLILITRE OF WATER'), - ('X1', 'HCHAIN'), - ('YDK', 'SQUARED YARD'), - ('YDQ', 'CUBIC YARD'), - ('YL', 'HUNDRED LINEAR YARD'), - ('YRD', 'YARD'), - ('YT', 'TEN YARD'), - ('Z1', 'LIFT VAN'), - ('Z2', 'CHEST'), - ('Z3', 'CASK'), - ('Z4', 'HOGSHEAD'), - ('Z5', 'LUG'), - ('Z6', 'CONFERENCE POINT'), - ('Z8', 'NEWSPAGE AGATE LINE'), - ('ZP', 'PAGE'), - ('ZZ', 'MUATUALLY DEFINED'), -] \ No newline at end of file diff --git a/backend/api/v1/modules/a76/general_catalogs/units_of_measure/service.py b/backend/api/v1/modules/a76/general_catalogs/units_of_measure/service.py deleted file mode 100644 index c514c2ee..00000000 --- a/backend/api/v1/modules/a76/general_catalogs/units_of_measure/service.py +++ /dev/null @@ -1,169 +0,0 @@ -from typing import List, Optional, Tuple, Dict, Any, Type -from sqlalchemy import Sequence -from sqlalchemy.orm import Session -from sqlalchemy.exc import IntegrityError -from fastapi import HTTPException -import logging - -logger = logging.getLogger(__name__) - -from .models import ( - UnitOfMeasureACE, UnitOfMeasureOMA, UnitOfMeasureAmerican, UnitOfMeasureCustoms, - UnitOfMeasure, UnitOfMeasureGeneral -) -from .dto import ( - UnitOfMeasureACECreate, UnitOfMeasureACEUpdate, - UnitOfMeasureOMACreate, UnitOfMeasureOMAUpdate, - UnitOfMeasureAmericanCreate, UnitOfMeasureAmericanUpdate, - UnitOfMeasureCustomsCreate, UnitOfMeasureCustomsUpdate, - UnitOfMeasureCreate, UnitOfMeasureUpdate, - UnitOfMeasureGeneralCreate, UnitOfMeasureGeneralUpdate -) - - -class BaseService: - model = None - - @classmethod - def get_all( - cls, - db: Session, - tenant_id: int, - company_id: int, - skip: int = 0, - limit: int = 100, - filters: Optional[Dict[str, Any]] = None, - ) -> Tuple[List[Any], int]: - query = db.query(cls.model) - - if hasattr(cls.model, "tenant_id"): - query = query.filter(cls.model.tenant_id == tenant_id) - - if hasattr(cls.model, "company_id"): - query = query.filter(cls.model.company_id == company_id) - - if filters: - if filters.get("code"): - query = query.filter( - cls.model.code.ilike(f"%{filters['code']}%")) - if filters.get("description"): - if hasattr(cls.model, "description"): - query = query.filter(cls.model.description.ilike( - f"%{filters['description']}%")) - - total = query.count() - items = query.offset(skip).limit(limit).all() - return items, total - - @classmethod - def get_by_id( - cls, db: Session, id: int, tenant_id: int, company_id: int - ) -> Optional[Any]: - query = db.query(cls.model).filter(cls.model.id == id) - - if hasattr(cls.model, "tenant_id"): - query = query.filter(cls.model.tenant_id == tenant_id) - - if hasattr(cls.model, "company_id"): - query = query.filter(cls.model.company_id == company_id) - - return query.first() - - @classmethod - def create( - cls, - db: Session, - data: Any, - tenant_id: int, - company_id: int, - ) -> Any: - create_kwargs = data.model_dump() - - if hasattr(cls.model, "tenant_id"): - create_kwargs["tenant_id"] = tenant_id - - if hasattr(cls.model, "company_id"): - create_kwargs["company_id"] = company_id - - db_obj = cls.model(**create_kwargs) - db.add(db_obj) - db.commit() - db.refresh(db_obj) - return db_obj - - @classmethod - def update( - cls, - db: Session, - id: int, - tenant_id: int, - data: Any, - company_id: int, - ) -> Optional[Any]: - db_obj = cls.get_by_id(db, id, tenant_id, company_id) - if not db_obj: - return None - - update_dict = data.model_dump(exclude_unset=True) - for key, value in update_dict.items(): - setattr(db_obj, key, value) - - db.commit() - db.refresh(db_obj) - return db_obj - - @classmethod - def delete( - cls, db: Session, id: int, tenant_id: int, company_id: int - ) -> bool: - db_obj = cls.get_by_id(db, id, tenant_id, company_id) - if not db_obj: - return False - - try: - db.delete(db_obj) - db.commit() - return True - except IntegrityError as e: - db.rollback() - logger.error(f"Error de integridad al eliminar {cls.model.__name__} {id}: {str(e)}") - raise HTTPException( - status_code=400, - detail="No se puede eliminar esta unidad de medida porque tiene registros relacionados. Primero debe eliminar o reasignar esos registros." - ) - - -class UnitOfMeasureACEService(BaseService): - model = UnitOfMeasureACE - - -class UnitOfMeasureOMAService(BaseService): - model = UnitOfMeasureOMA - - -class UnitOfMeasureAmericanService(BaseService): - model = UnitOfMeasureAmerican - - -class UnitOfMeasureCustomsService(BaseService): - model = UnitOfMeasureCustoms - - -class UnitOfMeasureService(BaseService): - model = UnitOfMeasure - - -class UnitOfMeasureGeneralService(BaseService): - model = UnitOfMeasure - - -def get_all_uom_general(session: Session, skip: int = 0, limit: int = 100) -> Sequence[UnitOfMeasureGeneral]: - return BaseService._get_all(session, UnitOfMeasureGeneral, skip, limit) - - -def update_uom_general(session: Session, db_obj: UnitOfMeasureGeneral, data: UnitOfMeasureGeneralUpdate) -> UnitOfMeasureGeneral: - return BaseService._update(session, db_obj, data) - - -def delete_uom_general(session: Session, db_obj: UnitOfMeasureGeneral) -> UnitOfMeasureGeneral: - return BaseService._delete(session, db_obj) diff --git a/backend/api/v1/modules/a76/invoice_settings/dto.py b/backend/api/v1/modules/a76/invoice_settings/dto.py deleted file mode 100644 index d330bf29..00000000 --- a/backend/api/v1/modules/a76/invoice_settings/dto.py +++ /dev/null @@ -1,22 +0,0 @@ -from typing import Any, Dict, Optional -from pydantic import BaseModel, ConfigDict -from enum import Enum - -from .models import OperationType - -class InvoiceSettingsBase(BaseModel): - invoice_type: str - operation_type: OperationType - settings: Dict[str, Any] - -class InvoiceSettingsRequest(InvoiceSettingsBase): - """Schema for creating or updating invoice settings""" - pass - -class InvoiceSettingsResponse(InvoiceSettingsBase): - """Schema for returning invoice settings""" - id: int - tenant_id: int - company_id: int - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/a76/invoice_settings/models.py b/backend/api/v1/modules/a76/invoice_settings/models.py deleted file mode 100644 index c9751664..00000000 --- a/backend/api/v1/modules/a76/invoice_settings/models.py +++ /dev/null @@ -1,47 +0,0 @@ -from typing import Optional -from sqlalchemy import BigInteger, ForeignKey, String, UniqueConstraint -from sqlalchemy.dialects.postgresql import JSONB -from sqlalchemy.orm import Mapped, mapped_column -from core.database import Base -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -import enum - -class OperationType(str, enum.Enum): - IMP = "imp" # Importación - EXP = "exp" # Exportación - SM_IN = "sm_in" # Entrada SM - SM_OUT = "sm_out" # Salida SM - CTM_SEND = "ctm_send" # Envío CTM - CTM_RECEIVE = "ctm_receive" # Recibo CTM - -class InvoiceSettings(Base, TenantScopedMixin, TimestampMixin): - __tablename__ = "invoice_settings" - __table_args__ = ( - UniqueConstraint( - "tenant_id", - "company_id", - "invoice_type", - "operation_type", - name="uq_invoice_settings_tenant_company_type_op" - ), - {"schema": "a76"}, - ) - - id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=True) - - # Configuration Scope - invoice_type: Mapped[str] = mapped_column( - ForeignKey("public.invoice_types.key"), - nullable=False - ) - - operation_type: Mapped[OperationType] = mapped_column( - String(11), - nullable=False - ) - - # The actual settings payload - settings: Mapped[dict] = mapped_column(JSONB, nullable=False, default={}) - - def __repr__(self): - return f"" diff --git a/backend/api/v1/modules/a76/invoice_settings/routes.py b/backend/api/v1/modules/a76/invoice_settings/routes.py deleted file mode 100644 index 91379008..00000000 --- a/backend/api/v1/modules/a76/invoice_settings/routes.py +++ /dev/null @@ -1,131 +0,0 @@ -from typing import List, Dict, Any -from fastapi import APIRouter, Depends, HTTPException, Query -from sqlalchemy.orm import Session -from core.database import get_core_db -from core.security import ( - get_current_user, - validate_access_to_resource, - is_hub_admin, - _has_local_super_admin_role, -) -from api.v1.modules.a76.invoice_settings import services -from api.v1.modules.a76.invoice_settings.dto import InvoiceSettingsRequest, InvoiceSettingsResponse, OperationType -from api.v1.modules.core.permissions.service import PermissionService -from api.v1.modules.a76.invoices.permission_map import get_invoice_permission_base - -router = APIRouter( - prefix="/a76/invoice-settings", - tags=["a76/invoice-settings"] -) - - -def _invoice_perm_base_for_settings(operation_type: OperationType, invoice_type: str) -> str: - """Alineado con `permission_map.get_invoice_permission_base`.""" - return get_invoice_permission_base(operation_type, invoice_type) - - -def _can_read_invoice_settings_row( - db: Session, - company_id: int, - current_user: Dict[str, Any], - invoice_type: str, - operation_type: OperationType, -) -> bool: - """ - Ver configuración por tipo/op: settings_general.view O ver facturas de ese mismo contexto - (para cargar defaults en alta/edición sin abrir la pantalla de parámetros). - """ - # Bypass de checks: hub_admin (atestado por el Hub) o super_admin local en - # la compañía. La autorización NO se decide por claims del JWT. - user_id = current_user.get("sub") or current_user.get("id") - if is_hub_admin(current_user) or _has_local_super_admin_role(db, user_id, company_id): - return True - if not user_id: - return False - ps = PermissionService(db) - if ps.has_permission(str(user_id), company_id, "settings_general.view"): - return True - base = _invoice_perm_base_for_settings(operation_type, invoice_type) - return ps.has_permission(str(user_id), company_id, f"{base}.view") - - -@router.get("/{invoice_type}", response_model=InvoiceSettingsResponse) -def get_invoice_settings( - invoice_type: str, - operation_type: OperationType = Query(...), - company_id: int = Query(...), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """Get settings for a specific invoice type and operation""" - tenant_id = validate_access_to_resource(db, company_id, current_user) - if not _can_read_invoice_settings_row( - db, company_id, current_user, invoice_type, operation_type - ): - raise HTTPException( - status_code=403, - detail="Missing required permissions: settings_general.view " - f"(o permiso de vista del tipo de factura solicitado, p. ej. {_invoice_perm_base_for_settings(operation_type, invoice_type)}.view)", - ) - - settings = services.get_settings( - db, - tenant_id, - company_id, - invoice_type, - operation_type - ) - - if not settings: - # Return empty default if not found, to simplify frontend logic - return InvoiceSettingsResponse( - invoice_type=invoice_type, - operation_type=operation_type, - settings={}, - id=0, - tenant_id=tenant_id, - company_id=company_id - ) - return InvoiceSettingsResponse.model_validate(settings) - -@router.get("/", response_model=List[InvoiceSettingsResponse]) -def list_invoice_settings( - company_id: int = Query(...), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """List all configured settings for validation or overview""" - tenant_id = validate_access_to_resource( - db, - company_id, - current_user, - required_permissions=["settings_general.view"], - ) - - return services.list_settings( - db, - tenant_id, - company_id - ) - -@router.put("/", response_model=InvoiceSettingsResponse) -def save_invoice_settings( - settings_data: InvoiceSettingsRequest, - company_id: int = Query(...), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """Create or update invoice settings""" - tenant_id = validate_access_to_resource( - db, - company_id, - current_user, - required_permissions=["settings_general.edit"], - ) - - return services.upsert_settings( - db, - tenant_id, - company_id, - settings_data - ) diff --git a/backend/api/v1/modules/a76/invoice_settings/services.py b/backend/api/v1/modules/a76/invoice_settings/services.py deleted file mode 100644 index 4e477142..00000000 --- a/backend/api/v1/modules/a76/invoice_settings/services.py +++ /dev/null @@ -1,98 +0,0 @@ -from typing import List, Optional, Any, Dict -from sqlalchemy.orm import Session -from api.v1.modules.a76.app_settings.service import AppSettingsService -from api.v1.modules.a76.invoice_settings.dto import InvoiceSettingsRequest, OperationType - -def get_settings( - db: Session, - tenant_id: int, - company_id: int, - invoice_type: str, - operation_type: OperationType -) -> Optional[Dict[str, Any]]: - """Retrieve settings for a specific context from app_settings""" - # Use AppSettingsService to get the unifed settings - app_settings = AppSettingsService.get_resolved_settings(db, tenant_id, company_id) - if not app_settings: - return None - - # Navigate to: invoices -> types -> {operation_type} -> {invoice_type} - invoices = app_settings.get("invoices", {}) - types_map = invoices.get("types", {}) - op_map = types_map.get(operation_type.value, {}) - settings_payload = op_map.get(invoice_type) - - if settings_payload is None: - return None - - return { - "id": 0, # Virtual ID for compatibility - "tenant_id": tenant_id, - "company_id": company_id, - "invoice_type": invoice_type, - "operation_type": operation_type, - "settings": settings_payload - } - -def list_settings( - db: Session, - tenant_id: int, - company_id: int -) -> List[Dict[str, Any]]: - """List all settings for a company from app_settings""" - app_settings = AppSettingsService.get_resolved_settings(db, tenant_id, company_id) - if not app_settings: - return [] - - invoices = app_settings.get("invoices", {}) - types_map = invoices.get("types", {}) - - results = [] - for op_val, op_map in types_map.items(): - for inv_type, settings_payload in op_map.items(): - results.append({ - "id": 0, - "tenant_id": tenant_id, - "company_id": company_id, - "invoice_type": inv_type, - "operation_type": op_val, - "settings": settings_payload - }) - return results - -def upsert_settings( - db: Session, - tenant_id: int, - company_id: int, - settings_data: InvoiceSettingsRequest -) -> Dict[str, Any]: - """Create or update settings in app_settings""" - # Construct the nested structure for AppSettingsService.upsert_settings - # We use deep_merge in AppSettingsService, so we just send the branch we want to update - payload = { - "invoices": { - "types": { - settings_data.operation_type.value: { - settings_data.invoice_type: settings_data.settings - } - } - } - } - - # Save using the unified service - AppSettingsService.upsert_settings( - db, - tenant_id=tenant_id, - company_id=company_id, - settings=payload - ) - - # Return the same structure as get_settings for consistency - return { - "id": 0, - "tenant_id": tenant_id, - "company_id": company_id, - "invoice_type": settings_data.invoice_type, - "operation_type": settings_data.operation_type, - "settings": settings_data.settings - } diff --git a/backend/api/v1/modules/a76/invoices/catalog_service.py b/backend/api/v1/modules/a76/invoices/catalog_service.py deleted file mode 100644 index 8249dc84..00000000 --- a/backend/api/v1/modules/a76/invoices/catalog_service.py +++ /dev/null @@ -1,225 +0,0 @@ - -from typing import Dict, Any, List, Optional -from sqlalchemy.orm import Session - -# Import Reference Data Models -from api.v1.modules.public.reference_data.invoice_types.models import InvoiceType -from api.v1.modules.public.reference_data.currency_types.models import CurrencyType -from api.v1.modules.public.reference_data.transport_types.models import TransportType -from api.v1.modules.public.reference_data.customs_sections.models import CustomsSection -from api.v1.modules.public.reference_data.pedimento_codes.models import PedimentoCode -from api.v1.modules.public.reference_data.code_pedimento_regimens.models import CodePedimentoRegimen -from api.v1.modules.public.reference_data.incoterms.models import Incoterm -from api.v1.modules.public.reference_data.valuation_methods.models import ValuationMethod -from api.v1.modules.public.reference_data.transport_modes.models import TransportMode - -# Import A76 Services -from api.v1.modules.a76.customs_brokers.services import CustomsBrokerService -from api.v1.modules.a76.clients_and_providers.service import ClientProviderService -from api.v1.modules.a76.transportation.transporters.services import TransporterService -from api.v1.modules.a76.transportation.vehicles.services import VehicleService -from api.v1.modules.a76.transportation.drivers.services import DriverService -from api.v1.modules.a76.transportation.trailers.services import TrailerService -from api.v1.modules.a76.general_catalogs.seal.services import SealService -from api.v1.modules.a76.pedmientos.services.pedimentos import PedimentosService - -# Import DTOs for mapping -from api.v1.modules.public.reference_data.invoice_types.dto import InvoiceTypeDTO -from api.v1.modules.a76.customs_brokers.dto import CustomsBrokerResponseDTO -from api.v1.modules.a76.clients_and_providers.dto import ClientProviderResponseDTO -from api.v1.modules.public.reference_data.currency_types.dto import CurrencyTypeDTO -from api.v1.modules.public.reference_data.transport_types.dto import TransportTypeDTO -from api.v1.modules.public.reference_data.customs_sections.dto import CustomsSectionDTO -from api.v1.modules.public.reference_data.code_pedimento_regimens.dto import CodePedimentoRegimenDTO -from api.v1.modules.public.reference_data.incoterms.dto import IncotermDTO -from api.v1.modules.public.reference_data.valuation_methods.dto import ValuationMethodDTO -from api.v1.modules.public.reference_data.transport_modes.dto import TransportModeDTO - -# Additional DTOs -from api.v1.modules.a76.transportation.transporters.dto import TransporterResponseDTO -from api.v1.modules.a76.transportation.vehicles.dto import VehicleResponseDTO -from api.v1.modules.a76.transportation.drivers.dto import DriverResponseDTO -from api.v1.modules.a76.transportation.trailers.dto import TrailerResponseDTO -from api.v1.modules.a76.general_catalogs.seal.dto import SealResponseDTO -from api.v1.modules.a76.pedmientos.dtos.pedimentos import PedimentosResponse - -from .schemas import InvoiceCatalogsResponse, InvoiceCreationResponse, InvoiceEditionResponse - -class InvoiceCatalogService: - """Service to fetch consolidated catalogs for Invoice views""" - - @staticmethod - def get_catalogs(db: Session, tenant_id: int, company_id: int) -> InvoiceCatalogsResponse: - """Fetch all catalogs""" - - response = InvoiceCatalogsResponse() - - # Helper to fetch reference data (no company_id needed) - def fetch_ref_data(): - response.invoice_types = [ - InvoiceTypeDTO.model_validate(obj) for obj in db.query(InvoiceType).all() - ] - response.currency_types = [ - CurrencyTypeDTO.model_validate(obj) for obj in db.query(CurrencyType).all() - ] - response.transport_types = [ - TransportTypeDTO.model_validate(obj) for obj in db.query(TransportType).all() - ] - response.customs_sections = [ - CustomsSectionDTO.model_validate(obj) for obj in db.query(CustomsSection).all() - ] - response.code_pedimento_regimens = [ - CodePedimentoRegimenDTO.model_validate(obj) for obj in db.query(CodePedimentoRegimen).all() - ] - response.incoterms = [ - IncotermDTO.model_validate(obj) for obj in db.query(Incoterm).all() - ] - response.valuation_methods = [ - ValuationMethodDTO.model_validate(obj) for obj in db.query(ValuationMethod).all() - ] - response.transport_modes = [ - TransportModeDTO.model_validate(obj) for obj in db.query(TransportMode).all() - ] - - # Helper to fetch tenant/company specific data - def fetch_tenant_data(): - # Customs Brokers - try: - brokers, _ = CustomsBrokerService.get_all(db, tenant_id, company_id, limit=1000) - response.customs_brokers = [ - CustomsBrokerResponseDTO.model_validate(obj) for obj in brokers - ] - except Exception as e: - print(f"Error fetching customs brokers: {e}") - - # Clients and Providers - try: - # Fetch all clients/providers - # Note: get_all returns list[ClientProvider] - all_cps, _ = ClientProviderService.get_all( - db, tenant_id, company_id, limit=2000 - ) - - # Helper to safely check client type (handles string or Enum) - def is_type(obj, types): - val = obj.client_or_provider - # If it's an enum, get its value, otherwise use as string - val_str = val.value if hasattr(val, 'value') else str(val) - return val_str in types - - response.clients = [ - ClientProviderResponseDTO.model_validate(obj) for obj in all_cps - if is_type(obj, ['client', 'both']) - ] - response.providers = [ - ClientProviderResponseDTO.model_validate(obj) for obj in all_cps - if is_type(obj, ['provider', 'both']) - ] - except Exception as e: - print(f"Error fetching clients/providers: {e}") - - - - # Transporters - try: - transporters, _ = TransporterService.get_all(db, tenant_id, company_id, limit=1000) - response.transporters = [ - TransporterResponseDTO.model_validate(t) for t in transporters - ] - except Exception as e: - print(f"Error fetching transporters: {e}") - - # Vehicles - try: - vehicles, _ = VehicleService.get_all(db, tenant_id, company_id, limit=1000) - response.vehicles = [ - VehicleResponseDTO.model_validate(v) for v in vehicles - ] - except Exception as e: - print(f"Error fetching vehicles: {e}") - - # Drivers - try: - drivers = DriverService.list_drivers(db, str(company_id), str(tenant_id)) - response.drivers = [ - DriverResponseDTO.model_validate(d) for d in drivers - ] - except Exception as e: - print(f"Error fetching drivers: {e}") - # Initialize drivers as empty list if an error occurs - drivers = [] - - # Trailers - try: - trailers, _ = TrailerService.get_all(db, tenant_id, company_id, limit=1000) - response.trailers = [ - TrailerResponseDTO.model_validate(t) for t in trailers - ] - except Exception as e: - print(f"Error fetching trailers: {e}") - - # Seals - try: - seals, _ = SealService.get_all(db, tenant_id, company_id, limit=1000) - response.seals = [ - SealResponseDTO.model_validate(s) for s in seals - ] - except Exception as e: - print(f"Error fetching seals: {e}") - - # Pedimentos - try: - # Fetch recent pedimentos (e.g. last 100) or filtered if necessary - pedimentos, _ = PedimentosService.get_all(db, tenant_id, company_id, limit=100) - response.pedimentos = [ - # Use dict for now if PedimentosResponse fails due to complexity or just map fields manually if needed - # But PedimentosResponse has from_attributes=True - # Note: PedimentosResponse structure is complex with nested relations. - # If Pedimentos model is fully loaded (eager load in service), this should work. - # However, to be safe against recursion or huge payload, we might want a lighter DTO. - # Re-using PedimentosResponse for now but be cautious of payload size. - PedimentosResponse.model_validate(p) for p in pedimentos - ] - except Exception as e: - print(f"Error fetching pedimentos: {e}") - - try: - fetch_ref_data() - fetch_tenant_data() - except Exception as e: - print(f"Error fetching catalogs: {e}") - # In production, we might want to log this properly and potentially return partial data - # For now, re-raising might be safer to debug, but for resiliency we could suppress. - # Let's log and re-raise to ensure frontend knows something went wrong during dev. - import traceback - traceback.print_exc() - raise e - - return response - - @staticmethod - def get_creation_data(db: Session, tenant_id: int, company_id: int) -> InvoiceCreationResponse: - catalogs = InvoiceCatalogService.get_catalogs(db, tenant_id, company_id) - return InvoiceCreationResponse( - **catalogs.model_dump(), - is_create=True, - filters={} - ) - - @staticmethod - def get_edition_data(db: Session, invoice_id: int, tenant_id: int, company_id: int) -> Optional[InvoiceEditionResponse]: - catalogs = InvoiceCatalogService.get_catalogs(db, tenant_id, company_id) - - from .services import InvoiceService - invoice = InvoiceService.get_by_id(db, invoice_id, tenant_id, company_id) - - if not invoice: - return None - - return InvoiceEditionResponse( - **catalogs.model_dump(), - is_create=False, - invoice=invoice, - invoice_id=invoice_id, - filters={} - ) diff --git a/backend/api/v1/modules/a76/invoices/common/calculations.py b/backend/api/v1/modules/a76/invoices/common/calculations.py deleted file mode 100644 index 0bbe1c5f..00000000 --- a/backend/api/v1/modules/a76/invoices/common/calculations.py +++ /dev/null @@ -1,50 +0,0 @@ -from sqlalchemy.orm import Session -from api.v1.modules.a76.invoices.models import InvoiceFinancials, InvoiceHeader, InvoiceLogistics -from core.exceptions import ErrorCollector - -from .. import schemas - - - -def apply_calculations( - invoice: schemas.InvoiceHeaderUpdate, -): - if invoice.compliance_mx: - if invoice.invoice_type == "CR": - invoice.compliance_mx.is_regime_change = True - else: - invoice.compliance_mx.is_regime_change = False - - if invoice.financials: - # Arithmetica defensiva: (val or 0) - freight = invoice.financials.freight or 0 - insurance = invoice.financials.insurance or 0 - packaging = invoice.financials.packaging or 0 - other = invoice.financials.other_increments or 0 - increments_me = freight + insurance + packaging + other - - # Tipo de cambio seguro - tc = invoice.financials.exchange_rate or 1 # Fallback a 1 para evitar division por cero - - if invoice.financials.currency == "foreign": - invoice.financials.total_increments_me = increments_me - invoice.financials.total_increments_mn = increments_me * tc - invoice.financials.currency_type = "USD" - elif invoice.financials.currency == "local": - invoice.financials.total_increments_mn = increments_me - invoice.financials.total_increments_me = increments_me / tc if tc != 0 else 0 - invoice.financials.currency_type = "MXN" - elif invoice.financials.currency == "manual": - # TC_MM para moneda manual - tc_mm = invoice.financials.exchange_rate_mm or 1 - invoice.financials.total_increments_me = increments_me / tc_mm if tc_mm != 0 else 0 - invoice.financials.total_increments_mn = invoice.financials.total_increments_me * tc - - if invoice.compliance_mx: - invoice.compliance_mx.is_pedimento_pending = False - if not invoice.compliance_mx.pedimento_id: - invoice.compliance_mx.is_pedimento_pending = True - - - - \ No newline at end of file diff --git a/backend/api/v1/modules/a76/invoices/common/common_validators.py b/backend/api/v1/modules/a76/invoices/common/common_validators.py deleted file mode 100644 index fd1559f0..00000000 --- a/backend/api/v1/modules/a76/invoices/common/common_validators.py +++ /dev/null @@ -1,995 +0,0 @@ -import logging -from typing import Any, Dict, Optional, Union -from core.exceptions import ErrorCollector -from sqlalchemy import func -from sqlalchemy.orm import Session -from .. import models -from .. import schemas -from ..models import InvoiceComplianceMx -from ..models import TransportType, Currency, WeightUnit -from api.v1.modules.a76.invoices.common.calculations import apply_calculations -from api.v1.modules.a76.items.models import LineItem -from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos -from api.v1.modules.a76.general_catalogs.exchange_rate.models import ExchangeRate -from api.v1.modules.a76.clients_and_providers.models import ClientProvider -from api.v1.modules.a76.customs_brokers.models import CustomsBroker -from api.v1.modules.a76.transportation.transporters.models import Transporter -from api.v1.modules.a76.transportation.vehicles.models import Vehicle -from api.v1.modules.a76.transportation.trailers.models import Trailer -from api.v1.modules.a76.transportation.drivers.models import Driver -from api.v1.modules.a76.manifests.manifest.models import Manifest -from api.v1.modules.public.reference_data.incoterms.models import Incoterm -from api.v1.modules.public.reference_data.currency_types.models import CurrencyType -from api.v1.modules.public.reference_data.customs_sections.models import CustomsSection -from api.v1.modules.a76.app_settings.service import AppSettingsService -logger = logging.getLogger(__name__) - - -def validate_invoice_items_decimals( - db: Session, - lines: list, - tenant_id: int, - company_id: int, - errors: ErrorCollector, -) -> None: - """ - Validates that items with unit of measure 'PZA' do not have decimal quantities - if the system parameter 'validadecencant' is active. - """ - settings = AppSettingsService.get_resolved_settings(db, tenant_id, company_id) - - # Check in ssisgen or qsisgen - gen_params = settings.get("ssisgen", {}) - if not gen_params: - gen_params = settings.get("qsisgen", {}) - - # Parameter for decimal validation (typically 'validadecencant') - param_val = str(gen_params.get("validadecencant", "0")).strip().upper() - skip_decimals = param_val in ["1", "TRUE", "SI", "SÍ"] - - if not skip_decimals: - return - - violations = [] - for line in lines: - if not line.unit_of_measure_info or not line.quantity: - continue - - uom_code = str(line.unit_of_measure_info.code).upper().strip() - # Common codes for pieces - if uom_code in ["PZA", "PIEZA", "PIEZAS", "PZAS", "PCE", "1"]: - qty = line.quantity.quantity - if qty is not None and float(qty) % 1 != 0: - line_info = f"Partida #{line.line_number}" - if line.part_info: - line_info += f" ({line.part_info.part_number})" - - violations.append(line_info) - logger.warning(f"BLINDAJE: {line_info} tiene decimales ({qty}) en unidad {uom_code}. Bloqueando proceso.") - - errors.add_error( - field=f"items.{line.line_number}.quantity", - message=f"{line_info}: No se permiten decimales en unidades de tipo '{uom_code}' según el parámetro del sistema (validadecencant).", - solution=["Ajuste la cantidad a un número entero o cambie la unidad de medida."], - code="DECIMALS_NOT_ALLOWED", - value=str(qty) - ) - - -def _logistics_str_nonempty(value) -> bool: - return value is not None and str(value).strip() != "" - - -def _normalize_invoice_currency_value(value) -> str: - """Lowercase currency code (foreign/local/manual) for comparisons.""" - if value is None or value == "": - return "" - raw = getattr(value, "value", value) - return str(raw).lower() - - -def invoice_exists( - db: Session, - invoice_number: str, - tenant_id: int, - company_id: int, - errors: Optional[ErrorCollector], -): - invoice = ( - db.query(models.InvoiceHeader) - .filter( - models.InvoiceHeader.invoice_number == invoice_number, - models.InvoiceHeader.tenant_id == tenant_id, - models.InvoiceHeader.company_id == company_id, - ) - .first() - ) - - if invoice: - if errors: - errors.add_duplicate_error( - "invoice_number", - invoice_number, - f"Ya existe una factura con el número '{invoice_number}'", - ) - return invoice - return None - -def invoice_exists_by_id( - db: Session, - invoice_id: str, - tenant_id: int, - company_id: int, - errors: Optional[ErrorCollector], -): - """ - Check if an invoice exists by ID. - WARNING: Adds a DUPLICATE error if found (to be used when creating NEW invoices with specific IDs). - """ - invoice = ( - db.query(models.InvoiceHeader) - .filter( - models.InvoiceHeader.id == invoice_id, - models.InvoiceHeader.tenant_id == tenant_id, - models.InvoiceHeader.company_id == company_id, - ) - .first() - ) - - if invoice: - if errors: - errors.add_duplicate_error( - "invoice_id", - invoice_id, - f"Ya existe una factura con el número '{invoice_id}'", - ) - return invoice - return None - -def invoice_id_required( - db: Session, - invoice_id: int, - tenant_id: int, - company_id: int, - errors: ErrorCollector, -) -> Optional[models.InvoiceHeader]: - """ - Validates that an invoice exists by ID. - Adds a NOT_FOUND error if it doesn't exist. - """ - if not invoice_id: - errors.add_required_error(field="invoice_id") - return None - - invoice = ( - db.query(models.InvoiceHeader) - .filter( - models.InvoiceHeader.id == invoice_id, - models.InvoiceHeader.tenant_id == tenant_id, - models.InvoiceHeader.company_id == company_id, - ) - .first() - ) - - if not invoice: - errors.add_error( - field="invoice_id", - message=f"La factura con ID '{invoice_id}' no existe.", - solution=["Seleccionar una factura válida."], - code="NOT_FOUND", - value=str(invoice_id), - ) - return invoice - -def invoice_processed( - db: Session, - invoice_id: str, - tenant_id: int, - company_id: int, - errors: ErrorCollector, -) -> bool: - status = ( - db.query(models.InvoiceHeader.status) - .filter( - models.InvoiceHeader.id == invoice_id, - models.InvoiceHeader.tenant_id == tenant_id, - models.InvoiceHeader.company_id == company_id, - ) - .scalar() - ) - - if status == models.InvoiceStatus.PROCESSED: - errors.add_error( - field="invoice_number", - message=f"La factura con el número '{invoice_id}' ya ha sido actualizada y no se puede modificar.", - solution="Capturar otro número de Factura de Importación Temporal o Desactualizar la factura.", - code="INVOICE_processed", - value=invoice_id, - ) - return True - return False - -def validate_required_fields_by_operation( - invoice_data: Dict[str, Any], - operation_type: str, - errors: ErrorCollector -) -> None: - """ - Valida campos obligatorios según tipo de operación. - Usar ANTES de guardar en BD. - """ - - # PROVEEDOR (SIEMPRE OBLIGATORIO - mensaje dinámico) - if not invoice_data.get('provider_id'): - # Mensaje dinámico según el header seleccionado - provider_labels = { - 'proveedor': 'Proveedor', - 'exportador': 'Exportador' - } - provider_header = invoice_data.get('provider_header') or 'proveedor' - field_label = provider_labels.get(provider_header, 'Proveedor') - - errors.add_error( - field="provider_id", - message=f"Debe seleccionar {field_label}", - solution=["Seleccione un proveedor de la lista desplegable"], - code="REQUIRED", - value=None - ) - - # VENDIDO A / CONSIGNADO A (SIEMPRE OBLIGATORIO - mensaje dinámico) - if not invoice_data.get('sold_to_id'): - # Mensaje dinámico según el header seleccionado - sold_to_labels = { - 'consignado_a': 'Consignado a', - 'vendido_a': 'Vendido a', - 'exportado_a': 'Exportado a', - 'importador': 'Importador' - } - sold_to_header = invoice_data.get('sold_to_header') or 'consignado_a' - field_label = sold_to_labels.get(sold_to_header, 'Cliente') - - errors.add_error( - field="sold_to_id", - message=f"Debe seleccionar {field_label}", - solution=["Seleccione una opción de la lista desplegable"], - code="REQUIRED", - value=None - ) - - # ENVIADO A (SIEMPRE OBLIGATORIO - mensaje fijo) - if not invoice_data.get('shipped_to_id'): - errors.add_error( - field="shipped_to_id", - message="Debe seleccionar el Destinatario", - solution=["Seleccione un destinatario de la lista desplegable"], - code="REQUIRED", - value=None - ) - - # AGENTE ADUANAL (OBLIGATORIO si hay pedimento) - if invoice_data.get('pedimento_id') and not invoice_data.get('customs_broker_id'): - errors.add_error( - field="customs_broker_id", - message="Debe seleccionar un Agente Aduanal", - solution=["Seleccione un agente aduanal de la lista desplegable"], - code="REQUIRED", - value=None - ) - - -def validate_common( - db: Session, - invoice: Union[schemas.InvoiceHeaderCreate, schemas.InvoiceHeaderUpdate], - tenant_id: int, - company_id: int, - errors: ErrorCollector, - existing_invoice: Optional[models.InvoiceHeader] = None, -): - if invoice.compliance_mx.pedimento_id: - pedimento = ( - db.query(Pedimentos) - .filter( - Pedimentos.id == invoice.compliance_mx.pedimento_id, - Pedimentos.tenant_id == tenant_id, - Pedimentos.company_id == company_id, - ) - .first() - ) - if not pedimento: - errors.add_error( - field="compliance_mx.pedimento_id", - message="El Pedimento no existe en el Catálogo de Pedimentos.", - solution=["Verifica el ID", "Revisa el catálogo"], - code="NOT_FOUND", - value=invoice.compliance_mx.pedimento_id, - ) - return # Stop here if pedimento not found - - if not invoice.compliance_mx.is_regime_change: - operacion = "Importación" if pedimento.operation_type == "imp" else "Exportación" - # Validar que el pedimento sea de importación - if pedimento.operation_type != invoice.operation_type: - errors.add_error( - field="compliance_mx.pedimento_id", - message=f"El Pedimento seleccionado no corresponde a una {operacion}.", - solution=[f"Selecciona un Pedimento de {operacion}"], - code="INVALID_OPERATION_TYPE", - value=pedimento.operation_type, - ) - else: - # Validar regímenes incompatibles - only_regimes = ["EXD", "ETE", "ETR"] - is_valid = True - if invoice.operation_type == "imp" and pedimento.regime in only_regimes: - is_valid = False - oposite_operacion = "Exportación" - elif invoice.operation_type == "exp" and pedimento.regime not in only_regimes: - is_valid = False - oposite_operacion = "Importación" - - if not is_valid: - errors.add_error( - field="compliance_mx.pedimento_id", - message=f"El Pedimento seleccionado no corresponde a una {oposite_operacion}, no a una {operacion}.", - solution=[f"Selecciona un Pedimento de {operacion}"], - code="INVALID_REGIME", - value=pedimento.regime, - ) - - # Validar que el tipo de documento coincida con el régimen del pedimento - # NOTA: Solo validamos si no hay errores previos y si document_type está presente - if not errors.has_errors() and invoice.document_type: - if invoice.document_type.upper().strip() != pedimento.regime: - errors.add_error( - field="document_type", - message=f"El Tipo de Documento '{invoice.document_type}' no coincide con el Régimen '{pedimento.regime}' del Pedimento seleccionado.", - solution=[ - "Ajusta el Tipo de Documento o selecciona otro Pedimento" - ], - code="REGIME_MISMATCH", - value=invoice.document_type, - ) - else: - # Cambio de Régimen - Generalmente es de Importación Temporal a Definitiva (IMD) - # En el código legacy se comparaba pedimento.operation_type != 2. - # Si asumimos que 2 era Importación en el sistema anterior: - if pedimento.operation_type != "imp": - errors.add_error( - field="compliance_mx.pedimento_id", - message="El Pedimento seleccionado no corresponde a una Importación (requerido para Cambio de Régimen).", - solution=["Selecciona un Pedimento de Importación"], - code="INVALID_OPERATION_TYPE", - value=pedimento.operation_type, - ) - else: - if pedimento.regime != "IMD" and invoice.document_type == "IMD": - # Si el destino es IMD, validamos que el pedimento original sea de importación - # (aunque usualmente el pedimento que se asocia aquí es el nuevo, el de IMD) - pass - - if invoice.document_type.upper().strip() != pedimento.regime: - errors.add_error( - field="document_type", - message=f"El Tipo de Documento '{invoice.document_type}' no coincide con el Régimen '{pedimento.regime}' del Pedimento seleccionado.", - solution=[ - "Ajusta el Tipo de Documento o selecciona otro Pedimento" - ], - code="REGIME_MISMATCH", - value=invoice.document_type, - ) - - if pedimento.pedimento_code not in ["A1", "A3", "F5"]: - errors.add_error( - field="compliance_mx.pedimento_id", - message=f"El Pedimento seleccionado no es de tipo A1, A3 o F5 requerido para Cambio de Régimen.", - solution=["Selecciona un Pedimento de tipo A1, A3 o F5"], - code="INVALID_PEDEMENTO_CODE", - value=pedimento.pedimento_code, - ) - - if pedimento.pedimento_type == "consolidated": - pd_dates = pedimento.pedimento_dates - if not pd_dates: - errors.add_error( - field="compliance_mx.pedimento_id", - message="El Pedimento seleccionado no tiene fechas registradas.", - solution=["Verifica las fechas del Pedimento en el catálogo"], - code="MISSING_PEDIMENTO_DATES", - value=invoice.compliance_mx.pedimento_id, - ) - else: - # Convertir invoice_date a date si es datetime para poder comparar - invoice_date = ( - invoice.invoice_date.date() - if hasattr(invoice.invoice_date, "date") - else invoice.invoice_date - ) - # Periodo consolidado: si existe start_date en catálogo, es el inicio del rango; - # si no, se usa entry_date (paridad con validación CSV). - period_start_src = getattr(pd_dates, "start_date", None) or pd_dates.entry_date - period_start_date = ( - period_start_src.date() - if hasattr(period_start_src, "date") - else period_start_src - ) - end_date = ( - pd_dates.end_date.date() - if hasattr(pd_dates.end_date, "date") - else pd_dates.end_date - ) - - if invoice_date < period_start_date or invoice_date > end_date: - errors.add_error( - field="invoice_date", - message=f"La Fecha de la Factura {invoice.invoice_date} no está dentro del rango de fechas del Pedimento {pedimento.customs_office}-{pedimento.license}-{pedimento.pedimento_number}.", - solution=[ - f"Capturar una Fecha de Factura, entre la Fecha de Inicio: {period_start_src} y la Fecha Final: {pd_dates.end_date} ." - ], - code="DATE_OUT_OF_RANGE", - value=invoice.invoice_date, - ) - - # Remesa check - if pedimento.pedimento_type == "consolidated": - if not invoice.compliance_mx.remesa: - errors.add_error( - field="compliance_mx.remesa", - message="El campo Remesa es obligatorio cuando se asocia un Pedimento consolidado.", - solution=["Proporciona un valor para Remesa"], - code="REQUIRED_FIELD", - value=invoice.compliance_mx.remesa, - ) - elif invoice.compliance_mx.remesa == 0: - errors.add_error( - field="compliance_mx.remesa", - message="El campo Remesa no puede ser cero cuando se asocia un Pedimento.", - solution=["Proporciona un valor válido para Remesa"], - code="INVALID_VALUE", - value=invoice.compliance_mx.remesa, - ) - else: - # Misma remesa puede repetirse en distintos pedimentos; solo debe ser única - # por (pedimento_id, empresa, tenant). Creación: existing_invoice es None. - current_invoice_id = None - if existing_invoice is not None: - current_invoice_id = existing_invoice.id - else: - current_invoice_id = getattr(invoice, "id", None) - - duplicated_remesa = ( - db.query(InvoiceComplianceMx) - .filter( - InvoiceComplianceMx.remesa == invoice.compliance_mx.remesa, - InvoiceComplianceMx.pedimento_id - == invoice.compliance_mx.pedimento_id, - InvoiceComplianceMx.tenant_id == tenant_id, - InvoiceComplianceMx.company_id == company_id, - ) - .first() - ) - if duplicated_remesa and ( - current_invoice_id is None - or duplicated_remesa.invoice_id != current_invoice_id - ): - errors.add_error( - field="compliance_mx.remesa", - message=( - "El valor de Remesa ya está registrado para este mismo Pedimento " - "en otra factura." - ), - solution=["Usa otra remesa o revisa la factura que ya la tiene capturada"], - code="DUPLICATE_VALUE", - value=invoice.compliance_mx.remesa, - ) - - # Financials checks (if provided) - if invoice.financials: - if not invoice.financials.exchange_rate or invoice.financials.exchange_rate <= 0: - exchange_rate_exists = ( - db.query(ExchangeRate) - .filter( - func.date(ExchangeRate.date) == invoice.invoice_date, - ExchangeRate.tenant_id == tenant_id, - ExchangeRate.company_id == company_id, - ) - .first() - ) - if not exchange_rate_exists: - errors.add_error( - field="financials.exchange_rate", - message=f"No existe un Tipo de Cambio registrado para la fecha {invoice.invoice_date}.", - solution=["Registra el Tipo de Cambio en el catálogo correspondiente"], - code="EXCHANGE_RATE_NOT_FOUND", - value=invoice.financials.exchange_rate, - ) - else: - invoice.financials.exchange_rate = exchange_rate_exists.value - else: - # If financials missing, we might want to error if it's required for this operation - pass - - if invoice.compliance_mx.is_regime_change: - if invoice.document_type in ["EXD", "ETE", "ETR"]: - errors.add_error( - field="document_type", - message="El Tipo de Documento no puede ser de Exportación cuando se trata de un Cambio de Régimen.", - solution=[ - "Selecciona un Tipo de Documento válido para Cambio de Régimen" - ], - code="INVALID_DOCUMENT_TYPE", - value=invoice.document_type, - ) - else: - if invoice.document_type == "IMD" and invoice.invoice_type.upper() != "DEF": - errors.add_error( - field="document_type", - message="El Tipo de Documento no puede ser 'IMD' a menos que sea un Cambio de Régimen.", - solution=["Selecciona un Tipo de Documento válido"], - code="INVALID_DOCUMENT_TYPE", - value=invoice.document_type, - ) - - # Validar proveedor solo si se proporciona - if invoice.compliance_mx.provider_id: - provider_exists = ( - db.query(ClientProvider) - .filter( - ClientProvider.id == invoice.compliance_mx.provider_id, - ClientProvider.tenant_id == tenant_id, - ClientProvider.company_id == company_id, - ) - .first() - ) - if not provider_exists: - errors.add_error( - field="compliance_mx.provider_id", - message="El Proveedor no existe en el Catálogo de Clientes y Proveedores.", - solution=["Verifica el ID del Proveedor", "Revisa el catálogo"], - code="NOT_FOUND", - value=invoice.compliance_mx.provider_id, - ) - - # Validar vendido a solo si se proporciona - if invoice.compliance_mx.sold_to_id: - selled_to_exists = ( - db.query(ClientProvider) - .filter( - ClientProvider.id == invoice.compliance_mx.sold_to_id, - ClientProvider.tenant_id == tenant_id, - ClientProvider.company_id == company_id, - ) - .first() - ) - if not selled_to_exists: - errors.add_error( - field="compliance_mx.sold_to_id", - message="El Cliente no existe en el Catálogo de Clientes y Proveedores.", - solution=["Verifica el ID del Cliente", "Revisa el catálogo"], - code="NOT_FOUND", - value=invoice.compliance_mx.sold_to_id, - ) - - # Validar destinatario solo si se proporciona - if invoice.compliance_mx.shipped_to_id: - shipped_to_exists = ( - db.query(ClientProvider) - .filter( - ClientProvider.id == invoice.compliance_mx.shipped_to_id, - ClientProvider.tenant_id == tenant_id, - ClientProvider.company_id == company_id, - ) - .first() - ) - if not shipped_to_exists: - errors.add_error( - field="compliance_mx.shipped_to_id", - message="El Destinatario no existe en el Catálogo de Clientes y Proveedores.", - solution=["Verifica el ID del Destinatario", "Revisa el catálogo"], - code="NOT_FOUND", - value=invoice.compliance_mx.shipped_to_id, - ) - - if invoice.compliance_mx.shipped_by_id: - shipped_by_exists = ( - db.query(ClientProvider) - .filter( - ClientProvider.id == invoice.compliance_mx.shipped_by_id, - ClientProvider.tenant_id == tenant_id, - ClientProvider.company_id == company_id, - ) - .first() - ) - if not shipped_by_exists: - errors.add_error( - field="compliance_mx.shipped_by_id", - message="El Remitente no existe en el Catálogo de Clientes y Proveedores.", - solution=["Verifica el ID del Remitente", "Revisa el catálogo"], - code="NOT_FOUND", - value=invoice.compliance_mx.shipped_by_id, - ) - - # Validar agente aduanal solo si se proporciona - if invoice.compliance_mx.customs_broker_id: - customs_broker_exists = ( - db.query(CustomsBroker) - .filter( - CustomsBroker.id == invoice.compliance_mx.customs_broker_id, - CustomsBroker.tenant_id == tenant_id, - CustomsBroker.company_id == company_id, - ) - .first() - ) - if not customs_broker_exists: - errors.add_error( - field="compliance_mx.customs_broker_id", - message="El Agente Aduanal no existe en el Catálogo de Clientes y Proveedores.", - solution=["Verifica el ID del Agente Aduanal", "Revisa el catálogo"], - code="NOT_FOUND", - value=invoice.compliance_mx.customs_broker_id, - ) - - if invoice.logistics: - carrier_exists = None - if invoice.logistics.carrier_id: - # `carrier_id` is the frontend/export key (string). Prefer validating by the internal int - # when present, otherwise validate by `transporter_key`. - if getattr(invoice.logistics, "carrier_int_id", None) is not None: - carrier_exists = ( - db.query(Transporter) - .filter( - Transporter.transporter_id - == invoice.logistics.carrier_int_id, - Transporter.tenant_id == tenant_id, - Transporter.company_id == company_id, - ) - .first() - ) - else: - carrier_exists = ( - db.query(Transporter) - .filter( - Transporter.transporter_key == invoice.logistics.carrier_id, - Transporter.tenant_id == tenant_id, - Transporter.company_id == company_id, - ) - .first() - ) - if not carrier_exists: - errors.add_error( - field="logistics.carrier_id", - message="El Transportista no existe en el Catálogo de Clientes y Proveedores.", - solution=["Verifica el ID del Transportista", "Revisa el catálogo"], - code="NOT_FOUND", - value=invoice.logistics.carrier_id, - ) - - if carrier_exists and _logistics_str_nonempty( - getattr(invoice.logistics, "driver_name", None) - ): - driver_row = ( - db.query(Driver) - .filter( - Driver.transporter_key == str(invoice.logistics.carrier_id).strip(), - Driver.driver_name - == str(invoice.logistics.driver_name).strip(), - Driver.tenant_id == tenant_id, - Driver.company_id == company_id, - ) - .first() - ) - if not driver_row: - errors.add_error( - field="logistics.driver_name", - message=( - "El conductor no existe en el catálogo de conductores " - "para el transportista indicado." - ), - solution=[ - "Registra el conductor en el catálogo o elige uno de la lista" - ], - code="NOT_FOUND", - value=invoice.logistics.driver_name, - ) - - if invoice.logistics.transport_type not in [t.value for t in TransportType]: - errors.add_error( - field="logistics.transport_type", - message="El Tipo de Transporte proporcionado no es válido.", - solution=[ - f"Selecciona un Tipo de Transporte válido: {[t.value for t in TransportType]}" - ], - code="INVALID_TRANSPORT_TYPE", - value=invoice.logistics.transport_type, - ) - else: - # Vehículo: solo catálogo si viene informado (cualquier tipo de transporte). - tid = getattr(invoice.logistics, "transport_int_id", None) - tcode = invoice.logistics.transport_id - if tid is not None: - v_by_int = ( - db.query(Vehicle) - .filter( - Vehicle.vehicle_id == tid, - Vehicle.tenant_id == tenant_id, - Vehicle.company_id == company_id, - ) - .first() - ) - if not v_by_int: - errors.add_error( - field="logistics.transport_int_id", - message="El Vehículo (transporte) no existe en el catálogo.", - solution=[ - "Verifica el ID interno del vehículo o usa la clave de transporte" - ], - code="NOT_FOUND", - value=tid, - ) - elif _logistics_str_nonempty(tcode): - v_by_key = ( - db.query(Vehicle) - .filter( - func.upper(Vehicle.vehicle_key) - == str(tcode).strip().upper(), - Vehicle.tenant_id == tenant_id, - Vehicle.company_id == company_id, - ) - .first() - ) - if not v_by_key: - errors.add_error( - field="logistics.transport_id", - message="El Vehículo (clave transporte) no existe en el catálogo.", - solution=["Verifica la clave en el catálogo de vehículos"], - code="NOT_FOUND", - value=tcode, - ) - - # Remolque: obligatorio si tipo != 'none'; con 'none' solo se valida catálogo si hay dato. - tr_int = getattr(invoice.logistics, "trailer_int_id", None) - tr_num = invoice.logistics.trailer_num - if invoice.logistics.transport_type != "none": - if tr_int is not None: - tr = ( - db.query(Trailer) - .filter( - Trailer.trailer_id == tr_int, - Trailer.tenant_id == tenant_id, - Trailer.company_id == company_id, - ) - .first() - ) - if not tr: - errors.add_error( - field="logistics.trailer_int_id", - message="El Remolque no existe en el catálogo.", - solution=["Verifica el ID interno del remolque"], - code="NOT_FOUND", - value=tr_int, - ) - elif not _logistics_str_nonempty(tr_num): - errors.add_error( - field="logistics.trailer_num", - message=( - "El Remolque es obligatorio cuando el Tipo de Transporte es distinto de 'none'." - ), - solution=["Selecciona un remolque del catálogo"], - code="REQUIRED_FIELD", - value=tr_num, - ) - else: - tr = ( - db.query(Trailer) - .filter( - func.upper(Trailer.trailer_number) - == str(tr_num).strip().upper(), - Trailer.tenant_id == tenant_id, - Trailer.company_id == company_id, - ) - .first() - ) - if not tr: - errors.add_error( - field="logistics.trailer_num", - message="El Remolque no existe en el catálogo.", - solution=["Verifica el número de remolque"], - code="NOT_FOUND", - value=tr_num, - ) - else: - if tr_int is not None: - tr = ( - db.query(Trailer) - .filter( - Trailer.trailer_id == tr_int, - Trailer.tenant_id == tenant_id, - Trailer.company_id == company_id, - ) - .first() - ) - if not tr: - errors.add_error( - field="logistics.trailer_int_id", - message="El Remolque no existe en el catálogo.", - solution=["Verifica el ID interno del remolque"], - code="NOT_FOUND", - value=tr_int, - ) - elif _logistics_str_nonempty(tr_num): - tr = ( - db.query(Trailer) - .filter( - func.upper(Trailer.trailer_number) - == str(tr_num).strip().upper(), - Trailer.tenant_id == tenant_id, - Trailer.company_id == company_id, - ) - .first() - ) - if not tr: - errors.add_error( - field="logistics.trailer_num", - message="El Remolque no existe en el catálogo.", - solution=["Verifica el número de remolque"], - code="NOT_FOUND", - value=tr_num, - ) - - if invoice.financials: - submitted = invoice.financials.currency - stored_str = "" - if existing_invoice and existing_invoice.financials is not None: - stored_str = _normalize_invoice_currency_value( - existing_invoice.financials.currency - ) - - if submitted is None or submitted == "": - if stored_str: - invoice.financials.currency = stored_str - else: - invoice.financials.currency = "foreign" - else: - invoice.financials.currency = ( - _normalize_invoice_currency_value(submitted) or "foreign" - ) - - if invoice.financials.currency not in [c.value for c in Currency]: - errors.add_error( - field="financials.currency", - message="La Moneda proporcionada no es válida.", - solution=[f"Selecciona una Moneda válida: {[c.value for c in Currency]}"], - code="INVALID_CURRENCY", - value=invoice.financials.currency, - ) - else: - invoice_id = getattr(invoice, "id", None) - if ( - invoice_id is not None - and existing_invoice is not None - and existing_invoice.financials is not None - ): - old_c = _normalize_invoice_currency_value( - existing_invoice.financials.currency - ) - new_c = invoice.financials.currency - if old_c != new_c: - has_items = ( - db.query(LineItem) - .filter( - LineItem.invoice_id == invoice_id, - LineItem.tenant_id == tenant_id, - LineItem.company_id == company_id, - ) - .first() - ) - if has_items: - errors.add_error( - field="items", - message=( - "La opción tipo de moneda no puede ser modificada " - "ya que la factura tiene partidas asociadas." - ), - solution=[ - "Verifica la moneda de las partidas asociadas a la factura." - ], - code="CURRENCY_CANNOT_BE_CHANGED", - value=invoice.financials.currency, - ) - - if invoice.financials.currency == "foreign": - invoice.financials.currency_type = "USD" - elif invoice.financials.currency == "local": - invoice.financials.currency_type = "MXN" - elif invoice.financials.currency == "manual": - if not invoice.financials.currency_type: - errors.add_error( - field="financials.currency_type", - message="El Tipo de Moneda es obligatorio cuando la Moneda es 'manual'.", - solution=["Proporciona un Tipo de Moneda válido"], - code="REQUIRED_FIELD", - value=invoice.financials.currency_type, - ) - else: - currency_exists = ( - db.query(CurrencyType) - .filter(CurrencyType.code == invoice.financials.currency_type) - .first() - ) - if not currency_exists: - errors.add_error( - field="financials.currency_type", - message="El Tipo de Moneda no existe en el Catálogo de Tipos de Moneda.", - solution=[ - "Verifica el código del Tipo de Moneda", - "Revisa el catálogo", - ], - code="NOT_FOUND", - value=invoice.financials.currency_type, - ) - - if invoice.logistics.incoterm: - incoterm_exists = ( - db.query(Incoterm) - .filter( - Incoterm.code == invoice.logistics.incoterm - ) - .first() - ) - if not incoterm_exists: - errors.add_error( - field="logistics.incoterm", - message="El Incoterm no existe en el Catálogo de Incoterms.", - solution=["Verifica el código del Incoterm", "Revisa el catálogo"], - code="NOT_FOUND", - value=invoice.logistics.incoterm, - ) - - if invoice.logistics.weight_type not in [w.value for w in WeightUnit]: - errors.add_error( - field="logistics.weight_type", - message="La Unidad de Peso proporcionada no es válida.", - solution=[ - f"Selecciona una Unidad de Peso válida: {[w.value for w in WeightUnit]}" - ], - code="INVALID_WEIGHT_UNIT", - value=invoice.logistics.weight_type, - ) - - if invoice.compliance_mx.aduana: - custom_section_exists = ( - db.query(CustomsSection) - .filter( - CustomsSection.customs_code == invoice.compliance_mx.aduana, - ) - .first() - ) - if not custom_section_exists: - errors.add_error( - field="compliance_mx.aduana", - message="La Aduana no existe en el Catálogo de Secciones Aduaneras.", - solution=["Verifica el código de la Aduana", "Revisa el catálogo"], - code="NOT_FOUND", - value=invoice.compliance_mx.aduana, - ) - - if invoice.compliance_mx.manifest_number: - manifest_exists = ( - db.query(Manifest) - .filter( - Manifest.manifest_number == invoice.compliance_mx.manifest_number, - Manifest.tenant_id == tenant_id, - Manifest.company_id == company_id, - ) - .first() - ) - if not manifest_exists: - errors.add_error( - field="compliance_mx.manifest_number", - message="El Número de Manifiesto no existe en el sistema.", - solution=["Verifica el Número de Manifiesto", "Revisa el catálogo"], - code="NOT_FOUND", - value=invoice.compliance_mx.manifest_number, - ) - - apply_calculations(invoice) \ No newline at end of file diff --git a/backend/api/v1/modules/a76/invoices/common/mappers.py b/backend/api/v1/modules/a76/invoices/common/mappers.py deleted file mode 100644 index 3b3fe57b..00000000 --- a/backend/api/v1/modules/a76/invoices/common/mappers.py +++ /dev/null @@ -1,13 +0,0 @@ -""" """ - -def clean_dict(data_dict: dict) -> dict: - cleaned = {} - for key, value in data_dict.items(): - - if isinstance(value, str) and not value.strip(): - cleaned[key] = None - - else: - cleaned[key] = value - return cleaned - diff --git a/backend/api/v1/modules/a76/invoices/common/process/review_equivalence.py b/backend/api/v1/modules/a76/invoices/common/process/review_equivalence.py deleted file mode 100644 index 7c0320d2..00000000 --- a/backend/api/v1/modules/a76/invoices/common/process/review_equivalence.py +++ /dev/null @@ -1,81 +0,0 @@ -from decimal import Decimal -from sqlalchemy.orm import Session -from api.v1.modules.a76.general_catalogs.unit_conversions.models import UnitConversion -from api.v1.modules.a76.general_catalogs.equivalencies.models import EquivalencyItem - -def _get_unit_equivalence( - db: Session, - from_unit: str, - to_unit: str, - tenant_id: str, - company_id: str, -) -> tuple[str, Decimal]: - """ - Busca una conversión entre dos unidades de medida. - Paridad: REVEQUIVALENCIA (Clarion SCAII). - - Busca primero en el catálogo de Conversiones (unit_conversions) y, - si no encuentra, en el catálogo de Equivalencias (equivalency_items). - - Retorna (multi_divide, factor_conv): - - ('M', factor) → multiplicar cantidad por factor - - ('D', factor) → dividir cantidad por factor - - ('', 0) → no existe equivalencia - """ - # ── 1. Catálogo de Conversiones ────────────────────────────────────────── - conv = ( - db.query(UnitConversion) - .filter( - UnitConversion.tenant_id == tenant_id, - UnitConversion.company_id == company_id, - UnitConversion.from_unit_code == from_unit, - UnitConversion.to_unit_code == to_unit, - ) - .first() - ) - if conv and conv.conversion_factor: - return "M", conv.conversion_factor - - conv_inv = ( - db.query(UnitConversion) - .filter( - UnitConversion.tenant_id == tenant_id, - UnitConversion.company_id == company_id, - UnitConversion.from_unit_code == to_unit, - UnitConversion.to_unit_code == from_unit, - ) - .first() - ) - if conv_inv and conv_inv.conversion_factor: - return "D", conv_inv.conversion_factor - - # ── 2. Catálogo de Equivalencias (fallback) ────────────────────────────── - eq = ( - db.query(EquivalencyItem) - .filter( - EquivalencyItem.tenant_id == tenant_id, - EquivalencyItem.company_id == company_id, - EquivalencyItem.original_field == from_unit, - EquivalencyItem.external_field == to_unit, - ) - .first() - ) - if eq: - factor = eq.conversion_factor if eq.conversion_factor else Decimal(1) - return "M", factor - - eq_inv = ( - db.query(EquivalencyItem) - .filter( - EquivalencyItem.tenant_id == tenant_id, - EquivalencyItem.company_id == company_id, - EquivalencyItem.original_field == to_unit, - EquivalencyItem.external_field == from_unit, - ) - .first() - ) - if eq_inv: - factor = eq_inv.conversion_factor if eq_inv.conversion_factor else Decimal(1) - return "D", factor - - return "", Decimal(0) diff --git a/backend/api/v1/modules/a76/invoices/common/process/review_uma.py b/backend/api/v1/modules/a76/invoices/common/process/review_uma.py deleted file mode 100644 index 96b0f9ae..00000000 --- a/backend/api/v1/modules/a76/invoices/common/process/review_uma.py +++ /dev/null @@ -1,128 +0,0 @@ -from decimal import Decimal - -from sqlalchemy.orm import Session - -from api.v1.modules.a76.general_catalogs.units_of_measure.models import UnitOfMeasureCustoms -from api.v1.modules.a76.items.models import LineItem -from core.exceptions import ErrorCollector - -from .review_equivalence import _get_unit_equivalence - -def revisa_uma( - db: Session, - line: LineItem, - tenant_id: str, - company_id: str, - errors: ErrorCollector, -) -> None: - """ - Revisa la unidad de medida americana (UMA) de aduana para una partida. - Paridad: REVISA_UMA (Clarion SCAII). - - Flujo: - 1. Verifica que la UM de la partida exista en el catálogo GUnimedida. - 2. Verifica que esa UM tenga asignada una Clave_AMex (UM aduana mexicana). - 3. Verifica que la Clave_AMex exista en el catálogo GUMAduana. - 4. Verifica que el registro de aduana tenga una UnidadSCAII (a76_unit_code). - 5. Calcula la cantidad UMA: - - Si la UM de la partida coincide con UnidadSCAII → cantidad directa. - - Si no → busca conversión en el catálogo de equivalencias. - 6. Escribe CantImpoUMA y ClaveUMA de regreso en la partida. - """ - uom = line.unit_of_measure_info - line_um_code = uom.code if uom else "" - - # ── 1. Verificar existencia de la UM en el catálogo ────────────────────── - if uom is None: - errors.add_error( - field=f"line[{line.line_number}].unit_of_measure", - message=( - f"No existe la unidad de medida {line_um_code}" - " en el catálogo de Unidades de Medida." - ), - solution=["Capturarla en el catálogo de Unidades de Medida."], - code="UNIMEDIDA", - ) - return - - # ── 2. Verificar que tenga Clave_AMex asignada ─────────────────────────── - customs_code = uom.customs_code or "" - if not customs_code: - errors.add_error( - field=f"line[{line.line_number}].unit_of_measure", - message=( - f"No existe la unidad de medida de la aduana para la unidad de medida " - f"{line_um_code} en el catálogo de Unidades de Medida." - ), - solution=["Asignar la U.M.A en el catálogo de Unidades de Medida."], - code="UNIMEDIDA", - ) - return - - # ── 3. Buscar la UM en el catálogo de Aduana Mex (GUMAduana) ───────────── - customs_uom = ( - db.query(UnitOfMeasureCustoms) - .filter(UnitOfMeasureCustoms.code == customs_code) - .first() - ) - - if customs_uom is None: - errors.add_error( - field=f"line[{line.line_number}].unit_of_measure", - message=( - f"No existe la unidad de medida {customs_code}" - " en el catálogo de Unidades de Medida de la Aduana Mex." - ), - solution=["Actualizar sus Catálogos Fijos o llamar a su proveedor."], - code="UNIMEDIDA", - ) - return - - # ── 4. Verificar que tenga UnidadSCAII asignada ────────────────────────── - scaii_unit = customs_uom.a76_unit_code or "" - if not scaii_unit: - errors.add_error( - field=f"line[{line.line_number}].unit_of_measure", - message=( - f"No tiene asignada la U.M. Equivalente SCAII la unidad de medida " - f"{customs_code} en el catálogo de Unidades de Medida de la Aduana Mex." - ), - solution=["Actualizar sus Catálogos Fijos o llamar a su proveedor."], - code="UNIMEDIDA", - ) - return - - # ── 5. Calcular cantidad UMA ────────────────────────────────────────────── - line_qty = Decimal(str(line.quantity.quantity or 0)) if line.quantity else Decimal(0) - cant_uma = Decimal(0) - - if line_um_code == scaii_unit: - cant_uma = line_qty - else: - multi_divide, factor_conv = _get_unit_equivalence( - db, line_um_code, scaii_unit, tenant_id, company_id - ) - - if multi_divide == "M": - cant_uma = line_qty * factor_conv - elif multi_divide == "D": - cant_uma = line_qty / factor_conv if factor_conv else Decimal(0) - else: - errors.add_error( - field=f"line[{line.line_number}].unit_of_measure", - message=( - f"No hay equivalencia entre la U.M. Partida: {line_um_code}" - f" y la U.M. Aduana {scaii_unit}." - ), - solution=[ - "Capturar su equivalencia en el Catálogo de Conversiones " - "o configurar la U.M.Aduana correcta en la U.M. Comercial." - ], - code="EQUIVALENCIA", - ) - return - - # ── 6. Escribir resultados en la partida ────────────────────────────────── - if line.quantity: - line.quantity.quantity_uma = cant_uma - line.uma_key = customs_code diff --git a/backend/api/v1/modules/a76/invoices/docs/README.MD b/backend/api/v1/modules/a76/invoices/docs/README.MD deleted file mode 100644 index a672c329..00000000 --- a/backend/api/v1/modules/a76/invoices/docs/README.MD +++ /dev/null @@ -1,151 +0,0 @@ -# Anexo76 — Documentación del Módulo de Facturas - -> **Sistema:** SCAII · Aduanasoft -> **Versión:** 1.0 · Marzo 2026 -> **Módulos documentados:** `imports/` · `exports/` - ---- - -## ¿Qué es este módulo? - -El módulo de facturas gestiona el ciclo completo de las operaciones aduaneras de una empresa maquiladora: la **entrada** de materiales al país (importaciones) y la **salida** (exportaciones). Ambos módulos están conectados a través de un ledger de inventario compartido — las importaciones crean saldos, las exportaciones los consumen. - ---- - -## Estructura de la documentación - -``` -docs/ -│ -├── README.md ← estás aquí -│ -├── exports/ -│ ├── exports_cu_comprensibles.md ← casos de uso -│ └── exports_cu_diagramas.md ← diagramas de flujo Mermaid -│ -└── imports/ - ├── imports_cu_comprensibles.md ← casos de uso - └── imports_cu_diagramas.md ← diagramas de flujo Mermaid -``` - ---- - -## Módulo de Exportaciones - -Gestiona la salida de materiales del país. Cada tipo de factura representa un escenario aduanero diferente. - -### Casos de uso - -| ID | Caso de uso | Descripción breve | Diagrama | -|----|-------------|-------------------|:--------:| -| [CU-EXP-001](./exports/exports_cu_comprensibles.md#cu-exp-001--procesar-una-factura-nodes) | Procesar NODES | Exportación sin descarga de inventario | [→](./exports/exports_cu_diagramas.md#cu-exp-001--procesar-nodes) | -| [CU-EXP-002](./exports/exports_cu_comprensibles.md#cu-exp-002--procesar-una-factura-donac) | Procesar DONAC | Donación al extranjero con descarga directa | [→](./exports/exports_cu_diagramas.md#cu-exp-002--procesar-con-descarga-de-inventario-donac) | -| [CU-EXP-003](./exports/exports_cu_comprensibles.md#cu-exp-003--procesar-una-factura-afijo) | Procesar AFIJO | Exportación con descarga e IMD opcional | [→](./exports/exports_cu_diagramas.md#cu-exp-003--procesar-afijo-con-cambio-de-régimen) | -| [CU-EXP-004](./exports/exports_cu_comprensibles.md#cu-exp-004--procesar-una-factura-scrap) | Procesar SCRAP | Exportación de desperdicio con descarga | [→](./exports/exports_cu_diagramas.md#cu-exp-004--procesar-scrap) | -| [CU-EXP-005](./exports/exports_cu_comprensibles.md#cu-exp-005--procesar-una-factura-reexp) | Procesar REEXP | Reexportación desde importaciones definitivas | [→](./exports/exports_cu_diagramas.md#cu-exp-005--procesar-reexp) | -| [CU-EXP-006](./exports/exports_cu_comprensibles.md#cu-exp-006--procesar-una-factura-vemex) | Procesar VEMEX | Venta al extranjero desde régimen especial | [→](./exports/exports_cu_diagramas.md#cu-exp-006--procesar-vemex) | -| [CU-EXP-007](./exports/exports_cu_comprensibles.md#cu-exp-007--revertir-una-factura-nodes) | Revertir NODES | Deshacer sin efectos en inventario | [→](./exports/exports_cu_diagramas.md#cu-exp-007--revertir-nodes) | -| [CU-EXP-008](./exports/exports_cu_comprensibles.md#cu-exp-008--revertir-una-factura-con-descarga-afijo-donac-scrap-reexp-vemex) | Revertir con descarga | Devolver saldos y cancelar registros del ledger | [→](./exports/exports_cu_diagramas.md#cu-exp-008--revertir-con-descarga-afijo-donac-scrap-reexp-vemex) | -| [CU-EXP-009](./exports/exports_cu_comprensibles.md#cu-exp-009--reversión-bloqueada-por-exportaciones-activas) | Reversión bloqueada por exportación | Bloqueo cuando otra exportación usa los saldos | [→](./exports/exports_cu_diagramas.md#cu-exp-009--reversión-bloqueada-por-exportaciones-activas) | -| [CU-EXP-010](./exports/exports_cu_comprensibles.md#cu-exp-010--reversión-bloqueada-por-importación-definitiva-existente) | Reversión bloqueada por IMD | Bloqueo cuando existe una IMD generada | [→](./exports/exports_cu_diagramas.md#cu-exp-010--reversión-bloqueada-por-importación-definitiva-existente) | - -### Resumen rápido por tipo - -| Tipo | Descarga inventario | Genera IMD | Requiere procedencia | -|------|:-------------------:|:----------:|:--------------------:| -| NODES | No | No | — | -| DONAC | Sí | No | — | -| AFIJO | Sí | Solo con cambio de régimen | TEM (si CR) | -| SCRAP | Sí | Solo con cambio de régimen | TEM (si CR) | -| REEXP | Sí | No | DEF obligatoria | -| VEMEX | Sí | No | DEF obligatoria | - ---- - -## Módulo de Importaciones - -Gestiona la entrada de materiales al país. Las importaciones temporales (TEM) son la base del inventario que las exportaciones consumen. - -### Casos de uso - -| ID | Caso de uso | Descripción breve | Diagrama | -|----|-------------|-------------------|:--------:| -| [CU-IMP-001](./imports/imports_cu_comprensibles.md#cu-imp-001--procesar-una-factura-tem-importación-temporal) | Procesar TEM | Entrada temporal, genera saldos de inventario | [→](./imports/imports_cu_diagramas.md#cu-imp-001--procesar-tem-importación-temporal) | -| [CU-IMP-002](./imports/imports_cu_comprensibles.md#cu-imp-002--procesar-una-factura-def-importación-definitiva) | Procesar DEF | Entrada definitiva con IVA, sin saldos | [→](./imports/imports_cu_diagramas.md#cu-imp-002--procesar-def-importación-definitiva) | -| [CU-IMP-003](./imports/imports_cu_comprensibles.md#cu-imp-003--procesar-una-factura-mex-compra-mexicana) | Procesar MEX | Compra nacional con IVA, sin saldos | [→](./imports/imports_cu_diagramas.md#cu-imp-003--procesar-mex-compra-mexicana) | -| [CU-IMP-004](./imports/imports_cu_comprensibles.md#cu-imp-004--procesar-una-factura-tem-con-regla-octava-prosec) | Procesar TEM + PROSEC | TEM con permisos de Regla Octava | [→](./imports/imports_cu_diagramas.md#cu-imp-004--procesar-tem-con-regla-octava-prosec) | -| [CU-IMP-005](./imports/imports_cu_comprensibles.md#cu-imp-005--revertir-una-factura-tem) | Revertir TEM | Anular saldos, restaurar cupos PROSEC | [→](./imports/imports_cu_diagramas.md#cu-imp-005--revertir-tem) | -| [CU-IMP-006](./imports/imports_cu_comprensibles.md#cu-imp-006--revertir-una-factura-def-o-mex) | Revertir DEF o MEX | Deshacer sin efectos en inventario | [→](./imports/imports_cu_diagramas.md#cu-imp-006--revertir-def-o-mex) | -| [CU-IMP-007](./imports/imports_cu_comprensibles.md#cu-imp-007--reversión-bloqueada-por-exportaciones-activas) | Reversión bloqueada | Bloqueo cuando exportaciones usan los saldos | [→](./imports/imports_cu_diagramas.md#cu-imp-007--reversión-bloqueada-por-exportaciones-activas) | - -### Resumen rápido por tipo - -| Tipo | Genera saldo inventario | Calcula IVA | Regla Octava | -|------|:-----------------------:|:-----------:|:------------:| -| TEM | Sí | No | Opcional | -| DEF | No | Sí | No | -| MEX | No | Sí | No | - ---- - -## Cómo se conectan importaciones y exportaciones - -``` -Importación TEM procesada - │ - │ crea movimientos ENTRADA en el ledger - │ (un registro por partida principal) - ▼ - Ledger de inventario (a24.balance_movement) - │ - │ las exportaciones consultan saldos disponibles - │ usando criterio PEPS — más antiguo primero - ▼ -Exportación procesa (DONAC, AFIJO, SCRAP, REEXP, VEMEX) - │ - │ inserta movimientos CONSUMO - │ reduce el saldo disponible - ▼ - Si se revierte la exportación → inserta RETORNO - Si se revierte la importación → inserta ANULACIÓN DE ENTRADA - El ledger nunca se borra — solo se agregan registros -``` - -**Regla de orden para reversiones:** -Siempre deben revertirse primero las exportaciones y luego la importación. El sistema lo garantiza mediante bloqueos automáticos. - ---- - -## Estados de una factura - -``` -PENDIENTE ──[Procesar]──▶ PROCESADA ──[Revertir]──▶ REVERTIDA - │ - │ si hay bloqueos activos - ▼ - BLOQUEADA (no es un estado real, - el sistema rechaza la operación - con un mensaje de error) -``` - ---- - -## Glosario - -| Término | Significado | -|---------|-------------| -| **TEM** | Importación Temporal — material que entra para ser procesado y reexportado | -| **DEF** | Importación Definitiva — material que entra pagando impuestos completos | -| **MEX** | Compra a proveedor mexicano | -| **IMD** | Importación Definitiva generada automáticamente por un cambio de régimen | -| **NODES** | Exportación sin descarga de saldos (No Descarga) | -| **AFIJO** | Exportación de material transformado, con descarga de TEM | -| **DONAC** | Donación al extranjero con descarga de inventario | -| **SCRAP** | Exportación de desperdicio o chatarra | -| **REEXP** | Reexportación de material importado definitivamente | -| **VEMEX** | Venta al extranjero desde régimen especial (IMMEX / zona franca) | -| **PEPS** | Primero en Entrar, Primero en Salir — criterio de consumo de inventario | -| **PROSEC** | Programa de Promoción Sectorial — beneficio arancelario para maquiladoras | -| **Regla Octava** | Mecanismo de la Ley Aduanera que permite importar con arancel preferencial bajo un permiso PROSEC | -| **Ledger a24** | Registro histórico de todos los movimientos de inventario — solo escritura, nunca se borra | -| **Cambio de régimen** | Conversión de material temporal a definitivo, requiere generación de IMD | \ No newline at end of file diff --git a/backend/api/v1/modules/a76/invoices/exports/docs/REPO_exports_casos_de_uso.md b/backend/api/v1/modules/a76/invoices/exports/docs/REPO_exports_casos_de_uso.md deleted file mode 100644 index a497c533..00000000 --- a/backend/api/v1/modules/a76/invoices/exports/docs/REPO_exports_casos_de_uso.md +++ /dev/null @@ -1,379 +0,0 @@ -# Casos de Uso — Módulo de Exportaciones - -**Sistema:** Anexo76 · SCAII -**Módulo:** `exports/` -**Versión:** 1.0 · Marzo 2026 - ---- - -## Contexto general - -El módulo de exportaciones gestiona el ciclo completo de una factura de exportación dentro del sistema: desde que el usuario la captura hasta que queda registrada como procesada ante la aduana. También permite revertirla si hubo un error. - -Toda factura de exportación pasa por uno de dos momentos: **procesarla** (actualizarla) o **revertirla** (desactualizarla). El sistema hace cosas muy distintas dependiendo del **tipo de factura**, porque cada tipo representa un escenario aduanero diferente con reglas propias. - ---- - -## CU-EXP-001 · Procesar una factura NODES - -### Descripción - -El usuario procesa una factura de exportación de tipo **NODES**. Este tipo representa mercancía que sale del país pero que **no está vinculada a ninguna importación temporal** registrada en el sistema — puede ser producción propia, material adquirido localmente o cualquier salida sin historial de entrada temporal. - -### Por qué existe - -No toda la mercancía que exporta una maquiladora fue importada temporalmente. Las empresas también exportan lo que fabrican localmente. El sistema necesita registrar esa salida y calcular su valor en pesos y dólares sin buscar ningún saldo previo en el inventario. - -### Quién lo usa - -El usuario de captura de exportaciones, desde la pantalla de facturas de exportación, al presionar **"Actualizar Factura"**. - -### Qué necesita estar listo antes - -- La factura debe estar en estado **Pendiente** — si ya fue procesada, el sistema la rechaza. -- Debe tener al menos una partida capturada. -- Todos los campos del encabezado deben estar completos: fecha, proveedor, destinatario, tipo de cambio, moneda, agente aduanal y pedimento. -- El tipo de cambio del día debe estar registrado en el catálogo. -- Las clases y fracciones arancelarias de cada partida deben existir en el catálogo oficial. -- Cada partida principal debe tener un costo unitario mayor a cero. - -### Qué hace el sistema - -Primero valida que todos los campos obligatorios del encabezado estén correctos y que las partidas existan. Luego marca explícitamente todas las partidas como "sin descarga" para que el inventario de importaciones temporales no se vea afectado. Valida que cada fracción arancelaria esté vigente, que el tipo de cambio coincida con el catálogo y que ninguna partida tenga costo en cero. - -Después calcula los valores monetarios de cada partida multiplicando el costo unitario por la cantidad y el tipo de cambio del día, obteniendo el valor en pesos, en dólares y en moneda de cuenta. Suma todos esos valores para obtener los totales de la factura. - -Finalmente guarda los totales calculados y marca la factura como **Procesada**. - -### Qué queda guardado - -La factura queda en estado **Procesada** con sus valores monetarios calculados. El inventario de importaciones temporales no se modifica — NODES no descarga ni registra ningún movimiento en el ledger de saldos. - -### Qué puede fallar - -| Situación | Qué hace el sistema | -|-----------|---------------------| -| La factura ya estaba procesada | La rechaza de inmediato sin ejecutar ningún paso más | -| El tipo de cambio capturado no coincide con el catálogo | Muestra el error y detiene el proceso | -| Una fracción arancelaria no existe en el catálogo oficial | Reporta qué partida tiene la fracción inválida | -| Una clase arancelaria está desactivada | Reporta qué partida tiene la clase desactivada | -| Una partida tiene costo unitario en cero | Indica qué partida no tiene precio capturado | -| Una partida tiene series activadas pero no hay series registradas | Indica qué partida le faltan series | - ---- - -## CU-EXP-002 · Procesar una factura DONAC - -### Descripción - -El usuario procesa una factura de exportación de tipo **DONAC**. Este tipo representa una **donación al extranjero** — la mercancía sale del país y descarga directamente el inventario de importaciones temporales, sin ningún trámite adicional de cambio de régimen. - -### Por qué existe - -Las empresas maquiladoras a veces donan material a organizaciones en el extranjero. La donación sigue siendo una salida de inventario: el material importado temporalmente sale del país y el sistema debe registrar que ese saldo ya no está disponible. DONAC es la vía más directa de hacerlo. - -### Quién lo usa - -El usuario de captura de exportaciones al procesar una factura marcada como tipo DONAC. - -### Qué necesita estar listo antes - -Lo mismo que NODES, más lo siguiente: - -- Cada partida que se va a descargar debe tener indicada su **factura de importación origen** (la TEM o DEF de la que proviene el material). -- Esa factura de importación debe estar procesada y tener una fecha anterior a la exportación. -- El saldo disponible en esa importación debe ser suficiente para cubrir la cantidad que se quiere exportar. -- La unidad de medida del material debe coincidir entre la importación y la exportación. - -### Qué hace el sistema - -Ejecuta las mismas validaciones que NODES (encabezado, clases, fracciones, tipo de cambio, costo unitario) pero además realiza el **descargo de inventario**. - -Para el descargo, el sistema construye una lista de todo lo que se quiere exportar y busca de qué lotes de importación proviene cada material. Aplica el criterio **PEPS** (Primero en Entrar, Primero en Salir): si hay varios lotes del mismo material de distintas fechas, consume primero el más antiguo. Verifica que el saldo alcance y distribuye las cantidades entre los lotes disponibles. - -Si todo está en orden, registra en el ledger de inventario que esos lotes fueron consumidos, actualiza los valores retornados en las partidas de importación origen y marca la factura como **Procesada**. - -### Qué queda guardado - -La factura queda **Procesada**. En el ledger de saldos quedan registrados movimientos de tipo **Consumo** por cada lote descargado. Las partidas de importación origen reflejan cuánto valor ya fue exportado. - -### Qué puede fallar - -| Situación | Qué hace el sistema | -|-----------|---------------------| -| La factura de importación origen no está procesada | Indica qué importación debe procesarse primero | -| La fecha de la importación es posterior a la exportación | Es un error de datos — no se puede exportar antes de importar | -| No hay saldo suficiente en la importación origen | Muestra exactamente cuánto falta por partida | -| La unidad de medida no coincide entre importación y exportación | Indica qué partidas tienen incompatibilidad de unidades | - ---- - -## CU-EXP-003 · Procesar una factura AFIJO - -### Descripción - -El usuario procesa una factura de exportación de tipo **AFIJO**. Este tipo puede tener dos variantes: **con cambio de régimen** o **sin cambio de régimen**. - -En ambos casos se descarga el inventario de importaciones temporales. La diferencia es que cuando hay cambio de régimen, el sistema también genera automáticamente una **Importación Definitiva (IMD)** que ampara la conversión del material. - -### Por qué existe - -AFIJO es uno de los tipos más comunes en operaciones maquiladoras. Ocurre cuando la empresa exporta material que procesó a partir de insumos importados temporalmente. El "afijo" hace referencia a que el material fue transformado o ensamblado dentro del país antes de salir. - -Cuando además hay **cambio de régimen**, significa que una parte del material importado temporalmente no será exportado sino que se quedará en México de forma permanente — por eso el sistema genera la IMD que formaliza ese cambio ante la aduana. - -### Quién lo usa - -El usuario de captura de exportaciones. Antes de procesar, el usuario ya debe haber indicado en la factura si aplica cambio de régimen (`is_regime_change = True`). - -### Qué necesita estar listo antes - -Lo mismo que DONAC. Si aplica cambio de régimen, adicionalmente: - -- Todas las partidas deben venir de importaciones de tipo **Temporal (TEM)** — si alguna viene de una Definitiva, el sistema lo rechaza. -- No debe existir ya una Importación Definitiva generada anteriormente para esta misma factura (el sistema lo verifica para evitar duplicados). - -### Qué hace el sistema - -**Sin cambio de régimen:** igual que DONAC — descarga el inventario directamente. - -**Con cambio de régimen:** antes del descargo, verifica que todas las partidas tengan procedencia TEM. Luego genera automáticamente una nueva factura de Importación Definitiva con los mismos datos del encabezado y una copia de todas las partidas. Esta IMD queda en estado Pendiente para que el usuario la asocie después a su pedimento. Después ejecuta el descargo de inventario normalmente. - -### Qué queda guardado - -La factura de exportación queda **Procesada**. Si hubo cambio de régimen, existe una nueva factura IMD en estado **Pendiente** lista para ser tramitada. Los movimientos de consumo quedan registrados en el ledger. - -### Qué puede fallar - -Todo lo que puede fallar en DONAC, más: - -| Situación | Qué hace el sistema | -|-----------|---------------------| -| Una partida tiene procedencia DEF en lugar de TEM (solo en cambio de régimen) | Indica qué partidas tienen la procedencia incorrecta | -| Ya existe una IMD generada para esta factura | El sistema la reutiliza sin crear un duplicado — no es un error | - ---- - -## CU-EXP-004 · Procesar una factura SCRAP - -### Descripción - -El usuario procesa una factura de exportación de tipo **SCRAP**. Representa la salida de material que se exporta como **desperdicio, chatarra o rezago** del proceso productivo. - -Su comportamiento es idéntico al de AFIJO: descarga el inventario y, si aplica, genera una Importación Definitiva por cambio de régimen. La diferencia es conceptual — no técnica: el material no fue transformado ni exportado como producto terminado, sino como subproducto o material sobrante. - -### Por qué existe - -El rezago y la chatarra son una realidad del proceso productivo maquilador. La regulación aduanera mexicana exige que estos materiales — aunque no sean el producto final — también sean reportados al salir del país. SCRAP les da un tratamiento específico diferenciado del producto terminado. - -### Quién lo usa - -El usuario de captura de exportaciones para facturas de desperdicios o rezagos. - -### Qué necesita estar listo antes - -Idéntico a AFIJO. - -### Qué hace el sistema - -Exactamente igual que AFIJO: valida, descarga inventario y, si hay cambio de régimen, genera la IMD correspondiente. - -### Qué puede fallar - -Idéntico a AFIJO. - ---- - -## CU-EXP-005 · Procesar una factura REEXP - -### Descripción - -El usuario procesa una factura de exportación de tipo **REEXP**. Representa la **reexportación** de material que en su momento entró al país como **Importación Definitiva (DEF)** — es decir, material que ya pagó impuestos al entrar y ahora se exporta nuevamente. - -### Por qué existe - -A veces las empresas importan material definitivamente, lo procesan y luego lo exportan. Como ese material entró de forma definitiva (no temporal), no aplica el régimen de maquila temporal — tiene su propio tratamiento. REEXP permite registrar esa salida correctamente, indicando que la procedencia es DEF y no TEM. - -### Quién lo usa - -El usuario de captura de exportaciones cuando el material a exportar proviene de importaciones definitivas. - -### Qué necesita estar listo antes - -Lo mismo que DONAC, con una diferencia clave: **todas las partidas deben tener procedencia DEF**. Si alguna viene de una importación temporal, el sistema la rechaza. - -### Qué hace el sistema - -Antes de iniciar el descargo, verifica que todas las partidas tengan procedencia DEF. Si alguna tiene TEM u otro tipo, detiene el proceso e indica cuál es la partida con el problema. Después ejecuta el descargo de inventario igual que DONAC. - -### Qué queda guardado - -Igual que DONAC — la factura queda Procesada y los movimientos de consumo quedan en el ledger. - -### Qué puede fallar - -Todo lo que puede fallar en DONAC, más: - -| Situación | Qué hace el sistema | -|-----------|---------------------| -| Una partida tiene procedencia TEM en lugar de DEF | Indica qué partidas tienen la procedencia incorrecta | - ---- - -## CU-EXP-006 · Procesar una factura VEMEX - -### Descripción - -El usuario procesa una factura de exportación de tipo **VEMEX**. Representa una **venta al extranjero** bajo un régimen aduanero especial — típicamente empresas en zonas francas o con programas IMMEX que realizan ventas virtuales al exterior. - -### Por qué existe - -VEMEX tiene un tratamiento similar a REEXP: el material que se "exporta" proviene de importaciones definitivas. La distinción respecto a REEXP es de naturaleza fiscal y operativa — en VEMEX la empresa tiene un régimen especial reconocido por la aduana que le permite registrar estas operaciones de forma diferente. El sistema los separa para respetar esa distinción documental. - -### Quién lo usa - -Empresas con régimen IMMEX o en zonas francas que realizan ventas al extranjero desde México. - -### Qué necesita estar listo antes - -Igual que REEXP. A diferencia de otros tipos, VEMEX no requiere `document_type` ni `customs_broker_id` — son opcionales en este tipo de operación porque el trámite aduanero tiene características distintas. - -### Qué hace el sistema - -Idéntico a REEXP: verifica que todas las partidas tengan procedencia DEF y ejecuta el descargo de inventario. - -### Qué puede fallar - -Idéntico a REEXP. - ---- - -## CU-EXP-007 · Revertir una factura NODES - -### Descripción - -El usuario deshace el procesamiento de una factura NODES — generalmente porque hubo un error de captura o necesita modificar los datos antes de reenviarla. - -### Por qué existe - -Un sistema de control aduanero no puede simplemente borrar registros procesados. La reversión permite corregir errores de forma controlada: el sistema regresa la factura a su estado anterior sin eliminar el historial. - -### Qué hace el sistema - -Limpia todos los valores calculados (totales de la factura, valores por partida) y regresa el estado a **Revertida**. Como NODES nunca tocó el inventario, no hay ningún saldo que devolver — es la reversión más simple del módulo. - -### Qué necesita estar listo antes - -La factura debe estar en estado **Procesada**. Si no lo está, el sistema rechaza la reversión. - -### Qué queda guardado - -La factura queda en estado **Revertida**, con los totales en cero, lista para ser corregida y reprocesada. - -### Qué puede fallar - -| Situación | Qué hace el sistema | -|-----------|---------------------| -| La factura no estaba procesada | La rechaza con un mensaje indicando que no puede revertirse | - ---- - -## CU-EXP-008 · Revertir una factura con descarga (AFIJO, DONAC, SCRAP, REEXP, VEMEX) - -### Descripción - -El usuario deshace el procesamiento de una factura que sí descargó inventario. Es la reversión más compleja del módulo porque hay que deshacer múltiples efectos: los saldos descargados, las series marcadas y los registros en el ledger de inventario. - -### Por qué existe - -Cuando una exportación descargó saldos de importaciones temporales, simplemente cambiar el estado de la factura no es suficiente. Hay que devolver formalmente ese inventario para que pueda ser utilizado por otras exportaciones futuras. El sistema lleva un registro histórico de todos los movimientos (no se borra nada) — la reversión se registra como un nuevo movimiento que neutraliza el efecto del original. - -### Qué hace el sistema paso a paso - -Primero verifica que ninguna de las partidas de esta importación esté siendo descargada activamente por otra exportación procesada. Si las hay, bloquea la reversión completamente — ver CU-EXP-009. - -Si no hay bloqueos, ejecuta cuatro acciones en orden: - -**1. Devuelve los valores a las importaciones origen.** Resta de cada partida de importación el valor que fue descargado al procesar. Si el material era temporal y la importación fue posterior al 31 de diciembre de 2014, también recalcula el IVA utilizado. - -**2. Desmarca las series.** Al procesar, las series de las partidas de importación quedaron marcadas como "ya exportadas". La reversión las desmarca para que vuelvan a estar disponibles. - -**3. Cancela los registros de descarga en el ledger.** El ledger de inventario nunca se modifica ni se borra. En su lugar, el sistema inserta nuevos movimientos de tipo **Retorno** que compensan exactamente los **Consumos** que se generaron al procesar. El saldo neto vuelve a ser el original. - -**4. Regresa la factura a Revertida.** Limpia los totales y cambia el estado. - -### Qué queda guardado - -La factura queda en estado **Revertida**. En el ledger quedan los movimientos originales de Consumo más los nuevos movimientos de Retorno que los neutralizan — el historial queda completo y auditable. Los saldos de las importaciones origen quedan como si la exportación nunca hubiera ocurrido. - -### Qué puede fallar - -| Situación | Qué hace el sistema | -|-----------|---------------------| -| Una partida está siendo descargada por otra exportación activa | Bloquea la reversión — ver CU-EXP-009 | -| La factura no estaba procesada | La rechaza indicando que no puede revertirse | - ---- - -## CU-EXP-009 · Reversión bloqueada por exportaciones activas - -### Descripción - -El usuario intenta revertir una factura de exportación con descarga, pero el sistema detecta que alguna de las partidas de importación origen **está siendo consumida actualmente por otra exportación procesada**. El sistema bloquea completamente la reversión. - -### Por qué existe este bloqueo - -Los saldos de inventario son compartidos entre exportaciones. Si se permitiera revertir una importación mientras otra exportación sigue activa sobre esos mismos saldos, el inventario quedaría en un estado inconsistente — con más saldo del que debería haber. El bloqueo garantiza que siempre se deshagan las exportaciones en orden inverso al que fueron procesadas. - -### Qué hace el sistema - -Recorre cada partida de la importación origen. Por cada una, busca si existe algún registro de descarga activo (con estado **Aplicado**) vinculado a una factura de exportación que sigue procesada. Si encuentra aunque sea uno, detiene todo el proceso y muestra un mensaje por cada exportación activa que está usando esos saldos, indicando exactamente qué factura y qué línea están involucradas. - -### Cómo lo resuelve el usuario - -Debe ir a cada factura de exportación indicada en el error y revertirla primero. Una vez que todas las exportaciones que consumían esos saldos estén revertidas, puede revertir la importación sin problemas. - -### Qué queda guardado - -Nada — el sistema no modifica ningún dato cuando bloquea la reversión. Todo queda exactamente igual que antes de intentar la operación. - ---- - -## CU-EXP-010 · Reversión bloqueada por Importación Definitiva existente - -### Descripción - -El usuario intenta revertir una factura de exportación AFIJO o SCRAP con cambio de régimen, pero el sistema detecta que **ya existe una Importación Definitiva (IMD) generada** a partir de esa exportación. El sistema bloquea la reversión. - -### Por qué existe este bloqueo - -La Importación Definitiva es un documento fiscal independiente con validez propia ante la aduana. Si se revierte la exportación sin eliminar primero la IMD, ese documento queda "flotando" sin ningún respaldo — una exportación que ya no existe lo generó. Eso crea una inconsistencia en los registros fiscales. - -### Qué hace el sistema - -Busca si existe en la base de datos una factura de tipo IMD cuyo número de factura coincida con el de la exportación que se quiere revertir. Si la encuentra, bloquea la reversión y le indica al usuario exactamente cuál es la IMD que debe eliminar primero, con su número de referencia. - -### Cómo lo resuelve el usuario - -Debe desactualizar y eliminar la Importación Definitiva indicada. Después puede revertir la exportación sin problema. - -### Qué queda guardado - -Nada — el sistema no modifica ningún dato cuando bloquea la reversión. - ---- - -## Resumen de los diez casos - -| # | Caso de uso | Tipo | Descarga inventario | Genera IMD | -|---|-------------|------|:-------------------:|:----------:| -| CU-EXP-001 | Procesar NODES | Proceso | No | No | -| CU-EXP-002 | Procesar DONAC | Proceso | Sí | No | -| CU-EXP-003 | Procesar AFIJO | Proceso | Sí | Solo si hay cambio de régimen | -| CU-EXP-004 | Procesar SCRAP | Proceso | Sí | Solo si hay cambio de régimen | -| CU-EXP-005 | Procesar REEXP | Proceso | Sí (solo desde DEF) | No | -| CU-EXP-006 | Procesar VEMEX | Proceso | Sí (solo desde DEF) | No | -| CU-EXP-007 | Revertir NODES | Reversión | No aplica | No | -| CU-EXP-008 | Revertir con descarga | Reversión | Devuelve saldos | No | -| CU-EXP-009 | Reversión bloqueada por exportación activa | Bloqueo | No (bloqueado) | No | -| CU-EXP-010 | Reversión bloqueada por IMD existente | Bloqueo | No (bloqueado) | No | diff --git a/backend/api/v1/modules/a76/invoices/exports/docs/REPO_exports_diagramas.md b/backend/api/v1/modules/a76/invoices/exports/docs/REPO_exports_diagramas.md deleted file mode 100644 index dcdc1dbe..00000000 --- a/backend/api/v1/modules/a76/invoices/exports/docs/REPO_exports_diagramas.md +++ /dev/null @@ -1,269 +0,0 @@ -# Diagramas de Flujo — Casos de Uso Exportaciones - -> Cada diagrama corresponde a un caso de uso en [`exports_cu_comprensibles.md`](./exports_cu_comprensibles.md). -> Formato: Mermaid — compatible con Notion, GitHub y VSCode. -> **Cómo usar en Notion:** bloque `/code` → lenguaje `mermaid` → pegar el contenido. - ---- - -## CU-EXP-001 · Procesar NODES - -```mermaid -flowchart TD - START([Usuario presiona\nActualizar Factura]) --> CHK1{¿La factura\nestá Pendiente?} - CHK1 -- No --> E1([❌ Error\nYa fue procesada]) - CHK1 -- Sí --> V1[Validar encabezado\nfecha · proveedor · destinatario\ntipo de cambio · pedimento] - V1 --> V2{¿Hay errores\nen el encabezado?} - V2 -- Sí --> E2([❌ Error\nCampo obligatorio faltante]) - V2 -- No --> V3[Verificar clases y fracciones\narancelarias en catálogo oficial] - V3 --> V4{¿Alguna clase\no fracción inválida?} - V4 -- Sí --> E3([❌ Error\nClase o fracción no existe]) - V4 -- No --> V5[Validar tipo de cambio\ncontra el catálogo del día] - V5 --> V6{¿TC de la factura\n== TC del catálogo?} - V6 -- No --> E4([❌ Error\nTipo de cambio incorrecto]) - V6 -- Sí --> V7[Marcar todas las partidas\ncomo sin descarga] - V7 --> V8[Calcular valores por partida\nCosto x Cantidad x TC\nen pesos · dólares · moneda cuenta] - V8 --> V9[Validar costo unitario · series · pesos] - V9 --> V10{¿Hay errores\nen partidas?} - V10 -- Sí --> E5([❌ Error\nCosto en cero o series faltantes]) - V10 -- No --> V11[Calcular totales de la factura\ncantidad · peso · valor MN · ME] - V11 --> FIN([✅ Factura PROCESADA\nSin cambios en inventario]) - - style FIN fill:#1D6B3C,color:#fff,rx:16 - style START fill:#1F4E79,color:#fff,rx:16 - style E1 fill:#C55A11,color:#fff,rx:16 - style E2 fill:#C55A11,color:#fff,rx:16 - style E3 fill:#C55A11,color:#fff,rx:16 - style E4 fill:#C55A11,color:#fff,rx:16 - style E5 fill:#C55A11,color:#fff,rx:16 -``` - ---- - -## CU-EXP-002 · Procesar con descarga de inventario (DONAC) - -```mermaid -flowchart TD - START([Usuario presiona\nActualizar Factura - DONAC]) --> VALID[Validaciones de encabezado\nclases · fracciones · TC · costos] - VALID --> ERR{¿Errores de\nvalidación?} - ERR -- Sí --> E1([❌ Error\nCorregir antes de continuar]) - ERR -- No --> DC1[Construir lista de descarga\n¿Qué se quiere exportar\ny de qué importación viene?] - DC1 --> DC2[Buscar importaciones origen\nVerificar que estén procesadas\ny con fecha anterior a la exportación] - DC2 --> DC3{¿Importación origen\nválida y procesada?} - DC3 -- No --> E2([❌ Error\nImportación no encontrada\no no procesada]) - DC3 -- Sí --> DC4[Calcular saldos disponibles\ncon criterio PEPS\nmás antiguo primero] - DC4 --> DC5{¿Hay saldo\nsuficiente?} - DC5 -- No --> E3([❌ Error\nSaldo insuficiente en\nla importación origen]) - DC5 -- Sí --> DC6[Distribuir cantidades\nentre lotes disponibles] - DC6 --> DC7[Verificación final\n¿Todo lo que se quiere exportar\nquedó cubierto?] - DC7 --> DC8{¿Alguna partida\nsin cubrir?} - DC8 -- Sí --> E4([❌ Error\nSaldo insuficiente\nen verificación final]) - DC8 -- No --> SAVE[Registrar la descarga\nen el ledger de inventario\nmovimiento CONSUMO por lote] - SAVE --> UPD[Actualizar valores retornados\nen las importaciones origen] - UPD --> FIN([✅ Factura PROCESADA\nSaldos de inventario reducidos]) - - style FIN fill:#1D6B3C,color:#fff,rx:16 - style START fill:#1F4E79,color:#fff,rx:16 - style E1 fill:#C55A11,color:#fff,rx:16 - style E2 fill:#C55A11,color:#fff,rx:16 - style E3 fill:#C55A11,color:#fff,rx:16 - style E4 fill:#C55A11,color:#fff,rx:16 -``` - ---- - -## CU-EXP-003 · Procesar AFIJO con cambio de régimen - -```mermaid -flowchart TD - START([Usuario presiona\nActualizar Factura - AFIJO]) --> CR{¿Tiene cambio\nde régimen?} - - CR -- No --> DISC[Ir a descarga\nde inventario normal] - DISC --> FIN_DISC([Ver CU-EXP-002\nflujo de descarga]) - - CR -- Sí --> PROC[Verificar procedencia\nde todas las partidas] - PROC --> CHK{¿Todas las\npartidas son TEM?} - CHK -- No --> E1([❌ Error\nAlguna partida tiene\nprocedencia DEF u otra]) - CHK -- Sí --> GEN{¿Se debe generar\nImportación Definitiva?} - GEN -- No --> DISC2[Ir a descarga normal] - GEN -- Sí --> IMD{¿Ya existe una IMD\npara esta factura?} - IMD -- Sí --> REUSE[Reutilizar la IMD existente\nno se crea duplicado] - IMD -- No --> CREATE[Generar nueva factura IMD\ncon los mismos datos del encabezado\ny copia de todas las partidas\nestado: Pendiente] - REUSE --> DISC3[Descarga de inventario\nver CU-EXP-002] - CREATE --> DISC3 - DISC2 --> DISC3 - DISC3 --> FIN([✅ Factura PROCESADA\nIMD generada en estado Pendiente\nSaldos de inventario reducidos]) - - style FIN fill:#1D6B3C,color:#fff,rx:16 - style START fill:#1F4E79,color:#fff,rx:16 - style E1 fill:#C55A11,color:#fff,rx:16 - style FIN_DISC fill:#2E75B6,color:#fff,rx:16 - style DISC2 fill:#BDD7EE,rx:8 - style DISC3 fill:#BDD7EE,rx:8 -``` - ---- - -## CU-EXP-004 · Procesar SCRAP - -```mermaid -flowchart TD - START([Usuario presiona\nActualizar Factura - SCRAP]) --> NOTE[Comportamiento idéntico a AFIJO\nSolo cambia la naturaleza del material:\nmaterial exportado como desperdicio o chatarra] - NOTE --> CR{¿Tiene cambio\nde régimen?} - CR -- No --> DISC[Descarga de inventario\nver CU-EXP-002] - CR -- Sí --> AFIJO[Flujo completo\nver CU-EXP-003\nVerifica TEM · Genera IMD · Descarga] - DISC --> FIN([✅ Factura PROCESADA]) - AFIJO --> FIN - - style FIN fill:#1D6B3C,color:#fff,rx:16 - style START fill:#1F4E79,color:#fff,rx:16 - style AFIJO fill:#BDD7EE,rx:8 - style DISC fill:#BDD7EE,rx:8 -``` - ---- - -## CU-EXP-005 · Procesar REEXP - -```mermaid -flowchart TD - START([Usuario presiona\nActualizar Factura - REEXP]) --> PROC[Verificar procedencia\nde todas las partidas] - PROC --> CHK{¿Todas las\npartidas son DEF?} - CHK -- No --> E1([❌ Error\nAlguna partida tiene\nprocedencia TEM u otra\nREEXP requiere procedencia DEF]) - CHK -- Sí --> DISC[Descarga de inventario\nver CU-EXP-002\nSaldos de importaciones DEF] - DISC --> FIN([✅ Factura PROCESADA\nSaldos de importaciones DEF reducidos]) - - style FIN fill:#1D6B3C,color:#fff,rx:16 - style START fill:#1F4E79,color:#fff,rx:16 - style E1 fill:#C55A11,color:#fff,rx:16 - style DISC fill:#BDD7EE,rx:8 -``` - ---- - -## CU-EXP-006 · Procesar VEMEX - -```mermaid -flowchart TD - START([Usuario presiona\nActualizar Factura - VEMEX]) --> NOTE[Empresa con régimen especial\nIMMEX o zona franca\nVenta virtual al extranjero] - NOTE --> PROC[Verificar procedencia\nde todas las partidas] - PROC --> CHK{¿Todas las\npartidas son DEF?} - CHK -- No --> E1([❌ Error\nVEMEX requiere procedencia DEF\nigual que REEXP]) - CHK -- Sí --> DISC[Descarga de inventario\nver CU-EXP-002] - DISC --> FIN([✅ Factura PROCESADA\nNo requiere document_type\nni agente aduanal]) - - style FIN fill:#1D6B3C,color:#fff,rx:16 - style START fill:#1F4E79,color:#fff,rx:16 - style E1 fill:#C55A11,color:#fff,rx:16 - style DISC fill:#BDD7EE,rx:8 -``` - ---- - -## CU-EXP-007 · Revertir NODES - -```mermaid -flowchart TD - START([Usuario presiona\nDesactualizar Factura - NODES]) --> CHK{¿La factura\nestá Procesada?} - CHK -- No --> E1([❌ Error\nNo se puede revertir\nlo que no fue procesado]) - CHK -- Sí --> CLEAN[Limpiar todos los valores calculados\ntotales MN · ME · cantidad · peso] - CLEAN --> STATUS[Regresar factura a estado\nRevertida] - STATUS --> FIN([✅ Factura REVERTIDA\nSin efectos en inventario\nNODES nunca tocó el ledger]) - - style FIN fill:#1D6B3C,color:#fff,rx:16 - style START fill:#1F4E79,color:#fff,rx:16 - style E1 fill:#C55A11,color:#fff,rx:16 -``` - ---- - -## CU-EXP-008 · Revertir con descarga (AFIJO, DONAC, SCRAP, REEXP, VEMEX) - -```mermaid -flowchart TD - START([Usuario presiona\nDesactualizar Factura]) --> CHK1{¿La factura\nestá Procesada?} - CHK1 -- No --> E1([❌ Error\nNo está procesada]) - CHK1 -- Sí --> BLOCK[Verificar si alguna partida\nestá siendo usada por\nuna exportación activa] - BLOCK --> CHK2{¿Hay exportaciones\nactivas usando\nestos saldos?} - CHK2 -- Sí --> E2([❌ Bloqueado\nver CU-EXP-009\nRevertir exportaciones primero]) - CHK2 -- No --> R1[1 · Devolver valores\na las importaciones origen\nrestar value_returned y recalcular IVA si aplica] - R1 --> R2[2 · Desmarcar series\nlas series de importación vuelven a estar\ndisponibles para futuras exportaciones] - R2 --> R3[3 · Cancelar registros de descarga\ninsertar movimientos RETORNO en el ledger\nel historial queda intacto] - R3 --> R4[4 · Regresar factura a Revertida\nlimpiar totales] - R4 --> FIN([✅ Factura REVERTIDA\nInventario restaurado\nHistorial completo y auditable]) - - style FIN fill:#1D6B3C,color:#fff,rx:16 - style START fill:#1F4E79,color:#fff,rx:16 - style E1 fill:#C55A11,color:#fff,rx:16 - style E2 fill:#C55A11,color:#fff,rx:16 -``` - ---- - -## CU-EXP-009 · Reversión bloqueada por exportaciones activas - -```mermaid -flowchart TD - START([Usuario intenta\nDesactualizar Factura]) --> SCAN[Recorrer cada partida\nde la importación origen] - SCAN --> FIND[Buscar registros de descarga\ncon estado APLICADO\nvinculados a exportaciones procesadas] - FIND --> CHK{¿Hay descargas\nactivas?} - CHK -- No --> OK([Reversión permitida\ncontinúa con CU-EXP-008]) - CHK -- Sí --> MSG[Mostrar mensaje de error\npor cada exportación activa\nindicando número de factura y línea] - MSG --> BLOCK([❌ Reversión BLOQUEADA\nEl usuario debe revertir primero\ncada exportación indicada]) - - style OK fill:#1D6B3C,color:#fff,rx:16 - style BLOCK fill:#C55A11,color:#fff,rx:16 - style START fill:#1F4E79,color:#fff,rx:16 -``` - ---- - -## CU-EXP-010 · Reversión bloqueada por Importación Definitiva existente - -```mermaid -flowchart TD - START([Usuario intenta\nDesactualizar Factura AFIJO o SCRAP\ncon cambio de régimen]) --> SEARCH[Buscar en la base de datos\nuna factura IMD con el mismo\nnúmero que la exportación] - SEARCH --> CHK{¿Existe\nuna IMD vinculada?} - CHK -- No --> OK([Reversión permitida\ncontinúa con CU-EXP-008]) - CHK -- Sí --> MSG[Mostrar mensaje de error\ncon el número de la IMD\nque debe eliminarse primero] - MSG --> BLOCK([❌ Reversión BLOQUEADA\nEliminar la IMD indicada\nluego intentar de nuevo]) - - style OK fill:#1D6B3C,color:#fff,rx:16 - style BLOCK fill:#C55A11,color:#fff,rx:16 - style START fill:#1F4E79,color:#fff,rx:16 -``` - ---- - -## Visión general — todos los tipos de exportación - -```mermaid -flowchart LR - subgraph PROCESAR ["⬆️ PROCESAR"] - NODES[NODES\nSin descarga] - DONAC[DONAC\nDescarga directa] - AFIJO[AFIJO\nDescarga + IMD opcional] - SCRAP[SCRAP\nDescarga + IMD opcional] - REEXP[REEXP\nProcedencia DEF] - VEMEX[VEMEX\nProcedencia DEF] - end - - subgraph REVERTIR ["⬇️ REVERTIR"] - RNODES[Revertir NODES\nSolo limpia valores] - RDESC[Revertir con descarga\nDevuelve saldos + cancela ledger] - end - - subgraph BLOQUEOS ["🚫 BLOQUEOS"] - BEXP[Bloqueada por\nexportación activa] - BIMD[Bloqueada por\nIMD existente] - end - - NODES --> RNODES - DONAC & AFIJO & SCRAP & REEXP & VEMEX --> RDESC - RDESC --> BEXP - AFIJO & SCRAP --> BIMD - - style PROCESAR fill:#E2EFDA,rx:8 - style REVERTIR fill:#DEEAF1,rx:8 - style BLOQUEOS fill:#FCE4D6,rx:8 -``` diff --git a/backend/api/v1/modules/a76/invoices/exports/process/main_process.py b/backend/api/v1/modules/a76/invoices/exports/process/main_process.py deleted file mode 100644 index 33c96e3c..00000000 --- a/backend/api/v1/modules/a76/invoices/exports/process/main_process.py +++ /dev/null @@ -1,137 +0,0 @@ -import logging -logger = logging.getLogger(__name__) -from datetime import datetime -from decimal import Decimal -from typing import List - -from sqlalchemy.orm import Session - -from api.v1.modules.a76.invoices.models import InvoiceHeader, InvoiceStatus -from api.v1.modules.a76.general_catalogs.exchange_rate.models import ExchangeRate -from core.exceptions import ErrorCollector -from .pre_validators import pre_validators -from .sub_process.review_exchange_rate import review_exchange_rate -from .sub_process.review_class import review_class -from .sub_process.review_qty_vs_weight import review_qty_vs_weight -from .sub_process.review_unit_cost import review_unit_cost -from .sub_process.series.review_qty_series import review_qty_series -from .sub_process.assign_values import assign_values -from .sub_process.assign_no_discharges import assign_no_discharges_items, assign_no_discharges_series - -# Parameter Service -from api.v1.modules.a76.app_settings.service import AppSettingsService - - -from .sub_process.finalize_invoice import finalize_invoice_no_discharge, finalize_invoice_with_discharge -from .sub_process.download_balance_collector import collect_lines_to_discharge -from .sub_process.fill_available_balances import fill_available_balances -from .sub_process.compare_balances import compare_balances -from .sub_process.verify_consolidated import verify_consolidated - -def pre_process(db: Session, invoice: InvoiceHeader, tenant_id: str, company_id: str, errors: ErrorCollector) -> list: - """Validaciones previas y obtención de partidas.""" - lines = pre_validators(db, invoice, tenant_id, company_id, errors) - if not lines: - errors.add_error(field="line_items", message="La factura debe contener partidas.", solution=["Agregue partidas."], code="NO_LINE_ITEMS") - return lines - - -def main_process(db: Session, invoice: InvoiceHeader, tenant_id: str, company_id: str, username: str = "SYSTEM") -> dict: - """Proceso principal para facturas de exportación.""" - errors = ErrorCollector() - lines = pre_process(db, invoice, tenant_id, company_id, errors) - errors.raise_if_errors() - - review_class(db, lines, errors) - - # REVISA_CANT_KG (KGS) and REVISA_CANT_LB (LBS) - review_qty_vs_weight(lines, "KGS", errors) - review_qty_vs_weight(lines, "LBS", errors) - - # REVISA_LINEA_COSTO - review_unit_cost(lines, errors) - - review_qty_series(db, invoice, lines, tenant_id, company_id, errors) - errors.raise_if_errors() - - settings = AppSettingsService.get_resolved_settings(db, int(tenant_id), int(company_id)) - q_settings = settings.get("qsisgen", {}) - s_settings = settings.get("ssisgen", {}) - - cal_val_base_tc = int(q_settings.get("calvalbasetcpedexpo") or s_settings.get("calvalbasetcpedexpo", 0)) - act_seguridad = int(q_settings.get("actseguridad") or s_settings.get("actseguridad") or 0) - logger.info(f"AUDIT_DEBUG: act_seguridad resolve result = {act_seguridad} for Invoice={invoice.invoice_number}") - - # El TC ahora se resuelve dentro de assign_values (para per-line) - # o dentro de _assign_invoice_totals (para base-pedimento-global). - # Sin embargo, para mantener compatibilidad con validaciones intermedias, lo dejamos aquí también: - exchange_rate = invoice.financials.exchange_rate if invoice.financials else 0 - which_exchange_rate = "TCFAC" - - if cal_val_base_tc == 1: - if invoice.compliance_mx and invoice.compliance_mx.pedimento: - ped = invoice.compliance_mx.pedimento - if ped.pedimento_dates and ped.pedimento_dates.payment_date: - payment_date = ped.pedimento_dates.payment_date - from sqlalchemy import select - stmt = select(ExchangeRate).where( - ExchangeRate.tenant_id == int(tenant_id), - ExchangeRate.company_id == int(company_id), - ExchangeRate.date == payment_date.date()) - ex_rate_row = db.execute(stmt).scalar_one_or_none() - if ex_rate_row: - exchange_rate = float(ex_rate_row.value) - which_exchange_rate = "TCPED" - - if invoice.compliance_mx: invoice.compliance_mx.which_exchange_rate = which_exchange_rate - if invoice.financials: invoice.financials.exchange_rate = exchange_rate - db.flush() - - review_exchange_rate(db, invoice, cal_val_base_tc, errors) - errors.raise_if_errors() - - savepoint = db.begin_nested() - try: - invoice_type = (invoice.invoice_type or "").strip().upper() - - # 1. Lógica específica de NODES - if invoice_type == "NODES": - assign_no_discharges_items(lines, errors) - assign_no_discharges_series(db, lines, errors) - - # 2. Asignar valores (costos/pesos) a las partidas - assign_values(db, invoice, lines, tenant_id, company_id, cal_val_base_tc, errors) - errors.raise_if_errors() - - # 3. Finalización (Límites, TC global, Auditoría, Descargas A24) - if invoice_type == "NODES": - finalize_invoice_no_discharge(db, invoice, lines, tenant_id, company_id, errors, username=username) - else: - # Lógica de descarga PEPS - to_discharge = collect_lines_to_discharge(db, invoice, lines, errors) - errors.raise_if_errors() - - if to_discharge: - fill_available_balances(db, invoice, to_discharge, errors) - errors.raise_if_errors() - - compare_balances(db, invoice, to_discharge, errors) - errors.raise_if_errors() - - verify_consolidated(db, invoice, to_discharge, errors) - errors.raise_if_errors() - - finalize_invoice_with_discharge( - db, invoice, lines, tenant_id, company_id, errors, - to_discharge=to_discharge, username=username - ) - - errors.raise_if_errors() - savepoint.commit() - - except Exception as e: - savepoint.rollback() - raise e - - db.flush() - return {"status": "success", "invoice_id": str(invoice.id)} diff --git a/backend/api/v1/modules/a76/invoices/exports/process/pre_validators.py b/backend/api/v1/modules/a76/invoices/exports/process/pre_validators.py deleted file mode 100644 index 2112076f..00000000 --- a/backend/api/v1/modules/a76/invoices/exports/process/pre_validators.py +++ /dev/null @@ -1,147 +0,0 @@ -from sqlalchemy import func -from sqlalchemy.orm import Session, joinedload -from api.v1.modules.a76.invoices.models import InvoiceHeader, InvoiceStatus -from api.v1.modules.a76.items.models import LineItem -from api.v1.modules.a76.parts.models import Part -from api.v1.modules.a76.general_catalogs.fractions.warning_fractions.models import WarningFraction -from api.v1.modules.a76.clients_and_providers.models import ClientProvider -from api.v1.modules.a76.app_settings.service import AppSettingsService -from core.exceptions import ErrorCollector -from api.v1.modules.a76.invoices.common.common_validators import validate_invoice_items_decimals - -def pre_validators(db: Session, invoice: InvoiceHeader, tenant_id: str, company_id: str, errors: ErrorCollector): - if invoice.status == InvoiceStatus.PROCESSED: - errors.add_error( - "status", - "La factura ya fue procesada y no puede volver a actualizarse. Desactualícela primero.", - solution=["Use el botón 'Desactualizar' antes de volver a procesar la factura."], - code="ALREADY_PROCESSED", - value=invoice.status, - ) - errors.raise_if_errors() - return - - if not invoice.invoice_date: - errors.add_required_error("invoice_date") - - if invoice.invoice_type != "VEMEX": - if not invoice.document_type: - errors.add_required_error("document_type") - - if not invoice.compliance_mx.provider_id: - errors.add_required_error("compliance_mx.provider_id") - - if not invoice.compliance_mx.sold_to_id: - errors.add_required_error("compliance_mx.sold_to_id") - - if not invoice.compliance_mx.shipped_to_id: - errors.add_required_error("compliance_mx.shipped_to_id") - else: - shipped_to_exists = db.query(ClientProvider).filter( - ClientProvider.id == invoice.compliance_mx.shipped_to_id, - ClientProvider.tenant_id == tenant_id, - ClientProvider.company_id == company_id, - ).first() - if not shipped_to_exists: - errors.add_error( - field="compliance_mx.shipped_to_id", - message="El Destinatario no existe en el Catálogo de Clientes y Proveedores.", - solution=["Verifica el ID del Destinatario", "Revisa el catálogo"], - code="NOT_FOUND", - value=invoice.compliance_mx.shipped_to_id, - ) - if not shipped_to_exists.address or not shipped_to_exists.address.country: - errors.add_error( - field="compliance_mx.shipped_to_id", - message="El Destinatario no tiene capturado el pais.", - solution=["Captura el pais de envío del Destinatario", "Revisa el catálogo"], - code="MISSING_COUNTRY", - value=invoice.compliance_mx.shipped_to_id, - ) - - if invoice.invoice_type != "VEMEX": - if not invoice.compliance_mx.customs_broker_id: - errors.add_required_error("compliance_mx.customs_broker_id") - - - if not invoice.financials.exchange_rate or invoice.financials.exchange_rate <= 0: - errors.add_range_error( - "financials.exchange_rate", - min_value=0.0001, - ) - - if not invoice.financials.currency: - errors.add_required_error("El Tipo de Moneda esta vacio no se puede actualizar") - elif invoice.financials.currency == "manual" and not invoice.financials.currency_type: - errors.add_required_error("financials.currency_type") - - # Validación de estatus de pedimento - # Validation of Pedimento status (CERRADO / PAGADO) - settings = AppSettingsService.get_resolved_settings(db, int(tenant_id), int(company_id)) - expo_params = settings.get("ssisexpo", {}) - # Resolver validarestatusped (podría estar en ssisexpo o qsisgen según el tipo de factura) - valida_estatus = int(expo_params.get("validarestatusped", 0)) - - if valida_estatus == 1: - if invoice.compliance_mx and invoice.compliance_mx.pedimento: - ped = invoice.compliance_mx.pedimento - if ped.pedimento_dates and ped.pedimento_dates.payment_date: - errors.add_error( - field="compliance_mx.pedimento", - message=f"No se puede procesar la factura porque el pedimento {ped.pedimento_number} ya se encuentra pagado el {ped.pedimento_dates.payment_date.date()}.", - solution=["Desactive el parámetro 'validarestatusped' o rectifique el pedimento si requiere cambios."], - code="PEDIMENTO_ALREADY_PAID" - ) - errors.raise_if_errors() - - # 3.- Validacion que deber de existir un pedimento cuando es requerido - if not invoice.compliance_mx.is_pedimento_pending and not invoice.compliance_mx.pedimento_id: - errors.add_required_error("compliance_mx.pedimento_number") - - # 4.- Verificacion de que existan partidas para la factura, si no hay partidas no se puede procesar - item_count = ( - db.query(func.count(LineItem.id)) - .filter( - LineItem.invoice_id == invoice.id, - LineItem.tenant_id == invoice.tenant_id, - LineItem.company_id == invoice.company_id, - ) - .scalar() - ) - if item_count == 0: - errors.add_error( - field="items", - message="La factura no tiene partidas capturadas.", - solution=["Capture al menos una partida antes de procesar la factura."], - code="NO_ITEMS_FOUND", - ) - - # Advertencias para las fracciones y su horario - lines = ( - db.query(LineItem) - .options( - joinedload(LineItem.fa_data), - joinedload(LineItem.unit_of_measure_info), - joinedload(LineItem.quantity), - joinedload(LineItem.part_info).joinedload(Part.unit_of_measure_info) - ) - .filter( - LineItem.invoice_id == invoice.id, - LineItem.tenant_id == tenant_id, - LineItem.company_id == company_id, - ) - .all() - ) - - # Validar decimales en piezas (Parámetro validadecencant) - validate_invoice_items_decimals(db, lines, int(tenant_id), int(company_id), errors) - - return lines - - - - - - - - \ No newline at end of file diff --git a/backend/api/v1/modules/a76/invoices/exports/process/routes.py b/backend/api/v1/modules/a76/invoices/exports/process/routes.py deleted file mode 100644 index 4014ae1e..00000000 --- a/backend/api/v1/modules/a76/invoices/exports/process/routes.py +++ /dev/null @@ -1,85 +0,0 @@ -from typing import Any, Dict - -from fastapi import APIRouter, Depends, Query -from sqlalchemy.orm import Session - -from core.celery_app import celery_app -from core.database import get_core_db -from core.security import get_current_user, validate_access_to_resource -from api.v1.modules.core.tasks_tracking import track_and_dispatch - -from .task import process_export_invoice_task - -router = APIRouter() - - -@router.post("/invoices/{invoice_id}/process") -def trigger_invoice_process( - invoice_id: int, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """ - Inicia el procesamiento de una factura de exportación como tarea Celery. - Retorna el task_id para hacer polling del progreso. - """ - tenant_id = validate_access_to_resource(db, company_id, current_user) - - username = current_user.get("preferred_username") or current_user.get("email") or current_user.get("sub") or "SYSTEM" - - task = track_and_dispatch( - db=db, - task=process_export_invoice_task, - tenant_id=tenant_id, - company_id=company_id, - requested_by_user=username, - task_name="process_export_invoice_task", - task_group="invoices", - task_origin="a76/invoices/exports/process", - args=[invoice_id, str(tenant_id), str(company_id), username], - ) - - return {"task_id": task.id} - - -@router.get("/invoices/process/{task_id}/status") -def get_invoice_process_status(task_id: str): - """ - Consulta el estado de progreso de una tarea de procesamiento de factura. - - Retorna: - - state: 'PROCESSING' | 'SUCCESS' | 'FAILURE' - - info: { current: int, status: str } (cuando state == 'PROCESSING') - - result: dict (cuando state == 'SUCCESS' o 'FAILURE') - """ - task_result = celery_app.AsyncResult(task_id) - - if task_result.state in ("PENDING", "STARTED"): - return { - "state": "PROCESSING", - "info": {"current": 0, "status": "Iniciando..."}, - } - - if task_result.state == "PROGRESS": - return { - "state": "PROCESSING", - "info": task_result.info or {"current": 0, "status": "Procesando..."}, - } - - if task_result.state == "SUCCESS": - return { - "state": "SUCCESS", - "result": task_result.result, - } - - error_info = task_result.result - if isinstance(error_info, Exception): - error_msg = str(error_info) - else: - error_msg = str(error_info) if error_info else "Error desconocido" - - return { - "state": "FAILURE", - "result": error_msg, - } diff --git a/backend/api/v1/modules/a76/invoices/exports/process/sub_process/__init__.py b/backend/api/v1/modules/a76/invoices/exports/process/sub_process/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/backend/api/v1/modules/a76/invoices/exports/process/sub_process/assign_no_discharges.py b/backend/api/v1/modules/a76/invoices/exports/process/sub_process/assign_no_discharges.py deleted file mode 100644 index 8505fcb2..00000000 --- a/backend/api/v1/modules/a76/invoices/exports/process/sub_process/assign_no_discharges.py +++ /dev/null @@ -1,82 +0,0 @@ -from typing import List - -from sqlalchemy.orm import Session - -from api.v1.modules.a76.invoices.models import InvoiceHeader -from api.v1.modules.a76.items.models import LineItem -from api.v1.modules.a76.items.series.models import Serie -from core.exceptions import ErrorCollector - - -def assign_no_discharges_items( - lines: List[LineItem], - errors: ErrorCollector, -) -> None: - """ - ASIGNA_NODESCARGA_PARTIDAS - Sets ``discharge = False`` on every line item of the invoice. - Used exclusively by invoice type NODES (no discharge). - - Legacy equivalent - ----------------- - UPDATE QEqeMaq SET Descarga = 0 - FROM QEqeMaq - WHERE Consecutivo = - """ - try: - for line in lines: - line.fa_data.discharge = False - except Exception as exc: - errors.add_error( - field="items.discharge", - message="Error al asignar No-Descarga en las partidas de exportación.", - solution=["Verifique la integridad de las partidas de la factura."], - code="ASSIGN_NO_DISCHARGE_ITEMS_ERROR", - value=str(exc), - ) - - -def assign_no_discharges_series( - db: Session, - lines: List[LineItem], - errors: ErrorCollector, -) -> None: - """ - ASIGNA_NODESCARGA_SERIES - Sets ``marca = False`` on every ``Serie`` row whose parent ``LineItem`` - has ``discharge = False`` (or ``discharge`` is ``None``). - - Legacy equivalent - ----------------- - UPDATE QSeriesExpo - SET Marca = 0 - FROM QSeriesExpo SerExpo - LEFT JOIN QEqeMaq EqiPex - ON EqiPex.Consecutivo = SerExpo.Consecutivo - AND EqiPex.LineaExpo = SerExpo.LineaExpo - WHERE SerExpo.Consecutivo = - AND EqiPex.Descarga = 0 - """ - try: - no_discharge_line_ids = { - line.id - for line in lines - if not line.fa_data.discharge - } - - if not no_discharge_line_ids: - return - - ( - db.query(Serie) - .filter(Serie.line_item_id.in_(no_discharge_line_ids)) - .update({"discharge": False}, synchronize_session="fetch") - ) - except Exception as exc: - errors.add_error( - field="series.discharge", - message="Error al asignar No-Descarga en las series de exportación.", - solution=["Verifique la integridad de las series de la factura."], - code="ASSIGN_NO_DISCHARGE_SERIES_ERROR", - value=str(exc), - ) \ No newline at end of file diff --git a/backend/api/v1/modules/a76/invoices/exports/process/sub_process/assign_values.py b/backend/api/v1/modules/a76/invoices/exports/process/sub_process/assign_values.py deleted file mode 100644 index 8a289d37..00000000 --- a/backend/api/v1/modules/a76/invoices/exports/process/sub_process/assign_values.py +++ /dev/null @@ -1,255 +0,0 @@ -""" -ASIGNA_VALORES_PARTIDAS_ASIGNA_PESOS -Resets and recalculates unit costs, export values -(KGS ↔ LBS) for every line item of an export invoice. - -Two cost-assignment strategies (controlled by SisExp:ValFactTC): - TCE → bulk SQL UPDATE using the invoice-level exchange rate (Loc:TipoCambio). - else → per-line loop that resolves each line's exchange rate from its - source import invoice (TEM → QFacImp, DEF → QFacImpDef). - -After costs are assigned the routine always: - 1. Calls REVISA_UMA for each line. -""" - -from decimal import Decimal -from typing import List - -from sqlalchemy.orm import Session - -from api.v1.modules.a76.invoices.models import Currency, InvoiceHeader -from api.v1.modules.a76.invoices.common.process.review_uma import revisa_uma -from api.v1.modules.a76.items.models import LineItem -from core.exceptions import ErrorCollector - -_KGS_TO_LBS = Decimal("2.204624") - - -# --------------------------------------------------------------------------- -# Internal helpers -# --------------------------------------------------------------------------- - -def _assign_costs_tce( - lines: List[LineItem], - currency: Currency, - tc: Decimal, - tc_mm: Decimal, -) -> None: - """ - Bulk-style cost assignment when SisExp:ValFactTC = 'TCE'. - Uses the single invoice-level exchange rate for all lines. - - Legacy equivalent (branch 1 of the IF SisExp:ValFactTC): - UPDATE QEqeMaq SET CostoUnitarioDlls = ..., CostoUnitarioPesos = ..., - ValorExpoMN = ..., ValorExpoME = ..., ValorExpoMC = ... - WHERE Consecutivo = - """ - for line in lines: - if line.financial is None or line.quantity is None: - continue - - capture = line.financial.unit_cost_capture or Decimal(0) - qty = line.quantity.quantity or Decimal(0) - - if currency == Currency.FOREIGN: # ME - line.financial.unit_cost_usd = capture - line.financial.unit_cost_mxn = capture * tc - line.financial.value_mxn = qty * capture * tc - line.financial.value_usd = qty * capture - line.financial.value_mc = qty * capture - - elif currency == Currency.LOCAL: # MN - line.financial.unit_cost_mxn = capture - line.financial.unit_cost_usd = (capture / tc) if tc else Decimal(0) - line.financial.value_mxn = qty * capture - line.financial.value_usd = (qty * capture / tc) if tc else Decimal(0) - line.financial.value_mc = qty * capture - - elif currency == Currency.MANUAL: # MC - cost_usd = capture * tc_mm - line.financial.unit_cost_usd = cost_usd - line.financial.unit_cost_mxn = cost_usd * tc - line.financial.value_mxn = qty * cost_usd * tc - line.financial.value_usd = qty * cost_usd - line.financial.value_mc = qty * capture - - -def _assign_costs_per_line( - db: Session, - invoice: InvoiceHeader, - lines: List[LineItem], - currency: Currency, - tc_mm: Decimal, - errors: ErrorCollector, -) -> None: - """ - Per-line cost assignment when SisExp:ValFactTC != 'TCE'. - Each line resolves the exchange rate from its source import invoice - (TEM → QFacImp header, DEF → QFacImpDef header). - - Legacy equivalent (ELSE branch – LOOP QEqeMaq): - If TipoMovImpo = 'TEM' → ACCESS:QFacImp.TryFetch(EqiFim:FKFacturaImpo) - Else → ACCESS:QFacImpDef.TryFetch(EqiFID:FKFacImpoDef) - then assign CostoUnitarioDlls / CostoUnitarioPesos / ValorExpoMN/ME/MC - """ - from api.v1.modules.a76.invoices.models import InvoiceHeader as InvHeader - - for line in lines: - if line.financial is None or line.quantity is None: - continue - - capture = line.financial.unit_cost_capture or Decimal(0) - qty = line.quantity.quantity or Decimal(0) - - # Resolve the exchange rate from the source import invoice - line_tc = _get_source_invoice_tc(db, invoice, line, errors) - - if currency == Currency.FOREIGN: # ME - line.financial.unit_cost_usd = capture - line.financial.unit_cost_mxn = capture * line_tc - - elif currency == Currency.LOCAL: # MN - line.financial.unit_cost_usd = (capture / line_tc) if line_tc else Decimal(0) - line.financial.unit_cost_mxn = capture - - elif currency == Currency.MANUAL: # MC - cost_usd = capture * tc_mm - line.financial.unit_cost_usd = cost_usd - line.financial.unit_cost_mxn = cost_usd * line_tc - - # Values are always: cost x qty - line.financial.value_mxn = (line.financial.unit_cost_mxn or Decimal(0)) * qty - line.financial.value_usd = (line.financial.unit_cost_usd or Decimal(0)) * qty - line.financial.value_mc = capture * qty - - -def _get_source_invoice_tc( - db: Session, - invoice: InvoiceHeader, - line: LineItem, - errors: ErrorCollector, -) -> Decimal: - """ - Returns the exchange rate of the import invoice linked to this export line. - - Movement type 'TEM' → look up QFacImp (temporary import header). - Any other type → look up QFacImpDef (definitive import header). - - Falls back to the export invoice's own exchange rate if the source invoice - is not found, and records a warning-level error. - - Legacy fields: - EqiPex:TipoMovImpo → line.customs.origin_procedure - EqiPex:FacturaImpo → line.fa_data.search_invoice - """ - fallback_tc = Decimal(str(invoice.financials.exchange_rate or 0)) - - movement_type = (line.customs.origin_procedure or "").strip().upper() if line.customs else "" - import_invoice_number = (line.fa_data.search_invoice if getattr(line, "fa_data", None) else None) or "" - - if not import_invoice_number: - return fallback_tc - - from api.v1.modules.a76.invoices.models import InvoiceHeader as InvHeader - - if movement_type == "TEM": - source = ( - db.query(InvHeader) - .filter( - InvHeader.invoice_number == import_invoice_number, - InvHeader.tenant_id == invoice.tenant_id, - InvHeader.company_id == invoice.company_id, - ) - .first() - ) - else: - # DEF: definitive import - source = ( - db.query(InvHeader) - .filter( - InvHeader.invoice_number == import_invoice_number, - InvHeader.tenant_id == invoice.tenant_id, - InvHeader.company_id == invoice.company_id, - ) - .first() - ) - - if source is None or source.financials is None: - errors.add_error( - field=f"line[{line.line_number}].import_invoice", - message=( - f"No se encontró la factura de importación '{import_invoice_number}' " - f"referenciada en la partida {line.line_number}." - ), - solution=[ - "Verificar el número de factura de importación en la partida.", - ], - code="SOURCE_INVOICE_NOT_FOUND", - ) - return fallback_tc - - return Decimal(str(source.financials.exchange_rate or 0)) or fallback_tc - -# --------------------------------------------------------------------------- -# Main entry point -# --------------------------------------------------------------------------- - -def assign_values( - db: Session, - invoice: InvoiceHeader, - lines: List[LineItem], - tenant_id: str, - company_id: str, - cal_val_base_tc: int, - errors: ErrorCollector, -) -> None: - """ - ASIGNA_VALORES_PARTIDAS_ASIGNA_PESOS - - 1. Assigns unit costs and export values to every line (ME / MN / MC). - Strategy A (TCE): bulk assignment using the invoice exchange rate. - Strategy B (per-line): resolves exchange rate per source import invoice. - 2. Calls REVISA_UMA for each line. - """ - currency = invoice.financials.currency - tc = Decimal(str(invoice.financials.exchange_rate or 0)) - tc_mm = Decimal(str(invoice.financials.exchange_rate_mm or 0)) - - - # --- Step 1: Assign costs / values --------------------------------------- - # Resolve Settings for Strategy - from api.v1.modules.a76.app_settings.service import AppSettingsService - settings = AppSettingsService.get_resolved_settings(db, int(tenant_id), int(company_id)) - - inv_type = (invoice.invoice_type or "").strip().upper() - op_type = "exp" - - # Hierarchical resolve: invoices.types.exp.{type}.ssisgen.ssimpFormData - inv_map = settings.get("invoices", {}).get("types", {}).get(op_type, {}).get(inv_type, {}) - form_data = inv_map.get("ssisgen", {}).get("ssimpFormData", {}) or inv_map.get("qsisgen", {}).get("ssimpFormData", {}) or settings.get("ssisexpo", {}) - - # Parameter ValFactTC: TCE (Encabezado) vs PER_LINE (Partida) - val_fact_tc = (form_data.get("valfacttc") or form_data.get("ValFactTC") or "").strip().upper() - - # Fallback/Auto-resolve: - # Si la configuración indica usar el TC del Pedimento (cal_val_base_tc = 1), - # usualmente forzamos TCE porque el encabezado ya fue nivelado al pago del pedimento. - if cal_val_base_tc == 1: - val_fact_tc = "TCE" - - # Default if empty - if not val_fact_tc: - val_fact_tc = "PER_LINE" - - if val_fact_tc == "TCE": - _assign_costs_tce(lines, currency, tc, tc_mm) - else: - _assign_costs_per_line(db, invoice, lines, currency, tc_mm, errors) - - # --- Step 2: REVISA_UMA -------------------------------------------------- - for line in lines: - revisa_uma(db=db, line=line, tenant_id=tenant_id, company_id=company_id, errors=errors) - - # --- Step 3: Assign weights ---------------------------------------------- - # In anexo76 will be calculated in realtime based in weight type by conversion factor - diff --git a/backend/api/v1/modules/a76/invoices/exports/process/sub_process/compare_balances.py b/backend/api/v1/modules/a76/invoices/exports/process/sub_process/compare_balances.py deleted file mode 100644 index 0a9ec622..00000000 --- a/backend/api/v1/modules/a76/invoices/exports/process/sub_process/compare_balances.py +++ /dev/null @@ -1,165 +0,0 @@ -""" -COMPARA_SALDOS_POR_FACTURA -Compares the discharge queue (QueADescargar) against the available PEPS lots -(QSaldoActual) and distributes the quantity to discharge across the available -lots, updating ``entry.quantity_used`` and ``lot.available_qty`` accordingly. - -Also validates that the unit of measure on the export line matches the one -on the import lot. - -If after consuming all available lots a discharge entry still has remaining -quantity, no explicit error is raised here — the caller (compare_balances) -detects this and reports an insufficient-balance error. - -Legacy mapping --------------- -QADesc:Cantidad → entry.quantity -QADesc:CantUsada → entry.quantity_used -QSaldo:Cantidad → lot.available_qty (net balance from ledger) -QSaldo:CantUsada → lot_used (tracked locally; lots are mutated in-place) -QADesc:UniMed → entry.unit_of_measure -QSaldo:UniMed → resolved from import line (stored on AvailableLot via uom) -""" - -from decimal import Decimal -from typing import List, Optional - -from sqlalchemy import select -from sqlalchemy.orm import Session - -from api.v1.modules.a76.invoices.models import InvoiceHeader -from api.v1.modules.a76.items.models import LineItem -from core.exceptions import ErrorCollector -from .discharge_types import AvailableLot, DownloadEntry - - -# --------------------------------------------------------------------------- -# Private helpers -# --------------------------------------------------------------------------- - -def _resolve_import_uom(db: Session, import_item_line_id: int) -> Optional[str]: - """ - Returns the unit-of-measure code of the import line (QSaldo:UniMed). - Equivalent to EqiPim:UnidadMedida resolved via the import LineItem. - """ - from api.v1.modules.a76.general_catalogs.units_of_measure.models import UnitOfMeasure - - row = db.execute( - select(LineItem.unit_of_measure).where(LineItem.id == import_item_line_id) - ).scalar_one_or_none() - - if row is None: - return None - - uom = db.get(UnitOfMeasure, row) - return uom.code if uom else None - - -# --------------------------------------------------------------------------- -# Public entry point -# --------------------------------------------------------------------------- - -def compare_balances( - db: Session, - export_invoice: InvoiceHeader, - to_discharge: List[DownloadEntry], - errors: ErrorCollector, -) -> None: - """ - COMPARA_SALDOS_POR_FACTURA - For each discharge entry distributes the quantity to discharge across the - available PEPS lots attached to the entry by ``fill_available_balances``. - - Mutates ``entry.quantity_used`` and ``lot.available_qty`` in-place. - After this call, ``entry.quantity_used`` should equal ``entry.quantity`` - for every entry; if not, there is insufficient balance. - - Parameters - ---------- - db : active SQLAlchemy session - export_invoice : the export invoice being processed - to_discharge : list of DownloadEntry objects populated by - fill_available_balances (entry.available_lots must be set) - errors : shared error collector - """ - # Sort mirrors Clarion: - # Sort(QueADescargar, -Procedencia, FacturaImpo, LineaImpo) - # Sort(QSaldoActual, -Procedencia, FacturaImpo, LineaImpo) - sorted_entries = sorted( - to_discharge, - key=lambda e: (e.origin_procedure, e.import_invoice, e.import_line), - reverse=True, - ) - - for entry in sorted_entries: - if not entry.available_lots: - # No lots were found for this entry — balance check will catch it - continue - - # ── Validate unit of measure matches the import lot ─────────────────── - first_lot = entry.available_lots[0] - import_uom = _resolve_import_uom(db, first_lot.import_item_line_id) - - if import_uom and entry.unit_of_measure and import_uom != entry.unit_of_measure: - errors.add_error( - field=f"line[{entry.export_line}].unit_of_measure", - message=( - f"La U.M.: '{entry.unit_of_measure}' de la partida: {entry.export_line} " - f"es diferente a la U.M: '{import_uom}' registrada en importación." - ), - solution=["Revisar la Partida de Exportación y cambiar la Unidad de Medida."], - code="UOM_MISMATCH", - value={ - "export_line": entry.export_line, - "export_uom": entry.unit_of_measure, - "import_uom": import_uom, - }, - ) - continue - - # ── Distribute quantity across available lots (PEPS order) ──────────── - # Lots are already ordered by order_peps (oldest first) from - # fill_available_balances; sort defensively here too. - sorted_lots: List[AvailableLot] = sorted( - entry.available_lots, key=lambda lot: lot.order_peps - ) - - for lot in sorted_lots: - remaining_entry = entry.quantity - entry.quantity_used - remaining_lot = lot.available_qty - - if remaining_entry <= 0: - break # Entry fully satisfied - - if remaining_lot <= 0: - continue # Lot exhausted — try next - - # Consume as much as possible from this lot - consume = min(remaining_entry, remaining_lot) - - entry.quantity_used += consume - lot.available_qty -= consume - lot.consumed_qty += consume - - # ── Check if the entry was fully satisfied ──────────────────────────── - if entry.quantity_used < entry.quantity: - shortage = entry.quantity - entry.quantity_used - errors.add_error( - field=f"line[{entry.export_line}].quantity", - message=( - f"Saldo insuficiente para la partida: {entry.export_line}. " - f"Se requieren {entry.quantity} y solo hay {entry.quantity_used} disponibles " - f"(faltan {shortage})." - ), - solution=[ - "Verificar el saldo disponible de la Factura de Importación.", - "Reducir la cantidad a descargar.", - ], - code="INSUFFICIENT_BALANCE", - value={ - "export_line": entry.export_line, - "required": str(entry.quantity), - "available": str(entry.quantity_used), - "shortage": str(shortage), - }, - ) diff --git a/backend/api/v1/modules/a76/invoices/exports/process/sub_process/discharge_types.py b/backend/api/v1/modules/a76/invoices/exports/process/sub_process/discharge_types.py deleted file mode 100644 index e8dbca22..00000000 --- a/backend/api/v1/modules/a76/invoices/exports/process/sub_process/discharge_types.py +++ /dev/null @@ -1,73 +0,0 @@ -""" -Shared dataclasses for the export invoice discharge process. - -Kept in a standalone module (no local imports) so that -download_balance_collector, review_series_exist, review_series_other_lines -and fill_available_balances can all import from here without circular deps. -""" - -from dataclasses import dataclass, field -from decimal import Decimal -from typing import List, Optional - - -@dataclass -class AvailableLot: - """ - One PEPS lot available for discharge — equivalent to one QSaldoActual record. - - Fields - ------ - import_item_line_id : a76.item_lines.id of the import line (the lot) - import_invoice_id : a76.invoice_header.id of the import invoice - part_number_id : denormalized from the import line - available_qty : net balance available (QSaldo:Cantidad); mutated by - compare_balances() as quantity is distributed - consumed_qty : how much was actually taken from this lot by - compare_balances(); used by register_discharge_ledger - to create the exact BalanceMovement amount - value_me : USD value of the full lot (for proportional calc) - value_mn : MXN value of the full lot (for proportional calc) - order_peps : PEPS ordering key — lower = older = consumed first - """ - import_item_line_id: int - import_invoice_id: int - part_number_id: Optional[int] - available_qty: Decimal - value_me: Optional[Decimal] - value_mn: Optional[Decimal] - order_peps: int - consumed_qty: Decimal = field(default_factory=Decimal) - - -@dataclass -class DownloadEntry: - """ - Represents one export line that will be discharged from inventory. - Equivalent to the QADesc (QueADescargar) record in the legacy system. - - Fields - ------ - origin_procedure : TipoMovImpo – 'TEM' (temporal) | 'DEF' (definitiva) - export_line : LineaExpo – line number on the export invoice - part_number : NumParte – part number code - class_code : Clase – class code - quantity : CantExpo – quantity to discharge - quantity_used : CantUsada – amount already consumed (starts at 0) - unit_of_measure : UniMed – unit of measure code - import_invoice : FacturaImpo – source import invoice number - import_line : LineaImpo – source import line number - line_item_id : internal DB id of the LineItem (for series lookups) - available_lots : PEPS lots attached by fill_available_balances - """ - origin_procedure: str - export_line: int - part_number: str - class_code: str - quantity: Decimal - unit_of_measure: str - import_invoice: str - import_line: int - line_item_id: int - quantity_used: Decimal = field(default_factory=Decimal) - available_lots: List[AvailableLot] = field(default_factory=list) diff --git a/backend/api/v1/modules/a76/invoices/exports/process/sub_process/download_balance_collector.py b/backend/api/v1/modules/a76/invoices/exports/process/sub_process/download_balance_collector.py deleted file mode 100644 index 091442f4..00000000 --- a/backend/api/v1/modules/a76/invoices/exports/process/sub_process/download_balance_collector.py +++ /dev/null @@ -1,125 +0,0 @@ -""" -QUIERE_DESCARGAR -Collects every export line item that has discharge = True and builds the -"QueADescargar" list used by the balance-verification steps that follow -(LLENA_QUEUE_SALDOS_DISPONIBLES_FACTURA, COMPARA_SALDOS_POR_FACTURA, -VERIFICAQCONSOLIDADO). - -For each discharge line the routine also triggers two series sub-validations: - · REVISA_SERIES_EXISTA → verifies the series to be discharged exist - · REVISA_SERIES_OTRAS_PAR → verifies the series are not already discharged - on another line (REVISA_SERIES_DESC was commented-out in the legacy) - -Legacy equivalent ------------------ -SELECT EqiPex.TipoMovImpo, EqiPex.LineaExpo, EqiPex.NumParte, - EqiPex.Clase, EqiPex.CantExpo, EqiPex.UnidadMedida, - EqiPex.FacturaImpo, EqiPex.LineaImpo -FROM QEqeMaq EqiPex -WHERE Consecutivo = - AND EqiPex.Descarga = 1 -""" - -from decimal import Decimal -from typing import List, Set, Tuple - -from sqlalchemy.orm import Session - -from api.v1.modules.a76.invoices.models import InvoiceHeader -from api.v1.modules.a76.items.models import LineItem -from core.exceptions import ErrorCollector -from .discharge_types import AvailableLot, DownloadEntry # re-exported for callers -from .series.review_series_exist import review_series_exist -from .series.review_series_other_lines import review_series_other_lines - - -# --------------------------------------------------------------------------- -# Main entry point -# --------------------------------------------------------------------------- - -def collect_lines_to_discharge( - db: Session, - invoice: InvoiceHeader, - lines: List[LineItem], - errors: ErrorCollector, -) -> List[DownloadEntry]: - """ - QUIERE_DESCARGAR - Builds and returns the list of ``DownloadEntry`` records for every line - that has ``discharge = True``. If no lines have discharge enabled the - list is empty and the subsequent balance steps are skipped. - - For each collected line the function also runs: - · _revisa_series_exista - · _revisa_series_otras_par - - Parameters - ---------- - db : active SQLAlchemy session - invoice : the export invoice being processed - lines : all line items of the invoice (already loaded by pre_validators) - errors : shared error collector - - Returns - ------- - List[DownloadEntry] — the "QueADescargar" equivalent - """ - to_discharge: List[DownloadEntry] = [] - - discharge_lines = [line for line in lines if line.fa_data and line.fa_data.discharge] - - if not discharge_lines: - return to_discharge - - # Shared across all lines — accumulates series keys to detect duplicates - # between lines (equivalent to QueueSeries in the Clarion) - seen_series: Set[Tuple] = set() - - for line in discharge_lines: - origin_procedure = ( - line.customs.origin_procedure - if line.customs and line.customs.origin_procedure - else "" - ) - part_number = "" - if line.part_info: - part_number = line.part_info.part_number or "" - - class_code = "" - if line.class_info: - class_code = line.class_info.class_code or "" - - quantity = ( - line.quantity.quantity or Decimal(0) - if line.quantity - else Decimal(0) - ) - - uom_code = "" - if line.unit_of_measure_info: - uom_code = line.unit_of_measure_info.code or "" - - import_invoice_number = "" - import_line_number = 0 - if line.fa_data: - import_invoice_number = line.fa_data.search_invoice or "" - import_line_number = line.fa_data.search_line or 0 - - entry = DownloadEntry( - origin_procedure=origin_procedure, - export_line=line.line_number, - part_number=part_number, - class_code=class_code, - quantity=quantity, - quantity_used=Decimal(0), - unit_of_measure=uom_code, - import_invoice=import_invoice_number, - import_line=import_line_number, - line_item_id=line.id, - ) - to_discharge.append(entry) - - review_series_exist(db, invoice, line, entry, errors) - review_series_other_lines(db, invoice, line, entry, seen_series, errors) - - return to_discharge diff --git a/backend/api/v1/modules/a76/invoices/exports/process/sub_process/fill_available_balances.py b/backend/api/v1/modules/a76/invoices/exports/process/sub_process/fill_available_balances.py deleted file mode 100644 index 32d33a0b..00000000 --- a/backend/api/v1/modules/a76/invoices/exports/process/sub_process/fill_available_balances.py +++ /dev/null @@ -1,221 +0,0 @@ -""" -LLENA_QUEUE_SALDOS_DISPONIBLES_FACTURA -For each entry in ``to_discharge`` (QueADescargar), validates the source -import invoice/line and computes the net available balance from -``a24.balance_movement`` using the PEPS ledger. - -New logic vs. legacy --------------------- -The legacy Clarion routine read ``CantImpo - CantRetornadaTemp - CantRetornada`` -directly from the import line record. Since we migrated to an append-only -ledger (a24.balance_movement), the available balance is now computed as: - - SUM(signed_qty) per import_item_line_id - -where sign = +1 for positive movement types and -1 for negative ones -(see NEGATIVE_MOVEMENTS set in the BalanceMovement model). - -Validations preserved from legacy ----------------------------------- -1. Import invoice must exist. -2. Import invoice must be processed (status != 'NA' / not 'unprocessed'). -3. Import invoice date must not be later than the export invoice date. -4. Import line must exist. -5. Net available balance must be > 0 (otherwise the lot is skipped). - -Skipped items (equivalent to legacy CYCLE) ------------------------------------------- -- Entries already seen in the same call (duplicate import_invoice + import_line - combination) — handled naturally since each entry is unique in QueADescargar. -- Lots with net balance <= 0. - -Output ------- -On success, ``entry.available_lots`` is populated with one ``AvailableLot`` -per lot that has available balance. Errors are added to ``errors``. -""" - -import datetime -from decimal import Decimal -from typing import List, Optional - -from sqlalchemy import case, func, select -from sqlalchemy.orm import Session - -from api.v1.modules.a76.invoices.models import InvoiceHeader, InvoiceStatus -from api.v1.modules.a76.items.models import LineItem -from api.v1.modules.a24.balance_movements.models import BalanceMovement, NEGATIVE_MOVEMENTS -from core.exceptions import ErrorCollector -from .discharge_types import AvailableLot, DownloadEntry - - -# --------------------------------------------------------------------------- -# Private helpers -# --------------------------------------------------------------------------- - -def _fetch_import_invoice( - db: Session, - invoice_number: str, - export_invoice: InvoiceHeader, -) -> Optional[InvoiceHeader]: - return ( - db.query(InvoiceHeader) - .filter( - InvoiceHeader.invoice_number == invoice_number, - InvoiceHeader.tenant_id == export_invoice.tenant_id, - InvoiceHeader.company_id == export_invoice.company_id, - ) - .first() - ) - - -def _fetch_import_line( - db: Session, - invoice_id: int, - line_number: int, - tenant_id: int, - company_id: int, -) -> Optional[LineItem]: - return ( - db.query(LineItem) - .filter( - LineItem.invoice_id == invoice_id, - LineItem.line_number == line_number, - LineItem.tenant_id == tenant_id, - LineItem.company_id == company_id, - ) - .first() - ) - - -def _net_balance_for_lot( - db: Session, - import_item_line_id: int, - as_of_date: datetime.date, -) -> Decimal: - """ - Computes the net available balance for one import lot as of ``as_of_date``. - - Equivalent to the legacy two-step calculation: - 1. Base check: CantImpo - CantRetornadaTemp - CantRetornada (general balance) - 2. CALCULA_SALDO_FECHA_EXPO: only count exits with operation_date <= export date - - In the new ledger, ENTRY movements have no operation_date restriction (the - lot exists from its import date). EXIT movements (CONSUMPTION, WASTE, etc.) - are only counted if their operation_date <= as_of_date, mirroring the - Clarion "FechaFactura > EqiFex:FechaFactura → CYCLE" guard. - - balance = SUM(+qty for ENTRY-type movements) - - SUM( qty for EXIT-type movements WHERE operation_date <= as_of_date) - """ - sign_expr = case( - (BalanceMovement.movement_type.in_(NEGATIVE_MOVEMENTS), Decimal(-1)), - else_=Decimal(1), - ) - # Positive movements: always count (entries, returns, adjustments in) - # Negative movements: only count if they occurred on or before the export date - date_filter = case( - ( - BalanceMovement.movement_type.in_(NEGATIVE_MOVEMENTS), - BalanceMovement.operation_date <= as_of_date, - ), - else_=True, - ) - result = db.execute( - select(func.sum(sign_expr * BalanceMovement.quantity)).where( - BalanceMovement.import_item_line_id == import_item_line_id, - date_filter, - ) - ).scalar() - return Decimal(str(result or 0)) - - -def _peps_order_for_lot(db: Session, import_item_line_id: int) -> int: - """Returns the minimum (oldest) order_peps for this lot.""" - result = db.execute( - select(func.min(BalanceMovement.order_peps)).where( - BalanceMovement.import_item_line_id == import_item_line_id, - ) - ).scalar() - return int(result or 0) - - -# --------------------------------------------------------------------------- -# Public entry point -# --------------------------------------------------------------------------- - -def fill_available_balances( - db: Session, - export_invoice: InvoiceHeader, - to_discharge: List[DownloadEntry], - errors: ErrorCollector, -) -> None: - """ - LLENA_QUEUE_SALDOS_DISPONIBLES_FACTURA - Refactorizada para PEPS Flexible: - Busca automáticamente todos los lotes con saldo disponible para el número de parte - de cada partida a descargar. - """ - export_date: datetime.date = ( - export_invoice.invoice_date.date() - if hasattr(export_invoice.invoice_date, "date") - else export_invoice.invoice_date - ) - - for entry in to_discharge: - # Recuperar la partida de exportación original para obtener IDs precisos - export_line = db.get(LineItem, entry.line_item_id) - if not export_line: - continue - - part_id = export_line.part_number_id - class_id = export_line.class_id - - # 1. Buscar todos los lotes candidatos (Importaciones del mismo número de parte y clase) - # Solo facturas procesadas y con fecha <= export_date - candidates = ( - db.query(LineItem) - .join(InvoiceHeader, InvoiceHeader.id == LineItem.invoice_id) - .filter( - LineItem.tenant_id == export_invoice.tenant_id, - LineItem.company_id == export_invoice.company_id, - LineItem.part_number_id == part_id, - LineItem.class_id == class_id, - InvoiceHeader.operation_type == "imp", - InvoiceHeader.status != InvoiceStatus.PENDING, - InvoiceHeader.invoice_date <= export_date - ) - .all() - ) - - # 2. Calcular saldos para cada candidato y llenar available_lots - found_any_balance = False - for imp_line in candidates: - available = _net_balance_for_lot(db, imp_line.id, export_date) - - if available > 0: - found_any_balance = True - fin = imp_line.financial - lot = AvailableLot( - import_item_line_id=imp_line.id, - import_invoice_id=imp_line.invoice_id, - part_number_id=imp_line.part_number_id, - available_qty=available, - value_me=Decimal(str(fin.value_usd or 0)) if fin else None, - value_mn=Decimal(str(fin.value_mxn or 0)) if fin else None, - order_peps=_peps_order_for_lot(db, imp_line.id), - ) - entry.available_lots.append(lot) - - # 3. Ordenar por PEPS (FIFO) - entry.available_lots.sort(key=lambda x: x.order_peps) - - # 4. Manejo de Errores: Solo si no se encontró ABSOLUTAMENTE NADA de saldo - if not found_any_balance: - errors.add_error( - field=f"line[{entry.export_line}].quantity", - message=f"No se encontró saldo disponible en inventario para el número de parte: {entry.part_number}", - solution=["Verificar que existan facturas de importación procesadas con saldo."], - code="NO_BALANCE_FOUND_ANYWHERE", - value=entry.part_number, - ) diff --git a/backend/api/v1/modules/a76/invoices/exports/process/sub_process/finalize_invoice.py b/backend/api/v1/modules/a76/invoices/exports/process/sub_process/finalize_invoice.py deleted file mode 100644 index 36e8b308..00000000 --- a/backend/api/v1/modules/a76/invoices/exports/process/sub_process/finalize_invoice.py +++ /dev/null @@ -1,234 +0,0 @@ -""" -finalize_invoice_no_discharge / finalize_invoice_with_discharge -(TERMINA_AC_O_LP_NODES / TERMINA_AC_O_LP_NORMAL) - -Last step of export invoice processing. Both variants: - 1. Review enabled classes (REVISACLASESHABILITADAS). - 2. Validate SisExp quantity / weight / value limits (min and max). - 3. If no errors: assign invoice-level totals and mark as PROCESSED. - -The "with_discharge" variant additionally: - 4. Generate definitive import (GENERAIMPODEFINITIVA) (if is_regime_change and generate_id) - 5. Register discharge in import lines (REGISTRA_DESCARGA_IMPORTACION) - 6. Register series discharge (REGISTRA_DESCARGA_SERIES) - -The legacy 'Of LP' branch (print-preview / progress-bar UI) is not ported. -""" - -import datetime -from decimal import Decimal -from typing import TYPE_CHECKING, List - -from sqlalchemy.orm import Session - -from .register_import_discharge import register_import_discharge -from .register_discharge_series import register_discharge_series -from .register_discharge_ledger import register_discharge_ledger - -if TYPE_CHECKING: - from .discharge_types import DownloadEntry - -from api.v1.modules.a76.classes.models import Class -from api.v1.modules.a76.invoices.models import InvoiceHeader, InvoiceStatus -from api.v1.modules.a76.items.models import LineItem -from core.exceptions import ErrorCollector -from .review_limits import limit_weight, limit_value -from .generate_definitive_import import generate_definitive_import, is_regime_change_detected - - -# --------------------------------------------------------------------------- -# REVISACLASESHABILITADAS -# --------------------------------------------------------------------------- - -def _review_enabled_classes( - db: Session, - invoice: InvoiceHeader, - lines: List[LineItem], - errors: ErrorCollector, -) -> None: - """ - REVISACLASESHABILITADAS - Verifies that every line item's class is active (not disabled). - - Clarion: loops QEqeMaq for the invoice, fetches QClaAct by class code, - errors if HabilitaDeshabilitaClase = 1 → Python: Class.is_active = False. - """ - for line in lines: - if not line.class_id: - continue - cls: Class | None = db.get(Class, line.class_id) - if cls is not None and cls.is_active is False: - errors.add_error( - field=f"line[{line.line_number}].class", - message=( - f"La Clase: '{cls.class_code}' esta desactivada, " - "no se pueden hacer movimientos." - ), - solution=["Seleccionar una clase activa."], - code="CLASS_DISABLED", - ) - - -# --------------------------------------------------------------------------- -# DO ASIGNA_VALORES_FACTURA -# --------------------------------------------------------------------------- -from api.v1.modules.a76.app_settings.service import AppSettingsService -from api.v1.modules.a76.general_catalogs.exchange_rate.models import ExchangeRate -from sqlalchemy import select -from .review_limits import review_sisexpo_limits - -def _assign_invoice_totals( - db: Session, - invoice: InvoiceHeader, - lines: List[LineItem], - tenant_id: str, - company_id: str, - username: str = "SYSTEM" -) -> None: - """ - DO ASIGNA_VALORES_FACTURA - Calcula totales, resuelve TC base pedimento si aplica, y marca como PROCESADO. - """ - settings = AppSettingsService.get_resolved_settings(db, int(tenant_id), int(company_id)) - - inv_type = (invoice.invoice_type or "").strip().upper() - op_type = (invoice.operation_type or "").strip().lower() # 'imp' or 'exp' - - # Hierarchical resolve: invoices.types.{op}.{type}.ssisgen.ssimpFormData - inv_map = settings.get("invoices", {}).get("types", {}).get(op_type, {}).get(inv_type, {}) - form_data = inv_map.get("ssisgen", {}).get("ssimpFormData", {}) or inv_map.get("qsisgen", {}).get("ssimpFormData", {}) or settings.get("ssisgen", {}) or settings.get("qsisgen", {}) - - cal_val_base_tc = int(form_data.get("calvalbasetcpedexpo") or form_data.get("CalValBaseTCPedExpo") or 0) - act_seguridad = int(form_data.get("actseguridad") or form_data.get("ActSeguridad") or 0) - - total_value_mn = Decimal(0) - total_value_me = Decimal(0) - total_qty = Decimal(0) - total_packages = 0 - total_net_weight = Decimal(0) - total_gross_weight = Decimal(0) - - for line in lines: - if line.financial: - total_value_mn += Decimal(str(line.financial.value_mxn or 0)) - total_value_me += Decimal(str(line.financial.value_usd or 0)) - if line.quantity: - total_qty += line.quantity.quantity or Decimal(0) - total_packages += line.quantity.package_quantity or 0 - total_net_weight += line.quantity.net_weight or Decimal(0) - total_gross_weight += line.quantity.gross_weight or Decimal(0) - - # 1. Resolver TC base pedimento si aplica - if cal_val_base_tc == 1: - if invoice.compliance_mx and invoice.compliance_mx.pedimento: - ped = invoice.compliance_mx.pedimento - if ped.pedimento_dates and ped.pedimento_dates.payment_date: - payment_date = ped.pedimento_dates.payment_date - stmt = select(ExchangeRate).where( - ExchangeRate.tenant_id == int(tenant_id), - ExchangeRate.company_id == int(company_id), - ExchangeRate.date == payment_date.date()) - ex_rate_row = db.execute(stmt).scalar_one_or_none() - if ex_rate_row and invoice.financials: - invoice.financials.exchange_rate = float(ex_rate_row.value) - invoice.compliance_mx.which_exchange_rate = "TCPED" - invoice.process_log = "Se Actualizó con el Tipo de Cambio de la Fecha de Pago de Pedimento." - - # 2. Asignar totales al header - if invoice.financials is not None: - invoice.financials.value_mn = float(total_value_mn) - invoice.financials.value_me = float(total_value_me) - invoice.financials.total_quantity = float(total_qty) - invoice.financials.total_packages = total_packages - invoice.financials.net_weight = float(total_net_weight) - invoice.financials.gross_weight = float(total_gross_weight) - - invoice.party_count = len([l for l in lines if not (l.fa_data and l.fa_data.is_subitem)]) - invoice.updated_date = datetime.date.today() - invoice.status = InvoiceStatus.PROCESSED - - # 3. Auditoría (ActSeguridad) - if act_seguridad == 1: - invoice.updated_by = username - from api.v1.modules.a76.audit_log.services.service import AuditService - AuditService.create_audit_log( - db=db, reference=invoice.invoice_number, procedure="ACTUALIZAR FACTURA", movement="EXPORTACION", - username=username, tenant_id=int(tenant_id), company_id=int(company_id), - table_name="invoice_header", record_id=str(invoice.id), operation_type="UPDATE" - ) - - -# --------------------------------------------------------------------------- -# Public entry points -# --------------------------------------------------------------------------- - -def finalize_invoice_no_discharge( - db: Session, - invoice: InvoiceHeader, - lines: List[LineItem], - tenant_id: str, - company_id: str, - errors: ErrorCollector, - username: str = "SYSTEM", -) -> None: - """ - TERMINA_AC_O_LP_NODES - Finalizes a NODES-type export invoice (no inventory discharge). - """ - _review_enabled_classes(db, invoice, lines, errors) - - total_qty, total_net_weight = limit_weight(lines) - total_value = limit_value(lines) - - settings = AppSettingsService.get_resolved_settings(db, int(tenant_id), int(company_id)) - review_sisexpo_limits(invoice, settings, total_qty, total_net_weight, total_value, errors) - - if not errors.has_errors(): - _assign_invoice_totals(db, invoice, lines, tenant_id, company_id, username) - - -def finalize_invoice_with_discharge( - db: Session, - invoice: InvoiceHeader, - lines: List[LineItem], - tenant_id: str, - company_id: str, - errors: ErrorCollector, - to_discharge: List["DownloadEntry"] | None = None, - username: str = "SYSTEM", -) -> None: - # Refrescar factura para asegurar info de compliance/pedimentos fresca - db.refresh(invoice) - _review_enabled_classes(db, invoice, lines, errors) - - total_qty, total_net_weight = limit_weight(lines) - total_value = limit_value(lines) - - settings = AppSettingsService.get_resolved_settings(db, int(tenant_id), int(company_id)) - review_sisexpo_limits(invoice, settings, total_qty, total_net_weight, total_value, errors) - - if not errors.has_errors(): - # Unified detection of Regime Change (F4, CR type, or Manual Flag) - if is_regime_change_detected(invoice, settings): - def_invoice, created = generate_definitive_import(db, invoice, to_discharge or [], settings, errors) - if created and not errors.has_errors(): - from .generate_definitive_import import ( - generate_definitive_import_all_lines, - generate_definitive_import_discharged_lines - ) - # Choice based on generate_desc_parties - if (invoice.generate_desc_parties or "").strip() == 'Todas': - generate_definitive_import_all_lines(db, invoice, def_invoice, errors) - else: - generate_definitive_import_discharged_lines( - db, invoice, def_invoice, to_discharge or [], errors - ) - - if to_discharge: - # Write BalanceMovement (CONSUMPTION) + DischargeHeader + DischargeDetail - register_discharge_ledger(db, invoice, to_discharge) - # Update returned values on the import lines - register_import_discharge(db, invoice, to_discharge) - register_discharge_series(db, invoice, to_discharge) - - _assign_invoice_totals(db, invoice, lines, tenant_id, company_id, username) diff --git a/backend/api/v1/modules/a76/invoices/exports/process/sub_process/generate_definitive_import.py b/backend/api/v1/modules/a76/invoices/exports/process/sub_process/generate_definitive_import.py deleted file mode 100644 index b7031c98..00000000 --- a/backend/api/v1/modules/a76/invoices/exports/process/sub_process/generate_definitive_import.py +++ /dev/null @@ -1,501 +0,0 @@ -""" -GENERAIMPODEFINITIVA -Generates a definitive import invoice header from the export invoice when -processing a regime-change (cambio de régimen) export. - -If an invoice with the same number already exists as a definitive import, -the step is skipped (idempotent). After creating the header the function -calls the appropriate lines sub-routine based on ``generate_desc_parties``: - - 'Todas' → generate_definitive_import_all_lines - other → generate_definitive_import_discharged_lines - -Legacy equivalent ------------------ -GENERAIMPODEFINITIVA Routine - Access:QFacImpDef.TryFetch(EqiFID:FKFacImpoDef) - If ErrorCode() = 35 Then ← not found → create - INSERT INTO QFacImpDef (...) - End - IF EqiFex:GenPartidas = 'Todas' THEN - DO GENERAIMPODEFINITIVA_PARTIDAS_TODAS - ELSE - DO GENERAIMPODEFINITIVA_PARTIDAS_CON_DESCARGA - END -""" - -from decimal import Decimal -from typing import List -from sqlalchemy import select -from sqlalchemy.orm import Session - -from api.v1.modules.a76.invoices.models import InvoiceHeader, InvoiceStatus, OperationType -from api.v1.modules.a76.items.models import LineItem -from api.v1.modules.a76.items.line_quantities.models import LineQuantity -from api.v1.modules.a76.items.line_customs.models import LineCustom -from api.v1.modules.a76.items.line_descriptions.models import LineDescription -from api.v1.modules.a76.items.series.models import Serie -from core.exceptions import ErrorCollector -from .discharge_types import DownloadEntry - - -# --------------------------------------------------------------------------- -# Header creation -# --------------------------------------------------------------------------- - -def _create_definitive_import_header( - db: Session, - export_invoice: InvoiceHeader, -) -> InvoiceHeader: - """ - Inserts a new InvoiceHeader of type 'IMD' (Importación Definitiva) cloning - the relevant fields from the export invoice. - - Clarion field mapping (EqiFex → EqiFID): - FacturaExpo → invoice_number - FechaFactura → invoice_date / updated_date - TipoCambio → financials.exchange_rate - TipoPeso → logistics.weight_type - Proveedor → provider_id - VendidoConsignado → sold_to_header ('Vendido a:') - VendidoA → sold_to_id - EnviadoTransferido → shipped_to_header ('Enviado a:') - EnviadoA → shipped_to_id - AAduanal → customs_broker_id - AAduanalAme → customs_broker_us_id - Aduana_Cruce → compliance_mx.aduana - Cant_Partidas → party_count - Transportista → logistics.carrier_id (approx) - Incoterm → logistics.incoterm - Precinto → logistics.precinto (approx) - SubEmpresa → sub_company (approx) - Flete / Seguros / etc. → financials.* - Observaciones → notes - TipoMoneda → financials.currency_type - ClaveMoneda → financials.currency (approx) - ModTrans → logistics.transport_mode (approx) - Ped_Pendiente_Asignar → compliance_mx.is_pedimento_pending - PedimentoExpo → compliance_mx.pedimento_id (approx) - Remesa → compliance_mx.remesa - Estatus → status = PENDING ('NA') - TipoDoc → invoice_type = 'IMD' - ProvImpoDefCR → 'C' (fixed — always definitiva por cambio de régimen) - Sujecion → 'MaqEquipo' (fixed) - """ - exp = export_invoice - exp_fin = exp.financials - exp_log = exp.logistics - exp_comp = exp.compliance_mx - - def_invoice = InvoiceHeader( - tenant_id=exp.tenant_id, - company_id=exp.company_id, - system=exp.system, - operation_type=OperationType.IMP, - invoice_type="DEF", - invoice_number=exp.invoice_number, - invoice_date=exp.invoice_date, - party_count=exp.party_count, - generate_id=False, - status=InvoiceStatus.PENDING, - - # Comments (Mapped from original OBSERVACIONE/I) - observation_es=exp.observation_es, - observation_en=exp.observation_en, - ) - db.add(def_invoice) - db.flush() # get def_invoice.id before creating child records - - # ── Financials ──────────────────────────────────────────────────────── - if exp_fin is not None: - from api.v1.modules.a76.invoices.models import InvoiceFinancials - def_fin = InvoiceFinancials( - tenant_id=exp.tenant_id, - company_id=exp.company_id, - invoice_id=def_invoice.id, - currency=exp_fin.currency, - currency_type=exp_fin.currency_type, - exchange_rate=exp_fin.exchange_rate, - freight=exp_fin.freight, - insurance=exp_fin.insurance, - insurance_value=exp_fin.insurance_value, - packaging=exp_fin.packaging, - other_increments=exp_fin.other_increments, - ) - db.add(def_fin) - - # ── Compliance / pedimento ──────────────────────────────────────────── - if exp_comp is not None: - from api.v1.modules.a76.invoices.models import InvoiceComplianceMx - def_comp = InvoiceComplianceMx( - tenant_id=exp.tenant_id, - company_id=exp.company_id, - invoice_id=def_invoice.id, - aduana=exp_comp.aduana, - remesa=exp_comp.remesa, - pedimento_id=exp_comp.pedimento_id, - is_pedimento_pending=exp_comp.is_pedimento_pending, - # Reubicados aquí (Correcto en el esquema unificado) - provider_id=exp_comp.provider_id, - sold_to_id=exp_comp.sold_to_id, - shipped_to_id=exp_comp.shipped_to_id, - customs_broker_id=exp_comp.customs_broker_id, - customs_broker_us_id=exp_comp.customs_broker_us_id, - provider_header=exp_comp.provider_header, - sold_to_header=exp_comp.sold_to_header, - shipped_to_header=exp_comp.shipped_to_header, - ) - db.add(def_comp) - - # ── Logistics ───────────────────────────────────────────────────────── - if exp_log is not None: - from api.v1.modules.a76.invoices.models import InvoiceLogistics - def_log = InvoiceLogistics( - tenant_id=exp.tenant_id, - company_id=exp.company_id, - invoice_id=def_invoice.id, - weight_type=exp_log.weight_type, - incoterm=exp_log.incoterm, - ) - db.add(def_log) - - db.flush() - return def_invoice - - -# --------------------------------------------------------------------------- -# Public entry point -# --------------------------------------------------------------------------- - -def is_regime_change_detected(invoice: InvoiceHeader, settings: any = None) -> bool: - """ - Detecta si la factura debe activar la generación de Importación Definitiva (DEF). - Usa el parámetro 'incambioregdesc' como interruptor maestro. - """ - # 0. Interruptor Maestro (Configuración Global) - # Soporta Dict de JSON o Objetos Pydantic/SQLA - if settings: - # Búsqueda exhaustiva y recursiva del parámetro maestro - def find_in_obj(obj, target_key): - if isinstance(obj, dict): - for k, v in obj.items(): - if k.lower() == target_key.lower(): - return v - res = find_in_obj(v, target_key) - if res is not None: return res - elif hasattr(obj, '__dict__'): - # Soporte para objetos Pydantic/SQLAlchemy - dict_rep = obj.__dict__ if hasattr(obj, '__dict__') else {} - if hasattr(obj, 'dict') and callable(getattr(obj, 'dict')): - try: dict_rep = obj.dict() - except: pass - elif hasattr(obj, 'model_dump') and callable(getattr(obj, 'model_dump')): - try: dict_rep = obj.model_dump() - except: pass - - for k, v in dict_rep.items(): - # Ignorar propiedades privadas de SQLAlchemy - if k.startswith('_'): continue - if k.lower() == target_key.lower(): - return v - res = find_in_obj(v, target_key) - if res is not None: return res - return None - - # Buscamos el valor en todo el árbol de configuración - val = find_in_obj(settings, 'incambioregdesc') - - # LOG DE EMERGENCIA en consola para debugging - print(f"[REGIME_CHANGE_DETECT] Factura: {invoice.invoice_number}, Valor de incambioregdesc: {val}") - - # Comprobación segura (1, '1', True, 'true') - master_on = str(val).strip().lower() in ['1', 'true'] - - if not master_on: - return False - - # 1. Export + Pedimento F4 (Art. 114) - op_type = str(invoice.operation_type or "").lower() - if op_type == "exp": - if invoice.compliance_mx: - ped = invoice.compliance_mx.pedimento - if ped and (ped.pedimento_code or "").strip().upper() == "F4": - return True - - # 2. Tipo de Factura 'CR' o Flag Manual - if (invoice.invoice_type or "").strip().upper() == "CR": - return True - - if invoice.compliance_mx and invoice.compliance_mx.is_regime_change: - return True - - return False - - -def generate_definitive_import( - db: Session, - invoice: InvoiceHeader, - to_discharge: List[DownloadEntry], - settings: any, - errors: ErrorCollector, -) -> tuple[InvoiceHeader, bool]: - """ - GENERAIMPODEFINITIVA - Creates a definitive import invoice header from ``invoice`` (export) when - processing a regime-change export. - - If a definitive import with the same ``invoice_number`` already exists, - the function is a no-op and returns the existing record. - - After creating (or finding) the header, delegates to the lines sub-routine: - generate_desc_parties == 'Todas' → TODO: GENERAIMPODEFINITIVA_PARTIDAS_TODAS - otherwise → TODO: GENERAIMPODEFINITIVA_PARTIDAS_CON_DESCARGA - - Parameters - ---------- - db : active SQLAlchemy session - invoice : the export invoice being processed (regime-change type) - errors : shared error collector - - Returns - ------- - The existing or newly created definitive import InvoiceHeader, or None if - a non-blocking SQL error occurred. - """ - # ── 1. Check whether the definitive import already exists ──────────────── - existing: InvoiceHeader | None = db.execute( - select(InvoiceHeader).where( - InvoiceHeader.tenant_id == invoice.tenant_id, - InvoiceHeader.company_id == invoice.company_id, - InvoiceHeader.invoice_number == invoice.invoice_number, - InvoiceHeader.invoice_type == "DEF", - ) - ).scalar_one_or_none() - - if existing is not None: - return existing, False - - # ── 2. Create the definitiva header ────────────────────────────────── - def_invoice = _create_definitive_import_header(db, invoice) - return def_invoice, True - - -# --------------------------------------------------------------------------- -# Shared helper — copies one export line into the definitive import invoice -# (EqiPex → EqiPdf, identical body in both PARTIDAS_CON_DESCARGA and -# PARTIDAS_TODAS Clarion routines) -# --------------------------------------------------------------------------- - -def _copy_line_to_definitive( - db: Session, - export_line: LineItem, - def_invoice: InvoiceHeader, - def_line_number: int, - custom_qty: Decimal | None = None, -) -> None: - """ - Copies a single export LineItem (and its series) into a new definitive - import LineItem under ``def_invoice``. - - If ``custom_qty`` is provided, the function scales weights and values - proportionally (Regime Change with Discharge logic). - """ - from api.v1.modules.a76.items.line_financials.models import LineFinancial - from api.v1.modules.a24.fa.fa_item_lines.models import FaLineItem - - # Calculate proportionality ratio if custom_qty is provided - original_qty = export_line.quantity.quantity if export_line.quantity else Decimal(1) - if original_qty == 0: - original_qty = Decimal(1) - - ratio = Decimal(1) - if custom_qty is not None: - ratio = custom_qty / original_qty - - def_line = LineItem( - tenant_id=def_invoice.tenant_id, - company_id=def_invoice.company_id, - invoice_id=def_invoice.id, - line_number=def_line_number, - part_number_id=export_line.part_number_id, - class_id=export_line.class_id, - unit_of_measure=export_line.unit_of_measure, - ) - db.add(def_line) - db.flush() # get def_line.id - - if export_line.quantity: - src_q = export_line.quantity - qty_val = custom_qty if custom_qty is not None else src_q.quantity - - db.add(LineQuantity( - item_line_id=def_line.id, - quantity=qty_val, - net_weight=(src_q.net_weight * ratio) if src_q.net_weight is not None else None, - gross_weight=(src_q.gross_weight * ratio) if src_q.gross_weight is not None else None, - package_quantity=int(src_q.package_quantity * ratio) if src_q.package_quantity is not None else None, - package_id=src_q.package_id, - )) - - if export_line.financial: - src_f = export_line.financial - - # Helper to scale optional Decimal fields - def scale(val: Decimal | None) -> Decimal | None: - return (val * ratio) if val is not None else None - - db.add(LineFinancial( - item_line_id=def_line.id, - unit_cost_capture=src_f.unit_cost_capture, - unit_cost_usd=src_f.unit_cost_usd, - unit_cost_mxn=src_f.unit_cost_mxn, - # Scale total values - value_mxn=scale(src_f.value_mxn), - value_usd=scale(src_f.value_usd), - customs_value_mxn=scale(src_f.customs_value_mxn), - customs_value_usd=scale(src_f.customs_value_usd), - value_added_mxn=scale(src_f.value_added_mxn), - value_added_usd=scale(src_f.value_added_usd), - )) - - if export_line.customs: - src_c = export_line.customs - db.add(LineCustom( - item_line_id=def_line.id, - fraction=src_c.fraction, - fraction_type=src_c.fraction_type, - rate=src_c.rate, - sector=src_c.sector, - origin_country=src_c.origin_country, - )) - - if export_line.description: - src_d = export_line.description - db.add(LineDescription( - item_line_id=def_line.id, - description_spanish=src_d.description_spanish, - extra_description=src_d.extra_description, - description_english=src_d.description_english, - package_description=src_d.package_description, - brand=src_d.brand, - model=src_d.model, - has_serial=src_d.has_serial, - )) - - db.add(FaLineItem( - id=def_line.id, - tenant_id=def_line.tenant_id, - company_id=def_line.company_id, - is_subitem=False, - contains_subitems=False, - subitem_number=0, - )) - - # Series: QSeriesExpo → QSeriesDef - export_series: list[Serie] = ( - db.execute(select(Serie).where(Serie.line_item_id == export_line.id)) - .scalars() - .all() - ) - for serie in export_series: - db.add(Serie( - tenant_id=def_invoice.tenant_id, - company_id=def_invoice.company_id, - line_item_id=def_line.id, - row=serie.row, - serial_numbers=serie.serial_numbers, # SerieImpo ← SerieExpo - model=serie.model, # ModeloImpo ← ModeloExpo - brand=serie.brand, # ParteImpo ← ParteExpo - )) - - -# --------------------------------------------------------------------------- -# GENERAIMPODEFINITIVA_PARTIDAS_CON_DESCARGA -# --------------------------------------------------------------------------- - -def generate_definitive_import_discharged_lines( - db: Session, - export_invoice: InvoiceHeader, - def_invoice: InvoiceHeader, - to_discharge: List[DownloadEntry], - errors: ErrorCollector, -) -> None: - """ - GENERAIMPODEFINITIVA_PARTIDAS_CON_DESCARGA - Creates definitive import lines only for the lots consumed in the discharge, - effectively splitting export lines if they came from multiple import batches. - - Clarion: Sort(QueADescargar, FacturaImpo, LineaImpo) → loop - """ - # Sort by import lot origin to match legacy behavior - sorted_entries = sorted( - to_discharge, - key=lambda e: (e.import_invoice, e.import_line), - ) - - def_line_number = 0 - for entry in sorted_entries: - export_line: LineItem | None = db.get(LineItem, entry.line_item_id) - if export_line is None: - continue - - # If the entry has LOTS assigned (PEPS), create one IMD line per lot consumed - lots_to_transfer = [lot for lot in entry.available_lots if lot.consumed_qty > 0] - - if lots_to_transfer: - for lot in lots_to_transfer: - def_line_number += 1 - _copy_line_to_definitive( - db, - export_line, - def_invoice, - def_line_number, - custom_qty=lot.consumed_qty - ) - else: - # Fallback: if no lot info but entry exists, use entry quantity - def_line_number += 1 - _copy_line_to_definitive( - db, - export_line, - def_invoice, - def_line_number, - custom_qty=entry.quantity - ) - - db.flush() - - -# --------------------------------------------------------------------------- -# GENERAIMPODEFINITIVA_PARTIDAS_TODAS -# --------------------------------------------------------------------------- - -def generate_definitive_import_all_lines( - db: Session, - export_invoice: InvoiceHeader, - def_invoice: InvoiceHeader, - errors: ErrorCollector, -) -> None: - """ - GENERAIMPODEFINITIVA_PARTIDAS_TODAS - Creates definitive import lines for ALL lines of the export invoice, - sorted by line_number (QueuePartidaID sorted by LineaExpo). - - Clarion: Sort(QueuePartidaID, LineaExpo) → loop over all export lines - """ - export_lines: list[LineItem] = ( - db.execute( - select(LineItem) - .where(LineItem.invoice_id == export_invoice.id) - .order_by(LineItem.line_number) - ) - .scalars() - .all() - ) - - def_line_number = 0 - for export_line in export_lines: - def_line_number += 1 - _copy_line_to_definitive(db, export_line, def_invoice, def_line_number) - - db.flush() diff --git a/backend/api/v1/modules/a76/invoices/exports/process/sub_process/register_discharge_ledger.py b/backend/api/v1/modules/a76/invoices/exports/process/sub_process/register_discharge_ledger.py deleted file mode 100644 index 33484748..00000000 --- a/backend/api/v1/modules/a76/invoices/exports/process/sub_process/register_discharge_ledger.py +++ /dev/null @@ -1,244 +0,0 @@ -""" -register_discharge_ledger -========================= -Creates the full Annex-24 discharge record for one export invoice: - - 1. ONE DischargeHeader (one per export event) - 2. N BalanceMovement rows (type=CONSUMPTION, one per lot consumed) - 3. N DischargeDetail rows (one per export-line x import-lot pair), - each referencing its BalanceMovement (design rule 3) - -Design rules from a24.balance_movement (preserved here): - 1. NEVER update existing balance_movement rows — only INSERT. - 2. Balance = SUM of movements. - 3. Every DischargeDetail.movement_id MUST reference a BalanceMovement row. - 4. order_peps = movement.id (set after flush, globally monotonic). -""" - -import datetime -import logging -from decimal import Decimal -from typing import List, Optional - -from sqlalchemy.orm import Session - -from api.v1.modules.a76.invoices.models import InvoiceHeader -from api.v1.modules.a24.balance_movements.models import BalanceMovement, MovementType -from api.v1.modules.a24.discharges.models import ( - DischargeDetail, - DischargeHeader, - DischargeStatus, - DischargeType, -) -from api.v1.modules.a76.invoices.models import InvoiceHeader as A76InvoiceHeader -from api.v1.modules.a76.items.models import LineItem -from .discharge_types import DownloadEntry, AvailableLot - -logger = logging.getLogger(__name__) - - -# --------------------------------------------------------------------------- -# Helpers -# --------------------------------------------------------------------------- - -def _discharge_type_for_invoice(invoice: InvoiceHeader) -> DischargeType: - mapping = { - "AFIJO": DischargeType.TEMPORARY, - "DONAC": DischargeType.TEMPORARY, - "SCRAP": DischargeType.WASTE_SCRAP, - "REEXP": DischargeType.DEFINITIVE, - "VEMEX": DischargeType.DEFINITIVE, - } - return mapping.get(invoice.invoice_type or "", DischargeType.TEMPORARY) - - -def _export_date(invoice: InvoiceHeader) -> datetime.date: - d = invoice.invoice_date - return d.date() if hasattr(d, "date") else d - - -def _proportional_value( - consume: Decimal, - lot_consumed_total: Decimal, - lot_value: Optional[Decimal], -) -> Optional[Decimal]: - """Returns the proportional value for *consume* units out of *lot_consumed_total*.""" - if not lot_value or lot_consumed_total <= 0: - return None - return (consume / lot_consumed_total) * lot_value - - -def _proportional_qty(consume: Decimal, base_qty: Optional[Decimal], base_total: Optional[Decimal]) -> Optional[Decimal]: - """ - Proratea un valor (peso/valor) en proporción a lo consumido. - - consume: cantidad consumida del lote - - base_qty: valor total del lote (ej. peso neto total del lote) - - base_total: cantidad total del lote (ej. quantity del lote) - """ - if base_qty is None: - return None - if base_total is None or base_total <= 0: - return None - return (consume / base_total) * base_qty - - -# --------------------------------------------------------------------------- -# Public entry point -# --------------------------------------------------------------------------- - -def register_discharge_ledger( - db: Session, - export_invoice: InvoiceHeader, - to_discharge: List[DownloadEntry], -) -> Optional[DischargeHeader]: - """ - Persists the complete Annex-24 discharge record for *export_invoice*. - - Expects that compare_balances() has already run and populated - ``lot.consumed_qty`` for every lot that was drawn from. - - Returns the created DischargeHeader, or None if nothing was discharged. - """ - # Only process entries that actually consumed something - active = [e for e in to_discharge if e.quantity_used > Decimal(0)] - if not active: - return None - - op_date = _export_date(export_invoice) - discharge_type = _discharge_type_for_invoice(export_invoice) - - # Caches to avoid N+1 queries in loops - export_line_cache: dict[int, LineItem] = {} - import_line_cache: dict[int, LineItem] = {} - import_invoice_number_cache: dict[int, str] = {} - - # ── 1. DischargeHeader ──────────────────────────────────────────────── - header = DischargeHeader( - tenant_id=export_invoice.tenant_id, - company_id=export_invoice.company_id, - source_invoice_id=export_invoice.id, - discharge_type=discharge_type, - status=DischargeStatus.APPLIED, - discharge_date=op_date, - ) - db.add(header) - db.flush() # get header.id - - total_movements = 0 - - for entry in active: - export_line_id: Optional[int] = entry.line_item_id - - export_line_obj: Optional[LineItem] = None - if export_line_id: - export_line_obj = export_line_cache.get(export_line_id) - if export_line_obj is None: - export_line_obj = db.get(LineItem, export_line_id) - if export_line_obj is not None: - export_line_cache[export_line_id] = export_line_obj - - # Only iterate lots that were actually consumed - consumed_lots: List[AvailableLot] = [ - lot for lot in entry.available_lots if lot.consumed_qty > Decimal(0) - ] - - for lot in consumed_lots: - consume = lot.consumed_qty - - # Load import-line object for denormalized customs/weights fields - import_line_obj = import_line_cache.get(lot.import_item_line_id) - if import_line_obj is None: - import_line_obj = db.get(LineItem, lot.import_item_line_id) - if import_line_obj is not None: - import_line_cache[lot.import_item_line_id] = import_line_obj - - # Import invoice number (for origin_import_invoice in DischargeDetail) - origin_import_invoice: Optional[str] = None - if lot.import_invoice_id: - origin_import_invoice = import_invoice_number_cache.get(lot.import_invoice_id) - if origin_import_invoice is None: - inv = db.get(A76InvoiceHeader, lot.import_invoice_id) - origin_import_invoice = inv.invoice_number if inv else None - if origin_import_invoice: - import_invoice_number_cache[lot.import_invoice_id] = origin_import_invoice - - # ── 2. BalanceMovement (CONSUMPTION) ────────────────────────── - # Proportional value: consume / lot_consumed_total x lot_value - # lot_consumed_total == consume for single-lot entries (most cases) - value_me = _proportional_value(consume, consume, lot.value_me) - value_mn = _proportional_value(consume, consume, lot.value_mn) - imp_qty = import_line_obj.quantity if import_line_obj else None - imp_total_qty = imp_qty.quantity if imp_qty else None - net_weight = _proportional_qty( - consume, - imp_qty.net_weight if imp_qty else None, - imp_total_qty, - ) - - movement = BalanceMovement( - tenant_id=export_invoice.tenant_id, - company_id=export_invoice.company_id, - import_invoice_id=lot.import_invoice_id, - import_item_line_id=lot.import_item_line_id, - part_number_id=lot.part_number_id, - movement_type=MovementType.CONSUMPTION, - quantity=consume, - value_me=value_me, - value_mn=value_mn, - net_weight=net_weight, - source_invoice_id=export_invoice.id, - source_item_line_id=export_line_id, - order_peps=0, # placeholder — set after flush (rule 4) - operation_date=op_date, - notes=( - f"Descarga por factura de exportación " - f"{export_invoice.invoice_number}" - ), - ) - db.add(movement) - db.flush() # get movement.id - movement.order_peps = movement.id # rule 4: monotonic - - # ── 3. DischargeDetail ───────────────────────────────────────── - # Denormalized fields expected by reports: - imp_cust = import_line_obj.customs if import_line_obj else None - # Reuse already computed net_weight for consistency with movement. - gross_weight = _proportional_qty(consume, imp_qty.gross_weight if imp_qty else None, imp_total_qty) - - detail = DischargeDetail( - tenant_id=export_invoice.tenant_id, - company_id=export_invoice.company_id, - discharge_header_id=header.id, - export_item_line_id=export_line_id, - import_item_line_id=lot.import_item_line_id, - movement_id=movement.id, - quantity_discharged=consume, - unit_of_measure=entry.unit_of_measure or None, - value_me=value_me, - value_mn=value_mn, - net_weight=net_weight, - gross_weight=gross_weight, - tariff_fraction=imp_cust.fraction if imp_cust else None, - fraction_type=imp_cust.fraction_type if imp_cust else None, - ad_valorem=imp_cust.advalorem if imp_cust else None, - country_of_origin=imp_cust.origin_country if imp_cust else None, - sector=imp_cust.sector if imp_cust else None, - procedence=entry.origin_procedure or None, - part_number=entry.part_number or None, - export_part_number=( - export_line_obj.part_info.part_number - if export_line_obj and export_line_obj.part_info and export_line_obj.part_info.part_number - else None - ), - origin_import_invoice=origin_import_invoice, - ) - db.add(detail) - total_movements += 1 - - logger.info( - "register_discharge_ledger: invoice=%s header_id=%s details=%d", - export_invoice.invoice_number, - header.id, - total_movements, - ) - return header diff --git a/backend/api/v1/modules/a76/invoices/exports/process/sub_process/register_discharge_series.py b/backend/api/v1/modules/a76/invoices/exports/process/sub_process/register_discharge_series.py deleted file mode 100644 index c6000ee4..00000000 --- a/backend/api/v1/modules/a76/invoices/exports/process/sub_process/register_discharge_series.py +++ /dev/null @@ -1,181 +0,0 @@ -""" -REGISTRA_DESCARGA_SERIES -Marks each import serie that is being discharged by this export invoice by -setting ``discharge = True`` (Clarion: ``SerImp:SerieExpo = 1``). - -The routine iterates over every export serie with ``discharge = True``, -resolves the corresponding import serie (via ``import_serie_row`` or serial -number lookup), and flags it as exported so it cannot be discharged again. - -Clarion mapping ---------------- -QueueSeries records (SerDes) → export Serie rows with discharge=True, - grouped by DownloadEntry -SerDes:ConsectivoImpo → import InvoiceHeader.id (via invoice_number) -SerDes:LineaImpo → import LineItem.line_number -SerDes:Renglon → Serie.row on the import side -SerDes:Procedencia → entry.origin_procedure ('TEM' | 'DEF') -SerImp/SerDef:SerieExpo = 1 → import_serie.discharge = True - -Legacy equivalent ------------------ -Sort(QueueSeries, -Procedencia, ConsectivoImpo, LineaImpo) -Loop: GET import serie by (Consecutivo, LineaImpo, Renglon) → set SerieExpo=1 -""" - -from typing import List, Optional - -from sqlalchemy import select -from sqlalchemy.orm import Session, joinedload - -from api.v1.modules.a76.invoices.models import InvoiceHeader -from api.v1.modules.a76.items.models import LineItem -from api.v1.modules.a76.items.series.models import Serie -from .discharge_types import DownloadEntry - - -# --------------------------------------------------------------------------- -# Private helpers -# --------------------------------------------------------------------------- - -def _fetch_import_invoice( - db: Session, - invoice_number: str, - export_invoice: InvoiceHeader, -) -> Optional[InvoiceHeader]: - return ( - db.query(InvoiceHeader) - .filter( - InvoiceHeader.invoice_number == invoice_number, - InvoiceHeader.tenant_id == export_invoice.tenant_id, - InvoiceHeader.company_id == export_invoice.company_id, - ) - .first() - ) - - -def _fetch_import_line_id( - db: Session, - invoice_id: int, - line_number: int, - tenant_id: int, - company_id: int, -) -> Optional[int]: - return db.execute( - select(LineItem.id).where( - LineItem.invoice_id == invoice_id, - LineItem.line_number == line_number, - LineItem.tenant_id == tenant_id, - LineItem.company_id == company_id, - ) - ).scalar_one_or_none() - - -def _fetch_import_serie( - db: Session, - import_line_id: int, - row: int, -) -> Optional[Serie]: - """Fetch the import serie by (line_item_id, row) — equiv. TryFetch PKConsec_Lin_Ren.""" - return db.execute( - select(Serie).where( - Serie.line_item_id == import_line_id, - Serie.row == row, - ) - ).scalar_one_or_none() - - -def _resolve_import_serie_row( - db: Session, - import_line_id: int, - serial_number: str, -) -> Optional[int]: - """ - Fallback: find the import serie row by matching serial_number when - export_serie.serie_row is not set. - """ - return db.execute( - select(Serie.row).where( - Serie.line_item_id == import_line_id, - Serie.serial_numbers == serial_number, - ) - ).scalar_one_or_none() - - -# --------------------------------------------------------------------------- -# Public entry point -# --------------------------------------------------------------------------- - -def register_discharge_series( - db: Session, - export_invoice: InvoiceHeader, - to_discharge: List[DownloadEntry], -) -> None: - """ - REGISTRA_DESCARGA_SERIES - For every export serie with ``discharge = True`` on each entry in - ``to_discharge``, locates the matching import serie and marks it as - discharged (``discharge = True``). - - Parameters - ---------- - db : active SQLAlchemy session - export_invoice : the export invoice being processed - to_discharge : list of DownloadEntry records (QueADescargar) - """ - # Sort mirrors Clarion: Sort(QueueSeries, -Procedencia, ConsectivoImpo, LineaImpo) - # Descending procedencia puts 'TEM' before 'DEF' (T > D alphabetically) - sorted_entries = sorted( - to_discharge, - key=lambda e: (e.origin_procedure, e.import_invoice, e.import_line), - reverse=True, - ) - - for entry in sorted_entries: - if not entry.import_invoice or entry.import_line == 0: - continue - - # ── Resolve import invoice and line ─────────────────────────────────── - import_invoice = _fetch_import_invoice(db, entry.import_invoice, export_invoice) - if import_invoice is None: - continue - - import_line_id = _fetch_import_line_id( - db, - import_invoice.id, - entry.import_line, - export_invoice.tenant_id, - export_invoice.company_id, - ) - if import_line_id is None: - continue - - # ── Fetch all export series for this discharge line ─────────────────── - export_series: List[Serie] = ( - db.execute( - select(Serie).where( - Serie.line_item_id == entry.line_item_id, - Serie.discharge == True, # noqa: E712 - ) - ) - .scalars() - .all() - ) - - for export_serie in export_series: - # Resolve which row in the import series table this corresponds to - import_row = export_serie.serie_row - if import_row is None: - import_row = _resolve_import_serie_row( - db, import_line_id, export_serie.serial_numbers or "" - ) - - if import_row is None: - continue - - import_serie = _fetch_import_serie(db, import_line_id, import_row) - if import_serie is None: - continue - - # SerImp:SerieExpo = 1 (or SerDef:SerieExpo = 1) - import_serie.discharge = True diff --git a/backend/api/v1/modules/a76/invoices/exports/process/sub_process/register_import_discharge.py b/backend/api/v1/modules/a76/invoices/exports/process/sub_process/register_import_discharge.py deleted file mode 100644 index 96cbf04b..00000000 --- a/backend/api/v1/modules/a76/invoices/exports/process/sub_process/register_import_discharge.py +++ /dev/null @@ -1,171 +0,0 @@ -""" -REGISTRA_DESCARGA_IMPORTACION -Updates the import line items (temporary or definitive) with the returned -quantities and values consumed by this export invoice. - -For each entry in ``to_discharge`` (QSaldoActual in the legacy) the routine: - · Looks up the source import invoice header (TEM → QFacImp, DEF → QFacImpDef). - · Looks up the corresponding import line item. - · Increments value_returned_mxn, value_returned_usd on the import line's - financial sub-record. - · For TEM invoices, also calculates vat_used_mxn / vat_used_usd when the - import invoice date is on or after 2014-12-31 (Clarion date 78165). - -Legacy equivalent ------------------ -Loop QSaldoActual: - If TEM → fetch QFacImp + QEqiMaq, update CantRetornada, ValorRetornadoMN/ME, - ValorIVAMNUsado / ValorIVAMEUsado - Else → fetch QFacImpDef + QEqiDef, update CantRetornada, ValorRetornadoMN/ME -""" - -import datetime -from decimal import Decimal -from typing import List, Optional - -from sqlalchemy.orm import Session, joinedload - -from api.v1.modules.a76.invoices.models import InvoiceHeader -from api.v1.modules.a76.items.models import LineItem -from .discharge_types import DownloadEntry - -# Cutoff date: Clarion day 78165 ≈ 2014-12-31 -_VAT_CUTOFF = datetime.date(2014, 12, 31) - - -# --------------------------------------------------------------------------- -# Private helpers -# --------------------------------------------------------------------------- - -def _fetch_import_invoice( - db: Session, - invoice_number: str, - export_invoice: InvoiceHeader, -) -> Optional[InvoiceHeader]: - """Return the import InvoiceHeader that matches *invoice_number*.""" - return ( - db.query(InvoiceHeader) - .filter( - InvoiceHeader.invoice_number == invoice_number, - InvoiceHeader.tenant_id == export_invoice.tenant_id, - InvoiceHeader.company_id == export_invoice.company_id, - ) - .first() - ) - - -def _fetch_import_line( - db: Session, - invoice_id: int, - line_number: int, - tenant_id: int, - company_id: int, -) -> Optional[LineItem]: - """Return the LineItem for *invoice_id* / *line_number*, with financial and quantity loaded.""" - return ( - db.query(LineItem) - .options( - joinedload(LineItem.quantity), - joinedload(LineItem.financial), - ) - .filter( - LineItem.invoice_id == invoice_id, - LineItem.line_number == line_number, - LineItem.tenant_id == tenant_id, - LineItem.company_id == company_id, - ) - .first() - ) - - -# --------------------------------------------------------------------------- -# Public entry point -# --------------------------------------------------------------------------- - -def register_import_discharge( - db: Session, - export_invoice: InvoiceHeader, - to_discharge: List[DownloadEntry], -) -> None: - """ - REGISTRA_DESCARGA_IMPORTACION - Accumulates discharged quantities and values back onto the source import - line items (temporal or definitive). - - Parameters - ---------- - db : active SQLAlchemy session - export_invoice : the export invoice being processed - to_discharge : list of DownloadEntry records (QSaldoActual equivalent); - each entry's ``quantity`` holds the amount consumed - (QSaldo:CantUsada in the legacy). - """ - # Sort mirrors the Clarion: Sort(QSaldoActual, -Procedencia, FacturaImpo, LineaImpo) - # (descending procedencia puts 'TEM' before 'DEF' alphabetically reversed) - sorted_entries = sorted( - to_discharge, - key=lambda e: (e.origin_procedure, e.import_invoice, e.import_line), - reverse=False, - ) - - for entry in sorted_entries: - qty_used = entry.quantity # CantUsada — full entry qty consumed - - if not entry.import_invoice or entry.import_line == 0: - continue - - # ── Fetch source import invoice header ─────────────────────────────── - import_invoice = _fetch_import_invoice(db, entry.import_invoice, export_invoice) - if import_invoice is None: - continue - - # ── Fetch source import line item ───────────────────────────────────── - import_line = _fetch_import_line( - db, - import_invoice.id, - entry.import_line, - export_invoice.tenant_id, - export_invoice.company_id, - ) - if import_line is None: - continue - - # ── Calculate proportional values ───────────────────────────────────── - # value_returned = qty_used * (line_value / line_qty) - fin = import_line.financial - qty_rec = import_line.quantity - - if fin is None or qty_rec is None: - continue - - original_qty = qty_rec.quantity or Decimal(0) - if original_qty == 0: - continue - - value_mn = Decimal(str(fin.value_mxn or 0)) - value_usd = Decimal(str(fin.value_usd or 0)) - - returned_mn = qty_used * value_mn / original_qty - returned_usd = qty_used * value_usd / original_qty - - # ── Accumulate returned value ────────────────────────────────────────── - fin.value_returned_mxn = (fin.value_returned_mxn or Decimal(0)) + returned_mn - fin.value_returned_usd = (fin.value_returned_usd or Decimal(0)) + returned_usd - - # ── VAT used (TEM only, and only for invoices on/after cutoff date) ─── - # Clarion: IF EqiFim:FechaFactura > 78165 (≈ 2014-12-31) - if entry.origin_procedure == "TEM": - inv_date = import_invoice.invoice_date - if isinstance(inv_date, datetime.datetime): - inv_date = inv_date.date() - - if inv_date and inv_date >= _VAT_CUTOFF: - iva_factor = Decimal(0) - if import_invoice.financials and import_invoice.financials.iva_factor: - iva_factor = Decimal(str(import_invoice.financials.iva_factor)) - - fin.vat_used_mxn = (returned_mn * iva_factor) / 100 - fin.vat_used_usd = (returned_usd * iva_factor) / 100 - else: - fin.vat_used_mxn = Decimal(0) - fin.vat_used_usd = Decimal(0) diff --git a/backend/api/v1/modules/a76/invoices/exports/process/sub_process/review_class.py b/backend/api/v1/modules/a76/invoices/exports/process/sub_process/review_class.py deleted file mode 100644 index 4857f926..00000000 --- a/backend/api/v1/modules/a76/invoices/exports/process/sub_process/review_class.py +++ /dev/null @@ -1,181 +0,0 @@ -""" -REVISA_CLASE -Validates that every line item of an export invoice has a valid class in the -catalog (QClaAct) and that the associated tariff fraction exists in either -the active fractions catalog (SFracciones) or the historical catalog -(GFraccionesHistorico). - -Two-pass logic (ported from legacy SCAII): - Pass A – no class errors: - Iterate all lines and validate their fractions. - Pass B – class errors detected: - Report each missing class and also validate its fraction. -""" - -from typing import List - -from sqlalchemy import func, or_ -from sqlalchemy.orm import Session - -from api.v1.modules.a76.classes.models import Class -from api.v1.modules.a76.general_catalogs.fractions.historical_tariff_fractions.models import ( - HistoricalTariffFraction, -) -from api.v1.modules.a76.general_catalogs.fractions.tariff_fractions.models import TariffFraction -from api.v1.modules.a76.invoices.models import InvoiceHeader -from api.v1.modules.a76.items.models import LineItem -from core.exceptions import ErrorCollector - - -# --------------------------------------------------------------------------- -# Fraction helpers (shared with imports, same catalog sources) -# --------------------------------------------------------------------------- - -def _fraction_exists_via_sitar(fraction_code: str) -> bool: - """ - Returns True when the fraction exists in SITAR (active or anterior). - Falls back to False when SITAR is not configured or unreachable. - - Format: first 8 chars = base fraction, chars 9-10 = NICO/country (optional). - """ - if not fraction_code: - return True - - base_frac = fraction_code[:8].strip() - nico = fraction_code[8:10].strip() if len(fraction_code) > 8 else "" - - try: - from api.v1.modules.sitar.fracciones.service import FraccionesService - from api.v1.modules.sitar.fracciones_anteriores.service import ( - FraccionesAnterioresService, - ) - - results = FraccionesService.search_sync( - fraccion=base_frac, - nico=nico if nico else None, - limit=1, - ) - if results: - return True - - hist_results = FraccionesAnterioresService.search_sync( - fraccion_anterior=base_frac, - limit=1, - ) - return len(hist_results) > 0 - - except Exception: - return False - - -def _fraction_exists_in_local_db(db: Session, fraction_code: str) -> bool: - """Fallback: validates against local TariffFraction and HistoricalTariffFraction tables.""" - if not fraction_code: - return True - - base_frac = fraction_code[:8] - nico = fraction_code[8:10] if len(fraction_code) > 8 else "" - - tariff_q = db.query(TariffFraction).filter( - func.left(TariffFraction.code, 8) == base_frac - ) - if nico: - tariff_q = tariff_q.filter(TariffFraction.nico == nico) - else: - tariff_q = tariff_q.filter( - or_(TariffFraction.nico.is_(None), TariffFraction.nico == "") - ) - if tariff_q.first() is not None: - return True - - hist_q = db.query(HistoricalTariffFraction).filter( - HistoricalTariffFraction.historical_fraction == base_frac - ) - if nico: - hist_q = hist_q.filter(HistoricalTariffFraction.country == nico) - else: - hist_q = hist_q.filter( - or_( - HistoricalTariffFraction.country.is_(None), - HistoricalTariffFraction.country == "", - ) - ) - return hist_q.first() is not None - - -def _fraction_exists_in_catalog(db: Session, fraction_code: str) -> bool: - """SITAR first, local DB as fallback.""" - if _fraction_exists_via_sitar(fraction_code): - return True - return _fraction_exists_in_local_db(db, fraction_code) - - -def _validate_line_fraction( - db: Session, - line: LineItem, - errors: ErrorCollector, -) -> None: - """Adds a FRACCION error when the line's export fraction is not in any catalog.""" - fraction = line.customs.fraction if line.customs else None - if not fraction: - return - - if _fraction_exists_in_catalog(db, fraction): - return - - class_code = line.class_info.class_code if line.class_info else "" - errors.add_error( - field=f"line[{line.line_number}].fraction", - message=( - f"La Factura contiene la fraccion: {fraction} asociada al Clase {class_code} " - "que no existe en el catálogo de fracciones" - ), - solution=["Agregar la fracción a fracciones históricas."], - code="FRACCION", - ) - - -# --------------------------------------------------------------------------- -# Main entry point -# --------------------------------------------------------------------------- - -def review_class( - db: Session, - lines: List[LineItem], - errors: ErrorCollector, -) -> None: - """ - Validates class and fraction integrity for all line items of an export invoice. - - Logic ported from legacy REVISA_CLASE: - - 1. Count line items whose class_id is not present in the active classes catalog. - 2a. No class errors (TotalReg = 0): - Iterate every line and validate its tariff fraction. - 2b. Class errors found (TotalReg > 0): - For each invalid line: report a CLASE error, then validate its fraction. - """ - invalid_class_lines = [ - line for line in lines - if line.class_id is None or db.get(Class, line.class_id) is None - ] - has_class_errors = bool(invalid_class_lines) - - if not has_class_errors: - # Pass A: all classes exist — validate fractions for every line - for line in lines: - _validate_line_fraction(db, line, errors) - else: - # Pass B: report missing classes and validate their fractions - for line in invalid_class_lines: - class_code = line.class_info.class_code if line.class_info else "" - errors.add_error( - field=f"line[{line.line_number}].class", - message=f"La clase: {class_code or '(vacía)'} no existe en catálogo de clases", - solution=[ - f"Borrar la partida: {line.line_number}, " - f"o dar de alta la clase: {class_code or '(vacía)'} en el catálogo de Clases" - ], - code="CLASE", - ) - _validate_line_fraction(db, line, errors) diff --git a/backend/api/v1/modules/a76/invoices/exports/process/sub_process/review_exchange_rate.py b/backend/api/v1/modules/a76/invoices/exports/process/sub_process/review_exchange_rate.py deleted file mode 100644 index a44f4726..00000000 --- a/backend/api/v1/modules/a76/invoices/exports/process/sub_process/review_exchange_rate.py +++ /dev/null @@ -1,94 +0,0 @@ -""" -REVISA_TIPOCAMBIO -Validates that the exchange rate captured on the export invoice matches the -rate registered in the exchange-rate catalogue for the invoice date. - -Only runs when SisGen:CalValBaseTCPedExpo = 0 (use invoice-date TC, not -pedimento-payment-date TC). When the flag is 1 the TC is taken from the -pedimento and this check is skipped — that branch is handled in the TODO -for step 7 of main_process. - -Clarion mapping ---------------- -gtipocambio → a76.exchange_rate (ExchangeRate model) -FECHA → ExchangeRate.date (cast to DATE for comparison) -VALOR → ExchangeRate.value -EqiFex:FechaFactura → invoice.invoice_date -EqiFex:TipoCambio → invoice.financials.exchange_rate -""" - -import datetime -from decimal import Decimal -from typing import Optional - -from sqlalchemy import cast, Date, select -from sqlalchemy.orm import Session - -from api.v1.modules.a76.invoices.models import InvoiceHeader -from api.v1.modules.a76.general_catalogs.exchange_rate.models import ExchangeRate -from core.exceptions import ErrorCollector - - -def review_exchange_rate( - db: Session, - invoice: InvoiceHeader, - cal_val_base_tc: int, - errors: ErrorCollector, -) -> None: - """ - REVISA_TIPOCAMBIO - Checks that the invoice's exchange rate matches the catalogue value for - the invoice date. - - Parameters - ---------- - db : active SQLAlchemy session - invoice : the export invoice being processed - cal_val_base_tc : flag from settings (1 = skip validation as TC comes from pedimento) - errors : shared error collector - """ - if cal_val_base_tc == 1: - # SKIP: TC is taken from pedimento payment date, resolved in main_process Step 7 - return - - if not invoice.financials: - return - - invoice_date: datetime.date = ( - invoice.invoice_date.date() - if hasattr(invoice.invoice_date, "date") - else invoice.invoice_date - ) - - # Look up the catalogue rate for the invoice date - catalogue_rate: Optional[ExchangeRate] = db.execute( - select(ExchangeRate).where( - ExchangeRate.tenant_id == invoice.tenant_id, - ExchangeRate.company_id == invoice.company_id, - cast(ExchangeRate.date, Date) == invoice_date, - ) - ).scalar_one_or_none() - - if catalogue_rate is None: - # No rate registered for this date — cannot validate, skip - # (the Clarion loop simply finds no rows and exits cleanly) - return - - invoice_tc = Decimal(str(invoice.financials.exchange_rate or 0)) - catalogue_tc = Decimal(str(catalogue_rate.value or 0)) - - if invoice_tc != catalogue_tc: - errors.add_error( - field="financials.exchange_rate", - message="No está capturado correctamente el Tipo de Cambio.", - solution=[ - "Capture o modifique el tipo de cambio que corresponda a la " - "factura en el catálogo de Tipo de Cambio." - ], - code="EXCHANGE_RATE_MISMATCH", - value={ - "invoice_date": str(invoice_date), - "invoice_tc": str(invoice_tc), - "catalogue_tc": str(catalogue_tc), - }, - ) diff --git a/backend/api/v1/modules/a76/invoices/exports/process/sub_process/review_limits.py b/backend/api/v1/modules/a76/invoices/exports/process/sub_process/review_limits.py deleted file mode 100644 index 92424119..00000000 --- a/backend/api/v1/modules/a76/invoices/exports/process/sub_process/review_limits.py +++ /dev/null @@ -1,96 +0,0 @@ -import logging -from decimal import Decimal -from typing import Dict, Any, List -from api.v1.modules.a76.invoices.models import InvoiceHeader -from core.exceptions import ErrorCollector - -logger = logging.getLogger(__name__) - -def review_sisexpo_limits( - invoice: InvoiceHeader, - settings: Dict[str, Any], - total_qty: Decimal, - total_net_weight: Decimal, - total_value: Decimal, - errors: ErrorCollector -) -> None: - """ - Validates invoice totals against the SisExpo min/max limit parameters. - Uses robust resolution for deep nested JSON structure. - """ - invoice_type = (invoice.invoice_type or "").strip().upper() - op_type = "exp" - - # 1. Start from ssisexpo root - params = settings.get("ssisexpo", {}) - cat_name = "ssisexpo" - - # 2. Deep resolution if not in root (Frontend structure) - # Search for any limit field to decide if we should deep dive - has_root_limits = any(params.get(k) is not None for k in ["cantlimite", "CantLimite", "pesolimite", "PesoLimite", "valorlimite", "ValorLimite"]) - - if not has_root_limits: - invoice_map = settings.get("invoices", {}).get("types", {}).get(op_type, {}).get(invoice_type, {}) - # Buscar en ssimpFormData (shared schema) dentro de qsisgen o ssisgen - params = invoice_map.get("qsisgen", {}).get("ssimpFormData", {}) or invoice_map.get("ssisgen", {}).get("ssimpFormData", {}) or params - cat_name = f"invoices.types.{op_type}.{invoice_type}.ssimpFormData" - - # Obtener límites Máximos - limit_qty = Decimal(str(params.get("cantlimite") or params.get("CantLimite") or 0)) - limit_weight = Decimal(str(params.get("pesolimite") or params.get("PesoLimite") or 0)) - limit_value = Decimal(str(params.get("valorlimite") or params.get("ValorLimite") or 0)) - - # Obtener límites Mínimos - min_limit_qty = Decimal(str(params.get("cantlimitemin") or params.get("CantLimiteMin") or 0)) - min_limit_weight = Decimal(str(params.get("pesolimitemin") or params.get("PesoLimiteMin") or 0)) - min_limit_value = Decimal(str(params.get("valorlimitemin") or params.get("ValorLimiteMin") or 0)) - - logger.info(f"DEBUG_EXPO_LIMITS: Invoice={invoice.invoice_number} | DetectedType={invoice_type} | ResolvedCat={cat_name}") - logger.info(f"DEBUG_EXPO_LIMITS: RAW_PARAMS_FOR_VAL={params}") # Cuidado, esto puede ser largo pero nos dirá la verdad - logger.info(f"DEBUG_EXPO_LIMITS: Qty: Current={total_qty} Max={limit_qty} Min={min_limit_qty}") - logger.info(f"LIMIT_CHECK_EXPO: Weight: Current={total_net_weight} Max={limit_weight} Min={min_limit_weight}") - logger.info(f"LIMIT_CHECK_EXPO: Value: Current={total_value} Max={limit_value} Min={min_limit_value}") - - solution = ["Ajuste los valores de la factura o consulte a su Administrador para parametrizar la factura."] - code = "PAR.EXPO" - - # --- Max Validations --- - if limit_qty > 0 and total_qty > limit_qty: - errors.add_error(field="financials.total_quantity", message=f"La cantidad total ({total_qty}) excede el máximo permitido ({limit_qty}).", solution=solution, code=code) - - if limit_weight > 0 and total_net_weight > limit_weight: - errors.add_error(field="financials.net_weight", message=f"El peso neto total ({total_net_weight}) excede el máximo permitido ({limit_weight}).", solution=solution, code=code) - - if limit_value > 0 and total_value > limit_value: - errors.add_error(field="financials.value_me", message=f"El valor total en USD ({total_value}) excede el máximo permitido ({limit_value}).", solution=solution, code=code) - - # --- Min Validations --- - if min_limit_qty > 0 and total_qty < min_limit_qty: - errors.add_error(field="financials.total_quantity", message=f"La cantidad total ({total_qty}) es inferior al mínimo requerido ({min_limit_qty}).", solution=solution, code=code) - - if min_limit_weight > 0 and total_net_weight < min_limit_weight: - errors.add_error(field="financials.net_weight", message=f"El peso neto total ({total_net_weight}) es inferior al mínimo requerido ({min_limit_weight}).", solution=solution, code=code) - - if min_limit_value > 0 and total_value < min_limit_value: - errors.add_error(field="financials.value_me", message=f"El valor total en USD ({total_value}) es inferior al mínimo requerido ({min_limit_value}).", solution=solution, code=code) - - -def limit_weight(lines: List[Any]) -> tuple[Decimal, Decimal]: - """Sums exported quantity and net weight across all line items.""" - total_qty = Decimal(0) - total_net_weight = Decimal(0) - for line in lines: - if line.quantity: - total_qty += line.quantity.quantity or Decimal(0) - total_net_weight += line.quantity.net_weight or Decimal(0) - return total_qty, total_net_weight - -def limit_value(lines: List[Any]) -> Decimal: - """Sums (unit_cost_capture x quantity) across all line items.""" - total_value = Decimal(0) - for line in lines: - if line.financial and line.quantity: - capture = line.financial.unit_cost_capture or Decimal(0) - qty = line.quantity.quantity or Decimal(0) - total_value += capture * qty - return total_value diff --git a/backend/api/v1/modules/a76/invoices/exports/process/sub_process/review_origin_procedure.py b/backend/api/v1/modules/a76/invoices/exports/process/sub_process/review_origin_procedure.py deleted file mode 100644 index 5e3783fd..00000000 --- a/backend/api/v1/modules/a76/invoices/exports/process/sub_process/review_origin_procedure.py +++ /dev/null @@ -1,74 +0,0 @@ -""" -REVISA_PROCEDENCIA_PAR -Validates that every line item of the export invoice has an import origin -procedure (TipoMovImpo) that matches what the invoice type requires: - - · Regime-change (AFIJO / SCRAP with EsCambioRegimen='S') → all lines must be 'TEM' - · REEXP / VEMEX → all lines must be 'DEF' - -Clarion mapping ---------------- -Loc:Procedencia → expected_procedure parameter ('TEM' | 'DEF') -GSQLFile2.SQL2:C2 → line.customs.origin_procedure -GSQLFile2.SQL2:C1 → line.line_number -EqiFex:TipoFactura → invoice.invoice_type -""" - -from typing import List - -from sqlalchemy.orm import Session - -from api.v1.modules.a76.invoices.models import InvoiceHeader -from api.v1.modules.a76.items.models import LineItem -from core.exceptions import ErrorCollector - - -def review_origin_procedure( - db: Session, - invoice: InvoiceHeader, - lines: List[LineItem], - expected_procedure: str, - errors: ErrorCollector, -) -> None: - """ - REVISA_PROCEDENCIA_PAR - Verifies that every export line's import origin procedure matches - ``expected_procedure``. - - Parameters - ---------- - db : active SQLAlchemy session - invoice : the export invoice being processed - lines : all line items of the invoice - expected_procedure : 'TEM' (regime-change) or 'DEF' (REEXP / VEMEX) - errors : shared error collector - """ - expected = expected_procedure.strip().upper() - - for line in lines: - line_procedure = ( - (line.customs.origin_procedure or "").strip().upper() - if line.customs - else "" - ) - - if line_procedure != expected: - errors.add_error( - field=f"line[{line.line_number}].origin_procedure", - message=( - f"La partida: {line.line_number} tiene una factura de importación " - f"de procedencia: '{line_procedure}', diferente a la que acepta el " - f"Tipo de Factura: '{invoice.invoice_type}'." - ), - solution=[ - "Para Cambio de Régimen todo debe ser procedencia TEM, " - "para Ventas y Reexpediciones debe ser procedencia DEF." - ], - code="INVALID_ORIGIN_PROCEDURE", - value={ - "line_number": line.line_number, - "found": line_procedure, - "expected": expected, - "invoice_type": invoice.invoice_type, - }, - ) diff --git a/backend/api/v1/modules/a76/invoices/exports/process/sub_process/review_qty_vs_weight.py b/backend/api/v1/modules/a76/invoices/exports/process/sub_process/review_qty_vs_weight.py deleted file mode 100644 index 4aa64b87..00000000 --- a/backend/api/v1/modules/a76/invoices/exports/process/sub_process/review_qty_vs_weight.py +++ /dev/null @@ -1,72 +0,0 @@ -""" -REVISA_CANT_vs_PESONETO_KGS / REVISA_CANT_vs_PESONETO_LBS -Validates that the net weight of each export line matches its quantity -when the line's unit of measure is weight-based (KGS or LBS). - -Rule (identical for both variants, only the unit differs): - - KGS: if UnitOfMeasure = 'KGS' → net_weight_kgs must equal quantity - - LBS: if UnitOfMeasure = 'LBS' → net_weight_lbs must equal quantity - -Legacy equivalents ------------------- -KGS: - SELECT COUNT(*) FROM QEqeMaq - WHERE Consecutivo = AND UnidadMedida = 'KGS' AND PesoNetoKGS <> CantExpo - -LBS: - SELECT COUNT(*) FROM QEqeMaq - WHERE Consecutivo = AND UnidadMedida = 'LBS' AND PesoNetoLBS <> CantExpo -""" - -from decimal import Decimal -from typing import List - -from sqlalchemy.orm import Session - -from api.v1.modules.a76.invoices.models import InvoiceHeader -from api.v1.modules.a76.items.models import LineItem -from core.exceptions import ErrorCollector - -_UNIT_KGS = "KGS" -_UNIT_LBS = "LBS" - - -def review_qty_vs_weight( - lines: List[LineItem], - unit_code: str, - errors: ErrorCollector, -) -> None: - """ - Generic validator used by both KGS and LBS variants. - - For every line whose unit of measure code matches ``unit_code``, - checks that the exported quantity equals the net weight. Adds a PESO_NETO error for each mismatch. - - Parameters - ---------- - lines : all LineItem rows for the invoice - unit_code : 'KGS' or 'LBS' — only lines with this UOM are evaluated - errors : collector for validation errors - """ - for line in lines: - uom = line.unit_of_measure_info - if uom is None: - continue - - line_uom_code = (uom.code or "").strip().upper() - if line_uom_code != unit_code: - continue - - if line.quantity is None: - continue - - qty = line.quantity.quantity or Decimal(0) - net_weight = line.quantity.net_weight or Decimal(0) - - if net_weight != qty: - errors.add_error( - field=f"line[{line.line_number}].quantity", - message=f"La cantidad es de {qty} {unit_code} y el Peso Neto es de {net_weight} {unit_code}.", - solution=["Igualar el Peso Neto con la cantidad a Exportar."], - code="PESO_NETO", - ) \ No newline at end of file diff --git a/backend/api/v1/modules/a76/invoices/exports/process/sub_process/review_unit_cost.py b/backend/api/v1/modules/a76/invoices/exports/process/sub_process/review_unit_cost.py deleted file mode 100644 index 91a065d8..00000000 --- a/backend/api/v1/modules/a76/invoices/exports/process/sub_process/review_unit_cost.py +++ /dev/null @@ -1,60 +0,0 @@ -""" -REVISA_COSTOS_0 -Validates that every principal line item (non sub-item) of an export invoice -has a unit cost greater than zero. - -A zero unit cost on a principal line is an error because values and customs -declarations cannot be computed without it. - -Legacy equivalent ------------------ -SELECT COUNT(*) FROM QEqeMaq EqiPex -WHERE EqiPex.Consecutivo = - AND EqiPex.CostoUnitarioCaptura = 0 - AND EqiPex.EsSubPartida = 'P' -- 'P' = Principal (not a sub-item) -""" - -from decimal import Decimal -from typing import List - -from api.v1.modules.a76.items.models import LineItem -from core.exceptions import ErrorCollector - - -def review_unit_cost( - lines: List[LineItem], - errors: ErrorCollector, -) -> None: - """ - REVISA_COSTOS_0 - - For every principal line (``fa_data.is_subitem`` is False or None) checks - that ``financial.unit_cost_capture`` is not zero. Adds a COSTO_CERO error - for each offending line. - - Sub-items are skipped because their cost derives from the principal line - and may legitimately be zero at this stage. - """ - for line in lines: - # Skip sub-items — EsSubPartida = 'P' means is_subitem is False/None - is_subitem = line.fa_data.is_subitem if line.fa_data else False - if is_subitem: - continue - - unit_cost = ( - line.financial.unit_cost_capture - if line.financial - else None - ) - if unit_cost is not None and unit_cost != Decimal(0): - continue - - errors.add_error( - field=f"line[{line.line_number}].unit_cost_capture", - message=f"La partida: {line.line_number} no tiene capturado el costo unitario", - solution=[ - f"Asignar el costo unitario a la partida: {line.line_number}, " - "o desactivar el parámetro de En Base al Costo de Captura." - ], - code="COSTO_CERO", - ) diff --git a/backend/api/v1/modules/a76/invoices/exports/process/sub_process/series/__init__.py b/backend/api/v1/modules/a76/invoices/exports/process/sub_process/series/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/backend/api/v1/modules/a76/invoices/exports/process/sub_process/series/review_qty_series.py b/backend/api/v1/modules/a76/invoices/exports/process/sub_process/series/review_qty_series.py deleted file mode 100644 index decbea05..00000000 --- a/backend/api/v1/modules/a76/invoices/exports/process/sub_process/series/review_qty_series.py +++ /dev/null @@ -1,130 +0,0 @@ -""" -REVISA_CANT_SERIES -Validates series count against export quantity for each line that carries -serial numbers (LlevaSerie = 1 / has_serial = True). - -Rules (ported from legacy SCAII REVISA_CANT_SERIES): - 1. If the line carries series but no series records exist → error SERIES_VACIAS. - 2. If SisGen:CantvsCantSeries = 1: - a. RFC-exception companies (hardcoded set): - - If invoice is a cambio de régimen (is_regime_change): only validate - when the line's unit of measure is 'PZA'. - - Otherwise: always validate count vs quantity. - b. All other companies: always validate count vs quantity. - -Note: the GNiv:CantSerievsCant = 0 block (series > quantity warning) was -commented-out in the original Clarion and is therefore not ported. -""" - -from decimal import Decimal -from typing import List - -from sqlalchemy.orm import Session - -from api.v1.modules.a76.general_catalogs.company.models import Company -from api.v1.modules.a76.invoices.models import InvoiceHeader -from api.v1.modules.a76.items.models import LineItem -from api.v1.modules.a76.items.series.models import Serie -from api.v1.modules.a76.app_settings.service import AppSettingsService -from core.exceptions import ErrorCollector - -# RFCs where qty-vs-series validation is conditional on UOM = PZA when is_regime_change -_RFC_EXCEPCION_PZA = { - "IMS030409FZ0", - "TOP140430PB6", - "AMA7504258K2", - "BZG111091T9", -} - - -def _validate_line_series( - db: Session, - invoice: InvoiceHeader, - line: LineItem, - company_rfc: str, - errors: ErrorCollector, - valida_cant_series: int = 0, -) -> None: - """Validates series count for a single line that has has_serial = True.""" - series_count = ( - db.query(Serie) - .filter(Serie.line_item_id == line.id) - .count() - ) - - # Rule 1: series flag active but no series records exist - if series_count == 0: - errors.add_error( - field=f"line[{line.line_number}].series", - message="La opción de contiene series esta activada y no existen registros de Series", - solution=[ - "Desactivar la opción de Lleva series o registrar las series a esta partida." - ], - code="SERIES_VACIAS", - ) - return - - # Rule 2: quantity vs series count check (controlled by SisGen flag) - if valida_cant_series != 1: - return - - qty = line.quantity.quantity if line.quantity else None - if qty is None: - return - - is_regime_change = bool( - invoice.compliance_mx and invoice.compliance_mx.is_regime_change - ) - - uom_code = "" - if line.unit_of_measure_info: - uom_code = (line.unit_of_measure_info.code or "").strip().upper() - - if company_rfc in _RFC_EXCEPCION_PZA: - # RFC-exception: when cambio de régimen only validate for PZA lines - if is_regime_change and uom_code != "PZA": - return - # For all other companies (and exception RFCs without cambio de régimen), - # always compare count vs quantity - - if series_count != qty: - errors.add_error( - field=f"line[{line.line_number}].series", - message="La Cantidad de Series No Coincide con la Cantidad de la Partida.", - solution=[f"Nivelar las series de la Partida {line.line_number}."], - code="SERIES_VS_CANT", - ) - - -def review_qty_series( - db: Session, - invoice: InvoiceHeader, - lines: List[LineItem], - tenant_id: str, - company_id: str, - errors: ErrorCollector, -) -> None: - """ - REVISA_CANT_SERIES - Iterates all line items and validates series count for those that carry - serial numbers (has_serial = True / LlevaSerie = 1). - """ - company = db.get(Company, company_id) - company_rfc = (company.rfc or "").strip().upper() if company else "" - - settings = AppSettingsService.get_resolved_settings(db, int(tenant_id), int(company_id)) - q_gen = settings.get("qsisgen", {}) - s_gen = settings.get("ssisgen", {}) - - # Switch maestro: validarseries = 0 desactiva toda la validación de series (SSisGen/QSisGen) - valida_series_global = int(q_gen.get("validarseries") or s_gen.get("validarseries", 0)) - if valida_series_global != 1: - return - - valida_cant_series = int(q_gen.get("cantvscantseries") or s_gen.get("cantvscantseries", 0)) - - for line in lines: - if not (line.description and line.description.has_serial): - continue - - _validate_line_series(db, invoice, line, company_rfc, errors, valida_cant_series) diff --git a/backend/api/v1/modules/a76/invoices/exports/process/sub_process/series/review_series_exist.py b/backend/api/v1/modules/a76/invoices/exports/process/sub_process/series/review_series_exist.py deleted file mode 100644 index 014ef8d9..00000000 --- a/backend/api/v1/modules/a76/invoices/exports/process/sub_process/series/review_series_exist.py +++ /dev/null @@ -1,149 +0,0 @@ -""" -REVISA_SERIES_EXISTA -Verifies that every export series marked for discharge (discharge=True / Marca=1) -references a row that actually exists in the corresponding import invoice line. - -Clarion mapping ---------------- -QSeriesExpo → Serie (line_item_id = export LineItem.id) -QSeriesImpo → Serie (line_item_id = import LineItem.id, for TEM invoices) -QSeriesDef → Serie (line_item_id = import LineItem.id, for DEF invoices) - -SerExpo.Marca = 1 → Serie.discharge = True -SerExpo.LineaSerieImpo → Serie.serie_row -SerImp.Renglon / SerDef.Renglon → Serie.row (on the import side) - -Logic ------ -For each export serie with discharge=True on this line, check that -``serie_row`` exists as a ``row`` in the series of the referenced -import invoice line. If it does not → error. - -The check differs by origin_procedure: - TEM → look in import invoice (InvoiceType='TEM') - DEF → look in import invoice (InvoiceType='DEF') -""" - -from typing import List, Set - -from sqlalchemy import select -from sqlalchemy.orm import Session - -from api.v1.modules.a76.invoices.models import InvoiceHeader -from api.v1.modules.a76.items.models import LineItem -from api.v1.modules.a76.items.series.models import Serie -from api.v1.modules.a76.invoices.exports.process.sub_process.discharge_types import ( - DownloadEntry, -) -from core.exceptions import ErrorCollector - - -def review_series_exist( - db: Session, - invoice: InvoiceHeader, - line: LineItem, - entry: DownloadEntry, - errors: ErrorCollector, -) -> None: - """ - REVISA_SERIES_EXISTA - Checks that every export serie marked for discharge on ``line`` references - an import serie row that actually exists in the import invoice line. - - Parameters - ---------- - db : active SQLAlchemy session - invoice : the export invoice being processed - line : the export LineItem whose series are being validated - entry : the DownloadEntry for this line (provides import_invoice / import_line) - errors : shared error collector - """ - # ── 1. Export series with discharge=True on this line ──────────────────── - export_series: List[Serie] = ( - db.execute( - select(Serie).where( - Serie.line_item_id == line.id, - Serie.discharge == True, # noqa: E712 — SQLAlchemy requires == - ) - ) - .scalars() - .all() - ) - - if not export_series: - return - - # ── 2. Resolve the import LineItem ─────────────────────────────────────── - invoice_type_filter = entry.origin_procedure.upper() # 'TEM' or 'DEF' - - import_line_id: int | None = ( - db.execute( - select(LineItem.id) - .join(InvoiceHeader, LineItem.invoice_id == InvoiceHeader.id) - .where( - InvoiceHeader.tenant_id == invoice.tenant_id, - InvoiceHeader.invoice_number == entry.import_invoice, - InvoiceHeader.invoice_type == invoice_type_filter, - LineItem.line_number == entry.import_line, - ) - ) - .scalar_one_or_none() - ) - - if import_line_id is None: - # The import line itself was not found — already caught by fill_available_balances, - # but add a targeted error here as well. - errors.add_error( - field=f"line[{line.line_number}].series", - message=( - f"No se encontró la línea {entry.import_line} de la factura de " - f"importación '{entry.import_invoice}' para validar las series." - ), - solution=["Verificar que la factura y línea de importación existen y están procesadas."], - code="IMPORT_LINE_NOT_FOUND_FOR_SERIES", - ) - return - - # ── 3. Fetch the set of valid import serie rows ─────────────────────────── - valid_rows: Set[int] = set( - db.execute( - select(Serie.row).where( - Serie.line_item_id == import_line_id, - ) - ) - .scalars() - .all() - ) - - # ── 4. Validate each export serie ──────────────────────────────────────── - invoice_type_label = ( - "Impo. Tem." if invoice_type_filter == "TEM" else "Impo. Def." - ) - invoice_type_code = ( - "FAC_IMPO_TEM" if invoice_type_filter == "TEM" else "FAC_IMPO_DEF" - ) - - for serie in export_series: - ref_row = serie.serie_row - - if ref_row is None or ref_row not in valid_rows: - errors.add_error( - field=f"line[{line.line_number}].series[{serie.row}]", - message=( - f"La Línea: {serie.serie_row} " - f"(Serie: {serie.serial_numbers or ''}) " - f"no existe en la Factura de {invoice_type_label}: " - f"'{entry.import_invoice}' con Línea: {entry.import_line}." - ), - solution=[ - "Capturar un número de Serie que exista en la Factura " - "y Línea a Descargar de Importación." - ], - code=invoice_type_code, - value={ - "export_serie_row": serie.row, - "serie_row": ref_row, - "import_invoice": entry.import_invoice, - "import_line": entry.import_line, - }, - ) diff --git a/backend/api/v1/modules/a76/invoices/exports/process/sub_process/series/review_series_other_lines.py b/backend/api/v1/modules/a76/invoices/exports/process/sub_process/series/review_series_other_lines.py deleted file mode 100644 index 076c37dc..00000000 --- a/backend/api/v1/modules/a76/invoices/exports/process/sub_process/series/review_series_other_lines.py +++ /dev/null @@ -1,210 +0,0 @@ -""" -REVISA_SERIES_OTRAS_PAR -Verifies that no export serie marked for discharge is already assigned for -discharge on a different line of the same export invoice. - -Clarion mapping ---------------- -QueueSeries → ``seen_series: set[tuple]`` passed in from the caller. - The set accumulates across all discharge lines so that a serie - registered on line 1 is detected as duplicate when line 2 is - processed. - -SerExpo (QSeriesExpo) → Serie (line_item_id = export LineItem.id) -SerImp (QSeriesImpo) → Serie (line_item_id = import LineItem.id, TEM) -SerDef (QSeriesDef) → Serie (line_item_id = import LineItem.id, DEF) - -Key tuple (equivalent to QueueSeries record used for GET/ADD): - (export_invoice_number, import_line, serial_number, - origin_procedure, serie_row, import_invoice_number) - -Logic ------ -For each export serie with discharge=True on this line: - 1. Build the key tuple. - 2. Resolve ``serie_row`` if blank: - TEM → look up the matching row in QSeriesImpo by serial_number - DEF → look up the matching row in QSeriesDef by serial_number - 3. If the key is already in ``seen_series`` → duplicate error. - 4. Otherwise → add to ``seen_series`` (mark as seen for subsequent lines). -""" - -from typing import Optional, Set, Tuple - -from sqlalchemy import select -from sqlalchemy.orm import Session - -from api.v1.modules.a76.invoices.models import InvoiceHeader -from api.v1.modules.a76.items.models import LineItem -from api.v1.modules.a76.items.series.models import Serie -from api.v1.modules.a76.invoices.exports.process.sub_process.discharge_types import ( - DownloadEntry, -) -from core.exceptions import ErrorCollector - -# Type alias for the duplicate-detection key (equiv. to QueueSeries record) -_SeriesKey = Tuple[str, int, str, str, Optional[int], str] - - -def review_series_other_lines( - db: Session, - invoice: InvoiceHeader, - line: LineItem, - entry: DownloadEntry, - seen_series: Set[_SeriesKey], - errors: ErrorCollector, -) -> None: - """ - REVISA_SERIES_OTRAS_PAR - Checks that no export serie on ``line`` (with discharge=True) is already - registered for discharge on another line of the same invoice. - - Parameters - ---------- - db : active SQLAlchemy session - invoice : the export invoice being processed - line : the export LineItem whose series are being validated - entry : the DownloadEntry for this line - seen_series : mutable set shared across all calls within one invoice - processing run — accumulates keys as lines are processed - errors : shared error collector - """ - export_series = ( - db.execute( - select(Serie).where( - Serie.line_item_id == line.id, - Serie.discharge == True, # noqa: E712 - ) - ) - .scalars() - .all() - ) - - if not export_series: - return - - # Resolve the import line id once (needed for serie row lookup) - import_line_id = _resolve_import_line_id(db, invoice, entry) - - for serie in export_series: - serial = serie.serial_numbers or "" - import_serie_row = serie.serie_row - - # If import_serie_row is not set on the export serie, resolve it from - # the import series table by matching serial_number - if import_serie_row is None and import_line_id is not None: - import_serie_row = _resolve_import_serie_row( - db, import_line_id, serial - ) - - key: _SeriesKey = ( - invoice.invoice_number or "", - entry.import_line, - serial, - entry.origin_procedure, - import_serie_row, - entry.import_invoice, - ) - - if key in seen_series: - # Find which export line already claimed this serie - existing_line = _find_existing_export_line( - db, invoice, line.id, serial, entry - ) - errors.add_error( - field=f"line[{entry.export_line}].series[{serie.row}]", - message=( - f"La Serie: '{serial}' ya fue descargada y está capturada " - f"para ser Descargada en la Partida: {existing_line}." - ), - solution=[ - "Capturar otro número de Serie o capturar el Renglón " - "de la Serie de Importación." - ], - code="SERIE_DUPLICATE_DISCHARGE", - value={ - "serial": serial, - "export_line": entry.export_line, - "conflicting_line": existing_line, - }, - ) - else: - seen_series.add(key) - - -# --------------------------------------------------------------------------- -# Helpers -# --------------------------------------------------------------------------- - -def _resolve_import_line_id( - db: Session, - invoice: InvoiceHeader, - entry: DownloadEntry, -) -> Optional[int]: - """Returns the import LineItem.id for the invoice/line referenced by the entry.""" - from api.v1.modules.a76.invoices.models import InvoiceHeader as IH - from api.v1.modules.a76.items.models import LineItem as LI - - return db.execute( - select(LI.id) - .join(IH, LI.invoice_id == IH.id) - .where( - IH.tenant_id == invoice.tenant_id, - IH.invoice_number == entry.import_invoice, - LI.line_number == entry.import_line, - ) - ).scalar_one_or_none() - - -def _resolve_import_serie_row( - db: Session, - import_line_id: int, - serial_number: str, -) -> Optional[int]: - """ - Looks up the ``row`` of an import serie by serial_number on the given - import line — equivalent to the SQL3 query in the Clarion for both TEM - and DEF cases (both use the same Serie model now). - """ - return db.execute( - select(Serie.row).where( - Serie.line_item_id == import_line_id, - Serie.serial_numbers == serial_number, - ) - ).scalar_one_or_none() - - -def _find_existing_export_line( - db: Session, - invoice: InvoiceHeader, - current_line_id: int, - serial_number: str, - entry: DownloadEntry, -) -> int: - """ - Returns the export_line number of another line on the same invoice that - already has this serial registered for discharge. - Falls back to entry.export_line if not found (shouldn't happen in practice). - """ - from api.v1.modules.a76.items.models import LineItem as LI - - # Find all export lines on this invoice that are not the current one - other_line_ids = db.execute( - select(LI.id, LI.line_number).where( - LI.invoice_id == invoice.id, - LI.id != current_line_id, - ) - ).all() - - for row in other_line_ids: - match = db.execute( - select(Serie.id).where( - Serie.line_item_id == row.id, - Serie.serial_numbers == serial_number, - Serie.discharge == True, # noqa: E712 - ) - ).scalar_one_or_none() - if match is not None: - return row.line_number - - return entry.export_line diff --git a/backend/api/v1/modules/a76/invoices/exports/process/sub_process/verify_consolidated.py b/backend/api/v1/modules/a76/invoices/exports/process/sub_process/verify_consolidated.py deleted file mode 100644 index 2a00a41c..00000000 --- a/backend/api/v1/modules/a76/invoices/exports/process/sub_process/verify_consolidated.py +++ /dev/null @@ -1,82 +0,0 @@ -""" -VERIFICAQCONSOLIDADO -Second-pass check after COMPARA_SALDOS_POR_FACTURA: iterates every discharge -entry and reports an error for any line that still has unmet quantity -(QADesc:Cantidad - QADesc:CantUsada <> 0). - -The Clarion routine distinguishes TEM vs DEF in the error message; this -translation preserves that distinction. - -Note: compare_balances already raises INSUFFICIENT_BALANCE errors per entry. -This routine acts as a final consolidation gate — if compare_balances is -called with raise_if_errors() afterwards, this function may be redundant in -practice. It is kept as a faithful port and can serve as the sole -insufficient-balance check if compare_balances is ever made non-raising. -""" - -from typing import List - -from sqlalchemy.orm import Session - -from api.v1.modules.a76.invoices.models import InvoiceHeader -from core.exceptions import ErrorCollector -from .discharge_types import DownloadEntry - - -def verify_consolidated( - db: Session, - export_invoice: InvoiceHeader, - to_discharge: List[DownloadEntry], - errors: ErrorCollector, -) -> None: - """ - VERIFICAQCONSOLIDADO - Reports an error for every discharge entry whose quantity was not fully - satisfied by ``compare_balances``. - - Parameters - ---------- - db : active SQLAlchemy session - export_invoice : the export invoice being processed - to_discharge : list of DownloadEntry objects after compare_balances ran - errors : shared error collector - """ - # Sort mirrors Clarion: Sort(QueADescargar, -Procedencia, FacturaImpo, LineaImpo) - sorted_entries = sorted( - to_discharge, - key=lambda e: (e.origin_procedure, e.import_invoice, e.import_line), - reverse=True, - ) - - for entry in sorted_entries: - remaining = entry.quantity - entry.quantity_used - if remaining == 0: - continue - - uom = entry.unit_of_measure or "" - procedure = (entry.origin_procedure or "").strip().upper() - - if procedure == "TEM": - message = ( - f"Insuficiencia TEM: La Linea: {entry.export_line} se quiere " - f"descargar: {entry.quantity} {uom} y hay: {entry.quantity_used} {uom}." - ) - else: # DEF or any other - message = ( - f"Insuficiencia DEF.: La Linea: {entry.export_line} se quiere " - f"descargar: {entry.quantity} {uom} y hay: {entry.quantity_used} {uom}." - ) - - errors.add_error( - field=f"line[{entry.export_line}].quantity", - message=message, - solution=["Asignar Facturas con Saldos Disponibles."], - code="INSUFFICIENT_BALANCE_CONSOLIDATED", - value={ - "export_line": entry.export_line, - "required": str(entry.quantity), - "available": str(entry.quantity_used), - "shortage": str(remaining), - "origin_procedure": procedure, - }, - ) diff --git a/backend/api/v1/modules/a76/invoices/exports/process/task.py b/backend/api/v1/modules/a76/invoices/exports/process/task.py deleted file mode 100644 index 935b58ff..00000000 --- a/backend/api/v1/modules/a76/invoices/exports/process/task.py +++ /dev/null @@ -1,57 +0,0 @@ -from celery import Task - -from core.celery_app import celery_app -from core.database import scoped_core_db -from core.exceptions import ValidationException - -from api.v1.modules.a76.invoices.models import InvoiceHeader, InvoiceStatus -from .main_process import main_process - - -def _progress(task: Task, current: int, status: str) -> None: - task.update_state(state="PROGRESS", meta={"current": current, "status": status}) - - -@celery_app.task(bind=True, name="process_export_invoice_task") -def process_export_invoice_task(self: Task, invoice_id: int, tenant_id: str, company_id: str, username: str = "SYSTEM") -> dict: - """ - Procesa una factura de exportación ejecutando todas las validaciones y - actualizaciones del proceso principal de exportación con reporte de progreso. - """ - with scoped_core_db(tenant_id=int(tenant_id), company_id=int(company_id)) as db: - try: - _progress(self, 5, "Cargando factura...") - invoice: InvoiceHeader | None = db.get(InvoiceHeader, invoice_id) - - if invoice is None: - return { - "status": "error", - "message": f"Factura con id {invoice_id} no encontrada.", - "errors": [], - } - - _progress(self, 10, "Verificando estatus de seguridad...") - if invoice.status == InvoiceStatus.PROCESSED: - return { - "status": "error", - "message": f"La factura {invoice.invoice_number} ya se encuentra procesada.", - "errors": [{"field": "status", "message": "Factura ya procesada."}], - } - - _progress(self, 15, "Iniciando proceso principal de exportación...") - result = main_process(db, invoice, tenant_id, company_id, username=username) - - db.commit() - _progress(self, 100, "Proceso completado.") - return {**result, "invoice_id": invoice_id} - - except ValidationException as exc: - db.rollback() - return { - "status": "validation_error", - "message": exc.message, - "errors": exc.errors, - } - except Exception as exc: - db.rollback() - raise exc diff --git a/backend/api/v1/modules/a76/invoices/exports/revert/main_process.py b/backend/api/v1/modules/a76/invoices/exports/revert/main_process.py deleted file mode 100644 index 70e23ada..00000000 --- a/backend/api/v1/modules/a76/invoices/exports/revert/main_process.py +++ /dev/null @@ -1,391 +0,0 @@ -from api.v1.modules.a76.app_settings.service import AppSettingsService -import datetime -from decimal import Decimal -from typing import List, Optional - -from sqlalchemy import select -from sqlalchemy.orm import Session - -from api.v1.modules.a24.discharges.models import ( - DischargeDetail, - DischargeHeader, - DischargeStatus, -) -from api.v1.modules.a24.balance_movements.models import BalanceMovement, MovementType -from api.v1.modules.a76.invoices.models import ( - InvoiceHeader, - InvoiceStatus, - OperationType, -) -from api.v1.modules.a76.items.models import LineItem -from api.v1.modules.a76.items.series.models import Serie -from core.exceptions import ErrorCollector - -_VAT_CUTOFF = datetime.date(2014, 12, 31) # Clarion day 78165 - - -def _validate_regime_change_definitive_invoice_exists( - db: Session, - invoice: InvoiceHeader, - errors: ErrorCollector, -) -> None: - # Robust detection logic (same as generator) - is_cr = False - if invoice.compliance_mx and invoice.compliance_mx.is_regime_change: - is_cr = True - elif (invoice.invoice_type or "").strip().upper() == "CR": - is_cr = True - elif invoice.operation_type == OperationType.EXP: - if invoice.compliance_mx and invoice.compliance_mx.pedimento: - if (invoice.compliance_mx.pedimento.pedimento_code or "").strip().upper() == "F4": - is_cr = True - - if not is_cr: - return - - if not invoice.invoice_number: - return - - exists = ( - db.query(InvoiceHeader.id) - .filter( - InvoiceHeader.tenant_id == invoice.tenant_id, - InvoiceHeader.company_id == invoice.company_id, - InvoiceHeader.operation_type == OperationType.IMP, - InvoiceHeader.invoice_type == "DEF", # Importación Definitiva generada - InvoiceHeader.invoice_number == invoice.invoice_number, - ) - .first() - is not None - ) - - if exists: - errors.add_error( - field="invoice_number", - message=( - "Error: Existe una Factura de Importación Definitiva a partir " - "de esta Factura." - ), - solution=[ - f"Desactualizar y borrar la Factura: {invoice.invoice_number} " - "de Importación Definitiva." - ], - code="DEFINITIVE_IMPORT_ALREADY_EXISTS", - value=invoice.invoice_number, - ) - - -def _check_access_lock(invoice: InvoiceHeader) -> None: - """ - Clarion block against GAccesosModulos (security lock by terminal/user). - Currently implemented as status-based concurrency lock in the Celery task. - """ - _ = invoice - - - -def _return_discharged_quantities( - db: Session, - export_invoice: InvoiceHeader, - lines: List[LineItem], -) -> None: - """ - REGRESA_CANT_RETORNADAS - Returns discharged quantities/values to their source import invoice lines. - - Uses the new specialized discharge tables: - a24.discharge_header + a24.discharge_detail - - We only revert details that belong to this export invoice and are applied. - This guarantees parity with the actual discharge ledger (instead of relying - on editable UI references in fa_data). - """ - _ = lines # source of truth is discharge tables - - details: List[DischargeDetail] = ( - db.query(DischargeDetail) - .join(DischargeHeader, DischargeHeader.id == DischargeDetail.discharge_header_id) - .filter( - DischargeHeader.source_invoice_id == export_invoice.id, - DischargeHeader.status == DischargeStatus.APPLIED, - DischargeDetail.tenant_id == export_invoice.tenant_id, - DischargeDetail.company_id == export_invoice.company_id, - ) - .all() - ) - - for detail in details: - qty_exported = Decimal(str(detail.quantity_discharged or 0)) - if qty_exported <= 0: - continue - - import_line = db.get(LineItem, detail.import_item_line_id) - if ( - import_line is None - or import_line.quantity is None - or import_line.financial is None - ): - continue - - import_invoice = db.get(InvoiceHeader, import_line.invoice_id) - if import_invoice is None: - continue - - qty_rec = import_line.quantity - fin = import_line.financial - original_qty = Decimal(str(qty_rec.quantity or 0)) - if original_qty == 0: - continue - - value_mxn = Decimal(str(fin.value_mxn or 0)) - value_usd = Decimal(str(fin.value_usd or 0)) - returned_mxn = qty_exported * value_mxn / original_qty - returned_usd = qty_exported * value_usd / original_qty - - # Reverse monetary returned values - # Clarion shows '-' for TEM and '+' for DEF; in the current ledger migration, - # register_import_discharge adds both TEM/DEF, so revert subtracts both. - fin.value_returned_mxn = (fin.value_returned_mxn or Decimal(0)) - returned_mxn - fin.value_returned_usd = (fin.value_returned_usd or Decimal(0)) - returned_usd - - # TEM VAT recalculation by header date cutoff - if (detail.procedence or "").upper() == "TEM": - inv_date = import_invoice.invoice_date - if isinstance(inv_date, datetime.datetime): - inv_date = inv_date.date() - if inv_date and inv_date >= _VAT_CUTOFF: - iva_factor = Decimal(0) - if import_invoice.financials and import_invoice.financials.iva_factor: - iva_factor = Decimal(str(import_invoice.financials.iva_factor)) - fin.vat_used_mxn = (fin.value_returned_mxn or Decimal(0)) * iva_factor / 100 - fin.vat_used_usd = (fin.value_returned_usd or Decimal(0)) * iva_factor / 100 - else: - fin.vat_used_mxn = Decimal(0) - fin.vat_used_usd = Decimal(0) - - -def _unmark_returned_series( - db: Session, - export_invoice: InvoiceHeader, - lines: List[LineItem], -) -> None: - """ - DESMARCA_SERIES_RETORNADAS - Resets import serie discharge marks that were set by this export invoice. - - New data-source logic: - - Uses a24.discharge_header/detail to identify which import lines were - consumed by this export invoice. - - Uses a76.item_line_series on export lines (discharge=True) to resolve the - import serie row (serie_row) or by serial number fallback. - """ - line_ids = [ln.id for ln in lines if ln.id is not None] - if not line_ids: - return - - # All discharge details belonging to this export invoice, grouped by export line. - details: List[DischargeDetail] = ( - db.query(DischargeDetail) - .join(DischargeHeader, DischargeHeader.id == DischargeDetail.discharge_header_id) - .filter( - DischargeHeader.source_invoice_id == export_invoice.id, - DischargeHeader.status == DischargeStatus.APPLIED, - DischargeDetail.export_item_line_id.in_(line_ids), - DischargeDetail.tenant_id == export_invoice.tenant_id, - DischargeDetail.company_id == export_invoice.company_id, - ) - .all() - ) - - import_lines_by_export: dict[int, set[int]] = {} - for d in details: - if d.export_item_line_id is None: - continue - import_lines_by_export.setdefault(d.export_item_line_id, set()).add(d.import_item_line_id) - - if not import_lines_by_export: - return - - # Export series marked for discharge (equivalent to SerExpo.Marca = 1) - export_series: List[Serie] = ( - db.execute( - select(Serie).where( - Serie.line_item_id.in_(line_ids), - Serie.discharge == True, # noqa: E712 - ) - ) - .scalars() - .all() - ) - - for ex_serie in export_series: - candidate_import_lines = import_lines_by_export.get(ex_serie.line_item_id, set()) - if not candidate_import_lines: - continue - - import_serie: Optional[Serie] = None - # 1) Prefer explicit mapped row from export serie - if ex_serie.serie_row is not None: - import_serie = db.execute( - select(Serie).where( - Serie.line_item_id.in_(candidate_import_lines), - Serie.row == ex_serie.serie_row, - ) - ).scalar_one_or_none() - - # 2) Fallback by serial number if row is absent - if import_serie is None and ex_serie.serial_numbers: - import_serie = db.execute( - select(Serie).where( - Serie.line_item_id.in_(candidate_import_lines), - Serie.serial_numbers == ex_serie.serial_numbers, - ) - ).scalar_one_or_none() - - if import_serie is None: - continue - - # Clarion equivalent: SerImp:SerieExpo = 0 / SerDef:SerieExpo = 0 - import_serie.discharge = False - - -def _cancel_discharge_records( - db: Session, - export_invoice: InvoiceHeader, - cancelled_by: Optional[str] = None, -) -> None: - """ - Cancels discharge records created by this export invoice in the new - specialized tables: - - a24.discharge_header: status -> CANCELLED - - a24.balance_movement: insert RETURN per discharge_detail row - - This is the ledger-safe equivalent of undoing "Descarga=1" effects. - """ - headers: List[DischargeHeader] = ( - db.query(DischargeHeader) - .filter( - DischargeHeader.source_invoice_id == export_invoice.id, - DischargeHeader.tenant_id == export_invoice.tenant_id, - DischargeHeader.company_id == export_invoice.company_id, - DischargeHeader.status.in_([DischargeStatus.APPLIED, DischargeStatus.PARTIAL]), - ) - .all() - ) - if not headers: - return - - op_date = ( - export_invoice.invoice_date.date() - if hasattr(export_invoice.invoice_date, "date") - else export_invoice.invoice_date - ) - - for header in headers: - details: List[DischargeDetail] = ( - db.query(DischargeDetail) - .filter( - DischargeDetail.discharge_header_id == header.id, - DischargeDetail.tenant_id == export_invoice.tenant_id, - DischargeDetail.company_id == export_invoice.company_id, - ) - .all() - ) - - for detail in details: - # Reverse each consumed lot with a RETURN movement (append-only ledger) - ret_mov = BalanceMovement( - tenant_id=export_invoice.tenant_id, - company_id=export_invoice.company_id, - import_invoice_id=detail.import_line.invoice_id if detail.import_line else None, - import_item_line_id=detail.import_item_line_id, - part_number_id=None, - movement_type=MovementType.RETURN, - quantity=detail.quantity_discharged or Decimal(0), - value_me=detail.value_me, - value_mn=detail.value_mn, - net_weight=detail.net_weight, - source_invoice_id=export_invoice.id, - source_item_line_id=detail.export_item_line_id, - order_peps=0, # set post-flush - operation_date=op_date, - notes=f"Reversa descargo factura {export_invoice.invoice_number} (header {header.id})", - ) - db.add(ret_mov) - db.flush() - ret_mov.order_peps = ret_mov.id - - header.status = DischargeStatus.CANCELLED - header.cancelled_by = cancelled_by - header.cancellation_reason = ( - f"Des-actualización de factura de exportación {export_invoice.invoice_number}" - ) - - -def _set_invoice_unprocessed(invoice: InvoiceHeader, line_count: int) -> None: - """ - Clarion mapping: - EqiFex:ComofueProcesada='' - EqiFex:Estatus='NA' - EqiFex:Cant_Partidas = Loc:PartidasExpo - """ - invoice.process_log = None - invoice.status = InvoiceStatus.REVERSED - invoice.party_count = line_count - - -def revert_process( - db: Session, - invoice: InvoiceHeader, - lines: List[LineItem], - tenant_id: str, - company_id: str, - errors: ErrorCollector, - cancelled_by: Optional[str] = None, -) -> list: - """ - Des-actualización de factura de exportación (paridad Clarion). - - Notes: - - BEGIN/COMMIT/ROLLBACK SQL explícitos del Clarion se controlan con la - transacción de SQLAlchemy en el task (commit/rollback externo). - - Las rutinas Clarion invocadas con DO se dejan como TODO por ahora. - """ - _ = (db, tenant_id, company_id) # reserved for future TO DO implementations - sql_errors: list = [] - - # PROCESO DE REVERSIÓN - _check_access_lock(invoice) - - # VERIFICAR SI HAY PARTIDAS DE EXPORTACION - line_count = len(lines) - - # VALIDACION DEL CAMBIO DE REGIMEN - _validate_regime_change_definitive_invoice_exists(db, invoice, errors) - errors.raise_if_errors() - - # VALIDACIONES DE PARTIDAS NORMAL <> REPARACION - if (invoice.invoice_type or "").upper() != "NODES": - _return_discharged_quantities(db, invoice, lines) - _unmark_returned_series(db, invoice, lines) - _cancel_discharge_records(db, invoice, cancelled_by) - - _set_invoice_unprocessed(invoice, line_count) - - # Proceso finalizado correctamente - - # Auditoría de Desactualización - settings = AppSettingsService.get_resolved_settings(db, int(tenant_id), int(company_id)) - q_gen = settings.get("qsisgen", {}) - s_gen = settings.get("ssisgen", {}) - act_seguridad = int(q_gen.get("actseguridad") or s_gen.get("actseguridad") or q_gen.get("ActSeguridad") or s_gen.get("ActSeguridad", 0)) - - if act_seguridad == 1: - from api.v1.modules.a76.audit_log.services.service import AuditService - AuditService.create_audit_log( - db=db, reference=invoice.invoice_number, procedure="ANULAR FACTURA", movement="DESACTUALIZACION", - username=cancelled_by or "SYSTEM", tenant_id=int(tenant_id), company_id=int(company_id), - table_name="invoice_header", record_id=str(invoice.id), operation_type="UPDATE" - ) - - return sql_errors diff --git a/backend/api/v1/modules/a76/invoices/exports/revert/pre_validators.py b/backend/api/v1/modules/a76/invoices/exports/revert/pre_validators.py deleted file mode 100644 index cc9b6f8e..00000000 --- a/backend/api/v1/modules/a76/invoices/exports/revert/pre_validators.py +++ /dev/null @@ -1,42 +0,0 @@ -from typing import List - -from sqlalchemy.orm import Session - -from api.v1.modules.a76.invoices.models import InvoiceHeader, InvoiceStatus -from api.v1.modules.a76.items.models import LineItem -from core.exceptions import ErrorCollector - - -def pre_validators( - db: Session, - invoice: InvoiceHeader, - tenant_id: str, - company_id: str, - errors: ErrorCollector, -) -> List[LineItem]: - """ - Validaciones previas a la reversión de una factura de importación temporal. - - - Verifica que la factura esté en estatus PROCESSED. - - Carga y retorna las partidas asociadas a la factura. - """ - if invoice.status != InvoiceStatus.PROCESSED: - errors.add_error( - "status", - "La factura no fue procesada y no puede ser revertida", - solution=["Verifique el estatus de la factura antes de intentar deshacer el proceso"], - code="NOT_PROCESSED", - value=invoice.status, - ) - - lines: List[LineItem] = ( - db.query(LineItem) - .filter( - LineItem.invoice_id == invoice.id, - LineItem.tenant_id == tenant_id, - LineItem.company_id == company_id, - ) - .all() - ) - - return lines diff --git a/backend/api/v1/modules/a76/invoices/exports/revert/routes.py b/backend/api/v1/modules/a76/invoices/exports/revert/routes.py deleted file mode 100644 index ad50c3a7..00000000 --- a/backend/api/v1/modules/a76/invoices/exports/revert/routes.py +++ /dev/null @@ -1,92 +0,0 @@ -from typing import Any, Dict - -from fastapi import APIRouter, Depends, HTTPException, Query -from sqlalchemy.orm import Session - -from core.celery_app import celery_app -from core.database import get_core_db -from core.security import get_current_user, validate_access_to_resource -from api.v1.modules.core.tasks_tracking import track_and_dispatch - -from .task import revert_invoice_task - -router = APIRouter() - - -@router.post("/invoices/{invoice_id}/revert") -def trigger_invoice_revert( - invoice_id: int, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """ - Inicia la des-actualización de una factura de importación temporal como - tarea Celery. - Retorna el task_id para hacer polling del progreso. - """ - tenant_id = validate_access_to_resource(db, company_id, current_user) - cancelled_by = ( - current_user.get("username") - or current_user.get("user_name") - or current_user.get("preferred_username") - or current_user.get("email") - or "system" - ) - - task = track_and_dispatch( - db=db, - task=revert_invoice_task, - tenant_id=tenant_id, - company_id=company_id, - requested_by_user=str(cancelled_by), - task_name="revert_export_invoice_task", - task_group="invoices", - task_origin="a76/invoices/exports/revert", - args=[invoice_id, str(tenant_id), str(company_id), str(cancelled_by)], - ) - - return {"task_id": task.id} - - -@router.get("/invoices/revert/{task_id}/status") -def get_invoice_revert_status(task_id: str): - """ - Consulta el estado de progreso de una tarea de des-actualización de - factura. - - Retorna: - - state: 'PROCESSING' | 'SUCCESS' | 'FAILURE' - - info: { current: int, status: str } (cuando state == 'PROCESSING') - - result: dict (cuando state == 'SUCCESS' o 'FAILURE') - """ - task_result = celery_app.AsyncResult(task_id) - - if task_result.state in ("PENDING", "STARTED"): - return { - "state": "PROCESSING", - "info": {"current": 0, "status": "Iniciando..."}, - } - - if task_result.state == "PROGRESS": - return { - "state": "PROCESSING", - "info": task_result.info or {"current": 0, "status": "Procesando..."}, - } - - if task_result.state == "SUCCESS": - return { - "state": "SUCCESS", - "result": task_result.result, - } - - error_info = task_result.result - if isinstance(error_info, Exception): - error_msg = str(error_info) - else: - error_msg = str(error_info) if error_info else "Error desconocido" - - return { - "state": "FAILURE", - "result": error_msg, - } diff --git a/backend/api/v1/modules/a76/invoices/exports/revert/task.py b/backend/api/v1/modules/a76/invoices/exports/revert/task.py deleted file mode 100644 index e2e8ead4..00000000 --- a/backend/api/v1/modules/a76/invoices/exports/revert/task.py +++ /dev/null @@ -1,92 +0,0 @@ -from celery import Task - -from core.celery_app import celery_app -from core.database import scoped_core_db -from core.exceptions import ErrorCollector, ValidationException - -from api.v1.modules.a76.invoices.models import InvoiceHeader -from .pre_validators import pre_validators -from .main_process import revert_process - - -def _progress(task: Task, current: int, status: str) -> None: - task.update_state(state="PROGRESS", meta={"current": current, "status": status}) - - -@celery_app.task(bind=True, name="revert_export_invoice_task") -def revert_invoice_task( - self: Task, - invoice_id: int, - tenant_id: str, - company_id: str, - cancelled_by: str | None = None, -) -> dict: - """ - Des-actualiza una factura de importación temporal ejecutando todas las - validaciones y reversiones del proceso principal (revert/main_process) con - reporte de progreso. - """ - with scoped_core_db(tenant_id=int(tenant_id), company_id=int(company_id)) as db: - try: - _progress(self, 5, "Cargando factura...") - invoice: InvoiceHeader | None = db.get(InvoiceHeader, invoice_id) - if invoice is None: - return { - "status": "error", - "message": f"Factura con id {invoice_id} no encontrada.", - "errors": [], - } - - _progress(self, 10, "Verificando estatus de seguridad...") - from api.v1.modules.a76.invoices.models import InvoiceStatus - if invoice.status != InvoiceStatus.PROCESSED: - return { - "status": "error", - "message": f"La factura {invoice.invoice_number} no se puede revertir porque no está procesada.", - "errors": [{"field": "status", "message": "Factura no procesada."}], - } - - errors = ErrorCollector() - - _progress(self, 10, "Validando estatus de la factura...") - lines = pre_validators(db, invoice, tenant_id, company_id, errors) - if not lines: - errors.add_error( - field="line_items", - message="La factura no contiene partidas para revertir", - solution=["Verifique que la factura tenga partidas antes de intentar revertirla"], - code="NO_LINE_ITEMS", - ) - errors.raise_if_errors() - - _progress(self, 40, "Verificando saldos de partidas...") - sql_errors = revert_process( - db=db, - invoice=invoice, - lines=lines, - tenant_id=tenant_id, - company_id=company_id, - errors=errors, - cancelled_by=cancelled_by, - ) - - _progress(self, 95, "Anulando saldos de inventario y confirmando...") - db.flush() - db.commit() - - return { - "status": "success", - "invoice_id": invoice_id, - "sql_errors": sql_errors, - } - - except ValidationException as exc: - db.rollback() - return { - "status": "validation_error", - "message": exc.message, - "errors": exc.errors, - } - except Exception as exc: - db.rollback() - raise exc diff --git a/backend/api/v1/modules/a76/invoices/exports/validators/create.py b/backend/api/v1/modules/a76/invoices/exports/validators/create.py deleted file mode 100644 index a5fc89c7..00000000 --- a/backend/api/v1/modules/a76/invoices/exports/validators/create.py +++ /dev/null @@ -1,85 +0,0 @@ -from sqlalchemy.orm import Session - -from api.v1.modules.a76.general_catalogs.exchange_rate.models import ExchangeRate -from core.exceptions import ErrorCollector -from ...schemas import InvoiceHeaderCreate -from ...common.common_validators import validate_common, validate_required_fields_by_operation - -def validate_create(db: Session, invoice: InvoiceHeaderCreate, tenant_id: int, company_id: int, errors: ErrorCollector) -> None: - """ Valida la creación de una nueva factura de importe temporal """ - - if not invoice.operation_type: - errors.add_required_error("operation_type") - - if not invoice.invoice_type: - errors.add_required_error("invoice_type") - - if not invoice.document_type and invoice.invoice_type != "MEX": - errors.add_required_error("document_type") - - if not invoice.invoice_number: - errors.add_required_error("invoice_number") - - if not invoice.invoice_date: - errors.add_required_error("invoice_date") - - # Validar campos obligatorios según tipo de operación - invoice_data = { - 'provider_header': invoice.compliance_mx.provider_header if invoice.compliance_mx else None, - 'provider_id': invoice.compliance_mx.provider_id if invoice.compliance_mx else None, - 'sold_to_header': invoice.compliance_mx.sold_to_header if invoice.compliance_mx else None, - 'sold_to_id': invoice.compliance_mx.sold_to_id if invoice.compliance_mx else None, - 'shipped_to_header': invoice.compliance_mx.shipped_to_header if invoice.compliance_mx else None, - 'shipped_to_id': invoice.compliance_mx.shipped_to_id if invoice.compliance_mx else None, - 'customs_broker_id': invoice.compliance_mx.customs_broker_id if invoice.compliance_mx else None, - 'pedimento_id': invoice.compliance_mx.pedimento_id if invoice.compliance_mx else None, - } - - validate_required_fields_by_operation( - invoice_data=invoice_data, - operation_type=invoice.operation_type, - errors=errors - ) - - if errors.has_errors(): - """Se retorna porque hay campos obligatorios según el tipo de operación que deben ser llenados""" - return - - validate_common(db, invoice, tenant_id, company_id, errors) - - if errors.has_errors(): - """Se retorna por que fallaron las validaciones generales""" - return - - if invoice.compliance_mx: - if not invoice.compliance_mx.pedimento_id: - invoice.compliance_mx.remesa = None - - if invoice.financials: - if not invoice.financials.exchange_rate: - invoice.financials.exchange_rate = db.query(ExchangeRate.value).filter(ExchangeRate.date == invoice.invoice_date).scalar() - - invoice.document_type = (invoice.document_type or "").upper() - - if invoice.logistics: - if not invoice.logistics.transport_type: - invoice.logistics.transport_type = "none" - - if invoice.logistics.transport_type == "none" and invoice.logistics.transport_num: - invoice.logistics.transport_num = None - - invoice.logistics.incoterm = (invoice.logistics.incoterm or "").upper() - - if not invoice.logistics.weight_type: - invoice.logistics.weight_type = "kgs" - - if invoice.financials: - if not invoice.financials.currency: - invoice.financials.currency = "foreign" - - if invoice.financials.currency == "local": - invoice.financials.currency_type = "MXN" - elif invoice.financials.currency == "foreign": - invoice.financials.currency_type = "USD" - elif invoice.financials.currency == "manual": - invoice.financials.currency_type = (invoice.financials.currency_type or "").upper() \ No newline at end of file diff --git a/backend/api/v1/modules/a76/invoices/exports/validators/update.py b/backend/api/v1/modules/a76/invoices/exports/validators/update.py deleted file mode 100644 index c6371453..00000000 --- a/backend/api/v1/modules/a76/invoices/exports/validators/update.py +++ /dev/null @@ -1,286 +0,0 @@ -from typing import Optional -from datetime import date -from decimal import Decimal - -from sqlalchemy.orm import Session - -from ...common.common_validators import validate_common, validate_required_fields_by_operation -from core.exceptions import ErrorCollector -from ...schemas import InvoiceHeaderUpdate -from ...models import InvoiceHeader - -# Helper function para limpiar strings (equivalente a Clip()) -def clean_str(value: Optional[str]) -> Optional[str]: - if value is None or value == "": - return None - return value.strip() - -def validate_update( - db: Session, - invoice: InvoiceHeaderUpdate, - existing_invoice: InvoiceHeader, - tenant_id: int, - company_id: int, - errors: ErrorCollector, -) -> None: - """ - Valida y procesa la actualización parcial de una factura de exportación. - - Lógica: Si un campo viene con valor, se limpia/valida. - Si no, se mantiene el valor existente de la factura. - - Args: - invoice: Datos de la factura a validar/actualizar (modificado in-place) - existing_invoice: Factura existente en la base de datos - errors: Colector de errores - - Returns: - None (modifica invoice in-place y acumula errores en errors) - """ - - # Validar campos requeridos según el tipo de operación - invoice_dict = { - 'provider_id': invoice.compliance_mx.provider_id if invoice.compliance_mx else (existing_invoice.compliance_mx.provider_id if existing_invoice.compliance_mx else None), - 'sold_to_id': invoice.compliance_mx.sold_to_id if invoice.compliance_mx else (existing_invoice.compliance_mx.sold_to_id if existing_invoice.compliance_mx else None), - 'sold_to_header': invoice.compliance_mx.sold_to_header if invoice.compliance_mx else (existing_invoice.compliance_mx.sold_to_header if existing_invoice.compliance_mx else None), - 'shipped_to_id': invoice.compliance_mx.shipped_to_id if invoice.compliance_mx else (existing_invoice.compliance_mx.shipped_to_id if existing_invoice.compliance_mx else None), - 'customs_broker_id': invoice.compliance_mx.customs_broker_id if invoice.compliance_mx else (existing_invoice.compliance_mx.customs_broker_id if existing_invoice.compliance_mx else None), - 'pedimento_id': invoice.compliance_mx.pedimento_id if invoice.compliance_mx else (existing_invoice.compliance_mx.pedimento_id if existing_invoice.compliance_mx else None), - } - - validate_required_fields_by_operation( - invoice_data=invoice_dict, - operation_type=invoice.operation_type or (existing_invoice.operation_type or 'imp'), - errors=errors - ) - - # Primero ejecutar validaciones comunes - validate_common( - db, invoice, tenant_id, company_id, errors, existing_invoice=existing_invoice - ) - - # Mapeo de columnas CSV a campos de la factura - # Siguiendo la lógica del código Clarion original - - # Columna A: Pedimento (si no viene en CSV, usar el existente) - if invoice.compliance_mx: - if invoice.compliance_mx.pedimento_id: - invoice.compliance_mx.pedimento_id = invoice.compliance_mx.pedimento_id - else: - invoice.compliance_mx.pedimento_id = existing_invoice.compliance_mx.pedimento_id if existing_invoice.compliance_mx else None - - # Columna B: Remesa - if invoice.compliance_mx: - if invoice.compliance_mx.remesa: - invoice.compliance_mx.remesa = invoice.compliance_mx.remesa - else: - invoice.compliance_mx.remesa = existing_invoice.compliance_mx.remesa if existing_invoice.compliance_mx else None - - # Columna C: Factura (OBLIGATORIO) - if invoice.invoice_number is not None: - invoice.invoice_number = clean_str(invoice.invoice_number) - if not invoice.invoice_number: - errors.add_required_error("invoice_number") - else: - invoice.invoice_number = existing_invoice.invoice_number - - # Columna D: Fecha - if not invoice.invoice_date: - invoice.invoice_date = existing_invoice.invoice_date - - # Columna E: Tipo Cambio - if invoice.financials: - if invoice.financials.exchange_rate is None: - if existing_invoice.financials: - invoice.financials.exchange_rate = existing_invoice.financials.exchange_rate - - # Columna F: Régimen - if invoice.document_type: - invoice.document_type = clean_str(invoice.document_type).upper() - else: - invoice.document_type = existing_invoice.document_type - - # Columna G: Clave Proveedor - if invoice.compliance_mx: - if invoice.compliance_mx.provider_id is None: - invoice.compliance_mx.provider_id = existing_invoice.compliance_mx.provider_id if existing_invoice.compliance_mx else None - - # Columna H: Clave Vendido A - if invoice.compliance_mx: - if invoice.compliance_mx.sold_to_id is None: - invoice.compliance_mx.sold_to_id = existing_invoice.compliance_mx.sold_to_id if existing_invoice.compliance_mx else None - - # Columna I: Clave Enviado A - if invoice.compliance_mx: - if invoice.compliance_mx.shipped_to_id is None: - invoice.compliance_mx.shipped_to_id = existing_invoice.compliance_mx.shipped_to_id if existing_invoice.compliance_mx else None - - # Columna J: Clave A. Aduanal - if invoice.compliance_mx: - if invoice.compliance_mx.customs_broker_id is None: - invoice.compliance_mx.customs_broker_id = existing_invoice.compliance_mx.customs_broker_id if existing_invoice.compliance_mx else None - - # Columna K: Clave Transportista - if invoice.logistics: - # Note: logistics in update schema seems to be a single object, but in model it's a list. - # This validator seems to expect a single object (InvoiceLogisticsUpdate). - # We'll stick to the existing logic but make it safe. - if hasattr(invoice.logistics, 'carrier_id') and invoice.logistics.carrier_id is None: - invoice.logistics.carrier_id = existing_invoice.logistics.carrier_id if existing_invoice.logistics else None - - # Columna L: Nombre Conductor - if invoice.logistics: - if hasattr(invoice.logistics, 'driver_name') and not invoice.logistics.driver_name: - invoice.logistics.driver_name = existing_invoice.logistics.driver_name if existing_invoice.logistics else None - elif hasattr(invoice.logistics, 'driver_name'): - invoice.logistics.driver_name = clean_str(invoice.logistics.driver_name) - - # Columna M: Tipo Transporte - if invoice.logistics: - if hasattr(invoice.logistics, 'transport_type') and not invoice.logistics.transport_type: - invoice.logistics.transport_type = existing_invoice.logistics.transport_type if existing_invoice.logistics else None - elif hasattr(invoice.logistics, 'transport_type'): - invoice.logistics.transport_type = clean_str(invoice.logistics.transport_type) - - # Columna N: Número de Transporte - if invoice.logistics: - if hasattr(invoice.logistics, 'transport_num') and not invoice.logistics.transport_num: - invoice.logistics.transport_num = existing_invoice.logistics.transport_num if existing_invoice.logistics else None - elif hasattr(invoice.logistics, 'transport_num'): - invoice.logistics.transport_num = clean_str(invoice.logistics.transport_num) - - # Columna O: Tipo de Moneda - if invoice.financials: - if not invoice.financials.currency: - invoice.financials.currency = existing_invoice.financials.currency if existing_invoice.financials else None - else: - invoice.financials.currency = clean_str(invoice.financials.currency).lower() - - # Columna P: Clave Moneda - if invoice.financials: - if not invoice.financials.currency_type: - invoice.financials.currency_type = existing_invoice.financials.currency_type if existing_invoice.financials else None - else: - invoice.financials.currency_type = clean_str(invoice.financials.currency_type).upper() - - # Columna Q: Flete - if invoice.financials: - if invoice.financials.freight is None: - invoice.financials.freight = existing_invoice.financials.freight if existing_invoice.financials else None - - # Columna R: Val Seguros - if invoice.financials: - if invoice.financials.insurance_value is None: - invoice.financials.insurance_value = existing_invoice.financials.insurance_value if existing_invoice.financials else None - - # Columna S: Seguros - if invoice.financials: - if invoice.financials.insurance is None: - invoice.financials.insurance = existing_invoice.financials.insurance if existing_invoice.financials else None - - # Columna T: Embalaje - if invoice.financials: - if invoice.financials.packaging is None: - invoice.financials.packaging = existing_invoice.financials.packaging if existing_invoice.financials else None - - # Columna U: Otros Incrementables - if invoice.financials: - if invoice.financials.other_increments is None: - invoice.financials.other_increments = existing_invoice.financials.other_increments if existing_invoice.financials else None - - # Columna V: Incoterms - if invoice.logistics: - if hasattr(invoice.logistics, 'incoterm') and not invoice.logistics.incoterm: - invoice.logistics.incoterm = existing_invoice.logistics.incoterm if existing_invoice.logistics else None - elif hasattr(invoice.logistics, 'incoterm'): - invoice.logistics.incoterm = clean_str(invoice.logistics.incoterm).upper() - - # Columna W: Precinto - if invoice.logistics: - if hasattr(invoice.logistics, 'seal_number') and not invoice.logistics.seal_number: - invoice.logistics.seal_number = existing_invoice.logistics.seal_number if existing_invoice.logistics else None - elif hasattr(invoice.logistics, 'seal_number'): - invoice.logistics.seal_number = clean_str(invoice.logistics.seal_number) - - # Columna X: Fecha de Emisión - if not invoice.emission_date: - invoice.emission_date = existing_invoice.emission_date - - # Columna Y: Tipo de Peso (Opcional) - if invoice.logistics: - if hasattr(invoice.logistics, 'weight_type') and not invoice.logistics.weight_type: - invoice.logistics.weight_type = existing_invoice.logistics.weight_type if existing_invoice.logistics else None - elif hasattr(invoice.logistics, 'weight_type'): - invoice.logistics.weight_type = clean_str(invoice.logistics.weight_type).upper() - - # Columna Z: Número de Manifiesto (Opcional) - if invoice.compliance_mx.manifest_number: - if not invoice.compliance_mx.manifest_number: - invoice.compliance_mx.manifest_number = existing_invoice.compliance_mx.manifest_number if existing_invoice.compliance_mx else None - else: - invoice.compliance_mx.manifest_number = clean_str(invoice.compliance_mx.manifest_number) - - # Columna AA: E-Document (Opcional) - if invoice.compliance_mx: - if not invoice.compliance_mx.edocument: - invoice.compliance_mx.edocument = existing_invoice.compliance_mx.edocument if existing_invoice.compliance_mx else None - else: - invoice.compliance_mx.edocument = clean_str(invoice.compliance_mx.edocument) - - # Columna AB: Num. Operación (Opcional) - if invoice.compliance_mx: - if not invoice.compliance_mx.vucem_operation_num: - invoice.compliance_mx.vucem_operation_num = existing_invoice.compliance_mx.vucem_operation_num if existing_invoice.compliance_mx else None - else: - invoice.compliance_mx.vucem_operation_num = clean_str(invoice.compliance_mx.vucem_operation_num) - - # Columna AB: Aduana (OBLIGATORIO) - if invoice.compliance_mx: - if not invoice.compliance_mx.aduana: - invoice.compliance_mx.aduana = existing_invoice.compliance_mx.aduana if existing_invoice.compliance_mx else None - else: - invoice.compliance_mx.aduana = clean_str(invoice.compliance_mx.aduana) - - # Columna AC: Enviado Por (Obligatorio) - if invoice.compliance_mx: - if not invoice.compliance_mx.shipped_by_id: - invoice.compliance_mx.shipped_by_id = existing_invoice.compliance_mx.shipped_by_id if existing_invoice.compliance_mx else None - else: - invoice.compliance_mx.shipped_by_id = clean_str(invoice.compliance_mx.shipped_by_id) - - # Columna AD: Aduana_Cruce (Obligatorio) - current_aduana = invoice.compliance_mx.aduana if invoice.compliance_mx else (existing_invoice.compliance_mx.aduana if existing_invoice.compliance_mx else None) - if not current_aduana: - errors.add_required_error("aduana") - - # Columna AC: Sección de Despacho / Puerto de Entrada (Opcional) - if invoice.compliance_mx: - if not invoice.compliance_mx.port_of_entry: - invoice.compliance_mx.port_of_entry = existing_invoice.compliance_mx.port_of_entry if existing_invoice.compliance_mx else None - else: - invoice.compliance_mx.port_of_entry = clean_str(invoice.compliance_mx.port_of_entry) - - # Columna AE: Observación en Español (Opcional) - if not invoice.observation_es: - invoice.observation_es = existing_invoice.observation_es - else: - invoice.observation_es = clean_str(invoice.observation_es) - - # Columna AF: Observación en Inglés (Opcional) - if not invoice.observation_en: - invoice.observation_en = existing_invoice.observation_en - else: - invoice.observation_en = clean_str(invoice.observation_en) - - # Columna AG: cfdi_uuid (Opcional) - if not invoice.cfdi_uuid: - invoice.cfdi_uuid = existing_invoice.cfdi_uuid if existing_invoice.compliance_mx else None - else: - invoice.cfdi_uuid = clean_str(invoice.cfdi_uuid) - - # Columna AH: Localizacion (Opcional) - if invoice.compliance_mx.location: - if not invoice.compliance_mx.location: - invoice.compliance_mx.location = existing_invoice.compliance_mx.location if existing_invoice.compliance_mx.location else None - else: - invoice.compliance_mx.location = clean_str(invoice.compliance_mx.location) \ No newline at end of file diff --git a/backend/api/v1/modules/a76/invoices/imports/balance/__init__.py b/backend/api/v1/modules/a76/invoices/imports/balance/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/backend/api/v1/modules/a76/invoices/imports/balance/create_balance_entries.py b/backend/api/v1/modules/a76/invoices/imports/balance/create_balance_entries.py deleted file mode 100644 index e19d9a9a..00000000 --- a/backend/api/v1/modules/a76/invoices/imports/balance/create_balance_entries.py +++ /dev/null @@ -1,120 +0,0 @@ -""" -create_balance_entries -Generates one ``BalanceMovement`` (type ENTRY) for every line item of a -processed import invoice, writing the initial inventory balance for each lot. - -Design rules (from a24.balance_movement): - 1. NEVER update existing rows — only INSERT. - 2. Balance = SUM of movements. No cached balance columns. - 3. order_peps is set to the new movement's id (globally monotonic) via a - post-flush assignment — SQLAlchemy fills autoincrement ids after flush. - -This function is called AFTER all validations pass and BEFORE db.flush() at -the end of the import main_process, so all inserts are part of the same -transaction. -""" - -from decimal import Decimal -from typing import List - -from sqlalchemy.orm import Session - -from api.v1.modules.a24.balance_movements.models import BalanceMovement, MovementType -from api.v1.modules.a76.invoices.models import InvoiceHeader -from api.v1.modules.a76.items.models import LineItem - - -def create_balance_entries( - db: Session, - invoice: InvoiceHeader, - lines: List[LineItem], -) -> List[BalanceMovement]: - """ - Inserts one ``BalanceMovement(type=ENTRY)`` for every import line item. - - Parameters - ---------- - db : active SQLAlchemy session (inside the process transaction) - invoice : the import invoice that has just been validated and totalled - lines : all LineItem rows of the invoice - - Returns - ------- - List of the newly created BalanceMovement objects (already added to the - session, ids available after the next flush). - - Notes - ----- - - ``order_peps`` is set equal to ``movement.id`` right after flush so that - the PEPS index is globally monotonic — older imports always have a lower - value and are consumed first on export. - - Sub-items (fa_data.is_subitem = True) are skipped; only principal lines - contribute to inventory. - - Lines with quantity = 0 are skipped to keep the ledger clean. - """ - movements: List[BalanceMovement] = [] - - operation_date = ( - invoice.invoice_date.date() - if hasattr(invoice.invoice_date, "date") - else invoice.invoice_date - ) - - for line in lines: - # Skip sub-items — they have no independent balance - is_subitem = line.fa_data.is_subitem if line.fa_data else False - if is_subitem: - continue - - qty = ( - Decimal(str(line.quantity.quantity or 0)) - if line.quantity - else Decimal(0) - ) - if qty <= 0: - continue - - value_me = ( - Decimal(str(line.financial.value_usd or 0)) - if line.financial - else Decimal(0) - ) - value_mn = ( - Decimal(str(line.financial.value_mxn or 0)) - if line.financial - else Decimal(0) - ) - net_weight = ( - Decimal(str(line.quantity.net_weight or 0)) - if line.quantity - else Decimal(0) - ) - - movement = BalanceMovement( - tenant_id=invoice.tenant_id, - company_id=invoice.company_id, - import_invoice_id=invoice.id, - import_item_line_id=line.id, - part_number_id=line.part_number_id, - movement_type=MovementType.ENTRY, - quantity=qty, - value_me=value_me if value_me > 0 else None, - value_mn=value_mn if value_mn > 0 else None, - net_weight=net_weight if net_weight > 0 else None, - source_invoice_id=None, - source_item_line_id=None, - order_peps=0, # placeholder — set after flush (see below) - operation_date=operation_date, - notes=f"Entrada por factura de importación {invoice.invoice_number}", - ) - db.add(movement) - movements.append(movement) - - if movements: - # Flush to get autoincrement ids, then set order_peps = id so that - # the PEPS index is monotonic and requires no separate sequence. - db.flush() - for mov in movements: - mov.order_peps = mov.id - - return movements diff --git a/backend/api/v1/modules/a76/invoices/imports/balance/void_balance_entries.py b/backend/api/v1/modules/a76/invoices/imports/balance/void_balance_entries.py deleted file mode 100644 index 4ece361a..00000000 --- a/backend/api/v1/modules/a76/invoices/imports/balance/void_balance_entries.py +++ /dev/null @@ -1,170 +0,0 @@ -""" -void_balance_entries -Cancels every open ENTRY balance of an import invoice by inserting a matching -ENTRY_VOID movement for each one. - -Design rules preserved: - 1. NEVER update or delete balance_movement rows — only INSERT. - 2. Net balance after void = SUM(ENTRY qty) - SUM(ENTRY_VOID qty) = 0. - 3. order_peps is set to the new movement's id (post-flush, globally monotonic). - -Called when an import invoice is un-processed (reverted) so that the lots can -no longer be consumed by export discharges. A subsequent re-process will -insert fresh ENTRY rows with up-to-date values. - -Guard: - If any ENTRY has already been partially or fully consumed (i.e. there exist - CONSUMPTION/WASTE/SCRAP/DESTRUCTION movements against it), the void is - blocked and a ``ValueError`` is raised — you cannot un-process an invoice - whose materials are already in use. -""" - -from decimal import Decimal -from typing import List - -from sqlalchemy import select, func, case -from sqlalchemy.orm import Session - -from api.v1.modules.a24.balance_movements.models import ( - BalanceMovement, - MovementType, - NEGATIVE_MOVEMENTS, - USED_MOVEMENTS, -) -from api.v1.modules.a76.invoices.models import InvoiceHeader - - -def void_balance_entries( - db: Session, - invoice: InvoiceHeader, -) -> List[BalanceMovement]: - """ - Inserts ``ENTRY_VOID`` movements that cancel every open ENTRY for the - given import invoice. - - Parameters - ---------- - db : active SQLAlchemy session (inside the revert transaction) - invoice : the import invoice being un-processed - - Returns - ------- - List of the newly created ENTRY_VOID BalanceMovement objects. - - Raises - ------ - ValueError - If any lot of the invoice has already been (partially) consumed by - an export, waste, scrap or destruction. In that case the invoice - cannot be un-processed without first cancelling those discharges. - """ - # ── 1. Fetch all ENTRY movements for this invoice ──────────────────────── - entries: List[BalanceMovement] = ( - db.execute( - select(BalanceMovement).where( - BalanceMovement.import_invoice_id == invoice.id, - BalanceMovement.movement_type == MovementType.ENTRY, - ) - ) - .scalars() - .all() - ) - - if not entries: - return [] - - import_line_ids = [e.import_item_line_id for e in entries] - - # ── 2. Guard: check no lot has been consumed ───────────────────────────── - sign_expr = case( - (BalanceMovement.movement_type.in_(NEGATIVE_MOVEMENTS), -1), - else_=1, - ) - # "used" must be NET of returns: - # + consumption/waste/scrap/destruction - # - return - # so an already reverted discharge does not block import un-processing. - used_net_expr = case( - (BalanceMovement.movement_type.in_(USED_MOVEMENTS), BalanceMovement.quantity), - (BalanceMovement.movement_type == MovementType.RETURN, -BalanceMovement.quantity), - else_=Decimal(0), - ) - - lot_summary = ( - db.execute( - select( - BalanceMovement.import_item_line_id, - func.sum(sign_expr * BalanceMovement.quantity).label("balance"), - func.sum(used_net_expr).label("used_net"), - ) - .where( - BalanceMovement.import_item_line_id.in_(import_line_ids), - ) - .group_by(BalanceMovement.import_item_line_id) - ) - .all() - ) - - consumed_lots = [row for row in lot_summary if (row.used_net or 0) > 0] - if consumed_lots: - lot_ids = ", ".join(str(r.import_item_line_id) for r in consumed_lots) - raise ValueError( - f"No se puede des-procesar la factura '{invoice.invoice_number}': " - f"los siguientes lotes ya tienen consumos registrados y deben " - f"cancelarse primero (item_line ids: {lot_ids})." - ) - - # ── 3. Build the ENTRY_VOID map: one void per LOT ──────────────────────── - # Map lot_id → open balance (should equal the original ENTRY qty since no - # consumptions exist, but we use the actual net balance to be safe). - balance_map: dict[int, Decimal] = { - row.import_item_line_id: Decimal(str(row.balance or 0)) - for row in lot_summary - } - # Pick one representative ENTRY per lot to copy informational fields. - entry_by_lot: dict[int, BalanceMovement] = {} - for e in entries: - if e.import_item_line_id not in entry_by_lot: - entry_by_lot[e.import_item_line_id] = e - - operation_date = ( - invoice.invoice_date.date() - if hasattr(invoice.invoice_date, "date") - else invoice.invoice_date - ) - - voids: List[BalanceMovement] = [] - for lot_id, entry in entry_by_lot.items(): - open_qty = balance_map.get(lot_id, Decimal(0)) - if open_qty <= 0: - continue - - void_mov = BalanceMovement( - tenant_id=invoice.tenant_id, - company_id=invoice.company_id, - import_invoice_id=invoice.id, - import_item_line_id=lot_id, - part_number_id=entry.part_number_id, - movement_type=MovementType.ENTRY_VOID, - quantity=open_qty, - value_me=entry.value_me, - value_mn=entry.value_mn, - net_weight=entry.net_weight, - source_invoice_id=None, - source_item_line_id=None, - order_peps=0, # set after flush - operation_date=operation_date, - notes=( - f"Anulación de entrada por des-procesamiento de " - f"factura {invoice.invoice_number} (entry id={entry.id})" - ), - ) - db.add(void_mov) - voids.append(void_mov) - - if voids: - db.flush() - for mov in voids: - mov.order_peps = mov.id - - return voids diff --git a/backend/api/v1/modules/a76/invoices/imports/docs/REPO_imports_casos_de_uso.md b/backend/api/v1/modules/a76/invoices/imports/docs/REPO_imports_casos_de_uso.md deleted file mode 100644 index eb9d4b75..00000000 --- a/backend/api/v1/modules/a76/invoices/imports/docs/REPO_imports_casos_de_uso.md +++ /dev/null @@ -1,315 +0,0 @@ -# Casos de Uso — Módulo de Importaciones - -**Sistema:** Anexo76 · SCAII -**Módulo:** `imports/` -**Versión:** 1.0 · Marzo 2026 - ---- - -## Contexto general - -El módulo de importaciones gestiona el ciclo completo de una factura de importación dentro del sistema: desde que el usuario la captura hasta que queda registrada como procesada. También permite revertirla si hubo un error. - -La importación es el punto de partida de todo el inventario de materiales. Cuando una empresa maquiladora importa insumos, el sistema registra cuánto entró, con qué valor y bajo qué régimen. Ese registro es lo que después permite a las exportaciones descargar saldos. Sin importaciones procesadas, no hay inventario que exportar. - -Toda factura de importación pasa por uno de dos momentos: **procesarla** (actualizarla) o **revertirla** (desactualizarla). - ---- - -## Los tipos de factura de importación - -Cada tipo representa un escenario aduanero diferente con reglas y efectos distintos sobre el inventario: - -| Tipo | ¿Qué representa en la práctica? | ¿Genera saldo de inventario? | ¿Calcula IVA por partida? | -|------|--------------------------------|:----------------------------:|:-------------------------:| -| **TEM** | Material que entra al país temporalmente para ser procesado y exportado | Sí | No | -| **DEF** | Material que entra al país de forma definitiva, pagando impuestos completos | No | Sí | -| **MEX** | Compra de material a proveedores mexicanos | No | Sí | - -La distinción más importante del módulo: **solo las importaciones temporales (TEM) generan saldo de inventario**. Las definitivas y las compras mexicanas simplemente registran la entrada y calculan el IVA, pero no alimentan el ledger que las exportaciones van a consumir. - ---- - -## CU-IMP-001 · Procesar una factura TEM (Importación Temporal) - -### Descripción - -El usuario procesa una factura de importación temporal. El material registrado en esta factura entra al país sin pagar impuestos definitivos, bajo el compromiso de que será exportado después de ser transformado o ensamblado. - -### Por qué existe - -El régimen de importación temporal es el corazón de la operación maquiladora. Las empresas necesitan registrar exactamente qué entró, cuánto y con qué valor, porque ese registro es el inventario del que después se nutren las exportaciones. Sin este caso de uso, no existirían los saldos que el módulo de exportaciones descarga. - -### Quién lo usa - -El usuario de captura de importaciones, al presionar **"Actualizar Factura"** en una factura de tipo TEM. - -### Qué necesita estar listo antes - -- La factura debe estar en estado **Pendiente**. -- Debe tener al menos una partida capturada. -- Todos los campos del encabezado deben estar completos: fecha, proveedor, destinatario, agente aduanal, tipo de cambio, moneda y pedimento. -- El tipo de cambio del día debe estar registrado en el catálogo — a diferencia de exportaciones, aquí es obligatorio que exista; si no existe, el sistema lanza un error (no solo una advertencia). -- Las clases y fracciones arancelarias de cada partida deben ser válidas. -- Cada partida principal debe tener costo unitario mayor a cero. -- Si la empresa tiene programa PROSEC y alguna partida usa Regla Octava, el permiso correspondiente debe estar activo y con cupo disponible. - -### Qué hace el sistema - -Primero valida los campos del encabezado y carga las partidas. Luego verifica que las clases y fracciones arancelarias sean correctas, que el tipo de cambio coincida con el catálogo y que los pesos declarados cuadren con las cantidades. - -Después calcula los valores monetarios de cada partida: costo unitario en pesos, en dólares y en moneda de cuenta, usando el tipo de cambio del día. Para TEM **no se calcula IVA** — ese cálculo solo aplica a DEF y MEX. - -Por cada partida también valida que la unidad de medida tenga una equivalencia con la unidad de aduana mexicana, para poder registrar la cantidad correcta ante la autoridad aduanera. - -Si la empresa tiene programa PROSEC y alguna partida tiene permiso de Regla Octava, el sistema verifica que ese permiso exista, que esté vigente, que la fracción arancelaria coincida y que haya cupo suficiente. Si todo está bien, descuenta ese cupo del permiso. - -Finalmente calcula los totales de la factura, agrega los incrementables (flete, seguro, embalaje) y genera en el ledger de inventario **un registro de entrada por cada partida principal**. Esos registros son los saldos que las exportaciones futuras van a consumir. - -### Qué queda guardado - -La factura queda en estado **Procesada** con sus totales calculados. En el ledger de saldos quedan registrados **movimientos de entrada** por cada partida, listos para ser consumidos por exportaciones. Si había Regla Octava, los cupos del permiso quedan descontados y el saldo queda registrado en la tabla de saldos PROSEC. - -### Qué puede fallar - -| Situación | Qué hace el sistema | -|-----------|---------------------| -| La factura ya estaba procesada | La rechaza de inmediato | -| El tipo de cambio del día no está en el catálogo | Error — a diferencia de exportaciones, aquí no es opcional | -| El tipo de cambio capturado difiere del catálogo | Error indicando la diferencia | -| Una fracción arancelaria no existe en el catálogo oficial | Reporta qué partida tiene la fracción inválida | -| Una clase arancelaria está desactivada | Reporta qué partida está afectada | -| Un número de parte está desactivado | Reporta qué partida está afectada | -| La cantidad y el peso neto no coinciden (en partidas KGS o LBS) | Indica qué partida tiene la diferencia | -| Una partida tiene costo en cero | Indica qué partida no tiene precio | -| La partida tiene series activadas pero no hay series registradas | Indica qué partida le faltan series | -| La partida usa Regla Octava pero la empresa no tiene PROSEC | Rechaza el uso del permiso | -| El permiso de Regla Octava no existe o está vencido | Indica qué permiso tiene el problema | -| El cupo del permiso de Regla Octava ya está agotado | Indica que no hay cupo disponible | -| La unidad de medida no tiene equivalencia con la unidad de aduana | Indica qué partida tiene el problema de unidades | - ---- - -## CU-IMP-002 · Procesar una factura DEF (Importación Definitiva) - -### Descripción - -El usuario procesa una factura de importación definitiva. El material entra al país pagando todos los impuestos correspondientes — es una compra permanente, no temporal. - -### Por qué existe - -No todos los materiales que usan las maquiladoras entran al país de forma temporal. Algunos insumos, herramientas o componentes se adquieren de forma definitiva. El sistema necesita registrar esa entrada, calcular el IVA que corresponde y dejar constancia del valor total de la importación para efectos contables y aduaneros. - -### Quién lo usa - -El usuario de captura de importaciones para facturas de importación definitiva. - -### Qué necesita estar listo antes - -Igual que TEM, con la diferencia de que el `iva_factor` debe estar configurado en la factura para que el sistema pueda calcular el IVA por partida. - -### Qué hace el sistema - -Ejecuta las mismas validaciones que TEM (encabezado, clases, fracciones, tipo de cambio, pesos, costo unitario, series, UMA). La diferencia está en el cálculo de valores: para DEF el sistema calcula **subtotal + IVA = total** por cada partida, en pesos, dólares y moneda de cuenta. El IVA se calcula como porcentaje del valor de cada partida. - -Al terminar, actualiza los totales del encabezado incluyendo los totales de IVA. - -**La diferencia crítica con TEM:** no genera ningún movimiento en el ledger de inventario. La importación definitiva no alimenta los saldos que las exportaciones consumen — ese material llegó para quedarse en México, no para ser reexportado bajo régimen temporal. - -### Qué queda guardado - -La factura queda **Procesada** con sus valores y totales de IVA calculados. El ledger de inventario no se modifica. - -### Qué puede fallar - -Las mismas situaciones que TEM, excepto lo relacionado con Regla Octava (que no aplica para DEF). - ---- - -## CU-IMP-003 · Procesar una factura MEX (Compra Mexicana) - -### Descripción - -El usuario procesa una factura de compra a un proveedor mexicano. El material proviene de dentro del país — no hay una importación aduanera real — pero el sistema igualmente registra la entrada y calcula el IVA correspondiente. - -### Por qué existe - -Las maquiladoras también compran materiales a proveedores locales mexicanos. Aunque no hay un trámite aduanero formal, la empresa igualmente necesita registrar ese costo, el IVA pagado y el valor de lo que adquirió, por razones contables y de control interno. - -### Quién lo usa - -El usuario de captura de importaciones para facturas de compras nacionales. - -### Qué necesita estar listo antes - -Igual que DEF. A diferencia de otros tipos, MEX no requiere `document_type` — al ser una compra nacional no hay régimen aduanero que declarar. - -### Qué hace el sistema - -Idéntico a DEF: valida el encabezado, calcula valores con IVA por partida y actualiza totales. No genera saldos en el ledger. - -### Qué queda guardado - -Igual que DEF — la factura queda **Procesada** con valores e IVA calculados, sin movimientos en el ledger de inventario. - -### Qué puede fallar - -Las mismas situaciones que DEF. - ---- - -## CU-IMP-004 · Procesar una factura TEM con Regla Octava (PROSEC) - -### Descripción - -Es una variante del caso TEM. Ocurre cuando la empresa tiene un **programa PROSEC** activo y alguna o todas sus partidas están amparadas bajo un **permiso de Regla Octava**, que le permite importar ciertos materiales con arancel preferencial. - -### Por qué existe - -La Regla Octava es un beneficio arancelario del gobierno mexicano para empresas del sector productivo. Las empresas PROSEC pueden importar materias primas con aranceles reducidos, pero a cambio deben consumir esos materiales dentro de cuotas autorizadas. El sistema lleva la cuenta de cuánto cupo ha sido usado y cuánto queda disponible por permiso. - -### Quién lo usa - -Empresas con programa PROSEC activo, al procesar facturas TEM donde alguna partida tiene un permiso de Regla Octava capturado. - -### Qué necesita estar listo antes - -Todo lo de TEM, más: - -- La empresa debe tener `prosec = True` en su configuración. -- El permiso de Regla Octava debe existir en el catálogo, estar vigente (dentro de las fechas del permiso) y tener cupo disponible. -- La fracción arancelaria de la partida debe coincidir con la fracción registrada en el permiso — o debe existir en el historial de fracciones anteriores si el permiso es previo a mayo de 2010. -- El país de origen de la partida debe estar registrado dentro del permiso. -- La unidad de medida del permiso debe ser compatible con la de la partida. - -### Qué hace el sistema - -Igual que TEM en todo, más un bloque adicional de validación y descuento de cupos: - -Para cada partida con permiso de Regla Octava, el sistema arma dos listas: una con lo que se quiere importar (cantidades y valores por permiso y línea) y otra con el cupo disponible por permiso. Luego cruza ambas listas para verificar que el cupo alcance. Si alcanza, descuenta ese cupo del permiso y registra en la tabla de saldos PROSEC cuánto se consumió de cada permiso por factura. - -### Qué queda guardado - -Igual que TEM, más: el cupo del permiso de Regla Octava queda reducido en la cantidad importada, y existe un registro en la tabla de saldos PROSEC vinculando esta factura con el permiso utilizado. Ese registro es el que se elimina si la factura se revierte. - -### Qué puede fallar - -Todo lo de TEM, más las situaciones específicas de Regla Octava descritas en ese caso. - ---- - -## CU-IMP-005 · Revertir una factura TEM - -### Descripción - -El usuario deshace el procesamiento de una factura de importación temporal — porque hubo un error de captura, los datos cambiaron, o necesita corregirla. - -### Por qué existe - -Una factura TEM procesada tiene efectos en el inventario: generó saldos que las exportaciones pueden estar usando. Revertirla no es solo cambiar un estado — hay que deshacer todos esos efectos de forma controlada para que el inventario quede consistente. - -### Qué hace el sistema - -Primero verifica que ninguna exportación activa esté consumiendo los saldos de esta importación. Si las hay, bloquea completamente la reversión — ver CU-IMP-007. - -Si no hay bloqueos, ejecuta cuatro acciones en orden: - -**1. Revierte los cupos de Regla Octava.** Si la factura usó permisos PROSEC, devuelve el cupo consumido a cada permiso y elimina los registros de saldo PROSEC que se habían creado al procesar. - -**2. Reinicia los totales del encabezado.** Pone todos los valores financieros en cero (cantidad, peso, valor en pesos, en dólares, IVA, incrementables) y regresa la factura a estado **Pendiente**. - -**3. Reinicia los contadores de cada partida.** Limpia los valores de retorno y de IVA utilizado que se habían calculado en las partidas. - -**4. Anula los saldos en el ledger de inventario.** El ledger nunca se borra. En su lugar, el sistema inserta nuevos movimientos de tipo **Anulación de Entrada** que compensan exactamente las **Entradas** originales. El saldo neto de cada lote queda en cero — las exportaciones ya no pueden consumirlo. - -### Qué necesita estar listo antes - -La factura debe estar en estado **Procesada**. Si no lo está, el sistema rechaza la reversión. - -### Qué queda guardado - -La factura queda en estado **Pendiente** con todos los valores en cero. En el ledger quedan los movimientos originales de Entrada más los nuevos de Anulación que los neutralizan — el historial queda completo y auditable. Si había Regla Octava, los cupos quedan restaurados y los registros de saldo PROSEC quedan eliminados. - -### Qué puede fallar - -| Situación | Qué hace el sistema | -|-----------|---------------------| -| Una partida está siendo descargada por una exportación activa | Bloquea toda la reversión — ver CU-IMP-007 | -| La factura no estaba procesada | La rechaza indicando que no puede revertirse | -| Error al actualizar el permiso de Regla Octava | Registra el error como no bloqueante y continúa — la reversión avanza de todas formas | - ---- - -## CU-IMP-006 · Revertir una factura DEF o MEX - -### Descripción - -El usuario deshace el procesamiento de una factura de importación definitiva o de una compra mexicana. - -### Por qué existe - -Aunque DEF y MEX no generan saldos de inventario, sí tienen totales calculados y estado procesado que pueden necesitar corrección. La reversión les permite volver a estado pendiente para ser corregidas. - -### Qué hace el sistema - -Es la más simple de las reversiones de importación porque DEF y MEX nunca generaron saldos en el ledger. El sistema verifica que no haya exportaciones activas usando esa importación (por consistencia, aunque en la práctica DEF y MEX no alimentan el ledger que exportaciones consume), reinicia los totales del encabezado a cero y regresa la factura a **Pendiente**. - -No hay cupos de Regla Octava que restaurar ni entradas de ledger que anular. - -### Qué queda guardado - -La factura queda en estado **Pendiente** con todos los valores en cero, lista para ser corregida y reprocesada. - -### Qué puede fallar - -| Situación | Qué hace el sistema | -|-----------|---------------------| -| La factura no estaba procesada | La rechaza indicando que no puede revertirse | - ---- - -## CU-IMP-007 · Reversión bloqueada por exportaciones activas - -### Descripción - -El usuario intenta revertir una factura de importación TEM, pero el sistema detecta que alguna de sus partidas está siendo **consumida actualmente por una exportación que sigue procesada**. El sistema bloquea completamente la reversión. - -### Por qué existe este bloqueo - -Los saldos de inventario de una importación temporal pueden estar siendo usados por varias exportaciones al mismo tiempo. Si se permitiera revertir la importación mientras esas exportaciones siguen activas, el inventario quedaría en un estado imposible: habría exportaciones que dicen haber consumido saldos de una importación que ya no existe. El bloqueo garantiza que el orden sea siempre el correcto: primero se deshacen las exportaciones y luego la importación. - -### Qué hace el sistema - -Recorre cada partida de la factura. Por cada una, busca si existe algún registro de descarga activo vinculado a una exportación procesada. Si encuentra aunque sea uno, detiene todo el proceso y muestra un mensaje por cada exportación activa involucrada, indicando exactamente qué factura de exportación y qué línea está usando el saldo. - -### Cómo lo resuelve el usuario - -Debe ir a cada factura de exportación indicada en el mensaje de error y revertirla primero. Una vez que todas las exportaciones que consumían esos saldos estén revertidas, puede revertir la importación sin problemas. - -### Qué queda guardado - -Nada — el sistema no modifica ningún dato cuando bloquea la reversión. Todo queda exactamente igual que antes del intento. - ---- - -## Resumen de los siete casos - -| # | Caso de uso | Tipo | Genera saldo inventario | Calcula IVA | Regla Octava | -|---|-------------|------|:-----------------------:|:-----------:|:------------:| -| CU-IMP-001 | Procesar TEM | Proceso | Sí | No | Opcional | -| CU-IMP-002 | Procesar DEF | Proceso | No | Sí | No | -| CU-IMP-003 | Procesar MEX | Proceso | No | Sí | No | -| CU-IMP-004 | Procesar TEM con PROSEC | Proceso | Sí | No | Sí — descuenta cupo | -| CU-IMP-005 | Revertir TEM | Reversión | Anula saldos | No | Restaura cupo | -| CU-IMP-006 | Revertir DEF o MEX | Reversión | No aplica | No | No | -| CU-IMP-007 | Reversión bloqueada por exportaciones activas | Bloqueo | No (bloqueado) | No | No | - ---- - -## Relación con el módulo de exportaciones - -Las importaciones y exportaciones están directamente conectadas a través del ledger de inventario: - -Una factura TEM procesada **crea** saldos. Esos saldos son los que las exportaciones AFIJO, DONAC, SCRAP, REEXP y VEMEX **consumen** al procesarse. Si una exportación consume saldos de una TEM y después esa TEM necesita revertirse, primero hay que revertir la exportación — de ahí viene el bloqueo del CU-IMP-007. - -Las facturas DEF y MEX no participan en este ciclo desde el lado de la importación, pero sí pueden ser el origen del material en una exportación de tipo REEXP o VEMEX, donde la procedencia requerida es justamente DEF. diff --git a/backend/api/v1/modules/a76/invoices/imports/docs/REPO_imports_diagramas.md b/backend/api/v1/modules/a76/invoices/imports/docs/REPO_imports_diagramas.md deleted file mode 100644 index 6864aaa8..00000000 --- a/backend/api/v1/modules/a76/invoices/imports/docs/REPO_imports_diagramas.md +++ /dev/null @@ -1,222 +0,0 @@ -# Diagramas de Flujo — Casos de Uso Importaciones - -> Cada diagrama corresponde a un caso de uso en [`imports_cu_comprensibles.md`](./imports_cu_comprensibles.md). -> Formato: Mermaid — compatible con Notion, GitHub y VSCode. -> **Cómo usar en Notion:** bloque `/code` → lenguaje `mermaid` → pegar el contenido. - ---- - -## CU-IMP-001 · Procesar TEM (Importación Temporal) - -```mermaid -flowchart TD - START([Usuario presiona\nActualizar Factura - TEM]) --> CHK1{¿La factura\nestá Pendiente?} - CHK1 -- No --> E1([❌ Error\nYa fue procesada]) - CHK1 -- Sí --> V1[Validar encabezado\nfecha · proveedor · destinatario\nTC · moneda · pedimento] - V1 --> TC{¿Existe el TC\ndel día en el catálogo?} - TC -- No --> E2([❌ Error\nTC obligatorio para TEM\nno es opcional como en exports]) - TC -- Sí --> TC2{¿TC de la factura\n== TC del catálogo?} - TC2 -- No --> E3([❌ Error\nTipo de cambio\nno coincide]) - TC2 -- Sí --> V2[Validar clases · fracciones\nnúmeros de parte · Regla 3.1.21\nrevisión física si aplica] - V2 --> V3{¿Errores en\nclases o fracciones?} - V3 -- Sí --> E4([❌ Error\nClase o fracción inválida]) - V3 -- No --> V4[Validar pesos por partida\nKGS o LBS según configuración] - V4 --> CALC[Calcular valores sin IVA\nCosto x Cantidad x TC\npesos · dólares · moneda cuenta] - CALC --> VL[Validar por cada partida\ncosto > 0 · clase activa\nparte activa · series · UMA] - VL --> VL2{¿Errores en\npartidas?} - VL2 -- Sí --> E5([❌ Error\nCosto cero · clase desactivada\nparte desactivada · series faltantes]) - VL2 -- No --> OCT{¿Alguna partida\ntiene permiso PROSEC?} - OCT -- Sí --> OCTVAL[Validar permisos\nde Regla Octava\nver CU-IMP-004] - OCT -- No --> TOTALS - OCTVAL --> OCTCHK{¿Permisos\nválidos y con cupo?} - OCTCHK -- No --> E6([❌ Error\nPermiso vencido\no cupo agotado]) - OCTCHK -- Sí --> TOTALS[Calcular totales de la factura\nsumar IVA si fecha ≥ 2014-12-31\nagregar incrementables] - TOTALS --> LEDGER[Generar entrada en el ledger\nun movimiento ENTRADA por partida principal\nbase del inventario futuro] - LEDGER --> FIN([✅ Factura PROCESADA\nSaldos de inventario creados\nListos para ser exportados]) - - style FIN fill:#1D6B3C,color:#fff,rx:16 - style START fill:#1F4E79,color:#fff,rx:16 - style E1 fill:#C55A11,color:#fff,rx:16 - style E2 fill:#C55A11,color:#fff,rx:16 - style E3 fill:#C55A11,color:#fff,rx:16 - style E4 fill:#C55A11,color:#fff,rx:16 - style E5 fill:#C55A11,color:#fff,rx:16 - style E6 fill:#C55A11,color:#fff,rx:16 -``` - ---- - -## CU-IMP-002 · Procesar DEF (Importación Definitiva) - -```mermaid -flowchart TD - START([Usuario presiona\nActualizar Factura - DEF]) --> V1[Validaciones de encabezado\nigual que TEM] - V1 --> CALC[Calcular valores CON IVA\nSubtotal + IVA = Total\npor cada partida\nen pesos · dólares · moneda cuenta] - CALC --> VL[Validar partidas\ncosto · clase · parte · series · UMA] - VL --> CHK{¿Errores?} - CHK -- Sí --> E1([❌ Error\nCorregir partidas]) - CHK -- No --> TOTALS[Calcular totales incluyendo\ntotales de IVA] - TOTALS --> NOTE[No se generan saldos\nen el ledger de inventario\neste material llegó para\nquedarse en México] - NOTE --> FIN([✅ Factura PROCESADA\nSin movimientos en inventario]) - - style FIN fill:#1D6B3C,color:#fff,rx:16 - style START fill:#1F4E79,color:#fff,rx:16 - style E1 fill:#C55A11,color:#fff,rx:16 - style NOTE fill:#FFF3E0,rx:8 -``` - ---- - -## CU-IMP-003 · Procesar MEX (Compra Mexicana) - -```mermaid -flowchart TD - START([Usuario presiona\nActualizar Factura - MEX]) --> NOTE[Compra a proveedor mexicano\nNo hay trámite aduanero real\ndocument_type no es obligatorio] - NOTE --> V1[Validaciones de encabezado\nigual que DEF] - V1 --> CALC[Calcular valores CON IVA\nidéntico a DEF] - CALC --> TOTALS[Calcular totales con IVA] - TOTALS --> FIN([✅ Factura PROCESADA\nSin movimientos en inventario]) - - style FIN fill:#1D6B3C,color:#fff,rx:16 - style START fill:#1F4E79,color:#fff,rx:16 - style NOTE fill:#FFF3E0,rx:8 -``` - ---- - -## CU-IMP-004 · Procesar TEM con Regla Octava (PROSEC) - -```mermaid -flowchart TD - START([Partida con permiso\nde Regla Octava detectada]) --> PROSEC{¿La empresa\ntiene PROSEC activo?} - PROSEC -- No --> E1([❌ Error\nNo se puede usar Regla Octava\nsin programa PROSEC]) - PROSEC -- Sí --> P1[Buscar el permiso\nen el catálogo] - P1 --> P2{¿Permiso\nexiste?} - P2 -- No --> E2([❌ Error\nPermiso no dado de alta]) - P2 -- Sí --> P3{¿Fecha de la factura\ndentro del rango\ndel permiso?} - P3 -- No --> E3([❌ Error\nFecha fuera del\nrango del permiso]) - P3 -- Sí --> P4{¿Fracción de la partida\ncoincide con la del permiso?} - P4 -- No --> P4B{¿Permiso anterior\na mayo 2010 y fracción\nen historial?} - P4B -- No --> E4([❌ Error\nFracción no corresponde\nal permiso]) - P4B -- Sí --> P5 - P4 -- Sí --> P5{¿Cupo del permiso\n> 0?} - P5 -- No --> E5([❌ Error\nCupo agotado]) - P5 -- Sí --> P6{¿País de origen\nen el permiso?} - P6 -- No --> E6([❌ Error\nPaís de origen no\nampara el permiso]) - P6 -- Sí --> P7[Calcular cantidad y valor\na descontar del cupo\ncon equivalencia de unidades si aplica] - P7 --> P8[Verificar que el cupo\nalcance para todas\nlas partidas del lote] - P8 --> P9{¿Cupo\nsuficiente?} - P9 -- No --> E7([❌ Error\nCupo insuficiente para\ncubrir la importación]) - P9 -- Sí --> SAVE[Descontar cupo del permiso\nRegistrar saldo PROSEC\nvinculado a esta factura] - SAVE --> FIN([✅ Permisos PROSEC descontados\nContinúa con flujo TEM normal\nver CU-IMP-001]) - - style FIN fill:#1D6B3C,color:#fff,rx:16 - style START fill:#2E75B6,color:#fff,rx:16 - style E1 fill:#C55A11,color:#fff,rx:16 - style E2 fill:#C55A11,color:#fff,rx:16 - style E3 fill:#C55A11,color:#fff,rx:16 - style E4 fill:#C55A11,color:#fff,rx:16 - style E5 fill:#C55A11,color:#fff,rx:16 - style E6 fill:#C55A11,color:#fff,rx:16 - style E7 fill:#C55A11,color:#fff,rx:16 -``` - ---- - -## CU-IMP-005 · Revertir TEM - -```mermaid -flowchart TD - START([Usuario presiona\nDesactualizar Factura - TEM]) --> CHK1{¿La factura\nestá Procesada?} - CHK1 -- No --> E1([❌ Error\nNo se puede revertir]) - CHK1 -- Sí --> BLOCK[Verificar si alguna partida\nestá siendo consumida por\nuna exportación activa] - BLOCK --> CHK2{¿Hay exportaciones\nactivas usando\nestos saldos?} - CHK2 -- Sí --> E2([❌ Bloqueado\nver CU-IMP-007\nRevertir exportaciones primero]) - CHK2 -- No --> R1[1 · Revertir Regla Octava si aplica\nDevolver cupos a los permisos\nEliminar registros de saldo PROSEC] - R1 --> R2[2 · Reiniciar totales del encabezado\ntodos los valores a cero\nstatus a Pendiente] - R2 --> R3[3 · Reiniciar contadores\nde cada partida a cero] - R3 --> R4[4 · Anular saldos en el ledger\ninsertar movimientos ANULACIÓN DE ENTRADA\nque neutralizan las ENTRADAS originales] - R4 --> FIN([✅ Factura REVERTIDA\nInventario neutralizado\nHistorial completo y auditable]) - - style FIN fill:#1D6B3C,color:#fff,rx:16 - style START fill:#1F4E79,color:#fff,rx:16 - style E1 fill:#C55A11,color:#fff,rx:16 - style E2 fill:#C55A11,color:#fff,rx:16 -``` - ---- - -## CU-IMP-006 · Revertir DEF o MEX - -```mermaid -flowchart TD - START([Usuario presiona\nDesactualizar Factura - DEF o MEX]) --> CHK1{¿La factura\nestá Procesada?} - CHK1 -- No --> E1([❌ Error\nNo se puede revertir]) - CHK1 -- Sí --> NOTE[DEF y MEX nunca generaron\nsaldos en el ledger\nni cupos de Regla Octava] - NOTE --> CLEAN[Reiniciar todos los valores\ndel encabezado a cero\nstatus a Pendiente] - CLEAN --> FIN([✅ Factura REVERTIDA\nSin efectos adicionales]) - - style FIN fill:#1D6B3C,color:#fff,rx:16 - style START fill:#1F4E79,color:#fff,rx:16 - style E1 fill:#C55A11,color:#fff,rx:16 - style NOTE fill:#FFF3E0,rx:8 -``` - ---- - -## CU-IMP-007 · Reversión bloqueada por exportaciones activas - -```mermaid -flowchart TD - START([Usuario intenta\nDesactualizar Factura TEM]) --> SCAN[Recorrer cada partida\nde la factura] - SCAN --> FIND[Buscar registros de descarga\ncon estado APLICADO\nvinculados a exportaciones procesadas] - FIND --> CHK{¿Hay descargas\nactivas?} - CHK -- No --> OK([Reversión permitida\ncontinúa con CU-IMP-005]) - CHK -- Sí --> MSG[Mostrar un mensaje por cada\nexportación activa involucrada\nfactura · línea · cantidad descargada] - MSG --> BLOCK([❌ Reversión BLOQUEADA\nEl usuario debe revertir primero\ncada exportación indicada]) - - style OK fill:#1D6B3C,color:#fff,rx:16 - style BLOCK fill:#C55A11,color:#fff,rx:16 - style START fill:#1F4E79,color:#fff,rx:16 -``` - ---- - -## Visión general — todos los tipos de importación - -```mermaid -flowchart LR - subgraph PROCESAR ["⬆️ PROCESAR"] - TEM[TEM\nGenera saldos PEPS\nsin IVA] - TEMO[TEM + PROSEC\nGenera saldos\ndescuenta cupo RO] - DEF[DEF\nSin saldos\ncon IVA] - MEX[MEX\nSin saldos\ncon IVA] - end - - subgraph REVERTIR ["⬇️ REVERTIR"] - RTEM[Revertir TEM\nAnula saldos · restaura cupo\nledger ANULACIÓN] - RDEF[Revertir DEF o MEX\nSolo limpia totales] - end - - subgraph BLOQUEOS ["🚫 BLOQUEOS"] - BEXP[Bloqueada por\nexportaciones activas\nque usan los saldos] - end - - subgraph RELACION ["🔗 Relación con Exports"] - EXP[Exportaciones AFIJO\nDONAC · SCRAP · REEXP · VEMEX\nconsumen saldos generados por TEM] - end - - TEM -->|crea saldos| EXP - TEMO -->|crea saldos| EXP - TEM --> RTEM - TEMO --> RTEM - DEF --> RDEF - MEX --> RDEF - RTEM --> BEXP - EXP -->|si está activa bloquea| BEXP - - style PROCESAR fill:#E2EFDA,rx:8 - style REVERTIR fill:#DEEAF1,rx:8 - style BLOQUEOS fill:#FCE4D6,rx:8 - style RELACION fill:#EAE3F0,rx:8 -``` diff --git a/backend/api/v1/modules/a76/invoices/imports/process/main_process.py b/backend/api/v1/modules/a76/invoices/imports/process/main_process.py deleted file mode 100644 index e672f3e8..00000000 --- a/backend/api/v1/modules/a76/invoices/imports/process/main_process.py +++ /dev/null @@ -1,262 +0,0 @@ -import logging -logger = logging.getLogger(__name__) -from datetime import date, datetime -from decimal import Decimal -from typing import List - -from sqlalchemy.orm import Session - -from api.v1.modules.a76.parts.models import Part -from api.v1.modules.a76.classes.models import Class -from api.v1.modules.a76.invoices.models import Currency, InvoiceHeader, InvoiceStatus -from api.v1.modules.a76.items.models import LineItem -from api.v1.modules.a76.items.common.fractions import search_fraction_preference -from api.v1.modules.a76.general_catalogs.company.models import Company -from core.exceptions import ErrorCollector -from .pre_validators import pre_validators -from .sub_process.review_classes import review_classes -from .sub_process.review_exchange_rate import review_exchange_rate -from .sub_process.review_weights import review_weights_kgs, review_weights_lbs -from .sub_process.review_series import review_series -from .sub_process.review_rule_octave import ( - llena_impo_permiso_regla_octava, - revpermiso_regla_octava, - valida_imp_regla_octava, - descuenta_cupo_r_octava, -) -from ...common.process.review_uma import revisa_uma -from .sub_process.assing_values import assign_values_lines, assign_values_invoice -from .sub_process.assing_values_def_mex import ( - assign_values_iva_lines, - assign_values_invoice_totals, -) -from ..balance.create_balance_entries import create_balance_entries -from .sub_process.review_limits import review_limits - -# Parameter Service -from api.v1.modules.a76.app_settings.service import AppSettingsService - - -def _validate_lines( - db: Session, - invoice: InvoiceHeader, - lines: List[LineItem], - tenant_id: str, - company_id: str, - errors: ErrorCollector, -) -> tuple[list, dict]: - """Recorre cada partida y ejecuta las validaciones individuales.""" - company = db.get(Company, invoice.company_id) - company_rfc = (company.rfc or "").strip().upper() if company else "" - octave_available: dict = {} - octave_desc: list = [] - - for line in lines: - if (line.financial and line.fa_data) and not line.fa_data.is_subitem and (line.financial.unit_cost_capture or Decimal(0)) == 0: - errors.add_error( - field=f"line[{line.line_number}].unit_cost_capture", - message="No existe el costo unitario para la Partida.", - solution=[f"Entrar a la partida: {line.line_number} y capturar el Costo Unitario."], - code="UNIT_COST_REQUIRED", - ) - - if line.class_id is not None: - cls: Class | None = db.get(Class, line.class_id) - if cls and cls.is_active is False: - errors.add_error( - field=f"line[{line.line_number}].class", - message=f"El número de parte: {cls.class_code} esta desactivado, no se pueden hacer movimientos.", - solution=["Seleccionar un número de parte activo."], - code="CLASS_DISABLED", - ) - - if line.part_number_id is not None: - part: Part | None = db.get(Part, line.part_number_id) - if part and part.is_active is False: - errors.add_error( - field=f"line[{line.line_number}].part_number", - message=f"El número de parte: {part.part_number} esta desactivado, no se pueden hacer movimientos.", - solution=["Seleccionar un número de parte activo."], - code="PART_DISABLED", - ) - - search_fraction_preference( - db=db, - country=line.customs.origin_country or "", - fraccion=line.customs.fraction if line.customs else "", - fraction_type=line.customs.fraction_type if line.customs else "", - sector=line.customs.sector if line.customs else "", - invoice_date=invoice.invoice_date, - errors=errors, - ) - - review_series(db, line, company_rfc, tenant_id, company_id, errors) - - if line.octave_permit: - if not company.prosec: - errors.add_error( - field=f"line[{line.line_number}].octave_permit", - message="No se puede hacer uso de la Regla Octava...", - solution=["Borrar el permiso o dar de alta el permiso PROSEC..."], - code="OCTAVA_SIN_PROSEC", - ) - else: - desc_entry = llena_impo_permiso_regla_octava( - db=db, invoice=invoice, line=line, company_rfc=company_rfc, - tenant_id=tenant_id, company_id=company_id, errors=errors, - ) - if desc_entry is not None: - octave_desc.append(desc_entry) - available = revpermiso_regla_octava( - db=db, invoice=invoice, line=line, company_rfc=company_rfc, - tenant_id=tenant_id, company_id=company_id, errors=errors, - ) - if available is not None: - key = (available.octave_permit, available.ro_line, available.country_code) - octave_available.setdefault(key, available) - - revisa_uma(db=db, line=line, tenant_id=tenant_id, company_id=company_id, errors=errors) - - return octave_desc, octave_available - - -def _validate_sisimp_limits(invoice: InvoiceHeader, settings: dict, errors: ErrorCollector) -> None: - """Valida los límites de cantidad, peso y valor configurados en SisImpo/SisDef.""" - review_limits(invoice, settings, errors) - - -def _update_invoice_totals(invoice: InvoiceHeader, lines: List[LineItem]) -> None: - """Actualiza totales, IVA e incrementables en la factura.""" - tc = Decimal(str(invoice.financials.exchange_rate or 0)) - tc_mm = Decimal(str(invoice.financials.exchange_rate_mm or 0)) - - iva_factor = Decimal(str(invoice.financials.iva_factor or 0)) if invoice.financials.iva_factor else Decimal(0) - if invoice.invoice_date and invoice.invoice_date >= date(2014, 12, 31): - invoice.financials.iva_mn = float(Decimal(str(invoice.financials.value_mn or 0)) * iva_factor / 100) - invoice.financials.iva_me = float(Decimal(str(invoice.financials.value_me or 0)) * iva_factor / 100) - else: - invoice.financials.iva_mn = 0.0 - invoice.financials.iva_me = 0.0 - - freight = Decimal(str(invoice.financials.freight or 0)) - insurance = Decimal(str(invoice.financials.insurance or 0)) - packaging = Decimal(str(invoice.financials.packaging or 0)) - other = Decimal(str(invoice.financials.other_increments or 0)) - base_increm = freight + insurance + packaging + other - - if invoice.financials.currency == Currency.FOREIGN: - val_seguro = Decimal(str(invoice.financials.total_increments_me or 0)) - base_increm - invoice.financials.total_increments_me = float(base_increm + val_seguro) - invoice.financials.total_increments_mn = float(Decimal(str(invoice.financials.total_increments_me)) * tc) - elif invoice.financials.currency == Currency.LOCAL: - val_seguro = Decimal(str(invoice.financials.total_increments_mn or 0)) - base_increm - invoice.financials.total_increments_mn = float(base_increm + val_seguro) - invoice.financials.total_increments_me = float((Decimal(str(invoice.financials.total_increments_mn)) / tc) if tc else Decimal(0)) - elif invoice.financials.currency == Currency.MANUAL: - val_seguro = ((Decimal(str(invoice.financials.total_increments_me or 0)) / tc_mm) if tc_mm else Decimal(0)) - base_increm - invoice.financials.total_increments_me = float((base_increm + val_seguro) * tc_mm) - invoice.financials.total_increments_mn = float(Decimal(str(invoice.financials.total_increments_me)) * tc) - - invoice.status = InvoiceStatus.PROCESSED - invoice.party_count = len(lines) - - if getattr(invoice, "_cal_val_base_tc", 0) == 1: - invoice.process_log = "Se Actualizó con el Tipo de Cambio de la Fecha de Pago de Pedimento." - if getattr(invoice, "_act_seguridad", 0) == 1 and hasattr(invoice, "_username"): - invoice.updated_by = invoice._username - - -def main_process(db: Session, invoice: InvoiceHeader, tenant_id: str, company_id: str, username: str = "SYSTEM") -> dict: - """Proceso principal para importar facturas.""" - errors = ErrorCollector() - lines = pre_validators(db, invoice, tenant_id, company_id, errors) - if not lines: - errors.add_error(field="line_items", message="La factura debe contener al menos una partida", solution=["Agregue partidas"], code="NO_LINE_ITEMS") - errors.raise_if_errors() - - settings = AppSettingsService.get_resolved_settings(db, int(tenant_id), int(company_id)) - - inv_type = (invoice.invoice_type or "").strip().upper() - op_type = "imp" - - # Hierarchical resolve: invoices.types.imp.{type}.ssisgen.ssimpFormData - inv_map = settings.get("invoices", {}).get("types", {}).get(op_type, {}).get(inv_type, {}) - form_data = inv_map.get("ssisgen", {}).get("ssimpFormData", {}) or inv_map.get("qsisgen", {}).get("ssimpFormData", {}) or settings.get("ssisgen", {}) or settings.get("qsisgen", {}) - - cal_val_base_tc = int(form_data.get("calvalbasetcped") or form_data.get("CalValBaseTCPed") or 0) - act_seguridad = int(form_data.get("actseguridad") or form_data.get("ActSeguridad") or 0) - logger.info(f"AUDIT_DEBUG: act_seguridad resolve result = {act_seguridad} for Invoice={invoice.invoice_number}") - - invoice._cal_val_base_tc = cal_val_base_tc - invoice._act_seguridad = act_seguridad - invoice._username = username - - exchange_rate = invoice.financials.exchange_rate if invoice.financials else 0 - which_exchange_rate = "TCFAC" - - if cal_val_base_tc == 1: - if invoice.compliance_mx and invoice.compliance_mx.pedimento: - pedimento = invoice.compliance_mx.pedimento - if pedimento.pedimento_dates and pedimento.pedimento_dates.payment_date: - payment_date = pedimento.pedimento_dates.payment_date - from api.v1.modules.a76.general_catalogs.exchange_rate.models import ExchangeRate - from sqlalchemy import select - stmt = select(ExchangeRate).where(ExchangeRate.tenant_id == int(tenant_id), ExchangeRate.company_id == int(company_id), ExchangeRate.date == payment_date.date()) - ex_rate_row = db.execute(stmt).scalar_one_or_none() - if ex_rate_row: - exchange_rate = float(ex_rate_row.value) - which_exchange_rate = "TCPED" - else: - errors.add_error(field="exchange_rate", message=f"No se encontró tipo de cambio para {payment_date.date()}.", solution=["Capturar TC en catálogos."], code="EXCHANGE_RATE_NOT_FOUND") - else: - errors.add_error(field="pedimento", message="El pedimento no tiene fecha de pago.", solution=["Capturar fecha de pago."], code="PEDIMENTO_NO_PAYMENT_DATE") - else: - errors.add_error(field="compliance_mx.pedimento", message="Se requiere pedimento para el TC.", solution=["Asignar pedimento."], code="PEDIMENTO_REQUIRED_FOR_TC") - - if invoice.compliance_mx: invoice.compliance_mx.which_exchange_rate = which_exchange_rate - if invoice.financials: invoice.financials.exchange_rate = exchange_rate - db.flush() - - review_classes(db, invoice, lines, tenant_id, company_id, errors) - review_exchange_rate(db, invoice, cal_val_base_tc, errors) - - if invoice.logistics and invoice.logistics.weight_type == "kgs": - review_weights_kgs(db, lines, tenant_id, company_id, errors) - elif invoice.logistics and invoice.logistics.weight_type == "lbs": - review_weights_lbs(db, lines, tenant_id, company_id, errors) - - invoice_type = (invoice.invoice_type or "").strip().upper() - if invoice_type in {"DEF", "MEX"}: - assign_values_iva_lines(invoice, lines) - assign_values_invoice_totals(invoice, lines) - else: - assign_values_lines(invoice, lines) - assign_values_invoice(invoice, lines) - - octave_desc, octave_available = _validate_lines(db, invoice, lines, tenant_id, company_id, errors) - company = db.get(Company, invoice.company_id) - if company.prosec and octave_desc: - valida_imp_regla_octava(db=db, desc_list=octave_desc, dis_dict=octave_available, tenant_id=tenant_id, company_id=company_id, errors=errors) - - _validate_sisimp_limits(invoice, settings, errors) - errors.raise_if_errors() - - sql_errors: list = [] - if octave_desc: - descuenta_cupo_r_octava(db=db, desc_list=octave_desc, tenant_id=tenant_id, company_id=company_id, sql_errors=sql_errors) - - _update_invoice_totals(invoice, lines) - if invoice_type not in {"DEF", "MEX"}: - create_balance_entries(db, invoice, lines) - - db.flush() - if act_seguridad == 1: - from api.v1.modules.a76.audit_log.services.service import AuditService - AuditService.create_audit_log( - db=db, reference=invoice.invoice_number, procedure="ACTUALIZAR FACTURA", movement="IMPORTACION", - username=username, tenant_id=int(tenant_id), company_id=int(company_id), - table_name="invoice_header", record_id=str(invoice.id), operation_type="UPDATE" - ) - - db.flush() - return {"status": "success", "invoice_id": str(invoice.id)} diff --git a/backend/api/v1/modules/a76/invoices/imports/process/pre_validators.py b/backend/api/v1/modules/a76/invoices/imports/process/pre_validators.py deleted file mode 100644 index 78ad4dcd..00000000 --- a/backend/api/v1/modules/a76/invoices/imports/process/pre_validators.py +++ /dev/null @@ -1,149 +0,0 @@ -from sqlalchemy import func -from sqlalchemy.orm import Session, joinedload -from api.v1.modules.a76.invoices.models import InvoiceHeader, InvoiceStatus -from api.v1.modules.a76.items.models import LineItem -from api.v1.modules.a76.parts.models import Part -from api.v1.modules.a76.general_catalogs.fractions.warning_fractions.models import WarningFraction -from api.v1.modules.a76.app_settings.service import AppSettingsService -from core.exceptions import ErrorCollector - -from api.v1.modules.a76.invoices.common.common_validators import validate_invoice_items_decimals - -def pre_validators(db: Session, invoice: InvoiceHeader, tenant_id: str, company_id: str, errors: ErrorCollector): - if invoice.status == InvoiceStatus.PROCESSED: - errors.add_error( - "status", - "La factura ya fue procesada y no puede volver a actualizarse. Desactualícela primero.", - solution=["Use el botón 'Desactualizar' antes de volver a procesar la factura."], - code="ALREADY_PROCESSED", - value=invoice.status, - ) - errors.raise_if_errors() - return - - if not invoice.invoice_date: - errors.add_required_error("invoice_date") - - if not invoice.document_type: - errors.add_required_error("document_type") - - if not invoice.compliance_mx.provider_id: - errors.add_required_error("compliance_mx.provider_id") - - if not invoice.compliance_mx.sold_to_id: - errors.add_required_error("compliance_mx.sold_to_id") - - if not invoice.compliance_mx.shipped_to_id: - errors.add_required_error("compliance_mx.shipped_to_id") - - if not invoice.compliance_mx.customs_broker_id: - errors.add_required_error("compliance_mx.customs_broker_id") - - # Validación de estatus de pedimento (CERRADO / PAGADO) - if invoice.compliance_mx and invoice.compliance_mx.pedimento: - ped = invoice.compliance_mx.pedimento - if ped.pedimento_dates and ped.pedimento_dates.payment_date: - from datetime import datetime - today = datetime.now().date() - pay_date = ped.pedimento_dates.payment_date.date() - - # Obtener parámetros de seguridad - settings = AppSettingsService.get_resolved_settings(db, int(tenant_id), int(company_id)) - # Revisar actseguridad en ssisgen o qsisgen (según el sistema origen) - gen_params = settings.get("ssisgen", {}) - if not gen_params: - gen_params = settings.get("qsisgen", {}) - - act_seguridad = int(gen_params.get("actseguridad", 1)) - - # Si la fecha es futura (> hoy) o si la seguridad está desactivada (0), permitimos con advertencia - if pay_date > today or act_seguridad == 0: - pass # Permitir la actualización, el proceso continuará - else: - errors.add_error( - field="compliance_mx.pedimento", - message=f"No se puede procesar la factura porque el pedimento {ped.pedimento_number} ya se encuentra pagado el {pay_date}.", - solution=["Si requiere hacer cambios, desactive 'Solicitar Autorización para Actualizar Facturas' en la configuración general o rectifique el pedimento."], - code="PEDIMENTO_ALREADY_PAID" - ) - errors.raise_if_errors() - - - if not invoice.financials.exchange_rate or invoice.financials.exchange_rate <= 0: - errors.add_range_error( - "financials.exchange_rate", - min_value=0.0001, - ) - - if not invoice.financials.currency: - errors.add_required_error("El Tipo de Moneda esta vacio no se puede actualizar") - elif invoice.financials.currency == "manual" and not invoice.financials.currency_type: - errors.add_required_error("financials.currency_type") - - # 3.- Validacion que deber de existir un pedimento cuando es requerido - if not invoice.compliance_mx.is_pedimento_pending and not invoice.compliance_mx.pedimento_id: - errors.add_required_error("compliance_mx.pedimento_number") - - # 4.- Verificacion de que existan partidas para la factura, si no hay partidas no se puede procesar - item_count = ( - db.query(func.count(LineItem.id)) - .filter( - LineItem.invoice_id == invoice.id, - LineItem.tenant_id == invoice.tenant_id, - LineItem.company_id == invoice.company_id, - ) - .scalar() - ) - if item_count == 0: - errors.add_error( - field="items", - message="La factura no tiene partidas capturadas.", - solution=["Capture al menos una partida antes de procesar la factura."], - code="NO_ITEMS_FOUND", - ) - - # Advertencias para las fracciones y su horario - lines = ( - db.query(LineItem) - .options( - joinedload(LineItem.unit_of_measure_info), - joinedload(LineItem.quantity), - joinedload(LineItem.part_info).joinedload(Part.unit_of_measure_info) - ) - .filter( - LineItem.invoice_id == invoice.id, - LineItem.tenant_id == tenant_id, - LineItem.company_id == company_id, - ) - .all() - ) - - # Validar decimales en piezas (Parámetro validadecencant) - validate_invoice_items_decimals(db, lines, int(tenant_id), int(company_id), errors) - - fractions = {line.customs.fraction for line in lines if line.customs.fraction} - if fractions: - warned_fractions = { - row.fraction - for row in db.query(WarningFraction.fraction) - .filter(WarningFraction.fraction.in_(fractions), WarningFraction.tenant_id == tenant_id) - .all() - } - for line in lines: - if line.customs.fraction in warned_fractions: - errors.add_warning( - field="fraction", - message="Advertencia: Esta mercancía, sólo podrá entrar al territorio nacional por las aduanas del país, de lunes a sábado de 8:00 a 13:00 hrs. Ley 10, 18, LIGIE 1, Capítulo 87, RGCE 4.5.31., Anexo 4.", - solution=[""], - code="WARNING_FRACTION", - ) - - return lines - - - - - - - - \ No newline at end of file diff --git a/backend/api/v1/modules/a76/invoices/imports/process/routes.py b/backend/api/v1/modules/a76/invoices/imports/process/routes.py deleted file mode 100644 index 08b4e72a..00000000 --- a/backend/api/v1/modules/a76/invoices/imports/process/routes.py +++ /dev/null @@ -1,109 +0,0 @@ -from typing import Any, Dict - -from fastapi import APIRouter, Depends, HTTPException, Query -from sqlalchemy.orm import Session - -from core.celery_app import celery_app -from core.database import get_core_db -from core.security import get_current_user, validate_access_to_resource -from api.v1.modules.core.tasks_tracking import track_and_dispatch - -from api.v1.modules.a76.invoices.models import InvoiceHeader, OperationType -from api.v1.modules.a76.invoices.permission_map import get_invoice_permission_base -from .task import process_invoice_task -from ...exports.process.task import process_export_invoice_task - -router = APIRouter() - - -@router.post("/invoices/{invoice_id}/process") -def trigger_invoice_process( - invoice_id: int, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """ - Inicia el procesamiento de una factura como tarea Celery. - Detecta automáticamente si es importación o exportación por el - operation_type de la factura y despacha al proceso correspondiente. - Retorna el task_id para hacer polling del progreso. - """ - tenant_id = validate_access_to_resource(db, company_id, current_user) - - invoice: InvoiceHeader | None = db.get(InvoiceHeader, invoice_id) - if invoice is None: - raise HTTPException(status_code=404, detail=f"Factura {invoice_id} no encontrada.") - - # 🛡️ Validación de permisos granulares - perm_base = get_invoice_permission_base(invoice.operation_type, invoice.invoice_type) - validate_access_to_resource(db, company_id, current_user, required_permissions=[f"{perm_base}.process"]) - - if invoice.operation_type == OperationType.EXP: - task = track_and_dispatch( - db=db, - task=process_export_invoice_task, - tenant_id=tenant_id, - company_id=company_id, - requested_by_user=current_user.get("preferred_username") or current_user.get("email") or current_user.get("sub"), - task_name="process_export_invoice_task", - task_group="invoices", - task_origin="a76/invoices/exports/process", - args=[invoice_id, str(tenant_id), str(company_id)], - ) - else: - task = track_and_dispatch( - db=db, - task=process_invoice_task, - tenant_id=tenant_id, - company_id=company_id, - requested_by_user=current_user.get("preferred_username") or current_user.get("email") or current_user.get("sub"), - task_name="process_invoice_task", - task_group="invoices", - task_origin="a76/invoices/imports/process", - args=[invoice_id, str(tenant_id), str(company_id)], - ) - - return {"task_id": task.id} - - -@router.get("/invoices/process/{task_id}/status") -def get_invoice_process_status(task_id: str): - """ - Consulta el estado de progreso de una tarea de procesamiento de factura. - - Retorna: - - state: 'PROCESSING' | 'SUCCESS' | 'FAILURE' - - info: { current: int, status: str } (cuando state == 'PROCESSING') - - result: dict (cuando state == 'SUCCESS' o 'FAILURE') - """ - task_result = celery_app.AsyncResult(task_id) - - if task_result.state in ("PENDING", "STARTED"): - return { - "state": "PROCESSING", - "info": {"current": 0, "status": "Iniciando..."}, - } - - if task_result.state == "PROGRESS": - return { - "state": "PROCESSING", - "info": task_result.info or {"current": 0, "status": "Procesando..."}, - } - - if task_result.state == "SUCCESS": - return { - "state": "SUCCESS", - "result": task_result.result, - } - - error_info = task_result.result - if isinstance(error_info, Exception): - error_msg = str(error_info) - else: - error_msg = str(error_info) if error_info else "Error desconocido" - - return { - "state": "FAILURE", - "result": error_msg, - } diff --git a/backend/api/v1/modules/a76/invoices/imports/process/sub_process/assing_values.py b/backend/api/v1/modules/a76/invoices/imports/process/sub_process/assing_values.py deleted file mode 100644 index 66a59e64..00000000 --- a/backend/api/v1/modules/a76/invoices/imports/process/sub_process/assing_values.py +++ /dev/null @@ -1,95 +0,0 @@ -from decimal import Decimal -from typing import List - -from sqlalchemy.orm import Session - -from api.v1.modules.a76.invoices.models import Currency, InvoiceHeader -from api.v1.modules.a76.items.models import LineItem - - -def assign_values_lines( - invoice: InvoiceHeader, - lines: List[LineItem], -) -> None: - """ - Calculates and assigns unit costs and values in all currency types for every - line item, based on the invoice currency and exchange rates. - - Legacy returned/existence counters were removed from item_line_quantities; - balances are now derived from a24.balance_movement and discharges. - - Currency mapping (legacy -> current enum): - ME (moneda extranjera / foreign) -> Currency.FOREIGN - MN (moneda nacional / local) -> Currency.LOCAL - MC (moneda de cuenta / manual) -> Currency.MANUAL - """ - currency = invoice.financials.currency - tc = Decimal(str(invoice.financials.exchange_rate or 0)) - tc_mm = Decimal(str(invoice.financials.exchange_rate_mm or 0)) - - for line in lines: - if line.financial is None: - continue - - capture = line.financial.unit_cost_capture or Decimal(0) - qty = (line.quantity.quantity or Decimal(0)) if line.quantity else Decimal(0) - - if currency == Currency.FOREIGN: # ME - line.financial.unit_cost_usd = capture - line.financial.value_usd = capture * qty - line.financial.unit_cost_mxn = capture * tc - line.financial.value_mxn = capture * tc * qty - line.financial.value_mc = capture * qty - elif currency == Currency.LOCAL: # MN - line.financial.unit_cost_mxn = capture - line.financial.value_mxn = capture * qty - line.financial.unit_cost_usd = (capture / tc) if tc else Decimal(0) - line.financial.value_usd = (capture / tc * qty) if tc else Decimal(0) - line.financial.value_mc = capture * qty - elif currency == Currency.MANUAL: # MC - line.financial.unit_cost_usd = capture * tc_mm - line.financial.value_usd = capture * tc_mm * qty - line.financial.unit_cost_mxn = capture * tc_mm * tc - line.financial.value_mxn = capture * tc_mm * tc * qty - line.financial.value_mc = capture * qty - - # NOTE: no legacy returned/existence counters to reset. - - -def assign_values_invoice( - invoice: InvoiceHeader, - lines: List[LineItem], -) -> None: - """ - Totals quantity, weight, and value fields from all line items and writes the - aggregated results to invoice.financials. - - Ported from legacy ASIGNAVALORES_FACTURA routine. - """ - total_quantity = Decimal(0) - total_net_weight = Decimal(0) - total_gross_weight = Decimal(0) - total_packages = 0 - total_value_mxn = Decimal(0) - total_value_usd = Decimal(0) - total_value_mc = Decimal(0) - - for line in lines: - if line.quantity: - total_quantity += line.quantity.quantity or Decimal(0) - total_net_weight += line.quantity.net_weight or Decimal(0) - total_gross_weight += line.quantity.gross_weight or Decimal(0) - total_packages += line.quantity.package_quantity or 0 - if line.financial: - total_value_mxn += line.financial.value_mxn or Decimal(0) - total_value_usd += line.financial.value_usd or Decimal(0) - total_value_mc += line.financial.value_mc or Decimal(0) - - if invoice.financials is not None: - invoice.financials.value_mn = float(total_value_mxn) - invoice.financials.value_me = float(total_value_usd) - invoice.financials.value_mc = float(total_value_mc) - invoice.financials.total_quantity = float(total_quantity) - invoice.financials.net_weight = float(total_net_weight) - invoice.financials.gross_weight = float(total_gross_weight) - invoice.financials.total_packages = total_packages diff --git a/backend/api/v1/modules/a76/invoices/imports/process/sub_process/assing_values_def_mex.py b/backend/api/v1/modules/a76/invoices/imports/process/sub_process/assing_values_def_mex.py deleted file mode 100644 index fec9e4d6..00000000 --- a/backend/api/v1/modules/a76/invoices/imports/process/sub_process/assing_values_def_mex.py +++ /dev/null @@ -1,159 +0,0 @@ -from decimal import Decimal -from typing import List - -from api.v1.modules.a76.invoices.models import Currency, InvoiceHeader -from api.v1.modules.a76.items.models import LineItem - - -def assign_values_iva_lines( - invoice: InvoiceHeader, - lines: List[LineItem], -) -> None: - """ - ASIGNAVALORES_IVA_PARTIDA - Asigna valores por partida para IMPO DEFINITIVA / COMPRAS MEXICANAS - calculando subtotal + IVA + total en ME/MN/MC. - - Nota: - - Los contadores legacy CantRetornada/CantRetornadaTemp ya no existen. - - La trazabilidad de saldos vive en balance_movement + discharges. - """ - if not invoice.financials: - return - - currency = invoice.financials.currency - tc = Decimal(str(invoice.financials.exchange_rate or 0)) - tc_mm = Decimal(str(invoice.financials.exchange_rate_mm or 0)) - iva_factor = Decimal(str(invoice.financials.iva_factor or 0)) - - for line in lines: - fin = line.financial - qty_rec = line.quantity - if fin is None or qty_rec is None: - continue - - qty = Decimal(str(qty_rec.quantity or 0)) - capture = Decimal(str(fin.unit_cost_capture or 0)) - if qty <= 0: - continue - - if currency == Currency.FOREIGN: # ME - # ME base - fin.unit_cost_usd = capture - fin.sub_import_value_usd = capture * qty - fin.vat_usd = fin.sub_import_value_usd * iva_factor / 100 - fin.value_usd = fin.sub_import_value_usd + fin.vat_usd - - # MN converted from ME - fin.unit_cost_mxn = capture * tc - fin.sub_import_value_mxn = fin.unit_cost_mxn * qty - fin.vat_mxn = fin.sub_import_value_mxn * iva_factor / 100 - fin.value_mxn = fin.sub_import_value_mxn + fin.vat_mxn - - # MC mirrors capture currency in legacy - fin.unit_cost_mc = capture - fin.sub_import_value_mc = capture * qty - fin.vat_mc = fin.sub_import_value_mc * iva_factor / 100 - fin.value_mc = fin.sub_import_value_mc + fin.vat_mc - - elif currency == Currency.LOCAL: # MN - # MN base - fin.unit_cost_mxn = capture - fin.sub_import_value_mxn = capture * qty - fin.vat_mxn = fin.sub_import_value_mxn * iva_factor / 100 - fin.value_mxn = fin.sub_import_value_mxn + fin.vat_mxn - - # ME converted from MN - fin.unit_cost_usd = (capture / tc) if tc else Decimal(0) - fin.sub_import_value_usd = fin.unit_cost_usd * qty - fin.vat_usd = fin.sub_import_value_usd * iva_factor / 100 - fin.value_usd = fin.sub_import_value_usd + fin.vat_usd - - # MC mirrors capture currency in legacy - fin.unit_cost_mc = capture - fin.sub_import_value_mc = capture * qty - fin.vat_mc = fin.sub_import_value_mc * iva_factor / 100 - fin.value_mc = fin.sub_import_value_mc + fin.vat_mc - - elif currency == Currency.MANUAL: # MC - # ME from MC * tc_mm - fin.unit_cost_usd = capture * tc_mm - fin.sub_import_value_usd = fin.unit_cost_usd * qty - fin.vat_usd = fin.sub_import_value_usd * iva_factor / 100 - fin.value_usd = fin.sub_import_value_usd + fin.vat_usd - - # MN from ME * tc - fin.unit_cost_mxn = fin.unit_cost_usd * tc - fin.sub_import_value_mxn = fin.unit_cost_mxn * qty - fin.vat_mxn = fin.sub_import_value_mxn * iva_factor / 100 - fin.value_mxn = fin.sub_import_value_mxn + fin.vat_mxn - - # MC base - fin.unit_cost_mc = capture - fin.sub_import_value_mc = capture * qty - fin.vat_mc = fin.sub_import_value_mc * iva_factor / 100 - fin.value_mc = fin.sub_import_value_mc + fin.vat_mc - - -def assign_values_invoice_totals( - invoice: InvoiceHeader, - lines: List[LineItem], -) -> None: - """ - ASIGNAVALORES_FACTURA - Totaliza cantidades, pesos y valores/IVA en encabezado para IMPO DEF/MEX. - """ - if not invoice.financials: - return - - total_qty = Decimal(0) - total_net = Decimal(0) - total_gross = Decimal(0) - total_packages = 0 - - total_val_mn = Decimal(0) - total_val_me = Decimal(0) - total_val_mc = Decimal(0) - total_iva_mn = Decimal(0) - total_iva_me = Decimal(0) - total_iva_mc = Decimal(0) - total_sub_mn = Decimal(0) - total_sub_me = Decimal(0) - total_sub_mc = Decimal(0) - - for line in lines: - q = line.quantity - f = line.financial - if q: - total_qty += Decimal(str(q.quantity or 0)) - total_net += Decimal(str(q.net_weight or 0)) - total_gross += Decimal(str(q.gross_weight or 0)) - total_packages += int(q.package_quantity or 0) - if f: - total_val_mn += Decimal(str(f.value_mxn or 0)) - total_val_me += Decimal(str(f.value_usd or 0)) - total_val_mc += Decimal(str(f.value_mc or 0)) - total_iva_mn += Decimal(str(f.vat_mxn or 0)) - total_iva_me += Decimal(str(f.vat_usd or 0)) - total_iva_mc += Decimal(str(f.vat_mc or 0)) - total_sub_mn += Decimal(str(f.sub_import_value_mxn or 0)) - total_sub_me += Decimal(str(f.sub_import_value_usd or 0)) - total_sub_mc += Decimal(str(f.sub_import_value_mc or 0)) - - fin = invoice.financials - fin.total_quantity = float(total_qty) - fin.net_weight = float(total_net) - fin.gross_weight = float(total_gross) - fin.total_packages = total_packages - - fin.value_mn = float(total_val_mn) - fin.value_me = float(total_val_me) - fin.value_mc = float(total_val_mc) - - fin.iva_mn = float(total_iva_mn) - fin.iva_me = float(total_iva_me) - fin.iva_mc = float(total_iva_mc) - - # No existe subtotal a nivel encabezado en el modelo actual. - # Se conserva en partidas (sub_import_value_*), de donde se agrega cuando se necesite. - _ = (total_sub_mn, total_sub_me, total_sub_mc) diff --git a/backend/api/v1/modules/a76/invoices/imports/process/sub_process/review_classes.py b/backend/api/v1/modules/a76/invoices/imports/process/sub_process/review_classes.py deleted file mode 100644 index 935bbca3..00000000 --- a/backend/api/v1/modules/a76/invoices/imports/process/sub_process/review_classes.py +++ /dev/null @@ -1,208 +0,0 @@ -from typing import List - -from sqlalchemy import func, or_ -from sqlalchemy.orm import Session - -from api.v1.modules.a76.classes.models import Class -from api.v1.modules.a76.general_catalogs.fractions.historical_tariff_fractions.models import ( - HistoricalTariffFraction, -) -from api.v1.modules.a76.general_catalogs.fractions.tariff_fractions.models import TariffFraction -from api.v1.modules.a76.invoices.models import InvoiceHeader -from api.v1.modules.a76.items.models import LineItem -from core.exceptions import ErrorCollector - - -def _fraction_exists_via_sitar(fraction_code: str) -> bool: - """Returns True if the fraction exists in SITAR (fracciones o fracciones-anteriores). - - Fraction format: first 8 chars = base fraction, chars 9-10 = NICO/country (optional). - Falls back to False if SITAR is not configured or request fails. - """ - if not fraction_code: - return True - - base_frac = fraction_code[:8].strip() - nico = fraction_code[8:10].strip() if len(fraction_code) > 8 else "" - - try: - from api.v1.modules.sitar.fracciones.service import FraccionesService - from api.v1.modules.sitar.fracciones_anteriores.service import ( - FraccionesAnterioresService, - ) - - # 1. Buscar en fracciones arancelarias (SITAR) - results = FraccionesService.search_sync( - fraccion=base_frac, - nico=nico if nico else None, - limit=1, - ) - if results: - return True - - # 2. Buscar en fracciones anteriores / histórico (SITAR) - hist_results = FraccionesAnterioresService.search_sync( - fraccion_anterior=base_frac, - limit=1, - ) - return len(hist_results) > 0 - - except (ValueError, Exception): - # SITAR no configurado o error de red: se usa fallback a BD local - return False - - -def _fraction_exists_in_local_db(db: Session, fraction_code: str) -> bool: - """Fallback: valida contra TariffFraction e HistoricalTariffFraction locales.""" - if not fraction_code: - return True - - base_frac = fraction_code[:8] - nico = fraction_code[8:10] if len(fraction_code) > 8 else "" - - tariff_q = db.query(TariffFraction).filter( - func.left(TariffFraction.code, 8) == base_frac - ) - if nico: - tariff_q = tariff_q.filter(TariffFraction.nico == nico) - else: - tariff_q = tariff_q.filter( - or_(TariffFraction.nico.is_(None), TariffFraction.nico == "") - ) - if tariff_q.first() is not None: - return True - - hist_q = db.query(HistoricalTariffFraction).filter( - HistoricalTariffFraction.historical_fraction == base_frac - ) - if nico: - hist_q = hist_q.filter(HistoricalTariffFraction.country == nico) - else: - hist_q = hist_q.filter( - or_( - HistoricalTariffFraction.country.is_(None), - HistoricalTariffFraction.country == "", - ) - ) - return hist_q.first() is not None - - -def _fraction_exists_in_catalog(db: Session, fraction_code: str) -> bool: - """Returns True if the fraction exists in SITAR (fracciones) or fallback a BD local. - - Usa las funciones de SITAR de fracciones como fuente principal. - Si SITAR no está configurado o falla, valida contra TariffFraction e HistoricalTariffFraction. - """ - if _fraction_exists_via_sitar(fraction_code): - return True - return _fraction_exists_in_local_db(db, fraction_code) - - -def _validate_line_fraction( - db: Session, line: LineItem, errors: ErrorCollector -) -> None: - """Adds a FRACCION error if the line's fraction does not exist in either catalog.""" - fraction = line.customs.fraction if line.customs else None - if not fraction: - return - - if _fraction_exists_in_catalog(db, fraction): - return - - class_code = line.class_info.class_code if line.class_info else "" - errors.add_error( - field="fraction", - message=( - f"La Factura contiene la fraccion: {fraction} asociada al Clase {class_code} " - "que no existe en el catálogo de fracciones" - ), - solution=["Agregar la fracción a fracciones históricas."], - code="FRACCION", - ) - - -def review_classes( - db: Session, - invoice: InvoiceHeader, - lines: List[LineItem], - tenant_id: str, - company_id: str, - errors: ErrorCollector, -) -> None: - """ - Validates class and fraction integrity for all line items of an invoice. - - Logic ported from legacy REVISA_CLASE routine: - 1. Identify line items with no assigned class (class_id IS NULL). - 2. If rule_3121_parties_ii: validate that every line has container_parts_ii set. - 3a. If no class errors: validate fractions for ALL lines. - 3b. If class errors exist: add a CLASE error per invalid line and validate - fractions only for those lines. - 4. If invoice logistics does not mark equipment_reviewed, flag lines whose - class requires physical review (Class.physical_review == 1). - """ - # 1. Lines whose class is not assigned / not found in the catalog - invalid_class_lines = [line for line in lines if line.class_id is None] - has_class_errors = bool(invalid_class_lines) - - # 2. Container validation (Regla 3.1.21 Partes II) - if invoice.compliance_mx.rule_3121_parties_ii: - for line in lines: - if not line.container_parts_ii: - class_code = line.class_info.class_code if line.class_info else "" - errors.add_error( - field="container_parts_ii", - message=f"La clase: {class_code} no tiene contenedor asignado", - solution=[f"Capturar el contenedor en la partida: {line.line_number}"], - code="CONTENEDOR", - ) - - # 3. Fraction and class validation - if not has_class_errors: - # All classes are valid — validate fractions for every line - for line in lines: - _validate_line_fraction(db, line, errors) - else: - # Some classes are missing — report CLASE errors and validate - # fractions only for the affected lines - for line in invalid_class_lines: - errors.add_error( - field="class", - message=f"La clase no existe en catálogo de clases (línea: {line.line_number})", - solution=[ - f"Borrar la partida: {line.line_number}, " - "o dar de alta la clase en el catálogo de Clases" - ], - code="CLASE", - ) - _validate_line_fraction(db, line, errors) - - # 4. Physical review check - if not (invoice.logistics and invoice.logistics.equipment_reviewed): - review_required_lines = ( - db.query(LineItem) - .join(Class, LineItem.class_id == Class.id) - .filter( - LineItem.invoice_id == invoice.id, - LineItem.tenant_id == tenant_id, - LineItem.company_id == company_id, - Class.physical_review == 1, - ) - .all() - ) - for line in review_required_lines: - cls = db.get(Class, line.class_id) - class_code = cls.class_code if cls else "" - errors.add_error( - field="physical_review", - message=( - f"La Clase: {class_code} en la Linea: {line.line_number} " - "No ha sido Revisada." - ), - solution=[ - f"Revisar el Equipo de la Partida: {line.line_number} " - "y marcar Equipo revisado en Continuación (logística) a nivel factura." - ], - code="CLASE", - ) - \ No newline at end of file diff --git a/backend/api/v1/modules/a76/invoices/imports/process/sub_process/review_exchange_rate.py b/backend/api/v1/modules/a76/invoices/imports/process/sub_process/review_exchange_rate.py deleted file mode 100644 index 0ab291a7..00000000 --- a/backend/api/v1/modules/a76/invoices/imports/process/sub_process/review_exchange_rate.py +++ /dev/null @@ -1,69 +0,0 @@ -from decimal import Decimal - -from sqlalchemy import func -from sqlalchemy.orm import Session - -from api.v1.modules.a76.general_catalogs.exchange_rate.models import ExchangeRate -from api.v1.modules.a76.invoices.models import InvoiceHeader -from core.exceptions import ErrorCollector - - -def review_exchange_rate( - db: Session, - invoice: InvoiceHeader, - cal_val_base_tc: int, - errors: ErrorCollector, -) -> None: - """ - Validates that the invoice's exchange rate matches the one registered in the - catalog for the invoice date (gtipocambio / ExchangeRate table). - - Ported from legacy REVISA_TIPOCAMBIO routine. - Only runs when the system is NOT configured to use the pedimento's exchange - rate (SisGen:CalValBaseTCPed = 0). - """ - if cal_val_base_tc == 1: - # SKIP: TC is derived from pedimento payment date - return - if not invoice.invoice_date: - return - - catalog_rate: ExchangeRate | None = ( - db.query(ExchangeRate) - .filter( - ExchangeRate.tenant_id == invoice.tenant_id, - ExchangeRate.company_id == invoice.company_id, - func.date(ExchangeRate.date) == invoice.invoice_date, - ) - .first() - ) - - if catalog_rate is None: - errors.add_error( - field="exchange_rate", - message="No existe tipo de cambio registrado para la fecha de la factura", - solution=[ - "Capture el tipo de cambio correspondiente a la fecha de la factura " - "en el catálogo de Tipo de Cambio" - ], - code="TIPO_DE_CAMBIO", - ) - return - - invoice_tc = ( - Decimal(str(invoice.financials.exchange_rate)) - if invoice.financials and invoice.financials.exchange_rate is not None - else None - ) - catalog_tc = catalog_rate.value - - if invoice_tc is None or catalog_tc is None or invoice_tc != catalog_tc: - errors.add_error( - field="exchange_rate", - message="No esta capturado correctamente el Tipo de Cambio", - solution=[ - "Capture o modifique el tipo de cambio que corresponda a la factura " - "en el catálogo de Tipo de Cambio" - ], - code="TIPO_DE_CAMBIO", - ) diff --git a/backend/api/v1/modules/a76/invoices/imports/process/sub_process/review_limits.py b/backend/api/v1/modules/a76/invoices/imports/process/sub_process/review_limits.py deleted file mode 100644 index 6e100527..00000000 --- a/backend/api/v1/modules/a76/invoices/imports/process/sub_process/review_limits.py +++ /dev/null @@ -1,80 +0,0 @@ -import logging -from decimal import Decimal -from typing import Dict, Any, Optional -from api.v1.modules.a76.invoices.models import InvoiceHeader -from core.exceptions import ErrorCollector - -logger = logging.getLogger(__name__) - -def review_limits( - invoice: InvoiceHeader, - settings: Dict[str, Any], - errors: ErrorCollector -) -> None: - """ - Valida que los totales de la factura no excedan los límites configurados - en SisImpo o SisDef. - """ - if not invoice.financials: - logger.warning(f"LIMIT_CHECK: No financials found for invoice {invoice.id}") - return - - # Determinamos qué categoría de parámetros usar según el tipo de factura - invoice_type = (invoice.invoice_type or "").strip().upper() - op_type = "imp" # Por ahora enfocado en importación - - # 1. Intentar obtener de la raíz (ssisimpo/ssisdef) - if invoice_type in {"DEF", "MEX"}: - params = settings.get("ssisdef", {}) - cat_name = "ssisdef" - else: - params = settings.get("ssisimpo", {}) - cat_name = "ssisimpo" - - # 2. Si no están en la raíz, intentar en la estructura profunda (invoices.types...) - # Esta es la estructura que viene del frontend según el JSON "hermoso" - if not params.get("CantLimite") and not params.get("cantlimite"): - invoice_map = settings.get("invoices", {}).get("types", {}).get(op_type, {}).get(invoice_type, {}) - # Buscar en ssimpFormData dentro de qsisgen o ssisgen - params = invoice_map.get("qsisgen", {}).get("ssimpFormData", {}) or invoice_map.get("ssisgen", {}).get("ssimpFormData", {}) or params - cat_name = f"invoices.types.{op_type}.{invoice_type}.ssimpFormData" - - # Obtener límites Máximos - limit_qty = Decimal(str(params.get("cantlimite") or params.get("CantLimite") or 0)) - limit_weight = Decimal(str(params.get("pesolimite") or params.get("PesoLimite") or 0)) - limit_value = Decimal(str(params.get("valorlimite") or params.get("ValorLimite") or 0)) - - # Obtener límites Mínimos - min_limit_qty = Decimal(str(params.get("cantlimitemin") or params.get("CantLimiteMin") or 0)) - min_limit_weight = Decimal(str(params.get("pesolimitemin") or params.get("PesoLimiteMin") or 0)) - min_limit_value = Decimal(str(params.get("valorlimitemin") or params.get("ValorLimiteMin") or 0)) - - # Totales actuales de la factura - current_qty = Decimal(str(invoice.financials.total_quantity or 0)) - current_weight = Decimal(str(invoice.financials.net_weight or 0)) - current_value = Decimal(str(invoice.financials.value_me or 0)) - - logger.info(f"LIMIT_CHECK: Invoice={invoice.invoice_number} Type={invoice_type} Cat={cat_name}") - logger.info(f"LIMIT_CHECK: Qty: Current={current_qty} Max={limit_qty} Min={min_limit_qty}") - logger.info(f"LIMIT_CHECK: Weight: Current={current_weight} Max={limit_weight} Min={min_limit_weight}") - logger.info(f"LIMIT_CHECK: Value: Current={current_value} Max={limit_value} Min={min_limit_value}") - - # --- Validaciones de Máximos --- - if limit_qty > 0 and current_qty > limit_qty: - errors.add_error(field="financials.total_quantity", message=f"La cantidad total ({current_qty}) excede el máximo permitido ({limit_qty}).", solution=["Ajuste las cantidades."], code="LIMIT_QTY_EXCEEDED") - - if limit_weight > 0 and current_weight > limit_weight: - errors.add_error(field="financials.net_weight", message=f"El peso neto total ({current_weight}) excede el máximo permitido ({limit_weight}).", solution=["Ajuste los pesos."], code="LIMIT_WEIGHT_EXCEEDED") - - if limit_value > 0 and current_value > limit_value: - errors.add_error(field="financials.value_me", message=f"El valor total en USD ({current_value}) excede el máximo permitido ({limit_value}).", solution=["Ajuste los costos."], code="LIMIT_VALUE_EXCEEDED") - - # --- Validaciones de Mínimos --- - if min_limit_qty > 0 and current_qty < min_limit_qty: - errors.add_error(field="financials.total_quantity", message=f"La cantidad total ({current_qty}) es inferior al mínimo requerido ({min_limit_qty}).", solution=["Aumente las cantidades."], code="MIN_LIMIT_QTY_NOT_MET") - - if min_limit_weight > 0 and current_weight < min_limit_weight: - errors.add_error(field="financials.net_weight", message=f"El peso neto total ({current_weight}) es inferior al mínimo requerido ({min_limit_weight}).", solution=["Aumente los pesos."], code="MIN_LIMIT_WEIGHT_NOT_MET") - - if min_limit_value > 0 and current_value < min_limit_value: - errors.add_error(field="financials.value_me", message=f"El valor total en USD ({current_value}) es inferior al mínimo requerido ({min_limit_value}).", solution=["Aumente los costos."], code="MIN_LIMIT_VALUE_NOT_MET") diff --git a/backend/api/v1/modules/a76/invoices/imports/process/sub_process/review_rule_octave.py b/backend/api/v1/modules/a76/invoices/imports/process/sub_process/review_rule_octave.py deleted file mode 100644 index 431070f5..00000000 --- a/backend/api/v1/modules/a76/invoices/imports/process/sub_process/review_rule_octave.py +++ /dev/null @@ -1,899 +0,0 @@ -from dataclasses import dataclass -from datetime import date -from decimal import Decimal -from typing import Optional, List - -from sqlalchemy.orm import Session - -from api.v1.modules.a76.general_catalogs.fractions.previous_fractions.models import PreviousFraction -from api.v1.modules.a76.invoices.models import InvoiceHeader -from api.v1.modules.a76.items.models import LineItem -from api.v1.modules.a76.rule_octave.balances.models import OctaveBalance -from api.v1.modules.a76.rule_octave.country.models import CountryRuleOct -from api.v1.modules.a76.rule_octave.fractions.models import FractionRuleOctave -from api.v1.modules.a76.rule_octave.permissions.models import OctavePermission -from ....common.process.review_equivalence import _get_unit_equivalence -from core.exceptions import ErrorCollector - -# Clarion date 80354 ≈ 2010-05-31 (see previous_fractions/models.py) -_CLARION_DATE_CUTOFF = date(2010, 5, 31) - -# RFCs where fraction comparison uses only the first 8 characters -_RFC_FRACTION_8_CHARS = {"CLA940831AZ5", "CTE9801305I8"} - - -@dataclass -class OctavePermitEntry: - """ - Entrada de permiso de Regla Octava para importación. - Paridad: registro en QueuePermReglaOctDesc / QROImp (Clarion SCAII). - """ - - invoice_import: str # FacturaImpo — encabezado de la factura - import_line: int # LineaImpo - part_number: str # NumParte (número de parte o clase) - octave_permit: str # PermisoROctava - ro_line: int # LineaRO - origin_country: str # PaisOrigen - fraction_type: str # TipoFracImpo - import_fraction: str # FraccionImpo - sector: str # Sector - class_code: str # Clase - unit_of_measure: str # UniMed — se resuelve en _revisa_um - quantity: Decimal # Cantidad - value: Decimal # Valor - quantity_used: Decimal # CantUsada (inicia en 0) - value_used: Decimal # ValorUsado (inicia en 0) - - -# ───────────────────────────────────────────────────────────────────────────── -# Helpers -# ───────────────────────────────────────────────────────────────────────────── - -def _previous_fraction_exists( - db: Session, - tenant_id: str, - company_id: str, - previous_fraction: str, - current_fraction: str, -) -> bool: - """ - Verifica si existe un mapeo fracción-anterior → fracción-actual. - Paridad: consulta a SFraccionesAnterioresN (Clarion SCAII). - """ - return ( - db.query(PreviousFraction) - .filter( - PreviousFraction.tenant_id == tenant_id, - PreviousFraction.company_id == company_id, - PreviousFraction.previous_fraction == previous_fraction, - PreviousFraction.current_fraction == current_fraction, - ) - .count() - ) > 0 - - -# ───────────────────────────────────────────────────────────────────────────── -# REVISA_UM_REGLA_OCTAVA -# ───────────────────────────────────────────────────────────────────────────── - -def _revisa_um_regla_octava( - db: Session, - line: LineItem, - permission: OctavePermission, - entry: OctavePermitEntry, - company_rfc: str, - tenant_id: str, - company_id: str, - errors: ErrorCollector, -) -> None: - """ - Revisión de unidades de medida para Regla Octava. - Paridad: REVISA_UM_REGLA_OCTAVA (Clarion SCAII). - - - Busca la fracción del permiso RO a nivel de línea. - - Valida que la fracción de la partida coincida con la del permiso. - - Determina la cantidad y UM a registrar en el entry, usando conversión si aplica. - """ - ro_line = line.reference.ro_line if line.reference else 0 - if not ro_line: - return - - line_fraction = ( - db.query(FractionRuleOctave) - .filter( - FractionRuleOctave.tenant_id == tenant_id, - FractionRuleOctave.company_id == company_id, - FractionRuleOctave.permission == line.octave_permit, - FractionRuleOctave.line == ro_line, - ) - .first() - ) - - if line_fraction is None: - errors.add_error( - field=f"line[{line.line_number}].octave_permit", - message=( - f"El Permiso de Regla Octava: {line.octave_permit}" - f" con línea: {ro_line} no existe." - ), - solution=["Revisar el Permiso de Regla Octava en la Partida de Importación."], - code="RO_FRACTION_NOT_FOUND", - ) - return - - # ── Validar fracción ───────────────────────────────────────────────────── - line_fraccion = (line.customs.fraction or "") if line.customs else "" - ro_fraccion = line_fraction.fraction or "" - - use_8_chars = company_rfc.upper() in _RFC_FRACTION_8_CHARS - frac_line = line_fraccion[:8] if use_8_chars else line_fraccion - frac_ro = ro_fraccion[:8] if use_8_chars else ro_fraccion - - if frac_line != frac_ro: - start_date = permission.start_date - if start_date and hasattr(start_date, "date"): - start_date = start_date.date() - allow_previous = start_date is not None and start_date <= _CLARION_DATE_CUTOFF - - fraction_ok = False - if allow_previous: - fraction_ok = _previous_fraction_exists( - db, - tenant_id, - company_id, - previous_fraction=ro_fraccion[:8], - current_fraction=line_fraccion[:8], - ) - - if not fraction_ok: - errors.add_error( - field=f"line[{line.line_number}].customs.fraction", - message=f"La fracción: {frac_line} es diferente a la Fraccion: {frac_ro}", - solution=["Revisar la Partida de Importación y el Permiso de Regla Octava."], - code="RO_FRACTION_MISMATCH", - ) - return - - # ── Determinar cantidad y valor según unidad de medida ─────────────────── - entry.unit_of_measure = line_fraction.unit_of_measure or "" - line_um = line.unit_of_measure_info.code if line.unit_of_measure_info else "" - quantity = Decimal(str(line.quantity.quantity or 0)) if line.quantity else Decimal(0) - value_me = Decimal(str(line.financial.value_usd or 0)) if line.financial else Decimal(0) - - if line_fraction.unit_of_measure == line_um: - entry.quantity = quantity - entry.value = value_me - return - - multi_divide, factor_conv = _get_unit_equivalence( - db, line_um, line_fraction.unit_of_measure or "", tenant_id, company_id - ) - - if multi_divide == "M": - entry.quantity = quantity * factor_conv - entry.value = value_me - elif multi_divide == "D": - entry.quantity = quantity / factor_conv if factor_conv else Decimal(0) - entry.value = value_me - else: - if line_fraction.unit_of_measure == "KGS": - net_weight = ( - Decimal(str(line.quantity.net_weight or 0)) if line.quantity else Decimal(0) - ) - entry.quantity = net_weight - entry.value = value_me - else: - errors.add_error( - field=f"line[{line.line_number}].unit_of_measure", - message=( - f"Regla Octava: No hay equivalencia entre la U.M. del Permiso de RO: " - f"{line_fraction.unit_of_measure} y la U.M. de captura {line_um}." - ), - solution=["Capturar su equivalencia en el Catálogo de Equivalencias."], - code="EQUIVALENCIA", - ) - - -# ───────────────────────────────────────────────────────────────────────────── -# LLENA_IMPO_PERMISO_REGLA_OCTAVA -# ───────────────────────────────────────────────────────────────────────────── - -def llena_impo_permiso_regla_octava( - db: Session, - invoice: InvoiceHeader, - line: LineItem, - company_rfc: str, - tenant_id: str, - company_id: str, - errors: ErrorCollector, -) -> Optional[OctavePermitEntry]: - """ - Llena lo que se quiere importar con los permisos de Regla Octava. - Paridad: LLENA_IMPO_PERMISO_REGLA_OCTAVA (Clarion SCAII). - - Retorna un OctavePermitEntry listo para agregar a la cola de permisos RO, - o None si el permiso indicado en la partida no existe en el catálogo. - """ - octave_permit_code = line.octave_permit or "" - - permission = ( - db.query(OctavePermission) - .filter( - OctavePermission.tenant_id == tenant_id, - OctavePermission.company_id == company_id, - OctavePermission.permission == octave_permit_code, - ) - .first() - ) - - if permission is None: - return None - - # NumParte: preferir número de parte, caer en código de clase - part_number = "" - if line.part_info and line.part_info.part_number: - part_number = line.part_info.part_number - elif line.class_info and line.class_info.class_code: - part_number = line.class_info.class_code - - class_code = (line.class_info.class_code or "") if line.class_info else "" - - # Sector: preferir el de la partida, caer en el del permiso - if line.customs and line.customs.sector: - sector = line.customs.sector - else: - sector = permission.sector or "" - - ro_line = line.reference.ro_line if line.reference else 0 - - entry = OctavePermitEntry( - invoice_import=invoice.invoice_number or "", - import_line=line.line_number, - part_number=part_number, - octave_permit=octave_permit_code, - ro_line=ro_line or 0, - origin_country=(line.customs.origin_country or "") if line.customs else "", - fraction_type=(line.customs.fraction_type or "") if line.customs else "", - import_fraction=(line.customs.fraction or "") if line.customs else "", - sector=sector, - class_code=class_code, - unit_of_measure="", # resuelto en _revisa_um_regla_octava - quantity=Decimal(0), - value=Decimal(0), - quantity_used=Decimal(0), - value_used=Decimal(0), - ) - - _revisa_um_regla_octava( - db=db, - line=line, - permission=permission, - entry=entry, - company_rfc=company_rfc, - tenant_id=tenant_id, - company_id=company_id, - errors=errors, - ) - - return entry - - -# ───────────────────────────────────────────────────────────────────────────── -# REVPERMISO_REGLA_OCTAVA -# ───────────────────────────────────────────────────────────────────────────── - -@dataclass -class OctaveAvailableEntry: - """ - Cupo disponible de una línea de permiso de Regla Octava. - Paridad: registro en QueuePermReglaOctDis / QRODis (Clarion SCAII). - """ - - octave_permit: str # Permiso - fraction: str # Fraccion - ro_line: int # LineaRO - country_code: str # ClaveM3 (PaisOrigen) - sector: str # Sector (del permiso) - available_quantity: Decimal # CantidadDisponible = CantidadCupo - CantUsada - quantity_used: Decimal # CantUsada (inicia en 0) - available_value: Decimal # ValorDisponible = ValorCupo - ValorUsada - value_used: Decimal # ValorUsado (inicia en 0) - - -def _validate_fraction_ro( - db: Session, - line: LineItem, - permission: OctavePermission, - line_fraction: FractionRuleOctave, - company_rfc: str, - tenant_id: str, - company_id: str, - errors: ErrorCollector, - octave_permit_code: str, -) -> bool: - """ - Valida que la fracción de la partida coincida con la del permiso de RO. - Reutilizada por REVPERMISO_REGLA_OCTAVA. - Retorna True si es válida, False si se añadió un error. - """ - line_fraccion = (line.customs.fraction or "") if line.customs else "" - ro_fraccion = line_fraction.fraction or "" - - use_8_chars = company_rfc.upper() in _RFC_FRACTION_8_CHARS - frac_line = line_fraccion[:8] if use_8_chars else line_fraccion - frac_ro = ro_fraccion[:8] if use_8_chars else ro_fraccion - - if frac_line == frac_ro: - return True - - start_date = permission.start_date - if start_date and hasattr(start_date, "date"): - start_date = start_date.date() - allow_previous = start_date is not None and start_date <= _CLARION_DATE_CUTOFF - - if allow_previous and _previous_fraction_exists( - db, tenant_id, company_id, - previous_fraction=ro_fraccion[:8], - current_fraction=line_fraccion[:8], - ): - return True - - errors.add_error( - field=f"line[{line.line_number}].customs.fraction", - message=f"La fracción: {frac_line} es diferente a la Fraccion: {frac_ro}.", - solution=[f"Agregarla al Número de Permiso de Regla Octava: {octave_permit_code}."], - code="PERMISO_REG_8VA", - ) - return False - - -def revpermiso_regla_octava( - db: Session, - invoice: InvoiceHeader, - line: LineItem, - company_rfc: str, - tenant_id: str, - company_id: str, - errors: ErrorCollector, -) -> Optional[OctaveAvailableEntry]: - """ - Revisión del Permiso de Regla Octava. - Paridad: REVPERMISO_REGLA_OCTAVA (Clarion SCAII). - - Valida: - - Existencia del permiso en el catálogo. - - Coherencia de sector (cuando fracción es PROSEC). - - Rango de fechas del permiso vs fecha de la factura. - - Existencia de la línea/fracción dentro del permiso. - - Concordancia de fracción arancelaria. - - Cupo disponible no agotado. - - Existencia del país de origen dentro de la línea del permiso. - - Retorna un OctaveAvailableEntry si no hay errores (para ser agregado a la - cola QueuePermReglaOctDis del proceso principal), o None en caso contrario. - """ - octave_permit_code = (line.octave_permit or "").strip() - ro_line = line.reference.ro_line if line.reference else 0 - origin_country = (line.customs.origin_country or "") if line.customs else "" - fraction_type = (line.customs.fraction_type or "") if line.customs else "" - line_sector = (line.customs.sector or "") if line.customs else "" - invoice_date = invoice.invoice_date - - has_error = False - - if ro_line: - if not octave_permit_code: - # Tiene LineaRO pero no hay permiso capturado - errors.add_error( - field=f"line[{line.line_number}].octave_permit", - message=( - "No existe capturado un Permiso de Regla Octava " - "y si una Línea de algún permiso de Regla Octava" - ), - solution=[ - "Capturar un Permiso de Regla Octava para la Linea " - "correspondiente del Permiso de Regla Octava." - ], - code="PERMISO_REG_8VA", - ) - return None - - # ── Buscar el permiso ───────────────────────────────────────────── - permission = ( - db.query(OctavePermission) - .filter( - OctavePermission.tenant_id == tenant_id, - OctavePermission.company_id == company_id, - OctavePermission.permission == octave_permit_code, - ) - .first() - ) - - if permission is None: - errors.add_error( - field=f"line[{line.line_number}].octave_permit", - message=( - f"La Partida: {line.line_number} tiene el permiso " - f"{octave_permit_code} de Regla Octava y no esta dado de alta." - ), - solution=[ - f"Dar de alta el permiso de Regla Octava o Borrar " - f"la partida: {line.line_number}." - ], - code="PERMISO_REG_8VA", - ) - return None - - # ── Validar sector cuando la preferencia es PROSEC ──────────────── - if fraction_type == "PROSEC" and line_sector != (permission.sector or ""): - errors.add_error( - field=f"line[{line.line_number}].customs.sector", - message=( - f"El sector: {line_sector} de la partida es diferente al sector: " - f"{permission.sector} del permiso de Regla Octava." - ), - solution=[ - f"Cambiar de sector en la Partida o en el Permiso: {octave_permit_code}." - ], - code="PERMISO_REG_8VA", - ) - has_error = True - - # ── Validar rango de fechas del permiso ─────────────────────────── - perm_start = permission.start_date - perm_end = permission.end_date - if perm_start and hasattr(perm_start, "date"): - perm_start = perm_start.date() - if perm_end and hasattr(perm_end, "date"): - perm_end = perm_end.date() - - inv_date = invoice_date - if inv_date and hasattr(inv_date, "date"): - inv_date = inv_date.date() - - if inv_date and perm_start and perm_end: - if inv_date < perm_start or inv_date > perm_end: - errors.add_error( - field=f"line[{line.line_number}].octave_permit", - message=( - f"La fecha de la factura no corresponde al rango de fechas " - f"del Permiso: {octave_permit_code} de Regla Octava." - ), - solution=[ - "Modificar el Rango de fechas del Permiso de Regla Octava " - "o seleccionar uno que en su rango entre la fecha de la Factura." - ], - code="PERMISO_REG_8VA", - ) - has_error = True - - # ── Buscar línea/fracción dentro del permiso ────────────────────── - line_fraction = ( - db.query(FractionRuleOctave) - .filter( - FractionRuleOctave.tenant_id == tenant_id, - FractionRuleOctave.company_id == company_id, - FractionRuleOctave.permission == octave_permit_code, - FractionRuleOctave.line == ro_line, - ) - .first() - ) - - if line_fraction is None: - errors.add_error( - field=f"line[{line.line_number}].octave_permit", - message=( - f"No existe la Línea: {ro_line} amparada en el permiso " - f"seleccionado {octave_permit_code} de Regla Octava." - ), - solution=["Verificar los Permisos de Regla Octava"], - code="PERMISO_REG_8VA", - ) - return None - - # ── Validar fracción arancelaria ────────────────────────────────── - if not _validate_fraction_ro( - db, line, permission, line_fraction, company_rfc, - tenant_id, company_id, errors, octave_permit_code, - ): - has_error = True - - # ── Validar cupo disponible ─────────────────────────────────────── - quota_qty = line_fraction.quota_quantity or Decimal(0) - qty_used = line_fraction.quantity_used or Decimal(0) - available_qty = quota_qty - qty_used - - if available_qty == 0: - errors.add_error( - field=f"line[{line.line_number}].octave_permit", - message=( - "La Cantidad Cupo ya esta agotada, no se puede importar " - "más para este permiso de Regla Octava." - ), - solution=["Tramitar otro permiso o seleccione otro Permiso."], - code="PERMISO_REG_8VA", - ) - has_error = True - - # ── Validar país de origen dentro de la línea del permiso ───────── - country = ( - db.query(CountryRuleOct) - .filter( - CountryRuleOct.tenant_id == tenant_id, - CountryRuleOct.company_id == company_id, - CountryRuleOct.permission == octave_permit_code, - CountryRuleOct.line == ro_line, - CountryRuleOct.fraction == (line_fraction.fraction or ""), - CountryRuleOct.country_code == origin_country, - ) - .first() - ) - - if country is None: - errors.add_error( - field=f"line[{line.line_number}].customs.origin_country", - message=( - f"No existe la Línea: {ro_line} con país: {origin_country} " - f"amparada en el permiso seleccionado" - ), - solution=[ - f"Agregar el País: {origin_country} al Número de Permiso " - f"de Regla Octava: {octave_permit_code}." - ], - code="PERMISO_REG_8VA", - ) - has_error = True - - if has_error: - return None - - # ── Construir entry de cupo disponible ──────────────────────────── - quota_val = line_fraction.quota_value or Decimal(0) - val_used = line_fraction.value_used or Decimal(0) - - return OctaveAvailableEntry( - octave_permit=octave_permit_code, - fraction=line_fraction.fraction or "", - ro_line=ro_line, - country_code=origin_country, - sector=permission.sector or "", - available_quantity=available_qty, - quantity_used=Decimal(0), - available_value=quota_val - val_used, - value_used=Decimal(0), - ) - - else: - # ro_line == 0: si hay permiso capturado es un error - if octave_permit_code: - errors.add_error( - field=f"line[{line.line_number}].octave_permit", - message=( - f"No existe una Línea capturada para esta partida de importación " - f"y se tiene el Permiso de Regla Octava: {octave_permit_code}." - ), - solution=["Capturar una Linea de Permiso de Regla Octava."], - code="PERMISO_REG_8VA", - ) - - return None - - -# ───────────────────────────────────────────────────────────────────────────── -# VALIDA_IMP_REGLA_OCTAVA -# ───────────────────────────────────────────────────────────────────────────── - -def valida_imp_regla_octava( - db: Session, - desc_list: List[OctavePermitEntry], - dis_dict: dict, - tenant_id: str, - company_id: str, - errors: ErrorCollector, -) -> None: - """ - Valida lo que se quiere importar contra los cupos disponibles de Regla Octava. - Paridad: VALIDA_IMP_REGLA_OCTAVA (Clarion SCAII). - - Fase 1 — Asignación de saldos: - Itera QueuePermReglaOctDesc (qué se quiere importar) contra - QueuePermReglaOctDis (cupo disponible) y distribuye la cantidad/valor - disponible entre las partidas, acumulando lo "usado" en cada lista. - - Fase 2 — Validación final: - - Cantidad == 0: error (sin conversión de U.M.). - - Cantidad – CantUsada != 0: error (cupo insuficiente). - - Parámetros: - desc_list Lista de OctavePermitEntry construida por llena_impo_permiso_regla_octava. - dis_dict Diccionario {(permiso, ro_line, country_code): OctaveAvailableEntry} - construido por revpermiso_regla_octava. - """ - # ── Fase 1: Balancear saldos ────────────────────────────────────────────── - desc_sorted = sorted( - desc_list, - key=lambda e: (e.octave_permit, e.ro_line, e.origin_country), - ) - - for desc in desc_sorted: - key = (desc.octave_permit, desc.ro_line, desc.origin_country) - dis = dis_dict.get(key) - if dis is None: - continue - - # BREAK: la partida ya está completamente consumida - if ( - desc.quantity - desc.quantity_used == 0 - or desc.value - desc.value_used == 0 - ): - continue - - # CYCLE: el cupo disponible ya está agotado - if ( - dis.available_quantity - dis.quantity_used == 0 - or dis.available_value - dis.value_used == 0 - ): - continue - - # ── Cantidad ────────────────────────────────────────────────────── - qty_needed = desc.quantity - desc.quantity_used - qty_avail = dis.available_quantity - dis.quantity_used - - if qty_needed <= qty_avail: - desc.quantity_used += qty_needed - dis.quantity_used += qty_needed - else: - desc.quantity_used += qty_avail - dis.quantity_used += qty_avail - - # ── Valor ───────────────────────────────────────────────────────── - val_needed = desc.value - desc.value_used - val_avail = dis.available_value - dis.value_used - - if val_needed <= val_avail: - # Asignación directa al total (no acumulativa) — paridad Clarion - desc.value_used = desc.value - dis.value_used += val_needed - else: - # Asignación directa al disponible — paridad Clarion - desc.value_used = dis.available_value - dis.value_used += val_avail - - # ── Fase 2: Validación final ────────────────────────────────────────────── - for desc in desc_list: - line_fraction = ( - db.query(FractionRuleOctave) - .filter( - FractionRuleOctave.tenant_id == tenant_id, - FractionRuleOctave.company_id == company_id, - FractionRuleOctave.permission == desc.octave_permit, - FractionRuleOctave.line == desc.ro_line, - ) - .first() - ) - # line_fraction reservado para validación de costo unitario (comentada en Clarion) - _ = line_fraction - - if desc.quantity == 0: - errors.add_error( - field=f"line[{desc.import_line}].octave_permit", - message=( - "La Cantidad a Descargar del Permiso de Regla Octava es Cero " - "debido a que No hay conversión entre la U.M. de la Partida " - "vs la del Permiso" - ), - solution=[ - "Revisar las U.M. de la Partida de Importación " - "y del Permiso de Regla Octava." - ], - code="PERMISO_REG_8VA", - ) - elif desc.quantity - desc.quantity_used != 0: - errors.add_error( - field=f"line[{desc.import_line}].octave_permit", - message=( - "No se puede importar más de este material " - "bajo el amparo de Regla Octava." - ), - solution=[ - "Revisar la cantidad a Importar de la línea " - "o solicitar otro permiso." - ], - code="PERMISO_REG_8VA", - ) - - -# ───────────────────────────────────────────────────────────────────────────── -# LLENASALDOS_REGLA_OCTAVA -# ───────────────────────────────────────────────────────────────────────────── - -def _llena_fraccion_regla_oct(sector: str) -> str: - """ - Devuelve la fracción arancelaria de Regla Octava según el sector. - Paridad: LLENA_FRACCION_REGLA_OCT (Clarion SCAII). - - El orden de comparación es deliberado: los prefijos más largos (XIX, XXIII) - se evalúan antes que los más cortos (XX, II) para evitar falsos positivos, - exactamente como advierte el comentario original del Clarion. - """ - s = (sector or "").strip() - - if s == "I": return "98020001" - if s[:2] == "II": return "98020002" - if s == "III": return "98020003" - if s == "IV": return "98020004" - if s == "V": return "98020005" - if s == "VI": return "98020006" - if s == "VII": return "98020007" - if s == "VIII": return "98020008" - if s == "IX": return "98020009" - if s == "X": return "98020010" - if s == "XI": return "98020011" - if s == "XII": return "98020012" - if s == "XIII": return "98020013" - if s == "XIV": return "98020014" - if s == "XV": return "98020015" - if s == "XVI": return "98020016" - if s == "XVII": return "98020017" - if s == "XVIII": return "98020018" - if s[:3] == "XIX": return "98020019" # antes de XX - if s == "XXI": return "98020021" - if s == "XXII": return "98020022" - if s[:5] == "XXIII": return "98020023" # antes de XX - if s == "XXIV": return "98020024" - if s == "XXV": return "98020025" - if s[:2] == "XX": return "98020020" # al último — paridad comentario Clarion - return "" - - -def llenasaldos_regla_octava( - db: Session, - entry: "OctavePermitEntry", - unit_cost_me: Decimal, - tenant_id: str, - company_id: str, - sql_errors: list, - consecutive_ref: list, -) -> None: - """ - Llena / actualiza el saldo de Regla Octava en SSaldosReglaOctava. - Paridad: LLENASALDOS_REGLA_OCTAVA (Clarion SCAII). - - - Si el registro no existe (ERRORCODE = 35 en Clarion → no encontrado): - inserta uno nuevo con CantExitencia = quantity_used y - ValorME = quantity_used * unit_cost_me. - - Si ya existe: acumula CantExitencia y ValorME. - - consecutive_ref es una lista de un elemento [int] que actúa como - contador mutable compartido con descuenta_cupo_r_octava. - """ - fraction = _llena_fraccion_regla_oct(entry.sector) - valor_me = entry.quantity_used * unit_cost_me - - existing = ( - db.query(OctaveBalance) - .filter( - OctaveBalance.tenant_id == tenant_id, - OctaveBalance.company_id == company_id, - OctaveBalance.invoice_import == entry.invoice_import, - OctaveBalance.part_number == entry.part_number, - OctaveBalance.origin_country == entry.origin_country, - OctaveBalance.fraction_type == entry.fraction_type, - OctaveBalance.sector == entry.sector, - OctaveBalance.octave_permit == entry.octave_permit, - OctaveBalance.origin == "TEM", - OctaveBalance.system == "SCAF", - OctaveBalance.line == entry.ro_line, - ) - .first() - ) - - try: - if existing is None: - new_balance = OctaveBalance( - tenant_id=tenant_id, - company_id=company_id, - invoice_import=entry.invoice_import, - part_number=entry.part_number, - origin_country=entry.origin_country, - fraction_type=entry.fraction_type, - sector=entry.sector, - octave_permit=entry.octave_permit, - origin="TEM", - system="SCAF", - line=entry.ro_line, - ro_fraction=fraction, - class_code=entry.class_code, - import_fraction=entry.import_fraction, - unit_of_measure=entry.unit_of_measure, - quantity_stock=entry.quantity_used, - value_me=valor_me, - ) - db.add(new_balance) - db.flush([new_balance]) - else: - existing.quantity_stock = (existing.quantity_stock or Decimal(0)) + entry.quantity_used - existing.value_me = (existing.value_me or Decimal(0)) + valor_me - db.flush([existing]) - except Exception as exc: - consecutive_ref[0] += 1 - action = "Agregar" if existing is None else "Actualizar" - sql_errors.append({ - "consecutive": consecutive_ref[0], - "error": ( - f"Error al {action} en (SSaldosReglaOctava) {exc}" - ), - }) - - -# ───────────────────────────────────────────────────────────────────────────── -# DESCUENTA_CUPO_R_OCTAVA -# ───────────────────────────────────────────────────────────────────────────── - -def descuenta_cupo_r_octava( - db: Session, - desc_list: List[OctavePermitEntry], - tenant_id: str, - company_id: str, - sql_errors: list, -) -> None: - """ - Descuenta los cupos de la Regla Octava en GFracROctava. - Paridad: DESCUENTA_CUPO_R_OCTAVA (Clarion SCAII). - - Por cada entrada en desc_list que tenga cantidad y valor usados distintos - de cero: - 1. Llama a llenasaldos_regla_octava para registrar/actualizar SSaldosReglaOctava. - 2. Actualiza FractionRuleOctave acumulando: - - quantity_used += entry.quantity_used - - value_used += entry.quantity_used * unit_cost_me - - Los errores de actualización se acumulan en sql_errors como dicts con - las claves 'consecutive' y 'error'. - """ - consecutive_ref = [0] - - for entry in desc_list: - if entry.quantity_used == 0 or entry.value_used == 0: - continue - - fra_oct = ( - db.query(FractionRuleOctave) - .filter( - FractionRuleOctave.tenant_id == tenant_id, - FractionRuleOctave.company_id == company_id, - FractionRuleOctave.permission == entry.octave_permit, - FractionRuleOctave.line == entry.ro_line, - ) - .first() - ) - - if fra_oct is None: - continue - - unit_cost_me = fra_oct.unit_cost_me or Decimal(0) - - llenasaldos_regla_octava( - db=db, - entry=entry, - unit_cost_me=unit_cost_me, - tenant_id=tenant_id, - company_id=company_id, - sql_errors=sql_errors, - consecutive_ref=consecutive_ref, - ) - - fra_oct.quantity_used = (fra_oct.quantity_used or Decimal(0)) + entry.quantity_used - fra_oct.value_used = (fra_oct.value_used or Decimal(0)) + entry.quantity_used * unit_cost_me - - try: - db.flush([fra_oct]) - except Exception as exc: - consecutive_ref[0] += 1 - sql_errors.append({ - "consecutive": consecutive_ref[0], - "error": ( - f"Error al regresar el Cupo en (Permiso de Regla Octava) {exc}" - ), - }) diff --git a/backend/api/v1/modules/a76/invoices/imports/process/sub_process/review_series.py b/backend/api/v1/modules/a76/invoices/imports/process/sub_process/review_series.py deleted file mode 100644 index 2746de19..00000000 --- a/backend/api/v1/modules/a76/invoices/imports/process/sub_process/review_series.py +++ /dev/null @@ -1,102 +0,0 @@ -from typing import List - -from sqlalchemy.orm import Session - -from api.v1.modules.a76.general_catalogs.company.models import Company -from api.v1.modules.a76.general_catalogs.units_of_measure.models import UnitOfMeasure -from api.v1.modules.a76.items.models import LineItem -from api.v1.modules.a76.items.series.models import Serie -from api.v1.modules.a76.app_settings.service import AppSettingsService -from core.exceptions import ErrorCollector - -# RFCs donde la validación de cantidad vs series únicamente aplica a PZA (paridad Clarion) -_RFC_EXCEPCION_PZA = { - "IMS030409FZ0", - "TOP140430PB6", - "AMA7504258K2", - "BZG111091T9", -} - - -def review_series( - db: Session, - line: LineItem, - company_rfc: str, - tenant_id: str, - company_id: str, - errors: ErrorCollector, -) -> None: - """ - Revisa la cantidad de series vs la cantidad a importar, por partida. - - Paridad: REVISA_CANT_SERIES (Clarion SCAII). - - Reglas: - - Si la partida tiene LlevaSerie / has_serial activado: - 1. Si no existen registros de series → error SERIES_VACIAS. - 2. Si SisGen:CantvsCantSeries = 1: - - Para RFC excepcionales: solo valida la coincidencia si UnidadMedida = 'PZA'. - - Para el resto: valida siempre que count(series) != cantidad. - """ - # Resolver RFC de la empresa una sola vez - - - # Solo aplica a partidas que llevan serie - if not (line.description and line.description.has_serial): - return - - series_count = ( - db.query(Serie) - .filter(Serie.line_item_id == line.id) - .count() - ) - - if series_count == 0: - errors.add_error( - field=f"line[{line.line_number}].series", - message="La opción de contiene series esta activada y no existen registros de Series.", - solution=[ - "Desactivar la opción de Lleva series o registrar las series a esta partida." - ], - code="SERIES_VACIAS", - ) - return - - # GNiv:CantSerievsCant = 0 → el bloque series > cantidad estaba comentado en Clarion original - settings = AppSettingsService.get_resolved_settings(db, int(tenant_id), int(company_id)) - q_gen = settings.get("qsisgen", {}) - s_gen = settings.get("ssisgen", {}) - - # Switch maestro: validarseries = 0 desactiva toda la validación de series (SSisGen/QSisGen) - valida_series_global = int(q_gen.get("validarseries") or s_gen.get("validarseries", 0)) - if valida_series_global != 1: - return - - valida_cant_series = int(q_gen.get("cantvscantseries") or s_gen.get("cantvscantseries", 0)) - - if valida_cant_series != 1: - return - - qty = line.quantity.quantity if line.quantity else None - if qty is None: - return - - # Determinar si se debe validar series - validate_series = ( - series_count != qty and - (company_rfc not in _RFC_EXCEPCION_PZA or line.unit_of_measure == "PZA") - ) - - if validate_series: - errors.add_error( - field=f"line[{line.line_number}].series", - message="La Cantidad de Series No Coincide con la Cantidad de la Partida.", - solution=[f"Nivelar las series de la Partida {line.line_number}."], - code="SERIES_VS_CANT", - ) - - - - - - \ No newline at end of file diff --git a/backend/api/v1/modules/a76/invoices/imports/process/sub_process/review_weights.py b/backend/api/v1/modules/a76/invoices/imports/process/sub_process/review_weights.py deleted file mode 100644 index b757405e..00000000 --- a/backend/api/v1/modules/a76/invoices/imports/process/sub_process/review_weights.py +++ /dev/null @@ -1,102 +0,0 @@ -from typing import List - -from sqlalchemy.orm import Session - -from api.v1.modules.a76.general_catalogs.units_of_measure.models import UnitOfMeasure -from api.v1.modules.a76.items.line_quantities.models import LineQuantity -from api.v1.modules.a76.items.models import LineItem -from core.exceptions import ErrorCollector - - -def review_weights_kgs( - db: Session, - lines: List[LineItem], - tenant_id: str, - company_id: str, - errors: ErrorCollector, -) -> None: - """ - Validates that, for lines whose unit of measure is KGS, the net weight - equals the import quantity. - - Ported from legacy REVISA_CANT_vs_PESONETO_KGS routine. - """ - # Resolve the id of the KGS unit of measure for this tenant/company - kgs_uom = ( - db.query(UnitOfMeasure) - .filter( - UnitOfMeasure.code == "KGS", - UnitOfMeasure.tenant_id == tenant_id, - UnitOfMeasure.company_id == company_id, - ) - .first() - ) - if kgs_uom is None: - # No KGS unit defined for this tenant — nothing to validate - return - - for line in lines: - if line.unit_of_measure != kgs_uom.id: - continue - if line.quantity is None: - continue - - net_weight = line.quantity.net_weight - qty = line.quantity.quantity - - if net_weight is None or qty is None or net_weight != qty: - errors.add_error( - field=f"line[{line.line_number}].net_weight", - message=( - f"Existe diferencia entre la cantidad y el Peso Neto, " - f"la cantidad es de {qty} KGS y el Peso Neto es de {net_weight} KGS." - ), - solution=["Igualar el Peso Neto con la cantidad a importar."], - code="PESO_NETO_KGS", - ) - - -def review_weights_lbs( - db: Session, - lines: List[LineItem], - tenant_id: str, - company_id: str, - errors: ErrorCollector, -) -> None: - """ - Validates that, for lines whose unit of measure is LBS, the net weight - equals the import quantity. - - Ported from legacy REVISA_CANT_vs_PESONETO_LBS routine. - """ - lbs_uom = ( - db.query(UnitOfMeasure) - .filter( - UnitOfMeasure.code == "LBS", - UnitOfMeasure.tenant_id == tenant_id, - UnitOfMeasure.company_id == company_id, - ) - .first() - ) - if lbs_uom is None: - return - - for line in lines: - if line.unit_of_measure != lbs_uom.id: - continue - if line.quantity is None: - continue - - net_weight = line.quantity.net_weight - qty = line.quantity.quantity - - if net_weight is None or qty is None or net_weight != qty: - errors.add_error( - field=f"line[{line.line_number}].net_weight", - message=( - f"Existe diferencia entre la cantidad y el Peso Neto, " - f"la cantidad es de {qty} LBS y el Peso Neto es de {net_weight} LBS." - ), - solution=["Igualar el Peso Neto con la cantidad a importar."], - code="PESO_NETO_LBS", - ) diff --git a/backend/api/v1/modules/a76/invoices/imports/process/task.py b/backend/api/v1/modules/a76/invoices/imports/process/task.py deleted file mode 100644 index 2e7f56aa..00000000 --- a/backend/api/v1/modules/a76/invoices/imports/process/task.py +++ /dev/null @@ -1,70 +0,0 @@ -import logging - -from celery import Task - -from core.celery_app import celery_app -from core.database import scoped_core_db -from core.exceptions import ValidationException - -from api.v1.modules.a76.invoices.models import InvoiceHeader, InvoiceStatus -from .main_process import main_process - -logger = logging.getLogger(__name__) - - -def _progress(task: Task, current: int, status: str) -> None: - task.update_state(state="PROGRESS", meta={"current": current, "status": status}) - - -@celery_app.task(bind=True, name="process_invoice_task") -def process_invoice_task(self: Task, invoice_id: int, tenant_id: str, company_id: str, username: str = "SYSTEM") -> dict: - """ - Procesa una factura de importación ejecutando todas las validaciones y - actualizaciones del proceso principal (main_process) con reporte de progreso. - """ - with scoped_core_db(tenant_id=int(tenant_id), company_id=int(company_id)) as db: - try: - _progress(self, 5, "Cargando factura...") - invoice: InvoiceHeader | None = db.get(InvoiceHeader, invoice_id) - - if invoice is None: - return { - "status": "error", - "message": f"Factura con id {invoice_id} no encontrada.", - "errors": [], - } - - _progress(self, 10, "Verificando estatus de seguridad...") - if invoice.status == InvoiceStatus.PROCESSED: - return { - "status": "error", - "message": f"La factura {invoice.invoice_number} ya se encuentra procesada.", - "errors": [{"field": "status", "message": "Factura ya procesada."}], - } - - _progress(self, 20, "Iniciando procesamiento de factura...") - result = main_process( - db=db, - invoice=invoice, - tenant_id=tenant_id, - company_id=company_id, - username=username, - ) - - _progress(self, 95, "Confirmando cambios...") - db.commit() - - _progress(self, 100, "Proceso completado.") - return result - - except ValidationException as exc: - db.rollback() - return { - "status": "validation_error", - "message": exc.message, - "errors": exc.errors, - } - except Exception as exc: - db.rollback() - logger.error(f"Error en process_invoice_task: {str(exc)}", exc_info=True) - raise exc diff --git a/backend/api/v1/modules/a76/invoices/imports/revert/main_process.py b/backend/api/v1/modules/a76/invoices/imports/revert/main_process.py deleted file mode 100644 index fd10f2d5..00000000 --- a/backend/api/v1/modules/a76/invoices/imports/revert/main_process.py +++ /dev/null @@ -1,229 +0,0 @@ -from api.v1.modules.a76.app_settings.service import AppSettingsService -from decimal import Decimal -from typing import List - -from sqlalchemy.orm import Session - -from api.v1.modules.a24.discharges.models import DischargeDetail, DischargeHeader, DischargeStatus -from api.v1.modules.a76.invoices.models import InvoiceHeader, InvoiceStatus -from api.v1.modules.a76.items.models import LineItem -from core.exceptions import ErrorCollector - -from .sub_process.review_rule_octave import borra_saldos_regla_octava -from ..balance.void_balance_entries import void_balance_entries - - -# ───────────────────────────────────────────────────────────────────────────── -# Validación de cantidades retornadas con detalle de exportaciones activas -# ───────────────────────────────────────────────────────────────────────────── - -def _validate_returned_quantities( - db: Session, - invoice: InvoiceHeader, - lines: List[LineItem], - errors: ErrorCollector, cancelled_by: str = "SYSTEM", -) -> None: - """ - Verifica que ninguna partida tenga saldos pendientes por exportaciones - activas que la afecten. - Paridad: bloque 'REVISA CANTIDADES RETORNADAS' (Clarion SCAII). - - El Clarion recorre las partidas con (CantRetornadaTemp + CantRetornada + - CantExistencia) <> 0 y luego busca en QEqeMaq (exportaciones definitivas), - QEqeMaqRep (exportaciones de reparación) y QEqiMaqRep (importaciones de - reparación) para identificar qué factura de exportación activa (Estatus='AC') - tiene esa partida descargada. - - En Python los mismos vínculos viven en: - DischargeDetail.import_item_line_id → la partida de importación consumida - DischargeDetail.header → DischargeHeader - DischargeHeader.source_invoice_id → InvoiceHeader (la factura de exportación) - InvoiceHeader.status → InvoiceStatus.PROCESSED (≡ Estatus='AC') - - Para cada partida con saldo se buscan DischargeDetail con status APPLIED en - una factura de exportación procesada y se reporta qué factura debe - desactualizarse primero. - """ - # Importante: - # Con la migración a tablas especializadas (discharge_* + balance_movement), - # los campos legacy de cantidades retornadas pueden quedar desfasados. - # Para bloquear una des-actualización solo debe considerarse descarga ACTIVA - # real (DischargeHeader.status=APPLIED y factura fuente procesada). - for line in lines: - - # Buscar DischargeDetail vinculados a esta partida de importación - # cuya factura de exportación esté activa (PROCESSED). - # Paridad: bucle sobre QEqeMaq/QEqeMaqRep donde Descarga=1 y - # encabezado de exportación con Estatus='AC'. - details: List[DischargeDetail] = ( - db.query(DischargeDetail) - .join(DischargeDetail.header) - .filter( - DischargeDetail.import_item_line_id == line.id, - DischargeHeader.status == DischargeStatus.APPLIED, - ) - .all() - ) - - active_details = [ - d for d in details - if d.header - and d.header.source_invoice is not None - and d.header.source_invoice.status == InvoiceStatus.PROCESSED - ] - - if active_details: - # Reportar un error por cada factura de exportación activa distinta - # (equivale a QueErr en el Clarion). - seen_export_invoices: set = set() - for detail in active_details: - src_invoice = detail.header.source_invoice - src_number = src_invoice.invoice_number or str(src_invoice.id) - - if src_number in seen_export_invoices: - continue - seen_export_invoices.add(src_number) - - export_line_number = ( - detail.export_line.line_number if detail.export_line else "?" - ) - - errors.add_error( - field=f"line[{line.line_number}].discharge", - message=( - f"La Línea: {line.line_number} se ha descargado " - f"{detail.quantity_discharged} en la factura: {src_number} " - f"de exportación con Línea: {export_line_number}." - ), - solution=[ - f"Desactualizar la factura: {src_number} " - "para regresar saldos a la partida." - ], - code="LINE_HAS_ACTIVE_DISCHARGE", - ) - # Si no hay discharge activo, NO bloquear por contadores legacy. - - -# ───────────────────────────────────────────────────────────────────────────── -# Reset de la factura e inventario -# ───────────────────────────────────────────────────────────────────────────── - -def _reset_invoice_financials(invoice: InvoiceHeader) -> None: - """ - Reinicia los totales financieros del encabezado de la factura a cero y - cambia el estatus a PENDING (equivalente a Estatus='NA' en Clarion). - Paridad: UPDATE QFacImp SET CantImpo=0, PesoNeto=0, PesoBruto=0, - Cantbultos=0, ValorImpoMN=0, ValorImpoME=0, ValorImpoMC=0, - ValorAduanasMN=0, ValorAduanasME=0, Estatus='NA', - ComofueProcesada='', ValorIVAMN=0, ValorIVAME=0 (Clarion SCAII). - """ - fin = invoice.financials - if fin is None: - return - - fin.total_quantity = 0.0 - fin.net_weight = 0.0 - fin.gross_weight = 0.0 - fin.total_packages = 0 - fin.value_mn = 0.0 - fin.value_me = 0.0 - fin.value_mc = 0.0 - fin.customs_value_mn = 0.0 - fin.customs_value_me = 0.0 - fin.iva_mn = 0.0 - fin.iva_me = 0.0 - fin.iva_mc = 0.0 - - invoice.status = InvoiceStatus.PENDING - invoice.process_method = None - - -def _reset_line_quantities(lines: List[LineItem]) -> None: - """ - Reinicia los contadores de inventario de cada partida a cero. - Paridad: UPDATE QEqiMaq SET CantRetornada=0, CantRetornadaTemp=0, - ValorRetornadoMN=0, ValorRetornadoME=0, CantExistencia=0, - ValorIVAMNUsado=0, ValorIVAMEUsado=0 (Clarion SCAII). - """ - for line in lines: - if line.financial is not None: - line.financial.value_returned_mxn = Decimal(0) - line.financial.value_returned_usd = Decimal(0) - line.financial.vat_used_mxn = Decimal(0) - line.financial.vat_used_usd = Decimal(0) - - -# ───────────────────────────────────────────────────────────────────────────── -# Proceso principal de reversión -# ───────────────────────────────────────────────────────────────────────────── - -def revert_process( - db: Session, - invoice: InvoiceHeader, - lines: List[LineItem], - tenant_id: str, - company_id: str, - errors: ErrorCollector, cancelled_by: str = "SYSTEM", -) -> list: - """ - Proceso principal de des-actualización de una factura de importación - temporal. - Paridad: rutina principal 'DESACTUALIZAR FACTURA IMPO TEMP' (Clarion SCAII). - - Flujo: - 1. Verifica que ninguna partida tenga saldos activos por exportaciones - procesadas (QueueErrorAct en Clarion). Si los hay → ValidationException. - 2. Si no hay errores de validación: - a. Do BORRASALDOS_REGLA_OCTAVA — revierte cupos de Regla Octava. - b. UPDATE QFacImp — reinicia totales del encabezado (status → PENDING). - c. UPDATE QEqiMaq — reinicia contadores de inventario por partida. - 3. Retorna sql_errors (errores no-bloqueantes de BD, equivalente a - QueueErrorSQL en Clarion). - - Raises: - ValidationException: si hay partidas con descargas activas - (equivale a Records(QueueErrorAct) <> 0). - """ - # ── Paso 1: REVISA CANTIDADES RETORNADAS ────────────────────────────────── - _validate_returned_quantities(db, invoice, lines, errors) - errors.raise_if_errors() - - # ── Paso 2a: Do BORRASALDOS_REGLA_OCTAVA ───────────────────────────────── - sql_errors: list = [] - borra_saldos_regla_octava( - db=db, - invoice_import=invoice.invoice_number or "", - tenant_id=tenant_id, - company_id=company_id, - sql_errors=sql_errors, - ) - - # ── Paso 2b: UPDATE QFacImp ─────────────────────────────────────────────── - _reset_invoice_financials(invoice) - - # ── Paso 2c: UPDATE QEqiMaq ─────────────────────────────────────────────── - _reset_line_quantities(lines) - - # ── Paso 2d: Anular saldos en a24.balance_movement ─────────────────────── - # Inserta ENTRY_VOID por cada ENTRY abierto de esta factura, dejando el - # balance neto en 0 para que las descargas de exportación no puedan - # consumir esos lotes. El guard interno confirma que no haya consumos - # activos (ya validado arriba, pero se mantiene como doble seguro). - void_balance_entries(db, invoice) - - - # Auditoría de Desactualización - settings = AppSettingsService.get_resolved_settings(db, int(tenant_id), int(company_id)) - q_gen = settings.get("qsisgen", {}) - s_gen = settings.get("ssisgen", {}) - act_seguridad = int(q_gen.get("actseguridad") or s_gen.get("actseguridad") or q_gen.get("ActSeguridad") or s_gen.get("ActSeguridad", 0)) - - if act_seguridad == 1: - from api.v1.modules.a76.audit_log.services.service import AuditService - AuditService.create_audit_log( - db=db, reference=invoice.invoice_number, procedure="ANULAR FACTURA", movement="DESACTUALIZACION", - username=cancelled_by or "SYSTEM", tenant_id=int(tenant_id), company_id=int(company_id), - table_name="invoice_header", record_id=str(invoice.id), operation_type="UPDATE" - ) - - return sql_errors diff --git a/backend/api/v1/modules/a76/invoices/imports/revert/pre_validators.py b/backend/api/v1/modules/a76/invoices/imports/revert/pre_validators.py deleted file mode 100644 index cc9b6f8e..00000000 --- a/backend/api/v1/modules/a76/invoices/imports/revert/pre_validators.py +++ /dev/null @@ -1,42 +0,0 @@ -from typing import List - -from sqlalchemy.orm import Session - -from api.v1.modules.a76.invoices.models import InvoiceHeader, InvoiceStatus -from api.v1.modules.a76.items.models import LineItem -from core.exceptions import ErrorCollector - - -def pre_validators( - db: Session, - invoice: InvoiceHeader, - tenant_id: str, - company_id: str, - errors: ErrorCollector, -) -> List[LineItem]: - """ - Validaciones previas a la reversión de una factura de importación temporal. - - - Verifica que la factura esté en estatus PROCESSED. - - Carga y retorna las partidas asociadas a la factura. - """ - if invoice.status != InvoiceStatus.PROCESSED: - errors.add_error( - "status", - "La factura no fue procesada y no puede ser revertida", - solution=["Verifique el estatus de la factura antes de intentar deshacer el proceso"], - code="NOT_PROCESSED", - value=invoice.status, - ) - - lines: List[LineItem] = ( - db.query(LineItem) - .filter( - LineItem.invoice_id == invoice.id, - LineItem.tenant_id == tenant_id, - LineItem.company_id == company_id, - ) - .all() - ) - - return lines diff --git a/backend/api/v1/modules/a76/invoices/imports/revert/routes.py b/backend/api/v1/modules/a76/invoices/imports/revert/routes.py deleted file mode 100644 index 5531b1f6..00000000 --- a/backend/api/v1/modules/a76/invoices/imports/revert/routes.py +++ /dev/null @@ -1,116 +0,0 @@ -from typing import Any, Dict - -from fastapi import APIRouter, Depends, HTTPException, Query -from sqlalchemy.orm import Session - -from core.celery_app import celery_app -from core.database import get_core_db -from core.security import get_current_user, validate_access_to_resource -from api.v1.modules.core.tasks_tracking import track_and_dispatch - -from api.v1.modules.a76.invoices.models import InvoiceHeader, OperationType -from api.v1.modules.a76.invoices.permission_map import get_invoice_permission_base -from .task import revert_invoice_task as revert_import_invoice_task -from ...exports.revert.task import revert_invoice_task as revert_export_invoice_task - -router = APIRouter() - - -@router.post("/invoices/{invoice_id}/revert") -def trigger_invoice_revert( - invoice_id: int, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """ - Endpoint unificado para des-actualizar facturas. - - operation_type=imp -> task de importación - - operation_type=exp -> task de exportación - """ - tenant_id = validate_access_to_resource(db, company_id, current_user) - cancelled_by = ( - current_user.get("username") - or current_user.get("user_name") - or current_user.get("preferred_username") - or current_user.get("email") - or "system" - ) - - invoice: InvoiceHeader | None = db.get(InvoiceHeader, invoice_id) - if invoice is None: - raise HTTPException(status_code=404, detail=f"Factura {invoice_id} no encontrada.") - - # 🛡️ Validación de permisos granulares: des-actualizar requiere el mismo permiso que procesar - perm_base = get_invoice_permission_base(invoice.operation_type, invoice.invoice_type) - validate_access_to_resource(db, company_id, current_user, required_permissions=[f"{perm_base}.process"]) - - if invoice.operation_type == OperationType.EXP: - task = track_and_dispatch( - db=db, - task=revert_export_invoice_task, - tenant_id=tenant_id, - company_id=company_id, - requested_by_user=str(cancelled_by), - task_name="revert_export_invoice_task", - task_group="invoices", - task_origin="a76/invoices/exports/revert", - args=[invoice_id, str(tenant_id), str(company_id), str(cancelled_by)], - ) - else: - task = track_and_dispatch( - db=db, - task=revert_import_invoice_task, - tenant_id=tenant_id, - company_id=company_id, - requested_by_user=str(cancelled_by), - task_name="revert_invoice_task", - task_group="invoices", - task_origin="a76/invoices/imports/revert", - args=[invoice_id, str(tenant_id), str(company_id), str(cancelled_by)], - ) - - return {"task_id": task.id} - - -@router.get("/invoices/revert/{task_id}/status") -def get_invoice_revert_status(task_id: str): - """ - Consulta el estado de progreso de una tarea de des-actualización de - factura. - - Retorna: - - state: 'PROCESSING' | 'SUCCESS' | 'FAILURE' - - info: { current: int, status: str } (cuando state == 'PROCESSING') - - result: dict (cuando state == 'SUCCESS' o 'FAILURE') - """ - task_result = celery_app.AsyncResult(task_id) - - if task_result.state in ("PENDING", "STARTED"): - return { - "state": "PROCESSING", - "info": {"current": 0, "status": "Iniciando..."}, - } - - if task_result.state == "PROGRESS": - return { - "state": "PROCESSING", - "info": task_result.info or {"current": 0, "status": "Procesando..."}, - } - - if task_result.state == "SUCCESS": - return { - "state": "SUCCESS", - "result": task_result.result, - } - - error_info = task_result.result - if isinstance(error_info, Exception): - error_msg = str(error_info) - else: - error_msg = str(error_info) if error_info else "Error desconocido" - - return { - "state": "FAILURE", - "result": error_msg, - } diff --git a/backend/api/v1/modules/a76/invoices/imports/revert/sub_process/review_rule_octave.py b/backend/api/v1/modules/a76/invoices/imports/revert/sub_process/review_rule_octave.py deleted file mode 100644 index 94805475..00000000 --- a/backend/api/v1/modules/a76/invoices/imports/revert/sub_process/review_rule_octave.py +++ /dev/null @@ -1,94 +0,0 @@ -from decimal import Decimal -from typing import List - -from sqlalchemy.orm import Session - -from api.v1.modules.a76.rule_octave.balances.models import OctaveBalance -from api.v1.modules.a76.rule_octave.fractions.models import FractionRuleOctave - - -def borra_saldos_regla_octava( - db: Session, - invoice_import: str, - tenant_id: str, - company_id: str, - sql_errors: list, -) -> None: - """ - Revierte los saldos de Regla Octava registrados al procesar una factura de - importación temporal. - Paridad: BORRASALDOS_REGLA_OCTAVA (Clarion SCAII). - - Por cada registro en SSaldosReglaOctava (OctaveBalance) con origin='TEM' y - system='SCAF' que corresponda a la factura: - 1. Resta de vuelta la cantidad y el valor en GFracROctava (FractionRuleOctave). - 2. Elimina el registro de OctaveBalance. - - Los errores de actualización se acumulan en sql_errors como dicts con las - claves 'consecutive' y 'error'. - """ - consecutive_ref = [0] - - balances: List[OctaveBalance] = ( - db.query(OctaveBalance) - .filter( - OctaveBalance.tenant_id == tenant_id, - OctaveBalance.company_id == company_id, - OctaveBalance.invoice_import == invoice_import, - OctaveBalance.origin == "TEM", - OctaveBalance.system == "SCAF", - ) - .all() - ) - - for balance in balances: - fra_oct = ( - db.query(FractionRuleOctave) - .filter( - FractionRuleOctave.tenant_id == tenant_id, - FractionRuleOctave.company_id == company_id, - FractionRuleOctave.permission == balance.octave_permit, - FractionRuleOctave.line == balance.line, - ) - .first() - ) - - # REGRESAR EL SALDO AL PERMISO DE REGLA OCTAVA - # Si el permiso no existe (TryFetch falla en Clarion), se omite el PUT - # pero el DELETE del saldo se ejecuta de todas formas — paridad Clarion. - if fra_oct is not None: - qty_back = balance.quantity_stock or Decimal(0) - val_back = balance.value_me or Decimal(0) - - fra_oct.quantity_used = max( - Decimal(0), - (fra_oct.quantity_used or Decimal(0)) - qty_back, - ) - fra_oct.value_used = max( - Decimal(0), - (fra_oct.value_used or Decimal(0)) - val_back, - ) - - try: - db.flush([fra_oct]) - except Exception as exc: - consecutive_ref[0] += 1 - sql_errors.append({ - "consecutive": consecutive_ref[0], - "error": ( - f"Error al regresar el Cupo en (Permiso de Regla Octava) {exc}" - ), - }) - - # DELETE(SSaldosReglaOctava) — siempre se intenta, igual que en Clarion - try: - db.delete(balance) - db.flush([balance]) - except Exception as exc: - consecutive_ref[0] += 1 - sql_errors.append({ - "consecutive": consecutive_ref[0], - "error": ( - f"Error al Eliminar en (SSaldosReglaOctava) {exc}" - ), - }) diff --git a/backend/api/v1/modules/a76/invoices/imports/revert/task.py b/backend/api/v1/modules/a76/invoices/imports/revert/task.py deleted file mode 100644 index c6bc4d69..00000000 --- a/backend/api/v1/modules/a76/invoices/imports/revert/task.py +++ /dev/null @@ -1,91 +0,0 @@ -from celery import Task - -from core.celery_app import celery_app -from core.database import scoped_core_db -from core.exceptions import ErrorCollector, ValidationException - -from api.v1.modules.a76.invoices.models import InvoiceHeader -from .pre_validators import pre_validators -from .main_process import revert_process - - -def _progress(task: Task, current: int, status: str) -> None: - task.update_state(state="PROGRESS", meta={"current": current, "status": status}) - - -@celery_app.task(bind=True, name="revert_import_invoice_task") -def revert_invoice_task( - self: Task, - invoice_id: int, - tenant_id: str, - company_id: str, - cancelled_by: str | None = None, -) -> dict: - """ - Des-actualiza una factura de importación temporal ejecutando todas las - validaciones y reversiones del proceso principal (revert/main_process) con - reporte de progreso. - """ - with scoped_core_db(tenant_id=int(tenant_id), company_id=int(company_id)) as db: - try: - _progress(self, 5, "Cargando factura...") - invoice: InvoiceHeader | None = db.get(InvoiceHeader, invoice_id) - if invoice is None: - return { - "status": "error", - "message": f"Factura con id {invoice_id} no encontrada.", - "errors": [], - } - - _progress(self, 10, "Verificando estatus de seguridad...") - from api.v1.modules.a76.invoices.models import InvoiceStatus - if invoice.status != InvoiceStatus.PROCESSED: - return { - "status": "error", - "message": f"La factura {invoice.invoice_number} no se puede revertir porque no está procesada.", - "errors": [{"field": "status", "message": "Factura no procesada."}], - } - - errors = ErrorCollector() - - _progress(self, 10, "Validando estatus de la factura...") - lines = pre_validators(db, invoice, tenant_id, company_id, errors) - if not lines: - errors.add_error( - field="line_items", - message="La factura no contiene partidas para revertir", - solution=["Verifique que la factura tenga partidas antes de intentar revertirla"], - code="NO_LINE_ITEMS", - ) - errors.raise_if_errors() - - _progress(self, 40, "Verificando saldos de partidas...") - sql_errors = revert_process( - db=db, - invoice=invoice, - lines=lines, - tenant_id=tenant_id, - company_id=company_id, - errors=errors, - ) - - _progress(self, 95, "Anulando saldos de inventario y confirmando...") - db.flush() - db.commit() - - return { - "status": "success", - "invoice_id": invoice_id, - "sql_errors": sql_errors, - } - - except ValidationException as exc: - db.rollback() - return { - "status": "validation_error", - "message": exc.message, - "errors": exc.errors, - } - except Exception as exc: - db.rollback() - raise exc diff --git a/backend/api/v1/modules/a76/invoices/imports/validators/create.py b/backend/api/v1/modules/a76/invoices/imports/validators/create.py deleted file mode 100644 index 3670ea35..00000000 --- a/backend/api/v1/modules/a76/invoices/imports/validators/create.py +++ /dev/null @@ -1,95 +0,0 @@ -from sqlalchemy.orm import Session - -from api.v1.modules.a76.general_catalogs.exchange_rate.models import ExchangeRate -from core.exceptions import ErrorCollector -from ...schemas import InvoiceHeaderCreate -from ...common.common_validators import validate_common, validate_required_fields_by_operation - -def validate_create(db: Session, invoice: InvoiceHeaderCreate, tenant_id: int, company_id: int, errors: ErrorCollector) -> None: - """ Valida la creación de una nueva factura de importe temporal """ - - if not invoice.operation_type: - errors.add_required_error("operation_type") - - if not invoice.invoice_type: - errors.add_required_error("invoice_type") - - if not invoice.document_type and invoice.invoice_type not in ["MEX", "AME"]: - errors.add_required_error("document_type") - - if not invoice.invoice_number: - errors.add_required_error("invoice_number") - - if not invoice.invoice_date: - errors.add_required_error("invoice_date") - - # Validar campos obligatorios según tipo de operación - invoice_data = { - 'provider_header': invoice.compliance_mx.provider_header if invoice.compliance_mx else None, - 'provider_id': invoice.compliance_mx.provider_id if invoice.compliance_mx else None, - 'sold_to_header': invoice.compliance_mx.sold_to_header if invoice.compliance_mx else None, - 'sold_to_id': invoice.compliance_mx.sold_to_id if invoice.compliance_mx else None, - 'shipped_to_header': invoice.compliance_mx.shipped_to_header if invoice.compliance_mx else None, - 'shipped_to_id': invoice.compliance_mx.shipped_to_id if invoice.compliance_mx else None, - 'customs_broker_id': invoice.compliance_mx.customs_broker_id if invoice.compliance_mx else None, - 'pedimento_id': invoice.compliance_mx.pedimento_id if invoice.compliance_mx else None, - } - - validate_required_fields_by_operation( - invoice_data=invoice_data, - operation_type=invoice.operation_type, - errors=errors - ) - - if errors.has_errors(): - """Se retorna porque hay campos obligatorios según el tipo de operación que deben ser llenados""" - return - - validate_common(db, invoice, tenant_id, company_id, errors) - - if errors.has_errors(): - """Se retorna por que fallaron las validaciones generales""" - return - - if invoice.compliance_mx: - if not invoice.compliance_mx.pedimento_id: - invoice.compliance_mx.remesa = None - - if invoice.financials: - if not invoice.financials.exchange_rate: - invoice.financials.exchange_rate = db.query(ExchangeRate.value).filter(ExchangeRate.date == invoice.invoice_date).scalar() - - invoice.document_type = (invoice.document_type or "").upper() - - if invoice.logistics: - if not invoice.logistics.transport_type: - invoice.logistics.transport_type = "none" - - if invoice.logistics.transport_type == "none" and invoice.logistics.transport_num: - invoice.logistics.transport_num = None - - invoice.logistics.incoterm = (invoice.logistics.incoterm or "").upper() - - if not invoice.logistics.weight_type: - invoice.logistics.weight_type = "kgs" - - if invoice.financials: - if not invoice.financials.currency: - invoice.financials.currency = "foreign" - - if invoice.financials.currency == "local": - invoice.financials.currency_type = "MXN" - elif invoice.financials.currency == "foreign": - invoice.financials.currency_type = "USD" - elif invoice.financials.currency == "manual": - invoice.financials.currency_type = (invoice.financials.currency_type or "").upper() - - - - - - - - - - \ No newline at end of file diff --git a/backend/api/v1/modules/a76/invoices/imports/validators/update.py b/backend/api/v1/modules/a76/invoices/imports/validators/update.py deleted file mode 100644 index a91b1676..00000000 --- a/backend/api/v1/modules/a76/invoices/imports/validators/update.py +++ /dev/null @@ -1,261 +0,0 @@ -from typing import Optional -from datetime import date -from decimal import Decimal - -from sqlalchemy.orm import Session - -from ...common.common_validators import validate_common, validate_required_fields_by_operation -from core.exceptions import ErrorCollector -from ...schemas import InvoiceHeaderUpdate -from ...models import InvoiceHeader - -# Helper function para limpiar strings (equivalente a Clip()) -def clean_str(value: Optional[str]) -> Optional[str]: - if value is None or value == "": - return None - return value.strip() - -def validate_update( - db: Session, - invoice: InvoiceHeaderUpdate, - existing_invoice: InvoiceHeader, - tenant_id: int, - company_id: int, - errors: ErrorCollector, -) -> None: - """ - Valida y procesa la actualización parcial de una factura de importación temporal. - - Lógica: Si un campo viene con valor, se limpia/valida. - Si no, se mantiene el valor existente de la factura. - - Args: - invoice: Datos de la factura a validar/actualizar (modificado in-place) - existing_invoice: Factura existente en la base de datos - errors: Colector de errores - - Returns: - None (modifica invoice in-place y acumula errores en errors) - """ - - # Validar campos requeridos según el tipo de operación - invoice_dict = { - 'provider_id': invoice.compliance_mx.provider_id if invoice.compliance_mx else (existing_invoice.compliance_mx.provider_id if existing_invoice.compliance_mx else None), - 'sold_to_id': invoice.compliance_mx.sold_to_id if invoice.compliance_mx else (existing_invoice.compliance_mx.sold_to_id if existing_invoice.compliance_mx else None), - 'sold_to_header': invoice.compliance_mx.sold_to_header if invoice.compliance_mx else (existing_invoice.compliance_mx.sold_to_header if existing_invoice.compliance_mx else None), - 'shipped_to_id': invoice.compliance_mx.shipped_to_id if invoice.compliance_mx else (existing_invoice.compliance_mx.shipped_to_id if existing_invoice.compliance_mx else None), - 'customs_broker_id': invoice.compliance_mx.customs_broker_id if invoice.compliance_mx else (existing_invoice.compliance_mx.customs_broker_id if existing_invoice.compliance_mx else None), - 'pedimento_id': invoice.compliance_mx.pedimento_id if invoice.compliance_mx else (existing_invoice.compliance_mx.pedimento_id if existing_invoice.compliance_mx else None), - } - - validate_required_fields_by_operation( - invoice_data=invoice_dict, - operation_type=invoice.operation_type or (existing_invoice.operation_type or 'imp'), - errors=errors - ) - - # Primero ejecutar validaciones comunes - validate_common( - db, invoice, tenant_id, company_id, errors, existing_invoice=existing_invoice - ) - - # Mapeo de columnas CSV a campos de la factura - # Siguiendo la lógica del código Clarion original - - # Columna A: Pedimento (si no viene en CSV, usar el existente) - if invoice.compliance_mx: - if invoice.compliance_mx.pedimento_id: - invoice.compliance_mx.pedimento_id = invoice.compliance_mx.pedimento_id - else: - invoice.compliance_mx.pedimento_id = existing_invoice.compliance_mx.pedimento_id if existing_invoice.compliance_mx else None - - # Columna B: Remesa - if invoice.compliance_mx: - if invoice.compliance_mx.remesa: - invoice.compliance_mx.remesa = invoice.compliance_mx.remesa - else: - invoice.compliance_mx.remesa = existing_invoice.compliance_mx.remesa if existing_invoice.compliance_mx else None - - # Columna C: Factura (OBLIGATORIO) - if invoice.invoice_number is not None: - invoice.invoice_number = clean_str(invoice.invoice_number) - if not invoice.invoice_number: - errors.add_required_error("invoice_number") - else: - invoice.invoice_number = existing_invoice.invoice_number - - # Columna D: Fecha - if not invoice.invoice_date: - invoice.invoice_date = existing_invoice.invoice_date - - # Columna E: Tipo Cambio - if invoice.financials: - if invoice.financials.exchange_rate is None: - if existing_invoice.financials: - invoice.financials.exchange_rate = existing_invoice.financials.exchange_rate - - # Columna F: Régimen - if invoice.document_type: - invoice.document_type = clean_str(invoice.document_type).upper() - else: - invoice.document_type = existing_invoice.document_type - - # Columna G: Clave Proveedor - if invoice.compliance_mx: - if invoice.compliance_mx.provider_id is None: - invoice.compliance_mx.provider_id = existing_invoice.compliance_mx.provider_id if existing_invoice.compliance_mx else None - - # Columna H: Clave Vendido A - if invoice.compliance_mx: - if invoice.compliance_mx.sold_to_id is None: - invoice.compliance_mx.sold_to_id = existing_invoice.compliance_mx.sold_to_id if existing_invoice.compliance_mx else None - - # Columna I: Clave Enviado A - if invoice.compliance_mx: - if invoice.compliance_mx.shipped_to_id is None: - invoice.compliance_mx.shipped_to_id = existing_invoice.compliance_mx.shipped_to_id if existing_invoice.compliance_mx else None - - # Columna J: Clave A. Aduanal - if invoice.compliance_mx: - if invoice.compliance_mx.customs_broker_id is None: - invoice.compliance_mx.customs_broker_id = existing_invoice.compliance_mx.customs_broker_id if existing_invoice.compliance_mx else None - - # Columna K: Clave Transportista - if invoice.logistics: - # Note: logistics in update schema seems to be a single object, but in model it's a list. - # This validator seems to expect a single object (InvoiceLogisticsUpdate). - # We'll stick to the existing logic but make it safe. - if hasattr(invoice.logistics, 'carrier_id') and invoice.logistics.carrier_id is None: - invoice.logistics.carrier_id = existing_invoice.logistics.carrier_id if existing_invoice.logistics else None - - # Columna L: Nombre Conductor - if invoice.logistics: - if hasattr(invoice.logistics, 'driver_name') and not invoice.logistics.driver_name: - invoice.logistics.driver_name = existing_invoice.logistics.driver_name if existing_invoice.logistics else None - elif hasattr(invoice.logistics, 'driver_name'): - invoice.logistics.driver_name = clean_str(invoice.logistics.driver_name) - - # Columna M: Tipo Transporte - if invoice.logistics: - if hasattr(invoice.logistics, 'transport_type') and not invoice.logistics.transport_type: - invoice.logistics.transport_type = existing_invoice.logistics.transport_type if existing_invoice.logistics else None - elif hasattr(invoice.logistics, 'transport_type'): - invoice.logistics.transport_type = clean_str(invoice.logistics.transport_type) - - # Columna N: Número de Transporte - if invoice.logistics: - if hasattr(invoice.logistics, 'transport_num') and not invoice.logistics.transport_num: - invoice.logistics.transport_num = existing_invoice.logistics.transport_num if existing_invoice.logistics else None - elif hasattr(invoice.logistics, 'transport_num'): - invoice.logistics.transport_num = clean_str(invoice.logistics.transport_num) - - # Columna O: Tipo de Moneda - if invoice.financials: - if not invoice.financials.currency: - invoice.financials.currency = existing_invoice.financials.currency if existing_invoice.financials else None - else: - invoice.financials.currency = clean_str(invoice.financials.currency).lower() - - # Columna P: Clave Moneda - if invoice.financials: - if not invoice.financials.currency_type: - invoice.financials.currency_type = existing_invoice.financials.currency_type if existing_invoice.financials else None - else: - invoice.financials.currency_type = clean_str(invoice.financials.currency_type).upper() - - # Columna Q: Flete - if invoice.financials: - if invoice.financials.freight is None: - invoice.financials.freight = existing_invoice.financials.freight if existing_invoice.financials else None - - # Columna R: Val Seguros - if invoice.financials: - if invoice.financials.insurance_value is None: - invoice.financials.insurance_value = existing_invoice.financials.insurance_value if existing_invoice.financials else None - - # Columna S: Seguros - if invoice.financials: - if invoice.financials.insurance is None: - invoice.financials.insurance = existing_invoice.financials.insurance if existing_invoice.financials else None - - # Columna T: Embalaje - if invoice.financials: - if invoice.financials.packaging is None: - invoice.financials.packaging = existing_invoice.financials.packaging if existing_invoice.financials else None - - # Columna U: Otros Incrementables - if invoice.financials: - if invoice.financials.other_increments is None: - invoice.financials.other_increments = existing_invoice.financials.other_increments if existing_invoice.financials else None - - # Columna V: Incoterms - if invoice.logistics: - if hasattr(invoice.logistics, 'incoterm') and not invoice.logistics.incoterm: - invoice.logistics.incoterm = existing_invoice.logistics.incoterm if existing_invoice.logistics else None - elif hasattr(invoice.logistics, 'incoterm'): - invoice.logistics.incoterm = clean_str(invoice.logistics.incoterm).upper() - - # Columna W: Precinto - if invoice.logistics: - if hasattr(invoice.logistics, 'seal_number') and not invoice.logistics.seal_number: - invoice.logistics.seal_number = existing_invoice.logistics.seal_number if existing_invoice.logistics else None - elif hasattr(invoice.logistics, 'seal_number'): - invoice.logistics.seal_number = clean_str(invoice.logistics.seal_number) - - # Columna X: Fecha de Emisión - if not invoice.emission_date: - invoice.emission_date = existing_invoice.emission_date - - # Columna Y: Tipo de Peso (Opcional) - if invoice.logistics: - if hasattr(invoice.logistics, 'weight_type') and not invoice.logistics.weight_type: - invoice.logistics.weight_type = existing_invoice.logistics.weight_type if existing_invoice.logistics else None - elif hasattr(invoice.logistics, 'weight_type'): - invoice.logistics.weight_type = clean_str(invoice.logistics.weight_type).upper() - - # Columna Z: E-Document (Opcional) - if invoice.compliance_mx: - if not invoice.compliance_mx.edocument: - invoice.compliance_mx.edocument = existing_invoice.compliance_mx.edocument if existing_invoice.compliance_mx else None - else: - invoice.compliance_mx.edocument = clean_str(invoice.compliance_mx.edocument) - - # Columna AA: Num. Operación (Opcional) - if invoice.compliance_mx: - if not invoice.compliance_mx.vucem_operation_num: - invoice.compliance_mx.vucem_operation_num = existing_invoice.compliance_mx.vucem_operation_num if existing_invoice.compliance_mx else None - else: - invoice.compliance_mx.vucem_operation_num = clean_str(invoice.compliance_mx.vucem_operation_num) - - # Columna AB: Aduana (OBLIGATORIO) - if invoice.compliance_mx: - if not invoice.compliance_mx.aduana: - invoice.compliance_mx.aduana = existing_invoice.compliance_mx.aduana if existing_invoice.compliance_mx else None - else: - invoice.compliance_mx.aduana = clean_str(invoice.compliance_mx.aduana) - - # Validar que aduana sea obligatorio (excepto para MEX) - if existing_invoice.invoice_type not in ["MEX", "AME"]: - current_aduana = invoice.compliance_mx.aduana if invoice.compliance_mx else (existing_invoice.compliance_mx.aduana if existing_invoice.compliance_mx else None) - if not current_aduana: - errors.add_required_error("aduana") - - # Columna AC: Sección de Despacho / Puerto de Entrada (Opcional) - if invoice.compliance_mx: - if not invoice.compliance_mx.port_of_entry: - invoice.compliance_mx.port_of_entry = existing_invoice.compliance_mx.port_of_entry if existing_invoice.compliance_mx else None - else: - invoice.compliance_mx.port_of_entry = clean_str(invoice.compliance_mx.port_of_entry) - - # Columna AD: Observación en Español (Opcional) - if not invoice.observation_es: - invoice.observation_es = existing_invoice.observation_es - else: - invoice.observation_es = clean_str(invoice.observation_es) - - # Columna AD: Observación en Inglés (Opcional) - if not invoice.observation_en: - invoice.observation_en = existing_invoice.observation_en - else: - invoice.observation_en = clean_str(invoice.observation_en) - diff --git a/backend/api/v1/modules/a76/invoices/models.py b/backend/api/v1/modules/a76/invoices/models.py deleted file mode 100644 index 8cf823e6..00000000 --- a/backend/api/v1/modules/a76/invoices/models.py +++ /dev/null @@ -1,775 +0,0 @@ -from enum import Enum -from typing import Optional, List, TYPE_CHECKING -from sqlalchemy import ( - BigInteger, - Boolean, - Date, - ForeignKey, - Integer, - Numeric, - String, - Text, - TIMESTAMP, - func, -) -from sqlalchemy.orm import Mapped, mapped_column, relationship -from core.database import Base -from datetime import datetime -from ....common.base_models import TenantScopedMixin, TimestampMixin - -if TYPE_CHECKING: - from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos - - -class Currency(str, Enum): - FOREIGN = "foreign" - LOCAL = "local" - MANUAL = "manual" - - -class WeightUnit(str, Enum): - KGS = "kgs" - LBS = "lbs" - - -class DestinationOriginCove(str, Enum): - EDO_BC_PARC_SON = "edo_bc_parc_son" - ESTADO_BCS = "estado_bcs" - ESTADO_ROO = "estado_roo" - MPIO_SALINA_CRUZ_OAX = "mpio_salina_cruz_oxa" - FRANJA_FRONT_NORTE = "franja_front_norte" - INTERIOR_PAIS = "interior_pais" - MPIO_CABORCA_SON = "mpio_caborca_son" - - -class OperationType(str, Enum): - IMP = "imp" # Importación - EXP = "exp" # Exportación - SM_IN = "sm_in" # Entrada SM - SM_OUT = "sm_out" # Salida SM - CTM_SEND = "ctm_send" # Envío CTM - CTM_RECEIVE = "ctm_receive" # Recibo CTM - - -class TransportType(str, Enum): - NONE = "none" - TRANSPORT = "transport" - BOX = "box" - PLATES = "licence plates" - TRUCK = "truck" - VESSEL = "vessel" - BARGE = "rail_barge" - CONTAINER = "container" - AIRPLANE = "airplane" - GONDOLA = "gondola" - FLATBED = "flatbed" - -class InvoiceStatus(str, Enum): - PENDING = "pending" - PROCESSED = "processed" - REVERSED = "reversed" - - -# --- 1. Invoice Header (invoice_header) --- -class InvoiceHeader(Base, TenantScopedMixin, TimestampMixin): - __tablename__ = "invoice_header" - __table_args__ = ({"schema": "a76"},) - - id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=True) - - # Identifiers - system: Mapped[str] = mapped_column( - String(12) - ) # SISTEMA / Sistema de origen <-- no tiene campo en la antigua base de datos, sera para fixed_asset(scaf), inventory(scaii) - operation_type: Mapped[OperationType] = mapped_column( - String(11) - ) # TIPOMOVIMIENTO / Clasifica imp/exp/sm/ctm - invoice_type: Mapped[str] = mapped_column( - ForeignKey("public.invoice_types.key") - ) # TIPOFACTURA / TIPODOC - document_type: Mapped[Optional[str]] = mapped_column( - ForeignKey("public.pedimento_regimens.code"), nullable=True - ) - # CLAVEDOCUMENTO / Clave de documento - invoice_number: Mapped[str] = mapped_column( - String(100) - ) # FACTURAIMPO/FACTURAEXPO/FACTURAREMISION/FACTURAENVIO/FACTURASALIDA - project_number: Mapped[Optional[str]] = mapped_column(String(14)) # NUMPROYECTO - purchase_order: Mapped[Optional[str]] = mapped_column(String(50)) # ORDENCOMPRA - related_doc_id: Mapped[Optional[int]] = mapped_column( - Integer - ) # IDRELDOC / Para Rectificaciones - alternate_invoice: Mapped[Optional[str]] = mapped_column( - String(99) - ) # FACTURAALTERNA - invoice_ref: Mapped[Optional[str]] = mapped_column(String(19)) # FACTURAEXPOREF - proforma_number: Mapped[Optional[str]] = mapped_column(String(20)) # NUMEROPROFORMA - - # Dates - invoice_date: Mapped[datetime] = mapped_column(Date) # FECHAFACTURA - capture_date: Mapped[datetime] = mapped_column( - TIMESTAMP(timezone=False), server_default=func.now() - ) # FECHACAPTURA + HORAACTUAL - emission_date: Mapped[Optional[datetime]] = mapped_column(Date) # FECHAEMISION - - # Status & Control - status: Mapped[InvoiceStatus] = mapped_column(String(10)) # ESTATUS - status_rec: Mapped[Optional[InvoiceStatus]] = mapped_column( - String(10) - ) # ESTATUSREC / Estatus de recepción - status_rep: Mapped[Optional[InvoiceStatus]] = mapped_column( - String(10) - ) # ESTATUSREP / Estatus de reporte - processed_date: Mapped[Optional[datetime]] = mapped_column( - TIMESTAMP(timezone=False) - ) # FECHAACTUALIZACION / FECHAACTUAL - who_processed: Mapped[Optional[str]] = mapped_column( - String(100) - ) # USUARIOACT / Quien actualizó - capture_user: Mapped[Optional[str]] = mapped_column( - String(100) - ) # USUARIOCAP / Usuario que capturó - - traffic_light_status: Mapped[Optional[str]] = mapped_column( - String(50) - ) # SEMAFORO / SEMAFOROEXPO/IMPO - process_log: Mapped[Optional[str]] = mapped_column(String(300)) # COMOFUEPROCESADA - - # Comments - observation_es: Mapped[Optional[str]] = mapped_column( - Text - ) # OBSERVACIONE / Observaciones en español - observation_en: Mapped[Optional[str]] = mapped_column( - Text - ) # OBSERVACIONI / Observaciones en inglés - comments_status: Mapped[Optional[str]] = mapped_column(Text) # COMENTARIOSESTATUS - vu_observations: Mapped[Optional[str]] = mapped_column( - String(500) - ) # OBSERVACIONESVU / Observaciones VUCEM - - # Digital Archive Links - cfdi_uuid: Mapped[Optional[str]] = mapped_column(String(100)) # CFDIUUID - path_pdf: Mapped[Optional[str]] = mapped_column(String(500)) # CFDIPATHPDF - path_xml: Mapped[Optional[str]] = mapped_column(String(500)) # CFDIPATHXML - - # Control & Subcompany - subcompany: Mapped[Optional[str]] = mapped_column(String(5)) # SUBEMPRESA - party_count: Mapped[Optional[int]] = mapped_column( - Integer - ) # CANT_PARTIDAS / Cantidad de partidas - - # Generation flags - generate_id: Mapped[Optional[bool]] = mapped_column( - Boolean, default=False, server_default="false" - ) # GENERAID - generate_desc_parties: Mapped[Optional[str]] = mapped_column( - String(12) - ) # GENDESCPARTIDAS / Generar descripción de partidas - apply_manual_discount: Mapped[Optional[bool]] = mapped_column( - Boolean, default=False, server_default="false" - ) # APLICADESCMANUAL - - # Bulk & Downloads - is_bulk: Mapped[Optional[bool]] = mapped_column(Boolean) # ESAGRANEL / Es a granel - download_substance: Mapped[Optional[bool]] = mapped_column( - Boolean - ) # DESCARGASUST / Descarga de sustancia - download_class: Mapped[Optional[bool]] = mapped_column( - Boolean - ) # DESCARGACLASE / Descarga de clase - download_def: Mapped[Optional[bool]] = mapped_column( - Boolean - ) # DESCARGADEF / Descarga definitiva - - # Additional fields - payment_terms: Mapped[Optional[str]] = mapped_column( - String(200) - ) # TERMINOSPAGO / Términos de pago - handling_fees: Mapped[Optional[float]] = mapped_column(Numeric(19, 8)) # MANIOBRAS - option_iv18: Mapped[Optional[str]] = mapped_column(String(50)) # OPCIONIV18 - enajenation_goods: Mapped[Optional[bool]] = mapped_column( - Boolean - ) # ENAJENACIONBIENES / Enajenación de bienes - - # Relationships - compliance_mx: Mapped[Optional["InvoiceComplianceMx"]] = relationship( - back_populates="header", cascade="all, delete-orphan", uselist=False - ) - financials: Mapped[Optional["InvoiceFinancials"]] = relationship( - back_populates="header", cascade="all, delete-orphan", uselist=False - ) - details: Mapped[List["InvoiceSalesDetails"]] = relationship( - back_populates="header", cascade="all, delete-orphan" - ) - collections: Mapped[List["InvoiceCollections"]] = relationship( - back_populates="header", cascade="all, delete-orphan" - ) - logistics: Mapped[Optional["InvoiceLogistics"]] = relationship( - back_populates="header", cascade="all, delete-orphan", uselist=False - ) - - -# --- 2. Compliance MX (invoice_compliance_mx) --- -class InvoiceComplianceMx(Base, TenantScopedMixin, TimestampMixin): - __tablename__ = "invoice_compliance_mx" - __table_args__ = ({"schema": "a76"},) - - invoice_id: Mapped[int] = mapped_column( - ForeignKey("a76.invoice_header.id"), primary_key=True - ) - - # Core Customs Data - pedimento_id: Mapped[Optional[int]] = mapped_column( - ForeignKey("a76.pedimentos.id") - ) # PEDIMENTO/PEDIMENTOIMPO/EXPO - pedimento_r1: Mapped[Optional[int]] = mapped_column( - ForeignKey("a76.pedimentos.id") - ) # PEDIMENTOR1 - pedimento_k1: Mapped[Optional[int]] = mapped_column( - ForeignKey("a76.pedimentos.id") - ) # PEDIMENTOK1 - remesa: Mapped[Optional[int]] = mapped_column(Integer) # REMESA - aduana: Mapped[Optional[str]] = mapped_column( - ForeignKey("public.customs_sections.customs_code") - ) # ADUANA_CRUCE - port_of_entry: Mapped[Optional[str]] = mapped_column( - String(6) - ) # PUERTOENTRADA / Puerto de entrada - destination: Mapped[Optional[str]] = mapped_column( - String(3) - ) # DESTINO / Código de destino - manifest_number: Mapped[Optional[str]] = mapped_column( - String(15) - ) # MANIFIESTO / Número de manifiesto - - # Clients & Providers - provider_header: Mapped[Optional[str]] = mapped_column( - String(20) - ) # PROVEEDOREXPORTADOR - provider_id: Mapped[Optional[int]] = mapped_column( - ForeignKey("a76.clients_and_providers.id") - ) # PROVEEDOR - sold_to_header: Mapped[Optional[str]] = mapped_column( - String(20) - ) # VENDIDOCONSIGNADO - sold_to_id: Mapped[Optional[int]] = mapped_column( - ForeignKey("a76.clients_and_providers.id") - ) # VENDIDOA - shipped_to_header: Mapped[Optional[str]] = mapped_column( - String(20) - ) # ENVIADOTRANSFERIDO - shipped_to_id: Mapped[Optional[int]] = mapped_column( - ForeignKey("a76.clients_and_providers.id") - ) # ENVIADOA - shipped_by_header: Mapped[Optional[str]] = mapped_column( - String(20) - ) # ENVIADOPORVENDIDOPOR - shipped_by_id: Mapped[Optional[int]] = mapped_column( - ForeignKey("a76.clients_and_providers.id") - ) # VENDIDOPOR/ENVIADOPOR - customs_broker_id: Mapped[Optional[int]] = mapped_column( - ForeignKey("a76.customs_brokers.id") - ) # AADUANAL / Agente aduanal - customs_broker_us_id: Mapped[Optional[int]] = mapped_column( - ForeignKey("a76.customs_brokers.id") - ) # AADUANALAME / Agente aduanal americano - - # Broker Invoice - broker_invoice_num: Mapped[Optional[str]] = mapped_column( - String(20) - ) # NUMFACTURABROKER / Número factura broker - broker_invoice_date: Mapped[Optional[datetime]] = mapped_column( - Date - ) # FECHAFACBROKER / Fecha factura broker - - # Flags & Specific Regimes - is_mixed: Mapped[Optional[bool]] = mapped_column(Boolean) # ESMIXTO / Es mixto - waste_type: Mapped[Optional[str]] = mapped_column( - String(1) - ) # TIPODESPERDICIO / Tipo de desperdicio - scrap_type: Mapped[Optional[str]] = mapped_column( - String(1) - ) # TIPOSCRAP / Tipo de scrap - appendix_17: Mapped[Optional[int]] = mapped_column( - Integer - ) # APENDICE17 / Apéndice 17 - is_regime_change: Mapped[Optional[bool]] = mapped_column( - Boolean, default=False, server_default="false" - ) # ESCAMBIOREGIMEN / Es cambio de régimen - which_exchange_rate: Mapped[Optional[str]] = mapped_column( - String(5) - ) # CUALTIPOCAMBIO / Cuál tipo de cambio - value_method: Mapped[Optional[str]] = mapped_column( - String(2) - ) # METVALOR / Método de valoración - act_value: Mapped[Optional[str]] = mapped_column( - String(5) - ) # ACTVALOR / Actualizar valor - rule_3121_parties_ii: Mapped[Optional[bool]] = mapped_column( - Boolean, default=False, server_default="false" - ) # REGLA3121PARTESII / Regla 3.1.21 Partes II - is_pedimento_pending: Mapped[Optional[bool]] = mapped_column( - Boolean, default=False, server_default="false" - ) # PED_PENDIENTE_ASIGNAR (Mapear 1 -> True, 0 -> False) - - # Ownership & Balances - is_owner_of_goods: Mapped[Optional[bool]] = mapped_column( - Boolean, default=False, server_default="false" - ) # ESDUENOMCIA / Es dueño de mercancía - generate_balances: Mapped[Optional[bool]] = mapped_column( - Boolean, default=False, server_default="false" - ) # GENERARSALDOS / Generar saldos - was_reviewed_by_company: Mapped[Optional[bool]] = mapped_column( - Boolean - ) # FUEREVISADAMCIA / Fue revisada por la compañía - - # VUCEM / Digital - edocument: Mapped[Optional[str]] = mapped_column( - String(50) - ) # EDOCUMENT / Documento electrónico - electronic_signature: Mapped[Optional[str]] = mapped_column( - String(999) - ) # FIRMAELECTRONICA / Firma electrónica - certificate_number: Mapped[Optional[str]] = mapped_column( - String(99) - ) # NUMEROCERTIFICADO / Número de certificado - niu_number: Mapped[Optional[str]] = mapped_column( - String(19) - ) # NUMERONIU / Número NIU - bill_of_lading_count: Mapped[Optional[str]] = mapped_column( - String(12) - ) # CANTGUIASEMBARQUE / Cantidad guías embarque - addendum_vu: Mapped[Optional[str]] = mapped_column( - String(204) - ) # ADENDAVU / Adenda VUCEM - origin_destination_cove: Mapped[Optional[DestinationOriginCove]] = mapped_column( - String(20) - ) # DESTINOORIGENCOVE / Destino/Origen COVE - vucem_operation_num: Mapped[Optional[str]] = mapped_column( - String(19) - ) # NUMOPERACIONVU / Número operación VUCEM - customs_person_line: Mapped[Optional[int]] = mapped_column( - Integer - ) # LINEAPERSONAAA / Línea persona agente aduanal - - # Additional Control - contingency_mode: Mapped[Optional[bool]] = mapped_column( - Boolean - ) # MODOCONTINGENCIA / Modo contingencia - enclosure: Mapped[Optional[str]] = mapped_column( - String(4) - ) # RECINTO / Recinto fiscal - guide_type_to_identify: Mapped[Optional[str]] = mapped_column( - String(1) - ) # TIPODEGUIAAIDENTIFICAR / Tipo de guía a identificar - location: Mapped[Optional[str]] = mapped_column( - String(200) - ) # LOCALIZACION / Localización - - # DOT & Official - dot_code: Mapped[Optional[str]] = mapped_column(String(20)) # CLAVEDOT / Clave DOT - subdivision: Mapped[Optional[str]] = mapped_column( - String(20) - ) # SUBDIVISION / Subdivisión - acts_as: Mapped[Optional[str]] = mapped_column( - String(20) - ) # FUNGECOMOCO / Funge como - movement_type: Mapped[Optional[str]] = mapped_column( - String(31) - ) # TIPOMOV / Tipo de movimiento - office_document: Mapped[Optional[str]] = mapped_column( - String(30) - ) # OFICIO / Oficio - reason_export: Mapped[Optional[str]] = mapped_column( - String(1) - ) # RAZONEXPORTACION / Razón de exportación - signature_key: Mapped[Optional[str]] = mapped_column( - String(100) - ) # CLAVEFIRMA / Clave de firma - - # SM specific - sem_id: Mapped[Optional[int]] = mapped_column( - Integer - ) # SEM / ID SEM (de SFacEntradaSM/SFacSalidaSM) - - # Relationships - header: Mapped["InvoiceHeader"] = relationship(back_populates="compliance_mx") - pedimento: Mapped[Optional["Pedimentos"]] = relationship( - foreign_keys=[pedimento_id], lazy="joined" - ) - - -# --- 3. Financials (invoice_financials) --- -class InvoiceFinancials(Base, TenantScopedMixin, TimestampMixin): - __tablename__ = "invoice_financials" - __table_args__ = ({"schema": "a76"},) - - id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=True) - invoice_id: Mapped[int] = mapped_column(ForeignKey("a76.invoice_header.id")) - - # Currency - currency: Mapped[Currency] = mapped_column( - String(7) - ) # CLAVEMONEDA / Clave de moneda - currency_type: Mapped[Optional[str]] = mapped_column( - ForeignKey("public.currency_types.code") - ) # TIPOMONEDA / TIPOCLAVEMONEDA - exchange_rate: Mapped[Optional[float]] = mapped_column( - Numeric(13, 6) - ) # TIPOCAMBIO / Tipo de cambio - exchange_rate_mm: Mapped[Optional[float]] = mapped_column( - Numeric(13, 6) - ) # TIPOCAMBIOMM / Tipo de cambio moneda a moneda - - # Merchandise Values (MN = National Currency, ME = Foreign Currency, MC = Third Currency) - value_mn: Mapped[Optional[float]] = mapped_column( - Numeric(23, 8), default=0, server_default="0" - ) # VALORIMPOMN/VALOREXPOMN/VALORENTMN/VALORSALMN - value_me: Mapped[Optional[float]] = mapped_column( - Numeric(23, 8), default=0, server_default="0" - ) # VALORIMPOME/VALOREXPOME/VALORENTME/VALORSALME - value_mc: Mapped[Optional[float]] = mapped_column( - Numeric(23, 8), default=0, server_default="0" - ) # VALORIMPOMC/VALOREXPOMC - - # Customs Value - customs_value_mn: Mapped[Optional[float]] = mapped_column( - Numeric(23, 8), default=0, server_default="0" - ) # VALORADUANASMN / Valor en aduanas MN - customs_value_me: Mapped[Optional[float]] = mapped_column( - Numeric(23, 8), default=0, server_default="0" - ) # VALORADUANASME / Valor en aduanas ME - - # Raw Materials - raw_material_value_mn: Mapped[Optional[float]] = mapped_column( - Numeric(23, 8), default=0, server_default="0" - ) # VALORMPMN / Valor materia prima MN - raw_material_value_me: Mapped[Optional[float]] = mapped_column( - Numeric(23, 8), default=0, server_default="0" - ) # VALORMPME / Valor materia prima ME - - # Aggregate Value - aggregate_value_mn: Mapped[Optional[float]] = mapped_column( - Numeric(23, 8), default=0, server_default="0" - ) # VALORAGREMN / Valor agregado MN - aggregate_value_me: Mapped[Optional[float]] = mapped_column( - Numeric(23, 8), default=0, server_default="0" - ) # VALORAGREME / Valor agregado ME - aggregate_value_mc: Mapped[Optional[float]] = mapped_column( - Numeric(23, 8), default=0, server_default="0" - ) # VALORAGREMC / Valor agregado MC - - # Mexican Merchandise Value - mexican_value_mn: Mapped[Optional[float]] = mapped_column( - Numeric(23, 8), default=0, server_default="0" - ) # VALORVMEXMN / Valor mercancía mexicana MN - mexican_value_me: Mapped[Optional[float]] = mapped_column( - Numeric(23, 8), default=0, server_default="0" - ) # VALORVMEXME / Valor mercancía mexicana ME - mexican_value_mc: Mapped[Optional[float]] = mapped_column( - Numeric(23, 8), default=0, server_default="0" - ) # VALORVMEXMC / Valor mercancía mexicana MC - - # National Packaging - national_packaging_mn: Mapped[Optional[float]] = mapped_column( - Numeric(23, 8), default=0, server_default="0" - ) # VALEMPAQUENACMN / Valor empaque nacional MN - national_packaging_me: Mapped[Optional[float]] = mapped_column( - Numeric(23, 8), default=0, server_default="0" - ) # VALEMPAQUENACME / Valor empaque nacional ME - national_packaging_mc: Mapped[Optional[float]] = mapped_column( - Numeric(23, 8), default=0, server_default="0" - ) # VALEMPAQUENACMC / Valor empaque nacional MC - - # Costs & Increments - freight: Mapped[Optional[float]] = mapped_column( - Numeric(19, 8), default=0, server_default="0" - ) # FLETE / Flete - insurance: Mapped[Optional[float]] = mapped_column( - Numeric(19, 8), default=0, server_default="0" - ) # SEGUROS / Seguros - insurance_value: Mapped[Optional[float]] = mapped_column( - Numeric(19, 8), default=0, server_default="0" - ) # VALSEGUROS / Valor seguros - packaging: Mapped[Optional[float]] = mapped_column( - Numeric(19, 8), default=0, server_default="0" - ) # EMBALAJES / Embalajes - other_increments: Mapped[Optional[float]] = mapped_column( - Numeric(19, 8), default=0, server_default="0" - ) # OTROSINCREMENTA / Otros incrementables - other_deductibles: Mapped[Optional[float]] = mapped_column( - Numeric(19, 8), default=0, server_default="0" - ) # OTROSDEDUCIBLES / Otros deducibles - total_increments_mn: Mapped[Optional[float]] = mapped_column( - Numeric(23, 8), default=0, server_default="0" - ) # TOTALINCREMMN / Total incrementables MN - total_increments_me: Mapped[Optional[float]] = mapped_column( - Numeric(23, 8), default=0, server_default="0" - ) # TOTALINCREMME / Total incrementables ME - - # Taxes - iva_mn: Mapped[Optional[float]] = mapped_column( - Numeric(23, 8), default=0, server_default="0" - ) # IVAEXPOMN/VALORIVAMN / IVA en MN - iva_me: Mapped[Optional[float]] = mapped_column( - Numeric(23, 8), default=0, server_default="0" - ) # IVAEXPOME/VALORIVAME / IVA en ME - iva_mc: Mapped[Optional[float]] = mapped_column( - Numeric(23, 8), default=0, server_default="0" - ) # IVAEXPOMC / IVA en MC - iva_factor: Mapped[Optional[float]] = mapped_column( - Numeric(23, 8), default=0, server_default="0" - ) # FACTORIVA / Factor IVA - tax_value_me: Mapped[Optional[float]] = mapped_column( - Numeric(23, 8), default=0, server_default="0" - ) # VALORIMPUESTOME / Valor impuesto ME - seal_value_2500: Mapped[Optional[bool]] = mapped_column( - Boolean - ) # SELLOVALOR2500 / Sello valor 2500 - - # Weights & Quantities - total_quantity: Mapped[Optional[float]] = mapped_column( - Numeric(19, 8) - ) # CANTEXPO/CANTIMPO / Cantidad total - total_packages: Mapped[Optional[int]] = mapped_column( - Integer - ) # CANTBULTOS / Cantidad de bultos - gross_weight: Mapped[Optional[float]] = mapped_column( - Numeric(19, 8) - ) # PESOBRUTO / Peso bruto - net_weight: Mapped[Optional[float]] = mapped_column( - Numeric(19, 8) - ) # PESONETO / Peso neto - bundle_count: Mapped[Optional[int]] = mapped_column( - Integer - ) # CANTBULTOS / Cantidad de bultos - weight_factor: Mapped[Optional[float]] = mapped_column( - Numeric(19, 8) - ) # FACTORPESO / Factor de peso - - # Relationship - header: Mapped["InvoiceHeader"] = relationship(back_populates="financials") - - -# --- 4. Logistics (invoice_logistics) --- -class InvoiceLogistics(Base, TenantScopedMixin, TimestampMixin): - __tablename__ = "invoice_logistics" - __table_args__ = ({"schema": "a76"},) - - id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=True) - invoice_id: Mapped[int] = mapped_column(ForeignKey("a76.invoice_header.id")) - - # Carrier Info - carrier_id: Mapped[Optional[str]] = mapped_column( - String(10) - ) # TRANSPORTISTA / Transportista - carrier_int_id: Mapped[Optional[int]] = mapped_column( - BigInteger, ForeignKey("a76.transporter.transporter_id"), nullable=True - ) # Internal surrogate ID for Transporter (by transporter_key) - transport_id: Mapped[Optional[str]] = mapped_column( - String(10) - ) # NUMTRAILER / Transportista - transport_int_id: Mapped[Optional[int]] = mapped_column( - BigInteger, ForeignKey("a76.vehicle.vehicle_id"), nullable=True - ) # Internal surrogate ID for Vehicle (by vehicle_key) - transport_us_id: Mapped[Optional[str]] = mapped_column( - String(10) - ) # TRANSPORTISTAAME / Transportista americano - transport_type: Mapped[TransportType] = mapped_column( - String(15), server_default="none" - ) # TRANSPORTE / Tipo de transporte - transport_num: Mapped[Optional[str]] = mapped_column( - String(20) - ) # NUMTRASPORTE / Número de transporte - transport_mode: Mapped[Optional[str]] = mapped_column( - String(15) - ) # MODTRANS / Modo de transporte - driver_name: Mapped[Optional[str]] = mapped_column( - String(80) - ) # CONDUCTOR / Nombre del conductor - is_rail: Mapped[Optional[bool]] = mapped_column( - Boolean, default=False, server_default="false" - ) # ESFERROCARRIL / Es ferrocarril - rail_id: Mapped[Optional[str]] = mapped_column( - String(31) - ) # IDFERRORCARRIL / ID ferrocarril - - # Vehicle & Tracking - vehicle_num: Mapped[Optional[str]] = mapped_column( - String(20) - ) # NUMVEHICULO / Número de vehículo - license_plate: Mapped[Optional[str]] = mapped_column( - String(20) - ) # NUMTRASPORTE / Número de transporte/placa - license_plate_complete: Mapped[Optional[str]] = mapped_column( - String(40) - ) # NUMTRASPORTECOMPLE / Número transporte completo - trailer_num: Mapped[Optional[str]] = mapped_column( - String(20) - ) # NUMTRAILER / Número de trailer - trailer_int_id: Mapped[Optional[int]] = mapped_column( - BigInteger, ForeignKey("a76.trailer.trailer_id"), nullable=True - ) # Internal surrogate ID for Trailer (by trailer_number) - seal_number: Mapped[Optional[str]] = mapped_column( - String(15) - ) # PRECINTO / Precinto - guide_number: Mapped[Optional[str]] = mapped_column( - String(20) - ) # NUMEROGUIA / Número de guía - bill_number: Mapped[Optional[str]] = mapped_column( - String(15) - ) # BILLNUMBER / Número de bill - reference_number: Mapped[Optional[str]] = mapped_column( - String(14) - ) # NUMREFERENCIA / Número de referencia - shipment_number: Mapped[Optional[str]] = mapped_column( - String(19) - ) # NUMEMBARQUE / Número de embarque - - # Incoterms - incoterm: Mapped[Optional[str]] = mapped_column( - String(5) - ) # INCOTERM / Término de comercio internacional - - # Identifiers & Complements - identifier_1: Mapped[Optional[str]] = mapped_column( - String(2) - ) # IDENTIFICADOR / Identificador 1 - complement_1: Mapped[Optional[str]] = mapped_column( - String(30) - ) # COMPLEMENTO1 / Complemento 1 - identifier_2: Mapped[Optional[str]] = mapped_column( - String(2) - ) # IDENTIFICADOR2 / Identificador 2 - complement_2: Mapped[Optional[str]] = mapped_column( - String(30) - ) # COMPLEMENTO2 / Complemento 2 - - # Weight & Container Info - weight_type: Mapped[WeightUnit] = mapped_column( - String(3) - ) # TIPOPESO / Tipo de peso - container_types: Mapped[Optional[str]] = mapped_column( - String(500) - ) # CONTENEDORESTIPO / Tipos de contenedores - vehicle_data: Mapped[Optional[str]] = mapped_column( - String(500) - ) # DATOSVEHICULO / Datos del vehículo - - # Locations & Routes - origin_location: Mapped[Optional[str]] = mapped_column( - String(200) - ) # ORIGENUBICACION / Ubicación de origen - destination_location: Mapped[Optional[str]] = mapped_column( - String(200) - ) # DESTINOUBICACION / Ubicación de destino - transport_itinerary: Mapped[Optional[str]] = mapped_column( - String(1000) - ) # ITINERARIOTRANPORTE / Itinerario del transporte - destination_goods: Mapped[Optional[str]] = mapped_column( - String(50) - ) # DESTINOMCIA / Destino de mercancía - - # Logistics Dates - entry_exit_date: Mapped[Optional[datetime]] = mapped_column( - Date - ) # FECHAENTRADA/FECHAENVIO/FECHARECIBO / Fecha entrada/salida - delivery_date: Mapped[Optional[datetime]] = mapped_column( - Date - ) # FECHAENTREGA / Fecha de entrega - - # Delivery Control - delivered_status: Mapped[Optional[bool]] = mapped_column( - Boolean, default=False, server_default="false" - ) # ENTREGADO / Estado de entrega - received_by: Mapped[Optional[str]] = mapped_column( - String(50) - ) # RECIBIDOPOR / Recibido por - - # Payment Info - payment_date: Mapped[Optional[datetime]] = mapped_column( - Date - ) # FECHAPAGO / Fecha de pago - payment_receipt_num: Mapped[Optional[str]] = mapped_column( - String(20) - ) # NUMRECIBOPAGO / Número de recibo de pago - - # CTM Process - is_ctm_process: Mapped[Optional[bool]] = mapped_column( - Boolean, default=False, server_default="false" - ) # SETRATAPROCESOCTM / Se trata de proceso CTM - - # Continuation Tab Fields - equipment_reviewed: Mapped[Optional[bool]] = mapped_column( - Boolean, default=False - ) # Fue revisado el equipo - is_subdivision: Mapped[Optional[bool]] = mapped_column( - Boolean, default=False - ) # Sub división - acts_as_cd: Mapped[Optional[bool]] = mapped_column( - Boolean, default=False - ) # Funge como CD - pedimento_arrived: Mapped[Optional[bool]] = mapped_column( - Boolean, default=False - ) # Llegó el pedimento - green_light_mx: Mapped[Optional[bool]] = mapped_column( - Boolean, default=False - ) # Semáforo verde México - green_light_us: Mapped[Optional[bool]] = mapped_column( - Boolean, default=False - ) # Semáforo verde USA - red_light_mx: Mapped[Optional[bool]] = mapped_column( - Boolean, default=False - ) # Semáforo rojo México - red_light_us: Mapped[Optional[bool]] = mapped_column( - Boolean, default=False - ) # Semáforo rojo USA - - # Relationship - header: Mapped["InvoiceHeader"] = relationship(back_populates="logistics") - - -# --- 5. Sales Order Details (invoice_sales_details) --- -class InvoiceSalesDetails(Base, TenantScopedMixin, TimestampMixin): - __tablename__ = "invoice_sales_details" - __table_args__ = ({"schema": "a76"},) - - id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=True) - invoice_id: Mapped[int] = mapped_column(ForeignKey("a76.invoice_header.id")) - - line_number: Mapped[int] = mapped_column(Integer) # LINEA / Número de línea - sales_order: Mapped[Optional[str]] = mapped_column( - String(20) - ) # ORDENVENTA / Orden de venta - - # Specific Custom Fields - colors_description: Mapped[Optional[str]] = mapped_column( - String(49) - ) # COLORES / Descripción de colores - square_color_code: Mapped[Optional[str]] = mapped_column( - String(1) - ) # COLORCUADRITO / Código de color cuadrito - line_bundles: Mapped[Optional[int]] = mapped_column( - Integer - ) # CANTBULTOS / Cantidad de bultos de la línea - - # Relationship - header: Mapped["InvoiceHeader"] = relationship(back_populates="details") - - -# --- 6. Collections (invoice_collections) --- -class InvoiceCollections(Base, TenantScopedMixin, TimestampMixin): - __tablename__ = "invoice_collections" - __table_args__ = ({"schema": "a76"},) - - id: Mapped[int] = mapped_column(BigInteger, primary_key=True, autoincrement=True) - invoice_id: Mapped[int] = mapped_column(ForeignKey("a76.invoice_header.id")) - - line_number: Mapped[int] = mapped_column(Integer) # LINEA / Número de línea - invoice_number: Mapped[Optional[str]] = mapped_column( - String(15) - ) # FACTURA / Número de factura - concept: Mapped[Optional[str]] = mapped_column(String(100)) # CONCEPTO / Concepto - - # Relationship - header: Mapped["InvoiceHeader"] = relationship(back_populates="collections") diff --git a/backend/api/v1/modules/a76/invoices/permission_map.py b/backend/api/v1/modules/a76/invoices/permission_map.py deleted file mode 100644 index 897ceb2d..00000000 --- a/backend/api/v1/modules/a76/invoices/permission_map.py +++ /dev/null @@ -1,91 +0,0 @@ -""" -Única fuente de verdad para permisos de facturas por (operation_type, invoice_type). - -Debe mantenerse alineado con el seed en api/v1/modules/core/permissions/seed.py -y con frontend/src/lib/permissions/invoice-permissions.ts. -""" - -from __future__ import annotations - -from typing import Any, Iterable, Optional - -# Tipos de exportación que cubre únicamente invoice.exp.view (histórico list_invoices). -EXP_TYPES_FROM_GENERAL_EXPORT_VIEW: tuple[str, ...] = ( - "EXDEF", - "MATDE", - "NODES", - "PTERM", - "SCRAP", - "VEMEX", - "VIRTU", - "AFIJO", - "REEXP", -) - - -def get_invoice_permission_base(operation_type: Any, invoice_type: Any) -> str: - """Devuelve la clave base del permiso (sin .view/.create/...) según tipo de factura.""" - op = str(operation_type).lower().split(".")[-1] if operation_type else "" - inv = str(invoice_type).upper() if invoice_type else "" - - if op == "imp": - if inv == "TEM": - return "invoice.imp.tem" - if inv == "DEF": - return "invoice.imp.def" - if inv == "MEX": - return "invoice.imp.cm" - if inv == "CR": - return "invoice.imp.cr" - if inv == "REP": - return "invoice.imp.rep" - return "invoice.imp.tem" - if op == "exp": - if inv == "REPAR": - return "invoice.exp.rep" - if inv == "DONAC": - return "invoice.exp.donac" - return "invoice.exp" - - return "invoice.imp.tem" - - -def build_allowed_types_from_view_permissions( - perm_codes: Iterable[str], -) -> list[tuple[str, str]]: - """ - Construye la lista de tuplas (operation_type en minúsculas, invoice_type en MAYÚSCULAS) - permitidas por los permisos *.view del usuario. Misma lógica que el antiguo list_invoices - (sin permiso global invoice.view_all — eliminado de producto). - """ - codes = set(perm_codes) - allowed: list[tuple[str, str]] = [] - seen: set[tuple[str, str]] = set() - - def add_pair(op: str, inv: str) -> None: - pair = (op.lower(), inv.upper()) - if pair not in seen: - seen.add(pair) - allowed.append(pair) - - if "invoice.imp.tem.view" in codes: - add_pair("imp", "TEM") - if "invoice.imp.def.view" in codes: - add_pair("imp", "DEF") - if "invoice.imp.cm.view" in codes: - add_pair("imp", "MEX") - if "invoice.imp.cr.view" in codes: - add_pair("imp", "CR") - if "invoice.imp.rep.view" in codes: - add_pair("imp", "REP") - - if "invoice.exp.rep.view" in codes: - add_pair("exp", "REPAR") - if "invoice.exp.donac.view" in codes: - add_pair("exp", "DONAC") - - if "invoice.exp.view" in codes: - for t in EXP_TYPES_FROM_GENERAL_EXPORT_VIEW: - add_pair("exp", t) - - return allowed diff --git a/backend/api/v1/modules/a76/invoices/routes.py b/backend/api/v1/modules/a76/invoices/routes.py deleted file mode 100644 index 68b0e2e3..00000000 --- a/backend/api/v1/modules/a76/invoices/routes.py +++ /dev/null @@ -1,781 +0,0 @@ -import logging -from typing import Dict, Any, Literal, Optional - -from core.config import settings -from core.database import get_core_db -from core.exceptions import BaseAPIException -from core.security import ( - _has_local_super_admin_role, - get_active_system, - get_current_user, - is_hub_admin, - validate_access_to_resource, -) -from fastapi import APIRouter, Depends, HTTPException, Query, Path, Request -from sqlalchemy import func, or_, and_ -from sqlalchemy.orm import Session - -from . import schemas, services, models -from .catalog_service import InvoiceCatalogService -from .permission_map import ( - build_allowed_types_from_view_permissions, - get_invoice_permission_base, -) - -# Create main router -router = APIRouter() -logger = logging.getLogger(__name__) - - -# --- RUTAS DE UTILIDAD --- - -@router.get("/invoices/creation-data", response_model=schemas.InvoiceCreationResponse) -def get_creation_data( - company_id: int = Query(..., description="Company ID"), - operation_type: schemas.OperationType = Query(..., description="Operation type for the new invoice"), - invoice_type: Optional[str] = Query( - None, - description="Invoice type key (required for import; optional for export — uses invoice.exp.create)", - ), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """Get consolidated data for creating a new invoice""" - try: - tenant_id = validate_access_to_resource(db, company_id, current_user) - ot = operation_type.value if hasattr(operation_type, "value") else operation_type - if ot == "imp": - if not invoice_type: - raise HTTPException( - status_code=422, - detail="invoice_type is required for import invoices", - ) - perm_base = get_invoice_permission_base(operation_type, invoice_type) - validate_access_to_resource( - db, - company_id, - current_user, - required_permissions=[f"{perm_base}.create"], - ) - else: - if invoice_type: - perm_base = get_invoice_permission_base(operation_type, invoice_type) - validate_access_to_resource( - db, - company_id, - current_user, - required_permissions=[f"{perm_base}.create"], - ) - else: - validate_access_to_resource( - db, - company_id, - current_user, - required_permissions=["invoice.exp.create"], - ) - return InvoiceCatalogService.get_creation_data(db, tenant_id, company_id) - except HTTPException: - raise - except BaseAPIException: - raise - except Exception as e: - logger.exception("get_creation_data failed: %s", e) - raise HTTPException(status_code=500, detail=f"Error al cargar datos de creación: {str(e)}") - -@router.get("/invoices/{invoice_id}/edition-data", response_model=schemas.InvoiceEditionResponse) -def get_edition_data( - invoice_id: int, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """Get consolidated data for editing an existing invoice""" - try: - tenant_id = validate_access_to_resource(db, company_id, current_user) - - # 1. Traer la factura para saber su tipo - invoice = services.InvoiceService.get_by_id(db, invoice_id, tenant_id, company_id) - if not invoice: - raise HTTPException(status_code=404, detail="Invoice not found") - - # 2. Validar permiso dinámico de Edición - perm_base = get_invoice_permission_base(invoice.operation_type, invoice.invoice_type) - validate_access_to_resource(db, company_id, current_user, required_permissions=[f"{perm_base}.edit"]) - - data = InvoiceCatalogService.get_edition_data(db, invoice_id, tenant_id, company_id) - return data - except HTTPException: - raise - except BaseAPIException: - raise - except Exception as e: - logger.exception("get_edition_data failed: %s", e) - raise HTTPException(status_code=500, detail=f"Error al cargar datos de edición: {str(e)}") - -@router.get("/invoices/remesa-suggestion", response_model=Dict[str, int]) -def get_remesa_suggestion( - pedimento_id: int = Query(..., description="Pedimento ID"), - company_id: int = Query(..., description="Company ID"), - operation_type: Optional[schemas.OperationType] = Query( - None, - description="Required when no invoice exists yet for this pedimento (context for permission)", - ), - invoice_type: Optional[str] = Query( - None, - description="Required when no invoice exists yet for this pedimento (context for permission)", - ), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """ - Sugiere siguiente remesa. Si ya hay facturas ligadas al pedimento, el permiso se infiere de ellas. - Si no hay facturas en compliance para ese pedimento, pasar operation_type e invoice_type del alta en curso. - """ - tenant_id = validate_access_to_resource(db, company_id, current_user) - - invoice_for_perm = ( - db.query(models.InvoiceHeader) - .join( - models.InvoiceComplianceMx, - models.InvoiceComplianceMx.invoice_id == models.InvoiceHeader.id, - ) - .filter( - models.InvoiceComplianceMx.pedimento_id == pedimento_id, - models.InvoiceHeader.tenant_id == tenant_id, - models.InvoiceHeader.company_id == company_id, - ) - .order_by(models.InvoiceHeader.id.desc()) - .first() - ) - if invoice_for_perm: - perm_base = get_invoice_permission_base( - invoice_for_perm.operation_type, invoice_for_perm.invoice_type - ) - validate_access_to_resource( - db, - company_id, - current_user, - required_permissions=[f"{perm_base}.edit"], - ) - elif operation_type is not None and invoice_type: - perm_base = get_invoice_permission_base(operation_type, invoice_type) - validate_access_to_resource( - db, - company_id, - current_user, - required_permissions=[f"{perm_base}.edit"], - ) - else: - raise HTTPException( - status_code=422, - detail="operation_type and invoice_type are required when no invoice is linked to this pedimento yet", - ) - - max_rem = ( - db.query(func.max(models.InvoiceComplianceMx.remesa)) - .filter( - models.InvoiceComplianceMx.pedimento_id == pedimento_id, - models.InvoiceComplianceMx.tenant_id == tenant_id, - models.InvoiceComplianceMx.company_id == company_id, - ) - .scalar() - ) - return {"next_remesa": int((max_rem or 0) + 1)} - - -# --- RUTAS CRUD MANUALES (Sustituyen al TenantCRUDRoutes por seguridad) --- - -@router.post("/invoices/", response_model=schemas.InvoiceHeaderResponse) -def create_invoice( - request: Request, - data: schemas.InvoiceHeaderCreate, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - try: - tenant_id = validate_access_to_resource(db, company_id, current_user) - - # Sistema autoritativo desde el contexto activo (header/cookie) - active_system = get_active_system(request) - if active_system: - data = data.model_copy(update={"system": active_system}) - - # Validamos usando los datos que vienen en el body (payload) - perm_base = get_invoice_permission_base(data.operation_type, data.invoice_type) - validate_access_to_resource(db, company_id, current_user, required_permissions=[f"{perm_base}.create"]) - - return services.InvoiceService.create(db, data, tenant_id, company_id) - except HTTPException: - raise - except BaseAPIException: - raise - except Exception as e: - logger.exception("create_invoice failed: %s", e) - raise HTTPException(status_code=500, detail=f"Error al guardar factura: {str(e)}") - -@router.get("/invoices/{invoice_id}", response_model=schemas.InvoiceHeaderResponse) -def get_invoice( - invoice_id: int, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - tenant_id = validate_access_to_resource(db, company_id, current_user) - invoice = services.InvoiceService.get_by_id(db, invoice_id, tenant_id, company_id) - if not invoice: - raise HTTPException(status_code=404, detail="Invoice not found") - - perm_base = get_invoice_permission_base(invoice.operation_type, invoice.invoice_type) - validate_access_to_resource(db, company_id, current_user, required_permissions=[f"{perm_base}.view"]) - - return invoice - -@router.put("/invoices/{invoice_id}", response_model=schemas.InvoiceHeaderResponse) -def update_invoice( - invoice_id: int, - data: schemas.InvoiceHeaderUpdate, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - tenant_id = validate_access_to_resource(db, company_id, current_user) - invoice = services.InvoiceService.get_by_id(db, invoice_id, tenant_id, company_id) - if not invoice: - raise HTTPException(status_code=404, detail="Invoice not found") - - perm_base = get_invoice_permission_base(invoice.operation_type, invoice.invoice_type) - validate_access_to_resource(db, company_id, current_user, required_permissions=[f"{perm_base}.edit"]) - - return services.InvoiceService.update(db, invoice_id, tenant_id, data, company_id) - -@router.delete("/invoices/{invoice_id}") -def delete_invoice( - invoice_id: int, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - tenant_id = validate_access_to_resource(db, company_id, current_user) - invoice = services.InvoiceService.get_by_id(db, invoice_id, tenant_id, company_id) - if not invoice: - raise HTTPException(status_code=404, detail="Invoice not found") - - perm_base = get_invoice_permission_base(invoice.operation_type, invoice.invoice_type) - validate_access_to_resource(db, company_id, current_user, required_permissions=[f"{perm_base}.delete"]) - - success = services.InvoiceService.delete(db, invoice_id, tenant_id, company_id) - return {"success": success} - -@router.post("/invoices/{invoice_id}/copy", response_model=schemas.InvoiceHeaderResponse) -def copy_invoice( - invoice_id: int = Path(..., description="ID de la factura a copiar"), - company_id: int = Query(..., description="Company ID"), - new_invoice_number: Optional[str] = Query(None, description="Número de factura para la copia"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - tenant_id = validate_access_to_resource(db, company_id, current_user) - original = services.InvoiceService.get_by_id(db, invoice_id, tenant_id, company_id) - if not original: - raise HTTPException(status_code=404, detail="Factura no encontrada") - - perm_base = get_invoice_permission_base(original.operation_type, original.invoice_type) - validate_access_to_resource( - db, company_id, current_user, required_permissions=[f"{perm_base}.create"] - ) - - try: - return services.InvoiceService.copy(db, invoice_id, tenant_id, company_id, new_invoice_number) - except HTTPException: - raise - except Exception as e: - logger.exception("copy_invoice failed: %s", e) - raise HTTPException(status_code=500, detail=f"Error al copiar factura: {str(e)}") - - -@router.post("/invoices/{invoice_id}/copy-header", response_model=schemas.InvoiceHeaderResponse) -def copy_invoice_header( - invoice_id: int = Path(..., description="ID de la factura"), - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - tenant_id = validate_access_to_resource(db, company_id, current_user) - original = services.InvoiceService.get_by_id(db, invoice_id, tenant_id, company_id) - if not original: - raise HTTPException(status_code=404, detail="Factura no encontrada") - perm_base = get_invoice_permission_base(original.operation_type, original.invoice_type) - validate_access_to_resource(db, company_id, current_user, required_permissions=[f"{perm_base}.create"]) - try: - return services.InvoiceService.copy_header_only(db, invoice_id, tenant_id, company_id) - except HTTPException: - raise - except Exception as e: - logger.exception("copy_invoice_header failed: %s", e) - raise HTTPException(status_code=500, detail=f"Error al copiar encabezado: {str(e)}") - - -@router.get("/invoices/{invoice_id}/export") -def export_invoice( - invoice_id: int = Path(..., description="ID de la factura"), - format: Literal['csv', 'xlsx', 'txt'] = Query(..., description="Formato: csv | xlsx | txt"), - company_id: int = Query(...), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - tenant_id = validate_access_to_resource(db, company_id, current_user) - invoice = services.InvoiceService.get_by_id(db, invoice_id, tenant_id, company_id) - if not invoice: - raise HTTPException(status_code=404, detail="Factura no encontrada") - perm_base = get_invoice_permission_base(invoice.operation_type, invoice.invoice_type) - validate_access_to_resource(db, company_id, current_user, required_permissions=[f"{perm_base}.view"]) - try: - return services.InvoiceService.export_items(db, invoice, format) - except HTTPException: - raise - except Exception as e: - logger.exception("export_invoice failed: %s", e) - raise HTTPException(status_code=500, detail=f"Error al exportar factura: {str(e)}") - - -@router.get("/invoices/{invoice_id}/interfaces/gm-transport") -def interface_gm_transport( - invoice_id: int = Path(..., description="ID de la factura"), - company_id: int = Query(...), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - tenant_id = validate_access_to_resource(db, company_id, current_user) - invoice = services.InvoiceService.get_by_id(db, invoice_id, tenant_id, company_id) - if not invoice: - raise HTTPException(status_code=404, detail="Factura no encontrada") - perm_base = get_invoice_permission_base(invoice.operation_type, invoice.invoice_type) - validate_access_to_resource(db, company_id, current_user, required_permissions=[f"{perm_base}.view"]) - try: - return services.InvoiceService.export_gm_transport(db, invoice) - except HTTPException: - raise - except Exception as e: - logger.exception("interface_gm_transport failed: %s", e) - raise HTTPException(status_code=500, detail=f"Error al generar interfaz GM Transport: {str(e)}") - - -@router.get("/invoices/{invoice_id}/interfaces/carta-porte") -def interface_carta_porte( - invoice_id: int = Path(..., description="ID de la factura"), - company_id: int = Query(...), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - tenant_id = validate_access_to_resource(db, company_id, current_user) - invoice = services.InvoiceService.get_by_id(db, invoice_id, tenant_id, company_id) - if not invoice: - raise HTTPException(status_code=404, detail="Factura no encontrada") - perm_base = get_invoice_permission_base(invoice.operation_type, invoice.invoice_type) - validate_access_to_resource(db, company_id, current_user, required_permissions=[f"{perm_base}.view"]) - try: - return services.InvoiceService.export_carta_porte(db, invoice) - except HTTPException: - raise - except Exception as e: - logger.exception("interface_carta_porte failed: %s", e) - raise HTTPException(status_code=500, detail=f"Error al generar interfaz Carta Porte: {str(e)}") - - -@router.get("/invoices/{invoice_id}/interfaces/tfc") -def interface_tfc( - invoice_id: int = Path(..., description="ID de la factura"), - company_id: int = Query(...), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - tenant_id = validate_access_to_resource(db, company_id, current_user) - invoice = services.InvoiceService.get_by_id(db, invoice_id, tenant_id, company_id) - if not invoice: - raise HTTPException(status_code=404, detail="Factura no encontrada") - perm_base = get_invoice_permission_base(invoice.operation_type, invoice.invoice_type) - validate_access_to_resource(db, company_id, current_user, required_permissions=[f"{perm_base}.view"]) - try: - return services.InvoiceService.export_tfc(db, invoice) - except HTTPException: - raise - except Exception as e: - logger.exception("interface_tfc failed: %s", e) - raise HTTPException(status_code=500, detail=f"Error al generar interfaz TFC: {str(e)}") - - -@router.get("/invoices/{invoice_id}/interfaces/carta-porte-consolidada") -def interface_carta_porte_consolidada( - invoice_id: int = Path(..., description="ID de la factura"), - company_id: int = Query(...), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - tenant_id = validate_access_to_resource(db, company_id, current_user) - invoice = services.InvoiceService.get_by_id(db, invoice_id, tenant_id, company_id) - if not invoice: - raise HTTPException(status_code=404, detail="Factura no encontrada") - perm_base = get_invoice_permission_base(invoice.operation_type, invoice.invoice_type) - validate_access_to_resource(db, company_id, current_user, required_permissions=[f"{perm_base}.view"]) - try: - return services.InvoiceService.export_carta_porte_consolidada(db, invoice) - except HTTPException: - raise - except Exception as e: - logger.exception("interface_carta_porte_consolidada failed: %s", e) - raise HTTPException(status_code=500, detail=f"Error al generar interfaz Carta Porte Consolidada: {str(e)}") - - -@router.get("/invoices/{invoice_id}/interfaces/aviso-cruce") -def interface_aviso_cruce( - invoice_id: int = Path(..., description="ID de la factura"), - company_id: int = Query(...), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - tenant_id = validate_access_to_resource(db, company_id, current_user) - invoice = services.InvoiceService.get_by_id(db, invoice_id, tenant_id, company_id) - if not invoice: - raise HTTPException(status_code=404, detail="Factura no encontrada") - perm_base = get_invoice_permission_base(invoice.operation_type, invoice.invoice_type) - validate_access_to_resource(db, company_id, current_user, required_permissions=[f"{perm_base}.view"]) - try: - return services.InvoiceService.export_aviso_cruce(db, invoice) - except HTTPException: - raise - except Exception as e: - logger.exception("interface_aviso_cruce failed: %s", e) - raise HTTPException(status_code=500, detail=f"Error al generar interfaz Aviso Cruce: {str(e)}") - - -@router.get("/invoices/{invoice_id}/interfaces/aaduanal-rs") -def interface_aaduanal_rs( - invoice_id: int = Path(..., description="ID de la factura"), - company_id: int = Query(...), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - tenant_id = validate_access_to_resource(db, company_id, current_user) - invoice = services.InvoiceService.get_by_id(db, invoice_id, tenant_id, company_id) - if not invoice: - raise HTTPException(status_code=404, detail="Factura no encontrada") - perm_base = get_invoice_permission_base(invoice.operation_type, invoice.invoice_type) - validate_access_to_resource(db, company_id, current_user, required_permissions=[f"{perm_base}.view"]) - try: - return services.InvoiceService.export_aaduanal_rs(db, invoice) - except HTTPException: - raise - except Exception as e: - logger.exception("interface_aaduanal_rs failed: %s", e) - raise HTTPException(status_code=500, detail=f"Error al generar interfaz AAduanal_RS: {str(e)}") - - -@router.get("/invoices/{invoice_id}/interfaces/caaarem") -def interface_caaarem( - invoice_id: int = Path(...), - company_id: int = Query(...), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - tenant_id = validate_access_to_resource(db, company_id, current_user) - invoice = services.InvoiceService.get_by_id(db, invoice_id, tenant_id, company_id) - if not invoice: - raise HTTPException(status_code=404, detail="Factura no encontrada") - perm_base = get_invoice_permission_base(invoice.operation_type, invoice.invoice_type) - validate_access_to_resource(db, company_id, current_user, required_permissions=[f"{perm_base}.view"]) - try: - return services.InvoiceService.export_caaarem(db, invoice) - except HTTPException: - raise - except Exception as e: - logger.exception("interface_caaarem failed: %s", e) - raise HTTPException(status_code=500, detail=f"Error al generar interfaz CAAAREM: {str(e)}") - - -@router.get("/invoices/{invoice_id}/interfaces/cp-genesis") -def interface_cp_genesis( - invoice_id: int = Path(...), - company_id: int = Query(...), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - tenant_id = validate_access_to_resource(db, company_id, current_user) - invoice = services.InvoiceService.get_by_id(db, invoice_id, tenant_id, company_id) - if not invoice: - raise HTTPException(status_code=404, detail="Factura no encontrada") - perm_base = get_invoice_permission_base(invoice.operation_type, invoice.invoice_type) - validate_access_to_resource(db, company_id, current_user, required_permissions=[f"{perm_base}.view"]) - try: - return services.InvoiceService.export_cp_genesis(db, invoice) - except HTTPException: - raise - except Exception as e: - logger.exception("interface_cp_genesis failed: %s", e) - raise HTTPException(status_code=500, detail=f"Error al generar interfaz Carta Porte Genesis: {str(e)}") - - -# --- RUTA DE LISTADO (FILTROS) --- - -@router.get("/invoices/", response_model=schemas.InvoiceHeaderListResponse) -def list_invoices( - request: Request, - company_id: int = Query(..., description="Company ID"), - page: int = Query(1, ge=1, description="Page number"), - page_size: int = Query(50, ge=1, le=200, description="Items per page"), - search: str = Query(None, description="Search by invoice number"), - status: Optional[Any] = Query(None, description="Filter by status"), - operation_type: schemas.OperationType = Query(None, description="Filter by operation type"), - invoice_type: str = Query(None, description="Filter by invoice type"), - manifest_number: str = Query(None, description="Filter by manifest number"), - pedimento: str = Query(None, description="Filter by pedimento"), - invoice_number: str = Query(None, description="Filter by invoice number"), - project_number: str = Query(None, description="Filter by project number"), - year: str = Query(None, description="Filter by year"), - sort_by: Optional[str] = Query(None, description="Column to sort by"), - sort_order: Optional[str] = Query("asc", pattern="^(asc|desc)$", description="Sort order (asc or desc)"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """ - List invoices with optional filters and granular permission enforcement. - """ - tenant_id = validate_access_to_resource(db, company_id, current_user) - - from api.v1.modules.core.permissions.service import PermissionService - - user_id = current_user.get("sub") or current_user.get("id") - # Bypass para "ver todo": hub_admin atestado por el Hub o super_admin local - # en la compañía. Se evita depender de claims del JWT crudo. - is_global_admin = is_hub_admin(current_user) or _has_local_super_admin_role( - db, user_id, company_id - ) - perm_service = PermissionService(db) - perm_codes = perm_service.get_user_permissions(user_id, company_id) - - if settings.ENVIRONMENT == "development": - user_name = current_user.get("preferred_username") or current_user.get( - "email", "Desconocido" - ) - logger.debug( - "[invoices.list] user=%s id=%s company=%s perms=%s", - user_name, - user_id, - company_id, - perm_codes, - ) - - allowed_filters = build_allowed_types_from_view_permissions(perm_codes) - - if not allowed_filters: - if is_global_admin: - logger.debug( - "[invoices.list] global admin without invoice view perms — unfiltered types fallback" - ) - allowed_filters = None - else: - raise HTTPException( - status_code=403, - detail="No tienes permisos para ver facturas en esta empresa", - ) - - try: - skip = (page - 1) * page_size - - # Combinar filtros de búsqueda con filtros granulares de permisos - filters = { - "invoice_number": invoice_number, - "status": status, - "operation_type": operation_type.value if operation_type else None, - "invoice_type": invoice_type, - "manifest_number": manifest_number, - "pedimento": pedimento, - "project_number": project_number, - "year": year, - "allowed_types": allowed_filters, - "system": get_active_system(request), - } - - filters = {k: v for k, v in filters.items() if v is not None} - - items, total = services.InvoiceService.get_all( - db, tenant_id, company_id, skip=skip, limit=page_size, filters=filters, sort_by=sort_by, sort_order=sort_order - ) - - return { - "items": items, - "total": total, - "page": page, - "page_size": page_size - } - except BaseAPIException: - raise - except Exception as e: - logger.exception("list_invoices failed: %s", e) - raise HTTPException(status_code=500, detail=f"Internal server error in invoices list: {str(e)}") - - -# --- RUTAS DE LOGÍSTICA --- - -@router.get("/invoices/{invoice_id}/logistics", response_model=list[schemas.InvoiceLogisticsResponse]) -def get_invoice_logistics( - invoice_id: int = Path(..., description="Invoice ID"), - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - tenant_id = validate_access_to_resource(db, company_id, current_user) - invoice = services.InvoiceService.get_by_id(db, invoice_id, tenant_id, company_id) - if not invoice: raise HTTPException(status_code=404, detail="Invoice not found") - - perm_base = get_invoice_permission_base(invoice.operation_type, invoice.invoice_type) - validate_access_to_resource(db, company_id, current_user, required_permissions=[f"{perm_base}.view"]) - - return services.InvoiceLogisticsService.get_all_by_invoice(db, invoice_id) - -@router.post("/invoices/{invoice_id}/logistics", response_model=schemas.InvoiceLogisticsResponse, status_code=201) -def create_invoice_logistics( - logistics_data: schemas.InvoiceLogisticsCreate, - invoice_id: int = Path(..., description="Invoice ID"), - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - tenant_id = validate_access_to_resource(db, company_id, current_user) - invoice = services.InvoiceService.get_by_id(db, invoice_id, tenant_id, company_id) - if not invoice: raise HTTPException(status_code=404, detail="Invoice not found") - - # Editar los hijos cuenta como editar la factura padre - perm_base = get_invoice_permission_base(invoice.operation_type, invoice.invoice_type) - validate_access_to_resource(db, company_id, current_user, required_permissions=[f"{perm_base}.edit"]) - - return services.InvoiceLogisticsService.create(db, logistics_data, invoice_id, tenant_id, company_id) - -@router.delete("/invoices/{invoice_id}/logistics/{logistics_id}", status_code=204) -def delete_invoice_logistics( - invoice_id: int = Path(..., description="Invoice ID"), - logistics_id: int = Path(..., description="Logistics ID"), - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - tenant_id = validate_access_to_resource(db, company_id, current_user) - invoice = services.InvoiceService.get_by_id(db, invoice_id, tenant_id, company_id) - if not invoice: raise HTTPException(status_code=404, detail="Invoice not found") - - perm_base = get_invoice_permission_base(invoice.operation_type, invoice.invoice_type) - validate_access_to_resource(db, company_id, current_user, required_permissions=[f"{perm_base}.edit"]) - - if not services.InvoiceLogisticsService.delete(db, logistics_id, invoice_id): - raise HTTPException(status_code=404, detail="Logistics entry not found") - return None - - -# --- RUTAS DE DETALLES DE VENTA (PARTIDAS) --- - -@router.get("/invoices/{invoice_id}/details", response_model=list[schemas.InvoiceSalesDetailsResponse]) -def get_invoice_details( - invoice_id: int = Path(..., description="Invoice ID"), - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - tenant_id = validate_access_to_resource(db, company_id, current_user) - invoice = services.InvoiceService.get_by_id(db, invoice_id, tenant_id, company_id) - if not invoice: raise HTTPException(status_code=404, detail="Invoice not found") - - perm_base = get_invoice_permission_base(invoice.operation_type, invoice.invoice_type) - validate_access_to_resource(db, company_id, current_user, required_permissions=[f"{perm_base}.view"]) - - return services.InvoiceSalesDetailsService.get_all_by_invoice(db, invoice_id) - -@router.post("/invoices/{invoice_id}/details", response_model=schemas.InvoiceSalesDetailsResponse, status_code=201) -def create_invoice_detail( - detail_data: schemas.InvoiceSalesDetailsCreate, - invoice_id: int = Path(..., description="Invoice ID"), - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - tenant_id = validate_access_to_resource(db, company_id, current_user) - invoice = services.InvoiceService.get_by_id(db, invoice_id, tenant_id, company_id) - if not invoice: raise HTTPException(status_code=404, detail="Invoice not found") - - perm_base = get_invoice_permission_base(invoice.operation_type, invoice.invoice_type) - validate_access_to_resource(db, company_id, current_user, required_permissions=[f"{perm_base}.edit"]) - - return services.InvoiceSalesDetailsService.create(db, detail_data, invoice_id, tenant_id, company_id) - -@router.delete("/invoices/{invoice_id}/details/{detail_id}", status_code=204) -def delete_invoice_detail( - invoice_id: int = Path(..., description="Invoice ID"), - detail_id: int = Path(..., description="Detail ID"), - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - tenant_id = validate_access_to_resource(db, company_id, current_user) - invoice = services.InvoiceService.get_by_id(db, invoice_id, tenant_id, company_id) - if not invoice: raise HTTPException(status_code=404, detail="Invoice not found") - - perm_base = get_invoice_permission_base(invoice.operation_type, invoice.invoice_type) - validate_access_to_resource(db, company_id, current_user, required_permissions=[f"{perm_base}.edit"]) - - if not services.InvoiceSalesDetailsService.delete(db, detail_id, invoice_id): - raise HTTPException(status_code=404, detail="Sales detail not found") - return None - - -# --- RUTAS DE COBRANZA (COLLECTIONS) --- - -@router.get("/invoices/{invoice_id}/collections", response_model=list[schemas.InvoiceCollectionsResponse]) -def get_invoice_collections( - invoice_id: int = Path(..., description="Invoice ID"), - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - tenant_id = validate_access_to_resource(db, company_id, current_user) - invoice = services.InvoiceService.get_by_id(db, invoice_id, tenant_id, company_id) - if not invoice: raise HTTPException(status_code=404, detail="Invoice not found") - - perm_base = get_invoice_permission_base(invoice.operation_type, invoice.invoice_type) - validate_access_to_resource(db, company_id, current_user, required_permissions=[f"{perm_base}.view"]) - - return services.InvoiceCollectionsService.get_all_by_invoice(db, invoice_id) - -@router.post("/invoices/{invoice_id}/collections", response_model=schemas.InvoiceCollectionsResponse, status_code=201) -def create_invoice_collection( - collection_data: schemas.InvoiceCollectionsCreate, - invoice_id: int = Path(..., description="Invoice ID"), - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - tenant_id = validate_access_to_resource(db, company_id, current_user) - invoice = services.InvoiceService.get_by_id(db, invoice_id, tenant_id, company_id) - if not invoice: raise HTTPException(status_code=404, detail="Invoice not found") - - perm_base = get_invoice_permission_base(invoice.operation_type, invoice.invoice_type) - validate_access_to_resource(db, company_id, current_user, required_permissions=[f"{perm_base}.edit"]) - - return services.InvoiceCollectionsService.create(db, collection_data, invoice_id, tenant_id, company_id) - -@router.delete("/invoices/{invoice_id}/collections/{collection_id}", status_code=204) -def delete_invoice_collection( - invoice_id: int = Path(..., description="Invoice ID"), - collection_id: int = Path(..., description="Collection ID"), - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - tenant_id = validate_access_to_resource(db, company_id, current_user) - invoice = services.InvoiceService.get_by_id(db, invoice_id, tenant_id, company_id) - if not invoice: raise HTTPException(status_code=404, detail="Invoice not found") - - perm_base = get_invoice_permission_base(invoice.operation_type, invoice.invoice_type) - validate_access_to_resource(db, company_id, current_user, required_permissions=[f"{perm_base}.edit"]) - - if not services.InvoiceCollectionsService.delete(db, collection_id, invoice_id): - raise HTTPException(status_code=404, detail="Collection not found") - return None \ No newline at end of file diff --git a/backend/api/v1/modules/a76/invoices/schemas.py b/backend/api/v1/modules/a76/invoices/schemas.py deleted file mode 100644 index 8fcc1ff9..00000000 --- a/backend/api/v1/modules/a76/invoices/schemas.py +++ /dev/null @@ -1,664 +0,0 @@ -from typing import Literal, Optional, List -from datetime import datetime, date -from decimal import Decimal -from pydantic import BaseModel, Field, field_validator -from .models import ( - DestinationOriginCove, - InvoiceStatus, - OperationType, - Currency, - TransportType, - WeightUnit, -) - - -# --- Base Schemas --- -class InvoiceHeaderBase(BaseModel): - """Base fields for Invoice Header""" - - system: Optional[str] = Field(None, max_length=12, description="System of origin") - operation_type: Optional[OperationType] = Field( - ..., description="Operation type: imp/exp/sm/ctm" - ) - - @field_validator("operation_type", mode="before") - @classmethod - def normalize_operation_type(cls, v): - """Accept DB string (e.g. 'IMP') and coerce to enum value ('imp').""" - if isinstance(v, str): - return v.lower() if v else v - return v - invoice_type: Optional[str] = Field( - None, max_length=5, description="Invoice type key" - ) - document_type: Optional[str] = Field( - None, max_length=3, description="Document type (Regimen Aduanero)" - ) - invoice_number: Optional[str] = Field( - None, max_length=100, description="Invoice number" - ) - project_number: Optional[str] = Field( - None, max_length=14, description="Project number" - ) - purchase_order: Optional[str] = Field( - None, max_length=50, description="Purchase order" - ) - related_doc_id: Optional[int] = Field( - None, description="Related document ID for rectifications" - ) - alternate_invoice: Optional[str] = Field( - None, max_length=99, description="Alternate invoice" - ) - invoice_ref: Optional[str] = Field( - None, max_length=19, description="Invoice reference" - ) - proforma_number: Optional[str] = Field( - None, max_length=20, description="Proforma number" - ) - invoice_date: date = Field(..., description="Invoice date") - emission_date: Optional[date] = Field(None, description="Emission date") - status: Optional[InvoiceStatus] = Field(None, description="Status: pending, processed, reversed") - - @field_validator("status", mode="before") - @classmethod - def normalize_status(cls, v): - """Coerce legacy boolean strings ('false'/'true') to InvoiceStatus.""" - if v is None: - return v - if isinstance(v, str): - v_lower = v.lower() - if v_lower == "false": - return InvoiceStatus.PENDING.value - if v_lower == "true": - return InvoiceStatus.PROCESSED.value - return v - processed_date: Optional[datetime] = Field(None, description="Update date") - who_processed: Optional[str] = Field(None, max_length=100, description="Who processed") - capture_user: Optional[str] = Field(None, max_length=100, description="Capture user") - traffic_light_status: Optional[str] = Field( - None, max_length=50, description="Traffic light status" - ) - process_log: Optional[str] = Field( - None, max_length=300, description="Processing log" - ) - status_rec: Optional[int] = Field(None, description="Reception status") - status_rep: Optional[str] = Field( - None, max_length=2, description="Report status" - ) - observation_es: Optional[str] = Field(None, description="Observations in Spanish") - observation_en: Optional[str] = Field(None, description="Observations in English") - comments_status: Optional[str] = Field(None, description="Comments status") - vu_observations: Optional[str] = Field( - None, max_length=500, description="VUCEM observations" - ) - cfdi_uuid: Optional[str] = Field(None, max_length=100, description="CFDI UUID") - path_pdf: Optional[str] = Field( - None, max_length=500, description="Path to PDF file" - ) - path_xml: Optional[str] = Field( - None, max_length=500, description="Path to XML file" - ) - subcompany: Optional[str] = Field(None, max_length=5, description="Subcompany") - party_count: Optional[int] = Field(None, description="Quantity of parties") - generate_id: Optional[bool] = Field(False, description="Generate ID") - generate_desc_parties: Optional[str] = Field( - None, max_length=12, description="Generate description of parties" - ) - apply_manual_discount: Optional[bool] = Field( - False, description="Apply manual discount" - ) - is_bulk: Optional[bool] = Field(None, description="Is bulk") - download_substance: Optional[bool] = Field(None, description="Download substance") - download_class: Optional[bool] = Field(None, description="Download class") - download_def: Optional[bool] = Field(None, description="Definitive download") - payment_terms: Optional[str] = Field( - None, max_length=200, description="Payment terms" - ) - handling_fees: Optional[Decimal] = Field(None, description="Handling fees") - option_iv18: Optional[str] = Field(None, max_length=50, description="Option IV18") - enajenation_goods: Optional[bool] = Field(None, description="Enajenation of goods") - - -class InvoiceComplianceMxBase(BaseModel): - """Base fields for Compliance MX""" - - pedimento_id: Optional[int] = Field(None, description="Pedimento id") - pedimento_r1: Optional[int] = Field(None, description="Pedimento id (R1)") - pedimento_k1: Optional[int] = Field(None, description="Pedimento id (K1)") - remesa: Optional[int] = Field(None, description="Remesa") - aduana: Optional[str] = Field(None, max_length=5, description="Customs office") - port_of_entry: Optional[str] = Field( - None, max_length=6, description="Port of entry" - ) - destination: Optional[str] = Field( - None, max_length=3, description="Destination code" - ) - manifest_number: Optional[str] = Field( - None, max_length=15, description="Manifest number" - ) - provider_header: Optional[str] = Field(None, max_length=20, description="Provider header") - provider_id: Optional[int] = Field(None, description="Provider ID") - sold_to_header: Optional[str] = Field(None, max_length=20, description="Sold to header") - sold_to_id: Optional[int] = Field(None, description="Sold to ID") - shipped_to_header: Optional[str] = Field(None, max_length=20, description="Shipped to header") - shipped_to_id: Optional[int] = Field(None, description="Shipped to ID") - shipped_by_header: Optional[str] = Field( - None, max_length=20, description="Shipped by header" - ) - shipped_by_id: Optional[int] = Field(None, description="Shipped by ID") - customs_broker_id: Optional[int] = Field(None, description="Customs broker ID (null for MEX)") - customs_broker_us_id: Optional[int] = Field( - None, description="US customs broker ID" - ) - broker_invoice_num: Optional[str] = Field( - None, max_length=20, description="Broker invoice number" - ) - broker_invoice_date: Optional[date] = Field(None, description="Broker invoice date") - is_mixed: Optional[bool] = Field(False, description="Is mixed operation") - waste_type: Optional[str] = Field(None, max_length=1, description="Waste type") - scrap_type: Optional[str] = Field(None, max_length=1, description="Scrap type") - appendix_17: Optional[int] = Field(None, description="Appendix 17") - is_regime_change: Optional[bool] = Field(False, description="Is regime change") - which_exchange_rate: Optional[str] = Field( - None, max_length=5, description="Which exchange rate" - ) - rule_3121_parties_ii: Optional[bool] = Field( - False, description="Is Rule 3.1.21 Parties II" - ) - value_method: Optional[str] = Field(None, max_length=2, description="Value method") - act_value: Optional[str] = Field(None, max_length=5, description="Act value") - is_pedimento_pending: Optional[bool] = Field(False, description="Is pedimento pending") - is_owner_of_goods: Optional[bool] = Field(False, description="Is owner of goods") - generate_balances: Optional[bool] = Field(False, description="Generate balances") - was_reviewed_by_company: Optional[bool] = Field( - None, description="Was reviewed by company" - ) - edocument: Optional[str] = Field(None, max_length=50, description="E-document") - electronic_signature: Optional[str] = Field( - None, max_length=999, description="Electronic signature" - ) - certificate_number: Optional[str] = Field( - None, max_length=99, description="Certificate number" - ) - niu_number: Optional[str] = Field(None, max_length=19, description="NIU number") - bill_of_lading_count: Optional[str] = Field( - None, max_length=12, description="Bill of lading count" - ) - addendum_vu: Optional[str] = Field( - None, max_length=204, description="VUCEM addendum" - ) - origin_destination_cove: Optional[DestinationOriginCove] = Field( - "franja_front_norte", max_length=20, description="Origin/Destination COVE" - ) - vucem_operation_num: Optional[str] = Field( - None, max_length=19, description="VUCEM operation number" - ) - customs_person_line: Optional[int] = Field(None, description="Customs person line") - contingency_mode: Optional[bool] = Field(None, description="Contingency mode") - enclosure: Optional[str] = Field(None, max_length=4, description="Enclosure") - guide_type_to_identify: Optional[str] = Field( - None, max_length=1, description="Guide type to identify" - ) - location: Optional[str] = Field(None, max_length=200, description="Location") - dot_code: Optional[str] = Field(None, max_length=20, description="DOT code") - subdivision: Optional[str] = Field(None, max_length=20, description="Subdivision") - acts_as: Optional[str] = Field(None, max_length=20, description="Acts as") - movement_type: Optional[str] = Field( - None, max_length=31, description="Movement type" - ) - office_document: Optional[str] = Field( - None, max_length=30, description="Office document" - ) - reason_export: Optional[str] = Field( - None, max_length=1, description="Reason for export" - ) - signature_key: Optional[str] = Field( - None, max_length=100, description="Signature key" - ) - sem_id: Optional[int] = Field(None, description="SEM ID") - - -class InvoiceFinancialsBase(BaseModel): - """Base fields for Financials""" - - currency: Optional[Currency] = Field(None, max_length=7, description="Currency code") - currency_type: Optional[str] = Field("USD", description="Currency type") - exchange_rate: Optional[Decimal] = Field(0.00, description="Exchange rate") - exchange_rate_mm: Optional[Decimal] = Field( - 0.00, description="Exchange rate currency to currency" - ) - value_mn: Optional[Decimal] = Field(0.00, description="Value in MXN") - value_me: Optional[Decimal] = Field(0.00, description="Value in foreign currency") - value_mc: Optional[Decimal] = Field(0.00, description="Value in third currency") - customs_value_mn: Optional[Decimal] = Field( - 0.00, description="Customs value in MXN" - ) - customs_value_me: Optional[Decimal] = Field( - 0.00, description="Customs value in foreign currency" - ) - raw_material_value_mn: Optional[Decimal] = Field( - 0.00, description="Raw material value in MXN" - ) - raw_material_value_me: Optional[Decimal] = Field( - 0.00, description="Raw material value in foreign currency" - ) - aggregate_value_mn: Optional[Decimal] = Field( - 0.00, description="Aggregate value in MXN" - ) - aggregate_value_me: Optional[Decimal] = Field( - 0.00, description="Aggregate value in foreign currency" - ) - aggregate_value_mc: Optional[Decimal] = Field( - 0.00, description="Aggregate value in third currency" - ) - mexican_value_mn: Optional[Decimal] = Field( - 0.00, description="Mexican merchandise value in MXN" - ) - mexican_value_me: Optional[Decimal] = Field( - 0.00, description="Mexican merchandise value in foreign currency" - ) - mexican_value_mc: Optional[Decimal] = Field( - 0.00, description="Mexican merchandise value in third currency" - ) - national_packaging_mn: Optional[Decimal] = Field( - 0.00, description="National packaging in MXN" - ) - national_packaging_me: Optional[Decimal] = Field( - 0.00, description="National packaging in foreign currency" - ) - national_packaging_mc: Optional[Decimal] = Field( - 0.00, description="National packaging in third currency" - ) - freight: Optional[Decimal] = Field(0.00, description="Freight cost") - insurance: Optional[Decimal] = Field(0.00, description="Insurance cost") - insurance_value: Optional[Decimal] = Field(0.00, description="Insurance value") - packaging: Optional[Decimal] = Field(0.00, description="Packaging") - other_increments: Optional[Decimal] = Field(0.00, description="Other increments") - other_deductibles: Optional[Decimal] = Field(0.00, description="Other deductibles") - total_increments_mn: Optional[Decimal] = Field( - 0.00, description="Total increments in MXN" - ) - total_increments_me: Optional[Decimal] = Field( - 0.00, description="Total increments in foreign currency" - ) - iva_mn: Optional[Decimal] = Field(0.00, description="IVA in MXN") - iva_me: Optional[Decimal] = Field(0.00, description="IVA in foreign currency") - iva_mc: Optional[Decimal] = Field(0.00, description="IVA in third currency") - iva_factor: Optional[Decimal] = Field(0.00, description="IVA factor") - tax_value_me: Optional[Decimal] = Field( - 0.00, description="Tax value in foreign currency" - ) - seal_value_2500: Optional[bool] = Field(None, description="Seal value 2500") - total_quantity: Optional[Decimal] = Field(0.00, description="Total quantity") - total_packages: Optional[int] = Field(0, description="Total packages") - gross_weight: Optional[Decimal] = Field(0.00, description="Gross weight") - net_weight: Optional[Decimal] = Field(0.00, description="Net weight") - bundle_count: Optional[int] = Field(0, description="Bundle count") - weight_factor: Optional[Decimal] = Field(0.00, description="Weight factor") - - -class InvoiceLogisticsBase(BaseModel): - """Base fields for Logistics""" - carrier_id: Optional[str] = Field(None, max_length=10, description="Carrier code (front-end key)") - carrier_int_id: Optional[int] = Field( - None, description="Internal carrier integer ID (mapped from carrier_id key)" - ) - transport_id: Optional[str] = Field(None, max_length=10, description="Transport ID") - transport_int_id: Optional[int] = Field( - None, description="Internal vehicle integer ID (mapped from transport_id key)" - ) - transport_us_id: Optional[str] = Field( - None, max_length=10, description="US transport ID" - ) - transport_type: TransportType = Field( - "none", max_length=15, description="Transport type" - ) - transport_num: Optional[str] = Field( - None, max_length=20, description="Transport number" - ) - transport_mode: Optional[str] = Field( - 30, max_length=15, description="Transport mode" - ) - driver_name: Optional[str] = Field(None, max_length=80, description="Driver name") - is_rail: Optional[bool] = Field(False, description="Is rail transport") - rail_id: Optional[str] = Field(None, max_length=31, description="Rail ID") - vehicle_num: Optional[str] = Field( - None, max_length=20, description="Vehicle number" - ) - license_plate: Optional[str] = Field( - None, max_length=20, description="License plate" - ) - license_plate_complete: Optional[str] = Field( - None, max_length=40, description="Complete license plate" - ) - trailer_num: Optional[str] = Field( - None, max_length=20, description="Trailer number" - ) - trailer_int_id: Optional[int] = Field( - None, description="Internal trailer integer ID (mapped from trailer_num key)" - ) - seal_number: Optional[str] = Field(None, max_length=15, description="Seal number") - guide_number: Optional[str] = Field(None, max_length=20, description="Guide number") - bill_number: Optional[str] = Field(None, max_length=15, description="Bill number") - reference_number: Optional[str] = Field( - None, max_length=14, description="Reference number" - ) - shipment_number: Optional[str] = Field( - None, max_length=19, description="Shipment number" - ) - incoterm: Optional[str] = Field(None, max_length=5, description="Incoterm") - identifier_1: Optional[str] = Field(None, max_length=2, description="Identifier 1") - complement_1: Optional[str] = Field(None, max_length=30, description="Complement 1") - identifier_2: Optional[str] = Field(None, max_length=2, description="Identifier 2") - complement_2: Optional[str] = Field(None, max_length=30, description="Complement 2") - weight_type: WeightUnit = Field( - default="kgs", max_length=3, description="Weight type" - ) - - @field_validator("weight_type", mode="before") - @classmethod - def normalize_weight_type(cls, v): - if isinstance(v, str): - return v.lower() - return v - container_types: Optional[str] = Field( - None, max_length=500, description="Container types" - ) - vehicle_data: Optional[str] = Field( - None, max_length=500, description="Vehicle data" - ) - origin_location: Optional[str] = Field( - None, max_length=200, description="Origin location" - ) - destination_location: Optional[str] = Field( - None, max_length=200, description="Destination location" - ) - transport_itinerary: Optional[str] = Field( - None, max_length=1000, description="Transport itinerary" - ) - destination_goods: Optional[str] = Field( - None, max_length=50, description="Destination of goods" - ) - entry_exit_date: Optional[date] = Field(None, description="Entry/Exit date") - delivery_date: Optional[date] = Field(None, description="Delivery date") - delivered_status: Optional[bool] = Field(False, description="Delivered status") - received_by: Optional[str] = Field(None, max_length=50, description="Received by") - payment_date: Optional[date] = Field(None, description="Payment date") - payment_receipt_num: Optional[str] = Field( - None, max_length=20, description="Payment receipt number" - ) - is_ctm_process: Optional[bool] = Field(False, description="Is CTM process") - # Continuation Tab Fields - equipment_reviewed: Optional[bool] = Field(False, description="Equipment reviewed") - is_subdivision: Optional[bool] = Field(False, description="Is subdivision") - acts_as_cd: Optional[bool] = Field(False, description="Acts as CD") - pedimento_arrived: Optional[bool] = Field(False, description="Pedimento arrived") - green_light_mx: Optional[bool] = Field(False, description="Green light Mexico") - green_light_us: Optional[bool] = Field(False, description="Green light USA") - red_light_mx: Optional[bool] = Field(False, description="Red light Mexico") - red_light_us: Optional[bool] = Field(False, description="Red light USA") - - -class InvoiceSalesDetailsBase(BaseModel): - """Base fields for Sales Details""" - - line_number: int = Field(..., description="Line number") - sales_order: Optional[str] = Field(None, max_length=20, description="Sales order") - colors_description: Optional[str] = Field( - None, max_length=49, description="Colors description" - ) - square_color_code: Optional[str] = Field( - None, max_length=1, description="Square color code" - ) - line_bundles: Optional[int] = Field(None, description="Line bundles count") - - -class InvoiceCollectionsBase(BaseModel): - """Base fields for Collections""" - - line_number: int = Field(..., description="Line number") - invoice_number: Optional[str] = Field( - None, max_length=15, description="Invoice number" - ) - concept: Optional[str] = Field(None, max_length=100, description="Concept") - - -# --- Create Schemas --- - - -class InvoiceComplianceMxCreate(InvoiceComplianceMxBase): - """Schema for creating Compliance MX""" - - pass - - -class InvoiceFinancialsCreate(InvoiceFinancialsBase): - """Schema for creating Financials""" - - pass - - -class InvoiceLogisticsCreate(InvoiceLogisticsBase): - """Schema for creating Logistics""" - - pass - - -class InvoiceSalesDetailsCreate(InvoiceSalesDetailsBase): - """Schema for creating Sales Details""" - - pass - - -class InvoiceCollectionsCreate(InvoiceCollectionsBase): - """Schema for creating Collections""" - - pass - - -class InvoiceHeaderCreate(InvoiceHeaderBase): - """Schema for creating Invoice Header with nested relations""" - - status: Optional[InvoiceStatus] = Field(InvoiceStatus.PENDING, description="Status: pending, processed, reversed") - - compliance_mx: Optional[InvoiceComplianceMxCreate] = None - financials: Optional[InvoiceFinancialsCreate] = None - logistics: Optional[InvoiceLogisticsCreate] = None - details: Optional[List[InvoiceSalesDetailsCreate]] = None - collections: Optional[List[InvoiceCollectionsCreate]] = None - - -# --- Update Schemas --- - - - -class InvoiceComplianceMxUpdate(InvoiceComplianceMxBase): - """Schema for updating Compliance MX""" - - is_pedimento_pending: Optional[bool] = None - - -class InvoiceFinancialsUpdate(InvoiceFinancialsBase): - """Schema for updating Financials""" - - pass - - -class InvoiceLogisticsUpdate(InvoiceLogisticsBase): - """Schema for updating Logistics""" - - pass - - -class InvoiceSalesDetailsUpdate(InvoiceSalesDetailsBase): - """Schema for updating Sales Details""" - - line_number: Optional[int] = None - - -class InvoiceCollectionsUpdate(InvoiceCollectionsBase): - """Schema for updating Collections""" - - line_number: Optional[int] = None - - -class InvoiceHeaderUpdate(InvoiceHeaderBase): - """Schema for updating Invoice Header with nested relations""" - - id: int - document_type: Optional[str] = None - invoice_date: Optional[date] = None - operation_type: Optional[OperationType] = None - - compliance_mx: Optional[InvoiceComplianceMxUpdate] = None - financials: Optional[InvoiceFinancialsUpdate] = None - logistics: Optional[InvoiceLogisticsUpdate] = None - details: Optional[List[InvoiceSalesDetailsUpdate]] = None - collections: Optional[List[InvoiceCollectionsUpdate]] = None - - -# --- Response Schemas --- - - -class PedimentoBasicInfo(BaseModel): - """Basic Pedimento information for Invoice response""" - - pedimento_number: Optional[str] = None - pedimento_code: Optional[str] = None - customs_office: Optional[str] = None - license: Optional[str] = None - year: Optional[str] = None - - class Config: - from_attributes = True - - -class InvoiceComplianceMxResponse(InvoiceComplianceMxBase): - """Schema for Compliance MX response""" - - invoice_id: int - pedimento: Optional[PedimentoBasicInfo] = None - - class Config: - from_attributes = True - - -class InvoiceFinancialsResponse(InvoiceFinancialsBase): - """Schema for Financials response""" - - id: int - invoice_id: int - - class Config: - from_attributes = True - - -class InvoiceLogisticsResponse(InvoiceLogisticsBase): - """Schema for Logistics response""" - - id: int - invoice_id: int - - class Config: - from_attributes = True - - -class InvoiceSalesDetailsResponse(InvoiceSalesDetailsBase): - """Schema for Sales Details response""" - - id: int - invoice_id: int - - class Config: - from_attributes = True - - -class InvoiceCollectionsResponse(InvoiceCollectionsBase): - """Schema for Collections response""" - - id: int - invoice_id: int - - class Config: - from_attributes = True - - -class InvoiceHeaderResponse(InvoiceHeaderBase): - """Schema for Invoice Header response with nested relations""" - - id: int - capture_date: datetime - compliance_mx: Optional[InvoiceComplianceMxResponse] = None - financials: Optional[InvoiceFinancialsResponse] = None - logistics: Optional[InvoiceLogisticsResponse] = None - details: Optional[List[InvoiceSalesDetailsResponse]] = [] - collections: Optional[List[InvoiceCollectionsResponse]] = [] - - class Config: - from_attributes = True - - -class InvoiceHeaderListResponse(BaseModel): - items: List[InvoiceHeaderResponse] - total: int - page: int - -# --- Consolidated Response Schemas --- - -# Import necessary DTOs from other modules -from api.v1.modules.public.reference_data.invoice_types.dto import InvoiceTypeDTO -from api.v1.modules.a76.customs_brokers.dto import CustomsBrokerResponseDTO -from api.v1.modules.a76.clients_and_providers.dto import ClientProviderResponseDTO -from api.v1.modules.public.reference_data.currency_types.dto import CurrencyTypeDTO -from api.v1.modules.public.reference_data.transport_types.dto import TransportTypeDTO -from api.v1.modules.public.reference_data.customs_sections.dto import CustomsSectionDTO -from api.v1.modules.public.reference_data.incoterms.dto import IncotermDTO -from api.v1.modules.public.reference_data.valuation_methods.dto import ValuationMethodDTO -from api.v1.modules.public.reference_data.transport_modes.dto import TransportModeDTO -from api.v1.modules.public.reference_data.code_pedimento_regimens.dto import CodePedimentoRegimenDTO - -# TODO: Check if these paths are correct or need adjustment based on actual file locations -# Using Any for now for potentially complex or unverified paths to avoid immediate ImportErrors -# Detailed verification is needed for: -# - TransporterDTO -# - VehicleDTO -# - DriverDTO -# - TrailerDTO -# - SealResponseDTO -# - PedimentoDTO - -class InvoiceCatalogsResponse(BaseModel): - """Consolidated response for all catalogs needed in Invoice Create/Edit views""" - invoice_types: List[InvoiceTypeDTO] = [] - customs_brokers: List[CustomsBrokerResponseDTO] = [] - clients: List[ClientProviderResponseDTO] = [] - providers: List[ClientProviderResponseDTO] = [] - currency_types: List[CurrencyTypeDTO] = [] - transport_types: List[TransportTypeDTO] = [] - transporters: List[dict] = [] # Placeholder, refine with actual DTO - vehicles: List[dict] = [] # Placeholder, refine with actual DTO - drivers: List[dict] = [] # Placeholder, refine with actual DTO - trailers: List[dict] = [] # Placeholder, refine with actual DTO - customs_sections: List[CustomsSectionDTO] = [] - code_pedimento_regimens: List[CodePedimentoRegimenDTO] = [] - seals: List[dict] = [] # Placeholder, refine with actual DTO - incoterms: List[IncotermDTO] = [] - valuation_methods: List[ValuationMethodDTO] = [] - pedimentos: List[dict] = [] # Placeholder, refine with actual DTO - transport_modes: List[TransportModeDTO] = [] - default_settings: Optional[dict] = None - -class InvoiceCreationResponse(InvoiceCatalogsResponse): - """Response for Invoice Creation View""" - is_create: bool = True - invoice: Optional[dict] = None # Should be null for creation - invoice_id: Optional[int] = None - filters: Optional[dict] = None # Pre-filled filters if any - -class InvoiceEditionResponse(InvoiceCatalogsResponse): - """Response for Invoice Edition View""" - is_create: bool = False - invoice: InvoiceHeaderResponse - invoice_id: int - filters: Optional[dict] = None - diff --git a/backend/api/v1/modules/a76/invoices/services.py b/backend/api/v1/modules/a76/invoices/services.py deleted file mode 100644 index d1037983..00000000 --- a/backend/api/v1/modules/a76/invoices/services.py +++ /dev/null @@ -1,2547 +0,0 @@ -import traceback -from typing import Optional, List, Tuple -from sqlalchemy.orm import Session -from sqlalchemy import and_, func, or_ -from core.exceptions import ErrorCollector, DuplicateResourceException -from core.context import get_user_context -from api.v1.modules.a76.audit_log.models import AuditLog -from api.v1.modules.a76.audit_log.services.service import AuditService -from .common.mappers import clean_dict -from .imports.validators.create import validate_create as validate_create_import -from .imports.validators.update import validate_update as validate_update_import -from .exports.validators.create import validate_create as validate_create_export -from .exports.validators.update import validate_update as validate_update_export -from .common.common_validators import invoice_exists -from api.v1.modules.a76.items.models import LineItem -from api.v1.modules.a76.items.line_financials.models import LineFinancial -from api.v1.modules.a76.items.line_quantities.models import LineQuantity -from api.v1.modules.a76.items.line_customs.models import LineCustom -from api.v1.modules.a76.items.line_descriptions.models import LineDescription -from api.v1.modules.a76.items.line_references.models import LineReference -from api.v1.modules.a76.items.series.models import Serie -from api.v1.modules.a76.general_catalogs.identifiers.models import IdentifierDetail -from . import models, schemas - - -def _autofill_transport_int_ids( - db: Session, - logistics_target, - tenant_id: int, - company_id: int, -) -> None: - """ - Ensures invoice logistics internal int IDs are populated when catalog rows exist. - - String keys (carrier_id, transport_id, trailer_num) are matched to existing catalog - rows for the tenant/company; no placeholder rows are created. Invalid references must - be rejected by validate_common before persist. - """ - - # Local imports to avoid circular dependencies. - from api.v1.modules.a76.transportation.transporters.models import Transporter - from api.v1.modules.a76.transportation.vehicles.models import Vehicle - from api.v1.modules.a76.transportation.trailers.models import Trailer - - def _get(field: str): - if isinstance(logistics_target, dict): - return logistics_target.get(field) - return getattr(logistics_target, field, None) - - def _set(field: str, value): - if isinstance(logistics_target, dict): - logistics_target[field] = value - else: - setattr(logistics_target, field, value) - - carrier_code = _get("carrier_id") - carrier_int_id = _get("carrier_int_id") - if carrier_code and carrier_int_id is None: - transporter_obj = ( - db.query(Transporter) - .filter( - Transporter.transporter_key == carrier_code, - Transporter.tenant_id == tenant_id, - Transporter.company_id == company_id, - ) - .first() - ) - if transporter_obj: - _set("carrier_int_id", transporter_obj.transporter_id) - - transport_code = _get("transport_id") - transport_int_id = _get("transport_int_id") - if transport_code and transport_int_id is None: - vehicle_obj = ( - db.query(Vehicle) - .filter( - func.upper(Vehicle.vehicle_key) == str(transport_code).strip().upper(), - Vehicle.tenant_id == tenant_id, - Vehicle.company_id == company_id, - ) - .first() - ) - if vehicle_obj: - _set("transport_int_id", vehicle_obj.vehicle_id) - - trailer_code = _get("trailer_num") - trailer_int_id = _get("trailer_int_id") - if trailer_code and trailer_int_id is None: - trailer_obj = ( - db.query(Trailer) - .filter( - func.upper(Trailer.trailer_number) - == str(trailer_code).strip().upper(), - Trailer.tenant_id == tenant_id, - Trailer.company_id == company_id, - ) - .first() - ) - if trailer_obj: - _set("trailer_int_id", trailer_obj.trailer_id) - - # Placa: si no vino valor, tomar del vehículo y si no del remolque (catálogo) - lp = _get("license_plate") - if lp is None or str(lp).strip() == "": - veh = None - tv_id = _get("transport_int_id") - tv_code = _get("transport_id") - if tv_id is not None: - veh = ( - db.query(Vehicle) - .filter( - Vehicle.vehicle_id == tv_id, - Vehicle.tenant_id == tenant_id, - Vehicle.company_id == company_id, - ) - .first() - ) - elif tv_code: - veh = ( - db.query(Vehicle) - .filter( - func.upper(Vehicle.vehicle_key) == str(tv_code).strip().upper(), - Vehicle.tenant_id == tenant_id, - Vehicle.company_id == company_id, - ) - .first() - ) - if veh and getattr(veh, "plate_number", None): - _set("license_plate", veh.plate_number) - else: - tr = None - tr_id = _get("trailer_int_id") - tr_code = _get("trailer_num") - if tr_id is not None: - tr = ( - db.query(Trailer) - .filter( - Trailer.trailer_id == tr_id, - Trailer.tenant_id == tenant_id, - Trailer.company_id == company_id, - ) - .first() - ) - elif tr_code: - tr = ( - db.query(Trailer) - .filter( - func.upper(Trailer.trailer_number) - == str(tr_code).strip().upper(), - Trailer.tenant_id == tenant_id, - Trailer.company_id == company_id, - ) - .first() - ) - if tr and getattr(tr, "plate_number", None): - _set("license_plate", tr.plate_number) - - -def _get_current_username() -> str: - """Helper to get current username from context or fallback to System""" - try: - context = get_user_context() - if context: - # Token usually has 'preferred_username' or 'name' or 'sub' - username = ( - context.get("preferred_username") - or context.get("email") - or context.get("sub") - or "System" - ) - print(f"DEBUG: _get_current_username found context: {username}") - return username - except Exception: - pass - print("DEBUG: _get_current_username NO context found, using System") - return "System" - - -def _autofill_remesa_if_needed(db: Session, invoice_data, tenant_id: int, company_id: int) -> None: - """ - Autocalcula remesa cuando hay pedimento consolidado y remesa viene vacía. - Se hace ANTES de validar para que cumpla reglas de required en validators. - """ - try: - compliance = getattr(invoice_data, "compliance_mx", None) - if not compliance: - return - - pedimento_id = getattr(compliance, "pedimento_id", None) - remesa = getattr(compliance, "remesa", None) - - if not pedimento_id or remesa: - return - - # No aplicar a MEX (por consistencia con CSV import donde remesa es None para MEX) - invoice_type = getattr(invoice_data, "invoice_type", None) - if invoice_type == "MEX": - return - - from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos, PedimentoType - - ped = ( - db.query(Pedimentos) - .filter( - Pedimentos.id == pedimento_id, - Pedimentos.tenant_id == tenant_id, - Pedimentos.company_id == company_id, - ) - .first() - ) - if not ped: - return - - if getattr(ped, "pedimento_type", None) != PedimentoType.CONSOLIDATED: - return - - max_rem = ( - db.query(func.max(models.InvoiceComplianceMx.remesa)) - .filter( - models.InvoiceComplianceMx.pedimento_id == pedimento_id, - models.InvoiceComplianceMx.tenant_id == tenant_id, - models.InvoiceComplianceMx.company_id == company_id, - ) - .scalar() - ) - next_rem = (max_rem or 0) + 1 - compliance.remesa = next_rem - except Exception: - # No bloquear guardado por fallo de autocalculo; validación normal aplicará. - return - - -def _ensure_create_audit_log( - db: Session, - *, - table_name: str, - record_id: str, - record_data: dict, - username: str, - tenant_id: int, - company_id: int, -) -> bool: - """ - Backup audit writer: writes CREATE only when missing. - Returns True when a backup log row was added. - """ - exists = ( - db.query(AuditLog.spec_id) - .filter( - AuditLog.table_name == table_name, - AuditLog.operation_type == "CREATE", - AuditLog.record_id == record_id, - AuditLog.tenant_id == tenant_id, - AuditLog.company_id == company_id, - ) - .first() - ) - if exists: - return False - - AuditService.log_crud_operation( - db=db, - table_name=table_name, - operation_type="CREATE", - record_data=record_data, - username=username, - record_id=record_id, - company_id=company_id, - tenant_id=tenant_id, - ) - return True - - -class InvoiceService: - """Service for Invoice Header operations""" - - @staticmethod - def get_by_id( - db: Session, invoice_id: int, tenant_id: int, company_id: int - ) -> Optional[models.InvoiceHeader]: - """Get an invoice by ID with tenant/company validation""" - return ( - db.query(models.InvoiceHeader) - .filter( - models.InvoiceHeader.id == invoice_id, - models.InvoiceHeader.tenant_id == tenant_id, - models.InvoiceHeader.company_id == company_id, - ) - .first() - ) - - @staticmethod - def get_all( - db: Session, - tenant_id: int, - company_id: int, - skip: int = 0, - limit: int = 100, - filters: Optional[dict] = None, - sort_by: Optional[str] = None, - sort_order: Optional[str] = "asc", - ) -> Tuple[List[models.InvoiceHeader], int]: - """Get all invoices for a tenant/company with pagination and optional filters""" - query = db.query(models.InvoiceHeader).filter( - models.InvoiceHeader.tenant_id == tenant_id, - models.InvoiceHeader.company_id == company_id, - ) - - # Apply filters if provided - print(f"DEBUG: Invoice Query - Company: {company_id}, Filters: {filters}, Skip: {skip}, Limit: {limit}") - if filters: - # Join compliance_mx if needed for filters - needs_compliance_join = any(k in filters for k in ["pedimento", "manifest_number"]) - if needs_compliance_join: - query = query.join(models.InvoiceComplianceMx) - - if filters.get("status") is not None: - status_val = filters["status"] - if status_val in [True, "processed", models.InvoiceStatus.PROCESSED]: - target_status = models.InvoiceStatus.PROCESSED - elif status_val in [False, "pending", models.InvoiceStatus.PENDING]: - target_status = models.InvoiceStatus.PENDING - else: - target_status = status_val - query = query.filter(models.InvoiceHeader.status == target_status) - - if filters.get("operation_type"): - ot = filters["operation_type"] - ot_val = ot.value if hasattr(ot, "value") else ot - query = query.filter(models.InvoiceHeader.operation_type == ot_val) - - if filters.get("invoice_type"): - query = query.filter( - models.InvoiceHeader.invoice_type == filters["invoice_type"] - ) - - if filters.get("invoice_number"): - query = query.filter( - models.InvoiceHeader.invoice_number.ilike( - f"%{filters['invoice_number']}%" - ) - ) - - if filters.get("project_number"): - query = query.filter( - models.InvoiceHeader.project_number.ilike( - f"%{filters['project_number']}%" - ) - ) - - if filters.get("year"): - try: - year_val = int(filters["year"]) - query = query.filter( - func.extract("year", models.InvoiceHeader.invoice_date) == year_val - ) - except (ValueError, TypeError): - pass - - if filters.get("pedimento"): - from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos - query = query.join(models.InvoiceComplianceMx.pedimento).filter( - Pedimentos.pedimento_number.ilike(f"%{filters['pedimento']}%") - ) - - if filters.get("manifest_number"): - query = query.filter( - models.InvoiceComplianceMx.manifest_number.ilike( - f"%{filters['manifest_number']}%" - ) - ) - - # Special case for exports: exclude REPAR if no invoice_type specified - ot_exp = filters.get("operation_type") - ot_exp_val = ot_exp.value if hasattr(ot_exp, "value") else ot_exp - if not filters.get("invoice_type") and ot_exp_val == "exp": - query = query.filter(models.InvoiceHeader.invoice_type != "REPAR") - - # Filtro por sistema activo (SCAF / SCAII) - if filters.get("system"): - query = query.filter(models.InvoiceHeader.system == filters["system"]) - - # Filtro por permisos granulares (allowed_types) - if "allowed_types" in filters: - allowed = filters["allowed_types"] - if allowed is None: - # Acceso global (admin o view_all) - no filtramos por tipos - pass - elif not allowed: - # Seguridad: Si el usuario NO tiene permisos para ningún tipo específico - query = query.filter(models.InvoiceHeader.id == -1) - else: - conditions = [] - for op, inv in allowed: - # Aseguramos comparación insensible a mayúsculas para mayor robustez con la DB - op_str = str(op).lower() - inv_str = str(inv).lower() - conditions.append( - and_( - func.lower(models.InvoiceHeader.operation_type) == op_str, - func.lower(models.InvoiceHeader.invoice_type) == inv_str - ) - ) - if conditions: - query = query.filter(or_(*conditions)) - else: - # Seguridad: Si tiene allowed_types pero no generamos condiciones, no debe ver nada - query = query.filter(models.InvoiceHeader.id == -1) - - # Apply sorting - if sort_by: - # Simple column mapping - # This can be improved to handle joins if needed - column = getattr(models.InvoiceHeader, sort_by, None) - if column: - if sort_order == "desc": - query = query.order_by(column.desc()) - else: - query = query.order_by(column.asc()) - else: - # Default sorting if none provided - query = query.order_by(models.InvoiceHeader.id.desc()) - - total = query.count() - items = query.offset(skip).limit(limit).all() - - # Keep party_count aligned with the real number of line items. - # This avoids stale values stored in invoice_header.party_count. - if items: - invoice_ids = [inv.id for inv in items] - counts = ( - db.query(LineItem.invoice_id, func.count(LineItem.id)) - .filter( - LineItem.invoice_id.in_(invoice_ids), - LineItem.tenant_id == tenant_id, - LineItem.company_id == company_id, - ) - .group_by(LineItem.invoice_id) - .all() - ) - count_map = {invoice_id: int(count) for invoice_id, count in counts} - for inv in items: - inv.party_count = count_map.get(inv.id, 0) - - return items, total - - @staticmethod - def create( - db: Session, - invoice_data: schemas.InvoiceHeaderCreate, - tenant_id: int, - company_id: int, - ) -> models.InvoiceHeader: - """Create a new invoice with all related data""" - - # Validaciones con ErrorCollector - errors = ErrorCollector() - - # Autocalculo remesa (si aplica) ANTES de validar - _autofill_remesa_if_needed(db, invoice_data, tenant_id, company_id) - - # DEBUG: Log payload for analysis - print(f"DEBUG: Creating invoice {invoice_data.invoice_number} of type {invoice_data.invoice_type}") - print(f"DEBUG: Payload: {invoice_data.model_dump()}") - - # Validar si la factura ya existe - invoice_exists(db, invoice_data.invoice_number, tenant_id, company_id, errors) - if invoice_data.operation_type == "exp": - validate_create_export(db, invoice_data, tenant_id, company_id, errors) - else: - validate_create_import(db, invoice_data, tenant_id, company_id, errors) - - # Si hay errores, lanzar excepción ANTES de intentar crear - errors.raise_if_errors("Error al crear la factura") - - # Extract nested data - compliance_data = invoice_data.compliance_mx - financials_data = invoice_data.financials - logistics_data = invoice_data.logistics - details_data = invoice_data.details or [] - collections_data = invoice_data.collections or [] - - try: - - # Create main invoice header - raw_invoice_dict = invoice_data.model_dump( - exclude={ - "compliance_mx", - "financials", - "logistics", - "details", - "collections", - } - ) - invoice_dict = clean_dict(raw_invoice_dict) - invoice_dict["tenant_id"] = tenant_id - invoice_dict["company_id"] = company_id - - # Automatic status and audit fields - username = _get_current_username() - invoice_dict["capture_user"] = username - invoice_dict["who_processed"] = username - - # Ensure document_type respects DB constraints for MEX/AME invoices (bypass clean_dict) - if invoice_dict.get("invoice_type") in ["MEX", "AME"] and not invoice_dict.get("document_type"): - invoice_dict["document_type"] = None - - new_invoice = models.InvoiceHeader(**invoice_dict) - - db.add(new_invoice) - db.flush() # Flush to get the invoice ID - - # Create compliance_mx if provided - if compliance_data: - raw_comp_dict = compliance_data.model_dump() - compliance_dict = clean_dict(raw_comp_dict) - - compliance_dict["invoice_id"] = new_invoice.id - compliance_dict["tenant_id"] = tenant_id - compliance_dict["company_id"] = company_id - - new_compliance = models.InvoiceComplianceMx(**compliance_dict) - db.add(new_compliance) - - # Create financials if provided - if financials_data: - raw_fin_dict = financials_data.model_dump() - financials_dict = clean_dict(raw_fin_dict) - - financials_dict["invoice_id"] = new_invoice.id - financials_dict["tenant_id"] = tenant_id - financials_dict["company_id"] = company_id - - new_financials = models.InvoiceFinancials(**financials_dict) - db.add(new_financials) - - # Create logistics entries - if logistics_data: - raw_log_dict = logistics_data.model_dump() - logistics_dict = clean_dict(raw_log_dict) - - logistics_dict["invoice_id"] = new_invoice.id - logistics_dict["tenant_id"] = tenant_id - logistics_dict["company_id"] = company_id - _autofill_transport_int_ids( - db, - logistics_dict, - tenant_id=tenant_id, - company_id=company_id, - ) - new_logistics = models.InvoiceLogistics(**logistics_dict) - db.add(new_logistics) - - # Create sales details - for detail_item in details_data: - raw_det_dict = detail_item.model_dump() - detail_dict = clean_dict(raw_det_dict) - - detail_dict["invoice_id"] = new_invoice.id - detail_dict["tenant_id"] = tenant_id - detail_dict["company_id"] = company_id - new_detail = models.InvoiceSalesDetails(**detail_dict) - db.add(new_detail) - - # Create collections - for collection_item in collections_data: - raw_col_dict = collection_item.model_dump() - collection_dict = clean_dict(raw_col_dict) - - collection_dict["invoice_id"] = new_invoice.id - collection_dict["tenant_id"] = tenant_id - collection_dict["company_id"] = company_id - new_collection = models.InvoiceCollections(**collection_dict) - db.add(new_collection) - - db.commit() - db.refresh(new_invoice) - backup_written = _ensure_create_audit_log( - db, - table_name=new_invoice.__tablename__, - record_id=str(new_invoice.id), - record_data={ - c.name: getattr(new_invoice, c.name) - for c in models.InvoiceHeader.__table__.columns - }, - username=username, - tenant_id=tenant_id, - company_id=company_id, - ) - for detail in new_invoice.details: - backup_written = ( - _ensure_create_audit_log( - db, - table_name=detail.__tablename__, - record_id=str(detail.id), - record_data={ - c.name: getattr(detail, c.name) - for c in models.InvoiceSalesDetails.__table__.columns - }, - username=username, - tenant_id=tenant_id, - company_id=company_id, - ) - or backup_written - ) - if backup_written: - db.commit() - return new_invoice - - except Exception as e: - db.rollback() - print("\n\n🔥 ERROR AL GUARDAR FACTURA 🔥") - print(f"Error: {str(e)}") - traceback.print_exc() # Esto imprime el error real en la consola - print("--------------------------------\n") - raise e - - @staticmethod - def update( - db: Session, - invoice_id: int, - tenant_id: int, - invoice_data: schemas.InvoiceHeaderUpdate, - company_id: int, - ) -> Optional[models.InvoiceHeader]: - """Update an existing invoice with validation""" - - # DEBUG: Log payload for analysis - print(f"DEBUG: Updating invoice ID {invoice_id} of type {invoice_data.invoice_type}") - print(f"DEBUG: Payload: {invoice_data.model_dump(exclude_unset=True)}") - - # Validaciones con ErrorCollector - errors = ErrorCollector() - - # Obtener la factura existente - invoice = InvoiceService.get_by_id(db, invoice_id, tenant_id, company_id) - if not invoice: - return None - - # Si se cambió el número de factura, validar que no exista otra con ese número - if invoice_data.invoice_number and invoice_data.invoice_number != invoice.invoice_number: - # Verificar que no exista otra factura con el nuevo número - existing_invoice = ( - db.query(models.InvoiceHeader.id) - .filter( - models.InvoiceHeader.invoice_number == invoice_data.invoice_number, - models.InvoiceHeader.tenant_id == tenant_id, - models.InvoiceHeader.company_id == company_id, - models.InvoiceHeader.id != invoice_id, # Excluir la factura actual - ) - .first() - ) - - if existing_invoice: - errors.add_duplicate_error( - "invoice_number", - invoice_data.invoice_number, - f"Ya existe otra factura con el número '{invoice_data.invoice_number}'", - ) - - if invoice_data.operation_type == "exp": - validate_update_export(db, invoice_data, invoice, tenant_id, company_id, errors) - else: - # Autocalculo remesa (si aplica) ANTES de validar - _autofill_remesa_if_needed(db, invoice_data, tenant_id, company_id) - validate_update_import(db, invoice_data, invoice, tenant_id, company_id, errors) - - # Si hay errores, lanzar excepción ANTES de actualizar - errors.raise_if_errors("Error al actualizar la factura") - - # Update main invoice header fields - update_dict = invoice_data.model_dump( - exclude={ - "id", - "compliance_mx", - "financials", - "logistics", - "details", - "collections", - }, - exclude_unset=True, - ) - for key, value in update_dict.items(): - setattr(invoice, key, value) - - # Audit update fields - username = _get_current_username() - invoice.who_processed = username - invoice.updated_date = func.now() - - # Backfill capture_user if missing or previous generic 'System' - if not invoice.capture_user or invoice.capture_user == "System": - if username != "System": - invoice.capture_user = username - - # Update compliance_mx if provided - if invoice_data.compliance_mx is not None: - print(f"DEBUG: 更新 compliance_mx para factura {invoice.id}: {invoice_data.compliance_mx}") - if invoice.compliance_mx: - for key, value in invoice_data.compliance_mx.model_dump( - exclude_unset=True - ).items(): - # Parche rápido para update - if value == "": - value = None - setattr(invoice.compliance_mx, key, value) - else: - compliance_dict = invoice_data.compliance_mx.model_dump() - # Aplicar limpieza manual si es necesario - if "customs_agent" in compliance_dict: - compliance_dict["customs_broker_id"] = compliance_dict.pop( - "customs_agent" - ) - - compliance_dict["invoice_id"] = invoice.id - compliance_dict["tenant_id"] = tenant_id - compliance_dict["company_id"] = company_id - new_compliance = models.InvoiceComplianceMx(**compliance_dict) - db.add(new_compliance) - - # Update financials if provided - if invoice_data.financials is not None: - if invoice.financials: - for key, value in invoice_data.financials.model_dump( - exclude_unset=True - ).items(): - if value == "": - value = None - setattr(invoice.financials, key, value) - else: - financials_dict = invoice_data.financials.model_dump() - financials_dict["invoice_id"] = invoice.id - financials_dict["tenant_id"] = tenant_id - financials_dict["company_id"] = company_id - new_financials = models.InvoiceFinancials(**financials_dict) - db.add(new_financials) - - # Update logistics if provided - if invoice_data.logistics is not None: - if invoice.logistics: - for key, value in invoice_data.logistics.model_dump( - exclude_unset=True - ).items(): - if value == "": - value = None - setattr(invoice.logistics, key, value) - _autofill_transport_int_ids( - db, - invoice.logistics, - tenant_id=tenant_id, - company_id=company_id, - ) - else: - logistics_dict = invoice_data.logistics.model_dump() - logistics_dict["invoice_id"] = invoice.id - logistics_dict["tenant_id"] = tenant_id - logistics_dict["company_id"] = company_id - _autofill_transport_int_ids( - db, - logistics_dict, - tenant_id=tenant_id, - company_id=company_id, - ) - new_logistics = models.InvoiceLogistics(**logistics_dict) - db.add(new_logistics) - - db.commit() - db.refresh(invoice) - return invoice - - @staticmethod - def delete(db: Session, invoice_id: int, tenant_id: int, company_id: int) -> bool: - """Delete an invoice and all related data (cascade delete)""" - invoice = InvoiceService.get_by_id(db, invoice_id, tenant_id, company_id) - if invoice: - db.delete(invoice) - db.commit() - return True - return False - - @staticmethod - def copy( - db: Session, - invoice_id: int, - tenant_id: int, - company_id: int, - new_invoice_number: Optional[str] = None, - ) -> models.InvoiceHeader: - """Duplica una factura con todas sus tablas relacionadas, incluyendo partidas. - - Si se provee new_invoice_number, se usa ese número (falla con 409 si ya existe). - Si no, genera sufijo '-COPIA' / '-COPIA-N' automáticamente. - Estado reseteado a 'pending', sin pedimento ni datos de procesamiento. - """ - from fastapi import HTTPException - original = ( - db.query(models.InvoiceHeader) - .filter( - models.InvoiceHeader.id == invoice_id, - models.InvoiceHeader.tenant_id == tenant_id, - models.InvoiceHeader.company_id == company_id, - ) - .first() - ) - if not original: - raise HTTPException(status_code=404, detail="Factura no encontrada") - - if new_invoice_number: - exists = db.query(models.InvoiceHeader).filter( - models.InvoiceHeader.invoice_number == new_invoice_number, - models.InvoiceHeader.tenant_id == tenant_id, - models.InvoiceHeader.company_id == company_id, - ).first() - if exists: - raise HTTPException( - status_code=409, - detail=f"Ya existe una factura con el número '{new_invoice_number}'" - ) - else: - base_number = original.invoice_number or "" - candidate = f"{base_number}-COPIA" - counter = 1 - while db.query(models.InvoiceHeader).filter( - models.InvoiceHeader.invoice_number == candidate, - models.InvoiceHeader.tenant_id == tenant_id, - models.InvoiceHeader.company_id == company_id, - ).first(): - counter += 1 - candidate = f"{base_number}-COPIA-{counter}" - new_invoice_number = candidate - - username = _get_current_username() - - # Columnas a excluir en la copia del encabezado - EXCLUDE_COLS = { - "id", "invoice_number", "status", "capture_date", "capture_user", - "who_processed", "processed_date", "process_log", "status_rec", - "status_rep", "comments_status", "vu_observations", "cfdi_uuid", - "path_pdf", "path_xml", "created_at", "updated_at", - } - - header_data = { - c.name: getattr(original, c.name) - for c in models.InvoiceHeader.__table__.columns - if c.name not in EXCLUDE_COLS - } - header_data["invoice_number"] = new_invoice_number - header_data["status"] = models.InvoiceStatus.PENDING - header_data["capture_user"] = username - header_data["who_processed"] = username - - new_invoice = models.InvoiceHeader(**header_data) - db.add(new_invoice) - db.flush() - - # Copiar compliance_mx (sin pedimento ni datos de procesamiento aduanal) - comp = db.query(models.InvoiceComplianceMx).filter( - models.InvoiceComplianceMx.invoice_id == invoice_id - ).first() - if comp: - EXCLUDE_COMP = { - "invoice_id", "vucem_operation_num", - "electronic_signature", "certificate_number", "niu_number", - "code_signature", "edocument", "created_at", "updated_at", - } - comp_data = { - c.name: getattr(comp, c.name) - for c in models.InvoiceComplianceMx.__table__.columns - if c.name not in EXCLUDE_COMP - } - comp_data["invoice_id"] = new_invoice.id - db.add(models.InvoiceComplianceMx(**comp_data)) - - # Copiar financials - fin = db.query(models.InvoiceFinancials).filter( - models.InvoiceFinancials.invoice_id == invoice_id - ).first() - if fin: - EXCLUDE_FIN = {"id", "invoice_id", "created_at", "updated_at"} - fin_data = { - c.name: getattr(fin, c.name) - for c in models.InvoiceFinancials.__table__.columns - if c.name not in EXCLUDE_FIN - } - fin_data["invoice_id"] = new_invoice.id - fin_data["tenant_id"] = tenant_id - fin_data["company_id"] = company_id - db.add(models.InvoiceFinancials(**fin_data)) - - # Copiar logistics - log_entries = db.query(models.InvoiceLogistics).filter( - models.InvoiceLogistics.invoice_id == invoice_id - ).all() - for log in log_entries: - EXCLUDE_LOG = {"id", "invoice_id", "created_at", "updated_at"} - log_data = { - c.name: getattr(log, c.name) - for c in models.InvoiceLogistics.__table__.columns - if c.name not in EXCLUDE_LOG - } - log_data["invoice_id"] = new_invoice.id - log_data["tenant_id"] = tenant_id - log_data["company_id"] = company_id - db.add(models.InvoiceLogistics(**log_data)) - - # Copiar sales details - details = db.query(models.InvoiceSalesDetails).filter( - models.InvoiceSalesDetails.invoice_id == invoice_id - ).all() - for det in details: - EXCLUDE_DET = {"id", "invoice_id", "created_at", "updated_at"} - det_data = { - c.name: getattr(det, c.name) - for c in models.InvoiceSalesDetails.__table__.columns - if c.name not in EXCLUDE_DET - } - det_data["invoice_id"] = new_invoice.id - det_data["tenant_id"] = tenant_id - det_data["company_id"] = company_id - db.add(models.InvoiceSalesDetails(**det_data)) - - # Copiar collections - collections = db.query(models.InvoiceCollections).filter( - models.InvoiceCollections.invoice_id == invoice_id - ).all() - for col in collections: - EXCLUDE_COL = {"id", "invoice_id", "created_at", "updated_at"} - col_data = { - c.name: getattr(col, c.name) - for c in models.InvoiceCollections.__table__.columns - if c.name not in EXCLUDE_COL - } - col_data["invoice_id"] = new_invoice.id - col_data["tenant_id"] = tenant_id - col_data["company_id"] = company_id - db.add(models.InvoiceCollections(**col_data)) - - # Copiar partidas (LineItem) con todas sus sub-tablas - EXCLUDE_LINE = {"id", "invoice_id", "created_at", "updated_at"} - EXCLUDE_SUB = {"id", "item_line_id", "created_at", "updated_at"} - EXCLUDE_SERIE = {"id", "line_item_id", "created_at", "updated_at"} - EXCLUDE_IDENT = {"id", "item_line_id", "created_at", "updated_at"} - - line_items = db.query(LineItem).filter( - LineItem.invoice_id == invoice_id, - LineItem.tenant_id == tenant_id, - LineItem.company_id == company_id, - ).order_by(LineItem.line_number).all() - - for item in line_items: - line_data = { - c.name: getattr(item, c.name) - for c in LineItem.__table__.columns - if c.name not in EXCLUDE_LINE - } - line_data["invoice_id"] = new_invoice.id - new_item = LineItem(**line_data) - db.add(new_item) - db.flush() - - # Copiar series primero para reusar sus IDs en LineReference - serie_id_map: dict[int, int] = {} - for serie in db.query(Serie).filter(Serie.line_item_id == item.id).all(): - s_data = { - c.name: getattr(serie, c.name) - for c in Serie.__table__.columns - if c.name not in EXCLUDE_SERIE - } - s_data["line_item_id"] = new_item.id - new_serie = Serie(**s_data) - db.add(new_serie) - db.flush() - serie_id_map[serie.id] = new_serie.id - - fin = db.query(LineFinancial).filter(LineFinancial.item_line_id == item.id).first() - if fin: - f_data = {c.name: getattr(fin, c.name) for c in LineFinancial.__table__.columns if c.name not in EXCLUDE_SUB} - f_data["item_line_id"] = new_item.id - db.add(LineFinancial(**f_data)) - - qty = db.query(LineQuantity).filter(LineQuantity.item_line_id == item.id).first() - if qty: - q_data = {c.name: getattr(qty, c.name) for c in LineQuantity.__table__.columns if c.name not in EXCLUDE_SUB} - q_data["item_line_id"] = new_item.id - db.add(LineQuantity(**q_data)) - - cus = db.query(LineCustom).filter(LineCustom.item_line_id == item.id).first() - if cus: - cu_data = {c.name: getattr(cus, c.name) for c in LineCustom.__table__.columns if c.name not in EXCLUDE_SUB} - cu_data["item_line_id"] = new_item.id - db.add(LineCustom(**cu_data)) - - desc = db.query(LineDescription).filter(LineDescription.item_line_id == item.id).first() - if desc: - d_data = {c.name: getattr(desc, c.name) for c in LineDescription.__table__.columns if c.name not in EXCLUDE_SUB} - d_data["item_line_id"] = new_item.id - db.add(LineDescription(**d_data)) - - ref = db.query(LineReference).filter(LineReference.item_line_id == item.id).first() - if ref: - r_data = { - c.name: getattr(ref, c.name) - for c in LineReference.__table__.columns - if c.name not in {"id", "item_line_id", "serie_id", "created_at", "updated_at"} - } - r_data["item_line_id"] = new_item.id - r_data["serie_id"] = serie_id_map.get(ref.serie_id) if ref.serie_id else None - db.add(LineReference(**r_data)) - - for ident in db.query(IdentifierDetail).filter(IdentifierDetail.item_line_id == item.id).all(): - i_data = {c.name: getattr(ident, c.name) for c in IdentifierDetail.__table__.columns if c.name not in EXCLUDE_IDENT} - i_data["item_line_id"] = new_item.id - db.add(IdentifierDetail(**i_data)) - - db.commit() - db.refresh(new_invoice) - return new_invoice - - @staticmethod - def export_items( - db: Session, - invoice: "models.InvoiceHeader", - fmt: str, - ): - """Genera StreamingResponse con partidas de la factura. - - Replica el OF 2 del POPUP legacy (QEqiDef → CSV/TXT/XLSX). - Formatos: csv (,) | txt (|) | xlsx (openpyxl). - Incluye sección de series al final si existen. - """ - import io - import csv - import re - from fastapi.responses import StreamingResponse - from sqlalchemy.orm import joinedload - from api.v1.modules.a76.items.models import LineItem - from api.v1.modules.a76.general_catalogs.fractions.tariff_fractions.models import TariffFraction - - def _strip_newlines(text): - if not text: - return '' - return re.sub(r'[\r\n]+', ' ', str(text)).strip() - - # --- Query principal: patrón joinedload igual que ItemService.get_all --- - items = ( - db.query(LineItem) - .options( - joinedload(LineItem.quantity), - joinedload(LineItem.financial), - joinedload(LineItem.customs), - joinedload(LineItem.description), - joinedload(LineItem.class_info), - joinedload(LineItem.part_info), - joinedload(LineItem.unit_of_measure_info), - joinedload(LineItem.series), - ) - .filter( - LineItem.invoice_id == invoice.id, - LineItem.tenant_id == invoice.tenant_id, - LineItem.company_id == invoice.company_id, - ) - .order_by(LineItem.line_number) - .all() - ) - - # --- Lookup bulk de UMCLAVE (TariffFraction.umt por código de fracción) --- - fractions = { - item.customs.fraction - for item in items - if item.customs and item.customs.fraction - } - umt_map: dict = {} - if fractions: - umt_map = { - r.code: r.umt - for r in db.query(TariffFraction.code, TariffFraction.umt) - .filter(TariffFraction.code.in_(fractions)) - .all() - } - - # --- Delimitadores y media type --- - fmt = fmt.lower() - if fmt == 'xlsx': - return InvoiceService._export_items_xlsx(invoice, items, umt_map) - - delim = ',' if fmt == 'csv' else '|' - media_type = 'text/csv; charset=utf-8' if fmt == 'csv' else 'text/plain; charset=utf-8' - ext = fmt - - HEADERS = [ - 'FACTURA', 'LINEA', 'PROCEDENCIA', 'FACTURA', 'LINEA', 'SI', - 'CANTIDAD', 'UNIMED', 'COSTO UNITARIO', - 'PESO NETO', 'PESO BRUTO', 'CANT BULTOS', 'CLAVE BULTOS', - 'PAIS', 'FRACCION', 'PREFERENCIA', 'SECTOR', 'FRAC AMERICANA', - 'ORDEN COMPRA', 'NUM PARTE', - 'DESCRIP ESP', 'DESCRIP ING', 'MARCA', 'MODELO', 'CLASE', 'UMCLAVE', - 'VALORIMPOMN', 'VALORIMPOME', 'LOTE', - 'DESCRIPCIÓN EXTRA EN ESPAÑOL', 'USUARIO QUE CAPTURO LA FACTURA', - ] - SER_HEADERS = ['FACTURA', 'LINEA', 'RENGLON', 'SERIE', 'MODELO', 'NUMID'] - - def _item_to_list(item: LineItem): - q = item.quantity - f = item.financial - c = item.customs - d = item.description - cls = item.class_info - part = item.part_info - uom = item.unit_of_measure_info - pais = c.origin_country if c else '' - fraccion = (c.octave_fraction or c.fraction) if c else '' - umclave = umt_map.get(c.fraction, '') if c and c.fraction else '' - return [ - invoice.invoice_number or '', - item.line_number or '', - 'TEM', - invoice.invoice_number or '', - item.line_number or '', - 'SI', - q.quantity if q else '', - uom.code if uom else '', - f.unit_cost_capture if f else '', - q.net_weight if q else '', - q.gross_weight if q else '', - q.package_quantity if q else '', - q.package_id if q else '', - pais or '', - fraccion, - c.fraction_type if c else '', - c.sector if c else '', - c.american_fraction if c else '', - invoice.purchase_order or '', - part.part_number if part else '', - _strip_newlines(d.description_spanish if d else None), - _strip_newlines(d.description_english if d else None), - d.brand if d else '', - d.model if d else '', - cls.class_code if cls else '', - umclave, - f.value_mxn if f else '', - f.value_usd if f else '', - d.lot if d else '', - _strip_newlines(d.extra_description if d else None), - invoice.capture_user or '', - ] - - def generate(): - buf = io.StringIO() - writer = csv.writer(buf, delimiter=delim, lineterminator='\r\n') - writer.writerow(HEADERS) - yield buf.getvalue() - for item in items: - buf.seek(0); buf.truncate(0) - writer.writerow(_item_to_list(item)) - yield buf.getvalue() - # Sección de series al final - has_series = any(item.series for item in items) - if has_series: - buf.seek(0); buf.truncate(0) - writer.writerow([]) - writer.writerow(SER_HEADERS) - yield buf.getvalue() - for item in items: - for s in sorted(item.series, key=lambda x: x.row): - buf.seek(0); buf.truncate(0) - writer.writerow([ - invoice.invoice_number or '', - item.line_number or '', - s.row or '', - s.serial_numbers or '', - s.model or '', - s.number_id or '', - ]) - yield buf.getvalue() - - safe_num = re.sub(r'[^\w\-]', '_', invoice.invoice_number or 'factura') - filename = f"{safe_num}_export.{ext}" - return StreamingResponse( - generate(), - media_type=media_type, - headers={"Content-Disposition": f'attachment; filename="{filename}"'}, - ) - - @staticmethod - def _export_items_xlsx(invoice, items, umt_map: dict): - """Genera el archivo XLSX con openpyxl.""" - import io - import re - from fastapi.responses import StreamingResponse - try: - import openpyxl - except ImportError: - raise ImportError("openpyxl no está instalado. Agrega 'openpyxl' a requirements.txt") - - def _strip_newlines(text): - if not text: - return '' - return re.sub(r'[\r\n]+', ' ', str(text)).strip() - - def _f(v): - return float(v) if v is not None else '' - - HEADERS = [ - 'FACTURA', 'LINEA', 'PROCEDENCIA', 'FACTURA', 'LINEA', 'SI', - 'CANTIDAD', 'UNIMED', 'COSTO UNITARIO', - 'PESO NETO', 'PESO BRUTO', 'CANT BULTOS', 'CLAVE BULTOS', - 'PAIS', 'FRACCION', 'PREFERENCIA', 'SECTOR', 'FRAC AMERICANA', - 'ORDEN COMPRA', 'NUM PARTE', - 'DESCRIP ESP', 'DESCRIP ING', 'MARCA', 'MODELO', 'CLASE', 'UMCLAVE', - 'VALORIMPOMN', 'VALORIMPOME', 'LOTE', - 'DESCRIPCIÓN EXTRA EN ESPAÑOL', 'USUARIO QUE CAPTURO LA FACTURA', - ] - wb = openpyxl.Workbook() - ws = wb.active - ws.title = "Partidas" - ws.append(HEADERS) - for item in items: - q = item.quantity - f = item.financial - c = item.customs - d = item.description - cls = item.class_info - part = item.part_info - uom = item.unit_of_measure_info - pais = c.origin_country if c else '' - fraccion = (c.octave_fraction or c.fraction) if c else '' - umclave = umt_map.get(c.fraction, '') if c and c.fraction else '' - ws.append([ - invoice.invoice_number or '', - item.line_number or '', - 'TEM', - invoice.invoice_number or '', - item.line_number or '', - 'SI', - _f(q.quantity if q else None), - uom.code if uom else '', - _f(f.unit_cost_capture if f else None), - _f(q.net_weight if q else None), - _f(q.gross_weight if q else None), - _f(q.package_quantity if q else None), - str(q.package_id or '') if q else '', - str(pais or ''), - str(fraccion), - str(c.fraction_type or '') if c else '', - str(c.sector or '') if c else '', - str(c.american_fraction or '') if c else '', - str(invoice.purchase_order or ''), - str(part.part_number or '') if part else '', - _strip_newlines(d.description_spanish if d else None), - _strip_newlines(d.description_english if d else None), - str(d.brand or '') if d else '', - str(d.model or '') if d else '', - str(cls.class_code or '') if cls else '', - str(umclave), - _f(f.value_mxn if f else None), - _f(f.value_usd if f else None), - str(d.lot or '') if d else '', - _strip_newlines(d.extra_description if d else None), - str(invoice.capture_user or ''), - ]) - has_series = any(item.series for item in items) - if has_series: - ws.append([]) - ws.append(['FACTURA', 'LINEA', 'RENGLON', 'SERIE', 'MODELO', 'NUMID']) - for item in items: - for s in sorted(item.series, key=lambda x: x.row): - ws.append([ - invoice.invoice_number or '', - item.line_number or '', - s.row or '', - s.serial_numbers or '', - s.model or '', - s.number_id or '', - ]) - buf = io.BytesIO() - wb.save(buf) - buf.seek(0) - safe_num = re.sub(r'[^\w\-]', '_', invoice.invoice_number or 'factura') - filename = f"{safe_num}_export.xlsx" - return StreamingResponse( - iter([buf.read()]), - media_type="application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", - headers={"Content-Disposition": f'attachment; filename="{filename}"'}, - ) - - @staticmethod - def export_gm_transport( - db: Session, - invoice: "models.InvoiceHeader", - ): - """Genera CSV para interfaz GM Transport (OF 5 del POPUP legacy). - - Replica las 21 columnas del formato GM Transport. Pedimento formateado - como: AÑO ADUANA PATENTE NUMERO con espacios dobles entre campos. - """ - import io - import csv - import re - from fastapi.responses import StreamingResponse - from sqlalchemy.orm import joinedload - from api.v1.modules.a76.items.models import LineItem - from api.v1.modules.a76.invoices.models import InvoiceComplianceMx - from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos - from api.v1.modules.a76.pedmientos.models.pedimento_dates import PedimentoDates - - def _strip(text): - if not text: - return '' - return re.sub(r'[\r\n]+', ' ', str(text)).strip() - - # Datos de pedimento y compliance son por factura (no por partida) - compliance = ( - db.query(InvoiceComplianceMx) - .filter(InvoiceComplianceMx.invoice_id == invoice.id) - .first() - ) - pedimento = None - ped_date = None - if compliance and compliance.pedimento_id: - pedimento = db.query(Pedimentos).filter(Pedimentos.id == compliance.pedimento_id).first() - if pedimento: - ped_date_row = ( - db.query(PedimentoDates) - .filter(PedimentoDates.pedimento_id == pedimento.id) - .first() - ) - ped_date = ped_date_row.entry_date if ped_date_row else None - - def _format_pedimento() -> str: - if not pedimento or not pedimento.pedimento_number: - return '' - return f"{pedimento.year or ''} {pedimento.customs_office or ''} {pedimento.license or ''} {pedimento.pedimento_number or ''}" - - # Partidas con sus sub-modelos cargados (joinedload — mismo patrón que ItemService) - items = ( - db.query(LineItem) - .options( - joinedload(LineItem.quantity), - joinedload(LineItem.customs), - joinedload(LineItem.class_info), - ) - .filter( - LineItem.invoice_id == invoice.id, - LineItem.tenant_id == invoice.tenant_id, - LineItem.company_id == invoice.company_id, - ) - .order_by(LineItem.line_number) - .all() - ) - - ped_str = _format_pedimento() - fecha_str = ped_date.strftime('%d/%m/%Y') if ped_date else '' - aduana_str = compliance.aduana if compliance else '' - - HEADERS = [ - 'CANTIDAD', 'ID UNIDAD EMBALAJE', 'DESC. MATERIAL CARGA', 'PESO', 'ID UNIDAD PESO', - 'CODIGO DE PRODUCTO Y SERVICIO', 'CLAVE UNIDAD DE MEDIDA Y EMBALAJE', 'CLAVE UNIDAD', - 'CLAVE FRACCIÓN ARANCELARIA', 'UUID COMERCIO EXTERIOR', 'ES MATERIAL PELIGROSO?', - 'CLAVE MATERIAL PELIGROSO', 'TIPO EMBALAJE', 'DESCRIPCIÓN EMBALAJE', - 'APLICA TARIFA', 'TARIFA', 'IMPORTE', 'IMPORTE BASE', - 'NÚMERO DE PEDIMENTO', 'FECHA', 'ADUANA', - ] - - def _item_to_list(item: LineItem): - q = item.quantity - c = item.customs - cls = item.class_info - return [ - q.quantity if q else '', - '', - _strip(cls.description_es if cls else None), - q.gross_weight if q else '', - '', - cls.fraction if cls else '', - '', - '', - ("'" + c.fraction) if c and c.fraction else '', - '', - 'NO', - '', '', '', '', '', '', '', - ped_str, - fecha_str, - aduana_str, - ] - - def generate(): - buf = io.StringIO() - writer = csv.writer(buf, delimiter=',', lineterminator='\r\n') - writer.writerow(HEADERS) - yield buf.getvalue() - for item in items: - buf.seek(0); buf.truncate(0) - writer.writerow(_item_to_list(item)) - yield buf.getvalue() - - safe_num = re.sub(r'[^\w\-]', '_', invoice.invoice_number or 'factura') - filename = f"{safe_num}_gm_transport.csv" - return StreamingResponse( - generate(), - media_type='text/csv; charset=utf-8', - headers={"Content-Disposition": f'attachment; filename="{filename}"'}, - ) - - @staticmethod - def export_carta_porte(db: Session, invoice) -> "StreamingResponse": - """ - Genera el CSV de Interfaz Carta Porte (OF-4). - 24 columnas: datos de partida + RFC proveedor/enviado a/importador + pedimento + régimen. - """ - import io - import csv - import re - from fastapi.responses import StreamingResponse - from sqlalchemy.orm import joinedload - from api.v1.modules.a76.items.models import LineItem - from api.v1.modules.a76.invoices.models import InvoiceComplianceMx - from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos - from api.v1.modules.a76.pedmientos.models.pedimento_dates import PedimentoDates - from api.v1.modules.a76.clients_and_providers.models import ClientProvider - from api.v1.modules.a76.general_catalogs.fractions.tariff_fractions.models import TariffFraction - from api.v1.modules.public.reference_data.carta_porte_codes.models import CartaPorte - - def _strip(text): - if not text: - return '' - return re.sub(r'[\r\n]+', ' ', str(text)).strip() - - # Datos fijos por factura: compliance, pedimento, RFCs - compliance = ( - db.query(InvoiceComplianceMx) - .filter(InvoiceComplianceMx.invoice_id == invoice.id) - .first() - ) - pedimento = None - ped_date = None - regime = '' - aduana_str = compliance.aduana if compliance else '' - provider_rfc = shipped_to_rfc = sold_to_rfc = '' - - if compliance: - if compliance.pedimento_id: - pedimento = db.query(Pedimentos).filter(Pedimentos.id == compliance.pedimento_id).first() - if pedimento: - regime = pedimento.regime or '' - ped_date_row = ( - db.query(PedimentoDates) - .filter(PedimentoDates.pedimento_id == pedimento.id) - .first() - ) - ped_date = ped_date_row.entry_date if ped_date_row else None - - def _get_rfc(client_id): - if not client_id: - return '' - # Identificador fiscal efectivo: RFC (nacional) o TAX-ID (extranjero) - cp = db.query(ClientProvider).filter(ClientProvider.id == client_id).first() - return (cp.fiscal_id or '') if cp else '' - - provider_rfc = _get_rfc(compliance.provider_id) - shipped_to_rfc = _get_rfc(compliance.shipped_to_id) - sold_to_rfc = _get_rfc(compliance.sold_to_id) - - def _format_pedimento() -> str: - if not pedimento or not pedimento.pedimento_number: - return '' - return ( - f"{pedimento.year or ''} {pedimento.customs_office or ''}" - f" {pedimento.license or ''} {pedimento.pedimento_number or ''}" - ) - - # Partidas con joinedload (mismo patrón que export_items) - items = ( - db.query(LineItem) - .options( - joinedload(LineItem.quantity), - joinedload(LineItem.financial), - joinedload(LineItem.customs), - joinedload(LineItem.description), - joinedload(LineItem.class_info), - joinedload(LineItem.unit_of_measure_info), - ) - .filter( - LineItem.invoice_id == invoice.id, - LineItem.tenant_id == invoice.tenant_id, - LineItem.company_id == invoice.company_id, - ) - .order_by(LineItem.line_number) - .all() - ) - - # Bulk lookups: UMT por fracción aduanera, CartaPorte por fracción de clase - customs_fractions = { - item.customs.fraction for item in items if item.customs and item.customs.fraction - } - class_fractions = { - item.class_info.fraction for item in items - if item.class_info and item.class_info.fraction - } - umt_map = ( - {r.code: r.umt for r in db.query(TariffFraction.code, TariffFraction.umt) - .filter(TariffFraction.code.in_(customs_fractions)).all()} - if customs_fractions else {} - ) - cp_map = ( - {r.code: r for r in db.query(CartaPorte) - .filter(CartaPorte.code.in_(class_fractions)).all()} - if class_fractions else {} - ) - - ped_str = _format_pedimento() - fecha_str = ped_date.strftime('%d/%m/%Y') if ped_date else '' - - HEADERS = [ - 'FACTURA', 'CLASE', 'DESCRIPCION CLASE', 'CODIGO DE PRODUCTO Y SERVICIO', - 'DESCRIPCION CÓDIGO', 'CANTIDAD', 'UNIDAD DE MEDIDA', 'UNIDAD DE MEDIDA SAT', - 'PESO NETO', 'PESO BRUTO', 'FRACCION', 'VALOR MN', 'VALOR ME', - 'RFC PROVEEDOR', 'RFC ENVIADO A:', 'PEDIMENTO', 'FECHA INICIO PEDIMENTO', - 'MATERIAL PELIGROSO', 'ADUANA', 'TIPO DE MATERIAL', 'DESCRIPCION DE LA MATERIA', - 'TIPO DE DOCUMENTO', 'RFC IMPORTADOR', 'REGIMEN ADUANERO', - ] - - def _item_to_list(item: LineItem): - q = item.quantity - fin = item.financial - c = item.customs - desc = item.description - cls = item.class_info - uom = item.unit_of_measure_info - customs_frac = c.fraction if c else '' - class_frac = cls.fraction if cls else None - cp = cp_map.get(class_frac) if class_frac else None - return [ - invoice.invoice_number or '', - cls.class_code if cls else '', - _strip(desc.description_spanish if desc else None), - cp.code if cp else '', - cp.description if cp else '', - q.quantity if q else '', - uom.code if uom else '', - umt_map.get(customs_frac, ''), - q.net_weight if q else '', - q.gross_weight if q else '', - customs_frac, - fin.value_mxn if fin else '', - fin.value_usd if fin else '', - provider_rfc, - shipped_to_rfc, - ped_str, - fecha_str, - 'NO', - aduana_str, - cls.material_key if cls else '', - '', # DESCRIPCION DE LA MATERIA — tabla material_types no mapeada - '01', # TIPO DE DOCUMENTO - sold_to_rfc, - regime, - ] - - def generate(): - buf = io.StringIO() - writer = csv.writer(buf, delimiter=',', lineterminator='\r\n') - writer.writerow(HEADERS) - yield buf.getvalue() - for item in items: - buf.seek(0); buf.truncate(0) - writer.writerow(_item_to_list(item)) - yield buf.getvalue() - - safe_num = re.sub(r'[^\w\-]', '_', invoice.invoice_number or 'factura') - filename = f"{safe_num}_carta_porte.csv" - return StreamingResponse( - generate(), - media_type='text/csv; charset=utf-8', - headers={"Content-Disposition": f'attachment; filename="{filename}"'}, - ) - - @staticmethod - def export_tfc(db: Session, invoice) -> "StreamingResponse": - """Interfaz TFC (OF-6). 17 columnas. Pedimento desglosado en 4 campos.""" - import io - import csv - import re - from fastapi.responses import StreamingResponse - from sqlalchemy.orm import joinedload - from api.v1.modules.a76.items.models import LineItem - from api.v1.modules.a76.invoices.models import InvoiceComplianceMx - from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos - from api.v1.modules.a76.pedmientos.models.pedimento_dates import PedimentoDates - - def _strip(text): - if not text: - return '' - return re.sub(r'[\r\n]+', ' ', str(text)).strip() - - compliance = ( - db.query(InvoiceComplianceMx) - .filter(InvoiceComplianceMx.invoice_id == invoice.id) - .first() - ) - pedimento = None - if compliance and compliance.pedimento_id: - pedimento = db.query(Pedimentos).filter(Pedimentos.id == compliance.pedimento_id).first() - - ped_year = pedimento.year or '' if pedimento else '' - ped_office = pedimento.customs_office or '' if pedimento else '' - ped_license = pedimento.license or '' if pedimento else '' - ped_number = pedimento.pedimento_number or '' if pedimento else '' - - items = ( - db.query(LineItem) - .options( - joinedload(LineItem.quantity), - joinedload(LineItem.customs), - joinedload(LineItem.class_info), - joinedload(LineItem.description), - ) - .filter( - LineItem.invoice_id == invoice.id, - LineItem.tenant_id == invoice.tenant_id, - LineItem.company_id == invoice.company_id, - ) - .order_by(LineItem.line_number) - .all() - ) - - HEADERS = [ - 'ID DESTINO', 'BIENES TRANSPORTADOS', 'CANTIDAD MERCANCIA', 'CLAVE DE UNIDAD', - 'PESO EN KG', 'DESCRIPCION DE MERCANCIA', 'MATERIAL PELIGROSO', 'CLAVE DE MATERIAL', - 'CLAVE EMBALAJE (SOLO SI ES MATERIAL PELIGROSO)', 'FRACCION ARANCELARIA', - 'UUID DE COMERCIO EXTERIOR', 'DESCRIPCION GUIA DE IDENTIFICACION', - 'PESO KG GUIA DE IDENTIFICACION', 'PEDIMENTO - VALIDACION', 'PEDIMENTO - ADUANA', - 'PEDIMENTO - PATENTE', 'PEDIMENTO NUMERACION PROGRESIVA', - ] - - def _item_to_list(item: LineItem): - q = item.quantity - c = item.customs - cls = item.class_info - d = item.description - return [ - '1', - cls.fraction if cls else '', - q.quantity if q else '', - '', - q.net_weight if q else '', - _strip(d.description_spanish if d else None), - 'NO', - '', '', - c.fraction if c else '', - '', '', '', - ped_year, - ped_office, - ped_license, - ped_number, - ] - - def generate(): - buf = io.StringIO() - writer = csv.writer(buf, delimiter=',', lineterminator='\r\n') - writer.writerow(HEADERS) - yield buf.getvalue() - for item in items: - buf.seek(0); buf.truncate(0) - writer.writerow(_item_to_list(item)) - yield buf.getvalue() - - safe_num = re.sub(r'[^\w\-]', '_', invoice.invoice_number or 'factura') - filename = f"{safe_num}_tfc.csv" - return StreamingResponse( - generate(), - media_type='text/csv; charset=utf-8', - headers={"Content-Disposition": f'attachment; filename="{filename}"'}, - ) - - @staticmethod - def export_carta_porte_consolidada(db: Session, invoice) -> "StreamingResponse": - """Interfaz Carta Porte Consolidada (OF-8). 24 cols, igual que OF-4 pero - partidas agrupadas por clase: SUM de cantidades/pesos/valores.""" - import io - import csv - import re - from decimal import Decimal - from fastapi.responses import StreamingResponse - from sqlalchemy.orm import joinedload - from api.v1.modules.a76.items.models import LineItem - from api.v1.modules.a76.invoices.models import InvoiceComplianceMx - from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos - from api.v1.modules.a76.pedmientos.models.pedimento_dates import PedimentoDates - from api.v1.modules.a76.clients_and_providers.models import ClientProvider - from api.v1.modules.a76.general_catalogs.fractions.tariff_fractions.models import TariffFraction - from api.v1.modules.public.reference_data.carta_porte_codes.models import CartaPorte - - UOM_SAT = { - 'PZA': 'H87', 'KGS': 'KGM', 'MT': 'MTR', 'LT': 'LTR', - 'JGO': 'SET', 'GR': 'GRM', 'M2': 'MTK', 'PAR': 'PR', 'CAJA': 'XBX', - } - MAT_DESC = { - '01': 'Materia prima', - '02': 'Materia procesada', - '03': 'Materia terminada(producto terminado)', - '04': 'Materia para la industria manufacturera', - '05': 'Otra', - } - MATERIAL_EXCLUIDOS = {'TERR', 'INSTA', 'EDIF'} - - def _strip(text): - if not text: - return '' - return re.sub(r'[\r\n]+', ' ', str(text)).strip() - - def _dec(v): - if v is None: - return Decimal('0') - return Decimal(str(v)) - - compliance = ( - db.query(InvoiceComplianceMx) - .filter(InvoiceComplianceMx.invoice_id == invoice.id) - .first() - ) - pedimento = None - ped_date = None - regime = '' - aduana_str = compliance.aduana if compliance else '' - provider_rfc = shipped_to_rfc = sold_to_rfc = '' - - if compliance: - if compliance.pedimento_id: - pedimento = db.query(Pedimentos).filter(Pedimentos.id == compliance.pedimento_id).first() - if pedimento: - regime = pedimento.regime or '' - ped_date_row = ( - db.query(PedimentoDates) - .filter(PedimentoDates.pedimento_id == pedimento.id) - .first() - ) - ped_date = ped_date_row.entry_date if ped_date_row else None - - def _get_rfc(client_id): - if not client_id: - return '' - # Identificador fiscal efectivo: RFC (nacional) o TAX-ID (extranjero) - cp = db.query(ClientProvider).filter(ClientProvider.id == client_id).first() - return (cp.fiscal_id or '') if cp else '' - - provider_rfc = _get_rfc(compliance.provider_id) - shipped_to_rfc = _get_rfc(compliance.shipped_to_id) - sold_to_rfc = _get_rfc(compliance.sold_to_id) - - def _format_pedimento() -> str: - if not pedimento or not pedimento.pedimento_number: - return '' - return ( - f"{pedimento.year or ''} {pedimento.customs_office or ''}" - f" {pedimento.license or ''} {pedimento.pedimento_number or ''}" - ) - - items = ( - db.query(LineItem) - .options( - joinedload(LineItem.quantity), - joinedload(LineItem.financial), - joinedload(LineItem.customs), - joinedload(LineItem.description), - joinedload(LineItem.class_info), - joinedload(LineItem.unit_of_measure_info), - ) - .filter( - LineItem.invoice_id == invoice.id, - LineItem.tenant_id == invoice.tenant_id, - LineItem.company_id == invoice.company_id, - ) - .order_by(LineItem.line_number) - .all() - ) - - customs_fractions = { - item.customs.fraction for item in items if item.customs and item.customs.fraction - } - class_fractions = { - item.class_info.fraction for item in items - if item.class_info and item.class_info.fraction - } - umt_map = ( - {r.code: r.umt for r in db.query(TariffFraction.code, TariffFraction.umt) - .filter(TariffFraction.code.in_(customs_fractions)).all()} - if customs_fractions else {} - ) - cp_map = ( - {r.code: r for r in db.query(CartaPorte) - .filter(CartaPorte.code.in_(class_fractions)).all()} - if class_fractions else {} - ) - - # Agrupación por class_code - groups: dict = {} - group_order: list = [] - for item in items: - key = item.class_info.class_code if item.class_info else '' - if key not in groups: - group_order.append(key) - cls = item.class_info - c = item.customs - uom = item.unit_of_measure_info - class_frac = cls.fraction if cls else None - cp = cp_map.get(class_frac) if class_frac else None - mat_key = cls.material_key if cls else '' - tipo_mat = '' if mat_key in MATERIAL_EXCLUIDOS else '05' - groups[key] = { - 'class_code': key, - 'description': _strip(item.description.description_spanish if item.description else None), - 'cp_code': cp.code if cp else '', - 'cp_desc': cp.description if cp else '', - 'uom': uom.code if uom else '', - 'uom_sat': UOM_SAT.get(uom.code if uom else '', ''), - 'customs_frac': c.fraction if c else '', - 'tipo_mat': tipo_mat, - 'mat_desc': MAT_DESC.get(tipo_mat, ''), - 'quantity': _dec(item.quantity.quantity if item.quantity else None), - 'net_weight': _dec(item.quantity.net_weight if item.quantity else None), - 'gross_weight': _dec(item.quantity.gross_weight if item.quantity else None), - 'value_mxn': _dec(item.financial.value_mxn if item.financial else None), - 'value_usd': _dec(item.financial.value_usd if item.financial else None), - } - else: - g = groups[key] - g['quantity'] += _dec(item.quantity.quantity if item.quantity else None) - g['net_weight'] += _dec(item.quantity.net_weight if item.quantity else None) - g['gross_weight'] += _dec(item.quantity.gross_weight if item.quantity else None) - g['value_mxn'] += _dec(item.financial.value_mxn if item.financial else None) - g['value_usd'] += _dec(item.financial.value_usd if item.financial else None) - - ped_str = _format_pedimento() - fecha_str = ped_date.strftime('%d/%m/%Y') if ped_date else '' - - HEADERS = [ - 'FACTURA', 'CLASE', 'DESCRIPCION CLASE', 'CODIGO DE PRODUCTO Y SERVICIO', - 'DESCRIPCION CÓDIGO', 'CANTIDAD', 'UNIDAD DE MEDIDA', 'UNIDAD DE MEDIDA SAT', - 'PESO NETO', 'PESO BRUTO', 'FRACCION', 'VALOR MN', 'VALOR ME', - 'RFC PROVEEDOR', 'RFC ENVIADO A:', 'PEDIMENTO', 'FECHA INICIO PEDIMENTO', - 'MATERIAL PELIGROSO', 'ADUANA', 'TIPO DE MATERIAL', 'DESCRIPCION DE LA MATERIA', - 'TIPO DE DOCUMENTO', 'RFC IMPORTADOR', 'REGIMEN ADUANERO', - ] - - def generate(): - buf = io.StringIO() - writer = csv.writer(buf, delimiter=',', lineterminator='\r\n') - writer.writerow(HEADERS) - yield buf.getvalue() - for key in group_order: - g = groups[key] - buf.seek(0); buf.truncate(0) - writer.writerow([ - invoice.invoice_number or '', - g['class_code'], - g['description'], - g['cp_code'], - g['cp_desc'], - g['quantity'], - g['uom'], - g['uom_sat'], - g['net_weight'], - g['gross_weight'], - g['customs_frac'], - g['value_mxn'], - g['value_usd'], - provider_rfc, - shipped_to_rfc, - ped_str, - fecha_str, - 'NO', - aduana_str, - g['tipo_mat'], - g['mat_desc'], - '01', - sold_to_rfc, - regime, - ]) - yield buf.getvalue() - - safe_num = re.sub(r'[^\w\-]', '_', invoice.invoice_number or 'factura') - filename = f"{safe_num}_carta_porte_consolidada.csv" - return StreamingResponse( - generate(), - media_type='text/csv; charset=utf-8', - headers={"Content-Disposition": f'attachment; filename="{filename}"'}, - ) - - @staticmethod - def export_aviso_cruce(db: Session, invoice) -> "StreamingResponse": - """Interfaz Aviso Cruce (OF-7). 4 columnas: descripción, UMC, cantidad, valor USD.""" - import io - import csv - import re - from fastapi.responses import StreamingResponse - from sqlalchemy.orm import joinedload - from api.v1.modules.a76.items.models import LineItem - - def _strip(text): - if not text: - return '' - return re.sub(r'[\r\n]+', ' ', str(text)).strip() - - items = ( - db.query(LineItem) - .options( - joinedload(LineItem.description), - joinedload(LineItem.quantity), - joinedload(LineItem.financial), - joinedload(LineItem.unit_of_measure_info), - ) - .filter( - LineItem.invoice_id == invoice.id, - LineItem.tenant_id == invoice.tenant_id, - LineItem.company_id == invoice.company_id, - ) - .order_by(LineItem.line_number) - .all() - ) - - HEADERS = ['DESCRIPCIÓN', 'UMC', 'CANTIDAD', 'VALOR DÓLARES'] - - def _item_to_list(item: LineItem): - d = item.description - q = item.quantity - f = item.financial - uom = item.unit_of_measure_info - return [ - _strip(d.description_spanish if d else None), - uom.code if uom else '', - q.quantity if q else '', - f.value_usd if f else '', - ] - - def generate(): - buf = io.StringIO() - writer = csv.writer(buf, delimiter=',', lineterminator='\r\n') - writer.writerow(HEADERS) - yield buf.getvalue() - for item in items: - buf.seek(0) - buf.truncate(0) - writer.writerow(_item_to_list(item)) - yield buf.getvalue() - - safe_num = re.sub(r'[^\w\-]', '_', invoice.invoice_number or 'factura') - filename = f"{safe_num}_aviso_cruce.csv" - return StreamingResponse( - generate(), - media_type='text/csv; charset=utf-8', - headers={"Content-Disposition": f'attachment; filename="{filename}"'}, - ) - - @staticmethod - def copy_header_only( - db: Session, - invoice_id: int, - tenant_id: int, - company_id: int, - ) -> "models.InvoiceHeader": - """Copia el encabezado sin partidas, zeroeando totales acumulados. - Equivale a COPIA_ENC_A_SCAII del legacy Clarion.""" - original = db.query(models.InvoiceHeader).filter( - models.InvoiceHeader.id == invoice_id, - models.InvoiceHeader.tenant_id == tenant_id, - models.InvoiceHeader.company_id == company_id, - ).first() - if not original: - raise HTTPException(status_code=404, detail="Factura no encontrada") - - base_num = original.invoice_number or str(invoice_id) - candidate = f"{base_num}-ENC" - count = 1 - while db.query(models.InvoiceHeader).filter( - models.InvoiceHeader.invoice_number == candidate, - models.InvoiceHeader.tenant_id == tenant_id, - models.InvoiceHeader.company_id == company_id, - ).first(): - candidate = f"{base_num}-ENC-{count}" - count += 1 - - EXCLUDE_COLS = { - "id", "invoice_number", "status", "capture_date", "capture_user", - "who_processed", "processed_date", "process_log", "status_rec", - "status_rep", "comments_status", "vu_observations", "cfdi_uuid", - "path_pdf", "path_xml", "created_at", "updated_at", - } - header_data = { - c.name: getattr(original, c.name) - for c in models.InvoiceHeader.__table__.columns - if c.name not in EXCLUDE_COLS - } - header_data["invoice_number"] = candidate - header_data["status"] = models.InvoiceStatus.PENDING - header_data["party_count"] = 0 - new_invoice = models.InvoiceHeader(**header_data) - db.add(new_invoice) - db.flush() - - comp = db.query(models.InvoiceComplianceMx).filter( - models.InvoiceComplianceMx.invoice_id == invoice_id - ).first() - if comp: - EXCLUDE_COMP = { - "id", "invoice_id", "pedimento_id", "pedimento_r1", "pedimento_k1", - "vucem_operation_num", "electronic_signature", "certificate_number", - "niu_number", "code_signature", "edocument", "created_at", "updated_at", - } - comp_data = { - c.name: getattr(comp, c.name) - for c in models.InvoiceComplianceMx.__table__.columns - if c.name not in EXCLUDE_COMP - } - comp_data["invoice_id"] = new_invoice.id - comp_data["tenant_id"] = tenant_id - comp_data["company_id"] = company_id - db.add(models.InvoiceComplianceMx(**comp_data)) - - for log in db.query(models.InvoiceLogistics).filter( - models.InvoiceLogistics.invoice_id == invoice_id - ).all(): - EXCLUDE_LOG = {"id", "invoice_id", "created_at", "updated_at"} - log_data = { - c.name: getattr(log, c.name) - for c in models.InvoiceLogistics.__table__.columns - if c.name not in EXCLUDE_LOG - } - log_data["invoice_id"] = new_invoice.id - log_data["tenant_id"] = tenant_id - log_data["company_id"] = company_id - db.add(models.InvoiceLogistics(**log_data)) - - fin = db.query(models.InvoiceFinancials).filter( - models.InvoiceFinancials.invoice_id == invoice_id - ).first() - if fin: - EXCLUDE_FIN = {"id", "invoice_id", "created_at", "updated_at"} - ZERO_FIN = { - "value_mn", "value_me", "value_mc", - "customs_value_mn", "customs_value_me", - "raw_material_value_mn", "raw_material_value_me", - "aggregate_value_mn", "aggregate_value_me", "aggregate_value_mc", - "mexican_value_mn", "mexican_value_me", "mexican_value_mc", - "national_packaging_mn", "national_packaging_me", "national_packaging_mc", - "iva_mn", "iva_me", "iva_mc", "tax_value_me", - "total_quantity", "total_packages", "bundle_count", - "gross_weight", "net_weight", - } - fin_data = {} - for c in models.InvoiceFinancials.__table__.columns: - if c.name in EXCLUDE_FIN: - continue - fin_data[c.name] = 0 if c.name in ZERO_FIN else getattr(fin, c.name) - fin_data["invoice_id"] = new_invoice.id - fin_data["tenant_id"] = tenant_id - fin_data["company_id"] = company_id - db.add(models.InvoiceFinancials(**fin_data)) - - db.commit() - db.refresh(new_invoice) - return new_invoice - - @staticmethod - def export_aaduanal_rs(db: Session, invoice) -> "StreamingResponse": - """Interfaz AAduanal_RS (OF-10). 16 columnas, headers en inglés.""" - import io - import csv - import re - from fastapi.responses import StreamingResponse - from sqlalchemy.orm import joinedload - from api.v1.modules.a76.items.models import LineItem - from api.v1.modules.a76.invoices.models import InvoiceComplianceMx - from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos - - def _strip(text): - if not text: - return '' - return re.sub(r'[\r\n]+', ' ', str(text)).strip() - - def _fmt_frac(frac): - if not frac or len(frac) < 7: - return frac or '' - return f"{frac[0:2]}.{frac[2:4]}.{frac[4:6]}.{frac[6:]}" - - compliance = ( - db.query(InvoiceComplianceMx) - .filter(InvoiceComplianceMx.invoice_id == invoice.id) - .first() - ) - pedimento = None - if compliance and compliance.pedimento_id: - pedimento = db.query(Pedimentos).filter(Pedimentos.id == compliance.pedimento_id).first() - - ped_num = '' - if pedimento: - ped_num = ( - (pedimento.customs_office or '') - + (pedimento.license or '') - + (pedimento.pedimento_number or '') - ) - - inv_date = invoice.invoice_date.strftime('%Y%m%d') if invoice.invoice_date else '' - edocument = (compliance.edocument or '') if compliance else '' - vu_obs = re.sub(r'[\t\r\n]+', '', invoice.vu_observations or '') if invoice.vu_observations else '' - - items = ( - db.query(LineItem) - .options( - joinedload(LineItem.quantity), - joinedload(LineItem.customs), - joinedload(LineItem.description), - joinedload(LineItem.financial), - joinedload(LineItem.unit_of_measure_info), - joinedload(LineItem.part_info), - ) - .filter( - LineItem.invoice_id == invoice.id, - LineItem.tenant_id == invoice.tenant_id, - LineItem.company_id == invoice.company_id, - ) - .order_by(LineItem.line_number) - .all() - ) - - HEADERS = [ - 'indPedNum', 'ihdInvNum', 'ihdInvDate', 'ihdPartNum', 'ihdQty', - 'ihdPartSKU', 'ihdusunitVal', 'ihdustotalval', 'ihdmexwt', 'ihdPartCtryOrig', - 'ihdPgmCode', 'ihdTariffNum', 'partSpanDesc', 'cpartRegla8', 'COVE', 'e-document', - ] - - def _row(item: LineItem): - q = item.quantity - c = item.customs - d = item.description - f = item.financial - uom = item.unit_of_measure_info - part = item.part_info - return [ - ped_num, - invoice.invoice_number or '', - inv_date, - part.part_number if part else '', - q.quantity if q else '', - (uom.code or '')[:2] if uom else '', - f.unit_cost_usd if f else '', - f.value_usd if f else '', - q.gross_weight if q else '', - c.origin_country if c else '', - 'REGLA-8' if (c and c.octave_fraction) else '', - _fmt_frac(c.fraction if c else None), - _strip(d.description_spanish if d else None), - _fmt_frac(c.octave_fraction if c else None), - edocument, - vu_obs, - ] - - def generate(): - buf = io.StringIO() - writer = csv.writer(buf, delimiter=',', lineterminator='\r\n') - writer.writerow(HEADERS) - yield buf.getvalue() - for item in items: - buf.seek(0) - buf.truncate(0) - writer.writerow(_row(item)) - yield buf.getvalue() - - safe_num = re.sub(r'[^\w\-]', '_', invoice.invoice_number or 'factura') - filename = f"{safe_num}_aaduanal_rs.csv" - return StreamingResponse( - generate(), - media_type='text/csv; charset=utf-8', - headers={"Content-Disposition": f'attachment; filename="{filename}"'}, - ) - - @staticmethod - def export_caaarem(db: Session, invoice) -> "StreamingResponse": - """Interfaz CAAAREM (OF-9). 47 columnas desnormalizadas: fila 1 = encabezado + partida 1, filas 2+ = 27 vacíos + partida.""" - import io - import csv - import re - from fastapi.responses import StreamingResponse - from sqlalchemy.orm import joinedload - from api.v1.modules.a76.invoices.models import InvoiceComplianceMx - from api.v1.modules.a76.items.models import LineItem - from api.v1.modules.a76.clients_and_providers.models import ClientProvider - from api.v1.modules.a76.general_catalogs.company.models import Company - - def _strip(text): - if not text: - return '' - return re.sub(r'[\r\n]+', ' ', str(text)).strip() - - def _vinculacion(v): - # Clarion: CASE 0→'0', 1/2→'1' - if not v or v == '0': - return '0' - return '1' - - def _currency_to_country(currency): - # Clarion: USD→'USA', MXP→'MEX', else '' - if currency == 'USD': - return 'USA' - if currency in ('MXP', 'MXN'): - return 'MEX' - return '' - - compliance = ( - db.query(InvoiceComplianceMx) - .filter(InvoiceComplianceMx.invoice_id == invoice.id) - .first() - ) - financials = invoice.financials - logistics = invoice.logistics - - company = db.query(Company).filter( - Company.tenant_id == invoice.tenant_id, - Company.id == invoice.company_id, - ).first() - - provider = None - if compliance and compliance.provider_id: - provider = ( - db.query(ClientProvider) - .options(joinedload(ClientProvider.address)) - .filter( - ClientProvider.id == compliance.provider_id, - ClientProvider.tenant_id == invoice.tenant_id, - ) - .first() - ) - - items = ( - db.query(LineItem) - .options( - joinedload(LineItem.quantity), - joinedload(LineItem.customs), - joinedload(LineItem.description), - joinedload(LineItem.financial), - joinedload(LineItem.unit_of_measure_info), - joinedload(LineItem.part_info), - joinedload(LineItem.series), - ) - .filter( - LineItem.invoice_id == invoice.id, - LineItem.tenant_id == invoice.tenant_id, - LineItem.company_id == invoice.company_id, - ) - .order_by(LineItem.line_number) - .all() - ) - - HEADERS = [ - 'TIPO OPERACION', 'CLIENTE', 'PROVEEDOR', 'E-DOCUMENT', 'SUBDIVISION', - 'NUMERO DE FACTURA', 'NUMERO DE EXPORTADOR', 'FECHA FACTURA', 'VALOR TOTAL FACTURA', - 'CERTIFICADO DE ORIGEN', 'MONEDA FACTURACION', 'OBSERVACION', 'VINCULACION FACTURA', - 'INCOTERM', 'PAIS DE FACTURACION', 'PESO TOTAL FACTURA', 'GUIA MASTER', 'GUIA HOUSE', - 'BULTOS TOTAL', 'SEGURO', 'MONEDA SEGURO', 'FLETE', 'MONEDA FLETE', - 'EMBALAJE', 'MONEDA EMBALAJE', 'OTROS', 'MONEDA OTROS', - 'NUMERO DE PARTE', 'FRACCION ARANCELARIA', 'DESCRIPCION PEDIMENTO', 'CANTIDAD', - 'U.M. COMERCIAL', 'VALOR TOTAL', 'CANTIDAD TARIFA', 'U.M. TARIFA', - 'PAIS ORIGEN / DESTINO', 'PAIS COMPRADOR / VENDEDOR', 'VINCULACION', 'VALORACION', - 'PESO', 'BULTOS', 'VALOR AGREGADO', 'MONEDA VALOR AGREGADO', - 'SERIE', 'MARCA', 'MODELO', 'SUBMODELO', - ] - - def _header_vals(): - currency = financials.currency if financials else '' - niu = compliance.niu_number if compliance else '' - is_rail = bool(logistics and logistics.is_rail) - return [ - '1', - compliance.sold_to_header or '' if compliance else '', - compliance.provider_header or '' if compliance else '', - compliance.edocument or '' if compliance else '', - '1' if (compliance and compliance.subdivision) else '0', - invoice.invoice_number or '', - company.manufacturer_id or '' if company else '', - invoice.invoice_date.strftime('%Y%m%d') if invoice.invoice_date else '', - financials.value_me if financials else '', - '1' if (compliance and compliance.acts_as) else '0', - currency, - _strip(invoice.vu_observations), - _vinculacion(provider.linking if provider else None), - logistics.incoterm or '' if logistics else '', - _currency_to_country(currency), - financials.gross_weight if financials else '', - niu if is_rail else '', - niu if is_rail else '', - financials.total_packages if financials else '', - financials.insurance if financials else '', - currency, - financials.freight if financials else '', - currency, - financials.packaging if financials else '', - currency, - financials.other_increments if financials else '', - currency, - ] - - def _item_vals(item: LineItem): - q = item.quantity - c = item.customs - d = item.description - f = item.financial - uom = item.unit_of_measure_info - part = item.part_info - first_serie = item.series[0] if item.series else None - return [ - _strip(part.part_number if part else ''), - c.fraction if c else '', - _strip(d.description_spanish if d else ''), - q.quantity if q else '', - uom.customs_code if uom else '', - f.value_usd if f else '', - q.net_weight if q else '', # CANTIDAD TARIFA simplificado (caso UMClave=1) - uom.customs_code if uom else '', # U.M. TARIFA simplificado - c.origin_country if c else '', - provider.address.country if (provider and provider.address) else '', - _vinculacion(provider.linking if provider else None), - item.valuation_method or '', - q.net_weight if q else '', - q.package_quantity if q else '', - '0', - 'USD', - first_serie.serial_numbers if first_serie else 'S/S', - d.brand or 'S/M' if d else 'S/M', - d.model or 'S/M' if d else 'S/M', - first_serie.sub_model if (first_serie and first_serie.sub_model) else '', - ] - - def generate(): - buf = io.StringIO() - writer = csv.writer(buf, delimiter=',', lineterminator='\r\n') - writer.writerow(HEADERS) - yield buf.getvalue() - for idx, item in enumerate(items): - buf.seek(0) - buf.truncate(0) - if idx == 0: - writer.writerow(_header_vals() + _item_vals(item)) - else: - writer.writerow([''] * 27 + _item_vals(item)) - yield buf.getvalue() - - safe_num = re.sub(r'[^\w\-]', '_', invoice.invoice_number or 'factura') - filename = f"{safe_num}_caaarem.csv" - return StreamingResponse( - generate(), - media_type='text/csv; charset=utf-8', - headers={"Content-Disposition": f'attachment; filename="{filename}"'}, - ) - - @staticmethod - def export_cp_genesis(db: Session, invoice) -> "StreamingResponse": - """Interfaz Carta Porte Genesis (OF-11). 31 columnas, una fila por partida.""" - import io - import csv - import re - from fastapi.responses import StreamingResponse - from sqlalchemy.orm import joinedload - from api.v1.modules.a76.invoices.models import InvoiceComplianceMx - from api.v1.modules.a76.items.models import LineItem - from api.v1.modules.a76.clients_and_providers.models import ClientProvider - from api.v1.modules.a76.general_catalogs.company.models import Company - from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos - from api.v1.modules.a76.classes.models import Class - - UOM_MAP = { - 'PZA': 'H87', 'KGS': 'KGM', 'MT': 'MTR', 'LT': 'LTR', - 'JGO': 'XKI', 'LB': 'LBR', 'GAL': 'GLL', 'FT': 'LF', - } - - def _uom_to_sat(code): - return UOM_MAP.get(code or '', '') - - def _rfc_or_taxid(cp): - # Clarion: TIPOEXTNAC N→RFC, E→TAXID truncado a 9 chars - if not cp: - return '' - val = cp.fiscal_id or '' - if cp.type_nat_foreign == 'E': - return val[:9] - return val - - def _proceso(prov): - # Dirección de tráfico según ciudad del proveedor - if not prov or not prov.address: - return '' - city = (prov.address.city or '').upper() - if 'JUAREZ' in city: - return 'EXPORTACION' - if 'EL PASO' in city or 'ELPASO' in city: - return 'IMPORTACION' - return '' - - def _ped_map(ped): - # (IDocAdu, ClaveTM, DescripMP, CDocAdu) - if not ped: - return ('', '', '', '') - MAP = { - 'AF': ('ITR', '05', 'Otra', '18'), - 'V1': ('ITR', '05', 'Otra', '18'), - 'A1': ('IMD', '05', 'Otra', '01'), - 'A3': ('IMD', '05', 'Otra', '01'), - } - return MAP.get(ped.pedimento_code or '', ('', '', '', '')) - - def _fmt_ped(ped): - if not ped: - return '' - y = str(ped.year or '').zfill(2) - co = (ped.customs_office or '')[:2] - lic = (ped.license or '').zfill(4) - num = (ped.pedimento_number or '').zfill(7) - return f"{y} {co} {lic} {num}" - - compliance = ( - db.query(InvoiceComplianceMx) - .filter(InvoiceComplianceMx.invoice_id == invoice.id) - .first() - ) - financials = invoice.financials - - company = db.query(Company).filter( - Company.tenant_id == invoice.tenant_id, - Company.id == invoice.company_id, - ).first() - - provider = None - if compliance and compliance.provider_id: - provider = ( - db.query(ClientProvider) - .options(joinedload(ClientProvider.address)) - .filter( - ClientProvider.id == compliance.provider_id, - ClientProvider.tenant_id == invoice.tenant_id, - ) - .first() - ) - - shipped_to = None - if compliance and compliance.shipped_to_id: - shipped_to = ( - db.query(ClientProvider) - .options(joinedload(ClientProvider.address)) - .filter( - ClientProvider.id == compliance.shipped_to_id, - ClientProvider.tenant_id == invoice.tenant_id, - ) - .first() - ) - - pedimento = None - if compliance and compliance.pedimento_id: - pedimento = db.query(Pedimentos).filter(Pedimentos.id == compliance.pedimento_id).first() - - items = ( - db.query(LineItem) - .options( - joinedload(LineItem.quantity), - joinedload(LineItem.customs), - joinedload(LineItem.financial), - joinedload(LineItem.class_info).joinedload(Class.unit_of_measure_info), - ) - .filter( - LineItem.invoice_id == invoice.id, - LineItem.tenant_id == invoice.tenant_id, - LineItem.company_id == invoice.company_id, - ) - .order_by(LineItem.line_number) - .all() - ) - - HEADERS = [ - 'ORIGEN', 'NombreRemitente', 'RFC o NumRegIdtrib Remitente', 'ResidenciaFiscal Remitente', - 'DESTINO', 'NombreDestinatario', 'RFC o NumRegIdtrib Destinatario', 'ResidenciaFiscal Destinatario', - 'BienesTransp', 'Descripcion', 'Cantidad', 'ClaveUnidad', 'Unidad', - 'MaterialPeligroso', 'CveMaterialPeligroso', 'Embalaje', 'DescripEmbalaje', - 'PesoEnKg', 'ValorMercancia', 'Moneda', 'TranspInternac', - 'FraccionArancelaria', 'UUIDComercioExt', 'RegimenAduanero', 'TipoMateria', - 'DescripcionMateria', 'TipoDocumento', 'Numero de Pedimento', 'IdentDocAduanero', - 'RFCImpo', 'PaisOrigenDestino', - ] - - ped_fmt = _fmt_ped(pedimento) - idoc, clave_tm, descrip_mp, cdoc = _ped_map(pedimento) - currency = financials.currency if financials else '' - proceso = _proceso(provider) - - def _row(item: LineItem): - q = item.quantity - c = item.customs - f = item.financial - ci = item.class_info - uom_info = ci.unit_of_measure_info if ci else None - val_mer = f.value_mc if (f and currency == 'USD') else '' - return [ - provider.address.city if (provider and provider.address) else '', - provider.name if provider else '', - _rfc_or_taxid(provider), - 'USA', - shipped_to.address.city if (shipped_to and shipped_to.address) else '', - shipped_to.name if shipped_to else '', - _rfc_or_taxid(shipped_to), - 'MEX', - c.fraction if c else '', - ci.description_es if ci else '', - q.quantity if q else '', - _uom_to_sat(ci.unit_of_measure if ci else ''), - uom_info.description if uom_info else '', - '', '', # MaterialPeligroso, CveMaterialPeligroso - '', '', # Embalaje, DescripEmbalaje - q.net_weight if q else '', - val_mer, - currency, - proceso, - ci.fraction if ci else '', - '', # UUIDComercioExt - idoc, - clave_tm, - descrip_mp, - cdoc, - ped_fmt, - ped_fmt, - company.rfc if company else '', - provider.address.country if (provider and provider.address) else '', - ] - - def generate(): - buf = io.StringIO() - writer = csv.writer(buf, delimiter=',', lineterminator='\r\n') - writer.writerow(HEADERS) - yield buf.getvalue() - for item in items: - buf.seek(0) - buf.truncate(0) - writer.writerow(_row(item)) - yield buf.getvalue() - - safe_num = re.sub(r'[^\w\-]', '_', invoice.invoice_number or 'factura') - filename = f"{safe_num}_cp_genesis.csv" - return StreamingResponse( - generate(), - media_type='text/csv; charset=utf-8', - headers={"Content-Disposition": f'attachment; filename="{filename}"'}, - ) diff --git a/backend/api/v1/modules/a76/item_presets/dto.py b/backend/api/v1/modules/a76/item_presets/dto.py deleted file mode 100644 index e37655c9..00000000 --- a/backend/api/v1/modules/a76/item_presets/dto.py +++ /dev/null @@ -1,25 +0,0 @@ -from typing import Any, Dict, List, Optional -from pydantic import BaseModel, ConfigDict - -class ItemPresetBase(BaseModel): - name: str - description: Optional[str] = None - items: List[Dict[str, Any]] - -class ItemPresetCreate(ItemPresetBase): - """Schema for creating a new item preset""" - pass - -class ItemPresetUpdate(BaseModel): - """Schema for updating an existing item preset""" - name: Optional[str] = None - description: Optional[str] = None - items: Optional[List[Dict[str, Any]]] = None - -class ItemPresetResponse(ItemPresetBase): - """Schema for returning an item preset""" - id: int - tenant_id: int - company_id: int - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/a76/item_presets/models.py b/backend/api/v1/modules/a76/item_presets/models.py deleted file mode 100644 index a63dcd4b..00000000 --- a/backend/api/v1/modules/a76/item_presets/models.py +++ /dev/null @@ -1,18 +0,0 @@ -from typing import Any, Dict, List -from sqlalchemy import String, Integer, JSON -from sqlalchemy.orm import Mapped, mapped_column -from core.database import Base -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin - - -class ItemPreset(Base, TenantScopedMixin, TimestampMixin): - __tablename__ = "item_presets" - __table_args__ = {"schema": "a76"} - - id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) - name: Mapped[str] = mapped_column(String(100), nullable=False) - description: Mapped[str | None] = mapped_column(String(500), nullable=True) - items: Mapped[List[Dict[str, Any]]] = mapped_column(JSON, nullable=False, default=[]) - - def __repr__(self): - return f"" diff --git a/backend/api/v1/modules/a76/item_presets/routes.py b/backend/api/v1/modules/a76/item_presets/routes.py deleted file mode 100644 index 0c5b356b..00000000 --- a/backend/api/v1/modules/a76/item_presets/routes.py +++ /dev/null @@ -1,72 +0,0 @@ -from typing import List, Any, Dict -from fastapi import APIRouter, Depends, Query, HTTPException -from sqlalchemy.orm import Session -from core.database import get_core_db -from core.security import get_current_user, validate_access_to_resource -from . import services, dto - -router = APIRouter() - -@router.get("/", response_model=List[dto.ItemPresetResponse]) -def list_item_presets( - company_id: int = Query(...), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """List all item presets for a company""" - tenant_id = validate_access_to_resource(db, company_id, current_user) - return services.get_presets(db, tenant_id, company_id) - -@router.post("/", response_model=dto.ItemPresetResponse) -def create_item_preset( - data: dto.ItemPresetCreate, - company_id: int = Query(...), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """Create a new item preset""" - tenant_id = validate_access_to_resource(db, company_id, current_user) - return services.create_preset(db, tenant_id, company_id, data) - -@router.get("/{preset_id}", response_model=dto.ItemPresetResponse) -def get_item_preset( - preset_id: int, - company_id: int = Query(...), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """Get a specific item preset""" - tenant_id = validate_access_to_resource(db, company_id, current_user) - preset = services.get_preset_by_id(db, preset_id, tenant_id, company_id) - if not preset: - raise HTTPException(status_code=404, detail="Item preset not found") - return preset - -@router.put("/{preset_id}", response_model=dto.ItemPresetResponse) -def update_item_preset( - preset_id: int, - data: dto.ItemPresetUpdate, - company_id: int = Query(...), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """Update an item preset""" - tenant_id = validate_access_to_resource(db, company_id, current_user) - preset = services.update_preset(db, preset_id, tenant_id, company_id, data) - if not preset: - raise HTTPException(status_code=404, detail="Item preset not found") - return preset - -@router.delete("/{preset_id}") -def delete_item_preset( - preset_id: int, - company_id: int = Query(...), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """Delete an item preset""" - tenant_id = validate_access_to_resource(db, company_id, current_user) - success = services.delete_preset(db, preset_id, tenant_id, company_id) - if not success: - raise HTTPException(status_code=404, detail="Item preset not found") - return {"status": "success", "message": "Preset deleted"} diff --git a/backend/api/v1/modules/a76/item_presets/services.py b/backend/api/v1/modules/a76/item_presets/services.py deleted file mode 100644 index 85274c1a..00000000 --- a/backend/api/v1/modules/a76/item_presets/services.py +++ /dev/null @@ -1,63 +0,0 @@ -from typing import List, Optional -from sqlalchemy.orm import Session -from .models import ItemPreset -from .dto import ItemPresetCreate, ItemPresetUpdate - -def get_presets(db: Session, tenant_id: int, company_id: int) -> List[ItemPreset]: - """Get all presets for a specific company and tenant""" - return db.query(ItemPreset).filter( - ItemPreset.tenant_id == tenant_id, - ItemPreset.company_id == company_id - ).all() - -def get_preset_by_id(db: Session, preset_id: int, tenant_id: int, company_id: int) -> Optional[ItemPreset]: - """Get a specific preset by ID""" - return db.query(ItemPreset).filter( - ItemPreset.id == preset_id, - ItemPreset.tenant_id == tenant_id, - ItemPreset.company_id == company_id - ).first() - -def create_preset(db: Session, tenant_id: int, company_id: int, data: ItemPresetCreate) -> ItemPreset: - """Create a new item preset""" - new_preset = ItemPreset( - tenant_id=tenant_id, - company_id=company_id, - name=data.name, - description=data.description, - items=data.items - ) - db.add(new_preset) - db.commit() - db.refresh(new_preset) - return new_preset - -def update_preset( - db: Session, - preset_id: int, - tenant_id: int, - company_id: int, - data: ItemPresetUpdate -) -> Optional[ItemPreset]: - """Update an existing item preset""" - preset = get_preset_by_id(db, preset_id, tenant_id, company_id) - if not preset: - return None - - update_data = data.model_dump(exclude_unset=True) - for key, value in update_data.items(): - setattr(preset, key, value) - - db.commit() - db.refresh(preset) - return preset - -def delete_preset(db: Session, preset_id: int, tenant_id: int, company_id: int) -> bool: - """Delete an item preset""" - preset = get_preset_by_id(db, preset_id, tenant_id, company_id) - if not preset: - return False - - db.delete(preset) - db.commit() - return True diff --git a/backend/api/v1/modules/a76/items/__init__.py b/backend/api/v1/modules/a76/items/__init__.py deleted file mode 100644 index 42d7abcf..00000000 --- a/backend/api/v1/modules/a76/items/__init__.py +++ /dev/null @@ -1,23 +0,0 @@ -""" -Items module - Annex 76 Compliance -""" - -# Import models in correct order to avoid circular dependencies -# LineItem must be imported before models that reference it -from .line_financials.models import LineFinancial -from .line_quantities.models import LineQuantity -from .line_customs.models import LineCustom -from .line_descriptions.models import LineDescription -from .line_references.models import LineReference -from .models import LineItem, CTMReceipt, SubassemblyEntry - -__all__ = [ - "LineItem", - "LineFinancial", - "LineQuantity", - "LineCustom", - "LineDescription", - "LineReference", - "CTMReceipt", - "SubassemblyEntry", -] diff --git a/backend/api/v1/modules/a76/items/common/common_validators.py b/backend/api/v1/modules/a76/items/common/common_validators.py deleted file mode 100644 index 4397f4fd..00000000 --- a/backend/api/v1/modules/a76/items/common/common_validators.py +++ /dev/null @@ -1,33 +0,0 @@ -from sqlalchemy import func -from core.exceptions import ErrorCollector -from .. import models -from sqlalchemy.orm import Session - - -def item_exists(db: Session, item_line: int, tenant_id: int, company_id: int): - item_exists = ( - db.query(models.LineItem) - .filter( - models.LineItem.line_number == item_line, - models.LineItem.tenant_id == tenant_id, - models.LineItem.company_id == company_id, - ) - .first() - ) - - return item_exists - - -def count_items(db: Session, invoice_id: int, tenant_id: int, company_id: int): - count = ( - db.query(func.count()) - .select_from(models.LineItem) - .filter( - models.LineItem.invoice_id == invoice_id, - models.LineItem.tenant_id == tenant_id, - models.LineItem.company_id == company_id, - ) - .scalar() - ) - - return count diff --git a/backend/api/v1/modules/a76/items/common/fractions.py b/backend/api/v1/modules/a76/items/common/fractions.py deleted file mode 100644 index 8f4fc501..00000000 --- a/backend/api/v1/modules/a76/items/common/fractions.py +++ /dev/null @@ -1,155 +0,0 @@ -import asyncio -from datetime import datetime -from typing import Optional, Tuple - -from api.v1.modules.a76.general_catalogs.company.models import Company -from api.v1.modules.a76.general_catalogs.fractions.historical_tariff_fractions.models import HistoricalTariffFraction -from api.v1.modules.sitar.tlcs import TLCSService -from api.v1.modules.sitar.prosec import ProsecService -from api.v1.modules.sitar.fracciones import FraccionesService -from sqlalchemy.orm import Session -from sqlalchemy import exists -from core.exceptions import ErrorCollector - -def search_historical_fraction(db: Session, fraction: str, country: str, fraction_type: str, sector: str, invoice_date: Optional[datetime], errors: Optional[ErrorCollector]) -> Tuple[Optional[str], float]: - """Search for historical fraction data - This corresponds to BUSCA_FRACCION_HISTORICA in original Clarion code - - Returns: - Tuple of (rate_im, adv_impo) - """ - # Placeholder for historical search - historical_exists = db.query(exists().where(HistoricalTariffFraction.historical_fraction == fraction)).scalar() - if not historical_exists: - errors.add_error( - field=f"fraction, country, fraction_type, sector", - message=f"La fraccion {fraction}, con pais {country}, con preferencia {fraction_type} y sector {sector} no existe.", - solution=["Revisar que si exista la preferencia para esta fracción en caso de ser historico, registrarlo en el catálogo de fracciones historicas."], - code="HISTORICAL_FRACTION_NOT_FOUND", - ) - else: - historical_exists = db.query(HistoricalTariffFraction).filter( - HistoricalTariffFraction.historical_fraction == fraction, - HistoricalTariffFraction.country == country, - HistoricalTariffFraction.fraction_type == fraction_type, - HistoricalTariffFraction.sector == sector, - HistoricalTariffFraction.publication_date <= invoice_date, - ).first() - - return historical_exists.import_tax_rate, historical_exists.import_tax_rate - - -def search_fraction_preference( - db: Session, - country: str, - fraccion: str, - fraction_type: str, - sector: Optional[str] = None, - invoice_date: Optional[datetime] = None, - errors: Optional[ErrorCollector] = None -) -> Tuple[Optional[str], float]: - """Search fraction preference and return rate_im and adv_impo - - Args: - country: Country code - fraction_type: Type of fraction (e.g., 'TLCS', 'PROSEC') - company: Company object with configuration - fraccion: Tariff fraction code to search - sector: Sector code (required for PROSEC searches) - - Returns: - Tuple of (rate_im, adv_impo) where: - - rate_im: Tax rate as string (e.g., "EXE", "5.0%") - - adv_impo: Numeric ad valorem rate - """ - rate_im = None - adv_impo = 0.0 - fraccion_8 = fraccion[:8] - - country_group = "USA" if country == "MEX" else country - - """" TLCS Search """ - if fraction_type.upper() == "TLCS": - try: - # Get TLCS service instance - tlcs_service = TLCSService.get_instance() - - # Fetch TLCS data using the service - tlcs_data = asyncio.run( - tlcs_service.search(fraccion=fraccion_8, pais=country_group, limit=100) - ) - - if not tlcs_data: - rate_im, adv_impo = search_historical_fraction(db=db, fraction=fraccion_8, country=country, fraction_type=fraction_type, sector=sector, invoice_date=invoice_date, errors=errors) - else: - first_record = tlcs_data[0] - rate_im = first_record.TASATXT - adv_impo = float(first_record.TASA1NUM or 0.0) - - except Exception as e: - print(f"Error fetching Fracciones data from SITAR API: {e}") - - """ ALADI Search """ - if fraction_type.upper() == "ALADI": - rate_im, adv_impo = search_historical_fraction(db=db, fraction=fraccion_8, country=country, fraction_type=fraction_type, sector=sector, invoice_date=invoice_date, errors=errors) - - """ PROSEC Search """ - if fraction_type.upper() == "PROSEC": - try: - # Búsqueda en API de PROSEC (TARIFA_AS..sProsec) - prosec_service = ProsecService.get_instance() - - # Buscar primero con ARTICULO = '4to' - prosec_data = asyncio.run( - prosec_service.search( - fraccion=fraccion_8, sector=sector, articulo="4to", limit=100 - ) - ) - - # Si no se encuentra con '4to', buscar con '5to' - if not prosec_data: - prosec_data = asyncio.run( - prosec_service.search( - fraccion=fraccion_8, sector=sector, articulo="5to", limit=100 - ) - ) - - if not prosec_data: - rate_im, adv_impo = search_historical_fraction(db=db, fraction=fraccion_8, country=country, fraction_type=fraction_type, sector=sector, invoice_date=invoice_date, errors=errors) - else: - first_record = prosec_data[0] - rate_im = first_record.TASATXT - adv_impo = float(first_record.TASANUM or 0.0) - - except Exception as e: - print(f"Error fetching Fracciones data from SITAR API: {e}") - - """ General search fallback """ - if fraction_type.upper() == "GENERAL": - # Búsqueda en API de Fracciones (sFracciones) - try: - fracciones_service = FraccionesService.get_instance() - - # Extraer fracción (primeros 8 caracteres) e nico (caracteres 9-10) - nico = fraccion[8:10] if len(fraccion) >= 10 else None - - # Buscar por fracción e histórico - fracciones_data = asyncio.run( - fracciones_service.search( - fraccion=fraccion_8, nico=nico, limit=100 - ) - ) - - if not fracciones_data: - rate_im, adv_impo = search_historical_fraction(db=db, fraction=fraccion_8, country=country, fraction_type=fraction_type, sector=sector, invoice_date=invoice_date, errors=errors) - else: - # Se encontraron registros, tomar el primero - first_record = fracciones_data[0] - rate_im = first_record.ADVIMPOTXT # AdvImpoTxt - adv_impo = float(first_record.ADVIMPONUM or 0.0) - - - except Exception as e: - print(f"Error fetching Fracciones data from SITAR API: {e}") - - return rate_im, adv_impo diff --git a/backend/api/v1/modules/a76/items/exports/validators/calculations.py b/backend/api/v1/modules/a76/items/exports/validators/calculations.py deleted file mode 100644 index f49a195e..00000000 --- a/backend/api/v1/modules/a76/items/exports/validators/calculations.py +++ /dev/null @@ -1,280 +0,0 @@ -from sqlalchemy.orm import Session -from api.v1.modules.a76.invoices.models import InvoiceFinancials, InvoiceHeader, InvoiceLogistics - -from ...models import LineItem -from ...series.models import Serie -from api.v1.modules.a76.items.models import LineItem -from api.v1.modules.a24.fa.fa_item_lines.models import FaLineItem -from api.v1.modules.a76.classes.models import Class -from api.v1.modules.a76.app_settings.service import AppSettingsService -from decimal import Decimal - - -def apply_calculations( - db: Session, line: LineItem, tenant_id: int, company_id: int, line_number: int -): - # 0. Obtener parámetros de configuración para validaciones dinámicas - settings = AppSettingsService.get_resolved_settings(db, int(tenant_id), int(company_id)) - - def find_in_obj(obj, target_key): - if isinstance(obj, dict): - for k, v in obj.items(): - if k.lower() == target_key.lower(): - return v - res = find_in_obj(v, target_key) - if res is not None: return res - elif hasattr(obj, '__dict__'): - dict_rep = obj.__dict__ - for k, v in dict_rep.items(): - if k.startswith('_'): continue - if k.lower() == target_key.lower(): - return v - res = find_in_obj(v, target_key) - if res is not None: return res - return None - - # Parámetro SCAF: Calcular costo unitario en base a valor total - calc_costo_unit = find_in_obj(settings, 'calcularcostounitarioenbaseavalortotal') or \ - find_in_obj(settings, 'calcularcostounitarioenbaseavalortotalscaf') - - if str(calc_costo_unit) == "1" and line.financial and (line.financial.unit_cost_capture or 0) == 0: - qty = line.quantity.quantity or Decimal("0") - if qty > 0: - total_val = line.financial.value_usd or line.financial.value_mxn or Decimal("0") - if total_val > 0: - line.financial.unit_cost_capture = total_val / qty - - calculate_values(db, line, tenant_id, company_id) - - # ========================================== - # LLEVASERIE / LlevaCodFDA defaults - # ========================================== - # EqiPex:LlevaCodFDA = 'N' - line.has_fda_code = False - - # ========================================== - # PAGO IMPUESTO default: SisExp:PagoImpuesto - # ========================================== - if line.tax_payment is None: - pref_pago = find_in_obj(settings, 'pagoimpuesto') - if pref_pago: - line.tax_payment = True if str(pref_pago).lower() == 'si' else False - else: - line.tax_payment = False - - # ========================================== - # FORMA DE PAGO default: SisExp:FormaPago - # ========================================== - if not line.payment_method: - pref_forma = find_in_obj(settings, 'formapago') - line.payment_method = pref_forma or "5" - - # ========================================== - # SUBPARTIDAS: EsSubPartida / ContieneSubP / IncuyeSubPartidas - # ========================================== - fa_data: FaLineItem = getattr(line, "fa_data", None) - if fa_data is not None: - if fa_data.is_subitem is None: - fa_data.is_subitem = False - - if not fa_data.is_subitem: - # Es partida principal — subitem_number se fuerza a 0 - fa_data.subitem_number = 0 - - # ContieneSubP: verificar si ya existen subitems en DB que referencian esta línea - # (útil en updates; en create siempre será False porque la línea aún no existe) - existing_subitems = ( - db.query(FaLineItem) - .join(LineItem, FaLineItem.id == LineItem.id) - .filter( - LineItem.invoice_id == line.invoice_id, - LineItem.line_number == line_number, - LineItem.tenant_id == tenant_id, - LineItem.company_id == company_id, - FaLineItem.is_subitem == True, - FaLineItem.subitem_number == line_number, - ) - .count() - ) - fa_data.contains_subitems = existing_subitems > 0 - - invoice_date = db.query(InvoiceHeader.invoice_date).filter(InvoiceHeader.id == line.invoice_id, InvoiceHeader.tenant_id == tenant_id, InvoiceHeader.company_id == company_id).scalar() - - line.depreciation_date = invoice_date - - part_info = getattr(line, "part_info", None) - if (not line.description.description_spanish and not line.description.description_english) and part_info and (part_info.description_spanish and part_info.description_english): - line.description.description_spanish = part_info.description_spanish - line.description.description_english = part_info.description_english - else: - if not line.description.description_spanish: - class_desc = ( - db.query(Class.description_es, Class.description_en) - .filter(Class.id == line.class_id, Class.tenant_id == tenant_id, Class.company_id == company_id) - .first() - ) - if class_desc: - line.description.description_spanish = class_desc[0] - if not line.description.description_english: - line.description.description_english = class_desc[1] - - -def calculate_values( - db: Session, - line: LineItem, - tenant_id: int, - company_id: int, -) -> None: - """ - Calcula valores financieros y copia campos de la línea de importación referenciada. - - Traduce el CALCULOS ROUTINE de Clarion: - - Busca la factura de importación por fa_data.search_invoice (TEM → DEF como fallback) - - Copia clase, unidad de medida, fracción (si fa_data.discharge), país, tipo fracción, - bultos y descripción inglés desde la línea de importación encontrada - - Calcula valores en moneda (USD/MXN/MC) según la moneda de la factura - """ - fa_data: FaLineItem = getattr(line, "fa_data", None) - - # ========================================== - # BUSCAR FACTURA DE IMPORTACIÓN (TEM → DEF) - # EqiFim:FacturaImpo = fa_data.search_invoice / EqiPim:LineaImpo = fa_data.search_line - # ========================================== - import_line = None - - import_invoice_number = fa_data.search_invoice if fa_data else None - import_line_number = fa_data.search_line if fa_data else None - - - if import_invoice_number and import_line_number: - # 1. Intentar TEM (Importación Temporal) - tem_invoice = ( - db.query(InvoiceHeader) - .filter( - InvoiceHeader.invoice_number == import_invoice_number, - InvoiceHeader.invoice_type == "TEM", - InvoiceHeader.tenant_id == tenant_id, - InvoiceHeader.company_id == company_id, - ) - .first() - ) - if tem_invoice: - import_line = ( - db.query(LineItem) - .filter( - LineItem.invoice_id == tem_invoice.id, - LineItem.line_number == import_line_number, - LineItem.tenant_id == tenant_id, - LineItem.company_id == company_id, - ) - .first() - ) - - # 2. Si no hay TEM, intentar DEF (Importación Definitiva) - if not import_line: - def_invoice = ( - db.query(InvoiceHeader) - .filter( - InvoiceHeader.invoice_number == import_invoice_number, - InvoiceHeader.invoice_type == "DEF", - InvoiceHeader.tenant_id == tenant_id, - InvoiceHeader.company_id == company_id, - ) - .first() - ) - if def_invoice: - import_line = ( - db.query(LineItem) - .filter( - LineItem.invoice_id == def_invoice.id, - LineItem.line_number == import_line_number, - LineItem.tenant_id == tenant_id, - LineItem.company_id == company_id, - ) - .first() - ) - - # ========================================== - # COPIAR CAMPOS DESDE LÍNEA DE IMPORTACIÓN - # EqiPex:Clase, UnidadMedida, FraccionExpo (condicional), PaisOrigen, - # TipoFraccion, CantBultos, ClaveBultos, DescripcionE - # ========================================== - if import_line: - line.class_id = import_line.class_id - line.unit_of_measure = import_line.unit_of_measure - - # Fracción: copiar solo si fa_data.discharge == True (≡ ColumnaV != '') - if fa_data and fa_data.discharge and import_line.customs: - line.customs.fraction = import_line.customs.fraction - - if import_line.customs: - line.customs.origin_country = import_line.customs.origin_country - line.customs.fraction_type = import_line.customs.fraction_type - - if import_line.quantity: - line.quantity.package_quantity = import_line.quantity.package_quantity - line.quantity.package_id = import_line.quantity.package_id - - if import_line.description and not line.description.description_english: - # Only fill from import if the user hasn't captured their own English description - line.description.description_english = import_line.description.description_english - - # ========================================== - # CÁLCULOS DE VALORES EN MONEDA - # Una sola fuente: currency_type (USD/ME, MXN/MN) cuando está presente, paridad con import y CSV. - # ========================================== - result = ( - db.query( - InvoiceFinancials.currency, - InvoiceFinancials.currency_type, - InvoiceFinancials.exchange_rate, - ) - .filter( - InvoiceFinancials.invoice_id == line.invoice_id, - InvoiceFinancials.tenant_id == tenant_id, - InvoiceFinancials.company_id == company_id, - ) - .first() - ) - if not result: - return - - if not line.financial or not line.quantity: - return - - currency, currency_type, exchange_rate = result - if currency_type in ("USD", "ME"): - currency = "foreign" - elif currency_type in ("MXN", "MN"): - currency = "local" - - # Evitar None * None / NoneType * Decimal al guardar sin cantidad o sin costo capturado - qty = ( - line.quantity.quantity - if line.quantity.quantity is not None - else Decimal("0") - ) - capture = ( - line.financial.unit_cost_capture - if line.financial.unit_cost_capture is not None - else Decimal("0") - ) - - if currency == "foreign": # ME - line.financial.unit_cost_usd = capture - line.financial.value_usd = capture * qty - line.financial.unit_cost_mxn = capture * (exchange_rate or 1) - line.financial.value_mxn = line.financial.unit_cost_mxn * qty - line.financial.value_mc = capture * qty - elif currency == "local": # MN - line.financial.unit_cost_mxn = capture - line.financial.value_mxn = line.financial.unit_cost_mxn * qty - line.financial.unit_cost_usd = capture / (exchange_rate or 1) - line.financial.value_usd = line.financial.unit_cost_usd * qty - line.financial.value_mc = line.financial.unit_cost_usd * qty - elif currency == "manual": # MC - line.financial.unit_cost_usd = capture / (exchange_rate or 1) - line.financial.value_usd = line.financial.unit_cost_usd * qty - line.financial.unit_cost_mxn = line.financial.unit_cost_usd * (exchange_rate or 1) - line.financial.value_mxn = line.financial.unit_cost_mxn * qty - line.financial.value_mc = capture * qty diff --git a/backend/api/v1/modules/a76/items/exports/validators/common.py b/backend/api/v1/modules/a76/items/exports/validators/common.py deleted file mode 100644 index 4acd302d..00000000 --- a/backend/api/v1/modules/a76/items/exports/validators/common.py +++ /dev/null @@ -1,480 +0,0 @@ -from sqlalchemy import exists -from sqlalchemy.orm import Session -from api.v1.modules.a76.invoices.common.common_validators import invoice_id_required -from core.exceptions import ErrorCollector -from sqlalchemy import func - -from ...common.fractions import search_fraction_preference -from ...common.common_validators import item_exists -from ...models import LineItem -from ...line_customs.models import FractionType, LineCustom -from api.v1.modules.a76.items.schemas import LineItemCreate -from api.v1.modules.a76.items.exports.validators.calculations import apply_calculations -from api.v1.modules.a76.invoices.models import InvoiceHeader -from api.v1.modules.a76.classes.models import Class -from api.v1.modules.a76.general_catalogs.units_of_measure.models import UnitOfMeasure -from api.v1.modules.a76.general_catalogs.packages.models import Package -from api.v1.modules.sitar.fracciones_usa.catalog_resolve import ( - resolve_american_fraction_from_sitar, - store_canonical_american_code, -) -from api.v1.modules.a24.fa.fa_item_lines.models import FaLineItem -from api.v1.modules.public.reference_data.countries.models import Country -from api.v1.modules.a76.general_catalogs.sectors.models import Sector -from api.v1.modules.public.reference_data.valuation_methods.models import ( - ValuationMethod, -) -from api.v1.modules.a76.parts.models import Part -from api.v1.modules.a76.app_settings.service import AppSettingsService -from api.v1.modules.a76.general_catalogs.company.models import Company - - -def validate_common( - db: Session, - line: LineItemCreate, - tenant_id: int, - company_id: int, - errors: ErrorCollector, - line_number: int, -): - invoice: InvoiceHeader = invoice_id_required( - db, line.invoice_id, tenant_id, company_id, errors - ) - line_item: LineItem = item_exists(db, line.line_number, tenant_id, company_id) - - fecha_factura = invoice.invoice_date if invoice else None - fraction = None - - class_ = db.query(Class).filter(Class.id == line.class_id).first() - if not class_: - errors.add_error( - field=f"line[{line_number}].class_id", - message="La clase especificada no existe.", - solution=["Darla de alta en el catalogo de clases."], - code="CLASS_NOT_FOUND", - ) - else: - if not line.unit_of_measure and not class_.unit_of_measure: - errors.add_error( - field=f"line[{line_number}].unit_of_measure", - message="La unidad de medida es obligatoria para la clase especificada.", - solution=["Proporciona una unidad de medida valida."], - code="UNIT_OF_MEASURE_REQUIRED", - ) - - if not line.customs.fraction: - if not line_item: - if not class_.fraction: - errors.add_error( - field=f"line[{line_number}].customs.fraction", - message="La fracción arancelaria es obligatoria para la clase especificada.", - solution=["Proporciona una fracción arancelaria valida."], - code="FRACTION_REQUIRED", - ) - else: - fraction = class_.fraction - else: - if not line.customs.fraction: - if not class_.fraction: - errors.add_error( - field=f"line[{line_number}].customs.fraction", - message="La fracción arancelaria es obligatoria para la clase especificada.", - solution=["Proporciona una fracción arancelaria valida."], - code="FRACTION_REQUIRED", - ) - else: - fraction = class_.fraction - else: - if line_item: - fraction = line.customs.fraction - - if not line.description.description_spanish and not class_.description_es: - errors.add_error( - field=f"line[{line_number}].description.description_spanish", - message="La descripción en español es obligatoria para la clase especificada.", - solution=["Proporciona una descripción en español valida."], - code="DESCRIPTION_SPANISH_REQUIRED", - ) - - if not line.description.description_english and not class_.description_en: - errors.add_error( - field=f"line[{line_number}].description.description_english", - message="La descripción en inglés es obligatoria para la clase especificada.", - solution=["Proporciona una descripción en inglés valida."], - code="DESCRIPTION_ENGLISH_REQUIRED", - ) - - if line.quantity.quantity and line.quantity.quantity <= 0: - errors.add_error( - field=f"line[{line_number}].quantity.quantity", - message="La cantidad debe ser mayor a cero.", - solution=["Proporciona una cantidad valida."], - code="QUANTITY_MUST_BE_GREATER_THAN_ZERO", - ) - - if line.unit_of_measure: - um = ( - db.query(func.count(UnitOfMeasure.id)) - .filter( - UnitOfMeasure.id == line.unit_of_measure, - UnitOfMeasure.tenant_id == tenant_id, - UnitOfMeasure.company_id == company_id, - ) - .scalar() - ) - if um == 0: - errors.add_error( - field=f"line[{line_number}].unit_of_measure", - message="La unidad de medida especificada no existe.", - solution=["Proporciona una unidad de medida valida."], - code="UNIT_OF_MEASURE_NOT_FOUND", - ) - - if line.quantity.package_id: - package = ( - db.query(func.count(Package.id)) - .filter( - Package.id == line.quantity.package_id, - Package.tenant_id == tenant_id, - Package.company_id == company_id, - ) - .scalar() - ) - if package == 0: - errors.add_error( - field=f"line[{line_number}].quantity.package_id", - message="El paquete especificado no existe.", - solution=["Proporciona un paquete valido."], - code="PACKAGE_NOT_FOUND", - ) - if not line.quantity.package_quantity: - errors.add_error( - field=f"line[{line_number}].quantity.package_quantity", - message="La cantidad de paquetes es obligatoria cuando se proporciona el paquete.", - solution=["Proporciona una cantidad de paquetes valida."], - code="PACKAGE_QUANTITY_REQUIRED", - ) - if line.quantity.package_quantity <= 0: - errors.add_error( - field=f"line[{line_number}].quantity.package_quantity", - message="La cantidad de paquetes debe ser mayor a cero.", - solution=["Proporciona una cantidad de paquetes valida."], - code="PACKAGE_QUANTITY_MUST_BE_GREATER_THAN_ZERO", - ) - else: - if line.quantity.package_quantity and (line.quantity.package_quantity > 0 and not line.quantity.package_id): - errors.add_error( - field=f"line[{line_number}].quantity.package_id", - message="El paquete es obligatorio cuando se proporciona la cantidad de paquetes.", - solution=["Proporciona un paquete valido."], - code="PACKAGE_ID_REQUIRED", - ) - - # ========================================== - # FA DATA: PROCEDENCIA Y REFERENCIA DE IMPORTACIÓN (Col. C / D / E / H) - # ========================================== - fa_data: FaLineItem = getattr(line, "fa_data", None) - - if fa_data and fa_data.movement_type_import: - # Col. C: debe ser 'TEM' o 'DEF' - if fa_data.movement_type_import.upper() not in ("TEM", "DEF"): - errors.add_error( - field=f"line[{line_number}].fa_data.movement_type_import", - message=f"La Procedencia '{fa_data.movement_type_import}' no es válida. Debe ser 'TEM' o 'DEF'.", - solution=["Capturar una procedencia válida como TEM o DEF."], - code="MOVEMENT_TYPE_IMPORT_INVALID", - ) - elif fa_data.search_invoice: - tipo_label = "Temporales" if fa_data.movement_type_import.upper() == "TEM" else "Definitivas" - tipo_label_sg = "Temporal" if fa_data.movement_type_import.upper() == "TEM" else "Definitiva" - - # Col. D: validar que la factura de importación exista - import_invoice: InvoiceHeader = ( - db.query(InvoiceHeader) - .filter( - InvoiceHeader.invoice_number == fa_data.search_invoice, - InvoiceHeader.invoice_type == fa_data.movement_type_import.upper(), - InvoiceHeader.tenant_id == tenant_id, - InvoiceHeader.company_id == company_id, - ) - .first() - ) - if not import_invoice: - errors.add_error( - field=f"line[{line_number}].fa_data.search_invoice", - message=f"La Factura '{fa_data.search_invoice}' de Importación {tipo_label_sg} no existe.", - solution=[f"Capturar un Número de Factura que exista en el Catálogo de Importaciones {tipo_label}."], - code="IMPORT_INVOICE_NOT_FOUND", - ) - elif fa_data.search_line: - # Col. D + E: validar que la línea de importación exista dentro de esa factura - import_line: LineItem = ( - db.query(LineItem) - .filter( - LineItem.invoice_id == import_invoice.id, - LineItem.line_number == fa_data.search_line, - LineItem.tenant_id == tenant_id, - LineItem.company_id == company_id, - ) - .first() - ) - if not import_line: - errors.add_error( - field=f"line[{line_number}].fa_data.search_line", - message=f"La Factura '{fa_data.search_invoice}' con línea '{fa_data.search_line}' de Importación {tipo_label_sg} no existe.", - solution=["Capturar un Número de Factura con diferente línea que esté en el Catálogo de Importaciones correspondiente."], - code="IMPORT_LINE_NOT_FOUND", - ) - elif ( - # Col. H: valida unidad de medida sólo cuando hay descarga - fa_data.discharge is True - and line.unit_of_measure - and import_line.unit_of_measure - and line.unit_of_measure != import_line.unit_of_measure - ): - errors.add_error( - field=f"line[{line_number}].unit_of_measure", - message=( - f"La Unidad de Medida '{line.unit_of_measure}' es diferente de " - f"'{import_line.unit_of_measure}', que es la U.M. de la Factura " - f"'{fa_data.search_invoice}' con línea '{fa_data.search_line}' " - f"de Importación {tipo_label_sg}." - ), - solution=["Capturar la Unidad de Medida correcta para el descargo de esta línea de Importación."], - code="UNIT_OF_MEASURE_MISMATCH", - ) - - country = None - fraction_type = None - sector = None - if fraction: - fraction = line.customs.fraction if line.customs.fraction else fraction - - country = line.customs.origin_country - if line_item and line_item.customs: - country = ( - line_item.customs.origin_country - if line_item.customs.origin_country - else country - ) - - fraction_type = line.customs.fraction_type.upper() - if line_item and line_item.customs: - fraction_type = ( - line_item.customs.fraction_type - if line_item.customs.fraction_type - else fraction_type - ) - - sector = line.customs.sector - if line_item and line_item.customs: - sector = line_item.customs.sector if line_item.customs.sector else sector - - country_m3 = db.query(Country.m3_key).filter(Country.m3_key == country).scalar() - if not country_m3: - country_m3 = ( - db.query(Country.m3_key).filter(Country.ame_key == country).scalar() - ) - - country = country_m3 - if not country: - errors.add_error( - field=f"line[{line_number}].customs.origin_country", - message="El país de origen especificado no existe.", - solution=["Proporciona un país de origen valido."], - code="ORIGIN_COUNTRY_NOT_FOUND", - ) - else: - if fraction_type.strip().upper() not in vars(FractionType).values(): - valid_types = [ - v - for k, v in vars(FractionType).items() - if not k.startswith("_") and isinstance(v, str) - ] - errors.add_error( - field=f"line[{line_number}].customs.fraction_type", - message="El tipo de fracción especificado no es válido.", - solution=[ - f"Proporciona un tipo de fracción válido. Valores permitidos: {', '.join(valid_types)}" - ], - code="FRACTION_TYPE_INVALID", - value=fraction_type, - ) - else: - if fraction_type.strip().upper() == FractionType.PROSEC and not sector: - errors.add_error( - field=f"line[{line_number}].customs.sector", - message="El sector es obligatorio cuando el tipo de fracción es 'PROSEC'.", - solution=["Proporciona un sector valido."], - code="SECTOR_REQUIRED_FOR_PROSEC", - ) - elif fraction_type.strip().upper() != FractionType.PROSEC and sector: - errors.add_error( - field=f"line[{line_number}].customs.sector", - message="El sector solo es aplicable cuando el tipo de fracción es 'PROSEC'.", - solution=[ - "Elimina el sector o cambia el tipo de fracción a 'PROSEC'." - ], - code="SECTOR_ONLY_FOR_PROSEC", - ) - elif fraction_type.strip().upper() == FractionType.PROSEC and sector: - sector_db: Sector = ( - db.query(Sector).filter( - Sector.key == sector, - Sector.tenant_id == tenant_id, - Sector.company_id == company_id, - ).scalar() - ) - if sector_db: - errors.add_error( - field=f"line[{line_number}].customs.sector", - message="El sector especificado no existe.", - solution=["Proporciona un sector valido."], - code="SECTOR_NOT_FOUND", - ) - else: - if not sector_db.authorized: - errors.add_error( - field=f"line[{line_number}].customs.sector", - message="El sector especificado no está autorizado.", - solution=["Proporciona un sector autorizado."], - code="SECTOR_NOT_AUTHORIZED", - ) - - company_db = db.query(Company).filter(Company.id == company_id).first() - if not company_db.prosec: - errors.add_error( - field=f"line[{line_number}].customs.sector", - message=" La empresa no cuenta con autorización PROSEC.", - solution=[ - "Accese a los datos de la empresa y selecione la opción Pertenece al Programa de Promoción Sectorial y capture el número de permiso PROSEC." - ], - code="COMPANY_NOT_AUTHORIZED_FOR_PROSEC", - ) - - if fraction: - search_fraction_preference( - db=db, - country=country, - fraccion=fraction, - fraction_type=fraction_type, - sector=sector, - invoice_date=fecha_factura, - errors=errors, - ) - - if line.customs.american_fraction: - resolved = resolve_american_fraction_from_sitar(str(line.customs.american_fraction)) - if not resolved: - errors.add_error( - field=f"line[{line_number}].customs.american_fraction", - message="La fracción americana especificada no existe.", - solution=["Proporciona una fracción americana valida."], - code="AMERICAN_FRACTION_NOT_FOUND", - ) - else: - _, canon = resolved - line.customs.american_fraction = store_canonical_american_code(canon) - - if line.order: - if len(line.order) > 20: - errors.add_error( - field=f"item.order", - message="El campo orden no debe exceder los 20 caracteres.", - solution=["Proporciona un valor valido para el campo orden."], - code="ORDER_EXCEEDS_MAX_LENGTH", - ) - - unit_of_measure = line.unit_of_measure or ( - class_.unit_of_measure if class_ else None - ) - - # 0. Obtener parámetros de configuración para validaciones dinámicas - settings = AppSettingsService.get_resolved_settings(db, int(tenant_id), int(company_id)) - - def find_in_obj(obj, target_key): - if isinstance(obj, dict): - for k, v in obj.items(): - if k.lower() == target_key.lower(): - return v - res = find_in_obj(v, target_key) - if res is not None: return res - elif hasattr(obj, '__dict__'): - dict_rep = obj.__dict__ - for k, v in dict_rep.items(): - if k.startswith('_'): continue - if k.lower() == target_key.lower(): - return v - res = find_in_obj(v, target_key) - if res is not None: return res - return None - - # Resolver el CÓDIGO de la unidad de medida para validación (Blindaje con STRIP y UPPER) - uom_code = "" - if line.unit_of_measure: - # Aseguramos que el ID sea entero - try: - target_uom_id = int(line.unit_of_measure) - except: - target_uom_id = line.unit_of_measure - - uom_rec = db.query(UnitOfMeasure).filter( - UnitOfMeasure.id == target_uom_id, - UnitOfMeasure.tenant_id == tenant_id, - UnitOfMeasure.company_id == company_id - ).first() - if uom_rec and uom_rec.code: - uom_code = str(uom_rec.code).strip().upper() - elif class_ and class_.unit_of_measure: - uom_code = str(class_.unit_of_measure).strip().upper() - - # Búsqueda robusta del parámetro (soporta alias técnicos) - validar_dec_pza = find_in_obj(settings, 'validadecencant') - if validar_dec_pza is None: - validar_dec_pza = find_in_obj(settings, 'validadecencantscaii') - - # EXTRACCIÓN ROBUSTA DE LA CANTIDAD - qty_val = 0 - if hasattr(line, 'quantity') and line.quantity: - qty_val = getattr(line.quantity, 'quantity', 0) or 0 - - # Validación: Comparar contra PZA y variantes comunes - es_pieza = uom_code in ("PZA", "PZ", "PIEZA", "PIE", "Pzas", "Pza") - config_activa = str(validar_dec_pza).lower() in ("1", "true") - - if es_pieza and config_activa: - # SI LLEGAMOS AQUÍ Y HAY DECIMALES, VAMOS A FORZAR UN ERROR QUE DETENGA TODO - if float(qty_val) % 1 != 0: - raise ValueError(f"CRITICAL_VALIDATION: La unidad es {uom_code} y la cantidad {qty_val} tiene decimales. El proceso DEBE detenerse.") - - if es_pieza and config_activa and float(qty_val) % 1 != 0: - errors.add_error( - field=f"line[{line_number}].quantity.quantity", - message=f"La cantidad ({qty_val}) no puede tener decimales cuando la unidad es PZA.", - solution=["Captura una cantidad entera."], - code="QUANTITY_INTEGER_REQUIRED", - ) - - if line.valuation_method: - valuation_method_exists = db.query( - exists().where(ValuationMethod.key == line.valuation_method) - ).scalar() - if not valuation_method_exists: - errors.add_error( - field=f"line[{line_number}].valuation_method", - message="El método de valoración especificado no existe.", - solution=["Proporciona un método de valoración valido."], - code="VALUATION_METHOD_NOT_FOUND", - ) - - if line.part_number_id: - part_exists = db.query(exists().where(Part.id == line.part_number_id)).scalar() - if not part_exists: - errors.add_error( - field=f"line[{line_number}].part_number_id", - message="El número de parte especificado no existe.", - solution=["Proporciona un número de parte valido."], - code="PART_NUMBER_NOT_FOUND", - ) - - apply_calculations(db, line, tenant_id, company_id, line_number) \ No newline at end of file diff --git a/backend/api/v1/modules/a76/items/exports/validators/create.py b/backend/api/v1/modules/a76/items/exports/validators/create.py deleted file mode 100644 index 1aaaf83f..00000000 --- a/backend/api/v1/modules/a76/items/exports/validators/create.py +++ /dev/null @@ -1,535 +0,0 @@ -from decimal import Decimal -from sqlalchemy import func, exists -from sqlalchemy.orm import Session -from ...common.common_validators import count_items -from core.exceptions import ErrorCollector - -from ...models import LineItem -from api.v1.modules.a24.fa.fa_item_lines.models import FaLineItem -from ...models import LineItem -from api.v1.modules.a76.invoices.models import InvoiceHeader -from api.v1.modules.a76.classes.models import Class -from api.v1.modules.a76.general_catalogs.packages.models import Package -from api.v1.modules.sitar.fracciones_usa.catalog_resolve import ( - american_fraction_ad_valorem_from_row, - resolve_american_fraction_from_sitar, - store_canonical_american_code, -) -from api.v1.modules.public.reference_data.payment_methods.models import PaymentMethod -from api.v1.modules.a76.app_settings.service import AppSettingsService -from .common import validate_common - - -def _normalize_weight_type(logistics) -> str: - """Paridad con CSV: enum o string a 'KGS'/'LBS' para comparaciones.""" - if logistics is None: - return "KGS" - wt = getattr(logistics, "weight_type", None) or "KGS" - if hasattr(wt, "value"): - wt = wt.value - weight_str = str(wt).upper() if wt else "KGS" - return weight_str if weight_str in ("KGS", "LBS") else "KGS" - - -def validate_create( - db: Session, - line: LineItem, - tenant_id: int, - company_id: int, - errors: ErrorCollector, - line_number: int, -): - """ - Validates and calculates fields for a new line item before DB creation. - Works with Pydantic schemas, modifying them in-place. - - Args: - line: LineItemCreate schema with nested data (financial, quantity, customs, etc.) - invoice_id: ID of the invoice this line belongs to - fa_data: FaLineItemCreateDTO or None (None for INV system) - """ - - # Access fa_data safely - fa_data: FaLineItem = getattr(line, "fa_data", None) - - # Required field validations - if not line.class_id: - errors.add_required_error(field=f"line[{line_number}].class_id") - - if not line.quantity or line.quantity.quantity is None: - errors.add_required_error(field=f"line[{line_number}].quantity.quantity") - elif line.quantity.quantity <= 0: - errors.add_error( - field=f"line[{line_number}].quantity.quantity", - message=f"La cantidad debe ser mayor a cero (recibido: {line.quantity.quantity})", - solution=["Capturar una cantidad válida mayor a cero."], - code="INVALID_QUANTITY", - value=float(line.quantity.quantity) - ) - - # 0. Obtener parámetros de configuración para validaciones dinámicas - settings = AppSettingsService.get_resolved_settings(db, int(tenant_id), int(company_id)) - - def find_in_obj(obj, target_key): - if isinstance(obj, dict): - for k, v in obj.items(): - if k.lower() == target_key.lower(): - return v - res = find_in_obj(v, target_key) - if res is not None: return res - elif hasattr(obj, '__dict__'): - dict_rep = obj.__dict__ - for k, v in dict_rep.items(): - if k.startswith('_'): continue - if k.lower() == target_key.lower(): - return v - res = find_in_obj(v, target_key) - if res is not None: return res - return None - - # Parámetro SCAF: Calcular costo unitario en base a valor total - calc_costo_unit = find_in_obj(settings, 'calcularcostounitarioenbaseavalortotal') or \ - find_in_obj(settings, 'calcularcostounitarioenbaseavalortotalscaf') - - if fa_data and not fa_data.is_subitem: - # Si el parámetro está apagado (o no existe), el costo unitario es obligatorio - if str(calc_costo_unit) != "1": - if ( - not line.financial - or not line.financial.unit_cost_capture - or line.financial.unit_cost_capture <= 0 - ): - errors.add_required_error( - field=f"line[{line_number}].financial.unit_cost_capture" - ) - - if not line.quantity or line.quantity.net_weight is None: - errors.add_required_error(field=f"line[{line_number}].quantity.net_weight") - elif line.quantity.net_weight <= 0: - errors.add_error( - field=f"line[{line_number}].quantity.net_weight", - message=f"El peso neto debe ser mayor a cero (recibido: {line.quantity.net_weight})", - solution=["Capturar un peso neto válido mayor a cero."], - code="INVALID_NET_WEIGHT", - value=float(line.quantity.net_weight) - ) - - if not line.customs or not line.customs.origin_country: - errors.add_required_error(field=f"line[{line_number}].customs.origin_country") - - if not line.customs or not line.customs.fraction_type: - errors.add_required_error(field=f"line[{line_number}].customs.fraction_type") - - # FA-specific validations - if fa_data: - # Col. C: Procedencia de la Importación (TipoMovImpo) — obligatorio - if not fa_data.movement_type_import: - errors.add_required_error(field=f"line[{line_number}].fa_data.movement_type_import") - - # Col. F: ¿Descarga la línea? (DescargaPartida) — obligatorio - if fa_data.discharge is None: - errors.add_required_error(field=f"line[{line_number}].fa_data.discharge") - - # Col. D / E: Factura y Línea de Impo — obligatorios sólo si hay descarga - if fa_data.discharge is True: - if not fa_data.search_invoice: - errors.add_required_error(field=f"line[{line_number}].fa_data.search_invoice") - if not fa_data.search_line: - errors.add_required_error(field=f"line[{line_number}].fa_data.search_line") - - # REVISA_FACTURA logic if both invoice and line are present - if fa_data.search_invoice and fa_data.search_line: - linked_inv = db.query(InvoiceHeader).filter( - InvoiceHeader.invoice_number == fa_data.search_invoice, - InvoiceHeader.tenant_id == tenant_id, - InvoiceHeader.company_id == company_id, - InvoiceHeader.operation_type == "imp" # Assuming link is always to an import - ).first() - - if not linked_inv: - errors.add_error( - field=f"line[{line_number}].fa_data.search_invoice", - message="La Factura No Existe Capture o seleccione una que si exista", - code="LINKED_INVOICE_NOT_FOUND", - solution=["Verificar el número de factura de importación."] - ) - elif linked_inv.status != "processed": - errors.add_error( - field=f"line[{line_number}].fa_data.search_invoice", - message="La Factura no esta Actualizada, Capture o seleccione una que si este Actualizada", - code="LINKED_INVOICE_NOT_PROCESSED", - solution=["Actualizar/Procesar la factura de importación antes de descargarla."] - ) - else: - # Invoice is valid and processed, check the line (REVISA_FACTURA part 2) - linked_line = db.query(LineItem).filter( - LineItem.invoice_id == linked_inv.id, - LineItem.line_number == fa_data.search_line, - LineItem.tenant_id == tenant_id, - LineItem.company_id == company_id - ).first() - - if not linked_line: - errors.add_error( - field=f"line[{line_number}].fa_data.search_line", - message="La Partida de la Factura No Existe Capture o seleccione una que si exista", - code="LINKED_LINE_NOT_FOUND", - solution=["Verificar el número de renglón en la factura de importación."] - ) - else: - # Validation for search_type == "Clase" (Parity with Clarion Valida.Validaciones) - if fa_data.search_type == "Clase" and linked_line.class_id != line.class_id: - # Load class codes for a better error message if necessary - current_class = db.query(Class).filter(Class.id == line.class_id).first() - import_class = db.query(Class).filter(Class.id == linked_line.class_id).first() - - errors.add_error( - field=f"line[{line_number}].class_id", - message=f"Es necesario que la clase: {current_class.class_code if current_class else line.class_id} sea igual a la clase: {import_class.class_code if import_class else linked_line.class_id} de la factura de importación seleccionada.", - code="CLASS_MISMATCH_FOR_SEARCH_TYPE_CLASE", - solution=["Asegurarse de que el activo que se exporta pertenezca a la misma familia/clase que el que se importó."] - ) - - # REVISA_CANTIDADES_A_DESC_TEM logic - # 1. Get current balance from ledger - from api.v1.modules.a24.balance_movements.models import BalanceMovement, NEGATIVE_MOVEMENTS - from sqlalchemy import case, select - - sign_expr = case( - (BalanceMovement.movement_type.in_(NEGATIVE_MOVEMENTS), Decimal("-1")), - else_=Decimal("1"), - ) - available_balance = db.execute( - select(func.sum(sign_expr * BalanceMovement.quantity)).where( - BalanceMovement.import_item_line_id == linked_line.id - ) - ).scalar() or Decimal("0") - - # 2. Get pending discharges in this same invoice (sibling lines) - from api.v1.modules.a76.items.line_quantities.models import LineQuantity - pending_sum = db.query(func.sum(LineQuantity.quantity)).join( - LineItem, LineItem.id == LineQuantity.id - ).join( - FaLineItem, FaLineItem.id == LineItem.id - ).filter( - LineItem.invoice_id == line.invoice_id, - FaLineItem.search_invoice == fa_data.search_invoice, - FaLineItem.search_line == fa_data.search_line, - FaLineItem.movement_type_import == fa_data.movement_type_import, - FaLineItem.discharge == True - ).scalar() or Decimal("0") - - current_qty = line.quantity.quantity or Decimal("0") - remaining = available_balance - pending_sum - current_qty - - # Note: Hard validation removed here to allow 'Multiple Source Discharge' - # or 'Automatic Deficit Handling' logic to function during full invoice processing. - # Current balance for information: {available_balance}, short: {remaining if remaining < 0 else 0} - pass - - - if ( - fa_data.is_subitem and fa_data.contains_subitems - ) and not fa_data.subitem_number: - errors.add_required_error( - field=f"line[{line_number}].fa_data.subitem_number" - ) - - # Validar que si es un subitem, existe un item principal correspondiente - if ( - fa_data.is_subitem - and fa_data.subitem_number - and fa_data.subitem_number != 0 - ): - principal_item_exists = db.query( - exists().where( - (LineItem.id == FaLineItem.id) - & (LineItem.id == LineItem.id) - & (LineItem.invoice_id == line.invoice_id) - & (LineItem.line_number == line_number) - & (FaLineItem.is_subitem == False) - & (FaLineItem.contains_subitems == True) - & (LineItem.tenant_id == tenant_id) - & (LineItem.company_id == company_id) - ) - ).scalar() - - if not principal_item_exists: - errors.add_error( - field=f"line[{line_number}]", - message=f"No existe un item principal registrado para esta linea {line_number} con subitem {fa_data.subitem_number}", - solution=[ - "Registrar el item principal correspondiente a esta linea antes de registrar subitems." - ], - code="SUBITEM_WITHOUT_PRINCIPAL_ITEM", - ) - - if fa_data.is_subitem and ( - fa_data.subitem_number == 0 or not fa_data.subitem_number - ): - errors.add_error( - field=f"line[{line_number}]", - message=f"El número de subitem no puede ser 0 si la línea es un subitem.", - solution=["Asignar un número de subitem mayor a 0 para esta línea."], - code="SUBITEM_NUMBER_INVALID", - ) - - validate_common(db, line, tenant_id, company_id, errors, line_number) - - if not errors.has_errors(): - # Ensure nested objects exist for calculations - from ...schemas import ( - LineFinancialCreate, - LineQuantityCreate, - LineCustomCreate, - LineDescriptionCreate, - ) - - if line.financial is None: - line.financial = LineFinancialCreate() - if line.quantity is None: - line.quantity = LineQuantityCreate() - if line.customs is None: - line.customs = LineCustomCreate() - if line.description is None: - line.description = LineDescriptionCreate() - - # Obtener la factura para acceder a tipo de cambio, moneda y peso - invoice: InvoiceHeader = ( - db.query(InvoiceHeader) - .filter( - InvoiceHeader.id == line.invoice_id, - InvoiceHeader.tenant_id == tenant_id, - InvoiceHeader.company_id == company_id, - ) - .first() - ) - - if not invoice or not invoice.financials or not invoice.logistics: - errors.add_error( - field=f"line[{line_number}]", - message="No se pudo obtener información de la factura", - solution=[ - "Verificar que la factura existe y tiene datos financieros y logísticos" - ], - code="INVOICE_DATA_MISSING", - ) - return - - # Obtener la clase para valores por defecto - class_info: Class = ( - db.query(Class) - .filter( - Class.id == line.class_id, - Class.tenant_id == tenant_id, - Class.company_id == company_id, - ) - .first() - ) - - # ========================================== - # ASIGNAR TIPO DE CAMBIO - # ========================================== - exchange_rate = invoice.financials.exchange_rate or Decimal("1.0") - - # ========================================== - # ASIGNAR UNIDAD DE MEDIDA - # ========================================== - # Si no se proporcionó unidad de medida, usar la de la clase - if not line.unit_of_measure and class_info: - line.unit_of_measure = class_info.unit_of_measure - - # ========================================== - # ASIGNAR TIPOS DE MONEDA Y CALCULAR COSTOS - # ========================================== - currency_type = invoice.financials.currency_type - unit_cost_capture = line.financial.unit_cost_capture or Decimal("0") - - # Calcular costos según tipo de moneda - if currency_type == "USD" or currency_type == "ME": # Moneda Extranjera (ME) - line.financial.unit_cost_capture = unit_cost_capture - line.financial.unit_cost_usd = unit_cost_capture - line.financial.unit_cost_mxn = unit_cost_capture * exchange_rate - elif currency_type == "MXN" or currency_type == "MN": # Moneda Nacional (MN) - line.financial.unit_cost_capture = unit_cost_capture - line.financial.unit_cost_usd = ( - unit_cost_capture / exchange_rate if exchange_rate else Decimal("0") - ) - line.financial.unit_cost_mxn = unit_cost_capture - # Si es otro tipo de moneda, dejamos el costo como está - - # Calcular valores totales basados en cantidad y costo unitario - quantity = line.quantity.quantity or Decimal("0") - - # Valor Comercial - if line.financial.unit_cost_usd is not None: - line.financial.value_usd = line.financial.unit_cost_usd * quantity - if line.financial.unit_cost_mxn is not None: - line.financial.value_mxn = line.financial.unit_cost_mxn * quantity - - # Valor Aduanas (asumiendo que es igual al Valor Comercial por defecto) - line.financial.customs_value_usd = line.financial.value_usd - line.financial.customs_value_mxn = line.financial.value_mxn - - # Valor MP Temp (Materia Prima Temporal) - line.financial.value_temp_material_usd = line.financial.value_usd - line.financial.value_temp_material_mxn = line.financial.value_mxn - - # value_mc: paridad con calculate_values y cargas CSV - currency = getattr(invoice.financials, "currency", None) - if currency == "foreign": - line.financial.value_mc = line.financial.value_usd - elif currency == "local": - line.financial.value_mc = line.financial.value_usd - elif currency == "manual": - line.financial.value_mc = unit_cost_capture * quantity - else: - line.financial.value_mc = line.financial.value_usd - - # ========================================== - # VALIDAR Y CONVERTIR PESOS NETOS - # ========================================== - invoice_weight_type = _normalize_weight_type(invoice.logistics) - quantity = line.quantity.quantity or Decimal("0") - net_weight_input = line.quantity.net_weight or Decimal("0") - - # UOM peso: aceptar 24/"24" y 25/"25" (paridad con CSV) - uom = line.unit_of_measure - unit_is_kgs = uom is not None and (str(uom) == "24" or uom == 24) - unit_is_lbs = uom is not None and (str(uom) == "25" or uom == 25) - - # Calcular peso neto en kilogramos (estándar interno) - if unit_is_kgs: - if invoice_weight_type.lower() == "kgs": - line.quantity.net_weight = quantity - else: # invoice en libras - line.quantity.net_weight = quantity * Decimal("2.204624") - elif unit_is_lbs: - if invoice_weight_type.lower() == "kgs": - line.quantity.net_weight = quantity / Decimal("2.204624") - else: # invoice en libras - line.quantity.net_weight = quantity - else: - # Otra unidad de medida - usar peso capturado y convertir si es necesario - if invoice_weight_type.lower() == "kgs": - # El peso capturado está en kilos - line.quantity.net_weight = net_weight_input - else: - # El peso capturado está en libras, convertir a kilos - line.quantity.net_weight = net_weight_input / Decimal("2.204624") - - # ========================================== - # CALCULAR PESO BRUTO - # ========================================== - gross_weight_input = line.quantity.gross_weight - package_quantity = line.quantity.package_quantity or 0 - package_weight_unit = Decimal("0") - - # Obtener peso unitario del bulto si existe - if line.quantity.package_id: - package: Package = ( - db.query(Package) - .filter( - Package.id == line.quantity.package_id, - Package.tenant_id == tenant_id, - Package.company_id == company_id, - ) - .first() - ) - if package and package.weight_unit: - package_weight_unit = package.weight_unit - - # Si no se proporcionó peso bruto, calcularlo - if not gross_weight_input or gross_weight_input == 0: - if invoice_weight_type.lower() == "kgs": - line.quantity.gross_weight = line.quantity.net_weight + ( - package_weight_unit * package_quantity - ) - else: # libras - line.quantity.gross_weight = line.quantity.net_weight + ( - (package_weight_unit * Decimal("2.204624")) * package_quantity - ) - else: - # Convertir peso bruto capturado según tipo de factura - if invoice_weight_type.lower() == "kgs": - line.quantity.gross_weight = gross_weight_input - else: # libras - line.quantity.gross_weight = gross_weight_input / Decimal("2.204624") - - # ========================================== - # VALIDAR PESO BRUTO < PESO NETO - # ========================================== - if line.quantity.gross_weight < line.quantity.net_weight: - line.quantity.gross_weight = line.quantity.net_weight + ( - package_weight_unit * package_quantity - ) - - # ========================================== - # ASIGNAR DESCRIPCIÓN DE BULTOS - # ========================================== - if package_quantity and package_quantity > 0 and line.quantity.package_id: - package: Package = ( - db.query(Package) - .filter( - Package.id == line.quantity.package_id, - Package.tenant_id == tenant_id, - Package.company_id == company_id, - ) - .first() - ) - if package: - line.description.package_description = package.description_es - else: - line.quantity.package_quantity = 0 - line.quantity.package_id = None - line.description.package_description = None - - # ========================================== - # ASIGNAR FRACCIÓN AMERICANA POR DEFECTO - # ========================================== - if not line.customs.american_fraction and class_info and class_info.us_fraction: - line.customs.american_fraction = class_info.us_fraction - - # Ad valorem desde catálogo SITAR (fracciones-usa) - if line.customs.american_fraction: - resolved = resolve_american_fraction_from_sitar(str(line.customs.american_fraction)) - if resolved: - sitar_row, canon = resolved - line.customs.american_fraction = store_canonical_american_code(canon) - line.customs.advalorem_american = american_fraction_ad_valorem_from_row( - sitar_row - ) - - # ========================================== - # ASIGNAR DESCRIPCIONES POR DEFECTO - # ========================================== - if not line.description.description_spanish and class_info: - line.description.description_spanish = class_info.description_es - - if not line.description.description_english and class_info: - line.description.description_english = class_info.description_en - - # ========================================== - # NORMALIZAR CAMPOS DE TEXTO - # ========================================== - # Convertir a mayúsculas campos que lo requieran - if line.description.brand: - line.description.brand = line.description.brand.upper().strip() - - if line.description.model: - line.description.model = line.description.model.upper().strip() - - # ========================================== - # ========================================== - # CUMPLIMIENTO MEXICANO (Valores por defecto gestionados en apply_calculations) - # ========================================== - # Validar existencia de forma de pago si se asignó - if line.payment_method: - payment_method_exists = ( - db.query(PaymentMethod) - .filter(PaymentMethod.key == line.payment_method) - .first() - ) - if not payment_method_exists: - # Si falló la validación porque el parámetro de sistema no está en el catálogo, lanzamos advertencia - pass diff --git a/backend/api/v1/modules/a76/items/exports/validators/update.py b/backend/api/v1/modules/a76/items/exports/validators/update.py deleted file mode 100644 index 896f8240..00000000 --- a/backend/api/v1/modules/a76/items/exports/validators/update.py +++ /dev/null @@ -1,375 +0,0 @@ -from decimal import Decimal -from sqlalchemy import func, exists -from sqlalchemy.orm import Session -from core.exceptions import ErrorCollector - -from ...models import LineItem -from api.v1.modules.a24.fa.fa_item_lines.models import FaLineItem -from api.v1.modules.a76.invoices.models import InvoiceHeader -from api.v1.modules.sitar.fracciones_usa.catalog_resolve import ( - american_fraction_ad_valorem_from_row, - resolve_american_fraction_from_sitar, - store_canonical_american_code, -) -from api.v1.modules.a76.classes.models import Class -from .common import validate_common - - -def validate_update( - db: Session, - line: LineItem, - existing_line: LineItem, - tenant_id: int, - company_id: int, - errors: ErrorCollector, - line_number: int, -): - """ - Validar y procesar actualización parcial de línea de importación temporal. - Si un campo no se proporciona, se mantiene el valor existente. - """ - validate_common(db, line, tenant_id, company_id, errors, line_number) - - if not errors.has_errors(): - # Ensure nested objects exist for calculations/partial updates - from ...schemas import ( - LineFinancialCreate, - LineQuantityCreate, - LineCustomCreate, - LineDescriptionCreate, - ) - - if line.financial is None: - line.financial = LineFinancialCreate() - if line.quantity is None: - line.quantity = LineQuantityCreate() - if line.customs is None: - line.customs = LineCustomCreate() - if line.description is None: - line.description = LineDescriptionCreate() - - # Obtener la factura para acceder a tipo de cambio, moneda y peso - invoice: InvoiceHeader = ( - db.query(InvoiceHeader) - .filter( - InvoiceHeader.id == line.invoice_id, - InvoiceHeader.tenant_id == tenant_id, - InvoiceHeader.company_id == company_id - ) - .first() - ) - - if not invoice or not invoice.financials or not invoice.logistics: - errors.add_error( - field=f"line[{line_number}]", - message="No se pudo obtener información de la factura", - solution=[ - "Verificar que la factura existe y tiene datos financieros y logísticos" - ], - code="INVOICE_DATA_MISSING", - ) - return - - # ========================================== - # ACTUALIZACIÓN PARCIAL DE CAMPOS - # Si no se proporciona, mantener valor existente - # ========================================== - - # Tipo de cambio de la factura - exchange_rate = invoice.financials.exchange_rate or Decimal("1.0") - - # Unidad de medida - if not line.unit_of_measure: - line.unit_of_measure = existing_line.unit_of_measure - - # Costo unitario - if line.financial.unit_cost_capture is None: - line.financial.unit_cost_capture = existing_line.financial.unit_cost_capture - - # Recalcular valores monetarios si el costo o la cantidad cambian - currency_type = invoice.financials.currency_type - unit_cost_capture = line.financial.unit_cost_capture or Decimal("0") - - if currency_type in ["USD", "ME"]: - line.financial.unit_cost_usd = unit_cost_capture - line.financial.unit_cost_mxn = unit_cost_capture * exchange_rate - elif currency_type in ["MXN", "MN"]: - line.financial.unit_cost_usd = (unit_cost_capture / exchange_rate) if exchange_rate else Decimal("0") - line.financial.unit_cost_mxn = unit_cost_capture - - quantity = line.quantity.quantity if line.quantity.quantity is not None else existing_line.quantity.quantity - quantity = quantity if quantity is not None else Decimal("0") - - if line.financial.unit_cost_usd is not None: - line.financial.value_usd = line.financial.unit_cost_usd * quantity - if line.financial.unit_cost_mxn is not None: - line.financial.value_mxn = line.financial.unit_cost_mxn * quantity - - line.financial.customs_value_usd = line.financial.value_usd - line.financial.customs_value_mxn = line.financial.value_mxn - - line.financial.value_temp_material_usd = line.financial.value_usd - line.financial.value_temp_material_mxn = line.financial.value_mxn - - # Convertir peso neto si se proporcionó - invoice_weight_type = invoice.logistics.weight_type - if line.quantity.net_weight is not None: - # Se proporcionó nuevo peso neto, convertir según tipo - net_weight_input = line.quantity.net_weight - - if invoice_weight_type.lower() == "kgs": - line.quantity.net_weight = net_weight_input - else: # libras, convertir a kilos - line.quantity.net_weight = net_weight_input / Decimal("2.204624") - else: - # Mantener peso existente - line.quantity.net_weight = existing_line.quantity.net_weight - - # Convertir peso bruto si se proporcionó - if line.quantity.gross_weight is not None: - gross_weight_input = line.quantity.gross_weight - - if invoice_weight_type.lower() == "kgs": - line.quantity.gross_weight = gross_weight_input - else: # libras, convertir a kilos - line.quantity.gross_weight = gross_weight_input / Decimal("2.204624") - else: - # Mantener peso existente - line.quantity.gross_weight = existing_line.quantity.gross_weight - - # Cantidad de bultos - if line.quantity.package_quantity is None: - line.quantity.package_quantity = existing_line.quantity.package_quantity - - # Clave de bultos - if not line.quantity.package_id: - line.quantity.package_id = existing_line.quantity.package_id - - # País de origen - if not line.customs.origin_country: - line.customs.origin_country = existing_line.customs.origin_country - - # Fracción arancelaria - if not line.customs.fraction: - line.customs.fraction = existing_line.customs.fraction - - # Tipo de fracción - if not line.customs.fraction_type: - line.customs.fraction_type = existing_line.customs.fraction_type - - # Sector - if not line.customs.sector: - line.customs.sector = existing_line.customs.sector - - # Fracción americana y su advalorem (SITAR) - if line.customs.american_fraction: - resolved = resolve_american_fraction_from_sitar(str(line.customs.american_fraction)) - if resolved: - sitar_row, canon = resolved - line.customs.american_fraction = store_canonical_american_code(canon) - line.customs.advalorem_american = american_fraction_ad_valorem_from_row( - sitar_row - ) - else: - # Mantener fracción americana existente - line.customs.american_fraction = existing_line.customs.american_fraction - line.customs.advalorem_american = existing_line.customs.advalorem_american - - # Orden de compra - if not line.order: - line.order = existing_line.order - - # Descripciones - if line.description.description_spanish is None: - line.description.description_spanish = ( - existing_line.description.description_spanish - ) - - if not line.description.description_english: - line.description.description_english = ( - existing_line.description.description_english - ) - - if line.description.extra_description is None: - line.description.extra_description = ( - existing_line.description.extra_description - ) - - # Marca y modelo - if line.description.brand is not None: - line.description.brand = line.description.brand.upper().strip() - else: - line.description.brand = existing_line.description.brand - - if line.description.model is not None: - line.description.model = line.description.model.upper().strip() - else: - line.description.model = existing_line.description.model - - # ========================================== - # DATOS FA (Activo Fijo) — ACTUALIZACIÓN PARCIAL - # ========================================== - fa_data: FaLineItem = getattr(line, "fa_data", None) - existing_fa_data: FaLineItem = getattr(existing_line, "fa_data", None) - - if fa_data is not None and existing_fa_data is not None: - # Col. C: Procedencia de la Importación (TipoMovImpo) - if not fa_data.movement_type_import: - fa_data.movement_type_import = existing_fa_data.movement_type_import - - # Col. F: ¿Descarga la línea? (Descarga) - if fa_data.discharge is None: - fa_data.discharge = existing_fa_data.discharge - - # Col. D: Factura de Importación — obligatoria sólo si hay descarga - if not fa_data.search_invoice: - fa_data.search_invoice = existing_fa_data.search_invoice - - # Col. E: Línea de Importación — obligatoria sólo si hay descarga - if fa_data.search_line is None: - fa_data.search_line = existing_fa_data.search_line - - # REVISA_FACTURA logic if both invoice and line are present (even after partial update) - if fa_data.discharge is True and fa_data.search_invoice and fa_data.search_line: - from sqlalchemy import exists - linked_inv = db.query(InvoiceHeader).filter( - InvoiceHeader.invoice_number == fa_data.search_invoice, - InvoiceHeader.tenant_id == tenant_id, - InvoiceHeader.company_id == company_id, - InvoiceHeader.operation_type == "imp" - ).first() - - if not linked_inv: - errors.add_error( - field=f"line[{line_number}].fa_data.search_invoice", - message="La Factura No Existe Capture o seleccione una que si exista", - code="LINKED_INVOICE_NOT_FOUND", - solution=["Verificar el número de factura de importación."] - ) - elif linked_inv.status != "processed": - errors.add_error( - field=f"line[{line_number}].fa_data.search_invoice", - message="La Factura no esta Actualizada, Capture o seleccione una que si este Actualizada", - code="LINKED_INVOICE_NOT_PROCESSED", - solution=["Actualizar/Procesar la factura de importación antes de descargarla."] - ) - else: - # Invoice is valid and processed, check the line - linked_line_exists = db.query(exists().where( - (LineItem.invoice_id == linked_inv.id) & - (LineItem.line_number == fa_data.search_line) & - (LineItem.tenant_id == tenant_id) & - (LineItem.company_id == company_id) - )).scalar() - - if not linked_line_exists: - errors.add_error( - field=f"line[{line_number}].fa_data.search_line", - message="La Partida de la Factura No Existe Capture o seleccione una que si exista", - code="LINKED_LINE_NOT_FOUND", - solution=["Verificar el número de renglón en la factura de importación."] - ) - else: - # Validation for search_type == "Clase" (Parity with Clarion Valida.Validaciones) - # We need to get the actual IDs to compare - linked_import_line = db.query(LineItem).filter( - LineItem.invoice_id == linked_inv.id, - LineItem.line_number == fa_data.search_line, - LineItem.tenant_id == tenant_id, - LineItem.company_id == company_id - ).first() - - if linked_import_line: - current_class_id = line.class_id if line.class_id else existing_line.class_id - if fa_data.search_type == "Clase" and linked_import_line.class_id != current_class_id: - current_class = db.query(Class).filter(Class.id == current_class_id).first() - import_class = db.query(Class).filter(Class.id == linked_import_line.class_id).first() - - errors.add_error( - field=f"line[{line_number}].class_id", - message=f"Es necesario que la clase: {current_class.class_code if current_class else current_class_id} sea igual a la clase: {import_class.class_code if import_class else linked_import_line.class_id} de la factura de importación seleccionada.", - code="CLASS_MISMATCH_FOR_SEARCH_TYPE_CLASE", - solution=["Asegurarse de que el activo que se exporta pertenezca a la misma familia/clase que el que se importó."] - ) - - # REVISA_CANTIDADES_A_DESC_TEM logic for Update - - # 0. Get the internal ID of the linked import line - linked_import_line = db.query(LineItem).filter( - LineItem.invoice_id == linked_inv.id, - LineItem.line_number == fa_data.search_line, - LineItem.tenant_id == tenant_id, - LineItem.company_id == company_id - ).first() - - if linked_import_line: - # 1. Get current balance from ledger - from api.v1.modules.a24.balance_movements.models import BalanceMovement, NEGATIVE_MOVEMENTS - from sqlalchemy import case, select - - sign_expr = case( - (BalanceMovement.movement_type.in_(NEGATIVE_MOVEMENTS), Decimal("-1")), - else_=Decimal("1"), - ) - available_balance = db.execute( - select(func.sum(sign_expr * BalanceMovement.quantity)).where( - BalanceMovement.import_item_line_id == linked_import_line.id - ) - ).scalar() or Decimal("0") - - # 2. Get pending discharges in this same invoice (sibling lines) - # EXCLUDING the current line we are updating - from api.v1.modules.a76.items.line_quantities.models import LineQuantity - pending_sum = db.query(func.sum(LineQuantity.quantity)).join( - LineItem, LineItem.id == LineQuantity.id - ).join( - FaLineItem, FaLineItem.id == LineItem.id - ).filter( - LineItem.invoice_id == line.invoice_id, - LineItem.id != existing_line.id, # IMPORTANT: Exclude self - FaLineItem.search_invoice == fa_data.search_invoice, - FaLineItem.search_line == fa_data.search_line, - FaLineItem.movement_type_import == fa_data.movement_type_import, - FaLineItem.discharge == True - ).scalar() or Decimal("0") - - current_qty = line.quantity.quantity if line.quantity.quantity is not None else existing_line.quantity.quantity - remaining = available_balance - pending_sum - current_qty - - # Note: Hard validation removed here to allow 'Multiple Source Discharge' - # or 'Automatic Deficit Handling' logic to function during full invoice processing. - # Current balance for information: {available_balance}, short: {remaining if remaining < 0 else 0} - pass - - - # Subpartidas (EsSubPartida / SubPartida) - if fa_data.is_subitem is None: - fa_data.is_subitem = existing_fa_data.is_subitem - if fa_data.subitem_number is None: - fa_data.subitem_number = existing_fa_data.subitem_number - - # Número de parte - if not line.part_number_id: - line.part_number_id = existing_line.part_number_id - - # Pago de impuesto - if line.tax_payment is None: - line.tax_payment = existing_line.tax_payment - - # Forma de pago - if not line.payment_method: - line.payment_method = existing_line.payment_method - - # Método de valoración - if not line.valuation_method: - if existing_line.valuation_method: - line.valuation_method = existing_line.valuation_method - # else: TODO: Tomar de SisImp:MetValor (preferencias del sistema) - - # Número de entrada - if not line.description.entry_number: - line.description.entry_number = existing_line.description.entry_number - - # Lote - if not line.description.lot: - line.description.lot = existing_line.description.lot diff --git a/backend/api/v1/modules/a76/items/imports/validators/calculations.py b/backend/api/v1/modules/a76/items/imports/validators/calculations.py deleted file mode 100644 index be6754a0..00000000 --- a/backend/api/v1/modules/a76/items/imports/validators/calculations.py +++ /dev/null @@ -1,169 +0,0 @@ -from sqlalchemy.orm import Session -from api.v1.modules.a76.invoices.models import InvoiceFinancials, InvoiceHeader -from core.exceptions import ErrorCollector - -from ...models import LineItem -from api.v1.modules.a76.classes.models import Class -from api.v1.modules.a76.app_settings.service import AppSettingsService -from decimal import Decimal - - -def apply_calculations( - db: Session, line: LineItem, tenant_id: int, company_id: int, line_number: int -): - # 0. Obtener parámetros de configuración para validaciones dinámicas - settings = AppSettingsService.get_resolved_settings(db, int(tenant_id), int(company_id)) - - def find_in_obj(obj, target_key): - if isinstance(obj, dict): - for k, v in obj.items(): - if k.lower() == target_key.lower(): - return v - res = find_in_obj(v, target_key) - if res is not None: return res - elif hasattr(obj, '__dict__'): - dict_rep = obj.__dict__ - for k, v in dict_rep.items(): - if k.startswith('_'): continue - if k.lower() == target_key.lower(): - return v - res = find_in_obj(v, target_key) - if res is not None: return res - return None - - # Parámetro SCAF: Calcular costo unitario en base a valor total - calc_costo_unit = find_in_obj(settings, 'calcularcostounitarioenbaseavalortotal') or \ - find_in_obj(settings, 'calcularcostounitarioenbaseavalortotalscaf') - - if str(calc_costo_unit) == "1" and line.financial and (line.financial.unit_cost_capture or 0) == 0: - qty = line.quantity.quantity or Decimal("0") - if qty > 0: - total_val = line.financial.value_usd or line.financial.value_mxn or Decimal("0") - if total_val > 0: - line.financial.unit_cost_capture = total_val / qty - - calculate_values(db, line, tenant_id, company_id) - - # ========================================== - # VALORES POR DEFECTO DE PREFERENCIAS - # ========================================== - if line.tax_payment is None: - pref_pago = find_in_obj(settings, 'pagoimpuesto') - if pref_pago: - line.tax_payment = True if str(pref_pago).lower() == 'si' else False - - if not line.payment_method: - line.payment_method = find_in_obj(settings, 'formapago') - - if not line.valuation_method: - line.valuation_method = find_in_obj(settings, 'metvalor') - - apply_calculations_after_values(db, line, tenant_id, company_id, line_number) - - -def apply_calculations_after_values( - db: Session, line: LineItem, tenant_id: int, company_id: int, line_number: int -): - """ - Aplica solo depreciation_date, descripción desde part/class y subitem_number. - Usado por el flujo CSV para no sobrescribir los valores ya calculados por currency_type. - """ - fa_data = getattr(line, "fa_data", None) - if fa_data is not None and not getattr(fa_data, "is_subitem", True): - fa_data.subitem_number = 0 - - invoice_date = db.query(InvoiceHeader.invoice_date).filter( - InvoiceHeader.id == line.invoice_id, - InvoiceHeader.tenant_id == tenant_id, - InvoiceHeader.company_id == company_id, - ).scalar() - if invoice_date is not None: - line.depreciation_date = invoice_date - - if not getattr(line.description, "description_spanish", None) and not getattr( - line.description, "description_english", None - ): - part_info = getattr(line, "part_info", None) - if part_info and getattr(part_info, "description_spanish", None) and getattr(part_info, "description_english", None): - line.description.description_spanish = part_info.description_spanish - line.description.description_english = part_info.description_english - else: - if not getattr(line.description, "description_spanish", None): - class_desc = ( - db.query(Class.description_es, Class.description_en) - .filter( - Class.id == line.class_id, - Class.tenant_id == tenant_id, - Class.company_id == company_id, - ) - .first() - ) - if class_desc: - line.description.description_spanish, line.description.description_english = class_desc - - -def calculate_values( - db: Session, line: LineItem, tenant_id: int, company_id: int -): - """ - Una sola fuente de verdad: usa currency_type (USD/ME, MXN/MN) cuando está presente, - para paridad con create.py y cargas CSV. Si no hay currency_type, usa currency (foreign/local/manual). - """ - result = ( - db.query( - InvoiceFinancials.currency, - InvoiceFinancials.currency_type, - InvoiceFinancials.exchange_rate, - ) - .filter( - InvoiceFinancials.invoice_id == line.invoice_id, - InvoiceFinancials.tenant_id == tenant_id, - InvoiceFinancials.company_id == company_id, - ) - .first() - ) - if not result: - return - - currency, currency_type, exchange_rate = result - - # Safety guard: Ensure nested objects exist before calculating - if not line.financial or not line.quantity: - return - - # Prioridad: currency_type para alinear con create.py y CSV - if currency_type in ("USD", "ME"): - currency = "foreign" - elif currency_type in ("MXN", "MN"): - currency = "local" - # si currency_type es otro o None, se usa currency tal cual - - qty = ( - line.quantity.quantity - if line.quantity.quantity is not None - else Decimal("0") - ) - capture = ( - line.financial.unit_cost_capture - if line.financial.unit_cost_capture is not None - else Decimal("0") - ) - - if currency == "foreign": - line.financial.unit_cost_usd = capture - line.financial.value_usd = capture * qty - line.financial.unit_cost_mxn = capture * (exchange_rate or 1) - line.financial.value_mxn = line.financial.unit_cost_mxn * qty - line.financial.value_mc = capture * qty - elif currency == "local": - line.financial.unit_cost_mxn = capture - line.financial.value_mxn = line.financial.unit_cost_mxn * qty - line.financial.unit_cost_usd = capture / (exchange_rate or 1) - line.financial.value_usd = line.financial.unit_cost_usd * qty - line.financial.value_mc = line.financial.unit_cost_usd * qty - elif currency == "manual": - line.financial.unit_cost_usd = capture / (exchange_rate or 1) - line.financial.value_usd = line.financial.unit_cost_usd * qty - line.financial.unit_cost_mxn = line.financial.unit_cost_usd * (exchange_rate or 1) - line.financial.value_mxn = line.financial.unit_cost_mxn * qty - line.financial.value_mc = capture * qty \ No newline at end of file diff --git a/backend/api/v1/modules/a76/items/imports/validators/common.py b/backend/api/v1/modules/a76/items/imports/validators/common.py deleted file mode 100644 index 3ab71464..00000000 --- a/backend/api/v1/modules/a76/items/imports/validators/common.py +++ /dev/null @@ -1,406 +0,0 @@ -from sqlalchemy import exists -from sqlalchemy.orm import Session -from api.v1.modules.a76.invoices.common.common_validators import invoice_id_required -from api.v1.modules.a76.items.imports.validators.calculations import apply_calculations -from core.exceptions import ErrorCollector -from sqlalchemy import func - -from ...common.fractions import search_fraction_preference -from ...common.common_validators import item_exists -from ...models import LineItem -from ...line_customs.models import FractionType, LineCustom -from api.v1.modules.sitar.fracciones_usa.catalog_resolve import ( - resolve_american_fraction_from_sitar, - store_canonical_american_code, -) -from api.v1.modules.a76.items.schemas import LineItemCreate -from api.v1.modules.a76.invoices.models import InvoiceHeader -from api.v1.modules.a76.classes.models import Class -from api.v1.modules.a76.general_catalogs.units_of_measure.models import UnitOfMeasure -from api.v1.modules.a76.general_catalogs.packages.models import Package -from api.v1.modules.public.reference_data.countries.models import Country -from api.v1.modules.a76.general_catalogs.sectors.models import Sector -from api.v1.modules.public.reference_data.valuation_methods.models import ( - ValuationMethod, -) -from api.v1.modules.a76.parts.models import Part -from api.v1.modules.a76.app_settings.service import AppSettingsService -from api.v1.modules.a76.general_catalogs.company.models import Company - -def validate_common( - db: Session, - line: LineItemCreate, - tenant_id: int, - company_id: int, - errors: ErrorCollector, - line_number: int, -): - invoice: InvoiceHeader = invoice_id_required( - db, line.invoice_id, tenant_id, company_id, errors - ) - line_item: LineItem = item_exists(db, line.line_number, tenant_id, company_id) - - fecha_factura = invoice.invoice_date if invoice else None - fraction = None - - class_ = db.query(Class).filter(Class.id == line.class_id).first() - if not class_: - errors.add_error( - field=f"line[{line_number}].class_id", - message="La clase especificada no existe.", - solution=["Darla de alta en el catalogo de clases."], - code="CLASS_NOT_FOUND", - ) - else: - if not line.unit_of_measure and not class_.unit_of_measure: - errors.add_error( - field=f"line[{line_number}].unit_of_measure", - message="La unidad de medida es obligatoria para la clase especificada.", - solution=["Proporciona una unidad de medida valida."], - code="UNIT_OF_MEASURE_REQUIRED", - ) - - if not line.customs or not line.customs.fraction: - if not line_item: - if not class_.fraction: - errors.add_error( - field=f"line[{line_number}].customs.fraction", - message="La fracción arancelaria es obligatoria para la clase especificada.", - solution=["Proporciona una fracción arancelaria valida."], - code="FRACTION_REQUIRED", - ) - else: - fraction = class_.fraction - else: - if not line.customs or not line.customs.fraction: - if not class_.fraction: - errors.add_error( - field=f"line[{line_number}].customs.fraction", - message="La fracción arancelaria es obligatoria para la clase especificada.", - solution=["Proporciona una fracción arancelaria valida."], - code="FRACTION_REQUIRED", - ) - else: - fraction = class_.fraction - else: - if line_item: - fraction = line.customs.fraction - - if (not line.description or not line.description.description_spanish) and not class_.description_es: - errors.add_error( - field=f"line[{line_number}].description.description_spanish", - message="La descripción en español es obligatoria para la clase especificada.", - solution=["Proporciona una descripción en español valida."], - code="DESCRIPTION_SPANISH_REQUIRED", - ) - - if (not line.description or not line.description.description_english) and not class_.description_en: - errors.add_error( - field=f"line[{line_number}].description.description_english", - message="La descripción en inglés es obligatoria para la clase especificada.", - solution=["Proporciona una descripción en inglés valida."], - code="DESCRIPTION_ENGLISH_REQUIRED", - ) - - if line.quantity and line.quantity.quantity and line.quantity.quantity <= 0: - errors.add_error( - field=f"line[{line_number}].quantity.quantity", - message="La cantidad debe ser mayor a cero.", - solution=["Proporciona una cantidad valida."], - code="QUANTITY_MUST_BE_GREATER_THAN_ZERO", - ) - - if line.unit_of_measure: - um = ( - db.query(func.count(UnitOfMeasure.id)) - .filter( - UnitOfMeasure.id == line.unit_of_measure, - UnitOfMeasure.tenant_id == tenant_id, - UnitOfMeasure.company_id == company_id, - ) - .scalar() - ) - if um == 0: - errors.add_error( - field=f"line[{line_number}].unit_of_measure", - message="La unidad de medida especificada no existe.", - solution=["Proporciona una unidad de medida valida."], - code="UNIT_OF_MEASURE_NOT_FOUND", - ) - - if line.quantity and line.quantity.package_id: - package = ( - db.query(func.count(Package.id)) - .filter( - Package.id == line.quantity.package_id, - Package.tenant_id == tenant_id, - Package.company_id == company_id, - ) - .scalar() - ) - if package == 0: - errors.add_error( - field=f"line[{line_number}].quantity.package_id", - message="El paquete especificado no existe.", - solution=["Proporciona un paquete valido."], - code="PACKAGE_NOT_FOUND", - ) - if not line.quantity.package_quantity: - errors.add_error( - field=f"line[{line_number}].quantity.package_quantity", - message="La cantidad de paquetes es obligatoria cuando se proporciona el paquete.", - solution=["Proporciona una cantidad de paquetes valida."], - code="PACKAGE_QUANTITY_REQUIRED", - ) - if line.quantity.package_quantity <= 0: - errors.add_error( - field=f"line[{line_number}].quantity.package_quantity", - message="La cantidad de paquetes debe ser mayor a cero.", - solution=["Proporciona una cantidad de paquetes valida."], - code="PACKAGE_QUANTITY_MUST_BE_GREATER_THAN_ZERO", - ) - else: - if line.quantity and line.quantity.package_quantity and (line.quantity.package_quantity > 0 and not line.quantity.package_id): - errors.add_error( - field=f"line[{line_number}].quantity.package_id", - message="El paquete es obligatorio cuando se proporciona la cantidad de paquetes.", - solution=["Proporciona un paquete valido."], - code="PACKAGE_ID_REQUIRED", - ) - - country = None - fraction_type = None - sector = None - if fraction: - fraction = (line.customs.fraction if line.customs.fraction else fraction) if line.customs else fraction - - country = line.customs.origin_country if line.customs else None - if line_item and line_item.customs: - country = ( - line_item.customs.origin_country - if line_item.customs.origin_country - else country - ) - - fraction_type = (line.customs.fraction_type.upper() if line.customs.fraction_type else None) if line.customs else None - if line_item and line_item.customs: - fraction_type = ( - line_item.customs.fraction_type - if line_item.customs.fraction_type - else fraction_type - ) - - sector = line.customs.sector if line.customs else None - if line_item and line_item.customs: - sector = line_item.customs.sector if line_item.customs.sector else sector - - country_m3 = db.query(Country.m3_key).filter(Country.m3_key == country).scalar() - if not country_m3: - country_m3 = ( - db.query(Country.m3_key).filter(Country.ame_key == country).scalar() - ) - - country = country_m3 - if not country: - errors.add_error( - field=f"line[{line_number}].customs.origin_country", - message="El país de origen especificado no existe.", - solution=["Proporciona un país de origen valido."], - code="ORIGIN_COUNTRY_NOT_FOUND", - ) - else: - if fraction_type and fraction_type.strip().upper() not in vars(FractionType).values(): - valid_types = [ - v - for k, v in vars(FractionType).items() - if not k.startswith("_") and isinstance(v, str) - ] - errors.add_error( - field=f"line[{line_number}].customs.fraction_type", - message="El tipo de fracción especificado no es válido.", - solution=[ - f"Proporciona un tipo de fracción válido. Valores permitidos: {', '.join(valid_types)}" - ], - code="FRACTION_TYPE_INVALID", - value=fraction_type, - ) - elif fraction_type: - ft_upper = fraction_type.strip().upper() - if ft_upper == FractionType.PROSEC and not sector: - errors.add_error( - field=f"line[{line_number}].customs.sector", - message="El sector es obligatorio cuando el tipo de fracción es 'PROSEC'.", - solution=["Proporciona un sector valido."], - code="SECTOR_REQUIRED_FOR_PROSEC", - ) - elif ft_upper != FractionType.PROSEC and sector: - errors.add_error( - field=f"line[{line_number}].customs.sector", - message="El sector solo es aplicable cuando el tipo de fracción es 'PROSEC'.", - solution=[ - "Elimina el sector o cambia el tipo de fracción a 'PROSEC'." - ], - code="SECTOR_ONLY_FOR_PROSEC", - ) - elif ft_upper == FractionType.PROSEC and sector: - sector_db: Sector = ( - db.query(Sector).filter( - Sector.key == sector, - Sector.tenant_id == tenant_id, - Sector.company_id == company_id, - ).scalar() - ) - if sector_db: - errors.add_error( - field=f"line[{line_number}].customs.sector", - message="El sector especificado no existe.", - solution=["Proporciona un sector valido."], - code="SECTOR_NOT_FOUND", - ) - else: - if not sector_db.authorized: - errors.add_error( - field=f"line[{line_number}].customs.sector", - message="El sector especificado no está autorizado.", - solution=["Proporciona un sector autorizado."], - code="SECTOR_NOT_AUTHORIZED", - ) - - company_db = db.query(Company).filter(Company.id == company_id).first() - if not company_db.prosec: - errors.add_error( - field=f"line[{line_number}].customs.sector", - message=" La empresa no cuenta con autorización PROSEC.", - solution=[ - "Accese a los datos de la empresa y selecione la opción Pertenece al Programa de Promoción Sectorial y capture el número de permiso PROSEC." - ], - code="COMPANY_NOT_AUTHORIZED_FOR_PROSEC", - ) - - if fraction: - search_fraction_preference( - db=db, - country=country, - fraccion=fraction, - fraction_type=fraction_type, - sector=sector, - invoice_date=fecha_factura, - errors=errors, - ) - - if line.customs.american_fraction: - raw_american_fraction = str(line.customs.american_fraction) - resolved = resolve_american_fraction_from_sitar(raw_american_fraction) - if not resolved: - errors.add_error( - field=f"line[{line_number}].customs.american_fraction", - message="La fracción americana especificada no existe.", - solution=["Proporciona una fracción americana valida."], - code="AMERICAN_FRACTION_NOT_FOUND", - ) - else: - _, canon = resolved - line.customs.american_fraction = store_canonical_american_code(canon) - - if line.order: - if len(line.order) > 20: - errors.add_error( - field=f"item.order", - message="El campo orden no debe exceder los 20 caracteres.", - solution=["Proporciona un valor valido para el campo orden."], - code="ORDER_EXCEEDS_MAX_LENGTH", - ) - - unit_of_measure = line.unit_of_measure or ( - class_.unit_of_measure if class_ else None - ) - - # 0. Obtener parámetros de configuración para validaciones dinámicas - settings = AppSettingsService.get_resolved_settings(db, int(tenant_id), int(company_id)) - - def find_in_obj(obj, target_key): - if isinstance(obj, dict): - for k, v in obj.items(): - if k.lower() == target_key.lower(): - return v - res = find_in_obj(v, target_key) - if res is not None: return res - elif hasattr(obj, '__dict__'): - dict_rep = obj.__dict__ - for k, v in dict_rep.items(): - if k.startswith('_'): continue - if k.lower() == target_key.lower(): - return v - res = find_in_obj(v, target_key) - if res is not None: return res - return None - - # Resolver el CÓDIGO de la unidad de medida para validación (Blindaje con STRIP y UPPER) - uom_code = "" - if line.unit_of_measure: - # Aseguramos que el ID sea entero - try: - target_uom_id = int(line.unit_of_measure) - except: - target_uom_id = line.unit_of_measure - - uom_rec = db.query(UnitOfMeasure).filter( - UnitOfMeasure.id == target_uom_id, - UnitOfMeasure.tenant_id == tenant_id, - UnitOfMeasure.company_id == company_id - ).first() - if uom_rec and uom_rec.code: - uom_code = str(uom_rec.code).strip().upper() - elif class_ and class_.unit_of_measure: - uom_code = str(class_.unit_of_measure).strip().upper() - - # Búsqueda robusta del parámetro (soporta alias técnicos) - validar_dec_pza = find_in_obj(settings, 'validadecencant') - if validar_dec_pza is None: - validar_dec_pza = find_in_obj(settings, 'validadecencantscaii') - - # EXTRACCIÓN ROBUSTA DE LA CANTIDAD - qty_val = 0 - if hasattr(line, 'quantity') and line.quantity: - qty_val = getattr(line.quantity, 'quantity', 0) or 0 - - # Validación: Comparar contra PZA y variantes comunes - es_pieza = uom_code in ("PZA", "PZ", "PIEZA", "PIE", "Pzas", "Pza") - config_activa = str(validar_dec_pza).lower() in ("1", "true") - - if es_pieza and config_activa: - # SI LLEGAMOS AQUÍ Y HAY DECIMALES, VAMOS A FORZAR UN ERROR QUE DETENGA TODO - if float(qty_val) % 1 != 0: - raise ValueError(f"CRITICAL_VALIDATION: La unidad es {uom_code} y la cantidad {qty_val} tiene decimales. El proceso DEBE detenerse.") - - if es_pieza and config_activa and float(qty_val) % 1 != 0: - errors.add_error( - field=f"line[{line_number}].quantity.quantity", - message=f"La cantidad ({qty_val}) no puede tener decimales cuando la unidad es PZA.", - solution=["Captura una cantidad entera."], - code="QUANTITY_INTEGER_REQUIRED", - ) - - if line.valuation_method: - valuation_method_exists = db.query( - exists().where(ValuationMethod.key == line.valuation_method) - ).scalar() - if not valuation_method_exists: - errors.add_error( - field=f"line[{line_number}].valuation_method", - message="El método de valoración especificado no existe.", - solution=["Proporciona un método de valoración valido."], - code="VALUATION_METHOD_NOT_FOUND", - ) - - if line.part_number_id: - part_exists = db.query(exists().where(Part.id == line.part_number_id)).scalar() - if not part_exists: - errors.add_error( - field=f"line[{line_number}].part_number_id", - message="El número de parte especificado no existe.", - solution=["Proporciona un número de parte valido."], - code="PART_NUMBER_NOT_FOUND", - ) - - apply_calculations(db, line, tenant_id, company_id, line_number) \ No newline at end of file diff --git a/backend/api/v1/modules/a76/items/imports/validators/create.py b/backend/api/v1/modules/a76/items/imports/validators/create.py deleted file mode 100644 index daa3d52a..00000000 --- a/backend/api/v1/modules/a76/items/imports/validators/create.py +++ /dev/null @@ -1,417 +0,0 @@ -from decimal import Decimal -from sqlalchemy import func, exists -from sqlalchemy.orm import Session -from ...common.common_validators import count_items -from core.exceptions import ErrorCollector - -from ...models import LineItem -from api.v1.modules.a24.fa.fa_item_lines.models import FaLineItem -from ...models import LineItem -from api.v1.modules.a76.invoices.models import InvoiceHeader -from api.v1.modules.a76.classes.models import Class -from api.v1.modules.a76.general_catalogs.packages.models import Package -from api.v1.modules.sitar.fracciones_usa.catalog_resolve import ( - american_fraction_ad_valorem_from_row, - resolve_american_fraction_from_sitar, - store_canonical_american_code, -) -from api.v1.modules.a76.app_settings.service import AppSettingsService -from .common import validate_common - - -def _normalize_weight_type(logistics) -> str: - """Paridad con CSV: enum o string a 'KGS'/'LBS' para comparaciones.""" - if logistics is None: - return "KGS" - wt = getattr(logistics, "weight_type", None) or "KGS" - if hasattr(wt, "value"): - wt = wt.value - weight_str = str(wt).upper() if wt else "KGS" - return weight_str if weight_str in ("KGS", "LBS") else "KGS" - - -def validate_create( - db: Session, - line: LineItem, - tenant_id: int, - company_id: int, - errors: ErrorCollector, - line_number: int, -): - """ - Validates and calculates fields for a new line item before DB creation. - Works with Pydantic schemas, modifying them in-place. - - Args: - line: LineItemCreate schema with nested data (financial, quantity, customs, etc.) - invoice_id: ID of the invoice this line belongs to - fa_data: FaLineItemCreateDTO or None (None for INV system) - """ - - # Access fa_data safely - fa_data: FaLineItem = getattr(line, "fa_data", None) - - # Required field validations - if not line.class_id: - errors.add_required_error(field=f"line[{line_number}].class_id") - - if not line.quantity or line.quantity.quantity is None: - errors.add_required_error(field=f"line[{line_number}].quantity.quantity") - elif line.quantity.quantity <= 0: - errors.add_error( - field=f"line[{line_number}].quantity.quantity", - message=f"La cantidad debe ser mayor a cero (recibido: {line.quantity.quantity})", - solution=["Capturar una cantidad válida mayor a cero."], - code="INVALID_QUANTITY", - value=float(line.quantity.quantity) - ) - - # 0. Obtener parámetros de configuración para validaciones dinámicas - settings = AppSettingsService.get_resolved_settings(db, int(tenant_id), int(company_id)) - - def find_in_obj(obj, target_key): - if isinstance(obj, dict): - for k, v in obj.items(): - if k.lower() == target_key.lower(): - return v - res = find_in_obj(v, target_key) - if res is not None: return res - elif hasattr(obj, '__dict__'): - dict_rep = obj.__dict__ - for k, v in dict_rep.items(): - if k.startswith('_'): continue - if k.lower() == target_key.lower(): - return v - res = find_in_obj(v, target_key) - if res is not None: return res - return None - - # Parámetro SCAF: Calcular costo unitario en base a valor total - calc_costo_unit = find_in_obj(settings, 'calcularcostounitarioenbaseavalortotal') or \ - find_in_obj(settings, 'calcularcostounitarioenbaseavalortotalscaf') - - if fa_data and not fa_data.is_subitem: - # Si el parámetro está apagado (o no existe), el costo unitario es obligatorio - if str(calc_costo_unit) != "1": - if ( - not line.financial - or not line.financial.unit_cost_capture - or line.financial.unit_cost_capture <= 0 - ): - errors.add_required_error( - field=f"line[{line_number}].financial.unit_cost_capture" - ) - - if not line.quantity or line.quantity.net_weight is None: - errors.add_required_error(field=f"line[{line_number}].quantity.net_weight") - elif line.quantity.net_weight <= 0: - errors.add_error( - field=f"line[{line_number}].quantity.net_weight", - message=f"El peso neto debe ser mayor a cero (recibido: {line.quantity.net_weight})", - solution=["Capturar un peso neto válido mayor a cero."], - code="INVALID_NET_WEIGHT", - value=float(line.quantity.net_weight) - ) - - if not line.customs or not line.customs.origin_country: - errors.add_required_error(field=f"line[{line_number}].customs.origin_country") - - if not line.customs or not line.customs.fraction_type: - errors.add_required_error(field=f"line[{line_number}].customs.fraction_type") - - # FA-specific validations - if fa_data: - if ( - fa_data.is_subitem and fa_data.contains_subitems - ) and not fa_data.subitem_number: - errors.add_required_error( - field=f"line[{line_number}].fa_data.subitem_number" - ) - - # Validar que si es un subitem, existe un item principal correspondiente - if ( - fa_data.is_subitem - and fa_data.subitem_number - and fa_data.subitem_number != 0 - ): - principal_item_exists = db.query( - exists().where( - (LineItem.id == FaLineItem.id) - & (LineItem.id == LineItem.id) - & (LineItem.invoice_id == line.invoice_id) - & (LineItem.line_number == line_number) - & (FaLineItem.is_subitem == False) - & (FaLineItem.contains_subitems == True) - & (LineItem.tenant_id == tenant_id) - & (LineItem.company_id == company_id) - ) - ).scalar() - - if not principal_item_exists: - errors.add_error( - field=f"line[{line_number}]", - message=f"No existe un item principal registrado para esta linea {line_number} con subitem {fa_data.subitem_number}", - solution=[ - "Registrar el item principal correspondiente a esta linea antes de registrar subitems." - ], - code="SUBITEM_WITHOUT_PRINCIPAL_ITEM", - ) - - if fa_data.is_subitem and ( - fa_data.subitem_number == 0 or not fa_data.subitem_number - ): - errors.add_error( - field=f"line[{line_number}]", - message=f"El número de subitem no puede ser 0 si la línea es un subitem.", - solution=["Asignar un número de subitem mayor a 0 para esta línea."], - code="SUBITEM_NUMBER_INVALID", - ) - - validate_common(db, line, tenant_id, company_id, errors, line_number) - - if not errors.has_errors(): - # Ensure nested objects exist for calculations - from ...schemas import ( - LineFinancialCreate, - LineQuantityCreate, - LineCustomCreate, - LineDescriptionCreate, - ) - - if line.financial is None: - line.financial = LineFinancialCreate() - if line.quantity is None: - line.quantity = LineQuantityCreate() - if line.customs is None: - line.customs = LineCustomCreate() - if line.description is None: - line.description = LineDescriptionCreate() - - # Obtener la factura para acceder a tipo de cambio, moneda y peso - invoice: InvoiceHeader = ( - db.query(InvoiceHeader) - .filter( - InvoiceHeader.id == line.invoice_id, - InvoiceHeader.tenant_id == tenant_id, - InvoiceHeader.company_id == company_id, - ) - .first() - ) - - if not invoice or not invoice.financials or not invoice.logistics: - errors.add_error( - field=f"line[{line_number}]", - message="No se pudo obtener información de la factura", - solution=[ - "Verificar que la factura existe y tiene datos financieros y logísticos" - ], - code="INVOICE_DATA_MISSING", - ) - return - - # Obtener la clase para valores por defecto - class_info: Class = ( - db.query(Class) - .filter( - Class.id == line.class_id, - Class.tenant_id == tenant_id, - Class.company_id == company_id, - ) - .first() - ) - - # ========================================== - # ASIGNAR TIPO DE CAMBIO - # ========================================== - exchange_rate = invoice.financials.exchange_rate or Decimal("1.0") - - # ========================================== - # ASIGNAR UNIDAD DE MEDIDA - # ========================================== - # Si no se proporcionó unidad de medida, usar la de la clase - if not line.unit_of_measure and class_info: - line.unit_of_measure = class_info.unit_of_measure - - # ========================================== - # ASIGNAR TIPOS DE MONEDA Y CALCULAR COSTOS - # ========================================== - currency_type = invoice.financials.currency_type - unit_cost_capture = line.financial.unit_cost_capture or Decimal("0") - - # Calcular costos según tipo de moneda - if currency_type == "USD" or currency_type == "ME": # Moneda Extranjera (ME) - line.financial.unit_cost_capture = unit_cost_capture - line.financial.unit_cost_usd = unit_cost_capture - line.financial.unit_cost_mxn = unit_cost_capture * exchange_rate - elif currency_type == "MXN" or currency_type == "MN": # Moneda Nacional (MN) - line.financial.unit_cost_capture = unit_cost_capture - line.financial.unit_cost_usd = ( - unit_cost_capture / exchange_rate if exchange_rate else Decimal("0") - ) - line.financial.unit_cost_mxn = unit_cost_capture - # Si es otro tipo de moneda, dejamos el costo como está - - # Calcular valores totales basados en cantidad y costo unitario - quantity = line.quantity.quantity or Decimal("0") - - # Valor Comercial - if line.financial.unit_cost_usd is not None: - line.financial.value_usd = line.financial.unit_cost_usd * quantity - if line.financial.unit_cost_mxn is not None: - line.financial.value_mxn = line.financial.unit_cost_mxn * quantity - - # Valor Aduanas (asumiendo que es igual al Valor Comercial por defecto) - line.financial.customs_value_usd = line.financial.value_usd - line.financial.customs_value_mxn = line.financial.value_mxn - - # Valor MP Temp (Materia Prima Temporal) - line.financial.value_temp_material_usd = line.financial.value_usd - line.financial.value_temp_material_mxn = line.financial.value_mxn - - # value_mc: paridad con calculate_values y cargas CSV - currency = getattr(invoice.financials, "currency", None) - if currency == "foreign": - line.financial.value_mc = line.financial.value_usd - elif currency == "local": - line.financial.value_mc = line.financial.value_usd - elif currency == "manual": - line.financial.value_mc = unit_cost_capture * quantity - else: - line.financial.value_mc = line.financial.value_usd - - # ========================================== - # VALIDAR Y CONVERTIR PESOS NETOS - # ========================================== - invoice_weight_type = _normalize_weight_type(invoice.logistics) - quantity = line.quantity.quantity or Decimal("0") - net_weight_input = line.quantity.net_weight or Decimal("0") - - # UOM peso: aceptar 24/"24" y 25/"25" (paridad con CSV) - uom = line.unit_of_measure - unit_is_kgs = uom is not None and (str(uom) == "24" or uom == 24) - unit_is_lbs = uom is not None and (str(uom) == "25" or uom == 25) - - # Calcular peso neto en kilogramos (estándar interno) - if unit_is_kgs: - if invoice_weight_type.lower() == "kgs": - line.quantity.net_weight = quantity - else: # invoice en libras - line.quantity.net_weight = quantity * Decimal("2.204624") - elif unit_is_lbs: - if invoice_weight_type.lower() == "kgs": - line.quantity.net_weight = quantity / Decimal("2.204624") - else: # invoice en libras - line.quantity.net_weight = quantity - else: - # Otra unidad de medida - usar peso capturado y convertir si es necesario - if invoice_weight_type.lower() == "kgs": - # El peso capturado está en kilos - line.quantity.net_weight = net_weight_input - else: - # El peso capturado está en libras, convertir a kilos - line.quantity.net_weight = net_weight_input / Decimal("2.204624") - - # ========================================== - # CALCULAR PESO BRUTO - # ========================================== - gross_weight_input = line.quantity.gross_weight - package_quantity = line.quantity.package_quantity or 0 - package_weight_unit = Decimal("0") - - # Obtener peso unitario del bulto si existe - if line.quantity.package_id: - package: Package = ( - db.query(Package) - .filter( - Package.id == line.quantity.package_id, - Package.tenant_id == tenant_id, - Package.company_id == company_id, - ) - .first() - ) - if package and package.weight_unit: - package_weight_unit = package.weight_unit - - # Si no se proporcionó peso bruto, calcularlo - if not gross_weight_input or gross_weight_input == 0: - if invoice_weight_type.lower() == "kgs": - line.quantity.gross_weight = line.quantity.net_weight + ( - package_weight_unit * package_quantity - ) - else: # libras - line.quantity.gross_weight = line.quantity.net_weight + ( - (package_weight_unit * Decimal("2.204624")) * package_quantity - ) - else: - # Convertir peso bruto capturado según tipo de factura - if invoice_weight_type.lower() == "kgs": - line.quantity.gross_weight = gross_weight_input - else: # libras - line.quantity.gross_weight = gross_weight_input / Decimal("2.204624") - - # ========================================== - # VALIDAR PESO BRUTO < PESO NETO - # ========================================== - if line.quantity.gross_weight < line.quantity.net_weight: - line.quantity.gross_weight = line.quantity.net_weight + ( - package_weight_unit * package_quantity - ) - - # ========================================== - # ASIGNAR DESCRIPCIÓN DE BULTOS - # ========================================== - if package_quantity and package_quantity > 0 and line.quantity.package_id: - package: Package = ( - db.query(Package) - .filter( - Package.id == line.quantity.package_id, - Package.tenant_id == tenant_id, - Package.company_id == company_id, - ) - .first() - ) - if package: - line.description.package_description = package.description_es - else: - line.quantity.package_quantity = 0 - line.quantity.package_id = None - line.description.package_description = None - - # ========================================== - # ASIGNAR FRACCIÓN AMERICANA POR DEFECTO - # ========================================== - if not line.customs.american_fraction and class_info and class_info.us_fraction: - line.customs.american_fraction = class_info.us_fraction - - # Ad valorem desde catálogo SITAR (fracciones-usa) - if line.customs.american_fraction: - resolved = resolve_american_fraction_from_sitar(str(line.customs.american_fraction)) - if resolved: - sitar_row, canon = resolved - line.customs.american_fraction = store_canonical_american_code(canon) - line.customs.advalorem_american = american_fraction_ad_valorem_from_row( - sitar_row - ) - - # ========================================== - # ASIGNAR DESCRIPCIONES POR DEFECTO - # ========================================== - if not line.description.description_spanish and class_info: - line.description.description_spanish = class_info.description_es - - if not line.description.description_english and class_info: - line.description.description_english = class_info.description_en - - # ========================================== - # NORMALIZAR CAMPOS DE TEXTO - # ========================================== - # Convertir a mayúsculas campos que lo requieran - if line.description.brand: - line.description.brand = line.description.brand.upper().strip() - - if line.description.model: - line.description.model = line.description.model.upper().strip() - - # ========================================== - # CUMPLIMIENTO MEXICANO (Valores por defecto gestionados en apply_calculations) - # ========================================== diff --git a/backend/api/v1/modules/a76/items/imports/validators/update.py b/backend/api/v1/modules/a76/items/imports/validators/update.py deleted file mode 100644 index ba69b5b4..00000000 --- a/backend/api/v1/modules/a76/items/imports/validators/update.py +++ /dev/null @@ -1,252 +0,0 @@ -from decimal import Decimal -from sqlalchemy.orm import Session -from core.exceptions import ErrorCollector - -from ...models import LineItem -from api.v1.modules.a76.invoices.models import InvoiceHeader -from api.v1.modules.sitar.fracciones_usa.catalog_resolve import ( - american_fraction_ad_valorem_from_row, - resolve_american_fraction_from_sitar, - store_canonical_american_code, -) -from .common import validate_common - - -def validate_update( - db: Session, - line: LineItem, - existing_line: LineItem, - tenant_id: int, - company_id: int, - errors: ErrorCollector, - line_number: int, -): - """ - Validar y procesar actualización parcial de línea de importación temporal. - Si un campo no se proporciona, se mantiene el valor existente. - """ - validate_common(db, line, tenant_id, company_id, errors, line_number) - - if not errors.has_errors(): - # Ensure nested objects exist for calculations/partial updates - from ...schemas import ( - LineFinancialCreate, - LineQuantityCreate, - LineCustomCreate, - LineDescriptionCreate, - ) - - if line.financial is None: - line.financial = LineFinancialCreate() - if line.quantity is None: - line.quantity = LineQuantityCreate() - if line.customs is None: - line.customs = LineCustomCreate() - if line.description is None: - line.description = LineDescriptionCreate() - - # Obtener la factura para acceder a tipo de cambio, moneda y peso - invoice: InvoiceHeader = ( - db.query(InvoiceHeader) - .filter( - InvoiceHeader.id == line.invoice_id, - InvoiceHeader.tenant_id == tenant_id, - InvoiceHeader.company_id == company_id - ) - .first() - ) - - if not invoice or not invoice.financials or not invoice.logistics: - errors.add_error( - field=f"line[{line_number}]", - message="No se pudo obtener información de la factura", - solution=[ - "Verificar que la factura existe y tiene datos financieros y logísticos" - ], - code="INVOICE_DATA_MISSING", - ) - return - - # ========================================== - # ACTUALIZACIÓN PARCIAL DE CAMPOS - # Si no se proporciona, mantener valor existente - # ========================================== - - # Tipo de cambio de la factura - exchange_rate = invoice.financials.exchange_rate or Decimal("1.0") - - # Unidad de medida - if not line.unit_of_measure: - line.unit_of_measure = existing_line.unit_of_measure - - # Costo unitario - if line.financial.unit_cost_capture is None: - line.financial.unit_cost_capture = existing_line.financial.unit_cost_capture - - # Recalcular valores monetarios si el costo o la cantidad cambian - currency_type = invoice.financials.currency_type - unit_cost_capture = line.financial.unit_cost_capture or Decimal("0") - - if currency_type in ["USD", "ME"]: - line.financial.unit_cost_usd = unit_cost_capture - line.financial.unit_cost_mxn = unit_cost_capture * exchange_rate - elif currency_type in ["MXN", "MN"]: - line.financial.unit_cost_usd = (unit_cost_capture / exchange_rate) if exchange_rate else Decimal("0") - line.financial.unit_cost_mxn = unit_cost_capture - - quantity = line.quantity.quantity if line.quantity.quantity is not None else existing_line.quantity.quantity - quantity = quantity if quantity is not None else Decimal("0") - - if line.financial.unit_cost_usd is not None: - line.financial.value_usd = line.financial.unit_cost_usd * quantity - if line.financial.unit_cost_mxn is not None: - line.financial.value_mxn = line.financial.unit_cost_mxn * quantity - - line.financial.customs_value_usd = line.financial.value_usd - line.financial.customs_value_mxn = line.financial.value_mxn - - line.financial.value_temp_material_usd = line.financial.value_usd - line.financial.value_temp_material_mxn = line.financial.value_mxn - - # Convertir peso neto si se proporcionó - invoice_weight_type = invoice.logistics.weight_type - if line.quantity.net_weight is not None: - # Se proporcionó nuevo peso neto, convertir según tipo - net_weight_input = line.quantity.net_weight - - if invoice_weight_type.lower() == "kgs": - line.quantity.net_weight = net_weight_input - else: # libras, convertir a kilos - line.quantity.net_weight = net_weight_input / Decimal("2.204624") - else: - # Mantener peso existente - line.quantity.net_weight = existing_line.quantity.net_weight - - # Convertir peso bruto si se proporcionó - if line.quantity.gross_weight is not None: - gross_weight_input = line.quantity.gross_weight - - if invoice_weight_type.lower() == "kgs": - line.quantity.gross_weight = gross_weight_input - else: # libras, convertir a kilos - line.quantity.gross_weight = gross_weight_input / Decimal("2.204624") - else: - # Mantener peso existente - line.quantity.gross_weight = existing_line.quantity.gross_weight - - # Cantidad de bultos - if line.quantity.package_quantity is None: - line.quantity.package_quantity = existing_line.quantity.package_quantity - - # Clave de bultos - if not line.quantity.package_id: - line.quantity.package_id = existing_line.quantity.package_id - - # País de origen - if not line.customs.origin_country: - line.customs.origin_country = existing_line.customs.origin_country - - # Fracción arancelaria - if not line.customs.fraction: - line.customs.fraction = existing_line.customs.fraction - - # Tipo de fracción - if not line.customs.fraction_type: - line.customs.fraction_type = existing_line.customs.fraction_type - - # Sector - if not line.customs.sector: - line.customs.sector = existing_line.customs.sector - - # Fracción americana y su advalorem (SITAR) - if line.customs.american_fraction: - resolved = resolve_american_fraction_from_sitar(str(line.customs.american_fraction)) - if resolved: - sitar_row, canon = resolved - line.customs.american_fraction = store_canonical_american_code(canon) - line.customs.advalorem_american = american_fraction_ad_valorem_from_row( - sitar_row - ) - else: - # Mantener fracción americana existente - line.customs.american_fraction = existing_line.customs.american_fraction - line.customs.advalorem_american = existing_line.customs.advalorem_american - - # Orden de compra - if not line.order: - line.order = existing_line.order - - # Descripciones - if not line.description.description_spanish: - line.description.description_spanish = ( - existing_line.description.description_spanish - ) - - if not line.description.description_english: - line.description.description_english = ( - existing_line.description.description_english - ) - - if line.description.extra_description is None: - line.description.extra_description = ( - existing_line.description.extra_description - ) - - # Brand and model - if line.description.brand is not None: - line.description.brand = line.description.brand.upper().strip() - else: - line.description.brand = existing_line.description.brand - - if line.description.model is not None: - line.description.model = line.description.model.upper().strip() - else: - line.description.model = existing_line.description.model - - # --- Resolve Settings for inherited parameters --- - from api.v1.modules.a76.app_settings.service import AppSettingsService - settings = AppSettingsService.get_resolved_settings(db, tenant_id, company_id) - - inv_type = (invoice.invoice_type or "").strip().upper() - op_type = (invoice.operation_type or "").strip().lower() # 'imp' or 'exp' - - # Helper to get nested value from invoices.types.{op}.{type}.ssisgen.ssimpFormData - inv_map = settings.get("invoices", {}).get("types", {}).get(op_type, {}).get(inv_type, {}) - # Prefeir ssisgen for this type, then qsisgen, then root ssimpo - form_data = inv_map.get("ssisgen", {}).get("ssimpFormData", {}) or inv_map.get("qsisgen", {}).get("ssimpFormData", {}) or settings.get("ssimpo", {}) - - # Subpartidas (si aplica) - # Clarion: LOC:LevantarSubpartidas = S - levantar_sub = bool(form_data.get("levantar_subpartidas") or form_data.get("LevantarSubpartidas") or False) - if levantar_sub: - # TODO: Add specific sub-item validation if needed (e.g. parent_line mandatory if it's a subpartida) - # Currently we just ensure the field is carried over if not provided - pass - - # Número de parte - if not line.part_number_id: - line.part_number_id = existing_line.part_number_id - - # Pago de impuesto - if line.tax_payment is None: - line.tax_payment = existing_line.tax_payment - - # Forma de pago - if not line.payment_method: - line.payment_method = existing_line.payment_method - - # Método de valoración - if not line.valuation_method: - if existing_line.valuation_method: - line.valuation_method = existing_line.valuation_method - else: - # Tomar de SisImp/SisDef:MetValor (preferencias del sistema) - line.valuation_method = form_data.get("metvalor") or form_data.get("MetValor") - - # Número de entrada - if not line.description.entry_number: - line.description.entry_number = existing_line.description.entry_number - - # Lote - if not line.description.lot: - line.description.lot = existing_line.description.lot diff --git a/backend/api/v1/modules/a76/items/line_customs/__init__.py b/backend/api/v1/modules/a76/items/line_customs/__init__.py deleted file mode 100644 index 6d676bd7..00000000 --- a/backend/api/v1/modules/a76/items/line_customs/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -"""Line customs module""" -from .models import LineCustom -from .schemas import ( - LineCustomBase, - LineCustomCreate, - LineCustomUpdate, - LineCustomResponse, -) - -__all__ = [ - "LineCustom", - "LineCustomBase", - "LineCustomCreate", - "LineCustomUpdate", - "LineCustomResponse", -] diff --git a/backend/api/v1/modules/a76/items/line_customs/models.py b/backend/api/v1/modules/a76/items/line_customs/models.py deleted file mode 100644 index 0830b768..00000000 --- a/backend/api/v1/modules/a76/items/line_customs/models.py +++ /dev/null @@ -1,62 +0,0 @@ -from decimal import Decimal -from typing import Optional, TYPE_CHECKING -from sqlalchemy import String, Numeric, ForeignKey -from sqlalchemy.orm import Mapped, mapped_column, relationship -from core.database import Base - -if TYPE_CHECKING: - from ..models import LineItem - -class FractionType: - """Enumeration for fraction types""" - GENERAL = "GENERAL" - PROSEC = "PROSEC" - ALADI = "ALADI" - TLCS = "TLCS" - -class LineCustom(Base): - """ - Customs details for line items - Consolidates all line-level data from Q and S tables - """ - __tablename__ = "item_line_customs" - __table_args__ = { - "schema": "a76", - } - - id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) - item_line_id: Mapped[int] = mapped_column(ForeignKey("a76.item_lines.id")) - - # Tariff/Customs Classifications - fraction: Mapped[Optional[str]] = mapped_column(String(10)) # FRACCION / FRACCIONIMPO / FRACCIONEXPO - fraction_type: Mapped[Optional[FractionType]] = mapped_column(String(7)) # TIPOFRACCION / TIPOFRACCIONIMPO / TIPOFRACCIONEXPO - american_fraction: Mapped[Optional[str]] = mapped_column(String(16)) # FRACCIONAMERICANA - alternate_fraction: Mapped[Optional[str]] = mapped_column(String(10)) # FRACCIONALTERNA - reference_fraction: Mapped[Optional[str]] = mapped_column(String(10)) # FRACCIONREFERENCIA - octave_fraction: Mapped[Optional[str]] = mapped_column(String(10)) # FRACCIONROCTAVA - tlcan_fraction: Mapped[Optional[str]] = mapped_column(String(13)) # FRACCIONTLCAN - extra_american_fraction: Mapped[Optional[str]] = mapped_column(String(16)) # FRACAMESELEXTRA - garment_fraction: Mapped[Optional[str]] = mapped_column(String(19)) # FRACCIONDELAPRENDA/FRACCIONDELAPARTIDA - - # Ad Valorem - advalorem: Mapped[Optional[str]] = mapped_column(String(10)) # ADVIMPO / ADVEXPO - advalorem_numeric: Mapped[Optional[Decimal]] = mapped_column(Numeric(7, 2)) # ADVIMPONUM / ADVEXPONUM - advalorem_american: Mapped[Optional[Decimal]] = mapped_column(Numeric(5, 2)) # ADVAME - advalorem_tlcan: Mapped[Optional[Decimal]] = mapped_column(Numeric(5, 2)) # ADVTLCAN - - # Rates - rate: Mapped[Optional[str]] = mapped_column(String(10)) # TASAIM / TASAEX - depreciation_rate: Mapped[Optional[Decimal]] = mapped_column(Numeric(5, 2)) # TASADEPRECIA - - # Origin/Destination - origin_country: Mapped[Optional[str]] = mapped_column(String(3)) # PAISORIGEN - destination_country: Mapped[Optional[str]] = mapped_column(String(3)) # PAISDESTINO - optional_country: Mapped[Optional[str]] = mapped_column(String(3)) # PAISOPCIONAL - origin_procedure: Mapped[Optional[str]] = mapped_column(String(3)) # PROCEDENCIA - scrap_procedure: Mapped[Optional[str]] = mapped_column(String(3)) # PROCSCRAP - - # Sector - sector: Mapped[Optional[str]] = mapped_column(String(8)) # SECTOR - - # Relationship (one-to-one) - line: Mapped["LineItem"] = relationship(back_populates="customs") \ No newline at end of file diff --git a/backend/api/v1/modules/a76/items/line_customs/schemas.py b/backend/api/v1/modules/a76/items/line_customs/schemas.py deleted file mode 100644 index 92f2fde4..00000000 --- a/backend/api/v1/modules/a76/items/line_customs/schemas.py +++ /dev/null @@ -1,58 +0,0 @@ -from decimal import Decimal -from typing import Optional -from pydantic import BaseModel, Field, ConfigDict - -# ============================================================================ -# LINE CUSTOMS SCHEMAS -# ============================================================================ - -class LineCustomBase(BaseModel): - """Base schema for line customs""" - # Tariff/Customs Classifications - fraction: Optional[str] = Field(None, max_length=10, description="Fraction (FRACCION/FRACCIONIMPO/FRACCIONEXPO)") - fraction_type: Optional[str] = Field(None, max_length=7, description="Fraction type (TIPOFRACCION/TIPOFRACCIONIMPO/TIPOFRACCIONEXPO)") - american_fraction: Optional[str] = Field(None, max_length=16, description="American fraction (FRACCIONAMERICANA)") - alternate_fraction: Optional[str] = Field(None, max_length=10, description="Alternate fraction (FRACCIONALTERNA)") - reference_fraction: Optional[str] = Field(None, max_length=10, description="Reference fraction (FRACCIONREFERENCIA)") - octave_fraction: Optional[str] = Field(None, max_length=10, description="Octave fraction (FRACCIONROCTAVA)") - tlcan_fraction: Optional[str] = Field(None, max_length=13, description="TLCAN fraction (FRACCIONTLCAN)") - extra_american_fraction: Optional[str] = Field(None, max_length=16, description="Extra American fraction (FRACAMESELEXTRA)") - garment_fraction: Optional[str] = Field(None, max_length=19, description="Garment fraction (FRACCIONDELAPRENDA/FRACCIONDELAPARTIDA)") - - # Ad Valorem - advalorem: Optional[str] = Field(None, max_length=10, description="Ad valorem (ADVIMPO/ADVEXPO)") - advalorem_numeric: Optional[Decimal] = Field(None, description="Ad valorem numeric (ADVIMPONUM/ADVEXPONUM)") - advalorem_american: Optional[Decimal] = Field(None, description="Ad valorem American (ADVAME)") - advalorem_tlcan: Optional[Decimal] = Field(None, description="Ad valorem TLCAN (ADVTLCAN)") - - # Rates - rate: Optional[str] = Field(None, max_length=10, description="Rate (TASAIM/TASAEX)") - depreciation_rate: Optional[Decimal] = Field(None, description="Depreciation rate (TASADEPRECIA)") - - # Origin/Destination - origin_country: Optional[str] = Field(None, max_length=3, description="Origin country (PAISORIGEN)") - destination_country: Optional[str] = Field(None, max_length=3, description="Destination country (PAISDESTINO)") - optional_country: Optional[str] = Field(None, max_length=3, description="Optional country (PAISOPCIONAL)") - origin_procedure: Optional[str] = Field(None, max_length=3, description="Origin procedure (PROCEDENCIA)") - scrap_procedure: Optional[str] = Field(None, max_length=3, description="Scrap procedure (PROCSCRAP)") - - # Sector - sector: Optional[str] = Field(None, max_length=8, description="Sector (SECTOR)") - - -class LineCustomCreate(LineCustomBase): - """Schema for creating line customs""" - pass - - -class LineCustomUpdate(LineCustomBase): - """Schema for updating line customs""" - pass - - -class LineCustomResponse(LineCustomBase): - """Schema for line customs response""" - id: int - item_line_id: int - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/a76/items/line_descriptions/__init__.py b/backend/api/v1/modules/a76/items/line_descriptions/__init__.py deleted file mode 100644 index 227064f6..00000000 --- a/backend/api/v1/modules/a76/items/line_descriptions/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -"""Line descriptions module""" -from .models import LineDescription -from .schemas import ( - LineDescriptionBase, - LineDescriptionCreate, - LineDescriptionUpdate, - LineDescriptionResponse, -) - -__all__ = [ - "LineDescription", - "LineDescriptionBase", - "LineDescriptionCreate", - "LineDescriptionUpdate", - "LineDescriptionResponse", -] diff --git a/backend/api/v1/modules/a76/items/line_descriptions/models.py b/backend/api/v1/modules/a76/items/line_descriptions/models.py deleted file mode 100644 index 85eaa6b0..00000000 --- a/backend/api/v1/modules/a76/items/line_descriptions/models.py +++ /dev/null @@ -1,52 +0,0 @@ -from typing import Optional, TYPE_CHECKING -from sqlalchemy import Boolean, String, Text, Integer, ForeignKey -from sqlalchemy.orm import Mapped, mapped_column, relationship -from core.database import Base - -if TYPE_CHECKING: - from ..models import LineItem - -class LineDescription(Base): - """ - Description details for line items - Consolidates all line-level data from Q and S tables - """ - __tablename__ = "item_line_descriptions" - __table_args__ = { - "schema": "a76", - } - - id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) - item_line_id: Mapped[int] = mapped_column(ForeignKey("a76.item_lines.id")) - - # Descriptions - description_spanish: Mapped[Optional[str]] = mapped_column(String(4999)) # DESCRIPCIONE - description_english: Mapped[Optional[str]] = mapped_column(String(4999)) # DESCRIPCIONI - extra_description: Mapped[Optional[str]] = mapped_column(Text) # DESCRIPCIONEEXTRA - part_description: Mapped[Optional[str]] = mapped_column(String(500)) # DESCRIPCIONPARTE - class_description: Mapped[Optional[str]] = mapped_column(String(500)) # DESCRIPCIONCLASE - package_description: Mapped[Optional[str]] = mapped_column(String(500)) # DESCRIPCIONBULTO - - # Product attributes - brand: Mapped[Optional[str]] = mapped_column(String(50)) # MARCA - model: Mapped[Optional[str]] = mapped_column(String(50)) # MODELO - has_serial: Mapped[Optional[bool]] = mapped_column(Boolean) # LLEVASERIE - - # Additional information - additional_info_spanish: Mapped[Optional[str]] = mapped_column(String(1000)) # INFOADICIONESP - additional_info_english: Mapped[Optional[str]] = mapped_column(String(1000)) # INFOADICIONING - - # Lot and entry tracking - lot: Mapped[Optional[str]] = mapped_column(String(254)) # LOTE - entry_number: Mapped[Optional[str]] = mapped_column(String(50)) # NUMENTRADA/NUMERODEENTRADA - - # Eighth rule and A31 fields - eighth_rule_fraction: Mapped[Optional[str]] = mapped_column(String(20)) # Eighth Rule Fraction - eighth_rule_line: Mapped[Optional[int]] = mapped_column(Integer) # Eighth Rule Line - consider_a31: Mapped[Optional[bool]] = mapped_column(Boolean, default=False) # Consider in A31 - - # Machinery location - machinery_location: Mapped[Optional[str]] = mapped_column(String(200)) # Machinery and equipment location - - # Relationship (one-to-one) - line: Mapped["LineItem"] = relationship(back_populates="description") \ No newline at end of file diff --git a/backend/api/v1/modules/a76/items/line_descriptions/schemas.py b/backend/api/v1/modules/a76/items/line_descriptions/schemas.py deleted file mode 100644 index 8a65d68e..00000000 --- a/backend/api/v1/modules/a76/items/line_descriptions/schemas.py +++ /dev/null @@ -1,55 +0,0 @@ -from typing import Optional -from pydantic import BaseModel, Field, ConfigDict - -# ============================================================================ -# LINE DESCRIPTION SCHEMAS -# ============================================================================ - -class LineDescriptionBase(BaseModel): - """Base schema for line descriptions""" - # Descriptions - description_spanish: Optional[str] = Field(None, max_length=4999, description="Description in Spanish (DESCRIPCIONE)") - description_english: Optional[str] = Field(None, max_length=4999, description="Description in English (DESCRIPCIONI)") - extra_description: Optional[str] = Field(None, description="Extra description (DESCRIPCIONEEXTRA)") - part_description: Optional[str] = Field(None, max_length=500, description="Part description (DESCRIPCIONPARTE)") - class_description: Optional[str] = Field(None, max_length=500, description="Class description (DESCRIPCIONCLASE)") - package_description: Optional[str] = Field(None, max_length=500, description="Package description (DESCRIPCIONBULTO)") - - # Product attributes - brand: Optional[str] = Field(None, max_length=50, description="Brand (MARCA)") - model: Optional[str] = Field(None, max_length=50, description="Model (MODELO)") - has_serial: Optional[bool] = Field(None, description="Has serial (LLEVASERIE)") - - # Additional information - additional_info_spanish: Optional[str] = Field(None, max_length=1000, description="Additional info in Spanish (INFOADICIONESP)") - additional_info_english: Optional[str] = Field(None, max_length=1000, description="Additional info in English (INFOADICIONING)") - - # Lot and entry tracking - lot: Optional[str] = Field(None, max_length=254, description="Lot (LOTE)") - entry_number: Optional[str] = Field(None, max_length=50, description="Entry number (NUMENTRADA/NUMERODEENTRADA)") - - # Eighth rule and A31 fields - eighth_rule_fraction: Optional[str] = Field(None, max_length=20, description="Eighth Rule Fraction") - eighth_rule_line: Optional[int] = Field(None, description="Eighth Rule Line") - consider_a31: Optional[bool] = Field(False, description="Consider in A31") - - # Machinery location - machinery_location: Optional[str] = Field(None, max_length=200, description="Machinery and equipment location") - - -class LineDescriptionCreate(LineDescriptionBase): - """Schema for creating line description""" - pass - - -class LineDescriptionUpdate(LineDescriptionBase): - """Schema for updating line description""" - pass - - -class LineDescriptionResponse(LineDescriptionBase): - """Schema for line description response""" - id: int - item_line_id: int - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/a76/items/line_financials/__init__.py b/backend/api/v1/modules/a76/items/line_financials/__init__.py deleted file mode 100644 index a090aee0..00000000 --- a/backend/api/v1/modules/a76/items/line_financials/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -"""Line financials module""" -from .models import LineFinancial -from .schemas import ( - LineFinancialBase, - LineFinancialCreate, - LineFinancialUpdate, - LineFinancialResponse, -) - -__all__ = [ - "LineFinancial", - "LineFinancialBase", - "LineFinancialCreate", - "LineFinancialUpdate", - "LineFinancialResponse", -] diff --git a/backend/api/v1/modules/a76/items/line_financials/models.py b/backend/api/v1/modules/a76/items/line_financials/models.py deleted file mode 100644 index 3edaaf7d..00000000 --- a/backend/api/v1/modules/a76/items/line_financials/models.py +++ /dev/null @@ -1,101 +0,0 @@ -from decimal import Decimal -from typing import Optional, TYPE_CHECKING -from sqlalchemy import Numeric, ForeignKey -from sqlalchemy.orm import Mapped, mapped_column, relationship -from core.database import Base - -if TYPE_CHECKING: - from ..models import LineItem - -class LineFinancial(Base): - """ - Financial details for line items - Consolidates all line-level data from Q and S tables - """ - __tablename__ = "item_line_financials" - __table_args__ = { - "schema": "a76", - } - - id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) - item_line_id: Mapped[int] = mapped_column(ForeignKey("a76.item_lines.id")) - - # Costs - Capture - unit_cost_capture: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # COSTOUNITARIOCAPTURA - - # Costs - USD - unit_cost_usd: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # COSTOUNITARIODLLS/COSTOUNITARIOME - unit_cost_commercial_usd: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # COSTOUCOMDLLS - unit_cost_current_usd: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # COSTOACTUALDLLS - unit_cost_depreciated_usd: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # COSTODEPRECME - unit_cost_subitem_usd: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # COSTOUNITARIOSUBPDLLS - unit_cost_auxiliary_usd: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # COSTOUAUXILIARME - sales_cost_usd: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # COSTOVENTAME - commercial_unit_cost: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # COSTOUNICOMERCIAL - - # Costs - MXN - unit_cost_mxn: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # COSTOUNITARIOPESOS/COSTOUNITARIOMN - unit_cost_commercial_mxn: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # COSTOUCOMPESOS - unit_cost_current_mxn: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # COSTOACTUALMN - unit_cost_depreciated_mxn: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # COSTODEPRECMN - unit_cost_subitem_mxn: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # COSTOUNITARIOSUBPPESOS - sales_cost_mxn: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # COSTOVENTAMN - - # Costs - MC (Custom Currency) - unit_cost_mc: Mapped[Optional[Decimal]] = mapped_column(Numeric(29, 8)) # COSTOUNITARIOMC - - # Values - MXN - value_mxn: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # VALORMN/VALORIMPOMN/VALOREXPOMN - value_commercial_mxn: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # VALOREXPOCOMMN - value_updated_mxn: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # VALORACTUALIZADOMN - value_subitem_mxn: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # VALORSUBPMN - sub_import_value_mxn: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # SUBVALORIMPOMN - value_returned_mxn: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # VALORRETORNADOMN - value_depreciated_mxn: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # VALORDEPRECIADOMN - customs_value_mxn: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # VALORADUANASMN - value_total_mxn: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # VALORTOTALMN - value_temp_material_mxn: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # VALORMPTEMPMN - value_def_material_mxn: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # VALORMPDEFMN - value_added_mxn: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # VALORAGREMN - value_national_packing_mxn: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # VALEMPAQUENACMN - vat_mxn: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # IVAIMPOMN/IVAEXPOMN/VALORIVAMN - vat_used_mxn: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # VALORIVAMNUSADO - advalorem_line_mxn: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # ADVALORMNLINEAPED - - # Values - USD - value_usd: Mapped[Optional[Decimal]] = mapped_column(Numeric(29, 8)) # VALORME/VALORIMPOME/VALOREXPOME - value_commercial_usd: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # VALOREXPOCOMME - value_updated_usd: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # VALORACTUALIZADOME - value_subitem_usd: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # VALORSUBPME - sub_import_value_usd: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # SUBVALORIMPOME - value_returned_usd: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # VALORRETORNADOME - value_depreciated_usd: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # VALORDEPRECIADOME - customs_value_usd: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # VALORADUANASME - value_auxiliary_usd: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # VALORIMPOAUXILIARME / VALOREXPORAUXILIARME - value_total_usd: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # VALORTOTALME - value_temp_material_usd: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # VALORMPTEMPME - value_def_material_usd: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # VALORMPDEFME - value_added_usd: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # VALORAGREME - value_national_packing_usd: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # VALEMPAQUENACME - value_us_packing_usd: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # VALEMPAQUEUSME - vat_usd: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # IVAIMPOME/IVAEXPOME/VALORIVAME - vat_used_usd: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # VALORIVAMEUSADO - value_non_originating_usd: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # VALORNOORIGINARIOME - value_originating_usd: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # VALORORIGINARIOME - igi_amount_usd: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # MONTOIGIME - exempt_amount_usd: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # MONTOEXCENTOME - total_commercial_value: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # VALORTOTALCOMERCIAL - advalorem_line_usd: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # ADVALORMELINEAPED - - # Values - MC - value_mc: Mapped[Optional[Decimal]] = mapped_column(Numeric(29, 8)) # VALORIMPOMC/VALOREXPOMC - sub_import_value_mc: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # SUBVALORIMPOMC - vat_mc: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # IVAIMPOMC/IVAEXPOMC - value_added_mc: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # VALORAGREMC - value_national_packing_mc: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # VALEMPAQUENACMC - value_total_mc: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # VALORTOTALMC - value_temp_material_mc: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # VALORMPTEMPMC - value_def_material_mc: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # VALORMPDEFMC - - # Relationship (one-to-one) - line: Mapped["LineItem"] = relationship(back_populates="financial") \ No newline at end of file diff --git a/backend/api/v1/modules/a76/items/line_financials/schemas.py b/backend/api/v1/modules/a76/items/line_financials/schemas.py deleted file mode 100644 index 0779a481..00000000 --- a/backend/api/v1/modules/a76/items/line_financials/schemas.py +++ /dev/null @@ -1,104 +0,0 @@ -from decimal import Decimal -from typing import Optional -from pydantic import BaseModel, Field, ConfigDict - -# ============================================================================ -# LINE FINANCIAL SCHEMAS -# ============================================================================ - -class LineFinancialBase(BaseModel): - """Base schema for line financials""" - # Costs - Capture - unit_cost_capture: Optional[Decimal] = Field(None, description="Unit cost in capture currency (COSTOUNITARIOCAPTURA)") - - # Costs - USD - unit_cost_usd: Optional[Decimal] = Field(None, description="Unit cost in USD (COSTOUNITARIODLLS/COSTOUNITARIOME)") - unit_cost_commercial_usd: Optional[Decimal] = Field(None, description="Commercial unit cost in USD (COSTOUCOMDLLS)") - unit_cost_current_usd: Optional[Decimal] = Field(None, description="Current unit cost in USD (COSTOACTUALDLLS)") - unit_cost_depreciated_usd: Optional[Decimal] = Field(None, description="Depreciated unit cost in USD (COSTODEPRECME)") - unit_cost_subitem_usd: Optional[Decimal] = Field(None, description="Subitem unit cost in USD (COSTOUNITARIOSUBPDLLS)") - unit_cost_auxiliary_usd: Optional[Decimal] = Field(None, description="Auxiliary unit cost in USD (COSTOUAUXILIARME)") - sales_cost_usd: Optional[Decimal] = Field(None, description="Sales cost in USD (COSTOVENTAME)") - commercial_unit_cost: Optional[Decimal] = Field(None, description="Commercial unit cost (COSTOUNICOMERCIAL)") - - # Costs - MXN - unit_cost_mxn: Optional[Decimal] = Field(None, description="Unit cost in MXN (COSTOUNITARIOPESOS/COSTOUNITARIOMN)") - unit_cost_commercial_mxn: Optional[Decimal] = Field(None, description="Commercial unit cost in MXN (COSTOUCOMPESOS)") - unit_cost_current_mxn: Optional[Decimal] = Field(None, description="Current unit cost in MXN (COSTOACTUALMN)") - unit_cost_depreciated_mxn: Optional[Decimal] = Field(None, description="Depreciated unit cost in MXN (COSTODEPRECMN)") - unit_cost_subitem_mxn: Optional[Decimal] = Field(None, description="Subitem unit cost in MXN (COSTOUNITARIOSUBPPESOS)") - sales_cost_mxn: Optional[Decimal] = Field(None, description="Sales cost in MXN (COSTOVENTAMN)") - - # Costs - MC (Custom Currency) - unit_cost_mc: Optional[Decimal] = Field(None, description="Unit cost in MC (COSTOUNITARIOMC)") - - # Values - MXN - value_mxn: Optional[Decimal] = Field(None, description="Value in MXN (VALORMN/VALORIMPOMN/VALOREXPOMN)") - value_commercial_mxn: Optional[Decimal] = Field(None, description="Commercial value in MXN (VALOREXPOCOMMN)") - value_updated_mxn: Optional[Decimal] = Field(None, description="Updated value in MXN (VALORACTUALIZADOMN)") - value_subitem_mxn: Optional[Decimal] = Field(None, description="Subitem value in MXN (VALORSUBPMN)") - sub_import_value_mxn: Optional[Decimal] = Field(None, description="Sub-import value in MXN (SUBVALORIMPOMN)") - value_returned_mxn: Optional[Decimal] = Field(None, description="Returned value in MXN (VALORRETORNADOMN)") - value_depreciated_mxn: Optional[Decimal] = Field(None, description="Depreciated value in MXN (VALORDEPRECIADOMN)") - customs_value_mxn: Optional[Decimal] = Field(None, description="Customs value in MXN (VALORADUANASMN)") - value_total_mxn: Optional[Decimal] = Field(None, description="Total value in MXN (VALORTOTALMN)") - value_temp_material_mxn: Optional[Decimal] = Field(None, description="Temporary material value in MXN (VALORMPTEMPMN)") - value_def_material_mxn: Optional[Decimal] = Field(None, description="Definitive material value in MXN (VALORMPDEFMN)") - value_added_mxn: Optional[Decimal] = Field(None, description="Added value in MXN (VALORAGREMN)") - value_national_packing_mxn: Optional[Decimal] = Field(None, description="National packing value in MXN (VALEMPAQUENACMN)") - vat_mxn: Optional[Decimal] = Field(None, description="VAT in MXN (IVAIMPOMN/IVAEXPOMN/VALORIVAMN)") - vat_used_mxn: Optional[Decimal] = Field(None, description="VAT used in MXN (VALORIVAMNUSADO)") - advalorem_line_mxn: Optional[Decimal] = Field(None, description="Ad valorem line in MXN (ADVALORMNLINEAPED)") - - # Values - USD - value_usd: Optional[Decimal] = Field(None, description="Value in USD (VALORME/VALORIMPOME/VALOREXPOME)") - value_commercial_usd: Optional[Decimal] = Field(None, description="Commercial value in USD (VALOREXPOCOMME)") - value_updated_usd: Optional[Decimal] = Field(None, description="Updated value in USD (VALORACTUALIZADOME)") - value_subitem_usd: Optional[Decimal] = Field(None, description="Subitem value in USD (VALORSUBPME)") - sub_import_value_usd: Optional[Decimal] = Field(None, description="Sub-import value in USD (SUBVALORIMPOME)") - value_returned_usd: Optional[Decimal] = Field(None, description="Returned value in USD (VALORRETORNADOME)") - value_depreciated_usd: Optional[Decimal] = Field(None, description="Depreciated value in USD (VALORDEPRECIADOME)") - customs_value_usd: Optional[Decimal] = Field(None, description="Customs value in USD (VALORADUANASME)") - value_auxiliary_usd: Optional[Decimal] = Field(None, description="Auxiliary value in USD (VALORIMPOAUXILIARME/VALOREXPORAUXILIARME)") - value_total_usd: Optional[Decimal] = Field(None, description="Total value in USD (VALORTOTALME)") - value_temp_material_usd: Optional[Decimal] = Field(None, description="Temporary material value in USD (VALORMPTEMPME)") - value_def_material_usd: Optional[Decimal] = Field(None, description="Definitive material value in USD (VALORMPDEFME)") - value_added_usd: Optional[Decimal] = Field(None, description="Added value in USD (VALORAGREME)") - value_national_packing_usd: Optional[Decimal] = Field(None, description="National packing value in USD (VALEMPAQUENACME)") - value_us_packing_usd: Optional[Decimal] = Field(None, description="US packing value in USD (VALEMPAQUEUSME)") - vat_usd: Optional[Decimal] = Field(None, description="VAT in USD (IVAIMPOME/IVAEXPOME/VALORIVAME)") - vat_used_usd: Optional[Decimal] = Field(None, description="VAT used in USD (VALORIVAMEUSADO)") - value_non_originating_usd: Optional[Decimal] = Field(None, description="Non-originating value in USD (VALORNOORIGINARIOME)") - value_originating_usd: Optional[Decimal] = Field(None, description="Originating value in USD (VALORORIGINARIOME)") - igi_amount_usd: Optional[Decimal] = Field(None, description="IGI amount in USD (MONTOIGIME)") - exempt_amount_usd: Optional[Decimal] = Field(None, description="Exempt amount in USD (MONTOEXCENTOME)") - total_commercial_value: Optional[Decimal] = Field(None, description="Total commercial value (VALORTOTALCOMERCIAL)") - advalorem_line_usd: Optional[Decimal] = Field(None, description="Ad valorem line in USD (ADVALORMELINEAPED)") - - # Values - MC - value_mc: Optional[Decimal] = Field(None, description="Value in MC (VALORIMPOMC/VALOREXPOMC)") - sub_import_value_mc: Optional[Decimal] = Field(None, description="Sub-import value in MC (SUBVALORIMPOMC)") - vat_mc: Optional[Decimal] = Field(None, description="VAT in MC (IVAIMPOMC/IVAEXPOMC)") - value_added_mc: Optional[Decimal] = Field(None, description="Added value in MC (VALORAGREMC)") - value_national_packing_mc: Optional[Decimal] = Field(None, description="National packing value in MC (VALEMPAQUENACMC)") - value_total_mc: Optional[Decimal] = Field(None, description="Total value in MC (VALORTOTALMC)") - value_temp_material_mc: Optional[Decimal] = Field(None, description="Temporary material value in MC (VALORMPTEMPMC)") - value_def_material_mc: Optional[Decimal] = Field(None, description="Definitive material value in MC (VALORMPDEFMC)") - - -class LineFinancialCreate(LineFinancialBase): - """Schema for creating line financial""" - pass - - -class LineFinancialUpdate(LineFinancialBase): - """Schema for updating line financial""" - pass - - -class LineFinancialResponse(LineFinancialBase): - """Schema for line financial response""" - id: int - item_line_id: int - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/a76/items/line_quantities/__init__.py b/backend/api/v1/modules/a76/items/line_quantities/__init__.py deleted file mode 100644 index 8e5f8d41..00000000 --- a/backend/api/v1/modules/a76/items/line_quantities/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -"""Line quantities module""" -from .models import LineQuantity -from .schemas import ( - LineQuantityBase, - LineQuantityCreate, - LineQuantityUpdate, - LineQuantityResponse, -) - -__all__ = [ - "LineQuantity", - "LineQuantityBase", - "LineQuantityCreate", - "LineQuantityUpdate", - "LineQuantityResponse", -] diff --git a/backend/api/v1/modules/a76/items/line_quantities/models.py b/backend/api/v1/modules/a76/items/line_quantities/models.py deleted file mode 100644 index 8dbee5ec..00000000 --- a/backend/api/v1/modules/a76/items/line_quantities/models.py +++ /dev/null @@ -1,49 +0,0 @@ -from decimal import Decimal -from typing import Optional, TYPE_CHECKING -from sqlalchemy import String, Integer, Numeric, SmallInteger, ForeignKey -from sqlalchemy.orm import Mapped, mapped_column, relationship -from core.database import Base - -from api.v1.modules.a76.general_catalogs.packages.models import Package - -if TYPE_CHECKING: - from ..models import LineItem - -class LineQuantity(Base): - """ - Quantity details for line items - Consolidates all line-level data from Q and S tables - """ - __tablename__ = "item_line_quantities" - __table_args__ = { - "schema": "a76", - } - - id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) - item_line_id: Mapped[int] = mapped_column(ForeignKey("a76.item_lines.id")) - - # Quantities - quantity: Mapped[Optional[Decimal]] = mapped_column(Numeric(19, 8)) # CANTIMPO / CANTEXPO / CANTIMPODEF - alternate_quantity: Mapped[Optional[Decimal]] = mapped_column(Numeric(19, 8)) # CANTALTERNA - quantity_uma: Mapped[Optional[Decimal]] = mapped_column(Numeric(19, 8)) # CANTIMPOUMA / CANTEXPOUMA - auxiliary_quantity: Mapped[Optional[Decimal]] = mapped_column(Numeric(19, 8)) # CANTIMPOAUXILIAR / CANTEXPOAUXILIAR - - # Quantities - Special (SCAF specific) - quantity_temp_export: Mapped[Optional[Decimal]] = mapped_column(Numeric(19, 8)) # CANTEXPOTEMP - serial_count: Mapped[Optional[int]] = mapped_column(Integer) # CANT_SERIES/CANT_SERIESDEF - - # Weight - net_weight: Mapped[Optional[Decimal]] = mapped_column(Numeric(19, 8)) # PESONETO - gross_weight: Mapped[Optional[Decimal]] = mapped_column(Numeric(19, 8)) # PESOBRUTO - - - # Packaging - package_id: Mapped[Optional[int]] = mapped_column(ForeignKey("a76.packages.id")) - package_quantity: Mapped[Optional[int]] = mapped_column(Integer) # CANTBULTOS - container_quantity: Mapped[Optional[int]] = mapped_column(SmallInteger) # CANTBULCONT - container_description: Mapped[Optional[str]] = mapped_column(String(40)) # DESCCONTENEDOR - box_count: Mapped[Optional[str]] = mapped_column(String(30)) # NOCAJAS - - # Relationship (one-to-one) - line: Mapped["LineItem"] = relationship(back_populates="quantity") - package_info: Mapped[Optional["Package"]] = relationship(Package) \ No newline at end of file diff --git a/backend/api/v1/modules/a76/items/line_quantities/schemas.py b/backend/api/v1/modules/a76/items/line_quantities/schemas.py deleted file mode 100644 index 4aeed5db..00000000 --- a/backend/api/v1/modules/a76/items/line_quantities/schemas.py +++ /dev/null @@ -1,48 +0,0 @@ -from decimal import Decimal -from typing import Optional -from pydantic import BaseModel, Field, ConfigDict - -# ============================================================================ -# LINE QUANTITY SCHEMAS -# ============================================================================ - -class LineQuantityBase(BaseModel): - """Base schema for line quantities""" - # Quantities - quantity: Optional[Decimal] = Field(None, description="Main quantity (CANTIMPO/CANTEXPO/CANTIMPODEF)") - alternate_quantity: Optional[Decimal] = Field(None, description="Alternate quantity (CANTALTERNA)") - quantity_uma: Optional[Decimal] = Field(None, description="UMA quantity (CANTIMPOUMA/CANTEXPOUMA)") - auxiliary_quantity: Optional[Decimal] = Field(None, description="Auxiliary quantity (CANTIMPOAUXILIAR/CANTEXPOAUXILIAR)") - - # Quantities - Special (SCAF specific) - quantity_temp_export: Optional[Decimal] = Field(None, description="Temporary export quantity (CANTEXPOTEMP)") - serial_count: Optional[int] = Field(None, description="Serial count (CANT_SERIES/CANT_SERIESDEF)") - - # Weight - net_weight: Optional[Decimal] = Field(None, description="Net weight (PESONETO)") - gross_weight: Optional[Decimal] = Field(None, description="Gross weight (PESOBRUTO)") - - # Packaging - package_id: Optional[int] = Field(None, description="Package ID (GBultos)") - package_quantity: Optional[int] = Field(None, description="Package quantity (CANTBULTOS)") - container_quantity: Optional[int] = Field(None, description="Container quantity (CANTBULCONT)") - container_description: Optional[str] = Field(None, max_length=40, description="Container description (DESCCONTENEDOR)") - box_count: Optional[str] = Field(None, max_length=30, description="Box count (NOCAJAS)") - - -class LineQuantityCreate(LineQuantityBase): - """Schema for creating line quantity""" - pass - - -class LineQuantityUpdate(LineQuantityBase): - """Schema for updating line quantity""" - pass - - -class LineQuantityResponse(LineQuantityBase): - """Schema for line quantity response""" - id: int - item_line_id: int - - model_config = ConfigDict(from_attributes=True) \ No newline at end of file diff --git a/backend/api/v1/modules/a76/items/line_references/__init__.py b/backend/api/v1/modules/a76/items/line_references/__init__.py deleted file mode 100644 index 872e095e..00000000 --- a/backend/api/v1/modules/a76/items/line_references/__init__.py +++ /dev/null @@ -1,16 +0,0 @@ -"""Line references module""" -from .models import LineReference -from .schemas import ( - LineReferenceBase, - LineReferenceCreate, - LineReferenceUpdate, - LineReferenceResponse, -) - -__all__ = [ - "LineReference", - "LineReferenceBase", - "LineReferenceCreate", - "LineReferenceUpdate", - "LineReferenceResponse", -] diff --git a/backend/api/v1/modules/a76/items/line_references/models.py b/backend/api/v1/modules/a76/items/line_references/models.py deleted file mode 100644 index 608d0c83..00000000 --- a/backend/api/v1/modules/a76/items/line_references/models.py +++ /dev/null @@ -1,36 +0,0 @@ -from typing import Optional, TYPE_CHECKING -from sqlalchemy import Integer, ForeignKey -from sqlalchemy.orm import Mapped, mapped_column, relationship -from core.database import Base - -if TYPE_CHECKING: - from ..models import LineItem - -class LineReference(Base): - """ - Reference details for line items - Consolidates all line-level data from Q and S tables - """ - __tablename__ = "item_line_references" - __table_args__ = { - "schema": "a76", - } - - id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) - item_line_id: Mapped[int] = mapped_column(ForeignKey("a76.item_lines.id")) - - serie_id: Mapped[Optional[int]] = mapped_column(ForeignKey("a76.item_line_series.id")) # SERIEPARTIDA - - # Customer/Vendor - customer_invoice: Mapped[Optional[str]] = mapped_column(ForeignKey("a76.clients_and_providers.id")) # CLIENTEFACTURAR - assigned_client: Mapped[Optional[str]] = mapped_column(ForeignKey("a76.clients_and_providers.id")) # CLIENTEASIGNADO - supplier: Mapped[Optional[str]] = mapped_column(ForeignKey("a76.clients_and_providers.id")) # PROVEEDOR - requisitioner: Mapped[Optional[str]] = mapped_column(ForeignKey("a76.clients_and_providers.id")) # REQUISITOR - sent_to: Mapped[Optional[str]] = mapped_column(ForeignKey("a76.clients_and_providers.id")) # ENVIADOA - - # PED line reference - ped_line: Mapped[Optional[int]] = mapped_column(Integer) # LINEAPED - ro_line: Mapped[Optional[int]] = mapped_column(Integer) # LINEARO - - # Relationship (one-to-one) - line: Mapped["LineItem"] = relationship(back_populates="reference") \ No newline at end of file diff --git a/backend/api/v1/modules/a76/items/line_references/schemas.py b/backend/api/v1/modules/a76/items/line_references/schemas.py deleted file mode 100644 index 7a9677dd..00000000 --- a/backend/api/v1/modules/a76/items/line_references/schemas.py +++ /dev/null @@ -1,39 +0,0 @@ -from typing import Optional -from pydantic import BaseModel, Field, ConfigDict - -# ============================================================================ -# LINE REFERENCE SCHEMAS -# ============================================================================ - -class LineReferenceBase(BaseModel): - """Base schema for line references""" - serie_id: Optional[int] = Field(None, description="Serie ID (SERIEPARTIDA)") - - # Customer/Vendor - customer_invoice: Optional[str] = Field(None, description="Customer invoice (CLIENTEFACTURAR)") - assigned_client: Optional[str] = Field(None, description="Assigned client (CLIENTEASIGNADO)") - supplier: Optional[str] = Field(None, description="Supplier (PROVEEDOR)") - requisitioner: Optional[str] = Field(None, description="Requisitioner (REQUISITOR)") - sent_to: Optional[str] = Field(None, description="Sent to (ENVIADOA)") - - # PED line reference - ped_line: Optional[int] = Field(None, description="PED line (LINEAPED)") - ro_line: Optional[int] = Field(None, description="RO line (LINEARO)") - - -class LineReferenceCreate(LineReferenceBase): - """Schema for creating line reference""" - pass - - -class LineReferenceUpdate(LineReferenceBase): - """Schema for updating line reference""" - pass - - -class LineReferenceResponse(LineReferenceBase): - """Schema for line reference response""" - id: int - item_line_id: int - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/a76/items/models.py b/backend/api/v1/modules/a76/items/models.py deleted file mode 100644 index 0fe72550..00000000 --- a/backend/api/v1/modules/a76/items/models.py +++ /dev/null @@ -1,407 +0,0 @@ -""" -Normalized Database Schema for SCAF (Fixed Assets) and SCAII (Parts Inventory) -SQLAlchemy v2 - Annex 24 Compliance -""" - -from datetime import datetime -from typing import Optional, List, TYPE_CHECKING -from core.database import Base -from decimal import Decimal -from sqlalchemy import Boolean, Date, String, Integer, Numeric, SmallInteger, ForeignKey -from sqlalchemy.orm import Mapped, mapped_column, relationship -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base - -from api.v1.modules.a76.classes.models import Class -from api.v1.modules.a76.general_catalogs.units_of_measure.models import UnitOfMeasure - -if TYPE_CHECKING: - from .line_financials.models import LineFinancial - from .line_quantities.models import LineQuantity - from .line_customs.models import LineCustom - from .line_descriptions.models import LineDescription - from .line_references.models import LineReference - from api.v1.modules.a76.invoices.models import InvoiceHeader - - -# ============================================================================ -# CORE ENTITIES -# ============================================================================ - - -class LineItem(Base, TenantScopedMixin, TimestampMixin): - """ - Unified item header table for all import/export operations - Consolidates headers from both SCAF and SCAII systems - """ - - __tablename__ = "item_lines" - __table_args__ = { - "schema": "a76", - } - - id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) - invoice_id: Mapped[int] = mapped_column( - ForeignKey("a76.invoice_header.id") - ) # CONSECUTIVO - - line_number: Mapped[int] = mapped_column(Integer) # LINEAIMPO/LINEAEXPO/LINEA - - # Part identification - part_number_id: Mapped[Optional[int]] = mapped_column( - Integer, ForeignKey("a76.parts.id") - ) # NUMPARTE - component_part_number_id: Mapped[Optional[int]] = mapped_column( - Integer, ForeignKey("a76.parts.id") - ) # NUMPARTECOM - class_id: Mapped[Optional[int]] = mapped_column( - ForeignKey("a76.classes.id") - ) # CLASE - - # Unit of measure - unit_of_measure: Mapped[Optional[int]] = mapped_column( - ForeignKey("a76.units_of_measure.id") - ) # UNIDADMEDIDA/UNIMED - alternate_unit: Mapped[Optional[int]] = mapped_column( - ForeignKey("a76.units_of_measure.id") - ) # UNIMEDALTERNA - uma_key: Mapped[Optional[str]] = mapped_column(String(2)) # CLAVEUMA - auxiliary_unit: Mapped[Optional[str]] = mapped_column(String(5)) # UNIMEDAUXILIAR - - # Permits and certificates - permit_number: Mapped[Optional[str]] = mapped_column(String(20)) # NUMPERMISO - page_line: Mapped[Optional[str]] = mapped_column(String(10)) # PAGRENGLON - has_certificate: Mapped[Optional[bool]] = mapped_column( - Boolean - ) # TIENECO/CERTORIGEN - certificate_number: Mapped[Optional[str]] = mapped_column( - String(10) - ) # NOCERTIFICADO - octave_permit: Mapped[Optional[str]] = mapped_column(String(20)) # PERMISOROCTAVA - permits_ped: Mapped[Optional[str]] = mapped_column(String(500)) # PERMISOSPED - - # FDA - has_fda_code: Mapped[Optional[bool]] = mapped_column(Boolean) # LLEVACODFDA - fda_key: Mapped[Optional[str]] = mapped_column(String(10)) # CLAVEFDA - - # Special flags - is_military_mcia: Mapped[Optional[bool]] = mapped_column(Boolean) # ESMCIAMILITAR - - # IV32 (Tax identification) - iv32_type_key: Mapped[Optional[str]] = mapped_column(String(5)) # CLAVETIPOIV32 - iv32_number: Mapped[Optional[str]] = mapped_column(String(35)) # NUMEROIV32 - - # IN CASE OF EXPO - scrap_invoice: Mapped[Optional[str]] = mapped_column(String(15)) # FACTURASCRAP - consecutive_destination: Mapped[Optional[int]] = mapped_column( - Integer - ) # CONSECUTIVODES - ctm_section: Mapped[Optional[str]] = mapped_column(String(3)) # APARTADOCTM - - # Tax payment - tax_payment: Mapped[Optional[bool]] = mapped_column(Boolean) # PAGOIMPUESTO - payment_method: Mapped[Optional[str]] = mapped_column( - String(9) - ) # FORMAPAGO/FORMAPAGOTIGI - igi_amount: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # MONTOIGI - igi_payment_method: Mapped[Optional[str]] = mapped_column( - String(9) - ) # FORMAPAGOTIGI - - # FCC - fcc_key: Mapped[Optional[str]] = mapped_column(String(30)) # CLAVEFCC - - # Valuation method - valuation_method: Mapped[Optional[str]] = mapped_column(String(2)) # METVALOR - valuation_determined_value: Mapped[Optional[Decimal]] = mapped_column( - Numeric(29, 8) - ) # METVALORVALORDETERMINADO/METVALORACIONVALORDETERMINADO - valuation_reason: Mapped[Optional[str]] = mapped_column( - String(500) - ) # METVALORMOTIVODEUSO/METVALORACIONMOTIVODEUSO - - # Container rules - container_rule: Mapped[Optional[str]] = mapped_column(String(50)) # CONTENEDORREGLA - container_parts_ii: Mapped[Optional[str]] = mapped_column( - String(50) - ) # CONTENEDORPARTESII - - # APHIS - consecutive_aphis: Mapped[Optional[int]] = mapped_column( - Integer - ) # CONSECUTIVOAPHIS - - # BOM/Commercial - bom_version: Mapped[Optional[int]] = mapped_column(Integer) # VERSIONBOM - bill_version: Mapped[Optional[int]] = mapped_column(Integer) # VERSIONBILL - - # TLCAN value - tlcan_value: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # VALORTLCAN - - # Identifier - identifier: Mapped[Optional[str]] = mapped_column(String(2)) # IDENTIFICADOR - - # Validation fields - validation_zero: Mapped[Optional[int]] = mapped_column(Integer) # VALIDACIONZERO - validation_one: Mapped[Optional[int]] = mapped_column(Integer) # VALIDACIONUNO - - # Material type - material_type: Mapped[Optional[str]] = mapped_column( - String(50) - ) # TIPOMAT/TIPODENUMPARTE - - # Order concept - order_type: Mapped[Optional[str]] = mapped_column(String(50)) # TIPODEORDEN - line_concept: Mapped[Optional[str]] = mapped_column( - String(50) - ) # CONCEPTODELAPARTIDA - - # Review dispatch - review_dispatch: Mapped[Optional[str]] = mapped_column(String(10)) # REVISARDESP - - # Take component from PT - take_component_pt: Mapped[Optional[int]] = mapped_column(Integer) # TOMARCOMOPT - - # Pallet - pallet2: Mapped[Optional[int]] = mapped_column(SmallInteger) # PALLET2 - - # Wildcard field - wildcard_field: Mapped[Optional[str]] = mapped_column(String(100)) # CAMPOCOMODIN - - # Item references - reference_number: Mapped[Optional[str]] = mapped_column(String(20)) # NUMREFERENCIA - order: Mapped[Optional[str]] = mapped_column(String(50)) # ORDENCOMPRA / ORDENVENTA - guide_number: Mapped[Optional[str]] = mapped_column( - String(50) - ) # NUMEROGUIA/NUMERODEGUIA - - # Dates - depreciation_date: Mapped[Optional[datetime]] = mapped_column( - Date - ) # FECHADEPRECIACION - - # Administrative fields - rectification: Mapped[Optional[bool]] = mapped_column(Boolean) # RECTIFICACION - warehouse: Mapped[Optional[str]] = mapped_column(String(30)) # BODEGA - location: Mapped[Optional[str]] = mapped_column(String(200)) # LOCALIZACION - - invoice: Mapped["InvoiceHeader"] = relationship("InvoiceHeader") - - # Relationships - financial: Mapped[Optional["LineFinancial"]] = relationship( - back_populates="line", cascade="all, delete-orphan", uselist=False - ) - quantity: Mapped[Optional["LineQuantity"]] = relationship( - back_populates="line", cascade="all, delete-orphan", uselist=False - ) - customs: Mapped[Optional["LineCustom"]] = relationship( - back_populates="line", cascade="all, delete-orphan", uselist=False - ) - description: Mapped[Optional["LineDescription"]] = relationship( - back_populates="line", cascade="all, delete-orphan", uselist=False - ) - reference: Mapped[Optional["LineReference"]] = relationship( - back_populates="line", cascade="all, delete-orphan", uselist=False - ) - class_info: Mapped[Optional["Class"]] = relationship( - "api.v1.modules.a76.classes.models.Class", - foreign_keys=[class_id], - viewonly=True, - ) - unit_of_measure_info: Mapped[Optional["UnitOfMeasure"]] = relationship( - "api.v1.modules.a76.general_catalogs.units_of_measure.models.UnitOfMeasure", - foreign_keys=[unit_of_measure], - viewonly=True, - ) - fa_data: Mapped[Optional["FaLineItem"]] = relationship( - "FaLineItem", - cascade="all, delete-orphan", - uselist=False, - ) - identifiers: Mapped[List["IdentifierDetail"]] = relationship( - "IdentifierDetail", - back_populates="line", - cascade="all, delete-orphan", - ) - series: Mapped[List["Serie"]] = relationship( - "Serie", - cascade="all, delete-orphan", - ) - - part_info: Mapped[Optional["Part"]] = relationship( - "Part", - foreign_keys=[part_number_id], - viewonly=True, - ) - component_part_info: Mapped[Optional["Part"]] = relationship( - "Part", - foreign_keys=[component_part_number_id], - viewonly=True, - ) - - -# ============================================================================ -# SUPPORTING TABLES -# ============================================================================ - - -class PackingList(Base, TenantScopedMixin, TimestampMixin): - """ - Packing list items - From: SPartidasPackingList - """ - - __tablename__ = "packing_lists" - __table_args__ = { - "schema": "a76", - } - - id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) - item_line_id: Mapped[int] = mapped_column(Integer) # LINEA - packing_list_number: Mapped[Optional[str]] = mapped_column( - String(100) - ) # NUMPACKINGLIST - - -class CTMReceipt(Base, TenantScopedMixin, TimestampMixin): - """ - CTM Receipt lines (temporary manufacturing) - From: SPartidasReciboCTM - """ - - __tablename__ = "ctm_receipts" - __table_args__ = { - "schema": "a76", - } - - id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) - receipt_line: Mapped[int] = mapped_column( - ForeignKey("a76.item_lines.id") - ) # LINEARECIBO - - option: Mapped[Optional[str]] = mapped_column(String(3)) # OPCION - exit_invoice: Mapped[Optional[str]] = mapped_column(String(19)) # FACTURASALIDA - - -class SubassemblyEntry(Base, TenantScopedMixin, TimestampMixin): - """ - Subassembly/Submanufacturing Entry lines - From: SPartidasEntradaSM - """ - - __tablename__ = "subassembly_entries" - __table_args__ = { - "schema": "a76", - } - - id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) - remission_line: Mapped[int] = mapped_column(Integer) # LINEAREMISION - exit_invoice: Mapped[Optional[str]] = mapped_column(String(15)) # FACTURASALIDA - exit_line: Mapped[Optional[int]] = mapped_column(Integer) # LINEASALIDA - - -# ============================================================================ -# INDEXES AND CONSTRAINTS -# ============================================================================ - - -""" -Recommended indexes for optimal query performance: - -CREATE INDEX idx_items_consecutive ON items(consecutive); -CREATE INDEX idx_items_invoice ON items(invoice_number); -CREATE INDEX idx_items_type_system ON items(item_type, system_origin); -CREATE INDEX idx_items_dates ON items(invoice_date, depreciation_date); - -CREATE INDEX idx_lines_item ON item_lines(item_id); -CREATE INDEX idx_lines_part ON item_lines(part_number); -CREATE INDEX idx_lines_class ON item_lines(class_code); -CREATE INDEX idx_lines_invoice_refs ON item_lines(import_invoice, export_invoice); -CREATE INDEX idx_lines_fractions ON item_lines(import_fraction, export_fraction); - -CREATE INDEX idx_packing_consecutive ON packing_lists(consecutive); -CREATE INDEX idx_packing_part ON packing_lists(part_number); - -CREATE INDEX idx_repair_invoice ON repair_parts(import_invoice, import_line); -CREATE INDEX idx_ctm_ship_consec ON ctm_shipments(consecutive); -CREATE INDEX idx_ctm_rcpt_consec ON ctm_receipts(consecutive); -CREATE INDEX idx_sub_entry_consec ON subassembly_entries(consecutive); -CREATE INDEX idx_sub_exit_consec ON subassembly_exits(consecutive); -CREATE INDEX idx_imposition_consec ON imposition_parts(consecutive); -""" - - -# ============================================================================ -# MIGRATION NOTES -# ============================================================================ - -""" -MIGRATION STRATEGY FROM ORIGINAL TABLES TO NORMALIZED SCHEMA: - -1. DOCUMENT MAPPING: - - QEqeMaq (Equipment Import Temp) → items (type='EQUIPMENT_IMPORT_TEMP', system='SCAF') - - QEqeMaqRep (Equipment Repair Export) → items (type='EQUIPMENT_REPAIR_EXPORT', system='SCAF') - - QEqiDef (Equipment Import Definitive) → items (type='EQUIPMENT_IMPORT_DEF', system='SCAF') - - QEqiMaq (Equipment Machinery) → items (type='EQUIPMENT_MACHINERY', system='SCAF') - - QEqiMaqRep (Equipment Machinery Repair) → items (type='EQUIPMENT_REPAIR_IMPORT', system='SCAF') - - SPartidasCM (Common Commerce) → items (type='COMMON_COMMERCE', system='SCAII') - - SPartidasExpo (Export) → items (type='EXPORT', system='SCAII') - - SPartidasImpo (Import) → items (type='IMPORT', system='SCAII') - -2. LINE MAPPING: - All line items from Q* and SPartidas* tables map to item_lines with appropriate - field mapping based on the original column names (preserved as comments). - -3. FIELD CONSOLIDATION RULES: - - Costs: Unified under unit_cost_* with currency suffix (usd/mxn/mc) - - Values: Unified under value_* with currency suffix - - Quantities: Unified under quantity_* with specific purpose suffixes - - Descriptions: Consolidated into description_spanish/english/extra - - Fractions: All fraction fields preserved with clear naming - -4. DATA INTEGRITY: - - Original CONSECUTIVO + LINE number preserved for traceability - - Foreign key relationships established via item_id - - All original fields retained to prevent data loss - -5. SPECIAL TABLES: - - PackingList, RepairPart, CTM*, Subassembly*, ImpositionPart remain separate - as they serve specific purposes and don't fit the main item/line pattern - -6. BENEFITS: - - Eliminates redundancy across 13 original tables - - Unified query interface for all operations - - Maintains full audit trail with original field names - - Enables cross-system reporting (SCAF + SCAII) - - Simplifies maintenance with single schema - -7. QUERYING EXAMPLES: - ```python - # Get all imports (both systems) - session.query(LineItem).filter( - LineItem.item_type.in_(['IMPORT', 'EQUIPMENT_IMPORT_TEMP', 'EQUIPMENT_IMPORT_DEF']) - ) - - # Get all lines for a specific part across all items - session.query(LineItem).filter( - LineItem.part_number == 'ABC123' - ) - - # Get SCAF equipment with depreciation - session.query(LineItem).join(LineItem).filter( - LineItem.system_origin == 'SCAF', - LineItem.value_depreciated_usd.isnot(None) - ) - ``` -""" -# ============================================================================ -# RUNTIME IMPORTS FOR MAPPER RESOLUTION -# ============================================================================ -# We import these specialized models at the bottom to ensure they are registered -# in the SQLAlchemy metadata for relationship resolution while avoiding -# circular import issues in the module head. - -from api.v1.modules.a24.fa.fa_item_lines.models import FaLineItem -from api.v1.modules.a76.parts.models import Part -from api.v1.modules.a76.items.series.models import Serie -from api.v1.modules.a76.general_catalogs.identifiers.models import IdentifierDetail diff --git a/backend/api/v1/modules/a76/items/routes.py b/backend/api/v1/modules/a76/items/routes.py deleted file mode 100644 index 12d65dc3..00000000 --- a/backend/api/v1/modules/a76/items/routes.py +++ /dev/null @@ -1,299 +0,0 @@ -""" -API Endpoints for Items management -Handles CRUD operations for Item with one-to-many relationships to LineItems -""" - -import datetime -from typing import Dict, Any, List, Optional -from fastapi import APIRouter, Depends, HTTPException, Query, Path, status -from sqlalchemy.orm import Session - -from core.database import get_core_db -from core.security import get_current_user, validate_access_to_resource - -from .schemas import ( - LineItemCreate, - LineItemUpdate, - LineItemResponse, - LineItemListResponse, -) -from .service import ItemService - -router = APIRouter(prefix="/items", tags=["Items"]) - - -# ITEM CRUD ENDPOINTS - -@router.post("/", response_model=LineItemResponse, status_code=status.HTTP_201_CREATED) -async def create_item( - item_data: LineItemCreate, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """ - Create a new item with multiple line items and their nested data - - The item follows a one-to-many relationship structure: - - Each LineItem has one LineFinancial - - Each LineItem has one LineQuantity - - Each LineItem has one LineCustoms - - Each LineItem has one LineDescription - - Each LineItem has one LineReference - """ - tenant_id = validate_access_to_resource(db, company_id, current_user) - - service = ItemService() - return service.create(db, item_data, tenant_id, company_id) - - -@router.get("/{item_id}", response_model=LineItemResponse) -async def get_item( - item_id: int = Path(..., description="Item ID"), - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """ - Get a specific item by ID with all nested data - """ - tenant_id = validate_access_to_resource(db, company_id, current_user) - - service = ItemService() - item = service.get_by_id(db, item_id, tenant_id, company_id) - - if not item: - raise HTTPException(status_code=404, detail="Item not found") - - return item - - -@router.get("/", response_model=LineItemListResponse) -async def list_items( - company_id: int = Query(..., description="Company ID"), - skip: int = Query(0, ge=0, description="Number of records to skip"), - limit: int = Query(100, ge=1, le=1000, - description="Maximum records to return"), - invoice_id: Optional[int] = Query( - None, description="Filter by invoice ID"), - item_type: Optional[str] = Query(None, description="Filter by item type"), - system_origin: Optional[str] = Query( - None, description="Filter by system origin (SCAF/SCAII)"), - search: Optional[str] = Query( - None, description="Search term for invoice number, reference, order, or guide"), - sort_by: Optional[str] = Query(None, description="Column to sort by"), - sort_order: Optional[str] = Query("asc", pattern="^(asc|desc)$", description="Sort order (asc or desc)"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """ - List items with optional filtering and pagination - - Filters: - - invoice_id: Filter by specific invoice - - item_type: Filter by item type (IMPORT_TEMP, IMPORT_DEF, EXPORT, REPAIR) - - system_origin: Filter by system (SCAF, SCAII) - - search: Search across multiple fields - """ - tenant_id = validate_access_to_resource(db, company_id, current_user) - - service = ItemService() - - filters = { - "invoice_id": invoice_id, - "item_type": item_type, - "system_origin": system_origin, - "search": search, - } - - items, total = service.get_all( - db, tenant_id, company_id, skip, limit, filters, sort_by=sort_by, sort_order=sort_order - ) - - return LineItemListResponse( - total=total, - items=items, - skip=skip, - limit=limit - ) - - -@router.put("/{item_id}", response_model=LineItemResponse) -async def update_item( - item_id: int = Path(..., description="Item ID"), - item_data: LineItemUpdate = ..., - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """ - Update an item and optionally its nested line data - """ - tenant_id = validate_access_to_resource(db, company_id, current_user) - - # Verify the item exists and belongs to the tenant/company - service = ItemService() - existing_item = service.get_by_id(db, item_id, tenant_id, company_id) - if not existing_item: - raise HTTPException(status_code=404, detail="Item not found") - - return service.update(db, item_id, item_data, tenant_id, company_id) - - -@router.delete("/{item_id}", status_code=status.HTTP_204_NO_CONTENT) -async def delete_item( - item_id: int = Path(..., description="Item ID"), - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """ - Delete an item and all its related data (cascade delete) - """ - tenant_id = validate_access_to_resource(db, company_id, current_user) - - service = ItemService() - # Verify the item exists and belongs to the tenant/company - existing_item = service.get_by_id(db, item_id, tenant_id, company_id) - if not existing_item: - raise HTTPException(status_code=404, detail="Item not found") - - success = service.delete(db, item_id, tenant_id, company_id) - if not success: - raise HTTPException(status_code=404, detail="Item not found") - - return None - - -@router.delete("/{item_id}/series", status_code=status.HTTP_200_OK) -async def delete_item_series( - item_id: int = Path(..., description="Item ID"), - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """ - Delete all serial numbers for a specific item. - Equivalent to Clarion BORRAR_SERIES_EXPO. - """ - tenant_id = validate_access_to_resource(db, company_id, current_user) - - service = ItemService() - count = service.delete_item_series(db, item_id, tenant_id, company_id) - - return {"message": f"Successfully deleted {count} series", "count": count} - - -# ADDITIONAL ENDPOINTS FOR INVOICE - -@router.get("/invoice/{invoice_id}/items/", response_model=LineItemListResponse) -async def list_items_by_invoice( - invoice_id: int = Path(..., description="Invoice ID"), - company_id: int = Query(..., description="Company ID"), - skip: int = Query(0, ge=0, description="Number of records to skip"), - limit: int = Query(100, ge=1, le=1000, description="Maximum records to return"), - sort_by: Optional[str] = Query(None, description="Column to sort by"), - sort_order: Optional[str] = Query("asc", pattern="^(asc|desc)$", description="Sort order (asc or desc)"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """ - List all items for a specific invoice - """ - tenant_id = validate_access_to_resource(db, company_id, current_user) - - service = ItemService() - items, total = service.get_by_invoice( - db, invoice_id, tenant_id, company_id, skip, limit, sort_by=sort_by, sort_order=sort_order - ) - - return LineItemListResponse( - total=total, - items=items, - skip=skip, - limit=limit - ) -@router.get("/invoice/{invoice_id}/items-with-balance", response_model=List[dict]) -async def get_items_with_balance( - invoice_id: int = Path(..., description="Import Invoice ID"), - company_id: int = Query(..., description="Company ID"), - as_of_date: Optional[datetime.date] = Query( - None, - description=( - "Cut-off date for balance calculation. Only consumptions on or " - "before this date are subtracted (CALCULA_SALDO_FECHA_EXPO logic)." - ), - ), - current_export_invoice_id: Optional[int] = Query( - None, - description="ID of the current export invoice being edited to subtract its pending quantities from balance." - ), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """ - Returns every line of the given import invoice with its available balance - from the a24.balance_movement ledger. - - Each item in the response includes: - - id, line_number, part_number, class_code, unit_of_measure_code - - quantity : original imported quantity - - available_balance : net balance still available for export discharge - - has_balance : true when available_balance > 0 - - If current_export_invoice_id is provided, quantities already assigned to - this specified invoice will be subtracted from available_balance. - - Use ``as_of_date`` to restrict consumption movements to a specific date - (pass the export invoice date so that future discharges are not counted). - """ - tenant_id = validate_access_to_resource(db, company_id, current_user) - service = ItemService() - return service.get_lines_with_balance(db, invoice_id, tenant_id, company_id, as_of_date, current_export_invoice_id) - - -# STATISTICS & UTILITIES - - -@router.get("/stats/summary") -async def get_items_summary( - company_id: int = Query(..., description="Company ID"), - invoice_id: Optional[int] = Query( - None, description="Filter by invoice ID"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - """ - Get summary statistics for items - """ - tenant_id = validate_access_to_resource(db, company_id, current_user) - - filters = {"invoice_id": invoice_id} if invoice_id else None - items, total = ItemService.get_all( - db=db, - tenant_id=tenant_id, - company_id=company_id, - skip=0, - limit=10000, # Get all for stats - filters=filters - ) - - # Calculate stats - stats = { - "total_items": total, - "by_type": {}, - "by_system": {}, - } - - for item in items: - # Count by type - if item.item_type: - stats["by_type"][item.item_type] = stats["by_type"].get( - item.item_type, 0) + 1 - - # Count by system - if item.system_origin: - stats["by_system"][item.system_origin] = stats["by_system"].get( - item.system_origin, 0) + 1 - - return stats diff --git a/backend/api/v1/modules/a76/items/schemas.py b/backend/api/v1/modules/a76/items/schemas.py deleted file mode 100644 index a1383768..00000000 --- a/backend/api/v1/modules/a76/items/schemas.py +++ /dev/null @@ -1,401 +0,0 @@ -""" -Schemas for Items and related entities -Complete nested one-to-one structure: -LineItem -> LineFinancial -> LineQuantity -> LineCustoms -> LineDescription -> LineReference -""" - -from typing import Any, Optional, Union -from datetime import datetime -from decimal import Decimal -from pydantic import BaseModel, Field, ConfigDict, model_validator - -# Import schemas from individual modules -# Import nested schemas -from .line_customs.schemas import ( - LineCustomCreate, - LineCustomUpdate, - LineCustomResponse, -) -from .line_descriptions.schemas import ( - LineDescriptionCreate, - LineDescriptionUpdate, - LineDescriptionResponse, -) -from .line_quantities.schemas import ( - LineQuantityCreate, - LineQuantityUpdate, - LineQuantityResponse, -) -from .line_financials.schemas import ( - LineFinancialCreate, - LineFinancialUpdate, - LineFinancialResponse, -) -from .line_references.schemas import ( - LineReferenceCreate, - LineReferenceUpdate, - LineReferenceResponse, -) - -from api.v1.modules.a76.general_catalogs.identifiers.dto import ( - IdentifierDetailCreate, - IdentifierDetailUpdate, - IdentifierDetailResponse, -) -from api.v1.modules.a76.classes.models import Class -from api.v1.modules.a24.fa.fa_item_lines.dto import ( - FaLineItemCreateDTO, - FaLineItemUpdateDTO, - FaLineItemResponseDTO, -) -from .series.schemas import ( - SerieCreate, - SerieUpdate, - SerieResponse, -) - - -# ============================================================================ -# ITEM SCHEMAS -# ============================================================================ - - -class LineItemBase(BaseModel): - """Base schema for items""" - - model_config = ConfigDict(populate_by_name=True) - - invoice_id: int = Field(..., description="Invoice ID") - line_number: int = Field(..., description="Line number") - - # Part identification - part_number_id: Union[int, str, None] = Field( - None, - description="Part number", - alias="part_number", - serialization_alias="part_number_id", - ) - component_part_number_id: Union[int, str, None] = Field( - None, - description="Component part number", - alias="component_part_number", - serialization_alias="component_part_number_id", - ) - class_id: Optional[int] = Field(None, description="Class code") - - # Unit of measure - unit_of_measure: Optional[int] = Field(None, description="Unit of measure") - alternate_unit: Optional[int] = Field(None, description="Alternate unit") - uma_key: Optional[str] = Field(None, max_length=2, description="UMA key") - auxiliary_unit: Optional[str] = Field( - None, max_length=5, description="Auxiliary unit" - ) - - # Permits and certificates - permit_number: Optional[str] = Field( - None, max_length=20, description="Permit number" - ) - page_line: Optional[str] = Field(None, max_length=10, description="Page line") - has_certificate: Optional[bool] = Field(None, description="Has certificate") - certificate_number: Optional[str] = Field( - None, max_length=10, description="Certificate number" - ) - octave_permit: Optional[str] = Field( - None, max_length=20, description="Octave permit" - ) - permits_ped: Optional[str] = Field(None, max_length=500, description="PED permits") - - # FDA - has_fda_code: Optional[bool] = Field(None, description="Has FDA code") - fda_key: Optional[str] = Field(None, max_length=10, description="FDA key") - - # Special flags - is_military_mcia: Optional[bool] = Field( - None, description="Is military merchandise" - ) - - # IV32 - iv32_type_key: Optional[str] = Field( - None, max_length=5, description="IV32 type key" - ) - iv32_number: Optional[str] = Field(None, max_length=35, description="IV32 number") - - # Export specific - scrap_invoice: Optional[str] = Field( - None, max_length=15, description="Scrap invoice" - ) - consecutive_destination: Optional[int] = Field( - None, description="Consecutive destination" - ) - ctm_section: Optional[str] = Field(None, max_length=3, description="CTM section") - - # Tax payment - tax_payment: Optional[bool] = Field(None, description="Tax payment") - payment_method: Optional[str] = Field( - None, max_length=9, description="Payment method" - ) - igi_amount: Optional[Decimal] = Field(None, description="IGI amount") - igi_payment_method: Optional[str] = Field( - None, max_length=9, description="IGI payment method" - ) - - # FCC - fcc_key: Optional[str] = Field(None, max_length=30, description="FCC key") - - # Valuation method - valuation_method: Optional[str] = Field( - None, max_length=2, description="Valuation method" - ) - valuation_determined_value: Optional[Decimal] = Field( - None, description="Valuation determined value" - ) - valuation_reason: Optional[str] = Field( - None, max_length=500, description="Valuation reason" - ) - - # Container rules - container_rule: Optional[str] = Field( - None, max_length=50, description="Container rule" - ) - container_parts_ii: Optional[str] = Field( - None, max_length=50, description="Container parts II" - ) - - # APHIS - consecutive_aphis: Optional[int] = Field(None, description="Consecutive APHIS") - - # BOM/Commercial - bom_version: Optional[int] = Field(None, description="BOM version") - bill_version: Optional[int] = Field(None, description="Bill version") - - # TLCAN value - tlcan_value: Optional[Decimal] = Field(None, description="TLCAN value") - - # Identifier - identifier: Optional[str] = Field(None, max_length=2, description="Identifier") - - # Validation fields - validation_zero: Optional[int] = Field(None, description="Validation zero") - validation_one: Optional[int] = Field(None, description="Validation one") - - # Material type - material_type: Optional[str] = Field( - None, max_length=50, description="Material type" - ) - - # Order concept - order_type: Optional[str] = Field(None, max_length=50, description="Order type") - line_concept: Optional[str] = Field(None, max_length=50, description="Line concept") - - # Review dispatch - review_dispatch: Optional[str] = Field( - None, max_length=10, description="Review dispatch" - ) - - # Take component from PT - take_component_pt: Optional[int] = Field(None, description="Take component from PT") - - # Pallet - pallet2: Optional[int] = Field(None, description="Pallet 2") - - # Wildcard field - wildcard_field: Optional[str] = Field( - None, max_length=100, description="Wildcard field" - ) - reference_number: Optional[str] = Field( - None, max_length=20, description="Reference number" - ) - order: Optional[str] = Field(None, max_length=50, description="Order") - guide_number: Optional[str] = Field(None, max_length=50, description="Guide number") - - # Dates - depreciation_date: Optional[datetime] = Field(None, description="Depreciation date") - - # Administrative fields - rectification: Optional[int] = Field(None, description="Rectification") - warehouse: Optional[str] = Field(None, max_length=30, description="Warehouse") - location: Optional[str] = Field(None, max_length=200, description="Location") - - -class LineItemCreate(LineItemBase): - """Schema for creating item with nested lines (one-to-many)""" - - # Override base fields - estos se asignan automáticamente en el service - line_number: Optional[int] = Field(None, description="Line number (auto-assigned)") - - financial: Optional[LineFinancialCreate] = Field( - None, description="Financial data for this line" - ) - quantity: Optional[LineQuantityCreate] = Field( - None, description="Quantity data for this line" - ) - customs: Optional[LineCustomCreate] = Field( - None, description="Customs data for this line" - ) - description: Optional[LineDescriptionCreate] = Field( - None, description="Description data for this line" - ) - reference: Optional[LineReferenceCreate] = Field( - None, description="Reference data for this line" - ) - fa_data: Optional[FaLineItemCreateDTO] = Field( - None, description="Fixed Asset data for this line" - ) - series: Optional[list[SerieCreate]] = Field( - None, description="Series data for this line (multiple per line)" - ) - identifiers: Optional[list[IdentifierDetailCreate]] = Field( - None, description="Identifiers for this line" - ) - - -class LineItemUpdate(LineItemBase): - """Schema for updating item""" - - invoice_id: Optional[int] = Field(None, description="Invoice ID") - # Override base fields - todos opcionales en updates - line_number: Optional[int] = Field(None, description="Line number") - financial: Optional[LineFinancialUpdate] = Field( - None, description="Financial data for this line" - ) - quantity: Optional[LineQuantityUpdate] = Field( - None, description="Quantity data for this line" - ) - customs: Optional[LineCustomUpdate] = Field( - None, description="Customs data for this line" - ) - description: Optional[LineDescriptionUpdate] = Field( - None, description="Description data for this line" - ) - reference: Optional[LineReferenceUpdate] = Field( - None, description="Reference data for this line" - ) - fa_data: Optional[FaLineItemUpdateDTO] = Field( - None, description="Fixed Asset data for this line" - ) - series: Optional[list[SerieUpdate]] = Field( - None, description="Series data for this line (replace all)" - ) - identifiers: Optional[list[IdentifierDetailUpdate]] = Field( - None, description="Identifiers for this line" - ) - - -class LineItemResponse(LineItemBase): - """Schema for single item response""" - - id: int - invoice_id: int - line_number: int - - # Part identification - part_number_id: Optional[int] = Field( - None, alias="part_number_id_input", serialization_alias="part_number_id" - ) - part_number: Optional[str] = None - component_part_number_id: Optional[int] = Field( - None, - alias="component_part_number_id_input", - serialization_alias="component_part_number_id", - ) - component_part_number: Optional[str] = None - class_id: Optional[int] = None - - # Nested data - financial: Optional[LineFinancialResponse] = None - quantity: Optional[LineQuantityResponse] = None - customs: Optional[LineCustomResponse] = None - description: Optional[LineDescriptionResponse] = None - reference: Optional[LineReferenceResponse] = None - fa_data: Optional[FaLineItemResponseDTO] = None - series: Optional[list[SerieResponse]] = None - identifiers: Optional[list[IdentifierDetailResponse]] = None - - # Fields populated from relationships - class_code: Optional[str] = None - class_description: Optional[str] = None - unit_of_measure_code: Optional[str] = None - - # Additional fields that might be present - reference_number: Optional[str] = None - order: Optional[str] = None - warehouse: Optional[str] = None - location: Optional[str] = None - - model_config = ConfigDict(from_attributes=True, populate_by_name=True) - - @model_validator(mode="before") - @classmethod - def extract_relationship_info(cls, data: Any) -> Any: - """Extract information from joined relationships to provide flat mapping for UI.""" - if isinstance(data, dict): - # If already a dict, ensure description syncs to top-level if missing - desc = data.get("description", {}) - if isinstance(desc, dict): - if not data.get("part_description_es"): - data["part_description_es"] = desc.get("description_spanish") - if not data.get("part_description_en"): - data["part_description_en"] = desc.get("description_english") - return data - - # It's an ORM object - result = {} - - # 1. Start with model attributes (columns) - if hasattr(data, "__table__"): - for k in data.__table__.columns.keys(): - result[k] = getattr(data, k, None) - else: - # Fallback for non-table objects if any - for k, v in data.__dict__.items(): - if not k.startswith("_"): - result[k] = v - - # Alias mapping for part numbers - if hasattr(data, "part_number_id") and "part_number_id" not in result: - result["part_number_id"] = data.part_number_id - if hasattr(data, "component_part_number_id") and "component_part_number_id" not in result: - result["component_part_number_id"] = data.component_part_number_id - - # Extract part info (string part numbers) from relationship objects - if hasattr(data, "part_info") and data.part_info is not None: - result["part_number"] = getattr(data.part_info, "part_number", None) - if hasattr(data, "component_part_info") and data.component_part_info is not None: - result["component_part_number"] = getattr(data.component_part_info, "part_number", None) - - # Extract class info - if hasattr(data, "class_info") and data.class_info is not None: - result["class_code"] = getattr(data.class_info, "class_code", None) - result["class_description"] = getattr(data.class_info, "description_es", None) - - # Extract unit of measure code - if hasattr(data, "unit_of_measure_info") and data.unit_of_measure_info is not None: - result["unit_of_measure_code"] = getattr(data.unit_of_measure_info, "code", None) - - # 2. Extract nested objects and populate redundant descriptions - # We MUST use explicit getattr for relationships to ensure SQLAlchemy loads/uses joined-loaded ones - for key in ["financial", "quantity", "customs", "description", "reference", "fa_data", "series", "identifiers"]: - val = getattr(data, key, None) - if val is not None: - result[key] = val - # Sync to top-level for description redundancy (huge boost for UI stability) - if key == "description": - result["part_description_es"] = getattr(val, "description_spanish", None) - result["part_description_en"] = getattr(val, "description_english", None) - else: - # Provide default empty dict for core containers to help frontend - if key in ["financial", "quantity", "customs", "description"]: - result[key] = {} - - return result - - -class LineItemListResponse(BaseModel): - """Schema for paginated item list""" - - total: int = Field(..., description="Total number of items") - items: list[LineItemResponse] = Field(..., description="List of items") - skip: int = Field(..., description="Number of skipped items") - limit: int = Field(..., description="Maximum items per page") - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/a76/items/series/models.py b/backend/api/v1/modules/a76/items/series/models.py deleted file mode 100644 index 0e9bdf45..00000000 --- a/backend/api/v1/modules/a76/items/series/models.py +++ /dev/null @@ -1,29 +0,0 @@ -from typing import Optional -from sqlalchemy import Boolean, ForeignKey, Integer, String -from sqlalchemy.orm import Mapped, mapped_column -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base - -class Serie(Base, TenantScopedMixin, TimestampMixin): - __tablename__ = "item_line_series" - __table_args__ = { - "schema": "a76", - } - - id: Mapped[int] = mapped_column(primary_key=True, autoincrement=True) - line_item_id: Mapped[int] = mapped_column(ForeignKey("a76.item_lines.id")) # LINEAIMPO / LINEAEXPO - row: Mapped[int] = mapped_column(Integer) # RENGLON - serial_numbers: Mapped[Optional[str]] = mapped_column(String(50)) # SERIEEXPO - model: Mapped[Optional[str]] = mapped_column(String(50)) # MODELOEXPO - sub_model: Mapped[Optional[str]] = mapped_column(String(50)) # SUBMODELOEXPO - brand: Mapped[Optional[str]] = mapped_column(String(50)) # MARCA - # expo_brand: Mapped[Optional[str]] = mapped_column(String(50)) # MARCAEXPO - number_id: Mapped[Optional[str]] = mapped_column(String(25)) # NUMIDEXPO - discharge: Mapped[Optional[bool]] = mapped_column(Boolean) # MARCA - serie_row: Mapped[Optional[int]] = mapped_column(Integer) # LINEASERIEIMPO <-- IN CASE OF EXPO - # import_invoice: Mapped[Optional[str]] = mapped_column(String(15)) # FACTURAIMPO - # import_line: Mapped[Optional[int]] = mapped_column(Integer) # LINEAIMPO - image_path: Mapped[Optional[str]] = mapped_column(String(255)) # PATH DE IMAGEN (MEX) - - - \ No newline at end of file diff --git a/backend/api/v1/modules/a76/items/series/schemas.py b/backend/api/v1/modules/a76/items/series/schemas.py deleted file mode 100644 index bb7b42ee..00000000 --- a/backend/api/v1/modules/a76/items/series/schemas.py +++ /dev/null @@ -1,30 +0,0 @@ -from typing import Optional -from pydantic import BaseModel, Field, ConfigDict - - -class SerieBase(BaseModel): - row: Optional[int] = Field(None, description="Serie row number (RENGLON)") - serial_numbers: Optional[str] = Field(None, max_length=50, description="Serial number (SERIEEXPO)") - model: Optional[str] = Field(None, max_length=50, description="Model (MODELOEXPO)") - sub_model: Optional[str] = Field(None, max_length=50, description="Sub model (SUBMODELOEXPO)") - brand: Optional[str] = Field(None, max_length=50, description="Brand (MARCA)") - # expo_brand: Optional[str] = Field(None, max_length=50, description="Expo brand (MARCAEXPO)") - number_id: Optional[str] = Field(None, max_length=25, description="Number ID (NUMIDEXPO)") - # import_invoice: Optional[str] = Field(None, max_length=15, description="Import invoice (FACTURAIMPO)") - # import_line: Optional[int] = Field(None, description="Import line (LINEAIMPO)") - image_path: Optional[str] = Field(None, max_length=255, description="Image path (IMAGEPATHMEX)") - - -class SerieCreate(SerieBase): - pass - - -class SerieUpdate(SerieBase): - pass - - -class SerieResponse(SerieBase): - id: int - line_item_id: int - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/a76/items/service.py b/backend/api/v1/modules/a76/items/service.py deleted file mode 100644 index 36c5190a..00000000 --- a/backend/api/v1/modules/a76/items/service.py +++ /dev/null @@ -1,1116 +0,0 @@ -""" -Service layer for Items business logic -Handles CRUD operations for LineItem with complete one-to-one relationships: -LineItem -> LineFinancial - -> LineQuantity - -> LineCustoms - -> LineDescription - -> LineReference - -> FaLineItem (Fixed Assets - a24) - -After refactoring: LineItem is the main entity, representing a single line item in an invoice. -There is no intermediate Item entity anymore. Each LineItem belongs directly to an InvoiceHeader. -""" - -import datetime -import logging -from decimal import Decimal -from typing import Any, Optional, List, Tuple -from fastapi import HTTPException -from sqlalchemy import and_, case, func, or_, select -from sqlalchemy.exc import IntegrityError -from sqlalchemy.orm import Session, joinedload - -from api.v1.modules.a76.invoices.common.common_validators import ( - invoice_exists_by_id, - invoice_processed, -) -from core.exceptions import ErrorCollector -from .imports.validators.create import validate_create as validate_create_import -from .imports.validators.update import validate_update as validate_update_import -from .exports.validators.create import validate_create as validate_create_export -from .exports.validators.update import validate_update as validate_update_export - -from .schemas import LineItemCreate, LineItemUpdate -from .line_financials.models import LineFinancial -from .line_quantities.models import LineQuantity -from .line_customs.models import LineCustom -from .line_descriptions.models import LineDescription -from .line_references.models import LineReference -from api.v1.modules.a24.fa.fa_item_lines.models import FaLineItem -from .models import LineItem -from .series.models import Serie -from api.v1.modules.a76.invoices.models import InvoiceHeader -from api.v1.modules.a76.parts.models import Part -from api.v1.modules.a76.general_catalogs.identifiers.models import IdentifierDetail -from api.v1.modules.a24.balance_movements.models import BalanceMovement, NEGATIVE_MOVEMENTS -from api.v1.modules.a24.discharges.models import DischargeDetail, DischargeHeader, DischargeStatus - -logger = logging.getLogger(__name__) - - -class ItemService: - """ - Service for managing Items and related entities with tenant/company isolation - """ - - @staticmethod - def _resolve_part_number( - db: Session, - part_number: Optional[str], - tenant_id: int, - company_id: int, - ) -> Optional[int]: - """Try to resolve a part number string to its database ID.""" - if not part_number: - return None - - # If it's already an integer (or a string representing an integer), it might be the ID - try: - return int(part_number) - except (ValueError, TypeError): - # It's a string part number (e.g., "MAQ-001"), look it up - part = ( - db.query(Part) - .filter( - Part.part_number == part_number, - Part.tenant_id == tenant_id, - Part.company_id == company_id, - ) - .first() - ) - return part.id if part else None - - @staticmethod - def _get_next_line_number(db: Session, invoice_id: int) -> int: - """Calculate the next line_number for a given invoice based on database.""" - from sqlalchemy import func - - max_line = ( - db.query(func.max(LineItem.line_number)) - .filter(LineItem.invoice_id == invoice_id) - .scalar() - ) - - return 1 if max_line is None else max_line + 1 - - @staticmethod - def _renumber_all_invoice_lines(db: Session, invoice_id: int) -> None: - """Renumber all line_items for a given invoice to be consecutive (1, 2, 3, ...).""" - items = ( - db.query(LineItem) - .filter(LineItem.invoice_id == invoice_id) - .order_by(LineItem.line_number) - .all() - ) - - for idx, item in enumerate(items, start=1): - item.line_number = idx - - @staticmethod - def _lock_invoice( - db: Session, - invoice_id: int, - tenant_id: int, - company_id: int, - errors: ErrorCollector, - ) -> Optional[InvoiceHeader]: - """Lock invoice to prevent concurrent modifications. Returns locked invoice or adds error.""" - try: - invoice = ( - db.query(InvoiceHeader) - .filter( - InvoiceHeader.id == invoice_id, - InvoiceHeader.tenant_id == tenant_id, - InvoiceHeader.company_id == company_id, - ) - .with_for_update() - .first() - ) - - if not invoice: - errors.add_error( - field="invoice_id", - message="La factura no existe o no se pudo bloquear", - code="LOCK_FAILED", - value=str(invoice_id), - ) - return invoice - except Exception as e: - logger.error(f"Error locking invoice {invoice_id}: {e}") - errors.add_error( - field="invoice_id", - message="Error al intentar bloquear la factura", - code="LOCK_ERROR", - ) - return None - - @staticmethod - def _filter_model_data(data: dict, model_class: Any) -> dict: - """Filter a dictionary to only include keys that exist as attributes in the model class.""" - if not data: - return {} - from sqlalchemy import inspect - mapper = inspect(model_class) - valid_keys = set(mapper.columns.keys()) - return {k: v for k, v in data.items() if k in valid_keys} - - @staticmethod - def _create_line_nested_data( - db: Session, line: LineItem, line_data, tenant_id: int, company_id: int - ) -> None: - """Create all nested data for a line item.""" - nested_models = [ - (line_data.financial, LineFinancial), - (line_data.quantity, LineQuantity), - (line_data.customs, LineCustom), - (line_data.description, LineDescription), - (line_data.reference, LineReference), - ] - - for data, model_class in nested_models: - if data: - nested_dict = ( - data.model_dump(exclude_unset=True) - if hasattr(data, "model_dump") - else data.model_dump() - ) - nested_dict["item_line_id"] = line.id - # Filter dict against model attributes - filtered_dict = ItemService._filter_model_data(nested_dict, model_class) - db.add(model_class(**filtered_dict)) - - # FA data uses line.id as primary key - if line_data.fa_data: - fa_dict = line_data.fa_data.model_dump( - exclude_unset=True, exclude={"line_item_id", "includes_subitems"} - ) - fa_dict.update( - {"id": line.id, "tenant_id": tenant_id, "company_id": company_id} - ) - db.add(FaLineItem(**fa_dict)) - - # Serie data (list: multiple series per line) - if hasattr(line_data, "series") and line_data.series: - series_list = ( - line_data.series - if isinstance(line_data.series, list) - else [line_data.series] - ) - new_series = [] - for i, s in enumerate(series_list): - serie_dict = ( - s.model_dump(exclude_unset=True) - if hasattr(s, "model_dump") - else (dict(s) if isinstance(s, dict) else {}) - ) - if not serie_dict: - continue - serie_dict.update({ - "line_item_id": line.id, - "tenant_id": tenant_id, - "company_id": company_id - }) - if serie_dict.get("row") is None: - serie_dict["row"] = i + 1 - - # Filter dict against model attributes - filtered_s = ItemService._filter_model_data(serie_dict, Serie) - db.add(Serie(**filtered_s)) - - # Identifier Detail data - if hasattr(line_data, "identifiers") and line_data.identifiers: - id_list = ( - line_data.identifiers - if isinstance(line_data.identifiers, list) - else [line_data.identifiers] - ) - new_ids = [] - for d in id_list: - id_dict = ( - d.model_dump(exclude_unset=True) - if hasattr(d, "model_dump") - else (dict(d) if isinstance(d, dict) else {}) - ) - if not id_dict: - continue - id_dict.update({ - "item_line_id": line.id, - "tenant_id": tenant_id, - "company_id": company_id - }) - - # Filter dict against model attributes - filtered_id = ItemService._filter_model_data(id_dict, IdentifierDetail) - new_ids.append(IdentifierDetail(**filtered_id)) - line.identifiers = new_ids - - @staticmethod - def _attach_series(db: Session, item: LineItem) -> None: - """Query and attach all Serie rows for this item as a list.""" - series = ( - db.query(Serie) - .filter(Serie.line_item_id == item.id) - .order_by(Serie.row, Serie.id) - .all() - ) - item.series = list(series) - - @staticmethod - def _attach_identifiers(db: Session, item: LineItem) -> None: - """Query and attach all IdentifierDetail rows for this item.""" - identifiers = ( - db.query(IdentifierDetail) - .filter(IdentifierDetail.item_line_id == item.id) - .all() - ) - item.identifiers = list(identifiers) - - - @staticmethod - def get_by_id( - db: Session, item_id: int, tenant_id: int, company_id: int - ) -> Optional[LineItem]: - """Get an item by ID with tenant/company validation""" - result = ( - db.query(LineItem) - .options( - joinedload(LineItem.financial), - joinedload(LineItem.quantity), - joinedload(LineItem.customs), - joinedload(LineItem.description), - joinedload(LineItem.reference), - joinedload(LineItem.class_info), - joinedload(LineItem.unit_of_measure_info), - joinedload(LineItem.fa_data), - joinedload(LineItem.part_info), - joinedload(LineItem.component_part_info), - ) - .filter( - LineItem.id == item_id, - LineItem.tenant_id == tenant_id, - LineItem.company_id == company_id, - ) - .first() - ) - if result: - ItemService._attach_series(db, result) - ItemService._attach_identifiers(db, result) - return result - - @staticmethod - def get_all( - db: Session, - tenant_id: int, - company_id: int, - skip: int = 0, - limit: int = 100, - filters: Optional[dict] = None, - sort_by: Optional[str] = None, - sort_order: Optional[str] = "asc", - ) -> Tuple[List[LineItem], int]: - """Get all items for a tenant/company with pagination and optional filters""" - query = ( - db.query(LineItem) - .options( - joinedload(LineItem.financial), - joinedload(LineItem.quantity), - joinedload(LineItem.customs), - joinedload(LineItem.description), - joinedload(LineItem.reference), - joinedload(LineItem.class_info), - joinedload(LineItem.unit_of_measure_info), - joinedload(LineItem.fa_data), - joinedload(LineItem.part_info), - joinedload(LineItem.component_part_info), - ) - .filter( - LineItem.tenant_id == tenant_id, - LineItem.company_id == company_id, - ) - ) - - # Apply filters if provided - if filters: - if filters.get("invoice_id"): - query = query.filter(LineItem.invoice_id == filters["invoice_id"]) - if filters.get("item_type"): - query = query.filter(LineItem.item_type == filters["item_type"]) - if filters.get("system_origin"): - query = query.filter(LineItem.system_origin == filters["system_origin"]) - if filters.get("search"): - search_term = f"%{filters['search']}%" - query = query.filter( - or_( - LineItem.invoice_id.ilike(search_term), - LineItem.reference_number.ilike(search_term), - LineItem.order.ilike(search_term), - LineItem.guide_number.ilike(search_term), - ) - ) - if filters.get("invoice_number"): - # Join with InvoiceHeader to search by invoice_number - query = query.join(InvoiceHeader).filter( - InvoiceHeader.invoice_number.ilike(f"%{filters['invoice_number']}%") - ) - - # Apply sorting - if sort_by: - # Map sort_by to actual model column if possible - # Note: Some columns might require joins if they are in related models - column = getattr(LineItem, sort_by, None) - if column: - if sort_order == "desc": - query = query.order_by(column.desc()) - else: - query = query.order_by(column.asc()) - else: - # Default sorting - query = query.order_by(LineItem.line_number.asc()) - - total = query.count() - items = query.offset(skip).limit(limit).all() - for item in items: - ItemService._attach_series(db, item) - ItemService._attach_identifiers(db, item) - return items, total - - @staticmethod - def get_by_invoice( - db: Session, - invoice_id: int, - tenant_id: int, - company_id: int, - skip: int = 0, - limit: int = 100, - sort_by: Optional[str] = None, - sort_order: Optional[str] = "asc", - ) -> Tuple[List[LineItem], int]: - """Get all items for a specific invoice""" - query = ( - db.query(LineItem) - .options( - joinedload(LineItem.financial), - joinedload(LineItem.quantity), - joinedload(LineItem.customs), - joinedload(LineItem.description), - joinedload(LineItem.reference), - joinedload(LineItem.fa_data), - joinedload(LineItem.part_info), - joinedload(LineItem.component_part_info), - ) - .filter( - LineItem.invoice_id == invoice_id, - LineItem.tenant_id == tenant_id, - LineItem.company_id == company_id, - ) - ) - - # Apply sorting - if sort_by: - column = getattr(LineItem, sort_by, None) - if column: - if sort_order == "desc": - query = query.order_by(column.desc()) - else: - query = query.order_by(column.asc()) - else: - # Default sorting - query = query.order_by(LineItem.line_number.asc()) - - total = query.count() - items = query.offset(skip).limit(limit).all() - for item in items: - ItemService._attach_series(db, item) - ItemService._attach_identifiers(db, item) - return items, total - - @staticmethod - def create( - db: Session, - item_data: LineItemCreate, - tenant_id: int, - company_id: int, - ) -> LineItem: - """Create a new item with all related nested data (multiple lines)""" - - # Validaciones con ErrorCollector - errors = ErrorCollector() - - invoice = invoice_exists_by_id( - db, item_data.invoice_id, tenant_id, company_id, None - ) - - # Validar que la factura exista y no esté actualizada (si viene invoice_id) - if not item_data.invoice_id: - errors.add_required_error(field="invoice_id") - errors.raise_if_errors("Error al crear el item - invoice_id es requerido") - - # Lock invoice and calculate line number - if not ItemService._lock_invoice( - db, item_data.invoice_id, tenant_id, company_id, errors - ): - errors.raise_if_errors("Error al crear el item") - - # Calculate the next line number for this single item - line_number = ItemService._get_next_line_number(db, item_data.invoice_id) - - # Resolve part ID if a string is provided in part_number (alias for part_number_id) - if item_data.part_number_id and not isinstance(item_data.part_number_id, int): - resolved_id = ItemService._resolve_part_number( - db, str(item_data.part_number_id), tenant_id, company_id - ) - if resolved_id: - item_data.part_number_id = resolved_id - - # Resolve component part ID - if item_data.component_part_number_id and not isinstance( - item_data.component_part_number_id, int - ): - resolved_id = ItemService._resolve_part_number( - db, str(item_data.component_part_number_id), tenant_id, company_id - ) - if resolved_id: - item_data.component_part_number_id = resolved_id - - # Validar el item - if invoice.operation_type == "exp": - if invoice.invoice_type == "CR" and invoice.document_type == "AFIJO": - errors.add_error("invoice_id", "No se pueden agregar items a una factura de tipo CR con documento AFIJO", code="INVALID_INVOICE_TYPE") - - validate_create_export( - db, - item_data, # Schema Pydantic completo - tenant_id, - company_id, - errors, - line_number, - ) - else: - validate_create_import( - db, - item_data, # Schema Pydantic completo - tenant_id, - company_id, - errors, - line_number, - ) - - # Validaciones adicionales específicas del negocio - if item_data.fa_data and item_data.fa_data.is_subitem is None: - errors.add_required_error(field=f"fa_data.is_subitem") - - if item_data.fa_data and item_data.fa_data.subitem_number is None: - errors.add_required_error(field=f"fa_data.subitem_number") - - - # Si hay errores, lanzar excepción ANTES de intentar crear - errors.raise_if_errors("Error al crear el item") - - try: - # Prepare item data - item_dict = item_data.model_dump( - exclude={ - "financial", - "quantity", - "customs", - "description", - "reference", - "fa_data", - "series", - } - ) - - # Add tenant, company and line number - item_dict.update( - { - "tenant_id": tenant_id, - "company_id": company_id, - "line_number": line_number, - } - ) - - # Create the item - # Filter main item_dict against LineItem model attributes - item_dict = ItemService._filter_model_data(item_dict, LineItem) - db_item = LineItem(**item_dict) - db.add(db_item) - db.flush() # Get the item ID - - # Create nested data - ItemService._create_line_nested_data( - db, db_item, item_data, tenant_id, company_id - ) - - db.commit() - - # Eager load EVERYTHING needed for the response before returning - final_item = ( - db.query(LineItem) - .options( - joinedload(LineItem.financial), - joinedload(LineItem.quantity), - joinedload(LineItem.customs), - joinedload(LineItem.description), - joinedload(LineItem.reference), - joinedload(LineItem.class_info), - joinedload(LineItem.unit_of_measure_info), - joinedload(LineItem.fa_data), - joinedload(LineItem.part_info), - joinedload(LineItem.component_part_info), - ) - .filter(LineItem.id == db_item.id) - .first() - ) - - if final_item: - ItemService._attach_series(db, final_item) - ItemService._attach_identifiers(db, final_item) - return final_item - - except IntegrityError as e: - db.rollback() - logger.error(f"Error creating item: {e}") - raise HTTPException( - status_code=400, - detail=f"LineItem creation failed - integrity constraint violated: {e.orig}", - ) - except Exception as e: - db.rollback() - import traceback - error_msg = f"Unexpected error creating LineItem: {str(e)}" - logger.error(f"{error_msg}\n{traceback.format_exc()}") - raise HTTPException( - status_code=500, - detail=f"Error interno al crear la partida: {type(e).__name__}: {str(e)}" - ) - - @staticmethod - def update( - db: Session, - item_id: int, - item_data: LineItemUpdate, - tenant_id: int, - company_id: int, - ) -> LineItem: - """Update an item and optionally its nested data (multiple lines)""" - - # Get existing item - db_item = ItemService.get_by_id(db, item_id, tenant_id, company_id) - if not db_item: - raise HTTPException(status_code=404, detail="LineItem not found") - - # Validaciones con ErrorCollector - errors = ErrorCollector() - - invoice = invoice_exists_by_id( - db, item_data.invoice_id, tenant_id, company_id, None - ) - if not invoice: - errors.add_error("invoice_id", "La factura no existe", code="NOT_FOUND", value=str(item_data.invoice_id)) - errors.raise_if_errors("Error al encontra la factura para el item") - - # Lock invoice - invoice_id_to_lock = ( - item_data.invoice_id if item_data.invoice_id else db_item.invoice_id - ) - if not ItemService._lock_invoice( - db, invoice_id_to_lock, tenant_id, company_id, errors - ): - errors.raise_if_errors("Error al actualizar el item") - - # Resolve part ID if a string is provided in part_number (alias for part_number_id) - if hasattr(item_data, 'part_number_id') and item_data.part_number_id and not isinstance(item_data.part_number_id, int): - resolved_id = ItemService._resolve_part_number( - db, str(item_data.part_number_id), tenant_id, company_id - ) - if resolved_id: - item_data.part_number_id = resolved_id - - # Resolve component part ID - if hasattr(item_data, 'component_part_number_id') and item_data.component_part_number_id and not isinstance( - item_data.component_part_number_id, int - ): - resolved_id = ItemService._resolve_part_number( - db, str(item_data.component_part_number_id), tenant_id, company_id - ) - if resolved_id: - item_data.component_part_number_id = resolved_id - - if invoice.operation_type == "exp": - # Validar el item que se va a actualizar - validate_update_export( - db, - item_data, # Schema de update - db_item, # LineItem existente en DB - tenant_id, - company_id, - errors, - db_item.line_number, - ) - else: - # Validar el item que se va a actualizar - validate_update_import( - db, - item_data, # Schema de update - db_item, # LineItem existente en DB - tenant_id, - company_id, - errors, - db_item.line_number, - ) - - # Validar tipo de partida - if hasattr(item_data, "item_type") and item_data.item_type: - tipo_partida = item_data.item_type - if tipo_partida and tipo_partida not in ["N", "S"]: - errors.add_error( - field=f"item_type", - message=f"Tipo de partida debe ser 'N' (Normal) o 'S' (Subpartida), recibido: '{tipo_partida}'", - solution=None, - code="INVALID_ITEM_TYPE", - value=str(tipo_partida), - ) - - # Si es subpartida (S), debe tener partida principal - if tipo_partida == "S": - if not hasattr(item_data, "main_line_id") or not item_data.main_line_id: - errors.add_error( - field=f"main_line_id", - message="Las subpartidas (tipo 'S') deben tener una partida principal", - solution=None, - code="MISSING_MAIN_LINE", - ) - - # Si hay errores, lanzar excepción ANTES de actualizar - errors.raise_if_errors("Error al actualizar el item") - - try: - # Get item data excluding nested objects - item_dict = item_data.model_dump( - exclude={ - "financial", - "quantity", - "customs", - "description", - "reference", - "fa_data", - "series", - "identifiers", - }, - exclude_unset=True, - ) - - # CONDITIONAL update of nested data to prevent data loss - # Perform in-place updates for one-to-one relations, full replacement for one-to-many - - # Update item attributes - # Filter main item_dict against LineItem model attributes - item_dict = ItemService._filter_model_data(item_dict, LineItem) - for key, value in item_dict.items(): - setattr(db_item, key, value) - - # 2. Update nested one-to-one objects (In-place update) - nested_relations = [ - ('financial', LineFinancial, 'item_line_id'), - ('quantity', LineQuantity, 'item_line_id'), - ('customs', LineCustom, 'item_line_id'), - ('description', LineDescription, 'item_line_id'), - ('reference', LineReference, 'item_line_id') - ] - - for attr_name, model_class, fk_name in nested_relations: - attr_data = getattr(item_data, attr_name) - if attr_data is not None: - db_nested = getattr(db_item, attr_name) - nested_dict = attr_data.model_dump(exclude_unset=True) - if db_nested: - # Update existing - for k, v in nested_dict.items(): - setattr(db_nested, k, v) - else: - # Create new - nested_dict[fk_name] = db_item.id - new_nested = model_class(**nested_dict) - setattr(db_item, attr_name, new_nested) - db.add(new_nested) - - # 3. Handle fa_data (special case as PK is shared) - if item_data.fa_data is not None: - fa_dict = item_data.fa_data.model_dump( - exclude_unset=True, exclude={"line_item_id", "includes_subitems"} - ) - if db_item.fa_data: - for k, v in fa_dict.items(): - setattr(db_item.fa_data, k, v) - else: - fa_dict.update({ - "id": db_item.id, - "tenant_id": tenant_id, - "company_id": company_id - }) - db_item.fa_data = FaLineItem(**fa_dict) - db.add(db_item.fa_data) - - # 4. Handle one-to-many arrays (Full replacement as these are collections) - if item_data.series is not None: - # Use synchronize_session='fetch' to ensure the session knows about the deletions - db.query(Serie).filter(Serie.line_item_id == db_item.id).delete(synchronize_session='fetch') - - for s_data in item_data.series: - s_dict = s_data.model_dump(exclude_unset=True) - s_dict.update({ - "line_item_id": db_item.id, - "tenant_id": tenant_id, - "company_id": company_id - }) - if s_dict.get("row") is None: - s_dict["row"] = 1 - - # Filter dict against model attributes - filtered_s = ItemService._filter_model_data(s_dict, Serie) - db.add(Serie(**filtered_s)) - - if item_data.identifiers is not None: - db.query(IdentifierDetail).filter(IdentifierDetail.item_line_id == db_item.id).delete() - for d in item_data.identifiers: - id_dict = d.model_dump(exclude_unset=True) - id_dict.update({ - "item_line_id": db_item.id, - "tenant_id": tenant_id, - "company_id": company_id - }) - - # Filter dict against model attributes - filtered_id = ItemService._filter_model_data(id_dict, IdentifierDetail) - db.add(IdentifierDetail(**filtered_id)) - - db.flush() - - # Renumber all lines for this invoice to ensure consecutive numbering - ItemService._renumber_all_invoice_lines(db, db_item.invoice_id) - - db.commit() - db.refresh(db_item, ["financial", "quantity", "customs", "description", "reference", "fa_data", "identifiers"]) - return db_item - - except HTTPException: - raise - except Exception as e: - db.rollback() - import traceback - logger.error(f"Unexpected error updating item: {e}") - raise HTTPException(status_code=500, detail=f"Error updating item: {str(e)}") - - @staticmethod - def delete_item_series( - db: Session, - item_id: int, - tenant_id: int, - company_id: int, - current_user_name: Optional[str] = None - ) -> int: - """ - Deletes all serial numbers for a specific item. - Equivalent to Clarion BORRAR_SERIES_EXPO. - """ - # Ensure item exists and belongs to the company - item = ItemService.get_by_id(db, item_id, tenant_id, company_id) - if not item: - raise HTTPException(status_code=404, detail="Item not found") - - # Delete series - deleted_count = db.query(Serie).filter(Serie.line_item_id == item_id).delete(synchronize_session='fetch') - - # Log to bitácora (if system supports it) - # GBitacora('BORRAR TODAS LAS SERIE EXPO ', item.invoice_number) - - db.commit() - return deleted_count - - @staticmethod - def delete( - db: Session, - item_id: int, - tenant_id: int, - company_id: int, - ) -> bool: - """Delete an item and all its related data (cascade delete)""" - try: - db_item = ItemService.get_by_id(db, item_id, tenant_id, company_id) - if not db_item: - return False - - invoice_id = db_item.invoice_id - errors = ErrorCollector() - - # Lock the invoice - if not ItemService._lock_invoice( - db, invoice_id, tenant_id, company_id, errors - ): - raise HTTPException( - status_code=404, detail="Invoice not found or could not be locked" - ) - - # Manual cascade cleanup for Anexo 24 references - from api.v1.modules.a24.balance_movements.models import BalanceMovement - from api.v1.modules.a24.discharges.models import DischargeDetail - - # Check if this item is used in any discharges - if db_item.invoice and db_item.invoice.operation_type == "imp": - # Import item: check if it has been consumed - consumptions = db.query(BalanceMovement).filter( - BalanceMovement.import_item_line_id == item_id, - BalanceMovement.movement_type != "entry" - ).count() - if consumptions > 0: - raise HTTPException( - status_code=400, - detail="No se puede borrar la partida de importación porque ya ha sido descargada/consumida parcial o totalmente." - ) - # It's safe to delete its ENTRY movements - db.query(BalanceMovement).filter(BalanceMovement.import_item_line_id == item_id).delete() - else: - # Export item: delete its derived consumptions and discharge details - db.query(DischargeDetail).filter(DischargeDetail.export_item_line_id == item_id).delete() - db.query(BalanceMovement).filter(BalanceMovement.source_item_line_id == item_id).delete() - - db.delete(db_item) - db.flush() - ItemService._renumber_all_invoice_lines(db, invoice_id) - db.commit() - return True - - except Exception as e: - db.rollback() - logger.error(f"Error deleting item: {e}", exc_info=True) - raise HTTPException(status_code=500, detail=str(e)) - - @staticmethod - def get_lines_with_balance( - db: Session, - invoice_id: int, - tenant_id: int, - company_id: int, - as_of_date: Optional[datetime.date] = None, - current_export_invoice_id: Optional[int] = None, - ) -> List[dict]: - """ - Returns every line of an import invoice together with its current - available balance calculated from the a24.balance_movement ledger. - - If current_export_invoice_id is provided, it also subtracts quantities - already allocated in that export invoice to provide a "real-time" - remaining balance for the user during capture. - - Parameters - ---------- - as_of_date : optional cut-off date. - current_export_invoice_id : current export invoice being edited. - """ - lines: List[LineItem] = ( - db.query(LineItem) - .filter( - LineItem.invoice_id == invoice_id, - LineItem.tenant_id == tenant_id, - LineItem.company_id == company_id, - ) - .options( - joinedload(LineItem.quantity), - joinedload(LineItem.description), - joinedload(LineItem.part_info), - joinedload(LineItem.class_info), - joinedload(LineItem.unit_of_measure_info), - joinedload(LineItem.fa_data), - joinedload(LineItem.invoice), - ) - .order_by(LineItem.line_number) - .all() - ) - - # 1. Get official balance from ledger - result = [] - used_map = ItemService._used_quantities_by_procedure( - db=db, - import_line_ids=[line.id for line in lines], - as_of_date=as_of_date, - ) - - # 2. Get locally reserved quantities in the current export invoice (if any) - reserved_map = {} - if current_export_invoice_id and lines: - import_inv = lines[0].invoice - if import_inv: - reserved_rows = ( - db.query(FaLineItem.search_line, func.sum(LineQuantity.quantity)) - .join(LineItem, LineItem.id == FaLineItem.id) - .join(LineQuantity, LineQuantity.id == LineItem.id) - .filter( - LineItem.invoice_id == current_export_invoice_id, - FaLineItem.search_invoice == import_inv.invoice_number, - FaLineItem.movement_type_import == import_inv.invoice_type, # Match TEM/DEF - FaLineItem.discharge == True - ) - .group_by(FaLineItem.search_line) - .all() - ) - reserved_map = {int(row[0]): Decimal(str(row[1] or 0)) for row in reserved_rows} - - for line in lines: - # Official ledger balance - available_balance = ItemService._compute_balance(db, line.id, as_of_date) - - # Subtract what's already assigned in THIS invoice - reserved = reserved_map.get(line.line_number, Decimal(0)) - active_balance = available_balance - reserved - - qty = line.quantity - desc = line.description - fa = line.fa_data - inv = line.invoice - qty_used_temp = used_map.get((line.id, "TEM"), Decimal(0)) - qty_used_def = used_map.get((line.id, "DEF"), Decimal(0)) - - # Count subitems (lines that reference this line as parent via subitem_number) - subitem_count = 0 - if fa and fa.contains_subitems: - from api.v1.modules.a24.fa.fa_item_lines.models import FaLineItem as FaModel - subitem_count = ( - db.query(func.count(LineItem.id)) - .join(FaModel, FaModel.id == LineItem.id) - .filter( - LineItem.invoice_id == invoice_id, - LineItem.tenant_id == tenant_id, - FaModel.is_subitem == True, - FaModel.subitem_number == line.line_number, - ) - .scalar() or 0 - ) - - result.append({ - "id": line.id, - "line_number": line.line_number, - # Invoice info - "invoice_number": inv.invoice_number if inv else None, - "invoice_date": inv.invoice_date.isoformat() if inv and inv.invoice_date else None, - "invoice_status": inv.status if inv and inv.status else None, - # Part / class - "part_number": line.part_info.part_number if line.part_info else None, - "class_code": line.class_info.class_code if line.class_info else None, - "description_spanish": desc.description_spanish if desc else None, - "unit_of_measure_code": line.unit_of_measure_info.code if line.unit_of_measure_info else None, - # Quantities - "quantity": float(qty.quantity) if qty and qty.quantity is not None else None, - "quantity_used_temp": float(qty_used_temp), - "quantity_used_def": float(qty_used_def), - # Balance - "available_balance": float(active_balance), - "has_balance": active_balance > Decimal(0), - # Weights for proportional calculation - "net_weight": float(qty.net_weight) if qty and qty.net_weight is not None else 0.0, - "gross_weight": float(qty.gross_weight) if qty and qty.gross_weight is not None else 0.0, - # FA / subitem info - "is_subitem": fa.is_subitem if fa else None, - "contains_subitems": fa.contains_subitems if fa else None, - "subitem_count": subitem_count, - }) - return result - - @staticmethod - def get_pending_discharge_sum( - db: Session, - invoice_id: int, - search_invoice: str, - search_line: int, - movement_type_import: Optional[str] = None, - exclude_line_id: Optional[int] = None - ) -> Decimal: - """ - CUENTA_CANTIDADES_A_DESC equivalent. - Sums quantity from other lines in the same invoice targeting the same import source. - """ - query = ( - select(func.sum(LineQuantity.quantity)) - .join(LineItem, LineItem.id == LineQuantity.id) - .join(FaLineItem, FaLineItem.id == LineItem.id) - .where( - LineItem.invoice_id == invoice_id, - FaLineItem.search_invoice == search_invoice, - FaLineItem.search_line == search_line, - FaLineItem.discharge == True - ) - ) - if movement_type_import: - query = query.where(FaLineItem.movement_type_import == movement_type_import) - - if exclude_line_id: - query = query.where(LineItem.id != exclude_line_id) - - result = db.execute(query).scalar() - return Decimal(str(result or 0)) - - @staticmethod - def _compute_balance( - db: Session, - item_line_id: int, - as_of_date: Optional[datetime.date], - ) -> Decimal: - """Net available balance for one import line from the ledger.""" - sign_expr = case( - (BalanceMovement.movement_type.in_(NEGATIVE_MOVEMENTS), Decimal(-1)), - else_=Decimal(1), - ) - if as_of_date is not None: - date_filter = case( - ( - BalanceMovement.movement_type.in_(NEGATIVE_MOVEMENTS), - BalanceMovement.operation_date <= as_of_date, - ), - else_=True, - ) - else: - date_filter = True # type: ignore[assignment] - - result = db.execute( - select(func.sum(sign_expr * BalanceMovement.quantity)).where( - BalanceMovement.import_item_line_id == item_line_id, - date_filter, - ) - ).scalar() - return Decimal(str(result or 0)) - - @staticmethod - def _used_quantities_by_procedure( - db: Session, - import_line_ids: List[int], - as_of_date: Optional[datetime.date], - ) -> dict[tuple[int, str], Decimal]: - """ - Returns net used quantity by import line and procedence (TEM/DEF), - based on active discharge records only (new ledger logic). - """ - if not import_line_ids: - return {} - - query = ( - select( - DischargeDetail.import_item_line_id, - DischargeDetail.procedence, - func.sum(DischargeDetail.quantity_discharged), - ) - .join(DischargeHeader, DischargeHeader.id == DischargeDetail.discharge_header_id) - .where( - DischargeDetail.import_item_line_id.in_(import_line_ids), - DischargeHeader.status == DischargeStatus.APPLIED, - DischargeDetail.procedence.in_(["TEM", "DEF"]), - ) - .group_by(DischargeDetail.import_item_line_id, DischargeDetail.procedence) - ) - if as_of_date is not None: - query = query.where(DischargeHeader.discharge_date <= as_of_date) - - rows = db.execute(query).all() - out: dict[tuple[int, str], Decimal] = {} - for line_id, procedence, qty in rows: - out[(int(line_id), str(procedence))] = Decimal(str(qty or 0)) - return out diff --git a/backend/api/v1/modules/a76/layouts_csv/__init__.py b/backend/api/v1/modules/a76/layouts_csv/__init__.py deleted file mode 100644 index 29379c6d..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# Lógica centralizada de cargas por CSV (routes, tasks, validaciones, template_config por proceso) diff --git a/backend/api/v1/modules/a76/layouts_csv/boms/__init__.py b/backend/api/v1/modules/a76/layouts_csv/boms/__init__.py deleted file mode 100644 index 7585c5fe..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/boms/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# boms.imports diff --git a/backend/api/v1/modules/a76/layouts_csv/boms/common/__init__.py b/backend/api/v1/modules/a76/layouts_csv/boms/common/__init__.py deleted file mode 100644 index 2d458d94..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/boms/common/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# common validators, mappers, fk_loader for boms CSV import diff --git a/backend/api/v1/modules/a76/layouts_csv/boms/common/common_validators.py b/backend/api/v1/modules/a76/layouts_csv/boms/common/common_validators.py deleted file mode 100644 index 7710d9b4..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/boms/common/common_validators.py +++ /dev/null @@ -1,79 +0,0 @@ -""" -Helpers reutilizables para validación de filas CSV (BOMs). -""" -import re -from decimal import Decimal, InvalidOperation -from typing import Dict, Any, Optional - - -def check_required(row: Dict[str, Any], col: str, line_num: int) -> Optional[Dict[str, Any]]: - val = (row.get(col) or "").strip() - if not val: - return {"line": line_num, "col": col, "msg": "Requerido"} - return None - - -def check_max_length( - row: Dict[str, Any], - col: str, - max_len: int, - line_num: int, - required: bool = False, -) -> Optional[Dict[str, Any]]: - val = (row.get(col) or "").strip() - if not val: - if required: - return {"line": line_num, "col": col, "msg": "Requerido"} - return None - if len(val) > max_len: - return {"line": line_num, "col": col, "msg": f"Máximo {max_len} caracteres"} - return None - - -def check_decimal_required_min( - row: Dict[str, Any], col: str, line_num: int, min_val: Decimal -) -> Optional[Dict[str, Any]]: - val = row.get(col) - if val is None or val == "": - return {"line": line_num, "col": col, "msg": "Requerido"} - try: - v = Decimal(str(val)) - if v < min_val: - return {"line": line_num, "col": col, "msg": "Debe ser mayor o igual a cero"} - except (InvalidOperation, ValueError, TypeError): - return {"line": line_num, "col": col, "msg": "Debe ser número"} - return None - - -def _optional_number(val: Any) -> bool: - if val is None: - return True - s = re.sub(r"\s+", "", str(val).strip()) - if not s: - return True - try: - float(s.replace(",", ".")) - return True - except (ValueError, TypeError): - return False - - -def check_optional_number(row: Dict[str, Any], col: str, line_num: int) -> Optional[Dict[str, Any]]: - if not _optional_number(row.get(col)): - return {"line": line_num, "col": col, "msg": "Debe ser número"} - return None - - -def check_in_set( - row: Dict[str, Any], - col: str, - line_num: int, - allowed: Optional[set], - msg: str, -) -> Optional[Dict[str, Any]]: - val = (row.get(col) or "").strip() - if not val or allowed is None or len(allowed) == 0: - return None - if val not in allowed: - return {"line": line_num, "col": col, "msg": msg} - return None diff --git a/backend/api/v1/modules/a76/layouts_csv/boms/common/fk_loader.py b/backend/api/v1/modules/a76/layouts_csv/boms/common/fk_loader.py deleted file mode 100644 index 1ee2b81c..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/boms/common/fk_loader.py +++ /dev/null @@ -1,27 +0,0 @@ -""" -Carga de conjuntos FK para validación de import CSV de BOMs. -""" -from typing import Set - -from core.database import CoreSessionLocal - - -def load_boms_fk_sets(tenant_id: int, company_id: int) -> Set[str]: - """Carga valid_part_numbers (Part.part_number por tenant/company).""" - valid_part_numbers: Set[str] = set() - try: - with CoreSessionLocal() as session: - from api.v1.modules.a76.parts.models import Part - for p in ( - session.query(Part.part_number) - .filter( - Part.tenant_id == tenant_id, - Part.company_id == company_id, - ) - .all() - ): - valid_part_numbers.add(p[0]) - except Exception as e: - import logging - logging.getLogger(__name__).warning("BOMs import: could not load parts: %s", e) - return valid_part_numbers diff --git a/backend/api/v1/modules/a76/layouts_csv/boms/common/mappers.py b/backend/api/v1/modules/a76/layouts_csv/boms/common/mappers.py deleted file mode 100644 index 997723a8..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/boms/common/mappers.py +++ /dev/null @@ -1,49 +0,0 @@ -""" -Mapeo fila CSV → datos para BOM (para cuando exista tabla BOM). -""" -from decimal import Decimal, InvalidOperation -from typing import Dict, Any, Optional, Set - - -def _decimal_or_none(val: Any) -> Optional[Decimal]: - if val is None or val == "": - return None - try: - return Decimal(str(val).replace(",", ".")) - except (InvalidOperation, ValueError, TypeError): - return None - - -def _str_or_none(val: Any, max_len: Optional[int] = None) -> Optional[str]: - if val is None: - return None - s = str(val).strip() - if not s: - return None - if max_len and len(s) > max_len: - return s[:max_len] - return s - - -def row_to_bom_data( - row_norm: Dict[str, Any], - valid_part_numbers: Set[str], -) -> Dict[str, Any]: - """ - Mapea una fila normalizada del CSV a un diccionario de datos para BOM. - Para uso futuro cuando exista tabla BOM. - """ - parent = _str_or_none(row_norm.get("NUMPARTE_PADRE"), 70) - component = _str_or_none(row_norm.get("NUMPARTE_COMPONENTE"), 70) - quantity = _decimal_or_none(row_norm.get("CANTIDAD")) - uom = _str_or_none(row_norm.get("UNIMED"), 10) - version_bom = _decimal_or_none(row_norm.get("VERSION_BOM")) - version_bill = _decimal_or_none(row_norm.get("VERSION_BILL")) - return { - "parent_part_number": parent, - "component_part_number": component, - "quantity": quantity, - "uom": uom, - "version_bom": version_bom, - "version_bill": version_bill, - } diff --git a/backend/api/v1/modules/a76/layouts_csv/boms/csv_max_chars_for_headers.py b/backend/api/v1/modules/a76/layouts_csv/boms/csv_max_chars_for_headers.py deleted file mode 100644 index bc9ce2c4..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/boms/csv_max_chars_for_headers.py +++ /dev/null @@ -1,11 +0,0 @@ -"""Límites alineados a boms/validators/common.py y common/mappers.""" -from typing import Dict - -BOMS_CSV_MAX_CHARS: Dict[str, int] = { - "NUMPARTE_PADRE": 70, - "NUMPARTE_COMPONENTE": 70, - "CANTIDAD": 30, - "UNIMED": 10, - "VERSION_BOM": 20, - "VERSION_BILL": 20, -} diff --git a/backend/api/v1/modules/a76/layouts_csv/boms/routes.py b/backend/api/v1/modules/a76/layouts_csv/boms/routes.py deleted file mode 100644 index 3f5aa53d..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/boms/routes.py +++ /dev/null @@ -1,178 +0,0 @@ -""" -Rutas de importación CSV para BOMs. -Flujo: upload → scan → status (polling) → commit. -""" -import json -import logging -import os -from uuid import uuid4 - -from fastapi import APIRouter, File, HTTPException, Query, UploadFile, Depends -from sqlalchemy.orm import Session -from typing import Dict, Any - -from core.celery_app import celery_app -from core.database import get_core_db -from core.security import get_current_user, validate_access_to_resource -from api.v1.modules.core.tasks_tracking import track_and_dispatch - -from .schemas import ImportJobResponse -from .tasks import ( - scan_file, - insert_valid_rows, - JOB_TYPE, - BOM_IMPORT_META_PREFIX, - BOM_IMPORT_REDIS_TTL, -) -from ..common import storage as common_storage -from ..common.error_csv import download_scan_errors_csv_stream -from ..common.track_commit_dispatch import dispatch_tracked_layouts_csv_commit -from ..common.responses import normalize_commit_status_payload - -router = APIRouter() -logger = logging.getLogger(__name__) - - -def _get_redis(): - import redis - url = os.getenv("VALKEY_URL", os.getenv("REDIS_URL", "redis://valkey:6379/0")) - return redis.Redis.from_url(url, decode_responses=False) - - -@router.post("/upload", response_model=ImportJobResponse) -async def upload_import_file( - file: UploadFile = File(...), - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - try: - tenant_id = validate_access_to_resource(db, company_id, current_user, ["csv_upload.process"]) - except Exception as e: - logger.error(f"BOMs import: access validation failed: {e}") - raise HTTPException(status_code=403, detail="Invalid company access") - - if not file.filename or not file.filename.lower().endswith(".csv"): - raise HTTPException(status_code=400, detail="Solo se permiten archivos .csv") - - job_id = str(uuid4()) - contents = await file.read() - - meta_data = { - "tenant_id": tenant_id, - "company_id": company_id, - "user_id": current_user.get("id"), - "template_id": "boms", - } - - try: - common_storage.store_import_file( - JOB_TYPE, - job_id, - contents, - meta_data, - tenant_id=int(tenant_id), - company_id=company_id, - ttl=BOM_IMPORT_REDIS_TTL, - log_label="BOMs import", - ) - except common_storage.ImportStoreError as e: - logger.error(f"BOMs import: store error: {e}") - raise HTTPException(status_code=500, detail="No se pudo encolar el archivo.") - except Exception as e: - logger.error(f"BOMs import: Redis store error: {e}") - raise HTTPException(status_code=500, detail="No se pudo encolar el archivo.") - - track_and_dispatch( - db=db, - task=scan_file, - tenant_id=int(tenant_id), - company_id=company_id, - requested_by_user=current_user.get("preferred_username") or current_user.get("email") or current_user.get("sub"), - task_name="boms_scan_file", - task_group="layouts_csv", - task_origin="a76/layouts_csv/boms/upload", - args=[job_id], - task_id=job_id, - ) - - return ImportJobResponse( - job_id=job_id, - status="queued", - message="Archivo subido. Escaneo iniciado.", - ) - - -@router.get("/{job_id}/status") -async def get_import_status(job_id: str): - task_result = celery_app.AsyncResult(job_id) - - if task_result.state == "PENDING": - return {"status": "processing", "progress": 0} - if task_result.state == "PROGRESS": - info = (task_result.info or {}) - return { - "status": "processing", - "progress": info.get("current", 0), - "total": info.get("total", 0), - } - if task_result.state == "SUCCESS": - result = task_result.result - if isinstance(result, dict) and "status" in result: - return normalize_commit_status_payload(result) - return {"status": "finished", "result": result} - - result = getattr(task_result, "result", None) - if isinstance(result, dict) and result.get("status") in ("finished", "warning"): - return normalize_commit_status_payload(result) - - logger.warning("BOMs import task %s failed: state=%s", job_id, task_result.state) - err_msg = None - tb = getattr(task_result, "traceback", None) - if tb and isinstance(tb, str): - lines = [l.strip() for l in tb.strip().split("\n") if l.strip()] - if lines: - err_msg = lines[-1] - if not err_msg: - try: - exc = task_result.get(propagate=False) - if exc is not None: - err_msg = str(exc) - except Exception: - pass - if not err_msg and result is not None: - if not isinstance(result, dict): - err_msg = str(result) - elif result.get("error") or result.get("message"): - err_msg = result.get("error") or result.get("message") - return {"status": "failed", "error": err_msg or "Task failed"} - - -@router.post("/{job_id}/commit") -async def commit_import_job( - job_id: str, - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - r = _get_redis() - commit_id = dispatch_tracked_layouts_csv_commit( - db=db, - current_user=current_user, - redis_client=r, - layout_job_id=job_id, - meta_redis_key=f"{BOM_IMPORT_META_PREFIX}{job_id}", - celery_task=insert_valid_rows, - task_name="boms_insert_valid_rows", - task_origin="a76/layouts_csv/boms/commit", - args=[job_id], - ) - return { - "status": "committing", - "message": "Inserción iniciada.", - "commit_job_id": commit_id, - } - - -@router.get("/{job_id}/errors/scan-csv") -async def download_scan_errors_csv(job_id: str): - return download_scan_errors_csv_stream("bom", job_id) diff --git a/backend/api/v1/modules/a76/layouts_csv/boms/schemas.py b/backend/api/v1/modules/a76/layouts_csv/boms/schemas.py deleted file mode 100644 index 7827dae6..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/boms/schemas.py +++ /dev/null @@ -1,22 +0,0 @@ -from pydantic import BaseModel -from typing import Optional - - -class ImportJobResponse(BaseModel): - job_id: str - status: str - message: str - - -class ImportJobStatus(BaseModel): - status: str - job_id: str - total_rows: Optional[int] = 0 - error_count: Optional[int] = 0 - valid_rows: Optional[int] = 0 - error: Optional[str] = None - inserted: Optional[int] = 0 - skipped_invalid: Optional[int] = 0 - skipped_missing_fk: Optional[int] = 0 - skipped_duplicate: Optional[int] = 0 - skipped_details: Optional[list] = None diff --git a/backend/api/v1/modules/a76/layouts_csv/boms/tasks.py b/backend/api/v1/modules/a76/layouts_csv/boms/tasks.py deleted file mode 100644 index 9f249de4..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/boms/tasks.py +++ /dev/null @@ -1,175 +0,0 @@ -""" -Tareas Celery para importación CSV de BOMs. -Flujo: scan_file (validación) → insert_valid_rows (commit). -Sin tabla BOM dedicada aún: insert_valid_rows solo valida y cuenta filas válidas. -Usa layouts_csv.common y common.fk_loader, validators. -""" -import json -import logging -import os -from typing import Dict, Any, List - -from core.celery_app import celery_app - -from ..common import storage as common_storage -from ..common import normalize as common_normalize -from ..common import csv_reader as common_csv -from ..common import meta as common_meta -from ..common import responses as common_responses -from .template_config import row_from_template -from .validators import validate_row_bom -from .common.fk_loader import load_boms_fk_sets - -logger = logging.getLogger(__name__) - -JOB_TYPE = "bom" - -# Para routes.py -BOM_IMPORT_FILE_PREFIX = "bom_import_file:" -BOM_IMPORT_META_PREFIX = "bom_import_meta:" -BOM_IMPORT_ERROR_LINES_PREFIX = "bom_import_error_lines:" -BOM_IMPORT_REDIS_TTL = common_storage.IMPORT_REDIS_TTL - - -@celery_app.task(bind=True) -def scan_file(self, job_id: str, config: str = None): - logger.info("BOMs import: starting scan for job %s", job_id) - - file_path = common_storage.ensure_file_from_redis(JOB_TYPE, job_id, "BOMs import") - if not file_path: - return {"status": "failed", "error": "Archivo no encontrado (expirado o no subido). Sube de nuevo."} - common_storage.ensure_meta_from_redis(JOB_TYPE, job_id, file_path, "BOMs import") - - error_path = common_storage.error_path_for_job(JOB_TYPE, job_id) - - try: - total_rows = common_csv.count_csv_rows(file_path) - except Exception as e: - return {"status": "failed", "error": str(e)} - - try: - tenant_id, company_id = common_meta.require_tenant_context(file_path) - except ValueError as e: - return {"status": "failed", "error": str(e)} - - valid_part_numbers = load_boms_fk_sets(tenant_id, company_id) - - error_count = 0 - processed_rows = 0 - errors_detail: List[Dict[str, Any]] = [] - error_lines_list: List[int] = [] - - try: - with open(error_path, "w", encoding="utf-8") as f_err: - for i, row in common_csv.iter_csv_rows(file_path): - self.update_state( - state="PROGRESS", - meta={"current": i, "total": total_rows, "errors": error_count}, - ) - row_norm = row_from_template(row, common_normalize.normalize_header) - row_errors = validate_row_bom(row_norm, i, valid_part_numbers=valid_part_numbers) - if row_errors: - error_count += 1 - error_lines_list.append(i) - for e in row_errors: - f_err.write(json.dumps(e) + "\n") - if len(errors_detail) < 500: - for e in row_errors: - if len(errors_detail) >= 500: - break - errors_detail.append({ - "line": e.get("line", i), - "col": e.get("col", ""), - "msg": e.get("msg", ""), - "solution": e.get("solution", ""), - "warning": bool(e.get("warning", False)), - }) - processed_rows += 1 - - if error_lines_list: - common_storage.store_error_lines(JOB_TYPE, job_id, error_lines_list) - except Exception as e: - logger.error("BOMs import scan failed: %s", e) - return {"status": "failed", "error": str(e)} - - return common_responses.scan_result( - job_id, processed_rows, error_count, errors_detail - ) - - -@celery_app.task(bind=True) -def insert_valid_rows(self, job_id: str): - logger.info("BOMs import: starting commit for job %s", job_id) - - file_path = common_storage.ensure_file_from_redis(JOB_TYPE, job_id, "BOMs import") - if not file_path: - alt_path = common_storage.file_path_for_job(JOB_TYPE, job_id) - if not os.path.exists(alt_path): - return {"status": "failed", "error": "Archivo no encontrado (expirado). Sube y confirma de nuevo."} - file_path = alt_path - else: - common_storage.ensure_meta_from_redis(JOB_TYPE, job_id, file_path, "BOMs import") - - error_path = common_storage.error_path_for_job(JOB_TYPE, job_id) - error_lines = common_storage.get_error_lines(JOB_TYPE, job_id, error_path) - - try: - tenant_id, company_id = common_meta.require_tenant_context(file_path) - except ValueError as e: - return {"status": "failed", "error": str(e)} - - valid_part_numbers = load_boms_fk_sets(tenant_id, company_id) - - inserted_count = 0 - skipped_invalid = 0 - valid_count = 0 - skipped_details: List[Dict[str, Any]] = [] - response = None - meta_path = common_meta.get_meta_path(file_path) - - try: - for i, row in common_csv.iter_csv_rows(file_path): - if i in error_lines: - continue - - row_norm = row_from_template(row, common_normalize.normalize_header) - row_errors = validate_row_bom(row_norm, i, valid_part_numbers=valid_part_numbers) - if row_errors: - skipped_invalid += 1 - for err in row_errors: - skipped_details.append({ - "line": i, - "reason": f"{err.get('col', '')}: {err.get('msg', '')}", - "solution": err.get("solution", ""), - }) - continue - - valid_count += 1 - # Sin tabla BOM dedicada: no se escribe en DB; solo se cuentan filas válidas. - - response = common_responses.commit_result( - "finished", inserted_count, skipped_invalid, 0, 0, skipped_details, - ) - if valid_count > 0 and inserted_count == 0: - response["message"] = ( - f"WIP: {valid_count} filas válidas. " - "La tabla BOM aún no existe en el sistema; no se insertó nada." - ) - except Exception as e: - logger.exception("BOMs import task failed") - response = common_responses.commit_result( - "failed", 0, skipped_invalid, 0, 0, skipped_details, error=str(e), - ) - - common_storage.cleanup_import_job( - JOB_TYPE, job_id, - file_path=file_path, - error_path=error_path, - meta_path=meta_path, - ) - - if response is None: - response = common_responses.commit_result( - "failed", 0, skipped_invalid, 0, 0, skipped_details, error="Error inesperado", - ) - return response diff --git a/backend/api/v1/modules/a76/layouts_csv/boms/template_config.py b/backend/api/v1/modules/a76/layouts_csv/boms/template_config.py deleted file mode 100644 index ecf15118..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/boms/template_config.py +++ /dev/null @@ -1,61 +0,0 @@ -""" -Configuración de plantilla CSV para BOMs (EstructuraBOMS.xlsx). -Placeholders hasta tener el XLS definitivo; ajustar canónicos y aliases según el archivo. -""" - -from typing import Dict, List, Any - -from ..common.cell_value import cell_to_str -from ..common.csv_hint_inject import inject_max_char_hints_into_columns -from ..common.inject_csv_labels_en import inject_en_labels_into_template_columns -from ..common.template_locale import merge_download_header_into_lookup, merge_labels_into_lookup -from .csv_max_chars_for_headers import BOMS_CSV_MAX_CHARS - -TEMPLATE_COLUMNS: Dict[str, List[Dict[str, Any]]] = { - "boms": [ - {"canonical": "NUMPARTE_PADRE", "aliases": ["PARTE PADRE", "PART NUMBER", "PARENT PART", "NUM PARTE PADRE"]}, - {"canonical": "NUMPARTE_COMPONENTE", "aliases": ["PARTE COMPONENTE", "COMPONENT PART", "NUM PARTE COMPONENTE"]}, - {"canonical": "CANTIDAD", "aliases": ["QTY", "QUANTITY", "CANT"]}, - {"canonical": "UNIMED", "aliases": ["UNIDAD MEDIDA", "UNIT", "UOM"]}, - {"canonical": "VERSION_BOM", "aliases": ["VERSION BOM", "BOM VERSION", "VERSIONBOM"]}, - {"canonical": "VERSION_BILL", "aliases": ["VERSION BILL", "BILL VERSION", "VERSIONBILL"]}, - ], -} - -inject_en_labels_into_template_columns(TEMPLATE_COLUMNS) - -# UNIMED también en parts/catálogo ("commercial UOM"); en BOM es unidad de cantidad genérica. -_BOM_LABEL_EN_OVERRIDES = {"UNIMED": "Unit of measure"} -for col in TEMPLATE_COLUMNS.get("boms") or []: - canon = col.get("canonical") - if canon in _BOM_LABEL_EN_OVERRIDES: - col.setdefault("labels", {})["en"] = _BOM_LABEL_EN_OVERRIDES[canon] - -inject_max_char_hints_into_columns(TEMPLATE_COLUMNS.get("boms"), BOMS_CSV_MAX_CHARS) - - -def build_normalized_lookup(normalize_header_fn) -> Dict[str, str]: - cols = TEMPLATE_COLUMNS.get("boms") - if not cols: - return {} - lookup: Dict[str, str] = {} - for item in cols: - canonical = item["canonical"] - lookup[normalize_header_fn(canonical)] = canonical - for alias in item.get("aliases") or []: - lookup[normalize_header_fn(alias)] = canonical - merge_labels_into_lookup(item, canonical, normalize_header_fn, lookup) - merge_download_header_into_lookup(item, canonical, normalize_header_fn, lookup) - return lookup - - -def row_from_template(row: Dict[str, Any], normalize_header_fn) -> Dict[str, Any]: - lookup = build_normalized_lookup(normalize_header_fn) - if not lookup: - return {normalize_header_fn(k): cell_to_str(v) for k, v in row.items()} - out: Dict[str, Any] = {} - for csv_header, value in row.items(): - key_norm = normalize_header_fn(csv_header) - if key_norm in lookup: - out[lookup[key_norm]] = cell_to_str(value) - return out diff --git a/backend/api/v1/modules/a76/layouts_csv/boms/validators/__init__.py b/backend/api/v1/modules/a76/layouts_csv/boms/validators/__init__.py deleted file mode 100644 index 43a40a2d..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/boms/validators/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .create import validate_row_bom - -__all__ = ["validate_row_bom"] diff --git a/backend/api/v1/modules/a76/layouts_csv/boms/validators/common.py b/backend/api/v1/modules/a76/layouts_csv/boms/validators/common.py deleted file mode 100644 index 739690ac..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/boms/validators/common.py +++ /dev/null @@ -1,54 +0,0 @@ -""" -Validaciones comunes de fila para import CSV de BOMs. -""" -from decimal import Decimal -from typing import Dict, Any, Optional, Set - -from ..common.common_validators import ( - check_max_length, - check_decimal_required_min, - check_optional_number, - check_in_set, -) - - -def validate_row_required_parent(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]: - return check_max_length(row, "NUMPARTE_PADRE", 70, line_num, required=True) - - -def validate_row_required_component(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]: - return check_max_length(row, "NUMPARTE_COMPONENTE", 70, line_num, required=True) - - -def validate_row_quantity(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]: - return check_decimal_required_min(row, "CANTIDAD", line_num, Decimal("0")) - - -def validate_row_lengths(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]: - return check_max_length(row, "UNIMED", 10, line_num) - - -def validate_row_optional_numbers(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]: - err = check_optional_number(row, "VERSION_BOM", line_num) - if err: - return err - return check_optional_number(row, "VERSION_BILL", line_num) - - -def validate_row_fks( - row: Dict[str, Any], - line_num: int, - valid_part_numbers: Optional[Set[str]], -) -> Optional[Dict[str, Any]]: - if not valid_part_numbers or len(valid_part_numbers) == 0: - return None - err = check_in_set( - row, "NUMPARTE_PADRE", line_num, - valid_part_numbers, "Parte padre no existe en catálogo", - ) - if err: - return err - return check_in_set( - row, "NUMPARTE_COMPONENTE", line_num, - valid_part_numbers, "Parte componente no existe en catálogo", - ) diff --git a/backend/api/v1/modules/a76/layouts_csv/boms/validators/create.py b/backend/api/v1/modules/a76/layouts_csv/boms/validators/create.py deleted file mode 100644 index 7d8f96bd..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/boms/validators/create.py +++ /dev/null @@ -1,38 +0,0 @@ -""" -Punto de entrada de validación para import de una fila BOM. -""" -from typing import Dict, Any, Optional, Set, List - -from .common import ( - validate_row_required_parent, - validate_row_required_component, - validate_row_quantity, - validate_row_lengths, - validate_row_optional_numbers, - validate_row_fks, -) - - -def validate_row_bom( - row: Dict[str, Any], - line_num: int, - valid_part_numbers: Optional[Set[str]] = None, -) -> List[Dict[str, Any]]: - """ - Valida una fila de CSV de BOMs. - Encadena: requeridos (padre, componente, cantidad) → longitudes → opcionales numéricos → FKs. - """ - errors: List[Dict[str, Any]] = [] - - for err in [ - validate_row_required_parent(row, line_num), - validate_row_required_component(row, line_num), - validate_row_quantity(row, line_num), - validate_row_lengths(row, line_num), - validate_row_optional_numbers(row, line_num), - validate_row_fks(row, line_num, valid_part_numbers), - ]: - if err: - errors.append(err) - - return errors diff --git a/backend/api/v1/modules/a76/layouts_csv/cambio_regimen_regularizacion/__init__.py b/backend/api/v1/modules/a76/layouts_csv/cambio_regimen_regularizacion/__init__.py deleted file mode 100644 index df6c436e..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/cambio_regimen_regularizacion/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# layouts_csv.cambio_regimen_regularizacion — carga CSV Cambio de régimen y Regularización (encabezado y partidas) diff --git a/backend/api/v1/modules/a76/layouts_csv/cambio_regimen_regularizacion/routes.py b/backend/api/v1/modules/a76/layouts_csv/cambio_regimen_regularizacion/routes.py deleted file mode 100644 index 4f5e3043..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/cambio_regimen_regularizacion/routes.py +++ /dev/null @@ -1,191 +0,0 @@ -""" -Rutas de importación CSV para Cambio de régimen y Regularización (encabezado y partidas). -Flujo: upload → scan → status (polling) → commit. Sin validaciones ni inserción aún. -""" -import json -import logging -import os -from uuid import uuid4 - -from fastapi import APIRouter, File, HTTPException, UploadFile, Depends, Form, Query -from sqlalchemy.orm import Session -from typing import Literal, Optional, Dict, Any - -from core.celery_app import celery_app -from core.database import get_core_db -from core.security import get_current_user, validate_access_to_resource -from api.v1.modules.core.tasks_tracking import track_and_dispatch - -from ..common.track_commit_dispatch import dispatch_tracked_layouts_csv_commit -from .schemas import ImportJobResponse, CommitRequest -from .tasks import scan_file, insert_valid_rows, JOB_TYPE, CRREG_IMPORT_REDIS_TTL -from ..common import storage as common_storage -from ..common.responses import normalize_commit_status_payload -from ..common.error_csv import download_scan_errors_csv_stream - -router = APIRouter() -logger = logging.getLogger(__name__) - - -def _get_redis(): - import redis - url = os.getenv("VALKEY_URL", os.getenv("REDIS_URL", "redis://valkey:6379/0")) - return redis.Redis.from_url(url, decode_responses=False) - - -def _default_template_id(model_target: str, document_type: Optional[str]) -> str: - if document_type == "regulariz": - return "regulariz_header" if model_target == "invoice_header" else "regulariz_details" - return "cam_reg_header" if model_target == "invoice_header" else "cam_reg_details" - - -@router.post("/upload/{model_target}", response_model=ImportJobResponse) -async def upload_import_file( - model_target: Literal["invoice_header", "invoice_details"], - file: UploadFile = File(...), - footer_config: Optional[str] = Form(None), - template_id: Optional[str] = Form(None), - document_type: Optional[str] = Query(None, description="cam_reg | regulariz"), - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """Subir CSV, guardar en Redis, encolar scan. template_id/document_type distinguen Cambio de régimen vs Regularización.""" - try: - tenant_id = validate_access_to_resource(db, company_id, current_user, ["csv_upload.process"]) - except Exception as e: - logger.error("Cambio régimen/Regularización import: access validation failed: %s", e) - raise HTTPException(status_code=403, detail="Invalid company access") - - if not file.filename or not file.filename.lower().endswith(".csv"): - raise HTTPException(status_code=400, detail="Solo se permiten archivos .csv") - - job_id = str(uuid4()) - contents = await file.read() - - meta_data = { - "tenant_id": tenant_id, - "company_id": company_id, - "user_id": current_user.get("id"), - "footer_config": footer_config, - "document_type": document_type or "cam_reg", - "template_id": template_id or _default_template_id(model_target, document_type), - } - - try: - common_storage.store_import_file( - JOB_TYPE, - job_id, - contents, - meta_data, - tenant_id=int(tenant_id), - company_id=company_id, - ttl=CRREG_IMPORT_REDIS_TTL, - log_label="Cambio régimen/Regularización import", - ) - except common_storage.ImportStoreError as e: - logger.error("Cambio régimen/Regularización import: store error: %s", e) - raise HTTPException(status_code=500, detail="No se pudo encolar el archivo.") - except Exception as e: - logger.error("Cambio régimen/Regularización import: Redis store error: %s", e) - raise HTTPException(status_code=500, detail="No se pudo encolar el archivo.") - - track_and_dispatch( - db=db, - task=scan_file, - tenant_id=int(tenant_id), - company_id=company_id, - requested_by_user=current_user.get("preferred_username") - or current_user.get("email") - or current_user.get("sub"), - task_name="cambio_regimen_regularizacion_scan_file", - task_group="layouts_csv", - task_origin="a76/layouts_csv/cambio_regimen_regularizacion/upload", - args=[job_id, model_target, footer_config], - task_id=job_id, - ) - - return ImportJobResponse( - job_id=job_id, - status="queued", - message="Archivo subido. Escaneo iniciado.", - ) - - -@router.get("/{job_id}/status") -async def get_import_status(job_id: str): - """Polling: estado del escaneo o del commit.""" - task_result = celery_app.AsyncResult(job_id) - - if task_result.state == "PENDING": - return {"status": "processing", "progress": 0} - if task_result.state == "PROGRESS": - info = task_result.info or {} - return { - "status": "processing", - "progress": info.get("current", 0), - "total": info.get("total", 0), - } - if task_result.state == "SUCCESS": - result = task_result.result - if isinstance(result, dict) and "status" in result: - return normalize_commit_status_payload(result) - return {"status": "finished", "result": result} - - if isinstance(getattr(task_result, "result", None), dict) and task_result.result.get("status") in ("finished", "warning"): - return normalize_commit_status_payload(task_result.result) - - logger.warning("Cambio régimen/Regularización import task %s failed: state=%s", job_id, task_result.state) - err_msg = None - tb = getattr(task_result, "traceback", None) - if tb and isinstance(tb, str): - lines = [l.strip() for l in tb.strip().split("\n") if l.strip()] - if lines: - err_msg = lines[-1] - if not err_msg: - try: - exc = task_result.get(propagate=False) - if exc is not None: - err_msg = str(exc) - except Exception: - pass - if not err_msg: - result = getattr(task_result, "result", None) - if result is not None and not isinstance(result, dict): - err_msg = str(result) - elif isinstance(result, dict) and (result.get("error") or result.get("message")): - err_msg = result.get("error") or result.get("message") - return {"status": "failed", "error": err_msg or "Task failed"} - - -@router.post("/{job_id}/commit") -async def commit_import_job( - job_id: str, - body: CommitRequest, - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """Usuario confirma; se encola la tarea de commit (por ahora sin inserción real).""" - _, meta_key, _ = common_storage.storage_keys(JOB_TYPE, job_id) - r = _get_redis() - commit_id = dispatch_tracked_layouts_csv_commit( - db=db, - current_user=current_user, - redis_client=r, - layout_job_id=job_id, - meta_redis_key=meta_key, - celery_task=insert_valid_rows, - task_name="cambio_regimen_regularizacion_insert_valid_rows", - task_origin="a76/layouts_csv/cambio_regimen_regularizacion/commit", - args=[job_id, body.model_target], - ) - return { - "status": "committing", - "message": "Proceso de commit iniciado.", - "commit_job_id": commit_id, - } - - -@router.get("/{job_id}/errors/scan-csv") -async def download_scan_errors_csv(job_id: str): - return download_scan_errors_csv_stream(JOB_TYPE, job_id) diff --git a/backend/api/v1/modules/a76/layouts_csv/cambio_regimen_regularizacion/schemas.py b/backend/api/v1/modules/a76/layouts_csv/cambio_regimen_regularizacion/schemas.py deleted file mode 100644 index 2a043f30..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/cambio_regimen_regularizacion/schemas.py +++ /dev/null @@ -1,23 +0,0 @@ -from pydantic import BaseModel -from typing import Optional, Literal - - -class ImportJobResponse(BaseModel): - job_id: str - status: str - message: str - - -class CommitRequest(BaseModel): - model_target: Literal["invoice_header", "invoice_details"] - - -class ImportJobStatus(BaseModel): - status: str - job_id: str - total_rows: Optional[int] = 0 - error_count: Optional[int] = 0 - valid_rows: Optional[int] = 0 - error: Optional[str] = None - inserted: Optional[int] = 0 - error_file: Optional[str] = None diff --git a/backend/api/v1/modules/a76/layouts_csv/cambio_regimen_regularizacion/tasks.py b/backend/api/v1/modules/a76/layouts_csv/cambio_regimen_regularizacion/tasks.py deleted file mode 100644 index a0a6c545..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/cambio_regimen_regularizacion/tasks.py +++ /dev/null @@ -1,145 +0,0 @@ -""" -Tareas Celery para importación CSV de Cambio de régimen y Regularización (encabezado y partidas). -Flujo: scan_file (sin validaciones) → insert_valid_rows (sin inserción en BD). -Usa layouts_csv.common (storage, normalize, meta, responses, csv_reader). -Validaciones independientes por document_type (cam_reg vs regulariz) se añadirán después. -""" -import logging -import os -from typing import Dict, Any, Optional - -from core.celery_app import celery_app - -from ..common import storage as common_storage -from ..common import normalize as common_normalize -from ..common import meta as common_meta -from ..common import responses as common_responses -from ..common import csv_reader as common_csv_reader -from .template_config import row_from_template - -logger = logging.getLogger(__name__) - -JOB_TYPE = "crreg" -CRREG_IMPORT_REDIS_TTL = common_storage.IMPORT_REDIS_TTL - - -def _ensure_file(job_id: str) -> Optional[str]: - return common_storage.ensure_file_from_redis(JOB_TYPE, job_id, "Cambio régimen/Regularización import") - - -def _ensure_meta(job_id: str, file_path: str) -> bool: - return common_storage.ensure_meta_from_redis(JOB_TYPE, job_id, file_path, "Cambio régimen/Regularización import") - - -def _norm_row(row: Dict[str, Any], template_id: str) -> Dict[str, Any]: - return row_from_template(row, template_id, common_normalize.normalize_header) - - -@celery_app.task(bind=True, name="api.v1.modules.a76.layouts_csv.cambio_regimen_regularizacion.tasks.scan_file") -def scan_file(self, job_id: str, model_target: str, config: str = None): - """ - Scan CSV sin validaciones: leer, normalizar con plantilla, devolver total_rows y 0 errores. - """ - logger.info("Cambio régimen/Regularización import: starting scan for job %s target %s", job_id, model_target) - - file_path = _ensure_file(job_id) - if not file_path: - return {"status": "failed", "error": "Archivo no encontrado (expirado o no subido). Sube de nuevo."} - if os.path.getsize(file_path) == 0: - return {"status": "failed", "error": "El archivo está vacío."} - _ensure_meta(job_id, file_path) - - try: - common_meta.require_tenant_context(file_path) - except ValueError as e: - return {"status": "failed", "error": str(e)} - - meta = common_meta.load_meta(file_path) or {} - document_type = meta.get("document_type") or "cam_reg" - template_id = meta.get("template_id") or ( - "cam_reg_header" if model_target == "invoice_header" else "cam_reg_details" - ) - if document_type == "regulariz" and not meta.get("template_id"): - template_id = "regulariz_header" if model_target == "invoice_header" else "regulariz_details" - - total_rows = 0 - processed_rows = 0 - - try: - total_rows = common_csv_reader.count_csv_rows(file_path, has_header=True) - except Exception as e: - return {"status": "failed", "error": str(e)} - - def on_progress(current: int, total: int) -> None: - self.update_state(state="PROGRESS", meta={"current": current, "total": total}) - - try: - for i, row in common_csv_reader.iter_csv_rows(file_path, fieldnames=None): - on_progress(i, total_rows) - _norm_row(row, template_id) - processed_rows += 1 - except Exception as e: - logger.error("Cambio régimen/Regularización import scan failed: %s", e) - return {"status": "failed", "error": str(e)} - - return common_responses.scan_result(job_id, processed_rows, 0, []) - - -@celery_app.task(bind=True, name="api.v1.modules.a76.layouts_csv.cambio_regimen_regularizacion.tasks.insert_valid_rows") -def insert_valid_rows(self, job_id: str, model_target: str): - """ - Commit sin inserción en BD: leer CSV, omitir líneas de error (vacío por ahora), cleanup, devolver finished con inserted=0. - """ - logger.info("Cambio régimen/Regularización import: starting commit for job %s target %s", job_id, model_target) - - file_path = _ensure_file(job_id) - if not file_path: - alt_path = common_storage.file_path_for_job(JOB_TYPE, job_id) - if not os.path.exists(alt_path): - return {"status": "failed", "error": "Archivo no encontrado (expirado). Sube y confirma de nuevo."} - file_path = alt_path - else: - _ensure_meta(job_id, file_path) - - try: - common_meta.require_tenant_context(file_path) - except ValueError as e: - return {"status": "failed", "error": str(e)} - - meta = common_meta.load_meta(file_path) or {} - meta_path = common_meta.get_meta_path(file_path) - error_path = common_storage.error_path_for_job(JOB_TYPE, job_id) - error_lines = common_storage.get_error_lines(JOB_TYPE, job_id, error_path) - - document_type = meta.get("document_type") or "cam_reg" - template_id = meta.get("template_id") or ( - "cam_reg_header" if model_target == "invoice_header" else "cam_reg_details" - ) - if document_type == "regulariz" and not meta.get("template_id"): - template_id = "regulariz_header" if model_target == "invoice_header" else "regulariz_details" - - try: - for i, row in common_csv_reader.iter_csv_rows(file_path, fieldnames=None): - if i in error_lines: - continue - _norm_row(row, template_id) - except Exception as e: - logger.error("Cambio régimen/Regularización import commit read failed: %s", e) - return {"status": "failed", "error": str(e)} - - common_storage.cleanup_import_job( - JOB_TYPE, job_id, - file_path=file_path, - error_path=error_path, - meta_path=meta_path, - ) - - return { - "status": "finished", - "inserted": 0, - "skipped_invalid": 0, - "skipped_missing_fk": 0, - "skipped_duplicate": 0, - "skipped_details": [], - "message": "Proceso base listo; validaciones e inserción pendientes.", - } diff --git a/backend/api/v1/modules/a76/layouts_csv/cambio_regimen_regularizacion/template_config.py b/backend/api/v1/modules/a76/layouts_csv/cambio_regimen_regularizacion/template_config.py deleted file mode 100644 index 40a833c7..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/cambio_regimen_regularizacion/template_config.py +++ /dev/null @@ -1,146 +0,0 @@ -""" -Plantillas CSV para Cambio de régimen y Regularización (encabezado y partidas). -Por ahora misma estructura que encabezado/partidas de exportación; luego se ajustan columnas si difieren. -""" - -from typing import Dict, List, Any, Optional - -from ..common.cell_value import cell_to_str -from ..common.inject_csv_labels_en import inject_en_labels_into_template_columns -from ..common.template_locale import merge_download_header_into_lookup, merge_labels_into_lookup - -# Cambio de régimen: cam_reg_header, cam_reg_details -# Regularización: regulariz_header, regulariz_details -TEMPLATE_COLUMNS: Dict[str, List[Dict[str, Any]]] = { - "cam_reg_header": [ - {"canonical": "NUMERO FACTURA", "aliases": ["NUM FACTURA", "FACTURA", "ID"]}, - {"canonical": "FECHA FACTURA", "aliases": ["FECHA"]}, - {"canonical": "FECHA EMISION"}, - {"canonical": "CLAVE PROVEEDOR"}, - {"canonical": "CLAVE VENDIDO A"}, - {"canonical": "CLAVE ENVIADO A"}, - {"canonical": "REGIMEN", "aliases": ["CLAVEDOCUMENTO"]}, - {"canonical": "ADUANA DE CRUCE"}, - {"canonical": "CLAVE MONEDA"}, - {"canonical": "CLAVE INCOTERM"}, - {"canonical": "TIPO MONEDA"}, - {"canonical": "TIPO DE CAMBIO"}, - {"canonical": "TIPO PESO"}, - {"canonical": "TIPO TRANSPORTE"}, - {"canonical": "REMESA"}, - {"canonical": "AGENTE ADUANAL"}, - {"canonical": "FLETES"}, - {"canonical": "VALOR SEGUROS"}, - {"canonical": "SEGUROS"}, - {"canonical": "EMBALAJES"}, - {"canonical": "OTROS INCREMENTABLES"}, - {"canonical": "NUM PROYECTO", "aliases": ["NUMPROYECTO"]}, - {"canonical": "ORDEN COMPRA", "aliases": ["ORDENCOMPRA"]}, - {"canonical": "FACTURA ALTERNA"}, - {"canonical": "FACTURA EXPO REF", "aliases": ["FACTURAEXPOREF"]}, - {"canonical": "OBSERVACIONES E"}, - {"canonical": "OBSERVACIONES I"}, - {"canonical": "E DOCUMENT"}, - {"canonical": "NUM OPERACION"}, - {"canonical": "CLAVE TRANSPORTISTA"}, - {"canonical": "NOMBRE CONDUCTOR"}, - {"canonical": "NUMERO TRANSPORTE"}, - {"canonical": "PRECINTO"}, - ], - "cam_reg_details": [ - {"canonical": "NUMERO FACTURA", "aliases": ["NUM FACTURA", "FACTURA"]}, - {"canonical": "LINEA", "aliases": ["RENGLON", "PARTIDA"]}, - {"canonical": "NUMPARTE", "aliases": ["NUMERO PARTE"]}, - {"canonical": "PRECIO UNITARIO", "aliases": ["PRECIOUNITARIO"]}, - {"canonical": "VALOR COMERCIAL", "aliases": ["VALORCOMERCIAL"]}, - {"canonical": "CANTIDAD"}, - {"canonical": "CANTIDAD BULTOS", "aliases": ["CANTIDADBULTOS"]}, - {"canonical": "DESCRIPCION"}, - {"canonical": "PAIS ORIGEN", "aliases": ["PAISORIGEN"]}, - {"canonical": "FRACCION"}, - {"canonical": "ORDEN DE COMPRA", "aliases": ["ORDENCOMPRA"]}, - ], - "regulariz_header": [ - {"canonical": "NUMERO FACTURA", "aliases": ["NUM FACTURA", "FACTURA", "ID"]}, - {"canonical": "FECHA FACTURA", "aliases": ["FECHA"]}, - {"canonical": "FECHA EMISION"}, - {"canonical": "CLAVE PROVEEDOR"}, - {"canonical": "CLAVE VENDIDO A"}, - {"canonical": "CLAVE ENVIADO A"}, - {"canonical": "REGIMEN", "aliases": ["CLAVEDOCUMENTO"]}, - {"canonical": "ADUANA DE CRUCE"}, - {"canonical": "CLAVE MONEDA"}, - {"canonical": "CLAVE INCOTERM"}, - {"canonical": "TIPO MONEDA"}, - {"canonical": "TIPO DE CAMBIO"}, - {"canonical": "TIPO PESO"}, - {"canonical": "TIPO TRANSPORTE"}, - {"canonical": "REMESA"}, - {"canonical": "AGENTE ADUANAL"}, - {"canonical": "FLETES"}, - {"canonical": "VALOR SEGUROS"}, - {"canonical": "SEGUROS"}, - {"canonical": "EMBALAJES"}, - {"canonical": "OTROS INCREMENTABLES"}, - {"canonical": "NUM PROYECTO", "aliases": ["NUMPROYECTO"]}, - {"canonical": "ORDEN COMPRA", "aliases": ["ORDENCOMPRA"]}, - {"canonical": "FACTURA ALTERNA"}, - {"canonical": "FACTURA EXPO REF", "aliases": ["FACTURAEXPOREF"]}, - {"canonical": "OBSERVACIONES E"}, - {"canonical": "OBSERVACIONES I"}, - {"canonical": "E DOCUMENT"}, - {"canonical": "NUM OPERACION"}, - {"canonical": "CLAVE TRANSPORTISTA"}, - {"canonical": "NOMBRE CONDUCTOR"}, - {"canonical": "NUMERO TRANSPORTE"}, - {"canonical": "PRECINTO"}, - ], - "regulariz_details": [ - {"canonical": "NUMERO FACTURA", "aliases": ["NUM FACTURA", "FACTURA"]}, - {"canonical": "LINEA", "aliases": ["RENGLON", "PARTIDA"]}, - {"canonical": "NUMPARTE", "aliases": ["NUMERO PARTE"]}, - {"canonical": "PRECIO UNITARIO", "aliases": ["PRECIOUNITARIO"]}, - {"canonical": "VALOR COMERCIAL", "aliases": ["VALORCOMERCIAL"]}, - {"canonical": "CANTIDAD"}, - {"canonical": "CANTIDAD BULTOS", "aliases": ["CANTIDADBULTOS"]}, - {"canonical": "DESCRIPCION"}, - {"canonical": "PAIS ORIGEN", "aliases": ["PAISORIGEN"]}, - {"canonical": "FRACCION"}, - {"canonical": "ORDEN DE COMPRA", "aliases": ["ORDENCOMPRA"]}, - ], -} - -inject_en_labels_into_template_columns(TEMPLATE_COLUMNS) - - -def _resolve_template_columns(template_id: str) -> Optional[List[Dict[str, Any]]]: - return TEMPLATE_COLUMNS.get(template_id) - - -def build_normalized_lookup(template_id: str, normalize_header_fn) -> Dict[str, str]: - """normalized_header -> canonical_name.""" - cols = _resolve_template_columns(template_id) - if not cols: - return {} - lookup: Dict[str, str] = {} - for item in cols: - canonical = item["canonical"] - lookup[normalize_header_fn(canonical)] = canonical - for alias in item.get("aliases") or []: - lookup[normalize_header_fn(alias)] = canonical - merge_labels_into_lookup(item, canonical, normalize_header_fn, lookup) - merge_download_header_into_lookup(item, canonical, normalize_header_fn, lookup) - return lookup - - -def row_from_template(row: Dict[str, Any], template_id: str, normalize_header_fn) -> Dict[str, Any]: - """Fila CSV -> dict con nombres canónicos de la plantilla.""" - lookup = build_normalized_lookup(template_id, normalize_header_fn) - if not lookup: - return {normalize_header_fn(k): cell_to_str(v) for k, v in row.items()} - out: Dict[str, Any] = {} - for csv_header, value in row.items(): - key_norm = normalize_header_fn(csv_header) - if key_norm in lookup: - out[lookup[key_norm]] = cell_to_str(value) - return out diff --git a/backend/api/v1/modules/a76/layouts_csv/classes/__init__.py b/backend/api/v1/modules/a76/layouts_csv/classes/__init__.py deleted file mode 100644 index e9eb448f..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/classes/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# CSV import for Clases de Materiales (upload → scan → status → commit). diff --git a/backend/api/v1/modules/a76/layouts_csv/classes/common/__init__.py b/backend/api/v1/modules/a76/layouts_csv/classes/common/__init__.py deleted file mode 100644 index e7e4b5e4..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/classes/common/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# common validators, mappers, fk_loader for classes CSV import diff --git a/backend/api/v1/modules/a76/layouts_csv/classes/common/common_validators.py b/backend/api/v1/modules/a76/layouts_csv/classes/common/common_validators.py deleted file mode 100644 index c08ace58..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/classes/common/common_validators.py +++ /dev/null @@ -1,108 +0,0 @@ -""" -Helpers reutilizables para validación de filas CSV (clases de materiales). -""" -from typing import Dict, Any, Optional - - -def check_required(row: Dict[str, Any], col: str, line_num: int) -> Optional[Dict[str, Any]]: - val = (row.get(col) or "").strip() - if not val: - return {"line": line_num, "col": col, "msg": "Requerido"} - return None - - -def check_max_length( - row: Dict[str, Any], - col: str, - max_len: int, - line_num: int, - required: bool = False, -) -> Optional[Dict[str, Any]]: - val = (row.get(col) or "").strip() - if not val: - if required: - return {"line": line_num, "col": col, "msg": "Requerido"} - return None - if len(val) > max_len: - return {"line": line_num, "col": col, "msg": f"Máximo {max_len} caracteres"} - return None - - -def check_min_length( - row: Dict[str, Any], - col: str, - min_len: int, - line_num: int, - msg: Optional[str] = None, -) -> Optional[Dict[str, Any]]: - """Solo valida si hay valor; error si longitud menor que min_len.""" - val = (row.get(col) or "").strip() - if not val: - return None - if len(val) < min_len: - return { - "line": line_num, - "col": col, - "msg": msg or f"Mínimo {min_len} caracteres", - } - return None - - -def check_int_range( - row: Dict[str, Any], - col: str, - line_num: int, - min_val: int, - max_val: int, -) -> Optional[Dict[str, Any]]: - """Solo valida si hay valor; devuelve error si no es int o está fuera de rango.""" - val = row.get(col) - if val is None or val == "": - return None - try: - v = int(val) - if v < min_val or v > max_val: - return {"line": line_num, "col": col, "msg": "Valor fuera de rango"} - except (ValueError, TypeError): - return {"line": line_num, "col": col, "msg": "Debe ser número entero"} - return None - - -def check_in_set( - row: Dict[str, Any], - col: str, - line_num: int, - allowed: Optional[set], - msg: str = "No existe en el catálogo", -) -> Optional[Dict[str, Any]]: - """Solo valida si hay valor y allowed no es None.""" - val = (row.get(col) or "").strip() - if not val or allowed is None: - return None - if val not in allowed: - return {"line": line_num, "col": col, "msg": msg} - return None - - -def check_decimal_max( - row: Dict[str, Any], - col: str, - line_num: int, - max_val: float, - msg: Optional[str] = None, -) -> Optional[Dict[str, Any]]: - """Solo valida si hay valor; error si no es numérico o si es mayor que max_val (Clarion Col H).""" - val = row.get(col) - if val is None or val == "": - return None - try: - v = float(val) - if v > max_val: - return { - "line": line_num, - "col": col, - "msg": msg or f"El valor no puede ser mayor a {max_val}.", - } - except (ValueError, TypeError): - return {"line": line_num, "col": col, "msg": "Debe ser un número."} - return None diff --git a/backend/api/v1/modules/a76/layouts_csv/classes/common/fk_loader.py b/backend/api/v1/modules/a76/layouts_csv/classes/common/fk_loader.py deleted file mode 100644 index 3d3b401c..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/classes/common/fk_loader.py +++ /dev/null @@ -1,75 +0,0 @@ -""" -Carga de conjuntos FK para validaci?n/mapeo de import CSV de clases de materiales. -Clarion: Tipo Activo Fijo, U.M., Fracci?n Mex (GFracGenSifra + hist?rico), Fracci?n Ame (GFracAme), C?digo Producto CP (si existe). -""" -from typing import Set, Tuple - -from core.database import CoreSessionLocal - - -def load_classes_fk_sets( - tenant_id: int, - company_id: int, -) -> Tuple[Set[str], Set[str], Set[str], Set[str], Set[str]]: - """ - Carga todos los conjuntos necesarios para validaci?n CSV de clases (paridad Clarion). - Devuelve (valid_material_keys, valid_uom_codes, valid_fraction_mex_8, valid_fraction_ame, valid_product_codes_cp). - - valid_fraction_mex_8: c?digos de 8 caracteres v?lidos (TariffFraction + HistoricalTariffFraction). - - valid_fraction_ame: c?digos de fracci?n americana (USTariffFraction por tenant/company). - - valid_product_codes_cp: c?digos de producto/servicio CP (vac?o si no existe cat?logo). - """ - valid_material_keys: Set[str] = set() - valid_uom_codes: Set[str] = set() - valid_fraction_mex_8: Set[str] = set() - valid_fraction_ame: Set[str] = set() - valid_product_codes_cp: Set[str] = set() - try: - with CoreSessionLocal() as session: - from api.v1.modules.public.reference_data.material_types.models import MaterialType - from api.v1.modules.a76.general_catalogs.units_of_measure.models import UnitOfMeasure - from api.v1.modules.a76.general_catalogs.fractions.tariff_fractions.models import TariffFraction - from api.v1.modules.a76.general_catalogs.fractions.historical_tariff_fractions.models import HistoricalTariffFraction - for m in session.query(MaterialType.key).all(): - if m[0]: - valid_material_keys.add(m[0]) - for u in ( - session.query(UnitOfMeasure.code) - .filter( - UnitOfMeasure.tenant_id == tenant_id, - UnitOfMeasure.company_id == company_id, - ) - .all() - ): - if u[0]: - valid_uom_codes.add(u[0]) - - for row in session.query(TariffFraction.code).all(): - if row[0]: - code = row[0].strip() - valid_fraction_mex_8.add(code[:8]) - - for row in ( - session.query(HistoricalTariffFraction.historical_fraction) - .filter( - HistoricalTariffFraction.tenant_id == tenant_id, - HistoricalTariffFraction.company_id == company_id, - HistoricalTariffFraction.historical_fraction.isnot(None), - ) - .distinct() - .all() - ): - if row[0] and row[0].strip(): - valid_fraction_mex_8.add(row[0].strip()[:8]) - - # Fracci?n americana: validaci?n por fila contra SITAR (no se precarga el cat?logo completo). - - except Exception as e: - import logging - logging.getLogger(__name__).warning("Classes import: could not load FK sets: %s", e) - return ( - valid_material_keys, - valid_uom_codes, - valid_fraction_mex_8, - valid_fraction_ame, - valid_product_codes_cp, - ) diff --git a/backend/api/v1/modules/a76/layouts_csv/classes/common/mappers.py b/backend/api/v1/modules/a76/layouts_csv/classes/common/mappers.py deleted file mode 100644 index d7380170..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/classes/common/mappers.py +++ /dev/null @@ -1,82 +0,0 @@ -""" -Mapeo fila CSV → datos para Class (clases de materiales). -""" -from typing import Dict, Any, Optional, Set - - -def _str_or_none(val: Any, max_len: Optional[int] = None) -> Optional[str]: - if val is None: - return None - s = str(val).strip() - if not s: - return None - if max_len and len(s) > max_len: - return s[:max_len] - return s - - -def _int_or_none(val: Any) -> Optional[int]: - if val is None or val == "": - return None - try: - return int(val) - except (ValueError, TypeError): - return None - - -def row_to_class_data( - row_norm: Dict[str, Any], - valid_material_keys: Set[str], - valid_uom_codes: Set[str], -) -> Dict[str, Any]: - """ - Mapea una fila normalizada del CSV a un diccionario de datos para Class. - class_code se normaliza a mayúsculas (Clip(Upper) Clarion). - """ - raw_clase = _str_or_none(row_norm.get("CLASE"), 8) - class_code = raw_clase.upper() if raw_clase else None - desc_es = _str_or_none(row_norm.get("DESCRIPCIONE"), 500) - desc_en = _str_or_none(row_norm.get("DESCRIPCIONI"), 500) - material_key = _str_or_none(row_norm.get("CLAVEMAT"), 10) - if material_key and material_key not in valid_material_keys: - material_key = None - unit_of_measure = _str_or_none(row_norm.get("UNIMED"), 5) - if unit_of_measure and unit_of_measure not in valid_uom_codes: - unit_of_measure = None - fraction = _str_or_none(row_norm.get("FRACCION"), 20) - us_fraction = _str_or_none(row_norm.get("FRACCIONAME"), 16) - sub_key = _str_or_none(row_norm.get("CLAVESUB"), 5) - physical_review = _int_or_none(row_norm.get("REVFISICA")) - iva_exempt_fraction = _str_or_none(row_norm.get("FRACCIONEXENTAIVA"), 4) - - return { - "class_code": class_code, - "description_es": desc_es, - "description_en": desc_en, - "material_key": material_key, - "unit_of_measure": unit_of_measure, - "fraction": fraction, - "us_fraction": us_fraction, - "sub_key": sub_key, - "physical_review": physical_review, - "iva_exempt_fraction": iva_exempt_fraction, - } - - -def row_to_class_data_merge_existing( - row_norm: Dict[str, Any], - existing_data: Dict[str, Any], - valid_material_keys: Set[str], - valid_uom_codes: Set[str], -) -> Dict[str, Any]: - """ - Para modo actualizar (parcial): valores del CSV si no vacíos, sino los de la clase existente (Clarion VALIDA_PARCIAL_CLASE). - """ - data = row_to_class_data(row_norm, valid_material_keys, valid_uom_codes) - if not data["class_code"]: - return data - for key in ("description_es", "description_en", "material_key", "unit_of_measure", - "fraction", "us_fraction", "sub_key", "physical_review", "iva_exempt_fraction"): - if data.get(key) is None or (isinstance(data[key], str) and not data[key].strip()): - data[key] = existing_data.get(key) - return data diff --git a/backend/api/v1/modules/a76/layouts_csv/classes/csv_max_chars_for_headers.py b/backend/api/v1/modules/a76/layouts_csv/classes/csv_max_chars_for_headers.py deleted file mode 100644 index c5521949..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/classes/csv_max_chars_for_headers.py +++ /dev/null @@ -1,16 +0,0 @@ -"""Máximos alineados a classes/validators/common.py.""" -from typing import Dict - -MATERIAL_CLASSES_CSV_MAX_CHARS: Dict[str, int] = { - "CLASE": 30, - "DESCRIPCIONE": 500, - "DESCRIPCIONI": 500, - "CLAVEMAT": 10, - "UNIMED": 5, - "FRACCION": 20, - "FRACCIONAME": 16, - "TASADEPRECIA": 24, - "CLAVESUB": 5, - "REVFISICA": 10, - "FRACCIONEXENTAIVA": 4, -} diff --git a/backend/api/v1/modules/a76/layouts_csv/classes/routes.py b/backend/api/v1/modules/a76/layouts_csv/classes/routes.py deleted file mode 100644 index 8d747921..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/classes/routes.py +++ /dev/null @@ -1,260 +0,0 @@ -""" -Rutas de importación CSV para Clases de Materiales. -Flujo: upload → scan → status (polling) → commit. -""" -import json -import logging -import os -from uuid import uuid4 - -from fastapi import APIRouter, File, HTTPException, Query, UploadFile, Depends -from sqlalchemy.orm import Session -from typing import Dict, Any, Optional - -from core.celery_app import celery_app -from core.database import get_core_db -from core.security import get_current_user, validate_access_to_resource -from api.v1.modules.core.tasks_tracking import track_and_dispatch - -from ..common.track_commit_dispatch import dispatch_tracked_layouts_csv_commit -from ..common.responses import normalize_commit_status_payload -from .schemas import ImportJobResponse -from .tasks import ( - scan_file, - insert_valid_rows, - JOB_TYPE, - CLS_IMPORT_META_PREFIX, - CLS_IMPORT_REDIS_TTL, -) -from ..common import storage as common_storage -from ..common.error_csv import download_scan_errors_csv_stream - -router = APIRouter() -logger = logging.getLogger(__name__) - - -def _get_redis(): - import redis - url = os.getenv("VALKEY_URL", os.getenv("REDIS_URL", "redis://valkey:6379/0")) - return redis.Redis.from_url(url, decode_responses=False) - - -def _assert_classes_csv_job_access( - db: Session, - job_id: str, - current_user: Dict[str, Any], -) -> None: - from api.v1.modules.core.tasks_tracking.models import TaskRun - - company_id: Optional[int] = None - row = db.query(TaskRun).filter(TaskRun.task_id == job_id).first() - if row is not None and row.company_id is not None: - company_id = int(row.company_id) - - r = _get_redis() - if company_id is None: - raw = r.get(f"{CLS_IMPORT_META_PREFIX}{job_id}") - if raw: - meta = json.loads(raw.decode("utf-8")) - cid = meta.get("company_id") - if cid is not None: - company_id = int(cid) - - if company_id is None and row is not None and row.meta_payload: - layout_jid = row.meta_payload.get("layout_import_job_id") - if layout_jid: - raw2 = r.get(f"{CLS_IMPORT_META_PREFIX}{layout_jid}") - if raw2: - meta2 = json.loads(raw2.decode("utf-8")) - cid2 = meta2.get("company_id") - if cid2 is not None: - company_id = int(cid2) - - if company_id is None: - raise HTTPException(status_code=403, detail="Sin acceso a este job") - try: - validate_access_to_resource( - db, company_id, current_user, ["csv_upload.process"] - ) - except HTTPException: - raise - except Exception as e: - logger.error("Classes import: job access validation failed: %s", e) - raise HTTPException(status_code=403, detail="Sin acceso a este job") from None - - -@router.post("/upload", response_model=ImportJobResponse) -async def upload_import_file( - file: UploadFile = File(...), - company_id: int = Query(..., description="Company ID"), - actualizar: bool = Query(False, description="Modo actualizar (ACT): validación parcial si la clase existe"), - siempre_toda: bool = Query(False, description="Forzar siempre validación completa"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - try: - tenant_id = validate_access_to_resource(db, company_id, current_user, ["csv_upload.process"]) - except Exception as e: - logger.error(f"Classes import: access validation failed: {e}") - raise HTTPException(status_code=403, detail="Invalid company access") - - if not file.filename or not file.filename.lower().endswith(".csv"): - raise HTTPException(status_code=400, detail="Solo se permiten archivos .csv") - - job_id = str(uuid4()) - contents = await file.read() - - meta_data = { - "tenant_id": tenant_id, - "company_id": company_id, - "user_id": current_user.get("id"), - "template_id": "material_classes", - "actualizar": actualizar, - "siempre_toda": siempre_toda, - } - - try: - common_storage.store_import_file( - JOB_TYPE, - job_id, - contents, - meta_data, - tenant_id=int(tenant_id), - company_id=company_id, - ttl=CLS_IMPORT_REDIS_TTL, - log_label="Classes import", - ) - except common_storage.ImportStoreError as e: - logger.error(f"Classes import: store error: {e}") - raise HTTPException(status_code=500, detail="No se pudo encolar el archivo.") - except Exception as e: - logger.error(f"Classes import: Redis store error: {e}") - raise HTTPException(status_code=500, detail="No se pudo encolar el archivo.") - - track_and_dispatch( - db=db, - task=scan_file, - tenant_id=int(tenant_id), - company_id=company_id, - requested_by_user=current_user.get("preferred_username") - or current_user.get("email") - or current_user.get("sub"), - task_name="classes_scan_file", - task_group="layouts_csv", - task_origin="a76/layouts_csv/classes/upload", - args=[job_id], - task_id=job_id, - ) - - return ImportJobResponse( - job_id=job_id, - status="queued", - message="Archivo subido. Escaneo iniciado.", - ) - - -@router.get("/{job_id}/status") -async def get_import_status( - job_id: str, - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - _assert_classes_csv_job_access(db, job_id, current_user) - task_result = celery_app.AsyncResult(job_id) - - if task_result.state == "PENDING": - return {"status": "processing", "progress": 0} - if task_result.state == "PROGRESS": - info = (task_result.info or {}) - return { - "status": "processing", - "progress": info.get("current", 0), - "total": info.get("total", 0), - } - if task_result.state == "SUCCESS": - result = task_result.result - if isinstance(result, dict) and "status" in result: - return normalize_commit_status_payload(result) - return {"status": "finished", "result": result} - - result = getattr(task_result, "result", None) - if isinstance(result, dict) and result.get("status") in ("finished", "warning"): - return normalize_commit_status_payload(result) - - # Si Celery devolvió el resultado como string (p. ej. JSON), parsear y devolver como scan si aplica - if isinstance(result, str): - try: - parsed = json.loads(result) - if isinstance(parsed, dict) and ( - parsed.get("status") == "waiting_confirmation" - or (parsed.get("job_id") and "total_rows" in parsed) - ): - return parsed - if isinstance(parsed, dict) and parsed.get("status") in ("finished", "warning"): - return normalize_commit_status_payload(parsed) - except (json.JSONDecodeError, TypeError): - pass - - # Si el resultado tiene forma de escaneo (waiting_confirmation), devolverlo para que el front muestre el modal - if isinstance(result, dict) and ( - result.get("status") == "waiting_confirmation" - or (result.get("job_id") and "total_rows" in result) - ): - return result - - logger.warning("Classes import task %s failed: state=%s", job_id, task_result.state) - err_msg = None - tb = getattr(task_result, "traceback", None) - if tb and isinstance(tb, str): - lines = [l.strip() for l in tb.strip().split("\n") if l.strip()] - if lines: - err_msg = lines[-1] - if not err_msg: - try: - exc = task_result.get(propagate=False) - if exc is not None: - err_msg = str(exc) - except Exception: - pass - if not err_msg and result is not None: - if not isinstance(result, dict): - err_msg = str(result) - elif result.get("error") or result.get("message"): - err_msg = result.get("error") or result.get("message") - return {"status": "failed", "error": err_msg or "Task failed"} - - -@router.post("/{job_id}/commit") -async def commit_import_job( - job_id: str, - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - r = _get_redis() - commit_id = dispatch_tracked_layouts_csv_commit( - db=db, - current_user=current_user, - redis_client=r, - layout_job_id=job_id, - meta_redis_key=f"{CLS_IMPORT_META_PREFIX}{job_id}", - celery_task=insert_valid_rows, - task_name="classes_insert_valid_rows", - task_origin="a76/layouts_csv/classes/commit", - args=[job_id], - required_permissions=["csv_upload.process"], - ) - return { - "status": "committing", - "message": "Inserción iniciada.", - "commit_job_id": commit_id, - } - - -@router.get("/{job_id}/errors/scan-csv") -async def download_scan_errors_csv( - job_id: str, - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - _assert_classes_csv_job_access(db, job_id, current_user) - return download_scan_errors_csv_stream("cls", job_id) diff --git a/backend/api/v1/modules/a76/layouts_csv/classes/schemas.py b/backend/api/v1/modules/a76/layouts_csv/classes/schemas.py deleted file mode 100644 index 2d362f01..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/classes/schemas.py +++ /dev/null @@ -1,21 +0,0 @@ -from pydantic import BaseModel -from typing import Optional - - -class ImportJobResponse(BaseModel): - job_id: str - status: str - message: str - - -class ImportJobStatus(BaseModel): - status: str - job_id: str - total_rows: Optional[int] = 0 - error_count: Optional[int] = 0 - valid_rows: Optional[int] = 0 - error: Optional[str] = None - inserted: Optional[int] = 0 - skipped_invalid: Optional[int] = 0 - skipped_missing_fk: Optional[int] = 0 - skipped_details: Optional[list] = None diff --git a/backend/api/v1/modules/a76/layouts_csv/classes/tasks.py b/backend/api/v1/modules/a76/layouts_csv/classes/tasks.py deleted file mode 100644 index c8146af0..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/classes/tasks.py +++ /dev/null @@ -1,329 +0,0 @@ -""" -Tareas Celery para importación CSV de Clases de Materiales. -Flujo: scan_file (validación) → insert_valid_rows (commit). -Usa layouts_csv.common (storage, normalize, csv_reader, meta, responses) y common.fk_loader, validators, mappers. -Sin ClassService de creación en API; los mappers CSV (row_to_class_data, row_to_class_data_merge_existing) son la fuente de verdad para reglas de negocio al crear/actualizar. -""" -import json -import logging -import os -from typing import Dict, Any, List - -from core.celery_app import celery_app -from core.database import CoreSessionLocal - -from ..common import storage as common_storage -from ..common import normalize as common_normalize -from ..common import csv_reader as common_csv -from ..common import meta as common_meta -from ..common import responses as common_responses -from .template_config import row_from_template, detect_headers_or_data -from .validators import validate_row_class, validate_row_class_partial -from .common.mappers import row_to_class_data, row_to_class_data_merge_existing -from .common.fk_loader import load_classes_fk_sets - -logger = logging.getLogger(__name__) - -JOB_TYPE = "cls" - -# Para routes.py -CLS_IMPORT_FILE_PREFIX = "cls_import_file:" -CLS_IMPORT_META_PREFIX = "cls_import_meta:" -CLS_IMPORT_ERROR_LINES_PREFIX = "cls_import_error_lines:" -CLS_IMPORT_REDIS_TTL = common_storage.IMPORT_REDIS_TTL - - -def _read_plan_for_classes(fieldnames): - if fieldnames: - return common_csv.CsvReadPlan(header_mode="headerless", fieldnames=fieldnames) - return common_csv.CsvReadPlan(header_mode="header") - - -@celery_app.task(bind=True) -def scan_file(self, job_id: str, config: str = None): - logger.info("Classes import: starting scan for job %s", job_id) - - file_path = common_storage.ensure_file_from_redis(JOB_TYPE, job_id, "Classes import") - if not file_path: - return {"status": "failed", "error": "Archivo no encontrado (expirado o no subido). Sube de nuevo."} - common_storage.ensure_meta_from_redis(JOB_TYPE, job_id, file_path, "Classes import") - - error_path = common_storage.error_path_for_job(JOB_TYPE, job_id) - - fieldnames, has_header = detect_headers_or_data(file_path, common_normalize.normalize_header) - read_plan = _read_plan_for_classes(fieldnames) - try: - total_rows = common_csv.count_csv_rows(file_path, has_header=has_header, read_plan=read_plan) - except Exception as e: - return {"status": "failed", "error": str(e)} - - try: - tenant_id, company_id = common_meta.require_tenant_context(file_path) - except ValueError as e: - return {"status": "failed", "error": str(e)} - - meta = common_meta.load_meta(file_path) - actualizar = meta.get("actualizar", False) - siempre_toda = meta.get("siempre_toda", False) - - valid_material_keys, valid_uom_codes, valid_fraction_mex_8, valid_fraction_ame, valid_product_codes_cp = load_classes_fk_sets( - tenant_id, company_id - ) - - from api.v1.modules.a76.classes.models import Class - existing_class_codes = set() - try: - with CoreSessionLocal() as session: - for c in session.query(Class.class_code).filter( - Class.tenant_id == tenant_id, - Class.company_id == company_id, - ).all(): - if c[0]: - existing_class_codes.add(c[0].strip().upper()) - except Exception as e: - logger.warning("Classes import: could not load existing class codes: %s", e) - - error_count = 0 - processed_rows = 0 - errors_detail: List[Dict[str, Any]] = [] - error_lines_list: List[int] = [] - - try: - with open(error_path, "w", encoding="utf-8") as f_err: - for i, row in common_csv.iter_csv_rows_with_plan(file_path, read_plan=read_plan): - self.update_state( - state="PROGRESS", - meta={"current": i, "total": total_rows, "errors": error_count}, - ) - row_norm = row_from_template(row, common_normalize.normalize_header) - row_errors = validate_row_class( - row_norm, - i, - valid_material_keys=valid_material_keys, - valid_uom_codes=valid_uom_codes, - actualizar=actualizar, - siempre_toda=siempre_toda, - existing_class_codes=existing_class_codes, - valid_fraction_mex_8=valid_fraction_mex_8, - valid_fraction_ame=valid_fraction_ame, - valid_product_codes_cp=valid_product_codes_cp, - ) - if row_errors: - has_blocking_error = any(not e.get("warning", False) for e in row_errors) - if has_blocking_error: - error_count += 1 - error_lines_list.append(i) - for e in row_errors: - if not e.get("warning", False): - f_err.write(json.dumps(e) + "\n") - if len(errors_detail) < 500: - for e in row_errors: - if len(errors_detail) >= 500: - break - errors_detail.append({ - "line": e.get("line", i), - "col": e.get("col", ""), - "msg": e.get("msg", ""), - "solution": e.get("solution", ""), - "warning": bool(e.get("warning", False)), - }) - processed_rows += 1 - - if error_lines_list: - common_storage.store_error_lines(JOB_TYPE, job_id, error_lines_list) - except Exception as e: - logger.error("Classes import scan failed: %s", e) - return {"status": "failed", "error": str(e)} - - return common_responses.scan_result( - job_id, processed_rows, error_count, errors_detail - ) - - -@celery_app.task(bind=True) -def insert_valid_rows(self, job_id: str): - logger.info("Classes import: starting commit for job %s", job_id) - - file_path = common_storage.ensure_file_from_redis(JOB_TYPE, job_id, "Classes import") - if not file_path: - alt_path = common_storage.file_path_for_job(JOB_TYPE, job_id) - if not os.path.exists(alt_path): - return {"status": "failed", "error": "Archivo no encontrado (expirado). Sube y confirma de nuevo."} - file_path = alt_path - else: - common_storage.ensure_meta_from_redis(JOB_TYPE, job_id, file_path, "Classes import") - - error_path = common_storage.error_path_for_job(JOB_TYPE, job_id) - error_lines = common_storage.get_error_lines(JOB_TYPE, job_id, error_path) - - try: - tenant_id, company_id = common_meta.require_tenant_context(file_path) - except ValueError as e: - return {"status": "failed", "error": str(e)} - - meta = common_meta.load_meta(file_path) - actualizar = meta.get("actualizar", False) - siempre_toda = meta.get("siempre_toda", False) - - from api.v1.modules.a76.classes.models import Class - - valid_material_keys, valid_uom_codes, valid_fraction_mex_8, valid_fraction_ame, valid_product_codes_cp = load_classes_fk_sets( - tenant_id, company_id - ) - - inserted_count = 0 - skipped_invalid = 0 - skipped_details: List[Dict[str, Any]] = [] - response = None - meta_path = common_meta.get_meta_path(file_path) - - fieldnames, _ = detect_headers_or_data(file_path, common_normalize.normalize_header) - read_plan = _read_plan_for_classes(fieldnames) - - try: - with CoreSessionLocal() as session: - existing_by_code = {} - for c in session.query(Class).filter( - Class.tenant_id == tenant_id, - Class.company_id == company_id, - ).all(): - key = (c.class_code or "").strip().upper() - if key: - existing_by_code[key] = c - - for i, row in common_csv.iter_csv_rows_with_plan(file_path, read_plan=read_plan): - if i in error_lines: - continue - - row_norm = row_from_template(row, common_normalize.normalize_header) - class_code_raw = (row_norm.get("CLASE") or "").strip().upper()[:8] - use_partial = actualizar and class_code_raw and class_code_raw in existing_by_code and not siempre_toda - - if use_partial: - row_errors = validate_row_class_partial( - row_norm, i, - valid_material_keys=valid_material_keys, - valid_uom_codes=valid_uom_codes, - valid_fraction_mex_8=valid_fraction_mex_8, - valid_fraction_ame=valid_fraction_ame, - valid_product_codes_cp=valid_product_codes_cp, - ) - else: - row_errors = validate_row_class( - row_norm, - i, - valid_material_keys=valid_material_keys, - valid_uom_codes=valid_uom_codes, - actualizar=actualizar, - siempre_toda=siempre_toda, - existing_class_codes=set(existing_by_code.keys()), - valid_fraction_mex_8=valid_fraction_mex_8, - valid_fraction_ame=valid_fraction_ame, - valid_product_codes_cp=valid_product_codes_cp, - ) - if row_errors and any(not e.get("warning", False) for e in row_errors): - skipped_invalid += 1 - blocking_errors = [e for e in row_errors if not e.get("warning", False)] - for blocking in blocking_errors: - skipped_details.append({ - "line": i, - "reason": f"{blocking.get('col', '')}: {blocking.get('msg', '')}", - "solution": blocking.get("solution", ""), - }) - continue - - if use_partial: - existing = existing_by_code.get(class_code_raw) - existing_data = { - "description_es": existing.description_es, - "description_en": existing.description_en, - "material_key": existing.material_key, - "unit_of_measure": existing.unit_of_measure, - "fraction": existing.fraction, - "us_fraction": existing.us_fraction, - "sub_key": existing.sub_key, - "physical_review": existing.physical_review, - "iva_exempt_fraction": existing.iva_exempt_fraction, - } - data = row_to_class_data_merge_existing( - row_norm, existing_data, valid_material_keys, valid_uom_codes, - ) - else: - data = row_to_class_data(row_norm, valid_material_keys, valid_uom_codes) - - class_code = data.get("class_code") - if not class_code: - skipped_invalid += 1 - continue - - existing = existing_by_code.get(class_code) - if existing: - existing.description_es = data["description_es"] - existing.description_en = data["description_en"] - existing.material_key = data["material_key"] - existing.unit_of_measure = data["unit_of_measure"] - existing.fraction = data["fraction"] - existing.us_fraction = data["us_fraction"] - existing.sub_key = data["sub_key"] - existing.physical_review = data["physical_review"] - existing.iva_exempt_fraction = data["iva_exempt_fraction"] - session.add(existing) - inserted_count += 1 - else: - new_class = Class( - tenant_id=tenant_id, - company_id=company_id, - **data, - ) - session.add(new_class) - existing_by_code[class_code] = new_class - inserted_count += 1 - - try: - session.commit() - except Exception as db_err: - session.rollback() - logger.error("Classes import DB error: %s", db_err) - return common_responses.commit_result( - "failed", 0, skipped_invalid, 0, 0, - skipped_details, error=str(db_err), - ) - - if inserted_count == 0 and skipped_invalid > 0: - response = common_responses.commit_result( - "warning", 0, skipped_invalid, 0, 0, - skipped_details, - message=f"No se insertaron registros. {skipped_invalid} rechazados.", - ) - elif inserted_count == 0: - response = common_responses.commit_result( - "failed", 0, skipped_invalid, 0, 0, - skipped_details, - error="No hay registros válidos en el archivo CSV", - ) - else: - response = common_responses.commit_result( - "finished", inserted_count, skipped_invalid, 0, 0, - skipped_details, - ) - - except Exception as e: - logger.exception("Classes import task failed") - response = common_responses.commit_result( - "failed", 0, skipped_invalid, 0, 0, - skipped_details, error=str(e), - ) - - common_storage.cleanup_import_job( - JOB_TYPE, job_id, - file_path=file_path, - error_path=error_path, - meta_path=meta_path, - ) - - if response is None: - response = common_responses.commit_result( - "failed", 0, skipped_invalid, 0, 0, - skipped_details, error="Error inesperado", - ) - return response diff --git a/backend/api/v1/modules/a76/layouts_csv/classes/template_config.py b/backend/api/v1/modules/a76/layouts_csv/classes/template_config.py deleted file mode 100644 index 3325be7d..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/classes/template_config.py +++ /dev/null @@ -1,198 +0,0 @@ -""" -Configuración de plantilla CSV para Clases de Materiales (EstructuraCatClasesAF.xls). -Cabeceras de descarga = Clarion: CLAVE CLASE, DESCRIPCION ESPAÑOL, DESCRIPCION INGLES, etc. -""" -import csv -import io -from typing import Dict, List, Any, Optional, Tuple - -from ..common.cell_value import cell_to_str -from ..common import csv_reader as common_csv_reader -from ..common.csv_hint_inject import inject_max_char_hints_into_columns, merge_enum_hint_before_max -from ..common.inject_csv_labels_en import inject_en_labels_into_template_columns -from ..common.template_locale import ( - download_header_cell, - merge_download_header_into_lookup, - merge_labels_into_lookup, - normalize_locale, -) -from .csv_max_chars_for_headers import MATERIAL_CLASSES_CSV_MAX_CHARS - - -# Valores que indican que la primera fila es cabecera (primera columna normalizada) -FIRST_COLUMN_HEADER_VALUES = ("CLAVE CLASE", "CLASE", "CLASS CODE") - -def detect_headers_or_data( - file_path: str, - normalize_header_fn, - encoding: str = "utf-8-sig", -) -> Tuple[Optional[List[str]], bool]: - """ - Lee la primera línea del CSV y decide si es cabecera o dato. - Devuelve (fieldnames, has_header). - - Si la primera celda normalizada está en FIRST_COLUMN_HEADER_VALUES -> has_header=True, fieldnames=None - (la primera fila es cabecera; iter_csv_rows sin fieldnames). - - Si no -> has_header=False, fieldnames=TEMPLATE_DOWNLOAD_HEADERS (la primera fila es dato). - """ - try: - sample, _ = common_csv_reader.read_text_sample( - file_path, - requested_encoding=encoding, - sample_chars=2048, - ) - except Exception: - try: - sample, _ = common_csv_reader.read_text_sample( - file_path, - requested_encoding="auto", - sample_chars=2048, - ) - except Exception: - return None, True - - if not sample: - return None, True - lines = sample.splitlines() - if not lines: - return None, True - dialect = common_csv_reader.detect_csv_dialect(sample, delimiters=",;\t") - reader = csv.reader(io.StringIO(lines[0]), dialect=dialect) - first_row = next(reader, None) - if not first_row: - return None, True - first_cell = (first_row[0] or "").strip() - first_cell_norm = normalize_header_fn(first_cell) - if first_cell_norm in FIRST_COLUMN_HEADER_VALUES: - return None, True - return list(TEMPLATE_DOWNLOAD_HEADERS), False - -# Cabeceras que se escriben al descargar la plantilla CSV (igual que Clarion EstructuraCatClasesAF) -TEMPLATE_DOWNLOAD_HEADERS: List[str] = [ - "CLAVE CLASE", - "DESCRIPCION ESPAÑOL", - "DESCRIPCION INGLES", - "TIPO DE MATERIAL", - "U.M. COMERCIAL", - "FRACCION ARANCELARIA", - "FRACCION AMERICANA", - "TASA DE DEPRECIACION", - "REVISION FISICA (1/0)", - "CODIGO DE PRODUCTO/SERVICIO CP", -] - -TEMPLATE_DOWNLOAD_HEADERS_EN: List[str] = [ - "CLASS CODE", - "DESCRIPTION (SPANISH)", - "DESCRIPTION (ENGLISH)", - "MATERIAL TYPE", - "COMMERCIAL UOM", - "MX FRACTION", - "US FRACTION", - "DEPRECIATION RATE", - "PHYSICAL REVIEW (1/0)", - "PRODUCT/SERVICE CP CODE", -] - -TEMPLATE_COLUMNS: Dict[str, List[Dict[str, Any]]] = { - "material_classes": [ - {"canonical": "CLASE", "aliases": ["CLAVE CLASE", "CLASS", "CODIGO", "CLASE CODIGO"]}, - {"canonical": "DESCRIPCIONE", "aliases": ["DESCRIPCION ESPAÑOL", "DESCRIPCION", "DESCRIPCION ES", "DESCRIPCION ESPAÑOL"]}, - {"canonical": "DESCRIPCIONI", "aliases": ["DESCRIPCION INGLES", "DESCRIPCION EN", "DESCRIPTION", "DESCRIPCION INGLES"]}, - {"canonical": "CLAVEMAT", "aliases": ["TIPO DE MATERIAL", "MATERIAL", "TIPOMAT", "CLAVE MATERIAL"]}, - {"canonical": "UNIMED", "aliases": ["U.M. COMERCIAL", "UNIDAD MEDIDA", "UNIT", "UOM", "TIPO DE MU.M.", "U.M. COMERCIAL"]}, - {"canonical": "FRACCION", "aliases": ["FRACCION ARANCELARIA", "FRACCION MEX", "COM FRACCION"]}, - {"canonical": "FRACCIONAME", "aliases": ["FRACCION AMERICANA", "FRACCION USA", "US FRACTION", "FRACCION"]}, - {"canonical": "TASADEPRECIA", "aliases": ["TASA DE DEPRECIACION", "TASA DEPRECIACIÓN", "TASA DEPRECIACION"]}, - {"canonical": "CLAVESUB", "aliases": ["SUB KEY", "CLAVE SUB"]}, - {"canonical": "REVFISICA", "aliases": ["REVISION FISICA (1/0)", "REV FISICA", "PHYSICAL REVIEW", "TASA DE REVISION", "REVISION FISICA"]}, - {"canonical": "FRACCIONEXENTAIVA", "aliases": ["CODIGO DE PRODUCTO/SERVICIO CP", "EXENTA IVA", "FRACCION EXENTA IVA", "CODIGO PRODUCTO SERVICIO CP"]}, - ], -} - -inject_en_labels_into_template_columns(TEMPLATE_COLUMNS) - -# Mismos canónicos que parts (CLASE, FRACCION, …); restaurar etiquetas de catálogo de clases. -# Mismo texto que TEMPLATE_DOWNLOAD_HEADERS_EN (plantilla Clarion EN). -_MATERIAL_CLASS_LABEL_EN_OVERRIDES = { - "CLASE": "CLASS CODE", - "UNIMED": "COMMERCIAL UOM", - "FRACCION": "MX FRACTION", - "FRACCIONAME": "US FRACTION", -} -for col in TEMPLATE_COLUMNS.get("material_classes") or []: - canon = col.get("canonical") - if canon in _MATERIAL_CLASS_LABEL_EN_OVERRIDES: - col.setdefault("labels", {})["en"] = _MATERIAL_CLASS_LABEL_EN_OVERRIDES[canon] - -inject_max_char_hints_into_columns( - TEMPLATE_COLUMNS.get("material_classes"), MATERIAL_CLASSES_CSV_MAX_CHARS -) -for _mc in TEMPLATE_COLUMNS.get("material_classes") or []: - if _mc.get("canonical") == "REVFISICA": - merge_enum_hint_before_max(_mc, ["1", "0"]) - -_MC_DL_ORDER = [ - "CLASE", - "DESCRIPCIONE", - "DESCRIPCIONI", - "CLAVEMAT", - "UNIMED", - "FRACCION", - "FRACCIONAME", - "TASADEPRECIA", - "REVFISICA", - "FRACCIONEXENTAIVA", -] -_MC_BY_CANON = {c["canonical"]: c for c in (TEMPLATE_COLUMNS.get("material_classes") or [])} -_MATERIAL_CLASSES_DOWNLOAD_DEFS = [dict(_MC_BY_CANON[k]) for k in _MC_DL_ORDER] - - -def download_headers_for_locale(locale: str) -> List[str]: - loc = normalize_locale(locale) - return [download_header_cell(d, loc) for d in _MATERIAL_CLASSES_DOWNLOAD_DEFS] - - -def build_normalized_lookup(normalize_header_fn) -> Dict[str, str]: - cols = TEMPLATE_COLUMNS.get("material_classes") - if not cols: - return {} - lookup: Dict[str, str] = {} - for item in cols: - canonical = item["canonical"] - lookup[normalize_header_fn(canonical)] = canonical - for alias in item.get("aliases") or []: - lookup[normalize_header_fn(alias)] = canonical - merge_labels_into_lookup(item, canonical, normalize_header_fn, lookup) - merge_download_header_into_lookup(item, canonical, normalize_header_fn, lookup) - for es_h, en_h in zip(TEMPLATE_DOWNLOAD_HEADERS, TEMPLATE_DOWNLOAD_HEADERS_EN): - if not es_h or not en_h: - continue - es_key = normalize_header_fn(es_h) - canon = lookup.get(es_key) - if canon: - lookup[normalize_header_fn(en_h)] = canon - return lookup - - -def row_from_template(row: Dict[str, Any], normalize_header_fn) -> Dict[str, Any]: - lookup = build_normalized_lookup(normalize_header_fn) - if not lookup: - return {normalize_header_fn(k): cell_to_str(v) for k, v in row.items()} - out: Dict[str, Any] = {} - for csv_header, value in row.items(): - key_norm = normalize_header_fn(csv_header) - if key_norm in lookup: - out[lookup[key_norm]] = cell_to_str(value) - elif key_norm.startswith("CLAVE CLASE"): - # CSV leído con delimitador incorrecto: primera columna puede venir colapsada. - if "CLASE" not in out and value: - val_str = cell_to_str(value) - first_val = val_str - for delimiter in (",", ";", "\t"): - if delimiter in first_val: - first_val = first_val.split(delimiter)[0] - break - first_val = first_val.lstrip("\ufeff").strip() - if first_val: - out["CLASE"] = first_val - return out diff --git a/backend/api/v1/modules/a76/layouts_csv/classes/validators/__init__.py b/backend/api/v1/modules/a76/layouts_csv/classes/validators/__init__.py deleted file mode 100644 index 9d108067..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/classes/validators/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .create import validate_row_class, validate_row_class_partial - -__all__ = ["validate_row_class", "validate_row_class_partial"] diff --git a/backend/api/v1/modules/a76/layouts_csv/classes/validators/common.py b/backend/api/v1/modules/a76/layouts_csv/classes/validators/common.py deleted file mode 100644 index a5ea6b7e..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/classes/validators/common.py +++ /dev/null @@ -1,233 +0,0 @@ -""" -Validaciones comunes de fila para import CSV de clases de materiales. -Paridad con Clarion: VALIDACIONES_CLASE (Col A len, Col D/E/F/G en catálogo, Col F len≥8, Col H ≤100, Col J en catálogo CP). -""" -from typing import Dict, Any, Optional, Set, List - -from ..common.common_validators import ( - check_max_length, - check_int_range, - check_decimal_max, -) - -MSG_CLASE_VACIO = "Error: (Col. A) La columna de Clase esta vacio y no se pueden hacer las validaciones." -MSG_CLASE_VACIO_SOLUCION = "Capturar en la Columna A una Clase nueva o una ya existente a la cual desee actualizar campos" - - -def validate_row_required(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]: - """CLASE obligatorio; mensaje Clarion si está vacío. Acepta clave 'CLASE' o 'CLAVE CLASE' (por si el CSV no se normalizó).""" - val = (row.get("CLASE") or row.get("CLAVE CLASE") or "").strip() - if not val: - return {"line": line_num, "col": "CLASE", "msg": f"{MSG_CLASE_VACIO} {MSG_CLASE_VACIO_SOLUCION}"} - if len(val) > 8: - return { - "line": line_num, - "col": "CLASE", - "msg": f"Error: (Col. A) La Clase: {val} supera la longitud de caracteres. Capturar en la columna A una Clase de 8 caracteres como máximo.", - } - return None - - -def validate_row_required_full(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]: - """Obligatorios en validación completa: B, D, E, F (Clarion VALIDA_TODA_CLASE).""" - cols_labels = [ - ("DESCRIPCIONE", "(Col.B) Descripción Español"), - ("CLAVEMAT", "(Col.D) Tipo de Material"), - ("UNIMED", "(Col.E) U.M. Comercial"), - ("FRACCION", "(Col.F) Fraccion Arancelaria Mex."), - ] - missing = [(col, label) for col, label in cols_labels if not (row.get(col) or "").strip()] - if not missing: - return None - campos = ", ".join(label for _, label in missing) - first_col = missing[0][0] - return { - "line": line_num, - "col": first_col, - "msg": f"Existen campos vacios que son obligatorios, es la {campos}. Revisar la línea del archivo y capturar los campos con la información correcta.", - } - - -def validate_row_lengths(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]: - checks = [ - ("DESCRIPCIONE", 500), - ("DESCRIPCIONI", 500), - ("CLAVEMAT", 10), - ("UNIMED", 5), - ("FRACCION", 20), - ("FRACCIONAME", 16), - ("CLAVESUB", 5), - ("FRACCIONEXENTAIVA", 4), - ] - for col, max_len in checks: - err = check_max_length(row, col, max_len, line_num) - if err: - return err - return None - - -def validate_row_fraction_min(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]: - """FRACCION (Col F): si no vacío, mínimo 8 caracteres (Clarion VALIDACIONES_CLASE).""" - val = (row.get("FRACCION") or "").strip() - if not val: - return None - if len(val) < 8: - return { - "line": line_num, - "col": "FRACCION", - "msg": f"La Fraccion {val} no alcanza la longitud de 8 caracteres. Capturar en la columna F una Fracción de 8 caracteres.", - } - return None - - -def validate_row_types(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]: - return check_int_range(row, "REVFISICA", line_num, -32768, 32767) - - -def validate_row_fks( - row: Dict[str, Any], - line_num: int, - valid_material_keys: Optional[Set[str]], - valid_uom_codes: Optional[Set[str]], -) -> Optional[Dict[str, Any]]: - val_d = (row.get("CLAVEMAT") or "").strip() - if val_d and valid_material_keys is not None and val_d not in valid_material_keys: - return { - "line": line_num, - "col": "CLAVEMAT", - "msg": f"Error: (Col. D) El Tipo de Activo Fijo: {val_d} no existe en el Catálogo de Tipos de Activo Fijo. Revisar este Tipo de Activo Fijo en el archivo, en caso de ser correcto actualice los catálogos fijos.", - } - val_e = (row.get("UNIMED") or "").strip() - if val_e and valid_uom_codes is not None and val_e not in valid_uom_codes: - return { - "line": line_num, - "col": "UNIMED", - "msg": f"Error: (Col. E) La Unidad de Medida Comercial: {val_e} no existe en el Catálogo de U.M. Revisar esta Unidad de Medida en el archivo, en caso de ser correcta actualice los catálogos.", - } - return None - - -def _normalize_fraction_mex_8(value: str) -> str: - """Primeros 8 caracteres si len>=10, sino hasta 8 (Clarion SUB(ColumnaF, 1, 8)).""" - if not value: - return "" - v = value.strip() - if len(v) >= 10: - return v[:8] - return v[:8] if len(v) > 8 else v - - -def validate_row_fraction_mex_catalog( - row: Dict[str, Any], - line_num: int, - valid_fraction_mex_8: Optional[Set[str]], -) -> Optional[Dict[str, Any]]: - """Col F: si no vacía, debe existir en catálogo Mex (GFracGenSifra) o Histórico (Clarion).""" - val = (row.get("FRACCION") or "").strip() - if not val or valid_fraction_mex_8 is None: - return None - code_8 = _normalize_fraction_mex_8(val) - if not code_8: - return None - if code_8 in valid_fraction_mex_8: - return None - return { - "line": line_num, - "col": "FRACCION", - "msg": ( - f"Error: (Col. F) La Fraccion Mexicana: {val} no existe en el Catálogo de Fracciones Arancelarias Sifr@ ni en el Historico. " - "Revisar esta Fracción Arancelaria en el archivo, en caso de ser correcta Actualizar las Fracciones Arancelarias." - ), - } - - -def validate_row_fraction_ame_catalog( - row: Dict[str, Any], - line_num: int, - valid_fraction_ame: Optional[Set[str]] = None, -) -> Optional[Dict[str, Any]]: - """Col G: si no vacía, debe existir en SITAR fracciones-usa (valid_fraction_ame ignorado; compat).""" - from api.v1.modules.sitar.fracciones_usa.catalog_resolve import ( - resolve_american_fraction_from_sitar, - ) - - val = (row.get("FRACCIONAME") or "").strip() - if not val: - return None - if resolve_american_fraction_from_sitar(val): - return None - return { - "line": line_num, - "col": "FRACCIONAME", - "msg": ( - f"Error: (Col. G) La Fraccion Americana: {val} no existe en el catálogo SITAR (fracciones USA)." - ), - } - - -def validate_row_tasa_depreciacion(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]: - """Col H: si viene informada, no puede ser mayor al 100 % (Clarion).""" - val = row.get("TASADEPRECIA") - if val is None or val == "": - return None - err = check_decimal_max(row, "TASADEPRECIA", line_num, 100.0, msg=None) - if err and "100" in (err.get("msg") or ""): - v = (row.get("TASADEPRECIA") or "").strip() - err["msg"] = f"Error: (Col. H) La Tasa de Depreciación: {v} no puede ser mayor al 100 %. Ajustar la Tasa de Depreciacion." - return err - - -def validate_row_codigo_producto_cp( - row: Dict[str, Any], - line_num: int, - valid_product_codes_cp: Optional[Set[str]], - class_code: str = "", -) -> Optional[Dict[str, Any]]: - """Col J: si no vacía y existe catálogo CP, debe existir en GCodigosProductoCP (Clarion).""" - val = (row.get("FRACCIONEXENTAIVA") or "").strip() - if not val: - return None - if valid_product_codes_cp is None or len(valid_product_codes_cp) == 0: - return None - if val in valid_product_codes_cp: - return None - cl = class_code or (row.get("CLASE") or "").strip() - return { - "line": line_num, - "col": "FRACCIONEXENTAIVA", - "msg": ( - f"Error: (Col. J) La clase: {cl} tiene asignado un código de producto inexistente. " - "Capturar un código de producto correcto." - ), - } - - -def validaciones_clase( - row: Dict[str, Any], - line_num: int, - valid_material_keys: Optional[Set[str]], - valid_uom_codes: Optional[Set[str]], - valid_fraction_mex_8: Optional[Set[str]] = None, - valid_fraction_ame: Optional[Set[str]] = None, - valid_product_codes_cp: Optional[Set[str]] = None, -) -> List[Dict[str, Any]]: - """Reglas compartidas Clarion (VALIDACIONES_CLASE): longitudes, tipos, FKs, fracciones, tasa, código CP.""" - errors: List[Dict[str, Any]] = [] - - class_code = (row.get("CLASE") or "").strip() - for err in [ - validate_row_lengths(row, line_num), - validate_row_fraction_min(row, line_num), - validate_row_types(row, line_num), - validate_row_fks(row, line_num, valid_material_keys, valid_uom_codes), - validate_row_fraction_mex_catalog(row, line_num, valid_fraction_mex_8), - validate_row_fraction_ame_catalog(row, line_num, valid_fraction_ame), - validate_row_tasa_depreciacion(row, line_num), - validate_row_codigo_producto_cp( - row, line_num, valid_product_codes_cp, class_code - ), - ]: - if err: - errors.append(err) - - return errors diff --git a/backend/api/v1/modules/a76/layouts_csv/classes/validators/create.py b/backend/api/v1/modules/a76/layouts_csv/classes/validators/create.py deleted file mode 100644 index 61c8456b..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/classes/validators/create.py +++ /dev/null @@ -1,90 +0,0 @@ -""" -Punto de entrada de validación para import de una fila de clase de material. -Flujo Clarion: no ACT → siempre VALIDA_TODA_CLASE; ACT y clase existe → VALIDA_PARCIAL_CLASE; -ACT y clase no existe → VALIDA_TODA_CLASE (validación completa) y si pasa se crea en el insert (ADD). -""" -from typing import Dict, Any, Optional, Set, List - -from .common import ( - validate_row_required, - validate_row_required_full, - validaciones_clase, -) - - -def validate_row_class( - row: Dict[str, Any], - line_num: int, - valid_material_keys: Optional[Set[str]] = None, - valid_uom_codes: Optional[Set[str]] = None, - actualizar: bool = False, - siempre_toda: bool = False, - existing_class_codes: Optional[Set[str]] = None, - valid_fraction_mex_8: Optional[Set[str]] = None, - valid_fraction_ame: Optional[Set[str]] = None, - valid_product_codes_cp: Optional[Set[str]] = None, -) -> List[Dict[str, Any]]: - """ - Igual que Clarion: - - No ACT (actualizar=False): siempre TODA (B,D,E,F obligatorios + validaciones_clase). - - ACT y clase existe: PARCIAL (solo CLASE + validaciones_clase); en insert se actualiza (PUT). - - ACT y clase no existe: TODA (validación completa); si pasa, en insert se crea (ADD). - siempre_toda fuerza TODA en todos los casos. - """ - errors: List[Dict[str, Any]] = [] - err = validate_row_required(row, line_num) - if err: - errors.append(err) - - class_code = (row.get("CLASE") or "").strip().upper()[:8] - use_full = siempre_toda or not actualizar - if actualizar and existing_class_codes is not None and class_code in existing_class_codes: - use_full = False - - if use_full: - err = validate_row_required_full(row, line_num) - if err: - errors.append(err) - # En Actualizar, si la clase no existe se valida completa y si pasa se crea en insert (como Clarion ADD). - - errors.extend( - validaciones_clase( - row, - line_num, - valid_material_keys, - valid_uom_codes, - valid_fraction_mex_8=valid_fraction_mex_8, - valid_fraction_ame=valid_fraction_ame, - valid_product_codes_cp=valid_product_codes_cp, - ) - ) - return errors - - -def validate_row_class_partial( - row: Dict[str, Any], - line_num: int, - valid_material_keys: Optional[Set[str]] = None, - valid_uom_codes: Optional[Set[str]] = None, - valid_fraction_mex_8: Optional[Set[str]] = None, - valid_fraction_ame: Optional[Set[str]] = None, - valid_product_codes_cp: Optional[Set[str]] = None, -) -> List[Dict[str, Any]]: - """Validación parcial (modo Act, clase existente): solo CLASE + validaciones_clase.""" - errors: List[Dict[str, Any]] = [] - err = validate_row_required(row, line_num) - if err: - errors.append(err) - - errors.extend( - validaciones_clase( - row, - line_num, - valid_material_keys, - valid_uom_codes, - valid_fraction_mex_8=valid_fraction_mex_8, - valid_fraction_ame=valid_fraction_ame, - valid_product_codes_cp=valid_product_codes_cp, - ) - ) - return errors diff --git a/backend/api/v1/modules/a76/layouts_csv/clients_and_providers/__init__.py b/backend/api/v1/modules/a76/layouts_csv/clients_and_providers/__init__.py deleted file mode 100644 index 94b7cb55..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/clients_and_providers/__init__.py +++ /dev/null @@ -1,2 +0,0 @@ -# CSV import flow for Clientes y Proveedores (Client Providers). -# Replicates the same two-phase flow as customs_brokers/imports: upload → scan → waiting_confirmation → commit. diff --git a/backend/api/v1/modules/a76/layouts_csv/clients_and_providers/common/__init__.py b/backend/api/v1/modules/a76/layouts_csv/clients_and_providers/common/__init__.py deleted file mode 100644 index 36641126..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/clients_and_providers/common/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# common_validators, mappers (no fk_loader for clients_and_providers) diff --git a/backend/api/v1/modules/a76/layouts_csv/clients_and_providers/common/common_validators.py b/backend/api/v1/modules/a76/layouts_csv/clients_and_providers/common/common_validators.py deleted file mode 100644 index efd1f60d..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/clients_and_providers/common/common_validators.py +++ /dev/null @@ -1,319 +0,0 @@ -""" -Validadores reutilizables para import CSV de clientes y proveedores. -Paridad Clarion: procedencia E/N, tipo C/P/A, clave máx 8, SECON, Prosec, Vinculación, -Es Empresa Certificada, Transformador/SubMaq, desfase. -""" -import re -from typing import Dict, Any, Optional - -from api.v1.modules.a76.clients_and_providers.models import ClientOrProviderEnum - - -def short_name_import_key(raw: Optional[str]) -> str: - """Clave estable para upsert CSV y validación ACT (strip + upper).""" - return (raw or "").strip().upper() - - -RFC_MAX = 30 - -# Formato RFC México: 3-4 letras, 6 dígitos, 3 homoclave. TAX-ID: 2 dígitos, guión, resto. Ej: 12-3456789. -RFC_PATTERN = re.compile(r"^[A-Z&Ñ]{3,4}\d{6}[A-Z0-9]{3}$", re.IGNORECASE) -TAX_ID_PATTERN = re.compile(r"^\d{2}-[A-Z0-9]{1,27}$", re.IGNORECASE) -NAME_MAX = 256 -SHORT_NAME_MAX = 10 -# Clarion: Col C máx 8 caracteres -SHORT_NAME_MAX_CLARION = 8 -CURP_MAX = 19 - -# Valores permitidos Col Q (Tipo programa SECON) -TIPO_PROGRAMA_SECON_VALIDOS = frozenset( - {"IMMEX", "Maquila", "Pitex", "Ecex", "RECIME", "Pronex", "Ninguno"} -) - - -def check_required_max(row: Dict[str, Any], col: str, max_len: int, line_num: int) -> Optional[Dict[str, Any]]: - val = (row.get(col) or "").strip() - if not val: - return {"line": line_num, "col": col, "msg": "Requerido"} - if len(val) > max_len: - return {"line": line_num, "col": col, "msg": f"Máximo {max_len} caracteres"} - return None - - -def check_rfc_format(row: Dict[str, Any], line_num: int, col: str = "RFC") -> Optional[Dict[str, Any]]: - """Columna RFC: si tiene valor, debe cumplir formato RFC México.""" - val = (row.get(col) or "").strip() - if not val: - return None - if not RFC_PATTERN.match(val.upper()): - return { - "line": line_num, - "col": col, - "msg": "El formato del RFC es inválido.", - "solution": "Capturar un RFC con formato válido (ej. XAXX010101000).", - } - return None - - -def check_tax_id_format(row: Dict[str, Any], line_num: int, col: str = "TAX_ID") -> Optional[Dict[str, Any]]: - """Columna TAX-ID: 2 dígitos, guión y resto alfanumérico. Ej: 12-3456789. Máx 30 caracteres.""" - val = (row.get(col) or "").strip() - if not val: - return None - if len(val) > 30 or not TAX_ID_PATTERN.match(val): - return { - "line": line_num, - "col": col, - "msg": "El formato del TAX-ID es inválido.", - "solution": "Capturar un TAX-ID con formato: 2 dígitos, guión y resto (ej. 12-3456789). Máx 30 caracteres.", - } - return None - - -def check_max_length(row: Dict[str, Any], col: str, max_len: int, line_num: int) -> Optional[Dict[str, Any]]: - val = (row.get(col) or "").strip() - if not val: - return None - if len(val) > max_len: - return {"line": line_num, "col": col, "msg": f"Máximo {max_len} caracteres"} - return None - - -def parse_client_or_provider(val: Optional[str]) -> Optional[ClientOrProviderEnum]: - if not val or not str(val).strip(): - return None - s = str(val).strip() - v = s.lower() - # Una sola letra: C, P, A o B (Clarion: A=Ambos; B también usado como Ambos) - if len(v) == 1: - if v == "c": - return ClientOrProviderEnum.CLIENT - if v == "p": - return ClientOrProviderEnum.PROVIDER - if v in ("a", "b"): - return ClientOrProviderEnum.BOTH - if v in ("client", "cliente"): - return ClientOrProviderEnum.CLIENT - if v in ("provider", "proveedor"): - return ClientOrProviderEnum.PROVIDER - if v in ("both", "ambos"): - return ClientOrProviderEnum.BOTH - return None - - -def check_tipo_client_provider(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]: - tipo_raw = (row.get("TIPO") or "").strip() - if not tipo_raw: - return None - if parse_client_or_provider(tipo_raw) is None: - return { - "line": line_num, - "col": "TIPO", - "msg": "Valor no válido. Use Cliente (C), Proveedor (P), Ambos (A) o dejar vacío.", - "solution": "Capturar una opción valida: C para Cliente, P para Proveedor, A para Ambos o dejar el campo vacio.", - } - return None - - -def check_procedencia( - row: Dict[str, Any], line_num: int -) -> Optional[Dict[str, Any]]: - """Col A: E o N. Si PAIS (Col L) tiene valor: N → MEX, E → no MEX.""" - val = (row.get("PROCEDENCIA") or "").strip().upper() - if not val: - return None - if val not in ("E", "N"): - return { - "line": line_num, - "col": "PROCEDENCIA", - "msg": f"Error: (Col. A) El tipo de cliente: {val} es incorrecto.", - "solution": "Capturar en la columna A el Tipo de cliente correcto: E para Extranjero y N para Nacional.", - } - pais = (row.get("PAIS") or "").strip().upper() - if not pais: - return None - if val == "N" and pais != "MEX": - return { - "line": line_num, - "col": "PAIS", - "msg": f"Error: (Col. L) El tipo de cliente es Nacional y tiene el país {pais}, es incorrecto.", - "solution": "Capturar en la columna L el país con clave MEX, ya que es un cliente Nacional.", - } - if val == "E" and pais == "MEX": - return { - "line": line_num, - "col": "PAIS", - "msg": f"Error: (Col. L) El tipo de cliente es Extranjero y tiene el país MEX, es incorrecto.", - "solution": "Capturar en la columna L un país con clave diferente de MEX, ya que es un cliente Extranjero.", - } - return None - - -def check_short_name_max_clarion( - row: Dict[str, Any], line_num: int, max_len: int = SHORT_NAME_MAX_CLARION -) -> Optional[Dict[str, Any]]: - """Col C: Clave cliente/proveedor máx 8 caracteres (Clarion).""" - val = (row.get("SHORT_NAME") or "").strip() - if not val: - return None - if len(val) > max_len: - return { - "line": line_num, - "col": "SHORT_NAME", - "msg": f"Error: (Col. C) La Clave de Cliente/Proveedor supera la longitud de caracteres (máx {max_len}).", - "solution": f"Capturar en la columna C una Clave de Cliente/Proveedor de {max_len} caracteres como máximo.", - } - return None - - -def check_tipo_programa_secon( - row: Dict[str, Any], line_num: int -) -> Optional[Dict[str, Any]]: - """Col Q: IMMEX, Maquila, Pitex, Ecex, RECIME, Pronex, Ninguno. - - Reglas Clarion para el import CSV: - - Si NO es Ninguno: obligatorios `NUM_PROGRAMA_SECON` (Col. R) y `FECHA_AUT_SECON` (Col. S). - - Si es Ninguno o viene vacío: `NUM_PROGRAMA_SECON` (Col. R) debe venir vacío; - `FECHA_AUT_SECON` (Col. S) se permite (no debe bloquear la carga). - """ - q_raw = (row.get("TIPO_PROGRAMA_SECON") or "").strip() - # Normalización robusta: CSV puede traer "Ninguno" o "ninguno", etc. - valid_map = {v.lower(): v for v in TIPO_PROGRAMA_SECON_VALIDOS} - q_lower = q_raw.lower() - q = valid_map.get(q_lower, q_raw) - r = (row.get("NUM_PROGRAMA_SECON") or "").strip() - s = (row.get("FECHA_AUT_SECON") or "").strip() - if q_raw and q not in TIPO_PROGRAMA_SECON_VALIDOS: - return { - "line": line_num, - "col": "TIPO_PROGRAMA_SECON", - "msg": f"Error: (Col. Q) El Tipo de Programa SECON: {q_raw} es incorrecto.", - "solution": "Capturar los Tipos de Programa correctos: IMMEX, Maquila, Pitex, Ecex, RECIME, Pronex o Ninguno.", - } - if not q or q == "Ninguno": - if r: - return { - "line": line_num, - "col": "NUM_PROGRAMA_SECON", - "msg": "Error: (Col. R) El Tipo de Programa es Ninguno y está capturado el número de programa.", - "solution": "Borrar la información en la columna R del archivo o asignar un Programa en la columna Q.", - } - return None - # No es Ninguno: R y S obligatorios - if not r: - return { - "line": line_num, - "col": "NUM_PROGRAMA_SECON", - "msg": f"Error: (Col. R) El Tipo de Programa es: {q} y no está capturado el número de programa.", - "solution": "Capturarlo en la columna R del archivo.", - } - if not s: - return { - "line": line_num, - "col": "FECHA_AUT_SECON", - "msg": f"Error: (Col. S) El Tipo de Programa es: {q} y no está capturada la Fecha de autorización.", - "solution": "Capturarla en la columna S del archivo.", - } - return None - - -def check_es_prosec_num_aut(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]: - """Col T (SI/NO): valida solo el formato/validez de `ES_PROSEC`. - - Nota de negocio (Clarion): `NUM_AUT_PROSEC` ya no debe ser forzado por la opción - de `ES_PROSEC` (la carga no debe depender entre ambas columnas). - """ - t = (row.get("ES_PROSEC") or "").strip().upper() - u = (row.get("NUM_AUT_PROSEC") or "").strip() - if t and t not in ("SI", "NO"): - return { - "line": line_num, - "col": "ES_PROSEC", - "msg": f"Error: (Col. T) La opción de si Es Prosec? {t} no es valida.", - "solution": "Capturar una opción valida: SI, NO, o dejar el campo vacio (se asigna NO).", - } - return None - - -def check_vinculacion(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]: - """Col V: solo 0, 1 o 2 (o vacío → 0).""" - val = (row.get("VINCULACION") or "").strip() - if not val: - return None - if val not in ("0", "1", "2"): - return { - "line": line_num, - "col": "VINCULACION", - "msg": f"Error: (Col. V) La opción de Vinculación: {val} no es valida.", - "solution": "Capturar una opción valida: 0, 1, 2 o dejar el campo vacio (se asigna 0).", - } - return None - - -def check_es_empresa_certificada_registro(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]: - """Col W (SI/NO): si SI → Col X obligatoria; si no SI → Col X vacía.""" - w = (row.get("ES_EMPRESA_CERTIFICADA") or "").strip().upper() - x = (row.get("REGISTRO_EMPRESA_CERT") or "").strip() - if w and w not in ("SI", "NO"): - return { - "line": line_num, - "col": "ES_EMPRESA_CERTIFICADA", - "msg": f"Error: (Col. W) La opción de si Es Empresa Certificada? {w} no es valida.", - "solution": "Capturar una opción valida: SI, NO, o dejar el campo vacio (se asigna NO).", - } - if w == "SI" and not x: - return { - "line": line_num, - "col": "REGISTRO_EMPRESA_CERT", - "msg": "Error: (Col. X) Es Empresa Certificada? es SI y no está capturado el número de empresa certificada.", - "solution": "Capturar en la columna X el número de Empresa Certificada o cambiar la opción a NO en la columna W.", - } - if w != "SI" and x: - return { - "line": line_num, - "col": "REGISTRO_EMPRESA_CERT", - "msg": "Error: (Col. X) Es Empresa Certificada? no es SI y está capturado el número de empresa certificada.", - "solution": "Borrar la información de la columna X o cambiar la opción a SI en la columna W.", - } - return None - - -def check_transformador_submaq(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]: - """Col AF: primera letra T, S o N (Transformador, SubMaquila, Ninguno) o vacío → Ninguno.""" - val = (row.get("TRANSFORMA_SUBMAQ") or "").strip().upper() - if not val: - return None - first = val[:1] if val else "" - if first not in ("T", "S", "N"): - return { - "line": line_num, - "col": "TRANSFORMA_SUBMAQ", - "msg": f"Error: (Col. AF) La opción (SUBMAQUILA/TRANSFORMADOR/NINGUNO): {val} no es valida.", - "solution": "Capturar una opción valida: Transformador, SubMaquila, Ninguno o dejar el campo vacio (se asigna Ninguno).", - } - return None - - -def check_desfase(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]: - """Si COL_EXTRA (Col AH) tiene valor → advertencia de desfase.""" - val = (row.get("COL_EXTRA") or "").strip() - if not val: - return None - return { - "line": line_num, - "col": "COL_EXTRA", - "msg": "Advertencia: Podría existir un desfase en esta línea.", - "solution": "Revisar esta línea del archivo CSV y verificar cada campo esté en la posición correcta.", - "warning": True, - } - - -def parse_active(val: Optional[str]) -> bool: - if not val or not str(val).strip(): - return True - v = str(val).strip().lower() - if v in ("1", "true", "si", "sí", "yes", "s", "x"): - return True - if v in ("0", "false", "no", "n"): - return False - return True diff --git a/backend/api/v1/modules/a76/layouts_csv/clients_and_providers/common/csv_max_chars_for_headers.py b/backend/api/v1/modules/a76/layouts_csv/clients_and_providers/common/csv_max_chars_for_headers.py deleted file mode 100644 index 06ab1a52..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/clients_and_providers/common/csv_max_chars_for_headers.py +++ /dev/null @@ -1,42 +0,0 @@ -""" -Máximos de caracteres por columna canonical (plantilla client_providers) para sufijos en cabecera CSV. - -Debe mantenerse alineado con `common/mappers.py` → MAX_LEN y columnas String(n) en -`clients_and_providers.models`. Sin imports de SQLAlchemy ni Pydantic para poder cargar la plantilla sin arrancar la app. -""" -from typing import Dict - -CLIENT_PROVIDER_CSV_COLUMN_MAX_CHARS: Dict[str, int] = { - "SHORT_NAME": 10, - "NOMBRE": 256, - "RFC": 30, - "TAX_ID": 30, - "DIRECCION": 100, - "NUM_EXT": 20, - "CODIGO POSTAL": 15, - "COLONIA": 40, - "CIUDAD": 30, - "ESTADO": 30, - "PAIS": 3, - "TELEFONO": 30, - "FAX": 30, - "EMAIL": 100, - "CURP": 19, - "TIPO_PROGRAMA_SECON": 7, - "NUM_PROGRAMA_SECON": 40, - "NUM_AUT_PROSEC": 20, - "REGISTRO_EMPRESA_CERT": 40, - "INFORMACION_EXTRA": 399, - "CONTACTO": 50, - "MANUFACTURER_ID": 25, - "TAX_ID_PROGRAMS": 30, - "BROKER_EXPO": 6, - "BROKER_IMPO": 6, - "CLAVE_TRANSFER": 8, - "CLAVE_WEB": 40, - "RESPONSABLE": 80, - "POSICION": 30, - "INCOTERM": 19, - "VINCULACION": 1, - "TRANSFORMA_SUBMAQ": 1, -} diff --git a/backend/api/v1/modules/a76/layouts_csv/clients_and_providers/common/mappers.py b/backend/api/v1/modules/a76/layouts_csv/clients_and_providers/common/mappers.py deleted file mode 100644 index 030a6c6a..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/clients_and_providers/common/mappers.py +++ /dev/null @@ -1,223 +0,0 @@ -""" -Mapeo fila CSV → datos para ClientProvider, ClientProviderAddress y ClientProviderPrograms. -Paridad Clarion: columnas A–AG a modelos. - -El CSV se alimenta en base a las tablas/modelos: -- cp_data: atributos de ClientProvider (clients_and_providers). Claves = nombres de columna del modelo. -- address_data: atributos de ClientProviderAddress (clients_and_providers_address). Se asignan en tasks. -- programs_data: atributos de ClientProviderPrograms (clients_and_providers_programs). Se asignan en tasks. - -Las validaciones (validators) aplican reglas de negocio Clarion y respetan longitudes máximas de los modelos. -""" -import logging -from datetime import datetime -from decimal import Decimal -from typing import Dict, Any, Optional, Tuple - -from api.v1.modules.a76.clients_and_providers.models import ClientOrProviderEnum - -from .common_validators import ( - parse_client_or_provider, - parse_active, -) - -logger = logging.getLogger(__name__) - -MAX_LEN = { - "rfc": 30, - "tax_id": 30, - "name": 256, - "short_name": 10, - "curp": 19, - "responsible": 80, - "position": 30, - "incoterm": 19, - "email": 100, - "phone": 30, - "address": 100, - "postal_code": 15, - "city": 30, - "state": 30, - "country": 3, - "contact": 50, - "extra_information": 399, - "web_key": 40, - "program": 7, - "program_number": 40, - "prosec_authorization": 20, - "secon_authorization": 20, - "manufacturer_id": 25, - "broker": 6, - "import_broker": 6, - "transfer_key": 8, - "certified_company_registry": 40, - "neighborhood": 40, - "exterior_number": 20, - "fax": 30, -} - - -def _str_or_none(val: Any, max_len: Optional[int] = None) -> Optional[str]: - if val is None: - return None - s = str(val).strip() - if not s: - return None - if max_len and len(s) > max_len: - return s[:max_len] - return s - - -def _parse_date_to_yyyymmdd(val: Any) -> Optional[int]: - """Convierte fecha dd/mm/yyyy o similar a entero YYYYMMDD para secon_auth_date.""" - if not val or not str(val).strip(): - return None - s = str(val).strip() - for fmt in ("%d/%m/%Y", "%Y-%m-%d", "%d-%m-%Y", "%Y/%m/%d"): - try: - d = datetime.strptime(s[:10], fmt) - return d.year * 10000 + d.month * 100 + d.day - except ValueError: - continue - return None - - -def row_to_client_provider_data( - row_norm: Dict[str, Any], tenant_id: int, company_id: int -) -> Tuple[Dict[str, Any], Optional[Dict[str, Any]], Optional[Dict[str, Any]]]: - """ - Mapea fila normalizada a datos para ClientProvider, ClientProviderAddress y ClientProviderPrograms. - Devuelve (cp_data, address_data_or_none, programs_data_or_none). - Identificadores fiscales separados: RFC (nacional) → cp.rfc; TAX-ID (extranjero) → cp.tax_id. - Compatibilidad: archivos legacy de columna única traían el TAX-ID en la columna RFC; si el registro es - extranjero y no hay columna TAX_ID dedicada, ese valor se rutea a tax_id (salvaguarda no silenciosa). - """ - procedencia = _str_or_none(row_norm.get("PROCEDENCIA"), 1) - if procedencia: - procedencia = procedencia.upper()[:1] - is_foreign = procedencia == "E" - - rfc_col = _str_or_none(row_norm.get("RFC"), MAX_LEN["rfc"]) - tax_id_col = _str_or_none(row_norm.get("TAX_ID"), MAX_LEN["tax_id"]) - tax_id_programs_col = _str_or_none(row_norm.get("TAX_ID_PROGRAMS"), MAX_LEN["tax_id"]) - - rfc_final = rfc_col - tax_id_final = tax_id_col - if is_foreign and not tax_id_final: - # Salvaguarda legacy: extranjero sin columna TAX_ID dedicada → el TAX-ID estaba en RFC (o AB). - if rfc_col: - tax_id_final = rfc_col - rfc_final = None - logger.info( - "CSV clients_providers: TAX-ID tomado de columna RFC (archivo legacy de columna única)" - ) - elif tax_id_programs_col: - tax_id_final = tax_id_programs_col - - short_name = _str_or_none(row_norm.get("SHORT_NAME"), MAX_LEN["short_name"]) - if not rfc_final and not tax_id_final and not short_name: - return ({}, None, None) - - client_or_provider = parse_client_or_provider(row_norm.get("TIPO")) or ClientOrProviderEnum.BOTH - - # Vinculación 0/1/2 → string - vinc = (row_norm.get("VINCULACION") or "").strip() - linking = None - if vinc in ("0", "1", "2"): - linking = vinc - - # Transformador/SubMaquila: primera letra T/S/N - trans = (row_norm.get("TRANSFORMA_SUBMAQ") or "").strip().upper()[:1] - transform_subassembly = trans if trans in ("T", "S", "N") else None - - cp_data = { - "tenant_id": tenant_id, - "company_id": company_id, - "rfc": rfc_final or None, - "tax_id": tax_id_final or None, - "name": _str_or_none(row_norm.get("NOMBRE"), MAX_LEN["name"]), - "short_name": short_name, - "curp": _str_or_none(row_norm.get("CURP"), MAX_LEN["curp"]), - "client_or_provider": client_or_provider, - "type_nat_foreign": procedencia, - "linking": linking, - "transform_subassembly": transform_subassembly, - "extra_information": _str_or_none(row_norm.get("INFORMACION_EXTRA"), MAX_LEN["extra_information"]), - "web_key": _str_or_none(row_norm.get("CLAVE_WEB"), MAX_LEN["web_key"]), - "responsible": _str_or_none(row_norm.get("RESPONSABLE"), MAX_LEN["responsible"]), - "position": _str_or_none(row_norm.get("POSICION"), MAX_LEN["position"]), - "incoterm": _str_or_none(row_norm.get("INCOTERM"), MAX_LEN["incoterm"]), - "is_active": parse_active(row_norm.get("ACTIVO")), - } - - # Address - email = _str_or_none(row_norm.get("EMAIL"), MAX_LEN["email"]) - phone = _str_or_none(row_norm.get("TELEFONO"), MAX_LEN["phone"]) - address_str = _str_or_none(row_norm.get("DIRECCION"), MAX_LEN["address"]) - address_data = None - if email or phone or address_str or _str_or_none(row_norm.get("CODIGO POSTAL")) or _str_or_none(row_norm.get("COLONIA")) or _str_or_none(row_norm.get("NUM_EXT")): - address_data = { - "tenant_id": tenant_id, - "company_id": company_id, - "streets": address_str, - "exterior_number": _str_or_none(row_norm.get("NUM_EXT"), MAX_LEN["exterior_number"]), - "neighborhood": _str_or_none(row_norm.get("COLONIA"), MAX_LEN["neighborhood"]), - "postal_code": _str_or_none(row_norm.get("CODIGO POSTAL"), MAX_LEN["postal_code"]), - "city": _str_or_none(row_norm.get("CIUDAD"), MAX_LEN["city"]), - "state": _str_or_none(row_norm.get("ESTADO"), MAX_LEN["state"]), - "country": _str_or_none(row_norm.get("PAIS"), MAX_LEN["country"]), - "phone": phone, - "fax_number": _str_or_none(row_norm.get("FAX"), MAX_LEN["fax"]), - "email": email, - "contact": _str_or_none(row_norm.get("CONTACTO"), MAX_LEN["contact"]), - } - - # Programs (SECON, PROSEC, empresa certificada, etc.) - tipo_prog = _str_or_none(row_norm.get("TIPO_PROGRAMA_SECON"), MAX_LEN["program"]) - if tipo_prog and tipo_prog.lower() == "ninguno": - tipo_prog = None - num_prog = _str_or_none(row_norm.get("NUM_PROGRAMA_SECON"), MAX_LEN["program_number"]) - secon_auth = _str_or_none(row_norm.get("NUM_PROGRAMA_SECON"), MAX_LEN["secon_authorization"]) - fecha_secon = _parse_date_to_yyyymmdd(row_norm.get("FECHA_AUT_SECON")) - es_prosec = (row_norm.get("ES_PROSEC") or "").strip().upper() - prosec_val = "1" if es_prosec == "SI" else ("0" if es_prosec else None) - num_aut_prosec = _str_or_none(row_norm.get("NUM_AUT_PROSEC"), MAX_LEN["prosec_authorization"]) - es_cert = (row_norm.get("ES_EMPRESA_CERTIFICADA") or "").strip().upper() - is_certified = "S" if es_cert == "SI" else ("N" if es_cert else None) - reg_cert = _str_or_none(row_norm.get("REGISTRO_EMPRESA_CERT"), MAX_LEN["certified_company_registry"]) - vinc_prop = row_norm.get("VINCULACION") - applied_proportion = None - if vinc_prop is not None and str(vinc_prop).strip() in ("0", "1", "2"): - try: - applied_proportion = Decimal(str(vinc_prop).strip()) - except Exception: - pass - - programs_data = None - if ( - tipo_prog or num_prog or fecha_secon is not None or prosec_val or num_aut_prosec - or is_certified or reg_cert - or _str_or_none(row_norm.get("MANUFACTURER_ID")) - or _str_or_none(row_norm.get("BROKER_EXPO")) - or _str_or_none(row_norm.get("BROKER_IMPO")) - or _str_or_none(row_norm.get("CLAVE_TRANSFER")) - or applied_proportion is not None - ): - # Identificador fiscal solo en ClientProvider.rfc (columna única) - programs_data = { - "program": tipo_prog[:7] if tipo_prog else None, - "program_number": num_prog, - "secon_authorization": secon_auth, - "secon_auth_date": fecha_secon, - "prosec": prosec_val, - "prosec_authorization": num_aut_prosec, - "is_certified_company": is_certified, - "certified_company_registry": reg_cert, - "manufacturer_id": _str_or_none(row_norm.get("MANUFACTURER_ID"), MAX_LEN["manufacturer_id"]), - "broker": _str_or_none(row_norm.get("BROKER_EXPO"), MAX_LEN["broker"]), - "import_broker": _str_or_none(row_norm.get("BROKER_IMPO"), MAX_LEN["import_broker"]), - "transfer_key": _str_or_none(row_norm.get("CLAVE_TRANSFER"), MAX_LEN["transfer_key"]), - "applied_proportion": applied_proportion, - } - - return (cp_data, address_data, programs_data) diff --git a/backend/api/v1/modules/a76/layouts_csv/clients_and_providers/routes.py b/backend/api/v1/modules/a76/layouts_csv/clients_and_providers/routes.py deleted file mode 100644 index 2fbf06d1..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/clients_and_providers/routes.py +++ /dev/null @@ -1,254 +0,0 @@ -""" -Rutas de importación CSV para Clientes y Proveedores. -Mismo flujo que a76.imports: upload → scan → status (polling) → commit. -""" -import json -import logging -import os -from uuid import uuid4 - -from fastapi import APIRouter, File, HTTPException, Query, UploadFile, Depends -from sqlalchemy.orm import Session -from typing import Dict, Any, Optional - -from core.celery_app import celery_app -from core.database import get_core_db -from core.security import get_current_user, validate_access_to_resource -from api.v1.modules.core.tasks_tracking import track_and_dispatch - -from ..common.track_commit_dispatch import dispatch_tracked_layouts_csv_commit -from ..common.responses import normalize_commit_status_payload -from .schemas import ImportJobResponse -from .tasks import ( - scan_file, - insert_valid_rows, - JOB_TYPE, - CP_IMPORT_META_PREFIX, - CP_IMPORT_REDIS_TTL, -) -from ..common import storage as common_storage -from ..common.error_csv import download_scan_errors_csv_stream - -router = APIRouter() -logger = logging.getLogger(__name__) - - -def _assert_cp_csv_job_access( - db: Session, - job_id: str, - current_user: Dict[str, Any], -) -> None: - """ - Exige autenticación + csv_upload.process para la compañía asociada al job - (task_runs, meta Redis del job, o meta del layout_import_job_id en commits). - """ - from api.v1.modules.core.tasks_tracking.models import TaskRun - - company_id: Optional[int] = None - row = db.query(TaskRun).filter(TaskRun.task_id == job_id).first() - if row is not None and row.company_id is not None: - company_id = int(row.company_id) - - r = _get_redis() - if company_id is None: - raw = r.get(f"{CP_IMPORT_META_PREFIX}{job_id}") - if raw: - meta = json.loads(raw.decode("utf-8")) - cid = meta.get("company_id") - if cid is not None: - company_id = int(cid) - - if company_id is None and row is not None and row.meta_payload: - layout_jid = row.meta_payload.get("layout_import_job_id") - if layout_jid: - raw2 = r.get(f"{CP_IMPORT_META_PREFIX}{layout_jid}") - if raw2: - meta2 = json.loads(raw2.decode("utf-8")) - cid2 = meta2.get("company_id") - if cid2 is not None: - company_id = int(cid2) - - if company_id is None: - raise HTTPException(status_code=403, detail="Sin acceso a este job") - try: - validate_access_to_resource( - db, company_id, current_user, ["csv_upload.process"] - ) - except HTTPException: - raise - except Exception as e: - logger.error("CP import: job access validation failed: %s", e) - raise HTTPException(status_code=403, detail="Sin acceso a este job") from None - - -def _get_redis(): - import redis - url = os.getenv("VALKEY_URL", os.getenv("REDIS_URL", "redis://valkey:6379/0")) - return redis.Redis.from_url(url, decode_responses=False) - - -@router.post("/upload", response_model=ImportJobResponse) -async def upload_import_file( - file: UploadFile = File(...), - company_id: int = Query(..., description="Company ID"), - actualizar: bool = Query( - False, - description="Modo Agregar/Actualizar (ACT); si True, validación parcial para claves existentes en catálogo", - ), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """ - Fase 1: Subir CSV, guardar en Redis, encolar tarea de escaneo. - """ - try: - tenant_id = validate_access_to_resource(db, company_id, current_user, ["csv_upload.process"]) - except Exception as e: - logger.error(f"CP import: access validation failed: {e}") - raise HTTPException(status_code=403, detail="Invalid company access or missing permissions") - - if not file.filename or not file.filename.lower().endswith(".csv"): - raise HTTPException(status_code=400, detail="Solo se permiten archivos .csv") - - job_id = str(uuid4()) - contents = await file.read() - - meta_data = { - "tenant_id": tenant_id, - "company_id": company_id, - "user_id": current_user.get("id"), - "template_id": "client_providers", - "actualizar": actualizar, - } - - try: - common_storage.store_import_file( - JOB_TYPE, - job_id, - contents, - meta_data, - tenant_id=int(tenant_id), - company_id=company_id, - ttl=CP_IMPORT_REDIS_TTL, - log_label="CP import", - ) - except common_storage.ImportStoreError as e: - logger.error(f"CP import: store error: {e}") - raise HTTPException(status_code=500, detail="No se pudo encolar el archivo.") - except Exception as e: - logger.error(f"CP import: Redis store error: {e}") - raise HTTPException(status_code=500, detail="No se pudo encolar el archivo.") - - track_and_dispatch( - db=db, - task=scan_file, - tenant_id=int(tenant_id), - company_id=company_id, - requested_by_user=current_user.get("preferred_username") - or current_user.get("email") - or current_user.get("sub"), - task_name="clients_and_providers_scan_file", - task_group="layouts_csv", - task_origin="a76/layouts_csv/clients_and_providers/upload", - args=[job_id], - task_id=job_id, - ) - - return ImportJobResponse( - job_id=job_id, - status="queued", - message="Archivo subido. Escaneo iniciado.", - ) - - -@router.get("/{job_id}/status") -async def get_import_status( - job_id: str, - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """ - Polling: estado del escaneo o del commit. - """ - _assert_cp_csv_job_access(db, job_id, current_user) - task_result = celery_app.AsyncResult(job_id) - - if task_result.state == "PENDING": - return {"status": "processing", "progress": 0} - if task_result.state == "PROGRESS": - info = (task_result.info or {}) - return { - "status": "processing", - "progress": info.get("current", 0), - "total": info.get("total", 0), - } - if task_result.state == "SUCCESS": - result = task_result.result - if isinstance(result, dict) and "status" in result: - return normalize_commit_status_payload(result) - return {"status": "finished", "result": result} - - # A veces Celery tiene el result disponible pero state aún no es SUCCESS; si el result es éxito, devolverlo - result = getattr(task_result, "result", None) - if isinstance(result, dict) and result.get("status") in ("finished", "warning"): - return normalize_commit_status_payload(result) - - logger.warning("CP import task %s failed: state=%s", job_id, task_result.state) - err_msg = None - tb = getattr(task_result, "traceback", None) - if tb and isinstance(tb, str): - lines = [l.strip() for l in tb.strip().split("\n") if l.strip()] - if lines: - err_msg = lines[-1] - if not err_msg: - try: - exc = task_result.get(propagate=False) - if exc is not None: - err_msg = str(exc) - except Exception: - pass - if not err_msg and result is not None: - if not isinstance(result, dict): - err_msg = str(result) - elif result.get("error") or result.get("message"): - err_msg = result.get("error") or result.get("message") - return {"status": "failed", "error": err_msg or "Task failed"} - - -@router.post("/{job_id}/commit") -async def commit_import_job( - job_id: str, - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """ - Fase 2: Usuario confirma; se encola la inserción de filas válidas. - """ - r = _get_redis() - commit_id = dispatch_tracked_layouts_csv_commit( - db=db, - current_user=current_user, - redis_client=r, - layout_job_id=job_id, - meta_redis_key=f"{CP_IMPORT_META_PREFIX}{job_id}", - celery_task=insert_valid_rows, - task_name="clients_and_providers_insert_valid_rows", - task_origin="a76/layouts_csv/clients_and_providers/commit", - args=[job_id], - required_permissions=["csv_upload.process"], - ) - return { - "status": "committing", - "message": "Inserción iniciada.", - "commit_job_id": commit_id, - } - - -@router.get("/{job_id}/errors/scan-csv") -async def download_scan_errors_csv( - job_id: str, - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - _assert_cp_csv_job_access(db, job_id, current_user) - return download_scan_errors_csv_stream("cp", job_id) diff --git a/backend/api/v1/modules/a76/layouts_csv/clients_and_providers/schemas.py b/backend/api/v1/modules/a76/layouts_csv/clients_and_providers/schemas.py deleted file mode 100644 index 98c39a98..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/clients_and_providers/schemas.py +++ /dev/null @@ -1,25 +0,0 @@ -from pydantic import BaseModel -from typing import Optional - - -class ImportJobResponse(BaseModel): - job_id: str - status: str - message: str - - -class CommitRequest(BaseModel): - pass # no body needed for single model - - -class ImportJobStatus(BaseModel): - status: str - job_id: str - total_rows: Optional[int] = 0 - error_count: Optional[int] = 0 - valid_rows: Optional[int] = 0 - error: Optional[str] = None - inserted: Optional[int] = 0 - skipped_invalid: Optional[int] = 0 - skipped_missing_fk: Optional[int] = 0 - skipped_details: Optional[list] = None diff --git a/backend/api/v1/modules/a76/layouts_csv/clients_and_providers/tasks.py b/backend/api/v1/modules/a76/layouts_csv/clients_and_providers/tasks.py deleted file mode 100644 index b0b9de12..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/clients_and_providers/tasks.py +++ /dev/null @@ -1,385 +0,0 @@ -""" -Tareas Celery para importación CSV de Clientes y Proveedores. -Flujo: scan_file (validación) → insert_valid_rows (commit). -Usa layouts_csv.common (storage, normalize, meta, responses, csv_reader). -""" -import json -import logging -import os -from typing import Dict, Any, Optional, List, Set - -from core.celery_app import celery_app -from core.database import CoreSessionLocal - -from ..common import storage as common_storage -from ..common import normalize as common_normalize -from ..common import meta as common_meta -from ..common import responses as common_responses -from ..common import csv_reader as common_csv_reader -from .template_config import row_from_template -from .validators import validate_row_client_provider -from .common.mappers import row_to_client_provider_data -from .common.common_validators import short_name_import_key - -logger = logging.getLogger(__name__) - -JOB_TYPE = "cp" - -# Para routes.py -CP_IMPORT_FILE_PREFIX = "cp_import_file:" -CP_IMPORT_META_PREFIX = "cp_import_meta:" -CP_IMPORT_ERROR_LINES_PREFIX = "cp_import_error_lines:" -CP_IMPORT_REDIS_TTL = common_storage.IMPORT_REDIS_TTL - - -def _do_scan(job_id: str, progress_callback: Optional[Any] = None) -> Dict[str, Any]: - file_path = common_storage.ensure_file_from_redis(JOB_TYPE, job_id, "CP import") - if not file_path: - return {"status": "failed", "error": "Archivo no encontrado (expirado o no subido). Sube de nuevo."} - common_storage.ensure_meta_from_redis(JOB_TYPE, job_id, file_path, "CP import") - - error_path = common_storage.error_path_for_job(JOB_TYPE, job_id) - - try: - total_rows = common_csv_reader.count_csv_rows(file_path) - except Exception as e: - return {"status": "failed", "error": str(e)} - - try: - tenant_id, company_id = common_meta.require_tenant_context(file_path) - except ValueError as e: - return {"status": "failed", "error": str(e)} - - meta = common_meta.load_meta(file_path) or {} - actualizar = meta.get("actualizar", False) - existing_short_names: Set[str] = set() - if actualizar: - try: - with CoreSessionLocal() as session: - from api.v1.modules.a76.clients_and_providers.models import ClientProvider - for cp in ( - session.query(ClientProvider) - .filter( - ClientProvider.tenant_id == tenant_id, - ClientProvider.company_id == company_id, - ) - .all() - ): - sn = short_name_import_key(cp.short_name) - if sn: - existing_short_names.add(sn) - except Exception as e: - logger.warning("CP import: could not load existing short_names for ACT: %s", e) - - error_count = 0 - processed_rows = 0 - errors_detail: List[Dict[str, Any]] = [] - error_lines_list: List[int] = [] - - try: - with open(error_path, "w", encoding="utf-8") as f_err: - for i, row in common_csv_reader.iter_csv_rows(file_path): - if progress_callback: - progress_callback(i, total_rows, error_count) - - row_norm = row_from_template(row, common_normalize.normalize_header) - row_errors = validate_row_client_provider( - row_norm, i, - actualizar=actualizar, - existing_short_names=existing_short_names if actualizar else None, - ) - if row_errors: - has_blocking_error = any(not e.get("warning", False) for e in row_errors) - if has_blocking_error: - error_count += 1 - error_lines_list.append(i) - for e in row_errors: - if not e.get("warning", False): - f_err.write(json.dumps(e) + "\n") - if len(errors_detail) < 500: - for e in row_errors: - if len(errors_detail) >= 500: - break - errors_detail.append({ - "line": e["line"], - "col": e.get("col", ""), - "msg": e.get("msg", ""), - "solution": e.get("solution", ""), - "warning": bool(e.get("warning", False)), - }) - processed_rows += 1 - - if error_lines_list: - common_storage.store_error_lines(JOB_TYPE, job_id, error_lines_list) - except Exception as e: - logger.error("CP import scan failed: %s", e) - return {"status": "failed", "error": str(e)} - - return common_responses.scan_result(job_id, processed_rows, error_count, errors_detail) - - -@celery_app.task(bind=True) -def scan_file(self, job_id: str, config: str = None): - logger.info("CP import: starting scan for job %s", job_id) - - def on_progress(current: int, total: int, errors: int) -> None: - self.update_state(state="PROGRESS", meta={"current": current, "total": total, "errors": errors}) - - return _do_scan(job_id, progress_callback=on_progress) - - -def _do_commit(job_id: str) -> Dict[str, Any]: - file_path = common_storage.ensure_file_from_redis(JOB_TYPE, job_id, "CP import") - if not file_path: - alt_path = common_storage.file_path_for_job(JOB_TYPE, job_id) - if not os.path.exists(alt_path): - return {"status": "failed", "error": "Archivo no encontrado (expirado). Sube y confirma de nuevo."} - file_path = alt_path - else: - common_storage.ensure_meta_from_redis(JOB_TYPE, job_id, file_path, "CP import") - - error_path = common_storage.error_path_for_job(JOB_TYPE, job_id) - error_lines = common_storage.get_error_lines(JOB_TYPE, job_id, error_path) - - try: - tenant_id, company_id = common_meta.require_tenant_context(file_path) - except ValueError as e: - return {"status": "failed", "error": str(e)} - - meta = common_meta.load_meta(file_path) or {} - actualizar = meta.get("actualizar", False) - existing_short_names: Set[str] = set() - if actualizar: - from api.v1.modules.a76.clients_and_providers.models import ClientProvider - try: - with CoreSessionLocal() as session: - for cp in ( - session.query(ClientProvider) - .filter( - ClientProvider.tenant_id == tenant_id, - ClientProvider.company_id == company_id, - ) - .all() - ): - sn = short_name_import_key(cp.short_name) - if sn: - existing_short_names.add(sn) - except Exception as e: - logger.warning("CP commit: could not load existing short_names for ACT: %s", e) - - from api.v1.modules.a76.clients_and_providers.models import ( - ClientProvider, - ClientProviderAddress, - ClientProviderPrograms, - ) - - inserted_count = 0 - updated_count = 0 - skipped_invalid = 0 - skipped_details: List[Dict[str, Any]] = [] - meta_path = common_meta.get_meta_path(file_path) - - try: - with CoreSessionLocal() as session: - existing_by_short_name: Dict[str, ClientProvider] = {} - for cp in ( - session.query(ClientProvider) - .filter( - ClientProvider.tenant_id == tenant_id, - ClientProvider.company_id == company_id, - ) - .all() - ): - sn_key = short_name_import_key(cp.short_name) - if sn_key: - existing_by_short_name[sn_key] = cp - - for i, row in common_csv_reader.iter_csv_rows(file_path): - if i in error_lines: - continue - - row_norm = row_from_template(row, common_normalize.normalize_header) - row_errors = validate_row_client_provider( - row_norm, i, - actualizar=actualizar, - existing_short_names=existing_short_names if actualizar else None, - ) - if row_errors and any(not e.get("warning", False) for e in row_errors): - skipped_invalid += 1 - blocking_errors = [e for e in row_errors if not e.get("warning", False)] - for blocking in blocking_errors: - skipped_details.append({ - "line": i, - "reason": f"{blocking.get('col', '')}: {blocking.get('msg', '')}", - "solution": blocking.get("solution", ""), - }) - continue - - cp_data, address_data, programs_data = row_to_client_provider_data(row_norm, tenant_id, company_id) - if not cp_data: - skipped_invalid += 1 - skipped_details.append( - { - "line": i, - "reason": "Fila sin RFC/TAX-ID ni Clave", - "solution": "Capturar en el CSV el RFC (columna RFC), el TAX-ID (columna TAX_ID) o la clave del cliente/proveedor (columna SHORT_NAME).", - } - ) - continue - - sn_key = short_name_import_key(cp_data.get("short_name")) - if not sn_key: - skipped_invalid += 1 - skipped_details.append( - { - "line": i, - "reason": "Clave (SHORT_NAME) requerida", - "solution": "Capturar en la columna SHORT_NAME la clave del cliente/proveedor.", - } - ) - continue - cp_data["short_name"] = sn_key - - existing = existing_by_short_name.get(sn_key) - if existing: - for k, v in cp_data.items(): - if k in ("tenant_id", "company_id"): - continue - if v is None or (isinstance(v, str) and not v.strip()): - existing_val = getattr(existing, k, None) - if existing_val is not None: - cp_data[k] = existing_val - for k, v in cp_data.items(): - if k not in ("tenant_id", "company_id"): - setattr(existing, k, v) - session.add(existing) - updated_count += 1 - if address_data and existing.address: - addr = existing.address - for k, v in address_data.items(): - if k not in ("tenant_id", "company_id") and v is not None: - setattr(addr, k, v) - session.add(addr) - elif address_data: - addr = ClientProviderAddress( - client_id=existing.id, - tenant_id=tenant_id, - company_id=company_id, - streets=address_data.get("streets"), - postal_code=address_data.get("postal_code"), - city=address_data.get("city"), - state=address_data.get("state"), - country=address_data.get("country"), - phone=address_data.get("phone"), - email=address_data.get("email"), - contact=address_data.get("contact"), - exterior_number=address_data.get("exterior_number"), - neighborhood=address_data.get("neighborhood"), - fax_number=address_data.get("fax_number"), - ) - session.add(addr) - if programs_data: - prog = session.query(ClientProviderPrograms).filter( - ClientProviderPrograms.client_id == existing.id, - ).first() - if prog: - for k, v in programs_data.items(): - if v is not None: - setattr(prog, k, v) - session.add(prog) - else: - prog = ClientProviderPrograms( - client_id=existing.id, - tenant_id=tenant_id, - company_id=company_id, - **{k: v for k, v in programs_data.items() if v is not None}, - ) - session.add(prog) - else: - new_cp = ClientProvider(**cp_data) - session.add(new_cp) - session.flush() - existing_by_short_name[sn_key] = new_cp - inserted_count += 1 - if address_data: - addr = ClientProviderAddress( - client_id=new_cp.id, - tenant_id=tenant_id, - company_id=company_id, - streets=address_data.get("streets"), - postal_code=address_data.get("postal_code"), - city=address_data.get("city"), - state=address_data.get("state"), - country=address_data.get("country"), - phone=address_data.get("phone"), - email=address_data.get("email"), - contact=address_data.get("contact"), - exterior_number=address_data.get("exterior_number"), - neighborhood=address_data.get("neighborhood"), - fax_number=address_data.get("fax_number"), - ) - session.add(addr) - if programs_data: - prog = ClientProviderPrograms( - client_id=new_cp.id, - tenant_id=tenant_id, - company_id=company_id, - **{k: v for k, v in programs_data.items() if v is not None}, - ) - session.add(prog) - - try: - session.commit() - except Exception as db_err: - session.rollback() - logger.error("CP import DB error: %s", db_err) - return {"status": "failed", "error": str(db_err)} - - except Exception as e: - logger.exception("CP import task failed") - return {"status": "failed", "error": str(e)} - - common_storage.cleanup_import_job( - JOB_TYPE, job_id, - file_path=file_path, - error_path=error_path, - meta_path=meta_path, - ) - - if inserted_count == 0 and updated_count == 0 and skipped_invalid > 0: - return { - "status": "warning", - "inserted": 0, - "updated": 0, - "skipped_invalid": skipped_invalid, - "skipped_duplicate": 0, - "skipped_missing_fk": 0, - "skipped_details": skipped_details, - "message": f"No se insertaron ni actualizaron registros. {skipped_invalid} rechazados.", - } - if inserted_count == 0 and updated_count == 0: - return { - "status": "failed", - "error": "No hay registros válidos en el archivo CSV", - "inserted": 0, - "updated": 0, - "skipped_invalid": skipped_invalid, - "skipped_duplicate": 0, - "skipped_missing_fk": 0, - "skipped_details": skipped_details, - } - return { - "status": "finished", - "inserted": inserted_count, - "updated": updated_count, - "skipped_invalid": skipped_invalid, - "skipped_duplicate": 0, - "skipped_missing_fk": 0, - "skipped_details": skipped_details, - } - - -@celery_app.task(bind=True) -def insert_valid_rows(self, job_id: str): - logger.info("CP import: starting commit for job %s", job_id) - return _do_commit(job_id) diff --git a/backend/api/v1/modules/a76/layouts_csv/clients_and_providers/template_config.py b/backend/api/v1/modules/a76/layouts_csv/clients_and_providers/template_config.py deleted file mode 100644 index ce333046..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/clients_and_providers/template_config.py +++ /dev/null @@ -1,168 +0,0 @@ -""" -Configuración de plantilla CSV para Clientes y Proveedores (EstructuraCatClienteProv.xls). -Layout Clarion: Col A = PROCEDENCIA (E/N), B = TIPO (C/P/A), C = CLAVE (máx 8), D = NOMBRE, E = RFC, F–AG. -Solo se leen columnas definidas aquí; el resto se ignora. El parseo es por nombre de cabecera (no posicional). -RFC y TAX-ID viven en columnas separadas: "RFC" (nacional) y "TAX_ID" (identificador fiscal extranjero). -Compatibilidad: archivos legacy de columna única (TAX-ID en "RFC") se rutean a tax_id según procedencia (ver mappers). -Correspondencia Clarion → canonical: A=PROCEDENCIA, B=TIPO, C=SHORT_NAME, D=NOMBRE, E=RFC, F=CALLES(DIRECCION), -G=NUM_EXT, H=CODIGO POSTAL, I=COLONIA, J=CIUDAD, K=ESTADO, L=PAIS, M=TELEFONO, N=FAX, O=EMAIL, P=CURP, -Q=TIPO_PROGRAMA_SECON, R=NUM_PROGRAMA_SECON, S=FECHA_AUT_SECON, T=ES_PROSEC, U=NUM_AUT_PROSEC, V=VINCULACION, -W=ES_EMPRESA_CERTIFICADA, X=REGISTRO_EMPRESA_CERT, Y=INFORMACION_EXTRA, Z=CONTACTO, AA=MANUFACTURER_ID, -AB=TAX_ID_PROGRAMS, AC=BROKER_EXPO, AD=BROKER_IMPO, AE=CLAVE_TRANSFER, AF=TRANSFORMA_SUBMAQ, AG=CLAVE_WEB, -AH=COL_EXTRA (desfase). -""" - -from typing import Dict, List, Any, Optional - -from ..common.cell_value import cell_to_str -from ..common.inject_csv_labels_en import inject_en_labels_into_template_columns -from ..common.template_locale import ( - merge_download_header_into_lookup, - merge_labels_into_lookup, -) -from .common.csv_max_chars_for_headers import CLIENT_PROVIDER_CSV_COLUMN_MAX_CHARS - -TEMPLATE_COLUMNS: Dict[str, List[Dict[str, Any]]] = { - "client_providers": [ - # Col A - Procedencia (E=Extranjero, N=Nacional). Manual: select N/E (≤3). Ver CSV_HEADER_HINTS.md - { - "canonical": "PROCEDENCIA", - "aliases": ["TIPO PROCEDENCIA", "EXTranjero/Nacional", "E/N"], - "csv_hint": {"kind": "enum_codes", "codes": ["N", "E"]}, - }, - # Col B - Tipo Cliente (C/P/A). Manual: client/provider/both (≤3). Ver CSV_HEADER_HINTS.md - { - "canonical": "TIPO", - "aliases": ["CLIENT_OR_PROVIDER", "TIPO ENTIDAD", "CLIENTE O PROVEEDOR"], - "csv_hint": {"kind": "enum_codes", "codes": ["C", "P", "A"]}, - }, - # Col C - Clave cliente/proveedor (máx 8 Clarion) - {"canonical": "SHORT_NAME", "aliases": ["CLAVE", "CLAVE CORTA", "NOMBRE CORTO", "SIGLAS"]}, - # Col D - Nombre - {"canonical": "NOMBRE", "aliases": ["RAZON SOCIAL", "NAME", "RAZON SOCIAL O NOMBRE"]}, - # Col E - RFC (solo RFC mexicano / nacional) - {"canonical": "RFC", "aliases": ["RFC NACIONAL"]}, - # Col E2 - TAX-ID dedicado (identificador fiscal extranjero), separado del RFC - {"canonical": "TAX_ID", "aliases": ["TAXID", "TAX-ID", "IDENTIFICADOR FISCAL", "IDENTIFICACION FISCAL"]}, - # Col F - Calles - {"canonical": "DIRECCION", "aliases": ["CALLES", "DOMICILIO", "DIRECCION FISCAL", "CALLE"]}, - # Col G - Número exterior - {"canonical": "NUM_EXT", "aliases": ["NUM EXTERIOR", "NUMERO EXTERIOR", "NO EXT"]}, - # Col H - Código postal - {"canonical": "CODIGO POSTAL", "aliases": ["CODIGOPOSTAL", "CP", "C.P."]}, - # Col I - Colonia - {"canonical": "COLONIA", "aliases": []}, - # Col J - Ciudad - {"canonical": "CIUDAD", "aliases": ["MUNICIPIO"]}, - # Col K - Estado - {"canonical": "ESTADO", "aliases": []}, - # Col L - País M3 - {"canonical": "PAIS", "aliases": ["COUNTRY", "PAIS M3"]}, - # Col M - Teléfono - {"canonical": "TELEFONO", "aliases": ["PHONE", "TEL", "TELEFONO CONTACTO"]}, - # Col N - Fax - {"canonical": "FAX", "aliases": ["NUMERO DE FAX", "FAX NUMBER"]}, - # Col O - Email - {"canonical": "EMAIL", "aliases": ["CORREO", "E-MAIL", "CORREO ELECTRONICO"]}, - # Col P - CURP - {"canonical": "CURP", "aliases": []}, - # Col Q - Tipo programa SECON - {"canonical": "TIPO_PROGRAMA_SECON", "aliases": ["PROGRAMA", "TIPO PROGRAMA SECON", "TIPO PROGRAMA"]}, - # Col R - Número programa SECON - {"canonical": "NUM_PROGRAMA_SECON", "aliases": ["NUM PROGRAMA SECON", "NUMERO PROGRAMA"]}, - # Col S - Fecha autorización SECON - {"canonical": "FECHA_AUT_SECON", "aliases": ["FECHA AUT SECON", "FECHA AUTORIZACION SECON"]}, - # Col T - Es Prosec? - {"canonical": "ES_PROSEC", "aliases": ["ES PROSEC", "PROSEC"]}, - # Col U - Número autorización PROSEC - {"canonical": "NUM_AUT_PROSEC", "aliases": ["NUM AUT PROSEC", "NUMERO AUTORIZACION PROSEC"]}, - # Col V - Vinculación (0/1/2) - {"canonical": "VINCULACION", "aliases": []}, - # Col W - Es Empresa Certificada? - {"canonical": "ES_EMPRESA_CERTIFICADA", "aliases": ["ES EMPRESA CERTIFICADA", "EMPRESA CERTIFICADA"]}, - # Col X - Registro empresa certificada - {"canonical": "REGISTRO_EMPRESA_CERT", "aliases": ["REGISTRO EMPRESA CERTIFICADA", "REGISTRO EMP CERT"]}, - # Col Y - Información extra - {"canonical": "INFORMACION_EXTRA", "aliases": ["INFORMACION EXTRA", "INFO EXTRA"]}, - # Col Z - Contacto - {"canonical": "CONTACTO", "aliases": ["CONTACT", "PERSONA CONTACTO"]}, - # Col AA - Manufacturer ID - {"canonical": "MANUFACTURER_ID", "aliases": ["MANUFACTURERID", "MANUFACTURER ID"]}, - # Col AB - Tax ID (programas). Alias "TAX ID" se quitó para no colisionar con la columna TAX_ID dedicada. - {"canonical": "TAX_ID_PROGRAMS", "aliases": ["TAXID PROGRAMS"]}, - # Col AC - Broker exportación - {"canonical": "BROKER_EXPO", "aliases": ["BROKER EXPO", "BROKER EXPORTACION"]}, - # Col AD - Broker importación - {"canonical": "BROKER_IMPO", "aliases": ["BROKER IMPO", "BROKER IMPORTACION"]}, - # Col AE - Clave transfer - {"canonical": "CLAVE_TRANSFER", "aliases": ["CLAVE TRANSFER", "TRANSFER KEY"]}, - # Col AF - Transformador/SubMaquila/Ninguno (T/S/N) - {"canonical": "TRANSFORMA_SUBMAQ", "aliases": ["TRANSFORMADOR SUBMAQUILA", "TRASFORMA SUBMAQ"]}, - # Col AG - Clave interface web - {"canonical": "CLAVE_WEB", "aliases": ["CLAVE WEB", "CLAVE INTERFACE WEB", "WEB KEY"]}, - # Col AH - Desfase (si tiene valor → advertencia) - {"canonical": "COL_EXTRA", "aliases": ["DESFASE", "COLUMNA EXTRA"]}, - # Legacy / otros - {"canonical": "RESPONSABLE", "aliases": ["RESPONSABLE AREA"]}, - {"canonical": "POSICION", "aliases": ["CARGO", "PUESTO"]}, - {"canonical": "INCOTERM", "aliases": []}, - {"canonical": "ACTIVO", "aliases": ["IS_ACTIVE", "ACTIVE", "ESTADO ACTIVO"]}, - ], -} - -inject_en_labels_into_template_columns(TEMPLATE_COLUMNS) - - -def _inject_max_char_hints_client_providers(columns: Optional[List[Dict[str, Any]]]) -> None: - """Añade csv_hint max_chars desde CLIENT_PROVIDER_CSV_COLUMN_MAX_CHARS (paridad modelos/MAX_LEN).""" - if not columns: - return - for item in columns: - if not isinstance(item, dict): - continue - canon = item.get("canonical") - if not canon: - continue - n = CLIENT_PROVIDER_CSV_COLUMN_MAX_CHARS.get(str(canon).strip()) - if n is None: - continue - mc: Dict[str, Any] = {"kind": "max_chars", "n": n} - ch = item.get("csv_hint") - if ch is None: - item["csv_hint"] = mc - elif isinstance(ch, dict): - item["csv_hint"] = [ch, mc] - elif isinstance(ch, list): - item["csv_hint"] = [*ch, mc] - - -_inject_max_char_hints_client_providers(TEMPLATE_COLUMNS.get("client_providers")) - - -def build_normalized_lookup(normalize_header_fn) -> Dict[str, str]: - """normalized_header -> canonical_name para plantilla client_providers.""" - cols = TEMPLATE_COLUMNS.get("client_providers") - if not cols: - return {} - lookup: Dict[str, str] = {} - for item in cols: - canonical = item["canonical"] - lookup[normalize_header_fn(canonical)] = canonical - for alias in item.get("aliases") or []: - lookup[normalize_header_fn(alias)] = canonical - merge_labels_into_lookup(item, canonical, normalize_header_fn, lookup) - merge_download_header_into_lookup(item, canonical, normalize_header_fn, lookup) - return lookup - - -def row_from_template(row: Dict[str, Any], normalize_header_fn) -> Dict[str, Any]: - """Fila CSV con solo columnas de la plantilla, en nombres canónicos.""" - lookup = build_normalized_lookup(normalize_header_fn) - if not lookup: - return {normalize_header_fn(k): cell_to_str(v) for k, v in row.items()} - out: Dict[str, Any] = {} - for csv_header, value in row.items(): - key_norm = normalize_header_fn(csv_header) - if key_norm in lookup: - out[lookup[key_norm]] = cell_to_str(value) - return out diff --git a/backend/api/v1/modules/a76/layouts_csv/clients_and_providers/validators/__init__.py b/backend/api/v1/modules/a76/layouts_csv/clients_and_providers/validators/__init__.py deleted file mode 100644 index 895b3327..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/clients_and_providers/validators/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .create import validate_row_client_provider - -__all__ = ["validate_row_client_provider"] diff --git a/backend/api/v1/modules/a76/layouts_csv/clients_and_providers/validators/common.py b/backend/api/v1/modules/a76/layouts_csv/clients_and_providers/validators/common.py deleted file mode 100644 index e57a63e7..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/clients_and_providers/validators/common.py +++ /dev/null @@ -1,190 +0,0 @@ -""" -Validaciones comunes de fila para import CSV de clientes y proveedores. -Paridad Clarion: VALIDA_TODA_CLIENTE_O_PROV, VALIDA_PARCIAL_CLIENTE_O_PROV, VALIDACIONES_CLIENTE_O_PROV. -Origen de reglas: código legacy Clarion (EstructuraCatClienteProv). -Mapa columnas: A=PROCEDENCIA, B=TIPO, C=SHORT_NAME, D=NOMBRE, E=RFC, F–AG (ver template_config). -""" -from typing import Dict, Any, Optional, Set, List - -from ..common.common_validators import ( - RFC_MAX, - NAME_MAX, - SHORT_NAME_MAX_CLARION, - CURP_MAX, - check_required_max, - check_max_length, - check_rfc_format, - check_tax_id_format, - check_tipo_client_provider, - check_procedencia, - check_short_name_max_clarion, - check_tipo_programa_secon, - check_es_prosec_num_aut, - check_vinculacion, - check_es_empresa_certificada_registro, - check_transformador_submaq, - check_desfase, - short_name_import_key, -) - - -# --- Mensajes obligatorios (VALIDA_TODA) --- -MSG_CAMPOS_OBLIGATORIOS = "Existen campos vacíos que son obligatorios: {campos}." -MSG_CAMPOS_OBLIGATORIOS_SOLUCION = "Revisar la línea del archivo y capturar los campos con la información correcta." -MSG_CLAVE_VACIA = ( - "Error: La Clave de Cliente/Proveedor está vacía y no se pueden hacer las validaciones." -) -MSG_CLAVE_VACIA_SOLUCION = ( - "Capturar una Clave de Cliente/Proveedor nueva o existente a la cual desee agregar, remplazar o actualizar campos." -) - - -def validate_row_desfase(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]: - """Si COL_EXTRA tiene valor → advertencia de desfase.""" - return check_desfase(row, line_num) - - -def validate_row_clave_vacia(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]: - """Clave (SHORT_NAME) vacía → error inmediato.""" - val = (row.get("SHORT_NAME") or "").strip() - if not val: - return { - "line": line_num, - "col": "SHORT_NAME", - "msg": MSG_CLAVE_VACIA, - "solution": MSG_CLAVE_VACIA_SOLUCION, - } - return None - - -def validaciones_cliente_o_prov(row: Dict[str, Any], line_num: int) -> List[Dict[str, Any]]: - """ - VALIDACIONES_CLIENTE_O_PROV: reglas de dominio compartidas. - Col A (E/N + coherencia con L), B (C/P/A), C (máx 8), Q/R/S, T/U, V, W/X, AF. - """ - errors: List[Dict[str, Any]] = [] - - checks = [ - check_procedencia, - check_tipo_client_provider, - check_short_name_max_clarion, - check_tipo_programa_secon, - check_es_prosec_num_aut, - check_vinculacion, - check_es_empresa_certificada_registro, - check_transformador_submaq, - ] - - for fn in checks: - err = fn(row, line_num) - if err: - errors.append(err) - - return errors - - -def valida_toda_cliente_o_prov( - row: Dict[str, Any], - line_num: int, - actualizar: bool = False, -) -> List[Dict[str, Any]]: - """ - VALIDA_TODA: obligatorios Col A (Procedencia), Col D (Nombre) cuando no es ACT. - Luego ejecuta VALIDACIONES_CLIENTE_O_PROV. - """ - campos_oblig = [] - # Col A - Procedencia obligatoria - if not (row.get("PROCEDENCIA") or "").strip(): - campos_oblig.append("(Col.A) Tipo Cliente Procedencia (E/N)") - # Col D - Nombre obligatorio solo cuando no es actualizar - if not actualizar and not (row.get("NOMBRE") or "").strip(): - campos_oblig.append("(Col.D) Nombre") - errors: List[Dict[str, Any]] = [] - if campos_oblig: - errors.append({ - "line": line_num, - "col": "PROCEDENCIA" if not (row.get("PROCEDENCIA") or "").strip() else "NOMBRE", - "msg": MSG_CAMPOS_OBLIGATORIOS.format(campos=", ".join(campos_oblig)), - "solution": MSG_CAMPOS_OBLIGATORIOS_SOLUCION, - }) - - errors.extend(validaciones_cliente_o_prov(row, line_num)) - return errors - - -def valida_parcial_cliente_o_prov(row: Dict[str, Any], line_num: int) -> List[Dict[str, Any]]: - """VALIDA_PARCIAL: solo VALIDACIONES_CLIENTE_O_PROV (no exige A ni D).""" - return validaciones_cliente_o_prov(row, line_num) - - -def validate_row_client_provider( - row: Dict[str, Any], - line_num: int, - actualizar: bool = False, - existing_short_names: Optional[Set[str]] = None, -) -> List[Dict[str, Any]]: - """ - Valida una fila de CSV de clientes y proveedores. - - Desfase (COL_EXTRA) primero. - - Clave vacía → error. - - Si actualizar y clave existe en catálogo → VALIDA_PARCIAL (solo validaciones comunes). - - Si no actualizar, o actualizar con clave nueva → VALIDA_TODA (A y D obligatorios cuando aplique, luego comunes). - Además se validan RFC (requerido max 30), longitudes NOMBRE/SHORT_NAME/CURP para compatibilidad. - """ - errors: List[Dict[str, Any]] = [] - err = validate_row_desfase(row, line_num) - if err: - errors.append(err) - - err = validate_row_clave_vacia(row, line_num) - if err: - errors.append(err) - - sn_key = short_name_import_key(row.get("SHORT_NAME")) - existing = existing_short_names or set() - use_partial = actualizar and sn_key in existing - - if use_partial: - errors.extend(valida_parcial_cliente_o_prov(row, line_num)) - else: - errors.extend(valida_toda_cliente_o_prov(row, line_num, actualizar=actualizar)) - - # Identificador fiscal requerido y validado por procedencia: - # Nacional (N) -> RFC en columna RFC - # Extranjero (E) -> TAX-ID en columna TAX_ID (compat: archivos legacy lo traían en columna RFC) - procedencia = (row.get("PROCEDENCIA") or "").strip().upper()[:1] - - if procedencia == "E": - # Columna efectiva: TAX_ID dedicada o, en su defecto, RFC (archivo legacy de columna única). - effective_col = "TAX_ID" if (row.get("TAX_ID") or "").strip() else "RFC" - if not (row.get(effective_col) or "").strip(): - errors.append({ - "line": line_num, - "col": "TAX_ID", - "msg": "Requerido (TAX-ID)", - "solution": "Capturar el TAX-ID del cliente/proveedor extranjero en la columna TAX_ID.", - }) - else: - err = check_tax_id_format(row, line_num, col=effective_col) - if err: - errors.append(err) - else: - # Nacional: RFC requerido + formato - err = check_required_max(row, "RFC", RFC_MAX, line_num) - if err: - errors.append(err) - else: - err = check_rfc_format(row, line_num) - if err: - errors.append(err) - - for err in [ - check_max_length(row, "NOMBRE", NAME_MAX, line_num), - check_max_length(row, "SHORT_NAME", 10, line_num), # modelo permite 10 - check_max_length(row, "CURP", CURP_MAX, line_num), - check_max_length(row, "TAX_ID", RFC_MAX, line_num), - ]: - if err: - errors.append(err) - - return errors diff --git a/backend/api/v1/modules/a76/layouts_csv/clients_and_providers/validators/create.py b/backend/api/v1/modules/a76/layouts_csv/clients_and_providers/validators/create.py deleted file mode 100644 index 734e7f8b..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/clients_and_providers/validators/create.py +++ /dev/null @@ -1,8 +0,0 @@ -""" -Punto de entrada de validación para import de una fila cliente/proveedor. -""" -from typing import Dict, Any, Optional - -from .common import validate_row_client_provider - -__all__ = ["validate_row_client_provider"] diff --git a/backend/api/v1/modules/a76/layouts_csv/common/__init__.py b/backend/api/v1/modules/a76/layouts_csv/common/__init__.py deleted file mode 100644 index d5f02bc1..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/common/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# Shared utilities for layouts_csv imports (storage, normalize, csv, meta, responses) diff --git a/backend/api/v1/modules/a76/layouts_csv/common/cell_value.py b/backend/api/v1/modules/a76/layouts_csv/common/cell_value.py deleted file mode 100644 index a5989742..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/common/cell_value.py +++ /dev/null @@ -1,19 +0,0 @@ -""" -Convierte valor de celda CSV a str. Evita 'list' object has no attribute 'strip' -cuando columnas duplicadas o el lector devuelve listas. -""" -from typing import Any - - -def cell_to_str(value: Any) -> str: - """ - Convierte valor de celda a str. - Si es lista (p. ej. CSV con columnas duplicadas), usa el primer elemento. - """ - if value is None: - return "" - if isinstance(value, list): - if not value: - return "" - return str(value[0]) if value[0] is not None else "" - return str(value) diff --git a/backend/api/v1/modules/a76/layouts_csv/common/csv_headers_en.py b/backend/api/v1/modules/a76/layouts_csv/common/csv_headers_en.py deleted file mode 100644 index 38052179..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/common/csv_headers_en.py +++ /dev/null @@ -1,408 +0,0 @@ -""" -Traducciones EN de cabeceras CSV por nombre canónico (clave interna). -Usado para plantillas descargadas locale=en y para aliases de carga. -""" - - -def _facturas_headers() -> dict[str, str]: - return { - # Encabezados / series / partidas — alineado con messages/en.json (pedimento, exchange rate, incrementables, discharge). - "PEDIMENTO": "Pedimento", - "REMESA": "Consignment", - "NUMERO FACTURA": "Invoice number", - "FECHA FACTURA": "Invoice date", - "TIPO DE CAMBIO": "Exchange rate", - "REGIMEN": "Regime", - "CLAVE PROVEEDOR": "Supplier code", - "CLAVE VENDIDO A": "Sold-to code", - "CLAVE ENVIADO A": "Ship-to code", - "AGENTE ADUANAL": "Customs broker", - "CLAVE TRANSPORTISTA": "Carrier code", - "NOMBRE CONDUCTOR": "Driver name", - "TIPO TRANSPORTE": "Transport type", - "CLAVE TRANSPORTE": "Transport code", - "NUMERO CAJA": "Trailer/box number", - "NUMERO TRANSPORTE": "Transport number", - "TIPO MONEDA": "Currency type", - "CLAVE MONEDA": "Currency code", - "FLETES": "Freight", - "VALOR SEGUROS": "Insurance value", - "SEGUROS": "Insurance", - "EMBALAJES": "Packaging", - "OTROS INCREMENTABLES": "Incrementables (other)", - "CLAVE INCOTERM": "Incoterm code", - "PRECINTO": "Seal", - "FECHA EMISION": "Issue date", - "TIPO PESO": "Weight type", - "E DOCUMENT": "E-document", - "NUM OPERACION": "Operation number", - "ADUANA DE CRUCE": "Customs office (crossing)", - "OBSERVACIONES E": "Export remarks", - "OBSERVACIONES I": "Import remarks", - "FACTURA ALTERNA": "Alternate invoice", - "NUM PROYECTO": "Project number", - "ORDEN COMPRA": "Purchase order", - "FACTURA EXPO REF": "Export invoice reference", - "MANIFIESTO": "Manifest", - "ENVIADO POR": "Shipped by", - # Partidas impo temp / compartidas - "NUMERO FACTURA EXPO": "Export invoice number", - "LINEA": "Line", - "LINEA EXPO": "Export line", - "LINEA IMPO": "Import line", - "LINEA FACTURA": "Invoice line", - "LINEA SERIE": "Serial line", - "TIPO DE IMPO": "Import type", - "FACTURA IMPO": "Import invoice", - "GENERA DESCARGA": "Generate discharge", - "CANTIDAD EXPORTADA/DESCARGAR": "Exported qty / qty to discharge", - "CLASE": "Class", - "CANTIDAD IMPORTADA": "Imported quantity", - "UNIDAD DE MEDIDA": "Unit of measure", - "COSTO UNITARIO": "Unit cost", - "PRECIO UNITARIO": "Unit price", - "VALOR COMERCIAL": "Commercial value", - "PESO NETO": "Net weight", - "PESO BRUTO": "Gross weight", - "CANTIDAD BULTOS": "Package count", - "CLAVE BULTOS": "Package code", - "PAIS ORIGEN": "Country of origin", - "FRACCION ARANCELARIA": "Tariff fraction", - "FRACCION": "Fraction", - "PREFERENCIA ARANCELARIA": "Tariff preference", - "SECTOR": "Sector", - "FRACCION AMERICANA": "US tariff fraction", - "ORDEN DE COMPRA": "Purchase order", - "DESCRIPCION ESPAÑOL": "Description (Spanish)", - "DESCRIPCION INGLES": "Description (English)", - "DESCRIPCION": "Description", - "MARCA": "Brand", - "MODELO": "Model", - "ES PARTIDA O SUBPARTIDA": "Line or sub-line", - "ES PARTIDA/SUBPARTIDA": "Line or sub-line", - "LINEA PRINCIPAL": "Main line", - "NUM. PARTE": "Part number", - "NUMPARTE": "Part number", - "SE PAGO IMPUESTO": "Tax paid (Y/N)", - "FORMA DE PAGO": "Payment method", - "METODO DE VALORACION": "Valuation method", - "DESCRIPCION EXTRA": "Extra description", - "INFORMACION ADICIONAL": "Additional information", - "AGREGAR/SUSTITUIR": "Add/replace", - "AGREGAR(A)/SUSTITUIR(S)": "Add/replace", - "TOTAL": "Total", - "NUMERO ENTRADA": "Entry number", - "LOTE": "Lot", - "ID TYPE": "ID type", - "SERIE": "Serial", - "SUB MODELO": "Sub-model", - "NUM PARTE": "Part number", - "NUMERO ID": "ID number", - "COL_EXTRA": "Extra column", - } - - -def _series_merge() -> dict[str, str]: - """Series share columns; ensure overlap with facturas_headers.""" - return { - "NUMERO FACTURA": "Invoice number", - "LINEA FACTURA": "Invoice line", - "LINEA SERIE": "Serial line", - "SERIE": "Serial", - "MODELO": "Model", - "NUM PARTE": "Part number", - "SUB MODELO": "Sub-model", - "NUMERO ID": "ID number", - "COL_EXTRA": "Extra column", - } - - -def _customs_exchange_us_fractions_boms() -> dict[str, str]: - return { - # customs_brokers - "TIPO": "Type", - "CLAVE": "Code", - "LICENCIA": "License", - "NOMBRE": "Name", - "RFC": "Tax ID", - "DIRECCION": "Address", - "CODIGO POSTAL": "Postal code", - "CIUDAD": "City", - "ESTADO": "State", - "PAIS": "Country", - "TELEFONO": "Phone", - "FAX": "Fax", - "EMAIL": "Email", - "PERSONAL_ID": "Personal ID / CURP", - "COL_EXTRA": "Extra column", - "POSICION": "Position", - "EMPRESA": "Company", - "CONTACTO": "Contact", - # exchange_rates - "FECHA": "Date", - "VALOR": "Exchange rate", - "MONEDA_LOCAL": "Local currency", - "MONEDA_EXTRANJERA": "Foreign currency", - # us_tariff_fractions - "FRACCION_ARANCELARIA": "Tariff fraction", - "PREFIJO": "Prefix", - "UNIDAD_DE_MEDIDA": "Unit of measure", - "DESCRIPCION": "Description", - "TIPO_DE_ADVALOREM": "Ad valorem type", - "ADVALOREM_PCT": "Ad valorem %", - "ADVALOREM_DLLS": "Ad valorem (USD)", - # boms - "NUMPARTE_PADRE": "Parent part number", - "NUMPARTE_COMPONENTE": "Component part number", - "CANTIDAD": "Quantity", - "UNIMED": "Unit of measure", - "VERSION_BOM": "BOM version", - "VERSION_BILL": "Bill version", - } - - -def _clients_providers_headers() -> dict[str, str]: - return { - "PROCEDENCIA": "Origin (foreign/domestic)", - "TIPO": "Entity type", - "SHORT_NAME": "Short code", - "NOMBRE": "Name", - "RFC": "Tax ID", - "DIRECCION": "Address", - "NUM_EXT": "Exterior number", - "CODIGO POSTAL": "Postal code", - "COLONIA": "District", - "CIUDAD": "City", - "ESTADO": "State", - "PAIS": "Country", - "TELEFONO": "Phone", - "FAX": "Fax", - "EMAIL": "Email", - "CURP": "CURP", - "TIPO_PROGRAMA_SECON": "SECON program type", - "NUM_PROGRAMA_SECON": "SECON program number", - "FECHA_AUT_SECON": "SECON authorization date", - "ES_PROSEC": "Is PROSEC", - "NUM_AUT_PROSEC": "PROSEC authorization number", - "VINCULACION": "Linkage", - "ES_EMPRESA_CERTIFICADA": "Certified company", - "REGISTRO_EMPRESA_CERT": "Certified company registry", - "INFORMACION_EXTRA": "Extra information", - "CONTACTO": "Contact", - "MANUFACTURER_ID": "Manufacturer ID", - "TAX_ID_PROGRAMS": "Tax ID programs", - "BROKER_EXPO": "Export broker", - "BROKER_IMPO": "Import broker", - "CLAVE_TRANSFER": "Transfer code", - "TRANSFORMA_SUBMAQ": "Transformer/sub-maquila", - "CLAVE_WEB": "Web code", - "COL_EXTRA": "Extra column", - "RESPONSABLE": "Responsible", - "POSICION": "Position", - "INCOTERM": "Incoterm", - "ACTIVO": "Active", - } - - -def _pedimentos_headers() -> dict[str, str]: - return { - "AÑO": "Year", - "PATENTE": "Patent", - "NUMERO": "Number", - "PEDIMENTO": "Pedimento", - "TIPO_OPERACION": "Operation type", - "TIPO_PEDIMENTO": "Pediment type", - "CLAVE_PEDIMENTO": "Pediment code", - "REGIMEN": "Regime", - "FECHA_INICIO": "Start date", - "FECHA_FINAL": "End date", - "FECHA_PAGO": "Payment date", - "FECHA_ENTRADA_RECINTO": "Compound entry date", - "FECHA_EXTRACCION_RECINTO": "Compound exit date", - "FECHA_RECIBIDO": "Received date", - "FECHA_AUTORIZACION": "Authorization date", - "FECHA_CIERRE": "Closing date", - "FECHA_REVISION": "Review date", - "ADUANA_SECCION_CRUCE": "Customs office / crossing section", - "ACUSE_ELECTRONICO": "Electronic acknowledgment", - "INDIVIDUAL_CONSOLIDADO": "Individual/consolidated", - "MET_TRANSP_ENTRADA": "Inbound transport method", - "MET_TRANSP_ARRIVO": "Arrival transport method", - "MET_TRANSP_SALIDA": "Outbound transport method", - "IEPS": "IEPS", - "IEPS_2": "IEPS (2)", - "DTA": "DTA", - "DTA_2": "DTA (2)", - "CNT": "CNT", - "CNT_2": "CNT (2)", - "PREVALIDACION": "Pre-validation", - "MONTO_TIGIE": "TIGIE amount", - "PAGO_IMPUESTO": "Tax paid (Y/N)", - "ES_MIXTO": "Mixed (yes/no)", - "OBS_RECTIFICA": "Rectification remarks", - "OPCION_DESTINO": "Destination option", - "VALOR_IVA": "VAT value", - "VALOR_ME": "Foreign currency value", - "VALOR_ADUANAS": "Customs value", - "VALOR_USD": "USD value", - "VALOR_SEGUROS": "Insurance value", - "FLETE": "Freight", - "SEGUROS": "Insurance", - "EMBALAJES": "Packaging", - "OTROS_INCREMENTABLES": "Incrementables (other)", - "ESTATUS": "Status", - "PERSONA_REV": "Reviewer", - "OBSERVACIONES": "Remarks", - "TIPO_CAMBIO": "Exchange rate", - "REPRESENTANTE_AA": "Customs broker representative", - "CLIENTE_SHORT_NAME": "Client short code", - "CLAVE_DEST_ORIGEN": "Destination origin code", - "IDENTIFICADORES": "Identifiers", - "CUOTAS_COMPENSATORIAS": "Compensatory quotas", - "ERRORES": "Errors", - "MULTAS": "Fines", - "RECARGOS": "Surcharges", - "PRECIO_PAGADO": "Price paid", - "PESO_BRUTO": "Gross weight", - "IVA_DE_PREV": "VAT from pre-validation", - "IVA_2": "VAT (2)", - "IGI_2": "IGI (2)", - "FORMA_PAGO_IVA": "VAT payment method", - "FORMA_PAGO_IVA_2": "VAT payment method (2)", - "FORMA_PAGO_IGI": "IGI payment method", - "FORMA_PAGO_IGI_2": "IGI payment method (2)", - "FORMA_PAGO_IEPS_2": "IEPS payment method (2)", - "FORMA_PAGO_DTA": "DTA payment method", - "FORMA_PAGO_DTA_2": "DTA payment method (2)", - "FORMA_PAGO_CNT_2": "CNT payment method (2)", - "FORMA_PAGO_PREVAL": "Pre-validation payment method", - "FORMA_PAGO_PREVALIDACION_2": "Pre-validation payment method (2)", - } - - -def _vehicles_drivers_trailers_transporters() -> dict[str, str]: - return { - # vehicles / transports template "vehicles" - "CLAVE": "Code", - "CODIGO DE ENTIDAD": "Entity code", - "TIPO TRANSPORTE": "Transport type", - "CLAVE TRANSPORTE": "Transport code", - "CLAVE ACE": "ACE code", - "PLACAS": "Plates", - "PRECINTO": "Seal", - "NUMERO DOT": "DOT number", - "TRANSPONDEDOR": "Transponder", - "VIN": "VIN", - "EMPRESA ASEGURADORA": "Insurance company", - "NUM. ASEGURADORA": "Insurance policy number", - "FECHA DE ASEGURADORA": "Insurance date", - "MONTO ASEGURADO": "Insured amount", - "COL_EXTRA": "Extra column", - # drivers - "TRANSPORTISTA": "Carrier", - "LINEA": "Line", - "CLAVE CONDUCTOR": "Driver code", - "NOMBRE(S)": "Given name(s)", - "APELLIDO PATERNO": "Last name (paternal)", - "SEXO": "Gender", - "FECHA NACIMIENTO": "Birth date", - "PAIS NACIMIENTO": "Birth country", - "LICENCIA": "License", - "FORMA IDENTIFICACION 1": "ID type 1", - "NUM. IDENTIFICACION 1": "ID number 1", - "FORMA IDENTIFICACION 2": "ID type 2", - "NUM. IDENTIFICACION 2": "ID number 2", - "IDENTIFICACION ACE": "ACE identification", - "PAIS": "Country", - "PAIS 2": "Country 2", - "ESTADO": "State", - "ESTADO 2": "State 2", - "PERMISO LINEA EXPRESS": "Express line permit", - "PERMISO MAT. PELIGROSO": "Hazmat permit", - "TRANSPORTA MAT. PELIGROSO?": "Transports hazardous material?", - # trailers - "NUMERO TRAILER": "Trailer number", - "TIPO TRAILER": "Trailer type", - "CODIGO ENTIDAD": "Entity code", - "CLAVE CONTENEDOR": "Container code", - # transporters - "CLAVE TRANSPORTISTA": "Carrier code", - "NOMBRE CORTO": "Short name", - "RESPONSABLE": "Responsible", - "RFC": "Tax ID", - "CALLES": "Street address", - "CODIGO CAAT": "CAAT code", - "CODIGO CARGADOR": "Loader code", - "CODIGO TRANS": "Transport code", - "TIPO INTERFASE TRANS": "Carrier interface type", - "SERVIDOR FTP": "FTP server", - "USUARIO FTP": "FTP user", - "CLAVE ACCESO FTP": "FTP password", - "DIRECTORIO FTP": "FTP directory", - } - - -def _material_classes_headers() -> dict[str, str]: - # Coincide con classes/template_config.TEMPLATE_DOWNLOAD_HEADERS_EN (salvo columnas solo en template extendido). - return { - "CLASE": "CLASS CODE", - "DESCRIPCIONE": "DESCRIPTION (SPANISH)", - "DESCRIPCIONI": "DESCRIPTION (ENGLISH)", - "CLAVEMAT": "MATERIAL TYPE", - "UNIMED": "COMMERCIAL UOM", - "FRACCION": "MX FRACTION", - "FRACCIONAME": "US FRACTION", - "TASADEPRECIA": "DEPRECIATION RATE", - "CLAVESUB": "SUB KEY", - "REVFISICA": "PHYSICAL REVIEW (1/0)", - "FRACCIONEXENTAIVA": "PRODUCT/SERVICE CP CODE", - } - - -def _parts_headers() -> dict[str, str]: - # Alineado con parts/template_config.TEMPLATE_DOWNLOAD_HEADERS_EN. - return { - "NUMPARTE": "PART NUMBER", - "NUMPARTECOM": "COMMERCIAL PART NUMBER", - "DESCRIPCIONE": "DESCRIPTION (SPANISH)", - "DESCRIPCIONI": "DESCRIPTION (ENGLISH)", - "CLASE": "CLASS", - "UNIMED": "COMMERCIAL UNIT OF MEASURE", - "COSTOUNIT": "UNIT COST", - "TIPOMONEDA": "COST CURRENCY TYPE", - "CLAVEMONEDA": "CURRENCY CODE", - "PESOUNIT": "UNIT WEIGHT", - "TIPOPESO": "WEIGHT TYPE", - "FRACCION": "FRACTION", - "FRACCIONAME": "US FRACTION", - "PAIS": "COUNTRY", - "PREFERENCIA": "PREFERENCE", - "SECTOR": "SECTOR", - "RUTAIMAGEN": "IMAGE PATH", - "FDAKEY": "FDA key", - "FCCKEY": "FCC key", - "LICENCIA": "License code", - "ECCN": "ECCN", - "EXPORTCODE": "Export code", - "EXCLUSION": "Exclusion", - "ACTIVO": "Active", - } - - -def _merge_all() -> dict[str, str]: - merged: dict[str, str] = {} - for part in ( - _facturas_headers(), - _series_merge(), - _customs_exchange_us_fractions_boms(), - _clients_providers_headers(), - _pedimentos_headers(), - _vehicles_drivers_trailers_transporters(), - _material_classes_headers(), - _parts_headers(), - ): - merged.update(part) - return merged - - -HEADER_LABEL_EN: dict[str, str] = _merge_all() diff --git a/backend/api/v1/modules/a76/layouts_csv/common/csv_hint_inject.py b/backend/api/v1/modules/a76/layouts_csv/common/csv_hint_inject.py deleted file mode 100644 index 09b86d41..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/common/csv_hint_inject.py +++ /dev/null @@ -1,66 +0,0 @@ -"""Helpers to merge csv_hint max_chars into column definitions after inject_en_labels.""" -from __future__ import annotations - -from typing import Any, Dict, List, Optional - - -def inject_max_char_hints_into_columns( - columns: Optional[List[Dict[str, Any]]], - max_by_canonical: Dict[str, int], -) -> None: - """Attach or append ``{\"kind\": \"max_chars\", \"n\": N}`` per canonical (aligned with validators/models).""" - if not columns or not max_by_canonical: - return - for item in columns: - if not isinstance(item, dict): - continue - canon = item.get("canonical") - if not canon: - continue - n = max_by_canonical.get(str(canon).strip()) - if n is None: - continue - mc: Dict[str, Any] = {"kind": "max_chars", "n": n} - ch = item.get("csv_hint") - if ch is None: - item["csv_hint"] = mc - elif isinstance(ch, dict): - item["csv_hint"] = [ch, mc] - elif isinstance(ch, list): - item["csv_hint"] = [*ch, mc] - - -def append_csv_hint(item: Dict[str, Any], extra: Dict[str, Any]) -> None: - """Añade un hint (digits, enum adicional, etc.) tras los existentes.""" - ch = item.get("csv_hint") - if ch is None: - item["csv_hint"] = extra - return - if isinstance(ch, dict): - item["csv_hint"] = [ch, extra] - return - if isinstance(ch, list): - item["csv_hint"] = [*ch, extra] - - -def merge_enum_hint_before_max( - item: Dict[str, Any], - enum_codes: List[str], -) -> None: - """Prepend enum_codes hint if not already present (for columns with fixed ≤3 values).""" - if len(enum_codes) == 0 or len(enum_codes) > 3: - return - enum_hint: Dict[str, Any] = {"kind": "enum_codes", "codes": list(enum_codes)} - ch = item.get("csv_hint") - if ch is None: - item["csv_hint"] = enum_hint - return - if isinstance(ch, dict): - if ch.get("kind") == "enum_codes": - return - item["csv_hint"] = [enum_hint, ch] - return - if isinstance(ch, list): - if any(isinstance(x, dict) and x.get("kind") == "enum_codes" for x in ch): - return - item["csv_hint"] = [enum_hint, *ch] diff --git a/backend/api/v1/modules/a76/layouts_csv/common/csv_reader.py b/backend/api/v1/modules/a76/layouts_csv/common/csv_reader.py deleted file mode 100644 index f24a7ba1..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/common/csv_reader.py +++ /dev/null @@ -1,282 +0,0 @@ -""" -Lectura de CSV con detección de delimitador (compartida por layouts_csv). -Si se pasa fieldnames, no se usa la primera fila como cabecera y se toma como dato (CSV sin cabeceras). -Si se pasa headerless_first_cell_values, se detecta si la primera fila es cabecera o dato por el valor de la primera celda. -""" -import csv -import io -import codecs -from dataclasses import dataclass -from typing import Iterator, Tuple, Dict, Any, Optional, List, Set, Sequence, Literal - - -def _normalize_empty_headers(headers: List[str]) -> List[str]: - """Sustituye cabeceras vacías por _COL_0_, _COL_1_, ... para que DictReader no colapse columnas.""" - result: List[str] = [] - empty_idx = 0 - for h in headers: - if (h or "").strip() == "": - result.append(f"_COL_{empty_idx}_") - empty_idx += 1 - else: - result.append(h) - return result - - -def dedupe_duplicate_headers(headers: List[str], fallback_name: str = "COL") -> List[str]: - """ - Hace únicos headers repetidos agregando sufijo incremental. - """ - counts: Dict[str, int] = {} - unique: List[str] = [] - for header in headers: - name = str(header or "").strip() or fallback_name - count = counts.get(name, 0) + 1 - counts[name] = count - unique.append(name if count == 1 else f"{name} {count}") - return unique - - -_ENCODING_FALLBACKS: Sequence[str] = ("utf-8-sig", "utf-8", "cp1252", "latin-1") - - -@dataclass(frozen=True) -class CsvReadPlan: - """ - Contrato declarativo para lectura de CSV. - - header_mode=header: primera fila siempre cabecera. - - header_mode=headerless: primera fila siempre dato. - - header_mode=auto: decide con headerless_first_cell_values. - """ - header_mode: Literal["header", "headerless", "auto"] = "header" - fieldnames: Optional[List[str]] = None - headerless_first_cell_values: Optional[Set[str]] = None - encoding: Optional[str] = "auto" - delimiters: str = ",;\t" - sample_chars: int = 2048 - - -@dataclass(frozen=True) -class CsvReadMetadata: - encoding: str - dialect: Any - has_header: bool - fieldnames: Optional[List[str]] - - -def _sample_decodes_with_encoding(raw: bytes, encoding: str) -> bool: - """ - Valida si un sample binario puede decodificarse con `encoding`. - Para UTF-8/UTF-8-SIG tolera corte al final de un multibyte (sample truncado). - """ - try: - raw.decode(encoding) - return True - except UnicodeDecodeError as err: - if encoding not in ("utf-8", "utf-8-sig"): - return False - # Si el error es por sample truncado al final del buffer, validar con decodificador incremental. - if err.end != len(raw): - return False - try: - decoder = codecs.getincrementaldecoder(encoding)(errors="strict") - decoder.decode(raw, final=False) - return True - except Exception: - return False - except Exception: - return False - - -def detect_text_encoding( - file_path: str, - encodings: Sequence[str] = _ENCODING_FALLBACKS, - sample_bytes: int = 8192, -) -> str: - """ - Detecta encoding por prueba de decodificación en un sample en binario. - Nota: latin-1 decodifica cualquier byte; por eso debe ir al final. - """ - with open(file_path, "rb") as f: - raw = f.read(sample_bytes) - last_err: Optional[Exception] = None - for enc in encodings: - try: - if _sample_decodes_with_encoding(raw, enc): - return enc - except Exception as e: - last_err = e - if last_err: - raise last_err - return "utf-8-sig" - - -def _detect_text_encoding( - file_path: str, - encodings: Sequence[str] = _ENCODING_FALLBACKS, - sample_bytes: int = 8192, -) -> str: - """ - Compatibilidad retroactiva para imports internos antiguos. - """ - return detect_text_encoding(file_path, encodings=encodings, sample_bytes=sample_bytes) - - -def resolve_read_encoding(file_path: str, requested_encoding: Optional[str] = "auto") -> str: - if requested_encoding and requested_encoding.lower() not in ("auto", "detect"): - return requested_encoding - return detect_text_encoding(file_path) - - -def read_text_sample( - file_path: str, - requested_encoding: Optional[str] = "auto", - sample_chars: int = 2048, -) -> Tuple[str, str]: - """ - Lee muestra de texto para detectar delimitador/primera fila. - Retorna (sample_text, resolved_encoding). - """ - encoding = resolve_read_encoding(file_path, requested_encoding) - with open(file_path, "r", encoding=encoding) as f: - return f.read(sample_chars), encoding - - -def detect_csv_dialect(sample: str, delimiters: str = ",;\t") -> Any: - try: - return csv.Sniffer().sniff(sample, delimiters=delimiters) - except Exception: - return "excel" - - -def inspect_csv(file_path: str, read_plan: Optional[CsvReadPlan] = None) -> CsvReadMetadata: - plan = read_plan or CsvReadPlan() - sample, encoding = read_text_sample( - file_path, - requested_encoding=plan.encoding, - sample_chars=plan.sample_chars, - ) - dialect = detect_csv_dialect(sample, delimiters=plan.delimiters) - has_header = True - fieldnames: Optional[List[str]] = None - if plan.header_mode == "headerless": - has_header = False - fieldnames = list(plan.fieldnames or []) - elif plan.header_mode == "auto" and plan.fieldnames and plan.headerless_first_cell_values is not None: - first_line = sample.splitlines()[0] if sample.splitlines() else "" - if first_line: - row_reader = csv.reader(io.StringIO(first_line), dialect=dialect) - first_cells = next(row_reader, None) - first_cell_clean = ((first_cells or [""])[0] or "").lstrip("\ufeff").strip().upper() - if first_cell_clean in plan.headerless_first_cell_values: - has_header = False - fieldnames = list(plan.fieldnames) - return CsvReadMetadata( - encoding=encoding, - dialect=dialect, - has_header=has_header, - fieldnames=fieldnames, - ) - - -def iter_csv_rows_with_plan( - file_path: str, - read_plan: Optional[CsvReadPlan] = None, -) -> Iterator[Tuple[int, Dict[str, Any]]]: - """ - Iterador común de filas usando un ReadPlan. - """ - plan = read_plan or CsvReadPlan() - metadata = inspect_csv(file_path, plan) - with open(file_path, "r", encoding=metadata.encoding) as f: - if metadata.has_header: - first_line = f.readline() - if not first_line: - return - row_reader = csv.reader(io.StringIO(first_line), dialect=metadata.dialect) - raw_headers = next(row_reader, None) - if not raw_headers: - return - normalized = _normalize_empty_headers(raw_headers) - reader = csv.DictReader(f, fieldnames=normalized, dialect=metadata.dialect, restval="") - for i, row in enumerate(reader, start=1): - yield i, dict(row) - return - - fieldnames = metadata.fieldnames or list(plan.fieldnames or []) - if not fieldnames: - return - row_reader = csv.reader(f, dialect=metadata.dialect) - for i, cells in enumerate(row_reader, start=1): - if cells is None: - continue - pad = len(fieldnames) - len(cells) - normalized_cells = cells[: len(fieldnames)] + ([""] * pad if pad > 0 else []) - yield i, dict(zip(fieldnames, normalized_cells)) - - -def iter_csv_rows_deduped_headers( - file_path: str, - fallback_name: str = "COL", -) -> Iterator[Tuple[int, Dict[str, Any]]]: - """ - Itera filas asumiendo cabecera en primera línea y deduplicando nombres repetidos. - """ - metadata = inspect_csv(file_path, CsvReadPlan(header_mode="header")) - with open(file_path, "r", encoding=metadata.encoding) as f: - first_line = f.readline() - if not first_line: - return - header_reader = csv.reader(io.StringIO(first_line), dialect=metadata.dialect) - raw_headers = next(header_reader, None) - if not raw_headers: - return - headers = dedupe_duplicate_headers(raw_headers, fallback_name=fallback_name) - dict_reader = csv.DictReader(f, fieldnames=headers, dialect=metadata.dialect, restval="") - for i, row in enumerate(dict_reader, start=1): - yield i, dict(row) - - -def iter_csv_rows( - file_path: str, - fieldnames: Optional[List[str]] = None, - headerless_first_cell_values: Optional[Set[str]] = None, - headerless_second_cell_key_pattern: Optional[str] = None, -) -> Iterator[Tuple[int, Dict[str, Any]]]: - """ - Abre el CSV, detecta dialecto y devuelve (line_num, row_dict) por cada fila. - line_num empieza en 1 (primera fila de datos). - Si fieldnames y headerless_first_cell_values se pasan: si la primera celda de la primera fila - (quitando BOM, strip, upper) está en headerless_first_cell_values, se trata como dato y se usan fieldnames. - headerless_second_cell_key_pattern se ignora si no se usa (reservado para otros layouts). - """ - if fieldnames and headerless_first_cell_values is not None: - plan = CsvReadPlan( - header_mode="auto", - fieldnames=fieldnames, - headerless_first_cell_values=headerless_first_cell_values, - ) - elif fieldnames: - plan = CsvReadPlan( - header_mode="headerless", - fieldnames=fieldnames, - ) - else: - plan = CsvReadPlan(header_mode="header") - - for item in iter_csv_rows_with_plan(file_path, plan): - yield item - - -def count_csv_rows( - file_path: str, - has_header: bool = True, - read_plan: Optional[CsvReadPlan] = None, -) -> int: - """Cuenta filas del CSV. Si has_header=True (por defecto), no cuenta la cabecera.""" - metadata = inspect_csv(file_path, read_plan) if read_plan else None - encoding = metadata.encoding if metadata else detect_text_encoding(file_path) - effective_has_header = metadata.has_header if metadata else has_header - with open(file_path, "r", encoding=encoding) as f: - total_lines = sum(1 for _ in f) - return total_lines if not effective_has_header else max(0, total_lines - 1) diff --git a/backend/api/v1/modules/a76/layouts_csv/common/error_csv.py b/backend/api/v1/modules/a76/layouts_csv/common/error_csv.py deleted file mode 100644 index 0d170d01..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/common/error_csv.py +++ /dev/null @@ -1,96 +0,0 @@ -""" -Generación de CSV de errores a partir del JSONL persistido por el worker. -""" - -import csv -import io -import json -import os -from typing import Any, Dict, Iterable, List - -from fastapi import HTTPException -from fastapi.responses import StreamingResponse - -from . import storage as common_storage - - -def _iter_jsonl_rows(error_path: str) -> Iterable[Dict[str, Any]]: - """ - Itera sobre errores normalizados desde un .jsonl. - - El worker suele escribir: - - dict por línea, o - - lista de dicts por línea. - """ - if not os.path.exists(error_path): - return [] - - with open(error_path, "r", encoding="utf-8") as f: - for line in f: - if not line.strip(): - continue - try: - payload = json.loads(line) - except Exception: - continue - - items: List[Any] = payload if isinstance(payload, list) else [payload] - for err in items: - if isinstance(err, dict): - yield err - - -def download_scan_errors_csv_stream(job_type: str, job_id: str, *, filename_prefix: str = "errores") -> StreamingResponse: - """ - Devuelve un StreamingResponse leyendo el JSONL del worker. - - Columnas (paridad con el payload JSONL / `_invoice_scan_error_row_payload` en facturas): - linea, columna, mensaje, solucion, advertencia, codigo, not_found_reason - """ - error_path = common_storage.error_path_for_job(job_type, job_id) - if not os.path.exists(error_path): - raise HTTPException( - status_code=404, - detail="Archivo de errores no encontrado. Vuelve a escanear o intenta más tarde.", - ) - - headers = { - "Content-Disposition": f'attachment; filename="{filename_prefix}_{job_id}.csv"', - } - - _CSV_ERRORS_HEADER = [ - "linea", - "columna", - "mensaje", - "solucion", - "advertencia", - "codigo", - "not_found_reason", - ] - - def row_iter(): - buffer = io.StringIO() - writer = csv.writer(buffer) - writer.writerow(_CSV_ERRORS_HEADER) - yield buffer.getvalue() - buffer.seek(0) - buffer.truncate(0) - - for err in _iter_jsonl_rows(error_path): - buffer.seek(0) - buffer.truncate(0) - writer.writerow( - [ - err.get("line", ""), - err.get("col", ""), - err.get("msg", ""), - err.get("solution", ""), - "si" if bool(err.get("warning")) else "no", - err.get("code", ""), - err.get("not_found_reason", ""), - ] - ) - yield buffer.getvalue() - - return StreamingResponse(row_iter(), media_type="text/csv; charset=utf-8", headers=headers) - diff --git a/backend/api/v1/modules/a76/layouts_csv/common/import_redis_keys.py b/backend/api/v1/modules/a76/layouts_csv/common/import_redis_keys.py deleted file mode 100644 index 8d763d1d..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/common/import_redis_keys.py +++ /dev/null @@ -1,69 +0,0 @@ -""" -Claves Redis por job de import CSV (layouts_csv): las de storage_keys más extras por módulo. -Usado por la limpieza periódica (victor "el senior de limpieza") para saber si un job sigue vivo en Redis. -""" -from __future__ import annotations - -from typing import Any, List - -from . import storage as common_storage - -# Prefijo en nombre de archivo `{prefix}_{uuid}.csv` -> job_type (vacío = solo `{uuid}.csv`) -FILE_PREFIX_TO_JOB_TYPE: dict[str, str] = { - "veh": "veh", - "fa": "fa", - "trl": "trl", - "trp": "trp", - "ped": "ped", - "part": "part", - "er": "er", - "cb": "cb", - "drv": "drv", - "cls": "cls", - "cp": "cp", - "bom": "bom", - "exp": "exp", - "crreg": "crreg", -} - - -def all_redis_keys_for_layout_import(job_type: str, job_id: str) -> List[str]: - """Todas las claves Redis que pueden existir para un job (file, meta, error_lines, extras).""" - file_k, meta_k, err_k = common_storage.storage_keys(job_type, job_id) - keys: List[str] = [file_k, meta_k, err_k] - if job_type == "veh": - keys.append(f"veh_import_status:{job_id}") - elif job_type == "trl": - keys.append(f"trl_import_status:{job_id}") - elif job_type == "trp": - keys.append(f"trp_import_status:{job_id}") - elif job_type == "drv": - keys.append(f"drv_import_status:{job_id}") - keys.append(f"drv_import_transporter_map:{job_id}") - return keys - - -def any_layout_import_key_exists(redis_client: Any, job_type: str, job_id: str) -> bool: - for k in all_redis_keys_for_layout_import(job_type, job_id): - if redis_client.exists(k): - return True - return False - - -def delete_extra_layout_import_keys_from_redis(redis_client: Any, job_type: str, job_id: str) -> None: - """Borra claves que delete_import_from_redis no cubre (status, mapas).""" - extras: list[str] = [] - if job_type == "veh": - extras.append(f"veh_import_status:{job_id}") - elif job_type == "trl": - extras.append(f"trl_import_status:{job_id}") - elif job_type == "trp": - extras.append(f"trp_import_status:{job_id}") - elif job_type == "drv": - extras.append(f"drv_import_status:{job_id}") - extras.append(f"drv_import_transporter_map:{job_id}") - if extras: - try: - redis_client.delete(*extras) - except Exception: - pass diff --git a/backend/api/v1/modules/a76/layouts_csv/common/inject_csv_labels_en.py b/backend/api/v1/modules/a76/layouts_csv/common/inject_csv_labels_en.py deleted file mode 100644 index e59613da..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/common/inject_csv_labels_en.py +++ /dev/null @@ -1,30 +0,0 @@ -""" -Inyecta labels.en en definiciones de columnas TEMPLATE_COLUMNS tras cargar el dict. -""" -from __future__ import annotations - -from typing import Any, Dict, List, Optional - -from .csv_headers_en import HEADER_LABEL_EN - - -def inject_en_labels_into_columns(columns: Optional[List[Dict[str, Any]]]) -> None: - """Añade item['labels']['en'] cuando existe traducción para canonical.""" - if not columns: - return - for item in columns: - if not isinstance(item, dict): - continue - c = item.get("canonical") - if not c: - continue - en = HEADER_LABEL_EN.get(str(c).strip()) - if en: - lab = item.setdefault("labels", {}) - lab["en"] = en - - -def inject_en_labels_into_template_columns(template_columns: Dict[str, Optional[List[Dict[str, Any]]]]) -> None: - """Recorre todas las plantillas de un TEMPLATE_COLUMNS.""" - for cols in template_columns.values(): - inject_en_labels_into_columns(cols) diff --git a/backend/api/v1/modules/a76/layouts_csv/common/meta.py b/backend/api/v1/modules/a76/layouts_csv/common/meta.py deleted file mode 100644 index fc68d96f..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/common/meta.py +++ /dev/null @@ -1,35 +0,0 @@ -""" -Carga y guardado de meta (tenant_id, company_id) para imports CSV. -""" -import json -import os -from typing import Dict, Any, Tuple - - -def load_meta(file_path: str) -> Dict[str, Any]: - """Carga meta desde archivo .meta.json asociado al CSV. Devuelve dict vacío si no existe o falla.""" - meta_path = file_path.replace(".csv", ".meta.json") - if not os.path.exists(meta_path): - return {} - try: - with open(meta_path, "r", encoding="utf-8") as f: - return json.load(f) or {} - except Exception: - return {} - - -def require_tenant_context(file_path: str) -> Tuple[int, int]: - """ - Obtiene tenant_id y company_id del meta. Lanza ValueError si faltan. - """ - meta = load_meta(file_path) - tenant_id = meta.get("tenant_id") - company_id = meta.get("company_id") - if not tenant_id or not company_id: - raise ValueError("Falta contexto (tenant/company)") - return int(tenant_id), int(company_id) - - -def get_meta_path(file_path: str) -> str: - """Ruta del archivo .meta.json para un CSV.""" - return file_path.replace(".csv", ".meta.json") diff --git a/backend/api/v1/modules/a76/layouts_csv/common/normalize.py b/backend/api/v1/modules/a76/layouts_csv/common/normalize.py deleted file mode 100644 index 6af81dbb..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/common/normalize.py +++ /dev/null @@ -1,16 +0,0 @@ -""" -Normalización de cabeceras CSV (compartida por todos los módulos layouts_csv). -""" -import re -import unicodedata -from typing import Optional - - -def normalize_header(name: Optional[str]) -> str: - """Normaliza nombre de columna: NFKD, mayúsculas, sin acentos, espacios colapsados.""" - if not name: - return "" - name = unicodedata.normalize("NFKD", str(name)).upper() - name = "".join(ch for ch in name if not unicodedata.combining(ch)) - name = re.sub(r"[^A-Z0-9]+", " ", name) - return re.sub(r"\s+", " ", name).strip() diff --git a/backend/api/v1/modules/a76/layouts_csv/common/responses.py b/backend/api/v1/modules/a76/layouts_csv/common/responses.py deleted file mode 100644 index 984be8dc..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/common/responses.py +++ /dev/null @@ -1,115 +0,0 @@ -""" -Helpers para construir respuestas de scan y commit (formato unificado). -""" -from typing import Dict, Any, List, Optional - - -ERRORS_PREVIEW_LIMIT = 100 -SUMMARY_LIMIT = 10 - - -def _summarize_reasons(items: List[Dict[str, Any]], key_name: str) -> List[Dict[str, Any]]: - counts: Dict[str, int] = {} - for item in items: - reason = str(item.get(key_name, "")).strip() - if not reason: - continue - counts[reason] = counts.get(reason, 0) + 1 - ordered = sorted(counts.items(), key=lambda kv: kv[1], reverse=True) - return [{"reason": reason, "count": count} for reason, count in ordered[:SUMMARY_LIMIT]] - - -def scan_result( - job_id: str, - processed_rows: int, - error_count: int, - errors_detail: List[Dict[str, Any]], - total_rows_in_file: Optional[int] = None, - message: Optional[str] = None, -) -> Dict[str, Any]: - """Respuesta de scan_file (waiting_confirmation). - Si total_rows_in_file no se pasa, se usa processed_rows como total (comportamiento anterior). - """ - total = total_rows_in_file if total_rows_in_file is not None else processed_rows - valid_rows = processed_rows - error_count - out: Dict[str, Any] = { - "status": "waiting_confirmation", - "job_id": job_id, - "total_rows": total, - "error_count": error_count, - "valid_rows": valid_rows, - # Para no saturar el front: devolvemos solo un preview. - # El detalle completo se descarga desde CSV usando el job_id. - "errors": errors_detail[:ERRORS_PREVIEW_LIMIT], - "error_summary": _summarize_reasons([e for e in errors_detail if not e.get("warning")], "msg"), - } - if message: - out["message"] = message - elif valid_rows == 0 and total > 0: - out["message"] = ( - "Ninguna fila quedó libre de observaciones en el escaneo. " - "Revise el reporte de errores (incluye advertencias por línea), " - "complete catálogos (clientes/proveedores, transporte, conductores, etc.) " - "y vuelva a cargar, o descargue el CSV de errores para corregir el archivo." - ) - return out - - -def commit_result( - status: str, - inserted: int, - skipped_invalid: int, - skipped_missing_fk: int, - skipped_duplicate: int, - skipped_details: List[Dict[str, Any]], - message: Optional[str] = None, - error: Optional[str] = None, -) -> Dict[str, Any]: - """Respuesta de insert_valid_rows (finished / warning / failed).""" - out = { - "status": status, - "inserted": inserted, - "skipped_invalid": skipped_invalid, - "skipped_missing_fk": skipped_missing_fk, - "skipped_duplicate": skipped_duplicate, - "skipped_details": skipped_details, - "skipped_summary": _summarize_reasons(skipped_details, "reason"), - } - if message: - out["message"] = message - if error: - out["error"] = error - return out - - -def normalize_commit_status_payload(d: Dict[str, Any]) -> Dict[str, Any]: - """ - Homogeneiza respuestas de commit CSV (finished / warning) al contrato usado por pedimentos: - inserted, updated, skipped_*, skipped_details, skipped_summary, critical_reference_gaps, - reference_state_ready. No modifica waiting_confirmation ni otros status. - """ - if not isinstance(d, dict): - return d - st = d.get("status") - if st not in ("finished", "warning"): - return d - out = dict(d) - out.setdefault("inserted", 0) - out.setdefault("updated", 0) - out.setdefault("skipped_invalid", 0) - out.setdefault("skipped_missing_fk", 0) - out.setdefault("skipped_duplicate", 0) - out.setdefault("skipped_missing_invoice", 0) - details = out.get("skipped_details") - if not isinstance(details, list): - out["skipped_details"] = [] - else: - out["skipped_details"] = details - out.setdefault("critical_reference_gaps", 0) - if "reference_state_ready" not in out: - out["reference_state_ready"] = out.get("critical_reference_gaps", 0) == 0 - if "skipped_summary" not in out: - out["skipped_summary"] = ( - _summarize_reasons(out["skipped_details"], "reason") if out["skipped_details"] else [] - ) - return out diff --git a/backend/api/v1/modules/a76/layouts_csv/common/storage.py b/backend/api/v1/modules/a76/layouts_csv/common/storage.py deleted file mode 100644 index f737441a..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/common/storage.py +++ /dev/null @@ -1,281 +0,0 @@ -""" -Redis y rutas de archivos para imports CSV (compartido por módulos layouts_csv). -Cada módulo usa un job_type (ej. "part", "cls", "bom") para prefijos y nombres de archivo. -""" -import os -import base64 -import json -import logging -from typing import Optional, Set, List - -from core.paths import layout_path - -logger = logging.getLogger(__name__) - -IMPORT_REDIS_TTL = 3600 - - -class ImportStoreError(Exception): - """Fallo al guardar CSV en Redis/MinIO.""" - - pass - - -def _get_redis(): - import redis - url = os.getenv("VALKEY_URL", os.getenv("REDIS_URL", "redis://valkey:6379/0")) - return redis.Redis.from_url(url, decode_responses=False) - - -def upload_dir() -> str: - """Directorio temporal para CSV en el worker.""" - return layout_path("imports", "temp") - - -def error_dir() -> str: - """Directorio de archivos JSONL de errores.""" - return layout_path("imports", "errors") - - -def storage_keys(job_type: str, job_id: str) -> tuple: - """Prefijos Redis para un job_type y job_id. Devuelve (file_key, meta_key, error_lines_key).""" - # Facturas usa prefijo "import_" sin tipo (compatibilidad con rutas existentes) - if job_type == "" or job_type == "invoice": - prefix = "import_" - else: - prefix = f"{job_type}_import_" - return ( - f"{prefix}file:{job_id}", - f"{prefix}meta:{job_id}", - f"{prefix}error_lines:{job_id}", - ) - - -def file_path_for_job(job_type: str, job_id: str) -> str: - """Ruta local del archivo CSV para un job.""" - if job_type == "" or job_type == "invoice": - return os.path.join(upload_dir(), f"{job_id}.csv") - return os.path.join(upload_dir(), f"{job_type}_{job_id}.csv") - - -def error_path_for_job(job_type: str, job_id: str) -> str: - """Ruta del archivo JSONL de errores para un job.""" - os.makedirs(error_dir(), exist_ok=True) - if job_type == "" or job_type == "invoice": - return os.path.join(error_dir(), f"{job_id}.jsonl") - return os.path.join(error_dir(), f"{job_type}_{job_id}.jsonl") - - -def store_import_file( - job_type: str, - job_id: str, - raw_bytes: bytes, - meta_dict: dict, - tenant_id, - company_id: int, - ttl: int = IMPORT_REDIS_TTL, - log_label: str = "", -) -> None: - """ - Guarda CSV y meta en Redis. Si CSV_IMPORT_STORAGE=minio, sube el CSV a S3 y en Redis - guarda JSON {"v":2,"s3_key":...}; si no, base64 en Redis (comportamiento anterior). - En modo redis opcionalmente escribe copia local bajo layouts/imports/temp (debug). - """ - from core.config import settings - - file_key, meta_key, _ = storage_keys(job_type, job_id) - r = _get_redis() - meta_bytes = json.dumps(meta_dict).encode("utf-8") - - if settings.CSV_IMPORT_STORAGE == "minio": - from core.storage_s3 import put_csv_object, s3_key_for_csv_import - - key = s3_key_for_csv_import(tenant_id, company_id, job_type, job_id) - try: - put_csv_object(key, raw_bytes) - except Exception as e: - logger.exception("%s MinIO put failed: %s", log_label or job_type, e) - raise ImportStoreError(str(e)) from e - payload = json.dumps({"v": 2, "s3_key": key}).encode("utf-8") - r.set(file_key, payload, ex=ttl) - else: - r.set(file_key, base64.b64encode(raw_bytes), ex=ttl) - - r.set(meta_key, meta_bytes, ex=ttl) - - if settings.CSV_IMPORT_STORAGE != "minio": - try: - path = file_path_for_job(job_type, job_id) - os.makedirs(upload_dir(), exist_ok=True) - with open(path, "wb") as f: - f.write(raw_bytes) - meta_path = path.replace(".csv", ".meta.json") - with open(meta_path, "w", encoding="utf-8") as f: - json.dump(meta_dict, f) - except Exception as e: - logger.warning("%s local file save failed: %s", log_label or job_type, e) - - -def ensure_file_from_redis(job_type: str, job_id: str, log_prefix: str = "") -> Optional[str]: - """ - Obtiene el CSV desde Redis (referencia MinIO o base64 legacy) y lo escribe en disco. - Devuelve la ruta del archivo o None si no hay datos o falla. - """ - from core.config import settings - - file_key, _, _ = storage_keys(job_type, job_id) - r = _get_redis() - data = r.get(file_key) - if not data: - return None - - path = file_path_for_job(job_type, job_id) - os.makedirs(upload_dir(), exist_ok=True) - - if data.startswith(b"{"): - try: - obj = json.loads(data.decode("utf-8")) - if isinstance(obj, dict) and obj.get("v") == 2 and obj.get("s3_key"): - if settings.CSV_IMPORT_STORAGE != "minio": - logger.warning( - "%s Redis has MinIO ref but CSV_IMPORT_STORAGE=%s", - log_prefix or job_type, - settings.CSV_IMPORT_STORAGE, - ) - from core.storage_s3 import get_object_bytes - - try: - raw = get_object_bytes(obj["s3_key"]) - except Exception as e: - logger.warning("%s MinIO get failed: %s", log_prefix or job_type, e) - return None - with open(path, "wb") as f: - f.write(raw) - return path - except json.JSONDecodeError: - pass - except Exception as e: - logger.warning("%s failed to parse MinIO ref from Redis: %s", log_prefix or job_type, e) - return None - - try: - raw = base64.b64decode(data) - except Exception as e: - logger.warning("%s failed to decode file from Redis: %s", log_prefix or job_type, e) - return None - with open(path, "wb") as f: - f.write(raw) - return path - - -def ensure_meta_from_redis(job_type: str, job_id: str, file_path: str, log_prefix: str = "") -> bool: - """Descarga meta desde Redis y la escribe en .meta.json. Devuelve True si hubo datos.""" - _, meta_key, _ = storage_keys(job_type, job_id) - r = _get_redis() - data = r.get(meta_key) - if not data: - return False - try: - meta = json.loads(data.decode("utf-8")) - except Exception as e: - logger.warning("%s failed to decode meta from Redis: %s", log_prefix or job_type, e) - return False - meta_path = file_path.replace(".csv", ".meta.json") - with open(meta_path, "w", encoding="utf-8") as f: - json.dump(meta, f) - return True - - -def store_error_lines(job_type: str, job_id: str, line_numbers: List[int]) -> None: - """Guarda la lista de números de línea con error en Redis.""" - _, _, error_key = storage_keys(job_type, job_id) - try: - r = _get_redis() - r.set(error_key, json.dumps(line_numbers).encode("utf-8"), ex=IMPORT_REDIS_TTL) - except Exception as e: - logger.warning("Failed to store error lines in Redis: %s", e) - - -def get_error_lines(job_type: str, job_id: str, error_path: str) -> Set[int]: - """ - Obtiene el conjunto de líneas con error: primero desde Redis, si está vacío desde el JSONL. - """ - _, _, error_key = storage_keys(job_type, job_id) - lines = set() - try: - r = _get_redis() - raw = r.get(error_key) - if raw: - lines = set(json.loads(raw.decode("utf-8"))) - except Exception as e: - logger.debug("Could not load error lines from Redis: %s", e) - if not lines and os.path.exists(error_path): - with open(error_path, "r", encoding="utf-8") as f: - for line in f: - try: - err = json.loads(line) - if "line" in err: - lines.add(err["line"]) - except Exception: - pass - return lines - - -def delete_import_from_redis(job_type: str, job_id: str) -> None: - """Borra claves Redis del import (file, meta, error_lines).""" - file_key, meta_key, error_key = storage_keys(job_type, job_id) - try: - r = _get_redis() - r.delete(file_key, meta_key, error_key) - except Exception as e: - logger.warning("Failed to delete import keys from Redis: %s", e) - - -def cleanup_import_job( - job_type: str, - job_id: str, - file_path: Optional[str] = None, - error_path: Optional[str] = None, - meta_path: Optional[str] = None, -) -> None: - """Elimina archivos locales, objeto MinIO si aplica, y claves Redis del job.""" - from core.config import settings - - if settings.CSV_IMPORT_STORAGE == "minio": - from core.storage_s3 import delete_object_if_exists - from core.s3_keys import legacy_csv_import_key - - file_key, _, _ = storage_keys(job_type, job_id) - try: - r = _get_redis() - data = r.get(file_key) - deleted = False - if data and data.startswith(b"{"): - try: - obj = json.loads(data.decode("utf-8")) - if isinstance(obj, dict) and obj.get("v") == 2 and obj.get("s3_key"): - delete_object_if_exists(obj["s3_key"]) - deleted = True - except Exception as e: - logger.warning("Cleanup: parse Redis file ref: %s", e) - if not deleted: - delete_object_if_exists(legacy_csv_import_key(job_type, job_id)) - except Exception as e: - logger.warning("Cleanup: MinIO delete failed: %s", e) - - if file_path and os.path.exists(file_path): - try: - os.remove(file_path) - except Exception as e: - logger.warning("Cleanup: failed to remove file %s: %s", file_path, e) - if error_path and os.path.exists(error_path): - try: - os.remove(error_path) - except Exception as e: - logger.warning("Cleanup: failed to remove error file %s: %s", error_path, e) - if meta_path and os.path.exists(meta_path): - try: - os.remove(meta_path) - except Exception as e: - logger.warning("Cleanup: failed to remove meta %s: %s", meta_path, e) - delete_import_from_redis(job_type, job_id) diff --git a/backend/api/v1/modules/a76/layouts_csv/common/template_locale.py b/backend/api/v1/modules/a76/layouts_csv/common/template_locale.py deleted file mode 100644 index 4c8df10d..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/common/template_locale.py +++ /dev/null @@ -1,160 +0,0 @@ -""" -Helpers for localized CSV template header labels (ES/EN) and alias registration. - -csv_hint (opcional en cada columna TEMPLATE_COLUMNS): un dict o lista de dicts (orden importa). -- kind "enum_codes" + codes: lista corta (≤3) mostrada como "Base (a, b, c)" en descarga. -- kind "digits" + n: sufijo numérico, ES "(n dígitos)" / EN "(n digits)". -- kind "max_chars" + n: máximo caracteres (modelo / validadores CSV). -- kind "literal" + es / en: sufijo libre entre paréntesis por idioma. - -cabeceras extendidas se registran en lookup de importación vía merge_download_header_into_lookup. -""" -from __future__ import annotations - -from typing import Any, Callable, Dict, Iterator, Optional - -CsvHintDict = Dict[str, Any] - - -def normalize_locale(locale: Optional[str]) -> str: - if not locale: - return "es" - l = str(locale).lower().strip() - if l.startswith("en"): - return "en" - return "es" - - -def display_header_for_locale(column: Dict[str, Any], locale: str) -> str: - """ - Pick the CSV header cell for one column definition. - Falls back to canonical when labels are absent. - For ES: no usar labels.en antes que canonical — si solo existe labels.en (inyectado), - sin labels.es el CSV en español debe seguir usando el nombre canónico (Clarion), no el EN. - """ - loc = normalize_locale(locale) - labels = column.get("labels") - if isinstance(labels, dict): - if loc == "en": - return str(labels.get("en") or labels.get("es") or column.get("canonical") or "") - return str(labels.get("es") or column.get("canonical") or labels.get("en") or "") - return str(column.get("canonical") or "") - - -def _csv_hints_iter(column: Dict[str, Any]) -> Iterator[CsvHintDict]: - """Siempre es generador (yield from ()) para filas sin csv_hint.""" - h = column.get("csv_hint") - if h is None: - yield from () - return - if isinstance(h, list): - for item in h: - if isinstance(item, dict): - yield item - elif isinstance(h, dict): - yield h - - -def _append_suffix(text: str, suffix_body: str) -> str: - inner = suffix_body.strip() - if not inner: - return text - return f"{text} ({inner})" if text else f"({inner})" - - -def _max_chars_suffix(n: int, locale: str) -> str: - loc = normalize_locale(locale) - if loc == "en": - if n == 1: - return "max. 1 character" - return f"max. {n} characters" - if n == 1: - return "máx. 1 carácter" - return f"máx. {n} caracteres" - - -def _apply_one_csv_hint(text: str, hint: CsvHintDict, locale: str) -> str: - """Aplica un sufijo a la cabecera acumulada (display o ya con hints previos).""" - loc = normalize_locale(locale) - kind = hint.get("kind") - if kind == "enum_codes": - codes = hint.get("codes") - if not isinstance(codes, list): - return text - if len(codes) == 0 or len(codes) > 3: - return text - inner = ", ".join(str(c) for c in codes) - return _append_suffix(text, inner) - if kind == "digits": - n = hint.get("n") - if n is None: - return text - try: - ni = int(n) - except (TypeError, ValueError): - return text - suf = f"{ni} digits" if loc == "en" else f"{ni} dígitos" - return _append_suffix(text, suf) - if kind == "max_chars": - n = hint.get("n") - if n is None: - return text - try: - ni = int(n) - except (TypeError, ValueError): - return text - if ni < 0: - return text - return _append_suffix(text, _max_chars_suffix(ni, loc)) - if kind == "literal": - lit = hint.get("en") if loc == "en" else hint.get("es") - if not lit and isinstance(hint.get("es"), str): - lit = hint.get("es") - if not lit and isinstance(hint.get("en"), str): - lit = hint.get("en") - if lit: - return _append_suffix(text, str(lit)) - return text - return text - - -def download_header_cell(column: Dict[str, Any], locale: Optional[str] = None) -> str: - """ - Texto final de la celda de cabecera en plantilla descargada (incl. sufijos de ayuda). - csv_hint puede ser un dict o lista de dicts (orden: p. ej. enum_codes luego max_chars). - enum_codes solo si len(codes) ≤ 3. - """ - loc = normalize_locale(locale) - text = display_header_for_locale(column, loc) - for hint in _csv_hints_iter(column): - text = _apply_one_csv_hint(text, hint, loc) - return text - - -def merge_download_header_into_lookup( - item: Dict[str, Any], - canonical: str, - normalize_header_fn: Callable[[str], str], - lookup: Dict[str, str], -) -> None: - """Registra cabeceras de descarga ES/EN (con sufijos csv_hint) como alias del canonical.""" - for loc in ("es", "en"): - cell = download_header_cell(item, loc) - if cell: - lookup[normalize_header_fn(cell)] = canonical - - -def merge_labels_into_lookup( - item: Dict[str, Any], - canonical: str, - normalize_header_fn: Callable[[str], str], - lookup: Dict[str, str], -) -> None: - """Register labels.es / labels.en as extra normalized aliases for canonical.""" - labels = item.get("labels") - if not isinstance(labels, dict): - return - for key in ("es", "en"): - lab = labels.get(key) - if lab: - lookup[normalize_header_fn(str(lab))] = canonical diff --git a/backend/api/v1/modules/a76/layouts_csv/common/track_commit_dispatch.py b/backend/api/v1/modules/a76/layouts_csv/common/track_commit_dispatch.py deleted file mode 100644 index ec304d5a..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/common/track_commit_dispatch.py +++ /dev/null @@ -1,75 +0,0 @@ -"""Encolar `insert_valid_rows` con registro en core.task_runs y validación de acceso.""" -from __future__ import annotations - -import json -from typing import Any -from uuid import uuid4 - -from celery import Task -from fastapi import HTTPException -from sqlalchemy.orm import Session - -from api.v1.modules.core.tasks_tracking import track_and_dispatch -from core.security import validate_access_to_resource - - -def dispatch_tracked_layouts_csv_commit( - *, - db: Session, - current_user: dict[str, Any], - redis_client: Any, - layout_job_id: str, - meta_redis_key: str, - celery_task: Task, - task_name: str, - task_origin: str, - args: list[Any], - required_permissions: list[str] | None = None, -) -> str: - """ - Lee tenant_id / company_id del meta en Redis, valida acceso y despacha la tarea Celery - con un task_id nuevo (commit) para que el polling use commit_job_id. - - Si ``required_permissions`` es None, se normaliza a [] para validar acceso a compañía - (``validate_access_to_resource(..., None)`` omitiría esa validación por el atajo tipo /me). - """ - raw = redis_client.get(meta_redis_key) - if not raw: - raise HTTPException(status_code=404, detail="Job no encontrado o expiró") - meta = json.loads(raw.decode("utf-8")) - tenant_id = int(meta["tenant_id"]) - company_id = meta.get("company_id") - if company_id is None: - raise HTTPException( - status_code=403, - detail="Job sin compañía asociada o meta incompleto", - ) - effective_permissions: list[str] = ( - [] if required_permissions is None else required_permissions - ) - try: - validate_access_to_resource( - db, int(company_id), current_user, effective_permissions - ) - except HTTPException: - raise - except Exception: - raise HTTPException(status_code=403, detail="Sin acceso a este job") from None - - commit_id = str(uuid4()) - track_and_dispatch( - db=db, - task=celery_task, - tenant_id=tenant_id, - company_id=int(company_id), - requested_by_user=current_user.get("preferred_username") - or current_user.get("email") - or current_user.get("sub"), - task_name=task_name, - task_group="layouts_csv", - task_origin=task_origin, - args=args, - task_id=commit_id, - meta_payload={"layout_import_job_id": layout_job_id}, - ) - return commit_id diff --git a/backend/api/v1/modules/a76/layouts_csv/common/victor.py b/backend/api/v1/modules/a76/layouts_csv/common/victor.py deleted file mode 100644 index eab7d2bf..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/common/victor.py +++ /dev/null @@ -1,172 +0,0 @@ -""" -Limpieza periódica de archivos huérfanos de import CSV (layouts/imports/temp y errors). -""" -from __future__ import annotations - -import logging -import os -import re -import time -from typing import Optional, Tuple - -from sqlalchemy import or_ - -from api.v1.modules.core.tasks_tracking.models import TaskRun, TaskStatus -from core.celery_app import celery_app -from core.database import CoreSessionLocal - -from . import storage as common_storage -from .import_redis_keys import ( - FILE_PREFIX_TO_JOB_TYPE, - any_layout_import_key_exists, - delete_extra_layout_import_keys_from_redis, -) - -logger = logging.getLogger(__name__) - -# Evita carreras con uploads/commits recién creados (segundos) -MIN_ORPHAN_FILE_AGE_SEC = int(os.getenv("LAYOUT_ORPHAN_MIN_AGE_SEC", "600")) - -_UUID_RE = r"[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}" - - -def _get_redis(): - import redis - - url = os.getenv("VALKEY_URL", os.getenv("REDIS_URL", "redis://valkey:6379/0")) - return redis.Redis.from_url(url, decode_responses=False) - - -def parse_temp_csv_filename(name: str) -> Optional[Tuple[str, str]]: - """Devuelve (job_type, job_id) o None si no es un CSV de import conocido.""" - if not name.endswith(".csv"): - return None - base = name[:-4] - m = re.match(rf"^({_UUID_RE})$", base, re.I) - if m: - return "", m.group(1) - m = re.match(rf"^([a-z0-9]+)_({_UUID_RE})$", base, re.I) - if not m: - return None - prefix, jid = m.group(1).lower(), m.group(2) - jt = FILE_PREFIX_TO_JOB_TYPE.get(prefix) - if jt is None: - return None - return jt, jid - - -def parse_errors_jsonl_filename(name: str) -> Optional[Tuple[str, str]]: - if not name.endswith(".jsonl"): - return None - base = name[:-6] - m = re.match(rf"^({_UUID_RE})$", base, re.I) - if m: - return "", m.group(1) - m = re.match(rf"^([a-z0-9]+)_({_UUID_RE})$", base, re.I) - if not m: - return None - prefix, jid = m.group(1).lower(), m.group(2) - jt = FILE_PREFIX_TO_JOB_TYPE.get(prefix) - if jt is None: - return None - return jt, jid - - -def _layout_import_protected_by_task_run(db, job_id: str) -> bool: - row = ( - db.query(TaskRun) - .filter( - TaskRun.task_group == "layouts_csv", - TaskRun.status.in_([TaskStatus.PENDING.value, TaskStatus.ACTIVE.value]), - or_( - TaskRun.task_id == job_id, - TaskRun.meta_payload.contains({"layout_import_job_id": job_id}), - ), - ) - .first() - ) - return row is not None - - -def _reference_mtime(job_type: str, job_id: str) -> Optional[float]: - csv_path = common_storage.file_path_for_job(job_type, job_id) - err_path = common_storage.error_path_for_job(job_type, job_id) - mtimes = [] - if os.path.isfile(csv_path): - mtimes.append(os.path.getmtime(csv_path)) - if os.path.isfile(err_path): - mtimes.append(os.path.getmtime(err_path)) - if not mtimes: - return None - return max(mtimes) - - -def _try_cleanup_layout_import_job(r, db, job_type: str, job_id: str) -> bool: - if any_layout_import_key_exists(r, job_type, job_id): - return False - if _layout_import_protected_by_task_run(db, job_id): - return False - mtime = _reference_mtime(job_type, job_id) - if mtime is None: - return False - if time.time() - mtime < MIN_ORPHAN_FILE_AGE_SEC: - return False - - file_path = common_storage.file_path_for_job(job_type, job_id) - error_path = common_storage.error_path_for_job(job_type, job_id) - meta_path = None - if os.path.isfile(file_path): - meta_path = file_path.replace(".csv", ".meta.json") - - common_storage.cleanup_import_job( - job_type, - job_id, - file_path=file_path if os.path.isfile(file_path) else None, - error_path=error_path if os.path.isfile(error_path) else None, - meta_path=meta_path if meta_path and os.path.isfile(meta_path) else None, - ) - delete_extra_layout_import_keys_from_redis(r, job_type, job_id) - return True - - -def run_orphan_layout_import_cleanup() -> dict: - """Barrido síncrono; devuelve contadores para logs/resultado Celery.""" - removed_jobs: list[str] = [] - r = _get_redis() - db = CoreSessionLocal() - try: - temp_dir = common_storage.upload_dir() - os.makedirs(temp_dir, exist_ok=True) - for name in os.listdir(temp_dir): - parsed = parse_temp_csv_filename(name) - if not parsed: - continue - job_type, job_id = parsed - if _try_cleanup_layout_import_job(r, db, job_type, job_id): - removed_jobs.append(f"{job_type or 'invoice'}:{job_id}") - - err_dir = common_storage.error_dir() - if os.path.isdir(err_dir): - for name in os.listdir(err_dir): - parsed = parse_errors_jsonl_filename(name) - if not parsed: - continue - job_type, job_id = parsed - csv_path = common_storage.file_path_for_job(job_type, job_id) - if os.path.isfile(csv_path): - continue - if _try_cleanup_layout_import_job(r, db, job_type, job_id): - rid = f"{job_type or 'invoice'}:{job_id}:errors_only" - if rid not in removed_jobs: - removed_jobs.append(rid) - finally: - db.close() - - if removed_jobs: - logger.info("layout import cleanup removed %s job(s): %s", len(removed_jobs), removed_jobs[:20]) - return {"removed_count": len(removed_jobs), "removed": removed_jobs} - - -@celery_app.task(name="cleanup_orphan_layout_imports") -def cleanup_orphan_layout_imports(): - return run_orphan_layout_import_cleanup() diff --git a/backend/api/v1/modules/a76/layouts_csv/customs_brokers/__init__.py b/backend/api/v1/modules/a76/layouts_csv/customs_brokers/__init__.py deleted file mode 100644 index 2632bd0c..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/customs_brokers/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# layouts_csv.customs_brokers diff --git a/backend/api/v1/modules/a76/layouts_csv/customs_brokers/common/__init__.py b/backend/api/v1/modules/a76/layouts_csv/customs_brokers/common/__init__.py deleted file mode 100644 index 47ef2168..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/customs_brokers/common/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# common_validators, mappers, fk_loader diff --git a/backend/api/v1/modules/a76/layouts_csv/customs_brokers/common/common_validators.py b/backend/api/v1/modules/a76/layouts_csv/customs_brokers/common/common_validators.py deleted file mode 100644 index 044246f9..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/customs_brokers/common/common_validators.py +++ /dev/null @@ -1,320 +0,0 @@ -""" -Validadores reutilizables para import CSV de agentes aduanales (customs brokers). -Paridad Clarion: VALIDACIONES_AGENTE_ADUANAL, tipo MEX/AME, patente obligatoria si MEX, -país en catálogo, RFC/CURP máx, desfase. -""" -import re -from typing import Dict, Any, Optional, Set - -BROKER_KEY_MAX = 5 -LICENSE_MAX = 4 -RFC_MAX = 30 -CURP_MAX = 19 -PHONE_MIN_DIGITS = 10 -PHONE_MAX_DIGITS = 15 - -# Formato RFC/CURP (paridad con flujo manual dashboard/customs_brokers) -RFC_PATTERN = re.compile(r"^[A-Z&Ñ]{3,4}\d{6}[A-Z0-9]{3}$", re.IGNORECASE) -# Email (paridad con flujo manual via DTO) -EMAIL_PATTERN = re.compile( - r"^$|^[a-zA-Z0-9_.+-]+@[a-zA-Z0-9-]+\.[a-zA-Z0-9-.]+$" -) -# CURP: 18 caracteres (inicial, vocal, 2 letras, fecha, sexo, estado, 3 consonantes, homoclave) -CURP_PATTERN = re.compile( - r"^[A-Z][AEIOUX][A-Z]{2}\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\d|3[01])[HM]" - r"(AS|BC|BS|CC|CS|CH|CL|CM|DF|DG|GT|GR|HG|JC|MC|MN|MS|NT|NL|OC|PL|QT|QR|SP|SL|SR|TC|TS|TL|VZ|YN|ZS|NE)" - r"[B-DF-HJ-NP-TV-Z]{3}[0-9A-Z]\d$", - re.IGNORECASE, -) - - -def check_required_broker_key(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]: - """Col B: Clave de Agente Aduanal obligatoria, máx 5 caracteres.""" - clave = (row.get("CLAVE") or "").strip() - if not clave: - return { - "line": line_num, - "col": "CLAVE", - "msg": f"Error: (Celda B{line_num}) La Clave de Agente Aduanal está vacía y no se pueden hacer las validaciones.", - "solution": f"Capturar en la Celda B{line_num} una Clave de Agente Aduanal nueva o existente a la cual desee agregar, remplazar o actualizar campos", - } - if len(clave) > BROKER_KEY_MAX: - return { - "line": line_num, - "col": "CLAVE", - "msg": f"Error: (Col. B) La Clave de Agente Aduanal: {clave} supera la longitud de caracteres.", - "solution": f"Capturar en la columna B una Clave de Agente Aduanal de {BROKER_KEY_MAX} caracteres como máximo.", - } - if not re.match(r"^[a-zA-Z0-9]+$", clave): - return { - "line": line_num, - "col": "CLAVE", - "msg": "Error: (Col. B) La Clave de Agente Aduanal solo puede contener letras y números.", - "solution": "Capturar en la columna B una Clave alfanumérica.", - } - return None - - -def check_optional_license(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]: - """Col C: Patente/Licencia opcional; si tiene valor, máx 4 dígitos.""" - licencia = (row.get("LICENCIA") or "").strip() - if not licencia: - return None - if len(licencia) > LICENSE_MAX or not licencia.isdigit(): - return { - "line": line_num, - "col": "LICENCIA", - "msg": f"Error: (Col. C) La Patente debe ser de hasta {LICENSE_MAX} dígitos numéricos.", - "solution": f"Capturar en la columna C una Patente de hasta {LICENSE_MAX} dígitos.", - } - return None - - -def check_desfase(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]: - """Si COL_EXTRA (Col O) tiene valor → advertencia de desfase.""" - val = (row.get("COL_EXTRA") or "").strip() - if not val: - return None - return { - "line": line_num, - "col": "COL_EXTRA", - "msg": "Advertencia: Podría existir un desfase en esta línea.", - "solution": "Revisar esta línea del archivo CSV y verificar cada campo esté en la posición correcta.", - "warning": True, - } - - -def parse_tipo_agente_aduanal(val: Optional[str]) -> Optional[str]: - """ - Parsea TIPO (Col A): letra (M/A) o palabra (MEX/Mexicano, AME/Americano). - Devuelve 'M' o 'A'; si no reconoce, None. - """ - if not val or not str(val).strip(): - return None - s = str(val).strip() - v = s.upper() - if len(v) == 1: - if v == "M": - return "M" - if v == "A": - return "A" - v_lower = s.lower() - if v_lower in ("mex", "mexicano"): - return "M" - if v_lower in ("ame", "americano"): - return "A" - return None - - -def check_tipo_mex_ame(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]: - """ - Col A: TIPO debe ser MEX o AME (flexible: M/MEX/Mexicano, A/AME/Americano). - Si TIPO=M entonces PAIS (Col J) debe ser MEX; si TIPO=A entonces PAIS no debe ser MEX. - """ - tipo_raw = (row.get("TIPO") or "").strip() - if not tipo_raw: - return None - tipo = parse_tipo_agente_aduanal(tipo_raw) - if tipo is None: - return { - "line": line_num, - "col": "TIPO", - "msg": f"Error: (Col. A) El tipo de agente aduanal: {tipo_raw} es incorrecto.", - "solution": "Capturar en columna A el Tipo de agente aduanal correcto, MEX para Mexicano y AME para Americano.", - } - pais = (row.get("PAIS") or "").strip().upper() - if not pais: - return None - if tipo == "M" and pais != "MEX": - return { - "line": line_num, - "col": "PAIS", - "msg": f"Error: (Col. J) El tipo de cliente: {tipo_raw} tiene el país {pais}, es incorrecto.", - "solution": "Capturar en la columna J el país con clave MEX, ya que es un Agente Aduanal Mexicano", - } - if tipo == "A" and pais == "MEX": - return { - "line": line_num, - "col": "PAIS", - "msg": f"Error: (Col. J) El tipo de cliente: {tipo_raw} tiene el país {pais}, es incorrecto.", - "solution": "Capturar en la columna J un país con clave diferente de MEX, ya que es un Agente Aduanal Americano.", - } - return None - - -def check_patente_obligatoria_si_mex(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]: - """Col C: Patente obligatoria si TIPO es MEX.""" - tipo = parse_tipo_agente_aduanal((row.get("TIPO") or "").strip()) - if tipo != "M": - return None - licencia = (row.get("LICENCIA") or "").strip() - if not licencia: - clave = (row.get("CLAVE") or "").strip() - return { - "line": line_num, - "col": "LICENCIA", - "msg": f"Error: (Col. C) La Patente para el Agente Aduanal con Clave: {clave} no está capturada.", - "solution": "Capturar en la columna C una Patente de Agente Aduanal.", - } - return None - - -def check_rfc_max(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]: - """Col E: RFC opcional; si tiene valor, máx 30 caracteres.""" - val = (row.get("RFC") or "").strip() - if not val: - return None - if len(val) > RFC_MAX: - clave = (row.get("CLAVE") or "").strip() - return { - "line": line_num, - "col": "RFC", - "msg": f"Error: (Col. E) El RFC de Agente Aduanal: {clave} supera la longitud de caracteres.", - "solution": f"Capturar en la columna E un RFC de {RFC_MAX} caracteres como máximo.", - } - return None - - -def check_curp_max(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]: - """Col N: CURP/PERSONAL_ID opcional; si tiene valor, máx 19 caracteres.""" - val = (row.get("PERSONAL_ID") or "").strip() - if not val: - return None - if len(val) > CURP_MAX: - clave = (row.get("CLAVE") or "").strip() - return { - "line": line_num, - "col": "PERSONAL_ID", - "msg": f"Error: (Col. N) El CURP de Agente Aduanal: {clave} supera la longitud de caracteres.", - "solution": f"Capturar en la columna N un CURP de Agente Aduanal de {CURP_MAX} caracteres como máximo.", - } - return None - - -def check_rfc_format(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]: - """Col E: RFC opcional; si tiene valor, debe cumplir formato RFC (ej. XAXX010101XXX).""" - val = (row.get("RFC") or "").strip() - if not val: - return None - if not RFC_PATTERN.match(val): - clave = (row.get("CLAVE") or "").strip() - return { - "line": line_num, - "col": "RFC", - "msg": "Error: (Col. E) El formato del RFC es inválido.", - "solution": "Capturar en la columna E un RFC con formato válido (ej. XAXX010101XXX).", - } - return None - - -def check_curp_format(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]: - """Col N: CURP/PERSONAL_ID opcional; si tiene valor, debe cumplir formato CURP de 18 caracteres.""" - val = (row.get("PERSONAL_ID") or "").strip() - if not val: - return None - if not CURP_PATTERN.match(val): - clave = (row.get("CLAVE") or "").strip() - return { - "line": line_num, - "col": "PERSONAL_ID", - "msg": "Error: (Col. N) El formato de la CURP es inválido.", - "solution": "Capturar en la columna N un CURP con formato válido de 18 caracteres.", - } - return None - - -def check_pais_catalogo( - row: Dict[str, Any], - line_num: int, - valid_country_m3: Optional[Set[str]] = None, -) -> Optional[Dict[str, Any]]: - """Col J: Si PAIS tiene valor, debe existir en catálogo (clave M3).""" - val = (row.get("PAIS") or "").strip() - if not val or valid_country_m3 is None: - return None - if val.upper() in valid_country_m3: - return None - clave = (row.get("CLAVE") or "").strip() - return { - "line": line_num, - "col": "PAIS", - "msg": f"Error: (Col. J) El Pais del Agente Aduanal: {clave} no está en el Catálogo de Países.", - "solution": "Capturar en la columna J un País válido.", - } - - -def normalize_phone_digits(value: Optional[str]) -> Optional[str]: - """ - Normaliza teléfono a solo dígitos. - - Permite separadores: espacio, guion, paréntesis y punto. - - No permite signos ni letras (por ejemplo '+', 'ext', '#'). - - Si queda vacío tras normalizar, devuelve None. - """ - if value is None: - return None - raw = str(value).strip() - if not raw: - return None - # Caracteres aceptados en entrada para normalizar. - if re.search(r"[^0-9\s\-\(\)\.]", raw): - return None - normalized = re.sub(r"[\s\-\(\)\.]", "", raw) - if not normalized: - return None - return normalized - - -def check_phone_format(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]: - """ - Col K: TELEFONO opcional. - Si existe, debe poder normalizarse a solo dígitos y quedar entre 10 y 15. - """ - raw_phone = (row.get("TELEFONO") or "").strip() - if not raw_phone: - return None - - normalized_phone = normalize_phone_digits(raw_phone) - if not normalized_phone: - return { - "line": line_num, - "col": "TELEFONO", - "msg": "Error: (Col. K) El teléfono contiene texto o símbolos no permitidos.", - "solution": "Capturar en la columna K solo números o separadores válidos (espacio, guion, paréntesis o punto).", - } - - digits = len(normalized_phone) - if digits < PHONE_MIN_DIGITS or digits > PHONE_MAX_DIGITS: - return { - "line": line_num, - "col": "TELEFONO", - "msg": f"Error: (Col. K) El teléfono debe contener entre {PHONE_MIN_DIGITS} y {PHONE_MAX_DIGITS} dígitos.", - "solution": f"Capturar en la columna K un teléfono válido de {PHONE_MIN_DIGITS} a {PHONE_MAX_DIGITS} dígitos.", - } - - # Se deja la fila ya normalizada para mapear/persistir consistentemente. - row["TELEFONO"] = normalized_phone - return None - - -def check_optional_email_format(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]: - """ - Col M: EMAIL opcional. - Si viene informado (no vacío), debe cumplir formato email. - - Nota: Se rechaza explícitamente texto como 'NULL' (string literal), - para evitar que el endpoint de listado falle por validación Pydantic. - """ - raw_email = (row.get("EMAIL") or "").strip() - if not raw_email: - return None - - # EMAIL_PATTERN incluye el caso '' via ^$; aun así aquí raw_email no está vacío. - if not EMAIL_PATTERN.match(raw_email): - clave = (row.get("CLAVE") or "").strip() - return { - "line": line_num, - "col": "EMAIL", - "msg": f"Error: (Col. M) El correo electrónico del Agente Aduanal con Clave: {clave} es inválido.", - "solution": "Capturar en la columna M un correo con formato válido (ej. usuario@dominio.com) o dejar la celda vacía.", - } - return None diff --git a/backend/api/v1/modules/a76/layouts_csv/customs_brokers/common/fk_loader.py b/backend/api/v1/modules/a76/layouts_csv/customs_brokers/common/fk_loader.py deleted file mode 100644 index 255f2603..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/customs_brokers/common/fk_loader.py +++ /dev/null @@ -1,26 +0,0 @@ -""" -Carga de conjuntos FK para validación de import CSV de agentes aduanales. -Clarion: catálogo de países (GPaises / m3_key). -""" -from typing import Set -import logging - -from core.database import CoreSessionLocal - -logger = logging.getLogger(__name__) - - -def load_customs_brokers_fk_sets() -> Set[str]: - """ - Carga valid_country_m3 (códigos país m3_key) para validar Col J PAIS. - """ - valid_country_m3: Set[str] = set() - try: - with CoreSessionLocal() as session: - from api.v1.modules.public.reference_data.countries.models import Country - for row in session.query(Country.m3_key).all(): - if row[0]: - valid_country_m3.add((row[0] or "").strip().upper()) - except Exception as e: - logger.warning("Customs brokers import: could not load country m3 keys: %s", e) - return valid_country_m3 diff --git a/backend/api/v1/modules/a76/layouts_csv/customs_brokers/common/mappers.py b/backend/api/v1/modules/a76/layouts_csv/customs_brokers/common/mappers.py deleted file mode 100644 index 1ddfb4d1..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/customs_brokers/common/mappers.py +++ /dev/null @@ -1,77 +0,0 @@ -""" -Mapeo fila CSV → datos para CustomsBroker. -Clarion: TIPO normalizado a M/A con parse_tipo_agente_aduanal; CURP/personal_id máx 19. -""" -from typing import Dict, Any, Optional - -from .common_validators import parse_tipo_agente_aduanal, CURP_MAX, normalize_phone_digits - -MAX_LEN = { - "broker_key": 5, - "type": 9, - "name": 80, - "address": 1500, - "postal_code": 15, - "city": 30, - "state": 30, - "phone": 30, - "fax": 30, - "email": 100, - "country": 3, - "tax_id": 30, - "personal_id": 20, - "position": 30, - "license": 4, - "company": 200, - "contact": 80, -} - - -def _str_or_none(val: Any, max_len: Optional[int] = None) -> Optional[str]: - if val is None: - return None - s = str(val).strip() - if not s: - return None - if max_len and len(s) > max_len: - return s[:max_len] - return s - - -def _license_value(row_norm: Dict[str, Any]) -> Optional[str]: - lic = (row_norm.get("LICENCIA") or "").strip() - if not lic or not lic.isdigit(): - return None - return lic[:4] - - -def row_to_customs_broker_data( - row_norm: Dict[str, Any], tenant_id: int, company_id: int -) -> Dict[str, Any]: - """Build dict for CustomsBroker model (create or update).""" - clave = _str_or_none(row_norm.get("CLAVE"), MAX_LEN["broker_key"]) - if not clave: - return {} - tipo_raw = (row_norm.get("TIPO") or "").strip() - tipo_normalized = parse_tipo_agente_aduanal(tipo_raw) if tipo_raw else None - return { - "tenant_id": tenant_id, - "company_id": company_id, - "broker_key": clave, - "type": tipo_normalized or _str_or_none(row_norm.get("TIPO"), MAX_LEN["type"]), - "name": _str_or_none(row_norm.get("NOMBRE"), MAX_LEN["name"]), - "address": _str_or_none(row_norm.get("DIRECCION"), MAX_LEN["address"]), - "postal_code": _str_or_none(row_norm.get("CODIGO POSTAL"), MAX_LEN["postal_code"]), - "city": _str_or_none(row_norm.get("CIUDAD"), MAX_LEN["city"]), - "state": _str_or_none(row_norm.get("ESTADO"), MAX_LEN["state"]), - "phone": _str_or_none(normalize_phone_digits(row_norm.get("TELEFONO")), MAX_LEN["phone"]), - "fax": _str_or_none(row_norm.get("FAX"), MAX_LEN["fax"]), - "email": _str_or_none(row_norm.get("EMAIL"), MAX_LEN["email"]), - "country": _str_or_none(row_norm.get("PAIS"), MAX_LEN["country"]), - "tax_id": _str_or_none(row_norm.get("RFC"), MAX_LEN["tax_id"]), - "personal_id": _str_or_none(row_norm.get("PERSONAL_ID"), CURP_MAX), - "position": _str_or_none(row_norm.get("POSICION"), MAX_LEN["position"]), - "license": _license_value(row_norm), - "company": _str_or_none(row_norm.get("EMPRESA"), MAX_LEN["company"]), - "contact": _str_or_none(row_norm.get("CONTACTO"), MAX_LEN["contact"]), - } diff --git a/backend/api/v1/modules/a76/layouts_csv/customs_brokers/csv_max_chars_for_headers.py b/backend/api/v1/modules/a76/layouts_csv/customs_brokers/csv_max_chars_for_headers.py deleted file mode 100644 index 6e3a50fc..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/customs_brokers/csv_max_chars_for_headers.py +++ /dev/null @@ -1,23 +0,0 @@ -"""Máximos y enums para cabeceras CSV agentes aduanales (validadores / mappers).""" -from typing import Dict - -from .common.mappers import MAX_LEN - -CUSTOMS_BROKERS_CSV_MAX_CHARS: Dict[str, int] = { - "CLAVE": MAX_LEN["broker_key"], - "LICENCIA": MAX_LEN["license"], - "NOMBRE": MAX_LEN["name"], - "RFC": MAX_LEN["tax_id"], - "DIRECCION": MAX_LEN["address"], - "CODIGO POSTAL": MAX_LEN["postal_code"], - "CIUDAD": MAX_LEN["city"], - "ESTADO": MAX_LEN["state"], - "PAIS": MAX_LEN["country"], - "TELEFONO": MAX_LEN["phone"], - "FAX": MAX_LEN["fax"], - "EMAIL": MAX_LEN["email"], - "PERSONAL_ID": MAX_LEN["personal_id"], - "POSICION": MAX_LEN["position"], - "EMPRESA": MAX_LEN["company"], - "CONTACTO": MAX_LEN["contact"], -} diff --git a/backend/api/v1/modules/a76/layouts_csv/customs_brokers/routes.py b/backend/api/v1/modules/a76/layouts_csv/customs_brokers/routes.py deleted file mode 100644 index 5c5ce0b7..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/customs_brokers/routes.py +++ /dev/null @@ -1,191 +0,0 @@ -""" -Rutas de importación CSV para Agentes Aduanales. -Mismo flujo que a76.imports: upload → scan → status (polling) → commit. -""" -import json -import logging -import os -from uuid import uuid4 - -from fastapi import APIRouter, File, HTTPException, Query, UploadFile, Depends -from sqlalchemy.orm import Session -from typing import Dict, Any - -from core.celery_app import celery_app -from core.database import get_core_db -from core.security import get_current_user, validate_access_to_resource -from api.v1.modules.core.tasks_tracking import track_and_dispatch - -from ..common.track_commit_dispatch import dispatch_tracked_layouts_csv_commit -from ..common.responses import normalize_commit_status_payload -from .schemas import ImportJobResponse -from .tasks import ( - scan_file, - insert_valid_rows, - JOB_TYPE, - CB_IMPORT_META_PREFIX, - CB_IMPORT_REDIS_TTL, -) -from ..common import storage as common_storage -from ..common.error_csv import download_scan_errors_csv_stream - -router = APIRouter() -logger = logging.getLogger(__name__) - - -def _get_redis(): - import redis - url = os.getenv("VALKEY_URL", os.getenv("REDIS_URL", "redis://valkey:6379/0")) - return redis.Redis.from_url(url, decode_responses=False) - - -@router.post("/upload", response_model=ImportJobResponse) -async def upload_import_file( - file: UploadFile = File(...), - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """ - Fase 1: Subir CSV, guardar en Redis, encolar tarea de escaneo. - """ - try: - tenant_id = validate_access_to_resource(db, company_id, current_user, ["csv_upload.process"]) - except Exception as e: - logger.error(f"CB import: access validation failed: {e}") - raise HTTPException(status_code=403, detail="Invalid company access or missing permissions") - - if not file.filename or not file.filename.lower().endswith(".csv"): - raise HTTPException(status_code=400, detail="Solo se permiten archivos .csv") - - job_id = str(uuid4()) - contents = await file.read() - - meta_data = { - "tenant_id": tenant_id, - "company_id": company_id, - "user_id": current_user.get("id"), - "template_id": "customs_brokers", - } - - try: - common_storage.store_import_file( - JOB_TYPE, - job_id, - contents, - meta_data, - tenant_id=int(tenant_id), - company_id=company_id, - ttl=CB_IMPORT_REDIS_TTL, - log_label="CB import", - ) - except common_storage.ImportStoreError as e: - logger.error(f"CB import: store error: {e}") - raise HTTPException(status_code=500, detail="No se pudo encolar el archivo.") - except Exception as e: - logger.error(f"CB import: Redis store error: {e}") - raise HTTPException(status_code=500, detail="No se pudo encolar el archivo.") - - track_and_dispatch( - db=db, - task=scan_file, - tenant_id=int(tenant_id), - company_id=company_id, - requested_by_user=current_user.get("preferred_username") - or current_user.get("email") - or current_user.get("sub"), - task_name="customs_brokers_scan_file", - task_group="layouts_csv", - task_origin="a76/layouts_csv/customs_brokers/upload", - args=[job_id], - task_id=job_id, - ) - - return ImportJobResponse( - job_id=job_id, - status="queued", - message="Archivo subido. Escaneo iniciado.", - ) - - -@router.get("/{job_id}/status") -async def get_import_status(job_id: str): - """ - Polling: estado del escaneo o del commit. - """ - task_result = celery_app.AsyncResult(job_id) - - if task_result.state == "PENDING": - return {"status": "processing", "progress": 0} - if task_result.state == "PROGRESS": - info = (task_result.info or {}) - return { - "status": "processing", - "progress": info.get("current", 0), - "total": info.get("total", 0), - } - if task_result.state == "SUCCESS": - result = task_result.result - if isinstance(result, dict) and "status" in result: - return normalize_commit_status_payload(result) - return {"status": "finished", "result": result} - - # A veces Celery tiene el result disponible pero state aún no es SUCCESS; si el result es éxito, devolverlo - result = getattr(task_result, "result", None) - if isinstance(result, dict) and result.get("status") in ("finished", "warning"): - return normalize_commit_status_payload(result) - - logger.warning("CB import task %s failed: state=%s", job_id, task_result.state) - err_msg = None - tb = getattr(task_result, "traceback", None) - if tb and isinstance(tb, str): - lines = [l.strip() for l in tb.strip().split("\n") if l.strip()] - if lines: - err_msg = lines[-1] - if not err_msg: - try: - exc = task_result.get(propagate=False) - if exc is not None: - err_msg = str(exc) - except Exception: - pass - if not err_msg and result is not None: - if not isinstance(result, dict): - err_msg = str(result) - elif result.get("error") or result.get("message"): - err_msg = result.get("error") or result.get("message") - return {"status": "failed", "error": err_msg or "Task failed"} - - -@router.post("/{job_id}/commit") -async def commit_import_job( - job_id: str, - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """ - Fase 2: Usuario confirma; se encola la inserción de filas válidas. - """ - r = _get_redis() - commit_id = dispatch_tracked_layouts_csv_commit( - db=db, - current_user=current_user, - redis_client=r, - layout_job_id=job_id, - meta_redis_key=f"{CB_IMPORT_META_PREFIX}{job_id}", - celery_task=insert_valid_rows, - task_name="customs_brokers_insert_valid_rows", - task_origin="a76/layouts_csv/customs_brokers/commit", - args=[job_id], - required_permissions=["csv_upload.process"], - ) - return { - "status": "committing", - "message": "Inserción iniciada.", - "commit_job_id": commit_id, - } - - -@router.get("/{job_id}/errors/scan-csv") -async def download_scan_errors_csv(job_id: str): - return download_scan_errors_csv_stream("cb", job_id) diff --git a/backend/api/v1/modules/a76/layouts_csv/customs_brokers/schemas.py b/backend/api/v1/modules/a76/layouts_csv/customs_brokers/schemas.py deleted file mode 100644 index 7d9b548c..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/customs_brokers/schemas.py +++ /dev/null @@ -1,22 +0,0 @@ -from pydantic import BaseModel -from typing import Optional - -class ImportJobResponse(BaseModel): - job_id: str - status: str - message: str - -class CommitRequest(BaseModel): - pass # no body needed for single model - -class ImportJobStatus(BaseModel): - status: str - job_id: str - total_rows: Optional[int] = 0 - error_count: Optional[int] = 0 - valid_rows: Optional[int] = 0 - error: Optional[str] = None - inserted: Optional[int] = 0 - skipped_invalid: Optional[int] = 0 - skipped_missing_fk: Optional[int] = 0 - skipped_details: Optional[list] = None diff --git a/backend/api/v1/modules/a76/layouts_csv/customs_brokers/tasks.py b/backend/api/v1/modules/a76/layouts_csv/customs_brokers/tasks.py deleted file mode 100644 index 0c13b14f..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/customs_brokers/tasks.py +++ /dev/null @@ -1,286 +0,0 @@ -""" -Tareas Celery para importación CSV de Agentes Aduanales. -Flujo: scan_file (validación) → insert_valid_rows (commit). -Usa layouts_csv.common (storage, normalize, meta, responses, csv_reader). -""" -import json -import logging -import os -from typing import Dict, Any, Optional, List, Set - -from core.celery_app import celery_app -from core.database import CoreSessionLocal - -from ..common import storage as common_storage -from ..common import normalize as common_normalize -from ..common import meta as common_meta -from ..common import responses as common_responses -from ..common import csv_reader as common_csv_reader -from .template_config import ( - row_from_template, - CUSTOMS_BROKERS_FIELDNAMES_ORDER, - CUSTOMS_BROKERS_HEADERLESS_FIRST_CELL, -) -from .validators import validate_row_customs_broker -from .common.mappers import row_to_customs_broker_data -from .common.fk_loader import load_customs_brokers_fk_sets - -logger = logging.getLogger(__name__) - -JOB_TYPE = "cb" - -# Para routes.py -CB_IMPORT_FILE_PREFIX = "cb_import_file:" -CB_IMPORT_META_PREFIX = "cb_import_meta:" -CB_IMPORT_ERROR_LINES_PREFIX = "cb_import_error_lines:" -CB_IMPORT_REDIS_TTL = common_storage.IMPORT_REDIS_TTL - - -def _do_scan(job_id: str, progress_callback: Optional[Any] = None) -> Dict[str, Any]: - file_path = common_storage.ensure_file_from_redis(JOB_TYPE, job_id, "CB import") - if not file_path: - return {"status": "failed", "error": "Archivo no encontrado (expirado o no subido). Sube de nuevo."} - common_storage.ensure_meta_from_redis(JOB_TYPE, job_id, file_path, "CB import") - - error_path = common_storage.error_path_for_job(JOB_TYPE, job_id) - - try: - total_rows = common_csv_reader.count_csv_rows(file_path) - except Exception as e: - return {"status": "failed", "error": str(e)} - - try: - tenant_id, company_id = common_meta.require_tenant_context(file_path) - except ValueError as e: - return {"status": "failed", "error": str(e)} - - meta = common_meta.load_meta(file_path) or {} - actualizar = meta.get("actualizar", False) - existing_broker_keys: Set[str] = set() - if actualizar: - try: - from api.v1.modules.a76.customs_brokers.models import CustomsBroker - with CoreSessionLocal() as session: - for b in ( - session.query(CustomsBroker) - .filter( - CustomsBroker.tenant_id == tenant_id, - CustomsBroker.company_id == company_id, - ) - .all() - ): - if (b.broker_key or "").strip(): - existing_broker_keys.add((b.broker_key or "").strip()) - except Exception as e: - logger.warning("CB import: could not load existing broker_keys for ACT: %s", e) - - valid_country_m3 = load_customs_brokers_fk_sets() - - error_count = 0 - processed_rows = 0 - errors_detail: List[Dict[str, Any]] = [] - error_lines_list: List[int] = [] - - try: - with open(error_path, "w", encoding="utf-8") as f_err: - for i, row in common_csv_reader.iter_csv_rows( - file_path, - fieldnames=CUSTOMS_BROKERS_FIELDNAMES_ORDER, - headerless_first_cell_values=CUSTOMS_BROKERS_HEADERLESS_FIRST_CELL, - ): - if progress_callback: - progress_callback(i, total_rows, error_count) - - row_norm = row_from_template(row, common_normalize.normalize_header) - row_errors = validate_row_customs_broker( - row_norm, - i, - actualizar=actualizar, - existing_broker_keys=existing_broker_keys, - valid_country_m3=valid_country_m3, - ) - if row_errors: - has_blocking_error = any(not e.get("warning", False) for e in row_errors) - if has_blocking_error: - error_count += 1 - # Guardar una sola vez por renglón (aunque haya múltiples errores en la misma línea). - error_lines_list.append(i) - for e in row_errors: - if not e.get("warning", False): - f_err.write(json.dumps(e) + "\n") - if len(errors_detail) < 500: - for e in row_errors: - if len(errors_detail) >= 500: - break - errors_detail.append({ - "line": e["line"], - "col": e.get("col", ""), - "msg": e.get("msg", ""), - "solution": e.get("solution", ""), - "warning": bool(e.get("warning", False)), - }) - processed_rows += 1 - - if error_lines_list: - common_storage.store_error_lines(JOB_TYPE, job_id, error_lines_list) - except Exception as e: - logger.error("CB import scan failed: %s", e) - return {"status": "failed", "error": str(e)} - - return common_responses.scan_result(job_id, processed_rows, error_count, errors_detail) - - -@celery_app.task(bind=True) -def scan_file(self, job_id: str, config: str = None): - logger.info("CB import: starting scan for job %s", job_id) - - def on_progress(current: int, total: int, errors: int) -> None: - self.update_state(state="PROGRESS", meta={"current": current, "total": total, "errors": errors}) - - return _do_scan(job_id, progress_callback=on_progress) - - -def _do_commit(job_id: str) -> Dict[str, Any]: - file_path = common_storage.ensure_file_from_redis(JOB_TYPE, job_id, "CB import") - if not file_path: - alt_path = common_storage.file_path_for_job(JOB_TYPE, job_id) - if not os.path.exists(alt_path): - return {"status": "failed", "error": "Archivo no encontrado (expirado). Sube y confirma de nuevo."} - file_path = alt_path - else: - common_storage.ensure_meta_from_redis(JOB_TYPE, job_id, file_path, "CB import") - - error_path = common_storage.error_path_for_job(JOB_TYPE, job_id) - error_lines = common_storage.get_error_lines(JOB_TYPE, job_id, error_path) - - try: - tenant_id, company_id = common_meta.require_tenant_context(file_path) - except ValueError as e: - return {"status": "failed", "error": str(e)} - - meta = common_meta.load_meta(file_path) or {} - actualizar = meta.get("actualizar", False) - valid_country_m3 = load_customs_brokers_fk_sets() - - from api.v1.modules.a76.customs_brokers.models import CustomsBroker - - inserted_count = 0 - skipped_invalid = 0 - skipped_details: List[Dict[str, Any]] = [] - meta_path = common_meta.get_meta_path(file_path) - - try: - with CoreSessionLocal() as session: - existing_by_key: Dict[str, CustomsBroker] = {} - for b in ( - session.query(CustomsBroker) - .filter( - CustomsBroker.tenant_id == tenant_id, - CustomsBroker.company_id == company_id, - ) - .all() - ): - existing_by_key[b.broker_key] = b - - existing_broker_keys = set(existing_by_key.keys()) - - for i, row in common_csv_reader.iter_csv_rows( - file_path, - fieldnames=CUSTOMS_BROKERS_FIELDNAMES_ORDER, - headerless_first_cell_values=CUSTOMS_BROKERS_HEADERLESS_FIRST_CELL, - ): - if i in error_lines: - continue - - row_norm = row_from_template(row, common_normalize.normalize_header) - row_errors = validate_row_customs_broker( - row_norm, - i, - actualizar=actualizar, - existing_broker_keys=existing_broker_keys, - valid_country_m3=valid_country_m3, - ) - if row_errors and any(not e.get("warning", False) for e in row_errors): - skipped_invalid += 1 - blocking_errors = [e for e in row_errors if not e.get("warning", False)] - for blocking in blocking_errors: - skipped_details.append({ - "line": i, - "reason": f"{blocking.get('col', '')}: {blocking.get('msg', '')}", - "solution": blocking.get("solution", ""), - }) - continue - - data = row_to_customs_broker_data(row_norm, tenant_id, company_id) - if not data or not data.get("broker_key"): - skipped_invalid += 1 - continue - - clave = data["broker_key"] - existing = existing_by_key.get(clave) - if existing: - for k, v in data.items(): - if k not in ("tenant_id", "company_id", "broker_key"): - setattr(existing, k, v) - session.add(existing) - else: - new_broker = CustomsBroker(**data) - session.add(new_broker) - existing_by_key[clave] = new_broker - inserted_count += 1 - - try: - session.commit() - except Exception as db_err: - session.rollback() - logger.error("CB import DB error: %s", db_err) - return {"status": "failed", "error": str(db_err)} - - except Exception as e: - logger.exception("CB import task failed") - return {"status": "failed", "error": str(e)} - - common_storage.cleanup_import_job( - JOB_TYPE, job_id, - file_path=file_path, - error_path=error_path, - meta_path=meta_path, - ) - - if inserted_count == 0 and skipped_invalid > 0: - return { - "status": "warning", - "inserted": 0, - "updated": 0, - "skipped_invalid": skipped_invalid, - "skipped_duplicate": 0, - "skipped_missing_fk": 0, - "skipped_details": skipped_details, - "message": f"No se insertaron registros. {skipped_invalid} rechazados.", - } - if inserted_count == 0: - return { - "status": "failed", - "error": "No hay registros válidos en el archivo CSV", - "inserted": 0, - "updated": 0, - "skipped_invalid": skipped_invalid, - "skipped_duplicate": 0, - "skipped_missing_fk": 0, - "skipped_details": skipped_details, - } - return { - "status": "finished", - "inserted": inserted_count, - "updated": 0, - "skipped_invalid": skipped_invalid, - "skipped_duplicate": 0, - "skipped_missing_fk": 0, - "skipped_details": skipped_details, - } - - -@celery_app.task(bind=True) -def insert_valid_rows(self, job_id: str): - logger.info("CB import: starting commit for job %s", job_id) - return _do_commit(job_id) diff --git a/backend/api/v1/modules/a76/layouts_csv/customs_brokers/template_config.py b/backend/api/v1/modules/a76/layouts_csv/customs_brokers/template_config.py deleted file mode 100644 index d91c411f..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/customs_brokers/template_config.py +++ /dev/null @@ -1,117 +0,0 @@ -""" -Configuración de plantilla CSV para Agentes Aduanales (EstructuraCatAgenteAduanal.xls). -Layout Clarion: A=TIPO, B=CLAVE AADUANAL, C=PATENTE, D=NOMBRE, E=RFC, F=DIRECCION, G=CODIGO POSTAL, -H=CIUDAD, I=ESTADO, J=PAIS, K=TELEFONO, L=NUMERO FAX, M=CORREO ELECTRONICO, N=CURP, O=COL_EXTRA (desfase). -Encabezado ejemplo: "TIPO(MEX=Mexicano,AME=AMERICANO)",CLAVE AADUANAL,PATENTE,NOMBRE,RFC,... -Solo se leen columnas definidas aquí; el resto se ignora. -""" - -from typing import Dict, List, Any, Optional - -from ..common.cell_value import cell_to_str -from ..common.csv_hint_inject import inject_max_char_hints_into_columns, merge_enum_hint_before_max -from ..common.inject_csv_labels_en import inject_en_labels_into_template_columns -from ..common.template_locale import merge_download_header_into_lookup, merge_labels_into_lookup -from .csv_max_chars_for_headers import CUSTOMS_BROKERS_CSV_MAX_CHARS - -TEMPLATE_COLUMNS: Dict[str, List[Dict[str, Any]]] = { - "customs_brokers": [ - # Col A - TIPO (MEX/Mexicano, AME/Americano) - {"canonical": "TIPO", "aliases": ["TIPO(MEX=Mexicano,AME=AMERICANO)"]}, - # Col B - Clave agente aduanal (máx 5) - {"canonical": "CLAVE", "aliases": ["BROKER_KEY", "CLAVE AGENTE", "CLAVE AADUANAL", "CLAVE ADUANAL", "ID"]}, - # Col C - Patente / Licencia (obligatoria si TIPO=MEX) - {"canonical": "LICENCIA", "aliases": ["PATENTE", "LICENSE"]}, - # Col D - Nombre - {"canonical": "NOMBRE", "aliases": ["NOMBRE COMPLETO", "RAZON SOCIAL"]}, - # Col E - RFC (máx 30) - {"canonical": "RFC", "aliases": ["TAX_ID", "TAXID"]}, - # Col F - Dirección - {"canonical": "DIRECCION", "aliases": ["DOMICILIO"]}, - # Col G - Código postal - {"canonical": "CODIGO POSTAL", "aliases": ["CODIGOPOSTAL", "CP", "C.P."]}, - # Col H - Ciudad - {"canonical": "CIUDAD"}, - # Col I - Estado - {"canonical": "ESTADO"}, - # Col J - País (clave M3) - {"canonical": "PAIS", "aliases": ["COUNTRY"]}, - # Col K - Teléfono - {"canonical": "TELEFONO", "aliases": ["PHONE", "TEL"]}, - # Col L - Fax - {"canonical": "FAX", "aliases": ["NUMERO FAX"]}, - # Col M - Email - {"canonical": "EMAIL", "aliases": ["CORREO", "E-MAIL", "CORREO ELECTRONICO"]}, - # Col N - CURP / Personal ID (máx 19) - {"canonical": "PERSONAL_ID", "aliases": ["PERSONALID", "ID PERSONAL", "CURP"]}, - # Col O - Desfase (si tiene valor → advertencia) - {"canonical": "COL_EXTRA", "aliases": ["COLUMNA EXTRA", "DESFASE"]}, - # Otros opcionales (no en encabezado oficial) - {"canonical": "POSICION", "aliases": ["CARGO", "PUESTO"]}, - {"canonical": "EMPRESA", "aliases": ["COMPANY"]}, - {"canonical": "CONTACTO", "aliases": ["CONTACT"]}, - ], -} - -inject_en_labels_into_template_columns(TEMPLATE_COLUMNS) - -inject_max_char_hints_into_columns( - TEMPLATE_COLUMNS.get("customs_brokers"), CUSTOMS_BROKERS_CSV_MAX_CHARS -) -for _col in TEMPLATE_COLUMNS.get("customs_brokers") or []: - if _col.get("canonical") == "TIPO": - merge_enum_hint_before_max(_col, ["MEX", "AME"]) - -# Orden oficial de columnas (para CSV sin encabezado o detección) -CUSTOMS_BROKERS_FIELDNAMES_ORDER = [ - "TIPO", - "CLAVE", - "LICENCIA", - "NOMBRE", - "RFC", - "DIRECCION", - "CODIGO POSTAL", - "CIUDAD", - "ESTADO", - "PAIS", - "TELEFONO", - "FAX", - "EMAIL", - "PERSONAL_ID", -] - -# Si la primera celda de la primera fila está en este set, se trata como CSV sin encabezado -CUSTOMS_BROKERS_HEADERLESS_FIRST_CELL = frozenset( - {"MEX", "AME", "M", "A", "MEXICANO", "AMERICANO"} -) - - -def build_normalized_lookup(normalize_header_fn) -> Dict[str, str]: - """normalized_header -> canonical_name para plantilla customs_brokers.""" - cols = TEMPLATE_COLUMNS.get("customs_brokers") - if not cols: - return {} - lookup: Dict[str, str] = {} - for item in cols: - canonical = item["canonical"] - lookup[normalize_header_fn(canonical)] = canonical - for alias in item.get("aliases") or []: - lookup[normalize_header_fn(alias)] = canonical - merge_labels_into_lookup(item, canonical, normalize_header_fn, lookup) - merge_download_header_into_lookup(item, canonical, normalize_header_fn, lookup) - for idx, name in enumerate(CUSTOMS_BROKERS_FIELDNAMES_ORDER): - lookup[normalize_header_fn(f"_COL_{idx}_")] = name - return lookup - - -def row_from_template(row: Dict[str, Any], normalize_header_fn) -> Dict[str, Any]: - """Fila CSV con solo columnas de la plantilla, en nombres canónicos.""" - lookup = build_normalized_lookup(normalize_header_fn) - if not lookup: - return {normalize_header_fn(k): cell_to_str(v) for k, v in row.items()} - out: Dict[str, Any] = {} - for csv_header, value in row.items(): - key_norm = normalize_header_fn(csv_header) - if key_norm in lookup: - out[lookup[key_norm]] = cell_to_str(value) - return out diff --git a/backend/api/v1/modules/a76/layouts_csv/customs_brokers/validators/__init__.py b/backend/api/v1/modules/a76/layouts_csv/customs_brokers/validators/__init__.py deleted file mode 100644 index ecfaa325..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/customs_brokers/validators/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .create import validate_row_customs_broker - -__all__ = ["validate_row_customs_broker"] diff --git a/backend/api/v1/modules/a76/layouts_csv/customs_brokers/validators/common.py b/backend/api/v1/modules/a76/layouts_csv/customs_brokers/validators/common.py deleted file mode 100644 index e386451d..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/customs_brokers/validators/common.py +++ /dev/null @@ -1,137 +0,0 @@ -""" -Validaciones comunes de fila para import CSV de agentes aduanales. -Paridad Clarion: VALIDA_TODA_AGENTE_ADUANAL, VALIDA_PARCIAL_AGENTE_ADUANAL, VALIDACIONES_AGENTE_ADUANAL. -""" -from typing import Dict, Any, Optional, Set, List - -from ..common.common_validators import ( - check_required_broker_key, - check_optional_license, - check_desfase, - check_tipo_mex_ame, - check_patente_obligatoria_si_mex, - check_rfc_max, - check_curp_max, - check_rfc_format, - check_curp_format, - check_pais_catalogo, - check_phone_format, - check_optional_email_format, -) - -MSG_CLAVE_NO_EXISTE = "Error: (Col. B) Clave de A. Aduanal No Existe en el Catalogo." -MSG_CLAVE_NO_EXISTE_SOLUCION = "La clave debe existir en el catálogo cuando el modo es Actualizar." -MSG_CAMPOS_OBLIGATORIOS = "Existen campos vacíos que son obligatorios, es la {campos}." -MSG_CAMPOS_OBLIGATORIOS_SOLUCION = "Revisar la línea del archivo y capturar los campos con la información correcta." - - -def validaciones_agente_aduanal( - row: Dict[str, Any], - line_num: int, - valid_country_m3: Optional[Set[str]] = None, -) -> List[Dict[str, Any]]: - """ - VALIDACIONES_AGENTE_ADUANAL: reglas de dominio compartidas. - Tipo MEX/AME + coherencia País, patente si MEX, RFC/CURP máx, país en catálogo, licencia formato. - """ - errors: List[Dict[str, Any]] = [] - - checks = [ - check_tipo_mex_ame, - check_patente_obligatoria_si_mex, - check_optional_license, - check_rfc_max, - check_rfc_format, - check_curp_max, - check_curp_format, - lambda r, ln: check_pais_catalogo(r, ln, valid_country_m3), - check_optional_email_format, - check_phone_format, - ] - - for fn in checks: - err = fn(row, line_num) - if err: - errors.append(err) - - return errors - - -def valida_toda_agente_aduanal( - row: Dict[str, Any], - line_num: int, - valid_country_m3: Optional[Set[str]] = None, -) -> List[Dict[str, Any]]: - """ - VALIDA_TODA: TIPO (Col A) y NOMBRE (Col D) obligatorios; luego VALIDACIONES_AGENTE_ADUANAL. - """ - campos_oblig = [] - if not (row.get("TIPO") or "").strip(): - campos_oblig.append("(Col.A) Tipo") - if not (row.get("NOMBRE") or "").strip(): - campos_oblig.append("(Col.D) Nombre") - if campos_oblig: - err = { - "line": line_num, - "col": "TIPO" if not (row.get("TIPO") or "").strip() else "NOMBRE", - "msg": MSG_CAMPOS_OBLIGATORIOS.format(campos=", ".join(campos_oblig)), - "solution": MSG_CAMPOS_OBLIGATORIOS_SOLUCION, - } - # No cortamos: también queremos ver errores de columnas dependientes/no-dependientes. - return [err] + validaciones_agente_aduanal(row, line_num, valid_country_m3) - - return validaciones_agente_aduanal(row, line_num, valid_country_m3) - - -def valida_parcial_agente_aduanal( - row: Dict[str, Any], - line_num: int, - valid_country_m3: Optional[Set[str]] = None, -) -> List[Dict[str, Any]]: - """VALIDA_PARCIAL: solo VALIDACIONES_AGENTE_ADUANAL (no exige TIPO ni NOMBRE).""" - return validaciones_agente_aduanal(row, line_num, valid_country_m3) - - -def validate_row_customs_broker( - row: Dict[str, Any], - line_num: int, - actualizar: bool = False, - existing_broker_keys: Optional[Set[str]] = None, - valid_country_m3: Optional[Set[str]] = None, -) -> List[Dict[str, Any]]: - """ - Valida una fila de CSV de agentes aduanales. - 1. Desfase (COL_EXTRA) primero. - 2. Clave vacía → error. - 3. Si actualizar y clave no existe en existing_broker_keys → error. - 4. Si actualizar y clave existe → valida_parcial_agente_aduanal. - 5. Si no actualizar o clave no existe → valida_toda_agente_aduanal. - """ - errors: List[Dict[str, Any]] = [] - - err = check_desfase(row, line_num) - if err: - errors.append(err) - - err = check_required_broker_key(row, line_num) - if err: - errors.append(err) - - clave = (row.get("CLAVE") or "").strip() - existing = existing_broker_keys or set() - use_partial = actualizar and clave in existing - - if actualizar and clave and clave not in existing: - errors.append({ - "line": line_num, - "col": "CLAVE", - "msg": MSG_CLAVE_NO_EXISTE, - "solution": MSG_CLAVE_NO_EXISTE_SOLUCION, - }) - - if use_partial: - errors.extend(valida_parcial_agente_aduanal(row, line_num, valid_country_m3)) - else: - errors.extend(valida_toda_agente_aduanal(row, line_num, valid_country_m3)) - - return errors diff --git a/backend/api/v1/modules/a76/layouts_csv/customs_brokers/validators/create.py b/backend/api/v1/modules/a76/layouts_csv/customs_brokers/validators/create.py deleted file mode 100644 index 6336bbb2..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/customs_brokers/validators/create.py +++ /dev/null @@ -1,6 +0,0 @@ -""" -Punto de entrada de validación para import de una fila agente aduanal. -""" -from .common import validate_row_customs_broker - -__all__ = ["validate_row_customs_broker"] diff --git a/backend/api/v1/modules/a76/layouts_csv/drivers/__init__.py b/backend/api/v1/modules/a76/layouts_csv/drivers/__init__.py deleted file mode 100644 index 98bef45c..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/drivers/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# CSV import for Drivers / Conductores (upload -> scan -> status -> commit). diff --git a/backend/api/v1/modules/a76/layouts_csv/drivers/common/__init__.py b/backend/api/v1/modules/a76/layouts_csv/drivers/common/__init__.py deleted file mode 100644 index 748b1a0f..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/drivers/common/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Drivers CSV helpers (validators, FK loader). Import submodules explicitly to avoid heavy deps at package init.""" diff --git a/backend/api/v1/modules/a76/layouts_csv/drivers/common/common_validators.py b/backend/api/v1/modules/a76/layouts_csv/drivers/common/common_validators.py deleted file mode 100644 index 573483a1..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/drivers/common/common_validators.py +++ /dev/null @@ -1,262 +0,0 @@ -""" -Helpers reutilizables para validación de filas CSV (conductores). -Paridad Clarion: VALIDACIONES_CONDUCTOR, obligatorios A/C, catálogos transportista/países, Sexo M/F, Si/No, tipo identificación, desfase. -""" -import re -from typing import Dict, Any, Optional, Set - - -MAX_LEN = { - # Alineado a a76.transporter.transporter_key (23); CSV Clarion histórico usaba claves cortas - "transporter_key": 23, - "driver_name": 80, - "license_number": 29, - "express_line_id": 17, - "ace_id": 20, - "gender": 1, - "birth_country": 3, - "hazardous_material_auth": 2, - "hazardous_material_state": 30, - "first_name": 20, - "last_name": 20, - "id_key1": 40, - "id_number1": 20, - "id_state1": 30, - "id_country1": 3, - "id_key2": 40, - "id_number2": 20, - "id_state2": 30, - "id_country2": 3, -} - -# Clarion: Col H Sexo M o F -SEXO_VALIDOS = {"M", "F"} - -# Clarion: Col J Si o No (comparar en mayúsculas) -MATERIAL_PELIGROSO_VALIDOS = {"SI", "NO"} - -# Clarion: Col N y Col R tipo identificación -FORMA_IDENTIFICACION_CLAVES = frozenset( - {"ACW", "ALR", "BCP", "BCN", "CDN", "CON", "OTD", "REP", "RTP", "5J", "5K", "30"} -) - -# Mapeo clave CSV → valor guardado en BD (Clarion QueCSV:ColumnaN/R) -FORMA_IDENTIFICACION_MAP = { - "ACW": "ACW-Pasaporte", - "ALR": "ALR-Residencia", - "BCP": "BCP-Permiso Cruce", - "BCN": "BCN-Acta Nacimiento", - "CDN": "CDN-Ciudadania", - "CON": "CON-CertificadoNaturalizacion", - "OTD": "OTD-Otra Identificación", - "REP": "REP-Permiso Reentrada", - "RTP": "RTP-Permiso de Viaje", - "5J": "5J - Licencia", - "5K": "5K -Licencia", - "30": "30 -Visa de EU", -} - - -def check_required(row: Dict[str, Any], col: str, line_num: int) -> Optional[Dict[str, Any]]: - val = (row.get(col) or "").strip() - if not val: - return {"line": line_num, "col": col, "msg": "Requerido"} - return None - - -def check_max_length( - row: Dict[str, Any], - col: str, - max_len: int, - line_num: int, - required: bool = False, -) -> Optional[Dict[str, Any]]: - val = (row.get(col) or "").strip() - if not val: - if required: - return {"line": line_num, "col": col, "msg": "Requerido"} - return None - if len(val) > max_len: - return {"line": line_num, "col": col, "msg": f"Maximo {max_len} caracteres"} - return None - - -def parse_int(val: Any) -> Optional[int]: - if val is None or (isinstance(val, str) and not val.strip()): - return None - s = str(val).strip() - if re.match(r"^\d+$", s): - return int(s) - if re.match(r"^\d+\.0+$", s): - try: - return int(float(s)) - except ValueError: - return None - return None - - -def check_int_positive(row: Dict[str, Any], col: str, line_num: int) -> Optional[Dict[str, Any]]: - v = parse_int(row.get(col)) - if v is None: - return {"line": line_num, "col": col, "msg": "Debe ser numerico"} - if v <= 0: - return {"line": line_num, "col": col, "msg": "Debe ser mayor a 0"} - return None - - -def parse_birth_date(val: Any) -> Optional[int]: - if val is None or (isinstance(val, str) and not val.strip()): - return None - s = str(val).strip() - if re.match(r"^\d{8}$", s): - try: - return int(s) - except ValueError: - return None - if re.match(r"^\d+\.0+$", s): - try: - return int(float(s)) - except ValueError: - return None - for sep in ["/", "-", "."]: - if sep in s: - parts = s.split(sep) - if len(parts) == 3: - try: - a, b, c = [p.strip() for p in parts] - if len(c) == 4 and len(a) <= 2 and len(b) <= 2: - return int(c) * 10000 + int(b) * 100 + int(a) - if len(a) == 4 and len(b) <= 2 and len(c) <= 2: - return int(a) * 10000 + int(b) * 100 + int(c) - except (ValueError, TypeError): - return None - break - return None - - -def check_optional_birth_date(row: Dict[str, Any], col: str, line_num: int) -> Optional[Dict[str, Any]]: - val = row.get(col) - if val is None or not str(val).strip(): - return None - if parse_birth_date(val) is None: - return { - "line": line_num, - "col": col, - "msg": "Formato de fecha invalido (use YYYYMMDD o DD/MM/YYYY)", - } - return None - - -def check_transportista_catalog( - row: Dict[str, Any], - line_num: int, - valid_transporter_keys: Optional[Set[str]] = None, -) -> Optional[Dict[str, Any]]: - """Col A: Si TRANSPORTISTA no vacío, debe existir en catálogo (GTransportista).""" - val = (row.get("TRANSPORTISTA") or "").strip() - if not val or valid_transporter_keys is None: - return None - if val.upper() in valid_transporter_keys: - return None - return { - "line": line_num, - "col": "TRANSPORTISTA", - "msg": f"Error: (Col. A) La Clave de Transportista: {val} es incorrecto.", - "solution": "Capturar en columna A un Transportista existente en catalogo.", - } - - -def check_sexo_m_f(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]: - """Col H: Si SEXO no vacío, debe ser M o F (Clarion).""" - val = (row.get("SEXO") or "").strip() - if not val: - return None - if val.upper() in SEXO_VALIDOS: - return None - conductor = (row.get("CLAVE CONDUCTOR") or "").strip() or "(Conductor)" - return { - "line": line_num, - "col": "SEXO", - "msg": f"Error: (Col. H) El Sexo: {val} del Conductor: {conductor} es incorrecto.", - "solution": "Capturar en columna H el sexo correcto (M o F).", - } - - -def check_pais_catalog_drivers( - row: Dict[str, Any], - col: str, - line_num: int, - valid_country_ame: Optional[Set[str]] = None, - col_letter: str = "", -) -> Optional[Dict[str, Any]]: - """Si col (PAIS NACIMIENTO, PAIS, PAIS 2) no vacío, debe ser clave americana en catálogo (GPaises.Pais_Ame).""" - val = (row.get(col) or "").strip() - if not val or valid_country_ame is None: - return None - val_upper = val.upper() - if len(val) > 3: - return { - "line": line_num, - "col": col, - "msg": f"Error: ({col_letter}) El Pais: {val} es incorrecto.", - "solution": "Capturar un Pais en clave americana (US, MX, etc.).", - } - if val_upper in valid_country_ame: - return None - return { - "line": line_num, - "col": col, - "msg": f"Error: ({col_letter}) El Pais: {val} es incorrecto.", - "solution": "Capturar en columna un Pais en clave americana.", - } - - -def check_material_peligroso_si_no(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]: - """Col J: TRANSPORTA MAT. PELIGROSO? debe ser Si o No (Clarion; comparar en mayúsculas).""" - val = (row.get("TRANSPORTA MAT. PELIGROSO?") or "").strip() - if not val: - return None - if val.upper() in MATERIAL_PELIGROSO_VALIDOS: - return None - return { - "line": line_num, - "col": "TRANSPORTA MAT. PELIGROSO?", - "msg": "Error: (Col. J) La Autorizacion para el Manejo de Material Peligroso es incorrecta.", - "solution": "Capturar en columna J Si o No la autorizacion.", - } - - -def check_tipo_identificacion( - row: Dict[str, Any], - col: str, - line_num: int, - col_letter: str = "", - primera_o_segunda: str = "Primera", -) -> Optional[Dict[str, Any]]: - """Col N o R: Si FORMA IDENTIFICACION no vacío, debe ser clave válida (ACW, ALR, ...).""" - val = (row.get(col) or "").strip() - if not val: - return None - val_upper = val.upper() - if val_upper in FORMA_IDENTIFICACION_CLAVES: - return None - return { - "line": line_num, - "col": col, - "msg": f"Error: ({col_letter}) El Tipo de Identificacion: {val} de la {primera_o_segunda} Identificacion es incorrecto.", - "solution": f"Capturar en columna {col_letter} una clave de Identificacion valida (ACW, ALR, BCP, BCN, CDN, CON, OTD, REP, RTP, 5J, 5K, 30).", - } - - -def check_desfase_drivers(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]: - """Si COL_EXTRA (Col V) tiene valor -> advertencia de desfase (Clarion, no bloqueante).""" - val = (row.get("COL_EXTRA") or "").strip() - if not val: - return None - return { - "line": line_num, - "col": "COL_EXTRA", - "msg": "Advertencia: Podria existir un desfase en esta linea.", - "solution": "Revisar esta linea del archivo CSV y verificar cada campo este en la posicion correcta.", - "warning": True, - } diff --git a/backend/api/v1/modules/a76/layouts_csv/drivers/common/fk_loader.py b/backend/api/v1/modules/a76/layouts_csv/drivers/common/fk_loader.py deleted file mode 100644 index 7584fec2..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/drivers/common/fk_loader.py +++ /dev/null @@ -1,58 +0,0 @@ -""" -Carga de conjuntos FK para validación de import CSV de conductores. -Clarion: GTransportista (ClaveTrans), GPaises (Pais_Ame). -""" -from typing import Set, Tuple, Optional, Dict -import logging - -from core.database import CoreSessionLocal - -logger = logging.getLogger(__name__) - - -def load_drivers_fk_sets( - tenant_id: Optional[int] = None, - company_id: Optional[int] = None, -) -> Tuple[Set[str], Set[str], Dict[str, str]]: - """ - Carga conjuntos para validación CSV de conductores (paridad Clarion). - Devuelve: - - valid_transporter_keys: todas las claves de transportistas en mayúsculas (a76.transporter) - - valid_country_ame: claves americana de países (GPaises.Pais_Ame / Country.ame_key), mayúsculas - - transporter_key_actual: dict clave_upper -> clave real en BD (para insert con mismo caso que en transporter) - """ - valid_transporter_keys: Set[str] = set() - valid_country_ame: Set[str] = set() - transporter_key_actual: Dict[str, str] = {} - - try: - with CoreSessionLocal() as session: - from api.v1.modules.a76.transportation.transporters.models import Transporter - from api.v1.modules.public.reference_data.countries.models import Country - - # Solo transportistas del tenant/company del upload (paridad con Driver.tenant_id/company_id) - q = session.query(Transporter.transporter_key).filter( - Transporter.tenant_id == tenant_id, - Transporter.company_id == company_id, - ) - for row in q.all(): - if row[0]: - raw = (row[0] or "").strip() - upper = raw.upper() - valid_transporter_keys.add(upper) - transporter_key_actual[upper] = raw - - for row in session.query(Country.ame_key).all(): - if row[0]: - valid_country_ame.add((row[0] or "").strip().upper()) - - except Exception as e: - logger.warning("Drivers import: could not load FK sets: %s", e) - - logger.info( - "Drivers import FK: %d transportistas (claves: %s), %d paises", - len(valid_transporter_keys), - sorted(valid_transporter_keys)[:20] if len(valid_transporter_keys) <= 20 else sorted(valid_transporter_keys)[:10] + ["..."], - len(valid_country_ame), - ) - return (valid_transporter_keys, valid_country_ame, transporter_key_actual) diff --git a/backend/api/v1/modules/a76/layouts_csv/drivers/common/mappers.py b/backend/api/v1/modules/a76/layouts_csv/drivers/common/mappers.py deleted file mode 100644 index 45f8ccbb..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/drivers/common/mappers.py +++ /dev/null @@ -1,79 +0,0 @@ -""" -Mapeo fila CSV → datos para Driver (conductores). -Clarion: FORMA IDENTIFICACION 1/2 se guardan expandidas (ACW → ACW-Pasaporte, etc.). -""" -from typing import Dict, Any, Optional - -from .common_validators import ( - MAX_LEN, - parse_int, - parse_birth_date, - FORMA_IDENTIFICACION_MAP, -) - - -def _str_or_none(val: Any, max_len: Optional[int] = None) -> Optional[str]: - if val is None: - return None - s = str(val).strip() - if not s: - return None - if max_len and len(s) > max_len: - return s[:max_len] - return s - - -def _forma_identificacion_or_raw(val: Any, max_len: int) -> Optional[str]: - """Si el valor es una clave Clarion (ACW, ALR, ...), devuelve el valor expandido; si no, el valor truncado.""" - s = _str_or_none(val, max_len) - if not s: - return None - expanded = FORMA_IDENTIFICACION_MAP.get(s.upper()) - if expanded: - return expanded[:max_len] if len(expanded) > max_len else expanded - return s - - -def row_to_driver_data( - row_norm: Dict[str, Any], - tenant_id: int, - company_id: int, -) -> Dict[str, Any]: - """Mapea una fila normalizada del CSV a un diccionario para DriverCreateDTO.""" - transporter_key = _str_or_none(row_norm.get("TRANSPORTISTA"), MAX_LEN["transporter_key"]) - line = parse_int(row_norm.get("LINEA")) - if not transporter_key or line is None: - return {} - return { - "transporter_key": transporter_key, - "line": line, - "driver_name": _str_or_none(row_norm.get("CLAVE CONDUCTOR"), MAX_LEN["driver_name"]), - "license_number": _str_or_none(row_norm.get("LICENCIA"), MAX_LEN["license_number"]), - "express_line_id": _str_or_none(row_norm.get("PERMISO LINEA EXPRESS"), MAX_LEN["express_line_id"]), - "ace_id": _str_or_none(row_norm.get("IDENTIFICACION ACE"), MAX_LEN["ace_id"]), - "birth_date": parse_birth_date(row_norm.get("FECHA NACIMIENTO")), - "gender": _str_or_none(row_norm.get("SEXO"), MAX_LEN["gender"]), - "birth_country": _str_or_none(row_norm.get("PAIS NACIMIENTO"), MAX_LEN["birth_country"]), - "hazardous_material_auth": _str_or_none( - row_norm.get("TRANSPORTA MAT. PELIGROSO?"), MAX_LEN["hazardous_material_auth"] - ), - "hazardous_material_state": _str_or_none( - row_norm.get("PERMISO MAT. PELIGROSO"), MAX_LEN["hazardous_material_state"] - ), - "first_name": _str_or_none(row_norm.get("NOMBRE(S)"), MAX_LEN["first_name"]), - "last_name": _str_or_none(row_norm.get("APELLIDO PATERNO"), MAX_LEN["last_name"]), - "id_key1": _forma_identificacion_or_raw( - row_norm.get("FORMA IDENTIFICACION 1"), MAX_LEN["id_key1"] - ), - "id_number1": _str_or_none(row_norm.get("NUM. IDENTIFICACION 1"), MAX_LEN["id_number1"]), - "id_state1": _str_or_none(row_norm.get("ESTADO"), MAX_LEN["id_state1"]), - "id_country1": _str_or_none(row_norm.get("PAIS"), MAX_LEN["id_country1"]), - "id_key2": _forma_identificacion_or_raw( - row_norm.get("FORMA IDENTIFICACION 2"), MAX_LEN["id_key2"] - ), - "id_number2": _str_or_none(row_norm.get("NUM. IDENTIFICACION 2"), MAX_LEN["id_number2"]), - "id_state2": _str_or_none(row_norm.get("ESTADO 2"), MAX_LEN["id_state2"]), - "id_country2": _str_or_none(row_norm.get("PAIS 2"), MAX_LEN["id_country2"]), - "company_id": company_id, - "tenant_id": tenant_id, - } diff --git a/backend/api/v1/modules/a76/layouts_csv/drivers/csv_max_chars_for_headers.py b/backend/api/v1/modules/a76/layouts_csv/drivers/csv_max_chars_for_headers.py deleted file mode 100644 index 8dda83cb..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/drivers/csv_max_chars_for_headers.py +++ /dev/null @@ -1,28 +0,0 @@ -"""Límites alineados a drivers/common/common_validators.MAX_LEN (canonical CSV).""" -from typing import Dict - -from .common.common_validators import MAX_LEN as D_MAX - -DRIVERS_CSV_MAX_CHARS: Dict[str, int] = { - "TRANSPORTISTA": D_MAX["transporter_key"], - "LINEA": 30, - "CLAVE CONDUCTOR": D_MAX["driver_name"], - "LICENCIA": D_MAX["license_number"], - "PERMISO LINEA EXPRESS": D_MAX["express_line_id"], - "IDENTIFICACION ACE": D_MAX["ace_id"], - "FECHA NACIMIENTO": 30, - "SEXO": D_MAX["gender"], - "PAIS NACIMIENTO": D_MAX["birth_country"], - "TRANSPORTA MAT. PELIGROSO?": D_MAX["hazardous_material_auth"], - "PERMISO MAT. PELIGROSO": D_MAX["hazardous_material_state"], - "NOMBRE(S)": D_MAX["first_name"], - "APELLIDO PATERNO": D_MAX["last_name"], - "FORMA IDENTIFICACION 1": D_MAX["id_key1"], - "NUM. IDENTIFICACION 1": D_MAX["id_number1"], - "ESTADO": D_MAX["id_state1"], - "PAIS": D_MAX["id_country1"], - "FORMA IDENTIFICACION 2": D_MAX["id_key2"], - "NUM. IDENTIFICACION 2": D_MAX["id_number2"], - "ESTADO 2": D_MAX["id_state2"], - "PAIS 2": D_MAX["id_country2"], -} diff --git a/backend/api/v1/modules/a76/layouts_csv/drivers/routes.py b/backend/api/v1/modules/a76/layouts_csv/drivers/routes.py deleted file mode 100644 index 43dbe591..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/drivers/routes.py +++ /dev/null @@ -1,185 +0,0 @@ -""" -Rutas de importacion CSV para Conductores. -Flujo: upload -> scan -> status (polling) -> commit. -""" -import json -import logging -import os -import threading -from uuid import uuid4 - -from fastapi import APIRouter, File, HTTPException, Query, UploadFile, Depends -from sqlalchemy.orm import Session -from typing import Dict, Any - -from core.celery_app import celery_app -from core.database import get_core_db -from core.security import get_current_user, validate_access_to_resource - -from .schemas import ImportJobResponse -from .tasks import ( - run_scan_sync, - run_commit_sync, - JOB_TYPE, - DRV_IMPORT_STATUS_PREFIX, - DRV_IMPORT_REDIS_TTL, -) -from ..common import storage as common_storage -from ..common.error_csv import download_scan_errors_csv_stream -from ..common.responses import normalize_commit_status_payload - -router = APIRouter() -logger = logging.getLogger(__name__) - - -def _get_redis(): - import redis - url = os.getenv("VALKEY_URL", os.getenv("REDIS_URL", "redis://valkey:6379/0")) - return redis.Redis.from_url(url, decode_responses=False) - - -@router.post("/upload", response_model=ImportJobResponse) -async def upload_import_file( - file: UploadFile = File(...), - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - try: - tenant_id = validate_access_to_resource(db, company_id, current_user, ["csv_upload.process"]) - except Exception as e: - logger.error(f"Drivers import: access validation failed: {e}") - raise HTTPException(status_code=403, detail="Invalid company access") - - if not file.filename or not file.filename.lower().endswith(".csv"): - raise HTTPException(status_code=400, detail="Solo se permiten archivos .csv") - - job_id = str(uuid4()) - contents = await file.read() - - meta_data = { - "tenant_id": tenant_id, - "company_id": company_id, - "user_id": current_user.get("id"), - "template_id": "drivers", - } - - try: - common_storage.store_import_file( - JOB_TYPE, - job_id, - contents, - meta_data, - tenant_id=int(tenant_id), - company_id=company_id, - ttl=DRV_IMPORT_REDIS_TTL, - log_label="Drivers import", - ) - except common_storage.ImportStoreError as e: - logger.error(f"Drivers import: store error: {e}") - raise HTTPException(status_code=500, detail="No se pudo encolar el archivo.") - except Exception as e: - logger.error(f"Drivers import: Redis store error: {e}") - raise HTTPException(status_code=500, detail="No se pudo encolar el archivo.") - - def run_scan_background(): - try: - run_scan_sync(job_id) - except Exception as e: - logger.exception(f"Drivers import: background scan failed: {e}") - - threading.Thread(target=run_scan_background, daemon=True).start() - - return ImportJobResponse( - job_id=job_id, - status="queued", - message="Archivo subido. Escaneo iniciado.", - ) - - -@router.get("/{job_id}/status") -async def get_import_status(job_id: str): - try: - r = _get_redis() - raw = r.get(f"{DRV_IMPORT_STATUS_PREFIX}{job_id}") - if raw: - data = json.loads(raw.decode("utf-8")) - if isinstance(data, dict) and data.get("status") in ("finished", "warning"): - return normalize_commit_status_payload(data) - return data - except Exception as e: - logger.debug(f"Drivers import: could not read status from Redis: {e}") - - task_result = celery_app.AsyncResult(job_id) - - if task_result.state == "PENDING": - return {"status": "processing", "progress": 0} - if task_result.state == "PROGRESS": - info = (task_result.info or {}) - return { - "status": "processing", - "progress": info.get("current", 0), - "total": info.get("total", 0), - } - if task_result.state == "SUCCESS": - result = task_result.result - if isinstance(result, dict) and "status" in result: - return normalize_commit_status_payload(result) - return {"status": "finished", "result": result} - - result = getattr(task_result, "result", None) - if isinstance(result, dict) and result.get("status") in ("finished", "warning"): - return normalize_commit_status_payload(result) - - logger.warning("Drivers import task %s failed: state=%s", job_id, task_result.state) - err_msg = None - tb = getattr(task_result, "traceback", None) - if tb and isinstance(tb, str): - lines = [l.strip() for l in tb.strip().split("\n") if l.strip()] - if lines: - err_msg = lines[-1] - if not err_msg: - try: - exc = task_result.get(propagate=False) - if exc is not None: - err_msg = str(exc) - except Exception: - pass - if not err_msg and result is not None: - if not isinstance(result, dict): - err_msg = str(result) - elif result.get("error") or result.get("message"): - err_msg = result.get("error") or result.get("message") - return {"status": "failed", "error": err_msg or "Task failed"} - - -@router.post("/{job_id}/commit") -async def commit_import_job(job_id: str): - try: - r = _get_redis() - r.set( - f"{DRV_IMPORT_STATUS_PREFIX}{job_id}", - json.dumps({"status": "processing", "message": "Insertando..."}).encode("utf-8"), - ex=DRV_IMPORT_REDIS_TTL, - ) - except Exception as e: - logger.debug(f"Drivers import: could not write processing status: {e}") - - def run_commit_background(): - try: - run_commit_sync(job_id) - except Exception as e: - logger.exception(f"Drivers import: background commit failed: {e}") - - threading.Thread(target=run_commit_background, daemon=True).start() - - return { - "status": "committing", - "message": "Insercion iniciada.", - "commit_job_id": job_id, - } - - -@router.get("/{job_id}/errors/scan-csv") -async def download_scan_errors_csv(job_id: str): - return download_scan_errors_csv_stream("drv", job_id) diff --git a/backend/api/v1/modules/a76/layouts_csv/drivers/schemas.py b/backend/api/v1/modules/a76/layouts_csv/drivers/schemas.py deleted file mode 100644 index c5d2c2cc..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/drivers/schemas.py +++ /dev/null @@ -1,23 +0,0 @@ -from pydantic import BaseModel -from typing import Optional - - -class ImportJobResponse(BaseModel): - job_id: str - status: str - message: str - - -class ImportJobStatus(BaseModel): - status: str - job_id: str - total_rows: Optional[int] = 0 - error_count: Optional[int] = 0 - valid_rows: Optional[int] = 0 - error: Optional[str] = None - inserted: Optional[int] = 0 - updated: Optional[int] = 0 - skipped_invalid: Optional[int] = 0 - skipped_missing_fk: Optional[int] = 0 - skipped_duplicate: Optional[int] = 0 - skipped_details: Optional[list] = None diff --git a/backend/api/v1/modules/a76/layouts_csv/drivers/tasks.py b/backend/api/v1/modules/a76/layouts_csv/drivers/tasks.py deleted file mode 100644 index d19405dd..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/drivers/tasks.py +++ /dev/null @@ -1,482 +0,0 @@ -""" -Tareas Celery para importación CSV de Conductores. -Flujo: scan_file (validación) → insert_valid_rows (commit). -Usa layouts_csv.common (storage, normalize, meta, responses); CSV con headers duplicados (dedupe) y clave de estado en Redis. -Paridad Clarion: actualizar, existing_driver_keys, valid_transporter_keys, valid_country_ame. -""" -import json -import logging -import os -from typing import Dict, Any, Optional, List, Set, Tuple - -from core.celery_app import celery_app -from core.database import CoreSessionLocal -from sqlalchemy import func - -from ..common import storage as common_storage -from ..common import normalize as common_normalize -from ..common import meta as common_meta -from ..common import responses as common_responses -from ..common import csv_reader as common_csv_reader -from .template_config import row_from_template -from .validators import validate_row_driver, validate_row_driver_desfase -from .common.mappers import row_to_driver_data -from .common.fk_loader import load_drivers_fk_sets - -logger = logging.getLogger(__name__) - -JOB_TYPE = "drv" - -# Para routes.py -DRV_IMPORT_FILE_PREFIX = "drv_import_file:" -DRV_IMPORT_META_PREFIX = "drv_import_meta:" -DRV_IMPORT_ERROR_LINES_PREFIX = "drv_import_error_lines:" -DRV_IMPORT_STATUS_PREFIX = "drv_import_status:" -DRV_IMPORT_REDIS_TTL = common_storage.IMPORT_REDIS_TTL -DRV_IMPORT_TRANSPORTER_MAP_PREFIX = "drv_import_transporter_map:" - - -def _get_redis(): - import redis - url = os.getenv("VALKEY_URL", os.getenv("REDIS_URL", "redis://valkey:6379/0")) - return redis.Redis.from_url(url, decode_responses=False) - - -def _do_scan(job_id: str, progress_callback: Optional[Any] = None) -> Dict[str, Any]: - file_path = common_storage.ensure_file_from_redis(JOB_TYPE, job_id, "Drivers import") - if not file_path: - return {"status": "failed", "error": "Archivo no encontrado (expirado o no subido). Sube de nuevo."} - common_storage.ensure_meta_from_redis(JOB_TYPE, job_id, file_path, "Drivers import") - - error_path = common_storage.error_path_for_job(JOB_TYPE, job_id) - - try: - total_rows = common_csv_reader.count_csv_rows(file_path, has_header=True) - except Exception as e: - return {"status": "failed", "error": str(e)} - - try: - tenant_id, company_id = common_meta.require_tenant_context(file_path) - except ValueError as e: - return {"status": "failed", "error": str(e)} - - meta = common_meta.load_meta(file_path) or {} - actualizar = meta.get("actualizar", False) - existing_driver_keys: Set[Tuple[str, int]] = set() - if actualizar: - try: - from api.v1.modules.a76.transportation.drivers.models import Driver - with CoreSessionLocal() as session: - for row in ( - session.query(Driver.transporter_key, Driver.line) - .filter( - Driver.tenant_id == tenant_id, - Driver.company_id == company_id, - ) - .all() - ): - if row[0] is not None and row[1] is not None: - existing_driver_keys.add( - ((row[0] or "").strip().upper(), int(row[1])) - ) - except Exception as e: - logger.warning("Drivers import: could not load existing_driver_keys for actualizar: %s", e) - - valid_transporter_keys, valid_country_ame, transporter_key_actual = load_drivers_fk_sets(tenant_id, company_id) - - try: - r = _get_redis() - r.set( - f"{DRV_IMPORT_TRANSPORTER_MAP_PREFIX}{job_id}", - json.dumps(transporter_key_actual).encode("utf-8"), - ex=DRV_IMPORT_REDIS_TTL, - ) - except Exception as e: - logger.warning("Drivers import: failed to store transporter map in Redis: %s", e) - - logger.info( - "Drivers import scan: job_id=%s tenant_id=%s company_id=%s actualizar=%s transportistas=%d", - job_id, tenant_id, company_id, actualizar, len(valid_transporter_keys), - ) - - error_count = 0 - processed_rows = 0 - errors_detail: List[Dict[str, Any]] = [] - error_lines_list: List[int] = [] - - try: - with open(error_path, "w", encoding="utf-8") as f_err: - for i, row in common_csv_reader.iter_csv_rows_deduped_headers(file_path): - if progress_callback: - progress_callback(i, total_rows, error_count) - - row_norm = row_from_template(row, common_normalize.normalize_header) - warn = validate_row_driver_desfase(row_norm, i) - if warn and len(errors_detail) < 500: - errors_detail.append({ - "line": warn.get("line", i), - "col": warn.get("col", ""), - "msg": warn.get("msg", ""), - "solution": warn.get("solution", ""), - "warning": bool(warn.get("warning", False)), - }) - - row_errors = validate_row_driver( - row_norm, - i, - actualizar=actualizar, - existing_driver_keys=existing_driver_keys, - valid_transporter_keys=valid_transporter_keys, - valid_country_ame=valid_country_ame, - ) - if row_errors: - has_blocking_error = any(not e.get("warning", False) for e in row_errors) - if has_blocking_error: - error_count += 1 - error_lines_list.append(i) - for e in row_errors: - if not e.get("warning", False): - f_err.write(json.dumps(e) + "\n") - if len(errors_detail) < 500: - for e in row_errors: - if len(errors_detail) >= 500: - break - errors_detail.append({ - "line": e.get("line", i), - "col": e.get("col", ""), - "msg": e.get("msg", ""), - "solution": e.get("solution", ""), - "warning": bool(e.get("warning", False)), - }) - processed_rows += 1 - - if error_lines_list: - common_storage.store_error_lines(JOB_TYPE, job_id, error_lines_list) - except Exception as e: - logger.error("Drivers import scan failed: %s", e) - return {"status": "failed", "error": str(e)} - - return common_responses.scan_result(job_id, processed_rows, error_count, errors_detail) - - -def run_scan_sync(job_id: str) -> Dict[str, Any]: - result = _do_scan(job_id, progress_callback=None) - try: - r = _get_redis() - r.set( - f"{DRV_IMPORT_STATUS_PREFIX}{job_id}", - json.dumps(result).encode("utf-8"), - ex=DRV_IMPORT_REDIS_TTL, - ) - except Exception as e: - logger.warning("Drivers import: failed to store scan status in Redis: %s", e) - return result - - -@celery_app.task(bind=True) -def scan_file(self, job_id: str, config: str = None): - logger.info("Drivers import: starting scan for job %s", job_id) - - def on_progress(current: int, total: int, errors: int) -> None: - self.update_state(state="PROGRESS", meta={"current": current, "total": total, "errors": errors}) - - result = _do_scan(job_id, progress_callback=on_progress) - try: - r = _get_redis() - r.set( - f"{DRV_IMPORT_STATUS_PREFIX}{job_id}", - json.dumps(result).encode("utf-8"), - ex=DRV_IMPORT_REDIS_TTL, - ) - except Exception as e: - logger.warning("Drivers import: failed to store scan status in Redis: %s", e) - return result - - -def _do_commit(job_id: str) -> Dict[str, Any]: - file_path = common_storage.ensure_file_from_redis(JOB_TYPE, job_id, "Drivers import") - if not file_path: - alt_path = common_storage.file_path_for_job(JOB_TYPE, job_id) - if not os.path.exists(alt_path): - return {"status": "failed", "error": "Archivo no encontrado (expirado). Sube y confirma de nuevo."} - file_path = alt_path - else: - common_storage.ensure_meta_from_redis(JOB_TYPE, job_id, file_path, "Drivers import") - - error_path = common_storage.error_path_for_job(JOB_TYPE, job_id) - error_lines = common_storage.get_error_lines(JOB_TYPE, job_id, error_path) - - try: - tenant_id, company_id = common_meta.require_tenant_context(file_path) - except ValueError as e: - return {"status": "failed", "error": str(e)} - - meta = common_meta.load_meta(file_path) or {} - actualizar = meta.get("actualizar", False) - existing_driver_keys: Set[Tuple[str, int]] = set() - if actualizar: - try: - from api.v1.modules.a76.transportation.drivers.models import Driver - with CoreSessionLocal() as session: - for row in ( - session.query(Driver.transporter_key, Driver.line) - .filter( - Driver.tenant_id == tenant_id, - Driver.company_id == company_id, - ) - .all() - ): - if row[0] is not None and row[1] is not None: - existing_driver_keys.add( - ((row[0] or "").strip().upper(), int(row[1])) - ) - except Exception as e: - logger.warning("Drivers import: could not load existing_driver_keys for actualizar: %s", e) - - valid_transporter_keys, valid_country_ame, transporter_key_actual = load_drivers_fk_sets(tenant_id, company_id) - - transporter_map_from_redis: Optional[Dict[str, str]] = None - try: - r = _get_redis() - map_key = f"{DRV_IMPORT_TRANSPORTER_MAP_PREFIX}{job_id}" - raw = r.get(map_key) - if raw: - transporter_map_from_redis = json.loads(raw.decode("utf-8")) - logger.info( - "Drivers import commit: using transporter map from Redis (job_id=%s, keys=%d)", - job_id, len(transporter_map_from_redis), - ) - else: - logger.warning( - "Drivers import commit: no transporter map in Redis for job_id=%s, using DB fallback", - job_id, - ) - except Exception as e: - logger.warning( - "Drivers import: could not load transporter map from Redis (job_id=%s): %s", - job_id, e, - ) - - logger.info( - "Drivers import commit: job_id=%s tenant_id=%s company_id=%s transportistas=%d", - job_id, tenant_id, company_id, len(valid_transporter_keys), - ) - - from api.v1.modules.a76.transportation.drivers.services import DriverService - from api.v1.modules.a76.transportation.drivers.dto import DriverCreateDTO - - inserted_count = 0 - updated_count = 0 - skipped_invalid = 0 - skipped_duplicate = 0 - skipped_details: List[Dict[str, Any]] = [] - seen_keys_in_file: Dict[str, int] = {} - meta_path = common_meta.get_meta_path(file_path) - - try: - with CoreSessionLocal() as session: - for i, row in common_csv_reader.iter_csv_rows_deduped_headers(file_path): - if i in error_lines: - continue - - row_norm = row_from_template(row, common_normalize.normalize_header) - row_errors = validate_row_driver( - row_norm, - i, - actualizar=actualizar, - existing_driver_keys=existing_driver_keys, - valid_transporter_keys=valid_transporter_keys, - valid_country_ame=valid_country_ame, - ) - if row_errors and any(not e.get("warning", False) for e in row_errors): - skipped_invalid += 1 - driver_key = (row_norm.get("CLAVE CONDUCTOR") or "").strip()[:80] or "-" - blocking_errors = [e for e in row_errors if not e.get("warning", False)] - for blocking in blocking_errors: - skipped_details.append({ - "line": i, - "driver_key": driver_key, - "invoice": driver_key, - "reason": f"{blocking.get('col', '')}: {blocking.get('msg', '')}", - "solution": blocking.get("solution", ""), - }) - continue - - data = row_to_driver_data(row_norm, tenant_id, company_id) - if not data or not data.get("transporter_key") or data.get("line") is None: - skipped_invalid += 1 - continue - - tk = (data["transporter_key"] or "").strip() - # Usar mapa del scan (Redis) si existe; si no, resolver en la sesión del commit (fallback) - if transporter_map_from_redis is not None: - tk_upper = tk.upper() - if tk_upper not in transporter_map_from_redis: - skipped_invalid += 1 - skipped_details.append({ - "line": i, - "driver_key": f"{tk}:{data.get('line')}", - "invoice": f"{tk}:{data.get('line')}", - "reason": f"El transportista {tk} no existe en el catálogo.", - "solution": "Capturar en el CSV un TRANSPORTISTA existente (columna TRANSPORTISTA CLAVE / CLAVE TRANSPORTISTA).", - }) - continue - data["transporter_key"] = transporter_map_from_redis[tk_upper] - else: - from api.v1.modules.a76.transportation.transporters.models import Transporter - transporter_row = ( - session.query(Transporter.transporter_key) - .filter( - Transporter.tenant_id == tenant_id, - Transporter.company_id == company_id, - func.upper(Transporter.transporter_key) == tk.upper(), - ) - .first() - ) - if not transporter_row or not transporter_row[0]: - skipped_invalid += 1 - skipped_details.append({ - "line": i, - "driver_key": f"{tk}:{data.get('line')}", - "invoice": f"{tk}:{data.get('line')}", - "reason": f"El transportista {tk} no existe en el catálogo.", - "solution": "Capturar en el CSV un TRANSPORTISTA existente (columna TRANSPORTISTA CLAVE / CLAVE TRANSPORTISTA).", - }) - continue - data["transporter_key"] = (transporter_row[0] or "").strip() - - key = f"{data['transporter_key']}:{data['line']}" - if key in seen_keys_in_file: - skipped_duplicate += 1 - skipped_details.append({ - "line": i, - "driver_key": key, - "invoice": key, - "reason": "Clave duplicada en el archivo (se usa la primera)", - "solution": "Revisa TRANSPORTISTA y LINEA para no duplicar la combinación en el archivo (se conserva la primera).", - }) - continue - seen_keys_in_file[key] = i - - existing = DriverService.get_driver_by_key_and_line( - session, data["transporter_key"], data["line"], str(company_id), tenant_id - ) - try: - if existing: - update_fields = { - k: v for k, v in data.items() - if k not in ("transporter_key", "line", "company_id", "tenant_id") - } - for field, value in update_fields.items(): - setattr(existing, field, value) - session.add(existing) - updated_count += 1 - else: - create_data = DriverCreateDTO(**data) - DriverService.create_driver(session, create_data) - inserted_count += 1 - except Exception as db_err: - session.rollback() - skipped_invalid += 1 - err_msg = str(db_err) - if "ForeignKeyViolation" in err_msg or "foreign key constraint" in err_msg.lower() or "driver_transporter_key_fkey" in err_msg: - err_msg = f"El transportista {data.get('transporter_key', '')} no existe en el catálogo." - logger.warning( - "Drivers import: FK violation linea %d transporter_key=%r (valid_transporter_keys tiene %d claves)", - i, data.get("transporter_key"), len(valid_transporter_keys), - ) - skipped_details.append({ - "line": i, - "driver_key": key, - "invoice": key, - "reason": err_msg, - "solution": "Revisar la línea del CSV y los catálogos relacionados para corregir el error e intentar de nuevo.", - }) - continue - - try: - session.commit() - except Exception as db_err: - session.rollback() - logger.error("Drivers import DB error: %s", db_err) - return {"status": "failed", "error": str(db_err)} - - except Exception as e: - logger.exception("Drivers import task failed") - return {"status": "failed", "error": str(e)} - - common_storage.cleanup_import_job( - JOB_TYPE, job_id, - file_path=file_path, - error_path=error_path, - meta_path=meta_path, - ) - try: - r = _get_redis() - r.delete(f"{DRV_IMPORT_STATUS_PREFIX}{job_id}") - r.delete(f"{DRV_IMPORT_TRANSPORTER_MAP_PREFIX}{job_id}") - except Exception as e: - logger.warning("Drivers import: failed to delete status key: %s", e) - - total_ok = inserted_count + updated_count - if total_ok == 0 and (skipped_invalid + skipped_duplicate) > 0: - return { - "status": "warning", - "inserted": inserted_count, - "updated": updated_count, - "skipped_invalid": skipped_invalid, - "skipped_duplicate": skipped_duplicate, - "skipped_missing_fk": 0, - "skipped_details": skipped_details, - "message": f"No se insertaron registros. {skipped_invalid + skipped_duplicate} rechazados.", - } - if total_ok == 0: - return { - "status": "failed", - "error": "No hay registros validos en el archivo CSV", - "inserted": 0, - "updated": 0, - "skipped_invalid": skipped_invalid, - "skipped_duplicate": skipped_duplicate, - "skipped_missing_fk": 0, - "skipped_details": skipped_details, - } - return { - "status": "finished", - "inserted": inserted_count, - "updated": updated_count, - "skipped_invalid": skipped_invalid, - "skipped_duplicate": skipped_duplicate, - "skipped_missing_fk": 0, - "skipped_details": skipped_details, - } - - -def run_commit_sync(job_id: str) -> Dict[str, Any]: - result = _do_commit(job_id) - try: - r = _get_redis() - r.set( - f"{DRV_IMPORT_STATUS_PREFIX}{job_id}", - json.dumps(result).encode("utf-8"), - ex=DRV_IMPORT_REDIS_TTL, - ) - except Exception as e: - logger.warning("Drivers import: failed to store commit status in Redis: %s", e) - return result - - -@celery_app.task(bind=True) -def insert_valid_rows(self, job_id: str): - logger.info("Drivers import: starting commit for job %s", job_id) - result = _do_commit(job_id) - try: - r = _get_redis() - r.set( - f"{DRV_IMPORT_STATUS_PREFIX}{job_id}", - json.dumps(result).encode("utf-8"), - ex=DRV_IMPORT_REDIS_TTL, - ) - except Exception as e: - logger.warning("Drivers import: failed to store commit status in Redis: %s", e) - return result diff --git a/backend/api/v1/modules/a76/layouts_csv/drivers/template_config.py b/backend/api/v1/modules/a76/layouts_csv/drivers/template_config.py deleted file mode 100644 index 803e8c3f..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/drivers/template_config.py +++ /dev/null @@ -1,75 +0,0 @@ -""" -Configuracion de plantilla CSV para Conductores (EstructuraCatConductor.xls). -""" - -from typing import Dict, List, Any - -from ..common.cell_value import cell_to_str -from ..common.csv_hint_inject import inject_max_char_hints_into_columns, merge_enum_hint_before_max -from ..common.inject_csv_labels_en import inject_en_labels_into_template_columns -from ..common.template_locale import merge_download_header_into_lookup, merge_labels_into_lookup -from .csv_max_chars_for_headers import DRIVERS_CSV_MAX_CHARS - -TEMPLATE_COLUMNS: Dict[str, List[Dict[str, Any]]] = { - "drivers": [ - {"canonical": "TRANSPORTISTA", "aliases": ["TRANSPORTISTA CLAVE", "CLAVE TRANSPORTISTA", "TRANSPORTER"]}, - {"canonical": "LINEA", "aliases": ["LINE", "LINEA CONDUCTOR"]}, - {"canonical": "CLAVE CONDUCTOR", "aliases": ["CONDUCTOR", "DRIVER", "NOMBRE CONDUCTOR"]}, - {"canonical": "LICENCIA", "aliases": ["LICENSE", "LICENCE"]}, - {"canonical": "PERMISO LINEA EXPRESS", "aliases": ["LINEA EXPRESS", "LINEA EXPRESS ID", "EXPRESS LINE"]}, - {"canonical": "IDENTIFICACION ACE", "aliases": ["ACE", "ACE ID", "IDENTIFICACION ACE ID"]}, - {"canonical": "FECHA NACIMIENTO", "aliases": ["FECHA NAC", "BIRTH DATE", "BIRTHDATE"]}, - {"canonical": "SEXO", "aliases": ["GENERO", "GENDER"]}, - {"canonical": "PAIS NACIMIENTO", "aliases": ["PAIS Nacimiento", "BIRTH COUNTRY"]}, - {"canonical": "TRANSPORTA MAT. PELIGROSO?", "aliases": ["MATERIAL PELIGROSO", "HAZMAT", "HAZARDOUS MATERIAL"]}, - {"canonical": "PERMISO MAT. PELIGROSO", "aliases": ["PERMISO MAT PELIGROSO", "HAZMAT PERMIT"]}, - {"canonical": "NOMBRE(S)", "aliases": ["NOMBRE", "NOMBRES", "FIRST NAME"]}, - {"canonical": "APELLIDO PATERNO", "aliases": ["APELLIDO", "LAST NAME", "APELLIDO P"]}, - {"canonical": "FORMA IDENTIFICACION 1", "aliases": ["FORMA IDENTIFICACION", "ID TIPO 1", "ID KEY 1"]}, - {"canonical": "NUM. IDENTIFICACION 1", "aliases": ["NUM IDENTIFICACION 1", "ID NUMERO 1", "ID NUMBER 1"]}, - {"canonical": "ESTADO", "aliases": ["ESTADO 1", "STATE 1"]}, - {"canonical": "PAIS", "aliases": ["PAIS 1", "COUNTRY 1"]}, - {"canonical": "FORMA IDENTIFICACION 2", "aliases": ["ID TIPO 2", "ID KEY 2"]}, - {"canonical": "NUM. IDENTIFICACION 2", "aliases": ["NUM IDENTIFICACION 2", "ID NUMERO 2", "ID NUMBER 2"]}, - {"canonical": "ESTADO 2", "aliases": ["STATE 2"]}, - {"canonical": "PAIS 2", "aliases": ["COUNTRY 2"]}, - {"canonical": "COL_EXTRA", "aliases": ["COLUMNA V", "COL V"]}, - ], -} - -inject_en_labels_into_template_columns(TEMPLATE_COLUMNS) - -inject_max_char_hints_into_columns(TEMPLATE_COLUMNS.get("drivers"), DRIVERS_CSV_MAX_CHARS) -for _dc in TEMPLATE_COLUMNS.get("drivers") or []: - c = _dc.get("canonical") - if c == "SEXO": - merge_enum_hint_before_max(_dc, ["M", "F"]) - elif c == "TRANSPORTA MAT. PELIGROSO?": - merge_enum_hint_before_max(_dc, ["SI", "NO"]) - - -def build_normalized_lookup(normalize_header_fn) -> Dict[str, str]: - cols = TEMPLATE_COLUMNS.get("drivers") - if not cols: - return {} - lookup: Dict[str, str] = {} - for item in cols: - canonical = item["canonical"] - lookup[normalize_header_fn(canonical)] = canonical - for alias in item.get("aliases") or []: - lookup[normalize_header_fn(alias)] = canonical - merge_labels_into_lookup(item, canonical, normalize_header_fn, lookup) - merge_download_header_into_lookup(item, canonical, normalize_header_fn, lookup) - return lookup - - -def row_from_template(row: Dict[str, Any], normalize_header_fn) -> Dict[str, Any]: - lookup = build_normalized_lookup(normalize_header_fn) - if not lookup: - return {normalize_header_fn(k): cell_to_str(v) for k, v in row.items()} - out: Dict[str, Any] = {} - for csv_header, value in row.items(): - key_norm = normalize_header_fn(csv_header) - if key_norm in lookup: - out[lookup[key_norm]] = cell_to_str(value) - return out diff --git a/backend/api/v1/modules/a76/layouts_csv/drivers/validators/__init__.py b/backend/api/v1/modules/a76/layouts_csv/drivers/validators/__init__.py deleted file mode 100644 index 4dd127be..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/drivers/validators/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .create import validate_row_driver, validate_row_driver_desfase - -__all__ = ["validate_row_driver", "validate_row_driver_desfase"] diff --git a/backend/api/v1/modules/a76/layouts_csv/drivers/validators/common.py b/backend/api/v1/modules/a76/layouts_csv/drivers/validators/common.py deleted file mode 100644 index 43478bbc..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/drivers/validators/common.py +++ /dev/null @@ -1,154 +0,0 @@ -""" -Validaciones comunes de fila para import CSV de conductores. -Paridad Clarion: VALIDACIONES_CONDUCTOR, VALIDA_TODA_CONDUCTOR, VALIDA_PARCIAL_CONDUCTOR. -""" -from typing import Dict, Any, Optional, Set, List - -from ..common.common_validators import ( - MAX_LEN, - check_max_length, - check_int_positive, - check_optional_birth_date, - check_transportista_catalog, - check_sexo_m_f, - check_pais_catalog_drivers, - check_material_peligroso_si_no, - check_tipo_identificacion, -) - - -def validate_row_driver_required(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]: - """Obligatorios Clarion: Col A (TRANSPORTISTA) y Col C (CLAVE CONDUCTOR). Si falta uno, no se ejecutan validaciones.""" - err = check_max_length( - row, "TRANSPORTISTA", MAX_LEN["transporter_key"], line_num, required=True - ) - if err: - return err - err = check_max_length( - row, "CLAVE CONDUCTOR", MAX_LEN["driver_name"], line_num, required=True - ) - if err: - return err - return None - - -def validate_row_driver_required_full(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]: - """VALIDA_TODA: obligatorios A, C y LINEA (Col B) para registro nuevo.""" - err = validate_row_driver_required(row, line_num) - if err: - return err - return check_int_positive(row, "LINEA", line_num) - - -def validate_row_driver_lengths(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]: - checks = [ - ("CLAVE CONDUCTOR", MAX_LEN["driver_name"]), - ("LICENCIA", MAX_LEN["license_number"]), - ("PERMISO LINEA EXPRESS", MAX_LEN["express_line_id"]), - ("IDENTIFICACION ACE", MAX_LEN["ace_id"]), - ("SEXO", MAX_LEN["gender"]), - ("PAIS NACIMIENTO", MAX_LEN["birth_country"]), - ("TRANSPORTA MAT. PELIGROSO?", MAX_LEN["hazardous_material_auth"]), - ("PERMISO MAT. PELIGROSO", MAX_LEN["hazardous_material_state"]), - ("NOMBRE(S)", MAX_LEN["first_name"]), - ("APELLIDO PATERNO", MAX_LEN["last_name"]), - ("FORMA IDENTIFICACION 1", MAX_LEN["id_key1"]), - ("NUM. IDENTIFICACION 1", MAX_LEN["id_number1"]), - ("ESTADO", MAX_LEN["id_state1"]), - ("PAIS", MAX_LEN["id_country1"]), - ("FORMA IDENTIFICACION 2", MAX_LEN["id_key2"]), - ("NUM. IDENTIFICACION 2", MAX_LEN["id_number2"]), - ("ESTADO 2", MAX_LEN["id_state2"]), - ("PAIS 2", MAX_LEN["id_country2"]), - ] - for col, max_len in checks: - err = check_max_length(row, col, max_len, line_num) - if err: - return err - return None - - -def validate_row_driver_date(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]: - return check_optional_birth_date(row, "FECHA NACIMIENTO", line_num) - - -def validaciones_conductores( - row: Dict[str, Any], - line_num: int, - valid_transporter_keys: Optional[Set[str]] = None, - valid_country_ame: Optional[Set[str]] = None, -) -> List[Dict[str, Any]]: - """ - VALIDACIONES_CONDUCTOR: reglas compartidas (longitudes, fecha, transportista en catálogo, - sexo M/F, país nacimiento, material peligroso Si/No, tipo ID 1/2, país ID1/ID2). - """ - errors: List[Dict[str, Any]] = [] - for err in [ - validate_row_driver_lengths(row, line_num), - validate_row_driver_date(row, line_num), - check_transportista_catalog(row, line_num, valid_transporter_keys), - check_sexo_m_f(row, line_num), - check_pais_catalog_drivers( - row, "PAIS NACIMIENTO", line_num, valid_country_ame, col_letter="Col. I" - ), - check_material_peligroso_si_no(row, line_num), - check_tipo_identificacion( - row, - "FORMA IDENTIFICACION 1", - line_num, - col_letter="Col. N", - primera_o_segunda="Primera", - ), - check_pais_catalog_drivers( - row, "PAIS", line_num, valid_country_ame, col_letter="Col. Q" - ), - check_tipo_identificacion( - row, - "FORMA IDENTIFICACION 2", - line_num, - col_letter="Col. R", - primera_o_segunda="Segunda", - ), - check_pais_catalog_drivers( - row, "PAIS 2", line_num, valid_country_ame, col_letter="Col. U" - ), - ]: - if err: - errors.append(err) - - return errors - - -def valida_toda_conductor( - row: Dict[str, Any], - line_num: int, - valid_transporter_keys: Optional[Set[str]] = None, - valid_country_ame: Optional[Set[str]] = None, -) -> List[Dict[str, Any]]: - """VALIDA_TODA_CONDUCTOR: obligatorios A, C y LINEA (B) + validaciones_conductores (registro nuevo).""" - errors: List[Dict[str, Any]] = [] - err = validate_row_driver_required_full(row, line_num) - if err: - errors.append(err) - errors.extend( - validaciones_conductores( - row, line_num, - valid_transporter_keys=valid_transporter_keys, - valid_country_ame=valid_country_ame, - ) - ) - return errors - - -def valida_parcial_conductor( - row: Dict[str, Any], - line_num: int, - valid_transporter_keys: Optional[Set[str]] = None, - valid_country_ame: Optional[Set[str]] = None, -) -> List[Dict[str, Any]]: - """VALIDA_PARCIAL_CONDUCTOR: solo validaciones_conductores (actualizar registro existente).""" - return validaciones_conductores( - row, line_num, - valid_transporter_keys=valid_transporter_keys, - valid_country_ame=valid_country_ame, - ) diff --git a/backend/api/v1/modules/a76/layouts_csv/drivers/validators/create.py b/backend/api/v1/modules/a76/layouts_csv/drivers/validators/create.py deleted file mode 100644 index e628e319..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/drivers/validators/create.py +++ /dev/null @@ -1,69 +0,0 @@ -""" -Punto de entrada de validación para import de una fila conductor. -Paridad Clarion: desfase (advertencia), obligatorios A y C, VALIDA_TODA vs VALIDA_PARCIAL según actualizar y clave existente. -""" -from typing import Dict, Any, Optional, Set, Tuple, List - -from ..common.common_validators import parse_int, check_desfase_drivers -from .common import ( - validate_row_driver_required, - valida_toda_conductor, - valida_parcial_conductor, -) - - -def validate_row_driver( - row: Dict[str, Any], - line_num: int, - actualizar: bool = False, - existing_driver_keys: Optional[Set[Tuple[str, int]]] = None, - valid_transporter_keys: Optional[Set[str]] = None, - valid_country_ame: Optional[Set[str]] = None, -) -> List[Dict[str, Any]]: - """ - Valida una fila de CSV de conductores. - 1. Obligatorios A (TRANSPORTISTA) y C (CLAVE CONDUCTOR) vacíos → error. - 2. Si actualizar y (TRANSPORTISTA, LINEA) en existing_driver_keys → valida_parcial_conductor. - 3. Si no actualizar o conductor no existe → valida_toda_conductor (obligatorios A, C y LINEA + validaciones). - Desfase (COL_EXTRA) no se valida aquí; el caller puede llamar validate_row_driver_desfase para advertencias no bloqueantes. - """ - errors: List[Dict[str, Any]] = [] - err = validate_row_driver_required(row, line_num) - if err: - errors.append(err) - - existing = existing_driver_keys or set() - transporter_key = (row.get("TRANSPORTISTA") or "").strip().upper() - line = parse_int(row.get("LINEA")) - use_partial = ( - actualizar - and bool(transporter_key and line is not None) - and (transporter_key, line) in existing - ) - - if use_partial: - errors.extend( - valida_parcial_conductor( - row, - line_num, - valid_transporter_keys=valid_transporter_keys, - valid_country_ame=valid_country_ame, - ) - ) - else: - errors.extend( - valida_toda_conductor( - row, - line_num, - valid_transporter_keys=valid_transporter_keys, - valid_country_ame=valid_country_ame, - ) - ) - return errors - - -def validate_row_driver_desfase(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]: - """ - Advertencia de desfase si COL_EXTRA (Col V) tiene valor. No bloqueante; el caller puede acumular en warnings. - """ - return check_desfase_drivers(row, line_num) diff --git a/backend/api/v1/modules/a76/layouts_csv/exchange_rate/__init__.py b/backend/api/v1/modules/a76/layouts_csv/exchange_rate/__init__.py deleted file mode 100644 index 4ac8a216..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/exchange_rate/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# Exchange rate CSV import module diff --git a/backend/api/v1/modules/a76/layouts_csv/exchange_rate/common/__init__.py b/backend/api/v1/modules/a76/layouts_csv/exchange_rate/common/__init__.py deleted file mode 100644 index ab0f8ea6..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/exchange_rate/common/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# common_validators, mappers (no fk_loader for exchange_rate) diff --git a/backend/api/v1/modules/a76/layouts_csv/exchange_rate/common/common_validators.py b/backend/api/v1/modules/a76/layouts_csv/exchange_rate/common/common_validators.py deleted file mode 100644 index 6d1bf126..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/exchange_rate/common/common_validators.py +++ /dev/null @@ -1,140 +0,0 @@ -""" -Validadores reutilizables para import CSV de tipos de cambio. -""" -from datetime import datetime, time -from decimal import Decimal -from typing import Dict, Any, Optional, List - -DATE_FORMATS: List[str] = ["%Y-%m-%d", "%d/%m/%Y", "%m/%d/%Y", "%d-%m-%Y", "%Y/%m/%d"] - -# Valores que envía el frontend (globalCsvParams dateFormat) -> formato strptime -# Cuando el usuario elige un formato en "Parámetros globales", solo se aceptan fechas en ese formato. -DATE_FORMAT_PREFERENCE_MAP: Dict[str, str] = { - "dd/mm/yyyy": "%d/%m/%Y", - "mm/dd/yyyy": "%m/%d/%Y", - "yyyy-mm-dd": "%Y-%m-%d", -} - -CURRENCY_MAX = 7 - - -def parse_date(val: Optional[str], date_format_preference: Optional[str] = None) -> Optional[datetime]: - """ - Parsea fecha. Si date_format_preference está definido (formato elegido en el frontend), - solo se acepta ese formato; si la cadena no coincide, se rechaza. - Si no hay preferencia, se intentan todos los formatos (retrocompatibilidad). - """ - if not val or not str(val).strip(): - return None - raw = str(val).strip() - if date_format_preference and date_format_preference in DATE_FORMAT_PREFERENCE_MAP: - fmt = DATE_FORMAT_PREFERENCE_MAP[date_format_preference] - try: - parsed = datetime.strptime(raw, fmt) - return datetime.combine(parsed.date(), time.min) - except ValueError: - return None - for fmt in DATE_FORMATS: - try: - parsed = datetime.strptime(raw, fmt) - return datetime.combine(parsed.date(), time.min) - except ValueError: - continue - return None - - -def parse_decimal_positive(val: Optional[str]) -> Optional[Decimal]: - if not val or not str(val).strip(): - return None - try: - v = float(str(val).strip().replace(",", ".")) - if v <= 0: - return None - return Decimal(str(round(v, 6))) - except (ValueError, TypeError): - return None - - -def check_required_date(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]: - fecha_raw = (row.get("FECHA") or "").strip() - if not fecha_raw: - return {"line": line_num, "col": "FECHA", "msg": "Requerido"} - if parse_date(fecha_raw) is None: - return {"line": line_num, "col": "FECHA", "msg": "Formato de fecha inválido (use YYYY-MM-DD o DD/MM/YYYY)"} - return None - - -def check_required_value_positive(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]: - valor_raw = (row.get("VALOR") or "").strip() - if not valor_raw: - return {"line": line_num, "col": "VALOR", "msg": "Requerido"} - try: - v = float(valor_raw.replace(",", ".")) - if v <= 0: - return {"line": line_num, "col": "VALOR", "msg": "Debe ser mayor que cero"} - except ValueError: - return {"line": line_num, "col": "VALOR", "msg": "Debe ser un número"} - return None - - -def check_optional_max_length(row: Dict[str, Any], col: str, max_len: int, line_num: int) -> Optional[Dict[str, Any]]: - val = (row.get(col) or "").strip() - if not val: - return None - if len(val) > max_len: - return {"line": line_num, "col": col, "msg": f"Máximo {max_len} caracteres"} - return None - - -FECHA_MAX_LEN = 10 -MSG_FECHA_LONGITUD = "Error: (Col. A) La Fecha: {fecha} supera la longitud de caracteres." -MSG_FECHA_LONGITUD_SOLUCION = "Capturar en la columna A el campo Fecha con este formato ##/##/####." -MSG_FECHA_DIA_INVALIDO = "Error: (Col. A) El día {dia} de la Fecha: {fecha} no es válido para el mes." -MSG_FECHA_DIA_SOLUCION = "Capturar correctamente en la columna A el día del campo Fecha, con este formato ##/##/####. (Día/Mes/Año)" -MSG_FECHA_MES_INVALIDO = "Error: (Col. A) El mes {mes} de la Fecha: {fecha} es mayor a 12 esto no es valido." -MSG_FECHA_MES_SOLUCION = "Capturar correctamente en la columna A el mes del campo Fecha, con este formato ##/##/####. (Día/Mes/Año)" - -# Etiquetas para mensaje cuando no coincide con el formato elegido -DATE_FORMAT_LABELS: Dict[str, str] = { - "dd/mm/yyyy": "DD/MM/YYYY (Día/Mes/Año)", - "mm/dd/yyyy": "MM/DD/YYYY (Mes/Día/Año)", - "yyyy-mm-dd": "YYYY-MM-DD (Año-Mes-Día)", -} - - -def validate_fecha_clarion( - raw_fecha: str, line_num: int, date_format_preference: Optional[str] = None -) -> Optional[Dict[str, Any]]: - """ - Valida la fecha según reglas Clarion: longitud ≤ 10, día válido para el mes, mes ≤ 12. - Sin límite de año. Si date_format_preference está definido (ej. dd/mm/yyyy), se prioriza ese formato. - """ - if not raw_fecha or not str(raw_fecha).strip(): - return None - raw = str(raw_fecha).strip() - if len(raw) > FECHA_MAX_LEN: - return { - "line": line_num, - "col": "FECHA", - "msg": f"{MSG_FECHA_LONGITUD.format(fecha=raw)} {MSG_FECHA_LONGITUD_SOLUCION}", - } - parsed = parse_date(raw, date_format_preference) - if parsed is None: - format_label = ( - DATE_FORMAT_LABELS.get(date_format_preference, "##/##/####") - if date_format_preference - else "##/##/####" - ) - return { - "line": line_num, - "col": "FECHA", - "msg": f"Error: (Col. A) La Fecha: {raw} no coincide con el formato elegido ({format_label}). {MSG_FECHA_LONGITUD_SOLUCION}", - } - d = parsed.date() - if d.month > 12: - return { - "line": line_num, - "col": "FECHA", - "msg": f"{MSG_FECHA_MES_INVALIDO.format(mes=d.month, fecha=raw)} {MSG_FECHA_MES_SOLUCION}", - } - return None diff --git a/backend/api/v1/modules/a76/layouts_csv/exchange_rate/common/mappers.py b/backend/api/v1/modules/a76/layouts_csv/exchange_rate/common/mappers.py deleted file mode 100644 index 136b1c5f..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/exchange_rate/common/mappers.py +++ /dev/null @@ -1,48 +0,0 @@ -""" -Mapeo fila CSV → datos para ExchangeRate. -""" -from datetime import datetime -from decimal import Decimal -from typing import Dict, Any, Optional - -from .common_validators import ( - parse_date, - parse_decimal_positive, -) - -CURRENCY_MAX = 7 - - -def _str_or_none(val: Any, max_len: Optional[int] = None) -> Optional[str]: - if val is None: - return None - s = str(val).strip() - if not s: - return None - if max_len and len(s) > max_len: - return s[:max_len] - return s - - -def row_to_exchange_rate_data( - row_norm: Dict[str, Any], tenant_id: int, company_id: int, date_format_preference: Optional[str] = None -) -> Dict[str, Any]: - """Build dict for ExchangeRate model. Returns {} if FECHA or VALOR invalid.""" - parsed_date = parse_date(row_norm.get("FECHA"), date_format_preference) - value_decimal = parse_decimal_positive(row_norm.get("VALOR")) - if not parsed_date or value_decimal is None: - return {} - local = _str_or_none(row_norm.get("MONEDA_LOCAL"), CURRENCY_MAX) - if local: - local = local.upper() - foreign = _str_or_none(row_norm.get("MONEDA_EXTRANJERA"), CURRENCY_MAX) - if foreign: - foreign = foreign.upper() - return { - "tenant_id": tenant_id, - "company_id": company_id, - "date": parsed_date, - "value": value_decimal, - "local_currency": local, - "foreign_currency": foreign, - } diff --git a/backend/api/v1/modules/a76/layouts_csv/exchange_rate/csv_max_chars_for_headers.py b/backend/api/v1/modules/a76/layouts_csv/exchange_rate/csv_max_chars_for_headers.py deleted file mode 100644 index e456f8d1..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/exchange_rate/csv_max_chars_for_headers.py +++ /dev/null @@ -1,11 +0,0 @@ -"""Límites cabecera plantilla tipos de cambio.""" -from typing import Dict - -from .common.common_validators import CURRENCY_MAX - -EXCHANGE_RATE_CSV_MAX_CHARS: Dict[str, int] = { - "FECHA": 30, - "VALOR": 24, - "MONEDA_LOCAL": CURRENCY_MAX, - "MONEDA_EXTRANJERA": CURRENCY_MAX, -} diff --git a/backend/api/v1/modules/a76/layouts_csv/exchange_rate/routes.py b/backend/api/v1/modules/a76/layouts_csv/exchange_rate/routes.py deleted file mode 100644 index 946539a1..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/exchange_rate/routes.py +++ /dev/null @@ -1,200 +0,0 @@ -""" -Rutas de importación CSV para Tipos de Cambio. -Mismo flujo que customs_brokers/imports: upload → scan → status (polling) → commit. -""" -import json -import logging -import os -from uuid import uuid4 - -from fastapi import APIRouter, File, HTTPException, Query, UploadFile, Depends -from sqlalchemy.orm import Session -from typing import Dict, Any, Optional - -from core.celery_app import celery_app -from core.database import get_core_db -from core.security import get_current_user, validate_access_to_resource -from api.v1.modules.core.tasks_tracking import track_and_dispatch - -from ..common.track_commit_dispatch import dispatch_tracked_layouts_csv_commit -from ..common.responses import normalize_commit_status_payload -from .schemas import ImportJobResponse -from .tasks import ( - scan_file, - insert_valid_rows, - JOB_TYPE, - ER_IMPORT_META_PREFIX, - ER_IMPORT_REDIS_TTL, -) -from ..common import storage as common_storage -from ..common.error_csv import download_scan_errors_csv_stream - -router = APIRouter() -logger = logging.getLogger(__name__) - - -def _get_redis(): - import redis - url = os.getenv("VALKEY_URL", os.getenv("REDIS_URL", "redis://valkey:6379/0")) - return redis.Redis.from_url(url, decode_responses=False) - - -@router.post("/upload", response_model=ImportJobResponse) -async def upload_import_file( - file: UploadFile = File(...), - company_id: int = Query(..., description="Company ID"), - reemplazar_sin_preguntar: bool = Query( - True, - description="Si True, reemplaza tipos de cambio existentes para la misma fecha; si False, solo agrega nuevos (omite fechas ya existentes)", - ), - date_format: Optional[str] = Query( - None, - description="Formato de fecha del CSV: dd/mm/yyyy, mm/dd/yyyy o yyyy-mm-dd. Si no se envía, se intentan todos.", - ), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """ - Fase 1: Subir CSV, guardar en Redis, encolar tarea de escaneo. - Parámetros globales de carga: reemplazar_sin_preguntar (Modo Reemplazar vs Actualizar), date_format (Formato de Fecha). - """ - try: - tenant_id = validate_access_to_resource(db, company_id, current_user, ["csv_upload.process"]) - except Exception as e: - logger.error(f"ER import: access validation failed: {e}") - raise HTTPException(status_code=403, detail="Invalid company access") - - if not file.filename or not file.filename.lower().endswith(".csv"): - raise HTTPException(status_code=400, detail="Solo se permiten archivos .csv") - - job_id = str(uuid4()) - contents = await file.read() - - meta_data = { - "tenant_id": tenant_id, - "company_id": company_id, - "user_id": current_user.get("id"), - "template_id": "exchange_rates", - "reemplazar_sin_preguntar": reemplazar_sin_preguntar, - "date_format": date_format, - } - - try: - common_storage.store_import_file( - JOB_TYPE, - job_id, - contents, - meta_data, - tenant_id=int(tenant_id), - company_id=company_id, - ttl=ER_IMPORT_REDIS_TTL, - log_label="ER import", - ) - except common_storage.ImportStoreError as e: - logger.error(f"ER import: store error: {e}") - raise HTTPException(status_code=500, detail="No se pudo encolar el archivo.") - except Exception as e: - logger.error(f"ER import: Redis store error: {e}") - raise HTTPException(status_code=500, detail="No se pudo encolar el archivo.") - - track_and_dispatch( - db=db, - task=scan_file, - tenant_id=int(tenant_id), - company_id=company_id, - requested_by_user=current_user.get("preferred_username") - or current_user.get("email") - or current_user.get("sub"), - task_name="exchange_rate_scan_file", - task_group="layouts_csv", - task_origin="a76/layouts_csv/exchange_rate/upload", - args=[job_id], - task_id=job_id, - ) - - return ImportJobResponse( - job_id=job_id, - status="queued", - message="Archivo subido. Escaneo iniciado.", - ) - - -@router.get("/{job_id}/status") -async def get_import_status(job_id: str): - """ - Polling: estado del escaneo o del commit. - """ - task_result = celery_app.AsyncResult(job_id) - - if task_result.state == "PENDING": - return {"status": "processing", "progress": 0} - if task_result.state == "PROGRESS": - info = (task_result.info or {}) - return { - "status": "processing", - "progress": info.get("current", 0), - "total": info.get("total", 0), - } - if task_result.state == "SUCCESS": - result = task_result.result - if isinstance(result, dict) and "status" in result: - return normalize_commit_status_payload(result) - return {"status": "finished", "result": result} - - result = getattr(task_result, "result", None) - if isinstance(result, dict) and result.get("status") in ("finished", "warning"): - return normalize_commit_status_payload(result) - - logger.warning("ER import task %s failed: state=%s", job_id, task_result.state) - err_msg = None - tb = getattr(task_result, "traceback", None) - if tb and isinstance(tb, str): - lines = [l.strip() for l in tb.strip().split("\n") if l.strip()] - if lines: - err_msg = lines[-1] - if not err_msg: - try: - exc = task_result.get(propagate=False) - if exc is not None: - err_msg = str(exc) - except Exception: - pass - if not err_msg and result is not None: - if not isinstance(result, dict): - err_msg = str(result) - elif result.get("error") or result.get("message"): - err_msg = result.get("error") or result.get("message") - return {"status": "failed", "error": err_msg or "Task failed"} - - -@router.post("/{job_id}/commit") -async def commit_import_job( - job_id: str, - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """ - Fase 2: Usuario confirma; se encola la inserción de filas válidas. - """ - r = _get_redis() - commit_id = dispatch_tracked_layouts_csv_commit( - db=db, - current_user=current_user, - redis_client=r, - layout_job_id=job_id, - meta_redis_key=f"{ER_IMPORT_META_PREFIX}{job_id}", - celery_task=insert_valid_rows, - task_name="exchange_rate_insert_valid_rows", - task_origin="a76/layouts_csv/exchange_rate/commit", - args=[job_id], - ) - return { - "status": "committing", - "message": "Inserción iniciada.", - "commit_job_id": commit_id, - } - - -@router.get("/{job_id}/errors/scan-csv") -async def download_scan_errors_csv(job_id: str): - return download_scan_errors_csv_stream("er", job_id) diff --git a/backend/api/v1/modules/a76/layouts_csv/exchange_rate/schemas.py b/backend/api/v1/modules/a76/layouts_csv/exchange_rate/schemas.py deleted file mode 100644 index 98c39a98..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/exchange_rate/schemas.py +++ /dev/null @@ -1,25 +0,0 @@ -from pydantic import BaseModel -from typing import Optional - - -class ImportJobResponse(BaseModel): - job_id: str - status: str - message: str - - -class CommitRequest(BaseModel): - pass # no body needed for single model - - -class ImportJobStatus(BaseModel): - status: str - job_id: str - total_rows: Optional[int] = 0 - error_count: Optional[int] = 0 - valid_rows: Optional[int] = 0 - error: Optional[str] = None - inserted: Optional[int] = 0 - skipped_invalid: Optional[int] = 0 - skipped_missing_fk: Optional[int] = 0 - skipped_details: Optional[list] = None diff --git a/backend/api/v1/modules/a76/layouts_csv/exchange_rate/tasks.py b/backend/api/v1/modules/a76/layouts_csv/exchange_rate/tasks.py deleted file mode 100644 index 3ebf4bbd..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/exchange_rate/tasks.py +++ /dev/null @@ -1,269 +0,0 @@ -""" -Tareas Celery para importación CSV de Tipos de Cambio. -Flujo: scan_file (validación) → insert_valid_rows (commit). -Usa layouts_csv.common (storage, normalize, meta, responses, csv_reader). -""" -import json -import logging -import os -from typing import Dict, Any, Optional, List - -from core.celery_app import celery_app -from core.database import CoreSessionLocal - -from ..common import storage as common_storage -from ..common import normalize as common_normalize -from ..common import meta as common_meta -from ..common import responses as common_responses -from ..common import csv_reader as common_csv_reader -from .template_config import row_from_template -from .validators import validate_row_exchange_rate -from .common.mappers import row_to_exchange_rate_data - -logger = logging.getLogger(__name__) - -JOB_TYPE = "er" -TEMPLATE_ID = "exchange_rates" - -# Para routes.py -ER_IMPORT_FILE_PREFIX = "er_import_file:" -ER_IMPORT_META_PREFIX = "er_import_meta:" -ER_IMPORT_ERROR_LINES_PREFIX = "er_import_error_lines:" -ER_IMPORT_REDIS_TTL = common_storage.IMPORT_REDIS_TTL - - -def _norm_row(row: Dict[str, Any]) -> Dict[str, Any]: - return row_from_template(row, common_normalize.normalize_header, TEMPLATE_ID) - - -def _do_scan(job_id: str, progress_callback: Optional[Any] = None) -> Dict[str, Any]: - file_path = common_storage.ensure_file_from_redis(JOB_TYPE, job_id, "ER import") - if not file_path: - return {"status": "failed", "error": "Archivo no encontrado (expirado o no subido). Sube de nuevo."} - common_storage.ensure_meta_from_redis(JOB_TYPE, job_id, file_path, "ER import") - - error_path = common_storage.error_path_for_job(JOB_TYPE, job_id) - - try: - total_rows = common_csv_reader.count_csv_rows(file_path) - except Exception as e: - return {"status": "failed", "error": str(e)} - - try: - tenant_id, company_id = common_meta.require_tenant_context(file_path) - except ValueError as e: - return {"status": "failed", "error": str(e)} - - meta = common_meta.load_meta(file_path) - # Formato activo del selector del frontend; si no viene, mismo default que el front (dd/mm/yyyy) - date_format_preference = meta.get("date_format") or meta.get("dateFormat") or "dd/mm/yyyy" - - error_count = 0 - processed_rows = 0 - errors_detail: List[Dict[str, Any]] = [] - error_lines_list: List[int] = [] - - try: - with open(error_path, "w", encoding="utf-8") as f_err: - for i, row in common_csv_reader.iter_csv_rows(file_path): - if progress_callback: - progress_callback(i, total_rows, error_count) - - row_norm = _norm_row(row) - row_errors = validate_row_exchange_rate( - row_norm, - i, - raw_row=row, - date_format_preference=date_format_preference, - ) - if row_errors: - has_blocking_error = any(not e.get("warning", False) for e in row_errors) - if has_blocking_error: - error_count += 1 - error_lines_list.append(i) - for e in row_errors: - if not e.get("warning", False): - f_err.write(json.dumps(e) + "\n") - if len(errors_detail) < 500: - for e in row_errors: - if len(errors_detail) >= 500: - break - errors_detail.append({ - "line": e["line"], - "col": e.get("col", ""), - "msg": e.get("msg", ""), - "solution": e.get("solution", ""), - "warning": bool(e.get("warning", False)), - }) - processed_rows += 1 - - if error_lines_list: - common_storage.store_error_lines(JOB_TYPE, job_id, error_lines_list) - except Exception as e: - logger.error("ER import scan failed: %s", e) - return {"status": "failed", "error": str(e)} - - return common_responses.scan_result(job_id, processed_rows, error_count, errors_detail) - - -@celery_app.task(bind=True) -def scan_file(self, job_id: str, config: str = None): - logger.info("ER import: starting scan for job %s", job_id) - - def on_progress(current: int, total: int, errors: int) -> None: - self.update_state(state="PROGRESS", meta={"current": current, "total": total, "errors": errors}) - - return _do_scan(job_id, progress_callback=on_progress) - - -def _do_commit(job_id: str) -> Dict[str, Any]: - file_path = common_storage.ensure_file_from_redis(JOB_TYPE, job_id, "ER import") - if not file_path: - alt_path = common_storage.file_path_for_job(JOB_TYPE, job_id) - if not os.path.exists(alt_path): - return {"status": "failed", "error": "Archivo no encontrado (expirado). Sube y confirma de nuevo."} - file_path = alt_path - else: - common_storage.ensure_meta_from_redis(JOB_TYPE, job_id, file_path, "ER import") - - error_path = common_storage.error_path_for_job(JOB_TYPE, job_id) - error_lines = common_storage.get_error_lines(JOB_TYPE, job_id, error_path) - - try: - tenant_id, company_id = common_meta.require_tenant_context(file_path) - except ValueError as e: - return {"status": "failed", "error": str(e)} - - meta = common_meta.load_meta(file_path) - reemplazar_sin_preguntar = meta.get("reemplazar_sin_preguntar", True) - date_format_preference = meta.get("date_format") or meta.get("dateFormat") - - from api.v1.modules.a76.general_catalogs.exchange_rate.models import ExchangeRate - - inserted_count = 0 - updated_count = 0 - skipped_duplicate = 0 - skipped_invalid = 0 - skipped_details: List[Dict[str, Any]] = [] - meta_path = common_meta.get_meta_path(file_path) - - try: - with CoreSessionLocal() as session: - existing_by_date: Dict[tuple, ExchangeRate] = {} - for er in ( - session.query(ExchangeRate) - .filter( - ExchangeRate.tenant_id == tenant_id, - ExchangeRate.company_id == company_id, - ) - .all() - ): - d = er.date.date() if hasattr(er.date, "date") else er.date - existing_by_date[(tenant_id, company_id, d)] = er - - for i, row in common_csv_reader.iter_csv_rows(file_path): - if i in error_lines: - continue - - row_norm = _norm_row(row) - row_errors = validate_row_exchange_rate( - row_norm, - i, - raw_row=row, - date_format_preference=date_format_preference, - ) - if row_errors and any(not e.get("warning", False) for e in row_errors): - skipped_invalid += 1 - blocking_errors = [e for e in row_errors if not e.get("warning", False)] - for blocking in blocking_errors: - skipped_details.append({ - "line": i, - "reason": f"{blocking.get('col', '')}: {blocking.get('msg', '')}", - "solution": blocking.get("solution", ""), - }) - continue - - data = row_to_exchange_rate_data(row_norm, tenant_id, company_id, date_format_preference) - if not data or not data.get("date"): - skipped_invalid += 1 - skipped_details.append( - { - "line": i, - "reason": "FECHA o VALOR no válidos", - "solution": "Capturar en la columna FECHA una fecha válida y en la columna VALOR un número válido.", - } - ) - continue - - key_date = data["date"].date() if hasattr(data["date"], "date") else data["date"] - existing = existing_by_date.get((tenant_id, company_id, key_date)) - if existing: - if not reemplazar_sin_preguntar: - skipped_duplicate += 1 - continue - existing.value = data["value"] - existing.local_currency = data.get("local_currency") - existing.foreign_currency = data.get("foreign_currency") - session.add(existing) - updated_count += 1 - else: - new_er = ExchangeRate(**data) - session.add(new_er) - existing_by_date[(tenant_id, company_id, key_date)] = new_er - inserted_count += 1 - - try: - session.commit() - except Exception as db_err: - session.rollback() - logger.error("ER import DB error: %s", db_err) - return {"status": "failed", "error": str(db_err)} - - except Exception as e: - logger.exception("ER import task failed") - return {"status": "failed", "error": str(e)} - - common_storage.cleanup_import_job( - JOB_TYPE, job_id, - file_path=file_path, - error_path=error_path, - meta_path=meta_path, - ) - - if inserted_count == 0 and updated_count == 0 and (skipped_invalid > 0 or skipped_duplicate > 0): - return { - "status": "warning", - "inserted": 0, - "updated": 0, - "skipped_invalid": skipped_invalid, - "skipped_duplicate": skipped_duplicate, - "skipped_missing_fk": 0, - "skipped_details": skipped_details, - "message": f"No se insertaron registros. {skipped_invalid} rechazados, {skipped_duplicate} omitidos por fecha existente.", - } - if inserted_count == 0 and updated_count == 0: - return { - "status": "failed", - "error": "No hay registros válidos en el archivo CSV", - "inserted": 0, - "updated": 0, - "skipped_invalid": skipped_invalid, - "skipped_duplicate": skipped_duplicate, - "skipped_missing_fk": 0, - "skipped_details": skipped_details, - } - return { - "status": "finished", - "inserted": inserted_count, - "updated": updated_count, - "skipped_invalid": skipped_invalid, - "skipped_duplicate": skipped_duplicate, - "skipped_missing_fk": 0, - "skipped_details": skipped_details, - } - - -@celery_app.task(bind=True) -def insert_valid_rows(self, job_id: str): - logger.info("ER import: starting commit for job %s", job_id) - return _do_commit(job_id) diff --git a/backend/api/v1/modules/a76/layouts_csv/exchange_rate/template_config.py b/backend/api/v1/modules/a76/layouts_csv/exchange_rate/template_config.py deleted file mode 100644 index c8d5471f..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/exchange_rate/template_config.py +++ /dev/null @@ -1,56 +0,0 @@ -""" -Configuración de plantilla CSV para Tipos de Cambio (EstructuraCatTiposCambio.xls). -Solo se leen columnas definidas aquí; el resto se ignora. -""" - -from typing import Dict, List, Any, Optional - -from ..common.cell_value import cell_to_str -from ..common.csv_hint_inject import inject_max_char_hints_into_columns -from ..common.inject_csv_labels_en import inject_en_labels_into_template_columns -from ..common.template_locale import merge_download_header_into_lookup, merge_labels_into_lookup -from .csv_max_chars_for_headers import EXCHANGE_RATE_CSV_MAX_CHARS - -TEMPLATE_COLUMNS: Dict[str, List[Dict[str, Any]]] = { - "exchange_rates": [ - {"canonical": "FECHA", "aliases": ["FECHA APLICABLE", "DATE", "FECHA TIPO CAMBIO"]}, - {"canonical": "VALOR", "aliases": ["TIPO_DE_CAMBIO", "TIPO CAMBIO", "TIPO DE CAMBIO", "VALUE", "RATE"]}, - {"canonical": "MONEDA_LOCAL", "aliases": ["MONEDA LOCAL", "LOCAL_CURRENCY", "MONEDA BASE"]}, - {"canonical": "MONEDA_EXTRANJERA", "aliases": ["MONEDA EXTRANJERA", "FOREIGN_CURRENCY", "MONEDA DESTINO"]}, - ], -} - -inject_en_labels_into_template_columns(TEMPLATE_COLUMNS) - -inject_max_char_hints_into_columns( - TEMPLATE_COLUMNS.get("exchange_rates"), EXCHANGE_RATE_CSV_MAX_CHARS -) - - -def build_normalized_lookup(normalize_header_fn, template_id: str = "exchange_rates") -> Dict[str, str]: - """normalized_header -> canonical_name para plantilla exchange_rates.""" - cols = TEMPLATE_COLUMNS.get(template_id) - if not cols: - return {} - lookup: Dict[str, str] = {} - for item in cols: - canonical = item["canonical"] - lookup[normalize_header_fn(canonical)] = canonical - for alias in item.get("aliases") or []: - lookup[normalize_header_fn(alias)] = canonical - merge_labels_into_lookup(item, canonical, normalize_header_fn, lookup) - merge_download_header_into_lookup(item, canonical, normalize_header_fn, lookup) - return lookup - - -def row_from_template(row: Dict[str, Any], normalize_header_fn, template_id: str = "exchange_rates") -> Dict[str, Any]: - """Fila CSV con solo columnas de la plantilla, en nombres canónicos.""" - lookup = build_normalized_lookup(normalize_header_fn, template_id) - if not lookup: - return {normalize_header_fn(k): cell_to_str(v) for k, v in row.items()} - out: Dict[str, Any] = {} - for csv_header, value in row.items(): - key_norm = normalize_header_fn(csv_header) - if key_norm in lookup: - out[lookup[key_norm]] = cell_to_str(value) - return out diff --git a/backend/api/v1/modules/a76/layouts_csv/exchange_rate/validators/__init__.py b/backend/api/v1/modules/a76/layouts_csv/exchange_rate/validators/__init__.py deleted file mode 100644 index cf3b1792..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/exchange_rate/validators/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .create import validate_row_exchange_rate - -__all__ = ["validate_row_exchange_rate"] diff --git a/backend/api/v1/modules/a76/layouts_csv/exchange_rate/validators/common.py b/backend/api/v1/modules/a76/layouts_csv/exchange_rate/validators/common.py deleted file mode 100644 index 89acf620..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/exchange_rate/validators/common.py +++ /dev/null @@ -1,102 +0,0 @@ -""" -Validaciones comunes de fila para import CSV de tipos de cambio. -Paridad Clarion: desfase (Col C vacía), obligatorios (Col A Fecha, Col B Tipo de Cambio), -validación fecha (longitud, día acorde al mes, mes ≤ 12; sin límite de año). -""" -from typing import Dict, Any, Optional, List - -from ...common.cell_value import cell_to_str -from ..common.common_validators import ( - check_required_value_positive, - check_optional_max_length, - validate_fecha_clarion, - CURRENCY_MAX, -) - - -MSG_DESFASE = "Error: Existe un desfase en esta línea." -MSG_DESFASE_SOLUCION = "Revisar esta línea del archivo CSV y verificar cada campo este en la posicion correcta." - -MSG_OBLIGATORIOS = "Existen campos vacios que son obligatorios, es la (Col.A) Fecha , (Col.B) Tipo de Cambio." -MSG_OBLIGATORIOS_SOLUCION = "Revisar la línea del archivo y capturar los campos con la información correcta." - - -def validate_row_desfase(raw_row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]: - """ - Si la fila tiene 3 o más columnas y la 3ª tiene valor, error de desfase (Clarion ColumnaC <> ''). - """ - values_ordered = list(raw_row.values()) if raw_row else [] - if len(values_ordered) >= 3: - cell = cell_to_str(values_ordered[2]) - if cell.strip(): - return { - "line": line_num, - "col": "", - "msg": f"{MSG_DESFASE} {MSG_DESFASE_SOLUCION}", - "warning": True, - } - return None - - -def validate_row_required_exchange_rate(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]: - """FECHA y VALOR obligatorios con mensaje Clarion.""" - fecha = (row.get("FECHA") or "").strip() - valor = (row.get("VALOR") or "").strip() - if not fecha or not valor: - return { - "line": line_num, - "col": "FECHA" if not fecha else "VALOR", - "msg": f"{MSG_OBLIGATORIOS} {MSG_OBLIGATORIOS_SOLUCION}", - } - return None - - -def validate_row_fecha_clarion( - row: Dict[str, Any], line_num: int, date_format_preference: Optional[str] = None -) -> Optional[Dict[str, Any]]: - """Longitud ≤ 10 y fecha válida (día acorde al mes, mes ≤ 12; sin límite de año).""" - raw_fecha = (row.get("FECHA") or "").strip() - if not raw_fecha: - return None - return validate_fecha_clarion(raw_fecha, line_num, date_format_preference) - - -def validate_row_exchange_rate( - row: Dict[str, Any], - line_num: int, - raw_row: Optional[Dict[str, Any]] = None, - date_format_preference: Optional[str] = None, -) -> List[Dict[str, Any]]: - """ - Valida una fila de CSV de tipos de cambio. - Orden: desfase (si raw_row) → obligatorios → fecha Clarion → VALOR > 0 → MONEDA opc (max 7). - date_format_preference: valor del parámetro global (ej. dd/mm/yyyy, mm/dd/yyyy, yyyy-mm-dd). - """ - errors: List[Dict[str, Any]] = [] - - if raw_row is not None: - err = validate_row_desfase(raw_row, line_num) - if err: - errors.append(err) - - err = validate_row_required_exchange_rate(row, line_num) - if err: - errors.append(err) - - err = validate_row_fecha_clarion(row, line_num, date_format_preference) - if err: - errors.append(err) - - err = check_required_value_positive(row, line_num) - if err: - errors.append(err) - - err = check_optional_max_length(row, "MONEDA_LOCAL", CURRENCY_MAX, line_num) - if err: - errors.append(err) - - err = check_optional_max_length(row, "MONEDA_EXTRANJERA", CURRENCY_MAX, line_num) - if err: - errors.append(err) - - return errors diff --git a/backend/api/v1/modules/a76/layouts_csv/exchange_rate/validators/create.py b/backend/api/v1/modules/a76/layouts_csv/exchange_rate/validators/create.py deleted file mode 100644 index ef5d3d47..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/exchange_rate/validators/create.py +++ /dev/null @@ -1,6 +0,0 @@ -""" -Punto de entrada de validación para import de una fila tipo de cambio. -""" -from .common import validate_row_exchange_rate - -__all__ = ["validate_row_exchange_rate"] diff --git a/backend/api/v1/modules/a76/layouts_csv/exportacion/__init__.py b/backend/api/v1/modules/a76/layouts_csv/exportacion/__init__.py deleted file mode 100644 index dadf0cb2..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/exportacion/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# layouts_csv.exportacion — carga CSV exportación (encabezado y partidas) diff --git a/backend/api/v1/modules/a76/layouts_csv/exportacion/routes.py b/backend/api/v1/modules/a76/layouts_csv/exportacion/routes.py deleted file mode 100644 index 3b73ff2e..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/exportacion/routes.py +++ /dev/null @@ -1,247 +0,0 @@ -""" -Rutas de importación CSV para Exportación (encabezado y partidas). -Flujo: upload → scan → status (polling) → commit. Sin validaciones ni inserción aún. -""" -import json -import logging -import os -from uuid import uuid4 -import csv -import io - -from fastapi import APIRouter, File, HTTPException, UploadFile, Depends, Form, Query -from fastapi.responses import StreamingResponse -from sqlalchemy.orm import Session -from typing import Literal, Optional, Dict, Any - -from core.celery_app import celery_app -from core.database import get_core_db -from core.security import get_current_user, validate_access_to_resource -from api.v1.modules.core.tasks_tracking import track_and_dispatch - -from .schemas import ImportJobResponse, CommitRequest -from .tasks import scan_file, insert_valid_rows, JOB_TYPE, EXP_IMPORT_REDIS_TTL -from ..common import storage as common_storage -from ..common.track_commit_dispatch import dispatch_tracked_layouts_csv_commit -from ..common.responses import normalize_commit_status_payload - -router = APIRouter() -logger = logging.getLogger(__name__) - - -def _get_redis(): - import redis - url = os.getenv("VALKEY_URL", os.getenv("REDIS_URL", "redis://valkey:6379/0")) - return redis.Redis.from_url(url, decode_responses=False) - - -@router.post("/upload/{model_target}", response_model=ImportJobResponse) -async def upload_import_file( - model_target: Literal["invoice_header", "invoice_details", "invoice_series"], - file: UploadFile = File(...), - footer_config: Optional[str] = Form(None), - template_id: Optional[str] = Form(None), - company_id: int = Query(..., description="Company ID"), - operation_type: Optional[str] = Query("exp"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """Subir CSV, guardar en Redis, encolar scan. operation_type=exp para exportación.""" - try: - tenant_id = validate_access_to_resource(db, company_id, current_user, ["csv_upload.process"]) - except Exception as e: - logger.error("Exportación import: access validation failed: %s", e) - raise HTTPException(status_code=403, detail="Invalid company access") - - if not file.filename or not file.filename.lower().endswith(".csv"): - raise HTTPException(status_code=400, detail="Solo se permiten archivos .csv") - - job_id = str(uuid4()) - contents = await file.read() - - default_template = ( - "exp_def_header" if model_target == "invoice_header" - else "exp_def_series" if model_target == "invoice_series" - else "exp_def_partidas" - ) - meta_data = { - "tenant_id": tenant_id, - "company_id": company_id, - "user_id": current_user.get("id"), - "capture_user": ( - current_user.get("preferred_username") - or current_user.get("email") - or current_user.get("sub") - or "CSV" - ), - "footer_config": footer_config, - "operation_type": operation_type or "exp", - "template_id": template_id or default_template, - } - - try: - common_storage.store_import_file( - JOB_TYPE, - job_id, - contents, - meta_data, - tenant_id=int(tenant_id), - company_id=company_id, - ttl=EXP_IMPORT_REDIS_TTL, - log_label="Exportación import", - ) - except common_storage.ImportStoreError as e: - logger.error("Exportación import: store error: %s", e) - raise HTTPException(status_code=500, detail="No se pudo encolar el archivo.") - except Exception as e: - logger.error("Exportación import: Redis store error: %s", e) - raise HTTPException(status_code=500, detail="No se pudo encolar el archivo.") - - track_and_dispatch( - db=db, - task=scan_file, - tenant_id=int(tenant_id), - company_id=company_id, - requested_by_user=current_user.get("preferred_username") or current_user.get("email") or current_user.get("sub"), - task_name="exportacion_scan_file", - task_group="layouts_csv", - task_origin="a76/layouts_csv/exportacion/upload", - args=[job_id, model_target, footer_config], - task_id=job_id, - ) - - return ImportJobResponse( - job_id=job_id, - status="queued", - message="Archivo subido. Escaneo iniciado.", - ) - - -@router.get("/{job_id}/status") -async def get_import_status(job_id: str): - """Polling: estado del escaneo o del commit.""" - task_result = celery_app.AsyncResult(job_id) - - if task_result.state == "PENDING": - return {"status": "processing", "progress": 0} - if task_result.state == "PROGRESS": - info = task_result.info or {} - return { - "status": "processing", - "progress": info.get("current", 0), - "total": info.get("total", 0), - } - if task_result.state == "SUCCESS": - result = task_result.result - if isinstance(result, dict) and "status" in result: - return normalize_commit_status_payload(result) - return {"status": "finished", "result": result} - - if isinstance(getattr(task_result, "result", None), dict) and task_result.result.get("status") in ("finished", "warning"): - return normalize_commit_status_payload(task_result.result) - - logger.warning("Exportación import task %s failed: state=%s", job_id, task_result.state) - err_msg = None - tb = getattr(task_result, "traceback", None) - if tb and isinstance(tb, str): - lines = [l.strip() for l in tb.strip().split("\n") if l.strip()] - if lines: - err_msg = lines[-1] - if not err_msg: - try: - exc = task_result.get(propagate=False) - if exc is not None: - err_msg = str(exc) - except Exception: - pass - if not err_msg: - result = getattr(task_result, "result", None) - if result is not None and not isinstance(result, dict): - err_msg = str(result) - elif isinstance(result, dict) and (result.get("error") or result.get("message")): - err_msg = result.get("error") or result.get("message") - return {"status": "failed", "error": err_msg or "Task failed"} - - -@router.post("/{job_id}/commit") -async def commit_import_job( - job_id: str, - body: CommitRequest, - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """Usuario confirma; se encola la tarea de commit (por ahora sin inserción real).""" - _, meta_key, _ = common_storage.storage_keys(JOB_TYPE, job_id) - r = _get_redis() - commit_id = dispatch_tracked_layouts_csv_commit( - db=db, - current_user=current_user, - redis_client=r, - layout_job_id=job_id, - meta_redis_key=meta_key, - celery_task=insert_valid_rows, - task_name="exportacion_insert_valid_rows", - task_origin="a76/layouts_csv/exportacion/commit", - args=[job_id, body.model_target], - ) - return { - "status": "committing", - "message": "Proceso de commit iniciado.", - "commit_job_id": commit_id, - } - - -@router.get("/{job_id}/errors/scan-csv") -async def download_scan_errors_csv(job_id: str): - """ - Descarga CSV con TODO el detalle de errores del scan (sin límite), - leyendo el archivo JSONL generado por el worker. - """ - - # Exportación usa job_type "exp" (storage: exp_{job_id}.jsonl) - error_path = common_storage.error_path_for_job(JOB_TYPE, job_id) - if not os.path.exists(error_path): - raise HTTPException( - status_code=404, - detail="Archivo de errores no encontrado. Vuelve a escanear o intenta más tarde.", - ) - - def row_iter(): - buffer = io.StringIO() - writer = csv.writer(buffer) - writer.writerow(["linea", "columna", "mensaje", "solucion"]) - yield buffer.getvalue() - buffer.seek(0) - buffer.truncate(0) - - with open(error_path, "r", encoding="utf-8") as f: - for line in f: - if not line.strip(): - continue - try: - payload = json.loads(line) - except Exception: - continue - - items = payload if isinstance(payload, list) else [payload] - for err in items: - if not isinstance(err, dict): - continue - buffer.seek(0) - buffer.truncate(0) - writer.writerow( - [ - err.get("line", ""), - err.get("col", ""), - err.get("msg", ""), - err.get("solution", ""), - ] - ) - yield buffer.getvalue() - buffer.seek(0) - buffer.truncate(0) - - headers = { - "Content-Disposition": f'attachment; filename="errores_{job_id}.csv"' - } - return StreamingResponse(row_iter(), media_type="text/csv; charset=utf-8", headers=headers) diff --git a/backend/api/v1/modules/a76/layouts_csv/exportacion/schemas.py b/backend/api/v1/modules/a76/layouts_csv/exportacion/schemas.py deleted file mode 100644 index ca97e233..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/exportacion/schemas.py +++ /dev/null @@ -1,23 +0,0 @@ -from pydantic import BaseModel -from typing import Optional, Literal - - -class ImportJobResponse(BaseModel): - job_id: str - status: str - message: str - - -class CommitRequest(BaseModel): - model_target: Literal["invoice_header", "invoice_details", "invoice_series"] - - -class ImportJobStatus(BaseModel): - status: str - job_id: str - total_rows: Optional[int] = 0 - error_count: Optional[int] = 0 - valid_rows: Optional[int] = 0 - error: Optional[str] = None - inserted: Optional[int] = 0 - error_file: Optional[str] = None diff --git a/backend/api/v1/modules/a76/layouts_csv/exportacion/tasks.py b/backend/api/v1/modules/a76/layouts_csv/exportacion/tasks.py deleted file mode 100644 index bbd06283..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/exportacion/tasks.py +++ /dev/null @@ -1,154 +0,0 @@ -""" -Tareas Celery para importación CSV de Exportación (encabezado y partidas). -Para invoice_header: scan_file delega en facturas._do_scan_file (validaciones FK y reporte de errores); -insert_valid_rows delega en facturas._do_insert_valid_rows (inserción en BD). Storage "exp". -""" -import logging -import os -from typing import Dict, Any, Optional - -from core.celery_app import celery_app - -from ..common import storage as common_storage -from ..common import normalize as common_normalize -from ..common import meta as common_meta -from ..common import responses as common_responses -from ..common import csv_reader as common_csv_reader -from .template_config import row_from_template - -logger = logging.getLogger(__name__) - -JOB_TYPE = "exp" -EXP_IMPORT_REDIS_TTL = common_storage.IMPORT_REDIS_TTL - - -def _ensure_file(job_id: str) -> Optional[str]: - return common_storage.ensure_file_from_redis(JOB_TYPE, job_id, "Exportación import") - - -def _ensure_meta(job_id: str, file_path: str) -> bool: - return common_storage.ensure_meta_from_redis(JOB_TYPE, job_id, file_path, "Exportación import") - - -def _norm_row(row: Dict[str, Any], template_id: str) -> Dict[str, Any]: - return row_from_template(row, template_id, common_normalize.normalize_header) - - -@celery_app.task(bind=True, name="api.v1.modules.a76.layouts_csv.exportacion.tasks.scan_file") -def scan_file(self, job_id: str, model_target: str, config: str = None): - """ - Para invoice_header: delega en facturas._do_scan_file con storage "exp" (validaciones FK, encabezados_expo). - Para invoice_details: delega en facturas._do_scan_file con template exp_def_partidas (validaciones partidas expo). - """ - logger.info("Exportación import: starting scan for job %s target %s", job_id, model_target) - - if model_target == "invoice_header": - from api.v1.modules.a76.layouts_csv.facturas.tasks import _do_scan_file - return _do_scan_file(self, job_id, "invoice_header", config, job_type_override="exp") - - if model_target == "invoice_details": - from api.v1.modules.a76.layouts_csv.facturas.tasks import _do_scan_file - return _do_scan_file(self, job_id, "invoice_details", config, job_type_override="exp") - - if model_target == "invoice_series": - from api.v1.modules.a76.layouts_csv.facturas.tasks import _do_scan_file - return _do_scan_file(self, job_id, "invoice_series", config, job_type_override="exp") - - # Fallback (e.g. unknown model_target) - file_path = _ensure_file(job_id) - if not file_path: - return {"status": "failed", "error": "Archivo no encontrado (expirado o no subido). Sube de nuevo."} - if os.path.getsize(file_path) == 0: - return {"status": "failed", "error": "El archivo está vacío."} - _ensure_meta(job_id, file_path) - try: - common_meta.require_tenant_context(file_path) - except ValueError as e: - return {"status": "failed", "error": str(e)} - meta = common_meta.load_meta(file_path) or {} - template_id = meta.get("template_id") or "exp_def_details" - try: - total_rows = common_csv_reader.count_csv_rows(file_path, has_header=True) - except Exception as e: - return {"status": "failed", "error": str(e)} - processed_rows = 0 - try: - for i, row in common_csv_reader.iter_csv_rows(file_path, fieldnames=None): - self.update_state(state="PROGRESS", meta={"current": i, "total": total_rows}) - _norm_row(row, template_id) - processed_rows += 1 - except Exception as e: - logger.error("Exportación import scan failed: %s", e) - return {"status": "failed", "error": str(e)} - return common_responses.scan_result(job_id, processed_rows, 0, []) - - -@celery_app.task(bind=True, name="api.v1.modules.a76.layouts_csv.exportacion.tasks.insert_valid_rows") -def insert_valid_rows(self, job_id: str, model_target: str): - """ - Commit: para invoice_header delega en facturas (inserción real en BD con storage "exp"). - Para invoice_details delega en facturas (inserción partidas expo cuando esté implementada; mientras tanto mismo flujo). - """ - logger.info("Exportación import: starting commit for job %s target %s", job_id, model_target) - - if model_target == "invoice_header": - from api.v1.modules.a76.layouts_csv.facturas.tasks import _do_insert_valid_rows - return _do_insert_valid_rows(job_id, "invoice_header", job_type_override="exp") - - if model_target == "invoice_details": - from api.v1.modules.a76.layouts_csv.facturas.tasks import _do_insert_valid_rows - return _do_insert_valid_rows(job_id, "invoice_details", job_type_override="exp") - - if model_target == "invoice_series": - from api.v1.modules.a76.layouts_csv.facturas.tasks import _do_insert_valid_rows - return _do_insert_valid_rows(job_id, "invoice_series", job_type_override="exp") - - # Fallback: stub sin inserción - file_path = _ensure_file(job_id) - if not file_path: - alt_path = common_storage.file_path_for_job(JOB_TYPE, job_id) - if not os.path.exists(alt_path): - return {"status": "failed", "error": "Archivo no encontrado (expirado). Sube y confirma de nuevo."} - file_path = alt_path - else: - _ensure_meta(job_id, file_path) - - try: - common_meta.require_tenant_context(file_path) - except ValueError as e: - return {"status": "failed", "error": str(e)} - - meta = common_meta.load_meta(file_path) or {} - meta_path = common_meta.get_meta_path(file_path) - error_path = common_storage.error_path_for_job(JOB_TYPE, job_id) - error_lines = common_storage.get_error_lines(JOB_TYPE, job_id, error_path) - - template_id = meta.get("template_id") or ( - "exp_def_header" if model_target == "invoice_header" else "exp_def_partidas" - ) - - try: - for i, row in common_csv_reader.iter_csv_rows(file_path, fieldnames=None): - if i in error_lines: - continue - _norm_row(row, template_id) - except Exception as e: - logger.error("Exportación import commit read failed: %s", e) - return {"status": "failed", "error": str(e)} - - common_storage.cleanup_import_job( - JOB_TYPE, job_id, - file_path=file_path, - error_path=error_path, - meta_path=meta_path, - ) - - return { - "status": "finished", - "inserted": 0, - "skipped_invalid": 0, - "skipped_missing_fk": 0, - "skipped_duplicate": 0, - "skipped_details": [], - "message": "Proceso base listo; validaciones e inserción pendientes.", - } diff --git a/backend/api/v1/modules/a76/layouts_csv/exportacion/template_config.py b/backend/api/v1/modules/a76/layouts_csv/exportacion/template_config.py deleted file mode 100644 index a8599d19..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/exportacion/template_config.py +++ /dev/null @@ -1,142 +0,0 @@ -""" -Plantillas CSV para Exportación (encabezado y partidas). -Misma estructura que facturas exp_def_header / exp_def_details; módulo autocontenido. -""" - -from typing import Dict, List, Any, Optional - -from ..common.cell_value import cell_to_str -from ..common.inject_csv_labels_en import inject_en_labels_into_template_columns -from ..common.template_locale import merge_download_header_into_lookup, merge_labels_into_lookup -from ..facturas.invoice_csv_column_hints import apply_invoice_csv_hints - -# Columnas para encabezado y partidas de exportación (EstructuraEncFacExpoCamReg / EstructuraParExpoCamReg) -TEMPLATE_COLUMNS: Dict[str, List[Dict[str, Any]]] = { - "exp_def_header": [ - {"canonical": "NUMERO FACTURA", "aliases": ["NUM FACTURA", "FACTURA", "ID"]}, - {"canonical": "FECHA FACTURA", "aliases": ["FECHA"]}, - {"canonical": "FECHA EMISION"}, - {"canonical": "CLAVE PROVEEDOR"}, - {"canonical": "CLAVE VENDIDO A"}, - {"canonical": "CLAVE ENVIADO A"}, - {"canonical": "REGIMEN", "aliases": ["CLAVEDOCUMENTO"]}, - {"canonical": "ADUANA DE CRUCE"}, - {"canonical": "CLAVE MONEDA"}, - {"canonical": "CLAVE INCOTERM"}, - {"canonical": "TIPO MONEDA"}, - {"canonical": "TIPO DE CAMBIO"}, - {"canonical": "TIPO PESO"}, - {"canonical": "TIPO TRANSPORTE"}, - {"canonical": "REMESA"}, - {"canonical": "AGENTE ADUANAL"}, - {"canonical": "FLETES"}, - {"canonical": "VALOR SEGUROS"}, - {"canonical": "SEGUROS"}, - {"canonical": "EMBALAJES"}, - {"canonical": "OTROS INCREMENTABLES"}, - {"canonical": "NUM PROYECTO", "aliases": ["NUMPROYECTO"]}, - {"canonical": "ORDEN COMPRA", "aliases": ["ORDENCOMPRA"]}, - {"canonical": "FACTURA ALTERNA"}, - {"canonical": "FACTURA EXPO REF", "aliases": ["FACTURAEXPOREF"]}, - {"canonical": "OBSERVACIONES E"}, - {"canonical": "OBSERVACIONES I"}, - {"canonical": "E DOCUMENT"}, - {"canonical": "NUM OPERACION"}, - {"canonical": "CLAVE TRANSPORTISTA"}, - {"canonical": "NOMBRE CONDUCTOR"}, - {"canonical": "NUMERO TRANSPORTE"}, - {"canonical": "PRECINTO"}, - ], - "exp_def_details": [ - {"canonical": "NUMERO FACTURA", "aliases": ["NUM FACTURA", "FACTURA"]}, - {"canonical": "LINEA", "aliases": ["RENGLON", "PARTIDA"]}, - {"canonical": "NUMPARTE", "aliases": ["NUMERO PARTE"]}, - {"canonical": "PRECIO UNITARIO", "aliases": ["PRECIOUNITARIO"]}, - {"canonical": "VALOR COMERCIAL", "aliases": ["VALORCOMERCIAL"]}, - {"canonical": "CANTIDAD"}, - {"canonical": "CANTIDAD BULTOS", "aliases": ["CANTIDADBULTOS"]}, - {"canonical": "DESCRIPCION"}, - {"canonical": "PAIS ORIGEN", "aliases": ["PAISORIGEN"]}, - {"canonical": "FRACCION"}, - {"canonical": "ORDEN DE COMPRA", "aliases": ["ORDENCOMPRA"]}, - ], - # Partidas exportación definitiva (Clarion EstructuraParExpoCamReg: A–V) - "exp_def_partidas": [ - {"canonical": "NUMERO FACTURA EXPO", "aliases": ["NUMERO FACTURA EXPO.", "NUM FACTURA EXPO", "FACTURA EXPO"]}, - {"canonical": "LINEA EXPO", "aliases": ["LINEA EXPO.", "RENGLON EXPO"]}, - {"canonical": "TIPO DE IMPO", "aliases": ["TIPO DE IMPO.", "TIPO IMPO", "PROCEDENCIA"]}, - {"canonical": "FACTURA IMPO", "aliases": ["FACTURA IMPO.", "FACTURA IMPORTACION"]}, - {"canonical": "LINEA IMPO", "aliases": ["LINEA IMPO.", "LINEA IMPORTACION"]}, - {"canonical": "GENERA DESCARGA", "aliases": ["GENERA DESCARGA?", "DESCARGA"]}, - {"canonical": "CANTIDAD EXPORTADA/DESCARGAR", "aliases": ["CANTIDAD EXPORTADA", "CANTIDAD EXPORTADA/DESCARGAR", "CANTIDAD"]}, - {"canonical": "UNIDAD DE MEDIDA", "aliases": ["U.M.", "UNIDAD MEDIDA"]}, - {"canonical": "COSTO UNITARIO", "aliases": ["COSTOUNITARIO"]}, - {"canonical": "PESO NETO", "aliases": ["PESONETO"]}, - {"canonical": "PESO BRUTO", "aliases": ["PESOBRUTO"]}, - {"canonical": "SE PAGO IMPUESTO", "aliases": ["SE PAGO IMPUESTO? (SI o NO)", "SEPAGOIMPUESTO"]}, - {"canonical": "FORMA DE PAGO", "aliases": ["FORMADEPAGO", "FORMA PAGO"]}, - {"canonical": "DESCRIPCION EXTRA", "aliases": ["DESCRIPCION EXTRA", "DESCRIPCIONEXTRA"]}, - {"canonical": "INFORMACION ADICIONAL", "aliases": ["INFORMACION ADICIONAL", "INFORMACIONADICIONAL"]}, - {"canonical": "AGREGAR(A)/SUSTITUIR(S)", "aliases": ["AGREGAR(A)/SUSTITUIR(S)", "AGREGAR/SUSTITUIR", "SUSTITUIR"]}, - {"canonical": "LOTE"}, - {"canonical": "NUMERO ENTRADA", "aliases": ["NUMERO ENTRADA", "NUM ENTRADA"]}, - {"canonical": "ES PARTIDA/SUBPARTIDA", "aliases": ["ES PARTIDA/SUBPARTIDA", "ESSUBPARTIDA", "ES PARTIDA O SUBPARTIDA"]}, - {"canonical": "LINEA PRINCIPAL", "aliases": ["LINEAPRINCIPAL", "PARTIDA PRINCIPAL"]}, - {"canonical": "FRACCION AMERICANA", "aliases": ["FRACCION AMERICANA", "FRACCIONAMERICANA"]}, - {"canonical": "FRACCION ARANCELARIA", "aliases": ["FRACCION ARANCELARIA", "FRACCIONARANCELARIA"]}, - {"canonical": "CANTIDAD BULTOS", "aliases": ["CANTIDADBULTOS"]}, - {"canonical": "CLAVE BULTOS", "aliases": ["CLAVEBULTOS"]}, - {"canonical": "PAIS ORIGEN", "aliases": ["PAISORIGEN", "PAIS"]}, - {"canonical": "NUM. PARTE", "aliases": ["NUMPARTE", "NUMERO PARTE", "NUM PARTE"]}, - {"canonical": "ORDEN DE COMPRA", "aliases": ["ORDENCOMPRA", "ORDEN DE VENTA"]}, - ], - # Series de exportación definitiva (misma estructura que imp_def_series; Clarion SERIES EXPO) - "exp_def_series": [ - {"canonical": "NUMERO FACTURA", "aliases": ["NUM FACTURA", "FACTURA", "FACTURA EXPO"]}, - {"canonical": "LINEA FACTURA", "aliases": ["LINEA", "PARTIDA"]}, - {"canonical": "LINEA SERIE", "aliases": ["RENGLON", "LINEA SER"]}, - {"canonical": "SERIE", "aliases": ["NUMERO SERIE"]}, - {"canonical": "MODELO"}, - {"canonical": "NUM PARTE", "aliases": ["NUMPARTE", "NUMERO PARTE", "NUM PART"]}, - {"canonical": "SUB MODELO", "aliases": ["SUBMODELO", "SUB MODE"]}, - {"canonical": "NUMERO ID", "aliases": ["NUMEROID"]}, - {"canonical": "COL_EXTRA"}, - ], -} - -inject_en_labels_into_template_columns(TEMPLATE_COLUMNS) - -apply_invoice_csv_hints(TEMPLATE_COLUMNS) - - -def _resolve_template_columns(template_id: str) -> Optional[List[Dict[str, Any]]]: - return TEMPLATE_COLUMNS.get(template_id) - - -def build_normalized_lookup(template_id: str, normalize_header_fn) -> Dict[str, str]: - """normalized_header -> canonical_name.""" - cols = _resolve_template_columns(template_id) - if not cols: - return {} - lookup: Dict[str, str] = {} - for item in cols: - canonical = item["canonical"] - lookup[normalize_header_fn(canonical)] = canonical - for alias in item.get("aliases") or []: - lookup[normalize_header_fn(alias)] = canonical - merge_labels_into_lookup(item, canonical, normalize_header_fn, lookup) - merge_download_header_into_lookup(item, canonical, normalize_header_fn, lookup) - return lookup - - -def row_from_template(row: Dict[str, Any], template_id: str, normalize_header_fn) -> Dict[str, Any]: - """Fila CSV -> dict con nombres canónicos de la plantilla.""" - lookup = build_normalized_lookup(template_id, normalize_header_fn) - if not lookup: - return {normalize_header_fn(k): cell_to_str(v) for k, v in row.items()} - out: Dict[str, Any] = {} - for csv_header, value in row.items(): - key_norm = normalize_header_fn(csv_header) - if key_norm in lookup: - out[lookup[key_norm]] = cell_to_str(value) - return out diff --git a/backend/api/v1/modules/a76/layouts_csv/facturas/__init__.py b/backend/api/v1/modules/a76/layouts_csv/facturas/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/backend/api/v1/modules/a76/layouts_csv/facturas/invoice_csv_column_hints.py b/backend/api/v1/modules/a76/layouts_csv/facturas/invoice_csv_column_hints.py deleted file mode 100644 index e22b5574..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/facturas/invoice_csv_column_hints.py +++ /dev/null @@ -1,116 +0,0 @@ -""" -Hints de cabecera para plantillas de factura (longitudes conservadoras + enums ≤3). - -Valores orientativos respecto a validación en tasks.py y modelos de factura; afinar si cambian ORM. -""" -from __future__ import annotations - -from typing import Any, Dict, List, Optional - -from ..common.csv_hint_inject import inject_max_char_hints_into_columns, merge_enum_hint_before_max - -# Unión de canónicos frecuentes en encabezados y partidas (multiplantilla) -INVOICE_MAX_CHARS_BY_CANONICAL: Dict[str, int] = { - "NUMERO FACTURA": 100, - "FECHA FACTURA": 30, - "FECHA EMISION": 30, - "TIPO DE CAMBIO": 24, - "REGIMEN": 10, - "PEDIMENTO": 30, - "REMESA": 12, - "CLAVE PROVEEDOR": 40, - "CLAVE VENDIDO A": 40, - "CLAVE ENVIADO A": 40, - "AGENTE ADUANAL": 20, - "CLAVE TRANSPORTISTA": 30, - "NOMBRE CONDUCTOR": 80, - "TIPO TRANSPORTE": 10, - "CLAVE TRANSPORTE": 30, - "NUMERO CAJA": 20, - "NUMERO TRANSPORTE": 30, - "TIPO MONEDA": 10, - "CLAVE MONEDA": 10, - "FLETES": 24, - "VALOR SEGUROS": 24, - "SEGUROS": 24, - "EMBALAJES": 24, - "OTROS INCREMENTABLES": 24, - "CLAVE INCOTERM": 10, - "PRECINTO": 49, - "TIPO PESO": 10, - "E DOCUMENT": 5, - "NUM OPERACION": 30, - "ADUANA DE CRUCE": 80, - "OBSERVACIONES E": 500, - "OBSERVACIONES I": 500, - "FACTURA ALTERNA": 99, - "NUM PROYECTO": 14, - "ORDEN COMPRA": 50, - "FACTURA EXPO REF": 19, - "MANIFIESTO": 50, - "ENVIADO POR": 80, - "LINEA": 10, - "CLASE": 20, - "CANTIDAD IMPORTADA": 24, - "UNIDAD DE MEDIDA": 10, - "COSTO UNITARIO": 24, - "PESO NETO": 24, - "PESO BRUTO": 24, - "CANTIDAD BULTOS": 24, - "CLAVE BULTOS": 20, - "PAIS ORIGEN": 3, - "FRACCION ARANCELARIA": 20, - "PREFERENCIA ARANCELARIA": 10, - "SECTOR": 20, - "FRACCION AMERICANA": 20, - "ORDEN DE COMPRA": 50, - "DESCRIPCION ESPAÑOL": 256, - "DESCRIPCION INGLES": 256, - "MARCA": 40, - "MODELO": 40, - "NUM. PARTE": 70, - "SE PAGO IMPUESTO": 10, - "FORMA DE PAGO": 30, - "METODO DE VALORACION": 30, - "DESCRIPCION EXTRA": 256, - "INFORMACION ADICIONAL": 500, - "AGREGAR/SUSTITUIR": 10, - "TOTAL": 24, - "NUMERO ENTRADA": 30, - "LOTE": 40, - "ID TYPE": 20, - "NUMERO FACTURA EXPO": 100, - "LINEA EXPO": 10, - "TIPO DE IMPO": 10, - "FACTURA IMPO": 100, - "LINEA IMPO": 10, - "GENERA DESCARGA": 5, - "CANTIDAD EXPORTADA/DESCARGAR": 24, - "AGREGAR(A)/SUSTITUIR(S)": 12, - "ES PARTIDA O SUBPARTIDA": 10, - "ES PARTIDA/SUBPARTIDA": 10, - "LINEA FACTURA": 10, - "LINEA SERIE": 10, - "SERIE": 40, - "NUM PARTE": 70, - "SUB MODELO": 40, - "NUMERO ID": 40, -} - - -def apply_invoice_csv_hints(template_columns: Dict[str, Optional[List[Dict[str, Any]]]]) -> None: - """Inyecta max_chars y enums en todas las listas de columnas no nulas.""" - for _tid, cols in template_columns.items(): - if not cols: - continue - inject_max_char_hints_into_columns(cols, INVOICE_MAX_CHARS_BY_CANONICAL) - for item in cols: - c = item.get("canonical") - if c == "E DOCUMENT": - merge_enum_hint_before_max(item, ["S", "N"]) - elif c in ("SE PAGO IMPUESTO",): - merge_enum_hint_before_max(item, ["SI", "NO"]) - elif c == "GENERA DESCARGA": - merge_enum_hint_before_max(item, ["SI", "NO"]) - elif c == "TIPO DE IMPO": - merge_enum_hint_before_max(item, ["I", "E"]) diff --git a/backend/api/v1/modules/a76/layouts_csv/facturas/line_item_enrichment/__init__.py b/backend/api/v1/modules/a76/layouts_csv/facturas/line_item_enrichment/__init__.py deleted file mode 100644 index 5db6f4e2..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/facturas/line_item_enrichment/__init__.py +++ /dev/null @@ -1,12 +0,0 @@ -""" -Enriquecimiento de partidas (line items) en la carga CSV de facturas. -Aplica las mismas reglas que el proceso manual (items/imports y items/exports validators) -para que los datos insertados por CSV coincidan con la API. -""" -from .import_enrichment import apply_import_defaults_and_calculations_for_csv -from .export_enrichment import apply_export_defaults_and_calculations_for_csv - -__all__ = [ - "apply_import_defaults_and_calculations_for_csv", - "apply_export_defaults_and_calculations_for_csv", -] diff --git a/backend/api/v1/modules/a76/layouts_csv/facturas/line_item_enrichment/export_enrichment.py b/backend/api/v1/modules/a76/layouts_csv/facturas/line_item_enrichment/export_enrichment.py deleted file mode 100644 index 354c7bf6..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/facturas/line_item_enrichment/export_enrichment.py +++ /dev/null @@ -1,104 +0,0 @@ -""" -Enriquecimiento de partidas de exportación cargadas por CSV. -Aplica las mismas reglas que items/exports/validators (create + calculations): -hereda de línea de importación y calcula valores por currency; defaults de export. -""" -from sqlalchemy.orm import Session, joinedload - -from api.v1.modules.a76.invoices.models import InvoiceHeader -from api.v1.modules.a76.classes.models import Class -from api.v1.modules.a76.parts.models import Part -from api.v1.modules.a24.fa.fa_item_lines.dto import FaLineItemCreateDTO - -from api.v1.modules.a76.items.schemas import LineItemCreate -from api.v1.modules.a76.items.exports.validators.calculations import ( - calculate_values, - apply_calculations, -) - - -def _ensure_nested(line_data: LineItemCreate) -> None: - """Asegura que existan los objetos anidados para calculate_values y apply_calculations.""" - from api.v1.modules.a76.items.line_financials.schemas import LineFinancialCreate - from api.v1.modules.a76.items.line_quantities.schemas import LineQuantityCreate - from api.v1.modules.a76.items.line_customs.schemas import LineCustomCreate - from api.v1.modules.a76.items.line_descriptions.schemas import LineDescriptionCreate - - if line_data.financial is None: - line_data.financial = LineFinancialCreate() - if line_data.quantity is None: - line_data.quantity = LineQuantityCreate() - if line_data.customs is None: - line_data.customs = LineCustomCreate() - if line_data.description is None: - line_data.description = LineDescriptionCreate() - if line_data.fa_data is None: - line_data.fa_data = FaLineItemCreateDTO(is_subitem=False, contains_subitems=False) - - -def apply_export_defaults_and_calculations_for_csv( - db: Session, - line_data: LineItemCreate, - tenant_id: int, - company_id: int, - line_number: int, -) -> bool: - """ - Aplica defaults y cálculos de partida de exportación según reglas del proceso manual - (copia desde línea de importación, currency, defaults has_fda_code, tax_payment, etc.). - Se invoca tras armar LineItemCreate desde el CSV. - Devuelve False si faltan factura/financials; True en caso contrario. - """ - _ensure_nested(line_data) - - invoice: InvoiceHeader = ( - db.query(InvoiceHeader) - .options(joinedload(InvoiceHeader.financials)) - .filter( - InvoiceHeader.id == line_data.invoice_id, - InvoiceHeader.tenant_id == tenant_id, - InvoiceHeader.company_id == company_id, - ) - .first() - ) - if not invoice or not invoice.financials: - return False - - part: Part | None = None - if line_data.part_number_id: - part = ( - db.query(Part) - .filter( - Part.id == line_data.part_number_id, - Part.tenant_id == tenant_id, - Part.company_id == company_id, - ) - .first() - ) - if part and line_data.description: - # Fallback explícito: priorizar descripciones de parte para paridad manual. - if not line_data.description.description_spanish and part.description_spanish: - line_data.description.description_spanish = part.description_spanish - if not line_data.description.description_english and part.description_english: - line_data.description.description_english = part.description_english - - if not line_data.unit_of_measure and line_data.class_id: - class_info = ( - db.query(Class) - .filter( - Class.id == line_data.class_id, - Class.tenant_id == tenant_id, - Class.company_id == company_id, - ) - .first() - ) - if class_info: - line_data.unit_of_measure = class_info.unit_of_measure - - # Copia desde línea de importación y cálculos por currency (reglas manual) - calculate_values(db, line_data, tenant_id, company_id) - - # Defaults: has_fda_code, tax_payment, payment_method, depreciation_date, descripciones - apply_calculations(db, line_data, tenant_id, company_id, line_number) - - return True diff --git a/backend/api/v1/modules/a76/layouts_csv/facturas/line_item_enrichment/import_enrichment.py b/backend/api/v1/modules/a76/layouts_csv/facturas/line_item_enrichment/import_enrichment.py deleted file mode 100644 index 9ce52568..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/facturas/line_item_enrichment/import_enrichment.py +++ /dev/null @@ -1,247 +0,0 @@ -""" -Enriquecimiento de partidas de importación cargadas por CSV. -Aplica las mismas reglas que items/imports/validators (create + calculations) -en base a currency_type y peso de la factura; no re-sobrescribe con currency -para evitar discrepancias con el proceso manual. -""" -from decimal import Decimal -from sqlalchemy.orm import Session, joinedload - -from api.v1.modules.a76.invoices.models import InvoiceHeader -from api.v1.modules.a76.classes.models import Class -from api.v1.modules.a76.parts.models import Part -from api.v1.modules.a76.general_catalogs.packages.models import Package -from api.v1.modules.sitar.fracciones_usa.catalog_resolve import ( - american_fraction_ad_valorem_from_row, - resolve_american_fraction_from_sitar, - store_canonical_american_code, -) -from api.v1.modules.a24.fa.fa_item_lines.dto import FaLineItemCreateDTO - -from api.v1.modules.a76.items.schemas import LineItemCreate -from api.v1.modules.a76.items.imports.validators.calculations import ( - apply_calculations_after_values, -) - - -def _normalize_weight_type(invoice) -> str: - """Obtiene weight_type de la factura como 'KGS' o 'LBS' (paridad con proceso manual).""" - wt = getattr(invoice.logistics, "weight_type", None) or "KGS" - if hasattr(wt, "value"): - wt = wt.value - weight_str = str(wt).upper() if wt else "KGS" - return weight_str if weight_str in ("KGS", "LBS") else "KGS" - - -def apply_import_defaults_and_calculations_for_csv( - db: Session, - line_data: LineItemCreate, - tenant_id: int, - company_id: int, - line_number: int, -) -> bool: - """ - Aplica defaults y cálculos de partida de importación según reglas del proceso manual - (currency_type, peso, descripciones). Se invoca tras armar LineItemCreate desde el CSV. - Devuelve False si faltan factura/financials/logistics; True en caso contrario. - """ - invoice: InvoiceHeader = ( - db.query(InvoiceHeader) - .options( - joinedload(InvoiceHeader.financials), - joinedload(InvoiceHeader.logistics), - ) - .filter( - InvoiceHeader.id == line_data.invoice_id, - InvoiceHeader.tenant_id == tenant_id, - InvoiceHeader.company_id == company_id, - ) - .first() - ) - if not invoice or not invoice.financials or not invoice.logistics: - return False - - class_info: Class | None = None - if line_data.class_id: - class_info = ( - db.query(Class) - .filter( - Class.id == line_data.class_id, - Class.tenant_id == tenant_id, - Class.company_id == company_id, - ) - .first() - ) - - part: Part | None = None - if line_data.part_number_id: - part = ( - db.query(Part) - .filter( - Part.id == line_data.part_number_id, - Part.tenant_id == tenant_id, - Part.company_id == company_id, - ) - .first() - ) - - if not line_data.fa_data: - line_data.fa_data = FaLineItemCreateDTO( - is_subitem=False, - contains_subitems=False, - ) - - exchange_rate = invoice.financials.exchange_rate or Decimal("1.0") - if not line_data.unit_of_measure and class_info: - line_data.unit_of_measure = class_info.unit_of_measure - - # Reglas de moneda igual que proceso manual (create.py): currency_type - currency_type = getattr(invoice.financials, "currency_type", None) or "USD" - unit_cost_capture = line_data.financial.unit_cost_capture or Decimal("0") - - if currency_type in ("USD", "ME"): - line_data.financial.unit_cost_capture = unit_cost_capture - line_data.financial.unit_cost_usd = unit_cost_capture - line_data.financial.unit_cost_mxn = unit_cost_capture * exchange_rate - elif currency_type in ("MXN", "MN"): - line_data.financial.unit_cost_capture = unit_cost_capture - line_data.financial.unit_cost_usd = ( - unit_cost_capture / exchange_rate if exchange_rate else Decimal("0") - ) - line_data.financial.unit_cost_mxn = unit_cost_capture - else: - line_data.financial.unit_cost_capture = unit_cost_capture - line_data.financial.unit_cost_usd = unit_cost_capture - line_data.financial.unit_cost_mxn = unit_cost_capture * exchange_rate - - quantity = line_data.quantity.quantity or Decimal("0") - if line_data.financial.unit_cost_usd is not None: - line_data.financial.value_usd = line_data.financial.unit_cost_usd * quantity - if line_data.financial.unit_cost_mxn is not None: - line_data.financial.value_mxn = line_data.financial.unit_cost_mxn * quantity - line_data.financial.customs_value_usd = line_data.financial.value_usd - line_data.financial.customs_value_mxn = line_data.financial.value_mxn - line_data.financial.value_temp_material_usd = line_data.financial.value_usd - line_data.financial.value_temp_material_mxn = line_data.financial.value_mxn - - # value_mc según currency de la factura (paridad con calculate_values manual) - currency = getattr(invoice.financials, "currency", None) - if currency == "foreign": - line_data.financial.value_mc = line_data.financial.value_usd - elif currency == "local": - line_data.financial.value_mc = line_data.financial.value_usd - elif currency == "manual": - line_data.financial.value_mc = unit_cost_capture * quantity - else: - line_data.financial.value_mc = line_data.financial.value_usd - - # Peso: misma lógica que proceso manual (create.py) con weight_type normalizado - weight_str = _normalize_weight_type(invoice) - quantity = line_data.quantity.quantity or Decimal("0") - net_weight_input = line_data.quantity.net_weight or Decimal("0") - uom = line_data.unit_of_measure - unit_is_kgs = uom is not None and (str(uom) == "24" or uom == 24) - unit_is_lbs = uom is not None and (str(uom) == "25" or uom == 25) - - if unit_is_kgs: - if weight_str == "KGS": - line_data.quantity.net_weight = quantity - else: - line_data.quantity.net_weight = quantity * Decimal("2.204624") - elif unit_is_lbs: - if weight_str == "KGS": - line_data.quantity.net_weight = quantity / Decimal("2.204624") - else: - line_data.quantity.net_weight = quantity - else: - if weight_str == "KGS": - line_data.quantity.net_weight = net_weight_input - else: - line_data.quantity.net_weight = net_weight_input / Decimal("2.204624") - - gross_weight_input = line_data.quantity.gross_weight - package_quantity = line_data.quantity.package_quantity or 0 - package_weight_unit = Decimal("0") - - if line_data.quantity.package_id: - package = ( - db.query(Package) - .filter( - Package.id == line_data.quantity.package_id, - Package.tenant_id == tenant_id, - Package.company_id == company_id, - ) - .first() - ) - if package and package.weight_unit: - package_weight_unit = package.weight_unit - - if not gross_weight_input or gross_weight_input == 0: - if weight_str == "KGS": - line_data.quantity.gross_weight = line_data.quantity.net_weight + ( - package_weight_unit * package_quantity - ) - else: - line_data.quantity.gross_weight = line_data.quantity.net_weight + ( - (package_weight_unit * Decimal("2.204624")) * package_quantity - ) - else: - if weight_str == "KGS": - line_data.quantity.gross_weight = gross_weight_input - else: - line_data.quantity.gross_weight = gross_weight_input / Decimal("2.204624") - - if line_data.quantity.gross_weight < line_data.quantity.net_weight: - line_data.quantity.gross_weight = line_data.quantity.net_weight + ( - package_weight_unit * package_quantity - ) - - if package_quantity and package_quantity > 0 and line_data.quantity.package_id: - package = ( - db.query(Package) - .filter( - Package.id == line_data.quantity.package_id, - Package.tenant_id == tenant_id, - Package.company_id == company_id, - ) - .first() - ) - if package: - line_data.description.package_description = package.description_es - else: - line_data.quantity.package_quantity = 0 - line_data.quantity.package_id = None - line_data.description.package_description = None - - if not line_data.customs.american_fraction and class_info and class_info.us_fraction: - line_data.customs.american_fraction = class_info.us_fraction - - if line_data.customs.american_fraction: - resolved = resolve_american_fraction_from_sitar( - str(line_data.customs.american_fraction) - ) - if resolved: - sitar_row, canon = resolved - line_data.customs.american_fraction = store_canonical_american_code(canon) - line_data.customs.advalorem_american = american_fraction_ad_valorem_from_row( - sitar_row - ) - - # Fallback explícito: primero descripción de parte (si existe), luego clase. - if not line_data.description.description_spanish and part and part.description_spanish: - line_data.description.description_spanish = part.description_spanish - if not line_data.description.description_english and part and part.description_english: - line_data.description.description_english = part.description_english - if not line_data.description.description_spanish and class_info: - line_data.description.description_spanish = class_info.description_es - if not line_data.description.description_english and class_info: - line_data.description.description_english = class_info.description_en - - if line_data.description.brand: - line_data.description.brand = line_data.description.brand.upper().strip() - if line_data.description.model: - line_data.description.model = line_data.description.model.upper().strip() - - # Solo depreciation_date, descripción part/class y subitem_number (sin re-calcular moneda) - apply_calculations_after_values(db, line_data, tenant_id, company_id, line_number) - return True diff --git a/backend/api/v1/modules/a76/layouts_csv/facturas/routes.py b/backend/api/v1/modules/a76/layouts_csv/facturas/routes.py deleted file mode 100644 index d9dafb55..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/facturas/routes.py +++ /dev/null @@ -1,253 +0,0 @@ -from datetime import datetime -from uuid import uuid4 -import os -import logging -from fastapi import APIRouter, UploadFile, File, Form, HTTPException, Depends, Query -from sqlalchemy.orm import Session -from typing import Optional, Literal, Dict, Any - -from core.celery_app import celery_app -from core.config import settings -from core.database import get_core_db -from core.security import get_current_user, validate_access_to_resource -from api.v1.modules.core.tasks_tracking import track_and_dispatch - -from .tasks import ( - scan_file, - insert_valid_rows, - JOB_TYPE, - IMPORT_META_KEY_PREFIX, - IMPORT_REDIS_TTL, -) -from .schemas import ImportJobResponse, ImportJobStatus, CommitRequest -from ..common import storage as common_storage -from ..common.error_csv import download_scan_errors_csv_stream -from ..common.responses import normalize_commit_status_payload -from ..common.track_commit_dispatch import dispatch_tracked_layouts_csv_commit - -router = APIRouter() -logger = logging.getLogger(__name__) - - -def _get_redis(): - """Redis client (same broker as Celery so worker can read).""" - import redis - url = os.getenv("VALKEY_URL", os.getenv("REDIS_URL", "redis://valkey:6379/0")) - return redis.Redis.from_url(url, decode_responses=False) - -@router.post("/upload/{model_target}", response_model=ImportJobResponse) -async def upload_import_file( - model_target: Literal["invoice_header", "invoice_details", "invoice_series"], - file: UploadFile = File(...), - footer_config: Optional[str] = Form(None), # JSON string with settings - template_id: Optional[str] = Form(None), # id de la plantilla (ej. imp_temp_header) para respetar columnas - company_id: int = Query(..., description="Company ID"), # Required for context - operation_type: Optional[str] = Query("imp"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """ - Step 1: Upload CSV, save to temp, trigger scan task. - Si se envía template_id, solo se leen las columnas de esa plantilla. - """ - header_company_id = None - try: - if db is not None and getattr(db, "info", None): - header_company_id = db.info.get("rls_company_id") - except Exception: - header_company_id = None - if header_company_id is not None and int(header_company_id) != int(company_id): - logger.warning( - "Facturas upload company mismatch request_company_id=%s db_rls_company_id=%s", - company_id, - header_company_id, - ) - - # 1. Validate Access & Get Tenant - try: - tenant_id = validate_access_to_resource(db, company_id, current_user, ["csv_upload.process"]) - except Exception as e: - logger.error(f"Access validation failed: {e}") - raise HTTPException(status_code=403, detail="Invalid company access") - - if not file.filename.endswith(".csv"): - raise HTTPException(status_code=400, detail="Only .csv files allowed") - - job_id = str(uuid4()) - contents = await file.read() - - meta_data = { - "tenant_id": tenant_id, - "company_id": company_id, - "user_id": current_user.get("id"), - "capture_user": ( - current_user.get("preferred_username") - or current_user.get("email") - or current_user.get("sub") - or "CSV" - ), - "footer_config": footer_config, - "operation_type": operation_type, - "template_id": template_id, - } - - try: - common_storage.store_import_file( - JOB_TYPE, - job_id, - contents, - meta_data, - tenant_id=int(tenant_id), - company_id=company_id, - ttl=IMPORT_REDIS_TTL, - log_label="Facturas import", - ) - except common_storage.ImportStoreError as e: - logger.error(f"Import store error: {e}") - raise HTTPException(status_code=500, detail="Failed to queue file for processing.") - except Exception as e: - logger.error(f"Redis store error: {e}") - raise HTTPException(status_code=500, detail="Failed to queue file for processing.") - - # Trigger Celery Task (Async). Worker loads file from Redis / MinIO. - logger.info( - "Queueing facturas scan job=%s tenant_id=%s company_id=%s template_id=%s operation_type=%s", - job_id, - tenant_id, - company_id, - template_id, - operation_type, - ) - track_and_dispatch( - db=db, - task=scan_file, - tenant_id=int(tenant_id), - company_id=company_id, - requested_by_user=current_user.get("preferred_username") or current_user.get("email") or current_user.get("sub"), - task_name="facturas_scan_file", - task_group="layouts_csv", - task_origin="a76/layouts_csv/facturas/upload", - args=[job_id, model_target, footer_config], - task_id=job_id, - ) - - return ImportJobResponse( - job_id=job_id, - status="queued", - message="File uploaded. Scanning started." - ) - -@router.get("/{job_id}/status") -async def get_import_status(job_id: str): - """ - Poll to get progress or final report. Always returns an object with "status". - """ - task_result = celery_app.AsyncResult(job_id) - - if task_result.state == "PENDING": - return {"status": "processing", "progress": 0} - # task_track_started=True: el worker pasa por STARTED (y a veces RECEIVED) antes de SUCCESS/PROGRESS. - # Sin esto, el polling cae en la rama final y devuelve status=failed aunque el scan vaya bien. - if task_result.state in ("STARTED", "RECEIVED"): - info = task_result.info if isinstance(task_result.info, dict) else {} - return { - "status": "processing", - "progress": info.get("current", 0), - "total": info.get("total", 0), - } - if task_result.state == "PROGRESS": - return { - "status": "processing", - "progress": (task_result.info or {}).get("current", 0), - "total": (task_result.info or {}).get("total", 0), - } - if task_result.state == "SUCCESS": - result = task_result.result - if isinstance(result, dict) and "status" in result: - return normalize_commit_status_payload(result) - return {"status": "finished", "result": result} - - # Recuperación: raro pero posible con backend de resultados — el payload ya está pero el estado no es SUCCESS. - raw = getattr(task_result, "result", None) - if isinstance(raw, dict) and raw.get("status") in ( - "waiting_confirmation", - "finished", - "warning", - "failed", - ): - return normalize_commit_status_payload(raw) - - # FAILURE: obtener mensaje real (traceback, result o get(propagate=False)) - logger.warning("Import task %s failed: state=%s", job_id, task_result.state) - err_msg = None - tb = getattr(task_result, "traceback", None) - if tb: - logger.debug("Task traceback: %s", tb[:500] if isinstance(tb, str) else tb) - if tb and isinstance(tb, str): - lines = [l.strip() for l in tb.strip().split("\n") if l.strip()] - if lines: - err_msg = lines[-1] - if not err_msg and len(lines) > 1: - err_msg = lines[-2] + " " + (lines[-1] or "") - if not err_msg: - try: - exc = task_result.get(propagate=False) - if exc is not None: - err_msg = str(exc) - except Exception: - pass - if not err_msg: - result = getattr(task_result, "result", None) - info = getattr(task_result, "info", None) - if result is not None and not isinstance(result, dict): - err_msg = str(result) - elif isinstance(result, dict) and (result.get("error") or result.get("message")): - err_msg = result.get("error") or result.get("message") - if not err_msg and isinstance(info, str): - err_msg = info - elif not err_msg and isinstance(info, dict) and "error" in info: - err_msg = str(info["error"]) - if not err_msg: - err_msg = "Task failed" - return {"status": "failed", "error": err_msg} - - -@router.get("/{job_id}/errors/scan-csv") -async def download_scan_errors_csv(job_id: str): - """ - Descarga CSV con TODO el detalle de errores del scan (sin límite), - leyendo el archivo JSONL generado por el worker (mismas columnas que otros layouts_csv). - """ - - # Facturas (impo/exp delega a facturas) usan job_type vacío (""). - return download_scan_errors_csv_stream("", job_id, filename_prefix="errores") - - -@router.post("/{job_id}/commit") -async def commit_import_job( - job_id: str, - body: CommitRequest, - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """ - Step 2: User confirms import. Trigger bulk insert. - """ - r = _get_redis() - commit_id = dispatch_tracked_layouts_csv_commit( - db=db, - current_user=current_user, - redis_client=r, - layout_job_id=job_id, - meta_redis_key=f"{IMPORT_META_KEY_PREFIX}{job_id}", - celery_task=insert_valid_rows, - task_name="facturas_insert_valid_rows", - task_origin="a76/layouts_csv/facturas/commit", - args=[job_id, body.model_target], - ) - - return { - "status": "committing", - "message": "Bulk insert started.", - "commit_job_id": commit_id, - } diff --git a/backend/api/v1/modules/a76/layouts_csv/facturas/schemas.py b/backend/api/v1/modules/a76/layouts_csv/facturas/schemas.py deleted file mode 100644 index 4cf4db4d..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/facturas/schemas.py +++ /dev/null @@ -1,20 +0,0 @@ -from pydantic import BaseModel -from typing import Optional, Literal - -class ImportJobResponse(BaseModel): - job_id: str - status: str - message: str - -class CommitRequest(BaseModel): - model_target: Literal["invoice_header", "invoice_details", "invoice_series"] - -class ImportJobStatus(BaseModel): - status: str - job_id: str - total_rows: Optional[int] = 0 - error_count: Optional[int] = 0 - valid_rows: Optional[int] = 0 - error: Optional[str] = None - inserted: Optional[int] = 0 - error_file: Optional[str] = None diff --git a/backend/api/v1/modules/a76/layouts_csv/facturas/tasks.py b/backend/api/v1/modules/a76/layouts_csv/facturas/tasks.py deleted file mode 100644 index 15244b51..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/facturas/tasks.py +++ /dev/null @@ -1,6665 +0,0 @@ -""" -Tareas Celery para importación CSV de facturas (encabezados, partidas, series). -Flujo: scan_file (validación) → insert_valid_rows (commit). - -Objetivo en BD (paridad con flujo normal): al terminar el commit, los datos deben quedar -igual que por UI/API: encabezados con capture_user/who_processed; partidas con costos, -pesos y descripciones calculados/heredados según items/imports/validators; series con -campos no presentes en CSV en null. No se modifican plantillas CSV; no se inventan -datos sin fuente (p. ej. LineReference solo si hay fuente explícita). -""" -import os -from datetime import datetime, timedelta -from decimal import Decimal -import csv -import json -import logging -import re -import unicodedata -from typing import Dict, Any, Optional, List, Set, Tuple - -from core.celery_app import celery_app -from core.database import scoped_core_db, rls_tenant_var, rls_company_var -from core.paths import layout_path -from sqlalchemy import func -from sqlalchemy.orm import Session - -from ..common import storage as common_storage -from ..common import meta as common_meta -from ..common import responses as common_responses -from ..common import csv_reader as common_csv_reader -from .template_config import row_from_template -from .validators.encabezados_impo_temp import csv_tipo_moneda_es_me_mn_mc -from .validators.pedimento_resolution import ( - normalize_catalog_pedimento_identity as _normalize_catalog_pedimento_identity, -) -from api.v1.modules.a76.app_settings.service import AppSettingsService -# Models are imported inside tasks to avoid circular dependencies and mapper initialization issues in the API process - -logger = logging.getLogger(__name__) - - -def _merge_unique_invoice_scan_error_lines( - precheck_lines: Set[int], - row_validation_lines: List[int], -) -> List[int]: - """Unión deduplicada: líneas marcadas en precheck + líneas con error en validación por fila (imp_temp_header).""" - return sorted(set(precheck_lines) | set(row_validation_lines)) - - -def _invoice_scan_error_row_payload(e: Dict[str, Any]) -> Dict[str, Any]: - """ - Unifica campos para archivo JSONL de errores y para errors_detail en scan de facturas. - Incluye code / not_found_reason cuando el validador los provee (paridad descarga CSV ↔ API). - """ - row: Dict[str, Any] = { - "line": e["line"], - "col": e.get("col", ""), - "msg": e.get("msg", ""), - "solution": e.get("solution", ""), - "warning": bool(e.get("warning", False)), - } - if e.get("not_found_reason"): - row["not_found_reason"] = e["not_found_reason"] - if e.get("code"): - row["code"] = e["code"] - return row - - -# Job type vacío para facturas (prefijo Redis "import_" sin tipo, ver common/storage.py) -JOB_TYPE = "" - -# Redis keys and TTL for import file/meta (exportados para routes; coinciden con common_storage cuando job_type="") -IMPORT_FILE_KEY_PREFIX = "import_file:" -IMPORT_META_KEY_PREFIX = "import_meta:" -IMPORT_ERROR_LINES_KEY_PREFIX = "import_error_lines:" -IMPORT_REDIS_TTL = common_storage.IMPORT_REDIS_TTL - - -def _ensure_worker_has_file_from_redis(job_id: str) -> Optional[str]: - """Usa common storage con job_type vacío (prefijo import_).""" - return common_storage.ensure_file_from_redis(JOB_TYPE, job_id, "Invoices import") - - -def _ensure_worker_has_meta_from_redis(job_id: str, file_path: str) -> bool: - return common_storage.ensure_meta_from_redis(JOB_TYPE, job_id, file_path, "Invoices import") - - -def _delete_import_from_redis(job_id: str) -> None: - common_storage.delete_import_from_redis(JOB_TYPE, job_id) - - -def _ensure_utf8_compatible_import_file(file_path: str) -> None: - """ - Homogeneiza a UTF-8-SIG cuando el archivo venga en otro encoding - para que todo el flujo legado de facturas lea exactamente lo mismo. - """ - encoding = common_csv_reader.detect_text_encoding(file_path) - if encoding in ("utf-8", "utf-8-sig"): - return - with open(file_path, "r", encoding=encoding) as src: - content = src.read() - with open(file_path, "w", encoding="utf-8-sig") as dst: - dst.write(content) - - -def _facturas_csv_encoding(file_path: str) -> str: - """ - Encapsula la detección para centralizar la política de lectura CSV en facturas. - """ - return common_csv_reader.detect_text_encoding(file_path) - -class ForeignKeyValidator: - def __init__(self, session, tenant_id, company_id): - self.session = session - self.tenant_id = tenant_id - self.company_id = company_id - self.cache = {} # {(model_name, field_name, is_public, value): bool} - - def check_exists(self, model, value, field_name="id", is_public=False): - if value is None: - return True # Assume optional if None, or let DB handle not-null - - key = (model.__name__, field_name, is_public, value) - if key in self.cache: - return self.cache[key] - - col = getattr(model, field_name) - if field_name == "short_name" and hasattr(model, "short_name"): - query = self.session.query(col).filter(func.upper(col) == (value.upper() if isinstance(value, str) else value)) - else: - query = self.session.query(col).filter(col == value) - if not is_public: - query = query.filter(model.tenant_id == self.tenant_id, model.company_id == self.company_id) - - exists = query.first() is not None - self.cache[key] = exists - return exists - - -TRANSPORT_TYPE_VALUES = { - "none", - "transport", - "box", - "licence plates", - "truck", - "vessel", - "rail_barge", - "container", - "airplane", - "gondola", - "flatbed", -} - - -def normalize_public_code(value: Optional[str]) -> Optional[str]: - if value is None: - return None - text = str(value).strip().upper() - return text or None - - -def validate_public_code( - validator: ForeignKeyValidator, - model, - value: Optional[str], - line_num: int, - col_name: str, - field_name: str = "code", - required: bool = False, -) -> Optional[Dict[str, Any]]: - code = normalize_public_code(value) - if not code: - if required: - return { - "line": line_num, - "col": col_name, - "msg": "Requerido", - "solution": f"Capturar el dato requerido en la columna {col_name}.", - } - return None - if not validator.check_exists(model, code, field_name=field_name, is_public=True): - return { - "line": line_num, - "col": col_name, - "msg": "No existe en el catalogo", - "solution": f"Capturar en la columna {col_name} un valor que exista en el catálogo.", - } - return None - - -def validate_tenant_fk_id( - validator: ForeignKeyValidator, - model, - value: Optional[int], - line_num: int, - col_name: str, - required: bool = False, -) -> Optional[Dict[str, Any]]: - if value is None: - if required: - return { - "line": line_num, - "col": col_name, - "msg": "Requerido", - "solution": f"Capturar el dato requerido en la columna {col_name}.", - } - return None - if not validator.check_exists(model, value): - return { - "line": line_num, - "col": col_name, - "msg": "No existe en el catalogo", - "solution": f"Capturar en la columna {col_name} un valor que exista en el catálogo.", - } - return None - - -def _validate_client_provider_ref( - validator: ForeignKeyValidator, - model, - raw_value: Any, - line_num: int, - col_name: str, - required: bool, -) -> Optional[Dict[str, Any]]: - """Valida CLAVE PROVEEDOR / VENDIDO A / ENVIADO A: acepta ID (entero) o short_name (texto).""" - if raw_value is None or not str(raw_value).strip(): - if required: - return { - "line": line_num, - "col": col_name, - "msg": "Requerido", - "solution": f"Capturar el dato requerido en la columna {col_name}.", - } - return None - pid = parse_int(raw_value) - if pid is not None: - return validate_tenant_fk_id(validator, model, pid, line_num, col_name, required=False) - short_norm = str(raw_value).strip().upper() - if not validator.check_exists(model, short_norm, field_name="short_name"): - return { - "line": line_num, - "col": col_name, - "msg": "No existe en el catalogo", - "solution": f"Capturar en la columna {col_name} un valor que exista en el catálogo.", - } - return None - - -def _validate_customs_broker_ref( - validator: ForeignKeyValidator, - model, - raw_value: Any, - line_num: int, - col_name: str, - required: bool = False, - broker_lookup: Optional[Dict[str, Any]] = None, -) -> Optional[Dict[str, Any]]: - """Valida AGENTE ADUANAL: acepta ID (entero) o clave broker_key (texto).""" - if raw_value is None or not str(raw_value).strip(): - if required: - return { - "line": line_num, - "col": col_name, - "msg": "Requerido", - "solution": f"Capturar el dato requerido en la columna {col_name}.", - } - return None - if broker_lookup is not None: - resolved_id, _mode, _normalized, _err = resolve_customs_broker_ref(raw_value, broker_lookup) - if resolved_id is None: - return { - "line": line_num, - "col": col_name, - "msg": "No existe en el catalogo", - "solution": f"Capturar en la columna {col_name} una clave que exista en el catálogo.", - } - return None - pid = parse_int(raw_value) - if pid is not None: - # Si viene numérico primero intentamos como ID. - # Si no existe ese ID, intentamos como broker_key (clave numérica). - err = validate_tenant_fk_id(validator, model, pid, line_num, col_name, required=False) - if err is None: - return None - clave = _normalize_broker_key(raw_value) - if not validator.check_exists(model, clave, field_name="broker_key"): - return { - "line": line_num, - "col": col_name, - "msg": "No existe en el catalogo", - "solution": f"Capturar en la columna {col_name} una clave que exista en el catálogo.", - } - return None - - -def _normalize_broker_key(value: Any) -> str: - return str(value or "").strip().upper() - - -def _build_customs_broker_lookup(rows: List[Tuple[int, Optional[str]]]) -> Dict[str, Any]: - """ - Construye lookup robusto para AGENTE ADUANAL: - - id_set: IDs válidos - - key_to_id: broker_key normalizado -> id - - alias_to_id: alias P#### <-> #### solo cuando es inequívoco - """ - id_set: Set[int] = set() - key_to_id: Dict[str, int] = {} - alias_votes: Dict[str, Set[int]] = {} - - for bid, bkey in rows: - id_set.add(int(bid)) - norm = _normalize_broker_key(bkey) - if not norm: - continue - key_to_id[norm] = int(bid) - if norm.startswith("P") and norm[1:].isdigit(): - alias_votes.setdefault(norm[1:], set()).add(int(bid)) - elif norm.isdigit(): - alias_votes.setdefault(f"P{norm}", set()).add(int(bid)) - - alias_to_id: Dict[str, int] = {} - for alias, ids in alias_votes.items(): - if len(ids) == 1: - alias_to_id[alias] = next(iter(ids)) - - return { - "id_set": id_set, - "key_to_id": key_to_id, - "alias_to_id": alias_to_id, - } - - -def resolve_customs_broker_ref( - raw_value: Any, - broker_lookup: Dict[str, Any], -) -> Tuple[Optional[int], Optional[str], str, Optional[str]]: - """ - Resuelve AGENTE ADUANAL con patrón robusto: - id -> broker_key exacto -> alias P####/####. - """ - text = str(raw_value or "").strip() - if not text: - return None, None, "", None - - id_set: Set[int] = broker_lookup.get("id_set", set()) - key_to_id: Dict[str, int] = broker_lookup.get("key_to_id", {}) - alias_to_id: Dict[str, int] = broker_lookup.get("alias_to_id", {}) - - pid = parse_int(raw_value) - if pid is not None and pid in id_set: - return int(pid), "id", text, None - - norm = _normalize_broker_key(raw_value) - if norm in key_to_id: - return key_to_id[norm], "broker_key", norm, None - if norm in alias_to_id: - return alias_to_id[norm], "broker_key_alias", norm, None - - return None, None, norm, "No existe en el catalogo" - - -def _summarize_top_reasons(details: List[Dict[str, Any]], limit: int = 10) -> List[Dict[str, Any]]: - counts: Dict[str, int] = {} - for detail in details: - reason = str(detail.get("reason", "")).strip() - if not reason: - continue - counts[reason] = counts.get(reason, 0) + 1 - ordered = sorted(counts.items(), key=lambda kv: kv[1], reverse=True) - return [{"reason": reason, "count": count} for reason, count in ordered[:limit]] - - -def _normalize_invoice_number(value: Any) -> str: - return str(value or "").strip().upper() - - -def _extract_invoice_number_from_row(row: Dict[str, Any]) -> str: - """Extrae y normaliza número de factura desde aliases de layouts.""" - return _normalize_invoice_number( - row.get("NUMERO FACTURA") - or row.get("NUM FACTURA") - or row.get("FACTURA") - ) - - -def _resolve_import_context( - model_target: str, - meta: Dict[str, Any], - footer_config: Dict[str, Any], - job_type_override: Optional[str] = None, -) -> Tuple[str, str, str]: - """ - Resuelve template/invoice_type/operation_type efectivos de forma consistente - para scan y commit. - """ - template_id = meta.get("template_id") or ( - "imp_temp_header" if model_target == "invoice_header" else - "imp_temp_details" if model_target == "invoice_details" else "imp_temp_series" - ) - op_type = str(meta.get("operation_type", "imp") or "imp").strip().lower() - inv_type = normalize_public_code( - footer_config.get("invoice_type") or meta.get("invoice_type") or "TEM" - ) or "TEM" - - if job_type_override == "exp" and model_target == "invoice_header": - template_id = "exp_def_header" - if job_type_override == "exp" and model_target == "invoice_details": - template_id = "exp_def_partidas" - if job_type_override == "exp" and model_target == "invoice_series": - template_id = "exp_def_series" - - if model_target == "invoice_details" and op_type == "exp": - template_id = "exp_def_partidas" - if model_target == "invoice_series" and inv_type in ("DEF", "MATDE", "EXDEF"): - template_id = "imp_def_series" - - if model_target == "invoice_header" and template_id == "imp_def_header": - inv_type = "DEF" - if model_target == "invoice_header" and template_id == "cmex_header": - inv_type = "MEX" - if model_target == "invoice_header" and template_id == "exp_def_header": - op_type = "exp" - inv_type = normalize_public_code( - meta.get("tipo_factura") or footer_config.get("tipo_factura") or "AFIJO" - ) or "AFIJO" - - if model_target == "invoice_details" and template_id == "imp_def_details": - inv_type = "DEF" - if model_target == "invoice_details" and template_id == "cmex_details": - inv_type = "MEX" - if model_target == "invoice_details" and template_id == "exp_def_partidas": - op_type = "exp" - inv_type = normalize_public_code( - meta.get("tipo_factura") or footer_config.get("tipo_factura") or "AFIJO" - ) or "AFIJO" - - if template_id == "cmex_series": - inv_type = "MEX" - - return template_id, inv_type, op_type - - -def _is_invoice_processed_status(value: Any) -> bool: - """Mapea status de factura a bandera de procesado real.""" - if isinstance(value, bool): - return value - text = str(value or "").strip().lower() - if not text: - return False - # pending equivale a no procesado. - if text in {"pending", "pendiente", "false", "0", "no"}: - return False - # Solo estados explícitos de procesado/actualización bloquean modificaciones. - return text in {"processed", "procesado", "updated", "actualizada", "actualizado", "true", "1", "yes", "si", "sí"} - - -def _resolve_invoice_id_for_details( - session, - InvoiceHeader, - tenant_id: int, - company_id: int, - invoice_number_raw: Any, - invoice_type: Optional[str], - operation_type: Optional[Any], - cache: Dict[str, Optional[int]], -) -> Optional[int]: - invoice_norm = _normalize_invoice_number(invoice_number_raw) - if not invoice_norm: - return None - op_text = str(operation_type.value if hasattr(operation_type, "value") else operation_type or "").strip().lower() - cache_key = f"{invoice_norm}|{str(invoice_type or '').upper()}|{op_text}" - if cache_key in cache: - return cache[cache_key] - q = ( - session.query(InvoiceHeader.id) - .filter( - InvoiceHeader.tenant_id == tenant_id, - InvoiceHeader.company_id == company_id, - func.upper(InvoiceHeader.invoice_number) == invoice_norm, - ) - ) - if invoice_type: - q = q.filter(InvoiceHeader.invoice_type == invoice_type) - if operation_type: - q = q.filter(InvoiceHeader.operation_type == operation_type) - found = q.scalar() - cache[cache_key] = found - return found - -@celery_app.task(bind=True) -def scan_file(self, job_id: str, model_target: str, config: str = None, job_type_override: Optional[str] = None): - """Pass 1: Read CSV, Validate types, Write Errors to JSONL. Delegates to _do_scan_file.""" - return _do_scan_file(self, job_id, model_target, config, job_type_override) - - -def _do_scan_file(self, job_id: str, model_target: str, config: Optional[str] = None, job_type_override: Optional[str] = None) -> Dict[str, Any]: - """Pass 1 body: load file/meta from storage, run validations, store error lines. Uses effective_job_type for storage.""" - effective_job_type = job_type_override if job_type_override is not None else JOB_TYPE - log_prefix = "Exportación import" if effective_job_type else "Invoices import" - - logger.info(f"Starting scan for job {job_id} target {model_target}") - - # 1. Get file from Redis and write to worker local disk - file_path = common_storage.ensure_file_from_redis(effective_job_type, job_id, log_prefix) - if not file_path: - return {"status": "failed", "error": "File not found (missing or expired in queue). Please upload again."} - common_storage.ensure_meta_from_redis(effective_job_type, job_id, file_path, log_prefix) - _ensure_utf8_compatible_import_file(file_path) - - error_path = common_storage.error_path_for_job(effective_job_type, job_id) - - total_rows = 0 - error_count = 0 - processed_rows = 0 - errors_detail: List[Dict[str, Any]] = [] - - # 3. Count Total (Quick Pass) or just estimate - try: - with open(file_path, 'r', encoding=_facturas_csv_encoding(file_path)) as f: - total_rows = sum(1 for _ in f) - 1 # Minus header - except Exception as e: - return {"status": "failed", "error": f"Cannot read file: {e}"} - - footer_config = parse_footer_config(config) - date_format = footer_config.get("dateFormat") - - # Validate and set default date_format if not provided - if not date_format: - date_format = "yyyy-mm-dd" # Default to ISO format - logger.info(f"No date_format specified in config, using default: {date_format}") - - ctx_tenant = rls_tenant_var.get() - ctx_company = rls_company_var.get() - logger.info( - "facturas_scan worker_context job_id=%s task_id=%s rls_tenant_ctx=%s rls_company_ctx=%s", - job_id, - getattr(getattr(self, "request", None), "id", None), - ctx_tenant, - ctx_company, - ) - - try: - tenant_id, company_id = common_meta.require_tenant_context(file_path) - except ValueError as e: - return {"status": "failed", "error": str(e)} - if ctx_tenant is not None and int(ctx_tenant) != int(tenant_id): - logger.warning( - "facturas_scan tenant mismatch between celery context and file meta job_id=%s task_tenant=%s meta_tenant=%s", - job_id, - ctx_tenant, - tenant_id, - ) - if company_id is not None and ctx_company is not None and int(ctx_company) != int(company_id): - logger.warning( - "facturas_scan company mismatch between celery context and file meta job_id=%s task_company=%s meta_company=%s", - job_id, - ctx_company, - company_id, - ) - - meta = common_meta.load_meta(file_path) or {} - template_id, inv_type_value, op_type_scan = _resolve_import_context( - model_target=model_target, - meta=meta, - footer_config=footer_config, - job_type_override=job_type_override, - ) - - logger.info( - "Scan job %s tenant_id=%s company_id=%s template_id=%s model_target=%s job_type_override=%s " - "op_type=%s inv_type=%s", - job_id, - tenant_id, - company_id, - template_id, - model_target, - job_type_override, - op_type_scan, - inv_type_value, - ) - - # --- Series de Importación Definitiva: flujo específico (Clarion VALIDA_TODA_SERIES_IMPO_DEF / VALIDA_PARCIAL) --- - DEF_SERIES_TEMPLATE_OR_TYPE = ( - model_target == "invoice_series" - and ( - template_id == "imp_def_series" - or (inv_type_value in ("DEF", "MATDE", "EXDEF")) - ) - ) - if DEF_SERIES_TEMPLATE_OR_TYPE: - try: - from api.v1.modules.a76.invoices.models import InvoiceHeader - from api.v1.modules.a76.items.models import LineItem - from api.v1.modules.a76.items.series.models import Serie - from api.v1.modules.a76.items.line_quantities.models import LineQuantity - from .validators.series_impo_def import ( - validate_row_series_impo_def, - ) - - DEF_INVOICE_TYPES = ("DEF", "MATDE", "EXDEF") - actualizar = meta.get("actualizar", False) - autonumerar = meta.get("autonumerar", True) - validar_series_exception = meta.get("validar_series", False) - _fc = parse_footer_config(meta.get("footer_config")) - if _fc: - if "actualizar" in _fc: - actualizar = bool(_fc["actualizar"]) - elif _fc.get("mode") == "update": - actualizar = True - elif _fc.get("mode") == "replace": - actualizar = False - if "autonumerar" in _fc: - autonumerar = bool(_fc["autonumerar"]) - else: - as_val = _fc.get("autonumber_series", "true") - autonumerar = str(as_val).lower() in ("true", "1", "si", "sí", "yes") - if "validar_series" in _fc: - validar_series_exception = bool(_fc["validar_series"]) - - with scoped_core_db(tenant_id=tenant_id, company_id=company_id) as session: - q = ( - session.query(InvoiceHeader.invoice_number, InvoiceHeader.id, InvoiceHeader.status) - .filter( - InvoiceHeader.tenant_id == tenant_id, - InvoiceHeader.company_id == company_id, - InvoiceHeader.operation_type == "imp", - InvoiceHeader.invoice_type.in_(DEF_INVOICE_TYPES), - ) - ) - rows_inv = q.all() - invoice_id_by_number: Dict[str, int] = {} - invoice_processed_by_number: Dict[str, bool] = {} - for num, iid, is_upd in rows_inv: - if num: - invoice_id_by_number[str(num).strip()] = iid - invoice_processed_by_number[str(num).strip()] = _is_invoice_processed_status(is_upd) - - partida_max_series: Dict[Tuple[str, str], int] = {} - q_qty = ( - session.query( - InvoiceHeader.invoice_number, - LineItem.line_number, - LineQuantity.quantity, - ) - .join(LineItem, LineItem.invoice_id == InvoiceHeader.id) - .outerjoin(LineQuantity, LineQuantity.item_line_id == LineItem.id) - .filter( - InvoiceHeader.tenant_id == tenant_id, - InvoiceHeader.company_id == company_id, - InvoiceHeader.operation_type == "imp", - InvoiceHeader.invoice_type.in_(DEF_INVOICE_TYPES), - ) - ) - for num, ln, qty in q_qty.all(): - if num is not None and ln is not None: - key = (str(num).strip(), str(ln).strip()) - if qty is not None: - partida_max_series[key] = int(qty) if qty else 0 - else: - partida_max_series[key] = 0 - - existing_series_keys: Set[Tuple[str, str, str]] = set() - existing_series_data: Dict[Tuple[str, str, str], Dict[str, Any]] = {} - if actualizar and not autonumerar: - q_ser = ( - session.query( - InvoiceHeader.invoice_number, - LineItem.line_number, - Serie.row, - Serie.serial_numbers, - Serie.model, - Serie.sub_model, - Serie.number_id, - ) - .join(LineItem, LineItem.invoice_id == InvoiceHeader.id) - .join(Serie, Serie.line_item_id == LineItem.id) - .filter( - InvoiceHeader.tenant_id == tenant_id, - InvoiceHeader.company_id == company_id, - InvoiceHeader.operation_type == "imp", - InvoiceHeader.invoice_type.in_(DEF_INVOICE_TYPES), - ) - ) - for num, ln, rw, sn, md, sm, nid in q_ser.all(): - if num is not None: - k = (str(num).strip(), str(ln).strip(), str(rw).strip()) - existing_series_keys.add(k) - existing_series_data.setdefault(k, { - "serial_numbers": sn or "", - "model": md or "", - "sub_model": sm or "", - "number_id": nid or "", - }) - - csv_series_count_so_far: Dict[Tuple[str, str], int] = {} - - with open(file_path, "r", encoding=_facturas_csv_encoding(file_path)) as f_in, open(error_path, "w", encoding="utf-8") as f_err: - sample = f_in.read(2048) - f_in.seek(0) - try: - dialect = common_csv_reader.detect_csv_dialect(sample, delimiters=",;\t") - except Exception: - dialect = "excel" - reader = csv.DictReader(f_in, dialect=dialect) - errors_detail = [] - error_lines_list: List[int] = [] - for i, row in enumerate(reader, start=1): - self.update_state(state="PROGRESS", meta={"current": i, "total": total_rows, "errors": len(error_lines_list)}) - row_norm = row_from_template(row, "imp_def_series", normalize_header) - warnings_list: List[Dict[str, Any]] = [] - row_errors = validate_row_series_impo_def( - row_norm, - i, - actualizar=actualizar, - autonumerar=autonumerar, - validar_series_exception=validar_series_exception, - invoice_id_by_number=invoice_id_by_number, - invoice_processed_by_number=invoice_processed_by_number, - partida_max_series=partida_max_series, - csv_series_count_so_far=csv_series_count_so_far, - existing_series_keys=existing_series_keys, - existing_series_data=existing_series_data, - warnings=warnings_list, - ) - for e in row_errors + warnings_list: - f_err.write(json.dumps(_invoice_scan_error_row_payload(e)) + "\n") - blocking = [e for e in row_errors if not e.get("warning")] - if blocking: - error_lines_list.append(i) - else: - inv_num = (row_norm.get("NUMERO FACTURA") or row_norm.get("NUM FACTURA") or "").strip() - line_fac = (row_norm.get("LINEA FACTURA") or row_norm.get("LINEA") or row_norm.get("PARTIDA") or "").strip() - if inv_num and line_fac: - key_csv = (inv_num, line_fac) - csv_series_count_so_far[key_csv] = csv_series_count_so_far.get(key_csv, 0) + 1 - for e in row_errors + warnings_list: - if len(errors_detail) < 5000: - errors_detail.append(_invoice_scan_error_row_payload(e)) - processed_rows += 1 - - error_count = len(error_lines_list) - common_storage.store_error_lines(effective_job_type, job_id, error_lines_list) - return common_responses.scan_result( - job_id, processed_rows, error_count, errors_detail, total_rows_in_file=total_rows - ) - except Exception as e: - logger.exception("Series importación definitiva scan failed: %s", e) - return {"status": "failed", "error": str(e)} - - # --- Series de Exportación Definitiva: flujo exp_def_series (Clarion VALIDA_TODA_SERIES_EXPO / VALIDA_PARCIAL) --- - if model_target == "invoice_series" and template_id == "exp_def_series": - logger.info("Series expo scan: running validation for job %s", job_id) - try: - from api.v1.modules.a76.invoices.models import InvoiceHeader - from api.v1.modules.a76.items.models import LineItem - from api.v1.modules.a76.items.series.models import Serie - from api.v1.modules.a76.items.line_quantities.models import LineQuantity - from api.v1.modules.a76.general_catalogs.company.models import Company - from .validators.series_expo import validate_row_series_expo - - _fc = parse_footer_config(meta.get("footer_config")) - autonumerar = meta.get("autonumerar", True) - actualizar = meta.get("actualizar", False) - validar_series_exception = meta.get("validar_series", False) - if _fc: - if "autonumerar" in _fc: - autonumerar = bool(_fc["autonumerar"]) - elif _fc.get("autonumber_series", "true") is not None: - autonumerar = str(_fc.get("autonumber_series", "true")).lower() in ("true", "1", "si", "sí", "yes") - if "actualizar" in _fc: - actualizar = bool(_fc["actualizar"]) - if "validar_series" in _fc: - validar_series_exception = bool(_fc["validar_series"]) - - RFC_EXCEPTION_EGM = {"EGM0303257J1"} - - with scoped_core_db(tenant_id=tenant_id, company_id=company_id) as session: - company = session.query(Company).filter(Company.id == company_id).first() - company_rfc = (company.rfc or "").strip().upper() if company else "" - - q_inv_expo = ( - session.query(InvoiceHeader.invoice_number, InvoiceHeader.id, InvoiceHeader.status) - .filter( - InvoiceHeader.tenant_id == tenant_id, - InvoiceHeader.company_id == company_id, - InvoiceHeader.operation_type == "exp", - ) - ) - invoice_id_by_number: Dict[str, int] = {} - invoice_processed_by_number: Dict[str, bool] = {} - for num, iid, is_upd in q_inv_expo.all(): - if num: - invoice_id_by_number[str(num).strip()] = iid - invoice_processed_by_number[str(num).strip()] = _is_invoice_processed_status(is_upd) - - partida_max_series: Dict[Tuple[str, str], int] = {} - q_qty = ( - session.query( - InvoiceHeader.invoice_number, - LineItem.line_number, - LineQuantity.quantity, - ) - .join(LineItem, LineItem.invoice_id == InvoiceHeader.id) - .outerjoin(LineQuantity, LineQuantity.item_line_id == LineItem.id) - .filter( - InvoiceHeader.tenant_id == tenant_id, - InvoiceHeader.company_id == company_id, - InvoiceHeader.operation_type == "exp", - ) - ) - for num, ln, qty in q_qty.all(): - if num is not None and ln is not None: - key = (str(num).strip(), str(ln).strip()) - if qty is not None: - partida_max_series[key] = int(qty) if qty else 0 - else: - partida_max_series[key] = 0 - - existing_series_keys: Set[Tuple[str, str, str]] = set() - existing_series_data: Dict[Tuple[str, str, str], Dict[str, Any]] = {} - if actualizar and not autonumerar: - q_ser = ( - session.query( - InvoiceHeader.invoice_number, - LineItem.line_number, - Serie.row, - Serie.serial_numbers, - Serie.model, - Serie.sub_model, - Serie.number_id, - ) - .join(LineItem, LineItem.invoice_id == InvoiceHeader.id) - .join(Serie, Serie.line_item_id == LineItem.id) - .filter( - InvoiceHeader.tenant_id == tenant_id, - InvoiceHeader.company_id == company_id, - InvoiceHeader.operation_type == "exp", - ) - ) - for num, ln, rw, sn, md, sm, nid in q_ser.all(): - if num is not None: - k = (str(num).strip(), str(ln).strip(), str(rw).strip()) - existing_series_keys.add(k) - existing_series_data.setdefault(k, { - "serial_numbers": sn or "", - "model": md or "", - "sub_model": sm or "", - "number_id": nid or "", - }) - - csv_series_count_so_far: Dict[Tuple[str, str], int] = {} - - invoice_numbers_from_csv: Set[str] = set() - with open(file_path, "r", encoding=_facturas_csv_encoding(file_path)) as f_in: - sample = f_in.read(2048) - f_in.seek(0) - try: - dialect = common_csv_reader.detect_csv_dialect(sample, delimiters=",;\t") - except Exception: - dialect = "excel" - reader = csv.DictReader(f_in, dialect=dialect) - for row in reader: - row_norm = row_from_template(row, "exp_def_series", normalize_header) - inv = (row_norm.get("NUMERO FACTURA") or row_norm.get("NUM FACTURA") or row_norm.get("FACTURA EXPO") or "").strip() - if inv: - invoice_numbers_from_csv.add(inv) - if company_rfc in RFC_EXCEPTION_EGM: - rfc_exception_updated: Set[str] = invoice_numbers_from_csv - else: - rfc_exception_updated = set() - - with open(file_path, "r", encoding=_facturas_csv_encoding(file_path)) as f_in, open(error_path, "w", encoding="utf-8") as f_err: - f_in.seek(0) - try: - dialect = common_csv_reader.detect_csv_dialect(f_in.read(2048), delimiters=",;\t") - except Exception: - dialect = "excel" - f_in.seek(0) - reader = csv.DictReader(f_in, dialect=dialect) - errors_detail = [] - error_lines_list: List[int] = [] - for i, row in enumerate(reader, start=1): - self.update_state(state="PROGRESS", meta={"current": i, "total": total_rows, "errors": len(error_lines_list)}) - row_norm = row_from_template(row, "exp_def_series", normalize_header) - warnings_list: List[Dict[str, Any]] = [] - row_errors = validate_row_series_expo( - row_norm, - i, - actualizar=actualizar, - autonumerar=autonumerar, - validar_series_exception=validar_series_exception, - invoice_id_by_number=invoice_id_by_number, - invoice_processed_by_number=invoice_processed_by_number, - rfc_exception_updated=rfc_exception_updated, - partida_max_series=partida_max_series, - csv_series_count_so_far=csv_series_count_so_far, - existing_series_keys=existing_series_keys, - existing_series_data=existing_series_data, - warnings=warnings_list, - ) - for e in row_errors + warnings_list: - f_err.write(json.dumps(_invoice_scan_error_row_payload(e)) + "\n") - blocking = [e for e in row_errors if not e.get("warning")] - if blocking: - error_lines_list.append(i) - else: - inv_num = (row_norm.get("NUMERO FACTURA") or row_norm.get("NUM FACTURA") or row_norm.get("FACTURA EXPO") or "").strip() - line_fac = (row_norm.get("LINEA FACTURA") or row_norm.get("LINEA") or row_norm.get("PARTIDA") or "").strip() - if inv_num and line_fac: - key_csv = (inv_num, line_fac) - csv_series_count_so_far[key_csv] = csv_series_count_so_far.get(key_csv, 0) + 1 - for e in row_errors + warnings_list: - if len(errors_detail) < 5000: - errors_detail.append(_invoice_scan_error_row_payload(e)) - processed_rows += 1 - - # El commit usa líneas únicas para omitir filas; el resumen preliminar - # debe usar la misma base para evitar discrepancias de válidos/errores. - unique_error_lines = sorted(set(error_lines_list)) - error_count = len(unique_error_lines) - common_storage.store_error_lines(effective_job_type, job_id, unique_error_lines) - return common_responses.scan_result( - job_id, processed_rows, error_count, errors_detail, total_rows_in_file=total_rows - ) - except Exception as e: - logger.exception("Series exportación definitiva scan failed: %s", e) - return {"status": "failed", "error": str(e)} - - # --- Series Compras Mexicanas: misma lógica que Impo Def, facturas MEX --- - if model_target == "invoice_series" and template_id == "cmex_series": - try: - from api.v1.modules.a76.invoices.models import InvoiceHeader - from api.v1.modules.a76.items.models import LineItem - from api.v1.modules.a76.items.series.models import Serie - from api.v1.modules.a76.items.line_quantities.models import LineQuantity - from .validators.series_impo_def import validate_row_series_impo_def - - actualizar = meta.get("actualizar", False) - autonumerar = meta.get("autonumerar", True) - validar_series_exception = meta.get("validar_series", False) - _fc = parse_footer_config(meta.get("footer_config")) - if _fc: - if "actualizar" in _fc: - actualizar = bool(_fc["actualizar"]) - elif _fc.get("mode") == "update": - actualizar = True - elif _fc.get("mode") == "replace": - actualizar = False - if "autonumerar" in _fc: - autonumerar = bool(_fc["autonumerar"]) - else: - as_val = _fc.get("autonumber_series", "true") - autonumerar = str(as_val).lower() in ("true", "1", "si", "sí", "yes") - if "validar_series" in _fc: - validar_series_exception = bool(_fc["validar_series"]) - - with scoped_core_db(tenant_id=tenant_id, company_id=company_id) as session: - q = ( - session.query(InvoiceHeader.invoice_number, InvoiceHeader.id, InvoiceHeader.status) - .filter( - InvoiceHeader.tenant_id == tenant_id, - InvoiceHeader.company_id == company_id, - InvoiceHeader.operation_type == "imp", - InvoiceHeader.invoice_type == "MEX", - ) - ) - rows_inv = q.all() - invoice_id_by_number: Dict[str, int] = {} - invoice_processed_by_number: Dict[str, bool] = {} - for num, iid, is_upd in rows_inv: - if num: - invoice_id_by_number[str(num).strip()] = iid - invoice_processed_by_number[str(num).strip()] = _is_invoice_processed_status(is_upd) - - partida_max_series: Dict[Tuple[str, str], int] = {} - q_qty = ( - session.query( - InvoiceHeader.invoice_number, - LineItem.line_number, - LineQuantity.quantity, - ) - .join(LineItem, LineItem.invoice_id == InvoiceHeader.id) - .outerjoin(LineQuantity, LineQuantity.item_line_id == LineItem.id) - .filter( - InvoiceHeader.tenant_id == tenant_id, - InvoiceHeader.company_id == company_id, - InvoiceHeader.operation_type == "imp", - InvoiceHeader.invoice_type == "MEX", - ) - ) - for num, ln, qty in q_qty.all(): - if num is not None and ln is not None: - key = (str(num).strip(), str(ln).strip()) - if qty is not None: - partida_max_series[key] = int(qty) if qty else 0 - else: - partida_max_series[key] = 0 - - existing_series_keys: Set[Tuple[str, str, str]] = set() - existing_series_data: Dict[Tuple[str, str, str], Dict[str, Any]] = {} - if actualizar and not autonumerar: - q_ser = ( - session.query( - InvoiceHeader.invoice_number, - LineItem.line_number, - Serie.row, - Serie.serial_numbers, - Serie.model, - Serie.sub_model, - Serie.number_id, - ) - .join(LineItem, LineItem.invoice_id == InvoiceHeader.id) - .join(Serie, Serie.line_item_id == LineItem.id) - .filter( - InvoiceHeader.tenant_id == tenant_id, - InvoiceHeader.company_id == company_id, - InvoiceHeader.operation_type == "imp", - InvoiceHeader.invoice_type == "MEX", - ) - ) - for num, ln, rw, sn, md, sm, nid in q_ser.all(): - if num is not None: - k = (str(num).strip(), str(ln).strip(), str(rw).strip()) - existing_series_keys.add(k) - existing_series_data.setdefault(k, { - "serial_numbers": sn or "", - "model": md or "", - "sub_model": sm or "", - "number_id": nid or "", - }) - - csv_series_count_so_far: Dict[Tuple[str, str], int] = {} - - with open(file_path, "r", encoding=_facturas_csv_encoding(file_path)) as f_in, open(error_path, "w", encoding="utf-8") as f_err: - sample = f_in.read(2048) - f_in.seek(0) - try: - dialect = common_csv_reader.detect_csv_dialect(sample, delimiters=",;\t") - except Exception: - dialect = "excel" - reader = csv.DictReader(f_in, dialect=dialect) - errors_detail = [] - error_lines_list: List[int] = [] - for i, row in enumerate(reader, start=1): - self.update_state(state="PROGRESS", meta={"current": i, "total": total_rows, "errors": len(error_lines_list)}) - row_norm = row_from_template(row, "cmex_series", normalize_header) - warnings_list: List[Dict[str, Any]] = [] - row_errors = validate_row_series_impo_def( - row_norm, - i, - actualizar=actualizar, - autonumerar=autonumerar, - validar_series_exception=validar_series_exception, - invoice_id_by_number=invoice_id_by_number, - invoice_processed_by_number=invoice_processed_by_number, - partida_max_series=partida_max_series, - csv_series_count_so_far=csv_series_count_so_far, - existing_series_keys=existing_series_keys, - existing_series_data=existing_series_data, - warnings=warnings_list, - catalog_label="Compras Mexicanas", - ) - for e in row_errors + warnings_list: - f_err.write(json.dumps(_invoice_scan_error_row_payload(e)) + "\n") - blocking = [e for e in row_errors if not e.get("warning")] - if blocking: - error_lines_list.append(i) - else: - inv_num = (row_norm.get("NUMERO FACTURA") or row_norm.get("NUM FACTURA") or "").strip() - line_fac = (row_norm.get("LINEA FACTURA") or row_norm.get("LINEA") or row_norm.get("PARTIDA") or "").strip() - if inv_num and line_fac: - key_csv = (inv_num, line_fac) - csv_series_count_so_far[key_csv] = csv_series_count_so_far.get(key_csv, 0) + 1 - for e in row_errors + warnings_list: - if len(errors_detail) < 5000: - errors_detail.append(_invoice_scan_error_row_payload(e)) - processed_rows += 1 - - error_count = len(error_lines_list) - common_storage.store_error_lines(effective_job_type, job_id, error_lines_list) - return common_responses.scan_result( - job_id, processed_rows, error_count, errors_detail, total_rows_in_file=total_rows - ) - except Exception as e: - logger.exception("Series Compras Mexicanas scan failed: %s", e) - return {"status": "failed", "error": str(e)} - - # --- Series de Importación Temporal: flujo específico (Clarion VALIDA_TODA / VALIDA_PARCIAL) --- - if model_target == "invoice_series": - try: - from api.v1.modules.a76.invoices.models import InvoiceHeader - from api.v1.modules.a76.items.models import LineItem - from api.v1.modules.a76.items.series.models import Serie - from .validators.series_impo_temp import ( - validate_row_series_impo_temp, - ) - - actualizar = meta.get("actualizar", False) - autonumerar = meta.get("autonumerar", True) - validar_series_exception = meta.get("validar_series", False) - _fc = parse_footer_config(meta.get("footer_config")) - if _fc: - if "actualizar" in _fc: - actualizar = bool(_fc["actualizar"]) - elif _fc.get("mode") == "update": - actualizar = True - elif _fc.get("mode") == "replace": - actualizar = False - if "autonumerar" in _fc: - autonumerar = bool(_fc["autonumerar"]) - else: - as_val = _fc.get("autonumber_series", "true") - autonumerar = str(as_val).lower() in ("true", "1", "si", "sí", "yes") - if "validar_series" in _fc: - validar_series_exception = bool(_fc["validar_series"]) - - with scoped_core_db(tenant_id=tenant_id, company_id=company_id) as session: - # Invoice lookup: imp + TEM - q = ( - session.query(InvoiceHeader.invoice_number, InvoiceHeader.id, InvoiceHeader.status) - .filter( - InvoiceHeader.tenant_id == tenant_id, - InvoiceHeader.company_id == company_id, - InvoiceHeader.operation_type == "imp", - InvoiceHeader.invoice_type == "TEM", - ) - ) - rows_inv = q.all() - invoice_id_by_number: Dict[str, int] = {} - invoice_processed_by_number: Dict[str, bool] = {} - for num, iid, is_upd in rows_inv: - if num: - invoice_id_by_number[str(num).strip()] = iid - invoice_processed_by_number[str(num).strip()] = _is_invoice_processed_status(is_upd) - - # Existing series keys: (invoice_number, linea_factura, linea_serie) - existing_series_keys: Set[Tuple[str, str, str]] = set() - existing_series_data: Dict[Tuple[str, str, str], Dict[str, Any]] = {} - if actualizar and not autonumerar: - q_ser = ( - session.query( - InvoiceHeader.invoice_number, - LineItem.line_number, - Serie.row, - Serie.serial_numbers, - Serie.model, - Serie.sub_model, - Serie.number_id, - ) - .join(LineItem, LineItem.invoice_id == InvoiceHeader.id) - .join(Serie, Serie.line_item_id == LineItem.id) - .filter( - InvoiceHeader.tenant_id == tenant_id, - InvoiceHeader.company_id == company_id, - InvoiceHeader.operation_type == "imp", - InvoiceHeader.invoice_type == "TEM", - ) - ) - for num, ln, rw, sn, md, sm, nid in q_ser.all(): - if num is not None: - k = (str(num).strip(), str(ln).strip(), str(rw).strip()) - existing_series_keys.add(k) - existing_series_data.setdefault(k, { - "serial_numbers": sn or "", - "model": md or "", - "sub_model": sm or "", - "number_id": nid or "", - }) - - with open(file_path, "r", encoding=_facturas_csv_encoding(file_path)) as f_in, open(error_path, "w", encoding="utf-8") as f_err: - sample = f_in.read(2048) - f_in.seek(0) - try: - dialect = common_csv_reader.detect_csv_dialect(sample, delimiters=",;\t") - except Exception: - dialect = "excel" - reader = csv.DictReader(f_in, dialect=dialect) - errors_detail: List[Dict[str, Any]] = [] - error_lines_list: List[int] = [] - for i, row in enumerate(reader, start=1): - self.update_state(state="PROGRESS", meta={"current": i, "total": total_rows, "errors": len(error_lines_list)}) - row_norm = row_from_template(row, "imp_temp_series", normalize_header) - warnings_list: List[Dict[str, Any]] = [] - row_errors = validate_row_series_impo_temp( - row_norm, - i, - actualizar=actualizar, - autonumerar=autonumerar, - validar_series_exception=validar_series_exception, - invoice_id_by_number=invoice_id_by_number, - invoice_processed_by_number=invoice_processed_by_number, - existing_series_keys=existing_series_keys, - existing_series_data=existing_series_data, - warnings=warnings_list, - ) - for e in row_errors + warnings_list: - f_err.write(json.dumps(_invoice_scan_error_row_payload(e)) + "\n") - blocking = [e for e in row_errors if not e.get("warning")] - if blocking: - error_lines_list.append(i) - for e in row_errors + warnings_list: - if len(errors_detail) < 5000: - errors_detail.append(_invoice_scan_error_row_payload(e)) - processed_rows += 1 - - # El commit usa líneas únicas para omitir filas; el resumen preliminar - # debe usar la misma base para evitar discrepancias de válidos/errores. - unique_error_lines = sorted(set(error_lines_list)) - error_count = len(unique_error_lines) - common_storage.store_error_lines(effective_job_type, job_id, unique_error_lines) - return common_responses.scan_result( - job_id, processed_rows, error_count, errors_detail, total_rows_in_file=total_rows - ) - except Exception as e: - logger.exception("Series import scan failed: %s", e) - return {"status": "failed", "error": str(e)} - - # --- Partidas de Importación Temporal: flujo específico (Clarion VALIDA_TODA / VALIDA_PARCIAL) --- - if model_target == "invoice_details" and template_id == "imp_temp_details": - try: - from api.v1.modules.a76.invoices.models import InvoiceHeader - from api.v1.modules.a76.items.models import LineItem - from api.v1.modules.a76.classes.models import Class - from api.v1.modules.a76.general_catalogs.units_of_measure.models import UnitOfMeasure - from api.v1.modules.a76.general_catalogs.packages.models import Package - from api.v1.modules.public.reference_data.countries.models import Country - from api.v1.modules.a76.general_catalogs.sectors.models import Sector - from api.v1.modules.public.reference_data.valuation_methods.models import ValuationMethod - from api.v1.modules.public.reference_data.payment_methods.models import PaymentMethod - from api.v1.modules.a76.general_catalogs.company.models import Company - from api.v1.modules.a76.parts.models import Part - from .validators.partidas_impo_temp import validate_row_partidas_impo_temp - - _fc = parse_footer_config(meta.get("footer_config")) - autonumerar = meta.get("autonumerar", True) - actualizar = meta.get("actualizar", False) - levantar_subpartidas = meta.get("levantar_subpartidas", False) - calcular_costo_en_base_a_total = meta.get("calcular_costo_unitario_en_base_a_valor_total", False) - validar_decimales_pza = meta.get("validar_decimales_pza", False) - if _fc: - if "autonumerar" in _fc: - autonumerar = bool(_fc["autonumerar"]) - elif _fc.get("autonumber_partidas", "true") is not None: - autonumerar = str(_fc.get("autonumber_partidas", "true")).lower() in ("true", "1", "si", "sí", "yes") - if "actualizar" in _fc: - actualizar = bool(_fc["actualizar"]) - if "levantar_subpartidas" in _fc: - levantar_subpartidas = bool(_fc["levantar_subpartidas"]) - if "calcular_costo_unitario_en_base_a_valor_total" in _fc: - calcular_costo_en_base_a_total = bool(_fc["calcular_costo_unitario_en_base_a_valor_total"]) - if "validar_decimales_pza" in _fc: - validar_decimales_pza = bool(_fc["validar_decimales_pza"]) - - RFC_EXCEPTION_UPDATED = {"TPI121217SF6", "TCI170502858"} - RFC_EXCEPTION_NUM_PARTE = {"CTE980130518"} - - with scoped_core_db(tenant_id=tenant_id, company_id=company_id) as session: - # Resolver parámetros del sistema: validadecencant (decimales PZA) y validarseries (switch maestro) - settings = AppSettingsService.get_resolved_settings(session, int(tenant_id), int(company_id)) - gen_params = settings.get("ssisgen", {}) or settings.get("qsisgen", {}) - if str(gen_params.get("validadecencant", "0")).strip() == "1": - validar_decimales_pza = True - - q_inv = ( - session.query(InvoiceHeader.invoice_number, InvoiceHeader.id, InvoiceHeader.status) - .filter( - InvoiceHeader.tenant_id == tenant_id, - InvoiceHeader.company_id == company_id, - InvoiceHeader.operation_type == op_type_scan, - InvoiceHeader.invoice_type == inv_type_value, - ) - ) - invoice_id_by_number: Dict[str, int] = {} - invoice_processed_by_number: Dict[str, bool] = {} - for num, iid, is_upd in q_inv.all(): - if num: - inv_norm = _normalize_invoice_number(num) - invoice_id_by_number[inv_norm] = iid - invoice_processed_by_number[inv_norm] = _is_invoice_processed_status(is_upd) - - existing_line_keys_by_invoice: Dict[str, Set[str]] = {} - q_li = ( - session.query(InvoiceHeader.invoice_number, LineItem.line_number) - .join(LineItem, LineItem.invoice_id == InvoiceHeader.id) - .filter( - InvoiceHeader.tenant_id == tenant_id, - InvoiceHeader.company_id == company_id, - InvoiceHeader.operation_type == op_type_scan, - InvoiceHeader.invoice_type == inv_type_value, - ) - ) - for num, ln in q_li.all(): - if num is not None: - key = _normalize_invoice_number(num) - if key not in existing_line_keys_by_invoice: - existing_line_keys_by_invoice[key] = set() - existing_line_keys_by_invoice[key].add(str(ln).strip()) - - partidas_principales_bd: Set[Tuple[str, str]] = set() - try: - from api.v1.modules.a24.fa.fa_item_lines.models import FaLineItem - q_pp = ( - session.query(InvoiceHeader.invoice_number, LineItem.line_number) - .join(LineItem, LineItem.invoice_id == InvoiceHeader.id) - .join(FaLineItem, FaLineItem.id == LineItem.id) - .filter( - InvoiceHeader.tenant_id == tenant_id, - InvoiceHeader.company_id == company_id, - FaLineItem.is_subitem == False, - FaLineItem.contains_subitems == True, - ) - ) - for num, ln in q_pp.all(): - if num is not None: - partidas_principales_bd.add((_normalize_invoice_number(num), str(ln).strip())) - except Exception: - pass - - valid_class_codes: Set[str] = set() - class_um_by_code: Dict[str, str] = {} - class_fraction_by_code: Dict[str, str] = {} - class_desc_es_by_code: Dict[str, str] = {} - class_desc_en_by_code: Dict[str, str] = {} - for c in session.query(Class).filter(Class.tenant_id == tenant_id, Class.company_id == company_id).all(): - code = (c.class_code or "").strip().upper() - if code: - valid_class_codes.add(code) - class_um_by_code[code] = (c.unit_of_measure or "").strip().upper() - class_fraction_by_code[code] = (c.fraction or "").strip() - class_desc_es_by_code[code] = (c.description_es or "").strip() - class_desc_en_by_code[code] = (c.description_en or "").strip() - - valid_uom_codes: Set[str] = set() - for u in session.query(UnitOfMeasure.code).filter(UnitOfMeasure.tenant_id == tenant_id, UnitOfMeasure.company_id == company_id).all(): - if u[0]: - valid_uom_codes.add((u[0] or "").strip().upper()) - - valid_bulks_codes: Set[str] = set() - for p in session.query(Package.key).filter(Package.tenant_id == tenant_id, Package.company_id == company_id).all(): - if p[0]: - valid_bulks_codes.add((p[0] or "").strip()) - - valid_country_keys: Set[str] = set() - for row in session.query(Country.m3_key, Country.ame_key).all(): - if row[0]: - valid_country_keys.add((row[0] or "").strip().upper()) - if row[1]: - valid_country_keys.add((row[1] or "").strip().upper()) - - valid_fraction_ame: Set[str] = set() # unused; SITAR resolve per row in validators - - authorized_sectors: Set[str] = set() - for row in session.query(Sector.key).filter( - Sector.authorized == True, - Sector.tenant_id == tenant_id, - Sector.company_id == company_id, - ).all(): - if row[0]: - authorized_sectors.add((row[0] or "").strip().upper()) - - valid_payment_methods: Set[str] = set() - for row in session.query(PaymentMethod.key).all(): - if row[0] is not None: - valid_payment_methods.add(str(row[0]).strip()) - - valid_valuation_methods: Set[str] = set() - for row in session.query(ValuationMethod.key).all(): - if row[0]: - valid_valuation_methods.add((row[0] or "").strip()) - - company = session.query(Company).filter(Company.id == company_id).first() - company_has_prosec = bool(company.prosec) if company else False - company_rfc = (company.rfc or "").strip().upper() if company else "" - - valid_part_numbers: Set[str] = set() - for row in session.query(Part.part_number).filter(Part.tenant_id == tenant_id, Part.company_id == company_id).all(): - if row[0]: - valid_part_numbers.add((row[0] or "").strip().upper()) - - rfc_exception_updated: Set[str] = set() - rfc_exception_num_parte: Set[str] = set() - - with open(file_path, "r", encoding=_facturas_csv_encoding(file_path)) as f_in: - sample = f_in.read(2048) - f_in.seek(0) - try: - dialect = common_csv_reader.detect_csv_dialect(sample, delimiters=",;\t") - except Exception: - dialect = "excel" - reader = csv.DictReader(f_in, dialect=dialect) - raw_fieldnames = list(reader.fieldnames or []) - rows_list = list(reader) - - normalized_headers = {normalize_header(h or "") for h in raw_fieldnames} - invoice_header_aliases = {"NUMERO FACTURA", "NUM FACTURA", "FACTURA"} - has_invoice_header = any(h in normalized_headers for h in invoice_header_aliases) - if not has_invoice_header: - structure_error = { - "line": 1, - "col": "NUMERO FACTURA", - "msg": "Error estructural: no se encontró una columna de factura (NUMERO FACTURA / NUM FACTURA / FACTURA).", - "solution": "Corrige el encabezado de la columna A y vuelve a subir el archivo.", - "warning": False, - } - with open(error_path, "w", encoding="utf-8") as f_err: - f_err.write(json.dumps(_invoice_scan_error_row_payload(structure_error)) + "\n") - common_storage.store_error_lines(effective_job_type, job_id, [1]) - return common_responses.scan_result( - job_id=job_id, - processed_rows=len(rows_list), - error_count=1, - errors_detail=[_invoice_scan_error_row_payload(structure_error)], - total_rows_in_file=total_rows, - message="Precheck estructural falló: la columna de número de factura no fue detectada.", - ) - - invoice_numbers_from_csv = set() - empty_invoice_lines = 0 - for row in rows_list: - row_norm = row_from_template(row, "imp_temp_details", normalize_header) - inv = _extract_invoice_number_from_row(row_norm) - if inv: - invoice_numbers_from_csv.add(inv) - else: - empty_invoice_lines += 1 - if company_rfc in RFC_EXCEPTION_UPDATED: - rfc_exception_updated = invoice_numbers_from_csv - if company_rfc in RFC_EXCEPTION_NUM_PARTE: - rfc_exception_num_parte = invoice_numbers_from_csv - - if rows_list: - empty_ratio = empty_invoice_lines / len(rows_list) - if empty_ratio >= 0.8: - structure_error = { - "line": 1, - "col": "NUMERO FACTURA", - "msg": "Error estructural: la mayoría de filas no contiene número de factura utilizable.", - "solution": "Verifica delimitador/encabezados del CSV y que la columna de factura esté poblada.", - "warning": False, - } - with open(error_path, "w", encoding="utf-8") as f_err: - f_err.write(json.dumps(_invoice_scan_error_row_payload(structure_error)) + "\n") - common_storage.store_error_lines(effective_job_type, job_id, [1]) - return common_responses.scan_result( - job_id=job_id, - processed_rows=len(rows_list), - error_count=1, - errors_detail=[_invoice_scan_error_row_payload(structure_error)], - total_rows_in_file=total_rows, - message=f"Precheck estructural falló: {empty_invoice_lines}/{len(rows_list)} filas sin número de factura.", - ) - - line_counts_csv: Dict[Tuple[str, str], int] = {} - partidas_principales_csv: Set[Tuple[str, str]] = set() - - def _get_row(row_norm: Dict[str, Any], *keys: str) -> str: - for k in keys: - v = row_norm.get(k) - if v is not None and str(v).strip(): - return str(v).strip() - return "" - - for row in rows_list: - row_norm = row_from_template(row, "imp_temp_details", normalize_header) - inv = _extract_invoice_number_from_row(row_norm) - linea = _get_row(row_norm, "LINEA", "RENGLON", "PARTIDA") - if inv and linea: - key = (inv, linea) - line_counts_csv[key] = line_counts_csv.get(key, 0) + 1 - u = _get_row(row_norm, "ES PARTIDA O SUBPARTIDA", "ESSUBPARTIDA").upper() - if u == "P" and inv and linea: - partidas_principales_csv.add((inv, linea)) - - error_count = 0 - processed_rows = 0 - error_lines_list = [] - errors_detail = [] - - with open(error_path, "w", encoding="utf-8") as f_err: - for i, row in enumerate(rows_list, start=1): - self.update_state(state="PROGRESS", meta={"current": i, "total": total_rows, "errors": error_count}) - row_norm = row_from_template(row, "imp_temp_details", normalize_header) - row_errors = validate_row_partidas_impo_temp( - row_norm, - i, - autonumerar=autonumerar, - actualizar=actualizar, - levantar_subpartidas=levantar_subpartidas, - calcular_costo_en_base_a_total=calcular_costo_en_base_a_total, - validar_decimales_pza=validar_decimales_pza, - invoice_id_by_number=invoice_id_by_number, - invoice_processed_by_number=invoice_processed_by_number, - rfc_exception_updated=rfc_exception_updated, - existing_line_keys_by_invoice=existing_line_keys_by_invoice, - line_counts_csv=line_counts_csv, - partidas_principales_csv=partidas_principales_csv, - partidas_principales_bd=partidas_principales_bd, - valid_class_codes=valid_class_codes, - class_um_by_code=class_um_by_code, - class_fraction_by_code=class_fraction_by_code, - class_desc_es_by_code=class_desc_es_by_code, - class_desc_en_by_code=class_desc_en_by_code, - valid_uom_codes=valid_uom_codes, - valid_bulks_codes=valid_bulks_codes, - valid_country_keys=valid_country_keys, - valid_fraction_ame=valid_fraction_ame, - valid_payment_methods=valid_payment_methods, - valid_valuation_methods=valid_valuation_methods, - authorized_sectors=authorized_sectors, - company_has_prosec=company_has_prosec, - rfc_exception_num_parte=rfc_exception_num_parte or None, - valid_part_numbers=valid_part_numbers, - warnings=None, - ) - for e in row_errors: - f_err.write(json.dumps(_invoice_scan_error_row_payload(e)) + "\n") - blocking = [e for e in row_errors if not e.get("warning")] - if blocking: - error_lines_list.append(i) - for e in row_errors: - if len(errors_detail) < 5000: - errors_detail.append(_invoice_scan_error_row_payload(e)) - processed_rows += 1 - - unique_error_lines = sorted(set(error_lines_list)) - error_count = len(unique_error_lines) - common_storage.store_error_lines(effective_job_type, job_id, unique_error_lines) - scan_message = None - if processed_rows > 0 and errors_detail: - reason_counts: Dict[str, int] = {} - for e in errors_detail: - if e.get("warning"): - continue - msg = str(e.get("msg", "")).strip() - if not msg: - continue - reason_counts[msg] = reason_counts.get(msg, 0) + 1 - if reason_counts: - top_reason, top_count = max(reason_counts.items(), key=lambda kv: kv[1]) - if top_count / max(processed_rows, 1) >= 0.8: - scan_message = ( - f"Causa dominante de rechazo ({top_count}/{processed_rows}): {top_reason}. " - f"contexto invoice_type={inv_type_value}, operation_type={op_type_scan}, template_id={template_id}." - ) - logger.warning("Scan partidas causa dominante: %s", scan_message) - return common_responses.scan_result( - job_id, - processed_rows, - error_count, - errors_detail, - total_rows_in_file=total_rows, - message=scan_message, - ) - except Exception as e: - logger.exception("Partidas import scan failed: %s", e) - return {"status": "failed", "error": str(e)} - - # --- Partidas de Importación Definitiva: flujo específico (Clarion VALIDA_TODA_PARIMPO_DEF / VALIDA_PARCIAL) --- - # Estructura de columnas: misma que partidas TEM (imp_def_details resuelve a imp_temp_details). Facturas DEF/MATDE/EXDEF. - if model_target == "invoice_details" and template_id == "imp_def_details": - try: - from api.v1.modules.a76.invoices.models import InvoiceHeader - from api.v1.modules.a76.items.models import LineItem - from api.v1.modules.a76.classes.models import Class - from api.v1.modules.a76.general_catalogs.units_of_measure.models import UnitOfMeasure - from api.v1.modules.a76.general_catalogs.packages.models import Package - from api.v1.modules.public.reference_data.countries.models import Country - from api.v1.modules.a76.general_catalogs.sectors.models import Sector - from api.v1.modules.public.reference_data.valuation_methods.models import ValuationMethod - from api.v1.modules.public.reference_data.payment_methods.models import PaymentMethod - from api.v1.modules.a76.general_catalogs.company.models import Company - from api.v1.modules.a76.parts.models import Part - from .validators.partidas_impo_def import validate_row_partidas_impo_def - - DEF_INVOICE_TYPES = ("DEF", "MATDE", "EXDEF") - _fc = parse_footer_config(meta.get("footer_config")) - autonumerar = meta.get("autonumerar", True) - actualizar = meta.get("actualizar", False) - levantar_subpartidas = meta.get("levantar_subpartidas", False) - calcular_costo_en_base_a_total = meta.get("calcular_costo_unitario_en_base_a_valor_total", False) - validar_decimales_pza = meta.get("validar_decimales_pza", False) - if _fc: - if "autonumerar" in _fc: - autonumerar = bool(_fc["autonumerar"]) - elif _fc.get("autonumber_partidas", "true") is not None: - autonumerar = str(_fc.get("autonumber_partidas", "true")).lower() in ("true", "1", "si", "sí", "yes") - if "actualizar" in _fc: - actualizar = bool(_fc["actualizar"]) - if "levantar_subpartidas" in _fc: - levantar_subpartidas = bool(_fc["levantar_subpartidas"]) - if "calcular_costo_unitario_en_base_a_valor_total" in _fc: - calcular_costo_en_base_a_total = bool(_fc["calcular_costo_unitario_en_base_a_valor_total"]) - if "validar_decimales_pza" in _fc: - validar_decimales_pza = bool(_fc["validar_decimales_pza"]) - - RFC_EXCEPTION_UPDATED = {"TPI121217SF6", "TCI170502858"} - RFC_EXCEPTION_NUM_PARTE = {"CTE980130518"} - - with scoped_core_db(tenant_id=tenant_id, company_id=company_id) as session: - # Resolver parámetros del sistema: validadecencant (decimales PZA) y validarseries (switch maestro) - settings = AppSettingsService.get_resolved_settings(session, int(tenant_id), int(company_id)) - gen_params = settings.get("ssisgen", {}) or settings.get("qsisgen", {}) - if str(gen_params.get("validadecencant", "0")).strip() == "1": - validar_decimales_pza = True - - q_inv = ( - session.query(InvoiceHeader.invoice_number, InvoiceHeader.id, InvoiceHeader.status) - .filter( - InvoiceHeader.tenant_id == tenant_id, - InvoiceHeader.company_id == company_id, - InvoiceHeader.operation_type == "imp", - InvoiceHeader.invoice_type.in_(DEF_INVOICE_TYPES), - ) - ) - invoice_id_by_number: Dict[str, int] = {} - invoice_processed_by_number: Dict[str, bool] = {} - for num, iid, is_upd in q_inv.all(): - if num: - invoice_id_by_number[str(num).strip()] = iid - invoice_processed_by_number[str(num).strip()] = _is_invoice_processed_status(is_upd) - - existing_line_keys_by_invoice: Dict[str, Set[str]] = {} - q_li = ( - session.query(InvoiceHeader.invoice_number, LineItem.line_number) - .join(LineItem, LineItem.invoice_id == InvoiceHeader.id) - .filter( - InvoiceHeader.tenant_id == tenant_id, - InvoiceHeader.company_id == company_id, - InvoiceHeader.operation_type == "imp", - InvoiceHeader.invoice_type.in_(DEF_INVOICE_TYPES), - ) - ) - for num, ln in q_li.all(): - if num is not None: - key = str(num).strip() - if key not in existing_line_keys_by_invoice: - existing_line_keys_by_invoice[key] = set() - existing_line_keys_by_invoice[key].add(str(ln).strip()) - - partidas_principales_bd: Set[Tuple[str, str]] = set() - try: - from api.v1.modules.a24.fa.fa_item_lines.models import FaLineItem - q_pp = ( - session.query(InvoiceHeader.invoice_number, LineItem.line_number) - .join(LineItem, LineItem.invoice_id == InvoiceHeader.id) - .join(FaLineItem, FaLineItem.id == LineItem.id) - .filter( - InvoiceHeader.tenant_id == tenant_id, - InvoiceHeader.company_id == company_id, - InvoiceHeader.operation_type == "imp", - InvoiceHeader.invoice_type.in_(DEF_INVOICE_TYPES), - FaLineItem.is_subitem == False, - FaLineItem.contains_subitems == True, - ) - ) - for num, ln in q_pp.all(): - if num is not None: - partidas_principales_bd.add((str(num).strip(), str(ln).strip())) - except Exception: - pass - - valid_class_codes: Set[str] = set() - class_um_by_code: Dict[str, str] = {} - class_fraction_by_code: Dict[str, str] = {} - class_desc_es_by_code: Dict[str, str] = {} - class_desc_en_by_code: Dict[str, str] = {} - for c in session.query(Class).filter(Class.tenant_id == tenant_id, Class.company_id == company_id).all(): - code = (c.class_code or "").strip().upper() - if code: - valid_class_codes.add(code) - class_um_by_code[code] = (c.unit_of_measure or "").strip().upper() - class_fraction_by_code[code] = (c.fraction or "").strip() - class_desc_es_by_code[code] = (c.description_es or "").strip() - class_desc_en_by_code[code] = (c.description_en or "").strip() - - valid_uom_codes: Set[str] = set() - for u in session.query(UnitOfMeasure.code).filter(UnitOfMeasure.tenant_id == tenant_id, UnitOfMeasure.company_id == company_id).all(): - if u[0]: - valid_uom_codes.add((u[0] or "").strip().upper()) - - valid_bulks_codes: Set[str] = set() - for p in session.query(Package.key).filter(Package.tenant_id == tenant_id, Package.company_id == company_id).all(): - if p[0]: - valid_bulks_codes.add((p[0] or "").strip()) - - valid_country_keys: Set[str] = set() - for row in session.query(Country.m3_key, Country.ame_key).all(): - if row[0]: - valid_country_keys.add((row[0] or "").strip().upper()) - if row[1]: - valid_country_keys.add((row[1] or "").strip().upper()) - - valid_fraction_ame: Set[str] = set() # unused; SITAR resolve per row in validators - - authorized_sectors: Set[str] = set() - for row in session.query(Sector.key).filter( - Sector.authorized == True, - Sector.tenant_id == tenant_id, - Sector.company_id == company_id, - ).all(): - if row[0]: - authorized_sectors.add((row[0] or "").strip().upper()) - - valid_payment_methods: Set[str] = set() - for row in session.query(PaymentMethod.key).all(): - if row[0] is not None: - valid_payment_methods.add(str(row[0]).strip()) - - valid_valuation_methods: Set[str] = set() - for row in session.query(ValuationMethod.key).all(): - if row[0]: - valid_valuation_methods.add((row[0] or "").strip()) - - company = session.query(Company).filter(Company.id == company_id).first() - company_has_prosec = bool(company.prosec) if company else False - company_rfc = (company.rfc or "").strip().upper() if company else "" - - valid_part_numbers: Set[str] = set() - for row in session.query(Part.part_number).filter(Part.tenant_id == tenant_id, Part.company_id == company_id).all(): - if row[0]: - valid_part_numbers.add((row[0] or "").strip().upper()) - - rfc_exception_updated: Set[str] = set() - rfc_exception_num_parte: Set[str] = set() - - with open(file_path, "r", encoding=_facturas_csv_encoding(file_path)) as f_in: - sample = f_in.read(2048) - f_in.seek(0) - try: - dialect = common_csv_reader.detect_csv_dialect(sample, delimiters=",;\t") - except Exception: - dialect = "excel" - reader = csv.DictReader(f_in, dialect=dialect) - rows_list = list(reader) - - invoice_numbers_from_csv = set() - for row in rows_list: - inv = (row.get("NUMERO FACTURA") or row.get("NUM FACTURA") or row.get("FACTURA") or "").strip() - if inv: - invoice_numbers_from_csv.add(inv) - if company_rfc in RFC_EXCEPTION_UPDATED: - rfc_exception_updated = invoice_numbers_from_csv - if company_rfc in RFC_EXCEPTION_NUM_PARTE: - rfc_exception_num_parte = invoice_numbers_from_csv - - line_counts_csv: Dict[Tuple[str, str], int] = {} - partidas_principales_csv: Set[Tuple[str, str]] = set() - - def _get_row_def(row_norm: Dict[str, Any], *keys: str) -> str: - for k in keys: - v = row_norm.get(k) - if v is not None and str(v).strip(): - return str(v).strip() - return "" - - for row in rows_list: - row_norm = row_from_template(row, "imp_def_details", normalize_header) - inv = _get_row_def(row_norm, "NUMERO FACTURA", "NUM FACTURA", "FACTURA") - linea = _get_row_def(row_norm, "LINEA", "RENGLON", "PARTIDA") - if inv and linea: - key = (inv, linea) - line_counts_csv[key] = line_counts_csv.get(key, 0) + 1 - u = _get_row_def(row_norm, "ES PARTIDA O SUBPARTIDA", "ESSUBPARTIDA").upper() - if u == "P" and inv and linea: - partidas_principales_csv.add((inv, linea)) - - error_count = 0 - processed_rows = 0 - error_lines_list = [] - errors_detail = [] - - with open(error_path, "w", encoding="utf-8") as f_err: - for i, row in enumerate(rows_list, start=1): - self.update_state(state="PROGRESS", meta={"current": i, "total": total_rows, "errors": error_count}) - row_norm = row_from_template(row, "imp_def_details", normalize_header) - row_errors = validate_row_partidas_impo_def( - row_norm, - i, - autonumerar=autonumerar, - actualizar=actualizar, - levantar_subpartidas=levantar_subpartidas, - calcular_costo_en_base_a_total=calcular_costo_en_base_a_total, - validar_decimales_pza=validar_decimales_pza, - invoice_id_by_number=invoice_id_by_number, - invoice_processed_by_number=invoice_processed_by_number, - rfc_exception_updated=rfc_exception_updated, - existing_line_keys_by_invoice=existing_line_keys_by_invoice, - line_counts_csv=line_counts_csv, - partidas_principales_csv=partidas_principales_csv, - partidas_principales_bd=partidas_principales_bd, - valid_class_codes=valid_class_codes, - class_um_by_code=class_um_by_code, - class_fraction_by_code=class_fraction_by_code, - class_desc_es_by_code=class_desc_es_by_code, - class_desc_en_by_code=class_desc_en_by_code, - valid_uom_codes=valid_uom_codes, - valid_bulks_codes=valid_bulks_codes, - valid_country_keys=valid_country_keys, - valid_fraction_ame=valid_fraction_ame, - valid_payment_methods=valid_payment_methods, - valid_valuation_methods=valid_valuation_methods, - authorized_sectors=authorized_sectors, - company_has_prosec=company_has_prosec, - rfc_exception_num_parte=rfc_exception_num_parte or None, - valid_part_numbers=valid_part_numbers, - warnings=None, - ) - for e in row_errors: - f_err.write(json.dumps(_invoice_scan_error_row_payload(e)) + "\n") - blocking = [e for e in row_errors if not e.get("warning")] - if blocking: - error_lines_list.append(i) - for e in row_errors: - if len(errors_detail) < 5000: - errors_detail.append(_invoice_scan_error_row_payload(e)) - processed_rows += 1 - - error_count = len(error_lines_list) - common_storage.store_error_lines(effective_job_type, job_id, error_lines_list) - return common_responses.scan_result(job_id, processed_rows, error_count, errors_detail) - except Exception as e: - logger.exception("Partidas importación definitiva scan failed: %s", e) - return {"status": "failed", "error": str(e)} - - # --- Partidas Exportación Definitiva: Clarion VALIDA_TODA_PAR_EXPO / VALIDA_PARCIAL_PAR_EXPO / VALIDACIONES_PAR_EXPO --- - if model_target == "invoice_details" and template_id == "exp_def_partidas": - logger.info("Partidas expo scan: running validation for job %s", job_id) - try: - from api.v1.modules.a76.invoices.models import InvoiceHeader - from api.v1.modules.a76.items.models import LineItem - from api.v1.modules.a76.general_catalogs.units_of_measure.models import UnitOfMeasure - from api.v1.modules.a76.general_catalogs.packages.models import Package - from api.v1.modules.public.reference_data.payment_methods.models import PaymentMethod - from api.v1.modules.a76.general_catalogs.company.models import Company - from api.v1.modules.a76.parts.models import Part - from .validators.partidas_expo import validate_row_partidas_expo - - _fc = parse_footer_config(meta.get("footer_config")) - autonumerar = meta.get("autonumerar", True) - actualizar = meta.get("actualizar", False) - levantar_subpartidas = meta.get("levantar_subpartidas", False) - validar_decimales_pza = meta.get("validar_decimales_pza", False) - if _fc: - if "autonumerar" in _fc: - autonumerar = bool(_fc["autonumerar"]) - elif _fc.get("autonumber_partidas", "true") is not None: - autonumerar = str(_fc.get("autonumber_partidas", "true")).lower() in ("true", "1", "si", "sí", "yes") - if "actualizar" in _fc: - actualizar = bool(_fc["actualizar"]) - if "levantar_subpartidas" in _fc: - levantar_subpartidas = bool(_fc["levantar_subpartidas"]) - if "validar_decimales_pza" in _fc: - validar_decimales_pza = bool(_fc["validar_decimales_pza"]) - - RFC_EXCEPTION_UPDATED = set() - RFC_EXCEPTION_EGM = {"EGM0303257J1"} - - with scoped_core_db(tenant_id=tenant_id, company_id=company_id) as session: - # Resolver parámetros del sistema: validadecencant (decimales PZA) y validarseries (switch maestro) - settings = AppSettingsService.get_resolved_settings(session, int(tenant_id), int(company_id)) - gen_params = settings.get("ssisgen", {}) or settings.get("qsisgen", {}) - if str(gen_params.get("validadecencant", "0")).strip() == "1": - validar_decimales_pza = True - - company = session.query(Company).filter(Company.id == company_id).first() - company_rfc = (company.rfc or "").strip().upper() if company else "" - if company_rfc in RFC_EXCEPTION_EGM: - rfc_exception_egm = True - else: - rfc_exception_egm = False - - q_inv_expo = ( - session.query(InvoiceHeader.invoice_number, InvoiceHeader.id, InvoiceHeader.status) - .filter( - InvoiceHeader.tenant_id == tenant_id, - InvoiceHeader.company_id == company_id, - InvoiceHeader.operation_type == "exp", - ) - ) - invoice_id_by_number: Dict[str, int] = {} - invoice_processed_by_number: Dict[str, bool] = {} - for num, iid, is_upd in q_inv_expo.all(): - if num: - invoice_id_by_number[str(num).strip()] = iid - invoice_processed_by_number[str(num).strip()] = _is_invoice_processed_status(is_upd) - - existing_line_keys_by_invoice: Dict[str, Set[str]] = {} - q_li_expo = ( - session.query(InvoiceHeader.invoice_number, LineItem.line_number) - .join(LineItem, LineItem.invoice_id == InvoiceHeader.id) - .filter( - InvoiceHeader.tenant_id == tenant_id, - InvoiceHeader.company_id == company_id, - InvoiceHeader.operation_type == "exp", - ) - ) - for num, ln in q_li_expo.all(): - if num is not None: - key = str(num).strip() - if key not in existing_line_keys_by_invoice: - existing_line_keys_by_invoice[key] = set() - existing_line_keys_by_invoice[key].add(str(ln).strip()) - - partidas_principales_bd: Set[Tuple[str, str]] = set() - try: - from api.v1.modules.a24.fa.fa_item_lines.models import FaLineItem - q_pp = ( - session.query(InvoiceHeader.invoice_number, LineItem.line_number) - .join(LineItem, LineItem.invoice_id == InvoiceHeader.id) - .join(FaLineItem, FaLineItem.id == LineItem.id) - .filter( - InvoiceHeader.tenant_id == tenant_id, - InvoiceHeader.company_id == company_id, - InvoiceHeader.operation_type == "exp", - FaLineItem.is_subitem == False, - FaLineItem.contains_subitems == True, - ) - ) - for num, ln in q_pp.all(): - if num is not None: - partidas_principales_bd.add((str(num).strip(), str(ln).strip())) - except Exception: - pass - - factura_impo_tem_by_number: Dict[str, int] = {} - q_tem = session.query(InvoiceHeader.invoice_number, InvoiceHeader.id).filter( - InvoiceHeader.tenant_id == tenant_id, - InvoiceHeader.company_id == company_id, - InvoiceHeader.operation_type == "imp", - InvoiceHeader.invoice_type == "TEM", - ) - for num, iid in q_tem.all(): - if num: - factura_impo_tem_by_number[str(num).strip()] = iid - - factura_impo_def_by_number: Dict[str, int] = {} - DEF_INVOICE_TYPES = ("DEF", "MATDE", "EXDEF") - q_def = session.query(InvoiceHeader.invoice_number, InvoiceHeader.id).filter( - InvoiceHeader.tenant_id == tenant_id, - InvoiceHeader.company_id == company_id, - InvoiceHeader.operation_type == "imp", - InvoiceHeader.invoice_type.in_(DEF_INVOICE_TYPES), - ) - for num, iid in q_def.all(): - if num: - factura_impo_def_by_number[str(num).strip()] = iid - - line_exists_tem: Set[Tuple[int, str]] = set() - q_li_tem = ( - session.query(LineItem.invoice_id, LineItem.line_number) - .join(InvoiceHeader, InvoiceHeader.id == LineItem.invoice_id) - .filter( - InvoiceHeader.tenant_id == tenant_id, - InvoiceHeader.company_id == company_id, - InvoiceHeader.operation_type == "imp", - InvoiceHeader.invoice_type == "TEM", - ) - ) - for inv_id, ln in q_li_tem.all(): - if inv_id is not None and ln is not None: - line_exists_tem.add((inv_id, str(ln).strip())) - - line_exists_def: Set[Tuple[int, str]] = set() - q_li_def = ( - session.query(LineItem.invoice_id, LineItem.line_number) - .join(InvoiceHeader, InvoiceHeader.id == LineItem.invoice_id) - .filter( - InvoiceHeader.tenant_id == tenant_id, - InvoiceHeader.company_id == company_id, - InvoiceHeader.operation_type == "imp", - InvoiceHeader.invoice_type.in_(DEF_INVOICE_TYPES), - ) - ) - for inv_id, ln in q_li_def.all(): - if inv_id is not None and ln is not None: - line_exists_def.add((inv_id, str(ln).strip())) - - valid_uom_codes: Set[str] = set() - for u in session.query(UnitOfMeasure.code).filter(UnitOfMeasure.tenant_id == tenant_id, UnitOfMeasure.company_id == company_id).all(): - if u[0]: - valid_uom_codes.add((u[0] or "").strip().upper()) - - valid_bulks_codes: Set[str] = set() - for p in session.query(Package.key).filter(Package.tenant_id == tenant_id, Package.company_id == company_id).all(): - if p[0]: - valid_bulks_codes.add((p[0] or "").strip()) - - valid_payment_methods: Set[str] = set() - for row in session.query(PaymentMethod.key).all(): - if row[0] is not None: - valid_payment_methods.add(str(row[0]).strip()) - - valid_fraction_ame: Set[str] = set() # unused; SITAR resolve per row in validators - - valid_part_numbers: Set[str] = set() - for row in session.query(Part.part_number).filter(Part.tenant_id == tenant_id, Part.company_id == company_id).all(): - if row[0]: - valid_part_numbers.add((row[0] or "").strip().upper()) - - invoice_numbers_from_csv = set() - with open(file_path, "r", encoding=_facturas_csv_encoding(file_path)) as f_in: - sample = f_in.read(2048) - f_in.seek(0) - try: - dialect = common_csv_reader.detect_csv_dialect(sample, delimiters=",;\t") - except Exception: - dialect = "excel" - reader = csv.DictReader(f_in, dialect=dialect) - rows_list = list(reader) - - for row in rows_list: - row_norm = row_from_template(row, "exp_def_partidas", normalize_header) - inv = (row_norm.get("NUMERO FACTURA EXPO") or row_norm.get("NUMERO FACTURA EXPO.") or row_norm.get("FACTURA EXPO") or "").strip() - if inv: - invoice_numbers_from_csv.add(inv) - if company_rfc in RFC_EXCEPTION_EGM: - rfc_exception_updated = invoice_numbers_from_csv - else: - rfc_exception_updated = set() - - line_counts_csv: Dict[Tuple[str, str], int] = {} - partidas_principales_csv: Set[Tuple[str, str]] = set() - - def _get_row_expo(row_norm: Dict[str, Any], *keys: str) -> str: - for k in keys: - v = row_norm.get(k) - if v is not None and str(v).strip(): - return str(v).strip() - return "" - - for row in rows_list: - row_norm = row_from_template(row, "exp_def_partidas", normalize_header) - inv = _get_row_expo(row_norm, "NUMERO FACTURA EXPO", "NUMERO FACTURA EXPO.", "FACTURA EXPO") - linea = _get_row_expo(row_norm, "LINEA EXPO", "LINEA EXPO.", "RENGLON EXPO") - if inv and linea: - key = (inv, linea) - line_counts_csv[key] = line_counts_csv.get(key, 0) + 1 - u = _get_row_expo(row_norm, "ES PARTIDA/SUBPARTIDA", "ESSUBPARTIDA", "ES PARTIDA O SUBPARTIDA").upper() - if u == "P" and inv and linea: - partidas_principales_csv.add((inv, linea)) - - error_count = 0 - processed_rows = 0 - error_lines_list = [] - errors_detail = [] - - with open(error_path, "w", encoding="utf-8") as f_err: - for i, row in enumerate(rows_list, start=1): - row_norm = row_from_template(row, "exp_def_partidas", normalize_header) - row_errors = validate_row_partidas_expo( - row_norm, - i, - autonumerar=autonumerar, - actualizar=actualizar, - levantar_subpartidas=levantar_subpartidas, - invoice_id_by_number=invoice_id_by_number, - invoice_processed_by_number=invoice_processed_by_number, - rfc_exception_updated=rfc_exception_updated, - existing_line_keys_by_invoice=existing_line_keys_by_invoice, - line_counts_csv=line_counts_csv, - partidas_principales_csv=partidas_principales_csv, - partidas_principales_bd=partidas_principales_bd, - valid_uom_codes=valid_uom_codes, - valid_bulks_codes=valid_bulks_codes, - valid_payment_methods=valid_payment_methods, - valid_fraction_ame=valid_fraction_ame, - valid_part_numbers=valid_part_numbers, - factura_impo_tem_by_number=factura_impo_tem_by_number, - factura_impo_def_by_number=factura_impo_def_by_number, - line_exists_tem=line_exists_tem, - line_exists_def=line_exists_def, - rfc_exception_egm=rfc_exception_egm, - validar_decimales_pza=validar_decimales_pza, - ) - for e in row_errors: - f_err.write(json.dumps(_invoice_scan_error_row_payload(e)) + "\n") - blocking = [e for e in row_errors if not e.get("warning")] - if blocking: - error_lines_list.append(i) - for e in row_errors: - if len(errors_detail) < 5000: - errors_detail.append(_invoice_scan_error_row_payload(e)) - processed_rows += 1 - - error_count = len(error_lines_list) - common_storage.store_error_lines(effective_job_type, job_id, error_lines_list) - return common_responses.scan_result( - job_id, processed_rows, error_count, errors_detail, total_rows_in_file=total_rows - ) - except Exception as e: - logger.exception("Partidas exportación definitiva scan failed: %s", e) - return {"status": "failed", "error": str(e)} - - # --- Partidas Compras Mexicanas: misma lógica que Impo Def, facturas MEX --- - if model_target == "invoice_details" and template_id == "cmex_details": - try: - from api.v1.modules.a76.invoices.models import InvoiceHeader - from api.v1.modules.a76.items.models import LineItem - from api.v1.modules.a76.classes.models import Class - from api.v1.modules.a76.general_catalogs.units_of_measure.models import UnitOfMeasure - from api.v1.modules.a76.general_catalogs.packages.models import Package - from api.v1.modules.public.reference_data.countries.models import Country - from api.v1.modules.a76.general_catalogs.sectors.models import Sector - from api.v1.modules.public.reference_data.valuation_methods.models import ValuationMethod - from api.v1.modules.public.reference_data.payment_methods.models import PaymentMethod - from api.v1.modules.a76.general_catalogs.company.models import Company - from api.v1.modules.a76.parts.models import Part - from .validators.partidas_impo_def import validate_row_partidas_impo_def - - _fc = parse_footer_config(meta.get("footer_config")) - autonumerar = meta.get("autonumerar", True) - actualizar = meta.get("actualizar", False) - levantar_subpartidas = meta.get("levantar_subpartidas", False) - calcular_costo_en_base_a_total = meta.get("calcular_costo_unitario_en_base_a_valor_total", False) - validar_decimales_pza = meta.get("validar_decimales_pza", False) - if _fc: - if "autonumerar" in _fc: - autonumerar = bool(_fc["autonumerar"]) - elif _fc.get("autonumber_partidas", "true") is not None: - autonumerar = str(_fc.get("autonumber_partidas", "true")).lower() in ("true", "1", "si", "sí", "yes") - if "actualizar" in _fc: - actualizar = bool(_fc["actualizar"]) - if "levantar_subpartidas" in _fc: - levantar_subpartidas = bool(_fc["levantar_subpartidas"]) - if "calcular_costo_unitario_en_base_a_valor_total" in _fc: - calcular_costo_en_base_a_total = bool(_fc["calcular_costo_unitario_en_base_a_valor_total"]) - if "validar_decimales_pza" in _fc: - validar_decimales_pza = bool(_fc["validar_decimales_pza"]) - - RFC_EXCEPTION_UPDATED = {"TPI121217SF6", "TCI170502858"} - RFC_EXCEPTION_NUM_PARTE = {"CTE980130518"} - - with scoped_core_db(tenant_id=tenant_id, company_id=company_id) as session: - # Resolver parámetros del sistema: validadecencant (decimales PZA) y validarseries (switch maestro) - settings = AppSettingsService.get_resolved_settings(session, int(tenant_id), int(company_id)) - gen_params = settings.get("ssisgen", {}) or settings.get("qsisgen", {}) - if str(gen_params.get("validadecencant", "0")).strip() == "1": - validar_decimales_pza = True - - q_inv = ( - session.query(InvoiceHeader.invoice_number, InvoiceHeader.id, InvoiceHeader.status) - .filter( - InvoiceHeader.tenant_id == tenant_id, - InvoiceHeader.company_id == company_id, - InvoiceHeader.operation_type == "imp", - InvoiceHeader.invoice_type == "MEX", - ) - ) - invoice_id_by_number: Dict[str, int] = {} - invoice_processed_by_number: Dict[str, bool] = {} - for num, iid, is_upd in q_inv.all(): - if num: - invoice_id_by_number[str(num).strip()] = iid - invoice_processed_by_number[str(num).strip()] = _is_invoice_processed_status(is_upd) - - existing_line_keys_by_invoice: Dict[str, Set[str]] = {} - q_li = ( - session.query(InvoiceHeader.invoice_number, LineItem.line_number) - .join(LineItem, LineItem.invoice_id == InvoiceHeader.id) - .filter( - InvoiceHeader.tenant_id == tenant_id, - InvoiceHeader.company_id == company_id, - InvoiceHeader.operation_type == "imp", - InvoiceHeader.invoice_type == "MEX", - ) - ) - for num, ln in q_li.all(): - if num is not None: - key = str(num).strip() - if key not in existing_line_keys_by_invoice: - existing_line_keys_by_invoice[key] = set() - existing_line_keys_by_invoice[key].add(str(ln).strip()) - - partidas_principales_bd: Set[Tuple[str, str]] = set() - try: - from api.v1.modules.a24.fa.fa_item_lines.models import FaLineItem - q_pp = ( - session.query(InvoiceHeader.invoice_number, LineItem.line_number) - .join(LineItem, LineItem.invoice_id == InvoiceHeader.id) - .join(FaLineItem, FaLineItem.id == LineItem.id) - .filter( - InvoiceHeader.tenant_id == tenant_id, - InvoiceHeader.company_id == company_id, - InvoiceHeader.operation_type == "imp", - InvoiceHeader.invoice_type == "MEX", - FaLineItem.is_subitem == False, - FaLineItem.contains_subitems == True, - ) - ) - for num, ln in q_pp.all(): - if num is not None: - partidas_principales_bd.add((str(num).strip(), str(ln).strip())) - except Exception: - pass - - valid_class_codes: Set[str] = set() - class_um_by_code: Dict[str, str] = {} - class_fraction_by_code: Dict[str, str] = {} - class_desc_es_by_code: Dict[str, str] = {} - class_desc_en_by_code: Dict[str, str] = {} - for c in session.query(Class).filter(Class.tenant_id == tenant_id, Class.company_id == company_id).all(): - code = (c.class_code or "").strip().upper() - if code: - valid_class_codes.add(code) - class_um_by_code[code] = (c.unit_of_measure or "").strip().upper() - class_fraction_by_code[code] = (c.fraction or "").strip() - class_desc_es_by_code[code] = (c.description_es or "").strip() - class_desc_en_by_code[code] = (c.description_en or "").strip() - - valid_uom_codes: Set[str] = set() - for u in session.query(UnitOfMeasure.code).filter(UnitOfMeasure.tenant_id == tenant_id, UnitOfMeasure.company_id == company_id).all(): - if u[0]: - valid_uom_codes.add((u[0] or "").strip().upper()) - - valid_bulks_codes: Set[str] = set() - for p in session.query(Package.key).filter(Package.tenant_id == tenant_id, Package.company_id == company_id).all(): - if p[0]: - valid_bulks_codes.add((p[0] or "").strip()) - - valid_country_keys: Set[str] = set() - for row in session.query(Country.m3_key, Country.ame_key).all(): - if row[0]: - valid_country_keys.add((row[0] or "").strip().upper()) - if row[1]: - valid_country_keys.add((row[1] or "").strip().upper()) - - valid_fraction_ame: Set[str] = set() # unused; SITAR resolve per row in validators - - authorized_sectors: Set[str] = set() - for row in session.query(Sector.key).filter( - Sector.authorized == True, - Sector.tenant_id == tenant_id, - Sector.company_id == company_id, - ).all(): - if row[0]: - authorized_sectors.add((row[0] or "").strip().upper()) - - valid_payment_methods: Set[str] = set() - for row in session.query(PaymentMethod.key).all(): - if row[0] is not None: - valid_payment_methods.add(str(row[0]).strip()) - - valid_valuation_methods: Set[str] = set() - for row in session.query(ValuationMethod.key).all(): - if row[0]: - valid_valuation_methods.add((row[0] or "").strip()) - - company = session.query(Company).filter(Company.id == company_id).first() - company_has_prosec = bool(company.prosec) if company else False - company_rfc = (company.rfc or "").strip().upper() if company else "" - - valid_part_numbers: Set[str] = set() - for row in session.query(Part.part_number).filter(Part.tenant_id == tenant_id, Part.company_id == company_id).all(): - if row[0]: - valid_part_numbers.add((row[0] or "").strip().upper()) - - rfc_exception_updated: Set[str] = set() - rfc_exception_num_parte: Set[str] = set() - - with open(file_path, "r", encoding=_facturas_csv_encoding(file_path)) as f_in: - sample = f_in.read(2048) - f_in.seek(0) - try: - dialect = common_csv_reader.detect_csv_dialect(sample, delimiters=",;\t") - except Exception: - dialect = "excel" - reader = csv.DictReader(f_in, dialect=dialect) - rows_list = list(reader) - - invoice_numbers_from_csv = set() - for row in rows_list: - inv = (row.get("NUMERO FACTURA") or row.get("NUM FACTURA") or row.get("FACTURA") or "").strip() - if inv: - invoice_numbers_from_csv.add(inv) - if company_rfc in RFC_EXCEPTION_UPDATED: - rfc_exception_updated = invoice_numbers_from_csv - if company_rfc in RFC_EXCEPTION_NUM_PARTE: - rfc_exception_num_parte = invoice_numbers_from_csv - - line_counts_csv: Dict[Tuple[str, str], int] = {} - partidas_principales_csv: Set[Tuple[str, str]] = set() - - def _get_row_cmex(row_norm: Dict[str, Any], *keys: str) -> str: - for k in keys: - v = row_norm.get(k) - if v is not None and str(v).strip(): - return str(v).strip() - return "" - - for row in rows_list: - row_norm = row_from_template(row, "cmex_details", normalize_header) - inv = _get_row_cmex(row_norm, "NUMERO FACTURA", "NUM FACTURA", "FACTURA") - linea = _get_row_cmex(row_norm, "LINEA", "RENGLON", "PARTIDA") - if inv and linea: - key = (inv, linea) - line_counts_csv[key] = line_counts_csv.get(key, 0) + 1 - u = _get_row_cmex(row_norm, "ES PARTIDA O SUBPARTIDA", "ESSUBPARTIDA").upper() - if u == "P" and inv and linea: - partidas_principales_csv.add((inv, linea)) - - error_count = 0 - processed_rows = 0 - error_lines_list = [] - errors_detail = [] - - with open(error_path, "w", encoding="utf-8") as f_err: - for i, row in enumerate(rows_list, start=1): - self.update_state(state="PROGRESS", meta={"current": i, "total": total_rows, "errors": error_count}) - row_norm = row_from_template(row, "cmex_details", normalize_header) - row_errors = validate_row_partidas_impo_def( - row_norm, - i, - autonumerar=autonumerar, - actualizar=actualizar, - levantar_subpartidas=levantar_subpartidas, - calcular_costo_en_base_a_total=calcular_costo_en_base_a_total, - validar_decimales_pza=validar_decimales_pza, - invoice_id_by_number=invoice_id_by_number, - invoice_processed_by_number=invoice_processed_by_number, - rfc_exception_updated=rfc_exception_updated, - existing_line_keys_by_invoice=existing_line_keys_by_invoice, - line_counts_csv=line_counts_csv, - partidas_principales_csv=partidas_principales_csv, - partidas_principales_bd=partidas_principales_bd, - valid_class_codes=valid_class_codes, - class_um_by_code=class_um_by_code, - class_fraction_by_code=class_fraction_by_code, - class_desc_es_by_code=class_desc_es_by_code, - class_desc_en_by_code=class_desc_en_by_code, - valid_uom_codes=valid_uom_codes, - valid_bulks_codes=valid_bulks_codes, - valid_country_keys=valid_country_keys, - valid_fraction_ame=valid_fraction_ame, - valid_payment_methods=valid_payment_methods, - valid_valuation_methods=valid_valuation_methods, - authorized_sectors=authorized_sectors, - company_has_prosec=company_has_prosec, - rfc_exception_num_parte=rfc_exception_num_parte or None, - valid_part_numbers=valid_part_numbers, - warnings=None, - catalog_label="Compras Mexicanas", - ) - for e in row_errors: - f_err.write(json.dumps(_invoice_scan_error_row_payload(e)) + "\n") - blocking = [e for e in row_errors if not e.get("warning")] - if blocking: - error_lines_list.append(i) - for e in row_errors: - if len(errors_detail) < 5000: - errors_detail.append(_invoice_scan_error_row_payload(e)) - processed_rows += 1 - - error_count = len(error_lines_list) - common_storage.store_error_lines(effective_job_type, job_id, error_lines_list) - return common_responses.scan_result(job_id, processed_rows, error_count, errors_detail) - except Exception as e: - logger.exception("Partidas Compras Mexicanas scan failed: %s", e) - return {"status": "failed", "error": str(e)} - - # --- Encabezados de Importación Temporal: flujo específico (Clarion VALIDA_TODA / VALIDA_PARCIAL) --- - if model_target == "invoice_header" and template_id == "imp_temp_header": - try: - from api.v1.modules.a76.invoices.models import InvoiceHeader, InvoiceComplianceMx, InvoiceFinancials - from api.v1.modules.a76.items.models import LineItem - from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos - from api.v1.modules.a76.pedmientos.models.pedimento_dates import PedimentoDates - from api.v1.modules.a76.clients_and_providers.models import ClientProvider - from api.v1.modules.a76.customs_brokers.models import CustomsBroker - from api.v1.modules.public.reference_data.customs_sections.models import CustomsSection - from api.v1.modules.public.reference_data.incoterms.models import Incoterm - from api.v1.modules.public.reference_data.currency_types.models import CurrencyType - from api.v1.modules.a76.general_catalogs.exchange_rate.models import ExchangeRate - from api.v1.modules.a76.transportation.transporters.models import Transporter - from .validators.encabezados_impo_temp import ( - REGIMENES_VALIDOS, - validate_row_encabezados_impo_temp, - parse_pedimento_col_a, - _pedimento_key_from_parsed, - _patente_from_agente_aduanal, - ) - from .validators.pedimento_resolution import resolve_pedimento_candidates - from .validators.transport_catalog import logistics_scan_row_errors - - def _ped_key_from_row(ped_str: str) -> Optional[str]: - parsed = parse_pedimento_col_a(ped_str) - if not parsed: - return None - return _pedimento_key_from_parsed(parsed[0], parsed[1], parsed[2]) - - _fc = parse_footer_config(meta.get("footer_config")) - actualizar = meta.get("actualizar", False) - autonumerar_remesas = meta.get("autonumerar_remesas", False) - control_remesa = bool(_fc.get("control_remesa", False)) - remesa_inicio = _fc.get("remesa_inicio") - remesa_fin = _fc.get("remesa_fin") - if remesa_inicio is not None: - try: - remesa_inicio = int(remesa_inicio) - except (TypeError, ValueError): - remesa_inicio = None - if remesa_fin is not None: - try: - remesa_fin = int(remesa_fin) - except (TypeError, ValueError): - remesa_fin = None - if _fc: - if "actualizar" in _fc: - actualizar = bool(_fc["actualizar"]) - if "autonumerar_remesas" in _fc: - autonumerar_remesas = bool(_fc["autonumerar_remesas"]) - date_format = _fc.get("dateFormat") or meta.get("date_format") - logger.warning(f"Using date format: {date_format} for job {job_id}") - - with scoped_core_db(tenant_id=tenant_id, company_id=company_id) as session: - q_inv = ( - session.query(InvoiceHeader.invoice_number, InvoiceHeader.id, InvoiceHeader.status) - .filter( - InvoiceHeader.tenant_id == tenant_id, - InvoiceHeader.company_id == company_id, - InvoiceHeader.operation_type == "imp", - InvoiceHeader.invoice_type == "TEM", - ) - ) - invoice_exists_by_number: Dict[str, bool] = {} - invoice_processed_by_number: Dict[str, bool] = {} - for num, iid, is_upd in q_inv.all(): - if num: - n = str(num).strip() - invoice_exists_by_number[n] = True - invoice_processed_by_number[n] = _is_invoice_processed_status(is_upd) - - logger.warning(f"Scan Job {job_id} started for tenant {tenant_id}, company {company_id}. Loaded {len(invoice_exists_by_number)} invoices for pre-check.") - - pedimento_rows: List[Dict[str, Any]] = [] - skipped_ped_catalog_empty = 0 - _seen_pedimento_catalog_ids: Set[int] = set() - for p_id, p_year, p_co, p_lic, p_num, p_op, p_reg, p_type, entry_date, end_date, start_date in ( - session.query( - Pedimentos.id, - Pedimentos.year, - Pedimentos.customs_office, - Pedimentos.license, - Pedimentos.pedimento_number, - Pedimentos.operation_type, - Pedimentos.regime, - Pedimentos.pedimento_type, - PedimentoDates.entry_date, - PedimentoDates.end_date, - PedimentoDates.start_date, - ) - .outerjoin(PedimentoDates, PedimentoDates.pedimento_id == Pedimentos.id) - .filter( - Pedimentos.tenant_id == tenant_id, - Pedimentos.company_id == company_id, - ) - .all() - ): - # Catálogo completo por empresa: el resolver debe ver todos los pedimentos. - # Si el alta vino como exp por error, igual se encuentra fila; validate_row_encabezados_impo_temp - # rechaza operation_type != IMP con mensaje explícito (no solo “no existe”). - if p_id in _seen_pedimento_catalog_ids: - continue - _seen_pedimento_catalog_ids.add(p_id) - tri = _normalize_catalog_pedimento_identity(p_co, p_lic, p_num) - if not tri: - skipped_ped_catalog_empty += 1 - continue - co, lic, num = tri - info = { - "id": p_id, - "customs_office": co, - "license": lic, - "pedimento_number": num, - "regime": (p_reg or "").strip(), - "operation_type": (getattr(p_op, "value", None) or str(p_op or "")).strip(), - "pedimento_type": (getattr(p_type, "value", None) or str(p_type or "")).strip(), - "entry_date": entry_date, - "end_date": end_date, - "start_date": start_date, - } - pedimento_rows.append(info) - logger.info( - "facturas_scan imp_temp_header pedimento_catalog job_id=%s tenant_id=%s company_id=%s " - "catalog_rows=%s skipped_empty_license_or_number=%s", - job_id, - tenant_id, - company_id, - len(pedimento_rows), - skipped_ped_catalog_empty, - ) - - remesa_por_pedimento_bd: Dict[str, Set[int]] = {} - q_rem = ( - session.query( - InvoiceComplianceMx.remesa, - Pedimentos.year, - Pedimentos.customs_office, - Pedimentos.license, - Pedimentos.pedimento_number, - ) - .join(Pedimentos, Pedimentos.id == InvoiceComplianceMx.pedimento_id) - .filter( - InvoiceComplianceMx.tenant_id == tenant_id, - InvoiceComplianceMx.company_id == company_id, - InvoiceComplianceMx.pedimento_id.isnot(None), - InvoiceComplianceMx.remesa.isnot(None), - ) - ) - for rem, y, co, lic, num in q_rem.all(): - if co and lic and num and rem is not None: - key = _pedimento_key_from_parsed( - (co or "").strip(), - (lic or "").strip(), - (num or "").strip(), - ) - if key not in remesa_por_pedimento_bd: - remesa_por_pedimento_bd[key] = set() - remesa_por_pedimento_bd[key].add(int(rem)) - - valid_provider_ids: Set[int] = set() - valid_sold_to_ids: Set[int] = set() - valid_shipped_to_ids: Set[int] = set() - valid_provider_short_names: Set[str] = set() - valid_sold_to_short_names: Set[str] = set() - valid_shipped_to_short_names: Set[str] = set() - for cp in session.query(ClientProvider.id, ClientProvider.short_name).filter( - ClientProvider.tenant_id == tenant_id, - ClientProvider.company_id == company_id, - ).all(): - valid_provider_ids.add(cp[0]) - valid_sold_to_ids.add(cp[0]) - valid_shipped_to_ids.add(cp[0]) - if cp[1] and str(cp[1]).strip(): - sn_upper = str(cp[1]).strip().upper() - valid_provider_short_names.add(sn_upper) - valid_sold_to_short_names.add(sn_upper) - valid_shipped_to_short_names.add(sn_upper) - - broker_rows = session.query(CustomsBroker.id, CustomsBroker.broker_key).filter( - CustomsBroker.tenant_id == tenant_id, - CustomsBroker.company_id == company_id, - ).all() - broker_lookup_scan = _build_customs_broker_lookup(broker_rows) - valid_broker_ids: Set[int] = set(broker_lookup_scan["id_set"]) - valid_broker_claves: Set[str] = set(broker_lookup_scan["key_to_id"].keys()) | set( - broker_lookup_scan["alias_to_id"].keys() - ) - - valid_transporter_keys: Set[str] = set() - for t in session.query(Transporter.transporter_key).filter( - Transporter.tenant_id == tenant_id, - Transporter.company_id == company_id, - ).all(): - if t[0]: - valid_transporter_keys.add((t[0] or "").strip().upper()) - - valid_incoterms: Set[str] = set() - for inc in session.query(Incoterm.code).all(): - if inc[0]: - valid_incoterms.add((inc[0] or "").strip().upper()) - - valid_aduana_codes: Set[str] = set() - for cs in session.query(CustomsSection.customs_code).all(): - if cs[0]: - valid_aduana_codes.add((cs[0] or "").strip()) - - valid_currency_codes: Set[str] = set() - for ct in session.query(CurrencyType.code).all(): - if ct[0]: - valid_currency_codes.add((ct[0] or "").strip().upper()) - - exchange_rate_by_date: Dict[str, Any] = {} - for er in session.query(ExchangeRate.date, ExchangeRate.value).filter( - ExchangeRate.tenant_id == tenant_id, - ExchangeRate.company_id == company_id, - ).all(): - if er[0] and er[1] is not None: - dk = er[0].strftime("%Y-%m-%d") if hasattr(er[0], "strftime") else str(er[0])[:10] - exchange_rate_by_date[dk] = er[1] - - invoice_has_partidas_by_number: Dict[str, bool] = {} - existing_tipo_moneda_by_number: Dict[str, str] = {} - q_li_count = ( - session.query(InvoiceHeader.invoice_number, func.count(LineItem.id)) - .join(LineItem, LineItem.invoice_id == InvoiceHeader.id) - .filter( - InvoiceHeader.tenant_id == tenant_id, - InvoiceHeader.company_id == company_id, - InvoiceHeader.operation_type == "imp", - InvoiceHeader.invoice_type == "TEM", - ) - .group_by(InvoiceHeader.invoice_number) - ) - for num, cnt in q_li_count.all(): - if num: - invoice_has_partidas_by_number[str(num).strip()] = cnt > 0 - q_fin = ( - session.query(InvoiceHeader.invoice_number, InvoiceFinancials.currency) - .join(InvoiceFinancials, InvoiceFinancials.invoice_id == InvoiceHeader.id) - .filter( - InvoiceHeader.tenant_id == tenant_id, - InvoiceHeader.company_id == company_id, - InvoiceHeader.operation_type == "imp", - InvoiceHeader.invoice_type == "TEM", - ) - ) - for num, cur in q_fin.all(): - if num and cur: - cur_str = (cur or "").strip().lower() - if cur_str == "foreign": - existing_tipo_moneda_by_number[str(num).strip()] = "ME" - elif cur_str == "local": - existing_tipo_moneda_by_number[str(num).strip()] = "MN" - else: - existing_tipo_moneda_by_number[str(num).strip()] = cur_str.upper()[:2] - - with open(file_path, "r", encoding=_facturas_csv_encoding(file_path)) as f_in: - sample = f_in.read(2048) - f_in.seek(0) - try: - dialect = common_csv_reader.detect_csv_dialect(sample, delimiters=",;\t") - except Exception: - dialect = "excel" - reader = csv.DictReader(f_in, dialect=dialect) - rows_list = list(reader) - - remesa_por_pedimento_csv: Dict[str, Dict[int, str]] = {} - for row in rows_list: - row_norm = row_from_template(row, "imp_temp_header", normalize_header) - ped = (row_norm.get("PEDIMENTO") or "").strip() - rem = row_norm.get("REMESA") - factura = (row_norm.get("NUMERO FACTURA") or row_norm.get("NUM FACTURA") or row_norm.get("FACTURA") or "").strip() - if not ped or not factura: - continue - key = _ped_key_from_row(ped) - if not key: - continue - try: - rem_int = int(rem) if rem is not None and str(rem).strip() else None - except (TypeError, ValueError): - rem_int = None - if rem_int is not None: - if key not in remesa_por_pedimento_csv: - remesa_por_pedimento_csv[key] = {} - if rem_int not in remesa_por_pedimento_csv[key]: - remesa_por_pedimento_csv[key][rem_int] = factura - - # Precheck de referencias críticas (misma resolución que validate_row: catálogo + aduana/patente). - precheck_errors: List[Dict[str, Any]] = [] - precheck_lines: Set[int] = set() - for i, row in enumerate(rows_list, start=1): - row_norm = row_from_template(row, "imp_temp_header", normalize_header) - ped_raw = (row_norm.get("PEDIMENTO") or "").strip() - inc_raw = (row_norm.get("CLAVE INCOTERM") or row_norm.get("INCOTERM") or "").strip().upper() - aduana_cruce = (row_norm.get("ADUANA DE CRUCE") or "").strip() - invoice_number = (row_norm.get("NUMERO FACTURA") or row_norm.get("NUM FACTURA") or row_norm.get("FACTURA") or "").strip() - invoice_date = parse_date(row_norm.get("FECHA FACTURA") or row_norm.get("FECHA"), date_format) - regimen_raw = (row_norm.get("REGIMEN") or row_norm.get("CLAVEDOCUMENTO") or "").strip().upper() - - if not invoice_number: - precheck_lines.add(i) - precheck_errors.append( - { - "line": i, - "col": "NUMERO FACTURA", - "msg": "Error: Número de factura faltante/inválido.", - "solution": "Completa NUMERO FACTURA para poder insertar/actualizar.", - "warning": False, - } - ) - elif not invoice_date and not (actualizar and invoice_exists_by_number.get(invoice_number)): - precheck_lines.add(i) - precheck_errors.append( - { - "line": i, - "col": "FECHA FACTURA", - "msg": "Error: Fecha de factura faltante/inválida.", - "solution": "Corrige FECHA FACTURA o usa update sobre una factura existente con fecha válida.", - "warning": False, - } - ) - - if inc_raw and inc_raw not in valid_incoterms: - precheck_lines.add(i) - precheck_errors.append( - { - "line": i, - "col": "CLAVE INCOTERM", - "msg": f"Error: La clave en CLAVE INCOTERM no existe en el Catálogo de INCOTERM ({inc_raw}).", - "solution": "Corrige la clave de INCOTERM o completa el catálogo antes de importar.", - "warning": False, - } - ) - - if ped_raw: - parsed_ped = parse_pedimento_col_a(ped_raw) - if not parsed_ped: - precheck_lines.add(i) - precheck_errors.append( - { - "line": i, - "col": "PEDIMENTO", - "msg": f"Error: Número de Pedimento: {ped_raw} formato inválido (use CC-LLLL-NNNNNNN).", - "solution": "Corrige el formato del pedimento en columna A o verifica el catálogo.", - "warning": False, - } - ) - else: - customs_office_p, license_p, num_p = parsed_ped - patente_lookup_pre = _patente_from_agente_aduanal(row_norm, license_p) - ped_chk = resolve_pedimento_candidates( - customs_office=customs_office_p, - license_val=license_p, - pedimento_number=num_p, - patente_lookup=patente_lookup_pre, - pedimento_rows=pedimento_rows, - ) - if ped_chk["status"] == "not_found": - not_found_reason = ped_chk.get("not_found_reason") - # Paridad con _validaciones_pedimento_remesa: cuando no hay match en catálogo - # pero el régimen es ITE/ITR, se permite capturar la factura como pendiente de asignar - # pedimento. En precheck solo se registra advertencia, no error bloqueante. - if not_found_reason == "no_catalog_match" and regimen_raw in REGIMENES_VALIDOS: - precheck_errors.append( - { - "line": i, - "col": "PEDIMENTO", - "msg": ( - f"Advertencia: Número de Pedimento: {ped_raw} no está aún en el Catálogo de " - "Pedimentos. Con régimen ITE/ITR la factura se importará pendiente de asignar " - "pedimento." - ), - "solution": ( - "Da de alta el pedimento en el catálogo cuando esté disponible o confirma la " - "carga para dejar la factura pendiente de asignar pedimento." - ), - "warning": True, - "not_found_reason": "no_catalog_match", - } - ) - else: - precheck_lines.add(i) - if not_found_reason == "customs_mismatch": - ped_msg = ( - f"Error: Pedimento {ped_raw} está en catálogo pero la aduana no coincide con la registrada. " - "Revise columna A y Aduana de cruce (Col.AB)." - ) - ped_sol = "Alinee la aduana del CSV con el alta del pedimento o corrija el catálogo." - elif not_found_reason == "license_or_patente_mismatch": - ped_msg = ( - f"Error: El número de pedimento aparece en el catálogo pero la patente/agente no coincide " - f"con el alta ({ped_raw}). Revise columna A y columna J (agente aduanal)." - ) - ped_sol = ( - "Alinee la patente del pedimento en columna A con el alta o corrija AGENTE ADUANAL (col. J)." - ) - else: - ped_msg = ( - f"Error: Número de Pedimento: {ped_raw} no existe en el Catálogo de Pedimentos." - ) - ped_sol = "Carga/corrige el pedimento antes de volver a importar facturas." - precheck_errors.append( - { - "line": i, - "col": "PEDIMENTO", - "msg": ped_msg, - "solution": ped_sol, - "warning": False, - "not_found_reason": not_found_reason, - } - ) - elif ped_chk["status"] == "ambiguous": - precheck_lines.add(i) - precheck_errors.append( - { - "line": i, - "col": "PEDIMENTO", - "msg": ( - f"Error: Número de Pedimento: {ped_raw} coincide con múltiples registros. " - "Valide aduana/patente para identificar un único pedimento." - ), - "solution": "Corrija duplicados en el catálogo o especifique datos que identifiquen un solo pedimento.", - "warning": False, - } - ) - # Solo es obligatorio si no viene en el pedimento de la columna A - if not aduana_cruce and not (parsed_ped and parsed_ped[0]): - precheck_lines.add(i) - precheck_errors.append( - { - "line": i, - "col": "ADUANA DE CRUCE", - "msg": "Error: Existen campos vacíos que son obligatorios: (Col.AB) Aduana de Cruce.", - "solution": "Completa ADUANA DE CRUCE en el CSV para filas con pedimento.", - "warning": False, - } - ) - - # No return temprano: el commit siempre valida por fila (logística, etc.). Si solo se - # devolvía el precheck, valid_rows del scan quedaba inflado vs inserted/skipped del commit. - scan_precheck_message: Optional[str] = None - if precheck_errors: - scan_precheck_message = ( - "Precheck de referencias falló. Corrige catálogos/pedimentos según los errores indicados. " - "Se aplicó también validación completa por fila (logística, etc.) para paridad con el commit." - ) - - processed_rows = 0 - error_lines_list: List[int] = [] - errors_detail: List[Dict[str, Any]] = [] - for e in precheck_errors: - if len(errors_detail) < 5000: - errors_detail.append(_invoice_scan_error_row_payload(e)) - - with scoped_core_db(tenant_id=tenant_id, company_id=company_id) as session: - with open(error_path, "w", encoding="utf-8") as f_err: - for e in precheck_errors: - f_err.write(json.dumps(_invoice_scan_error_row_payload(e)) + "\n") - for i, row in enumerate(rows_list, start=1): - self.update_state( - state="PROGRESS", - meta={ - "current": i, - "total": total_rows, - "errors": len( - _merge_unique_invoice_scan_error_lines( - precheck_lines, error_lines_list - ) - ), - }, - ) - row_norm = row_from_template(row, "imp_temp_header", normalize_header) - warnings_row: List[Dict[str, Any]] = [] - row_errors = validate_row_encabezados_impo_temp( - row_norm, - i, - actualizar=actualizar, - invoice_exists_by_number=invoice_exists_by_number, - invoice_processed_by_number=invoice_processed_by_number, - pedimento_rows=pedimento_rows, - remesa_por_pedimento_bd=remesa_por_pedimento_bd, - remesa_por_pedimento_csv=remesa_por_pedimento_csv, - valid_provider_ids=valid_provider_ids, - valid_sold_to_ids=valid_sold_to_ids, - valid_shipped_to_ids=valid_shipped_to_ids, - valid_provider_short_names=valid_provider_short_names, - valid_sold_to_short_names=valid_sold_to_short_names, - valid_shipped_to_short_names=valid_shipped_to_short_names, - valid_broker_ids=valid_broker_ids, - valid_broker_claves=valid_broker_claves, - valid_transporter_keys=valid_transporter_keys, - valid_incoterms=valid_incoterms, - valid_aduana_codes=valid_aduana_codes, - valid_currency_codes=valid_currency_codes, - exchange_rate_by_date=exchange_rate_by_date, - invoice_has_partidas_by_number=invoice_has_partidas_by_number, - existing_tipo_moneda_by_number=existing_tipo_moneda_by_number, - autonumerar_remesas=autonumerar_remesas, - control_remesa=control_remesa, - remesa_inicio=remesa_inicio, - remesa_fin=remesa_fin, - date_format=date_format, - parse_date_fn=parse_date, - warnings=warnings_row, - ) - row_errors.extend( - logistics_scan_row_errors(row_norm, session, tenant_id, company_id, i) - ) - blocking = [e for e in row_errors if not e.get("warning")] - if blocking: - error_lines_list.append(i) - # CSV/jsonl de errores: incluir bloqueantes y advertencias (paridad con errors_detail). - for e in row_errors + warnings_row: - f_err.write(json.dumps(_invoice_scan_error_row_payload(e)) + "\n") - for e in row_errors + warnings_row: - if len(errors_detail) < 5000: - errors_detail.append(_invoice_scan_error_row_payload(e)) - processed_rows += 1 - - # El commit usa líneas únicas para omitir filas; unir precheck + validación por fila. - unique_error_lines = _merge_unique_invoice_scan_error_lines(precheck_lines, error_lines_list) - error_count = len(unique_error_lines) - common_storage.store_error_lines(effective_job_type, job_id, unique_error_lines) - scan_message_final: Optional[str] = scan_precheck_message - if processed_rows > 0 and errors_detail: - reason_counts_header: Dict[str, int] = {} - for e in errors_detail: - if e.get("warning"): - continue - msg_h = str(e.get("msg", "")).strip() - if not msg_h: - continue - reason_counts_header[msg_h] = reason_counts_header.get(msg_h, 0) + 1 - if reason_counts_header: - top_reason_h, top_count_h = max(reason_counts_header.items(), key=lambda kv: kv[1]) - if top_count_h / max(processed_rows, 1) >= 0.8: - scan_message_final = ( - f"Causa dominante de rechazo ({top_count_h}/{processed_rows}): {top_reason_h}. " - f"contexto template_id={template_id}, invoice_type={inv_type_value}, operation_type={op_type_scan}." - ) - logger.warning("Scan encabezados impo temp causa dominante: %s", scan_message_final) - return common_responses.scan_result( - job_id, - processed_rows, - error_count, - errors_detail, - total_rows_in_file=total_rows, - message=scan_message_final, - ) - except Exception as e: - logger.exception("Encabezados importación temporal scan failed: %s", e) - return {"status": "failed", "error": str(e)} - - # --- Encabezados de Importación Definitiva: flujo específico (Clarion VALIDA_TODA_FACIMPO_DEF / VALIDA_PARCIAL) --- - if model_target == "invoice_header" and template_id == "imp_def_header": - try: - from api.v1.modules.a76.invoices.models import InvoiceHeader, InvoiceComplianceMx, InvoiceFinancials - from api.v1.modules.a76.items.models import LineItem - from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos - from api.v1.modules.a76.pedmientos.models.pedimento_dates import PedimentoDates - from api.v1.modules.a76.clients_and_providers.models import ClientProvider - from api.v1.modules.a76.customs_brokers.models import CustomsBroker - from api.v1.modules.public.reference_data.customs_sections.models import CustomsSection - from api.v1.modules.public.reference_data.incoterms.models import Incoterm - from api.v1.modules.public.reference_data.currency_types.models import CurrencyType - from api.v1.modules.a76.general_catalogs.exchange_rate.models import ExchangeRate - from api.v1.modules.a76.transportation.transporters.models import Transporter - from .validators.encabezados_impo_def import ( - validate_row_encabezados_impo_def, - parse_pedimento_col_a_impo_def, - ) - from .validators.encabezados_impo_temp import _pedimento_key_from_parsed - from .validators.transport_catalog import logistics_scan_row_errors - - DEF_INVOICE_TYPES = ("DEF", "MATDE", "EXDEF") - - def _ped_key_from_row_def(ped_str: str) -> Optional[str]: - parsed = parse_pedimento_col_a_impo_def(ped_str) - if not parsed: - return None - return _pedimento_key_from_parsed(parsed[0], parsed[1], parsed[2]) - - _fc = parse_footer_config(meta.get("footer_config")) - actualizar = meta.get("actualizar", False) - autonumerar_remesas = meta.get("autonumerar_remesas", False) - control_remesa = bool(_fc.get("control_remesa", False)) - remesa_inicio = _fc.get("remesa_inicio") - remesa_fin = _fc.get("remesa_fin") - if remesa_inicio is not None: - try: - remesa_inicio = int(remesa_inicio) - except (TypeError, ValueError): - remesa_inicio = None - if remesa_fin is not None: - try: - remesa_fin = int(remesa_fin) - except (TypeError, ValueError): - remesa_fin = None - if _fc: - if "actualizar" in _fc: - actualizar = bool(_fc["actualizar"]) - if "autonumerar_remesas" in _fc: - autonumerar_remesas = bool(_fc["autonumerar_remesas"]) - - with scoped_core_db(tenant_id=tenant_id, company_id=company_id) as session: - q_inv = ( - session.query(InvoiceHeader.invoice_number, InvoiceHeader.id, InvoiceHeader.status) - .filter( - InvoiceHeader.tenant_id == tenant_id, - InvoiceHeader.company_id == company_id, - InvoiceHeader.operation_type == "imp", - InvoiceHeader.invoice_type.in_(DEF_INVOICE_TYPES), - ) - ) - invoice_exists_by_number = {} - invoice_processed_by_number = {} - for num, iid, is_upd in q_inv.all(): - if num: - n = str(num).strip() - invoice_exists_by_number[n] = True - invoice_processed_by_number[n] = _is_invoice_processed_status(is_upd) - - pedimento_rows = [] - skipped_ped_def_catalog = 0 - for p_id, p_year, p_co, p_lic, p_num, p_op, p_reg, p_type, entry_date, end_date, start_date in ( - session.query( - Pedimentos.id, - Pedimentos.year, - Pedimentos.customs_office, - Pedimentos.license, - Pedimentos.pedimento_number, - Pedimentos.operation_type, - Pedimentos.regime, - Pedimentos.pedimento_type, - PedimentoDates.entry_date, - PedimentoDates.end_date, - PedimentoDates.start_date, - ) - .outerjoin(PedimentoDates, PedimentoDates.pedimento_id == Pedimentos.id) - .filter( - Pedimentos.tenant_id == tenant_id, - Pedimentos.company_id == company_id, - Pedimentos.operation_type == "imp", - ) - .all() - ): - tri = _normalize_catalog_pedimento_identity(p_co, p_lic, p_num) - if not tri: - skipped_ped_def_catalog += 1 - continue - co, lic, num = tri - info = { - "id": p_id, - "customs_office": co, - "license": lic, - "pedimento_number": num, - "regime": (p_reg or "").strip(), - "operation_type": (getattr(p_op, "value", None) or str(p_op or "")).strip(), - "pedimento_type": (getattr(p_type, "value", None) or str(p_type or "")).strip(), - "entry_date": entry_date, - "end_date": end_date, - "start_date": start_date, - } - pedimento_rows.append(info) - logger.info( - "facturas_scan imp_def_header pedimento_catalog job_id=%s tenant_id=%s company_id=%s " - "catalog_rows=%s skipped_empty_license_or_number=%s", - job_id, - tenant_id, - company_id, - len(pedimento_rows), - skipped_ped_def_catalog, - ) - - remesa_por_pedimento_bd = {} - q_rem = ( - session.query( - InvoiceComplianceMx.remesa, - Pedimentos.customs_office, - Pedimentos.license, - Pedimentos.pedimento_number, - ) - .join(InvoiceHeader, InvoiceHeader.id == InvoiceComplianceMx.invoice_id) - .join(Pedimentos, Pedimentos.id == InvoiceComplianceMx.pedimento_id) - .filter( - InvoiceComplianceMx.tenant_id == tenant_id, - InvoiceComplianceMx.company_id == company_id, - InvoiceComplianceMx.pedimento_id.isnot(None), - InvoiceComplianceMx.remesa.isnot(None), - InvoiceHeader.invoice_type.in_(DEF_INVOICE_TYPES), - ) - ) - for rem, co, lic, num in q_rem.all(): - if co and lic and num and rem is not None: - key = _pedimento_key_from_parsed( - (co or "").strip(), - (lic or "").strip(), - (num or "").strip(), - ) - if key not in remesa_por_pedimento_bd: - remesa_por_pedimento_bd[key] = set() - remesa_por_pedimento_bd[key].add(int(rem)) - - valid_provider_ids = set() - valid_sold_to_ids = set() - valid_shipped_to_ids = set() - valid_provider_short_names = set() - valid_sold_to_short_names = set() - valid_shipped_to_short_names = set() - for cp in session.query(ClientProvider.id, ClientProvider.short_name).filter( - ClientProvider.tenant_id == tenant_id, - ClientProvider.company_id == company_id, - ).all(): - valid_provider_ids.add(cp[0]) - valid_sold_to_ids.add(cp[0]) - valid_shipped_to_ids.add(cp[0]) - if cp[1] and str(cp[1]).strip(): - sn_upper = str(cp[1]).strip().upper() - valid_provider_short_names.add(sn_upper) - valid_sold_to_short_names.add(sn_upper) - valid_shipped_to_short_names.add(sn_upper) - - broker_rows = session.query(CustomsBroker.id, CustomsBroker.broker_key).filter( - CustomsBroker.tenant_id == tenant_id, - CustomsBroker.company_id == company_id, - ).all() - broker_lookup_scan = _build_customs_broker_lookup(broker_rows) - valid_broker_ids = set(broker_lookup_scan["id_set"]) - valid_broker_claves = set(broker_lookup_scan["key_to_id"].keys()) | set( - broker_lookup_scan["alias_to_id"].keys() - ) - - valid_transporter_keys = set() - for t in session.query(Transporter.transporter_key).filter( - Transporter.tenant_id == tenant_id, - Transporter.company_id == company_id, - ).all(): - if t[0]: - valid_transporter_keys.add((t[0] or "").strip().upper()) - - valid_incoterms = set() - for inc in session.query(Incoterm.code).all(): - if inc[0]: - valid_incoterms.add((inc[0] or "").strip().upper()) - - valid_aduana_codes = set() - for cs in session.query(CustomsSection.customs_code).all(): - if cs[0]: - valid_aduana_codes.add((cs[0] or "").strip()) - - valid_currency_codes = set() - for ct in session.query(CurrencyType.code).all(): - if ct[0]: - valid_currency_codes.add((ct[0] or "").strip().upper()) - - exchange_rate_by_date = {} - for er in session.query(ExchangeRate.date, ExchangeRate.value).filter( - ExchangeRate.tenant_id == tenant_id, - ExchangeRate.company_id == company_id, - ).all(): - if er[0] and er[1] is not None: - dk = er[0].strftime("%Y-%m-%d") if hasattr(er[0], "strftime") else str(er[0])[:10] - exchange_rate_by_date[dk] = er[1] - - invoice_has_partidas_by_number = {} - existing_tipo_moneda_by_number = {} - q_li_count = ( - session.query(InvoiceHeader.invoice_number, func.count(LineItem.id)) - .join(LineItem, LineItem.invoice_id == InvoiceHeader.id) - .filter( - InvoiceHeader.tenant_id == tenant_id, - InvoiceHeader.company_id == company_id, - InvoiceHeader.operation_type == "imp", - InvoiceHeader.invoice_type.in_(DEF_INVOICE_TYPES), - ) - .group_by(InvoiceHeader.invoice_number) - ) - for num, cnt in q_li_count.all(): - if num: - invoice_has_partidas_by_number[str(num).strip()] = cnt > 0 - q_fin = ( - session.query(InvoiceHeader.invoice_number, InvoiceFinancials.currency) - .join(InvoiceFinancials, InvoiceFinancials.invoice_id == InvoiceHeader.id) - .filter( - InvoiceHeader.tenant_id == tenant_id, - InvoiceHeader.company_id == company_id, - InvoiceHeader.operation_type == "imp", - InvoiceHeader.invoice_type.in_(DEF_INVOICE_TYPES), - ) - ) - for num, cur in q_fin.all(): - if num and cur: - cur_str = (cur or "").strip().lower() - if cur_str == "foreign": - existing_tipo_moneda_by_number[str(num).strip()] = "ME" - elif cur_str == "local": - existing_tipo_moneda_by_number[str(num).strip()] = "MN" - else: - existing_tipo_moneda_by_number[str(num).strip()] = (cur_str or "").upper()[:2] - - with open(file_path, "r", encoding=_facturas_csv_encoding(file_path)) as f_in: - sample = f_in.read(2048) - f_in.seek(0) - try: - dialect = common_csv_reader.detect_csv_dialect(sample, delimiters=",;\t") - except Exception: - dialect = "excel" - reader = csv.DictReader(f_in, dialect=dialect) - rows_list = list(reader) - - remesa_por_pedimento_csv = {} - for row in rows_list: - row_norm = row_from_template(row, "imp_def_header", normalize_header) - ped = (row_norm.get("PEDIMENTO") or "").strip() - rem = row_norm.get("REMESA") - factura = (row_norm.get("NUMERO FACTURA") or row_norm.get("NUM FACTURA") or row_norm.get("FACTURA") or "").strip() - if not ped or not factura: - continue - key = _ped_key_from_row_def(ped) - if not key: - continue - try: - rem_int = int(rem) if rem is not None and str(rem).strip() else None - except (TypeError, ValueError): - rem_int = None - if rem_int is not None: - if key not in remesa_por_pedimento_csv: - remesa_por_pedimento_csv[key] = {} - if rem_int not in remesa_por_pedimento_csv[key]: - remesa_por_pedimento_csv[key][rem_int] = factura - - error_count = 0 - processed_rows = 0 - error_lines_list = [] - errors_detail = [] - - with open(error_path, "w", encoding="utf-8") as f_err: - for i, row in enumerate(rows_list, start=1): - self.update_state(state="PROGRESS", meta={"current": i, "total": total_rows, "errors": error_count}) - row_norm = row_from_template(row, "imp_def_header", normalize_header) - warnings_row = [] - row_errors = validate_row_encabezados_impo_def( - row_norm, - i, - actualizar=actualizar, - invoice_exists_by_number=invoice_exists_by_number, - invoice_processed_by_number=invoice_processed_by_number, - pedimento_rows=pedimento_rows, - remesa_por_pedimento_bd=remesa_por_pedimento_bd, - remesa_por_pedimento_csv=remesa_por_pedimento_csv, - valid_provider_ids=valid_provider_ids, - valid_sold_to_ids=valid_sold_to_ids, - valid_shipped_to_ids=valid_shipped_to_ids, - valid_provider_short_names=valid_provider_short_names, - valid_sold_to_short_names=valid_sold_to_short_names, - valid_shipped_to_short_names=valid_shipped_to_short_names, - valid_broker_ids=valid_broker_ids, - valid_broker_claves=valid_broker_claves, - valid_transporter_keys=valid_transporter_keys, - valid_incoterms=valid_incoterms, - valid_aduana_codes=valid_aduana_codes, - valid_currency_codes=valid_currency_codes, - exchange_rate_by_date=exchange_rate_by_date, - invoice_has_partidas_by_number=invoice_has_partidas_by_number, - existing_tipo_moneda_by_number=existing_tipo_moneda_by_number, - autonumerar_remesas=autonumerar_remesas, - control_remesa=control_remesa, - remesa_inicio=remesa_inicio, - remesa_fin=remesa_fin, - date_format=date_format, - parse_date_fn=parse_date, - warnings=warnings_row, - ) - row_errors.extend( - logistics_scan_row_errors(row_norm, session, tenant_id, company_id, i) - ) - blocking = [e for e in row_errors if not e.get("warning")] - if blocking: - error_lines_list.append(i) - for e in row_errors + warnings_row: - f_err.write(json.dumps(_invoice_scan_error_row_payload(e)) + "\n") - for e in row_errors + warnings_row: - if len(errors_detail) < 5000: - errors_detail.append(_invoice_scan_error_row_payload(e)) - processed_rows += 1 - - error_count = len(error_lines_list) - common_storage.store_error_lines(effective_job_type, job_id, error_lines_list) - return common_responses.scan_result( - job_id, processed_rows, error_count, errors_detail, total_rows_in_file=total_rows - ) - except Exception as e: - logger.exception("Encabezados importación definitiva scan failed: %s", e) - return {"status": "failed", "error": str(e)} - - # --- Encabezados Exportación (Expo Def) y Cambio de Régimen: flujo específico (Clarion VALIDA_TODA_FAC_EXPO / VALIDA_PARCIAL) --- - if model_target == "invoice_header" and template_id == "exp_def_header": - try: - from api.v1.modules.a76.invoices.models import InvoiceHeader, InvoiceComplianceMx, InvoiceFinancials - from api.v1.modules.a76.items.models import LineItem - from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos - from api.v1.modules.a76.pedmientos.models.pedimento_dates import PedimentoDates - from api.v1.modules.a76.clients_and_providers.models import ClientProvider - from api.v1.modules.a76.customs_brokers.models import CustomsBroker - from api.v1.modules.public.reference_data.customs_sections.models import CustomsSection - from api.v1.modules.public.reference_data.incoterms.models import Incoterm - from api.v1.modules.public.reference_data.currency_types.models import CurrencyType - from api.v1.modules.a76.general_catalogs.exchange_rate.models import ExchangeRate - from api.v1.modules.a76.transportation.transporters.models import Transporter - from api.v1.modules.a76.manifests.manifest.models import Manifest - from .validators.encabezados_expo import validate_row_encabezados_expo - from .validators.encabezados_impo_temp import _pedimento_key_from_parsed - from .validators.encabezados_impo_def import parse_pedimento_col_a_impo_def - from .validators.transport_catalog import logistics_scan_row_errors - - _fc = parse_footer_config(meta.get("footer_config")) - actualizar = meta.get("actualizar", False) - autonumerar_remesas = meta.get("autonumerar_remesas", False) - recalcular_fecha_pedimentos = meta.get("recalcular_fecha_pedimentos", False) - if _fc: - if "actualizar" in _fc: - actualizar = bool(_fc["actualizar"]) - if "autonumerar_remesas" in _fc: - autonumerar_remesas = bool(_fc["autonumerar_remesas"]) - if "recalcular_fecha_pedimentos" in _fc: - recalcular_fecha_pedimentos = bool(_fc["recalcular_fecha_pedimentos"]) - - cambio_regimen_raw = (meta.get("cambio_regimen") or _fc.get("cambio_regimen") or "NO").strip().upper() - cambio_regimen = cambio_regimen_raw == "SI" - tipo_factura = (meta.get("tipo_factura") or _fc.get("tipo_factura") or "AFIJO").strip().upper() - - TIPOS_FACTURA_EXPO_VALIDOS = frozenset({"NODES", "AFIJO", "DONAC", "SCRAP"}) - if tipo_factura not in TIPOS_FACTURA_EXPO_VALIDOS: - return { - "status": "failed", - "error": f"Tipo de factura '{tipo_factura}' no válido para Exportación. Debe ser uno de: NODES, AFIJO, DONAC, SCRAP.", - } - - if cambio_regimen and tipo_factura != "AFIJO": - return { - "status": "failed", - "error": "Este tipo de factura no es compatible para Cambio de Régimen, seleccionar AFIJO.", - } - - def _ped_key_from_row_expo(ped_str: str): - parsed = parse_pedimento_col_a_impo_def(ped_str) - if not parsed: - return None - return _pedimento_key_from_parsed(parsed[0], parsed[1], parsed[2]) - - with scoped_core_db(tenant_id=tenant_id, company_id=company_id) as session: - q_inv = ( - session.query(InvoiceHeader.invoice_number, InvoiceHeader.id, InvoiceHeader.status, InvoiceHeader.status_rep) - .filter( - InvoiceHeader.tenant_id == tenant_id, - InvoiceHeader.company_id == company_id, - InvoiceHeader.operation_type == "exp", - ) - ) - invoice_exists_by_number = {} - invoice_processed_by_number = {} - invoice_in_report_by_number = {} - for num, iid, is_upd, is_rep in q_inv.all(): - if num: - n = str(num).strip() - invoice_exists_by_number[n] = True - invoice_processed_by_number[n] = _is_invoice_processed_status(is_upd) - invoice_in_report_by_number[n] = bool(is_rep) if is_rep is not None else False - - if cambio_regimen: - ped_filter_op = "imp" - else: - ped_filter_op = "exp" - - pedimento_rows = [] - skipped_ped_expo_catalog = 0 - for p_id, p_co, p_lic, p_num, p_op, p_reg, p_type, p_code, entry_date, end_date, start_date in ( - session.query( - Pedimentos.id, - Pedimentos.customs_office, - Pedimentos.license, - Pedimentos.pedimento_number, - Pedimentos.operation_type, - Pedimentos.regime, - Pedimentos.pedimento_type, - Pedimentos.pedimento_code, - PedimentoDates.entry_date, - PedimentoDates.end_date, - PedimentoDates.start_date, - ) - .outerjoin(PedimentoDates, PedimentoDates.pedimento_id == Pedimentos.id) - .filter( - Pedimentos.tenant_id == tenant_id, - Pedimentos.company_id == company_id, - Pedimentos.operation_type == ped_filter_op, - ) - .all() - ): - tri = _normalize_catalog_pedimento_identity(p_co, p_lic, p_num) - if not tri: - skipped_ped_expo_catalog += 1 - continue - co, lic, num = tri - info = { - "id": p_id, - "customs_office": co, - "license": lic, - "pedimento_number": num, - "operation_type": (getattr(p_op, "value", None) or str(p_op or "")).strip(), - "regime": (p_reg or "").strip(), - "pedimento_type": (getattr(p_type, "value", None) or str(p_type or "")).strip(), - "pedimento_code": (p_code or "").strip(), - "entry_date": entry_date, - "end_date": end_date, - "start_date": start_date, - } - pedimento_rows.append(info) - logger.info( - "facturas_scan exp_def_header pedimento_catalog job_id=%s tenant_id=%s company_id=%s " - "filter_op=%s catalog_rows=%s skipped_empty_license_or_number=%s", - job_id, - tenant_id, - company_id, - ped_filter_op, - len(pedimento_rows), - skipped_ped_expo_catalog, - ) - - remesa_por_pedimento_bd = {} - q_rem = ( - session.query( - InvoiceComplianceMx.remesa, - Pedimentos.customs_office, - Pedimentos.license, - Pedimentos.pedimento_number, - ) - .join(InvoiceHeader, InvoiceHeader.id == InvoiceComplianceMx.invoice_id) - .join(Pedimentos, Pedimentos.id == InvoiceComplianceMx.pedimento_id) - .filter( - InvoiceComplianceMx.tenant_id == tenant_id, - InvoiceComplianceMx.company_id == company_id, - InvoiceComplianceMx.pedimento_id.isnot(None), - InvoiceComplianceMx.remesa.isnot(None), - InvoiceHeader.operation_type == "exp", - ) - ) - for rem, co, lic, num in q_rem.all(): - if co and lic and num and rem is not None: - key = _pedimento_key_from_parsed( - (co or "").strip()[:2], - (lic or "").strip(), - (num or "").strip(), - ) - if key not in remesa_por_pedimento_bd: - remesa_por_pedimento_bd[key] = set() - remesa_por_pedimento_bd[key].add(int(rem)) - - valid_provider_ids = set() - valid_sold_to_ids = set() - valid_shipped_to_ids = set() - valid_provider_short_names = set() - valid_sold_to_short_names = set() - valid_shipped_to_short_names = set() - for cp in session.query(ClientProvider.id, ClientProvider.short_name).filter( - ClientProvider.tenant_id == tenant_id, - ClientProvider.company_id == company_id, - ).all(): - valid_provider_ids.add(cp[0]) - valid_sold_to_ids.add(cp[0]) - valid_shipped_to_ids.add(cp[0]) - if cp[1] and str(cp[1]).strip(): - sn_upper = str(cp[1]).strip().upper() - valid_provider_short_names.add(sn_upper) - valid_sold_to_short_names.add(sn_upper) - valid_shipped_to_short_names.add(sn_upper) - - broker_rows = session.query(CustomsBroker.id, CustomsBroker.broker_key).filter( - CustomsBroker.tenant_id == tenant_id, - CustomsBroker.company_id == company_id, - ).all() - broker_lookup_scan = _build_customs_broker_lookup(broker_rows) - valid_broker_ids = set(broker_lookup_scan["id_set"]) - valid_broker_claves = set(broker_lookup_scan["key_to_id"].keys()) | set( - broker_lookup_scan["alias_to_id"].keys() - ) - - valid_transporter_keys = set() - for t in session.query(Transporter.transporter_key).filter( - Transporter.tenant_id == tenant_id, - Transporter.company_id == company_id, - ).all(): - if t[0]: - valid_transporter_keys.add((t[0] or "").strip().upper()) - - valid_incoterms = set() - for inc in session.query(Incoterm.code).all(): - if inc[0]: - valid_incoterms.add((inc[0] or "").strip().upper()) - - valid_aduana_codes = set() - for cs in session.query(CustomsSection.customs_code).all(): - if cs[0]: - valid_aduana_codes.add((cs[0] or "").strip()) - - valid_currency_codes = set() - for ct in session.query(CurrencyType.code).all(): - if ct[0]: - valid_currency_codes.add((ct[0] or "").strip().upper()) - - valid_manifiesto_codes = set() - for m in session.query(Manifest.manifest_number).filter( - Manifest.tenant_id == tenant_id, - Manifest.company_id == company_id, - ).all(): - if m[0] and str(m[0]).strip(): - valid_manifiesto_codes.add(str(m[0]).strip()) - - exchange_rate_by_date = {} - for er in session.query(ExchangeRate.date, ExchangeRate.value).filter( - ExchangeRate.tenant_id == tenant_id, - ExchangeRate.company_id == company_id, - ).all(): - if er[0] and er[1] is not None: - dk = er[0].strftime("%Y-%m-%d") if hasattr(er[0], "strftime") else str(er[0])[:10] - exchange_rate_by_date[dk] = er[1] - - invoice_has_partidas_by_number = {} - existing_tipo_moneda_by_number = {} - q_li_count = ( - session.query(InvoiceHeader.invoice_number, func.count(LineItem.id)) - .join(LineItem, LineItem.invoice_id == InvoiceHeader.id) - .filter( - InvoiceHeader.tenant_id == tenant_id, - InvoiceHeader.company_id == company_id, - InvoiceHeader.operation_type == "exp", - ) - .group_by(InvoiceHeader.invoice_number) - ) - for num, cnt in q_li_count.all(): - if num: - invoice_has_partidas_by_number[str(num).strip()] = cnt > 0 - q_fin = ( - session.query(InvoiceHeader.invoice_number, InvoiceFinancials.currency) - .join(InvoiceFinancials, InvoiceFinancials.invoice_id == InvoiceHeader.id) - .filter( - InvoiceHeader.tenant_id == tenant_id, - InvoiceHeader.company_id == company_id, - InvoiceHeader.operation_type == "exp", - ) - ) - for num, cur in q_fin.all(): - if num and cur: - cur_str = (cur or "").strip().lower() - if cur_str == "foreign": - existing_tipo_moneda_by_number[str(num).strip()] = "ME" - elif cur_str == "local": - existing_tipo_moneda_by_number[str(num).strip()] = "MN" - else: - existing_tipo_moneda_by_number[str(num).strip()] = (cur_str or "").upper()[:2] - - date_format = _fc.get("dateFormat") or meta.get("date_format") - logger.warning(f"Using date format: {date_format} for job {job_id}") - - with open(file_path, "r", encoding=_facturas_csv_encoding(file_path)) as f_in: - sample = f_in.read(2048) - f_in.seek(0) - try: - dialect = common_csv_reader.detect_csv_dialect(sample, delimiters=",;\t") - except Exception: - dialect = "excel" - reader = csv.DictReader(f_in, dialect=dialect) - rows_list = list(reader) - - remesa_por_pedimento_csv = {} - for row in rows_list: - row_norm = row_from_template(row, "exp_def_header", normalize_header) - ped = (row_norm.get("PEDIMENTO") or "").strip() - rem = row_norm.get("REMESA") - factura = (row_norm.get("NUMERO FACTURA") or row_norm.get("NUM FACTURA") or row_norm.get("FACTURA") or "").strip() - if not ped or not factura: - continue - key = _ped_key_from_row_expo(ped) - if not key: - continue - try: - rem_int = int(rem) if rem is not None and str(rem).strip() else None - except (TypeError, ValueError): - rem_int = None - if rem_int is not None: - if key not in remesa_por_pedimento_csv: - remesa_por_pedimento_csv[key] = {} - if rem_int not in remesa_por_pedimento_csv[key]: - remesa_por_pedimento_csv[key][rem_int] = factura - - error_count = 0 - processed_rows = 0 - error_lines_list = [] - errors_detail = [] - - with open(error_path, "w", encoding="utf-8") as f_err: - for i, row in enumerate(rows_list, start=1): - self.update_state(state="PROGRESS", meta={"current": i, "total": total_rows, "errors": error_count}) - row_norm = row_from_template(row, "exp_def_header", normalize_header) - warnings_row = [] - row_errors = validate_row_encabezados_expo( - row_norm, - i, - actualizar=actualizar, - cambio_regimen=cambio_regimen, - tipo_factura=tipo_factura, - invoice_exists_by_number=invoice_exists_by_number, - invoice_processed_by_number=invoice_processed_by_number, - invoice_in_report_by_number=invoice_in_report_by_number, - pedimento_rows=pedimento_rows, - remesa_por_pedimento_bd=remesa_por_pedimento_bd, - remesa_por_pedimento_csv=remesa_por_pedimento_csv, - valid_provider_ids=valid_provider_ids, - valid_sold_to_ids=valid_sold_to_ids, - valid_shipped_to_ids=valid_shipped_to_ids, - valid_broker_ids=valid_broker_ids, - valid_broker_claves=valid_broker_claves, - valid_transporter_keys=valid_transporter_keys, - valid_incoterms=valid_incoterms, - valid_aduana_codes=valid_aduana_codes, - valid_currency_codes=valid_currency_codes, - valid_provider_short_names=valid_provider_short_names, - valid_sold_to_short_names=valid_sold_to_short_names, - valid_shipped_to_short_names=valid_shipped_to_short_names, - valid_manifiesto_codes=valid_manifiesto_codes, - valid_enviado_por_ids=valid_provider_ids, - valid_enviado_por_short_names=valid_provider_short_names, - exchange_rate_by_date=exchange_rate_by_date, - invoice_has_partidas_by_number=invoice_has_partidas_by_number, - existing_tipo_moneda_by_number=existing_tipo_moneda_by_number, - autonumerar_remesas=autonumerar_remesas, - recalcular_fecha_pedimentos=recalcular_fecha_pedimentos, - date_format=date_format, - parse_date_fn=parse_date, - warnings=warnings_row, - ) - row_errors.extend( - logistics_scan_row_errors(row_norm, session, tenant_id, company_id, i) - ) - blocking = [e for e in row_errors if not e.get("warning")] - if blocking: - error_lines_list.append(i) - for e in row_errors + warnings_row: - f_err.write(json.dumps(_invoice_scan_error_row_payload(e)) + "\n") - for e in row_errors + warnings_row: - if len(errors_detail) < 5000: - errors_detail.append(_invoice_scan_error_row_payload(e)) - processed_rows += 1 - - error_count = len(error_lines_list) - common_storage.store_error_lines(effective_job_type, job_id, error_lines_list) - return common_responses.scan_result( - job_id, processed_rows, error_count, errors_detail, total_rows_in_file=total_rows - ) - except Exception as e: - logger.exception("Encabezados exportación scan failed: %s", e) - return {"status": "failed", "error": str(e)} - - # --- Encabezados Compras Mexicanas: flujo específico (Clarion VALIDA_TODA_FAC_COM_MEX / VALIDA_PARCIAL) --- - if model_target == "invoice_header" and template_id == "cmex_header": - try: - from api.v1.modules.a76.invoices.models import InvoiceHeader, InvoiceFinancials - from api.v1.modules.a76.items.models import LineItem - from api.v1.modules.a76.clients_and_providers.models import ClientProvider - from api.v1.modules.public.reference_data.incoterms.models import Incoterm - from api.v1.modules.public.reference_data.currency_types.models import CurrencyType - from api.v1.modules.a76.general_catalogs.exchange_rate.models import ExchangeRate - from api.v1.modules.a76.transportation.transporters.models import Transporter - from .validators.encabezados_cmex import validate_row_encabezados_cmex - from .validators.transport_catalog import logistics_scan_row_errors - - _fc = parse_footer_config(meta.get("footer_config")) - actualizar = meta.get("actualizar", False) - if _fc and "actualizar" in _fc: - actualizar = bool(_fc["actualizar"]) - - with scoped_core_db(tenant_id=tenant_id, company_id=company_id) as session: - q_inv = ( - session.query(InvoiceHeader.invoice_number, InvoiceHeader.id, InvoiceHeader.status) - .filter( - InvoiceHeader.tenant_id == tenant_id, - InvoiceHeader.company_id == company_id, - InvoiceHeader.operation_type == "imp", - InvoiceHeader.invoice_type == "MEX", - ) - ) - invoice_exists_by_number = {} - invoice_processed_by_number = {} - for num, iid, is_upd in q_inv.all(): - if num: - n = str(num).strip() - invoice_exists_by_number[n] = True - invoice_processed_by_number[n] = _is_invoice_processed_status(is_upd) - - valid_provider_ids = set() - valid_sold_to_ids = set() - valid_shipped_to_ids = set() - valid_provider_short_names = set() - valid_sold_to_short_names = set() - valid_shipped_to_short_names = set() - for cp in session.query(ClientProvider.id, ClientProvider.short_name).filter( - ClientProvider.tenant_id == tenant_id, - ClientProvider.company_id == company_id, - ).all(): - valid_provider_ids.add(cp[0]) - valid_sold_to_ids.add(cp[0]) - valid_shipped_to_ids.add(cp[0]) - if cp[1] and str(cp[1]).strip(): - sn_upper = str(cp[1]).strip().upper() - valid_provider_short_names.add(sn_upper) - valid_sold_to_short_names.add(sn_upper) - valid_shipped_to_short_names.add(sn_upper) - - valid_transporter_keys = set() - for t in session.query(Transporter.transporter_key).filter( - Transporter.tenant_id == tenant_id, - Transporter.company_id == company_id, - ).all(): - if t[0]: - valid_transporter_keys.add((t[0] or "").strip().upper()) - - valid_incoterms = set() - for inc in session.query(Incoterm.code).all(): - if inc[0]: - valid_incoterms.add((inc[0] or "").strip().upper()) - - valid_currency_codes = set() - for ct in session.query(CurrencyType.code).all(): - if ct[0]: - valid_currency_codes.add((ct[0] or "").strip().upper()) - - exchange_rate_by_date = {} - for er in session.query(ExchangeRate.date, ExchangeRate.value).filter( - ExchangeRate.tenant_id == tenant_id, - ExchangeRate.company_id == company_id, - ).all(): - if er[0] and er[1] is not None: - dk = er[0].strftime("%Y-%m-%d") if hasattr(er[0], "strftime") else str(er[0])[:10] - exchange_rate_by_date[dk] = er[1] - - invoice_has_partidas_by_number = {} - existing_tipo_moneda_by_number = {} - q_li_count = ( - session.query(InvoiceHeader.invoice_number, func.count(LineItem.id)) - .join(LineItem, LineItem.invoice_id == InvoiceHeader.id) - .filter( - InvoiceHeader.tenant_id == tenant_id, - InvoiceHeader.company_id == company_id, - InvoiceHeader.operation_type == "imp", - InvoiceHeader.invoice_type == "MEX", - ) - .group_by(InvoiceHeader.invoice_number) - ) - for num, cnt in q_li_count.all(): - if num: - invoice_has_partidas_by_number[str(num).strip()] = cnt > 0 - q_fin = ( - session.query(InvoiceHeader.invoice_number, InvoiceFinancials.currency) - .join(InvoiceFinancials, InvoiceFinancials.invoice_id == InvoiceHeader.id) - .filter( - InvoiceHeader.tenant_id == tenant_id, - InvoiceHeader.company_id == company_id, - InvoiceHeader.operation_type == "imp", - InvoiceHeader.invoice_type == "MEX", - ) - ) - for num, cur in q_fin.all(): - if num and cur: - cur_str = (cur or "").strip().lower() - if cur_str == "foreign": - existing_tipo_moneda_by_number[str(num).strip()] = "ME" - elif cur_str == "local": - existing_tipo_moneda_by_number[str(num).strip()] = "MN" - else: - existing_tipo_moneda_by_number[str(num).strip()] = (cur_str or "").upper()[:2] - - with open(file_path, "r", encoding=_facturas_csv_encoding(file_path)) as f_in: - sample = f_in.read(2048) - f_in.seek(0) - try: - dialect = common_csv_reader.detect_csv_dialect(sample, delimiters=",;\t") - except Exception: - dialect = "excel" - reader = csv.DictReader(f_in, dialect=dialect) - rows_list = list(reader) - - error_count = 0 - processed_rows = 0 - error_lines_list = [] - errors_detail = [] - - with scoped_core_db(tenant_id=tenant_id, company_id=company_id) as session: - with open(error_path, "w", encoding="utf-8") as f_err: - for i, row in enumerate(rows_list, start=1): - self.update_state( - state="PROGRESS", - meta={"current": i, "total": total_rows, "errors": error_count}, - ) - row_norm = row_from_template(row, "cmex_header", normalize_header) - warnings_row = [] - row_errors = validate_row_encabezados_cmex( - row_norm, - i, - actualizar=actualizar, - invoice_exists_by_number=invoice_exists_by_number, - invoice_processed_by_number=invoice_processed_by_number, - valid_provider_ids=valid_provider_ids, - valid_sold_to_ids=valid_sold_to_ids, - valid_shipped_to_ids=valid_shipped_to_ids, - valid_transporter_keys=valid_transporter_keys, - valid_incoterms=valid_incoterms, - valid_currency_codes=valid_currency_codes, - exchange_rate_by_date=exchange_rate_by_date, - invoice_has_partidas_by_number=invoice_has_partidas_by_number, - existing_tipo_moneda_by_number=existing_tipo_moneda_by_number, - valid_provider_short_names=valid_provider_short_names, - valid_sold_to_short_names=valid_sold_to_short_names, - valid_shipped_to_short_names=valid_shipped_to_short_names, - date_format=date_format, - parse_date_fn=parse_date, - warnings=warnings_row, - ) - row_errors.extend( - logistics_scan_row_errors(row_norm, session, tenant_id, company_id, i) - ) - blocking = [e for e in row_errors if not e.get("warning")] - if blocking: - error_lines_list.append(i) - for e in row_errors + warnings_row: - f_err.write(json.dumps(_invoice_scan_error_row_payload(e)) + "\n") - for e in row_errors + warnings_row: - if len(errors_detail) < 5000: - errors_detail.append(_invoice_scan_error_row_payload(e)) - processed_rows += 1 - - error_count = len(error_lines_list) - common_storage.store_error_lines(effective_job_type, job_id, error_lines_list) - return common_responses.scan_result( - job_id, processed_rows, error_count, errors_detail, total_rows_in_file=total_rows - ) - except Exception as e: - logger.exception("Encabezados Compras Mexicanas scan failed: %s", e) - return {"status": "failed", "error": str(e)} - - try: - from api.v1.modules.a76.invoices.models import InvoiceHeader - from api.v1.modules.a76.clients_and_providers.models import ClientProvider - from api.v1.modules.a76.customs_brokers.models import CustomsBroker - from api.v1.modules.public.reference_data.currency_types.models import CurrencyType - from api.v1.modules.public.reference_data.pedimento_codes.models import PedimentoCode - from api.v1.modules.public.reference_data.code_pedimento_regimens.models import ( - CodePedimentoRegimen, - ) - from api.v1.modules.public.reference_data.pedimento_regimens.models import RegimenPedimento - from api.v1.modules.public.reference_data.invoice_types.models import InvoiceType - from api.v1.modules.public.reference_data.customs_sections.models import CustomsSection - from api.v1.modules.public.reference_data.incoterms.models import Incoterm - from api.v1.modules.a76.parts.models import Part - - models = { - "InvoiceHeader": InvoiceHeader, - "InvoiceType": InvoiceType, - "ClientProvider": ClientProvider, - "CustomsBroker": CustomsBroker, - "RegimenPedimento": RegimenPedimento, - "CodePedimentoRegimen": CodePedimentoRegimen, - "PedimentoCode": PedimentoCode, - "CurrencyType": CurrencyType, - "CustomsSection": CustomsSection, - "Incoterm": Incoterm, - "Part": Part, - } - - with scoped_core_db(tenant_id=tenant_id, company_id=company_id) as session, \ - open(file_path, 'r', encoding=_facturas_csv_encoding(file_path)) as f_in, \ - open(error_path, 'w', encoding='utf-8') as f_err: - validator = ForeignKeyValidator(session, tenant_id, company_id) - - def clear_existing_invoice_details(invoice_id: int) -> None: - """ - Reemplazo seguro por factura: evita bulk delete directo sobre item_lines - para que ORM aplique cascadas y no rompa FK en tablas hijas. - """ - existing_line_ids = [ - line_id - for (line_id,) in session.query(LineItem.id) - .filter(LineItem.invoice_id == invoice_id) - .all() - ] - if not existing_line_ids: - session.query(InvoiceSalesDetails).filter( - InvoiceSalesDetails.invoice_id == invoice_id - ).delete(synchronize_session=False) - return - - # Dependencia cross-schema fuera de cascada DB; se limpia explícitamente. - session.query(FaLineItem).filter( - FaLineItem.id.in_(existing_line_ids) - ).delete(synchronize_session=False) - - # Borrado por ORM para activar cascadas definidas en LineItem. - existing_lines = ( - session.query(LineItem) - .filter(LineItem.id.in_(existing_line_ids)) - .all() - ) - for existing_line in existing_lines: - session.delete(existing_line) - - session.query(InvoiceSalesDetails).filter( - InvoiceSalesDetails.invoice_id == invoice_id - ).delete(synchronize_session=False) - invoice_id_cache: Dict[str, Optional[int]] = {} - - # Detect Delimiter - sample = f_in.read(2048) - f_in.seek(0) - try: - dialect = common_csv_reader.detect_csv_dialect(sample, delimiters=",;\t") - except: - dialect = 'excel' - - reader = csv.DictReader(f_in, dialect=dialect) - - for i, row in enumerate(reader, start=1): - # Check for Progress Update - self.update_state(state='PROGRESS', meta={ - 'current': i, - 'total': total_rows, - 'errors': error_count - }) - - # Solo columnas de la plantilla (respetar plantilla tal cual) - row_norm = row_from_template(row, template_id, normalize_header) - row_errs = validate_row_strict( - row_norm, - model_target, - i, - date_format, - validator, - inv_type_value, - invoice_id_cache, - models, - ) - - if row_errs: - error_count += 1 - for e in row_errs: - f_err.write(json.dumps(_invoice_scan_error_row_payload(e)) + "\n") - for e in row_errs: - if len(errors_detail) < 5000: - errors_detail.append(_invoice_scan_error_row_payload(e)) - - processed_rows += 1 - - except Exception as e: - logger.error(f"Scan failed: {e}") - return {"status": "failed", "error": str(e)} - - # 4. Store error line numbers in Redis so insert_valid_rows can skip them (any worker) - error_lines_list = [] - try: - if os.path.exists(error_path): - with open(error_path, "r", encoding="utf-8") as f: - for line in f: - try: - err = json.loads(line) - if "line" in err: - error_lines_list.append(err["line"]) - except Exception: - pass - common_storage.store_error_lines(effective_job_type, job_id, error_lines_list) - except Exception as e: - logger.warning(f"Failed to store error lines in Redis: {e}") - - return common_responses.scan_result(job_id, processed_rows, error_count, errors_detail) - -def validate_row_phase_1( - row: Dict[str, Any], - target: str, - line_num: int, - date_format: Optional[str], -) -> Optional[Dict[str, Any]]: - """ - Validation: Unique IDs, Dates, and Numeric constraint checks. - Target: 'invoice_header' or 'invoice_details' - """ - def check_decimal(col_name): - val = row.get(col_name) - if val and str(val).strip(): - if parse_decimal(val) is None: - return { - "line": line_num, - "col": col_name, - "msg": "Debe ser un número decimal válido", - "solution": f"Capturar en la columna {col_name} un número decimal válido (ej. 12.34).", - } - return None - - def check_int(col_name): - val = row.get(col_name) - if val and str(val).strip(): - if parse_int(val) is None: - return { - "line": line_num, - "col": col_name, - "msg": "Debe ser un número entero válido", - "solution": f"Capturar en la columna {col_name} un número entero válido (solo números, sin letras).", - } - return None - - def check_date(col_name): - date_str = row.get(col_name) - if date_str and str(date_str).strip(): - if not is_valid_date(date_str, date_format): - expected = display_date_format(date_format) - return { - "line": line_num, - "col": col_name, - "msg": f"Formato de fecha inválido ({expected})", - "solution": f"Capturar una fecha con el formato esperado ({expected}).", - } - return None - - def check_weight(col_name): - val = row.get(col_name) - if val and str(val).strip(): - if parse_weight_unit(val) is None: - return { - "line": line_num, - "col": col_name, - "msg": "Unidad de peso inválida (ej. KGS, LBS)", - "solution": f"Capturar en la columna {col_name} una unidad de peso válida (ej. KGS o LBS).", - } - return None - - def check_currency(col_name): - val = row.get(col_name) - if val and str(val).strip(): - if csv_tipo_moneda_es_me_mn_mc(str(val)): - return None - parsed_currency = parse_currency(val, None) - val_norm = normalize_header(val) - # parse_currency returns MANUAL if unknown, so if it wasn't explicitly MANUAL, it's invalid - if parsed_currency.value == "manual" and "MANUAL" not in val_norm: - return { - "line": line_num, - "col": col_name, - "msg": "Moneda inválida (ej. MN, ME, USD, PESOS)", - "solution": f"Capturar en la columna {col_name} una moneda válida (ej. MN, ME, USD, PESOS) o usar 'MANUAL' si aplica.", - } - return None - - def check_transport_type(col_name): - val = row.get(col_name) - if val and str(val).strip(): - if str(val).strip().lower() not in TRANSPORT_TYPE_VALUES: - return { - "line": line_num, - "col": col_name, - "msg": "Tipo de transporte inválido (ej. box, truck, container)", - "solution": f"Capturar en la columna {col_name} un tipo de transporte válido (ej. box, truck, container).", - } - return None - - # A. Invoice Header - if target == 'invoice_header': - # 1. Unique ID - if not row.get('NUMERO FACTURA') and not row.get('NUM FACTURA') and not row.get('ID'): - return { - "line": line_num, - "col": "NUMERO FACTURA", - "msg": "Requerido", - "solution": "Capturar el número de Factura en la columna NUMERO FACTURA.", - } - - # 2. Date Format - date_str = row.get('FECHA FACTURA') or row.get('FECHA') - if not date_str or not str(date_str).strip(): - return { - "line": line_num, - "col": "FECHA FACTURA", - "msg": "Requerido", - "solution": "Capturar la FECHA FACTURA en la columna FECHA FACTURA.", - } - - err = check_date('FECHA FACTURA') or check_date('FECHA') - if err: return err - - err = check_date('FECHA EMISION') - if err: return err - - # 3. Numeric Fields - for col in ['TIPO DE CAMBIO', 'FLETES', 'VALOR SEGUROS', 'SEGUROS', 'EMBALAJES', 'OTROS INCREMENTABLES']: - err = check_decimal(col) - if err: return err - - # 4. Integer FKs (CLAVE PROVEEDOR/VENDIDO/ENVIADO aceptan short_name; AGENTE ADUANAL acepta clave; solo REMESA exige entero) - int_fk_cols = ['CLAVE PROVEEDOR', 'CLAVE VENDIDO A', 'CLAVE ENVIADO A', 'AGENTE ADUANAL', 'REMESA'] - if target == 'invoice_header': - int_fk_cols = ['REMESA'] # proveedor/vendido/enviado por short_name; agente aduanal por clave - for col in int_fk_cols: - err = check_int(col) - if err: return err - - # 5. Enums - for col in ['TIPO PESO']: - err = check_weight(col) - if err: return err - - for col in ['TIPO MONEDA']: - err = check_currency(col) - if err: return err - - for col in ['TIPO TRANSPORTE']: - err = check_transport_type(col) - if err: return err - - # B. Invoice Details (Parts) - elif target == 'invoice_details': - # 1. Line Number - if not row.get('LINEA') and not row.get('RENGLON') and not row.get('PARTIDA'): - return { - "line": line_num, - "col": "LINEA", - "msg": "Requerido", - "solution": "Capturar el número de línea en la columna LINEA.", - } - - # 2. Parent Link (Invoice Number) - if not (row.get('NUMERO FACTURA') or row.get('NUM FACTURA') or row.get('FACTURA')): - return { - "line": line_num, - "col": "NUMERO FACTURA", - "msg": "Requerido", - "solution": "Capturar el número de Factura en la columna NUMERO FACTURA.", - } - - # 3. Numeric Fields - for col in ['PRECIO UNITARIO', 'PRECIOUNITARIO', 'VALOR COMERCIAL', 'VALORCOMERCIAL', 'CANTIDAD']: - err = check_decimal(col) - if err: return err - - for col in ['CANTIDAD BULTOS', 'CANTIDADBULTOS', 'LINEA', 'RENGLON', 'PARTIDA']: - err = check_int(col) - if err: return err - - return None - - -def validate_row_strict( - row: Dict[str, Any], - target: str, - line_num: int, - date_format: Optional[str], - validator: ForeignKeyValidator, - inv_type_value: str, - invoice_id_cache: Dict[str, Optional[int]], - models: Dict[str, Any], -) -> List[Dict[str, Any]]: - """ - Devuelve lista vacía si la fila es válida; en caso contrario, todas las observaciones - detectadas (el CSV/jsonl de errores debe listar cada una). - """ - err = validate_row_phase_1(row, target, line_num, date_format) - if err: - return [err] - - InvoiceHeader = models["InvoiceHeader"] - InvoiceType = models["InvoiceType"] - ClientProvider = models["ClientProvider"] - CustomsBroker = models["CustomsBroker"] - RegimenPedimento = models["RegimenPedimento"] - CurrencyType = models["CurrencyType"] - CustomsSection = models["CustomsSection"] - Incoterm = models["Incoterm"] - Part = models["Part"] - - if target == "invoice_header": - if not validator.check_exists(InvoiceType, inv_type_value, field_name="key", is_public=True): - return [ - { - "line": line_num, - "col": "TIPO FACTURA", - "msg": "No existe en el catalogo", - "solution": "Capturar un TIPO FACTURA válido que exista en el catálogo.", - } - ] - - errs: List[Dict[str, Any]] = [] - - err = _validate_client_provider_ref( - validator, ClientProvider, row.get("CLAVE PROVEEDOR"), line_num, "CLAVE PROVEEDOR", required=True - ) - if err: - errs.append(err) - - err = _validate_client_provider_ref( - validator, ClientProvider, row.get("CLAVE VENDIDO A"), line_num, "CLAVE VENDIDO A", required=True - ) - if err: - errs.append(err) - - err = _validate_client_provider_ref( - validator, ClientProvider, row.get("CLAVE ENVIADO A"), line_num, "CLAVE ENVIADO A", required=True - ) - if err: - errs.append(err) - - err = _validate_customs_broker_ref( - validator, CustomsBroker, row.get("AGENTE ADUANAL"), line_num, "AGENTE ADUANAL", required=False - ) - if err: - errs.append(err) - - err = validate_public_code( - validator, - RegimenPedimento, - row.get("REGIMEN") or row.get("CLAVEDOCUMENTO"), - line_num, - "CLAVEDOCUMENTO", - ) - if err: - errs.append(err) - - err = validate_public_code( - validator, - CustomsSection, - row.get("ADUANA DE CRUCE"), - line_num, - "ADUANA DE CRUCE", - field_name="customs_code", - ) - if err: - errs.append(err) - - err = validate_public_code( - validator, - CurrencyType, - row.get("CLAVE MONEDA"), - line_num, - "CLAVE MONEDA", - ) - if err: - errs.append(err) - - err = validate_public_code( - validator, - Incoterm, - row.get("CLAVE INCOTERM"), - line_num, - "CLAVE INCOTERM", - ) - if err: - errs.append(err) - - from .validators.transport_catalog import logistics_scan_row_errors - - log_errs = logistics_scan_row_errors( - row, - validator.session, - validator.tenant_id, - validator.company_id, - line_num, - ) - errs.extend(log_errs) - - return errs - - if target == "invoice_details": - invoice_number = (row.get("NUMERO FACTURA") or row.get("NUM FACTURA") or row.get("FACTURA") or "").strip() - if not invoice_number: - return [ - { - "line": line_num, - "col": "NUMERO FACTURA", - "msg": "Requerido", - "solution": "Capturar el número de Factura en la columna NUMERO FACTURA.", - } - ] - - cache_key = f"{invoice_number}|{inv_type_value}" - if cache_key in invoice_id_cache: - invoice_id = invoice_id_cache[cache_key] - else: - invoice_id = ( - validator.session.query(InvoiceHeader.id) - .filter( - InvoiceHeader.tenant_id == validator.tenant_id, - InvoiceHeader.company_id == validator.company_id, - InvoiceHeader.invoice_number == invoice_number, - InvoiceHeader.invoice_type == inv_type_value, - ) - .scalar() - ) - invoice_id_cache[cache_key] = invoice_id - if not invoice_id: - return [ - { - "line": line_num, - "col": "NUMERO FACTURA", - "msg": "Factura no existe", - "solution": "Capturar un número de Factura que exista en el sistema (verificar NUMERO FACTURA).", - } - ] - - part_num = (row.get("NUMPARTE") or row.get("NUMERO PARTE") or "").strip() - if not part_num: - return [ - { - "line": line_num, - "col": "NUMPARTE", - "msg": "Requerido", - "solution": "Capturar el número de parte en la columna NUMPARTE.", - } - ] - if not validator.check_exists(Part, part_num, field_name="part_number"): - return [ - { - "line": line_num, - "col": "NUMPARTE", - "msg": "No existe en el catalogo", - "solution": "Capturar un número de parte que exista en el catálogo (verificar NUMPARTE).", - } - ] - - return [] - -def parse_footer_config(config: Optional[str]) -> Dict[str, Any]: - if not config: - return {} - try: - if isinstance(config, str): - return json.loads(config) - if isinstance(config, dict): - return config - except Exception: - return {} - return {} - - -def display_date_format(date_format: Optional[str]) -> str: - if not date_format: - return "YYYY-MM-DD" - return date_format.upper() - - -def parse_date(date_text: Optional[str], date_format: Optional[str]) -> Optional[datetime.date]: - if not date_text: - return None - raw = str(date_text).strip() - # Soporte para fechas numéricas (layouts legacy): - # - Clarion: días desde 1800-12-28 (ej. 75519) - # - Excel serial: días desde 1899-12-30 - if re.fullmatch(r"\d+(\.\d+)?", raw): - try: - serial = int(float(raw)) - if serial > 0: - clarion_date = datetime(1800, 12, 28).date() + timedelta(days=serial) - if 1900 <= clarion_date.year <= 2200: - return clarion_date - excel_date = datetime(1899, 12, 30).date() + timedelta(days=serial) - if 1900 <= excel_date.year <= 2200: - return excel_date - except Exception: - pass - candidates = [] - fmt_map = { - "dd/mm/yyyy": "%d/%m/%Y", - "mm/dd/yyyy": "%m/%d/%Y", - "yyyy-mm-dd": "%Y-%m-%d", - } - if date_format and date_format in fmt_map: - candidates.append(fmt_map[date_format]) - candidates.extend(["%Y-%m-%d", "%d/%m/%Y", "%m/%d/%Y"]) - for fmt in candidates: - try: - return datetime.strptime(raw, fmt).date() - except ValueError: - continue - return None - - -def is_valid_date(date_text: Optional[str], date_format: Optional[str]) -> bool: - return parse_date(date_text, date_format) is not None - - -def normalize_header(name: Optional[str]) -> str: - if not name: - return "" - name = unicodedata.normalize("NFKD", str(name)).upper() - name = "".join(ch for ch in name if not unicodedata.combining(ch)) - name = re.sub(r"[^A-Z0-9]+", " ", name) - return re.sub(r"\s+", " ", name).strip() - - -def normalize_row(row: Dict[str, Any]) -> Dict[str, Any]: - return {normalize_header(k): v for k, v in row.items()} - - -def parse_int(value: Any) -> Optional[int]: - if value is None: - return None - text = str(value).strip() - if not text: - return None - try: - return int(text) - except ValueError: - pass - try: - f = float(text.replace(",", "")) - if f == int(f): - return int(f) - return None - except ValueError: - return None - - -def parse_decimal(value: Any) -> Optional[Decimal]: - if value is None: - return None - text = str(value).strip() - if not text: - return None - text = text.replace(",", "") - try: - return Decimal(text) - except Exception: - return None - - -def decimal_or_zero(value: Any) -> Decimal: - """Return parsed decimal or Decimal('0') for CSV nulls/empty (vanilla default).""" - return parse_decimal(value) or Decimal("0") - - -def int_or_zero(value: Any) -> int: - """Return parsed int or 0 for CSV nulls/empty (vanilla default).""" - return parse_int(value) if parse_int(value) is not None else 0 - - -def parse_currency(value: Optional[str], currency_type: Optional[str]): - from api.v1.modules.a76.invoices.models import Currency - if value: - normalized = normalize_header(value) - if normalized in {"MN", "M N", "NACIONAL", "LOCAL", "PESOS", "PESO"}: - return Currency.LOCAL - if normalized in {"ME", "M E", "EXTRANJERA", "EXTRANJERO", "FOREIGN", "USD", "DOLAR", "DOLARES"}: - return Currency.FOREIGN - if "MANUAL" in normalized: - return Currency.MANUAL - # MC (moneda por clave): paridad con encabezados_impo_temp / InvoiceFinancials + CLAVE MONEDA - if normalized.replace(" ", "") == "MC": - if currency_type and str(currency_type).strip().upper() == "MXN": - return Currency.LOCAL - if currency_type: - return Currency.FOREIGN - return Currency.FOREIGN - if currency_type and str(currency_type).strip().upper() == "MXN": - return Currency.LOCAL - if currency_type: - return Currency.FOREIGN - return Currency.MANUAL - - -def parse_weight_unit(value: Optional[str]): - from api.v1.modules.a76.invoices.models import WeightUnit - if not value: - return None - normalized = normalize_header(value) - if normalized in {"KG", "KGS", "KILOS", "KILOGRAMOS"}: - return WeightUnit.KGS - if normalized in {"LB", "LBS", "LIBRAS"}: - return WeightUnit.LBS - return None - - -def resolve_tenant_fk_id( - session: Session, - model, - value: Optional[int], - tenant_id: int, - company_id: int, - cache: Dict[int, Optional[int]], -) -> Optional[int]: - if value is None: - return None - if value in cache: - return cache[value] - exists = ( - session.query(model.id) - .filter( - model.id == value, - model.tenant_id == tenant_id, - model.company_id == company_id, - ) - .scalar() - ) - cache[value] = value if exists is not None else None - return cache[value] - - -def resolve_client_provider_id( - session: Session, - model, - value: Any, - tenant_id: int, - company_id: int, - cache: Dict[Any, Optional[int]], -) -> Optional[int]: - """Resuelve ID de ClientProvider por id (entero) o por short_name (texto). value puede ser int o str.""" - if value is None or (isinstance(value, str) and not value.strip()): - return None - if value in cache: - return cache[value] - pid = parse_int(value) - if pid is not None: - found = ( - session.query(model.id) - .filter( - model.id == pid, - model.tenant_id == tenant_id, - model.company_id == company_id, - ) - .scalar() - ) - cache[value] = found - return found - short_norm = str(value).strip().upper() - if short_norm in cache: - return cache[short_norm] - found = ( - session.query(model.id) - .filter( - func.upper(model.short_name) == short_norm, - model.tenant_id == tenant_id, - model.company_id == company_id, - ) - .scalar() - ) - cache[value] = found - cache[short_norm] = found - return found - - -def resolve_customs_broker_id( - session: Session, - model, - value: Any, - tenant_id: int, - company_id: int, - cache: Dict[Any, Optional[int]], - broker_lookup: Optional[Dict[str, Any]] = None, -) -> Optional[int]: - """Resuelve ID de CustomsBroker por id (entero) o por broker_key (clave). value puede ser int o str.""" - if value is None or (isinstance(value, str) and not value.strip()): - return None - if broker_lookup is not None: - resolved_id, _mode, norm, _err = resolve_customs_broker_ref(value, broker_lookup) - cache[value] = resolved_id - if norm: - cache[norm] = resolved_id - return resolved_id - if value in cache: - return cache[value] - pid = parse_int(value) - if pid is not None: - # Primero resolvemos como ID; si no existe, seguimos como broker_key. - found = ( - session.query(model.id) - .filter( - model.id == pid, - model.tenant_id == tenant_id, - model.company_id == company_id, - ) - .scalar() - ) - if found is not None: - cache[value] = found - return found - clave = str(value).strip() - if clave in cache: - return cache[clave] - found = ( - session.query(model.id) - .filter( - model.broker_key == clave, - model.tenant_id == tenant_id, - model.company_id == company_id, - ) - .scalar() - ) - cache[value] = found - cache[clave] = found - return found - - -def resolve_public_code( - session: Session, - model, - column, - value: Optional[str], - cache: Dict[str, Optional[str]], -) -> Optional[str]: - if not value: - return None - normalized = str(value).strip().upper() - if not normalized: - return None - if normalized in cache: - return cache[normalized] - exists = session.query(column).filter(column == normalized).scalar() - cache[normalized] = normalized if exists is not None else None - return cache[normalized] - -@celery_app.task(bind=True) -def insert_valid_rows(self, job_id: str, model_target: str, job_type_override: Optional[str] = None): - """Pass 2: Re-read CSV, Skip Errors, Bulk Insert. Delegates to _do_insert_valid_rows.""" - return _do_insert_valid_rows(job_id, model_target, job_type_override) - - -def _do_insert_valid_rows(job_id: str, model_target: str, job_type_override: Optional[str] = None) -> Dict[str, Any]: - """ - Pass 2: Re-read CSV, Skip Errors, Bulk Insert. - File and meta are loaded from Redis if present (same as scan_file), so worker does not need shared filesystem. - When job_type_override is set (e.g. "exp" for Exportación), storage keys use that prefix. - """ - effective_job_type = job_type_override if job_type_override is not None else JOB_TYPE - log_prefix = "Exportación import" if effective_job_type else "Invoices import" - - logger.info("Starting Commit for job_id=%s target=%s", job_id, model_target) - - # Ensure we have the file on this worker: prefer Redis (so any worker can run commit) - file_path = common_storage.ensure_file_from_redis(effective_job_type, job_id, log_prefix) - if not file_path: - alt_path = common_storage.file_path_for_job(effective_job_type, job_id) - if not os.path.exists(alt_path): - return {"status": "failed", "error": "File not found (missing or expired). Please upload and confirm again."} - file_path = alt_path - else: - common_storage.ensure_meta_from_redis(effective_job_type, job_id, file_path, log_prefix) - _ensure_utf8_compatible_import_file(file_path) - - try: - tenant_id, company_id = common_meta.require_tenant_context(file_path) - except ValueError as e: - return {"status": "failed", "error": str(e)} - - logger.info( - "facturas_commit context job_id=%s tenant_id=%s company_id=%s target=%s", - job_id, - tenant_id, - company_id, - model_target, - ) - - meta = common_meta.load_meta(file_path) - meta_path = common_meta.get_meta_path(file_path) - error_path = common_storage.error_path_for_job(effective_job_type, job_id) - error_lines = common_storage.get_error_lines(effective_job_type, job_id, error_path) - - # Si el upload fue de series (template_id imp_temp_series o imp_def_series), usar flujo series aunque model_target venga mal - use_series_flow = ( - model_target == "invoice_series" - or meta.get("template_id") in ("imp_temp_series", "imp_def_series", "cmex_series", "exp_def_series") - ) - - _footer_for_series = parse_footer_config(meta.get("footer_config")) or {} - _inv_type_series = normalize_public_code(_footer_for_series.get("invoice_type") or meta.get("invoice_type") or "") - use_def_series_commit = ( - use_series_flow - and ( - meta.get("template_id") == "imp_def_series" - or meta.get("template_id") == "cmex_series" - or _inv_type_series in ("DEF", "MATDE", "EXDEF") - ) - ) - use_expo_series_commit = use_series_flow and meta.get("template_id") == "exp_def_series" - - # --- Series de Exportación Definitiva: commit (INSERT/UPDATE item_line_series para facturas exp) --- - if use_expo_series_commit: - try: - from api.v1.modules.a76.invoices.models import InvoiceHeader - from api.v1.modules.a76.items.models import LineItem - from api.v1.modules.a76.items.series.models import Serie - from .validators.series_expo import row_to_series_normalized_expo - - actualizar = meta.get("actualizar", False) - autonumerar = meta.get("autonumerar", True) - _fc = parse_footer_config(meta.get("footer_config")) - if _fc: - if "actualizar" in _fc: - actualizar = bool(_fc["actualizar"]) - if "autonumerar" in _fc: - autonumerar = bool(_fc["autonumerar"]) - else: - as_val = _fc.get("autonumber_series", "true") - autonumerar = str(as_val).lower() in ("true", "1", "si", "sí", "yes") - - with scoped_core_db(tenant_id=tenant_id, company_id=company_id) as session: - q_inv = ( - session.query(InvoiceHeader.invoice_number, InvoiceHeader.id) - .filter( - InvoiceHeader.tenant_id == tenant_id, - InvoiceHeader.company_id == company_id, - InvoiceHeader.operation_type == "exp", - ) - ) - invoice_id_by_number: Dict[str, int] = {} - for num, iid in q_inv.all(): - if num: - invoice_id_by_number[str(num).strip()] = iid - - inserted_count = 0 - updated_count = 0 - skipped_invalid = 0 - skipped_details: List[Dict[str, Any]] = [] - - with open(file_path, "r", encoding=_facturas_csv_encoding(file_path)) as f: - sample = f.read(2048) - f.seek(0) - try: - dialect = common_csv_reader.detect_csv_dialect(sample, delimiters=",;\t") - except Exception: - dialect = "excel" - reader = csv.DictReader(f, dialect=dialect) - - for i, row in enumerate(reader, start=1): - if i in error_lines: - skipped_invalid += 1 - continue - row_norm = row_from_template(row, "exp_def_series", normalize_header) - data = row_to_series_normalized_expo(row_norm) - invoice_number = data["NUMERO FACTURA"] - linea_factura = data["LINEA FACTURA"] - linea_serie = data["LINEA SERIE"] - - if not invoice_number or invoice_number not in invoice_id_by_number: - skipped_invalid += 1 - skipped_details.append({ - "line": i, - "invoice": invoice_number or "(vacío)", - "reason": "Factura de exportación no encontrada.", - "solution": "Capturar en el CSV un número de Factura de exportación que exista en el sistema (columna NUMERO FACTURA).", - }) - continue - invoice_id = invoice_id_by_number[invoice_number] - line_number_val = parse_int(linea_factura) - if line_number_val is None: - skipped_invalid += 1 - skipped_details.append({ - "line": i, - "invoice": invoice_number, - "reason": "LINEA FACTURA debe ser numérico.", - "solution": "Capturar en el CSV la LINEA FACTURA solo con números enteros.", - }) - continue - line_item = ( - session.query(LineItem) - .filter( - LineItem.invoice_id == invoice_id, - LineItem.line_number == line_number_val, - LineItem.tenant_id == tenant_id, - LineItem.company_id == company_id, - ) - .first() - ) - if not line_item: - line_numbers = [ - r[0] for r in - session.query(LineItem.line_number) - .filter( - LineItem.invoice_id == invoice_id, - LineItem.tenant_id == tenant_id, - LineItem.company_id == company_id, - ) - .order_by(LineItem.line_number) - .all() - ] - existing_str = ", ".join(str(n) for n in line_numbers) if line_numbers else "ninguna" - skipped_invalid += 1 - skipped_details.append({ - "line": i, - "invoice": invoice_number, - "reason": f"Partida línea {linea_factura} no existe en la factura. Partidas existentes: {existing_str}.", - "solution": "Revisar que la partida (LINEA FACTURA) exista en la factura indicada en el CSV.", - }) - continue - - if autonumerar: - max_row = ( - session.query(Serie.row) - .filter(Serie.line_item_id == line_item.id) - .order_by(Serie.row.desc()) - .limit(1) - .scalar() - ) - row_num = (max_row or 0) + 1 - else: - row_num = parse_int(linea_serie) - if row_num is None: - skipped_invalid += 1 - skipped_details.append({ - "line": i, - "invoice": invoice_number, - "reason": "LINEA SERIE debe ser numérico.", - "solution": "Capturar en el CSV la LINEA SERIE solo con números enteros.", - }) - continue - - existing_serie = ( - session.query(Serie) - .filter( - Serie.line_item_id == line_item.id, - Serie.row == row_num, - ) - .first() - ) - - if existing_serie: - if actualizar: - existing_serie.serial_numbers = data["SERIE"] or existing_serie.serial_numbers - existing_serie.model = data["MODELO"] or existing_serie.model - existing_serie.sub_model = data["SUB MODELO"] or existing_serie.sub_model - existing_serie.number_id = data["NUMERO ID"] or existing_serie.number_id - session.add(existing_serie) - updated_count += 1 - else: - skipped_invalid += 1 - skipped_details.append({ - "line": i, - "invoice": invoice_number, - "reason": "Serie ya existe (use actualizar).", - "solution": "La serie ya existe: activa la opción de Actualizar o ajusta tu carga para que actualice en lugar de crear.", - }) - else: - new_serie = Serie( - tenant_id=tenant_id, - company_id=company_id, - line_item_id=line_item.id, - row=row_num, - serial_numbers=data["SERIE"] or None, - model=data["MODELO"] or None, - sub_model=data["SUB MODELO"] or None, - number_id=data["NUMERO ID"] or None, - ) - session.add(new_serie) - inserted_count += 1 - - session.commit() - - common_storage.cleanup_import_job(effective_job_type, job_id, file_path=file_path, error_path=error_path, meta_path=meta_path) - status = "finished" if (inserted_count + updated_count) > 0 else ("warning" if skipped_invalid else "failed") - out = { - "status": status, - "inserted": inserted_count, - "updated": updated_count, - "skipped_invalid": skipped_invalid, - "skipped_missing_fk": 0, - "skipped_duplicate": 0, - "skipped_details": skipped_details, - "skipped_summary": _summarize_top_reasons(skipped_details), - } - if status == "failed": - out["error"] = "No hay registros válidos en el archivo CSV." - elif status == "warning" and skipped_invalid: - out["message"] = f"No se insertaron registros. {skipped_invalid} fueron rechazados." - return out - except Exception as e: - logger.exception("Series exportación definitiva commit failed: %s", e) - return {"status": "failed", "error": str(e)} - - # --- Series de Importación Definitiva: commit (INSERT/UPDATE item_line_series para facturas DEF o MEX) --- - if use_def_series_commit: - try: - from api.v1.modules.a76.invoices.models import InvoiceHeader - from api.v1.modules.a76.items.models import LineItem - from api.v1.modules.a76.items.series.models import Serie - from api.v1.modules.a76.items.line_quantities.models import LineQuantity - from .validators.series_impo_def import ( - validate_row_series_impo_def, - row_to_series_normalized_def, - ) - - _series_template_id = meta.get("template_id") - is_cmex_series = _series_template_id == "cmex_series" - SERIES_INV_TYPES = ("MEX",) if is_cmex_series else ("DEF", "MATDE", "EXDEF") - series_row_template_id = "cmex_series" if is_cmex_series else "imp_def_series" - - DEF_INVOICE_TYPES = ("DEF", "MATDE", "EXDEF") # keep for any legacy reference - actualizar = meta.get("actualizar", False) - autonumerar = meta.get("autonumerar", True) - validar_series_exception = meta.get("validar_series", False) - _fc = parse_footer_config(meta.get("footer_config")) - if _fc: - if "actualizar" in _fc: - actualizar = bool(_fc["actualizar"]) - elif _fc.get("mode") == "update": - actualizar = True - elif _fc.get("mode") == "replace": - actualizar = False - if "autonumerar" in _fc: - autonumerar = bool(_fc["autonumerar"]) - else: - as_val = _fc.get("autonumber_series", "true") - autonumerar = str(as_val).lower() in ("true", "1", "si", "sí", "yes") - if "validar_series" in _fc: - validar_series_exception = bool(_fc["validar_series"]) - - with scoped_core_db(tenant_id=tenant_id, company_id=company_id) as session: - # Switch maestro: validarseries desactiva toda la validación de series cuando = 0 - _sys_settings = AppSettingsService.get_resolved_settings(session, int(tenant_id), int(company_id)) - _gen_p = _sys_settings.get("ssisgen", {}) or _sys_settings.get("qsisgen", {}) - if str(_gen_p.get("validarseries", "0")).strip() != "1": - validar_series_exception = False - - q = ( - session.query(InvoiceHeader.invoice_number, InvoiceHeader.id, InvoiceHeader.status) - .filter( - InvoiceHeader.tenant_id == tenant_id, - InvoiceHeader.company_id == company_id, - InvoiceHeader.operation_type == "imp", - InvoiceHeader.invoice_type.in_(SERIES_INV_TYPES), - ) - ) - rows_inv = q.all() - invoice_id_by_number: Dict[str, int] = {} - invoice_processed_by_number: Dict[str, bool] = {} - for num, iid, is_upd in rows_inv: - if num: - invoice_id_by_number[str(num).strip()] = iid - invoice_processed_by_number[str(num).strip()] = _is_invoice_processed_status(is_upd) - - partida_max_series = {} - q_qty = ( - session.query( - InvoiceHeader.invoice_number, - LineItem.line_number, - LineQuantity.quantity, - ) - .join(LineItem, LineItem.invoice_id == InvoiceHeader.id) - .outerjoin(LineQuantity, LineQuantity.item_line_id == LineItem.id) - .filter( - InvoiceHeader.tenant_id == tenant_id, - InvoiceHeader.company_id == company_id, - InvoiceHeader.operation_type == "imp", - InvoiceHeader.invoice_type.in_(SERIES_INV_TYPES), - ) - ) - for num, ln, qty in q_qty.all(): - if num is not None and ln is not None: - key = (str(num).strip(), str(ln).strip()) - partida_max_series[key] = int(qty) if qty else 0 - - existing_series_keys = set() - existing_series_data: Dict[Tuple[str, str, str], Dict[str, Any]] = {} - if actualizar and not autonumerar: - q_ser = ( - session.query( - InvoiceHeader.invoice_number, - LineItem.line_number, - Serie.row, - Serie.serial_numbers, - Serie.model, - Serie.sub_model, - Serie.number_id, - ) - .join(LineItem, LineItem.invoice_id == InvoiceHeader.id) - .join(Serie, Serie.line_item_id == LineItem.id) - .filter( - InvoiceHeader.tenant_id == tenant_id, - InvoiceHeader.company_id == company_id, - InvoiceHeader.operation_type == "imp", - InvoiceHeader.invoice_type.in_(SERIES_INV_TYPES), - ) - ) - for num, ln, rw, sn, md, sm, nid in q_ser.all(): - if num is not None: - k = (str(num).strip(), str(ln).strip(), str(rw).strip()) - existing_series_keys.add(k) - existing_series_data.setdefault(k, { - "serial_numbers": sn or "", - "model": md or "", - "sub_model": sm or "", - "number_id": nid or "", - }) - - csv_series_count_so_far: Dict[Tuple[str, str], int] = {} - inserted_count = 0 - updated_count = 0 - skipped_invalid = 0 - skipped_details: List[Dict[str, Any]] = [] - - with open(file_path, "r", encoding=_facturas_csv_encoding(file_path)) as f: - sample = f.read(2048) - f.seek(0) - try: - dialect = common_csv_reader.detect_csv_dialect(sample, delimiters=",;\t") - except Exception: - dialect = "excel" - reader = csv.DictReader(f, dialect=dialect) - - for i, row in enumerate(reader, start=1): - if i in error_lines: - continue - row_norm = row_from_template(row, series_row_template_id, normalize_header) - row_errors = validate_row_series_impo_def( - row_norm, - i, - actualizar=actualizar, - autonumerar=autonumerar, - validar_series_exception=validar_series_exception, - invoice_id_by_number=invoice_id_by_number, - invoice_processed_by_number=invoice_processed_by_number, - partida_max_series=partida_max_series, - csv_series_count_so_far=csv_series_count_so_far, - existing_series_keys=existing_series_keys, - existing_series_data=existing_series_data, - warnings=None, - catalog_label="Compras Mexicanas" if is_cmex_series else "Importación Definitiva", - ) - _blocking = [e for e in row_errors if not e.get("warning")] - if _blocking: - skipped_invalid += 1 - for blocking in _blocking: - skipped_details.append({ - "line": i, - "invoice": (row_norm.get("NUMERO FACTURA") or row_norm.get("NUM FACTURA") or "").strip(), - "reason": blocking.get("msg", ""), - "solution": blocking.get("solution", ""), - }) - continue - - data = row_to_series_normalized_def(row_norm) - invoice_number = data["NUMERO FACTURA"] - linea_factura = data["LINEA FACTURA"] - linea_serie = data["LINEA SERIE"] - - if not invoice_number or invoice_number not in invoice_id_by_number: - skipped_invalid += 1 - continue - invoice_id = invoice_id_by_number[invoice_number] - line_number_val = parse_int(linea_factura) - if line_number_val is None: - skipped_invalid += 1 - skipped_details.append({ - "line": i, - "invoice": invoice_number, - "reason": "LINEA FACTURA debe ser numérico.", - "solution": "Capturar en el CSV la LINEA FACTURA solo con números enteros.", - }) - continue - line_item = ( - session.query(LineItem) - .filter( - LineItem.invoice_id == invoice_id, - LineItem.line_number == line_number_val, - LineItem.tenant_id == tenant_id, - LineItem.company_id == company_id, - ) - .first() - ) - if not line_item: - line_numbers = [ - r[0] for r in - session.query(LineItem.line_number) - .filter( - LineItem.invoice_id == invoice_id, - LineItem.tenant_id == tenant_id, - LineItem.company_id == company_id, - ) - .order_by(LineItem.line_number) - .all() - ] - existing_str = ", ".join(str(n) for n in line_numbers) if line_numbers else "ninguna" - skipped_invalid += 1 - skipped_details.append({ - "line": i, - "invoice": invoice_number, - "reason": f"Partida línea {linea_factura} no existe en la factura. Partidas existentes en la factura: {existing_str}.", - "solution": "Revisar que la partida (línea) exista en la factura indicada y corregir el valor en el CSV.", - }) - continue - - if autonumerar: - max_row = ( - session.query(Serie.row) - .filter(Serie.line_item_id == line_item.id) - .order_by(Serie.row.desc()) - .limit(1) - .scalar() - ) - row_num = (max_row or 0) + 1 - else: - row_num = parse_int(linea_serie) - if row_num is None: - skipped_invalid += 1 - skipped_details.append({ - "line": i, - "invoice": invoice_number, - "reason": "LINEA SERIE debe ser numérico.", - "solution": "Capturar en el CSV la LINEA SERIE solo con números enteros.", - }) - continue - - existing_serie = ( - session.query(Serie) - .filter( - Serie.line_item_id == line_item.id, - Serie.row == row_num, - ) - .first() - ) - - if existing_serie: - if actualizar: - existing_serie.serial_numbers = data["SERIE"] or existing_serie.serial_numbers - existing_serie.model = data["MODELO"] or existing_serie.model - existing_serie.sub_model = data["SUB MODELO"] or existing_serie.sub_model - existing_serie.number_id = data["NUMERO ID"] or existing_serie.number_id - session.add(existing_serie) - updated_count += 1 - else: - skipped_invalid += 1 - skipped_details.append({ - "line": i, - "invoice": invoice_number, - "reason": "Serie ya existe (use actualizar).", - "solution": "Si la serie ya existe, usa el modo Actualizar para que el archivo haga update.", - }) - else: - new_serie = Serie( - tenant_id=tenant_id, - company_id=company_id, - line_item_id=line_item.id, - row=row_num, - serial_numbers=data["SERIE"] or None, - model=data["MODELO"] or None, - sub_model=data["SUB MODELO"] or None, - number_id=data["NUMERO ID"] or None, - ) - session.add(new_serie) - inserted_count += 1 - - key_csv = (invoice_number.strip(), (linea_factura or "").strip()) - if key_csv[0] and key_csv[1]: - csv_series_count_so_far[key_csv] = csv_series_count_so_far.get(key_csv, 0) + 1 - - session.commit() - - common_storage.cleanup_import_job(effective_job_type, job_id, file_path=file_path, error_path=error_path, meta_path=meta_path) - status = "finished" if (inserted_count + updated_count) > 0 else ("warning" if skipped_invalid else "failed") - out = { - "status": status, - "inserted": inserted_count, - "updated": updated_count, - "skipped_invalid": skipped_invalid, - "skipped_missing_fk": 0, - "skipped_duplicate": 0, - "skipped_details": skipped_details, - "skipped_summary": _summarize_top_reasons(skipped_details), - } - if status == "failed": - out["error"] = "No hay registros válidos en el archivo CSV." - elif status == "warning" and skipped_invalid: - out["message"] = f"No se insertaron registros. {skipped_invalid} fueron rechazados." - return out - except Exception as e: - logger.exception("Series importación definitiva commit failed: %s", e) - return {"status": "failed", "error": str(e)} - - # --- Series de Importación Temporal: commit (INSERT/UPDATE item_line_series) --- - # Paridad CSV: campos no presentes en CSV se persisten como null; no se exigen campos que no están en la plantilla. - if use_series_flow: - try: - from api.v1.modules.a76.invoices.models import InvoiceHeader - from api.v1.modules.a76.items.models import LineItem - from api.v1.modules.a76.items.series.models import Serie - from .validators.series_impo_temp import ( - validate_row_series_impo_temp, - row_to_series_normalized, - ) - - actualizar = meta.get("actualizar", False) - autonumerar = meta.get("autonumerar", True) - validar_series_exception = meta.get("validar_series", False) - _fc = parse_footer_config(meta.get("footer_config")) - if _fc: - if "actualizar" in _fc: - actualizar = bool(_fc["actualizar"]) - elif _fc.get("mode") == "update": - actualizar = True - elif _fc.get("mode") == "replace": - actualizar = False - if "autonumerar" in _fc: - autonumerar = bool(_fc["autonumerar"]) - else: - as_val = _fc.get("autonumber_series", "true") - autonumerar = str(as_val).lower() in ("true", "1", "si", "sí", "yes") - if "validar_series" in _fc: - validar_series_exception = bool(_fc["validar_series"]) - - with scoped_core_db(tenant_id=tenant_id, company_id=company_id) as session: - # Switch maestro: validarseries desactiva toda la validación de series cuando = 0 - _sys_settings = AppSettingsService.get_resolved_settings(session, int(tenant_id), int(company_id)) - _gen_p = _sys_settings.get("ssisgen", {}) or _sys_settings.get("qsisgen", {}) - if str(_gen_p.get("validarseries", "0")).strip() != "1": - validar_series_exception = False - - q = ( - session.query(InvoiceHeader.invoice_number, InvoiceHeader.id, InvoiceHeader.status) - .filter( - InvoiceHeader.tenant_id == tenant_id, - InvoiceHeader.company_id == company_id, - InvoiceHeader.operation_type == "imp", - InvoiceHeader.invoice_type == "TEM", - ) - ) - rows_inv = q.all() - invoice_id_by_number: Dict[str, int] = {} - invoice_processed_by_number: Dict[str, bool] = {} - for num, iid, is_upd in rows_inv: - if num: - invoice_id_by_number[str(num).strip()] = iid - invoice_processed_by_number[str(num).strip()] = _is_invoice_processed_status(is_upd) - - existing_series_keys: Set[Tuple[str, str, str]] = set() - existing_series_data: Dict[Tuple[str, str, str], Dict[str, Any]] = {} - if actualizar and not autonumerar: - q_ser = ( - session.query( - InvoiceHeader.invoice_number, - LineItem.line_number, - Serie.row, - Serie.serial_numbers, - Serie.model, - Serie.sub_model, - Serie.number_id, - ) - .join(LineItem, LineItem.invoice_id == InvoiceHeader.id) - .join(Serie, Serie.line_item_id == LineItem.id) - .filter( - InvoiceHeader.tenant_id == tenant_id, - InvoiceHeader.company_id == company_id, - InvoiceHeader.operation_type == "imp", - InvoiceHeader.invoice_type == "TEM", - ) - ) - for num, ln, rw, sn, md, sm, nid in q_ser.all(): - if num is not None: - k = (str(num).strip(), str(ln).strip(), str(rw).strip()) - existing_series_keys.add(k) - existing_series_data.setdefault(k, { - "serial_numbers": sn or "", - "model": md or "", - "sub_model": sm or "", - "number_id": nid or "", - }) - - inserted_count = 0 - updated_count = 0 - skipped_invalid = 0 - skipped_details: List[Dict[str, Any]] = [] - - with open(file_path, "r", encoding=_facturas_csv_encoding(file_path)) as f: - sample = f.read(2048) - f.seek(0) - try: - dialect = common_csv_reader.detect_csv_dialect(sample, delimiters=",;\t") - except Exception: - dialect = "excel" - reader = csv.DictReader(f, dialect=dialect) - - for i, row in enumerate(reader, start=1): - if i in error_lines: - continue - row_norm = row_from_template(row, "imp_temp_series", normalize_header) - row_errors = validate_row_series_impo_temp( - row_norm, - i, - actualizar=actualizar, - autonumerar=autonumerar, - validar_series_exception=validar_series_exception, - invoice_id_by_number=invoice_id_by_number, - invoice_processed_by_number=invoice_processed_by_number, - existing_series_keys=existing_series_keys, - existing_series_data=existing_series_data, - warnings=None, - ) - _blocking = [e for e in row_errors if not e.get("warning")] - if _blocking: - skipped_invalid += 1 - for blocking in _blocking: - skipped_details.append({ - "line": i, - "invoice": (row_norm.get("NUMERO FACTURA") or row_norm.get("NUM FACTURA") or "").strip(), - "reason": blocking.get("msg", ""), - "solution": blocking.get("solution", ""), - }) - continue - - data = row_to_series_normalized(row_norm) - invoice_number = data["NUMERO FACTURA"] - linea_factura = data["LINEA FACTURA"] - linea_serie = data["LINEA SERIE"] - - if not invoice_number or invoice_number not in invoice_id_by_number: - skipped_invalid += 1 - continue - invoice_id = invoice_id_by_number[invoice_number] - line_number_val = parse_int(linea_factura) - if line_number_val is None: - skipped_invalid += 1 - skipped_details.append({ - "line": i, - "invoice": invoice_number, - "reason": "LINEA FACTURA debe ser numérico.", - "solution": "Capturar en el CSV la LINEA FACTURA solo con números enteros.", - }) - continue - line_item = ( - session.query(LineItem) - .filter( - LineItem.invoice_id == invoice_id, - LineItem.line_number == line_number_val, - LineItem.tenant_id == tenant_id, - LineItem.company_id == company_id, - ) - .first() - ) - if not line_item: - skipped_invalid += 1 - skipped_details.append({ - "line": i, - "invoice": invoice_number, - "reason": f"Partida línea {linea_factura} no existe en la factura.", - "solution": "Revisar que la partida (línea) exista en la factura indicada.", - }) - continue - - if autonumerar: - max_row = ( - session.query(Serie.row) - .filter(Serie.line_item_id == line_item.id) - .order_by(Serie.row.desc()) - .limit(1) - .scalar() - ) - row_num = (max_row or 0) + 1 - else: - row_num = parse_int(linea_serie) - if row_num is None: - skipped_invalid += 1 - skipped_details.append({ - "line": i, - "invoice": invoice_number, - "reason": "LINEA SERIE debe ser numérico.", - "solution": "Capturar en el CSV la LINEA SERIE solo con números enteros.", - }) - continue - - existing_serie = ( - session.query(Serie) - .filter( - Serie.line_item_id == line_item.id, - Serie.row == row_num, - ) - .first() - ) - - if existing_serie: - if actualizar: - existing_serie.serial_numbers = data["SERIE"] or existing_serie.serial_numbers - existing_serie.model = data["MODELO"] or existing_serie.model - existing_serie.sub_model = data["SUB MODELO"] or existing_serie.sub_model - existing_serie.number_id = data["NUMERO ID"] or existing_serie.number_id - session.add(existing_serie) - updated_count += 1 - else: - skipped_invalid += 1 - skipped_details.append({ - "line": i, - "invoice": invoice_number, - "reason": "Serie ya existe (use actualizar).", - "solution": "Si la serie ya existe, usa el modo Actualizar para que el sistema haga update.", - }) - else: - new_serie = Serie( - tenant_id=tenant_id, - company_id=company_id, - line_item_id=line_item.id, - row=row_num, - serial_numbers=data["SERIE"] or None, - model=data["MODELO"] or None, - sub_model=data["SUB MODELO"] or None, - number_id=data["NUMERO ID"] or None, - ) - session.add(new_serie) - inserted_count += 1 - - session.commit() - - common_storage.cleanup_import_job(effective_job_type, job_id, file_path=file_path, error_path=error_path, meta_path=meta_path) - status = "finished" if (inserted_count + updated_count) > 0 else ("warning" if skipped_invalid else "failed") - out = { - "status": status, - "inserted": inserted_count, - "updated": updated_count, - "skipped_invalid": skipped_invalid, - "skipped_missing_fk": 0, - "skipped_duplicate": 0, - "skipped_details": skipped_details, - "skipped_summary": _summarize_top_reasons(skipped_details), - } - if status == "failed": - out["error"] = "No hay registros válidos en el archivo CSV." - elif status == "warning" and skipped_invalid: - out["message"] = f"No se insertaron registros. {skipped_invalid} fueron rechazados." - return out - except Exception as e: - logger.exception("Series import commit failed: %s", e) - return {"status": "failed", "error": str(e)} - - try: - from api.v1.modules.a76.invoices.models import ( - InvoiceHeader, - InvoiceComplianceMx, - InvoiceFinancials, - InvoiceLogistics, - InvoiceSalesDetails, - InvoiceStatus, - OperationType, - TransportType, - WeightUnit, - ) - from api.v1.modules.a76.clients_and_providers.models import ClientProvider - from api.v1.modules.a76.customs_brokers.models import CustomsBroker - from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos - from api.v1.modules.public.reference_data.currency_types.models import CurrencyType - from api.v1.modules.public.reference_data.pedimento_codes.models import PedimentoCode - from api.v1.modules.public.reference_data.code_pedimento_regimens.models import CodePedimentoRegimen - from api.v1.modules.public.reference_data.pedimento_regimens.models import RegimenPedimento - from api.v1.modules.public.reference_data.invoice_types.models import InvoiceType - from api.v1.modules.public.reference_data.customs_sections.models import CustomsSection - from api.v1.modules.public.reference_data.incoterms.models import Incoterm - from .validators.encabezados_impo_temp import ( - parse_pedimento_col_a, - _pedimento_key_from_parsed, - row_to_transport_type_clarion, - _patente_from_agente_aduanal, - ) - from .validators.transport_catalog import ( - normalize_transport_cell, - resolve_logistics_transport_fields_for_commit, - resolve_transport_csv_fields, - validate_csv_invoice_logistics_transport, - ) - from .validators.encabezados_impo_def import parse_pedimento_col_a_impo_def - from .validators.pedimento_resolution import resolve_pedimento_candidates - - from api.v1.modules.a76.items.models import LineItem - from api.v1.modules.a76.items.line_financials.models import LineFinancial - from api.v1.modules.a76.items.line_quantities.models import LineQuantity - from api.v1.modules.a76.items.line_customs.models import LineCustom - from api.v1.modules.a76.items.line_descriptions.models import LineDescription - from api.v1.modules.a76.items.schemas import LineItemCreate - from api.v1.modules.a76.items.line_financials.schemas import LineFinancialCreate - from api.v1.modules.a76.items.line_quantities.schemas import LineQuantityCreate - from api.v1.modules.a76.items.line_customs.schemas import LineCustomCreate - from api.v1.modules.a76.items.line_descriptions.schemas import LineDescriptionCreate - from api.v1.modules.a24.fa.fa_item_lines.dto import FaLineItemCreateDTO - from api.v1.modules.a24.fa.fa_item_lines.models import FaLineItem - from api.v1.modules.a76.layouts_csv.facturas.line_item_enrichment import ( - apply_import_defaults_and_calculations_for_csv, - apply_export_defaults_and_calculations_for_csv, - ) - from api.v1.modules.a76.items.service import ItemService - from api.v1.modules.a76.parts.models import Part - from api.v1.modules.a76.classes.models import Class - from api.v1.modules.a76.general_catalogs.units_of_measure.models import UnitOfMeasure - from api.v1.modules.a76.general_catalogs.packages.models import Package - - footer_config = parse_footer_config(meta.get("footer_config")) - - date_format = footer_config.get("dateFormat") - # Validate and set default date_format if not provided - if not date_format: - date_format = "yyyy-mm-dd" # Default to ISO format - logger.info(f"No date_format specified in config, using default: {date_format}") - else: - logger.info(f"Using date_format from config: {date_format}") - - _template_id_insert, inv_type_value, op_type_raw = _resolve_import_context( - model_target=model_target, - meta=meta, - footer_config=footer_config, - job_type_override=job_type_override, - ) - op_type_value = OperationType(op_type_raw) - _es_cambio_regimen = None - if model_target == "invoice_header" and _template_id_insert == "exp_def_header": - cambio_regimen_raw = ( - str(meta.get("cambio_regimen") or footer_config.get("cambio_regimen") or "NO").strip().upper() - ) - _es_cambio_regimen = "S" if cambio_regimen_raw == "SI" else "N" - - logger.info(f"Processing CSV with operation_type={op_type_value}, invoice_type={inv_type_value}, date_format={date_format}") - - headers_to_insert = [] - details_to_insert = [] - skipped_invalid = 0 - skipped_missing_invoice = 0 - skipped_missing_fk = 0 - skipped_fk_details = [] - inserted_count = 0 - response = None - - with scoped_core_db(tenant_id=tenant_id, company_id=company_id) as session: - invoice_id_cache = {} - cleared_invoices = set() # Track invoices where we've already cleared items in this job - provider_cache: Dict[Any, Optional[int]] = {} - sold_to_cache: Dict[Any, Optional[int]] = {} - shipped_to_cache: Dict[Any, Optional[int]] = {} - broker_cache: Dict[Any, Optional[int]] = {} - regimen_cache: Dict[str, Optional[str]] = {} - currency_type_cache: Dict[str, Optional[str]] = {} - customs_section_cache: Dict[str, Optional[str]] = {} - part_cache: Dict[str, Optional[int]] = {} - pedimento_id_cache: Dict[str, Optional[int]] = {} - pedimento_rows_insert: List[Dict[str, Any]] = [] - broker_lookup: Optional[Dict[str, Any]] = None - shipped_by_cache: Dict[Any, Optional[int]] = {} - _fc_insert = parse_footer_config(meta.get("footer_config")) - autonumerar_remesas_insert = _fc_insert.get("autonumerar_remesas", False) - actualizar_insert = bool(meta.get("actualizar", False)) - if isinstance(_fc_insert, dict): - if "actualizar" in _fc_insert: - actualizar_insert = bool(_fc_insert.get("actualizar")) - elif _fc_insert.get("mode") == "update": - actualizar_insert = True - elif _fc_insert.get("mode") == "replace": - actualizar_insert = False - class_id_by_code: Dict[str, int] = {} - class_uom_by_code: Dict[str, Optional[str]] = {} - class_fraction_by_code: Dict[str, Optional[str]] = {} - class_desc_es_by_code: Dict[str, Optional[str]] = {} - class_desc_en_by_code: Dict[str, Optional[str]] = {} - uom_id_by_code: Dict[str, int] = {} - package_id_by_key: Dict[str, int] = {} - company_rfc_insert = "" - normalize_preferencia_arancelaria_commit = lambda value: (str(value or "").strip().upper()) - - def clear_existing_invoice_details(invoice_id: int) -> None: - """ - Reemplazo seguro por factura en commit de invoice_details. - Evita bulk delete directo sobre item_lines para no romper FK. - """ - existing_line_ids = [ - line_id - for (line_id,) in session.query(LineItem.id) - .filter(LineItem.invoice_id == invoice_id) - .all() - ] - if not existing_line_ids: - session.query(InvoiceSalesDetails).filter( - InvoiceSalesDetails.invoice_id == invoice_id - ).delete(synchronize_session=False) - return - - session.query(FaLineItem).filter( - FaLineItem.id.in_(existing_line_ids) - ).delete(synchronize_session=False) - - existing_lines = ( - session.query(LineItem) - .filter(LineItem.id.in_(existing_line_ids)) - .all() - ) - for existing_line in existing_lines: - session.delete(existing_line) - - session.query(InvoiceSalesDetails).filter( - InvoiceSalesDetails.invoice_id == invoice_id - ).delete(synchronize_session=False) - - if model_target == 'invoice_details': - try: - from .validators.partidas_impo_temp import ( - normalize_preferencia_arancelaria as normalize_preferencia_arancelaria_commit, - ) - except Exception: - # Fallback conservador: comportamiento previo (trim + upper). - normalize_preferencia_arancelaria_commit = lambda value: (str(value or "").strip().upper()) - for c in session.query( - Class.id, - Class.class_code, - Class.unit_of_measure, - Class.fraction, - Class.description_es, - Class.description_en, - ).filter(Class.tenant_id == tenant_id, Class.company_id == company_id).all(): - if c[1]: - class_code_key = (c[1] or "").strip().upper() - class_id_by_code[class_code_key] = c[0] - class_uom_by_code[class_code_key] = (c[2] or "").strip().upper() or None - class_fraction_by_code[class_code_key] = (c[3] or "").strip() or None - class_desc_es_by_code[class_code_key] = (c[4] or "").strip() or None - class_desc_en_by_code[class_code_key] = (c[5] or "").strip() or None - for u in session.query(UnitOfMeasure.id, UnitOfMeasure.code).filter(UnitOfMeasure.tenant_id == tenant_id, UnitOfMeasure.company_id == company_id).all(): - if u[1]: - uom_id_by_code[(u[1] or "").strip().upper()] = u[0] - for p in session.query(Package.id, Package.key).filter(Package.tenant_id == tenant_id, Package.company_id == company_id).all(): - if p[1]: - package_id_by_key[(p[1] or "").strip()] = p[0] - try: - from api.v1.modules.a76.general_catalogs.company.models import Company - company = session.query(Company).filter(Company.id == company_id).first() - company_rfc_insert = (company.rfc or "").strip().upper() if company else "" - except Exception: - company_rfc_insert = "" - - validator = ForeignKeyValidator(session, tenant_id, company_id) - - if model_target == "invoice_header": - broker_rows = session.query(CustomsBroker.id, CustomsBroker.broker_key).filter( - CustomsBroker.tenant_id == tenant_id, - CustomsBroker.company_id == company_id, - ).all() - broker_lookup = _build_customs_broker_lookup(broker_rows) - skipped_ped_commit_catalog = 0 - for p in ( - session.query( - Pedimentos.id, - Pedimentos.customs_office, - Pedimentos.license, - Pedimentos.pedimento_number, - Pedimentos.operation_type, - Pedimentos.regime, - ) - .filter( - Pedimentos.tenant_id == tenant_id, - Pedimentos.company_id == company_id, - ) - .all() - ): - tri = _normalize_catalog_pedimento_identity( - p.customs_office, p.license, p.pedimento_number - ) - if not tri: - skipped_ped_commit_catalog += 1 - continue - co, lic, num = tri - info = { - "id": p.id, - "customs_office": co, - "license": lic, - "pedimento_number": num, - "operation_type": ( - getattr(p.operation_type, "value", None) or str(p.operation_type or "") - ).strip(), - "regime": (p.regime or "").strip(), - } - pedimento_rows_insert.append(info) - logger.info( - "facturas_commit invoice_header pedimento_catalog tenant_id=%s company_id=%s " - "catalog_rows=%s skipped_empty_license_or_number=%s", - tenant_id, - company_id, - len(pedimento_rows_insert), - skipped_ped_commit_catalog, - ) - - error_msg_by_line: Dict[int, str] = {} - if error_path and os.path.exists(error_path): - try: - with open(error_path, "r", encoding="utf-8") as f_err: - for line in f_err: - line = line.strip() - if not line: - continue - try: - rec = json.loads(line) - if "line" in rec and "msg" in rec: - error_msg_by_line[int(rec["line"])] = str(rec["msg"]).strip() - except (json.JSONDecodeError, ValueError, TypeError): - pass - except Exception: - pass - - with open(file_path, 'r', encoding=_facturas_csv_encoding(file_path)) as f: - # Detect Delimiter - sample = f.read(2048) - f.seek(0) - try: - dialect = common_csv_reader.detect_csv_dialect(sample, delimiters=",;\t") - except: - dialect = 'excel' - - reader = csv.DictReader(f, dialect=dialect) - - template_id = _template_id_insert - - for i, row in enumerate(reader, start=1): - row_norm = row_from_template(row, template_id, normalize_header) - customs_broker_id_resolved: Optional[int] = None - if i in error_lines: - skipped_invalid += 1 - if model_target == 'invoice_header': - inv_for_detail = _extract_invoice_number_from_row(row_norm) - elif _template_id_insert == "exp_def_partidas": - inv_for_detail = (row_norm.get('NUMERO FACTURA EXPO') or row_norm.get('NUMERO FACTURA EXPO.') or row_norm.get('FACTURA EXPO') or '').strip() - else: - inv_for_detail = _extract_invoice_number_from_row(row_norm) - reason = error_msg_by_line.get(i, "Línea marcada con error en el escaneo previo (revisar reporte de validación).") - skipped_fk_details.append({ - "line": i, - "invoice": inv_for_detail or "(vacío)", - "reason": reason, - }) - continue - - # Mapping Logic (solo campos que acepta el modelo de facturas) - if model_target == 'invoice_header': - invoice_number = _extract_invoice_number_from_row(row_norm) - invoice_date = parse_date(row_norm.get('FECHA FACTURA') or row_norm.get('FECHA'), date_format) - existing_header = None - - if not invoice_number: - skipped_invalid += 1 - reason = "Número de factura faltante/inválido" - skipped_fk_details.append({"line": i, "invoice": invoice_number or "(vacío)", "reason": reason}) - logger.debug(f"Row {i}: Skipped - missing invoice_number. Invoice: {invoice_number}") - continue - - if not invoice_date and actualizar_insert: - # En modo update, si la fecha no viene parseable en CSV, - # reutilizamos la fecha existente de la factura. - existing_header = ( - session.query(InvoiceHeader) - .filter( - InvoiceHeader.tenant_id == tenant_id, - InvoiceHeader.company_id == company_id, - InvoiceHeader.invoice_number == invoice_number, - InvoiceHeader.invoice_type == inv_type_value, - InvoiceHeader.operation_type == op_type_value, - ) - .first() - ) - if existing_header and existing_header.invoice_date: - invoice_date = existing_header.invoice_date - - if not invoice_date: - skipped_invalid += 1 - reason = "Fecha de factura faltante/inválida" - skipped_fk_details.append({"line": i, "invoice": invoice_number, "reason": reason}) - logger.debug( - f"Row {i}: Skipped - invalid invoice_date. Invoice: {invoice_number}, " - f"Date: {row_norm.get('FECHA FACTURA') or row_norm.get('FECHA')}" - ) - continue - - # --- NEW: Foreign Key Validations --- - # 1. Invoice Type (Public) - if not validator.check_exists(InvoiceType, inv_type_value, field_name="key", is_public=True): - skipped_missing_fk += 1 - reason = f"Tipo de factura '{inv_type_value}' no existe" - skipped_fk_details.append({"line": i, "invoice": invoice_number, "reason": reason}) - logger.warning(f"Row {i} (Invoice {invoice_number}): {reason}") - continue - - err = _validate_client_provider_ref( - validator, - ClientProvider, - row_norm.get('CLAVE PROVEEDOR'), - i, - "CLAVE PROVEEDOR", - required=True, - ) - if err: - skipped_invalid += 1 - reason = f"{err['col']}: {err['msg']}" - skipped_fk_details.append({"line": i, "invoice": invoice_number, "reason": reason}) - logger.warning(f"Row {i} (Invoice {invoice_number}): {reason}") - continue - - err = _validate_client_provider_ref( - validator, - ClientProvider, - row_norm.get('CLAVE VENDIDO A'), - i, - "CLAVE VENDIDO A", - required=True, - ) - if err: - skipped_invalid += 1 - reason = f"{err['col']}: {err['msg']}" - skipped_fk_details.append({"line": i, "invoice": invoice_number, "reason": reason}) - logger.warning(f"Row {i} (Invoice {invoice_number}): {reason}") - continue - - err = _validate_client_provider_ref( - validator, - ClientProvider, - row_norm.get('CLAVE ENVIADO A'), - i, - "CLAVE ENVIADO A", - required=True, - ) - if err: - skipped_invalid += 1 - reason = f"{err['col']}: {err['msg']}" - skipped_fk_details.append({"line": i, "invoice": invoice_number, "reason": reason}) - logger.warning(f"Row {i} (Invoice {invoice_number}): {reason}") - continue - - if inv_type_value != "MEX": - if broker_lookup is not None: - raw_agent = row_norm.get('AGENTE ADUANAL') - resolved_id, mode, normalized_agent, resolve_err = resolve_customs_broker_ref(raw_agent, broker_lookup) - if resolve_err: - skipped_missing_fk += 1 - reason = ( - f"AGENTE ADUANAL: {resolve_err} " - f"(valor='{str(raw_agent or '').strip()}', normalizado='{normalized_agent}')" - ) - skipped_fk_details.append({"line": i, "invoice": invoice_number, "reason": reason}) - logger.warning(f"Row {i} (Invoice {invoice_number}): {reason}") - continue - customs_broker_id_resolved = resolved_id - # Sustitución interna: una vez resuelto, usar el ID real para todo el pipeline. - if customs_broker_id_resolved is not None: - row_norm['AGENTE ADUANAL'] = customs_broker_id_resolved - - err = _validate_customs_broker_ref( - validator, - CustomsBroker, - row_norm.get('AGENTE ADUANAL'), - i, - "AGENTE ADUANAL", - required=False, - broker_lookup=broker_lookup, - ) - if err: - skipped_missing_fk += 1 - reason = f"{err['col']}: {err['msg']}" - skipped_fk_details.append({"line": i, "invoice": invoice_number, "reason": reason}) - logger.warning(f"Row {i} (Invoice {invoice_number}): {reason}") - continue - - err = validate_public_code( - validator, - RegimenPedimento, - row_norm.get('REGIMEN') or row_norm.get('CLAVEDOCUMENTO'), - i, - "CLAVEDOCUMENTO", - ) - if err: - skipped_missing_fk += 1 - reason = f"{err['col']}: {err['msg']}" - skipped_fk_details.append({"line": i, "invoice": invoice_number, "reason": reason}) - logger.warning(f"Row {i} (Invoice {invoice_number}): {reason}") - continue - - err = validate_public_code( - validator, - CustomsSection, - row_norm.get('ADUANA DE CRUCE'), - i, - "ADUANA DE CRUCE", - field_name="customs_code", - ) - if err: - skipped_missing_fk += 1 - reason = f"{err['col']}: {err['msg']}" - skipped_fk_details.append({"line": i, "invoice": invoice_number, "reason": reason}) - logger.warning(f"Row {i} (Invoice {invoice_number}): {reason}") - continue - - if inv_type_value == "MEX": - err = validate_public_code( - validator, - CurrencyType, - row_norm.get('CLAVE MONEDA'), - i, - "CLAVE MONEDA", - ) - if err and (row_norm.get('TIPO MONEDA') or '').strip().upper() == 'MC': - skipped_missing_fk += 1 - reason = f"{err['col']}: {err['msg']}" - skipped_fk_details.append({"line": i, "invoice": invoice_number, "reason": reason}) - logger.warning(f"Row {i} (Invoice {invoice_number}): {reason}") - continue - else: - err = validate_public_code( - validator, - CurrencyType, - row_norm.get('CLAVE MONEDA'), - i, - "CLAVE MONEDA", - ) - if err: - skipped_missing_fk += 1 - reason = f"{err['col']}: {err['msg']}" - skipped_fk_details.append({"line": i, "invoice": invoice_number, "reason": reason}) - logger.warning(f"Row {i} (Invoice {invoice_number}): {reason}") - continue - - err = validate_public_code( - validator, - Incoterm, - row_norm.get('CLAVE INCOTERM'), - i, - "CLAVE INCOTERM", - ) - if err: - skipped_missing_fk += 1 - reason = f"{err['col']}: {err['msg']}" - skipped_fk_details.append({"line": i, "invoice": invoice_number, "reason": reason}) - logger.warning(f"Row {i} (Invoice {invoice_number}): {reason}") - continue - - transport_type_val = row_norm.get('TIPO TRANSPORTE') - transport_str_normalized = (row_to_transport_type_clarion(transport_type_val) or str(transport_type_val or "").strip().lower() or "none") - if transport_type_val and transport_str_normalized not in TRANSPORT_TYPE_VALUES: - skipped_invalid += 1 - reason = "TIPO TRANSPORTE: Tipo de transporte invalido" - skipped_fk_details.append({"line": i, "invoice": invoice_number, "reason": reason}) - logger.warning(f"Row {i} (Invoice {invoice_number}): {reason}") - continue - - currency_val = row_norm.get('TIPO MONEDA') - if currency_val and str(currency_val).strip(): - if not csv_tipo_moneda_es_me_mn_mc(str(currency_val)): - parsed_currency = parse_currency(currency_val, None) - val_norm = normalize_header(currency_val) - if parsed_currency.value == "manual" and "MANUAL" not in val_norm: - skipped_invalid += 1 - reason = "TIPO MONEDA: Moneda invalida" - skipped_fk_details.append({"line": i, "invoice": invoice_number, "reason": reason}) - logger.warning(f"Row {i} (Invoice {invoice_number}): {reason}") - continue - - # 2. Client/Provider and broker checks are handled above - - # --- 4. Check for Existing Invoice (Upsert Logic) --- - if invoice_number and existing_header is None: - existing_header = ( - session.query(InvoiceHeader) - .filter( - InvoiceHeader.tenant_id == tenant_id, - InvoiceHeader.company_id == company_id, - InvoiceHeader.invoice_number == invoice_number, - InvoiceHeader.invoice_type == inv_type_value, - InvoiceHeader.operation_type == op_type_value, - ) - .first() - ) - - # --- Resolve PEDIMENTO (Col A) to pedimento_id and REMESA (Col B) --- - pedimento_id = None - remesa_val = parse_int(row_norm.get('REMESA')) if inv_type_value != "MEX" else None - ped_str = (row_norm.get('PEDIMENTO') or '').strip() if inv_type_value != "MEX" else '' - if ped_str: - if template_id == "exp_def_header": - parsed = parse_pedimento_col_a_impo_def(ped_str) - else: - parsed = parse_pedimento_col_a(ped_str) - if parsed: - customs_office_p, license_p, num_p = (x.strip() if x else "" for x in parsed) - patente_lookup = _patente_from_agente_aduanal(row_norm, license_p) - cache_key_p = f"{template_id}|{_es_cambio_regimen}|{customs_office_p}|{license_p}|{num_p}|{patente_lookup}" - if cache_key_p not in pedimento_id_cache: - ped_resolved = resolve_pedimento_candidates( - customs_office=customs_office_p, - license_val=license_p, - pedimento_number=num_p, - patente_lookup=patente_lookup, - pedimento_rows=pedimento_rows_insert, - ) - resolved_id: Optional[int] = None - if ped_resolved["status"] == "ok" and ped_resolved["pedimento"]: - ped_info = ped_resolved["pedimento"] - op_type = str(ped_info.get("operation_type") or "").strip().lower() - regimen = str(ped_info.get("regime") or "").strip().upper() - if template_id == "exp_def_header": - if _es_cambio_regimen == "S": - if op_type == "imp" and regimen == "IMD": - resolved_id = ped_info.get("id") - else: - if op_type == "exp" and regimen in {"EXD", "ETE", "ETR"}: - resolved_id = ped_info.get("id") - else: - resolved_id = ped_info.get("id") - pedimento_id_cache[cache_key_p] = resolved_id - pedimento_id = pedimento_id_cache[cache_key_p] - if pedimento_id is not None and remesa_val is None and autonumerar_remesas_insert: - max_rem = ( - session.query(func.max(InvoiceComplianceMx.remesa)) - .filter(InvoiceComplianceMx.pedimento_id == pedimento_id) - .scalar() - ) - remesa_val = (max_rem or 0) + 1 - - if existing_header: - # UPDATE existing header (paridad con InvoiceService.update) - header = existing_header - header.invoice_date = invoice_date - header.operation_type = op_type_value - # CSV import only captures data; "processed" is set by the manual/import processing flow. - # Legacy CSV imports may have persisted booleans ('True'/'False') into status. - if header.status not in (InvoiceStatus.PENDING, InvoiceStatus.PROCESSED, InvoiceStatus.REVERSED): - header.status = InvoiceStatus.PENDING - header.updated_date = datetime.utcnow() - capture_user = meta.get("capture_user") or "CSV" - header.who_processed = capture_user - # Backfill capture_user if missing or generic (paridad con service) - if not header.capture_user or header.capture_user == "System": - if capture_user != "CSV": - header.capture_user = capture_user - header.document_type = ( - None if inv_type_value == "MEX" else - resolve_public_code( - session, - RegimenPedimento, - RegimenPedimento.code, - (row_norm.get('REGIMEN') or row_norm.get('CLAVEDOCUMENTO')), - regimen_cache, - ) - ) - header.project_number = (row_norm.get('NUM PROYECTO') or row_norm.get('NUMPROYECTO') or None) - header.purchase_order = (row_norm.get('ORDEN COMPRA') or row_norm.get('ORDENCOMPRA') or None) - header.alternate_invoice = (row_norm.get('FACTURA ALTERNA') or None) - header.invoice_ref = (row_norm.get('FACTURA EXPO REF') or row_norm.get('FACTURAEXPOREF') or None) - header.emission_date = parse_date(row_norm.get('FECHA EMISION'), date_format) - header.observation_es = (row_norm.get('OBSERVACIONES E') or None) - header.observation_en = (row_norm.get('OBSERVACIONES I') or None) - logger.info(f"Row {i}: Updating existing invoice {invoice_number}") - - # Clean up related data that will be re-inserted/updated - # Note: compliance, financials, logistics are 1-to-1 relationships and will be updated by assignment below - # but we might want to be explicit if ORM doesn't handle replace well. - # SQLAlchemy relationship assignment usually handles 1-to-1 updates correctly. - - else: - # CREATE new header (paridad con InvoiceService.create: capture_user, who_processed) - capture_user = meta.get("capture_user") or "CSV" - header = InvoiceHeader( - invoice_number=invoice_number, - invoice_date=invoice_date, - operation_type=op_type_value, - status=InvoiceStatus.PENDING, - system="fixed_asset", - capture_date=datetime.utcnow(), - capture_user=capture_user, - who_processed=capture_user, - invoice_type=inv_type_value, - document_type=( - None if inv_type_value == "MEX" else - resolve_public_code( - session, - RegimenPedimento, - RegimenPedimento.code, - (row_norm.get('REGIMEN') or row_norm.get('CLAVEDOCUMENTO')), - regimen_cache, - ) - ), - project_number=(row_norm.get('NUM PROYECTO') or row_norm.get('NUMPROYECTO') or None), - purchase_order=(row_norm.get('ORDEN COMPRA') or row_norm.get('ORDENCOMPRA') or None), - alternate_invoice=(row_norm.get('FACTURA ALTERNA') or None), - invoice_ref=(row_norm.get('FACTURA EXPO REF') or row_norm.get('FACTURAEXPOREF') or None), - emission_date=parse_date(row_norm.get('FECHA EMISION'), date_format), - observation_es=(row_norm.get('OBSERVACIONES E') or None), - observation_en=(row_norm.get('OBSERVACIONES I') or None), - tenant_id=tenant_id, - company_id=company_id, - ) - - compliance = InvoiceComplianceMx( - pedimento_id=pedimento_id if inv_type_value != "MEX" else None, - remesa=remesa_val if inv_type_value != "MEX" else None, - aduana=( - None if inv_type_value == "MEX" else - resolve_public_code( - session, - CustomsSection, - CustomsSection.customs_code, - row_norm.get('ADUANA DE CRUCE'), - customs_section_cache, - ) - ), - provider_id=resolve_client_provider_id( - session, - ClientProvider, - row_norm.get('CLAVE PROVEEDOR'), - tenant_id, - company_id, - provider_cache, - ), - sold_to_id=resolve_client_provider_id( - session, - ClientProvider, - row_norm.get('CLAVE VENDIDO A'), - tenant_id, - company_id, - sold_to_cache, - ), - shipped_to_id=resolve_client_provider_id( - session, - ClientProvider, - row_norm.get('CLAVE ENVIADO A'), - tenant_id, - company_id, - shipped_to_cache, - ), - customs_broker_id=( - None if inv_type_value == "MEX" else - customs_broker_id_resolved - if customs_broker_id_resolved is not None - else resolve_customs_broker_id( - session, - CustomsBroker, - row_norm.get('AGENTE ADUANAL'), - tenant_id, - company_id, - broker_cache, - broker_lookup=broker_lookup, - ) - ), - edocument=(row_norm.get('E DOCUMENT') or None), - vucem_operation_num=(row_norm.get('NUM OPERACION') or None), - manifest_number=(row_norm.get('MANIFIESTO') or None), - shipped_by_id=resolve_client_provider_id( - session, - ClientProvider, - row_norm.get('ENVIADO POR'), - tenant_id, - company_id, - shipped_by_cache, - ) if row_norm.get('ENVIADO POR') else None, - tenant_id=tenant_id, - company_id=company_id, - ) - - financials_currency_type = resolve_public_code( - session, - CurrencyType, - CurrencyType.code, - row_norm.get('CLAVE MONEDA'), - currency_type_cache, - ) - financials = InvoiceFinancials( - currency=parse_currency(row_norm.get('TIPO MONEDA'), financials_currency_type), - currency_type=financials_currency_type, - exchange_rate=decimal_or_zero(row_norm.get('TIPO DE CAMBIO')), - freight=decimal_or_zero(row_norm.get('FLETES')), - insurance_value=decimal_or_zero(row_norm.get('VALOR SEGUROS')), - insurance=decimal_or_zero(row_norm.get('SEGUROS')), - packaging=decimal_or_zero(row_norm.get('EMBALAJES')), - other_increments=decimal_or_zero(row_norm.get('OTROS INCREMENTABLES')), - tenant_id=tenant_id, - company_id=company_id, - ) - - weight_type = parse_weight_unit(row_norm.get('TIPO PESO')) - logistics = None - transport_key_csv, trailer_num_csv, transport_num_csv = resolve_transport_csv_fields(row_norm) - has_logistics_data = bool( - weight_type - or row_norm.get('TIPO TRANSPORTE') - or row_norm.get('NUMERO TRANSPORTE') - or transport_key_csv - or trailer_num_csv - or row_norm.get('CLAVE TRANSPORTISTA') - or row_norm.get('NOMBRE CONDUCTOR') - ) - if has_logistics_data: - raw_transport = row_norm.get('TIPO TRANSPORTE') - transport_str = (row_to_transport_type_clarion(raw_transport) or str(raw_transport or "").strip().lower() or "none") - try: - transport_type = TransportType(transport_str) - except ValueError: - transport_type = TransportType.NONE - err_transport = validate_csv_invoice_logistics_transport( - row_norm=row_norm, - session=session, - tenant_id=tenant_id, - company_id=company_id, - transport_type=transport_type, - ) - if err_transport: - skipped_invalid += 1 - reason = err_transport - skipped_fk_details.append( - {"line": i, "invoice": invoice_number, "reason": reason} - ) - logger.warning(f"Row {i} (Invoice {invoice_number}): {reason}") - continue - - transport_num_effective = (transport_num_csv or None) - if transport_type == TransportType.NONE: - transport_num_effective = None - - carrier_code = normalize_transport_cell(row_norm.get('CLAVE TRANSPORTISTA')) or None - carrier_int_id = None - if carrier_code: - from api.v1.modules.a76.transportation.transporters.models import ( - Transporter, - ) - - carrier_obj = ( - session.query(Transporter) - .filter( - Transporter.transporter_key == carrier_code, - Transporter.tenant_id == tenant_id, - Transporter.company_id == company_id, - ) - .first() - ) - if carrier_obj: - carrier_int_id = carrier_obj.transporter_id - - ( - transport_id_value, - trailer_num_value, - transport_int_id, - trailer_int_id, - ) = resolve_logistics_transport_fields_for_commit( - session=session, - tenant_id=tenant_id, - company_id=company_id, - clave_transporte_raw=transport_key_csv, - numero_caja_raw=trailer_num_csv, - ) - logistics = InvoiceLogistics( - carrier_id=carrier_code, - carrier_int_id=carrier_int_id, - driver_name=(row_norm.get('NOMBRE CONDUCTOR') or None), - transport_type=transport_type, - transport_num=transport_num_effective, - transport_id=transport_id_value, - trailer_num=trailer_num_value, - transport_int_id=transport_int_id, - trailer_int_id=trailer_int_id, - weight_type=weight_type or WeightUnit.KGS, - seal_number=(row_norm.get('PRECINTO') or None), - incoterm=(row_norm.get('CLAVE INCOTERM') or None), - entry_exit_date=parse_date(row_norm.get('FECHA EMISION'), date_format), - tenant_id=tenant_id, - company_id=company_id, - ) - - header.compliance_mx = compliance - header.financials = financials - if logistics: - header.logistics = logistics - - headers_to_insert.append(header) - - elif model_target == 'invoice_details': - if _template_id_insert == "exp_def_partidas": - invoice_number = ( - row_norm.get('NUMERO FACTURA EXPO') or row_norm.get('NUMERO FACTURA EXPO.') or row_norm.get('FACTURA EXPO') or '' - ).strip() - else: - invoice_number = _extract_invoice_number_from_row(row_norm) - if not invoice_number: - skipped_invalid += 1 - continue - - if _template_id_insert == "exp_def_partidas": - # Expo: lookup by invoice_number + operation_type only (no invoice_type filter, matching scan behavior) - cache_key = f"{invoice_number}|exp" - if cache_key in invoice_id_cache: - invoice_id = invoice_id_cache[cache_key] - else: - invoice_id = ( - session.query(InvoiceHeader.id) - .filter( - InvoiceHeader.tenant_id == tenant_id, - InvoiceHeader.company_id == company_id, - InvoiceHeader.invoice_number == invoice_number, - InvoiceHeader.operation_type == "exp", - ) - .scalar() - ) - invoice_id_cache[cache_key] = invoice_id - else: - invoice_id = _resolve_invoice_id_for_details( - session=session, - InvoiceHeader=InvoiceHeader, - tenant_id=tenant_id, - company_id=company_id, - invoice_number_raw=invoice_number, - invoice_type=inv_type_value, - operation_type=op_type_value, - cache=invoice_id_cache, - ) - - if not invoice_id: - logger.warning( - "Invoice not found for details row %s (invoice_number=%s)", - i, - invoice_number, - ) - skipped_missing_invoice += 1 - continue - - # --- Partidas Exportación Definitiva: paridad con flujo normal (validators + ItemService) --- - if _template_id_insert == "exp_def_partidas": - part_num = (row_norm.get('NUM. PARTE') or row_norm.get('NUMPARTE') or row_norm.get('NUMERO PARTE') or row_norm.get('NUM PARTE') or '').strip() - part_id = part_cache.get(part_num) if part_num else None - if part_id is None and part_num: - p = session.query(Part.id).filter(Part.part_number == part_num, Part.tenant_id == tenant_id, Part.company_id == company_id).first() - if p: - part_id = p.id - part_cache[part_num] = p.id - - line_num_val = (row_norm.get('LINEA EXPO') or row_norm.get('LINEA EXPO.') or row_norm.get('RENGLON EXPO')) - line_num = parse_int(line_num_val) or (len(details_to_insert) + 1) - - uom_code = (row_norm.get('UNIDAD DE MEDIDA') or row_norm.get('U.M.') or row_norm.get('UNIDAD MEDIDA') or '').strip().upper() - uom_id = uom_id_by_code.get(uom_code) if uom_code else None - bulk_key = (row_norm.get('CLAVE BULTOS') or row_norm.get('CLAVEBULTOS') or '').strip() - package_id = package_id_by_key.get(bulk_key) if bulk_key else None - - descarga_val = (row_norm.get('GENERA DESCARGA') or row_norm.get('GENERA DESCARGA?') or row_norm.get('DESCARGA') or 'SI').strip().upper() - tipo_impo = (row_norm.get('TIPO DE IMPO') or row_norm.get('TIPO DE IMPO.') or row_norm.get('TIPO IMPO') or row_norm.get('PROCEDENCIA') or '').strip().upper() - factura_impo = (row_norm.get('FACTURA IMPO') or row_norm.get('FACTURA IMPO.') or row_norm.get('FACTURA IMPORTACION') or '').strip() - linea_impo_val = (row_norm.get('LINEA IMPO') or row_norm.get('LINEA IMPO.') or row_norm.get('LINEA IMPORTACION') or '').strip() - - se_pago = (row_norm.get('SE PAGO IMPUESTO') or row_norm.get('SE PAGO IMPUESTO? (SI o NO)') or row_norm.get('SEPAGOIMPUESTO') or '').strip().upper() - forma_pago = (row_norm.get('FORMA DE PAGO') or row_norm.get('FORMADEPAGO') or row_norm.get('FORMA PAGO') or '').strip() or None - - es_sub_raw = (row_norm.get('ES PARTIDA/SUBPARTIDA') or row_norm.get('ESSUBPARTIDA') or row_norm.get('ES PARTIDA O SUBPARTIDA') or '').strip().upper() - linea_principal_val = (row_norm.get('LINEA PRINCIPAL') or row_norm.get('LINEAPRINCIPAL') or row_norm.get('PARTIDA PRINCIPAL') or '').strip() - is_subitem = (es_sub_raw == 'S') - contains_subitems = (es_sub_raw == 'P') - - # Clear existing line items once per invoice - if invoice_id not in cleared_invoices: - logger.info(f"Clearing existing details for Expo Invoice {invoice_number} (ID: {invoice_id})") - clear_existing_invoice_details(invoice_id) - cleared_invoices.add(invoice_id) - - price = parse_decimal(row_norm.get('COSTO UNITARIO') or row_norm.get('COSTOUNITARIO')) - qty = parse_decimal(row_norm.get('CANTIDAD EXPORTADA/DESCARGAR') or row_norm.get('CANTIDAD EXPORTADA') or row_norm.get('CANTIDAD')) - commercial_total = (price * qty) if price and qty else None - net_w = parse_decimal(row_norm.get('PESO NETO') or row_norm.get('PESONETO')) - gross_w = parse_decimal(row_norm.get('PESO BRUTO') or row_norm.get('PESOBRUTO')) - origin = (row_norm.get('PAIS ORIGEN') or row_norm.get('PAISORIGEN') or row_norm.get('PAIS') or '').strip() - fraction = (row_norm.get('FRACCION ARANCELARIA') or row_norm.get('FRACCIONARANCELARIA') or '').strip() - american_fraction = (row_norm.get('FRACCION AMERICANA') or row_norm.get('FRACCIONAMERICANA') or '').strip() - extra_desc = (row_norm.get('DESCRIPCION EXTRA') or row_norm.get('DESCRIPCIONEXTRA') or '').strip() - additional_info = (row_norm.get('INFORMACION ADICIONAL') or row_norm.get('INFORMACIONADICIONAL') or '').strip() - lot = (row_norm.get('LOTE') or '').strip() - entry_number = (row_norm.get('NUMERO ENTRADA') or row_norm.get('NUM ENTRADA') or '').strip() - order_compra = (row_norm.get('ORDEN DE COMPRA') or row_norm.get('ORDENCOMPRA') or row_norm.get('ORDEN DE VENTA') or None) - - line_data = LineItemCreate( - invoice_id=invoice_id, - line_number=line_num, - part_number_id=part_id, - class_id=None, - unit_of_measure=uom_id, - order=order_compra, - tax_payment=(se_pago == 'SI'), - payment_method=forma_pago, - financial=LineFinancialCreate( - unit_cost_capture=decimal_or_zero(price), - total_commercial_value=decimal_or_zero(commercial_total), - ), - quantity=LineQuantityCreate( - quantity=decimal_or_zero(qty), - net_weight=decimal_or_zero(net_w), - gross_weight=decimal_or_zero(gross_w), - package_quantity=int_or_zero(row_norm.get('CANTIDAD BULTOS') or row_norm.get('CANTIDADBULTOS')), - package_id=package_id, - ), - customs=LineCustomCreate( - origin_country=origin or None, - fraction=fraction or None, - american_fraction=american_fraction or None, - ), - description=LineDescriptionCreate( - extra_description=extra_desc or None, - additional_info_spanish=additional_info or None, - lot=lot or None, - entry_number=entry_number or None, - ), - fa_data=FaLineItemCreateDTO( - search_invoice=factura_impo or None, - search_line=parse_int(linea_impo_val), - search_type=tipo_impo or None, - download=(descarga_val == 'SI'), - is_subitem=is_subitem, - contains_subitems=contains_subitems, - subitem_number=parse_int(linea_principal_val) if is_subitem else 0, - ), - ) - if not apply_export_defaults_and_calculations_for_csv( - session, line_data, tenant_id, company_id, line_num - ): - skipped_invalid += 1 - skipped_fk_details.append({"line": i, "invoice": invoice_number, "reason": "Factura sin datos financieros para enriquecer partida de exportación."}) - continue - - item_dict = line_data.model_dump( - exclude={ - "financial", - "quantity", - "customs", - "description", - "reference", - "fa_data", - "series", - "identifiers", - }, - exclude_none=True, - ) - item_dict.pop("identifiers", None) - item_dict["tenant_id"] = tenant_id - item_dict["company_id"] = company_id - item_dict["line_number"] = line_num - line = LineItem(**item_dict) - session.add(line) - session.flush() - ItemService._create_line_nested_data(session, line, line_data, tenant_id, company_id) - - session.add(InvoiceSalesDetails( - invoice_id=invoice_id, - line_number=line_num, - sales_order=order_compra, - line_bundles=int_or_zero(row_norm.get('CANTIDAD BULTOS') or row_norm.get('CANTIDADBULTOS')), - tenant_id=tenant_id, - company_id=company_id, - )) - details_to_insert.append(line) - continue - - part_num = (row_norm.get('NUM. PARTE') or row_norm.get('NUMPARTE') or row_norm.get('NUMERO PARTE') or row_norm.get('NUM PARTE') or '').strip() - require_part_number = company_rfc_insert in {"CTE980130518"} - if require_part_number and not part_num: - skipped_invalid += 1 - reason = "NUMPARTE: Requerido por regla de RFC" - skipped_fk_details.append({"line": i, "invoice": invoice_number, "reason": reason}) - logger.warning(f"Row {i} (Invoice {invoice_number}): {reason}") - continue - if part_num and not validator.check_exists(Part, part_num, field_name="part_number"): - skipped_missing_fk += 1 - reason = f"NUMPARTE '{part_num}' no existe" - skipped_fk_details.append({"line": i, "invoice": invoice_number, "reason": reason}) - logger.warning(f"Row {i} (Invoice {invoice_number}): {reason}") - continue - - # --- Prevent Duplicates: Clear existing line items for this invoice (Once per job) --- - if invoice_id not in cleared_invoices: - logger.info(f"Clearing existing details for Invoice {invoice_number} (ID: {invoice_id}) to prevent duplicates") - clear_existing_invoice_details(invoice_id) - cleared_invoices.add(invoice_id) - - # --- Partidas importación: paridad con flujo normal (validators + ItemService) --- - # LineReference: solo se crea si line_data.reference viene informado; no inventar datos sin fuente (plan paridad CSV). - # Build LineItemCreate from CSV, apply import defaults/calculations, then persist. - part_num = (row_norm.get('NUM. PARTE') or row_norm.get('NUMPARTE') or row_norm.get('NUMERO PARTE') or row_norm.get('NUM PARTE') or '').strip() - part_id = part_cache.get(part_num) if part_num else None - if part_id is None and part_num: - p = session.query(Part.id).filter(Part.part_number == part_num, Part.tenant_id == tenant_id, Part.company_id == company_id).first() - if p: - part_id = p.id - part_cache[part_num] = part_id - - line_num_val = (row_norm.get('LINEA') or row_norm.get('RENGLON') or row_norm.get('PARTIDA')) - line_num = parse_int(line_num_val) or (len(details_to_insert) + 1) - - class_code = (row_norm.get('CLASE') or '').strip().upper() - class_id = class_id_by_code.get(class_code) if class_code else None - uom_code = (row_norm.get('UNIDAD DE MEDIDA') or row_norm.get('UNIDAD MEDIDA') or '').strip().upper() - if not uom_code and class_code: - uom_code = class_uom_by_code.get(class_code) or '' - uom_id = uom_id_by_code.get(uom_code) if uom_code else None - bulk_key = (row_norm.get('CLAVE BULTOS') or row_norm.get('CLAVEBULTOS') or '').strip() - package_id = package_id_by_key.get(bulk_key) if bulk_key else None - - price = parse_decimal(row_norm.get('COSTO UNITARIO') or row_norm.get('PRECIO UNITARIO') or row_norm.get('PRECIOUNITARIO')) - if price is None: - total_val = parse_decimal(row_norm.get('TOTAL')) - qty = parse_decimal(row_norm.get('CANTIDAD IMPORTADA') or row_norm.get('CANTIDAD')) - price = (total_val / qty) if (total_val and qty and qty != 0) else None - qty = parse_decimal(row_norm.get('CANTIDAD IMPORTADA') or row_norm.get('CANTIDAD')) - commercial_total = (price * qty) if price and qty else parse_decimal(row_norm.get('TOTAL')) - net_w = parse_decimal(row_norm.get('PESO NETO') or row_norm.get('PESONETO')) - gross_w = parse_decimal(row_norm.get('PESO BRUTO') or row_norm.get('PESOBRUTO')) - - origin = (row_norm.get('PAIS ORIGEN') or row_norm.get('PAISORIGEN') or row_norm.get('PAIS') or '').strip() - fraction = (row_norm.get('FRACCION ARANCELARIA') or row_norm.get('FRACCION') or row_norm.get('FRACCIONARANCELARIA') or '').strip() - if not fraction and class_code: - fraction = class_fraction_by_code.get(class_code) or '' - fraction_type_raw = row_norm.get('PREFERENCIA ARANCELARIA') or row_norm.get('PREFERENCIA') or '' - fraction_type = normalize_preferencia_arancelaria_commit(fraction_type_raw) - sector = (row_norm.get('SECTOR') or '').strip() - american_fraction = (row_norm.get('FRACCION AMERICANA') or row_norm.get('FRACCIONAMERICANA') or '').strip() - - desc_es = (row_norm.get('DESCRIPCION ESPAÑOL') or row_norm.get('DESCRIPCIONE') or row_norm.get('DESCRIPCION') or '').strip() - if not desc_es and class_code: - desc_es = class_desc_es_by_code.get(class_code) or '' - desc_en = (row_norm.get('DESCRIPCION INGLES') or row_norm.get('DESCRIPCIONI') or '').strip() - if not desc_en and class_code: - desc_en = class_desc_en_by_code.get(class_code) or '' - brand = (row_norm.get('MARCA') or '').strip() - model = (row_norm.get('MODELO') or '').strip() - extra_desc = (row_norm.get('DESCRIPCION EXTRA') or row_norm.get('DESCRIPCIONEXTRA') or '').strip() - additional_info = (row_norm.get('INFORMACION ADICIONAL') or row_norm.get('INFORMACIONADICIONAL') or '').strip() - lot = (row_norm.get('LOTE') or '').strip() - entry_number = (row_norm.get('NUMERO ENTRADA') or row_norm.get('NUMEROENTRADA') or row_norm.get('NUM ENTRADA') or '').strip() - - line_data = LineItemCreate( - invoice_id=invoice_id, - line_number=line_num, - part_number_id=part_id, - class_id=class_id, - unit_of_measure=uom_id, - order=(row_norm.get('ORDEN DE COMPRA') or row_norm.get('ORDENCOMPRA') or None), - material_type=(row_norm.get('ID TYPE') or row_norm.get('IDTYPE') or None), - tax_payment=(str(row_norm.get('SE PAGO IMPUESTO') or row_norm.get('SEPAGOIMPUESTO') or '').strip().upper() == 'SI'), - payment_method=(row_norm.get('FORMA DE PAGO') or row_norm.get('FORMADEPAGO') or row_norm.get('FORMA PAGO') or None), - valuation_method=(row_norm.get('METODO DE VALORACION') or row_norm.get('METODODEVALORACION') or row_norm.get('METODO VALORACION') or None), - financial=LineFinancialCreate( - unit_cost_capture=decimal_or_zero(price), - total_commercial_value=decimal_or_zero(commercial_total), - ), - quantity=LineQuantityCreate( - quantity=decimal_or_zero(qty), - net_weight=decimal_or_zero(net_w), - gross_weight=decimal_or_zero(gross_w), - package_quantity=int_or_zero(row_norm.get('CANTIDAD BULTOS') or row_norm.get('CANTIDADBULTOS')), - package_id=package_id, - ), - customs=LineCustomCreate( - origin_country=origin or None, - fraction=fraction or None, - fraction_type=fraction_type or None, - sector=sector or None, - american_fraction=american_fraction or None, - ), - description=LineDescriptionCreate( - description_spanish=desc_es or None, - description_english=desc_en or None, - brand=brand or None, - model=model or None, - extra_description=extra_desc or None, - additional_info_spanish=additional_info or None, - lot=lot or None, - entry_number=entry_number or None, - ), - fa_data=FaLineItemCreateDTO(is_subitem=False, contains_subitems=False), - ) - if not apply_import_defaults_and_calculations_for_csv( - session, line_data, tenant_id, company_id, line_num - ): - skipped_invalid += 1 - skipped_fk_details.append({"line": i, "invoice": invoice_number, "reason": "Factura sin datos financieros/logísticos para enriquecer partida."}) - continue - - item_dict = line_data.model_dump( - exclude={ - "financial", - "quantity", - "customs", - "description", - "reference", - "fa_data", - "series", - "identifiers", - }, - exclude_none=True, - ) - item_dict.pop("identifiers", None) - item_dict["tenant_id"] = tenant_id - item_dict["company_id"] = company_id - item_dict["line_number"] = line_num - line = LineItem(**item_dict) - session.add(line) - session.flush() - ItemService._create_line_nested_data(session, line, line_data, tenant_id, company_id) - session.add(InvoiceSalesDetails( - invoice_id=invoice_id, - line_number=line_num, - sales_order=(row_norm.get('ORDEN DE COMPRA') or row_norm.get('ORDENCOMPRA') or None), - line_bundles=int_or_zero(row_norm.get('CANTIDAD BULTOS') or row_norm.get('CANTIDADBULTOS')), - tenant_id=tenant_id, - company_id=company_id, - )) - details_to_insert.append(line) - - # 3. Bulk Insert (ORM Transaction) - try: - if model_target == 'invoice_header': - if headers_to_insert: - logger.info(f"Attempting to commit {len(headers_to_insert)} headers") - session.add_all(headers_to_insert) - session.commit() - inserted_count = len(headers_to_insert) - logger.info(f"Headers commit successful. Inserted: {inserted_count}") - else: - logger.warning(f"No headers to insert for job {job_id}") - else: - if details_to_insert: - logger.info(f"Attempting to commit {len(details_to_insert)} items and related data") - session.commit() # Everything was already added with session.add() - inserted_count = len(details_to_insert) - logger.info(f"Details commit successful. Inserted: {inserted_count}") - else: - logger.warning(f"No details to insert for job {job_id}") - - except Exception as db_err: - session.rollback() - logger.error(f"DB Error during {model_target} commit: {db_err}") - import traceback - logger.error(traceback.format_exc()) - return {"status": "failed", "error": str(db_err)} - - # 4. Determine final status and prepare response (inside session block to access variables) - total_skipped = skipped_invalid + skipped_missing_fk + skipped_missing_invoice - - # Log summary - logger.info(f"Job {job_id} completed. Inserted: {inserted_count}, Skipped: {total_skipped} " - f"(invalid: {skipped_invalid}, missing_fk: {skipped_missing_fk}, missing_invoice: {skipped_missing_invoice})") - - # Prepare response based on results - if inserted_count == 0: - if total_skipped > 0: - logger.warning(f"No valid records to insert for job {job_id}. All {total_skipped} records were rejected.") - response = { - "status": "warning", - "inserted": 0, - "skipped_invalid": skipped_invalid, - "skipped_missing_invoice": skipped_missing_invoice, - "skipped_missing_fk": skipped_missing_fk, - "skipped_details": skipped_fk_details, - "skipped_summary": _summarize_top_reasons(skipped_fk_details), - "message": f"No se insertaron registros. {total_skipped} fueron rechazados. Revisa el detalle por línea a continuación.", - } - else: - logger.error(f"No valid records found in CSV for job {job_id}") - response = { - "status": "failed", - "error": "No hay registros válidos en el archivo CSV", - "inserted": 0, - "skipped_invalid": skipped_invalid, - "skipped_missing_invoice": skipped_missing_invoice, - "skipped_missing_fk": skipped_missing_fk, - "skipped_details": skipped_fk_details, - "skipped_summary": _summarize_top_reasons(skipped_fk_details), - } - else: - # Success case - at least some records were inserted - response = { - "status": "finished", - "inserted": inserted_count, - "skipped_invalid": skipped_invalid, - "skipped_missing_invoice": skipped_missing_invoice, - "skipped_missing_fk": skipped_missing_fk, - "skipped_details": skipped_fk_details, - "skipped_summary": _summarize_top_reasons(skipped_fk_details), - } - - except Exception as e: - logger.error(f"Task failed: {e}") - import traceback - logger.error(traceback.format_exc()) - return {"status": "failed", "error": str(e)} - - # 5. Cleanup: remove temp files and Redis keys so data is not kept indefinitely - try: - common_storage.cleanup_import_job( - effective_job_type, job_id, - file_path=file_path, - error_path=error_path, - meta_path=meta_path, - ) - except Exception as cleanup_err: - logger.warning("Failed to cleanup temp files or Redis: %s", cleanup_err) - - # Ensure response is defined (fallback in case of unexpected errors) - if response is None: - logger.error(f"Unexpected error: response not set for job {job_id}") - response = { - "status": "failed", - "error": "Error inesperado durante el procesamiento", - "inserted": 0, - "skipped_invalid": skipped_invalid, - "skipped_missing_invoice": skipped_missing_invoice, - "skipped_missing_fk": skipped_missing_fk, - "skipped_details": skipped_fk_details, - "skipped_summary": _summarize_top_reasons(skipped_fk_details), - } - - return response diff --git a/backend/api/v1/modules/a76/layouts_csv/facturas/template_config.py b/backend/api/v1/modules/a76/layouts_csv/facturas/template_config.py deleted file mode 100644 index a1158fe1..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/facturas/template_config.py +++ /dev/null @@ -1,330 +0,0 @@ -""" -Configuración de plantillas CSV: columnas que trae cada plantilla y cómo se mapean. -La plantilla se respeta tal cual: solo se leen columnas definidas aquí; el resto se ignora. -Solo se escribe en BD lo que los modelos de facturas aceptan (respetando models). -""" - -from typing import Dict, List, Any, Optional - -from ..common.cell_value import cell_to_str -from ..common.inject_csv_labels_en import inject_en_labels_into_template_columns -from ..common.template_locale import merge_download_header_into_lookup, merge_labels_into_lookup -from .invoice_csv_column_hints import apply_invoice_csv_hints - -# Cada plantilla define sus columnas canónicas y alias (otros nombres que aceptamos en el CSV). -# canonical = nombre estándar con el que trabajamos internamente; debe coincidir con lo que -# espera la lógica de validación e insert (tasks.py). -# aliases = cabeceras alternativas que la plantilla .xls puede traer (ej. "Num Factura" → NUM FACTURA). - -TEMPLATE_COLUMNS: Dict[str, List[Dict[str, Any]]] = { - # --- Encabezado factura: Impo Temp (EstructuraEncFacImpoTemp.xls) - Clarion A-AD --- - "imp_temp_header": [ - {"canonical": "PEDIMENTO", "aliases": ["NUMERO PEDIMENTO", "PEDIMENTO NUMERO", "NUMERO DE PEDIMENTO", "PED", "NO. PEDIMENTO", "PEDIMENTO NO.", "NO PEDIMENTO"]}, - {"canonical": "REMESA"}, - {"canonical": "NUMERO FACTURA", "aliases": ["NUM FACTURA", "FACTURA", "ID"]}, - {"canonical": "FECHA FACTURA", "aliases": ["FECHA"]}, - {"canonical": "TIPO DE CAMBIO"}, - # Clarion columna F — ITE/ITR cuando el pedimento aún no está en catálogo (pendiente de asignar). - {"canonical": "REGIMEN", "aliases": ["CLAVEDOCUMENTO"]}, - {"canonical": "CLAVE PROVEEDOR"}, - {"canonical": "CLAVE VENDIDO A"}, - {"canonical": "CLAVE ENVIADO A"}, - {"canonical": "AGENTE ADUANAL"}, - {"canonical": "CLAVE TRANSPORTISTA"}, - {"canonical": "NOMBRE CONDUCTOR"}, - {"canonical": "TIPO TRANSPORTE"}, - {"canonical": "CLAVE TRANSPORTE"}, - {"canonical": "NUMERO CAJA"}, - {"canonical": "NUMERO TRANSPORTE"}, - {"canonical": "TIPO MONEDA"}, - {"canonical": "CLAVE MONEDA"}, - {"canonical": "FLETES"}, - {"canonical": "VALOR SEGUROS"}, - {"canonical": "SEGUROS"}, - {"canonical": "EMBALAJES"}, - {"canonical": "OTROS INCREMENTABLES"}, - {"canonical": "CLAVE INCOTERM"}, - {"canonical": "PRECINTO"}, - {"canonical": "FECHA EMISION"}, - {"canonical": "TIPO PESO"}, - {"canonical": "E DOCUMENT", "aliases": ["E-DOCUMENT", "EDOCUMENT", "E DOCUMENT"]}, - {"canonical": "NUM OPERACION", "aliases": ["NUM. OPERACION", "NUMOPERACION", "NUM OPERACION"]}, - {"canonical": "ADUANA DE CRUCE", "aliases": ["ADUANA", "ADUANA CRUCE", "ADUANA DE CRUCE.", "ADUANA DE CRUCE / SECCION"]}, - {"canonical": "OBSERVACIONES E"}, - {"canonical": "OBSERVACIONES I"}, - {"canonical": "FACTURA ALTERNA"}, - {"canonical": "NUM PROYECTO", "aliases": ["NUMPROYECTO"]}, - {"canonical": "ORDEN COMPRA", "aliases": ["ORDENCOMPRA"]}, - {"canonical": "FACTURA EXPO REF", "aliases": ["FACTURAEXPOREF"]}, - ], - # --- Encabezado factura: Impo Def (EstructuraEncFacImpoDef.xls) - 30 columnas Clarion --- - "imp_def_header": [ - {"canonical": "PEDIMENTO", "aliases": ["NUMERO PEDIMENTO", "PEDIMENTO NUMERO", "NUMERO DE PEDIMENTO", "PED", "NO. PEDIMENTO", "PEDIMENTO NO.", "NO PEDIMENTO"]}, - {"canonical": "REMESA"}, - {"canonical": "NUMERO FACTURA", "aliases": ["NUM FACTURA", "FACTURA", "ID"]}, - {"canonical": "FECHA FACTURA", "aliases": ["FECHA"]}, - {"canonical": "TIPO DE CAMBIO"}, - {"canonical": "REGIMEN", "aliases": ["CLAVEDOCUMENTO"]}, - {"canonical": "CLAVE PROVEEDOR"}, - {"canonical": "CLAVE VENDIDO A", "aliases": ["CLAVE VENDIDO A:"]}, - {"canonical": "CLAVE ENVIADO A"}, - {"canonical": "AGENTE ADUANAL"}, - {"canonical": "CLAVE TRANSPORTISTA"}, - {"canonical": "NOMBRE CONDUCTOR"}, - {"canonical": "TIPO TRANSPORTE"}, - {"canonical": "CLAVE TRANSPORTE"}, - {"canonical": "NUMERO CAJA"}, - {"canonical": "NUMERO TRANSPORTE"}, - {"canonical": "TIPO MONEDA"}, - {"canonical": "CLAVE MONEDA"}, - {"canonical": "FLETES"}, - {"canonical": "VALOR SEGUROS"}, - {"canonical": "SEGUROS"}, - {"canonical": "EMBALAJES"}, - {"canonical": "OTROS INCREMENTABLES"}, - {"canonical": "CLAVE INCOTERM"}, - {"canonical": "PRECINTO"}, - {"canonical": "FECHA EMISION"}, - {"canonical": "TIPO PESO"}, - {"canonical": "E DOCUMENT", "aliases": ["E-DOCUMENT", "EDOCUMENT", "E DOCUMENT"]}, - {"canonical": "NUM OPERACION", "aliases": ["NUM. OPERACION", "NUMOPERACION", "NUM OPERACION"]}, - {"canonical": "ADUANA DE CRUCE", "aliases": ["ADUANA", "ADUANA CRUCE", "ADUANA DE CRUCE.", "ADUANA DE CRUCE / SECCION"]}, - {"canonical": "OBSERVACIONES E"}, - {"canonical": "OBSERVACIONES I"}, - ], - # --- Encabezado factura: Expo (EstructuraEncFacExpoCamReg.xls) - Clarion A-AE --- - # PEDIMENTO, REMESA, NUMERO FACTURA, FECHA FACTURA, TIPO DE CAMBIO, REGIMEN, CLAVE PROVEEDOR, - # CLAVE VENDIDO A:, CLAVE ENVIADO A, AGENTE ADUANAL, ... MANIFIESTO, E-DOCUMENT, NUM. OPERACION, - # ENVIADO POR, ADUANA DE CRUCE, OBSERVACIONES E, OBSERVACIONES I, FACTURA ALTERNA - "exp_def_header": [ - {"canonical": "PEDIMENTO", "aliases": ["NUMERO PEDIMENTO", "PEDIMENTO NUMERO", "NUMERO DE PEDIMENTO", "PED", "NO. PEDIMENTO", "PEDIMENTO NO.", "NO PEDIMENTO"]}, - {"canonical": "REMESA"}, - {"canonical": "NUMERO FACTURA", "aliases": ["NUM FACTURA", "FACTURA", "ID"]}, - {"canonical": "FECHA FACTURA", "aliases": ["FECHA"]}, - {"canonical": "TIPO DE CAMBIO"}, - {"canonical": "REGIMEN", "aliases": ["CLAVEDOCUMENTO"]}, - {"canonical": "CLAVE PROVEEDOR"}, - {"canonical": "CLAVE VENDIDO A", "aliases": ["CLAVE VENDIDO A:"]}, - {"canonical": "CLAVE ENVIADO A"}, - {"canonical": "AGENTE ADUANAL"}, - {"canonical": "CLAVE TRANSPORTISTA"}, - {"canonical": "NOMBRE CONDUCTOR"}, - {"canonical": "TIPO TRANSPORTE"}, - {"canonical": "CLAVE TRANSPORTE"}, - {"canonical": "NUMERO CAJA"}, - {"canonical": "NUMERO TRANSPORTE"}, - {"canonical": "TIPO MONEDA"}, - {"canonical": "CLAVE MONEDA"}, - {"canonical": "FLETES"}, - {"canonical": "VALOR SEGUROS"}, - {"canonical": "SEGUROS"}, - {"canonical": "EMBALAJES"}, - {"canonical": "OTROS INCREMENTABLES"}, - {"canonical": "CLAVE INCOTERM"}, - {"canonical": "PRECINTO"}, - {"canonical": "TIPO PESO"}, - {"canonical": "MANIFIESTO"}, - {"canonical": "E DOCUMENT", "aliases": ["E-DOCUMENT", "EDOCUMENT", "E DOCUMENT"]}, - {"canonical": "NUM OPERACION", "aliases": ["NUM. OPERACION", "NUMOPERACION", "NUM OPERACION"]}, - {"canonical": "ENVIADO POR"}, - {"canonical": "ADUANA DE CRUCE", "aliases": ["ADUANA", "ADUANA CRUCE", "ADUANA DE CRUCE.", "ADUANA DE CRUCE / SECCION"]}, - {"canonical": "OBSERVACIONES E"}, - {"canonical": "OBSERVACIONES I"}, - {"canonical": "FACTURA ALTERNA"}, - ], - # --- Encabezado factura: Compras Mexicanas (Clarion VALIDA_TODA_FAC_COM_MEX / VALIDA_PARCIAL) --- - # Estructura CSV: A,B=CAPTURAR CMEX; C=NUMERO FACTURA; D=FECHA FACTURA; E=TIPO DE CAMBIO; F=CAPTURAR CMEX; - # G=CLAVE PROVEEDOR; H=CLAVE VENDIDO A; I=CLAVE ENVIADO A; J=CAPTURAR CMEX; K=CLAVE TRANSPORTISTA; ...; Z=OBSERVACIONES E - "cmex_header": [ - {"canonical": "NUMERO FACTURA", "aliases": ["NUM FACTURA", "FACTURA"]}, - {"canonical": "FECHA FACTURA", "aliases": ["FECHA"]}, - {"canonical": "TIPO DE CAMBIO"}, - {"canonical": "CLAVE PROVEEDOR"}, - {"canonical": "CLAVE VENDIDO A", "aliases": ["CLAVE VENDIDO A:"]}, - {"canonical": "CLAVE ENVIADO A"}, - {"canonical": "CLAVE TRANSPORTISTA"}, - {"canonical": "NOMBRE CONDUCTOR"}, - {"canonical": "TIPO TRANSPORTE"}, - {"canonical": "CLAVE TRANSPORTE"}, - {"canonical": "NUMERO CAJA"}, - {"canonical": "NUMERO TRANSPORTE"}, - {"canonical": "TIPO MONEDA"}, - {"canonical": "CLAVE MONEDA"}, - {"canonical": "FLETES"}, - {"canonical": "VALOR SEGUROS"}, - {"canonical": "SEGUROS"}, - {"canonical": "EMBALAJES"}, - {"canonical": "OTROS INCREMENTABLES"}, - {"canonical": "CLAVE INCOTERM"}, - {"canonical": "PRECINTO"}, - {"canonical": "FECHA EMISION"}, - {"canonical": "TIPO PESO"}, - {"canonical": "OBSERVACIONES E"}, - ], - # --- Partidas factura: Impo Temp (EstructuraParFacImpoTemp - paridad Clarion A-AG) --- - "imp_temp_details": [ - {"canonical": "NUMERO FACTURA", "aliases": ["NUM FACTURA", "FACTURA"]}, - {"canonical": "LINEA", "aliases": ["RENGLON", "PARTIDA"]}, - {"canonical": "CLASE"}, - {"canonical": "CANTIDAD IMPORTADA", "aliases": ["CANTIDAD"]}, - {"canonical": "UNIDAD DE MEDIDA", "aliases": ["UNIDAD MEDIDA"]}, - {"canonical": "COSTO UNITARIO", "aliases": ["COSTOUNITARIO", "PRECIO UNITARIO", "PRECIOUNITARIO"]}, - {"canonical": "PESO NETO", "aliases": ["PESONETO"]}, - {"canonical": "PESO BRUTO", "aliases": ["PESOBRUTO"]}, - {"canonical": "CANTIDAD BULTOS", "aliases": ["CANTIDADBULTOS"]}, - {"canonical": "CLAVE BULTOS", "aliases": ["CLAVEBULTOS"]}, - {"canonical": "PAIS ORIGEN", "aliases": ["PAISORIGEN", "PAIS"]}, - {"canonical": "FRACCION ARANCELARIA", "aliases": ["FRACCION", "FRACCIONARANCELARIA"]}, - {"canonical": "PREFERENCIA ARANCELARIA", "aliases": ["PREFERENCIA", "PREFERENCIAARANCELARIA"]}, - {"canonical": "SECTOR"}, - {"canonical": "FRACCION AMERICANA", "aliases": ["FRACCIONAMERICANA"]}, - {"canonical": "ORDEN DE COMPRA", "aliases": ["ORDENCOMPRA", "ORDEN COMPRA"]}, - {"canonical": "DESCRIPCION ESPAÑOL", "aliases": ["DESCRIPCIONE", "DESCRIPCION"]}, - {"canonical": "DESCRIPCION INGLES", "aliases": ["DESCRIPCION INGLES", "DESCRIPCIONI"]}, - {"canonical": "MARCA"}, - {"canonical": "MODELO"}, - {"canonical": "ES PARTIDA O SUBPARTIDA", "aliases": ["ES PARTIDA O SUBPARTIDA", "ESSUBPARTIDA"]}, - {"canonical": "LINEA PRINCIPAL", "aliases": ["LINEAPRINCIPAL", "PARTIDA PRINCIPAL"]}, - {"canonical": "NUM. PARTE", "aliases": ["NUMPARTE", "NUMERO PARTE", "NUM PARTE"]}, - {"canonical": "SE PAGO IMPUESTO", "aliases": ["SE PAGO IMPUESTO", "SEPAGOIMPUESTO"]}, - {"canonical": "FORMA DE PAGO", "aliases": ["FORMADEPAGO", "FORMA PAGO"]}, - {"canonical": "METODO DE VALORACION", "aliases": ["METODODEVALORACION", "METODO VALORACION"]}, - {"canonical": "DESCRIPCION EXTRA", "aliases": ["DESCRIPCIONEXTRA"]}, - {"canonical": "INFORMACION ADICIONAL", "aliases": ["INFORMACIONADICIONAL"]}, - {"canonical": "AGREGAR/SUSTITUIR", "aliases": ["AGREGAR SUSTITUIR", "SUSTITUIR"]}, - {"canonical": "TOTAL"}, - {"canonical": "NUMERO ENTRADA", "aliases": ["NUMEROENTRADA", "NUM ENTRADA"]}, - {"canonical": "LOTE"}, - {"canonical": "ID TYPE", "aliases": ["IDTYPE"]}, - ], - # --- Partidas: Impo Def y Expo - misma estructura --- - "imp_def_details": None, - "exp_def_details": None, - # --- Partidas exportación definitiva (Clarion EstructuraParExpoCamReg A–V) --- - "exp_def_partidas": [ - {"canonical": "NUMERO FACTURA EXPO", "aliases": ["NUMERO FACTURA EXPO.", "NUM FACTURA EXPO", "FACTURA EXPO"]}, - {"canonical": "LINEA EXPO", "aliases": ["LINEA EXPO.", "RENGLON EXPO"]}, - {"canonical": "TIPO DE IMPO", "aliases": ["TIPO DE IMPO.", "TIPO IMPO", "PROCEDENCIA"]}, - {"canonical": "FACTURA IMPO", "aliases": ["FACTURA IMPO.", "FACTURA IMPORTACION"]}, - {"canonical": "LINEA IMPO", "aliases": ["LINEA IMPO.", "LINEA IMPORTACION"]}, - {"canonical": "GENERA DESCARGA", "aliases": ["GENERA DESCARGA?", "DESCARGA"]}, - {"canonical": "CANTIDAD EXPORTADA/DESCARGAR", "aliases": ["CANTIDAD EXPORTADA", "CANTIDAD EXPORTADA/DESCARGAR", "CANTIDAD"]}, - {"canonical": "UNIDAD DE MEDIDA", "aliases": ["U.M.", "UNIDAD MEDIDA"]}, - {"canonical": "COSTO UNITARIO", "aliases": ["COSTOUNITARIO"]}, - {"canonical": "PESO NETO", "aliases": ["PESONETO"]}, - {"canonical": "PESO BRUTO", "aliases": ["PESOBRUTO"]}, - {"canonical": "SE PAGO IMPUESTO", "aliases": ["SE PAGO IMPUESTO? (SI o NO)", "SEPAGOIMPUESTO"]}, - {"canonical": "FORMA DE PAGO", "aliases": ["FORMADEPAGO", "FORMA PAGO"]}, - {"canonical": "DESCRIPCION EXTRA", "aliases": ["DESCRIPCION EXTRA", "DESCRIPCIONEXTRA"]}, - {"canonical": "INFORMACION ADICIONAL", "aliases": ["INFORMACION ADICIONAL", "INFORMACIONADICIONAL"]}, - {"canonical": "AGREGAR(A)/SUSTITUIR(S)", "aliases": ["AGREGAR(A)/SUSTITUIR(S)", "AGREGAR/SUSTITUIR", "SUSTITUIR"]}, - {"canonical": "LOTE"}, - {"canonical": "NUMERO ENTRADA", "aliases": ["NUMERO ENTRADA", "NUM ENTRADA"]}, - {"canonical": "ES PARTIDA/SUBPARTIDA", "aliases": ["ES PARTIDA/SUBPARTIDA", "ESSUBPARTIDA", "ES PARTIDA O SUBPARTIDA"]}, - {"canonical": "LINEA PRINCIPAL", "aliases": ["LINEAPRINCIPAL", "PARTIDA PRINCIPAL"]}, - {"canonical": "FRACCION AMERICANA", "aliases": ["FRACCION AMERICANA", "FRACCIONAMERICANA"]}, - {"canonical": "FRACCION ARANCELARIA", "aliases": ["FRACCION ARANCELARIA", "FRACCIONARANCELARIA"]}, - {"canonical": "CANTIDAD BULTOS", "aliases": ["CANTIDADBULTOS"]}, - {"canonical": "CLAVE BULTOS", "aliases": ["CLAVEBULTOS"]}, - {"canonical": "PAIS ORIGEN", "aliases": ["PAISORIGEN", "PAIS"]}, - {"canonical": "NUM. PARTE", "aliases": ["NUMPARTE", "NUMERO PARTE", "NUM PARTE"]}, - {"canonical": "ORDEN DE COMPRA", "aliases": ["ORDENCOMPRA", "ORDEN DE VENTA"]}, - ], - # --- Series de Importación Temporal (EstructuraSeriesFacImpoTemp.xls) --- - # Clarion: NUMERO FACTURA, LINEA FACTURA, LINEA SERIE, SERIE, MODELO, NUM PARTE, SUB MODELO, NUMERO ID - "imp_temp_series": [ - {"canonical": "NUMERO FACTURA", "aliases": ["NUM FACTURA", "FACTURA"]}, - {"canonical": "LINEA FACTURA", "aliases": ["LINEA", "PARTIDA"]}, - {"canonical": "LINEA SERIE", "aliases": ["RENGLON"]}, - {"canonical": "SERIE", "aliases": ["NUMERO SERIE"]}, - {"canonical": "MODELO"}, - {"canonical": "NUM PARTE", "aliases": ["NUMPARTE", "NUMERO PARTE"]}, - {"canonical": "SUB MODELO", "aliases": ["SUBMODELO"]}, - {"canonical": "NUMERO ID", "aliases": ["NUMEROID"]}, - {"canonical": "COL_EXTRA"}, # Optional; if has value → desfase warning (Clarion) - ], - # --- Series de Importación Definitiva (misma estructura que TEM; cabeceras imagen: NUMERO/LINEA FAC, LINEA SER, etc.) --- - "imp_def_series": [ - {"canonical": "NUMERO FACTURA", "aliases": ["NUM FACTURA", "FACTURA", "NUMERO / LINEA FAC"]}, - {"canonical": "LINEA FACTURA", "aliases": ["LINEA", "PARTIDA"]}, - {"canonical": "LINEA SERIE", "aliases": ["RENGLON", "LINEA SER"]}, - {"canonical": "SERIE", "aliases": ["NUMERO SERIE"]}, - {"canonical": "MODELO"}, - {"canonical": "NUM PARTE", "aliases": ["NUMPARTE", "NUMERO PARTE", "NUM PART"]}, - {"canonical": "SUB MODELO", "aliases": ["SUBMODELO", "SUB MODE"]}, - {"canonical": "NUMERO ID", "aliases": ["NUMEROID"]}, - {"canonical": "COL_EXTRA"}, - ], - # --- Series de Exportación Definitiva (Clarion VALIDA_TODA_SERIES_EXPO / VALIDA_PARCIAL_SERIES_EXPO) --- - # Misma estructura: NUMERO FACTURA, LINEA FACTURA, LINEA SERIE, SERIE, MODELO, NUM PARTE, SUB MODELO, NUMERO ID - "exp_def_series": [ - {"canonical": "NUMERO FACTURA", "aliases": ["NUM FACTURA", "FACTURA", "FACTURA EXPO"]}, - {"canonical": "LINEA FACTURA", "aliases": ["LINEA", "PARTIDA"]}, - {"canonical": "LINEA SERIE", "aliases": ["RENGLON", "LINEA SER"]}, - {"canonical": "SERIE", "aliases": ["NUMERO SERIE"]}, - {"canonical": "MODELO"}, - {"canonical": "NUM PARTE", "aliases": ["NUMPARTE", "NUMERO PARTE", "NUM PART"]}, - {"canonical": "SUB MODELO", "aliases": ["SUBMODELO", "SUB MODE"]}, - {"canonical": "NUMERO ID", "aliases": ["NUMEROID"]}, - {"canonical": "COL_EXTRA"}, - ], -} - -inject_en_labels_into_template_columns(TEMPLATE_COLUMNS) - -apply_invoice_csv_hints(TEMPLATE_COLUMNS) - - -def _resolve_template_columns(template_id: str) -> Optional[List[Dict[str, Any]]]: - cols = TEMPLATE_COLUMNS.get(template_id) - if cols is not None: - return cols - if template_id == "imp_def_header": - return TEMPLATE_COLUMNS.get("imp_temp_header") - if template_id in ("imp_def_details", "exp_def_details"): - return TEMPLATE_COLUMNS.get("imp_temp_details") - if template_id == "exp_def_partidas": - return TEMPLATE_COLUMNS.get("exp_def_partidas") - if template_id == "cmex_details": - return TEMPLATE_COLUMNS.get("imp_temp_details") - if template_id == "cmex_series": - return TEMPLATE_COLUMNS.get("imp_def_series") - if template_id == "exp_def_series": - return TEMPLATE_COLUMNS.get("exp_def_series") - return None - - -def build_normalized_lookup(template_id: str, normalize_header_fn) -> Dict[str, str]: - """ - Construye un diccionario: normalized_header -> canonical_name. - normalize_header_fn(str) -> str debe ser la función que normaliza cabeceras (ej. mayúsculas, sin acentos). - """ - cols = _resolve_template_columns(template_id) - if not cols: - return {} - lookup: Dict[str, str] = {} - for item in cols: - canonical = item["canonical"] - lookup[normalize_header_fn(canonical)] = canonical - for alias in item.get("aliases") or []: - lookup[normalize_header_fn(alias)] = canonical - merge_labels_into_lookup(item, canonical, normalize_header_fn, lookup) - merge_download_header_into_lookup(item, canonical, normalize_header_fn, lookup) - return lookup - - -def row_from_template(row: Dict[str, Any], template_id: str, normalize_header_fn) -> Dict[str, Any]: - """ - A partir de una fila CSV (dict header->value) y un template_id, devuelve un dict - solo con las columnas de la plantilla, usando nombres canónicos. - Así la plantilla se respeta: solo entran columnas definidas en la plantilla. - """ - lookup = build_normalized_lookup(template_id, normalize_header_fn) - if not lookup: - return {normalize_header_fn(k): cell_to_str(v) for k, v in row.items()} - out: Dict[str, Any] = {} - for csv_header, value in row.items(): - key_norm = normalize_header_fn(csv_header) - if key_norm in lookup: - out[lookup[key_norm]] = cell_to_str(value) - return out diff --git a/backend/api/v1/modules/a76/layouts_csv/facturas/validators/__init__.py b/backend/api/v1/modules/a76/layouts_csv/facturas/validators/__init__.py deleted file mode 100644 index 3ec8ac98..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/facturas/validators/__init__.py +++ /dev/null @@ -1,42 +0,0 @@ -# Validators for invoice CSV imports (header, details, series). -from .encabezados_impo_temp import ( - validate_row_encabezados_impo_temp, - row_to_transport_type_clarion, - parse_pedimento_col_a, - _pedimento_key_from_parsed, -) -from .encabezados_impo_def import ( - validate_row_encabezados_impo_def, - parse_pedimento_col_a_impo_def, -) -from .encabezados_cmex import validate_row_encabezados_cmex -from .encabezados_expo import validate_row_encabezados_expo -from .partidas_expo import validate_row_partidas_expo -from .partidas_impo_def import validate_row_partidas_impo_def -from .series_impo_def import ( - validate_row_series_impo_def, - row_to_series_normalized_def, -) -from .series_expo import ( - validate_row_series_expo, - row_to_series_normalized_expo, -) -from .consolidated_invoice_dates import validate_consolidated_invoice_date_csv - -__all__ = [ - "validate_row_encabezados_impo_temp", - "validate_row_encabezados_impo_def", - "validate_row_encabezados_cmex", - "validate_row_encabezados_expo", - "validate_row_partidas_expo", - "validate_row_partidas_impo_def", - "validate_row_series_impo_def", - "row_to_series_normalized_def", - "validate_row_series_expo", - "row_to_series_normalized_expo", - "row_to_transport_type_clarion", - "parse_pedimento_col_a", - "parse_pedimento_col_a_impo_def", - "_pedimento_key_from_parsed", - "validate_consolidated_invoice_date_csv", -] diff --git a/backend/api/v1/modules/a76/layouts_csv/facturas/validators/consolidated_invoice_dates.py b/backend/api/v1/modules/a76/layouts_csv/facturas/validators/consolidated_invoice_dates.py deleted file mode 100644 index 19895328..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/facturas/validators/consolidated_invoice_dates.py +++ /dev/null @@ -1,107 +0,0 @@ -""" -Validación de FECHA FACTURA vs pedimento consolidado en CSV de encabezados. - -Paridad con `validate_common` (factura manual) cuando `pedimento_type == "consolidated"`: -- Sin fechas de pedimento en catálogo → equivalente a MISSING_PEDIMENTO_DATES. -- Rango permitido: [start_date, end_date] si `start_date` está capturada en el catálogo; - si no, [entry_date, end_date] (mismo criterio que captura manual / periodo consolidado). -""" -from datetime import date, datetime -from typing import Any, Dict, Optional - - -def _to_calendar_date(val: Any) -> Optional[date]: - if val is None: - return None - if isinstance(val, datetime): - return val.date() - if isinstance(val, date): - return val - if hasattr(val, "date"): - return val.date() # type: ignore[union-attr] - return None - - -def validate_consolidated_invoice_date_csv( - *, - line_num: int, - pedimento_key_display: str, - pedimento_type: str, - invoice_date_parsed: Optional[datetime], - entry_raw: Any, - end_raw: Any, - start_raw: Any = None, -) -> Optional[Dict[str, Any]]: - """ - Retorna dict de error compatible con validadores CSV (line, col, msg, …) o None si aplica/no hay error. - - No aplica si el pedimento no es consolidado. - """ - pt = (pedimento_type or "").strip().lower() - if pt != "consolidated": - return None - - if end_raw is None: - return { - "line": line_num, - "col": "PEDIMENTO", - "msg": ( - f"Error: (Celda A{line_num}) El Pedimento {pedimento_key_display} no tiene fechas registradas " - "en el catálogo." - ), - "solution": "Verifica las fechas del Pedimento en el catálogo", - "code": "MISSING_PEDIMENTO_DATES", - } - - range_low_raw = start_raw if start_raw is not None else entry_raw - if range_low_raw is None: - return { - "line": line_num, - "col": "PEDIMENTO", - "msg": ( - f"Error: (Celda A{line_num}) El Pedimento {pedimento_key_display} no tiene fechas registradas " - "en el catálogo." - ), - "solution": "Verifica las fechas del Pedimento en el catálogo", - "code": "MISSING_PEDIMENTO_DATES", - } - - if not invoice_date_parsed: - return None - - entry = _to_calendar_date(range_low_raw) - end = _to_calendar_date(end_raw) - if entry is None or end is None: - return { - "line": line_num, - "col": "PEDIMENTO", - "msg": ( - f"Error: (Celda A{line_num}) El Pedimento {pedimento_key_display} no tiene fechas registradas " - "en el catálogo." - ), - "solution": "Verifica las fechas del Pedimento en el catálogo", - "code": "MISSING_PEDIMENTO_DATES", - } - - if isinstance(invoice_date_parsed, datetime): - inv_d = invoice_date_parsed.date() - elif isinstance(invoice_date_parsed, date): - inv_d = invoice_date_parsed - else: - return None - - if inv_d < entry or inv_d > end: - return { - "line": line_num, - "col": "FECHA FACTURA", - "msg": ( - f"Error: (Celda D{line_num} y A{line_num}) La Fecha de la Factura no corresponde al rango " - f"de fechas del Pedimento {pedimento_key_display}." - ), - "solution": ( - "Capturar una Fecha de Factura dentro del rango de fechas del pedimento consolidado " - "registrado en el catálogo." - ), - "code": "DATE_OUT_OF_RANGE", - } - return None diff --git a/backend/api/v1/modules/a76/layouts_csv/facturas/validators/encabezados_cmex.py b/backend/api/v1/modules/a76/layouts_csv/facturas/validators/encabezados_cmex.py deleted file mode 100644 index f46b639b..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/facturas/validators/encabezados_cmex.py +++ /dev/null @@ -1,261 +0,0 @@ -""" -Validaciones CSV para Encabezados de Facturas de Compras Mexicanas. -Paridad Clarion: VALIDA_TODA_FAC_COM_MEX, VALIDA_PARCIAL_FAC_COM_MEX, VALIDACIONES_FAC_COM_MEX. -Sin pedimento, remesa, agente aduanal ni aduana de cruce. -Estructura CSV: NUMERO FACTURA (C), FECHA FACTURA (D), TIPO DE CAMBIO (E), CLAVE PROVEEDOR (G), ... -""" -from datetime import datetime -from decimal import Decimal -from typing import Any, Dict, List, Optional, Set - -from .encabezados_impo_temp import ( - MAX_LEN_FACTURA, - TIPO_PESO_VALIDOS, - TIPOS_MONEDA_VALIDOS, - _clip, - _err, - _get, - _parse_decimal, - _parse_int, - _validaciones_factura_longitud, - _validaciones_moneda, - _validaciones_tipo_cambio, - _validaciones_tipo_peso, -) - -# Clarion Col M: Compras Mexicanas incluye "FERRO BARCAZA" (con espacio) y "PLATAFORMA" -TIPO_TRANSPORTE_VALIDOS_CMEX = frozenset({ - "NINGUNO", "TRANSPORTE", "CAJA", "PLACAS", "CAMION", "BUQUE", - "FERROBARCAZA", "FERRO BARCAZA", "CONTENEDOR", "PLATAFORMA", "AVION", -}) - - -def _validaciones_transporte_cmex(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]: - """Tipo transporte (Col M) y número (Col N). Acepta FERRO BARCAZA y FERROBARCAZA.""" - m_raw = _get(row, "TIPO TRANSPORTE") - m = m_raw.upper().replace(" ", "") if m_raw else "" - m_with_space = m_raw.upper() if m_raw else "" - n = _get(row, "NUMERO TRANSPORTE") - if m_raw and m_with_space not in TIPO_TRANSPORTE_VALIDOS_CMEX and m not in TIPO_TRANSPORTE_VALIDOS_CMEX: - return _err( - line_num, - "TIPO TRANSPORTE", - "Error: (Celda M{}) El Tipo de Transporte: {} no es válido. " - "Válidos: NINGUNO, TRANSPORTE, CAJA, PLACAS, CAMION, BUQUE, FERRO BARCAZA, CONTENEDOR, PLATAFORMA, AVION.".format( - line_num, m_raw - ), - ) - if not m_raw and n: - return _err( - line_num, - "NUMERO TRANSPORTE", - "Error: (Celda N{}) El Tipo de Transporte está vacío y está capturado un número de transporte.".format(line_num), - ) - return None - - -def _validaciones_obligatorios_toda_cmex( - row: Dict[str, Any], - line_num: int, - actualizar: bool, -) -> Optional[Dict[str, Any]]: - """Obligatorios VALIDA_TODA_FAC_COM_MEX: C siempre; si no es actualizar, también D, G, H, I.""" - obligatorios: List[str] = [] - if not _get(row, "NUMERO FACTURA", "NUM FACTURA", "FACTURA"): - obligatorios.append("(Col.C) Factura") - if not actualizar: - if not _get(row, "FECHA FACTURA", "FECHA"): - obligatorios.append("(Col.D) Fecha de la Factura") - if not _get(row, "CLAVE PROVEEDOR"): - obligatorios.append("(Col.G) Clave del Proveedor") - if not _get(row, "CLAVE VENDIDO A"): - obligatorios.append("(Col.H) Clave del Vendido A") - if not _get(row, "CLAVE ENVIADO A"): - obligatorios.append("(Col.I) Clave del Enviado A") - if obligatorios: - return _err( - line_num, - "ARCHIVO CSV", - "Existen campos vacíos que son obligatorios: {}. Revisar la línea del archivo y capturar los campos con la información correcta.".format( - ", ".join(obligatorios) - ), - ) - return None - - -def _validaciones_catalogos_cmex( - row: Dict[str, Any], - line_num: int, - valid_provider_ids: Set[int], - valid_sold_to_ids: Set[int], - valid_shipped_to_ids: Set[int], - valid_provider_short_names: Set[str], - valid_sold_to_short_names: Set[str], - valid_shipped_to_short_names: Set[str], - valid_transporter_keys: Set[str], - valid_incoterms: Set[str], -) -> Optional[Dict[str, Any]]: - """Catálogos para Compras Mexicanas: Proveedor, Vendido A, Enviado A, Transportista, Incoterm (sin Agente Aduanal ni Aduana).""" - - def check_id_or_rfc( - val: Any, - col: str, - catalog_name: str, - valid_ids: Set[int], - valid_short_names: Set[str], - ) -> Optional[Dict[str, Any]]: - if val is None or str(val).strip() == "": - return None - v = _parse_int(val) - if v is not None: - if valid_ids and v not in valid_ids: - return _err(line_num, col, "Error: La clave en {} no existe en el Catálogo de {}.".format(col, catalog_name)) - return None - sn_norm = str(val).strip().upper() - if valid_short_names and sn_norm not in valid_short_names: - return _err(line_num, col, "Error: La clave/corta en {} no existe en el Catálogo de {}.".format(col, catalog_name)) - if not valid_short_names: - return _err(line_num, col, "Error: (Celda) {} debe ser un número entero o clave corta (short name) válida.".format(col)) - return None - - err = check_id_or_rfc( - row.get("CLAVE PROVEEDOR"), "CLAVE PROVEEDOR", "Clientes/Proveedores", - valid_provider_ids, valid_provider_short_names, - ) - if err: - return err - err = check_id_or_rfc( - row.get("CLAVE VENDIDO A"), "CLAVE VENDIDO A", "Clientes/Proveedores", - valid_sold_to_ids, valid_sold_to_short_names, - ) - if err: - return err - err = check_id_or_rfc( - row.get("CLAVE ENVIADO A"), "CLAVE ENVIADO A", "Clientes/Proveedores", - valid_shipped_to_ids, valid_shipped_to_short_names, - ) - if err: - return err - - k = _get(row, "CLAVE TRANSPORTISTA") - if k and valid_transporter_keys and k.upper() not in valid_transporter_keys: - return _err( - line_num, - "CLAVE TRANSPORTISTA", - "Error: (Celda K{}) La Clave del Transportista: {} no existe en el Catálogo de Transportistas.".format(line_num, k), - ) - - v = _get(row, "CLAVE INCOTERM") - if v and valid_incoterms and v.upper() not in valid_incoterms: - return _err( - line_num, - "CLAVE INCOTERM", - "Error: (Celda V{}) La Clave de INCOTERM: {} no existe en el Catálogo de INCOTERMS.".format(line_num, v), - ) - - return None - - -def validate_row_encabezados_cmex( - row: Dict[str, Any], - line_num: int, - actualizar: bool, - invoice_exists_by_number: Dict[str, bool], - invoice_processed_by_number: Dict[str, bool], - valid_provider_ids: Set[int], - valid_sold_to_ids: Set[int], - valid_shipped_to_ids: Set[int], - valid_transporter_keys: Set[str], - valid_incoterms: Set[str], - valid_currency_codes: Set[str], - exchange_rate_by_date: Optional[Dict[str, Any]] = None, - invoice_has_partidas_by_number: Optional[Dict[str, bool]] = None, - existing_tipo_moneda_by_number: Optional[Dict[str, str]] = None, - valid_provider_short_names: Optional[Set[str]] = None, - valid_sold_to_short_names: Optional[Set[str]] = None, - valid_shipped_to_short_names: Optional[Set[str]] = None, - date_format: Optional[str] = None, - parse_date_fn=None, - warnings: Optional[List[Dict[str, Any]]] = None, -) -> List[Dict[str, Any]]: - """ - Valida una fila de CSV de Encabezados de Compras Mexicanas. - Clarion: VALIDA_TODA_FAC_COM_MEX (factura nueva o no actualizar) vs VALIDA_PARCIAL_FAC_COM_MEX (actualizar existente). - Siempre ejecuta VALIDACIONES_FAC_COM_MEX (longitud C, tipo cambio, catálogos G/H/I/K, transporte M/N, moneda O/P, incoterm V, tipo peso Y). - """ - errors: List[Dict[str, Any]] = [] - - factura = _get(row, "NUMERO FACTURA", "NUM FACTURA", "FACTURA") - if not factura: - errors.append(_err( - line_num, - "NUMERO FACTURA", - "Error: (Col.C) La columna de Número de Factura está vacía y no se pueden hacer las validaciones.", - )) - return errors - - if invoice_processed_by_number.get(factura.strip(), False): - errors.append(_err( - line_num, - "NUMERO FACTURA", - "Error: (Celda C{}) El Número de Factura: {} ya existe y está Actualizada, no se puede hacer cambios.".format( - line_num, factura - ), - )) - return errors - - if actualizar and factura.strip() not in invoice_exists_by_number: - errors.append(_err( - line_num, - "NUMERO FACTURA", - "Error: (Col.C) Factura de importación {} no existe (modo Actualizar).".format(factura), - )) - return errors - - use_partial = actualizar and invoice_exists_by_number.get(factura.strip(), False) - - if not use_partial: - err = _validaciones_obligatorios_toda_cmex(row, line_num, actualizar) - if err: - errors.append(err) - - has_partidas = invoice_has_partidas_by_number.get(factura.strip(), False) if invoice_has_partidas_by_number else False - existing_moneda = existing_tipo_moneda_by_number.get(factura.strip()) if existing_tipo_moneda_by_number else None - - invoice_date_parsed = None - if parse_date_fn: - date_str = _get(row, "FECHA FACTURA", "FECHA") - if date_str: - invoice_date_parsed = parse_date_fn(date_str, date_format) - - for err in [ - _validaciones_factura_longitud(row, line_num), - _validaciones_transporte_cmex(row, line_num), - _validaciones_moneda( - row, - line_num, - valid_currency_codes or set(), - has_partidas if use_partial else None, - existing_moneda if use_partial else None, - ), - _validaciones_tipo_peso(row, line_num), - _validaciones_catalogos_cmex( - row, - line_num, - valid_provider_ids or set(), - valid_sold_to_ids or set(), - valid_shipped_to_ids or set(), - valid_provider_short_names or set(), - valid_sold_to_short_names or set(), - valid_shipped_to_short_names or set(), - valid_transporter_keys or set(), - valid_incoterms or set(), - ), - _validaciones_tipo_cambio( - row, line_num, invoice_date_parsed, exchange_rate_by_date or {}, warnings - ), - ]: - if err: - errors.append(err) - - return errors diff --git a/backend/api/v1/modules/a76/layouts_csv/facturas/validators/encabezados_expo.py b/backend/api/v1/modules/a76/layouts_csv/facturas/validators/encabezados_expo.py deleted file mode 100644 index accb13a7..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/facturas/validators/encabezados_expo.py +++ /dev/null @@ -1,469 +0,0 @@ -""" -Validaciones CSV para Encabezados de Facturas de Exportación (Expo Def) y Cambio de Régimen. -Paridad Clarion: VALIDA_TODA_FAC_EXPO, VALIDA_PARCIAL_FAC_EXPO, VALIDACIONES_FAC_EXPO. -Estructura CSV: PEDIMENTO (A), REMESA (B), NUMERO FACTURA (C), ... MANIFIESTO (Y), E-DOCUMENT (Z), -NUM. OPERACION (AA), ENVIADO POR (AB), ADUANA DE CRUCE (AC), OBSERVACIONES E/I, FACTURA ALTERNA. -""" -from datetime import datetime -from typing import Any, Dict, List, Optional, Set -from .pedimento_resolution import resolve_pedimento_candidates -from .consolidated_invoice_dates import validate_consolidated_invoice_date_csv - -from .encabezados_impo_temp import ( - _clip, - _err, - _get, - _parse_int, - _pedimento_key_from_parsed, - _patente_from_agente_aduanal, - _validaciones_catalogos, - _validaciones_factura_longitud, - _validaciones_moneda, - _validaciones_tipo_cambio, - _validaciones_tipo_peso, - _validaciones_transporte, -) -from .encabezados_impo_def import ( - parse_pedimento_col_a_impo_def, -) - -# Expo: pedimento mismo formato ##-####-####### (15 chars) -MAX_LEN_PEDIMENTO_EXPO = 15 -MAX_LEN_FACTURA = 15 - -# Regímenes: Exportación (sin cambio de régimen) vs Cambio de Régimen (IMD) -REGIMENES_EXPO = frozenset({"EXD", "ETE", "ETR"}) -REGIMEN_IMD = "IMD" -CLAVES_PEDIMENTO_CAMBIO_REGIMEN = frozenset({"F5", "A3"}) - - -def _validaciones_obligatorios_toda_expo( - row: Dict[str, Any], - line_num: int, - tiene_pedimento: bool, -) -> Optional[Dict[str, Any]]: - """Obligatorios VALIDA_TODA para Expo: C, D, F, G, H, I, J; AC (Aduana de Cruce) si hay pedimento.""" - obligatorios: List[str] = [] - if not _get(row, "NUMERO FACTURA", "NUM FACTURA", "FACTURA"): - obligatorios.append("(Col.C) Número de Factura") - if not _get(row, "FECHA FACTURA", "FECHA"): - obligatorios.append("(Col.D) Fecha de la Factura") - if not _get(row, "REGIMEN", "CLAVEDOCUMENTO"): - obligatorios.append("(Col.F) Régimen") - if not _get(row, "CLAVE PROVEEDOR"): - obligatorios.append("(Col.G) Clave del Proveedor") - if not _get(row, "CLAVE VENDIDO A"): - obligatorios.append("(Col.H) Clave del Vendido A") - if not _get(row, "CLAVE ENVIADO A"): - obligatorios.append("(Col.I) Clave del Enviado A") - if not _get(row, "AGENTE ADUANAL"): - obligatorios.append("(Col.J) Clave del Agente Aduanal") - if tiene_pedimento and not _get(row, "ADUANA DE CRUCE"): - obligatorios.append("(Col.AC) Aduana de Cruce") - if obligatorios: - return _err( - line_num, - "ARCHIVO CSV", - f"Existen campos vacíos que son obligatorios: {', '.join(obligatorios)}. Revisar para Exportación.", - ) - return None - - -def _validaciones_regimen_expo( - row: Dict[str, Any], - line_num: int, - cambio_regimen: bool, -) -> Optional[Dict[str, Any]]: - """Col F: si cambio_regimen → IMD; si no → EXD, ETR, ETE.""" - f = _get(row, "REGIMEN", "CLAVEDOCUMENTO") - if not f: - return None - f_upper = f.upper() - if cambio_regimen: - if f_upper != REGIMEN_IMD: - return _err( - line_num, - "REGIMEN", - f"Error: (Celda F{line_num}) El Régimen Aduanero: {f} no es válido para este tipo de movimiento. " - "Los válidos para Cambio de Régimen son: IMD.", - ) - else: - if f_upper not in REGIMENES_EXPO: - return _err( - line_num, - "REGIMEN", - f"Error: (Celda F{line_num}) El Régimen Aduanero: {f} no es válido para este tipo de movimiento. " - "Los válidos para Exportación son: EXD, ETE y ETR.", - ) - return None - - -def _validaciones_pedimento_remesa_expo( - row: Dict[str, Any], - line_num: int, - cambio_regimen: bool, - pedimento_rows: List[Dict[str, Any]], - remesa_por_pedimento_bd: Dict[str, Set[int]], - remesa_por_pedimento_csv: Dict[str, Dict[int, str]], - autonumerar_remesas: bool, - invoice_date_parsed: Optional[datetime], - recalcular_fecha_pedimentos: bool = False, -) -> Optional[Dict[str, Any]]: - """Pedimento Col A: formato ##-####-#######. Si cambio_regimen: tipo I, régimen IMD, ClavePed F5/A3. Si no: tipo E, régimen EXD/ETE/ETR. Remesa igual que imp_def.""" - col_a = _get(row, "PEDIMENTO") - col_b_raw = row.get("REMESA") - col_b = _clip(col_b_raw) - col_f = _get(row, "REGIMEN", "CLAVEDOCUMENTO").upper() - - if not col_a: - if col_b: - return _err( - line_num, - "REMESA", - f"Error: (Celda B{line_num}) Está asignado el número de Remesa y no se tiene un pedimento en (Celda A{line_num}).", - ) - return None - - if len(col_a) > MAX_LEN_PEDIMENTO_EXPO: - return _err( - line_num, - "PEDIMENTO", - f"Error: (Celda A{line_num}) El Pedimento: {col_a} supera la longitud de caracteres. Use formato ##-####-#######.", - ) - parsed = parse_pedimento_col_a_impo_def(col_a) - if not parsed: - return _err( - line_num, - "PEDIMENTO", - f"Error: (Celda A{line_num}) El Formato del Pedimento: {col_a} es incorrecto. Use ##-####-#######.", - ) - - customs_office, license_val, pedimento_number = parsed - key = _pedimento_key_from_parsed(customs_office, license_val, pedimento_number) - patente_lookup = _patente_from_agente_aduanal(row, license_val) - ped_resolved = resolve_pedimento_candidates( - customs_office=customs_office, - license_val=license_val, - pedimento_number=pedimento_number, - patente_lookup=patente_lookup, - pedimento_rows=pedimento_rows, - ) - if ped_resolved["status"] == "not_found": - if ped_resolved.get("not_found_reason") == "customs_mismatch": - return _err( - line_num, - "PEDIMENTO", - f"Error: (Celda A{line_num}) El pedimento {col_a} está en el catálogo pero la aduana no coincide con la registrada. " - "Revise columna A y el alta del pedimento en el sistema.", - ) - return _err( - line_num, - "PEDIMENTO", - f"Error: (Celda A{line_num}) El Número de Pedimento: {col_a} no existe en el Catálogo de Pedimentos. " - + ( - "Darlo de alta como pedimento de Importación Definitiva (Cambio de Régimen)." - if cambio_regimen - else "Darlo de alta como pedimento de Exportación." - ), - ) - if ped_resolved["status"] == "ambiguous": - return _err( - line_num, - "PEDIMENTO", - f"Error: (Celda A{line_num}) El Número de Pedimento: {col_a} coincide con múltiples registros. " - "Valide aduana/patente para identificar un único pedimento.", - ) - - ped_info = ped_resolved["pedimento"] - op_type = (ped_info.get("operation_type") or "").strip().upper() - regimen_ped = (ped_info.get("regime") or "").strip().upper() - pedimento_code = (ped_info.get("pedimento_code") or "").strip().upper() - - if cambio_regimen: - if op_type != "IMP" and op_type != "I": - return _err( - line_num, - "PEDIMENTO", - f"Error: (Celda A{line_num}) Este Número de Pedimento: {col_a} no está marcado como Importación Definitiva.", - ) - if regimen_ped != REGIMEN_IMD: - return _err( - line_num, - "PEDIMENTO", - f"Error: (Celda A{line_num}) El Número de Pedimento: {col_a} tiene el Régimen {regimen_ped}, no válido para Cambio de Régimen. Válidos: IMD.", - ) - if col_f and col_f != REGIMEN_IMD: - return _err( - line_num, - "REGIMEN", - f"Error: (Celda F{line_num}) El Régimen Aduanero: {col_f} debe ser IMD para Cambio de Régimen.", - ) - if pedimento_code and pedimento_code not in CLAVES_PEDIMENTO_CAMBIO_REGIMEN: - return _err( - line_num, - "PEDIMENTO", - f"Error: (Celda A{line_num}) El Número de Pedimento: {col_a} tiene la clave {pedimento_code}, no definida para Cambio de Régimen/Regularización. Use F5 o A3.", - ) - else: - if op_type != "EXP" and op_type != "E": - return _err( - line_num, - "PEDIMENTO", - f"Error: (Celda A{line_num}) Este Número de Pedimento: {col_a} no está marcado como Exportación.", - ) - if regimen_ped not in REGIMENES_EXPO: - return _err( - line_num, - "PEDIMENTO", - f"Error: (Celda A{line_num}) El Número de Pedimento: {col_a} tiene el Régimen {regimen_ped}, no válido para Exportación. Válidos: EXD, ETE, ETR.", - ) - if col_f and col_f != regimen_ped: - return _err( - line_num, - "REGIMEN", - f"Error: (Celda F{line_num}) El Régimen Aduanero: {col_f} capturado es diferente al del Pedimento: {regimen_ped}.", - ) - - # Rango de fechas si pedimento consolidado (omitir si recalcular_fecha_pedimentos = True, paridad Clarion) - if not recalcular_fecha_pedimentos: - pedimento_type = (ped_info.get("pedimento_type") or "").strip().lower() - err_consolidated = validate_consolidated_invoice_date_csv( - line_num=line_num, - pedimento_key_display=col_a, - pedimento_type=pedimento_type, - invoice_date_parsed=invoice_date_parsed, - entry_raw=ped_info.get("entry_date"), - end_raw=ped_info.get("end_date"), - start_raw=ped_info.get("start_date"), - ) - if err_consolidated: - return err_consolidated - - if not autonumerar_remesas and not col_b: - return _err( - line_num, - "REMESA", - f"Error: (Celda B{line_num}) El Número de Remesa está vacío y se tiene un Pedimento en la Celda A{line_num}.", - ) - remesa_int = _parse_int(col_b_raw) - if col_b and remesa_int is not None and remesa_int == 0: - return _err( - line_num, - "REMESA", - f"Error: (Celda B{line_num}) El Número de Remesa no puede ser 0.", - ) - - factura_actual = _get(row, "NUMERO FACTURA", "NUM FACTURA", "FACTURA") - if remesa_int is not None and key in remesa_por_pedimento_csv: - other = remesa_por_pedimento_csv[key].get(remesa_int) - if other and other != factura_actual: - return _err( - line_num, - "REMESA", - f"Error: (Celda B{line_num}) El Número de Remesa ya está asignado a la factura {other} en este archivo CSV.", - ) - return None - - -def _validaciones_manifiesto( - row: Dict[str, Any], - line_num: int, - valid_manifiesto_codes: Set[str], -) -> Optional[Dict[str, Any]]: - """Col Y MANIFIESTO: si viene informado, debe existir en catálogo.""" - y = _get(row, "MANIFIESTO") - if not y: - return None - if valid_manifiesto_codes and y.strip() not in valid_manifiesto_codes: - return _err( - line_num, - "MANIFIESTO", - f"Error: (Celda Y{line_num}) El Número de Manifiesto: {y} no está dado de alta en el Catálogo de Manifiestos.", - ) - return None - - -def _validaciones_enviado_por( - row: Dict[str, Any], - line_num: int, - valid_enviado_por_ids: Set[int], - valid_enviado_por_short_names: Set[str], -) -> Optional[Dict[str, Any]]: - """Col AB ENVIADO POR: Cliente/Proveedor o equivalente.""" - ab = row.get("ENVIADO POR") - if ab is None or str(ab).strip() == "": - return None - v = _parse_int(ab) - if v is not None: - if valid_enviado_por_ids and v not in valid_enviado_por_ids: - return _err( - line_num, - "ENVIADO POR", - f"Error: (Celda AB{line_num}) La Clave del Enviado Por: {ab} no existe en el Catálogo de Clientes/Proveedores o Equivalentes.", - ) - return None - sn_norm = str(ab).strip().upper() - if valid_enviado_por_short_names and sn_norm not in valid_enviado_por_short_names: - return _err( - line_num, - "ENVIADO POR", - f"Error: (Celda AB{line_num}) La Clave del Enviado Por: {ab} no existe en el Catálogo de Clientes/Proveedores o Equivalentes.", - ) - return None - - -def _normalize_tipo_transporte_ferro(row: Dict[str, Any]) -> Dict[str, Any]: - """Clarion acepta 'FERRO BARCAZA'. Normaliza a FERROBARCAZA para reutilizar validación TEM.""" - out = dict(row) - m = out.get("TIPO TRANSPORTE") - if m is not None and str(m).strip().upper().replace(" ", "") == "FERROBARCAZA": - out["TIPO TRANSPORTE"] = "FERROBARCAZA" - return out - - -def validate_row_encabezados_expo( - row: Dict[str, Any], - line_num: int, - actualizar: bool, - cambio_regimen: bool, - tipo_factura: str, - invoice_exists_by_number: Dict[str, bool], - invoice_processed_by_number: Dict[str, bool], - remesa_por_pedimento_bd: Dict[str, Set[int]], - remesa_por_pedimento_csv: Dict[str, Dict[int, str]], - valid_provider_ids: Set[int], - valid_sold_to_ids: Set[int], - valid_shipped_to_ids: Set[int], - valid_broker_ids: Set[int], - valid_transporter_keys: Set[str], - valid_incoterms: Set[str], - valid_aduana_codes: Set[str], - valid_currency_codes: Set[str], - invoice_in_report_by_number: Optional[Dict[str, bool]] = None, - pedimento_rows: Optional[List[Dict[str, Any]]] = None, - valid_provider_short_names: Optional[Set[str]] = None, - valid_sold_to_short_names: Optional[Set[str]] = None, - valid_shipped_to_short_names: Optional[Set[str]] = None, - valid_broker_claves: Optional[Set[str]] = None, - valid_manifiesto_codes: Optional[Set[str]] = None, - valid_enviado_por_ids: Optional[Set[int]] = None, - valid_enviado_por_short_names: Optional[Set[str]] = None, - exchange_rate_by_date: Optional[Dict[str, Any]] = None, - invoice_has_partidas_by_number: Optional[Dict[str, bool]] = None, - existing_tipo_moneda_by_number: Optional[Dict[str, str]] = None, - autonumerar_remesas: bool = False, - recalcular_fecha_pedimentos: bool = False, - date_format: Optional[str] = None, - parse_date_fn=None, - warnings: Optional[List[Dict[str, Any]]] = None, -) -> List[Dict[str, Any]]: - """ - Valida una fila de CSV de Encabezados de Exportación (Expo Def) o Cambio de Régimen. - Clarion: VALIDA_TODA_FAC_EXPO vs VALIDA_PARCIAL_FAC_EXPO. - """ - errors: List[Dict[str, Any]] = [] - - factura = _get(row, "NUMERO FACTURA", "NUM FACTURA", "FACTURA") - if not factura: - errors.append(_err( - line_num, - "NUMERO FACTURA", - "Error: (Col.C) La columna de Número de Factura está vacía y no se pueden hacer las validaciones.", - )) - return errors - - if invoice_processed_by_number.get(factura.strip(), False): - errors.append(_err( - line_num, - "NUMERO FACTURA", - f"Error: (Celda C{line_num}) El Número de Factura: {factura} ya existe y está Actualizada, no se puede hacer cambios.", - )) - return errors - - if actualizar and factura.strip() not in invoice_exists_by_number: - errors.append(_err( - line_num, - "NUMERO FACTURA", - "Error: (Col.C) Factura de Exportación no existe (modo Actualizar).", - )) - return errors - - if actualizar and (invoice_in_report_by_number or {}).get(factura.strip(), False): - errors.append(_err( - line_num, - "NUMERO FACTURA", - "Error: (Col.C) Factura de Exportación Rep. La factura está en reporte y no se puede actualizar.", - )) - return errors - - use_partial = actualizar and invoice_exists_by_number.get(factura.strip(), False) - tiene_pedimento = bool(_get(row, "PEDIMENTO")) - - if not use_partial: - err = _validaciones_obligatorios_toda_expo(row, line_num, tiene_pedimento) - if err: - errors.append(err) - - invoice_date_parsed = None - if parse_date_fn: - date_str = _get(row, "FECHA FACTURA", "FECHA") - if date_str: - invoice_date_parsed = parse_date_fn(date_str, date_format) - - row_transport = _normalize_tipo_transporte_ferro(row) - has_partidas = invoice_has_partidas_by_number.get(factura.strip(), False) if invoice_has_partidas_by_number else False - existing_moneda = existing_tipo_moneda_by_number.get(factura.strip()) if existing_tipo_moneda_by_number else None - - for err in [ - _validaciones_pedimento_remesa_expo( - row, - line_num, - cambio_regimen, - pedimento_rows or [], - remesa_por_pedimento_bd, - remesa_por_pedimento_csv, - autonumerar_remesas, - invoice_date_parsed, - recalcular_fecha_pedimentos, - ), - _validaciones_factura_longitud(row, line_num), - _validaciones_regimen_expo(row, line_num, cambio_regimen), - _validaciones_transporte(row_transport, line_num), - _validaciones_moneda( - row, - line_num, - valid_currency_codes or set(), - has_partidas if use_partial else None, - existing_moneda if use_partial else None, - ), - _validaciones_tipo_peso(row, line_num), - _validaciones_catalogos( - row, - line_num, - valid_provider_ids or set(), - valid_sold_to_ids or set(), - valid_shipped_to_ids or set(), - valid_provider_short_names or set(), - valid_sold_to_short_names or set(), - valid_shipped_to_short_names or set(), - valid_broker_ids or set(), - valid_broker_claves or set(), - valid_transporter_keys or set(), - valid_incoterms or set(), - valid_aduana_codes or set(), - ), - _validaciones_manifiesto(row, line_num, valid_manifiesto_codes or set()), - _validaciones_enviado_por( - row, - line_num, - valid_enviado_por_ids or set(), - valid_enviado_por_short_names or set(), - ), - _validaciones_tipo_cambio( - row, line_num, invoice_date_parsed, exchange_rate_by_date or {}, warnings - ), - ]: - if err: - errors.append(err) - - return errors diff --git a/backend/api/v1/modules/a76/layouts_csv/facturas/validators/encabezados_impo_def.py b/backend/api/v1/modules/a76/layouts_csv/facturas/validators/encabezados_impo_def.py deleted file mode 100644 index 69fa9b20..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/facturas/validators/encabezados_impo_def.py +++ /dev/null @@ -1,381 +0,0 @@ -""" -Validaciones CSV para Encabezados de Facturas de Importación Definitiva. -Paridad Clarion: VALIDA_TODA_FACIMPO_DEF, VALIDA_PARCIAL_FACIMPO_DEF, VALIDACIONES_FACIMPO_DEF. -Reutiliza de encabezados_impo_temp: claves, short names y validaciones de catálogos, transporte, -moneda, tipo peso y tipo cambio (misma lógica que TEM). -""" -from datetime import datetime -from typing import Any, Dict, List, Optional, Set, Tuple -from .pedimento_resolution import resolve_pedimento_candidates -from .consolidated_invoice_dates import validate_consolidated_invoice_date_csv - -# Reutilizar del TEM: helpers y validaciones de catálogos (claves/short names), transporte, moneda, tipo peso, tipo cambio -from .encabezados_impo_temp import ( - _clip, - _err, - _get, - _parse_int, - _pedimento_key_from_parsed, - _patente_from_agente_aduanal, - _validaciones_catalogos, - _validaciones_factura_longitud, - _validaciones_moneda, - _validaciones_tipo_cambio, - _validaciones_tipo_peso, - _validaciones_transporte, -) - -# Impo Def: régimen único y pedimento formato ##-####-####### (15 chars) -REGIMEN_IMD = "IMD" -MAX_LEN_PEDIMENTO_DEF = 16 -ALLOWED_DEF_REGIMENS = {"IMD", "V1", "A1", "F4", "F5", "D1"} -MAX_LEN_FACTURA = 15 # mismo que TEM - - -def parse_pedimento_col_a_impo_def(pedimento_str: str) -> Optional[Tuple[str, str, str]]: - """ - Parsea Col A (PEDIMENTO) formato ##-####-####### (15 chars) o ###-####-####### (16 chars). - Retorna (aduana, patente, numero) o None. - """ - if not pedimento_str or not isinstance(pedimento_str, str): - return None - s = (pedimento_str or "").strip() - if not (15 <= len(s) <= 16): - return None - - # Buscamos los guiones. Deben ser 2. - parts = s.split("-") - if len(parts) != 3: - return None - - part0, part1, part2 = parts - if not (2 <= len(part0) <= 3) or len(part1) != 4 or len(part2) != 7: - return None - - if not part0.isdigit() or not part1.isdigit() or not part2.isdigit(): - return None - - return (part0, part1, part2) - - -def _validaciones_obligatorios_toda_def( - row: Dict[str, Any], - line_num: int, - tiene_pedimento: bool, -) -> Optional[Dict[str, Any]]: - """Obligatorios VALIDA_TODA para Impo Def: C, D, F, G, H, I, J. Aduana de Cruce (AB) no obligatoria.""" - obligatorios: List[str] = [] - if not _get(row, "NUMERO FACTURA", "NUM FACTURA", "FACTURA"): - obligatorios.append("(Col.C) Factura") - if not _get(row, "FECHA FACTURA", "FECHA"): - obligatorios.append("(Col.D) Fecha de la Factura") - if not _get(row, "REGIMEN", "CLAVEDOCUMENTO"): - obligatorios.append("(Col.F) Clave Régimen") - if not _get(row, "CLAVE PROVEEDOR"): - obligatorios.append("(Col.G) Clave del Proveedor") - if not _get(row, "CLAVE VENDIDO A"): - obligatorios.append("(Col.H) Clave del Vendido A") - if not _get(row, "CLAVE ENVIADO A"): - obligatorios.append("(Col.I) Clave del Enviado A") - if not _get(row, "AGENTE ADUANAL"): - obligatorios.append("(Col.J) Clave del Agente Aduanal") - if obligatorios: - return _err( - line_num, - "ARCHIVO CSV", - f"Existen campos vacíos que son obligatorios: {', '.join(obligatorios)}. Revisar para Importación Definitiva.", - ) - return None - - -def _validaciones_regimen_imd(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]: - """Col F: solo IMD válido para Impo Def.""" - f = _get(row, "REGIMEN", "CLAVEDOCUMENTO") - if f and f.upper() != REGIMEN_IMD: - return _err( - line_num, - "REGIMEN", - f"Error: (Celda F{line_num}) El Régimen Aduanero: {f} no es válido para este tipo de movimiento. Los válidos son: IMD.", - ) - return None - - -def _validaciones_pedimento_remesa_def( - row: Dict[str, Any], - line_num: int, - pedimento_rows: List[Dict[str, Any]], - remesa_por_pedimento_bd: Dict[str, Set[int]], - remesa_por_pedimento_csv: Dict[str, Dict[int, str]], - autonumerar_remesas: bool, - control_remesa: bool, - remesa_inicio: Optional[int], - remesa_fin: Optional[int], - invoice_date_parsed: Optional[datetime], -) -> Optional[Dict[str, Any]]: - """Pedimento Col A: max 15 chars, formato ##-####-#######; catálogo con regime IMD. Remesa igual que TEM.""" - col_a = _get(row, "PEDIMENTO") - col_b_raw = row.get("REMESA") - col_b = _clip(col_b_raw) - col_f = _get(row, "REGIMEN", "CLAVEDOCUMENTO").upper() - - if not col_a: - if col_b: - return _err( - line_num, - "REMESA", - f"Error: (Celda B{line_num}) Está asignado el número de Remesa y no se tiene un pedimento en (Celda A{line_num}).", - ) - return None - - if len(col_a) > MAX_LEN_PEDIMENTO_DEF: - return _err( - line_num, - "PEDIMENTO", - f"Error: (Celda A{line_num}) El Pedimento: {col_a} supera la longitud de caracteres. Use formato ##-####-#######.", - ) - parsed = parse_pedimento_col_a_impo_def(col_a) - if not parsed: - return _err( - line_num, - "PEDIMENTO", - f"Error: (Celda A{line_num}) El Formato del Pedimento: {col_a} es incorrecto. Use ##-####-#######.", - ) - - customs_office, license_val, pedimento_number = parsed - key = _pedimento_key_from_parsed(customs_office, license_val, pedimento_number) - patente_lookup = _patente_from_agente_aduanal(row, license_val) - ped_resolved = resolve_pedimento_candidates( - customs_office=customs_office, - license_val=license_val, - pedimento_number=pedimento_number, - patente_lookup=patente_lookup, - pedimento_rows=pedimento_rows, - ) - if ped_resolved["status"] == "not_found": - if ped_resolved.get("not_found_reason") == "customs_mismatch": - return _err( - line_num, - "PEDIMENTO", - f"Error: (Celda A{line_num}) El pedimento {col_a} está en el catálogo pero la aduana no coincide con la registrada. " - "Revise columna A y el alta del pedimento en el sistema.", - ) - return _err( - line_num, - "PEDIMENTO", - f"Error: (Celda A{line_num}) El Número de Pedimento: {col_a} no existe en el Catálogo de Pedimentos. " - "Darlo de alta como pedimento de Importación Definitiva.", - ) - if ped_resolved["status"] == "ambiguous": - return _err( - line_num, - "PEDIMENTO", - f"Error: (Celda A{line_num}) El Número de Pedimento: {col_a} coincide con múltiples registros. " - "Valide aduana/patente para identificar un único pedimento.", - ) - - ped_info = ped_resolved["pedimento"] - regimen_ped = (ped_info.get("regime") or "").strip().upper() - if regimen_ped not in ALLOWED_DEF_REGIMENS: - return _err( - line_num, - "PEDIMENTO", - f"Error: (Celda A{line_num}) El Número de Pedimento: {col_a} tiene el Régimen {regimen_ped}, no válido para este tipo de movimiento. Válidos: {', '.join(sorted(ALLOWED_DEF_REGIMENS))}.", - ) - if col_f and col_f not in ALLOWED_DEF_REGIMENS: - return _err( - line_num, - "REGIMEN", - f"Error: (Celda F{line_num}) El Régimen Aduanero: {col_f} no es válido para este tipo de movimiento. Válidos: {', '.join(sorted(ALLOWED_DEF_REGIMENS))}.", - ) - if col_f and col_f != regimen_ped: - return _err( - line_num, - "REGIMEN", - f"Error: (Celda F{line_num}) El Régimen Aduanero: {col_f} capturado es diferente al del Pedimento: {regimen_ped}. Debe ser IMD.", - ) - - pedimento_type = (ped_info.get("pedimento_type") or "").strip().lower() - err_consolidated = validate_consolidated_invoice_date_csv( - line_num=line_num, - pedimento_key_display=col_a, - pedimento_type=pedimento_type, - invoice_date_parsed=invoice_date_parsed, - entry_raw=ped_info.get("entry_date"), - end_raw=ped_info.get("end_date"), - start_raw=ped_info.get("start_date"), - ) - if err_consolidated: - return err_consolidated - - if not autonumerar_remesas and not col_b: - return _err( - line_num, - "REMESA", - f"Error: (Celda B{line_num}) El Número de Remesa está vacío y se tiene un Pedimento en la Celda A{line_num}.", - ) - remesa_int = _parse_int(col_b_raw) - if col_b and remesa_int is not None and remesa_int == 0: - return _err( - line_num, - "REMESA", - f"Error: (Celda B{line_num}) El Número de Remesa no puede ser 0.", - ) - if control_remesa and remesa_int is not None and remesa_inicio is not None and remesa_fin is not None: - if remesa_int < remesa_inicio or remesa_int > remesa_fin: - return _err( - line_num, - "REMESA", - f"Error: (Celda B{line_num}) El Número de Remesa: {col_b} está fuera del rango configurado ({remesa_inicio}-{remesa_fin}).", - ) - - factura_actual = _get(row, "NUMERO FACTURA", "NUM FACTURA", "FACTURA") - if remesa_int is not None and key in remesa_por_pedimento_csv: - other = remesa_por_pedimento_csv[key].get(remesa_int) - if other and other != factura_actual: - return _err( - line_num, - "REMESA", - f"Error: (Celda B{line_num}) El Número de Remesa ya está asignado a la factura {other} en este archivo CSV.", - ) - return None - - -def _normalize_tipo_transporte_ferro(row: Dict[str, Any]) -> Dict[str, Any]: - """Clarion DEF acepta 'FERRO BARCAZA' (con espacio). Normaliza a FERROBARCAZA para reutilizar validación TEM.""" - out = dict(row) - m = out.get("TIPO TRANSPORTE") - if m is not None and str(m).strip().upper().replace(" ", "") == "FERROBARCAZA": - out["TIPO TRANSPORTE"] = "FERROBARCAZA" - return out - - -def validate_row_encabezados_impo_def( - row: Dict[str, Any], - line_num: int, - actualizar: bool, - invoice_exists_by_number: Dict[str, bool], - invoice_processed_by_number: Dict[str, bool], - pedimento_rows: List[Dict[str, Any]], - remesa_por_pedimento_bd: Dict[str, Set[int]], - remesa_por_pedimento_csv: Dict[str, Dict[int, str]], - valid_provider_ids: Set[int], - valid_sold_to_ids: Set[int], - valid_shipped_to_ids: Set[int], - valid_broker_ids: Set[int], - valid_transporter_keys: Set[str], - valid_incoterms: Set[str], - valid_aduana_codes: Set[str], - valid_currency_codes: Set[str], - valid_provider_short_names: Optional[Set[str]] = None, - valid_sold_to_short_names: Optional[Set[str]] = None, - valid_shipped_to_short_names: Optional[Set[str]] = None, - valid_broker_claves: Optional[Set[str]] = None, - exchange_rate_by_date: Optional[Dict[str, Any]] = None, - invoice_has_partidas_by_number: Optional[Dict[str, bool]] = None, - existing_tipo_moneda_by_number: Optional[Dict[str, str]] = None, - autonumerar_remesas: bool = False, - control_remesa: bool = False, - remesa_inicio: Optional[int] = None, - remesa_fin: Optional[int] = None, - date_format: Optional[str] = None, - parse_date_fn=None, - warnings: Optional[List[Dict[str, Any]]] = None, -) -> List[Dict[str, Any]]: - """ - Valida una fila de CSV de Encabezados de Importación Definitiva. - Clarion: VALIDA_TODA_FACIMPO_DEF vs VALIDA_PARCIAL_FACIMPO_DEF. - Reutiliza de encabezados_impo_temp las validaciones de catálogos (claves y short names), - transporte, moneda, tipo peso y tipo cambio. - """ - errors: List[Dict[str, Any]] = [] - - factura = _get(row, "NUMERO FACTURA", "NUM FACTURA", "FACTURA") - if not factura: - errors.append(_err( - line_num, - "NUMERO FACTURA", - "Error: (Col.C) La columna de Número de Factura está vacía y no se pueden hacer las validaciones.", - )) - return errors - - if invoice_processed_by_number.get(factura.strip(), False): - errors.append(_err( - line_num, - "NUMERO FACTURA", - f"Error: (Celda C{line_num}) El Número de Factura: {factura} ya existe y está Actualizada, no se puede hacer cambios a las facturas actualizadas.", - )) - return errors - - if actualizar and factura.strip() not in invoice_exists_by_number: - errors.append(_err( - line_num, - "NUMERO FACTURA", - f"Error: (Col.C) Factura de importación Definitiva no existe (modo Actualizar).", - )) - return errors - - use_partial = actualizar and invoice_exists_by_number.get(factura.strip(), False) - tiene_pedimento = bool(_get(row, "PEDIMENTO")) - - if not use_partial: - err = _validaciones_obligatorios_toda_def(row, line_num, tiene_pedimento) - if err: - errors.append(err) - - invoice_date_parsed = None - if parse_date_fn: - date_str = _get(row, "FECHA FACTURA", "FECHA") - if date_str: - invoice_date_parsed = parse_date_fn(date_str, date_format) - - row_transport = _normalize_tipo_transporte_ferro(row) - has_partidas = invoice_has_partidas_by_number.get(factura.strip(), False) if invoice_has_partidas_by_number else False - existing_moneda = existing_tipo_moneda_by_number.get(factura.strip()) if existing_tipo_moneda_by_number else None - - for err in [ - _validaciones_pedimento_remesa_def( - row, - line_num, - pedimento_rows, - remesa_por_pedimento_bd, - remesa_por_pedimento_csv, - autonumerar_remesas, - control_remesa, - remesa_inicio, - remesa_fin, - invoice_date_parsed, - ), - _validaciones_factura_longitud(row, line_num), - _validaciones_regimen_imd(row, line_num), - _validaciones_transporte(row_transport, line_num), - _validaciones_moneda( - row, - line_num, - valid_currency_codes or set(), - has_partidas if use_partial else None, - existing_moneda if use_partial else None, - ), - _validaciones_tipo_peso(row, line_num), - _validaciones_catalogos( - row, - line_num, - valid_provider_ids or set(), - valid_sold_to_ids or set(), - valid_shipped_to_ids or set(), - valid_provider_short_names or set(), - valid_sold_to_short_names or set(), - valid_shipped_to_short_names or set(), - valid_broker_ids or set(), - valid_broker_claves or set(), - valid_transporter_keys or set(), - valid_incoterms or set(), - valid_aduana_codes or set(), - ), - _validaciones_tipo_cambio( - row, line_num, invoice_date_parsed, exchange_rate_by_date or {}, warnings - ), - ]: - if err: - errors.append(err) - - return errors diff --git a/backend/api/v1/modules/a76/layouts_csv/facturas/validators/encabezados_impo_temp.py b/backend/api/v1/modules/a76/layouts_csv/facturas/validators/encabezados_impo_temp.py deleted file mode 100644 index 171dab04..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/facturas/validators/encabezados_impo_temp.py +++ /dev/null @@ -1,764 +0,0 @@ -""" -Validaciones CSV para Encabezados de Facturas de Importación Temporal. -Paridad Clarion: VALIDA_TODA_FACIMPO_TEM, VALIDA_PARCIAL_FACIMPO_TEM, VALIDACIONES_FACIMPO_TEM. -Mapeo a BD en commit: LLENA_FACIMPO_TEM (tasks.insert_valid_rows). -Estructura CSV: PEDIMENTO (A), REMESA (B), NUMERO FACTURA (C), ... ADUANA DE CRUCE (AB), OBSERVACIONES E/I, FACTURA ALTERNA. -""" -import logging -from datetime import datetime -from decimal import Decimal, InvalidOperation -from typing import Any, Dict, List, Optional, Set, Tuple -from .pedimento_resolution import ( - aduana_cruce_matches_catalog, - canonical_customs_office_for_pedimento_key, - normalize_pedimento_number_str, - resolve_pedimento_candidates, -) -from .consolidated_invoice_dates import validate_consolidated_invoice_date_csv - -logger = logging.getLogger(__name__) - -# Longitudes máximas Clarion -MAX_LEN_PEDIMENTO = 18 # CC-LLLL-NNNNNNN o CCC-LLLL-NNNNNNN (sin año; ej. 01-1234-2312412 o 640-1234-2312412) -MAX_LEN_FACTURA = 15 - -# Formato PEDIMENTO: CC-LLLL-NNNNNNN (sin año: aduana 2-3, patente 4, número 7) - -REGIMENES_VALIDOS = frozenset({"ITE", "ITR"}) -TIPOS_MONEDA_VALIDOS = frozenset({"ME", "MN", "MC"}) -TIPO_PESO_VALIDOS = frozenset({"KILOS", "LIBRAS"}) - - -def csv_tipo_moneda_es_me_mn_mc(value: Any) -> bool: - """ - ME / MN / MC con la misma regla que _validaciones_moneda (strip + upper). - Compartido entre scan (encabezados) y commit / validate_row_strict en tasks. - """ - if value is None or not str(value).strip(): - return False - return str(value).strip().upper() in TIPOS_MONEDA_VALIDOS - -# Clarion Col M → valor normalizado (minúscula para TransportType enum) -TIPO_TRANSPORTE_CLARION_TO_NORM = { - "NINGUNO": "none", - "TRANSPORTE": "transport", - "CAJA": "box", - "PLACAS": "licence plates", - "CAMION": "truck", - "BUQUE": "vessel", - "FERROBARCAZA": "rail_barge", - "CONTENEDOR": "container", - "PLATAFORMA": "flatbed", - "GONDOLA": "gondola", - "AVION": "airplane", -} -TIPO_TRANSPORTE_VALIDOS = frozenset(TIPO_TRANSPORTE_CLARION_TO_NORM.keys()) - - -def _clip(val: Any) -> str: - if val is None: - return "" - return str(val).strip() - - -def _get(row: Dict[str, Any], *keys: str) -> str: - for k in keys: - v = row.get(k) - if v is not None and str(v).strip(): - return _clip(v) - return "" - - -def _parse_decimal(val: Any) -> Optional[Decimal]: - if val is None: - return None - s = _clip(val) - if not s: - return None - s = s.replace(",", "") - try: - return Decimal(s) - except (InvalidOperation, ValueError): - return None - - -def _parse_int(val: Any) -> Optional[int]: - if val is None: - return None - s = _clip(val) - if not s: - return None - try: - return int(s) - except ValueError: - return None - - -def _sanitize_pedimento_csv_delimiters(raw: Any) -> str: - """Guiones Unicode / BOM → ASCII (Excel a veces exporta caracteres distintos de '-').""" - s = str(raw if raw is not None else "").strip().strip("\ufeff") - for ch in ("\u2212", "\u2013", "\u2014"): - s = s.replace(ch, "-") - return s - - -def _digits_segment_from_csv_cell(seg: Any, *, max_digits: int, min_digits: int = 1) -> Optional[str]: - """ - Segmento numérico desde CSV: tolera celdas Excel como 3428.0, 7011747.0 o enteros. - """ - raw = str(seg if seg is not None else "").strip().replace(",", "").replace(" ", "") - if not raw: - return None - if raw.endswith(".0") and raw[:-2].isdigit(): - raw = raw[:-2] - elif "." in raw or "e" in raw.lower(): - try: - fv = float(raw) - if fv < 0 or fv != int(fv): - return None - raw = str(int(fv)) - except (ValueError, OverflowError): - return None - if not raw.isdigit(): - return None - if len(raw) > max_digits or len(raw) < min_digits: - return None - return raw - - -def parse_pedimento_col_a(pedimento_str: Any) -> Optional[Tuple[str, str, str]]: - """ - Parsea Col A (PEDIMENTO) formato CC-LLLL-NNNNNNN o CCC-LLLL-NNNNNNN (sin año). - El número puede llevar 1–7 dígitos; se normaliza a 7 con ceros a la izquierda. - Tolera exportación Excel (ej. aduana 7 → 07, 3428.0, 7011747.0). - Retorna (customs_office_2o3, license_4, pedimento_number_7) o None si formato inválido. - """ - if pedimento_str is None or (isinstance(pedimento_str, str) and not pedimento_str.strip()): - return None - s = _sanitize_pedimento_csv_delimiters(pedimento_str) - parts = s.split("-") - if len(parts) != 3: - return None - - customs_raw = _digits_segment_from_csv_cell(parts[0], max_digits=3, min_digits=1) - if not customs_raw: - return None - if len(customs_raw) == 1: - customs_office = customs_raw.zfill(2) - else: - customs_office = customs_raw - if len(customs_office) not in (2, 3): - return None - - lic_raw = _digits_segment_from_csv_cell(parts[1], max_digits=4, min_digits=1) - if not lic_raw: - return None - license_val = lic_raw.zfill(4) - - num_raw = _digits_segment_from_csv_cell(parts[2], max_digits=7, min_digits=1) - if not num_raw: - return None - pedimento_number = normalize_pedimento_number_str(num_raw) - return (customs_office, license_val, pedimento_number) - - -def _pedimento_key_from_parsed(customs_office: str, license_val: str, pedimento_number: str) -> str: - """Clave para lookup: CC-LLLL-NNNNNNN (aduana canónica 2 dígitos, número 7 dígitos).""" - co = canonical_customs_office_for_pedimento_key(customs_office) - num = normalize_pedimento_number_str(pedimento_number) - lic = (license_val or "").strip() - return f"{co}-{lic}-{num}" - - -def _patente_from_agente_aduanal(row: Dict[str, Any], default_license: str) -> str: - """ - Obtiene patente para lookup desde AGENTE ADUANAL (columna específica). - Si no es numérica o viene vacía, usa la patente parseada del PEDIMENTO. - """ - agente = _get(row, "AGENTE ADUANAL") - if agente and agente.isdigit(): - if len(agente) < 4: - return agente.zfill(4) - if len(agente) > 4: - return agente[-4:] - return agente - return (default_license or "").strip() - - -def _err(line_num: int, col: str, msg: str, not_found_reason: Optional[str] = None) -> Dict[str, Any]: - out: Dict[str, Any] = {"line": line_num, "col": col, "msg": msg} - if not_found_reason: - out["not_found_reason"] = not_found_reason - return out - - -# --- Obligatorios VALIDA_TODA (cuando no es actualizar) --- -def _validaciones_obligatorios_toda( - row: Dict[str, Any], - line_num: int, - tiene_pedimento: bool, -) -> Optional[Dict[str, Any]]: - """Obligatorios: C, D, F, G, H, I, J; AB obligatoria si A tiene valor.""" - obligatorios: List[str] = [] - if not _get(row, "NUMERO FACTURA", "NUM FACTURA", "FACTURA"): - obligatorios.append("(Col.C) Número de Factura") - if not _get(row, "FECHA FACTURA", "FECHA"): - obligatorios.append("(Col.D) Fecha de la Factura") - if not _get(row, "REGIMEN", "CLAVEDOCUMENTO"): - obligatorios.append("(Col.F) Clave Régimen") - if not _get(row, "CLAVE PROVEEDOR"): - obligatorios.append("(Col.G) Clave del Proveedor") - if not _get(row, "CLAVE VENDIDO A"): - obligatorios.append("(Col.H) Clave del Vendido A") - if not _get(row, "CLAVE ENVIADO A"): - obligatorios.append("(Col.I) Clave del Enviado A") - if not _get(row, "AGENTE ADUANAL"): - obligatorios.append("(Col.J) Clave del Agente Aduanal") - if tiene_pedimento and not _get(row, "ADUANA DE CRUCE"): - obligatorios.append("(Col.AB) Aduana de Cruce") - if obligatorios: - msg = f"Existen campos vacíos que son obligatorios: {', '.join(obligatorios)}." - logger.warning(f"Line {line_num} rejected: {msg}") - return _err( - line_num, - "ARCHIVO CSV", - msg, - ) - return None - - -# --- Pedimento y Remesa (Col A, B) --- -def _validaciones_pedimento_remesa( - row: Dict[str, Any], - line_num: int, - pedimento_rows: List[Dict[str, Any]], - remesa_por_pedimento_bd: Dict[str, Set[int]], - remesa_por_pedimento_csv: Dict[str, Dict[int, str]], - autonumerar_remesas: bool, - control_remesa: bool, - remesa_inicio: Optional[int], - remesa_fin: Optional[int], - date_format: Optional[str], - invoice_date_parsed: Optional[datetime], -) -> Optional[Dict[str, Any]]: - """Col A: formato, longitud, catálogo, tipo I, régimen ITE/ITR, match Col F, fechas. Col B: oblig si A, no 0, rango, unicidad.""" - col_a = _get(row, "PEDIMENTO") - col_b_raw = row.get("REMESA") - col_b = _clip(col_b_raw) - col_f = _get(row, "REGIMEN", "CLAVEDOCUMENTO").upper() - - if not col_a: - if col_b: - return _err( - line_num, - "REMESA", - f"Error: (Celda B{line_num}) Está asignado el número de Remesa y no se tiene un pedimento en (Celda A{line_num}).", - ) - return None - - if len(col_a) > MAX_LEN_PEDIMENTO: - return _err( - line_num, - "PEDIMENTO", - f"Error: (Celda A{line_num}) El Pedimento: {col_a} supera la longitud de caracteres.", - ) - parsed = parse_pedimento_col_a(col_a) - if not parsed: - return _err( - line_num, - "PEDIMENTO", - f"Error: (Celda A{line_num}) El Formato del Pedimento: {col_a} es incorrecto. Use CC-LLLL-NNNNNNN (aduana 2–3 dígitos, patente 4, número 1–7 dígitos, sin año).", - ) - - customs_office, license_val, pedimento_number = parsed - key = _pedimento_key_from_parsed(customs_office, license_val, pedimento_number) - patente_lookup = _patente_from_agente_aduanal(row, license_val) - ped_resolved = resolve_pedimento_candidates( - customs_office=customs_office, - license_val=license_val, - pedimento_number=pedimento_number, - patente_lookup=patente_lookup, - pedimento_rows=pedimento_rows, - ) - if ped_resolved["status"] == "not_found": - if ped_resolved.get("not_found_reason") == "customs_mismatch": - return _err( - line_num, - "PEDIMENTO", - f"Error: (Celda A{line_num}) El pedimento {col_a} está en el catálogo pero la aduana no coincide con la registrada. " - "Revise columna A (aduana en el pedimento) y Col.AB Aduana de cruce frente al alta del pedimento.", - not_found_reason="customs_mismatch", - ) - if ped_resolved.get("not_found_reason") == "license_or_patente_mismatch": - return _err( - line_num, - "PEDIMENTO", - f"Error: (Celda A{line_num}) El número de pedimento aparece en el catálogo pero la patente/agente no coincide " - f"con el alta (clave {col_a}). Revise columna A y columna J (agente aduanal).", - not_found_reason="license_or_patente_mismatch", - ) - # no_catalog_match: paridad captura manual — pedimento aún no dado de alta; se permite continuar - # si el régimen en columna F es ITE/ITR (validación local sin catálogo). - if not col_f or col_f.upper() not in REGIMENES_VALIDOS: - return _err( - line_num, - "REGIMEN", - f"Error: (Celda F{line_num}) Cuando el pedimento no está en el catálogo, el Régimen (ITE o ITR) es obligatorio " - "para poder importar la factura como pendiente de asignar pedimento.", - not_found_reason="no_catalog_match", - ) - ped_info = None - elif ped_resolved["status"] == "ambiguous": - return _err( - line_num, - "PEDIMENTO", - f"Error: (Celda A{line_num}) El Número de Pedimento: {col_a} coincide con múltiples registros. " - "Valide aduana/patente para identificar un único pedimento.", - ) - else: - ped_info = ped_resolved["pedimento"] - - if ped_info is not None: - if (ped_info.get("operation_type") or "").upper() != "IMP": - return _err( - line_num, - "PEDIMENTO", - f"Error: (Celda A{line_num}) Este Número de Pedimento: {col_a} no está marcado como Importación Temporal.", - ) - regimen_ped = (ped_info.get("regime") or "").strip().upper() - if regimen_ped not in REGIMENES_VALIDOS: - msg = f"Error: (Celda A{line_num}) El Pedimento: {col_a} tiene el Régimen {regimen_ped}, no válido (ITE o ITR)." - logger.warning(f"Line {line_num} rejected: {msg}") - return _err( - line_num, - "PEDIMENTO", - msg, - ) - if col_f and col_f not in REGIMENES_VALIDOS: - pass - elif col_f and col_f != regimen_ped: - # Relax: allow ITE/ITR interchange for temporal imports - if col_f in REGIMENES_VALIDOS and regimen_ped in REGIMENES_VALIDOS: - pass - else: - return _err( - line_num, - "REGIMEN", - f"Error: (Celda F{line_num}) El Régimen Aduanero: {col_f} no coincide con el del Pedimento: {regimen_ped}.", - ) - - pedimento_type = (ped_info.get("pedimento_type") or "").strip().lower() - err_consolidated = validate_consolidated_invoice_date_csv( - line_num=line_num, - pedimento_key_display=col_a, - pedimento_type=pedimento_type, - invoice_date_parsed=invoice_date_parsed, - entry_raw=ped_info.get("entry_date"), - end_raw=ped_info.get("end_date"), - start_raw=ped_info.get("start_date"), - ) - if err_consolidated: - return err_consolidated - - if not autonumerar_remesas and not col_b: - return _err( - line_num, - "REMESA", - f"Error: (Celda B{line_num}) El Número de Remesa está vacío y se tiene un Pedimento en la Celda A{line_num}.", - ) - remesa_int = _parse_int(col_b_raw) - if col_b and remesa_int is not None and remesa_int == 0: - return _err( - line_num, - "REMESA", - f"Error: (Celda B{line_num}) El Número de Remesa no puede ser 0.", - ) - if control_remesa and remesa_int is not None and remesa_inicio is not None and remesa_fin is not None: - if remesa_int < remesa_inicio or remesa_int > remesa_fin: - return _err( - line_num, - "REMESA", - f"Error: (Celda B{line_num}) El Número de Remesa: {col_b} está fuera del rango configurado ({remesa_inicio}-{remesa_fin}).", - ) - - factura_actual = _get(row, "NUMERO FACTURA", "NUM FACTURA", "FACTURA") - if remesa_int is not None and key in remesa_por_pedimento_csv: - other = remesa_por_pedimento_csv[key].get(remesa_int) - if other and other != factura_actual: - return _err( - line_num, - "REMESA", - f"Error: (Celda B{line_num}) El Número de Remesa ya está asignado a la factura {other} en este archivo CSV.", - ) - return None - - -# --- Factura longitud (Col C) --- -def _validaciones_factura_longitud(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]: - c = _get(row, "NUMERO FACTURA", "NUM FACTURA", "FACTURA") - if c and len(c) > MAX_LEN_FACTURA: - return _err(line_num, "NUMERO FACTURA", f"Error: (Celda C{line_num}) El Número de Factura supera la longitud de caracteres.") - return None - - -# --- Régimen (Col F) --- -def _validaciones_regimen(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]: - f = _get(row, "REGIMEN", "CLAVEDOCUMENTO") - if f and f.upper() not in REGIMENES_VALIDOS: - return _err( - line_num, - "REGIMEN", - f"Error: (Celda F{line_num}) El Régimen Aduanero: {f} no es válido. Use ITE o ITR.", - ) - return None - - -# --- Tipo transporte y número (Col M, N) --- -def _validaciones_transporte(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]: - m = _get(row, "TIPO TRANSPORTE").upper() - n = _get(row, "NUMERO TRANSPORTE") - if m and m not in TIPO_TRANSPORTE_VALIDOS: - return _err( - line_num, - "TIPO TRANSPORTE", - f"Error: (Celda M{line_num}) El Tipo de Transporte: {m} no es válido. Válidos: NINGUNO, TRANSPORTE, CAJA, PLACAS, CAMION, BUQUE, FERROBARCAZA, CONTENEDOR, PLATAFORMA, GONDOLA, AVION.", - ) - if not m and n: - return _err( - line_num, - "NUMERO TRANSPORTE", - f"Error: (Celda N{line_num}) El Tipo de Transporte está vacío y está capturado un número de transporte.", - ) - return None - - -# --- Tipo moneda y clave moneda (Col O, P) --- -def _validaciones_moneda( - row: Dict[str, Any], - line_num: int, - valid_currency_codes: Set[str], - invoice_has_partidas: Optional[bool], - existing_tipo_moneda: Optional[str], -) -> Optional[Dict[str, Any]]: - o = _get(row, "TIPO MONEDA").upper() - p = _get(row, "CLAVE MONEDA").upper() - if o and o not in TIPOS_MONEDA_VALIDOS: - return _err( - line_num, - "TIPO MONEDA", - f"Error: (Celda O{line_num}) La opción de Tipo Moneda: {row.get('TIPO MONEDA')} no es válida. Use ME, MN o MC.", - ) - if o == "MC": - if not p: - return _err( - line_num, - "CLAVE MONEDA", - f"Error: (Celda P{line_num}) La Clave de la Moneda es obligatoria cuando Tipo de Moneda es MC.", - ) - if p and valid_currency_codes and p not in valid_currency_codes: - return _err( - line_num, - "CLAVE MONEDA", - f"Error: (Celda P{line_num}) La Clave de la Moneda: {p} no existe en el Catálogo.", - ) - if invoice_has_partidas and existing_tipo_moneda and o and o != existing_tipo_moneda.upper(): - return _err( - line_num, - "TIPO MONEDA", - f"Error: (Celda O{line_num}) No se puede cambiar el Tipo de Moneda porque la factura ya tiene partidas. Use {existing_tipo_moneda}.", - ) - return None - - -# --- Tipo peso (Col Y) --- -def _validaciones_tipo_peso(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]: - y = _get(row, "TIPO PESO").upper() - if y and y not in TIPO_PESO_VALIDOS: - return _err( - line_num, - "TIPO PESO", - f"Error: (Celda Y{line_num}) La opción de Tipo de Peso: {row.get('TIPO PESO')} no es válida. Use KILOS o LIBRAS.", - ) - return None - - -# --- Catálogos: Proveedor, Vendido A, Enviado A, Agente, Transportista, Incoterm, Aduana --- -def _validaciones_catalogos( - row: Dict[str, Any], - line_num: int, - valid_provider_ids: Set[int], - valid_sold_to_ids: Set[int], - valid_shipped_to_ids: Set[int], - valid_provider_short_names: Set[str], - valid_sold_to_short_names: Set[str], - valid_shipped_to_short_names: Set[str], - valid_broker_ids: Set[int], - valid_broker_claves: Set[str], - valid_transporter_keys: Set[str], - valid_incoterms: Set[str], - valid_aduana_codes: Set[str], -) -> Optional[Dict[str, Any]]: - def check_id(val: Any, col: str, catalog_name: str, valid_set: Set[int]) -> Optional[Dict[str, Any]]: - if val is None or str(val).strip() == "": - return None - v = _parse_int(val) - if v is None: - return _err(line_num, col, f"Error: (Celda) {col} debe ser un número entero.") - if valid_set and v not in valid_set: - return _err(line_num, col, f"Error: La clave en {col} no existe en el Catálogo de {catalog_name}.") - return None - - def check_id_or_rfc( - val: Any, - col: str, - catalog_name: str, - valid_ids: Set[int], - valid_short_names: Set[str], - ) -> Optional[Dict[str, Any]]: - if val is None or str(val).strip() == "": - return None - v = _parse_int(val) - if v is not None: - if valid_ids and v not in valid_ids: - return _err(line_num, col, f"Error: La clave en {col} no existe en el Catálogo de {catalog_name}.") - return None - sn_norm = str(val).strip().upper() - if valid_short_names and sn_norm not in valid_short_names: - return _err(line_num, col, f"Error: La clave/corta en {col} no existe en el Catálogo de {catalog_name}.") - if not valid_short_names: - return _err(line_num, col, f"Error: (Celda) {col} debe ser un número entero o clave corta (short name) válida.") - return None - - def check_id_or_clave( - val: Any, - col: str, - catalog_name: str, - valid_ids: Set[int], - valid_claves: Set[str], - ) -> Optional[Dict[str, Any]]: - """Para AGENTE ADUANAL: acepta id (entero) o broker_key (clave).""" - if val is None or str(val).strip() == "": - return None - v = _parse_int(val) - if v is not None: - # Si no existe como id, intentar también como clave (p.ej. "5567" como broker_key). - if not valid_ids or v in valid_ids: - return None - clave = str(val).strip().upper() - if valid_claves and clave not in valid_claves: - return _err(line_num, col, f"Error: La clave en {col} no existe en el Catálogo de {catalog_name}.") - if not valid_claves: - return _err(line_num, col, f"Error: (Celda) {col} debe ser un número entero o clave de agente aduanal válida.") - return None - - err = check_id_or_rfc( - row.get("CLAVE PROVEEDOR"), "CLAVE PROVEEDOR", "Clientes/Proveedores", - valid_provider_ids, valid_provider_short_names, - ) - if err: - return err - err = check_id_or_rfc( - row.get("CLAVE VENDIDO A"), "CLAVE VENDIDO A", "Clientes/Proveedores", - valid_sold_to_ids, valid_sold_to_short_names, - ) - if err: - return err - err = check_id_or_rfc( - row.get("CLAVE ENVIADO A"), "CLAVE ENVIADO A", "Clientes/Proveedores", - valid_shipped_to_ids, valid_shipped_to_short_names, - ) - if err: - return err - err = check_id_or_clave( - row.get("AGENTE ADUANAL"), "AGENTE ADUANAL", "Agentes Aduanales", - valid_broker_ids, valid_broker_claves, - ) - if err: - return err - - k = _get(row, "CLAVE TRANSPORTISTA").upper() - if k and valid_transporter_keys and k not in valid_transporter_keys: - return _err(line_num, "CLAVE TRANSPORTISTA", f"Error: (Celda K{line_num}) La Clave del Transportista: {k} no existe en el Catálogo.") - - v = _get(row, "CLAVE INCOTERM").upper() - if v and valid_incoterms and v not in valid_incoterms: - return _err(line_num, "CLAVE INCOTERM", f"Error: (Celda V{line_num}) La Clave de INCOTERM: {v} no existe en el Catálogo.") - - ab = _get(row, "ADUANA DE CRUCE") - if ab and valid_aduana_codes and not aduana_cruce_matches_catalog(ab, valid_aduana_codes): - return _err(line_num, "ADUANA DE CRUCE", f"Error: (Celda AB{line_num}) La Aduana de Cruce: {ab} no existe en el Catálogo.") - - return None - - -def _validaciones_tipo_cambio( - row: Dict[str, Any], - line_num: int, - date_parsed: Optional[datetime], - exchange_rate_by_date: Dict[str, Any], - warnings: Optional[List[Dict[str, Any]]], -) -> Optional[Dict[str, Any]]: - """Si hay fecha y no hay tipo cambio, debe existir en catálogo. Si hay tipo cambio, puede advertir si difiere del catálogo.""" - d = _get(row, "FECHA FACTURA", "FECHA") - e = row.get("TIPO DE CAMBIO") - if not d or not date_parsed: - return None - date_key = date_parsed.isoformat()[:10] if hasattr(date_parsed, "isoformat") else str(date_parsed)[:10] - catalog_tc = exchange_rate_by_date.get(date_key) if exchange_rate_by_date else None - if not _clip(e): - if catalog_tc is None: - return _err( - line_num, - "TIPO DE CAMBIO", - f"Error: (Celda E{line_num}) El Tipo de Cambio para la Fecha {d} no se encontró en el Catálogo.", - ) - return None - val_e = _parse_decimal(e) - if val_e is not None and catalog_tc is not None: - cat_val = catalog_tc if isinstance(catalog_tc, (Decimal, int, float)) else getattr(catalog_tc, "valor", None) or getattr(catalog_tc, "value", None) - if cat_val is not None and abs(float(val_e) - float(cat_val)) > 0.0001 and warnings is not None: - warnings.append({ - "line": line_num, - "col": "TIPO DE CAMBIO", - "msg": f"Advertencia: (Celda E{line_num}) El Tipo de Cambio capturado ({e}) difiere del Catálogo para la fecha {d}.", - "warning": True, - }) - return None - - -def validate_row_encabezados_impo_temp( - row: Dict[str, Any], - line_num: int, - actualizar: bool, - invoice_exists_by_number: Dict[str, bool], - invoice_processed_by_number: Dict[str, bool], - pedimento_rows: List[Dict[str, Any]], - remesa_por_pedimento_bd: Dict[str, Set[int]], - remesa_por_pedimento_csv: Dict[str, Dict[int, str]], - valid_provider_ids: Set[int], - valid_sold_to_ids: Set[int], - valid_shipped_to_ids: Set[int], - valid_broker_ids: Set[int], - valid_transporter_keys: Set[str], - valid_incoterms: Set[str], - valid_aduana_codes: Set[str], - valid_currency_codes: Set[str], - valid_provider_short_names: Optional[Set[str]] = None, - valid_sold_to_short_names: Optional[Set[str]] = None, - valid_shipped_to_short_names: Optional[Set[str]] = None, - valid_broker_claves: Optional[Set[str]] = None, - exchange_rate_by_date: Optional[Dict[str, Any]] = None, - invoice_has_partidas_by_number: Optional[Dict[str, bool]] = None, - existing_tipo_moneda_by_number: Optional[Dict[str, str]] = None, - autonumerar_remesas: bool = False, - control_remesa: bool = False, - remesa_inicio: Optional[int] = None, - remesa_fin: Optional[int] = None, - date_format: Optional[str] = None, - parse_date_fn=None, - warnings: Optional[List[Dict[str, Any]]] = None, -) -> List[Dict[str, Any]]: - """ - Punto de entrada: valida una fila de CSV de Encabezados de Importación Temporal. - Clarion: VALIDA_TODA_FACIMPO_TEM vs VALIDA_PARCIAL_FACIMPO_TEM según actualizar y si la factura existe. - """ - errors: List[Dict[str, Any]] = [] - - factura = _get(row, "NUMERO FACTURA", "NUM FACTURA", "FACTURA") - if not factura: - errors.append(_err(line_num, "NUMERO FACTURA", "Error: (Col.C) La columna de Número de Factura está vacía y no se pueden hacer las validaciones.")) - return errors - - if invoice_processed_by_number.get(factura.strip(), False): - errors.append(_err( - line_num, - "NUMERO FACTURA", - f"Error: (Celda C{line_num}) El Número de Factura: {factura} ya existe y está Actualizada, no se puede hacer cambios.", - )) - return errors - - if actualizar and factura.strip() not in invoice_exists_by_number: - errors.append(_err( - line_num, - "NUMERO FACTURA", - f"Error: (Col.C) Factura de importación {factura} no existe (modo Actualizar).", - )) - return errors - - use_partial = actualizar and invoice_exists_by_number.get(factura.strip(), False) - tiene_pedimento = bool(_get(row, "PEDIMENTO")) - - if not use_partial: - err = _validaciones_obligatorios_toda(row, line_num, tiene_pedimento) - if err: - errors.append(err) - - invoice_date_parsed = None - if parse_date_fn: - date_str = _get(row, "FECHA FACTURA", "FECHA") - if date_str: - invoice_date_parsed = parse_date_fn(date_str, date_format) - - for err in [ - _validaciones_pedimento_remesa( - row, - line_num, - pedimento_rows, - remesa_por_pedimento_bd, - remesa_por_pedimento_csv, - autonumerar_remesas, - control_remesa, - remesa_inicio, - remesa_fin, - date_format, - invoice_date_parsed, - ), - _validaciones_factura_longitud(row, line_num), - _validaciones_regimen(row, line_num), - _validaciones_transporte(row, line_num), - ]: - if err: - errors.append(err) - - has_partidas = invoice_has_partidas_by_number.get(factura.strip(), False) if invoice_has_partidas_by_number else False - existing_moneda = existing_tipo_moneda_by_number.get(factura.strip()) if existing_tipo_moneda_by_number else None - for err in [ - _validaciones_moneda( - row, - line_num, - valid_currency_codes or set(), - has_partidas if use_partial else None, - existing_moneda if use_partial else None, - ), - _validaciones_tipo_peso(row, line_num), - _validaciones_catalogos( - row, - line_num, - valid_provider_ids or set(), - valid_sold_to_ids or set(), - valid_shipped_to_ids or set(), - valid_provider_short_names or set(), - valid_sold_to_short_names or set(), - valid_shipped_to_short_names or set(), - valid_broker_ids or set(), - valid_broker_claves or set(), - valid_transporter_keys or set(), - valid_incoterms or set(), - valid_aduana_codes or set(), - ), - _validaciones_tipo_cambio( - row, line_num, invoice_date_parsed, exchange_rate_by_date or {}, warnings - ), - ]: - if err: - errors.append(err) - - return errors - - -def row_to_transport_type_clarion(val: Optional[str]) -> Optional[str]: - """Mapea valor Clarion Col M a valor enum TransportType (minúscula).""" - if not val: - return "none" - u = _clip(val).upper() - return TIPO_TRANSPORTE_CLARION_TO_NORM.get(u, "none") diff --git a/backend/api/v1/modules/a76/layouts_csv/facturas/validators/partidas_expo.py b/backend/api/v1/modules/a76/layouts_csv/facturas/validators/partidas_expo.py deleted file mode 100644 index f4096474..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/facturas/validators/partidas_expo.py +++ /dev/null @@ -1,543 +0,0 @@ -""" -Validaciones CSV para Partidas de Exportación Definitiva (y Cambio de Régimen). -Paridad Clarion: VALIDA_TODA_PAR_EXPO, VALIDA_PARCIAL_PAR_EXPO, VALIDACIONES_PAR_EXPO. -Estructura: NUMERO FACTURA EXPO, LINEA EXPO, TIPO DE IMPO, FACTURA IMPO, LINEA IMPO, GENERA DESCARGA, ... -Variante RFC EGM0303257J1: columnas ES PARTIDA/SUBPARTIDA y LINEA PRINCIPAL en T y U. -""" -from decimal import Decimal, InvalidOperation -from typing import Dict, Any, Optional, Set, Tuple, List - -from .partidas_impo_temp import _clip, _get - -# Longitudes Clarion partidas expo (Factura EXPO alineado con invoice_number String(100) en InvoiceHeader) -MAX_LEN_FACTURA_EXPO = 100 -MAX_LEN_LINEA_EXPO = 5 -MAX_LEN_TIPO_IMPO = 3 -MAX_LEN_ORDEN_COMPRA = 20 -MAX_LEN_NUM_PARTE = 30 - -TIPO_IMPO_VALIDOS = frozenset({"TEM", "DEF"}) -GENERA_DESCARGA_VALIDOS = frozenset({"SI", "NO"}) -SE_PAGO_IMPUESTO_VALIDOS = frozenset({"SI", "NO"}) - - -def _err( - line_num: int, - col: str, - msg: str, - identifier: str = "ARCHIVO CSV", -) -> Dict[str, Any]: - return {"line": line_num, "col": col, "msg": msg} - - -def _parse_decimal(val: Any) -> Optional[Decimal]: - if val is None: - return None - s = _clip(val) - if not s: - return None - s = str(s).replace(",", "") - try: - return Decimal(s) - except (InvalidOperation, ValueError): - return None - - -def _check_factura_expo_vacia(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]: - val = _get(row, "NUMERO FACTURA EXPO", "NUMERO FACTURA EXPO.", "NUM FACTURA EXPO", "FACTURA EXPO") - if not val: - return _err( - line_num, - "NUMERO FACTURA EXPO", - f"Error: (Celda A{line_num}) La Factura de Exportación está vacía y no se pueden hacer las validaciones. " - "Capturar en la Celda A un número de Factura existente al cual desee agregar o actualizar partidas.", - ) - return None - - -def _check_factura_expo_existe( - invoice_number: str, - line_num: int, - invoice_id_by_number: Dict[str, int], -) -> Optional[Dict[str, Any]]: - if invoice_number not in invoice_id_by_number: - return _err( - line_num, - "NUMERO FACTURA EXPO", - f"Error: (Celda A{line_num}) La Factura de Exportación {invoice_number} no existe en SCAII y no se pueden hacer las validaciones. " - "Capturar en la Celda A un número de Factura existente al cual desee agregar o actualizar partidas.", - ) - return None - - -def _check_factura_expo_no_actualizada( - invoice_number: str, - line_num: int, - invoice_processed_by_number: Dict[str, bool], - rfc_exception_updated: Set[str], -) -> Optional[Dict[str, Any]]: - if invoice_number in rfc_exception_updated: - return None - if invoice_processed_by_number.get(invoice_number, False): - return _err( - line_num, - "NUMERO FACTURA EXPO", - f"Error: (Celda A{line_num}) La Factura de Exportación: {invoice_number} ya existe y está Actualizada, no se puede hacer cambios a las facturas actualizadas. " - "Capturar otro número de Factura de Exportación o Desactualizar la factura.", - ) - return None - - -def _check_linea_expo_si_no_autonumerar( - row: Dict[str, Any], line_num: int, autonumerar: bool -) -> Optional[Dict[str, Any]]: - if autonumerar: - return None - val = _get(row, "LINEA EXPO", "LINEA EXPO.", "RENGLON EXPO") - if not val: - return _err( - line_num, - "LINEA EXPO", - f"Error: (Celda B{line_num}) El campo de la línea de la partida está vacío y no se pueden hacer las validaciones, ya que se tiene la opción de Autonumerar como NO. " - "Capturar en la Celda B la línea de la partida al cual desee agregar o actualizar información.", - ) - return None - - -def _check_levantar_subpartidas_expo( - row: Dict[str, Any], - line_num: int, - levantar_subpartidas: bool, - rfc_exception_egm: bool, -) -> Optional[Dict[str, Any]]: - """Clarion: si LevantarSubpartidas=S, obligatorios ES PARTIDA/SUBPARTIDA y LINEA PRINCIPAL (col S/T o T/U para EGM0303257J1).""" - if not levantar_subpartidas: - return None - es_sub = _get(row, "ES PARTIDA/SUBPARTIDA", "ESSUBPARTIDA", "ES PARTIDA O SUBPARTIDA") - if not es_sub: - col = "T" if rfc_exception_egm else "S" - return _err( - line_num, - "ES PARTIDA/SUBPARTIDA", - f"Error: (Celda {col}{line_num}) El campo del tipo de la partida (partida o subpartida) está vacío. " - "Capturar el tipo de la partida. [P] = Partida o [S] = Subpartida.", - ) - linea_principal = _get(row, "LINEA PRINCIPAL", "LINEAPRINCIPAL", "PARTIDA PRINCIPAL") - if not linea_principal: - col = "U" if rfc_exception_egm else "T" - return _err( - line_num, - "LINEA PRINCIPAL", - f"Error: (Celda {col}{line_num}) El campo de la partida principal está vacío. " - f"Capturar en la Celda {col} la partida principal.", - ) - return None - - -def _valida_toda_obligatorios_expo( - row: Dict[str, Any], - line_num: int, - levantar_subpartidas: bool, - rfc_exception_egm: bool, -) -> Optional[Dict[str, Any]]: - """VALIDA_TODA_PAR_EXPO obligatorios: C (tipo impo), F (descarga), G (cantidad); si descarga=SI: D, E; si subpartidas: S/T o T/U.""" - obligatorios: List[str] = [] - tipo_impo = _get(row, "TIPO DE IMPO", "TIPO DE IMPO.", "TIPO IMPO", "PROCEDENCIA") - if not tipo_impo: - obligatorios.append("(Col.C) Procedencia de la Importación.") - descarga = _get(row, "GENERA DESCARGA", "GENERA DESCARGA?", "DESCARGA") - if descarga == "SI": - if not _get(row, "FACTURA IMPO", "FACTURA IMPO.", "FACTURA IMPORTACION"): - obligatorios.append("(Col.D) Factura de Impo.") - if not _get(row, "LINEA IMPO", "LINEA IMPO.", "LINEA IMPORTACION"): - obligatorios.append("(Col.E) Línea de Impo.") - if not descarga: - obligatorios.append("(Col.F) Descarga? SI o NO") - if not _get(row, "CANTIDAD EXPORTADA/DESCARGAR", "CANTIDAD EXPORTADA", "CANTIDAD"): - obligatorios.append("(Col.G) Cantidad Expo.") - if levantar_subpartidas: - if rfc_exception_egm: - if not _get(row, "ES PARTIDA/SUBPARTIDA", "ESSUBPARTIDA", "ES PARTIDA O SUBPARTIDA"): - obligatorios.append("(Col.T) EsSubpartida?.") - if not _get(row, "LINEA PRINCIPAL", "LINEAPRINCIPAL", "PARTIDA PRINCIPAL"): - obligatorios.append("(Col.U) Partida Principal.") - else: - if not _get(row, "ES PARTIDA/SUBPARTIDA", "ESSUBPARTIDA", "ES PARTIDA O SUBPARTIDA"): - obligatorios.append("(Col.S) EsSubpartida?.") - if not _get(row, "LINEA PRINCIPAL", "LINEAPRINCIPAL", "PARTIDA PRINCIPAL"): - obligatorios.append("(Col.T) Partida Principal.") - if obligatorios: - return _err( - line_num, - "TIPO DE IMPO", - f"Existen campos vacíos que son obligatorios, es la {', '.join(obligatorios)}. " - "Revisar la línea del archivo y capturar los campos con la información correcta.", - ) - return None - - -def _valida_subpartidas_duplicados_expo( - factura_expo: str, - linea_expo: str, - line_num: int, - line_counts_csv: Dict[Tuple[str, str], int], -) -> Optional[Dict[str, Any]]: - key = (factura_expo.strip(), _clip(linea_expo)) - if line_counts_csv.get(key, 0) > 1: - return _err( - line_num, - "LINEA EXPO", - f"Error: (Celda B{line_num}) El campo de la partida está duplicado entre las partidas. " - f"Capturar en la Celda B{line_num} otro número de partida.", - ) - return None - - -def _valida_subpartida_tiene_principal_expo( - row: Dict[str, Any], - line_num: int, - factura_expo: str, - partidas_principales_csv: Set[Tuple[str, str]], - partidas_principales_bd: Set[Tuple[str, str]], - rfc_exception_egm: bool, -) -> Optional[Dict[str, Any]]: - es_sub = _get(row, "ES PARTIDA/SUBPARTIDA", "ESSUBPARTIDA", "ES PARTIDA O SUBPARTIDA").upper() - linea_principal = _get(row, "LINEA PRINCIPAL", "LINEAPRINCIPAL", "PARTIDA PRINCIPAL") - if es_sub != "S" or not linea_principal or linea_principal == "0": - return None - key_principal = (factura_expo.strip(), _clip(linea_principal)) - if key_principal in partidas_principales_csv or key_principal in partidas_principales_bd: - return None - col = "U" if rfc_exception_egm else "T" - return _err( - line_num, - "LINEA PRINCIPAL", - f"Error: (Celda {col}{line_num}) La partida principal {linea_principal} no existe. " - f"Capturar en la Celda {col} la partida principal y/o verificar que si permita contener subpartidas.", - ) - - -def _valida_subpartida_linea_principal_no_cero_expo( - row: Dict[str, Any], line_num: int, rfc_exception_egm: bool -) -> Optional[Dict[str, Any]]: - es_sub = _get(row, "ES PARTIDA/SUBPARTIDA", "ESSUBPARTIDA", "ES PARTIDA O SUBPARTIDA").upper() - linea_principal = _get(row, "LINEA PRINCIPAL", "LINEAPRINCIPAL", "PARTIDA PRINCIPAL") - if es_sub == "S" and linea_principal == "0": - col = "U" if rfc_exception_egm else "T" - return _err( - line_num, - "LINEA PRINCIPAL", - f"Error: (Celda {col}{line_num}) La SubPartida no tiene asignada una partida principal. " - f"Capturar en la Celda {col} una partida principal.", - ) - return None - - -def _validaciones_par_expo( - row: Dict[str, Any], - line_num: int, - factura_expo: str, - valid_uom_codes: Set[str], - valid_bulks_codes: Set[str], - valid_payment_methods: Set[str], - valid_fraction_ame: Set[str], - valid_part_numbers: Optional[Set[str]], - factura_impo_tem_by_number: Dict[str, int], - factura_impo_def_by_number: Dict[str, int], - line_exists_tem: Set[Tuple[int, str]], - line_exists_def: Set[Tuple[int, str]], - validar_decimales_pza: bool = False, -) -> Optional[Dict[str, Any]]: - """VALIDACIONES_PAR_EXPO: longitudes, TEM/DEF, FK factura impo + línea, descarga SI/NO, cantidad, U.M., bultos, forma pago, impuesto, fracción ame, num parte, decimales PZA.""" - tipo_impo = _get(row, "TIPO DE IMPO", "TIPO DE IMPO.", "TIPO IMPO", "PROCEDENCIA").strip().upper() - if not tipo_impo: - tipo_impo = "TEM" - factura_impo = _get(row, "FACTURA IMPO", "FACTURA IMPO.", "FACTURA IMPORTACION") - linea_impo = _get(row, "LINEA IMPO", "LINEA IMPO.", "LINEA IMPORTACION") - descarga = _get(row, "GENERA DESCARGA", "GENERA DESCARGA?", "DESCARGA").upper() - - # Longitud A - if factura_expo and len(factura_expo) > MAX_LEN_FACTURA_EXPO: - return _err( - line_num, - "NUMERO FACTURA EXPO", - f"Error: (Celda A{line_num}) La Factura de Exportación: {factura_expo} supera la longitud de caracteres. " - f"Capturar en la Celda A el campo Factura de Exportación con un máximo de {MAX_LEN_FACTURA_EXPO} caracteres.", - ) - # Longitud B - linea_expo = _get(row, "LINEA EXPO", "LINEA EXPO.", "RENGLON EXPO") - if linea_expo and len(linea_expo) > MAX_LEN_LINEA_EXPO: - return _err( - line_num, - "LINEA EXPO", - f"Error: (Celda B{line_num}) La Línea de Exportación: {linea_expo} supera la longitud de caracteres. " - "Capturar en la Celda B una Línea de Exportación con formato #####.", - ) - # Longitud C - if tipo_impo and len(tipo_impo) > MAX_LEN_TIPO_IMPO: - return _err( - line_num, - "TIPO DE IMPO", - f"Error: (Celda C{line_num}) La Procedencia debe ser especificada como TEM o DEF. " - "Capturar en la Celda C una procedencia no mayor de 3 caracteres.", - ) - # C: TEM o DEF - if tipo_impo and tipo_impo not in TIPO_IMPO_VALIDOS: - return _err( - line_num, - "TIPO DE IMPO", - f"Error: (Celda C{line_num}) El Tipo de Descargo: {tipo_impo} no es valido. Capturar uno valido como TEM o DEF.", - ) - # D, E: factura impo + línea existen en catálogo TEM o DEF - if factura_impo and tipo_impo: - consec_tem = factura_impo_tem_by_number.get(factura_impo.strip()) - consec_def = factura_impo_def_by_number.get(factura_impo.strip()) - if tipo_impo == "TEM": - if consec_tem is None: - return _err( - line_num, - "FACTURA IMPO", - f"Error: (Celda D{line_num}) La Factura: {factura_impo} de Importación Temporal no existe. " - "Capturar un Número de Factura que exista en el Catálogo de Importaciones Temporales.", - ) - key_line = (consec_tem, _clip(linea_impo)) - if linea_impo and key_line not in line_exists_tem: - return _err( - line_num, - "LINEA IMPO", - f"Error: (Celda D{line_num}, E{line_num}) La Factura: {factura_impo} con línea: {linea_impo} de Importación Temporal no existe. " - "Capturar un Número de Factura con diferente línea que este en el Catálogo de Importaciones Temporales.", - ) - elif tipo_impo == "DEF": - if consec_def is None: - return _err( - line_num, - "FACTURA IMPO", - f"Error: (Celda D{line_num}) La Factura: {factura_impo} de Importación Definitiva no existe. " - "Capturar un Número de Factura que exista en el Catálogo de Importaciones Definitivas.", - ) - key_line = (consec_def, _clip(linea_impo)) - if linea_impo and key_line not in line_exists_def: - return _err( - line_num, - "LINEA IMPO", - f"Error: (Celda D{line_num}, E{line_num}) La Factura: {factura_impo} con línea: {linea_impo} de Importación Definitiva no existe. " - "Capturar un Número de Factura con diferente línea que este en el Catálogo de Importaciones Definitivas.", - ) - # F: SI o NO - if descarga and descarga not in GENERA_DESCARGA_VALIDOS: - return _err( - line_num, - "GENERA DESCARGA", - f"Error: (Celda F{line_num}) La captura: {descarga} no es valido para la opción de que la partida genere descarga. " - "Capturar un valor valido como SI o NO o dejar vacio y lo tomará como un SI.", - ) - # G: cantidad no cero - cant_str = _get(row, "CANTIDAD EXPORTADA/DESCARGAR", "CANTIDAD EXPORTADA", "CANTIDAD") - if cant_str: - cant = _parse_decimal(cant_str) - if cant is not None and cant == 0: - return _err( - line_num, - "CANTIDAD EXPORTADA/DESCARGAR", - f"Error: (Celda G{line_num}) La Cantidad a Exportar: {cant_str} no puede ser cero. Capturar una cantidad a exportar valida.", - ) - # H: U.M. en catálogo (si no se toma de impo) - um = _get(row, "UNIDAD DE MEDIDA", "U.M.", "UNIDAD MEDIDA") - if um and valid_uom_codes and um.upper() not in valid_uom_codes: - return _err( - line_num, - "UNIDAD DE MEDIDA", - f"Error: (Celda H{line_num}) La U.M.: {um} no existe en catálogo de Unidades de Medida. " - "Capturar una Clave de Unidad de Medida que exista en el Catálogo.", - ) - # Bultos: cantidad + clave (Clarion Col L, M) - clave_bultos = _get(row, "CLAVE BULTOS", "CLAVEBULTOS") - cant_bultos = row.get("CANTIDAD BULTOS") or row.get("CANTIDADBULTOS") - if clave_bultos: - if valid_bulks_codes and clave_bultos not in valid_bulks_codes: - return _err( - line_num, - "CLAVE BULTOS", - f"Error: (Celda M{line_num}) La Clave de Bulto: {clave_bultos} no existe en el Catálogo de Claves de Bultos. " - "Darlo de alta en el Catálogo de Claves de Bultos o capturar uno ya existente.", - ) - cant_bultos_val = _parse_decimal(cant_bultos) - if cant_bultos_val is None: - return _err( - line_num, - "CANTIDAD BULTOS", - f"Error: (Celda L{line_num}) La Cantidad de Bultos está vacía y en la Celda M{line_num} se tienen la Clave de Bulto: {clave_bultos}.", - ) - if cant_bultos_val == 0: - return _err( - line_num, - "CANTIDAD BULTOS", - f"Error: (Celda L{line_num}) La Cantidad de Bultos es cero y en la Celda M{line_num} se tienen la Clave de Bulto: {clave_bultos}.", - ) - else: - cant_bultos_val = _parse_decimal(cant_bultos) - if cant_bultos_val is not None and cant_bultos_val > 0: - return _err( - line_num, - "CANTIDAD BULTOS", - f"Error: (Celda L{line_num}) La Cantidad de Bultos es {cant_bultos} y en la Celda M{line_num} no se tienen la Clave de Bulto.", - ) - # L: Se pagó impuesto SI/NO - se_pago = _get(row, "SE PAGO IMPUESTO", "SE PAGO IMPUESTO? (SI o NO)", "SEPAGOIMPUESTO") - if se_pago and se_pago.upper() not in SE_PAGO_IMPUESTO_VALIDOS: - return _err( - line_num, - "SE PAGO IMPUESTO", - f"Error: (Celda L{line_num}) El Valor Capturado para Se Pago Impuesto no es Válido. Capturar en la Celda L{line_num} SI o NO.", - ) - # M: Forma de pago en catálogo - forma_pago = _get(row, "FORMA DE PAGO", "FORMADEPAGO", "FORMA PAGO") - if forma_pago and valid_payment_methods and forma_pago not in valid_payment_methods: - return _err( - line_num, - "FORMA DE PAGO", - f"Error: (Celda M{line_num}) La Forma de Pago Capturado no es Válido. " - "Capturar en la Celda M una Forma de Pago dentro del Catálogo General de Formas de Pago.", - ) - # Fracción americana (Clarion Col R) — SITAR fracciones-usa - from api.v1.modules.sitar.fracciones_usa.catalog_resolve import ( - resolve_american_fraction_from_sitar, - ) - - frac_ame = _get(row, "FRACCION AMERICANA", "FRACCIONAMERICANA") - if frac_ame and not resolve_american_fraction_from_sitar(frac_ame): - return _err( - line_num, - "FRACCION AMERICANA", - f"Error: (Celda R{line_num}) La Fracción Americana: {frac_ame} no está en el catálogo SITAR (fracciones USA). " - "Capturar en la Celda R una fracción válida según SITAR.", - ) - # Orden de compra / orden venta (Clarion Col S) máx 20 - orden = _get(row, "ORDEN DE COMPRA", "ORDENCOMPRA", "ORDEN DE VENTA") - if orden and len(orden) > MAX_LEN_ORDEN_COMPRA: - return _err( - line_num, - "ORDEN DE COMPRA", - f"Error: (Celda S{line_num}) La Orden de Venta: {orden} supera la cantidad de caracteres permitidos. " - "Capturar en la Celda S una orden de compra no mayor de 20 caracteres.", - ) - # Número de parte (Clarion Col T): longitud y catálogo - num_parte = _get(row, "NUM. PARTE", "NUMPARTE", "NUMERO PARTE", "NUM PARTE") - if num_parte: - if len(num_parte) > MAX_LEN_NUM_PARTE: - return _err( - line_num, - "NUM. PARTE", - f"Error: (Celda T{line_num}) El Número de Parte: {num_parte} supera la longitud de caracteres. " - "Capturar en la Celda T el Número de Parte no mayor de 30 caracteres.", - ) - if valid_part_numbers is not None and num_parte.upper() not in valid_part_numbers: - return _err( - line_num, - "NUM. PARTE", - f"Error: (Celda T{line_num}) El Número de Parte: {num_parte} no existe en el Catálogo de Partes. Darlo de alta en el Catálogo de Partes.", - ) - # Decimales PZA - if validar_decimales_pza and um and um.upper() in ["PZA", "PIEZA", "PIEZAS", "PZAS", "PCE", "1"] and cant_str: - d = _parse_decimal(cant_str) - if d is not None and d != int(d): - return _err( - line_num, - "CANTIDAD EXPORTADA/DESCARGAR", - f"Error: (Celda G{line_num}) La Unidad de Medida es PZA, Por lo Tanto no es Válida la Captura de Decimales. Asignar una Cantidad sin decimales.", - ) - return None - - -def validate_row_partidas_expo( - row: Dict[str, Any], - line_num: int, - autonumerar: bool, - actualizar: bool, - levantar_subpartidas: bool, - invoice_id_by_number: Dict[str, int], - invoice_processed_by_number: Dict[str, bool], - rfc_exception_updated: Set[str], - existing_line_keys_by_invoice: Dict[str, Set[str]], - line_counts_csv: Dict[Tuple[str, str], int], - partidas_principales_csv: Set[Tuple[str, str]], - partidas_principales_bd: Set[Tuple[str, str]], - valid_uom_codes: Set[str], - valid_bulks_codes: Set[str], - valid_payment_methods: Set[str], - valid_fraction_ame: Set[str], - valid_part_numbers: Optional[Set[str]], - factura_impo_tem_by_number: Dict[str, int], - factura_impo_def_by_number: Dict[str, int], - line_exists_tem: Set[Tuple[int, str]], - line_exists_def: Set[Tuple[int, str]], - rfc_exception_egm: bool = False, - validar_decimales_pza: bool = False, -) -> List[Dict[str, Any]]: - """ - Valida una fila de CSV de Partidas de Exportación Definitiva (o Cambio de Régimen). - Clarion: VALIDA_TODA_PAR_EXPO vs VALIDA_PARCIAL_PAR_EXPO según autonumerar, actualizar y si la partida existe. - """ - errors: List[Dict[str, Any]] = [] - - err = _check_factura_expo_vacia(row, line_num) - if err: - errors.append(err) - return errors - - factura_expo = _get(row, "NUMERO FACTURA EXPO", "NUMERO FACTURA EXPO.", "NUM FACTURA EXPO", "FACTURA EXPO") - if not factura_expo: - errors.append(_err(line_num, "NUMERO FACTURA EXPO", "Requerido")) - return errors - - for err in [ - _check_factura_expo_existe(factura_expo, line_num, invoice_id_by_number), - _check_factura_expo_no_actualizada(factura_expo, line_num, invoice_processed_by_number, rfc_exception_updated), - _check_linea_expo_si_no_autonumerar(row, line_num, autonumerar), - _check_levantar_subpartidas_expo(row, line_num, levantar_subpartidas, rfc_exception_egm), - ]: - if err: - errors.append(err) - - if errors: - return errors - - linea_expo = _get(row, "LINEA EXPO", "LINEA EXPO.", "RENGLON EXPO") - existing_lines = existing_line_keys_by_invoice.get(factura_expo.strip(), set()) - partida_existe = bool(linea_expo and linea_expo in existing_lines) - use_partial = actualizar and not autonumerar and partida_existe - - _expo_kwargs = dict( - valid_uom_codes=valid_uom_codes, - valid_bulks_codes=valid_bulks_codes, - valid_payment_methods=valid_payment_methods, - valid_fraction_ame=valid_fraction_ame, - valid_part_numbers=valid_part_numbers, - factura_impo_tem_by_number=factura_impo_tem_by_number, - factura_impo_def_by_number=factura_impo_def_by_number, - line_exists_tem=line_exists_tem, - line_exists_def=line_exists_def, - validar_decimales_pza=validar_decimales_pza, - ) - - if use_partial: - err = _validaciones_par_expo(row, line_num, factura_expo, **_expo_kwargs) - if err: - errors.append(err) - else: - err = _valida_toda_obligatorios_expo(row, line_num, levantar_subpartidas, rfc_exception_egm) - if err: - errors.append(err) - if levantar_subpartidas: - for err in [ - _valida_subpartidas_duplicados_expo(factura_expo, linea_expo or "", line_num, line_counts_csv), - _valida_subpartida_tiene_principal_expo(row, line_num, factura_expo, partidas_principales_csv, partidas_principales_bd, rfc_exception_egm), - _valida_subpartida_linea_principal_no_cero_expo(row, line_num, rfc_exception_egm), - ]: - if err: - errors.append(err) - err = _validaciones_par_expo(row, line_num, factura_expo, **_expo_kwargs) - if err: - errors.append(err) - return errors diff --git a/backend/api/v1/modules/a76/layouts_csv/facturas/validators/partidas_impo_def.py b/backend/api/v1/modules/a76/layouts_csv/facturas/validators/partidas_impo_def.py deleted file mode 100644 index 24ffe293..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/facturas/validators/partidas_impo_def.py +++ /dev/null @@ -1,171 +0,0 @@ -""" -Validaciones CSV para Partidas de Importación Definitiva. -Paridad Clarion: VALIDA_TODA_PARIMPO_DEF, VALIDA_PARCIAL_PARIMPO_DEF, VALIDACIONES_PARIMPO_DEF. -Reutiliza la lógica de partidas_impo_temp; solo cambia el mensaje cuando la factura no existe -(«no existe en el catálogo de Importación Definitiva») y el origen de facturas (DEF/MATDE/EXDEF en tasks.py). -Estructura de columnas: misma que partidas TEM (NUMERO FACTURA, LINEA, CLASE, ... ID TYPE). -""" -from typing import Dict, Any, Optional, Set, Tuple, List - -from .partidas_impo_temp import ( - _clip, - _get, - _check_factura_vacia, - _check_factura_no_actualizada, - _check_linea_si_no_autonumerar, - _check_levantar_subpartidas_uv, - _valida_toda_obligatorios, - _valida_toda_numericos, - _valida_subpartidas_duplicados, - _valida_subpartida_tiene_principal, - _valida_subpartida_v_no_cero, - _validaciones_parimpo_tem, - _warn_apostrofes_num_parte, -) - - -def _check_factura_existe_def( - invoice_number: str, - line_num: int, - invoice_id_by_number: Dict[str, int], - catalog_label: str = "Importación Definitiva", -) -> Optional[Dict[str, Any]]: - """Misma lógica que _check_factura_existe; mensaje específico según catalog_label (DEF o Compras Mexicanas).""" - if invoice_number not in invoice_id_by_number: - return { - "line": line_num, - "col": "NUMERO FACTURA", - "msg": ( - f"Error: (Celda A{line_num}) La Factura de Importación {invoice_number} " - f"no existe en el catálogo de {catalog_label} y no se pueden hacer las validaciones. " - ), - } - return None - - -def validate_row_partidas_impo_def( - row: Dict[str, Any], - line_num: int, - autonumerar: bool, - actualizar: bool, - levantar_subpartidas: bool, - calcular_costo_en_base_a_total: bool, - validar_decimales_pza: bool, - invoice_id_by_number: Dict[str, int], - invoice_processed_by_number: Dict[str, bool], - rfc_exception_updated: Set[str], - existing_line_keys_by_invoice: Dict[str, Set[str]], - line_counts_csv: Dict[Tuple[str, str], int], - partidas_principales_csv: Set[Tuple[str, str]], - partidas_principales_bd: Set[Tuple[str, str]], - valid_class_codes: Set[str], - class_um_by_code: Dict[str, str], - class_fraction_by_code: Dict[str, str], - class_desc_es_by_code: Dict[str, str], - class_desc_en_by_code: Dict[str, str], - valid_uom_codes: Set[str], - valid_bulks_codes: Set[str], - valid_country_keys: Set[str], - valid_fraction_ame: Set[str], - valid_payment_methods: Set[str], - valid_valuation_methods: Set[str], - authorized_sectors: Set[str], - company_has_prosec: bool, - rfc_exception_num_parte: Optional[Set[str]], - valid_part_numbers: Optional[Set[str]], - warnings: Optional[List[Dict[str, Any]]] = None, - catalog_label: str = "Importación Definitiva", -) -> List[Dict[str, Any]]: - """ - Valida una fila de CSV de Partidas de Importación Definitiva. - Clarion: VALIDA_TODA_PARIMPO_DEF vs VALIDA_PARCIAL_PARIMPO_DEF según autonumerar, actualizar y si la partida existe. - Reutiliza todo de partidas_impo_temp salvo el check de factura existente (mensaje DEF). - """ - errors: List[Dict[str, Any]] = [] - - err = _check_factura_vacia(row, line_num) - if err: - errors.append(err) - return errors - - invoice_number = _get(row, "NUMERO FACTURA", "NUM FACTURA", "FACTURA") - if not invoice_number: - errors.append({"line": line_num, "col": "NUMERO FACTURA", "msg": "Requerido"}) - return errors - - for err in [ - _check_factura_existe_def(invoice_number, line_num, invoice_id_by_number, catalog_label), - _check_factura_no_actualizada(invoice_number, line_num, invoice_processed_by_number, rfc_exception_updated), - _check_linea_si_no_autonumerar(row, line_num, autonumerar), - _check_levantar_subpartidas_uv(row, line_num, levantar_subpartidas), - ]: - if err: - errors.append(err) - - if errors: - return errors - - _warn_apostrofes_num_parte(row, line_num, warnings) - - linea = _get(row, "LINEA", "RENGLON", "PARTIDA") - existing_lines = existing_line_keys_by_invoice.get(invoice_number.strip(), set()) - partida_existe = bool(linea and linea in existing_lines) - use_partial = actualizar and not autonumerar and partida_existe - - _validaciones_kwargs = dict( - valid_class_codes=valid_class_codes, - class_um_by_code=class_um_by_code, - class_fraction_by_code=class_fraction_by_code, - class_desc_es_by_code=class_desc_es_by_code, - class_desc_en_by_code=class_desc_en_by_code, - valid_uom_codes=valid_uom_codes, - valid_bulks_codes=valid_bulks_codes, - valid_country_keys=valid_country_keys, - valid_fraction_ame=valid_fraction_ame, - valid_payment_methods=valid_payment_methods, - valid_valuation_methods=valid_valuation_methods, - authorized_sectors=authorized_sectors, - company_has_prosec=company_has_prosec, - validar_decimales_pza=validar_decimales_pza, - rfc_exception_num_parte=rfc_exception_num_parte, - invoice_number=invoice_number, - ) - - if use_partial: - err = _validaciones_parimpo_tem(row, line_num, **_validaciones_kwargs) - if err: - errors.append(err) - else: - err = _valida_toda_obligatorios(row, line_num, levantar_subpartidas, calcular_costo_en_base_a_total) - if err: - errors.append(err) - num_parte = _get(row, "NUM. PARTE", "NUMPARTE", "NUMERO PARTE", "NUM PARTE") - if not (num_parte and str(num_parte).strip()): - errors.append({ - "line": line_num, - "col": "NUM. PARTE", - "msg": f"NUM. PARTE: Requerido (obligatorio para partidas de {catalog_label}).", - }) - err = _valida_toda_numericos(row, line_num, calcular_costo_en_base_a_total) - if err: - errors.append(err) - if levantar_subpartidas: - for err in [ - _valida_subpartidas_duplicados(invoice_number, linea, line_num, line_counts_csv), - _valida_subpartida_tiene_principal(row, line_num, partidas_principales_csv, partidas_principales_bd), - _valida_subpartida_v_no_cero(row, line_num), - ]: - if err: - errors.append(err) - err = _validaciones_parimpo_tem(row, line_num, **_validaciones_kwargs) - if err: - errors.append(err) - if rfc_exception_num_parte and invoice_number in rfc_exception_num_parte and valid_part_numbers is not None: - num_parte = _get(row, "NUM. PARTE", "NUMPARTE", "NUMERO PARTE", "NUM PARTE") - if num_parte and num_parte.upper() not in valid_part_numbers: - errors.append({ - "line": line_num, - "col": "NUM. PARTE", - "msg": f"Error: (Celda W{line_num}) El número de parte Capturado: {num_parte} no existe.", - }) - return errors diff --git a/backend/api/v1/modules/a76/layouts_csv/facturas/validators/partidas_impo_temp.py b/backend/api/v1/modules/a76/layouts_csv/facturas/validators/partidas_impo_temp.py deleted file mode 100644 index f69350f4..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/facturas/validators/partidas_impo_temp.py +++ /dev/null @@ -1,552 +0,0 @@ -""" -Validaciones CSV para Partidas de Importación Temporal. -Paridad Clarion: VALIDA_TODA_PARIMPO_TEM, VALIDA_PARCIAL_PARIMPO_TEM, VALIDACIONES_PARIMPO_TEM. -Estructura: NUMERO FACTURA, LINEA, CLASE, CANTIDAD IMPORTADA, ... hasta ID TYPE (columnas A-AG). -""" -from decimal import Decimal, InvalidOperation -from typing import Dict, Any, Optional, Set, Tuple, List - -# Longitudes máximas Clarion -MAX_LEN_FACTURA = 15 -MAX_LEN_LINEA = 5 -MAX_LEN_CLASE = 9 -MAX_LEN_CANTIDAD_STR = 19 -MAX_LEN_ORDEN_COMPRA = 20 - -PREFERENCIAS_VALIDAS = frozenset({"GENERAL", "TLCS", "PROSEC", "ALADI"}) -PREFERENCIA_ALIASES = { - "N": "GENERAL", - "NORMAL": "GENERAL", - "GRAL": "GENERAL", - "TLC": "TLCS", - "PRO": "PROSEC", -} -SE_PAGO_IMPUESTO_VALIDOS = frozenset({"SI", "NO"}) - -APOSTROFE = "'" - - -def _clip(val: Any) -> str: - if val is None: - return "" - return str(val).strip() - - -def _parse_decimal(val: Any) -> Optional[Decimal]: - if val is None: - return None - s = _clip(val) - if not s: - return None - s = s.replace(",", "") - try: - return Decimal(s) - except (InvalidOperation, ValueError): - return None - - -def _get(row: Dict[str, Any], *keys: str) -> str: - for k in keys: - v = row.get(k) - if v is not None and str(v).strip(): - return _clip(v) - return "" - - -def _norm_invoice(value: Any) -> str: - return _clip(value).upper() - - -def normalize_preferencia_arancelaria(value: Any) -> str: - """Map CSV/manual textual variants to system canonical preference types.""" - raw = _clip(value) - if not raw: - return "" - text = raw.upper() - return PREFERENCIA_ALIASES.get(text, text) - - -def _check_factura_vacia(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]: - val = _get(row, "NUMERO FACTURA", "NUM FACTURA", "FACTURA") - if not val: - return { - "line": line_num, - "col": "NUMERO FACTURA", - "msg": "Error: (Celda A) La Factura de Importación está vacía y no se pueden hacer las validaciones.", - } - return None - - -def _check_factura_existe( - invoice_number: str, - line_num: int, - invoice_id_by_number: Dict[str, int], -) -> Optional[Dict[str, Any]]: - if _norm_invoice(invoice_number) not in invoice_id_by_number: - return { - "line": line_num, - "col": "NUMERO FACTURA", - "msg": f"Error: (Celda A) La Factura de Importación {invoice_number} no existe en SCAII y no se pueden hacer las validaciones.", - } - return None - - -def _check_factura_no_actualizada( - invoice_number: str, - line_num: int, - invoice_processed_by_number: Dict[str, bool], - rfc_exception_updated: Set[str], -) -> Optional[Dict[str, Any]]: - inv_norm = _norm_invoice(invoice_number) - if inv_norm in rfc_exception_updated: - return None - if invoice_processed_by_number.get(inv_norm, False): - return { - "line": line_num, - "col": "NUMERO FACTURA", - "msg": f"Error: (Celda A) La Factura de Importación: {invoice_number} ya existe y está Actualizada, no se puede hacer cambios a las facturas actualizadas.", - } - return None - - -def _check_linea_si_no_autonumerar(row: Dict[str, Any], line_num: int, autonumerar: bool) -> Optional[Dict[str, Any]]: - if autonumerar: - return None - val = _get(row, "LINEA", "RENGLON", "PARTIDA") - if not val: - return { - "line": line_num, - "col": "LINEA", - "msg": "Error: (Celda B) El campo de la línea de la partida está vacío y no se pueden hacer las validaciones, ya que se tiene la opción de Autonumerar como NO.", - } - return None - - -def _check_levantar_subpartidas_uv( - row: Dict[str, Any], line_num: int, levantar_subpartidas: bool -) -> Optional[Dict[str, Any]]: - if not levantar_subpartidas: - return None - u = _get(row, "ES PARTIDA O SUBPARTIDA", "ESSUBPARTIDA") - if not u: - return { - "line": line_num, - "col": "ES PARTIDA O SUBPARTIDA", - "msg": "Error: (Celda U) El campo del tipo de la partida (partida o subpartida) está vacío y no se pueden hacer las validaciones, ya que se tiene la opción de Levantar Subpartidas como Si.", - } - v = _get(row, "LINEA PRINCIPAL", "LINEAPRINCIPAL", "PARTIDA PRINCIPAL") - if not v: - return { - "line": line_num, - "col": "LINEA PRINCIPAL", - "msg": "Error: (Celda V) El campo de la partida principal está vacío y no se pueden hacer las validaciones, ya que se tiene la opción de Levantar Subpartidas como Si.", - } - return None - - -def _valida_toda_obligatorios( - row: Dict[str, Any], - line_num: int, - levantar_subpartidas: bool, - calcular_costo_en_base_a_total: bool, -) -> Optional[Dict[str, Any]]: - """Obligatorios vacíos: C, D, F (condicional), G, K, M; U, V si LevantarSubpartidas.""" - obligatorios: List[str] = [] - if not _get(row, "CLASE"): - obligatorios.append("(Col.C) Clases") - if not _get(row, "CANTIDAD IMPORTADA", "CANTIDAD"): - obligatorios.append("(Col.D) Cantidad Importada") - es_subpartida = _get(row, "ES PARTIDA O SUBPARTIDA", "ESSUBPARTIDA").upper() == "S" - if not calcular_costo_en_base_a_total and not es_subpartida: - if not _get(row, "COSTO UNITARIO", "COSTOUNITARIO", "PRECIO UNITARIO", "PRECIOUNITARIO"): - obligatorios.append("(Col.F) Costo Unitario") - if not _get(row, "PESO NETO", "PESONETO"): - obligatorios.append("(Col.G) Peso Neto") - if not _get(row, "PAIS ORIGEN", "PAISORIGEN", "PAIS"): - obligatorios.append("(Col.K) País") - if not _get(row, "PREFERENCIA ARANCELARIA", "PREFERENCIA", "PREFERENCIAARANCELARIA"): - obligatorios.append("(Col.M) Preferencia.") - if levantar_subpartidas: - if not _get(row, "ES PARTIDA O SUBPARTIDA", "ESSUBPARTIDA"): - obligatorios.append("(Col.U) EsSubpartida?.") - if not _get(row, "LINEA PRINCIPAL", "LINEAPRINCIPAL", "PARTIDA PRINCIPAL"): - obligatorios.append("(Col.V) Partida Principal.") - if obligatorios: - return { - "line": line_num, - "col": "CLASE", - "msg": f"Existen campos vacíos que son obligatorios: {', '.join(obligatorios)}.", - } - return None - - -def _valida_toda_numericos( - row: Dict[str, Any], - line_num: int, - calcular_costo_en_base_a_total: bool, -) -> Optional[Dict[str, Any]]: - """Costo unitario y peso neto no pueden ser cero (salvo subpartida / costo por total).""" - es_subpartida = _get(row, "ES PARTIDA O SUBPARTIDA", "ESSUBPARTIDA").upper() == "S" - if not calcular_costo_en_base_a_total and not es_subpartida: - costo = _parse_decimal(row.get("COSTO UNITARIO") or row.get("COSTOUNITARIO") or row.get("PRECIO UNITARIO") or row.get("PRECIOUNITARIO")) - if costo is not None and costo == 0: - return { - "line": line_num, - "col": "COSTO UNITARIO", - "msg": f"Error: (Celda F{line_num}) El Costo Unitario no puede ser cero.", - } - peso_neto = _parse_decimal(row.get("PESO NETO") or row.get("PESONETO")) - if peso_neto is not None and peso_neto == 0: - return { - "line": line_num, - "col": "PESO NETO", - "msg": f"Error: (Celda G{line_num}) El Peso Neto no puede ser cero.", - } - return None - - -def _valida_subpartidas_duplicados( - invoice_number: str, - linea: str, - line_num: int, - line_counts: Dict[Tuple[str, str], int], -) -> Optional[Dict[str, Any]]: - key = (_norm_invoice(invoice_number), _clip(linea)) - if line_counts.get(key, 0) > 1: - return { - "line": line_num, - "col": "LINEA", - "msg": f"Error: (Celda B{line_num}) El campo de la partida está duplicado entre las partidas.", - } - return None - - -def _valida_subpartida_tiene_principal( - row: Dict[str, Any], - line_num: int, - partidas_principales_en_csv: Set[Tuple[str, str]], - partidas_principales_en_bd: Set[Tuple[str, str]], -) -> Optional[Dict[str, Any]]: - u = _get(row, "ES PARTIDA O SUBPARTIDA", "ESSUBPARTIDA").upper() - v = _get(row, "LINEA PRINCIPAL", "LINEAPRINCIPAL", "PARTIDA PRINCIPAL") - if u != "S" or not v or v == "0": - return None - inv = _get(row, "NUMERO FACTURA", "NUM FACTURA", "FACTURA") - if not inv: - return None - key_principal = (_norm_invoice(inv), _clip(v)) - if key_principal in partidas_principales_en_csv or key_principal in partidas_principales_en_bd: - return None - return { - "line": line_num, - "col": "LINEA PRINCIPAL", - "msg": f"Error: (Celda V{line_num}) La partida principal {v} no existe.", - } - - -def _valida_subpartida_v_no_cero(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]: - u = _get(row, "ES PARTIDA O SUBPARTIDA", "ESSUBPARTIDA").upper() - v = _get(row, "LINEA PRINCIPAL", "LINEAPRINCIPAL", "PARTIDA PRINCIPAL") - if u == "S" and v == "0": - return { - "line": line_num, - "col": "LINEA PRINCIPAL", - "msg": f"Error: (Celda V{line_num}) La SubPartida no tiene asignada una partida principal.", - } - return None - - -def _validaciones_parimpo_tem( - row: Dict[str, Any], - line_num: int, - valid_class_codes: Set[str], - class_um_by_code: Dict[str, str], - class_fraction_by_code: Dict[str, str], - class_desc_es_by_code: Dict[str, str], - class_desc_en_by_code: Dict[str, str], - valid_uom_codes: Set[str], - valid_bulks_codes: Set[str], - valid_country_keys: Set[str], - valid_fraction_ame: Set[str], - valid_payment_methods: Set[str], - valid_valuation_methods: Set[str], - authorized_sectors: Set[str], - company_has_prosec: bool, - validar_decimales_pza: bool, - rfc_exception_num_parte: Optional[Set[str]], - invoice_number: str, -) -> Optional[Dict[str, Any]]: - """VALIDACIONES_PARIMPO_TEM: longitudes, catálogos, reglas de negocio.""" - - def err(col: str, msg: str) -> Dict[str, Any]: - return {"line": line_num, "col": col, "msg": msg} - - clase = _get(row, "CLASE") - um = _get(row, "UNIDAD DE MEDIDA", "UNIDAD MEDIDA") - - # Longitudes - factura = _get(row, "NUMERO FACTURA", "NUM FACTURA", "FACTURA") - if factura and len(factura) > MAX_LEN_FACTURA: - return err("NUMERO FACTURA", f"Error: (Celda A{line_num}) La Factura de Importación: {factura} supera la longitud de caracteres.") - linea = _get(row, "LINEA", "RENGLON", "PARTIDA") - if linea and len(linea) > MAX_LEN_LINEA: - return err("LINEA", f"Error: (Celda B{line_num}) La Línea de Importación: {linea} supera la longitud de caracteres.") - if clase: - if len(clase) > MAX_LEN_CLASE: - return err("CLASE", f"Error: (Celda C{line_num}) La Clase: {clase} supera la longitud de caracteres.") - if clase.upper() not in valid_class_codes: - return err("CLASE", f"Error: (Celda C{line_num}) La Clase: {clase} no existe en el Catálogo de Clases.") - if not um and not class_um_by_code.get(clase.upper()): - return err("UNIDAD DE MEDIDA", f"Error: (Celda E{line_num}) Debido a que esta celda es vacía, se asignará la unidad de medida de la Clase pero también está vacía.") - frac = _get(row, "FRACCION ARANCELARIA", "FRACCION", "FRACCIONARANCELARIA") - if not frac and not class_fraction_by_code.get(clase.upper()): - return err("FRACCION ARANCELARIA", f"Error: (Celda L{line_num}) Debido a que esta celda es vacía, se asignará la fracción de la Clase pero también está vacía.") - if not _get(row, "DESCRIPCION ESPAÑOL", "DESCRIPCIONE", "DESCRIPCION") and not class_desc_es_by_code.get(clase.upper()): - return err("DESCRIPCION ESPAÑOL", f"Error: (Celda Q{line_num}) Debido a que esta celda es vacía, se asignará la Descripción en Español de la Clase pero también está vacía.") - if not _get(row, "DESCRIPCION INGLES", "DESCRIPCIONI") and not class_desc_en_by_code.get(clase.upper()): - return err("DESCRIPCION INGLES", f"Error: (Celda R{line_num}) Debido a que esta celda es vacía, se asignará la Descripción en Inglés de la Clase pero también está vacía.") - - # D: Cantidad importada - cant_str = _get(row, "CANTIDAD IMPORTADA", "CANTIDAD") - if cant_str: - cant = _parse_decimal(cant_str) - if cant is not None and cant <= 0: - return err("CANTIDAD IMPORTADA", f"Error: (Celda D{line_num}) La Cantidad Importada: {cant_str} es cero.") - if len(cant_str) > MAX_LEN_CANTIDAD_STR: - return err("CANTIDAD IMPORTADA", f"Error: (Celda D{line_num}) La Cantidad Importada: {cant_str} supera la cantidad de caracteres permitidos.") - - # E: Unidad de medida en catálogo - if um and um.upper() not in valid_uom_codes: - return err("UNIDAD DE MEDIDA", f"Error: (Celda E{line_num}) La Unidad de Medida: {um} no existe en el Catálogo de Unidades de Medida.") - - # I, J: Bultos condicional - clave_bultos = _get(row, "CLAVE BULTOS", "CLAVEBULTOS") - cant_bultos = row.get("CANTIDAD BULTOS") or row.get("CANTIDADBULTOS") - if clave_bultos: - if clave_bultos not in valid_bulks_codes: - return err("CLAVE BULTOS", f"Error: (Celda J{line_num}) La Clave de Bulto: {clave_bultos} no existe en el Catálogo de Claves de Bultos.") - cant_bultos_val = _parse_decimal(cant_bultos) - if cant_bultos_val is None: - return err("CANTIDAD BULTOS", f"Error: (Celda I{line_num}) La Cantidad de Bultos está vacía y en la Celda J{line_num} se tiene la Clave de Bulto.") - if cant_bultos_val == 0: - return err("CANTIDAD BULTOS", f"Error: (Celda I{line_num}) La Cantidad de Bultos es cero y en la Celda J{line_num} se tiene la Clave de Bulto.") - else: - cant_bultos_val = _parse_decimal(cant_bultos) - if cant_bultos_val is not None and cant_bultos_val > 0: - return err("CANTIDAD BULTOS", f"Error: (Celda I{line_num}) La Cantidad de Bultos es {cant_bultos} y en la Celda J{line_num} no se tiene la Clave de Bulto.") - - # K: País SAAIM3 o americana - pais = _get(row, "PAIS ORIGEN", "PAISORIGEN", "PAIS") - if pais and pais.upper() not in valid_country_keys: - return err("PAIS ORIGEN", f"Error: (Celda K{line_num}) El País: {pais} no se encontró como Clave SAAIM3 ni Clave Americana en el Catálogo de Paises.") - - # M: Preferencia - pref_raw = _get(row, "PREFERENCIA ARANCELARIA", "PREFERENCIA", "PREFERENCIAARANCELARIA") - pref = normalize_preferencia_arancelaria(pref_raw) - pref_ctx = f"{pref} (capturada: {pref_raw})" if pref_raw and pref_raw.strip().upper() != pref else pref - if pref and pref not in PREFERENCIAS_VALIDAS: - return err( - "PREFERENCIA ARANCELARIA", - f"Error: (Celda M{line_num}) La Preferencia Arancelaria: {pref_ctx} no es correcta para el sistema SCAF.", - ) - sector = _get(row, "SECTOR") - if pref == "PROSEC": - if not sector: - return err( - "PREFERENCIA ARANCELARIA", - f"Error: (Celda M{line_num} y N{line_num}) La Preferencia Arancelaria es: {pref_ctx} y en la columna N no tiene sector.", - ) - if sector not in authorized_sectors: - return err("SECTOR", f"Error: (Celda N{line_num}) El Sector: {sector} no existe en el Catálogo de Sectores.") - if not company_has_prosec: - return err("SECTOR", f"Error: (Celda N{line_num}) La empresa no cuenta con autorización PROSEC.") - elif pref and pref != "PROSEC" and sector: - return err( - "PREFERENCIA ARANCELARIA", - f"Error: (Celda M{line_num} y N{line_num}) La Preferencia Arancelaria es: {pref_ctx} y en la columna N tiene sector.", - ) - - # O: Fracción americana (SITAR fracciones-usa; valid_fraction_ame ignorado) - from api.v1.modules.sitar.fracciones_usa.catalog_resolve import ( - resolve_american_fraction_from_sitar, - ) - - frac_ame = _get(row, "FRACCION AMERICANA", "FRACCIONAMERICANA") - if frac_ame and not resolve_american_fraction_from_sitar(frac_ame): - return err( - "FRACCION AMERICANA", - f"Advertencia: (Celda O{line_num}) La Fracción Americana: {frac_ame} no existe en el catálogo SITAR (fracciones USA).", - ) - - # P: Orden de compra máx 20 - orden = _get(row, "ORDEN DE COMPRA", "ORDENCOMPRA") - if orden and len(orden) > MAX_LEN_ORDEN_COMPRA: - return err("ORDEN DE COMPRA", f"Error: (Celda P{line_num}) La Orden de Compra: {orden} supera la cantidad de caracteres permitidos.") - - # Decimales PZA - if validar_decimales_pza: - um_code = (um or class_um_by_code.get(clase.upper() or "") or "").upper() - if um_code in ["PZA", "PIEZA", "PIEZAS", "PZAS", "PCE", "1"] and cant_str: - d = _parse_decimal(cant_str) - if d is not None and d != int(d): - return err("CANTIDAD IMPORTADA", "Error: (Celda D) La Unidad de Medida es PZA, Por lo Tanto no es Válida la Captura de Decimales.") - - # Z: Método de valoración - met_val = _get(row, "METODO DE VALORACION", "METODODEVALORACION", "METODO VALORACION") - if met_val and met_val not in valid_valuation_methods: - return err("METODO DE VALORACION", f"Error: (Celda Z{line_num}) El Método de Valoración Capturado: {met_val} No es Válido.") - - # RFC excepción: W obligatorio - if rfc_exception_num_parte and invoice_number in rfc_exception_num_parte: - num_parte = _get(row, "NUM. PARTE", "NUMPARTE", "NUMERO PARTE", "NUM PARTE") - if not num_parte: - return err("NUM. PARTE", f"Error: (Celda W{line_num}) No está capturado el número de parte.") - - # X: Se pagó impuesto SI/NO - x = _get(row, "SE PAGO IMPUESTO", "SEPAGOIMPUESTO") - if x and x.upper() not in SE_PAGO_IMPUESTO_VALIDOS: - return err("SE PAGO IMPUESTO", f"Error: (Celda X{line_num}) El Valor Capturado para Se Pago Impuesto no es Válido. Capturar SI o NO.") - - # Y: Forma de pago en catálogo - forma_pago = _get(row, "FORMA DE PAGO", "FORMADEPAGO", "FORMA PAGO") - if forma_pago and forma_pago not in valid_payment_methods: - return err("FORMA DE PAGO", f"Error: (Celda Y{line_num}) La Forma de Pago Capturado no es Válido.") - - return None - - -def _warn_apostrofes_num_parte( - row: Dict[str, Any], line_num: int, warnings: Optional[List[Dict[str, Any]]] -) -> None: - val = _get(row, "NUM. PARTE", "NUMPARTE", "NUMERO PARTE", "NUM PARTE") - if val and APOSTROFE in val and warnings is not None: - warnings.append({ - "line": line_num, - "col": "NUM. PARTE", - "msg": f"Advertencia: El Número de Parte: {val} Contiene Apostrofes.", - "warning": True, - }) - - -def validate_row_partidas_impo_temp( - row: Dict[str, Any], - line_num: int, - autonumerar: bool, - actualizar: bool, - levantar_subpartidas: bool, - calcular_costo_en_base_a_total: bool, - validar_decimales_pza: bool, - invoice_id_by_number: Dict[str, int], - invoice_processed_by_number: Dict[str, bool], - rfc_exception_updated: Set[str], - existing_line_keys_by_invoice: Dict[str, Set[str]], - line_counts_csv: Dict[Tuple[str, str], int], - partidas_principales_csv: Set[Tuple[str, str]], - partidas_principales_bd: Set[Tuple[str, str]], - valid_class_codes: Set[str], - class_um_by_code: Dict[str, str], - class_fraction_by_code: Dict[str, str], - class_desc_es_by_code: Dict[str, str], - class_desc_en_by_code: Dict[str, str], - valid_uom_codes: Set[str], - valid_bulks_codes: Set[str], - valid_country_keys: Set[str], - valid_fraction_ame: Set[str], - valid_payment_methods: Set[str], - valid_valuation_methods: Set[str], - authorized_sectors: Set[str], - company_has_prosec: bool, - rfc_exception_num_parte: Optional[Set[str]], - valid_part_numbers: Optional[Set[str]], - warnings: Optional[List[Dict[str, Any]]] = None, -) -> List[Dict[str, Any]]: - """ - Punto de entrada: valida una fila de CSV de Partidas de Importación Temporal. - Clarion: VALIDA_TODA vs VALIDA_PARCIAL según autonumerar, actualizar y si la partida existe. - """ - errors: List[Dict[str, Any]] = [] - - err = _check_factura_vacia(row, line_num) - if err: - errors.append(err) - return errors - - invoice_number = _get(row, "NUMERO FACTURA", "NUM FACTURA", "FACTURA") - if not invoice_number: - errors.append({"line": line_num, "col": "NUMERO FACTURA", "msg": "Requerido"}) - return errors - - for err in [ - _check_factura_existe(invoice_number, line_num, invoice_id_by_number), - _check_factura_no_actualizada( - invoice_number, line_num, invoice_processed_by_number, rfc_exception_updated - ), - _check_linea_si_no_autonumerar(row, line_num, autonumerar), - _check_levantar_subpartidas_uv(row, line_num, levantar_subpartidas), - ]: - if err: - errors.append(err) - - if errors: - return errors - - _warn_apostrofes_num_parte(row, line_num, warnings) - - linea = _get(row, "LINEA", "RENGLON", "PARTIDA") - invoice_number_norm = _norm_invoice(invoice_number) - existing_lines = existing_line_keys_by_invoice.get(invoice_number_norm, set()) - partida_existe = bool(linea and linea in existing_lines) - use_partial = actualizar and not autonumerar and partida_existe - - _validaciones_kwargs = dict( - valid_class_codes=valid_class_codes, - class_um_by_code=class_um_by_code, - class_fraction_by_code=class_fraction_by_code, - class_desc_es_by_code=class_desc_es_by_code, - class_desc_en_by_code=class_desc_en_by_code, - valid_uom_codes=valid_uom_codes, - valid_bulks_codes=valid_bulks_codes, - valid_country_keys=valid_country_keys, - valid_fraction_ame=valid_fraction_ame, - valid_payment_methods=valid_payment_methods, - valid_valuation_methods=valid_valuation_methods, - authorized_sectors=authorized_sectors, - company_has_prosec=company_has_prosec, - validar_decimales_pza=validar_decimales_pza, - rfc_exception_num_parte=rfc_exception_num_parte, - invoice_number=invoice_number, - ) - - if use_partial: - err = _validaciones_parimpo_tem(row, line_num, **_validaciones_kwargs) - if err: - errors.append(err) - else: - for err in [ - _valida_toda_obligatorios(row, line_num, levantar_subpartidas, calcular_costo_en_base_a_total), - _valida_toda_numericos(row, line_num, calcular_costo_en_base_a_total), - ]: - if err: - errors.append(err) - if levantar_subpartidas: - for err in [ - _valida_subpartidas_duplicados(invoice_number_norm, linea, line_num, line_counts_csv), - _valida_subpartida_tiene_principal(row, line_num, partidas_principales_csv, partidas_principales_bd), - _valida_subpartida_v_no_cero(row, line_num), - ]: - if err: - errors.append(err) - err = _validaciones_parimpo_tem(row, line_num, **_validaciones_kwargs) - if err: - errors.append(err) - # Paridad con commit: si viene NUM. PARTE capturado, debe existir en catálogo. - # La excepción RFC solo controla obligatoriedad; no desactiva la verificación de existencia. - if valid_part_numbers is not None: - num_parte = _get(row, "NUM. PARTE", "NUMPARTE", "NUMERO PARTE", "NUM PARTE") - if num_parte and num_parte.upper() not in valid_part_numbers: - errors.append({ - "line": line_num, - "col": "NUM. PARTE", - "msg": f"Error: (Celda W{line_num}) El número de parte Capturado: {num_parte} no existe.", - }) - return errors diff --git a/backend/api/v1/modules/a76/layouts_csv/facturas/validators/pedimento_resolution.py b/backend/api/v1/modules/a76/layouts_csv/facturas/validators/pedimento_resolution.py deleted file mode 100644 index 7e135899..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/facturas/validators/pedimento_resolution.py +++ /dev/null @@ -1,278 +0,0 @@ -from typing import Any, Dict, List, Optional, Set, Tuple -import logging - -logger = logging.getLogger(__name__) - - -def scalar_trim(val: Any) -> str: - """ORM/csv pueden devolver tipos no-str; el matching debe ser estable.""" - if val is None: - return "" - return str(val).strip() - - -def normalize_license_for_match(s: Any) -> str: - """ - Patente aduanal: 4 dígitos. Alinea CSV vs BD (ej. 428 → 0428; espacios). - Si no es numérico o está vacío, retorna el valor strip (compatibilidad). - """ - t = scalar_trim(s) - if not t: - return "" - if t.isdigit() and len(t) <= 4: - return t.zfill(4) - return t - - -def normalize_pedimento_number_str(s: Any) -> str: - """ - Número de pedimento a 7 dígitos para comparar CSV vs BD. - - - CSV (parse_pedimento_col_a) ya entrega segmentos numéricos ≤7 dígitos. - - BD puede traer espacios, basura no numérica, o datos legacy con más de 7 dígitos - (p. ej. clave larga pegada en la columna); se extraen dígitos y, si hay más de 7, - se usan los últimos 7 (el número oficial que coincide con lo capturado en facturas). - """ - t = scalar_trim(s) - if not t: - return "" - digits = "".join(ch for ch in t if ch.isdigit()) - if not digits: - return "" - if len(digits) <= 7: - return digits.zfill(7) - return digits[-7:] - - -def _row_number_looks_like_csv_number(row_number: Any, csv_num_norm: str) -> bool: - """ - Heurística defensiva para catálogos legacy: - además del match canónico por últimos 7 dígitos, considera match - si los dígitos crudos contienen el número CSV normalizado. - """ - if not csv_num_norm: - return False - row_norm = normalize_pedimento_number_str(row_number) - if row_norm == csv_num_norm: - return True - raw_digits = "".join(ch for ch in scalar_trim(row_number) if ch.isdigit()) - return bool(raw_digits and csv_num_norm in raw_digits) - - -def normalize_catalog_pedimento_identity( - customs_office: Any, - license_val: Any, - pedimento_number: Any, -) -> Optional[Tuple[str, str, str]]: - """ - Normaliza aduana, patente y número tal como los usa resolve_pedimento_candidates - al cargar filas del catálogo en memoria. - - La patente puede venir vacía en BD (alta incompleta); se usa "" y el resolver - no exige coincidencia de patente CSV vs catálogo en ese caso. - El número de pedimento es obligatorio (sin número no hay fila válida). - """ - co = scalar_trim(customs_office) - lic_raw = scalar_trim(license_val) - num_raw = scalar_trim(pedimento_number) - if not num_raw: - return None - if lic_raw: - lic = normalize_license_for_match(lic_raw) or lic_raw - else: - lic = "" - num = normalize_pedimento_number_str(num_raw) or num_raw - return co, lic, num - - -def canonical_customs_office_for_pedimento_key(customs_office: Any) -> str: - """ - Aduana canónica de 2 dígitos para claves y matching (007 vs 07, 640 vs 64). - Tres dígitos con ceros a la izquierda tipo 007 → últimos dos (07); otros tres dígitos → primeros dos (64). - """ - co = scalar_trim(customs_office) - if not co: - return "" - if len(co) == 3 and co.isdigit(): - if co[0] == "0": - return co[1:3] - return co[:2] - if len(co) == 1 and co.isdigit(): - return co.zfill(2) - return co[:2] if len(co) >= 2 else co - - -def customs_offices_equivalent(a: str, b: str) -> bool: - """True si dos códigos de aduana representan la misma sección para efectos de pedimento.""" - ca = canonical_customs_office_for_pedimento_key(a) - cb = canonical_customs_office_for_pedimento_key(b) - if ca and cb: - return ca == cb - return _same_customs_office(a or "", b or "") - - -def _co_variants(customs_office: str) -> Set[str]: - co = scalar_trim(customs_office) - if not co: - return set() - out: Set[str] = {co} - if len(co) == 1 and co.isdigit(): - out.add(co.zfill(2)) - if len(co) >= 2: - out.add(co[:2]) - out.add(co[-2:]) - if len(co) >= 3: - out.add(co[:3]) - return {x for x in out if x} - - -def _same_customs_office(csv_customs: str, candidate_customs: str) -> bool: - csv_variants = _co_variants(csv_customs) - cand_variants = _co_variants(candidate_customs) - if not csv_variants or not cand_variants: - return False - return bool(csv_variants.intersection(cand_variants)) - - -def aduana_cruce_matches_catalog(ab: str, valid_codes: Set[str]) -> bool: - """ - Valida Col.AB contra public.customs_sections: mismo criterio que pedimento/aduana - (p. ej. CSV `070` vs catálogo `07`). - Si no hay catálogo cargado o la celda viene vacía, no rechaza. - """ - if not scalar_trim(ab) or not valid_codes: - return True - a = scalar_trim(ab) - if a in valid_codes: - return True - for vc in valid_codes: - if _same_customs_office(a, scalar_trim(vc)): - return True - return False - - -def resolve_pedimento_candidates( - customs_office: str, - license_val: str, - pedimento_number: str, - patente_lookup: Optional[str], - pedimento_rows: List[Dict[str, Any]], -) -> Dict[str, Any]: - """ - Resuelve candidatos de pedimento por componentes reales. - Prioriza match exacto por aduana/patente/numero y usa fallback por patente/numero. - """ - candidates: List[Dict[str, Any]] = [] - seen_ids: Set[Any] = set() - - lookup_licenses: List[str] = [] - lic = scalar_trim(license_val) - if lic: - lookup_licenses.append(lic) - patente = scalar_trim(patente_lookup) - if patente and patente not in lookup_licenses: - lookup_licenses.append(patente) - - lookup_license_raw: Set[str] = set() - lookup_license_norm: Set[str] = set() - for x in lookup_licenses: - if not x: - continue - rx = x.strip() - lookup_license_raw.add(rx) - nx = normalize_license_for_match(rx) - if nx: - lookup_license_norm.add(nx) - - csv_num_norm = normalize_pedimento_number_str(pedimento_number) - - logger.debug(f"Resolving pedimento: customs={customs_office}, license={license_val}, num={pedimento_number} (norm={csv_num_norm}), lookup_licenses={lookup_license_norm}") - - total_catalog_rows = len(pedimento_rows or []) - num_matches = 0 - - for info in (pedimento_rows or []): - row_num_raw = scalar_trim(info.get("pedimento_number")) - if not _row_number_looks_like_csv_number(row_num_raw, csv_num_norm): - continue - - num_matches += 1 - row_lic = scalar_trim(info.get("license")) - if lookup_license_raw or lookup_license_norm: - # Alta sin patente en catálogo: no excluir por patente del CSV (dato incompleto). - if row_lic: - row_ln = normalize_license_for_match(row_lic) - if row_lic not in lookup_license_raw and row_ln not in lookup_license_norm: - continue - pid = info.get("id") - if pid not in seen_ids: - candidates.append(info) - seen_ids.add(pid) - - if not candidates: - # Diagnostic: find why it failed - num_only_matches = [] - for info in (pedimento_rows or []): - rn_raw = scalar_trim(info.get("pedimento_number")) - if _row_number_looks_like_csv_number(rn_raw, csv_num_norm): - num_only_matches.append(info) - - # DEBUG: one line per CSV row on large jobs would flood logs; enable when diagnosing catalog mismatches. - logger.debug( - "Pedimento catalog search results: found 0 candidates for %s-%s-%s " - "(csv_num_norm: %s) among %s rows. Matches by number only: %s", - customs_office, - license_val, - pedimento_number, - csv_num_norm, - total_catalog_rows, - [{"co": c.get("customs_office"), "lic": c.get("license"), "id": c.get("id")} for c in num_only_matches], - ) - if num_only_matches: - return { - "status": "not_found", - "pedimento": None, - "candidates": [], - "not_found_reason": "license_or_patente_mismatch", - } - return { - "status": "not_found", - "pedimento": None, - "candidates": [], - "not_found_reason": "no_catalog_match", - } - - customs_filtered = [ - c for c in candidates - if _same_customs_office(customs_office, scalar_trim(c.get("customs_office"))) - ] - if len(customs_filtered) == 1: - res = customs_filtered[0] - logger.debug( - "Pedimento found: ID %s for %s-%s-%s", - res.get("id"), - customs_office, - license_val, - pedimento_number, - ) - return {"status": "ok", "pedimento": res, "candidates": customs_filtered} - if len(customs_filtered) > 1: - logger.warning(f"Ambiguous pedimento (multiple customs match): {len(customs_filtered)} candidates for {customs_office}-{license_val}-{pedimento_number}") - return {"status": "ambiguous", "pedimento": None, "candidates": customs_filtered} - - if len(candidates) == 1: - solo = candidates[0] - cand_co = scalar_trim(solo.get("customs_office")) - if not cand_co: - return {"status": "ok", "pedimento": solo, "candidates": candidates} - - logger.warning(f"Pedimento customs mismatch: expected {customs_office}, found {cand_co}") - return { - "status": "not_found", - "pedimento": None, - "candidates": [], - "not_found_reason": "customs_mismatch", - } - - logger.warning(f"Ambiguous pedimento (no customs match but multiple candidates): {len(candidates)} candidates") - return {"status": "ambiguous", "pedimento": None, "candidates": candidates} diff --git a/backend/api/v1/modules/a76/layouts_csv/facturas/validators/series_expo.py b/backend/api/v1/modules/a76/layouts_csv/facturas/validators/series_expo.py deleted file mode 100644 index 7b3ed842..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/facturas/validators/series_expo.py +++ /dev/null @@ -1,279 +0,0 @@ -""" -Validaciones CSV para Series de Exportación Definitiva. -Paridad Clarion: VALIDA_TODA_SERIES_EXPO, VALIDA_PARCIAL_SERIES_EXPO, LLENA_SERIES_EXPO. -Estructura: NUMERO FACTURA, LINEA FACTURA, LINEA SERIE, SERIE, MODELO, NUM PARTE, SUB MODELO, NUMERO ID. -Reutiliza helpers de series_impo_temp y series_impo_def; factura = exportación (FAC_EXPO), rfc_exception_updated. -""" -from typing import Dict, Any, Optional, Set, Tuple, List - -from .series_impo_temp import ( - _clip, - normalize_sacarcomasenters, - _check_desfase, - _check_linea_factura_vacia, - _check_linea_serie_si_no_autonumerar, - _warn_apostrofes, - _check_max_length, - MAX_LEN, -) -from .series_impo_def import ( - _check_partida_existe_en_factura, - _check_cantidad_series_vs_partida, - _get_val_def, -) - - -def _check_factura_vacia_expo(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]: - """NUMERO FACTURA (A) vacío → error. Mensaje Factura de Exportación.""" - val = _clip(row.get("NUMERO FACTURA") or row.get("NUM FACTURA") or row.get("FACTURA") or row.get("FACTURA EXPO")) - if not val: - return { - "line": line_num, - "col": "NUMERO FACTURA", - "msg": ( - f"Error: (Celda A{line_num}) La Factura de Exportación está vacía y no se pueden hacer las validaciones. " - ), - "solution": ( - f"Capturar en la Celda A{line_num} un número de Factura existente " - f"al cual desee agregar o actualizar series" - ), - } - return None - - -def _check_factura_existe_expo( - invoice_number: str, - line_num: int, - invoice_id_by_number: Dict[str, int], -) -> Optional[Dict[str, Any]]: - """Factura debe existir en BD (catálogo exportación).""" - if invoice_number not in invoice_id_by_number: - return { - "line": line_num, - "col": "NUMERO FACTURA", - "msg": ( - f"Error: (Celda A{line_num}) La Factura de Exportación {invoice_number} " - f"no existe en SCAII y no se pueden hacer las validaciones. " - ), - "solution": ( - f"Capturar en la Celda A{line_num} un número de Factura existente " - f"al cual desee agregar o actualizar series" - ), - "identifier": "FAC_EXPO", - "fields": invoice_number, - } - return None - - -def _check_factura_no_actualizada_expo( - invoice_number: str, - line_num: int, - invoice_processed_by_number: Dict[str, bool], - rfc_exception_updated: Set[str], -) -> Optional[Dict[str, Any]]: - """Si factura ya actualizada (Estatus AC) no se pueden hacer cambios; excepción por RFC (ej. EGM0303257J1).""" - if invoice_number in rfc_exception_updated: - return None - if invoice_processed_by_number.get(invoice_number, False): - return { - "line": line_num, - "col": "NUMERO FACTURA", - "msg": ( - f"Error: (Celda A{line_num}) La Factura de Exportación: {invoice_number} " - "ya existe y está Actualizada, no se puede hacer cambios a las facturas actualizadas." - ), - "solution": "Capturar otro número de Factura de Exportación o Desactualizar la factura.", - "identifier": "FAC_EXPO", - } - return None - - -def valida_toda_series_expo( - row: Dict[str, Any], - line_num: int, - validar_series_exception: bool, - warnings: Optional[List[Dict[str, Any]]], -) -> Optional[Dict[str, Any]]: - """ - VALIDA_TODA_SERIES_EXPO: cuando no existe la serie o autonumerar=SI. - Si D+E+F están todos vacíos y no aplica excepción ValidarSeries → error obligatorios. - Valida longitudes máximas. - """ - d = _get_val_def(row, "SERIE") - e = _get_val_def(row, "MODELO") - f = _get_val_def(row, "NUM PARTE") - campos = d + e + f - - if not campos and not validar_series_exception: - obligatorios = [] - if not d: - obligatorios.append("(Col.D) Serie") - if not e: - obligatorios.append("(Col.E) Modelo") - if not f: - obligatorios.append("(Col.F) Num. Parte") - if obligatorios: - return { - "line": line_num, - "col": "SERIE", - "msg": ( - f"Existen campos vacíos que son obligatorios al no tener ningun campo, " - f"es la {', '.join(obligatorios)}." - ), - "solution": "Revisar la línea del archivo y capturar los campos con la información correcta.", - "identifier": "ARCHIVO CSV", - } - - for col, key, max_len in [ - ("SERIE", "SERIE", MAX_LEN["serial_numbers"]), - ("MODELO", "MODELO", MAX_LEN["model"]), - ("NUM PARTE", "NUM PARTE", 50), - ("SUB MODELO", "SUB MODELO", MAX_LEN["sub_model"]), - ("NUMERO ID", "NUMERO ID", MAX_LEN["number_id"]), - ]: - val = _get_val_def(row, key) - if val: - err = _check_max_length(col, val, line_num, max_len) - if err: - return err - return None - - -def valida_parcial_series_expo( - row: Dict[str, Any], - line_num: int, - existing_series_data: Dict[str, Any], - warnings: Optional[List[Dict[str, Any]]], -) -> Optional[Dict[str, Any]]: - """ - VALIDA_PARCIAL_SERIES_EXPO: actualizar serie existente; campos vacíos se rellenan con existente. - Solo validar longitudes en campos no vacíos. - """ - for col, key, max_len in [ - ("SERIE", "SERIE", MAX_LEN["serial_numbers"]), - ("MODELO", "MODELO", MAX_LEN["model"]), - ("NUM PARTE", "NUM PARTE", 50), - ("SUB MODELO", "SUB MODELO", MAX_LEN["sub_model"]), - ("NUMERO ID", "NUMERO ID", MAX_LEN["number_id"]), - ]: - val = _get_val_def(row, key) - if val: - err = _check_max_length(col, val, line_num, max_len) - if err: - return err - return None - - -def _series_key(invoice_number: str, linea_factura: str, linea_serie: str) -> Tuple[str, str, str]: - return (invoice_number.strip(), _clip(linea_factura), _clip(linea_serie)) - - -def validate_row_series_expo( - row: Dict[str, Any], - line_num: int, - actualizar: bool, - autonumerar: bool, - validar_series_exception: bool, - invoice_id_by_number: Dict[str, int], - invoice_processed_by_number: Dict[str, bool], - rfc_exception_updated: Set[str], - partida_max_series: Dict[Tuple[str, str], int], - csv_series_count_so_far: Dict[Tuple[str, str], int], - existing_series_keys: Set[Tuple[str, str, str]], - existing_series_data: Optional[Dict[Tuple[str, str, str], Dict[str, Any]]], - warnings: Optional[List[Dict[str, Any]]] = None, -) -> List[Dict[str, Any]]: - """ - Punto de entrada: valida una fila de CSV de Series de Exportación Definitiva. - Clarion: decisión VALIDA_TODA vs VALIDA_PARCIAL según autonumerar, actualizar y si la serie existe. - rfc_exception_updated: set de números de factura que se consideran no actualizadas (ej. EGM0303257J1). - """ - errors: List[Dict[str, Any]] = [] - - desfase = _check_desfase(row, line_num) - if desfase and warnings is not None: - warnings.append(desfase) - - err = _check_factura_vacia_expo(row, line_num) - if err: - errors.append(err) - return errors - - invoice_number = _clip( - row.get("NUMERO FACTURA") or row.get("NUM FACTURA") or row.get("FACTURA") or row.get("FACTURA EXPO") - ) - if not invoice_number: - errors.append({"line": line_num, "col": "NUMERO FACTURA", "msg": "Requerido"}) - return errors - - linea_factura = _clip(row.get("LINEA FACTURA") or row.get("LINEA") or row.get("PARTIDA")) - - for err in [ - _check_factura_existe_expo(invoice_number, line_num, invoice_id_by_number), - _check_factura_no_actualizada_expo(invoice_number, line_num, invoice_processed_by_number, rfc_exception_updated), - _check_linea_factura_vacia(row, line_num), - _check_partida_existe_en_factura(invoice_number, linea_factura, line_num, partida_max_series), - _check_cantidad_series_vs_partida(invoice_number, linea_factura, line_num, partida_max_series, csv_series_count_so_far), - _check_linea_serie_si_no_autonumerar(row, line_num, autonumerar), - ]: - if err: - errors.append(err) - - if errors: - return errors - - _warn_apostrofes(row, line_num, warnings) - - linea_serie = _clip(row.get("LINEA SERIE") or row.get("RENGLON")) - key = _series_key(invoice_number, linea_factura, linea_serie) - - use_partial = ( - actualizar - and not autonumerar - and bool(linea_serie) - and key in (existing_series_keys or set()) - ) - - if use_partial and existing_series_data and key in existing_series_data: - err = valida_parcial_series_expo(row, line_num, existing_series_data[key], warnings) - else: - err = valida_toda_series_expo(row, line_num, validar_series_exception, warnings) - - if err: - errors.append(err) - return errors - - -def row_to_series_normalized_expo(row: Dict[str, Any]) -> Dict[str, Any]: - """ - Normaliza fila para guardar: SACARCOMASENTERS en D, E, F, G, H. - Clarion LLENA_SERIES_EXPO: asigna QueCSV a SerExpo. - NUM PARTE se valida pero el modelo Serie no tiene campo parte. - """ - def clip(col: str, alt: Optional[List[str]] = None) -> str: - v = row.get(col) - if alt: - for k in alt: - if v is None or (isinstance(v, str) and not v.strip()): - v = row.get(k) - return _clip(v) if v is not None else "" - - def norm(col: str, alt: Optional[List[str]] = None, max_len: int = 50) -> str: - v = row.get(col) - if alt: - for k in alt: - if v is None or (isinstance(v, str) and not v.strip()): - v = row.get(k) - s = normalize_sacarcomasenters(v) if v is not None else "" - return s[:max_len] if s else "" - - return { - "NUMERO FACTURA": clip("NUMERO FACTURA", ["NUM FACTURA", "FACTURA", "FACTURA EXPO"]), - "LINEA FACTURA": clip("LINEA FACTURA", ["LINEA", "PARTIDA"]), - "LINEA SERIE": clip("LINEA SERIE", ["RENGLON"]), - "SERIE": norm("SERIE", max_len=MAX_LEN["serial_numbers"]), - "MODELO": norm("MODELO", max_len=MAX_LEN["model"]), - "NUM PARTE": norm("NUM PARTE", ["NUMPARTE", "NUMERO PARTE"]), - "SUB MODELO": norm("SUB MODELO", ["SUBMODELO"], MAX_LEN["sub_model"]), - "NUMERO ID": norm("NUMERO ID", ["NUMEROID"], MAX_LEN["number_id"]), - } diff --git a/backend/api/v1/modules/a76/layouts_csv/facturas/validators/series_impo_def.py b/backend/api/v1/modules/a76/layouts_csv/facturas/validators/series_impo_def.py deleted file mode 100644 index 053a94de..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/facturas/validators/series_impo_def.py +++ /dev/null @@ -1,323 +0,0 @@ -""" -Validaciones CSV para Series de Importación Definitiva. -Paridad Clarion: VALIDA_TODA_SERIES_IMPO_DEF, VALIDA_PARCIAL_SERIES_IMPO_DEF, LLENA_SERIES_IMPO_DEF. -Estructura: NUMERO FACTURA, LINEA FACTURA, LINEA SERIE, SERIE, MODELO, NUM PARTE, SUB MODELO, NUMERO ID. -Reutiliza helpers de series_impo_temp; añade reglas DEF: factura en catálogo DEF, cantidad series vs partida. -""" -from typing import Dict, Any, Optional, Set, Tuple, List - -from .series_impo_temp import ( - _clip, - normalize_sacarcomasenters, - _check_desfase, - _check_factura_vacia, - _check_linea_factura_vacia, - _check_linea_serie_si_no_autonumerar, - _warn_apostrofes, - _check_max_length, - MAX_LEN, -) - - -def _check_factura_existe_def( - invoice_number: str, - line_num: int, - invoice_id_by_number: Dict[str, int], - catalog_label: str = "Importación Definitiva", -) -> Optional[Dict[str, Any]]: - """Factura debe existir en BD (Importación Definitiva o Compras Mexicanas según catalog_label).""" - if invoice_number not in invoice_id_by_number: - return { - "line": line_num, - "col": "NUMERO FACTURA", - "msg": ( - f"Error: (Celda A{line_num}) La Factura de Importación {invoice_number} " - f"no existe en SCAII y no se pueden hacer las validaciones. " - ), - "solution": ( - f"Capturar en la Celda A{line_num} un número de Factura existente " - f"al cual desee agregar o actualizar series" - ), - "identifier": "FAC_IMPO_DEF", - "fields": invoice_number, - } - return None - - -def _check_factura_no_actualizada_def( - invoice_number: str, - line_num: int, - invoice_processed_by_number: Dict[str, bool], - catalog_label: str = "Importación Definitiva", -) -> Optional[Dict[str, Any]]: - """Si factura ya actualizada (Estatus AC) no se pueden hacer cambios. Mensaje según catalog_label.""" - if invoice_processed_by_number.get(invoice_number, False): - return { - "line": line_num, - "col": "NUMERO FACTURA", - "msg": ( - f"Error: (Celda A{line_num}) La Factura de {catalog_label}: {invoice_number} " - "ya existe y está Actualizada, no se puede hacer cambios a las facturas actualizadas." - ), - "solution": ( - "Capturar otro número de Factura de Importación Temporal o Desactualizar la factura." - ), - "identifier": "FAC_IMPO_DEF", - } - return None - - -def _check_partida_existe_en_factura( - invoice_number: str, - linea_factura: str, - line_num: int, - partida_max_series: Dict[Tuple[str, str], int], -) -> Optional[Dict[str, Any]]: - """ - Clarion: la partida (línea de factura) debe existir en la factura. - partida_max_series tiene como claves (invoice_number, line_number) de las partidas existentes. - """ - if not linea_factura: - return None - key = (invoice_number.strip(), _clip(linea_factura)) - if key in partida_max_series: - return None - return { - "line": line_num, - "col": "LINEA FACTURA", - "msg": f"Partida línea {linea_factura} no existe en la factura.", - "solution": "Usar un número de línea de partida que exista en la factura (capturar partidas antes de cargar series).", - "identifier": "ARCHIVO CSV", - } - - -def _check_cantidad_series_vs_partida( - invoice_number: str, - linea_factura: str, - line_num: int, - partida_max_series: Dict[Tuple[str, str], int], - csv_series_count_so_far: Dict[Tuple[str, str], int], -) -> Optional[Dict[str, Any]]: - """ - Clarion: si cantidad de series en CSV para (factura, partida) excede CantImpoDef → error. - partida_max_series[(inv, line)] = máximo permitido (desde LineQuantity.quantity). - csv_series_count_so_far = conteo actual de filas válidas ya procesadas por (inv, line). - """ - key = (invoice_number.strip(), _clip(linea_factura)) - max_allowed = partida_max_series.get(key) - if max_allowed is None or max_allowed <= 0: - return None - current = csv_series_count_so_far.get(key, 0) - if current + 1 > max_allowed: - return { - "line": line_num, - "col": "LINEA FACTURA", - "msg": ( - f"Error: La cantidad de Series de la partida {_clip(linea_factura)} en la factura " - f"{invoice_number} es menor al número de Series en el archivo. " - ), - "solution": "Nivelar la cantidad de la Partida o el número de Series.", - "identifier": "ARCHIVO CSV", - } - return None - - -def _get_val_def(row: Dict[str, Any], k: str) -> str: - """Obtener valor normalizado por clave (aliases DEF/TEM).""" - if k == "NUM PARTE": - return _clip(row.get("NUM PARTE") or row.get("NUMPARTE") or row.get("NUMERO PARTE")) - if k == "SUB MODELO": - return _clip(row.get("SUB MODELO") or row.get("SUBMODELO")) - if k == "NUMERO ID": - return _clip(row.get("NUMERO ID") or row.get("NUMEROID")) - return _clip(row.get(k)) - - -def valida_toda_series_impo_def( - row: Dict[str, Any], - line_num: int, - validar_series_exception: bool, - warnings: Optional[List[Dict[str, Any]]], -) -> Optional[Dict[str, Any]]: - """ - VALIDA_TODA_SERIES_IMPO_DEF: cuando no existe la serie o autonumerar=SI. - Si D+E+F están todos vacíos y no aplica excepción ValidarSeries → error obligatorios. - Valida longitudes máximas. - """ - d = _get_val_def(row, "SERIE") - e = _get_val_def(row, "MODELO") - f = _get_val_def(row, "NUM PARTE") - campos = d + e + f - - if not campos and not validar_series_exception: - obligatorios = [] - if not d: - obligatorios.append("(Col.D) Serie") - if not e: - obligatorios.append("(Col.E) Modelo") - if not f: - obligatorios.append("(Col.F) Num. Parte") - if obligatorios: - return { - "line": line_num, - "col": "SERIE", - "msg": ( - f"Existen campos vacíos que son obligatorios al no tener ningun campo, " - f"es la {', '.join(obligatorios)}." - ), - "solution": "Revisar la línea del archivo y capturar los campos con la información correcta.", - "identifier": "ARCHIVO CSV", - } - - for col, key, max_len in [ - ("SERIE", "SERIE", MAX_LEN["serial_numbers"]), - ("MODELO", "MODELO", MAX_LEN["model"]), - ("NUM PARTE", "NUM PARTE", 50), - ("SUB MODELO", "SUB MODELO", MAX_LEN["sub_model"]), - ("NUMERO ID", "NUMERO ID", MAX_LEN["number_id"]), - ]: - val = _get_val_def(row, key) - if val: - err = _check_max_length(col, val, line_num, max_len) - if err: - return err - return None - - -def valida_parcial_series_impo_def( - row: Dict[str, Any], - line_num: int, - existing_series_data: Dict[str, Any], - warnings: Optional[List[Dict[str, Any]]], -) -> Optional[Dict[str, Any]]: - """ - VALIDA_PARCIAL_SERIES_IMPO_DEF: actualizar serie existente; campos vacíos se rellenan con existente. - Solo validar longitudes en campos no vacíos. - """ - for col, key, max_len in [ - ("SERIE", "SERIE", MAX_LEN["serial_numbers"]), - ("MODELO", "MODELO", MAX_LEN["model"]), - ("NUM PARTE", "NUM PARTE", 50), - ("SUB MODELO", "SUB MODELO", MAX_LEN["sub_model"]), - ("NUMERO ID", "NUMERO ID", MAX_LEN["number_id"]), - ]: - val = _get_val_def(row, key) - if val: - err = _check_max_length(col, val, line_num, max_len) - if err: - return err - return None - - -def _series_key(invoice_number: str, linea_factura: str, linea_serie: str) -> Tuple[str, str, str]: - return (invoice_number.strip(), _clip(linea_factura), _clip(linea_serie)) - - -def validate_row_series_impo_def( - row: Dict[str, Any], - line_num: int, - actualizar: bool, - autonumerar: bool, - validar_series_exception: bool, - invoice_id_by_number: Dict[str, int], - invoice_processed_by_number: Dict[str, bool], - partida_max_series: Dict[Tuple[str, str], int], - csv_series_count_so_far: Dict[Tuple[str, str], int], - existing_series_keys: Set[Tuple[str, str, str]], - existing_series_data: Optional[Dict[Tuple[str, str, str], Dict[str, Any]]], - warnings: Optional[List[Dict[str, Any]]] = None, - catalog_label: str = "Importación Definitiva", -) -> List[Dict[str, Any]]: - """ - Punto de entrada: valida una fila de CSV de Series de Importación Definitiva. - Clarion: decisión VALIDA_TODA vs VALIDA_PARCIAL según autonumerar, actualizar y si la serie existe. - partida_max_series: máximo de series por (invoice_number, linea_factura); 0 o ausente = no validar. - csv_series_count_so_far: conteo de filas ya aceptadas por (invoice_number, linea_factura); el caller debe incrementar al aceptar. - """ - errors: List[Dict[str, Any]] = [] - - desfase = _check_desfase(row, line_num) - if desfase and warnings is not None: - warnings.append(desfase) - - err = _check_factura_vacia(row, line_num) - if err: - errors.append(err) - return errors - - invoice_number = _clip(row.get("NUMERO FACTURA") or row.get("NUM FACTURA") or row.get("FACTURA")) - if not invoice_number: - errors.append({"line": line_num, "col": "NUMERO FACTURA", "msg": "Requerido"}) - return errors - - linea_factura = _clip(row.get("LINEA FACTURA") or row.get("LINEA") or row.get("PARTIDA")) - - for err in [ - _check_factura_existe_def(invoice_number, line_num, invoice_id_by_number, catalog_label), - _check_factura_no_actualizada_def(invoice_number, line_num, invoice_processed_by_number, catalog_label), - _check_linea_factura_vacia(row, line_num), - _check_partida_existe_en_factura(invoice_number, linea_factura, line_num, partida_max_series), - _check_cantidad_series_vs_partida(invoice_number, linea_factura, line_num, partida_max_series, csv_series_count_so_far), - _check_linea_serie_si_no_autonumerar(row, line_num, autonumerar), - ]: - if err: - errors.append(err) - - if errors: - return errors - - _warn_apostrofes(row, line_num, warnings) - - linea_serie = _clip(row.get("LINEA SERIE") or row.get("RENGLON")) - key = _series_key(invoice_number, linea_factura, linea_serie) - - use_partial = ( - actualizar - and not autonumerar - and bool(linea_serie) - and key in (existing_series_keys or set()) - ) - - if use_partial and existing_series_data and key in existing_series_data: - err = valida_parcial_series_impo_def(row, line_num, existing_series_data[key], warnings) - else: - err = valida_toda_series_impo_def(row, line_num, validar_series_exception, warnings) - - if err: - errors.append(err) - return errors - - -def row_to_series_normalized_def(row: Dict[str, Any]) -> Dict[str, Any]: - """ - Normaliza fila para guardar: SACARCOMASENTERS en D, E, F, G, H. - Clarion LLENA_SERIES_IMPO_DEF: asigna QueCSV a SerDef. - Incluye NUM PARTE para validación; el modelo Serie actual no tiene campo parte (documentar si se añade). - """ - def clip(col: str, alt: Optional[List[str]] = None) -> str: - v = row.get(col) - if alt: - for k in alt: - if v is None or (isinstance(v, str) and not v.strip()): - v = row.get(k) - return _clip(v) if v is not None else "" - - def norm(col: str, alt: Optional[List[str]] = None, max_len: int = 50) -> str: - v = row.get(col) - if alt: - for k in alt: - if v is None or (isinstance(v, str) and not v.strip()): - v = row.get(k) - s = normalize_sacarcomasenters(v) if v is not None else "" - return s[:max_len] if s else "" - - return { - "NUMERO FACTURA": clip("NUMERO FACTURA", ["NUM FACTURA", "FACTURA"]), - "LINEA FACTURA": clip("LINEA FACTURA", ["LINEA", "PARTIDA"]), - "LINEA SERIE": clip("LINEA SERIE", ["RENGLON"]), - "SERIE": norm("SERIE", max_len=MAX_LEN["serial_numbers"]), - "MODELO": norm("MODELO", max_len=MAX_LEN["model"]), - "NUM PARTE": norm("NUM PARTE", ["NUMPARTE", "NUMERO PARTE"]), - "SUB MODELO": norm("SUB MODELO", ["SUBMODELO"], MAX_LEN["sub_model"]), - "NUMERO ID": norm("NUMERO ID", ["NUMEROID"], MAX_LEN["number_id"]), - } diff --git a/backend/api/v1/modules/a76/layouts_csv/facturas/validators/series_impo_temp.py b/backend/api/v1/modules/a76/layouts_csv/facturas/validators/series_impo_temp.py deleted file mode 100644 index 257e032b..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/facturas/validators/series_impo_temp.py +++ /dev/null @@ -1,356 +0,0 @@ -""" -Validaciones CSV para Series de Importación Temporal. -Paridad Clarion: VALIDA_TODA_SERIES_IMPO_TEM, VALIDA_PARCIAL_SERIES_IMPO_TEM, LLENA_SERIES_IMPO_TEM. -Estructura: NUMERO FACTURA, LINEA FACTURA, LINEA SERIE, SERIE, MODELO, NUM PARTE, SUB MODELO, NUMERO ID. -""" -from typing import Dict, Any, Optional, Set, Tuple, List - -# Longitudes máximas según modelo Serie (item_line_series) -MAX_LEN = { - "serial_numbers": 50, - "model": 50, - "sub_model": 50, - "number_id": 25, -} - -APOSTROFE = "'" - - -def _clip(val: Any) -> str: - if val is None: - return "" - return str(val).strip() - - -def normalize_sacarcomasenters(val: Any) -> str: - """Clarion SACARCOMASENTERS: quitar comas y saltos de línea.""" - s = _clip(val) - s = s.replace(",", " ").replace("\n", " ").replace("\r", " ") - return " ".join(s.split()) - - -def _check_desfase(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]: - """Si COL_EXTRA tiene valor → advertencia desfase (no bloqueante).""" - val = _clip(row.get("COL_EXTRA")) - if not val: - return None - return { - "line": line_num, - "col": "COL_EXTRA", - "msg": "Advertencia: Podría existir un desfase en esta línea.", - "solution": "Revisar esta línea del archivo CSV y verificar cada campo esté en la posición correcta.", - "warning": True, - } - - -def _check_factura_vacia(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]: - """NUMERO FACTURA (A) vacío → error bloqueante.""" - val = _clip(row.get("NUMERO FACTURA") or row.get("NUM FACTURA") or row.get("FACTURA")) - if not val: - return { - "line": line_num, - "col": "NUMERO FACTURA", - "msg": "Error: (Celda A) La Factura de Importación está vacía y no se pueden hacer las validaciones.", - "solution": "Capturar en la Celda A un número de Factura existente al cual desee agregar o actualizar series.", - } - return None - - -def _check_factura_existe( - invoice_number: str, - line_num: int, - invoice_id_by_number: Dict[str, int], -) -> Optional[Dict[str, Any]]: - """Factura debe existir en BD (imp + TEM).""" - if invoice_number not in invoice_id_by_number: - return { - "line": line_num, - "col": "NUMERO FACTURA", - "msg": f"Error: (Celda A) La Factura de Importación {invoice_number} no existe en SCAII y no se pueden hacer las validaciones.", - "solution": "Capturar en la Celda A un número de Factura existente al cual desee agregar o actualizar partidas.", - "identifier": "FAC_IMPO_TEM", - "fields": invoice_number, - } - return None - - -def _check_factura_no_actualizada( - invoice_number: str, - line_num: int, - invoice_processed_by_number: Dict[str, bool], -) -> Optional[Dict[str, Any]]: - """Si factura ya actualizada (Estatus AC) no se pueden hacer cambios.""" - if invoice_processed_by_number.get(invoice_number, False): - return { - "line": line_num, - "col": "NUMERO FACTURA", - "msg": f"Error: (Celda A) La Factura de Importación: {invoice_number} ya existe y está Actualizada, no se puede hacer cambios a las facturas actualizadas.", - "solution": "Capturar otro número de Factura de Importación Temporal o Desactualizar la factura.", - "identifier": "FAC_IMPO_TEM", - } - return None - - -def _check_linea_factura_vacia(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]: - """LINEA FACTURA (B) vacía → error.""" - val = _clip(row.get("LINEA FACTURA") or row.get("LINEA") or row.get("PARTIDA")) - if not val: - return { - "line": line_num, - "col": "LINEA FACTURA", - "msg": "Error: (Celda B) El campo de la línea de la partida está vacío y no se pueden hacer las validaciones.", - "solution": "Capturar en la Celda B la línea de la partida al cual desee agregar o actualizar información.", - } - return None - - -def _check_linea_serie_si_no_autonumerar( - row: Dict[str, Any], - line_num: int, - autonumerar: bool, -) -> Optional[Dict[str, Any]]: - """Si Autonumerar = NO, LINEA SERIE (C) es obligatoria.""" - if autonumerar: - return None - val = _clip(row.get("LINEA SERIE") or row.get("RENGLON")) - if not val: - return { - "line": line_num, - "col": "LINEA SERIE", - "msg": "Error: (Celda C) El campo de Renglón está vacío y no se pueden hacer las validaciones, ya que se tiene la opción de Autonumerar como NO.", - "solution": "Capturar en la Celda C el renglón de la serie la cual desee agregar o actualizar información.", - } - return None - - -def _warn_apostrofes( - row: Dict[str, Any], - line_num: int, - warnings: Optional[List[Dict[str, Any]]], -) -> None: - """Advertencias si SERIE, MODELO o NUM PARTE contienen apostrofe (no bloqueante).""" - if warnings is None: - return - checks = [ - ("SERIE", "Número de Serie", row.get("SERIE")), - ("MODELO", "Número de Modelo", row.get("MODELO")), - ("NUM PARTE", "Número de Parte", row.get("NUM PARTE") or row.get("NUMPARTE") or row.get("NUMERO PARTE")), - ] - for col, label, val in checks: - v = _clip(val) if val is not None else "" - if v and APOSTROFE in v: - warnings.append({ - "line": line_num, - "col": col, - "msg": f"Advertencia: El {label}: {v} Contiene Apostrofes.", - "solution": "Se Omitirá el Apostrofe para Subir.", - "warning": True, - }) - - -def _check_max_length(col: str, val: str, line_num: int, max_len: int) -> Optional[Dict[str, Any]]: - if not val or len(val) <= max_len: - return None - return { - "line": line_num, - "col": col, - "msg": f"Máximo {max_len} caracteres", - } - - -def valida_toda_series_impo_tem( - row: Dict[str, Any], - line_num: int, - validar_series_exception: bool, - warnings: Optional[List[Dict[str, Any]]], -) -> Optional[Dict[str, Any]]: - """ - VALIDA_TODA_SERIES_IMPO_TEM: cuando no existe la partida/serie o autonumerar=SI. - Si D+E+F están todos vacíos y no aplica excepción ValidarSeries → error obligatorios. - Valida longitudes máximas. - """ - d = _clip(row.get("SERIE")) - e = _clip(row.get("MODELO")) - f = _clip(row.get("NUM PARTE") or row.get("NUMPARTE") or row.get("NUMERO PARTE")) - campos = d + e + f - - if not campos and not validar_series_exception: - obligatorios = [] - if not d: - obligatorios.append("(Col.D) Serie") - if not e: - obligatorios.append("(Col.E) Modelo") - if not f: - obligatorios.append("(Col.F) Num. Parte") - if obligatorios: - return { - "line": line_num, - "col": "SERIE", - "msg": f"Existen campos vacíos que son obligatorios al no tener ningun campo: {', '.join(obligatorios)}.", - "solution": "Revisar la línea del archivo y capturar los campos con la información correcta.", - } - - # Longitudes (solo si hay valor) - def get_val(k: str) -> str: - if k == "NUM PARTE": - return _clip(row.get("NUM PARTE") or row.get("NUMPARTE") or row.get("NUMERO PARTE")) - if k == "SUB MODELO": - return _clip(row.get("SUB MODELO") or row.get("SUBMODELO")) - if k == "NUMERO ID": - return _clip(row.get("NUMERO ID") or row.get("NUMEROID")) - return _clip(row.get(k)) - - for col, key, max_len in [ - ("SERIE", "SERIE", MAX_LEN["serial_numbers"]), - ("MODELO", "MODELO", MAX_LEN["model"]), - ("NUM PARTE", "NUM PARTE", 50), - ("SUB MODELO", "SUB MODELO", MAX_LEN["sub_model"]), - ("NUMERO ID", "NUMERO ID", MAX_LEN["number_id"]), - ]: - val = get_val(key) - if val: - err = _check_max_length(col, val, line_num, max_len) - if err: - return err - - return None - - -def valida_parcial_series_impo_tem( - row: Dict[str, Any], - line_num: int, - existing_series_data: Dict[str, Any], - warnings: Optional[List[Dict[str, Any]]], -) -> Optional[Dict[str, Any]]: - """ - VALIDA_PARCIAL_SERIES_IMPO_TEM: actualizar serie existente; campos vacíos se rellenan con existente. - Solo validar longitudes en campos no vacíos. - """ - def get_val(k: str) -> str: - if k == "NUM PARTE": - return _clip(row.get("NUM PARTE") or row.get("NUMPARTE") or row.get("NUMERO PARTE")) - if k == "SUB MODELO": - return _clip(row.get("SUB MODELO") or row.get("SUBMODELO")) - if k == "NUMERO ID": - return _clip(row.get("NUMERO ID") or row.get("NUMEROID")) - return _clip(row.get(k)) - - for col, key, max_len in [ - ("SERIE", "SERIE", MAX_LEN["serial_numbers"]), - ("MODELO", "MODELO", MAX_LEN["model"]), - ("NUM PARTE", "NUM PARTE", 50), - ("SUB MODELO", "SUB MODELO", MAX_LEN["sub_model"]), - ("NUMERO ID", "NUMERO ID", MAX_LEN["number_id"]), - ]: - val = get_val(key) - if val: - err = _check_max_length(col, val, line_num, max_len) - if err: - return err - return None - - -def _series_key(invoice_number: str, linea_factura: str, linea_serie: str) -> Tuple[str, str, str]: - return (invoice_number.strip(), _clip(linea_factura), _clip(linea_serie)) - - -def validate_row_series_impo_temp( - row: Dict[str, Any], - line_num: int, - actualizar: bool, - autonumerar: bool, - validar_series_exception: bool, - invoice_id_by_number: Dict[str, int], - invoice_processed_by_number: Dict[str, bool], - existing_series_keys: Set[Tuple[str, str, str]], - existing_series_data: Optional[Dict[Tuple[str, str, str], Dict[str, Any]]], - warnings: Optional[List[Dict[str, Any]]] = None, -) -> List[Dict[str, Any]]: - """ - Punto de entrada: valida una fila de CSV de Series de Importación Temporal. - Clarion: decisión VALIDA_TODA vs VALIDA_PARCIAL según autonumerar, actualizar y si la serie existe. - """ - errors: List[Dict[str, Any]] = [] - - # Desfase (solo advertencia, no bloqueante) - desfase = _check_desfase(row, line_num) - if desfase and warnings is not None: - warnings.append(desfase) - - err = _check_factura_vacia(row, line_num) - if err: - errors.append(err) - return errors - - invoice_number = _clip(row.get("NUMERO FACTURA") or row.get("NUM FACTURA") or row.get("FACTURA")) - if not invoice_number: - errors.append({"line": line_num, "col": "NUMERO FACTURA", "msg": "Requerido"}) - return errors - - for err in [ - _check_factura_existe(invoice_number, line_num, invoice_id_by_number), - _check_factura_no_actualizada(invoice_number, line_num, invoice_processed_by_number), - _check_linea_factura_vacia(row, line_num), - _check_linea_serie_si_no_autonumerar(row, line_num, autonumerar), - ]: - if err: - errors.append(err) - - if errors: - return errors - - _warn_apostrofes(row, line_num, warnings) - - linea_factura = _clip(row.get("LINEA FACTURA") or row.get("LINEA") or row.get("PARTIDA")) - linea_serie = _clip(row.get("LINEA SERIE") or row.get("RENGLON")) - key = _series_key(invoice_number, linea_factura, linea_serie) - - use_partial = ( - actualizar - and not autonumerar - and bool(linea_serie) - and key in (existing_series_keys or set()) - ) - - if use_partial and existing_series_data and key in existing_series_data: - err = valida_parcial_series_impo_tem(row, line_num, existing_series_data[key], warnings) - else: - err = valida_toda_series_impo_tem(row, line_num, validar_series_exception, warnings) - - if err: - errors.append(err) - return errors - - -def row_to_series_normalized(row: Dict[str, Any]) -> Dict[str, Any]: - """ - Normaliza fila para guardar: SACARCOMASENTERS en D, E, F, G, H. - Clarion LLENA_SERIES_IMPO_TEM: asigna QueCSV a SerImp. - """ - def clip(col: str, alt: Optional[List[str]] = None) -> str: - v = row.get(col) - if alt: - for k in alt: - if v is None or (isinstance(v, str) and not v.strip()): - v = row.get(k) - return _clip(v) if v is not None else "" - - def norm(col: str, alt: Optional[List[str]] = None, max_len: int = 50) -> str: - v = row.get(col) - if alt: - for k in alt: - if v is None or (isinstance(v, str) and not v.strip()): - v = row.get(k) - s = normalize_sacarcomasenters(v) if v is not None else "" - return s[:max_len] if s else "" - - return { - "NUMERO FACTURA": clip("NUMERO FACTURA", ["NUM FACTURA", "FACTURA"]), - "LINEA FACTURA": clip("LINEA FACTURA", ["LINEA", "PARTIDA"]), - "LINEA SERIE": clip("LINEA SERIE", ["RENGLON"]), - "SERIE": norm("SERIE", max_len=MAX_LEN["serial_numbers"]), - "MODELO": norm("MODELO", max_len=MAX_LEN["model"]), - "NUM PARTE": norm("NUM PARTE", ["NUMPARTE", "NUMERO PARTE"]), - "SUB MODELO": norm("SUB MODELO", ["SUBMODELO"], MAX_LEN["sub_model"]), - "NUMERO ID": norm("NUMERO ID", ["NUMEROID"], MAX_LEN["number_id"]), - } diff --git a/backend/api/v1/modules/a76/layouts_csv/facturas/validators/transport_catalog.py b/backend/api/v1/modules/a76/layouts_csv/facturas/validators/transport_catalog.py deleted file mode 100644 index 9f6151e3..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/facturas/validators/transport_catalog.py +++ /dev/null @@ -1,239 +0,0 @@ -"""Mapeo y validación de transporte para CSV (paridad con facturas manuales / validate_common).""" - -from __future__ import annotations - -from typing import Any, Dict, List, Optional, Tuple - -from sqlalchemy import func -from sqlalchemy.orm import Session - -from api.v1.modules.a76.invoices.models import TransportType - - -def normalize_transport_cell(raw: Optional[str]) -> str: - if raw is None: - return "" - return str(raw).replace("\u00a0", " ").strip() - - -def resolve_transport_csv_fields(row: dict) -> Tuple[str, str, str]: - """ - Prioridad de columnas: - 1) CLAVE TRANSPORTE / NUMERO CAJA - 2) NUMERO TRANSPORTE como fallback legacy. - """ - legacy = normalize_transport_cell(row.get("NUMERO TRANSPORTE")) - clave_transporte = normalize_transport_cell(row.get("CLAVE TRANSPORTE")) or legacy - numero_caja = normalize_transport_cell(row.get("NUMERO CAJA")) or legacy - numero_transporte = legacy - return clave_transporte, numero_caja, numero_transporte - - -def resolve_logistics_transport_fields_for_commit( - session: Session, - tenant_id: int, - company_id: int, - clave_transporte_raw: str, - numero_caja_raw: str, -) -> Tuple[Optional[str], Optional[str], Optional[int], Optional[int]]: - """ - Resuelve claves y IDs internos para logística: - - transport_id / transport_int_id a partir de Vehicle.vehicle_key - - trailer_num / trailer_int_id a partir de Trailer.trailer_number - """ - from api.v1.modules.a76.transportation.vehicles.models import Vehicle - from api.v1.modules.a76.transportation.trailers.models import Trailer - - clave_transporte = normalize_transport_cell(clave_transporte_raw) - numero_caja = normalize_transport_cell(numero_caja_raw) - - transport_id: Optional[str] = clave_transporte or None - trailer_num: Optional[str] = numero_caja or None - transport_int_id: Optional[int] = None - trailer_int_id: Optional[int] = None - - if transport_id: - vehicle = ( - session.query(Vehicle) - .filter( - func.upper(Vehicle.vehicle_key) == transport_id.upper(), - Vehicle.tenant_id == tenant_id, - Vehicle.company_id == company_id, - ) - .first() - ) - if vehicle is not None: - transport_int_id = vehicle.vehicle_id - - if trailer_num: - trailer = ( - session.query(Trailer) - .filter( - func.upper(Trailer.trailer_number) == trailer_num.upper(), - Trailer.tenant_id == tenant_id, - Trailer.company_id == company_id, - ) - .first() - ) - if trailer is not None: - trailer_int_id = trailer.trailer_id - - return transport_id, trailer_num, transport_int_id, trailer_int_id - - -def validate_csv_invoice_logistics_transport( - row_norm: dict, - session: Session, - tenant_id: int, - company_id: int, - transport_type: TransportType, -) -> Optional[str]: - """ - Paridad con validate_common (factura manual): - - Transportista y conductor: solo catálogo si vienen informados. - - Vehículo: solo catálogo si viene CLAVE TRANSPORTE explícita y no resuelve. - - Remolque: obligatorio (y catálogo) si tipo ≠ none; con none, solo catálogo si NUMERO CAJA explícito. - """ - from api.v1.modules.a76.transportation.transporters.models import Transporter - from api.v1.modules.a76.transportation.drivers.models import Driver - - carrier_code = normalize_transport_cell(row_norm.get("CLAVE TRANSPORTISTA")) - carrier_ok = False - if carrier_code: - tr_obj = ( - session.query(Transporter) - .filter( - Transporter.transporter_key == carrier_code, - Transporter.tenant_id == tenant_id, - Transporter.company_id == company_id, - ) - .first() - ) - if not tr_obj: - return "CLAVE TRANSPORTISTA no existe en el catálogo" - carrier_ok = True - - driver_raw = normalize_transport_cell(row_norm.get("NOMBRE CONDUCTOR")) - if carrier_ok and driver_raw: - dr = ( - session.query(Driver) - .filter( - Driver.transporter_key == carrier_code, - Driver.driver_name == driver_raw, - Driver.tenant_id == tenant_id, - Driver.company_id == company_id, - ) - .first() - ) - if not dr: - return ( - "NOMBRE CONDUCTOR: no existe en el catálogo de conductores " - "para el transportista indicado" - ) - - transport_key_csv, trailer_num_csv, _ = resolve_transport_csv_fields(row_norm) - ( - _transport_id_value, - trailer_num_resolved, - transport_int_id, - trailer_int_id, - ) = resolve_logistics_transport_fields_for_commit( - session=session, - tenant_id=tenant_id, - company_id=company_id, - clave_transporte_raw=transport_key_csv, - numero_caja_raw=trailer_num_csv, - ) - - explicit_clave = normalize_transport_cell(row_norm.get("CLAVE TRANSPORTE")) - if explicit_clave and transport_int_id is None: - return "CLAVE TRANSPORTE: vehículo no existe en el catálogo" - - explicit_caja = normalize_transport_cell(row_norm.get("NUMERO CAJA")) - if transport_type == TransportType.NONE: - if explicit_caja and trailer_int_id is None: - return "NUMERO CAJA: remolque no existe en el catálogo" - return None - - if trailer_int_id is None: - if not (trailer_num_resolved and str(trailer_num_resolved).strip()): - return ( - "NUMERO CAJA (remolque) obligatorio cuando TIPO TRANSPORTE es distinto de NINGUNO" - ) - return "NUMERO CAJA: remolque no existe en el catálogo" - - return None - - -def _csv_row_has_resolved_weight_unit(row_norm: dict) -> bool: - """ - Paridad con insert_valid_rows: has_logistics_data incluye parse_weight_unit(TIPO PESO). - Import diferido para evitar ciclo al cargar tasks. - """ - from api.v1.modules.a76.layouts_csv.facturas.tasks import parse_weight_unit - - return parse_weight_unit(row_norm.get("TIPO PESO")) is not None - - -def logistics_scan_row_errors( - row_norm: dict, - session: Session, - tenant_id: int, - company_id: int, - line_num: int, -) -> List[Dict[str, Any]]: - """ - Errores de scan (misma lógica que commit: validate_csv_invoice_logistics_transport). - Solo corre si la fila trae algún dato de logística de transporte. - """ - from .encabezados_impo_temp import row_to_transport_type_clarion - - transport_key_csv, trailer_num_csv, _ = resolve_transport_csv_fields(row_norm) - - def _cell(key: str) -> str: - return normalize_transport_cell(row_norm.get(key)) - - has_data = bool( - _cell("TIPO TRANSPORTE") - or _cell("NUMERO TRANSPORTE") - or _cell("CLAVE TRANSPORTE") - or transport_key_csv - or trailer_num_csv - or _cell("CLAVE TRANSPORTISTA") - or _cell("NOMBRE CONDUCTOR") - or _csv_row_has_resolved_weight_unit(row_norm) - ) - if not has_data: - return [] - - raw_transport = row_norm.get("TIPO TRANSPORTE") - transport_str = ( - row_to_transport_type_clarion(raw_transport) - or str(raw_transport or "").strip().lower() - or "none" - ) - try: - tt = TransportType(transport_str) - except ValueError: - tt = TransportType.NONE - - msg = validate_csv_invoice_logistics_transport( - row_norm=row_norm, - session=session, - tenant_id=tenant_id, - company_id=company_id, - transport_type=tt, - ) - if not msg: - return [] - return [ - { - "line": line_num, - "col": "LOGISTICA TRANSPORTE", - "msg": msg, - "solution": ( - "Verifica CLAVE TRANSPORTISTA, NOMBRE CONDUCTOR, CLAVE TRANSPORTE, " - "NUMERO CAJA y TIPO TRANSPORTE (remolque obligatorio si no es NINGUNO)." - ), - } - ] diff --git a/backend/api/v1/modules/a76/layouts_csv/parts/__init__.py b/backend/api/v1/modules/a76/layouts_csv/parts/__init__.py deleted file mode 100644 index 025db976..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/parts/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# CSV import for Parts (Números de parte) diff --git a/backend/api/v1/modules/a76/layouts_csv/parts/common/__init__.py b/backend/api/v1/modules/a76/layouts_csv/parts/common/__init__.py deleted file mode 100644 index 1efc271d..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/parts/common/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# common validators for parts CSV import diff --git a/backend/api/v1/modules/a76/layouts_csv/parts/common/common_validators.py b/backend/api/v1/modules/a76/layouts_csv/parts/common/common_validators.py deleted file mode 100644 index 9e77983c..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/parts/common/common_validators.py +++ /dev/null @@ -1,49 +0,0 @@ -""" -Helpers reutilizables para validación de filas CSV (partes). -Cada función devuelve Optional[Dict] con keys line, col, msg. -Referencia: a76/invoices (common_validators, validators/common). -""" -from decimal import Decimal, InvalidOperation -from typing import Dict, Any, Optional - - -def check_required( - row: Dict[str, Any], col: str, line_num: int -) -> Optional[Dict[str, Any]]: - """Devuelve error si el campo está vacío.""" - val = (row.get(col) or "").strip() - if not val: - return {"line": line_num, "col": col, "msg": "Requerido"} - return None - - -def check_max_length( - row: Dict[str, Any], - col: str, - max_len: int, - line_num: int, - required: bool = False, -) -> Optional[Dict[str, Any]]: - """Devuelve error si el campo excede max_len. Si required=True, también exige que haya valor.""" - val = (row.get(col) or "").strip() - if not val: - if required: - return {"line": line_num, "col": col, "msg": "Requerido"} - return None - if len(val) > max_len: - return {"line": line_num, "col": col, "msg": f"Máximo {max_len} caracteres"} - return None - - -def check_decimal( - row: Dict[str, Any], col: str, line_num: int -) -> Optional[Dict[str, Any]]: - """Devuelve error si el valor no es un número decimal válido (solo cuando hay valor).""" - val = row.get(col) - if val is None or val == "": - return None - try: - Decimal(str(val)) - except (InvalidOperation, ValueError, TypeError): - return {"line": line_num, "col": col, "msg": "Debe ser número"} - return None diff --git a/backend/api/v1/modules/a76/layouts_csv/parts/common/fk_loader.py b/backend/api/v1/modules/a76/layouts_csv/parts/common/fk_loader.py deleted file mode 100644 index 04f33435..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/parts/common/fk_loader.py +++ /dev/null @@ -1,145 +0,0 @@ -""" -Carga de conjuntos FK para validación/mapeo de import CSV de partes. -Clarion: Clases, UOM, monedas, fracción Mex (+ histórico), países, sectores autorizados, excepción RFC. -""" -from typing import Set, Tuple, Optional -import logging - -from core.database import CoreSessionLocal - -logger = logging.getLogger(__name__) - -# RFCs con excepción: E (UOM) obligatorio solo cuando D (Clase) está vacía; Preferencia solo cuando D no vacía -RFC_EXCEPTION_SET = {"CLA940831AZ5", "CTE980130518"} - - -def load_parts_fk_sets( - tenant_id: int, - company_id: int, -) -> Tuple[ - Set[str], - Set[str], - Set[str], - Set[str], - Set[str], - Set[str], - bool, - bool, -]: - """ - Carga conjuntos para validación CSV de partes (paridad Clarion). - Devuelve: - - valid_class_codes - - valid_uom_codes - - valid_currency_codes (claves moneda para G=MC y genéricas) - - valid_fraction_mex_8 (fracción Mex 8 chars: TariffFraction + HistoricalTariffFraction) - - valid_country_m3 (códigos país m3_key) - - authorized_sector_keys (sectores con authorized=True) - - company_has_prosec (Company.prosec) - - is_rfc_exception (Company.rfc en RFC_EXCEPTION_SET) - """ - valid_class_codes: Set[str] = set() - valid_uom_codes: Set[str] = set() - valid_currency_codes: Set[str] = set() - valid_fraction_mex_8: Set[str] = set() - valid_country_m3: Set[str] = set() - authorized_sector_keys: Set[str] = set() - company_has_prosec = False - is_rfc_exception = False - - try: - with CoreSessionLocal() as session: - from api.v1.modules.a76.classes.models import Class - from api.v1.modules.a76.general_catalogs.units_of_measure.models import UnitOfMeasure - from api.v1.modules.public.reference_data.currency_types.models import CurrencyType - from api.v1.modules.a76.general_catalogs.company.models import Company - from api.v1.modules.public.reference_data.countries.models import Country - from api.v1.modules.a76.general_catalogs.sectors.models import Sector - from api.v1.modules.a76.general_catalogs.fractions.tariff_fractions.models import TariffFraction - from api.v1.modules.a76.general_catalogs.fractions.historical_tariff_fractions.models import ( - HistoricalTariffFraction, - ) - - for c in ( - session.query(Class.class_code) - .filter( - Class.tenant_id == tenant_id, - Class.company_id == company_id, - ) - .all() - ): - if c[0]: - valid_class_codes.add((c[0] or "").strip().upper()) - - for u in ( - session.query(UnitOfMeasure.code) - .filter( - UnitOfMeasure.tenant_id == tenant_id, - UnitOfMeasure.company_id == company_id, - ) - .all() - ): - if u[0]: - valid_uom_codes.add((u[0] or "").strip().upper()) - - for cur in session.query(CurrencyType.code).all(): - if cur[0]: - valid_currency_codes.add((cur[0] or "").strip().upper()) - - company = ( - session.query(Company) - .filter(Company.id == company_id) - .first() - ) - if company: - company_has_prosec = bool(company.prosec) - rfc = (company.rfc or "").strip().upper() - is_rfc_exception = rfc in RFC_EXCEPTION_SET - - for row in session.query(Country.m3_key).all(): - if row[0]: - valid_country_m3.add((row[0] or "").strip().upper()) - - for row in ( - session.query(Sector.key) - .filter( - Sector.authorized == True, - Sector.tenant_id == tenant_id, - Sector.company_id == company_id, - ) - .all() - ): - if row[0]: - authorized_sector_keys.add((row[0] or "").strip().upper()) - - for row in session.query(TariffFraction.code).all(): - if row[0]: - code = (row[0] or "").strip() - valid_fraction_mex_8.add(code[:8]) - - for row in ( - session.query(HistoricalTariffFraction.historical_fraction) - .filter( - HistoricalTariffFraction.tenant_id == tenant_id, - HistoricalTariffFraction.company_id == company_id, - HistoricalTariffFraction.historical_fraction.isnot(None), - ) - .distinct() - .all() - ): - if row[0] and (row[0] or "").strip(): - valid_fraction_mex_8.add((row[0] or "").strip()[:8]) - - except Exception as e: - logger.warning("Parts import: could not load FK sets: %s", e) - - return ( - valid_class_codes, - valid_uom_codes, - valid_currency_codes, - valid_fraction_mex_8, - valid_country_m3, - authorized_sector_keys, - company_has_prosec, - is_rfc_exception, - ) diff --git a/backend/api/v1/modules/a76/layouts_csv/parts/common/mappers.py b/backend/api/v1/modules/a76/layouts_csv/parts/common/mappers.py deleted file mode 100644 index 75b32962..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/parts/common/mappers.py +++ /dev/null @@ -1,204 +0,0 @@ -""" -Mapeo fila CSV → datos para Part. Helpers de normalización de valores. -Clarion: ME/USD, MN/MXP, MC/ClaveMoneda; apóstrofes omitidos en NUMPARTE; RFC excepción rellena desde Class. -""" -from decimal import Decimal, InvalidOperation -from typing import Dict, Any, Optional, Set - - -def clean_dict(data_dict: dict) -> dict: - """Convierte strings vacíos a None; mantiene 0 en campos no-_id. Igual que a76/invoices.""" - cleaned = {} - for key, value in data_dict.items(): - if isinstance(value, str) and not value.strip(): - cleaned[key] = None - elif value == 0 and (key.endswith("_id") or key == "remesa"): - cleaned[key] = None - else: - cleaned[key] = value - return cleaned - - -def _str_or_none(val: Any, max_len: Optional[int] = None) -> Optional[str]: - if val is None: - return None - s = str(val).strip() - if not s: - return None - if max_len and len(s) > max_len: - return s[:max_len] - return s - - -def _decimal_or_none(val: Any) -> Optional[Decimal]: - if val is None or val == "": - return None - try: - return Decimal(str(val)) - except (InvalidOperation, ValueError, TypeError): - return None - - -def _bool_from_row(val: Any) -> bool: - if val is None or val == "": - return True - s = str(val).strip().upper() - if s in ("0", "F", "FALSE", "NO", "N"): - return False - return True - - -def _normalize_num_parte(val: Optional[str], max_len: int = 70) -> Optional[str]: - """NUMPARTE: mayúsculas, sin apóstrofes (Clarion: Se Omitirá el Apostrofe).""" - if val is None: - return None - s = str(val).strip().replace("'", "") - if not s: - return None - s = s.upper() - if max_len and len(s) > max_len: - return s[:max_len] - return s - - -def _currency_from_row( - row_norm: Dict[str, Any], - valid_currency_codes: Set[str], -) -> tuple: - """ - Deriva currency_type y currency_key desde TIPOMONEDA (Col G) y CLAVEMONEDA (Col H). - Clarion: G vacío -> ME/USD; G=ME -> USD; G=MN -> MXP; G=MC -> H obligatoria. - """ - g = (row_norm.get("TIPOMONEDA") or row_norm.get("MONEDA") or "").strip().upper() - h = (row_norm.get("CLAVEMONEDA") or "").strip().upper() - if not g: - return "ME", "USD" - if g == "ME": - return "ME", "USD" - if g == "MN": - return "MN", "MXP" - if g == "MC" and h and h in valid_currency_codes: - return "MC", h - if h and h in valid_currency_codes: - return g[:2], h - return None, None - - -def row_to_part_data( - row_norm: Dict[str, Any], - valid_class_codes: Set[str], - valid_uom_codes: Set[str], - valid_currency_codes: Set[str], -) -> Dict[str, Any]: - """ - Mapea una fila normalizada del CSV a un diccionario de datos para Part. - Clarion: NUMPARTE sin apóstrofes; Tipo Moneda ME/MN/MC con Clave; PAIS, PREFERENCIA, SECTOR, RUTA IMAGEN. - """ - part_number = _normalize_num_parte(row_norm.get("NUMPARTE"), 70) - commercial = _str_or_none(row_norm.get("NUMPARTECOM"), 70) - desc_es = _str_or_none(row_norm.get("DESCRIPCIONE"), 500) - desc_en = _str_or_none(row_norm.get("DESCRIPCIONI"), 500) - part_class = _str_or_none(row_norm.get("CLASE"), 8) - if part_class and part_class.upper() not in valid_class_codes: - part_class = None - unit_of_measure = _str_or_none(row_norm.get("UNIMED"), 5) - if unit_of_measure and unit_of_measure.upper() not in valid_uom_codes: - unit_of_measure = None - - currency_type, currency_key = _currency_from_row(row_norm, valid_currency_codes) - if currency_key and currency_key not in valid_currency_codes: - currency_key = None - currency_type = None - - unit_cost = _decimal_or_none(row_norm.get("COSTOUNIT")) - unit_weight = _decimal_or_none(row_norm.get("PESOUNIT")) - weight_type = _str_or_none(row_norm.get("TIPOPESO"), 6) - if weight_type and weight_type.upper() not in ("KILOS", "LIBRAS"): - weight_type = "KILOS" - fraction = _str_or_none(row_norm.get("FRACCION"), 10) - us_fraction = _str_or_none(row_norm.get("FRACCIONAME"), 16) - part_photo = _str_or_none(row_norm.get("RUTAIMAGEN"), 255) - - fda_key = _str_or_none(row_norm.get("FDAKEY"), 20) - fcc_key = _str_or_none(row_norm.get("FCCKEY"), 30) - license_code = _str_or_none(row_norm.get("LICENCIA"), 3) - eccn = _str_or_none(row_norm.get("ECCN"), 20) - export_code = _str_or_none(row_norm.get("EXPORTCODE"), 2) - exclusion_symbol = _str_or_none(row_norm.get("EXCLUSION"), 19) - is_active = _bool_from_row(row_norm.get("ACTIVO")) - - return { - "part_number": part_number, - "commercial_part_number": commercial, - "description_spanish": desc_es, - "description_english": desc_en, - "part_class": part_class, - "unit_of_measure": unit_of_measure, - "unit_cost": unit_cost, - "currency_type": currency_type, - "currency_key": currency_key, - "unit_weight": unit_weight, - "weight_type": weight_type, - "fraction": fraction, - "us_fraction": us_fraction, - "part_photo": part_photo, - "fda_key": fda_key, - "fcc_key": fcc_key, - "license_code": license_code, - "eccn": eccn, - "export_code": export_code, - "exclusion_symbol": exclusion_symbol, - "is_active": is_active, - } - - -def row_to_part_data_merge_existing( - row_norm: Dict[str, Any], - existing_data: Dict[str, Any], - valid_class_codes: Set[str], - valid_uom_codes: Set[str], - valid_currency_codes: Set[str], -) -> Dict[str, Any]: - """ - Para modo Actualizar (parcial): valores del CSV si no vacíos, sino los de la parte existente (Clarion VALIDA_PARCIAL_PARTES). - """ - data = row_to_part_data( - row_norm, valid_class_codes, valid_uom_codes, valid_currency_codes - ) - if not data.get("part_number"): - return data - merge_keys = ( - "description_spanish", - "description_english", - "part_class", - "unit_of_measure", - "unit_cost", - "currency_type", - "currency_key", - "unit_weight", - "weight_type", - "fraction", - "us_fraction", - "part_photo", - ) - for key in merge_keys: - val = data.get(key) - if val is None or (isinstance(val, str) and not val.strip()): - data[key] = existing_data.get(key) - return data - - -def apply_rfc_exception_from_class( - data: Dict[str, Any], - class_obj: Any, -) -> Dict[str, Any]: - """ - Clarion LLENA_PARTES: si empresa CLA/CTE y part_class informado, sobrescribir desde la clase: - UniMed, DescripcionE, DescripcionI, Fraccion, FraccionAme, TipoFraccion='GENERAL'. - """ - data["unit_of_measure"] = getattr(class_obj, "unit_of_measure", None) or data.get("unit_of_measure") - data["description_spanish"] = getattr(class_obj, "description_es", None) or data.get("description_spanish") - data["description_english"] = getattr(class_obj, "description_en", None) or data.get("description_english") - data["fraction"] = getattr(class_obj, "fraction", None) or data.get("fraction") - data["us_fraction"] = getattr(class_obj, "us_fraction", None) or data.get("us_fraction") - return data diff --git a/backend/api/v1/modules/a76/layouts_csv/parts/csv_max_chars_for_headers.py b/backend/api/v1/modules/a76/layouts_csv/parts/csv_max_chars_for_headers.py deleted file mode 100644 index e60fb6a7..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/parts/csv_max_chars_for_headers.py +++ /dev/null @@ -1,29 +0,0 @@ -"""Máximos alineados a parts/validators/common.py (canonical interno).""" -from typing import Dict - -PART_NUMBERS_CSV_MAX_CHARS: Dict[str, int] = { - "NUMPARTE": 70, - "NUMPARTECOM": 70, - "DESCRIPCIONE": 256, - "DESCRIPCIONI": 256, - "CLASE": 20, - "UNIMED": 5, - "COSTOUNIT": 24, - "TIPOMONEDA": 3, - "CLAVEMONEDA": 3, - "PESOUNIT": 24, - "TIPOPESO": 6, - "FRACCION": 10, - "FRACCIONAME": 10, - "PAIS": 3, - "PREFERENCIA": 7, - "SECTOR": 5, - "RUTAIMAGEN": 255, - "FDAKEY": 30, - "FCCKEY": 30, - "LICENCIA": 30, - "ECCN": 20, - "EXPORTCODE": 20, - "EXCLUSION": 20, - "ACTIVO": 5, -} diff --git a/backend/api/v1/modules/a76/layouts_csv/parts/routes.py b/backend/api/v1/modules/a76/layouts_csv/parts/routes.py deleted file mode 100644 index 5add9e21..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/parts/routes.py +++ /dev/null @@ -1,258 +0,0 @@ -""" -Rutas de importación CSV para Números de Parte. -Flujo: upload → scan → status (polling) → commit. -""" -import json -import logging -import os -from uuid import uuid4 - -from fastapi import APIRouter, File, HTTPException, Query, UploadFile, Depends -from sqlalchemy.orm import Session -from typing import Dict, Any, Optional - -from core.celery_app import celery_app -from core.database import get_core_db -from core.security import get_current_user, validate_access_to_resource -from api.v1.modules.core.tasks_tracking import track_and_dispatch - -from .schemas import ImportJobResponse -from .tasks import ( - scan_file, - insert_valid_rows, - JOB_TYPE, - PART_IMPORT_META_PREFIX, - PART_IMPORT_REDIS_TTL, -) -from ..common import storage as common_storage -from ..common.error_csv import download_scan_errors_csv_stream -from ..common.track_commit_dispatch import dispatch_tracked_layouts_csv_commit -from ..common.responses import normalize_commit_status_payload - -router = APIRouter() -logger = logging.getLogger(__name__) - - -def _get_redis(): - import redis - url = os.getenv("VALKEY_URL", os.getenv("REDIS_URL", "redis://valkey:6379/0")) - return redis.Redis.from_url(url, decode_responses=False) - - -def _assert_parts_csv_job_access( - db: Session, - job_id: str, - current_user: Dict[str, Any], -) -> None: - from api.v1.modules.core.tasks_tracking.models import TaskRun - - company_id: Optional[int] = None - row = db.query(TaskRun).filter(TaskRun.task_id == job_id).first() - if row is not None and row.company_id is not None: - company_id = int(row.company_id) - - r = _get_redis() - if company_id is None: - raw = r.get(f"{PART_IMPORT_META_PREFIX}{job_id}") - if raw: - meta = json.loads(raw.decode("utf-8")) - cid = meta.get("company_id") - if cid is not None: - company_id = int(cid) - - if company_id is None and row is not None and row.meta_payload: - layout_jid = row.meta_payload.get("layout_import_job_id") - if layout_jid: - raw2 = r.get(f"{PART_IMPORT_META_PREFIX}{layout_jid}") - if raw2: - meta2 = json.loads(raw2.decode("utf-8")) - cid2 = meta2.get("company_id") - if cid2 is not None: - company_id = int(cid2) - - if company_id is None: - raise HTTPException(status_code=403, detail="Sin acceso a este job") - try: - validate_access_to_resource( - db, company_id, current_user, ["csv_upload.process"] - ) - except HTTPException: - raise - except Exception as e: - logger.error("Parts import: job access validation failed: %s", e) - raise HTTPException(status_code=403, detail="Sin acceso a este job") from None - - -@router.post("/upload", response_model=ImportJobResponse) -async def upload_import_file( - file: UploadFile = File(...), - company_id: int = Query(..., description="Company ID"), - actualizar: bool = Query(False, description="Modo actualizar (ACT): validación parcial si la parte existe"), - reemplazar_sin_preguntar: bool = Query(True, description="Si False, solo agregar nuevas (no sobrescribir existentes)"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - try: - tenant_id = validate_access_to_resource(db, company_id, current_user, ["csv_upload.process"]) - except Exception as e: - logger.error(f"Parts import: access validation failed: {e}") - raise HTTPException(status_code=403, detail="Invalid company access") - - if not file.filename or not file.filename.lower().endswith(".csv"): - raise HTTPException(status_code=400, detail="Solo se permiten archivos .csv") - - job_id = str(uuid4()) - contents = await file.read() - - meta_data = { - "tenant_id": tenant_id, - "company_id": company_id, - "user_id": current_user.get("id"), - "template_id": "part_numbers", - "actualizar": actualizar, - "reemplazar_sin_preguntar": reemplazar_sin_preguntar, - } - - try: - common_storage.store_import_file( - JOB_TYPE, - job_id, - contents, - meta_data, - tenant_id=int(tenant_id), - company_id=company_id, - ttl=PART_IMPORT_REDIS_TTL, - log_label="Parts import", - ) - except common_storage.ImportStoreError as e: - logger.error(f"Parts import: store error: {e}") - raise HTTPException(status_code=500, detail="No se pudo encolar el archivo.") - except Exception as e: - logger.error(f"Parts import: Redis store error: {e}") - raise HTTPException(status_code=500, detail="No se pudo encolar el archivo.") - - track_and_dispatch( - db=db, - task=scan_file, - tenant_id=int(tenant_id), - company_id=company_id, - requested_by_user=current_user.get("preferred_username") or current_user.get("email") or current_user.get("sub"), - task_name="parts_scan_file", - task_group="layouts_csv", - task_origin="a76/layouts_csv/parts/upload", - args=[job_id], - task_id=job_id, - ) - - return ImportJobResponse( - job_id=job_id, - status="queued", - message="Archivo subido. Escaneo iniciado.", - ) - - -@router.get("/{job_id}/status") -async def get_import_status( - job_id: str, - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - _assert_parts_csv_job_access(db, job_id, current_user) - task_result = celery_app.AsyncResult(job_id) - - if task_result.state == "PENDING": - return {"status": "processing", "progress": 0} - if task_result.state == "PROGRESS": - info = (task_result.info or {}) - return { - "status": "processing", - "progress": info.get("current", 0), - "total": info.get("total", 0), - } - if task_result.state == "SUCCESS": - result = task_result.result - if isinstance(result, dict) and "status" in result: - return normalize_commit_status_payload(result) - return {"status": "finished", "result": result} - - result = getattr(task_result, "result", None) - if isinstance(result, dict) and result.get("status") in ("finished", "warning"): - return normalize_commit_status_payload(result) - - # Si Celery devolvió el resultado como string (p. ej. JSON), parsear y devolver como scan si aplica - if isinstance(result, str): - try: - parsed = json.loads(result) - if isinstance(parsed, dict) and ( - parsed.get("status") == "waiting_confirmation" - or (parsed.get("job_id") and "total_rows" in parsed) - ): - return parsed - if isinstance(parsed, dict) and parsed.get("status") in ("finished", "warning"): - return normalize_commit_status_payload(parsed) - except (json.JSONDecodeError, TypeError): - pass - - # Si el resultado tiene forma de escaneo (waiting_confirmation), devolverlo para que el front muestre el modal - if isinstance(result, dict) and ( - result.get("status") == "waiting_confirmation" - or (result.get("job_id") and "total_rows" in result) - ): - return result - - logger.warning("Parts import task %s failed: state=%s", job_id, task_result.state) - err_msg = None - tb = getattr(task_result, "traceback", None) - if tb and isinstance(tb, str): - lines = [l.strip() for l in tb.strip().split("\n") if l.strip()] - if lines: - err_msg = lines[-1] - if not err_msg: - try: - exc = task_result.get(propagate=False) - if exc is not None: - err_msg = str(exc) - except Exception: - pass - if not err_msg and result is not None: - if not isinstance(result, dict): - err_msg = str(result) - elif result.get("error") or result.get("message"): - err_msg = result.get("error") or result.get("message") - return {"status": "failed", "error": err_msg or "Task failed"} - - -@router.post("/{job_id}/commit") -async def commit_import_job( - job_id: str, - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - r = _get_redis() - commit_id = dispatch_tracked_layouts_csv_commit( - db=db, - current_user=current_user, - redis_client=r, - layout_job_id=job_id, - meta_redis_key=f"{PART_IMPORT_META_PREFIX}{job_id}", - celery_task=insert_valid_rows, - task_name="parts_insert_valid_rows", - task_origin="a76/layouts_csv/parts/commit", - args=[job_id], - required_permissions=["csv_upload.process"], - ) - return { - "status": "committing", - "message": "Inserción iniciada.", - "commit_job_id": commit_id, - } - - -@router.get("/{job_id}/errors/scan-csv") -async def download_scan_errors_csv( - job_id: str, - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - _assert_parts_csv_job_access(db, job_id, current_user) - return download_scan_errors_csv_stream("part", job_id) diff --git a/backend/api/v1/modules/a76/layouts_csv/parts/schemas.py b/backend/api/v1/modules/a76/layouts_csv/parts/schemas.py deleted file mode 100644 index 7827dae6..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/parts/schemas.py +++ /dev/null @@ -1,22 +0,0 @@ -from pydantic import BaseModel -from typing import Optional - - -class ImportJobResponse(BaseModel): - job_id: str - status: str - message: str - - -class ImportJobStatus(BaseModel): - status: str - job_id: str - total_rows: Optional[int] = 0 - error_count: Optional[int] = 0 - valid_rows: Optional[int] = 0 - error: Optional[str] = None - inserted: Optional[int] = 0 - skipped_invalid: Optional[int] = 0 - skipped_missing_fk: Optional[int] = 0 - skipped_duplicate: Optional[int] = 0 - skipped_details: Optional[list] = None diff --git a/backend/api/v1/modules/a76/layouts_csv/parts/tasks.py b/backend/api/v1/modules/a76/layouts_csv/parts/tasks.py deleted file mode 100644 index 1589e61c..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/parts/tasks.py +++ /dev/null @@ -1,382 +0,0 @@ -""" -Tareas Celery para importación CSV de Números de Parte. -Flujo: scan_file (validación) → insert_valid_rows (commit). -Paridad Clarion: actualizar (ACT), validación full/parcial, merge existente, reemplazar_sin_preguntar, RFC desde clase. -Sin PartService de creación en API; los mappers CSV (row_to_part_data, apply_rfc_exception_from_class) son la fuente de verdad para reglas de negocio al crear/actualizar. -""" -import json -import logging -import os -from typing import Dict, Any, List - -from core.celery_app import celery_app -from core.database import CoreSessionLocal - -from ..common import storage as common_storage -from ..common import normalize as common_normalize -from ..common import csv_reader as common_csv -from ..common import meta as common_meta -from ..common import responses as common_responses -from .template_config import row_from_template, detect_headers_or_data -from .validators import validate_row_part, get_row_warnings -from .common.mappers import ( - row_to_part_data, - row_to_part_data_merge_existing, - apply_rfc_exception_from_class, -) -from .common.fk_loader import load_parts_fk_sets - -logger = logging.getLogger(__name__) - -JOB_TYPE = "part" - -# Para routes.py (upload guarda con estos prefijos) -PART_IMPORT_FILE_PREFIX = "part_import_file:" -PART_IMPORT_META_PREFIX = "part_import_meta:" -PART_IMPORT_ERROR_LINES_PREFIX = "part_import_error_lines:" -PART_IMPORT_REDIS_TTL = common_storage.IMPORT_REDIS_TTL - - -def _read_plan_for_parts(fieldnames): - if fieldnames: - return common_csv.CsvReadPlan(header_mode="headerless", fieldnames=fieldnames) - return common_csv.CsvReadPlan(header_mode="header") - - -@celery_app.task(bind=True) -def scan_file(self, job_id: str, config: str = None): - logger.info("Parts import: starting scan for job %s", job_id) - - file_path = common_storage.ensure_file_from_redis(JOB_TYPE, job_id, "Parts import") - if not file_path: - return {"status": "failed", "error": "Archivo no encontrado (expirado o no subido). Sube de nuevo."} - common_storage.ensure_meta_from_redis(JOB_TYPE, job_id, file_path, "Parts import") - - error_path = common_storage.error_path_for_job(JOB_TYPE, job_id) - - fieldnames, has_header = detect_headers_or_data(file_path, common_normalize.normalize_header) - read_plan = _read_plan_for_parts(fieldnames) - try: - total_rows = common_csv.count_csv_rows(file_path, has_header=has_header, read_plan=read_plan) - except Exception as e: - return {"status": "failed", "error": str(e)} - - try: - tenant_id, company_id = common_meta.require_tenant_context(file_path) - except ValueError as e: - return {"status": "failed", "error": str(e)} - - meta = common_meta.load_meta(file_path) - actualizar = meta.get("actualizar", False) - - ( - valid_class_codes, - valid_uom_codes, - valid_currency_codes, - valid_fraction_mex_8, - valid_country_m3, - authorized_sector_keys, - company_has_prosec, - is_rfc_exception, - ) = load_parts_fk_sets(tenant_id, company_id) - - from api.v1.modules.a76.parts.models import Part - existing_part_numbers = set() - try: - with CoreSessionLocal() as session: - for p in session.query(Part.part_number).filter( - Part.tenant_id == tenant_id, - Part.company_id == company_id, - ).all(): - if p[0]: - existing_part_numbers.add((p[0] or "").strip().upper()) - except Exception as e: - logger.warning("Parts import: could not load existing part numbers: %s", e) - - error_count = 0 - processed_rows = 0 - errors_detail: List[Dict[str, Any]] = [] - error_lines_list: List[int] = [] - - try: - with open(error_path, "w", encoding="utf-8") as f_err: - for i, row in common_csv.iter_csv_rows_with_plan(file_path, read_plan=read_plan): - self.update_state( - state="PROGRESS", - meta={"current": i, "total": total_rows, "errors": error_count}, - ) - row_norm = row_from_template(row, common_normalize.normalize_header) - row_errors = validate_row_part( - row_norm, - i, - valid_class_codes=valid_class_codes, - valid_uom_codes=valid_uom_codes, - valid_currency_codes=valid_currency_codes, - valid_fraction_mex_8=valid_fraction_mex_8, - valid_country_m3=valid_country_m3, - authorized_sector_keys=authorized_sector_keys, - company_has_prosec=company_has_prosec, - is_rfc_exception=is_rfc_exception, - actualizar=actualizar, - existing_part_numbers=existing_part_numbers, - ) - warnings_list = get_row_warnings(row_norm, i) - has_blocking_error = any(not e.get("warning", False) for e in row_errors) - if has_blocking_error: - error_count += 1 - error_lines_list.append(i) - for e in row_errors: - if not e.get("warning", False): - f_err.write(json.dumps(e) + "\n") - - if len(errors_detail) < 500: - for e in row_errors: - if len(errors_detail) >= 500: - break - errors_detail.append({ - "line": e.get("line", i), - "col": e.get("col", ""), - "msg": e.get("msg", ""), - "solution": e.get("solution", ""), - "warning": bool(e.get("warning", False)), - }) - for w in warnings_list: - if len(errors_detail) >= 500: - break - errors_detail.append({ - "line": w.get("line", i), - "col": w.get("col", ""), - "msg": w.get("msg", ""), - "solution": w.get("solution", ""), - "warning": True, - }) - processed_rows += 1 - - if error_lines_list: - common_storage.store_error_lines(JOB_TYPE, job_id, error_lines_list) - except Exception as e: - logger.error("Parts import scan failed: %s", e) - return {"status": "failed", "error": str(e)} - - return common_responses.scan_result( - job_id, processed_rows, error_count, errors_detail - ) - - -@celery_app.task(bind=True) -def insert_valid_rows(self, job_id: str): - logger.info("Parts import: starting commit for job %s", job_id) - - file_path = common_storage.ensure_file_from_redis(JOB_TYPE, job_id, "Parts import") - if not file_path: - alt_path = common_storage.file_path_for_job(JOB_TYPE, job_id) - if not os.path.exists(alt_path): - return {"status": "failed", "error": "Archivo no encontrado (expirado). Sube y confirma de nuevo."} - file_path = alt_path - else: - common_storage.ensure_meta_from_redis(JOB_TYPE, job_id, file_path, "Parts import") - - error_path = common_storage.error_path_for_job(JOB_TYPE, job_id) - error_lines = common_storage.get_error_lines(JOB_TYPE, job_id, error_path) - - try: - tenant_id, company_id = common_meta.require_tenant_context(file_path) - except ValueError as e: - return {"status": "failed", "error": str(e)} - - meta = common_meta.load_meta(file_path) - actualizar = meta.get("actualizar", False) - reemplazar_sin_preguntar = meta.get("reemplazar_sin_preguntar", True) - - from api.v1.modules.a76.parts.models import Part - from api.v1.modules.a76.classes.models import Class - - ( - valid_class_codes, - valid_uom_codes, - valid_currency_codes, - valid_fraction_mex_8, - valid_country_m3, - authorized_sector_keys, - company_has_prosec, - is_rfc_exception, - ) = load_parts_fk_sets(tenant_id, company_id) - - inserted_count = 0 - skipped_invalid = 0 - skipped_missing_fk = 0 - skipped_duplicate = 0 - skipped_details: List[Dict[str, Any]] = [] - response = None - meta_path = common_meta.get_meta_path(file_path) - - fieldnames, _ = detect_headers_or_data(file_path, common_normalize.normalize_header) - read_plan = _read_plan_for_parts(fieldnames) - - try: - with CoreSessionLocal() as session: - existing_by_part_number = {} - for p in session.query(Part).filter( - Part.tenant_id == tenant_id, - Part.company_id == company_id, - ).all(): - key = (p.part_number or "").strip().upper() - if key: - existing_by_part_number[key] = p - - for i, row in common_csv.iter_csv_rows_with_plan(file_path, read_plan=read_plan): - if i in error_lines: - continue - - row_norm = row_from_template(row, common_normalize.normalize_header) - part_number_raw = (row_norm.get("NUMPARTE") or "").strip().upper() - use_partial = ( - actualizar - and part_number_raw - and part_number_raw in existing_by_part_number - and reemplazar_sin_preguntar - ) - - row_errors = validate_row_part( - row_norm, - i, - valid_class_codes=valid_class_codes, - valid_uom_codes=valid_uom_codes, - valid_currency_codes=valid_currency_codes, - valid_fraction_mex_8=valid_fraction_mex_8, - valid_country_m3=valid_country_m3, - authorized_sector_keys=authorized_sector_keys, - company_has_prosec=company_has_prosec, - is_rfc_exception=is_rfc_exception, - actualizar=actualizar, - existing_part_numbers=set(existing_by_part_number.keys()), - ) - if row_errors and any(not e.get("warning", False) for e in row_errors): - skipped_invalid += 1 - blocking_errors = [e for e in row_errors if not e.get("warning", False)] - for blocking in blocking_errors: - skipped_details.append({ - "line": i, - "reason": f"{blocking.get('col', '')}: {blocking.get('msg', '')}", - "solution": blocking.get("solution", ""), - }) - continue - - if use_partial: - existing = existing_by_part_number.get(part_number_raw) - existing_data = { - "description_spanish": existing.description_spanish, - "description_english": existing.description_english, - "part_class": existing.part_class, - "unit_of_measure": existing.unit_of_measure, - "unit_cost": existing.unit_cost, - "currency_type": existing.currency_type, - "currency_key": existing.currency_key, - "unit_weight": existing.unit_weight, - "weight_type": existing.weight_type, - "fraction": existing.fraction, - "us_fraction": existing.us_fraction, - "part_photo": existing.part_photo, - } - data = row_to_part_data_merge_existing( - row_norm, - existing_data, - valid_class_codes, - valid_uom_codes, - valid_currency_codes, - ) - else: - data = row_to_part_data( - row_norm, - valid_class_codes, - valid_uom_codes, - valid_currency_codes, - ) - - part_number = data.get("part_number") - if not part_number: - skipped_invalid += 1 - continue - - if is_rfc_exception and data.get("part_class"): - class_obj = ( - session.query(Class) - .filter( - Class.tenant_id == tenant_id, - Class.company_id == company_id, - Class.class_code == (data.get("part_class") or "").strip().upper(), - ) - .first() - ) - if class_obj: - data = apply_rfc_exception_from_class(data, class_obj) - - existing = existing_by_part_number.get(part_number) - if existing: - if not reemplazar_sin_preguntar: - skipped_duplicate += 1 - continue - for key, value in data.items(): - if hasattr(existing, key): - setattr(existing, key, value) - session.add(existing) - inserted_count += 1 - else: - new_part = Part( - tenant_id=tenant_id, - company_id=company_id, - **data, - ) - session.add(new_part) - existing_by_part_number[part_number] = new_part - inserted_count += 1 - - try: - session.commit() - except Exception as db_err: - session.rollback() - logger.error("Parts import DB error: %s", db_err) - return common_responses.commit_result( - "failed", 0, skipped_invalid, skipped_missing_fk, skipped_duplicate, - skipped_details, error=str(db_err), - ) - - total_skipped = skipped_invalid + skipped_missing_fk + skipped_duplicate - if inserted_count == 0 and total_skipped > 0: - response = common_responses.commit_result( - "warning", 0, skipped_invalid, skipped_missing_fk, skipped_duplicate, - skipped_details, - message=f"No se insertaron registros. {total_skipped} rechazados.", - ) - elif inserted_count == 0: - response = common_responses.commit_result( - "failed", 0, skipped_invalid, skipped_missing_fk, skipped_duplicate, - skipped_details, - error="No hay registros válidos en el archivo CSV", - ) - else: - response = common_responses.commit_result( - "finished", inserted_count, skipped_invalid, skipped_missing_fk, - skipped_duplicate, skipped_details, - ) - - except Exception as e: - logger.exception("Parts import task failed") - response = common_responses.commit_result( - "failed", 0, skipped_invalid, skipped_missing_fk, skipped_duplicate, - skipped_details, error=str(e), - ) - - common_storage.cleanup_import_job( - JOB_TYPE, job_id, - file_path=file_path, - error_path=error_path, - meta_path=meta_path, - ) - - if response is None: - response = common_responses.commit_result( - "failed", 0, skipped_invalid, skipped_missing_fk, skipped_duplicate, - skipped_details, error="Error inesperado", - ) - return response diff --git a/backend/api/v1/modules/a76/layouts_csv/parts/template_config.py b/backend/api/v1/modules/a76/layouts_csv/parts/template_config.py deleted file mode 100644 index d59c3694..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/parts/template_config.py +++ /dev/null @@ -1,239 +0,0 @@ -""" -Configuración de plantilla CSV para Números de Parte (EstructuraCatPartesAF). -Cabeceras de descarga = Clarion: NUMERO DE PARTE, DESCRIPCION EN ESPAÑOL, etc. -""" -import csv -import io -from typing import Dict, List, Any, Optional, Tuple - -from ..common.cell_value import cell_to_str -from ..common import csv_reader as common_csv_reader -from ..common.csv_hint_inject import inject_max_char_hints_into_columns -from ..common.inject_csv_labels_en import inject_en_labels_into_template_columns -from ..common.template_locale import ( - download_header_cell, - merge_download_header_into_lookup, - merge_labels_into_lookup, - normalize_locale, -) -from .csv_max_chars_for_headers import PART_NUMBERS_CSV_MAX_CHARS - - -# Valores que indican que la primera fila es cabecera (primera columna normalizada) -FIRST_COLUMN_HEADER_VALUES = ("NUMERO DE PARTE", "NUMPARTE", "PART NUMBER") - - -def detect_headers_or_data( - file_path: str, - normalize_header_fn, - encoding: str = "utf-8-sig", -) -> Tuple[Optional[List[str]], bool]: - """ - Lee la primera línea del CSV y decide si es cabecera o dato. - - Si la primera celda normalizada está en FIRST_COLUMN_HEADER_VALUES -> has_header=True, fieldnames=None. - - Si no -> has_header=False, fieldnames=TEMPLATE_DOWNLOAD_HEADERS (primera fila = dato). - """ - try: - sample, _ = common_csv_reader.read_text_sample( - file_path, - requested_encoding=encoding, - sample_chars=2048, - ) - except Exception: - try: - sample, _ = common_csv_reader.read_text_sample( - file_path, - requested_encoding="auto", - sample_chars=2048, - ) - except Exception: - return None, True - lines = sample.splitlines() - if not lines: - return None, True - dialect = common_csv_reader.detect_csv_dialect(sample, delimiters=",;\t") - reader = csv.reader(io.StringIO(lines[0]), dialect=dialect) - first_row = next(reader, None) - if not first_row: - return None, True - first_cell = (first_row[0] or "").strip() - first_cell_norm = normalize_header_fn(first_cell) - if first_cell_norm in FIRST_COLUMN_HEADER_VALUES: - return None, True - return list(TEMPLATE_FIELDNAMES_FOR_READING), False - - -# Cabeceras que se escriben al descargar la plantilla CSV (igual que Clarion EstructuraCatPartesAF) -# Columnas I y J vacías en Clarion: dos comas entre CLAVE MONEDA y PESO UNITARIO (espacios en blanco separados por coma) -TEMPLATE_DOWNLOAD_HEADERS: List[str] = [ - "NUMERO DE PARTE", - "DESCRIPCION EN ESPAÑOL", - "DESCRIPCION EN INGLES", - "CLASE", - "UNIDAD DE MEDIDA COMERCIAL", - "COSTO UNITARIO", - "TIPO MONEDA COSTO", - "CLAVE MONEDA", - "", # Col I vacía - "", # Col J vacía - "PESO UNITARIO", - "TIPO PESO", - "FRACCION", - "PAIS", - "PREFERENCIA", - "SECTOR", - "RUTA DE LA IMAGEN", -] - -# Misma longitud que TEMPLATE_DOWNLOAD_HEADERS; columnas I/J vacías. -TEMPLATE_DOWNLOAD_HEADERS_EN: List[str] = [ - "PART NUMBER", - "DESCRIPTION (SPANISH)", - "DESCRIPTION (ENGLISH)", - "CLASS", - "COMMERCIAL UNIT OF MEASURE", - "UNIT COST", - "COST CURRENCY TYPE", - "CURRENCY CODE", - "", - "", - "UNIT WEIGHT", - "WEIGHT TYPE", - "FRACTION", - "COUNTRY", - "PREFERENCE", - "SECTOR", - "IMAGE PATH", -] - -# Para lectura cuando la primera fila es dato (sin cabecera): nombres únicos para columnas I y J -TEMPLATE_FIELDNAMES_FOR_READING: List[str] = [ - "NUMERO DE PARTE", - "DESCRIPCION EN ESPAÑOL", - "DESCRIPCION EN INGLES", - "CLASE", - "UNIDAD DE MEDIDA COMERCIAL", - "COSTO UNITARIO", - "TIPO MONEDA COSTO", - "CLAVE MONEDA", - "_COL_I_", - "_COL_J_", - "PESO UNITARIO", - "TIPO PESO", - "FRACCION", - "PAIS", - "PREFERENCIA", - "SECTOR", - "RUTA DE LA IMAGEN", -] - -TEMPLATE_COLUMNS: Dict[str, List[Dict[str, Any]]] = { - "part_numbers": [ - {"canonical": "NUMPARTE", "aliases": ["NUMERO DE PARTE", "NUMERO PARTE", "PART NUMBER", "NUM PARTE"]}, - {"canonical": "NUMPARTECOM", "aliases": ["NUMERO PARTE COMERCIAL", "COMMERCIAL PART", "PARTE COMERCIAL"]}, - {"canonical": "DESCRIPCIONE", "aliases": ["DESCRIPCION EN ESPAÑOL", "DESCRIPCION EN ESPANOL", "DESCRIPCION", "DESCRIPCION ES", "DESC ESPANOL"]}, - {"canonical": "DESCRIPCIONI", "aliases": ["DESCRIPCION EN INGLES", "DESCRIPCION EN", "DESCRIPTION", "DESC INGLES"]}, - {"canonical": "CLASE", "aliases": ["CLASS", "CLASE MATERIAL", "PART CLASS"]}, - {"canonical": "UNIMED", "aliases": ["UNIDAD DE MEDIDA COMERCIAL", "UNIDAD MEDIDA", "UNIT", "UOM", "UNIT OF MEASURE"]}, - {"canonical": "COSTOUNIT", "aliases": ["COSTO UNITARIO", "UNIT COST", "COSTO"]}, - {"canonical": "TIPOMONEDA", "aliases": ["TIPO MONEDA COSTO", "TIPO MONEDA", "MONEDA"]}, - {"canonical": "CLAVEMONEDA", "aliases": ["CLAVE MONEDA", "CURRENCY", "MONEDA CLAVE", "CURRENCY KEY"]}, - {"canonical": "PESOUNIT", "aliases": ["PESO UNITARIO", "UNIT WEIGHT", "PESO"]}, - {"canonical": "TIPOPESO", "aliases": ["TIPO PESO", "WEIGHT TYPE"]}, - {"canonical": "FRACCION", "aliases": ["FRACCION MEX"]}, - {"canonical": "FRACCIONAME", "aliases": ["FRACCION USA", "US FRACTION"]}, - {"canonical": "PAIS", "aliases": ["PAIS", "COUNTRY"]}, - {"canonical": "PREFERENCIA", "aliases": ["PREFERENCIA", "PREFERENCIA ARANCELARIA"]}, - {"canonical": "SECTOR", "aliases": ["SECTOR"]}, - {"canonical": "RUTAIMAGEN", "aliases": ["RUTA DE LA IMAGEN", "RUTA IMAGEN", "FOTO"]}, - {"canonical": "FDAKEY", "aliases": ["FDA", "FDA KEY"]}, - {"canonical": "FCCKEY", "aliases": ["FCC", "FCC KEY"]}, - {"canonical": "LICENCIA", "aliases": ["LICENSE CODE", "LICENSE"]}, - {"canonical": "ECCN", "aliases": ["ECCN CODE"]}, - {"canonical": "EXPORTCODE", "aliases": ["EXPORT CODE", "CODIGO EXPORT"]}, - {"canonical": "EXCLUSION", "aliases": ["EXCLUSION SYMBOL", "SIMBOLO EXCLUSION"]}, - {"canonical": "ACTIVO", "aliases": ["IS ACTIVE", "ACTIVE", "ACTIVO"]}, - ], -} - -inject_en_labels_into_template_columns(TEMPLATE_COLUMNS) - -inject_max_char_hints_into_columns( - TEMPLATE_COLUMNS.get("part_numbers"), PART_NUMBERS_CSV_MAX_CHARS -) - -_PART_ORDER_DOWNLOAD = [ - "NUMPARTE", - "DESCRIPCIONE", - "DESCRIPCIONI", - "CLASE", - "UNIMED", - "COSTOUNIT", - "TIPOMONEDA", - "CLAVEMONEDA", - None, - None, - "PESOUNIT", - "TIPOPESO", - "FRACCION", - "PAIS", - "PREFERENCIA", - "SECTOR", - "RUTAIMAGEN", -] - -_PN_BY_CANON = {c["canonical"]: c for c in (TEMPLATE_COLUMNS.get("part_numbers") or [])} - - -def _parts_download_column_defs() -> List[Dict[str, Any]]: - out: List[Dict[str, Any]] = [] - for key in _PART_ORDER_DOWNLOAD: - if key is None: - out.append({"canonical": ""}) - else: - base = _PN_BY_CANON.get(key) - out.append(dict(base) if base else {"canonical": key}) - return out - - -_PARTS_DOWNLOAD_DEFS = _parts_download_column_defs() - - -def download_headers_for_locale(locale: str) -> List[str]: - loc = normalize_locale(locale) - return [download_header_cell(d, loc) for d in _PARTS_DOWNLOAD_DEFS] - - -def build_normalized_lookup(normalize_header_fn) -> Dict[str, str]: - cols = TEMPLATE_COLUMNS.get("part_numbers") - if not cols: - return {} - lookup: Dict[str, str] = {} - for item in cols: - canonical = item["canonical"] - lookup[normalize_header_fn(canonical)] = canonical - for alias in item.get("aliases") or []: - lookup[normalize_header_fn(alias)] = canonical - merge_labels_into_lookup(item, canonical, normalize_header_fn, lookup) - merge_download_header_into_lookup(item, canonical, normalize_header_fn, lookup) - # Plantilla EN: mismas posiciones que TEMPLATE_DOWNLOAD_HEADERS - for es_h, en_h in zip(TEMPLATE_DOWNLOAD_HEADERS, TEMPLATE_DOWNLOAD_HEADERS_EN): - if not es_h or not en_h: - continue - es_key = normalize_header_fn(es_h) - canon = lookup.get(es_key) - if canon: - lookup[normalize_header_fn(en_h)] = canon - return lookup - - -def row_from_template(row: Dict[str, Any], normalize_header_fn) -> Dict[str, Any]: - lookup = build_normalized_lookup(normalize_header_fn) - if not lookup: - return {normalize_header_fn(k): cell_to_str(v) for k, v in row.items()} - out: Dict[str, Any] = {} - for csv_header, value in row.items(): - key_norm = normalize_header_fn(csv_header) - if key_norm in lookup: - out[lookup[key_norm]] = cell_to_str(value) - return out diff --git a/backend/api/v1/modules/a76/layouts_csv/parts/validators/__init__.py b/backend/api/v1/modules/a76/layouts_csv/parts/validators/__init__.py deleted file mode 100644 index 18aa9521..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/parts/validators/__init__.py +++ /dev/null @@ -1,4 +0,0 @@ -# validators for parts CSV import row validation -from .create import validate_row_part, validate_row_part_partial, get_row_warnings - -__all__ = ["validate_row_part", "validate_row_part_partial", "get_row_warnings"] diff --git a/backend/api/v1/modules/a76/layouts_csv/parts/validators/common.py b/backend/api/v1/modules/a76/layouts_csv/parts/validators/common.py deleted file mode 100644 index b26dd1f4..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/parts/validators/common.py +++ /dev/null @@ -1,423 +0,0 @@ -""" -Validaciones comunes de fila para import CSV de partes. -Paridad Clarion: VALIDA_TODA_PARTES (obligatorios A, B, E salvo excepción RFC), VALIDACIONES_PARTES. -""" -from typing import Dict, Any, Optional, Set, List - -from ..common.common_validators import check_max_length, check_decimal -from decimal import Decimal, InvalidOperation - - -MSG_NUMPARTE_VACIO = ( - "Error: (Col. A) La columna de Número de Parte esta vacio y no se pueden hacer las validaciones. " - "Capturar en la Columna A un Número de Parte nuevo o una ya existente a la cual desee actualizar campos" -) -NUMPARTE_MAX_LEN_CLARION = 30 -NUMPARTE_MAX_LEN_DB = 70 - - -def validate_row_required(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]: - """Col A (NUMPARTE) obligatorio; mensaje Clarion si está vacío.""" - val = (row.get("NUMPARTE") or "").strip() - if not val: - return {"line": line_num, "col": "NUMPARTE", "msg": MSG_NUMPARTE_VACIO} - if len(val) > NUMPARTE_MAX_LEN_DB: - return { - "line": line_num, - "col": "NUMPARTE", - "msg": f"Error: (Col. A) El Número de Parte: {val} supera la longitud de caracteres. " - f"Capturar en la columna A un Número de Parte de {NUMPARTE_MAX_LEN_DB} caracteres como máximo.", - } - return None - - -def validate_row_required_full( - row: Dict[str, Any], - line_num: int, - is_rfc_exception: bool = False, -) -> Optional[Dict[str, Any]]: - """Obligatorios en validación completa: A, B; y E (U.M. Comercial) salvo excepción RFC con D vacía.""" - err = validate_row_required(row, line_num) - if err: - return err - if not (row.get("DESCRIPCIONE") or "").strip(): - return { - "line": line_num, - "col": "DESCRIPCIONE", - "msg": "Existen campos vacios que son obligatorios, es la (Col.B) Descripción Español. " - "Revisar la línea del archivo y capturar los campos con la información correcta.", - } - # E obligatorio salvo que empresa sea excepción RFC y Col D (Clase) esté vacía - col_d = (row.get("CLASE") or "").strip() - if not is_rfc_exception or col_d: - if not (row.get("UNIMED") or "").strip(): - return { - "line": line_num, - "col": "UNIMED", - "msg": "Existen campos vacios que son obligatorios, es la (Col.E) U.M. Comercial. " - "Revisar la línea del archivo y capturar los campos con la información correcta.", - } - return None - - -def validate_row_required_act_part_exists( - row: Dict[str, Any], - line_num: int, - existing_part_numbers: Set[str], -) -> Optional[Dict[str, Any]]: - """Cuando actualizar=True y se hace validación full: el número de parte debe existir en catálogo.""" - num_parte = (row.get("NUMPARTE") or "").strip().upper() - if not num_parte: - return None - if num_parte not in existing_part_numbers: - return { - "line": line_num, - "col": "NUMPARTE", - "msg": "(Col.A) El número de parte no existe.", - } - return None - - -def validate_row_lengths(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]: - """Longitudes máximas (Clarion: Col A 30; resto según modelo).""" - err = check_max_length(row, "NUMPARTE", NUMPARTE_MAX_LEN_DB, line_num) - if err: - return err - checks = [ - ("DESCRIPCIONE", 500), - ("DESCRIPCIONI", 500), - ("CLASE", 8), - ("UNIMED", 5), - ("TIPOMONEDA", 3), - ("CLAVEMONEDA", 3), - ("FRACCION", 10), - ("PAIS", 3), - ("PREFERENCIA", 7), - ("SECTOR", 5), - ("RUTAIMAGEN", 255), - ("TIPOPESO", 6), - ] - for col, max_len in checks: - err = check_max_length(row, col, max_len, line_num) - if err: - return err - return None - - -def validate_row_types(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]: - """Tipos numéricos (decimales) de la fila.""" - err = check_decimal(row, "COSTOUNIT", line_num) - if err: - return err - # No negativos - raw = (row.get("COSTOUNIT") or "").strip() - if raw: - try: - if Decimal(raw) < 0: - return { - "line": line_num, - "col": "COSTOUNIT", - "msg": "Error: (Col. I) El Costo Unitario no puede ser negativo.", - } - except (InvalidOperation, ValueError): - # check_decimal already handles format; ignore here - pass - err = check_decimal(row, "PESOUNIT", line_num) - if err: - return err - raw = (row.get("PESOUNIT") or "").strip() - if raw: - try: - if Decimal(raw) < 0: - return { - "line": line_num, - "col": "PESOUNIT", - "msg": "Error: (Col. K) El Peso Unitario no puede ser negativo.", - } - except (InvalidOperation, ValueError): - pass - return None - - -def validate_row_class_fk( - row: Dict[str, Any], - line_num: int, - valid_class_codes: Optional[Set[str]], -) -> Optional[Dict[str, Any]]: - """Col D (Clase): si no vacía, debe existir en catálogo de clases.""" - val = (row.get("CLASE") or "").strip() - if not val or valid_class_codes is None: - return None - if val.upper() in valid_class_codes: - return None - return { - "line": line_num, - "col": "CLASE", - "msg": f"Error: (Col. D) La Clase: {val} no existe en el Catálogo de Clases de Activo Fijo. " - "Dar de alta la Clase en el Catálogo de Clases de Activo Fijo.", - } - - -def validate_row_uom_fk( - row: Dict[str, Any], - line_num: int, - valid_uom_codes: Optional[Set[str]], -) -> Optional[Dict[str, Any]]: - """Col E (U.M. Comercial): si no vacía, debe existir en catálogo UOM.""" - val = (row.get("UNIMED") or "").strip() - if not val or valid_uom_codes is None: - return None - if val.upper() in valid_uom_codes: - return None - return { - "line": line_num, - "col": "UNIMED", - "msg": f"Error: (Col. E) La Unidad de Medida Comercial: {val} no existe en el Catálogo de U.M. " - "Revisar esta Unidad de Medida en el archivo, en caso de ser correcta dar la de alta en el Catálogo de U.M.", - } - - -def validate_row_tipo_moneda( - row: Dict[str, Any], - line_num: int, -) -> Optional[Dict[str, Any]]: - """Col G (Tipo Moneda): solo ME, MN o MC (o vacío).""" - val = (row.get("TIPOMONEDA") or "").strip().upper() - if not val: - return None - if val in ("ME", "MN", "MC"): - return None - return { - "line": line_num, - "col": "TIPOMONEDA", - "msg": f"Error: (Col. G) La opción de Tipo Moneda: {val} no es valida. " - "Capturar una opción valida: ME para Dolares, MN para Pesos, MC para Moneda de Captura o dejar el campo vacio.", - } - - -def validate_row_clave_moneda( - row: Dict[str, Any], - line_num: int, - valid_currency_codes: Optional[Set[str]], -) -> Optional[Dict[str, Any]]: - """Col H (Clave Moneda): si G = MC, H obligatoria y debe existir en catálogo.""" - g = (row.get("TIPOMONEDA") or "").strip().upper() - if g != "MC": - return None - val = (row.get("CLAVEMONEDA") or "").strip() - if not val: - return { - "line": line_num, - "col": "CLAVEMONEDA", - "msg": "Error: (Col. H) La Clave de la Moneda es obligatoria cuando Tipo Moneda es MC.", - } - if valid_currency_codes is not None and val.upper() not in valid_currency_codes: - return { - "line": line_num, - "col": "CLAVEMONEDA", - "msg": f"Error: (Col. H) La Clave de la Moneda: {val} no existe en el Catálogo de Claves de Moneda. " - "Revisar esta clave de la Moneda en el archivo, en caso de ser correcta Actualizar los Catálogos Fijos.", - } - return None - - -def validate_row_tipo_peso(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]: - """Col L (Tipo Peso): solo KILOS o LIBRAS (o vacío).""" - val = (row.get("TIPOPESO") or "").strip() - if not val: - return None - if val.upper() in ("KILOS", "LIBRAS"): - return None - return { - "line": line_num, - "col": "TIPOPESO", - "msg": f"Error: (Col. L) La opción del Tipo de Peso {val} no es valida. " - "Capturar una opción valida: KILOS, LIBRAS o dejar el campo vacio y automaticamente se asigna KILOS.", - } - - -def _normalize_fraction_mex_8(value: str) -> str: - """Primeros 8 caracteres si len>=10, sino hasta 8 (Clarion SUB).""" - if not value: - return "" - v = value.strip() - if len(v) >= 10: - return v[:8] - return v[:8] if len(v) > 8 else v - - -def validate_row_fraction_mex_catalog( - row: Dict[str, Any], - line_num: int, - valid_fraction_mex_8: Optional[Set[str]], -) -> Optional[Dict[str, Any]]: - """Col M (Fracción Mex): si no vacía, debe existir en catálogo Mex o histórico.""" - val = (row.get("FRACCION") or "").strip() - if not val or valid_fraction_mex_8 is None: - return None - code_8 = _normalize_fraction_mex_8(val) - if not code_8: - return None - if code_8 in valid_fraction_mex_8: - return None - return { - "line": line_num, - "col": "FRACCION", - "msg": f"Error: (Col. M) La Fraccion Mexicana: {val} no existe en el Catálogo de Fracciones Arancelarias Sifr@ ni en el Historico. " - "Revisar esta Fracción Arancelaria en el archivo, en caso de ser correcta Actualizar las Fracciones Arancelarias.", - } - - -def validate_row_country_fk( - row: Dict[str, Any], - line_num: int, - valid_country_m3: Optional[Set[str]], -) -> Optional[Dict[str, Any]]: - """Col N (País): si no vacía, debe existir en catálogo de países (m3_key).""" - val = (row.get("PAIS") or "").strip() - if not val or valid_country_m3 is None: - return None - if val.upper() in valid_country_m3: - return None - return { - "line": line_num, - "col": "PAIS", - "msg": f"Error: (Col. N) La Clave del País: {val} no existe en el Catálogo de Paises. Dar lo de alta en el Catálogo de Paises.", - } - - -def validate_row_preferencia( - row: Dict[str, Any], - line_num: int, - is_rfc_exception: bool, -) -> Optional[Dict[str, Any]]: - """Col O (Preferencia): solo GENERAL, TLCS, PROSEC o ALADI. Excepción RFC: solo aplica si D no vacía.""" - col_d = (row.get("CLASE") or "").strip() - if is_rfc_exception and not col_d: - return None - val = (row.get("PREFERENCIA") or "").strip().upper() - if not val: - return None - if val in ("GENERAL", "TLCS", "PROSEC", "ALADI"): - return None - return { - "line": line_num, - "col": "PREFERENCIA", - "msg": "Error: (Col. O) La opción de Preferencia Arancelaria no es valida. " - "Capturar una opción valida: GENERAL, TLCS, PROSEC, ALADI.", - } - - -def validate_row_sector( - row: Dict[str, Any], - line_num: int, - company_has_prosec: bool, - authorized_sector_keys: Optional[Set[str]], -) -> Optional[Dict[str, Any]]: - """Col P (Sector): si O=PROSEC entonces P obligatorio, empresa PROSEC, sector autorizado; si O≠PROSEC y P no vacío error.""" - pref = (row.get("PREFERENCIA") or "").strip().upper() - sector = (row.get("SECTOR") or "").strip() - # Formato: solo A-Z/0-9 (sin espacios ni especiales), hasta 8 - if sector: - s = sector.strip().upper() - if not (1 <= len(s) <= 8) or not s.isalnum(): - return { - "line": line_num, - "col": "SECTOR", - "msg": "Error: (Col. P) El Sector contiene caracteres no permitidos. Use solo letras y números sin espacios (máx. 8 caracteres).", - } - if pref == "PROSEC": - if not sector: - return { - "line": line_num, - "col": "SECTOR", - "msg": "Error: (Col. P) no hay un sector capturado y se tiene la opción de Preferencia Arancelaria: PROSEC. Capturar un Sector valido en la columna P", - } - if not company_has_prosec: - return { - "line": line_num, - "col": "SECTOR", - "msg": f"Error: (Col. P) Se quiere aplicar el sector {sector} pero la empresa no cuenta con Permiso PROSEC. " - "Marcar la opción que cuenta con un Permiso PROSEC en los Datos de la Empresa.", - } - if authorized_sector_keys is not None and sector.upper() not in authorized_sector_keys: - return { - "line": line_num, - "col": "SECTOR", - "msg": f"Error: (Col. P) El sector {sector} no esta promovido para esta Empresa. " - "Capturar un Sector en la columna P que este promovido en los Datos de la Empresa o active el Sector en el Catálogo de Sectores.", - } - else: - if sector: - return { - "line": line_num, - "col": "SECTOR", - "msg": f"Error: (Col. P) Hay un sector capturado y se tiene la opción de Preferencia Arancelaria: {pref or '(vacío)'}. " - "Borra el sector en la columna P o cambiar la preferencia en la columna O.", - } - return None - - -def validaciones_parte( - row: Dict[str, Any], - line_num: int, - valid_class_codes: Optional[Set[str]] = None, - valid_uom_codes: Optional[Set[str]] = None, - valid_currency_codes: Optional[Set[str]] = None, - valid_fraction_mex_8: Optional[Set[str]] = None, - valid_country_m3: Optional[Set[str]] = None, - authorized_sector_keys: Optional[Set[str]] = None, - company_has_prosec: bool = False, - is_rfc_exception: bool = False, -) -> List[Dict[str, Any]]: - """Reglas compartidas Clarion (VALIDACIONES_PARTES): longitudes, tipos, FKs, preferencia, sector.""" - errors: List[Dict[str, Any]] = [] - - for err in [ - validate_row_lengths(row, line_num), - validate_row_types(row, line_num), - validate_row_class_fk(row, line_num, valid_class_codes), - validate_row_uom_fk(row, line_num, valid_uom_codes), - validate_row_tipo_moneda(row, line_num), - validate_row_clave_moneda(row, line_num, valid_currency_codes), - validate_row_tipo_peso(row, line_num), - validate_row_fraction_mex_catalog(row, line_num, valid_fraction_mex_8), - validate_row_country_fk(row, line_num, valid_country_m3), - validate_row_preferencia(row, line_num, is_rfc_exception), - validate_row_sector(row, line_num, company_has_prosec, authorized_sector_keys), - ]: - if err: - errors.append(err) - - return errors - - -def validate_row_warnings( - row: Dict[str, Any], - line_num: int, -) -> list: - """ - Devuelve lista de advertencias (no bloquean; no se añaden a error_lines). - Clarion: desfase cuando Col Q tiene valor; apóstrofes en Número de Parte. - """ - warnings: list = [] - # Advertencia desfase: Clarion cuando CSVArc:ColumnaQ <> '' - if (row.get("RUTAIMAGEN") or "").strip(): - warnings.append({ - "line": line_num, - "col": "RUTAIMAGEN", - "msg": "Advertencia: Podría existir un desfase en esta línea.", - "severity": "warning", - "warning": True, - }) - # Advertencia apóstrofes en Número de Parte - num_parte = (row.get("NUMPARTE") or "").strip() - if "'" in num_parte: - warnings.append({ - "line": line_num, - "col": "NUMPARTE", - "msg": f"Advertencia: El Número de Parte: {num_parte} Contiene Apostrofes. Se Omitirá el Apostrofe para Subir el Número de Parte.", - "severity": "warning", - "warning": True, - }) - return warnings diff --git a/backend/api/v1/modules/a76/layouts_csv/parts/validators/create.py b/backend/api/v1/modules/a76/layouts_csv/parts/validators/create.py deleted file mode 100644 index c37e400e..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/parts/validators/create.py +++ /dev/null @@ -1,127 +0,0 @@ -""" -Punto de entrada de validación para import de una fila de parte. -Flujo Clarion: no ACT → siempre VALIDA_TODA_PARTES; ACT y parte existe → VALIDA_PARCIAL_PARTES; -ACT y parte no existe → VALIDA_TODA_PARTES (obligatorios + validaciones) y en commit se crea (ADD). -""" -from typing import Dict, Any, Optional, Set, List - -from .common import ( - validate_row_required, - validate_row_required_full, - validaciones_parte, - validate_row_warnings, -) - - -def validate_row_part( - row: Dict[str, Any], - line_num: int, - valid_class_codes: Optional[Set[str]] = None, - valid_uom_codes: Optional[Set[str]] = None, - valid_currency_codes: Optional[Set[str]] = None, - valid_fraction_mex_8: Optional[Set[str]] = None, - valid_country_m3: Optional[Set[str]] = None, - authorized_sector_keys: Optional[Set[str]] = None, - company_has_prosec: bool = False, - is_rfc_exception: bool = False, - actualizar: bool = False, - existing_part_numbers: Optional[Set[str]] = None, -) -> List[Dict[str, Any]]: - """ - Valida una fila de CSV de partes para import. - - No actualizar: siempre validación completa (obligatorios A,B,E + validaciones_parte). - - Actualizar y número de parte ya existe: validación parcial (solo validaciones_parte). - - Actualizar y número de parte no existe: validación completa; si pasa, en commit se crea (ADD). - Si actualizar y validación full, además exige que el número de parte exista en catálogo. - """ - errors: List[Dict[str, Any]] = [] - err = validate_row_required(row, line_num) - if err: - errors.append(err) - - part_number = (row.get("NUMPARTE") or "").strip().upper() - use_partial = ( - actualizar - and existing_part_numbers is not None - and part_number in existing_part_numbers - ) - - if use_partial: - errors.extend( - validate_row_part_partial( - row, - line_num, - valid_class_codes=valid_class_codes, - valid_uom_codes=valid_uom_codes, - valid_currency_codes=valid_currency_codes, - valid_fraction_mex_8=valid_fraction_mex_8, - valid_country_m3=valid_country_m3, - authorized_sector_keys=authorized_sector_keys, - company_has_prosec=company_has_prosec, - is_rfc_exception=is_rfc_exception, - ) - ) - return errors - - # Full validation (también cuando actualizar y parte no existe → ADD en commit) - err = validate_row_required_full(row, line_num, is_rfc_exception=is_rfc_exception) - if err: - errors.append(err) - # No exigir que exista en actualizar: si no existe se valida completa y en commit se crea (ADD) - errors.extend( - validaciones_parte( - row, - line_num, - valid_class_codes=valid_class_codes, - valid_uom_codes=valid_uom_codes, - valid_currency_codes=valid_currency_codes, - valid_fraction_mex_8=valid_fraction_mex_8, - valid_country_m3=valid_country_m3, - authorized_sector_keys=authorized_sector_keys, - company_has_prosec=company_has_prosec, - is_rfc_exception=is_rfc_exception, - ) - ) - return errors - - -def validate_row_part_partial( - row: Dict[str, Any], - line_num: int, - valid_class_codes: Optional[Set[str]] = None, - valid_uom_codes: Optional[Set[str]] = None, - valid_currency_codes: Optional[Set[str]] = None, - valid_fraction_mex_8: Optional[Set[str]] = None, - valid_country_m3: Optional[Set[str]] = None, - authorized_sector_keys: Optional[Set[str]] = None, - company_has_prosec: bool = False, - is_rfc_exception: bool = False, -) -> List[Dict[str, Any]]: - """ - Validación parcial (modo Actualizar, parte existente): solo NUMPARTE + validaciones_parte. - Los campos vacíos se rellenan desde la parte existente en el mapper. - """ - errors: List[Dict[str, Any]] = [] - err = validate_row_required(row, line_num) - if err: - errors.append(err) - errors.extend( - validaciones_parte( - row, - line_num, - valid_class_codes=valid_class_codes, - valid_uom_codes=valid_uom_codes, - valid_currency_codes=valid_currency_codes, - valid_fraction_mex_8=valid_fraction_mex_8, - valid_country_m3=valid_country_m3, - authorized_sector_keys=authorized_sector_keys, - company_has_prosec=company_has_prosec, - is_rfc_exception=is_rfc_exception, - ) - ) - return errors - - -def get_row_warnings(row: Dict[str, Any], line_num: int) -> list: - """Devuelve lista de advertencias para la fila (desfase, apóstrofes). No bloquean.""" - return validate_row_warnings(row, line_num) diff --git a/backend/api/v1/modules/a76/layouts_csv/pedmientos/__init__.py b/backend/api/v1/modules/a76/layouts_csv/pedmientos/__init__.py deleted file mode 100644 index 8378bf88..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/pedmientos/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# CSV import for Pedimentos (upload → scan → commit) diff --git a/backend/api/v1/modules/a76/layouts_csv/pedmientos/common/__init__.py b/backend/api/v1/modules/a76/layouts_csv/pedmientos/common/__init__.py deleted file mode 100644 index 47ef2168..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/pedmientos/common/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# common_validators, mappers, fk_loader diff --git a/backend/api/v1/modules/a76/layouts_csv/pedmientos/common/common_validators.py b/backend/api/v1/modules/a76/layouts_csv/pedmientos/common/common_validators.py deleted file mode 100644 index 3f9609ba..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/pedmientos/common/common_validators.py +++ /dev/null @@ -1,150 +0,0 @@ -""" -Validadores reutilizables para import CSV de pedimentos. -Incluye parseo de fechas (paridad exchange_rate) para FECHA_INICIO, FECHA_FINAL, FECHA_PAGO. -""" -from datetime import datetime, time -from decimal import Decimal, InvalidOperation -from typing import Dict, Any, Optional, Set, List - -DATE_FORMATS: List[str] = ["%Y-%m-%d", "%d/%m/%Y", "%m/%d/%Y", "%d-%m-%Y", "%Y/%m/%d"] -DATE_FORMAT_PREFERENCE_MAP: Dict[str, str] = { - "dd/mm/yyyy": "%d/%m/%Y", - "mm/dd/yyyy": "%m/%d/%Y", - "yyyy-mm-dd": "%Y-%m-%d", -} - - -def parse_date( - val: Optional[str], date_format_preference: Optional[str] = None -) -> Optional[datetime]: - """Parsea fecha; si date_format_preference está definido, solo se acepta ese formato.""" - if not val or not str(val).strip(): - return None - raw = str(val).strip() - if date_format_preference and date_format_preference in DATE_FORMAT_PREFERENCE_MAP: - fmt = DATE_FORMAT_PREFERENCE_MAP[date_format_preference] - try: - parsed = datetime.strptime(raw, fmt) - return datetime.combine(parsed.date(), time.min) - except ValueError: - return None - for fmt in DATE_FORMATS: - try: - parsed = datetime.strptime(raw, fmt) - return datetime.combine(parsed.date(), time.min) - except ValueError: - continue - return None - - -def check_required_max(row: Dict[str, Any], col: str, max_len: int, line_num: int) -> Optional[Dict[str, Any]]: - val = (row.get(col) or "").strip() - if not val: - return {"line": line_num, "col": col, "msg": "Requerido"} - if len(val) > max_len: - return {"line": line_num, "col": col, "msg": f"Máximo {max_len} caracteres"} - return None - - -def check_int_in_set( - row: Dict[str, Any], col: str, valid_ids: Set[int], line_num: int -) -> Optional[Dict[str, Any]]: - val = (row.get(col) or "").strip() - if not val: - return {"line": line_num, "col": col, "msg": "Requerido"} - try: - client_id = int(val) - except ValueError: - return {"line": line_num, "col": col, "msg": "Debe ser número entero"} - if client_id not in valid_ids: - return {"line": line_num, "col": col, "msg": "Cliente no existe en catálogo"} - return None - - -def check_optional_int_in_set( - row: Dict[str, Any], col: str, valid_ids: Set[int], line_num: int -) -> Optional[Dict[str, Any]]: - """Si la columna viene vacía no se valida; si trae valor debe ser entero y estar en valid_ids.""" - val = (row.get(col) or "").strip() - if not val: - return None - try: - client_id = int(val) - except ValueError: - return {"line": line_num, "col": col, "msg": "Debe ser número entero"} - if valid_ids and client_id not in valid_ids: - return {"line": line_num, "col": col, "msg": "Cliente no existe en catálogo"} - return None - - -def check_optional_short_name( - row: Dict[str, Any], - col: str, - short_name_to_id: Dict[str, int], - line_num: int, -) -> Optional[Dict[str, Any]]: - """Si la columna viene vacía no se valida; si trae valor debe ser short_name existente en catálogo. Solo string, no ID.""" - val = (row.get(col) or "").strip() - if not val: - return None - key = val.upper() - if key not in short_name_to_id: - return { - "line": line_num, - "col": col, - "msg": "Cliente no encontrado (short_name no existe en catálogo)", - } - return None - - -def check_in_set( - row: Dict[str, Any], - col: str, - valid_set: Set[str], - line_num: int, - max_len: int = 10, - catalog_name: str = "catálogo", -) -> Optional[Dict[str, Any]]: - val = (row.get(col) or "").strip() - if not val: - return {"line": line_num, "col": col, "msg": "Requerido"} - if len(val) > max_len: - return {"line": line_num, "col": col, "msg": f"Máximo {max_len} caracteres"} - if valid_set and val not in valid_set: - return {"line": line_num, "col": col, "msg": f"No existe en {catalog_name}"} - return None - - -def check_optional_max_length(row: Dict[str, Any], col: str, max_len: int, line_num: int) -> Optional[Dict[str, Any]]: - val = (row.get(col) or "").strip() - if not val: - return None - if len(val) > max_len: - return {"line": line_num, "col": col, "msg": f"Máximo {max_len} caracteres"} - return None - - -def check_optional_decimal(row: Dict[str, Any], col: str, line_num: int) -> Optional[Dict[str, Any]]: - val = (row.get(col) or "").strip() - if not val: - return None - try: - Decimal(val.replace(",", ".")) - except (InvalidOperation, ValueError): - return {"line": line_num, "col": col, "msg": "Valor numérico inválido"} - return None - - -def check_optional_in_set( - row: Dict[str, Any], col: str, allowed: Set[str], line_num: int, msg: str -) -> Optional[Dict[str, Any]]: - val = (row.get(col) or "").strip().lower() - if not val: - return None - if val not in allowed: - return {"line": line_num, "col": col, "msg": msg} - return None - - -PEDIMENTO_CODE_MAX = 2 -REGIMEN_MAX = 3 diff --git a/backend/api/v1/modules/a76/layouts_csv/pedmientos/common/fk_loader.py b/backend/api/v1/modules/a76/layouts_csv/pedmientos/common/fk_loader.py deleted file mode 100644 index 6cdc17e8..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/pedmientos/common/fk_loader.py +++ /dev/null @@ -1,167 +0,0 @@ -""" -Carga de conjuntos FK para validación de import CSV de pedimentos. -Clarion: clave+regimen+tipo (CodePedimentoRegimen), aduana (CustomsSection), patente (CustomsBroker.license), existing_pedimento_keys. -Legacy: valid_client_ids, valid_regimes, valid_pedimento_codes. Cliente por short_name: short_name_to_id. -""" -from typing import Dict, Set, Tuple - -from sqlalchemy.orm import Session - - -def _pedimento_key(year: str, customs_office: str, license_val: str, pedimento_number: str) -> str: - """Clave única para identificar un pedimento (tenant/company se filtra en query).""" - return f"{year}|{customs_office}|{license_val}|{pedimento_number}" - - -def load_pedimentos_fk_sets( - session: Session, tenant_id: int, company_id: int -) -> Tuple[ - Set[int], - Set[str], - Set[str], - Set[Tuple[str, str, str]], - Set[str], - Set[str], - Set[str], - Set[str], - Dict[str, int], -]: - """ - Carga todos los conjuntos FK para validación Clarion y legacy. - Returns: - valid_client_ids, - valid_regimes, - valid_pedimento_codes, - valid_clave_regimen_tipo, # (pedimento_code, regimen_code, type_code) - valid_aduana_seccion, # customs_code - existing_pedimento_keys, # key strings para actualizar - valid_anexo22_claves, # catálogo de transporte Anexo 22 (pedimento_transport_catalog.code) - valid_patentes, # CustomsBroker.license (tenant/company) - short_name_to_id, # short_name normalizado (upper) -> client id (primera aparición gana) - """ - from api.v1.modules.a76.clients_and_providers.models import ClientProvider - from api.v1.modules.a76.customs_brokers.models import CustomsBroker - from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos - from api.v1.modules.public.reference_data.code_pedimento_regimens.models import ( - CodePedimentoRegimen, - ) - from api.v1.modules.public.reference_data.code_pedimento_regimens.seed import ( - seed as code_pedimento_regimens_seed, - ) - from api.v1.modules.public.reference_data.customs_sections.models import CustomsSection - from api.v1.modules.public.reference_data.pedimento_codes.models import PedimentoCode - from api.v1.modules.public.reference_data.pedimento_regimens.models import RegimenPedimento - from api.v1.modules.public.reference_data.pedimento_transport_catalog.models import ( - PedimentoTransportCatalog, - ) - - valid_client_ids: Set[int] = set() - valid_regimes: Set[str] = set() - valid_pedimento_codes: Set[str] = set() - valid_clave_regimen_tipo: Set[Tuple[str, str, str]] = set() - valid_aduana_seccion: Set[str] = set() - existing_pedimento_keys: Set[str] = set() - valid_anexo22_claves: Set[str] = set() - valid_patentes: Set[str] = set() - short_name_to_id: Dict[str, int] = {} - - for cp in ( - session.query(ClientProvider) - .filter( - ClientProvider.tenant_id == tenant_id, - ClientProvider.company_id == company_id, - ) - .order_by(ClientProvider.id) - .all() - ): - valid_client_ids.add(cp.id) - sn = (cp.short_name or "").strip() - if sn: - key = sn.upper() - if key not in short_name_to_id: - short_name_to_id[key] = cp.id - - for r in session.query(RegimenPedimento).all(): - valid_regimes.add(r.code) - - for pc in session.query(PedimentoCode).all(): - valid_pedimento_codes.add(pc.code) - - for cpr in session.query(CodePedimentoRegimen).all(): - # type_code puede ser None; Clarion usa I/E. Normalizar a mayúsculas para coincidir con CSV. - t = (cpr.type_code or "").strip().upper() - if t: - valid_clave_regimen_tipo.add( - ( - (cpr.pedimento_code or "").strip().upper(), - (cpr.regimen_code or "").strip().upper(), - t, - ) - ) - # Respaldo desde seed: evita desalineación temporal cuando el catálogo en BD - # no ha sido re-sincronizado tras actualizar la semilla. - for ped_code, reg_code, type_code in code_pedimento_regimens_seed: - ped = (ped_code or "").strip().upper() - reg = (reg_code or "").strip().upper() - typ = (type_code or "").strip().upper() - if ped and reg and typ: - valid_clave_regimen_tipo.add((ped, reg, typ)) - - for cs in session.query(CustomsSection).all(): - valid_aduana_seccion.add(cs.customs_code.strip()) - - for tm in session.query(PedimentoTransportCatalog.code).all(): - if (tm[0] or "").strip(): - valid_anexo22_claves.add((tm[0] or "").strip().upper()) - - for cb in ( - session.query(CustomsBroker) - .filter( - CustomsBroker.tenant_id == tenant_id, - CustomsBroker.company_id == company_id, - ) - .all() - ): - if (cb.license or "").strip(): - valid_patentes.add((cb.license or "").strip()) - - for p in ( - session.query( - Pedimentos.year, - Pedimentos.customs_office, - Pedimentos.license, - Pedimentos.pedimento_number, - ) - .filter( - Pedimentos.tenant_id == tenant_id, - Pedimentos.company_id == company_id, - ) - .all() - ): - existing_pedimento_keys.add( - _pedimento_key( - (p.year or "").strip(), - (p.customs_office or "").strip(), - (p.license or "").strip(), - (p.pedimento_number or "").strip(), - ) - ) - - return ( - valid_client_ids, - valid_regimes, - valid_pedimento_codes, - valid_clave_regimen_tipo, - valid_aduana_seccion, - existing_pedimento_keys, - valid_anexo22_claves, - valid_patentes, - short_name_to_id, - ) - - -def pedimento_key_from_parsed( - year: str, customs_office: str, license_val: str, pedimento_number: str -) -> str: - """Construye la clave para comparar con existing_pedimento_keys.""" - return _pedimento_key(year, customs_office, license_val, pedimento_number) diff --git a/backend/api/v1/modules/a76/layouts_csv/pedmientos/common/mappers.py b/backend/api/v1/modules/a76/layouts_csv/pedmientos/common/mappers.py deleted file mode 100644 index e83ebb44..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/pedmientos/common/mappers.py +++ /dev/null @@ -1,190 +0,0 @@ -""" -Mapeo fila CSV → datos para PedimentosCreate. -Soporta layout Clarion (PEDIMENTO, TIPO_OPERACION, CLAVE_PEDIMENTO, etc.) y legacy (AÑO, ADUANA, PATENTE, NUMERO). -""" -from decimal import Decimal, InvalidOperation -from typing import Dict, Any, Optional - -from ..template_config import is_clarion_layout, parse_pedimento_col_a -from ..common.common_validators import parse_date - - -def parse_decimal(val: Any) -> Optional[Decimal]: - if val is None or (isinstance(val, str) and not val.strip()): - return None - try: - return Decimal(str(val).strip().replace(",", ".")) - except (InvalidOperation, ValueError): - return None - - -def resolve_client_id_from_short_name( - valor: str, short_name_to_id: Dict[str, int] -) -> Optional[int]: - """Resuelve short_name (string) a client_id. Vacío → None. Misma normalización que fk_loader (strip + upper).""" - if not valor or not str(valor).strip(): - return None - key = str(valor).strip().upper() - return short_name_to_id.get(key) - - -def _row_to_pedimento_data_clarion( - row_norm: Dict[str, Any], - short_name_to_id: Dict[str, int], - date_format_preference: Optional[str] = None, -) -> Dict[str, Any]: - """Desde layout Clarion: Cols 1-3 (AÑO, PATENTE, NUMERO) o Col A (PEDIMENTO); Col H → customs_office; B,C,D,J y fechas. CLIENTE_SHORT_NAME opcional.""" - año = (row_norm.get("AÑO") or "").strip() - patente = (row_norm.get("PATENTE") or "").strip() - numero = (row_norm.get("NUMERO") or "").strip() - if año and patente and numero: - year = año[:2] - license_val = patente[:4] - pedimento_number = numero[:7] - else: - ped = (row_norm.get("PEDIMENTO") or "").strip() - parsed = parse_pedimento_col_a(ped) - if not parsed: - raise ValueError("PEDIMENTO inválido o vacío") - year, license_val, pedimento_number = parsed - aduana = (row_norm.get("ADUANA_SECCION_CRUCE") or "").strip()[:3] - clave = (row_norm.get("CLAVE_PEDIMENTO") or "").strip()[:2] - regime = (row_norm.get("REGIMEN") or "").strip()[:3] - client_short_name = (row_norm.get("CLIENTE_SHORT_NAME") or "").strip() - client_id = resolve_client_id_from_short_name(client_short_name, short_name_to_id) - - data: Dict[str, Any] = { - "year": year, - "customs_office": aduana, - "license": license_val, - "pedimento_number": pedimento_number, - "pedimento_code": clave, - "regime": regime, - } - if client_id is not None: - data["client_id"] = client_id - - tipo = (row_norm.get("TIPO_OPERACION") or "").strip().upper() - if tipo == "I": - data["operation_type"] = "imp" - elif tipo == "E": - data["operation_type"] = "exp" - - ind_con = (row_norm.get("INDIVIDUAL_CONSOLIDADO") or "").strip().upper() - if ind_con == "IND": - data["pedimento_type"] = "normal" - elif ind_con == "CON": - data["pedimento_type"] = "consolidated" - - status = (row_norm.get("ESTATUS") or "").strip() - if status: - data["status"] = status[:30] - - data["usd_value"] = parse_decimal(row_norm.get("VALOR_USD")) - data["paid_price"] = parse_decimal(row_norm.get("PRECIO_PAGADO")) - data["gross_weight"] = parse_decimal(row_norm.get("PESO_BRUTO")) - data["exchange_rate"] = parse_decimal(row_norm.get("TIPO_CAMBIO")) - obs = (row_norm.get("OBSERVACIONES") or "").strip() - if obs: - data["observations"] = obs - - # Fechas E, F, G → pedimento_dates - # Paridad legacy: Col.G es la fecha de referencia operativa (pago/entrada). - start_str = (row_norm.get("FECHA_INICIO") or "").strip() - end_str = (row_norm.get("FECHA_FINAL") or "").strip() - payment_str = (row_norm.get("FECHA_PAGO") or "").strip() - start_dt = parse_date(start_str, date_format_preference) if start_str else None - end_dt = parse_date(end_str, date_format_preference) if end_str else None - payment_dt = parse_date(payment_str, date_format_preference) if payment_str else None - if start_dt and end_dt: - entry_dt = payment_dt or start_dt - data["pedimento_dates"] = { - "entry_date": entry_dt, - "end_date": end_dt, - "start_date": start_dt, - "payment_date": payment_dt or entry_dt, - } - return data - - -def _row_to_pedimento_data_legacy( - row_norm: Dict[str, Any], short_name_to_id: Dict[str, int] -) -> Dict[str, Any]: - """Layout legacy: AÑO, ADUANA, PATENTE, NUMERO, CLIENTE_SHORT_NAME, CODIGO_PEDIMENTO/CLAVE_PEDIMENTO, REGIMEN.""" - year = (row_norm.get("AÑO") or "").strip()[:2] - customs_office = (row_norm.get("ADUANA_SECCION_CRUCE") or row_norm.get("ADUANA") or "").strip()[:3] - license_val = (row_norm.get("PATENTE") or "").strip()[:4] - pedimento_number = (row_norm.get("NUMERO") or "").strip()[:7] - client_short_name = (row_norm.get("CLIENTE_SHORT_NAME") or "").strip() - client_id = resolve_client_id_from_short_name(client_short_name, short_name_to_id) - pedimento_code = (row_norm.get("CLAVE_PEDIMENTO") or row_norm.get("CODIGO_PEDIMENTO") or "").strip()[:2] - regime = (row_norm.get("REGIMEN") or "").strip()[:3] - - data = { - "year": year, - "customs_office": customs_office, - "license": license_val, - "pedimento_number": pedimento_number, - "client_id": client_id, - "pedimento_code": pedimento_code, - "regime": regime, - } - - op = (row_norm.get("TIPO_OPERACION") or "").strip().lower() - if op in ("imp", "exp"): - data["operation_type"] = op - ptype = (row_norm.get("TIPO_PEDIMENTO") or "").strip().lower() - if ptype in ("normal", "consolidated", "complementary", "automobile"): - data["pedimento_type"] = ptype - status = (row_norm.get("ESTATUS") or "").strip() - if status: - data["status"] = status[:30] - data["usd_value"] = parse_decimal(row_norm.get("VALOR_USD")) - data["paid_price"] = parse_decimal(row_norm.get("PRECIO_PAGADO")) - data["gross_weight"] = parse_decimal(row_norm.get("PESO_BRUTO")) - data["exchange_rate"] = parse_decimal(row_norm.get("TIPO_CAMBIO")) - obs = (row_norm.get("OBSERVACIONES") or "").strip() - if obs: - data["observations"] = obs - return data - - -def row_to_pedimento_data( - row_norm: Dict[str, Any], - short_name_to_id: Dict[str, int], - date_format_preference: Optional[str] = None, -) -> Dict[str, Any]: - """ - Build dict for PedimentosCreate from normalized CSV row. - Usa layout Clarion si existe PEDIMENTO; si no, layout legacy. Cliente por short_name → client_id. - """ - if is_clarion_layout(row_norm): - return _row_to_pedimento_data_clarion( - row_norm, short_name_to_id, date_format_preference - ) - return _row_to_pedimento_data_legacy(row_norm, short_name_to_id) - - -def row_to_pedimento_data_merge_existing( - row_norm: Dict[str, Any], - existing: Dict[str, Any], - short_name_to_id: Dict[str, int], - date_format_preference: Optional[str] = None, -) -> Dict[str, Any]: - """ - Para modo actualizar: campos vacíos en la fila se rellenan con el pedimento existente. - """ - if is_clarion_layout(row_norm): - new_data = _row_to_pedimento_data_clarion( - row_norm, short_name_to_id, date_format_preference - ) - else: - new_data = _row_to_pedimento_data_legacy(row_norm, short_name_to_id) - - for key, val in new_data.items(): - if val is None or (isinstance(val, str) and not val.strip()): - if key in existing and existing[key] is not None: - new_data[key] = existing[key] - if not new_data.get("pedimento_dates") and existing.get("pedimento_dates"): - new_data["pedimento_dates"] = existing["pedimento_dates"] - return new_data diff --git a/backend/api/v1/modules/a76/layouts_csv/pedmientos/pedimentos_csv_hints.py b/backend/api/v1/modules/a76/layouts_csv/pedmientos/pedimentos_csv_hints.py deleted file mode 100644 index 01df9256..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/pedmientos/pedimentos_csv_hints.py +++ /dev/null @@ -1,58 +0,0 @@ -"""Hints cabecera CSV pedimentos (dígitos col Clarion, enums acotados, longitudes típicas).""" -from __future__ import annotations - -from typing import Any, Dict, List, Optional - -from ..common.csv_hint_inject import ( - append_csv_hint, - inject_max_char_hints_into_columns, - merge_enum_hint_before_max, -) - -# Cols 1–3: solo dígitos con longitud fija -_PEDIMENTOS_DIGITS: Dict[str, int] = { - "AÑO": 2, - "PATENTE": 4, - "NUMERO": 7, -} - -# Resto de campos con tope razonable (validadores/modelos variados) -_PEDIMENTOS_MAX_CHARS: Dict[str, int] = { - "TIPO_OPERACION": 8, - "CLAVE_PEDIMENTO": 30, - "REGIMEN": 10, - "FECHA_INICIO": 30, - "FECHA_FINAL": 30, - "FECHA_PAGO": 30, - "ADUANA_SECCION_CRUCE": 120, - "ACUSE_ELECTRONICO": 80, - "INDIVIDUAL_CONSOLIDADO": 10, - "MET_TRANSP_ENTRADA": 30, - "MET_TRANSP_ARRIVO": 30, - "MET_TRANSP_SALIDA": 30, - "PEDIMENTO": 25, - "CLIENTE_SHORT_NAME": 30, - "TIPO_PEDIMENTO": 20, - "OBSERVACIONES": 500, -} - - -def apply_pedimentos_csv_hints(columns: Optional[List[Dict[str, Any]]]) -> None: - if not columns: - return - inject_max_char_hints_into_columns(columns, _PEDIMENTOS_MAX_CHARS) - for item in columns: - canon = item.get("canonical") - if not canon: - continue - nd = _PEDIMENTOS_DIGITS.get(str(canon).strip()) - if nd is not None: - append_csv_hint(item, {"kind": "digits", "n": nd}) - if canon == "TIPO_OPERACION": - merge_enum_hint_before_max(item, ["I", "E"]) - elif canon == "PAGO_IMPUESTO": - merge_enum_hint_before_max(item, ["S", "N"]) - elif canon == "ES_MIXTO": - merge_enum_hint_before_max(item, ["SI", "NO"]) - elif canon == "INDIVIDUAL_CONSOLIDADO": - merge_enum_hint_before_max(item, ["IND", "CON"]) diff --git a/backend/api/v1/modules/a76/layouts_csv/pedmientos/routes.py b/backend/api/v1/modules/a76/layouts_csv/pedmientos/routes.py deleted file mode 100644 index f26420b5..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/pedmientos/routes.py +++ /dev/null @@ -1,194 +0,0 @@ -""" -Rutas de importación CSV para Pedimentos. -Mismo flujo que customs_brokers/imports: upload → scan → status (polling) → commit. -""" -import json -import logging -import os -from uuid import uuid4 - -from fastapi import APIRouter, File, HTTPException, Query, UploadFile, Depends -from sqlalchemy.orm import Session -from typing import Dict, Any, Optional - -from core.celery_app import celery_app -from core.database import get_core_db -from core.security import get_current_user, validate_access_to_resource -from api.v1.modules.core.tasks_tracking import track_and_dispatch - -from ..common.track_commit_dispatch import dispatch_tracked_layouts_csv_commit -from ..common.responses import normalize_commit_status_payload -from .schemas import ImportJobResponse -from .tasks import ( - scan_file, - insert_valid_rows, - JOB_TYPE as PED_JOB_TYPE, - PED_IMPORT_REDIS_TTL, -) -from ..common import storage as common_storage -from ..common.error_csv import download_scan_errors_csv_stream - -router = APIRouter() -logger = logging.getLogger(__name__) - - -def _get_redis(): - import redis - url = os.getenv("VALKEY_URL", os.getenv("REDIS_URL", "redis://valkey:6379/0")) - return redis.Redis.from_url(url, decode_responses=False) - - -@router.post("/upload", response_model=ImportJobResponse) -async def upload_import_file( - file: UploadFile = File(...), - company_id: int = Query(..., description="Company ID"), - actualizar: bool = Query(False, description="Modo actualizar: validación parcial si el pedimento existe"), - dateFormat: Optional[str] = Query(None, description="Formato de fecha: dd/mm/yyyy, mm/dd/yyyy, yyyy-mm-dd"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """ - Fase 1: Subir CSV, guardar en Redis, encolar tarea de escaneo. - """ - try: - tenant_id = validate_access_to_resource(db, company_id, current_user, ["csv_upload.process"]) - except Exception as e: - logger.error(f"Pedimentos import: access validation failed: {e}") - raise HTTPException(status_code=403, detail="Invalid company access") - - if not file.filename or not file.filename.lower().endswith(".csv"): - raise HTTPException(status_code=400, detail="Solo se permiten archivos .csv") - - job_id = str(uuid4()) - contents = await file.read() - - meta_data: Dict[str, Any] = { - "tenant_id": tenant_id, - "company_id": company_id, - "user_id": current_user.get("id"), - "template_id": "pedimentos", - "actualizar": actualizar, - } - if dateFormat: - meta_data["dateFormat"] = dateFormat - - try: - common_storage.store_import_file( - PED_JOB_TYPE, - job_id, - contents, - meta_data, - tenant_id=int(tenant_id), - company_id=company_id, - ttl=PED_IMPORT_REDIS_TTL, - log_label="Pedimentos import", - ) - except common_storage.ImportStoreError as e: - logger.error(f"Pedimentos import: store error: {e}") - raise HTTPException(status_code=500, detail="No se pudo encolar el archivo.") - except Exception as e: - logger.error(f"Pedimentos import: Redis store error: {e}") - raise HTTPException(status_code=500, detail="No se pudo encolar el archivo.") - - track_and_dispatch( - db=db, - task=scan_file, - tenant_id=int(tenant_id), - company_id=company_id, - requested_by_user=current_user.get("preferred_username") - or current_user.get("email") - or current_user.get("sub"), - task_name="pedmientos_scan_file", - task_group="layouts_csv", - task_origin="a76/layouts_csv/pedmientos/upload", - args=[job_id], - task_id=job_id, - ) - - return ImportJobResponse( - job_id=job_id, - status="queued", - message="Archivo subido. Escaneo iniciado.", - ) - - -@router.get("/{job_id}/status") -async def get_import_status(job_id: str): - """ - Polling: estado del escaneo o del commit. - """ - task_result = celery_app.AsyncResult(job_id) - - if task_result.state == "PENDING": - return {"status": "processing", "progress": 0} - if task_result.state == "PROGRESS": - info = task_result.info or {} - return { - "status": "processing", - "progress": info.get("current", 0), - "total": info.get("total", 0), - } - if task_result.state == "SUCCESS": - result = task_result.result - if isinstance(result, dict) and "status" in result: - return normalize_commit_status_payload(result) - return {"status": "finished", "result": result} - - result = getattr(task_result, "result", None) - if isinstance(result, dict) and result.get("status") in ("finished", "warning"): - return normalize_commit_status_payload(result) - - logger.warning("Pedimentos import task %s failed: state=%s", job_id, task_result.state) - err_msg = None - tb = getattr(task_result, "traceback", None) - if tb and isinstance(tb, str): - lines = [l.strip() for l in tb.strip().split("\n") if l.strip()] - if lines: - err_msg = lines[-1] - if not err_msg: - try: - exc = task_result.get(propagate=False) - if exc is not None: - err_msg = str(exc) - except Exception: - pass - if not err_msg and result is not None: - if not isinstance(result, dict): - err_msg = str(result) - elif result.get("error") or result.get("message"): - err_msg = result.get("error") or result.get("message") - return {"status": "failed", "error": err_msg or "Task failed"} - - -@router.post("/{job_id}/commit") -async def commit_import_job( - job_id: str, - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """ - Fase 2: Usuario confirma; se encola la inserción de filas válidas. - """ - _, meta_key, _ = common_storage.storage_keys(PED_JOB_TYPE, job_id) - r = _get_redis() - commit_id = dispatch_tracked_layouts_csv_commit( - db=db, - current_user=current_user, - redis_client=r, - layout_job_id=job_id, - meta_redis_key=meta_key, - celery_task=insert_valid_rows, - task_name="pedmientos_insert_valid_rows", - task_origin="a76/layouts_csv/pedmientos/commit", - args=[job_id], - ) - return { - "status": "committing", - "message": "Inserción iniciada.", - "commit_job_id": commit_id, - } - - -@router.get("/{job_id}/errors/scan-csv") -async def download_scan_errors_csv(job_id: str): - return download_scan_errors_csv_stream(PED_JOB_TYPE, job_id) diff --git a/backend/api/v1/modules/a76/layouts_csv/pedmientos/schemas.py b/backend/api/v1/modules/a76/layouts_csv/pedmientos/schemas.py deleted file mode 100644 index 6fe69385..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/pedmientos/schemas.py +++ /dev/null @@ -1,25 +0,0 @@ -from pydantic import BaseModel -from typing import Optional - - -class ImportJobResponse(BaseModel): - job_id: str - status: str - message: str - - -class CommitRequest(BaseModel): - pass # no body needed for single model - - -class ImportJobStatus(BaseModel): - status: str - job_id: Optional[str] = None - total_rows: Optional[int] = 0 - error_count: Optional[int] = 0 - valid_rows: Optional[int] = 0 - error: Optional[str] = None - inserted: Optional[int] = 0 - skipped_invalid: Optional[int] = 0 - skipped_missing_fk: Optional[int] = 0 - skipped_details: Optional[list] = None diff --git a/backend/api/v1/modules/a76/layouts_csv/pedmientos/tasks.py b/backend/api/v1/modules/a76/layouts_csv/pedmientos/tasks.py deleted file mode 100644 index 91112275..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/pedmientos/tasks.py +++ /dev/null @@ -1,468 +0,0 @@ -""" -Tareas Celery para importación CSV de Pedimentos. -Flujo: scan_file (validación) → insert_valid_rows (commit). -Usa layouts_csv.common (storage, normalize, meta, responses, csv_reader), fk_loader, validators, mappers. -Create/update delegan en PedimentosService; defaults de fechas (pedimento_dates) y merge vs replace -están alineados con el servicio para paridad con el flujo API. -""" -import json -import logging -import os -from datetime import datetime -from typing import Dict, Any, Optional, List - -from core.celery_app import celery_app -from core.database import CoreSessionLocal - -from ..common import storage as common_storage -from ..common import normalize as common_normalize -from ..common import meta as common_meta -from ..common import responses as common_responses -from ..common import csv_reader as common_csv_reader -from .template_config import ( - row_from_template, - is_clarion_layout, - parse_pedimento_col_a, - detect_headers_or_data, -) -from .validators import validate_row_pedimento -from .common.mappers import row_to_pedimento_data, row_to_pedimento_data_merge_existing -from .common.fk_loader import load_pedimentos_fk_sets, pedimento_key_from_parsed - -logger = logging.getLogger(__name__) - -JOB_TYPE = "ped" -TEMPLATE_ID = "pedimentos" - -# Para routes.py -PED_IMPORT_FILE_PREFIX = "ped_import_file:" -PED_IMPORT_META_PREFIX = "ped_import_meta:" -PED_IMPORT_ERROR_LINES_PREFIX = "ped_import_error_lines:" -PED_IMPORT_REDIS_TTL = common_storage.IMPORT_REDIS_TTL - - -def _read_plan_for_pedimentos(fieldnames): - if fieldnames: - return common_csv_reader.CsvReadPlan(header_mode="headerless", fieldnames=fieldnames) - return common_csv_reader.CsvReadPlan(header_mode="header") - - -def _norm_row(row: Dict[str, Any]) -> Dict[str, Any]: - return row_from_template(row, common_normalize.normalize_header, TEMPLATE_ID) - - -def _do_scan(job_id: str, progress_callback: Optional[Any] = None) -> Dict[str, Any]: - file_path = common_storage.ensure_file_from_redis(JOB_TYPE, job_id, "Pedimentos import") - if not file_path: - return {"status": "failed", "error": "Archivo no encontrado (expirado o no subido). Sube de nuevo."} - if os.path.getsize(file_path) == 0: - return {"status": "failed", "error": "El archivo está vacío. Verifica que el CSV tenga contenido."} - common_storage.ensure_meta_from_redis(JOB_TYPE, job_id, file_path, "Pedimentos import") - - error_path = common_storage.error_path_for_job(JOB_TYPE, job_id) - - fieldnames, has_header = detect_headers_or_data( - file_path, - common_normalize.normalize_header, - parse_pedimento_col_a, - ) - read_plan = _read_plan_for_pedimentos(fieldnames) - try: - total_rows = common_csv_reader.count_csv_rows(file_path, has_header=has_header, read_plan=read_plan) - except Exception as e: - return {"status": "failed", "error": str(e)} - - try: - tenant_id, company_id = common_meta.require_tenant_context(file_path) - except ValueError as e: - return {"status": "failed", "error": str(e)} - - meta = common_meta.load_meta(file_path) or {} - actualizar = meta.get("actualizar", False) - date_format_preference = meta.get("dateFormat") or meta.get("date_format") - - try: - with CoreSessionLocal() as session: - ( - valid_client_ids, - valid_regimes, - valid_pedimento_codes, - valid_clave_regimen_tipo, - valid_aduana_seccion, - existing_pedimento_keys, - valid_anexo22_claves, - valid_patentes, - short_name_to_id, - ) = load_pedimentos_fk_sets(session, tenant_id, company_id) - except Exception as e: - logger.error("Pedimentos import: failed to load FK sets: %s", e) - return {"status": "failed", "error": "No se pudo cargar catálogos"} - - error_count = 0 - processed_rows = 0 - errors_detail: List[Dict[str, Any]] = [] - error_lines_list: List[int] = [] - - try: - with open(error_path, "w", encoding="utf-8") as f_err: - for i, row in common_csv_reader.iter_csv_rows_with_plan(file_path, read_plan=read_plan): - if progress_callback: - progress_callback(i, total_rows, error_count) - - row_norm = _norm_row(row) - row_errors = validate_row_pedimento( - row_norm, - i, - short_name_to_id, - valid_regimes, - valid_pedimento_codes, - valid_clave_regimen_tipo, - valid_aduana_seccion, - existing_pedimento_keys, - valid_anexo22_claves, - valid_patentes, - actualizar=actualizar, - raw_row=row, - date_format_preference=date_format_preference, - ) - if row_errors: - has_blocking_error = any(not e.get("warning", False) for e in row_errors) - if has_blocking_error: - error_count += 1 - error_lines_list.append(i) - for e in row_errors: - if not e.get("warning", False): - f_err.write(json.dumps(e) + "\n") - if len(errors_detail) < 500: - for e in row_errors: - if len(errors_detail) >= 500: - break - errors_detail.append({ - "line": e["line"], - "col": e.get("col", ""), - "msg": e.get("msg", ""), - "solution": e.get("solution", ""), - "warning": bool(e.get("warning", False)), - }) - processed_rows += 1 - - if error_lines_list: - common_storage.store_error_lines(JOB_TYPE, job_id, error_lines_list) - except Exception as e: - logger.error("Pedimentos import scan failed: %s", e) - return {"status": "failed", "error": str(e)} - - return common_responses.scan_result(job_id, processed_rows, error_count, errors_detail) - - -@celery_app.task(bind=True) -def scan_file(self, job_id: str, config: str = None): - logger.info("Pedimentos import: starting scan for job %s", job_id) - - def on_progress(current: int, total: int, errors: int) -> None: - self.update_state(state="PROGRESS", meta={"current": current, "total": total, "errors": errors}) - - return _do_scan(job_id, progress_callback=on_progress) - - -def _do_commit(job_id: str) -> Dict[str, Any]: - file_path = common_storage.ensure_file_from_redis(JOB_TYPE, job_id, "Pedimentos import") - if not file_path: - alt_path = common_storage.file_path_for_job(JOB_TYPE, job_id) - if not os.path.exists(alt_path): - return {"status": "failed", "error": "Archivo no encontrado (expirado). Sube y confirma de nuevo."} - file_path = alt_path - common_storage.ensure_meta_from_redis(JOB_TYPE, job_id, file_path, "Pedimentos import") - - error_path = common_storage.error_path_for_job(JOB_TYPE, job_id) - error_lines = common_storage.get_error_lines(JOB_TYPE, job_id, error_path) - - try: - tenant_id, company_id = common_meta.require_tenant_context(file_path) - except ValueError as e: - return {"status": "failed", "error": str(e)} - - meta = common_meta.load_meta(file_path) or {} - actualizar = meta.get("actualizar", False) - date_format_preference = meta.get("dateFormat") or meta.get("date_format") - - try: - with CoreSessionLocal() as session: - ( - valid_client_ids, - valid_regimes, - valid_pedimento_codes, - valid_clave_regimen_tipo, - valid_aduana_seccion, - existing_pedimento_keys, - valid_anexo22_claves, - valid_patentes, - short_name_to_id, - ) = load_pedimentos_fk_sets(session, tenant_id, company_id) - except Exception as e: - logger.error("Pedimentos import: failed to load FK sets: %s", e) - return {"status": "failed", "error": "No se pudo cargar catálogos"} - - from api.v1.modules.a76.pedmientos.dtos.pedimentos import ( - PedimentosCreate, - PedimentosUpdate, - ) - from api.v1.modules.a76.pedmientos.dtos.pedimento_dates import PedimentoDatesCreate - from api.v1.modules.a76.pedmientos.services.pedimentos import PedimentosService - from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos - - inserted_count = 0 - updated_count = 0 - skipped_invalid = 0 - skipped_missing_fk = 0 - skipped_duplicate = 0 - skipped_details: List[Dict[str, Any]] = [] - meta_path = common_meta.get_meta_path(file_path) - - fieldnames_commit, _ = detect_headers_or_data( - file_path, - common_normalize.normalize_header, - parse_pedimento_col_a, - ) - read_plan_commit = _read_plan_for_pedimentos(fieldnames_commit) - - def _key_from_row(r: Dict[str, Any]) -> Optional[str]: - if is_clarion_layout(r): - año = (r.get("AÑO") or "").strip() - patente = (r.get("PATENTE") or "").strip() - numero = (r.get("NUMERO") or "").strip() - if año and patente and numero: - adu = (r.get("ADUANA_SECCION_CRUCE") or "").strip()[:3] - return pedimento_key_from_parsed(año[:2], adu, patente[:4], numero[:7]) - parsed = parse_pedimento_col_a((r.get("PEDIMENTO") or "").strip()) - if not parsed: - return None - y, lic, num = parsed - adu = (r.get("ADUANA_SECCION_CRUCE") or "").strip()[:3] - return pedimento_key_from_parsed(y, adu, lic, num) - y = (r.get("AÑO") or "").strip()[:2] - adu = (r.get("ADUANA_SECCION_CRUCE") or r.get("ADUANA") or "").strip()[:3] - lic = (r.get("PATENTE") or "").strip()[:4] - num = (r.get("NUMERO") or "").strip()[:7] - return pedimento_key_from_parsed(y, adu, lic, num) - - try: - with CoreSessionLocal() as session: - for i, row in common_csv_reader.iter_csv_rows_with_plan(file_path, read_plan=read_plan_commit): - if i in error_lines: - continue - - row_norm = _norm_row(row) - row_errors = validate_row_pedimento( - row_norm, - i, - short_name_to_id, - valid_regimes, - valid_pedimento_codes, - valid_clave_regimen_tipo, - valid_aduana_seccion, - existing_pedimento_keys, - valid_anexo22_claves, - valid_patentes, - actualizar=actualizar, - raw_row=row, - date_format_preference=date_format_preference, - ) - if row_errors and any(not e.get("warning", False) for e in row_errors): - skipped_invalid += 1 - blocking_errors = [e for e in row_errors if not e.get("warning", False)] - for blocking in blocking_errors: - skipped_details.append({ - "line": i, - "reason": f"{blocking.get('col', '')}: {blocking.get('msg', '')}", - "solution": blocking.get("solution", ""), - }) - continue - - key = _key_from_row(row_norm) - try: - # Si el pedimento ya existe: actualizar (merge) o reemplazar (crear). Si no existe: crear. - if key and key in existing_pedimento_keys: - existing = ( - session.query(Pedimentos) - .filter( - Pedimentos.tenant_id == tenant_id, - Pedimentos.company_id == company_id, - Pedimentos.year == key.split("|")[0], - Pedimentos.customs_office == key.split("|")[1], - Pedimentos.license == key.split("|")[2], - Pedimentos.pedimento_number == key.split("|")[3], - ) - .first() - ) - if existing: - if actualizar: - # Modo actualizar: merge con existente - existing_dict = { - "year": existing.year, - "customs_office": existing.customs_office, - "license": existing.license, - "pedimento_number": existing.pedimento_number, - "client_id": existing.client_id, - "pedimento_code": existing.pedimento_code, - "regime": existing.regime, - "operation_type": existing.operation_type, - "pedimento_type": existing.pedimento_type, - "status": existing.status, - "usd_value": existing.usd_value, - "paid_price": existing.paid_price, - "gross_weight": existing.gross_weight, - "exchange_rate": existing.exchange_rate, - "observations": existing.observations, - } - if existing.pedimento_dates: - existing_dict["pedimento_dates"] = { - "entry_date": existing.pedimento_dates.entry_date, - "end_date": existing.pedimento_dates.end_date, - "start_date": getattr( - existing.pedimento_dates, "start_date", existing.pedimento_dates.entry_date - ), - "payment_date": getattr( - existing.pedimento_dates, "payment_date", existing.pedimento_dates.entry_date - ), - } - data = row_to_pedimento_data_merge_existing( - row_norm, - existing_dict, - short_name_to_id, - date_format_preference, - ) - else: - # Modo crear: reemplazar con datos del CSV - data = row_to_pedimento_data( - row_norm, short_name_to_id, date_format_preference - ) - if "pedimento_dates" not in data or data.get("pedimento_dates") is None: - data["pedimento_dates"] = PedimentoDatesCreate( - entry_date=datetime.now(), - end_date=datetime.now(), - ) - update_data = {k: v for k, v in data.items() if k != "pedimento_dates"} - if data.get("pedimento_dates"): - update_data["pedimento_dates"] = PedimentoDatesCreate( - **data["pedimento_dates"] - ) - update_schema = PedimentosUpdate(**update_data) - PedimentosService.update( - session, existing.id, tenant_id, update_schema, company_id - ) - updated_count += 1 - else: - skipped_invalid += 1 - skipped_details.append( - { - "line": i, - "reason": "Pedimento no encontrado", - "solution": "Verifica que el pedimento exista en el sistema usando las columnas PEDIMENTO (o AÑO/PATENTE/NUMERO) y ADUANA_SECCION_CRUCE; si aplica, cambia a modo creación.", - } - ) - else: - # No existe: crear (ambos modos) - data = row_to_pedimento_data( - row_norm, short_name_to_id, date_format_preference - ) - if "pedimento_dates" not in data or data.get("pedimento_dates") is None: - data["pedimento_dates"] = PedimentoDatesCreate( - entry_date=datetime.now(), - end_date=datetime.now(), - ) - create_data = PedimentosCreate(**data) - PedimentosService.create(session, create_data, tenant_id, company_id) - inserted_count += 1 - except ValueError as ve: - if "Ya existe" in str(ve) or "duplicate" in str(ve).lower(): - skipped_duplicate += 1 - skipped_details.append( - { - "line": i, - "reason": str(ve), - "solution": "Si deseas actualizar registros existentes, activa la opción de actualizar en la importación; si no, corrige el pedimento para evitar duplicados.", - } - ) - else: - skipped_invalid += 1 - skipped_details.append( - { - "line": i, - "reason": str(ve), - "solution": "Revisa la línea del CSV y el valor que falló la validación; corrige el dato y vuelve a intentar.", - } - ) - except Exception as e: - logger.warning("Pedimentos import line %s: %s", i, e) - skipped_invalid += 1 - skipped_details.append( - { - "line": i, - "reason": str(e), - "solution": "Revisa la línea del CSV y los catálogos/fechas relacionados para resolver el error.", - } - ) - - except Exception as e: - logger.exception("Pedimentos import task failed") - return {"status": "failed", "error": str(e)} - - common_storage.cleanup_import_job( - JOB_TYPE, job_id, - file_path=file_path, - error_path=error_path, - meta_path=meta_path, - ) - - total_skipped = skipped_invalid + skipped_missing_fk + skipped_duplicate - critical_reference_gaps = skipped_invalid + skipped_missing_fk - reference_state_ready = critical_reference_gaps == 0 - if inserted_count == 0 and updated_count == 0 and total_skipped > 0: - reasons = "; ".join( - f"Línea {d.get('line', '?')}: {d.get('reason', '')}" for d in skipped_details[:5] - ) - if len(skipped_details) > 5: - reasons += f" (+{len(skipped_details) - 5} más)" - return { - "status": "warning", - "inserted": 0, - "updated": 0, - "skipped_invalid": skipped_invalid, - "skipped_missing_fk": skipped_missing_fk, - "skipped_duplicate": skipped_duplicate, - "skipped_details": skipped_details, - "critical_reference_gaps": critical_reference_gaps, - "reference_state_ready": reference_state_ready, - "message": f"No se insertaron registros. {total_skipped} rechazados. Motivos: {reasons}", - } - if inserted_count == 0 and updated_count == 0: - return { - "status": "failed", - "error": "No hay registros válidos en el archivo CSV", - "inserted": 0, - "updated": 0, - "skipped_invalid": skipped_invalid, - "skipped_missing_fk": skipped_missing_fk, - "skipped_duplicate": skipped_duplicate, - "skipped_details": skipped_details, - "critical_reference_gaps": critical_reference_gaps, - "reference_state_ready": reference_state_ready, - } - return { - "status": "finished", - "inserted": inserted_count, - "updated": updated_count, - "skipped_invalid": skipped_invalid, - "skipped_missing_fk": skipped_missing_fk, - "skipped_duplicate": skipped_duplicate, - "skipped_details": skipped_details, - "critical_reference_gaps": critical_reference_gaps, - "reference_state_ready": reference_state_ready, - } - - -@celery_app.task(bind=True) -def insert_valid_rows(self, job_id: str): - logger.info("Pedimentos import: starting commit for job %s", job_id) - return _do_commit(job_id) diff --git a/backend/api/v1/modules/a76/layouts_csv/pedmientos/template_config.py b/backend/api/v1/modules/a76/layouts_csv/pedmientos/template_config.py deleted file mode 100644 index 7c654b00..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/pedmientos/template_config.py +++ /dev/null @@ -1,251 +0,0 @@ -""" -Configuración de plantilla CSV para Pedimentos (EstructuraCatPedimentos.xls). -Layout Clarion: Cols 1-3 = AÑO (2), PATENTE (4), NUMERO (7); luego TIPO, CLAVE PEDIMENTO, REGIMEN, -fechas, ADUANA Y SECCION CRUCE, etc. Compatibilidad: PEDIMENTO (##-####-#######) y layout legacy. -""" -import csv -import io -from typing import Dict, List, Any, Optional, Tuple - -# Convierte valor de celda a str; si es lista (p. ej. CSV con columnas duplicadas), toma el primer elemento. -# Re-exportado desde common para uso en validators; ver layouts_csv.common.cell_value. -from ..common.cell_value import cell_to_str as _cell_to_str -from ..common.inject_csv_labels_en import inject_en_labels_into_template_columns -from ..common.template_locale import merge_download_header_into_lookup, merge_labels_into_lookup -from .pedimentos_csv_hints import apply_pedimentos_csv_hints -from ..common import csv_reader as common_csv_reader - - -# Longitudes para validación (sin afectar modelos) -AÑO_LEN = 2 -PATENTE_LEN = 4 -NUMERO_LEN = 7 - -# Columnas Clarion (canónicos) + aliases exactos de la plantilla y legacy -TEMPLATE_COLUMNS: Dict[str, List[Dict[str, Any]]] = { - "pedimentos": [ - # Cols 1-3: AÑO, PATENTE, NUMERO (reemplazan Col A única PEDIMENTO) - {"canonical": "AÑO", "aliases": ["YEAR", "ANIO"]}, - {"canonical": "PATENTE", "aliases": ["LICENCIA", "LICENSE", "LIC"]}, - {"canonical": "NUMERO", "aliases": ["PEDIMENTO_NUMBER", "PEDIMENTO NUMBER"]}, - # Col B - {"canonical": "TIPO_OPERACION", "aliases": ["TIPO MOV(I=Importación,E=Exportación)", "TIPO MOV(I=Impotación,E=Expotación)", "TIPO MOV", "TIPO", "OPERATION_TYPE", "OPERACION"]}, - # Col C - {"canonical": "CLAVE_PEDIMENTO", "aliases": ["CLAVE PEDIMENTO", "CODIGO_PEDIMENTO", "PEDIMENTO_CODE", "CODIGO"]}, - # Col D - {"canonical": "REGIMEN", "aliases": ["REGIME", "CLAVE RÉGIMEN"]}, - # Col E, F, G - {"canonical": "FECHA_INICIO", "aliases": ["FECHA INICIO", "FECHA INICIAL"]}, - {"canonical": "FECHA_FINAL", "aliases": ["FECHA FINAL", "FECHA FIN"]}, - { - "canonical": "FECHA_PAGO", - "aliases": [ - "FECHA DE PAGO", - "FECHA PAGO", - "FECHA_ENTRADA", - "FECHA ENTRADA", - "ENTRY_DATE", - "ENTRY DATE", - ], - }, - # Col H - {"canonical": "ADUANA_SECCION_CRUCE", "aliases": ["ADUANA Y SECCION DE CRUCE", "ADUANA Y SECCION CRUCE", "ADUANA", "CUSTOMS_OFFICE", "CUSTOMS OFFICE"]}, - # Col I - {"canonical": "ACUSE_ELECTRONICO", "aliases": ["ACUSE ELECTRONICO", "ACUSE ELECTRÓNICO"]}, - # Col J - {"canonical": "INDIVIDUAL_CONSOLIDADO", "aliases": ["INDIVIDUAL o CONSOLIDADO (IND,CON)", "INDIVIDUAL o CONSOLIDADO", "IND/CON", "INDIVIDUAL CONSOLIDADO"]}, - # Col K, L, M - {"canonical": "MET_TRANSP_ENTRADA", "aliases": ["MET TRANS ENTRADA", "METODO TRANSP ENTRADA"]}, - {"canonical": "MET_TRANSP_ARRIVO", "aliases": ["MET TRANS ARRIVO", "METODO TRANSP ARRIVO"]}, - {"canonical": "MET_TRANSP_SALIDA", "aliases": ["MET TRANS SALIDA", "METODO TRANSP SALIDA"]}, - # Col N (desfase) y resto de columnas de la plantilla - {"canonical": "IEPS", "aliases": []}, - {"canonical": "DTA", "aliases": []}, - {"canonical": "CNT", "aliases": []}, - {"canonical": "PREVALIDACION", "aliases": []}, - {"canonical": "MONTO_TIGIE", "aliases": ["MONTO TIGIE"]}, - {"canonical": "PAGO_IMPUESTO", "aliases": ["PAGO IMPUESTO? (S/N)"]}, - {"canonical": "ES_MIXTO", "aliases": ["ES MIXTO (SI/NO)"]}, - {"canonical": "OBS_RECTIFICA", "aliases": ["OBS RECTIFICA"]}, - {"canonical": "OPCION_DESTINO", "aliases": ["OPCION DESTINO(Interior del Pais/Región Fronteriza/Franja Fronteriza)", "OPCION DESTINO"]}, - {"canonical": "VALOR_IVA", "aliases": ["VALOR IVA"]}, - {"canonical": "VALOR_ME", "aliases": ["VALOR ME"]}, - {"canonical": "VALOR_ADUANAS", "aliases": ["VALOR ADUANAS"]}, - {"canonical": "FLETE", "aliases": []}, - {"canonical": "VALOR_SEGUROS", "aliases": ["VALOR SEGUROS"]}, - {"canonical": "SEGUROS", "aliases": []}, - {"canonical": "EMBALAJES", "aliases": []}, - {"canonical": "OTROS_INCREMENTABLES", "aliases": ["OTROS INCREMENTABLES"]}, - {"canonical": "ESTATUS", "aliases": ["ESTATUS (ABIERTO/CERRADO)", "STATUS", "ESTADO"]}, - {"canonical": "PERSONA_REV", "aliases": ["PERSONA REV"]}, - {"canonical": "FECHA_CIERRE", "aliases": ["FECHA CIERRE"]}, - {"canonical": "FECHA_REVISION", "aliases": ["FECHA REVISION"]}, - {"canonical": "FECHA_AUTORIZACION", "aliases": ["FECHA AUTORIZACION"]}, - {"canonical": "FECHA_RECIBIDO", "aliases": ["FECHA RECIBIDO"]}, - {"canonical": "REPRESENTANTE_AA", "aliases": ["REPRESENTANTE AA"]}, - {"canonical": "CLAVE_DEST_ORIGEN", "aliases": ["CLAVE DEST ORIGEN"]}, - {"canonical": "FECHA_ENTRADA_RECINTO", "aliases": ["FECHA ENTRADA RECINTO"]}, - {"canonical": "FECHA_EXTRACCION_RECINTO", "aliases": ["FECHA EXTRACCION RECINTO"]}, - {"canonical": "ERRORES", "aliases": []}, - {"canonical": "FORMA_PAGO_DTA", "aliases": ["FORMA PAGO DTA"]}, - {"canonical": "FORMA_PAGO_IGI", "aliases": ["FORMA PAGO IGI"]}, - {"canonical": "FORMA_PAGO_PREVAL", "aliases": ["FORMA PAGO PREVAL"]}, - {"canonical": "FORMA_PAGO_IVA", "aliases": ["FORMA PAGO IVA"]}, - {"canonical": "RECARGOS", "aliases": []}, - {"canonical": "MULTAS", "aliases": []}, - {"canonical": "IVA_DE_PREV", "aliases": ["IVA DE PREV"]}, - {"canonical": "CUOTAS_COMPENSATORIAS", "aliases": ["CUOTAS CONPENSATORIAS"]}, - {"canonical": "IDENTIFICADORES", "aliases": []}, - {"canonical": "IEPS_2", "aliases": ["IEPS 2"]}, - {"canonical": "FORMA_PAGO_IEPS_2", "aliases": ["FORMA DE PAGO IEPS 2"]}, - {"canonical": "DTA_2", "aliases": ["DTA 2"]}, - {"canonical": "FORMA_PAGO_DTA_2", "aliases": ["FORMA DE PAGO DTA 2"]}, - {"canonical": "IVA_2", "aliases": ["IVA 2"]}, - {"canonical": "FORMA_PAGO_IVA_2", "aliases": ["FORMA DE PAGO IVA 2"]}, - {"canonical": "IGI_2", "aliases": ["IGI 2"]}, - {"canonical": "FORMA_PAGO_IGI_2", "aliases": ["FORMA DE PAGO IGI 2"]}, - {"canonical": "FORMA_PAGO_PREVALIDACION_2", "aliases": ["FORMA DE PAGO PREVALIDACION 2"]}, - {"canonical": "CNT_2", "aliases": ["CNT 2"]}, - {"canonical": "FORMA_PAGO_CNT_2", "aliases": ["FORMA DE PAGO CNT 2"]}, - # Legacy / compatibilidad (PEDIMENTO una columna ##-####-#######, y otros) - {"canonical": "PEDIMENTO", "aliases": ["NUMERO DE PEDIMENTO (##-####-######)", "NUMERO DE PEDIMENTO", "NUMERO PEDIMENTO", "PED"]}, - {"canonical": "CLIENTE_SHORT_NAME", "aliases": ["CLIENTE", "SHORT_NAME", "CLIENTE_ID", "CLIENT_ID", "ID CLIENTE"]}, - {"canonical": "TIPO_PEDIMENTO", "aliases": ["PEDIMENTO_TYPE", "TIPO PED"]}, - {"canonical": "VALOR_USD", "aliases": ["USD_VALUE", "VALOR USD", "USD"]}, - {"canonical": "PRECIO_PAGADO", "aliases": ["PAID_PRICE", "PRECIO PAGADO"]}, - {"canonical": "PESO_BRUTO", "aliases": ["GROSS_WEIGHT", "PESO BRUTO", "PESO"]}, - {"canonical": "TIPO_CAMBIO", "aliases": ["EXCHANGE_RATE", "TIPO CAMBIO", "CAMBIO"]}, - {"canonical": "OBSERVACIONES", "aliases": ["OBSERVATIONS", "OBS", "NOTAS"]}, - ], -} - -inject_en_labels_into_template_columns(TEMPLATE_COLUMNS) - -apply_pedimentos_csv_hints(TEMPLATE_COLUMNS.get("pedimentos")) - -# Orden de columnas para CSV sin cabecera (primera fila = datos). Usado por detect_headers_or_data. -PEDIMENTOS_TEMPLATE_ORDER: List[str] = [ - item["canonical"] for item in TEMPLATE_COLUMNS["pedimentos"] -] - - -def _first_row_looks_like_three_cols_data(first_row: List[str]) -> bool: - """True si las primeras 3 celdas son AÑO (2 dígitos), PATENTE (4), NUMERO (7).""" - if not first_row or len(first_row) < 3: - return False - c0 = (first_row[0] or "").strip() - c1 = (first_row[1] or "").strip() - c2 = (first_row[2] or "").strip() - return ( - len(c0) <= AÑO_LEN and c0.isdigit() - and len(c1) <= PATENTE_LEN and c1.isdigit() - and len(c2) <= NUMERO_LEN and c2.isdigit() - ) - - -def detect_headers_or_data( - file_path: str, - normalize_header_fn, - parse_pedimento_fn, - encoding: str = "utf-8-sig", -) -> Tuple[Optional[List[str]], bool]: - """ - Lee la primera línea del CSV y decide si es cabecera o dato. - - Si las primeras 3 celdas son 2, 4 y 7 dígitos (AÑO, PATENTE, NUMERO) -> has_header=False. - - Si la primera celda tiene formato ##-####-####### (compatibilidad) -> has_header=False. - - Si no -> has_header=True. Devuelve (fieldnames, has_header). - """ - try: - sample, _ = common_csv_reader.read_text_sample( - file_path, - requested_encoding=encoding, - sample_chars=2048, - ) - except Exception: - try: - sample, _ = common_csv_reader.read_text_sample( - file_path, - requested_encoding="auto", - sample_chars=2048, - ) - except Exception: - return None, True - lines = sample.splitlines() - if not lines: - return None, True - dialect = common_csv_reader.detect_csv_dialect(sample, delimiters=",;\t") - reader = csv.reader(io.StringIO(lines[0]), dialect=dialect) - first_row = next(reader, None) - if not first_row: - return None, True - if _first_row_looks_like_three_cols_data(first_row): - return list(PEDIMENTOS_TEMPLATE_ORDER), False - first_cell = (first_row[0] or "").strip() - if parse_pedimento_fn(first_cell) is not None: - return list(PEDIMENTOS_TEMPLATE_ORDER), False - return None, True - - -def build_normalized_lookup(normalize_header_fn, template_id: str = "pedimentos") -> Dict[str, str]: - """normalized_header -> canonical_name para plantilla pedimentos.""" - cols = TEMPLATE_COLUMNS.get(template_id) - if not cols: - return {} - lookup: Dict[str, str] = {} - for item in cols: - canonical = item["canonical"] - lookup[normalize_header_fn(canonical)] = canonical - for alias in item.get("aliases") or []: - lookup[normalize_header_fn(alias)] = canonical - merge_labels_into_lookup(item, canonical, normalize_header_fn, lookup) - merge_download_header_into_lookup(item, canonical, normalize_header_fn, lookup) - return lookup - - -def row_from_template(row: Dict[str, Any], normalize_header_fn, template_id: str = "pedimentos") -> Dict[str, Any]: - """Fila CSV con solo columnas de la plantilla, en nombres canónicos. Valores siempre str (listas convertidas).""" - lookup = build_normalized_lookup(normalize_header_fn, template_id) - if not lookup: - return {normalize_header_fn(k): _cell_to_str(v) for k, v in row.items()} - out: Dict[str, Any] = {} - for csv_header, value in row.items(): - key_norm = normalize_header_fn(csv_header) - if key_norm in lookup: - out[lookup[key_norm]] = _cell_to_str(value) - return out - - -# Formato Clarion Col A: ##-####-####### (15 chars, guiones en posiciones 3 y 8, 1-based) -PEDIMENTO_LEN = 15 -PEDIMENTO_DASH_POSITIONS = (2, 7) # 0-based: pos 2 y 7 deben ser '-' - - -def parse_pedimento_col_a(pedimento_str: str) -> Optional[Tuple[str, str, str]]: - """ - Parsea Col A (PEDIMENTO) formato ##-####-#######. - Retorna (year_2, license_4, pedimento_number_7) o None si formato inválido. - """ - if not pedimento_str or not isinstance(pedimento_str, str): - return None - s = (pedimento_str or "").strip() - if len(s) != PEDIMENTO_LEN: - return None - if s[PEDIMENTO_DASH_POSITIONS[0]] != "-" or s[PEDIMENTO_DASH_POSITIONS[1]] != "-": - return None - year = s[0:2] - license_val = s[3:7] - pedimento_number = s[8:15] - if not year.isdigit() or not license_val.isdigit() or not pedimento_number.isdigit(): - return None - return (year, license_val, pedimento_number) - - -def is_clarion_layout(row_norm: Dict[str, Any]) -> bool: - """True si la fila tiene las 3 columnas AÑO, PATENTE, NUMERO con valor, o PEDIMENTO con valor (compatibilidad).""" - año = (row_norm.get("AÑO") or "").strip() - patente = (row_norm.get("PATENTE") or "").strip() - numero = (row_norm.get("NUMERO") or "").strip() - if año and patente and numero: - return True - ped = (row_norm.get("PEDIMENTO") or "").strip() - return bool(ped) diff --git a/backend/api/v1/modules/a76/layouts_csv/pedmientos/validators/__init__.py b/backend/api/v1/modules/a76/layouts_csv/pedmientos/validators/__init__.py deleted file mode 100644 index 6ea80cef..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/pedmientos/validators/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .create import validate_row_pedimento - -__all__ = ["validate_row_pedimento"] diff --git a/backend/api/v1/modules/a76/layouts_csv/pedmientos/validators/common.py b/backend/api/v1/modules/a76/layouts_csv/pedmientos/validators/common.py deleted file mode 100644 index 9df12c92..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/pedmientos/validators/common.py +++ /dev/null @@ -1,521 +0,0 @@ -""" -Validaciones comunes de fila para import CSV de pedimentos. -Paridad Clarion: desfase (Col N), Cols 1-3 (AÑO, PATENTE, NUMERO) u Col A PEDIMENTO, obligatorios B–H (VALIDA_TODA), -VALIDACIONES_PEDIMENTO (Tipo I/E, Clave+Régimen+Tipo, Aduana, Patente, IND/CON, Método transporte). -""" -from typing import Dict, Any, Optional, Set, Tuple, List - -from ..template_config import ( - AÑO_LEN, - PATENTE_LEN, - NUMERO_LEN, - PEDIMENTO_LEN, - PEDIMENTO_DASH_POSITIONS, - parse_pedimento_col_a, - _cell_to_str, -) -from ..common.common_validators import ( - check_required_max, - check_in_set, - check_int_in_set, - check_optional_int_in_set, - check_optional_short_name, - check_optional_max_length, - check_optional_decimal, - check_optional_in_set, - parse_date, - DATE_FORMAT_PREFERENCE_MAP, -) - -# --- Desfase -MSG_DESFASE = "Advertencia: Podría existir un desfase en esta línea." -MSG_DESFASE_SOLUCION = "Revisar esta línea del archivo CSV y verificar cada campo esté en la posición correcta." - -# --- Col A --- -MSG_PEDIMENTO_VACIO = ( - "Error: (Col. A) La columna de Pedimento está vacío y no se pueden hacer las validaciones. " - "Capturar en la Columna A un Pedimento nuevo o uno ya existente al cual desee actualizar campos" -) -MSG_PEDIMENTO_LONGITUD = ( - "Error: (Col. A) La columna de Pedimento no cumple con la longitud o sintaxis correcta Ej. (XX-XXXX-XXXXXXX)" -) -MSG_PEDIMENTO_FORMATO = "Error: (Col. A) El Formato del Pedimento: {val} es incorrecto." -MSG_PEDIMENTO_FORMATO_SOLUCION = "Capturar en la columna A el campo Pedimento con este formato ##-####-#######." - -# --- Cols 1-3 (AÑO, PATENTE, NUMERO) --- -MSG_ANIO_PATENTE_NUMERO = "Error: (Cols 1-3) AÑO, PATENTE y NUMERO son obligatorios (AÑO 2 dígitos, PATENTE 4, NUMERO 7)." -MSG_PATENTE_NO_EXISTE = "Error: (Patente) La patente {val} no está dada de alta en el catálogo de agentes aduanales." - -# --- Obligatorios B–H --- -MSG_OBLIGATORIOS = "Existen campos vacios que son obligatorios, es la {campos}." -MSG_OBLIGATORIOS_SOLUCION = "Revisar la línea del archivo y capturar los campos con la información correcta." - -# --- Tipo B --- -MSG_TIPO_INVALIDO = "Error: (Col. B) El Tipo de Operación: {val} no es valido." -MSG_TIPO_SOLUCION = "Capturar una opción valida, que seria I para Importación, E para Exportación." - -# --- Combinación B+C+D --- -MSG_COMBINACION = "Error, (Col. B, C y D) La combinación de una operación de {b} con clave de pedimento {c} y régimen {d} no es posible." -MSG_COMBINACION_SOLUCION = "Las posibles combinaciones son {comb}." - -# --- Aduana H --- -MSG_ADUANA_NO_EXISTE = "Error: (Col. H) La aduana y sección: {val} no existe." -MSG_ADUANA_SOLUCION = "Revisar que sea correcta esta aduana y seccion de cruce, de ser así actualice los catálogos fijos." - -# --- Col J IND/CON --- -MSG_IND_CON_INVALIDO = "Error: (Col. J) La opción de Individual/Consolidado: {val} no es valido." -MSG_IND_CON_SOLUCION = "Capturar una opción valida: IND para Individual, CON para Consolidado: o dejar el campo vacio y automaticamente se asigna Consolidado." - -# --- Transporte K/L/M --- -MSG_TRANSP_NO_EXISTE = "Error: (Col. {col}) El Metodo de Transporte: {val} no existe en el Catálogo del Anexo 22 Apendice 3." -MSG_TRANSP_SOLUCION = "Revisar si esta asignado Correctamente." - -# --- Fechas --- -FECHA_MAX_LEN = 10 - - -def validate_row_desfase_pedimento( - raw_row: Dict[str, Any], line_num: int -) -> Optional[Dict[str, Any]]: - """Si la fila tiene al menos 16 columnas y la 16ª (Col N, IEPS/desfase) tiene valor, error de desfase. Orden: AÑO,PATENTE,NUMERO,TIPO,...,IEPS en índice 15.""" - values_ordered = list(raw_row.values()) if raw_row else [] - desfase_idx = 15 # IEPS en PEDIMENTOS_TEMPLATE_ORDER (tras AÑO,PATENTE,NUMERO + 12 columnas más) - if len(values_ordered) >= (desfase_idx + 1): - cell = _cell_to_str(values_ordered[desfase_idx]) - if cell.strip(): - return { - "line": line_num, - "col": "", - "msg": f"{MSG_DESFASE} {MSG_DESFASE_SOLUCION}", - "warning": True, - } - return None - - -def validate_row_pedimento_required_col_a( - row: Dict[str, Any], line_num: int -) -> Optional[Dict[str, Any]]: - """Col A (PEDIMENTO) obligatorio y longitud 15.""" - val = (row.get("PEDIMENTO") or "").strip() - if not val: - return {"line": line_num, "col": "PEDIMENTO", "msg": MSG_PEDIMENTO_VACIO} - if len(val) != PEDIMENTO_LEN: - return { - "line": line_num, - "col": "PEDIMENTO", - "msg": f"{MSG_PEDIMENTO_LONGITUD} {MSG_PEDIMENTO_FORMATO_SOLUCION}", - } - return None - - -def validate_row_pedimento_format( - row: Dict[str, Any], line_num: int -) -> Optional[Dict[str, Any]]: - """Formato ##-####-#######: guiones en posiciones 3 y 8 (1-based). Solo aplica cuando la fila trae PEDIMENTO.""" - val = (row.get("PEDIMENTO") or "").strip() - if not val or len(val) != PEDIMENTO_LEN: - return None - if val[PEDIMENTO_DASH_POSITIONS[0]] != "-" or val[PEDIMENTO_DASH_POSITIONS[1]] != "-": - return { - "line": line_num, - "col": "PEDIMENTO", - "msg": f"{MSG_PEDIMENTO_FORMATO.format(val=val)} {MSG_PEDIMENTO_FORMATO_SOLUCION}", - } - return None - - -def validate_row_pedimento_required_three_cols( - row: Dict[str, Any], line_num: int -) -> Optional[Dict[str, Any]]: - """Cols 1-3: AÑO (2 dígitos), PATENTE (4), NUMERO (7) obligatorios y numéricos.""" - cols_missing = [] - año = (row.get("AÑO") or "").strip() - patente = (row.get("PATENTE") or "").strip() - numero = (row.get("NUMERO") or "").strip() - if not año: - cols_missing.append("Col.1) AÑO") - elif len(año) > AÑO_LEN or not año.isdigit(): - return {"line": line_num, "col": "AÑO", "msg": "AÑO debe ser 2 dígitos numéricos."} - if not patente: - cols_missing.append("Col.2) PATENTE") - elif len(patente) > PATENTE_LEN or not patente.isdigit(): - return {"line": line_num, "col": "PATENTE", "msg": "PATENTE debe ser 4 dígitos numéricos."} - if not numero: - cols_missing.append("Col.3) NUMERO") - elif len(numero) > NUMERO_LEN or not numero.isdigit(): - return {"line": line_num, "col": "NUMERO", "msg": "NUMERO debe ser 7 dígitos numéricos."} - if cols_missing: - campos = ", ".join(cols_missing) - return { - "line": line_num, - "col": "", - "msg": f"{MSG_ANIO_PATENTE_NUMERO} Faltan: {campos}.", - } - return None - - -def validate_row_patente( - row: Dict[str, Any], - line_num: int, - valid_patentes: Set[str], -) -> Optional[Dict[str, Any]]: - """Si la fila trae PATENTE o PEDIMENTO (parseado), valida que la patente esté en el catálogo de agentes aduanales.""" - patente_val = (row.get("PATENTE") or "").strip() - if not patente_val: - ped = (row.get("PEDIMENTO") or "").strip() - parsed = parse_pedimento_col_a(ped) if ped else None - if parsed: - _, patente_val, _ = parsed - if not patente_val: - return None - if valid_patentes and patente_val not in valid_patentes: - return { - "line": line_num, - "col": "PATENTE", - "msg": MSG_PATENTE_NO_EXISTE.format(val=patente_val), - } - return None - - -def validate_row_pedimento_required_full( - row: Dict[str, Any], line_num: int -) -> Optional[Dict[str, Any]]: - """Obligatorios cuando no es actualizar: TIPO (B), CLAVE_PEDIMENTO (C), REGIMEN (D), FECHA_INICIO (E), FECHA_FINAL (F), FECHA_PAGO/ENTRADA (G), ADUANA_SECCION_CRUCE (H), IND/CON (J).""" - cols_missing = [] - col_labels = [ - ("TIPO_OPERACION", "Col.B) Tipo Operación"), - ("CLAVE_PEDIMENTO", "Col.C) Clave Pedimento"), - ("REGIMEN", "Col.D) Clave Régimen"), - ("FECHA_INICIO", "Col.E) Fecha Inicio"), - ("FECHA_FINAL", "Col.F) Fecha Final"), - ("FECHA_PAGO", "Col.G) Fecha de Entrada/Referencia"), - ("ADUANA_SECCION_CRUCE", "Col.H) Aduana y Sección de Cruce"), - ("INDIVIDUAL_CONSOLIDADO", "Col.J) Tipo de Pedimento (IND/CON)"), - ] - for key, label in col_labels: - if not (row.get(key) or "").strip(): - cols_missing.append(label) - if not cols_missing: - return None - campos = ", ".join(cols_missing) - return { - "line": line_num, - "col": "", - "msg": f"{MSG_OBLIGATORIOS.format(campos=campos)} {MSG_OBLIGATORIOS_SOLUCION}", - } - - -def validate_row_fecha_pedimento( - row: Dict[str, Any], - col: str, - line_num: int, - date_format_preference: Optional[str] = None, -) -> Optional[Dict[str, Any]]: - """Valida que el campo de fecha sea válido (longitud ≤10, parseable).""" - val = (row.get(col) or "").strip() - if not val: - return None - if len(val) > FECHA_MAX_LEN: - return { - "line": line_num, - "col": col, - "msg": f"Error: ({col}) La fecha supera la longitud de caracteres.", - } - if parse_date(val, date_format_preference) is None: - format_label = ( - DATE_FORMAT_PREFERENCE_MAP.get(date_format_preference, "##/##/####") - if date_format_preference - else "##/##/####" - ) - return { - "line": line_num, - "col": col, - "msg": f"Error: ({col}) La fecha no coincide con el formato ({format_label}).", - } - return None - - -def validate_row_tipo_operacion(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]: - """Col B: si tiene valor debe ser I o E.""" - val = (row.get("TIPO_OPERACION") or "").strip().upper() - if not val: - return None - if val not in ("I", "E"): - return { - "line": line_num, - "col": "TIPO_OPERACION", - "msg": f"{MSG_TIPO_INVALIDO.format(val=row.get('TIPO_OPERACION'))} {MSG_TIPO_SOLUCION}", - } - return None - - -def validate_row_clave_regimen_tipo( - row: Dict[str, Any], - line_num: int, - valid_clave_regimen_tipo: Set[Tuple[str, str, str]], -) -> Optional[Dict[str, Any]]: - """Combinación B+C+D debe existir en CodePedimentoRegimen.""" - b = (row.get("TIPO_OPERACION") or "").strip().upper() - c = (row.get("CLAVE_PEDIMENTO") or "").strip().upper() - d = (row.get("REGIMEN") or "").strip().upper() - if not b or not c or not d: - return None - key = (c, d, b) - if valid_clave_regimen_tipo and key not in valid_clave_regimen_tipo: - # Opcional: listar combinaciones válidas para esa clave - comb = ", ".join( - f"operación {t} con clave {cp} y régimen {r}" - for cp, r, t in valid_clave_regimen_tipo - if cp == c - ) or "ninguna para esta clave" - return { - "line": line_num, - "col": "TIPO_OPERACION", - "msg": f"{MSG_COMBINACION.format(b=b, c=c, d=d)} {MSG_COMBINACION_SOLUCION.format(comb=comb)}", - } - return None - - -def validate_row_aduana( - row: Dict[str, Any], - line_num: int, - valid_aduana_seccion: Set[str], -) -> Optional[Dict[str, Any]]: - """Col H: si tiene valor debe existir en CustomsSection.""" - val = (row.get("ADUANA_SECCION_CRUCE") or "").strip() - if not val: - return None - # customs_code puede ser 3 caracteres; si Col H trae más, tomar primeros 3 - code = val[:3] if len(val) >= 3 else val - if valid_aduana_seccion and code not in valid_aduana_seccion: - return { - "line": line_num, - "col": "ADUANA_SECCION_CRUCE", - "msg": f"{MSG_ADUANA_NO_EXISTE.format(val=val)} {MSG_ADUANA_SOLUCION}", - } - return None - - -def validate_row_ind_con(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]: - """Col J: si tiene valor debe ser IND o CON.""" - val = (row.get("INDIVIDUAL_CONSOLIDADO") or "").strip().upper() - if not val: - return None - if val not in ("IND", "CON"): - return { - "line": line_num, - "col": "INDIVIDUAL_CONSOLIDADO", - "msg": f"{MSG_IND_CON_INVALIDO.format(val=val)} {MSG_IND_CON_SOLUCION}", - } - return None - - -def validate_row_transporte( - row: Dict[str, Any], - line_num: int, - col_key: str, - col_label: str, - valid_anexo22_claves: Set[str], -) -> Optional[Dict[str, Any]]: - """Valida una columna de método de transporte (K, L o M) contra Anexo 22. Si el conjunto está vacío, no se valida.""" - val = (row.get(col_key) or "").strip().upper() - if not val: - return None - if valid_anexo22_claves and val not in valid_anexo22_claves: - return { - "line": line_num, - "col": col_key, - "msg": f"{MSG_TRANSP_NO_EXISTE.format(col=col_label, val=val)} {MSG_TRANSP_SOLUCION}", - } - return None - - -def validaciones_pedimento( - row: Dict[str, Any], - line_num: int, - valid_clave_regimen_tipo: Set[Tuple[str, str, str]], - valid_aduana_seccion: Set[str], - valid_anexo22_claves: Set[str], - valid_patentes: Set[str], - short_name_to_id: Optional[Dict[str, int]] = None, - date_format_preference: Optional[str] = None, -) -> List[Dict[str, Any]]: - """Orquestador: formato PEDIMENTO si aplica, tipo B, combinación B+C+D, aduana H, patente, IND/CON J, transporte K/L/M, fechas E/F/G. CLIENTE_SHORT_NAME opcional.""" - errors: List[Dict[str, Any]] = [] - - if (row.get("PEDIMENTO") or "").strip(): - err = validate_row_pedimento_format(row, line_num) - if err: - errors.append(err) - - # CLIENTE_SHORT_NAME opcional: si viene valor se valida que exista en catálogo. - if short_name_to_id is not None: - err = check_optional_short_name( - row, "CLIENTE_SHORT_NAME", short_name_to_id, line_num - ) - if err: - errors.append(err) - - for err in [ - validate_row_patente(row, line_num, valid_patentes), - validate_row_tipo_operacion(row, line_num), - validate_row_clave_regimen_tipo( - row, line_num, valid_clave_regimen_tipo - ), - validate_row_aduana(row, line_num, valid_aduana_seccion), - validate_row_ind_con(row, line_num), - validate_row_transporte(row, line_num, "MET_TRANSP_ENTRADA", "K", valid_anexo22_claves), - validate_row_transporte(row, line_num, "MET_TRANSP_ARRIVO", "L", valid_anexo22_claves), - validate_row_transporte(row, line_num, "MET_TRANSP_SALIDA", "M", valid_anexo22_claves), - ]: - if err: - errors.append(err) - - for col in ("FECHA_INICIO", "FECHA_FINAL", "FECHA_PAGO"): - err = validate_row_fecha_pedimento(row, col, line_num, date_format_preference) - if err: - errors.append(err) - - err = validate_row_fechas_coherencia_clarion(row, line_num, date_format_preference) - if err: - errors.append(err) - - return errors - - -def validate_row_fechas_coherencia_clarion( - row: Dict[str, Any], - line_num: int, - date_format_preference: Optional[str] = None, -) -> Optional[Dict[str, Any]]: - """ - Reglas legacy Clarion: - - CON: inicio <= final <= fecha de referencia (pago/entrada). - - IND: si las fechas difieren, advertencia no bloqueante. - En este layout CSV la referencia operativa está en FECHA_PAGO (Col G). - """ - start_raw = (row.get("FECHA_INICIO") or "").strip() - end_raw = (row.get("FECHA_FINAL") or "").strip() - ref_raw = (row.get("FECHA_PAGO") or "").strip() - if not start_raw or not end_raw or not ref_raw: - return None - - start_dt = parse_date(start_raw, date_format_preference) - end_dt = parse_date(end_raw, date_format_preference) - ref_dt = parse_date(ref_raw, date_format_preference) - if not start_dt or not end_dt or not ref_dt: - return None - - start_date = start_dt.date() - end_date = end_dt.date() - ref_date = ref_dt.date() - - ind_con = (row.get("INDIVIDUAL_CONSOLIDADO") or "").strip().upper() - # Compatibilidad legacy: vacío se trata como consolidado. - if not ind_con: - ind_con = "CON" - - if ind_con == "CON": - if start_date > end_date: - return { - "line": line_num, - "col": "FECHA_FINAL", - "msg": "Error: La fecha inicio no puede ser mayor que la fecha final.", - } - if start_date > ref_date: - return { - "line": line_num, - "col": "FECHA_PAGO", - "msg": "Error: La fecha inicio no puede ser mayor que la fecha de referencia (Col.G).", - } - if end_date > ref_date: - return { - "line": line_num, - "col": "FECHA_PAGO", - "msg": "Error: La fecha final no puede ser mayor que la fecha de referencia (Col.G).", - } - return None - - if ind_con == "IND": - if not (start_date == end_date == ref_date): - return { - "line": line_num, - "col": "FECHA_INICIO", - "msg": "Advertencia: En pedimento individual las fechas inicio/final/referencia son distintas.", - "warning": True, - } - return None - - -# --- Legacy (layout sin PEDIMENTO único): mantener para compatibilidad --- -def validate_row_pedimento_required_legacy( - row: Dict[str, Any], line_num: int -) -> Optional[Dict[str, Any]]: - """Requeridos para layout legacy: AÑO, ADUANA (ADUANA_SECCION_CRUCE), PATENTE, NUMERO.""" - for col, max_len in [ - ("AÑO", 2), - ("ADUANA_SECCION_CRUCE", 3), - ("PATENTE", 4), - ("NUMERO", 7), - ]: - val = (row.get(col) or "").strip() - if not val: - return {"line": line_num, "col": col, "msg": "Requerido"} - if len(val) > max_len: - return {"line": line_num, "col": col, "msg": f"Máximo {max_len} caracteres"} - return None - - -def validate_row_pedimento_fk_legacy( - row: Dict[str, Any], - line_num: int, - short_name_to_id: Dict[str, int], - valid_regimes: Set[str], - valid_pedimento_codes: Set[str], -) -> Optional[Dict[str, Any]]: - """FK para layout legacy (CLAVE_PEDIMENTO o CODIGO_PEDIMENTO, REGIMEN). CLIENTE_SHORT_NAME opcional.""" - err = check_optional_short_name(row, "CLIENTE_SHORT_NAME", short_name_to_id, line_num) - if err: - return err - # Template puede dar CLAVE_PEDIMENTO o CODIGO_PEDIMENTO según alias - code = (row.get("CLAVE_PEDIMENTO") or row.get("CODIGO_PEDIMENTO") or "").strip() - if not code: - return {"line": line_num, "col": "CLAVE_PEDIMENTO", "msg": "Requerido"} - if len(code) > 2: - return {"line": line_num, "col": "CLAVE_PEDIMENTO", "msg": "Máximo 2 caracteres"} - if valid_pedimento_codes and code not in valid_pedimento_codes: - return {"line": line_num, "col": "CLAVE_PEDIMENTO", "msg": "No existe en código pedimento"} - err = check_in_set( - row, "REGIMEN", valid_regimes, line_num, max_len=3, catalog_name="régimen" - ) - if err: - return err - return None - - -def validate_row_pedimento_optionals_legacy( - row: Dict[str, Any], line_num: int -) -> Optional[Dict[str, Any]]: - """Opcionales para layout legacy.""" - err = check_optional_max_length(row, "ESTATUS", 30, line_num) - if err: - return err - for col in ("VALOR_USD", "PRECIO_PAGADO", "PESO_BRUTO", "TIPO_CAMBIO"): - err = check_optional_decimal(row, col, line_num) - if err: - return err - err = check_optional_in_set( - row, "TIPO_OPERACION", {"imp", "exp"}, line_num, "Debe ser imp o exp" - ) - if err: - return err - err = check_optional_in_set( - row, - "TIPO_PEDIMENTO", - {"normal", "consolidated", "complementary", "automobile"}, - line_num, - "Tipo no válido", - ) - if err: - return err - return None diff --git a/backend/api/v1/modules/a76/layouts_csv/pedmientos/validators/create.py b/backend/api/v1/modules/a76/layouts_csv/pedmientos/validators/create.py deleted file mode 100644 index 1888b802..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/pedmientos/validators/create.py +++ /dev/null @@ -1,144 +0,0 @@ -""" -Punto de entrada de validación para import de una fila pedimento. -Flujo Clarion: - - Modo actualizar: crear si no existe, actualizar si existe. Si existe → VALIDA_PARCIAL; si no existe → VALIDA_TODA. - - Modo crear: crear siempre; si existe reemplazar. Siempre VALIDA_TODA. -Legacy: si la fila no tiene PEDIMENTO (Col A Clarion), se usa validación por AÑO/ADUANA/PATENTE/NUMERO. -""" -from typing import Dict, Any, Optional, Set, Tuple, List - -from ..template_config import is_clarion_layout, parse_pedimento_col_a -from ..common.fk_loader import pedimento_key_from_parsed -from .common import ( - validate_row_desfase_pedimento, - validate_row_pedimento_required_col_a, - validate_row_pedimento_format, - validate_row_pedimento_required_three_cols, - validate_row_pedimento_required_full, - validaciones_pedimento, - validate_row_pedimento_required_legacy, - validate_row_pedimento_fk_legacy, - validate_row_pedimento_optionals_legacy, -) - - -def _clarion_pedimento_key(row: Dict[str, Any]) -> Optional[str]: - """Construye la clave (year|customs_office|license|pedimento_number) desde fila Clarion (3 cols o PEDIMENTO).""" - año = (row.get("AÑO") or "").strip() - patente = (row.get("PATENTE") or "").strip() - numero = (row.get("NUMERO") or "").strip() - if año and patente and numero: - aduana = (row.get("ADUANA_SECCION_CRUCE") or "").strip()[:3] - return pedimento_key_from_parsed(año[:2], aduana, patente[:4], numero[:7]) - parsed = parse_pedimento_col_a((row.get("PEDIMENTO") or "").strip()) - if not parsed: - return None - year, license_val, pedimento_number = parsed - aduana = (row.get("ADUANA_SECCION_CRUCE") or "").strip()[:3] - return pedimento_key_from_parsed(year, aduana, license_val, pedimento_number) - - -def validate_row_pedimento( - row: Dict[str, Any], - line_num: int, - short_name_to_id: Dict[str, int], - valid_regimes: Set[str], - valid_pedimento_codes: Set[str], - valid_clave_regimen_tipo: Set[Tuple[str, str, str]], - valid_aduana_seccion: Set[str], - existing_pedimento_keys: Set[str], - valid_anexo22_claves: Set[str], - valid_patentes: Set[str], - actualizar: bool = False, - raw_row: Optional[Dict[str, Any]] = None, - date_format_preference: Optional[str] = None, -) -> List[Dict[str, Any]]: - """ - Valida una fila de CSV de pedimentos. - - Si raw_row está presente, se valida desfase (Col N) primero. - - Layout Clarion (PEDIMENTO con valor): flujo VALIDA_TODA / VALIDA_PARCIAL según actualizar y existing_pedimento_keys. - - Layout legacy (sin PEDIMENTO): requeridos AÑO, ADUANA, PATENTE, NUMERO, CODIGO_PEDIMENTO, REGIMEN; CLIENTE_SHORT_NAME opcional. - """ - errors: List[Dict[str, Any]] = [] - - if raw_row is not None: - err = validate_row_desfase_pedimento(raw_row, line_num) - if err: - errors.append(err) - - if not is_clarion_layout(row): - err = validate_row_pedimento_required_legacy(row, line_num) - if err: - errors.append(err) - - err = validate_row_pedimento_fk_legacy( - row, line_num, short_name_to_id, valid_regimes, valid_pedimento_codes - ) - if err: - errors.append(err) - - err = validate_row_pedimento_optionals_legacy(row, line_num) - if err: - errors.append(err) - - return errors - - # Layout Clarion: validar Cols 1-3 (AÑO, PATENTE, NUMERO) o Col A (PEDIMENTO) si compatibilidad - has_three_cols = ( - (row.get("AÑO") or "").strip() - and (row.get("PATENTE") or "").strip() - and (row.get("NUMERO") or "").strip() - ) - if has_three_cols: - err = validate_row_pedimento_required_three_cols(row, line_num) - else: - err = validate_row_pedimento_required_col_a(row, line_num) - # Formato solo aplica cuando hay valor en Col A, pero podemos acumular igualmente. - fmt_err = validate_row_pedimento_format(row, line_num) - if fmt_err: - errors.append(fmt_err) - - if err: - errors.append(err) - - key = _clarion_pedimento_key(row) - # Actualizar: si existe → validación parcial; si no existe → validación completa (crear). - # Crear: siempre validación completa (crear siempre; si existe reemplazar). - use_partial = ( - actualizar - and key is not None - and key in existing_pedimento_keys - ) - - if use_partial: - errors.extend( - validaciones_pedimento( - row, - line_num, - valid_clave_regimen_tipo, - valid_aduana_seccion, - valid_anexo22_claves, - valid_patentes, - short_name_to_id=short_name_to_id, - date_format_preference=date_format_preference, - ) - ) - return errors - - err = validate_row_pedimento_required_full(row, line_num) - if err: - errors.append(err) - - errors.extend( - validaciones_pedimento( - row, - line_num, - valid_clave_regimen_tipo, - valid_aduana_seccion, - valid_anexo22_claves, - valid_patentes, - short_name_to_id=short_name_to_id, - date_format_preference=date_format_preference, - ) - ) - return errors diff --git a/backend/api/v1/modules/a76/layouts_csv/trailers/__init__.py b/backend/api/v1/modules/a76/layouts_csv/trailers/__init__.py deleted file mode 100644 index ef25c98a..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/trailers/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# Trailers CSV import: upload -> scan -> status -> commit diff --git a/backend/api/v1/modules/a76/layouts_csv/trailers/common/__init__.py b/backend/api/v1/modules/a76/layouts_csv/trailers/common/__init__.py deleted file mode 100644 index 47ef2168..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/trailers/common/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# common_validators, mappers, fk_loader diff --git a/backend/api/v1/modules/a76/layouts_csv/trailers/common/common_validators.py b/backend/api/v1/modules/a76/layouts_csv/trailers/common/common_validators.py deleted file mode 100644 index 6948bf3c..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/trailers/common/common_validators.py +++ /dev/null @@ -1,183 +0,0 @@ -""" -Validadores reutilizables para import CSV de trailers (longitudes, requerido). -Paridad Clarion: VALIDACIONES_TRAILER, código entidad C/I/A/B, catálogos tipo/país/estado, desfase. -""" -from typing import Dict, Any, Optional, Set, Tuple - -# Max lengths from Trailer model (a76.trailer) -MAX_LEN = { - "trailer_number": 20, - "ace_trailer_number": 10, - "trailer_type_key": 2, - "seal": 15, - "entity_code": 1, - "plate_number": 17, - "state": 30, - "country": 3, - "container_key": 3, -} - -CODIGO_ENTIDAD_VALIDOS = {"C", "I", "A", "B"} - - -def check_required(row: Dict[str, Any], col: str, max_len: int, line_num: int) -> Optional[Dict[str, Any]]: - val = (row.get(col) or "").strip() - if not val: - return {"line": line_num, "col": col, "msg": "Requerido"} - if len(val) > max_len: - return {"line": line_num, "col": col, "msg": f"Máximo {max_len} caracteres"} - return None - - -def check_max_length( - row: Dict[str, Any], col: str, max_len: int, line_num: int, required: bool = False -) -> Optional[Dict[str, Any]]: - val = (row.get(col) or "").strip() - if not val: - if required: - return {"line": line_num, "col": col, "msg": "Requerido"} - return None - if len(val) > max_len: - return {"line": line_num, "col": col, "msg": f"Máximo {max_len} caracteres"} - return None - - -def _get_codigo_entidad(row: Dict[str, Any]) -> str: - return (row.get("CODIGO DE ENTIDAD") or row.get("CODIGO ENTIDAD") or "").strip() - - -def check_codigo_entidad_valores(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]: - """Col E: Si CODIGO DE ENTIDAD no vacío, debe ser C, I, A o B (Clarion).""" - val = _get_codigo_entidad(row).upper() - if not val: - return None - if val in CODIGO_ENTIDAD_VALIDOS: - return None - return { - "line": line_num, - "col": "CODIGO DE ENTIDAD", - "msg": f"Error: (Col. E) El Codigo de Entidad: {val} es incorrecto.", - "solution": "Capturar en columna E el Codigo de Entidad correcto, C, I, A ó B.", - } - - -def check_trailer_type_catalog( - row: Dict[str, Any], - line_num: int, - valid_trailer_type_keys: Optional[Set[str]] = None, -) -> Optional[Dict[str, Any]]: - """Col C: Si TIPO TRAILER no vacío, debe existir en catálogo (GTipoTrailer).""" - val = (row.get("TIPO TRAILER") or "").strip() - if not val or valid_trailer_type_keys is None: - return None - if val.upper() in valid_trailer_type_keys: - return None - return { - "line": line_num, - "col": "TIPO TRAILER", - "msg": f"Error: (Col. C) El Tipo de Trailer: {val} es incorrecto.", - "solution": "Capturar en columna C un Tipo del Trailer existente.", - } - - -def check_pais_catalog_trailers( - row: Dict[str, Any], - line_num: int, - valid_country_ame: Optional[Set[str]] = None, -) -> Optional[Dict[str, Any]]: - """Col H: Si PAIS no vacío, debe ser clave americana (≤2 chars) y existir en catálogo (GPaises.Pais_Ame).""" - val = (row.get("PAIS") or "").strip() - if not val or valid_country_ame is None: - return None - val_upper = val.upper() - if len(val) > 2: - return { - "line": line_num, - "col": "PAIS", - "msg": f"Error: (Celda H{line_num}) El Pais: {val} Es Incorrecto", - "solution": "Capturar en columna H un Pais en Clave Americana(US = Estados Unidos, MX = Mexico, ES = España, etc).", - } - if val_upper in valid_country_ame: - return None - return { - "line": line_num, - "col": "PAIS", - "msg": f"Error: (Col. H) El Pais: {val} es incorrecto.", - "solution": "Capturar en columna H el Pais del Transporte en Clave Americana.", - } - - -def check_estado_catalog_trailers( - row: Dict[str, Any], - line_num: int, - state_descriptions_upper: Optional[Set[str]] = None, - state_ame_to_description: Optional[Dict[str, str]] = None, -) -> Tuple[Optional[Dict[str, Any]], Optional[str]]: - """ - Col G: Si ESTADO no vacío, debe existir en catálogo (GEstados). Si hay estado, PAIS no puede estar vacío. - Devuelve (error, estado_resuelto_upper). estado_resuelto_upper es la descripción en mayúsculas para estado-país. - """ - state_ame_to_description = state_ame_to_description or {} - val = (row.get("ESTADO") or "").strip() - if not val or state_descriptions_upper is None: - return None, None - val_upper = val.upper() - resolved_upper: Optional[str] = None - if val_upper in state_descriptions_upper: - resolved_upper = val_upper - elif len(val) <= 2 and val_upper in state_ame_to_description: - resolved_upper = state_ame_to_description[val_upper] - if resolved_upper is None: - return { - "line": line_num, - "col": "ESTADO", - "msg": f"Error: (Celda G{line_num}) El Estado: {val} es incorrecto.", - "solution": "Capturar en columna G el Estado del Trailer en Clave Americana o Nombre Completo.", - }, None - # Si existe estado, PAIS no puede estar vacío (Clarion) - pais = (row.get("PAIS") or "").strip() - if not pais: - return { - "line": line_num, - "col": "PAIS", - "msg": f"Error: (Celda G{line_num}) El Estado: {val} No esta ligado a ningun Pais.", - "solution": "Capturar en columna H un Pais en Clave Americana(US = Estados Unidos, MX = Mexico, ES = España, etc).", - }, None - return None, resolved_upper - - -def check_estado_pais_consistency_trailers( - row: Dict[str, Any], - line_num: int, - state_country_set: Optional[Set[Tuple[str, str]]] = None, - estado_resuelto_upper: Optional[str] = None, -) -> Optional[Dict[str, Any]]: - """Si hay ESTADO y PAIS, validar que el estado pertenezca al país (Clarion GEstados-GPaises).""" - estado = (row.get("ESTADO") or "").strip() - pais = (row.get("PAIS") or "").strip().upper() - if not estado or not pais or state_country_set is None: - return None - desc_upper = estado_resuelto_upper or estado.upper() - key = (pais, desc_upper) - if key in state_country_set: - return None - return { - "line": line_num, - "col": "ESTADO", - "msg": f"Error: (Col. G) EL Estado: {estado} no pertenece al Pais: {pais}.", - "solution": "Capturar en columna G un Estado que pertenesca al Pais de la columna H.", - } - - -def check_desfase_trailers(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]: - """Si COL_EXTRA tiene valor → advertencia de desfase (Clarion, no bloqueante).""" - val = (row.get("COL_EXTRA") or "").strip() - if not val: - return None - return { - "line": line_num, - "col": "COL_EXTRA", - "msg": "Advertencia: Podría existir un desfase en esta línea.", - "solution": "Revisar esta línea del archivo CSV y verificar cada campo este en la posicion correcta.", - "warning": True, - } diff --git a/backend/api/v1/modules/a76/layouts_csv/trailers/common/fk_loader.py b/backend/api/v1/modules/a76/layouts_csv/trailers/common/fk_loader.py deleted file mode 100644 index f9e2c3cf..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/trailers/common/fk_loader.py +++ /dev/null @@ -1,92 +0,0 @@ -""" -Carga de conjuntos FK para validación de import CSV de trailers/cajas. -Clarion: GTipoTrailer, GPaises (Pais_Ame), GEstados (Descripcion / Clave_Ame), relación Estado-País. -""" -from typing import Set, Tuple, Optional, Dict -import logging - -from core.database import CoreSessionLocal - -logger = logging.getLogger(__name__) - - -def load_trailers_fk_sets( - tenant_id: Optional[int] = None, - company_id: Optional[int] = None, -) -> Tuple[ - Set[str], - Set[str], - Set[str], - Set[Tuple[str, str]], - Dict[str, str], -]: - """ - Carga conjuntos para validación CSV de trailers (paridad Clarion). - Devuelve: - - valid_trailer_type_keys: códigos de trailer_type (GTipoTrailer) - - valid_country_ame: claves americana de países (GPaises.Pais_Ame), mayúsculas - - state_descriptions_upper: descripciones y mex_key en mayúsculas (GEstados) - - state_country_set: (ame_key_pais, token_upper) para description o mex_key - - state_ame_to_description: dict clave_ame_upper -> description_upper (opcional; vacío si State no tiene ame_key) - """ - valid_trailer_type_keys: Set[str] = set() - valid_country_ame: Set[str] = set() - state_descriptions_upper: Set[str] = set() - state_country_set: Set[Tuple[str, str]] = set() - state_ame_to_description: Dict[str, str] = {} - - try: - with CoreSessionLocal() as session: - from api.v1.modules.public.reference_data.trailer_types.models import TrailerType - from api.v1.modules.public.reference_data.countries.models import Country - from api.v1.modules.public.reference_data.states.models import State - - for row in session.query(TrailerType.trailer_type_key).all(): - if row[0]: - valid_trailer_type_keys.add((row[0] or "").strip().upper()) - - for row in session.query(Country.ame_key).all(): - if row[0]: - valid_country_ame.add((row[0] or "").strip().upper()) - - for state in session.query(State).all(): - country = ( - session.query(Country) - .filter(Country.m3_key == state.m3_key) - .first() - ) - ame_country = None - if country and (country.ame_key or "").strip(): - ame_country = (country.ame_key or "").strip().upper() - - desc = (state.description or "").strip() - if desc: - state_descriptions_upper.add(desc.upper()) - if ame_country: - state_country_set.add((ame_country, desc.upper())) - - mex_key = (state.mex_key or "").strip() - if mex_key: - mk = mex_key.upper() - state_descriptions_upper.add(mk) - if ame_country: - state_country_set.add((ame_country, mk)) - if desc: - state_ame_to_description[mk] = desc.upper() - - ame_state = getattr(state, "ame_key", None) - if ame_state and (ame_state or "").strip(): - state_ame_to_description[(ame_state or "").strip().upper()] = ( - desc.upper() if desc else (ame_state or "").strip().upper() - ) - - except Exception as e: - logger.warning("Trailers import: could not load FK sets: %s", e) - - return ( - valid_trailer_type_keys, - valid_country_ame, - state_descriptions_upper, - state_country_set, - state_ame_to_description, - ) diff --git a/backend/api/v1/modules/a76/layouts_csv/trailers/common/mappers.py b/backend/api/v1/modules/a76/layouts_csv/trailers/common/mappers.py deleted file mode 100644 index 032a8edb..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/trailers/common/mappers.py +++ /dev/null @@ -1,94 +0,0 @@ -""" -Mapeo fila CSV → datos para Trailer. -Paridad Clarion LLENA_TRAILER: en actualización, campo vacío en CSV usa valor existente del trailer. -""" -from typing import Dict, Any, Optional, Union - -from .common_validators import MAX_LEN - - -def _str_or_none(val: Any, max_len: Optional[int] = None) -> Optional[str]: - if val is None: - return None - s = str(val).strip() - if not s: - return None - if max_len and len(s) > max_len: - return s[:max_len] - return s - - -def _get_existing_val(existing: Union[Any, Dict[str, Any]], key: str) -> Any: - """Obtiene valor del trailer existente (modelo ORM o dict).""" - if existing is None: - return None - if isinstance(existing, dict): - return existing.get(key) - return getattr(existing, key, None) - - -def _get_entity_code(row_norm: Dict[str, Any]) -> Optional[str]: - return _str_or_none( - row_norm.get("CODIGO ENTIDAD") or row_norm.get("CODIGO DE ENTIDAD"), - MAX_LEN["entity_code"], - ) - - -def row_to_trailer_data(row_norm: Dict[str, Any]) -> Dict[str, Any]: - """Build dict for TrailerCreateDTO / TrailerUpdateDTO from normalized row.""" - trailer_number = _str_or_none( - row_norm.get("NUMERO TRAILER") or row_norm.get("CLAVE TRAILER"), - MAX_LEN["trailer_number"], - ) - if not trailer_number: - return {} - return { - "trailer_number": trailer_number, - "ace_trailer_number": _str_or_none(row_norm.get("CLAVE ACE"), MAX_LEN["ace_trailer_number"]), - "trailer_type_key": _str_or_none(row_norm.get("TIPO TRAILER"), MAX_LEN["trailer_type_key"]), - "seal": _str_or_none(row_norm.get("PRECINTO"), MAX_LEN["seal"]), - "entity_code": _get_entity_code(row_norm), - "plate_number": _str_or_none(row_norm.get("PLACAS"), MAX_LEN["plate_number"]), - "state": _str_or_none(row_norm.get("ESTADO"), MAX_LEN["state"]), - "country": _str_or_none(row_norm.get("PAIS"), MAX_LEN["country"]), - "container_key": _str_or_none(row_norm.get("CLAVE CONTENEDOR"), MAX_LEN["container_key"]), - } - - -def row_to_trailer_data_for_update( - row_norm: Dict[str, Any], - existing_trailer: Union[Any, Dict[str, Any]], -) -> Dict[str, Any]: - """ - Build dict for TrailerUpdateDTO: CSV value if non-empty, else existing trailer value (Clarion VALIDA_PARCIAL / LLENA_TRAILER). - """ - trailer_number = _str_or_none( - row_norm.get("NUMERO TRAILER") or row_norm.get("CLAVE TRAILER"), - MAX_LEN["trailer_number"], - ) or _get_existing_val(existing_trailer, "trailer_number") - if not trailer_number: - return {} - - def _csv_or_existing(csv_key: str, dto_key: str, max_len: Optional[int] = None): - v = _str_or_none(row_norm.get(csv_key), max_len) - if v is not None and v != "": - return v - return _get_existing_val(existing_trailer, dto_key) - - def _entity_code_or_existing(): - v = _get_entity_code(row_norm) - if v is not None and v != "": - return v - return _get_existing_val(existing_trailer, "entity_code") - - return { - "trailer_number": trailer_number, - "ace_trailer_number": _csv_or_existing("CLAVE ACE", "ace_trailer_number", MAX_LEN["ace_trailer_number"]), - "trailer_type_key": _csv_or_existing("TIPO TRAILER", "trailer_type_key", MAX_LEN["trailer_type_key"]), - "seal": _csv_or_existing("PRECINTO", "seal", MAX_LEN["seal"]), - "entity_code": _entity_code_or_existing(), - "plate_number": _csv_or_existing("PLACAS", "plate_number", MAX_LEN["plate_number"]), - "state": _csv_or_existing("ESTADO", "state", MAX_LEN["state"]), - "country": _csv_or_existing("PAIS", "country", MAX_LEN["country"]), - "container_key": _csv_or_existing("CLAVE CONTENEDOR", "container_key", MAX_LEN["container_key"]), - } diff --git a/backend/api/v1/modules/a76/layouts_csv/trailers/csv_max_chars_for_headers.py b/backend/api/v1/modules/a76/layouts_csv/trailers/csv_max_chars_for_headers.py deleted file mode 100644 index 717e8996..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/trailers/csv_max_chars_for_headers.py +++ /dev/null @@ -1,16 +0,0 @@ -"""Límites alineados a trailers/common/common_validators.MAX_LEN.""" -from typing import Dict - -from .common.common_validators import MAX_LEN as T_MAX - -TRAILERS_CSV_MAX_CHARS: Dict[str, int] = { - "NUMERO TRAILER": T_MAX["trailer_number"], - "CLAVE ACE": T_MAX["ace_trailer_number"], - "TIPO TRAILER": T_MAX["trailer_type_key"], - "PRECINTO": T_MAX["seal"], - "CODIGO ENTIDAD": T_MAX["entity_code"], - "PLACAS": T_MAX["plate_number"], - "ESTADO": T_MAX["state"], - "PAIS": T_MAX["country"], - "CLAVE CONTENEDOR": T_MAX["container_key"], -} diff --git a/backend/api/v1/modules/a76/layouts_csv/trailers/routes.py b/backend/api/v1/modules/a76/layouts_csv/trailers/routes.py deleted file mode 100644 index ca25a38b..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/trailers/routes.py +++ /dev/null @@ -1,204 +0,0 @@ -""" -Rutas de importación CSV para Trailers y Cajas. -Flujo: upload -> scan -> status (polling) -> commit. -""" -import json -import logging -import os -import threading -from uuid import uuid4 - -from fastapi import APIRouter, File, HTTPException, Query, UploadFile, Depends -from sqlalchemy.orm import Session -from typing import Dict, Any - -from core.celery_app import celery_app -from core.database import get_core_db -from core.security import get_current_user, validate_access_to_resource -from api.v1.modules.core.tasks_tracking import track_and_dispatch - -from .schemas import ImportJobResponse -from .tasks import ( - scan_file, - run_scan_sync, - run_commit_sync, - JOB_TYPE, - TRL_IMPORT_STATUS_PREFIX, - TRL_IMPORT_REDIS_TTL, -) -from ..common import storage as common_storage -from ..common.error_csv import download_scan_errors_csv_stream -from ..common.responses import normalize_commit_status_payload - -router = APIRouter() -logger = logging.getLogger(__name__) - - -def _get_redis(): - import redis - url = os.getenv("VALKEY_URL", os.getenv("REDIS_URL", "redis://valkey:6379/0")) - return redis.Redis.from_url(url, decode_responses=False) - - -@router.post("/upload", response_model=ImportJobResponse) -async def upload_import_file( - file: UploadFile = File(...), - company_id: int = Query(..., description="Company ID"), - actualizar: bool = Query(False, description="Modo Agregar/Actualizar (ACT); si True, validación parcial para claves existentes"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - try: - tenant_id = validate_access_to_resource(db, company_id, current_user, ["csv_upload.process"]) - except Exception as e: - logger.error(f"Trailers import: access validation failed: {e}") - raise HTTPException(status_code=403, detail="Invalid company access") - - if not file.filename or not file.filename.lower().endswith(".csv"): - raise HTTPException(status_code=400, detail="Solo se permiten archivos .csv") - - job_id = str(uuid4()) - contents = await file.read() - - meta_data = { - "tenant_id": tenant_id, - "company_id": company_id, - "user_id": current_user.get("id"), - "template_id": "trailers", - "actualizar": actualizar, - } - - try: - common_storage.store_import_file( - JOB_TYPE, - job_id, - contents, - meta_data, - tenant_id=int(tenant_id), - company_id=company_id, - ttl=TRL_IMPORT_REDIS_TTL, - log_label="Trailers import", - ) - except common_storage.ImportStoreError as e: - logger.error(f"Trailers import: store error: {e}") - raise HTTPException(status_code=500, detail="No se pudo encolar el archivo.") - except Exception as e: - logger.error(f"Trailers import: Redis store error: {e}") - raise HTTPException(status_code=500, detail="No se pudo encolar el archivo.") - - track_and_dispatch( - db=db, - task=scan_file, - tenant_id=int(tenant_id), - company_id=company_id, - requested_by_user=current_user.get("preferred_username") - or current_user.get("email") - or current_user.get("sub"), - task_name="trailers_scan_file", - task_group="layouts_csv", - task_origin="a76/layouts_csv/trailers/upload", - args=[job_id], - task_id=job_id, - ) - - def run_scan_background(): - try: - run_scan_sync(job_id) - except Exception as e: - logger.exception(f"Trailers import: background scan failed: {e}") - - threading.Thread(target=run_scan_background, daemon=True).start() - - return ImportJobResponse( - job_id=job_id, - status="queued", - message="Archivo subido. Escaneo iniciado.", - ) - - -@router.get("/{job_id}/status") -async def get_import_status(job_id: str): - try: - r = _get_redis() - raw = r.get(f"{TRL_IMPORT_STATUS_PREFIX}{job_id}") - if raw: - data = json.loads(raw.decode("utf-8")) - if isinstance(data, dict) and data.get("status") in ("finished", "warning"): - return normalize_commit_status_payload(data) - return data - except Exception as e: - logger.debug(f"Trailers import: could not read status from Redis: {e}") - - task_result = celery_app.AsyncResult(job_id) - - if task_result.state == "PENDING": - return {"status": "processing", "progress": 0} - if task_result.state == "PROGRESS": - info = (task_result.info or {}) - return { - "status": "processing", - "progress": info.get("current", 0), - "total": info.get("total", 0), - } - if task_result.state == "SUCCESS": - result = task_result.result - if isinstance(result, dict) and "status" in result: - return normalize_commit_status_payload(result) - return {"status": "finished", "result": result} - - result = getattr(task_result, "result", None) - if isinstance(result, dict) and result.get("status") in ("finished", "warning"): - return normalize_commit_status_payload(result) - - logger.warning("Trailers import task %s failed: state=%s", job_id, task_result.state) - err_msg = None - tb = getattr(task_result, "traceback", None) - if tb and isinstance(tb, str): - lines = [l.strip() for l in tb.strip().split("\n") if l.strip()] - if lines: - err_msg = lines[-1] - if not err_msg: - try: - exc = task_result.get(propagate=False) - if exc is not None: - err_msg = str(exc) - except Exception: - pass - if not err_msg and result is not None: - if not isinstance(result, dict): - err_msg = str(result) - elif result.get("error") or result.get("message"): - err_msg = result.get("error") or result.get("message") - return {"status": "failed", "error": err_msg or "Task failed"} - - -@router.post("/{job_id}/commit") -async def commit_import_job(job_id: str): - try: - r = _get_redis() - r.set( - f"{TRL_IMPORT_STATUS_PREFIX}{job_id}", - json.dumps({"status": "processing", "message": "Insertando..."}).encode("utf-8"), - ex=TRL_IMPORT_REDIS_TTL, - ) - except Exception as e: - logger.debug(f"Trailers import: could not write processing status: {e}") - - def run_commit_background(): - try: - run_commit_sync(job_id) - except Exception as e: - logger.exception(f"Trailers import: background commit failed: {e}") - - threading.Thread(target=run_commit_background, daemon=True).start() - - return { - "status": "committing", - "message": "Inserción iniciada.", - "commit_job_id": job_id, - } - - -@router.get("/{job_id}/errors/scan-csv") -async def download_scan_errors_csv(job_id: str): - return download_scan_errors_csv_stream("trl", job_id) diff --git a/backend/api/v1/modules/a76/layouts_csv/trailers/schemas.py b/backend/api/v1/modules/a76/layouts_csv/trailers/schemas.py deleted file mode 100644 index c5d2c2cc..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/trailers/schemas.py +++ /dev/null @@ -1,23 +0,0 @@ -from pydantic import BaseModel -from typing import Optional - - -class ImportJobResponse(BaseModel): - job_id: str - status: str - message: str - - -class ImportJobStatus(BaseModel): - status: str - job_id: str - total_rows: Optional[int] = 0 - error_count: Optional[int] = 0 - valid_rows: Optional[int] = 0 - error: Optional[str] = None - inserted: Optional[int] = 0 - updated: Optional[int] = 0 - skipped_invalid: Optional[int] = 0 - skipped_missing_fk: Optional[int] = 0 - skipped_duplicate: Optional[int] = 0 - skipped_details: Optional[list] = None diff --git a/backend/api/v1/modules/a76/layouts_csv/trailers/tasks.py b/backend/api/v1/modules/a76/layouts_csv/trailers/tasks.py deleted file mode 100644 index f37f605f..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/trailers/tasks.py +++ /dev/null @@ -1,409 +0,0 @@ -""" -Tareas Celery para importación CSV de Trailers y Cajas. -Flujo: scan_file (validación) → insert_valid_rows (commit). -Usa layouts_csv.common (storage, normalize, meta, responses); CSV con headers duplicados (dedupe). -""" -import json -import logging -import os -from typing import Dict, Any, Optional, List, Set - -from core.celery_app import celery_app -from core.database import CoreSessionLocal - -from ..common import storage as common_storage -from ..common import normalize as common_normalize -from ..common import meta as common_meta -from ..common import responses as common_responses -from ..common import csv_reader as common_csv_reader -from .template_config import row_from_template -from .validators import validate_row_trailer, validate_row_trailer_desfase -from .common.mappers import row_to_trailer_data, row_to_trailer_data_for_update -from .common.fk_loader import load_trailers_fk_sets - -logger = logging.getLogger(__name__) - -JOB_TYPE = "trl" - -# Para routes.py -TRL_IMPORT_FILE_PREFIX = "trl_import_file:" -TRL_IMPORT_META_PREFIX = "trl_import_meta:" -TRL_IMPORT_ERROR_LINES_PREFIX = "trl_import_error_lines:" -TRL_IMPORT_STATUS_PREFIX = "trl_import_status:" -TRL_IMPORT_REDIS_TTL = common_storage.IMPORT_REDIS_TTL - - -def _get_redis(): - import redis - url = os.getenv("VALKEY_URL", os.getenv("REDIS_URL", "redis://valkey:6379/0")) - return redis.Redis.from_url(url, decode_responses=False) - - -def _do_scan(job_id: str, progress_callback: Optional[Any] = None) -> Dict[str, Any]: - file_path = common_storage.ensure_file_from_redis(JOB_TYPE, job_id, "Trailers import") - if not file_path: - return {"status": "failed", "error": "Archivo no encontrado (expirado o no subido). Sube de nuevo."} - common_storage.ensure_meta_from_redis(JOB_TYPE, job_id, file_path, "Trailers import") - - error_path = common_storage.error_path_for_job(JOB_TYPE, job_id) - - try: - total_rows = common_csv_reader.count_csv_rows(file_path, has_header=True) - except Exception as e: - return {"status": "failed", "error": str(e)} - - try: - tenant_id, company_id = common_meta.require_tenant_context(file_path) - except ValueError as e: - return {"status": "failed", "error": str(e)} - - meta = common_meta.load_meta(file_path) or {} - actualizar = meta.get("actualizar", False) - existing_trailer_numbers: Set[str] = set() - if actualizar: - try: - from api.v1.modules.a76.transportation.trailers.models import Trailer - with CoreSessionLocal() as session: - for t in ( - session.query(Trailer.trailer_number) - .filter( - Trailer.tenant_id == tenant_id, - Trailer.company_id == company_id, - ) - .all() - ): - if t[0] and (t[0] or "").strip(): - existing_trailer_numbers.add((t[0] or "").strip()) - except Exception as e: - logger.warning("Trailers import: could not load existing trailer_numbers for actualizar: %s", e) - - ( - valid_trailer_type_keys, - valid_country_ame, - state_descriptions_upper, - state_country_set, - state_ame_to_description, - ) = load_trailers_fk_sets(tenant_id, company_id) - - error_count = 0 - processed_rows = 0 - errors_detail: List[Dict[str, Any]] = [] - error_lines_list: List[int] = [] - - try: - with open(error_path, "w", encoding="utf-8") as f_err: - for i, row in common_csv_reader.iter_csv_rows_deduped_headers(file_path): - if progress_callback: - progress_callback(i, total_rows, error_count) - - row_norm = row_from_template(row, common_normalize.normalize_header) - # Desfase: advertencia no bloqueante (se muestra en UI pero no se agrega a error_lines) - warn = validate_row_trailer_desfase(row_norm, i) - if warn and len(errors_detail) < 500: - errors_detail.append({ - "line": warn.get("line", i), - "col": warn.get("col", ""), - "msg": warn.get("msg", ""), - "solution": warn.get("solution", ""), - "warning": bool(warn.get("warning", False)), - }) - - row_errors = validate_row_trailer( - row_norm, - i, - actualizar=actualizar, - existing_trailer_numbers=existing_trailer_numbers, - valid_trailer_type_keys=valid_trailer_type_keys, - valid_country_ame=valid_country_ame, - state_descriptions_upper=state_descriptions_upper, - state_country_set=state_country_set, - state_ame_to_description=state_ame_to_description, - ) - if row_errors: - has_blocking_error = any(not e.get("warning", False) for e in row_errors) - if has_blocking_error: - error_count += 1 - error_lines_list.append(i) - for e in row_errors: - if not e.get("warning", False): - f_err.write(json.dumps(e) + "\n") - if len(errors_detail) < 500: - for e in row_errors: - if len(errors_detail) >= 500: - break - errors_detail.append({ - "line": e.get("line", i), - "col": e.get("col", ""), - "msg": e.get("msg", ""), - "solution": e.get("solution", ""), - "warning": bool(e.get("warning", False)), - }) - processed_rows += 1 - - if error_lines_list: - common_storage.store_error_lines(JOB_TYPE, job_id, error_lines_list) - except Exception as e: - logger.error("Trailers import scan failed: %s", e) - return {"status": "failed", "error": str(e)} - - return common_responses.scan_result(job_id, processed_rows, error_count, errors_detail) - - -def run_scan_sync(job_id: str) -> Dict[str, Any]: - result = _do_scan(job_id, progress_callback=None) - try: - r = _get_redis() - r.set( - f"{TRL_IMPORT_STATUS_PREFIX}{job_id}", - json.dumps(result).encode("utf-8"), - ex=TRL_IMPORT_REDIS_TTL, - ) - except Exception as e: - logger.warning("Trailers import: failed to store scan status in Redis: %s", e) - return result - - -@celery_app.task(bind=True) -def scan_file(self, job_id: str, config: str = None): - logger.info("Trailers import: starting scan for job %s", job_id) - - def on_progress(current: int, total: int, errors: int) -> None: - self.update_state(state="PROGRESS", meta={"current": current, "total": total, "errors": errors}) - - result = _do_scan(job_id, progress_callback=on_progress) - try: - r = _get_redis() - r.set( - f"{TRL_IMPORT_STATUS_PREFIX}{job_id}", - json.dumps(result).encode("utf-8"), - ex=TRL_IMPORT_REDIS_TTL, - ) - except Exception as e: - logger.warning("Trailers import: failed to store scan status in Redis: %s", e) - return result - - -def _do_commit(job_id: str) -> Dict[str, Any]: - file_path = common_storage.ensure_file_from_redis(JOB_TYPE, job_id, "Trailers import") - if not file_path: - alt_path = common_storage.file_path_for_job(JOB_TYPE, job_id) - if not os.path.exists(alt_path): - return {"status": "failed", "error": "Archivo no encontrado (expirado). Sube y confirma de nuevo."} - file_path = alt_path - else: - common_storage.ensure_meta_from_redis(JOB_TYPE, job_id, file_path, "Trailers import") - - error_path = common_storage.error_path_for_job(JOB_TYPE, job_id) - error_lines = common_storage.get_error_lines(JOB_TYPE, job_id, error_path) - - try: - tenant_id, company_id = common_meta.require_tenant_context(file_path) - except ValueError as e: - return {"status": "failed", "error": str(e)} - - meta = common_meta.load_meta(file_path) or {} - actualizar = meta.get("actualizar", False) - existing_trailer_numbers: Set[str] = set() - if actualizar: - try: - from api.v1.modules.a76.transportation.trailers.models import Trailer - with CoreSessionLocal() as session: - for t in ( - session.query(Trailer.trailer_number) - .filter( - Trailer.tenant_id == tenant_id, - Trailer.company_id == company_id, - ) - .all() - ): - if t[0] and (t[0] or "").strip(): - existing_trailer_numbers.add((t[0] or "").strip()) - except Exception as e: - logger.warning("Trailers import: could not load existing trailer_numbers for actualizar: %s", e) - - ( - valid_trailer_type_keys, - valid_country_ame, - state_descriptions_upper, - state_country_set, - state_ame_to_description, - ) = load_trailers_fk_sets(tenant_id, company_id) - - from api.v1.modules.a76.transportation.trailers.services import TrailerService - from api.v1.modules.a76.transportation.trailers.dto import TrailerCreateDTO, TrailerUpdateDTO - - inserted_count = 0 - updated_count = 0 - skipped_invalid = 0 - skipped_duplicate = 0 - skipped_details: List[Dict[str, Any]] = [] - seen_keys_in_file: Dict[str, int] = {} - meta_path = common_meta.get_meta_path(file_path) - - try: - with CoreSessionLocal() as session: - for i, row in common_csv_reader.iter_csv_rows_deduped_headers(file_path): - if i in error_lines: - continue - - row_norm = row_from_template(row, common_normalize.normalize_header) - row_errors = validate_row_trailer( - row_norm, - i, - actualizar=actualizar, - existing_trailer_numbers=existing_trailer_numbers, - valid_trailer_type_keys=valid_trailer_type_keys, - valid_country_ame=valid_country_ame, - state_descriptions_upper=state_descriptions_upper, - state_country_set=state_country_set, - state_ame_to_description=state_ame_to_description, - ) - if row_errors and any(not e.get("warning", False) for e in row_errors): - skipped_invalid += 1 - tn = (row_norm.get("NUMERO TRAILER") or row_norm.get("CLAVE TRAILER") or "").strip()[:20] or "-" - blocking_errors = [e for e in row_errors if not e.get("warning", False)] - for blocking in blocking_errors: - skipped_details.append({ - "line": i, - "trailer_number": tn, - "invoice": tn, - "reason": f"{blocking.get('col', '')}: {blocking.get('msg', '')}", - "solution": blocking.get("solution", ""), - }) - continue - - tn = (row_norm.get("NUMERO TRAILER") or row_norm.get("CLAVE TRAILER") or "").strip()[:20] or "" - if not tn: - skipped_invalid += 1 - continue - - if tn in seen_keys_in_file: - skipped_duplicate += 1 - skipped_details.append({ - "line": i, - "trailer_number": tn, - "invoice": tn, - "reason": "Clave duplicada en el archivo (se usa la primera)", - "solution": "El sistema conserva la primera ocurrencia; no dupliques el NUMERO TRAILER (CLAVE TRAILER) en el archivo.", - }) - continue - seen_keys_in_file[tn] = i - - existing = TrailerService.get_by_id(session, tn, tenant_id, company_id) - try: - if existing: - if actualizar: - data = row_to_trailer_data_for_update(row_norm, existing) - else: - data = row_to_trailer_data(row_norm) - if not data or not data.get("trailer_number"): - skipped_invalid += 1 - continue - update_data = TrailerUpdateDTO(**{k: v for k, v in data.items() if k != "trailer_number"}) - TrailerService.update(session, tn, tenant_id, update_data, company_id) - updated_count += 1 - else: - data = row_to_trailer_data(row_norm) - if not data or not data.get("trailer_number"): - skipped_invalid += 1 - continue - create_data = TrailerCreateDTO(**data) - TrailerService.create(session, create_data, tenant_id, company_id) - inserted_count += 1 - except Exception as db_err: - session.rollback() - skipped_invalid += 1 - skipped_details.append({ - "line": i, - "trailer_number": tn, - "invoice": tn, - "reason": str(db_err), - "solution": "Revisar el CSV para el NUMERO TRAILER indicado y corregir los datos/catálogos para resolver el error.", - }) - continue - - try: - session.commit() - except Exception as db_err: - session.rollback() - logger.error("Trailers import DB error: %s", db_err) - return {"status": "failed", "error": str(db_err)} - - except Exception as e: - logger.exception("Trailers import task failed") - return {"status": "failed", "error": str(e)} - - common_storage.cleanup_import_job( - JOB_TYPE, job_id, - file_path=file_path, - error_path=error_path, - meta_path=meta_path, - ) - try: - r = _get_redis() - r.delete(f"{TRL_IMPORT_STATUS_PREFIX}{job_id}") - except Exception as e: - logger.warning("Trailers import: failed to delete status key: %s", e) - - total_ok = inserted_count + updated_count - if total_ok == 0 and (skipped_invalid + skipped_duplicate) > 0: - return { - "status": "warning", - "inserted": inserted_count, - "updated": updated_count, - "skipped_invalid": skipped_invalid, - "skipped_duplicate": skipped_duplicate, - "skipped_missing_fk": 0, - "skipped_details": skipped_details, - "message": f"No se insertaron registros. {skipped_invalid + skipped_duplicate} rechazados.", - } - if total_ok == 0: - return { - "status": "failed", - "error": "No hay registros válidos en el archivo CSV", - "inserted": 0, - "updated": 0, - "skipped_invalid": skipped_invalid, - "skipped_duplicate": skipped_duplicate, - "skipped_missing_fk": 0, - "skipped_details": skipped_details, - } - return { - "status": "finished", - "inserted": inserted_count, - "updated": updated_count, - "skipped_invalid": skipped_invalid, - "skipped_duplicate": skipped_duplicate, - "skipped_missing_fk": 0, - "skipped_details": skipped_details, - } - - -def run_commit_sync(job_id: str) -> Dict[str, Any]: - result = _do_commit(job_id) - try: - r = _get_redis() - r.set( - f"{TRL_IMPORT_STATUS_PREFIX}{job_id}", - json.dumps(result).encode("utf-8"), - ex=TRL_IMPORT_REDIS_TTL, - ) - except Exception as e: - logger.warning("Trailers import: failed to store commit status in Redis: %s", e) - return result - - -@celery_app.task(bind=True) -def insert_valid_rows(self, job_id: str): - logger.info("Trailers import: starting commit for job %s", job_id) - result = _do_commit(job_id) - try: - r = _get_redis() - r.set( - f"{TRL_IMPORT_STATUS_PREFIX}{job_id}", - json.dumps(result).encode("utf-8"), - ex=TRL_IMPORT_REDIS_TTL, - ) - except Exception as e: - logger.warning("Trailers import: failed to store commit status in Redis: %s", e) - return result diff --git a/backend/api/v1/modules/a76/layouts_csv/trailers/template_config.py b/backend/api/v1/modules/a76/layouts_csv/trailers/template_config.py deleted file mode 100644 index 91bd4cd9..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/trailers/template_config.py +++ /dev/null @@ -1,58 +0,0 @@ -""" -Configuración de plantilla CSV para Trailers / Cajas (EstructuraCatTrailers.xls). -Mapeo: NUMERO TRAILER → trailer_number, CLAVE ACE → ace_trailer_number, etc. -""" - -from typing import Dict, List, Any - -from ..common.cell_value import cell_to_str -from ..common.csv_hint_inject import inject_max_char_hints_into_columns -from ..common.inject_csv_labels_en import inject_en_labels_into_template_columns -from ..common.template_locale import merge_download_header_into_lookup, merge_labels_into_lookup -from .csv_max_chars_for_headers import TRAILERS_CSV_MAX_CHARS - -TEMPLATE_COLUMNS: Dict[str, List[Dict[str, Any]]] = { - "trailers": [ - {"canonical": "NUMERO TRAILER", "aliases": ["CLAVE TRAILER", "TRAILER NUMBER", "TRAILER", "NUMERO"]}, - {"canonical": "CLAVE ACE", "aliases": ["ACE", "NUMERO ACE", "ACE TRAILER"]}, - {"canonical": "TIPO TRAILER", "aliases": ["TIPO", "TRAILER TYPE", "TIPO CAJA"]}, - {"canonical": "PRECINTO", "aliases": ["SEAL"]}, - {"canonical": "CODIGO ENTIDAD", "aliases": ["ENTIDAD", "ENTITY CODE", "CODIGO DE ENTIDAD"]}, - {"canonical": "PLACAS", "aliases": ["PLACA", "PLATE NUMBER", "PLATE"]}, - {"canonical": "ESTADO", "aliases": ["STATE"]}, - {"canonical": "PAIS", "aliases": ["COUNTRY", "Pais"]}, - {"canonical": "CLAVE CONTENEDOR", "aliases": ["CONTAINER", "CONTAINER KEY", "CONTENEDOR"]}, - {"canonical": "COL_EXTRA", "aliases": ["COLUMNA I", "COL I", "COL 9"]}, - ], -} - -inject_en_labels_into_template_columns(TEMPLATE_COLUMNS) - -inject_max_char_hints_into_columns(TEMPLATE_COLUMNS.get("trailers"), TRAILERS_CSV_MAX_CHARS) - - -def build_normalized_lookup(normalize_header_fn) -> Dict[str, str]: - cols = TEMPLATE_COLUMNS.get("trailers") - if not cols: - return {} - lookup: Dict[str, str] = {} - for item in cols: - canonical = item["canonical"] - lookup[normalize_header_fn(canonical)] = canonical - for alias in item.get("aliases") or []: - lookup[normalize_header_fn(alias)] = canonical - merge_labels_into_lookup(item, canonical, normalize_header_fn, lookup) - merge_download_header_into_lookup(item, canonical, normalize_header_fn, lookup) - return lookup - - -def row_from_template(row: Dict[str, Any], normalize_header_fn) -> Dict[str, Any]: - lookup = build_normalized_lookup(normalize_header_fn) - if not lookup: - return {normalize_header_fn(k): cell_to_str(v) for k, v in row.items()} - out: Dict[str, Any] = {} - for csv_header, value in row.items(): - key_norm = normalize_header_fn(csv_header) - if key_norm in lookup: - out[lookup[key_norm]] = cell_to_str(value) - return out diff --git a/backend/api/v1/modules/a76/layouts_csv/trailers/validators/__init__.py b/backend/api/v1/modules/a76/layouts_csv/trailers/validators/__init__.py deleted file mode 100644 index 456065a3..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/trailers/validators/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .create import validate_row_trailer, validate_row_trailer_desfase - -__all__ = ["validate_row_trailer", "validate_row_trailer_desfase"] diff --git a/backend/api/v1/modules/a76/layouts_csv/trailers/validators/common.py b/backend/api/v1/modules/a76/layouts_csv/trailers/validators/common.py deleted file mode 100644 index cefda59f..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/trailers/validators/common.py +++ /dev/null @@ -1,131 +0,0 @@ -""" -Validaciones comunes de fila para import CSV de trailers. -Paridad Clarion: VALIDACIONES_TRAILER, VALIDA_TODA_TRAILER, VALIDA_PARCIAL_TRAILER. -""" -from typing import Dict, Any, Optional, Set, Tuple, List - -from ..common.common_validators import ( - MAX_LEN, - check_required, - check_max_length, - check_codigo_entidad_valores, - check_trailer_type_catalog, - check_pais_catalog_trailers, - check_estado_catalog_trailers, - check_estado_pais_consistency_trailers, -) - - -def validate_row_trailer_required(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]: - return check_required(row, "NUMERO TRAILER", MAX_LEN["trailer_number"], line_num) - - -def validate_row_trailer_lengths(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]: - checks = [ - ("CLAVE ACE", MAX_LEN["ace_trailer_number"]), - ("TIPO TRAILER", MAX_LEN["trailer_type_key"]), - ("PRECINTO", MAX_LEN["seal"]), - ("CODIGO ENTIDAD", MAX_LEN["entity_code"]), - ("CODIGO DE ENTIDAD", MAX_LEN["entity_code"]), - ("PLACAS", MAX_LEN["plate_number"]), - ("ESTADO", MAX_LEN["state"]), - ("PAIS", MAX_LEN["country"]), - ("CLAVE CONTENEDOR", MAX_LEN["container_key"]), - ] - for col, max_len in checks: - err = check_max_length(row, col, max_len, line_num) - if err: - return err - return None - - -def validaciones_trailer( - row: Dict[str, Any], - line_num: int, - valid_trailer_type_keys: Optional[Set[str]] = None, - valid_country_ame: Optional[Set[str]] = None, - state_descriptions_upper: Optional[Set[str]] = None, - state_country_set: Optional[Set[Tuple[str, str]]] = None, - state_ame_to_description: Optional[Dict[str, str]] = None, -) -> List[Dict[str, Any]]: - """ - VALIDACIONES_TRAILER: reglas compartidas (longitudes, tipo trailer, código entidad, - país, estado, estado-país). No exige CODIGO DE ENTIDAD obligatorio. - """ - errors: List[Dict[str, Any]] = [] - - err = validate_row_trailer_required(row, line_num) - if err: - errors.append(err) - - err = validate_row_trailer_lengths(row, line_num) - if err: - errors.append(err) - - err = check_codigo_entidad_valores(row, line_num) - if err: - errors.append(err) - - err = check_trailer_type_catalog(row, line_num, valid_trailer_type_keys) - if err: - errors.append(err) - - err = check_pais_catalog_trailers(row, line_num, valid_country_ame) - if err: - errors.append(err) - - err_estado, estado_resuelto_upper = check_estado_catalog_trailers( - row, line_num, state_descriptions_upper, state_ame_to_description - ) - if err_estado: - errors.append(err_estado) - - err = check_estado_pais_consistency_trailers( - row, line_num, state_country_set, estado_resuelto_upper - ) - if err: - errors.append(err) - - return errors - - -def valida_toda_trailer( - row: Dict[str, Any], - line_num: int, - valid_trailer_type_keys: Optional[Set[str]] = None, - valid_country_ame: Optional[Set[str]] = None, - state_descriptions_upper: Optional[Set[str]] = None, - state_country_set: Optional[Set[Tuple[str, str]]] = None, - state_ame_to_description: Optional[Dict[str, str]] = None, -) -> List[Dict[str, Any]]: - """VALIDA_TODA_TRAILER: mismo que validaciones_trailer (Col A ya validada antes).""" - return validaciones_trailer( - row, - line_num, - valid_trailer_type_keys=valid_trailer_type_keys, - valid_country_ame=valid_country_ame, - state_descriptions_upper=state_descriptions_upper, - state_country_set=state_country_set, - state_ame_to_description=state_ame_to_description, - ) - - -def valida_parcial_trailer( - row: Dict[str, Any], - line_num: int, - valid_trailer_type_keys: Optional[Set[str]] = None, - valid_country_ame: Optional[Set[str]] = None, - state_descriptions_upper: Optional[Set[str]] = None, - state_country_set: Optional[Set[Tuple[str, str]]] = None, - state_ame_to_description: Optional[Dict[str, str]] = None, -) -> List[Dict[str, Any]]: - """VALIDA_PARCIAL_TRAILER: solo validaciones_trailer (actualizar registro existente).""" - return validaciones_trailer( - row, - line_num, - valid_trailer_type_keys=valid_trailer_type_keys, - valid_country_ame=valid_country_ame, - state_descriptions_upper=state_descriptions_upper, - state_country_set=state_country_set, - state_ame_to_description=state_ame_to_description, - ) diff --git a/backend/api/v1/modules/a76/layouts_csv/trailers/validators/create.py b/backend/api/v1/modules/a76/layouts_csv/trailers/validators/create.py deleted file mode 100644 index f4f087f3..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/trailers/validators/create.py +++ /dev/null @@ -1,73 +0,0 @@ -""" -Punto de entrada de validación para import de una fila trailer. -Paridad Clarion: desfase (advertencia), CLAVE vacía, VALIDA_TODA vs VALIDA_PARCIAL según actualizar y clave existente. -""" -from typing import Dict, Any, Optional, Set, Tuple, List - -from .common import ( - validate_row_trailer_required, - valida_toda_trailer, - valida_parcial_trailer, -) -from ..common.common_validators import check_desfase_trailers - - -def validate_row_trailer( - row: Dict[str, Any], - line_num: int, - actualizar: bool = False, - existing_trailer_numbers: Optional[Set[str]] = None, - valid_trailer_type_keys: Optional[Set[str]] = None, - valid_country_ame: Optional[Set[str]] = None, - state_descriptions_upper: Optional[Set[str]] = None, - state_country_set: Optional[Set[Tuple[str, str]]] = None, - state_ame_to_description: Optional[Dict[str, str]] = None, -) -> List[Dict[str, Any]]: - """ - Valida una fila de CSV de trailers. - 1. Clave (NUMERO TRAILER) vacía → error. - 2. Si actualizar y clave en existing_trailer_numbers → valida_parcial_trailer. - 3. Si no actualizar o clave no existe → valida_toda_trailer. - Desfase (COL_EXTRA) no se valida aquí; el caller puede llamar validate_row_trailer_desfase para advertencias no bloqueantes. - """ - errors: List[Dict[str, Any]] = [] - err = validate_row_trailer_required(row, line_num) - if err: - errors.append(err) - - existing = existing_trailer_numbers or set() - clave = (row.get("NUMERO TRAILER") or row.get("CLAVE TRAILER") or "").strip() - use_partial = actualizar and bool(clave and clave in existing) - - if use_partial: - errors.extend( - valida_parcial_trailer( - row, - line_num, - valid_trailer_type_keys=valid_trailer_type_keys, - valid_country_ame=valid_country_ame, - state_descriptions_upper=state_descriptions_upper, - state_country_set=state_country_set, - state_ame_to_description=state_ame_to_description, - ) - ) - else: - errors.extend( - valida_toda_trailer( - row, - line_num, - valid_trailer_type_keys=valid_trailer_type_keys, - valid_country_ame=valid_country_ame, - state_descriptions_upper=state_descriptions_upper, - state_country_set=state_country_set, - state_ame_to_description=state_ame_to_description, - ) - ) - return errors - - -def validate_row_trailer_desfase(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]: - """ - Advertencia de desfase si COL_EXTRA tiene valor. No bloqueante; el caller puede acumular en warnings. - """ - return check_desfase_trailers(row, line_num) diff --git a/backend/api/v1/modules/a76/layouts_csv/transportistas/__init__.py b/backend/api/v1/modules/a76/layouts_csv/transportistas/__init__.py deleted file mode 100644 index be468a87..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/transportistas/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# layouts_csv.transportistas: CSV import for Transportistas (carriers catalog). Paridad Clarion. diff --git a/backend/api/v1/modules/a76/layouts_csv/transportistas/common/__init__.py b/backend/api/v1/modules/a76/layouts_csv/transportistas/common/__init__.py deleted file mode 100644 index e2a4a863..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/transportistas/common/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# transportistas common: fk_loader, common_validators, mappers diff --git a/backend/api/v1/modules/a76/layouts_csv/transportistas/common/common_validators.py b/backend/api/v1/modules/a76/layouts_csv/transportistas/common/common_validators.py deleted file mode 100644 index 7f7630b2..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/transportistas/common/common_validators.py +++ /dev/null @@ -1,143 +0,0 @@ -""" -Validadores reutilizables para import CSV de transportistas. -Paridad Clarion: VALIDACIONES_TRANSPORTISTAS (estado, país, estado-país), desfase Col S. -""" -from typing import Dict, Any, Optional, Set, Tuple - -# Max lengths from Transporter model (a76.transporter) -MAX_LEN = { - "transporter_key": 23, - "name": 256, - "short_name": 10, - "responsible": 100, - "rfc": 30, - "streets": 100, - "postal_code": 15, - "city": 30, - "state": 30, - "country": 3, - "loader_code": 9, - "caat_code": 49, - "transport_code": 8, - "transport_interface_type": 20, - "ftp_server": 200, - "ftp_user": 200, - "ftp_password": 100, - "ftp_directory": 1000, -} - - -def check_required(row: Dict[str, Any], col: str, max_len: int, line_num: int) -> Optional[Dict[str, Any]]: - val = (row.get(col) or "").strip() - if not val: - return {"line": line_num, "col": col, "msg": "Requerido"} - if max_len and len(val) > max_len: - return {"line": line_num, "col": col, "msg": f"Máximo {max_len} caracteres"} - return None - - -def check_max_length( - row: Dict[str, Any], - col: str, - max_len: int, - line_num: int, - required: bool = False, -) -> Optional[Dict[str, Any]]: - val = (row.get(col) or "").strip() - if not val: - if required: - return {"line": line_num, "col": col, "msg": "Requerido"} - return None - if len(val) > max_len: - return {"line": line_num, "col": col, "msg": f"Máximo {max_len} caracteres"} - return None - - -def check_desfase_transportistas(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]: - """Si COL_EXTRA (columna 19 / Col S) tiene valor → advertencia de desfase (Clarion, no bloqueante).""" - val = (row.get("COL_EXTRA") or "").strip() - if not val: - return None - return { - "line": line_num, - "col": "COL_EXTRA", - "msg": "Advertencia: Podría existir un desfase en esta línea.", - "solution": "Revisar esta línea del archivo CSV y verificar cada campo esté en la posición correcta.", - "warning": True, - } - - -def check_pais_catalog_transportistas( - row: Dict[str, Any], - line_num: int, - valid_country_ame: Optional[Set[str]] = None, -) -> Optional[Dict[str, Any]]: - """Col J (PAIS): Si no vacío, debe ser clave americana (2 chars) y existir en catálogo (GPaises.Pais_Ame).""" - val = (row.get("PAIS") or "").strip() - if not val or valid_country_ame is None: - return None - val_upper = val.upper() - if len(val) > 2: - return { - "line": line_num, - "col": "PAIS", - "msg": f"Error: (Celda J{line_num}) El Pais: {val} Es Incorrecto", - "solution": "Capturar en columna J un Pais en Clave Americana(US = Estados Unidos, MX = Mexico, ES = España, etc).", - } - if val_upper in valid_country_ame: - return None - return { - "line": line_num, - "col": "PAIS", - "msg": f"Error: (Col. J) El Pais: {val} es incorrecto.", - "solution": "Capturar en columna J el Pais del Transportista en Clave Americana.", - } - - -def check_estado_catalog_transportistas( - row: Dict[str, Any], - line_num: int, - state_descriptions_upper: Optional[Set[str]] = None, -) -> Optional[Dict[str, Any]]: - """Col I (ESTADO): Si no vacío, debe existir en catálogo (GEstados por descripción). Si hay estado, PAIS no puede estar vacío.""" - val = (row.get("ESTADO") or "").strip() - if not val or state_descriptions_upper is None: - return None - val_upper = val.upper() - if val_upper not in state_descriptions_upper: - return { - "line": line_num, - "col": "ESTADO", - "msg": f"Error: (Celda I{line_num}) El Estado: {val} es incorrecto.", - "solution": "Capturar en columna I el Estado del Transportista en Clave Americana o Nombre Completo.", - } - pais = (row.get("PAIS") or "").strip() - if not pais: - return { - "line": line_num, - "col": "PAIS", - "msg": f"Error: (Celda I{line_num}) El Estado: {val} No esta ligado a ningun Pais.", - "solution": "Capturar en columna J un Pais en Clave Americana(US = Estados Unidos, MX = Mexico, ES = España, etc).", - } - return None - - -def check_estado_pais_consistency_transportistas( - row: Dict[str, Any], - line_num: int, - state_country_set: Optional[Set[Tuple[str, str]]] = None, -) -> Optional[Dict[str, Any]]: - """Si hay ESTADO y PAIS, validar que el estado pertenezca al país (Clarion GEstados-GPaises).""" - estado = (row.get("ESTADO") or "").strip() - pais = (row.get("PAIS") or "").strip().upper() - if not estado or not pais or state_country_set is None: - return None - key = (pais, estado.upper()) - if key in state_country_set: - return None - return { - "line": line_num, - "col": "ESTADO", - "msg": f"Error: (Col. I) EL Estado: {estado} no pertenece al Pais: {pais}.", - "solution": "Capturar en columna I un Estado que pertenesca al Pais de la columna J.", - } diff --git a/backend/api/v1/modules/a76/layouts_csv/transportistas/common/fk_loader.py b/backend/api/v1/modules/a76/layouts_csv/transportistas/common/fk_loader.py deleted file mode 100644 index ad60c9b2..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/transportistas/common/fk_loader.py +++ /dev/null @@ -1,96 +0,0 @@ -""" -Carga de conjuntos FK para validación de import CSV de transportistas. -Clarion: GTransportista (ClaveTrans), GPaises (Pais_Ame), GEstados (Descripcion), relación Estado-País. -""" -from typing import Set, Tuple, Optional -from sqlalchemy.orm import Session -import logging - -from core.database import CoreSessionLocal - -logger = logging.getLogger(__name__) - - -def load_transportistas_fk_sets( - tenant_id: Optional[int] = None, - company_id: Optional[int] = None, - db: Optional[Session] = None, -) -> Tuple[ - Set[str], - Set[str], - Set[str], - Set[Tuple[str, str]], -]: - """ - Carga conjuntos para validación CSV de transportistas (paridad Clarion). - Devuelve: - - existing_transporter_keys: claves de transportistas existentes (tenant/company) en mayúsculas - - valid_country_ame: claves americana de países (GPaises.Pais_Ame), mayúsculas - - state_descriptions_upper: descripciones y claves cortas (mex_key) de estados en mayúsculas (GEstados) - - state_country_set: set de (ame_key_pais, token_upper) para description o mex_key - """ - existing_transporter_keys: Set[str] = set() - valid_country_ame: Set[str] = set() - state_descriptions_upper: Set[str] = set() - state_country_set: Set[Tuple[str, str]] = set() - - def _load(session: Session): - from api.v1.modules.a76.transportation.transporters.models import Transporter - from api.v1.modules.public.reference_data.countries.models import Country - from api.v1.modules.public.reference_data.states.models import State - - if tenant_id is not None and company_id is not None: - for row in ( - session.query(Transporter.transporter_key) - .filter( - Transporter.tenant_id == tenant_id, - Transporter.company_id == company_id, - ) - .all() - ): - if row[0] and (row[0] or "").strip(): - existing_transporter_keys.add((row[0] or "").strip().upper()) - - for row in session.query(Country.ame_key).all(): - if row[0]: - valid_country_ame.add((row[0] or "").strip().upper()) - - for state in session.query(State).all(): - country = ( - session.query(Country) - .filter(Country.m3_key == state.m3_key) - .first() - ) - ame = None - if country and (country.ame_key or "").strip(): - ame = (country.ame_key or "").strip().upper() - - desc = (state.description or "").strip() - if desc: - state_descriptions_upper.add(desc.upper()) - if ame: - state_country_set.add((ame, desc.upper())) - - mex_key = (state.mex_key or "").strip() - if mex_key: - mk = mex_key.upper() - state_descriptions_upper.add(mk) - if ame: - state_country_set.add((ame, mk)) - - try: - if db: - _load(db) - else: - with CoreSessionLocal() as session: - _load(session) - - except Exception as e: - logger.warning("Transportistas import: could not load FK sets: %s", e) - - return ( - existing_transporter_keys, - valid_country_ame, - state_descriptions_upper, - state_country_set, - ) diff --git a/backend/api/v1/modules/a76/layouts_csv/transportistas/common/mappers.py b/backend/api/v1/modules/a76/layouts_csv/transportistas/common/mappers.py deleted file mode 100644 index 0b93f40d..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/transportistas/common/mappers.py +++ /dev/null @@ -1,95 +0,0 @@ -""" -Mapeo fila CSV → datos para Transporter. -Paridad Clarion LLENA_TRANSPORTISTAS / VALIDA_PARCIAL: en actualización, campo vacío en CSV usa valor existente. -UPPER aplicado según Clarion: NOMBRE CORTO, CODIGO POSTAL, PAIS, CODIGO CARGADOR, CODIGO CAAT, CODIGO TRANS, etc. -""" -from typing import Dict, Any, Optional, Union - -from .common_validators import MAX_LEN - - -def _str_or_none(val: Any, max_len: Optional[int] = None, upper: bool = False) -> Optional[str]: - if val is None: - return None - s = str(val).strip() - if not s: - return None - if upper: - s = s.upper() - if max_len and len(s) > max_len: - return s[:max_len] - return s - - -def _get_existing_val(existing: Union[Any, Dict[str, Any]], key: str) -> Any: - if existing is None: - return None - if isinstance(existing, dict): - return existing.get(key) - return getattr(existing, key, None) - - -def row_to_transporter_data(row_norm: Dict[str, Any]) -> Dict[str, Any]: - """Build dict suitable for TransporterCreateDTO from normalized row (Clarion QueCSV → GenTra).""" - transporter_key = _str_or_none(row_norm.get("CLAVE TRANSPORTISTA"), MAX_LEN["transporter_key"]) - if not transporter_key: - return {} - return { - "transporter_key": transporter_key, - "name": _str_or_none(row_norm.get("NOMBRE"), MAX_LEN["name"]), - "short_name": _str_or_none(row_norm.get("NOMBRE CORTO"), MAX_LEN["short_name"], upper=True), - "responsible": _str_or_none(row_norm.get("RESPONSABLE"), MAX_LEN["responsible"]), - "rfc": _str_or_none(row_norm.get("RFC"), MAX_LEN["rfc"]), - "streets": _str_or_none(row_norm.get("CALLES"), MAX_LEN["streets"]), - "postal_code": _str_or_none(row_norm.get("CODIGO POSTAL"), MAX_LEN["postal_code"], upper=True), - "city": _str_or_none(row_norm.get("CIUDAD"), MAX_LEN["city"]), - "state": _str_or_none(row_norm.get("ESTADO"), MAX_LEN["state"]), - "country": _str_or_none(row_norm.get("PAIS"), MAX_LEN["country"], upper=True), - "loader_code": _str_or_none(row_norm.get("CODIGO CARGADOR"), MAX_LEN["loader_code"], upper=True), - "caat_code": _str_or_none(row_norm.get("CODIGO CAAT"), MAX_LEN["caat_code"], upper=True), - "transport_code": _str_or_none(row_norm.get("CODIGO TRANS"), MAX_LEN["transport_code"], upper=True), - "transport_interface_type": _str_or_none(row_norm.get("TIPO INTERFASE TRANS"), MAX_LEN["transport_interface_type"]), - "ftp_server": _str_or_none(row_norm.get("SERVIDOR FTP"), MAX_LEN["ftp_server"]), - "ftp_user": _str_or_none(row_norm.get("USUARIO FTP"), MAX_LEN["ftp_user"]), - "ftp_password": _str_or_none(row_norm.get("CLAVE ACCESO FTP"), MAX_LEN["ftp_password"]), - "ftp_directory": _str_or_none(row_norm.get("DIRECTORIO FTP"), MAX_LEN["ftp_directory"]), - } - - -def row_to_transporter_data_for_update( - row_norm: Dict[str, Any], - existing_transporter: Union[Any, Dict[str, Any]], -) -> Dict[str, Any]: - """ - Build dict for TransporterUpdateDTO: CSV value if non-empty, else existing (Clarion VALIDA_PARCIAL). - """ - transporter_key = _str_or_none(row_norm.get("CLAVE TRANSPORTISTA"), MAX_LEN["transporter_key"]) or _get_existing_val(existing_transporter, "transporter_key") - if not transporter_key: - return {} - - def _csv_or_existing(csv_key: str, dto_key: str, max_len: Optional[int] = None, upper: bool = False): - v = _str_or_none(row_norm.get(csv_key), max_len, upper=upper) - if v is not None and v != "": - return v - return _get_existing_val(existing_transporter, dto_key) - - return { - "transporter_key": transporter_key, - "name": _csv_or_existing("NOMBRE", "name", MAX_LEN["name"]), - "short_name": _csv_or_existing("NOMBRE CORTO", "short_name", MAX_LEN["short_name"], upper=True), - "responsible": _csv_or_existing("RESPONSABLE", "responsible", MAX_LEN["responsible"]), - "rfc": _csv_or_existing("RFC", "rfc", MAX_LEN["rfc"]), - "streets": _csv_or_existing("CALLES", "streets", MAX_LEN["streets"]), - "postal_code": _csv_or_existing("CODIGO POSTAL", "postal_code", MAX_LEN["postal_code"], upper=True), - "city": _csv_or_existing("CIUDAD", "city", MAX_LEN["city"]), - "state": _csv_or_existing("ESTADO", "state", MAX_LEN["state"]), - "country": _csv_or_existing("PAIS", "country", MAX_LEN["country"], upper=True), - "loader_code": _csv_or_existing("CODIGO CARGADOR", "loader_code", MAX_LEN["loader_code"], upper=True), - "caat_code": _csv_or_existing("CODIGO CAAT", "caat_code", MAX_LEN["caat_code"], upper=True), - "transport_code": _csv_or_existing("CODIGO TRANS", "transport_code", MAX_LEN["transport_code"], upper=True), - "transport_interface_type": _csv_or_existing("TIPO INTERFASE TRANS", "transport_interface_type", MAX_LEN["transport_interface_type"]), - "ftp_server": _csv_or_existing("SERVIDOR FTP", "ftp_server", MAX_LEN["ftp_server"]), - "ftp_user": _csv_or_existing("USUARIO FTP", "ftp_user", MAX_LEN["ftp_user"]), - "ftp_password": _csv_or_existing("CLAVE ACCESO FTP", "ftp_password", MAX_LEN["ftp_password"]), - "ftp_directory": _csv_or_existing("DIRECTORIO FTP", "ftp_directory", MAX_LEN["ftp_directory"]), - } diff --git a/backend/api/v1/modules/a76/layouts_csv/transportistas/csv_max_chars_for_headers.py b/backend/api/v1/modules/a76/layouts_csv/transportistas/csv_max_chars_for_headers.py deleted file mode 100644 index 9c51441d..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/transportistas/csv_max_chars_for_headers.py +++ /dev/null @@ -1,25 +0,0 @@ -"""Límites alineados a transportistas/common/common_validators.MAX_LEN.""" -from typing import Dict - -from .common.common_validators import MAX_LEN as TR_MAX - -TRANSPORTISTAS_CSV_MAX_CHARS: Dict[str, int] = { - "CLAVE TRANSPORTISTA": TR_MAX["transporter_key"], - "NOMBRE": TR_MAX["name"], - "NOMBRE CORTO": TR_MAX["short_name"], - "RESPONSABLE": TR_MAX["responsible"], - "RFC": TR_MAX["rfc"], - "CALLES": TR_MAX["streets"], - "CODIGO POSTAL": TR_MAX["postal_code"], - "CIUDAD": TR_MAX["city"], - "ESTADO": TR_MAX["state"], - "PAIS": TR_MAX["country"], - "CODIGO CARGADOR": TR_MAX["loader_code"], - "CODIGO CAAT": TR_MAX["caat_code"], - "CODIGO TRANS": TR_MAX["transport_code"], - "TIPO INTERFASE TRANS": TR_MAX["transport_interface_type"], - "SERVIDOR FTP": TR_MAX["ftp_server"], - "USUARIO FTP": TR_MAX["ftp_user"], - "CLAVE ACCESO FTP": TR_MAX["ftp_password"], - "DIRECTORIO FTP": TR_MAX["ftp_directory"], -} diff --git a/backend/api/v1/modules/a76/layouts_csv/transportistas/routes.py b/backend/api/v1/modules/a76/layouts_csv/transportistas/routes.py deleted file mode 100644 index 499da430..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/transportistas/routes.py +++ /dev/null @@ -1,204 +0,0 @@ -""" -Rutas de importación CSV para Transportistas. -Flujo: upload → scan → status (polling) → commit. -""" -import json -import logging -import os -import threading -from uuid import uuid4 - -from fastapi import APIRouter, File, HTTPException, Query, UploadFile, Depends -from sqlalchemy.orm import Session -from typing import Dict, Any - -from core.celery_app import celery_app -from core.database import get_core_db -from core.security import get_current_user, validate_access_to_resource -from api.v1.modules.core.tasks_tracking import track_and_dispatch - -from .schemas import ImportJobResponse -from .tasks import ( - scan_file, - run_scan_sync, - run_commit_sync, - JOB_TYPE, - TRP_IMPORT_STATUS_PREFIX, - TRP_IMPORT_REDIS_TTL, -) -from ..common import storage as common_storage -from ..common.error_csv import download_scan_errors_csv_stream -from ..common.responses import normalize_commit_status_payload - -router = APIRouter() -logger = logging.getLogger(__name__) - - -def _get_redis(): - import redis - url = os.getenv("VALKEY_URL", os.getenv("REDIS_URL", "redis://valkey:6379/0")) - return redis.Redis.from_url(url, decode_responses=False) - - -@router.post("/upload", response_model=ImportJobResponse) -async def upload_import_file( - file: UploadFile = File(...), - company_id: int = Query(..., description="Company ID"), - actualizar: bool = Query(False, description="Modo Agregar/Actualizar (ACT); si True, validación parcial para claves existentes"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - try: - tenant_id = validate_access_to_resource(db, company_id, current_user, ["csv_upload.process"]) - except Exception as e: - logger.error("Transportistas import: access validation failed: %s", e) - raise HTTPException(status_code=403, detail="Invalid company access") - - if not file.filename or not file.filename.lower().endswith(".csv"): - raise HTTPException(status_code=400, detail="Solo se permiten archivos .csv") - - job_id = str(uuid4()) - contents = await file.read() - - meta_data = { - "tenant_id": tenant_id, - "company_id": company_id, - "user_id": current_user.get("id"), - "template_id": "transporters", - "actualizar": actualizar, - } - - try: - common_storage.store_import_file( - JOB_TYPE, - job_id, - contents, - meta_data, - tenant_id=int(tenant_id), - company_id=company_id, - ttl=TRP_IMPORT_REDIS_TTL, - log_label="Transportistas import", - ) - except common_storage.ImportStoreError as e: - logger.error("Transportistas import: store error: %s", e) - raise HTTPException(status_code=500, detail="No se pudo encolar el archivo.") - except Exception as e: - logger.error("Transportistas import: Redis store error: %s", e) - raise HTTPException(status_code=500, detail="No se pudo encolar el archivo.") - - track_and_dispatch( - db=db, - task=scan_file, - tenant_id=int(tenant_id), - company_id=company_id, - requested_by_user=current_user.get("preferred_username") - or current_user.get("email") - or current_user.get("sub"), - task_name="transportistas_scan_file", - task_group="layouts_csv", - task_origin="a76/layouts_csv/transportistas/upload", - args=[job_id], - task_id=job_id, - ) - - def run_scan_background(): - try: - run_scan_sync(job_id) - except Exception as e: - logger.exception("Transportistas import: background scan failed: %s", e) - - threading.Thread(target=run_scan_background, daemon=True).start() - - return ImportJobResponse( - job_id=job_id, - status="queued", - message="Archivo subido. Escaneo iniciado.", - ) - - -@router.get("/{job_id}/status") -async def get_import_status(job_id: str): - try: - r = _get_redis() - raw = r.get(f"{TRP_IMPORT_STATUS_PREFIX}{job_id}") - if raw: - data = json.loads(raw.decode("utf-8")) - if isinstance(data, dict) and data.get("status") in ("finished", "warning"): - return normalize_commit_status_payload(data) - return data - except Exception as e: - logger.debug("Transportistas import: could not read status from Redis: %s", e) - - task_result = celery_app.AsyncResult(job_id) - - if task_result.state == "PENDING": - return {"status": "processing", "progress": 0} - if task_result.state == "PROGRESS": - info = (task_result.info or {}) - return { - "status": "processing", - "progress": info.get("current", 0), - "total": info.get("total", 0), - } - if task_result.state == "SUCCESS": - result = task_result.result - if isinstance(result, dict) and "status" in result: - return normalize_commit_status_payload(result) - return {"status": "finished", "result": result} - - result = getattr(task_result, "result", None) - if isinstance(result, dict) and result.get("status") in ("finished", "warning"): - return normalize_commit_status_payload(result) - - logger.warning("Transportistas import task %s failed: state=%s", job_id, task_result.state) - err_msg = None - tb = getattr(task_result, "traceback", None) - if tb and isinstance(tb, str): - lines = [l.strip() for l in tb.strip().split("\n") if l.strip()] - if lines: - err_msg = lines[-1] - if not err_msg: - try: - exc = task_result.get(propagate=False) - if exc is not None: - err_msg = str(exc) - except Exception: - pass - if not err_msg and result is not None: - if not isinstance(result, dict): - err_msg = str(result) - elif result.get("error") or result.get("message"): - err_msg = result.get("error") or result.get("message") - return {"status": "failed", "error": err_msg or "Task failed"} - - -@router.post("/{job_id}/commit") -async def commit_import_job(job_id: str): - try: - r = _get_redis() - r.set( - f"{TRP_IMPORT_STATUS_PREFIX}{job_id}", - json.dumps({"status": "processing", "message": "Insertando..."}).encode("utf-8"), - ex=TRP_IMPORT_REDIS_TTL, - ) - except Exception as e: - logger.debug("Transportistas import: could not write processing status: %s", e) - - def run_commit_background(): - try: - run_commit_sync(job_id) - except Exception as e: - logger.exception("Transportistas import: background commit failed: %s", e) - - threading.Thread(target=run_commit_background, daemon=True).start() - - return { - "status": "committing", - "message": "Inserción iniciada.", - "commit_job_id": job_id, - } - - -@router.get("/{job_id}/errors/scan-csv") -async def download_scan_errors_csv(job_id: str): - return download_scan_errors_csv_stream("trp", job_id) diff --git a/backend/api/v1/modules/a76/layouts_csv/transportistas/schemas.py b/backend/api/v1/modules/a76/layouts_csv/transportistas/schemas.py deleted file mode 100644 index dc0f5dc5..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/transportistas/schemas.py +++ /dev/null @@ -1,22 +0,0 @@ -from pydantic import BaseModel -from typing import Optional - - -class ImportJobResponse(BaseModel): - job_id: str - status: str - message: str - - -class ImportJobStatus(BaseModel): - status: str - job_id: str - total_rows: Optional[int] = 0 - error_count: Optional[int] = 0 - valid_rows: Optional[int] = 0 - error: Optional[str] = None - inserted: Optional[int] = 0 - updated: Optional[int] = 0 - skipped_invalid: Optional[int] = 0 - skipped_duplicate: Optional[int] = 0 - skipped_details: Optional[list] = None diff --git a/backend/api/v1/modules/a76/layouts_csv/transportistas/tasks.py b/backend/api/v1/modules/a76/layouts_csv/transportistas/tasks.py deleted file mode 100644 index 09664a24..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/transportistas/tasks.py +++ /dev/null @@ -1,367 +0,0 @@ -""" -Tareas Celery para importación CSV de Transportistas. -Flujo: scan_file (validación) → commit. -Usa layouts_csv.common (storage, normalize, meta, responses, csv_reader). -""" -import json -import logging -import os -from typing import Dict, Any, Optional, List, Set - -from core.celery_app import celery_app -from core.database import CoreSessionLocal - -from ..common import storage as common_storage -from ..common import normalize as common_normalize -from ..common import meta as common_meta -from ..common import responses as common_responses -from ..common import csv_reader as common_csv_reader -from .template_config import row_from_template -from .validators import validate_row_transporter, validate_row_transporter_desfase -from .common.mappers import row_to_transporter_data, row_to_transporter_data_for_update -from .common.fk_loader import load_transportistas_fk_sets - -logger = logging.getLogger(__name__) - -JOB_TYPE = "trp" - -# Para routes.py (deben coincidir con common_storage.storage_keys(JOB_TYPE, job_id)) -TRP_IMPORT_FILE_PREFIX = "trp_import_file:" -TRP_IMPORT_META_PREFIX = "trp_import_meta:" -TRP_IMPORT_ERROR_LINES_PREFIX = "trp_import_error_lines:" -TRP_IMPORT_STATUS_PREFIX = "trp_import_status:" -TRP_IMPORT_REDIS_TTL = common_storage.IMPORT_REDIS_TTL - - -def _get_redis(): - import redis - url = os.getenv("VALKEY_URL", os.getenv("REDIS_URL", "redis://valkey:6379/0")) - return redis.Redis.from_url(url, decode_responses=False) - - -def _do_scan(job_id: str, progress_callback: Optional[Any] = None) -> Dict[str, Any]: - file_path = common_storage.ensure_file_from_redis(JOB_TYPE, job_id, "Transportistas import") - if not file_path: - return {"status": "failed", "error": "Archivo no encontrado (expirado o no subido). Sube de nuevo."} - common_storage.ensure_meta_from_redis(JOB_TYPE, job_id, file_path, "Transportistas import") - - error_path = common_storage.error_path_for_job(JOB_TYPE, job_id) - - try: - total_rows = common_csv_reader.count_csv_rows(file_path) - except Exception as e: - return {"status": "failed", "error": str(e)} - - try: - tenant_id, company_id = common_meta.require_tenant_context(file_path) - except ValueError as e: - return {"status": "failed", "error": str(e)} - - meta = common_meta.load_meta(file_path) or {} - actualizar = meta.get("actualizar", False) - ( - existing_transporter_keys, - valid_country_ame, - state_descriptions_upper, - state_country_set, - ) = load_transportistas_fk_sets(tenant_id, company_id) - - if actualizar: - pass # existing_transporter_keys ya cargado - else: - existing_transporter_keys = set() - - error_count = 0 - processed_rows = 0 - errors_detail: List[Dict[str, Any]] = [] - error_lines_list: List[int] = [] - - try: - with open(error_path, "w", encoding="utf-8") as f_err: - for i, row in common_csv_reader.iter_csv_rows(file_path): - if progress_callback: - progress_callback(i, total_rows, error_count) - - row_norm = row_from_template(row, common_normalize.normalize_header) - warn = validate_row_transporter_desfase(row_norm, i) - if warn and len(errors_detail) < 500: - errors_detail.append({ - "line": warn.get("line", i), - "col": warn.get("col", ""), - "msg": warn.get("msg", ""), - "solution": warn.get("solution", ""), - "warning": bool(warn.get("warning", False)), - }) - - row_errors = validate_row_transporter( - row_norm, - i, - actualizar=actualizar, - existing_transporter_keys=existing_transporter_keys, - valid_country_ame=valid_country_ame, - state_descriptions_upper=state_descriptions_upper, - state_country_set=state_country_set, - ) - if row_errors: - has_blocking_error = any(not e.get("warning", False) for e in row_errors) - if has_blocking_error: - error_count += 1 - error_lines_list.append(i) - for e in row_errors: - if not e.get("warning", False): - f_err.write(json.dumps(e) + "\n") - if len(errors_detail) < 500: - for e in row_errors: - if len(errors_detail) >= 500: - break - errors_detail.append({ - "line": e.get("line", i), - "col": e.get("col", ""), - "msg": e.get("msg", ""), - "solution": e.get("solution", ""), - "warning": bool(e.get("warning", False)), - }) - processed_rows += 1 - - if error_lines_list: - common_storage.store_error_lines(JOB_TYPE, job_id, error_lines_list) - except Exception as e: - logger.error("Transportistas import scan failed: %s", e) - return {"status": "failed", "error": str(e)} - - return common_responses.scan_result(job_id, processed_rows, error_count, errors_detail) - - -def run_scan_sync(job_id: str) -> Dict[str, Any]: - result = _do_scan(job_id, progress_callback=None) - try: - r = _get_redis() - r.set( - f"{TRP_IMPORT_STATUS_PREFIX}{job_id}", - json.dumps(result).encode("utf-8"), - ex=TRP_IMPORT_REDIS_TTL, - ) - except Exception as e: - logger.warning("Transportistas import: failed to store scan status in Redis: %s", e) - return result - - -@celery_app.task(bind=True) -def scan_file(self, job_id: str, config: str = None): - logger.info("Transportistas import: starting scan for job %s", job_id) - - def on_progress(current: int, total: int, errors: int) -> None: - self.update_state(state="PROGRESS", meta={"current": current, "total": total, "errors": errors}) - - result = _do_scan(job_id, progress_callback=on_progress) - try: - r = _get_redis() - r.set( - f"{TRP_IMPORT_STATUS_PREFIX}{job_id}", - json.dumps(result).encode("utf-8"), - ex=TRP_IMPORT_REDIS_TTL, - ) - except Exception as e: - logger.warning("Transportistas import: failed to store scan status in Redis: %s", e) - return result - - -def _do_commit(job_id: str) -> Dict[str, Any]: - file_path = common_storage.ensure_file_from_redis(JOB_TYPE, job_id, "Transportistas import") - if not file_path: - alt_path = common_storage.file_path_for_job(JOB_TYPE, job_id) - if not os.path.exists(alt_path): - return {"status": "failed", "error": "Archivo no encontrado (expirado). Sube y confirma de nuevo."} - file_path = alt_path - else: - common_storage.ensure_meta_from_redis(JOB_TYPE, job_id, file_path, "Transportistas import") - - error_path = common_storage.error_path_for_job(JOB_TYPE, job_id) - error_lines = common_storage.get_error_lines(JOB_TYPE, job_id, error_path) - - try: - tenant_id, company_id = common_meta.require_tenant_context(file_path) - except ValueError as e: - return {"status": "failed", "error": str(e)} - - meta = common_meta.load_meta(file_path) or {} - actualizar = meta.get("actualizar", False) - ( - existing_transporter_keys, - valid_country_ame, - state_descriptions_upper, - state_country_set, - ) = load_transportistas_fk_sets(tenant_id, company_id) - - from api.v1.modules.a76.transportation.transporters.services import TransporterService - from api.v1.modules.a76.transportation.transporters.dto import TransporterCreateDTO, TransporterUpdateDTO - - inserted_count = 0 - updated_count = 0 - skipped_invalid = 0 - skipped_duplicate = 0 - skipped_details: List[Dict[str, Any]] = [] - seen_keys_in_file: Dict[str, int] = {} - meta_path = common_meta.get_meta_path(file_path) - - try: - with CoreSessionLocal() as session: - for i, row in common_csv_reader.iter_csv_rows(file_path): - if i in error_lines: - continue - - row_norm = row_from_template(row, common_normalize.normalize_header) - row_errors = validate_row_transporter( - row_norm, - i, - actualizar=actualizar, - existing_transporter_keys=existing_transporter_keys, - valid_country_ame=valid_country_ame, - state_descriptions_upper=state_descriptions_upper, - state_country_set=state_country_set, - ) - if row_errors and any(not e.get("warning", False) for e in row_errors): - skipped_invalid += 1 - tk = (row_norm.get("CLAVE TRANSPORTISTA") or "").strip()[:23] or "-" - blocking_errors = [e for e in row_errors if not e.get("warning", False)] - for blocking in blocking_errors: - skipped_details.append({ - "line": i, - "transporter_key": tk, - "reason": f"{blocking.get('col', '')}: {blocking.get('msg', '')}", - "solution": blocking.get("solution", ""), - }) - continue - - tk = (row_norm.get("CLAVE TRANSPORTISTA") or "").strip()[:23] or "" - if not tk: - skipped_invalid += 1 - continue - tk_upper = tk.upper() - if tk_upper in seen_keys_in_file: - skipped_duplicate += 1 - skipped_details.append({ - "line": i, - "transporter_key": tk, - "reason": "Clave duplicada en el archivo (se usa la primera)", - "solution": "El sistema conserva la primera ocurrencia; no dupliques el valor de CLAVE TRANSPORTISTA en el archivo.", - }) - continue - seen_keys_in_file[tk_upper] = i - - existing = TransporterService.get_by_id(session, tk, tenant_id, company_id) - if not existing: - existing = TransporterService.get_by_id_ignore_case(session, tk, tenant_id, company_id) - try: - if existing: - if actualizar: - data = row_to_transporter_data_for_update(row_norm, existing) - else: - data = row_to_transporter_data(row_norm) - if not data or not data.get("transporter_key"): - skipped_invalid += 1 - continue - update_data = TransporterUpdateDTO(**{k: v for k, v in data.items() if k != "transporter_key"}) - TransporterService.update(session, existing.transporter_key, tenant_id, update_data, company_id) - updated_count += 1 - else: - data = row_to_transporter_data(row_norm) - if not data or not data.get("transporter_key"): - skipped_invalid += 1 - continue - create_data = TransporterCreateDTO(**data) - TransporterService.create(session, create_data, tenant_id, company_id) - inserted_count += 1 - except Exception as db_err: - session.rollback() - skipped_invalid += 1 - skipped_details.append({ - "line": i, - "transporter_key": tk, - "reason": str(db_err), - "solution": "Revisar el valor de CLAVE TRANSPORTISTA del CSV y los catálogos relacionados para corregir el error.", - }) - continue - - try: - session.commit() - except Exception as db_err: - session.rollback() - logger.error("Transportistas import DB error: %s", db_err) - return {"status": "failed", "error": str(db_err)} - - except Exception as e: - logger.exception("Transportistas import task failed") - return {"status": "failed", "error": str(e)} - - common_storage.cleanup_import_job( - JOB_TYPE, job_id, - file_path=file_path, - error_path=error_path, - meta_path=meta_path, - ) - try: - r = _get_redis() - r.delete(f"{TRP_IMPORT_STATUS_PREFIX}{job_id}") - except Exception as e: - logger.warning("Transportistas import: failed to delete status key: %s", e) - - total_ok = inserted_count + updated_count - if total_ok == 0 and (skipped_invalid + skipped_duplicate) > 0: - return { - "status": "warning", - "inserted": inserted_count, - "updated": updated_count, - "skipped_invalid": skipped_invalid, - "skipped_duplicate": skipped_duplicate, - "skipped_details": skipped_details, - "message": f"No se insertaron registros. {skipped_invalid + skipped_duplicate} rechazados.", - } - if total_ok == 0: - return { - "status": "failed", - "error": "No hay registros válidos en el archivo CSV", - "inserted": 0, - "updated": 0, - "skipped_invalid": skipped_invalid, - "skipped_duplicate": skipped_duplicate, - "skipped_details": skipped_details, - } - return { - "status": "finished", - "inserted": inserted_count, - "updated": updated_count, - "skipped_invalid": skipped_invalid, - "skipped_duplicate": skipped_duplicate, - "skipped_details": skipped_details, - } - - -def run_commit_sync(job_id: str) -> Dict[str, Any]: - result = _do_commit(job_id) - try: - r = _get_redis() - r.set( - f"{TRP_IMPORT_STATUS_PREFIX}{job_id}", - json.dumps(result).encode("utf-8"), - ex=TRP_IMPORT_REDIS_TTL, - ) - except Exception as e: - logger.warning("Transportistas import: failed to store commit status in Redis: %s", e) - return result - - -@celery_app.task(bind=True) -def insert_valid_rows(self, job_id: str): - logger.info("Transportistas import: starting commit for job %s", job_id) - result = _do_commit(job_id) - try: - r = _get_redis() - r.set( - f"{TRP_IMPORT_STATUS_PREFIX}{job_id}", - json.dumps(result).encode("utf-8"), - ex=TRP_IMPORT_REDIS_TTL, - ) - except Exception as e: - logger.warning("Transportistas import: failed to store commit status in Redis: %s", e) - return result diff --git a/backend/api/v1/modules/a76/layouts_csv/transportistas/template_config.py b/backend/api/v1/modules/a76/layouts_csv/transportistas/template_config.py deleted file mode 100644 index 7ba75abe..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/transportistas/template_config.py +++ /dev/null @@ -1,69 +0,0 @@ -""" -Configuración de plantilla CSV para Transportistas (EstructuraCatTransportistas). -Mapeo Clarion: Col A = CLAVE TRANSPORTISTA, B = NOMBRE, ... R = DIRECTORIO FTP, S = desfase. -""" - -from typing import Dict, List, Any - -from ..common.cell_value import cell_to_str -from ..common.csv_hint_inject import inject_max_char_hints_into_columns -from ..common.inject_csv_labels_en import inject_en_labels_into_template_columns -from ..common.template_locale import merge_download_header_into_lookup, merge_labels_into_lookup -from .csv_max_chars_for_headers import TRANSPORTISTAS_CSV_MAX_CHARS - -TEMPLATE_COLUMNS: Dict[str, List[Dict[str, Any]]] = { - "transporters": [ - {"canonical": "CLAVE TRANSPORTISTA", "aliases": ["TRANSPORTISTA", "CLAVE TRANS", "CARRIER KEY"]}, - {"canonical": "NOMBRE", "aliases": ["NOMBRE TRANSPORTISTA", "NAME"]}, - {"canonical": "NOMBRE CORTO", "aliases": ["NOMBRE CORTO TRANS", "SHORT NAME"]}, - {"canonical": "RESPONSABLE", "aliases": ["RESPONSABLE TRANS"]}, - {"canonical": "RFC", "aliases": ["RFC TRANS"]}, - {"canonical": "CALLES", "aliases": ["STREETS", "DIRECCION"]}, - {"canonical": "CODIGO POSTAL", "aliases": ["CODIGO POSTAL TRANS", "POSTAL CODE", "CP"]}, - {"canonical": "CIUDAD", "aliases": ["CITY", "CIUDAD TRANS"]}, - {"canonical": "ESTADO", "aliases": ["STATE", "ESTADO TRANS"]}, - {"canonical": "PAIS", "aliases": ["COUNTRY", "PAIS TRANS"]}, - {"canonical": "CODIGO CARGADOR", "aliases": ["COD CARGADOR", "LOADER CODE"]}, - {"canonical": "CODIGO CAAT", "aliases": ["CAAT", "CODIGO CAAT TRANS"]}, - {"canonical": "CODIGO TRANS", "aliases": ["COD TRANS", "TRANSPORT CODE", "SCAC"]}, - {"canonical": "TIPO INTERFASE TRANS", "aliases": ["TIPO INTERFASE", "INTERFACE TYPE"]}, - {"canonical": "SERVIDOR FTP", "aliases": ["FTP SERVER", "SERVIDOR FTP TRANS"]}, - {"canonical": "USUARIO FTP", "aliases": ["FTP USER", "USUARIO FTP TRANS"]}, - {"canonical": "CLAVE ACCESO FTP", "aliases": ["CLAVE FTP", "FTP PASSWORD", "PASSWORD FTP"]}, - {"canonical": "DIRECTORIO FTP", "aliases": ["FTP DIRECTORY", "DIRECTORIO FTP TRANS"]}, - {"canonical": "COL_EXTRA", "aliases": ["COLUMNA S", "COL S", "COL 19"]}, - ], -} - -inject_en_labels_into_template_columns(TEMPLATE_COLUMNS) - -inject_max_char_hints_into_columns( - TEMPLATE_COLUMNS.get("transporters"), TRANSPORTISTAS_CSV_MAX_CHARS -) - - -def build_normalized_lookup(normalize_header_fn) -> Dict[str, str]: - cols = TEMPLATE_COLUMNS.get("transporters") - if not cols: - return {} - lookup: Dict[str, str] = {} - for item in cols: - canonical = item["canonical"] - lookup[normalize_header_fn(canonical)] = canonical - for alias in item.get("aliases") or []: - lookup[normalize_header_fn(alias)] = canonical - merge_labels_into_lookup(item, canonical, normalize_header_fn, lookup) - merge_download_header_into_lookup(item, canonical, normalize_header_fn, lookup) - return lookup - - -def row_from_template(row: Dict[str, Any], normalize_header_fn) -> Dict[str, Any]: - lookup = build_normalized_lookup(normalize_header_fn) - if not lookup: - return {normalize_header_fn(k): cell_to_str(v) for k, v in row.items()} - out: Dict[str, Any] = {} - for csv_header, value in row.items(): - key_norm = normalize_header_fn(csv_header) - if key_norm in lookup: - out[lookup[key_norm]] = cell_to_str(value) - return out diff --git a/backend/api/v1/modules/a76/layouts_csv/transportistas/validators/__init__.py b/backend/api/v1/modules/a76/layouts_csv/transportistas/validators/__init__.py deleted file mode 100644 index d4bd5455..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/transportistas/validators/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .create import validate_row_transporter, validate_row_transporter_desfase - -__all__ = ["validate_row_transporter", "validate_row_transporter_desfase"] diff --git a/backend/api/v1/modules/a76/layouts_csv/transportistas/validators/common.py b/backend/api/v1/modules/a76/layouts_csv/transportistas/validators/common.py deleted file mode 100644 index 20e53cf7..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/transportistas/validators/common.py +++ /dev/null @@ -1,97 +0,0 @@ -""" -Validaciones comunes de fila para import CSV de transportistas. -Paridad Clarion: VALIDACIONES_TRANSPORTISTAS, VALIDA_TODA_TRANSPORTISTAS, VALIDA_PARCIAL_TRANSPORTISTAS. -""" -from typing import Dict, Any, Optional, Set, Tuple, List - -from ..common.common_validators import ( - MAX_LEN, - check_required, - check_max_length, - check_pais_catalog_transportistas, - check_estado_catalog_transportistas, - check_estado_pais_consistency_transportistas, -) - - -def validate_row_transporter_required(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]: - """Col A (CLAVE TRANSPORTISTA) obligatoria.""" - return check_required(row, "CLAVE TRANSPORTISTA", MAX_LEN["transporter_key"], line_num) - - -def validate_row_transporter_required_full( - row: Dict[str, Any], - line_num: int, -) -> Optional[Dict[str, Any]]: - """VALIDA_TODA: Col A y Col B (NOMBRE) obligatorios cuando se agrega o se reemplaza.""" - err = validate_row_transporter_required(row, line_num) - if err: - return err - err = check_max_length( - row, "NOMBRE", MAX_LEN["name"], line_num, required=True - ) - if err: - return err - return None - - -def validaciones_transportistas( - row: Dict[str, Any], - line_num: int, - valid_country_ame: Optional[Set[str]] = None, - state_descriptions_upper: Optional[Set[str]] = None, - state_country_set: Optional[Set[Tuple[str, str]]] = None, -) -> List[Dict[str, Any]]: - """ - VALIDACIONES_TRANSPORTISTAS: estado, país, estado-país (Clarion). - """ - errors: List[Dict[str, Any]] = [] - for err in [ - check_estado_catalog_transportistas(row, line_num, state_descriptions_upper), - check_pais_catalog_transportistas(row, line_num, valid_country_ame), - check_estado_pais_consistency_transportistas(row, line_num, state_country_set), - ]: - if err: - errors.append(err) - return errors - - -def valida_toda_transportistas( - row: Dict[str, Any], - line_num: int, - valid_country_ame: Optional[Set[str]] = None, - state_descriptions_upper: Optional[Set[str]] = None, - state_country_set: Optional[Set[Tuple[str, str]]] = None, -) -> List[Dict[str, Any]]: - """VALIDA_TODA: obligatorios A y B + VALIDACIONES_TRANSPORTISTAS (usado para agregar nuevo o reemplazar).""" - errors: List[Dict[str, Any]] = [] - err = validate_row_transporter_required_full(row, line_num) - if err: - errors.append(err) - errors.extend( - validaciones_transportistas( - row, - line_num, - valid_country_ame=valid_country_ame, - state_descriptions_upper=state_descriptions_upper, - state_country_set=state_country_set, - ) - ) - return errors - - -def valida_parcial_transportistas( - row: Dict[str, Any], - line_num: int, - valid_country_ame: Optional[Set[str]] = None, - state_descriptions_upper: Optional[Set[str]] = None, - state_country_set: Optional[Set[Tuple[str, str]]] = None, -) -> List[Dict[str, Any]]: - """VALIDA_PARCIAL_TRANSPORTISTAS: solo VALIDACIONES_TRANSPORTISTAS (actualizar registro existente).""" - return validaciones_transportistas( - row, - line_num, - valid_country_ame=valid_country_ame, - state_descriptions_upper=state_descriptions_upper, - state_country_set=state_country_set, - ) diff --git a/backend/api/v1/modules/a76/layouts_csv/transportistas/validators/create.py b/backend/api/v1/modules/a76/layouts_csv/transportistas/validators/create.py deleted file mode 100644 index 7dd6b16e..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/transportistas/validators/create.py +++ /dev/null @@ -1,72 +0,0 @@ -""" -Punto de entrada de validación para import de una fila transportista. -Actualizar = merge: si no existe la clave se crea (valida_toda), si existe se actualizan solo campos enviados (valida_parcial). -Reemplazar = reescribir: siempre valida_toda; en commit se sustituye el registro completo o se agrega. -""" -from typing import Dict, Any, Optional, Set, Tuple, List - -from .common import ( - validate_row_transporter_required, - valida_toda_transportistas, - valida_parcial_transportistas, -) -from ..common.common_validators import check_desfase_transportistas - - -def validate_row_transporter( - row: Dict[str, Any], - line_num: int, - actualizar: bool = False, - existing_transporter_keys: Optional[Set[str]] = None, - valid_country_ame: Optional[Set[str]] = None, - state_descriptions_upper: Optional[Set[str]] = None, - state_country_set: Optional[Set[Tuple[str, str]]] = None, -) -> List[Dict[str, Any]]: - """ - Valida una fila de CSV de transportistas. - 1. CLAVE TRANSPORTISTA (Col A) vacía → error. - 2. Si actualizar y clave existe en catálogo → valida_parcial (solo validaciones de catálogo; campos vacíos = mantener actual). - 3. Si reemplazar o (actualizar y clave no existe) → valida_toda (A y B obligatorios + validaciones). - """ - errors: List[Dict[str, Any]] = [] - err = validate_row_transporter_required(row, line_num) - if err: - errors.append(err) - - existing = existing_transporter_keys or set() - clave = (row.get("CLAVE TRANSPORTISTA") or "").strip().upper() - use_partial = actualizar and bool(clave and clave in existing) - - if not use_partial and clave and clave in existing: - errors.append({ - "line": line_num, - "col": "CLAVE TRANSPORTISTA", - "msg": f"La clave '{row.get('CLAVE TRANSPORTISTA')}' ya existe en el catálogo." - }) - - if use_partial: - errors.extend( - valida_parcial_transportistas( - row, - line_num, - valid_country_ame=valid_country_ame, - state_descriptions_upper=state_descriptions_upper, - state_country_set=state_country_set, - ) - ) - else: - errors.extend( - valida_toda_transportistas( - row, - line_num, - valid_country_ame=valid_country_ame, - state_descriptions_upper=state_descriptions_upper, - state_country_set=state_country_set, - ) - ) - return errors - - -def validate_row_transporter_desfase(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]: - """Advertencia de desfase si COL_EXTRA (Col S) tiene valor. No bloqueante.""" - return check_desfase_transportistas(row, line_num) diff --git a/backend/api/v1/modules/a76/layouts_csv/us_tariff_fractions/__init__.py b/backend/api/v1/modules/a76/layouts_csv/us_tariff_fractions/__init__.py deleted file mode 100644 index cff9f961..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/us_tariff_fractions/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# CSV import for US Tariff Fractions (Fracción Americana): upload → scan → commit diff --git a/backend/api/v1/modules/a76/layouts_csv/us_tariff_fractions/common/__init__.py b/backend/api/v1/modules/a76/layouts_csv/us_tariff_fractions/common/__init__.py deleted file mode 100644 index d7025515..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/us_tariff_fractions/common/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# common_validators, mappers (no fk_loader for us_tariff_fractions) diff --git a/backend/api/v1/modules/a76/layouts_csv/us_tariff_fractions/common/common_validators.py b/backend/api/v1/modules/a76/layouts_csv/us_tariff_fractions/common/common_validators.py deleted file mode 100644 index b77457c3..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/us_tariff_fractions/common/common_validators.py +++ /dev/null @@ -1,83 +0,0 @@ -""" -Validadores reutilizables para import CSV de fracciones arancelarias americanas. -Paridad Clarion: Col A max 16, Col C en catálogo U.M., Col E PO/ME o vacío (default PO). -""" -from decimal import Decimal -from typing import Dict, Any, Optional, Set - -CODE_MAX = 16 -PREFIX_MAX = 10 -UNIT_MAX = 10 -TYPE_MAX = 10 - -# Clarion: Tipo Advalorem PO (Porcentaje), ME (Costos Fijo Dlls) o vacío → PO -TIPO_ADVALOREM_VALIDOS = frozenset({"PO", "ME"}) - - -def normalize_code(raw: Optional[str]) -> str: - """Normalize fraction code: strip and remove dots/dashes, max 16 chars.""" - if not raw: - return "" - s = str(raw).strip().replace(".", "").replace("-", "") - return s[:CODE_MAX] if len(s) > CODE_MAX else s - - -def check_required_code(row: Dict[str, Any], col: str, line_num: int) -> Optional[Dict[str, Any]]: - code_raw = (row.get(col) or "").strip() - if not code_raw: - return {"line": line_num, "col": col, "msg": "Requerido"} - code_norm = normalize_code(code_raw) - if not code_norm: - return {"line": line_num, "col": col, "msg": "Requerido"} - if len(code_norm) > CODE_MAX: - return {"line": line_num, "col": col, "msg": f"Máximo {CODE_MAX} caracteres"} - return None - - -def check_optional_max_length(row: Dict[str, Any], col: str, max_len: int, line_num: int) -> Optional[Dict[str, Any]]: - val = (row.get(col) or "").strip() - if not val: - return None - if len(val) > max_len: - return {"line": line_num, "col": col, "msg": f"Máximo {max_len} caracteres"} - return None - - -def parse_float_min_zero(val: Any) -> Optional[float]: - if val is None or str(val).strip() == "": - return None - try: - v = float(str(val).strip().replace(",", ".")) - return v if v >= 0 else None - except (ValueError, TypeError): - return None - - -def check_optional_decimal_min_zero(row: Dict[str, Any], col: str, line_num: int) -> Optional[Dict[str, Any]]: - val = row.get(col) - if val is None or not str(val).strip(): - return None - try: - v = float(str(val).strip().replace(",", ".")) - if v < 0: - return {"line": line_num, "col": col, "msg": "Debe ser >= 0"} - except ValueError: - return {"line": line_num, "col": col, "msg": "Debe ser un número"} - return None - - -def check_tipo_advalorem(row: Dict[str, Any], col: str, line_num: int) -> Optional[Dict[str, Any]]: - """Col E: solo PO, ME o vacío. Clarion mensaje.""" - val = (row.get(col) or "").strip().upper() - if not val: - return None - if val in TIPO_ADVALOREM_VALIDOS: - return None - return { - "line": line_num, - "col": col, - "msg": ( - f"Error: (Col. E) La opción de Tipo de Advalorem: {row.get(col) or ''} no es valido. " - "Capturar una opción valida: PO para Porcentaje, ME para Costos Fijo en Dlls o dejar el campo vacio y automaticamente se asigna Porcentaje." - ), - } diff --git a/backend/api/v1/modules/a76/layouts_csv/us_tariff_fractions/common/fk_loader.py b/backend/api/v1/modules/a76/layouts_csv/us_tariff_fractions/common/fk_loader.py deleted file mode 100644 index 10ffa10b..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/us_tariff_fractions/common/fk_loader.py +++ /dev/null @@ -1,39 +0,0 @@ -""" -Carga de conjuntos FK para validación/mapeo de import CSV de Fracciones Americanas. -Clarion: U.M. (GUniMedida), códigos existentes de fracción americana (GFracAme). -""" -from typing import Set, Tuple - -from core.database import CoreSessionLocal - - -def load_fa_fk_sets( - tenant_id: int, - company_id: int, -) -> Tuple[Set[str], Set[str]]: - """ - Carga conjuntos para validación CSV Fracciones Americanas (paridad Clarion). - Devuelve (valid_uom_codes, existing_fraction_codes). - - valid_uom_codes: códigos de Unidad de Medida (a76.units_of_measure, code UPPER, max 5 chars). - - existing_fraction_codes: reservado (vacío); existencia se valida contra SITAR por fila. - """ - valid_uom_codes: Set[str] = set() - existing_fraction_codes: Set[str] = set() - try: - with CoreSessionLocal() as session: - from api.v1.modules.a76.general_catalogs.units_of_measure.models import UnitOfMeasure - for row in ( - session.query(UnitOfMeasure.code) - .filter( - UnitOfMeasure.tenant_id == tenant_id, - UnitOfMeasure.company_id == company_id, - ) - .all() - ): - if row[0]: - valid_uom_codes.add((row[0].strip() or "").upper()[:5]) - - except Exception as e: - import logging - logging.getLogger(__name__).warning("FA import: could not load FK sets: %s", e) - return valid_uom_codes, existing_fraction_codes diff --git a/backend/api/v1/modules/a76/layouts_csv/us_tariff_fractions/common/mappers.py b/backend/api/v1/modules/a76/layouts_csv/us_tariff_fractions/common/mappers.py deleted file mode 100644 index 6f0be32e..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/us_tariff_fractions/common/mappers.py +++ /dev/null @@ -1,90 +0,0 @@ -""" -Mapeo fila CSV → datos para USTariffFraction. -""" -from decimal import Decimal -from typing import Dict, Any, Optional - -from .common_validators import ( - normalize_code, - parse_float_min_zero, -) - -MAX_LEN = {"prefix": 10, "unit_of_measure": 10, "type_code": 10} - - -def _str_or_none(val: Any, max_len: Optional[int] = None) -> Optional[str]: - if val is None: - return None - s = str(val).strip() - if not s: - return None - if max_len and len(s) > max_len: - return s[:max_len] - return s - - -def row_to_us_tariff_fraction_data( - row_norm: Dict[str, Any], tenant_id: int, company_id: int, existing: Optional[Any] = None -) -> Dict[str, Any]: - """ - Build dict for USTariffFraction model. - Si existing está presente (modo Actualizar), campos vacíos en row_norm se rellenan desde existing (VALIDA_PARCIAL Clarion). - Col E vacío → type_code 'PO'. - """ - code = normalize_code(row_norm.get("FRACCION_ARANCELARIA")) - if not code: - return {} - - def _prefijo() -> Optional[str]: - v = _str_or_none(row_norm.get("PREFIJO"), MAX_LEN["prefix"]) - if v is not None: - return v - return getattr(existing, "prefix", None) if existing else None - - def _unit() -> Optional[str]: - v = _str_or_none(row_norm.get("UNIDAD_DE_MEDIDA"), MAX_LEN["unit_of_measure"]) - if v is not None: - return v - return getattr(existing, "unit_of_measure", None) if existing else None - - def _desc() -> Optional[str]: - v = _str_or_none(row_norm.get("DESCRIPCION")) - if v is not None: - return v - return getattr(existing, "description", None) if existing else None - - def _type_code() -> Optional[str]: - v = _str_or_none(row_norm.get("TIPO_DE_ADVALOREM"), MAX_LEN["type_code"]) - if v is not None: - return v.upper() if v else "PO" - if existing is not None: - return getattr(existing, "type_code", None) or "PO" - return "PO" - - def _adv() -> Optional[float]: - x = parse_float_min_zero(row_norm.get("ADVALOREM_PCT")) - if x is not None: - return x - return getattr(existing, "ad_valorem", None) if existing else None - - def _fixed() -> Optional[Decimal]: - fixed_cost_raw = parse_float_min_zero(row_norm.get("ADVALOREM_DLLS")) - if fixed_cost_raw is not None: - return Decimal(str(round(fixed_cost_raw, 8))) - if existing is not None: - v = getattr(existing, "fixed_cost", None) - return Decimal(str(v)) if v is not None else None - return None - - fixed_cost = _fixed() - return { - "tenant_id": tenant_id, - "company_id": company_id, - "code": code, - "prefix": _prefijo(), - "type_code": _type_code(), - "ad_valorem": _adv(), - "fixed_cost": fixed_cost, - "unit_of_measure": _unit(), - "description": _desc(), - } diff --git a/backend/api/v1/modules/a76/layouts_csv/us_tariff_fractions/csv_max_chars_for_headers.py b/backend/api/v1/modules/a76/layouts_csv/us_tariff_fractions/csv_max_chars_for_headers.py deleted file mode 100644 index a68c2243..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/us_tariff_fractions/csv_max_chars_for_headers.py +++ /dev/null @@ -1,14 +0,0 @@ -"""Fracción americana: common_validators CODE_MAX, etc.""" -from typing import Dict - -from .common.common_validators import CODE_MAX, PREFIX_MAX, UNIT_MAX, TYPE_MAX - -US_TARIFF_FRACTIONS_CSV_MAX_CHARS: Dict[str, int] = { - "FRACCION_ARANCELARIA": CODE_MAX, - "PREFIJO": PREFIX_MAX, - "UNIDAD_DE_MEDIDA": UNIT_MAX, - "DESCRIPCION": 500, - "TIPO_DE_ADVALOREM": TYPE_MAX, - "ADVALOREM_PCT": 24, - "ADVALOREM_DLLS": 24, -} diff --git a/backend/api/v1/modules/a76/layouts_csv/us_tariff_fractions/routes.py b/backend/api/v1/modules/a76/layouts_csv/us_tariff_fractions/routes.py deleted file mode 100644 index 50b9586d..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/us_tariff_fractions/routes.py +++ /dev/null @@ -1,247 +0,0 @@ -""" -Rutas de importación CSV para Fracción Americana (US Tariff Fractions). -Mismo flujo que exchange_rate/imports: upload → scan → status (polling) → commit. -""" -import json -import logging -import os -from uuid import uuid4 - -from fastapi import APIRouter, File, HTTPException, Query, UploadFile, Depends -from sqlalchemy.orm import Session -from typing import Dict, Any, Optional - -from core.celery_app import celery_app -from core.database import get_core_db -from core.security import get_current_user, validate_access_to_resource -from api.v1.modules.core.tasks_tracking import track_and_dispatch - -from ..common.track_commit_dispatch import dispatch_tracked_layouts_csv_commit -from ..common.responses import normalize_commit_status_payload -from .schemas import ImportJobResponse -from .tasks import ( - scan_file, - insert_valid_rows, - JOB_TYPE, - FA_IMPORT_META_PREFIX, - FA_IMPORT_REDIS_TTL, -) -from ..common import storage as common_storage -from ..common.error_csv import download_scan_errors_csv_stream - -router = APIRouter() -logger = logging.getLogger(__name__) - - -def _get_redis(): - import redis - url = os.getenv("VALKEY_URL", os.getenv("REDIS_URL", "redis://valkey:6379/0")) - return redis.Redis.from_url(url, decode_responses=False) - - -def _assert_us_tariff_csv_job_access( - db: Session, - job_id: str, - current_user: Dict[str, Any], -) -> None: - from api.v1.modules.core.tasks_tracking.models import TaskRun - - company_id: Optional[int] = None - row = db.query(TaskRun).filter(TaskRun.task_id == job_id).first() - if row is not None and row.company_id is not None: - company_id = int(row.company_id) - - r = _get_redis() - if company_id is None: - raw = r.get(f"{FA_IMPORT_META_PREFIX}{job_id}") - if raw: - meta = json.loads(raw.decode("utf-8")) - cid = meta.get("company_id") - if cid is not None: - company_id = int(cid) - - if company_id is None and row is not None and row.meta_payload: - layout_jid = row.meta_payload.get("layout_import_job_id") - if layout_jid: - raw2 = r.get(f"{FA_IMPORT_META_PREFIX}{layout_jid}") - if raw2: - meta2 = json.loads(raw2.decode("utf-8")) - cid2 = meta2.get("company_id") - if cid2 is not None: - company_id = int(cid2) - - if company_id is None: - raise HTTPException(status_code=403, detail="Sin acceso a este job") - try: - validate_access_to_resource( - db, company_id, current_user, ["csv_upload.process"] - ) - except HTTPException: - raise - except Exception as e: - logger.error("US tariff fractions import: job access validation failed: %s", e) - raise HTTPException(status_code=403, detail="Sin acceso a este job") from None - - -@router.post("/upload", response_model=ImportJobResponse) -async def upload_import_file( - file: UploadFile = File(...), - company_id: int = Query(..., description="Company ID"), - actualizar: bool = Query(False, description="Modo Agregar/Actualizar (ACT); si False, Agregar/Reemplazar"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """ - Fase 1: Subir CSV, guardar en Redis, encolar tarea de escaneo. - actualizar=True simula Clarion 'Agr./Actual.'; actualizar=False 'Agr./Reempl.'. - """ - try: - tenant_id = validate_access_to_resource(db, company_id, current_user, ["csv_upload.process"]) - except Exception as e: - logger.error(f"FA import: access validation failed: {e}") - raise HTTPException(status_code=403, detail="Invalid company access") - - if not file.filename or not file.filename.lower().endswith(".csv"): - raise HTTPException(status_code=400, detail="Solo se permiten archivos .csv") - - job_id = str(uuid4()) - contents = await file.read() - - meta_data = { - "tenant_id": tenant_id, - "company_id": company_id, - "user_id": current_user.get("id"), - "template_id": "us_tariff_fractions", - "actualizar": actualizar, - } - - try: - common_storage.store_import_file( - JOB_TYPE, - job_id, - contents, - meta_data, - tenant_id=int(tenant_id), - company_id=company_id, - ttl=FA_IMPORT_REDIS_TTL, - log_label="FA import", - ) - except common_storage.ImportStoreError as e: - logger.error(f"FA import: store error: {e}") - raise HTTPException(status_code=500, detail="No se pudo encolar el archivo.") - except Exception as e: - logger.error(f"FA import: Redis store error: {e}") - raise HTTPException(status_code=500, detail="No se pudo encolar el archivo.") - - track_and_dispatch( - db=db, - task=scan_file, - tenant_id=int(tenant_id), - company_id=company_id, - requested_by_user=current_user.get("preferred_username") - or current_user.get("email") - or current_user.get("sub"), - task_name="us_tariff_fractions_scan_file", - task_group="layouts_csv", - task_origin="a76/layouts_csv/us_tariff_fractions/upload", - args=[job_id], - task_id=job_id, - ) - - return ImportJobResponse( - job_id=job_id, - status="queued", - message="Archivo subido. Escaneo iniciado.", - ) - - -@router.get("/{job_id}/status") -async def get_import_status( - job_id: str, - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """ - Polling: estado del escaneo o del commit. - """ - _assert_us_tariff_csv_job_access(db, job_id, current_user) - task_result = celery_app.AsyncResult(job_id) - - if task_result.state == "PENDING": - return {"status": "processing", "progress": 0} - if task_result.state == "PROGRESS": - info = task_result.info or {} - return { - "status": "processing", - "progress": info.get("current", 0), - "total": info.get("total", 0), - } - if task_result.state == "SUCCESS": - result = task_result.result - if isinstance(result, dict) and "status" in result: - return normalize_commit_status_payload(result) - return {"status": "finished", "result": result} - - result = getattr(task_result, "result", None) - if isinstance(result, dict) and result.get("status") in ("finished", "warning"): - return normalize_commit_status_payload(result) - - logger.warning("FA import task %s failed: state=%s", job_id, task_result.state) - err_msg = None - tb = getattr(task_result, "traceback", None) - if tb and isinstance(tb, str): - lines = [l.strip() for l in tb.strip().split("\n") if l.strip()] - if lines: - err_msg = lines[-1] - if not err_msg: - try: - exc = task_result.get(propagate=False) - if exc is not None: - err_msg = str(exc) - except Exception: - pass - if not err_msg and result is not None: - if not isinstance(result, dict): - err_msg = str(result) - elif result.get("error") or result.get("message"): - err_msg = result.get("error") or result.get("message") - return {"status": "failed", "error": err_msg or "Task failed"} - - -@router.post("/{job_id}/commit") -async def commit_import_job( - job_id: str, - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """ - Fase 2: Usuario confirma; se encola la inserción de filas válidas. - """ - r = _get_redis() - commit_id = dispatch_tracked_layouts_csv_commit( - db=db, - current_user=current_user, - redis_client=r, - layout_job_id=job_id, - meta_redis_key=f"{FA_IMPORT_META_PREFIX}{job_id}", - celery_task=insert_valid_rows, - task_name="us_tariff_fractions_insert_valid_rows", - task_origin="a76/layouts_csv/us_tariff_fractions/commit", - args=[job_id], - required_permissions=["csv_upload.process"], - ) - return { - "status": "committing", - "message": "Inserción iniciada.", - "commit_job_id": commit_id, - } - - -@router.get("/{job_id}/errors/scan-csv") -async def download_scan_errors_csv( - job_id: str, - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - _assert_us_tariff_csv_job_access(db, job_id, current_user) - return download_scan_errors_csv_stream("fa", job_id) diff --git a/backend/api/v1/modules/a76/layouts_csv/us_tariff_fractions/schemas.py b/backend/api/v1/modules/a76/layouts_csv/us_tariff_fractions/schemas.py deleted file mode 100644 index 98c39a98..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/us_tariff_fractions/schemas.py +++ /dev/null @@ -1,25 +0,0 @@ -from pydantic import BaseModel -from typing import Optional - - -class ImportJobResponse(BaseModel): - job_id: str - status: str - message: str - - -class CommitRequest(BaseModel): - pass # no body needed for single model - - -class ImportJobStatus(BaseModel): - status: str - job_id: str - total_rows: Optional[int] = 0 - error_count: Optional[int] = 0 - valid_rows: Optional[int] = 0 - error: Optional[str] = None - inserted: Optional[int] = 0 - skipped_invalid: Optional[int] = 0 - skipped_missing_fk: Optional[int] = 0 - skipped_details: Optional[list] = None diff --git a/backend/api/v1/modules/a76/layouts_csv/us_tariff_fractions/tasks.py b/backend/api/v1/modules/a76/layouts_csv/us_tariff_fractions/tasks.py deleted file mode 100644 index 06d8d5f2..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/us_tariff_fractions/tasks.py +++ /dev/null @@ -1,281 +0,0 @@ -""" -Tareas Celery para importación CSV de Fracción Americana (US Tariff Fractions). -Flujo: scan_file (validación) → insert_valid_rows (commit). -Usa layouts_csv.common (storage, normalize, meta, responses, csv_reader). -""" -import json -import logging -import os -from typing import Dict, Any, Optional, List - -from core.celery_app import celery_app -from core.database import CoreSessionLocal - -from ..common import storage as common_storage -from ..common import normalize as common_normalize -from ..common import meta as common_meta -from ..common import responses as common_responses -from ..common import csv_reader as common_csv_reader -from .template_config import row_from_template -from .validators import validate_row_us_tariff_fraction, validate_row_desfase_fa -from .common.mappers import row_to_us_tariff_fraction_data -from .common.fk_loader import load_fa_fk_sets - -logger = logging.getLogger(__name__) - -JOB_TYPE = "fa" -TEMPLATE_ID = "us_tariff_fractions" - -# Para routes.py -FA_IMPORT_FILE_PREFIX = "fa_import_file:" -FA_IMPORT_META_PREFIX = "fa_import_meta:" -FA_IMPORT_ERROR_LINES_PREFIX = "fa_import_error_lines:" -FA_IMPORT_REDIS_TTL = common_storage.IMPORT_REDIS_TTL - - -def _norm_row(row: Dict[str, Any]) -> Dict[str, Any]: - return row_from_template(row, common_normalize.normalize_header, TEMPLATE_ID) - - -def _do_scan(job_id: str, progress_callback: Optional[Any] = None) -> Dict[str, Any]: - file_path = common_storage.ensure_file_from_redis(JOB_TYPE, job_id, "FA import") - if not file_path: - return {"status": "failed", "error": "Archivo no encontrado (expirado o no subido). Sube de nuevo."} - common_storage.ensure_meta_from_redis(JOB_TYPE, job_id, file_path, "FA import") - - error_path = common_storage.error_path_for_job(JOB_TYPE, job_id) - - try: - total_rows = common_csv_reader.count_csv_rows(file_path) - except Exception as e: - return {"status": "failed", "error": str(e)} - - try: - tenant_id, company_id = common_meta.require_tenant_context(file_path) - except ValueError as e: - return {"status": "failed", "error": str(e)} - - meta_data = common_meta.load_meta(file_path) - actualizar = meta_data.get("actualizar", False) - valid_uom_codes, existing_fraction_codes = load_fa_fk_sets(tenant_id, company_id) - - error_count = 0 - processed_rows = 0 - errors_detail: List[Dict[str, Any]] = [] - error_lines_list: List[int] = [] - warnings_detail: List[Dict[str, Any]] = [] - - try: - with open(error_path, "w", encoding="utf-8") as f_err: - for i, row in common_csv_reader.iter_csv_rows(file_path): - if progress_callback: - progress_callback(i, total_rows, error_count) - - warn = validate_row_desfase_fa(row, i) - if warn and len(warnings_detail) < 500: - warnings_detail.append({ - "line": warn["line"], - "col": warn.get("col", ""), - "msg": warn.get("msg", ""), - }) - - row_norm = _norm_row(row) - row_errors = validate_row_us_tariff_fraction( - row_norm, - i, - actualizar=actualizar, - existing_fraction_codes=existing_fraction_codes, - valid_uom_codes=valid_uom_codes, - raw_row=row, - ) - if row_errors: - has_blocking_error = any(not e.get("warning", False) for e in row_errors) - if has_blocking_error: - error_count += 1 - error_lines_list.append(i) - for e in row_errors: - if not e.get("warning", False): - f_err.write(json.dumps(e) + "\n") - if len(errors_detail) < 500: - for e in row_errors: - if len(errors_detail) >= 500: - break - errors_detail.append({ - "line": e["line"], - "col": e.get("col", ""), - "msg": e.get("msg", ""), - "solution": e.get("solution", ""), - }) - processed_rows += 1 - - if error_lines_list: - common_storage.store_error_lines(JOB_TYPE, job_id, error_lines_list) - except Exception as e: - logger.error("FA import scan failed: %s", e) - return {"status": "failed", "error": str(e)} - - result = common_responses.scan_result(job_id, processed_rows, error_count, errors_detail) - if warnings_detail: - result["warnings"] = warnings_detail - return result - - -@celery_app.task(bind=True) -def scan_file(self, job_id: str, config: str = None): - logger.info("FA import: starting scan for job %s", job_id) - - def on_progress(current: int, total: int, errors: int) -> None: - self.update_state(state="PROGRESS", meta={"current": current, "total": total, "errors": errors}) - - return _do_scan(job_id, progress_callback=on_progress) - - -def _do_commit(job_id: str) -> Dict[str, Any]: - file_path = common_storage.ensure_file_from_redis(JOB_TYPE, job_id, "FA import") - if not file_path: - alt_path = common_storage.file_path_for_job(JOB_TYPE, job_id) - if not os.path.exists(alt_path): - return {"status": "failed", "error": "Archivo no encontrado (expirado). Sube y confirma de nuevo."} - file_path = alt_path - else: - common_storage.ensure_meta_from_redis(JOB_TYPE, job_id, file_path, "FA import") - - error_path = common_storage.error_path_for_job(JOB_TYPE, job_id) - error_lines = common_storage.get_error_lines(JOB_TYPE, job_id, error_path) - - try: - tenant_id, company_id = common_meta.require_tenant_context(file_path) - except ValueError as e: - return {"status": "failed", "error": str(e)} - - meta_data = common_meta.load_meta(file_path) - actualizar = meta_data.get("actualizar", False) - valid_uom_codes, existing_fraction_codes = load_fa_fk_sets(tenant_id, company_id) - - from api.v1.modules.a76.general_catalogs.fractions.us_tariff_fractions.models import USTariffFraction - - inserted_count = 0 - updated_count = 0 - skipped_invalid = 0 - skipped_details: List[Dict[str, Any]] = [] - meta_path = common_meta.get_meta_path(file_path) - - try: - with CoreSessionLocal() as session: - for i, row in common_csv_reader.iter_csv_rows(file_path): - if i in error_lines: - continue - - row_norm = _norm_row(row) - row_errors = validate_row_us_tariff_fraction( - row_norm, - i, - actualizar=actualizar, - existing_fraction_codes=existing_fraction_codes, - valid_uom_codes=valid_uom_codes, - raw_row=row, - ) - if row_errors and any(not e.get("warning", False) for e in row_errors): - skipped_invalid += 1 - blocking_errors = [e for e in row_errors if not e.get("warning", False)] - for blocking in blocking_errors: - skipped_details.append({ - "line": i, - "reason": f"{blocking.get('col', '')}: {blocking.get('msg', '')}", - "solution": blocking.get("solution", ""), - }) - continue - - data = row_to_us_tariff_fraction_data(row_norm, tenant_id, company_id) - if not data or not data.get("code"): - skipped_invalid += 1 - skipped_details.append( - { - "line": i, - "reason": "FRACCION_ARANCELARIA vacío", - "solution": "Capturar en la columna FRACCION_ARANCELARIA una fracción arancelaria válida.", - } - ) - continue - - existing = ( - session.query(USTariffFraction) - .filter( - USTariffFraction.tenant_id == tenant_id, - USTariffFraction.company_id == company_id, - USTariffFraction.code == data["code"], - ) - .first() - ) - if existing: - data = row_to_us_tariff_fraction_data( - row_norm, tenant_id, company_id, existing=existing - ) - existing.prefix = data.get("prefix") - existing.type_code = data.get("type_code") - existing.ad_valorem = data.get("ad_valorem") - existing.fixed_cost = data.get("fixed_cost") - existing.unit_of_measure = data.get("unit_of_measure") - existing.description = data.get("description") - session.add(existing) - updated_count += 1 - else: - new_row = USTariffFraction(**data) - session.add(new_row) - inserted_count += 1 - - try: - session.commit() - except Exception as db_err: - session.rollback() - logger.error("FA import DB error: %s", db_err) - return {"status": "failed", "error": str(db_err)} - - except Exception as e: - logger.exception("FA import task failed") - return {"status": "failed", "error": str(e)} - - common_storage.cleanup_import_job( - JOB_TYPE, job_id, - file_path=file_path, - error_path=error_path, - meta_path=meta_path, - ) - - if inserted_count == 0 and updated_count == 0 and skipped_invalid > 0: - return { - "status": "warning", - "inserted": 0, - "updated": 0, - "skipped_invalid": skipped_invalid, - "skipped_duplicate": 0, - "skipped_missing_fk": 0, - "skipped_details": skipped_details, - "message": f"No se insertaron registros. {skipped_invalid} rechazados.", - } - if inserted_count == 0 and updated_count == 0: - return { - "status": "failed", - "error": "No hay registros válidos en el archivo CSV", - "inserted": 0, - "updated": 0, - "skipped_invalid": skipped_invalid, - "skipped_duplicate": 0, - "skipped_missing_fk": 0, - "skipped_details": skipped_details, - } - return { - "status": "finished", - "inserted": inserted_count, - "updated": updated_count, - "skipped_invalid": skipped_invalid, - "skipped_duplicate": 0, - "skipped_missing_fk": 0, - "skipped_details": skipped_details, - } - - -@celery_app.task(bind=True) -def insert_valid_rows(self, job_id: str): - logger.info("FA import: starting commit for job %s", job_id) - return _do_commit(job_id) diff --git a/backend/api/v1/modules/a76/layouts_csv/us_tariff_fractions/template_config.py b/backend/api/v1/modules/a76/layouts_csv/us_tariff_fractions/template_config.py deleted file mode 100644 index 4af8022d..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/us_tariff_fractions/template_config.py +++ /dev/null @@ -1,69 +0,0 @@ -""" -Configuración de plantilla CSV para Fracción Americana (EstructuraCatFraccAme.xls). -Solo se leen columnas definidas aquí; el resto se ignora. -""" - -from typing import Dict, List, Any, Optional - -from ..common.cell_value import cell_to_str as _cell_to_str -from ..common.csv_hint_inject import inject_max_char_hints_into_columns, merge_enum_hint_before_max -from ..common.inject_csv_labels_en import inject_en_labels_into_template_columns -from ..common.template_locale import merge_download_header_into_lookup, merge_labels_into_lookup -from .csv_max_chars_for_headers import US_TARIFF_FRACTIONS_CSV_MAX_CHARS - -TEMPLATE_COLUMNS: Dict[str, List[Dict[str, Any]]] = { - "us_tariff_fractions": [ - {"canonical": "FRACCION_ARANCELARIA", "aliases": ["FRACCION ARANCELARIA", "CODE", "FRACCION"]}, - {"canonical": "PREFIJO"}, - {"canonical": "UNIDAD_DE_MEDIDA", "aliases": ["UNIDAD DE MEDIDA", "UMT"]}, - {"canonical": "DESCRIPCION"}, - {"canonical": "TIPO_DE_ADVALOREM", "aliases": ["TIPO DE ADVALOREM", "TIPO"]}, - {"canonical": "ADVALOREM_PCT", "aliases": ["ADVALOREM %", "ADVALOREM"]}, - {"canonical": "ADVALOREM_DLLS", "aliases": ["ADVALOREM DLLS", "ADVALOREM DLL"]}, - {"canonical": "COL_EXTRA", "aliases": ["DESFASE"]}, - ], -} - -inject_en_labels_into_template_columns(TEMPLATE_COLUMNS) - -inject_max_char_hints_into_columns( - TEMPLATE_COLUMNS.get("us_tariff_fractions"), US_TARIFF_FRACTIONS_CSV_MAX_CHARS -) -for _uc in TEMPLATE_COLUMNS.get("us_tariff_fractions") or []: - if _uc.get("canonical") == "TIPO_DE_ADVALOREM": - merge_enum_hint_before_max(_uc, ["PO", "ME"]) - -# Orden A..H para detectar desfase en 8ª columna (raw_row.values()[7]) -TEMPLATE_ORDER = [item["canonical"] for item in TEMPLATE_COLUMNS["us_tariff_fractions"]] -DESFASE_COLUMN_INDEX = 7 - - -def build_normalized_lookup(normalize_header_fn, template_id: str = "us_tariff_fractions") -> Dict[str, str]: - """normalized_header -> canonical_name para plantilla us_tariff_fractions.""" - cols = TEMPLATE_COLUMNS.get(template_id) - if not cols: - return {} - lookup: Dict[str, str] = {} - for item in cols: - canonical = item["canonical"] - lookup[normalize_header_fn(canonical)] = canonical - for alias in item.get("aliases") or []: - lookup[normalize_header_fn(alias)] = canonical - merge_labels_into_lookup(item, canonical, normalize_header_fn, lookup) - merge_download_header_into_lookup(item, canonical, normalize_header_fn, lookup) - return lookup - - -def row_from_template( - row: Dict[str, Any], normalize_header_fn, template_id: str = "us_tariff_fractions" -) -> Dict[str, Any]: - """Fila CSV con solo columnas de la plantilla, en nombres canónicos. Valores siempre str (listas convertidas).""" - lookup = build_normalized_lookup(normalize_header_fn, template_id) - if not lookup: - return {normalize_header_fn(k): _cell_to_str(v) for k, v in row.items()} - out: Dict[str, Any] = {} - for csv_header, value in row.items(): - key_norm = normalize_header_fn(csv_header) - if key_norm in lookup: - out[lookup[key_norm]] = _cell_to_str(value) - return out diff --git a/backend/api/v1/modules/a76/layouts_csv/us_tariff_fractions/validators/__init__.py b/backend/api/v1/modules/a76/layouts_csv/us_tariff_fractions/validators/__init__.py deleted file mode 100644 index 389af650..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/us_tariff_fractions/validators/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .create import validate_row_us_tariff_fraction, validate_row_desfase_fa - -__all__ = ["validate_row_us_tariff_fraction", "validate_row_desfase_fa"] diff --git a/backend/api/v1/modules/a76/layouts_csv/us_tariff_fractions/validators/common.py b/backend/api/v1/modules/a76/layouts_csv/us_tariff_fractions/validators/common.py deleted file mode 100644 index d9e071b3..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/us_tariff_fractions/validators/common.py +++ /dev/null @@ -1,180 +0,0 @@ -""" -Validaciones comunes de fila para import CSV de fracciones arancelarias americanas. -Paridad Clarion: desfase Col H, VALIDA_TODA_FRACCIONAME / VALIDA_PARCIAL_FRACCIONAME, VALIDACIONES_FRACCIONAME. -""" -from typing import Dict, Any, Optional, Set, List - -from ..template_config import DESFASE_COLUMN_INDEX -from ...common.cell_value import cell_to_str -from ..common.common_validators import ( - normalize_code, - check_optional_max_length, - check_optional_decimal_min_zero, - check_tipo_advalorem, - PREFIX_MAX, - UNIT_MAX, - CODE_MAX, -) -from ..common.common_validators import TIPO_ADVALOREM_VALIDOS # noqa: F401 re-export -from api.v1.modules.sitar.fracciones_usa.catalog_resolve import ( - resolve_american_fraction_from_sitar, -) - -# Mensajes Clarion -MSG_COL_A_VACIO = ( - "Error: (Col. A) La columna de Fracción Americana esta vacia y no se pueden hacer las validaciones. " - "Capturar en la Columna A una Fracción Americana nueva o una ya existente al cual desee actualizar campos" -) -MSG_COL_A_LONGITUD = ( - "Capturar en la columna A una Fraccion Arancelaria de 16 caracteres como máximo." -) -MSG_COL_D_OBLIGATORIO = ( - "Existen campos vacios que son obligatorios, es la (Col.D) Descripción. " - "Revisar la línea del archivo y capturar los campos con la información correcta." -) -MSG_FRACCION_NO_EXISTE = ( - "Error: (Col. A) La Fraccion Americana no existe." -) -MSG_DESFASE = "Advertencia: Podría existir un desfase en esta línea." -MSG_DESFASE_SOLUCION = "Revisar esta línea del archivo CSV y verificar cada campo esté en la posición correcta." - - -def validate_row_desfase_fa( - raw_row: Optional[Dict[str, Any]], line_num: int -) -> Optional[Dict[str, Any]]: - """ - Si la fila tiene 8+ columnas y la 8ª (Col H) tiene valor → advertencia (no bloqueante). - Devuelve dict con severity=warning para que el caller pueda acumularlo en warnings_detail. - """ - if not raw_row: - return None - values_ordered = list(raw_row.values()) - if len(values_ordered) <= DESFASE_COLUMN_INDEX: - return None - cell = cell_to_str(values_ordered[DESFASE_COLUMN_INDEX]) - if not cell.strip(): - return None - return { - "line": line_num, - "col": "COL_EXTRA", - "msg": MSG_DESFASE, - "solution": MSG_DESFASE_SOLUCION, - "severity": "warning", - "warning": True, - } - - -def _validate_col_a_required(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]: - """Col A obligatoria; mensaje Clarion.""" - code_raw = (row.get("FRACCION_ARANCELARIA") or "").strip() - if not code_raw: - return {"line": line_num, "col": "FRACCION_ARANCELARIA", "msg": MSG_COL_A_VACIO} - code_norm = normalize_code(code_raw) - if not code_norm: - return {"line": line_num, "col": "FRACCION_ARANCELARIA", "msg": MSG_COL_A_VACIO} - if len(code_norm) > CODE_MAX: - return { - "line": line_num, - "col": "FRACCION_ARANCELARIA", - "msg": f"Error: (Col. A) La Fraccion Americana: {code_raw} supera la longitud de caracteres. {MSG_COL_A_LONGITUD}", - } - return None - - -def _validate_col_d_required(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]: - """Col D obligatoria en validación completa (VALIDA_TODA).""" - if (row.get("DESCRIPCION") or "").strip(): - return None - return {"line": line_num, "col": "DESCRIPCION", "msg": MSG_COL_D_OBLIGATORIO} - - -def _validate_uom_catalog( - row: Dict[str, Any], line_num: int, valid_uom_codes: Optional[Set[str]] -) -> Optional[Dict[str, Any]]: - """Col C: si no vacía, debe existir en catálogo U.M. (Clarion GUniMedida).""" - val = (row.get("UNIDAD_DE_MEDIDA") or "").strip().upper() - if not val or valid_uom_codes is None: - return None - if val in valid_uom_codes: - return None - return { - "line": line_num, - "col": "UNIDAD_DE_MEDIDA", - "msg": ( - f"Error: (Col. C) La Unidad de Medida: {row.get('UNIDAD_DE_MEDIDA') or ''} no existe en el Catálogo de U.M. " - "Revisar esta Unidad de Medida en el archivo, en caso de ser correcta dar la de alta en el Catálogo de U.M." - ), - } - - -def _validaciones_fraccioname( - row: Dict[str, Any], - line_num: int, - valid_uom_codes: Optional[Set[str]], -) -> List[Dict[str, Any]]: - """VALIDACIONES_FRACCIONAME: Col A len ≤16, Col C en catálogo U.M., Col E PO/ME/vacío, F/G ≥0.""" - errors: List[Dict[str, Any]] = [] - - for err in [ - _validate_col_a_required(row, line_num), - check_optional_max_length(row, "PREFIJO", PREFIX_MAX, line_num), - check_optional_max_length(row, "UNIDAD_DE_MEDIDA", UNIT_MAX, line_num), - _validate_uom_catalog(row, line_num, valid_uom_codes), - check_tipo_advalorem(row, "TIPO_DE_ADVALOREM", line_num), - check_optional_decimal_min_zero(row, "ADVALOREM_PCT", line_num), - check_optional_decimal_min_zero(row, "ADVALOREM_DLLS", line_num), - ]: - if err: - errors.append(err) - - return errors - - -def validate_row_us_tariff_fraction( - row: Dict[str, Any], - line_num: int, - *, - actualizar: bool = False, - existing_fraction_codes: Optional[Set[str]] = None, - valid_uom_codes: Optional[Set[str]] = None, - raw_row: Optional[Dict[str, Any]] = None, -) -> List[Dict[str, Any]]: - """ - Valida una fila de CSV de fracción arancelaria americana. - - raw_row: si se pasa y la 8ª columna tiene valor, se devuelve advertencia de desfase (no bloqueante; el caller decide si la trata como error). - - Col A vacía → error. - - Si actualizar y fracción no existe en existing_fraction_codes → error "La Fracción Americana no existe". - - Si actualizar y fracción existe → VALIDA_PARCIAL (solo VALIDACIONES_FRACCIONAME; Col D no obligatoria). - - Si no actualizar → VALIDA_TODA (Col D obligatoria + VALIDACIONES_FRACCIONAME). - """ - # Desfase: advertencia (no bloqueante por defecto; se devuelve como error para que scan la registre en errors_detail pero no bloquea commit si no está en error_lines) - # Plan: "advertencia no bloqueante" → no añadimos a error_lines; guardamos en warnings. Para simplificar, desfase lo devolvemos como error de severidad warning y en tasks no lo añadimos a error_lines (solo a warnings_detail). Mejor: desfase retornamos None (no error) y el caller puede llamar a validate_row_desfase_fa por separado y acumular warnings. Así no bloqueamos. Entonces en validate_row_us_tariff_fraction no llamamos desfase como error; en tasks llamamos primero validate_row_desfase_fa y si hay warning lo guardamos en warnings_detail, luego llamamos validate_row_us_tariff_fraction que puede devolver error. OK. - # So we don't return desfase from validate_row_us_tariff_fraction; tasks will call validate_row_desfase_fa and collect warnings. So no change here for desfase inside this function. - - errors: List[Dict[str, Any]] = [] - - err = _validate_col_a_required(row, line_num) - if err: - errors.append(err) - - code_norm = normalize_code((row.get("FRACCION_ARANCELARIA") or "").strip()) - fraction_exists = ( - bool(code_norm) - and resolve_american_fraction_from_sitar(code_norm) is not None - ) - - if actualizar and not fraction_exists: - errors.append({ - "line": line_num, - "col": "FRACCION_ARANCELARIA", - "msg": MSG_FRACCION_NO_EXISTE, - }) - - use_full = not actualizar or not fraction_exists - if use_full: - err = _validate_col_d_required(row, line_num) - if err: - errors.append(err) - - errors.extend(_validaciones_fraccioname(row, line_num, valid_uom_codes)) - return errors diff --git a/backend/api/v1/modules/a76/layouts_csv/us_tariff_fractions/validators/create.py b/backend/api/v1/modules/a76/layouts_csv/us_tariff_fractions/validators/create.py deleted file mode 100644 index 10ec95f4..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/us_tariff_fractions/validators/create.py +++ /dev/null @@ -1,10 +0,0 @@ -""" -Punto de entrada de validación para import de una fila fracción arancelaria americana. -Firma: validate_row_us_tariff_fraction(row, line_num, *, actualizar=False, existing_fraction_codes=None, valid_uom_codes=None, raw_row=None). -""" -from .common import ( - validate_row_us_tariff_fraction, - validate_row_desfase_fa, -) - -__all__ = ["validate_row_us_tariff_fraction", "validate_row_desfase_fa"] diff --git a/backend/api/v1/modules/a76/layouts_csv/vehicles/__init__.py b/backend/api/v1/modules/a76/layouts_csv/vehicles/__init__.py deleted file mode 100644 index 87b6b8b2..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/vehicles/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# CSV import for Vehicles / Transportes (upload → scan → status → commit). diff --git a/backend/api/v1/modules/a76/layouts_csv/vehicles/common/__init__.py b/backend/api/v1/modules/a76/layouts_csv/vehicles/common/__init__.py deleted file mode 100644 index 0dd687d4..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/vehicles/common/__init__.py +++ /dev/null @@ -1 +0,0 @@ -# common_validators, mappers (no fk_loader for vehicles) diff --git a/backend/api/v1/modules/a76/layouts_csv/vehicles/common/common_validators.py b/backend/api/v1/modules/a76/layouts_csv/vehicles/common/common_validators.py deleted file mode 100644 index fd588f6e..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/vehicles/common/common_validators.py +++ /dev/null @@ -1,248 +0,0 @@ -""" -Validadores reutilizables para import CSV de vehículos (longitudes, decimal, fecha aseguradora). -Paridad Clarion: VALIDACIONES_TRANSPORTE, código entidad C/I/A/B, catálogos tipo/país/estado, desfase. -""" -import re -from decimal import Decimal -from typing import Dict, Any, Optional, Set, Tuple - -# Max lengths from Vehicle model (a76.vehicle). Clarion Col A máx 15; modelo actual 14. -MAX_LEN = { - "vehicle_key": 14, - "ace_vehicle_key": 10, - "transporter_key": 23, - "transport_identifier": 30, - "transport_type": 2, - "entity_code": 1, - "transponder_number": 16, - "dot_number": 8, - "plate_number": 17, - "city": 30, - "state": 30, - "country": 3, - "seal": 49, - "insurance_company_name": 30, - "insurance_number": 20, - "series": 30, -} - - -def check_required(row: Dict[str, Any], col: str, max_len: int, line_num: int) -> Optional[Dict[str, Any]]: - val = (row.get(col) or "").strip() - if not val: - return {"line": line_num, "col": col, "msg": "Requerido"} - if len(val) > max_len: - return {"line": line_num, "col": col, "msg": f"Máximo {max_len} caracteres"} - return None - - -def check_max_length( - row: Dict[str, Any], col: str, max_len: int, line_num: int, required: bool = False -) -> Optional[Dict[str, Any]]: - val = (row.get(col) or "").strip() - if not val: - if required: - return {"line": line_num, "col": col, "msg": "Requerido"} - return None - if len(val) > max_len: - return {"line": line_num, "col": col, "msg": f"Máximo {max_len} caracteres"} - return None - - -def parse_decimal(val: Any) -> Optional[Decimal]: - if val is None or (isinstance(val, str) and not val.strip()): - return None - try: - s = str(val).strip().replace(",", "") - return Decimal(s) - except Exception: - return None - - -def parse_insurance_date(val: Any) -> Optional[int]: - """Parse FECHA DE ASEGURADORA to integer yyyymmdd. Tolerates DD/MM/YYYY, YYYY-MM-DD, or YYYYMMDD.""" - if val is None or (isinstance(val, str) and not val.strip()): - return None - s = str(val).strip() - if not s: - return None - if re.match(r"^\d{8}$", s): - try: - return int(s) - except ValueError: - pass - for sep in ["/", "-", "."]: - if sep in s: - parts = s.split(sep) - if len(parts) == 3: - try: - a, b, c = [p.strip() for p in parts] - if len(c) == 4 and len(a) <= 2 and len(b) <= 2: - return int(c) * 10000 + int(b) * 100 + int(a) - if len(a) == 4 and len(b) <= 2 and len(c) <= 2: - return int(a) * 10000 + int(b) * 100 + int(c) - except (ValueError, TypeError): - pass - break - return None - - -def check_optional_decimal(row: Dict[str, Any], col: str, line_num: int) -> Optional[Dict[str, Any]]: - val = row.get(col) - if val is None or (isinstance(val, str) and not val.strip()): - return None - if parse_decimal(val) is None: - return {"line": line_num, "col": col, "msg": "Debe ser numérico"} - return None - - -def check_optional_insurance_date(row: Dict[str, Any], col: str, line_num: int) -> Optional[Dict[str, Any]]: - val = row.get(col) - if val is None or (isinstance(val, str) and not val.strip()): - return None - if parse_insurance_date(val) is None: - return {"line": line_num, "col": col, "msg": "Formato de fecha inválido (use YYYYMMDD o DD/MM/YYYY)"} - return None - - -# --- Clarion VALIDACIONES_TRANSPORTE: dominio y catálogos --- - -CODIGO_ENTIDAD_VALIDOS = {"C", "I", "A", "B"} - - -def check_codigo_entidad_valores(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]: - """Col F: Si CODIGO DE ENTIDAD no vacío, debe ser C, I, A o B (Clarion).""" - val = (row.get("CODIGO DE ENTIDAD") or "").strip().upper() - if not val: - return None - if val in CODIGO_ENTIDAD_VALIDOS: - return None - return { - "line": line_num, - "col": "CODIGO DE ENTIDAD", - "msg": f"Error: (Col. F) El Codigo de Entidad: {val} es incorrecto.", - "solution": "Capturar en columna F el Codigo de Entidad correcto, C, I, A ó B.", - } - - -def check_codigo_entidad_required(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]: - """VALIDA_TODA: Col F (Codigo de Entidad) obligatorio cuando registro es nuevo.""" - val = (row.get("CODIGO DE ENTIDAD") or "").strip() - if val: - return None - return { - "line": line_num, - "col": "CODIGO DE ENTIDAD", - "msg": "Existen campos vacios que son obligatorios, es la (Col.F) Codigo de Entidad.", - "solution": "Revisar la línea del archivo y capturar los campos con la información correcta.", - } - - -def check_transport_type_catalog( - row: Dict[str, Any], - line_num: int, - valid_transport_codes: Optional[Set[str]] = None, -) -> Optional[Dict[str, Any]]: - """Col E: Si TIPO TRANSPORTE no vacío, debe existir en catálogo (GTipoTransportes).""" - val = (row.get("TIPO TRANSPORTE") or "").strip() - if not val or valid_transport_codes is None: - return None - if val.upper() in valid_transport_codes: - return None - return { - "line": line_num, - "col": "TIPO TRANSPORTE", - "msg": f"Error: (Col. E) El Tipo de Transporte: {val} es incorrecto.", - "solution": "Capturar en columna E algun Tipo de Transporte correcto.", - } - - -def check_pais_catalog_vehicles( - row: Dict[str, Any], - line_num: int, - valid_country_ame: Optional[Set[str]] = None, -) -> Optional[Dict[str, Any]]: - """Col L: Si PAIS no vacío, debe ser clave americana (2 chars) y existir en catálogo (GPaises.Pais_Ame).""" - val = (row.get("PAIS") or "").strip() - if not val or valid_country_ame is None: - return None - val_upper = val.upper() - if len(val) > 2: - return { - "line": line_num, - "col": "PAIS", - "msg": f"Error: (Celda L{line_num}) El Pais: {val} Es Incorrecto", - "solution": "Capturar en columna L un Pais en Clave Americana(US = Estados Unidos, MX = Mexico, ES = España, etc).", - } - if val_upper in valid_country_ame: - return None - return { - "line": line_num, - "col": "PAIS", - "msg": f"Error: (Col. L) El Pais: {val} es incorrecto.", - "solution": "Capturar en columna L el Pais del Transporte en Clave Americana.", - } - - -def check_estado_catalog( - row: Dict[str, Any], - line_num: int, - state_descriptions_upper: Optional[Set[str]] = None, -) -> Optional[Dict[str, Any]]: - """Col K: Si ESTADO no vacío, debe existir en catálogo (GEstados). Si hay estado, PAIS no puede estar vacío.""" - val = (row.get("ESTADO") or "").strip() - if not val or state_descriptions_upper is None: - return None - val_upper = val.upper() - if val_upper not in state_descriptions_upper: - return { - "line": line_num, - "col": "ESTADO", - "msg": f"Error: (Celda K{line_num}) El Estado: {val} es incorrecto.", - "solution": "Capturar en columna K el Estado del Transporte en Clave Americana o Nombre Completo.", - } - # Si existe estado, PAIS no puede estar vacío (Clarion) - pais = (row.get("PAIS") or "").strip() - if not pais: - return { - "line": line_num, - "col": "PAIS", - "msg": f"Error: (Celda K{line_num}) El Estado: {val} No esta ligado a ningun Pais.", - "solution": "Capturar en columna L un Pais en Clave Americana(US = Estados Unidos, MX = Mexico, ES = España, etc).", - } - return None - - -def check_estado_pais_consistency( - row: Dict[str, Any], - line_num: int, - state_country_set: Optional[Set[Tuple[str, str]]] = None, -) -> Optional[Dict[str, Any]]: - """Si hay ESTADO y PAIS, validar que el estado pertenezca al país (Clarion GEstados-GPaises).""" - estado = (row.get("ESTADO") or "").strip() - pais = (row.get("PAIS") or "").strip().upper() - if not estado or not pais or state_country_set is None: - return None - key = (pais, estado.upper()) - if key in state_country_set: - return None - return { - "line": line_num, - "col": "ESTADO", - "msg": f"Error: (Col. K) EL Estado: {estado} no pertenece al Pais: {pais}.", - "solution": "Capturar en columna K un Estado que pertenesca al Pais de la columna L.", - } - - -def check_desfase_vehicles(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]: - """Si COL_EXTRA (columna 18 / Col R) tiene valor → advertencia de desfase (Clarion, no bloqueante por defecto).""" - val = (row.get("COL_EXTRA") or "").strip() - if not val: - return None - return { - "line": line_num, - "col": "COL_EXTRA", - "msg": "Advertencia: Podría existir un desfase en esta línea.", - "solution": "Revisar esta línea del archivo CSV y verificar cada campo este en la posicion correcta.", - "warning": True, - } diff --git a/backend/api/v1/modules/a76/layouts_csv/vehicles/common/fk_loader.py b/backend/api/v1/modules/a76/layouts_csv/vehicles/common/fk_loader.py deleted file mode 100644 index 1cf2b300..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/vehicles/common/fk_loader.py +++ /dev/null @@ -1,80 +0,0 @@ -""" -Carga de conjuntos FK para validación de import CSV de vehículos (transportes). -Clarion: GTipoTransportes, GPaises (Pais_Ame), GEstados (Descripcion / Clave_Ame), relación Estado-País. -""" -from typing import Set, Tuple, Optional -import logging - -from core.database import CoreSessionLocal - -logger = logging.getLogger(__name__) - - -def load_vehicles_fk_sets( - tenant_id: Optional[int] = None, - company_id: Optional[int] = None, -) -> Tuple[ - Set[str], - Set[str], - Set[str], - Set[Tuple[str, str]], -]: - """ - Carga conjuntos para validación CSV de vehículos (paridad Clarion). - Devuelve: - - valid_transport_codes: códigos de transport_types (GTipoTransportes) - - valid_country_ame: claves americana de países (GPaises.Pais_Ame), mayúsculas - - state_descriptions_upper: descripciones y mex_key en mayúsculas (GEstados) - - state_country_set: (ame_key_pais, token_upper) para description o mex_key - """ - valid_transport_codes: Set[str] = set() - valid_country_ame: Set[str] = set() - state_descriptions_upper: Set[str] = set() - state_country_set: Set[Tuple[str, str]] = set() - - try: - with CoreSessionLocal() as session: - from api.v1.modules.public.reference_data.transport_types.models import TransportType - from api.v1.modules.public.reference_data.countries.models import Country - from api.v1.modules.public.reference_data.states.models import State - - for row in session.query(TransportType.transport_code).all(): - if row[0]: - valid_transport_codes.add((row[0] or "").strip().upper()) - - for row in session.query(Country.ame_key).all(): - if row[0]: - valid_country_ame.add((row[0] or "").strip().upper()) - - for state in session.query(State).all(): - country = ( - session.query(Country) - .filter(Country.m3_key == state.m3_key) - .first() - ) - ame = None - if country and (country.ame_key or "").strip(): - ame = (country.ame_key or "").strip().upper() - - desc = (state.description or "").strip() - if desc: - state_descriptions_upper.add(desc.upper()) - if ame: - state_country_set.add((ame, desc.upper())) - - mex_key = (state.mex_key or "").strip() - if mex_key: - mk = mex_key.upper() - state_descriptions_upper.add(mk) - if ame: - state_country_set.add((ame, mk)) - - except Exception as e: - logger.warning("Vehicles import: could not load FK sets: %s", e) - - return ( - valid_transport_codes, - valid_country_ame, - state_descriptions_upper, - state_country_set, - ) diff --git a/backend/api/v1/modules/a76/layouts_csv/vehicles/common/mappers.py b/backend/api/v1/modules/a76/layouts_csv/vehicles/common/mappers.py deleted file mode 100644 index a44f7ff9..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/vehicles/common/mappers.py +++ /dev/null @@ -1,117 +0,0 @@ -""" -Mapeo fila CSV → datos para Vehicle. -Paridad Clarion LLENA_TRANSPORTE: en actualización, campo vacío en CSV usa valor existente del vehículo. -""" -from typing import Dict, Any, Optional, Union - -from .common_validators import ( - MAX_LEN, - parse_decimal, - parse_insurance_date, -) - - -def _str_or_none(val: Any, max_len: Optional[int] = None) -> Optional[str]: - if val is None: - return None - s = str(val).strip() - if not s: - return None - if max_len and len(s) > max_len: - return s[:max_len] - return s - - -def _get_existing_val(existing: Union[Any, Dict[str, Any]], key: str) -> Any: - """Obtiene valor del vehículo existente (modelo ORM o dict).""" - if existing is None: - return None - if isinstance(existing, dict): - return existing.get(key) - return getattr(existing, key, None) - - -def row_to_vehicle_data(row_norm: Dict[str, Any]) -> Dict[str, Any]: - """Build dict suitable for VehicleCreateDTO / VehicleUpdateDTO from normalized row.""" - vehicle_key = _str_or_none(row_norm.get("CLAVE"), MAX_LEN["vehicle_key"]) - if not vehicle_key: - return {} - return { - "vehicle_key": vehicle_key, - "ace_vehicle_key": _str_or_none(row_norm.get("CLAVE ACE"), MAX_LEN["ace_vehicle_key"]), - "transporter_key": _str_or_none(row_norm.get("CLAVE TRANSPORTE"), MAX_LEN["transporter_key"]), - "series": _str_or_none(row_norm.get("VIN"), MAX_LEN["series"]), - "transport_type": _str_or_none(row_norm.get("TIPO TRANSPORTE"), MAX_LEN["transport_type"]), - "entity_code": _str_or_none(row_norm.get("CODIGO DE ENTIDAD"), MAX_LEN["entity_code"]), - "transponder_number": _str_or_none(row_norm.get("TRANSPONDEDOR"), MAX_LEN["transponder_number"]), - "dot_number": _str_or_none(row_norm.get("NUMERO DOT"), MAX_LEN["dot_number"]), - "plate_number": _str_or_none(row_norm.get("PLACAS"), MAX_LEN["plate_number"]), - "city": _str_or_none(row_norm.get("CIUDAD"), MAX_LEN["city"]), - "state": _str_or_none(row_norm.get("ESTADO"), MAX_LEN["state"]), - "country": _str_or_none(row_norm.get("PAIS"), MAX_LEN["country"]), - "seal": _str_or_none(row_norm.get("PRECINTO"), MAX_LEN["seal"]), - "insurance_company_name": _str_or_none(row_norm.get("EMPRESA ASEGURADORA"), MAX_LEN["insurance_company_name"]), - "insurance_number": _str_or_none(row_norm.get("NUM. ASEGURADORA"), MAX_LEN["insurance_number"]), - "insurance_amount": parse_decimal(row_norm.get("MONTO ASEGURADO") or row_norm.get("MONTO")), - "insurance_date": parse_insurance_date(row_norm.get("FECHA DE ASEGURADORA") or row_norm.get("FECHA ASEGURADORA")), - } - - -def row_to_vehicle_data_for_update( - row_norm: Dict[str, Any], - existing_vehicle: Union[Any, Dict[str, Any]], -) -> Dict[str, Any]: - """ - Build dict for VehicleUpdateDTO: CSV value if non-empty, else existing vehicle value (Clarion VALIDA_PARCIAL / LLENA_TRANSPORTE). - """ - vehicle_key = _str_or_none(row_norm.get("CLAVE"), MAX_LEN["vehicle_key"]) or _get_existing_val(existing_vehicle, "vehicle_key") - if not vehicle_key: - return {} - - def _csv_or_existing(csv_key: str, dto_key: str, max_len: Optional[int] = None): - v = _str_or_none(row_norm.get(csv_key), max_len) - if v is not None and v != "": - return v - return _get_existing_val(existing_vehicle, dto_key) - - def _csv_decimal_or_existing(csv_key: str, dto_key: str): - raw = row_norm.get("MONTO ASEGURADO") or row_norm.get("MONTO") if csv_key == "MONTO ASEGURADO" else row_norm.get(csv_key) - if raw is not None and str(raw).strip(): - d = parse_decimal(raw) - if d is not None: - return float(d) - val = _get_existing_val(existing_vehicle, dto_key) - if val is not None and hasattr(val, "__float__"): - try: - return float(val) - except (TypeError, ValueError): - pass - return val - - def _csv_date_or_existing(csv_key: str, dto_key: str): - raw = row_norm.get("FECHA DE ASEGURADORA") or row_norm.get("FECHA ASEGURADORA") - if raw is not None and str(raw).strip(): - d = parse_insurance_date(raw) - if d is not None: - return d - return _get_existing_val(existing_vehicle, dto_key) - - return { - "vehicle_key": vehicle_key, - "ace_vehicle_key": _csv_or_existing("CLAVE ACE", "ace_vehicle_key", MAX_LEN["ace_vehicle_key"]), - "transporter_key": _csv_or_existing("CLAVE TRANSPORTE", "transporter_key", MAX_LEN["transporter_key"]), - "series": _csv_or_existing("VIN", "series", MAX_LEN["series"]), - "transport_type": _csv_or_existing("TIPO TRANSPORTE", "transport_type", MAX_LEN["transport_type"]), - "entity_code": _csv_or_existing("CODIGO DE ENTIDAD", "entity_code", MAX_LEN["entity_code"]), - "transponder_number": _csv_or_existing("TRANSPONDEDOR", "transponder_number", MAX_LEN["transponder_number"]), - "dot_number": _csv_or_existing("NUMERO DOT", "dot_number", MAX_LEN["dot_number"]), - "plate_number": _csv_or_existing("PLACAS", "plate_number", MAX_LEN["plate_number"]), - "city": _csv_or_existing("CIUDAD", "city", MAX_LEN["city"]), - "state": _csv_or_existing("ESTADO", "state", MAX_LEN["state"]), - "country": _csv_or_existing("PAIS", "country", MAX_LEN["country"]), - "seal": _csv_or_existing("PRECINTO", "seal", MAX_LEN["seal"]), - "insurance_company_name": _csv_or_existing("EMPRESA ASEGURADORA", "insurance_company_name", MAX_LEN["insurance_company_name"]), - "insurance_number": _csv_or_existing("NUM. ASEGURADORA", "insurance_number", MAX_LEN["insurance_number"]), - "insurance_amount": _csv_decimal_or_existing("MONTO ASEGURADO", "insurance_amount"), - "insurance_date": _csv_date_or_existing("FECHA DE ASEGURADORA", "insurance_date"), - } diff --git a/backend/api/v1/modules/a76/layouts_csv/vehicles/csv_max_chars_for_headers.py b/backend/api/v1/modules/a76/layouts_csv/vehicles/csv_max_chars_for_headers.py deleted file mode 100644 index 36f9b06f..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/vehicles/csv_max_chars_for_headers.py +++ /dev/null @@ -1,24 +0,0 @@ -"""Límites de cabecera alineados a vehicles/common/common_validators.MAX_LEN.""" -from typing import Dict - -from .common.common_validators import MAX_LEN as V_MAX - -VEHICLES_CSV_MAX_CHARS: Dict[str, int] = { - "CLAVE": V_MAX["vehicle_key"], - "CLAVE ACE": V_MAX["ace_vehicle_key"], - "CLAVE TRANSPORTE": V_MAX["transporter_key"], - "VIN": V_MAX["transport_identifier"], - "TIPO TRANSPORTE": V_MAX["transport_type"], - "CODIGO DE ENTIDAD": V_MAX["entity_code"], - "TRANSPONDEDOR": V_MAX["transponder_number"], - "NUMERO DOT": V_MAX["dot_number"], - "PLACAS": V_MAX["plate_number"], - "CIUDAD": V_MAX["city"], - "ESTADO": V_MAX["state"], - "PAIS": V_MAX["country"], - "PRECINTO": V_MAX["seal"], - "EMPRESA ASEGURADORA": V_MAX["insurance_company_name"], - "NUM. ASEGURADORA": V_MAX["insurance_number"], - "MONTO ASEGURADO": 24, - "FECHA DE ASEGURADORA": 30, -} diff --git a/backend/api/v1/modules/a76/layouts_csv/vehicles/routes.py b/backend/api/v1/modules/a76/layouts_csv/vehicles/routes.py deleted file mode 100644 index 08473cf6..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/vehicles/routes.py +++ /dev/null @@ -1,206 +0,0 @@ -""" -Rutas de importación CSV para Vehículos (Transportes). -Flujo: upload → scan → status (polling) → commit. -""" -import json -import logging -import os -import threading -from uuid import uuid4 - -from fastapi import APIRouter, File, HTTPException, Query, UploadFile, Depends -from sqlalchemy.orm import Session -from typing import Dict, Any - -from core.celery_app import celery_app -from core.database import get_core_db -from core.security import get_current_user, validate_access_to_resource -from api.v1.modules.core.tasks_tracking import track_and_dispatch - -from .schemas import ImportJobResponse -from .tasks import ( - scan_file, - run_scan_sync, - run_commit_sync, - JOB_TYPE, - VEHL_IMPORT_STATUS_PREFIX, - VEHL_IMPORT_REDIS_TTL, -) -from ..common import storage as common_storage -from ..common.error_csv import download_scan_errors_csv_stream -from ..common.responses import normalize_commit_status_payload - -router = APIRouter() -logger = logging.getLogger(__name__) - - -def _get_redis(): - import redis - url = os.getenv("VALKEY_URL", os.getenv("REDIS_URL", "redis://valkey:6379/0")) - return redis.Redis.from_url(url, decode_responses=False) - - -@router.post("/upload", response_model=ImportJobResponse) -async def upload_import_file( - file: UploadFile = File(...), - company_id: int = Query(..., description="Company ID"), - actualizar: bool = Query(False, description="Modo Agregar/Actualizar (ACT); si True, validación parcial para claves existentes"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - try: - tenant_id = validate_access_to_resource(db, company_id, current_user, ["csv_upload.process"]) - except Exception as e: - logger.error(f"Vehicles import: access validation failed: {e}") - raise HTTPException(status_code=403, detail="Invalid company access") - - if not file.filename or not file.filename.lower().endswith(".csv"): - raise HTTPException(status_code=400, detail="Solo se permiten archivos .csv") - - job_id = str(uuid4()) - contents = await file.read() - - meta_data = { - "tenant_id": tenant_id, - "company_id": company_id, - "user_id": current_user.get("id"), - "template_id": "vehicles", - "actualizar": actualizar, - } - - try: - common_storage.store_import_file( - JOB_TYPE, - job_id, - contents, - meta_data, - tenant_id=int(tenant_id), - company_id=company_id, - ttl=VEHL_IMPORT_REDIS_TTL, - log_label="Vehicles import", - ) - except common_storage.ImportStoreError as e: - logger.error(f"Vehicles import: store error: {e}") - raise HTTPException(status_code=500, detail="No se pudo encolar el archivo.") - except Exception as e: - logger.error(f"Vehicles import: Redis store error: {e}") - raise HTTPException(status_code=500, detail="No se pudo encolar el archivo.") - - track_and_dispatch( - db=db, - task=scan_file, - tenant_id=int(tenant_id), - company_id=company_id, - requested_by_user=current_user.get("preferred_username") or current_user.get("email") or current_user.get("sub"), - task_name="vehicles_scan_file", - task_group="layouts_csv", - task_origin="a76/layouts_csv/vehicles/upload", - args=[job_id], - task_id=job_id, - ) - # Fallback sin worker: ejecutar scan en un hilo y guardar resultado en Redis - def run_scan_background(): - try: - run_scan_sync(job_id) - except Exception as e: - logger.exception(f"Vehicles import: background scan failed: {e}") - - threading.Thread(target=run_scan_background, daemon=True).start() - - return ImportJobResponse( - job_id=job_id, - status="queued", - message="Archivo subido. Escaneo iniciado.", - ) - - -@router.get("/{job_id}/status") -async def get_import_status(job_id: str): - # Si el resultado del scan está en Redis (worker o hilo de fallback), usarlo - try: - r = _get_redis() - raw = r.get(f"{VEHL_IMPORT_STATUS_PREFIX}{job_id}") - if raw: - data = json.loads(raw.decode("utf-8")) - if isinstance(data, dict) and data.get("status") in ("finished", "warning"): - return normalize_commit_status_payload(data) - return data - except Exception as e: - logger.debug(f"Vehicles import: could not read status from Redis: {e}") - - task_result = celery_app.AsyncResult(job_id) - - if task_result.state == "PENDING": - return {"status": "processing", "progress": 0} - if task_result.state == "PROGRESS": - info = (task_result.info or {}) - return { - "status": "processing", - "progress": info.get("current", 0), - "total": info.get("total", 0), - } - if task_result.state == "SUCCESS": - result = task_result.result - if isinstance(result, dict) and "status" in result: - return normalize_commit_status_payload(result) - return {"status": "finished", "result": result} - - result = getattr(task_result, "result", None) - if isinstance(result, dict) and result.get("status") in ("finished", "warning"): - return normalize_commit_status_payload(result) - - logger.warning("Vehicles import task %s failed: state=%s", job_id, task_result.state) - err_msg = None - tb = getattr(task_result, "traceback", None) - if tb and isinstance(tb, str): - lines = [l.strip() for l in tb.strip().split("\n") if l.strip()] - if lines: - err_msg = lines[-1] - if not err_msg: - try: - exc = task_result.get(propagate=False) - if exc is not None: - err_msg = str(exc) - except Exception: - pass - if not err_msg and result is not None: - if not isinstance(result, dict): - err_msg = str(result) - elif result.get("error") or result.get("message"): - err_msg = result.get("error") or result.get("message") - return {"status": "failed", "error": err_msg or "Task failed"} - - -@router.post("/{job_id}/commit") -async def commit_import_job(job_id: str): - # Escribir "processing" en Redis para que el polling muestre estado hasta que termine el commit - try: - r = _get_redis() - r.set( - f"{VEHL_IMPORT_STATUS_PREFIX}{job_id}", - json.dumps({"status": "processing", "message": "Insertando..."}).encode("utf-8"), - ex=VEHL_IMPORT_REDIS_TTL, - ) - except Exception as e: - logger.debug(f"Vehicles import: could not write processing status: {e}") - - # Ejecutar commit en hilo (mismo fallback que el scan: no depende del worker de Celery) - def run_commit_background(): - try: - run_commit_sync(job_id) - except Exception as e: - logger.exception(f"Vehicles import: background commit failed: {e}") - - threading.Thread(target=run_commit_background, daemon=True).start() - - # Mismo job_id para que el front siga haciendo polling y reciba el resultado de Redis - return { - "status": "committing", - "message": "Inserción iniciada.", - "commit_job_id": job_id, - } - - -@router.get("/{job_id}/errors/scan-csv") -async def download_scan_errors_csv(job_id: str): - return download_scan_errors_csv_stream("veh", job_id) diff --git a/backend/api/v1/modules/a76/layouts_csv/vehicles/schemas.py b/backend/api/v1/modules/a76/layouts_csv/vehicles/schemas.py deleted file mode 100644 index 7827dae6..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/vehicles/schemas.py +++ /dev/null @@ -1,22 +0,0 @@ -from pydantic import BaseModel -from typing import Optional - - -class ImportJobResponse(BaseModel): - job_id: str - status: str - message: str - - -class ImportJobStatus(BaseModel): - status: str - job_id: str - total_rows: Optional[int] = 0 - error_count: Optional[int] = 0 - valid_rows: Optional[int] = 0 - error: Optional[str] = None - inserted: Optional[int] = 0 - skipped_invalid: Optional[int] = 0 - skipped_missing_fk: Optional[int] = 0 - skipped_duplicate: Optional[int] = 0 - skipped_details: Optional[list] = None diff --git a/backend/api/v1/modules/a76/layouts_csv/vehicles/tasks.py b/backend/api/v1/modules/a76/layouts_csv/vehicles/tasks.py deleted file mode 100644 index ad9608ed..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/vehicles/tasks.py +++ /dev/null @@ -1,404 +0,0 @@ -""" -Tareas Celery para importación CSV de Vehículos (Transportes). -Flujo: scan_file (validación) → insert_valid_rows (commit). -Usa layouts_csv.common (storage, normalize, meta, responses, csv_reader). -""" -import json -import logging -import os -from typing import Dict, Any, Optional, List, Set - -from core.celery_app import celery_app -from core.database import CoreSessionLocal - -from ..common import storage as common_storage -from ..common import normalize as common_normalize -from ..common import meta as common_meta -from ..common import responses as common_responses -from ..common import csv_reader as common_csv_reader -from .template_config import row_from_template -from .validators import validate_row_vehicle, validate_row_vehicle_desfase -from .common.mappers import row_to_vehicle_data, row_to_vehicle_data_for_update -from .common.fk_loader import load_vehicles_fk_sets - -logger = logging.getLogger(__name__) - -JOB_TYPE = "veh" - -# Para routes.py -VEHL_IMPORT_FILE_PREFIX = "veh_import_file:" -VEHL_IMPORT_META_PREFIX = "veh_import_meta:" -VEHL_IMPORT_ERROR_LINES_PREFIX = "veh_import_error_lines:" -VEHL_IMPORT_STATUS_PREFIX = "veh_import_status:" -VEHL_IMPORT_REDIS_TTL = common_storage.IMPORT_REDIS_TTL - - -def _get_redis(): - import redis - url = os.getenv("VALKEY_URL", os.getenv("REDIS_URL", "redis://valkey:6379/0")) - return redis.Redis.from_url(url, decode_responses=False) - - -def _do_scan(job_id: str, progress_callback: Optional[Any] = None) -> Dict[str, Any]: - file_path = common_storage.ensure_file_from_redis(JOB_TYPE, job_id, "Vehicles import") - if not file_path: - return {"status": "failed", "error": "Archivo no encontrado (expirado o no subido). Sube de nuevo."} - common_storage.ensure_meta_from_redis(JOB_TYPE, job_id, file_path, "Vehicles import") - - error_path = common_storage.error_path_for_job(JOB_TYPE, job_id) - - try: - total_rows = common_csv_reader.count_csv_rows(file_path) - except Exception as e: - return {"status": "failed", "error": str(e)} - - try: - tenant_id, company_id = common_meta.require_tenant_context(file_path) - except ValueError as e: - return {"status": "failed", "error": str(e)} - - meta = common_meta.load_meta(file_path) or {} - actualizar = meta.get("actualizar", False) - existing_vehicle_keys: Set[str] = set() - if actualizar: - try: - from api.v1.modules.a76.transportation.vehicles.models import Vehicle - with CoreSessionLocal() as session: - for v in ( - session.query(Vehicle.vehicle_key) - .filter( - Vehicle.tenant_id == tenant_id, - Vehicle.company_id == company_id, - ) - .all() - ): - if v[0] and (v[0] or "").strip(): - existing_vehicle_keys.add((v[0] or "").strip()) - except Exception as e: - logger.warning("Vehicles import: could not load existing vehicle_keys for actualizar: %s", e) - - ( - valid_transport_codes, - valid_country_ame, - state_descriptions_upper, - state_country_set, - ) = load_vehicles_fk_sets(tenant_id, company_id) - - error_count = 0 - processed_rows = 0 - errors_detail: List[Dict[str, Any]] = [] - error_lines_list: List[int] = [] - - try: - with open(error_path, "w", encoding="utf-8") as f_err: - for i, row in common_csv_reader.iter_csv_rows(file_path): - if progress_callback: - progress_callback(i, total_rows, error_count) - - row_norm = row_from_template(row, common_normalize.normalize_header) - # Desfase: advertencia no bloqueante (no se añade a error_lines) - warn = validate_row_vehicle_desfase(row_norm, i) - if warn and len(errors_detail) < 500: - errors_detail.append({ - "line": warn.get("line", i), - "col": warn.get("col", ""), - "msg": warn.get("msg", ""), - "solution": warn.get("solution", ""), - "warning": True, - }) - - row_errors = validate_row_vehicle( - row_norm, - i, - actualizar=actualizar, - existing_vehicle_keys=existing_vehicle_keys, - valid_transport_codes=valid_transport_codes, - valid_country_ame=valid_country_ame, - state_descriptions_upper=state_descriptions_upper, - state_country_set=state_country_set, - ) - if row_errors: - has_blocking_error = any(not e.get("warning", False) for e in row_errors) - if has_blocking_error: - error_count += 1 - error_lines_list.append(i) - for e in row_errors: - if not e.get("warning", False): - f_err.write(json.dumps(e) + "\n") - if len(errors_detail) < 500: - for e in row_errors: - if len(errors_detail) >= 500: - break - errors_detail.append({ - "line": e.get("line", i), - "col": e.get("col", ""), - "msg": e.get("msg", ""), - "solution": e.get("solution", ""), - "warning": bool(e.get("warning", False)), - }) - processed_rows += 1 - - if error_lines_list: - common_storage.store_error_lines(JOB_TYPE, job_id, error_lines_list) - except Exception as e: - logger.error("Vehicles import scan failed: %s", e) - return {"status": "failed", "error": str(e)} - - return common_responses.scan_result(job_id, processed_rows, error_count, errors_detail) - - -def run_scan_sync(job_id: str) -> Dict[str, Any]: - result = _do_scan(job_id, progress_callback=None) - try: - r = _get_redis() - r.set( - f"{VEHL_IMPORT_STATUS_PREFIX}{job_id}", - json.dumps(result).encode("utf-8"), - ex=VEHL_IMPORT_REDIS_TTL, - ) - except Exception as e: - logger.warning("Vehicles import: failed to store scan status in Redis: %s", e) - return result - - -@celery_app.task(bind=True) -def scan_file(self, job_id: str, config: str = None): - logger.info("Vehicles import: starting scan for job %s", job_id) - - def on_progress(current: int, total: int, errors: int) -> None: - self.update_state(state="PROGRESS", meta={"current": current, "total": total, "errors": errors}) - - result = _do_scan(job_id, progress_callback=on_progress) - try: - r = _get_redis() - r.set( - f"{VEHL_IMPORT_STATUS_PREFIX}{job_id}", - json.dumps(result).encode("utf-8"), - ex=VEHL_IMPORT_REDIS_TTL, - ) - except Exception as e: - logger.warning("Vehicles import: failed to store scan status in Redis: %s", e) - return result - - -def _do_commit(job_id: str) -> Dict[str, Any]: - file_path = common_storage.ensure_file_from_redis(JOB_TYPE, job_id, "Vehicles import") - if not file_path: - alt_path = common_storage.file_path_for_job(JOB_TYPE, job_id) - if not os.path.exists(alt_path): - return {"status": "failed", "error": "Archivo no encontrado (expirado). Sube y confirma de nuevo."} - file_path = alt_path - else: - common_storage.ensure_meta_from_redis(JOB_TYPE, job_id, file_path, "Vehicles import") - - error_path = common_storage.error_path_for_job(JOB_TYPE, job_id) - error_lines = common_storage.get_error_lines(JOB_TYPE, job_id, error_path) - - try: - tenant_id, company_id = common_meta.require_tenant_context(file_path) - except ValueError as e: - return {"status": "failed", "error": str(e)} - - meta = common_meta.load_meta(file_path) or {} - actualizar = meta.get("actualizar", False) - existing_vehicle_keys: Set[str] = set() - if actualizar: - try: - from api.v1.modules.a76.transportation.vehicles.models import Vehicle - with CoreSessionLocal() as session: - for v in ( - session.query(Vehicle.vehicle_key) - .filter( - Vehicle.tenant_id == tenant_id, - Vehicle.company_id == company_id, - ) - .all() - ): - if v[0] and (v[0] or "").strip(): - existing_vehicle_keys.add((v[0] or "").strip()) - except Exception as e: - logger.warning("Vehicles import: could not load existing vehicle_keys for actualizar: %s", e) - - ( - valid_transport_codes, - valid_country_ame, - state_descriptions_upper, - state_country_set, - ) = load_vehicles_fk_sets(tenant_id, company_id) - - from api.v1.modules.a76.transportation.vehicles.services import VehicleService - from api.v1.modules.a76.transportation.vehicles.dto import VehicleCreateDTO, VehicleUpdateDTO - - inserted_count = 0 - updated_count = 0 - skipped_invalid = 0 - skipped_duplicate = 0 - skipped_details: List[Dict[str, Any]] = [] - seen_keys_in_file: Dict[str, int] = {} - meta_path = common_meta.get_meta_path(file_path) - - try: - with CoreSessionLocal() as session: - for i, row in common_csv_reader.iter_csv_rows(file_path): - if i in error_lines: - continue - - row_norm = row_from_template(row, common_normalize.normalize_header) - row_errors = validate_row_vehicle( - row_norm, - i, - actualizar=actualizar, - existing_vehicle_keys=existing_vehicle_keys, - valid_transport_codes=valid_transport_codes, - valid_country_ame=valid_country_ame, - state_descriptions_upper=state_descriptions_upper, - state_country_set=state_country_set, - ) - if row_errors and any(not e.get("warning", False) for e in row_errors): - skipped_invalid += 1 - vk = (row_norm.get("CLAVE") or "").strip()[:14] or "-" - blocking_errors = [e for e in row_errors if not e.get("warning", False)] - for blocking in blocking_errors: - skipped_details.append({ - "line": i, - "vehicle_key": vk, - "invoice": vk, - "reason": f"{blocking.get('col', '')}: {blocking.get('msg', '')}", - "solution": blocking.get("solution", ""), - }) - continue - - vk = (row_norm.get("CLAVE") or "").strip()[:14] or "" - if not vk: - skipped_invalid += 1 - continue - if vk in seen_keys_in_file: - skipped_duplicate += 1 - skipped_details.append({ - "line": i, - "vehicle_key": vk, - "invoice": vk, - "reason": "Clave duplicada en el archivo (se usa la primera)", - "solution": "El sistema conserva la primera ocurrencia; no dupliques el valor de CLAVE en el archivo.", - }) - continue - seen_keys_in_file[vk] = i - - existing = VehicleService.get_by_id(session, vk, tenant_id, company_id) - try: - if existing: - if actualizar: - data = row_to_vehicle_data_for_update(row_norm, existing) - else: - data = row_to_vehicle_data(row_norm) - if not data or not data.get("vehicle_key"): - skipped_invalid += 1 - continue - update_data = VehicleUpdateDTO(**{k: v for k, v in data.items() if k != "vehicle_key"}) - VehicleService.update(session, vk, tenant_id, update_data, company_id) - updated_count += 1 - else: - data = row_to_vehicle_data(row_norm) - if not data or not data.get("vehicle_key"): - skipped_invalid += 1 - continue - create_data = VehicleCreateDTO(**data) - VehicleService.create(session, create_data, tenant_id, company_id) - inserted_count += 1 - except Exception as db_err: - session.rollback() - skipped_invalid += 1 - skipped_details.append({ - "line": i, - "vehicle_key": vk, - "invoice": vk, - "reason": str(db_err), - "solution": "Revisar el valor de CLAVE del vehículo y los catálogos relacionados para corregir el error.", - }) - continue - - try: - session.commit() - except Exception as db_err: - session.rollback() - logger.error("Vehicles import DB error: %s", db_err) - return {"status": "failed", "error": str(db_err)} - - except Exception as e: - logger.exception("Vehicles import task failed") - return {"status": "failed", "error": str(e)} - - common_storage.cleanup_import_job( - JOB_TYPE, job_id, - file_path=file_path, - error_path=error_path, - meta_path=meta_path, - ) - try: - r = _get_redis() - r.delete(f"{VEHL_IMPORT_STATUS_PREFIX}{job_id}") - except Exception as e: - logger.warning("Vehicles import: failed to delete status key: %s", e) - - total_ok = inserted_count + updated_count - if total_ok == 0 and (skipped_invalid + skipped_duplicate) > 0: - return { - "status": "warning", - "inserted": inserted_count, - "updated": updated_count, - "skipped_invalid": skipped_invalid, - "skipped_duplicate": skipped_duplicate, - "skipped_missing_fk": 0, - "skipped_details": skipped_details, - "message": f"No se insertaron registros. {skipped_invalid + skipped_duplicate} rechazados.", - } - if total_ok == 0: - return { - "status": "failed", - "error": "No hay registros válidos en el archivo CSV", - "inserted": 0, - "updated": 0, - "skipped_invalid": skipped_invalid, - "skipped_duplicate": skipped_duplicate, - "skipped_missing_fk": 0, - "skipped_details": skipped_details, - } - return { - "status": "finished", - "inserted": inserted_count, - "updated": updated_count, - "skipped_invalid": skipped_invalid, - "skipped_duplicate": skipped_duplicate, - "skipped_missing_fk": 0, - "skipped_details": skipped_details, - } - - -def run_commit_sync(job_id: str) -> Dict[str, Any]: - result = _do_commit(job_id) - try: - r = _get_redis() - r.set( - f"{VEHL_IMPORT_STATUS_PREFIX}{job_id}", - json.dumps(result).encode("utf-8"), - ex=VEHL_IMPORT_REDIS_TTL, - ) - except Exception as e: - logger.warning("Vehicles import: failed to store commit status in Redis: %s", e) - return result - - -@celery_app.task(bind=True) -def insert_valid_rows(self, job_id: str): - logger.info("Vehicles import: starting commit for job %s", job_id) - result = _do_commit(job_id) - try: - r = _get_redis() - r.set( - f"{VEHL_IMPORT_STATUS_PREFIX}{job_id}", - json.dumps(result).encode("utf-8"), - ex=VEHL_IMPORT_REDIS_TTL, - ) - except Exception as e: - logger.warning("Vehicles import: failed to store commit status in Redis: %s", e) - return result diff --git a/backend/api/v1/modules/a76/layouts_csv/vehicles/template_config.py b/backend/api/v1/modules/a76/layouts_csv/vehicles/template_config.py deleted file mode 100644 index 459d7617..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/vehicles/template_config.py +++ /dev/null @@ -1,66 +0,0 @@ -""" -Configuración de plantilla CSV para Vehículos / Transportes (EstructuraCatTransportes.xls). -Mapeo: CLAVE → vehicle_key, CLAVE ACE → ace_vehicle_key, etc. -""" - -from typing import Dict, List, Any - -from ..common.cell_value import cell_to_str -from ..common.csv_hint_inject import inject_max_char_hints_into_columns -from ..common.inject_csv_labels_en import inject_en_labels_into_template_columns -from ..common.template_locale import merge_download_header_into_lookup, merge_labels_into_lookup -from .csv_max_chars_for_headers import VEHICLES_CSV_MAX_CHARS - -TEMPLATE_COLUMNS: Dict[str, List[Dict[str, Any]]] = { - "vehicles": [ - {"canonical": "CLAVE", "aliases": ["CLAVE VEHICULO", "VEHICLE KEY", "KEY"]}, - {"canonical": "CLAVE ACE", "aliases": ["ACE", "CLAVEACE"]}, - {"canonical": "CLAVE TRANSPORTE", "aliases": ["TRANSPORTE", "TRANSPORTISTA"]}, - {"canonical": "VIN", "aliases": ["SERIE", "SERIES", "NUMERO SERIE"]}, - {"canonical": "TIPO TRANSPORTE", "aliases": ["TIPO", "TRANSPORT TYPE"]}, - {"canonical": "CODIGO DE ENTIDAD", "aliases": ["ENTIDAD", "ENTITY CODE"]}, - {"canonical": "TRANSPONDEDOR", "aliases": ["TRANSPONDER", "TRANSPONDER NUMBER"]}, - {"canonical": "NUMERO DOT", "aliases": ["DOT", "NUM DOT"]}, - {"canonical": "PLACAS", "aliases": ["PLACA", "PLATE", "PLATE NUMBER"]}, - {"canonical": "CIUDAD", "aliases": ["CITY"]}, - {"canonical": "ESTADO", "aliases": ["STATE"]}, - {"canonical": "PAIS", "aliases": ["COUNTRY", "Pais"]}, - {"canonical": "PRECINTO", "aliases": ["SEAL"]}, - {"canonical": "EMPRESA ASEGURADORA", "aliases": ["ASEGURADORA", "INSURANCE COMPANY"]}, - {"canonical": "NUM. ASEGURADORA", "aliases": ["NUM ASEGURADORA", "POLIZA", "INSURANCE NUMBER"]}, - {"canonical": "MONTO ASEGURADO", "aliases": ["MONTO", "INSURANCE AMOUNT"]}, - {"canonical": "FECHA DE ASEGURADORA", "aliases": ["FECHA ASEGURADORA", "INSURANCE DATE"]}, - {"canonical": "COL_EXTRA", "aliases": ["COLUMNA R", "COL R"]}, - ], -} - -inject_en_labels_into_template_columns(TEMPLATE_COLUMNS) - -inject_max_char_hints_into_columns(TEMPLATE_COLUMNS.get("vehicles"), VEHICLES_CSV_MAX_CHARS) - - -def build_normalized_lookup(normalize_header_fn) -> Dict[str, str]: - cols = TEMPLATE_COLUMNS.get("vehicles") - if not cols: - return {} - lookup: Dict[str, str] = {} - for item in cols: - canonical = item["canonical"] - lookup[normalize_header_fn(canonical)] = canonical - for alias in item.get("aliases") or []: - lookup[normalize_header_fn(alias)] = canonical - merge_labels_into_lookup(item, canonical, normalize_header_fn, lookup) - merge_download_header_into_lookup(item, canonical, normalize_header_fn, lookup) - return lookup - - -def row_from_template(row: Dict[str, Any], normalize_header_fn) -> Dict[str, Any]: - lookup = build_normalized_lookup(normalize_header_fn) - if not lookup: - return {normalize_header_fn(k): cell_to_str(v) for k, v in row.items()} - out: Dict[str, Any] = {} - for csv_header, value in row.items(): - key_norm = normalize_header_fn(csv_header) - if key_norm in lookup: - out[lookup[key_norm]] = cell_to_str(value) - return out diff --git a/backend/api/v1/modules/a76/layouts_csv/vehicles/validators/__init__.py b/backend/api/v1/modules/a76/layouts_csv/vehicles/validators/__init__.py deleted file mode 100644 index b6a725f1..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/vehicles/validators/__init__.py +++ /dev/null @@ -1,3 +0,0 @@ -from .create import validate_row_vehicle, validate_row_vehicle_desfase - -__all__ = ["validate_row_vehicle", "validate_row_vehicle_desfase"] diff --git a/backend/api/v1/modules/a76/layouts_csv/vehicles/validators/common.py b/backend/api/v1/modules/a76/layouts_csv/vehicles/validators/common.py deleted file mode 100644 index 5eea7142..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/vehicles/validators/common.py +++ /dev/null @@ -1,136 +0,0 @@ -""" -Validaciones comunes de fila para import CSV de vehículos. -Paridad Clarion: VALIDACIONES_TRANSPORTE, VALIDA_TODA_TRANSPORTE, VALIDA_PARCIAL_TRANSPORTE. -""" -from typing import Dict, Any, Optional, Set, Tuple, List - -from ..common.common_validators import ( - MAX_LEN, - check_required, - check_max_length, - check_codigo_entidad_valores, - check_codigo_entidad_required, - check_transport_type_catalog, - check_pais_catalog_vehicles, - check_estado_catalog, - check_estado_pais_consistency, -) - - -def validate_row_vehicle_required(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]: - return check_required(row, "CLAVE", MAX_LEN["vehicle_key"], line_num) - - -def validate_row_vehicle_lengths(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]: - checks = [ - ("CLAVE ACE", MAX_LEN["ace_vehicle_key"]), - ("CLAVE TRANSPORTE", MAX_LEN["transporter_key"]), - ("VIN", MAX_LEN["series"]), - ("TIPO TRANSPORTE", MAX_LEN["transport_type"]), - ("CODIGO DE ENTIDAD", MAX_LEN["entity_code"]), - ("TRANSPONDEDOR", MAX_LEN["transponder_number"]), - ("NUMERO DOT", MAX_LEN["dot_number"]), - ("PLACAS", MAX_LEN["plate_number"]), - ("CIUDAD", MAX_LEN["city"]), - ("ESTADO", MAX_LEN["state"]), - ("PAIS", MAX_LEN["country"]), - ("PRECINTO", MAX_LEN["seal"]), - ("EMPRESA ASEGURADORA", MAX_LEN["insurance_company_name"]), - ("NUM. ASEGURADORA", MAX_LEN["insurance_number"]), - ] - for col, max_len in checks: - err = check_max_length(row, col, max_len, line_num) - if err: - return err - return None - - -def validate_row_vehicle_amount(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]: - from ..common.common_validators import parse_decimal - monto = row.get("MONTO ASEGURADO") or row.get("MONTO") - if monto is None or not str(monto).strip(): - return None - if parse_decimal(monto) is None: - return {"line": line_num, "col": "MONTO ASEGURADO", "msg": "Debe ser numérico"} - return None - - -def validate_row_vehicle_insurance_date(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]: - from ..common.common_validators import parse_insurance_date - fecha = row.get("FECHA DE ASEGURADORA") or row.get("FECHA ASEGURADORA") - if fecha is None or not str(fecha).strip(): - return None - if parse_insurance_date(fecha) is None: - return {"line": line_num, "col": "FECHA DE ASEGURADORA", "msg": "Formato de fecha inválido (use YYYYMMDD o DD/MM/YYYY)"} - return None - - -def validaciones_transporte( - row: Dict[str, Any], - line_num: int, - valid_transport_codes: Optional[Set[str]] = None, - valid_country_ame: Optional[Set[str]] = None, - state_descriptions_upper: Optional[Set[str]] = None, - state_country_set: Optional[Set[Tuple[str, str]]] = None, -) -> List[Dict[str, Any]]: - """ - VALIDACIONES_TRANSPORTE: reglas compartidas (longitudes, tipo transporte, código entidad dominio, - país, estado, estado-país). No exige CODIGO DE ENTIDAD obligatorio (eso es solo VALIDA_TODA). - """ - errors: List[Dict[str, Any]] = [] - for err in [ - validate_row_vehicle_lengths(row, line_num), - validate_row_vehicle_amount(row, line_num), - validate_row_vehicle_insurance_date(row, line_num), - check_codigo_entidad_valores(row, line_num), - check_transport_type_catalog(row, line_num, valid_transport_codes), - check_pais_catalog_vehicles(row, line_num, valid_country_ame), - check_estado_catalog(row, line_num, state_descriptions_upper), - check_estado_pais_consistency(row, line_num, state_country_set), - ]: - if err: - errors.append(err) - return errors - - -def valida_toda_transporte( - row: Dict[str, Any], - line_num: int, - valid_transport_codes: Optional[Set[str]] = None, - valid_country_ame: Optional[Set[str]] = None, - state_descriptions_upper: Optional[Set[str]] = None, - state_country_set: Optional[Set[Tuple[str, str]]] = None, -) -> List[Dict[str, Any]]: - """VALIDA_TODA_TRANSPORTE: CODIGO DE ENTIDAD obligatorio + VALIDACIONES_TRANSPORTE (registro nuevo).""" - errors: List[Dict[str, Any]] = [] - err = check_codigo_entidad_required(row, line_num) - if err: - errors.append(err) - errors.extend( - validaciones_transporte( - row, line_num, - valid_transport_codes=valid_transport_codes, - valid_country_ame=valid_country_ame, - state_descriptions_upper=state_descriptions_upper, - state_country_set=state_country_set, - ) - ) - return errors - - -def valida_parcial_transporte( - row: Dict[str, Any], - line_num: int, - valid_transport_codes: Optional[Set[str]] = None, - valid_country_ame: Optional[Set[str]] = None, - state_descriptions_upper: Optional[Set[str]] = None, - state_country_set: Optional[Set[Tuple[str, str]]] = None, -) -> List[Dict[str, Any]]: - """VALIDA_PARCIAL_TRANSPORTE: solo VALIDACIONES_TRANSPORTE (actualizar registro existente).""" - return validaciones_transporte( - row, line_num, - valid_transport_codes=valid_transport_codes, - valid_country_ame=valid_country_ame, - state_descriptions_upper=state_descriptions_upper, - state_country_set=state_country_set, - ) diff --git a/backend/api/v1/modules/a76/layouts_csv/vehicles/validators/create.py b/backend/api/v1/modules/a76/layouts_csv/vehicles/validators/create.py deleted file mode 100644 index 002cc239..00000000 --- a/backend/api/v1/modules/a76/layouts_csv/vehicles/validators/create.py +++ /dev/null @@ -1,68 +0,0 @@ -""" -Punto de entrada de validación para import de una fila vehículo. -Paridad Clarion: desfase (advertencia), CLAVE vacía, VALIDA_TODA vs VALIDA_PARCIAL según actualizar y clave existente. -""" -from typing import Dict, Any, Optional, Set, Tuple, List - -from .common import ( - validate_row_vehicle_required, - valida_toda_transporte, - valida_parcial_transporte, -) -from ..common.common_validators import check_desfase_vehicles - - -def validate_row_vehicle( - row: Dict[str, Any], - line_num: int, - actualizar: bool = False, - existing_vehicle_keys: Optional[Set[str]] = None, - valid_transport_codes: Optional[Set[str]] = None, - valid_country_ame: Optional[Set[str]] = None, - state_descriptions_upper: Optional[Set[str]] = None, - state_country_set: Optional[Set[Tuple[str, str]]] = None, -) -> List[Dict[str, Any]]: - """ - Valida una fila de CSV de vehículos. - 1. CLAVE vacía → error. - 2. Si actualizar y CLAVE en existing_vehicle_keys → valida_parcial_transporte (sin exigir CODIGO DE ENTIDAD). - 3. Si no actualizar o CLAVE no existe → valida_toda_transporte (CODIGO DE ENTIDAD obligatorio). - Desfase (COL_EXTRA) no se valida aquí; el caller puede llamar validate_row_vehicle_desfase para advertencias no bloqueantes. - """ - errors: List[Dict[str, Any]] = [] - err = validate_row_vehicle_required(row, line_num) - if err: - errors.append(err) - - existing = existing_vehicle_keys or set() - clave = (row.get("CLAVE") or "").strip() - use_partial = actualizar and bool(clave and clave in existing) - - if use_partial: - errors.extend( - valida_parcial_transporte( - row, line_num, - valid_transport_codes=valid_transport_codes, - valid_country_ame=valid_country_ame, - state_descriptions_upper=state_descriptions_upper, - state_country_set=state_country_set, - ) - ) - else: - errors.extend( - valida_toda_transporte( - row, line_num, - valid_transport_codes=valid_transport_codes, - valid_country_ame=valid_country_ame, - state_descriptions_upper=state_descriptions_upper, - state_country_set=state_country_set, - ) - ) - return errors - - -def validate_row_vehicle_desfase(row: Dict[str, Any], line_num: int) -> Optional[Dict[str, Any]]: - """ - Advertencia de desfase si COL_EXTRA (Col R) tiene valor. No bloqueante; el caller puede acumular en warnings. - """ - return check_desfase_vehicles(row, line_num) diff --git a/backend/api/v1/modules/a76/manifests/__init__.py b/backend/api/v1/modules/a76/manifests/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/backend/api/v1/modules/a76/manifests/concept_manifestation/__init__.py b/backend/api/v1/modules/a76/manifests/concept_manifestation/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/backend/api/v1/modules/a76/manifests/concept_manifestation/dtos.py b/backend/api/v1/modules/a76/manifests/concept_manifestation/dtos.py deleted file mode 100644 index 5c88df41..00000000 --- a/backend/api/v1/modules/a76/manifests/concept_manifestation/dtos.py +++ /dev/null @@ -1,48 +0,0 @@ -from decimal import Decimal -from typing import List, Optional - -from pydantic import BaseModel, Field - - -class ConceptManifestationBaseDTO(BaseModel): - value_manifestation_id: int = Field(..., description="Value Manifestation ID") - line_number: int = Field(..., description="Line number") - attachment_type: Optional[str] = Field(None, max_length=10, description="Attachment type") - number: Optional[str] = Field(None, max_length=10, description="Number") - merchandise_provider: Optional[str] = Field(None, max_length=100, description="Merchandise provider") - invoice_document: Optional[str] = Field(None, max_length=200, description="Invoice document") - amount: Optional[Decimal] = Field(None, description="Amount") - currency: Optional[str] = Field(None, max_length=3, description="Currency") - concept_load: Optional[str] = Field(None, max_length=200, description="Concept load") - - -class ConceptManifestationCreateDTO(ConceptManifestationBaseDTO): - pass - - -class ConceptManifestationUpdateDTO(BaseModel): - attachment_type: Optional[str] = Field(None, max_length=10, description="Attachment type") - number: Optional[str] = Field(None, max_length=10, description="Number") - merchandise_provider: Optional[str] = Field(None, max_length=100, description="Merchandise provider") - invoice_document: Optional[str] = Field(None, max_length=200, description="Invoice document") - amount: Optional[Decimal] = Field(None, description="Amount") - currency: Optional[str] = Field(None, max_length=3, description="Currency") - concept_load: Optional[str] = Field(None, max_length=200, description="Concept load") - - -class ConceptManifestationResponseDTO(ConceptManifestationBaseDTO): - tenant_id: int - company_id: int - - class Config: - from_attributes = True - - -class ConceptManifestationListDTO(BaseModel): - items: List[ConceptManifestationResponseDTO] - total: int - page: int - page_size: int - - class Config: - from_attributes = True diff --git a/backend/api/v1/modules/a76/manifests/concept_manifestation/models.py b/backend/api/v1/modules/a76/manifests/concept_manifestation/models.py deleted file mode 100644 index 0242102f..00000000 --- a/backend/api/v1/modules/a76/manifests/concept_manifestation/models.py +++ /dev/null @@ -1,45 +0,0 @@ -from decimal import Decimal -from typing import TYPE_CHECKING, Optional - -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base -from sqlalchemy import ( - ForeignKeyConstraint, - Index, - Integer, - Numeric, - PrimaryKeyConstraint, - String, -) -from sqlalchemy.orm import Mapped, mapped_column, relationship - -if TYPE_CHECKING: - from api.v1.modules.a76.manifests.value_manifestation.models import ValueManifestation - -class ConceptManifestation(Base, TenantScopedMixin, TimestampMixin): - __tablename__ = "concept_manifestations" - __table_args__ = ( - PrimaryKeyConstraint("value_manifestation_id", "line_number", name="concept_manifestations_pkey"), - ForeignKeyConstraint( - ["value_manifestation_id"], - ["a76.value_manifestations.id"], - name="fk_concept_manifestation_value_manifestation" - ), - Index("idx_concept_manifestations_value_manifestation_id", "value_manifestation_id"), - {"schema": "a76"}, - ) - - value_manifestation_id: Mapped[int] = mapped_column(Integer) - line_number: Mapped[int] = mapped_column(Integer) - attachment_type: Mapped[Optional[str]] = mapped_column(String(10), nullable=True) - number: Mapped[Optional[str]] = mapped_column(String(10), nullable=True) - merchandise_provider: Mapped[Optional[str]] = mapped_column(String(100), nullable=True) - invoice_document: Mapped[Optional[str]] = mapped_column(String(200), nullable=True) - amount: Mapped[Optional[Decimal]] = mapped_column(Numeric(19, 9), nullable=True) - currency: Mapped[Optional[str]] = mapped_column(String(3), nullable=True) - concept_load: Mapped[Optional[str]] = mapped_column(String(200), nullable=True) - - value_manifestation: Mapped["ValueManifestation"] = relationship( - "ValueManifestation", - backref="concepts" - ) diff --git a/backend/api/v1/modules/a76/manifests/concept_manifestation/routes.py b/backend/api/v1/modules/a76/manifests/concept_manifestation/routes.py deleted file mode 100644 index e45543e6..00000000 --- a/backend/api/v1/modules/a76/manifests/concept_manifestation/routes.py +++ /dev/null @@ -1,112 +0,0 @@ -from typing import Optional - -from fastapi import APIRouter, Depends, Query, HTTPException -from sqlalchemy.orm import Session - -from core.database import get_core_db -from core.security import get_current_user, validate_access_to_resource -from .dtos import ( - ConceptManifestationCreateDTO, - ConceptManifestationUpdateDTO, - ConceptManifestationResponseDTO, - ConceptManifestationListDTO, -) -from .service import ConceptManifestationService - -router = APIRouter(prefix="/concept-manifestations") - - -@router.get("/", response_model=ConceptManifestationListDTO) -async def list_concept_manifestations( - company_id: int = Query(..., description="Company ID"), - value_manifestation_id: Optional[int] = Query( - None, description="Filter by Value Manifestation ID" - ), - skip: int = Query(0, ge=0), - limit: int = Query(100, ge=1, le=1000), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - tenant_id = validate_access_to_resource(db, company_id, current_user) - filters = {} - if value_manifestation_id: - filters["value_manifestation_id"] = value_manifestation_id - - items, total = ConceptManifestationService.get_all( - db, tenant_id, company_id, skip=skip, limit=limit, filters=filters - ) - return { - "items": [ConceptManifestationResponseDTO.model_validate(item) for item in items], - "total": total, - "page": (skip // limit) + 1, - "page_size": limit, - } - - -@router.post("/", response_model=ConceptManifestationResponseDTO) -async def create_concept_manifestation( - data: ConceptManifestationCreateDTO, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - tenant_id = validate_access_to_resource(db, company_id, current_user) - return ConceptManifestationService.create(db, data, tenant_id, company_id) - - -@router.get( - "/{value_manifestation_id}/{line_number}", - response_model=ConceptManifestationResponseDTO, -) -async def get_concept_manifestation( - value_manifestation_id: int, - line_number: int, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - tenant_id = validate_access_to_resource(db, company_id, current_user) - item = ConceptManifestationService.get_by_id( - db, value_manifestation_id, line_number, tenant_id, company_id - ) - if not item: - raise HTTPException(status_code=404, detail="Concept Manifestation not found") - return item - - -@router.patch( - "/{value_manifestation_id}/{line_number}", - response_model=ConceptManifestationResponseDTO, -) -async def update_concept_manifestation( - value_manifestation_id: int, - line_number: int, - data: ConceptManifestationUpdateDTO, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - tenant_id = validate_access_to_resource(db, company_id, current_user) - item = ConceptManifestationService.update( - db, value_manifestation_id, line_number, tenant_id, company_id, data - ) - if not item: - raise HTTPException(status_code=404, detail="Concept Manifestation not found") - return item - - -@router.delete("/{value_manifestation_id}/{line_number}", response_model=bool) -async def delete_concept_manifestation( - value_manifestation_id: int, - line_number: int, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - tenant_id = validate_access_to_resource(db, company_id, current_user) - success = ConceptManifestationService.delete( - db, value_manifestation_id, line_number, tenant_id, company_id - ) - if not success: - raise HTTPException(status_code=404, detail="Concept Manifestation not found") - return success diff --git a/backend/api/v1/modules/a76/manifests/concept_manifestation/service.py b/backend/api/v1/modules/a76/manifests/concept_manifestation/service.py deleted file mode 100644 index b0ee853a..00000000 --- a/backend/api/v1/modules/a76/manifests/concept_manifestation/service.py +++ /dev/null @@ -1,143 +0,0 @@ -import logging -from typing import List, Optional, Dict, Any, Tuple - -from fastapi import HTTPException -from sqlalchemy import or_ -from sqlalchemy.orm import Session - -from .models import ConceptManifestation -from .dtos import ( - ConceptManifestationCreateDTO, - ConceptManifestationUpdateDTO, -) - -logger = logging.getLogger(__name__) - - -class ConceptManifestationService: - @staticmethod - def get_all( - db: Session, - tenant_id: int, - company_id: int, - skip: int = 0, - limit: int = 100, - filters: Optional[Dict[str, Any]] = None, - ) -> Tuple[List[ConceptManifestation], int]: - query = db.query(ConceptManifestation).filter( - ConceptManifestation.tenant_id == tenant_id, - ConceptManifestation.company_id == company_id, - ) - - if filters: - if filters.get("value_manifestation_id"): - query = query.filter( - ConceptManifestation.value_manifestation_id == filters["value_manifestation_id"] - ) - - total = query.count() - items = query.offset(skip).limit(limit).all() - - return items, total - - @staticmethod - def get_by_id( - db: Session, - value_manifestation_id: int, - line_number: int, - tenant_id: int, - company_id: int, - ) -> Optional[ConceptManifestation]: - return ( - db.query(ConceptManifestation) - .filter( - ConceptManifestation.value_manifestation_id == value_manifestation_id, - ConceptManifestation.line_number == line_number, - ConceptManifestation.tenant_id == tenant_id, - ConceptManifestation.company_id == company_id, - ) - .first() - ) - - @staticmethod - def create( - db: Session, - data: ConceptManifestationCreateDTO, - tenant_id: int, - company_id: int, - ) -> ConceptManifestation: - try: - db_item = ConceptManifestation( - **data.model_dump(exclude_unset=True), - tenant_id=tenant_id, - company_id=company_id, - ) - db.add(db_item) - db.commit() - db.refresh(db_item) - return db_item - except Exception as e: - db.rollback() - logger.error(f"Error creating concept manifestation: {str(e)}") - raise HTTPException( - status_code=500, detail="Error creating concept manifestation" - ) - - @staticmethod - def update( - db: Session, - value_manifestation_id: int, - line_number: int, - tenant_id: int, - company_id: int, - data: ConceptManifestationUpdateDTO, - ) -> Optional[ConceptManifestation]: - item = ConceptManifestationService.get_by_id( - db, value_manifestation_id, line_number, tenant_id, company_id - ) - if not item: - return None - - try: - update_data = data.model_dump(exclude_unset=True) - for field, value in update_data.items(): - setattr(item, field, value) - - db.commit() - db.refresh(item) - return item - except Exception as e: - db.rollback() - logger.error( - f"Error updating concept manifestation {value_manifestation_id}-{line_number}: {str(e)}" - ) - raise HTTPException( - status_code=500, detail="Error updating concept manifestation" - ) - - @staticmethod - def delete( - db: Session, - value_manifestation_id: int, - line_number: int, - tenant_id: int, - company_id: int, - ) -> bool: - item = ConceptManifestationService.get_by_id( - db, value_manifestation_id, line_number, tenant_id, company_id - ) - if not item: - return False - - try: - db.delete(item) - db.commit() - return True - except Exception as e: - db.rollback() - logger.error( - f"Error deleting concept manifestation {value_manifestation_id}-{line_number}: {str(e)}" - ) - raise HTTPException( - status_code=500, detail="Error deleting concept manifestation" - ) diff --git a/backend/api/v1/modules/a76/manifests/driver/__init__.py b/backend/api/v1/modules/a76/manifests/driver/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/backend/api/v1/modules/a76/manifests/driver/dtos.py b/backend/api/v1/modules/a76/manifests/driver/dtos.py deleted file mode 100644 index c93256b8..00000000 --- a/backend/api/v1/modules/a76/manifests/driver/dtos.py +++ /dev/null @@ -1,42 +0,0 @@ -from typing import List, Optional -from pydantic import BaseModel, Field - - -class ManifestDriverBaseDTO(BaseModel): - manifest_number: str = Field(..., max_length=15, description="Manifest number") - driver_name: str = Field(..., max_length=80, description="Driver name") - driver_type: Optional[str] = Field(None, max_length=1, description="Driver type") - address_1: Optional[str] = Field(None, max_length=100, description="Address line 1") - address_2: Optional[str] = Field(None, max_length=100, description="Address line 2") - city: Optional[str] = Field(None, max_length=30, description="City") - state: Optional[str] = Field(None, max_length=30, description="State") - postal_code: Optional[str] = Field(None, max_length=15, description="Postal code") - country: Optional[str] = Field(None, max_length=3, description="Country") - - -class ManifestDriverCreateDTO(ManifestDriverBaseDTO): - pass - - -class ManifestDriverUpdateDTO(ManifestDriverBaseDTO): - # Overriding to make PKs optional for update if needed, but usually PKs are in URL - manifest_number: Optional[str] = Field(None, max_length=15) - driver_name: Optional[str] = Field(None, max_length=80) - - -class ManifestDriverResponseDTO(ManifestDriverBaseDTO): - tenant_id: int - company_id: int - - class Config: - from_attributes = True - - -class ManifestDriverListDTO(BaseModel): - items: List[ManifestDriverResponseDTO] - total: int - page: int - page_size: int - - class Config: - from_attributes = True diff --git a/backend/api/v1/modules/a76/manifests/driver/models.py b/backend/api/v1/modules/a76/manifests/driver/models.py deleted file mode 100644 index 67fbed47..00000000 --- a/backend/api/v1/modules/a76/manifests/driver/models.py +++ /dev/null @@ -1,30 +0,0 @@ -from typing import Optional - -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base -from sqlalchemy import ( - Index, - Integer, - PrimaryKeyConstraint, - String, -) -from sqlalchemy.orm import Mapped, mapped_column - - -class ManifestDriver(Base, TenantScopedMixin, TimestampMixin): - __tablename__ = "manifest_drivers" - __table_args__ = ( - PrimaryKeyConstraint("manifest_number", "driver_name", name="manifest_drivers_pkey"), - Index("idx_manifest_drivers_manifest_number", "manifest_number"), - {"schema": "a76"}, - ) - - manifest_number: Mapped[str] = mapped_column(String(15)) - driver_name: Mapped[str] = mapped_column(String(80)) - driver_type: Mapped[Optional[str]] = mapped_column(String(1), nullable=True) - address_1: Mapped[Optional[str]] = mapped_column(String(100), nullable=True) - address_2: Mapped[Optional[str]] = mapped_column(String(100), nullable=True) - city: Mapped[Optional[str]] = mapped_column(String(30), nullable=True) - state: Mapped[Optional[str]] = mapped_column(String(30), nullable=True) - postal_code: Mapped[Optional[str]] = mapped_column(String(15), nullable=True) - country: Mapped[Optional[str]] = mapped_column(String(3), nullable=True) diff --git a/backend/api/v1/modules/a76/manifests/driver/routes.py b/backend/api/v1/modules/a76/manifests/driver/routes.py deleted file mode 100644 index f7e86a7f..00000000 --- a/backend/api/v1/modules/a76/manifests/driver/routes.py +++ /dev/null @@ -1,98 +0,0 @@ -from typing import List, Optional, Dict, Any - -from fastapi import APIRouter, Depends, Query, HTTPException -from sqlalchemy.orm import Session - -from core.database import get_core_db -from core.security import get_current_user, validate_access_to_resource -from .dtos import ( - ManifestDriverCreateDTO, - ManifestDriverUpdateDTO, - ManifestDriverResponseDTO, - ManifestDriverListDTO, -) -from .service import ManifestDriverService - -router = APIRouter(prefix="/manifest-drivers") - - -@router.get("/", response_model=ManifestDriverListDTO) -async def list_manifest_drivers( - company_id: int = Query(..., description="Company ID"), - manifest_number: Optional[str] = Query(None, description="Filter by Manifest Number"), - search: Optional[str] = Query(None, description="Search term"), - skip: int = Query(0, ge=0), - limit: int = Query(100, ge=1, le=1000), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - tenant_id = validate_access_to_resource(db, company_id, current_user) - filters = {"manifest_number": manifest_number, "search": search} - items, total = ManifestDriverService.get_all( - db, tenant_id, company_id, skip=skip, limit=limit, filters=filters - ) - return { - "items": [ManifestDriverResponseDTO.model_validate(item) for item in items], - "total": total, - "page": (skip // limit) + 1, - "page_size": limit, - } - - -@router.post("/", response_model=ManifestDriverResponseDTO) -async def create_manifest_driver( - data: ManifestDriverCreateDTO, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - tenant_id = validate_access_to_resource(db, company_id, current_user) - return ManifestDriverService.create(db, data, tenant_id, company_id) - - -@router.get("/{manifest_number}/{driver_name}", response_model=ManifestDriverResponseDTO) -async def get_manifest_driver( - manifest_number: str, - driver_name: str, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - tenant_id = validate_access_to_resource(db, company_id, current_user) - item = ManifestDriverService.get_by_pk(db, manifest_number, driver_name, tenant_id, company_id) - if not item: - raise HTTPException(status_code=404, detail="Manifest Driver not found") - return item - - -@router.patch("/{manifest_number}/{driver_name}", response_model=ManifestDriverResponseDTO) -async def update_manifest_driver( - manifest_number: str, - driver_name: str, - data: ManifestDriverUpdateDTO, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - tenant_id = validate_access_to_resource(db, company_id, current_user) - item = ManifestDriverService.update( - db, manifest_number, driver_name, tenant_id, company_id, data - ) - if not item: - raise HTTPException(status_code=404, detail="Manifest Driver not found") - return item - - -@router.delete("/{manifest_number}/{driver_name}", response_model=bool) -async def delete_manifest_driver( - manifest_number: str, - driver_name: str, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - tenant_id = validate_access_to_resource(db, company_id, current_user) - success = ManifestDriverService.delete(db, manifest_number, driver_name, tenant_id, company_id) - if not success: - raise HTTPException(status_code=404, detail="Manifest Driver not found") - return success diff --git a/backend/api/v1/modules/a76/manifests/driver/service.py b/backend/api/v1/modules/a76/manifests/driver/service.py deleted file mode 100644 index 8f3529a4..00000000 --- a/backend/api/v1/modules/a76/manifests/driver/service.py +++ /dev/null @@ -1,124 +0,0 @@ -import logging -from typing import List, Optional, Dict, Any, Tuple - -from fastapi import HTTPException -from sqlalchemy import or_ -from sqlalchemy.orm import Session - -from .models import ManifestDriver -from .dtos import ManifestDriverCreateDTO, ManifestDriverUpdateDTO - -logger = logging.getLogger(__name__) - - -class ManifestDriverService: - @staticmethod - def get_all( - db: Session, - tenant_id: int, - company_id: int, - skip: int = 0, - limit: int = 100, - filters: Optional[Dict[str, Any]] = None, - ) -> Tuple[List[ManifestDriver], int]: - query = db.query(ManifestDriver).filter( - ManifestDriver.tenant_id == tenant_id, - ManifestDriver.company_id == company_id, - ) - - if filters: - if filters.get("search"): - search_pattern = f"%{filters['search']}%" - query = query.filter( - or_( - ManifestDriver.manifest_number.ilike(search_pattern), - ManifestDriver.driver_name.ilike(search_pattern), - ) - ) - if filters.get("manifest_number"): - query = query.filter(ManifestDriver.manifest_number == filters["manifest_number"]) - - total = query.count() - items = query.offset(skip).limit(limit).all() - - return items, total - - @staticmethod - def get_by_pk( - db: Session, manifest_number: str, driver_name: str, tenant_id: int, company_id: int - ) -> Optional[ManifestDriver]: - return ( - db.query(ManifestDriver) - .filter( - ManifestDriver.manifest_number == manifest_number, - ManifestDriver.driver_name == driver_name, - ManifestDriver.tenant_id == tenant_id, - ManifestDriver.company_id == company_id, - ) - .first() - ) - - @staticmethod - def create( - db: Session, - data: ManifestDriverCreateDTO, - tenant_id: int, - company_id: int, - ) -> ManifestDriver: - try: - db_item = ManifestDriver( - **data.model_dump(exclude_unset=True), - tenant_id=tenant_id, - company_id=company_id, - ) - db.add(db_item) - db.commit() - db.refresh(db_item) - return db_item - except Exception as e: - db.rollback() - logger.error(f"Error creating manifest driver: {str(e)}") - raise HTTPException(status_code=500, detail="Error creating manifest driver") - - @staticmethod - def update( - db: Session, - manifest_number: str, - driver_name: str, - tenant_id: int, - company_id: int, - data: ManifestDriverUpdateDTO, - ) -> Optional[ManifestDriver]: - item = ManifestDriverService.get_by_pk(db, manifest_number, driver_name, tenant_id, company_id) - if not item: - return None - - try: - update_data = data.model_dump(exclude_unset=True) - for field, value in update_data.items(): - setattr(item, field, value) - - db.commit() - db.refresh(item) - return item - except Exception as e: - db.rollback() - logger.error(f"Error updating manifest driver: {str(e)}") - raise HTTPException(status_code=500, detail="Error updating manifest driver") - - @staticmethod - def delete( - db: Session, manifest_number: str, driver_name: str, tenant_id: int, company_id: int - ) -> bool: - item = ManifestDriverService.get_by_pk(db, manifest_number, driver_name, tenant_id, company_id) - if not item: - return False - - try: - db.delete(item) - db.commit() - return True - except Exception as e: - db.rollback() - logger.error(f"Error deleting manifest driver: {str(e)}") - raise HTTPException(status_code=500, detail="Error deleting manifest driver") diff --git a/backend/api/v1/modules/a76/manifests/manifest/debug_db.py b/backend/api/v1/modules/a76/manifests/manifest/debug_db.py deleted file mode 100644 index e191fd3c..00000000 --- a/backend/api/v1/modules/a76/manifests/manifest/debug_db.py +++ /dev/null @@ -1,41 +0,0 @@ - -import sys -import os -from sqlalchemy import text, inspect - -# Assume we run this from backend/ with PYTHONPATH=. -from core.database import CoreSessionLocal, core_engine as engine - -def check_table(): - inspector = inspect(engine) - schemas = inspector.get_schema_names() - print(f"Schemas found: {schemas}") - - if 'a76' not in schemas: - print("Schema 'a76' does not exist!") - # return # Proceed anyway to check if table exists in public or other schemas? - - # Check for table in a76 schema - try: - table_names = inspector.get_table_names(schema='a76') - print(f"Tables in 'a76': {table_names}") - - if 'manifests' in table_names: - print("Table 'a76.manifests' exists.") - columns = [c['name'] for c in inspector.get_columns('manifests', schema='a76')] - print(f"Columns: {columns}") - - # Try a simple count - try: - with CoreSessionLocal() as db: - result = db.execute(text("SELECT count(*) FROM a76.manifests")) - print(f"Count result: {result.scalar()}") - except Exception as e: - print(f"Error querying table: {e}") - else: - print("Table 'a76.manifests' DOES NOT exist in schema 'a76'.") - except Exception as e: - print(f"Error inspecting schema 'a76': {e}") - -if __name__ == "__main__": - check_table() diff --git a/backend/api/v1/modules/a76/manifests/manifest/dtos.py b/backend/api/v1/modules/a76/manifests/manifest/dtos.py deleted file mode 100644 index ae4675a1..00000000 --- a/backend/api/v1/modules/a76/manifests/manifest/dtos.py +++ /dev/null @@ -1,66 +0,0 @@ -from decimal import Decimal -from typing import List, Optional - -from pydantic import BaseModel, Field - - -class ManifestBaseDTO(BaseModel): - manifest_number: Optional[str] = Field(None, max_length=15, description="Manifest number") - importer_details: Optional[str] = Field(None, max_length=60, description="Importer details") - person_in_charge: Optional[str] = Field(None, max_length=60, description="Person in charge") - consigned_to: Optional[str] = Field(None, max_length=8, description="Consigned to") - sent_by: Optional[str] = Field(None, max_length=8, description="Sent by") - foreign_exit_port: Optional[str] = Field(None, max_length=6, description="Foreign exit port") - foreign_exit_port_loc: Optional[str] = Field(None, max_length=4, description="Foreign exit port location") - destination_port: Optional[str] = Field(None, max_length=6, description="Destination port") - destination_port_loc: Optional[str] = Field(None, max_length=4, description="Destination port location") - entry_port: Optional[str] = Field(None, max_length=6, description="Entry port") - entry_port_loc: Optional[str] = Field(None, max_length=4, description="Entry port location") - entry_date: Optional[int] = Field(None, description="Entry date") - net_weight: Optional[Decimal] = Field(None, description="Net weight") - gross_weight: Optional[Decimal] = Field(None, description="Gross weight") - total_value: Optional[Decimal] = Field(None, description="Total value") - description: Optional[str] = Field(None, max_length=2500, description="Description") - broker_code: Optional[str] = Field(None, max_length=5, description="Broker code") - carrier_code: Optional[str] = Field(None, max_length=5, description="Carrier code") - payment_invoice_number: Optional[str] = Field(None, max_length=5, description="Payment invoice number") - seal_number: Optional[str] = Field(None, max_length=30, description="Seal number") - entry_hour: Optional[int] = Field(None, description="Entry hour") - hazardous_material: Optional[str] = Field(None, max_length=2, description="Hazardous material") - transport_mode: Optional[str] = Field(None, max_length=2, description="Transport mode") - transport_code: Optional[str] = Field(None, max_length=14, description="Transport code") - trailer_number: Optional[str] = Field(None, max_length=20, description="Trailer number") - status: Optional[str] = Field(None, max_length=14, description="Status") - status_description: Optional[str] = Field(None, max_length=1000, description="Status description") - manifest_type: Optional[str] = Field(None, max_length=3, description="Manifest type") - - -class ManifestCreateDTO(ManifestBaseDTO): - pass - - -class ManifestUpdateDTO(ManifestBaseDTO): - pass - - -class ManifestResponseDTO(ManifestBaseDTO): - id: int - tenant_id: int - company_id: int - - class Config: - from_attributes = True - - -class ManifestListDTO(BaseModel): - items: List[ManifestResponseDTO] - total: int - page: int - page_size: int - - class Config: - from_attributes = True - - -class InvoiceManifestUpdateDTO(BaseModel): - manifest_number: Optional[str] = Field(None, max_length=15, description="Manifest number") diff --git a/backend/api/v1/modules/a76/manifests/manifest/models.py b/backend/api/v1/modules/a76/manifests/manifest/models.py deleted file mode 100644 index 2f97d550..00000000 --- a/backend/api/v1/modules/a76/manifests/manifest/models.py +++ /dev/null @@ -1,52 +0,0 @@ -from decimal import Decimal -from typing import Optional - -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base -from sqlalchemy import ( - Index, - Integer, - Numeric, - PrimaryKeyConstraint, - String, - Text, -) -from sqlalchemy.orm import Mapped, mapped_column - -class Manifest(Base, TenantScopedMixin, TimestampMixin): - __tablename__ = "manifests" - __table_args__ = ( - Index("idx_manifests_manifest_number", "manifest_number"), - {"schema": "a76"}, - ) - - id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) - - manifest_number: Mapped[str] = mapped_column(String(15), nullable=True) - importer_details: Mapped[Optional[str]] = mapped_column(String(60), nullable=True) - person_in_charge: Mapped[Optional[str]] = mapped_column(String(60), nullable=True) - consigned_to: Mapped[Optional[str]] = mapped_column(String(8), nullable=True) - sent_by: Mapped[Optional[str]] = mapped_column(String(8), nullable=True) - foreign_exit_port: Mapped[Optional[str]] = mapped_column(String(6), nullable=True) - foreign_exit_port_loc: Mapped[Optional[str]] = mapped_column(String(4), nullable=True) - destination_port: Mapped[Optional[str]] = mapped_column(String(6), nullable=True) - destination_port_loc: Mapped[Optional[str]] = mapped_column(String(4), nullable=True) - entry_port: Mapped[Optional[str]] = mapped_column(String(6), nullable=True) - entry_port_loc: Mapped[Optional[str]] = mapped_column(String(4), nullable=True) - entry_date: Mapped[Optional[int]] = mapped_column(Integer, nullable=True) - net_weight: Mapped[Optional[Decimal]] = mapped_column(Numeric(19, 8), nullable=True) - gross_weight: Mapped[Optional[Decimal]] = mapped_column(Numeric(19, 8), nullable=True) - total_value: Mapped[Optional[Decimal]] = mapped_column(Numeric(19, 8), nullable=True) - description: Mapped[Optional[str]] = mapped_column(String(2500), nullable=True) - broker_code: Mapped[Optional[str]] = mapped_column(String(5), nullable=True) - carrier_code: Mapped[Optional[str]] = mapped_column(String(5), nullable=True) - payment_invoice_number: Mapped[Optional[str]] = mapped_column(String(5), nullable=True) - seal_number: Mapped[Optional[str]] = mapped_column(String(30), nullable=True) - entry_hour: Mapped[Optional[int]] = mapped_column(Integer, nullable=True) - hazardous_material: Mapped[Optional[str]] = mapped_column(String(2), nullable=True) - transport_mode: Mapped[Optional[str]] = mapped_column(String(2), nullable=True) - transport_code: Mapped[Optional[str]] = mapped_column(String(14), nullable=True) - trailer_number: Mapped[Optional[str]] = mapped_column(String(20), nullable=True) - status: Mapped[Optional[str]] = mapped_column(String(14), nullable=True) - status_description: Mapped[Optional[str]] = mapped_column(String(1000), nullable=True) - manifest_type: Mapped[Optional[str]] = mapped_column(String(3), nullable=True) diff --git a/backend/api/v1/modules/a76/manifests/manifest/routes.py b/backend/api/v1/modules/a76/manifests/manifest/routes.py deleted file mode 100644 index 969f4fea..00000000 --- a/backend/api/v1/modules/a76/manifests/manifest/routes.py +++ /dev/null @@ -1,127 +0,0 @@ -from typing import List, Optional, Dict, Any - -from fastapi import APIRouter, Depends, Query, HTTPException -from sqlalchemy.orm import Session - -from core.database import get_core_db -from core.security import get_current_user, validate_access_to_resource -from .dtos import ( - ManifestCreateDTO, - ManifestUpdateDTO, - ManifestResponseDTO, - ManifestListDTO, - InvoiceManifestUpdateDTO, -) -from .service import ManifestService - -router = APIRouter(prefix="/manifests") - - -@router.get("/", response_model=ManifestListDTO) -async def list_manifests( - company_id: int = Query(..., description="Company ID"), - search: Optional[str] = Query(None, description="Search term"), - manifest_number: Optional[str] = Query(None, description="Manifest number"), - start_date: Optional[int] = Query(None, description="Start date (YYYYMMDD)"), - end_date: Optional[int] = Query(None, description="End date (YYYYMMDD)"), - skip: int = Query(0, ge=0, description="Skip"), - limit: int = Query(50, ge=1, le=100, description="Limit"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """ - List all manifests for a company with pagination and filters - """ - tenant_id = validate_access_to_resource(db, company_id, current_user, required_permissions=["export_manifest.view"]) - filters = { - "search": search, - "manifest_number": manifest_number, - "start_date": start_date, - "end_date": end_date, - } - items, total = ManifestService.get_all( - db, tenant_id, company_id, skip=skip, limit=limit, filters=filters - ) - return { - "items": [ManifestResponseDTO.model_validate(item) for item in items], - "total": total, - "page": (skip // limit) + 1, - "page_size": limit, - } - - -@router.post("/", response_model=ManifestResponseDTO) -async def create_manifest( - manifest_data: ManifestCreateDTO, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - tenant_id = validate_access_to_resource(db, company_id, current_user, required_permissions=["export_manifest.view"]) - return ManifestService.create(db, manifest_data, tenant_id, company_id) - - -@router.get("/{manifest_id}", response_model=ManifestResponseDTO) -async def get_manifest( - manifest_id: int, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - tenant_id = validate_access_to_resource(db, company_id, current_user, required_permissions=["export_manifest.view"]) - manifest = ManifestService.get_by_id(db, manifest_id, tenant_id, company_id) - if not manifest: - raise HTTPException(status_code=404, detail="Manifest not found") - return manifest - - -@router.patch("/{manifest_id}", response_model=ManifestResponseDTO) -async def update_manifest( - manifest_id: int, - manifest_data: ManifestUpdateDTO, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - tenant_id = validate_access_to_resource(db, company_id, current_user, required_permissions=["export_manifest.view"]) - manifest = ManifestService.update( - db, manifest_id, tenant_id, company_id, manifest_data - ) - if not manifest: - raise HTTPException(status_code=404, detail="Manifest not found") - return manifest - - -@router.delete("/{manifest_id}", response_model=bool) -async def delete_manifest( - manifest_id: int, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - tenant_id = validate_access_to_resource(db, company_id, current_user, required_permissions=["export_manifest.view"]) - success = ManifestService.delete(db, manifest_id, tenant_id, company_id) - if not success: - raise HTTPException(status_code=404, detail="Manifest not found") - return success - - -@router.patch("/invoices/{invoice_id}/compliance", response_model=bool) -async def update_invoice_manifest( - invoice_id: int, - data: InvoiceManifestUpdateDTO, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - """ - Dedicated endpoint to update manifest_number for an invoice. - Safely bypasses all core invoice update validations. - """ - tenant_id = validate_access_to_resource(db, company_id, current_user) - success = ManifestService.update_invoice_manifest( - db, invoice_id, tenant_id, company_id, data.manifest_number - ) - if not success: - raise HTTPException(status_code=404, detail="Invoice not found") - return success diff --git a/backend/api/v1/modules/a76/manifests/manifest/service.py b/backend/api/v1/modules/a76/manifests/manifest/service.py deleted file mode 100644 index b0045ce9..00000000 --- a/backend/api/v1/modules/a76/manifests/manifest/service.py +++ /dev/null @@ -1,192 +0,0 @@ -import logging -from typing import List, Optional, Dict, Any, Tuple - -from fastapi import HTTPException -from sqlalchemy import or_ -from sqlalchemy.orm import Session - -from .models import Manifest -from .dtos import ManifestCreateDTO, ManifestUpdateDTO, ManifestResponseDTO - -logger = logging.getLogger(__name__) - - -class ManifestService: - @staticmethod - def get_all( - db: Session, - tenant_id: int, - company_id: int, - skip: int = 0, - limit: int = 100, - filters: Optional[Dict[str, Any]] = None, - ) -> Tuple[List[Manifest], int]: - query = db.query(Manifest).filter( - Manifest.tenant_id == tenant_id, - Manifest.company_id == company_id, - ) - - if filters: - if filters.get("search"): - search_pattern = f"%{filters['search']}%" - query = query.filter( - or_( - Manifest.manifest_number.ilike(search_pattern), - Manifest.description.ilike(search_pattern), - ) - ) - - if filters.get("manifest_number"): - query = query.filter( - Manifest.manifest_number.ilike(f"%{filters['manifest_number']}%") - ) - - if filters.get("start_date"): - query = query.filter(Manifest.entry_date >= filters["start_date"]) - - if filters.get("end_date"): - query = query.filter(Manifest.entry_date <= filters["end_date"]) - - total = query.count() - items = query.offset(skip).limit(limit).all() - - return items, total - - @staticmethod - def get_by_id( - db: Session, manifest_id: int, tenant_id: int, company_id: int - ) -> Optional[Manifest]: - return ( - db.query(Manifest) - .filter( - Manifest.id == manifest_id, - Manifest.tenant_id == tenant_id, - Manifest.company_id == company_id, - ) - .first() - ) - - @staticmethod - def create( - db: Session, - manifest_data: ManifestCreateDTO, - tenant_id: int, - company_id: int, - ) -> Manifest: - try: - db_manifest = Manifest( - **manifest_data.model_dump(exclude_unset=True), - tenant_id=tenant_id, - company_id=company_id, - ) - db.add(db_manifest) - db.commit() - db.refresh(db_manifest) - return db_manifest - except Exception as e: - db.rollback() - logger.error(f"Error creating manifest: {str(e)}") - raise HTTPException(status_code=500, detail="Error creating manifest") - - @staticmethod - def update( - db: Session, - manifest_id: int, - tenant_id: int, - company_id: int, - manifest_data: ManifestUpdateDTO, - ) -> Optional[Manifest]: - manifest = ManifestService.get_by_id(db, manifest_id, tenant_id, company_id) - if not manifest: - return None - - try: - update_data = manifest_data.model_dump(exclude_unset=True) - for field, value in update_data.items(): - setattr(manifest, field, value) - - db.commit() - db.refresh(manifest) - return manifest - except Exception as e: - db.rollback() - logger.error(f"Error updating manifest {manifest_id}: {str(e)}") - raise HTTPException(status_code=500, detail="Error updating manifest") - - @staticmethod - def delete( - db: Session, manifest_id: int, tenant_id: int, company_id: int - ) -> bool: - manifest = ManifestService.get_by_id(db, manifest_id, tenant_id, company_id) - if not manifest: - return False - - try: - db.delete(manifest) - db.commit() - return True - except Exception as e: - db.rollback() - logger.error(f"Error deleting manifest {manifest_id}: {str(e)}") - raise HTTPException(status_code=500, detail="Error deleting manifest") - - @staticmethod - def update_invoice_manifest( - db: Session, - invoice_id: int, - tenant_id: int, - company_id: int, - manifest_number: Optional[str], - ) -> bool: - """ - Update ONLY the manifest_number of an invoice. - Creates compliance_mx record if it doesn't exist. - """ - # Import models here to avoid circular dependencies if any - from api.v1.modules.a76.invoices.models import ( - InvoiceHeader, - InvoiceComplianceMx, - ) - - invoice = ( - db.query(InvoiceHeader) - .filter( - InvoiceHeader.id == invoice_id, - InvoiceHeader.tenant_id == tenant_id, - InvoiceHeader.company_id == company_id, - ) - .first() - ) - - if not invoice: - return False - - try: - if invoice.compliance_mx: - invoice.compliance_mx.manifest_number = manifest_number - else: - # Create new compliance record with defaults for required fields - new_compliance = InvoiceComplianceMx( - invoice_id=invoice.id, - tenant_id=tenant_id, - company_id=company_id, - manifest_number=manifest_number, - # Required fields defaults - is_pedimento_pending=False, - is_regime_change=False, - is_owner_of_goods=False, - generate_balances=False, - is_mixed=False, - ) - db.add(new_compliance) - - db.commit() - return True - except Exception as e: - db.rollback() - logger.error( - f"Error linking invoice {invoice_id} to manifest {manifest_number}: {str(e)}" - ) - raise HTTPException( - status_code=500, detail="Error linking invoice to manifest" - ) diff --git a/backend/api/v1/modules/a76/manifests/manifiesto_anexo/__init__.py b/backend/api/v1/modules/a76/manifests/manifiesto_anexo/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/backend/api/v1/modules/a76/manifests/manifiesto_anexo/dtos.py b/backend/api/v1/modules/a76/manifests/manifiesto_anexo/dtos.py deleted file mode 100644 index 51eb2a37..00000000 --- a/backend/api/v1/modules/a76/manifests/manifiesto_anexo/dtos.py +++ /dev/null @@ -1,37 +0,0 @@ -from typing import List, Optional -from pydantic import BaseModel, Field - - -class ManifestAnexoBaseDTO(BaseModel): - consecutive: int = Field(..., description="Consecutive number") - line_number: int = Field(..., description="Line number") - attachment_type: Optional[str] = Field(None, max_length=10, description="Attachment type") - number: Optional[str] = Field(None, max_length=10, description="Attachment number") - attached_doc: Optional[str] = Field(None, max_length=200, description="Attached document path/name") - - -class ManifestAnexoCreateDTO(ManifestAnexoBaseDTO): - pass - - -class ManifestAnexoUpdateDTO(ManifestAnexoBaseDTO): - consecutive: Optional[int] = Field(None) - line_number: Optional[int] = Field(None) - - -class ManifestAnexoResponseDTO(ManifestAnexoBaseDTO): - tenant_id: int - company_id: int - - class Config: - from_attributes = True - - -class ManifestAnexoListDTO(BaseModel): - items: List[ManifestAnexoResponseDTO] - total: int - page: int - page_size: int - - class Config: - from_attributes = True diff --git a/backend/api/v1/modules/a76/manifests/manifiesto_anexo/models.py b/backend/api/v1/modules/a76/manifests/manifiesto_anexo/models.py deleted file mode 100644 index 1c8f509d..00000000 --- a/backend/api/v1/modules/a76/manifests/manifiesto_anexo/models.py +++ /dev/null @@ -1,26 +0,0 @@ -from typing import Optional - -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base -from sqlalchemy import ( - Index, - Integer, - PrimaryKeyConstraint, - String, -) -from sqlalchemy.orm import Mapped, mapped_column - - -class ManifestAnexo(Base, TenantScopedMixin, TimestampMixin): - __tablename__ = "manifest_anexos" - __table_args__ = ( - PrimaryKeyConstraint("consecutive", "line_number", name="manifest_anexos_pkey"), - Index("idx_manifest_anexos_consecutive", "consecutive"), - {"schema": "a76"}, - ) - - consecutive: Mapped[int] = mapped_column(Integer) - line_number: Mapped[int] = mapped_column(Integer) - attachment_type: Mapped[Optional[str]] = mapped_column(String(10), nullable=True) - number: Mapped[Optional[str]] = mapped_column(String(10), nullable=True) - attached_doc: Mapped[Optional[str]] = mapped_column(String(200), nullable=True) diff --git a/backend/api/v1/modules/a76/manifests/manifiesto_anexo/routes.py b/backend/api/v1/modules/a76/manifests/manifiesto_anexo/routes.py deleted file mode 100644 index 6c43bff4..00000000 --- a/backend/api/v1/modules/a76/manifests/manifiesto_anexo/routes.py +++ /dev/null @@ -1,98 +0,0 @@ -from typing import List, Optional, Dict, Any - -from fastapi import APIRouter, Depends, Query, HTTPException -from sqlalchemy.orm import Session - -from core.database import get_core_db -from core.security import get_current_user, validate_access_to_resource -from .dtos import ( - ManifestAnexoCreateDTO, - ManifestAnexoUpdateDTO, - ManifestAnexoResponseDTO, - ManifestAnexoListDTO, -) -from .service import ManifestAnexoService - -router = APIRouter(prefix="/manifest-anexos") - - -@router.get("/", response_model=ManifestAnexoListDTO) -async def list_manifest_anexos( - company_id: int = Query(..., description="Company ID"), - consecutive: Optional[int] = Query(None, description="Filter by Consecutive"), - search: Optional[str] = Query(None, description="Search term"), - skip: int = Query(0, ge=0), - limit: int = Query(100, ge=1, le=1000), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - tenant_id = validate_access_to_resource(db, company_id, current_user) - filters = {"consecutive": consecutive, "search": search} - items, total = ManifestAnexoService.get_all( - db, tenant_id, company_id, skip=skip, limit=limit, filters=filters - ) - return { - "items": [ManifestAnexoResponseDTO.model_validate(item) for item in items], - "total": total, - "page": (skip // limit) + 1, - "page_size": limit, - } - - -@router.post("/", response_model=ManifestAnexoResponseDTO) -async def create_manifest_anexo( - data: ManifestAnexoCreateDTO, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - tenant_id = validate_access_to_resource(db, company_id, current_user) - return ManifestAnexoService.create(db, data, tenant_id, company_id) - - -@router.get("/{consecutive}/{line_number}", response_model=ManifestAnexoResponseDTO) -async def get_manifest_anexo( - consecutive: int, - line_number: int, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - tenant_id = validate_access_to_resource(db, company_id, current_user) - item = ManifestAnexoService.get_by_pk(db, consecutive, line_number, tenant_id, company_id) - if not item: - raise HTTPException(status_code=404, detail="Manifest Anexo not found") - return item - - -@router.patch("/{consecutive}/{line_number}", response_model=ManifestAnexoResponseDTO) -async def update_manifest_anexo( - consecutive: int, - line_number: int, - data: ManifestAnexoUpdateDTO, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - tenant_id = validate_access_to_resource(db, company_id, current_user) - item = ManifestAnexoService.update( - db, consecutive, line_number, tenant_id, company_id, data - ) - if not item: - raise HTTPException(status_code=404, detail="Manifest Anexo not found") - return item - - -@router.delete("/{consecutive}/{line_number}", response_model=bool) -async def delete_manifest_anexo( - consecutive: int, - line_number: int, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - tenant_id = validate_access_to_resource(db, company_id, current_user) - success = ManifestAnexoService.delete(db, consecutive, line_number, tenant_id, company_id) - if not success: - raise HTTPException(status_code=404, detail="Manifest Anexo not found") - return success diff --git a/backend/api/v1/modules/a76/manifests/manifiesto_anexo/service.py b/backend/api/v1/modules/a76/manifests/manifiesto_anexo/service.py deleted file mode 100644 index 11cd7bf1..00000000 --- a/backend/api/v1/modules/a76/manifests/manifiesto_anexo/service.py +++ /dev/null @@ -1,124 +0,0 @@ -import logging -from typing import List, Optional, Dict, Any, Tuple - -from fastapi import HTTPException -from sqlalchemy import or_ -from sqlalchemy.orm import Session - -from .models import ManifestAnexo -from .dtos import ManifestAnexoCreateDTO, ManifestAnexoUpdateDTO - -logger = logging.getLogger(__name__) - - -class ManifestAnexoService: - @staticmethod - def get_all( - db: Session, - tenant_id: int, - company_id: int, - skip: int = 0, - limit: int = 100, - filters: Optional[Dict[str, Any]] = None, - ) -> Tuple[List[ManifestAnexo], int]: - query = db.query(ManifestAnexo).filter( - ManifestAnexo.tenant_id == tenant_id, - ManifestAnexo.company_id == company_id, - ) - - if filters: - if filters.get("consecutive"): - query = query.filter(ManifestAnexo.consecutive == filters["consecutive"]) - if filters.get("search"): - search_pattern = f"%{filters['search']}%" - query = query.filter( - or_( - ManifestAnexo.number.ilike(search_pattern), - ManifestAnexo.attached_doc.ilike(search_pattern), - ) - ) - - total = query.count() - items = query.offset(skip).limit(limit).all() - - return items, total - - @staticmethod - def get_by_pk( - db: Session, consecutive: int, line_number: int, tenant_id: int, company_id: int - ) -> Optional[ManifestAnexo]: - return ( - db.query(ManifestAnexo) - .filter( - ManifestAnexo.consecutive == consecutive, - ManifestAnexo.line_number == line_number, - ManifestAnexo.tenant_id == tenant_id, - ManifestAnexo.company_id == company_id, - ) - .first() - ) - - @staticmethod - def create( - db: Session, - data: ManifestAnexoCreateDTO, - tenant_id: int, - company_id: int, - ) -> ManifestAnexo: - try: - db_item = ManifestAnexo( - **data.model_dump(exclude_unset=True), - tenant_id=tenant_id, - company_id=company_id, - ) - db.add(db_item) - db.commit() - db.refresh(db_item) - return db_item - except Exception as e: - db.rollback() - logger.error(f"Error creating manifest anexo: {str(e)}") - raise HTTPException(status_code=500, detail="Error creating manifest anexo") - - @staticmethod - def update( - db: Session, - consecutive: int, - line_number: int, - tenant_id: int, - company_id: int, - data: ManifestAnexoUpdateDTO, - ) -> Optional[ManifestAnexo]: - item = ManifestAnexoService.get_by_pk(db, consecutive, line_number, tenant_id, company_id) - if not item: - return None - - try: - update_data = data.model_dump(exclude_unset=True) - for field, value in update_data.items(): - setattr(item, field, value) - - db.commit() - db.refresh(item) - return item - except Exception as e: - db.rollback() - logger.error(f"Error updating manifest anexo: {str(e)}") - raise HTTPException(status_code=500, detail="Error updating manifest anexo") - - @staticmethod - def delete( - db: Session, consecutive: int, line_number: int, tenant_id: int, company_id: int - ) -> bool: - item = ManifestAnexoService.get_by_pk(db, consecutive, line_number, tenant_id, company_id) - if not item: - return False - - try: - db.delete(item) - db.commit() - return True - except Exception as e: - db.rollback() - logger.error(f"Error deleting manifest anexo: {str(e)}") - raise HTTPException(status_code=500, detail="Error deleting manifest anexo") diff --git a/backend/api/v1/modules/a76/manifests/value_manifestation/__init__.py b/backend/api/v1/modules/a76/manifests/value_manifestation/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/backend/api/v1/modules/a76/manifests/value_manifestation/dtos.py b/backend/api/v1/modules/a76/manifests/value_manifestation/dtos.py deleted file mode 100644 index 37f998ad..00000000 --- a/backend/api/v1/modules/a76/manifests/value_manifestation/dtos.py +++ /dev/null @@ -1,75 +0,0 @@ -from decimal import Decimal -from typing import List, Optional - -from pydantic import BaseModel, Field - - -class ValueManifestationBaseDTO(BaseModel): - manifestation_number: Optional[str] = Field(None, max_length=100, description="Manifestation number") - pedimento: Optional[str] = Field(None, max_length=15, description="Pedimento") - periodicity: Optional[str] = Field(None, max_length=10, description="Periodicity") - semester: Optional[int] = Field(None, description="Semester") - year: Optional[str] = Field(None, max_length=4, description="Year") - pedimento_type: Optional[str] = Field(None, max_length=3, description="Pedimento type") - aa_code: Optional[str] = Field(None, max_length=5, description="AA code") - patent: Optional[str] = Field(None, max_length=4, description="Patent") - first_name: Optional[str] = Field(None, max_length=80, description="First name") - last_name_paternal: Optional[str] = Field(None, max_length=80, description="Last name paternal") - last_name_maternal: Optional[str] = Field(None, max_length=80, description="Last name maternal") - methods_count: Optional[int] = Field(None, description="Methods count") - merchandise_value_method: Optional[str] = Field(None, max_length=10, description="Merchandise value method") - transaction_value: Optional[int] = Field(None, description="Transaction value") - identical_merchandise_value: Optional[int] = Field(None, description="Identical merchandise value") - similar_merchandise_value: Optional[int] = Field(None, description="Similar merchandise value") - unit_sale_price_value: Optional[int] = Field(None, description="Unit sale price value") - reconstructed_value: Optional[int] = Field(None, description="Reconstructed value") - article_78_value: Optional[int] = Field(None, description="Article 78 value") - provisional_value_declaration: Optional[int] = Field(None, description="Provisional value declaration") - has_attachments: Optional[int] = Field(None, description="Has attachments") - attachment_pages_number: Optional[str] = Field(None, max_length=100, description="Attachment pages number") - transaction_value_paid_price: Optional[Decimal] = Field(None, description="Transaction value paid price") - price_pre_invoice: Optional[int] = Field(None, description="Price pre invoice") - price_other_docs: Optional[int] = Field(None, description="Price other docs") - concept_article_66: Optional[int] = Field(None, description="Concept article 66") - concept_article_66_breakdown: Optional[int] = Field(None, description="Concept article 66 breakdown") - attachment_article_66: Optional[str] = Field(None, max_length=2, description="Attachment article 66") - prepaid_merchandise_article_65: Optional[str] = Field(None, max_length=2, description="Prepaid merchandise article 65") - attachment_article_65: Optional[str] = Field(None, max_length=2, description="Attachment article 65") - tax_base_no_sale: Optional[str] = Field(None, max_length=2, description="Tax base no sale") - exists_circumstances_article_67_71: Optional[str] = Field(None, max_length=2, description="Exists circumstances article 67 71") - customs_value_attachment: Optional[str] = Field(None, max_length=2, description="Customs value attachment") - provisional_value_determination: Optional[str] = Field(None, max_length=2, description="Provisional value determination") - merchandise_value_proof_attachment: Optional[str] = Field(None, max_length=2, description="Merchandise value proof attachment") - legal_rep_rfc: Optional[str] = Field(None, max_length=30, description="Legal rep RFC") - legal_representative: Optional[str] = Field(None, max_length=100, description="Legal representative") - date: Optional[int] = Field(None, description="Date") - selected_invoice: Optional[str] = Field(None, max_length=20, description="Selected invoice") - invoice_option: Optional[str] = Field(None, max_length=3, description="Invoice option") - importer_to_use: Optional[str] = Field(None, max_length=8, description="Importer to use") - - -class ValueManifestationCreateDTO(ValueManifestationBaseDTO): - pass - - -class ValueManifestationUpdateDTO(ValueManifestationBaseDTO): - pass - - -class ValueManifestationResponseDTO(ValueManifestationBaseDTO): - id: int - tenant_id: int - company_id: int - - class Config: - from_attributes = True - - -class ValueManifestationListDTO(BaseModel): - items: List[ValueManifestationResponseDTO] - total: int - page: int - page_size: int - - class Config: - from_attributes = True diff --git a/backend/api/v1/modules/a76/manifests/value_manifestation/models.py b/backend/api/v1/modules/a76/manifests/value_manifestation/models.py deleted file mode 100644 index ae252145..00000000 --- a/backend/api/v1/modules/a76/manifests/value_manifestation/models.py +++ /dev/null @@ -1,66 +0,0 @@ -from decimal import Decimal -from typing import Optional - -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base -from sqlalchemy import ( - Index, - Integer, - Numeric, - PrimaryKeyConstraint, - SmallInteger, - String, -) -from sqlalchemy.orm import Mapped, mapped_column - - -class ValueManifestation(Base, TenantScopedMixin, TimestampMixin): - __tablename__ = "value_manifestations" - __table_args__ = ( - Index("idx_value_manifestations_manifestation_number", "manifestation_number"), - {"schema": "a76"}, - ) - - id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) - - manifestation_number: Mapped[Optional[str]] = mapped_column(String(100), nullable=True) - pedimento: Mapped[Optional[str]] = mapped_column(String(15), nullable=True) - periodicity: Mapped[Optional[str]] = mapped_column(String(10), nullable=True) - semester: Mapped[Optional[int]] = mapped_column(SmallInteger, nullable=True) - year: Mapped[Optional[str]] = mapped_column(String(4), nullable=True) - pedimento_type: Mapped[Optional[str]] = mapped_column(String(3), nullable=True) - aa_code: Mapped[Optional[str]] = mapped_column(String(5), nullable=True) - patent: Mapped[Optional[str]] = mapped_column(String(4), nullable=True) - first_name: Mapped[Optional[str]] = mapped_column(String(80), nullable=True) - last_name_paternal: Mapped[Optional[str]] = mapped_column(String(80), nullable=True) - last_name_maternal: Mapped[Optional[str]] = mapped_column(String(80), nullable=True) - methods_count: Mapped[Optional[int]] = mapped_column(Integer, nullable=True) - merchandise_value_method: Mapped[Optional[str]] = mapped_column(String(10), nullable=True) - transaction_value: Mapped[Optional[int]] = mapped_column(SmallInteger, nullable=True) - identical_merchandise_value: Mapped[Optional[int]] = mapped_column(SmallInteger, nullable=True) - similar_merchandise_value: Mapped[Optional[int]] = mapped_column(SmallInteger, nullable=True) - unit_sale_price_value: Mapped[Optional[int]] = mapped_column(SmallInteger, nullable=True) - reconstructed_value: Mapped[Optional[int]] = mapped_column(SmallInteger, nullable=True) - article_78_value: Mapped[Optional[int]] = mapped_column(SmallInteger, nullable=True) - provisional_value_declaration: Mapped[Optional[int]] = mapped_column(Integer, nullable=True) - has_attachments: Mapped[Optional[int]] = mapped_column(SmallInteger, nullable=True) - attachment_pages_number: Mapped[Optional[str]] = mapped_column(String(100), nullable=True) - transaction_value_paid_price: Mapped[Optional[Decimal]] = mapped_column(Numeric(19, 8), nullable=True) - price_pre_invoice: Mapped[Optional[int]] = mapped_column(SmallInteger, nullable=True) - price_other_docs: Mapped[Optional[int]] = mapped_column(SmallInteger, nullable=True) - concept_article_66: Mapped[Optional[int]] = mapped_column(SmallInteger, nullable=True) - concept_article_66_breakdown: Mapped[Optional[int]] = mapped_column(SmallInteger, nullable=True) - attachment_article_66: Mapped[Optional[str]] = mapped_column(String(2), nullable=True) - prepaid_merchandise_article_65: Mapped[Optional[str]] = mapped_column(String(2), nullable=True) - attachment_article_65: Mapped[Optional[str]] = mapped_column(String(2), nullable=True) - tax_base_no_sale: Mapped[Optional[str]] = mapped_column(String(2), nullable=True) - exists_circumstances_article_67_71: Mapped[Optional[str]] = mapped_column(String(2), nullable=True) - customs_value_attachment: Mapped[Optional[str]] = mapped_column(String(2), nullable=True) - provisional_value_determination: Mapped[Optional[str]] = mapped_column(String(2), nullable=True) - merchandise_value_proof_attachment: Mapped[Optional[str]] = mapped_column(String(2), nullable=True) - legal_rep_rfc: Mapped[Optional[str]] = mapped_column(String(30), nullable=True) - legal_representative: Mapped[Optional[str]] = mapped_column(String(100), nullable=True) - date: Mapped[Optional[int]] = mapped_column(Integer, nullable=True) - selected_invoice: Mapped[Optional[str]] = mapped_column(String(20), nullable=True) - invoice_option: Mapped[Optional[str]] = mapped_column(String(3), nullable=True) - importer_to_use: Mapped[Optional[str]] = mapped_column(String(8), nullable=True) diff --git a/backend/api/v1/modules/a76/manifests/value_manifestation/service.py b/backend/api/v1/modules/a76/manifests/value_manifestation/service.py deleted file mode 100644 index 7b867837..00000000 --- a/backend/api/v1/modules/a76/manifests/value_manifestation/service.py +++ /dev/null @@ -1,136 +0,0 @@ -import logging -from typing import List, Optional, Dict, Any, Tuple - -from fastapi import HTTPException -from sqlalchemy import or_ -from sqlalchemy.orm import Session - -from .models import ValueManifestation -from .dtos import ( - ValueManifestationCreateDTO, - ValueManifestationUpdateDTO, -) - -logger = logging.getLogger(__name__) - - -class ValueManifestationService: - @staticmethod - def get_all( - db: Session, - tenant_id: int, - company_id: int, - skip: int = 0, - limit: int = 100, - filters: Optional[Dict[str, Any]] = None, - ) -> Tuple[List[ValueManifestation], int]: - query = db.query(ValueManifestation).filter( - ValueManifestation.tenant_id == tenant_id, - ValueManifestation.company_id == company_id, - ) - - if filters: - if filters.get("search"): - search_pattern = f"%{filters['search']}%" - query = query.filter( - or_( - ValueManifestation.manifestation_number.ilike(search_pattern), - ValueManifestation.pedimento.ilike(search_pattern), - ) - ) - - total = query.count() - items = query.offset(skip).limit(limit).all() - - return items, total - - @staticmethod - def get_by_id( - db: Session, - value_manifestation_id: int, - tenant_id: int, - company_id: int, - ) -> Optional[ValueManifestation]: - return ( - db.query(ValueManifestation) - .filter( - ValueManifestation.id == value_manifestation_id, - ValueManifestation.tenant_id == tenant_id, - ValueManifestation.company_id == company_id, - ) - .first() - ) - - @staticmethod - def create( - db: Session, - data: ValueManifestationCreateDTO, - tenant_id: int, - company_id: int, - ) -> ValueManifestation: - try: - db_item = ValueManifestation( - **data.model_dump(exclude_unset=True), - tenant_id=tenant_id, - company_id=company_id, - ) - db.add(db_item) - db.commit() - db.refresh(db_item) - return db_item - except Exception as e: - db.rollback() - logger.error(f"Error creating value manifestation: {str(e)}") - raise HTTPException( - status_code=500, detail="Error creating value manifestation" - ) - - @staticmethod - def update( - db: Session, - value_manifestation_id: int, - tenant_id: int, - company_id: int, - data: ValueManifestationUpdateDTO, - ) -> Optional[ValueManifestation]: - item = ValueManifestationService.get_by_id( - db, value_manifestation_id, tenant_id, company_id - ) - if not item: - return None - - try: - update_data = data.model_dump(exclude_unset=True) - for field, value in update_data.items(): - setattr(item, field, value) - - db.commit() - db.refresh(item) - return item - except Exception as e: - db.rollback() - logger.error(f"Error updating value manifestation {value_manifestation_id}: {str(e)}") - raise HTTPException( - status_code=500, detail="Error updating value manifestation" - ) - - @staticmethod - def delete( - db: Session, value_manifestation_id: int, tenant_id: int, company_id: int - ) -> bool: - item = ValueManifestationService.get_by_id( - db, value_manifestation_id, tenant_id, company_id - ) - if not item: - return False - - try: - db.delete(item) - db.commit() - return True - except Exception as e: - db.rollback() - logger.error(f"Error deleting value manifestation {value_manifestation_id}: {str(e)}") - raise HTTPException( - status_code=500, detail="Error deleting value manifestation" - ) diff --git a/backend/api/v1/modules/a76/parts/__init__.py b/backend/api/v1/modules/a76/parts/__init__.py deleted file mode 100644 index 33bfb6c2..00000000 --- a/backend/api/v1/modules/a76/parts/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -""" -Módulo de GParts -""" - -from .routes import router - -__all__ = ["router"] diff --git a/backend/api/v1/modules/a76/parts/dto.py b/backend/api/v1/modules/a76/parts/dto.py deleted file mode 100644 index 9064ad9d..00000000 --- a/backend/api/v1/modules/a76/parts/dto.py +++ /dev/null @@ -1,222 +0,0 @@ -from datetime import datetime -from decimal import Decimal -from typing import List, Optional, Literal -from pydantic import BaseModel, Field, ConfigDict -from api.v1.modules.a24.inv.inv_aphis.dto import InvPartAphisGeneralDTO - -# --- SUB-DTO: DATOS ADUANALES (FaData) --- -class FaDataDTO(BaseModel): - origin_country: Optional[str] = None - sector: Optional[str] = None - fraction_type: Optional[str] = None - - model_config = ConfigDict(from_attributes=True) - -# --- SUB-DTO: RELACIONES CON PAÍSES --- -class PartCountryDataDTO(BaseModel): - country_code: Optional[str] = None - fraction: Optional[str] = None - preference: Optional[str] = "GENERAL" - has_certificate: Optional[bool] = False - certificate_number: Optional[str] = None - end_date: Optional[datetime] = None - previous_fractions_7m: Optional[bool] = False - omission_import: Optional[bool] = False - omission_export: Optional[bool] = False - import_percentage: Optional[Decimal] = None - export_percentage: Optional[Decimal] = None - sector: Optional[str] = None - - model_config = ConfigDict(from_attributes=True) - -# --- SUB-DTO: COMPONENTES BOM --- -class InvBomItemDTO(BaseModel): - id: Optional[int] = None - component_part_id: Optional[int] = Field(None, gt=0, description="ID de la parte componente (debe ser > 0)") - component_part_number: Optional[str] = None - quantity: Optional[Decimal] = Field(Decimal('1.0'), gt=0, description="Cantidad debe ser > 0") - uom_code: Optional[str] = Field(None, max_length=5, description="Código de unidad de medida") - procedure_type: Optional[str] = None - is_percentage: Optional[bool] = True - raw_material: Optional[Decimal] = None - waste: Optional[Decimal] = None - merma: Optional[Decimal] = None - - model_config = ConfigDict(from_attributes=True) - -# --- SUB-DTO: DATOS DE INVENTARIO Y COSTEO (InvData) --- -class InvDataDTO(BaseModel): - part_type: Optional[str] = None - material_type: Optional[str] = None - reference_number: Optional[str] = None - flex_reference_number: Optional[str] = None - equivalent_uom: Optional[str] = None - conversion_factor: Optional[Decimal] = None - stock_uom: Optional[str] = None - alternate_uom: Optional[str] = None - conversion_uom: Optional[str] = None - added_value: Optional[Decimal] = None - added_value_type: Optional[str] = None - assigned_client: Optional[str] = None - supplier_code: Optional[str] = None - is_textile: Optional[str] = None - bom_version: Optional[int] = None - is_repair: Optional[str] = None - is_hazardous: Optional[str] = None - emergency_number: Optional[str] = None - danger_class: Optional[str] = None - packaging_group: Optional[str] = None - width: Optional[str] = None - thickness: Optional[str] = None - specification: Optional[str] = None - - # Desglose de Costos (Anexo 24 - Valor Agregado) - total_value: Optional[Decimal] = None - direct_labor: Optional[Decimal] = None - general_expenses: Optional[Decimal] = None - total_expenses: Optional[Decimal] = None - depreciation: Optional[Decimal] = None - tooling: Optional[Decimal] = None - material_consumed: Optional[Decimal] = None - profit: Optional[Decimal] = None - - # Fracciones adicionales y TLCAN/T-MEC - us_fraction_alt: Optional[str] = None - ca_fraction: Optional[str] = None - ad_valorem_us: Optional[Decimal] = None - nafta_result: Optional[str] = None - nafta_percentage: Optional[Decimal] = None - dta: Optional[str] = None - dtb: Optional[str] = None - dtg: Optional[str] = None - - # --- CAMPOS ADICIONALES FRONTEND --- - substitute_part: Optional[str] = None - complementary_part: Optional[str] = None - preference_part: Optional[str] = None - use_alternate_quantity: Optional[bool] = False - un_number: Optional[str] = None - shipping_name: Optional[str] = None - hazard_notes: Optional[str] = None - repair_unit_cost: Optional[Decimal] = None - repair_added_value: Optional[Decimal] = None - fraction_9801: Optional[str] = None - immex_type: Optional[str] = None - disable_movements: Optional[bool] = False - pga_program_code: Optional[str] = None - usmca_fraction: Optional[str] = None - scrap_part_number: Optional[str] = None - waste_part_number: Optional[str] = None - scrap_description_en: Optional[str] = None - scrap_description_es: Optional[str] = None - scrap_export_fraction: Optional[str] = None - scrap_us_fraction: Optional[str] = None - equivalent_uom_2: Optional[str] = None - conversion_factor_2: Optional[Decimal] = None - has_auxiliary: Optional[bool] = False - auxiliary_uom: Optional[str] = None - auxiliary_conversion: Optional[Decimal] = None - auxiliary_unit_cost: Optional[Decimal] = None - mex_packing: Optional[Decimal] = None - sales_order: Optional[str] = None - use_rule_8: Optional[bool] = False - sector: Optional[str] = None - origin_country: Optional[str] = None - fraction_type: Optional[str] = None - - # Extensiones - agency_code_definition: Optional[str] = None - carta_porte: Optional[str] = None - client_part_names: Optional[List[dict]] = None - part_identifiers: Optional[List[dict]] = None - substitute_parts: Optional[List[dict]] = None - aphis_data: Optional[dict] = None - aphis_records: Optional[List[InvPartAphisGeneralDTO]] = None - - non_discharge_clients: Optional[List[dict]] = None - - # Lista de materiales (BOM) - bom_items: Optional[List[InvBomItemDTO]] = None - - # Lista de países de origen - countries: Optional[List[PartCountryDataDTO]] = None - - model_config = ConfigDict(from_attributes=True) - - -class PartBase(BaseModel): - part_number: str = Field(..., max_length=70) - commercial_part_number: Optional[str] = None - - description_spanish: Optional[str] = None - description_english: Optional[str] = None - part_class: Optional[str] = None - unit_of_measure: Optional[str] = "PZ" - - unit_cost: Optional[Decimal] = Field(default=None, ge=0) - currency_key: Optional[str] = None - currency_type: Optional[str] = None - - unit_weight: Optional[Decimal] = Field(default=None, ge=0) - weight_type: Optional[str] = None - - fraction: Optional[str] = None - us_fraction: Optional[str] = None - - # Regulatorios - fda_key: Optional[str] = None - fcc_key: Optional[str] = None - license_code: Optional[str] = None - eccn: Optional[str] = None - export_code: Optional[str] = None - exclusion_symbol: Optional[str] = None - - is_active: bool = True - part_photo: Optional[str] = None - - # Sistema al que pertenece la parte: 'fixed_asset' (SCAF) o 'inventory' (SCAII) - system: str = Field(default="fixed_asset", max_length=12) - - # Anidados - fa_data: Optional[FaDataDTO] = None - inv_data: Optional[InvDataDTO] = None - -# --- CREACIÓN --- -class PartCreateDTO(PartBase): - # Opcional para que lo tome de la URL si no viene en el body - company_id: Optional[int] = None - -# --- ACTUALIZACIÓN --- -class PartUpdateDTO(PartBase): - part_number: Optional[str] = None - # Todo opcional para PATCH - pass - -# --- RESPUESTA --- -class PartResponseDTO(PartBase): - id: int - tenant_id: int - company_id: int - creation_date: Optional[int] = None - modification_date_iso: Optional[datetime] = None - - model_config = ConfigDict(from_attributes=True) - -# --- BÁSICO (Para listados ligeros) --- -class PartBasicDTO(BaseModel): - id: int - part_number: str - description_spanish: Optional[str] = None - fraction: Optional[str] = None - is_active: bool - - model_config = ConfigDict(from_attributes=True) - -# --- LISTADO PAGINADO --- -class PartListResponseDTO(BaseModel): - items: List[PartResponseDTO] - total: int - page: int - page_size: int - pages: int - diff --git a/backend/api/v1/modules/a76/parts/models.py b/backend/api/v1/modules/a76/parts/models.py deleted file mode 100644 index 1d2f35e4..00000000 --- a/backend/api/v1/modules/a76/parts/models.py +++ /dev/null @@ -1,146 +0,0 @@ -""" -Modelos ORM para gestión de partes/componentes - Anexo 76 (Master Data) -""" - -from datetime import datetime -from decimal import Decimal -from typing import TYPE_CHECKING, Optional, List - -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base -from sqlalchemy import ( - ForeignKeyConstraint, - Integer, - Numeric, - PrimaryKeyConstraint, - String, - UniqueConstraint, - Boolean, - DateTime, -) - -# Importante usar relationship y Mapped -from sqlalchemy.orm import Mapped, mapped_column, relationship - -from api.v1.modules.public.reference_data.currency_types.models import CurrencyType -from api.v1.modules.a24.fa.fa_parts.models import FaPart -from api.v1.modules.a24.inv.inv_parts.models import InvPart -from api.v1.modules.a24.inv.bom.models import BillOfMaterial -from api.v1.modules.a24.inv.part_countries.models import PartCountry - - -if TYPE_CHECKING: - from api.v1.modules.a76.classes.models import Class - from api.v1.modules.a76.general_catalogs.units_of_measure.models import ( - UnitOfMeasure, - ) - - -class Part(Base, TenantScopedMixin, TimestampMixin): - __tablename__ = "parts" - __table_args__ = ( - PrimaryKeyConstraint("id", name="parts_pkey"), - ForeignKeyConstraint( - ["currency_key"], ["public.currency_types.code"], name="fk_parts_currency" - ), - ForeignKeyConstraint( - ["unit_of_measure", "tenant_id", "company_id"], - [ - "a76.units_of_measure.code", - "a76.units_of_measure.tenant_id", - "a76.units_of_measure.company_id", - ], - ), - # Puente hacia la tabla de clases - ForeignKeyConstraint( - ["part_class", "tenant_id", "company_id"], - [ - "a76.classes.class_code", - "a76.classes.tenant_id", - "a76.classes.company_id", - ], - name="fk_parts_class", - ), - UniqueConstraint( - "tenant_id", "company_id", "part_number", name="client_part_ukey" - ), - {"schema": "a76"}, - ) - - id: Mapped[int] = mapped_column(Integer, primary_key=True) - part_number: Mapped[str] = mapped_column(String(70)) - commercial_part_number: Mapped[Optional[str]] = mapped_column(String(70)) - - description_spanish: Mapped[Optional[str]] = mapped_column(String(500)) - description_english: Mapped[Optional[str]] = mapped_column(String(500)) - part_class: Mapped[Optional[str]] = mapped_column(String(8)) - unit_of_measure: Mapped[Optional[str]] = mapped_column(String(5)) - - unit_cost: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) - currency_type: Mapped[Optional[str]] = mapped_column(String(2)) - currency_key: Mapped[Optional[str]] = mapped_column(String(3)) - - unit_weight: Mapped[Optional[Decimal]] = mapped_column(Numeric(19, 8)) - weight_type: Mapped[Optional[str]] = mapped_column(String(6)) - - fraction: Mapped[Optional[str]] = mapped_column(String(10)) - us_fraction: Mapped[Optional[str]] = mapped_column(String(16)) - fda_key: Mapped[Optional[str]] = mapped_column(String(20)) - fcc_key: Mapped[Optional[str]] = mapped_column(String(30)) - license_code: Mapped[Optional[str]] = mapped_column(String(3)) - eccn: Mapped[Optional[str]] = mapped_column(String(20)) - export_code: Mapped[Optional[str]] = mapped_column(String(2)) - exclusion_symbol: Mapped[Optional[str]] = mapped_column(String(19)) - - is_active: Mapped[Optional[bool]] = mapped_column( - Boolean, default=True, server_default="true" - ) - part_photo: Mapped[Optional[str]] = mapped_column(String(255)) - - # Sistema al que pertenece la parte: 'fixed_asset' (SCAF) o 'inventory' (SCAII) - system: Mapped[str] = mapped_column(String(12), nullable=False, server_default="fixed_asset") - - creation_date: Mapped[Optional[int]] = mapped_column() - modification_date: Mapped[Optional[int]] = mapped_column() - modification_date_iso: Mapped[Optional[datetime]] = mapped_column(DateTime) - - # --- RELACIONES --- - currency: Mapped[Optional["CurrencyType"]] = relationship("CurrencyType") - unit_of_measure_info: Mapped[Optional["UnitOfMeasure"]] = relationship( - "UnitOfMeasure" - ) - part_class_info: Mapped[Optional["Class"]] = relationship( - "Class", back_populates="parts", overlaps="unit_of_measure_info" - ) - - # Extensiones Anexo 24 - fa_data: Mapped[Optional["FaPart"]] = relationship( - "FaPart", - back_populates="master_info", - uselist=False, - cascade="all, delete-orphan", - ) - inv_data: Mapped[Optional["InvPart"]] = relationship( - "InvPart", - back_populates="master_info", - uselist=False, - cascade="all, delete-orphan", - ) - - # Bom Components (as parent) - bom_items: Mapped[List["BillOfMaterial"]] = relationship( - "BillOfMaterial", - foreign_keys=[BillOfMaterial.parent_part_id], - cascade="all, delete-orphan", - back_populates="parent_part" - ) - - # Países Anexo 24 - inv_countries: Mapped[List["PartCountry"]] = relationship( - "PartCountry", - cascade="all, delete-orphan", - back_populates="part" - ) - - def __repr__(self) -> str: - return f"" diff --git a/backend/api/v1/modules/a76/parts/routes.py b/backend/api/v1/modules/a76/parts/routes.py deleted file mode 100644 index 28da1648..00000000 --- a/backend/api/v1/modules/a76/parts/routes.py +++ /dev/null @@ -1,38 +0,0 @@ -""" -Endpoints API para gestión de partes (SCAII) -""" - -from fastapi import APIRouter - -from api.v1.common.tenant_crud_routes import TenantCRUDRoutes - -from .dto import PartCreateDTO, PartResponseDTO, PartUpdateDTO -from .service import PartService -from api.v1.modules.a76.layouts_csv.parts.routes import router as imports_router - -router = APIRouter() - -# CSV import (upload → scan → status → commit) -router.include_router(imports_router, prefix="/parts/imports", tags=["a76 / parts / csv_import"]) - -# CRUD -router.include_router( - TenantCRUDRoutes( - service=PartService, - create_schema=PartCreateDTO, - update_schema=PartUpdateDTO, - response_schema=PartResponseDTO, - prefix="/parts", - tags=["a76 / parts"], - resource_name="Part", - id_name="part_id", - enable_list=True, - enable_filters=True, - max_page_size=1000, - list_permissions=["goods_parts.view"], - get_permissions=["goods_parts.view"], - create_permissions=["goods_parts.create"], - update_permissions=["goods_parts.edit"], - delete_permissions=["goods_parts.delete"], - ).router -) \ No newline at end of file diff --git a/backend/api/v1/modules/a76/parts/service.py b/backend/api/v1/modules/a76/parts/service.py deleted file mode 100644 index 4e6c1528..00000000 --- a/backend/api/v1/modules/a76/parts/service.py +++ /dev/null @@ -1,546 +0,0 @@ -import logging -from typing import Any, Dict, List, Optional - -from fastapi import HTTPException -from sqlalchemy import or_, insert as sa_insert, update as sa_update -from sqlalchemy.exc import IntegrityError -from sqlalchemy.orm import Session, defer, joinedload, load_only - -# Importamos el modelo PRINCIPAL -from .models import Part - -# Importamos TODOS los DTOs necesarios -from .dto import ( - PartCreateDTO, - PartUpdateDTO, - PartResponseDTO, - PartBasicDTO # ¡Importante tener este! -) - -logger = logging.getLogger(__name__) - - -def _apply_part_currency_defaults(data: dict) -> None: - """Moneda obligatoria en partes: si no viene, extranjera USD / tipo EX.""" - ck = data.get("currency_key") - ct = data.get("currency_type") - if ck is None or (isinstance(ck, str) and not str(ck).strip()): - data["currency_key"] = "USD" - if ct is None or (isinstance(ct, str) and not str(ct).strip()): - data["currency_type"] = "EX" - - -class PartService: - """Servicio para gestión de Partes (Anexo 76 + Anexo 24)""" - - # Estos campos están en el modelo pero NO en la DB todavía, faltan las migraciones del usuario - MISSING_INV_COLUMNS = [] - - # Campos que SÍ existen en la DB (Verificados con \d a24.inv_partes) - SAFE_INV_COLUMNS = [ - "id", "part_type", "material_type", "reference_number", "flex_reference_number", - "equivalent_uom", "conversion_factor", "stock_uom", "alternate_uom", "conversion_uom", - "added_value", "added_value_type", "assigned_client", "supplier_code", "is_textile", - "bom_version", "is_repair", "is_hazardous", "emergency_number", "danger_class", - "packaging_group", "width", "thickness", "specification", "total_value", "direct_labor", - "general_expenses", "total_expenses", "depreciation", "tooling", "material_consumed", - "profit", "us_fraction_alt", "ca_fraction", "ad_valorem_us", "nafta_result", - "nafta_percentage", "dta", "dtb", "dtg", "tenant_id", "company_id", - "substitute_part", "complementary_part", "preference_part", "use_alternate_quantity", - "un_number", "shipping_name", "hazard_notes", "repair_unit_cost", "repair_added_value", - "fraction_9801", "immex_type", "disable_movements", "pga_program_code", "usmca_fraction", - "scrap_part_number", "waste_part_number", "scrap_description_en", "scrap_description_es", - "scrap_export_fraction", "scrap_us_fraction", "equivalent_uom_2", "conversion_factor_2", - "has_auxiliary", "auxiliary_uom", "auxiliary_conversion", "auxiliary_unit_cost", - "mex_packing", "sales_order", "use_rule_8", "sector", "origin_country", "fraction_type", - "non_discharge_clients", "agency_code_definition", "carta_porte", - "client_part_names", "part_identifiers", "substitute_parts", "aphis_data" - ] - - @classmethod - def get_all( - cls, - db: Session, - tenant_id: int, - company_id: Optional[int], - skip: int = 0, - limit: int = 100, - filters: Optional[Dict[str, Any]] = None, - sort_by: Optional[str] = None, - sort_order: Optional[str] = "asc", - ) -> tuple[List[Part], int]: - - query = db.query(Part).filter(Part.tenant_id == tenant_id) - - if company_id is not None: - query = query.filter(Part.company_id == company_id) - - # Cargar inv_data de forma segura (Solo las columnas que existen) - from api.v1.modules.a24.inv.inv_parts.models import InvPart - from api.v1.modules.a24.fa.fa_parts.models import FaPart - - # Usamos joinedload + load_only para ser 100% seguros de qué columnas se piden - load_inv_opt = joinedload(Part.inv_data).load_only(*[getattr(InvPart, c) for c in cls.SAFE_INV_COLUMNS]) - load_fa_opt = joinedload(Part.fa_data) - query = query.options(load_inv_opt, load_fa_opt) - - if filters: - logger.info(f"Applying filters to Part list: {filters}") - if filters.get("q"): - search = f"%{filters['q']}%" - query = query.filter( - or_( - Part.part_number.ilike(search), - Part.description_spanish.ilike(search), - Part.description_english.ilike(search), - Part.commercial_part_number.ilike(search) - ) - ) - if filters.get("system"): - query = query.filter(Part.system == filters["system"]) - # Otros filtros... - - # Apply sorting - if sort_by: - column = getattr(Part, sort_by, None) - if column: - if sort_order == "desc": - query = query.order_by(column.desc()) - else: - query = query.order_by(column.asc()) - else: - # Default sorting: Newest first - query = query.order_by(Part.id.desc()) - - total = query.count() - items = query.offset(skip).limit(limit).all() - - # FIX PROACTIVO: Inyectar None en campos inexistentes para evitar que Pydantic dispare la carga perezosa - for item in items: - if item.inv_data: - for col in cls.MISSING_INV_COLUMNS: - item.inv_data.__dict__[col] = None - - return items, total - - @classmethod - def get_by_id(cls, db: Session, part_id: int, tenant_id: int, company_id: int) -> Optional[Part]: - query = db.query(Part).filter( - Part.id == part_id, - Part.tenant_id == tenant_id, - Part.company_id == company_id - ) - - # Cargar inv_data de forma segura y fa_data - from api.v1.modules.a24.inv.inv_parts.models import InvPart - from api.v1.modules.a24.fa.fa_parts.models import FaPart - load_inv_opt = joinedload(Part.inv_data).load_only(*[getattr(InvPart, c) for c in cls.SAFE_INV_COLUMNS]) - load_fa_opt = joinedload(Part.fa_data) - query = query.options(load_inv_opt, load_fa_opt) - - item = query.first() - - # FIX PROACTIVO: Inyectar None - if item and item.inv_data: - for col in cls.MISSING_INV_COLUMNS: - item.inv_data.__dict__[col] = None - - return item - - @classmethod - def create(cls, db: Session, part_data: PartCreateDTO, tenant_id: int, company_id: int) -> Part: - # 1. Preparar datos - data = part_data.model_dump() - _apply_part_currency_defaults(data) - print(f"DEBUG: PartService.create - full data: {data}") - - # Separar datos anidados - fa_dict = data.pop('fa_data', None) - inv_dict = data.pop('inv_data', None) - - # Extraer BOM items si existen en inv_data - bom_items_data = None - if inv_dict: - bom_items_data = inv_dict.pop('bom_items', None) - - # Extraer datos de países si existen en inv_data - countries_data = None - aphis_records_data = None - if inv_dict: - countries_data = inv_dict.pop('countries', None) - aphis_records_data = inv_dict.pop('aphis_records', None) - - # Inyectar IDs de contexto (Seguridad Multi-tenant) - data['company_id'] = company_id - data['tenant_id'] = tenant_id - - # 2. Verificar duplicados (Usando la UniqueConstraint del modelo) - existing = db.query(Part).filter( - Part.tenant_id == tenant_id, - Part.company_id == company_id, - Part.part_number == data['part_number'] - ).first() - - if existing: - raise HTTPException( - status_code=400, - detail=f"El número de parte '{data['part_number']}' ya existe." - ) - - # 3. Crear objeto Part - db_part = Part(**data) - - # 4. Crear relaciones (Anexo 24) - # Importamos aquí para evitar ciclos, usando las rutas de tu modelo - if fa_dict: - from api.v1.modules.a24.fa.fa_parts.models import FaPart - # Importante: Pasar tenant/company también al hijo - db_part.fa_data = FaPart(**fa_dict, tenant_id=tenant_id, company_id=company_id) - - # Filtrar campos que no existen en la DB (Safe filtering) para insert Core - safe_inv_data = None - if inv_dict: - safe_inv_data = {k: v for k, v in inv_dict.items() if k not in cls.MISSING_INV_COLUMNS} - - # 5. Agregar la parte primero para obtener el ID (flush) - db.add(db_part) - db.flush() # Obtener el ID generado sin hacer commit - - # 5b. Insertar INV Data vía Core (Evita que el ORM use columnas inexistentes) - if safe_inv_data is not None: - from api.v1.modules.a24.inv.inv_parts.models import InvPart - db.execute( - sa_insert(InvPart.__table__).values( - id=db_part.id, - tenant_id=tenant_id, - company_id=company_id, - **safe_inv_data - ) - ) - - # Ahora crear BOM items con el parent_part_id - if bom_items_data: - from api.v1.modules.a24.inv.bom.models import BillOfMaterial - for item in bom_items_data: - # El DTO puede traer component_part_number pero el modelo usa IDs - # En creación asumimos que enviamos component_part_id - item.pop('id', None) # Limpiar ID si viene - item.pop('component_part_number', None) # Limpiar part_number - item['parent_part_id'] = db_part.id # Asignar el parent_part_id - bom_obj = BillOfMaterial( - **item, - tenant_id=tenant_id, - company_id=company_id - ) - db_part.bom_items.append(bom_obj) - - # 6. Crear relaciones con países - if countries_data: - from api.v1.modules.a24.inv.part_countries.models import PartCountry - for country in countries_data: - country_obj = PartCountry( - part_id=db_part.id if db_part.id else None, # Se asignará después del flush - **country, - tenant_id=tenant_id, - company_id=company_id - ) - db_part.inv_countries.append(country_obj) - - # 7. Crear registros Aphis - if aphis_records_data: - from api.v1.modules.a24.inv.inv_aphis.models import ( - InvPartAphisGeneral, InvPartAphisCharacteristic, InvPartAphisStypePitems, - InvPartAphisLpcos, InvPartAphisEntities, InvPartAphisContainers, InvPartAphisRouting - ) - for a_data in aphis_records_data: - a_data.pop('id', None) - char_data = a_data.pop('characteristics', []) - stype_data = a_data.pop('stype_pitems', []) - lpco_data = a_data.pop('lpcos', []) - entity_data = a_data.pop('entities', []) - container_data = a_data.pop('containers', []) - routing_data = a_data.pop('routing', []) - - aphis_obj = InvPartAphisGeneral( - **a_data, - inv_part_id=db_part.id, - tenant_id=tenant_id, - company_id=company_id - ) - - for c_data in char_data: - c_data.pop('id', None); c_data.pop('aphis_general_id', None) - aphis_obj.characteristics.append(InvPartAphisCharacteristic(**c_data, tenant_id=tenant_id, company_id=company_id)) - - for s_data in stype_data: - s_data.pop('id', None); s_data.pop('aphis_general_id', None) - aphis_obj.stype_pitems.append(InvPartAphisStypePitems(**s_data, tenant_id=tenant_id, company_id=company_id)) - - for l_data in lpco_data: - l_data.pop('id', None); l_data.pop('aphis_general_id', None) - aphis_obj.lpcos.append(InvPartAphisLpcos(**l_data, tenant_id=tenant_id, company_id=company_id)) - - for e_data in entity_data: - e_data.pop('id', None); e_data.pop('aphis_general_id', None) - aphis_obj.entities.append(InvPartAphisEntities(**e_data, tenant_id=tenant_id, company_id=company_id)) - - for con_data in container_data: - con_data.pop('id', None); con_data.pop('aphis_general_id', None) - aphis_obj.containers.append(InvPartAphisContainers(**con_data, tenant_id=tenant_id, company_id=company_id)) - - for r_data in routing_data: - r_data.pop('id', None); r_data.pop('aphis_general_id', None) - aphis_obj.routing.append(InvPartAphisRouting(**r_data, tenant_id=tenant_id, company_id=company_id)) - - db.add(aphis_obj) - - try: - db.commit() # db.add ya se hizo en el flush anterior - db.refresh(db_part) - return db_part - - except IntegrityError as e: - db.rollback() - err_msg = str(e.orig) - logger.exception("Error DB creando parte") - - # Mensajes específicos para BOM - if "inv_bom_component" in err_msg or "fk_inv_bom_component" in err_msg: - raise HTTPException( - 400, - "Error en BOM: La parte componente no existe. Verifique que haya seleccionado una parte válida." - ) - if "inv_bom_parent" in err_msg or "fk_inv_bom_parent" in err_msg: - raise HTTPException( - 400, - "Error en BOM: La parte padre no existe. Contacte al administrador." - ) - - if "foreign key" in err_msg: - if "unit_of_measure" in err_msg: - raise HTTPException(400, "La Unidad de Medida no existe en el catálogo.") - if "currency_key" in err_msg: - raise HTTPException(400, "La Moneda no existe en el catálogo.") - if "part_class" in err_msg: - raise HTTPException(400, "La Clase no existe para este cliente.") - - # El error genérico si falla Company/Client - raise HTTPException(400, "Error de referencia: Verifique Cliente, Compañía o Catálogos.") - - raise HTTPException(400, f"Error al guardar la parte: {err_msg}") - - @classmethod - def update(cls, db: Session, part_id: int, tenant_id: int, part_data: PartUpdateDTO, company_id: int) -> Optional[Part]: - print(f"DEBUG: PartService.update - part_id={part_id}, data={part_data.model_dump(exclude_unset=True)}") - db_part = PartService.get_by_id(db, part_id, tenant_id, company_id) - if not db_part: - return None - - data = part_data.model_dump(exclude_unset=True) - if "currency_key" in data and ( - data["currency_key"] is None - or (isinstance(data["currency_key"], str) and not data["currency_key"].strip()) - ): - data["currency_key"] = "USD" - if "currency_type" in data and ( - data["currency_type"] is None - or (isinstance(data["currency_type"], str) and not data["currency_type"].strip()) - ): - data["currency_type"] = "EX" - fa_dict = data.pop('fa_data', None) - inv_dict = data.pop('inv_data', None) - - bom_items_data = None - if inv_dict: - bom_items_data = inv_dict.pop('bom_items', None) - - countries_data = None - aphis_records_data = None - if inv_dict: - countries_data = inv_dict.pop('countries', None) - aphis_records_data = inv_dict.pop('aphis_records', None) - - # Actualizar campos directos - for key, value in data.items(): - setattr(db_part, key, value) - - # Actualizar FA Data - if fa_dict is not None: - if db_part.fa_data: - for k, v in fa_dict.items(): - setattr(db_part.fa_data, k, v) - else: - from api.v1.modules.a24.fa.fa_parts.models import FaPart - db_part.fa_data = FaPart(**fa_dict, tenant_id=tenant_id, company_id=company_id) - - # Actualizar INV Data vía Core - if inv_dict is not None: - from api.v1.modules.a24.inv.inv_parts.models import InvPart - safe_inv_dict = {k: v for k, v in inv_dict.items() if k in cls.SAFE_INV_COLUMNS} - - # Verificar si ya existe el registro en inv_partes - has_inv = db.query(InvPart.id).filter(InvPart.id == part_id).first() is not None - - if has_inv: - if safe_inv_dict: - db.execute( - sa_update(InvPart.__table__) - .where(InvPart.__table__.c.id == part_id) - .values(**safe_inv_dict) - ) - else: - db.execute( - sa_insert(InvPart.__table__) - .values( - id=part_id, - tenant_id=tenant_id, - company_id=company_id, - **safe_inv_dict - ) - ) - # Expirar para que se recargue con el load_only fix si se accede - db.expire(db_part, ["inv_data"]) - - # Actualizar BOM items - if bom_items_data is not None: - from api.v1.modules.a24.inv.bom.models import BillOfMaterial - # Estrategia: Reemplazo total por ahora para simplicidad (típico en BOMs de formularios) - # Si se requiere edición fina por ID se puede implementar luego - db_part.bom_items = [] - for item in bom_items_data: - # Limpiar ID si viene para que SQLAlchemy cree nuevos o los maneje - item.pop('id', None) - item.pop('component_part_number', None) - # Asignar el parent_part_id (la parte que se está editando) - item['parent_part_id'] = db_part.id - bom_obj = BillOfMaterial( - **item, - tenant_id=tenant_id, - company_id=company_id - ) - db_part.bom_items.append(bom_obj) - - # Actualizar relaciones con países - if countries_data is not None: - from api.v1.modules.a24.inv.part_countries.models import PartCountry - # Estrategia: Reemplazo total (típico en formularios) - db_part.inv_countries = [] - for country in countries_data: - country_obj = PartCountry( - part_id=db_part.id, - **country, - tenant_id=tenant_id, - company_id=company_id - ) - db_part.inv_countries.append(country_obj) - - # Actualizar registros Aphis (Esquema relacional) - if aphis_records_data is not None: - from api.v1.modules.a24.inv.inv_aphis.models import ( - InvPartAphisGeneral, - InvPartAphisCharacteristic, - InvPartAphisStypePitems, - InvPartAphisLpcos, - InvPartAphisEntities, - InvPartAphisContainers, - InvPartAphisRouting - ) - if db_part.inv_data: - # Reemplazo total de registros Aphis para esta parte - db_part.inv_data.aphis_records = [] - for a_data in aphis_records_data: - # Limpiar IDs y sub-datos para recreación - a_data.pop('id', None) - - char_data = a_data.pop('characteristics', []) - stype_data = a_data.pop('stype_pitems', []) - lpco_data = a_data.pop('lpcos', []) - entity_data = a_data.pop('entities', []) - container_data = a_data.pop('containers', []) - routing_data = a_data.pop('routing', []) - - aphis_obj = InvPartAphisGeneral( - **a_data, - tenant_id=tenant_id, - company_id=company_id - ) - - for c_data in char_data: - c_data.pop('id', None) - c_data.pop('aphis_general_id', None) - aphis_obj.characteristics.append( - InvPartAphisCharacteristic(**c_data, tenant_id=tenant_id, company_id=company_id) - ) - - for s_data in stype_data: - s_data.pop('id', None) - s_data.pop('aphis_general_id', None) - aphis_obj.stype_pitems.append( - InvPartAphisStypePitems(**s_data, tenant_id=tenant_id, company_id=company_id) - ) - - for l_data in lpco_data: - l_data.pop('id', None) - l_data.pop('aphis_general_id', None) - aphis_obj.lpcos.append( - InvPartAphisLpcos(**l_data, tenant_id=tenant_id, company_id=company_id) - ) - - for e_data in entity_data: - e_data.pop('id', None) - e_data.pop('aphis_general_id', None) - aphis_obj.entities.append( - InvPartAphisEntities(**e_data, tenant_id=tenant_id, company_id=company_id) - ) - - for con_data in container_data: - con_data.pop('id', None) - con_data.pop('aphis_general_id', None) - aphis_obj.containers.append( - InvPartAphisContainers(**con_data, tenant_id=tenant_id, company_id=company_id) - ) - - for r_data in routing_data: - r_data.pop('id', None) - r_data.pop('aphis_general_id', None) - aphis_obj.routing.append( - InvPartAphisRouting(**r_data, tenant_id=tenant_id, company_id=company_id) - ) - - db_part.inv_data.aphis_records.append(aphis_obj) - - try: - db.commit() - db.refresh(db_part) - return db_part - except IntegrityError as e: - db.rollback() - err_msg = str(e.orig) - logger.exception("Error DB actualizando parte") - - # Mensajes de error específicos para BOM - if "inv_bom_component" in err_msg or "fk_inv_bom_component" in err_msg: - raise HTTPException( - 400, - "Error en BOM: La parte componente no existe en la base de datos. Verifique que haya seleccionado una parte válida." - ) - if "inv_bom_parent" in err_msg or "fk_inv_bom_parent" in err_msg: - raise HTTPException( - 400, - "Error en BOM: La parte padre no existe. Contacte al administrador." - ) - - raise HTTPException(400, f"Error actualizando: {err_msg}") - - @staticmethod - def delete(db: Session, part_id: int, tenant_id: int, company_id: int) -> bool: - db_part = PartService.get_by_id(db, part_id, tenant_id, company_id) - if not db_part: return False - - try: - db.delete(db_part) - db.commit() - return True - except Exception: - db.rollback() - raise HTTPException(500, "Error eliminando parte") \ No newline at end of file diff --git a/backend/api/v1/modules/a76/parts/test_parts.py b/backend/api/v1/modules/a76/parts/test_parts.py deleted file mode 100644 index bb54e2b0..00000000 --- a/backend/api/v1/modules/a76/parts/test_parts.py +++ /dev/null @@ -1,36 +0,0 @@ -import pytest -from fastapi import FastAPI -from fastapi.testclient import TestClient - -from .routes import router - -app = FastAPI() -app.include_router(router) -client = TestClient(app) - - -@pytest.mark.usefixtures("client", "access_token") -def test_list_parts(client, access_token): - headers = {"Authorization": f"Bearer {access_token}"} - response = client.get("/parts/", headers=headers) - assert response.status_code == 200 - assert "items" in response.json() - assert "page" in response.json() - assert "page_size" in response.json() - - -@pytest.mark.usefixtures("client", "access_token") -def test_get_part_not_found(client, access_token): - headers = {"Authorization": f"Bearer {access_token}"} - response = client.get("/parts/invalid_id", headers=headers) - assert response.status_code == 404 - - -def test_create_part_forbidden(): - response = client.post("/parts/", json={"name": "Test Part"}) - assert response.status_code in (403, 405, 404) - - -def test_update_part_forbidden(): - response = client.put("/parts/1", json={"name": "Updated Part"}) - assert response.status_code in (403, 405, 404) diff --git a/backend/api/v1/modules/a76/pedmientos/catalog_service.py b/backend/api/v1/modules/a76/pedmientos/catalog_service.py deleted file mode 100644 index d283f2a6..00000000 --- a/backend/api/v1/modules/a76/pedmientos/catalog_service.py +++ /dev/null @@ -1,158 +0,0 @@ - -from typing import List -from sqlalchemy.orm import Session - -# Import Reference Data Models -from api.v1.modules.public.reference_data.pedimento_codes.models import PedimentoCode -from api.v1.modules.public.reference_data.customs_sections.models import CustomsSection -from api.v1.modules.public.reference_data.code_pedimento_regimens.models import CodePedimentoRegimen -from api.v1.modules.public.reference_data.transport_types.models import TransportType -from api.v1.modules.public.reference_data.transport_modes.models import TransportMode -from api.v1.modules.public.reference_data.pedimento_transport_catalog.models import ( - PedimentoTransportCatalog, -) - -# Import A76 Services -from api.v1.modules.a76.customs_brokers.services import CustomsBrokerService -from api.v1.modules.a76.clients_and_providers.service import ClientProviderService - -# Import DTOs for mapping -from api.v1.modules.public.reference_data.pedimento_codes.dto import PedimentoCodeDTO -from api.v1.modules.public.reference_data.customs_sections.dto import CustomsSectionDTO -from api.v1.modules.public.reference_data.code_pedimento_regimens.dto import CodePedimentoRegimenDTO -from api.v1.modules.public.reference_data.transport_types.dto import TransportTypeDTO -from api.v1.modules.public.reference_data.transport_modes.dto import TransportModeDTO -from api.v1.modules.public.reference_data.pedimento_transport_catalog.dto import ( - PedimentoTransportCatalogDTO, -) -from api.v1.modules.a76.customs_brokers.dto import CustomsBrokerResponseDTO -from api.v1.modules.a76.clients_and_providers.dto import ClientProviderResponseDTO -from .dtos.pedimentos import PedimentosResponse - -from .schemas import PedimentoCatalogsResponse, PedimentoCreationResponse, PedimentoEditionResponse - -class PedimentoCatalogService: - """Service to fetch consolidated catalogs for Pedimento views""" - - @staticmethod - def get_catalogs(db: Session, tenant_id: int, company_id: int) -> PedimentoCatalogsResponse: - """Fetch all catalogs with graceful degradation""" - - response = PedimentoCatalogsResponse() - - # Helper to fetch reference data (no company_id needed) - def fetch_ref_data(): - try: - response.pedimento_codes = [ - PedimentoCodeDTO.model_validate(obj) for obj in db.query(PedimentoCode).limit(100).all() - ] - except Exception as e: - print(f"Error fetching pedimento_codes: {e}") - - try: - response.customs_sections = [ - CustomsSectionDTO.model_validate(obj) for obj in db.query(CustomsSection).limit(100).all() - ] - except Exception as e: - print(f"Error fetching customs_sections: {e}") - - try: - response.code_pedimento_regimens = [ - CodePedimentoRegimenDTO.model_validate(obj) for obj in db.query(CodePedimentoRegimen).limit(100).all() - ] - except Exception as e: - print(f"Error fetching code_pedimento_regimens: {e}") - - try: - response.transport_types = [ - TransportTypeDTO.model_validate(obj) for obj in db.query(TransportType).limit(200).all() - ] - except Exception as e: - print(f"Error fetching transport_types: {e}") - - try: - response.transport_modes = [ - TransportModeDTO.model_validate(obj) for obj in db.query(TransportMode).limit(100).all() - ] - except Exception as e: - print(f"Error fetching transport_modes: {e}") - - try: - response.pedimento_transport_catalog = [ - PedimentoTransportCatalogDTO.model_validate(obj) - for obj in db.query(PedimentoTransportCatalog) - .order_by(PedimentoTransportCatalog.code.asc()) - .all() - ] - except Exception as e: - print(f"Error fetching pedimento_transport_catalog: {e}") - - # Helper to fetch tenant/company specific data - def fetch_tenant_data(): - # Customs Brokers - try: - brokers, _ = CustomsBrokerService.get_all(db, tenant_id, company_id, limit=1000) - response.customs_brokers = [ - CustomsBrokerResponseDTO.model_validate(obj) for obj in brokers - ] - except Exception as e: - print(f"Error fetching customs brokers: {e}") - - # Clients (only clients, not providers) - try: - all_cps, _ = ClientProviderService.get_all( - db, tenant_id, company_id, limit=1000 - ) - - # Helper to safely check client type (handles string or Enum) - def is_type(obj, types): - val = obj.client_or_provider - # If it's an enum, get its value, otherwise use as string - val_str = val.value if hasattr(val, 'value') else str(val) - return val_str in types - - response.clients = [ - ClientProviderResponseDTO.model_validate(obj) for obj in all_cps - if is_type(obj, ['client', 'both']) - ] - except Exception as e: - print(f"Error fetching clients: {e}") - - try: - fetch_ref_data() - fetch_tenant_data() - except Exception as e: - print(f"Error fetching catalogs: {e}") - import traceback - traceback.print_exc() - raise e - - return response - - @staticmethod - def get_creation_data(db: Session, tenant_id: int, company_id: int) -> PedimentoCreationResponse: - catalogs = PedimentoCatalogService.get_catalogs(db, tenant_id, company_id) - return PedimentoCreationResponse( - **catalogs.model_dump(), - is_create=True - ) - - @staticmethod - def get_edition_data(db: Session, pedimento_id: int, tenant_id: int, company_id: int) -> PedimentoEditionResponse: - catalogs = PedimentoCatalogService.get_catalogs(db, tenant_id, company_id) - - from .services.pedimentos import PedimentosService - pedimento = PedimentosService.get_by_id(db, pedimento_id, tenant_id, company_id) - - if not pedimento: - return None - - # Convert SQLAlchemy object to Pydantic DTO - pedimento_dto = PedimentosResponse.model_validate(pedimento) - - return PedimentoEditionResponse( - **catalogs.model_dump(), - is_create=False, - pedimento=pedimento_dto, - pedimento_id=pedimento_id - ) diff --git a/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_config_additional.py b/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_config_additional.py deleted file mode 100644 index 641a9b77..00000000 --- a/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_config_additional.py +++ /dev/null @@ -1,53 +0,0 @@ -from datetime import datetime -from typing import Optional - -from pydantic import BaseModel, ConfigDict, Field - - -class PedimentoConfigAdditionalBase(BaseModel): - """Base schema for Pedimento Config Additional""" - - pedimento_id: Optional[int] = Field(None, description="Pedimento ID") - tenant_id: Optional[int] = Field(None, description="Tenant ID") - add_po_identifier: Optional[bool] = Field(None, description="Add PO identifier") - do_not_exempt_norms_complement_x: Optional[bool] = Field( - None, description="Do not exempt norms complement X" - ) - manual_pedimento_year: Optional[str] = Field( - None, max_length=2, description="Manual pedimento year" - ) - enable_import_invoice_recipient: Optional[bool] = Field( - None, description="Enable import invoice recipient" - ) - send_502_validation_file_for_consolidated: Optional[bool] = Field( - None, description="Send 502 validation file for consolidated" - ) - add_remove_norms: Optional[bool] = Field(None, description="Add/remove norms") - choose_invoice_cove: Optional[bool] = Field(None, description="Choose invoice COVE in items") - - -class PedimentoConfigAdditionalCreate(PedimentoConfigAdditionalBase): - """Schema for creating a new Pedimento Config Additional""" - - pass - - -class PedimentoConfigAdditionalUpdate(BaseModel): - """Schema for updating a Pedimento Config Additional""" - - add_po_identifier: Optional[bool] = None - do_not_exempt_norms_complement_x: Optional[bool] = None - manual_pedimento_year: Optional[str] = Field(None, max_length=2) - enable_import_invoice_recipient: Optional[bool] = None - send_502_validation_file_for_consolidated: Optional[bool] = None - add_remove_norms: Optional[bool] = None - choose_invoice_cove: Optional[bool] = None - - -class PedimentoConfigAdditionalResponse(PedimentoConfigAdditionalBase): - """Schema for Pedimento Config Additional response""" - - id: int - created_at: datetime - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_config_calculations.py b/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_config_calculations.py deleted file mode 100644 index 74fc640e..00000000 --- a/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_config_calculations.py +++ /dev/null @@ -1,61 +0,0 @@ -from datetime import datetime -from typing import Optional - -from pydantic import BaseModel, ConfigDict, Field - - -class PedimentoConfigCalculationsBase(BaseModel): - """Base schema for Pedimento Config Calculations""" - - pedimento_id: Optional[int] = Field(None, description="Pedimento ID") - tenant_id: Optional[int] = Field(None, description="Tenant ID") - dta_type: Optional[str] = Field(None, max_length=1, description="DTA type") - dta_operation: Optional[bool] = Field(False, description="DTA operation") - dta_vehicle_count: Optional[int] = Field(0, description="DTA vehicle count") - dta_mixed_rate_8permil: Optional[bool] = Field( - False, description="DTA mixed rate 8 per mil" - ) - pays_vat: Optional[bool] = Field(False, description="Pays VAT") - pays_prevalidation: Optional[bool] = Field(False, description="Pays prevalidation") - include_sagar_certificate_fee: Optional[bool] = Field( - False, description="Include SAGAR certificate fee" - ) - fixed_vehicle_dta_fee: Optional[bool] = Field( - False, description="Fixed vehicle DTA fee" - ) - additional_fixed_fee: Optional[int] = Field( - 0, description="Additional fixed fee" - ) - additional_fixed_fee_payment_method: Optional[int] = Field( - None, description="Additional fixed fee payment method" - ) - - -class PedimentoConfigCalculationsCreate(PedimentoConfigCalculationsBase): - """Schema for creating a new Pedimento Config Calculations""" - - pass - - -class PedimentoConfigCalculationsUpdate(BaseModel): - """Schema for updating a Pedimento Config Calculations""" - - dta_type: Optional[str] = Field(None, max_length=1) - dta_operation: Optional[bool] = None - dta_vehicle_count: Optional[int] = None - dta_mixed_rate_8permil: Optional[bool] = None - pays_vat: Optional[bool] = None - pays_prevalidation: Optional[bool] = None - include_sagar_certificate_fee: Optional[bool] = None - fixed_vehicle_dta_fee: Optional[bool] = None - additional_fixed_fee: Optional[int] = None - additional_fixed_fee_payment_method: Optional[int] = None - - -class PedimentoConfigCalculationsResponse(PedimentoConfigCalculationsBase): - """Schema for Pedimento Config Calculations response""" - - id: int - created_at: datetime - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_config_parameters.py b/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_config_parameters.py deleted file mode 100644 index d04a03e4..00000000 --- a/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_config_parameters.py +++ /dev/null @@ -1,66 +0,0 @@ -from datetime import datetime -from decimal import Decimal -from typing import Optional - -from pydantic import BaseModel, ConfigDict, Field - - -class PedimentoConfigParametersBase(BaseModel): - """Base schema for Pedimento Config Parameters""" - - pedimento_id: Optional[int] = Field(None, description="Pedimento ID") - tenant_id: Optional[int] = Field(None, description="Tenant ID") - is_embassy: Optional[bool] = Field(False, description="Is embassy") - embassy_dta: Optional[Decimal] = Field(Decimal('0.00'), description="Embassy DTA") - rule_3121_section_ii: Optional[bool] = Field( - False, description="Rule 3.1.21 Section II" - ) - use_previous_tariff: Optional[bool] = Field(False, description="Use previous tariff") - use_payment_date_fi: Optional[bool] = Field(False, description="Use payment date FI") - add_state_supplier_record_505: Optional[bool] = Field( - False, description="Add state supplier record 505" - ) - customs_value_calculation: Optional[bool] = Field( - False, description="Customs value calculation" - ) - two_decimals_unit_value: Optional[bool] = Field( - False, description="Two decimals unit value" - ) - customs_value_per_item: Optional[bool] = Field( - False, description="Customs value per item" - ) - is_national_supplier: Optional[bool] = Field( - False, description="Is national supplier" - ) - is_consolidated: Optional[bool] = Field(False, description="Is consolidated") - - -class PedimentoConfigParametersCreate(PedimentoConfigParametersBase): - """Schema for creating a new Pedimento Config Parameters""" - - pass - - -class PedimentoConfigParametersUpdate(BaseModel): - """Schema for updating a Pedimento Config Parameters""" - - is_embassy: Optional[bool] = None - embassy_dta: Optional[Decimal] = None - rule_3121_section_ii: Optional[bool] = None - use_previous_tariff: Optional[bool] = None - use_payment_date_fi: Optional[bool] = None - add_state_supplier_record_505: Optional[bool] = None - customs_value_calculation: Optional[bool] = None - two_decimals_unit_value: Optional[bool] = None - customs_value_per_item: Optional[bool] = None - is_national_supplier: Optional[bool] = None - is_consolidated: Optional[bool] = None - - -class PedimentoConfigParametersResponse(PedimentoConfigParametersBase): - """Schema for Pedimento Config Parameters response""" - - id: int - created_at: datetime - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_config_surcharges.py b/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_config_surcharges.py deleted file mode 100644 index 4337770c..00000000 --- a/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_config_surcharges.py +++ /dev/null @@ -1,43 +0,0 @@ -from datetime import datetime -from typing import Optional - -from pydantic import BaseModel, ConfigDict, Field - - -class PedimentoConfigSurchargesBase(BaseModel): - """Base schema for Pedimento Config Surcharges""" - - pedimento_id: Optional[int] = Field(None, description="Pedimento ID") - tenant_id: Optional[int] = Field(None, description="Tenant ID") - surcharge_igi: Optional[bool] = Field(None, description="Surcharge IGI") - surcharge_dta: Optional[bool] = Field(None, description="Surcharge DTA") - surcharge_vat: Optional[bool] = Field(None, description="Surcharge VAT") - surcharge_isan: Optional[bool] = Field(None, description="Surcharge ISAN") - surcharge_ieps: Optional[bool] = Field(None, description="Surcharge IEPS") - surcharge_cc: Optional[bool] = Field(None, description="Surcharge CC") - - -class PedimentoConfigSurchargesCreate(PedimentoConfigSurchargesBase): - """Schema for creating a new Pedimento Config Surcharges""" - - pass - - -class PedimentoConfigSurchargesUpdate(BaseModel): - """Schema for updating a Pedimento Config Surcharges""" - - surcharge_igi: Optional[bool] = None - surcharge_dta: Optional[bool] = None - surcharge_vat: Optional[bool] = None - surcharge_isan: Optional[bool] = None - surcharge_ieps: Optional[bool] = None - surcharge_cc: Optional[bool] = None - - -class PedimentoConfigSurchargesResponse(PedimentoConfigSurchargesBase): - """Schema for Pedimento Config Surcharges response""" - - id: int - created_at: datetime - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_config_update_rectification.py b/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_config_update_rectification.py deleted file mode 100644 index 6255e781..00000000 --- a/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_config_update_rectification.py +++ /dev/null @@ -1,37 +0,0 @@ -from datetime import datetime -from typing import Optional - -from pydantic import BaseModel, ConfigDict, Field - -from api.v1.common.dto_mixins import UpdateFlagsMixin - - -class PedimentoConfigUpdateRectificationBase(BaseModel, UpdateFlagsMixin): - """Base schema for Pedimento Config Update Rectification""" - - pedimento_id: Optional[int] = Field(None, description="Pedimento ID") - tenant_id: Optional[int] = Field(None, description="Tenant ID") - calculate_surcharge: Optional[bool] = Field(None, description="Calculate surcharge") - - -class PedimentoConfigUpdateRectificationCreate(PedimentoConfigUpdateRectificationBase): - """Schema for creating a new Pedimento Config Update Rectification""" - - pass - - -class PedimentoConfigUpdateRectificationUpdate(BaseModel, UpdateFlagsMixin): - """Schema for updating a Pedimento Config Update Rectification""" - - calculate_surcharge: Optional[bool] = None - - -class PedimentoConfigUpdateRectificationResponse( - PedimentoConfigUpdateRectificationBase -): - """Schema for Pedimento Config Update Rectification response""" - - id: int - created_at: datetime - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_config_updates.py b/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_config_updates.py deleted file mode 100644 index 0931d66c..00000000 --- a/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_config_updates.py +++ /dev/null @@ -1,32 +0,0 @@ -from datetime import datetime -from typing import Optional - -from pydantic import BaseModel, ConfigDict, Field - -from api.v1.common.dto_mixins import UpdateFlagsMixin - - -class PedimentoConfigUpdatesBase(BaseModel, UpdateFlagsMixin): - """Base schema for Pedimento Config Updates""" - - pedimento_id: Optional[int] = Field(None, description="Pedimento ID") - tenant_id: Optional[int] = Field(None, description="Tenant ID") - -class PedimentoConfigUpdatesCreate(PedimentoConfigUpdatesBase): - """Schema for creating a new Pedimento Config Updates""" - - pass - - -class PedimentoConfigUpdatesUpdate(BaseModel, UpdateFlagsMixin): - """Schema for updating a Pedimento Config Updates""" - pass - - -class PedimentoConfigUpdatesResponse(PedimentoConfigUpdatesBase): - """Schema for Pedimento Config Updates response""" - - id: int - created_at: datetime - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_contributions.py b/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_contributions.py deleted file mode 100644 index e2f5795c..00000000 --- a/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_contributions.py +++ /dev/null @@ -1,49 +0,0 @@ -from decimal import Decimal -from datetime import datetime -from typing import Optional - -from pydantic import BaseModel, ConfigDict - - -class PedimentoContributionBase(BaseModel): - """Base schema for Pedimento Contribution""" - - pedimento_id: Optional[int] = None - contribucion: Optional[str] = None - tipo_tasa: Optional[str] = None - tasa: Optional[Decimal] = None - forma_pago: Optional[str] = None - importe: Optional[Decimal] = None - gravamen: Optional[str] = None - abreviacion: Optional[str] = None - forma_pago_2: Optional[str] = None - importe_2: Optional[Decimal] = None - - -class PedimentoContributionCreate(PedimentoContributionBase): - """Schema for creating Pedimento Contribution""" - - pass - - -class PedimentoContributionUpdate(BaseModel): - """Schema for updating Pedimento Contribution""" - - contribucion: Optional[str] = None - tipo_tasa: Optional[str] = None - tasa: Optional[Decimal] = None - forma_pago: Optional[str] = None - importe: Optional[Decimal] = None - gravamen: Optional[str] = None - abreviacion: Optional[str] = None - forma_pago_2: Optional[str] = None - importe_2: Optional[Decimal] = None - - -class PedimentoContributionResponse(PedimentoContributionBase): - """Schema for Pedimento Contribution response""" - - id: int - created_at: datetime - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_customs_offices.py b/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_customs_offices.py deleted file mode 100644 index 7c556e95..00000000 --- a/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_customs_offices.py +++ /dev/null @@ -1,39 +0,0 @@ -from datetime import datetime -from typing import Optional - -from pydantic import BaseModel, ConfigDict, Field - - -class PedimentoCustomsOfficesBase(BaseModel): - """Base schema for Pedimento Customs Offices""" - - pedimento_id: Optional[int] = Field(None, description="Pedimento ID") - tenant_id: Optional[int] = Field(None, description="Tenant ID") - dispatch_customs: Optional[str] = Field( - None, max_length=3, description="Dispatch customs" - ) - entry_exit_customs: Optional[str] = Field( - None, max_length=3, description="Entry/exit customs" - ) - - -class PedimentoCustomsOfficesCreate(PedimentoCustomsOfficesBase): - """Schema for creating a new Pedimento Customs Offices""" - - pass - - -class PedimentoCustomsOfficesUpdate(BaseModel): - """Schema for updating a Pedimento Customs Offices""" - - dispatch_customs: Optional[str] = Field(None, max_length=3) - entry_exit_customs: Optional[str] = Field(None, max_length=3) - - -class PedimentoCustomsOfficesResponse(PedimentoCustomsOfficesBase): - """Schema for Pedimento Customs Offices response""" - - id: int - created_at: datetime - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_dates.py b/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_dates.py deleted file mode 100644 index b1c1b633..00000000 --- a/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_dates.py +++ /dev/null @@ -1,62 +0,0 @@ -from datetime import datetime, time -from typing import Optional - -from pydantic import BaseModel, ConfigDict, Field - - -class PedimentoDatesBase(BaseModel): - """Base schema for Pedimento Dates""" - - entry_date: datetime = Field(..., description="Entry date") - pedimento_date: Optional[datetime] = Field(None, description="Pedimento date") - payment_date: Optional[datetime] = Field(None, description="Payment date") - rectification_payment_date: Optional[datetime] = Field( - None, description="Rectification payment date" - ) - extraction_date: Optional[datetime] = Field(None, description="Extraction date") - submission_date: Optional[datetime] = Field(None, description="Submission date") - eucan_date: Optional[datetime] = Field(None, description="EUCAN date") - original_date: Optional[datetime] = Field(None, description="Original date") - start_date: Optional[datetime] = Field(None, description="Start date") - end_date: datetime = Field(..., description="End date") - - -class PedimentoDatesCreate(BaseModel): - """Schema for creating a new Pedimento Dates - pedimento_id and tenant_id are set by backend""" - - entry_date: datetime = Field(..., description="Entry date") - pedimento_date: Optional[datetime] = Field(None, description="Pedimento date") - payment_date: Optional[datetime] = Field(None, description="Payment date") - rectification_payment_date: Optional[datetime] = Field(None, description="Rectification payment date") - extraction_date: Optional[datetime] = Field(None, description="Extraction date") - submission_date: Optional[datetime] = Field(None, description="Submission date") - eucan_date: Optional[datetime] = Field(None, description="EUCAN date") - original_date: Optional[datetime] = Field(None, description="Original date") - start_date: Optional[datetime] = Field(None, description="Start date") - end_date: datetime = Field(..., description="End date") - - -class PedimentoDatesUpdate(BaseModel): - """Schema for updating a Pedimento Dates""" - - entry_date: Optional[datetime] = None - pedimento_date: Optional[datetime] = None - payment_date: Optional[datetime] = None - rectification_payment_date: Optional[datetime] = None - extraction_date: Optional[datetime] = None - submission_date: Optional[datetime] = None - eucan_date: Optional[datetime] = None - original_date: Optional[datetime] = None - start_date: Optional[datetime] = None - end_date: Optional[datetime] = None - - -class PedimentoDatesResponse(PedimentoDatesBase): - """Schema for Pedimento Dates response""" - - id: int - pedimento_id: int = Field(..., description="Pedimento ID") - tenant_id: int = Field(..., description="Tenant ID") - created_at: datetime - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_decrementables.py b/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_decrementables.py deleted file mode 100644 index c9718994..00000000 --- a/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_decrementables.py +++ /dev/null @@ -1,54 +0,0 @@ -from datetime import datetime -from decimal import Decimal -from typing import Optional - -from pydantic import BaseModel, ConfigDict, Field - - -class PedimentoDecrementablesBase(BaseModel): - """Base schema for Pedimento Decrementables""" - - pedimento_id: Optional[int] = Field(None, description="Pedimento ID") - tenant_id: Optional[int] = Field(None, description="Tenant ID") - freight: Optional[Decimal] = Field(None, description="Freight") - insurance: Optional[Decimal] = Field(None, description="Insurance") - loading: Optional[Decimal] = Field(None, description="Loading") - unloading: Optional[Decimal] = Field(None, description="Unloading") - others: Optional[Decimal] = Field(None, description="Others") - currency: Optional[str] = Field(None, max_length=3, description="Currency") - currency_factor: Optional[Decimal] = Field(None, description="Currency factor") - not_affect_usd_value: Optional[int] = Field( - None, description="Not affect USD value" - ) - not_affect_customs_value: Optional[int] = Field( - None, description="Not affect customs value" - ) - - -class PedimentoDecrementablesCreate(PedimentoDecrementablesBase): - """Schema for creating a new Pedimento Decrementables""" - - pass - - -class PedimentoDecrementablesUpdate(BaseModel): - """Schema for updating a Pedimento Decrementables""" - - freight: Optional[Decimal] = None - insurance: Optional[Decimal] = None - loading: Optional[Decimal] = None - unloading: Optional[Decimal] = None - others: Optional[Decimal] = None - currency: Optional[str] = Field(None, max_length=3) - currency_factor: Optional[Decimal] = None - not_affect_usd_value: Optional[int] = None - not_affect_customs_value: Optional[int] = None - - -class PedimentoDecrementablesResponse(PedimentoDecrementablesBase): - """Schema for Pedimento Decrementables response""" - - id: int - created_at: datetime - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_incrementables.py b/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_incrementables.py deleted file mode 100644 index acc6312e..00000000 --- a/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_incrementables.py +++ /dev/null @@ -1,56 +0,0 @@ -from datetime import datetime -from decimal import Decimal -from typing import Optional - -from pydantic import BaseModel, ConfigDict, Field - - -class PedimentoIncrementablesBase(BaseModel): - """Base schema for Pedimento Incrementables""" - - pedimento_id: Optional[int] = Field(None, description="Pedimento ID") - tenant_id: Optional[int] = Field(None, description="Tenant ID") - insured_value: Optional[Decimal] = Field(None, description="Insured value") - freight: Optional[Decimal] = Field(None, description="Freight") - insurance: Optional[Decimal] = Field(None, description="Insurance") - packaging: Optional[Decimal] = Field(None, description="Packaging") - others: Optional[Decimal] = Field(None, description="Others") - deductibles: Optional[Decimal] = Field(None, description="Deductibles") - currency: Optional[str] = Field(None, max_length=3, description="Currency") - currency_factor: Optional[Decimal] = Field(None, description="Currency factor") - not_affect_usd_value: Optional[int] = Field( - None, description="Not affect USD value" - ) - not_affect_customs_value: Optional[int] = Field( - None, description="Not affect customs value" - ) - - -class PedimentoIncrementablesCreate(PedimentoIncrementablesBase): - """Schema for creating a new Pedimento Incrementables""" - - pass - - -class PedimentoIncrementablesUpdate(BaseModel): - """Schema for updating a Pedimento Incrementables""" - - insured_value: Optional[Decimal] = None - freight: Optional[Decimal] = None - insurance: Optional[Decimal] = None - packaging: Optional[Decimal] = None - others: Optional[Decimal] = None - deductibles: Optional[Decimal] = None - currency: Optional[str] = Field(None, max_length=3) - currency_factor: Optional[Decimal] = None - not_affect_usd_value: Optional[int] = None - not_affect_customs_value: Optional[int] = None - - -class PedimentoIncrementablesResponse(PedimentoIncrementablesBase): - """Schema for Pedimento Incrementables response""" - - id: int - created_at: datetime - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_indexes.py b/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_indexes.py deleted file mode 100644 index 55edc40f..00000000 --- a/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_indexes.py +++ /dev/null @@ -1,40 +0,0 @@ -from datetime import datetime -from decimal import Decimal -from typing import Optional - -from pydantic import BaseModel, ConfigDict, Field - - -class PedimentoIndexesBase(BaseModel): - """Base schema for Pedimento Indexes""" - - pedimento_id: Optional[int] = Field(None, description="Pedimento ID") - tenant_id: Optional[int] = Field(None, description="Tenant ID") - update_factor_type: Optional[int] = Field(None, description="Update factor type") - update_factor: Optional[Decimal] = Field(None, description="Update factor") - manual_update_factor: Optional[bool] = Field( - None, description="Manual update factor" - ) - - -class PedimentoIndexesCreate(PedimentoIndexesBase): - """Schema for creating a new Pedimento Indexes""" - - pass - - -class PedimentoIndexesUpdate(BaseModel): - """Schema for updating a Pedimento Indexes""" - - update_factor_type: Optional[int] = None - update_factor: Optional[Decimal] = None - manual_update_factor: Optional[bool] = None - - -class PedimentoIndexesResponse(PedimentoIndexesBase): - """Schema for Pedimento Indexes response""" - - id: int - created_at: datetime - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_packages_transport.py b/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_packages_transport.py deleted file mode 100644 index e8dd3800..00000000 --- a/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_packages_transport.py +++ /dev/null @@ -1,178 +0,0 @@ -from datetime import datetime -from typing import Optional - -from pydantic import BaseModel, ConfigDict - - -class PedimentoPackagesBase(BaseModel): - """Base schema for Pedimento Packages""" - - pedimento_id: Optional[int] = None - quantity: Optional[int] = None - brand: Optional[str] = None - number: Optional[str] = None - vehicles: Optional[int] = None - - -class PedimentoPackagesCreate(PedimentoPackagesBase): - """Schema for creating Pedimento Packages""" - - pass - - -class PedimentoPackagesUpdate(BaseModel): - """Schema for updating Pedimento Packages""" - - quantity: Optional[int] = None - brand: Optional[str] = None - number: Optional[str] = None - vehicles: Optional[int] = None - - -class PedimentoPackagesResponse(PedimentoPackagesBase): - """Schema for Pedimento Packages response""" - - id: int - created_at: datetime - - model_config = ConfigDict(from_attributes=True) - - -class PedimentoTransportCarrierBase(BaseModel): - """Base schema for Pedimento Transport Carrier""" - - pedimento_id: Optional[int] = None - carrier: Optional[str] = None - rfc: Optional[str] = None - curp: Optional[str] = None - name: Optional[str] = None - address: Optional[str] = None - city: Optional[str] = None - state: Optional[str] = None - country: Optional[str] = None - tax_id: Optional[str] = None - total_packages: Optional[int] = None - identification: Optional[str] = None - - -class PedimentoTransportCarrierCreate(PedimentoTransportCarrierBase): - """Schema for creating Pedimento Transport Carrier""" - - pass - - -class PedimentoTransportCarrierUpdate(BaseModel): - """Schema for updating Pedimento Transport Carrier""" - - carrier: Optional[str] = None - rfc: Optional[str] = None - curp: Optional[str] = None - name: Optional[str] = None - address: Optional[str] = None - city: Optional[str] = None - state: Optional[str] = None - country: Optional[str] = None - tax_id: Optional[str] = None - total_packages: Optional[int] = None - identification: Optional[str] = None - - -class PedimentoTransportCarrierResponse(PedimentoTransportCarrierBase): - """Schema for Pedimento Transport Carrier response""" - - id: int - created_at: datetime - - model_config = ConfigDict(from_attributes=True) - - -class PedimentoSealBase(BaseModel): - """Base schema for Pedimento Seal""" - - pedimento_id: Optional[int] = None - number: Optional[str] = None - identification: Optional[str] = None - - -class PedimentoSealCreate(PedimentoSealBase): - """Schema for creating Pedimento Seal""" - - pass - - -class PedimentoSealUpdate(BaseModel): - """Schema for updating Pedimento Seal""" - - number: Optional[str] = None - identification: Optional[str] = None - - -class PedimentoSealResponse(PedimentoSealBase): - """Schema for Pedimento Seal response""" - - id: int - created_at: datetime - - model_config = ConfigDict(from_attributes=True) - - -class PedimentoContainerBase(BaseModel): - """Base schema for Pedimento Container""" - - pedimento_id: Optional[int] = None - number: Optional[str] = None - identification: Optional[str] = None - type: Optional[str] = None - - -class PedimentoContainerCreate(PedimentoContainerBase): - """Schema for creating Pedimento Container""" - - pass - - -class PedimentoContainerUpdate(BaseModel): - """Schema for updating Pedimento Container""" - - number: Optional[str] = None - identification: Optional[str] = None - type: Optional[str] = None - - -class PedimentoContainerResponse(PedimentoContainerBase): - """Schema for Pedimento Container response""" - - id: int - created_at: datetime - - model_config = ConfigDict(from_attributes=True) - - -class PedimentoGuideBase(BaseModel): - """Base schema for Pedimento Guide""" - - pedimento_id: Optional[int] = None - guide: Optional[str] = None - identifier: Optional[str] = None - - -class PedimentoGuideCreate(PedimentoGuideBase): - """Schema for creating Pedimento Guide""" - - pass - - -class PedimentoGuideUpdate(BaseModel): - """Schema for updating Pedimento Guide""" - - guide: Optional[str] = None - identifier: Optional[str] = None - - -class PedimentoGuideResponse(PedimentoGuideBase): - """Schema for Pedimento Guide response""" - - id: int - created_at: datetime - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_payments.py b/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_payments.py deleted file mode 100644 index 992e02d7..00000000 --- a/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_payments.py +++ /dev/null @@ -1,69 +0,0 @@ -from datetime import date as Date -from datetime import datetime -from datetime import time as Time -from typing import Optional - -from pydantic import BaseModel, ConfigDict, Field - - -class PedimentoPaymentsBase(BaseModel): - """Base schema for Pedimento Payments""" - - pedimento_id: Optional[int] = Field(None, description="Pedimento ID") - tenant_id: Optional[int] = Field(None, description="Tenant ID") - acknowledgment: Optional[str] = Field( - None, max_length=20, description="Acknowledgment" - ) - operation_number: Optional[str] = Field( - None, max_length=14, description="Operation number" - ) - bank_code: Optional[int] = Field(None, description="Bank code") - cashier: Optional[str] = Field(None, max_length=2, description="Cashier") - date: Optional[Date] = Field(None, description="Date") - time: Optional[Time] = Field(None, description="Time") - shift: Optional[str] = Field(None, max_length=1, description="Shift") - total_cash_paid: Optional[int] = Field(None, description="Total cash paid") - total_contributions: Optional[int] = Field(None, description="Total contributions") - counter_payment: Optional[int] = Field(None, description="Counter payment") - pece_code: Optional[str] = Field(None, max_length=5, description="PECE code") - - -class PedimentoPaymentsCreate(BaseModel): - """Schema for creating a new Pedimento Payments - pedimento_id and tenant_id are set by backend""" - - acknowledgment: Optional[str] = Field(None, max_length=20, description="Acknowledgment") - operation_number: Optional[str] = Field(None, max_length=14, description="Operation number") - bank_code: Optional[int] = Field(None, description="Bank code") - cashier: Optional[str] = Field(None, max_length=2, description="Cashier") - date: Optional[Date] = Field(None, description="Date") - time: Optional[Time] = Field(None, description="Time") - shift: Optional[str] = Field(None, max_length=1, description="Shift") - total_cash_paid: Optional[int] = Field(None, description="Total cash paid") - total_contributions: Optional[int] = Field(None, description="Total contributions") - counter_payment: Optional[int] = Field(None, description="Counter payment") - pece_code: Optional[str] = Field(None, max_length=5, description="PECE code") - - -class PedimentoPaymentsUpdate(BaseModel): - """Schema for updating a Pedimento Payments""" - - acknowledgment: Optional[str] = Field(None, max_length=20) - operation_number: Optional[str] = Field(None, max_length=14) - bank_code: Optional[int] = None - cashier: Optional[str] = Field(None, max_length=2) - date: Optional[Date] = None - time: Optional[Time] = None - shift: Optional[str] = Field(None, max_length=1) - total_cash_paid: Optional[int] = None - total_contributions: Optional[int] = None - counter_payment: Optional[int] = None - pece_code: Optional[str] = Field(None, max_length=5) - - -class PedimentoPaymentsResponse(PedimentoPaymentsBase): - """Schema for Pedimento Payments response""" - - id: int - created_at: datetime - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_rectification_destination.py b/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_rectification_destination.py deleted file mode 100644 index 7a39e104..00000000 --- a/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_rectification_destination.py +++ /dev/null @@ -1,45 +0,0 @@ -from typing import Optional - -from pydantic import BaseModel, ConfigDict, Field - - -class PedimentoRectificationDestinationBase(BaseModel): - """Base schema for Pedimento Rectification Destination""" - - pedimento_id: Optional[int] = Field(None, description="Pedimento ID") - tenant_id: Optional[int] = Field(None, description="Tenant ID") - destination_pedimento_year: Optional[str] = Field( - None, max_length=2, description="Destination pedimento year" - ) - destination_customs_office: Optional[str] = Field( - None, max_length=3, description="Destination customs office" - ) - destination_license: Optional[str] = Field( - None, max_length=4, description="Destination license" - ) - destination_pedimento_number: Optional[str] = Field( - None, max_length=7, description="Destination pedimento number" - ) - - -class PedimentoRectificationDestinationCreate(PedimentoRectificationDestinationBase): - """Schema for creating a new Pedimento Rectification Destination""" - - pass - - -class PedimentoRectificationDestinationUpdate(BaseModel): - """Schema for updating a Pedimento Rectification Destination""" - - destination_pedimento_year: Optional[str] = Field(None, max_length=2) - destination_customs_office: Optional[str] = Field(None, max_length=3) - destination_license: Optional[str] = Field(None, max_length=4) - destination_pedimento_number: Optional[str] = Field(None, max_length=7) - - -class PedimentoRectificationDestinationResponse(PedimentoRectificationDestinationBase): - """Schema for Pedimento Rectification Destination response""" - - id: int - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_rectification_origin.py b/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_rectification_origin.py deleted file mode 100644 index 8cc56bb3..00000000 --- a/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_rectification_origin.py +++ /dev/null @@ -1,72 +0,0 @@ -from datetime import datetime -from typing import Optional - -from pydantic import BaseModel, ConfigDict, Field - - -class PedimentoRectificationOriginBase(BaseModel): - """Base schema for Pedimento Rectification Origin""" - - pedimento_id: Optional[int] = Field(None, description="Pedimento ID") - tenant_id: Optional[int] = Field(None, description="Tenant ID") - original_pedimento_year: Optional[str] = Field( - None, max_length=2, description="Original pedimento year" - ) - original_customs_office: Optional[str] = Field( - None, max_length=3, description="Original customs office" - ) - original_license: Optional[str] = Field( - None, max_length=4, description="Original license" - ) - original_pedimento_number: Optional[str] = Field( - None, max_length=7, description="Original pedimento number" - ) - original_pedimento_code: Optional[str] = Field( - None, max_length=2, description="Original pedimento key" - ) - original_payment_date: Optional[datetime] = Field( - None, description="Original payment date" - ) - total_cash: Optional[int] = Field(None, description="Total cash") - total_others: Optional[int] = Field(None, description="Total others") - reason: Optional[str] = Field(None, max_length=255, description="Reason") - charge_to_client: Optional[int] = Field(None, description="Charge to client") - use_original_payment_date_for_interest_calc: Optional[int] = Field( - None, description="Use original payment date for interest calculation" - ) - manual_calculation: Optional[int] = Field(None, description="Manual calculation") - original_pedimento_norms: Optional[int] = Field( - None, description="Original pedimento norms" - ) - - -class PedimentoRectificationOriginCreate(PedimentoRectificationOriginBase): - """Schema for creating a new Pedimento Rectification Origin""" - - pass - - -class PedimentoRectificationOriginUpdate(BaseModel): - """Schema for updating a Pedimento Rectification Origin""" - - original_pedimento_year: Optional[str] = Field(None, max_length=2) - original_customs_office: Optional[str] = Field(None, max_length=3) - original_license: Optional[str] = Field(None, max_length=4) - original_pedimento_number: Optional[str] = Field(None, max_length=7) - original_pedimento_code: Optional[str] = Field(None, max_length=2) - original_payment_date: Optional[datetime] = None - total_cash: Optional[int] = None - total_others: Optional[int] = None - reason: Optional[str] = Field(None, max_length=255) - charge_to_client: Optional[int] = None - use_original_payment_date_for_interest_calc: Optional[int] = None - manual_calculation: Optional[int] = None - original_pedimento_norms: Optional[int] = None - - -class PedimentoRectificationOriginResponse(PedimentoRectificationOriginBase): - """Schema for Pedimento Rectification Origin response""" - - id: int - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_transport_means.py b/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_transport_means.py deleted file mode 100644 index d769822f..00000000 --- a/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_transport_means.py +++ /dev/null @@ -1,42 +0,0 @@ -from datetime import datetime -from typing import Optional - -from pydantic import BaseModel, ConfigDict, Field - - -class PedimentoTransportMeansBase(BaseModel): - """Base schema for Pedimento Transport Means""" - - pedimento_id: Optional[int] = Field(None, description="Pedimento ID") - tenant_id: Optional[int] = Field(None, description="Tenant ID") - destination: Optional[int] = Field(None, description="Destination") - entry_exit: Optional[str] = Field(None, max_length=3, description="Entry/exit") - arrival: str = Field(..., max_length=3, description="Arrival") - departure: str = Field(..., max_length=3, description="Departure") - - -class PedimentoTransportMeansCreate(BaseModel): - """Schema for creating a new Pedimento Transport Means - pedimento_id and tenant_id are set by backend""" - - destination: Optional[int] = Field(None, description="Destination") - entry_exit: Optional[str] = Field(None, max_length=3, description="Entry/exit") - arrival: str = Field(..., max_length=3, description="Arrival") - departure: str = Field(..., max_length=3, description="Departure") - - -class PedimentoTransportMeansUpdate(BaseModel): - """Schema for updating a Pedimento Transport Means""" - - destination: Optional[int] = None - entry_exit: Optional[str] = Field(None, max_length=3) - arrival: Optional[str] = Field(None, max_length=3) - departure: Optional[str] = Field(None, max_length=3) - - -class PedimentoTransportMeansResponse(PedimentoTransportMeansBase): - """Schema for Pedimento Transport Means response""" - - id: int - created_at: datetime - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_validation.py b/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_validation.py deleted file mode 100644 index 7be73678..00000000 --- a/backend/api/v1/modules/a76/pedmientos/dtos/pedimento_validation.py +++ /dev/null @@ -1,62 +0,0 @@ -from datetime import datetime -from typing import Optional - -from pydantic import BaseModel, ConfigDict, Field - - -class PedimentoValidationBase(BaseModel): - """Base schema for Pedimento Validation""" - - pedimento_id: Optional[int] = Field(None, description="Pedimento ID") - tenant_id: Optional[int] = Field(None, description="Tenant ID") - validator: Optional[str] = Field(None, max_length=3, description="Validator") - validation_ack: Optional[str] = Field( - None, max_length=8, description="Validation acknowledgment" - ) - pre_ack: Optional[str] = Field(None, max_length=8, description="Pre-acknowledgment") - line_signature: Optional[str] = Field( - None, max_length=50, description="Line signature" - ) - electronic_signature: Optional[str] = Field( - None, max_length=999, description="Electronic signature" - ) - certificate_number: Optional[str] = Field( - None, max_length=99, description="Certificate number" - ) - validator_id: Optional[int] = Field(None, description="Validator ID") - responsible_id: Optional[int] = Field(None, description="Responsible ID") - - -class PedimentoValidationCreate(BaseModel): - """Schema for creating a new Pedimento Validation - pedimento_id and tenant_id are set by backend""" - - validator: Optional[str] = Field(None, max_length=3, description="Validator") - validation_ack: Optional[str] = Field(None, max_length=8, description="Validation acknowledgment") - pre_ack: Optional[str] = Field(None, max_length=8, description="Previous acknowledgment") - line_signature: Optional[str] = Field(None, max_length=50, description="Line signature") - electronic_signature: Optional[str] = Field(None, max_length=999, description="Electronic signature") - certificate_number: Optional[str] = Field(None, max_length=99, description="Certificate number") - validator_id: Optional[int] = Field(None, description="Validator ID") - responsible_id: Optional[int] = Field(None, description="Responsible ID") - - -class PedimentoValidationUpdate(BaseModel): - """Schema for updating a Pedimento Validation""" - - validator: Optional[str] = Field(None, max_length=3) - validation_ack: Optional[str] = Field(None, max_length=8) - pre_ack: Optional[str] = Field(None, max_length=8) - line_signature: Optional[str] = Field(None, max_length=50) - electronic_signature: Optional[str] = Field(None, max_length=999) - certificate_number: Optional[str] = Field(None, max_length=99) - validator_id: Optional[int] = None - responsible_id: Optional[int] = None - - -class PedimentoValidationResponse(PedimentoValidationBase): - """Schema for Pedimento Validation response""" - - id: int - created_at: datetime - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/a76/pedmientos/dtos/pedimentos.py b/backend/api/v1/modules/a76/pedmientos/dtos/pedimentos.py deleted file mode 100644 index ffde30df..00000000 --- a/backend/api/v1/modules/a76/pedmientos/dtos/pedimentos.py +++ /dev/null @@ -1,227 +0,0 @@ -from datetime import datetime -from decimal import Decimal -from typing import Optional - -from pydantic import BaseModel, ConfigDict, Field - -from ..models.pedimentos import OperationType, PedimentoType -from .pedimento_config_additional import ( - PedimentoConfigAdditionalCreate, - PedimentoConfigAdditionalResponse, -) -from .pedimento_config_calculations import ( - PedimentoConfigCalculationsCreate, - PedimentoConfigCalculationsResponse, -) -from .pedimento_config_parameters import ( - PedimentoConfigParametersCreate, - PedimentoConfigParametersResponse, -) -from .pedimento_config_surcharges import ( - PedimentoConfigSurchargesCreate, - PedimentoConfigSurchargesResponse, -) -from .pedimento_config_update_rectification import ( - PedimentoConfigUpdateRectificationCreate, - PedimentoConfigUpdateRectificationResponse, -) -from .pedimento_config_updates import ( - PedimentoConfigUpdatesCreate, - PedimentoConfigUpdatesResponse, -) -from .pedimento_customs_offices import ( - PedimentoCustomsOfficesCreate, - PedimentoCustomsOfficesResponse, -) -from .pedimento_dates import PedimentoDatesCreate, PedimentoDatesResponse -from .pedimento_decrementables import ( - PedimentoDecrementablesCreate, - PedimentoDecrementablesResponse, -) -from .pedimento_incrementables import ( - PedimentoIncrementablesCreate, - PedimentoIncrementablesResponse, -) -from .pedimento_indexes import PedimentoIndexesCreate, PedimentoIndexesResponse -from .pedimento_packages_transport import ( - PedimentoContainerCreate, - PedimentoContainerResponse, - PedimentoPackagesCreate, - PedimentoPackagesResponse, - PedimentoSealCreate, - PedimentoSealResponse, - PedimentoTransportCarrierCreate, - PedimentoTransportCarrierResponse, - PedimentoGuideCreate, - PedimentoGuideResponse, -) -from .pedimento_contributions import ( - PedimentoContributionCreate, - PedimentoContributionResponse, -) -from .pedimento_payments import PedimentoPaymentsCreate, PedimentoPaymentsResponse -from .pedimento_rectification_destination import ( - PedimentoRectificationDestinationCreate, - PedimentoRectificationDestinationResponse, -) -from .pedimento_rectification_origin import ( - PedimentoRectificationOriginCreate, - PedimentoRectificationOriginResponse, -) -from .pedimento_transport_means import ( - PedimentoTransportMeansCreate, - PedimentoTransportMeansResponse, -) -from .pedimento_validation import PedimentoValidationCreate, PedimentoValidationResponse - - -class PedimentosBase(BaseModel): - """Base schema for Pedimentos""" - - year: Optional[str] = Field(None, max_length=2, description="Year") - customs_office: Optional[str] = Field( - None, max_length=3, description="Customs office" - ) - license: Optional[str] = Field(None, max_length=4, description="License") - pedimento_number: Optional[str] = Field( - None, max_length=7, description="Pedimento number" - ) - client_id: Optional[int] = Field(None, description="Client ID") - operation_type: Optional[OperationType] = Field(None, description="Operation type") - pedimento_type: Optional[PedimentoType] = Field(None, description="Pedimento type") - pedimento_code: str = Field(..., max_length=2, description="Pedimento key") - regime: str = Field(..., max_length=3, description="Regime") - status: Optional[str] = Field(None, max_length=30, description="Status") - usd_value: Optional[Decimal] = Field(None, description="USD value") - paid_price: Optional[Decimal] = Field(None, description="Paid price") - gross_weight: Optional[Decimal] = Field(None, description="Gross weight") - exchange_rate: Optional[Decimal] = Field(None, description="Exchange rate") - observations: Optional[str] = Field(None, description="Observations") - - -class PedimentosCreate(PedimentosBase): - """Schema for creating a new Pedimento""" - - # Override to make required fields non-optional - year: str = Field(..., max_length=2, description="Year") - customs_office: str = Field(..., max_length=3, description="Customs office") - license: str = Field(..., max_length=4, description="License") - pedimento_number: str = Field(..., max_length=7, description="Pedimento number") - client_id: Optional[int] = Field(None, description="Client ID (opcional)") - # operation_type, pedimento_type, status son opcionales - se pueden llenar después - pedimento_code: str = Field(..., max_length=2, description="Pedimento key") - regime: str = Field(..., max_length=3, description="Regime") - - pedimento_dates: Optional[PedimentoDatesCreate] = None - pedimento_decrementables: Optional[PedimentoDecrementablesCreate] = None - pedimento_incrementables: Optional[PedimentoIncrementablesCreate] = None - pedimento_indexes: Optional[PedimentoIndexesCreate] = None - pedimento_validation: Optional[PedimentoValidationCreate] = None - pedimento_customs_offices: Optional[PedimentoCustomsOfficesCreate] = None - pedimento_payments: Optional[PedimentoPaymentsCreate] = None - pedimento_rectification_destination: Optional[ - PedimentoRectificationDestinationCreate - ] = None - pedimento_rectification_origin: Optional[PedimentoRectificationOriginCreate] = None - pedimento_transport_means: Optional[PedimentoTransportMeansCreate] = None - pedimento_config_additional: Optional[PedimentoConfigAdditionalCreate] = None - pedimento_config_calculations: Optional[PedimentoConfigCalculationsCreate] = None - pedimento_config_parameters: Optional[PedimentoConfigParametersCreate] = None - pedimento_config_surcharges: Optional[PedimentoConfigSurchargesCreate] = None - pedimento_config_update_rectification: Optional[ - PedimentoConfigUpdateRectificationCreate - ] = None - pedimento_config_updates: Optional[PedimentoConfigUpdatesCreate] = None - pedimento_packages: Optional[PedimentoPackagesCreate] = None - pedimento_transport_carriers: Optional[list[PedimentoTransportCarrierCreate]] = None - pedimento_guides: Optional[list[PedimentoGuideCreate]] = None - pedimento_contributions: Optional[list[PedimentoContributionCreate]] = None - pedimento_seals: Optional[list[PedimentoSealCreate]] = None - pedimento_containers: Optional[list[PedimentoContainerCreate]] = None - - -class PedimentosUpdate(BaseModel): - """Schema for updating a Pedimento""" - - year: Optional[str] = Field(None, max_length=2) - customs_office: Optional[str] = Field(None, max_length=3) - license: Optional[str] = Field(None, max_length=4) - pedimento_number: Optional[str] = Field(None, max_length=7) - client_id: Optional[int] = None - operation_type: Optional[str] = Field(None, max_length=3) - pedimento_type: Optional[str] = Field(None, max_length=20) - pedimento_code: Optional[str] = Field(None, max_length=2) - regime: Optional[str] = Field(None, max_length=3) - status: Optional[str] = Field(None, max_length=30) - usd_value: Optional[Decimal] = None - paid_price: Optional[Decimal] = None - gross_weight: Optional[Decimal] = None - exchange_rate: Optional[Decimal] = None - observations: Optional[str] = None - - # Sub-resources - pedimento_dates: Optional[PedimentoDatesCreate] = None - pedimento_decrementables: Optional[PedimentoDecrementablesCreate] = None - pedimento_incrementables: Optional[PedimentoIncrementablesCreate] = None - pedimento_indexes: Optional[PedimentoIndexesCreate] = None - pedimento_validation: Optional[PedimentoValidationCreate] = None - pedimento_customs_offices: Optional[PedimentoCustomsOfficesCreate] = None - pedimento_payments: Optional[PedimentoPaymentsCreate] = None - pedimento_rectification_destination: Optional[ - PedimentoRectificationDestinationCreate - ] = None - pedimento_rectification_origin: Optional[PedimentoRectificationOriginCreate] = None - pedimento_transport_means: Optional[PedimentoTransportMeansCreate] = None - pedimento_config_additional: Optional[PedimentoConfigAdditionalCreate] = None - pedimento_config_calculations: Optional[PedimentoConfigCalculationsCreate] = None - pedimento_config_parameters: Optional[PedimentoConfigParametersCreate] = None - pedimento_config_surcharges: Optional[PedimentoConfigSurchargesCreate] = None - pedimento_config_update_rectification: Optional[ - PedimentoConfigUpdateRectificationCreate - ] = None - pedimento_config_updates: Optional[PedimentoConfigUpdatesCreate] = None - pedimento_packages: Optional[PedimentoPackagesCreate] = None - pedimento_transport_carriers: Optional[list[PedimentoTransportCarrierCreate]] = None - pedimento_guides: Optional[list[PedimentoGuideCreate]] = None - pedimento_contributions: Optional[list[PedimentoContributionCreate]] = None - pedimento_seals: Optional[list[PedimentoSealCreate]] = None - pedimento_containers: Optional[list[PedimentoContainerCreate]] = None - - -class PedimentosResponse(PedimentosBase): - """Schema for Pedimento response""" - - id: int - tenant_id: int - created_at: datetime - - pedimento_dates: Optional[PedimentoDatesResponse] = None - pedimento_decrementables: Optional[PedimentoDecrementablesResponse] = None - pedimento_incrementables: Optional[PedimentoIncrementablesResponse] = None - pedimento_indexes: Optional[PedimentoIndexesResponse] = None - pedimento_validation: Optional[PedimentoValidationResponse] = None - pedimento_customs_offices: Optional[PedimentoCustomsOfficesResponse] = None - pedimento_payments: Optional[PedimentoPaymentsResponse] = None - pedimento_rectification_destination: Optional[ - PedimentoRectificationDestinationResponse - ] = None - pedimento_rectification_origin: Optional[PedimentoRectificationOriginResponse] = ( - None - ) - pedimento_transport_means: Optional[PedimentoTransportMeansResponse] = None - pedimento_config_additional: Optional[PedimentoConfigAdditionalResponse] = None - pedimento_config_calculations: Optional[PedimentoConfigCalculationsResponse] = None - pedimento_config_parameters: Optional[PedimentoConfigParametersResponse] = None - pedimento_config_surcharges: Optional[PedimentoConfigSurchargesResponse] = None - pedimento_config_update_rectification: Optional[ - PedimentoConfigUpdateRectificationResponse - ] = None - pedimento_config_updates: Optional[PedimentoConfigUpdatesResponse] = None - pedimento_packages: Optional[PedimentoPackagesResponse] = None - pedimento_transport_carriers: Optional[list[PedimentoTransportCarrierResponse]] = None - pedimento_guides: Optional[list[PedimentoGuideResponse]] = None - pedimento_contributions: Optional[list[PedimentoContributionResponse]] = None - pedimento_seals: Optional[list[PedimentoSealResponse]] = None - pedimento_containers: Optional[list[PedimentoContainerResponse]] = None - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/a76/pedmientos/models/__init__.py b/backend/api/v1/modules/a76/pedmientos/models/__init__.py deleted file mode 100644 index 8e5d83ca..00000000 --- a/backend/api/v1/modules/a76/pedmientos/models/__init__.py +++ /dev/null @@ -1,84 +0,0 @@ -"""Models for pedimentos module""" - -# Import all models to ensure they are registered with SQLAlchemy -from api.v1.modules.a76.pedmientos.models.pedimento_config_additional import ( - PedimentoConfigAdditional, -) -from api.v1.modules.a76.pedmientos.models.pedimento_config_calculations import ( - PedimentoConfigCalculations, -) -from api.v1.modules.a76.pedmientos.models.pedimento_config_parameters import ( - PedimentoConfigParameters, -) -from api.v1.modules.a76.pedmientos.models.pedimento_config_surcharges import ( - PedimentoConfigSurcharges, -) -from api.v1.modules.a76.pedmientos.models.pedimento_config_update_rectification import ( - PedimentoConfigUpdateRectification, -) -from api.v1.modules.a76.pedmientos.models.pedimento_config_updates import ( - PedimentoConfigUpdates, -) -from api.v1.modules.a76.pedmientos.models.pedimento_containers import ( - PedimentoContainers, -) -from api.v1.modules.a76.pedmientos.models.pedimento_contributions import ( - PedimentoContributions, -) -from api.v1.modules.a76.pedmientos.models.pedimento_customs_offices import ( - PedimentoCustomsOffices, -) -from api.v1.modules.a76.pedmientos.models.pedimento_dates import PedimentoDates -from api.v1.modules.a76.pedmientos.models.pedimento_decrementables import ( - PedimentoDecrementables, -) -from api.v1.modules.a76.pedmientos.models.pedimento_incrementables import ( - PedimentoIncrementables, -) -from api.v1.modules.a76.pedmientos.models.pedimento_indexes import PedimentoIndexes -from api.v1.modules.a76.pedmientos.models.pedimento_packages import PedimentoPackages -from api.v1.modules.a76.pedmientos.models.pedimento_payments import PedimentoPayments -from api.v1.modules.a76.pedmientos.models.pedimento_guides import PedimentoGuides -from api.v1.modules.a76.pedmientos.models.pedimento_rectification_destination import ( - PedimentoRectificationDestination, -) -from api.v1.modules.a76.pedmientos.models.pedimento_rectification_origin import ( - PedimentoRectificationOrigin, -) -from api.v1.modules.a76.pedmientos.models.pedimento_seals import PedimentoSeals -from api.v1.modules.a76.pedmientos.models.pedimento_transport_carriers import ( - PedimentoTransportCarriers, -) -from api.v1.modules.a76.pedmientos.models.pedimento_transport_means import ( - PedimentoTransportMeans, -) -from api.v1.modules.a76.pedmientos.models.pedimento_validation import ( - PedimentoValidation, -) -from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos - -__all__ = [ - "Pedimentos", - "PedimentoConfigAdditional", - "PedimentoConfigCalculations", - "PedimentoConfigParameters", - "PedimentoConfigSurcharges", - "PedimentoConfigUpdateRectification", - "PedimentoConfigUpdates", - "PedimentoContainers", - "PedimentoContributions", - "PedimentoCustomsOffices", - "PedimentoDates", - "PedimentoDecrementables", - "PedimentoIncrementables", - "PedimentoIndexes", - "PedimentoPackages", - "PedimentoGuides", - "PedimentoPayments", - "PedimentoRectificationDestination", - "PedimentoRectificationOrigin", - "PedimentoSeals", - "PedimentoTransportCarriers", - "PedimentoTransportMeans", - "PedimentoValidation", -] diff --git a/backend/api/v1/modules/a76/pedmientos/models/pedimento_config_additional.py b/backend/api/v1/modules/a76/pedmientos/models/pedimento_config_additional.py deleted file mode 100644 index 7dc3181e..00000000 --- a/backend/api/v1/modules/a76/pedmientos/models/pedimento_config_additional.py +++ /dev/null @@ -1,50 +0,0 @@ -from typing import TYPE_CHECKING, Optional - -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base -from sqlalchemy import ( - Boolean, - Boolean, - ForeignKeyConstraint, - Integer, - PrimaryKeyConstraint, - UniqueConstraint, -) -from sqlalchemy.orm import Mapped, mapped_column, relationship - -if TYPE_CHECKING: - from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos - - -class PedimentoConfigAdditional(Base, TenantScopedMixin, TimestampMixin): - __tablename__ = "pedimento_config_additional" - __table_args__ = ( - PrimaryKeyConstraint("id", name="pedimento_config_additional_pkey"), - ForeignKeyConstraint( - ["pedimento_id"], - ["a76.pedimentos.id"], - ondelete="CASCADE", - name="fk_pedimento_config_additional", - ), - UniqueConstraint( - "tenant_id", - "company_id", - "pedimento_id", - name="pedimento_config_additional_pedimento_id_key", - ), - {"schema": "a76"}, - ) - - id: Mapped[int] = mapped_column(Integer) - pedimento_id: Mapped[int] = mapped_column(Integer, nullable=False) - - add_po_identifier: Mapped[bool] = mapped_column(Boolean) - do_not_exempt_norms_complement_x: Mapped[bool] = mapped_column(Boolean) - manual_pedimento_year: Mapped[Optional[int]] = mapped_column(Integer) - enable_import_invoice_recipient: Mapped[bool] = mapped_column(Boolean) - send_502_validation_file_for_consolidated: Mapped[bool] = mapped_column(Boolean) - add_remove_norms: Mapped[bool] = mapped_column(Boolean) - - pedimento: Mapped["Pedimentos"] = relationship( - "Pedimentos", back_populates="pedimento_config_additional" - ) diff --git a/backend/api/v1/modules/a76/pedmientos/models/pedimento_config_calculations.py b/backend/api/v1/modules/a76/pedmientos/models/pedimento_config_calculations.py deleted file mode 100644 index 3dcb2859..00000000 --- a/backend/api/v1/modules/a76/pedmientos/models/pedimento_config_calculations.py +++ /dev/null @@ -1,55 +0,0 @@ -from typing import TYPE_CHECKING, Optional - -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base -from sqlalchemy import ( - Boolean, - ForeignKeyConstraint, - Integer, - PrimaryKeyConstraint, - SmallInteger, - String, - UniqueConstraint, -) -from sqlalchemy.orm import Mapped, mapped_column, relationship - -if TYPE_CHECKING: - from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos - - -class PedimentoConfigCalculations(Base, TenantScopedMixin, TimestampMixin): - __tablename__ = "pedimento_config_calculations" - __table_args__ = ( - PrimaryKeyConstraint("id", name="pedimento_config_calculations_pkey"), - ForeignKeyConstraint( - ["pedimento_id"], - ["a76.pedimentos.id"], - ondelete="CASCADE", - name="fk_pedimento_config_calculations", - ), - UniqueConstraint( - "tenant_id", - "company_id", - "pedimento_id", - name="pedimento_config_calculations_pedimento_id_key", - ), - {"schema": "a76"}, - ) - - id: Mapped[int] = mapped_column(Integer) - pedimento_id: Mapped[int] = mapped_column(Integer, nullable=False) - - dta_type: Mapped[Optional[str]] = mapped_column(String(1), nullable=True) - dta_operation: Mapped[bool] = mapped_column(Boolean, default=False, server_default='false') - dta_vehicle_count: Mapped[int] = mapped_column(SmallInteger, default=0, server_default='0') - dta_mixed_rate_8permil: Mapped[bool] = mapped_column(Boolean, default=False, server_default='false') - pays_vat: Mapped[bool] = mapped_column(Boolean, default=False, server_default='false') - pays_prevalidation: Mapped[bool] = mapped_column(Boolean, default=False, server_default='false') - include_sagar_certificate_fee: Mapped[bool] = mapped_column(Boolean, default=False, server_default='false') - fixed_vehicle_dta_fee: Mapped[bool] = mapped_column(Boolean, default=False, server_default='false') - additional_fixed_fee: Mapped[int] = mapped_column(SmallInteger, default=0, server_default='0') - additional_fixed_fee_payment_method: Mapped[Optional[int]] = mapped_column(SmallInteger, nullable=True) - - pedimento: Mapped["Pedimentos"] = relationship( - "Pedimentos", back_populates="pedimento_config_calculations" - ) diff --git a/backend/api/v1/modules/a76/pedmientos/models/pedimento_config_parameters.py b/backend/api/v1/modules/a76/pedmientos/models/pedimento_config_parameters.py deleted file mode 100644 index d53bf32b..00000000 --- a/backend/api/v1/modules/a76/pedmientos/models/pedimento_config_parameters.py +++ /dev/null @@ -1,57 +0,0 @@ -from decimal import Decimal -from typing import TYPE_CHECKING - -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base -from sqlalchemy import ( - Boolean, - ForeignKeyConstraint, - Integer, - Numeric, - PrimaryKeyConstraint, - SmallInteger, - UniqueConstraint, -) -from sqlalchemy.orm import Mapped, mapped_column, relationship - -if TYPE_CHECKING: - from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos - - -class PedimentoConfigParameters(Base, TenantScopedMixin, TimestampMixin): - __tablename__ = "pedimento_config_parameters" - __table_args__ = ( - PrimaryKeyConstraint("id", name="pedimento_config_parameters_pkey"), - ForeignKeyConstraint( - ["pedimento_id"], - ["a76.pedimentos.id"], - ondelete="CASCADE", - name="fk_pedimento_config_parameters", - ), - UniqueConstraint( - "tenant_id", - "company_id", - "pedimento_id", - name="pedimento_config_parameters_pedimento_id_key", - ), - {"schema": "a76"}, - ) - - id: Mapped[int] = mapped_column(Integer) - pedimento_id: Mapped[int] = mapped_column(Integer, nullable=False) - - is_embassy: Mapped[bool] = mapped_column(Boolean, default=False, server_default='false') - embassy_dta: Mapped[Decimal] = mapped_column(Numeric(11, 2), default=Decimal('0.00'), server_default='0.00') - rule_3121_section_ii: Mapped[bool] = mapped_column(Boolean, default=False, server_default='false') - use_previous_tariff: Mapped[bool] = mapped_column(Boolean, default=False, server_default='false') - use_payment_date_fi: Mapped[bool] = mapped_column(Boolean, default=False, server_default='false') - add_state_supplier_record_505: Mapped[bool] = mapped_column(Boolean, default=False, server_default='false') - customs_value_calculation: Mapped[bool] = mapped_column(Boolean, default=False, server_default='false') - two_decimals_unit_value: Mapped[bool] = mapped_column(Boolean, default=False, server_default='false') - customs_value_per_item: Mapped[bool] = mapped_column(Boolean, default=False, server_default='false') - is_national_supplier: Mapped[bool] = mapped_column(Boolean, default=False, server_default='false') - is_consolidated: Mapped[bool] = mapped_column(Boolean, default=False, server_default='false') - - pedimento: Mapped["Pedimentos"] = relationship( - "Pedimentos", back_populates="pedimento_config_parameters" - ) diff --git a/backend/api/v1/modules/a76/pedmientos/models/pedimento_config_surcharges.py b/backend/api/v1/modules/a76/pedmientos/models/pedimento_config_surcharges.py deleted file mode 100644 index 6943404a..00000000 --- a/backend/api/v1/modules/a76/pedmientos/models/pedimento_config_surcharges.py +++ /dev/null @@ -1,50 +0,0 @@ -from typing import TYPE_CHECKING - -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base -from sqlalchemy import ( - Boolean, - ForeignKeyConstraint, - Integer, - PrimaryKeyConstraint, - SmallInteger, - UniqueConstraint, -) -from sqlalchemy.orm import Mapped, mapped_column, relationship - -if TYPE_CHECKING: - from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos - - -class PedimentoConfigSurcharges(Base, TenantScopedMixin, TimestampMixin): - __tablename__ = "pedimento_config_surcharges" - __table_args__ = ( - PrimaryKeyConstraint("id", name="pedimento_config_surcharges_pkey"), - ForeignKeyConstraint( - ["pedimento_id"], - ["a76.pedimentos.id"], - ondelete="CASCADE", - name="fk_pedimento_config_surcharges", - ), - UniqueConstraint( - "tenant_id", - "company_id", - "pedimento_id", - name="pedimento_config_surcharges_pedimento_id_key", - ), - {"schema": "a76"}, - ) - - id: Mapped[int] = mapped_column(Integer) - pedimento_id: Mapped[int] = mapped_column(Integer, nullable=False) - - surcharge_igi: Mapped[bool] = mapped_column(Boolean) - surcharge_dta: Mapped[bool] = mapped_column(Boolean) - surcharge_vat: Mapped[bool] = mapped_column(Boolean) - surcharge_isan: Mapped[bool] = mapped_column(Boolean) - surcharge_ieps: Mapped[bool] = mapped_column(Boolean) - surcharge_cc: Mapped[bool] = mapped_column(Boolean) - - pedimento: Mapped["Pedimentos"] = relationship( - "Pedimentos", back_populates="pedimento_config_surcharges" - ) diff --git a/backend/api/v1/modules/a76/pedmientos/models/pedimento_config_update_rectification.py b/backend/api/v1/modules/a76/pedmientos/models/pedimento_config_update_rectification.py deleted file mode 100644 index a6552902..00000000 --- a/backend/api/v1/modules/a76/pedmientos/models/pedimento_config_update_rectification.py +++ /dev/null @@ -1,49 +0,0 @@ -from typing import TYPE_CHECKING - -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base -from sqlalchemy import ( - Boolean, - ForeignKeyConstraint, - Integer, - PrimaryKeyConstraint, - UniqueConstraint, -) -from sqlalchemy.orm import Mapped, mapped_column, relationship - -if TYPE_CHECKING: - from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos - - -class PedimentoConfigUpdateRectification(Base, TenantScopedMixin, TimestampMixin): - __tablename__ = "pedimento_config_update_rectification" - __table_args__ = ( - PrimaryKeyConstraint("id", name="pedimento_config_update_rectification_pkey"), - ForeignKeyConstraint( - ["pedimento_id"], - ["a76.pedimentos.id"], - ondelete="CASCADE", - name="fk_pedimento_config_update_rectification", - ), - UniqueConstraint( - "tenant_id", - "company_id", - "pedimento_id", - name="pedimento_config_update_rectification_pedimento_id_key", - ), - {"schema": "a76"}, - ) - - id: Mapped[int] = mapped_column(Integer) - pedimento_id: Mapped[int] = mapped_column(Integer, nullable=False) - - update_vat: Mapped[bool] = mapped_column(Boolean, default=False, server_default='false') - update_advalorem: Mapped[bool] = mapped_column(Boolean, default=False, server_default='false') - update_dta: Mapped[bool] = mapped_column(Boolean, default=False, server_default='false') - update_cc: Mapped[bool] = mapped_column(Boolean, default=False, server_default='false') - update_ieps: Mapped[bool] = mapped_column(Boolean, default=False, server_default='false') - calculate_surcharge: Mapped[bool] = mapped_column(Boolean, default=False, server_default='false') - - pedimento: Mapped["Pedimentos"] = relationship( - "Pedimentos", back_populates="pedimento_config_update_rectification" - ) diff --git a/backend/api/v1/modules/a76/pedmientos/models/pedimento_config_updates.py b/backend/api/v1/modules/a76/pedmientos/models/pedimento_config_updates.py deleted file mode 100644 index 0e1a5a79..00000000 --- a/backend/api/v1/modules/a76/pedmientos/models/pedimento_config_updates.py +++ /dev/null @@ -1,48 +0,0 @@ -from typing import TYPE_CHECKING - -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base -from sqlalchemy import ( - Boolean, - ForeignKeyConstraint, - Integer, - PrimaryKeyConstraint, - UniqueConstraint, -) -from sqlalchemy.orm import Mapped, mapped_column, relationship - -if TYPE_CHECKING: - from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos - - -class PedimentoConfigUpdates(Base, TenantScopedMixin, TimestampMixin): - __tablename__ = "pedimento_config_updates" - __table_args__ = ( - PrimaryKeyConstraint("id", name="pedimento_config_updates_pkey"), - ForeignKeyConstraint( - ["pedimento_id"], - ["a76.pedimentos.id"], - ondelete="CASCADE", - name="fk_pedimento_config_updates", - ), - UniqueConstraint( - "tenant_id", - "company_id", - "pedimento_id", - name="pedimento_config_updates_pedimento_id_key", - ), - {"schema": "a76"}, - ) - - id: Mapped[int] = mapped_column(Integer) - pedimento_id: Mapped[int] = mapped_column(Integer, nullable=False) - - update_vat: Mapped[bool] = mapped_column(Boolean, default=False, server_default='false') - update_advalorem: Mapped[bool] = mapped_column(Boolean, default=False, server_default='false') - update_dta: Mapped[bool] = mapped_column(Boolean, default=False, server_default='false') - update_cc: Mapped[bool] = mapped_column(Boolean, default=False, server_default='false') - update_ieps: Mapped[bool] = mapped_column(Boolean, default=False, server_default='false') - - pedimento: Mapped["Pedimentos"] = relationship( - "Pedimentos", back_populates="pedimento_config_updates" - ) diff --git a/backend/api/v1/modules/a76/pedmientos/models/pedimento_containers.py b/backend/api/v1/modules/a76/pedmientos/models/pedimento_containers.py deleted file mode 100644 index 702002ae..00000000 --- a/backend/api/v1/modules/a76/pedmientos/models/pedimento_containers.py +++ /dev/null @@ -1,39 +0,0 @@ -from typing import TYPE_CHECKING - -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base -from sqlalchemy import ( - ForeignKeyConstraint, - Integer, - PrimaryKeyConstraint, - String, -) -from sqlalchemy.orm import Mapped, mapped_column, relationship - -if TYPE_CHECKING: - from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos - - -class PedimentoContainers(Base, TenantScopedMixin, TimestampMixin): - __tablename__ = "pedimento_containers" - __table_args__ = ( - PrimaryKeyConstraint("id", name="pedimento_containers_pkey"), - ForeignKeyConstraint( - ["pedimento_id"], - ["a76.pedimentos.id"], - ondelete="CASCADE", - name="fk_pedimento_containers", - ), - {"schema": "a76"}, - ) - - id: Mapped[int] = mapped_column(Integer) - pedimento_id: Mapped[int] = mapped_column(Integer, nullable=False) - - number: Mapped[str | None] = mapped_column(String(100), nullable=True) - identification: Mapped[str | None] = mapped_column(String(100), nullable=True) - type: Mapped[str | None] = mapped_column(String(50), nullable=True) - - pedimento: Mapped["Pedimentos"] = relationship( - "Pedimentos", back_populates="pedimento_containers" - ) diff --git a/backend/api/v1/modules/a76/pedmientos/models/pedimento_contributions.py b/backend/api/v1/modules/a76/pedmientos/models/pedimento_contributions.py deleted file mode 100644 index 8aac377f..00000000 --- a/backend/api/v1/modules/a76/pedmientos/models/pedimento_contributions.py +++ /dev/null @@ -1,46 +0,0 @@ -from typing import TYPE_CHECKING - -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base -from sqlalchemy import ( - ForeignKeyConstraint, - Integer, - Numeric, - PrimaryKeyConstraint, - String, -) -from sqlalchemy.orm import Mapped, mapped_column, relationship - -if TYPE_CHECKING: - from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos - - -class PedimentoContributions(Base, TenantScopedMixin, TimestampMixin): - __tablename__ = "pedimento_contributions" - __table_args__ = ( - PrimaryKeyConstraint("id", name="pedimento_contributions_pkey"), - ForeignKeyConstraint( - ["pedimento_id"], - ["a76.pedimentos.id"], - ondelete="CASCADE", - name="fk_pedimento_contributions", - ), - {"schema": "a76"}, - ) - - id: Mapped[int] = mapped_column(Integer) - pedimento_id: Mapped[int] = mapped_column(Integer, nullable=False) - - contribucion: Mapped[str | None] = mapped_column(String(50), nullable=True) - tipo_tasa: Mapped[str | None] = mapped_column(String(100), nullable=True) - tasa: Mapped[Numeric | None] = mapped_column(Numeric(15, 8), nullable=True) - forma_pago: Mapped[str | None] = mapped_column(String(50), nullable=True) - importe: Mapped[Numeric | None] = mapped_column(Numeric(17, 2), nullable=True) - gravamen: Mapped[str | None] = mapped_column(String(100), nullable=True) - abreviacion: Mapped[str | None] = mapped_column(String(50), nullable=True) - forma_pago_2: Mapped[str | None] = mapped_column(String(50), nullable=True) - importe_2: Mapped[Numeric | None] = mapped_column(Numeric(17, 2), nullable=True) - - pedimento: Mapped["Pedimentos"] = relationship( - "Pedimentos", back_populates="pedimento_contributions" - ) diff --git a/backend/api/v1/modules/a76/pedmientos/models/pedimento_customs_offices.py b/backend/api/v1/modules/a76/pedmientos/models/pedimento_customs_offices.py deleted file mode 100644 index 14decb00..00000000 --- a/backend/api/v1/modules/a76/pedmientos/models/pedimento_customs_offices.py +++ /dev/null @@ -1,45 +0,0 @@ -from typing import TYPE_CHECKING - -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base -from sqlalchemy import ( - ForeignKeyConstraint, - Integer, - PrimaryKeyConstraint, - String, - UniqueConstraint, -) -from sqlalchemy.orm import Mapped, mapped_column, relationship - -if TYPE_CHECKING: - from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos - - -class PedimentoCustomsOffices(Base, TenantScopedMixin, TimestampMixin): - __tablename__ = "pedimento_customs_offices" - __table_args__ = ( - PrimaryKeyConstraint("id", name="pedimento_customs_offices_pkey"), - ForeignKeyConstraint( - ["pedimento_id"], - ["a76.pedimentos.id"], - ondelete="CASCADE", - name="fk_pedimento_customs_offices", - ), - UniqueConstraint( - "tenant_id", - "company_id", - "pedimento_id", - name="pedimento_customs_offices_pedimento_id_key", - ), - {"schema": "a76"}, - ) - - id: Mapped[int] = mapped_column(Integer) - pedimento_id: Mapped[int] = mapped_column(Integer, nullable=False) - - dispatch_customs: Mapped[str] = mapped_column(String(3)) - entry_exit_customs: Mapped[str] = mapped_column(String(3)) - - pedimento: Mapped["Pedimentos"] = relationship( - "Pedimentos", back_populates="pedimento_customs_offices" - ) diff --git a/backend/api/v1/modules/a76/pedmientos/models/pedimento_dates.py b/backend/api/v1/modules/a76/pedmientos/models/pedimento_dates.py deleted file mode 100644 index 2a43a656..00000000 --- a/backend/api/v1/modules/a76/pedmientos/models/pedimento_dates.py +++ /dev/null @@ -1,60 +0,0 @@ -from datetime import datetime -from datetime import time as datetime_time -from typing import TYPE_CHECKING, Optional - -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base -from sqlalchemy import ( - DateTime, - ForeignKeyConstraint, - Index, - Integer, - PrimaryKeyConstraint, - Time, - UniqueConstraint, -) -from sqlalchemy.orm import Mapped, mapped_column, relationship - -if TYPE_CHECKING: - from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos - - -class PedimentoDates(Base, TenantScopedMixin, TimestampMixin): - __tablename__ = "pedimento_dates" - __table_args__ = ( - PrimaryKeyConstraint("id", name="pedimento_dates_pkey"), - ForeignKeyConstraint( - ["pedimento_id"], - ["a76.pedimentos.id"], - ondelete="CASCADE", - name="fk_pedimento_dates", - ), - UniqueConstraint( - "tenant_id", - "company_id", - "pedimento_id", - name="pedimento_dates_pedimento_id_key", - ), - Index("idx_pedimento_dates_pedimento_id", "pedimento_id"), - {"schema": "a76"}, - ) - - id: Mapped[int] = mapped_column(Integer) - pedimento_id: Mapped[int] = mapped_column(Integer, nullable=False) - - entry_date: Mapped[datetime] = mapped_column(DateTime) - pedimento_date: Mapped[Optional[datetime]] = mapped_column(DateTime) - payment_date: Mapped[Optional[datetime]] = mapped_column(DateTime) - rectification_payment_date: Mapped[Optional[datetime]] = mapped_column(DateTime) - extraction_date: Mapped[Optional[datetime]] = mapped_column(DateTime) - submission_date: Mapped[Optional[datetime]] = mapped_column(DateTime) - eucan_date: Mapped[Optional[datetime]] = mapped_column(DateTime) - original_date: Mapped[Optional[datetime]] = mapped_column(DateTime) - start_date: Mapped[Optional[datetime]] = mapped_column(DateTime) - end_date: Mapped[datetime] = mapped_column(DateTime) - - capture_time: Mapped[datetime_time] = mapped_column(Time) - - pedimento: Mapped["Pedimentos"] = relationship( - "Pedimentos", back_populates="pedimento_dates" - ) diff --git a/backend/api/v1/modules/a76/pedmientos/models/pedimento_decrementables.py b/backend/api/v1/modules/a76/pedmientos/models/pedimento_decrementables.py deleted file mode 100644 index 27ecda48..00000000 --- a/backend/api/v1/modules/a76/pedmientos/models/pedimento_decrementables.py +++ /dev/null @@ -1,55 +0,0 @@ -from decimal import Decimal -from typing import TYPE_CHECKING, Optional - -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base -from sqlalchemy import ( - ForeignKeyConstraint, - Integer, - Numeric, - PrimaryKeyConstraint, - SmallInteger, - String, - UniqueConstraint, -) -from sqlalchemy.orm import Mapped, mapped_column, relationship - -if TYPE_CHECKING: - from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos - - -class PedimentoDecrementables(Base, TenantScopedMixin, TimestampMixin): - __tablename__ = "pedimento_decrementables" - __table_args__ = ( - PrimaryKeyConstraint("id", name="pedimento_decrementables_pkey"), - ForeignKeyConstraint( - ["pedimento_id"], - ["a76.pedimentos.id"], - ondelete="CASCADE", - name="fk_pedimento_decrementables", - ), - UniqueConstraint( - "tenant_id", - "company_id", - "pedimento_id", - name="pedimento_decrementables_pedimento_id_key", - ), - {"schema": "a76"}, - ) - - id: Mapped[int] = mapped_column(Integer) - pedimento_id: Mapped[int] = mapped_column(Integer, nullable=False) - - freight: Mapped[Optional[Decimal]] = mapped_column(Numeric(13, 2)) - insurance: Mapped[Optional[Decimal]] = mapped_column(Numeric(13, 2)) - loading: Mapped[Optional[Decimal]] = mapped_column(Numeric(13, 2)) - unloading: Mapped[Optional[Decimal]] = mapped_column(Numeric(13, 2)) - others: Mapped[Optional[Decimal]] = mapped_column(Numeric(13, 2)) - currency: Mapped[Optional[str]] = mapped_column(String(3)) - currency_factor: Mapped[Optional[Decimal]] = mapped_column(Numeric(15, 8)) - not_affect_usd_value: Mapped[Optional[int]] = mapped_column(SmallInteger) - not_affect_customs_value: Mapped[Optional[int]] = mapped_column(SmallInteger) - - pedimento: Mapped["Pedimentos"] = relationship( - "Pedimentos", back_populates="pedimento_decrementables" - ) diff --git a/backend/api/v1/modules/a76/pedmientos/models/pedimento_guides.py b/backend/api/v1/modules/a76/pedmientos/models/pedimento_guides.py deleted file mode 100644 index 23e14a2f..00000000 --- a/backend/api/v1/modules/a76/pedmientos/models/pedimento_guides.py +++ /dev/null @@ -1,38 +0,0 @@ -from typing import TYPE_CHECKING - -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base -from sqlalchemy import ( - ForeignKeyConstraint, - Integer, - PrimaryKeyConstraint, - String, -) -from sqlalchemy.orm import Mapped, mapped_column, relationship - -if TYPE_CHECKING: - from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos - - -class PedimentoGuides(Base, TenantScopedMixin, TimestampMixin): - __tablename__ = "pedimento_guides" - __table_args__ = ( - PrimaryKeyConstraint("id", name="pedimento_guides_pkey"), - ForeignKeyConstraint( - ["pedimento_id"], - ["a76.pedimentos.id"], - ondelete="CASCADE", - name="fk_pedimento_guides", - ), - {"schema": "a76"}, - ) - - id: Mapped[int] = mapped_column(Integer) - pedimento_id: Mapped[int] = mapped_column(Integer, nullable=False) - - guide: Mapped[str | None] = mapped_column(String(100), nullable=True) - identifier: Mapped[str | None] = mapped_column(String(100), nullable=True) - - pedimento: Mapped["Pedimentos"] = relationship( - "Pedimentos", back_populates="pedimento_guides" - ) diff --git a/backend/api/v1/modules/a76/pedmientos/models/pedimento_incrementables.py b/backend/api/v1/modules/a76/pedmientos/models/pedimento_incrementables.py deleted file mode 100644 index 04a40423..00000000 --- a/backend/api/v1/modules/a76/pedmientos/models/pedimento_incrementables.py +++ /dev/null @@ -1,56 +0,0 @@ -from decimal import Decimal -from typing import TYPE_CHECKING, Optional - -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base -from sqlalchemy import ( - ForeignKeyConstraint, - Integer, - Numeric, - PrimaryKeyConstraint, - SmallInteger, - String, - UniqueConstraint, -) -from sqlalchemy.orm import Mapped, mapped_column, relationship - -if TYPE_CHECKING: - from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos - - -class PedimentoIncrementables(Base, TenantScopedMixin, TimestampMixin): - __tablename__ = "pedimento_incrementables" - __table_args__ = ( - PrimaryKeyConstraint("id", name="pedimento_incrementables_pkey"), - ForeignKeyConstraint( - ["pedimento_id"], - ["a76.pedimentos.id"], - ondelete="CASCADE", - name="fk_pedimento_incrementables", - ), - UniqueConstraint( - "tenant_id", - "company_id", - "pedimento_id", - name="pedimento_incrementables_pedimento_id_key", - ), - {"schema": "a76"}, - ) - - id: Mapped[int] = mapped_column(Integer) - pedimento_id: Mapped[int] = mapped_column(Integer, nullable=False) - - insured_value: Mapped[Optional[Decimal]] = mapped_column(Numeric(13, 2)) - freight: Mapped[Optional[Decimal]] = mapped_column(Numeric(13, 2)) - insurance: Mapped[Optional[Decimal]] = mapped_column(Numeric(13, 2)) - packaging: Mapped[Optional[Decimal]] = mapped_column(Numeric(13, 2)) - others: Mapped[Optional[Decimal]] = mapped_column(Numeric(13, 3)) - deductibles: Mapped[Optional[Decimal]] = mapped_column(Numeric(13, 3)) - currency: Mapped[Optional[str]] = mapped_column(String(3)) - currency_factor: Mapped[Optional[Decimal]] = mapped_column(Numeric(15, 8)) - not_affect_usd_value: Mapped[Optional[int]] = mapped_column(SmallInteger) - not_affect_customs_value: Mapped[Optional[int]] = mapped_column(SmallInteger) - - pedimento: Mapped["Pedimentos"] = relationship( - "Pedimentos", back_populates="pedimento_incrementables" - ) diff --git a/backend/api/v1/modules/a76/pedmientos/models/pedimento_indexes.py b/backend/api/v1/modules/a76/pedmientos/models/pedimento_indexes.py deleted file mode 100644 index 4bfb8e91..00000000 --- a/backend/api/v1/modules/a76/pedmientos/models/pedimento_indexes.py +++ /dev/null @@ -1,48 +0,0 @@ -from decimal import Decimal -from typing import TYPE_CHECKING, Optional - -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base -from sqlalchemy import ( - ForeignKeyConstraint, - Integer, - Numeric, - PrimaryKeyConstraint, - SmallInteger, - UniqueConstraint, -) -from sqlalchemy.orm import Mapped, mapped_column, relationship - -if TYPE_CHECKING: - from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos - - -class PedimentoIndexes(Base, TenantScopedMixin, TimestampMixin): - __tablename__ = "pedimento_indexes" - __table_args__ = ( - PrimaryKeyConstraint("id", name="pedimento_indexes_pkey"), - ForeignKeyConstraint( - ["pedimento_id"], - ["a76.pedimentos.id"], - ondelete="CASCADE", - name="fk_pedimento_indexes", - ), - UniqueConstraint( - "tenant_id", - "company_id", - "pedimento_id", - name="pedimento_indexes_pedimento_id_key", - ), - {"schema": "a76"}, - ) - - id: Mapped[int] = mapped_column(Integer) - pedimento_id: Mapped[int] = mapped_column(Integer, nullable=False) - - update_factor_type: Mapped[Optional[int]] = mapped_column(SmallInteger) - update_factor: Mapped[Optional[Decimal]] = mapped_column(Numeric(7, 4)) - manual_update_factor: Mapped[Optional[int]] = mapped_column(SmallInteger) - - pedimento: Mapped["Pedimentos"] = relationship( - "Pedimentos", back_populates="pedimento_indexes" - ) diff --git a/backend/api/v1/modules/a76/pedmientos/models/pedimento_packages.py b/backend/api/v1/modules/a76/pedmientos/models/pedimento_packages.py deleted file mode 100644 index 4b313d0d..00000000 --- a/backend/api/v1/modules/a76/pedmientos/models/pedimento_packages.py +++ /dev/null @@ -1,48 +0,0 @@ -from typing import TYPE_CHECKING - -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base -from sqlalchemy import ( - ForeignKeyConstraint, - Integer, - Numeric, - PrimaryKeyConstraint, - String, - UniqueConstraint, -) -from sqlalchemy.orm import Mapped, mapped_column, relationship - -if TYPE_CHECKING: - from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos - - -class PedimentoPackages(Base, TenantScopedMixin, TimestampMixin): - __tablename__ = "pedimento_packages" - __table_args__ = ( - PrimaryKeyConstraint("id", name="pedimento_packages_pkey"), - ForeignKeyConstraint( - ["pedimento_id"], - ["a76.pedimentos.id"], - ondelete="CASCADE", - name="fk_pedimento_packages", - ), - UniqueConstraint( - "tenant_id", - "company_id", - "pedimento_id", - name="pedimento_packages_pedimento_id_key", - ), - {"schema": "a76"}, - ) - - id: Mapped[int] = mapped_column(Integer) - pedimento_id: Mapped[int] = mapped_column(Integer, nullable=False) - - quantity: Mapped[int | None] = mapped_column(Integer, nullable=True) - brand: Mapped[str | None] = mapped_column(String(100), nullable=True) - number: Mapped[str | None] = mapped_column(String(100), nullable=True) - vehicles: Mapped[int | None] = mapped_column(Integer, nullable=True) - - pedimento: Mapped["Pedimentos"] = relationship( - "Pedimentos", back_populates="pedimento_packages" - ) diff --git a/backend/api/v1/modules/a76/pedmientos/models/pedimento_payments.py b/backend/api/v1/modules/a76/pedmientos/models/pedimento_payments.py deleted file mode 100644 index faad1622..00000000 --- a/backend/api/v1/modules/a76/pedmientos/models/pedimento_payments.py +++ /dev/null @@ -1,61 +0,0 @@ -from datetime import date as Date2 -from datetime import time as Time2 -from typing import TYPE_CHECKING - -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base -from sqlalchemy import ( - Date, - ForeignKeyConstraint, - Index, - Integer, - PrimaryKeyConstraint, - SmallInteger, - String, - Time, - UniqueConstraint, -) -from sqlalchemy.orm import Mapped, mapped_column, relationship - -if TYPE_CHECKING: - from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos - - -class PedimentoPayments(Base, TenantScopedMixin, TimestampMixin): - __tablename__ = "pedimento_payments" - __table_args__ = ( - PrimaryKeyConstraint("id", name="pedimento_payments_pkey"), - ForeignKeyConstraint( - ["pedimento_id"], - ["a76.pedimentos.id"], - ondelete="CASCADE", - name="fk_pedimento_payments", - ), - UniqueConstraint( - "tenant_id", - "company_id", - "pedimento_id", - name="pedimento_payments_pedimento_id_key", - ), - Index("idx_pedimento_payments_pedimento_id", "pedimento_id"), - {"schema": "a76"}, - ) - - id: Mapped[int] = mapped_column(Integer) - pedimento_id: Mapped[int] = mapped_column(Integer, nullable=False) - - acknowledgment: Mapped[str] = mapped_column(String(20)) - operation_number: Mapped[str] = mapped_column(String(14)) - bank_code: Mapped[int] = mapped_column(Integer) - cashier: Mapped[str] = mapped_column(String(2)) - date: Mapped[Date2] = mapped_column(Date) - time: Mapped[Time2] = mapped_column(Time) - shift: Mapped[str] = mapped_column(String(1)) - total_cash_paid: Mapped[int] = mapped_column(Integer) - total_contributions: Mapped[int] = mapped_column(Integer) - counter_payment: Mapped[int] = mapped_column(SmallInteger) - pece_code: Mapped[str] = mapped_column(String(5)) - - pedimento: Mapped["Pedimentos"] = relationship( - "Pedimentos", back_populates="pedimento_payments" - ) diff --git a/backend/api/v1/modules/a76/pedmientos/models/pedimento_rectification_destination.py b/backend/api/v1/modules/a76/pedmientos/models/pedimento_rectification_destination.py deleted file mode 100644 index 9b9928c2..00000000 --- a/backend/api/v1/modules/a76/pedmientos/models/pedimento_rectification_destination.py +++ /dev/null @@ -1,47 +0,0 @@ -from typing import TYPE_CHECKING - -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base -from sqlalchemy import ( - ForeignKeyConstraint, - Integer, - PrimaryKeyConstraint, - String, - UniqueConstraint, -) -from sqlalchemy.orm import Mapped, mapped_column, relationship - -if TYPE_CHECKING: - from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos - - -class PedimentoRectificationDestination(Base, TenantScopedMixin, TimestampMixin): - __tablename__ = "pedimento_rectification_destination" - __table_args__ = ( - PrimaryKeyConstraint("id", name="pedimento_rectification_destination_pkey"), - ForeignKeyConstraint( - ["pedimento_id"], - ["a76.pedimentos.id"], - ondelete="CASCADE", - name="fk_pedimento_rectification_destination", - ), - UniqueConstraint( - "tenant_id", - "company_id", - "pedimento_id", - name="pedimento_rectification_destination_pedimento_id_key", - ), - {"schema": "a76"}, - ) - - id: Mapped[int] = mapped_column(Integer) - pedimento_id: Mapped[int] = mapped_column(Integer, nullable=False) - - destination_pedimento_year: Mapped[str] = mapped_column(String(2)) - destination_customs_office: Mapped[str] = mapped_column(String(3)) - destination_license: Mapped[str] = mapped_column(String(4)) - destination_pedimento_number: Mapped[str] = mapped_column(String(7)) - - pedimento: Mapped["Pedimentos"] = relationship( - "Pedimentos", back_populates="pedimento_rectification_destination" - ) diff --git a/backend/api/v1/modules/a76/pedmientos/models/pedimento_rectification_origin.py b/backend/api/v1/modules/a76/pedmientos/models/pedimento_rectification_origin.py deleted file mode 100644 index 66f92514..00000000 --- a/backend/api/v1/modules/a76/pedmientos/models/pedimento_rectification_origin.py +++ /dev/null @@ -1,61 +0,0 @@ -from datetime import datetime -from typing import TYPE_CHECKING - -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base -from sqlalchemy import ( - DateTime, - ForeignKeyConstraint, - Integer, - PrimaryKeyConstraint, - SmallInteger, - String, - UniqueConstraint, -) -from sqlalchemy.orm import Mapped, mapped_column, relationship - -if TYPE_CHECKING: - from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos - - -class PedimentoRectificationOrigin(Base, TenantScopedMixin, TimestampMixin): - __tablename__ = "pedimento_rectification_origin" - __table_args__ = ( - PrimaryKeyConstraint("id", name="pedimento_rectification_origin_pkey"), - ForeignKeyConstraint( - ["pedimento_id"], - ["a76.pedimentos.id"], - ondelete="CASCADE", - name="fk_pedimento_rectification_origin", - ), - UniqueConstraint( - "tenant_id", - "company_id", - "pedimento_id", - name="pedimento_rectification_origin_pedimento_id_key", - ), - {"schema": "a76"}, - ) - - id: Mapped[int] = mapped_column(Integer) - pedimento_id: Mapped[int] = mapped_column(Integer, nullable=False) - - original_pedimento_year: Mapped[str | None] = mapped_column(String(2), nullable=True) - original_customs_office: Mapped[str | None] = mapped_column(String(3), nullable=True) - original_license: Mapped[str | None] = mapped_column(String(4), nullable=True) - original_pedimento_number: Mapped[str | None] = mapped_column(String(7), nullable=True) - original_pedimento_code: Mapped[str | None] = mapped_column(String(2), nullable=True) - original_payment_date: Mapped[datetime | None] = mapped_column(DateTime, nullable=True) - total_cash: Mapped[int | None] = mapped_column(Integer, nullable=True) - total_others: Mapped[int | None] = mapped_column(Integer, nullable=True) - reason: Mapped[str | None] = mapped_column(String(255), nullable=True) - charge_to_client: Mapped[int | None] = mapped_column(SmallInteger, nullable=True) - use_original_payment_date_for_interest_calc: Mapped[int | None] = mapped_column( - SmallInteger, nullable=True - ) - manual_calculation: Mapped[int | None] = mapped_column(SmallInteger, nullable=True) - original_pedimento_norms: Mapped[int | None] = mapped_column(SmallInteger, nullable=True) - - pedimento: Mapped["Pedimentos"] = relationship( - "Pedimentos", back_populates="pedimento_rectification_origin" - ) diff --git a/backend/api/v1/modules/a76/pedmientos/models/pedimento_seals.py b/backend/api/v1/modules/a76/pedmientos/models/pedimento_seals.py deleted file mode 100644 index aa5d3199..00000000 --- a/backend/api/v1/modules/a76/pedmientos/models/pedimento_seals.py +++ /dev/null @@ -1,38 +0,0 @@ -from typing import TYPE_CHECKING - -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base -from sqlalchemy import ( - ForeignKeyConstraint, - Integer, - PrimaryKeyConstraint, - String, -) -from sqlalchemy.orm import Mapped, mapped_column, relationship - -if TYPE_CHECKING: - from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos - - -class PedimentoSeals(Base, TenantScopedMixin, TimestampMixin): - __tablename__ = "pedimento_seals" - __table_args__ = ( - PrimaryKeyConstraint("id", name="pedimento_seals_pkey"), - ForeignKeyConstraint( - ["pedimento_id"], - ["a76.pedimentos.id"], - ondelete="CASCADE", - name="fk_pedimento_seals", - ), - {"schema": "a76"}, - ) - - id: Mapped[int] = mapped_column(Integer) - pedimento_id: Mapped[int] = mapped_column(Integer, nullable=False) - - number: Mapped[str | None] = mapped_column(String(100), nullable=True) - identification: Mapped[str | None] = mapped_column(String(100), nullable=True) - - pedimento: Mapped["Pedimentos"] = relationship( - "Pedimentos", back_populates="pedimento_seals" - ) diff --git a/backend/api/v1/modules/a76/pedmientos/models/pedimento_transport_carriers.py b/backend/api/v1/modules/a76/pedmientos/models/pedimento_transport_carriers.py deleted file mode 100644 index 63396a33..00000000 --- a/backend/api/v1/modules/a76/pedmientos/models/pedimento_transport_carriers.py +++ /dev/null @@ -1,47 +0,0 @@ -from typing import TYPE_CHECKING - -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base -from sqlalchemy import ( - ForeignKeyConstraint, - Integer, - PrimaryKeyConstraint, - String, -) -from sqlalchemy.orm import Mapped, mapped_column, relationship - -if TYPE_CHECKING: - from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos - - -class PedimentoTransportCarriers(Base, TenantScopedMixin, TimestampMixin): - __tablename__ = "pedimento_transport_carriers" - __table_args__ = ( - PrimaryKeyConstraint("id", name="pedimento_transport_carriers_pkey"), - ForeignKeyConstraint( - ["pedimento_id"], - ["a76.pedimentos.id"], - ondelete="CASCADE", - name="fk_pedimento_transport_carriers", - ), - {"schema": "a76"}, - ) - - id: Mapped[int] = mapped_column(Integer) - pedimento_id: Mapped[int] = mapped_column(Integer, nullable=False) - - carrier: Mapped[str | None] = mapped_column(String(200), nullable=True) - rfc: Mapped[str | None] = mapped_column(String(20), nullable=True) - curp: Mapped[str | None] = mapped_column(String(20), nullable=True) - name: Mapped[str | None] = mapped_column(String(200), nullable=True) - address: Mapped[str | None] = mapped_column(String(300), nullable=True) - city: Mapped[str | None] = mapped_column(String(100), nullable=True) - state: Mapped[str | None] = mapped_column(String(100), nullable=True) - country: Mapped[str | None] = mapped_column(String(3), nullable=True) - tax_id: Mapped[str | None] = mapped_column(String(50), nullable=True) - total_packages: Mapped[int | None] = mapped_column(Integer, nullable=True) - identification: Mapped[str | None] = mapped_column(String(100), nullable=True) - - pedimento: Mapped["Pedimentos"] = relationship( - "Pedimentos", back_populates="pedimento_transport_carriers" - ) diff --git a/backend/api/v1/modules/a76/pedmientos/models/pedimento_transport_means.py b/backend/api/v1/modules/a76/pedmientos/models/pedimento_transport_means.py deleted file mode 100644 index a1702aa2..00000000 --- a/backend/api/v1/modules/a76/pedmientos/models/pedimento_transport_means.py +++ /dev/null @@ -1,48 +0,0 @@ -from typing import TYPE_CHECKING - -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base -from sqlalchemy import ( - ForeignKeyConstraint, - Integer, - PrimaryKeyConstraint, - SmallInteger, - String, - UniqueConstraint, -) -from sqlalchemy.orm import Mapped, mapped_column, relationship - -if TYPE_CHECKING: - from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos - - -class PedimentoTransportMeans(Base, TenantScopedMixin, TimestampMixin): - __tablename__ = "pedimento_transport_means" - __table_args__ = ( - PrimaryKeyConstraint("id", name="pedimento_transport_means_pkey"), - ForeignKeyConstraint( - ["pedimento_id"], - ["a76.pedimentos.id"], - ondelete="CASCADE", - name="fk_pedimento_transport_means", - ), - UniqueConstraint( - "tenant_id", - "company_id", - "pedimento_id", - name="pedimento_transport_means_pedimento_id_key", - ), - {"schema": "a76"}, - ) - - id: Mapped[int] = mapped_column(Integer) - pedimento_id: Mapped[int] = mapped_column(Integer, nullable=False) - - destination: Mapped[int] = mapped_column(SmallInteger) - entry_exit: Mapped[str] = mapped_column(String(3)) - arrival: Mapped[str] = mapped_column(String(3)) - departure: Mapped[str] = mapped_column(String(3)) - - pedimento: Mapped["Pedimentos"] = relationship( - "Pedimentos", back_populates="pedimento_transport_means" - ) diff --git a/backend/api/v1/modules/a76/pedmientos/models/pedimento_validation.py b/backend/api/v1/modules/a76/pedmientos/models/pedimento_validation.py deleted file mode 100644 index 4a5a98f5..00000000 --- a/backend/api/v1/modules/a76/pedmientos/models/pedimento_validation.py +++ /dev/null @@ -1,51 +0,0 @@ -from typing import TYPE_CHECKING - -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base -from sqlalchemy import ( - ForeignKeyConstraint, - Integer, - PrimaryKeyConstraint, - String, - UniqueConstraint, -) -from sqlalchemy.orm import Mapped, mapped_column, relationship - -if TYPE_CHECKING: - from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos - - -class PedimentoValidation(Base, TenantScopedMixin, TimestampMixin): - __tablename__ = "pedimento_validation" # PedimentoValidacion - __table_args__ = ( - PrimaryKeyConstraint("id", name="pedimento_validation_pkey"), - ForeignKeyConstraint( - ["pedimento_id"], - ["a76.pedimentos.id"], - ondelete="CASCADE", - name="fk_pedimento_validation", - ), - UniqueConstraint( - "tenant_id", - "company_id", - "pedimento_id", - name="pedimento_validation_pedimento_id_key", - ), - {"schema": "a76"}, - ) - - id: Mapped[int] = mapped_column(Integer) - pedimento_id: Mapped[int] = mapped_column(Integer, nullable=False) - - validator: Mapped[str] = mapped_column(String(3)) # validador - validation_ack: Mapped[str] = mapped_column(String(8)) # acuse_validacion - pre_ack: Mapped[str] = mapped_column(String(8)) # acuse_previo - line_signature: Mapped[str] = mapped_column(String(50)) # firma_linea_captura - electronic_signature: Mapped[str] = mapped_column(String(999)) # firma_electronica - certificate_number: Mapped[str] = mapped_column(String(99)) # numero_certificado - validator_id: Mapped[int] = mapped_column(Integer) - responsible_id: Mapped[int] = mapped_column(Integer) - - pedimento: Mapped["Pedimentos"] = relationship( - "Pedimentos", back_populates="pedimento_validation" - ) diff --git a/backend/api/v1/modules/a76/pedmientos/models/pedimentos.py b/backend/api/v1/modules/a76/pedmientos/models/pedimentos.py deleted file mode 100644 index 4a29b11e..00000000 --- a/backend/api/v1/modules/a76/pedmientos/models/pedimentos.py +++ /dev/null @@ -1,265 +0,0 @@ -from decimal import Decimal -from enum import Enum -from typing import TYPE_CHECKING, Optional - -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base -from sqlalchemy import ( - ForeignKeyConstraint, - Index, - Integer, - Numeric, - PrimaryKeyConstraint, - String, - Text, - UniqueConstraint, -) -from sqlalchemy.orm import Mapped, mapped_column, relationship - -if TYPE_CHECKING: - from api.v1.modules.a76.pedmientos.models.pedimento_config_additional import ( - PedimentoConfigAdditional, - ) - from api.v1.modules.a76.pedmientos.models.pedimento_config_calculations import ( - PedimentoConfigCalculations, - ) - from api.v1.modules.a76.pedmientos.models.pedimento_config_parameters import ( - PedimentoConfigParameters, - ) - from api.v1.modules.a76.pedmientos.models.pedimento_config_surcharges import ( - PedimentoConfigSurcharges, - ) - from api.v1.modules.a76.pedmientos.models.pedimento_config_update_rectification import ( - PedimentoConfigUpdateRectification, - ) - from api.v1.modules.a76.pedmientos.models.pedimento_config_updates import ( - PedimentoConfigUpdates, - ) - from api.v1.modules.a76.pedmientos.models.pedimento_containers import ( - PedimentoContainers, - ) - from api.v1.modules.a76.pedmientos.models.pedimento_customs_offices import ( - PedimentoCustomsOffices, - ) - from api.v1.modules.a76.pedmientos.models.pedimento_dates import PedimentoDates - from api.v1.modules.a76.pedmientos.models.pedimento_decrementables import ( - PedimentoDecrementables, - ) - from api.v1.modules.a76.pedmientos.models.pedimento_incrementables import ( - PedimentoIncrementables, - ) - from api.v1.modules.a76.pedmientos.models.pedimento_indexes import PedimentoIndexes - from api.v1.modules.a76.pedmientos.models.pedimento_packages import ( - PedimentoPackages, - ) - from api.v1.modules.a76.pedmientos.models.pedimento_guides import ( - PedimentoGuides, - ) - from api.v1.modules.a76.pedmientos.models.pedimento_contributions import ( - PedimentoContributions, - ) - from api.v1.modules.a76.pedmientos.models.pedimento_payments import ( - PedimentoPayments, - ) - from api.v1.modules.a76.pedmientos.models.pedimento_rectification_destination import ( - PedimentoRectificationDestination, - ) - from api.v1.modules.a76.pedmientos.models.pedimento_rectification_origin import ( - PedimentoRectificationOrigin, - ) - from api.v1.modules.a76.pedmientos.models.pedimento_seals import ( - PedimentoSeals, - ) - from api.v1.modules.a76.pedmientos.models.pedimento_transport_carriers import ( - PedimentoTransportCarriers, - ) - from api.v1.modules.a76.pedmientos.models.pedimento_transport_means import ( - PedimentoTransportMeans, - ) - from api.v1.modules.a76.pedmientos.models.pedimento_validation import ( - PedimentoValidation, - ) - - -class PedimentoType(str, Enum): - NORMAL = "normal" - CONSOLIDATED = "consolidated" - COMPLEMENTARY = "complementary" - AUTOMOBILE = "automobile" - -class OperationType(str, Enum): - IMP = "imp" # Importación - EXP = "exp" # Exportación - - -class Pedimentos(Base, TenantScopedMixin, TimestampMixin): - __tablename__ = "pedimentos" - __table_args__ = ( - PrimaryKeyConstraint("id", name="pedimentos_pkey"), - ForeignKeyConstraint( - ["client_id"], ["a76.clients_and_providers.id"], name="fk_pedimentos_client" - ), - ForeignKeyConstraint( - ["regime"], ["public.pedimento_regimens.code"], name="fk_pedimentos_regime" - ), - ForeignKeyConstraint( - ["pedimento_code"], - ["public.pedimento_codes.code"], - name="fk_pedimentos_code", - ), - UniqueConstraint( - "tenant_id", - "company_id", - "year", - "customs_office", - "license", - "pedimento_number", - name="pedimentos_unique_key", - ), - Index("idx_pedimentos_client_id", "client_id"), - Index("idx_pedimentos_created_at", "created_at"), - Index("idx_pedimentos_status", "status"), - {"schema": "a76"}, - ) - - id: Mapped[int] = mapped_column(Integer) - - year: Mapped[str] = mapped_column(String(2)) - customs_office: Mapped[str] = mapped_column(String(3)) - license: Mapped[str] = mapped_column(String(4)) - pedimento_number: Mapped[str] = mapped_column(String(7)) - client_id: Mapped[Optional[int]] = mapped_column( - Integer, nullable=True - ) # Opcional: no requerido para el pedimento - operation_type: Mapped[OperationType] = mapped_column(String(3)) - pedimento_type: Mapped[PedimentoType] = mapped_column(String(20)) - pedimento_code: Mapped[str] = mapped_column(String(2)) - regime: Mapped[str] = mapped_column(String(3)) - status: Mapped[Optional[str]] = mapped_column(String(30)) - usd_value: Mapped[Optional[Decimal]] = mapped_column(Numeric(17, 6)) - paid_price: Mapped[Optional[Decimal]] = mapped_column(Numeric(17, 6)) - gross_weight: Mapped[Optional[Decimal]] = mapped_column(Numeric(19, 3)) - exchange_rate: Mapped[Optional[Decimal]] = mapped_column(Numeric(9, 5)) - observations: Mapped[Optional[str]] = mapped_column(Text) - - pedimento_config_additional: Mapped["PedimentoConfigAdditional"] = relationship( - "PedimentoConfigAdditional", - uselist=False, - back_populates="pedimento", - cascade="all, delete-orphan", - ) - pedimento_config_calculations: Mapped["PedimentoConfigCalculations"] = relationship( - "PedimentoConfigCalculations", - uselist=False, - back_populates="pedimento", - cascade="all, delete-orphan", - ) - pedimento_config_parameters: Mapped["PedimentoConfigParameters"] = relationship( - "PedimentoConfigParameters", - uselist=False, - back_populates="pedimento", - cascade="all, delete-orphan", - ) - pedimento_config_surcharges: Mapped["PedimentoConfigSurcharges"] = relationship( - "PedimentoConfigSurcharges", - uselist=False, - back_populates="pedimento", - cascade="all, delete-orphan", - ) - pedimento_config_update_rectification: Mapped[ - "PedimentoConfigUpdateRectification" - ] = relationship( - "PedimentoConfigUpdateRectification", - uselist=False, - back_populates="pedimento", - cascade="all, delete-orphan", - ) - pedimento_config_updates: Mapped["PedimentoConfigUpdates"] = relationship( - "PedimentoConfigUpdates", - uselist=False, - back_populates="pedimento", - cascade="all, delete-orphan", - ) - pedimento_customs_offices: Mapped["PedimentoCustomsOffices"] = relationship( - "PedimentoCustomsOffices", - uselist=False, - back_populates="pedimento", - cascade="all, delete-orphan", - ) - pedimento_dates: Mapped["PedimentoDates"] = relationship( - "PedimentoDates", - uselist=False, - back_populates="pedimento", - cascade="all, delete-orphan", - lazy="joined", - ) - pedimento_decrementables: Mapped["PedimentoDecrementables"] = relationship( - "PedimentoDecrementables", - uselist=False, - back_populates="pedimento", - cascade="all, delete-orphan", - ) - pedimento_incrementables: Mapped["PedimentoIncrementables"] = relationship( - "PedimentoIncrementables", - uselist=False, - back_populates="pedimento", - cascade="all, delete-orphan", - ) - pedimento_indexes: Mapped["PedimentoIndexes"] = relationship( - "PedimentoIndexes", - uselist=False, - back_populates="pedimento", - cascade="all, delete-orphan", - ) - pedimento_payments: Mapped["PedimentoPayments"] = relationship( - "PedimentoPayments", - uselist=False, - back_populates="pedimento", - cascade="all, delete-orphan", - ) - pedimento_rectification_destination: Mapped["PedimentoRectificationDestination"] = ( - relationship( - "PedimentoRectificationDestination", - uselist=False, - back_populates="pedimento", - cascade="all, delete-orphan", - ) - ) - pedimento_rectification_origin: Mapped["PedimentoRectificationOrigin"] = ( - relationship( - "PedimentoRectificationOrigin", - uselist=False, - back_populates="pedimento", - cascade="all, delete-orphan", - ) - ) - pedimento_transport_means: Mapped["PedimentoTransportMeans"] = relationship( - "PedimentoTransportMeans", - uselist=False, - back_populates="pedimento", - cascade="all, delete-orphan", - ) - pedimento_validation: Mapped["PedimentoValidation"] = relationship( - "PedimentoValidation", - uselist=False, - back_populates="pedimento", - cascade="all, delete-orphan", - ) - pedimento_packages: Mapped["PedimentoPackages"] = relationship( - "PedimentoPackages", uselist=False, back_populates="pedimento", cascade="all, delete-orphan" - ) - pedimento_transport_carriers: Mapped[list["PedimentoTransportCarriers"]] = relationship( - "PedimentoTransportCarriers", back_populates="pedimento", cascade="all, delete-orphan" - ) - pedimento_guides: Mapped[list["PedimentoGuides"]] = relationship( - "PedimentoGuides", back_populates="pedimento", cascade="all, delete-orphan" - ) - pedimento_contributions: Mapped[list["PedimentoContributions"]] = relationship( - "PedimentoContributions", back_populates="pedimento", cascade="all, delete-orphan" - ) - pedimento_seals: Mapped[list["PedimentoSeals"]] = relationship( - "PedimentoSeals", back_populates="pedimento", cascade="all, delete-orphan" - ) - pedimento_containers: Mapped[list["PedimentoContainers"]] = relationship( - "PedimentoContainers", back_populates="pedimento", cascade="all, delete-orphan" - ) diff --git a/backend/api/v1/modules/a76/pedmientos/router.py b/backend/api/v1/modules/a76/pedmientos/router.py deleted file mode 100644 index b9444c04..00000000 --- a/backend/api/v1/modules/a76/pedmientos/router.py +++ /dev/null @@ -1,125 +0,0 @@ -from fastapi import APIRouter - -from .routes.pedimento_config_additional import ( - router as pedimento_config_additional_router, -) -from .routes.pedimento_config_calculations import ( - router as pedimento_config_calculations_router, -) -from .routes.pedimento_config_parameters import ( - router as pedimento_config_parameters_router, -) -from .routes.pedimento_config_surcharges import ( - router as pedimento_config_surcharges_router, -) -from .routes.pedimento_config_update_rectification import ( - router as pedimento_config_update_rectification_router, -) -from .routes.pedimento_config_updates import router as pedimento_config_updates_router -from .routes.pedimento_customs_offices import router as pedimento_customs_offices_router -from .routes.pedimento_dates import router as pedimento_dates_router -from .routes.pedimento_decrementables import router as pedimento_decrementables_router -from .routes.pedimento_incrementables import router as pedimento_incrementables_router -from .routes.pedimento_indexes import router as pedimento_indexes_router -from .routes.pedimento_payments import router as pedimento_payments_router -from .routes.pedimento_rectification_destination import ( - router as pedimento_rectification_destination_router, -) -from .routes.pedimento_rectification_origin import ( - router as pedimento_rectification_origin_router, -) -from .routes.pedimento_transport_means import router as pedimento_transport_means_router -from .routes.pedimento_validation import router as pedimento_validation_router -from .routes.pedimentos import router as pedimentos_router -from api.v1.modules.a76.layouts_csv.pedmientos.routes import router as pedimentos_imports_router - -router = APIRouter() - -router.include_router( - pedimento_config_additional_router, - prefix="/pedimentos", - tags=["a76 / pedimentos / pedimento_config_additional"], -) -router.include_router( - pedimento_config_calculations_router, - prefix="/pedimentos", - tags=["a76 / pedimentos / pedimento_config_calculations"], -) -router.include_router( - pedimento_config_parameters_router, - prefix="/pedimentos", - tags=["a76 / pedimentos / pedimento_config_parameters"], -) -router.include_router( - pedimento_config_surcharges_router, - prefix="/pedimentos", - tags=["a76 / pedimentos / pedimento_config_surcharges"], -) -router.include_router( - pedimento_config_update_rectification_router, - prefix="/pedimentos", - tags=["a76 / pedimentos / pedimento_config_update_rectification"], -) -router.include_router( - pedimento_config_updates_router, - prefix="/pedimentos", - tags=["a76 / pedimentos / pedimento_config_updates"], -) -router.include_router( - pedimento_customs_offices_router, - prefix="/pedimentos", - tags=["a76 / pedimentos / pedimento_customs_offices"], -) -router.include_router( - pedimento_dates_router, - prefix="/pedimentos", - tags=["a76 / pedimentos / pedimento_dates"], -) -router.include_router( - pedimento_decrementables_router, - prefix="/pedimentos", - tags=["a76 / pedimentos / pedimento_decrementables"], -) -router.include_router( - pedimento_incrementables_router, - prefix="/pedimentos", - tags=["a76 / pedimentos / pedimento_incrementables"], -) -router.include_router( - pedimento_indexes_router, - prefix="/pedimentos", - tags=["a76 / pedimentos / pedimento_indexes"], -) -router.include_router( - pedimento_payments_router, - prefix="/pedimentos", - tags=["a76 / pedimentos / pedimento_payments"], -) -router.include_router( - pedimento_rectification_destination_router, - prefix="/pedimentos", - tags=["a76 / pedimentos / pedimento_rectification_destination"], -) -router.include_router( - pedimento_rectification_origin_router, - prefix="/pedimentos", - tags=["a76 / pedimentos / pedimento_rectification_origin"], -) -router.include_router( - pedimento_transport_means_router, - prefix="/pedimentos", - tags=["a76 / pedimentos / pedimento_transport_means"], -) -router.include_router( - pedimento_validation_router, - prefix="/pedimentos", - tags=["a76 / pedimentos / pedimento_validation"], -) -router.include_router( - pedimentos_router, prefix="/pedimentos", tags=["a76 / pedimentos"] -) -router.include_router( - pedimentos_imports_router, - prefix="/pedimentos/imports", - tags=["a76 / pedimentos / csv_import"], -) diff --git a/backend/api/v1/modules/a76/pedmientos/routes/pedimento_config_additional.py b/backend/api/v1/modules/a76/pedmientos/routes/pedimento_config_additional.py deleted file mode 100644 index 2c82b61f..00000000 --- a/backend/api/v1/modules/a76/pedmientos/routes/pedimento_config_additional.py +++ /dev/null @@ -1,30 +0,0 @@ -""" -Routes for PedimentoConfigAdditional CRUD operations -""" - -from api.v1.common.tenant_crud_routes import TenantCRUDRoutes - -from ..dtos.pedimento_config_additional import ( - PedimentoConfigAdditionalCreate, - PedimentoConfigAdditionalResponse, - PedimentoConfigAdditionalUpdate, -) -from ..services.pedimento_config_additional import PedimentoConfigAdditionalService - -# Create router with generic CRUD routes for child resource -router = TenantCRUDRoutes( - service=PedimentoConfigAdditionalService, - create_schema=PedimentoConfigAdditionalCreate, - update_schema=PedimentoConfigAdditionalUpdate, - response_schema=PedimentoConfigAdditionalResponse, - prefix="/{pedimento_id}/config-additional", - tags=[], - resource_name="Config additional", - parent_id_name="pedimento_id", - enable_list=False, - validate_parent_match=True, - get_permissions=["pedimentos_mgmt.view"], - create_permissions=["pedimentos_mgmt.edit"], - update_permissions=["pedimentos_mgmt.edit"], - delete_permissions=["pedimentos_mgmt.edit"], -).router diff --git a/backend/api/v1/modules/a76/pedmientos/routes/pedimento_config_calculations.py b/backend/api/v1/modules/a76/pedmientos/routes/pedimento_config_calculations.py deleted file mode 100644 index 0fce3aa3..00000000 --- a/backend/api/v1/modules/a76/pedmientos/routes/pedimento_config_calculations.py +++ /dev/null @@ -1,30 +0,0 @@ -""" -Routes for PedimentoConfigCalculations CRUD operations -""" - -from api.v1.common.tenant_crud_routes import TenantCRUDRoutes - -from ..dtos.pedimento_config_calculations import ( - PedimentoConfigCalculationsCreate, - PedimentoConfigCalculationsResponse, - PedimentoConfigCalculationsUpdate, -) -from ..services.pedimento_config_calculations import PedimentoConfigCalculationsService - -# Create router with generic CRUD routes for child resource -router = TenantCRUDRoutes( - service=PedimentoConfigCalculationsService, - create_schema=PedimentoConfigCalculationsCreate, - update_schema=PedimentoConfigCalculationsUpdate, - response_schema=PedimentoConfigCalculationsResponse, - prefix="/{pedimento_id}/config-calculations", - tags=[], - resource_name="Config calculations", - parent_id_name="pedimento_id", - enable_list=False, - validate_parent_match=True, - get_permissions=["pedimentos_mgmt.view"], - create_permissions=["pedimentos_mgmt.edit"], - update_permissions=["pedimentos_mgmt.edit"], - delete_permissions=["pedimentos_mgmt.edit"], -).router diff --git a/backend/api/v1/modules/a76/pedmientos/routes/pedimento_config_parameters.py b/backend/api/v1/modules/a76/pedmientos/routes/pedimento_config_parameters.py deleted file mode 100644 index 5afa06d3..00000000 --- a/backend/api/v1/modules/a76/pedmientos/routes/pedimento_config_parameters.py +++ /dev/null @@ -1,30 +0,0 @@ -""" -Routes for PedimentoConfigParameters CRUD operations -""" - -from api.v1.common.tenant_crud_routes import TenantCRUDRoutes - -from ..dtos.pedimento_config_parameters import ( - PedimentoConfigParametersCreate, - PedimentoConfigParametersResponse, - PedimentoConfigParametersUpdate, -) -from ..services.pedimento_config_parameters import PedimentoConfigParametersService - -# Create router with generic CRUD routes for child resource -router = TenantCRUDRoutes( - service=PedimentoConfigParametersService, - create_schema=PedimentoConfigParametersCreate, - update_schema=PedimentoConfigParametersUpdate, - response_schema=PedimentoConfigParametersResponse, - prefix="/{pedimento_id}/config-parameters", - tags=[], - resource_name="Config parameters", - parent_id_name="pedimento_id", - enable_list=False, - validate_parent_match=True, - get_permissions=["pedimentos_mgmt.view"], - create_permissions=["pedimentos_mgmt.edit"], - update_permissions=["pedimentos_mgmt.edit"], - delete_permissions=["pedimentos_mgmt.edit"], -).router diff --git a/backend/api/v1/modules/a76/pedmientos/routes/pedimento_config_surcharges.py b/backend/api/v1/modules/a76/pedmientos/routes/pedimento_config_surcharges.py deleted file mode 100644 index 508e75ea..00000000 --- a/backend/api/v1/modules/a76/pedmientos/routes/pedimento_config_surcharges.py +++ /dev/null @@ -1,30 +0,0 @@ -""" -Routes for PedimentoConfigSurcharges CRUD operations -""" - -from api.v1.common.tenant_crud_routes import TenantCRUDRoutes - -from ..dtos.pedimento_config_surcharges import ( - PedimentoConfigSurchargesCreate, - PedimentoConfigSurchargesResponse, - PedimentoConfigSurchargesUpdate, -) -from ..services.pedimento_config_surcharges import PedimentoConfigSurchargesService - -# Create router with generic CRUD routes for child resource -router = TenantCRUDRoutes( - service=PedimentoConfigSurchargesService, - create_schema=PedimentoConfigSurchargesCreate, - update_schema=PedimentoConfigSurchargesUpdate, - response_schema=PedimentoConfigSurchargesResponse, - prefix="/{pedimento_id}/config-surcharges", - tags=[], - resource_name="Config surcharges", - parent_id_name="pedimento_id", - enable_list=False, - validate_parent_match=True, - get_permissions=["pedimentos_mgmt.view"], - create_permissions=["pedimentos_mgmt.edit"], - update_permissions=["pedimentos_mgmt.edit"], - delete_permissions=["pedimentos_mgmt.edit"], -).router diff --git a/backend/api/v1/modules/a76/pedmientos/routes/pedimento_config_update_rectification.py b/backend/api/v1/modules/a76/pedmientos/routes/pedimento_config_update_rectification.py deleted file mode 100644 index 6d9ed316..00000000 --- a/backend/api/v1/modules/a76/pedmientos/routes/pedimento_config_update_rectification.py +++ /dev/null @@ -1,32 +0,0 @@ -""" -Routes for PedimentoConfigUpdateRectification CRUD operations -""" - -from api.v1.common.tenant_crud_routes import TenantCRUDRoutes - -from ..dtos.pedimento_config_update_rectification import ( - PedimentoConfigUpdateRectificationCreate, - PedimentoConfigUpdateRectificationResponse, - PedimentoConfigUpdateRectificationUpdate, -) -from ..services.pedimento_config_update_rectification import ( - PedimentoConfigUpdateRectificationService, -) - -# Create router with generic CRUD routes for child resource -router = TenantCRUDRoutes( - service=PedimentoConfigUpdateRectificationService, - create_schema=PedimentoConfigUpdateRectificationCreate, - update_schema=PedimentoConfigUpdateRectificationUpdate, - response_schema=PedimentoConfigUpdateRectificationResponse, - prefix="/{pedimento_id}/config-update-rectification", - tags=[], - resource_name="Config update rectification", - parent_id_name="pedimento_id", - enable_list=False, - validate_parent_match=True, - get_permissions=["pedimentos_mgmt.view"], - create_permissions=["pedimentos_mgmt.edit"], - update_permissions=["pedimentos_mgmt.edit"], - delete_permissions=["pedimentos_mgmt.edit"], -).router diff --git a/backend/api/v1/modules/a76/pedmientos/routes/pedimento_config_updates.py b/backend/api/v1/modules/a76/pedmientos/routes/pedimento_config_updates.py deleted file mode 100644 index aa763ed4..00000000 --- a/backend/api/v1/modules/a76/pedmientos/routes/pedimento_config_updates.py +++ /dev/null @@ -1,30 +0,0 @@ -""" -Routes for PedimentoConfigUpdates CRUD operations -""" - -from api.v1.common.tenant_crud_routes import TenantCRUDRoutes - -from ..dtos.pedimento_config_updates import ( - PedimentoConfigUpdatesCreate, - PedimentoConfigUpdatesResponse, - PedimentoConfigUpdatesUpdate, -) -from ..services.pedimento_config_updates import PedimentoConfigUpdatesService - -# Create router with generic CRUD routes for child resource -router = TenantCRUDRoutes( - service=PedimentoConfigUpdatesService, - create_schema=PedimentoConfigUpdatesCreate, - update_schema=PedimentoConfigUpdatesUpdate, - response_schema=PedimentoConfigUpdatesResponse, - prefix="/{pedimento_id}/config-updates", - tags=[], - resource_name="Config updates", - parent_id_name="pedimento_id", - enable_list=False, - validate_parent_match=True, - get_permissions=["pedimentos_mgmt.view"], - create_permissions=["pedimentos_mgmt.edit"], - update_permissions=["pedimentos_mgmt.edit"], - delete_permissions=["pedimentos_mgmt.edit"], -).router diff --git a/backend/api/v1/modules/a76/pedmientos/routes/pedimento_customs_offices.py b/backend/api/v1/modules/a76/pedmientos/routes/pedimento_customs_offices.py deleted file mode 100644 index 5de86a85..00000000 --- a/backend/api/v1/modules/a76/pedmientos/routes/pedimento_customs_offices.py +++ /dev/null @@ -1,125 +0,0 @@ -""" -Routes for PedimentoCustomsOffices CRUD operations -""" - -from typing import Any, Dict, List - -from core.database import get_core_db -from core.security import get_current_user, validate_access_to_resource -from fastapi import APIRouter, Depends, HTTPException, Query -from sqlalchemy.orm import Session - -from ..dtos.pedimento_customs_offices import ( - PedimentoCustomsOfficesCreate, - PedimentoCustomsOfficesResponse, - PedimentoCustomsOfficesUpdate, -) -from ..services.pedimento_customs_offices import PedimentoCustomsOfficesService - -router = APIRouter(prefix="/{pedimento_id}/customs-offices") - - -@router.get("/", response_model=List[PedimentoCustomsOfficesResponse]) -async def list_customs_offices( - pedimento_id: int, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """Get all customs offices for a pedimento""" - tenant_id = validate_access_to_resource( - db, company_id, current_user, ["pedimentos_mgmt.view"] - ) - - offices = PedimentoCustomsOfficesService.get_by_pedimento_id( - db, pedimento_id, tenant_id, company_id - ) - return offices - - -@router.get("/{office_id}", response_model=PedimentoCustomsOfficesResponse) -async def get_customs_office( - pedimento_id: int, - office_id: int, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """Get a specific customs office by ID""" - tenant_id = validate_access_to_resource( - db, company_id, current_user, ["pedimentos_mgmt.view"] - ) - - office = PedimentoCustomsOfficesService.get_by_id( - db, office_id, pedimento_id, tenant_id, company_id - ) - if not office: - raise HTTPException(status_code=404, detail="Customs office not found") - - return office - - -@router.post("/", response_model=PedimentoCustomsOfficesResponse, status_code=201) -async def create_customs_office( - pedimento_id: int, - data: PedimentoCustomsOfficesCreate, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """Create a new customs office""" - tenant_id = validate_access_to_resource( - db, company_id, current_user, ["pedimentos_mgmt.edit"] - ) - - # Ensure pedimento_id matches - if data.pedimento_id != pedimento_id: - raise HTTPException(status_code=400, detail="Pedimento ID mismatch") - - office = PedimentoCustomsOfficesService.create(db, data, tenant_id, company_id) - return office - - -@router.put("/{office_id}", response_model=PedimentoCustomsOfficesResponse) -async def update_customs_office( - pedimento_id: int, - office_id: int, - data: PedimentoCustomsOfficesUpdate, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """Update a customs office""" - tenant_id = validate_access_to_resource( - db, company_id, current_user, ["pedimentos_mgmt.edit"] - ) - - office = PedimentoCustomsOfficesService.update( - db, office_id, pedimento_id, tenant_id, company_id, data - ) - if not office: - raise HTTPException(status_code=404, detail="Customs office not found") - - return office - - -@router.delete("/{office_id}", status_code=204) -async def delete_customs_office( - pedimento_id: int, - office_id: int, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """Delete a customs office""" - tenant_id = validate_access_to_resource( - db, company_id, current_user, ["pedimentos_mgmt.edit"] - ) - - success = PedimentoCustomsOfficesService.delete( - db, office_id, pedimento_id, tenant_id, company_id - ) - if not success: - raise HTTPException(status_code=404, detail="Customs office not found") - - return None diff --git a/backend/api/v1/modules/a76/pedmientos/routes/pedimento_dates.py b/backend/api/v1/modules/a76/pedmientos/routes/pedimento_dates.py deleted file mode 100644 index 6d30b124..00000000 --- a/backend/api/v1/modules/a76/pedmientos/routes/pedimento_dates.py +++ /dev/null @@ -1,30 +0,0 @@ -""" -Routes for PedimentoDates CRUD operations -""" - -from api.v1.common.tenant_crud_routes import TenantCRUDRoutes - -from ..dtos.pedimento_dates import ( - PedimentoDatesCreate, - PedimentoDatesResponse, - PedimentoDatesUpdate, -) -from ..services.pedimento_dates import PedimentoDatesService - -# Create router with generic CRUD routes for child resource -router = TenantCRUDRoutes( - service=PedimentoDatesService, - create_schema=PedimentoDatesCreate, - update_schema=PedimentoDatesUpdate, - response_schema=PedimentoDatesResponse, - prefix="/{pedimento_id}/dates", - tags=[], - resource_name="Pedimento dates", - parent_id_name="pedimento_id", - enable_list=False, - validate_parent_match=True, - get_permissions=["pedimentos_mgmt.view"], - create_permissions=["pedimentos_mgmt.edit"], - update_permissions=["pedimentos_mgmt.edit"], - delete_permissions=["pedimentos_mgmt.edit"], -).router diff --git a/backend/api/v1/modules/a76/pedmientos/routes/pedimento_decrementables.py b/backend/api/v1/modules/a76/pedmientos/routes/pedimento_decrementables.py deleted file mode 100644 index 7c36fc91..00000000 --- a/backend/api/v1/modules/a76/pedmientos/routes/pedimento_decrementables.py +++ /dev/null @@ -1,127 +0,0 @@ -""" -Routes for PedimentoDecrementables CRUD operations -""" - -from typing import Any, Dict, List - -from core.database import get_core_db -from core.security import get_current_user, validate_access_to_resource -from fastapi import APIRouter, Depends, HTTPException, Query -from sqlalchemy.orm import Session - -from ..dtos.pedimento_decrementables import ( - PedimentoDecrementablesCreate, - PedimentoDecrementablesResponse, - PedimentoDecrementablesUpdate, -) -from ..services.pedimento_decrementables import PedimentoDecrementablesService - -router = APIRouter(prefix="/{pedimento_id}/decrementables") - - -@router.get("/", response_model=List[PedimentoDecrementablesResponse]) -async def list_decrementables( - pedimento_id: int, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """Get all decrementables for a pedimento""" - tenant_id = validate_access_to_resource( - db, company_id, current_user, ["pedimentos_mgmt.view"] - ) - - decrementables = PedimentoDecrementablesService.get_by_pedimento_id( - db, pedimento_id, tenant_id, company_id - ) - return decrementables - - -@router.get("/{decrementable_id}", response_model=PedimentoDecrementablesResponse) -async def get_decrementable( - pedimento_id: int, - decrementable_id: int, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """Get a specific decrementable by ID""" - tenant_id = validate_access_to_resource( - db, company_id, current_user, ["pedimentos_mgmt.view"] - ) - - decrementable = PedimentoDecrementablesService.get_by_id( - db, decrementable_id, pedimento_id, tenant_id, company_id - ) - if not decrementable: - raise HTTPException(status_code=404, detail="Decrementable not found") - - return decrementable - - -@router.post("/", response_model=PedimentoDecrementablesResponse, status_code=201) -async def create_decrementable( - pedimento_id: int, - data: PedimentoDecrementablesCreate, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """Create a new decrementable""" - tenant_id = validate_access_to_resource( - db, company_id, current_user, ["pedimentos_mgmt.edit"] - ) - - # Ensure pedimento_id matches - if data.pedimento_id != pedimento_id: - raise HTTPException(status_code=400, detail="Pedimento ID mismatch") - - decrementable = PedimentoDecrementablesService.create( - db, data, tenant_id, company_id - ) - return decrementable - - -@router.put("/{decrementable_id}", response_model=PedimentoDecrementablesResponse) -async def update_decrementable( - pedimento_id: int, - decrementable_id: int, - data: PedimentoDecrementablesUpdate, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """Update a decrementable""" - tenant_id = validate_access_to_resource( - db, company_id, current_user, ["pedimentos_mgmt.edit"] - ) - - decrementable = PedimentoDecrementablesService.update( - db, decrementable_id, pedimento_id, tenant_id, company_id, data - ) - if not decrementable: - raise HTTPException(status_code=404, detail="Decrementable not found") - - return decrementable - - -@router.delete("/{decrementable_id}", status_code=204) -async def delete_decrementable( - pedimento_id: int, - decrementable_id: int, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """Delete a decrementable""" - tenant_id = validate_access_to_resource( - db, company_id, current_user, ["pedimentos_mgmt.edit"] - ) - - success = PedimentoDecrementablesService.delete( - db, decrementable_id, pedimento_id, tenant_id, company_id - ) - if not success: - raise HTTPException(status_code=404, detail="Decrementable not found") - - return None diff --git a/backend/api/v1/modules/a76/pedmientos/routes/pedimento_incrementables.py b/backend/api/v1/modules/a76/pedmientos/routes/pedimento_incrementables.py deleted file mode 100644 index 5912f85b..00000000 --- a/backend/api/v1/modules/a76/pedmientos/routes/pedimento_incrementables.py +++ /dev/null @@ -1,127 +0,0 @@ -""" -Routes for PedimentoIncrementables CRUD operations -""" - -from typing import Any, Dict, List - -from core.database import get_core_db -from core.security import get_current_user, validate_access_to_resource -from fastapi import APIRouter, Depends, HTTPException, Query -from sqlalchemy.orm import Session - -from ..dtos.pedimento_incrementables import ( - PedimentoIncrementablesCreate, - PedimentoIncrementablesResponse, - PedimentoIncrementablesUpdate, -) -from ..services.pedimento_incrementables import PedimentoIncrementablesService - -router = APIRouter(prefix="/{pedimento_id}/incrementables") - - -@router.get("/", response_model=List[PedimentoIncrementablesResponse]) -async def list_incrementables( - pedimento_id: int, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """Get all incrementables for a pedimento""" - tenant_id = validate_access_to_resource( - db, company_id, current_user, ["pedimentos_mgmt.view"] - ) - - incrementables = PedimentoIncrementablesService.get_by_pedimento_id( - db, pedimento_id, tenant_id, company_id - ) - return incrementables - - -@router.get("/{incrementable_id}", response_model=PedimentoIncrementablesResponse) -async def get_incrementable( - pedimento_id: int, - incrementable_id: int, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """Get a specific incrementable by ID""" - tenant_id = validate_access_to_resource( - db, company_id, current_user, ["pedimentos_mgmt.view"] - ) - - incrementable = PedimentoIncrementablesService.get_by_id( - db, incrementable_id, pedimento_id, tenant_id, company_id - ) - if not incrementable: - raise HTTPException(status_code=404, detail="Incrementable not found") - - return incrementable - - -@router.post("/", response_model=PedimentoIncrementablesResponse, status_code=201) -async def create_incrementable( - pedimento_id: int, - data: PedimentoIncrementablesCreate, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """Create a new incrementable""" - tenant_id = validate_access_to_resource( - db, company_id, current_user, ["pedimentos_mgmt.edit"] - ) - - # Ensure pedimento_id matches - if data.pedimento_id != pedimento_id: - raise HTTPException(status_code=400, detail="Pedimento ID mismatch") - - incrementable = PedimentoIncrementablesService.create( - db, data, tenant_id, company_id - ) - return incrementable - - -@router.put("/{incrementable_id}", response_model=PedimentoIncrementablesResponse) -async def update_incrementable( - pedimento_id: int, - incrementable_id: int, - data: PedimentoIncrementablesUpdate, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """Update an incrementable""" - tenant_id = validate_access_to_resource( - db, company_id, current_user, ["pedimentos_mgmt.edit"] - ) - - incrementable = PedimentoIncrementablesService.update( - db, incrementable_id, pedimento_id, tenant_id, company_id, data - ) - if not incrementable: - raise HTTPException(status_code=404, detail="Incrementable not found") - - return incrementable - - -@router.delete("/{incrementable_id}", status_code=204) -async def delete_incrementable( - pedimento_id: int, - incrementable_id: int, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """Delete an incrementable""" - tenant_id = validate_access_to_resource( - db, company_id, current_user, ["pedimentos_mgmt.edit"] - ) - - success = PedimentoIncrementablesService.delete( - db, incrementable_id, pedimento_id, tenant_id, company_id - ) - if not success: - raise HTTPException(status_code=404, detail="Incrementable not found") - - return None diff --git a/backend/api/v1/modules/a76/pedmientos/routes/pedimento_indexes.py b/backend/api/v1/modules/a76/pedmientos/routes/pedimento_indexes.py deleted file mode 100644 index 0d6a90d4..00000000 --- a/backend/api/v1/modules/a76/pedmientos/routes/pedimento_indexes.py +++ /dev/null @@ -1,30 +0,0 @@ -""" -Routes for PedimentoIndexes CRUD operations -""" - -from api.v1.common.tenant_crud_routes import TenantCRUDRoutes - -from ..dtos.pedimento_indexes import ( - PedimentoIndexesCreate, - PedimentoIndexesResponse, - PedimentoIndexesUpdate, -) -from ..services.pedimento_indexes import PedimentoIndexesService - -# Create router with generic CRUD routes for child resource -router = TenantCRUDRoutes( - service=PedimentoIndexesService, - create_schema=PedimentoIndexesCreate, - update_schema=PedimentoIndexesUpdate, - response_schema=PedimentoIndexesResponse, - prefix="/{pedimento_id}/indexes", - tags=[], - resource_name="Pedimento indexes", - parent_id_name="pedimento_id", - enable_list=False, - validate_parent_match=True, - get_permissions=["pedimentos_mgmt.view"], - create_permissions=["pedimentos_mgmt.edit"], - update_permissions=["pedimentos_mgmt.edit"], - delete_permissions=["pedimentos_mgmt.edit"], -).router diff --git a/backend/api/v1/modules/a76/pedmientos/routes/pedimento_payments.py b/backend/api/v1/modules/a76/pedmientos/routes/pedimento_payments.py deleted file mode 100644 index 958fbe36..00000000 --- a/backend/api/v1/modules/a76/pedmientos/routes/pedimento_payments.py +++ /dev/null @@ -1,125 +0,0 @@ -""" -Routes for PedimentoPayments CRUD operations -""" - -from typing import Any, Dict, List - -from core.database import get_core_db -from core.security import get_current_user, validate_access_to_resource -from fastapi import APIRouter, Depends, HTTPException, Query -from sqlalchemy.orm import Session - -from ..dtos.pedimento_payments import ( - PedimentoPaymentsCreate, - PedimentoPaymentsResponse, - PedimentoPaymentsUpdate, -) -from ..services.pedimento_payments import PedimentoPaymentsService - -router = APIRouter(prefix="/{pedimento_id}/payments") - - -@router.get("/", response_model=List[PedimentoPaymentsResponse]) -async def list_payments( - pedimento_id: int, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """Get all payments for a pedimento""" - tenant_id = validate_access_to_resource( - db, company_id, current_user, ["pedimentos_mgmt.view"] - ) - - payments = PedimentoPaymentsService.get_by_pedimento_id( - db, pedimento_id, tenant_id, company_id - ) - return payments - - -@router.get("/{id}", response_model=PedimentoPaymentsResponse) -async def get_payment( - pedimento_id: int, - id: int, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """Get a specific payment by ID""" - tenant_id = validate_access_to_resource( - db, company_id, current_user, ["pedimentos_mgmt.view"] - ) - - payment = PedimentoPaymentsService.get_by_id( - db, id, pedimento_id, tenant_id, company_id - ) - if not payment: - raise HTTPException(status_code=404, detail="Payment not found") - - return payment - - -@router.post("/", response_model=PedimentoPaymentsResponse, status_code=201) -async def create_payment( - pedimento_id: int, - data: PedimentoPaymentsCreate, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """Create a new payment""" - tenant_id = validate_access_to_resource( - db, company_id, current_user, ["pedimentos_mgmt.edit"] - ) - - # Ensure pedimento_id matches - if data.pedimento_id != pedimento_id: - raise HTTPException(status_code=400, detail="Pedimento ID mismatch") - - payment = PedimentoPaymentsService.create(db, data, tenant_id, company_id) - return payment - - -@router.put("/{id}", response_model=PedimentoPaymentsResponse) -async def update_payment( - pedimento_id: int, - id: int, - data: PedimentoPaymentsUpdate, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """Update a payment""" - tenant_id = validate_access_to_resource( - db, company_id, current_user, ["pedimentos_mgmt.edit"] - ) - - payment = PedimentoPaymentsService.update( - db, id, pedimento_id, tenant_id, company_id, data - ) - if not payment: - raise HTTPException(status_code=404, detail="Payment not found") - - return payment - - -@router.delete("/{id}", status_code=204) -async def delete_payment( - pedimento_id: int, - id: int, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """Delete a payment""" - tenant_id = validate_access_to_resource( - db, company_id, current_user, ["pedimentos_mgmt.edit"] - ) - - success = PedimentoPaymentsService.delete( - db, id, pedimento_id, tenant_id, company_id - ) - if not success: - raise HTTPException(status_code=404, detail="Payment not found") - - return None diff --git a/backend/api/v1/modules/a76/pedmientos/routes/pedimento_rectification_destination.py b/backend/api/v1/modules/a76/pedmientos/routes/pedimento_rectification_destination.py deleted file mode 100644 index db359cb5..00000000 --- a/backend/api/v1/modules/a76/pedmientos/routes/pedimento_rectification_destination.py +++ /dev/null @@ -1,32 +0,0 @@ -""" -Routes for PedimentoRectificationDestination CRUD operations -""" - -from api.v1.common.tenant_crud_routes import TenantCRUDRoutes - -from ..dtos.pedimento_rectification_destination import ( - PedimentoRectificationDestinationCreate, - PedimentoRectificationDestinationResponse, - PedimentoRectificationDestinationUpdate, -) -from ..services.pedimento_rectification_destination import ( - PedimentoRectificationDestinationService, -) - -# Create router with generic CRUD routes for child resource -router = TenantCRUDRoutes( - service=PedimentoRectificationDestinationService, - create_schema=PedimentoRectificationDestinationCreate, - update_schema=PedimentoRectificationDestinationUpdate, - response_schema=PedimentoRectificationDestinationResponse, - prefix="/{pedimento_id}/rectification-destination", - tags=[], - resource_name="Rectification destination", - parent_id_name="pedimento_id", - enable_list=False, - validate_parent_match=True, - get_permissions=["pedimentos_mgmt.view"], - create_permissions=["pedimentos_mgmt.edit"], - update_permissions=["pedimentos_mgmt.edit"], - delete_permissions=["pedimentos_mgmt.edit"], -).router diff --git a/backend/api/v1/modules/a76/pedmientos/routes/pedimento_rectification_origin.py b/backend/api/v1/modules/a76/pedmientos/routes/pedimento_rectification_origin.py deleted file mode 100644 index 005a3609..00000000 --- a/backend/api/v1/modules/a76/pedmientos/routes/pedimento_rectification_origin.py +++ /dev/null @@ -1,32 +0,0 @@ -""" -Routes for PedimentoRectificationOrigin CRUD operations -""" - -from api.v1.common.tenant_crud_routes import TenantCRUDRoutes - -from ..dtos.pedimento_rectification_origin import ( - PedimentoRectificationOriginCreate, - PedimentoRectificationOriginResponse, - PedimentoRectificationOriginUpdate, -) -from ..services.pedimento_rectification_origin import ( - PedimentoRectificationOriginService, -) - -# Create router with generic CRUD routes for child resource -router = TenantCRUDRoutes( - service=PedimentoRectificationOriginService, - create_schema=PedimentoRectificationOriginCreate, - update_schema=PedimentoRectificationOriginUpdate, - response_schema=PedimentoRectificationOriginResponse, - prefix="/{pedimento_id}/rectification-origin", - tags=[], - resource_name="Rectification origin", - parent_id_name="pedimento_id", - enable_list=False, - validate_parent_match=True, - get_permissions=["pedimentos_mgmt.view"], - create_permissions=["pedimentos_mgmt.edit"], - update_permissions=["pedimentos_mgmt.edit"], - delete_permissions=["pedimentos_mgmt.edit"], -).router diff --git a/backend/api/v1/modules/a76/pedmientos/routes/pedimento_transport_means.py b/backend/api/v1/modules/a76/pedmientos/routes/pedimento_transport_means.py deleted file mode 100644 index 893a7474..00000000 --- a/backend/api/v1/modules/a76/pedmientos/routes/pedimento_transport_means.py +++ /dev/null @@ -1,127 +0,0 @@ -""" -Routes for PedimentoTransportMeans CRUD operations -""" - -from typing import Any, Dict, List - -from core.database import get_core_db -from core.security import get_current_user, validate_access_to_resource -from fastapi import APIRouter, Depends, HTTPException, Query -from sqlalchemy.orm import Session - -from ..dtos.pedimento_transport_means import ( - PedimentoTransportMeansCreate, - PedimentoTransportMeansResponse, - PedimentoTransportMeansUpdate, -) -from ..services.pedimento_transport_means import PedimentoTransportMeansService - -router = APIRouter(prefix="/{pedimento_id}/transport-means") - - -@router.get("/", response_model=List[PedimentoTransportMeansResponse]) -async def list_transport_means( - pedimento_id: int, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """Get all transport means for a pedimento""" - tenant_id = validate_access_to_resource( - db, company_id, current_user, ["pedimentos_mgmt.view"] - ) - - transport_means = PedimentoTransportMeansService.get_by_pedimento_id( - db, pedimento_id, tenant_id, company_id - ) - return transport_means - - -@router.get("/{transport_mean_id}", response_model=PedimentoTransportMeansResponse) -async def get_transport_mean( - pedimento_id: int, - transport_mean_id: int, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """Get a specific transport mean by ID""" - tenant_id = validate_access_to_resource( - db, company_id, current_user, ["pedimentos_mgmt.view"] - ) - - transport_mean = PedimentoTransportMeansService.get_by_id( - db, transport_mean_id, pedimento_id, tenant_id, company_id - ) - if not transport_mean: - raise HTTPException(status_code=404, detail="Transport mean not found") - - return transport_mean - - -@router.post("/", response_model=PedimentoTransportMeansResponse, status_code=201) -async def create_transport_mean( - pedimento_id: int, - data: PedimentoTransportMeansCreate, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """Create a new transport mean""" - tenant_id = validate_access_to_resource( - db, company_id, current_user, ["pedimentos_mgmt.edit"] - ) - - # Ensure pedimento_id matches - if data.pedimento_id != pedimento_id: - raise HTTPException(status_code=400, detail="Pedimento ID mismatch") - - transport_mean = PedimentoTransportMeansService.create( - db, data, tenant_id, company_id - ) - return transport_mean - - -@router.put("/{transport_mean_id}", response_model=PedimentoTransportMeansResponse) -async def update_transport_mean( - pedimento_id: int, - transport_mean_id: int, - data: PedimentoTransportMeansUpdate, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """Update a transport mean""" - tenant_id = validate_access_to_resource( - db, company_id, current_user, ["pedimentos_mgmt.edit"] - ) - - transport_mean = PedimentoTransportMeansService.update( - db, transport_mean_id, pedimento_id, tenant_id, company_id, data - ) - if not transport_mean: - raise HTTPException(status_code=404, detail="Transport mean not found") - - return transport_mean - - -@router.delete("/{transport_mean_id}", status_code=204) -async def delete_transport_mean( - pedimento_id: int, - transport_mean_id: int, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - """Delete a transport mean""" - tenant_id = validate_access_to_resource( - db, company_id, current_user, ["pedimentos_mgmt.edit"] - ) - - success = PedimentoTransportMeansService.delete( - db, transport_mean_id, pedimento_id, tenant_id, company_id - ) - if not success: - raise HTTPException(status_code=404, detail="Transport mean not found") - - return None diff --git a/backend/api/v1/modules/a76/pedmientos/routes/pedimento_validation.py b/backend/api/v1/modules/a76/pedmientos/routes/pedimento_validation.py deleted file mode 100644 index 7b916c50..00000000 --- a/backend/api/v1/modules/a76/pedmientos/routes/pedimento_validation.py +++ /dev/null @@ -1,30 +0,0 @@ -""" -Routes for PedimentoValidation CRUD operations -""" - -from api.v1.common.tenant_crud_routes import TenantCRUDRoutes - -from ..dtos.pedimento_validation import ( - PedimentoValidationCreate, - PedimentoValidationResponse, - PedimentoValidationUpdate, -) -from ..services.pedimento_validation import PedimentoValidationService - -# Create router with generic CRUD routes for child resource -router = TenantCRUDRoutes( - service=PedimentoValidationService, - create_schema=PedimentoValidationCreate, - update_schema=PedimentoValidationUpdate, - response_schema=PedimentoValidationResponse, - prefix="/{pedimento_id}/validation", - tags=[], - resource_name="Pedimento validation", - parent_id_name="pedimento_id", - enable_list=False, # Child resource - no list endpoint - validate_parent_match=True, # Validate pedimento_id matches in create - get_permissions=["pedimentos_mgmt.view"], - create_permissions=["pedimentos_mgmt.edit"], - update_permissions=["pedimentos_mgmt.edit"], - delete_permissions=["pedimentos_mgmt.edit"], -).router diff --git a/backend/api/v1/modules/a76/pedmientos/routes/pedimentos.py b/backend/api/v1/modules/a76/pedmientos/routes/pedimentos.py deleted file mode 100644 index f2c501a6..00000000 --- a/backend/api/v1/modules/a76/pedmientos/routes/pedimentos.py +++ /dev/null @@ -1,104 +0,0 @@ -""" -Routes for Pedimentos CRUD operations -""" - -from fastapi import APIRouter, Depends, HTTPException, Query -from sqlalchemy.orm import Session - -from api.v1.common.tenant_crud_routes import TenantCRUDRoutes -from core.database import get_core_db -from core.security import get_current_user - -from ..dtos.pedimentos import PedimentosCreate, PedimentosResponse, PedimentosUpdate -from ..services.pedimentos import PedimentosService -from ..catalog_service import PedimentoCatalogService -from ..schemas import PedimentoCreationResponse, PedimentoEditionResponse - -# Create a new router for custom endpoints -router = APIRouter() - -# Add consolidated catalog endpoints FIRST (before generic CRUD routes) -# This ensures they have priority over the generic /{id} route -@router.get("/creation-data", response_model=PedimentoCreationResponse, tags=["a76 / pedimentos"]) -async def get_creation_data( - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) -): - """ - Get all catalogs needed for creating a new pedimento. - Consolidates multiple catalog calls into a single endpoint. - """ - from core.security import validate_access_to_resource - # Alta de pedimento: requiere permiso create. - validate_access_to_resource(db, company_id, current_user, ["pedimentos_mgmt.create"]) - - tenant_id = current_user["tenant_id"] - - try: - return PedimentoCatalogService.get_creation_data(db, tenant_id, company_id) - except HTTPException: - raise - except Exception as e: - print(f"Error fetching creation data: {e}") - import traceback - traceback.print_exc() - raise HTTPException(status_code=500, detail="Error fetching creation data") - - -@router.get("/{pedimento_id}/edition-data", response_model=PedimentoEditionResponse, tags=["a76 / pedimentos"]) -async def get_edition_data( - pedimento_id: int, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) -): - """ - Get all catalogs and pedimento data needed for editing an existing pedimento. - Consolidates multiple catalog calls + pedimento fetch into a single endpoint. - """ - from core.security import validate_access_to_resource - validate_access_to_resource(db, company_id, current_user, ["pedimentos_mgmt.edit"]) - - tenant_id = current_user["tenant_id"] - - try: - result = PedimentoCatalogService.get_edition_data(db, pedimento_id, tenant_id, company_id) - - if result is None: - raise HTTPException(status_code=404, detail="Pedimento not found") - - return result - except HTTPException: - raise - except Exception as e: - print(f"Error fetching edition data: {e}") - import traceback - traceback.print_exc() - raise HTTPException(status_code=500, detail="Error fetching edition data") - - -# Now include generic CRUD routes -# These will be registered AFTER the custom endpoints above -crud_router = TenantCRUDRoutes( - service=PedimentosService, - create_schema=PedimentosCreate, - update_schema=PedimentosUpdate, - response_schema=PedimentosResponse, - prefix="", # No prefix here, will be added in main router - tags=["a76 / pedimentos"], # Tag for Swagger documentation - resource_name="Pedimento", - id_name="id", # Use standard REST convention - enable_list=True, # Enable GET / with pagination - enable_filters=True, # Enable status, client_id, year filters - default_page_size=50, - max_page_size=1000, - list_permissions=["pedimentos_mgmt.view"], - get_permissions=["pedimentos_mgmt.view"], - create_permissions=["pedimentos_mgmt.create"], - update_permissions=["pedimentos_mgmt.edit"], - delete_permissions=["pedimentos_mgmt.delete"], -).router - -# Include the CRUD routes into our main router -router.include_router(crud_router) diff --git a/backend/api/v1/modules/a76/pedmientos/schemas.py b/backend/api/v1/modules/a76/pedmientos/schemas.py deleted file mode 100644 index e68d7a9e..00000000 --- a/backend/api/v1/modules/a76/pedmientos/schemas.py +++ /dev/null @@ -1,46 +0,0 @@ -""" -Consolidated schemas for Pedimento catalog responses -""" - -from typing import List, Optional -from pydantic import BaseModel - -# Import DTOs for catalog items -from api.v1.modules.public.reference_data.pedimento_codes.dto import PedimentoCodeDTO -from api.v1.modules.public.reference_data.customs_sections.dto import CustomsSectionDTO -from api.v1.modules.public.reference_data.code_pedimento_regimens.dto import CodePedimentoRegimenDTO -from api.v1.modules.a76.customs_brokers.dto import CustomsBrokerResponseDTO -from api.v1.modules.a76.clients_and_providers.dto import ClientProviderResponseDTO -from api.v1.modules.public.reference_data.transport_types.dto import TransportTypeDTO -from api.v1.modules.public.reference_data.transport_modes.dto import TransportModeDTO -from api.v1.modules.public.reference_data.pedimento_transport_catalog.dto import ( - PedimentoTransportCatalogDTO, -) -from .dtos.pedimentos import PedimentosResponse - - -class PedimentoCatalogsResponse(BaseModel): - """Base response containing all catalogs needed for pedimento views""" - - pedimento_codes: List[PedimentoCodeDTO] = [] - customs_sections: List[CustomsSectionDTO] = [] - code_pedimento_regimens: List[CodePedimentoRegimenDTO] = [] - customs_brokers: List[CustomsBrokerResponseDTO] = [] - clients: List[ClientProviderResponseDTO] = [] - transport_types: List[TransportTypeDTO] = [] - transport_modes: List[TransportModeDTO] = [] - pedimento_transport_catalog: List[PedimentoTransportCatalogDTO] = [] - - -class PedimentoCreationResponse(PedimentoCatalogsResponse): - """Response for creating a new pedimento (catalogs only)""" - - is_create: bool = True - - -class PedimentoEditionResponse(PedimentoCatalogsResponse): - """Response for editing an existing pedimento (catalogs + pedimento data)""" - - is_create: bool = False - pedimento: Optional[PedimentosResponse] = None - pedimento_id: Optional[int] = None diff --git a/backend/api/v1/modules/a76/pedmientos/services/pedimento_config_additional.py b/backend/api/v1/modules/a76/pedmientos/services/pedimento_config_additional.py deleted file mode 100644 index 5f3a8141..00000000 --- a/backend/api/v1/modules/a76/pedmientos/services/pedimento_config_additional.py +++ /dev/null @@ -1,85 +0,0 @@ -""" -Service layer for PedimentoConfigAdditional CRUD operations -""" - -from typing import Optional - -from sqlalchemy.orm import Session - -from ..dtos.pedimento_config_additional import ( - PedimentoConfigAdditionalCreate, - PedimentoConfigAdditionalUpdate, -) -from ..models.pedimento_config_additional import PedimentoConfigAdditional - - -class PedimentoConfigAdditionalService: - """Service class for PedimentoConfigAdditional business logic""" - - @staticmethod - def get_by_pedimento_id( - db: Session, pedimento_id: int, tenant_id: int, company_id: int - ) -> Optional[PedimentoConfigAdditional]: - """Get config by pedimento ID""" - return ( - db.query(PedimentoConfigAdditional) - .filter( - PedimentoConfigAdditional.pedimento_id == pedimento_id, - PedimentoConfigAdditional.tenant_id == tenant_id, - PedimentoConfigAdditional.company_id == company_id, - ) - .first() - ) - - @staticmethod - def create( - db: Session, - config_data: PedimentoConfigAdditionalCreate, - tenant_id: int, - company_id: int, - ) -> PedimentoConfigAdditional: - """Create a new config""" - config = PedimentoConfigAdditional(**config_data.model_dump()) - config.tenant_id = tenant_id - config.company_id = company_id - - db.add(config) - db.commit() - db.refresh(config) - return config - - @staticmethod - def update( - db: Session, - pedimento_id: int, - tenant_id: int, - company_id: int, - config_data: PedimentoConfigAdditionalUpdate, - ) -> Optional[PedimentoConfigAdditional]: - """Update config""" - config = PedimentoConfigAdditionalService.get_by_pedimento_id( - db, pedimento_id, tenant_id, company_id - ) - if not config: - return None - - update_data = config_data.model_dump(exclude_unset=True) - for field, value in update_data.items(): - setattr(config, field, value) - - db.commit() - db.refresh(config) - return config - - @staticmethod - def delete(db: Session, pedimento_id: int, tenant_id: int, company_id: int) -> bool: - """Delete config""" - config = PedimentoConfigAdditionalService.get_by_pedimento_id( - db, pedimento_id, tenant_id, company_id - ) - if not config: - return False - - db.delete(config) - db.commit() - return True diff --git a/backend/api/v1/modules/a76/pedmientos/services/pedimento_config_calculations.py b/backend/api/v1/modules/a76/pedmientos/services/pedimento_config_calculations.py deleted file mode 100644 index 50dbaeb0..00000000 --- a/backend/api/v1/modules/a76/pedmientos/services/pedimento_config_calculations.py +++ /dev/null @@ -1,85 +0,0 @@ -""" -Service layer for PedimentoConfigCalculations CRUD operations -""" - -from typing import Optional - -from sqlalchemy.orm import Session - -from ..dtos.pedimento_config_calculations import ( - PedimentoConfigCalculationsCreate, - PedimentoConfigCalculationsUpdate, -) -from ..models.pedimento_config_calculations import PedimentoConfigCalculations - - -class PedimentoConfigCalculationsService: - """Service class for PedimentoConfigCalculations business logic""" - - @staticmethod - def get_by_pedimento_id( - db: Session, pedimento_id: int, tenant_id: int, company_id: int - ) -> Optional[PedimentoConfigCalculations]: - """Get config by pedimento ID""" - return ( - db.query(PedimentoConfigCalculations) - .filter( - PedimentoConfigCalculations.pedimento_id == pedimento_id, - PedimentoConfigCalculations.tenant_id == tenant_id, - PedimentoConfigCalculations.company_id == company_id, - ) - .first() - ) - - @staticmethod - def create( - db: Session, - config_data: PedimentoConfigCalculationsCreate, - tenant_id: int, - company_id: int, - ) -> PedimentoConfigCalculations: - """Create a new config""" - config = PedimentoConfigCalculations(**config_data.model_dump()) - config.tenant_id = tenant_id - config.company_id = company_id - - db.add(config) - db.commit() - db.refresh(config) - return config - - @staticmethod - def update( - db: Session, - pedimento_id: int, - tenant_id: int, - company_id: int, - config_data: PedimentoConfigCalculationsUpdate, - ) -> Optional[PedimentoConfigCalculations]: - """Update config""" - config = PedimentoConfigCalculationsService.get_by_pedimento_id( - db, pedimento_id, tenant_id, company_id - ) - if not config: - return None - - update_data = config_data.model_dump(exclude_unset=True) - for field, value in update_data.items(): - setattr(config, field, value) - - db.commit() - db.refresh(config) - return config - - @staticmethod - def delete(db: Session, pedimento_id: int, tenant_id: int, company_id: int) -> bool: - """Delete config""" - config = PedimentoConfigCalculationsService.get_by_pedimento_id( - db, pedimento_id, tenant_id, company_id - ) - if not config: - return False - - db.delete(config) - db.commit() - return True diff --git a/backend/api/v1/modules/a76/pedmientos/services/pedimento_config_parameters.py b/backend/api/v1/modules/a76/pedmientos/services/pedimento_config_parameters.py deleted file mode 100644 index 321836c0..00000000 --- a/backend/api/v1/modules/a76/pedmientos/services/pedimento_config_parameters.py +++ /dev/null @@ -1,77 +0,0 @@ -""" -Service layer for PedimentoConfigParameters CRUD operations -""" - -from typing import Optional - -from sqlalchemy.orm import Session - -from ..dtos.pedimento_config_parameters import ( - PedimentoConfigParametersCreate, - PedimentoConfigParametersUpdate, -) -from ..models.pedimento_config_parameters import PedimentoConfigParameters - - -class PedimentoConfigParametersService: - """Service class for PedimentoConfigParameters business logic""" - - @staticmethod - def get_by_pedimento_id( - db: Session, pedimento_id: int, tenant_id: int, company_id: int = None - ) -> Optional[PedimentoConfigParameters]: - """Get config by pedimento ID""" - return ( - db.query(PedimentoConfigParameters) - .filter( - PedimentoConfigParameters.pedimento_id == pedimento_id, - PedimentoConfigParameters.tenant_id == tenant_id, - ) - .first() - ) - - @staticmethod - def create( - db: Session, config_data: PedimentoConfigParametersCreate - ) -> PedimentoConfigParameters: - """Create a new config""" - config = PedimentoConfigParameters(**config_data.model_dump()) - db.add(config) - db.commit() - db.refresh(config) - return config - - @staticmethod - def update( - db: Session, - pedimento_id: int, - tenant_id: int, - config_data: PedimentoConfigParametersUpdate, - ) -> Optional[PedimentoConfigParameters]: - """Update config""" - config = PedimentoConfigParametersService.get_by_pedimento_id( - db, pedimento_id, tenant_id - ) - if not config: - return None - - update_data = config_data.model_dump(exclude_unset=True) - for field, value in update_data.items(): - setattr(config, field, value) - - db.commit() - db.refresh(config) - return config - - @staticmethod - def delete(db: Session, pedimento_id: int, tenant_id: int) -> bool: - """Delete config""" - config = PedimentoConfigParametersService.get_by_pedimento_id( - db, pedimento_id, tenant_id - ) - if not config: - return False - - db.delete(config) - db.commit() - return True diff --git a/backend/api/v1/modules/a76/pedmientos/services/pedimento_config_surcharges.py b/backend/api/v1/modules/a76/pedmientos/services/pedimento_config_surcharges.py deleted file mode 100644 index a1f13e17..00000000 --- a/backend/api/v1/modules/a76/pedmientos/services/pedimento_config_surcharges.py +++ /dev/null @@ -1,77 +0,0 @@ -""" -Service layer for PedimentoConfigSurcharges CRUD operations -""" - -from typing import Optional - -from sqlalchemy.orm import Session - -from ..dtos.pedimento_config_surcharges import ( - PedimentoConfigSurchargesCreate, - PedimentoConfigSurchargesUpdate, -) -from ..models.pedimento_config_surcharges import PedimentoConfigSurcharges - - -class PedimentoConfigSurchargesService: - """Service class for PedimentoConfigSurcharges business logic""" - - @staticmethod - def get_by_pedimento_id( - db: Session, pedimento_id: int, tenant_id: int, company_id: int = None - ) -> Optional[PedimentoConfigSurcharges]: - """Get config by pedimento ID""" - return ( - db.query(PedimentoConfigSurcharges) - .filter( - PedimentoConfigSurcharges.pedimento_id == pedimento_id, - PedimentoConfigSurcharges.tenant_id == tenant_id, - ) - .first() - ) - - @staticmethod - def create( - db: Session, config_data: PedimentoConfigSurchargesCreate - ) -> PedimentoConfigSurcharges: - """Create a new config""" - config = PedimentoConfigSurcharges(**config_data.model_dump()) - db.add(config) - db.commit() - db.refresh(config) - return config - - @staticmethod - def update( - db: Session, - pedimento_id: int, - tenant_id: int, - config_data: PedimentoConfigSurchargesUpdate, - ) -> Optional[PedimentoConfigSurcharges]: - """Update config""" - config = PedimentoConfigSurchargesService.get_by_pedimento_id( - db, pedimento_id, tenant_id - ) - if not config: - return None - - update_data = config_data.model_dump(exclude_unset=True) - for field, value in update_data.items(): - setattr(config, field, value) - - db.commit() - db.refresh(config) - return config - - @staticmethod - def delete(db: Session, pedimento_id: int, tenant_id: int) -> bool: - """Delete config""" - config = PedimentoConfigSurchargesService.get_by_pedimento_id( - db, pedimento_id, tenant_id - ) - if not config: - return False - - db.delete(config) - db.commit() - return True diff --git a/backend/api/v1/modules/a76/pedmientos/services/pedimento_config_update_rectification.py b/backend/api/v1/modules/a76/pedmientos/services/pedimento_config_update_rectification.py deleted file mode 100644 index 65f0360f..00000000 --- a/backend/api/v1/modules/a76/pedmientos/services/pedimento_config_update_rectification.py +++ /dev/null @@ -1,79 +0,0 @@ -""" -Service layer for PedimentoConfigUpdateRectification CRUD operations -""" - -from typing import Optional - -from sqlalchemy.orm import Session - -from ..dtos.pedimento_config_update_rectification import ( - PedimentoConfigUpdateRectificationCreate, - PedimentoConfigUpdateRectificationUpdate, -) -from ..models.pedimento_config_update_rectification import ( - PedimentoConfigUpdateRectification, -) - - -class PedimentoConfigUpdateRectificationService: - """Service class for PedimentoConfigUpdateRectification business logic""" - - @staticmethod - def get_by_pedimento_id( - db: Session, pedimento_id: int, tenant_id: int, company_id: int = None - ) -> Optional[PedimentoConfigUpdateRectification]: - """Get config by pedimento ID""" - return ( - db.query(PedimentoConfigUpdateRectification) - .filter( - PedimentoConfigUpdateRectification.pedimento_id == pedimento_id, - PedimentoConfigUpdateRectification.tenant_id == tenant_id, - ) - .first() - ) - - @staticmethod - def create( - db: Session, config_data: PedimentoConfigUpdateRectificationCreate - ) -> PedimentoConfigUpdateRectification: - """Create a new config""" - config = PedimentoConfigUpdateRectification(**config_data.model_dump()) - db.add(config) - db.commit() - db.refresh(config) - return config - - @staticmethod - def update( - db: Session, - pedimento_id: int, - tenant_id: int, - config_data: PedimentoConfigUpdateRectificationUpdate, - ) -> Optional[PedimentoConfigUpdateRectification]: - """Update config""" - config = PedimentoConfigUpdateRectificationService.get_by_pedimento_id( - db, pedimento_id, tenant_id - ) - if not config: - return None - - update_data = config_data.model_dump(exclude_unset=True) - for field, value in update_data.items(): - setattr(config, field, value) - - db.commit() - db.refresh(config) - return config - - @staticmethod - def delete(db: Session, pedimento_id: int, tenant_id: int) -> bool: - """Delete config""" - config = PedimentoConfigUpdateRectificationService.get_by_pedimento_id( - db, pedimento_id, tenant_id - ) - if not config: - return False - - db.delete(config) - db.commit() - return True diff --git a/backend/api/v1/modules/a76/pedmientos/services/pedimento_config_updates.py b/backend/api/v1/modules/a76/pedmientos/services/pedimento_config_updates.py deleted file mode 100644 index 562a4a3e..00000000 --- a/backend/api/v1/modules/a76/pedmientos/services/pedimento_config_updates.py +++ /dev/null @@ -1,77 +0,0 @@ -""" -Service layer for PedimentoConfigUpdates CRUD operations -""" - -from typing import Optional - -from sqlalchemy.orm import Session - -from ..dtos.pedimento_config_updates import ( - PedimentoConfigUpdatesCreate, - PedimentoConfigUpdatesUpdate, -) -from ..models.pedimento_config_updates import PedimentoConfigUpdates - - -class PedimentoConfigUpdatesService: - """Service class for PedimentoConfigUpdates business logic""" - - @staticmethod - def get_by_pedimento_id( - db: Session, pedimento_id: int, tenant_id: int, company_id: int = None - ) -> Optional[PedimentoConfigUpdates]: - """Get config by pedimento ID""" - return ( - db.query(PedimentoConfigUpdates) - .filter( - PedimentoConfigUpdates.pedimento_id == pedimento_id, - PedimentoConfigUpdates.tenant_id == tenant_id, - ) - .first() - ) - - @staticmethod - def create( - db: Session, config_data: PedimentoConfigUpdatesCreate - ) -> PedimentoConfigUpdates: - """Create a new config""" - config = PedimentoConfigUpdates(**config_data.model_dump()) - db.add(config) - db.commit() - db.refresh(config) - return config - - @staticmethod - def update( - db: Session, - pedimento_id: int, - tenant_id: int, - config_data: PedimentoConfigUpdatesUpdate, - ) -> Optional[PedimentoConfigUpdates]: - """Update config""" - config = PedimentoConfigUpdatesService.get_by_pedimento_id( - db, pedimento_id, tenant_id - ) - if not config: - return None - - update_data = config_data.model_dump(exclude_unset=True) - for field, value in update_data.items(): - setattr(config, field, value) - - db.commit() - db.refresh(config) - return config - - @staticmethod - def delete(db: Session, pedimento_id: int, tenant_id: int) -> bool: - """Delete config""" - config = PedimentoConfigUpdatesService.get_by_pedimento_id( - db, pedimento_id, tenant_id - ) - if not config: - return False - - db.delete(config) - db.commit() - return True diff --git a/backend/api/v1/modules/a76/pedmientos/services/pedimento_customs_offices.py b/backend/api/v1/modules/a76/pedmientos/services/pedimento_customs_offices.py deleted file mode 100644 index 1cb07a17..00000000 --- a/backend/api/v1/modules/a76/pedmientos/services/pedimento_customs_offices.py +++ /dev/null @@ -1,77 +0,0 @@ -""" -Service layer for PedimentoCustomsOffices CRUD operations -""" - -from typing import Optional - -from sqlalchemy.orm import Session - -from ..dtos.pedimento_customs_offices import ( - PedimentoCustomsOfficesCreate, - PedimentoCustomsOfficesUpdate, -) -from ..models.pedimento_customs_offices import PedimentoCustomsOffices - - -class PedimentoCustomsOfficesService: - """Service class for PedimentoCustomsOffices business logic""" - - @staticmethod - def get_by_pedimento_id( - db: Session, pedimento_id: int, tenant_id: int, company_id: int = None - ) -> Optional[PedimentoCustomsOffices]: - """Get customs offices by pedimento ID""" - return ( - db.query(PedimentoCustomsOffices) - .filter( - PedimentoCustomsOffices.pedimento_id == pedimento_id, - PedimentoCustomsOffices.tenant_id == tenant_id, - ) - .first() - ) - - @staticmethod - def create( - db: Session, data: PedimentoCustomsOfficesCreate - ) -> PedimentoCustomsOffices: - """Create new customs offices""" - offices = PedimentoCustomsOffices(**data.model_dump()) - db.add(offices) - db.commit() - db.refresh(offices) - return offices - - @staticmethod - def update( - db: Session, - pedimento_id: int, - tenant_id: int, - data: PedimentoCustomsOfficesUpdate, - ) -> Optional[PedimentoCustomsOffices]: - """Update customs offices""" - offices = PedimentoCustomsOfficesService.get_by_pedimento_id( - db, pedimento_id, tenant_id - ) - if not offices: - return None - - update_data = data.model_dump(exclude_unset=True) - for field, value in update_data.items(): - setattr(offices, field, value) - - db.commit() - db.refresh(offices) - return offices - - @staticmethod - def delete(db: Session, pedimento_id: int, tenant_id: int) -> bool: - """Delete customs offices""" - offices = PedimentoCustomsOfficesService.get_by_pedimento_id( - db, pedimento_id, tenant_id - ) - if not offices: - return False - - db.delete(offices) - db.commit() - return True diff --git a/backend/api/v1/modules/a76/pedmientos/services/pedimento_dates.py b/backend/api/v1/modules/a76/pedmientos/services/pedimento_dates.py deleted file mode 100644 index 1736ea2a..00000000 --- a/backend/api/v1/modules/a76/pedmientos/services/pedimento_dates.py +++ /dev/null @@ -1,68 +0,0 @@ -""" -Service layer for PedimentoDates CRUD operations -""" - -import logging -from typing import Optional - -from sqlalchemy.orm import Session - -from ..dtos.pedimento_dates import PedimentoDatesCreate, PedimentoDatesUpdate -from ..models.pedimento_dates import PedimentoDates - -logger = logging.getLogger(__name__) - - -class PedimentoDatesService: - """Service class for PedimentoDates business logic""" - - @staticmethod - def get_by_pedimento_id( - db: Session, pedimento_id: int, tenant_id: int, company_id: int = None - ) -> Optional[PedimentoDates]: - """Get dates by pedimento ID""" - return ( - db.query(PedimentoDates) - .filter( - PedimentoDates.pedimento_id == pedimento_id, - PedimentoDates.tenant_id == tenant_id, - ) - .first() - ) - - @staticmethod - def create(db: Session, data: PedimentoDatesCreate) -> PedimentoDates: - """Create new pedimento dates""" - dates = PedimentoDates(**data.model_dump()) - db.add(dates) - db.commit() - db.refresh(dates) - return dates - - @staticmethod - def update( - db: Session, pedimento_id: int, tenant_id: int, data: PedimentoDatesUpdate - ) -> Optional[PedimentoDates]: - """Update pedimento dates""" - dates = PedimentoDatesService.get_by_pedimento_id(db, pedimento_id, tenant_id) - if not dates: - return None - - update_data = data.model_dump(exclude_unset=True) - for field, value in update_data.items(): - setattr(dates, field, value) - - db.commit() - db.refresh(dates) - return dates - - @staticmethod - def delete(db: Session, pedimento_id: int, tenant_id: int) -> bool: - """Delete pedimento dates""" - dates = PedimentoDatesService.get_by_pedimento_id(db, pedimento_id, tenant_id) - if not dates: - return False - - db.delete(dates) - db.commit() - return True diff --git a/backend/api/v1/modules/a76/pedmientos/services/pedimento_decrementables.py b/backend/api/v1/modules/a76/pedmientos/services/pedimento_decrementables.py deleted file mode 100644 index c7855ea8..00000000 --- a/backend/api/v1/modules/a76/pedmientos/services/pedimento_decrementables.py +++ /dev/null @@ -1,77 +0,0 @@ -""" -Service layer for PedimentoDecrementables CRUD operations -""" - -from typing import Optional - -from sqlalchemy.orm import Session - -from ..dtos.pedimento_decrementables import ( - PedimentoDecrementablesCreate, - PedimentoDecrementablesUpdate, -) -from ..models.pedimento_decrementables import PedimentoDecrementables - - -class PedimentoDecrementablesService: - """Service class for PedimentoDecrementables business logic""" - - @staticmethod - def get_by_pedimento_id( - db: Session, pedimento_id: int, tenant_id: int, company_id: int = None - ) -> Optional[PedimentoDecrementables]: - """Get decrementables by pedimento ID""" - return ( - db.query(PedimentoDecrementables) - .filter( - PedimentoDecrementables.pedimento_id == pedimento_id, - PedimentoDecrementables.tenant_id == tenant_id, - ) - .first() - ) - - @staticmethod - def create( - db: Session, data: PedimentoDecrementablesCreate - ) -> PedimentoDecrementables: - """Create new decrementables""" - decrementables = PedimentoDecrementables(**data.model_dump()) - db.add(decrementables) - db.commit() - db.refresh(decrementables) - return decrementables - - @staticmethod - def update( - db: Session, - pedimento_id: int, - tenant_id: int, - data: PedimentoDecrementablesUpdate, - ) -> Optional[PedimentoDecrementables]: - """Update decrementables""" - decrementables = PedimentoDecrementablesService.get_by_pedimento_id( - db, pedimento_id, tenant_id - ) - if not decrementables: - return None - - update_data = data.model_dump(exclude_unset=True) - for field, value in update_data.items(): - setattr(decrementables, field, value) - - db.commit() - db.refresh(decrementables) - return decrementables - - @staticmethod - def delete(db: Session, pedimento_id: int, tenant_id: int) -> bool: - """Delete decrementables""" - decrementables = PedimentoDecrementablesService.get_by_pedimento_id( - db, pedimento_id, tenant_id - ) - if not decrementables: - return False - - db.delete(decrementables) - db.commit() - return True diff --git a/backend/api/v1/modules/a76/pedmientos/services/pedimento_incrementables.py b/backend/api/v1/modules/a76/pedmientos/services/pedimento_incrementables.py deleted file mode 100644 index 9125d295..00000000 --- a/backend/api/v1/modules/a76/pedmientos/services/pedimento_incrementables.py +++ /dev/null @@ -1,77 +0,0 @@ -""" -Service layer for PedimentoIncrementables CRUD operations -""" - -from typing import Optional - -from sqlalchemy.orm import Session - -from ..dtos.pedimento_incrementables import ( - PedimentoIncrementablesCreate, - PedimentoIncrementablesUpdate, -) -from ..models.pedimento_incrementables import PedimentoIncrementables - - -class PedimentoIncrementablesService: - """Service class for PedimentoIncrementables business logic""" - - @staticmethod - def get_by_pedimento_id( - db: Session, pedimento_id: int, tenant_id: int, company_id: int = None - ) -> Optional[PedimentoIncrementables]: - """Get incrementables by pedimento ID""" - return ( - db.query(PedimentoIncrementables) - .filter( - PedimentoIncrementables.pedimento_id == pedimento_id, - PedimentoIncrementables.tenant_id == tenant_id, - ) - .first() - ) - - @staticmethod - def create( - db: Session, data: PedimentoIncrementablesCreate - ) -> PedimentoIncrementables: - """Create new incrementables""" - incrementables = PedimentoIncrementables(**data.model_dump()) - db.add(incrementables) - db.commit() - db.refresh(incrementables) - return incrementables - - @staticmethod - def update( - db: Session, - pedimento_id: int, - tenant_id: int, - data: PedimentoIncrementablesUpdate, - ) -> Optional[PedimentoIncrementables]: - """Update incrementables""" - incrementables = PedimentoIncrementablesService.get_by_pedimento_id( - db, pedimento_id, tenant_id - ) - if not incrementables: - return None - - update_data = data.model_dump(exclude_unset=True) - for field, value in update_data.items(): - setattr(incrementables, field, value) - - db.commit() - db.refresh(incrementables) - return incrementables - - @staticmethod - def delete(db: Session, pedimento_id: int, tenant_id: int) -> bool: - """Delete incrementables""" - incrementables = PedimentoIncrementablesService.get_by_pedimento_id( - db, pedimento_id, tenant_id - ) - if not incrementables: - return False - - db.delete(incrementables) - db.commit() - return True diff --git a/backend/api/v1/modules/a76/pedmientos/services/pedimento_indexes.py b/backend/api/v1/modules/a76/pedmientos/services/pedimento_indexes.py deleted file mode 100644 index 2bce8ed0..00000000 --- a/backend/api/v1/modules/a76/pedmientos/services/pedimento_indexes.py +++ /dev/null @@ -1,69 +0,0 @@ -""" -Service layer for PedimentoIndexes CRUD operations -""" - -from typing import Optional - -from sqlalchemy.orm import Session - -from ..dtos.pedimento_indexes import PedimentoIndexesCreate, PedimentoIndexesUpdate -from ..models.pedimento_indexes import PedimentoIndexes - - -class PedimentoIndexesService: - """Service class for PedimentoIndexes business logic""" - - @staticmethod - def get_by_pedimento_id( - db: Session, pedimento_id: int, tenant_id: int, company_id: int = None - ) -> Optional[PedimentoIndexes]: - """Get indexes by pedimento ID""" - return ( - db.query(PedimentoIndexes) - .filter( - PedimentoIndexes.pedimento_id == pedimento_id, - PedimentoIndexes.tenant_id == tenant_id, - ) - .first() - ) - - @staticmethod - def create(db: Session, data: PedimentoIndexesCreate) -> PedimentoIndexes: - """Create new indexes""" - indexes = PedimentoIndexes(**data.model_dump()) - db.add(indexes) - db.commit() - db.refresh(indexes) - return indexes - - @staticmethod - def update( - db: Session, pedimento_id: int, tenant_id: int, data: PedimentoIndexesUpdate - ) -> Optional[PedimentoIndexes]: - """Update indexes""" - indexes = PedimentoIndexesService.get_by_pedimento_id( - db, pedimento_id, tenant_id - ) - if not indexes: - return None - - update_data = data.model_dump(exclude_unset=True) - for field, value in update_data.items(): - setattr(indexes, field, value) - - db.commit() - db.refresh(indexes) - return indexes - - @staticmethod - def delete(db: Session, pedimento_id: int, tenant_id: int) -> bool: - """Delete indexes""" - indexes = PedimentoIndexesService.get_by_pedimento_id( - db, pedimento_id, tenant_id - ) - if not indexes: - return False - - db.delete(indexes) - db.commit() - return True diff --git a/backend/api/v1/modules/a76/pedmientos/services/pedimento_payments.py b/backend/api/v1/modules/a76/pedmientos/services/pedimento_payments.py deleted file mode 100644 index a4b8539b..00000000 --- a/backend/api/v1/modules/a76/pedmientos/services/pedimento_payments.py +++ /dev/null @@ -1,69 +0,0 @@ -""" -Service layer for PedimentoPayments CRUD operations -""" - -from typing import Optional - -from sqlalchemy.orm import Session - -from ..dtos.pedimento_payments import PedimentoPaymentsCreate, PedimentoPaymentsUpdate -from ..models.pedimento_payments import PedimentoPayments - - -class PedimentoPaymentsService: - """Service class for PedimentoPayments business logic""" - - @staticmethod - def get_by_pedimento_id( - db: Session, pedimento_id: int, tenant_id: int, company_id: int = None - ) -> Optional[PedimentoPayments]: - """Get payments by pedimento ID""" - return ( - db.query(PedimentoPayments) - .filter( - PedimentoPayments.pedimento_id == pedimento_id, - PedimentoPayments.tenant_id == tenant_id, - ) - .first() - ) - - @staticmethod - def create(db: Session, data: PedimentoPaymentsCreate) -> PedimentoPayments: - """Create new payments""" - payments = PedimentoPayments(**data.model_dump()) - db.add(payments) - db.commit() - db.refresh(payments) - return payments - - @staticmethod - def update( - db: Session, pedimento_id: int, tenant_id: int, data: PedimentoPaymentsUpdate - ) -> Optional[PedimentoPayments]: - """Update payments""" - payments = PedimentoPaymentsService.get_by_pedimento_id( - db, pedimento_id, tenant_id - ) - if not payments: - return None - - update_data = data.model_dump(exclude_unset=True) - for field, value in update_data.items(): - setattr(payments, field, value) - - db.commit() - db.refresh(payments) - return payments - - @staticmethod - def delete(db: Session, pedimento_id: int, tenant_id: int) -> bool: - """Delete payments""" - payments = PedimentoPaymentsService.get_by_pedimento_id( - db, pedimento_id, tenant_id - ) - if not payments: - return False - - db.delete(payments) - db.commit() - return True diff --git a/backend/api/v1/modules/a76/pedmientos/services/pedimento_rectification_destination.py b/backend/api/v1/modules/a76/pedmientos/services/pedimento_rectification_destination.py deleted file mode 100644 index eab865ff..00000000 --- a/backend/api/v1/modules/a76/pedmientos/services/pedimento_rectification_destination.py +++ /dev/null @@ -1,80 +0,0 @@ -""" -Service layer for PedimentoRectificationDestination CRUD operations -""" - -from typing import Optional - -from sqlalchemy.orm import Session - -from ..dtos.pedimento_rectification_destination import ( - PedimentoRectificationDestinationCreate, - PedimentoRectificationDestinationUpdate, -) -from ..models.pedimento_rectification_destination import ( - PedimentoRectificationDestination, -) - - -class PedimentoRectificationDestinationService: - """Service class for PedimentoRectificationDestination business logic""" - - @staticmethod - def get_by_pedimento_id( - db: Session, pedimento_id: int, tenant_id: int, company_id: int = None - ) -> Optional[PedimentoRectificationDestination]: - """Get rectification destination by pedimento ID""" - return ( - db.query(PedimentoRectificationDestination) - .filter( - PedimentoRectificationDestination.pedimento_id == pedimento_id, - PedimentoRectificationDestination.tenant_id == tenant_id, - PedimentoRectificationDestination.company_id == company_id, - ) - .first() - ) - - @staticmethod - def create( - db: Session, data: PedimentoRectificationDestinationCreate - ) -> PedimentoRectificationDestination: - """Create new rectification destination""" - rectification = PedimentoRectificationDestination(**data.model_dump()) - db.add(rectification) - db.commit() - db.refresh(rectification) - return rectification - - @staticmethod - def update( - db: Session, - pedimento_id: int, - tenant_id: int, - data: PedimentoRectificationDestinationUpdate, - ) -> Optional[PedimentoRectificationDestination]: - """Update rectification destination""" - rectification = PedimentoRectificationDestinationService.get_by_pedimento_id( - db, pedimento_id, tenant_id - ) - if not rectification: - return None - - update_data = data.model_dump(exclude_unset=True) - for field, value in update_data.items(): - setattr(rectification, field, value) - - db.commit() - db.refresh(rectification) - return rectification - - @staticmethod - def delete(db: Session, pedimento_id: int, tenant_id: int) -> bool: - """Delete rectification destination""" - rectification = PedimentoRectificationDestinationService.get_by_pedimento_id( - db, pedimento_id, tenant_id - ) - if not rectification: - return False - - db.delete(rectification) - db.commit() - return True diff --git a/backend/api/v1/modules/a76/pedmientos/services/pedimento_rectification_origin.py b/backend/api/v1/modules/a76/pedmientos/services/pedimento_rectification_origin.py deleted file mode 100644 index 39304792..00000000 --- a/backend/api/v1/modules/a76/pedmientos/services/pedimento_rectification_origin.py +++ /dev/null @@ -1,78 +0,0 @@ -""" -Service layer for PedimentoRectificationOrigin CRUD operations -""" - -from typing import Optional - -from sqlalchemy.orm import Session - -from ..dtos.pedimento_rectification_origin import ( - PedimentoRectificationOriginCreate, - PedimentoRectificationOriginUpdate, -) -from ..models.pedimento_rectification_origin import PedimentoRectificationOrigin - - -class PedimentoRectificationOriginService: - """Service class for PedimentoRectificationOrigin business logic""" - - @staticmethod - def get_by_pedimento_id( - db: Session, pedimento_id: int, tenant_id: int, company_id: int = None - ) -> Optional[PedimentoRectificationOrigin]: - """Get rectification origin by pedimento ID""" - return ( - db.query(PedimentoRectificationOrigin) - .filter( - PedimentoRectificationOrigin.pedimento_id == pedimento_id, - PedimentoRectificationOrigin.tenant_id == tenant_id, - PedimentoRectificationOrigin.company_id == company_id, - ) - .first() - ) - - @staticmethod - def create( - db: Session, data: PedimentoRectificationOriginCreate - ) -> PedimentoRectificationOrigin: - """Create new rectification origin""" - rectification = PedimentoRectificationOrigin(**data.model_dump()) - db.add(rectification) - db.commit() - db.refresh(rectification) - return rectification - - @staticmethod - def update( - db: Session, - pedimento_id: int, - tenant_id: int, - data: PedimentoRectificationOriginUpdate, - ) -> Optional[PedimentoRectificationOrigin]: - """Update rectification origin""" - rectification = PedimentoRectificationOriginService.get_by_pedimento_id( - db, pedimento_id, tenant_id - ) - if not rectification: - return None - - update_data = data.model_dump(exclude_unset=True) - for field, value in update_data.items(): - setattr(rectification, field, value) - - db.commit() - db.refresh(rectification) - return rectification - - @staticmethod - def delete(db: Session, pedimento_id: int, tenant_id: int) -> bool: - """Delete rectification origin""" - rectification = PedimentoRectificationOriginService.get_by_pedimento_id( - db, pedimento_id, tenant_id - ) - if not rectification: - return False - - db.delete(rectification) - db.commit() - return True diff --git a/backend/api/v1/modules/a76/pedmientos/services/pedimento_transport_means.py b/backend/api/v1/modules/a76/pedmientos/services/pedimento_transport_means.py deleted file mode 100644 index 0c6c5e3a..00000000 --- a/backend/api/v1/modules/a76/pedmientos/services/pedimento_transport_means.py +++ /dev/null @@ -1,77 +0,0 @@ -""" -Service layer for PedimentoTransportMeans CRUD operations -""" - -from typing import Optional - -from sqlalchemy.orm import Session - -from ..dtos.pedimento_transport_means import ( - PedimentoTransportMeansCreate, - PedimentoTransportMeansUpdate, -) -from ..models.pedimento_transport_means import PedimentoTransportMeans - - -class PedimentoTransportMeansService: - """Service class for PedimentoTransportMeans business logic""" - - @staticmethod - def get_by_pedimento_id( - db: Session, pedimento_id: int, tenant_id: int, company_id: int = None - ) -> Optional[PedimentoTransportMeans]: - """Get transport means by pedimento ID""" - return ( - db.query(PedimentoTransportMeans) - .filter( - PedimentoTransportMeans.pedimento_id == pedimento_id, - PedimentoTransportMeans.tenant_id == tenant_id, - ) - .first() - ) - - @staticmethod - def create( - db: Session, data: PedimentoTransportMeansCreate - ) -> PedimentoTransportMeans: - """Create new transport means""" - transport = PedimentoTransportMeans(**data.model_dump()) - db.add(transport) - db.commit() - db.refresh(transport) - return transport - - @staticmethod - def update( - db: Session, - pedimento_id: int, - tenant_id: int, - data: PedimentoTransportMeansUpdate, - ) -> Optional[PedimentoTransportMeans]: - """Update transport means""" - transport = PedimentoTransportMeansService.get_by_pedimento_id( - db, pedimento_id, tenant_id - ) - if not transport: - return None - - update_data = data.model_dump(exclude_unset=True) - for field, value in update_data.items(): - setattr(transport, field, value) - - db.commit() - db.refresh(transport) - return transport - - @staticmethod - def delete(db: Session, pedimento_id: int, tenant_id: int) -> bool: - """Delete transport means""" - transport = PedimentoTransportMeansService.get_by_pedimento_id( - db, pedimento_id, tenant_id - ) - if not transport: - return False - - db.delete(transport) - db.commit() - return True diff --git a/backend/api/v1/modules/a76/pedmientos/services/pedimento_validation.py b/backend/api/v1/modules/a76/pedmientos/services/pedimento_validation.py deleted file mode 100644 index ceea59e2..00000000 --- a/backend/api/v1/modules/a76/pedmientos/services/pedimento_validation.py +++ /dev/null @@ -1,72 +0,0 @@ -""" -Service layer for PedimentoValidation CRUD operations -""" - -from typing import Optional - -from sqlalchemy.orm import Session - -from ..dtos.pedimento_validation import ( - PedimentoValidationCreate, - PedimentoValidationUpdate, -) -from ..models.pedimento_validation import PedimentoValidation - - -class PedimentoValidationService: - """Service class for PedimentoValidation business logic""" - - @staticmethod - def get_by_pedimento_id( - db: Session, pedimento_id: int, tenant_id: int, company_id: int = None - ) -> Optional[PedimentoValidation]: - """Get validation by pedimento ID""" - return ( - db.query(PedimentoValidation) - .filter( - PedimentoValidation.pedimento_id == pedimento_id, - PedimentoValidation.tenant_id == tenant_id, - ) - .first() - ) - - @staticmethod - def create(db: Session, data: PedimentoValidationCreate) -> PedimentoValidation: - """Create new validation""" - validation = PedimentoValidation(**data.model_dump()) - db.add(validation) - db.commit() - db.refresh(validation) - return validation - - @staticmethod - def update( - db: Session, pedimento_id: int, tenant_id: int, data: PedimentoValidationUpdate - ) -> Optional[PedimentoValidation]: - """Update validation""" - validation = PedimentoValidationService.get_by_pedimento_id( - db, pedimento_id, tenant_id - ) - if not validation: - return None - - update_data = data.model_dump(exclude_unset=True) - for field, value in update_data.items(): - setattr(validation, field, value) - - db.commit() - db.refresh(validation) - return validation - - @staticmethod - def delete(db: Session, pedimento_id: int, tenant_id: int) -> bool: - """Delete validation""" - validation = PedimentoValidationService.get_by_pedimento_id( - db, pedimento_id, tenant_id - ) - if not validation: - return False - - db.delete(validation) - db.commit() - return True diff --git a/backend/api/v1/modules/a76/pedmientos/services/pedimentos.py b/backend/api/v1/modules/a76/pedmientos/services/pedimentos.py deleted file mode 100644 index 93372d6a..00000000 --- a/backend/api/v1/modules/a76/pedmientos/services/pedimentos.py +++ /dev/null @@ -1,654 +0,0 @@ -""" -Service layer for Pedimentos CRUD operations -""" - -import logging -import re -from typing import Any, Dict, List, Optional - -from sqlalchemy import desc, func -from sqlalchemy.orm import Session, joinedload -from sqlalchemy.orm import selectinload -from sqlalchemy.exc import IntegrityError -from datetime import datetime - -from api.v1.modules.a76.audit_log.models import AuditLog -from api.v1.modules.a76.audit_log.services.service import AuditService -from core.context import get_user_context -from ..dtos.pedimentos import PedimentosCreate, PedimentosUpdate - -from .pedimento_config_additional import PedimentoConfigAdditionalService -from .pedimento_config_calculations import PedimentoConfigCalculationsService -from .pedimento_config_parameters import PedimentoConfigParametersService -from .pedimento_config_surcharges import PedimentoConfigSurchargesService -from .pedimento_config_update_rectification import PedimentoConfigUpdateRectificationService -from .pedimento_config_updates import PedimentoConfigUpdatesService -from .pedimento_customs_offices import PedimentoCustomsOfficesService -from .pedimento_dates import PedimentoDatesService -from .pedimento_decrementables import PedimentoDecrementablesService -from .pedimento_incrementables import PedimentoIncrementablesService -from .pedimento_indexes import PedimentoIndexesService -from .pedimento_payments import PedimentoPaymentsService -from .pedimento_rectification_destination import PedimentoRectificationDestinationService -from .pedimento_rectification_origin import PedimentoRectificationOriginService -from .pedimento_transport_means import PedimentoTransportMeansService -from .pedimento_validation import PedimentoValidationService - -# Crear tablas relacionadas si existen datos -from ..models.pedimentos import Pedimentos -from ..models.pedimento_dates import PedimentoDates -from ..models.pedimento_decrementables import PedimentoDecrementables -from ..models.pedimento_incrementables import PedimentoIncrementables -from ..models.pedimento_indexes import PedimentoIndexes -from ..models.pedimento_validation import PedimentoValidation -from ..models.pedimento_customs_offices import PedimentoCustomsOffices -from ..models.pedimento_payments import PedimentoPayments -from ..models.pedimento_rectification_destination import PedimentoRectificationDestination -from ..models.pedimento_rectification_origin import PedimentoRectificationOrigin -from ..models.pedimento_transport_means import PedimentoTransportMeans -from ..models.pedimento_config_additional import PedimentoConfigAdditional -from ..models.pedimento_config_calculations import PedimentoConfigCalculations -from ..models.pedimento_config_parameters import PedimentoConfigParameters -from ..models.pedimento_config_surcharges import PedimentoConfigSurcharges -from ..models.pedimento_config_update_rectification import PedimentoConfigUpdateRectification -from ..models.pedimento_config_updates import PedimentoConfigUpdates -from ..models.pedimento_packages import PedimentoPackages -from ..models.pedimento_transport_carriers import PedimentoTransportCarriers -from ..models.pedimento_seals import PedimentoSeals -from ..models.pedimento_containers import PedimentoContainers -from ..models.pedimento_guides import PedimentoGuides -from ..models.pedimento_contributions import PedimentoContributions - -logger = logging.getLogger(__name__) - - -def _ensure_create_audit_log( - db: Session, *, table_name: str, record_id: str, record_data: Dict[str, Any], username: str, tenant_id: int, company_id: int -) -> None: - """ - Backup audit writer: inserts CREATE log only when listener did not. - """ - exists = ( - db.query(AuditLog.spec_id) - .filter( - AuditLog.table_name == table_name, - AuditLog.operation_type == "CREATE", - AuditLog.record_id == record_id, - AuditLog.tenant_id == tenant_id, - AuditLog.company_id == company_id, - ) - .first() - ) - if exists: - return - - AuditService.log_crud_operation( - db=db, - table_name=table_name, - operation_type="CREATE", - record_data=record_data, - username=username, - record_id=record_id, - company_id=company_id, - tenant_id=tenant_id, - ) - db.commit() - - -def _get_current_username() -> str: - try: - context = get_user_context() - if context: - return ( - context.get("preferred_username") - or context.get("email") - or context.get("sub") - or "System" - ) - except Exception: - pass - return "System" - - -class PedimentosService: - """Service class for Pedimentos business logic""" - - @staticmethod - def get_all( - db: Session, - tenant_id: int, - company_id: Optional[int], - skip: int = 0, - limit: int = 100, - filters: Optional[Dict[str, Any]] = None, - sort_by: Optional[str] = None, - sort_order: Optional[str] = "asc", - ) -> tuple[List[Pedimentos], int]: - """ - Get all pedimentos for a tenant with pagination and filters - - Args: - db: Database session - tenant_id: Tenant ID - company_id: Optional Company ID - skip: Number of records to skip - limit: Maximum number of records to return - filters: Optional filters dict - - Returns: - Tuple of (list of pedimentos, total count) - """ - query = db.query(Pedimentos).filter(Pedimentos.tenant_id == tenant_id) - - if company_id is not None: - query = query.filter(Pedimentos.company_id == company_id) - - if filters: - if filters.get("status"): - query = query.filter(Pedimentos.status == filters["status"]) - if filters.get("client_id"): - query = query.filter(Pedimentos.client_id == filters["client_id"]) - if filters.get("year"): - query = query.filter(Pedimentos.year == filters["year"]) - if filters.get("pedimento"): - raw_value = str(filters["pedimento"]) - # Normalizar: dejar solo dígitos (ignorar guiones, espacios, etc.) - normalized = re.sub(r"\D", "", raw_value) - if normalized: - ped_key_expr = func.concat( - func.coalesce(Pedimentos.year, ""), - func.coalesce(func.substr(Pedimentos.customs_office, 1, 2), ""), - func.coalesce(Pedimentos.license, ""), - func.coalesce(Pedimentos.pedimento_number, ""), - ) - query = query.filter(ped_key_expr.ilike(f"%{normalized}%")) - - total = query.count() - - # Eager load all relationships for the response schema - items = ( - query.options( - selectinload(Pedimentos.pedimento_dates), - selectinload(Pedimentos.pedimento_decrementables), - selectinload(Pedimentos.pedimento_incrementables), - selectinload(Pedimentos.pedimento_indexes), - selectinload(Pedimentos.pedimento_validation), - selectinload(Pedimentos.pedimento_customs_offices), - selectinload(Pedimentos.pedimento_payments), - selectinload(Pedimentos.pedimento_rectification_destination), - selectinload(Pedimentos.pedimento_rectification_origin), - selectinload(Pedimentos.pedimento_transport_means), - selectinload(Pedimentos.pedimento_config_additional), - selectinload(Pedimentos.pedimento_config_calculations), - selectinload(Pedimentos.pedimento_config_parameters), - selectinload(Pedimentos.pedimento_config_surcharges), - selectinload(Pedimentos.pedimento_config_update_rectification), - selectinload(Pedimentos.pedimento_config_updates), - selectinload(Pedimentos.pedimento_packages), - selectinload(Pedimentos.pedimento_transport_carriers), - selectinload(Pedimentos.pedimento_guides), - selectinload(Pedimentos.pedimento_contributions), - selectinload(Pedimentos.pedimento_seals), - selectinload(Pedimentos.pedimento_containers), - ) - ) - - # Apply sorting - if sort_by: - column = getattr(Pedimentos, sort_by, None) - if column: - if sort_order == "desc": - query = query.order_by(column.desc()) - else: - query = query.order_by(column.asc()) - else: - # Default sorting - query = query.order_by(desc(Pedimentos.created_at)) - - items = query.offset(skip).limit(limit).all() - - return items, total - - @staticmethod - def get_by_id( - db: Session, pedimento_id: int, tenant_id: int, company_id: int = None - ) -> Optional[Pedimentos]: - """ - Get a pedimento by ID - - Args: - db: Database session - pedimento_id: Pedimento ID - tenant_id: Tenant ID - company_id: Company ID (optional for backwards compatibility) - - Returns: - Pedimento or None if not found - """ - query = db.query(Pedimentos).filter( - Pedimentos.id == pedimento_id, Pedimentos.tenant_id == tenant_id - ) - - if company_id is not None: - query = query.filter(Pedimentos.company_id == company_id) - - # Eager load all relationships for the response schema - query = query.options( - selectinload(Pedimentos.pedimento_dates), - selectinload(Pedimentos.pedimento_decrementables), - selectinload(Pedimentos.pedimento_incrementables), - selectinload(Pedimentos.pedimento_indexes), - selectinload(Pedimentos.pedimento_validation), - selectinload(Pedimentos.pedimento_customs_offices), - selectinload(Pedimentos.pedimento_payments), - selectinload(Pedimentos.pedimento_rectification_destination), - selectinload(Pedimentos.pedimento_rectification_origin), - selectinload(Pedimentos.pedimento_transport_means), - selectinload(Pedimentos.pedimento_config_additional), - selectinload(Pedimentos.pedimento_config_calculations), - selectinload(Pedimentos.pedimento_config_parameters), - selectinload(Pedimentos.pedimento_config_surcharges), - selectinload(Pedimentos.pedimento_config_update_rectification), - selectinload(Pedimentos.pedimento_config_updates), - selectinload(Pedimentos.pedimento_packages), - selectinload(Pedimentos.pedimento_transport_carriers), - selectinload(Pedimentos.pedimento_guides), - selectinload(Pedimentos.pedimento_contributions), - selectinload(Pedimentos.pedimento_seals), - selectinload(Pedimentos.pedimento_containers), - ) - - return query.first() - - @staticmethod - def create( - db: Session, pedimento_data: PedimentosCreate, tenant_id: int, company_id: int - ) -> Pedimentos: - """ - Create a new pedimento with related tables - - Args: - db: Database session - pedimento_data: Pedimento creation data - tenant_id: Tenant ID - company_id: Company ID - - Returns: - Created pedimento - """ - if company_id is None: - raise ValueError("company_id es obligatorio para crear pedimentos.") - try: - # Check for existing pedimento with same key (Year, Aduana, Patente, Number) - # This avoids IntegrityError in many cases and provides a better error message. - existing = db.query(Pedimentos).filter( - Pedimentos.tenant_id == tenant_id, - Pedimentos.company_id == company_id, - Pedimentos.year == pedimento_data.year, - Pedimentos.customs_office == pedimento_data.customs_office, - Pedimentos.license == pedimento_data.license, - Pedimentos.pedimento_number == pedimento_data.pedimento_number, - Pedimentos.deleted_at.is_(None) - ).first() - - if existing: - raise ValueError( - f"Ya existe un pedimento con estos datos: {pedimento_data.year}-{pedimento_data.customs_office}-{pedimento_data.license}-{pedimento_data.pedimento_number}" - ) - - # Extraer datos de tablas relacionadas - - # Extraer datos de tablas relacionadas - related_data = { - 'pedimento_dates': pedimento_data.pedimento_dates, - 'pedimento_decrementables': pedimento_data.pedimento_decrementables, - 'pedimento_incrementables': pedimento_data.pedimento_incrementables, - 'pedimento_indexes': pedimento_data.pedimento_indexes, - 'pedimento_validation': pedimento_data.pedimento_validation, - 'pedimento_customs_offices': pedimento_data.pedimento_customs_offices, - 'pedimento_payments': pedimento_data.pedimento_payments, - 'pedimento_rectification_destination': pedimento_data.pedimento_rectification_destination, - 'pedimento_rectification_origin': pedimento_data.pedimento_rectification_origin, - 'pedimento_transport_means': pedimento_data.pedimento_transport_means, - 'pedimento_config_additional': pedimento_data.pedimento_config_additional, - 'pedimento_config_calculations': pedimento_data.pedimento_config_calculations, - 'pedimento_config_parameters': pedimento_data.pedimento_config_parameters, - 'pedimento_config_surcharges': pedimento_data.pedimento_config_surcharges, - 'pedimento_config_update_rectification': pedimento_data.pedimento_config_update_rectification, - 'pedimento_config_updates': pedimento_data.pedimento_config_updates, - 'pedimento_packages': pedimento_data.pedimento_packages, - 'pedimento_transport_carriers': pedimento_data.pedimento_transport_carriers, - 'pedimento_guides': pedimento_data.pedimento_guides, - 'pedimento_contributions': getattr(pedimento_data, 'pedimento_contributions', None), - 'pedimento_seals': pedimento_data.pedimento_seals, - 'pedimento_containers': pedimento_data.pedimento_containers, - } - - # Crear pedimento principal (excluyendo relaciones) - pedimento_dict = pedimento_data.model_dump(exclude={ - 'pedimento_dates', 'pedimento_decrementables', 'pedimento_incrementables', - 'pedimento_indexes', 'pedimento_validation', 'pedimento_customs_offices', - 'pedimento_payments', 'pedimento_rectification_destination', - 'pedimento_rectification_origin', 'pedimento_transport_means', - 'pedimento_config_additional', 'pedimento_config_calculations', - 'pedimento_config_parameters', 'pedimento_config_surcharges', - 'pedimento_config_update_rectification', 'pedimento_config_updates', - 'pedimento_packages', 'pedimento_transport_carriers', 'pedimento_guides', - 'pedimento_contributions', 'pedimento_seals', 'pedimento_containers' - }) - - pedimento = Pedimentos(**pedimento_dict) - pedimento.tenant_id = tenant_id - pedimento.company_id = company_id - - db.add(pedimento) - db.flush() # Flush para obtener el ID sin commit - - # Helper function para crear objetos relacionados (uno a uno) - def create_related(model_class, data, extra_fields=None): - if data or extra_fields: - # Inicializar obj_dict desde data si existe, sino como dict vacío - obj_dict = data.model_dump(exclude_none=True) if data else {} - # Agregar campos extra si se proporcionan - if extra_fields: - obj_dict.update(extra_fields) - # Solo crear si hay datos significativos (más que solo IDs) - significant_fields = {k: v for k, v in obj_dict.items() - if k not in ('pedimento_id', 'tenant_id', 'company_id') and v is not None} - if not significant_fields and not extra_fields: - return - obj = model_class(**obj_dict) - obj.pedimento_id = pedimento.id - obj.tenant_id = tenant_id - obj.company_id = company_id - db.add(obj) - - # Crear PedimentoDates con capture_time automático - create_related( - PedimentoDates, - related_data['pedimento_dates'], - extra_fields={'capture_time': datetime.now().time()} - ) - create_related(PedimentoDecrementables, - related_data['pedimento_decrementables']) - create_related(PedimentoIncrementables, - related_data['pedimento_incrementables']) - create_related(PedimentoIndexes, related_data['pedimento_indexes']) - create_related(PedimentoValidation, - related_data['pedimento_validation']) - create_related(PedimentoCustomsOffices, - related_data['pedimento_customs_offices']) - create_related(PedimentoPayments, - related_data['pedimento_payments']) - create_related(PedimentoRectificationDestination, - related_data['pedimento_rectification_destination']) - create_related(PedimentoRectificationOrigin, - related_data['pedimento_rectification_origin']) - create_related(PedimentoTransportMeans, - related_data['pedimento_transport_means']) - create_related(PedimentoConfigAdditional, - related_data['pedimento_config_additional']) - create_related(PedimentoConfigCalculations, - related_data['pedimento_config_calculations']) - create_related(PedimentoConfigParameters, - related_data['pedimento_config_parameters']) - create_related(PedimentoConfigSurcharges, - related_data['pedimento_config_surcharges']) - create_related(PedimentoConfigUpdateRectification, - related_data['pedimento_config_update_rectification']) - create_related(PedimentoConfigUpdates, - related_data['pedimento_config_updates']) - - # Crear PedimentoPackages (uno a uno) - create_related(PedimentoPackages, related_data['pedimento_packages']) - - # Crear relaciones uno a muchos - def create_many(model_class, items): - if not items: - return - for item in items: - obj_dict = item.model_dump(exclude_none=True) - obj = model_class(**obj_dict) - obj.pedimento_id = pedimento.id - obj.tenant_id = tenant_id - obj.company_id = company_id - db.add(obj) - - create_many(PedimentoTransportCarriers, - related_data['pedimento_transport_carriers']) - create_many(PedimentoGuides, related_data['pedimento_guides']) - create_many(PedimentoContributions, related_data['pedimento_contributions']) - create_many(PedimentoSeals, related_data['pedimento_seals']) - create_many(PedimentoContainers, - related_data['pedimento_containers']) - - db.commit() - db.refresh(pedimento) - _ensure_create_audit_log( - db, - table_name=pedimento.__tablename__, - record_id=str(pedimento.id), - record_data={c.name: getattr(pedimento, c.name) for c in Pedimentos.__table__.columns}, - username=_get_current_username(), - tenant_id=tenant_id, - company_id=company_id, - ) - return pedimento - - except IntegrityError as e: - db.rollback() - # Detectar si es un error de integridad de duplicados o similar - error_msg = str(e.orig).lower() - - # Case-insensitive check and support for both Spanish and English common error patterns - is_unique_violation = any(kw in error_msg for kw in [ - 'pedimentos_unique_key', - 'unique constraint', - 'duplicate key', - 'duplicada', - 'unicidad', - 'ya existe' - ]) - - if is_unique_violation: - logger.warning(f"Attempted to create duplicate pedimento or common record: {e}") - raise ValueError("Ya existe un pedimento o registro relacionado con estos datos. Verifica los campos únicos.") - - logger.error(f"Integrity error creating pedimento: {e}") - raise - except Exception as e: - db.rollback() - logger.error(f"Error creating pedimento with related data: {e}") - raise - - @staticmethod - def update( - db: Session, pedimento_id: int, tenant_id: int, pedimento_data: PedimentosUpdate, company_id: int = None - ) -> Optional[Pedimentos]: - """ - Update a pedimento and its related tables - - Args: - db: Database session - pedimento_id: Pedimento ID - tenant_id: Tenant ID - pedimento_data: Updated data - company_id: Company ID (optional for backwards compatibility) - - Returns: - Updated pedimento or None if not found - """ - pedimento = PedimentosService.get_by_id( - db, pedimento_id, tenant_id, company_id) - if not pedimento: - return None - - # Ensure company_id is set from the existing record - company_id = pedimento.company_id - if company_id is None: - logger.error( - "Pedimento %s without company_id detected during update tenant_id=%s", - pedimento_id, - tenant_id, - ) - raise ValueError( - "El pedimento no tiene company_id asignado. Corrige el dato antes de actualizar." - ) - - try: - # Actualizar campos principales del pedimento - update_data = pedimento_data.model_dump(exclude_unset=True, exclude={ - 'pedimento_dates', 'pedimento_decrementables', 'pedimento_incrementables', - 'pedimento_indexes', 'pedimento_validation', 'pedimento_customs_offices', - 'pedimento_payments', 'pedimento_rectification_destination', - 'pedimento_rectification_origin', 'pedimento_transport_means', - 'pedimento_config_additional', 'pedimento_config_calculations', - 'pedimento_config_parameters', 'pedimento_config_surcharges', - 'pedimento_config_update_rectification', 'pedimento_config_updates', - 'pedimento_packages', 'pedimento_transport_carriers', 'pedimento_guides', 'pedimento_contributions', 'pedimento_seals', 'pedimento_containers' - }) - - for field, value in update_data.items(): - setattr(pedimento, field, value) - - db.flush() - - # Helper function para actualizar o crear objetos relacionados - def update_or_create_related(service_class, model_class, data_attr): - # Obtener datos del payload completo (no solo exclude_unset) - full_data = pedimento_data.model_dump() - - if data_attr not in full_data: - return - - data = full_data[data_attr] - if not data: - return - - existing = None - if service_class: - existing = service_class.get_by_pedimento_id( - db, pedimento_id, tenant_id, company_id) - else: - existing = getattr(pedimento, data_attr, None) - - if existing: - # Actualizar existente (excluir pedimento_id, tenant_id, company_id) - # Solo actualizar valores no-None para evitar sobrescribir con None - for field, value in data.items(): - if hasattr(existing, field) and field not in ('pedimento_id', 'tenant_id', 'company_id'): - if value is not None: - setattr(existing, field, value) - else: - # Crear nuevo - obj = model_class(**data) - obj.pedimento_id = pedimento_id - obj.tenant_id = tenant_id - obj.company_id = company_id - db.add(obj) - - def upsert_one_to_many(model_class, data_attr): - full_data = pedimento_data.model_dump() - if data_attr not in full_data: - return - - items = full_data[data_attr] - if items is None: - return - - # Reemplazar completamente la colección por simplicidad - existing_items = getattr(pedimento, data_attr) - if existing_items: - for item in list(existing_items): - db.delete(item) - - for item in items: - obj = model_class(**item) - obj.pedimento_id = pedimento_id - obj.tenant_id = tenant_id - obj.company_id = company_id - db.add(obj) - - # Actualizar o crear tablas relacionadas - update_or_create_related( - PedimentoDatesService, PedimentoDates, 'pedimento_dates') - update_or_create_related( - PedimentoDecrementablesService, PedimentoDecrementables, 'pedimento_decrementables') - update_or_create_related( - PedimentoIncrementablesService, PedimentoIncrementables, 'pedimento_incrementables') - update_or_create_related( - PedimentoIndexesService, PedimentoIndexes, 'pedimento_indexes') - update_or_create_related( - PedimentoValidationService, PedimentoValidation, 'pedimento_validation') - update_or_create_related( - PedimentoCustomsOfficesService, PedimentoCustomsOffices, 'pedimento_customs_offices') - update_or_create_related( - PedimentoPaymentsService, PedimentoPayments, 'pedimento_payments') - update_or_create_related(PedimentoRectificationDestinationService, - PedimentoRectificationDestination, 'pedimento_rectification_destination') - update_or_create_related(PedimentoRectificationOriginService, - PedimentoRectificationOrigin, 'pedimento_rectification_origin') - update_or_create_related( - PedimentoTransportMeansService, PedimentoTransportMeans, 'pedimento_transport_means') - update_or_create_related(PedimentoConfigAdditionalService, - PedimentoConfigAdditional, 'pedimento_config_additional') - update_or_create_related(PedimentoConfigCalculationsService, - PedimentoConfigCalculations, 'pedimento_config_calculations') - update_or_create_related(PedimentoConfigParametersService, - PedimentoConfigParameters, 'pedimento_config_parameters') - update_or_create_related(PedimentoConfigSurchargesService, - PedimentoConfigSurcharges, 'pedimento_config_surcharges') - update_or_create_related(PedimentoConfigUpdateRectificationService, - PedimentoConfigUpdateRectification, 'pedimento_config_update_rectification') - update_or_create_related( - PedimentoConfigUpdatesService, PedimentoConfigUpdates, 'pedimento_config_updates') - - # Manejar nuevas relaciones - update_or_create_related(None, PedimentoPackages, 'pedimento_packages') - upsert_one_to_many(PedimentoTransportCarriers, - 'pedimento_transport_carriers') - upsert_one_to_many(PedimentoGuides, 'pedimento_guides') - upsert_one_to_many(PedimentoContributions, 'pedimento_contributions') - upsert_one_to_many(PedimentoSeals, 'pedimento_seals') - upsert_one_to_many(PedimentoContainers, 'pedimento_containers') - - db.commit() - db.refresh(pedimento) - return pedimento - - except IntegrityError as e: - db.rollback() - # Detectar si es un error de pedimento duplicado - error_msg = str(e.orig) - if 'pedimentos_unique_key' in error_msg or 'duplicate key value violates unique constraint' in error_msg: - logger.warning(f"Attempted to update to duplicate pedimento: {e}") - raise ValueError("Ya existe otro pedimento con estos datos (Año, Aduana, Patente, Número)") - logger.error(f"Integrity error updating pedimento: {e}") - raise - except Exception as e: - db.rollback() - logger.error(f"Error updating pedimento with related data: {e}") - raise - - @staticmethod - def delete(db: Session, pedimento_id: int, tenant_id: int, company_id: int = None) -> bool: - """ - Delete a pedimento - - Args: - db: Database session - pedimento_id: Pedimento ID - tenant_id: Tenant ID - company_id: Company ID (optional for backwards compatibility) - - Returns: - True if deleted, False if not found - """ - pedimento = PedimentosService.get_by_id( - db, pedimento_id, tenant_id, company_id) - if not pedimento: - return False - - db.delete(pedimento) - db.commit() - return True diff --git a/backend/api/v1/modules/a76/permission_rule_oct/dto.py b/backend/api/v1/modules/a76/permission_rule_oct/dto.py deleted file mode 100644 index 6df882ab..00000000 --- a/backend/api/v1/modules/a76/permission_rule_oct/dto.py +++ /dev/null @@ -1,31 +0,0 @@ -from typing import Optional - -from pydantic import BaseModel, Field - - -class PermissionRuleOctBaseDTO(BaseModel): - permission: str = Field(..., description="Permission identifier", max_length=20) - start_date: Optional[int] = Field(None, description="Start date") - end_date: Optional[int] = Field(None, description="End date") - sector: Optional[str] = Field(None, max_length=8, description="Sector") - system: Optional[str] = Field(None, max_length=5, description="System") - - -class PermissionRuleOctCreateDTO(PermissionRuleOctBaseDTO): - """Schema for creating a permission rule OCT""" - pass - - -class PermissionRuleOctUpdateDTO(PermissionRuleOctBaseDTO): - """Schema for updating a permission rule OCT""" - permission: Optional[str] = Field(None, description="Permission identifier", max_length=20) - - -class PermissionRuleOctResponseDTO(PermissionRuleOctBaseDTO): - """Schema for permission rule OCT response""" - id: int - company_id: int - tenant_id: int - - class Config: - from_attributes = True diff --git a/backend/api/v1/modules/a76/permission_rule_oct/models.py b/backend/api/v1/modules/a76/permission_rule_oct/models.py deleted file mode 100644 index 56e47d2a..00000000 --- a/backend/api/v1/modules/a76/permission_rule_oct/models.py +++ /dev/null @@ -1,33 +0,0 @@ -from typing import Optional - -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base -from sqlalchemy import ( - ForeignKeyConstraint, - Integer, - PrimaryKeyConstraint, - String, - UniqueConstraint, -) -from sqlalchemy.orm import Mapped, mapped_column - - -class PermissionRuleOct(Base, TenantScopedMixin, TimestampMixin): - __tablename__ = "permission_rule_oct" - __table_args__ = ( - PrimaryKeyConstraint("id", name="permission_rule_oct_pkey"), - UniqueConstraint( - "tenant_id", - "company_id", - "permission", - name="permission_rule_oct_permission_tenant_ukey", - ), - {"schema": "a76"}, - ) - id: Mapped[int] = mapped_column(Integer, primary_key=True) - - permission: Mapped[str] = mapped_column(String(20)) - start_date: Mapped[Optional[int]] = mapped_column() - end_date: Mapped[Optional[int]] = mapped_column() - sector: Mapped[Optional[str]] = mapped_column(String(8)) - system: Mapped[Optional[str]] = mapped_column(String(5)) diff --git a/backend/api/v1/modules/a76/permission_rule_oct/routes.py b/backend/api/v1/modules/a76/permission_rule_oct/routes.py deleted file mode 100644 index c39eecd7..00000000 --- a/backend/api/v1/modules/a76/permission_rule_oct/routes.py +++ /dev/null @@ -1,24 +0,0 @@ -from api.v1.common.tenant_crud_routes import TenantCRUDRoutes - -from .dto import ( - PermissionRuleOctCreateDTO, - PermissionRuleOctResponseDTO, - PermissionRuleOctUpdateDTO, -) -from .services import PermissionRuleOctService - -# Create router using TenantCRUDRoutes factory -router = TenantCRUDRoutes( - service=PermissionRuleOctService, - create_schema=PermissionRuleOctCreateDTO, - update_schema=PermissionRuleOctUpdateDTO, - response_schema=PermissionRuleOctResponseDTO, - prefix="/permission-rule-oct", - tags=[], - resource_name="Permission Rule OCT", - id_name="id", # Using numeric ID - enable_list=True, # Enable GET /permission-rule-oct with pagination - enable_filters=True, # Enable filtering by permission, sector, system - default_page_size=50, - max_page_size=100, -).router diff --git a/backend/api/v1/modules/a76/permission_rule_oct/services.py b/backend/api/v1/modules/a76/permission_rule_oct/services.py deleted file mode 100644 index 894c895d..00000000 --- a/backend/api/v1/modules/a76/permission_rule_oct/services.py +++ /dev/null @@ -1,114 +0,0 @@ -from typing import Optional, Tuple, List, Dict, Any - -from sqlalchemy.orm import Session - -from . import dto, models - - -class PermissionRuleOctService: - """Service for PermissionRuleOct CRUD operations with tenant support""" - - @staticmethod - def get_all( - db: Session, - tenant_id: int, - company_id: int, - skip: int = 0, - limit: int = 50, - filters: Optional[Dict[str, Any]] = None, - ) -> Tuple[List[models.PermissionRuleOct], int]: - """Get all permission rules OCT for a tenant/company with pagination""" - query = db.query(models.PermissionRuleOct).filter( - models.PermissionRuleOct.tenant_id == tenant_id, - models.PermissionRuleOct.company_id == company_id, - ) - - # Apply filters if provided - if filters: - if filters.get("permission"): - query = query.filter( - models.PermissionRuleOct.permission.ilike(f"%{filters['permission']}%") - ) - if filters.get("sector"): - query = query.filter( - models.PermissionRuleOct.sector == filters["sector"] - ) - if filters.get("system"): - query = query.filter( - models.PermissionRuleOct.system == filters["system"] - ) - - total = query.count() - permissions = query.offset(skip).limit(limit).all() - - return permissions, total - - @staticmethod - def get_by_id( - db: Session, permission_id: int, tenant_id: int, company_id: int - ) -> Optional[models.PermissionRuleOct]: - """Get permission rule OCT by ID""" - return ( - db.query(models.PermissionRuleOct) - .filter( - models.PermissionRuleOct.id == permission_id, - models.PermissionRuleOct.tenant_id == tenant_id, - models.PermissionRuleOct.company_id == company_id, - ) - .first() - ) - - @staticmethod - def create( - db: Session, - permission_data: dto.PermissionRuleOctCreateDTO, - tenant_id: int, - company_id: int, - ) -> models.PermissionRuleOct: - """Create a new permission rule OCT""" - new_permission = models.PermissionRuleOct( - **permission_data.model_dump(), tenant_id=tenant_id, company_id=company_id - ) - db.add(new_permission) - db.commit() - db.refresh(new_permission) - return new_permission - - @staticmethod - def update( - db: Session, - permission_id: int, - tenant_id: int, - company_id: int, - permission_data: dto.PermissionRuleOctUpdateDTO, - ) -> Optional[models.PermissionRuleOct]: - """Update a permission rule OCT""" - permission = PermissionRuleOctService.get_by_id( - db, permission_id, tenant_id, company_id - ) - if not permission: - return None - - # Update fields - update_data = permission_data.model_dump(exclude_unset=True) - for field, value in update_data.items(): - setattr(permission, field, value) - - db.commit() - db.refresh(permission) - return permission - - @staticmethod - def delete( - db: Session, permission_id: int, tenant_id: int, company_id: int - ) -> bool: - """Delete a permission rule OCT""" - permission = PermissionRuleOctService.get_by_id( - db, permission_id, tenant_id, company_id - ) - if not permission: - return False - - db.delete(permission) - db.commit() - return True diff --git a/backend/api/v1/modules/a76/permission_rule_oct/test_permission_rule_oct.py b/backend/api/v1/modules/a76/permission_rule_oct/test_permission_rule_oct.py deleted file mode 100644 index b7dbdc04..00000000 --- a/backend/api/v1/modules/a76/permission_rule_oct/test_permission_rule_oct.py +++ /dev/null @@ -1,36 +0,0 @@ -import pytest -from fastapi import FastAPI -from fastapi.testclient import TestClient - -from .routes import router - -app = FastAPI() -app.include_router(router) -client = TestClient(app) - - -@pytest.mark.usefixtures("client", "access_token") -def test_list_permission_rules(client, access_token): - headers = {"Authorization": f"Bearer {access_token}"} - response = client.get("/permission_rule_oct/", headers=headers) - assert response.status_code == 200 - assert "items" in response.json() - assert "page" in response.json() - assert "page_size" in response.json() - - -@pytest.mark.usefixtures("client", "access_token") -def test_get_permission_rule_not_found(client, access_token): - headers = {"Authorization": f"Bearer {access_token}"} - response = client.get("/permission_rule_oct/invalid_id", headers=headers) - assert response.status_code == 404 - - -def test_create_permission_rule_forbidden(): - response = client.post("/permission_rule_oct/", json={"rule": "Test Rule"}) - assert response.status_code in (403, 405, 404) - - -def test_update_permission_rule_forbidden(): - response = client.put("/permission_rule_oct/1", json={"rule": "Updated Rule"}) - assert response.status_code in (403, 405, 404) diff --git a/backend/api/v1/modules/a76/reports/exportacion/__init__.py b/backend/api/v1/modules/a76/reports/exportacion/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/backend/api/v1/modules/a76/reports/exportacion/aviso_consolidado/__init__.py b/backend/api/v1/modules/a76/reports/exportacion/aviso_consolidado/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/backend/api/v1/modules/a76/reports/exportacion/aviso_consolidado/routes.py b/backend/api/v1/modules/a76/reports/exportacion/aviso_consolidado/routes.py deleted file mode 100644 index 5b804b3a..00000000 --- a/backend/api/v1/modules/a76/reports/exportacion/aviso_consolidado/routes.py +++ /dev/null @@ -1,57 +0,0 @@ - -from typing import Dict, Any -from fastapi import APIRouter, Depends, Query -from sqlalchemy.orm import Session -from celery.result import AsyncResult -from core.celery_app import celery_app -from core.database import get_core_db -from core.security import get_current_user, validate_access_to_resource -from api.v1.modules.core.tasks_tracking import track_and_dispatch -from .task import generar_pdf_aviso_consolidado_exp_async - -router = APIRouter() - -@router.get("/tasks/{task_id}") -async def get_task_status( - task_id: str, - current_user: Dict[str, Any] = Depends(get_current_user), - db: Session = Depends(get_core_db) -): - task_result = AsyncResult(task_id, app=celery_app) - - response = { - "task_id": task_id, - "state": task_result.state, - "result": None, - "info": None - } - - if task_result.state == 'FAILURE': - response["result"] = str(task_result.result) - elif task_result.state == 'SUCCESS': - response["result"] = task_result.result - elif task_result.state == 'PROCESSING': - response["info"] = task_result.info - - return response - -@router.post("/{invoice_id}/download-async") -async def trigger_descarga_aviso_consolidado_exp( - invoice_id: int, - company_id: int = Query(..., description="ID de la empresa"), - current_user: Dict[str, Any] = Depends(get_current_user), - db: Session = Depends(get_core_db) -): - tenant_id = validate_access_to_resource(db, company_id, current_user) - task = track_and_dispatch( - db=db, - task=generar_pdf_aviso_consolidado_exp_async, - tenant_id=tenant_id, - company_id=company_id, - requested_by_user=current_user.get("preferred_username") or current_user.get("email") or current_user.get("sub"), - task_name="generar_pdf_aviso_consolidado_exp_async", - task_group="reports", - task_origin="a76/reports/exportacion/aviso_consolidado", - args=[invoice_id, company_id], - ) - return {"task_id": task.id, "message": "Generación iniciada"} diff --git a/backend/api/v1/modules/a76/reports/exportacion/aviso_consolidado/service.py b/backend/api/v1/modules/a76/reports/exportacion/aviso_consolidado/service.py deleted file mode 100644 index 58fb59e5..00000000 --- a/backend/api/v1/modules/a76/reports/exportacion/aviso_consolidado/service.py +++ /dev/null @@ -1,651 +0,0 @@ - -import shutil -import base64 -import pdfkit -from pathlib import Path -from typing import Tuple, List, Callable, Optional, Dict, Any -from jinja2 import Environment, FileSystemLoader, select_autoescape -from fastapi import HTTPException -from sqlalchemy.orm import Session -from pydantic import BaseModel -from io import BytesIO -import pdf417gen - -# --- MODELOS --- -from api.v1.modules.a76.invoices.models import InvoiceHeader, InvoiceComplianceMx, InvoiceFinancials, InvoiceLogistics -from api.v1.modules.a76.general_catalogs.company.models import Company -from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos -from api.v1.modules.a76.pedmientos.models.pedimento_validation import PedimentoValidation -from api.v1.modules.a76.customs_brokers.models import CustomsBroker, CustomsBrokerPersonnel -from api.v1.modules.a76.items.models import LineItem -from api.v1.modules.a76.clients_and_providers.models import ClientProvider, ClientProviderAddress, ClientProviderPrograms - -# --- SCHEMAS FOR TEMPLATE CONTEXT --- -class EmpresaSchema(BaseModel): - rfc: str - razon_social: str - direccion_completa: str - tax_id: Optional[str] = None # Extra info just in case - -class PersonaSchema(BaseModel): - nombre: str - rfc: str - curp: str - -class AvisoSchema(BaseModel): - pedimento_completo: str - tipo_operacion: str - clave_pedimento: str - acus_valor: str - aduana_seccion: str - numero_remesa: str - peso_bruto: str - codigo_aceptacion: str - codigo_barras_b64: Optional[str] = None - clave_seccion: str - marcas_numeros_bultos: str - candados: List[str] - vehiculo_placas: str - vehiculo_tipo: str - observaciones: str - numero_certificado: str - tipo_documento: str # NEW: Invoice Type - firma_electronica: str - -class AvisoConsolidadoContext(BaseModel): - aviso: AvisoSchema - empresa: EmpresaSchema - agente: PersonaSchema - mandatario: PersonaSchema - -class AvisoConsolidadoExportacionService: - def __init__(self): - self.template_dir = Path(__file__).parent / "templates" - self.jinja_env = Environment( - loader=FileSystemLoader(self.template_dir), - autoescape=select_autoescape(['html', 'xml']) - ) - self.template = self.jinja_env.get_template('avcon_exp.html') - - def _get_wkhtmltopdf_config(self): - path = shutil.which("wkhtmltopdf") or "/usr/local/bin/wkhtmltopdf" - if not Path(path).exists(): - raise RuntimeError("wkhtmltopdf no encontrado.") - return pdfkit.configuration(wkhtmltopdf=path) - - def obtener_datos(self, db: Session, invoice_id: int, company_id: int, progress_callback: Optional[Callable] = None) -> AvisoConsolidadoContext: - try: - with open("/tmp/barcode_debug.log", "a") as f: f.write(f"ENTER obtener_datos ID={invoice_id}\n") - if progress_callback: progress_callback(10, "Buscando factura...") - - # Fetch minimal real data if possible, or use placeholders as requested - header = db.query(InvoiceHeader).filter(InvoiceHeader.id == invoice_id, InvoiceHeader.company_id == company_id).first() - if not header: - # We can't strictly raise 404 if we want to support testing with non-existent IDs for pure UI check, - # but valid workflow requires a real invoice. Raising 404 is better practice. - raise HTTPException(status_code=404, detail="Factura no encontrada") - - company = db.query(Company).filter(Company.id == company_id).first() - - if progress_callback: progress_callback(30, "Preparando datos...") - - # --- FETCHING REAL DATA --- - - # 1. Compliance & Pedimento - compliance = header.compliance_mx - pedimento = None - if compliance and compliance.pedimento_id: - pedimento = db.query(Pedimentos).filter(Pedimentos.id == compliance.pedimento_id).first() - - # Pedimento Completo Construction - pedimento_txt = "S/P" - clave_ped = "" - if pedimento: - # Format: YY OFF LIC NUMBER - year = pedimento.year or "" - office = pedimento.customs_office or "" - lic = pedimento.license or "" - num = pedimento.pedimento_number or "" - pedimento_txt = f"{year} {office} {lic} {num}" - clave_ped = pedimento.pedimento_code or "" - - # 2. Importer/Exporter Data (Clarion 100% Match) - # Logic: - # IF EqiFex:EsCambioRegimen = 'S' THEN - # CliPro:Cliente = EqiFex:VendidoA - # ELSE - # CliPro:Cliente = EqiFex:Proveedor - # END - - target_entity_data = { - "rfc": "", - "razon_social": "", - "direccion_completa": "DOMICILIO NO REGISTRADO" - } - - target_client_id = None - - if compliance: - if compliance.is_regime_change: - target_client_id = compliance.sold_to_id - else: - target_client_id = compliance.provider_id - - if target_client_id: - client_obj = db.query(ClientProvider).filter(ClientProvider.id == target_client_id).first() - if client_obj: - # Fetch Address - c_addr = db.query(ClientProviderAddress).filter(ClientProviderAddress.client_id == target_client_id).first() - # Identificador fiscal efectivo: RFC (nacional) o TAX-ID (extranjero) - c_rfc = getattr(client_obj, "fiscal_id", "") or "" - - c_dir_str = "DOMICILIO NO REGISTRADO" - if c_addr: - parts_c = [] - if c_addr.streets: parts_c.append(c_addr.streets) - if c_addr.exterior_number: parts_c.append(f"No. {c_addr.exterior_number}") - if c_addr.interior_number: parts_c.append(f"Int. {c_addr.interior_number}") - if c_addr.neighborhood: parts_c.append(f"Col. {c_addr.neighborhood}") - if c_addr.postal_code: parts_c.append(f"CP {c_addr.postal_code}") - if c_addr.city: parts_c.append(c_addr.city) - if c_addr.state: parts_c.append(c_addr.state) - if c_addr.country: parts_c.append(c_addr.country) - - if parts_c: - c_dir_str = ", ".join(parts_c).upper() - - target_entity_data = { - "rfc": c_rfc or "", - "razon_social": client_obj.name or client_obj.short_name or "", - "direccion_completa": c_dir_str - } - - empresa = EmpresaSchema( - rfc=target_entity_data["rfc"], - razon_social=target_entity_data["razon_social"], - direccion_completa=target_entity_data["direccion_completa"] - ) - - # Destino/Origen (Clarion: Loc:DestinoOrigen = 'Destino/Origen: '&EqiFex:DestinoOrigenCOVE) - destino_origen_str = "" - if compliance and compliance.origin_destination_cove: - # Assuming enum value or string is what we want. - # If it's an Enum object, accessing .value is safer. - val = compliance.origin_destination_cove - if hasattr(val, 'value'): val = val.value - destino_origen_str = f"Destino/Origen: {val}" - - - - # 3. Datos Aviso (Invoice/Compliance/Logistics/Financials) - financials = header.financials - logistics = header.logistics - - # Fetch Items associated with this invoice (MOVED UP FOR WEIGHT CALCULATION) - items = db.query(LineItem).filter(LineItem.invoice_id == invoice_id).all() - - # Peso Bruto - peso_bruto_val = "0.0" - calculated_gross_weight = 0.0 - - # Calculate sum from items first - if items: - for item in items: - if item.quantity and item.quantity.gross_weight: - try: - calculated_gross_weight += float(item.quantity.gross_weight) - except (ValueError, TypeError): - pass - - if financials and financials.gross_weight and float(financials.gross_weight) > 0: - peso_bruto_val = f"{financials.gross_weight:,.2f}" - elif calculated_gross_weight > 0: - peso_bruto_val = f"{calculated_gross_weight:,.2f}" - elif pedimento and pedimento.gross_weight: - peso_bruto_val = f"{pedimento.gross_weight:,.2f}" - - # Candados (Seals) - candados_list = [] - if logistics and logistics.seal_number: - # Split by comma or space if multiple - candados_list = [s.strip() for s in logistics.seal_number.replace(',', ' ').split() if s.strip()] - - # Vehiculo / Contenedor Logic (Replicating Clarion) - # Clarion Logic: - # 1. Check for explicit `DatosVehiculo`. - # 2. Check for `EsFerrocarril`. - # 3. Build string from Trailer + Transport. - - # Since we don't have a direct "DatosVehiculo" text field in Logistics (usually), we construct it. - # However, we'll check if `license_plate` is being used as a catch-all or if we should build it. - - vehiculo_str = "" - tipo_display = "" - - # Basic Logistics Data - l_trailer = logistics.trailer_num.strip() if (logistics and logistics.trailer_num) else "" - l_placa = logistics.license_plate.strip() if (logistics and logistics.license_plate) else "" - l_trans_type = logistics.transport_type.strip() if (logistics and logistics.transport_type) else "" - l_vehicle_num = logistics.vehicle_num.strip() if (logistics and logistics.vehicle_num) else "" - l_container_types = logistics.container_types.strip() if (logistics and logistics.container_types) else "" - - # Check for Ferrocarril explicitly - is_rail = False - if "FERRO" in l_trans_type.upper() or "RAIL" in l_trans_type.upper(): - is_rail = True - - # --- LOGIC NUMERO / TIPO --- - final_numero = "" - final_tipo = "" - - # 1. Container Logic (Clarion: ContenedoresTipo parsing) - # Format expected: "CONTENEDOR|TIPO,CONTENEDOR2|TIPO2..." - if l_container_types: - # Take first container - first_cont_group = l_container_types.split(',')[0] # Split by comma - if '|' in first_cont_group: - parts = first_cont_group.split('|') - final_numero = parts[0].strip() - final_tipo = parts[1].strip() - else: - # Fallback if no pipe - final_numero = first_cont_group.strip() - final_tipo = "CONT" # Default? - - # 2. Transport = Container Logic - elif l_trans_type.upper() == "CONTENEDOR": - # Use trailer num as container num - if l_trailer: - final_numero = l_trailer - # Try to find Type? In Clarion it does a DB lookup into GTrailers.ClaveContenedor - # We assume 'CONTENEDOR' or a default if not found in simplified logic - final_tipo = "CONT" - - # 3. Trailer/General Logic (Fallback) - if not final_numero: - # Construct valid string - parts_veh = [] - if l_trailer: - parts_veh.append(f"TRAILER: {l_trailer}") - if not tipo_display: tipo_display = "TRAILER" - - if l_trans_type and l_trans_type.upper() != "NINGUNO": - if is_rail: - if l_vehicle_num: - parts_veh.append(f"CONTENEDOR: {l_vehicle_num}") - tipo_display = "FERROCARRIL" - else: - segment = l_trans_type - if l_vehicle_num: segment += f": {l_vehicle_num}" - parts_veh.append(segment) - if not tipo_display: tipo_display = l_trans_type - - if not parts_veh and l_placa: - parts_veh.append(f"PLACAS: {l_placa}") - - final_numero = ", ".join(parts_veh).upper() - final_tipo = tipo_display.upper() - - # Codigo de Aceptacion aka Acuse de Validacion - codigo_aceptacion_val = "" - if pedimento and pedimento.pedimento_validation: - # Assuming relationship "pedimento_validation" exists on Pedimentos model (lazy loaded) - # Or we can query it if relationship is scalar 'uselist=False' - if pedimento.pedimento_validation.validation_ack: - codigo_aceptacion_val = pedimento.pedimento_validation.validation_ack - - aviso = AvisoSchema( - pedimento_completo=pedimento_txt, - tipo_operacion=header.operation_type.upper() if header.operation_type else "EXP", - clave_pedimento=clave_ped, - acus_valor=compliance.edocument.upper() if (compliance and compliance.edocument) else "", - aduana_seccion=compliance.aduana if (compliance and compliance.aduana) else "", - numero_remesa=str(compliance.remesa) if (compliance and compliance.remesa) else "", - peso_bruto=peso_bruto_val, - codigo_aceptacion=codigo_aceptacion_val, - codigo_barras_b64=None, - clave_seccion=compliance.aduana if (compliance and compliance.aduana) else "", # Using Aduana as Section Key - marcas_numeros_bultos=f"{financials.bundle_count} BULTOS" if (financials and financials.bundle_count) else "1 BULTOS", - candados=candados_list, - vehiculo_placas=final_numero, - vehiculo_tipo=final_tipo, - observaciones=(header.observation_es or "") + ("\n" + destino_origen_str if destino_origen_str else ""), - numero_certificado=compliance.certificate_number if (compliance and compliance.certificate_number) else "", - tipo_documento=header.document_type or "FACTURA", # Default - firma_electronica=compliance.electronic_signature if (compliance and compliance.electronic_signature) else "" - ) - - # --- BARCODE GENERATION (PDF417) --- - # Replicating Clarion "LLENADOCODIGODEBARRAS" logic - try: - # Debug Log - debug_log = [] - debug_log.append(f"Processing Invoice {invoice_id}") - - # 1. Patente (4 Digits) - From Pedimento or Compliance - patente_txt = pedimento.license if pedimento and pedimento.license else "" - if not patente_txt and pedimento_txt: - # Fallback parsing "YY OFF LIC NUMBER" -> LIC is index 2 (0, 1, 2) - try: - parts = pedimento_txt.split() - if len(parts) >= 3: patente_txt = parts[2] - except: pass - - # 2. Pedimento Number (7 Digits) - pedimento_num = pedimento.pedimento_number if pedimento and pedimento.pedimento_number else "" - if not pedimento_num and pedimento_txt: - try: - parts = pedimento_txt.split() - if len(parts) >= 4: pedimento_num = parts[3] - except: pass - - # 3. Recinto (3 chars) - Default to 000 if invalid/missing as per Clarion 'ELSE LINEPRINT('000'...' - # Clarion: Loc:Recinto = EqiFex:Recinto - recinto_txt = "000" - if compliance and compliance.enclosure: - recinto_txt = compliance.enclosure[:3] - if not recinto_txt: recinto_txt = "000" - - # 4. E-Document - edoc_txt = aviso.acus_valor # Already uppercased - - # 5. Num Contenedor (Rail) or 000... - # Clarion: IF Loc:EsFerrocarril = 'SI' ... LINEPRINT(CLIP(Loc:NumContenedor)) ELSE LINEPRINT('0000000000000') - # We reused logic for 'vehiculo_placas' and 'vehiculo_tipo' earlier. - # Let's re-evaluate "EsFerrocarril" logic safely - is_rail_bar = False - if "FERRO" in final_tipo.upper() or "RAIL" in final_tipo.upper(): - is_rail_bar = True - - field_5 = "0000000000000" - if is_rail_bar: - # We extracted container into 'parts_veh' earlier but let's grab from raw if possible or from aviso? - # In our logic above: "CONTENEDOR: {l_vehicle_num}" was added to textual description. - # Let's use compliance.container_ids or logistics.vehicle_num - c_num = logistics.vehicle_num if logistics and logistics.vehicle_num else "" - if c_num: field_5 = c_num - - # 6. Firma Electronica - firma_txt = aviso.firma_electronica - - # 7. Cantidad Comercial (Format @n015.3 -> 15 chars total, 3 decimals?) - # We need to sum quantities. - cant_total = 0.0 - - if items: - for item in items: - for line in item: - # Priority: Quantity (UMA or Standard) - q = 0.0 - if line.quantity: - try: - if line.quantity.quantity_uma is not None: - q = float(line.quantity.quantity_uma) - elif line.quantity.quantity is not None: - q = float(line.quantity.quantity) - except (ValueError, TypeError): - q = 0.0 - cant_total += q - - # Format: 15 chars, 3 decimals? Actually Clarion LINEPRINT usually just prints the text. - # Clarion 'CLIP(FORMAT(Loc:CantTotal,@n015.3))' removes spaces. - cant_total_str = f"{cant_total:.3f}" - - - - # 8. Valor Total Dlls - # Clarion: LINEPRINT(FORMAT(Loc:ValorTotalDlls,@n012)) -> Integer? Or just standard? - # Clarion @n012 usually means right justified or just specific length? - # Code says: Loc:ValorTotalDlls = GSQLFile3.SQL3:C1 + (rounding logic). - val_usd = 0.0 - if financials: - try: - # Use value_me (Foreign Currency) as primary source for USD amount - if financials.value_me is not None: - val_usd = float(financials.value_me) - elif financials.value_mn is not None: - # Fallback to MN if ME is missing (though technically incorrect for USD field, avoids crash) - val_usd = float(financials.value_mn) - except (ValueError, TypeError): - val_usd = 0.0 - - # Clarion logic: - # IF GSQLFile3.SQL3:C1 > 0 AND GSQLFile3.SQL3:C1 < 1 THEN - # Loc:ValorTotalDlls = GSQLFile3.SQL3:C1 + (1 - GSQLFile3.SQL3:C1) (Result is 1.0) - # ELSE ... ROUND(...,1) or Raw. - - final_val_usd = val_usd - if 0.0 < val_usd < 1.0: - final_val_usd = 1.0 - elif (val_usd - int(val_usd)) > 0 and (val_usd - int(val_usd)) < 0.5: - # Clarion: IF Loc:Decimal > 0 AND Loc:Decimal < 0.5 THEN Loc:ValorTotalDlls = ROUND(GSQLFile3.SQL3:C1,1) - # Round to 1 decimal place? Or standard round? Python round matches generally. - final_val_usd = round(val_usd, 1) - - val_usd_str = f"{final_val_usd:.2f}" - - # 9. Cant Embarques (Rail) - field_9 = "000000000000" - if is_rail_bar: - # Logic for Cant Embarques? - # Clarion: EqiFex:CantGuiasEmbarque - # usage unknown in current DB. Defaulting to 0. - pass - - # 10. NIU / DTA (Rail) - field_10 = "0000000000000" - if is_rail_bar: - # Clarion: EqiFex:NumeroNIU - if compliance and compliance.niu: - field_10 = compliance.niu - - # 11. Remesa (4 chars) - remesa_txt = str(compliance.remesa) if (compliance and compliance.remesa) else "0" - - # 12. Filler - field_12 = "00000000.000" - - # Construct Line Prints (Text content for barcode) - # Clarion LINEPRINT separates by NewLine? Or is it one long string? - # "Glo:GeneraTXT" is a file. LINEPRINT appends a line. - # So the Barcode Content is a multi-line string or specific format. - # PDF417 normally encodes the full text block. - - # 1. Patente (4 Digits) - # Formatted: @P####P -> 4 digits. - # Assuming simple string slice or pad. - patente_formatted = f"{patente_txt}".strip()[:4] - - # 2. Pedimento (7 Digits) - pedimento_formatted = f"{pedimento_num}".strip()[:7] - - # 3. Recinto (3 Digits Zero Padded @n03) - # Ensure it's numeric-like for zero padding or just string pad? - # Clarion FORMAT(Loc:Recinto,@n03) implies numeric. - try: - recinto_val = int(recinto_txt) - recinto_formatted = f"{recinto_val:03d}" - except: - recinto_formatted = "000" - - # 4. E-Document (Left aligned, clipped) - edoc_formatted = edoc_txt.strip() - - # 5. Container (13 chars?) or Rail Logic - # Clarion: IF Rail -> CLIP(Loc:NumContenedor) ELSE '0000000000000' - if is_rail_bar and field_5 and len(field_5) > 0: - field_5_formatted = field_5.strip() - else: - field_5_formatted = "0000000000000" - - # 6. Firma (Clipped) - firma_formatted = firma_txt.strip() - - # 7. Cantidad (FORMAT(Loc:CantTotal,@n015.3)) -> 15 chars, 3 decimals, Zero Padded? - # Python f"{val:015.3f}" produces 15 chars total (including dot) with zero padding. - cant_total_formatted = f"{cant_total:015.3f}" - - # 8. Valor USD (FORMAT(Loc:ValorTotalDlls,@n012)) -> 12 chars, Integer?, Zero Padded? - # If Clarion @n012 means Integer: - # But previously we calculated rounding. If it is integer, we cast to int. - # Clarion default doubles formatted with @n012 usually rounds to integer. - # Let's assume Integer Zero Padded for now based on @n012 (no decimal part). - val_usd_formatted = f"{int(final_val_usd):012d}" - - # 9. Cant Embarques (Rail) (FORMAT(...,@n012)) - field_9_formatted = "000000000000" - if is_rail_bar: - # If we had a value... assuming 0 generally. - pass - - # 10. NIU (Rail) (@s13 -> String 13 chars?) or DTA - # Clarion: LINEPRINT(CLIP(FORMAT(Loc:NumeroNIU,@s13)),Glo:GeneraTXT) - # CLIP removes spaces, FORMAT @s13 makes it string 13? - # Actually CLIP(FORMAT(...,@s13)) might just mean "The string value". - # The ELSE is '0000000000000' (13 chars). - field_10_formatted = "0000000000000" - if is_rail_bar and field_10 != "0000000000000": - field_10_formatted = field_10.strip() - - # 11. Remesa (FORMAT(...,@n04) -> 4 digits zero padded) - try: - remesa_val = int(remesa_txt) - remesa_formatted = f"{remesa_val:04d}" - except: - remesa_formatted = "0000" - - # 12. Filler / Appendix 17 - # Clarion Logic: - # IF TipoFactura = 'IMPOTEMP'/'EXPO'/'IMPODEF' ... IF Apendice17=1 -> '000000000003' ELSE '00000000.000' - # Default '00000000.000' - field_12_formatted = "00000000.000" - if compliance and compliance.appendix_17 == 1: - # Check Doc Type? Assuming broadly for now based on flag. - field_12_formatted = "000000000003" - - barcode_lines = [ - patente_formatted, - pedimento_formatted, - recinto_formatted, - edoc_formatted, - field_5_formatted, - firma_formatted, - cant_total_formatted, - val_usd_formatted, - field_9_formatted, - field_10_formatted, - remesa_formatted, - field_12_formatted - ] - - # Join with appropriate separator. Clarion LINEPRINT adds CR/LF (Windows). - barcode_content = "\r\n".join(barcode_lines) - - # Generate Image - codes = pdf417gen.encode(barcode_content, columns=14) - image = pdf417gen.render_image(codes, scale=5, padding=5) - - # Convert to B64 - buffered = BytesIO() - image.save(buffered, format="PNG") - img_str = base64.b64encode(buffered.getvalue()).decode("utf-8") - - aviso.codigo_barras_b64 = f"data:image/png;base64,{img_str}" - debug_log.append("SUCCESS: Barcode generated.") - - except Exception as e: - import traceback - error_msg = f"Error generando codigo de barras InvID={invoice_id}: {str(e)}\n{traceback.format_exc()}" - print(error_msg) - debug_log.append(f"ERROR: {error_msg}") - aviso.codigo_barras_b64 = None - - # Write Debug Log - try: - with open("/tmp/barcode_debug.log", "a") as f: - f.write("\n".join(debug_log) + "\n--------------------------------\n") - except Exception as e_log: - print(f"FAILED TO WRITE LOG: {e_log}") - - # 4. Agente Aduanal - nombre_agente = "" - rfc_agente = "" - curp_agente = "" - - if compliance and compliance.customs_broker_id: - broker = db.query(CustomsBroker).filter(CustomsBroker.id == compliance.customs_broker_id).first() - if broker: - nombre_agente = broker.name or "" - rfc_agente = broker.tax_id or "" - curp_agente = broker.personal_id or "" - - agente = PersonaSchema( - nombre=nombre_agente, - rfc=rfc_agente, - curp=curp_agente - ) - - # 5. Mandatario (CustomsBrokerPersonnel) - mandatario = PersonaSchema(nombre="", rfc="", curp="") - - if broker: - # Try to find personnel associated with this broker - # Using direct query to ensure specific order if needed, typically just the first valid one - personnel = db.query(CustomsBrokerPersonnel).filter( - CustomsBrokerPersonnel.customs_broker_id == broker.id - ).first() - - if personnel: - # Construct name if main field is empty - full_name = personnel.name - if not full_name: - parts = [] - if personnel.first_name: parts.append(personnel.first_name) - if personnel.last_name: parts.append(personnel.last_name) - if personnel.middle_name: parts.append(personnel.middle_name) - full_name = " ".join(parts) - - mandatario = PersonaSchema( - nombre=full_name or "", - rfc=personnel.tax_id or "", - curp=personnel.personal_id or "" - ) - - return AvisoConsolidadoContext( - aviso=aviso, - empresa=empresa, - agente=agente, - mandatario=mandatario - ) - - except Exception as e: - print(f"Error Service A76 Export Aviso Consolidado: {e}") - raise HTTPException(status_code=500, detail=f"Error: {str(e)}") - - def generar_pdf(self, db: Session, invoice_id: int, company_id: int, progress_callback: Optional[Callable] = None) -> Tuple[bytes, str, str]: - if progress_callback: progress_callback(5, "Iniciando servicio de reporte...") - - datos = self.obtener_datos(db, invoice_id, company_id, progress_callback) - - if progress_callback: progress_callback(80, "Renderizando plantilla...") - - context = datos.model_dump() - html_content = self.template.render(**context) - nombre = f"AvisoConsolidado_Exp_{invoice_id}.pdf" - - if progress_callback: progress_callback(90, "Generando PDF final...") - - options = { - 'page-size': 'Letter', - 'margin-top': '0.5in', - 'margin-right': '0.5in', - 'margin-bottom': '0.5in', - 'margin-left': '0.5in', - 'encoding': "UTF-8", - 'enable-local-file-access': None - } - - pdf = pdfkit.from_string(html_content, False, options=options, configuration=self._get_wkhtmltopdf_config()) - - if progress_callback: progress_callback(100, "Completado") - return pdf, nombre, "application/pdf" diff --git a/backend/api/v1/modules/a76/reports/exportacion/aviso_consolidado/task.py b/backend/api/v1/modules/a76/reports/exportacion/aviso_consolidado/task.py deleted file mode 100644 index d70aad60..00000000 --- a/backend/api/v1/modules/a76/reports/exportacion/aviso_consolidado/task.py +++ /dev/null @@ -1,50 +0,0 @@ -from fastapi import HTTPException -import base64 -import logging -from core.celery_app import celery_app -from core.database import CoreSessionLocal - -from .service import AvisoConsolidadoExportacionService - -logger = logging.getLogger(__name__) - -@celery_app.task(name="generar_pdf_aviso_consolidado_exp_async", bind=True) -def generar_pdf_aviso_consolidado_exp_async(self, invoice_id: int, company_id: int): - # 1. Abrimos conexión a la DB - db = CoreSessionLocal() - try: - logger.info(f"Worker procesando Aviso Consolidado Exp {invoice_id}...") - - # 2. Instanciamos el servicio - service = AvisoConsolidadoExportacionService() - - # Update state to PROCESSING - self.update_state(state='PROCESSING', meta={'current': 5, 'total': 100, 'status': 'Iniciando generación...'}) - - def progress_callback(progress: int, status: str): - self.update_state(state='PROCESSING', meta={'current': progress, 'total': 100, 'status': status}) - - # 3. Generamos los bytes del PDF - pdf_bytes, nombre, media_type = service.generar_pdf( - db=db, - invoice_id=invoice_id, - company_id=company_id, - progress_callback=progress_callback - ) - - # 4. Codificamos a base64 - pdf_base64 = base64.b64encode(pdf_bytes).decode('utf-8') - - return { - "status": "success", - "file_name": nombre, - "content": pdf_base64, - "media_type": media_type - } - - except Exception as e: - logger.error(f"Error en Celery Worker Aviso Consolidado Exp: {str(e)}") - return {"status": "error", "message": str(e)} - - finally: - db.close() \ No newline at end of file diff --git a/backend/api/v1/modules/a76/reports/exportacion/aviso_consolidado/templates/avcon_exp.html b/backend/api/v1/modules/a76/reports/exportacion/aviso_consolidado/templates/avcon_exp.html deleted file mode 100644 index d24ba325..00000000 --- a/backend/api/v1/modules/a76/reports/exportacion/aviso_consolidado/templates/avcon_exp.html +++ /dev/null @@ -1,357 +0,0 @@ - - - - - - Aviso Consolidado - {{ aviso.pedimento_completo }} - - - - - - - - - -
-

AVISO CONSOLIDADO

-
-

Página 1 de 1

-
- - - - - - - - -
- NUM. PEDIMENTO: - {{ aviso.pedimento_completo }} - - T. OPER: - {{ aviso.tipo_operacion }} - - CVE. PEDIMENTO: - {{ aviso.clave_pedimento }} - - CERTIFICACIONES -
- TIPO: {{ aviso.tipo_documento }} -
- - - - - - - - - - - - - - - - - -
- NUMERO DE ACUSE DE VALOR: - {{ aviso.acus_valor }} - -

 

-
- ADUANA E/S: - {{ aviso.aduana_seccion }} - - NUM. REMESA: - {{ aviso.numero_remesa }} - - PESO BRUTO: - {{ aviso.peso_bruto }} -
DATOS DEL IMPORTADOR/EXPORTADOR
-
-

RFC:

-

{{ empresa.rfc }}

-
-
-

NOMBRE, DENOMINACION O RAZON SOCIAL:

-

{{ empresa.razon_social }}

-

{{ empresa.direccion_completa }}

-
-
- - - - - - - -
-

CODIGO DE ACEPTACION:

-

{{ aviso.codigo_aceptacion }}

-
-

CODIGO DE BARRAS

-
- {% if aviso.codigo_barras_b64 %} - - {% else %} -


- {% endif %} -
-
-

CLAVE DE LA SECCION ADUANERA DE DESPACHO:

-

{{ aviso.clave_seccion }}

-
- - - - - - - - -
MARCAS, NUMEROS Y TOTAL DE BULTOS: -
-

{{ aviso.marcas_numeros_bultos }}

-
- - - - - - - - - - -
NUMERO DE CANDADO: - {{ aviso.candados[0] if aviso.candados|length > 0 }}{{ aviso.candados[1] if aviso.candados|length > 1 }}{{ aviso.candados[2] if aviso.candados|length > 2 }}{{ aviso.candados[3] if aviso.candados|length > 3 }}{{ aviso.candados[4] if aviso.candados|length > 4 }}
- - - - - - - - - - - - - - -
1RA. REVISION -
2DA. REVISION -
- - - - - - - - -
NUMERO/TIPO:{{ aviso.vehiculo_placas }}{{ aviso.vehiculo_tipo }}
- - - - - - - - -
OBSERVACIONES
-

{{ aviso.observaciones }}

-
- - - - - -
-

AGENTE ADUANAL, APODERADO ADUANAL:

- -
- NOMBRE: - {{ agente.nombre }} -
- -
-
- RFC: - {{ agente.rfc }} -
-
- CURP: - {{ agente.curp }} -
-
- -
- MANDATARIO/PERSONA AUTORIZADA: -
- -
- NOMBRE: - {{ mandatario.nombre }} -
- -
-
- RFC: - {{ mandatario.rfc }} -
-
- CURP: - {{ mandatario.curp }} -
-
- -
- NUMERO DE SERIE DEL CERTIFICADO: - {{ aviso.numero_certificado }} -
-
- e.firma: -

{{ aviso.firma_electronica }}

-
-
- -

*********************************************************************** FIN DE LA - IMPRESION ***********************************************************************

- - - - \ No newline at end of file diff --git a/backend/api/v1/modules/a76/reports/exportacion/descargo/routes.py b/backend/api/v1/modules/a76/reports/exportacion/descargo/routes.py deleted file mode 100644 index 8882ed5f..00000000 --- a/backend/api/v1/modules/a76/reports/exportacion/descargo/routes.py +++ /dev/null @@ -1,89 +0,0 @@ - -from fastapi import APIRouter, Depends, BackgroundTasks, HTTPException -from fastapi.responses import JSONResponse, Response -from sqlalchemy.orm import Session -from typing import Dict, Any - -from core.database import get_core_db as get_db -from core.security import get_current_user, validate_access_to_resource -from api.v1.modules.core.tasks_tracking import track_and_dispatch -from .service import FIFOAssignmentService -from .task import generate_descarga_pdf_task # FORCE RELOAD -from celery.result import AsyncResult - -router = APIRouter() - -@router.post("/fifo-assign/{invoice_id}") -def run_fifo_assignment( - invoice_id: int, - db: Session = Depends(get_db), - current_user: Any = Depends(get_current_user), -): - """ - Executes FIFO (PEPS) calculation for an Export Invoice. - Returns the calculated discharges in JSON format. - """ - service = FIFOAssignmentService() - try: - discharges = service.calculate_fifo(db, invoice_id) - return { - "invoice_id": invoice_id, - "total_discharges": len(discharges), - "discharges": discharges - } - except Exception as e: - raise HTTPException( - status_code=500, - detail=f"Error calculating FIFO: {str(e)}" - ) - -@router.post("/{invoice_id}/download-async") -async def trigger_descarga_generation( - invoice_id: int, - company_id: int, - db: Session = Depends(get_db), - current_user: Any = Depends(get_current_user) -): - """ - Inicia la generación del reporte de Descarga PEPS en segundo plano (Celery). - Retorna el task_id para polling. - """ - try: - tenant_id = validate_access_to_resource(db, company_id, current_user) - task = track_and_dispatch( - db=db, - task=generate_descarga_pdf_task, - tenant_id=tenant_id, - company_id=company_id, - requested_by_user=current_user.get("preferred_username") or current_user.get("email") or current_user.get("sub"), - task_name="generate_descarga_pdf_task", - task_group="reports", - task_origin="a76/reports/exportacion/descargo", - args=[invoice_id, company_id], - ) - return {"task_id": task.id, "status": "processing"} - except Exception as e: - raise HTTPException(status_code=500, detail=str(e)) - -@router.get("/tasks/{task_id}") -async def get_task_status(task_id: str, current_user: Any = Depends(get_current_user)): - """ - Consulta el estado de la tarea de Celery. - """ - task_result = AsyncResult(task_id) - - response = { - "task_id": task_id, - "state": task_result.state, - "result": None, - "info": None - } - - if task_result.state == 'FAILURE': - response["result"] = str(task_result.result) - elif task_result.state == 'SUCCESS': - response["result"] = task_result.result - elif task_result.state == 'PROCESSING': - response["info"] = task_result.info - - return response diff --git a/backend/api/v1/modules/a76/reports/exportacion/descargo/service.py b/backend/api/v1/modules/a76/reports/exportacion/descargo/service.py deleted file mode 100644 index 960ffb30..00000000 --- a/backend/api/v1/modules/a76/reports/exportacion/descargo/service.py +++ /dev/null @@ -1,398 +0,0 @@ - -import shutil -import base64 -import pdfkit -from pathlib import Path -from typing import Tuple, List, Callable, Optional, Dict, Any -from jinja2 import Environment, FileSystemLoader, select_autoescape -from fastapi import HTTPException -from sqlalchemy.orm import Session -from pydantic import BaseModel -from decimal import Decimal -from datetime import datetime - - -# --- MODELOS (Imported from system for Header info) --- -from api.v1.modules.a76.invoices.models import InvoiceHeader, InvoiceComplianceMx -from api.v1.modules.a76.general_catalogs.company.models import Company -from api.v1.modules.a76.items.line_quantities.models import LineQuantity -from api.v1.modules.a76.parts.models import Part -from api.v1.modules.a76.items.models import LineItem -from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos -from api.v1.modules.a76.pedmientos.models.pedimento_dates import PedimentoDates -from api.v1.modules.core.tenants.models import Tenant -from api.v1.modules.a76.general_catalogs.exchange_rate.models import ExchangeRate -from sqlalchemy.orm import joinedload, load_only - -# --- FIFO SERVICE (Internalized) --- -from api.v1.modules.a76.items.series.models import Serie - -class FIFOAssignmentService: - """ - Service for calculating FIFO (PEPS) assignments in real-time. - Does not persist to database, returns calculated discharge objects. - """ - - def calculate_fifo(self, db: Session, invoice_id: int) -> List[Dict[str, Any]]: - """ - Calculates the FIFO trail for all lines in an export invoice. - Returns a list of calculated discharges. - """ - # 1. Get Export Lines - export_lines = db.query(LineItem).join(LineItem).filter( - LineItem.invoice_id == invoice_id - ).options( - joinedload(LineItem.quantity), - joinedload(LineItem.description), - joinedload(LineItem.customs), - joinedload(LineItem.unit_of_measure_info), - joinedload(LineItem.part_info) - ).all() - - results = [] - self._log(f"Starting FIFO for Invoice {invoice_id}. Export Lines: {len(export_lines)}") - - for exp_line in export_lines: - qty_needed = float(exp_line.quantity.quantity) if exp_line.quantity and exp_line.quantity.quantity is not None else 0.0 - if qty_needed <= 0: - continue - - part_number = exp_line.part_number_id - if not part_number: - self._log(f"Skipping line {exp_line.id}, no part number") - continue - - self._log(f"Processing Exp Line {exp_line.id}, Part: {part_number}, Qty Needed: {qty_needed}") - - # --- SERIES EXPO LOOKUP --- - series_desc = "" - series_count = db.query(Serie).filter(Serie.line_item_id == exp_line.id).count() - if series_count > 0: - series_list = db.query(Serie).filter(Serie.line_item_id == exp_line.id).all() - parts_str = [] - for idx, s in enumerate(series_list, 1): - line_parts = [f"{idx}) Serie: {s.serial_numbers or ''}", f"Modelo: {s.model or ''}", f"Parte: {exp_line.part_info.part_number if exp_line.part_info else ''}", f"Num ID Expo: {s.number_id or ''}", f"SubModelo: {s.sub_model or ''}"] - parts_str.append(", ".join([p for p in line_parts if p])) - if parts_str: - series_desc = "\nSeries:\n" + "\n".join(parts_str) - - # 2. Find Import Candidates (FIFO order by payment date) - # Use outerjoin for pedimento dates to avoid filtering out candidates with missing dates - candidates = db.query(LineItem).join(InvoiceHeader)\ - .join(InvoiceComplianceMx).join(InvoiceComplianceMx.pedimento).outerjoin(Pedimentos.pedimento_dates)\ - .filter( - LineItem.part_number_id == part_number, - InvoiceHeader.operation_type == 'imp', # Assuming 'imp' is the value for Import based on Enum - ).order_by( - PedimentoDates.payment_date.asc() - ).options( - joinedload(LineItem.quantity), - joinedload(LineItem.customs), - joinedload(LineItem.financial), - joinedload(LineItem.item).joinedload(LineItem.invoice).joinedload(InvoiceHeader.compliance_mx).joinedload(InvoiceComplianceMx.pedimento).joinedload(Pedimentos.pedimento_dates) - ).all() - - self._log(f"Found {len(candidates)} candidates for {part_number}") - - for imp_line in candidates: - if qty_needed <= 0: - break - - imp_qty_total = float(imp_line.quantity.quantity) if imp_line.quantity and imp_line.quantity.quantity is not None else 0.0 - if imp_qty_total <= 0: - continue - - take = min(qty_needed, imp_qty_total) - ratio = take / imp_qty_total if imp_qty_total > 0 else 0 - - # Calculate Proportions - imp_weight = float(imp_line.quantity.net_weight) if imp_line.quantity and imp_line.quantity.net_weight is not None else 0.0 - # imp_val_mn = float(imp_line.customs.customs_value) if imp_line.customs and imp_line.customs.customs_value is not None else 0.0 - imp_val_me = float(imp_line.financial.customs_value_usd) if imp_line.financial and imp_line.financial.customs_value_usd is not None else 0.0 - imp_igi = float(imp_line.customs.igi_amount) if imp_line.customs and imp_line.customs.igi_amount is not None else 0.0 - - imp_inv = imp_line.item.invoice - ped = imp_inv.compliance_mx.pedimento if imp_inv and imp_inv.compliance_mx else None - - # --- EXCHANGE RATE VALIDATION --- - payment_date = ped.pedimento_dates.payment_date if ped and ped.pedimento_dates else None - exchange_rate_val = 1.0 - validation_error = None - - if payment_date: - er_obj = db.query(ExchangeRate).filter(ExchangeRate.date == payment_date).first() - if er_obj: - exchange_rate_val = float(er_obj.value) - else: - # Try previous day if strict match fails (mimicking SisGen:UtilizarFechaPagoPedDeUnDiaAnterior logic broadly or just flagging) - # For now, flag it. - validation_error = f"Tipo de Cambio no encontrado para fecha {payment_date}" - - # Calculate Valor MN based on Clarion logic: (CantDesc * ValorImpoME / CantImpo) * TC - # which simplifies to: Ratio * ValorImpoME * TC - val_mn_calc = (imp_val_me * ratio) * exchange_rate_val - - discharge = { - "export_line_id": exp_line.id, - "import_line_id": imp_line.id, - "quantity": take, - "net_weight": imp_weight * ratio, - "value_mxn": val_mn_calc, - "value_usd": imp_val_me * ratio, - "igi_amount": imp_igi * ratio, - "import_invoice": imp_inv.invoice_number if imp_inv else "N/A", - "pedimento": ped.pedimento_number if ped else "N/A", - "pedimento_clave": ped.pedimento_code if ped else "", - "pedimento_date": payment_date.isoformat() if payment_date else None, - "series_desc": series_desc, - "validation_error": validation_error - } - - self._log(f"MATCH: Taking {take} from Imp Line {imp_line.id}") - results.append(discharge) - qty_needed -= take - - return results - - def _log(self, msg): - try: - with open("/tmp/fifo_debug.log", "a") as f: - f.write(f"{datetime.now()}: {msg}\n") - except: pass - - -# --- SCHEMAS FOR TEMPLATE CONTEXT --- - -class DischargeItemSchema(BaseModel): - # Column 1: Pedimento Info - pedimento_numero: str - pedimento_clave: str - pedimento_fecha_pago: str - - # Column 2: Import Invoice - factura_impo: str - - # Column 3: Part Info - numero_parte: str - descripcion: str - fraccion: str - origen_pref_sector: str # e.g. "CHN-GENERAL" - - # Metrics - cantidad: str - unidad_medida: str - peso_neto: str - - # Values - valor_mn: str - valor_me: str - valor_igi: str - - # Flags - se_pago: str # "0.0" or "Yes"? Image says "0.0" in column "Se Pago"? No, "Se Pago" might be a flag, image key implies payment. - # Image: "Se Pago" column has "0.0"? No, look closer. - # "Value/Monto IGI USD/Dolares" has "0.0". - # "Se Pago" column seems empty or has '1'? - # Wait, looking at image: - # Col: "Se Pago", Row: "0.0"? No that's IGI. - # Let's assume Se Pago is a boolean/string. - # Last col: "Linea Expo". - - linea_expo: str - - # Errors - error_msg: Optional[str] = None - - # Helper for Jinja (if methods not allowed in pydantic models in template) - def __init__(self, **data): - super().__init__(**data) - -class DischargeContext(BaseModel): - items: List[DischargeItemSchema] - invoice_number: str - company_name: str - company_address: str - company_rfc: str - company_immex: str - - # Totals - total_cantidad: str - total_peso: str - total_valor_mn: str - total_valor_me: str - total_igi: str - -class DescargaReportService: - def formatear_numero(self, valor, decimales: int = 2): - if valor is None: return "0.00" - try: - return "{:,.{}f}".format(float(valor), decimales) - except: return "0.00" - - def __init__(self): - self.template_dir = Path(__file__).parent / "templates" - self.jinja_env = Environment( - loader=FileSystemLoader(self.template_dir), - autoescape=select_autoescape(['html', 'xml']) - ) - self.template = self.jinja_env.get_template('descarga.html') - - def _get_wkhtmltopdf_config(self): - path = shutil.which("wkhtmltopdf") or "/usr/local/bin/wkhtmltopdf" or "/usr/bin/wkhtmltopdf" - if not Path(path).exists(): - raise RuntimeError("wkhtmltopdf no encontrado.") - return pdfkit.configuration(wkhtmltopdf=path) - - def obtener_datos(self, db: Session, invoice_id: int, company_id: int, progress_callback: Optional[Callable] = None) -> DischargeContext: - try: - if progress_callback: progress_callback(10, "Buscando factura...") - - # --- 1. Obtener Cabeceras (Igual que antes) --- - header = db.query(InvoiceHeader).filter(InvoiceHeader.id == invoice_id).first() - if not header: - raise HTTPException(status_code=404, detail="Factura no encontrada") - - tenant = db.query(Tenant).filter(Tenant.id == header.tenant_id).first() - company = db.query(Company).filter(Company.id == header.company_id).first() - - # --- 2. Obtener Líneas de Exportación (Lo que necesitamos cubrir) --- - if progress_callback: progress_callback(20, "Obteniendo items a exportar...") - - export_lines = db.query(LineItem).filter( - LineItem.invoice_id == invoice_id - ).options( - joinedload(LineItem.quantity), - joinedload(LineItem.customs), - joinedload(LineItem.description), - joinedload(LineItem.unit_of_measure_info), - joinedload(LineItem.part_info) - ).join(LineItem).all() - - items_reporte = [] - - # --- 3. EL ALGORITMO PEPS EN VIVO --- - if progress_callback: progress_callback(40, "Calculando PEPS en tiempo real...") - - fifo_service = FIFOAssignmentService() - discharges = fifo_service.calculate_fifo(db, invoice_id) - - print(f"DEBUG: Calculated {len(discharges)} discharges.") # FORCE PRINT - - # Create map for faster/safer lookup - exp_map = {l.id: l for l in export_lines} - - for d in discharges: - exp_id = d["export_line_id"] - exp_line = exp_map.get(exp_id) - - if not exp_line: - print(f"DEBUG: Skipping discharge, Exp Line {exp_id} not found in map keys: {list(exp_map.keys())}") - continue - - print(f"DEBUG: Adding item to report: Imp {d['import_line_id']} -> Exp {exp_id}") - - desc_final = exp_line.description.description_spanish if exp_line.description else "S/D" - items_reporte.append(DischargeItemSchema( - pedimento_numero=d["pedimento"], - pedimento_clave=d["pedimento_clave"], - pedimento_fecha_pago=d["pedimento_date"].split("T")[0] if d["pedimento_date"] else "", - - factura_impo=d["import_invoice"], - - numero_parte=exp_line.part_info.part_number if exp_line.part_info else "", - descripcion=desc_final, - fraccion=exp_line.customs.fraction if exp_line.customs else "", - origen_pref_sector=f"{exp_line.customs.origin_country or ''} - {exp_line.customs.sector or ''}" if exp_line.customs else "", - - cantidad=self.formatear_numero(d["quantity"], 3), - unidad_medida=exp_line.unit_of_measure_info.code if exp_line.unit_of_measure_info else "PZA", - - peso_neto=self.formatear_numero(d["net_weight"], 3), - valor_mn=self.formatear_numero(d["value_mxn"]), - valor_me=self.formatear_numero(d["value_usd"]), - valor_igi=self.formatear_numero(d["igi_amount"]), - - se_pago="", - linea_expo=str(exp_line.line_number), - error_msg=d.get("validation_error") - )) - - print(f"DEBUG: Final Report Items Count: {len(items_reporte)}") - - # --- 4. Totales y Finalización (Igual que antes) --- - addr_str = "DIRECCION NO REGISTRADA" - immex_val = "" - - if company: - # Address Logic - if company.addresses: - # Prefer 'main' address, otherwise take the first one - main_addr = next((a for a in company.addresses if a.address_type == 'main'), company.addresses[0]) - - parts = [] - if main_addr.street: parts.append(main_addr.street) - if main_addr.exterior_number: parts.append(f"No. {main_addr.exterior_number}") - if main_addr.neighborhood: parts.append(main_addr.neighborhood) - if main_addr.city: parts.append(main_addr.city) - if main_addr.state: parts.append(main_addr.state) - if main_addr.postal_code: parts.append(f"CP {main_addr.postal_code}") - - if parts: - addr_str = ", ".join(parts) - - # IMMEX Logic - if company.program and "IMMEX" in company.program and company.program_number: - immex_val = company.program_number - - # Recalcular totales basados en la lista generada - t_cant = sum(float(i.cantidad.replace(",","")) for i in items_reporte) - t_peso = sum(float(i.peso_neto.replace(",","")) for i in items_reporte) - t_mn = sum(float(i.valor_mn.replace(",","")) for i in items_reporte) - t_me = sum(float(i.valor_me.replace(",","")) for i in items_reporte) - t_igi = sum(float(i.valor_igi.replace(",","")) for i in items_reporte) - - return DischargeContext( - items=items_reporte, - invoice_number=header.invoice_number or "SIN FOLIO", - company_name=company.name if company else (tenant.name if tenant else "EMPRESA DESCONOCIDA"), - company_address=addr_str, - company_rfc=company.rfc if company else "", - company_immex=immex_val, - total_cantidad=self.formatear_numero(t_cant, 3), - total_peso=self.formatear_numero(t_peso, 3), - total_valor_mn=self.formatear_numero(t_mn), - total_valor_me=self.formatear_numero(t_me), - total_igi=self.formatear_numero(t_igi) - ) - - except Exception as e: - print(f"Error Service Discharge Report: {e}") - raise HTTPException(status_code=500, detail=f"Error: {str(e)}") - - def generar_pdf(self, db: Session, invoice_id: int, company_id: int, progress_callback: Optional[Callable] = None) -> Tuple[bytes, str, str]: - if progress_callback: progress_callback(5, "Iniciando servicio de reporte...") - - datos = self.obtener_datos(db, invoice_id, company_id, progress_callback) - - if progress_callback: progress_callback(80, "Renderizando plantilla...") - - context = datos.model_dump() - html_content = self.template.render(**context) - nombre = f"Descarga_{datos.invoice_number}.pdf" - - if progress_callback: progress_callback(90, "Generando PDF final...") - - options = { - 'page-size': 'Letter', - 'orientation': 'Landscape', # Correct argument for wkhtmltopdf - 'margin-top': '0.5in', - 'margin-right': '0.5in', - 'margin-bottom': '0.5in', - 'margin-left': '0.5in', - 'encoding': "UTF-8" - } - - pdf = pdfkit.from_string(html_content, False, options=options, configuration=self._get_wkhtmltopdf_config()) - - if progress_callback: progress_callback(100, "Completado") - return pdf, nombre, "application/pdf" diff --git a/backend/api/v1/modules/a76/reports/exportacion/descargo/task.py b/backend/api/v1/modules/a76/reports/exportacion/descargo/task.py deleted file mode 100644 index 93e6738e..00000000 --- a/backend/api/v1/modules/a76/reports/exportacion/descargo/task.py +++ /dev/null @@ -1,50 +0,0 @@ - -from celery import shared_task -from sqlalchemy.orm import Session -from core.database import CoreSessionLocal as SessionLocal -from .service import DescargaReportService # FORCE RELOAD 2 -from fastapi import HTTPException -import base64 -import traceback - -@shared_task(bind=True, name="generate_descarga_pdf_task") -def generate_descarga_pdf_task(self, invoice_id: int, company_id: int): - """ - Tarea de Celery para generar el PDF del Reporte de Descarga - """ - db: Session = SessionLocal() - try: - service = DescargaReportService() - - def update_progress(percent, message): - self.update_state( - state='PROCESSING', - meta={'current': percent, 'total': 100, 'status': message} - ) - - pdf_bytes, filename, content_type = service.generar_pdf( - db=db, - invoice_id=invoice_id, - company_id=company_id, - progress_callback=update_progress - ) - - # Retornar el PDF en base64 para que el front lo descargue - pdf_b64 = base64.b64encode(pdf_bytes).decode('utf-8') - - return { - "status": "success", - "file_name": filename, - "content": pdf_b64, - "media_type": content_type, - "message": "Reporte generado correctamente" - } - - except Exception as e: - self.update_state( - state='FAILURE', - meta={'exc_type': type(e).__name__, 'exc_message': str(e)} - ) - raise e - finally: - db.close() diff --git a/backend/api/v1/modules/a76/reports/exportacion/descargo/templates/descarga.html b/backend/api/v1/modules/a76/reports/exportacion/descargo/templates/descarga.html deleted file mode 100644 index 177847ba..00000000 --- a/backend/api/v1/modules/a76/reports/exportacion/descargo/templates/descarga.html +++ /dev/null @@ -1,184 +0,0 @@ - - - - - - Descarga de Factura {{ invoice_number }} - - - - - - - - - - - - -
- DESCARGA DE LA FACTURA: {{ invoice_number }} - - {{ company_name }}
- {{ company_address }}
- R.F.C.: {{ company_rfc }}, IMMEX: {{ company_immex }} -
- Page/Página: Of/de -
- -
La factura se descargo de:
- - - - - - - - - - - - - - - - - - - - {% for item in items %} - - - - - - - - - - - - - - {% endfor %} - - - - - - - - - - - - - - - - - - - - - - - - - - -
No. Pedimento
Fecha de Pago
Import Invoice/
Factura de Impo.
Part Number/No. de Parte
Description/Descripción
Fracción
(Origen-Prefer.-Sector)
Quantity/
Cantidad U.M.
Net Weight/
Peso Neto (KGS)
Value/Valor M.N.
MXP/Pesos
Value/Valor M.E.
USD/Dolares
Value/Monto IGI
USD/Dolares
Se
Pagó
Linea Expo
Expo Line
-
{{ item.pedimento_numero }}
- - - - - -
{{ - item.pedimento_clave }}{{ - item.pedimento_fecha_pago }}
-
{{ item.factura_impo }} - {{ item.numero_parte }}
- {{ item.descripcion }} -
- {{ item.fraccion }}
- {{ item.origen_pref_sector }} -
- {{ item.cantidad }} {{ item.unidad_medida }} - {{ item.peso_neto }}{{ item.valor_mn }}{{ item.valor_me }}{{ item.valor_igi }}{{ item.se_pago }}{{ item.linea_expo }}
Totales de los Comp. Temporales:{{ total_cantidad }}{{ total_peso }}{{ total_valor_mn }}{{ total_valor_me }}{{ total_igi }}
TOTALES:{{ total_cantidad }}{{ total_peso }}{{ total_valor_mn }}{{ total_valor_me }}{{ total_igi }}
- - - - \ No newline at end of file diff --git a/backend/api/v1/modules/a76/reports/exportacion/partes_descargadas/__init__.py b/backend/api/v1/modules/a76/reports/exportacion/partes_descargadas/__init__.py deleted file mode 100644 index 20433b71..00000000 --- a/backend/api/v1/modules/a76/reports/exportacion/partes_descargadas/__init__.py +++ /dev/null @@ -1 +0,0 @@ -"""Downloaded parts report module.""" \ No newline at end of file diff --git a/backend/api/v1/modules/a76/reports/exportacion/partes_descargadas/routes.py b/backend/api/v1/modules/a76/reports/exportacion/partes_descargadas/routes.py deleted file mode 100644 index 007b17c2..00000000 --- a/backend/api/v1/modules/a76/reports/exportacion/partes_descargadas/routes.py +++ /dev/null @@ -1,43 +0,0 @@ -from typing import Any - -from fastapi import APIRouter, Body, Depends, Query -from fastapi.responses import StreamingResponse -from sqlalchemy.orm import Session - -from core.database import get_core_db -from core.security import get_current_user, validate_access_to_resource - -from .schemas import DownloadedPartsReportBootstrap, DownloadedPartsReportRequest -from .service import DownloadedPartsReportService - -router = APIRouter(tags=["Reports - Downloaded Parts"]) - - -@router.get( - "/bootstrap", - summary="Get downloaded parts report bootstrap", - description="Returns the base metadata required to render the downloaded parts report screen.", -) -def get_downloaded_parts_report_bootstrap( - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: Any = Depends(get_current_user), -): - tenant_id = validate_access_to_resource(db, company_id, current_user) - service = DownloadedPartsReportService() - return service.build_bootstrap(company_id=company_id, tenant_id=tenant_id) - - -@router.post( - "/generate", - summary="Generate downloaded parts report CSV", -) -def generate_downloaded_parts_report( - company_id: int = Query(..., description="Company ID"), - request: DownloadedPartsReportRequest = Body(...), - db: Session = Depends(get_core_db), - current_user: Any = Depends(get_current_user), -) -> StreamingResponse: - tenant_id = validate_access_to_resource(db, company_id, current_user) - service = DownloadedPartsReportService() - return service.generate_csv(db=db, req=request, company_id=company_id, tenant_id=tenant_id) diff --git a/backend/api/v1/modules/a76/reports/exportacion/partes_descargadas/schemas.py b/backend/api/v1/modules/a76/reports/exportacion/partes_descargadas/schemas.py deleted file mode 100644 index 923371ae..00000000 --- a/backend/api/v1/modules/a76/reports/exportacion/partes_descargadas/schemas.py +++ /dev/null @@ -1,56 +0,0 @@ -from datetime import date -from typing import Literal, Optional - -from pydantic import BaseModel, Field - - -class DownloadedPartsReportRequest(BaseModel): - date_from: date - date_to: date - class_from: Optional[str] = None - class_to: Optional[str] = None - print_class_mode: Literal['exported', 'downloaded'] = 'downloaded' - exchange_rate_mode: Literal['invoice', 'pedimento_payment'] = 'invoice' - currency_mode: Literal['dollars', 'pesos', 'both'] = 'both' - temporality_mode: Literal['temporales', 'definitivos', 'ambos'] = 'temporales' - weight_type_mode: Literal['kilos', 'libras', 'ambos'] = 'kilos' - operation_mode: Literal['importacion', 'exportacion'] = 'importacion' - # Optional filters - material_type: Optional[str] = None - invoice_type: Optional[str] = None - parts: Optional[list[str]] = None - pedimento_key: Optional[str] = None - provider_id: Optional[int] = None - sold_to_id: Optional[int] = None - shipped_to_id: Optional[int] = None - destination_customs: Optional[str] = None - # Option flags - include_series: bool = False - print_class_total: bool = False - include_totals_by_fraction: bool = False - julian_date: bool = False - show_item_description: bool = True - include_exempt_fraction: bool = False - show_export_fraction: bool = False - include_rule_octava: bool = False - include_american_fraction_and_country: bool = False - respect_import_invoice_value_in_pesos: bool = False - show_all_temporary_balances: bool = False - - -class DownloadedPartsReportSection(BaseModel): - id: str - title: str - description: str - - -class DownloadedPartsReportBootstrap(BaseModel): - report_key: str = Field(default="downloaded_parts") - title: str = Field(default="Partes descargadas") - description: str - company_id: int - tenant_id: int - status: str = Field(default="draft") - available_filters: list[str] - next_steps: list[str] - sections: list[DownloadedPartsReportSection] diff --git a/backend/api/v1/modules/a76/reports/exportacion/partes_descargadas/service.py b/backend/api/v1/modules/a76/reports/exportacion/partes_descargadas/service.py deleted file mode 100644 index cd0805c9..00000000 --- a/backend/api/v1/modules/a76/reports/exportacion/partes_descargadas/service.py +++ /dev/null @@ -1,959 +0,0 @@ -import csv -import io -from datetime import date, datetime, timedelta -from decimal import Decimal -from typing import Optional - -from fastapi import HTTPException -from fastapi.responses import StreamingResponse -from sqlalchemy import and_, func, or_, select -from sqlalchemy.orm import Session, aliased, selectinload - -from api.v1.modules.a24.discharges.models import DischargeDetail, DischargeHeader, DischargeType -from api.v1.modules.a24.fa.fa_item_lines.models import FaLineItem -from api.v1.modules.a76.app_settings.service import AppSettingsService -from api.v1.modules.a76.classes.models import Class -from api.v1.modules.a76.general_catalogs.company.models import Company -from api.v1.modules.a76.general_catalogs.exchange_rate.models import ExchangeRate -from api.v1.modules.a76.general_catalogs.fractions.tariff_fractions.models import TariffFraction -from api.v1.modules.a76.invoices.models import InvoiceComplianceMx, InvoiceHeader, InvoiceStatus -from api.v1.modules.a76.items.line_customs.models import LineCustom -from api.v1.modules.a76.items.line_descriptions.models import LineDescription -from api.v1.modules.a76.items.line_financials.models import LineFinancial -from api.v1.modules.a76.items.line_quantities.models import LineQuantity -from api.v1.modules.a76.items.models import LineItem -from api.v1.modules.a76.items.series.models import Serie -from api.v1.modules.a76.parts.models import Part -from api.v1.modules.a76.pedmientos.models.pedimento_dates import PedimentoDates -from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos - -from .schemas import DownloadedPartsReportBootstrap, DownloadedPartsReportRequest, DownloadedPartsReportSection - -LBS_PER_KG = Decimal('2.20462') - - -class DownloadedPartsReportService: - # ------------------------------------------------------------------ - # Helpers - # ------------------------------------------------------------------ - - def _build_pedimento_str( - self, - year: Optional[str], - customs: Optional[str], - license_: Optional[str], - number: Optional[str], - ) -> str: - if not all([year, customs, license_, number]): - return '' - return f"{year}/{customs}/{license_}/{number}" - - def _build_pedimento_18( - self, - year: Optional[str], - customs: Optional[str], - license_: Optional[str], - number: Optional[str], - code: Optional[str], - ) -> str: - if not all([year, customs, license_, number, code]): - return '' - return f"{year}{customs}{license_}{number}{code}" - - def _format_date(self, d: Optional[date | datetime], julian: bool = False) -> str: - if d is None: - return '' - if isinstance(d, datetime): - d = d.date() - if julian: - return str(d.timetuple().tm_yday).zfill(3) - return d.strftime('%d/%m/%Y') - - def _decimal_str(self, val, decimals: int = 2) -> str: - if val is None: - return '' - return f"{Decimal(str(val)):.{decimals}f}" - - def _to_decimal(self, value) -> Decimal: - if value is None: - return Decimal('0') - if isinstance(value, Decimal): - return value - return Decimal(str(value)) - - def _clean_text(self, value: Optional[str]) -> str: - if not value: - return '' - return value.replace(',', ' ').replace('\r', ' ').replace('\n', ' ').strip() - - def _excel_text(self, value) -> str: - if value is None or value == '': - return '' - return f"'{value}" - - def _as_date(self, value: Optional[date | datetime]) -> Optional[date]: - if value is None: - return None - if isinstance(value, datetime): - return value.date() - return value - - def _adjust_payment_date( - self, - value: Optional[date | datetime], - use_previous_day: bool, - ) -> Optional[date]: - resolved = self._as_date(value) - if resolved is None: - return None - if use_previous_day: - return resolved - timedelta(days=1) - return resolved - - def _pedimento_headers(self, company: Optional[Company], ped_type: str) -> tuple[str, str]: - if company and company.rfc == 'MWE220512359': - if ped_type == 'export': - return ('PEDIMENTO DE EXPORTACIÓN', 'PEDIMENTO DE EXPORTACIÓN ORIGINAL RECTIFICADO') - return ('PEDIMENTO DE IMPORTACIÓN', 'PEDIMENTO DE IMPORTACIÓN ORIGINAL RECTIFICADO') - - if ped_type == 'export': - return ('PEDIMENTO EXPORTACIÓN', 'PED. EXPO R1') - return ('PEDIMENTO IMPORTACIÓN', 'PED. IMPO R1') - - def _pedimento_values( - self, - company: Optional[Company], - pedimento: str, - pedimento_r1: str, - ) -> tuple[str, str]: - if company and company.rfc == 'MWE220512359': - if pedimento_r1: - return pedimento_r1, pedimento - return pedimento, '' - return pedimento, pedimento_r1 - - def _build_headers(self, req: DownloadedPartsReportRequest, company: Optional[Company]) -> list[str]: - export_headers = self._pedimento_headers(company, 'export') - import_headers = self._pedimento_headers(company, 'import') - - headers = [ - export_headers[0], - export_headers[1], - 'FECHA PAGO PED EXPO', - 'CLAVE', - 'FECHA DE DESCARGA', - 'FACTURA EXPO', - 'FECHA EMISION', - 'CLASE', - 'DESCRIPCION', - 'FRACCION ARANCELARIA', - 'CANTIDAD', - 'U.M.', - import_headers[0], - import_headers[1], - 'FECHA PAGO PED IMPO', - 'FACTURA IMPORTACION', - 'FECHA EMISION IMPO', - 'PESO NETO', - 'VALOR TOTAL (DOLARES)', - 'VALOR TOTAL (MONEDA NACIONAL)', - 'TIPO DE CAMBIO', - 'NO. DE PARTE', - 'DESCRIPCION PARTE', - 'TIPOEXPO', - 'FRACCION CLASE', - 'PEDIMENTO IMPO 18', - 'PEDIMENTO EXPO 18', - 'U.M. TARIFA', - ] - - if req.include_american_fraction_and_country: - headers.extend(['FRACCION AMERICANA', 'PAIS DE ORIGEN']) - - return headers - - def _write_company_header( - self, - writer: csv.writer, - company: Optional[Company], - settings: dict, - ) -> None: - writer.writerow(['REPORTE DE CLASES EXPORTADAS/DESCARGADAS']) - - if not company: - writer.writerow([ - f"Fecha Generación: {datetime.now().strftime('%d/%m/%Y')} Hora Generación: {datetime.now().strftime('%H:%M:%S')}" - ]) - writer.writerow(['PROVEEDOR DE SOFTWARE: ADUANASOFT']) - writer.writerow([]) - return - - if company.name: - writer.writerow([company.name]) - - main_address = next((addr for addr in company.addresses if addr.address_type == 'main'), None) - if main_address: - fiscal_line = 'Domicilio Fiscal: ' + (main_address.street or '') - if main_address.exterior_number: - fiscal_line += f" Ext. Num: {main_address.exterior_number}" - if main_address.interior_number: - fiscal_line += f" Int. Num: {main_address.interior_number}" - writer.writerow([fiscal_line.strip()]) - - colony_line = (main_address.neighborhood or '').strip() - if main_address.postal_code: - colony_line = (colony_line + f" Código Postal: {main_address.postal_code}").strip() - if colony_line: - writer.writerow([colony_line]) - - city_line = ' '.join(filter(None, [main_address.city, main_address.state])) - if city_line: - writer.writerow([city_line]) - - industrial_address = next( - (addr for addr in company.addresses if addr.address_type == 'industrial'), - None, - ) - if industrial_address: - industrial_line = 'Domicilio Industrial: ' + (industrial_address.street or '') - if industrial_address.exterior_number: - industrial_line += f" Ext. Num: {industrial_address.exterior_number}" - if industrial_address.interior_number: - industrial_line += f" Int. Num: {industrial_address.interior_number}" - writer.writerow([industrial_line.strip()]) - - industrial_colony = (industrial_address.neighborhood or '').strip() - if industrial_address.postal_code: - industrial_colony = ( - industrial_colony + f" Código Postal: {industrial_address.postal_code}" - ).strip() - if industrial_colony: - writer.writerow([industrial_colony]) - - industrial_city = ' '.join(filter(None, [industrial_address.city, industrial_address.state])) - if industrial_city: - writer.writerow([industrial_city]) - - if company.rfc: - writer.writerow([f"R.F.C: {company.rfc}"]) - - if settings.get('mostrarprogramaimmexprosec'): - if company.program_number: - if company.program == 'Maquila': - writer.writerow([f"SICEX: {company.program_number}"]) - else: - writer.writerow([f"{company.program or 'Programa'}: {company.program_number}"]) - if company.prosec_authorization: - writer.writerow([f"Autorización PROSEC: {company.prosec_authorization}"]) - - writer.writerow([ - f"Fecha Generación: {datetime.now().strftime('%d/%m/%Y')} Hora Generación: {datetime.now().strftime('%H:%M:%S')}" - ]) - writer.writerow(['PROVEEDOR DE SOFTWARE: ADUANASOFT']) - writer.writerow([]) - - def _get_settings(self, db: Session, tenant_id: int, company_id: int) -> dict: - return AppSettingsService.get_resolved_settings(db, tenant_id, company_id) or {} - - def _collect_rate_dates( - self, - rows: list, - req: DownloadedPartsReportRequest, - use_previous_payment_day: bool, - ) -> set[date]: - dates: set[date] = set() - - for row in rows: - if req.exchange_rate_mode == 'invoice': - export_invoice_date = self._as_date(row.expo_invoice_date) - import_invoice_date = self._as_date(row.impo_invoice_date) - if export_invoice_date: - dates.add(export_invoice_date) - if req.operation_mode == 'importacion' and import_invoice_date: - dates.add(import_invoice_date) - continue - - target_date = row.impo_payment_date if req.operation_mode == 'importacion' else row.expo_payment_date - adjusted = self._adjust_payment_date(target_date, use_previous_payment_day) - if adjusted: - dates.add(adjusted) - - return dates - - def _load_exchange_rates( - self, - db: Session, - dates: set[date], - company_id: int, - tenant_id: int, - ) -> dict[date, Decimal]: - if not dates: - return {} - - rows = db.execute( - select(func.date(ExchangeRate.date), ExchangeRate.value).where( - ExchangeRate.tenant_id == tenant_id, - ExchangeRate.company_id == company_id, - func.date(ExchangeRate.date).in_(sorted(dates)), - ) - ).fetchall() - - return { - self._as_date(rate_date): self._to_decimal(rate_value) - for rate_date, rate_value in rows - if rate_date is not None - } - - def _resolve_selected_rate( - self, - row, - req: DownloadedPartsReportRequest, - rate_lookup: dict[date, Decimal], - use_previous_payment_day: bool, - ) -> Optional[Decimal]: - export_invoice_date = self._as_date(row.expo_invoice_date) - import_invoice_date = self._as_date(row.impo_invoice_date) - export_payment_date = self._adjust_payment_date(row.expo_payment_date, use_previous_payment_day) - import_payment_date = self._adjust_payment_date(row.impo_payment_date, use_previous_payment_day) - - if req.exchange_rate_mode == 'invoice': - if req.operation_mode == 'importacion': - return rate_lookup.get(import_invoice_date) or rate_lookup.get(export_invoice_date) - return rate_lookup.get(export_invoice_date) - - if req.operation_mode == 'importacion': - return rate_lookup.get(import_payment_date) - return rate_lookup.get(export_payment_date) - - def _find_missing_rate_dates( - self, - rows: list, - req: DownloadedPartsReportRequest, - rate_lookup: dict[date, Decimal], - use_previous_payment_day: bool, - ) -> list[str]: - missing: set[date] = set() - - for row in rows: - if req.exchange_rate_mode == 'invoice': - export_invoice_date = self._as_date(row.expo_invoice_date) - import_invoice_date = self._as_date(row.impo_invoice_date) - if export_invoice_date and export_invoice_date not in rate_lookup: - missing.add(export_invoice_date) - if req.operation_mode == 'importacion' and import_invoice_date and import_invoice_date not in rate_lookup: - missing.add(import_invoice_date) - continue - - target_date = row.impo_payment_date if req.operation_mode == 'importacion' else row.expo_payment_date - adjusted = self._adjust_payment_date(target_date, use_previous_payment_day) - if adjusted and adjusted not in rate_lookup: - missing.add(adjusted) - - return [d.strftime('%d/%m/%Y') for d in sorted(missing)] - - def _resolve_export_fraction(self, row, req: DownloadedPartsReportRequest) -> str: - if req.include_rule_octava and row.export_octave_fraction: - return row.export_octave_fraction - return row.export_fraction or '' - - def _resolve_class_fraction(self, row, req: DownloadedPartsReportRequest) -> str: - export_fraction = self._resolve_export_fraction(row, req) - import_fraction = row.import_fraction or '' - if req.include_rule_octava and row.import_octave_fraction: - import_fraction = row.import_octave_fraction - - if req.show_export_fraction: - return row.class_fraction or '' - - if req.print_class_mode == 'downloaded': - return import_fraction or row.class_fraction or '' - - return export_fraction or row.class_fraction or '' - - def _resolve_description(self, row, req: DownloadedPartsReportRequest) -> str: - line_description = row.export_line_description or row.export_line_part_description - class_description = row.export_line_class_description or row.class_description - part_description = row.part_description or row.export_line_part_description - - if req.show_item_description: - return self._clean_text(line_description or part_description or class_description) - - if part_description and class_description: - return self._clean_text(f"{part_description} / {class_description}") - - return self._clean_text(part_description or class_description or line_description) - - def _resolve_part_description(self, row) -> str: - return self._clean_text(row.export_line_part_description or row.part_description) - - def _resolve_base_values(self, row) -> tuple[Decimal, Decimal]: - qty = self._to_decimal(row.quantity) - detail_mn = self._to_decimal(row.value_mn) - detail_usd = self._to_decimal(row.value_me) - import_qty = self._to_decimal(row.import_quantity_total) - - if row.import_is_subitem: - return detail_mn, detail_usd - - if row.import_unit_cost_mxn is not None or row.import_unit_cost_usd is not None: - return ( - qty * self._to_decimal(row.import_unit_cost_mxn), - qty * self._to_decimal(row.import_unit_cost_usd), - ) - - if import_qty > 0: - ratio = qty / import_qty - customs_mxn = self._to_decimal(row.import_customs_value_mxn) - customs_usd = self._to_decimal(row.import_customs_value_usd) - if customs_mxn > 0 or customs_usd > 0: - return customs_mxn * ratio, customs_usd * ratio - - return detail_mn, detail_usd - - def _resolve_values( - self, - row, - req: DownloadedPartsReportRequest, - rate_lookup: dict[date, Decimal], - use_previous_payment_day: bool, - ) -> tuple[Decimal, Decimal, Optional[Decimal]]: - detail_mn = self._to_decimal(row.value_mn) - detail_usd = self._to_decimal(row.value_me) - base_mn, base_usd = self._resolve_base_values(row) - selected_rate = self._resolve_selected_rate(row, req, rate_lookup, use_previous_payment_day) - - if req.print_class_mode == 'downloaded': - source_mn = base_mn if base_mn > 0 else detail_mn - source_usd = base_usd if base_usd > 0 else detail_usd - else: - source_mn = detail_mn if detail_mn > 0 else base_mn - source_usd = detail_usd if detail_usd > 0 else base_usd - - if req.operation_mode == 'importacion' and req.respect_import_invoice_value_in_pesos: - value_mn = source_mn - value_usd = source_mn / selected_rate if selected_rate and source_mn > 0 else source_usd - return value_mn, value_usd, selected_rate - - if source_usd > 0 and selected_rate: - return source_usd * selected_rate, source_usd, selected_rate - - if source_mn > 0 and selected_rate: - return source_mn, source_mn / selected_rate, selected_rate - - return source_mn, source_usd, selected_rate - - def _build_series_map(self, db: Session, line_ids: list[int], tenant_id: int) -> dict[int, list[Serie]]: - if not line_ids: - return {} - - series_rows = ( - db.query(Serie) - .filter( - Serie.tenant_id == tenant_id, - Serie.line_item_id.in_(line_ids), - ) - .order_by(Serie.line_item_id, Serie.row, Serie.id) - .all() - ) - - series_map: dict[int, list[Serie]] = {} - for series in series_rows: - series_map.setdefault(series.line_item_id, []).append(series) - return series_map - - # ------------------------------------------------------------------ - # Bootstrap - # ------------------------------------------------------------------ - - def build_bootstrap(self, company_id: int, tenant_id: int) -> DownloadedPartsReportBootstrap: - return DownloadedPartsReportBootstrap( - description=( - "Base inicial para construir el reporte de partes descargadas desde exportacion. " - "Incluye metadatos, filtros sugeridos y bloques base para la vista." - ), - company_id=company_id, - tenant_id=tenant_id, - available_filters=[ - "fecha_inicio", - "fecha_fin", - "parte", - "pedimento", - "factura_exportacion", - "cliente", - ], - next_steps=[ - "Definir origen exacto de datos para descargas por parte.", - "Agregar filtros funcionales y tabla de resultados.", - "Conectar exportacion a Excel o CSV cuando el layout quede definido.", - ], - sections=[ - DownloadedPartsReportSection( - id="filters", - title="Filtros", - description="Contenedor para criterios de busqueda del reporte.", - ), - DownloadedPartsReportSection( - id="results", - title="Resultados", - description="Espacio reservado para tabla o listado de partes descargadas.", - ), - DownloadedPartsReportSection( - id="exports", - title="Exportacion", - description="Zona para acciones futuras de descarga y generacion de archivos.", - ), - ], - ) - - # ------------------------------------------------------------------ - # Exchange rate validation - # ------------------------------------------------------------------ - - def validate_exchange_rates( - self, - db: Session, - req: DownloadedPartsReportRequest, - company_id: int, - tenant_id: int, - ) -> list[str]: - settings = self._get_settings(db, tenant_id, company_id) - use_previous_payment_day = bool(settings.get('utilizarfechapagopeddeundiaanterior')) - rows = self.query_discharge_data(db, req, company_id, tenant_id) - rate_dates = self._collect_rate_dates(rows, req, use_previous_payment_day) - rate_lookup = self._load_exchange_rates(db, rate_dates, company_id, tenant_id) - return self._find_missing_rate_dates(rows, req, rate_lookup, use_previous_payment_day) - - # ------------------------------------------------------------------ - # Main data query - # ------------------------------------------------------------------ - - def query_discharge_data( - self, - db: Session, - req: DownloadedPartsReportRequest, - company_id: int, - tenant_id: int, - ) -> list: - ExportLine = aliased(LineItem, name='export_line') - ImportLine = aliased(LineItem, name='import_line') - ExportInvoice = aliased(InvoiceHeader, name='export_invoice') - ImportInvoice = aliased(InvoiceHeader, name='import_invoice') - ExportCompliance = aliased(InvoiceComplianceMx, name='export_compliance') - ImportCompliance = aliased(InvoiceComplianceMx, name='import_compliance') - ExportPedimento = aliased(Pedimentos, name='export_pedimento') - ImportPedimento = aliased(Pedimentos, name='import_pedimento') - ExportPedR1 = aliased(Pedimentos, name='export_ped_r1') - ImportPedR1 = aliased(Pedimentos, name='import_ped_r1') - ExportPedDates = aliased(PedimentoDates, name='export_ped_dates') - ImportPedDates = aliased(PedimentoDates, name='import_ped_dates') - ExportPart = aliased(Part, name='export_part') - ExportCustom = aliased(LineCustom, name='export_custom') - ImportCustom = aliased(LineCustom, name='import_custom') - ExportDescription = aliased(LineDescription, name='export_description') - ImportFinancial = aliased(LineFinancial, name='import_financial') - ImportQuantity = aliased(LineQuantity, name='import_quantity') - ImportFa = aliased(FaLineItem, name='import_fa') - ExportTariffFraction = aliased(TariffFraction, name='export_tariff_fraction') - - stmt = ( - select( - ExportLine.id.label('export_line_id'), - ExportPedimento.year.label('expo_ped_year'), - ExportPedimento.customs_office.label('expo_ped_customs'), - ExportPedimento.license.label('expo_ped_license'), - ExportPedimento.pedimento_number.label('expo_ped_number'), - ExportPedimento.pedimento_code.label('expo_ped_code'), - ExportPedR1.year.label('expo_r1_year'), - ExportPedR1.customs_office.label('expo_r1_customs'), - ExportPedR1.license.label('expo_r1_license'), - ExportPedR1.pedimento_number.label('expo_r1_number'), - ExportPedDates.payment_date.label('expo_payment_date'), - ExportPedimento.pedimento_code.label('expo_clave'), - DischargeHeader.discharge_date.label('discharge_date'), - ExportInvoice.invoice_number.label('expo_invoice_number'), - ExportInvoice.invoice_date.label('expo_invoice_date'), - Class.class_code.label('class_code'), - Class.description_es.label('class_description'), - Class.material_key.label('material_key'), - Class.fraction.label('class_fraction'), - ExportCustom.fraction.label('export_fraction'), - ExportCustom.american_fraction.label('american_fraction'), - ExportCustom.octave_fraction.label('export_octave_fraction'), - ImportCustom.fraction.label('import_fraction'), - ImportCustom.octave_fraction.label('import_octave_fraction'), - DischargeDetail.quantity_discharged.label('quantity'), - DischargeDetail.unit_of_measure.label('unit_of_measure'), - DischargeDetail.value_me.label('value_me'), - DischargeDetail.value_mn.label('value_mn'), - DischargeDetail.net_weight.label('net_weight'), - DischargeDetail.origin_import_invoice.label('import_invoice_str'), - DischargeDetail.part_number.label('part_number_str'), - DischargeDetail.country_of_origin.label('country_of_origin'), - ImportPedimento.year.label('impo_ped_year'), - ImportPedimento.customs_office.label('impo_ped_customs'), - ImportPedimento.license.label('impo_ped_license'), - ImportPedimento.pedimento_number.label('impo_ped_number'), - ImportPedimento.pedimento_code.label('impo_ped_code'), - ImportPedR1.year.label('impo_r1_year'), - ImportPedR1.customs_office.label('impo_r1_customs'), - ImportPedR1.license.label('impo_r1_license'), - ImportPedR1.pedimento_number.label('impo_r1_number'), - ImportPedDates.payment_date.label('impo_payment_date'), - ImportInvoice.invoice_number.label('import_invoice_number'), - ImportInvoice.invoice_date.label('impo_invoice_date'), - ImportInvoice.invoice_type.label('import_invoice_type'), - ExportPart.description_spanish.label('part_description'), - ExportDescription.description_spanish.label('export_line_description'), - ExportDescription.part_description.label('export_line_part_description'), - ExportDescription.class_description.label('export_line_class_description'), - ExportDescription.brand.label('export_brand'), - ExportDescription.model.label('export_model'), - ImportFinancial.unit_cost_mxn.label('import_unit_cost_mxn'), - ImportFinancial.unit_cost_usd.label('import_unit_cost_usd'), - ImportFinancial.customs_value_mxn.label('import_customs_value_mxn'), - ImportFinancial.customs_value_usd.label('import_customs_value_usd'), - ImportQuantity.quantity.label('import_quantity_total'), - ImportLine.payment_method.label('import_payment_method'), - ImportFa.is_subitem.label('import_is_subitem'), - ExportTariffFraction.umt.label('tariff_uom'), - ) - .select_from(DischargeDetail) - .join(DischargeHeader, DischargeDetail.discharge_header_id == DischargeHeader.id) - .join(ExportLine, DischargeDetail.export_item_line_id == ExportLine.id) - .join(ImportLine, DischargeDetail.import_item_line_id == ImportLine.id) - .join(ExportInvoice, ExportLine.invoice_id == ExportInvoice.id) - .join(ImportInvoice, ImportLine.invoice_id == ImportInvoice.id) - .outerjoin(ExportCompliance, ExportCompliance.invoice_id == ExportInvoice.id) - .outerjoin(ImportCompliance, ImportCompliance.invoice_id == ImportInvoice.id) - .outerjoin(ExportPedimento, ExportPedimento.id == ExportCompliance.pedimento_id) - .outerjoin(ImportPedimento, ImportPedimento.id == ImportCompliance.pedimento_id) - .outerjoin(ExportPedR1, ExportPedR1.id == ExportCompliance.pedimento_r1) - .outerjoin(ImportPedR1, ImportPedR1.id == ImportCompliance.pedimento_r1) - .outerjoin(ExportPedDates, ExportPedDates.pedimento_id == ExportPedimento.id) - .outerjoin(ImportPedDates, ImportPedDates.pedimento_id == ImportPedimento.id) - .outerjoin(Class, Class.id == ExportLine.class_id) - .outerjoin(ExportPart, ExportPart.id == ExportLine.part_number_id) - .outerjoin(ExportCustom, ExportCustom.item_line_id == ExportLine.id) - .outerjoin(ImportCustom, ImportCustom.item_line_id == ImportLine.id) - .outerjoin(ExportDescription, ExportDescription.item_line_id == ExportLine.id) - .outerjoin(ImportFinancial, ImportFinancial.item_line_id == ImportLine.id) - .outerjoin(ImportQuantity, ImportQuantity.item_line_id == ImportLine.id) - .outerjoin(ImportFa, ImportFa.id == ImportLine.id) - .outerjoin( - ExportTariffFraction, - ExportTariffFraction.code == func.substr( - func.replace(func.coalesce(ExportCustom.fraction, ''), '.', ''), - 1, - 8, - ), - ) - .where( - DischargeHeader.tenant_id == tenant_id, - DischargeHeader.company_id == company_id, - ExportInvoice.status == InvoiceStatus.PROCESSED, - ) - ) - - # Date filter - if req.print_class_mode == 'exported': - stmt = stmt.where( - ExportInvoice.invoice_date.between(req.date_from, req.date_to) - ) - else: - stmt = stmt.where( - ExportInvoice.invoice_type != 'NODES', - or_( - ExportPedDates.payment_date.between(req.date_from, req.date_to), - and_( - ExportPedDates.payment_date.is_(None), - ExportInvoice.invoice_date.between(req.date_from, req.date_to), - ), - ), - ) - - # Temporality - if req.temporality_mode == 'temporales': - stmt = stmt.where(DischargeHeader.discharge_type == DischargeType.TEMPORARY) - elif req.temporality_mode == 'definitivos': - stmt = stmt.where(DischargeHeader.discharge_type == DischargeType.DEFINITIVE) - - # Class range - if req.class_from: - stmt = stmt.where(Class.class_code >= req.class_from) - if req.class_to: - stmt = stmt.where(Class.class_code <= req.class_to) - - if req.material_type: - stmt = stmt.where(Class.material_key == req.material_type) - if req.invoice_type: - stmt = stmt.where(ExportInvoice.invoice_type == req.invoice_type) - if req.parts: - stmt = stmt.where(DischargeDetail.part_number.in_(req.parts)) - if req.pedimento_key: - stmt = stmt.where(ExportPedimento.pedimento_code == req.pedimento_key) - - if req.provider_id: - stmt = stmt.where(ExportCompliance.provider_id == req.provider_id) - - if req.sold_to_id: - stmt = stmt.where(ExportCompliance.sold_to_id == req.sold_to_id) - - if getattr(req, 'shipped_to_id', None): - stmt = stmt.where(ExportCompliance.shipped_to_id == req.shipped_to_id) - - if req.destination_customs: - stmt = stmt.where(ExportCompliance.aduana == req.destination_customs) - - if not req.include_exempt_fraction: - stmt = stmt.where( - or_( - Class.iva_exempt_fraction.is_(None), - Class.iva_exempt_fraction != 'Si', - ) - ) - - if req.print_class_mode == 'downloaded' and not req.show_all_temporary_balances: - stmt = stmt.where( - or_( - ImportLine.payment_method.is_(None), - ImportLine.payment_method != '2', - ) - ) - - stmt = stmt.order_by( - Class.class_code.nullslast(), - ExportInvoice.invoice_date, - DischargeDetail.id, - ) - - return db.execute(stmt).fetchall() - - # ------------------------------------------------------------------ - # CSV generation - # ------------------------------------------------------------------ - - def generate_csv( - self, - db: Session, - req: DownloadedPartsReportRequest, - company_id: int, - tenant_id: int, - ) -> StreamingResponse: - settings = self._get_settings(db, tenant_id, company_id) - use_previous_payment_day = bool(settings.get('utilizarfechapagopeddeundiaanterior')) - company = ( - db.query(Company) - .options(selectinload(Company.addresses)) - .filter(Company.id == company_id, Company.tenant_id == tenant_id) - .first() - ) - - rows = self.query_discharge_data(db, req, company_id, tenant_id) - rate_dates = self._collect_rate_dates(rows, req, use_previous_payment_day) - rate_lookup = self._load_exchange_rates(db, rate_dates, company_id, tenant_id) - missing = self._find_missing_rate_dates(rows, req, rate_lookup, use_previous_payment_day) - if missing: - raise HTTPException(status_code=422, detail={'missing_dates': missing}) - - headers = self._build_headers(req, company) - output = io.StringIO() - writer = csv.writer(output) - - self._write_company_header(writer, company, settings) - writer.writerow(headers) - - current_class: Optional[str] = None - class_qty = Decimal('0') - class_weight_kgs = Decimal('0') - class_weight_lbs = Decimal('0') - class_mn = Decimal('0') - class_me = Decimal('0') - fraction_totals: dict[str, dict[str, Decimal]] = {} - series_map = self._build_series_map( - db, - [row.export_line_id for row in rows if row.export_line_id is not None], - tenant_id, - ) - - def flush_class_total() -> None: - if req.print_class_total and current_class is not None: - writer.writerow(['TOTAL DE LA CLASE']) - writer.writerow([ - self._decimal_str(class_qty, 4), - self._decimal_str(class_weight_kgs, 4), - self._decimal_str(class_weight_lbs, 4), - self._decimal_str(class_mn), - self._decimal_str(class_me), - ]) - - for row in rows: - class_code = row.class_code or '' - if req.print_class_total and class_code != current_class: - flush_class_total() - current_class = class_code - class_qty = Decimal('0') - class_weight_kgs = Decimal('0') - class_weight_lbs = Decimal('0') - class_mn = Decimal('0') - class_me = Decimal('0') - - export_fraction = self._resolve_export_fraction(row, req) - class_fraction = self._resolve_class_fraction(row, req) - description_value = self._resolve_description(row, req) - part_description = self._resolve_part_description(row) - value_mn, value_me, selected_rate = self._resolve_values( - row, - req, - rate_lookup, - use_previous_payment_day, - ) - - quantity = self._to_decimal(row.quantity) - weight_kgs = self._to_decimal(row.net_weight) - weight_lbs = weight_kgs * LBS_PER_KG - - if req.print_class_total: - class_qty += quantity - class_weight_kgs += weight_kgs - class_weight_lbs += weight_lbs - class_mn += value_mn - class_me += value_me - - if req.include_totals_by_fraction: - fraction_key = export_fraction or '' - if fraction_key not in fraction_totals: - fraction_totals[fraction_key] = {'mn': Decimal('0'), 'me': Decimal('0')} - fraction_totals[fraction_key]['mn'] += value_mn - fraction_totals[fraction_key]['me'] += value_me - - export_ped = self._build_pedimento_str( - row.expo_ped_year, - row.expo_ped_customs, - row.expo_ped_license, - row.expo_ped_number, - ) - export_r1 = self._build_pedimento_str( - row.expo_r1_year, - row.expo_r1_customs, - row.expo_r1_license, - row.expo_r1_number, - ) - import_ped = self._build_pedimento_str( - row.impo_ped_year, - row.impo_ped_customs, - row.impo_ped_license, - row.impo_ped_number, - ) - import_r1 = self._build_pedimento_str( - row.impo_r1_year, - row.impo_r1_customs, - row.impo_r1_license, - row.impo_r1_number, - ) - export_ped_18 = self._build_pedimento_18( - row.expo_ped_year, - row.expo_ped_customs, - row.expo_ped_license, - row.expo_ped_number, - row.expo_ped_code, - ) - import_ped_18 = self._build_pedimento_18( - row.impo_ped_year, - row.impo_ped_customs, - row.impo_ped_license, - row.impo_ped_number, - row.impo_ped_code, - ) - - export_ped_values = self._pedimento_values(company, export_ped, export_r1) - import_ped_values = self._pedimento_values(company, import_ped, import_r1) - - csv_row = [ - self._excel_text(export_ped_values[0]), - self._excel_text(export_ped_values[1]), - self._excel_text(self._format_date(row.expo_payment_date, req.julian_date)), - self._excel_text(row.expo_clave or ''), - self._excel_text(self._format_date(row.discharge_date, req.julian_date)), - self._excel_text(row.expo_invoice_number or ''), - self._excel_text(self._format_date(row.expo_invoice_date, req.julian_date)), - self._excel_text(class_code), - self._excel_text(description_value), - self._excel_text(export_fraction), - self._decimal_str(quantity, 4), - self._excel_text(row.unit_of_measure or ''), - self._excel_text(import_ped_values[0]), - self._excel_text(import_ped_values[1]), - self._excel_text(self._format_date(row.impo_payment_date, req.julian_date)), - self._excel_text(row.import_invoice_number or row.import_invoice_str or ''), - self._excel_text(self._format_date(row.impo_invoice_date, req.julian_date)), - self._decimal_str(weight_kgs, 4), - self._decimal_str(value_me), - self._decimal_str(value_mn), - self._decimal_str(selected_rate, 6) if selected_rate else '', - self._excel_text(row.part_number_str or ''), - self._excel_text(part_description), - self._excel_text(row.material_key or ''), - self._excel_text(class_fraction), - self._excel_text(import_ped_18), - self._excel_text(export_ped_18), - self._excel_text(row.tariff_uom or ''), - ] - - if req.include_american_fraction_and_country: - csv_row.extend([ - self._excel_text(row.american_fraction or ''), - self._excel_text(row.country_of_origin or ''), - ]) - - writer.writerow(csv_row) - - if req.include_series and row.export_line_id in series_map: - writer.writerow([ - 'RENGLON', - 'SERIE', - 'MODELO', - 'SUBMODELO', - 'PARTE', - 'NUM ID EXPO', - 'MARCA PARTIDA', - 'MODELO PARTIDA', - ]) - for series in series_map[row.export_line_id]: - writer.writerow([ - self._excel_text(series.row or ''), - self._excel_text(series.serial_numbers or ''), - self._excel_text(series.model or ''), - self._excel_text(series.sub_model or ''), - self._excel_text(row.part_number_str or ''), - self._excel_text(series.number_id or ''), - self._excel_text(row.export_brand or ''), - self._excel_text(row.export_model or ''), - ]) - - flush_class_total() - - if req.include_totals_by_fraction and fraction_totals: - writer.writerow([]) - writer.writerow(['TOTALES POR FRACCION']) - writer.writerow(['FRACCION', 'VALOR MN', 'VALOR ME']) - total_mn = Decimal('0') - total_me = Decimal('0') - for fraction, totals in sorted(fraction_totals.items()): - writer.writerow([ - fraction, - self._decimal_str(totals['mn']), - self._decimal_str(totals['me']), - ]) - total_mn += totals['mn'] - total_me += totals['me'] - - writer.writerow(['TOTAL']) - writer.writerow(['', self._decimal_str(total_mn), self._decimal_str(total_me)]) - - csv_content = output.getvalue() - filename = f"partes_descargadas_{req.date_from}_{req.date_to}.csv" - return StreamingResponse( - iter([csv_content.encode('utf-8-sig')]), - media_type='text/csv', - headers={'Content-Disposition': f'attachment; filename="{filename}"'}, - ) diff --git a/backend/api/v1/modules/a76/reports/exportacion/transmission/MAINX30/__init__.py b/backend/api/v1/modules/a76/reports/exportacion/transmission/MAINX30/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/backend/api/v1/modules/a76/reports/exportacion/transmission/MAINX30/processors.py b/backend/api/v1/modules/a76/reports/exportacion/transmission/MAINX30/processors.py deleted file mode 100644 index b0282e5d..00000000 --- a/backend/api/v1/modules/a76/reports/exportacion/transmission/MAINX30/processors.py +++ /dev/null @@ -1,347 +0,0 @@ -from typing import List, Dict, Any, Tuple -from sqlalchemy.orm import Session, joinedload - -from .schemas import Mainx30GenerationRequest, ErrorValidacion - -# --- MODELOS A76 --- -from api.v1.modules.a76.invoices.models import InvoiceHeader, InvoiceComplianceMx -from api.v1.modules.a76.items.models import LineItem -from api.v1.modules.a76.clients_and_providers.models import ClientProvider - -class ScaiiProcessor: - def __init__(self): - self.cuenta_partidas = 0 - self.cuenta_facturas = 0 - self.valor_total_factura = 0.0 - self.peso_bruto_factura = 0.0 - self.peso_neto_factura = 0.0 - self.errores: List[ErrorValidacion] = [] - - def _obtener_datos_cliente(self, cliente: ClientProvider) -> dict: - """Extrae de manera segura los datos del cliente/dirección""" - # Determine Tax ID: RFC for MX, Tax ID for others - address = cliente.address - pais_raw = (address.country or "MX").upper() - - pais = "MX" - if pais_raw in ["MEXICO", "MEX", "MX"]: - pais = "MX" - elif pais_raw in ["USA", "US", "UNITED STATES"]: - pais = "US" - else: - pais = pais_raw[:2] - - # Identificador fiscal del cliente: RFC (nacional) o TAX-ID (extranjero) - tax_id = cliente.fiscal_id or "" - - data = { - "nombre": (cliente.name or "")[:39], - "tax_id": tax_id[:15], - "broker": "", "calle": "", "cp": "", "ciudad": "", "estado": "", "pais": pais, "tel": "" - } - - if cliente.programs: - data["broker"] = (cliente.programs.broker or "")[:6] - - if address: - calle_comp = f"{address.streets or ''} {address.exterior_number or ''}".strip() - data["calle"] = calle_comp[:35] - data["cp"] = (address.postal_code or "")[:9] - data["ciudad"] = (address.city or "")[:20] - data["estado"] = (address.state or "")[:2].upper() - data["tel"] = (address.phone or "")[:15] # Remove default "000000" - - return data - - def procesar_facturas( - self, db: Session, manifiesto: str, empresa_dict: Dict[str, Any], request: Mainx30GenerationRequest - ) -> Tuple[List[str], List[ErrorValidacion]]: - lineas = [] - self.errores = [] - - # 1. Traer Facturas del Manifiesto - facturas = db.query(InvoiceHeader).join( - InvoiceComplianceMx, InvoiceHeader.id == InvoiceComplianceMx.invoice_id - ).options( - joinedload(InvoiceHeader.financials), - joinedload(InvoiceHeader.compliance_mx) - ).filter( - InvoiceComplianceMx.manifest_number == manifiesto - ).all() - - for factura in facturas: - self.cuenta_facturas += 1 - f_val_total = 0.0 - f_pb = 0.0 - f_pn = 0.0 - f_consec_partidas = 0 - - # --- MF20 / MF22: Per-Invoice Header at Manifest Level --- - # Sample: MF20AAK22-001 I10900 1234 1234 - # Invoice(15) + Type(1?) + Port(5?) + ... - entry_port = manifiesto.replace("-", "")[:4] # or from manifest object if available here? - # Manifiesto passed to this method is just a string 'manifest_number'. - # We need to query manifest or pass it. - # Actually, `manifiesto` arg is just the number string. - # But we can pass the entry_port from service.py in empresa_dict or request? - # Let's check service.py. - - # Assuming it is in empresa_dict for now (I will add it next step) - # --- MF20 / MF22: Per-Invoice Header at Manifest Level --- - # Sample: MF20AAK22-001 I10900 1234 1234 - - port_code = empresa_dict.get('entry_port', '')[:4] - manufacturer_id = empresa_dict.get('manufacturer_id', '')[:10] - - # Constructing line to match sample length/spacing - lineas.append( - f"MF20" - f"{factura.invoice_number[:15]:<15}" - f"I{manufacturer_id:<15}" - f"{port_code:<20}" - f"{port_code:<4}" - ) - lineas.append(f"MF22") - self.cuenta_partidas += 2 - - # --- IV01: Header de Factura --- - flete = float(factura.financials.freight) if factura.financials and factura.financials.freight else 0.0 - fecha_str = factura.invoice_date.strftime("%y%m%d") if factura.invoice_date else "000000" - - s_rfc = "" - c_rfc = "" - - if factura.compliance_mx: - if factura.compliance_mx.provider_id: - s_obj = db.query(ClientProvider).filter(ClientProvider.id == factura.compliance_mx.provider_id).first() - if s_obj: s_rfc = s_obj.fiscal_id or "" - if factura.compliance_mx.sold_to_id: - c_obj = db.query(ClientProvider).filter(ClientProvider.id == factura.compliance_mx.sold_to_id).first() - if c_obj: c_rfc = c_obj.fiscal_id or "" - - lineas.append( - f"IV01{factura.invoice_number[:15]:<15}" - f"{fecha_str}01 " # 6 + 3 = 9 - f"{port_code:<11}" # Port (Use same as MF20) - f"{empresa_dict.get('broker', '')[:6]:<15}" # Broker - f"{s_rfc[:12]:<12}{c_rfc[:12]:<12}" - ) - self.cuenta_partidas += 1 - - # --- IV02: Company Name --- - nombre_empresa = empresa_dict.get('nombre_empresa', '')[:40] - lineas.append(f"IV02 {nombre_empresa:<40}") - self.cuenta_partidas += 1 - - # --- IV10: Goods Description & Contact --- - # Dynamic Description from Invoice (observation_en or observation_es) - desc_global = (factura.observation_en or factura.observation_es or "")[:30] - - contacto = empresa_dict.get('responsable', '')[:30] - lineas.append(f"IV10 {desc_global:<30}{contacto:<30}") - self.cuenta_partidas += 1 - - # --- IV11: Headers --- - lineas.append(f"IV11H") - lineas.append(f"IV11F") - self.cuenta_partidas += 2 - - # --- DATOS DE DIRECCIONES (S, C, T, I) --- - # Shipper (S) -> Proveedor de la factura - if factura.compliance_mx and factura.compliance_mx.provider_id: - s_cliente = db.query(ClientProvider).filter(ClientProvider.id == factura.compliance_mx.provider_id).first() - if s_cliente: - s_data = self._obtener_datos_cliente(s_cliente) - calle_cp = f"{s_data['calle']} {s_data['cp']}".strip() - lineas.append(f"IV12S {s_data['nombre'][:39]:<39}") - lineas.append(f"IV13S {calle_cp[:35]:<35}") - lineas.append(f"IV14S{s_data['ciudad'][:20]:<20}{s_data['estado'][:2]}{s_data['pais'][:2]}{s_data['tel'][:15]:<15}{s_data['tax_id']:<15}00000") - self.cuenta_partidas += 3 - - # Consignee / Vendido A (C) - c_data = None - if factura.compliance_mx and factura.compliance_mx.sold_to_id: - c_cliente = db.query(ClientProvider).filter(ClientProvider.id == factura.compliance_mx.sold_to_id).first() - if c_cliente: - c_data = self._obtener_datos_cliente(c_cliente) - calle_cp = f"{c_data['calle']} {c_data['cp']}".strip() - lineas.append(f"IV12C {c_data['nombre'][:39]:<39}") - lineas.append(f"IV13C {calle_cp[:35]:<35}") - lineas.append(f"IV14C{c_data['ciudad'][:20]:<20}{c_data['estado'][:2]}{c_data['pais'][:2]}{c_data['tel'][:15]:<15}{c_data['tax_id']:<15}00000") - self.cuenta_partidas += 3 - - # Ship To / Enviado A (T) - if factura.compliance_mx and factura.compliance_mx.shipped_to_id: - t_cliente = db.query(ClientProvider).filter(ClientProvider.id == factura.compliance_mx.shipped_to_id).first() - if t_cliente: - t_data = self._obtener_datos_cliente(t_cliente) - calle_cp = f"{t_data['calle']} {t_data['cp']}".strip() - lineas.append(f"IV12T {t_data['nombre'][:39]:<39}") - lineas.append(f"IV13T {calle_cp[:35]:<35}") - lineas.append(f"IV14T{t_data['ciudad'][:20]:<20}{t_data['estado'][:2]}{t_data['pais'][:2]}{t_data['tel'][:15]:<15}{t_data['tax_id']:<15}00000") - self.cuenta_partidas += 3 - - # Importer (I) - Sample shows it same as Consignee or Importer - if c_data: - # Reuse c_data calculation or re-fetch if needed. Reusing c_data structure. - calle_cp = f"{c_data['calle']} {c_data['cp']}".strip() - lineas.append(f"IV12I {c_data['nombre'][:39]:<39}") - lineas.append(f"IV13I {calle_cp[:35]:<35}") - lineas.append(f"IV14I{c_data['ciudad'][:20]:<20}{c_data['estado'][:2]}{c_data['pais'][:2]}{c_data['tel'][:15]:<15}{c_data['tax_id']:<15}00000") - self.cuenta_partidas += 3 - - # --- PARTIDAS (DETALLE IV20-IV27) --- - items_query = db.query(LineItem).filter( - LineItem.invoice_id == factura.id - ).options( - joinedload(LineItem.part_info), - joinedload(LineItem.description), - joinedload(LineItem.financial), - joinedload(LineItem.quantity), # Added quantity relation - joinedload(LineItem.customs), - joinedload(LineItem.unit_of_measure_info) - ).all() - - for line in items_query: - f_consec_partidas += 1 - - part_num = line.part_info.part_number if line.part_info else "S/N" - po_num = factura.purchase_order or "" - - desc = "" - if line.description: - desc = line.description.description_english or line.description.description_spanish or "" - - # --- OBTENCIÓN DE DATOS DE LINEFINANCIAL / LINEQUANTITY --- - qty = 0.0; pb = 0.0; pn = 0.0; val_usd = 0.0 - val_no_duty = 0.0; val_packing = 0.0 - - if line.quantity: - qty = float(line.quantity.quantity or 0.0) - pb = float(line.quantity.gross_weight or 0.0) - pn = float(line.quantity.net_weight or 0.0) - - if pb == 0 and pn > 0: pb = pn - - if line.financial: - val_usd = float(line.financial.value_usd or 0.0) - val_no_duty = float(line.financial.exempt_amount_usd or 0.0) # IV24 - val_packing = float(line.financial.value_us_packing_usd or 0.0) # IV26 - - f_pb += pb - f_pn += pn - f_val_total += val_usd # Assuming Total Invoice Value is sum of line.value_usd - - # Aduanas - hts_ame = "" - pais_orig = "MX" - if line.customs: - raw_hts = line.customs.american_fraction or line.customs.fraction or "" - hts_ame = raw_hts.replace(".", "").strip() - pais_orig = (line.customs.origin_country or "MX")[:2] - - # UM - um_ame = "PC" - if line.unit_of_measure_info: - um_ame = line.unit_of_measure_info.american_code or "PC" - - # Escritura (Igual que el Clarion) - lineas.append(f"IV20{f_consec_partidas:03d} {part_num[:25]:<25}A {po_num[:20]:<20}") - lineas.append(f"IV21{' ':21}{desc[:50]:<50}") - - # IV22: Fix alignment based on sample - # Sample: N0000002235PCS000050000CN0000010000000000000000 000000549000000408 - # HTS(10?) + Val(10) + UM(3) + Cant(9) + Pais(2) + ... - - v_int = int(round(val_usd * 100)) - q_int = int(round(qty * 1000)) # Sample 000050000 for 50? 50 * 1000 = 50000. - pb_int = int(round(pb * 100)) - pn_int = int(round(pn * 100)) - - lineas.append( - f"IV22 N" # 10 spaces + N - f"{v_int:010d}" # Value (integer 10) - f"{um_ame[:3]:<3}" # UM (3) - f"{q_int:09d}" # Qty (integer 9) - f"{pais_orig[:2]:<2}" # Pais (2) - f"0000010000000000000000 " # Fixed (23 with space) - f"{pb_int:010d}" # Peso Bruto (10 chars) - f"{pn_int:010d}" # Peso Neto (10 chars) - ) - - # IV24 (No Duty / Exempt) - # Dynamic Logic: Use exempt_amount_usd if > 0 - v_nd_int = int(round(val_no_duty * 100)) - # IV24 uses same UM and Qty layout as IV22 but for NoDuty portion? - # Sample shows just value and then mostly zeros? - # Sample: IV24 0000000000 000000000 0000000000000000000000 - # We will use v_nd_int. If 0, it renders as 0000000000. - if v_nd_int > 0: - # If there IS a No Duty value, we should probably output it. - # Format seems to start at same pos as IV22 Value? - # IV22 starts value at col 20 (approx). - # IV24 starts value at col 20 (approx). - # IV24 {Val} {Qty?} ... - # Given sample: `IV24 0000000000 000000000 ...` - # It looks like: Prefix(15) + Val(10) + Space(3) + Qty??(9) + ... - # Let's mimic structure - lineas.append(f"IV24 {v_nd_int:010d} {0:09d} 0000000000000000000000") - else: - lineas.append(f"IV24 {0:010d} {0:09d} 0000000000000000000000") - - # IV26 (Packing) - # Dynamic Logic: Use value_us_packing_usd - v_p_int = int(round(val_packing * 100)) - if v_p_int > 0: - lineas.append(f"IV26 {v_p_int:010d} {0:09d} 0000000000000000000000") - else: - lineas.append(f"IV26 {0:010d} {0:09d} 0000000000000000000000") - - # IV27 (Unit Costs) - # Sample: IV27 000000000000000000000000000000000000000000000000000000000000000000 - # If we have distinct values, maybe we should calculate unit costs? - # But legacy sample shows all zeros. - # Calculating separate unit costs for Duty/NoDuty/Packing: - c_u_d = val_usd / qty if qty > 0 else 0 - c_u_nd = val_no_duty / qty if qty > 0 else 0 - c_u_p = val_packing / qty if qty > 0 else 0 - - # If user wants NO HARDCODING, maybe we should populate this? - # But sample had 0s. Let's populate specific costs if values exist, else 0. - # Format: IV27 + 10 spaces + CostDuty(11) + CostNoDuty(11) + CostPacking(11) + ... - # Based on legacy Clarion: `FORMAT(Left(Loc:CostoUDuty),@n011v5)` - - cud_int = int(round(c_u_d * 100000)) - cund_int = int(round(c_u_nd * 100000)) - cup_int = int(round(c_u_p * 100000)) - - lineas.append(f"IV27 {cud_int:011d}{cund_int:011d}{cup_int:011d}000000000000000000000000000000000") - - self.cuenta_partidas += 6 - - # --- TOTALES FACTURA --- - # Sample: IV900000700000000000000000063320000005348 - # IV90 + CantPartidas(5) + ValTotal(12) + PesoBruto(10) + PesoNeto(10) - f_val_int = int(round(f_val_total * 100)) - f_pb_int = int(round(f_pb * 100)) - f_pn_int = int(round(f_pn * 100)) - lineas.append(f"IV90{f_consec_partidas:05d}{f_val_int:012d}{f_pb_int:010d}{f_pn_int:010d}") - self.cuenta_partidas += 1 - - self.valor_total_factura += f_val_total - self.peso_bruto_factura += f_pb - self.peso_neto_factura += f_pn - - return lineas, self.errores - - def _agregar_error(self, partida, id_err, desc, sol, tipo): - self.errores.append(ErrorValidacion(partida=partida, linea=0, descripcion=f"[ {id_err} ] {desc}", soluciones=sol, identificador=tipo)) - -# (Dummy Processors para que no truene el Service) -class ScafDefProcessor: - def __init__(self): self.cuenta_partidas=0; self.cuenta_facturas=0; self.valor_total_factura=0; self.peso_bruto_factura=0; self.peso_neto_factura=0 - def procesar_facturas(self, db, manifiesto, empresa_dict, request): return [], [] - -class ScafTempProcessor: - def __init__(self): self.cuenta_partidas=0; self.cuenta_facturas=0; self.valor_total_factura=0; self.peso_bruto_factura=0; self.peso_neto_factura=0 - def procesar_facturas(self, db, manifiesto, empresa_dict, request): return [], [] \ No newline at end of file diff --git a/backend/api/v1/modules/a76/reports/exportacion/transmission/MAINX30/routes.py b/backend/api/v1/modules/a76/reports/exportacion/transmission/MAINX30/routes.py deleted file mode 100644 index e53fd95f..00000000 --- a/backend/api/v1/modules/a76/reports/exportacion/transmission/MAINX30/routes.py +++ /dev/null @@ -1,56 +0,0 @@ -from typing import Dict, Any -from fastapi import APIRouter, Depends, Body -from sqlalchemy.orm import Session -from celery.result import AsyncResult -from core.celery_app import celery_app -from core.database import get_core_db -from core.security import get_current_user, get_tenant_from_token -from api.v1.modules.core.tasks_tracking import track_and_dispatch -from .task import generar_transmission_file_async -from .schemas import Mainx30GenerationRequest - -router = APIRouter() - -@router.get("/tasks/{task_id}") -async def get_task_status( - task_id: str, - current_user: Dict[str, Any] = Depends(get_current_user) -): - task_result = AsyncResult(task_id, app=celery_app) - - response = { - "task_id": task_id, - "state": task_result.state, - "result": None, - "info": None - } - - if task_result.state == 'FAILURE': - response["result"] = str(task_result.result) - elif task_result.state == 'SUCCESS': - response["result"] = task_result.result - elif task_result.state == 'PROCESSING': - # Ensure info is serializable - response["info"] = task_result.info - - return response - -@router.post("/generate") -async def trigger_generation( - request: Mainx30GenerationRequest, - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user) -): - tenant_id = get_tenant_from_token(current_user) or current_user.get("tenant_id") - task = track_and_dispatch( - db=db, - task=generar_transmission_file_async, - tenant_id=int(tenant_id), - company_id=getattr(request, "company_id", None), - requested_by_user=current_user.get("preferred_username") or current_user.get("email") or current_user.get("sub"), - task_name="generar_transmission_file_async", - task_group="reports", - task_origin="a76/reports/exportacion/transmission/MAINX30", - args=[request.model_dump(), tenant_id], - ) - return {"task_id": task.id, "message": "Generación iniciada"} diff --git a/backend/api/v1/modules/a76/reports/exportacion/transmission/MAINX30/schemas.py b/backend/api/v1/modules/a76/reports/exportacion/transmission/MAINX30/schemas.py deleted file mode 100644 index 6146dae4..00000000 --- a/backend/api/v1/modules/a76/reports/exportacion/transmission/MAINX30/schemas.py +++ /dev/null @@ -1,71 +0,0 @@ -from typing import List, Optional, Any -from pydantic import BaseModel, Field - -class Mainx30GenerationRequest(BaseModel): - """ - Schema for the Mainx30 file generation request - """ - manifiestos: List[str] = Field(..., description="Lista de números de manifiesto a procesar") - nomenclatura_factura: bool = Field(False, description="Usar nomenclatura basada en factura") - consolidar_rbs: bool = Field(False, description="Consolidar por fracción RB System") - emanifest_fast_blanco: bool = Field(False, description="E-Manifest y FAST en blanco") - no_enviar_emanifest: bool = Field(False, description="No enviar E-Manifest") - consolidar_partidas: bool = Field(False, description="Consolidar partidas (XML OPTIMA Y RBS2)") - main_x40_emanifest: bool = Field(False, description="Main X40 E-Manifest") - main_x30_fedex: bool = Field(False, description="Main X30 (FEDEX)") - iv11: bool = Field(False, description="IV11") - iv42: bool = Field(False, description="IV42") - -class ErrorValidacion(BaseModel): - """ - Schema for validation errors during file generation - """ - partida: int - linea: int - descripcion: str - soluciones: str - identificador: str - campos: str = "" - campos2: str = "" - -class Mainx30Response(BaseModel): - """ - Schema for the generation response - """ - success: bool - message: str - task_id: Optional[str] = None - archivo_generado: Optional[str] = None - ruta_archivo: Optional[str] = None - content: Optional[str] = None - - # Statistics - cuenta_partidas: int = 0 - valor_total: float = 0.0 - flete_total: float = 0.0 - peso_bruto_total: float = 0.0 - peso_neto_total: float = 0.0 - cuenta_facturas: int = 0 - - # Validation - errores: List[ErrorValidacion] = [] - tiene_inconsistencias: bool = False - -class BrokerValidationResult(BaseModel): - es_valido: bool - mensaje_error: Optional[str] = None - broker_cliente: Optional[str] = None - -class EmpresaDatos(BaseModel): - broker: str - responsable: str - rfc: str - tiene_linea_express: str - nombre_empresa: str = "AAKRON RULE CORPORATION" - manufacturer_id: str = "I10900" - ftp_key: str = "00SCSI" - -class ConfiguracionSistema(BaseModel): - path_arch_transmision: str - utilizar_nombre_generico_mainx30: bool - utilizar_codigo_broker_cliente: bool diff --git a/backend/api/v1/modules/a76/reports/exportacion/transmission/MAINX30/service.py b/backend/api/v1/modules/a76/reports/exportacion/transmission/MAINX30/service.py deleted file mode 100644 index e48363fe..00000000 --- a/backend/api/v1/modules/a76/reports/exportacion/transmission/MAINX30/service.py +++ /dev/null @@ -1,300 +0,0 @@ -import os -import tempfile -from datetime import date, datetime -from typing import List, Tuple, Optional -from pathlib import Path - -from sqlalchemy.orm import Session -from fastapi import HTTPException - -from .schemas import ( - Mainx30GenerationRequest, Mainx30Response, ErrorValidacion, - EmpresaDatos, ConfiguracionSistema -) - -# --- HELPERS --- -def fecha_clarion_a_iso(clarion_date): - """Convierte fecha Clarion (días desde 1800-12-28) a ISO YYYY-MM-DD""" - if not clarion_date: return "1900-01-01" - try: - from datetime import date, timedelta - base_date = date(1800, 12, 28) - delta = timedelta(days=int(clarion_date)) - return (base_date + delta).isoformat() - except: - return "1900-01-01" - -# --- MODELOS A76 --- -from api.v1.modules.a76.manifests.manifest.models import Manifest -from api.v1.modules.a76.general_catalogs.company.models import Company as GEmpresa - -# --- PROCESADORES --- -from .processors import ScaiiProcessor, ScafDefProcessor, ScafTempProcessor - -class Mainx30Service: - def __init__(self): - self.errores_validacion: List[ErrorValidacion] = [] - self.cuenta_partidas = 0 - self.cuenta_facturas = 0 - self.valor_total = 0.0 - self.flete_total = 0.0 - self.peso_bruto_total = 0.0 - self.peso_neto_total = 0.0 - - def generar_mainx30_expo( - self, - db: Session, - request: Mainx30GenerationRequest, - task_instance=None - ) -> Mainx30Response: - try: - self._inicializar_variables() - fecha_transmision = date.today().strftime("%y%m%d") - - config_sistema = self._obtener_configuracion_sistema(db) - datos_empresa = self._obtener_datos_empresa(db) - self._validar_datos_empresa(datos_empresa) - - if not request.manifiestos: - raise HTTPException(status_code=400, detail="No se seleccionaron manifiestos") - - nombre_archivo = self._generar_nombre_archivo(config_sistema, request, request.manifiestos[0]) - lineas_archivo = [] - - # Línea A - lineas_archivo.append(self._generar_linea_a(fecha_transmision, datos_empresa)) - - for manifiesto_num in request.manifiestos: - if task_instance: - task_instance.update_state(state='PROCESSING', meta={'status': f'Procesando {manifiesto_num}'}) - - lineas_manifiesto = self._procesar_manifiesto( - db, manifiesto_num, datos_empresa, fecha_transmision, request - ) - lineas_archivo.extend(lineas_manifiesto) - - # Línea Z - lineas_archivo.append(f"Z {self.cuenta_partidas:05d}") - - ruta_completa = os.path.join("api/v1/modules/reports/generated", nombre_archivo) - self._escribir_archivo(ruta_completa, lineas_archivo) - - return Mainx30Response( - success=len(self.errores_validacion) == 0, - message=self._generar_mensaje_resultado(ruta_completa), - archivo_generado=nombre_archivo, - ruta_archivo=ruta_completa, - cuenta_partidas=self.cuenta_partidas, - valor_total=self.valor_total, - flete_total=self.flete_total, - peso_bruto_total=self.peso_bruto_total, - peso_neto_total=self.peso_neto_total, - cuenta_facturas=self.cuenta_facturas, - errores=self.errores_validacion, - tiene_inconsistencias=len(self.errores_validacion) > 0, - content="\r\n".join(lineas_archivo) - ) - - except Exception as e: - import traceback - traceback.print_exc() - raise HTTPException(status_code=500, detail=f"Error generando Mainx30: {str(e)}") - - def _inicializar_variables(self): - self.errores_validacion = [] - self.cuenta_partidas = 1 # Empieza en 1 por la línea A - self.valor_total = 0.0 - self.flete_total = 0.0 - self.peso_bruto_total = 0.0 - self.peso_neto_total = 0.0 - self.cuenta_facturas = 0 - - def _procesar_manifiesto( - self, - db: Session, - manifiesto_num: str, - datos_empresa: EmpresaDatos, - fecha_transmision: str, - request: Mainx30GenerationRequest - ) -> List[str]: - lineas = [] - - # --- TABLA A76: MANIFEST --- - manifiesto = db.query(Manifest).filter( - Manifest.manifest_number == manifiesto_num - ).first() - - if not manifiesto: - self._agregar_error_validacion(0, "MF", f"Manifiesto {manifiesto_num} no encontrado.", "Verificar BD", "MANIFIESTO") - return lineas - - persona_cargo = manifiesto.person_in_charge or "" - if not persona_cargo: - self._agregar_error_validacion(0, "MF03", "Falta Persona a Cargo", "Capturar en Manifiesto", "MANIFIESTO") - - num_manifiesto_clean = manifiesto_num.replace("-", "") - - # Fecha en formato yyMMdd. Asumimos entry_date almacena Clarion Date o Timestamp. - fecha_entrada_str = "000000" - if manifiesto.entry_date: - try: - # Si es Clarion Date - fecha_iso = fecha_clarion_a_iso(manifiesto.entry_date) - fecha_entrada_str = datetime.strptime(fecha_iso, "%Y-%m-%d").strftime("%y%m%d") - except: pass - - firms_code = manifiesto.entry_port_loc or "" - entry_port = manifiesto.entry_port or "000" - - # MF01 - # Sample Clarion: MF01AKR 1234 1234 2602061233026021345 - # Layout: - # MF01 (4) - # Broker (6) -> "AKR " - # Port Ent (5) -> "1234 " - # Port Sal (5) -> "1234 " - # FecEnt (6) -> "260206" - # 12 (2) -> Prefix? - # 3 (1) -> Digit 3? - # 30 (2) -> Constant? - # FecTrans (6) -> "260213" - # Manifiesto (15?) -> "45 " (Sample has '45' at end, maybe manifest is '45'?) - - # Let's align with sample string length and fields. - # "MF01" - # Broker: Left aligned 6 chars - # Port1: Left aligned 5 chars - # Port2: Left aligned 5 chars - # Date1: 6 chars - # "12330" (Hardcoded sequence based on sample analysis vs previous logic) - # Date2: 6 chars - # Manifest: Left aligned 15 chars? Sample "45" is at end. - - # Re-analyzing sample: "MF01AKR 1234 1234 2602061233026021345" - # Length: 4+6+5+5+6+2+1+2+6+2 = 39? No. - # AKR : 6 - # 1234 : 5 - # 1234 : 5 - # 260206: 6 - # 12: 2 - # 3: 1 - # 30: 2 - # 260213: 6 - # 45: 2? - # Total: 4+6+5+5+6+5+6+2 = 39 chars displayed. - - # My generated was: MF01123 000 000 0001011230260213123456879 - # It was way off. - - man_clean = num_manifiesto_clean[:15] - - lineas.append( - f"MF01{datos_empresa.broker:<6}" - f"{entry_port:<5}" - f"{entry_port:<5}" - f"{fecha_entrada_str}" - f"12330{fecha_transmision}" # Fixed sequence "12330" inferred from sample - f"{man_clean:<15}" - ) - self.cuenta_partidas += 1 - - # MF03 - # Sample: MF03FRANCISCO 1234 - # MF03 (4) - # Person (Top Left?) - # Sample: "MF03FRANCISCO 1234" - # It seems "FRANCISCO" is right after MF03. That's the PERSON. - # "1234" is the Gafete/License. - # My previous code put Carrier first: "MF03TRUCK Lopez Doriga..." - # Correct mapping: MF03 + Person(Included Name) + License - - # Let's follow sample: - # MF03 + Person(15?) + License(15?) - # MF03 - transportista = manifiesto.carrier_code or "" - persona = persona_cargo or "" - # 'driver_license' attribute does not exist in Manifest model. - # Using 'transport_code' or similar as fallback for license/gafete. - licencia = manifiesto.transport_code or "" - - lineas.append( - f"MF03{persona[:15]:<15} {licencia[:15]:<15}" - ) - self.cuenta_partidas += 1 - - # --- PROCESAR FACTURAS --- - empresa_dict = { - 'broker': datos_empresa.broker, - 'responsable': datos_empresa.responsable, - 'rfc': datos_empresa.rfc, - 'nombre_empresa': datos_empresa.nombre_empresa, - 'entry_port': entry_port, - 'manufacturer_id': datos_empresa.manufacturer_id - } - - processor = ScaiiProcessor() - l_facturas, e_facturas = processor.procesar_facturas(db, manifiesto_num, empresa_dict, request) - - lineas.extend(l_facturas) - self.errores_validacion.extend(e_facturas) - - # Actualizar acumuladores Globales - self.cuenta_partidas += processor.cuenta_partidas - self.cuenta_facturas += processor.cuenta_facturas - self.valor_total += processor.valor_total_factura - self.peso_bruto_total += processor.peso_bruto_factura - self.peso_neto_total += processor.peso_neto_factura - - # MF80 (Totales Manifiesto) - # Sample: MF80000000000000000200000001099200000002000000009736 - # MF80 (4) + Val(12) + CantFact(4) + PB(12) + Flete(8) + PN(12) - # Importante: El sample muestra que los totales NO tienen puntos y son enteros (centavos). - val_int = int(round(processor.valor_total_factura * 100)) - pb_int = int(round(processor.peso_bruto_factura * 100)) - pn_int = int(round(processor.peso_neto_factura * 100)) - flete_int = 0 # Flete total - - lineas.append( - f"MF80{val_int:012d}" - f"{processor.cuenta_facturas:04d}" - f"{pb_int:012d}" - f"{flete_int:08d}" - f"{pn_int:012d}" - ) - self.cuenta_partidas += 1 - - return lineas - - # (Mantenemos los métodos auxiliares: _obtener_configuracion_sistema, _obtener_datos_empresa, _escribir_archivo, etc.) - def _obtener_configuracion_sistema(self, db): return ConfiguracionSistema(path_arch_transmision="/tmp", utilizar_nombre_generico_mainx30=True, utilizar_codigo_broker_cliente=False) - def _obtener_datos_empresa(self, db): - empresa = db.query(GEmpresa).first() - if not empresa: - # Fallback safe defaults if no company config found - return EmpresaDatos(broker="", responsable="", rfc="", tiene_linea_express="N", nombre_empresa="", manufacturer_id="", ftp_key="") - - return EmpresaDatos( - broker=(empresa.broker_company or "")[:5], - responsable=(empresa.responsible or "")[:30], - rfc=(empresa.rfc or "")[:13], - tiene_linea_express="N", - nombre_empresa=(empresa.name or "")[:40], - manufacturer_id=(empresa.manufacturer_id or "")[:10], - ftp_key=(empresa.ftp_key or "")[:10] - ) - def _validar_datos_empresa(self, datos): pass - def _generar_nombre_archivo(self, c, r, m): return f"{m}_Mainx30.dat" - - def _generar_linea_a(self, f, d): - # Sample: A 26021203AKR AKR 00SCSI - broker = d.broker.strip()[:6] - # Use ftp_key (password?) - password = (d.ftp_key or "00SCSI")[:6] - return f"A {f}03{broker:<6}{broker:<10}{password}" - - def _escribir_archivo(self, ruta, lineas): - Path(ruta).parent.mkdir(parents=True, exist_ok=True) - with open(ruta, 'w', encoding='latin-1') as f: f.write('\r\n'.join(lineas)) - def _generar_mensaje_resultado(self, nombre): return f"Generado: {nombre}" - def _agregar_error_validacion(self, partida, id_err, desc, sol, tipo): - self.errores_validacion.append(ErrorValidacion(partida=partida, linea=0, descripcion=desc, soluciones=sol, identificador=tipo)) \ No newline at end of file diff --git a/backend/api/v1/modules/a76/reports/exportacion/transmission/MAINX30/task.py b/backend/api/v1/modules/a76/reports/exportacion/transmission/MAINX30/task.py deleted file mode 100644 index a6f34be2..00000000 --- a/backend/api/v1/modules/a76/reports/exportacion/transmission/MAINX30/task.py +++ /dev/null @@ -1,47 +0,0 @@ -from celery import Task -from fastapi import HTTPException -from core.celery_app import celery_app -from core.celery_app import celery_app -from core.database import get_core_db as get_db -from .service import Mainx30Service -from .schemas import Mainx30GenerationRequest, Mainx30Response - -@celery_app.task(name="generar_transmission_file_async", bind=True) -def generar_transmission_file_async(self, request_data: dict, tenant_id: int): - """ - Generates the transmission .dat file asynchronously using Mainx30Service - """ - try: - # Re-create db session for task - # Using next(get_db()) is a common pattern for obtaining a session in tasks - # but ensure context management - db = next(get_db()) - - # Deserialize request - request = Mainx30GenerationRequest(**request_data) - - service = Mainx30Service() - response = service.generar_mainx30_expo(db, request, task_instance=self) - - # Return result as dict for Celery serialization - # Ensure we return valid JSON serializable dict - result = response.model_dump() - - # If we returned content directly, encode it if it's bytes (it's str here) - if response.content: - import base64 - # Mainx30Service returns content as string with \r\n - encoded_content = base64.b64encode(response.content.encode('utf-8')).decode('utf-8') - # Add to result to match expected format by frontend dialog - result['content'] = encoded_content - result['file_name'] = response.archivo_generado - result['media_type'] = "text/plain" - - return result - - except Exception as e: - import traceback - traceback.print_exc() - self.update_state(state='FAILURE', meta={'exc_type': type(e).__name__, 'exc_message': str(e)}) - # Re-raise to mark task as failed in Celery - raise e diff --git a/backend/api/v1/modules/a76/reports/importacion/consolidados/mex/schemas.py b/backend/api/v1/modules/a76/reports/importacion/consolidados/mex/schemas.py deleted file mode 100644 index cbb83926..00000000 --- a/backend/api/v1/modules/a76/reports/importacion/consolidados/mex/schemas.py +++ /dev/null @@ -1,98 +0,0 @@ -from decimal import Decimal -from typing import List, Optional, Union -from pydantic import BaseModel, field_validator - - -class ClienteSchema(BaseModel): - header: str - nombre: str - # Ponemos valor por defecto "" y permitimos que sea opcional - direccion: Optional[str] = "" - num_exterior: Optional[str] = "" - num_interior: Optional[str] = "" - colonia: Optional[str] = "" - codigo_postal: Optional[str] = "" - ciudad: Optional[str] = "" - estado: Optional[str] = "" - pais: Optional[str] = "" - tax_id: Optional[str] = "" - programa: Optional[str] = "" - autorizacion: Optional[str] = "" - prosec: Optional[str] = "" - reg_emp: Optional[str] = "" - cert: Optional[str] = "" - - # Si llega un None, lo convertimos en "" automáticamente - @field_validator('direccion', 'nombre', 'tax_id', mode='before') - @classmethod - def prevent_none(cls, v): - return v or "" - -class FacturaSchema(BaseModel): - numero: str - fecha: str - tipo_cambio: float - moneda: str - # Campos de aduanas (Opcionales por si A76 aún no los tiene) - pedimento: str = "" - clave_pedimento: str = "" - remesa: str = "" - acuse_electronico: str = "" - representante_legal: str = "" - nombre_empresa: str = "" - agente_aduanal: str = "" - patente: str = "" - precinto: str = "" - regimen: str = "" - transportista: str = "" - scac: str = "" - caat: str = "" - incoterm: str = "" - transporte: str = "" - num_transporte: str = "" - placas: str = "" - placas_remolque: str = "" - licencia_conductor: str = "" - caat: str = "" - scac: str = "" - aduana: str = "" - destino: str = "" - observaciones: str = "" - transportista_info: str = "" - -class PartidaSchema(BaseModel): - numero_parte: str - descripcion: str - fraccion: str - origen: str - - advalorem:Optional[str] = "" - preferencia:Optional[str] = "" - - cantidad_importacion: Union[float, str] - unidad_medida: str - cantidad_bultos: int - clave_bultos: str - peso_neto: Union[float, str] - peso_bruto: Union[float, str] - valor_costo_unitario: Union[float, str] - valor_total: Union[float, str] - valor_estimado: Union[float, str] = "0.00" - -class TotalesSchema(BaseModel): - cantidad_total: Union[float, str] - bultos_total: int - clave_bultos: str = "" - peso_neto_total: Union[float, str] - peso_bruto_total: Union[float, str] - valor_total_total: Union[float, str] - valor_total_dolares: Union[float, str] - valor_estimado_total: Union[float, str] = "0.00" - -class FacturaImportacionCompleta(BaseModel): - cliente_proveedor: ClienteSchema - cliente_vendido: ClienteSchema - cliente_enviado: ClienteSchema - factura: FacturaSchema - partidas: List[PartidaSchema] - totales: TotalesSchema diff --git a/backend/api/v1/modules/a76/reports/importacion/consolidados/mex/service.py b/backend/api/v1/modules/a76/reports/importacion/consolidados/mex/service.py deleted file mode 100644 index 0422907a..00000000 --- a/backend/api/v1/modules/a76/reports/importacion/consolidados/mex/service.py +++ /dev/null @@ -1,920 +0,0 @@ -import shutil -import base64 -import pdfkit -from pathlib import Path -from decimal import Decimal -from typing import Tuple, List, Callable, Optional - -from jinja2 import Environment, FileSystemLoader, select_autoescape -from fastapi import HTTPException -from sqlalchemy.orm import Session - -# --- MODELOS --- -from api.v1.modules.a76.invoices.models import ( - InvoiceHeader, - InvoiceLogistics, - InvoiceComplianceMx, -) -from api.v1.modules.a76.items.line_financials.models import LineFinancial -from api.v1.modules.a76.items.line_quantities.models import LineQuantity -from api.v1.modules.a76.items.models import LineItem -from api.v1.modules.a76.clients_and_providers.models import ( - ClientProvider, - ClientProviderAddress, - ClientProviderPrograms, -) -from api.v1.modules.a76.parts.models import Part -from api.v1.modules.a76.pedmientos.models import Pedimentos -from api.v1.modules.a76.general_catalogs.company.models import Company -from api.v1.modules.a76.customs_brokers.models import CustomsBroker -from api.v1.modules.a76.items.models import LineItem - -# --- TRANSPORTATION MODELS --- -from api.v1.modules.a76.transportation.transporters.models import Transporter -from api.v1.modules.a76.transportation.vehicles.models import Vehicle -from api.v1.modules.a76.transportation.trailers.models import Trailer -from api.v1.modules.a76.transportation.drivers.models import Driver - -# --- MODELO DE FRACCIONES --- -from api.v1.modules.a76.general_catalogs.fractions.tariff_fractions.models import TariffFraction - -# --- MODELO DE UNIDADES DE MEDIDA --- -from api.v1.modules.a76.general_catalogs.units_of_measure.models import UnitOfMeasure - -# --- SCHEMAS --- -from .schemas import ( - ClienteSchema, - PartidaSchema, - TotalesSchema, - FacturaSchema, - FacturaImportacionCompleta, -) - -from api.v1.modules.sitar.fracciones_usa.catalog_resolve import ( - american_fraction_ad_valorem_from_row, - resolve_american_fraction_from_sitar, -) - - -class ConsolidadoImportacionMexService: - def __init__(self): - self.template_dir = Path(__file__).parent.parent / "templates" - self.jinja_env = Environment( - loader=FileSystemLoader(self.template_dir), - autoescape=select_autoescape(["html", "xml"]), - ) - self.template = self.jinja_env.get_template("cons_mex_ver.html") - - def _get_wkhtmltopdf_config(self): - path = shutil.which("wkhtmltopdf") or "/usr/local/bin/wkhtmltopdf" or "/usr/bin/wkhtmltopdf" - if not Path(path).exists(): - raise RuntimeError("wkhtmltopdf no encontrado.") - return pdfkit.configuration(wkhtmltopdf=path) - - def formatear_numero(self, valor, decimales: int = 2): - """ - Formatea un número con separadores de miles y decimales especificados. - Retorna una cadena formateada para mostrar en reportes. - """ - if valor is None: - valor = 0.0 - try: - num = round(float(valor), decimales) - # Formatear con separadores de miles y decimales - return f"{num:,.{decimales}f}" - except: - return f"0.{'0' * decimales}" - - def _format_fraccion_fallback(self, fraccion_raw: str) -> str: - if not fraccion_raw or len(fraccion_raw) < 8: - return fraccion_raw - return f"{fraccion_raw[:4]}.{fraccion_raw[4:6]}.{fraccion_raw[6:]}" - - def _obtener_datos_cliente( - self, db: Session, client_id: int, rol: str - ) -> ClienteSchema: - main = db.query(ClientProvider).filter(ClientProvider.id == client_id).first() - if not main: - return ClienteSchema( - header=rol, - nombre="Desconocido", - direccion="", - tax_id="", - codigo_postal="", - ciudad="", - estado="", - pais="MEX", - ) - - addr = ( - db.query(ClientProviderAddress) - .filter(ClientProviderAddress.client_id == client_id) - .first() - ) - prog = ( - db.query(ClientProviderPrograms) - .filter(ClientProviderPrograms.client_id == client_id) - .first() - ) - - return ClienteSchema( - header=rol, - nombre=(main.name or main.short_name) or "S/N", - direccion=(addr.streets or "") if addr else "", - num_exterior=(addr.exterior_number or "") if addr else "", - num_interior=(addr.interior_number or "") if addr else "", - colonia=(addr.neighborhood or "") if addr else "", - codigo_postal=(addr.postal_code or "") if addr else "", - ciudad=(addr.city or "") if addr else "", - estado=(addr.state or "") if addr else "", - pais=(addr.country or "MEX") if addr else "MEX", - tax_id=getattr(main, "fiscal_id", "") or "", - programa="IMMEX" if (prog and prog.program) else "", - autorizacion=prog.program_number if prog else "", - prosec=( - prog.prosec_authorization - if (prog and prog.prosec and prog.prosec_authorization) - else "" - ), - reg_emp=( - prog.val_certified_company_registry - if (prog and hasattr(prog, "val_certified_company_registry")) - else ( - prog.certified_company_registry - if (prog and prog.certified_company_registry) - else "" - ) - ), - cert=( - prog.is_certified_company - if (prog and prog.is_certified_company) - else "" - ), - ) - - def obtener_datos( - self, - db: Session, - invoice_id: int, - company_id: int, - progress_callback: Optional[Callable] = None, - ) -> FacturaImportacionCompleta: - try: - if progress_callback: - progress_callback(10, "Buscando factura...") - header = ( - db.query(InvoiceHeader) - .filter( - InvoiceHeader.id == invoice_id, - InvoiceHeader.company_id == company_id, - ) - .first() - ) - if not header: - raise HTTPException(status_code=404, detail="Factura no encontrada") - - compliance = header.compliance_mx - logistics = header.logistics if header.logistics else None - financials = header.financials if header.financials else None - if progress_callback: - progress_callback(20, "Obteniendo datos de pedimento...") - pedimento_id = ( - compliance.pedimento_id - if (compliance and compliance.pedimento_id) - else header.related_doc_id - ) - pedimento = ( - db.query(Pedimentos).filter(Pedimentos.id == pedimento_id).first() - if pedimento_id - else None - ) - - if progress_callback: - progress_callback(30, "Obteniendo cliente y proveedor...") - proveedor_id = compliance.provider_id if compliance else None - cliente_proveedor = ( - self._obtener_datos_cliente(db, proveedor_id, "Proveedor / supplier:") - if proveedor_id - else ClienteSchema( - header="Proveedor", - nombre="No Asignado", - direccion="", - tax_id="", - codigo_postal="", - ciudad="", - estado="", - pais="", - ) - ) - - nombre_agente = "" - if compliance and compliance.customs_broker_id: - broker = ( - db.query(CustomsBroker) - .filter(CustomsBroker.id == compliance.customs_broker_id) - .first() - ) - if broker: - nombre_agente = broker.name - - company = db.query(Company).filter(Company.id == header.company_id).first() - # Datos Default (Company/Importer) - Used for fallback or Right Side (Enviado A) - # Default Header (Company) - cliente_default = ClienteSchema( - header="Importer / Consignee:", - nombre=getattr(company, "name", "Empresa Local"), - direccion="DOMICILIO FISCAL", - num_exterior="", - colonia="", - codigo_postal="", - ciudad="", - estado="", - pais="MEX", - tax_id=getattr(company, "rfc", ""), - programa=getattr(company, "program", "IMMEX"), - autorizacion=getattr(company, "program_number", ""), - ) - - # Left Side Logic (Consignatario / Sold To) - cliente_vendido = cliente_default - if compliance and compliance.sold_to_id: - # Map known headers or default to Sold To / Vendido a - raw = (compliance.sold_to_header or "").upper() - if "CONSIGN" in raw: - clean_header = "Consignee / Consignatario:" - else: - clean_header = "Sold To / Vendido a:" - - cliente_vendido = self._obtener_datos_cliente( - db, compliance.sold_to_id, clean_header - ) - - # Right Side Logic (Enviado A / Shipped To) - cliente_enviado = cliente_default - if compliance and compliance.shipped_to_id: - # Map to Shipped To / Enviado a - clean_header_shipped = "Shipped To / Enviado a:" - - # Fetch client data - cliente_enviado = self._obtener_datos_cliente( - db, compliance.shipped_to_id, clean_header_shipped - ) - - remesa_valor = ( - str(compliance.remesa) if (compliance and compliance.remesa) else "" - ) - acuse_valor = ( - str(compliance.edocument) - if (compliance and compliance.edocument) - else "N/A" - ) - - patente_val = "" - if pedimento and pedimento.license: - patente_val = pedimento.license - elif "broker" in locals() and broker and broker.license: - patente_val = broker.license - - # --- Transport Data Fetching --- - transporte_txt = ( - str(logistics.transport_type) - if (logistics and logistics.transport_type) - else "" - ) - num_transporte_val = (logistics.trailer_num or "") if logistics else "" - - # Init values - placas_val = ( - (logistics.license_plate or "") if logistics else "" - ) # Placas Tracto - placas_remolque_val = "" - transportista_val = (logistics.carrier_id or "") if logistics else "" - caat_val = "" - scac_val = "" - licencia_cond_val = "" - conductor_nombre = "" - - # Block Logic (Clarion Style) for transportista_info - transport_lines = [] - - if logistics: - # 1. Transporter (CAAT / SCAC) - if logistics.carrier_int_id is not None: - transporter_obj = ( - db.query(Transporter) - .filter(Transporter.transporter_id == logistics.carrier_int_id) - .first() - ) - elif logistics.carrier_id: - transporter_obj = ( - db.query(Transporter) - .filter(Transporter.transporter_key == logistics.carrier_id) - .first() - ) - if transporter_obj: - caat_val = transporter_obj.caat_code or "" - scac_val = ( - transporter_obj.transport_code or "" - ) # Mapping transport_code to SCAC - transportista_val = transporter_obj.name or logistics.carrier_id - - # Clarion Logic: Name first - # Line 1: Name - transport_lines.append(transporter_obj.name or "") - - # Line 2: Streets - if transporter_obj.streets: - transport_lines.append(transporter_obj.streets) - - # Line 3: City, State, Country - loc_line = "" - if transporter_obj.city: - loc_line = transporter_obj.city - if transporter_obj.state: - loc_line += f", {transporter_obj.state}, " - else: - loc_line += ", " - else: - if transporter_obj.state: - loc_line = f"{transporter_obj.state}," - - country_desc = transporter_obj.country or "" - if loc_line: - loc_line += f" {country_desc}" - elif country_desc: - loc_line = country_desc - - if loc_line.strip(", "): - transport_lines.append(loc_line) - - # 2. Vehicle (Placas Tracto) - Try transport_id first - if logistics.transport_id: - if logistics.transport_int_id is not None: - veh_obj = ( - db.query(Vehicle) - .filter(Vehicle.vehicle_id == logistics.transport_int_id) - .first() - ) - else: - veh_obj = ( - db.query(Vehicle) - .filter(Vehicle.vehicle_key == logistics.transport_id) - .first() - ) - if veh_obj: - placas_val = veh_obj.plate_number or placas_val - elif ( - logistics.vehicle_num - ): # Fallback to vehicle_num if populated and transport_id failed/empty - veh_obj = ( - db.query(Vehicle) - .filter(Vehicle.vehicle_key == logistics.vehicle_num) - .first() - ) - if veh_obj: - placas_val = veh_obj.plate_number or placas_val - - # 3. Trailer (Placas Remolque) - if logistics.trailer_num: - if logistics.trailer_int_id is not None: - trl_obj = ( - db.query(Trailer) - .filter(Trailer.trailer_id == logistics.trailer_int_id) - .first() - ) - else: - trl_obj = ( - db.query(Trailer) - .filter(Trailer.trailer_number == logistics.trailer_num) - .first() - ) - if trl_obj: - placas_remolque_val = trl_obj.plate_number or "" - - # 4. Driver (License) - if logistics.carrier_id and logistics.driver_name: - conductor_nombre = logistics.driver_name - # Attempt to find driver by name + carrier - drv_obj = ( - db.query(Driver) - .filter( - Driver.transporter_key == logistics.carrier_id, - Driver.driver_name == logistics.driver_name, - ) - .first() - ) - if drv_obj: - licencia_cond_val = drv_obj.license_number or "" - - # --- Building the rest of the block --- - - # Line 4: Driver - if conductor_nombre: - transport_lines.append(f"Driver/Conductor: {conductor_nombre}") - - # Line 5: Conveyance / Transporte - t_label = "Conveyance / Transporte" - t_val = placas_val # Default to Truck Plate - - if logistics.transport_type: - ttype = str(logistics.transport_type).lower() - if "caja" in ttype or "trailer" in ttype: - t_label = "Trailer / Caja" - t_val = placas_remolque_val or num_transporte_val - elif "placa" in ttype: - t_label = "Plates / Placas" - elif "camion" in ttype or "truck" in ttype: - t_label = "Truck / Camión" - - if t_val: - transport_lines.append(f"{t_label}: {t_val}") - - # Line 6: SCAC / CAAT - codes_line = "" - if scac_val: - codes_line = f"SCAC Code/Clave: {scac_val}" - if caat_val: - if codes_line: - codes_line += f", CAAT Code/Clave: {caat_val}" - else: - codes_line = f"CAAT Code/Clave: {caat_val}" - - if codes_line: - transport_lines.append(codes_line) - - # Join with newlines - transport_block_str = "\n".join([l for l in transport_lines if l]) - - factura_schema = FacturaSchema( - numero=header.invoice_number or "S/N", - fecha=str(header.invoice_date) if header.invoice_date else "", - tipo_cambio=( - float(financials.exchange_rate) - if (financials and financials.exchange_rate) - else ( - float(pedimento.exchange_rate) - if pedimento and pedimento.exchange_rate - else 1.0 - ) - ), - moneda=getattr(header, "currency", "USD") or "USD", - incoterm=(logistics.incoterm or "") if logistics else "", - observaciones=header.observation_es or header.observation_en or "", - pedimento=( - f"{pedimento.year} {pedimento.customs_office[:2] if pedimento.customs_office else ''} {pedimento.license} {pedimento.pedimento_number}" - if pedimento - else "" - ), - clave_pedimento=pedimento.pedimento_code if pedimento else "", - regimen=header.document_type or "", - patente=patente_val, - agente_aduanal=nombre_agente, - transporte=transporte_txt, - num_transporte=num_transporte_val, - placas=placas_val, - placas_remolque=placas_remolque_val, - transportista=transportista_val, - caat=caat_val, - scac=scac_val, - licencia_conductor=licencia_cond_val, - aduana=( - compliance.aduana - if (compliance and compliance.aduana) - else ( - pedimento.customs_office[:2] - if (pedimento and pedimento.customs_office) - else "" - ) - ), - precinto=(logistics.seal_number or "") if logistics else "", - destino=(logistics.destination_goods or "") if logistics else "", - remesa=remesa_valor, - acuse_electronico=acuse_valor, - representante_legal=getattr(company, "responsible", "") or "", - nombre_empresa=getattr(company, "name", "") or "", - transportista_info=transport_block_str, - ) - - if progress_callback: - progress_callback(50, "Procesando partidas...") - - # --- Fetch Lines from SINGLE Invoice (Requested Scope Change) --- - # User requested to ONLY report items from the specific selected invoice, - # NOT consolidating all invoices from the same Pedimento. - target_invoice_ids = [header.id] - - lines = ( - db.query(LineItem) - .filter(LineItem.invoice_id.in_(target_invoice_ids)) - .all() - ) - - partidas_list = [] - - # --- AGGREGATION LOGIC (Refactoring based on Clarion) --- - from collections import defaultdict - - # Key: (us_fraction_code, origin_country) - # Value: Object with accumulated fields - aggregated_data = defaultdict( - lambda: { - "qty": 0.0, - "net_weight_kgs": 0.0, - "gross_weight_kgs": 0.0, - "total_value": 0.0, - "est_total_value": 0.0, - "description": "", - "advalorem_txt": "0%", - "unit_measure": "PZA", # Placeholder, takes first one found - "hts_code_print": "", - "part_number_display": "CONSOLIDADO", - } - ) - - # Pre-fetch US Tariff Fractions for efficiency if possible, or query inside loop (caching recommended) - # For simplicity in this step, we query inside or rely on Part data. - # Ideally fetch USTariffFraction from DB based on Part.us_fraction - - # --- Optimización: Cargar Facturas en Memoria --- - invoices_list = ( - db.query(InvoiceHeader) - .filter(InvoiceHeader.id.in_(target_invoice_ids)) - .all() - ) - invoice_map = {inv.id: inv for inv in invoices_list} - - for line in lines: - qty = ( - db.query(LineQuantity) - .filter(LineQuantity.item_line_id == line.id) - .first() - ) - fin = ( - db.query(LineFinancial) - .filter(LineFinancial.item_line_id == line.id) - .first() - ) - part_master = db.query(Part).filter(Part.id == line.part_number_id).first() - - # --- Resolver Identificadores (MOVED INSIDE MAIN LOOP) --- - us_fraction_raw = "" - origin_final = "MEX" - - if part_master: - origin_final = ( - part_master.fa_data.origin_country - if (part_master.fa_data and part_master.fa_data.origin_country) - else "MEX" - ) - us_fraction_raw = ( - part_master.us_fraction if part_master.us_fraction else "" - ) - - # Key for aggregation - us_frac_clean = us_fraction_raw.strip() - agg_key = (us_frac_clean, origin_final) - # --- Weights & Qty --- - q_line = float(qty.quantity) if (qty and qty.quantity) else 0.0 - nw_line = float(qty.net_weight) if qty else 0.0 - gw_line = float(qty.gross_weight) if qty else 0.0 - - # --- Multi-Currency Normalization Logic --- - # Determine Line Currency context - # Use manual lookup instead of specific attribute - invoice_id = line.invoice_id - line_invoice = invoice_map.get(invoice_id) if invoice_id else None - - line_currency_is_mxn = False - line_exchange_rate = 1.0 - - if line_invoice and line_invoice.financials: - # Check explicit currency string AND code - curr_desc = str(line_invoice.financials.currency or "").upper() - curr_code = str(line_invoice.financials.currency_type or "").upper() - - # Logic: It is MXN if description says PESO/MX or code is MXN/MN - is_mx_desc = "MX" in curr_desc or "PESO" in curr_desc - is_mx_code = "MXN" in curr_code or "MN" == curr_code - - # But if code allows clarifying USD, prioritize that - is_usd_code = "USD" in curr_code - - if is_usd_code: - line_currency_is_mxn = False - elif is_mx_code or is_mx_desc: - line_currency_is_mxn = True - else: - line_currency_is_mxn = False # Default to Foreign/USD if unsure - - line_exchange_rate = float( - line_invoice.financials.exchange_rate or 1.0 - ) - - # Target Report Currency - report_is_mxn = factura_schema.moneda == "MXN" - - # DEBUG LOGGING - if line_invoice: - print( - f"DEBUG: Line {line.id} - Inv {line_invoice.id} - CurrDesc: '{curr_desc}' Code: '{curr_code}' - Rate: {line_exchange_rate}" - ) - print( - f"DEBUG: Is MXN Context? {line_currency_is_mxn}. Report is MXN? {report_is_mxn}" - ) - - # --- Get Financials for Line (Raw) --- - v_total_raw = 0.0 - v_unitario_raw = 0.0 - - if fin: - # NEW PRIORITY LOGIC (To avoid Inflation from dirty Customs Unit Cost) - # Priority 1: Use 'fin.value_usd' if it exists and > 0. - # Priority 2: Use 'fin.total_commercial_value' if it exists and > 0. - # Priority 3: Calculate using 'fin.unit_cost_commercial_usd' * 'q_line'. - # Priority 4: Only use 'fin.unit_cost_usd' * 'q_line' if commercial data is also missing. - - val_usd = float(fin.value_usd or 0.0) - total_comm = float(fin.total_commercial_value or 0.0) - unit_comm_usd = float(fin.unit_cost_commercial_usd or 0.0) - unit_usd = float(fin.unit_cost_usd or 0.0) - - # 1. Direct Total: Custom Value (Best case) - if val_usd > 0: - v_total_raw = val_usd - - # 2. Direct Total: Commercial Total - elif total_comm > 0: - # Convert if invoice currency is MXN - if line_currency_is_mxn and line_exchange_rate > 0: - v_total_raw = total_comm / line_exchange_rate - else: - v_total_raw = total_comm - - # 3. Calc from Commercial Unit Cost (Safe Fallback) - elif unit_comm_usd > 0 and q_line > 0: - v_total_raw = unit_comm_usd * q_line - - # 4. Calc from Customs Unit Cost (Unknown Risk - Last Resort) - elif unit_usd > 0 and q_line > 0: - v_total_raw = unit_usd * q_line - - else: - v_total_raw = 0.0 - - # NOTE: v_unitario_raw is left as 0.0 here. - # It will be calculated in the 'Calculation Gap Fill' block below: - # v_unitario_raw = v_total_raw / q_line - # This guarantees consistency and avoids the inflated unit cost record (198.00). - - # --- Calculation Gap Fill (Raw) --- - if q_line > 0: - if v_total_raw == 0 and v_unitario_raw > 0: - v_total_raw = v_unitario_raw * q_line - if v_unitario_raw == 0 and v_total_raw > 0: - v_unitario_raw = v_total_raw / q_line - - # --- Conversion to Report Currency (DISABLED TEMPORARILY) --- - # User confirms all are USD. Forcing direct sum to avoid logic errors in detection. - v_total_line = v_total_raw - v_unitario_line = v_unitario_raw - - # if report_is_mxn and not line_currency_is_mxn: - # # USD -> MXN - # v_total_line = v_total_raw * line_exchange_rate - # v_unitario_line = v_unitario_raw * line_exchange_rate - # elif not report_is_mxn and line_currency_is_mxn: - # # MXN -> USD - # if line_exchange_rate > 0: - # v_total_line = v_total_raw / line_exchange_rate - # v_unitario_line = v_unitario_raw / line_exchange_rate - # else: - # v_total_line = 0.0 - # v_unitario_line = 0.0 - - print(f"DEBUG: ValRaw: {v_total_raw} -> ValFinal: {v_total_line}") - - # --- Resolve Fraction Details (Description & Rate) --- - # Only if this is the first time we see this key (or overwrite, doesn't matter much as they should be same for same HTS) - # We check if we already have description set to avoid re-querying if we want optimization, - # but relying on DB query per distinct fraction is safer. - - current_agg = aggregated_data[agg_key] - - if not current_agg["description"]: - resolved = ( - resolve_american_fraction_from_sitar(us_frac_clean) - if us_frac_clean - else None - ) - if resolved: - sitar_row, _canon = resolved - current_agg["description"] = ( - (sitar_row.DESCRIPCION or "").strip() or "Sin Descripción" - ) - adv_val = american_fraction_ad_valorem_from_row(sitar_row) - current_agg["advalorem_txt"] = ( - f"{adv_val}%" if adv_val is not None else "0%" - ) - else: - current_agg["description"] = ( - part_master.description_spanish if part_master else "S/D" - ) - - current_agg["hts_code_print"] = us_frac_clean - - # Obtener descripción de la unidad de medida desde la tabla a76.item_lines - if line.unit_of_measure: - uom = ( - db.query(UnitOfMeasure) - .filter( - UnitOfMeasure.id == line.unit_of_measure, - UnitOfMeasure.company_id == company_id, - ) - .first() - ) - current_agg["unit_measure"] = ( - uom.description - if (uom and uom.description) - else (uom.code if uom else "KGS") - ) - else: - current_agg["unit_measure"] = "KGS" # Default fallback - - # --- Calculate Estimated Tax for this Line --- - rate = 0.0 - try: - clean_adv = current_agg["advalorem_txt"].replace("%", "").strip() - rate = float(clean_adv) / 100.0 - except: - rate = 0.0 - - v_est_line = v_total_line * rate - - # --- Accumulate --- - current_agg["qty"] += q_line - current_agg["net_weight_kgs"] += nw_line - current_agg["gross_weight_kgs"] += gw_line - current_agg["total_value"] += v_total_line - current_agg["est_total_value"] += v_est_line - - # --- Convert Aggregated Data to Schema List --- - partidas_list = [] - - for (hts, origin), data in aggregated_data.items(): - - # Calculate Unit Price based on Total Value / Total Qty - unit_price = 0.0 - if data["qty"] > 0: - unit_price = data["total_value"] / data["qty"] - - partidas_list.append( - PartidaSchema( - numero_parte="VARIOS", # Or empty - descripcion=data["description"], - fraccion=data["hts_code_print"], - origen=origin, - advalorem=data["advalorem_txt"], - preferencia="General", - cantidad_importacion=self.formatear_numero(data["qty"]), - unidad_medida=data["unit_measure"], - cantidad_bultos=0, # Summing bultos might be tricky if not homogeneous, check logic later - clave_bultos="", - peso_neto=self.formatear_numero(data["net_weight_kgs"]), - peso_bruto=self.formatear_numero(data["gross_weight_kgs"]), - valor_costo_unitario=self.formatear_numero(unit_price), - valor_total=self.formatear_numero(data["total_value"]), - valor_estimado=self.formatear_numero(data["est_total_value"]), - ) - ) - - # Sort by Fraction (HTS Code) - partidas_list.sort(key=lambda x: x.fraccion) - - totales = self.calcular_totales( - partidas_list, Decimal(factura_schema.tipo_cambio) - ) - - return FacturaImportacionCompleta( - cliente_proveedor=cliente_proveedor, - cliente_vendido=cliente_vendido, - cliente_enviado=cliente_enviado, - factura=factura_schema, - partidas=partidas_list, - totales=totales, - ) - - except Exception as e: - print(f"Error Service A76: {e}") - raise HTTPException(status_code=500, detail=f"Error: {str(e)}") - - def calcular_totales( - self, partidas: List[PartidaSchema], tipo_cambio: Decimal - ) -> TotalesSchema: - cant = sum(p.cantidad_importacion for p in partidas) - valor = sum(p.valor_total for p in partidas) - peso_n = sum(p.peso_neto for p in partidas) - peso_b = sum(p.peso_bruto for p in partidas) - bultos = sum(p.cantidad_bultos for p in partidas) - claves = [p.clave_bultos for p in partidas if p.clave_bultos] - clave_comun = max(set(claves), key=claves.count) if claves else "" - if bultos > 1 and clave_comun and not clave_comun.endswith("S"): - clave_comun += "S" - v_est = sum( - p.valor_estimado - for p in partidas - if isinstance(p.valor_estimado, (int, float, Decimal)) - ) - - tc = float(tipo_cambio) if tipo_cambio else 1.0 - return TotalesSchema( - cantidad_total=self.formatear_numero(cant), - bultos_total=bultos, - clave_bultos=clave_comun, - peso_neto_total=self.formatear_numero(peso_n), - peso_bruto_total=self.formatear_numero(peso_b), - valor_total_total=self.formatear_numero(valor), - valor_total_dolares=self.formatear_numero(valor / tc if tc > 0 else 0), - valor_estimado_total=self.formatear_numero(v_est), - ) - - def generar_factura_completa( - self, - db: Session, - invoice_id: int, - company_id: int, - formato: str = "pdf", - progress_callback: Optional[Callable] = None, - ) -> Tuple[bytes, str, str]: - if progress_callback: - progress_callback(5, "Iniciando servicio de reporte...") - datos = self.obtener_datos(db, invoice_id, company_id, progress_callback) - - if progress_callback: - progress_callback(80, "Renderizando plantilla...") - - # LOGO LOGIC - logo_b64 = None - try: - # Fetch company to get logo path - - comp_logo = db.query(Company).filter(Company.id == company_id).first() - if comp_logo and comp_logo.logo: - p = Path(comp_logo.logo) - - # Logic robusta de búsqueda (igual que en routes.py) - target_path = p - if not target_path.exists(): - # Intentar en la ruta estándar: app_data/logos/{id}/{nombre} - # Esto cubre el caso donde solo se guardó el nombre del archivo o la ruta absoluta cambió - fallback = Path(f"app_data/logos/{company_id}") / p.name - if fallback.exists(): - target_path = fallback - - if target_path.exists(): - with open(target_path, "rb") as image_file: - encoded_string = base64.b64encode(image_file.read()).decode( - "utf-8" - ) - # Detect MIME type loosely - mime = "image/png" - if target_path.suffix.lower() in [".jpg", ".jpeg"]: - mime = "image/jpeg" - logo_b64 = f"data:{mime};base64,{encoded_string}" - except Exception as e: - print(f"Error loading logo: {e}") - - context = { - "cliente_proveedor": datos.cliente_proveedor.model_dump(), - "cliente_vendido": datos.cliente_vendido.model_dump(), - "cliente_enviado": datos.cliente_enviado.model_dump(), - "factura": datos.factura.model_dump(), - "partidas": [p.model_dump() for p in datos.partidas], - "totales": datos.totales.model_dump(), - "logo_b64": logo_b64, - } - html_content = self.template.render(**context) - nombre = f"Consolidado_{datos.factura.numero}.{formato}" - if formato == "html": - return html_content.encode("utf-8"), nombre, "text/html" - - if progress_callback: - progress_callback(90, "Generando PDF final...") - options = { - "page-size": "Letter", - "margin-top": "0.5in", - "margin-right": "0.5in", - "margin-bottom": "0.5in", - "margin-left": "0.5in", - "encoding": "UTF-8", - "enable-local-file-access": None, - } - pdf = pdfkit.from_string( - html_content, - False, - options=options, - configuration=self._get_wkhtmltopdf_config(), - ) - - if progress_callback: - progress_callback(100, "Completado") - return pdf, nombre, "application/pdf" diff --git a/backend/api/v1/modules/a76/reports/importacion/consolidados/routes.py b/backend/api/v1/modules/a76/reports/importacion/consolidados/routes.py deleted file mode 100644 index 629805ea..00000000 --- a/backend/api/v1/modules/a76/reports/importacion/consolidados/routes.py +++ /dev/null @@ -1,59 +0,0 @@ -from enum import Enum -from typing import Dict, Any -from fastapi import APIRouter, Depends, Query, Response, HTTPException -from sqlalchemy.orm import Session -from celery.result import AsyncResult -from core.celery_app import celery_app -from core.database import get_core_db -from core.security import get_current_user, validate_access_to_resource -from api.v1.modules.core.tasks_tracking import track_and_dispatch -from .mex.service import ConsolidadoImportacionMexService -from .task import generar_pdf_consolidado_async - -router = APIRouter() -servicio_mex = ConsolidadoImportacionMexService() - -@router.get("/tasks/{task_id}") -async def get_task_status( - task_id: str, - current_user: Dict[str, Any] = Depends(get_current_user), - db: Session = Depends(get_core_db) -): - task_result = AsyncResult(task_id, app=celery_app) - - response = { - "task_id": task_id, - "state": task_result.state, - "result": None, - "info": None - } - - if task_result.state == 'FAILURE': - response["result"] = str(task_result.result) - elif task_result.state == 'SUCCESS': - response["result"] = task_result.result - elif task_result.state == 'PROCESSING': - response["info"] = task_result.info - - return response - -@router.post("/{invoice_id}/download-async") -async def trigger_descarga_consolidado( - invoice_id: int, - company_id: int = Query(..., description="ID de la empresa"), - current_user: Dict[str, Any] = Depends(get_current_user), - db: Session = Depends(get_core_db) -): - tenant_id = validate_access_to_resource(db, company_id, current_user) - task = track_and_dispatch( - db=db, - task=generar_pdf_consolidado_async, - tenant_id=tenant_id, - company_id=company_id, - requested_by_user=current_user.get("preferred_username") or current_user.get("email") or current_user.get("sub"), - task_name="generar_pdf_consolidado_async", - task_group="reports", - task_origin="a76/reports/importacion/consolidados", - args=[invoice_id, company_id], - ) - return {"task_id": task.id, "message": "Generación iniciada"} diff --git a/backend/api/v1/modules/a76/reports/importacion/consolidados/task.py b/backend/api/v1/modules/a76/reports/importacion/consolidados/task.py deleted file mode 100644 index 3500ac70..00000000 --- a/backend/api/v1/modules/a76/reports/importacion/consolidados/task.py +++ /dev/null @@ -1,55 +0,0 @@ -from fastapi import HTTPException -import base64 -import logging -from core.celery_app import celery_app -from celery import current_task, states -from core.database import CoreSessionLocal - -from .mex.service import ConsolidadoImportacionMexService - -logger = logging.getLogger(__name__) - -@celery_app.task(name="generar_pdf_consolidado_async", bind=True) -def generar_pdf_consolidado_async(self, invoice_id: int, company_id: int): - - # 1. Abrimos conexión a la DB - db = CoreSessionLocal() - try: - logger.info(f"Worker procesando consolidado {invoice_id}...") - - # 2. Instanciamos el servicio de reportes - service = ConsolidadoImportacionMexService() - - # Update state to PROCESSING - self.update_state(state='PROCESSING', meta={'current': 5, 'total': 100, 'status': 'Iniciando generación...'}) - - def progress_callback(progress: int, status: str): - self.update_state(state='PROCESSING', meta={'current': progress, 'total': 100, 'status': status}) - - # 3. Generamos los bytes del PDF - pdf_bytes, nombre, media_type = service.generar_factura_completa( - db=db, - invoice_id=invoice_id, - company_id=company_id, - progress_callback=progress_callback - ) - - # 4. Codificamos a base64 para que viaje seguro por Valkey - pdf_base64 = base64.b64encode(pdf_bytes).decode('utf-8') - - return { - "status": "success", - "file_name": nombre, - "content": pdf_base64, - "media_type": media_type - } - - except HTTPException as e: - logger.error(f"HTTP Error en Celery Worker: {str(e.detail)}") - return {"status": "error", "message": str(e.detail)} - except Exception as e: - logger.error(f"Error en Celery Worker: {str(e)}") - return {"status": "error", "message": f"Error interno en el servidor de reportes: {str(e)}"} - finally: - # 5. MUY IMPORTANTE: Cerramos la conexión para no saturar Postgres - db.close() diff --git a/backend/api/v1/modules/a76/reports/importacion/consolidados/templates/cons_mex_ver.html b/backend/api/v1/modules/a76/reports/importacion/consolidados/templates/cons_mex_ver.html deleted file mode 100644 index 2f0d026b..00000000 --- a/backend/api/v1/modules/a76/reports/importacion/consolidados/templates/cons_mex_ver.html +++ /dev/null @@ -1,540 +0,0 @@ - - - - - - Consolidado Importacion Mexicana - {{ factura.numero }} - - - - -
-
-
-

HTS Code / Fracción Americana (temporal)

-
-

-
-
-

-


-
-
-
-
- {% if logo_b64 %} -
- -
- {% endif %} -
-

{{ cliente_proveedor.header }}

-

{{ cliente_proveedor.nombre }}

-

{{ cliente_proveedor.direccion }} - {% if cliente_proveedor.num_exterior %} Ext: {{ cliente_proveedor.num_exterior }}{% endif %} - {% if cliente_proveedor.num_interior %} Int: {{ cliente_proveedor.num_interior }}{% endif %} -

-

{{ cliente_proveedor.colonia }} {% if cliente_proveedor.codigo_postal %} CP: {{ - cliente_proveedor.codigo_postal }}{% endif %}

-

{{ cliente_proveedor.ciudad }}, {{ cliente_proveedor.estado }}, {{ cliente_proveedor.pais }}

-

TAX ID: {{ cliente_proveedor.tax_id }} - {% if cliente_proveedor.programa and cliente_proveedor.programa != 'Ninguno' %} - {{ cliente_proveedor.programa }}: {{ cliente_proveedor.autorizacion }} - {% endif %} -

-


-
-
- -
- - - - - - - - - - - - - -
-

Invoice No.:

-
-

{{ factura.numero }}

-
-

Date:

-
-

{{ factura.fecha }}

-
-

Rate:

-
-

{{ factura.tipo_cambio }}

-
- - -
-

Coveyance Co. / Cia. Transportista:

-

{{ factura.transportista_info }}

-
-
-
- -
-
-

{{ cliente_vendido.header }}

-

{{ cliente_vendido.nombre }}

-

{{ cliente_vendido.direccion }} - {% if cliente_vendido.num_exterior %} Ext: {{ cliente_vendido.num_exterior }}{% endif %} - {% if cliente_vendido.num_interior %} Int: {{ cliente_vendido.num_interior }}{% endif %} -

-

{{ cliente_vendido.colonia }} {% if cliente_vendido.codigo_postal %} CP: {{ - cliente_vendido.codigo_postal }}{% endif %}

-

{{ cliente_vendido.ciudad }}, {{ cliente_vendido.estado }}, {{ cliente_vendido.pais }} -

-

RFC: {{ cliente_vendido.tax_id }} - {% if cliente_vendido.programa and cliente_vendido.programa != 'Ninguno' %} - {{ cliente_vendido.programa }}: {{ cliente_vendido.autorizacion }} - {% endif %} -

-

- {% if cliente_vendido.prosec %}PROSEC: {{ cliente_vendido.prosec }} {% endif %} - {% if cliente_vendido.reg_emp %}REG EMP: {{ cliente_vendido.reg_emp }} {% endif %} - {% if cliente_vendido.cert %}CERT: {{ cliente_vendido.cert }}{% endif %} -

-
- -
-

{{ cliente_enviado.header }}

-

{{ cliente_enviado.nombre }}

-

{{ cliente_enviado.direccion }} - {% if cliente_enviado.num_exterior %} Ext: {{ cliente_enviado.num_exterior }}{% endif %} - {% if cliente_enviado.num_interior %} Int: {{ cliente_enviado.num_interior }}{% endif %} -

-

{{ cliente_enviado.colonia }} {% if cliente_enviado.codigo_postal %} CP: {{ - cliente_enviado.codigo_postal }}{% endif %}

-

{{ cliente_enviado.ciudad }}, {{ cliente_enviado.estado }}, {{ cliente_enviado.pais }} -

-

RFC: {{ cliente_enviado.tax_id }} - {% if cliente_enviado.programa and cliente_enviado.programa != 'Ninguno' %} - {{ cliente_enviado.programa }}: {{ cliente_enviado.autorizacion }} - {% endif %} -

-

- {% if cliente_enviado.prosec %}PROSEC: {{ cliente_enviado.prosec }} {% endif %} - {% if cliente_enviado.reg_emp %}REG EMP: {{ cliente_enviado.reg_emp }} {% endif %} - {% if cliente_enviado.cert %}CERT: {{ cliente_enviado.cert }}{% endif %} -

-
-
-


-
- - - - - - - - - - - - - - - - - {% for partida in partidas %} - - - - - - - - - - - - - {% endfor %} - - - - - - - - - - - - - - - - - - - - - - -
-

HTS Code / Fracción Americana (temporal) -

-
-

Country

-
-

Comm. Qty

-
-

Unit

-
-

Weight (KGS)

-
-
-

Net

-
-
-

Gross

-
-
-
-

Dutiable Values

-
-
-

Unit

-
-
-

Totals

-
-
-
-

Rate

-
-

Est. Duties

-
-

{{ partida.fraccion }}

-
-

{{ partida.origen or 'MEX' }}

-
-

{{ partida.cantidad_importacion }}

-
-

{{ partida.unidad_medida }}

-
-

{{ partida.peso_neto }}

-
-

{{ partida.peso_bruto }}

-
-

${{ partida.valor_costo_unitario }}

-
-

${{ partida.valor_total }}

-
-

{{ partida.advalorem }}

-
-

${{ partida.valor_estimado }}

-
-

- TOTALS -

-
-

{{ totales.cantidad_total }}

-
-

- {% if totales.bultos_total != 0 %}{{ totales.bultos_total }}{% endif %} - {{ totales.clave_bultos or '' }} -

-
-

{{ totales.peso_neto_total }}

-
-

{{ totales.peso_bruto_total }}

-
- - -

${{ totales.valor_total_total }}

-
- - -

${{ totales.valor_estimado_total }}

-
-

{{ factura.observaciones }}

-
-

-

{{ factura.representante_legal }}

-

{{ factura.nombre_empresa }}

-


-
-


-

Normal Por Parte

-

I declare under penalty of perjury that the information contained in - this document is true and I am responsible for proving what is declared here.

-
- - - \ No newline at end of file diff --git a/backend/api/v1/modules/a76/reports/importacion/consolidados/temporary/mex/service.py b/backend/api/v1/modules/a76/reports/importacion/consolidados/temporary/mex/service.py deleted file mode 100644 index 630e1351..00000000 --- a/backend/api/v1/modules/a76/reports/importacion/consolidados/temporary/mex/service.py +++ /dev/null @@ -1,664 +0,0 @@ -import shutil -import base64 -import pdfkit -from pathlib import Path -from decimal import Decimal -from typing import Tuple, List, Callable, Optional - -from jinja2 import Environment, FileSystemLoader, select_autoescape -from fastapi import HTTPException -from sqlalchemy.orm import Session - -# --- MODELOS --- -from api.v1.modules.a76.invoices.models import InvoiceHeader, InvoiceLogistics, InvoiceComplianceMx -from api.v1.modules.a76.items.line_financials.models import LineFinancial -from api.v1.modules.a76.items.line_quantities.models import LineQuantity -from api.v1.modules.a76.clients_and_providers.models import ( - ClientProvider, ClientProviderAddress, ClientProviderPrograms -) -from api.v1.modules.a76.parts.models import Part -from api.v1.modules.a76.pedmientos.models import Pedimentos -from api.v1.modules.a76.general_catalogs.company.models import Company -from api.v1.modules.a76.customs_brokers.models import CustomsBroker -from api.v1.modules.a76.items.models import LineItem - -# --- TRANSPORTATION MODELS --- -from api.v1.modules.a76.transportation.transporters.models import Transporter -from api.v1.modules.a76.transportation.vehicles.models import Vehicle -from api.v1.modules.a76.transportation.trailers.models import Trailer -from api.v1.modules.a76.transportation.drivers.models import Driver - -# --- MODELO DE FRACCIONES --- -from api.v1.modules.a76.general_catalogs.fractions.tariff_fractions.models import TariffFraction - -# --- SCHEMAS --- -from ...mex.schemas import ( - ClienteSchema, PartidaSchema, TotalesSchema, - FacturaSchema, FacturaImportacionCompleta -) - -class ConsolidadoImportacionMexService: - def __init__(self): - self.template_dir = Path(__file__).parent.parent / "templates" - self.jinja_env = Environment( - loader=FileSystemLoader(self.template_dir), - autoescape=select_autoescape(['html', 'xml']) - ) - self.template = self.jinja_env.get_template('cons_mex_ver.html') - - def _get_wkhtmltopdf_config(self): - path = shutil.which("wkhtmltopdf") or "/usr/local/bin/wkhtmltopdf" - if not Path(path).exists(): - raise RuntimeError("wkhtmltopdf no encontrado.") - return pdfkit.configuration(wkhtmltopdf=path) - - def formatear_numero(self, valor, decimales: int = 2): - if valor is None: return 0.0 - try: - return round(float(valor), decimales) - except: return 0.0 - - def _format_fraccion_fallback(self, fraccion_raw: str) -> str: - if not fraccion_raw or len(fraccion_raw) < 8: - return fraccion_raw - return f"{fraccion_raw[:4]}.{fraccion_raw[4:6]}.{fraccion_raw[6:]}" - - def _obtener_datos_cliente(self, db: Session, client_id: int, rol: str) -> ClienteSchema: - main = db.query(ClientProvider).filter(ClientProvider.id == client_id).first() - if not main: - return ClienteSchema(header=rol, nombre="Desconocido", direccion="", tax_id="", codigo_postal="", ciudad="", estado="", pais="MEX") - - addr = db.query(ClientProviderAddress).filter(ClientProviderAddress.client_id == client_id).first() - prog = db.query(ClientProviderPrograms).filter(ClientProviderPrograms.client_id == client_id).first() - - return ClienteSchema( - header=rol, - nombre=(main.name or main.short_name) or "S/N", - direccion=(addr.streets or "") if addr else "", - num_exterior=(addr.exterior_number or "") if addr else "", - num_interior=(addr.interior_number or "") if addr else "", - colonia=(addr.neighborhood or "") if addr else "", - codigo_postal=(addr.postal_code or "") if addr else "", - ciudad=(addr.city or "") if addr else "", - estado=(addr.state or "") if addr else "", - pais=(addr.country or "MEX") if addr else "MEX", - tax_id=getattr(main, "fiscal_id", "") or "", - programa="IMMEX" if (prog and prog.program) else "", - autorizacion=prog.program_number if prog else "", - prosec=prog.prosec_authorization if (prog and prog.prosec and prog.prosec_authorization) else "", - reg_emp=prog.val_certified_company_registry if (prog and hasattr(prog, 'val_certified_company_registry')) else ( - prog.certified_company_registry if (prog and prog.certified_company_registry) else "" - ), - cert=prog.is_certified_company if (prog and prog.is_certified_company) else "" - ) - - def obtener_datos(self, db: Session, invoice_id: int, company_id: int, progress_callback: Optional[Callable] = None) -> FacturaImportacionCompleta: - try: - if progress_callback: progress_callback(10, "Buscando factura...") - header = db.query(InvoiceHeader).filter(InvoiceHeader.id == invoice_id, InvoiceHeader.company_id == company_id).first() - if not header: raise HTTPException(status_code=404, detail="Factura no encontrada") - - compliance = header.compliance_mx - logistics = header.logistics if header.logistics else None - financials = header.financials if header.financials else None - if progress_callback: progress_callback(20, "Obteniendo datos de pedimento...") - pedimento_id = compliance.pedimento_id if (compliance and compliance.pedimento_id) else header.related_doc_id - pedimento = db.query(Pedimentos).filter(Pedimentos.id == pedimento_id).first() if pedimento_id else None - - if progress_callback: progress_callback(30, "Obteniendo cliente y proveedor...") - proveedor_id = compliance.provider_id if compliance else None - cliente_proveedor = self._obtener_datos_cliente(db, proveedor_id, "Proveedor / supplier:") if proveedor_id else ClienteSchema(header="Proveedor", nombre="No Asignado", direccion="", tax_id="", codigo_postal="", ciudad="", estado="", pais="") - - nombre_agente = "" - if compliance and compliance.customs_broker_id: - broker = db.query(CustomsBroker).filter(CustomsBroker.id == compliance.customs_broker_id).first() - if broker: nombre_agente = broker.name - - company = db.query(Company).filter(Company.id == header.company_id).first() - # Datos Default (Company/Importer) - Used for fallback or Right Side (Enviado A) - # Default Header (Company) - cliente_default = ClienteSchema( - header="Importer / Consignee:", - nombre=getattr(company, 'name', "Empresa Local"), - direccion="DOMICILIO FISCAL", - num_exterior="", colonia="", codigo_postal="", ciudad="", estado="", pais="MEX", - tax_id=getattr(company, 'rfc', ""), - programa=getattr(company, 'program', "IMMEX"), autorizacion=getattr(company, 'program_number', "") - ) - - # Left Side Logic (Consignatario / Sold To) - cliente_vendido = cliente_default - if compliance and compliance.sold_to_id: - # Map known headers or default to Sold To / Vendido a - raw = (compliance.sold_to_header or "").upper() - if "CONSIGN" in raw: - clean_header = "Consignee / Consignatario:" - else: - clean_header = "Sold To / Vendido a:" - - cliente_vendido = self._obtener_datos_cliente(db, compliance.sold_to_id, clean_header) - - # Right Side Logic (Enviado A / Shipped To) - cliente_enviado = cliente_default - if compliance and compliance.shipped_to_id: - # Map to Shipped To / Enviado a - clean_header_shipped = "Shipped To / Enviado a:" - - # Fetch client data - cliente_enviado = self._obtener_datos_cliente(db, compliance.shipped_to_id, clean_header_shipped) - - remesa_valor = str(compliance.remesa) if (compliance and compliance.remesa) else "" - acuse_valor = str(compliance.edocument) if (compliance and compliance.edocument) else "N/A" - - patente_val = "" - if pedimento and pedimento.license: - patente_val = pedimento.license - elif 'broker' in locals() and broker and broker.license: - patente_val = broker.license - - - # --- Transport Data Fetching --- - transporte_txt = str(logistics.transport_type) if (logistics and logistics.transport_type) else "" - num_transporte_val = (logistics.trailer_num or "") if logistics else "" - - # Init values - placas_val = (logistics.license_plate or "") if logistics else "" # Placas Tracto - placas_remolque_val = "" - transportista_val = (logistics.carrier_id or "") if logistics else "" - caat_val = "" - scac_val = "" - licencia_cond_val = "" - conductor_nombre = "" - - # Block Logic (Clarion Style) for transportista_info - transport_lines = [] - - if logistics: - # 1. Transporter (CAAT / SCAC) - if logistics.carrier_int_id is not None: - transporter_obj = ( - db.query(Transporter) - .filter(Transporter.transporter_id == logistics.carrier_int_id) - .first() - ) - elif logistics.carrier_id: - transporter_obj = db.query(Transporter).filter( - Transporter.transporter_key == logistics.carrier_id - ).first() - if transporter_obj: - caat_val = transporter_obj.caat_code or "" - scac_val = transporter_obj.transport_code or "" # Mapping transport_code to SCAC - transportista_val = transporter_obj.name or logistics.carrier_id - - # Clarion Logic: Name first - # Line 1: Name - transport_lines.append(transporter_obj.name or "") - - # Line 2: Streets - if transporter_obj.streets: - transport_lines.append(transporter_obj.streets) - - # Line 3: City, State, Country - loc_line = "" - if transporter_obj.city: - loc_line = transporter_obj.city - if transporter_obj.state: - loc_line += f", {transporter_obj.state}, " - else: - loc_line += ", " - else: - if transporter_obj.state: - loc_line = f"{transporter_obj.state}," - - country_desc = transporter_obj.country or "" - if loc_line: - loc_line += f" {country_desc}" - elif country_desc: - loc_line = country_desc - - if loc_line.strip(", "): - transport_lines.append(loc_line) - - # 2. Vehicle (Placas Tracto) - Try transport_id first - if logistics.transport_id: - if logistics.transport_int_id is not None: - veh_obj = ( - db.query(Vehicle) - .filter(Vehicle.vehicle_id == logistics.transport_int_id) - .first() - ) - else: - veh_obj = db.query(Vehicle).filter( - Vehicle.vehicle_key == logistics.transport_id - ).first() - if veh_obj: - placas_val = veh_obj.plate_number or placas_val - elif logistics.vehicle_num: # Fallback to vehicle_num if populated and transport_id failed/empty - veh_obj = db.query(Vehicle).filter(Vehicle.vehicle_key == logistics.vehicle_num).first() - if veh_obj: - placas_val = veh_obj.plate_number or placas_val - - # 3. Trailer (Placas Remolque) - if logistics.trailer_num: - if logistics.trailer_int_id is not None: - trl_obj = ( - db.query(Trailer) - .filter(Trailer.trailer_id == logistics.trailer_int_id) - .first() - ) - else: - trl_obj = db.query(Trailer).filter( - Trailer.trailer_number == logistics.trailer_num - ).first() - if trl_obj: - placas_remolque_val = trl_obj.plate_number or "" - - # 4. Driver (License) - if logistics.carrier_id and logistics.driver_name: - conductor_nombre = logistics.driver_name - # Attempt to find driver by name + carrier - drv_obj = db.query(Driver).filter( - Driver.transporter_key == logistics.carrier_id, - Driver.driver_name == logistics.driver_name - ).first() - if drv_obj: - licencia_cond_val = drv_obj.license_number or "" - - # --- Building the rest of the block --- - - # Line 4: Driver - if conductor_nombre: - transport_lines.append(f"Driver/Conductor: {conductor_nombre}") - - # Line 5: Conveyance / Transporte - t_label = "Conveyance / Transporte" - t_val = placas_val # Default to Truck Plate - - if logistics.transport_type: - ttype = str(logistics.transport_type).lower() - if "caja" in ttype or "trailer" in ttype: - t_label = "Trailer / Caja" - t_val = placas_remolque_val or num_transporte_val - elif "placa" in ttype: - t_label = "Plates / Placas" - elif "camion" in ttype or "truck" in ttype: - t_label = "Truck / Camión" - - if t_val: - transport_lines.append(f"{t_label}: {t_val}") - - # Line 6: SCAC / CAAT - codes_line = "" - if scac_val: - codes_line = f"SCAC Code/Clave: {scac_val}" - if caat_val: - if codes_line: - codes_line += f", CAAT Code/Clave: {caat_val}" - else: - codes_line = f"CAAT Code/Clave: {caat_val}" - - if codes_line: - transport_lines.append(codes_line) - - # Join with newlines - transport_block_str = "\n".join([l for l in transport_lines if l]) - - factura_schema = FacturaSchema( - numero=header.invoice_number or "S/N", - fecha=str(header.invoice_date) if header.invoice_date else "", - tipo_cambio=float(financials.exchange_rate) if (financials and financials.exchange_rate) else (float(pedimento.exchange_rate) if pedimento and pedimento.exchange_rate else 1.0), - moneda=getattr(header, 'currency', "USD") or "USD", - incoterm=(logistics.incoterm or "") if logistics else "", - observaciones=header.observation_es or header.observation_en or "", - pedimento=f"{pedimento.year} {pedimento.customs_office[:2] if pedimento.customs_office else ''} {pedimento.license} {pedimento.pedimento_number}" if pedimento else "", - clave_pedimento=pedimento.pedimento_code if pedimento else "", - regimen=header.document_type or "", - patente=patente_val, - agente_aduanal=nombre_agente, - transporte=transporte_txt, - num_transporte=num_transporte_val, - placas=placas_val, - placas_remolque=placas_remolque_val, - transportista=transportista_val, - caat=caat_val, - scac=scac_val, - licencia_conductor=licencia_cond_val, - aduana=compliance.aduana if (compliance and compliance.aduana) else (pedimento.customs_office[:2] if (pedimento and pedimento.customs_office) else ""), - precinto=(logistics.seal_number or "") if logistics else "", - destino=(logistics.destination_goods or "") if logistics else "", - remesa=remesa_valor, acuse_electronico=acuse_valor, - representante_legal=getattr(company, 'responsible', "") or "", - nombre_empresa=getattr(company, 'name', "") or "", - transportista_info=transport_block_str - ) - - if progress_callback: progress_callback(50, "Procesando partidas...") - - # --- Fetch Lines from SINGLE Invoice (Requested Scope Change) --- - # User requested to ONLY report items from the specific selected invoice, - # NOT consolidating all invoices from the same Pedimento. - target_invoice_ids = [header.id] - - lines = db.query(LineItem).filter( - LineItem.invoice_id.in_(target_invoice_ids) - ).all() - - partidas_list = [] - - # --- AGGREGATION LOGIC (Refactoring based on Clarion) --- - from collections import defaultdict - # Key: (us_fraction_code, origin_country) - # Value: Object with accumulated fields - aggregated_data = defaultdict(lambda: { - "qty": 0.0, - "net_weight_kgs": 0.0, - "gross_weight_kgs": 0.0, - "total_value": 0.0, - "est_total_value": 0.0, - "description": "", - "advalorem_txt": "0%", - "unit_measure": "PZA", # Placeholder, takes first one found - "hts_code_print": "", - "part_number_display": "CONSOLIDADO" - }) - - # Pre-fetch US Tariff Fractions for efficiency if possible, or query inside loop (caching recommended) - # For simplicity in this step, we query inside or rely on Part data. - # Ideally fetch USTariffFraction from DB based on Part.us_fraction - - # --- Optimización: Cargar Facturas en Memoria --- - invoices_list = db.query(InvoiceHeader).filter(InvoiceHeader.id.in_(target_invoice_ids)).all() - invoice_map = {inv.id: inv for inv in invoices_list} - - from api.v1.modules.sitar.fracciones_usa.catalog_resolve import ( - american_fraction_ad_valorem_from_row, - resolve_american_fraction_from_sitar, - ) - - for line in lines: - qty = db.query(LineQuantity).filter(LineQuantity.item_line_id == line.id).first() - fin = db.query(LineFinancial).filter(LineFinancial.item_line_id == line.id).first() - part_master = db.query(Part).filter(Part.id == line.part_number_id).first() - - # --- Resolver Identificadores (MOVED INSIDE MAIN LOOP) --- - us_fraction_raw = "" - origin_final = "MEX" - - if part_master: - origin_final = part_master.fa_data.origin_country if (part_master.fa_data and part_master.fa_data.origin_country) else "MEX" - us_fraction_raw = part_master.us_fraction if part_master.us_fraction else "" - - # Key for aggregation - us_frac_clean = us_fraction_raw.strip() - agg_key = (us_frac_clean, origin_final) - # --- Weights & Qty --- - q_line = float(qty.quantity) if (qty and qty.quantity) else 0.0 - nw_line = float(qty.net_weight) if qty else 0.0 - gw_line = float(qty.gross_weight) if qty else 0.0 - - # --- Multi-Currency Normalization Logic --- - # Determine Line Currency context - # Use manual lookup instead of specific attribute - invoice_id = line.invoice_id - line_invoice = invoice_map.get(invoice_id) if invoice_id else None - - line_currency_is_mxn = False - line_exchange_rate = 1.0 - - if line_invoice and line_invoice.financials: - # Check explicit currency string AND code - curr_desc = str(line_invoice.financials.currency or "").upper() - curr_code = str(line_invoice.financials.currency_type or "").upper() - - # Logic: It is MXN if description says PESO/MX or code is MXN/MN - is_mx_desc = ("MX" in curr_desc or "PESO" in curr_desc) - is_mx_code = ("MXN" in curr_code or "MN" == curr_code) - - # But if code allows clarifying USD, prioritize that - is_usd_code = ("USD" in curr_code) - - if is_usd_code: - line_currency_is_mxn = False - elif is_mx_code or is_mx_desc: - line_currency_is_mxn = True - else: - line_currency_is_mxn = False # Default to Foreign/USD if unsure - - line_exchange_rate = float(line_invoice.financials.exchange_rate or 1.0) - - # Target Report Currency - report_is_mxn = (factura_schema.moneda == 'MXN') - - # DEBUG LOGGING - if line_invoice: - print(f"DEBUG: Line {line.id} - Inv {line_invoice.id} - CurrDesc: '{curr_desc}' Code: '{curr_code}' - Rate: {line_exchange_rate}") - print(f"DEBUG: Is MXN Context? {line_currency_is_mxn}. Report is MXN? {report_is_mxn}") - - # --- Get Financials for Line (Raw) --- - v_total_raw = 0.0 - v_unitario_raw = 0.0 - - if fin: - # NEW PRIORITY LOGIC (To avoid Inflation from dirty Customs Unit Cost) - # Priority 1: Use 'fin.value_usd' if it exists and > 0. - # Priority 2: Use 'fin.total_commercial_value' if it exists and > 0. - # Priority 3: Calculate using 'fin.unit_cost_commercial_usd' * 'q_line'. - # Priority 4: Only use 'fin.unit_cost_usd' * 'q_line' if commercial data is also missing. - - val_usd = float(fin.value_usd or 0.0) - total_comm = float(fin.total_commercial_value or 0.0) - unit_comm_usd = float(fin.unit_cost_commercial_usd or 0.0) - unit_usd = float(fin.unit_cost_usd or 0.0) - - # 1. Direct Total: Custom Value (Best case) - if val_usd > 0: - v_total_raw = val_usd - - # 2. Direct Total: Commercial Total - elif total_comm > 0: - # Convert if invoice currency is MXN - if line_currency_is_mxn and line_exchange_rate > 0: - v_total_raw = total_comm / line_exchange_rate - else: - v_total_raw = total_comm - - # 3. Calc from Commercial Unit Cost (Safe Fallback) - elif unit_comm_usd > 0 and q_line > 0: - v_total_raw = unit_comm_usd * q_line - - # 4. Calc from Customs Unit Cost (Unknown Risk - Last Resort) - elif unit_usd > 0 and q_line > 0: - v_total_raw = unit_usd * q_line - - else: - v_total_raw = 0.0 - - # NOTE: v_unitario_raw is left as 0.0 here. - # It will be calculated in the 'Calculation Gap Fill' block below: - # v_unitario_raw = v_total_raw / q_line - # This guarantees consistency and avoids the inflated unit cost record (198.00). - - # --- Calculation Gap Fill (Raw) --- - if q_line > 0: - if v_total_raw == 0 and v_unitario_raw > 0: - v_total_raw = v_unitario_raw * q_line - if v_unitario_raw == 0 and v_total_raw > 0: - v_unitario_raw = v_total_raw / q_line - - # --- Conversion to Report Currency (DISABLED TEMPORARILY) --- - # User confirms all are USD. Forcing direct sum to avoid logic errors in detection. - v_total_line = v_total_raw - v_unitario_line = v_unitario_raw - - # if report_is_mxn and not line_currency_is_mxn: - # # USD -> MXN - # v_total_line = v_total_raw * line_exchange_rate - # v_unitario_line = v_unitario_raw * line_exchange_rate - # elif not report_is_mxn and line_currency_is_mxn: - # # MXN -> USD - # if line_exchange_rate > 0: - # v_total_line = v_total_raw / line_exchange_rate - # v_unitario_line = v_unitario_raw / line_exchange_rate - # else: - # v_total_line = 0.0 - # v_unitario_line = 0.0 - - print(f"DEBUG: ValRaw: {v_total_raw} -> ValFinal: {v_total_line}") - - # --- Resolve Fraction Details (Description & Rate) --- - # Only if this is the first time we see this key (or overwrite, doesn't matter much as they should be same for same HTS) - # We check if we already have description set to avoid re-querying if we want optimization, - # but relying on DB query per distinct fraction is safer. - - current_agg = aggregated_data[agg_key] - - if not current_agg["description"]: - resolved = ( - resolve_american_fraction_from_sitar(us_frac_clean) - if us_frac_clean - else None - ) - if resolved: - sitar_row, _canon = resolved - current_agg["description"] = ( - (sitar_row.DESCRIPCION or "").strip() or "Sin Descripción" - ) - adv_val = american_fraction_ad_valorem_from_row(sitar_row) - current_agg["advalorem_txt"] = ( - f"{adv_val}%" if adv_val is not None else "0%" - ) - else: - current_agg["description"] = part_master.description_spanish if part_master else "S/D" - - current_agg["hts_code_print"] = us_frac_clean - current_agg["unit_measure"] = qty.weight_unit if qty else "KGS" # Default to first found - - # --- Calculate Estimated Tax for this Line --- - rate = 0.0 - try: - clean_adv = current_agg["advalorem_txt"].replace("%", "").strip() - rate = float(clean_adv) / 100.0 - except: rate = 0.0 - - v_est_line = v_total_line * rate - - # --- Accumulate --- - current_agg["qty"] += q_line - current_agg["net_weight_kgs"] += nw_line - current_agg["gross_weight_kgs"] += gw_line - current_agg["total_value"] += v_total_line - current_agg["est_total_value"] += v_est_line - - - # --- Convert Aggregated Data to Schema List --- - partidas_list = [] - - for (hts, origin), data in aggregated_data.items(): - - # Calculate Unit Price based on Total Value / Total Qty - unit_price = 0.0 - if data["qty"] > 0: - unit_price = data["total_value"] / data["qty"] - - partidas_list.append(PartidaSchema( - numero_parte="VARIOS", # Or empty - descripcion=data["description"], - fraccion=data["hts_code_print"], - origen=origin, - advalorem=data["advalorem_txt"], - preferencia="General", - cantidad_importacion=self.formatear_numero(data["qty"]), - unidad_medida=data["unit_measure"], - cantidad_bultos=0, # Summing bultos might be tricky if not homogeneous, check logic later - clave_bultos="", - peso_neto=self.formatear_numero(data["net_weight_kgs"]), - peso_bruto=self.formatear_numero(data["gross_weight_kgs"]), - valor_costo_unitario=self.formatear_numero(unit_price), - valor_total=self.formatear_numero(data["total_value"]), - valor_estimado=self.formatear_numero(data["est_total_value"]) - )) - - # Sort by Fraction (HTS Code) - partidas_list.sort(key=lambda x: x.fraccion) - - totales = self.calcular_totales(partidas_list, Decimal(factura_schema.tipo_cambio)) - - return FacturaImportacionCompleta( - cliente_proveedor=cliente_proveedor, cliente_vendido=cliente_vendido, - cliente_enviado=cliente_enviado, factura=factura_schema, - partidas=partidas_list, totales=totales - ) - - except Exception as e: - print(f"Error Service A76: {e}") - raise HTTPException(status_code=500, detail=f"Error: {str(e)}") - - def calcular_totales(self, partidas: List[PartidaSchema], tipo_cambio: Decimal) -> TotalesSchema: - cant = sum(p.cantidad_importacion for p in partidas) - valor = sum(p.valor_total for p in partidas) - peso_n = sum(p.peso_neto for p in partidas) - peso_b = sum(p.peso_bruto for p in partidas) - bultos = sum(p.cantidad_bultos for p in partidas) - claves = [p.clave_bultos for p in partidas if p.clave_bultos] - clave_comun = max(set(claves), key=claves.count) if claves else "" - if bultos > 1 and clave_comun and not clave_comun.endswith("S"): clave_comun += "S" - v_est = sum(p.valor_estimado for p in partidas if isinstance(p.valor_estimado, (int, float, Decimal))) - - tc = float(tipo_cambio) if tipo_cambio else 1.0 - return TotalesSchema( - cantidad_total=self.formatear_numero(cant), bultos_total=bultos, clave_bultos=clave_comun, - peso_neto_total=self.formatear_numero(peso_n), peso_bruto_total=self.formatear_numero(peso_b), - valor_total_total=self.formatear_numero(valor), valor_total_dolares=self.formatear_numero(valor / tc if tc > 0 else 0), - valor_estimado_total=self.formatear_numero(v_est) - ) - - def generar_factura_completa(self, db: Session, invoice_id: int, company_id: int, formato: str = "pdf", progress_callback: Optional[Callable] = None) -> Tuple[bytes, str, str]: - if progress_callback: progress_callback(5, "Iniciando servicio de reporte...") - datos = self.obtener_datos(db, invoice_id, company_id, progress_callback) - - if progress_callback: progress_callback(80, "Renderizando plantilla...") - - # LOGO LOGIC - logo_b64 = None - try: - # Fetch company to get logo path - - comp_logo = db.query(Company).filter(Company.id == company_id).first() - if comp_logo and comp_logo.logo: - p = Path(comp_logo.logo) - - # Logic robusta de búsqueda (igual que en routes.py) - target_path = p - if not target_path.exists(): - # Intentar en la ruta estándar: app_data/logos/{id}/{nombre} - # Esto cubre el caso donde solo se guardó el nombre del archivo o la ruta absoluta cambió - fallback = Path(f"app_data/logos/{company_id}") / p.name - if fallback.exists(): - target_path = fallback - - if target_path.exists(): - with open(target_path, "rb") as image_file: - encoded_string = base64.b64encode(image_file.read()).decode('utf-8') - # Detect MIME type loosely - mime = "image/png" - if target_path.suffix.lower() in ['.jpg', '.jpeg']: mime = "image/jpeg" - logo_b64 = f"data:{mime};base64,{encoded_string}" - except Exception as e: - print(f"Error loading logo: {e}") - - context = { - 'cliente_proveedor': datos.cliente_proveedor.model_dump(), 'cliente_vendido': datos.cliente_vendido.model_dump(), - 'cliente_enviado': datos.cliente_enviado.model_dump(), 'factura': datos.factura.model_dump(), - 'partidas': [p.model_dump() for p in datos.partidas], 'totales': datos.totales.model_dump(), - 'logo_b64': logo_b64 - } - html_content = self.template.render(**context) - nombre = f"Consolidado_{datos.factura.numero}.{formato}" - if formato == "html": return html_content.encode('utf-8'), nombre, "text/html" - - if progress_callback: progress_callback(90, "Generando PDF final...") - options = {'page-size': 'Letter', 'margin-top': '0.5in', 'margin-right': '0.5in', 'margin-bottom': '0.5in', 'margin-left': '0.5in', 'encoding': "UTF-8", 'enable-local-file-access': None} - pdf = pdfkit.from_string(html_content, False, options=options, configuration=self._get_wkhtmltopdf_config()) - - if progress_callback: progress_callback(100, "Completado") - return pdf, nombre, "application/pdf" diff --git a/backend/api/v1/modules/a76/reports/importacion/facturas/mex/schemas.py b/backend/api/v1/modules/a76/reports/importacion/facturas/mex/schemas.py deleted file mode 100644 index f40c2f54..00000000 --- a/backend/api/v1/modules/a76/reports/importacion/facturas/mex/schemas.py +++ /dev/null @@ -1,98 +0,0 @@ -from decimal import Decimal -from typing import List, Optional, Union -from pydantic import BaseModel, field_validator - - -class ClienteSchema(BaseModel): - header: str - nombre: str - # Ponemos valor por defecto "" y permitimos que sea opcional - direccion: Optional[str] = "" - num_exterior: Optional[str] = "" - num_interior: Optional[str] = "" - colonia: Optional[str] = "" - codigo_postal: Optional[str] = "" - ciudad: Optional[str] = "" - estado: Optional[str] = "" - pais: Optional[str] = "" - tax_id: Optional[str] = "" - programa: Optional[str] = "" - autorizacion: Optional[str] = "" - prosec: Optional[str] = "" - reg_emp: Optional[str] = "" - cert: Optional[str] = "" - - # Si llega un None, lo convertimos en "" automáticamente - @field_validator('direccion', 'nombre', 'tax_id', mode='before') - @classmethod - def prevent_none(cls, v): - return v or "" - -class FacturaSchema(BaseModel): - numero: str - titulo_documento: str = "Factura de Importacion" # Titulo dinámico basado en document_type - fecha: str - tipo_cambio: float - moneda: str - # Campos de aduanas (Opcionales por si A76 aún no los tiene) - pedimento: str = "" - clave_pedimento: str = "" - remesa: str = "" - acuse_electronico: str = "" - agente_aduanal: str = "" - patente: str = "" - precinto: str = "" - regimen: str = "" - transportista: str = "" - scac: str = "" - caat: str = "" - incoterm: str = "" - transporte: str = "" - num_transporte: str = "" - placas: str = "" - placas_remolque: str = "" - licencia_conductor: str = "" - caat: str = "" - scac: str = "" - aduana: str = "" - destino: str = "" - observaciones: str = "" - -class PartidaSchema(BaseModel): - numero_parte: str - descripcion: str - fraccion: str - origen: str - - advalorem:Optional[str] = "" - preferencia:Optional[str] = "" - - marca: Optional[str] = "" - modelo: Optional[str] = "" - series: List[str] = [] - - cantidad_importacion: Union[float, str] - unidad_medida: str - cantidad_bultos: int - clave_bultos: str - peso_neto: Union[float, str] - peso_bruto: Union[float, str] - valor_costo_unitario: Union[float, str] - valor_total: Union[float, str] - -class TotalesSchema(BaseModel): - cantidad_total: Union[float, str] - bultos_total: int - clave_bultos: str = "" - peso_neto_total: Union[float, str] - peso_bruto_total: Union[float, str] - valor_total_total: Union[float, str] - valor_total_dolares: Union[float, str] - -class FacturaImportacionCompleta(BaseModel): - cliente_proveedor: ClienteSchema - cliente_vendido: ClienteSchema - cliente_enviado: ClienteSchema - factura: FacturaSchema - partidas: List[PartidaSchema] - totales: TotalesSchema \ No newline at end of file diff --git a/backend/api/v1/modules/a76/reports/importacion/facturas/mex/service.py b/backend/api/v1/modules/a76/reports/importacion/facturas/mex/service.py deleted file mode 100644 index 4974dc46..00000000 --- a/backend/api/v1/modules/a76/reports/importacion/facturas/mex/service.py +++ /dev/null @@ -1,764 +0,0 @@ -import shutil -import base64 -import pdfkit -from pathlib import Path -from decimal import Decimal -from typing import Tuple, List, Callable, Optional - -from jinja2 import Environment, FileSystemLoader, select_autoescape -from fastapi import HTTPException -from sqlalchemy.orm import Session - -# --- MODELOS --- -from api.v1.modules.a76.invoices.models import InvoiceHeader, InvoiceLogistics -from api.v1.modules.a76.items.line_financials.models import LineFinancial -from api.v1.modules.a76.items.line_quantities.models import LineQuantity -from api.v1.modules.a76.clients_and_providers.models import ( - ClientProvider, - ClientProviderAddress, - ClientProviderPrograms, -) -from api.v1.modules.a76.parts.models import Part -from api.v1.modules.a76.pedmientos.models import Pedimentos -from api.v1.modules.a76.general_catalogs.company.models import Company -from api.v1.modules.a76.customs_brokers.models import CustomsBroker -from api.v1.modules.a76.items.models import LineItem -from api.v1.modules.a76.items.line_customs.models import LineCustom -from api.v1.modules.a76.items.line_descriptions.models import LineDescription -from api.v1.modules.a76.items.series.models import Serie - -# --- TRANSPORTATION MODELS --- -from api.v1.modules.a76.transportation.transporters.models import Transporter -from api.v1.modules.a76.transportation.vehicles.models import Vehicle -from api.v1.modules.a76.transportation.trailers.models import Trailer -from api.v1.modules.a76.transportation.drivers.models import Driver - -# --- MODELO DE FRACCIONES --- -from api.v1.modules.a76.general_catalogs.fractions.tariff_fractions.models import TariffFraction - -# --- MODELO DE UNIDADES DE MEDIDA --- -from api.v1.modules.a76.general_catalogs.units_of_measure.models import UnitOfMeasure -from api.v1.modules.a76.general_catalogs.packages.models import Package - -# --- SCHEMAS --- -from .schemas import ( - ClienteSchema, - PartidaSchema, - TotalesSchema, - FacturaSchema, - FacturaImportacionCompleta, -) - - -class FacturaImportacionMexService: - def __init__(self): - self.template_dir = Path(__file__).parent.parent / "templates" - self.jinja_env = Environment( - loader=FileSystemLoader(self.template_dir), - autoescape=select_autoescape(["html", "xml"]), - ) - self.template = self.jinja_env.get_template('factura_mex_ver.html') - - def _get_document_title(self, invoice_type: str, is_american: bool = False) -> str: - """ - Determina el título del documento basado en el tipo de factura. - - Args: - invoice_type: Tipo de factura (TEM, DEF, MEX, CR) - is_american: Si es factura americana (True) o mexicana (False) - - Returns: - Título formateado para la factura - """ - # Mapeo para facturas mexicanas - mexican_titles = { - "MEX": "Factura Importación Compras Mexicanas", - "DEF": "Importación Definitiva", - "TEM": "Importación Temporal", - "CR": "Importación de Cambio de Régimen", - } - - # Mapeo para facturas americanas - american_titles = { - "MEX": "Mexican Purchases Import Invoice", - "DEF": "Definitive Importation", - "TEM": "Temporary Importation", - "CR": "Regime Change Importation", - } - - # Seleccionar el mapa correcto - titles = american_titles if is_american else mexican_titles - - # Obtener el título (normalizar a mayúsculas) - invoice_type_upper = invoice_type.upper() if invoice_type else "" - title = titles.get(invoice_type_upper, "") - - # Fallback a genéricos si no se encuentra - if not title: - return "Commercial Invoice" if is_american else "Factura de Importación" - - return title - - def _get_wkhtmltopdf_config(self): - path = shutil.which("wkhtmltopdf") or "/usr/local/bin/wkhtmltopdf" or "/usr/bin/wkhtmltopdf" - if not Path(path).exists(): - raise RuntimeError("wkhtmltopdf no encontrado.") - return pdfkit.configuration(wkhtmltopdf=path) - - def formatear_numero(self, valor, decimales: int = 2): - """ - Formatea un número con separadores de miles y decimales especificados. - Retorna una cadena formateada para mostrar en reportes. - """ - if valor is None: - valor = 0.0 - try: - num = round(float(valor), decimales) - # Formatear con separadores de miles y decimales - return f"{num:,.{decimales}f}" - except: - return f"0.{'0' * decimales}" - - def _format_fraccion_fallback(self, fraccion_raw: str) -> str: - if not fraccion_raw or len(fraccion_raw) < 8: - return fraccion_raw - return f"{fraccion_raw[:4]}.{fraccion_raw[4:6]}.{fraccion_raw[6:]}" - - def _obtener_datos_cliente( - self, db: Session, client_id: int, rol: str - ) -> ClienteSchema: - main = db.query(ClientProvider).filter(ClientProvider.id == client_id).first() - if not main: - return ClienteSchema( - header=rol, - nombre="Desconocido", - direccion="", - tax_id="", - codigo_postal="", - ciudad="", - estado="", - pais="MEX", - ) - - addr = ( - db.query(ClientProviderAddress) - .filter(ClientProviderAddress.client_id == client_id) - .first() - ) - prog = ( - db.query(ClientProviderPrograms) - .filter(ClientProviderPrograms.client_id == client_id) - .first() - ) - - return ClienteSchema( - header=rol, - nombre=(main.name or main.short_name) or "S/N", - direccion=(addr.streets or "") if addr else "", - num_exterior=(addr.exterior_number or "") if addr else "", - num_interior=(addr.interior_number or "") if addr else "", - colonia=(addr.neighborhood or "") if addr else "", - codigo_postal=(addr.postal_code or "") if addr else "", - ciudad=(addr.city or "") if addr else "", - estado=(addr.state or "") if addr else "", - pais=(addr.country or "MEX") if addr else "MEX", - tax_id=getattr(main, "fiscal_id", "") or "", - programa="IMMEX" if (prog and prog.program) else "", - autorizacion=prog.program_number if prog else "", - prosec=( - prog.prosec_authorization - if (prog and prog.prosec and prog.prosec_authorization) - else "" - ), - reg_emp=( - prog.val_certified_company_registry - if (prog and hasattr(prog, "val_certified_company_registry")) - else ( - prog.certified_company_registry - if (prog and prog.certified_company_registry) - else "" - ) - ), - cert=( - prog.is_certified_company - if (prog and prog.is_certified_company) - else "" - ), - ) - - def obtener_datos(self, db: Session, invoice_id: int, company_id: int, progress_callback: Optional[Callable] = None, currency_code: str = 'ORIGINAL') -> FacturaImportacionCompleta: - try: - if progress_callback: - progress_callback(10, "Buscando factura...") - header = ( - db.query(InvoiceHeader) - .filter( - InvoiceHeader.id == invoice_id, - InvoiceHeader.company_id == company_id, - ) - .first() - ) - if not header: - raise HTTPException(status_code=404, detail="Factura no encontrada") - - compliance = header.compliance_mx - logistics = header.logistics if header.logistics else None - financials = header.financials if header.financials else None - if progress_callback: - progress_callback(20, "Obteniendo datos de pedimento...") - pedimento_id = ( - compliance.pedimento_id - if (compliance and compliance.pedimento_id) - else header.related_doc_id - ) - pedimento = ( - db.query(Pedimentos).filter(Pedimentos.id == pedimento_id).first() - if pedimento_id - else None - ) - - if progress_callback: - progress_callback(30, "Obteniendo cliente y proveedor...") - proveedor_id = compliance.provider_id if compliance else None - cliente_proveedor = ( - self._obtener_datos_cliente(db, proveedor_id, "Proveedor / supplier:") - if proveedor_id - else ClienteSchema( - header="Proveedor", - nombre="No Asignado", - direccion="", - tax_id="", - codigo_postal="", - ciudad="", - estado="", - pais="", - ) - ) - - nombre_agente = "" - if compliance and compliance.customs_broker_id: - broker = ( - db.query(CustomsBroker) - .filter(CustomsBroker.id == compliance.customs_broker_id) - .first() - ) - if broker: - nombre_agente = broker.name - - company = db.query(Company).filter(Company.id == header.company_id).first() - # Datos Default (Company/Importer) - Used for fallback or Right Side (Enviado A) - cliente_default = ClienteSchema( - header="Importador / consignatario:", - nombre=getattr(company, "name", "Empresa Local"), - direccion="DOMICILIO FISCAL", - num_exterior="", - colonia="", - codigo_postal="", - ciudad="", - estado="", - pais="MEX", - tax_id=getattr(company, "rfc", ""), - programa=getattr(company, "program", "IMMEX"), - autorizacion=getattr(company, "program_number", ""), - ) - - # Left Side Logic (Consignatario / Sold To) - cliente_vendido = cliente_default - if compliance and compliance.sold_to_id: - raw_header = compliance.sold_to_header or "CONSIGNATARIO" - clean_header = raw_header.replace("_", " ").capitalize() + ":" - cliente_vendido = self._obtener_datos_cliente( - db, compliance.sold_to_id, clean_header - ) - - # Right Side Logic (Enviado A / Shipped To) - cliente_enviado = cliente_default - if compliance and compliance.shipped_to_id: - # Clean header: "enviado_a" -> "Enviado a:" - raw_header_shipped = compliance.shipped_to_header or "DESTINATARIO" - clean_header_shipped = ( - raw_header_shipped.replace("_", " ").capitalize() + ":" - ) - - # Fetch client data - cliente_enviado = self._obtener_datos_cliente( - db, compliance.shipped_to_id, clean_header_shipped - ) - - remesa_valor = ( - str(compliance.remesa) if (compliance and compliance.remesa) else "" - ) - acuse_valor = ( - str(compliance.edocument) - if (compliance and compliance.edocument) - else "N/A" - ) - - patente_val = "" - if pedimento and pedimento.license: - patente_val = pedimento.license - elif "broker" in locals() and broker and broker.license: - patente_val = broker.license - - # --- Transport Data Fetching --- - transporte_txt = ( - str(logistics.transport_type) - if (logistics and logistics.transport_type) - else "" - ) - num_transporte_val = (logistics.trailer_num or "") if logistics else "" - - # Init values - placas_val = ( - (logistics.license_plate or "") if logistics else "" - ) # Placas Tracto - placas_remolque_val = "" - transportista_val = (logistics.carrier_id or "") if logistics else "" - caat_val = "" - scac_val = "" - licencia_cond_val = "" - - if logistics: - # 1. Transporter (CAAT / SCAC) - if logistics.carrier_int_id is not None: - transporter_obj = ( - db.query(Transporter) - .filter(Transporter.transporter_id == logistics.carrier_int_id) - .first() - ) - if transporter_obj: - caat_val = transporter_obj.caat_code or "" - scac_val = transporter_obj.transport_code or "" - transportista_val = transporter_obj.name or logistics.carrier_id - elif logistics.carrier_id: - transporter_obj = ( - db.query(Transporter) - .filter(Transporter.transporter_key == logistics.carrier_id) - .first() - ) - if transporter_obj: - caat_val = transporter_obj.caat_code or "" - scac_val = ( - transporter_obj.transport_code or "" - ) # Mapping transport_code to SCAC - transportista_val = transporter_obj.name or logistics.carrier_id - - # 2. Vehicle (Placas Tracto) - Try transport_id first - if logistics.transport_id: - if logistics.transport_int_id is not None: - veh_obj = ( - db.query(Vehicle) - .filter(Vehicle.vehicle_id == logistics.transport_int_id) - .first() - ) - else: - veh_obj = ( - db.query(Vehicle) - .filter(Vehicle.vehicle_key == logistics.transport_id) - .first() - ) - if veh_obj: - placas_val = veh_obj.plate_number or placas_val - elif ( - logistics.vehicle_num - ): # Fallback to vehicle_num if populated and transport_id failed/empty - veh_obj = ( - db.query(Vehicle) - .filter(Vehicle.vehicle_key == logistics.vehicle_num) - .first() - ) - if veh_obj: - placas_val = veh_obj.plate_number or placas_val - - # 3. Trailer (Placas Remolque) - if logistics.trailer_num: - if logistics.trailer_int_id is not None: - trl_obj = ( - db.query(Trailer) - .filter(Trailer.trailer_id == logistics.trailer_int_id) - .first() - ) - else: - trl_obj = ( - db.query(Trailer) - .filter(Trailer.trailer_number == logistics.trailer_num) - .first() - ) - if trl_obj: - placas_remolque_val = trl_obj.plate_number or "" - - # 4. Driver (License) - if logistics.carrier_id and logistics.driver_name: - # Attempt to find driver by name + carrier - drv_obj = ( - db.query(Driver) - .filter( - Driver.transporter_key == logistics.carrier_id, - Driver.driver_name == logistics.driver_name, - ) - .first() - ) - if drv_obj: - licencia_cond_val = drv_obj.license_number or "" - - # Determine Currency - moneda_final = getattr(header, 'currency', "USD") or "USD" - if currency_code == 'MXN': - moneda_final = 'MXN' - elif currency_code == 'USD': - moneda_final = 'USD' - - factura_schema = FacturaSchema( - numero=header.invoice_number or "S/N", - titulo_documento=self._get_document_title(header.invoice_type or "", is_american=False), - fecha=str(header.invoice_date) if header.invoice_date else "", - tipo_cambio=float(financials.exchange_rate) if (financials and financials.exchange_rate) else (float(pedimento.exchange_rate) if pedimento and pedimento.exchange_rate else 1.0), - moneda=moneda_final, - incoterm=(logistics.incoterm or "") if logistics else "", - observaciones=header.observation_es or header.observation_en or "", - pedimento=( - f"{pedimento.year} {pedimento.customs_office[:2] if pedimento.customs_office else ''} {pedimento.license} {pedimento.pedimento_number}" - if pedimento - else "" - ), - clave_pedimento=pedimento.pedimento_code if pedimento else "", - regimen=header.document_type or "", - patente=patente_val, - agente_aduanal=nombre_agente, - transporte=transporte_txt, - num_transporte=num_transporte_val, - placas=placas_val, - placas_remolque=placas_remolque_val, - transportista=transportista_val, - caat=caat_val, - scac=scac_val, - licencia_conductor=licencia_cond_val, - aduana=( - compliance.aduana - if (compliance and compliance.aduana) - else ( - pedimento.customs_office[:2] - if (pedimento and pedimento.customs_office) - else "" - ) - ), - precinto=(logistics.seal_number or "") if logistics else "", - destino=(logistics.destination_goods or "") if logistics else "", - remesa=remesa_valor, - acuse_electronico=acuse_valor, - ) - - if progress_callback: - progress_callback(50, "Procesando partidas...") - lines = ( - db.query(LineItem) - .filter(LineItem.invoice_id == header.id) - .all() - ) - partidas_list = [] - - # Initialize raw totals - raw_cant = 0.0 - raw_valor = 0.0 - raw_peso_n = 0.0 - raw_peso_b = 0.0 - raw_bultos = 0 - - for line in lines: - qty = ( - db.query(LineQuantity) - .filter(LineQuantity.item_line_id == line.id) - .first() - ) - fin = ( - db.query(LineFinancial) - .filter(LineFinancial.item_line_id == line.id) - .first() - ) - customs = ( - db.query(LineCustom) - .filter(LineCustom.item_line_id == line.id) - .first() - ) - desc_obj = ( - db.query(LineDescription) - .filter(LineDescription.item_line_id == line.id) - .first() - ) - series_objs = ( - db.query(Serie) - .filter(Serie.line_item_id == line.id) - .all() - ) - part_master = db.query(Part).filter(Part.id == line.part_number_id).first() - - desc_final = "S/D" - num_parte_final = str(line.part_number_id or "S/N") - fraccion_raw = "" - origen_final = "MEX" - - if part_master: - desc_final = ( - part_master.description_spanish - or part_master.description_english - or "Sin Desc." - ) - num_parte_final = part_master.part_number - fraccion_raw = part_master.fraction if part_master.fraction else "" - - # Fetch Origin from Master Catalog (FaPart) - if part_master.fa_data and part_master.fa_data.origin_country: - origen_final = part_master.fa_data.origin_country - - # Prioritize description from LineDescription (specific to this invoice) - if desc_obj: - desc_final = desc_obj.description_spanish or desc_obj.description_english or desc_final - - fraccion_limpia = fraccion_raw.replace(".", "").strip() - if fraccion_limpia: - fraccion_limpia = fraccion_limpia[:8].zfill(8) - - # Consultar tabla tariff_fractions - fraccion_db = ( - db.query(TariffFraction) - .filter(TariffFraction.code == fraccion_limpia) - .first() - ) - - preferencia_txt = "General" - advalorem_txt = "0%" - - # 1. Prioridad: Datos específicos de la partida (LineCustom) - if customs: - if customs.fraction_type: - preferencia_txt = str(customs.fraction_type).upper() - - if customs.advalorem: - adv_val = customs.advalorem.strip() - if adv_val not in ["0", "0.0", "0.00", ""]: - advalorem_txt = adv_val if "%" in adv_val else f"{adv_val}%" - - # 2. Fallback: Datos de la fracción en catálogo (TariffFraction) si no hay en la partida - elif fraccion_db: - adv_db = fraccion_db.adv_impo - if adv_db and adv_db.strip() not in ["0", "0.0", "0.00", ""]: - advalorem_txt = adv_db if "%" in adv_db else f"{adv_db}%" - else: - advalorem_txt = "0%" - - # 3. Formatear Fracción para imprimir - if fraccion_db: - fraccion_imprimir = fraccion_db.fraction or fraccion_raw - else: - fraccion_imprimir = self._format_fraccion_fallback(fraccion_limpia) - - # 4. Datos de Marca, Modelo y Series - marca_val = desc_obj.brand if desc_obj else "" - modelo_val = desc_obj.model if desc_obj else "" - series_list = [s.serial_numbers for s in series_objs if s.serial_numbers] - - # Logic to determine values - Prioritize Specific Currency Columns - v_unitario = 0.0 - v_total = 0.0 - - if fin: - is_mxn = factura_schema.moneda == "MXN" - - # 1. Try Specific Currency Columns First - if is_mxn: - v_unitario = float(fin.unit_cost_commercial_mxn or 0.0) - v_total = float(fin.value_commercial_mxn or 0.0) - else: - v_unitario = float(fin.unit_cost_commercial_usd or 0.0) - v_total = float(fin.value_commercial_usd or 0.0) - - # 2. Fallback to Generic independently if Specific is 0 - if not v_unitario: - v_unitario = float(fin.commercial_unit_cost or 0.0) - - if not v_total: - v_total = float(fin.total_commercial_value or 0.0) - - # 3. Calculate from Quantity if still missing - cantidad = float(qty.quantity) if (qty and qty.quantity) else 0.0 - - if cantidad > 0: - if v_unitario > 0 and v_total == 0: - v_total = v_unitario * cantidad - elif v_total > 0 and v_unitario == 0: - v_unitario = v_total / cantidad - - # Accumulate raw totals - raw_cant += float(qty.quantity) if qty and qty.quantity else 0.0 - raw_valor += v_total - raw_peso_n += float(qty.net_weight) if qty and qty.net_weight else 0.0 - raw_peso_b += float(qty.gross_weight) if qty and qty.gross_weight else 0.0 - raw_bultos += int(qty.package_quantity) if qty and qty.package_quantity else 0 - - # Obtener descripción de la unidad de medida desde la tabla a76.item_lines - unidad_desc = "" - if line.unit_of_measure: - uom = ( - db.query(UnitOfMeasure) - .filter( - UnitOfMeasure.id == line.unit_of_measure, - UnitOfMeasure.company_id == company_id, - ) - .first() - ) - if uom: - unidad_desc = uom.description or uom.code - else: - unidad_desc = "" - - partidas_list.append( - PartidaSchema( - numero_parte=num_parte_final, - descripcion=desc_final, - fraccion=fraccion_imprimir, - origen=origen_final, - advalorem=advalorem_txt, - preferencia=preferencia_txt, - marca=marca_val, - modelo=modelo_val, - series=series_list, - cantidad_importacion=self.formatear_numero( - qty.quantity if qty else 0 - ), - unidad_medida=unidad_desc, - cantidad_bultos=( - int(qty.package_quantity) - if qty and qty.package_quantity - else 0 - ), - clave_bultos=(qty.package_info.key if (qty and qty.package_info) else ""), - peso_neto=self.formatear_numero(qty.net_weight if qty else 0), - peso_bruto=self.formatear_numero( - qty.gross_weight if qty else 0 - ), - valor_costo_unitario=self.formatear_numero(v_unitario), - valor_total=self.formatear_numero(v_total), - ) - ) - - totales = self.calcular_totales( - partidas_list, - Decimal(factura_schema.tipo_cambio), - raw_cant=raw_cant, - raw_valor=raw_valor, - raw_peso_n=raw_peso_n, - raw_peso_b=raw_peso_b, - raw_bultos=raw_bultos - ) - - return FacturaImportacionCompleta( - cliente_proveedor=cliente_proveedor, - cliente_vendido=cliente_vendido, - cliente_enviado=cliente_enviado, - factura=factura_schema, - partidas=partidas_list, - totales=totales, - ) - - except Exception as e: - print(f"Error Service A76: {e}") - raise HTTPException(status_code=500, detail=f"Error: {str(e)}") - - def calcular_totales( - self, - partidas: List[PartidaSchema], - tipo_cambio: Decimal, - raw_cant: float = 0, - raw_valor: float = 0, - raw_peso_n: float = 0, - raw_peso_b: float = 0, - raw_bultos: int = 0 - ) -> TotalesSchema: - # Use raw values passed from obtener_datos to avoid TypeError with formatted strings - claves = [p.clave_bultos for p in partidas if p.clave_bultos] - clave_comun = max(set(claves), key=claves.count) if claves else "" - if raw_bultos > 1 and clave_comun and not clave_comun.endswith("S"): - clave_comun += "S" - tc = float(tipo_cambio) if tipo_cambio else 1.0 - return TotalesSchema( - cantidad_total=self.formatear_numero(raw_cant), - bultos_total=raw_bultos, - clave_bultos=clave_comun, - peso_neto_total=self.formatear_numero(raw_peso_n), - peso_bruto_total=self.formatear_numero(raw_peso_b), - valor_total_total=self.formatear_numero(raw_valor), - valor_total_dolares=self.formatear_numero(raw_valor / tc if tc > 0 else 0), - ) - - def generar_factura_completa(self, db: Session, invoice_id: int, company_id: int, formato: str = "pdf", progress_callback: Optional[Callable] = None, currency_code: str = 'ORIGINAL') -> Tuple[bytes, str, str]: - if progress_callback: progress_callback(5, "Iniciando servicio de reporte...") - datos = self.obtener_datos(db, invoice_id, company_id, progress_callback, currency_code) - - if progress_callback: progress_callback(80, "Renderizando plantilla...") - - # LOGO LOGIC - logo_b64 = None - try: - # Fetch company to get logo path - - comp_logo = db.query(Company).filter(Company.id == company_id).first() - if comp_logo and comp_logo.logo: - p = Path(comp_logo.logo) - - # Logic robusta de búsqueda (igual que en routes.py) - target_path = p - if not target_path.exists(): - # Intentar en la ruta estándar: app_data/logos/{id}/{nombre} - # Esto cubre el caso donde solo se guardó el nombre del archivo o la ruta absoluta cambió - fallback = Path(f"app_data/logos/{company_id}") / p.name - if fallback.exists(): - target_path = fallback - - if target_path.exists(): - with open(target_path, "rb") as image_file: - encoded_string = base64.b64encode(image_file.read()).decode( - "utf-8" - ) - # Detect MIME type loosely - mime = "image/png" - if target_path.suffix.lower() in [".jpg", ".jpeg"]: - mime = "image/jpeg" - logo_b64 = f"data:{mime};base64,{encoded_string}" - except Exception as e: - print(f"Error loading logo: {e}") - - context = { - "cliente_proveedor": datos.cliente_proveedor.model_dump(), - "cliente_vendido": datos.cliente_vendido.model_dump(), - "cliente_enviado": datos.cliente_enviado.model_dump(), - "factura": datos.factura.model_dump(), - "partidas": [p.model_dump() for p in datos.partidas], - "totales": datos.totales.model_dump(), - "logo_b64": logo_b64, - } - html_content = self.template.render(**context) - nombre = f"Factura_{datos.factura.numero}.{formato}" - if formato == "html": - return html_content.encode("utf-8"), nombre, "text/html" - - if progress_callback: - progress_callback(90, "Generando PDF final...") - options = { - "page-size": "Letter", - "margin-top": "0.5in", - "margin-right": "0.5in", - "margin-bottom": "0.5in", - "margin-left": "0.5in", - "encoding": "UTF-8", - "enable-local-file-access": None, - } - pdf = pdfkit.from_string( - html_content, - False, - options=options, - configuration=self._get_wkhtmltopdf_config(), - ) - - if progress_callback: - progress_callback(100, "Completado") - return pdf, nombre, "application/pdf" diff --git a/backend/api/v1/modules/a76/reports/importacion/facturas/routes.py b/backend/api/v1/modules/a76/reports/importacion/facturas/routes.py deleted file mode 100644 index b602b15c..00000000 --- a/backend/api/v1/modules/a76/reports/importacion/facturas/routes.py +++ /dev/null @@ -1,61 +0,0 @@ -from enum import Enum -from typing import Dict, Any -from fastapi import APIRouter, Depends, Query, Response, HTTPException -from sqlalchemy.orm import Session -from celery.result import AsyncResult -from core.celery_app import celery_app -from core.database import get_core_db -from core.security import get_current_user, validate_access_to_resource -from api.v1.modules.core.tasks_tracking import track_and_dispatch -from .mex.service import FacturaImportacionMexService -from .task import generar_pdf_factura_async - -router = APIRouter() -servicio_mex = FacturaImportacionMexService() - -@router.get("/tasks/{task_id}") -async def get_task_status( - task_id: str, - current_user: Dict[str, Any] = Depends(get_current_user), - db: Session = Depends(get_core_db) -): - task_result = AsyncResult(task_id, app=celery_app) - - response = { - "task_id": task_id, - "state": task_result.state, - "result": None, - "info": None - } - - if task_result.state == 'FAILURE': - response["result"] = str(task_result.result) - elif task_result.state == 'SUCCESS': - response["result"] = task_result.result - elif task_result.state == 'PROCESSING': - response["info"] = task_result.info - - return response - -@router.post("/{invoice_id}/download-async") -async def trigger_descarga_factura( - invoice_id: int, - company_id: int = Query(..., description="ID de la empresa"), - invoice_type: str = Query('mexican', description="Tipo de factura: 'mexican' o 'american'"), - currency_code: str = Query('ORIGINAL', description="Moneda: 'MXN', 'USD', o 'ORIGINAL'"), - current_user: Dict[str, Any] = Depends(get_current_user), - db: Session = Depends(get_core_db) -): - tenant_id = validate_access_to_resource(db, company_id, current_user) - task = track_and_dispatch( - db=db, - task=generar_pdf_factura_async, - tenant_id=tenant_id, - company_id=company_id, - requested_by_user=current_user.get("preferred_username") or current_user.get("email") or current_user.get("sub"), - task_name="generar_pdf_factura_async", - task_group="reports", - task_origin="a76/reports/importacion/facturas", - args=[invoice_id, company_id, invoice_type, currency_code], - ) - return {"task_id": task.id, "message": "Generación iniciada"} \ No newline at end of file diff --git a/backend/api/v1/modules/a76/reports/importacion/facturas/task.py b/backend/api/v1/modules/a76/reports/importacion/facturas/task.py deleted file mode 100644 index fabd082f..00000000 --- a/backend/api/v1/modules/a76/reports/importacion/facturas/task.py +++ /dev/null @@ -1,53 +0,0 @@ -import base64 -import logging -from core.celery_app import celery_app -from celery import current_task, states -from core.database import CoreSessionLocal - -from .mex.service import FacturaImportacionMexService -from .usa.service import FacturaImportacionUsaService - -logger = logging.getLogger(__name__) - -@celery_app.task(name="generar_pdf_factura_async", bind=True) -def generar_pdf_factura_async(self, invoice_id: int, company_id: int, invoice_type: str = 'mexican', currency_code: str = 'ORIGINAL'): - - db = CoreSessionLocal() - try: - logger.info(f"Worker procesando factura {invoice_id} ({invoice_type}, {currency_code})...") - - if invoice_type == 'american': - service = FacturaImportacionUsaService() - else: - service = FacturaImportacionMexService() - - self.update_state(state='PROCESSING', meta={'current': 5, 'total': 100, 'status': 'Iniciando generación...'}) - - def progress_callback(progress: int, status: str): - self.update_state(state='PROCESSING', meta={'current': progress, 'total': 100, 'status': status}) - - pdf_bytes, nombre, media_type = service.generar_factura_completa( - db=db, - invoice_id=invoice_id, - company_id=company_id, - progress_callback=progress_callback, - currency_code=currency_code - ) - - - pdf_base64 = base64.b64encode(pdf_bytes).decode('utf-8') - - return { - "status": "success", - "file_name": nombre, - "content": pdf_base64, - "media_type": media_type - } - - except Exception as e: - logger.error(f"Error en Celery Worker: {str(e)}") - return {"status": "error", "message": str(e)} - - finally: - - db.close() \ No newline at end of file diff --git a/backend/api/v1/modules/a76/reports/importacion/facturas/templates/factura_mex_ver.html b/backend/api/v1/modules/a76/reports/importacion/facturas/templates/factura_mex_ver.html deleted file mode 100644 index d78932fe..00000000 --- a/backend/api/v1/modules/a76/reports/importacion/facturas/templates/factura_mex_ver.html +++ /dev/null @@ -1,647 +0,0 @@ - - - - - - Factura Importacion Mexicana - {{ factura.numero }} - - - - -
-
-
-

{{ factura.titulo_documento }}

-
-

-
-
-

-


-
-
-
-
- {% if logo_b64 %} -
- -
- {% endif %} -
-

{{ cliente_proveedor.header }}

-

{{ cliente_proveedor.nombre }}

-

{{ cliente_proveedor.direccion }} - {% if cliente_proveedor.num_exterior %} Ext: {{ cliente_proveedor.num_exterior }}{% endif %} - {% if cliente_proveedor.num_interior %} Int: {{ cliente_proveedor.num_interior }}{% endif %} -

-

{{ cliente_proveedor.colonia }} {% if cliente_proveedor.codigo_postal %} CP: {{ - cliente_proveedor.codigo_postal }}{% endif %}

-

{{ cliente_proveedor.ciudad }}, {{ cliente_proveedor.estado }}, {{ cliente_proveedor.pais }}

-

TAX ID: {{ cliente_proveedor.tax_id }} - {% if cliente_proveedor.programa and cliente_proveedor.programa != 'Ninguno' %} - {{ cliente_proveedor.programa }}: {{ cliente_proveedor.autorizacion }} - {% endif %} -

-


-
-
- -
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
-

FACTURA:

-
-

{{ factura.numero }}

-
-

Fecha:

-
-

{{ factura.fecha }}

-
-

T. Cambio:

-
-

{{ factura.tipo_cambio }}

-
-

Pedimento:

-
-

{{ factura.pedimento }}

-
-

Clave:

-
-

{{ factura.clave_pedimento }}

-
-

Remesa:

-
-

{{ factura.remesa }}

-
-

Acuse:

-
-

{{ factura.acuse_electronico or 'N/A' }}

-
-

Agente Aduanal:

-

{{ factura.agente_aduanal or '' }}

-
-

Patente: {{ factura.patente or '' }}

-
- Regimen:{{ - factura.regimen or '' }} - -

INCOTERM:

-

{{ factura.incoterm or '' }}

-
- {% if factura.precinto %} -

Precinto: {{ factura.precinto }}

- {% endif %} -
-

Aduana: {{ factura.aduana }}

-
- {% if factura.destino %} -

Destino: {{ factura.destino }}

- {% endif %} -
-
-
- -
-
-

{{ cliente_vendido.header }}

-

{{ cliente_vendido.nombre }}

-

{{ cliente_vendido.direccion }} - {% if cliente_vendido.num_exterior %} Ext: {{ cliente_vendido.num_exterior }}{% endif %} - {% if cliente_vendido.num_interior %} Int: {{ cliente_vendido.num_interior }}{% endif %} -

-

{{ cliente_vendido.colonia }} {% if cliente_vendido.codigo_postal %} CP: {{ - cliente_vendido.codigo_postal }}{% endif %}

-

{{ cliente_vendido.ciudad }}, {{ cliente_vendido.estado }}, {{ cliente_vendido.pais }} -

-

RFC: {{ cliente_vendido.tax_id }} - {% if cliente_vendido.programa and cliente_vendido.programa != 'Ninguno' %} - {{ cliente_vendido.programa }}: {{ cliente_vendido.autorizacion }} - {% endif %} -

-

- {% if cliente_vendido.prosec %}PROSEC: {{ cliente_vendido.prosec }} {% endif %} - {% if cliente_vendido.reg_emp %}REG EMP: {{ cliente_vendido.reg_emp }} {% endif %} - {% if cliente_vendido.cert %}CERT: {{ cliente_vendido.cert }}{% endif %} -

-
- -
-

{{ cliente_enviado.header }}

-

{{ cliente_enviado.nombre }}

-

{{ cliente_enviado.direccion }} - {% if cliente_enviado.num_exterior %} Ext: {{ cliente_enviado.num_exterior }}{% endif %} - {% if cliente_enviado.num_interior %} Int: {{ cliente_enviado.num_interior }}{% endif %} -

-

{{ cliente_enviado.colonia }} {% if cliente_enviado.codigo_postal %} CP: {{ - cliente_enviado.codigo_postal }}{% endif %}

-

{{ cliente_enviado.ciudad }}, {{ cliente_enviado.estado }}, {{ cliente_enviado.pais }} -

-

RFC: {{ cliente_enviado.tax_id }} - {% if cliente_enviado.programa and cliente_enviado.programa != 'Ninguno' %} - {{ cliente_enviado.programa }}: {{ cliente_enviado.autorizacion }} - {% endif %} -

-

- {% if cliente_enviado.prosec %}PROSEC: {{ cliente_enviado.prosec }} {% endif %} - {% if cliente_enviado.reg_emp %}REG EMP: {{ cliente_enviado.reg_emp }} {% endif %} - {% if cliente_enviado.cert %}CERT: {{ cliente_enviado.cert }}{% endif %} -

-
-
-


-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {% for partida in partidas %} - - - - - - - - - - - - {% endfor %} - - - - - - - - - - - - - - - - - - - - - -
-

Transportista:

-
-

{{ factura.transportista }}

-
-

SCAC: {{ factura.scac }}

-
-

INCOTERM:

-
-

{{ factura.incoterm }}

-
-

Aduana: {{ factura.aduana }} / Ped: {{ factura.pedimento }}

-
-


-
-

Transporte:

-
-

{{ factura.transporte }}: {{ factura.num_transporte }}

-
-

CAAT: {{ factura.caat }}

-
-

Placas: {{ factura.placas or '' }} / Rem: {{ factura.placas_remolque or - '' }}

-
-

Chofer/Licencia:

-
-

{{ factura.licencia_conductor or 'N/A' }}

-
-

Línea

-
-

Descripción de la Mercancía

-
-

Comercial

-
-

Empaque

-
-

Peso (KGS)

-
-

Valores

-
-

Cantidad

-
-

U.M.

-
-

Tipo

-
-

Neto

-
-

Bruto

-
-

Unitario

-
-

Total

-
-

{{ loop.index }}

-
-

- {{ partida.descripcion }} / Origen: {{ partida.origen or 'MEX' }} / Fracción: {{ partida.fraccion }} / Preferencia: {{ partida.preferencia }} / Advalorem: {{ partida.advalorem }} -

- {% if partida.marca %}

Marca: {{ partida.marca }}

{% endif %} - {% if partida.modelo %}

Modelo: {{ partida.modelo }}

{% endif %} - {% if partida.series %} -

- SERIE(S):

-

Serie: {{ partida.series|join(', ') }}

- {% endif %} -
-

{{ partida.cantidad_importacion }}

-
-

{{ partida.unidad_medida }}

-
-

- {% if partida.cantidad_bultos != 0 %}{{ partida.cantidad_bultos }}{% endif %} - {{ partida.clave_bultos }} -

-
-

{{ partida.peso_neto }}

-
-

{{ partida.peso_bruto }}

-
-

${{ partida.valor_costo_unitario }}

-
-

${{ partida.valor_total }}

-
-

- Observaciones: - TOTALES -

-
-

{{ totales.cantidad_total }}

-
-

- {% if totales.bultos_total != 0 %}{{ totales.bultos_total }}{% endif %} - {{ totales.clave_bultos or '' }} -

-
-

{{ totales.peso_neto_total }}

-
-

{{ totales.peso_bruto_total }}

-
-

${{ totales.valor_total_total }}

-
-

{{ factura.observaciones }}

-
-

-

{{ cliente_proveedor.nombre }}

-


-

Los valores expresados en esta factura - son en: {{ factura.moneda }}

-
-


-

Normal Por Parte

-

Declaro bajo protesta de decir verdad que la información contenida en - este documento es verdadera y me hago responsable de comprobar lo aquí declarado.

-
- - - \ No newline at end of file diff --git a/backend/api/v1/modules/a76/reports/importacion/facturas/templates/factura_usa_ver.html b/backend/api/v1/modules/a76/reports/importacion/facturas/templates/factura_usa_ver.html deleted file mode 100644 index 7ec1a681..00000000 --- a/backend/api/v1/modules/a76/reports/importacion/facturas/templates/factura_usa_ver.html +++ /dev/null @@ -1,598 +0,0 @@ - - - - - - Commercial Invoice - {{ factura.numero }} - - - - -
-
-
-

{{ factura.titulo_documento }}

-
-

-
-
-

-


-
-
-
-
- {% if logo_b64 %} -
- -
- {% endif %} -
-

{{ cliente_proveedor.header }}

-

{{ cliente_proveedor.nombre }}

-

{{ cliente_proveedor.direccion }} - {% if cliente_proveedor.num_exterior %} Ext: {{ cliente_proveedor.num_exterior }}{% endif %} - {% if cliente_proveedor.num_interior %} Int: {{ cliente_proveedor.num_interior }}{% endif %} -

-

{{ cliente_proveedor.colonia }} {% if cliente_proveedor.codigo_postal %} Zip Code: {{ - cliente_proveedor.codigo_postal }}{% endif %}

-

{{ cliente_proveedor.ciudad }}, {{ cliente_proveedor.estado }}, {{ cliente_proveedor.pais }}

-

TAX ID: {{ cliente_proveedor.tax_id }} - {% if cliente_proveedor.programa and cliente_proveedor.programa != 'Ninguno' %} - {{ cliente_proveedor.programa }}: {{ cliente_proveedor.autorizacion }} - {% endif %} -

-


-
-
- -
- - - - - - - - - - - - - - - - - - - -
-

INVOICE:

-
-

{{ factura.numero }}

-
-

Date:

-
-

{{ factura.fecha }}

-
-

Ex. Rate:

-
-

{{ factura.tipo_cambio }}

-
-

INCOTERM:

-
-

{{ factura.incoterm or '' }}

-
-

Customs:

-
-

{{ factura.aduana }}

-
-
-
- -
-
-

{{ cliente_vendido.header }}

-

{{ cliente_vendido.nombre }}

-

{{ cliente_vendido.direccion }} - {% if cliente_vendido.num_exterior %} Ext: {{ cliente_vendido.num_exterior }}{% endif %} - {% if cliente_vendido.num_interior %} Int: {{ cliente_vendido.num_interior }}{% endif %} -

-

{{ cliente_vendido.colonia }} {% if cliente_vendido.codigo_postal %} Zip Code: {{ - cliente_vendido.codigo_postal }}{% endif %}

-

{{ cliente_vendido.ciudad }}, {{ cliente_vendido.estado }}, {{ cliente_vendido.pais }} -

-

Tax ID: {{ cliente_vendido.tax_id }} - {% if cliente_vendido.programa and cliente_vendido.programa != 'Ninguno' %} - {{ cliente_vendido.programa }}: {{ cliente_vendido.autorizacion }} - {% endif %} -

-

- {% if cliente_vendido.prosec %}PROSEC: {{ cliente_vendido.prosec }} {% endif %} - {% if cliente_vendido.reg_emp %}REG EMP: {{ cliente_vendido.reg_emp }} {% endif %} - {% if cliente_vendido.cert %}CERT: {{ cliente_vendido.cert }}{% endif %} -

-
- -
-

{{ cliente_enviado.header }}

-

{{ cliente_enviado.nombre }}

-

{{ cliente_enviado.direccion }} - {% if cliente_enviado.num_exterior %} Ext: {{ cliente_enviado.num_exterior }}{% endif %} - {% if cliente_enviado.num_interior %} Int: {{ cliente_enviado.num_interior }}{% endif %} -

-

{{ cliente_enviado.colonia }} {% if cliente_enviado.codigo_postal %} Zip Code: {{ - cliente_enviado.codigo_postal }}{% endif %}

-

{{ cliente_enviado.ciudad }}, {{ cliente_enviado.estado }}, {{ cliente_enviado.pais }} -

-

Tax ID: {{ cliente_enviado.tax_id }} - {% if cliente_enviado.programa and cliente_enviado.programa != 'Ninguno' %} - {{ cliente_enviado.programa }}: {{ cliente_enviado.autorizacion }} - {% endif %} -

-

- {% if cliente_enviado.prosec %}PROSEC: {{ cliente_enviado.prosec }} {% endif %} - {% if cliente_enviado.reg_emp %}REG EMP: {{ cliente_enviado.reg_emp }} {% endif %} - {% if cliente_enviado.cert %}CERT: {{ cliente_enviado.cert }}{% endif %} -

-
-
-


-
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - {% for partida in partidas %} - - - - - - - - - - - - {% endfor %} - - - - - - - - - - - - - - - - - - - - - -
-

Carrier:

-
-

{{ factura.transportista }}

-
-

SCAC: {{ factura.scac }}

-
-

INCOTERM:

-
-

{{ factura.incoterm }}

-
-

Customs: {{ factura.aduana }} / Ped: {{ factura.pedimento }}

-
-


-
-

Transport:

-
-

{{ factura.transporte }}: {{ factura.num_transporte }}

-
-

CAAT: {{ factura.caat }}

-
-

Plates: {{ factura.placas or '' }} / Trl: {{ factura.placas_remolque or - '' }}

-
-

Driver/Lic:

-
-

{{ factura.licencia_conductor or 'N/A' }}

-
-

Line

-
-

Description of raw material

-
-

Commercial

-
-

Packaging

-
-

Weight (KGS)

-
-

Values

-
-

Quantity

-
-

U.M.

-
-

Type

-
-

Net

-
-

Gross

-
-

Unit

-
-

Total

-
-

{{ loop.index }}

-
-

- {{ partida.descripcion }} / HTS Code: {{ partida.fraccion }} / Origin: {{ partida.origen or 'MEX' }} / Preference: {{ partida.preferencia }} / Advalorem: {{ partida.advalorem }} -

- {% if partida.marca %}

Brand: {{ partida.marca }}

{% endif %} - {% if partida.modelo %}

Model: {{ partida.modelo }}

{% endif %} - {% if partida.series %} -

- SERIAL(S):

-

Serial: {{ partida.series|join(', ') }}

- {% endif %} -
-

{{ partida.cantidad_importacion }}

-
-

{{ partida.unidad_medida }}

-
-

- {% if partida.cantidad_bultos != 0 %}{{ partida.cantidad_bultos }}{% endif %} - {{ partida.clave_bultos }} -

-
-

{{ partida.peso_neto }}

-
-

{{ partida.peso_bruto }}

-
-

${{ partida.valor_costo_unitario }}

-
-

${{ partida.valor_total }}

-
-

- Remarks: - TOTALS -

-
-

{{ totales.cantidad_total }}

-
-

- {% if totales.bultos_total != 0 %}{{ totales.bultos_total }}{% endif %} - {{ totales.clave_bultos or '' }} -

-
-

{{ totales.peso_neto_total }}

-
-

{{ totales.peso_bruto_total }}

-
-

${{ totales.valor_total_total }}

-
-

{{ factura.observaciones }}

-
-

-

{{ cliente_proveedor.nombre }}

-


-

Values expressed in: {{ factura.moneda - }}

-
-


-

-

I declare under penalty of perjury that the information contained in - this document is true and correct.

-
- - - \ No newline at end of file diff --git a/backend/api/v1/modules/a76/reports/importacion/facturas/temporary/__init__.py b/backend/api/v1/modules/a76/reports/importacion/facturas/temporary/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/backend/api/v1/modules/a76/reports/importacion/facturas/temporary/mex/service.py b/backend/api/v1/modules/a76/reports/importacion/facturas/temporary/mex/service.py deleted file mode 100644 index 5fef67bc..00000000 --- a/backend/api/v1/modules/a76/reports/importacion/facturas/temporary/mex/service.py +++ /dev/null @@ -1,428 +0,0 @@ -import shutil -import base64 -import pdfkit -from pathlib import Path -from decimal import Decimal -from typing import Tuple, List, Callable, Optional - -from jinja2 import Environment, FileSystemLoader, select_autoescape -from fastapi import HTTPException -from sqlalchemy.orm import Session - -# --- MODELOS --- -from api.v1.modules.a76.invoices.models import InvoiceHeader, InvoiceLogistics -from api.v1.modules.a76.items.line_financials.models import LineFinancial -from api.v1.modules.a76.items.line_quantities.models import LineQuantity -from api.v1.modules.a76.clients_and_providers.models import ( - ClientProvider, ClientProviderAddress, ClientProviderPrograms -) -from api.v1.modules.a76.parts.models import Part -from api.v1.modules.a76.pedmientos.models import Pedimentos -from api.v1.modules.a76.general_catalogs.company.models import Company -from api.v1.modules.a76.customs_brokers.models import CustomsBroker -from api.v1.modules.a76.items.models import LineItem - -# --- TRANSPORTATION MODELS --- -from api.v1.modules.a76.transportation.transporters.models import Transporter -from api.v1.modules.a76.transportation.vehicles.models import Vehicle -from api.v1.modules.a76.transportation.trailers.models import Trailer -from api.v1.modules.a76.transportation.drivers.models import Driver - -# --- MODELO DE FRACCIONES --- -from api.v1.modules.a76.general_catalogs.fractions.tariff_fractions.models import TariffFraction - -# --- SCHEMAS --- -from ...mex.schemas import ( - ClienteSchema, PartidaSchema, TotalesSchema, - FacturaSchema, FacturaImportacionCompleta -) - -class FacturaImportacionMexService: - def __init__(self): - self.template_dir = Path(__file__).parent.parent / "templates" - self.jinja_env = Environment( - loader=FileSystemLoader(self.template_dir), - autoescape=select_autoescape(['html', 'xml']) - ) - self.template = self.jinja_env.get_template('factura_mex_ver.html') - - def _get_wkhtmltopdf_config(self): - path = shutil.which("wkhtmltopdf") or "/usr/local/bin/wkhtmltopdf" - if not Path(path).exists(): - raise RuntimeError("wkhtmltopdf no encontrado.") - return pdfkit.configuration(wkhtmltopdf=path) - - def formatear_numero(self, valor, decimales: int = 2): - if valor is None: return 0.0 - try: - return round(float(valor), decimales) - except: return 0.0 - - def _format_fraccion_fallback(self, fraccion_raw: str) -> str: - if not fraccion_raw or len(fraccion_raw) < 8: - return fraccion_raw - return f"{fraccion_raw[:4]}.{fraccion_raw[4:6]}.{fraccion_raw[6:]}" - - def _obtener_datos_cliente(self, db: Session, client_id: int, rol: str) -> ClienteSchema: - main = db.query(ClientProvider).filter(ClientProvider.id == client_id).first() - if not main: - return ClienteSchema(header=rol, nombre="Desconocido", direccion="", tax_id="", codigo_postal="", ciudad="", estado="", pais="MEX") - - addr = db.query(ClientProviderAddress).filter(ClientProviderAddress.client_id == client_id).first() - prog = db.query(ClientProviderPrograms).filter(ClientProviderPrograms.client_id == client_id).first() - - return ClienteSchema( - header=rol, - nombre=(main.name or main.short_name) or "S/N", - direccion=(addr.streets or "") if addr else "", - num_exterior=(addr.exterior_number or "") if addr else "", - num_interior=(addr.interior_number or "") if addr else "", - colonia=(addr.neighborhood or "") if addr else "", - codigo_postal=(addr.postal_code or "") if addr else "", - ciudad=(addr.city or "") if addr else "", - estado=(addr.state or "") if addr else "", - pais=(addr.country or "MEX") if addr else "MEX", - tax_id=getattr(main, "fiscal_id", "") or "", - programa="IMMEX" if (prog and prog.program) else "", - autorizacion=prog.program_number if prog else "", - prosec=prog.prosec_authorization if (prog and prog.prosec and prog.prosec_authorization) else "", - reg_emp=prog.val_certified_company_registry if (prog and hasattr(prog, 'val_certified_company_registry')) else ( - prog.certified_company_registry if (prog and prog.certified_company_registry) else "" - ), - cert=prog.is_certified_company if (prog and prog.is_certified_company) else "" - ) - - def obtener_datos(self, db: Session, invoice_id: int, company_id: int, progress_callback: Optional[Callable] = None) -> FacturaImportacionCompleta: - try: - if progress_callback: progress_callback(10, "Buscando factura...") - header = db.query(InvoiceHeader).filter(InvoiceHeader.id == invoice_id, InvoiceHeader.company_id == company_id).first() - if not header: raise HTTPException(status_code=404, detail="Factura no encontrada") - - compliance = header.compliance_mx - logistics = header.logistics if header.logistics else None - financials = header.financials if header.financials else None - if progress_callback: progress_callback(20, "Obteniendo datos de pedimento...") - pedimento_id = compliance.pedimento_id if (compliance and compliance.pedimento_id) else header.related_doc_id - pedimento = db.query(Pedimentos).filter(Pedimentos.id == pedimento_id).first() if pedimento_id else None - - if progress_callback: progress_callback(30, "Obteniendo cliente y proveedor...") - proveedor_id = compliance.provider_id if compliance else None - cliente_proveedor = self._obtener_datos_cliente(db, proveedor_id, "Proveedor / supplier:") if proveedor_id else ClienteSchema(header="Proveedor", nombre="No Asignado", direccion="", tax_id="", codigo_postal="", ciudad="", estado="", pais="") - - nombre_agente = "" - if compliance and compliance.customs_broker_id: - broker = db.query(CustomsBroker).filter(CustomsBroker.id == compliance.customs_broker_id).first() - if broker: nombre_agente = broker.name - - company = db.query(Company).filter(Company.id == header.company_id).first() - # Datos Default (Company/Importer) - Used for fallback or Right Side (Enviado A) - cliente_default = ClienteSchema( - header="Importador / consignatario:", - nombre=getattr(company, 'name', "Empresa Local"), - direccion="DOMICILIO FISCAL", - num_exterior="", colonia="", codigo_postal="", ciudad="", estado="", pais="MEX", - tax_id=getattr(company, 'rfc', ""), - programa=getattr(company, 'program', "IMMEX"), autorizacion=getattr(company, 'program_number', "") - ) - - # Left Side Logic (Consignatario / Sold To) - cliente_vendido = cliente_default - if compliance and compliance.sold_to_id: - raw_header = compliance.sold_to_header or "CONSIGNATARIO" - clean_header = raw_header.replace("_", " ").capitalize() + ":" - cliente_vendido = self._obtener_datos_cliente(db, compliance.sold_to_id, clean_header) - - # Right Side Logic (Enviado A / Shipped To) - cliente_enviado = cliente_default - if compliance and compliance.shipped_to_id: - # Clean header: "enviado_a" -> "Enviado a:" - raw_header_shipped = compliance.shipped_to_header or "DESTINATARIO" - clean_header_shipped = raw_header_shipped.replace("_", " ").capitalize() + ":" - - # Fetch client data - cliente_enviado = self._obtener_datos_cliente(db, compliance.shipped_to_id, clean_header_shipped) - - remesa_valor = str(compliance.remesa) if (compliance and compliance.remesa) else "" - acuse_valor = str(compliance.edocument) if (compliance and compliance.edocument) else "N/A" - - patente_val = "" - if pedimento and pedimento.license: - patente_val = pedimento.license - elif 'broker' in locals() and broker and broker.license: - patente_val = broker.license - - - # --- Transport Data Fetching --- - transporte_txt = str(logistics.transport_type) if (logistics and logistics.transport_type) else "" - num_transporte_val = (logistics.trailer_num or "") if logistics else "" - - # Init values - placas_val = (logistics.license_plate or "") if logistics else "" # Placas Tracto - placas_remolque_val = "" - transportista_val = (logistics.carrier_id or "") if logistics else "" - caat_val = "" - scac_val = "" - licencia_cond_val = "" - - if logistics: - # 1. Transporter (CAAT / SCAC) - if logistics.carrier_int_id is not None: - transporter_obj = ( - db.query(Transporter) - .filter(Transporter.transporter_id == logistics.carrier_int_id) - .first() - ) - elif logistics.carrier_id: - transporter_obj = db.query(Transporter).filter( - Transporter.transporter_key == logistics.carrier_id - ).first() - if transporter_obj: - caat_val = transporter_obj.caat_code or "" - scac_val = transporter_obj.transport_code or "" # Mapping transport_code to SCAC - transportista_val = transporter_obj.name or logistics.carrier_id - - # 2. Vehicle (Placas Tracto) - Try transport_id first - if logistics.transport_id: - if logistics.transport_int_id is not None: - veh_obj = ( - db.query(Vehicle) - .filter(Vehicle.vehicle_id == logistics.transport_int_id) - .first() - ) - else: - veh_obj = db.query(Vehicle).filter( - Vehicle.vehicle_key == logistics.transport_id - ).first() - if veh_obj: - placas_val = veh_obj.plate_number or placas_val - elif logistics.vehicle_num: # Fallback to vehicle_num if populated and transport_id failed/empty - veh_obj = db.query(Vehicle).filter(Vehicle.vehicle_key == logistics.vehicle_num).first() - if veh_obj: - placas_val = veh_obj.plate_number or placas_val - - # 3. Trailer (Placas Remolque) - if logistics.trailer_num: - if logistics.trailer_int_id is not None: - trl_obj = ( - db.query(Trailer) - .filter(Trailer.trailer_id == logistics.trailer_int_id) - .first() - ) - else: - trl_obj = db.query(Trailer).filter( - Trailer.trailer_number == logistics.trailer_num - ).first() - if trl_obj: - placas_remolque_val = trl_obj.plate_number or "" - - # 4. Driver (License) - if logistics.carrier_id and logistics.driver_name: - # Attempt to find driver by name + carrier - drv_obj = db.query(Driver).filter( - Driver.transporter_key == logistics.carrier_id, - Driver.driver_name == logistics.driver_name - ).first() - if drv_obj: - licencia_cond_val = drv_obj.license_number or "" - - factura_schema = FacturaSchema( - numero=header.invoice_number or "S/N", - fecha=str(header.invoice_date) if header.invoice_date else "", - tipo_cambio=float(financials.exchange_rate) if (financials and financials.exchange_rate) else (float(pedimento.exchange_rate) if pedimento and pedimento.exchange_rate else 1.0), - moneda=getattr(header, 'currency', "USD") or "USD", - incoterm=(logistics.incoterm or "") if logistics else "", - observaciones=header.observation_es or header.observation_en or "", - pedimento=f"{pedimento.year} {pedimento.customs_office[:2] if pedimento.customs_office else ''} {pedimento.license} {pedimento.pedimento_number}" if pedimento else "", - clave_pedimento=pedimento.pedimento_code if pedimento else "", - regimen=header.document_type or "", - patente=patente_val, - agente_aduanal=nombre_agente, - transporte=transporte_txt, - num_transporte=num_transporte_val, - placas=placas_val, - placas_remolque=placas_remolque_val, - transportista=transportista_val, - caat=caat_val, - scac=scac_val, - licencia_conductor=licencia_cond_val, - aduana=compliance.aduana if (compliance and compliance.aduana) else (pedimento.customs_office[:2] if (pedimento and pedimento.customs_office) else ""), - precinto=(logistics.seal_number or "") if logistics else "", - destino=(logistics.destination_goods or "") if logistics else "", - remesa=remesa_valor, acuse_electronico=acuse_valor - ) - - if progress_callback: progress_callback(50, "Procesando partidas...") - lines = db.query(LineItem).filter(LineItem.invoice_id == header.id).all() - partidas_list = [] - - for line in lines: - qty = db.query(LineQuantity).filter(LineQuantity.item_line_id == line.id).first() - fin = db.query(LineFinancial).filter(LineFinancial.item_line_id == line.id).first() - part_master = db.query(Part).filter(Part.id == line.part_number_id).first() - - desc_final = "S/D" - num_parte_final = str(line.part_number_id or "S/N") - fraccion_raw = "" - origen_final = "MEX" - - if part_master: - desc_final = part_master.description_spanish or part_master.description_english or "Sin Desc." - num_parte_final = part_master.part_number - fraccion_raw = part_master.fraction if part_master.fraction else "" - - # Fetch Origin from Master Catalog (FaPart) - if part_master.fa_data and part_master.fa_data.origin_country: - origen_final = part_master.fa_data.origin_country - - - fraccion_limpia = fraccion_raw.replace(".", "").strip() - if fraccion_limpia: - fraccion_limpia = fraccion_limpia[:8].zfill(8) - - # Consultar tabla tariff_fractions - fraccion_db = db.query(TariffFraction).filter(TariffFraction.code == fraccion_limpia).first() - - - preferencia_txt = "General" - advalorem_txt = "0%" - fraccion_imprimir = fraccion_raw - - if fraccion_db: - # Si el valor en BD es None, "0", o vacío, dejarlo como "0%" o "EXENTO" - adv_db = fraccion_db.adv_impo - if adv_db and adv_db.strip() not in ["0", "0.0", "0.00", ""]: - advalorem_txt = adv_db if "%" in adv_db else f"{adv_db}%" - else: - advalorem_txt = "0%" - - fraccion_imprimir = fraccion_db.fraction or fraccion_raw - else: - - fraccion_imprimir = self._format_fraccion_fallback(fraccion_limpia) - - # Logic to determine values - Prioritize Specific Currency Columns - v_unitario = 0.0 - v_total = 0.0 - - if fin: - is_mxn = (factura_schema.moneda == 'MXN') - - # 1. Try Specific Currency Columns First - if is_mxn: - v_unitario = float(fin.unit_cost_commercial_mxn or 0.0) - v_total = float(fin.value_commercial_mxn or 0.0) - else: - v_unitario = float(fin.unit_cost_commercial_usd or 0.0) - v_total = float(fin.value_commercial_usd or 0.0) - - # 2. Fallback to Generic independently if Specific is 0 - if not v_unitario: - v_unitario = float(fin.commercial_unit_cost or 0.0) - - if not v_total: - v_total = float(fin.total_commercial_value or 0.0) - - # 3. Calculate from Quantity if still missing - cantidad = float(qty.quantity) if (qty and qty.quantity) else 0.0 - - if cantidad > 0: - if v_unitario > 0 and v_total == 0: - v_total = v_unitario * cantidad - elif v_total > 0 and v_unitario == 0: - v_unitario = v_total / cantidad - - partidas_list.append(PartidaSchema( - numero_parte=num_parte_final, - descripcion=desc_final, - fraccion=fraccion_imprimir, - origen=origen_final, - advalorem=advalorem_txt, - preferencia=preferencia_txt, - cantidad_importacion=self.formatear_numero(qty.quantity if qty else 0), - unidad_medida=qty.weight_unit if qty else "PZA", - cantidad_bultos=int(qty.package_quantity) if qty and qty.package_quantity else 0, - clave_bultos=(qty.package_info.key if (qty and qty.package_info) else ""), - peso_neto=self.formatear_numero(qty.net_weight if qty else 0), - peso_bruto=self.formatear_numero(qty.gross_weight if qty else 0), - valor_costo_unitario=self.formatear_numero(v_unitario), - valor_total=self.formatear_numero(v_total) - )) - - totales = self.calcular_totales(partidas_list, Decimal(factura_schema.tipo_cambio)) - - return FacturaImportacionCompleta( - cliente_proveedor=cliente_proveedor, cliente_vendido=cliente_vendido, - cliente_enviado=cliente_enviado, factura=factura_schema, - partidas=partidas_list, totales=totales - ) - - except Exception as e: - print(f"Error Service A76: {e}") - raise HTTPException(status_code=500, detail=f"Error: {str(e)}") - - def calcular_totales(self, partidas: List[PartidaSchema], tipo_cambio: Decimal) -> TotalesSchema: - cant = sum(p.cantidad_importacion for p in partidas) - valor = sum(p.valor_total for p in partidas) - peso_n = sum(p.peso_neto for p in partidas) - peso_b = sum(p.peso_bruto for p in partidas) - bultos = sum(p.cantidad_bultos for p in partidas) - claves = [p.clave_bultos for p in partidas if p.clave_bultos] - clave_comun = max(set(claves), key=claves.count) if claves else "" - if bultos > 1 and clave_comun and not clave_comun.endswith("S"): clave_comun += "S" - tc = float(tipo_cambio) if tipo_cambio else 1.0 - return TotalesSchema( - cantidad_total=self.formatear_numero(cant), bultos_total=bultos, clave_bultos=clave_comun, - peso_neto_total=self.formatear_numero(peso_n), peso_bruto_total=self.formatear_numero(peso_b), - valor_total_total=self.formatear_numero(valor), valor_total_dolares=self.formatear_numero(valor / tc if tc > 0 else 0) - ) - - def generar_factura_completa(self, db: Session, invoice_id: int, company_id: int, formato: str = "pdf", progress_callback: Optional[Callable] = None) -> Tuple[bytes, str, str]: - if progress_callback: progress_callback(5, "Iniciando servicio de reporte...") - datos = self.obtener_datos(db, invoice_id, company_id, progress_callback) - - if progress_callback: progress_callback(80, "Renderizando plantilla...") - - # LOGO LOGIC - logo_b64 = None - try: - # Fetch company to get logo path - - comp_logo = db.query(Company).filter(Company.id == company_id).first() - if comp_logo and comp_logo.logo: - p = Path(comp_logo.logo) - - # Logic robusta de búsqueda (igual que en routes.py) - target_path = p - if not target_path.exists(): - # Intentar en la ruta estándar: app_data/logos/{id}/{nombre} - # Esto cubre el caso donde solo se guardó el nombre del archivo o la ruta absoluta cambió - fallback = Path(f"app_data/logos/{company_id}") / p.name - if fallback.exists(): - target_path = fallback - - if target_path.exists(): - with open(target_path, "rb") as image_file: - encoded_string = base64.b64encode(image_file.read()).decode('utf-8') - # Detect MIME type loosely - mime = "image/png" - if target_path.suffix.lower() in ['.jpg', '.jpeg']: mime = "image/jpeg" - logo_b64 = f"data:{mime};base64,{encoded_string}" - except Exception as e: - print(f"Error loading logo: {e}") - - context = { - 'cliente_proveedor': datos.cliente_proveedor.model_dump(), 'cliente_vendido': datos.cliente_vendido.model_dump(), - 'cliente_enviado': datos.cliente_enviado.model_dump(), 'factura': datos.factura.model_dump(), - 'partidas': [p.model_dump() for p in datos.partidas], 'totales': datos.totales.model_dump(), - 'logo_b64': logo_b64 - } - html_content = self.template.render(**context) - nombre = f"Factura_{datos.factura.numero}.{formato}" - if formato == "html": return html_content.encode('utf-8'), nombre, "text/html" - - if progress_callback: progress_callback(90, "Generando PDF final...") - options = {'page-size': 'Letter', 'margin-top': '0.5in', 'margin-right': '0.5in', 'margin-bottom': '0.5in', 'margin-left': '0.5in', 'encoding': "UTF-8", 'enable-local-file-access': None} - pdf = pdfkit.from_string(html_content, False, options=options, configuration=self._get_wkhtmltopdf_config()) - - if progress_callback: progress_callback(100, "Completado") - return pdf, nombre, "application/pdf" \ No newline at end of file diff --git a/backend/api/v1/modules/a76/reports/importacion/facturas/usa/service.py b/backend/api/v1/modules/a76/reports/importacion/facturas/usa/service.py deleted file mode 100644 index d98b7878..00000000 --- a/backend/api/v1/modules/a76/reports/importacion/facturas/usa/service.py +++ /dev/null @@ -1,750 +0,0 @@ -import shutil -import base64 -import pdfkit -from pathlib import Path -from decimal import Decimal -from typing import Tuple, List, Callable, Optional - -from jinja2 import Environment, FileSystemLoader, select_autoescape -from fastapi import HTTPException -from sqlalchemy.orm import Session - -# --- MODELOS --- -from api.v1.modules.a76.invoices.models import InvoiceHeader, InvoiceLogistics -from api.v1.modules.a76.items.line_financials.models import LineFinancial -from api.v1.modules.a76.items.line_quantities.models import LineQuantity -from api.v1.modules.a76.clients_and_providers.models import ( - ClientProvider, - ClientProviderAddress, - ClientProviderPrograms, -) -from api.v1.modules.a76.parts.models import Part -from api.v1.modules.a76.pedmientos.models import Pedimentos -from api.v1.modules.a76.general_catalogs.company.models import Company -from api.v1.modules.a76.customs_brokers.models import CustomsBroker -from api.v1.modules.a76.items.models import LineItem -from api.v1.modules.a76.items.line_customs.models import LineCustom -from api.v1.modules.a76.items.line_descriptions.models import LineDescription -from api.v1.modules.a76.items.series.models import Serie - -# --- TRANSPORTATION MODELS --- -from api.v1.modules.a76.transportation.transporters.models import Transporter -from api.v1.modules.a76.transportation.vehicles.models import Vehicle -from api.v1.modules.a76.transportation.trailers.models import Trailer -from api.v1.modules.a76.transportation.drivers.models import Driver - -# --- MODELO DE FRACCIONES --- -from api.v1.modules.a76.general_catalogs.fractions.tariff_fractions.models import ( - TariffFraction, -) - -# --- SCHEMAS --- -# Reuse schemas from neighbor package as they fit the same data structure -from ..mex.schemas import ( - ClienteSchema, - PartidaSchema, - TotalesSchema, - FacturaSchema, - FacturaImportacionCompleta, -) - - -class FacturaImportacionUsaService: - def __init__(self): - self.template_dir = Path(__file__).parent.parent / "templates" - self.jinja_env = Environment( - loader=FileSystemLoader(self.template_dir), - autoescape=select_autoescape(["html", "xml"]), - ) - self.template = self.jinja_env.get_template("factura_usa_ver.html") - - def _get_document_title(self, invoice_type: str, is_american: bool = True) -> str: - """ - Determina el título del documento basado en el tipo de factura. - - Args: - invoice_type: Tipo de factura (TEM, DEF, MEX, CR) - is_american: Si es factura americana (True) o mexicana (False) - - Returns: - Título formateado para la factura - """ - # Mapeo para facturas mexicanas - mexican_titles = { - "MEX": "Factura Importación Compras Mexicanas", - "DEF": "Importación Definitiva", - "TEM": "Importación Temporal", - "CR": "Importación de Cambio de Régimen", - } - - # Mapeo para facturas americanas - american_titles = { - "MEX": "Mexican Purchases Import Invoice", - "DEF": "Definitive Importation", - "TEM": "Temporary Importation", - "CR": "Regime Change Importation", - } - - # Seleccionar el mapa correcto - titles = american_titles if is_american else mexican_titles - - # Obtener el título (normalizar a mayúsculas) - invoice_type_upper = invoice_type.upper() if invoice_type else "" - title = titles.get(invoice_type_upper, "") - - # Fallback a genéricos si no se encuentra - if not title: - return "Commercial Invoice" if is_american else "Factura de Importación" - - return title - - def _get_wkhtmltopdf_config(self): - path = shutil.which("wkhtmltopdf") or "/usr/local/bin/wkhtmltopdf" - if not Path(path).exists(): - raise RuntimeError("wkhtmltopdf no encontrado.") - return pdfkit.configuration(wkhtmltopdf=path) - - def formatear_numero(self, valor, decimales: int = 2): - if valor is None: - valor = 0.0 - try: - num = round(float(valor), decimales) - return f"{num:,.{decimales}f}" - except: - return f"0.{'0' * decimales}" - - def _format_fraccion_fallback(self, fraccion_raw: str) -> str: - if not fraccion_raw or len(fraccion_raw) < 8: - return fraccion_raw - return f"{fraccion_raw[:4]}.{fraccion_raw[4:6]}.{fraccion_raw[6:]}" - - def _obtener_datos_cliente( - self, db: Session, client_id: int, rol: str - ) -> ClienteSchema: - main = db.query(ClientProvider).filter(ClientProvider.id == client_id).first() - if not main: - return ClienteSchema( - header=rol, - nombre="Unknown", - direccion="", - tax_id="", - codigo_postal="", - ciudad="", - estado="", - pais="USA", - ) - - addr = ( - db.query(ClientProviderAddress) - .filter(ClientProviderAddress.client_id == client_id) - .first() - ) - prog = ( - db.query(ClientProviderPrograms) - .filter(ClientProviderPrograms.client_id == client_id) - .first() - ) - - return ClienteSchema( - header=rol, - nombre=(main.name or main.short_name) or "N/A", - direccion=(addr.streets or "") if addr else "", - num_exterior=(addr.exterior_number or "") if addr else "", - num_interior=(addr.interior_number or "") if addr else "", - colonia=(addr.neighborhood or "") if addr else "", - codigo_postal=(addr.postal_code or "") if addr else "", - ciudad=(addr.city or "") if addr else "", - estado=(addr.state or "") if addr else "", - pais=(addr.country or "USA") if addr else "USA", - tax_id=getattr(main, "fiscal_id", "") or "", - programa="IMMEX" if (prog and prog.program) else "", - autorizacion=prog.program_number if prog else "", - prosec=( - prog.prosec_authorization - if (prog and prog.prosec and prog.prosec_authorization) - else "" - ), - reg_emp=( - prog.val_certified_company_registry - if (prog and hasattr(prog, "val_certified_company_registry")) - else ( - prog.certified_company_registry - if (prog and prog.certified_company_registry) - else "" - ) - ), - cert=( - prog.is_certified_company - if (prog and prog.is_certified_company) - else "" - ), - ) - - def obtener_datos( - self, - db: Session, - invoice_id: int, - company_id: int, - progress_callback: Optional[Callable] = None, - currency_code: str = "ORIGINAL", - ) -> FacturaImportacionCompleta: - try: - if progress_callback: - progress_callback(10, "Searching invoice...") - header = ( - db.query(InvoiceHeader) - .filter( - InvoiceHeader.id == invoice_id, - InvoiceHeader.company_id == company_id, - ) - .first() - ) - if not header: - raise HTTPException(status_code=404, detail="Invoice not found") - - compliance = header.compliance_mx - logistics = header.logistics if header.logistics else None - financials = header.financials if header.financials else None - if progress_callback: - progress_callback(20, "Fetching entry data...") - pedimento_id = ( - compliance.pedimento_id - if (compliance and compliance.pedimento_id) - else header.related_doc_id - ) - pedimento = ( - db.query(Pedimentos).filter(Pedimentos.id == pedimento_id).first() - if pedimento_id - else None - ) - - if progress_callback: - progress_callback(30, "Fetching client and supplier...") - proveedor_id = compliance.provider_id if compliance else None - cliente_proveedor = ( - self._obtener_datos_cliente(db, proveedor_id, "Supplier:") - if proveedor_id - else ClienteSchema( - header="Supplier", - nombre="Unassigned", - direccion="", - tax_id="", - codigo_postal="", - ciudad="", - estado="", - pais="", - ) - ) - - nombre_agente = "" - if compliance and compliance.customs_broker_id: - broker = ( - db.query(CustomsBroker) - .filter(CustomsBroker.id == compliance.customs_broker_id) - .first() - ) - if broker: - nombre_agente = broker.name - - company = db.query(Company).filter(Company.id == header.company_id).first() - # Datos Default (Company/Importer) - cliente_default = ClienteSchema( - header="Importer / Consignee:", - nombre=getattr(company, "name", "Local Company"), - direccion="FISCAL ADDRESS", - num_exterior="", - colonia="", - codigo_postal="", - ciudad="", - estado="", - pais="MEX", - tax_id=getattr(company, "rfc", ""), - programa=getattr(company, "program", "IMMEX"), - autorizacion=getattr(company, "program_number", ""), - ) - - # Left Side Logic (Sold To) - cliente_vendido = cliente_default - if compliance and compliance.sold_to_id: - # Force English header for American Invoice - clean_header = "Sold To:" - # raw_header = compliance.sold_to_header or "SOLD_TO" - # clean_header = raw_header.replace("_", " ").title() + ":" - cliente_vendido = self._obtener_datos_cliente( - db, compliance.sold_to_id, clean_header - ) - - # Right Side Logic (Shipped To) - cliente_enviado = cliente_default - if compliance and compliance.shipped_to_id: - # Force English header for American Invoice - clean_header_shipped = "Shipped To:" - # raw_header_shipped = compliance.shipped_to_header or "SHIPPED_TO" - # clean_header_shipped = raw_header_shipped.replace("_", " ").title() + ":" - - # Fetch client data - cliente_enviado = self._obtener_datos_cliente( - db, compliance.shipped_to_id, clean_header_shipped - ) - - remesa_valor = ( - str(compliance.remesa) if (compliance and compliance.remesa) else "" - ) - acuse_valor = ( - str(compliance.edocument) - if (compliance and compliance.edocument) - else "N/A" - ) - - patente_val = "" - if pedimento and pedimento.license: - patente_val = pedimento.license - elif "broker" in locals() and broker and broker.license: - patente_val = broker.license - - # --- Transport Data Fetching --- - transporte_txt = ( - str(logistics.transport_type) - if (logistics and logistics.transport_type) - else "" - ) - num_transporte_val = (logistics.trailer_num or "") if logistics else "" - - # Init values - placas_val = (logistics.license_plate or "") if logistics else "" # Plates - placas_remolque_val = "" - transportista_val = (logistics.carrier_id or "") if logistics else "" - caat_val = "" - scac_val = "" - licencia_cond_val = "" - - if logistics: - # 1. Transporter (CAAT / SCAC) - if logistics.carrier_int_id is not None: - transporter_obj = ( - db.query(Transporter) - .filter(Transporter.transporter_id == logistics.carrier_int_id) - .first() - ) - if transporter_obj: - caat_val = transporter_obj.caat_code or "" - scac_val = transporter_obj.transport_code or "" - transportista_val = transporter_obj.name or logistics.carrier_id - elif logistics.carrier_id: - transporter_obj = ( - db.query(Transporter) - .filter(Transporter.transporter_key == logistics.carrier_id) - .first() - ) - if transporter_obj: - caat_val = transporter_obj.caat_code or "" - scac_val = transporter_obj.transport_code or "" - transportista_val = transporter_obj.name or logistics.carrier_id - - # 2. Vehicle (Plates) - if logistics.transport_id: - if logistics.transport_int_id is not None: - veh_obj = ( - db.query(Vehicle) - .filter(Vehicle.vehicle_id == logistics.transport_int_id) - .first() - ) - else: - veh_obj = ( - db.query(Vehicle) - .filter(Vehicle.vehicle_key == logistics.transport_id) - .first() - ) - if veh_obj: - placas_val = veh_obj.plate_number or placas_val - elif logistics.vehicle_num: - veh_obj = ( - db.query(Vehicle) - .filter(Vehicle.vehicle_key == logistics.vehicle_num) - .first() - ) - if veh_obj: - placas_val = veh_obj.plate_number or placas_val - - # 3. Trailer - if logistics.trailer_num: - if logistics.trailer_int_id is not None: - trl_obj = ( - db.query(Trailer) - .filter(Trailer.trailer_id == logistics.trailer_int_id) - .first() - ) - else: - trl_obj = ( - db.query(Trailer) - .filter(Trailer.trailer_number == logistics.trailer_num) - .first() - ) - if trl_obj: - placas_remolque_val = trl_obj.plate_number or "" - - # 4. Driver (License) - if logistics.carrier_id and logistics.driver_name: - drv_obj = ( - db.query(Driver) - .filter( - Driver.transporter_key == logistics.carrier_id, - Driver.driver_name == logistics.driver_name, - ) - .first() - ) - if drv_obj: - licencia_cond_val = drv_obj.license_number or "" - - # Determine Currency - moneda_final = getattr(header, "currency", "USD") or "USD" - if currency_code == "MXN": - moneda_final = "MXN" - elif currency_code == "USD": - moneda_final = "USD" - - factura_schema = FacturaSchema( - numero=header.invoice_number or "N/A", - titulo_documento=self._get_document_title( - header.invoice_type or "", is_american=True - ), - fecha=str(header.invoice_date) if header.invoice_date else "", - tipo_cambio=( - float(financials.exchange_rate) - if (financials and financials.exchange_rate) - else ( - float(pedimento.exchange_rate) - if pedimento and pedimento.exchange_rate - else 1.0 - ) - ), - moneda=moneda_final, - incoterm=(logistics.incoterm or "") if logistics else "", - observaciones=header.observation_es or header.observation_en or "", - pedimento=( - f"{pedimento.year} {pedimento.customs_office[:2] if pedimento.customs_office else ''} {pedimento.license} {pedimento.pedimento_number}" - if pedimento - else "" - ), - clave_pedimento=pedimento.pedimento_code if pedimento else "", - regimen=header.document_type or "", - patente=patente_val, - agente_aduanal=nombre_agente, - transporte=transporte_txt, - num_transporte=num_transporte_val, - placas=placas_val, - placas_remolque=placas_remolque_val, - transportista=transportista_val, - caat=caat_val, - scac=scac_val, - licencia_conductor=licencia_cond_val, - aduana=( - compliance.aduana - if (compliance and compliance.aduana) - else ( - pedimento.customs_office[:2] - if (pedimento and pedimento.customs_office) - else "" - ) - ), - precinto=(logistics.seal_number or "") if logistics else "", - destino=(logistics.destination_goods or "") if logistics else "", - remesa=remesa_valor, - acuse_electronico=acuse_valor, - ) - - if progress_callback: - progress_callback(50, "Processing items...") - lines = db.query(LineItem).filter(LineItem.invoice_id == header.id).all() - partidas_list = [] - - # Initialize raw totals - raw_cant = 0.0 - raw_valor = 0.0 - raw_peso_n = 0.0 - raw_peso_b = 0.0 - raw_bultos = 0 - - for line in lines: - qty = ( - db.query(LineQuantity) - .filter(LineQuantity.item_line_id == line.id) - .first() - ) - fin = ( - db.query(LineFinancial) - .filter(LineFinancial.item_line_id == line.id) - .first() - ) - customs = ( - db.query(LineCustom) - .filter(LineCustom.item_line_id == line.id) - .first() - ) - desc_obj = ( - db.query(LineDescription) - .filter(LineDescription.item_line_id == line.id) - .first() - ) - series_objs = ( - db.query(Serie) - .filter(Serie.line_item_id == line.id) - .all() - ) - part_master = ( - db.query(Part).filter(Part.id == line.part_number_id).first() - ) - - desc_final = "N/D" - num_parte_final = str(line.part_number_id or "N/A") - fraccion_raw = "" - origen_final = "MEX" - - if part_master: - # Prefer English description if available, else Spanish - desc_final = ( - part_master.description_english - or part_master.description_spanish - or "No Desc." - ) - num_parte_final = part_master.part_number - # Prefer US Fraction (HTS) if available - fraccion_raw = ( - part_master.us_fraction if part_master.us_fraction else "" - ) - - if part_master.fa_data and part_master.fa_data.origin_country: - origen_final = part_master.fa_data.origin_country - - # Prioritize description from LineDescription (specific to this invoice) - if desc_obj: - desc_final = desc_obj.description_english or desc_obj.description_spanish or desc_final - - # FRACTION LOGIC: Use US Fraction (us_fraction) if available, otherwise blank - fraccion_imprimir = "" - - # Check part master US fraction - if part_master and part_master.us_fraction: - fraccion_imprimir = part_master.us_fraction.strip() - - # Preference and Advalorem logic - preferencia_txt = "General" - advalorem_txt = "0%" - - # 1. Prioridad: Datos específicos de la partida (LineCustom) - if customs: - if customs.fraction_type: - preferencia_txt = str(customs.fraction_type).upper() - - if customs.advalorem: - adv_val = customs.advalorem.strip() - if adv_val not in ["0", "0.0", "0.00", ""]: - advalorem_txt = adv_val if "%" in adv_val else f"{adv_val}%" - - # 2. Marca, Modelo y Series - marca_val = desc_obj.brand if desc_obj else "" - modelo_val = desc_obj.model if desc_obj else "" - series_list = [s.serial_numbers for s in series_objs if s.serial_numbers] - - # Prioritize USD for American Invoice logic if available? - # Sticking to same logic as Mex for now but could prioritize USD columns. - # Actually, duplicate logic from mex service for now to ensure consistency. - - v_unitario = 0.0 - v_total = 0.0 - - if fin: - is_mxn = factura_schema.moneda == "MXN" - - if is_mxn: - v_unitario = float(fin.unit_cost_commercial_mxn or 0.0) - v_total = float(fin.value_commercial_mxn or 0.0) - else: - v_unitario = float(fin.unit_cost_commercial_usd or 0.0) - v_total = float(fin.value_commercial_usd or 0.0) - - if not v_unitario: - v_unitario = float(fin.commercial_unit_cost or 0.0) - - if not v_total: - v_total = float(fin.total_commercial_value or 0.0) - - cantidad = float(qty.quantity) if (qty and qty.quantity) else 0.0 - - if cantidad > 0: - if v_unitario > 0 and v_total == 0: - v_total = v_unitario * cantidad - elif v_total > 0 and v_unitario == 0: - v_unitario = v_total / cantidad - - # Accumulate raw totals - raw_cant += float(qty.quantity) if qty and qty.quantity else 0.0 - raw_valor += v_total - raw_peso_n += float(qty.net_weight) if qty and qty.net_weight else 0.0 - raw_peso_b += float(qty.gross_weight) if qty and qty.gross_weight else 0.0 - raw_bultos += int(qty.package_quantity) if qty and qty.package_quantity else 0 - - # UOM Mapping for English context - uom_raw = line.unit_of_measure_info.code if line.unit_of_measure_info else "PCS" - if uom_raw == "PZA": - uom_raw = "PCS" - - partidas_list.append( - PartidaSchema( - numero_parte=num_parte_final, - descripcion=desc_final, - fraccion=fraccion_imprimir, - origen=origen_final, - advalorem=advalorem_txt, - preferencia=preferencia_txt, - marca=marca_val, - modelo=modelo_val, - series=series_list, - cantidad_importacion=self.formatear_numero( - qty.quantity if qty else 0 - ), - unidad_medida=uom_raw, - cantidad_bultos=( - int(qty.package_quantity) - if qty and qty.package_quantity - else 0 - ), - clave_bultos=( - qty.package_info.key if (qty and qty.package_info) else "" - ), - peso_neto=self.formatear_numero(qty.net_weight if qty else 0), - peso_bruto=self.formatear_numero( - qty.gross_weight if qty else 0 - ), - valor_costo_unitario=self.formatear_numero(v_unitario), - valor_total=self.formatear_numero(v_total), - ) - ) - - totales = self.calcular_totales( - partidas_list, - Decimal(factura_schema.tipo_cambio), - raw_cant=raw_cant, - raw_valor=raw_valor, - raw_peso_n=raw_peso_n, - raw_peso_b=raw_peso_b, - raw_bultos=raw_bultos - ) - - return FacturaImportacionCompleta( - cliente_proveedor=cliente_proveedor, - cliente_vendido=cliente_vendido, - cliente_enviado=cliente_enviado, - factura=factura_schema, - partidas=partidas_list, - totales=totales, - ) - - except Exception as e: - print(f"Error Service A76 USA: {e}") - raise HTTPException(status_code=500, detail=f"Error: {str(e)}") - - def calcular_totales( - self, - partidas: List[PartidaSchema], - tipo_cambio: Decimal, - raw_cant: float = 0, - raw_valor: float = 0, - raw_peso_n: float = 0, - raw_peso_b: float = 0, - raw_bultos: int = 0 - ) -> TotalesSchema: - # Use raw values passed from obtener_datos to avoid TypeError with formatted strings - claves = [p.clave_bultos for p in partidas if p.clave_bultos] - clave_comun = max(set(claves), key=claves.count) if claves else "" - # if bultos > 1 and clave_comun and not clave_comun.endswith("S"): clave_comun += "S" - # Don't pluralize strictly in English without logic, kept simple. - - tc = float(tipo_cambio) if tipo_cambio else 1.0 - return TotalesSchema( - cantidad_total=self.formatear_numero(raw_cant), - bultos_total=raw_bultos, - clave_bultos=clave_comun, - peso_neto_total=self.formatear_numero(raw_peso_n), - peso_bruto_total=self.formatear_numero(raw_peso_b), - valor_total_total=self.formatear_numero(raw_valor), - valor_total_dolares=self.formatear_numero(raw_valor / tc if tc > 0 else 0), - ) - - def generar_factura_completa( - self, - db: Session, - invoice_id: int, - company_id: int, - formato: str = "pdf", - progress_callback: Optional[Callable] = None, - currency_code: str = "ORIGINAL", - ) -> Tuple[bytes, str, str]: - if progress_callback: - progress_callback(5, "Starting report service...") - datos = self.obtener_datos( - db, invoice_id, company_id, progress_callback, currency_code - ) - - if progress_callback: - progress_callback(80, "Rendering template...") - - # LOGO LOGIC - logo_b64 = None - try: - comp_logo = db.query(Company).filter(Company.id == company_id).first() - if comp_logo and comp_logo.logo: - p = Path(comp_logo.logo) - target_path = p - if not target_path.exists(): - fallback = Path(f"app_data/logos/{company_id}") / p.name - if fallback.exists(): - target_path = fallback - - if target_path.exists(): - with open(target_path, "rb") as image_file: - encoded_string = base64.b64encode(image_file.read()).decode( - "utf-8" - ) - mime = "image/png" - if target_path.suffix.lower() in [".jpg", ".jpeg"]: - mime = "image/jpeg" - logo_b64 = f"data:{mime};base64,{encoded_string}" - except Exception as e: - print(f"Error loading logo: {e}") - - context = { - "cliente_proveedor": datos.cliente_proveedor.model_dump(), - "cliente_vendido": datos.cliente_vendido.model_dump(), - "cliente_enviado": datos.cliente_enviado.model_dump(), - "factura": datos.factura.model_dump(), - "partidas": [p.model_dump() for p in datos.partidas], - "totales": datos.totales.model_dump(), - "logo_b64": logo_b64, - } - html_content = self.template.render(**context) - nombre = f"Commercial_Invoice_{datos.factura.numero}.{formato}" - if formato == "html": - return html_content.encode("utf-8"), nombre, "text/html" - - if progress_callback: - progress_callback(90, "Generating PDF...") - options = { - "page-size": "Letter", - "margin-top": "0.5in", - "margin-right": "0.5in", - "margin-bottom": "0.5in", - "margin-left": "0.5in", - "encoding": "UTF-8", - "enable-local-file-access": None, - } - pdf = pdfkit.from_string( - html_content, - False, - options=options, - configuration=self._get_wkhtmltopdf_config(), - ) - - if progress_callback: - progress_callback(100, "Completed") - return pdf, nombre, "application/pdf" diff --git a/backend/api/v1/modules/a76/reports/importacion/packing_list/routes.py b/backend/api/v1/modules/a76/reports/importacion/packing_list/routes.py deleted file mode 100644 index 45869290..00000000 --- a/backend/api/v1/modules/a76/reports/importacion/packing_list/routes.py +++ /dev/null @@ -1,59 +0,0 @@ -from typing import Dict, Any -from fastapi import APIRouter, Depends, Query, Response, HTTPException -from sqlalchemy.orm import Session -from core.database import get_core_db -from core.security import get_current_user, validate_access_to_resource -from api.v1.modules.core.tasks_tracking import track_and_dispatch -from .service import PackingListService - -router = APIRouter() -service = PackingListService() - -from celery.result import AsyncResult -from core.celery_app import celery_app -from .task import generar_packing_list_async - -@router.get("/tasks/{task_id}") -async def get_task_status( - task_id: str, - current_user: Dict[str, Any] = Depends(get_current_user), - db: Session = Depends(get_core_db) -): - task_result = AsyncResult(task_id, app=celery_app) - - response = { - "task_id": task_id, - "state": task_result.state, - "result": None, - "info": None - } - - if task_result.state == 'FAILURE': - response["result"] = str(task_result.result) - elif task_result.state == 'SUCCESS': - response["result"] = task_result.result - elif task_result.state == 'PROCESSING': - response["info"] = task_result.info - - return response - -@router.post("/{invoice_id}/download-async") -async def trigger_download_packing_list( - invoice_id: int, - company_id: int = Query(..., description="ID de la empresa"), - current_user: Dict[str, Any] = Depends(get_current_user), - db: Session = Depends(get_core_db) -): - tenant_id = validate_access_to_resource(db, company_id, current_user) - task = track_and_dispatch( - db=db, - task=generar_packing_list_async, - tenant_id=tenant_id, - company_id=company_id, - requested_by_user=current_user.get("preferred_username") or current_user.get("email") or current_user.get("sub"), - task_name="generar_packing_list_async", - task_group="reports", - task_origin="a76/reports/importacion/packing_list", - args=[invoice_id, company_id], - ) - return {"task_id": task.id, "message": "Generación iniciada"} diff --git a/backend/api/v1/modules/a76/reports/importacion/packing_list/schemas.py b/backend/api/v1/modules/a76/reports/importacion/packing_list/schemas.py deleted file mode 100644 index 464b5539..00000000 --- a/backend/api/v1/modules/a76/reports/importacion/packing_list/schemas.py +++ /dev/null @@ -1,93 +0,0 @@ -from typing import List, Optional, Union, Any -from pydantic import BaseModel, field_validator - -class ClienteSchema(BaseModel): - header: str - nombre: str - direccion: Optional[str] = "" - num_exterior: Optional[str] = "" - num_interior: Optional[str] = "" - colonia: Optional[str] = "" - codigo_postal: Optional[str] = "" - ciudad: Optional[str] = "" - estado: Optional[str] = "" - pais: Optional[str] = "" - tax_id: Optional[str] = "" - programa: Optional[str] = "" - autorizacion: Optional[str] = "" - prosec: Optional[str] = "" - reg_emp: Optional[str] = "" - cert: Optional[str] = "" - - @field_validator('direccion', 'nombre', 'tax_id', mode='before') - @classmethod - def prevent_none(cls, v): - return v or "" - -class FacturaSchema(BaseModel): - numero: str - fecha: str - tipo_cambio: Union[float, str] - moneda: str - pedimento: str = "" - clave_pedimento: str = "" - remesa: str = "" - acuse_electronico: str = "" - agente_aduanal: str = "" - patente: str = "" - precinto: str = "" - regimen: str = "" - transportista: str = "" - scac: str = "" - caat: str = "" - incoterm: str = "" - transporte: str = "" - num_transporte: str = "" - placas: str = "" - placas_remolque: str = "" - licencia_conductor: str = "" - aduana: str = "" - destino: str = "" - observaciones: str = "" - -class PartidaSchema(BaseModel): - numero_parte: str - descripcion: str - fraccion: str - fraccion_americana: Optional[str] = "" - origen: str - - advalorem:Optional[str] = "" - preferencia:Optional[str] = "" - - cantidad_importacion: Union[float, str] - unidad_medida: str - cantidad_bultos: int - clave_bultos: str - peso_neto: Union[float, str] - peso_bruto: Union[float, str] - peso_neto_lbs: Union[float, str] = 0.0 - peso_bruto_lbs: Union[float, str] = 0.0 - valor_costo_unitario: Union[float, str] = "" - valor_total: Union[float, str] = "" - -class TotalesSchema(BaseModel): - cantidad_total: Union[float, str] - bultos_total: int - clave_bultos: str = "" - peso_neto_total: Union[float, str] - peso_bruto_total: Union[float, str] - peso_neto_total_lbs: Union[float, str] = 0.0 - peso_bruto_total_lbs: Union[float, str] = 0.0 - valor_total_total: Union[float, str] = "" - valor_total_dolares: Union[float, str] = "" - -class PackingListSchema(BaseModel): - cliente_proveedor: ClienteSchema - cliente_vendido: ClienteSchema - cliente_enviado: ClienteSchema - factura: FacturaSchema - partidas: List[PartidaSchema] - totales: TotalesSchema - logo_b64: Optional[str] = None - diff --git a/backend/api/v1/modules/a76/reports/importacion/packing_list/service.py b/backend/api/v1/modules/a76/reports/importacion/packing_list/service.py deleted file mode 100644 index 6653b159..00000000 --- a/backend/api/v1/modules/a76/reports/importacion/packing_list/service.py +++ /dev/null @@ -1,466 +0,0 @@ -import shutil -import base64 -import pdfkit -from pathlib import Path -from decimal import Decimal -from typing import Tuple, List, Callable, Optional - -from jinja2 import Environment, FileSystemLoader, select_autoescape -from fastapi import HTTPException -from pydantic import ValidationError -from sqlalchemy.orm import Session - -# --- MODELOS --- -from api.v1.modules.a76.invoices.models import InvoiceHeader, InvoiceLogistics -from api.v1.modules.a76.items.line_quantities.models import LineQuantity -from api.v1.modules.a76.items.line_customs.models import LineCustom -from api.v1.modules.a76.clients_and_providers.models import ( - ClientProvider, ClientProviderAddress, ClientProviderPrograms -) -from api.v1.modules.a76.parts.models import Part -from api.v1.modules.a76.pedmientos.models import Pedimentos -from api.v1.modules.a76.general_catalogs.company.models import Company -from api.v1.modules.a76.customs_brokers.models import CustomsBroker -from api.v1.modules.a76.items.models import LineItem - -# --- TRANSPORTATION MODELS --- -from api.v1.modules.a76.transportation.transporters.models import Transporter -from api.v1.modules.a76.transportation.vehicles.models import Vehicle -from api.v1.modules.a76.transportation.trailers.models import Trailer -from api.v1.modules.a76.transportation.drivers.models import Driver - -# --- MODELO DE FRACCIONES --- -from api.v1.modules.a76.general_catalogs.fractions.tariff_fractions.models import TariffFraction - -# --- SCHEMAS --- -from .schemas import ( - ClienteSchema, PartidaSchema, TotalesSchema, - FacturaSchema, PackingListSchema -) - -class PackingListService: - def __init__(self): - self.template_dir = Path(__file__).parent / "templates" - self.jinja_env = Environment( - loader=FileSystemLoader(self.template_dir), - autoescape=select_autoescape(['html', 'xml']) - ) - self.template = self.jinja_env.get_template('packing_list.html') - - def _get_wkhtmltopdf_config(self): - # List of possible paths - paths = [ - shutil.which("wkhtmltopdf"), - "/usr/local/bin/wkhtmltopdf", - "/usr/bin/wkhtmltopdf", - "C:\\Program Files\\wkhtmltopdf\\bin\\wkhtmltopdf.exe" - ] - - path = next((p for p in paths if p and Path(p).exists()), None) - - if not path: - if shutil.which("echo"): - print("WARNING: wkhtmltopdf not found, PDF generation will fail.") - raise RuntimeError(f"wkhtmltopdf binary not found. Searched in: {paths}") - - return pdfkit.configuration(wkhtmltopdf=path) - - def formatear_numero(self, valor, decimales: int = 2): - """ - Formatea un número con separadores de miles y decimales especificados. - Retorna una cadena formateada para mostrar en reportes. - """ - if valor is None: - valor = 0.0 - try: - num = round(float(valor), decimales) - # Formatear con separadores de miles y decimales - return f"{num:,.{decimales}f}" - except: - return f"0.{'0' * decimales}" - - def _format_fraccion_fallback(self, fraccion_raw: str) -> str: - if not fraccion_raw or len(fraccion_raw) < 8: - return fraccion_raw or "" - return f"{fraccion_raw[:4]}.{fraccion_raw[4:6]}.{fraccion_raw[6:]}" - - def _obtener_datos_cliente(self, db: Session, client_id: int, rol: str) -> ClienteSchema: - main = db.query(ClientProvider).filter(ClientProvider.id == client_id).first() - if not main: - return ClienteSchema(header=rol, nombre="Desconocido", direccion="", tax_id="", codigo_postal="", ciudad="", estado="", pais="MEX") - - addr = db.query(ClientProviderAddress).filter(ClientProviderAddress.client_id == client_id).first() - prog = db.query(ClientProviderPrograms).filter(ClientProviderPrograms.client_id == client_id).first() - - return ClienteSchema( - header=rol, - nombre=(main.name or main.short_name) or "S/N", - direccion=(addr.streets or "") if addr else "", - num_exterior=(addr.exterior_number or "") if addr else "", - num_interior=(addr.interior_number or "") if addr else "", - colonia=(addr.neighborhood or "") if addr else "", - codigo_postal=(addr.postal_code or "") if addr else "", - ciudad=(addr.city or "") if addr else "", - estado=(addr.state or "") if addr else "", - pais=(addr.country or "MEX") if addr else "MEX", - tax_id=getattr(main, "fiscal_id", "") or "", - programa="IMMEX" if (prog and prog.program) else "", - autorizacion=prog.program_number if prog else "", - prosec=prog.prosec_authorization if (prog and prog.prosec and prog.prosec_authorization) else "", - reg_emp=prog.val_certified_company_registry if (prog and hasattr(prog, 'val_certified_company_registry')) else ( - prog.certified_company_registry if (prog and prog.certified_company_registry) else "" - ), - cert=prog.is_certified_company if (prog and prog.is_certified_company) else "" - ) - - def get_packing_list_data(self, db: Session, invoice_id: int, company_id: int, progress_callback: Optional[Callable] = None) -> PackingListSchema: - try: - if progress_callback: progress_callback(10, "Buscando factura...") - header = db.query(InvoiceHeader).filter(InvoiceHeader.id == invoice_id, InvoiceHeader.company_id == company_id).first() - if not header: raise HTTPException(status_code=404, detail="Factura no encontrada") - - compliance = header.compliance_mx - logistics = header.logistics if header.logistics else None - financials = header.financials if header.financials else None - - if progress_callback: progress_callback(20, "Obteniendo datos de pedimento...") - pedimento_id = compliance.pedimento_id if (compliance and compliance.pedimento_id) else header.related_doc_id - pedimento = db.query(Pedimentos).filter(Pedimentos.id == pedimento_id).first() if pedimento_id else None - - if progress_callback: progress_callback(30, "Obteniendo cliente y proveedor...") - proveedor_id = compliance.provider_id if compliance else None - cliente_proveedor = self._obtener_datos_cliente(db, proveedor_id, "Proveedor / supplier:") if proveedor_id else ClienteSchema(header="Proveedor", nombre="No Asignado", direccion="", tax_id="", codigo_postal="", ciudad="", estado="", pais="") - - nombre_agente = "" - if compliance and compliance.customs_broker_id: - broker = db.query(CustomsBroker).filter(CustomsBroker.id == compliance.customs_broker_id).first() - if broker: nombre_agente = broker.name - - company = db.query(Company).filter(Company.id == header.company_id).first() - # Datos Default (Company/Importer) - cliente_default = ClienteSchema( - header="Importer / Consignee:", - nombre=getattr(company, 'name', "Empresa Local"), - direccion="DOMICILIO FISCAL", - num_exterior="", colonia="", codigo_postal="", ciudad="", estado="", pais="MEX", - tax_id=getattr(company, 'rfc', ""), - programa=getattr(company, 'program', "IMMEX"), autorizacion=getattr(company, 'program_number', "") - ) - - # Left Side Logic (Consignatario / Sold To) - cliente_vendido = cliente_default - if compliance and compliance.sold_to_id: - raw = (compliance.sold_to_header or "").upper() - if "CONSIGN" in raw: - clean_header = "Consignee / Consignatario:" - else: - clean_header = "Sold To / Vendido a:" - - cliente_vendido = self._obtener_datos_cliente(db, compliance.sold_to_id, clean_header) - - # Right Side Logic (Enviado A / Shipped To) - cliente_enviado = cliente_default - if compliance and compliance.shipped_to_id: - clean_header_shipped = "Shipped To / Enviado a:" - cliente_enviado = self._obtener_datos_cliente(db, compliance.shipped_to_id, clean_header_shipped) - - remesa_valor = str(compliance.remesa) if (compliance and compliance.remesa) else "" - acuse_valor = str(compliance.edocument) if (compliance and compliance.edocument) else "N/A" - - patente_val = "" - if pedimento and pedimento.license: - patente_val = pedimento.license - elif 'broker' in locals() and broker and broker.license: - patente_val = broker.license - - # --- Transport Data Fetching --- - transporte_txt = str(logistics.transport_type) if (logistics and logistics.transport_type) else "" - num_transporte_val = (logistics.trailer_num or "") if logistics else "" - - placas_val = (logistics.license_plate or "") if logistics else "" - placas_remolque_val = "" - transportista_val = (logistics.carrier_id or "") if logistics else "" - caat_val = "" - scac_val = "" - licencia_cond_val = "" - - if logistics: - # 1. Transporter (CAAT / SCAC) - if logistics.carrier_int_id is not None: - transporter_obj = ( - db.query(Transporter) - .filter(Transporter.transporter_id == logistics.carrier_int_id) - .first() - ) - elif logistics.carrier_id: - transporter_obj = ( - db.query(Transporter) - .filter(Transporter.transporter_key == logistics.carrier_id) - .first() - ) - if transporter_obj: - caat_val = transporter_obj.caat_code or "" - scac_val = transporter_obj.transport_code or "" - transportista_val = transporter_obj.name or logistics.carrier_id - - # 2. Vehicle (Placas Tracto) - if logistics.transport_id: - if logistics.transport_int_id is not None: - veh_obj = ( - db.query(Vehicle) - .filter(Vehicle.vehicle_id == logistics.transport_int_id) - .first() - ) - else: - veh_obj = ( - db.query(Vehicle) - .filter(Vehicle.vehicle_key == logistics.transport_id) - .first() - ) - if veh_obj: - placas_val = veh_obj.plate_number or placas_val - elif logistics.vehicle_num: - veh_obj = db.query(Vehicle).filter(Vehicle.vehicle_key == logistics.vehicle_num).first() - if veh_obj: - placas_val = veh_obj.plate_number or placas_val - - # 3. Trailer (Placas Remolque) - if logistics.trailer_num: - if logistics.trailer_int_id is not None: - trl_obj = ( - db.query(Trailer) - .filter(Trailer.trailer_id == logistics.trailer_int_id) - .first() - ) - else: - trl_obj = ( - db.query(Trailer) - .filter(Trailer.trailer_number == logistics.trailer_num) - .first() - ) - if trl_obj: - placas_remolque_val = trl_obj.plate_number or "" - - # 4. Driver (License) - if logistics.carrier_id and logistics.driver_name: - drv_obj = db.query(Driver).filter( - Driver.transporter_key == logistics.carrier_id, - Driver.driver_name == logistics.driver_name - ).first() - if drv_obj: - licencia_cond_val = drv_obj.license_number or "" - - factura_schema = FacturaSchema( - numero=header.invoice_number or "S/N", - fecha=str(header.invoice_date) if header.invoice_date else "", - tipo_cambio=float(financials.exchange_rate) if (financials and financials.exchange_rate) else (float(pedimento.exchange_rate) if pedimento and pedimento.exchange_rate else 1.0), - moneda=getattr(header, 'currency', "USD") or "USD", - incoterm=(logistics.incoterm or "") if logistics else "", - observaciones=header.observation_es or header.observation_en or "", - pedimento=f"{pedimento.year} {pedimento.customs_office[:2] if pedimento.customs_office else ''} {pedimento.license} {pedimento.pedimento_number}" if pedimento else "", - clave_pedimento=pedimento.pedimento_code if pedimento else "", - regimen=header.document_type or "", - patente=patente_val, - agente_aduanal=nombre_agente, - transporte=transporte_txt, - num_transporte=num_transporte_val, - placas=placas_val, - placas_remolque=placas_remolque_val, - transportista=transportista_val, - caat=caat_val, - scac=scac_val, - licencia_conductor=licencia_cond_val, - aduana=compliance.aduana if (compliance and compliance.aduana) else (pedimento.customs_office[:2] if (pedimento and pedimento.customs_office) else ""), - precinto=(logistics.seal_number or "") if logistics else "", - destino=(logistics.destination_goods or "") if logistics else "", - remesa=remesa_valor, acuse_electronico=acuse_valor - ) - - if progress_callback: progress_callback(50, "Procesando partidas...") - lines = db.query(LineItem).filter(LineItem.invoice_id == header.id).all() - partidas_list = [] - - # Initialize raw totals - raw_cant = 0.0 - raw_peso_n = 0.0 - raw_peso_b = 0.0 - raw_peso_n_lb = 0.0 - raw_peso_b_lb = 0.0 - raw_bultos = 0 - - for line in lines: - weight_type = db.query(InvoiceLogistics.weight_type).filter(InvoiceLogistics.invoice_id == line.invoice_id).scalar() - qty = db.query(LineQuantity).filter(LineQuantity.item_line_id == line.id).first() - - # --- WEIGHT CALCULATION LOGIC --- - peso_neto_kg = 0.0 - peso_bruto_kg = 0.0 - peso_neto_lb = 0.0 - peso_bruto_lb = 0.0 - - raw_net = float(qty.net_weight or 0) - raw_gross = float(qty.gross_weight or 0) - unit = (weight_type or "KGS").upper() - - if unit == "LBS": - peso_neto_lb = raw_net - peso_bruto_lb = raw_gross - peso_neto_kg = raw_net / 2.20462 - peso_bruto_kg = raw_gross / 2.20462 - else: # Default KG - peso_neto_kg = raw_net - peso_bruto_kg = raw_gross - peso_neto_lb = raw_net * 2.20462 - peso_bruto_lb = raw_gross * 2.20462 - # -------------------------------- - - # Accumulate raw totals - raw_cant += float(qty.quantity) if qty and qty.quantity else 0.0 - raw_peso_n += peso_neto_kg - raw_peso_b += peso_bruto_kg - raw_peso_n_lb += peso_neto_lb - raw_peso_b_lb += peso_bruto_lb - raw_bultos += int(qty.package_quantity) if qty and qty.package_quantity else 0 - - custom_obj = db.query(LineCustom).filter(LineCustom.item_line_id == line.id).first() - part_master = db.query(Part).filter(Part.id == line.part_number_id).first() - - desc_final = "S/D" - num_parte_final = str(line.part_number_id or "S/N") - fraccion_raw = "" - origen_final = "MEX" - uom_comercial = "PZA" # Default UOM - - if part_master: - desc_final = part_master.description_spanish or part_master.description_english or "Sin Desc." - num_parte_final = part_master.part_number - fraccion_raw = part_master.fraction if part_master.fraction else "" - # Commercial UOM from Part Master - uom_comercial = part_master.unit_of_measure or "PZA" - - if part_master.fa_data and part_master.fa_data.origin_country: - origen_final = part_master.fa_data.origin_country - - fraccion_limpia = fraccion_raw.replace(".", "").strip() - if fraccion_limpia: - fraccion_limpia = fraccion_limpia[:8].zfill(8) - - fraccion_db = db.query(TariffFraction).filter(TariffFraction.code == fraccion_limpia).first() - - fraccion_imprimir = fraccion_raw - if fraccion_db: - fraccion_imprimir = fraccion_db.fraction or fraccion_raw - else: - fraccion_imprimir = self._format_fraccion_fallback(fraccion_limpia) - - # FOR PACKING LIST: FINANCIALS ARE HIDDEN/EMPTY - v_unitario = "" - v_total = "" - - partidas_list.append(PartidaSchema( - numero_parte=num_parte_final, - descripcion=desc_final, - fraccion=fraccion_imprimir, - fraccion_americana=custom_obj.american_fraction if custom_obj and custom_obj.american_fraction else "", - origen=origen_final, - advalorem="", # Hidden - preferencia="", # Hidden - cantidad_importacion=qty.quantity if qty else 0, - unidad_medida=uom_comercial, # Commercial UOM (PCS, EA) - cantidad_bultos=int(qty.package_quantity) if qty and qty.package_quantity else 0, - clave_bultos=(qty.package_info.key if qty and qty.package_info else "") if qty else "", - peso_neto=self.formatear_numero(peso_neto_kg), - peso_bruto=self.formatear_numero(peso_bruto_kg), - peso_neto_lbs=self.formatear_numero(peso_neto_lb), - peso_bruto_lbs=self.formatear_numero(peso_bruto_lb), - valor_costo_unitario=v_unitario, # Hidden - valor_total=v_total # Hidden - )) - - totales = self.calcular_totales( - partidas_list, - Decimal(factura_schema.tipo_cambio), - raw_cant=raw_cant, - raw_peso_n=raw_peso_n, - raw_peso_b=raw_peso_b, - raw_peso_n_lbs=raw_peso_n_lb, - raw_peso_b_lbs=raw_peso_b_lb, - raw_bultos=raw_bultos - ) - - return PackingListSchema( - cliente_proveedor=cliente_proveedor, cliente_vendido=cliente_vendido, - cliente_enviado=cliente_enviado, factura=factura_schema, - partidas=partidas_list, totales=totales - ) - - except ValidationError as e: - print(f"Validation Error: {e.json()}") - raise HTTPException(status_code=500, detail=f"Schema Error: {e}") - except Exception as e: - print(f"Error Service A76: {e}") - raise HTTPException(status_code=500, detail=f"Error: {str(e)}") - - def calcular_totales(self, - partidas: List[PartidaSchema], - tipo_cambio: Decimal, - raw_cant: float = 0, - raw_peso_n: float = 0, - raw_peso_b: float = 0, - raw_peso_n_lbs: float = 0, - raw_peso_b_lbs: float = 0, - raw_bultos: int = 0 - ) -> TotalesSchema: - # Use raw values to avoid ValueError/TypeError with formatted strings - claves = [p.clave_bultos for p in partidas if p.clave_bultos] - clave_comun = max(set(claves), key=claves.count) if claves else "" - if raw_bultos > 1 and clave_comun and not clave_comun.endswith("S"): clave_comun += "S" - - return TotalesSchema( - cantidad_total=self.formatear_numero(raw_cant), bultos_total=raw_bultos, clave_bultos=clave_comun, - peso_neto_total=self.formatear_numero(raw_peso_n), peso_bruto_total=self.formatear_numero(raw_peso_b), - peso_neto_total_lbs=self.formatear_numero(raw_peso_n_lbs), peso_bruto_total_lbs=self.formatear_numero(raw_peso_b_lbs), - valor_total_total="", valor_total_dolares="" - ) - - def generate_pdf(self, db: Session, invoice_id: int, company_id: int, progress_callback: Optional[Callable] = None) -> Tuple[bytes, str]: - if progress_callback: progress_callback(5, "Iniciando servicio de reporte...") - data = self.get_packing_list_data(db, invoice_id, company_id, progress_callback) - - if progress_callback: progress_callback(80, "Renderizando plantilla...") - - # LOGO LOGIC - logo_b64 = None - try: - comp_logo = db.query(Company).filter(Company.id == company_id).first() - if comp_logo and comp_logo.logo: - p = Path(comp_logo.logo) - target_path = p - if not target_path.exists(): - fallback = Path(f"app_data/logos/{company_id}") / p.name - if fallback.exists(): - target_path = fallback - - if target_path.exists(): - with open(target_path, "rb") as image_file: - encoded_string = base64.b64encode(image_file.read()).decode('utf-8') - mime = "image/png" - if target_path.suffix.lower() in ['.jpg', '.jpeg']: mime = "image/jpeg" - logo_b64 = f"data:{mime};base64,{encoded_string}" - except Exception as e: - print(f"Error loading logo: {e}") - - data.logo_b64 = logo_b64 # Assign logo to schema - - context = data.model_dump() - html_content = self.template.render(**context) - - if progress_callback: progress_callback(90, "Generando PDF final...") - - options = {'page-size': 'Letter', 'margin-top': '0.5in', 'margin-right': '0.5in', 'margin-bottom': '0.5in', 'margin-left': '0.5in', 'encoding': "UTF-8", 'enable-local-file-access': None} - pdf = pdfkit.from_string(html_content, False, options=options, configuration=self._get_wkhtmltopdf_config()) - - if progress_callback: progress_callback(100, "Completado") - - filename = f"PackingList_{data.factura.numero}.pdf" - return pdf, filename diff --git a/backend/api/v1/modules/a76/reports/importacion/packing_list/task.py b/backend/api/v1/modules/a76/reports/importacion/packing_list/task.py deleted file mode 100644 index 338380ba..00000000 --- a/backend/api/v1/modules/a76/reports/importacion/packing_list/task.py +++ /dev/null @@ -1,52 +0,0 @@ -from fastapi import HTTPException -import base64 -from core.celery_app import celery_app -from core.database import CoreSessionLocal -from .service import PackingListService - -@celery_app.task(bind=True) -def generar_packing_list_async(self, invoice_id: int, company_id: int): - """ - Tarea asíncrona para generar el Packing List - """ - db = CoreSessionLocal() - try: - service = PackingListService() - - def update_progress(percent, message): - self.update_state( - state='PROCESSING', - meta={ - 'current': percent, - 'total': 100, - 'status': message - } - ) - - pdf_bytes, filename = service.generate_pdf(db, invoice_id, company_id, update_progress) - - # Codificar a base64 para enviar por JSON - pdf_b64 = base64.b64encode(pdf_bytes).decode('utf-8') - - return { - 'status': 'success', - 'file_name': filename, - 'content': pdf_b64, - 'media_type': 'application/pdf' - } - - except Exception as e: - print(f"Error en tarea Packing List: {e}") - import traceback - traceback.print_exc() - self.update_state( - state='FAILURE', - meta={ - 'exc_type': type(e).__name__, - 'exc_message': str(e), - 'custom': 'Error generating PDF' - } - ) - raise e - finally: - db.close() diff --git a/backend/api/v1/modules/a76/reports/importacion/packing_list/templates/packing_list.html b/backend/api/v1/modules/a76/reports/importacion/packing_list/templates/packing_list.html deleted file mode 100644 index a82f2aaa..00000000 --- a/backend/api/v1/modules/a76/reports/importacion/packing_list/templates/packing_list.html +++ /dev/null @@ -1,519 +0,0 @@ - - - - - - Packing List - {{ factura.numero }} - - - - -
-
-
-

PACKING LIST / LISTA DE EMPAQUE

-
-

-
-
- - - - - - - - - - - -
-

PACKING LIST / LISTA DE EMPAQUE:

-
-

{{ factura.numero }}

-
-

MX CUSTOM BROKER / AGENTE ADUANAL MEXICANO:

-
-

{{ factura.agente_aduanal or '' }}

-
-
-
-
-
- {% if logo_b64 %} -
- -
- {% endif %} -
-

{{ cliente_proveedor.header }}

-

{{ cliente_proveedor.nombre }}

-

{{ cliente_proveedor.direccion }} - {% if cliente_proveedor.num_exterior %} Ext: {{ cliente_proveedor.num_exterior }}{% endif %} - {% if cliente_proveedor.num_interior %} Int: {{ cliente_proveedor.num_interior }}{% endif %} -

-

{{ cliente_proveedor.colonia }} {% if cliente_proveedor.codigo_postal %} CP: {{ - cliente_proveedor.codigo_postal }}{% endif %}

-

{{ cliente_proveedor.ciudad }}, {{ cliente_proveedor.estado }}, {{ cliente_proveedor.pais }}

-

TAX ID: {{ cliente_proveedor.tax_id }} - {% if cliente_proveedor.programa and cliente_proveedor.programa != 'Ninguno' %} - {{ cliente_proveedor.programa }}: {{ cliente_proveedor.autorizacion }} - {% endif %} -

-


-
-
- - -
- -
-
-

{{ cliente_vendido.header }}

-

{{ cliente_vendido.nombre }}

-

{{ cliente_vendido.direccion }} - {% if cliente_vendido.num_exterior %} Ext: {{ cliente_vendido.num_exterior }}{% endif %} - {% if cliente_vendido.num_interior %} Int: {{ cliente_vendido.num_interior }}{% endif %} -

-

{{ cliente_vendido.colonia }} {% if cliente_vendido.codigo_postal %} CP: {{ - cliente_vendido.codigo_postal }}{% endif %}

-

{{ cliente_vendido.ciudad }}, {{ cliente_vendido.estado }}, {{ cliente_vendido.pais }} -

-

RFC: {{ cliente_vendido.tax_id }} - {% if cliente_vendido.programa and cliente_vendido.programa != 'Ninguno' %} - {{ cliente_vendido.programa }}: {{ cliente_vendido.autorizacion }} - {% endif %} -

-

- {% if cliente_vendido.prosec %}PROSEC: {{ cliente_vendido.prosec }} {% endif %} - {% if cliente_vendido.reg_emp %}REG EMP: {{ cliente_vendido.reg_emp }} {% endif %} - {% if cliente_vendido.cert %}CERT: {{ cliente_vendido.cert }}{% endif %} -

-
- -
-

{{ cliente_enviado.header }}

-

{{ cliente_enviado.nombre }}

-

{{ cliente_enviado.direccion }} - {% if cliente_enviado.num_exterior %} Ext: {{ cliente_enviado.num_exterior }}{% endif %} - {% if cliente_enviado.num_interior %} Int: {{ cliente_enviado.num_interior }}{% endif %} -

-

{{ cliente_enviado.colonia }} {% if cliente_enviado.codigo_postal %} CP: {{ - cliente_enviado.codigo_postal }}{% endif %}

-

{{ cliente_enviado.ciudad }}, {{ cliente_enviado.estado }}, {{ cliente_enviado.pais }} -

-

RFC: {{ cliente_enviado.tax_id }} - {% if cliente_enviado.programa and cliente_enviado.programa != 'Ninguno' %} - {{ cliente_enviado.programa }}: {{ cliente_enviado.autorizacion }} - {% endif %} -

-

- {% if cliente_enviado.prosec %}PROSEC: {{ cliente_enviado.prosec }} {% endif %} - {% if cliente_enviado.reg_emp %}REG EMP: {{ cliente_enviado.reg_emp }} {% endif %} - {% if cliente_enviado.cert %}CERT: {{ cliente_enviado.cert }}{% endif %} -

-
-
-


-
- - - - - - - - - - - - - - - - - - - - - - - - - - {% for partida in partidas %} - - - - - - - - - - - {% endfor %} - - - - - - - - - - - - - - - - - - - - -
-

Line / Línea

-
-

Part Number / Número de Parte

-

Description / Descripción

-
-

Quantity / Cantidad

-
-

Packing / Empaque

-
-

Weight / Peso (KGS)

-
-

Qty / Cant.

-
-

U.M.

-
-

Qty / Cant.

-
-

Type / Tipo

-
-

Net / Neto

-

(LBS / KGS)

-
-

Gross / Bruto

-

(LBS / KGS)

-
-

{{ loop.index }}

-
-

{{ partida.numero_parte }}

-

{{ partida.descripcion }} / {{ partida.fraccion_americana }} / {{ partida.origen }} -

-
-

{{ partida.cantidad_importacion }}

-
-

{{ partida.unidad_medida }}

-
-

- {% if partida.cantidad_bultos != 0 %}{{ partida.cantidad_bultos }}{% endif %} -

-
-

- {{ partida.clave_bultos }} -

-
-

{{ partida.peso_neto_lbs }}

-

{{ partida.peso_neto }}

-
-

{{ partida.peso_bruto_lbs }}

-

{{ partida.peso_bruto }}

-
-

- Observaciones: - TOTALES -

-
-

{{ totales.cantidad_total }}

-
-

- {% if totales.bultos_total != 0 %}{{ totales.bultos_total }}{% endif %} -

-
-

- {{ totales.clave_bultos or '' }} -

-
-

{{ totales.peso_neto_total_lbs }} LBS

-

{{ totales.peso_neto_total }} KGS

-
-

{{ totales.peso_bruto_total_lbs }} LBS

-

{{ totales.peso_bruto_total }} KGS

-
-

{{ factura.observaciones }}

-
-

-

{{ cliente_proveedor.nombre }}

-


-
-


-
- - - \ No newline at end of file diff --git a/backend/api/v1/modules/a76/reports/importacion/transmission/definitive/MAINX30/processors.py b/backend/api/v1/modules/a76/reports/importacion/transmission/definitive/MAINX30/processors.py deleted file mode 100644 index c5b917f9..00000000 --- a/backend/api/v1/modules/a76/reports/importacion/transmission/definitive/MAINX30/processors.py +++ /dev/null @@ -1,370 +0,0 @@ -from typing import List, Dict, Any, Tuple -from sqlalchemy.orm import Session, joinedload -from datetime import datetime - -from .schemas import Mainx30GenerationRequest, ErrorValidacion - -# --- MODELOS A76 --- -from api.v1.modules.a76.invoices.models import InvoiceHeader, InvoiceComplianceMx -from api.v1.modules.a76.items.models import LineItem -from api.v1.modules.a76.clients_and_providers.models import ClientProvider - -class ScaiiProcessor: - def __init__(self): - self.cuenta_partidas = 0 - self.cuenta_facturas = 0 - self.valor_total_factura = 0.0 - self.flete_total = 0.0 - self.peso_bruto_factura = 0.0 - self.peso_neto_factura = 0.0 - self.errores: List[ErrorValidacion] = [] - - def _obtener_datos_cliente(self, cliente: ClientProvider) -> dict: - """Extrae de manera segura los datos del cliente/dirección""" - address = cliente.address - pais_raw = (address.country or "MX").upper() if address else "MX" - - pais = "MX" - if pais_raw in ["MEXICO", "MEX", "MX"]: - pais = "MX" - elif pais_raw in ["USA", "US", "UNITED STATES"]: - pais = "US" - else: - pais = pais_raw[:2] - - # Identificador fiscal del cliente: RFC (nacional) o TAX-ID (extranjero) - tax_id = cliente.fiscal_id or "" - - data = { - "nombre": (cliente.name or "")[:39], - "tax_id": tax_id[:15], - "broker": "", "calle": "", "cp": "", "ciudad": "", "estado": "", "pais": pais, "tel": "", - "manufacturer_id": "", "tipo_ext_nac": "E" - } - - # TipoExtNac logic from Clarion: N (National/ManufacturerID) or E (External/TaxID) - # We'll use TaxID as default for definitive if not specified - data["manufacturer_id"] = (cliente.programs.manufacturer_id or "")[:15] if cliente.programs else "" - - if cliente.programs: - data["broker"] = (cliente.programs.broker or "")[:6] - - if address: - calle_base = address.streets or "" - num_base = address.exterior_number or "" - # Prevent 'None' string - calle_comp = f"{calle_base} {num_base}".strip() - - # Clarion expects 20 chars for city_state: 5 CP + 11 City + 4 State - cp_formatted = (address.postal_code or "")[:5] - city_formatted = (address.city or "")[:11] - state_formatted = (address.state or "")[:4] - data["city_state"] = f"{cp_formatted:<5}{city_formatted:<11}{state_formatted:<4}" - data["calle"] = calle_comp[:35] - data["cp"] = (address.postal_code or "")[:9] - data["ciudad"] = (address.city or "").strip()[:20] - data["estado"] = (address.state or "").strip()[:2].upper() - data["tel"] = (address.phone or "")[:15] - - return data - - def _agregar_error(self, partida, id_err, desc, sol, tipo): - self.errores.append(ErrorValidacion(partida=partida, linea=0, descripcion=f"[ {id_err} ] {desc}", soluciones=sol, identificador=tipo)) - -class ScafDefProcessor(ScaiiProcessor): - """Procesador para Importación Definitiva basado en lógica Clarion (SComprasMexID)""" - def __init__(self): - super().__init__() - - def procesar_facturas( - self, db: Session, facturas_nums: List[str], empresa_dict: Dict[str, Any], request: Mainx30GenerationRequest - ) -> Tuple[List[str], List[ErrorValidacion]]: - lineas = [] - self.errores = [] - - # 1. Traer Facturas por número - facturas = db.query(InvoiceHeader).join( - InvoiceComplianceMx, InvoiceHeader.id == InvoiceComplianceMx.invoice_id - ).options( - joinedload(InvoiceHeader.financials), - joinedload(InvoiceHeader.compliance_mx), - joinedload(InvoiceHeader.logistics) - ).filter( - InvoiceHeader.invoice_number.in_(facturas_nums) - ).all() - - entry_port = request.entry_port or "" - exit_port = request.exit_port or "" - fecha_trans = datetime.now().strftime("%y%m%d") - - entry_port_desc = empresa_dict.get('entry_port_desc', 'PUERTO ENTRADA')[:15] - exit_port_desc = empresa_dict.get('exit_port_desc', 'PUERTO SALIDA')[:15] - main_activity = empresa_dict.get('main_activity', 'RAW MATERIAL')[:30] - city_state = empresa_dict.get('city_state', '')[:30] - - for factura in facturas: - self.cuenta_facturas += 1 - f_val_total = 0.0 - f_pb = 0.0 - f_pn = 0.0 - f_consec_partidas = 0 - - # MF01: Header - mod_trans = factura.logistics.transport_mode if factura.logistics else "30" - f_fecha = factura.invoice_date.strftime("%y%m%d") if factura.invoice_date else fecha_trans - - lineas.append( - f"MF01{empresa_dict['broker'][:6]:<6}" - f"{exit_port[:5]:<5}" - f"{entry_port[:5]:<5}" - f"{fecha_trans}" - f" {mod_trans[:2]:<2}" - f"{f_fecha}" - f"{factura.invoice_number[:15]:<15}" - ) - self.cuenta_partidas += 1 - - # MF03 - conductor = (factura.logistics.driver_name or "")[:23] if factura.logistics else "" - carrier = (factura.logistics.carrier_id or "")[:10] - - lineas.append( - f"MF03{carrier:<10}{conductor:<23}{entry_port_desc:<15}{exit_port_desc:<15}" - ) - self.cuenta_partidas += 1 - - # MF20 - num_transporte = (factura.logistics.transport_num or "")[:15] if factura.logistics else "" - - # Use dynamic city/state from entry_port description if available, otherwise blank - # Clarion format: 5 digit Zip (if relevant) + City State - # We will use the entry_port_desc (e.g. CD. JUAREZ CHIH) and assume a dummy zip '00000' if not parsed - # Or better, just use the entry_port_desc fully aligned - - # Using entry_port_desc directly instead of hardcoded default - cruce_desc = entry_port_desc[:20] if entry_port_desc else " " - - lineas.append( - f"MF20{factura.invoice_number[:15]:<15}I{num_transporte:<15}00000{cruce_desc:<20}" - f"{exit_port[:5]:<5}{entry_port_desc:<15} " - ) - self.cuenta_partidas += 1 - - # MF22 - lineas.append(f"MF22{main_activity:<60}") - self.cuenta_partidas += 1 - - # IV01 - flete = int(round(float(factura.financials.freight or 0))) if factura.financials else 0 - self.flete_total += float(factura.financials.freight or 0) if factura.financials else 0.0 - - s_tax = ""; c_tax = "" - if factura.compliance_mx: - if factura.compliance_mx.sold_to_id: - c_obj = db.query(ClientProvider).filter(ClientProvider.id == factura.compliance_mx.sold_to_id).first() - if c_obj: c_tax = (c_obj.fiscal_id or "")[:12] - if factura.compliance_mx.provider_id: - s_obj = db.query(ClientProvider).filter(ClientProvider.id == factura.compliance_mx.provider_id).first() - if s_obj: s_tax = (s_obj.fiscal_id or "")[:12] - - # IV01 uses 11 spaces then 'C' per Clarion logic - # Adjusted validation for RFCs to avoid crashes or None - s_tax_safe = s_tax if s_tax else " " - c_tax_safe = c_tax if c_tax else " " - - lineas.append( - f"IV01{factura.invoice_number[:15]:<15}{f_fecha}78{' ':<12}C" - f"{empresa_dict['broker'][:6]:<6}{flete:08d}{s_tax_safe:<13}{c_tax_safe:<13}" - ) - self.cuenta_partidas += 1 - - # IV02: Vendor/Manufacturer Info - # Clarion: CliVen:ManufacterID, CliVen:Nombre - manufacturer_id = "" - vendor_name = "" - if factura.compliance_mx and factura.compliance_mx.provider_id: - vendor = db.query(ClientProvider).options( - joinedload(ClientProvider.programs) - ).filter(ClientProvider.id == factura.compliance_mx.provider_id).first() - if vendor: - manufacturer_id = (vendor.programs.manufacturer_id or "")[:16] if vendor.programs else "" - vendor_name = (vendor.name or "")[:39] - - lineas.append(f"IV02{manufacturer_id:<16}{vendor_name:<39}") - self.cuenta_partidas += 1 - - # IV10: Responsible - lineas.append(f"IV10 {main_activity[:30]:<30}{empresa_dict['responsable'][:30]:<30}") - self.cuenta_partidas += 1 - - # IV11: Obs - lineas.append(f"IV11H") - self.cuenta_partidas += 1 - - obs_line = f"IV11F{(factura.notes or '')[:70]:<70}" if request.iv11 else "IV11F" - lineas.append(obs_line) - self.cuenta_partidas += 1 - - # Participants IV12-14 (S, C, T, I) - # Shipper (S) - if factura.compliance_mx and factura.compliance_mx.provider_id: - s_cliente = db.query(ClientProvider).options( - joinedload(ClientProvider.address), - joinedload(ClientProvider.programs) - ).filter(ClientProvider.id == factura.compliance_mx.provider_id).first() - if s_cliente: - s_data = self._obtener_datos_cliente(s_cliente) - broker_impo = s_data['broker'] or empresa_dict['broker'] - lineas.append(f"IV12S{broker_impo[:6]:<6}{s_data['nombre'][:39]:<39}") - # Ensure spacing aligns with Clarion example (30 spaces + 35 address + space + CP) - lineas.append(f"IV13S{'':<30}{s_data['calle']:<35} {s_data['cp']:<9}") - # IV14: City(20)+State(2)+Country(2)+Phone(30?? No, Clarion example shows Phone then TaxID) - # Clarion example: IV14S... CITY... STMX... TAXID... TEL... - # Re-aligning based on provided example: - # IV14SAKRON NEUS 16-0919851 00000 - # City (20) State (2) Country (2) Space(30) TaxID(15) Tel(5?) - - lineas.append(f"IV14S{s_data['ciudad']:<20}{s_data['estado']:<2}{s_data['pais']:<2}{'':<30}{s_data['tax_id']:<15}{s_data['tel'][:5]:<5}") - self.cuenta_partidas += 3 - - # Consignee (C), Ship To (T), and Importer (I) - if factura.compliance_mx and factura.compliance_mx.sold_to_id: - c_cliente = db.query(ClientProvider).options( - joinedload(ClientProvider.address), - joinedload(ClientProvider.programs) - ).filter(ClientProvider.id == factura.compliance_mx.sold_to_id).first() - if c_cliente: - c_data = self._obtener_datos_cliente(c_cliente) - broker_impo = c_data['broker'] or empresa_dict['broker'] - - # Consignee (C) - lineas.append(f"IV12C{broker_impo[:6]:<6}{c_data['nombre'][:39]:<39}") - lineas.append(f"IV13C{'':<30}{c_data['calle']:<35} {c_data['cp']:<9}") - lineas.append(f"IV14C{c_data['ciudad']:<20}{c_data['estado']:<2}{c_data['pais']:<2}{'':<30}{c_data['tax_id']:<15}{c_data['tel'][:5]:<5}") - self.cuenta_partidas += 3 - - # Ship To (T) - check if different - t_id = factura.compliance_mx.shipped_to_id - if t_id and t_id != factura.compliance_mx.sold_to_id: - t_cl = db.query(ClientProvider).options( - joinedload(ClientProvider.address), - joinedload(ClientProvider.programs) - ).filter(ClientProvider.id == t_id).first() - if t_cl: - t_data = self._obtener_datos_cliente(t_cl) - b_t = t_data['broker'] or empresa_dict['broker'] - lineas.append(f"IV12T{b_t[:6]:<6}{t_data['nombre'][:39]:<39}") - lineas.append(f"IV13T{'':<30}{t_data['calle']:<35} {t_data['cp']:<9}") - lineas.append(f"IV14T{t_data['ciudad']:<20}{t_data['estado']:<2}{t_data['pais']:<2}{'':<30}{t_data['tax_id']:<15}{t_data['tel'][:5]:<5}") - self.cuenta_partidas += 3 - - # Importer (I) - Usually same as Consignee in Definitive unless ShippedBy is set - i_id = factura.compliance_mx.shipped_by_id - if i_id and i_id != factura.compliance_mx.sold_to_id and i_id != t_id: - i_cl = db.query(ClientProvider).options( - joinedload(ClientProvider.address), - joinedload(ClientProvider.programs) - ).filter(ClientProvider.id == i_id).first() - if i_cl: - i_data = self._obtener_datos_cliente(i_cl) - b_i = i_data['broker'] or empresa_dict['broker'] - lineas.append(f"IV12I{b_i[:6]:<6}{i_data['nombre'][:39]:<39}") - lineas.append(f"IV13I{'':<30}{i_data['calle']:<35} {i_data['cp']:<9}") - lineas.append(f"IV14I{i_data['ciudad']:<20}{i_data['estado']:<2}{i_data['pais']:<2}{'':<30}{i_data['tax_id']:<15}{i_data['tel'][:5]:<5}") - self.cuenta_partidas += 3 - else: - # Fallback: repeat C as I if not specified (following Clarion pattern) - lineas.append(f"IV12I{broker_impo[:6]:<6}{c_data['nombre'][:39]:<39}") - lineas.append(f"IV13I{'':<30}{c_data['calle']:<35} {c_data['cp']:<9}") - lineas.append(f"IV14I{c_data['ciudad']:<20}{c_data['estado']:<2}{c_data['pais']:<2}{'':<30}{c_data['tax_id']:<15}{c_data['tel'][:5]:<5}") - self.cuenta_partidas += 3 - - # Items IV20-27 - items_headers = db.query(LineItem).filter(LineItem.invoice_id == factura.id).all() - item_ids = [ih.id for ih in items_headers] - - if item_ids: - items_query = db.query(LineItem).filter( - LineItem.id.in_(item_ids) - ).options( - joinedload(LineItem.part_info), - joinedload(LineItem.description), - joinedload(LineItem.financial), - joinedload(LineItem.quantity), - joinedload(LineItem.customs), - joinedload(LineItem.unit_of_measure_info) - ).all() - - for line in items_query: - f_consec_partidas += 1 - part_num = (line.part_info.part_number if line.part_info else "S/N")[:25] - po = (line.order or "")[:20] if line.financial else "" - - # IV20 - lineas.append(f"IV20{f_consec_partidas:03d} {part_num:<25}C {po:<20}") - self.cuenta_partidas += 1 - - # IV21 - desc_ingles = (line.description.description_english or "")[:50] if line.description else "" - lineas.append(f"IV21 {desc_ingles:<50}") - self.cuenta_partidas += 1 - - # Data for IV22 - val_me = float(line.financial.value_usd or 0) if line.financial else 0.0 - qty = float(line.quantity.quantity or 0) if line.quantity else 0.0 - pb = float(line.quantity.gross_weight or 0) if line.quantity else 0.0 - pn = float(line.quantity.net_weight or 0) if line.quantity else 0.0 - costo_u = float(line.financial.unit_cost_capture or 0) if line.financial else 0.0 - - um = (line.unit_of_measure_info.american_code or "PCS")[:3] if line.unit_of_measure_info else "PCS" - pais = ((line.customs.origin_country or "MX")[:2]).upper() if line.customs else "MX" - hts = (line.customs.american_fraction or "").replace(".", "")[:10] if line.customs else "" - - # Formato Clarion @n...v... - val_int = int(round(val_me * 100)) # @n010v2 - qty_int = int(round(qty * 100)) # @n09v2 - pb_int = int(round(pb * 100)) # @n09v2 - pn_int = int(round(pn * 100)) # @n09v2 - costo_int = int(round(costo_u * 100000)) # @n011v5 - - prog_ind = "S" if line.customs and line.customs.has_origin_certificate else "N" - - # IV22 - lineas.append( - f"IV22{hts:<10}{prog_ind}" - f"{val_int:010d}{um:<3}{qty_int:09d}{pais:<2}0000010000000000100000 " - f"{pb_int:09d}{pn_int:09d}" - ) - self.cuenta_partidas += 1 - - # IV24, IV26 (Zeros per logic) - lineas.append(f"IV24 0000000000 000000000 0000000000000000000000") - lineas.append(f"IV26 0000000000 000000000 0000000000000000000000") - self.cuenta_partidas += 2 - - # IV27 - lineas.append(f"IV27{hts:<10}{costo_int:011d}0000000000000000000000000000000000000 ") - self.cuenta_partidas += 1 - - # IV42 (Optional) - if request.iv42: - um4 = um[:4] - lineas.append(f"IV42 0{qty_int:09d}{um4:<4}0000000000 0000000000 0000000000 0000000000 ") - self.cuenta_partidas += 1 - - f_val_total += val_me - f_pb += pb - f_pn += pn - - # IV90: Footer per Invoice - fv_int = int(round(f_val_total * 100)) - fpb_int = int(round(f_pb * 100)) - fpn_int = int(round(f_pn * 100)) - lineas.append(f"IV90{f_consec_partidas:05d}{fv_int:012d}{fpb_int:010d}{fpn_int:010d}") - self.cuenta_partidas += 1 - - self.valor_total_factura += f_val_total - self.peso_bruto_factura += f_pb - self.peso_neto_factura += f_pn - - return lineas, self.errores diff --git a/backend/api/v1/modules/a76/reports/importacion/transmission/definitive/MAINX30/routes.py b/backend/api/v1/modules/a76/reports/importacion/transmission/definitive/MAINX30/routes.py deleted file mode 100644 index 74f95712..00000000 --- a/backend/api/v1/modules/a76/reports/importacion/transmission/definitive/MAINX30/routes.py +++ /dev/null @@ -1,55 +0,0 @@ -from typing import Dict, Any -from fastapi import APIRouter, Depends, Body -from sqlalchemy.orm import Session -from celery.result import AsyncResult -from core.celery_app import celery_app -from core.database import get_core_db -from core.security import get_current_user, get_tenant_from_token -from api.v1.modules.core.tasks_tracking import track_and_dispatch -from .task import generar_transmission_definitiva_async -from .schemas import Mainx30GenerationRequest - -router = APIRouter() - -@router.get("/tasks/{task_id}") -async def get_task_status( - task_id: str, - current_user: Dict[str, Any] = Depends(get_current_user) -): - task_result = AsyncResult(task_id, app=celery_app) - - response = { - "task_id": task_id, - "state": task_result.state, - "result": None, - "info": None - } - - if task_result.state == 'FAILURE': - response["result"] = str(task_result.result) - elif task_result.state == 'SUCCESS': - response["result"] = task_result.result - elif task_result.state == 'PROCESSING': - response["info"] = task_result.info - - return response - -@router.post("/generate") -async def trigger_generation( - request: Mainx30GenerationRequest, - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user) -): - tenant_id = get_tenant_from_token(current_user) or current_user.get("tenant_id") - task = track_and_dispatch( - db=db, - task=generar_transmission_definitiva_async, - tenant_id=int(tenant_id), - company_id=getattr(request, "company_id", None), - requested_by_user=current_user.get("preferred_username") or current_user.get("email") or current_user.get("sub"), - task_name="generar_transmission_definitiva_async", - task_group="reports", - task_origin="a76/reports/importacion/transmission/definitive/MAINX30", - args=[request.model_dump(), tenant_id], - ) - return {"task_id": task.id, "message": "Generación Definitiva iniciada"} diff --git a/backend/api/v1/modules/a76/reports/importacion/transmission/definitive/MAINX30/schemas.py b/backend/api/v1/modules/a76/reports/importacion/transmission/definitive/MAINX30/schemas.py deleted file mode 100644 index 29448fbb..00000000 --- a/backend/api/v1/modules/a76/reports/importacion/transmission/definitive/MAINX30/schemas.py +++ /dev/null @@ -1,72 +0,0 @@ -from typing import List, Optional, Any -from pydantic import BaseModel, Field - -class Mainx30GenerationRequest(BaseModel): - """ - Schema for the Mainx30 file generation request for Definitive Import - """ - manifiestos: Optional[List[str]] = Field(None, description="Lista de números de manifiesto a procesar") - facturas: Optional[List[str]] = Field(None, description="Lista de números de factura a procesar") - entry_port: Optional[str] = Field(None, description="Puerto de entrada") - exit_port: Optional[str] = Field(None, description="Puerto de salida") - regimen: Optional[str] = Field("Definitiva", description="Regimen de importación (Temporal/Definitiva)") - nomenclatura_factura: bool = Field(False, description="Usar nomenclatura basada en factura") - consolidar_rbs: bool = Field(False, description="Consolidar por fracción RB System") - emanifest_fast_blanco: bool = Field(False, description="E-Manifest y FAST en blanco") - no_enviar_emanifest: bool = Field(False, description="No enviar E-Manifest") - consolidar_partidas: bool = Field(False, description="Consolidar partidas (XML OPTIMA Y RBS2)") - main_x40_emanifest: bool = Field(False, description="Main X40 E-Manifest") - main_x30_fedex: bool = Field(False, description="Main X30 (FEDEX)") - iv11: bool = Field(False, description="IV11") - iv42: bool = Field(False, description="IV42") - -class ErrorValidacion(BaseModel): - """ - Schema for validation errors during file generation - """ - partida: int - linea: int - descripcion: str - soluciones: str - identificador: str - campos: str = "" - campos2: str = "" - -class Mainx30Response(BaseModel): - """ - Schema for the generation response - """ - success: bool - message: str - task_id: Optional[str] = None - archivo_generado: Optional[str] = None - ruta_archivo: Optional[str] = None - content: Optional[str] = None - - # Statistics - cuenta_partidas: int = 0 - valor_total: float = 0.0 - flete_total: float = 0.0 - peso_bruto_total: float = 0.0 - peso_neto_total: float = 0.0 - cuenta_facturas: int = 0 - - # Validation - errores: List[ErrorValidacion] = [] - tiene_inconsistencias: bool = False - -class EmpresaDatos(BaseModel): - broker: str - responsable: str - rfc: str - tiene_linea_express: str - nombre_empresa: str = "" - manufacturer_id: str = "" - ftp_key: str = "00SCSI" - main_activity: str = "RAW MATERIAL" - city_state: str = "" - -class ConfiguracionSistema(BaseModel): - path_arch_transmision: str - utilizar_nombre_generico_mainx30: bool - utilizar_codigo_broker_cliente: bool diff --git a/backend/api/v1/modules/a76/reports/importacion/transmission/definitive/MAINX30/service.py b/backend/api/v1/modules/a76/reports/importacion/transmission/definitive/MAINX30/service.py deleted file mode 100644 index 881406b3..00000000 --- a/backend/api/v1/modules/a76/reports/importacion/transmission/definitive/MAINX30/service.py +++ /dev/null @@ -1,175 +0,0 @@ -import os -import tempfile -from datetime import date, datetime -from typing import List, Tuple, Optional, Dict, Any -from pathlib import Path - -from sqlalchemy.orm import Session, joinedload -from fastapi import HTTPException - -from .schemas import ( - Mainx30GenerationRequest, Mainx30Response, ErrorValidacion, - EmpresaDatos, ConfiguracionSistema -) - -# --- MODELOS A76 --- -from api.v1.modules.a76.general_catalogs.company.models import Company as GEmpresa -from api.v1.modules.a76.general_catalogs.ports.models import Port - -# --- PROCESADORES --- -from .processors import ScafDefProcessor - -class Mainx30DefinitiveService: - def __init__(self): - self.errores_validacion: List[ErrorValidacion] = [] - self.cuenta_partidas = 0 - self.cuenta_facturas = 0 - self.valor_total = 0.0 - self.flete_total = 0.0 - self.peso_bruto_total = 0.0 - self.peso_neto_total = 0.0 - - def generar_mainx30( - self, - db: Session, - request: Mainx30GenerationRequest, - task_instance=None - ) -> Mainx30Response: - try: - self._inicializar_variables() - - # 1. Obtener Datos de Empresa - datos_empresa = self._obtener_datos_company(db) - emp_dict = datos_empresa.model_dump() - - # 2. Obtener Descripciones de Puertos - if request.entry_port: - p_ent = db.query(Port).filter(Port.port_code == request.entry_port).first() - if p_ent: emp_dict['entry_port_desc'] = p_ent.description or p_ent.location_description or "" - - if request.exit_port: - p_sal = db.query(Port).filter(Port.port_code == request.exit_port).first() - if p_sal: emp_dict['exit_port_desc'] = p_sal.description or p_sal.location_description or "" - - # 3. Fecha de Transmisión (YYMMDD) - fecha_transmision = datetime.now().strftime("%y%m%d") - - # 4. Determinar Procesador (Always ScafDefProcessor for this service) - processor = ScafDefProcessor() - # Both 'Definitiva' and 'DEFINITIVO SCAF' use the same heavy processor - - # 5. Procesar Facturas - if not request.facturas: - raise HTTPException(status_code=400, detail="No se proporcionaron facturas para procesar.") - - l_facturas, e_facturas = processor.procesar_facturas(db, request.facturas, emp_dict, request) - - self.errores_validacion.extend(e_facturas) - - # 6. Construir Líneas del Archivo - lineas = [] - - # Línea A - broker = (datos_empresa.broker or "")[:6] - # Clarion uses Broker twice in 'A' record for Definitive - ftp_key = (datos_empresa.ftp_key or "00SCSI")[:6] - lineas.append(f"A {fecha_transmision}03{broker:<6}{broker:<10}{ftp_key:<6}") - self.cuenta_partidas += 1 - - # Agregar Líneas de Facturas - lineas.extend(l_facturas) - self.cuenta_partidas += processor.cuenta_partidas - self.cuenta_facturas = processor.cuenta_facturas - self.valor_total = processor.valor_total_factura - self.peso_bruto_total = processor.peso_bruto_factura - self.peso_neto_total = processor.peso_neto_factura - self.flete_total = processor.flete_total - - # MF80 (Totales Globales) - val_int = int(round(self.valor_total * 100)) - pb_int = int(round(self.peso_bruto_total * 10000)) - pn_int = int(round(self.peso_neto_total * 10000)) - flete_int = int(round(self.flete_total * 100)) - - lineas.append( - f"MF80{val_int:012d}" - f"{self.cuenta_facturas:04d}" - f"{pb_int:012d}" - f"{flete_int:08d}" - f"{pn_int:012d}" - ) - self.cuenta_partidas += 1 - - # Línea Z (Total de líneas) - lineas.append(f"Z {self.cuenta_partidas:05d}") - self.cuenta_partidas += 1 - - # 7. Generar Nombre y Guardar - nombre_archivo = f"{request.facturas[0][:15]}_Mainx30.dat" - if len(request.facturas) > 1: - nombre_archivo = f"MULTIPLE_Mainx30.dat" - - if request.nomenclatura_factura and len(request.facturas) == 1: - nombre_archivo = f"{request.facturas[0][:15]}_Mainx30.dat" - - content = '\r\n'.join(lineas) - - return Mainx30Response( - success=len(self.errores_validacion) == 0, - message="Archivo generado" if len(self.errores_validacion) == 0 else "Archivo generado con errores de validación", - archivo_generado=nombre_archivo, - ruta_archivo="", - content=content, - errores_validacion=self.errores_validacion, - cuenta_partidas=self.cuenta_partidas, - valor_total=self.valor_total, - flete_total=self.flete_total, - peso_bruto_total=self.peso_bruto_total, - peso_neto_total=self.peso_neto_total, - cuenta_facturas=self.cuenta_facturas, - tiene_inconsistencias=len(self.errores_validacion) > 0 - ) - - except HTTPException: - raise - except Exception as e: - import traceback - traceback.print_exc() - raise HTTPException(status_code=500, detail=f"Error generando Mainx30 Definitivo: {str(e)}") - - def _inicializar_variables(self): - self.errores_validacion = [] - self.cuenta_partidas = 0 - self.valor_total = 0.0 - self.flete_total = 0.0 - self.peso_bruto_total = 0.0 - self.peso_neto_total = 0.0 - self.cuenta_facturas = 0 - - def _obtener_datos_company(self, db: Session) -> EmpresaDatos: - empresa = db.query(GEmpresa).options(joinedload(GEmpresa.addresses)).first() - if not empresa: - return EmpresaDatos(broker="", responsable="", rfc="", tiene_linea_express="N", nombre_empresa="", manufacturer_id="", ftp_key="", main_activity="", city_state="") - - city_state = "" - main_addr = next((a for a in (empresa.addresses or []) if a.address_type == 'main'), None) - if not main_addr and empresa.addresses: - main_addr = empresa.addresses[0] - - if main_addr: - cp = (main_addr.postal_code or "")[:5] - city = (main_addr.city or "")[:11] - state = (main_addr.state or "")[:4] - city_state = f"{cp:<5}{city:<11}{state:<4}" - - return EmpresaDatos( - broker=(empresa.broker_company or "")[:5], - responsable=(empresa.responsible or "")[:30], - rfc=(empresa.rfc or "")[:13], - tiene_linea_express="N", - nombre_empresa=(empresa.name or "")[:40], - manufacturer_id=(empresa.manufacturer_id or "")[:10], - ftp_key=(empresa.ftp_key or "")[:10], - main_activity=(empresa.main_activity or "")[:30], - city_state=city_state[:30] - ) diff --git a/backend/api/v1/modules/a76/reports/importacion/transmission/definitive/MAINX30/task.py b/backend/api/v1/modules/a76/reports/importacion/transmission/definitive/MAINX30/task.py deleted file mode 100644 index c6559af3..00000000 --- a/backend/api/v1/modules/a76/reports/importacion/transmission/definitive/MAINX30/task.py +++ /dev/null @@ -1,46 +0,0 @@ -from fastapi import HTTPException -from core.celery_app import celery_app -from core.database import get_core_db as get_db -from .service import Mainx30DefinitiveService -from .schemas import Mainx30GenerationRequest - -@celery_app.task(name="generar_transmission_definitiva_async", bind=True) -def generar_transmission_definitiva_async(self, request_data: dict, tenant_id: int): - """ - Celery task to generate Mainx30 file for Definitive Import - """ - try: - # Re-create db session for task - db = next(get_db()) - - # Deserialize request - request = Mainx30GenerationRequest(**request_data) - - service = Mainx30DefinitiveService() - response = service.generar_mainx30(db, request, task_instance=self) - - # Return result as dict for Celery serialization - result = response.model_dump() - - # Add base64 encoding for content to match frontend expectations - if response.content: - import base64 - encoded_content = base64.b64encode(response.content.encode('utf-8')).decode('utf-8') - result['content'] = encoded_content - result['file_name'] = response.archivo_generado - result['media_type'] = "text/plain" - - return result - - except Exception as e: - import traceback - traceback_print = traceback.format_exc() - self.update_state( - state='FAILURE', - meta={ - 'exc_type': type(e).__name__, - 'exc_message': str(e), - 'traceback': traceback_print - } - ) - raise e diff --git a/backend/api/v1/modules/a76/reports/importacion/transmission/temporal/MAINX30/processors.py b/backend/api/v1/modules/a76/reports/importacion/transmission/temporal/MAINX30/processors.py deleted file mode 100644 index 18b06692..00000000 --- a/backend/api/v1/modules/a76/reports/importacion/transmission/temporal/MAINX30/processors.py +++ /dev/null @@ -1,428 +0,0 @@ -from typing import List, Dict, Any, Tuple -from sqlalchemy.orm import Session, joinedload -from datetime import datetime - -from .schemas import Mainx30GenerationRequest, ErrorValidacion - -# --- MODELOS A76 --- -from api.v1.modules.a76.invoices.models import InvoiceHeader, InvoiceComplianceMx -from api.v1.modules.a76.items.models import LineItem -from api.v1.modules.a76.clients_and_providers.models import ClientProvider - -class ScaiiProcessor: - def __init__(self): - self.cuenta_partidas = 0 - self.cuenta_facturas = 0 - self.valor_total_factura = 0.0 - self.flete_total = 0.0 - self.peso_bruto_factura = 0.0 - self.peso_neto_factura = 0.0 - self.errores: List[ErrorValidacion] = [] - - def _obtener_datos_cliente(self, cliente: ClientProvider) -> dict: - """Extrae de manera segura los datos del cliente/dirección""" - address = cliente.address - pais_raw = (address.country or "MX").upper() if address else "MX" - - pais = "MX" - if pais_raw in ["MEXICO", "MEX", "MX"]: - pais = "MX" - elif pais_raw in ["USA", "US", "UNITED STATES"]: - pais = "US" - else: - pais = pais_raw[:2] - - # Identificador fiscal del cliente: RFC (nacional) o TAX-ID (extranjero) - tax_id = cliente.fiscal_id or "" - - data = { - "nombre": (cliente.name or "")[:39], - "tax_id": tax_id[:15], - "broker": "", "calle": "", "cp": "", "ciudad": "", "estado": "", "pais": pais, "tel": "" - } - - if cliente.programs: - data["broker"] = (cliente.programs.broker or "")[:6] - - if address: - calle_comp = f"{address.streets or ''} {address.exterior_number or ''}".strip() - # Clarion expects 20 chars for city_state: 5 CP + 11 City + 4 State - cp_formatted = (address.postal_code or "")[:5] - city_formatted = (address.city or "")[:11] - state_formatted = (address.state or "")[:4] - data["city_state"] = f"{cp_formatted:<5}{city_formatted:<11}{state_formatted:<4}" - data["calle"] = calle_comp[:35] - data["cp"] = (address.postal_code or "")[:9] - data["ciudad"] = (address.city or "")[:20] - data["estado"] = (address.state or "")[:2].upper() - data["tel"] = (address.phone or "")[:15] - - return data - - def procesar_facturas( - self, db: Session, manifiesto: str, empresa_dict: Dict[str, Any], request: Mainx30GenerationRequest - ) -> Tuple[List[str], List[ErrorValidacion]]: - # This base method is used for Manifest-based processing (Exportacion/Legacy) - lineas = [] - self.errores = [] - return lineas, self.errores - - def _agregar_error(self, partida, id_err, desc, sol, tipo): - self.errores.append(ErrorValidacion(partida=partida, linea=0, descripcion=f"[ {id_err} ] {desc}", soluciones=sol, identificador=tipo)) - -# --- PROCESADORES ESPECIFICOS --- -class ScafDefProcessor(ScaiiProcessor): - """Procesador para Importación Definitiva""" - def __init__(self): - super().__init__() - - def procesar_facturas( - self, db: Session, facturas_nums: List[str], empresa_dict: Dict[str, Any], request: Mainx30GenerationRequest - ) -> Tuple[List[str], List[ErrorValidacion]]: - # Placeholder for Definitiva logic - lineas = [] - self.errores = [] - return lineas, self.errores - -class ScafTempProcessor(ScaiiProcessor): - """Procesador para Importación Temporal basado en lógica Clarion""" - def __init__(self): - super().__init__() - - def procesar_facturas( - self, db: Session, facturas_nums: List[str], empresa_dict: Dict[str, Any], request: Mainx30GenerationRequest - ) -> Tuple[List[str], List[ErrorValidacion]]: - lineas = [] - self.errores = [] - - # 1. Traer Facturas por número (Importación Temporal trabaja por factura) - facturas = db.query(InvoiceHeader).join( - InvoiceComplianceMx, InvoiceHeader.id == InvoiceComplianceMx.invoice_id - ).options( - joinedload(InvoiceHeader.financials), - joinedload(InvoiceHeader.compliance_mx), - joinedload(InvoiceHeader.logistics) - ).filter( - InvoiceHeader.invoice_number.in_(facturas_nums) - ).all() - - # Helper for ports from request - entry_port = request.entry_port or "" - exit_port = request.exit_port or "" - - # Date for transmission records (YYMMDD) - fecha_trans = datetime.now().strftime("%y%m%d") - - # Port descriptions from empresa_dict (populated in service.py) - entry_port_desc = empresa_dict.get('entry_port_desc', 'PUERTO ENTRADA')[:15] - exit_port_desc = empresa_dict.get('exit_port_desc', 'PUERTO SALIDA')[:15] - main_activity = empresa_dict.get('main_activity', 'RAW MATERIAL')[:30] - city_state = empresa_dict.get('city_state', '')[:30] - - for factura in facturas: - self.cuenta_facturas += 1 - f_val_total = 0.0 - f_pb = 0.0 - f_pn = 0.0 - f_consec_partidas = 0 - - # MF01: Header per Invoice in Importacion Temporal - mod_trans = factura.logistics.transport_mode if factura.logistics else "30" - f_fecha = factura.invoice_date.strftime("%y%m%d") if factura.invoice_date else fecha_trans - - lineas.append( - f"MF01{empresa_dict['broker'][:6]:<6}" - f"{exit_port[:5]:<5}" - f"{entry_port[:5]:<5}" - f"{fecha_trans}" - f" {mod_trans[:2]:<2}" - f"{f_fecha}" - f"{factura.invoice_number[:15]:<15}" - ) - self.cuenta_partidas += 1 - - # MF03: Dynamic Driver/Carrier info - conductor = (factura.logistics.driver_name or "") if factura.logistics else "" - - # Carrier Logic from Clarion: - # IF ERRORCODE() = 35 THEN Loc:NumTransporte = '00000TRUCK' - # ELSE IF GenTra:NombreCorto = '' THEN Loc:NumTransporte = GenTra:Nombre - # ELSE Loc:NumTransporte = GenTra:NombreCorto - carrier = "00000TRUCK" - if factura.logistics: - # Logic simplified: assume carrier_id holds the correct code/name or fallback - carrier = (factura.logistics.carrier_id or "00000TRUCK") - - lineas.append( - f"MF03{carrier[:10]:<10}{conductor[:23]:<23}{entry_port_desc:<15}{exit_port_desc:<15}" - ) - self.cuenta_partidas += 1 - - # MF20 - num_transporte = (factura.logistics.transport_num or "") if factura.logistics else "" - lineas.append( - f"MF20{factura.invoice_number[:15]:<15}I{num_transporte[:15]:<15}{city_state[:20]:<20}" - f"{exit_port[:5]:<5}{entry_port_desc:<15} " - ) - self.cuenta_partidas += 1 - - # MF22 - lineas.append(f"MF22{main_activity:<60}") - self.cuenta_partidas += 1 - - # IV01: Header - f_fecha = factura.invoice_date.strftime("%y%m%d") if factura.invoice_date else "000000" - flete = int(round(float(factura.financials.freight or 0))) if factura.financials else 0 - self.flete_total += float(factura.financials.freight or 0) if factura.financials else 0.0 - - s_tax = ""; c_tax = "" - if factura.compliance_mx: - if factura.compliance_mx.sold_to_id: - c_obj = db.query(ClientProvider).filter(ClientProvider.id == factura.compliance_mx.sold_to_id).first() - if c_obj: c_tax = c_obj.fiscal_id[:12] if c_obj.fiscal_id else "" - if factura.compliance_mx.provider_id: - s_obj = db.query(ClientProvider).filter(ClientProvider.id == factura.compliance_mx.provider_id).first() - if s_obj: s_tax = s_obj.fiscal_id[:12] if s_obj.fiscal_id else "" - - lineas.append( - f"IV01{factura.invoice_number[:15]:<15}{f_fecha}{entry_port:<5}{' ':<11}C" - f"{empresa_dict['broker'][:6]:<6}{flete:08d}{c_tax:<13}" - ) - self.cuenta_partidas += 1 - - # IV02: Shipper Name - lineas.append(f"IV02 {empresa_dict['nombre_empresa'][:40]:<40}") - self.cuenta_partidas += 1 - - # IV10: Responsible - lineas.append(f"IV10 {main_activity[:30]:<30}{empresa_dict['responsable'][:30]:<30}") - self.cuenta_partidas += 1 - - # IV11: Obs - lineas.append(f"IV11H") - lineas.append(f"IV11F") - self.cuenta_partidas += 2 - - # IV12-14 (S, C, T, I) - # Shipper (S) - if factura.compliance_mx and factura.compliance_mx.provider_id: - s_cliente = db.query(ClientProvider).filter(ClientProvider.id == factura.compliance_mx.provider_id).first() - if s_cliente: - s_data = self._obtener_datos_cliente(s_cliente) - lineas.append(f"IV12S {s_data['nombre'][:39]:<39}") - lineas.append(f"IV13S {s_data['calle'][:35]:<35}{s_data['cp']:<9}") - lineas.append(f"IV14S{s_data['ciudad'][:20]:<20}{s_data['state_full'][:2] if 'state_full' in s_data else s_data['estado'][:2]}{s_data['pais'][:2]}{s_data['tel'][:30]:<30}{s_data['tax_id']:<15}00000") - self.cuenta_partidas += 3 - - # Consignee (C), Ship To (T), and Intermediate (I) - if factura.compliance_mx and factura.compliance_mx.sold_to_id: - c_cliente = db.query(ClientProvider).filter(ClientProvider.id == factura.compliance_mx.sold_to_id).first() - if c_cliente: - c_data = self._obtener_datos_cliente(c_cliente) - l12c = f"IV12C {c_data['nombre'][:39]:<39}" - l13c = f"IV13C {c_data['calle'][:35]:<35}{c_data['cp']:<9}" - l14c = f"IV14C{c_data['ciudad'][:20]:<20}{c_data['estado'][:2]}{c_data['pais'][:2]}{c_data['tel'][:30]:<30}{c_data['tax_id']:<15}00000" - - # Output C - lineas.extend([l12c, l13c, l14c]) - self.cuenta_partidas += 3 - - # T (Ship To) - Only if different from C - t_id = factura.compliance_mx.shipped_to_id - if t_id and t_id != factura.compliance_mx.sold_to_id: - t_cl = db.query(ClientProvider).filter(ClientProvider.id == t_id).first() - if t_cl: - t_data = self._obtener_datos_cliente(t_cl) - lineas.append(f"IV12T {t_data['nombre'][:39]:<39}") - lineas.append(f"IV13T {t_data['calle'][:35]:<35}{t_data['cp']:<9}") - lineas.append(f"IV14T{t_data['ciudad'][:20]:<20}{t_data['estado'][:2]}{t_data['pais'][:2]}{t_data['tel'][:30]:<30}{t_data['tax_id']:<15}00000") - self.cuenta_partidas += 3 - - # I (Intermediate) - Only if different from C and T - i_id = factura.compliance_mx.shipped_by_id - if i_id and i_id != factura.compliance_mx.sold_to_id and i_id != t_id: - i_cl = db.query(ClientProvider).filter(ClientProvider.id == i_id).first() - if i_cl: - i_data = self._obtener_datos_cliente(i_cl) - lineas.append(f"IV12I {i_data['nombre'][:39]:<39}") - lineas.append(f"IV13I {i_data['calle'][:35]:<35}{i_data['cp']:<9}") - lineas.append(f"IV14I{i_data['ciudad'][:20]:<20}{i_data['estado'][:2]}{i_data['pais'][:2]}{i_data['tel'][:30]:<30}{i_data['tax_id']:<15}00000") - self.cuenta_partidas += 3 - - # Partidas IV20, IV21, IV22, IV24, IV26, IV27 - # Let's get Item IDs first to ensure we find them - items_headers = db.query(LineItem).filter(LineItem.invoice_id == factura.id).all() - item_ids = [ih.id for ih in items_headers] - - if item_ids: - items_query = db.query(LineItem).filter( - LineItem.id.in_(item_ids) - ).options( - joinedload(LineItem.part_info), - joinedload(LineItem.description), - joinedload(LineItem.financial), - joinedload(LineItem.quantity), - joinedload(LineItem.customs), - joinedload(LineItem.unit_of_measure_info) - ).all() - - for line in items_query: - f_consec_partidas += 1 - part_num = line.part_info.part_number if line.part_info else "S/N" - - # IV20 - lineas.append(f"IV20{f_consec_partidas:03d} {part_num[:25]:<25}C") - self.cuenta_partidas += 1 - - # IV21 - lineas.append(f"IV21") - self.cuenta_partidas += 1 - - # Data - val_me = float(line.financial.value_usd or 0) if line.financial else 0.0 - qty = float(line.quantity.quantity or 0) if line.quantity else 0.0 - pb = float(line.quantity.gross_weight or 0) if line.quantity else 0.0 - pn = float(line.quantity.net_weight or 0) if line.quantity else 0.0 - costo_u = float(line.financial.unit_cost_capture or 0) if line.financial else 0.0 - - um = line.unit_of_measure_info.american_code if line.unit_of_measure_info else "PCS" - pais = (line.customs.origin_country or "MX")[:2] if line.customs else "MX" - hts = (line.customs.american_fraction or "").replace(".", "")[:10] if line.customs else "" - - val_int = int(round(val_me * 10000)) - qty_int = int(round(qty * 10000)) - pb_int = int(round(pb * 10000)) - pn_int = int(round(pn * 10000)) - costo_int = int(round(costo_u * 100000)) - - # IV22 - lineas.append( - f"IV22{hts:<10} " - f"{val_int:010d}{um[:3]:<3}{qty_int:09d}{pais:<2}0000010000000000100000 " - f"{pb_int:010d}{pn_int:010d}" - ) - self.cuenta_partidas += 1 - - # IV24, IV26 (Zeros) - lineas.append(f"IV24 0000000000 000000000 0000000000000000000000") - lineas.append(f"IV26 0000000000 000000000 0000000000000000000000") - self.cuenta_partidas += 2 - - # IV27 - lineas.append(f"IV27{hts:<10}{costo_int:011d}0000000000000000000000000000000000000") - self.cuenta_partidas += 1 - - f_val_total += val_me - f_pb += pb - f_pn += pn - - # IV90: Footer per Invoice - - # IV90 - f_val_int = int(round(f_val_total * 100)) - f_pb_int = int(round(f_pb * 10000)) - f_pn_int = int(round(f_pn * 10000)) - lineas.append(f"IV90{f_consec_partidas:05d}{f_val_int:012d}{f_pb_int:010d}{f_pn_int:010d}") - self.cuenta_partidas += 1 - - self.valor_total_factura += f_val_total - self.peso_bruto_factura += f_pb - self.peso_neto_factura += f_pn - - return lineas, self.errores - -class ScaiiTempProcessor(ScafTempProcessor): - """ - Procesador para Importación Temporal (Versión Ligera/SCAII) - Se salta los registros MF20, MF22, IV10-14 para mayor velocidad y menor detalle. - """ - def procesar_facturas( - self, db: Session, facturas_nums: List[str], empresa_dict: Dict[str, Any], request: Mainx30GenerationRequest - ) -> Tuple[List[str], List[ErrorValidacion]]: - lineas = [] - self.errores = [] - - # 1. Traer Facturas por número - facturas = db.query(InvoiceHeader).join( - InvoiceComplianceMx, InvoiceHeader.id == InvoiceComplianceMx.invoice_id - ).options( - joinedload(InvoiceHeader.financials), - joinedload(InvoiceHeader.compliance_mx), - joinedload(InvoiceHeader.logistics) - ).filter( - InvoiceHeader.invoice_number.in_(facturas_nums) - ).all() - - entry_port = request.entry_port or "" - exit_port = request.exit_port or "" - fecha_trans = datetime.now().strftime("%y%m%d") - - entry_port_desc = empresa_dict.get('entry_port_desc', 'PUERTO ENTRADA')[:15] - exit_port_desc = empresa_dict.get('exit_port_desc', 'PUERTO SALIDA')[:15] - - for factura in facturas: - self.cuenta_facturas += 1 - f_val_total = 0.0 - f_consec_partidas = 0 - - # MF01 - mod_trans = factura.logistics.transport_mode if factura.logistics else "30" - f_fecha = factura.invoice_date.strftime("%y%m%d") if factura.invoice_date else fecha_trans - lineas.append( - f"MF01{empresa_dict['broker'][:6]:<6}{exit_port[:5]:<5}{entry_port[:5]:<5}" - f"{fecha_trans} {mod_trans[:2]:<2}{f_fecha}{factura.invoice_number[:15]:<15}" - ) - self.cuenta_partidas += 1 - - # MF03 - conductor = (factura.logistics.driver_name or "") if factura.logistics else "" - carrier = (factura.logistics.carrier_id or "00000TRUCK") - lineas.append(f"MF03{carrier[:10]:<10}{conductor[:23]:<23}{entry_port_desc:<15}{exit_port_desc:<15}") - self.cuenta_partidas += 1 - - # IV01 - f_fecha_iv = factura.invoice_date.strftime("%y%m%d") if factura.invoice_date else "000000" - flete = int(round(float(factura.financials.freight or 0))) if factura.financials else 0 - self.flete_total += float(factura.financials.freight or 0) if factura.financials else 0.0 - - lineas.append( - f"IV01{factura.invoice_number[:15]:<15}{f_fecha_iv}{entry_port:<5}{' ':<11}C" - f"{empresa_dict['broker'][:6]:<6}{flete:08d}{'':<13}" - ) - self.cuenta_partidas += 1 - - # Partidas IV20, IV21, IV22 - items_headers = db.query(LineItem).filter(LineItem.invoice_id == factura.id).all() - item_ids = [ih.id for ih in items_headers] - - if item_ids: - items_query = db.query(LineItem).filter(LineItem.id.in_(item_ids)).all() - - for line in items_query: - f_consec_partidas += 1 - part_num = line.part_info.part_number if line.part_info else "S/N" - - # IV20 - lineas.append(f"IV20{f_consec_partidas:03d} {part_num[:25]:<25}C") - self.cuenta_partidas += 1 - - # IV22 - qty = float(line.quantity.quantity or 0) if line.quantity else 0.0 - val_me = float(line.financial.value_usd or 0) if line.financial else 0.0 - hts = (line.customs.american_fraction or "").replace(".", "")[:10] if line.customs else "" - - val_int = int(round(val_me * 10000)) - qty_int = int(round(qty * 10000)) - - lineas.append(f"IV22{hts:<10} {val_int:010d}PCS{qty_int:09d}MX0000010000000000100000 00000000000000000000") - self.cuenta_partidas += 1 - f_val_total += val_me - - # IV90 - fv_int = int(round(f_val_total * 100)) - lineas.append(f"IV90{f_consec_partidas:05d}{fv_int:012d}00000000000000000000") - self.cuenta_partidas += 1 - - self.valor_total_factura += f_val_total - - return lineas, self.errores \ No newline at end of file diff --git a/backend/api/v1/modules/a76/reports/importacion/transmission/temporal/MAINX30/routes.py b/backend/api/v1/modules/a76/reports/importacion/transmission/temporal/MAINX30/routes.py deleted file mode 100644 index a2453785..00000000 --- a/backend/api/v1/modules/a76/reports/importacion/transmission/temporal/MAINX30/routes.py +++ /dev/null @@ -1,56 +0,0 @@ -from typing import Dict, Any -from fastapi import APIRouter, Depends, Body -from sqlalchemy.orm import Session -from celery.result import AsyncResult -from core.celery_app import celery_app -from core.database import get_core_db -from core.security import get_current_user, get_tenant_from_token -from api.v1.modules.core.tasks_tracking import track_and_dispatch -from .task import generar_transmission_temporal_async -from .schemas import Mainx30GenerationRequest - -router = APIRouter() - -@router.get("/tasks/{task_id}") -async def get_task_status( - task_id: str, - current_user: Dict[str, Any] = Depends(get_current_user) -): - task_result = AsyncResult(task_id, app=celery_app) - - response = { - "task_id": task_id, - "state": task_result.state, - "result": None, - "info": None - } - - if task_result.state == 'FAILURE': - response["result"] = str(task_result.result) - elif task_result.state == 'SUCCESS': - response["result"] = task_result.result - elif task_result.state == 'PROCESSING': - # Ensure info is serializable - response["info"] = task_result.info - - return response - -@router.post("/generate") -async def trigger_generation( - request: Mainx30GenerationRequest, - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user) -): - tenant_id = get_tenant_from_token(current_user) or current_user.get("tenant_id") - task = track_and_dispatch( - db=db, - task=generar_transmission_temporal_async, - tenant_id=int(tenant_id), - company_id=getattr(request, "company_id", None), - requested_by_user=current_user.get("preferred_username") or current_user.get("email") or current_user.get("sub"), - task_name="generar_transmission_temporal_async", - task_group="reports", - task_origin="a76/reports/importacion/transmission/temporal/MAINX30", - args=[request.model_dump(), tenant_id], - ) - return {"task_id": task.id, "message": "Generación Temporal iniciada"} diff --git a/backend/api/v1/modules/a76/reports/importacion/transmission/temporal/MAINX30/schemas.py b/backend/api/v1/modules/a76/reports/importacion/transmission/temporal/MAINX30/schemas.py deleted file mode 100644 index 1e3d397d..00000000 --- a/backend/api/v1/modules/a76/reports/importacion/transmission/temporal/MAINX30/schemas.py +++ /dev/null @@ -1,77 +0,0 @@ -from typing import List, Optional, Any -from pydantic import BaseModel, Field - -class Mainx30GenerationRequest(BaseModel): - """ - Schema for the Mainx30 file generation request - """ - manifiestos: Optional[List[str]] = Field(None, description="Lista de números de manifiesto a procesar") - facturas: Optional[List[str]] = Field(None, description="Lista de números de factura a procesar") - entry_port: Optional[str] = Field(None, description="Puerto de entrada") - exit_port: Optional[str] = Field(None, description="Puerto de salida") - regimen: Optional[str] = Field("Temporal", description="Regimen de importación (Temporal/Definitiva)") - nomenclatura_factura: bool = Field(False, description="Usar nomenclatura basada en factura") - consolidar_rbs: bool = Field(False, description="Consolidar por fracción RB System") - emanifest_fast_blanco: bool = Field(False, description="E-Manifest y FAST en blanco") - no_enviar_emanifest: bool = Field(False, description="No enviar E-Manifest") - consolidar_partidas: bool = Field(False, description="Consolidar partidas (XML OPTIMA Y RBS2)") - main_x40_emanifest: bool = Field(False, description="Main X40 E-Manifest") - main_x30_fedex: bool = Field(False, description="Main X30 (FEDEX)") - iv11: bool = Field(False, description="IV11") - iv42: bool = Field(False, description="IV42") - -class ErrorValidacion(BaseModel): - """ - Schema for validation errors during file generation - """ - partida: int - linea: int - descripcion: str - soluciones: str - identificador: str - campos: str = "" - campos2: str = "" - -class Mainx30Response(BaseModel): - """ - Schema for the generation response - """ - success: bool - message: str - task_id: Optional[str] = None - archivo_generado: Optional[str] = None - ruta_archivo: Optional[str] = None - content: Optional[str] = None - - # Statistics - cuenta_partidas: int = 0 - valor_total: float = 0.0 - flete_total: float = 0.0 - peso_bruto_total: float = 0.0 - peso_neto_total: float = 0.0 - cuenta_facturas: int = 0 - - # Validation - errores: List[ErrorValidacion] = [] - tiene_inconsistencias: bool = False - -class BrokerValidationResult(BaseModel): - es_valido: bool - mensaje_error: Optional[str] = None - broker_cliente: Optional[str] = None - -class EmpresaDatos(BaseModel): - broker: str - responsable: str - rfc: str - tiene_linea_express: str - nombre_empresa: str = "AAKRON RULE CORPORATION" - manufacturer_id: str = "I10900" - ftp_key: str = "00SCSI" - main_activity: str = "RAW MATERIAL" - city_state: str = "" - -class ConfiguracionSistema(BaseModel): - path_arch_transmision: str - utilizar_nombre_generico_mainx30: bool - utilizar_codigo_broker_cliente: bool diff --git a/backend/api/v1/modules/a76/reports/importacion/transmission/temporal/MAINX30/service.py b/backend/api/v1/modules/a76/reports/importacion/transmission/temporal/MAINX30/service.py deleted file mode 100644 index 276182bd..00000000 --- a/backend/api/v1/modules/a76/reports/importacion/transmission/temporal/MAINX30/service.py +++ /dev/null @@ -1,177 +0,0 @@ -import os -import tempfile -from datetime import date, datetime -from typing import List, Tuple, Optional, Dict, Any -from pathlib import Path - -from sqlalchemy.orm import Session, joinedload -from fastapi import HTTPException - -from .schemas import ( - Mainx30GenerationRequest, Mainx30Response, ErrorValidacion, - EmpresaDatos, ConfiguracionSistema -) - -# --- MODELOS A76 --- -from api.v1.modules.a76.manifests.manifest.models import Manifest -from api.v1.modules.a76.general_catalogs.company.models import Company as GEmpresa -from api.v1.modules.a76.general_catalogs.ports.models import Port - -# --- PROCESADORES --- -from .processors import ScaiiProcessor, ScafDefProcessor, ScafTempProcessor, ScaiiTempProcessor - -class Mainx30Service: - def __init__(self): - self.errores_validacion: List[ErrorValidacion] = [] - self.cuenta_partidas = 0 - self.cuenta_facturas = 0 - self.valor_total = 0.0 - self.flete_total = 0.0 - self.peso_bruto_total = 0.0 - self.peso_neto_total = 0.0 - - def generar_mainx30( - self, - db: Session, - request: Mainx30GenerationRequest, - task_instance=None - ) -> Mainx30Response: - try: - self._inicializar_variables() - - # 1. Obtener Datos de Empresa - datos_empresa = self._obtener_datos_company(db) - emp_dict = datos_empresa.model_dump() - - # 2. Obtener Descripciones de Puertos - if request.entry_port: - p_ent = db.query(Port).filter(Port.port_code == request.entry_port).first() - if p_ent: emp_dict['entry_port_desc'] = p_ent.description or p_ent.location_description or "" - - if request.exit_port: - p_sal = db.query(Port).filter(Port.port_code == request.exit_port).first() - if p_sal: emp_dict['exit_port_desc'] = p_sal.description or p_sal.location_description or "" - - # 3. Fecha de Transmisión (Clarion @D11 = mm/dd/yy, but example uses YYMMDD) - fecha_transmision = datetime.now().strftime("%y%m%d") - - # 4. Determinar Procesador - processor = ScafTempProcessor() # Now 'Temporal' defaults to Heavy (SCAF) logic per user request - if request.regimen == "TEMPORAL SCAF": - processor = ScaiiTempProcessor() # 'TEMPORAL SCAF' uses light (SCAII) logic - elif request.regimen == "Definitiva" or request.regimen == "DEFINITIVO SCAF": - processor = ScafDefProcessor() - - # 5. Procesar Facturas - if not request.facturas: - raise HTTPException(status_code=400, detail="No se proporcionaron facturas para procesar.") - - l_facturas, e_facturas = processor.procesar_facturas(db, request.facturas, emp_dict, request) - - self.errores_validacion.extend(e_facturas) - - # 6. Construir Líneas del Archivo - lineas = [] - - # Línea A - broker = (datos_empresa.broker or "")[:6] - ftp_key = (datos_empresa.ftp_key or "00SCSI")[:6] - lineas.append(f"A {fecha_transmision}03{broker:<6}{broker:<10}{ftp_key:<6}") - self.cuenta_partidas += 1 - - # Agregar Líneas de Facturas - lineas.extend(l_facturas) - self.cuenta_partidas += processor.cuenta_partidas - self.cuenta_facturas = processor.cuenta_facturas - self.valor_total = processor.valor_total_factura - self.peso_bruto_total = processor.peso_bruto_factura - self.peso_neto_total = processor.peso_neto_factura - - # MF80 (Totales Globales) - val_int = int(round(self.valor_total * 100)) - pb_int = int(round(self.peso_bruto_total * 10000)) - pn_int = int(round(self.peso_neto_total * 10000)) - flete_int = 0 - - lineas.append( - f"MF80{val_int:012d}" - f"{self.cuenta_facturas:04d}" - f"{pb_int:012d}" - f"{flete_int:08d}" - f"{pn_int:012d}" - ) - self.cuenta_partidas += 1 - - # Línea Z (Total de líneas) - lineas.append(f"Z {self.cuenta_partidas:05d}") - self.cuenta_partidas += 1 - - # 7. Generar Nombre y Guardar - nombre_archivo = f"{request.facturas[0][:15]}_Mainx30.dat" - if len(request.facturas) > 1: - nombre_archivo = f"MULTIPLE_Mainx30.dat" - - if request.nomenclatura_factura and len(request.facturas) == 1: - nombre_archivo = f"{request.facturas[0][:15]}_Mainx30.dat" - - content = '\r\n'.join(lineas) - - return Mainx30Response( - success=len(self.errores_validacion) == 0, - message="Archivo generado" if len(self.errores_validacion) == 0 else "Archivo generado con errores de validación", - archivo_generado=nombre_archivo, - ruta_archivo="", - content=content, - errores_validacion=self.errores_validacion, - cuenta_partidas=self.cuenta_partidas, - valor_total=self.valor_total, - peso_bruto_total=self.peso_bruto_total, - peso_neto_total=self.peso_neto_total, - cuenta_facturas=self.cuenta_facturas - ) - - except HTTPException: - raise - except Exception as e: - import traceback - traceback.print_exc() - raise HTTPException(status_code=500, detail=f"Error generando Mainx30: {str(e)}") - - def _inicializar_variables(self): - self.errores_validacion = [] - self.cuenta_partidas = 0 - self.valor_total = 0.0 - self.flete_total = 0.0 - self.peso_bruto_total = 0.0 - self.peso_neto_total = 0.0 - self.cuenta_facturas = 0 - - def _obtener_datos_company(self, db: Session) -> EmpresaDatos: - empresa = db.query(GEmpresa).options(joinedload(GEmpresa.addresses)).first() - if not empresa: - return EmpresaDatos(broker="", responsable="", rfc="", tiene_linea_express="N", nombre_empresa="", manufacturer_id="", ftp_key="", main_activity="", city_state="") - - # Get city/state from main address, or first found - city_state = "" - main_addr = next((a for a in (empresa.addresses or []) if a.address_type == 'main'), None) - if not main_addr and empresa.addresses: - main_addr = empresa.addresses[0] - - if main_addr: - # Clarion expects 20 chars for city_state: 5 CP + 11 City + 4 State - cp = (main_addr.postal_code or "")[:5] - city = (main_addr.city or "")[:11] - state = (main_addr.state or "")[:4] - city_state = f"{cp:<5}{city:<11}{state:<4}" - - return EmpresaDatos( - broker=(empresa.broker_company or "")[:5], - responsable=(empresa.responsible or "")[:30], - rfc=(empresa.rfc or "")[:13], - tiene_linea_express="N", - nombre_empresa=(empresa.name or "")[:40], - manufacturer_id=(empresa.manufacturer_id or "")[:10], - ftp_key=(empresa.ftp_key or "")[:10], - main_activity=(empresa.main_activity or "")[:30], - city_state=city_state[:30] - ) \ No newline at end of file diff --git a/backend/api/v1/modules/a76/reports/importacion/transmission/temporal/MAINX30/task.py b/backend/api/v1/modules/a76/reports/importacion/transmission/temporal/MAINX30/task.py deleted file mode 100644 index 584ea348..00000000 --- a/backend/api/v1/modules/a76/reports/importacion/transmission/temporal/MAINX30/task.py +++ /dev/null @@ -1,47 +0,0 @@ -from celery import Task -from fastapi import HTTPException -from core.celery_app import celery_app -from core.celery_app import celery_app -from core.database import get_core_db as get_db -from .service import Mainx30Service -from .schemas import Mainx30GenerationRequest, Mainx30Response - -@celery_app.task(name="generar_transmission_temporal_async", bind=True) -def generar_transmission_temporal_async(self, request_data: dict, tenant_id: int): - """ - Generates the transmission .dat file asynchronously using Mainx30Service - """ - try: - # Re-create db session for task - # Using next(get_db()) is a common pattern for obtaining a session in tasks - # but ensure context management - db = next(get_db()) - - # Deserialize request - request = Mainx30GenerationRequest(**request_data) - - service = Mainx30Service() - response = service.generar_mainx30(db, request, task_instance=self) - - # Return result as dict for Celery serialization - # Ensure we return valid JSON serializable dict - result = response.model_dump() - - # If we returned content directly, encode it if it's bytes (it's str here) - if response.content: - import base64 - # Mainx30Service returns content as string with \r\n - encoded_content = base64.b64encode(response.content.encode('utf-8')).decode('utf-8') - # Add to result to match expected format by frontend dialog - result['content'] = encoded_content - result['file_name'] = response.archivo_generado - result['media_type'] = "text/plain" - - return result - - except Exception as e: - import traceback - traceback.print_exc() - self.update_state(state='FAILURE', meta={'exc_type': type(e).__name__, 'exc_message': str(e)}) - # Re-raise to mark task as failed in Celery - raise e diff --git a/backend/api/v1/modules/a76/reports/importacion/winsaai/__init__.py b/backend/api/v1/modules/a76/reports/importacion/winsaai/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/backend/api/v1/modules/a76/reports/importacion/winsaai/invoices/__init__.py b/backend/api/v1/modules/a76/reports/importacion/winsaai/invoices/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/backend/api/v1/modules/a76/reports/importacion/winsaai/invoices/routes.py b/backend/api/v1/modules/a76/reports/importacion/winsaai/invoices/routes.py deleted file mode 100644 index bd83d6d3..00000000 --- a/backend/api/v1/modules/a76/reports/importacion/winsaai/invoices/routes.py +++ /dev/null @@ -1,91 +0,0 @@ -from fastapi import APIRouter, Depends, BackgroundTasks -from sqlalchemy.orm import Session -from core.database import get_core_db -from .schemas import WinsaaiGenerationRequest, WinsaaiResponse -from .task import generate_winsaai_task -from core.celery_app import celery_app -from celery.result import AsyncResult -from typing import Dict, Any -import logging -from core.security import get_current_user, get_tenant_from_token -from api.v1.modules.core.tasks_tracking import track_and_dispatch - -logger = logging.getLogger(__name__) - -router = APIRouter(tags=["WINSAAI"]) - -@router.post("/generate", response_model=WinsaaiResponse) -async def trigger_winsaai_generation( - request: WinsaaiGenerationRequest, - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - tenant_id = get_tenant_from_token(current_user) or current_user.get("tenant_id") - task = track_and_dispatch( - db=db, - task=generate_winsaai_task, - tenant_id=int(tenant_id), - requested_by_user=current_user.get("preferred_username") or current_user.get("email") or current_user.get("sub"), - task_name="generate_winsaai_task", - task_group="reports", - task_origin="a76/reports/importacion/winsaai/invoices", - args=[request.invoice_ids, request.is_temporal], - ) - return WinsaaiResponse( - task_id=task.id, - status="PENDING", - message="WINSAAI generation task started" - ) - -@router.get("/status/{task_id}") -async def get_task_status(task_id: str): - logger.info(f"Polling status for WINSAAI task {task_id}") - task_result = AsyncResult(task_id, app=celery_app) - - try: - state = task_result.state - info = task_result.info - logger.debug(f"Task {task_id} state: {state}, info: {info}") - except Exception as e: - logger.error(f"Error polling task {task_id}: {str(e)}") - return {"task_id": task_id, "state": "FAILURE", "result": str(e), "info": None} - - response = { - "task_id": task_id, - "state": state, - "result": None, - "info": None - } - - if state == 'FAILURE': - # Safely try to get result, but handle cases where it can't be deserialized - try: - # result = task_result.result # This might trigger the ValueError - # Better: if it's FAILURE, just report it as failed and maybe look into info/meta - response["result"] = "Task failed. Check worker logs for details." - if isinstance(info, dict) and 'error' in info: - response["result"] = info['error'] - except Exception: - response["result"] = "Task failed (error reading exception details)" - elif state == 'SUCCESS': - response["result"] = task_result.result - elif state in ['PROCESSING', 'PROGRESS']: - response["info"] = info - - return response - -@router.get("/download/{task_id}") -async def download_winsaai_file(task_id: str): - res = AsyncResult(task_id) - if not res.ready(): - return {"error": "Task not ready"} - - result = res.result - if isinstance(result, dict) and "filepath" in result: - from fastapi.responses import FileResponse - return FileResponse( - path=result["filepath"], - filename=result["filename"], - media_type="text/plain" - ) - return {"error": "File not found"} diff --git a/backend/api/v1/modules/a76/reports/importacion/winsaai/invoices/schemas.py b/backend/api/v1/modules/a76/reports/importacion/winsaai/invoices/schemas.py deleted file mode 100644 index a41d017f..00000000 --- a/backend/api/v1/modules/a76/reports/importacion/winsaai/invoices/schemas.py +++ /dev/null @@ -1,20 +0,0 @@ -from pydantic import BaseModel, Field -from typing import List, Optional -from datetime import datetime - -class WinsaaiGenerationRequest(BaseModel): - invoice_ids: List[int] = Field(..., description="List of invoice IDs to process") - is_temporal: bool = Field(True, description="Whether to generate as temporal (SCAII) or definitive") - -class ErrorValidacion(BaseModel): - partida: Optional[int] = None - linea: int - descripcion: str - soluciones: Optional[str] = None - identificador: Optional[str] = None - -class WinsaaiResponse(BaseModel): - task_id: str - status: str = "PENDING" - message: str = "WINSAAI generation task started" - validation_errors: List[ErrorValidacion] = [] diff --git a/backend/api/v1/modules/a76/reports/importacion/winsaai/invoices/service.py b/backend/api/v1/modules/a76/reports/importacion/winsaai/invoices/service.py deleted file mode 100644 index c00cafce..00000000 --- a/backend/api/v1/modules/a76/reports/importacion/winsaai/invoices/service.py +++ /dev/null @@ -1,340 +0,0 @@ -""" -Invoice WINSAAI Service – Legacy Format -Generates the legacy WINSAAI .txt file that matches the output of the -original Clarion system, used with the WINSAAI utility. - -Format reference (from real system example): - 501|1|REGIME|PED_NUM|ADUANA|IGI|IVA|DTA|PREVAL|OTROS|WEIGHT|BULTOS|7|7|7||... - 503||GUIA|| - 505|FACTURA|YYYYMMDD|INCOTERM|MONEDA|VALOR_ME|VALOR_MN|...|CANT_LINES|SOLD_TO_SHORT|SOLD_TO_NAME| - 551|FRACCION|DESC|VAL_MN|VAL_ME|CANT|UM|CANT_TAR|0|0|FPAGO|||P_ORIG|P_IMP|||||0||PESO|||MONEDA|FACTURA||0|PESO|PESO|MET_VALOR|LINEA|||... - 558|| - 999| -""" - -import re -from typing import List, Optional -from sqlalchemy.orm import Session, joinedload - -from api.v1.modules.a76.invoices.models import ( - InvoiceHeader, InvoiceComplianceMx, InvoiceFinancials, InvoiceLogistics, -) -from api.v1.modules.a76.items.models import LineItem -from api.v1.modules.a76.items.line_quantities.models import LineQuantity -from api.v1.modules.a76.items.line_financials.models import LineFinancial -from api.v1.modules.a76.items.line_customs.models import LineCustom -from api.v1.modules.a76.items.line_descriptions.models import LineDescription -from api.v1.modules.a76.general_catalogs.units_of_measure.models import UnitOfMeasure -from api.v1.modules.a76.parts.models import Part -from api.v1.modules.a76.clients_and_providers.models import ClientProvider -from api.v1.modules.a76.customs_brokers.models import CustomsBroker -from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos -from api.v1.modules.a76.pedmientos.models.pedimento_contributions import PedimentoContributions - - -# ───────────────────────────────────────────────────────────────────────────── -# HELPERS -# ───────────────────────────────────────────────────────────────────────────── - -def _s(val) -> str: - """Safe string conversion – return '' for None.""" - if val is None: - return "" - return str(val).strip() - - -def _n(val, decimals: int = 0) -> str: - """Format numeric value without leading zeros, with optional decimal places.""" - if val is None: - return "0" - try: - f = float(val) - if decimals: - return f"{f:.{decimals}f}" - else: - # Integer representation, no decimals - return str(int(f)) - except (TypeError, ValueError): - return "0" - - -def _fmt_date_ymd(dt) -> str: - """Format as YYYYMMDD (legacy WINSAAI style).""" - if not dt: - return "" - return dt.strftime("%Y%m%d") - - -def _clean(text: str) -> str: - """Remove pipes and line breaks that would corrupt the pipe-delimited format.""" - if not text: - return "" - text = re.sub(r'[\r\n]+', ' ', text) - text = text.replace('|', '/') - return text.strip() - - -def _sanitize_filename(name: str) -> str: - return re.sub(r'[/\\:*?"<>|]', '', name) - - -# ───────────────────────────────────────────────────────────────────────────── -# SERVICE -# ───────────────────────────────────────────────────────────────────────────── - -class WinsaaiService: - def __init__(self, db: Session, company_id: int, tenant_id: int): - self.db = db - self.company_id = company_id - self.tenant_id = tenant_id - - # ── ENTRY POINT ────────────────────────────────────────────────────────── - - def generate_winsaai_data(self, invoice_ids: List[int], is_temporal: bool) -> List[str]: - all_lines: List[str] = [] - for inv_id in invoice_ids: - invoice = ( - self.db.query(InvoiceHeader) - .options( - joinedload(InvoiceHeader.compliance_mx), - joinedload(InvoiceHeader.financials), - joinedload(InvoiceHeader.logistics), - joinedload(InvoiceHeader.details), - ) - .filter(InvoiceHeader.id == inv_id) - .first() - ) - if not invoice: - continue - all_lines.extend(self._gen_legacy(invoice)) - # Terminator - all_lines.append("999|") - return all_lines - - # ── MAIN GENERATOR ─────────────────────────────────────────────────────── - - def _gen_legacy(self, inv: InvoiceHeader) -> List[str]: - lines: List[str] = [] - - comp: Optional[InvoiceComplianceMx] = inv.compliance_mx - fin: Optional[InvoiceFinancials] = inv.financials - log: Optional[InvoiceLogistics] = inv.logistics - - # ── Pedimento ──────────────────────────────────────────────────────── - ped: Optional[Pedimentos] = None - if comp and comp.pedimento_id: - ped = self.db.query(Pedimentos).filter(Pedimentos.id == comp.pedimento_id).first() - - ped_full = (ped.pedimento_number or "") if ped else "" - # Clarion: SUB(Pedimento,4,4) → chars 3-7 = license/patente - # SUB(Pedimento,9,7) → after hyphen = 7-digit number - # Legacy example uses just the number portion (the integer part) - # We'll parse: aduana(3) + año(2) + patente(4) + num(7) or use raw sub-strings - ped_aduana = _s(ped.customs_office) if ped else "" - ped_regime = _s(ped.regime) if ped else "" - - # Try to extract 7-digit number from pedimento_number - # Formats: "010 IN 3130441" or "01023130441" - ped_num_raw = "".join(filter(str.isdigit, ped_full)) - if len(ped_num_raw) >= 7: - ped_num_short = ped_num_raw[-7:] # last 7 digits (the folio) - else: - ped_num_short = ped_num_raw - - # ── Partners ───────────────────────────────────────────────────────── - sold_to = self._get_partner(comp.sold_to_id) if comp and comp.sold_to_id else None - provider = self._get_partner(comp.provider_id) if comp and comp.provider_id else None - - # ── Financial totals ───────────────────────────────────────────────── - tc = float(fin.exchange_rate or 1) if fin else 1.0 - currency = _s(fin.currency_type) if fin else "USD" - if currency.upper() in ("MXP", "MXN"): - currency = "MXP" - else: - currency = "USD" - - valor_me_hdr = float(fin.value_me or 0) if fin else 0.0 - freight = float(fin.freight or 0) if fin else 0.0 - insurance = float(fin.insurance or 0) if fin else 0.0 - gross_wt_hdr = float(fin.gross_weight or 0) if fin else 0.0 - bundles = int(fin.bundle_count or 0) if fin else 0 - incoterm = _s(log.incoterm) if log else "" - - # ── Items (needed early for summing) ───────────────────────────────── - items = ( - self.db.query(LineItem) - .filter(LineItem.invoice_id == inv.id) - .order_by(LineItem.line_number) - .all() - ) - - # Sum values from line items when header totals are missing/zero - sum_val_me = 0.0 - sum_val_mn = 0.0 - sum_gross = 0.0 # goes into 501 (peso bruto total) - sum_bultos = 0 - for _item in items: - _fin = _item.financial - _qty = _item.quantity - if _fin: - sum_val_me += float(_fin.value_usd or 0) - sum_val_mn += float(_fin.value_mxn or 0) - if _qty: - sum_gross += float(_qty.gross_weight or _qty.net_weight or 0) - sum_bultos += int(_qty.package_quantity or 0) - - # Use header value if present, otherwise sum from items - valor_me = valor_me_hdr if valor_me_hdr > 0 else sum_val_me - valor_mn = (valor_me * tc) if sum_val_mn == 0 else sum_val_mn - gross_wt = gross_wt_hdr if gross_wt_hdr > 0 else sum_gross - if bundles == 0 and sum_bultos > 0: - bundles = sum_bultos - - # ── Contributions (DTA, Prevalidación, IGI, IVA) ───────────────────── - igi_total = 0.0 - iva_total = 0.0 - dta_total = 0.0 - preval_total = 0.0 - if ped: - contribs = ( - self.db.query(PedimentoContributions) - .filter(PedimentoContributions.pedimento_id == ped.id) - .all() - ) - for c in contribs: - code = (c.contribucion or "").upper() - abrev = (c.abreviacion or "").upper() - amount = float(c.importe or 0) - if "IGI" in code or abrev == "IGI": - igi_total += amount - elif "IVA" in code or abrev == "IVA": - iva_total += amount - elif "DTA" in code or abrev == "DTA": - dta_total += amount - elif "PREVAL" in code or "15" in code: - preval_total += amount - - - - # ── REGISTRO 501 ────────────────────────────────────────────────────── - # Legacy: 501|1|REGIME|PED_NUM|ADUANA|IGI|IVA|DTA|PREVAL|0|GROSS_WT|BUNDLES|7|7|7||... - # Example: 501|1|IN|3130441|ENTRADA|0|0|0|0|0|4156.960|0|7|7|7||||160|1764|... - lines.append( - f"501|1|{ped_regime}|{ped_num_short}|{ped_aduana}|" - f"{_n(igi_total, 3)}|{_n(iva_total, 3)}|{_n(dta_total, 3)}|{_n(preval_total, 3)}|0|" - f"{_n(gross_wt, 3)}|{bundles}|7|7|7||||" - f"{_n(freight, 3)}|{_n(insurance, 3)}|1|||||||||||||||" - ) - - # ── REGISTRO 503 ────────────────────────────────────────────────────── - niu = _s(comp.niu_number) if comp else "" - guide = _s(comp.guide_type_to_identify) if comp else "" - lines.append(f"503|{niu}|{guide}||") - - # ── REGISTRO 505 ────────────────────────────────────────────────────── - # Legacy: 505|FACTURA|YYYYMMDD|INCOTERM|MONEDA|VALOR_ME|VALOR_MN||||||||||CANT_LINES|SOLD_SHORT|SOLD_NAME| - # Example: 505|1111|20230525|DAP|USD|69760.00|69760.00||||||||||3|ENTRADA|ENTRADA GROUP...| - sold_short = _s(sold_to.short_name) if sold_to else "" - sold_name = _clean(_s(sold_to.name)) if sold_to else "" - invoicenum = _s(inv.invoice_number) - fecha_fac = _fmt_date_ymd(inv.invoice_date) - - lines.append( - f"505|{invoicenum}|{fecha_fac}|{incoterm}|{currency}|" - f"{_n(valor_me, 2)}|{_n(valor_mn, 2)}||||||||||" - f"{len(items)}|{sold_short}|{sold_name}|" - ) - - # ── REGISTROS 551 + 558 por partida ────────────────────────────────── - # Legacy 551 format (from example): - # 551|FRACCION|DESC|VAL_MN(int)|VAL_ME(dec)|CANT(3dec)|UM_ADUANA|CANT_TAR(3dec)|0|0|FPAGO|||P_ORIG|P_IMP|||||0||PESO|||MONEDA|FACTURA||0|PESO|PESO|MET_VALOR|LINEA|||||||FPAGO||||||||99| - linea_num = 0 - for item in items: - linea_num += 1 - item_qty: Optional[LineQuantity] = item.quantity - item_fin: Optional[LineFinancial] = item.financial - item_cust: Optional[LineCustom] = item.customs - item_desc: Optional[LineDescription] = item.description - - # Fraccion - fraccion = "" - if item_cust and item_cust.fraction: - fraccion = item_cust.fraction[:8] - - # Descripción - part = None - if item.part_number_id: - part = self.db.query(Part).filter(Part.id == item.part_number_id).first() - desc = "" - if part: - desc = _clean(part.description_spanish or part.description or "") - if not desc and item_desc: - desc = _clean(item_desc.description_spanish or item_desc.description1 or "") - - # Valores - val_me = float(item_fin.value_usd or 0) if item_fin else 0.0 - val_mn = float(item_fin.value_mxn or val_me * tc) if item_fin else val_me * tc - - # Cantidades - cant = float(item_qty.quantity or 0) if item_qty else 0.0 - - # UM aduanas (Clave_AMex) - um_aduana = "" - if item.unit_of_measure: - uom = self.db.query(UnitOfMeasure).filter(UnitOfMeasure.id == item.unit_of_measure).first() - if uom: - um_aduana = _s(uom.customs_code) - - # Cantidad tarifa - cant_tar = float(item_qty.quantity_uma or cant) if item_qty else cant - - # Forma de pago (del item) - forma_pago = _s(item.payment_method) - - # País origen y país importador - pais_orig = _s(item_cust.origin_country) if item_cust else "" - pais_imp = "" - if sold_to and sold_to.address: - pais_imp = _s(sold_to.address.country) - - # Peso neto - peso = float(item_qty.net_weight or 0) if item_qty else 0.0 - - # MetValor - met_valor = _s(item.valuation_method) or _s(comp.value_method if comp else "") - - # Legacy 551 field layout (counting from example): - # 551|frac|desc|val_mn|val_me|cant|um|cant_tar|0|0|fpago|||p_orig|p_imp|||||0||peso|||moneda|factura||0|peso|peso|met_valor|linea|||||||fpago||||||||99| - lines.append( - f"551|{fraccion}|{desc}|{_n(val_mn, 0)}|{_n(val_me, 2)}|" - f"{_n(cant, 3)}|{um_aduana}|{_n(cant_tar, 3)}|0|0|{forma_pago}|||" - f"{pais_orig}|{pais_imp}|||||0||{_n(peso, 3)}|||" - f"{currency}|{invoicenum}||0|{_n(peso, 3)}|{_n(peso, 3)}|" - f"{met_valor}|{linea_num}|||||||{forma_pago}||||||||99|" - ) - - # ── REGISTRO 558 ────────────────────────────────────────────────── - # Legacy: always emit an empty 558 after each 551 - # If there is additional info, emit it, otherwise just empty: 558|| - info = "" - if item_desc and hasattr(item_desc, 'extra_description'): - info = _clean(item_desc.extra_description or "") - lines.append(f"558|{info}|") - - return lines - - # ── HELPERS ────────────────────────────────────────────────────────────── - - def _get_partner(self, partner_id: int) -> Optional[ClientProvider]: - if not partner_id: - return None - return ( - self.db.query(ClientProvider) - .options(joinedload(ClientProvider.address)) - .filter(ClientProvider.id == partner_id) - .first() - ) - - def get_filename(self, invoice: InvoiceHeader) -> str: - inv_num = _sanitize_filename(invoice.invoice_number or "") - return f"{inv_num}.txt" diff --git a/backend/api/v1/modules/a76/reports/importacion/winsaai/invoices/task.py b/backend/api/v1/modules/a76/reports/importacion/winsaai/invoices/task.py deleted file mode 100644 index d4e14e1c..00000000 --- a/backend/api/v1/modules/a76/reports/importacion/winsaai/invoices/task.py +++ /dev/null @@ -1,63 +0,0 @@ -from fastapi import HTTPException -from core.celery_app import celery_app -from core.database import CoreSessionLocal -from .service import WinsaaiService -import os -import tempfile - -import logging -import sys - -logger = logging.getLogger(__name__) - -@celery_app.task(bind=True) -def generate_winsaai_task(self, invoice_ids: list, is_temporal: bool): - logger.info(f"Starting WINSAAI task {self.request.id} for {invoice_ids}") - db = CoreSessionLocal() - try: - self.update_state(state='PROCESSING', meta={'current': 10, 'status': 'Cargando servicio...'}) - from api.v1.modules.a76.invoices.models import InvoiceHeader as _InvH - _first = db.query(_InvH).filter(_InvH.id == invoice_ids[0]).first() if invoice_ids else None - _company_id = _first.company_id if _first else 0 - _tenant_id = _first.tenant_id if _first else 0 - service = WinsaaiService(db, _company_id, _tenant_id) - - self.update_state(state='PROCESSING', meta={'current': 30, 'status': 'Generando datos...'}) - lines = service.generate_winsaai_data(invoice_ids, is_temporal) - - self.update_state(state='PROCESSING', meta={'current': 70, 'status': 'Guardando archivo...'}) - - # Determine filename - from api.v1.modules.a76.invoices.models import InvoiceHeader - first_invoice = db.query(InvoiceHeader).filter(InvoiceHeader.id == invoice_ids[0]).first() if invoice_ids else None - filename = f"{first_invoice.invoice_number}.txt" if first_invoice else f"winsaai_{self.request.id}.txt" - - temp_dir = tempfile.gettempdir() - filepath = os.path.join(temp_dir, filename) - - with open(filepath, "w", encoding="latin-1") as f: - for line in lines: - f.write(line + "\n") - - self.update_state(state='PROCESSING', meta={'current': 90, 'status': 'Finalizando...'}) - - import base64 - with open(filepath, "rb") as f: - encoded_content = base64.b64encode(f.read()).decode('utf-8') - - logger.info(f"WINSAAI task {self.request.id} completed successfully") - return { - "status": "success", - "content": encoded_content, - "filepath": filepath, - "filename": filename, - "file_name": filename, - "media_type": "text/plain", - "validation_errors": [] - } - except Exception as e: - logger.error(f"Error in generate_winsaai_task: {str(e)}", exc_info=True) - # No actualizar estado a FAILURE manualmente, dejar que Celery lo haga al re-lanzar - raise e - finally: - db.close() diff --git a/backend/api/v1/modules/a76/reports/importacion/winsaai/pedimentos/__init__.py b/backend/api/v1/modules/a76/reports/importacion/winsaai/pedimentos/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/backend/api/v1/modules/a76/reports/importacion/winsaai/pedimentos/routes.py b/backend/api/v1/modules/a76/reports/importacion/winsaai/pedimentos/routes.py deleted file mode 100644 index 4c274f1f..00000000 --- a/backend/api/v1/modules/a76/reports/importacion/winsaai/pedimentos/routes.py +++ /dev/null @@ -1,84 +0,0 @@ -from fastapi import APIRouter, Depends, HTTPException -from sqlalchemy.orm import Session -from core.database import get_core_db -from .schemas import PedimentosWinsaaiGenerationRequest, WinsaaiResponse -from .task import generate_pedimentos_winsaai_task -from core.celery_app import celery_app -from celery.result import AsyncResult -from typing import Dict, Any -import logging -from core.security import get_current_user, get_tenant_from_token -from api.v1.modules.core.tasks_tracking import track_and_dispatch - -logger = logging.getLogger(__name__) - -router = APIRouter(tags=["WINSAAI-Pedimentos"]) - -@router.post("/generate", response_model=WinsaaiResponse) -async def trigger_pedimentos_generation( - request: PedimentosWinsaaiGenerationRequest, - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - tenant_id = get_tenant_from_token(current_user) or current_user.get("tenant_id") - task = track_and_dispatch( - db=db, - task=generate_pedimentos_winsaai_task, - tenant_id=int(tenant_id), - requested_by_user=current_user.get("preferred_username") or current_user.get("email") or current_user.get("sub"), - task_name="generate_pedimentos_winsaai_task", - task_group="reports", - task_origin="a76/reports/importacion/winsaai/pedimentos", - args=[request.pedimento_ids, request.is_temporal, request.is_by_class], - ) - return WinsaaiResponse( - task_id=task.id, - status="PENDING", - message="WINSAAI pedimentos generation task started" - ) - -@router.get("/status/{task_id}") -async def get_task_status(task_id: str): - logger.info(f"Polling status for WINSAAI pedimento task {task_id}") - task_result = AsyncResult(task_id, app=celery_app) - - try: - state = task_result.state - info = task_result.info - except Exception as e: - logger.error(f"Error polling task {task_id}: {str(e)}") - return {"task_id": task_id, "state": "FAILURE", "result": str(e), "info": None} - - response = { - "task_id": task_id, - "state": state, - "result": None, - "info": None - } - - if state == 'FAILURE': - response["result"] = "Task failed. Check worker logs for details." - if isinstance(info, dict) and 'error' in info: - response["result"] = info['error'] - elif state == 'SUCCESS': - response["result"] = task_result.result - elif state in ['PROCESSING', 'PROGRESS']: - response["info"] = info - - return response - -@router.get("/download/{task_id}") -async def download_file(task_id: str): - res = AsyncResult(task_id) - if not res.ready(): - raise HTTPException(status_code=400, detail="Task not ready") - - result = res.result - if isinstance(result, dict) and "filepath" in result: - from fastapi.responses import FileResponse - return FileResponse( - path=result["filepath"], - filename=result["filename"], - media_type="text/plain" - ) - raise HTTPException(status_code=404, detail="File not found") diff --git a/backend/api/v1/modules/a76/reports/importacion/winsaai/pedimentos/schemas.py b/backend/api/v1/modules/a76/reports/importacion/winsaai/pedimentos/schemas.py deleted file mode 100644 index 8bbbb72e..00000000 --- a/backend/api/v1/modules/a76/reports/importacion/winsaai/pedimentos/schemas.py +++ /dev/null @@ -1,20 +0,0 @@ -from pydantic import BaseModel, Field -from typing import List, Optional - -class PedimentosWinsaaiGenerationRequest(BaseModel): - pedimento_ids: List[int] = Field(..., description="List of Pedimento IDs to process") - is_temporal: bool = Field(True, description="Whether to generate as temporal (SCAII) or definitive") - is_by_class: bool = Field(False, description="Whether to group items by Class (SCAF)") - -class ErrorValidacion(BaseModel): - partida: Optional[int] = None - linea: int - descripcion: str - soluciones: Optional[str] = None - identificador: Optional[str] = None - -class WinsaaiResponse(BaseModel): - task_id: str - status: str = "PENDING" - message: str = "WINSAAI generation task started" - validation_errors: List[ErrorValidacion] = [] diff --git a/backend/api/v1/modules/a76/reports/importacion/winsaai/pedimentos/service.py b/backend/api/v1/modules/a76/reports/importacion/winsaai/pedimentos/service.py deleted file mode 100644 index 84d0f8eb..00000000 --- a/backend/api/v1/modules/a76/reports/importacion/winsaai/pedimentos/service.py +++ /dev/null @@ -1,460 +0,0 @@ -""" -Pedimentos WINSAAI Service – Legacy Format -Generates the legacy WINSAAI .txt file for a pedimento, consolidating -all linked invoices and their line items into a single file. - -Format: - 501| - 503| - 505| - 551| (repeated) - 558| - 999| -""" - -import re -from typing import List, Optional -from decimal import Decimal -from sqlalchemy.orm import Session, joinedload -from fastapi import HTTPException - -from api.v1.modules.a76.invoices.models import ( - InvoiceHeader, InvoiceComplianceMx, InvoiceFinancials, InvoiceLogistics -) -from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos -from api.v1.modules.a76.items.models import LineItem -from api.v1.modules.a76.items.line_quantities.models import LineQuantity -from api.v1.modules.a76.items.line_financials.models import LineFinancial -from api.v1.modules.a76.items.line_customs.models import LineCustom -from api.v1.modules.a76.items.line_descriptions.models import LineDescription -from api.v1.modules.a76.general_catalogs.company.models import Company -from api.v1.modules.a76.general_catalogs.units_of_measure.models import UnitOfMeasure -from api.v1.modules.a76.customs_brokers.models import CustomsBroker -from api.v1.modules.a76.clients_and_providers.models import ClientProvider -from api.v1.modules.a76.parts.models import Part -from api.v1.modules.a76.classes.models import Class -from .schemas import ErrorValidacion - - -# ───────────────────────────────────────────────────────────────────────────── -# HELPERS -# ───────────────────────────────────────────────────────────────────────────── - -def _s(val) -> str: - if val is None: - return "" - return str(val).strip() - - -def _n(val, decimals: int = 0) -> str: - if val is None: - return "0" - try: - f = float(val) - if decimals: - return f"{f:.{decimals}f}" - return str(int(f)) - except (TypeError, ValueError): - return "0" - - -def _fmt_date_ymd(dt) -> str: - if not dt: - return "" - return dt.strftime("%Y%m%d") - - -def _clean(text: str) -> str: - if not text: - return "" - text = re.sub(r'[\r\n]+', ' ', text) - text = text.replace('|', '/') - return text.strip() - - -# ───────────────────────────────────────────────────────────────────────────── -# SERVICE -# ───────────────────────────────────────────────────────────────────────────── - -class PedimentosWinsaaiService: - def __init__(self, db: Session): - self.db = db - self.validation_errors: List[ErrorValidacion] = [] - self.line_count = 0 - - # ── ENTRY POINT ────────────────────────────────────────────────────────── - - def generate_pedimentos_data( - self, - pedimento_ids: List[int], - is_temporal: bool = True, - is_by_class: bool = False, - ) -> List[str]: - lines: List[str] = [] - self.line_count = 0 - self.validation_errors = [] - - company = self.db.query(Company).first() - if not company: - raise HTTPException(status_code=404, detail="Company information not found") - - for p_id in pedimento_ids: - pedimento = self.db.query(Pedimentos).filter(Pedimentos.id == p_id).first() - if not pedimento: - continue - - # All invoices linked to this pedimento - invoices = ( - self.db.query(InvoiceHeader) - .join(InvoiceHeader.compliance_mx) - .filter(InvoiceComplianceMx.pedimento_id == p_id) - .options( - joinedload(InvoiceHeader.compliance_mx), - joinedload(InvoiceHeader.financials), - joinedload(InvoiceHeader.logistics), - ) - .all() - ) - - if not invoices: - self.validation_errors.append(ErrorValidacion( - linea=self.line_count, - descripcion=f"Pedimento {pedimento.pedimento_number} no tiene facturas vinculadas", - identificador=pedimento.pedimento_number, - )) - continue - - # ── Consolidate totals across all invoices for 501 ──────────────── - total_peso = 0.0 - total_bultos = 0 - total_flete = 0.0 - total_seguro = 0.0 - total_embalaje = 0.0 - total_otros = 0.0 - - for inv in invoices: - fin = inv.financials - if fin: - total_flete += float(fin.freight or 0) - total_seguro += float(fin.insurance or 0) - total_embalaje += float(getattr(fin, 'packaging', None) or 0) - total_otros += float(getattr(fin, 'other_increments', None) or 0) - - h_peso = float(fin.gross_weight or 0) - h_bultos = int(fin.bundle_count or 0) - if h_peso > 0: - total_peso += h_peso - else: - rows = ( - self.db.query(LineQuantity.gross_weight) - .join(LineItem) - .filter(LineItem.invoice_id == inv.id) - .all() - ) - total_peso += sum(float(r[0] or 0) for r in rows) - - if h_bultos > 0: - total_bultos += h_bultos - else: - rows = ( - self.db.query(LineQuantity.package_quantity) - .join(LineItem) - .filter(LineItem.invoice_id == inv.id) - .all() - ) - total_bultos += sum(int(r[0] or 0) for r in rows) - - # ── REGISTRO 501 (once per pedimento) ──────────────────────────── - lines.append(self._record_501( - pedimento, invoices[0], - total_peso, total_bultos, - total_flete, total_seguro, total_embalaje, total_otros, - )) - - # ── One 503 + 505 + 551s per invoice ───────────────────────────── - for inv_idx, inv in enumerate(invoices, 1): - # 503 - lines.append(self._record_503(inv)) - - # Sum financial totals for 505 - items = ( - self.db.query(LineItem) - .options( - joinedload(LineItem.financial), - joinedload(LineItem.quantity), - joinedload(LineItem.customs), - joinedload(LineItem.description), - joinedload(LineItem.part_info), - joinedload(LineItem.unit_of_measure_info), - joinedload(LineItem.class_info), - joinedload(LineItem.fa_data), - ) - .filter(LineItem.invoice_id == inv.id) - .all() - ) - - sum_me = sum(float(it.financial.value_usd or 0) for it in items if it.financial) - sum_mn = sum(float(it.financial.value_mxn or 0) for it in items if it.financial) - # Fallback to header - if inv.financials: - hdr_me = float(inv.financials.value_me or 0) - if hdr_me > 0: - sum_me = hdr_me - - # 505 - lines.append(self._record_505(inv, sum_mn, sum_me, inv_idx)) - - # Items — optionally grouped by class - items_to_process = self._group_items(items, is_by_class) - - for it_idx, (item, source_items) in enumerate(items_to_process, 1): - tc = float(inv.financials.exchange_rate or 1) if inv.financials else 1.0 - lines.append(self._record_551(item, inv, tc, it_idx)) - - # 554 identifiers - cust = item.customs - if cust and cust.fraction_type: - ftype = cust.fraction_type - if ftype == "TLCS": - lines.append(f"554|TL|{_s(cust.origin_country)}|") - elif ftype == "PROSEC": - lines.append(f"554|PS|{_s(cust.sector)}|") - elif ftype == "ALADI": - lines.append(f"554|AL|{_s(cust.sector)}|") - - # 558 observations - parts: List[str] = [] - if item.description: - if item.description.brand: - parts.append(f"MARCA: {item.description.brand}") - if item.description.model: - parts.append(f"MODELO: {item.description.model}") - if item.description.extra_description: - parts.append(item.description.extra_description) - for src in source_items: - if src.fa_data and src.fa_data.equipment_message: - if src.fa_data.equipment_message not in parts: - parts.append(src.fa_data.equipment_message) - - full_obs = _clean(". ".join(parts)) - if full_obs: - for i in range(0, len(full_obs), 120): - lines.append(f"558|{full_obs[i:i+120]}|") - else: - lines.append("558||") - - self.line_count += len(items_to_process) - - lines.append("999|") - return lines - - # ── RECORD BUILDERS ────────────────────────────────────────────────────── - - def _record_501( - self, - ped: Pedimentos, - rep_inv: InvoiceHeader, - peso: float, bultos: int, - flete: float, seguro: float, embalaje: float, otros: float, - ) -> str: - """ - Legacy: 501|1|REGIME|NUM7|ADUANA|IGI|IVA|DTA|PREVAL|0|PESO|BULTOS|7|7|7||||FLETE|SEGURO|... - Example: 501|1|IN|3130441|ENTRADA|0|0|0|0|0|4156.960|0|7|7|7||||160|1764|... - """ - regime = _s(ped.regime) - aduana = _s(ped.customs_office) - num_raw = "".join(filter(str.isdigit, _s(ped.pedimento_number))) - num7 = num_raw[-7:] if len(num_raw) >= 7 else num_raw - - return ( - f"501|1|{regime}|{num7}|{aduana}|0|0|0|0|0|" - f"{_n(peso, 3)}|{bultos}|7|7|7||||" - f"{_n(flete, 3)}|{_n(seguro, 3)}|1|||||||||||||||||" - ) - - def _record_503(self, inv: InvoiceHeader) -> str: - comp = inv.compliance_mx - niu = _s(comp.niu_number) if comp else "" - guide = _s(comp.guide_type_to_identify) if comp else "" - return f"503|{niu}|{guide}||" - - def _record_505( - self, - inv: InvoiceHeader, - val_mn: float, - val_me: float, - seq: int, - ) -> str: - """ - Legacy: 505|FACTURA|YYYYMMDD|INCOTERM|MONEDA|VAL_ME|VAL_MN|...|COUNT|SHORT|NAME| - """ - comp = inv.compliance_mx - fin = inv.financials - log = inv.logistics - - invoicenum = _s(inv.invoice_number) - fecha = _fmt_date_ymd(inv.invoice_date) - incoterm = _s(log.incoterm) if log else "" - currency = _s(fin.currency_type) if fin else "USD" - currency = "MXP" if currency.upper() in ("MXP", "MXN") else "USD" - tc = float(fin.exchange_rate or 1) if fin else 1.0 - - # val_mn fallback - if val_mn == 0 and val_me > 0: - val_mn = val_me * tc - - sold_to_id = comp.sold_to_id if comp else None - sold_to = self._get_partner(sold_to_id) - sold_short = _s(sold_to.short_name) if sold_to else "" - sold_name = _clean(_s(sold_to.name)) if sold_to else "" - - # Count items for this invoice - item_count = ( - self.db.query(LineItem) - .filter(LineItem.invoice_id == inv.id) - .count() - ) - - return ( - f"505|{invoicenum}|{fecha}|{incoterm}|{currency}|" - f"{_n(val_me, 2)}|{_n(val_mn, 2)}||||||||||" - f"{item_count}|{sold_short}|{sold_name}|" - ) - - def _record_551( - self, - item: LineItem, - inv: InvoiceHeader, - tc: float, - line_num: int, - ) -> str: - """ - Legacy 551: - 551|FRACCION|DESC|VAL_MN|VAL_ME|CANT|UM|CANT_TAR|0|0|FPAGO|||P_ORIG|P_IMP|||||0||PESO|||MONEDA|FACTURA||0|PESO|PESO|MET_VALOR|LINEA|||...99| - """ - fin = item.financial - qty = item.quantity - cust = item.customs - desc = item.description - - # Fracción - fraccion = "" - if cust and cust.fraction: - fraccion = cust.fraction[:8] - - # Descripción - part = None - if item.part_number_id: - part = self.db.query(Part).filter(Part.id == item.part_number_id).first() - description = "" - if part: - description = _clean(part.description_spanish or part.description or "") - if not description and desc: - description = _clean(getattr(desc, 'description_spanish', None) or getattr(desc, 'description1', None) or "") - if not description and item.class_info: - description = _clean(getattr(item.class_info, 'description_es', None) or "") - - # Valores - val_me = float(fin.value_usd or 0) if fin else 0.0 - val_mn = float(fin.value_mxn or val_me * tc) if fin else val_me * tc - - # Cantidades - cant = float(qty.quantity or 0) if qty else 0.0 - - # UM aduanas - um_aduana = "" - if item.unit_of_measure: - uom = self.db.query(UnitOfMeasure).filter(UnitOfMeasure.id == item.unit_of_measure).first() - if uom: - um_aduana = _s(uom.customs_code) - - cant_tar = float(qty.quantity_uma or cant) if qty else cant - - # FormaPago y MetValor - forma_pago = _s(item.payment_method) - met_valor = _s(item.valuation_method) - if not met_valor and inv.compliance_mx: - met_valor = _s(inv.compliance_mx.value_method) - - # País origen e importador - pais_orig = _s(cust.origin_country) if cust else "" - pais_imp = "" - comp = inv.compliance_mx - if comp and comp.sold_to_id: - sold_to = self._get_partner(comp.sold_to_id) - if sold_to and sold_to.address: - pais_imp = _s(sold_to.address.country) - - # Peso neto - peso = float(qty.net_weight or 0) if qty else 0.0 - - # Currency and invoice number - fin_obj = inv.financials - currency = _s(fin_obj.currency_type) if fin_obj else "USD" - currency = "MXP" if currency.upper() in ("MXP", "MXN") else "USD" - invoicenum = _s(inv.invoice_number) - - return ( - f"551|{fraccion}|{description}|{_n(val_mn, 0)}|{_n(val_me, 2)}|" - f"{_n(cant, 3)}|{um_aduana}|{_n(cant_tar, 3)}|0|0|{forma_pago}|||" - f"{pais_orig}|{pais_imp}|||||0||{_n(peso, 3)}|||" - f"{currency}|{invoicenum}||0|{_n(peso, 3)}|{_n(peso, 3)}|" - f"{met_valor}|{line_num}|||||||{forma_pago}||||||||99|" - ) - - # ── HELPERS ────────────────────────────────────────────────────────────── - - def _group_items(self, items, is_by_class: bool): - """Returns list of (item, source_items). Groups by class if is_by_class=True.""" - if not is_by_class: - return [(it, [it]) for it in items] - - groups = {} - for it in items: - fraction = (it.class_info.fraction if it.class_info else getattr(it.customs, 'fraction', '')) or '' - country = getattr(it.customs, 'origin_country', '') or '' - key = (it.class_id, fraction, country, it.valuation_method) - if key not in groups: - groups[key] = { - 'rep': it, - 'val_me': 0.0, 'val_mn': 0.0, - 'qty': 0.0, 'qty_uma': 0.0, - 'gross': 0.0, 'net': 0.0, 'bultos': 0, - 'sources': [], - } - g = groups[key] - g['val_me'] += float(it.financial.value_usd or 0) if it.financial else 0 - g['val_mn'] += float(it.financial.value_mxn or 0) if it.financial else 0 - g['qty'] += float(it.quantity.quantity or 0) if it.quantity else 0 - g['qty_uma'] += float(it.quantity.quantity_uma or 0) if it.quantity else 0 - g['gross'] += float(it.quantity.gross_weight or 0) if it.quantity else 0 - g['net'] += float(it.quantity.net_weight or 0) if it.quantity else 0 - g['bultos'] += int(it.quantity.package_quantity or 0) if it.quantity else 0 - g['sources'].append(it) - - result = [] - for g in groups.values(): - rep = g['rep'] - if rep.financial: - rep.financial.value_usd = g['val_me'] - rep.financial.value_mxn = g['val_mn'] - if rep.quantity: - rep.quantity.quantity = g['qty'] - rep.quantity.quantity_uma = g['qty_uma'] - rep.quantity.gross_weight = g['gross'] - rep.quantity.net_weight = g['net'] - rep.quantity.package_quantity = g['bultos'] - if rep.class_info and rep.customs: - rep.customs.fraction = getattr(rep.class_info, 'fraction', rep.customs.fraction) - result.append((rep, g['sources'])) - return result - - def _get_partner(self, partner_id: Optional[int]) -> Optional[ClientProvider]: - if not partner_id: - return None - return ( - self.db.query(ClientProvider) - .options(joinedload(ClientProvider.address)) - .filter(ClientProvider.id == partner_id) - .first() - ) \ No newline at end of file diff --git a/backend/api/v1/modules/a76/reports/importacion/winsaai/pedimentos/task.py b/backend/api/v1/modules/a76/reports/importacion/winsaai/pedimentos/task.py deleted file mode 100644 index 65ebaf31..00000000 --- a/backend/api/v1/modules/a76/reports/importacion/winsaai/pedimentos/task.py +++ /dev/null @@ -1,54 +0,0 @@ -from fastapi import HTTPException -import logging -from typing import List -import base64 -import os -from core.celery_app import celery_app -from core.database import CoreSessionLocal -from .service import PedimentosWinsaaiService - -logger = logging.getLogger(__name__) - -@celery_app.task(bind=True) -def generate_pedimentos_winsaai_task(self, pedimento_ids: List[int], is_temporal: bool, is_by_class: bool = False): - logger.info(f"Starting Pedimentos WINSAAI task {self.request.id} for pedimentos {pedimento_ids} (by_class={is_by_class})") - db = CoreSessionLocal() - try: - self.update_state(state='PROCESSING', meta={'current': 10, 'status': 'Cargando servicio...'}) - service = PedimentosWinsaaiService(db) - - self.update_state(state='PROCESSING', meta={'current': 30, 'status': 'Generando datos...'}) - lines = service.generate_pedimentos_data(pedimento_ids, is_temporal, is_by_class) - - self.update_state(state='PROCESSING', meta={'current': 70, 'status': 'Guardando archivo...'}) - # Use first pedimento number for filename - from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos - ped = db.query(Pedimentos).filter(Pedimentos.id == pedimento_ids[0]).first() if pedimento_ids else None - if ped: - filename = f"{ped.year}-{ped.license}-{ped.pedimento_number}.txt" - else: - filename = "pedimentos.txt" - filepath = f"/tmp/{filename}" - - # Clarion uses ANSI/UTF-8 usually, but let's stick to UTF-8 for now unless specific requirement - with open(filepath, "w", encoding="utf-8") as f: - f.write("\n".join(lines)) - - self.update_state(state='PROCESSING', meta={'current': 90, 'status': 'Finalizando...'}) - with open(filepath, "rb") as f: - content_b64 = base64.b64encode(f.read()).decode("utf-8") - - return { - "status": "success", - "content": content_b64, - "filepath": filepath, - "filename": filename, - "file_name": filename, - "media_type": "text/plain", - "validation_errors": [e.model_dump() for e in service.validation_errors] - } - except Exception as e: - logger.error(f"Error in generate_pedimentos_winsaai_task: {str(e)}", exc_info=True) - raise e - finally: - db.close() diff --git a/backend/api/v1/modules/a76/reports/importacion/winsaai/router.py b/backend/api/v1/modules/a76/reports/importacion/winsaai/router.py deleted file mode 100644 index 409edbfe..00000000 --- a/backend/api/v1/modules/a76/reports/importacion/winsaai/router.py +++ /dev/null @@ -1,9 +0,0 @@ -from fastapi import APIRouter -from .invoices.routes import router as invoices_router - -router = APIRouter() - -router.include_router(invoices_router, prefix="/invoices", tags=["WINSAAI - Invoices"]) - -from .pedimentos.routes import router as pedimentos_router -router.include_router(pedimentos_router, prefix="/pedimentos", tags=["WINSAAI - Pedimentos"]) diff --git a/backend/api/v1/modules/a76/reports/movements/__init__.py b/backend/api/v1/modules/a76/reports/movements/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/backend/api/v1/modules/a76/reports/movements/invoices/__init__.py b/backend/api/v1/modules/a76/reports/movements/invoices/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/backend/api/v1/modules/a76/reports/movements/invoices/csv_utils.py b/backend/api/v1/modules/a76/reports/movements/invoices/csv_utils.py deleted file mode 100644 index be0ea597..00000000 --- a/backend/api/v1/modules/a76/reports/movements/invoices/csv_utils.py +++ /dev/null @@ -1,151 +0,0 @@ -""" -CSV generation utilities for invoice movement reports. -""" -import csv -import io -from typing import List, Union -from datetime import datetime, date - -from .schemas import MovementItem, MovementItemDetailed, AllMovementsFilter - - -def generate_csv_from_movements( - movements: List[Union[MovementItem, MovementItemDetailed]], - filters: AllMovementsFilter -) -> str: - """ - Generate CSV content from movement items. - - Args: - movements: List of movement items (normal or detailed) - filters: Filter object containing report parameters - - Returns: - CSV content as string - """ - output = io.StringIO() - - if filters.report_type.value.lower() == "normal": - # Normal report - fieldnames = [ - # Identification - 'Factura', 'Pedimento', 'FechaFactura', 'ClavePed', - # Values - 'ValorComercialMN', 'ValorMPTemp', 'TipoCambio', 'ValorAgre', - # Classification - 'TipoMovTemDef', 'Estatus', 'TipoExpo', 'EsCambioRegimen', - # Dates - 'Fecha_Pago', - # References - 'PedimentoR1', 'EDocument', 'NumOperacionVU', - # Logistics - 'NumCaja', 'NumGafUni', 'AduanaCru', - # Metadata - 'BaseDeDatos', 'UsuarioCap', 'UsuarioAcr' - ] - - writer = csv.DictWriter(output, fieldnames=fieldnames, extrasaction='ignore') - writer.writeheader() - - for movement in movements: - row = movement.model_dump() - # Format datetime fields - row['FechaFactura'] = _format_datetime(row.get('FechaFactura')) - row['Fecha_Pago'] = _format_datetime(row.get('Fecha_Pago')) - - # Format numeric fields - row['ValorComercialMN'] = _format_decimal(row.get('ValorComercialMN')) - row['TipoCambio'] = _format_decimal(row.get('TipoCambio')) - row['ValorMPTemp'] = _format_decimal(row.get('ValorMPTemp')) - row['ValorAgre'] = _format_decimal(row.get('ValorAgre')) - - writer.writerow(row) - - else: - # Detailed report - fieldnames = [ - # Identification - 'Linea', 'Factura', 'Pedimento', 'FechaFactura', 'ClavePed', - # Parties - 'Proveedor', 'RFCProveedor', 'ProveedorTaxID', - 'VendidoA', 'VendidoARFC', 'VendidoATaxID', - # Customs broker - 'AgenteAduanal', 'Patente', - # Product - 'NumParte', 'DescripcionE', 'DescripcionI', 'CantidadIE', 'UniMed', - # Classification - 'FraccionArancelaria', 'FraccionAmericana', 'ECCN', 'Sector', 'PaisOrigen', - # Values - 'ValorComercialMN', 'TipoCambio', 'PesoNeto', 'PesoBruto', - # Customs - 'TipoMovTemDef', 'Regimen', 'Aduana', 'Advalorem', 'Preferencia', - # References - 'OrdenCompraVenta', 'Remesa', 'PedimentoR1', 'EDocument', 'NumOperacionVU', - # Identifiers - 'Series', 'Marca', 'Modelo', 'SimboloEx', - # Dates - 'Fecha_Pago', 'Fecha_Inicio', 'Fecha_Fin', 'FechaEmision', - # Logistics - 'Transportista', 'NumCaja', 'NumGafUni', 'AduanaCru', 'Lote', - # Metadata - 'Estatus', 'BaseDeDatos', 'TipoExpo', 'EsCambioRegimen', 'Pedimento18', - 'UsuarioCap', 'UsuarioAcr' - ] - - writer = csv.DictWriter(output, fieldnames=fieldnames, extrasaction='ignore') - writer.writeheader() - - for movement in movements: - row = movement.model_dump() - # Format datetime fields - row['FechaFactura'] = _format_datetime(row.get('FechaFactura')) - row['Fecha_Pago'] = _format_datetime(row.get('Fecha_Pago')) - row['Fecha_Inicio'] = _format_datetime(row.get('Fecha_Inicio')) - row['Fecha_Fin'] = _format_datetime(row.get('Fecha_Fin')) - row['FechaEmision'] = _format_datetime(row.get('FechaEmision')) - - # Format numeric fields - row['ValorComercialMN'] = _format_decimal(row.get('ValorComercialMN')) - row['TipoCambio'] = _format_decimal(row.get('TipoCambio')) - row['CantidadIE'] = _format_decimal(row.get('CantidadIE')) - row['PesoNeto'] = _format_decimal(row.get('PesoNeto')) - row['PesoBruto'] = _format_decimal(row.get('PesoBruto')) - - writer.writerow(row) - - csv_content = output.getvalue() - output.close() - return csv_content - - -def _format_datetime(dt) -> str: - """Format datetime for CSV export.""" - if not dt or dt == '' or dt == '-' or dt == '0': - return '' - try: - if isinstance(dt, str): - if 'T' in dt: - dt_obj = datetime.strptime(dt.split('T')[0], '%Y-%m-%d') - elif len(dt) == 8 and dt.isdigit(): - dt_obj = datetime.strptime(dt, '%Y%m%d') - elif '-' in dt: - dt_obj = datetime.strptime(dt, '%Y-%m-%d') - else: - return dt - elif isinstance(dt, (datetime, date)): - dt_obj = dt - else: - return '' - return dt_obj.strftime('%d/%m/%Y') - except (ValueError, TypeError): - return str(dt) if dt else '' - - -def _format_decimal(value, decimals: int = 2) -> str: - """Format decimal values for CSV export.""" - if value is None: - return '' - try: - return f"{float(value):.{decimals}f}" - except (ValueError, TypeError): - return str(value) if value else '' \ No newline at end of file diff --git a/backend/api/v1/modules/a76/reports/movements/invoices/movement_service.py b/backend/api/v1/modules/a76/reports/movements/invoices/movement_service.py deleted file mode 100644 index 41af2b37..00000000 --- a/backend/api/v1/modules/a76/reports/movements/invoices/movement_service.py +++ /dev/null @@ -1,316 +0,0 @@ -""" -Unified service for invoice movement operations. -This service delegates to specialized handlers for each import type. -""" - -import logging -from sqlalchemy.orm import Session -from typing import List - -from .schemas import ( - ImportTemporaryFilter, - ImportDefinitiveFilter, - ImportRepairFilter, - ExportFilter, - ExportRepairFilter, - AllMovementsFilter, - MovementItem, - MovementItemDetailed, - ReportType -) -from .services.temporary import TemporaryImportService -from .services.definitive import DefinitiveImportService -from .services.repair import RepairImportService -from .services.export import ExportService -from .services.export_repair import ExportRepairService - -logger = logging.getLogger(__name__) - - -class MovementService: - """ - Unified service for handling all types of movements. - Delegates to specialized services for each movement type. - """ - - def __init__(self): - self.temporary_service = TemporaryImportService() - self.definitive_service = DefinitiveImportService() - self.repair_service = RepairImportService() - self.export_service = ExportService() - self.export_repair_service = ExportRepairService() - - # ===== TEMPORARY IMPORTS ===== - - def get_temporary_import_movements( - self, - db: Session, - filters: ImportTemporaryFilter - ) -> List[MovementItem]: - """Get temporary import movements (normal mode - grouped by invoice).""" - return self.temporary_service.get_movements(db, filters) - - def get_temporary_import_movements_detailed( - self, - db: Session, - filters: ImportTemporaryFilter - ) -> List[MovementItemDetailed]: - """Get temporary import movements (detailed mode - line by line).""" - return self.temporary_service.get_movements_detailed(db, filters) - - # ===== DEFINITIVE IMPORTS ===== - - def get_definitive_import_movements( - self, - db: Session, - filters: ImportDefinitiveFilter - ) -> List[MovementItem]: - """Get definitive import movements (normal mode - grouped by invoice).""" - return self.definitive_service.get_movements(db, filters) - - def get_definitive_import_movements_detailed( - self, - db: Session, - filters: ImportDefinitiveFilter - ) -> List[MovementItemDetailed]: - """Get definitive import movements (detailed mode - line by line).""" - return self.definitive_service.get_movements_detailed(db, filters) - - # ===== REPAIR IMPORTS ===== - - def get_repair_import_movements( - self, - db: Session, - filters: ImportRepairFilter - ) -> List[MovementItem]: - """Get repair import movements (normal mode - grouped by invoice).""" - return self.repair_service.get_movements(db, filters) - - def get_repair_import_movements_detailed( - self, - db: Session, - filters: ImportRepairFilter - ) -> List[MovementItemDetailed]: - """Get repair import movements (detailed mode - line by line).""" - return self.repair_service.get_movements_detailed(db, filters) - - # ===== EXPORTS ===== - - def get_export_movements( - self, - db: Session, - filters: ExportFilter - ) -> List[MovementItem]: - """Get export movements (normal mode - grouped by invoice).""" - return self.export_service.get_movements(db, filters) - - def get_export_movements_detailed( - self, - db: Session, - filters: ExportFilter - ) -> List[MovementItemDetailed]: - """Get export movements (detailed mode - line by line).""" - return self.export_service.get_movements_detailed(db, filters) - - # ===== EXPORT REPAIRS ===== - - def get_export_repair_movements( - self, - db: Session, - filters: ExportRepairFilter - ) -> List[MovementItem]: - """Get export repair movements (normal mode - grouped by invoice).""" - return self.export_repair_service.get_movements(db, filters) - - def get_export_repair_movements_detailed( - self, - db: Session, - filters: ExportRepairFilter - ) -> List[MovementItemDetailed]: - """Get export repair movements (detailed mode - line by line).""" - return self.export_repair_service.get_movements_detailed(db, filters) - - # ===== ALL MOVEMENTS ===== - - def get_all_movements( - self, - db: Session, - filters: AllMovementsFilter - ) -> List[MovementItem]: - """ - Get all invoice movements (all types combined). - - This combines: - - Temporary imports - - Definitive imports - - Repair imports - - All export types - - Export repairs - - Returns a unified list sorted by date. - """ - all_movements = [] - - # Convert AllMovementsFilter to individual filter types - # We'll use the same filter parameters for all queries - - # Determine which services to call based on granular flags - # Default behavior: If granular flags are all defaults (True) but operation_type is set, - # we might need to respect operation_type. - # But for simplicity, we assume granular flags from frontend are the source of truth. - # If frontend didn't set them (legacy call?), they default to True. - - # Override based on operation_type if provided (legacy compatibility or coarse filter) - if filters.operation_type == 'imp': - filters.export_def = False - filters.export_rep = False - elif filters.operation_type == 'exp': - filters.import_temp = False - filters.import_def = False - filters.import_rep = False - - logger.info(f"Fetching movements with flags: Temp={filters.import_temp}, Def={filters.import_def}, Rep={filters.import_rep}, ExpDef={filters.export_def}, ExpRep={filters.export_rep}") - - # 1. Temporary Imports - if filters.import_temp: - temp_filter = ImportTemporaryFilter( - range_type=filters.range_type, - start_date=filters.start_date, - end_date=filters.end_date, - include_cancelled=filters.include_cancelled, - provider=filters.provider, - buyer=filters.buyer, - pedimento_code=filters.pedimento_code, - report_type=filters.report_type, - currency_type=filters.currency_type, - exchange_rate_type=filters.exchange_rate_type, - is_shelter=filters.is_shelter, - database_name='default' - ) - if filters.report_type == ReportType.DETAILED: - temp_movements = self.temporary_service.get_movements_detailed(db, temp_filter) - else: - temp_movements = self.temporary_service.get_movements(db, temp_filter) - all_movements.extend(temp_movements) - logger.info(f"Added {len(temp_movements)} temporary import movements") - - # 2. Definitive Imports - if filters.import_def: - def_filter = ImportDefinitiveFilter( - range_type=filters.range_type, - start_date=filters.start_date, - end_date=filters.end_date, - include_cancelled=filters.include_cancelled, - provider=filters.provider, - buyer=filters.buyer, - pedimento_code=filters.pedimento_code, - report_type=filters.report_type, - currency_type=filters.currency_type, - exchange_rate_type=filters.exchange_rate_type, - is_shelter=filters.is_shelter, - database_name='default', - movement_type='ALL', - use_transport_method=False - ) - if filters.report_type == ReportType.DETAILED: - def_movements = self.definitive_service.get_movements_detailed(db, def_filter) - else: - def_movements = self.definitive_service.get_movements(db, def_filter) - all_movements.extend(def_movements) - logger.info(f"Added {len(def_movements)} definitive import movements") - - # 3. Repair Imports - if filters.import_rep: - repair_filter = ImportRepairFilter( - range_type=filters.range_type, - start_date=filters.start_date, - end_date=filters.end_date, - include_cancelled=filters.include_cancelled, - provider=filters.provider, - buyer=filters.buyer, - pedimento_code=filters.pedimento_code, - report_type=filters.report_type, - currency_type=filters.currency_type, - exchange_rate_type=filters.exchange_rate_type, - is_shelter=filters.is_shelter, - database_name='default', - discharge_filter='ALL', - use_transport_method=False - ) - if filters.report_type == ReportType.DETAILED: - repair_movements = self.repair_service.get_movements_detailed(db, repair_filter) - else: - repair_movements = self.repair_service.get_movements(db, repair_filter) - all_movements.extend(repair_movements) - logger.info(f"Added {len(repair_movements)} repair import movements") - - # 4. Exports (Definitive) - if filters.export_def: - export_filter = ExportFilter( - range_type=filters.range_type, - start_date=filters.start_date, - end_date=filters.end_date, - include_cancelled=filters.include_cancelled, - provider=filters.provider, - buyer=filters.buyer, - pedimento_code=filters.pedimento_code, - report_type=filters.report_type, - currency_type=filters.currency_type, - exchange_rate_type=filters.exchange_rate_type, - is_shelter=filters.is_shelter, - database_name='default', - movement_type='ALL', - discharge_filter='ALL', - use_transport_method=False - ) - if filters.report_type == ReportType.DETAILED: - export_movements = self.export_service.get_movements_detailed(db, export_filter) - else: - export_movements = self.export_service.get_movements(db, export_filter) - all_movements.extend(export_movements) - logger.info(f"Added {len(export_movements)} export movements") - - # 5. Export Repairs - if filters.export_rep: - export_repair_filter = ExportRepairFilter( - range_type=filters.range_type, - start_date=filters.start_date, - end_date=filters.end_date, - include_cancelled=filters.include_cancelled, - provider=filters.provider, - buyer=filters.buyer, - pedimento_code=filters.pedimento_code, - report_type=filters.report_type, - currency_type=filters.currency_type, - exchange_rate_type=filters.exchange_rate_type, - is_shelter=filters.is_shelter, - database_name='default', - movement_type='ALL', - discharge_filter='ALL' - ) - if filters.report_type == ReportType.DETAILED: - export_repair_movements = self.export_repair_service.get_movements_detailed(db, export_repair_filter) - else: - export_repair_movements = self.export_repair_service.get_movements(db, export_repair_filter) - all_movements.extend(export_repair_movements) - logger.info(f"Added {len(export_repair_movements)} export repair movements") - - # Sort all movements by date (Fecha field) - # Handle mixed datetime and string types - def get_sort_key(movement): - fecha = movement.FechaFactura - if not fecha: - return "" - # Convert datetime to string for consistent comparison - if hasattr(fecha, 'strftime'): - return fecha.strftime('%Y%m%d') - return str(fecha) - - all_movements.sort(key=get_sort_key) - - logger.info(f"Total movements combined: {len(all_movements)}") - return all_movements - - -# Singleton instance -movement_service = MovementService() diff --git a/backend/api/v1/modules/a76/reports/movements/invoices/routes.py b/backend/api/v1/modules/a76/reports/movements/invoices/routes.py deleted file mode 100644 index 3563c0cc..00000000 --- a/backend/api/v1/modules/a76/reports/movements/invoices/routes.py +++ /dev/null @@ -1,815 +0,0 @@ -import logging -from fastapi import APIRouter, Depends, HTTPException, status, Query -from sqlalchemy.orm import Session -from typing import List, Union - -from core.database import get_core_db -from core.security import get_current_user, get_tenant_from_token -from api.v1.modules.core.tasks_tracking import track_and_dispatch -from .schemas import ( - ImportTemporaryFilter, - ImportDefinitiveFilter, - ImportRepairFilter, - ExportFilter, - ExportRepairFilter, - AllMovementsFilter, - MovementItem, - MovementItemDetailed -) -from .movement_service import movement_service - -logger = logging.getLogger(__name__) - -router = APIRouter( - tags=["Reports - Movement Invoices"] -) - - -@router.post( - "/temporary", - response_model=List[MovementItem], - summary="Get Temporary Import Movements", - description=""" - Retrieve temporary import movements from legacy database based on filter criteria. - This endpoint corresponds to the 'LLENADOTEMPORAL' (Fill Temporary) logic from the legacy system. - - **Note**: Requires connection to legacy SQL Server database. - """ -) -def get_temporary_import_movements( - filters: ImportTemporaryFilter, - company_id: int = Query(..., description="ID de la empresa"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) -): - """ - Get temporary import movements based on filters. - - Args: - filters: Filter criteria for querying movements - db: Database session - current_user: Authenticated user information - - Returns: - List of movement items matching the criteria - - Raises: - HTTPException: If database query fails or user is unauthorized - """ - try: - logger.info( - f"User {current_user.get('preferred_username', 'unknown')} " - f"requesting temporary import movements" - ) - from core.security import validate_access_to_resource - validate_access_to_resource(db, company_id, current_user, ["report.view"]) - - movements = movement_service.get_temporary_import_movements( - db=db, - filters=filters - ) - logger.info(f"Successfully retrieved {len(movements)} movements") - return movements - except ValueError as e: - logger.warning(f"Validation error fetching movements: {str(e)}") - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail=str(e) - ) - except Exception as e: - logger.error(f"Error fetching movements: {str(e)}", exc_info=True) - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail=f"Error processing import temporary movements: {str(e)}" - ) - - -@router.post( - "/temporary-detailed", - response_model=List[MovementItemDetailed], - summary="Get Detailed Temporary Import Movements", - description=""" - Retrieve detailed temporary import movements (line by line) from legacy database. - This endpoint corresponds to the 'LLENADOTEMPORAL - DETALLADO' logic from the legacy system. - - Each line/partida is returned separately with complete information including: - - Provider and buyer details (name, RFC, Tax ID) - - Customs broker information - - Item descriptions and specifications - - Series information - - All related metadata - - **Note**: Requires connection to legacy SQL Server database. - """ -) -def get_temporary_import_movements_detailed( - filters: ImportTemporaryFilter, - company_id: int = Query(..., description="ID de la empresa"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) -): - """ - Get detailed temporary import movements (line by line) based on filters. - - Args: - filters: Filter criteria for querying movements - db: Database session - current_user: Authenticated user information - - Returns: - List of detailed movement items matching the criteria - - Raises: - HTTPException: If database query fails or user is unauthorized - """ - try: - logger.info( - f"User {current_user.get('preferred_username', 'unknown')} " - f"requesting DETAILED temporary import movements" - ) - from core.security import validate_access_to_resource - validate_access_to_resource(db, company_id, current_user, ["report.view"]) - - movements = movement_service.get_temporary_import_movements_detailed( - db=db, - filters=filters - ) - logger.info(f"Successfully retrieved {len(movements)} detailed movements") - return movements - except ValueError as e: - logger.warning(f"Validation error fetching detailed movements: {str(e)}") - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail=str(e) - ) - except Exception as e: - logger.error(f"Error fetching detailed movements: {str(e)}", exc_info=True) - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail=f"Error processing detailed import temporary movements: {str(e)}" - ) - - -@router.post( - "/definitive", - response_model=List[MovementItem], - summary="Get Definitive Import Movements", - description=""" - Retrieve definitive import movements from legacy database based on filter criteria. - This endpoint corresponds to the 'LLENADODEFINITIVO - NORMAL' logic from the legacy system. - - Definitive imports are aggregated by invoice number and can be filtered by: - - Movement type (COMEX or IMPDF based on ProvImpoDefCR field) - - Date range (invoice date or payment date) - - Provider and buyer - - Pedimento code - - Status (active or including cancelled) - - **Special Features**: - - Supports shelter company logic for exchange rate calculations - - MetTrans# = 1 logic for specific pedimento types (1, 4, 98E) - - Retrieves driver badge information - - Handles rectification pedimento lookups - - **Note**: Requires connection to legacy SQL Server database. - """ -) -def get_definitive_import_movements( - filters: ImportDefinitiveFilter, - company_id: int = Query(..., description="ID de la empresa"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) -): - """ - Get definitive import movements based on filters. - - Args: - filters: Filter criteria for querying movements - db: Database session - current_user: Authenticated user information - - Returns: - List of movement items matching the criteria - - Raises: - HTTPException: If database query fails or user is unauthorized - """ - try: - logger.info( - f"User {current_user.get('preferred_username', 'unknown')} " - f"requesting definitive import movements" - ) - from core.security import validate_access_to_resource - validate_access_to_resource(db, company_id, current_user, ["report.view"]) - - movements = movement_service.get_definitive_import_movements( - db=db, - filters=filters - ) - logger.info(f"Successfully retrieved {len(movements)} definitive movements") - return movements - except Exception as e: - logger.error(f"Error fetching definitive movements: {str(e)}", exc_info=True) - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail=f"Error processing definitive import movements: {str(e)}" - ) - - -@router.post( - "/definitive-detailed", - response_model=List[MovementItemDetailed], - summary="Get Detailed Definitive Import Movements", - description=""" - Retrieve detailed definitive import movements (line by line) from legacy database. - This endpoint corresponds to the 'LLENADODEFINITIVO - DETALLADO' logic from the legacy system. - - Each line/partida is returned separately with complete information including: - - Provider and buyer details (name, RFC, Tax ID) - - Customs broker information - - Item descriptions and specifications - - Series information from QSeriesDef table - - All related metadata - - **Special Logic**: - - Only Partidas (EsSubPartida = 'P') have values calculated - - Subpartidas (EsSubPartida = 'S') return with zero values - - Series formatted as: "1) SERIE123. Modelo: MOD1. Parte: PART1 | 2) SERIE456..." - - Exchange rate calculation supports shelter and non-shelter logic - - MetTrans# = 1 logic for pedimento types 1, 4, 98E - - **Note**: Requires connection to legacy SQL Server database. - """ -) -def get_definitive_import_movements_detailed( - filters: ImportDefinitiveFilter, - company_id: int = Query(..., description="ID de la empresa"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) -): - """ - Get detailed definitive import movements (line by line) based on filters. - - Args: - filters: Filter criteria for querying movements - db: Database session - current_user: Authenticated user information - - Returns: - List of detailed movement items matching the criteria - - Raises: - HTTPException: If database query fails or user is unauthorized - """ - try: - logger.info( - f"User {current_user.get('preferred_username', 'unknown')} " - f"requesting DETAILED definitive import movements" - ) - from core.security import validate_access_to_resource - validate_access_to_resource(db, company_id, current_user, ["report.view"]) - - movements = movement_service.get_definitive_import_movements_detailed( - db=db, - filters=filters - ) - logger.info(f"Successfully retrieved {len(movements)} detailed definitive movements") - return movements - except Exception as e: - logger.error(f"Error fetching detailed definitive movements: {str(e)}", exc_info=True) - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail=f"Error processing detailed definitive import movements: {str(e)}" - ) - - -@router.post( - "/repair", - response_model=List[MovementItem], - summary="Get Repair Import Movements", - description=""" - Retrieve repair import movements from legacy database based on filter criteria. - This endpoint corresponds to the 'LLENADOIMP_REPARACION - NORMAL' logic from the legacy system. - - Repair imports are aggregated by invoice number and can be filtered by: - - Discharge status (SiDes: discharged, NoDes: not discharged, ALL: no filter) - - Date range (invoice date or payment date) - - Provider and buyer - - Pedimento code - - Status (active or including cancelled) - - **Special Features**: - - Excludes regime changes (EsCambioRegimen <> 'S') - - Supports discharge filter (unique to repair imports) - - Exchange rate calculation with shelter/non-shelter logic - - MetTrans# = 1 logic for specific pedimento types (1, 4, 98E) - - Retrieves driver badge information - - **Database Tables**: - - QFacImpRep: Repair import invoices - - QEqiMaqRep: Repair import items/partidas - - QPedimentos: Pedimentos (customs declarations) - - **Note**: Requires connection to legacy SQL Server database. - """ -) -def get_repair_import_movements( - filters: ImportRepairFilter, - company_id: int = Query(..., description="ID de la empresa"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) -): - """ - Get repair import movements based on filters. - - Args: - filters: Filter criteria for querying movements - db: Database session - current_user: Authenticated user information - - Returns: - List of movement items matching the criteria - - Raises: - HTTPException: If database query fails or user is unauthorized - """ - try: - logger.info( - f"User {current_user.get('preferred_username', 'unknown')} " - f"requesting repair import movements" - ) - from core.security import validate_access_to_resource - validate_access_to_resource(db, company_id, current_user, ["report.view"]) - - movements = movement_service.get_repair_import_movements( - db=db, - filters=filters - ) - logger.info(f"Successfully retrieved {len(movements)} repair movements") - return movements - except Exception as e: - logger.error(f"Error fetching repair movements: {str(e)}", exc_info=True) - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail=f"Error processing repair import movements: {str(e)}" - ) - - -@router.post( - "/repair-detailed", - response_model=List[MovementItemDetailed], - summary="Get detailed repair import movements", - description=""" - Retrieve detailed repair import movements (IMPRE) with individual partida lines. - - **LLENADOIMP_REPARACION - DETALLADO** - - Returns individual partida (line item) records for repair imports with full detail including: - - Complete invoice and customs clearance information - - Series, model, and part numbers for each item - - Client/supplier and sold-to information with tax IDs - - Exchange rate calculations (MN/ME) based on filter options - - Customs agent and customs section details - - Driver badge unique number - - All partida-level fields (part number, descriptions, quantities, weights, etc.) - - **Discharge Filter Options:** - - `SiDes`: Only include discharged items (Descarga = 1) - - `NoDes`: Only include non-discharged items (Descarga = 0) - - `ALL`: Include all items regardless of discharge status - - **Database Tables Used:** - - QFacImpRep: Repair import invoices - - QPedimentos: Customs declarations - - QEqiMaqRep: Repair import partidas (line items) - - QSeriesImpoRep: Series information - - GClientesPro: Suppliers - - GCliVendido: Sold-to clients - - GAAduanal: Customs agents - - GAduanaSec: Customs sections - - GConductor: Drivers (for badge numbers) - - GTipoCambio: Exchange rates - """, - tags=["Import Movements - Repair"] -) -async def get_import_repair_movements_detailed( - filters: ImportRepairFilter, - company_id: int = Query(..., description="ID de la empresa"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) -): - """ - Get detailed repair import movements based on filter criteria. - Returns partida-level detail with series information and full client/customs data. - """ - try: - logger.info(f"User {current_user.get('sub')} requesting detailed repair movements") - from core.security import validate_access_to_resource - validate_access_to_resource(db, company_id, current_user, ["report.view"]) - - movements = movement_service.get_repair_import_movements_detailed( - db=db, - filters=filters - ) - logger.info(f"Successfully retrieved {len(movements)} detailed repair partidas") - return movements - except Exception as e: - logger.error(f"Error fetching detailed repair movements: {str(e)}", exc_info=True) - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail=f"Error processing detailed repair import movements: {str(e)}" - ) - - -@router.post( - "/export", - response_model=List[MovementItem], - summary="Get export movements", - description=""" - Retrieve export movements (EXPO DEF) grouped by invoice. - - **LLENADOEXPORTACION - NORMAL** - - Returns aggregated data grouped by invoice number for export movements. - - **Movement Type Options:** - - `AFIJO`: Fixed assets - - `NODES`: No discharge - - `SCRAP`: Scrap materials - - `REEXP`: Re-exports - - `DONAC`: Donations - - `VEMEX`: Sales to Mexico - - `ALL`: All movement types - - **Discharge Filter Options:** - - `SiDes`: Only discharged items (Descarga = 1) - - `NoDes`: Only non-discharged items (Descarga = 0) - - `ALL`: All items regardless of discharge status - - **Database Tables Used:** - - QFacExp: Export invoices - - QEqeMaq: Export partidas (line items) - - QPedimentos: Customs declarations - - QClaAct: Part classifications - - GAAduanal: Customs agents - - GAduanaSec: Customs sections - - GConductor: Drivers - - GTipoCambio: Exchange rates - - Automatically excludes regime changes (EsCambioRegimen = 'N') - """, - tags=["Export Movements"] -) -async def get_export_movements( - filters: ExportFilter, - company_id: int = Query(..., description="ID de la empresa"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) -): - """ - Get export movements based on filter criteria. - Returns aggregated data grouped by invoice. - """ - try: - logger.info(f"User {current_user.get('sub')} requesting export movements") - from core.security import validate_access_to_resource - validate_access_to_resource(db, company_id, current_user, ["report.view"]) - - movements = movement_service.get_export_movements( - db=db, - filters=filters - ) - logger.info(f"Successfully retrieved {len(movements)} export movements") - return movements - except Exception as e: - logger.error(f"Error fetching export movements: {str(e)}", exc_info=True) - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail=f"Error processing export movements: {str(e)}" - ) - - -@router.post( - "/export-detailed", - response_model=List[MovementItemDetailed], - summary="Get detailed export movements", - description=""" - Retrieve detailed export movements with individual partida lines. - - **LLENADOEXPORTACION - DETALLADO** - - Returns individual partida (line item) records for exports with full detail including: - - Complete invoice and customs clearance information - - Series, model, and part numbers for each item - - Client/supplier and buyer information with tax IDs - - Exchange rate calculations (MN/ME) based on filter options - - Customs agent and customs section details - - Driver badge unique number - - All partida-level fields - - **Movement Type Options:** - - `AFIJO`: Fixed assets - - `NODES`: No discharge - - `SCRAP`: Scrap materials - - `REEXP`: Re-exports - - `DONAC`: Donations - - `VEMEX`: Sales to Mexico - - `ALL`: All movement types - - **Discharge Filter Options:** - - `SiDes`: Only discharged items - - `NoDes`: Only non-discharged items - - `ALL`: All items - - **Database Tables Used:** - - QFacExp: Export invoices - - QEqeMaq: Export partidas - - QSeriesExpo: Serial numbers - - QPedimentos: Customs declarations - - GClientesPro: Suppliers - - GCliVendido: Buyers - - GAAduanal: Customs agents - - GAduanaSec: Customs sections - - GConductor: Drivers - - GTipoCambio: Exchange rates - """, - tags=["Export Movements"] -) -async def get_export_movements_detailed( - filters: ExportFilter, - company_id: int = Query(..., description="ID de la empresa"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) -): - """ - Get detailed export movements based on filter criteria. - Returns partida-level detail with series information and full client/customs data. - """ - try: - logger.info(f"User {current_user.get('sub')} requesting detailed export movements") - from core.security import validate_access_to_resource - validate_access_to_resource(db, company_id, current_user, ["report.view"]) - - movements = movement_service.get_export_movements_detailed( - db=db, - filters=filters - ) - logger.info(f"Successfully retrieved {len(movements)} detailed export partidas") - return movements - except Exception as e: - logger.error(f"Error fetching detailed export movements: {str(e)}", exc_info=True) - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail=f"Error processing detailed export movements: {str(e)}" - ) - - -@router.post("/export-repair", response_model=List[MovementItem]) -def get_export_repair_movements( - filters: ExportRepairFilter, - company_id: int = Query(..., description="ID de la empresa"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) -): - """ - **LLENADOEXP_REPARACION - NORMAL** - - Get export repair movements (EXPO REP) based on filter criteria. - Groups results by invoice (FacturaExpo). - - Clarion logic: - - Query from QFacExpRep, QEqeMaqRep tables - - Filters: date range (FF/FP), provider, buyer, pedimento code - - Movement types: AFIJO, NODES - - Discharge filter: SiDes, NoDes, or ALL - - Calculates totals from partidas where EsSubpartida = 'P' - - Exchange rate logic based on currency type and Scaii.ini MetTrans - - Always filters by EsCambioRegimen = 'N' - """ - try: - logger.info(f"User {current_user.get('sub')} requesting export repair movements") - from core.security import validate_access_to_resource - validate_access_to_resource(db, company_id, current_user, ["report.view"]) - - movements = movement_service.get_export_repair_movements( - db=db, - filters=filters - ) - logger.info(f"Successfully retrieved {len(movements)} export repair invoices") - return movements - except Exception as e: - logger.error(f"Error fetching export repair movements: {str(e)}", exc_info=True) - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail=f"Error processing export repair movements: {str(e)}" - ) - - -@router.post("/export-repair-detailed", response_model=List[MovementItemDetailed]) -def get_export_repair_movements_detailed( - filters: ExportRepairFilter, - company_id: int = Query(..., description="ID de la empresa"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) -): - """ - Get detailed export repair movements based on filter criteria. - Returns partida-level detail with series information and full client/customs data. - """ - try: - logger.info(f"User {current_user.get('sub')} requesting detailed export repair movements") - from core.security import validate_access_to_resource - validate_access_to_resource(db, company_id, current_user, ["report.view"]) - - movements = movement_service.get_export_repair_movements_detailed( - db=db, - filters=filters - ) - logger.info(f"Successfully retrieved {len(movements)} detailed export repair partidas") - return movements - except Exception as e: - logger.error(f"Error fetching detailed export repair movements: {str(e)}", exc_info=True) - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail=f"Error processing detailed export repair movements: {str(e)}" - ) - - -@router.post( - "/all", - response_model=Union[List[MovementItemDetailed], List[MovementItem]], - summary="Get All Invoice Movements", - description=""" - Retrieve all invoice movements (imports and exports of all types) from database. - This endpoint combines temporary, definitive, and repair imports with all export types. - - Use this when "TODAS" checkbox is selected to get a comprehensive view of all movements - regardless of their specific type. - - If send_email is True, the report will be sent to the authenticated user's email address. - """ -) -async def get_all_movements( - filters: AllMovementsFilter, - company_id: int = Query(..., description="ID de la empresa"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) -): - """ - Get all invoice movements (all types combined) based on filters. - - Args: - filters: Filter criteria for querying movements - db: Database session - current_user: Authenticated user information - - Returns: - List of all movement items matching the criteria - - Raises: - HTTPException: If database query fails or user is unauthorized - """ - try: - logger.info( - f"User {current_user.get('preferred_username', 'unknown')} " - f"requesting all invoice movements (send_email={filters.send_email})" - ) - from core.security import validate_access_to_resource - validate_access_to_resource(db, company_id, current_user, ["report.view"]) - - movements = movement_service.get_all_movements( - db=db, - filters=filters - ) - logger.info(f"Successfully retrieved {len(movements)} total movements") - - # Send email if requested - if filters.send_email: - user_email = current_user.get('email') - if not user_email: - logger.warning(f"User {current_user.get('sub')} has no email address - skipping email") - else: - try: - from core.email import EmailService - from .csv_utils import generate_csv_from_movements - from datetime import datetime - - # Generate CSV - csv_content = generate_csv_from_movements( - movements=movements, - filters=filters - ) - - # Generate filename - filename = f"reporte_facturas_{filters.start_date}_{filters.end_date}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv" - - # Send email - email_sent = await EmailService.send_report_email( - recipient_email=user_email, - subject=f"Reporte de Facturas - {filters.start_date} al {filters.end_date}", - body_text=f"Se ha generado el reporte de facturas solicitado con {len(movements)} registros.", - csv_content=csv_content, - filename=filename - ) - - if email_sent: - logger.info(f"Report emailed successfully to {user_email}") - else: - logger.warning(f"Failed to send email to {user_email} - SMTP may not be configured correctly") - - except Exception as email_error: - logger.warning(f"Email sending failed: {str(email_error)} - continuing with report generation") - - return movements - except ValueError as e: - logger.warning(f"Validation error fetching all movements: {str(e)}") - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail=str(e) - ) - except HTTPException: - raise - except Exception as e: - logger.error(f"Error fetching all movements: {str(e)}", exc_info=True) - raise HTTPException( - status_code=status.HTTP_500_INTERNAL_SERVER_ERROR, - detail=f"Error processing all movements: {str(e)}" - ) - - -@router.post( - "/generate", - summary="Generate Invoice Report (Async)", - description="Trigger background generation of invoice report." -) -def generate_invoice_report_async( - filters: AllMovementsFilter, - company_id: int = Query(..., description="ID de la empresa"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user) -): - """ - Trigger background generation of invoice report. - Returns task_id to poll status. - """ - from .tasks import generate_invoice_movements_async - from core.security import validate_access_to_resource - - logger.info(f"User {current_user.get('preferred_username', 'unknown')} triggering async report generation") - - # validate_access_to_resource returns the integer tenant_id from DB - tenant_id = validate_access_to_resource(db, company_id, current_user, ["report.process"]) - - # Guardia explícita: tenant_id debe ser un entero positivo antes del dispatch a Celery. - # Un valor inválido aquí generaría un reporte sin filtro de tenant o un crash en la tarea. - if not isinstance(tenant_id, int) or tenant_id <= 0: - raise HTTPException( - status_code=400, - detail="No se pudo determinar el tenant para esta empresa. Verifica que la empresa exista.", - ) - - # Serialize filters to dict for Celery - filter_data = filters.model_dump() - user_email = current_user.get('email') - - task = track_and_dispatch( - db=db, - task=generate_invoice_movements_async, - tenant_id=tenant_id, - company_id=company_id, - requested_by_user=current_user.get("preferred_username") or current_user.get("email") or current_user.get("sub"), - task_name="generate_invoice_movements_async", - task_group="reports", - task_origin="a76/reports/movements/invoices", - args=[filter_data, user_email], - ) - - return {"task_id": task.id} - - -@router.get( - "/task/{task_id}", - summary="Get Async Task Status", - description="Check status of background report generation task." -) -def get_task_status(task_id: str): - """ - Get status of background task. - """ - from celery.result import AsyncResult - from core.celery_app import celery_app - - task_result = AsyncResult(task_id, app=celery_app) - - response = { - "task_id": task_id, - "status": task_result.status, - } - - if task_result.state == 'PROCESSING': - response["meta"] = task_result.info - - if task_result.ready(): - response["result"] = task_result.result - - return response diff --git a/backend/api/v1/modules/a76/reports/movements/invoices/schemas.py b/backend/api/v1/modules/a76/reports/movements/invoices/schemas.py deleted file mode 100644 index 002331cf..00000000 --- a/backend/api/v1/modules/a76/reports/movements/invoices/schemas.py +++ /dev/null @@ -1,609 +0,0 @@ -from typing import Optional -from pydantic import BaseModel, Field -from datetime import datetime, date -from enum import Enum - - -class RangeType(str, Enum): - """Date range type for filtering""" - INVOICE_DATE = "FF" # Filter by invoice date - PAYMENT_DATE = "FP" # Filter by payment date - - -class ReportType(str, Enum): - """Report type""" - NORMAL = "Normal" - DETAILED = "Detallado" - - -class CurrencyType(str, Enum): - """Currency type for calculations""" - FOREIGN = "ME" # Foreign currency (Moneda Extranjera) - LOCAL = "MN" # Local currency (Moneda Nacional) - - -class ExchangeRateType(str, Enum): - """Exchange rate calculation type""" - PAYMENT = "FP" # Use payment date - INVOICE = "FF" # Use invoice date - - -class MovementTypeFilter(str, Enum): - """Movement type filter for definitive imports""" - COMEX = "COMEX" # ProvImpoDefCR = 'P' - IMPDF = "IMPDF" # ProvImpoDefCR != 'P' - ALL = "ALL" # No filter - - -class DischargeFilter(str, Enum): - """Discharge filter for repair imports""" - DISCHARGED = "SiDes" # RepPim.Descarga = 1 - NOT_DISCHARGED = "NoDes" # RepPim.Descarga = 0 - ALL = "ALL" # No filter - - -class ExportMovementType(str, Enum): - """Export movement type filter""" - AFIJO = "AFIJO" # Fixed assets - NODES = "NODES" # No discharge - SCRAP = "SCRAP" # Scrap - REEXP = "REEXP" # Re-export - DONAC = "DONAC" # Donation - VEMEX = "VEMEX" # Sale to Mexico - ALL = "ALL" # All types - - -class AllMovementsFilter(BaseModel): - """Filters for all movements query (all types combined)""" - range_type: RangeType = Field( - default=RangeType.INVOICE_DATE, - description="Date range type: FF for invoice date, FP for payment date" - ) - start_date: str = Field( - ..., - description="Start date in YYYYMMDD format or ISO format" - ) - end_date: str = Field( - ..., - description="End date in YYYYMMDD format or ISO format" - ) - include_cancelled: bool = Field( - default=False, - description="Include cancelled invoices (Estatus != 'AC')" - ) - provider: Optional[str] = Field( - default=None, - description="Filter by provider code" - ) - buyer: Optional[str] = Field( - default=None, - description="Filter by buyer code" - ) - pedimento_code: Optional[str] = Field( - default=None, - description="Filter by pedimento code (ClavePed)" - ) - report_type: ReportType = Field( - default=ReportType.NORMAL, - description="Report type: Normal (grouped by invoice) or Detailed (line by line)" - ) - currency_type: CurrencyType = Field( - default=CurrencyType.FOREIGN, - description="Currency type for value calculations" - ) - exchange_rate_type: ExchangeRateType = Field( - default=ExchangeRateType.PAYMENT, - description="Exchange rate calculation method" - ) - is_shelter: bool = Field( - default=False, - description="Use shelter company logic" - ) - operation_type: Optional[str] = Field( - default=None, - description="Filter by operation type: 'imp' for imports only, 'exp' for exports only, None for all" - ) - send_email: bool = Field( - default=False, - description="Send report via email to current user" - ) - - # Granular movement selection - import_temp: bool = Field(default=True, description="Include temporary imports (IMTEM)") - import_def: bool = Field(default=True, description="Include definitive imports (IMPDF/COMEX)") - import_rep: bool = Field(default=True, description="Include repair imports (IMPRE)") - export_def: bool = Field(default=True, description="Include definitive exports") - export_rep: bool = Field(default=True, description="Include repair exports") - - # Specific filters - export_types: Optional[list[str]] = Field( - default=None, - description="Specific export legacy codes to include (AFIJO, NODES, etc)" - ) - discharge_filter: DischargeFilter = Field( - default=DischargeFilter.ALL, - description="Global discharge filter for repair movements" - ) - - -class ImportTemporaryFilter(BaseModel): - """Filters for temporary import movements query""" - range_type: RangeType = Field( - default=RangeType.INVOICE_DATE, - description="Date range type: FF for invoice date, FP for payment date" - ) - start_date: str = Field( - ..., - description="Start date in YYYYMMDD format or ISO format" - ) - end_date: str = Field( - ..., - description="End date in YYYYMMDD format or ISO format" - ) - include_cancelled: bool = Field( - default=False, - description="Include cancelled invoices (Estatus != 'AC')" - ) - provider: Optional[str] = Field( - default=None, - description="Filter by provider code" - ) - buyer: Optional[str] = Field( - default=None, - description="Filter by buyer code" - ) - pedimento_code: Optional[str] = Field( - default=None, - description="Filter by pedimento code (ClavePed)" - ) - report_type: ReportType = Field( - default=ReportType.NORMAL, - description="Report type: Normal or Detailed" - ) - currency_type: CurrencyType = Field( - default=CurrencyType.FOREIGN, - description="Currency type for value calculations" - ) - exchange_rate_type: ExchangeRateType = Field( - default=ExchangeRateType.PAYMENT, - description="Exchange rate calculation method" - ) - is_shelter: bool = Field( - default=False, - description="Use shelter company logic" - ) - database_name: str = Field( - ..., - description="Legacy database name to query from" - ) - send_email: bool = Field( - default=False, - description="Send report via email to current user" - ) - - -class ImportDefinitiveFilter(BaseModel): - """Filters for definitive import movements query""" - range_type: RangeType = Field( - default=RangeType.INVOICE_DATE, - description="Date range type: FF for invoice date, FP for payment date" - ) - start_date: str = Field( - ..., - description="Start date in YYYYMMDD format or ISO format" - ) - end_date: str = Field( - ..., - description="End date in YYYYMMDD format or ISO format" - ) - include_cancelled: bool = Field( - default=False, - description="Include cancelled invoices (Estatus != 'AC')" - ) - provider: Optional[str] = Field( - default=None, - description="Filter by provider code" - ) - buyer: Optional[str] = Field( - default=None, - description="Filter by buyer code (VendidoA)" - ) - pedimento_code: Optional[str] = Field( - default=None, - description="Filter by pedimento code (ClavePed)" - ) - movement_type: MovementTypeFilter = Field( - default=MovementTypeFilter.ALL, - description="Movement type filter: COMEX, IMPDF, or ALL" - ) - report_type: ReportType = Field( - default=ReportType.NORMAL, - description="Report type: Normal or Detailed" - ) - currency_type: CurrencyType = Field( - default=CurrencyType.FOREIGN, - description="Currency type for value calculations" - ) - exchange_rate_type: ExchangeRateType = Field( - default=ExchangeRateType.PAYMENT, - description="Exchange rate calculation method" - ) - is_shelter: bool = Field( - default=False, - description="Use shelter company logic" - ) - use_transport_method: bool = Field( - default=False, - description="Use MetTrans# = 1 logic for specific pedimento types" - ) - database_name: str = Field( - ..., - description="Legacy database name to query from" - ) - send_email: bool = Field( - default=False, - description="Send report via email to current user" - ) - -class MovementItem(BaseModel): - """Movement item representing a temporary import invoice""" - Factura: Optional[str] = Field(None, description="Invoice number") - Pedimento: Optional[str] = Field(None, description="Pedimento number") - FechaFactura: Optional[datetime] = Field(None, description="Invoice date") - Estatus: Optional[str] = Field(None, description="Status (AC=Active, etc)") - ClavePed: Optional[str] = Field(None, description="Pedimento code") - TipoMovTemDef: Optional[str] = Field(None, description="Movement type (IMTEM=Temporary Import)") - EsCambioRegimen: Optional[str] = Field(None, description="Is regime change (S/N)") - ValorMPTemp: Optional[float] = Field(None, description="Temporary raw material value") - ValorComercialMN: Optional[float] = Field(None, description="Commercial value in MN") - TipoCambio: Optional[float] = Field(None, description="Exchange rate used") - ValorAgre: Optional[float] = Field(default=0.0, description="Aggregate value") - TipoExpo: Optional[str] = Field(default='', description="Export type") - PedimentoR1: Optional[str] = Field(None, description="Rectification pedimento") - EDocument: Optional[str] = Field(None, description="Electronic document") - NumOperacionVU: Optional[str] = Field(None, description="VU operation number") - BaseDeDatos: Optional[str] = Field(None, description="Source database name") - NumGafUni: Optional[str] = Field(None, description="Unique badge number (driver)") - UsuarioCap: Optional[str] = Field(None, description="Capture user") - UsuarioAcr: Optional[str] = Field(None, description="Update user") - Fecha_Pago: Optional[datetime] = Field(None, description="Payment date") - NumCaja: Optional[str] = Field(None, description="Box/Container number") - Pedimento18: Optional[str] = Field(None, description="18-digit pedimento") - AduanaCru: Optional[str] = Field(None, description="Crossing customs") - Lote: Optional[str] = Field(None, description="Lot number") - - model_config = { - "json_schema_extra": { - "example": { - "Factura": "F-2024-001", - "Pedimento": "24 47 3807 8001234", - "FechaFactura": "2024-01-15T00:00:00", - "Estatus": "AC", - "ClavePed": "IM", - "TipoMovTemDef": "IMTEM", - "ValorMPTemp": 10000.50, - "TipoCambio": 17.25 - } - } - } - - -class ImportRepairFilter(BaseModel): - """Filters for repair import movements query""" - range_type: RangeType = Field( - default=RangeType.INVOICE_DATE, - description="Date range type: FF for invoice date, FP for payment date" - ) - start_date: str = Field( - ..., - description="Start date in YYYYMMDD format or ISO format" - ) - end_date: str = Field( - ..., - description="End date in YYYYMMDD format or ISO format" - ) - include_cancelled: bool = Field( - default=False, - description="Include cancelled invoices (Estatus != 'AC')" - ) - provider: Optional[str] = Field( - default=None, - description="Filter by provider code" - ) - buyer: Optional[str] = Field( - default=None, - description="Filter by buyer code (VendidoA)" - ) - pedimento_code: Optional[str] = Field( - default=None, - description="Filter by pedimento code (ClavePed)" - ) - discharge_filter: DischargeFilter = Field( - default=DischargeFilter.ALL, - description="Discharge filter: SiDes (discharged), NoDes (not discharged), or ALL" - ) - report_type: ReportType = Field( - default=ReportType.NORMAL, - description="Report type: Normal or Detailed" - ) - currency_type: CurrencyType = Field( - default=CurrencyType.FOREIGN, - description="Currency type for value calculations" - ) - exchange_rate_type: ExchangeRateType = Field( - default=ExchangeRateType.PAYMENT, - description="Exchange rate calculation method" - ) - is_shelter: bool = Field( - default=False, - description="Use shelter company logic" - ) - use_transport_method: bool = Field( - default=False, - description="Use MetTrans# = 1 logic for specific pedimento types" - ) - database_name: str = Field( - ..., - description="Legacy database name to query from" - ) - send_email: bool = Field( - default=False, - description="Send report via email to current user" - ) - - -class MovementItemDetailed(BaseModel): - """Detailed movement item with all line-level information""" - Linea: Optional[int] = Field(None, description="Line number") - Factura: Optional[str] = Field(None, description="Invoice number") - Pedimento: Optional[str] = Field(None, description="Pedimento number") - FechaFactura: Optional[datetime] = Field(None, description="Invoice date") - Estatus: Optional[str] = Field(None, description="Status (AC=Active, etc)") - ClavePed: Optional[str] = Field(None, description="Pedimento code") - TipoMovTemDef: Optional[str] = Field(None, description="Movement type") - EsCambioRegimen: Optional[str] = Field(None, description="Is regime change (S/N)") - Regimen: Optional[str] = Field(None, description="Regime") - Fecha_Inicio: Optional[datetime] = Field(None, description="Start date") - Fecha_Fin: Optional[datetime] = Field(None, description="End date") - Fecha_Pago: Optional[datetime] = Field(None, description="Payment date") - Remesa: Optional[str] = Field(None, description="Remesa") - - # Provider information - Proveedor: Optional[str] = Field(None, description="Provider name") - RFCProveedor: Optional[str] = Field(None, description="Provider RFC") - ProveedorTaxID: Optional[str] = Field(None, description="Provider Tax ID") - - # Buyer information - VendidoA: Optional[str] = Field(None, description="Buyer name") - VendidoARFC: Optional[str] = Field(None, description="Buyer RFC") - VendidoATaxID: Optional[str] = Field(None, description="Buyer Tax ID") - - # Customs broker - AgenteAduanal: Optional[str] = Field(None, description="Customs broker name") - Patente: Optional[str] = Field(None, description="Customs broker patent") - - # Item details - NumParte: Optional[str] = Field(None, description="Part number") - DescripcionE: Optional[str] = Field(None, description="Spanish description") - DescripcionI: Optional[str] = Field(None, description="English description") - CantidadIE: Optional[float] = Field(None, description="Quantity") - UniMed: Optional[str] = Field(None, description="Unit of measure") - ValorComercialMN: Optional[float] = Field(None, description="Commercial value in MN") - TipoCambio: Optional[float] = Field(None, description="Exchange rate") - PesoNeto: Optional[float] = Field(None, description="Net weight") - PesoBruto: Optional[float] = Field(None, description="Gross weight") - - # Additional fields - OrdenCompraVenta: Optional[str] = Field(None, description="Purchase order") - FraccionArancelaria: Optional[str] = Field(None, description="Tariff fraction") - Preferencia: Optional[str] = Field(None, description="Preference") - Sector: Optional[str] = Field(None, description="Sector") - PaisOrigen: Optional[str] = Field(None, description="Country of origin") - Aduana: Optional[str] = Field(None, description="Customs office") - Advalorem: Optional[str] = Field(None, description="Ad valorem") - TipoExpo: Optional[str] = Field(default='', description="Export type") - PedimentoR1: Optional[str] = Field(None, description="Rectification pedimento") - EDocument: Optional[str] = Field(None, description="Electronic document") - NumOperacionVU: Optional[str] = Field(None, description="VU operation number") - - # Series information - Series: Optional[str] = Field(None, description="Serial numbers") - Marca: Optional[str] = Field(None, description="Brand") - Modelo: Optional[str] = Field(None, description="Model") - FraccionAmericana: Optional[str] = Field(None, description="American tariff fraction") - ECCN: Optional[str] = Field(None, description="ECCN code") - SimboloEx: Optional[str] = Field(None, description="Export symbol/license") - FechaEmision: Optional[datetime] = Field(None, description="Emission date") - - # Metadata - BaseDeDatos: Optional[str] = Field(None, description="Source database") - NumGafUni: Optional[str] = Field(None, description="Unique badge number") - UsuarioCap: Optional[str] = Field(None, description="Capture user") - UsuarioAcr: Optional[str] = Field(None, description="Update user") - Transportista: Optional[str] = Field(None, description="Transporter") - NumCaja: Optional[str] = Field(None, description="Box number") - Pedimento18: Optional[str] = Field(None, description="18-digit pedimento") - AduanaCru: Optional[str] = Field(None, description="Crossing customs") - Lote: Optional[str] = Field(None, description="Lot number") - - model_config = { - "json_schema_extra": { - "example": { - "Linea": 1 - } - } - } - - -class ExportFilter(BaseModel): - """Filters for export movements query""" - range_type: RangeType = Field( - default=RangeType.INVOICE_DATE, - description="Date range type: FF for invoice date, FP for payment date" - ) - start_date: str = Field( - ..., - description="Start date in YYYYMMDD format or ISO format" - ) - end_date: str = Field( - ..., - description="End date in YYYYMMDD format or ISO format" - ) - include_cancelled: bool = Field( - default=False, - description="Include cancelled invoices (Estatus = 'NA')" - ) - provider: Optional[str] = Field( - None, - description="Filter by provider code" - ) - buyer: Optional[str] = Field( - None, - description="Filter by buyer code (VendidoA)" - ) - pedimento_code: Optional[str] = Field( - None, - description="Filter by pedimento code (ClavePed)" - ) - movement_type: ExportMovementType = Field( - default=ExportMovementType.ALL, - description="Filter by export movement type (AFIJO, NODES, SCRAP, REEXP, DONAC, VEMEX)" - ) - discharge_filter: DischargeFilter = Field( - default=DischargeFilter.ALL, - description="Filter by discharge status: SiDes, NoDes, or ALL" - ) - report_type: ReportType = Field( - default=ReportType.NORMAL, - description="Normal (grouped by invoice) or Detallado (line by line)" - ) - currency_type: CurrencyType = Field( - default=CurrencyType.FOREIGN, - description="Currency type: ME (foreign) or MN (local)" - ) - exchange_rate_type: ExchangeRateType = Field( - default=ExchangeRateType.PAYMENT, - description="Exchange rate type: FP (payment date) or FF (invoice date)" - ) - is_shelter: bool = Field( - default=False, - description="Shelter company flag" - ) - use_transport_method: bool = Field( - default=False, - description="Use transport method for exchange rate logic" - ) - database_name: str = Field( - ..., - description="Legacy database name" - ) - send_email: bool = Field( - default=False, - description="Send report via email to current user" - ) - - model_config = { - "json_schema_extra": { - "example": { - "range_type": "FF", - "start_date": "20240101", - "end_date": "20240131", - "include_cancelled": False, - "provider": None, - "buyer": None, - "pedimento_code": None, - "movement_type": "ALL", - "discharge_filter": "ALL", - "report_type": "Normal", - "currency_type": "ME", - "exchange_rate_type": "FP", - "is_shelter": False, - "use_transport_method": False, - "database_name": "MYDB" - } - } - } - -class ExportRepairFilter(BaseModel): - """Filters for export repair movements query (EXPO REP)""" - range_type: RangeType = Field( - default=RangeType.INVOICE_DATE, - description="Date range type: FF for invoice date, FP for payment date" - ) - start_date: str = Field( - ..., - description="Start date in YYYYMMDD format or ISO format" - ) - end_date: str = Field( - ..., - description="End date in YYYYMMDD format or ISO format" - ) - include_cancelled: bool = Field( - default=False, - description="Include cancelled invoices (Estatus = 'NA')" - ) - provider: Optional[str] = Field( - None, - description="Filter by provider code" - ) - buyer: Optional[str] = Field( - None, - description="Filter by buyer code (VendidoA)" - ) - pedimento_code: Optional[str] = Field( - None, - description="Filter by pedimento code (ClavePed)" - ) - movement_type: ExportMovementType = Field( - default=ExportMovementType.ALL, - description="Filter by movement type (AFIJO, NODES for repair exports)" - ) - discharge_filter: DischargeFilter = Field( - default=DischargeFilter.ALL, - description="Filter by discharge status: SiDes, NoDes, or ALL" - ) - report_type: ReportType = Field( - default=ReportType.NORMAL, - description="Normal (grouped by invoice) or Detallado (line by line)" - ) - currency_type: CurrencyType = Field( - default=CurrencyType.FOREIGN, - description="Currency type: ME (foreign) or MN (local)" - ) - exchange_rate_type: ExchangeRateType = Field( - default=ExchangeRateType.PAYMENT, - description="Exchange rate type: FP (payment date) or FF (invoice date)" - ) - is_shelter: bool = Field( - default=False, - description="Shelter company flag" - ) - database_name: str = Field( - ..., - description="Legacy database name" - ) - send_email: bool = Field( - default=False, - description="Send report via email to current user" - ) - - model_config = { - "json_schema_extra": { - "example": { - "range_type": "FF", - "start_date": "20240101", - "end_date": "20240131", - "include_cancelled": False, - "provider": None, - "buyer": None, - "pedimento_code": None, - "movement_type": "ALL", - "discharge_filter": "ALL", - "report_type": "Normal", - "currency_type": "ME", - "exchange_rate_type": "FP", - "is_shelter": False, - "database_name": "MYDB" - } - } - } \ No newline at end of file diff --git a/backend/api/v1/modules/a76/reports/movements/invoices/services.py b/backend/api/v1/modules/a76/reports/movements/invoices/services.py deleted file mode 100644 index f218bd71..00000000 --- a/backend/api/v1/modules/a76/reports/movements/invoices/services.py +++ /dev/null @@ -1,112 +0,0 @@ -""" -Unified service for invoice movement operations. -This service delegates to specialized handlers for each import type. -""" - -import logging -from sqlalchemy.orm import Session -from typing import List - -from .schemas import ( - ImportTemporaryFilter, - ImportDefinitiveFilter, - ImportRepairFilter, - ExportFilter, - MovementItem, - MovementItemDetailed -) -from .services.temporary import TemporaryImportService -from .services.definitive import DefinitiveImportService -from .services.repair import RepairImportService -from .services.export import ExportService - -logger = logging.getLogger(__name__) - - -class MovementService: - """ - Unified service for handling all types of movements. - Delegates to specialized services for each movement type. - """ - - def __init__(self): - self.temporary_service = TemporaryImportService() - self.definitive_service = DefinitiveImportService() - self.repair_service = RepairImportService() - self.export_service = ExportService() - - # ===== TEMPORARY IMPORTS ===== - - def get_temporary_import_movements( - self, - db: Session, - filters: ImportTemporaryFilter - ) -> List[MovementItem]: - """Get temporary import movements (normal mode - grouped by invoice).""" - return self.temporary_service.get_movements(db, filters) - - def get_temporary_import_movements_detailed( - self, - db: Session, - filters: ImportTemporaryFilter - ) -> List[MovementItemDetailed]: - """Get temporary import movements (detailed mode - line by line).""" - return self.temporary_service.get_movements_detailed(db, filters) - - # ===== DEFINITIVE IMPORTS ===== - - def get_definitive_import_movements( - self, - db: Session, - filters: ImportDefinitiveFilter - ) -> List[MovementItem]: - """Get definitive import movements (normal mode - grouped by invoice).""" - return self.definitive_service.get_movements(db, filters) - - def get_definitive_import_movements_detailed( - self, - db: Session, - filters: ImportDefinitiveFilter - ) -> List[MovementItemDetailed]: - """Get definitive import movements (detailed mode - line by line).""" - return self.definitive_service.get_movements_detailed(db, filters) - - # ===== REPAIR IMPORTS ===== - - def get_repair_import_movements( - self, - db: Session, - filters: ImportRepairFilter - ) -> List[MovementItem]: - """Get repair import movements (normal mode - grouped by invoice).""" - return self.repair_service.get_movements(db, filters) - - def get_repair_import_movements_detailed( - self, - db: Session, - filters: ImportRepairFilter - ) -> List[MovementItemDetailed]: - """Get repair import movements (detailed mode - line by line).""" - return self.repair_service.get_movements_detailed(db, filters) - - # ===== EXPORTS ===== - - def get_export_movements( - self, - db: Session, - filters: ExportFilter - ) -> List[MovementItem]: - """Get export movements (normal mode - grouped by invoice).""" - return self.export_service.get_movements(db, filters) - - def get_export_movements_detailed( - self, - db: Session, - filters: ExportFilter - ) -> List[MovementItemDetailed]: - """Get export movements (detailed mode - line by line).""" - return self.export_service.get_movements_detailed(db, filters) - - -# Singleton instance -movement_service = MovementService() diff --git a/backend/api/v1/modules/a76/reports/movements/invoices/services/__init__.py b/backend/api/v1/modules/a76/reports/movements/invoices/services/__init__.py deleted file mode 100644 index fdde7fed..00000000 --- a/backend/api/v1/modules/a76/reports/movements/invoices/services/__init__.py +++ /dev/null @@ -1,26 +0,0 @@ -""" -Invoice Movement Services Module - -This package contains the business logic for handling different types of movements: -- Temporary imports (IMTEM) -- Definitive imports (COMEX/IMPDF) -- Repair imports (IMPRE) -- Exports (EXPO DEF) -- Export repairs (EXPO REP) - -The services are organized into specialized modules for better maintainability. -""" - -from .temporary import TemporaryImportService -from .definitive import DefinitiveImportService -from .repair import RepairImportService -from .export import ExportService -from .export_repair import ExportRepairService - -__all__ = [ - 'TemporaryImportService', - 'DefinitiveImportService', - 'RepairImportService', - 'ExportService', - 'ExportRepairService', -] diff --git a/backend/api/v1/modules/a76/reports/movements/invoices/services/base.py b/backend/api/v1/modules/a76/reports/movements/invoices/services/base.py deleted file mode 100644 index e04262a8..00000000 --- a/backend/api/v1/modules/a76/reports/movements/invoices/services/base.py +++ /dev/null @@ -1,85 +0,0 @@ -""" -Base utilities and configuration helpers for invoice movement services. -""" - -import logging -import configparser -from typing import Optional - -logger = logging.getLogger(__name__) - - -class ConfigHelper: - """Helper for reading configuration files.""" - - @staticmethod - def get_met_trans_config() -> int: - """ - Read MetTrans configuration from Scaii.ini file. - - Returns: - MetTrans value (0 or 1) - """ - try: - config = configparser.ConfigParser() - config.read('Scaii.ini') - met_trans = config.getint('METTRANS', 'TipoCambio', fallback=0) - logger.debug(f"INI met_trans value: {met_trans}") - return met_trans - except Exception as e: - logger.warning(f"Could not read Scaii.ini, using default met_trans=0: {e}") - return 0 - - -class StringHelper: - """Helper for string manipulation.""" - - @staticmethod - def remove_commas(text: Optional[str]) -> Optional[str]: - """Remove commas from text for CSV compatibility.""" - if not text: - return text - return text.replace(',', '') - - @staticmethod - def clean_text(text: Optional[str]) -> Optional[str]: - """Clean text by stripping whitespace and removing special characters.""" - if not text: - return None - # Remove special characters and extra whitespace - cleaned = text.strip() - return cleaned if cleaned else None - - -class DateHelper: - """Helper for date-related operations.""" - - @staticmethod - def get_fecha_tipo_cambio( - fecha_pago, - fecha_inicio, - tipo_pedimento: str, - use_transport_method: bool, - met_trans: int - ): - """ - Determine which date to use for exchange rate lookup based on MetTrans logic. - - Args: - fecha_pago: Payment date - fecha_inicio: Start/entry date - tipo_pedimento: Pedimento type code - use_transport_method: Whether to apply transport method logic - met_trans: MetTrans configuration value - - Returns: - Date to use for exchange rate lookup - """ - fecha = fecha_pago - - # MetTrans# = 1 logic: use fecha_inicio for specific pedimento types - if use_transport_method and met_trans == 1: - if tipo_pedimento in ('1', '4', '98E'): - fecha = fecha_inicio - - return fecha diff --git a/backend/api/v1/modules/a76/reports/movements/invoices/services/database_helpers.py b/backend/api/v1/modules/a76/reports/movements/invoices/services/database_helpers.py deleted file mode 100644 index bdfd3039..00000000 --- a/backend/api/v1/modules/a76/reports/movements/invoices/services/database_helpers.py +++ /dev/null @@ -1,522 +0,0 @@ -""" -Database query helpers for invoice movements. -""" - -import logging -from sqlalchemy import text -from sqlalchemy.orm import Session -from typing import Optional, Dict - -logger = logging.getLogger(__name__) - - -class DatabaseHelper: - """Helper for common database operations.""" - - @staticmethod - def get_database_name(db: Session) -> Optional[str]: - """ - Get the current database name from the session. - - Returns: - Database name or None if not found - """ - try: - result = db.execute(text("SELECT current_database()")).fetchone() - return result[0] if result else None - except Exception as e: - logger.error(f"Error getting database name: {e}") - return None - - @staticmethod - def get_exchange_rate( - db: Session, - db_name: str, - fecha, - is_shelter: bool = False, - raise_on_missing: bool = False, - pedimento_number: Optional[str] = None - ) -> Optional[float]: - """ - Get exchange rate for the given date from exchange_rate table. - - Args: - db: Database session - db_name: Legacy database name (kept for compatibility, not used) - fecha: Date for exchange rate lookup - is_shelter: Shelter company flag (when True and rate not found, raises detailed error) - raise_on_missing: If True, raises ValueError when rate not found - pedimento_number: Pedimento number for error messages - - Returns: - Exchange rate as float, or None if not found - - Raises: - ValueError: When is_shelter=True and exchange rate not found - """ - if not fecha: - return None - - try: - # TODO: Verify exchange_rate table structure and column names - sql_tc = text(""" - SELECT rate - FROM a76.exchange_rate - WHERE rate_date = :fecha - ORDER BY rate_date DESC - LIMIT 1 - """) - res = db.execute(sql_tc, {"fecha": fecha}).fetchone() - if res and res[0]: - return float(res[0]) - else: - # Clarion logic: For Shelter operations with FP, missing exchange rate is an error - if is_shelter and raise_on_missing: - fecha_str = fecha.strftime('%d/%m/%Y') if hasattr(fecha, 'strftime') else str(fecha) - ped_info = f" del Pedimento: {pedimento_number}" if pedimento_number else "" - raise ValueError( - f"Falta el tipo de cambio del día {fecha_str}. " - f"Por favor regístralo en el catálogo de Tipos de Cambio." - ) - logger.warning(f"Exchange rate not found for date {fecha}") - return None - except ValueError: - raise # Re-raise validation errors - except Exception as e: - logger.error(f"Error fetching exchange rate for date {fecha}: {e}") - return None - - @staticmethod - def get_client_info( - db: Session, - db_name: str, - client_code: str, - is_supplier: bool = True - ) -> Dict[str, Optional[str]]: - """ - Get client or supplier information (name, RFC, TaxID). - - Args: - db: Database session - db_name: Database name (kept for compatibility, not used) - client_code: Client/supplier code - is_supplier: True for suppliers, False for clients - - Returns: - Dict with 'name', 'rfc', 'tax_id' keys - """ - if not client_code: - return {"name": None, "rfc": None, "tax_id": None} - - # In our schema, enum values are lowercase: 'client', 'provider', 'both' - client_type = 'provider' if is_supplier else 'client' - - try: - sql = text(""" - SELECT cp.name, cp.rfc, cp.tax_id - FROM a76.clients_and_providers cp - WHERE cp.id = :client_code - AND (cp.client_or_provider = :client_type OR cp.client_or_provider = 'both') - """) - result = db.execute(sql, {"client_code": client_code, "client_type": client_type}).fetchone() - - if result: - # Identificadores separados: rfc (nacional) y tax_id (extranjero), cada uno en su columna - return { - "name": result[0], - "rfc": result[1], - "tax_id": result[2], - } - else: - logger.debug(f"Client {client_code} not found as {client_type}") - return {"name": None, "rfc": None, "tax_id": None} - except Exception as e: - logger.error(f"Error fetching client info for {client_code}: {e}") - raise - - @staticmethod - def get_customs_agent_info( - db: Session, - db_name: str, - agent_code: str - ) -> Dict[str, Optional[str]]: - """ - Get customs agent information (name, license). - - Args: - db: Database session - db_name: Database name (kept for compatibility, not used) - agent_code: Customs agent code - - Returns: - Dict with 'name', 'license' keys - """ - if not agent_code: - return {"name": None, "license": None} - - try: - sql = text(""" - SELECT name, license - FROM a76.customs_brokers - WHERE id = :agent_code - LIMIT 1 - """) - result = db.execute(sql, {"agent_code": agent_code}).fetchone() - - if result: - return { - "name": result[0], - "license": result[1] - } - else: - logger.debug(f"Customs agent {agent_code} not found") - return {"name": None, "license": None} - except Exception as e: - logger.error(f"Error fetching customs agent info for {agent_code}: {e}") - raise - - @staticmethod - def get_aduana_seccion_nombre( - db: Session, - db_name: str, - aduana_seccion: str - ) -> Optional[str]: - """ - Get customs section name. - - Args: - db: Database session - db_name: Database name - aduana_seccion: Customs section code - - Returns: - Customs section name or None - """ - if not aduana_seccion: - return None - - try: - query = text(""" - SELECT section_name - FROM public.customs_sections - WHERE customs_code = :code - """) - result = db.execute(query, {"code": aduana_seccion}).fetchone() - return result[0] if result else None - except Exception as e: - logger.error(f"Error fetching customs section name: {e}") - raise - - @staticmethod - def get_series_info( - db: Session, - db_name: str, - invoice_id: int, - linea: str, - is_shelter: bool - ) -> Optional[str]: - """ - Get series information for import items. - - Args: - db: Database session - db_name: Legacy database name (not used in PostgreSQL) - invoice_id: Invoice header ID - linea: Line number - is_shelter: Shelter flag (not used) - - Returns: - Formatted series string or None - """ - if not invoice_id or not linea: - return None - - try: - query = text(""" - SELECT serial_numbers, model, brand - FROM a76.item_line_series ils - INNER JOIN a76.item_lines il ON ils.line_item_id = il.id - WHERE il.invoice_id = :invoice_id - AND il.line_number = :linea - ORDER BY ils.id - LIMIT 1 - """) - result = db.execute(query, { - "invoice_id": invoice_id, - "linea": linea - }).fetchone() - - if result: - serial_numbers, model, brand = result - parts = [] - if serial_numbers: - parts.append(serial_numbers) - if model: - parts.append(model) - if brand: - parts.append(brand) - return " / ".join(parts) if parts else None - return None - except Exception as e: - logger.error(f"Error fetching series info: {e}") - raise - - @staticmethod - def get_series_info_export( - db: Session, - db_name: str, - invoice_id: int, - linea: str, - is_shelter: bool - ) -> Optional[str]: - """ - Get series information for export items. - - Args: - db: Database session - db_name: Legacy database name - invoice_id: Invoice header ID - linea: LineaExpo value - is_shelter: Shelter flag - - Returns: - Formatted series string or None - """ - if not invoice_id or not linea: - return None - - try: - # Note: Postgres items table calls it expo_brad (typo in DB schema) - # ItemLineSeries FK is line_item_id, not item_line_id - query = text(""" - SELECT serial_numbers, model, expo_brad - FROM a76.item_line_series ils - INNER JOIN a76.item_lines il ON ils.line_item_id = il.id - WHERE il.invoice_id = :invoice_id - AND il.line_number = :linea - ORDER BY ils.id - LIMIT 1 - """) - result = db.execute(query, { - "invoice_id": invoice_id, - "linea": linea - }).fetchone() - - if result: - serial_numbers, model, expo_brand = result - parts = [] - if serial_numbers: - parts.append(serial_numbers) - if model: - parts.append(model) - if expo_brand: - parts.append(expo_brand) - return " | ".join(parts) if parts else None - return None - except Exception as e: - logger.error(f"Error fetching export series info: {e}") - raise - - @staticmethod - def get_rectification_pedimento( - db: Session, - pedimento: str, - ped_rectifica: Optional[str], - is_shelter: bool = False - ) -> Optional[str]: - """ - Get final pedimento rectification number following the chain recursively. - - Clarion logic: - - IF Loc:OpcionShelter = 1 THEN: use direct field value (PedRectifica) - - ELSE: call BuscarRectificacion() - follows rectification chain recursively - - BuscarRectificacion follows the chain: - Example: A1 -> A2 -> A3 -> A4 (returns A4, the final rectification) - - Args: - db: Database session - pedimento: Original pedimento number - ped_rectifica: Initial rectification pedimento from database field - (already resolved from pedimento_rectification_origin JOIN in query) - is_shelter: Shelter company flag - - Returns: - Final rectification pedimento number in the chain, or None/empty if no rectification - """ - if is_shelter: - # Shelter: use direct value from PedRectifica field - result = ped_rectifica - else: - # Non-Shelter: implement BuscarRectificacion logic - result = DatabaseHelper._buscar_rectificacion(db, pedimento, ped_rectifica) - return result - - @staticmethod - def _buscar_rectificacion( - db: Session, - pedimento_orig: str, - ped_rec: Optional[str] - ) -> Optional[str]: - """ - BUSCA ULTIMO PEDIMENTO DE RECTIFICACION - Returns the rectification origin pedimento string already resolved by the query - builder's JOIN on pedimento_rectification_origin. - - Args: - db: Database session - pedimento_orig: Original pedimento number (e.g. "1234567") - ped_rec: Rectification pedimento origin string already computed by the SQL JOIN - (e.g. "25-470-8000-1234567") - - Returns: - The rectification origin string, or empty string if none. - """ - if not ped_rec: - return '' - - # The ped_rec value already comes from the JOIN on pedimento_rectification_origin - # in the query builder, so it is the directly stored origin pedimento. - # Return it directly without any further recursive DB lookup. - return ped_rec - - @staticmethod - def _busca_pedimento_r1( - db: Session, - pedimento: str, - visited: set - ) -> Optional[str]: - """ - BUSCA_PEDIMENTO_R1 ROUTINE - Recursive search for final rectification pedimento - using pedimento_rectification_origin table. - - Args: - db: Database session - pedimento: Current pedimento number to check - visited: Set of already visited pedimentos (prevents infinite loops) - - Returns: - Final pedimento in chain, or None if circular reference detected - """ - if pedimento in visited: - # Circular reference detected (ERRORCODE = 30 equivalent) - logger.warning(f"Circular reference detected in rectification chain: {pedimento}") - return None - - try: - # Query pedimento_rectification_origin for the next pedimento in the chain. - # NOTE: The a76.pedimentos table does NOT have a ped_rectifica column. - # Rectification data lives in pedimento_rectification_origin. - sql = text(""" - SELECT - pro.original_pedimento_year || '-' || pro.original_customs_office || - '-' || pro.original_license || '-' || pro.original_pedimento_number AS ped_origen - FROM a76.pedimento_rectification_origin pro - INNER JOIN a76.pedimentos ped ON ped.id = pro.pedimento_id - WHERE ped.pedimento_number = :pedimento - AND pro.deleted_at IS NULL - LIMIT 1 - """) - result = db.execute(sql, {"pedimento": pedimento}).fetchone() - - if result and result[0] and result[0].replace('-', '').strip(): - ped_rectifica_next = result[0] - - # Add current pedimento to visited set - visited.add(pedimento) - - # Recurse with next rectification origin - final_ped = DatabaseHelper._busca_pedimento_r1( - db, ped_rectifica_next, visited - ) - - return final_ped if final_ped else pedimento - else: - # No more rectifications, this is the final pedimento - return pedimento - - except Exception as e: - logger.error(f"Error fetching rectification origin for pedimento {pedimento}: {e}") - return None - - @staticmethod - def get_driver_badge( - db: Session, - db_name: str, - factura: str - ) -> Optional[str]: - """ - Get driver unique badge number (NUMGAFETEUNICO) for invoice. - - Clarion query: - SELECT NUMGAFETEUNICO FROM GConductor - LEFT JOIN QFacImp ON QFacImp.CONDUCTOR = GConductor.CONDUCTOR - WHERE FacturaImpo = '' - - Modern schema: - - invoice_header has invoice_number - - invoice_logistics links to invoice via invoice_id and has driver_name - - driver table has unique_badge_number and driver_name - - Args: - db: Database session - db_name: Database name (not used in modern schema) - factura: Invoice number - - Returns: - Driver unique badge number or None - """ - if not factura: - return None - - try: - # Join invoice_header -> invoice_logistics -> driver via driver_name - query = text(""" - SELECT d.unique_badge_number - FROM a76.invoice_header ih - LEFT JOIN a76.invoice_logistics log ON log.invoice_id = ih.id - LEFT JOIN a76.driver d ON d.driver_name = log.driver_name - WHERE ih.invoice_number = :factura - AND d.unique_badge_number IS NOT NULL - LIMIT 1 - """) - result = db.execute(query, {"factura": factura}).fetchone() - return result[0] if result else None - except Exception as e: - logger.error(f"Error fetching driver badge for invoice {factura}: {e}") - raise - - @staticmethod - def get_part_export_symbol( - db: Session, - db_name: str, - num_parte: str, - is_shelter: bool - ) -> Optional[str]: - """ - Get export symbol/license for a part number. - - Args: - db: Database session - db_name: Database name (not used in PostgreSQL, kept for compatibility) - num_parte: Part number - is_shelter: Shelter flag (not used, kept for compatibility) - - Returns: - Export symbol/license or None - """ - if not num_parte: - return None - - try: - query = text(""" - SELECT exclusion_symbol - FROM a76.parts - WHERE part_number = :num_parte - LIMIT 1 - """) - result = db.execute(query, {"num_parte": num_parte}).fetchone() - return result[0] if result else None - except Exception as e: - logger.error(f"Error fetching export symbol for part {num_parte}: {e}") - raise diff --git a/backend/api/v1/modules/a76/reports/movements/invoices/services/definitive.py b/backend/api/v1/modules/a76/reports/movements/invoices/services/definitive.py deleted file mode 100644 index f0897df1..00000000 --- a/backend/api/v1/modules/a76/reports/movements/invoices/services/definitive.py +++ /dev/null @@ -1,383 +0,0 @@ -""" -Definitive import service - handles COMEX/IMPDF movements. -""" - -import logging -from datetime import datetime -from sqlalchemy import text -from sqlalchemy.orm import Session -from typing import List, Optional, TYPE_CHECKING - -if TYPE_CHECKING: - from ..schemas import ImportDefinitiveFilter, MovementItem, MovementItemDetailed - -from .base import ConfigHelper, StringHelper -from .database_helpers import DatabaseHelper -from .exchange_rate import ExchangeRateCalculator -from .query_builders import DefinitiveImportQueries - -logger = logging.getLogger(__name__) - - -def parse_yyyymmdd_date(date_str: str) -> Optional[datetime]: - """Parse date string in YYYYMMDD format to datetime.""" - if not date_str or date_str == '': - return None - try: - return datetime.strptime(date_str, '%Y%m%d') - except (ValueError, TypeError): - return None - - -class DefinitiveImportService: - """Service for handling definitive import movements (COMEX/IMPDF).""" - - def get_movements( - self, - db: Session, - filters: "ImportDefinitiveFilter" - ) -> List["MovementItem"]: - """ - Get definitive import movements (normal mode - grouped by invoice). - - Args: - db: Database session - filters: Filter criteria - - Returns: - List of movement items grouped by invoice - """ - from ..schemas import MovementItem - - try: - logger.info(f"Fetching definitive import movements with filters: {filters.model_dump()}") - - # Get MetTrans configuration - met_trans = ConfigHelper.get_met_trans_config() - - # Build WHERE clause - where_clause = self._build_where_clause(filters) - - # Execute optimized aggregated query for NORMAL mode - sql = text(DefinitiveImportQueries.build_aggregated_query(filters.database_name, where_clause)) - results = db.execute(sql).fetchall() - logger.info(f"Found {len(results)} definitive import invoices") - - movements = [] - - for row in results: - factura = row[0] # C1 - FacturaImpoDef - estatus = row[3] # C4 - Estatus (AC o NA) - - # Filtrar facturas según include_cancelled - # Si include_cancelled=False, solo mostrar AC (status=true) - # Si include_cancelled=True, mostrar todas (AC y NA) - if not filters.include_cancelled and estatus != 'AC': - continue - - invoice_id = row[16] # C35 - invoice ID - - # Helper to safely convert to float - def to_float(val): - if val is None or val == '': - return 0.0 - try: - return float(val) - except (ValueError, TypeError): - return 0.0 - - # Totals come directly from GROUP BY query (no N+1 problem) - total_me = to_float(row[28]) # total_me from SUM aggregation - total_mn = to_float(row[29]) # total_mn from SUM aggregation - sum_value_usd = to_float(row[30]) - sum_value_mxn = to_float(row[31]) - - total_me = sum_value_usd if sum_value_usd > 0 else total_me - total_mn = sum_value_mxn if sum_value_mxn > 0 else total_mn - - # Calculate exchange rate and value - valor_comercial, tipo_cambio = ExchangeRateCalculator.calculate_for_aggregated( - db=db, - db_name=filters.database_name, - valor_me=total_me, - valor_mn=total_mn, - tipo_cambio_db=to_float(row[20]), # C51 - TipoCambio - fecha_pago=row[8], # C13 - Fecha_Pago - fecha_inicio=row[6], # C11 - Fecha_Inicio - tipo_pedimento=row[4], # C5 - ClavePed (Fix: using C5 instead of empty C59) - currency_type=filters.currency_type.value, - exchange_rate_type=filters.exchange_rate_type.value, - is_shelter=filters.is_shelter, - use_transport_method=False, - met_trans=met_trans - ) - - # Get pedimento rectification - pedimento_r1 = DatabaseHelper.get_rectification_pedimento( - db, - row[1], # C2 - PedimentoImpoDef - row[17], # C42 - PedRectifica - filters.is_shelter - ) - - # Get driver badge - num_gaf_uni = DatabaseHelper.get_driver_badge( - db, filters.database_name, factura - ) - - # Build movement item - movement = MovementItem( - Factura=factura, - Pedimento=row[1], # C2 - PedimentoImpoDef - FechaFactura=parse_yyyymmdd_date(row[2]), # C3 - FechaFactura - Estatus=row[3], # C4 - Estatus - ClavePed=row[4], # C5 - ClavePed - TipoMovTemDef='IMPDF', - EsCambioRegimen='N', - ValorMPTemp=valor_comercial, - ValorComercialMN=valor_comercial, - TipoCambio=tipo_cambio, - ValorAgre=0.0, - TipoExpo='', - PedimentoR1=pedimento_r1, - EDocument=row[18], # C43 - EDocument - NumOperacionVU=row[19], # C44 - NumOperacionVU - BaseDeDatos=filters.database_name, - NumGafUni=num_gaf_uni, - UsuarioCap=row[22], # C53 - UsuarioCap - UsuarioAcr=row[23], # C54 - UsuarioAct - Fecha_Pago=parse_yyyymmdd_date(row[8]), # C13 - Fecha_Pago - NumCaja=row[24], # C56 - Transporte + NumTrasporte - Pedimento18=row[25], # C57 - empty (index 25) - AduanaCru=row[15], # C39 - Aduana_Cruce - Lote=row[26] # C58 - empty (index 26) - ) - - movements.append(movement) - - logger.info(f"Successfully processed {len(movements)} definitive import movements") - return movements - - except Exception as e: - logger.error(f"Error fetching definitive import movements: {e}", exc_info=True) - raise - - def get_movements_detailed( - self, - db: Session, - filters: "ImportDefinitiveFilter" - ) -> List["MovementItemDetailed"]: - """ - Get definitive import movements (detailed mode - line by line). - - Args: - db: Database session - filters: Filter criteria - - Returns: - List of detailed movement items (one per partida) - """ - from ..schemas import MovementItemDetailed - - try: - logger.info(f"Fetching detailed definitive import movements with filters: {filters.model_dump()}") - - # Get MetTrans configuration - met_trans = ConfigHelper.get_met_trans_config() - - # Build WHERE clause - where_clause = self._build_where_clause(filters) - - # Execute main query - sql = text(DefinitiveImportQueries.build_main_query(filters.database_name, where_clause)) - results = db.execute(sql).fetchall() - logger.info(f"Found {len(results)} detailed definitive import partidas") - - movements = [] - - for row in results: - # Skip cancelled if not included - if not filters.include_cancelled and row[3] != 'AC': # C4 - Estatus - continue - - # Get additional detailed information - # Provider and client names now come directly from query (row[7], row[8]) - # But we still need RFC and TaxID from the helper - proveedor_info = DatabaseHelper.get_client_info( - db, filters.database_name, row[15], is_supplier=True - ) - vendido_info = DatabaseHelper.get_client_info( - db, filters.database_name, row[16], is_supplier=False - ) - agente_info = DatabaseHelper.get_customs_agent_info( - db, filters.database_name, row[17] - ) - aduana_nombre = DatabaseHelper.get_aduana_seccion_nombre( - db, filters.database_name, row[38] - ) - - # Calculate values using unified method - valor_comercial, tipo_cambio = ExchangeRateCalculator.calculate_exchange_rate_and_value( - db=db, - db_name=filters.database_name, - es_subpartida=row[39], # C40 - EsSubPartida - valor_me=row[26], # C27 - ValorImpoME - valor_mn_direct=row[24], # C25 - ValorImpoMN - fecha_pago=row[12], # C13 - Fecha_Pago - fecha_inicio=row[10], # C1 entry_date - clave_ped=row[57] if len(row) > 57 else '', # C58 - TIPOPEDIMENTOTRANSPORTEE - tipo_cambio_partida=row[49], # C50 - TipoCambio - currency_type=filters.currency_type.value, - exchange_rate_type=filters.exchange_rate_type.value, - met_trans=met_trans - ) - - # Set peso values based on subpartida flag - if row[39] == 'P': # C40 - EsSubPartida - peso_neto = float(row[28]) if row[28] else 0.0 # C29 - peso_bruto = float(row[29]) if row[29] else 0.0 # C30 - else: - peso_neto = 0.0 - peso_bruto = 0.0 - - series_info = DatabaseHelper.get_series_info( - db, filters.database_name, row[38], row[43], filters.is_shelter # C39, C44 - ) - - simbolo_ex = None - if row[48]: # C49 - Part Number - simbolo_ex = DatabaseHelper.get_part_export_symbol( - db, filters.database_name, row[48], filters.is_shelter - ) - - pedimento_r1 = DatabaseHelper.get_rectification_pedimento( - db, row[1], row[40], filters.is_shelter # C2, C41 - ) - - num_gaf_uni = DatabaseHelper.get_driver_badge( - db, filters.database_name, row[0] # C1 - ) - - movement = MovementItemDetailed( - Linea=row[43], # C44 - Factura=row[0], # C1 - Pedimento=row[1], # C2 - FechaFactura=parse_yyyymmdd_date(row[2]), # C3 - Estatus=row[3], # C4 - ClavePed=row[4], # C5 - TipoMovTemDef='IMPDF', - EsCambioRegimen='N', - Regimen=row[9], # C10 - Fecha_Inicio=parse_yyyymmdd_date(row[10]), # C11 - Fecha_Fin=parse_yyyymmdd_date(row[11]), # C12 - Fecha_Pago=parse_yyyymmdd_date(row[12]), # C13 - Remesa=str(row[13]) if row[13] is not None else None, # C14 - Proveedor=row[7], # C8 - Provider name (from JOIN) - RFCProveedor=proveedor_info.get('rfc'), - ProveedorTaxID=proveedor_info.get('tax_id'), - VendidoA=row[8], # C9 - Client name (from JOIN) - VendidoARFC=vendido_info.get('rfc'), - VendidoATaxID=vendido_info.get('tax_id'), - AgenteAduanal=agente_info.get('name'), - Patente=agente_info.get('license'), - NumParte=row[48], # C49 - DescripcionE=StringHelper.clean_text(row[20]), # C21 - DescripcionI=StringHelper.clean_text(row[21]), # C22 - CantidadIE=float(row[22]) if row[22] else 0.0, # C23 - UniMed=row[23], # C24 - ValorComercialMN=valor_comercial, - TipoCambio=tipo_cambio, - PesoNeto=peso_neto, - PesoBruto=peso_bruto, - OrdenCompraVenta=row[30], # C31 - FraccionArancelaria=row[31], # C32 - Preferencia=row[32], # C33 - Sector=row[34], # C35 - PaisOrigen=row[36], # C37 - Aduana=aduana_nombre, - Advalorem='P' if row[39] == 'P' else 'S', # C40 - TipoExpo='', - PedimentoR1=pedimento_r1, - EDocument=row[41], # C42 - NumOperacionVU=row[42], # C43 - Series=series_info, - Marca=StringHelper.clean_text(row[44]), # C45 - Modelo=StringHelper.clean_text(row[45]), # C46 - FraccionAmericana=row[46], # C47 - ECCN=row[47], # C48 - SimboloEx=simbolo_ex, - FechaEmision=parse_yyyymmdd_date(row[50]) if row[50] else None, # C51 - BaseDeDatos=filters.database_name, - NumGafUni=num_gaf_uni, - UsuarioCap=row[51], # C52 - UsuarioAcr=row[52], # C53 - Transportista=row[53], # C54 - NumCaja=row[54], # C55 - Pedimento18=row[55] if len(row) > 55 else '', # C56 - AduanaCru=row[37], # C38 - Lote=row[56] if len(row) > 56 else '' # C57 - ) - - movements.append(movement) - - logger.info(f"Successfully processed {len(movements)} detailed definitive import movements") - return movements - - except Exception as e: - logger.error(f"Error fetching detailed definitive import movements: {e}", exc_info=True) - raise - - def _build_where_clause(self, filters: "ImportDefinitiveFilter") -> str: - """Build WHERE clause for definitive imports query.""" - where_conditions = [] - - # STRICT SEPARATION: Only imports - where_conditions.append("ih.operation_type = 'imp'") - - # GOLDEN RULE: If movement_type is ALL, only filter by operation_type - # ALWAYS filter by specific invoice_type to avoid duplication with Temporary service - where_conditions.append("ih.invoice_type IN ('DEF', 'MATDE', 'EXDEF')") - - # Date range filter - if filters.range_type.value == "FF": - where_conditions.append(f"ih.invoice_date >= TO_DATE('{filters.start_date}', 'YYYYMMDD') AND ih.invoice_date <= TO_DATE('{filters.end_date}', 'YYYYMMDD')") - else: - where_conditions.append(f"pd.payment_date >= TO_DATE('{filters.start_date}', 'YYYYMMDD') AND pd.payment_date <= TO_DATE('{filters.end_date}', 'YYYYMMDD')") - - # Note: Status filter applied at Python level after CASE WHEN in SELECT - # because status doesn't directly represent AC/NA status - - # Provider filter - if filters.provider: - where_conditions.append(f"cmp.provider_id IN (SELECT id FROM a76.clients_and_providers WHERE name = '{filters.provider}')") - - # Buyer filter - if filters.buyer: - where_conditions.append(f"cmp.sold_to_id IN (SELECT id FROM a76.clients_and_providers WHERE name = '{filters.buyer}')") - - # Pedimento code filter - if filters.pedimento_code: - where_conditions.append(f"ped.pedimento_code = '{filters.pedimento_code}'") - - return " AND ".join(where_conditions) - - def _calculate_totals(self, db: Session, db_name: str, consecutivo: int) -> tuple: - """Calculate totals for main partidas only. - - Only sums partidas where is_subpartida is false (equivalent to EsSubpartida = 'P' in Clarion). - """ - sql = text(""" - SELECT - COALESCE(SUM(lf.value_usd), 0), - COALESCE(SUM(lf.value_mxn), 0) - FROM a76.item_line_financials lf - INNER JOIN a76.item_lines il ON il.id = lf.item_line_id - WHERE il.invoice_id = :consecutivo - AND COALESCE(il.is_subpartida, false) = false - """) - - result = db.execute(sql, {"consecutivo": consecutivo}).fetchone() - total_me = float(result[0]) if result and result[0] is not None else 0.0 - total_mn = float(result[1]) if result and result[1] is not None else 0.0 - - return total_me, total_mn diff --git a/backend/api/v1/modules/a76/reports/movements/invoices/services/exchange_rate.py b/backend/api/v1/modules/a76/reports/movements/invoices/services/exchange_rate.py deleted file mode 100644 index 5c9f2010..00000000 --- a/backend/api/v1/modules/a76/reports/movements/invoices/services/exchange_rate.py +++ /dev/null @@ -1,193 +0,0 @@ -""" -Exchange rate calculation logic for invoice movements. -""" - -import logging -from sqlalchemy.orm import Session -from typing import Tuple, Optional -from .base import DateHelper -from .database_helpers import DatabaseHelper - -logger = logging.getLogger(__name__) - - -class ExchangeRateCalculator: - """Handles exchange rate calculations and commercial value conversions.""" - - @staticmethod - def calculate_exchange_rate_and_value( - db: Session, - db_name: str, - es_subpartida: str, - valor_me: Optional[float], - valor_mn_direct: Optional[float], - fecha_pago, - fecha_inicio, - clave_ped: str, - tipo_cambio_partida: Optional[float], - currency_type: str, - exchange_rate_type: str, - met_trans: int - ) -> Tuple[float, Optional[float]]: - """ - Unified method to calculate exchange rate and commercial value. - Eliminates duplicated logic across all import types. - - Args: - db: Database session - db_name: Database name - es_subpartida: Subpartida flag ('P' for partida, 'S' for subpartida) - valor_me: Value in foreign currency (ME) - valor_mn_direct: Direct value in local currency (MN) - fecha_pago: Payment date - fecha_inicio: Start/entry date - clave_ped: Pedimento type code - tipo_cambio_partida: Exchange rate from partida record - currency_type: "ME" or "MN" - exchange_rate_type: "FP" (payment date) or "FT" (transaction date) - met_trans: MetTrans configuration value - - Returns: - Tuple of (valor_comercial_mn, tipo_cambio_final) - """ - # Handle subpartidas - always return zero - if es_subpartida == 'S': - return (0.0, None) - - # Handle foreign currency (ME) case - if currency_type == "ME": - valor_comercial = valor_me or 0.0 - tipo_cambio_final = tipo_cambio_partida - - # Try to get exchange rate from GTipoCambio if using payment date - if exchange_rate_type == "FP" and fecha_pago: - fecha_tc = DateHelper.get_fecha_tipo_cambio( - fecha_pago=fecha_pago, - fecha_inicio=fecha_inicio, - tipo_pedimento=clave_ped, - use_transport_method=True, - met_trans=met_trans - ) - tc_value = DatabaseHelper.get_exchange_rate(db, db_name, fecha_tc) - if tc_value: - tipo_cambio_final = tc_value - - # For ME, the value is always in foreign currency (USD) - return (valor_comercial, tipo_cambio_final) - - # Handle local currency (MN) case - if exchange_rate_type == "FP" and fecha_pago: - fecha_tc = DateHelper.get_fecha_tipo_cambio( - fecha_pago=fecha_pago, - fecha_inicio=fecha_inicio, - tipo_pedimento=clave_ped, - use_transport_method=True, - met_trans=met_trans - ) - tc_value = DatabaseHelper.get_exchange_rate(db, db_name, fecha_tc) - - if tc_value and valor_me is not None: - # Calculate MN value from ME * payment date exchange rate - return (valor_me * tc_value, tc_value) - else: - if tc_value is None: - logger.warning(f"Exchange rate not found for date {fecha_tc}, using partida values") - return (valor_mn_direct or 0.0, tipo_cambio_partida) - else: - # exchange_rate_type == "FT" (Invoice Date) - # Use direct MN value and partida exchange rate - return (valor_mn_direct or 0.0, tipo_cambio_partida) - - @staticmethod - def calculate_for_aggregated( - db: Session, - db_name: str, - valor_me: float, - valor_mn: float, - tipo_cambio_db: float, - fecha_pago, - fecha_inicio, - tipo_pedimento: str, - currency_type: str, - exchange_rate_type: str, - is_shelter: bool, - use_transport_method: bool, - met_trans: int - ) -> Tuple[float, Optional[float]]: - """ - Calculate exchange rate and value for aggregated (normal mode) movements. - - This method is used when movements are grouped by invoice rather than - showing individual partidas. - - Args: - db: Database session - db_name: Database name - valor_me: Aggregated value in foreign currency - valor_mn: Aggregated value in local currency - tipo_cambio_db: Exchange rate from database - fecha_pago: Payment date - fecha_inicio: Start date - tipo_pedimento: Pedimento type - currency_type: "ME" or "MN" - exchange_rate_type: "FP" or "FT" - is_shelter: Shelter company flag (kept for compatibility) - use_transport_method: Use transport method flag - met_trans: MetTrans value from config - - Returns: - Tuple of (valor_comercial_mn, tipo_cambio) - """ - # Foreign currency case - if currency_type == "ME": - valor_comercial = valor_me - tipo_cambio = tipo_cambio_db - - # Try to get exchange rate if using payment date - if exchange_rate_type == "FP" and fecha_pago: - fecha_tc = DateHelper.get_fecha_tipo_cambio( - fecha_pago=fecha_pago, - fecha_inicio=fecha_inicio, - tipo_pedimento=tipo_pedimento, - use_transport_method=use_transport_method, - met_trans=met_trans - ) - tc_value = DatabaseHelper.get_exchange_rate( - db, db_name, fecha_tc, - is_shelter=is_shelter, - raise_on_missing=is_shelter - ) - - if tc_value: - # For ME, the value is always in foreign currency (USD). Just return the new exchange rate. - return (valor_comercial, tc_value) - else: - logger.warning(f"Exchange rate not found for date {fecha_tc}, using DB values") - - return (valor_comercial, tipo_cambio) - - # Local currency case - else: - if exchange_rate_type == "FP" and fecha_pago: - fecha_tc = DateHelper.get_fecha_tipo_cambio( - fecha_pago=fecha_pago, - fecha_inicio=fecha_inicio, - tipo_pedimento=tipo_pedimento, - use_transport_method=use_transport_method, - met_trans=met_trans - ) - tc_value = DatabaseHelper.get_exchange_rate( - db, db_name, fecha_tc, - is_shelter=is_shelter, - raise_on_missing=is_shelter - ) - - if tc_value and valor_me is not None: - # Calculate MN value from ME * payment date exchange rate - return (valor_me * tc_value, tc_value) - else: - if tc_value is None: - logger.warning(f"Exchange rate not found for date {fecha_tc}, using DB values") - return (valor_mn, tipo_cambio_db) - else: - return (valor_mn, tipo_cambio_db) diff --git a/backend/api/v1/modules/a76/reports/movements/invoices/services/export.py b/backend/api/v1/modules/a76/reports/movements/invoices/services/export.py deleted file mode 100644 index 67aa6b08..00000000 --- a/backend/api/v1/modules/a76/reports/movements/invoices/services/export.py +++ /dev/null @@ -1,408 +0,0 @@ -""" -Export service - handles export movements (EXPO DEF). -""" - -import logging -from datetime import datetime -from sqlalchemy import text -from sqlalchemy.orm import Session -from typing import List, Optional - -from ..schemas import ExportFilter, MovementItem, MovementItemDetailed -from .base import ConfigHelper, StringHelper -from .database_helpers import DatabaseHelper -from .exchange_rate import ExchangeRateCalculator -from .query_builders import ExportQueries - -logger = logging.getLogger(__name__) - - -def parse_yyyymmdd_date(date_str: str) -> Optional[datetime]: - """Parse date string in YYYYMMDD format to datetime.""" - if not date_str or date_str == '': - return None - try: - return datetime.strptime(date_str, '%Y%m%d') - except (ValueError, TypeError): - return None - - -class ExportService: - """Service for handling export movements (EXPO DEF).""" - - def get_movements( - self, - db: Session, - filters: ExportFilter - ) -> List[MovementItem]: - """ - Get export movements (normal mode - grouped by invoice). - - Args: - db: Database session - filters: Filter criteria - - Returns: - List of movement items grouped by invoice - """ - try: - logger.info(f"Fetching export movements with filters: {filters.model_dump()}") - - # Get MetTrans configuration - met_trans = ConfigHelper.get_met_trans_config() - - # Build WHERE clause - where_clause = self._build_where_clause(filters) - - # Execute optimized aggregated query for NORMAL mode - # Note: discharge_clause not used in aggregated query for exports - sql = text(ExportQueries.build_aggregated_query(filters.database_name, where_clause)) - results = db.execute(sql).fetchall() - logger.info(f"Found {len(results)} export invoices") - - movements = [] - - for row in results: - factura = row[0] # C1 - FacturaExpo - tipo_mov = row[14] # C34 - TipoFactura - - # Skip cancelled if not included - if not filters.include_cancelled and row[3] != 'AC': # C6 - Estatus - continue - - consecutivo = row[15] # C35 - Consecutivo - - # Totals come directly from GROUP BY query (no N+1 problem) - def to_float(val): - if val is None or val == '': return 0.0 - try: return float(val) - except (ValueError, TypeError): return 0.0 - - total_me = to_float(row[23]) # total_me from SUM aggregation - total_mn = to_float(row[24]) # total_mn from SUM aggregation - sum_value_usd = to_float(row[26]) - sum_value_mxn = to_float(row[27]) - - total_me = sum_value_usd if sum_value_usd > 0 else total_me - total_mn = sum_value_mxn if sum_value_mxn > 0 else total_mn - - # Calculate exchange rate and value - valor_comercial, tipo_cambio = ExchangeRateCalculator.calculate_for_aggregated( - db=db, - db_name=filters.database_name, - valor_me=total_me, - valor_mn=total_mn, - tipo_cambio_db=row[18], # C48 - TipoCambio - fecha_pago=row[7], # C11 - Fecha_Pago - fecha_inicio=row[6], # C10 - Fecha_Inicio (mapped previously to C9) - tipo_pedimento='', # Not in aggregated query - currency_type=filters.currency_type.value, - exchange_rate_type=filters.exchange_rate_type.value, - is_shelter=filters.is_shelter, - use_transport_method=filters.use_transport_method, - met_trans=met_trans - ) - - # Get pedimento rectification - rectified_pedimento = DatabaseHelper.get_rectification_pedimento( - db, - row[1], # C2 - PedimentoExpo - row[25], # C54 - PedRectifica - filters.is_shelter - ) - - # Get driver badge - driver_badge = self._get_driver_badge(db, filters.database_name, factura) - - # Build movement item - movement = MovementItem( - Factura=factura, - Pedimento=row[1], # C2 - PedimentoExpo - FechaFactura=parse_yyyymmdd_date(row[2]), # C3 - FechaFactura - Estatus=row[3], # C6 - Estatus - ClavePed=row[4], # C7 - ClavePed - TipoMovTemDef=tipo_mov, - EsCambioRegimen='N', - ValorMPTemp=valor_comercial, - ValorComercialMN=valor_comercial, - TipoCambio=tipo_cambio, - ValorAgre=to_float(row[28]), - TipoExpo='EXPO DEF', - PedimentoR1=rectified_pedimento, - EDocument=row[16], # C40 - EDocument - NumOperacionVU=row[17], # C41 - NumOperacionVU - BaseDeDatos=filters.database_name, - NumGafUni=driver_badge, - UsuarioCap=row[20], # C50 - UsuarioCap - UsuarioAcr=row[21], # C51 - UsuarioAct - Fecha_Pago=parse_yyyymmdd_date(row[7]), # C11 - Fecha_Pago - NumCaja=row[22], # C53 - Transporte + NumTrasporte - Pedimento18='', # Not in aggregated query - AduanaCru=row[13], # C33 - Aduana_Cruce - Lote='' # Not in aggregated query - ) - - movements.append(movement) - - logger.info(f"Successfully processed {len(movements)} export movements") - return movements - - except Exception as e: - logger.error(f"Error fetching export movements: {e}", exc_info=True) - raise - - def get_movements_detailed( - self, - db: Session, - filters: ExportFilter - ) -> List[MovementItemDetailed]: - """ - Get export movements (detailed mode - line by line). - - Args: - db: Database session - filters: Filter criteria - - Returns: - List of detailed movement items (one per partida) - """ - try: - logger.info(f"Fetching detailed export movements with filters: {filters.model_dump()}") - - # Get MetTrans configuration - met_trans = ConfigHelper.get_met_trans_config() - - # Build WHERE clause - where_clause = self._build_where_clause(filters) - - # Build discharge filter for main query - discharge_clause = "" - if filters.discharge_filter == "SiDes": - discharge_clause = " AND EqiPex.Descarga = 1" - elif filters.discharge_filter == "NoDes": - discharge_clause = " AND EqiPex.Descarga = 0" - - # Modify main query to include discharge filter - where_with_discharge = where_clause + discharge_clause - - # Execute main query - sql = text(ExportQueries.build_main_query(filters.database_name, where_with_discharge)) - results = db.execute(sql).fetchall() - logger.info(f"Found {len(results)} detailed export partidas") - - movements = [] - - for row in results: - # Skip cancelled if not included - if not filters.include_cancelled and row[5] == 'NA': # C6 - Estatus - continue - - # Get client/supplier information - proveedor_info = DatabaseHelper.get_client_info( - db, filters.database_name, row[13], is_supplier=True # C14 - Proveedor - ) - vendido_info = DatabaseHelper.get_client_info( - db, filters.database_name, row[14], is_supplier=False # C15 - VendidoA - ) - - # Get customs agent information - agente_info = DatabaseHelper.get_customs_agent_info( - db, filters.database_name, row[15] # C16 - AAduanal - ) - - # Get customs section name - customs_name = DatabaseHelper.get_aduana_seccion_nombre( - db, filters.database_name, row[32] # C33 - Aduana_Cruce - ) - - # Calculate exchange rate and value for this partida - valor_mn, tipo_cambio_final = ExchangeRateCalculator.calculate_exchange_rate_and_value( - db=db, - db_name=filters.database_name, - es_subpartida=row[37], # C38 - EsSubPartida - valor_me=row[36], # C37 - ValorExpoME - valor_mn_direct=row[35], # C36 - ValorExpoMN - fecha_pago=row[10], # C11 - Fecha_Pago - fecha_inicio=row[8], # C9 - Fecha_Inicio - clave_ped=row[55], # C56 - TIPOPEDIMENTOTRANSPORTEE - tipo_cambio_partida=row[47], # C48 - TipoCambio - currency_type=filters.currency_type.value, - exchange_rate_type=filters.exchange_rate_type.value, - met_trans=met_trans - ) - - # Set peso values (material_type is typically 'PT' or 'MP', not just 'P') - peso_neto_final = row[24] if row[37] != 'S' else 0 # C25 - PesoNeto - peso_bruto_final = row[25] if row[37] != 'S' else 0 # C26 - PesoBruto - - # Get series information - series_info = DatabaseHelper.get_series_info_export( - db, filters.database_name, row[34], row[41], filters.is_shelter - ) - - # Get pedimento rectification - rectified_pedimento = DatabaseHelper.get_rectification_pedimento( - db, - row[1], # C2 - PedimentoExpo - row[38], # C39 - PedRectifica - filters.is_shelter - ) - - # Get driver badge - driver_badge = self._get_driver_badge(db, filters.database_name, row[0]) # C1 - FacturaExpo - - # Build detailed movement item - movement = MovementItemDetailed( - Linea=row[41], # C42 - LineaExpo - Factura=row[0], # C1 - FacturaExpo - Pedimento=row[1], # C2 - PedimentoExpo - FechaFactura=row[2], # C3 - FechaFactura - Estatus=row[5], # C6 - Estatus - ClavePed=row[4], # C5 - ClavePed - TipoMovTemDef=row[31], # C34 - TipoFactura - EsCambioRegimen='N', - Regimen=row[5], # C6 - Regime (Shared index with Estatus in this query) - Fecha_Inicio=parse_yyyymmdd_date(row[6]), # C7 - Fecha_Inicio - Fecha_Fin=parse_yyyymmdd_date(row[7]), # C8 - Fecha_Fin - Fecha_Pago=parse_yyyymmdd_date(row[8]), # C9 - Fecha_Pago - Remesa=row[9], # C12 - Remesa - TipoCambio=tipo_cambio_final, - Proveedor=proveedor_info.get("name"), - RFCProveedor=proveedor_info.get("rfc"), - ProveedorTaxID=proveedor_info.get("tax_id"), - VendidoA=vendido_info.get("name"), - VendidoARFC=vendido_info.get("rfc"), - VendidoATaxID=vendido_info.get("tax_id"), - AgenteAduanal=agente_info.get("name"), - Patente=agente_info.get("license"), - NumParte=row[17], # C18 - NumParte - DescripcionE=StringHelper.remove_commas(row[18]), # C19 - DescripcionE - DescripcionI=StringHelper.remove_commas(row[19]), # C20 - DescripcionI - CantidadIE=row[20], # C21 - CantExpo - UniMed=row[21], # C22 - UnidadMedida - ValorComercialMN=valor_mn, - PesoNeto=peso_neto_final, - PesoBruto=peso_bruto_final, - OrdenCompraVenta=row[26], # C27 - OrdenCompra - FraccionArancelaria=row[27], # C28 - FraccionExpo - Preferencia=row[28], # C29 - TipoFraccion - Sector=row[30], # C31 - Sector - PaisOrigen=row[31], # C32 - PaisOrigen - Aduana=customs_name, - Advalorem=row[29], # C30 - Advalorem - TipoExpo='EXPO DEF', - PedimentoR1=rectified_pedimento, - EDocument=row[39], # C40 - EDocument - NumOperacionVU=row[40], # C41 - NumOperacionVU - Series=series_info, - Marca=StringHelper.clean_text(row[42]), # C43 - Marca - Modelo=StringHelper.clean_text(row[43]), # C44 - Modelo - FraccionAmericana=row[44], # C45 - FraccionAme - ECCN=row[45], # C46 - ECCN - FechaEmision=parse_yyyymmdd_date(row[48]) if row[48] else None, # C49 - FechaEmision - BaseDeDatos=filters.database_name, - NumGafUni=driver_badge, - UsuarioCap=row[49], # C50 - UsuarioCap - UsuarioAcr=row[50], # C51 - UsuarioAct - Transportista=row[51], # C52 - Carrier ID (derived from log.transport_id) - NumCaja=row[52], # C53 - log.transport_id || log.transport_num - Pedimento18=row[53], # C54 - empty - AduanaCru=row[32], # C33 - Aduana_Cruce - Lote=row[54] if len(row) > 54 else '' # C55 - Lote - ) - - - movements.append(movement) - - logger.info(f"Successfully processed {len(movements)} detailed export movements") - return movements - - except Exception as e: - logger.error(f"Error fetching detailed export movements: {e}", exc_info=True) - raise - - def _build_where_clause(self, filters: ExportFilter) -> str: - """ - Build WHERE clause for export query. - - IMPORTANT: Returns conditions WITHOUT the WHERE keyword (already in base query) - AC (Active) = status = true - NA (Not Applicable/Deactivated) = status = false - """ - conditions = [] - - # STRICT SEPARATION: Only exports - conditions.append("ih.operation_type = 'exp'") - - # Exclude REP (export reports) - conditions.append("ih.invoice_type NOT IN ('REP')") - - # GOLDEN RULE: If movement_type is ALL, only filter by operation_type - if filters.movement_type.value != "ALL": - # Filter by invoice type for exports - conditions.append("ih.invoice_type IN ('EXP', 'EXREP')") - - # CRITICAL VALIDATION: AC/NA status filter - # If include_cancelled is False (checkbox unchecked), only show AC invoices - # AC (Active) = status = true - # NA (Not Applicable/Deactivated) = status = false - if not filters.include_cancelled: - conditions.append("ih.status = true") - logger.debug("Filtering only active invoices (status = true)") - else: - logger.debug("Including cancelled invoices (include_cancelled = true)") - - # Date range - date_field = "ih.invoice_date" if filters.range_type.value == "FF" else "pd.payment_date" - conditions.append(f"{date_field} >= TO_DATE('{filters.start_date}', 'YYYYMMDD')") - conditions.append(f"{date_field} <= TO_DATE('{filters.end_date}', 'YYYYMMDD')") - - # Optional filters - if filters.provider: - conditions.append(f"cmp.provider_id IN (SELECT id FROM a76.clients_and_providers WHERE name = '{filters.provider}')") - if filters.buyer: - conditions.append(f"cmp.sold_to_id IN (SELECT id FROM a76.clients_and_providers WHERE name = '{filters.buyer}')") - if filters.pedimento_code: - conditions.append(f"ped.pedimento_code = '{filters.pedimento_code}'") - - return " AND ".join(conditions) - - def _build_discharge_clause(self, discharge_filter: str) -> str: - """Build discharge filter clause for totals query.""" - if discharge_filter == "SiDes": - return " AND il.is_discharged = true" - elif discharge_filter == "NoDes": - return " AND il.is_discharged = false" - return "" - - def _calculate_totals( - self, - db: Session, - db_name: str, - consecutivo: int, - discharge_clause: str - ) -> tuple: - """Calculate total values for an export invoice.""" - try: - sql = text(ExportQueries.build_totals_query(db_name, discharge_clause)) - result = db.execute(sql, {"consecutivo": consecutivo}).fetchone() - - if result: - return (result[0] or 0, result[1] or 0) - return (0, 0) - except Exception as e: - logger.error(f"Error calculating export totals for consecutivo {consecutivo}: {e}") - return (0, 0) - - def _get_driver_badge(self, db: Session, db_name: str, factura: str) -> str: - """Get driver's unique badge number for an export invoice.""" - if not factura: - return None - - try: - sql = text(ExportQueries.build_driver_badge_query(db_name)) - result = db.execute(sql, {"factura": factura}).fetchone() - return result[0] if result else None - except Exception as e: - logger.debug(f"Error fetching driver badge for export invoice {factura}: {e}") - return None diff --git a/backend/api/v1/modules/a76/reports/movements/invoices/services/export_repair.py b/backend/api/v1/modules/a76/reports/movements/invoices/services/export_repair.py deleted file mode 100644 index f867acd7..00000000 --- a/backend/api/v1/modules/a76/reports/movements/invoices/services/export_repair.py +++ /dev/null @@ -1,409 +0,0 @@ -""" -Export repair service - handles EXPO REP movements (repair exports). -""" - -import logging -from datetime import datetime -from sqlalchemy import text -from sqlalchemy.orm import Session -from typing import List, Optional - -from ..schemas import ExportRepairFilter, MovementItem, MovementItemDetailed -from .base import ConfigHelper, StringHelper -from .database_helpers import DatabaseHelper -from .exchange_rate import ExchangeRateCalculator -from .query_builders import ExportRepairQueries - -logger = logging.getLogger(__name__) - - -def parse_yyyymmdd_date(date_str: str) -> Optional[datetime]: - """Parse date string in YYYYMMDD format to datetime.""" - if not date_str or date_str == '': - return None - try: - return datetime.strptime(date_str, '%Y%m%d') - except (ValueError, TypeError): - return None - - -class ExportRepairService: - """Service for handling export repair movements (EXPO REP).""" - - def get_movements( - self, - db: Session, - filters: ExportRepairFilter - ) -> List[MovementItem]: - """ - Get export repair movements (normal mode - grouped by invoice). - - Aggregated query column order (ExportRepairQueries.build_aggregated_query): - [0] C1 - invoice_number - [1] C2 - pedimento_number - [2] C3 - invoice_date - [3] C6 - estatus (AC/NA) - [4] C7 - pedimento_code - [5] C8 - regime - [6] C11 - payment_date - [7] C12 - remesa - [8] C13 - exchange_rate (fecha_pago context) - [9] C14 - provider_id - [10] C15 - sold_to_id - [11] C16 - customs_broker_id - [12] C27 - purchase_order - [13] C33 - customs_office ← AduanaCru - [14] C34 - document_type ← TipoFactura / tipo_mov - [15] C35 - id ← consecutivo - [16] C40 - edocument ← EDocument - [17] C41 - vucem_op_num ← NumOperacionVU - [18] C48 - exchange_rate ← TipoCambio - [19] C49 - emission_date - [20] C50 - capture_user ← UsuarioCap - [21] C51 - who_processed ← UsuarioAcr - [22] C52 - carrier_id ← Transportista - [23] C53 - transport ← NumCaja - [24] total_me - [25] total_mn - [26] C54 - ped_r1 ← PedimentoR1 - - Args: - db: Database session - filters: Filter criteria - - Returns: - List of movement items grouped by invoice - """ - try: - logger.info(f"Fetching export repair movements with filters: {filters.model_dump()}") - - # Get MetTrans configuration - met_trans = ConfigHelper.get_met_trans_config() - - # Build WHERE clause - where_clause = self._build_where_clause(filters) - - # Execute optimized aggregated query for NORMAL mode - sql = text(ExportRepairQueries.build_aggregated_query(filters.database_name, where_clause)) - results = db.execute(sql).fetchall() - logger.info(f"Found {len(results)} export repair invoices") - - movements = [] - - for row in results: - factura = row[0] # C1 - FacturaExpo - tipo_mov = row[14] # C34 - TipoFactura - estatus = row[3] # C6 - Estatus (AC o NA) - - # Filtrar facturas según include_cancelled - if not filters.include_cancelled and estatus != 'AC': - continue - - consecutivo = row[15] # C35 - Consecutivo - - # Totals come directly from GROUP BY query (no N+1 problem) - def to_float(val): - if val is None or val == '': return 0.0 - try: return float(val) - except (ValueError, TypeError): return 0.0 - - total_me = to_float(row[24]) # total_me - total_mn = to_float(row[25]) # total_mn - sum_value_usd = to_float(row[27]) - sum_value_mxn = to_float(row[28]) - - total_me = sum_value_usd if sum_value_usd > 0 else total_me - total_mn = sum_value_mxn if sum_value_mxn > 0 else total_mn - - # Calculate exchange rate and value - valor_comercial, tipo_cambio = ExchangeRateCalculator.calculate_for_aggregated( - db=db, - db_name=filters.database_name, - valor_me=total_me, - valor_mn=total_mn, - tipo_cambio_db=row[18], # C48 - TipoCambio - fecha_pago=row[6], # C11 - Fecha_Pago - fecha_inicio='', - tipo_pedimento='', - currency_type=filters.currency_type.value, - exchange_rate_type=filters.exchange_rate_type.value, - is_shelter=filters.is_shelter, - use_transport_method=False, - met_trans=met_trans - ) - - # Get pedimento rectification (already resolved by SQL COALESCE) - pedimento_r1 = DatabaseHelper.get_rectification_pedimento( - db, - row[1], # C2 - PedimentoExpo - row[26], # C54 - PedRectifica (pre-built by SQL) - filters.is_shelter - ) - - # Get driver badge - num_gaf_uni = self._get_driver_badge(db, filters.database_name, factura) - - # Build movement item - movement = MovementItem( - Factura=factura, - Pedimento=row[1], # C2 - PedimentoExpo - FechaFactura=parse_yyyymmdd_date(row[2]), # C3 - FechaFactura - Estatus=row[3], # C6 - Estatus - ClavePed=row[4], # C7 - ClavePed - TipoMovTemDef=tipo_mov, # C34 - TipoFactura - EsCambioRegimen='N', - ValorMPTemp=valor_comercial, - ValorComercialMN=valor_comercial, - TipoCambio=tipo_cambio, - ValorAgre=to_float(row[29]), - TipoExpo='EXPO REP', - PedimentoR1=pedimento_r1, - EDocument=row[16], # C40 - EDocument ← FIXED (was 17) - NumOperacionVU=row[17], # C41 - NumOperacionVU ← FIXED (was 18) - BaseDeDatos=filters.database_name, - NumGafUni=num_gaf_uni, - UsuarioCap=row[20], # C50 - UsuarioCap ← FIXED (was 21) - UsuarioAcr=row[21], # C51 - UsuarioAct ← FIXED (was 22) - Fecha_Pago=parse_yyyymmdd_date(row[6]), # C11 - Fecha_Pago ← FIXED (was 8) - NumCaja=row[23], # C53 - NumCaja - Pedimento18='', - AduanaCru=row[13], # C33 - customs_office ← FIXED (was 14) - Lote='' - ) - - movements.append(movement) - - logger.info(f"Successfully processed {len(movements)} export repair movements") - return movements - - except Exception as e: - logger.error(f"Error fetching export repair movements: {e}", exc_info=True) - raise - - def get_movements_detailed( - self, - db: Session, - filters: ExportRepairFilter - ) -> List[MovementItemDetailed]: - """ - Get export repair movements (detailed mode - line by line). - - Args: - db: Database session - filters: Filter criteria - - Returns: - List of detailed movement items (one per partida) - """ - try: - logger.info(f"Fetching detailed export repair movements with filters: {filters.model_dump()}") - - # Get MetTrans configuration - met_trans = ConfigHelper.get_met_trans_config() - - # Build WHERE clause - where_clause = self._build_where_clause(filters) - - # Add discharge filter to WHERE clause - discharge_clause = "" - if filters.discharge_filter.value == "SiDes": - discharge_clause = " AND RepPex.Descarga = 1" - elif filters.discharge_filter.value == "NoDes": - discharge_clause = " AND RepPex.Descarga = 0" - - where_with_discharge = where_clause + discharge_clause - - # Execute main query - sql = text(ExportRepairQueries.build_main_query(filters.database_name, where_with_discharge)) - results = db.execute(sql).fetchall() - logger.info(f"Found {len(results)} detailed export repair partidas") - - movements = [] - - for row in results: - # Skip cancelled if not included - if not filters.include_cancelled and row[5] == 'NA': # C6 - Estatus - continue - - # Get provider information - proveedor_info = DatabaseHelper.get_client_info( - db, filters.database_name, row[13], is_supplier=True # C14 - Proveedor - ) - - # Get buyer information - vendido_info = DatabaseHelper.get_client_info( - db, filters.database_name, row[14], is_supplier=False # C15 - VendidoA - ) - - # Get customs agent information - agente_info = DatabaseHelper.get_customs_agent_info( - db, filters.database_name, row[15] # C16 - AAduanal - ) - - # Get customs section name - aduana_nombre = DatabaseHelper.get_aduana_seccion_nombre( - db, filters.database_name, row[32] # C33 - Aduana_Cruce - ) - - # Set peso values based on subpartida flag (allow 'PT', 'MP', etc. but block 'S') - if row[37] != 'S': # C38 - EsSubPartida - valor_comercial, tipo_cambio = ExchangeRateCalculator.calculate_for_partida( - db=db, - db_name=filters.database_name, - valor_me=row[36], # C37 - ValorExpoME - valor_mn=row[35], # C36 - ValorExpoMN - tipo_cambio_db=row[47], # C48 - TipoCambio - fecha_pago=row[10], # C11 - Fecha_Pago - fecha_inicio=row[8], # C9 - Fecha_Inicio - tipo_pedimento=row[55], # C56 - TIPOPEDIMENTOTRANSPORTEE - currency_type=filters.currency_type.value, - exchange_rate_type=filters.exchange_rate_type.value, - is_shelter=filters.is_shelter, - use_transport_method=False, - met_trans=met_trans - ) - peso_neto = float(row[24]) if row[24] else 0.0 # C25 - peso_bruto = float(row[25]) if row[25] else 0.0 # C26 - else: # Subpartida - valor_comercial = 0.0 - tipo_cambio = 0.0 - peso_neto = 0.0 - peso_bruto = 0.0 - - # Get series information - series_info = DatabaseHelper.get_series_info_export( - db, filters.database_name, row[34], row[41], filters.is_shelter # C35, C42 - ) - - # Get part export symbol - simbolo_ex = None - if row[46]: # C47 - NumParte - simbolo_ex = DatabaseHelper.get_part_export_symbol( - db, filters.database_name, row[46], filters.is_shelter - ) - - # Get pedimento rectification - pedimento_r1 = DatabaseHelper.get_rectification_pedimento( - db, - row[1], # C2 - PedimentoExpo - row[38], # C39 - PedRectifica - filters.is_shelter - ) - - # Get driver badge - num_gaf_uni = self._get_driver_badge(db, filters.database_name, row[0]) - - # Build detailed movement item - movement = MovementItemDetailed( - Linea=row[41], # C42 - LineaExpo - Factura=row[0], # C1 - FacturaExpo - Pedimento=row[1], # C2 - PedimentoExpo - FechaFactura=row[2], # C3 - FechaFactura - Estatus=row[5], # C6 - Estatus - ClavePed=row[6], # C7 - ClavePed - TipoMovTemDef=row[33], # C34 - TipoFactura - EsCambioRegimen='N', - Regimen=row[7], # C8 - Regimen - Fecha_Inicio=parse_yyyymmdd_date(row[8]), # C9 - Fecha_Inicio - Fecha_Fin=parse_yyyymmdd_date(row[9]), # C10 - Fecha_Fin - Fecha_Pago=parse_yyyymmdd_date(row[10]), # C11 - Fecha_Pago - Remesa=row[11], # C12 - Remesa - Proveedor=proveedor_info.get('name'), - RFCProveedor=proveedor_info.get('rfc'), - ProveedorTaxID=proveedor_info.get('tax_id'), - VendidoA=vendido_info.get('name'), - VendidoARFC=vendido_info.get('rfc'), - VendidoATaxID=vendido_info.get('tax_id'), - AgenteAduanal=agente_info.get('name'), - Patente=agente_info.get('license'), - NumParte=row[46], # C47 - NumParte - DescripcionE=StringHelper.clean_text(row[18]), # C19 - DescripcionI=StringHelper.clean_text(row[19]), # C20 - CantidadIE=float(row[20]) if row[20] else 0.0, # C21 - UniMed=row[21], # C22 - ValorComercialMN=valor_comercial, - TipoCambio=tipo_cambio, - PesoNeto=peso_neto, - PesoBruto=peso_bruto, - OrdenCompraVenta=row[26], # C27 - OrdenCompra - FraccionArancelaria=row[27], # C28 - FraccionExpo - Preferencia=row[28], # C29 - TipoFraccion - Sector=row[30], # C31 - Sector - PaisOrigen=row[31], # C32 - PaisOrigen - Aduana=aduana_nombre, - Advalorem=row[37], # C38 - EsSubPartida - TipoExpo='EXPO REP', - PedimentoR1=pedimento_r1, - EDocument=row[39], # C40 - EDocument - NumOperacionVU=row[40], # C41 - NumOperacionVU - Series=series_info, - Marca=StringHelper.clean_text(row[42]), # C43 - Modelo=StringHelper.clean_text(row[43]), # C44 - FraccionAmericana=row[44], # C45 - FraccionAme - ECCN=row[45], # C46 - ECCN - SimboloEx=simbolo_ex, - FechaEmision=parse_yyyymmdd_date(row[48]) if row[48] else None, # C49 - FechaFactura - BaseDeDatos=filters.database_name, - NumGafUni=num_gaf_uni, - UsuarioCap=row[49], # C50 - UsuarioCap - UsuarioAcr=row[50], # C51 - UsuarioAct - Transportista=row[51], # C52 - Transportista - NumCaja=row[52], # C53 - Transporte + NumTrasporte - Pedimento18=row[53], # C54 - Pedimento18 - AduanaCru=row[32], # C33 - Aduana_Cruce - Lote=row[54] if len(row) > 54 else '' # C55 - Lote - ) - - movements.append(movement) - - logger.info(f"Successfully processed {len(movements)} detailed export repair movements") - return movements - - except Exception as e: - logger.error(f"Error fetching detailed export repair movements: {e}", exc_info=True) - raise - - def _build_where_clause(self, filters: ExportRepairFilter) -> str: - """Build WHERE clause for export repair query.""" - where_conditions = [] - - # STRICT SEPARATION: Only exports for repair - where_conditions.append("ih.operation_type = 'exp'") - # GOLDEN RULE: If movement_type is ALL, only filter by operation_type - if filters.movement_type.value == "ALL": - pass - else: - where_conditions.append("ih.invoice_type = 'REPAR'") - - # Date range filter - if filters.range_type.value == "FF": - where_conditions.append(f"ih.invoice_date >= TO_DATE('{filters.start_date}', 'YYYYMMDD') AND ih.invoice_date <= TO_DATE('{filters.end_date}', 'YYYYMMDD')") - else: - where_conditions.append(f"pd.payment_date >= TO_DATE('{filters.start_date}', 'YYYYMMDD') AND pd.payment_date <= TO_DATE('{filters.end_date}', 'YYYYMMDD')") - - # Provider filter - if filters.provider: - where_conditions.append(f"cmp.provider_id IN (SELECT id FROM a76.clients_and_providers WHERE name = '{filters.provider}')") - - # Buyer filter - if filters.buyer: - where_conditions.append(f"cmp.sold_to_id IN (SELECT id FROM a76.clients_and_providers WHERE name = '{filters.buyer}')") - - # Pedimento code filter - if filters.pedimento_code: - where_conditions.append(f"ped.pedimento_code = '{filters.pedimento_code}'") - - # Movement type filter - if filters.movement_type.value == "AFIJO": - where_conditions.append("ih.document_type = 'AFIJO'") - elif filters.movement_type.value == "NODES": - where_conditions.append("ih.document_type = 'NODES'") - - return " AND ".join(where_conditions) - - return total_me, total_mn - - def _get_driver_badge(self, db: Session, db_name: str, factura: str) -> str: - """Get driver badge number for invoice.""" - # TODO: GConductor table not migrated to PostgreSQL yet - return None \ No newline at end of file diff --git a/backend/api/v1/modules/a76/reports/movements/invoices/services/query_builders.py b/backend/api/v1/modules/a76/reports/movements/invoices/services/query_builders.py deleted file mode 100644 index e00286cb..00000000 --- a/backend/api/v1/modules/a76/reports/movements/invoices/services/query_builders.py +++ /dev/null @@ -1,1071 +0,0 @@ -""" -SQL Query builders for invoice movement services. -Centralizes all SQL query construction logic. -""" - - -class TemporaryImportQueries: - """SQL queries for temporary imports using PostgreSQL tables.""" - - @staticmethod - def build_aggregated_query(db_name: str, where_str: str) -> str: - """Build optimized query for NORMAL mode (grouped by invoice with totals).""" - # Note: db_name parameter kept for compatibility but not used in PostgreSQL - return f""" - SELECT - ih.invoice_number AS C1, - COALESCE(ped.pedimento_number, '') AS C2, - TO_CHAR(ih.invoice_date, 'YYYYMMDD') AS C3, - CASE WHEN ih.status THEN 'AC' ELSE 'NA' END AS C4, - COALESCE(ped.pedimento_code, '') AS C5, - COALESCE(ped.regime, '') AS C10, - COALESCE(TO_CHAR(pd.entry_date, 'YYYYMMDD'), '') AS C11, - COALESCE(TO_CHAR(pd.end_date, 'YYYYMMDD'), TO_CHAR(pd.payment_date, 'YYYYMMDD'), '') AS C12, - COALESCE(TO_CHAR(pd.payment_date, 'YYYYMMDD'), '') AS C13, - COALESCE(cmp.remesa, 0) AS C14, - COALESCE(fin.exchange_rate, 0) AS C15, - COALESCE(cmp.provider_id::text, '') AS C16, - COALESCE(cmp.sold_to_id::text, '') AS C17, - COALESCE(cmp.customs_broker_id::text, '') AS C18, - COALESCE(cmp.aduana, '') AS C38, - ih.id AS C39, - COALESCE( - ped_r1.year || '-' || ped_r1.customs_office || '-' || ped_r1.license || '-' || ped_r1.pedimento_number, - pro_origin.original_pedimento_year || '-' || pro_origin.original_customs_office || '-' || pro_origin.original_license || '-' || pro_origin.original_pedimento_number, - '' - ) AS C41, - COALESCE(cmp.edocument, '') AS C42, - COALESCE(cmp.vucem_operation_num, '') AS C43, - COALESCE(fin.exchange_rate, 0) AS C50, - COALESCE(TO_CHAR(ih.emission_date, 'YYYYMMDD'), '') AS C51, - COALESCE(ih.capture_user, '') AS C52, - COALESCE(ih.who_processed, '') AS C53, - COALESCE(log.carrier_id, '') AS C54, - COALESCE(NULLIF(CONCAT_WS(' ', log.transport_num, log.license_plate), ''), '') AS C55, - '' AS C56, - '' AS C57, - '' AS C58, - COALESCE(fin.value_me, 0) AS total_me, - COALESCE(fin.value_mn, 0) AS total_mn, - COALESCE(lf_agg.sum_value_mxn, 0) AS valor_comercial_mn, - COALESCE(lf_agg.sum_value_temp_mxn, 0) AS valor_mp_temp_mn, - COALESCE(lf_agg.sum_value_added_mxn, 0) AS valor_agre_mn, - COALESCE(lf_agg.sum_value_temp_usd, 0) AS valor_mp_temp_usd, - COALESCE(lf_agg.sum_value_usd, 0) AS sum_value_usd - FROM a76.invoice_header ih - LEFT JOIN a76.invoice_compliance_mx cmp ON cmp.invoice_id = ih.id - LEFT JOIN a76.invoice_financials fin ON fin.invoice_id = ih.id - LEFT JOIN a76.invoice_logistics log ON log.invoice_id = ih.id - LEFT JOIN a76.pedimentos ped ON ped.id = cmp.pedimento_id - LEFT JOIN a76.pedimento_rectification_origin pro_origin ON pro_origin.pedimento_id = ped.id - LEFT JOIN a76.pedimento_rectification_origin pro_rect ON - pro_rect.original_pedimento_year = ped.year AND - pro_rect.original_customs_office = ped.customs_office AND - pro_rect.original_license = ped.license AND - pro_rect.original_pedimento_number = ped.pedimento_number - LEFT JOIN a76.pedimentos ped_r1 ON ped_r1.id = pro_rect.pedimento_id - LEFT JOIN a76.pedimento_dates pd ON pd.pedimento_id = ped.id - LEFT JOIN ( - SELECT il.invoice_id, - SUM(COALESCE(lf.value_mxn, 0)) AS sum_value_mxn, - SUM(COALESCE(lf.value_temp_material_mxn, 0)) AS sum_value_temp_mxn, - SUM(COALESCE(lf.value_added_mxn, 0)) AS sum_value_added_mxn, - SUM(COALESCE(lf.value_temp_material_usd, 0)) AS sum_value_temp_usd, - SUM(COALESCE(lf.value_usd, 0)) AS sum_value_usd - FROM a76.item_lines il - JOIN a76.item_line_financials lf ON lf.item_line_id = il.id - GROUP BY il.invoice_id - ) lf_agg ON lf_agg.invoice_id = ih.id - WHERE ih.operation_type = 'imp' - AND ih.invoice_type = 'TEM' - AND {where_str} - ORDER BY ih.invoice_number - """ - - - @staticmethod - def build_main_query(db_name: str, where_str: str) -> str: - """Build main SQL query for DETAILED mode (all partidas) from PostgreSQL.""" - # Note: db_name parameter is kept for compatibility but not used in PostgreSQL - return f""" - SELECT - ih.invoice_number AS C1, - COALESCE(ped.pedimento_number, '') AS C2, - TO_CHAR(ih.invoice_date, 'YYYYMMDD') AS C3, - CASE WHEN ih.status THEN 'AC' ELSE 'NA' END AS C4, - COALESCE(ped.pedimento_code, '') AS C5, - COALESCE(fin.value_me, 0) AS C6, - COALESCE(fin.value_mn, 0) AS C7, - COALESCE(prov.name, '') AS C8, - COALESCE(client.name, '') AS C9, - COALESCE(ped.regime, '') AS C10, - COALESCE(TO_CHAR(pd.entry_date, 'YYYYMMDD'), '') AS C11, - COALESCE(TO_CHAR(pd.end_date, 'YYYYMMDD'), TO_CHAR(pd.payment_date, 'YYYYMMDD'), '') AS C12, - COALESCE(TO_CHAR(pd.payment_date, 'YYYYMMDD'), '') AS C13, - COALESCE(cmp.remesa, 0) AS C14, - COALESCE(fin.exchange_rate, 0) AS C15, - COALESCE(cmp.provider_id::text, '') AS C16, - COALESCE(cmp.sold_to_id::text, '') AS C17, - COALESCE(cmp.customs_broker_id::text, '') AS C18, - '' AS C19, - COALESCE(il.class_id::text, '') AS C20, - REPLACE(REPLACE(REPLACE(REPLACE(COALESCE(ld.description_spanish, ''), CHR(44), ' '), CHR(9), ' '), CHR(10), ' '), CHR(13), ' ') AS C21, - REPLACE(REPLACE(REPLACE(REPLACE(COALESCE(ld.description_english, ''), CHR(44), ' '), CHR(9), ' '), CHR(10), ' '), CHR(13), ' ') AS C22, - COALESCE(lq.quantity, 0) AS C23, - COALESCE(il.unit_of_measure::text, '') AS C24, - COALESCE(lf.value_mxn, 0) AS C25, - COALESCE(lf.customs_value_mxn, 0) AS C26, - COALESCE(lf.value_usd, 0) AS C27, - COALESCE(lf.customs_value_usd, 0) AS C28, - COALESCE(lq.net_weight, 0) AS C29, - COALESCE(lq.gross_weight, 0) AS C30, - COALESCE(il.order, ih.purchase_order, '') AS C31, - COALESCE(lc.fraction, '') AS C32, - COALESCE(lc.fraction_type, '') AS C33, - COALESCE(lc.advalorem_numeric, 0) AS C34, - COALESCE(NULLIF(lc.sector, ''), NULLIF(fap.sector, ''), '') AS C35, - COALESCE(lf.igi_amount_usd, 0) AS C36, - COALESCE(lc.origin_country, '') AS C37, - COALESCE(cmp.aduana, '') AS C38, - ih.id AS C39, - COALESCE(il.material_type, 'P') AS C40, - COALESCE( - ped_r1.year || '-' || ped_r1.customs_office || '-' || ped_r1.license || '-' || ped_r1.pedimento_number, - pro_origin.original_pedimento_year || '-' || pro_origin.original_customs_office || '-' || pro_origin.original_license || '-' || pro_origin.original_pedimento_number, - '' - ) AS C41, -- [40] rectification_id - cmp.edocument AS C42, -- [41] - cmp.vucem_operation_num AS C43, -- [42] - COALESCE(il.line_number, 0) AS C44, - REPLACE(REPLACE(REPLACE(REPLACE(COALESCE(ld.brand, ''), CHR(44), ' '), CHR(9), ' '), CHR(10), ' '), CHR(13), ' ') AS C45, - REPLACE(REPLACE(REPLACE(REPLACE(COALESCE(ld.model, ''), CHR(44), ' '), CHR(9), ' '), CHR(10), ' '), CHR(13), ' ') AS C46, - COALESCE(cls.us_fraction, '') AS C47, - COALESCE(prt.eccn, fac.eccn_code, '') AS C48, - COALESCE(prt.part_number::text, '') AS C49, - COALESCE(fin.exchange_rate, 0) AS C50, - COALESCE(TO_CHAR(ih.emission_date, 'YYYYMMDD'), '') AS C51, - COALESCE(ih.capture_user, '') AS C52, - COALESCE(ih.who_processed, '') AS C53, - COALESCE(log.carrier_id, '') AS C54, - COALESCE(NULLIF(CONCAT_WS(' ', log.transport_num, log.license_plate), ''), '') AS C55, - '' AS C56, - COALESCE(ld.lot, '') AS C57, - '' AS C58, - COALESCE(lf.value_temp_material_mxn, 0) AS C59, - COALESCE(lf.value_temp_material_usd, 0) AS C60 - FROM a76.invoice_header ih - LEFT JOIN a76.invoice_compliance_mx cmp ON cmp.invoice_id = ih.id - LEFT JOIN a76.invoice_financials fin ON fin.invoice_id = ih.id - LEFT JOIN a76.invoice_logistics log ON log.invoice_id = ih.id - LEFT JOIN a76.pedimentos ped ON ped.id = cmp.pedimento_id - LEFT JOIN a76.pedimento_rectification_origin pro_origin ON pro_origin.pedimento_id = ped.id - LEFT JOIN a76.pedimento_rectification_origin pro_rect ON - pro_rect.original_pedimento_year = ped.year AND - pro_rect.original_customs_office = ped.customs_office AND - pro_rect.original_license = ped.license AND - pro_rect.original_pedimento_number = ped.pedimento_number - LEFT JOIN a76.pedimentos ped_r1 ON ped_r1.id = pro_rect.pedimento_id - LEFT JOIN a76.pedimento_dates pd ON pd.pedimento_id = ped.id - LEFT JOIN a76.clients_and_providers prov ON prov.id = cmp.provider_id - LEFT JOIN a76.clients_and_providers client ON client.id = cmp.sold_to_id - LEFT JOIN a76.item_lines il ON il.invoice_id = ih.id - LEFT JOIN a76.item_line_descriptions ld ON ld.item_line_id = il.id - LEFT JOIN a76.item_line_quantities lq ON lq.item_line_id = il.id - LEFT JOIN a76.item_line_financials lf ON lf.item_line_id = il.id - LEFT JOIN a76.item_line_customs lc ON lc.item_line_id = il.id - LEFT JOIN a76.classes cls ON cls.id = il.class_id - LEFT JOIN a24.fa_classes fac ON fac.class_id = cls.id - LEFT JOIN a76.parts prt ON prt.id = il.part_number_id - LEFT JOIN a24.fa_partes fap ON fap.id = prt.id - LEFT JOIN a76.units_of_measure um ON um.id = il.unit_of_measure - WHERE ih.operation_type = 'imp' - AND ih.invoice_type = 'TEM' - AND {where_str} - ORDER BY ih.invoice_number, il.line_number - """ - - @staticmethod - def build_totals_query(db_name: str) -> str: - """Build query to get totals for an invoice.""" - return f""" - SELECT - COALESCE(SUM(EqiPim.ValorImpoME), 0), - COALESCE(SUM(EqiPim.ValorImpoMN), 0) - FROM [{db_name}].dbo.QEqiMaq EqiPim - WHERE EqiPim.Consecutivo = :consecutivo - AND EqiPim.EsSubpartida = 'P' - """ - - @staticmethod - def build_series_query(db_name: str) -> str: - """Build query to get series information.""" - return f""" - SELECT SerieImpo, ModeloImpo, ParteImpo - FROM [{db_name}].dbo.QSeriesImpo - WHERE Consecutivo = :consecutivo - AND LineaImpo = :linea - ORDER BY RenImpo - """ - - @staticmethod - def build_driver_badge_query(db_name: str) -> str: - """Build query to get driver badge number.""" - return f""" - SELECT TOP 1 NUMGAFETEUNICO - FROM [{db_name}].dbo.GConductor - LEFT JOIN [{db_name}].dbo.QFacImp - ON QFacImp.CONDUCTOR = GConductor.CONDUCTOR - WHERE FacturaImpo = :factura - """ - - -class DefinitiveImportQueries: - """SQL queries for definitive imports (PostgreSQL schema).""" - - @staticmethod - def build_aggregated_query(db_name: str, where_clause: str) -> str: - """Build optimized query for NORMAL mode (grouped by invoice with totals).""" - return f""" - SELECT - ih.invoice_number AS C1, - COALESCE(ped.pedimento_number, '') AS C2, - TO_CHAR(ih.invoice_date, 'YYYYMMDD') AS C3, - CASE WHEN ih.status THEN 'AC' ELSE 'NA' END AS C4, - COALESCE(ped.pedimento_code, '') AS C5, - COALESCE(ped.regime, '') AS C10, - COALESCE(TO_CHAR(pd.entry_date, 'YYYYMMDD'), '') AS C11, - COALESCE(TO_CHAR(pd.end_date, 'YYYYMMDD'), TO_CHAR(pd.payment_date, 'YYYYMMDD'), '') AS C12, - COALESCE(TO_CHAR(pd.payment_date, 'YYYYMMDD'), '') AS C13, - COALESCE(log.payment_receipt_num, '') AS C14, - COALESCE(fin.exchange_rate, 0) AS C15, - COALESCE(cmp.provider_id::text, '') AS C16, - COALESCE(cmp.sold_to_id::text, '') AS C17, - COALESCE(cmp.customs_broker_id::text, '') AS C18, - COALESCE(ih.purchase_order, '') AS C31, - COALESCE(cmp.aduana, '') AS C39, - ih.id AS C35, - COALESCE( - ped_r1.year || '-' || ped_r1.customs_office || '-' || ped_r1.license || '-' || ped_r1.pedimento_number, - pro_origin.original_pedimento_year || '-' || pro_origin.original_customs_office || '-' || pro_origin.original_license || '-' || pro_origin.original_pedimento_number, - '' - ) AS C42, - COALESCE(cmp.edocument, '') AS C43, - COALESCE(cmp.vucem_operation_num, '') AS C44, - COALESCE(fin.exchange_rate, 0) AS C51, - COALESCE(TO_CHAR(ih.emission_date, 'YYYYMMDD'), '') AS C52, - COALESCE(ih.capture_user, '') AS C53, - COALESCE(ih.who_processed, '') AS C54, - COALESCE(NULLIF(CONCAT_WS(' ', log.transport_id, log.transport_num), ''), '') AS C56, - '' AS C57, - '' AS C58, - '' AS C59, - COALESCE(fin.value_me, 0) AS total_me, - COALESCE(fin.value_mn, 0) AS total_mn, - COALESCE(lf_agg.sum_value_usd, 0) AS sum_value_usd, - COALESCE(lf_agg.sum_value_mxn, 0) AS sum_value_mxn - FROM a76.invoice_header ih - LEFT JOIN a76.invoice_compliance_mx cmp ON cmp.invoice_id = ih.id - LEFT JOIN a76.invoice_financials fin ON fin.invoice_id = ih.id - LEFT JOIN a76.invoice_logistics log ON log.invoice_id = ih.id - LEFT JOIN a76.pedimentos ped ON ped.id = cmp.pedimento_id - LEFT JOIN a76.pedimento_rectification_origin pro_origin ON pro_origin.pedimento_id = ped.id - LEFT JOIN a76.pedimento_rectification_origin pro_rect ON - pro_rect.original_pedimento_year = ped.year AND - pro_rect.original_customs_office = ped.customs_office AND - pro_rect.original_license = ped.license AND - pro_rect.original_pedimento_number = ped.pedimento_number - LEFT JOIN a76.pedimentos ped_r1 ON ped_r1.id = pro_rect.pedimento_id - LEFT JOIN a76.pedimento_dates pd ON pd.pedimento_id = ped.id - LEFT JOIN ( - SELECT il.invoice_id, - SUM(CASE WHEN COALESCE(fil.is_subitem, false) THEN 0 ELSE COALESCE(lf.value_mxn, 0) END) AS sum_value_mxn, - SUM(CASE WHEN COALESCE(fil.is_subitem, false) THEN 0 ELSE COALESCE(lf.value_usd, 0) END) AS sum_value_usd - FROM a76.item_lines il - JOIN a76.item_line_financials lf ON lf.item_line_id = il.id - LEFT JOIN a24.fa_item_lines fil ON fil.id = il.id - GROUP BY il.invoice_id - ) lf_agg ON lf_agg.invoice_id = ih.id - WHERE ih.operation_type = 'imp' - AND ih.invoice_type IN ('DEF', 'EXDEF', 'MATDE') - AND {where_clause} - ORDER BY ih.invoice_number - """ - - @staticmethod - def build_main_query(db_name: str, where_clause: str) -> str: - """Build main SQL query for DETAILED mode (all partidas) from PostgreSQL for definitive imports.""" - return f""" - SELECT - ih.invoice_number AS C1, - COALESCE(ped.pedimento_number, '') AS C2, - TO_CHAR(ih.invoice_date, 'YYYYMMDD') AS C3, - CASE WHEN ih.status THEN 'AC' ELSE 'NA' END AS C4, - COALESCE(ped.pedimento_code, '') AS C5, - COALESCE(fin.value_me, 0) AS C6, - COALESCE(fin.value_mn, 0) AS C7, - COALESCE(prov.name, '') AS C8, - COALESCE(client.name, '') AS C9, - COALESCE(ped.regime, '') AS C10, - COALESCE(TO_CHAR(pd.entry_date, 'YYYYMMDD'), '') AS C11, - COALESCE(TO_CHAR(pd.end_date, 'YYYYMMDD'), TO_CHAR(pd.payment_date, 'YYYYMMDD'), '') AS C12, - COALESCE(TO_CHAR(pd.payment_date, 'YYYYMMDD'), '') AS C13, - COALESCE(log.payment_receipt_num, '') AS C14, - COALESCE(fin.exchange_rate, 0) AS C15, - COALESCE(cmp.provider_id::text, '') AS C16, - COALESCE(cmp.sold_to_id::text, '') AS C17, - COALESCE(cmp.customs_broker_id::text, '') AS C18, - '' AS C19, - COALESCE(il.class_id::text, '') AS C20, - REPLACE(REPLACE(REPLACE(REPLACE(COALESCE(ld.description_spanish, ''), CHR(44), ' '), CHR(9), ' '), CHR(10), ' '), CHR(13), ' ') AS C21, - REPLACE(REPLACE(REPLACE(REPLACE(COALESCE(ld.description_english, ''), CHR(44), ' '), CHR(9), ' '), CHR(10), ' '), CHR(13), ' ') AS C22, - COALESCE(lq.quantity, 0) AS C23, - COALESCE(um.code, '') AS C24, - COALESCE(lf.value_mxn, 0) AS C25, - COALESCE(lf.customs_value_mxn, 0) AS C26, - COALESCE(lf.value_usd, 0) AS C27, - COALESCE(lf.customs_value_usd, 0) AS C28, - COALESCE(lq.net_weight, 0) AS C29, - COALESCE(lq.gross_weight, 0) AS C30, - COALESCE(il.order, ih.purchase_order, '') AS C31, - COALESCE(lc.fraction, '') AS C32, - COALESCE(lc.fraction_type, '') AS C33, - COALESCE(lc.advalorem_numeric, 0) AS C34, - COALESCE(NULLIF(lc.sector, ''), NULLIF(fap.sector, ''), '') AS C35, - COALESCE(lf.igi_amount_usd, 0) AS C36, - COALESCE(lc.origin_country, '') AS C37, - COALESCE(cmp.aduana, '') AS C38, - ih.id AS C39, - COALESCE(il.material_type, 'P') AS C40, - COALESCE( - ped_r1.year || '-' || ped_r1.customs_office || '-' || ped_r1.license || '-' || ped_r1.pedimento_number, - pro_origin.original_pedimento_year || '-' || pro_origin.original_customs_office || '-' || pro_origin.original_license || '-' || pro_origin.original_pedimento_number, - '' - ) AS C41, - cmp.edocument AS C42, - cmp.vucem_operation_num AS C43, - COALESCE(il.line_number, 0) AS C44, - REPLACE(REPLACE(REPLACE(REPLACE(COALESCE(ld.brand, ''), CHR(44), ' '), CHR(9), ' '), CHR(10), ' '), CHR(13), ' ') AS C45, - REPLACE(REPLACE(REPLACE(REPLACE(COALESCE(ld.model, ''), CHR(44), ' '), CHR(9), ' '), CHR(10), ' '), CHR(13), ' ') AS C46, - COALESCE(cls.us_fraction, '') AS C47, - COALESCE(prt.eccn, fac.eccn_code, '') AS C48, - COALESCE(prt.part_number::text, '') AS C49, - COALESCE(fin.exchange_rate, 0) AS C50, - COALESCE(TO_CHAR(ih.emission_date, 'YYYYMMDD'), '') AS C51, - COALESCE(ih.capture_user, '') AS C52, - COALESCE(ih.who_processed, '') AS C53, - COALESCE(log.carrier_id, '') AS C54, - COALESCE(NULLIF(CONCAT_WS(' ', log.transport_id, log.transport_num), ''), '') AS C55, - '' AS C56, - COALESCE(ld.lot, '') AS C57, - '' AS C58, - 0 AS C59, - 0 AS C60 - FROM a76.invoice_header ih - LEFT JOIN a76.invoice_compliance_mx cmp ON cmp.invoice_id = ih.id - LEFT JOIN a76.invoice_financials fin ON fin.invoice_id = ih.id - LEFT JOIN a76.invoice_logistics log ON log.invoice_id = ih.id - LEFT JOIN a76.pedimentos ped ON ped.id = cmp.pedimento_id - LEFT JOIN a76.pedimento_rectification_origin pro_origin ON pro_origin.pedimento_id = ped.id - LEFT JOIN a76.pedimento_rectification_origin pro_rect ON - pro_rect.original_pedimento_year = ped.year AND - pro_rect.original_customs_office = ped.customs_office AND - pro_rect.original_license = ped.license AND - pro_rect.original_pedimento_number = ped.pedimento_number - LEFT JOIN a76.pedimentos ped_r1 ON ped_r1.id = pro_rect.pedimento_id - LEFT JOIN a76.pedimento_dates pd ON pd.pedimento_id = ped.id - LEFT JOIN a76.clients_and_providers prov ON prov.id = cmp.provider_id - LEFT JOIN a76.clients_and_providers client ON client.id = cmp.sold_to_id - LEFT JOIN a76.item_lines il ON il.invoice_id = ih.id - LEFT JOIN a76.item_line_descriptions ld ON ld.item_line_id = il.id - LEFT JOIN a76.item_line_quantities lq ON lq.item_line_id = il.id - LEFT JOIN a76.item_line_financials lf ON lf.item_line_id = il.id - LEFT JOIN a76.item_line_customs lc ON lc.item_line_id = il.id - LEFT JOIN a76.classes cls ON cls.id = il.class_id - LEFT JOIN a24.fa_classes fac ON fac.class_id = cls.id - LEFT JOIN a76.parts prt ON prt.id = il.part_number_id - LEFT JOIN a24.fa_partes fap ON fap.id = prt.id - LEFT JOIN a76.units_of_measure um ON um.id = il.unit_of_measure - WHERE {where_clause} - ORDER BY ih.invoice_number, il.line_number - """ - - @staticmethod - def build_totals_query(db_name: str) -> str: - """Build query to get totals for a definitive import invoice.""" - return f""" - SELECT - COALESCE(SUM(CASE WHEN COALESCE(fil.is_subitem, false) THEN 0 ELSE lf.value_usd END), 0), - COALESCE(SUM(CASE WHEN COALESCE(fil.is_subitem, false) THEN 0 ELSE lf.value_mxn END), 0) - FROM a76.item_line_financials lf - INNER JOIN a76.item_lines il ON il.id = lf.item_line_id - LEFT JOIN a24.fa_item_lines fil ON fil.id = il.id - WHERE il.invoice_id = :consecutivo - - """ - - @staticmethod - def build_series_query(db_name: str) -> str: - """Build query to get series information for definitive imports.""" - # TODO: QSeriesDef table not migrated to PostgreSQL yet - return """ - SELECT '' as serie, '' as modelo, '' as parte - WHERE 1=0 - """ - - @staticmethod - def build_driver_badge_query(db_name: str) -> str: - """Build query to get driver badge number for definitive imports.""" - # TODO: GConductor table not migrated to PostgreSQL yet - return """ - SELECT '' as badge - WHERE 1=0 - """ - - -class RepairImportQueries: - """SQL queries for repair imports (PostgreSQL schema). - - Note: Repair imports are NOT identified by invoice_type, but by having - cross-references (search_invoice field) that link them to export invoices. - These are regular import invoices (TEM, DEF, etc.) that were imported for repair. - """ - - @staticmethod - def build_aggregated_query(db_name: str, where_str: str, discharge_clause: str = "") -> str: - """Build optimized query for NORMAL mode (grouped by invoice with totals).""" - # Use fa_item_lines.discharge field (true = discharged, false = not discharged) - if "SiDes" in discharge_clause: - discharge_filter = "AND fil.discharge = true" - elif "NoDes" in discharge_clause: - discharge_filter = "AND fil.discharge = false" - else: - discharge_filter = "" - - return f""" - SELECT - ih.invoice_number AS C2, - COALESCE(ped.pedimento_number, '') AS C3, - TO_CHAR(ih.invoice_date, 'YYYYMMDD') AS C4, - CASE WHEN ih.status THEN 'AC' ELSE 'NA' END AS C5, - COALESCE(ped.pedimento_code, '') AS C6, - COALESCE(ped.regime, '') AS C7, - COALESCE(TO_CHAR(pd.payment_date, 'YYYYMMDD'), '') AS C9, - COALESCE(cmp.remesa::text, '') AS C10, - COALESCE(fin.exchange_rate, 0) AS C11, - COALESCE(cmp.provider_id::text, '') AS C12, - COALESCE(cmp.sold_to_id::text, '') AS C13, - COALESCE(cmp.customs_broker_id::text, '') AS C14, - COALESCE(ih.purchase_order, '') AS C24, - COALESCE(ped.customs_office, '') AS C29, - ih.id AS C30, - COALESCE(cmp.edocument, '') AS C33, - COALESCE(cmp.vucem_operation_num, '') AS C34, - COALESCE(fin.exchange_rate, 0) AS C40, - COALESCE(TO_CHAR(ih.emission_date, 'YYYYMMDD'), '') AS C41, - COALESCE(ih.capture_user, '') AS C42, - COALESCE(ih.who_processed, '') AS C43, - COALESCE(log.carrier_id, '') AS C44, - COALESCE(log.transport_num, '') AS C45, - COALESCE(ped.pedimento_code, '') AS C47, - COALESCE(fin.value_me, 0) AS total_me, - COALESCE(fin.value_mn, 0) AS total_mn, - COALESCE( - ped_r1.year || '-' || ped_r1.customs_office || '-' || ped_r1.license || '-' || ped_r1.pedimento_number, - pro_origin.original_pedimento_year || '-' || pro_origin.original_customs_office || '-' || pro_origin.original_license || '-' || pro_origin.original_pedimento_number, - '' - ) AS C48, - COALESCE(lf_agg.sum_value_usd, 0) AS sum_value_usd, - COALESCE(lf_agg.sum_value_mxn, 0) AS sum_value_mxn - FROM a76.invoice_header ih - LEFT JOIN a76.invoice_compliance_mx cmp ON cmp.invoice_id = ih.id - LEFT JOIN a76.invoice_financials fin ON fin.invoice_id = ih.id - LEFT JOIN a76.invoice_logistics log ON log.invoice_id = ih.id - LEFT JOIN a76.pedimentos ped ON ped.id = cmp.pedimento_id - LEFT JOIN a76.pedimento_rectification_origin pro_origin ON pro_origin.pedimento_id = ped.id - LEFT JOIN a76.pedimento_rectification_origin pro_rect ON - pro_rect.original_pedimento_year = ped.year AND - pro_rect.original_customs_office = ped.customs_office AND - pro_rect.original_license = ped.license AND - pro_rect.original_pedimento_number = ped.pedimento_number - LEFT JOIN a76.pedimentos ped_r1 ON ped_r1.id = pro_rect.pedimento_id - LEFT JOIN a76.pedimento_dates pd ON pd.pedimento_id = ped.id - LEFT JOIN ( - SELECT il.invoice_id, - SUM(CASE WHEN COALESCE(fil.is_subitem, false) THEN 0 ELSE COALESCE(lf.value_mxn, 0) END) AS sum_value_mxn, - SUM(CASE WHEN COALESCE(fil.is_subitem, false) THEN 0 ELSE COALESCE(lf.value_usd, 0) END) AS sum_value_usd - FROM a76.item_lines il - JOIN a76.item_line_financials lf ON lf.item_line_id = il.id - LEFT JOIN a24.fa_item_lines fil ON fil.id = il.id - WHERE 1=1 {discharge_filter} - GROUP BY il.invoice_id - ) lf_agg ON lf_agg.invoice_id = ih.id - WHERE ih.operation_type = 'imp' - AND COALESCE(cmp.is_regime_change, false) = false - AND EXISTS ( - SELECT 1 FROM a76.item_lines il2 - INNER JOIN a24.fa_item_lines fil2 ON fil2.id = il2.id - WHERE il2.invoice_id = ih.id AND fil2.search_invoice IS NOT NULL - {discharge_filter} - ) - {"AND " + where_str if where_str else ""} - ORDER BY ih.invoice_number - """ - - @staticmethod - def build_main_query(db_name: str, where_str: str, discharge_clause: str = "") -> str: - """Build main SQL query for repair import data.""" - # Use fa_item_lines.discharge field (true = discharged, false = not discharged) - if "SiDes" in discharge_clause: - discharge_filter = "AND fil.discharge = true" - elif "NoDes" in discharge_clause: - discharge_filter = "AND fil.discharge = false" - else: - discharge_filter = "" - return f""" - SELECT - il.line_number, - ih.invoice_number, - COALESCE(ped.pedimento_number, ''), - TO_CHAR(ih.invoice_date, 'YYYYMMDD'), - CASE WHEN ih.status THEN 'AC' ELSE 'NA' END, - COALESCE(ped.pedimento_code, ''), - COALESCE(ped.regime, ''), - COALESCE(TO_CHAR(pd.entry_date, 'YYYYMMDD'), ''), - COALESCE(TO_CHAR(pd.end_date, 'YYYYMMDD'), TO_CHAR(pd.payment_date, 'YYYYMMDD'), ''), - COALESCE(TO_CHAR(pd.payment_date, 'YYYYMMDD'), ''), - COALESCE(cmp.remesa::text, ''), - COALESCE(fin.exchange_rate, 0), - COALESCE(cmp.provider_id::text, ''), - COALESCE(cmp.sold_to_id::text, ''), - COALESCE(cmp.customs_broker_id::text, ''), - COALESCE(prt.part_number::text, ''), - REPLACE(REPLACE(REPLACE(REPLACE(COALESCE(ld.description_spanish, ''), CHR(44), ' '), CHR(9), ' '), CHR(10), ' '), CHR(13), ' '), - REPLACE(REPLACE(REPLACE(REPLACE(COALESCE(ld.description_english, ''), CHR(44), ' '), CHR(9), ' '), CHR(10), ' '), CHR(13), ' '), - COALESCE(lq.quantity, 0), - COALESCE(il.unit_of_measure, 0), - COALESCE(lf.value_mxn, 0), - COALESCE(lf.value_usd, 0), - COALESCE(lq.net_weight, 0), - COALESCE(lq.gross_weight, 0), - COALESCE(il.order, ih.purchase_order, ''), - COALESCE(lc.fraction, ''), - '', - COALESCE(NULLIF(lc.sector, ''), NULLIF(fap.sector, ''), ''), - COALESCE(lc.origin_country, ''), - COALESCE(ped.customs_office, ''), - ih.id, - COALESCE(il.material_type, 'P'), - COALESCE( - ped_r1.year || '-' || ped_r1.customs_office || '-' || ped_r1.license || '-' || ped_r1.pedimento_number, - pro_origin.original_pedimento_year || '-' || pro_origin.original_customs_office || '-' || pro_origin.original_license || '-' || pro_origin.original_pedimento_number, - '' - ), - COALESCE(cmp.edocument, ''), - COALESCE(cmp.vucem_operation_num, ''), - REPLACE(REPLACE(REPLACE(REPLACE(COALESCE(ld.brand, ''), CHR(44), ' '), CHR(9), ' '), CHR(10), ' '), CHR(13), ' '), - REPLACE(REPLACE(REPLACE(REPLACE(COALESCE(ld.model, ''), CHR(44), ' '), CHR(9), ' '), CHR(10), ' '), CHR(13), ' '), - COALESCE(lc.american_fraction, ''), - COALESCE(prt.eccn, fac.eccn_code, ''), - COALESCE(fin.exchange_rate, 0), - COALESCE(TO_CHAR(ih.emission_date, 'YYYYMMDD'), ''), - COALESCE(ih.capture_user, ''), - COALESCE(ih.who_processed, ''), - COALESCE(log.carrier_id, ''), - COALESCE(log.transport_num, ''), - '', - COALESCE(ped.pedimento_code, '') - FROM a76.invoice_header ih - LEFT JOIN a76.invoice_compliance_mx cmp ON cmp.invoice_id = ih.id - LEFT JOIN a76.invoice_financials fin ON fin.invoice_id = ih.id - LEFT JOIN a76.invoice_logistics log ON log.invoice_id = ih.id - LEFT JOIN a76.pedimentos ped ON ped.id = cmp.pedimento_id - LEFT JOIN a76.pedimento_rectification_origin pro_origin ON pro_origin.pedimento_id = ped.id - LEFT JOIN a76.pedimento_rectification_origin pro_rect ON - pro_rect.original_pedimento_year = ped.year AND - pro_rect.original_customs_office = ped.customs_office AND - pro_rect.original_license = ped.license AND - pro_rect.original_pedimento_number = ped.pedimento_number - LEFT JOIN a76.pedimentos ped_r1 ON ped_r1.id = pro_rect.pedimento_id - LEFT JOIN a76.pedimento_dates pd ON pd.pedimento_id = ped.id - LEFT JOIN a76.item_lines il ON il.invoice_id = ih.id - LEFT JOIN a76.item_line_descriptions ld ON ld.item_line_id = il.id - LEFT JOIN a76.item_line_quantities lq ON lq.item_line_id = il.id - LEFT JOIN a76.item_line_financials lf ON lf.item_line_id = il.id - LEFT JOIN a76.item_line_customs lc ON lc.item_line_id = il.id - LEFT JOIN a24.fa_item_lines fil ON fil.id = il.id - LEFT JOIN a76.classes cls ON cls.id = il.class_id - LEFT JOIN a24.fa_classes fac ON fac.class_id = cls.id - LEFT JOIN a76.parts prt ON prt.id = il.part_number_id - LEFT JOIN a24.fa_partes fap ON fap.id = prt.id - LEFT JOIN a76.units_of_measure um ON um.id = il.unit_of_measure - WHERE ih.operation_type = 'imp' - AND COALESCE(cmp.is_regime_change, false) = false - AND fil.search_invoice IS NOT NULL - {"AND " + where_str if where_str else ""} - {discharge_filter} - ORDER BY ih.invoice_number, il.line_number - """ - - @staticmethod - def build_totals_query(db_name: str, discharge_clause: str = "") -> str: - """Build query to get totals for a repair import invoice.""" - # Use fa_item_lines.discharge field (true = discharged, false = not discharged) - if "SiDes" in discharge_clause: - discharge_filter = "AND fil.discharge = true" - elif "NoDes" in discharge_clause: - discharge_filter = "AND fil.discharge = false" - else: - discharge_filter = "" - return f""" - SELECT - COALESCE(SUM(CASE WHEN COALESCE(fil.is_subitem, false) THEN 0 ELSE lf.value_usd END), 0), - COALESCE(SUM(CASE WHEN COALESCE(fil.is_subitem, false) THEN 0 ELSE lf.value_mxn END), 0) - FROM a76.item_line_financials lf - INNER JOIN a76.item_lines il ON il.id = lf.item_line_id - LEFT JOIN a24.fa_item_lines fil ON fil.id = il.id - WHERE il.invoice_id = :consecutivo - - {discharge_filter} - """ - - @staticmethod - def build_series_query(db_name: str) -> str: - """Build query to get series information for repair imports.""" - # TODO: QSeriesImpoRep table not migrated to PostgreSQL yet - return """ - SELECT '' as serie, '' as modelo, '' as parte - WHERE 1=0 - """ - - @staticmethod - def build_driver_badge_query(db_name: str) -> str: - """Build query to get driver badge number for repair imports.""" - # TODO: GConductor table not migrated to PostgreSQL yet - return """ - SELECT '' as badge - WHERE 1=0 - """ - - -class ExportQueries: - """SQL queries for exports (PostgreSQL schema).""" - - @staticmethod - def build_aggregated_query(db_name: str, where_clause: str) -> str: - """ - Build optimized query for NORMAL mode (grouped by invoice with totals). - - Args: - db_name: Database name (not used in PostgreSQL version) - where_clause: Additional WHERE conditions (without WHERE keyword) - """ - return f""" - SELECT - ih.invoice_number AS C1, - COALESCE(ped.pedimento_number, '') AS C2, - TO_CHAR(ih.invoice_date, 'YYYYMMDD') AS C3, - CASE WHEN ih.status THEN 'AC' ELSE 'NA' END AS C6, - COALESCE(ped.pedimento_code, '') AS C7, - COALESCE(ped.regime, '') AS C8, - COALESCE(TO_CHAR(pd.end_date, 'YYYYMMDD'), TO_CHAR(pd.payment_date, 'YYYYMMDD'), '') AS C10, - COALESCE(TO_CHAR(pd.payment_date, 'YYYYMMDD'), '') AS C11, - COALESCE(log.payment_receipt_num, '') AS C12, - COALESCE(cmp.provider_id::text, '') AS C14, - COALESCE(cmp.sold_to_id::text, '') AS C15, - COALESCE(cmp.customs_broker_id::text, '') AS C16, - COALESCE(ih.purchase_order, '') AS C27, - COALESCE(cmp.aduana, '') AS C33, - COALESCE(ih.invoice_type, '') AS C34, - ih.id AS C35, - COALESCE(cmp.edocument, '') AS C40, - COALESCE(cmp.vucem_operation_num, '') AS C41, - COALESCE(fin.exchange_rate, 0) AS C48, - COALESCE(TO_CHAR(ih.emission_date, 'YYYYMMDD'), '') AS C49, - COALESCE(ih.capture_user, '') AS C50, - COALESCE(ih.who_processed, '') AS C51, - COALESCE(NULLIF(CONCAT_WS(' ', log.transport_id, log.transport_num), ''), '') AS C53, - COALESCE(fin.value_me, 0) AS total_me, - COALESCE(fin.value_mn, 0) AS total_mn, - COALESCE( - ped_r1.year || '-' || ped_r1.customs_office || '-' || ped_r1.license || '-' || ped_r1.pedimento_number, - pro_origin.original_pedimento_year || '-' || pro_origin.original_customs_office || '-' || pro_origin.original_license || '-' || pro_origin.original_pedimento_number, - '' - ) AS C54, - COALESCE(lf_agg.sum_value_usd, 0) AS sum_value_usd, - COALESCE(lf_agg.sum_value_mxn, 0) AS sum_value_mxn - FROM a76.invoice_header ih - LEFT JOIN a76.invoice_compliance_mx cmp ON cmp.invoice_id = ih.id - LEFT JOIN a76.invoice_financials fin ON fin.invoice_id = ih.id - LEFT JOIN a76.invoice_logistics log ON log.invoice_id = ih.id - LEFT JOIN a76.pedimentos ped ON ped.id = cmp.pedimento_id - LEFT JOIN a76.pedimento_rectification_origin pro_origin ON pro_origin.pedimento_id = ped.id - LEFT JOIN a76.pedimento_rectification_origin pro_rect ON - pro_rect.original_pedimento_year = ped.year AND - pro_rect.original_customs_office = ped.customs_office AND - pro_rect.original_license = ped.license AND - pro_rect.original_pedimento_number = ped.pedimento_number - LEFT JOIN a76.pedimentos ped_r1 ON ped_r1.id = pro_rect.pedimento_id - LEFT JOIN a76.pedimento_dates pd ON pd.pedimento_id = ped.id - LEFT JOIN ( - SELECT il.invoice_id, - SUM(CASE WHEN COALESCE(fil.is_subitem, false) THEN 0 ELSE COALESCE(lf.value_mxn, 0) END) AS sum_value_mxn, - SUM(CASE WHEN COALESCE(fil.is_subitem, false) THEN 0 ELSE COALESCE(lf.value_usd, 0) END) AS sum_value_usd - FROM a76.item_lines il - JOIN a76.item_line_financials lf ON lf.item_line_id = il.id - LEFT JOIN a24.fa_item_lines fil ON fil.id = il.id - GROUP BY il.invoice_id - ) lf_agg ON lf_agg.invoice_id = ih.id - WHERE {where_clause} - ORDER BY ih.invoice_number - """ - - @staticmethod - def build_main_query(db_name: str, where_clause: str) -> str: - return f""" - SELECT - ih.invoice_number AS C1, -- [0] - ped.pedimento_number AS C2, -- [1] - ih.invoice_date AS C3, -- [2] - '' AS C4, -- [3] - ped.pedimento_code AS C5, -- [4] - ped.regime AS C6, -- [5] - pd.entry_date AS C7, -- [6] - COALESCE(pd.end_date, pd.payment_date) AS C8, -- [7] - pd.payment_date AS C9, -- [8] - log.payment_receipt_num AS C12, -- [11] - '' AS C13, -- [12] - cmp.provider_id AS C14, -- [13] - cmp.sold_to_id AS C15, -- [14] - cmp.customs_broker_id AS C16, -- [15] - '' AS C17, -- [16] - COALESCE(prt.part_number::text, '') AS C18, -- [17] - REPLACE(REPLACE(REPLACE(REPLACE(COALESCE(ld.description_spanish, ''), CHR(44), ' '), CHR(9), ' '), CHR(10), ' '), CHR(13), ' ') AS C19, -- [18] - REPLACE(REPLACE(REPLACE(REPLACE(COALESCE(ld.description_english, ''), CHR(44), ' '), CHR(9), ' '), CHR(10), ' '), CHR(13), ' ') AS C20, -- [19] - lq.quantity AS C21, -- [20] - um.code AS C22, -- [21] - '' AS C23, -- [22] - '' AS C24, -- [23] - COALESCE(lq.net_weight, 0) AS C25, -- [24] - COALESCE(lq.gross_weight, 0) AS C26, -- [25] - COALESCE(il.order, ih.purchase_order, '') AS C27, -- [26] - COALESCE(lc.fraction, '') AS C28, -- [27] - COALESCE(lc.fraction_type, '') AS C29, -- [28] - COALESCE(lc.advalorem_numeric, 0) AS C30, -- [29] - COALESCE(NULLIF(lc.sector, ''), NULLIF(fap.sector, ''), '') AS C31, -- [30] - COALESCE(lc.origin_country, '') AS C32, -- [31] - cmp.aduana AS C33, -- [32] - ih.invoice_type AS C34, -- [33] - ih.id AS C35, -- [34] - lf.value_mxn AS C36, -- [35] - lf.value_usd AS C37, -- [36] - il.material_type AS C38, -- [37] - COALESCE( - ped_r1.year || '-' || ped_r1.customs_office || '-' || ped_r1.license || '-' || ped_r1.pedimento_number, - pro_origin.original_pedimento_year || '-' || pro_origin.original_customs_office || '-' || pro_origin.original_license || '-' || pro_origin.original_pedimento_number, - '' - ) AS C39, -- [38] rectification_id - cmp.edocument AS C40, -- [39] - cmp.vucem_operation_num AS C41, -- [40] - il.line_number AS C42, -- [41] - REPLACE(REPLACE(REPLACE(REPLACE(COALESCE(ld.brand, ''), CHR(44), ' '), CHR(9), ' '), CHR(10), ' '), CHR(13), ' ') AS C43, -- [42] - REPLACE(REPLACE(REPLACE(REPLACE(COALESCE(ld.model, ''), CHR(44), ' '), CHR(9), ' '), CHR(10), ' '), CHR(13), ' ') AS C44, -- [43] - COALESCE(cls.us_fraction, '') AS C45, -- [44] - COALESCE(prt.eccn, fac.eccn_code, '') AS C46, -- [45] - prt.id AS C47, -- [46] - fin.exchange_rate AS C48, -- [47] - ih.emission_date AS C49, -- [48] - ih.capture_user AS C50, -- [49] - ih.who_processed AS C51, -- [50] - '' AS C52, -- [51] - COALESCE(NULLIF(CONCAT_WS(' ', log.transport_id, log.transport_num), ''), '') AS C53, -- [52] NumCaja - '' AS C54, -- [53] Pedimento18 - COALESCE(ld.lot, '') AS C55, -- [54] Lote - '' AS C56, -- [55] TipoPedimentoTransporte - '' AS C57, -- [56] - '' AS C58, -- [57] - '' AS C59, -- [58] - '' AS C60 -- [59] Relleno final - FROM a76.invoice_header ih - LEFT JOIN a76.invoice_compliance_mx cmp ON cmp.invoice_id = ih.id - LEFT JOIN a76.invoice_financials fin ON fin.invoice_id = ih.id - LEFT JOIN a76.invoice_logistics log ON log.invoice_id = ih.id - LEFT JOIN a76.pedimentos ped ON ped.id = cmp.pedimento_id - LEFT JOIN a76.pedimento_rectification_origin pro_origin ON pro_origin.pedimento_id = ped.id - LEFT JOIN a76.pedimento_rectification_origin pro_rect ON - pro_rect.original_pedimento_year = ped.year AND - pro_rect.original_customs_office = ped.customs_office AND - pro_rect.original_license = ped.license AND - pro_rect.original_pedimento_number = ped.pedimento_number - LEFT JOIN a76.pedimentos ped_r1 ON ped_r1.id = pro_rect.pedimento_id - LEFT JOIN a76.pedimento_dates pd ON pd.pedimento_id = ped.id - LEFT JOIN a76.item_lines il ON il.invoice_id = ih.id - LEFT JOIN a76.item_line_descriptions ld ON ld.item_line_id = il.id - LEFT JOIN a76.item_line_quantities lq ON lq.item_line_id = il.id - LEFT JOIN a76.item_line_financials lf ON lf.item_line_id = il.id - LEFT JOIN a76.item_line_customs lc ON lc.item_line_id = il.id - LEFT JOIN a76.classes cls ON cls.id = il.class_id - LEFT JOIN a24.fa_classes fac ON fac.class_id = cls.id - LEFT JOIN a76.parts prt ON prt.id = il.part_number_id - LEFT JOIN a24.fa_partes fap ON fap.id = prt.id - LEFT JOIN a76.units_of_measure um ON um.id = il.unit_of_measure - WHERE {where_clause} - ORDER BY ih.invoice_number, il.line_number - """ - - @staticmethod - def build_totals_query(db_name: str, discharge_clause: str = "") -> str: - """Build query to get totals for an export invoice. - - Only sums partidas where is_subitem is false (main partidas, not sub-items). - """ - # Use fa_item_lines.discharge field (true = discharged, false = not discharged) - if "SiDes" in discharge_clause: - discharge_filter = "AND fil.discharge = true" - elif "NoDes" in discharge_clause: - discharge_filter = "AND fil.discharge = false" - else: - discharge_filter = "" - return f""" - SELECT - COALESCE(SUM(CASE WHEN COALESCE(fil.is_subitem, false) THEN 0 ELSE lf.value_usd END), 0), - COALESCE(SUM(CASE WHEN COALESCE(fil.is_subitem, false) THEN 0 ELSE lf.value_mxn END), 0) - FROM a76.item_line_financials lf - INNER JOIN a76.item_lines il ON il.id = lf.item_line_id - LEFT JOIN a24.fa_item_lines fil ON fil.id = il.id - WHERE il.invoice_id = :consecutivo - {discharge_filter} - """ - - @staticmethod - def build_series_query(db_name: str) -> str: - """Build query to get series information for exports.""" - # TODO: QSeriesExpo table not migrated to PostgreSQL yet - return """ - SELECT '' as serie, '' as modelo, '' as parte - WHERE 1=0 - """ - - @staticmethod - def build_driver_badge_query(db_name: str) -> str: - """Build query to get driver badge number for exports.""" - # TODO: GConductor table not migrated to PostgreSQL yet - return """ - SELECT '' as badge - WHERE 1=0 - """ - - -class ExportRepairQueries: - """SQL queries for export repairs (PostgreSQL schema).""" - - @staticmethod - def build_aggregated_query(db_name: str, where_str: str, discharge_clause: str = "") -> str: - """Build optimized query for NORMAL mode (grouped by invoice with totals).""" - # Use fa_item_lines.discharge field (true = discharged, false = not discharged) - if "SiDes" in discharge_clause: - discharge_filter = "AND fil.discharge = true" - elif "NoDes" in discharge_clause: - discharge_filter = "AND fil.discharge = false" - else: - discharge_filter = "" - - return f""" - SELECT - ih.invoice_number AS C1, - COALESCE(ped.pedimento_number, '') AS C2, - TO_CHAR(ih.invoice_date, 'YYYYMMDD') AS C3, - CASE WHEN ih.status THEN 'AC' ELSE 'NA' END AS C6, - COALESCE(ped.pedimento_code, '') AS C7, - COALESCE(ped.regime, '') AS C8, - COALESCE(TO_CHAR(pd.payment_date, 'YYYYMMDD'), '') AS C11, - COALESCE(cmp.remesa::text, '') AS C12, - COALESCE(fin.exchange_rate, 0) AS C13, - COALESCE(cmp.provider_id::text, '') AS C14, - COALESCE(cmp.sold_to_id::text, '') AS C15, - COALESCE(cmp.customs_broker_id::text, '') AS C16, - COALESCE(ih.purchase_order, '') AS C27, - COALESCE(ped.customs_office, '') AS C33, - COALESCE(ih.document_type, '') AS C34, - ih.id AS C35, - COALESCE(cmp.edocument, '') AS C40, - COALESCE(cmp.vucem_operation_num, '') AS C41, - COALESCE(fin.exchange_rate, 0) AS C48, - COALESCE(TO_CHAR(ih.invoice_date, 'YYYYMMDD'), '') AS C49, - COALESCE(ih.capture_user, '') AS C50, - COALESCE(ih.who_processed, '') AS C51, - COALESCE(log.carrier_id, '') AS C52, - COALESCE(NULLIF(CONCAT_WS(' ', log.transport_id, log.transport_num), ''), '') AS C53, - COALESCE(fin.value_me, 0) AS total_me, - COALESCE(fin.value_mn, 0) AS total_mn, - COALESCE( - ped_r1.year || '-' || ped_r1.customs_office || '-' || ped_r1.license || '-' || ped_r1.pedimento_number, - pro_origin.original_pedimento_year || '-' || pro_origin.original_customs_office || '-' || pro_origin.original_license || '-' || pro_origin.original_pedimento_number, - '' - ) AS C54, - COALESCE(lf_agg.sum_value_usd, 0) AS sum_value_usd, - COALESCE(lf_agg.sum_value_mxn, 0) AS sum_value_mxn - FROM a76.invoice_header ih - LEFT JOIN a76.invoice_compliance_mx cmp ON cmp.invoice_id = ih.id - LEFT JOIN a76.invoice_financials fin ON fin.invoice_id = ih.id - LEFT JOIN a76.invoice_logistics log ON log.invoice_id = ih.id - LEFT JOIN a76.pedimentos ped ON ped.id = cmp.pedimento_id - LEFT JOIN a76.pedimento_rectification_origin pro_origin ON pro_origin.pedimento_id = ped.id - LEFT JOIN a76.pedimento_rectification_origin pro_rect ON - pro_rect.original_pedimento_year = ped.year AND - pro_rect.original_customs_office = ped.customs_office AND - pro_rect.original_license = ped.license AND - pro_rect.original_pedimento_number = ped.pedimento_number - LEFT JOIN a76.pedimentos ped_r1 ON ped_r1.id = pro_rect.pedimento_id - LEFT JOIN a76.pedimento_dates pd ON pd.pedimento_id = ped.id - LEFT JOIN ( - SELECT il.invoice_id, - SUM(CASE WHEN COALESCE(fil.is_subitem, false) THEN 0 ELSE COALESCE(lf.value_mxn, 0) END) AS sum_value_mxn, - SUM(CASE WHEN COALESCE(fil.is_subitem, false) THEN 0 ELSE COALESCE(lf.value_usd, 0) END) AS sum_value_usd - FROM a76.item_lines il - JOIN a76.item_line_financials lf ON lf.item_line_id = il.id - LEFT JOIN a24.fa_item_lines fil ON fil.id = il.id - WHERE 1=1 {discharge_filter} - GROUP BY il.invoice_id - ) lf_agg ON lf_agg.invoice_id = ih.id - WHERE ih.operation_type = 'exp' - AND ih.invoice_type = 'REPAR' - {"AND " + where_str if where_str else ""} - ORDER BY ih.invoice_number - """ - - @staticmethod - def build_main_query(db_name: str, where_str: str) -> str: - """Build main SQL query for export repair data.""" - return f""" - SELECT - ih.invoice_number AS C1, - COALESCE(ped.pedimento_number, '') AS C2, - TO_CHAR(ih.invoice_date, 'YYYYMMDD') AS C3, - COALESCE(fin.value_me, 0) AS C4, - COALESCE(fin.value_mn, 0) AS C5, - CASE WHEN ih.status THEN 'AC' ELSE 'NA' END AS C6, - COALESCE(ped.pedimento_code, '') AS C7, - COALESCE(ped.regime, '') AS C8, - COALESCE(TO_CHAR(pd.entry_date, 'YYYYMMDD'), '') AS C9, - COALESCE(TO_CHAR(pd.end_date, 'YYYYMMDD'), TO_CHAR(pd.payment_date, 'YYYYMMDD'), '') AS C10, - COALESCE(TO_CHAR(pd.payment_date, 'YYYYMMDD'), '') AS C11, - COALESCE(cmp.remesa::text, '') AS C12, - COALESCE(fin.exchange_rate, 0) AS C13, - COALESCE(cmp.provider_id::text, '') AS C14, - COALESCE(cmp.sold_to_id::text, '') AS C15, - COALESCE(cmp.customs_broker_id::text, '') AS C16, - '' AS C17, - COALESCE(cls.class_code, '') AS C18, - REPLACE(REPLACE(REPLACE(REPLACE(COALESCE(ld.description_spanish, ''), CHR(44), ' '), CHR(9), ' '), CHR(10), ' '), CHR(13), ' ') AS C19, - REPLACE(REPLACE(REPLACE(REPLACE(COALESCE(cls.description_en, ''), CHR(44), ' '), CHR(9), ' '), CHR(10), ' '), CHR(13), ' ') AS C20, - COALESCE(lq.quantity, 0) AS C21, - COALESCE(il.unit_of_measure, 0) AS C22, - COALESCE(lf.customs_value_mxn, 0) AS C23, - COALESCE(lf.customs_value_usd, 0) AS C24, - COALESCE(lq.net_weight, 0) AS C25, - COALESCE(lq.gross_weight, 0) AS C26, - COALESCE(il.order, ih.purchase_order, '') AS C27, - COALESCE(lc.fraction, '') AS C28, - COALESCE(lc.fraction_type, '') AS C29, - COALESCE(lc.advalorem_numeric, 0) AS C30, - COALESCE(NULLIF(lc.sector, ''), NULLIF(fap.sector, ''), '') AS C31, - COALESCE(lc.origin_country, '') AS C32, - COALESCE(ped.customs_office, '') AS C33, - COALESCE(ih.document_type, '') AS C34, - ih.id AS C35, - COALESCE(lf.value_mxn, 0) AS C36, - COALESCE(lf.value_usd, 0) AS C37, - COALESCE(il.material_type, 'P') AS C38, - COALESCE( - ped_r1.year || '-' || ped_r1.customs_office || '-' || ped_r1.license || '-' || ped_r1.pedimento_number, - pro_origin.original_pedimento_year || '-' || pro_origin.original_customs_office || '-' || pro_origin.original_license || '-' || pro_origin.original_pedimento_number, - '' - ) AS C39, - COALESCE(cmp.edocument, '') AS C40, - COALESCE(cmp.vucem_operation_num, '') AS C41, - COALESCE(il.line_number, 0) AS C42, - REPLACE(REPLACE(REPLACE(REPLACE(COALESCE(ld.brand, ''), CHR(44), ' '), CHR(9), ' '), CHR(10), ' '), CHR(13), ' ') AS C43, - REPLACE(REPLACE(REPLACE(REPLACE(COALESCE(ld.model, ''), CHR(44), ' '), CHR(9), ' '), CHR(10), ' '), CHR(13), ' ') AS C44, - COALESCE(cls.us_fraction, '') AS C45, - COALESCE(prt.eccn, fac.eccn_code, '') AS C46, - COALESCE(prt.part_number::text, '') AS C47, - COALESCE(fin.exchange_rate, 0) AS C48, - COALESCE(TO_CHAR(ih.invoice_date, 'YYYYMMDD'), '') AS C49, - COALESCE(ih.capture_user, '') AS C50, - COALESCE(ih.who_processed, '') AS C51, - COALESCE(log.carrier_id, '') AS C52, - COALESCE(NULLIF(CONCAT_WS(' ', log.transport_id, log.transport_num), ''), '') AS C53, - '' AS C54, - COALESCE(ld.lot, '') AS C55, - '' AS C56, - '' AS C57, - '' AS C58, - '' AS C59 - FROM a76.invoice_header ih - LEFT JOIN a76.invoice_compliance_mx cmp ON cmp.invoice_id = ih.id - LEFT JOIN a76.invoice_financials fin ON fin.invoice_id = ih.id - LEFT JOIN a76.invoice_logistics log ON log.invoice_id = ih.id - LEFT JOIN a76.pedimentos ped ON ped.id = cmp.pedimento_id - LEFT JOIN a76.pedimento_rectification_origin pro_origin ON pro_origin.pedimento_id = ped.id - LEFT JOIN a76.pedimento_rectification_origin pro_rect ON - pro_rect.original_pedimento_year = ped.year AND - pro_rect.original_customs_office = ped.customs_office AND - pro_rect.original_license = ped.license AND - pro_rect.original_pedimento_number = ped.pedimento_number - LEFT JOIN a76.pedimentos ped_r1 ON ped_r1.id = pro_rect.pedimento_id - LEFT JOIN a76.pedimento_dates pd ON pd.pedimento_id = ped.id - LEFT JOIN a76.item_lines il ON il.invoice_id = ih.id - LEFT JOIN a76.item_line_descriptions ld ON ld.item_line_id = il.id - LEFT JOIN a76.item_line_quantities lq ON lq.item_line_id = il.id - LEFT JOIN a76.item_line_financials lf ON lf.item_line_id = il.id - LEFT JOIN a76.item_line_customs lc ON lc.item_line_id = il.id - LEFT JOIN a76.classes cls ON cls.id = il.class_id - LEFT JOIN a24.fa_classes fac ON fac.class_id = cls.id - LEFT JOIN a76.parts prt ON prt.id = il.part_number_id - LEFT JOIN a24.fa_partes fap ON fap.id = prt.id - LEFT JOIN a76.units_of_measure um ON um.id = il.unit_of_measure - WHERE UPPER(ih.operation_type) IN ('EXP', 'TRA', 'RET') - AND UPPER(ih.invoice_type) IN ('DEF', 'REPAR', 'EXDEF', 'MATDE') - AND {where_str} - ORDER BY ih.invoice_number, il.line_number - """ - - @staticmethod - def build_totals_query(db_name: str, discharge_clause: str = "") -> str: - """Build query to get totals for an export repair invoice.""" - # Use fa_item_lines.discharge field (true = discharged, false = not discharged) - if "SiDes" in discharge_clause: - discharge_filter = "AND fil.discharge = true" - elif "NoDes" in discharge_clause: - discharge_filter = "AND fil.discharge = false" - else: - discharge_filter = "" - return f""" - SELECT - COALESCE(SUM(CASE WHEN COALESCE(fil.is_subitem, false) THEN 0 ELSE lf.value_usd END), 0), - COALESCE(SUM(CASE WHEN COALESCE(fil.is_subitem, false) THEN 0 ELSE lf.value_mxn END), 0) - FROM a76.item_line_financials lf - INNER JOIN a76.item_lines il ON il.id = lf.item_line_id - LEFT JOIN a24.fa_item_lines fil ON il.id = lf.item_line_id - WHERE il.invoice_id = :consecutivo - - {discharge_filter} - """ - - @staticmethod - def build_series_query(db_name: str) -> str: - """Build query to get series information for export repairs.""" - # TODO: QSeriesExpoRep table not migrated to PostgreSQL yet - return """ - SELECT '' as serie, '' as modelo, '' as parte - WHERE 1=0 - """ - - @staticmethod - def build_driver_badge_query(db_name: str) -> str: - """Build query to get driver badge number for export repairs.""" - # TODO: GConductor table not migrated to PostgreSQL yet - return """ - SELECT '' as badge - WHERE 1=0 - """ diff --git a/backend/api/v1/modules/a76/reports/movements/invoices/services/repair.py b/backend/api/v1/modules/a76/reports/movements/invoices/services/repair.py deleted file mode 100644 index e4143f6b..00000000 --- a/backend/api/v1/modules/a76/reports/movements/invoices/services/repair.py +++ /dev/null @@ -1,400 +0,0 @@ -""" -Repair import service - handles IMPRE movements. -""" - -import logging -from datetime import datetime -from sqlalchemy import text -from sqlalchemy.orm import Session -from typing import List, Optional, TYPE_CHECKING - -if TYPE_CHECKING: - from ..schemas import ImportRepairFilter, MovementItem, MovementItemDetailed - -from .base import ConfigHelper, StringHelper -from .database_helpers import DatabaseHelper -from .exchange_rate import ExchangeRateCalculator -from .query_builders import RepairImportQueries - -logger = logging.getLogger(__name__) - - -def parse_yyyymmdd_date(date_str: str) -> Optional[datetime]: - """Parse date string in YYYYMMDD format to datetime.""" - if not date_str or date_str == '': - return None - try: - return datetime.strptime(date_str, '%Y%m%d') - except (ValueError, TypeError): - return None - - -class RepairImportService: - """Service for handling repair import movements (IMPRE).""" - - def get_movements( - self, - db: Session, - filters: "ImportRepairFilter" - ) -> List["MovementItem"]: - """ - Get repair import movements (normal mode - grouped by invoice). - - Args: - db: Database session - filters: Filter criteria - - Returns: - List of movement items grouped by invoice - """ - from ..schemas import MovementItem - - try: - logger.info(f"Fetching repair import movements with filters: {filters.model_dump()}") - - # Get MetTrans configuration - met_trans = ConfigHelper.get_met_trans_config() - - # Build WHERE clause - where_clause = self._build_where_clause(filters) - - # Execute optimized aggregated query for NORMAL mode - sql = text(RepairImportQueries.build_aggregated_query( - filters.database_name, - where_clause, - filters.discharge_filter.value - )) - results = db.execute(sql).fetchall() - logger.info(f"Found {len(results)} repair import invoices") - - movements = [] - - for row in results: - factura = row[0] # C2 - FacturaImpoRep - estatus = row[3] # C5 - Estatus (AC o NA) - - # Filtrar facturas según include_cancelled - # Si include_cancelled=False, solo mostrar AC (status=true) - # Si include_cancelled=True, mostrar todas (AC y NA) - if not filters.include_cancelled and estatus != 'AC': - continue - - consecutivo = row[14] # C30 - Consecutivo - - # Helper to safely convert to float - def to_float(val): - if val is None or val == '': - return 0.0 - try: - return float(val) - except (ValueError, TypeError): - return 0.0 - - # Totals come directly from GROUP BY query (no N+1 problem) - total_me = to_float(row[24]) # total_me from SUM aggregation - total_mn = to_float(row[25]) # total_mn from SUM aggregation - sum_value_usd = to_float(row[27]) - sum_value_mxn = to_float(row[28]) - - total_me = sum_value_usd if sum_value_usd > 0 else total_me - total_mn = sum_value_mxn if sum_value_mxn > 0 else total_mn - - # Calculate exchange rate and value - valor_comercial, tipo_cambio = ExchangeRateCalculator.calculate_for_aggregated( - db=db, - db_name=filters.database_name, - valor_me=total_me, - valor_mn=total_mn, - tipo_cambio_db=to_float(row[17]), # C40 - TipoCambio - fecha_pago=row[6], # C9 - Fecha_Pago - fecha_inicio='', # Not available in aggregated query - tipo_pedimento=row[23], # C47 - pedimento_code (used as tipo_pedimento) - currency_type=filters.currency_type.value, - exchange_rate_type=filters.exchange_rate_type.value, - is_shelter=filters.is_shelter, - use_transport_method=False, - met_trans=met_trans - ) - - # Get pedimento rectification - pedimento_r1 = DatabaseHelper.get_rectification_pedimento( - db, - row[1], # C3 - PedimentoImpoRep - row[26], # C48 - PedRectifica - filters.is_shelter - ) - - # Get driver badge - num_gaf_uni = DatabaseHelper.get_driver_badge( - db, filters.database_name, factura - ) - - # Build movement item - movement = MovementItem( - Factura=factura, - Pedimento=row[1], # C3 - PedimentoImpoRep - FechaFactura=parse_yyyymmdd_date(row[2]), # C4 - FechaFactura - Estatus=row[3], # C5 - Estatus - ClavePed=row[4], # C6 - ClavePed - TipoMovTemDef='IMPRE', - EsCambioRegimen='N', - ValorMPTemp=valor_comercial, - ValorComercialMN=valor_comercial, - TipoCambio=tipo_cambio, - ValorAgre=0.0, - TipoExpo='', - PedimentoR1=pedimento_r1, - EDocument=row[15], # C33 - EDocument - NumOperacionVU=row[16], # C34 - NumOperacionVU - BaseDeDatos=filters.database_name, - NumGafUni=num_gaf_uni, - UsuarioCap=row[19], # C42 - UsuarioCap - UsuarioAcr=row[20], # C43 - UsuarioAct - Fecha_Pago=parse_yyyymmdd_date(row[6]), # C9 - Fecha_Pago - NumCaja=row[22], # C45 - Transport num - Pedimento18='', # Not in aggregated query - AduanaCru=row[13], # C29 - customs_office - Lote='' # Not in aggregated query - ) - - movements.append(movement) - - logger.info(f"Successfully processed {len(movements)} repair import movements") - return movements - - except Exception as e: - logger.error(f"Error fetching repair import movements: {e}", exc_info=True) - raise - - def get_movements_detailed( - self, - db: Session, - filters: "ImportRepairFilter" - ) -> List["MovementItemDetailed"]: - """ - Get repair import movements (detailed mode - line by line). - - - Args: - db: Database session - filters: Filter criteria - - Returns: - List of detailed movement items (one per partida) - """ - from ..schemas import MovementItemDetailed - - try: - logger.info(f"Fetching detailed repair import movements with filters: {filters.model_dump()}") - - # Get MetTrans configuration - met_trans = ConfigHelper.get_met_trans_config() - - # Build WHERE clause with discharge filter - where_clause = self._build_where_clause(filters) - - # Add discharge filter to WHERE clause - discharge_clause = "" - if filters.discharge_filter.value == "SiDes": - discharge_clause = " AND RepPim.Descarga = 1" - elif filters.discharge_filter.value == "NoDes": - discharge_clause = " AND RepPim.Descarga = 0" - - where_with_discharge = where_clause + discharge_clause - - # Execute main query - sql = text(RepairImportQueries.build_main_query(filters.database_name, where_with_discharge)) - results = db.execute(sql).fetchall() - logger.info(f"Found {len(results)} detailed repair import partidas") - - movements = [] - - for row in results: - # Skip cancelled if not included - if not filters.include_cancelled and row[4] != 'AC': # C5 - Estatus - continue - - # Get all detailed information - proveedor_info = DatabaseHelper.get_client_info( - db, filters.database_name, row[11], is_supplier=True - ) - vendido_info = DatabaseHelper.get_client_info( - db, filters.database_name, row[12], is_supplier=False - ) - agente_info = DatabaseHelper.get_customs_agent_info( - db, filters.database_name, row[13] - ) - aduana_nombre = DatabaseHelper.get_aduana_seccion_nombre( - db, filters.database_name, row[28] - ) - - # Calculate values using unified method - valor_comercial, tipo_cambio = ExchangeRateCalculator.calculate_exchange_rate_and_value( - db=db, - db_name=filters.database_name, - es_subpartida=row[30], # 'P' or 'S' - valor_me=row[21], - valor_mn_direct=row[20], - fecha_pago=row[8], - fecha_inicio=row[7], - clave_ped=row[45], - tipo_cambio_partida=row[38], - currency_type=filters.currency_type.value, - exchange_rate_type=filters.exchange_rate_type.value, - met_trans=met_trans - ) - - # Set peso values based on subpartida flag - if row[31] == 'P': - peso_neto = float(row[22]) if row[22] else 0.0 - peso_bruto = float(row[23]) if row[23] else 0.0 - else: - peso_neto = 0.0 - peso_bruto = 0.0 - - series_info = DatabaseHelper.get_series_info( - db, filters.database_name, row[29], row[0], filters.is_shelter - ) - - simbolo_ex = None - if row[15]: - simbolo_ex = DatabaseHelper.get_part_export_symbol( - db, filters.database_name, row[15], filters.is_shelter - ) - - pedimento_r1 = DatabaseHelper.get_rectification_pedimento( - db, row[2], row[32], filters.is_shelter - ) - - num_gaf_uni = DatabaseHelper.get_driver_badge( - db, filters.database_name, row[1] - ) - - movement = MovementItemDetailed( - Linea=row[0], - Factura=row[1], - Pedimento=row[2], - FechaFactura=row[3], - Estatus=row[4], - ClavePed=row[5], - TipoMovTemDef='IMPRE', - EsCambioRegimen='N', - Regimen=row[6], - Fecha_Inicio=parse_yyyymmdd_date(row[7]), - Fecha_Fin=parse_yyyymmdd_date(row[8]), - Fecha_Pago=parse_yyyymmdd_date(row[9]), - Remesa=row[10], - Proveedor=proveedor_info.get('name'), - RFCProveedor=proveedor_info.get('rfc'), - ProveedorTaxID=proveedor_info.get('tax_id'), - VendidoA=vendido_info.get('name'), - VendidoARFC=vendido_info.get('rfc'), - VendidoATaxID=vendido_info.get('tax_id'), - AgenteAduanal=agente_info.get('name'), - Patente=agente_info.get('license'), - NumParte=row[15], - DescripcionE=StringHelper.clean_text(row[16]), - DescripcionI=StringHelper.clean_text(row[17]), - CantidadIE=float(row[18]) if row[18] else 0.0, - UniMed=row[19], - ValorComercialMN=valor_comercial, - TipoCambio=tipo_cambio, - PesoNeto=peso_neto, - PesoBruto=peso_bruto, - OrdenCompraVenta=row[24], - FraccionArancelaria=row[25], - Preferencia=row[26], - Sector=row[27], - PaisOrigen=row[28], - Aduana=aduana_nombre, - Advalorem='', - TipoExpo='', - PedimentoR1=pedimento_r1, - EDocument=row[33], - NumOperacionVU=row[34], - Series=series_info, - Marca=StringHelper.clean_text(row[35]), - Modelo=StringHelper.clean_text(row[36]), - FraccionAmericana=row[37], - ECCN=row[38], - SimboloEx=simbolo_ex, - FechaEmision=parse_yyyymmdd_date(row[40]) if row[40] else None, # C41 - FechaEmision - BaseDeDatos=filters.database_name, - NumGafUni=num_gaf_uni, - UsuarioCap=row[41], - UsuarioAcr=row[42], - Transportista=row[43], - NumCaja=row[44], - Pedimento18=row[45], - AduanaCru=row[29], - Lote=row[54] if len(row) > 54 else '' # Not in query but mapped safely - ) - - movements.append(movement) - - logger.info(f"Successfully processed {len(movements)} detailed repair import movements") - return movements - - except Exception as e: - logger.error(f"Error fetching detailed repair import movements: {e}", exc_info=True) - raise - - def _build_where_clause(self, filters: "ImportRepairFilter") -> str: - """Build WHERE clause for repair imports query.""" - where_conditions = [] - - # STRICT SEPARATION: Only imports for repair - where_conditions.append("ih.operation_type = 'imp'") - # Repair imports are identified by cross-references, not invoice_type - where_conditions.append("COALESCE(cmp.is_regime_change, false) = false") - - # Date range filter - if filters.range_type.value == "FF": - where_conditions.append(f"ih.invoice_date >= TO_DATE('{filters.start_date}', 'YYYYMMDD') AND ih.invoice_date <= TO_DATE('{filters.end_date}', 'YYYYMMDD')") - else: - where_conditions.append(f"pd.payment_date >= TO_DATE('{filters.start_date}', 'YYYYMMDD') AND pd.payment_date <= TO_DATE('{filters.end_date}', 'YYYYMMDD')") - - # Note: Status filter applied at Python level after CASE WHEN in SELECT - # because status doesn't directly represent AC/NA status - - # Provider filter - if filters.provider: - where_conditions.append(f"cmp.provider_id IN (SELECT id FROM a76.clients_and_providers WHERE name = '{filters.provider}')") - - # Buyer filter - if filters.buyer: - where_conditions.append(f"cmp.sold_to_id IN (SELECT id FROM a76.clients_and_providers WHERE name = '{filters.buyer}')") - - # Pedimento code filter - if filters.pedimento_code: - where_conditions.append(f"ped.pedimento_code = '{filters.pedimento_code}'") - - return " AND ".join(where_conditions) - - def _calculate_totals(self, db: Session, db_name: str, consecutivo: int, discharge_filter: str) -> tuple: - """Calculate totals for main partidas with discharge filter.""" - # Build discharge clause - # Note: is_discharged field not yet migrated to PostgreSQL schema - discharge_clause = "" - # Temporarily disabled until schema migration: - # if discharge_filter == "SiDes": - # discharge_clause = " AND il.is_discharged = true" - # elif discharge_filter == "NoDes": - # discharge_clause = " AND il.is_discharged = false" - - sql = text(f""" - SELECT - COALESCE(SUM(lf.value_usd), 0), - COALESCE(SUM(lf.value_mxn), 0) - FROM a76.item_line_financials lf - INNER JOIN a76.item_lines il ON il.id = lf.item_line_id - WHERE il.invoice_id = :consecutivo - AND il.is_subitem = false - {discharge_clause} - """) - - result = db.execute(sql, {"consecutivo": consecutivo}).fetchone() - total_me = float(result[0]) if result and result[0] is not None else 0.0 - total_mn = float(result[1]) if result and result[1] is not None else 0.0 - - return total_me, total_mn diff --git a/backend/api/v1/modules/a76/reports/movements/invoices/services/temporary.py b/backend/api/v1/modules/a76/reports/movements/invoices/services/temporary.py deleted file mode 100644 index 3d27b674..00000000 --- a/backend/api/v1/modules/a76/reports/movements/invoices/services/temporary.py +++ /dev/null @@ -1,459 +0,0 @@ -""" -Temporary import service - handles IMTEM movements. -""" - -import logging -from datetime import datetime -from sqlalchemy import text -from sqlalchemy.orm import Session -from typing import List, Optional, TYPE_CHECKING - -if TYPE_CHECKING: - from ..schemas import ImportTemporaryFilter, MovementItem, MovementItemDetailed - -from .base import ConfigHelper, StringHelper -from .database_helpers import DatabaseHelper -from .exchange_rate import ExchangeRateCalculator -from .query_builders import TemporaryImportQueries - -logger = logging.getLogger(__name__) - - -def parse_yyyymmdd_date(date_str: str) -> Optional[datetime]: - """Parse date string in YYYYMMDD format to datetime.""" - if not date_str or date_str == '': - return None - try: - return datetime.strptime(date_str, '%Y%m%d') - except (ValueError, TypeError): - return None - - -class TemporaryImportService: - """Service for handling temporary import movements (IMTEM).""" - - def get_movements( - self, - db: Session, - filters: "ImportTemporaryFilter" - ) -> List["MovementItem"]: - """ - Get temporary import movements (normal mode - grouped by invoice). - - Args: - db: Database session - filters: Filter criteria - - Returns: - List of movement items grouped by invoice - """ - from ..schemas import MovementItem - - try: - logger.info(f"Fetching temporary import movements with filters: {filters.model_dump()}") - - # Get MetTrans configuration - met_trans = ConfigHelper.get_met_trans_config() - - # Build WHERE clause - where_clause = self._build_where_clause(filters) - - # Execute optimized aggregated query for NORMAL mode (GROUP BY with totals) - sql = text(TemporaryImportQueries.build_aggregated_query(filters.database_name, where_clause)) - results = db.execute(sql).fetchall() - logger.info(f"Found {len(results)} temporary import invoices") - - movements = [] - - for row in results: - factura = row[0] # C1 - FacturaImpo - estatus = row[3] # C4 - Estatus (AC o NA) - - # Filtrar facturas según include_cancelled - # Si include_cancelled=False, solo mostrar AC (status=true) - # Si include_cancelled=True, mostrar todas (AC y NA) - if not filters.include_cancelled and estatus != 'AC': - continue - - consecutivo = row[15] # C39 - Consecutivo - - # Helper to convert empty strings to None - def none_if_empty(val): - return None if val == '' else val - - # Helper to safely convert to float - def to_float(val): - if val is None or val == '': - return 0.0 - try: - return float(val) - except (ValueError, TypeError): - return 0.0 - - # Totals come directly from GROUP BY query (no N+1 problem) - # Correct indices based on TemporaryImportQueries.build_aggregated_query - total_me = to_float(row[28]) # total_me (index 28) - total_mn = to_float(row[29]) # total_mn (index 29) - valor_comercial_mn = to_float(row[30]) # valor_comercial_mn from item_line_financials - valor_mp_temp_mn = to_float(row[31]) # valor_mp_temp_mn from item_line_financials - valor_agre_mn = to_float(row[32]) # valor_agre_mn from item_line_financials - valor_mp_temp_usd = to_float(row[33]) # valor_mp_temp_usd from item_line_financials - sum_value_usd = to_float(row[34]) # sum_value_usd from item_line_financials (line item sum avoids zero-value header bug) - - # Replace zero values with the computed sums - total_me = sum_value_usd if sum_value_usd > 0 else total_me - total_mn = valor_comercial_mn if valor_comercial_mn > 0 else total_mn - - # Calculate exchange rate and value - valor_comercial, tipo_cambio = ExchangeRateCalculator.calculate_for_aggregated( - db=db, - db_name=filters.database_name, - valor_me=total_me, - valor_mn=total_mn, - tipo_cambio_db=to_float(row[19]), # C50 - TipoCambio - fecha_pago=row[8], # C13 - Fecha_Pago - fecha_inicio=row[6], # C11 - Fecha_Inicio - tipo_pedimento=row[4], # C5 - ClavePed (Using correct index) - currency_type=filters.currency_type.value, - exchange_rate_type=filters.exchange_rate_type.value, - is_shelter=filters.is_shelter, - use_transport_method=False, # Not used for temporary imports - met_trans=met_trans - ) - - # Get pedimento rectification - pedimento_r1 = DatabaseHelper.get_rectification_pedimento( - db, - row[1], # C2 - PedimentoImpo - row[16], # C41 - PedRectifica - filters.is_shelter - ) - - # Get driver badge - num_gaf_uni = DatabaseHelper.get_driver_badge( - db, filters.database_name, factura - ) - - # Calculate exchange rate for MPTemp explicitly decoupled from Valor Comercial - valor_mp_temp_raw, _ = ExchangeRateCalculator.calculate_for_aggregated( - db=db, - db_name=filters.database_name, - valor_me=to_float(row[33]), # sum_value_temp_usd - valor_mn=to_float(row[31]), # sum_value_temp_mxn - tipo_cambio_db=to_float(row[19]), - fecha_pago=row[8], - fecha_inicio=row[6], - tipo_pedimento=row[4], - currency_type=filters.currency_type.value, - exchange_rate_type=filters.exchange_rate_type.value, - is_shelter=filters.is_shelter, - use_transport_method=False, - met_trans=met_trans - ) - valor_mp_temp = float(valor_mp_temp_raw) - - # Build movement item - movement = MovementItem( - Factura=factura, - Pedimento=row[1], # C2 - PedimentoImpo - FechaFactura=parse_yyyymmdd_date(row[2]), # C3 - FechaFactura - Estatus=row[3], # C4 - Estatus - ClavePed=row[4], # C5 - ClavePed - TipoMovTemDef='IMTEM', - EsCambioRegimen='N', - ValorMPTemp=valor_mp_temp, - ValorComercialMN=valor_comercial, - TipoCambio=tipo_cambio, - ValorAgre=valor_agre_mn, - TipoExpo='', - PedimentoR1=pedimento_r1, - EDocument=row[17], # C42 - EDocument - NumOperacionVU=row[18], # C43 - NumOperacionVU - BaseDeDatos=filters.database_name, - NumGafUni=num_gaf_uni, - UsuarioCap=row[21], # C52 - UsuarioCap - UsuarioAcr=row[22], # C53 - UsuarioAct - Fecha_Pago=parse_yyyymmdd_date(none_if_empty(row[8])), # C13 - Fecha_Pago - NumCaja=row[24], # C55 - transport_num || license_plate (index 24) - Pedimento18=row[25], # C56 - '' empty (index 25) - AduanaCru=row[14], # C38 - Aduana_Cruce (index 14) - Lote=row[26] # C57 - '' empty (index 26) - ) - - movements.append(movement) - - logger.info(f"Successfully processed {len(movements)} temporary import movements") - return movements - - except Exception as e: - logger.error(f"Error fetching temporary import movements: {e}", exc_info=True) - raise - - def get_movements_detailed( - self, - db: Session, - filters: "ImportTemporaryFilter" - ) -> List["MovementItemDetailed"]: - """ - Get temporary import movements (detailed mode - line by line). - - Args: - db: Database session - filters: Filter criteria - - Returns: - List of detailed movement items (one per partida) - """ - from ..schemas import MovementItemDetailed - - try: - logger.info(f"Fetching detailed temporary import movements with filters: {filters.model_dump()}") - - # Get MetTrans configuration - met_trans = ConfigHelper.get_met_trans_config() - - # Build WHERE clause - where_clause = self._build_where_clause(filters) - - # Execute main query - sql = text(TemporaryImportQueries.build_main_query(filters.database_name, where_clause)) - results = db.execute(sql).fetchall() - logger.info(f"Found {len(results)} detailed temporary import partidas") - - movements = [] - - for row in results: - # Skip cancelled if not included - if not filters.include_cancelled and row[3] != 'AC': # C4 - Estatus - continue - - # Provider and client names now come directly from query (C8, C9) - # No need for additional database lookups - logger.info(f"Processing invoice {row[0]}: Proveedor='{row[7]}', VendidoA='{row[8]}', CantidadIE={row[22]}, DescripcionE='{row[20][:50] if row[20] else None}'") - - # Get customs agent information - agente_info = DatabaseHelper.get_customs_agent_info( - db, filters.database_name, row[17] # C18 - AAduanal - ) - - # Get provider and client details including RFC and TaxID - provider_info = DatabaseHelper.get_client_info( - db, filters.database_name, row[15], is_supplier=True - ) if row[15] else {} - - client_info = DatabaseHelper.get_client_info( - db, filters.database_name, row[16], is_supplier=False - ) if row[16] else {} - - # Get customs section name - aduana_nombre = DatabaseHelper.get_aduana_seccion_nombre( - db, filters.database_name, row[37] # C38 - Aduana_Cruce - ) - - # Calculate values (only for main partidas 'P', not subpartidas 'S') - valor_comercial, tipo_cambio = ExchangeRateCalculator.calculate_exchange_rate_and_value( - db=db, - db_name=filters.database_name, - es_subpartida=row[39], # C40 - EsSubPartida - valor_me=row[26], # C27 - ValorImpoME - valor_mn_direct=row[24], # C25 - ValorImpoMN - fecha_pago=row[12], # C13 - Fecha_Pago - fecha_inicio=row[10], # C11 - Fecha_Inicio - clave_ped=row[57], # C58 - TIPOPEDIMENTOTRANSPORTEE - tipo_cambio_partida=row[49], # C50 - TipoCambio - currency_type=filters.currency_type.value, - exchange_rate_type=filters.exchange_rate_type.value, - met_trans=met_trans - ) - - valor_mp_temp_raw, _ = ExchangeRateCalculator.calculate_exchange_rate_and_value( - db=db, - db_name=filters.database_name, - es_subpartida=row[39], # C40 - EsSubPartida - valor_me=float(row[59]) if row[59] else 0.0, - valor_mn_direct=float(row[58]) if row[58] else 0.0, - fecha_pago=row[12], # C13 - Fecha_Pago - fecha_inicio=row[10], # C11 - Fecha_Inicio - clave_ped=row[57], # C58 - TIPOPEDIMENTOTRANSPORTEE - tipo_cambio_partida=row[49], # C50 - TipoCambio - currency_type=filters.currency_type.value, - exchange_rate_type=filters.exchange_rate_type.value, - met_trans=met_trans - ) - valor_mp_temp_mn = float(valor_mp_temp_raw) - - # Assign the properly converted commercial value directly - valor_comercial_mn = float(valor_comercial) - - # Set peso values based on subpartida flag (allow 'PT', 'MP', etc. but block 'S') - if row[39] != 'S': # C40 - EsSubPartida - peso_neto = float(row[28]) if row[28] else 0.0 # C29 - peso_bruto = float(row[29]) if row[29] else 0.0 # C30 - else: - peso_neto = 0.0 - peso_bruto = 0.0 - - # Get series information - series_info = DatabaseHelper.get_series_info( - db, filters.database_name, row[38], row[43], filters.is_shelter # C39, C44 - ) - - # Get part export symbol - simbolo_ex = None - if row[48]: # C49 - NumParte - simbolo_ex = DatabaseHelper.get_part_export_symbol( - db, filters.database_name, row[48], filters.is_shelter - ) - - # Get pedimento rectification - pedimento_r1 = DatabaseHelper.get_rectification_pedimento( - db, - row[1], # C2 - PedimentoImpo - row[40], # C41 - PedRectifica - filters.is_shelter - ) - - # Get driver badge - num_gaf_uni = DatabaseHelper.get_driver_badge( - db, filters.database_name, row[0] # C1 - FacturaImpo - ) - - # Helper to convert empty strings to None for dates - def none_if_empty(val): - if val == '' or val is None: - return None - return val - - # Helper to convert to string (for Remesa, Advalorem) - def to_str(val): - if val is None or val == '': - return None - if isinstance(val, bool): - return 'P' if val else 'S' # Convert bool to P/S for Advalorem - return str(val) - - logger.error(f"DEBUG ROW: NumParte(17)='{row[17]}', Sector(30)='{row[30]}', Partida {row[43]}, Original Part ID(46)='{row[46]}'") - - # Build detailed movement item - movement = MovementItemDetailed( - Linea=row[43], # C44 - LineaImpo - Factura=row[0], # C1 - FacturaImpo - Pedimento=row[1], # C2 - PedimentoImpo - FechaFactura=parse_yyyymmdd_date(row[2]), # C3 - FechaFactura (convert to datetime) - Estatus=row[3], # C4 - Estatus - ClavePed=row[4], # C5 - ClavePed - TipoMovTemDef='IMTEM', - EsCambioRegimen='N', - Regimen=row[9], # C10 - Regimen - Fecha_Inicio=parse_yyyymmdd_date(row[10]), # C11 - Fecha_Inicio - Fecha_Fin=parse_yyyymmdd_date(row[11]), # C12 - Fecha_Fin - Fecha_Pago=parse_yyyymmdd_date(row[12]), # C13 - Fecha_Pago - Remesa=to_str(row[13]), # C14 - Remesa - Proveedor=row[7], # C8 - Provider name (from JOIN) - RFCProveedor=provider_info.get('rfc'), - ProveedorTaxID=provider_info.get('tax_id'), - VendidoA=row[8], # C9 - Client name (from JOIN) - VendidoARFC=client_info.get('rfc'), - VendidoATaxID=client_info.get('tax_id'), - AgenteAduanal=agente_info.get('name'), - Patente=agente_info.get('license'), - NumParte=row[48], # C49 - Part Number (from JOIN) - DescripcionE=StringHelper.clean_text(row[20]), # C21 - DescripcionI=StringHelper.clean_text(row[21]), # C22 - CantidadIE=float(row[22]) if row[22] else 0.0, # C23 - UniMed=row[23], # C24 - ValorComercialMN=valor_comercial_mn, - ValorMPTemp=valor_mp_temp_mn, - TipoCambio=tipo_cambio, - PesoNeto=peso_neto, - PesoBruto=peso_bruto, - OrdenCompraVenta=row[30], # C31 - OrdenCompra - FraccionArancelaria=row[31], # C32 - Fraccion - Preferencia=row[32], # C33 - TipoFraccion - Sector=row[34], # C35 - Sector (from COALESCE) - PaisOrigen=row[36], # C37 - PaisOrigen - Aduana=aduana_nombre, - Advalorem=to_str(row[39]), # C40 - EsSubPartida (convert bool to str) - TipoExpo='', - PedimentoR1=pedimento_r1, - EDocument=row[41], # C42 - EDocument - NumOperacionVU=row[42], # C43 - NumOperacionVU - Series=series_info, - Marca=StringHelper.clean_text(row[44]), # C45 - Modelo=StringHelper.clean_text(row[45]), # C46 - FraccionAmericana=row[46], # C47 - FraccionAme - ECCN=row[47], # C48 - ECCN - SimboloEx=simbolo_ex, - FechaEmision=parse_yyyymmdd_date(row[50]), # C51 - FechaEmision - BaseDeDatos=filters.database_name, - NumGafUni=num_gaf_uni, - UsuarioCap=row[51], # C52 - UsuarioCap - UsuarioAcr=row[52], # C53 - UsuarioAct - Transportista=row[53], # C54 - Carrier ID - NumCaja=row[54], # C55 - Transport num - Pedimento18=row[55], # C56 - Pedimento18 - AduanaCru=row[37], # C38 - Aduana_Cruce - Lote=row[56] # C57 - LOTE - ) - - movements.append(movement) - - logger.info(f"Successfully processed {len(movements)} detailed temporary import movements") - return movements - - except Exception as e: - logger.error(f"Error fetching detailed temporary import movements: {e}", exc_info=True) - raise - - def _build_where_clause(self, filters: "ImportTemporaryFilter") -> str: - """Build WHERE clause for temporary imports query using PostgreSQL tables.""" - where_conditions = [] - - # STRICT SEPARATION: Only temporary imports - where_conditions.append("ih.operation_type = 'imp'") - # ALWAYS filter by specific invoice_type to avoid duplication with Definitive service - where_conditions.append("ih.invoice_type IN ('TEM', 'MATTEM')") - - # Date range filter - if filters.range_type.value == "FF": - where_conditions.append(f"ih.invoice_date >= TO_DATE('{filters.start_date}', 'YYYYMMDD') AND ih.invoice_date <= TO_DATE('{filters.end_date}', 'YYYYMMDD')") - else: - where_conditions.append(f"pd.payment_date >= TO_DATE('{filters.start_date}', 'YYYYMMDD') AND pd.payment_date <= TO_DATE('{filters.end_date}', 'YYYYMMDD')") - - # Note: Status filter applied at Python level after CASE WHEN in SELECT - # because status doesn't directly represent AC/NA status - - # Provider filter - if filters.provider: - where_conditions.append(f"cmp.provider_id IN (SELECT id FROM a76.clients_and_providers WHERE name = '{filters.provider}')") - - # Buyer filter - if filters.buyer: - where_conditions.append(f"cmp.sold_to_id IN (SELECT id FROM a76.clients_and_providers WHERE name = '{filters.buyer}')") - - # Pedimento code filter - if filters.pedimento_code: - where_conditions.append(f"ped.pedimento_code = '{filters.pedimento_code}'") - - return " AND ".join(where_conditions) if where_conditions else "1=1" - - def _calculate_totals(self, db: Session, db_name: str, consecutivo: int) -> tuple: - """Calculate totals for main partidas only using PostgreSQL. - - Only sums partidas where is_subpartida is false (equivalent to EsSubpartida = 'P' in Clarion). - """ - sql = text(""" - SELECT - COALESCE(SUM(lf.value_usd), 0), - COALESCE(SUM(lf.value_mxn), 0) - FROM a76.item_line_financials lf - JOIN a76.item_lines il ON il.id = lf.item_line_id - WHERE il.invoice_id = :consecutivo - AND COALESCE(il.is_subpartida, false) = false - """) - - result = db.execute(sql, {"consecutivo": consecutivo}).fetchone() - total_me = float(result[0]) if result and result[0] is not None else 0.0 - total_mn = float(result[1]) if result and result[1] is not None else 0.0 - - return total_me, total_mn diff --git a/backend/api/v1/modules/a76/reports/movements/invoices/tasks.py b/backend/api/v1/modules/a76/reports/movements/invoices/tasks.py deleted file mode 100644 index 47114e98..00000000 --- a/backend/api/v1/modules/a76/reports/movements/invoices/tasks.py +++ /dev/null @@ -1,108 +0,0 @@ - -import base64 -import logging -import traceback -from typing import Dict, Any - -from core.celery_app import celery_app -from core.database import CoreSessionLocal -from core.email import EmailService -from datetime import datetime - -from .movement_service import movement_service -from .schemas import AllMovementsFilter -from .csv_utils import generate_csv_from_movements - -logger = logging.getLogger(__name__) - -@celery_app.task(bind=True, name="generate_invoice_movements_async") -def generate_invoice_movements_async(self, filter_data: Dict[str, Any], user_email: str = None): - """ - Async task to generate invoice movements report. - FETCHES data -> GENERATES CSV -> SENDS EMAIL (optional) -> RETURNS CSV (base64) - """ - db = CoreSessionLocal() - try: - # 1. Update Progress - self.update_state(state='PROCESSING', meta={'current': 10, 'total': 100, 'status': 'Inicializando reporte...'}) - - # 2. Reconstruct Filter - filters = AllMovementsFilter(**filter_data) - - # 3. Fetch Data - self.update_state(state='PROCESSING', meta={'current': 30, 'total': 100, 'status': 'Obteniendo movimientos de base de datos...'}) - logger.info(f"Async Task: Fetching movements for {filters}") - - movements = movement_service.get_all_movements(db=db, filters=filters) - - self.update_state(state='PROCESSING', meta={'current': 70, 'total': 100, 'status': f'Procesando {len(movements)} registros...'}) - - # 4. Generate CSV - csv_content = generate_csv_from_movements( - movements=movements, - filters=filters - ) - - # 5. Send Email if requested - email_sent = False - if filters.send_email and user_email: - self.update_state(state='PROCESSING', meta={'current': 90, 'total': 100, 'status': 'Enviando correo electrónico...'}) - try: - # Generate filename - filename = f"reporte_facturas_{filters.start_date}_{filters.end_date}_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv" - - # Send email (using the new async wrapper or run_until_complete if needed, - # but since we are in a sync celery task we might need to be careful with async/await. - # Actually EmailService.send_report_email is async. - # We need to run it synchronously here or make the task async. - # Celery tasks are sync by default. We can use asgiref.sync.async_to_sync - - import asyncio - from asgiref.sync import async_to_sync - - # Helper to run async method - result = async_to_sync(EmailService.send_report_email)( - recipient_email=user_email, - subject=f"Reporte de Facturas - {filters.start_date} al {filters.end_date}", - body_text=f"Se ha generado el reporte de facturas solicitado con {len(movements)} registros.", - csv_content=csv_content, - filename=filename - ) - - if result: - email_sent = True - logger.info(f"Async Task: Email sent to {user_email}") - else: - logger.warning(f"Async Task: Failed to send email to {user_email}") - - except Exception as e: - logger.error(f"Async Task: Email error: {str(e)}") - - # 6. Encode and Return - self.update_state(state='PROCESSING', meta={'current': 95, 'total': 100, 'status': 'Finalizando...'}) - - # Convert string csv to bytes then base64 - pdf_b64 = base64.b64encode(csv_content.encode('utf-8')).decode('utf-8') - - return { - 'status': 'success', - 'file_name': f"reporte_facturas_{datetime.now().strftime('%Y%m%d')}.csv", - 'content': pdf_b64, - 'media_type': 'text/csv', - 'email_sent': email_sent, - 'total_records': len(movements) - } - - except Exception as e: - logger.error(f"Error in generate_invoice_movements_async: {str(e)}", exc_info=True) - self.update_state( - state='FAILURE', - meta={ - 'exc_type': type(e).__name__, - 'exc_message': str(e), - 'custom': 'Error generating report' - } - ) - raise e - finally: - db.close() diff --git a/backend/api/v1/modules/a76/reports/movements/saldos/__init__.py b/backend/api/v1/modules/a76/reports/movements/saldos/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/backend/api/v1/modules/a76/reports/movements/saldos/csv_utils.py b/backend/api/v1/modules/a76/reports/movements/saldos/csv_utils.py deleted file mode 100644 index 791e4cc6..00000000 --- a/backend/api/v1/modules/a76/reports/movements/saldos/csv_utils.py +++ /dev/null @@ -1,971 +0,0 @@ -""" -CSV generation utilities for Saldos Temporales report. - -Translated from Clarion routines: - GENERA_EXCEL_CSV_PEDIMENTO (range_type = 'pedimento') - GENERA_EXCEL_CSV_FECHA (range_type = 'payment_date' | 'invoice_date') - GENERA_EXCEL_CSV_PARTE (range_type = 'parts') - GENERA_EXCEL_CSV_CLASE (range_type = 'classes') - -Real Anexo76 tables (schema a76): - QEqiMaq / SPartidasImp → a76.item_lines - QFacImp → a76.invoice_header + a76.invoice_compliance_mx - QPedimentos → a76.pedimentos + a76.pedimento_dates - QClaAct → a76.classes - QSeriesImpo → a76.item_line_series - sFracciones → a76.tariff_fractions (column: umt = UMAbreviacion) - GTipoCambio → a76.exchange_rate - Line quantities → a76.item_line_quantities - Line financials → a76.item_line_financials - Line customs → a76.item_line_customs - Line descriptions → a76.item_line_descriptions -""" - -import csv -import io -import logging -import pytz -from decimal import Decimal, ROUND_HALF_UP, InvalidOperation -from datetime import datetime, date -from typing import Dict, List, Optional - -from sqlalchemy import text -from sqlalchemy.orm import Session - -from .schemas import SaldosFilter - -logger = logging.getLogger(__name__) - -# --------------------------------------------------------------------------- -# Helpers -# --------------------------------------------------------------------------- - -def _d(value, decimals: int = 8) -> Decimal: - try: - exp = Decimal(10) ** -decimals - return Decimal(str(value or 0)).quantize(exp, rounding=ROUND_HALF_UP) - except (InvalidOperation, TypeError): - return Decimal(0) - - -def _fmt_date(val) -> str: - """Format a date value to MM/DD/YY (Clarion @D06 equivalent).""" - if val is None: - return "" - if isinstance(val, (datetime, date)): - return val.strftime("%m/%d/%y") - return str(val) - - -def _fmt_num(d: Decimal) -> str: - """Format Decimal without scientific notation and trailing zeros.""" - if d is None: - return "" - s = "{:f}".format(d) - if "." in s: - s = s.rstrip("0").rstrip(".") - if s == "-0": - return "0" - return s - - -def _clean(text: str) -> str: - """Remove newlines/carriage returns (Clarion SACARCOMASENTERS equivalent).""" - return str(text or "").replace("\n", " ").replace("\r", "").replace(",", " ").strip() - - -def _subpartida_sql(level: str) -> str: - # NOTE: is_sub_part column does not exist yet in item_lines — returns empty - return "" - - -# --------------------------------------------------------------------------- -# Per-row lookups (per Clarion row-level ACCESS calls) -# --------------------------------------------------------------------------- - -def _get_exchange_rate(db: Session, fecha, tenant_id: int, company_id: int) -> Optional[Decimal]: - """GTipoCambio → a76.exchange_rate""" - if not fecha: - return None - try: - row = db.execute( - text( - "SELECT value FROM a76.exchange_rate " - "WHERE tenant_id = :tid AND company_id = :cid AND date::date = :fecha " - "LIMIT 1" - ), - {"tid": tenant_id, "cid": company_id, "fecha": fecha}, - ).fetchone() - if row: - return _d(row[0]) - except Exception as e: - logger.warning(f"exchange_rate not found for {fecha}: {e}") - return None - - -def _get_series_data(db: Session, line_item_id) -> Dict: - """ - QSeriesImpo → a76.item_line_series - Returns: SeriesSolas, ModeloSeries, NumParteSeries, NoId - """ - out = {"SeriesSolas": None, "ModeloSeries": None, "NumParteSeries": None, "NoId": None} - if not line_item_id: - return out - try: - rows = db.execute( - text( - "SELECT serial_numbers, model, sub_model, number_id " - "FROM a76.item_line_series " - "WHERE line_item_id = :lid ORDER BY id" - ), - {"lid": line_item_id}, - ).fetchall() - except Exception: - return out - - serials, models, numbers_id = [], [], [] - for i, r in enumerate(rows, 1): - if r[0]: - serials.append(f"{i}) {r[0]}") - if r[1]: - models.append(f"{i}) {r[1]}") - if r[3]: - numbers_id.append(f"{i}) {r[3]}") - - out["SeriesSolas"] = ", ".join(serials) if serials else None - out["ModeloSeries"] = ", ".join(models) if models else None - out["NumParteSeries"] = None # sub_model used as num-parte-series if relevant - out["NoId"] = ", ".join(numbers_id) if numbers_id else None - return out - - -def _get_um_tarifa(db: Session, fraccion: str) -> str: - """ - sFracciones → a76.tariff_fractions - Clarion: SUB(fraccion,1,8) for code and SUB(fraccion,9,2) for historico - We match on the first 8 chars of the code. - """ - if not fraccion: - return "" - frac_code = fraccion.replace("'", "")[:8] # strip leading quote and take 8 chars - try: - row = db.execute( - text( - "SELECT umt FROM a76.tariff_fractions " - "WHERE LEFT(code, 8) = :frac LIMIT 1" - ), - {"frac": frac_code}, - ).fetchone() - if row and row[0]: - return str(row[0]) - except Exception as e: - logger.debug(f"UMTarifa not found for {frac_code}: {e}") - return "" - - -def _get_fraccion_ame(db: Session, class_id) -> str: - """QClaAct.FraccionAme → a76.classes.us_fraction""" - if not class_id: - return "" - try: - row = db.execute( - text("SELECT us_fraction FROM a76.classes WHERE id = :cid LIMIT 1"), - {"cid": class_id}, - ).fetchone() - if row and row[0]: - return str(row[0]) - except Exception: - pass - return "" - - -# --------------------------------------------------------------------------- -# Base SQL (shared across all range types) -# --------------------------------------------------------------------------- - -BASE_SELECT = """ - ih.invoice_number AS "C1", - ih.company_id AS "company_id", - CONCAT(ped.year,'-',ped.license,'-',ped.pedimento_number) AS "C2", - COALESCE(icm.provider_header,'') AS "C3", - COALESCE(icm.sold_to_header,'') AS "C_sold_to", - il.rectification AS "C4", - icm.pedimento_id AS "C5", - icm.pedimento_r1 AS "C5R1", - pd.payment_date AS "C7", - ped.customs_office AS "C8", - pd.start_date AS "C9", - ih.invoice_date AS "C11", - ih.emission_date AS "C12_emission", - cl.id AS "C13_class_id", - COALESCE(cl.class_code,'') AS "C13", - REPLACE(REPLACE(COALESCE(ild.description_spanish,''),CHR(10),''),CHR(13),' ') AS "C14", - REPLACE(REPLACE(COALESCE(ild.description_english,''),CHR(10),''),CHR(13),' ') AS "C15", - COALESCE(ilc.origin_country,'') AS "C16", - COALESCE(ent.qty_impo, 0) AS "C17", - COALESCE(bal.qty_used, 0) AS "C18", - COALESCE(uom.code,'') AS "C19", - COALESCE(ent.val_mn_impo, 0) AS "C20", - COALESCE(bal.val_mn_used, 0) AS "C21", - COALESCE(ent.val_me_impo, 0) AS "C22", - COALESCE(bal.val_me_used, 0) AS "C23", - COALESCE(ilq.net_weight, 0) AS "C24", - COALESCE(ilc.fraction,'') AS "C26", - COALESCE(ilc.fraction_type,'') AS "C27", - COALESCE(ilc.advalorem,'') AS "C28", - COALESCE(ilc.sector,'') AS "C29", - COALESCE(ilq.net_weight, 0) AS "C30", - COALESCE(ild.brand,'') AS "C32", - COALESCE(ild.model,'') AS "C33", - il.id AS "C34", - il.line_number AS "C35", - COALESCE(p.part_number,'') AS "C36", - 0 AS "C37", - COALESCE(il.location,'') AS "C38", - '' AS "C39", - COALESCE(icm.edocument,'') AS "C40", - COALESCE(icm.vucem_operation_num,'') AS "C41", - COALESCE(cl.material_key,'') AS "C42", - CONCAT(ped.year,'-',ped.customs_office,'-',ped.license,'-',ped.pedimento_number) AS "C43", - COALESCE(ptc.payment_date_code, 'P') AS "C44", - COALESCE(lm.last_movement_type, '') AS "C_last_movement_type", - COALESCE(ilc.octave_fraction,'') AS "C45", - '' AS "C47", - COALESCE(ped.pedimento_code,'') AS "C48", - COALESCE(ilc.rate,'') AS "C49", - COALESCE(il.iv32_type_key,'') AS "C50", - COALESCE(il.guide_number,'') AS "C_embarque", - COALESCE(c_proj.name, '') AS "C_proyecto", - COALESCE(bal.qty_balance, 0) AS "C_balance" -""" - -BASE_JOINS = """ - JOIN a76.invoice_header ih ON ih.id = il.invoice_id - LEFT JOIN a76.company c_proj ON c_proj.id = ih.company_id - JOIN a76.invoice_compliance_mx icm ON icm.invoice_id = ih.id - LEFT JOIN a76.pedimentos ped ON ped.id = icm.pedimento_id - LEFT JOIN a76.pedimento_dates pd ON pd.pedimento_id = ped.id - LEFT JOIN a76.pedimento_transport_means ptm ON ptm.pedimento_id = ped.id - LEFT JOIN public.pedimento_transport_catalog ptc ON ptc.code = ptm.entry_exit - LEFT JOIN a76.classes cl ON cl.id = il.class_id - LEFT JOIN a76.item_line_quantities ilq ON ilq.item_line_id = il.id - LEFT JOIN a76.item_line_financials ilf ON ilf.item_line_id = il.id - LEFT JOIN a76.item_line_customs ilc ON ilc.item_line_id = il.id - LEFT JOIN a76.item_line_descriptions ild ON ild.item_line_id = il.id - LEFT JOIN a76.parts p ON p.id = il.part_number_id - LEFT JOIN a76.units_of_measure uom ON uom.id = il.unit_of_measure - LEFT JOIN ( - SELECT DISTINCT ON (import_item_line_id) - import_item_line_id, - movement_type AS last_movement_type - FROM a24.balance_movement - WHERE tenant_id = :tenant_id - ORDER BY import_item_line_id, id DESC - ) lm ON lm.import_item_line_id = il.id - JOIN ( - SELECT - import_item_line_id, - SUM( - CASE - WHEN movement_type = 'entry' THEN quantity - WHEN movement_type = 'entry_void' THEN -1 * quantity - ELSE 0 - END - ) AS qty_impo, - SUM( - CASE - WHEN movement_type = 'entry' THEN COALESCE(value_me, 0) - WHEN movement_type = 'entry_void' THEN -1 * COALESCE(value_me, 0) - ELSE 0 - END - ) AS val_me_impo, - SUM( - CASE - WHEN movement_type = 'entry' THEN COALESCE(value_mn, 0) - WHEN movement_type = 'entry_void' THEN -1 * COALESCE(value_mn, 0) - ELSE 0 - END - ) AS val_mn_impo - FROM a24.balance_movement - WHERE tenant_id = :tenant_id - GROUP BY import_item_line_id - ) ent ON ent.import_item_line_id = il.id - LEFT JOIN ( - SELECT - import_item_line_id, - SUM( - CASE - WHEN movement_type IN ('consumption', 'waste', 'scrap', 'destruction') THEN quantity - WHEN movement_type = 'return' THEN -1 * quantity - ELSE 0 - END - ) as qty_used, - SUM( - CASE - WHEN movement_type IN ('consumption', 'waste', 'scrap', 'destruction') THEN COALESCE(value_me, 0) - WHEN movement_type = 'return' THEN -1 * COALESCE(value_me, 0) - ELSE 0 - END - ) as val_me_used, - SUM( - CASE - WHEN movement_type IN ('consumption', 'waste', 'scrap', 'destruction') THEN COALESCE(value_mn, 0) - WHEN movement_type = 'return' THEN -1 * COALESCE(value_mn, 0) - ELSE 0 - END - ) as val_mn_used, - SUM(CASE WHEN movement_type IN ('consumption', 'waste', 'scrap', 'destruction', 'neg_adj', 'transfer_out', 'expiration', 'regime_chg_out', 'entry_void') - THEN -1 * quantity ELSE quantity END) as qty_balance - FROM a24.balance_movement - WHERE tenant_id = :tenant_id - GROUP BY import_item_line_id - ) bal ON bal.import_item_line_id = il.id -""" - -# --------------------------------------------------------------------------- -# 5 query builders -# --------------------------------------------------------------------------- - -def _get_base_query_filters(filters: SaldosFilter) -> tuple: - """Helper to return base where-clause and parameters for company/tenant.""" - company_filter = "" - params: dict = {"tenant_id": filters.tenant_id} - - # If Shelter option is true, ignore company_id to fetch all projects in the tenant. - if not filters.shelter: - company_filter = "AND ih.company_id = :company_id" - params["company_id"] = filters.company_id - - return company_filter, params - -def _query_ped(filters: SaldosFilter) -> tuple: - level_filter = _subpartida_sql(filters.level) - date_filter = "" - company_filter, params = _get_base_query_filters(filters) - - if filters.start_date and filters.end_date: - s, e = filters.start_date, filters.end_date - if s > e: - s, e = e, s - date_filter = "AND CONCAT(ped.year,'-',ped.license,'-',ped.pedimento_number) BETWEEN :start AND :end" - params["start"] = s - params["end"] = e - sql = f""" - SELECT {BASE_SELECT} - FROM a76.item_lines il - {BASE_JOINS} - WHERE ih.tenant_id = :tenant_id - AND ih.operation_type = 'imp' - AND ent.qty_impo > 0 - {company_filter} - {date_filter} - {level_filter} - ORDER BY CONCAT(ped.year,ped.customs_office,ped.license,ped.pedimento_number), - ih.invoice_date - """ - return sql, params - - -def _query_fpp(filters: SaldosFilter) -> tuple: - level_filter = _subpartida_sql(filters.level) - date_filter = "" - company_filter, params = _get_base_query_filters(filters) - - if filters.start_date and filters.end_date: - date_filter = "AND pd.payment_date BETWEEN :start AND :end" - params["start"] = filters.start_date - params["end"] = filters.end_date - sql = f""" - SELECT {BASE_SELECT} - FROM a76.item_lines il - {BASE_JOINS} - WHERE ih.tenant_id = :tenant_id - AND ih.operation_type = 'imp' - AND ent.qty_impo > 0 - {company_filter} - {date_filter} - {level_filter} - ORDER BY ih.invoice_date - """ - return sql, params - - -def _query_ffa(filters: SaldosFilter) -> tuple: - level_filter = _subpartida_sql(filters.level) - date_filter = "" - company_filter, params = _get_base_query_filters(filters) - - if filters.start_date and filters.end_date: - date_filter = "AND ih.invoice_date BETWEEN :start AND :end" - params["start"] = filters.start_date - params["end"] = filters.end_date - sql = f""" - SELECT {BASE_SELECT} - FROM a76.item_lines il - {BASE_JOINS} - WHERE ih.tenant_id = :tenant_id - AND ih.operation_type = 'imp' - AND ent.qty_impo > 0 - {company_filter} - {date_filter} - {level_filter} - ORDER BY ih.invoice_date - """ - return sql, params - - -def _query_par(filters: SaldosFilter) -> tuple: - level_filter = _subpartida_sql(filters.level) - date_filter = "" - id_filter = "" - company_filter, params = _get_base_query_filters(filters) - - if filters.start_date and filters.end_date: - s, e = filters.start_date, filters.end_date - if s > e: - s, e = e, s - id_filter = "AND p.part_number BETWEEN :start AND :end" - params["start"] = s - params["end"] = e - if filters.date_start and filters.date_end: - date_filter = "AND ih.invoice_date BETWEEN :date_start AND :date_end" - params["date_start"] = filters.date_start - params["date_end"] = filters.date_end - sql = f""" - SELECT {BASE_SELECT} - FROM a76.item_lines il - {BASE_JOINS} - WHERE ih.tenant_id = :tenant_id - AND ih.operation_type = 'imp' - AND ent.qty_impo > 0 - {company_filter} - {id_filter} - {date_filter} - {level_filter} - ORDER BY p.part_number, ih.invoice_date - """ - return sql, params - - -def _query_cla(filters: SaldosFilter) -> tuple: - level_filter = _subpartida_sql(filters.level) - date_filter = "" - id_filter = "" - company_filter, params = _get_base_query_filters(filters) - - if filters.start_date and filters.end_date: - s, e = filters.start_date, filters.end_date - if s > e: - s, e = e, s - id_filter = "AND cl.class_code BETWEEN :start AND :end" - params["start"] = s - params["end"] = e - if filters.date_start and filters.date_end: - date_filter = "AND ih.invoice_date BETWEEN :date_start AND :date_end" - params["date_start"] = filters.date_start - params["date_end"] = filters.date_end - sql = f""" - SELECT {BASE_SELECT} - FROM a76.item_lines il - {BASE_JOINS} - WHERE ih.tenant_id = :tenant_id - AND ih.operation_type = 'imp' - AND ent.qty_impo > 0 - {company_filter} - {id_filter} - {date_filter} - {level_filter} - ORDER BY cl.class_code, ih.invoice_date - """ - return sql, params - - -# --------------------------------------------------------------------------- -# Row-level business logic (Clarion _build_row equivalent) -# --------------------------------------------------------------------------- - -def _fraccion(row: Dict, include_regla_octava: bool) -> str: - c26 = str(row.get("C26") or "") - c45 = str(row.get("C45") or "") - target = c45 if (include_regla_octava and c45) else c26 - if target and target[0] == "0": - return "'" + target - return target - - -def _build_row( - row: Dict, - db: Session, - filters: SaldosFilter, - *, - num_parte_field: str = "C36", -) -> Optional[Dict]: - """ - Apply business logic and enrich with per-row DB lookups. - Returns None if the row should be filtered out. - """ - use_mn = filters.currency == "national" - use_fp = filters.exchange_rate == "payment_date" - - # CANTIDADES - cant_orig = _d(row.get("C17")) - cant_used = _d(row.get("C18")) - cant_saldo = _d(row.get("C_balance")) - - # Mostrar saldo 0, excepto lotes anulados (último movimiento ENTRY_VOID). - if str(row.get("C_last_movement_type") or "").lower() == "entry_void": - return None - - if filters.omit_low_balance and cant_saldo <= Decimal(0): - return None - - # PESO - peso_neto = _d(row.get("C30")) - peso_usado = (cant_used * peso_neto / cant_orig) if cant_orig != 0 else Decimal(0) - peso_saldo = peso_neto - peso_usado - - # TIPO DE CAMBIO: - # C44 now carries payment_date_code from pedimento_transport_catalog: - # E => Fecha_Inicio, P => Fecha_Pago. - # If explicitly "invoice_date", we use invoice_date (C11) instead. - tc = Decimal(1) - fecha_pago = row.get("C7") - fecha_inicio = row.get("C9") - fecha_factura= row.get("C11") - payment_date_code = str(row.get("C44") or "").upper() - - tc_fecha_display = None - # TIPO DE CAMBIO - tc_fecha = None - if use_fp: - tc_fecha = fecha_inicio if payment_date_code == "E" else fecha_pago - else: - tc_fecha = fecha_factura - - if tc_fecha: - target_company_id = row.get("company_id") or filters.company_id - tc_val = _get_exchange_rate(db, tc_fecha, filters.tenant_id, target_company_id) - if tc_val: - tc = tc_val - elif filters.shelter: - # In Shelter mode, missing exchange rate skips the row (enforce strict data) - return None - - # VALOR ORIGINAL - if use_mn: - valor_orig = _d(row.get("C22")) * tc if use_fp and fecha_pago else _d(row.get("C20")) - else: - valor_orig = _d(row.get("C22")) - - # VALOR USADO - if cant_orig != 0: - if use_mn: - if use_fp and fecha_pago: - valor_usado = cant_used * _d(row.get("C22")) * tc / cant_orig - else: - valor_usado = cant_used * _d(row.get("C20")) / cant_orig - else: - valor_usado = cant_used * _d(row.get("C22")) / cant_orig - else: - valor_usado = Decimal(0) - - # VALOR SALDO — SubPartida logic - es_subpartida = str(row.get("C39") or "") - contiene_subp = str(row.get("C47") or "") - - if es_subpartida == "S": - valor_saldo = ( - _d(row.get("C20")) - _d(row.get("C21")) - if use_mn else - _d(row.get("C22")) - _d(row.get("C23")) - ) - elif es_subpartida == "P" and contiene_subp == "S": - valor_saldo = ( - valor_orig - _d(row.get("C21")) - if use_mn else - valor_orig - _d(row.get("C23")) - ) - else: - valor_saldo = valor_orig - valor_usado - - # BALANCE TYPE filter - is_repair = row.get("C4") - if filters.balance_type == "normal" and is_repair: - return None - if filters.balance_type == "repair" and not is_repair: - return None - - # FRACCION ARANCELARIA - fraccion = _fraccion(row, filters.include_regla_octava) - - # NUM PARTE display - if num_parte_field == "C13" or filters.print_class: - num_parte_display = str(row.get("C13") or "") - else: - num_parte_display = str(row.get("C36") or "") - - # Series (per-row DB lookup) - series_data = _get_series_data(db, row.get("C34")) - - # UMTarifa (Clarion: query sFracciones with fraccion) - um_tarifa = _get_um_tarifa(db, fraccion) - - # FraccionAme (Clarion: ACCESS:QClaAct → EqiCla:FraccionAme) - fraccion_ame = _get_fraccion_ame(db, row.get("C13_class_id")) - - # TipoCambio value to embed in CSV - tc_value = tc if tc != Decimal(1) else (tc if _get_exchange_rate(db, tc_fecha, filters.tenant_id, target_company_id) else None) - tipo_cambio_str = str(tc_value) if tc_value else "" - - # Pedimento R1 logic (Clarion: some companies invert ped / pedR1) - ped_impo = str(row.get("C2") or "") - ped_r1 = str(row.get("C5R1") or "") - - # Fechas formateadas - fecha_pago_fmt = _fmt_date(fecha_pago) - fecha_entrada_fmt = _fmt_date(row.get("C11")) - fecha_emision_fmt = _fmt_date(row.get("C12_emission")) - - # Aduana cruce (first 2-3 chars per Clarion) - aduana = str(row.get("C8") or "") - - return { - "TipoMovimiento": "Importación Temporal:", - "Pedimento": ped_impo, - "PedimentoR1": ped_r1, - "ClavePed": str(row.get("C48") or ""), # pedimento_code = Clave - "FechaPago": fecha_pago_fmt, - "FechaEntrada": fecha_entrada_fmt, - "FechaEmision": fecha_emision_fmt, - "Factura": "'" + str(row.get("C1") or ""), # leading ' for Excel - "Linea": str(row.get("C35") or ""), - "Tipo": "P" if es_subpartida == "P" else ("S" if es_subpartida == "S" else ""), - "NumParteClase": num_parte_display, - "NumParteFijo": str(row.get("C36") or ""), - "DescripcionEsp": _clean(row.get("C14") or ""), - "DescripcionIng": _clean(row.get("C15") or ""), - "CantidadOriginal": _fmt_num(cant_orig), - "UM": str(row.get("C19") or ""), - "PesoNeto": _fmt_num(peso_neto), - "ValorOriginal": _fmt_num(valor_orig), - "CantidadUsada": _fmt_num(cant_used), - "PesoUsado": _fmt_num(peso_usado), - "ValorUsado": _fmt_num(valor_usado), - "CantidadSaldo": _fmt_num(cant_saldo), - "PesoSaldo": _fmt_num(peso_saldo), - "ValorSaldo": _fmt_num(valor_saldo), - "FraccionImpo": fraccion, - "Preferencia": str(row.get("C27") or ""), - "PaisOrigen": str(row.get("C16") or ""), - "Sector": str(row.get("C29") or ""), - "Marca": str(row.get("C32") or ""), - "Modelo": str(row.get("C33") or ""), - "SeriesSolas": series_data["SeriesSolas"], - "Assets": "", # QAssetTag — pending mapping - "EDocument": str(row.get("C40") or ""), - "NumOperacionVU": str(row.get("C41") or ""), - "TipoMaqEquipo": str(row.get("C42") or ""), - "UbicacionMaq": str(row.get("C38") or ""), - "ModeloSeries": series_data["ModeloSeries"], - "NumParteSeries": series_data["NumParteSeries"], - "NoId": series_data["NoId"], - "Pedimento18": str(row.get("C43") or ""), - "AduanaCruce": "'" + aduana, - "NumEmbarque": str(row.get("C_embarque") or ""), - "UMTarifa": um_tarifa, - "IdType": str(row.get("C50") or ""), - "Secuencia": "", # not in Anexo76 models, leave blank - "FraccionAmericana": fraccion_ame, - "Proyecto": str(row.get("C_proyecto") or "") if filters.shelter else "", - "TipoCambio": tipo_cambio_str, - # Internal only (not written to CSV, used for ordering) - "_FechaFacturaRaw": row.get("C11"), - "_PedimentoRaw": ped_impo, - "_NumParteRaw": str(row.get("C36") or ""), - "_ClaseRaw": str(row.get("C13") or ""), - } - - -# --------------------------------------------------------------------------- -# CSV header definitions per range type (mirrors Clarion GTxt:Linea headers) -# --------------------------------------------------------------------------- - -# Common columns (all range types end with these) -_COMMON_END = [ - "FraccionImpo", "Preferencia", "PaisOrigen", "Sector", - "Marca", "Modelo", "SeriesSolas", "Assets", - "EDocument", "NumOperacionVU", "TipoMaqEquipo", - "UbicacionMaq", "ModeloSeries", "NumParteSeries", "NoId", - "Pedimento18", "AduanaCruce", "NumEmbarque", - "UMTarifa", "IdType", "Secuencia", "FraccionAmericana", - "Proyecto", "TipoCambio", -] - -# Column list per range type -COLUMNS_PED = [ - "TipoMovimiento", "Pedimento", "ClavePed", "FechaPago", "PedimentoR1", - "NumParteClase", "NumParteFijo", "DescripcionEsp", "DescripcionIng", - "FechaEntrada", "FechaEmision", "Factura", "Linea", "Tipo", - "CantidadOriginal", "UM", "PesoNeto", "ValorOriginal", - "CantidadUsada", "PesoUsado", "ValorUsado", - "CantidadSaldo", "PesoSaldo", "ValorSaldo", -] + _COMMON_END - -COLUMNS_FECHA = [ - "TipoMovimiento", "FechaEntrada", "Pedimento", "FechaPago", "ClavePed", "PedimentoR1", - "NumParteClase", "NumParteFijo", "DescripcionEsp", "DescripcionIng", - "Factura", "FechaEmision", "Linea", "Tipo", - "CantidadOriginal", "UM", "PesoNeto", "ValorOriginal", - "CantidadUsada", "PesoUsado", "ValorUsado", - "CantidadSaldo", "PesoSaldo", "ValorSaldo", -] + _COMMON_END - -COLUMNS_PAR = [ - "TipoMovimiento", "NumParteClase", "NumParteFijo", "DescripcionEsp", "DescripcionIng", - "FechaEntrada", "FechaEmision", "Factura", "Linea", "Tipo", - "Pedimento", "ClavePed", "FechaPago", "PedimentoR1", - "CantidadOriginal", "UM", "PesoNeto", "ValorOriginal", - "CantidadUsada", "PesoUsado", "ValorUsado", - "CantidadSaldo", "PesoSaldo", "ValorSaldo", -] + _COMMON_END - -COLUMNS_CLA = [ - "TipoMovimiento", "NumParteClase", "NumParteFijo", "DescripcionEsp", "DescripcionIng", - "FechaEntrada", "FechaEmision", "Factura", "Linea", "Tipo", - "Pedimento", "ClavePed", "FechaPago", "PedimentoR1", - "CantidadOriginal", "UM", "PesoNeto", "ValorOriginal", - "CantidadUsada", "PesoUsado", "ValorUsado", - "CantidadSaldo", "PesoSaldo", "ValorSaldo", -] + _COMMON_END - -_HEADER_LABELS: Dict[str, str] = { - "TipoMovimiento": "Tipo Movimiento", - "Pedimento": "Pedimento", - "PedimentoR1": "Pedimento Rectificacion", - "ClavePed": "Clave", - "FechaPago": "Fecha Pago", - "FechaEntrada": "Fecha Entrada", - "FechaEmision": "Fecha de Emisión", - "Factura": "Factura", - "Linea": "Línea", - "Tipo": "Tipo", - "NumParteClase": "Num. Parte/Clase", - "NumParteFijo": "Num.Parte", - "DescripcionEsp": "Descripción Español", - "DescripcionIng": "Descripción Inglés", - "CantidadOriginal": "Cantidad Orig.", - "UM": "U.M.", - "PesoNeto": "Peso Orig.", - "ValorOriginal": "Valor Orig.", - "CantidadUsada": "Cantidad Usada", - "PesoUsado": "Peso Usado", - "ValorUsado": "Valor Usado", - "CantidadSaldo": "Cantidad Saldo", - "PesoSaldo": "Peso Saldo", - "ValorSaldo": "Valor Saldo", - "FraccionImpo": "Fracción Arancelaria", - "Preferencia": "Preferencia", - "PaisOrigen": "País", - "Sector": "Sector", - "Marca": "Marca", - "Modelo": "Modelo", - "SeriesSolas": "Series", - "Assets": "Asset Tags", - "EDocument": "E-Document", - "NumOperacionVU": "Núm. Operación", - "TipoMaqEquipo": "Tipo de Activo Fijo", - "UbicacionMaq": "Ubicacion", - "ModeloSeries": "Modelo", - "NumParteSeries": "Num. Parte Serie", - "NoId": "Num ID", - "Pedimento18": "Pedimento 18", - "AduanaCruce": "Aduana Cruce", - "NumEmbarque": "Advalorem", # Clarion col position: NumEmbarque after AduanaCruce - "UMTarifa": "U.M. Tarifa", - "IdType": "ID TYPE", - "Secuencia": "Secuencia", - "FraccionAmericana": "Fracción Americana", - "Proyecto": "", # conditional (Shelter) - "TipoCambio": "Tipo de Cambio", -} - -_COLUMNS_BY_RANGE = { - "pedimento": COLUMNS_PED, - "payment_date": COLUMNS_FECHA, - "invoice_date": COLUMNS_FECHA, - "parts": COLUMNS_PAR, - "classes": COLUMNS_CLA, -} - -# --------------------------------------------------------------------------- -# Main fetch -# --------------------------------------------------------------------------- - -def fetch_saldos_data(db: Session, filters: SaldosFilter) -> List[Dict]: - range_map = { - "pedimento": _query_ped, - "payment_date": _query_fpp, - "invoice_date": _query_ffa, - "parts": _query_par, - "classes": _query_cla, - } - - builder = range_map.get(filters.range_type, _query_ped) - sql, params = builder(filters) - - if filters.asset_type: - sql = sql.replace( - "ORDER BY", - "AND cl.material_key = :asset_type\n ORDER BY", - ) - params["asset_type"] = filters.asset_type - - try: - result = db.execute(text(sql), params) - raw_rows = [dict(r._mapping) for r in result] - except Exception as e: - logger.error(f"fetch_saldos_data SQL error [{filters.range_type}]: {e}", exc_info=True) - return [] - - num_parte_field = "C13" if filters.range_type == "classes" else "C36" - - processed: List[Dict] = [] - for raw in raw_rows: - # In-memory filters (Clarion CYCLE) - if filters.buyer and str(raw.get("C_sold_to") or "") != filters.buyer: - continue - if filters.provider and str(raw.get("C3") or "") != filters.provider: - continue - if filters.location and str(raw.get("C38") or "") != filters.location: - continue - if filters.asset_class and filters.range_type != "classes": - if str(raw.get("C13") or "") != filters.asset_class: - continue - if filters.pedimento_code and str(raw.get("C48") or "") != filters.pedimento_code: - continue - - row_data = _build_row(raw, db, filters, num_parte_field=num_parte_field) - if row_data is not None: - processed.append(row_data) - - logger.info(f"fetch_saldos_data: {len(processed)} rows (range_type={filters.range_type})") - return processed - - -# --------------------------------------------------------------------------- -# CSV builder -# --------------------------------------------------------------------------- - -def generate_saldos_csv(filters: SaldosFilter, db: Optional[Session] = None) -> str: - """Build the CSV string matching Clarion's GENERA_EXCEL_CSV_* output.""" - output = io.StringIO() - writer = csv.writer(output) - - columns = _COLUMNS_BY_RANGE.get(filters.range_type, COLUMNS_PED) - headers = [_HEADER_LABELS.get(c, c) for c in columns] - if filters.shelter: - # Enforce header label if Shelter is active - for i, col in enumerate(columns): - if col == "Proyecto": - headers[i] = "Proyecto" - - # Dynamically build legacy title - range_map_title = { - "pedimento": "Por RANGO de PEDIMENTO", - "payment_date": "Por RANGO de FECHA PAGO", - "invoice_date": "Por RANGO de FECHA FAC.", - "parts": "Por PARTE", - "classes": "Por CLASE", - } - rango_str = range_map_title.get(filters.range_type, "") - - report_type_map = { - "normal": "Normal", - "detailed": "Detallado", - "with_download": "Con Descargas", - } - tipo_reporte_str = report_type_map.get(filters.report_type, "Normal") - agrupacion_str = "Por CLASE" if filters.print_class else "Por PARTE" - - title_row = f"REPORTE DE SALDOS DE ACTIVO FIJO {rango_str}, {tipo_reporte_str} {agrupacion_str}" - - # Fetch company profile if DB available - c_name = "" - c_rfc = "" - c_immex = "" - main_addr_str1 = "" - main_addr_str2 = "" - main_addr_str3 = "" - ind_addr_str1 = "" - ind_addr_str2 = "" - ind_addr_str3 = "" - - if db is not None: - try: - from api.v1.modules.a76.general_catalogs.company.models import Company - - query = db.query(Company) - if filters.company_id: - query = query.filter(Company.id == filters.company_id) - - company = query.first() - if company: - c_name = company.name or "" - c_rfc = company.rfc or "" - # IMMEX often uses both program and program_number - p_base = company.program or "" - p_num = company.program_number or "" - if p_base and p_num: - c_immex = f"{p_base}-{p_num}" - else: - c_immex = p_base or p_num or "" - - for addr in company.addresses: - if addr.address_type == "main": - main_addr_str1 = f"Domicilio Fiscal: {addr.street or ''} Ext. Num: {addr.exterior_number or ''}".strip() - main_addr_str2 = f"{addr.neighborhood or ''} Código Postal: {addr.postal_code or ''}".strip() - main_addr_str3 = f"{addr.city or ''} {addr.state or ''}".strip() - elif addr.address_type == "industrial": - ind_addr_str1 = f"Domicilio Industrial: {addr.street or ''} Ext. Num: {addr.exterior_number or ''}".strip() - ind_addr_str2 = f"{addr.neighborhood or ''} Código Postal: {addr.postal_code or ''}".strip() - ind_addr_str3 = f"{addr.city or ''} {addr.state or ''}".strip() - except Exception as e: - logger.warning(f"Could not load company info for headers: {e}") - - # Format Spanish dates correctly (e.g. 5 MAR 2026 Hora Generación: 09:09PM) - # Use America/Mexico_City timezone to match user's local time (-06:00) - tz = pytz.timezone('America/Mexico_City') - now = datetime.now(tz) - mo_es = {1:"ENE", 2:"FEB", 3:"MAR", 4:"ABR", 5:"MAY", 6:"JUN", 7:"JUL", 8:"AGO", 9:"SEP", 10:"OCT", 11:"NOV", 12:"DIC"}[now.month] - fecha_gen = f"{now.day} {mo_es} {now.year}" - hora_gen = now.strftime("%I:%M%p").upper() - - # Write Title block strictly matching Clarion - writer.writerow([title_row]) - if c_name: writer.writerow([c_name]) - if main_addr_str1: writer.writerow([main_addr_str1]) - if main_addr_str2: writer.writerow([main_addr_str2]) - if main_addr_str3: writer.writerow([main_addr_str3]) - if ind_addr_str1: writer.writerow([ind_addr_str1]) - if ind_addr_str2: writer.writerow([ind_addr_str2]) - if ind_addr_str3: writer.writerow([ind_addr_str3]) - writer.writerow([f"R.F.C: {c_rfc}"]) - writer.writerow([f"IMMEX: {c_immex}"]) - writer.writerow([f"Fecha Generación: {fecha_gen} Hora Generación: {hora_gen}"]) - writer.writerow(["PROVEEDOR DE SOFTWARE: ADUANASOFT"]) - writer.writerow([]) - writer.writerow([]) - - writer.writerow(headers) - writer.writerow([]) # blank second header row (Clarion: ConDescarga=0 → blank) - - if db is not None: - rows = fetch_saldos_data(db, filters) - else: - rows = [{c: f"DEMO-{c}" for c in columns}] - - for row in rows: - writer.writerow([row.get(col, "") for col in columns]) - - writer.writerow([]) - return output.getvalue() diff --git a/backend/api/v1/modules/a76/reports/movements/saldos/routes.py b/backend/api/v1/modules/a76/reports/movements/saldos/routes.py deleted file mode 100644 index 375ce60f..00000000 --- a/backend/api/v1/modules/a76/reports/movements/saldos/routes.py +++ /dev/null @@ -1,92 +0,0 @@ -""" -FastAPI routes for Saldos Temporales report. - -Endpoints: - POST /generate – trigger async CSV generation (returns task_id) - GET /task/{id} – poll task status -""" -import logging -from fastapi import APIRouter, Depends, Query -from sqlalchemy.orm import Session - -from core.database import get_core_db -from core.security import get_current_user -from api.v1.modules.core.tasks_tracking import track_and_dispatch -from .schemas import SaldosFilter - -logger = logging.getLogger(__name__) - -router = APIRouter(tags=["Reports - Saldos Temporales"]) - - -@router.post( - "/generate", - summary="Generate Saldos Temporales CSV (Async)", - description=( - "Triggers a background Celery task to generate the Saldos Temporales CSV. " - "Returns a `task_id` that can be polled via `/task/{task_id}`." - ), -) -def generate_saldos_report_async( - filters: SaldosFilter, - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - """Enqueue the Celery task and return its ID.""" - from .tasks import generate_saldos_temporales_async - from core.security import validate_access_to_resource - - logger.info( - f"User {current_user.get('preferred_username', 'unknown')} " - f"triggering Saldos Temporales report generation" - ) - - # validate_access_to_resource returns the integer tenant_id from DB - tenant_id = validate_access_to_resource(db, company_id, current_user, ["report.process"]) - - # Inject scoping fields (not from the UI body) - filters.company_id = company_id - filters.tenant_id = tenant_id - - filter_data = filters.model_dump() - user_email = current_user.get("email") - - task = track_and_dispatch( - db=db, - task=generate_saldos_temporales_async, - tenant_id=tenant_id, - company_id=company_id, - requested_by_user=current_user.get("preferred_username") or current_user.get("email") or current_user.get("sub"), - task_name="generate_saldos_temporales_async", - task_group="reports", - task_origin="a76/reports/movements/saldos", - args=[filter_data, user_email], - ) - return {"task_id": task.id} - - -@router.get( - "/task/{task_id}", - summary="Get Saldos Temporales Task Status", - description="Poll the status of a background Saldos Temporales generation task.", -) -def get_saldos_task_status(task_id: str): - """Return current status and (when ready) the result of the Celery task.""" - from celery.result import AsyncResult - from core.celery_app import celery_app - - task_result = AsyncResult(task_id, app=celery_app) - - response: dict = { - "task_id": task_id, - "status": task_result.status, - } - - if task_result.state == "PROCESSING": - response["meta"] = task_result.info - - if task_result.ready(): - response["result"] = task_result.result - - return response diff --git a/backend/api/v1/modules/a76/reports/movements/saldos/schemas.py b/backend/api/v1/modules/a76/reports/movements/saldos/schemas.py deleted file mode 100644 index 5eaeda47..00000000 --- a/backend/api/v1/modules/a76/reports/movements/saldos/schemas.py +++ /dev/null @@ -1,52 +0,0 @@ -""" -Schemas for Saldos Temporales report. -""" -from typing import Optional -from pydantic import BaseModel - - -class SaldosFilter(BaseModel): - """ - Filter parameters for the Saldos Temporales CSV report. - Maps directly to the UI options chosen by the user. - """ - # Range / Period - range_type: str = "pedimento" # pedimento | payment_date | invoice_date | parts | classes - start_date: Optional[str] = None # identifier "from" (e.g. pedimento number or part string) - end_date: Optional[str] = None # identifier "to" - date_start: Optional[str] = None # Explicit start date - date_end: Optional[str] = None # Explicit end date - - # Column 2 – Filtros e Identificadores - currency: str = "foreign" # foreign | national - report_type: str = "normal" # normal | detailed | with_download - exchange_rate: str = "invoice_date" # invoice_date | payment_date - omit_low_balance: bool = False - balance_type: str = "normal" # normal | repair | both - level: str = "all" # partida | subpartida | all - - # Column 3 – Configuración Final - end_date_as_cutoff: bool = False - show_pending_series: bool = False - include_asset_tag_images: bool = False - use_large_asset_tag_icons: bool = False - send_email: bool = False - julian_date: bool = False - include_regla_octava: bool = False - shelter: bool = False - - # Print options (Column 1) - print_part: bool = False - print_class: bool = False - - # Optional identifiers populated from catalog selectors - provider: Optional[str] = None - buyer: Optional[str] = None - asset_class: Optional[str] = None # Clase - asset_type: Optional[str] = None # Parte - location: Optional[str] = None - pedimento_code: Optional[str] = None # Clave de Pedimento - - # Security / scoping (set by the route, not by the UI) - company_id: Optional[int] = None - tenant_id: Optional[int] = None diff --git a/backend/api/v1/modules/a76/reports/movements/saldos/tasks.py b/backend/api/v1/modules/a76/reports/movements/saldos/tasks.py deleted file mode 100644 index 070543e1..00000000 --- a/backend/api/v1/modules/a76/reports/movements/saldos/tasks.py +++ /dev/null @@ -1,103 +0,0 @@ -""" -Celery task for asynchronous Saldos Temporales CSV generation. -""" -import base64 -import logging -from typing import Dict, Any - -from core.celery_app import celery_app -from core.email import EmailService -from datetime import datetime - -from .schemas import SaldosFilter -from .csv_utils import generate_saldos_csv - -logger = logging.getLogger(__name__) - - -@celery_app.task(bind=True, name="generate_saldos_temporales_async") -def generate_saldos_temporales_async( - self, - filter_data: Dict[str, Any], - user_email: str = None -): - """ - Async Celery task: generate Saldos Temporales CSV and optionally e-mail it. - """ - try: - # 1. Progress – initialising - self.update_state( - state="PROCESSING", - meta={"current": 10, "total": 100, "status": "Inicializando reporte de Saldos Temporales..."}, - ) - - # 2. Re-construct filter - filters = SaldosFilter(**filter_data) - - # 3. Fetch + generate CSV (real DB session) - self.update_state( - state="PROCESSING", - meta={"current": 40, "total": 100, "status": "Generando datos de Saldos Temporales..."}, - ) - logger.info(f"Saldos task: building CSV (range_type={filters.range_type})") - - from core.database import CoreSessionLocal - db = CoreSessionLocal() - try: - csv_content = generate_saldos_csv(filters, db=db) - finally: - db.close() - - # 4. Optional e-mail - email_sent = False - if filters.send_email and user_email: - self.update_state( - state="PROCESSING", - meta={"current": 85, "total": 100, "status": "Enviando correo electrónico..."}, - ) - try: - import asyncio - from asgiref.sync import async_to_sync - - filename = ( - f"saldos_temporales_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv" - ) - result = async_to_sync(EmailService.send_report_email)( - recipient_email=user_email, - subject=f"Saldos Temporales – {datetime.now().strftime('%d/%m/%Y')}", - body_text="Se adjunta el reporte de Saldos Temporales generado.", - csv_content=csv_content, - filename=filename, - ) - email_sent = bool(result) - except Exception as e: - logger.error(f"Saldos task: email error: {e}") - - # 5. Encode to base64 and return - self.update_state( - state="PROCESSING", - meta={"current": 95, "total": 100, "status": "Finalizando..."}, - ) - - content_b64 = base64.b64encode(csv_content.encode("utf-8")).decode("utf-8") - filename = f"saldos_temporales_{datetime.now().strftime('%Y%m%d')}.csv" - - return { - "status": "success", - "file_name": filename, - "content": content_b64, - "media_type": "text/csv", - "email_sent": email_sent, - } - - except Exception as e: - logger.error(f"Error in generate_saldos_temporales_async: {e}", exc_info=True) - self.update_state( - state="FAILURE", - meta={ - "exc_type": type(e).__name__, - "exc_message": str(e), - "custom": "Error generating Saldos Temporales report", - }, - ) - raise diff --git a/backend/api/v1/modules/a76/reports/movements/vencimiento/__init__.py b/backend/api/v1/modules/a76/reports/movements/vencimiento/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/backend/api/v1/modules/a76/reports/movements/vencimiento/routes.py b/backend/api/v1/modules/a76/reports/movements/vencimiento/routes.py deleted file mode 100644 index 613f1e8c..00000000 --- a/backend/api/v1/modules/a76/reports/movements/vencimiento/routes.py +++ /dev/null @@ -1,36 +0,0 @@ -""" -FastAPI routes for Reporte de Vencimiento. -""" -import logging -from typing import Any - -from fastapi import APIRouter, Body, Depends, Query -from fastapi.responses import StreamingResponse -from sqlalchemy.orm import Session - -from core.database import get_core_db -from core.security import get_current_user, validate_access_to_resource - -from .schemas import VencimientoFilter -from .service import VencimientoReportService - -logger = logging.getLogger(__name__) - -router = APIRouter(tags=["Reports - Vencimiento"]) - - -@router.post( - "/generate", - summary="Generate Reporte de Vencimiento CSV", -) -def generate_vencimiento_report( - filters: VencimientoFilter = Body(...), - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: Any = Depends(get_current_user), -) -> StreamingResponse: - tenant_id = validate_access_to_resource(db, company_id, current_user) - filters.company_id = company_id - filters.tenant_id = tenant_id - service = VencimientoReportService() - return service.generate_csv_response(db=db, filters=filters) diff --git a/backend/api/v1/modules/a76/reports/movements/vencimiento/schemas.py b/backend/api/v1/modules/a76/reports/movements/vencimiento/schemas.py deleted file mode 100644 index df758853..00000000 --- a/backend/api/v1/modules/a76/reports/movements/vencimiento/schemas.py +++ /dev/null @@ -1,36 +0,0 @@ -""" -Schemas for Reporte de Vencimiento. -""" -from typing import Optional -from pydantic import BaseModel, Field - - -class VencimientoFilter(BaseModel): - """ - Filter parameters for the Reporte de Vencimiento CSV. - Mirrors the UI from the legacy REPORTE DE VENCIMIENTO dialog. - """ - # Main parameter from the dialog header - days_ahead: int = Field(default=0, ge=0, description="Facturas próximas a vencer en N días") - - # TIPO DE MONEDA - currency: str = Field(default="foreign", description="foreign | national") - - # FILTRAR POR - client_id: Optional[int] = Field(default=None, description="ID del cliente (sold_to_id)") - - # OMITIR CANTIDADES - min_balance: float = Field(default=0.0, description="Omitir balances menores a este valor (0 = no omitir)") - - # FILTRO OPCIONAL - conforme_anexo_31: bool = Field(default=False, description="Filtrar conforme al Anexo 31") - usar_fecha_corte: bool = Field(default=False, description="Usar fecha de corte en lugar de end_date del pedimento") - fecha_corte: Optional[str] = Field(default=None, description="Fecha de corte ISO (YYYY-MM-DD) cuando usar_fecha_corte=True") - - # OUTPUT - send_email: bool = Field(default=False) - julian_date: bool = Field(default=False) - - # Scoping (set by the route, NOT by UI) - company_id: Optional[int] = None - tenant_id: Optional[int] = None diff --git a/backend/api/v1/modules/a76/reports/movements/vencimiento/service.py b/backend/api/v1/modules/a76/reports/movements/vencimiento/service.py deleted file mode 100644 index 060485d6..00000000 --- a/backend/api/v1/modules/a76/reports/movements/vencimiento/service.py +++ /dev/null @@ -1,412 +0,0 @@ -""" -Service for Reporte de Vencimiento CSV generation. - -Translated from Clarion LLENA_SALDOS_VENC routine. - -Logic: - - Fetch all temporary import item lines whose pedimento end_date - is <= baseline + days_ahead AND still have a positive balance. - - baseline = fecha_corte (if usar_fecha_corte) else today. - - No lower bound on end_date (mirrors Clarion FechaInicio = 1). - - Output: CSV with 23 columns matching the legacy EXPORTAR A format. -""" -import csv -import io -import logging -from datetime import date, timedelta -from decimal import Decimal, ROUND_HALF_UP, InvalidOperation -from typing import Optional - -from fastapi.responses import StreamingResponse -from sqlalchemy import text -from sqlalchemy.orm import Session - -from .schemas import VencimientoFilter - -logger = logging.getLogger(__name__) - - -# --------------------------------------------------------------------------- -# Decimal / format helpers -# --------------------------------------------------------------------------- - -def _d(value, decimals: int = 8) -> Decimal: - try: - exp = Decimal(10) ** -decimals - return Decimal(str(value or 0)).quantize(exp, rounding=ROUND_HALF_UP) - except (InvalidOperation, TypeError): - return Decimal(0) - - -def _fmt_date(val, julian: bool = False) -> str: - """Format date value. - julian=False → MM/DD/YY (Clarion @D06) - julian=True → raw ISO string as stored in DB - """ - if val is None: - return "" - if julian: - return str(val) # ISO: 2025-03-15 - if hasattr(val, "strftime"): - return val.strftime("%m/%d/%y") - return str(val) - - -def _fmt_num(d: Decimal) -> str: - """Format Decimal as plain fixed-point string (no scientific notation).""" - if d is None: - return "" - # Use fixed-point format to prevent scientific notation for large values - s = format(d, "f") - if "." in s: - s = s.rstrip("0").rstrip(".") - return "0" if s in ("", "-0") else s - - -def _text(val) -> str: - """Force a value to plain text with ' prefix so Excel never coerces it - to a number (same as Clarion ''''&CLIP(...) pattern).""" - return "'" + _clean(val) - - -def _clean(text_val) -> str: - """Remove commas and newlines (SACARCOMASENTERS equivalent).""" - return str(text_val or "").replace("\n", " ").replace("\r", "").replace(",", " ").strip() - - -# --------------------------------------------------------------------------- -# SQL -# --------------------------------------------------------------------------- - -_VENCIMIENTO_SQL = """ -SELECT - ih.invoice_number AS "C1", - ih.company_id AS "company_id", - CONCAT(ped.year,'-',ped.license,'-',ped.pedimento_number) AS "C2", - COALESCE( - CONCAT(ped_r1.year,'-',ped_r1.license,'-',ped_r1.pedimento_number), - '' - ) AS "C_ped_r1", - pd.payment_date AS "C7", - pd.end_date AS "C_end_date", - ih.invoice_date AS "C11", - cl.id AS "C13_class_id", - COALESCE(cl.us_fraction,'') AS "C_frac_ame", - REPLACE(REPLACE(COALESCE(ild.description_english,''),CHR(10),''),CHR(13),' ') AS "C15", - COALESCE(ilc.origin_country,'') AS "C16", - COALESCE(ilq.quantity, 0) AS "C17", - COALESCE(bal.qty_used, 0) AS "C18", - COALESCE(uom.code,'') AS "C19", - COALESCE(ilf.value_mxn, 0) AS "C20", - COALESCE(bal.val_mn_used, 0) AS "C21", - COALESCE(ilf.value_usd, 0) AS "C22", - COALESCE(bal.val_me_used, 0) AS "C23", - COALESCE(ilq.net_weight, 0) AS "C24", - COALESCE(ilc.fraction,'') AS "C26", - COALESCE(ilc.fraction_type,'') AS "C_frac_type", - COALESCE(ilc.sector,'') AS "C_sector", - COALESCE(p.part_number,'') AS "C36", - COALESCE(p.commercial_part_number,'') AS "C_part_ref", - icm.sold_to_id AS "C_sold_to_id" -FROM a76.item_lines il -JOIN a76.invoice_header ih ON ih.id = il.invoice_id -JOIN a76.invoice_compliance_mx icm ON icm.invoice_id = ih.id -LEFT JOIN a76.pedimentos ped ON ped.id = icm.pedimento_id -LEFT JOIN a76.pedimentos ped_r1 ON ped_r1.id = icm.pedimento_r1 -LEFT JOIN a76.pedimento_dates pd ON pd.pedimento_id = ped.id -LEFT JOIN a76.classes cl ON cl.id = il.class_id -LEFT JOIN a76.item_line_quantities ilq ON ilq.item_line_id = il.id -LEFT JOIN a76.item_line_financials ilf ON ilf.item_line_id = il.id -LEFT JOIN a76.item_line_customs ilc ON ilc.item_line_id = il.id -LEFT JOIN a76.item_line_descriptions ild ON ild.item_line_id = il.id -LEFT JOIN a76.parts p ON p.id = il.part_number_id -LEFT JOIN a76.units_of_measure uom ON uom.id = il.unit_of_measure -LEFT JOIN ( - SELECT - import_item_line_id, - SUM(CASE WHEN movement_type IN ('consumption','waste','scrap','destruction') THEN quantity - WHEN movement_type = 'return' THEN -quantity - ELSE 0 END) AS qty_used, - SUM(CASE WHEN movement_type IN ('consumption','waste','scrap','destruction') THEN COALESCE(value_me, 0) - WHEN movement_type = 'return' THEN -COALESCE(value_me, 0) - ELSE 0 END) AS val_me_used, - SUM(CASE WHEN movement_type IN ('consumption','waste','scrap','destruction') THEN COALESCE(value_mn, 0) - WHEN movement_type = 'return' THEN -COALESCE(value_mn, 0) - ELSE 0 END) AS val_mn_used - FROM a24.balance_movement - WHERE tenant_id = :tenant_id - AND (:bal_date_cutoff IS NULL OR operation_date <= :bal_date_cutoff) - GROUP BY import_item_line_id -) bal ON bal.import_item_line_id = il.id -WHERE ih.tenant_id = :tenant_id - AND ih.operation_type = 'imp' - AND ih.company_id = :company_id - AND COALESCE(ilq.quantity, 0) > 0 - AND COALESCE(ilq.quantity, 0) - COALESCE(bal.qty_used, 0) > 0 - AND pd.end_date <= :date_to - {client_filter} - {anexo31_filter} -ORDER BY pd.end_date, - CONCAT(ped.year, ped.customs_office, ped.license, ped.pedimento_number) -""" - -# CSV column headers (exact match to legacy EXPORTAR A) -CSV_COLUMNS = [ - "Num. Factura Impo", - "Pedimento", - "Pedimento Rectificado", - "Fecha Entrada", - "Fecha Vencimiento", - "Estatus/Dias", - "Num. Parte", - "Descripción", - "Pais", - "Cant. Original", - "Cant. Usada", - "Cant. Saldo", - "U.M.", - "Valor Original", - "Valor Usado", - "Valor Saldo", - "Peso Original", - "Peso Usado", - "Peso Saldo", - "Fraccion", - "Tipo Fraccion", - "Sector", - "Fraccion Americana", -] - - -# --------------------------------------------------------------------------- -# Company header (IMPRIMIR_EMPRESA equivalent) -# --------------------------------------------------------------------------- - -def _fetch_company_header_rows(db: Session, company_id: int) -> list[str]: - """ - Return a list of strings representing the company header lines. - All fields are always emitted with their label, even when empty. - """ - row = db.execute( - text(""" - SELECT - c.name, c.rfc, c.program, c.program_number, - ca.street, ca.exterior_number, ca.neighborhood, - ca.postal_code, ca.city, ca.state - FROM a76.company c - LEFT JOIN a76.company_address ca - ON ca.company_id = c.id AND ca.address_type = 'main' - WHERE c.id = :cid - LIMIT 1 - """), - {"cid": company_id}, - ).fetchone() - - if not row: - return [] - - name, rfc, program, prog_num, street, ext_num, neighborhood, postal, city, state = row - - def s(v) -> str: - return str(v).strip() if v else "" - - lines: list[str] = [] - - # Line 1: Company name (no label, same as legacy) - lines.append(s(name)) - - # Line 2: Dirección (always emitted) - dir_line = f"Dirección: {s(street)}" - if s(ext_num): - dir_line += f" Ext. Num: {s(ext_num)}" - lines.append(dir_line) - - # Line 3: Colonia + Código Postal (always emitted) - lines.append(f"Colonia: {s(neighborhood)} Código Postal: {s(postal)}") - - # Line 4: Ciudad + Estado (always emitted) - ciudad_estado = f"{s(city)} {s(state)}".strip() - lines.append(f"Ciudad: {ciudad_estado}") - - # Line 5: RFC (always emitted) - lines.append(f"R.F.C: {s(rfc)}") - - # Line 6: Program (always emitted) - prog_label = "SICEX" if s(program) == "Maquila" else (s(program) or "IMMEX") - lines.append(f"{prog_label}: {s(prog_num)}") - - return lines - - -# --------------------------------------------------------------------------- -# Core CSV generation -# --------------------------------------------------------------------------- - -def generate_vencimiento_csv(filters: VencimientoFilter, db: Session) -> bytes: - """Build the CSV bytes for the Reporte de Vencimiento.""" - today = date.today() - - # Legacy exact behavior: - # FechaFinal = Today() + Loc:Dias - # IF UsarFechaCorte AND FechaCorte <> '' THEN FechaFinal = FechaCorte - # → fecha_corte completely replaces today+days_ahead; days_ahead is ignored when usar_fecha_corte=True - if filters.usar_fecha_corte and filters.fecha_corte: - try: - date_to = date.fromisoformat(filters.fecha_corte) - except ValueError: - date_to = today + timedelta(days=filters.days_ahead) - else: - date_to = today + timedelta(days=filters.days_ahead) - - # Build dynamic filter fragments - client_filter = "" - anexo31_filter = "" - # Legacy: bal date filter only when usar_fecha_corte=1 (count all discharges otherwise) - bal_date_cutoff = str(date_to) if filters.usar_fecha_corte and filters.fecha_corte else None - params: dict = { - "tenant_id": filters.tenant_id, - "company_id": filters.company_id, - "date_to": str(date_to), - "bal_date_cutoff": bal_date_cutoff, - } - - if filters.client_id: - client_filter = "AND icm.sold_to_id = :client_id" - params["client_id"] = filters.client_id - - if filters.conforme_anexo_31: - # Mirrors legacy: only invoices marked to generate balances (IMMEX conforme) - anexo31_filter = "AND icm.generate_balances = TRUE" - - sql = _VENCIMIENTO_SQL.format( - client_filter=client_filter, - anexo31_filter=anexo31_filter, - ) - - rows = db.execute(text(sql), params).mappings().fetchall() - - # Currency flag: ME = foreign (USD), MN = national (MXP) - use_me = filters.currency == "foreign" - min_bal = _d(filters.min_balance) - julian = filters.julian_date - - output = io.StringIO() - writer = csv.writer(output, lineterminator="\n") - - # ── Header block (IMPRIMIR_EMPRESA) ────────────────────────────────────── - writer.writerow(["", "", "", "", "REPORTE DE VENCIMIENTO"]) - - company_lines = _fetch_company_header_rows(db, filters.company_id) - for line in company_lines: - writer.writerow(["", "", "", "", line]) - - writer.writerow(["", ""]) # blank separator - writer.writerow(CSV_COLUMNS) # column headers - writer.writerow(["", ""]) # blank separator after headers - - # ── Data rows ──────────────────────────────────────────────────────────── - for row in rows: - # -- Quantities -- - cant_orig = _d(row["C17"]) - cant_used = _d(row["C18"]) - cant_saldo = cant_orig - cant_used - - # Skip rows with zero or negative balance - # Legacy: If CantidadOmitir <> 0 Then If CantidadSaldo <= CantidadOmitir Then Cycle - if cant_saldo <= 0: - continue - if min_bal > 0 and cant_saldo <= min_bal: - continue - - # -- Peso (proportional calculation, mirrors legacy formula) -- - peso_neto = _d(row["C24"]) - if cant_orig != 0: - peso_usado = (cant_used * peso_neto) / cant_orig - else: - peso_usado = Decimal(0) - peso_saldo = peso_neto - peso_usado - - # -- Valor según moneda (ME = foreign, MN = national) -- - # Legacy (UsarDescargos=1): ValorUsado = (CantUsada x ValorOrig) / CantOrig (proporcional, igual que peso) - if use_me: - valor_orig = _d(row["C22"]) - else: - valor_orig = _d(row["C20"]) - if cant_orig != 0: - valor_usado = (cant_used * valor_orig) / cant_orig - else: - valor_usado = Decimal(0) - valor_saldo = valor_orig - valor_usado - - # -- TextoVencimiento (días vs HOY, not baseline) -- - end_date = row.get("C_end_date") - if end_date is not None: - if hasattr(end_date, "date"): - end_date = end_date.date() - diff = (end_date - today).days - if diff < 0: - texto_venc = "VENCIDO" - elif diff == 0: - texto_venc = "VENCE HOY" - else: - texto_venc = str(diff) - else: - texto_venc = "" - - writer.writerow([ - _text(row["C1"]), # Num. Factura Impo (prefijo ' para Excel) - _text(row["C2"]), # Pedimento - _text(row["C_ped_r1"]), # Pedimento Rectificado - _fmt_date(row["C11"], julian), # Fecha Entrada - _fmt_date(row["C_end_date"], julian), # Fecha Vencimiento - texto_venc, # Estatus/Dias - _text(row["C36"]), # Num. Parte (prefijo ' para Excel) - _clean(row["C15"]), # Descripción - _clean(row["C16"]), # Pais - _fmt_num(cant_orig), # Cant. Original - _fmt_num(cant_used), # Cant. Usada - _fmt_num(cant_saldo), # Cant. Saldo - _clean(row["C19"]), # U.M. - _fmt_num(valor_orig), # Valor Original - _fmt_num(valor_usado), # Valor Usado - _fmt_num(valor_saldo), # Valor Saldo - _fmt_num(peso_neto), # Peso Original - _fmt_num(peso_usado), # Peso Usado - _fmt_num(peso_saldo), # Peso Saldo - _text(row["C26"]), # Fraccion (código arancelario, forzar texto) - _clean(row["C_frac_type"]), # Tipo Fraccion - _clean(row["C_sector"]), # Sector - _text(row["C_frac_ame"]), # Fraccion Americana (código arancelario, forzar texto) - ]) - - return output.getvalue().encode("utf-8-sig") - - -# --------------------------------------------------------------------------- -# Service class (FastAPI integration) -# --------------------------------------------------------------------------- - -class VencimientoReportService: - - def generate_csv_response( - self, db: Session, filters: VencimientoFilter - ) -> StreamingResponse: - today = date.today() - - if filters.usar_fecha_corte and filters.fecha_corte: - try: - date_to = date.fromisoformat(filters.fecha_corte) - except ValueError: - date_to = today + timedelta(days=filters.days_ahead) - else: - date_to = today + timedelta(days=filters.days_ahead) - - filename = f"vencimiento_{today.isoformat()}_{date_to.isoformat()}.csv" - - csv_bytes = generate_vencimiento_csv(filters, db) - - return StreamingResponse( - iter([csv_bytes]), - media_type="text/csv", - headers={"Content-Disposition": f'attachment; filename="{filename}"'}, - ) diff --git a/backend/api/v1/modules/a76/reports/movements/vencimiento/tasks.py b/backend/api/v1/modules/a76/reports/movements/vencimiento/tasks.py deleted file mode 100644 index 03f8a6aa..00000000 --- a/backend/api/v1/modules/a76/reports/movements/vencimiento/tasks.py +++ /dev/null @@ -1,107 +0,0 @@ -""" -Celery task for asynchronous Vencimiento CSV generation. -Mirrors the pattern of generate_saldos_temporales_async. -""" -import base64 -import logging -from datetime import datetime -from typing import Dict, Any - -from core.celery_app import celery_app -from core.email import EmailService - -from .schemas import VencimientoFilter -from .service import generate_vencimiento_csv - -logger = logging.getLogger(__name__) - - -@celery_app.task(bind=True, name="generate_vencimiento_csv_async") -def generate_vencimiento_csv_async( - self, - filter_data: Dict[str, Any], - user_email: str = None, -): - """ - Async Celery task: generate Reporte de Vencimiento CSV and optionally e-mail it. - """ - try: - # 1. Inicializando - self.update_state( - state="PROCESSING", - meta={"current": 10, "total": 100, "status": "Inicializando reporte de Vencimiento..."}, - ) - - # 2. Re-construir filtro - filters = VencimientoFilter(**filter_data) - - # 3. Generar CSV con sesión de BD propia - self.update_state( - state="PROCESSING", - meta={"current": 40, "total": 100, "status": "Generando datos de Vencimiento..."}, - ) - logger.info( - f"Vencimiento task: building CSV " - f"(days_ahead={filters.days_ahead}, currency={filters.currency})" - ) - - from core.database import CoreSessionLocal - db = CoreSessionLocal() - try: - csv_bytes = generate_vencimiento_csv(filters, db) - finally: - db.close() - - # csv_bytes ya viene como bytes (utf-8-sig), necesitamos el string para email - csv_content = csv_bytes.decode("utf-8-sig") - - # 4. Envío de correo opcional - email_sent = False - if filters.send_email and user_email: - self.update_state( - state="PROCESSING", - meta={"current": 85, "total": 100, "status": "Enviando correo electrónico..."}, - ) - try: - from asgiref.sync import async_to_sync - - filename = f"reporte_vencimiento_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv" - result = async_to_sync(EmailService.send_report_email)( - recipient_email=user_email, - subject=f"Reporte de Vencimiento – {datetime.now().strftime('%d/%m/%Y')}", - body_text="Se adjunta el Reporte de Vencimiento generado.", - csv_content=csv_content, - filename=filename, - ) - email_sent = bool(result) - except Exception as e: - logger.error(f"Vencimiento task: email error: {e}") - - # 5. Codificar a base64 y retornar - self.update_state( - state="PROCESSING", - meta={"current": 95, "total": 100, "status": "Finalizando..."}, - ) - - content_b64 = base64.b64encode(csv_bytes).decode("utf-8") - filename = f"reporte_vencimiento_{datetime.now().strftime('%Y%m%d')}.csv" - - return { - "status": "success", - "file_name": filename, - "content": content_b64, - "media_type": "text/csv", - "email_sent": email_sent, - } - - except Exception as e: - logger.error(f"Error in generate_vencimiento_csv_async: {e}", exc_info=True) - self.update_state( - state="FAILURE", - meta={ - "exc_type": type(e).__name__, - "exc_message": str(e), - "custom": "Error generating Vencimiento report", - }, - ) - raise diff --git a/backend/api/v1/modules/a76/reports/xml_optima/__init__.py b/backend/api/v1/modules/a76/reports/xml_optima/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/backend/api/v1/modules/a76/reports/xml_optima/routes.py b/backend/api/v1/modules/a76/reports/xml_optima/routes.py deleted file mode 100644 index 56660515..00000000 --- a/backend/api/v1/modules/a76/reports/xml_optima/routes.py +++ /dev/null @@ -1,31 +0,0 @@ -import base64 -from typing import Dict, Any -from fastapi import APIRouter, Depends, HTTPException -from sqlalchemy.orm import Session - -from core.database import get_core_db -from core.security import get_current_user - -from .schemas import XmlOptimaExpoRequest -from .service import XmlOptimaService - -router = APIRouter() - - -@router.post("/exportacion/generate") -async def generate_optima_expo( - request: XmlOptimaExpoRequest, - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - result = XmlOptimaService().generar_expo(db, request) - if not result.success: - raise HTTPException(status_code=500, detail=result.message) - return { - "success": True, - "message": result.message, - "file_name": result.archivo_generado, - "media_type": "application/xml", - "content": base64.b64encode(result.content.encode("utf-8")).decode("utf-8"), - "inconsistencias": result.inconsistencias, - } diff --git a/backend/api/v1/modules/a76/reports/xml_optima/schemas.py b/backend/api/v1/modules/a76/reports/xml_optima/schemas.py deleted file mode 100644 index e93ebff5..00000000 --- a/backend/api/v1/modules/a76/reports/xml_optima/schemas.py +++ /dev/null @@ -1,17 +0,0 @@ -from typing import List, Optional -from pydantic import BaseModel, Field - - -class XmlOptimaExpoRequest(BaseModel): - manifest_numbers: List[str] = Field(..., description="Números de manifiesto a procesar") - consolidar_partidas: bool = Field(False, description="Consolidar partidas en una sola por factura (VarLoc:ConsolidarPartidas)") - - -class XmlOptimaResponse(BaseModel): - success: bool - message: str - archivo_generado: Optional[str] = None - content: Optional[str] = None - cuenta_manifiestos: int = 0 - cuenta_partidas: int = 0 - inconsistencias: List[str] = [] diff --git a/backend/api/v1/modules/a76/reports/xml_optima/service.py b/backend/api/v1/modules/a76/reports/xml_optima/service.py deleted file mode 100644 index 40d50cd0..00000000 --- a/backend/api/v1/modules/a76/reports/xml_optima/service.py +++ /dev/null @@ -1,764 +0,0 @@ -"""Generador de XML Optima para Exportación (manifiestos con shipments).""" - -from datetime import date -from decimal import Decimal -from typing import List, Optional - -from sqlalchemy.orm import Session, joinedload - -from api.v1.modules.a76.clients_and_providers.models import ClientProvider -from api.v1.modules.a76.invoices.models import ( - InvoiceComplianceMx, - InvoiceHeader, - WeightUnit, -) -from api.v1.modules.a76.items.line_quantities.models import LineQuantity -from api.v1.modules.a76.items.models import LineItem -from api.v1.modules.a76.manifests.driver.models import ManifestDriver -from api.v1.modules.a76.manifests.manifest.models import Manifest -from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos -from api.v1.modules.a76.transportation.drivers.models import Driver -from api.v1.modules.a76.transportation.trailers.models import Trailer -from api.v1.modules.a76.transportation.transporters.models import Transporter -from api.v1.modules.a76.transportation.vehicles.models import Vehicle -from api.v1.modules.public.reference_data.countries.models import Country -from api.v1.modules.public.reference_data.states.models import State - -from .schemas import XmlOptimaExpoRequest, XmlOptimaResponse - - -# ============================================================================= -# Helpers de formato -# ============================================================================= - -def _xml_escape(s: Optional[str]) -> str: - if not s: - return "" - return ( - str(s) - .replace("&", "&") - .replace("<", "<") - .replace(">", ">") - .replace('"', """) - ) - - -def _clean_filename(s: str) -> str: - """Equivale al loop de Clarion que quita /\\:*"?<>| y - del nombre del manifiesto.""" - bad = set('/\\:*"?<>|-') - return "".join(c for c in (s or "") if c not in bad) - - -def _format_entry_date(entry_date: Optional[int]) -> str: - """entry_date está en BD como entero YYYYMMDD. Devuelve YYYY-MM-DD.""" - if not entry_date: - return "" - s = str(int(entry_date)).zfill(8) - return f"{s[:4]}-{s[4:6]}-{s[6:8]}" - - -def _format_entry_hour(entry_hour: Optional[int]) -> str: - """entry_hour en BD como entero HHMM. Devuelve HH:MM.""" - if entry_hour is None: - return "" - s = str(int(entry_hour)).zfill(4) - return f"{s[:2]}:{s[2:4]}" - - -def _format_pedimento_clean(p: Optional[Pedimentos]) -> str: - """Genera 'YY+customs+license+number' (sin guiones) como en Clarion: - SUB(YEAR(PedMat:Fecha_Inicio),3,2) & PedimentoExpo_sin_guiones. - Aquí: pedimento.year ya está como 2 dígitos.""" - if not p: - return "" - yy = (p.year or "")[-2:] - return f"{yy}{p.customs_office or ''}{p.license or ''}{p.pedimento_number or ''}" - - -def _csv_append(acc: str, value: str) -> str: - """Append value a una lista separada por comas, sin duplicar.""" - if not value: - return acc - if not acc: - return value - if value in acc.split(", "): - return acc - return f"{acc}, {value}" - - -# ============================================================================= -# Validadores (acumulan inconsistencias) -# ============================================================================= - -def _validate_trip_header( - transporter: Optional[Transporter], - manifest: Manifest, - issues: list, -) -> tuple[str, str]: - """Valida transportista + manifiesto. Retorna (preparerSCAC, carrierSCAC).""" - preparer_scac = "" - carrier_scac = "" - - if not transporter: - issues.append( - f"No existe el Transportista (clave: '{manifest.carrier_code or ''}') del Manifiesto '{manifest.manifest_number}'. " - f"Solución: capturar el transportista con código y SCAC code." - ) - else: - preparer_scac = transporter.transporter_key or "" - carrier_scac = transporter.loader_code or "" - if not preparer_scac: - issues.append( - "Falta capturar el Código de Transportista. " - "Solución: capturar la información correspondiente en los datos del Transportista." - ) - if not carrier_scac: - issues.append( - "Falta capturar el Código del Cargador (SCAC). " - "Solución: capturar la información correspondiente en los datos del Transportista." - ) - - if not manifest.entry_date: - issues.append( - f"Falta capturar la Fecha de Entrada del Manifiesto '{manifest.manifest_number}'. " - "Solución: capturar la información en los datos del manifiesto." - ) - if manifest.entry_hour is None: - issues.append( - f"Falta capturar la Hora de Entrada del Manifiesto '{manifest.manifest_number}'. " - "Solución: capturar la información en los datos del manifiesto." - ) - if not manifest.entry_port: - issues.append( - f"Falta capturar el Puerto de Entrada del Manifiesto '{manifest.manifest_number}'. " - "Solución: capturar la información en los datos del manifiesto." - ) - - return preparer_scac, carrier_scac - - -def _validate_client_optima( - db: Session, - client: Optional[ClientProvider], - client_key: str, - role: str, - manifest_num: str, - issues: list, -) -> dict: - """ - Valida un cliente (Shipper o Consignee) para XML Optima. - Retorna dict con: name, address, address2, city, zip, state, country, contact, phone, email. - """ - out = { - "name": "", "address": "", "address2": "", "city": "", "zip": "", - "state": "", "country": "", "contact": "", "phone": "", "email": "", - } - if not client: - issues.append( - f"No existe el {role} con clave '{client_key}' del Manifiesto '{manifest_num}'. " - f"Solución: capturar el cliente con datos obligatorios." - ) - return out - - display = client.short_name or client.name or client_key - - if not client.name: - issues.append(f"Falta capturar el nombre del cliente '{display}' ({role}). " - "Solución: capturar la información en los datos del cliente.") - out["name"] = client.name or "" - - addr = client.address - if not (addr and addr.streets): - issues.append(f"Falta capturar la dirección del cliente '{display}' ({role}). " - "Solución: capturar la información en los datos del cliente.") - if addr: - out["address"] = f"{(addr.streets or '').strip()} {(addr.exterior_number or '').strip()}".strip() - out["address2"] = addr.neighborhood or "" - - if not addr.city: - issues.append(f"Falta capturar la ciudad del cliente '{display}' ({role}). " - "Solución: capturar la información en los datos del cliente.") - out["city"] = addr.city or "" - - if not addr.postal_code: - issues.append(f"Falta capturar el código postal del cliente '{display}' ({role}). " - "Solución: capturar la información en los datos del cliente.") - out["zip"] = addr.postal_code or "" - - # Estado: mexicano o americano según type_nat_foreign - state_obj = None - if addr.country and addr.state: - state_obj = ( - db.query(State) - .filter(State.m3_key == addr.country, State.description == addr.state) - .first() - ) - - if (client.type_nat_foreign or "").upper() == "N": - state_key = (state_obj.mex_key if state_obj else "") or "" - if not state_key: - issues.append( - f"Falta capturar la clave del estado mexicano '{addr.state or ''}' del cliente '{display}'. " - "Solución: capturar la información en el catálogo de Países/Estados." - ) - out["state"] = state_key - else: - # Clave americana del estado: el modelo State no tiene ame_key, queda como inconsistencia - issues.append( - f"Falta la clave americana del estado '{addr.state or ''}' del cliente '{display}'. " - "Solución: el catálogo de Estados no soporta clave americana actualmente; capturar manualmente." - ) - out["state"] = "" - - # País: clave americana - country_obj = ( - db.query(Country).filter(Country.m3_key == addr.country).first() - if addr.country else None - ) - ame_country = (country_obj.ame_key if country_obj else "") or "" - if not ame_country: - issues.append( - f"Falta capturar la clave americana del País '{addr.country or ''}' del cliente '{display}'. " - "Solución: capturar la información en el catálogo de Países." - ) - out["country"] = ame_country - out["contact"] = addr.contact or "" - out["phone"] = addr.phone or "" - out["email"] = addr.email or "" - - return out - - -# ============================================================================= -# Builders -# ============================================================================= - -def _lookup_client_by_key(db: Session, client_key: Optional[str]) -> Optional[ClientProvider]: - """ - Busca ClientProvider por la clave que trae Manifest.sent_by / consigned_to. - Intenta como id entero primero; si falla, busca por short_name (CliPro:Cliente). - """ - if not client_key: - return None - opts = [joinedload(ClientProvider.address), joinedload(ClientProvider.programs)] - try: - cid = int(client_key.strip()) - return db.query(ClientProvider).options(*opts).filter(ClientProvider.id == cid).first() - except (ValueError, TypeError): - return ( - db.query(ClientProvider) - .options(*opts) - .filter(ClientProvider.short_name == client_key.strip()) - .first() - ) - - -def _query_invoices_for_manifest(db: Session, manifest_num: str) -> List[InvoiceHeader]: - """Trae todas las facturas (SCAII/SCAF/REPAR) ligadas al manifiesto con estatus procesado.""" - return ( - db.query(InvoiceHeader) - .join(InvoiceComplianceMx, InvoiceComplianceMx.invoice_id == InvoiceHeader.id) - .options( - joinedload(InvoiceHeader.compliance_mx), - joinedload(InvoiceHeader.logistics), - ) - .filter( - InvoiceComplianceMx.manifest_number == manifest_num, - InvoiceHeader.status == "processed", - ) - .all() - ) - - -def _query_line_items_for_invoice(db: Session, invoice_id: int) -> List[LineItem]: - return ( - db.query(LineItem) - .filter(LineItem.invoice_id == invoice_id) - .options( - joinedload(LineItem.part_info), - joinedload(LineItem.class_info), - joinedload(LineItem.description), - joinedload(LineItem.quantity).joinedload(LineQuantity.package_info), - ) - .all() - ) - - -def _line_description_en(line: LineItem) -> str: - """Descripción inglés según tipo de partida (SCAII usa Part, SCAF usa Class).""" - # 1) Si hay LineDescription con descripcion_english, usarla - desc = (line.description.description_english if line.description else None) or "" - if desc: - return desc.upper() - # 2) SCAII: usar Part.description_english - if line.part_info and getattr(line.part_info, "description_english", None): - return (line.part_info.description_english or "").upper() - # 3) SCAF: usar Class.description_en - if line.class_info and getattr(line.class_info, "description_en", None): - return (line.class_info.description_en or "").upper() - return "" - - -def _gross_uom(weight_type: Optional[WeightUnit]) -> str: - if weight_type == WeightUnit.KGS: - return "K" - if weight_type == WeightUnit.LBS: - return "L" - return "" - - -def _build_trailers_for_manifest( - db: Session, manifest: Manifest, carrier_scac: str -) -> tuple[list, dict]: - """ - Construye lista de únicos (formato CodCargador$TrailerAce$Country$State$Plate) - y devuelve metadatos del trailer principal (para ). - """ - trailer_list: list[str] = [] - seen: set[str] = set() - - main: dict = {"type": "", "equipmentNumber": "", "plate": "", "plateState": "", "plateCountry": ""} - - if not manifest.trailer_number: - return trailer_list, main - - trailer = ( - db.query(Trailer) - .filter(Trailer.trailer_number == manifest.trailer_number) - .first() - ) - if not trailer: - return trailer_list, main - - ace = trailer.ace_trailer_number or "" - country = trailer.country or "" - state = trailer.state or "" - plate = trailer.plate_number or "" - trailer_type = trailer.trailer_type_key or "" - - reg = f"{carrier_scac}${ace}${country}${state}${plate}" - if reg not in seen and reg != "$$$$": - trailer_list.append(reg) - seen.add(reg) - - main = { - "type": trailer_type, - "equipmentNumber": ace, - "plate": plate, - "plateState": state, - "plateCountry": country, - } - return trailer_list, main - - -def _collect_seals_from_invoices(invoices: List[InvoiceHeader]) -> str: - """Concatena seal_number único de cada factura, separados por ', '.""" - seals: list[str] = [] - seen = set() - for inv in invoices: - log = inv.logistics - seal = (log.seal_number or "").strip() if log else "" - if seal and seal not in seen: - seals.append(seal) - seen.add(seal) - return ", ".join(seals) - - -# Nota: Clarion convertía Pais_Ame + descripción → Clave_Ame del estado para el plate -# del trailer. El modelo State actual no tiene ame_key, así que el plate state se emite -# con la descripción cruda y se acumula inconsistencia si aplica. - - -# ============================================================================= -# DET: elements -# ============================================================================= - -def _emit_merchandise( - desc: str, - bulks, - bulks_uom: str, - gross_weight, - gross_uom: str, - out: list, -) -> None: - out.append(" ") - out.append(f" {_xml_escape(desc)}") - out.append(f" {bulks if bulks else ''}") - out.append(f" {_xml_escape(bulks_uom)}") - if gross_uom: - gw = f"{Decimal(gross_weight or 0):.4f}" if gross_weight is not None else "" - out.append(f" {gw}") - out.append(f" {gross_uom}") - out.append(" ") - - -def _build_merchandise_normal( - db: Session, - invoices: List[InvoiceHeader], - out: list, - issues: list, -) -> int: - """Sin consolidar: emite un por cada partida.""" - count = 0 - for inv in invoices: - weight_type = inv.logistics.weight_type if inv.logistics else None - gross_uom = _gross_uom(weight_type) - for line in _query_line_items_for_invoice(db, inv.id): - desc = _line_description_en(line) - if not desc: - ref = (line.part_info.part_number if line.part_info else None) \ - or (line.class_info.class_code if line.class_info else "") - tipo = "número de parte" if line.part_info else "clase" - issues.append( - f"Falta capturar la descripción en inglés del {tipo} '{ref}'. " - "Solución: capturar la información en los datos de la partida." - ) - - qty = line.quantity - bulks = qty.package_quantity if qty else None - pkg = qty.package_info if qty else None - bulks_uom = (pkg.code_ace or "").upper() if pkg else "" - gross = qty.gross_weight if qty else None - - _emit_merchandise(desc, bulks, bulks_uom, gross, gross_uom, out) - count += 1 - return count - - -def _build_merchandise_consolidado( - db: Session, - invoices: List[InvoiceHeader], - out: list, - issues: list, -) -> int: - """ - Consolidación equivalente a VarLoc:ConsolidarPartidas=1 de Clarion. - Agrupa partidas por (invoice_number, package_id) y suma pesos brutos. - """ - # estructura: line_no → {desc, bulks, package_id, gross_weight, bulks_uom, gross_uom} - grouped: dict[int, dict] = {} - consecutivo = 0 - last_invoice = None - - # tomar gross_uom de la primera factura (Clarion usa MatFex:TipoPeso global del primero) - primary_uom = "" - if invoices: - primary_uom = _gross_uom(invoices[0].logistics.weight_type if invoices[0].logistics else None) - - for inv in invoices: - for line in _query_line_items_for_invoice(db, inv.id): - if last_invoice != inv.invoice_number: - consecutivo += 1 - last_invoice = inv.invoice_number - - qty = line.quantity - bulks = (qty.package_quantity if qty else None) or 0 - if bulks > 0: - consecutivo += 1 - - desc = _line_description_en(line) - if not desc: - ref = (line.part_info.part_number if line.part_info else None) \ - or (line.class_info.class_code if line.class_info else "") - tipo = "número de parte" if line.part_info else "clase" - issues.append( - f"Falta capturar la descripción en inglés del {tipo} '{ref}'. " - "Solución: capturar la información en los datos de la partida." - ) - - pkg = qty.package_info if qty else None - bulks_uom = (pkg.code_ace or "").upper() if (pkg and bulks > 0) else "" - gross = qty.gross_weight if qty else Decimal(0) - - existing = grouped.get(consecutivo) - if existing is None: - grouped[consecutivo] = { - "desc": desc, - "bulks": bulks, - "bulks_uom": bulks_uom, - "gross_weight": Decimal(gross or 0), - } - else: - # Concatenar descripción si no está - if desc and desc not in existing["desc"]: - existing["desc"] = ( - f"{existing['desc']} - {desc}" if existing["desc"] else desc - ) - existing["gross_weight"] += Decimal(gross or 0) - - for _, data in sorted(grouped.items()): - _emit_merchandise( - data["desc"], data["bulks"], data["bulks_uom"], - data["gross_weight"], primary_uom, out, - ) - - return len(grouped) - - -# ============================================================================= -# ENC: + + -# ============================================================================= - -def _build_shipment_enc( - db: Session, - manifest: Manifest, - invoices: List[InvoiceHeader], - out: list, - issues: list, -) -> None: - """ - Acumula CSVs de pedimento/factura/remesa/fecha de TODAS las facturas - y emite , , . - """ - # 1) Validar Shipper (EnviadoPor) y Consignee (ConsignadoA) - if not manifest.sent_by: - issues.append( - f"Falta capturar el Shipper en el Manifiesto '{manifest.manifest_number}'. " - "Solución: capturar la información en los datos del manifiesto." - ) - if not manifest.consigned_to: - issues.append( - f"Falta capturar el Consignado del Manifiesto '{manifest.manifest_number}'. " - "Solución: capturar la información en los datos del manifiesto." - ) - - shipper_data = _validate_client_optima( - db, _lookup_client_by_key(db, manifest.sent_by), - manifest.sent_by or "", "Shipper", manifest.manifest_number or "", issues, - ) - consignee_data = _validate_client_optima( - db, _lookup_client_by_key(db, manifest.consigned_to), - manifest.consigned_to or "", "Consignee", manifest.manifest_number or "", issues, - ) - - # 2) Acumular pedimento / factura / remesa / fecha - inv_pedimento = "" - inv_factura = "" - inv_remesa = "" - inv_fecha = "" - - for inv in invoices: - cmx = inv.compliance_mx - ped = None - if cmx and cmx.pedimento_id: - ped = db.query(Pedimentos).filter(Pedimentos.id == cmx.pedimento_id).first() - inv_pedimento = _csv_append(inv_pedimento, _format_pedimento_clean(ped)) - inv_factura = _csv_append(inv_factura, inv.invoice_number or "") - if cmx and cmx.remesa is not None: - inv_remesa = _csv_append(inv_remesa, str(cmx.remesa)) - if inv.invoice_date: - inv_fecha = _csv_append(inv_fecha, inv.invoice_date.strftime("%Y-%m-%d")) - - # 3) - out.append(" ") - out.append(f" {_xml_escape(inv_factura)}") - out.append(f" {_xml_escape(inv_pedimento)}") - out.append(f" {_xml_escape(inv_remesa)}") - out.append(f" {_xml_escape(inv_fecha)}") - out.append(" ") - - # 4) - out.append(" ") - for tag in ("name", "address", "address2", "city", "zip", "state", "country", "contact"): - out.append(f" <{tag}>{_xml_escape(shipper_data[tag])}") - out.append(f" {_xml_escape(shipper_data['phone'])}") - out.append(f" {_xml_escape(shipper_data['email'])}") - out.append(" ") - - # 5) - out.append(" ") - for tag in ("name", "address", "address2", "city", "zip", "state", "country", "contact"): - out.append(f" <{tag}>{_xml_escape(consignee_data[tag])}") - out.append(f" {_xml_escape(consignee_data['phone'])}") - out.append(f" {_xml_escape(consignee_data['email'])}") - out.append(" ") - - -# ============================================================================= -# Servicio principal -# ============================================================================= - -class XmlOptimaService: - - def generar_expo( - self, db: Session, request: XmlOptimaExpoRequest - ) -> XmlOptimaResponse: - """Genera el XML con shipments para cada manifiesto.""" - try: - issues: list[str] = [] - lines: list[str] = [] - cuenta_partidas = 0 - cuenta_manifiestos = 0 - - if not request.manifest_numbers: - return XmlOptimaResponse( - success=False, message="No se proporcionaron manifiestos." - ) - - # Manifiesto principal (primer manifiesto) — el resto se procesan como shipments - primary_num = request.manifest_numbers[0] - primary_manifest = ( - db.query(Manifest) - .filter(Manifest.manifest_number == primary_num) - .first() - ) - if not primary_manifest: - return XmlOptimaResponse( - success=False, - message=f"No se encontró el manifiesto '{primary_num}'.", - ) - - # Transportista principal - transporter = None - if primary_manifest.carrier_code: - transporter = ( - db.query(Transporter) - .filter(Transporter.transporter_key == primary_manifest.carrier_code) - .first() - ) - - preparer_scac, carrier_scac = _validate_trip_header( - transporter, primary_manifest, issues - ) - - # Conductor (busca ManifestDriver → Driver) - manifest_driver_row = ( - db.query(ManifestDriver) - .filter(ManifestDriver.manifest_number == primary_num) - .first() - ) - driver: Optional[Driver] = None - if manifest_driver_row and primary_manifest.carrier_code: - driver = ( - db.query(Driver) - .filter( - Driver.transporter_key == primary_manifest.carrier_code, - Driver.driver_name == manifest_driver_row.driver_name, - ) - .first() - ) - license_num = (driver.license_number or "") if driver else "" - if not license_num: - issues.append( - f"Falta capturar el Número de Licencia del Conductor del Manifiesto '{primary_num}'. " - "Solución: capturar la información en los datos del conductor." - ) - - # Tractor (vía vehicle_key = manifest.trailer_number) - vehicle: Optional[Vehicle] = None - if primary_manifest.trailer_number: - vehicle = ( - db.query(Vehicle) - .filter(Vehicle.vehicle_key == primary_manifest.trailer_number) - .first() - ) - tractor_plate = (vehicle.plate_number or "") if vehicle else "" - if not tractor_plate: - issues.append( - f"Falta capturar el Número de Placas del Tractor del Manifiesto '{primary_num}'. " - "Solución: capturar la información en los datos del trailer." - ) - - # Trailers (únicos) y datos de - trailer_regs, trailer_main = _build_trailers_for_manifest( - db, primary_manifest, carrier_scac - ) - - # .plateState: Clarion convertía descripción → clave americana. - # Sin ame_key en State, se emite la descripción cruda del trailer. - trailer_state_ame = trailer_main["plateState"] - - # Tipo de transporte (vehículo) - vehicle_type = (vehicle.transport_type or "") if vehicle else "" - - # Sellos: agregados de todas las facturas de todos los manifiestos solicitados - all_invoices_for_seals: list[InvoiceHeader] = [] - for mn in request.manifest_numbers: - all_invoices_for_seals.extend(_query_invoices_for_manifest(db, mn)) - seal_csv = _collect_seals_from_invoices(all_invoices_for_seals) - - # ----- COMIENZO DEL XML ----- - clean_trip = _clean_filename(primary_num) - lines.append('') - lines.append("") - lines.append(f" {_xml_escape(preparer_scac)}") - lines.append(f" {_xml_escape(carrier_scac)}") - lines.append(f" {_xml_escape(clean_trip)}") - lines.append(f" {_format_entry_date(primary_manifest.entry_date)}") - lines.append(f" {_format_entry_hour(primary_manifest.entry_hour)}") - lines.append(f" {_xml_escape(primary_manifest.entry_port)}") - lines.append(f" {_xml_escape(tractor_plate)}") - for reg in trailer_regs: - lines.append(f" {_xml_escape(reg)}") - lines.append(f" {_xml_escape(license_num)}") - lines.append(" R") - lines.append(f" {_xml_escape(seal_csv)}") - lines.append(" ") - lines.append(f" {_xml_escape(vehicle_type or trailer_main['type'])}") - lines.append(f" {_xml_escape(trailer_main['equipmentNumber'])}") - lines.append(f" {_xml_escape(trailer_main['plate'])}") - lines.append(f" {_xml_escape(trailer_state_ame)}") - lines.append(f" {_xml_escape(trailer_main['plateCountry'])}") - lines.append(" ") - - # ----- por cada manifiesto ----- - for mn in request.manifest_numbers: - manifest = ( - db.query(Manifest) - .filter(Manifest.manifest_number == mn) - .first() - ) - if not manifest: - issues.append(f"No se encontró el manifiesto '{mn}'. Se omite.") - continue - - if not manifest.foreign_exit_port: - issues.append( - f"Falta capturar el Puerto Exterior de Salida (Port of Lading) del Manifiesto '{mn}'. " - "Solución: capturar la información en los datos del manifiesto." - ) - - clean_num = _clean_filename(mn) - invoices = _query_invoices_for_manifest(db, mn) - - lines.append(" ") - lines.append(f" {_xml_escape(clean_num)}") - lines.append(f" {_xml_escape(clean_num)}") - lines.append(f" {_xml_escape(manifest.foreign_exit_port)}") - lines.append(" MX") - lines.append(f" {_xml_escape(manifest.manifest_type)}") - lines.append(f" {_xml_escape(mn[:3])}") - - _build_shipment_enc(db, manifest, invoices, lines, issues) - - if request.consolidar_partidas: - n = _build_merchandise_consolidado(db, invoices, lines, issues) - else: - n = _build_merchandise_normal(db, invoices, lines, issues) - cuenta_partidas += n - - lines.append(" ") - cuenta_manifiestos += 1 - - lines.append("") - - xml_str = "\n".join(lines) - filename = f"XML_OPTIMA_{clean_trip}_{date.today().strftime('%Y%m%d')}.xml" - msg = f"{cuenta_manifiestos} manifiesto(s), {cuenta_partidas} partida(s) generadas" - if issues: - msg += f" — {len(issues)} inconsistencia(s)" - - return XmlOptimaResponse( - success=True, - message=msg, - archivo_generado=filename, - content=xml_str, - cuenta_manifiestos=cuenta_manifiestos, - cuenta_partidas=cuenta_partidas, - inconsistencias=issues, - ) - - except Exception as e: - import traceback - traceback.print_exc() - return XmlOptimaResponse(success=False, message=str(e)) diff --git a/backend/api/v1/modules/a76/reports/xml_rb_systems/__init__.py b/backend/api/v1/modules/a76/reports/xml_rb_systems/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/backend/api/v1/modules/a76/reports/xml_rb_systems/routes.py b/backend/api/v1/modules/a76/reports/xml_rb_systems/routes.py deleted file mode 100644 index f22787f3..00000000 --- a/backend/api/v1/modules/a76/reports/xml_rb_systems/routes.py +++ /dev/null @@ -1,90 +0,0 @@ -import base64 -from typing import Dict, Any -from fastapi import APIRouter, Depends -from sqlalchemy.orm import Session - -from core.database import get_core_db -from core.security import get_current_user - -from .schemas import XmlRbSystemsImpoRequest, XmlRbSystemsExpoRequest -from .service import XmlRbSystemsService - -router = APIRouter() - - -@router.post("/importacion-definitiva/generate") -async def generate_impo_def( - request: XmlRbSystemsImpoRequest, - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - result = XmlRbSystemsService().generar_impo_def(db, request) - if not result.success: - from fastapi import HTTPException - raise HTTPException(status_code=500, detail=result.message) - return { - "success": True, - "message": result.message, - "file_name": result.archivo_generado, - "media_type": "application/xml", - "content": base64.b64encode(result.content.encode("utf-8")).decode("utf-8"), - "inconsistencias": result.inconsistencias, - } - - -@router.post("/importacion-temporal/generate") -async def generate_impo_temp( - request: XmlRbSystemsImpoRequest, - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - result = XmlRbSystemsService().generar_impo_temp(db, request) - if not result.success: - from fastapi import HTTPException - raise HTTPException(status_code=500, detail=result.message) - return { - "success": True, - "message": result.message, - "file_name": result.archivo_generado, - "media_type": "application/xml", - "content": base64.b64encode(result.content.encode("utf-8")).decode("utf-8"), - "inconsistencias": result.inconsistencias, - } - - -@router.post("/importacion/generate") -async def generate_impo( - request: XmlRbSystemsImpoRequest, - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - result = XmlRbSystemsService().generar_impo(db, request) - if not result.success: - from fastapi import HTTPException - raise HTTPException(status_code=500, detail=result.message) - return { - "success": True, - "message": result.message, - "file_name": result.archivo_generado, - "media_type": "application/xml", - "content": base64.b64encode(result.content.encode("utf-8")).decode("utf-8"), - } - - -@router.post("/exportacion/generate") -async def generate_expo( - request: XmlRbSystemsExpoRequest, - db: Session = Depends(get_core_db), - current_user: Dict[str, Any] = Depends(get_current_user), -): - result = XmlRbSystemsService().generar_expo(db, request) - if not result.success: - from fastapi import HTTPException - raise HTTPException(status_code=500, detail=result.message) - return { - "success": True, - "message": result.message, - "file_name": result.archivo_generado, - "media_type": "application/xml", - "content": base64.b64encode(result.content.encode("utf-8")).decode("utf-8"), - } diff --git a/backend/api/v1/modules/a76/reports/xml_rb_systems/schemas.py b/backend/api/v1/modules/a76/reports/xml_rb_systems/schemas.py deleted file mode 100644 index cd52cc8a..00000000 --- a/backend/api/v1/modules/a76/reports/xml_rb_systems/schemas.py +++ /dev/null @@ -1,25 +0,0 @@ -from typing import List, Optional -from pydantic import BaseModel, Field - - -class XmlRbSystemsImpoRequest(BaseModel): - invoice_numbers: List[str] = Field(..., description="Números de factura a incluir") - entry_port: Optional[str] = Field(None, description="Puerto de entrada") - exit_port: Optional[str] = Field(None, description="Puerto de salida") - - -class XmlRbSystemsExpoRequest(BaseModel): - manifest_numbers: List[str] = Field(..., description="Números de manifiesto a procesar") - entry_port: Optional[str] = Field(None, description="Puerto de entrada") - exit_port: Optional[str] = Field(None, description="Puerto de salida") - include_emanifest: bool = Field(True, description="Incluir sección (VarLoc:NoEmanifest=0 en Clarion)") - - -class XmlRbSystemsResponse(BaseModel): - success: bool - message: str - archivo_generado: Optional[str] = None - content: Optional[str] = None - cuenta_facturas: int = 0 - cuenta_partidas: int = 0 - inconsistencias: List[str] = [] diff --git a/backend/api/v1/modules/a76/reports/xml_rb_systems/service.py b/backend/api/v1/modules/a76/reports/xml_rb_systems/service.py deleted file mode 100644 index f3eede1a..00000000 --- a/backend/api/v1/modules/a76/reports/xml_rb_systems/service.py +++ /dev/null @@ -1,1469 +0,0 @@ -"""Generador de XML en formato RB Systems (SED/AES) para transferencia electrónica.""" - -import xml.etree.ElementTree as ET -from datetime import date -from xml.dom import minidom -from typing import List, Optional - -from sqlalchemy.orm import Session, joinedload - -from api.v1.modules.a76.clients_and_providers.models import ClientProvider -from api.v1.modules.a76.general_catalogs.company.models import Company as GEmpresa -from api.v1.modules.a76.general_catalogs.fda_catalog.models import FDACatalog -from api.v1.modules.a76.invoices.models import InvoiceHeader, InvoiceComplianceMx -from api.v1.modules.a76.items.models import LineItem -from api.v1.modules.a76.items.line_quantities.models import LineQuantity -from api.v1.modules.a76.manifests.driver.models import ManifestDriver -from api.v1.modules.a76.manifests.manifest.models import Manifest -from api.v1.modules.a76.transportation.drivers.models import Driver -from api.v1.modules.a76.transportation.trailers.models import Trailer -from api.v1.modules.a76.transportation.transporters.models import Transporter -from api.v1.modules.a76.transportation.vehicles.models import Vehicle -from api.v1.modules.public.reference_data.countries.models import Country -from api.v1.modules.public.reference_data.states.models import State - -from .schemas import XmlRbSystemsImpoRequest, XmlRbSystemsExpoRequest, XmlRbSystemsResponse - - -def _sub(parent: ET.Element, tag: str, text: str) -> ET.Element: - el = ET.SubElement(parent, tag) - el.text = text or "" - return el - - -def _format_weight(value) -> str: - try: - n = float(value or 0) - return f"{n:,.0f}" - except Exception: - return "0" - - -def _origin_indicator(country: Optional[str]) -> str: - """D = doméstico (USA), F = extranjero.""" - if not country: - return "F" - return "D" if country.upper() in ("US", "USA", "") else "F" - - -def _strip_hts(raw: str) -> str: - """Elimina puntos, apóstrofes y letras de fracción americana. - Equivale al loop FA# de Clarion: excluye '.', "'", y caracteres alfa.""" - return "".join(c for c in raw if c not in (".", "'") and not c.isalpha()) - - -def _lookup_provider(db: Session, client_id: Optional[int]) -> Optional[ClientProvider]: - if not client_id: - return None - return ( - db.query(ClientProvider) - .options(joinedload(ClientProvider.programs), joinedload(ClientProvider.address)) - .filter(ClientProvider.id == client_id) - .first() - ) - - -def _ame_country(db: Session, m3_key: Optional[str]) -> str: - if not m3_key: - return "" - country = db.query(Country).filter(Country.m3_key == m3_key).first() - return (country.ame_key or "") if country else "" - - -def _ame_state(db: Session, m3_key: Optional[str], state_desc: Optional[str]) -> str: - if not m3_key or not state_desc: - return "" - state = ( - db.query(State) - .filter(State.m3_key == m3_key, State.description == state_desc) - .first() - ) - return getattr(state, "ame_key", "") or "" if state else "" - - -def _ame_state_from_ame_country(db: Session, ame_country: Optional[str], state_desc: Optional[str]) -> str: - """Trailer/Vehicle almacenan el país como ame_key; resuelve m3_key antes de buscar el estado.""" - if not ame_country or not state_desc: - return "" - country = db.query(Country).filter(Country.ame_key == ame_country).first() - if not country: - return "" - return _ame_state(db, country.m3_key, state_desc) - - -def _lookup_provider_by_key(db: Session, key: Optional[str]) -> Optional[ClientProvider]: - """Busca ClientProvider por clave de texto (Manifest.sent_by / consigned_to). - Intenta parsear como int (id); si falla busca por short_name.""" - if not key: - return None - opts = [joinedload(ClientProvider.programs), joinedload(ClientProvider.address)] - try: - client_id = int(key.strip()) - return db.query(ClientProvider).options(*opts).filter(ClientProvider.id == client_id).first() - except (ValueError, TypeError): - return db.query(ClientProvider).options(*opts).filter(ClientProvider.short_name == key.strip()).first() - - -def _clean_manifest_number(manifest: str) -> str: - """Elimina caracteres especiales del número de manifiesto (para FilerCode/EntryNumber).""" - skip = set("/\\*<>?:-") - return "".join(c for c in manifest if c not in skip) - - -def _format_clarion_date(clarion_int: Optional[int]) -> str: - """Convierte fecha Clarion (días desde 1800-12-28) a MM/DD/YYYY.""" - if not clarion_int: - return "" - try: - from datetime import date as dt, timedelta - base = dt(1800, 12, 28) - return (base + timedelta(days=int(clarion_int))).strftime("%m/%d/%Y") - except Exception: - return "" - - -def _get_niu_for_manifest(db: Session, manifest_number: str) -> str: - """Para TripFlightNumber (MOT=40/aéreo): NIU de la primera factura del manifiesto.""" - record = ( - db.query(InvoiceComplianceMx.niu_number) - .filter(InvoiceComplianceMx.manifest_number == manifest_number) - .order_by(InvoiceComplianceMx.invoice_id.desc()) - .first() - ) - return (record[0] or "") if record else "" - - -def _xml_escape(s: Optional[str]) -> str: - if not s: - return "" - return s.replace("&", "&").replace("<", "<").replace(">", ">") - - -def _clean_product_name(name: Optional[str]) -> str: - """Limpia nombre de producto: elimina los mismos chars que Clarion en DescripcionI. - Clarion descarta: / & * \\ < > ? : -""" - if not name: - return "" - skip = set("/&*\\<>?:-") - cleaned = "".join(c for c in name if c not in skip) - return _xml_escape(cleaned[:80]) - - -def _special_country_program(has_certificate: Optional[bool], origin_m3: Optional[str]) -> str: - """ - Equivale a la lógica TieneCO + PaisOrigen de Clarion EXPO_SCAII_CONSOLIDADO. - Si tiene CO y el origen es MEX/USA/CAN retorna el código AME; si no, vacío. - """ - if not has_certificate: - return "" - mapping = {"MEX": "MX", "USA": "US", "CAN": "CA"} - return mapping.get((origin_m3 or "").strip().upper(), "") - - -def _append_invoice_line_scaii( - db: Session, - line: LineItem, - lines: list, - country_of_export: str, -) -> dict: - """ - Emite un para una partida de exportación SCAII (CONSOLIDADO). - Retorna dict {hts_frac: {valorD, valorN, valorP}} para acumular en QueueRBSystem2. - Equivale al bloque de partidas en GENERACION_XML_RBSYSTEMS_EXPO_SCAII_CONSOLIDADO. - """ - customs = line.customs - qty_info = line.quantity - - part_key = (line.part_info.part_number or "") if line.part_info else "" - part_name = _clean_product_name((line.part_info.description_english or "") if line.part_info else "") - - origin_m3 = (customs.origin_country or "").strip() if customs else "" - country_of_origin = _ame_country(db, origin_m3) if origin_m3 else "" - - # SpecialCountryProgram: TieneCO × PaisOrigen (Clarion: MatPex:TieneCO / MatPex:PaisOrigen) - scp = _special_country_program(line.has_certificate, origin_m3) - - qty_val = f"{float(qty_info.quantity or 0):.4f}" if qty_info else "0.0000" - unit_code = "PC" - if line.unit_of_measure_info: - unit_code = line.unit_of_measure_info.american_code or line.uma_key or "PC" - elif line.uma_key: - unit_code = line.uma_key - - net_weight_kg = f"{float(qty_info.net_weight or 0):.4f}" if qty_info else "0.0000" - - # NaftaNetCost: 'Y'/'N' (Clarion: MatPex:TieneCO) - nafta_net = "Y" if line.has_certificate else "N" - - lines.append(' ') - lines.append(f' {_xml_escape(part_key)}') - lines.append(f' {part_name}') - lines.append(' ') - lines.append(f' {country_of_origin}') - lines.append(f' {country_of_export}') - lines.append(f' {scp}') - lines.append(f' {qty_val}') - lines.append(f' {net_weight_kg}') - lines.append(f' {nafta_net}') - - # FdaLine (Clarion: Loc:TieneFDA / GCodigosFDA) - if line.has_fda_code and line.fda_key: - fda = ( - db.query(FDACatalog) - .options(joinedload(FDACatalog.affirmation_codes)) - .filter(FDACatalog.fda_key == line.fda_key) - .first() - ) - if fda: - lines.append(' ') - lines.append(f' {_xml_escape(fda.fda_code or "")}') - lines.append(f' {_xml_escape(fda.description or "")}') - lines.append(f' {_xml_escape(fda.storage_status or "")}') - lines.append(f' {_xml_escape(fda.manufacturer_number or "")}') - lines.append(' ') - lines.append(' ') - lines.append(' ') - lines.append(' ') - lines.append(' ') - lines.append(' ') - lines.append(' ') - lines.append(' ') - lines.append(' ') - lines.append(' ') - for aoc in (fda.affirmation_codes or []): - lines.append(f' {_xml_escape(aoc.aoc_code or "")}') - lines.append(' ') - lines.append(' ') - - # DotLine siempre presente con todos sus hijos (Clarion: sección ) - lines.append(' ') - lines.append(' ') - lines.append(' ') - lines.append(' ') - lines.append(' ') - lines.append(' ') - lines.append(' ') - lines.append(' ') - lines.append(' ') - lines.append(' ') - lines.append(' ') - lines.append(' ') - lines.append(' ') - lines.append(' ') - lines.append(' ') - lines.append(' ') - lines.append(' ') - lines.append(' ') - lines.append(' ') - - # FccLine (Clarion: Loc:TieneFCC / GCodigosFCC) - # GCodigosFCC no tiene modelo equivalente en el nuevo sistema; se usa fcc_key como identificador. - if line.fcc_key: - lines.append(' ') - lines.append(' ') - lines.append(' ') - lines.append(f' {_xml_escape(line.fcc_key)}') - lines.append(' ') - lines.append(' ') - lines.append(' ') - lines.append(' ') - lines.append(' ') - - lines.append(' ') - - # Construye contribución al QueueRBSystem2 (Clarion: SFracAmeFact D/N/P) - hts_contrib: dict = {} - val_d = float((line.financial.value_usd or 0)) if line.financial else 0.0 - if customs: - for frac_raw, key in ( - (customs.american_fraction, "D"), - (customs.tlcan_fraction, "N"), - (customs.extra_american_fraction, "P"), - ): - if frac_raw: - frac = _strip_hts(frac_raw) - if frac: - entry = hts_contrib.setdefault(frac, {"valorD": 0.0, "valorN": 0.0, "valorP": 0.0}) - if key == "D": - entry["valorD"] += val_d - elif key == "N": - entry["valorN"] += val_d - else: - entry["valorP"] += val_d - return hts_contrib - - -def _emit_hts_queue(hts_queue: dict, lines: list) -> None: - """ - Emite las secciones , , acumuladas. - Equivale al LOOP FV# final de GENERACION_XML_RBSYSTEMS_EXPO_SCAII_CONSOLIDADO. - """ - for frac, vals in hts_queue.items(): - lines.append(f' {frac}') - lines.append(f' {vals["valorD"]:.2f}') - - # SpecialProgram para 9802/9813 (Clarion: SUB(FraccionD,1,4)) - if frac[:4] in ("9802", "9813"): - lines.append(' ') - lines.append(f' {frac}') - lines.append(f' {vals["valorN"]:.2f}') - lines.append(' ') - - # Packing para fracciones no vacías y no 9801 (Clarion: FraccionD <> '' AND <> '9801') - if frac and frac[:4] != "9801": - lines.append(' ') - lines.append(f' {frac}') - lines.append(f' {vals["valorP"]:.2f}') - lines.append(' ') - - -def _build_entry_invoices_scaii( - db: Session, - manifest_num: str, - manufacturer_id: str, - lines: list, -) -> tuple[int, int]: - """ - Genera los dentro de y el bloque HTS consolidado al final. - Equivale a GENERACION_XML_RBSYSTEMS_EXPO_SCAII_CONSOLIDADO de Clarion. - Retorna (num_facturas, num_partidas). - """ - facturas = ( - db.query(InvoiceHeader) - .join(InvoiceComplianceMx, InvoiceComplianceMx.invoice_id == InvoiceHeader.id) - .options( - joinedload(InvoiceHeader.compliance_mx), - joinedload(InvoiceHeader.financials), - joinedload(InvoiceHeader.logistics), - ) - .filter( - InvoiceComplianceMx.manifest_number == manifest_num, - InvoiceHeader.status == "processed", - ) - .all() - ) - - # QueueRBSystem2: acumula fracciones HTS de todas las partidas del manifiesto - hts_queue: dict = {} - - total_partidas = 0 - for factura in facturas: - cmx = factura.compliance_mx - fin = factura.financials - log = factura.logistics - - # ConsigneeKey: VendidoA.programs.broker (Clarion: CliVen:Broker) - sold_to = _lookup_provider(db, cmx.sold_to_id if cmx else None) - consignee_key = (sold_to.programs.broker or "") if sold_to and sold_to.programs else "" - - # CountryOfExport: país del proveedor de la factura (Clarion: CliEnv:Pais → AME) - provider = _lookup_provider(db, cmx.provider_id if cmx else None) - provider_m3 = (provider.address.country if provider and provider.address else None) - country_of_export = _ame_country(db, provider_m3) if provider_m3 else "" - - invoice_date = factura.invoice_date.strftime("%m/%d/%Y") if factura.invoice_date else "" - freight = f"{float(fin.freight or 0):.2f}" if fin else "0.00" - total_packages = str(fin.total_packages or 0) if fin else "0" - incoterm = _xml_escape((log.incoterm or "") if log else "") - - lines.append(' ') - lines.append(f' {_xml_escape(factura.invoice_number or "")}') - lines.append(f' {_xml_escape(consignee_key)}') - lines.append(f' {invoice_date}') - lines.append(f' {_xml_escape(manufacturer_id)}') - lines.append(f' {freight}') - lines.append(f' {total_packages}') - lines.append(f' {incoterm}') - - items = _query_invoice_items(db, factura.id) - for item in items: - contrib = _append_invoice_line_scaii(db, item, lines, country_of_export) - # Acumula en QueueRBSystem2 - for frac, vals in contrib.items(): - entry = hts_queue.setdefault(frac, {"valorD": 0.0, "valorN": 0.0, "valorP": 0.0}) - entry["valorD"] += vals["valorD"] - entry["valorN"] += vals["valorN"] - entry["valorP"] += vals["valorP"] - total_partidas += len(items) - - lines.append(' ') - - # Emite HTS consolidado después del loop de facturas (Clarion: LOOP FV#) - _emit_hts_queue(hts_queue, lines) - - return len(facturas), total_partidas - - -def _build_shipment( - shipment_set: ET.Element, - exporter_name: str, - invoice: InvoiceHeader, - items: List[LineItem], - entry_port: str = "", - exit_port: str = "", -) -> int: - """Agrega un elemento al ShipmentSet. Retorna número de partidas.""" - shipment = ET.SubElement(shipment_set, "Shipment") - - _sub(shipment, "ExporterName", exporter_name) - _sub(shipment, "CustomerReference", invoice.invoice_number or "") - _sub(shipment, "InvoiceNumber", invoice.invoice_number or "") - - mot = "" - export_date = "" - export_port = "" - container = "" - seal = "" - scac = "" - - if invoice.logistics: - mot = invoice.logistics.transport_mode or "" - container = invoice.logistics.transport_num or "" - seal = invoice.logistics.seal_number or "" - scac = invoice.logistics.carrier_id or "" - - if invoice.invoice_date: - export_date = invoice.invoice_date.strftime("%m/%d/%y") - - # Puerto: compliance_mx > parámetro de la request - export_port = (invoice.compliance_mx.port_of_entry if invoice.compliance_mx else None) or entry_port - - _sub(shipment, "MOT", mot) - _sub(shipment, "ExportDate", export_date) - _sub(shipment, "ExportPort", export_port) - _sub(shipment, "ExitPort", exit_port) - _sub(shipment, "Container", container) - _sub(shipment, "Seal", seal) - _sub(shipment, "SCAC", scac) - - for line in items: - item_el = ET.SubElement(shipment, "Item") - - hts = "" - license_code = "" - origin_ind = "F" - permit_num = "" - - if line.customs: - raw_hts = line.customs.american_fraction or line.customs.fraction or "" - hts = raw_hts.replace(".", "").strip() - license_code = line.customs.advalorem or "" - origin_ind = _origin_indicator(line.customs.origin_country) - - permit_num = line.permit_number or "" - - qty = "" - unit1 = "PC" - value = "" - gross_weight = "" - - if line.quantity: - qty = str(line.quantity.quantity or "") - gross_weight = _format_weight(line.quantity.gross_weight) - - if line.unit_of_measure_info: - unit1 = line.unit_of_measure_info.american_code or line.uma_key or "PC" - elif line.uma_key: - unit1 = line.uma_key - - if line.financial: - value = str(line.financial.value_usd or "") - - _sub(item_el, "HTSNumber", hts) - _sub(item_el, "LicenseCode", license_code) - _sub(item_el, "OriginIndicator", origin_ind) - _sub(item_el, "Qty1", qty) - _sub(item_el, "Unit1", unit1) - _sub(item_el, "Value", value) - _sub(item_el, "GrossWeight", gross_weight) - _sub(item_el, "LicenseNumber", permit_num) - - return len(items) - - -def _pretty_xml(root: ET.Element) -> str: - raw = ET.tostring(root, encoding="unicode") - return minidom.parseString(raw).toprettyxml(indent=" ", encoding=None) - - -def _query_invoice_items(db: Session, invoice_id: int) -> List[LineItem]: - return ( - db.query(LineItem) - .filter(LineItem.invoice_id == invoice_id) - .options( - joinedload(LineItem.part_info), - joinedload(LineItem.description), - joinedload(LineItem.financial), - joinedload(LineItem.quantity).joinedload(LineQuantity.package_info), - joinedload(LineItem.customs), - joinedload(LineItem.unit_of_measure_info), - ) - .all() - ) - - -def _query_invoice_items_scaf(db: Session, invoice_id: int) -> List[LineItem]: - """Partidas SCAF: class_id IS NOT NULL (activos fijos / QEqeMaq en Clarion).""" - return ( - db.query(LineItem) - .filter(LineItem.invoice_id == invoice_id, LineItem.class_id.isnot(None)) - .options( - joinedload(LineItem.class_info), - joinedload(LineItem.description), - joinedload(LineItem.financial), - joinedload(LineItem.quantity), - joinedload(LineItem.customs), - joinedload(LineItem.unit_of_measure_info), - ) - .all() - ) - - -def _append_invoice_line_scaf(db: Session, line: LineItem, lines: list) -> None: - """ - Emite un para una partida SCAF de exportación. - Equivale al bloque de partidas en GENERACION_XML_RBSYSTEMS_EXPO_SCAF de Clarion. - HtsProduct + SpecialProgram se emiten inline (no hay queue). - """ - customs = line.customs - qty_info = line.quantity - - # ProductKey = EqiPex:Clase (class_code, no NumParte) - product_key = (line.class_info.class_code if line.class_info else "") or "" - # ProductName = EqiPex:DescripcionI — solo XML-escape, sin limpiar chars especiales - product_name = _xml_escape( - (line.description.description_english or "") if line.description else "" - ) - - origin_m3 = (customs.origin_country or "").strip() if customs else "" - # CountryOfExport = CountryOfOrigin (Clarion reutiliza GenPai de PaisOrigen) - country_ame = _ame_country(db, origin_m3) if origin_m3 else "" - - scp = _special_country_program(line.has_certificate, origin_m3) - - qty_val = f"{float(qty_info.quantity or 0):.4f}" if qty_info else "0.0000" - unit_code = "PC" - if line.unit_of_measure_info: - unit_code = line.unit_of_measure_info.american_code or line.uma_key or "PC" - elif line.uma_key: - unit_code = line.uma_key - - # Fracción americana (EqiPex:FraccionAmericana) - frac_scaf = _strip_hts(customs.american_fraction or "") if customs else "" - - # ValorExpoME (EqiPex:ValorExpoME) - valor_expo_me = f"{float(line.financial.value_usd or 0):.2f}" if line.financial else "0.00" - - net_weight_kg = f"{float(qty_info.net_weight or 0):.4f}" if qty_info else "0.0000" - nafta_net = "Y" if line.has_certificate else "N" - - lines.append(' ') - lines.append(f' {_xml_escape(product_key)}') - lines.append(f' {product_name}') - lines.append(' ') - lines.append(f' {country_ame}') - lines.append(f' {country_ame}') - lines.append(f' {scp}') - lines.append(f' {qty_val}') - # HtsProduct + HtsProductValue inline (Clarion: no usa queue) - lines.append(f' {frac_scaf}') - lines.append(f' {valor_expo_me}') - lines.append(f' {net_weight_kg}') - lines.append(f' {nafta_net}') - - # SpecialProgram 9802/9813 inline - if frac_scaf[:4] in ("9802", "9813"): - lines.append(' ') - lines.append(f' {frac_scaf}') - lines.append(f' {valor_expo_me}') - lines.append(' ') - - # FdaLine (mismo estructura que SCAII — Clarion: Loc:TieneFDA / GCodigosFDA) - if line.has_fda_code and line.fda_key: - fda = ( - db.query(FDACatalog) - .options(joinedload(FDACatalog.affirmation_codes)) - .filter(FDACatalog.fda_key == line.fda_key) - .first() - ) - if fda: - lines.append(' ') - lines.append(f' {_xml_escape(fda.fda_code or "")}') - lines.append(f' {_xml_escape(fda.description or "")}') - lines.append(f' {_xml_escape(fda.storage_status or "")}') - lines.append(f' {_xml_escape(fda.manufacturer_number or "")}') - lines.append(' ') - lines.append(' ') - lines.append(' ') - lines.append(' ') - lines.append(' ') - lines.append(' ') - lines.append(' ') - lines.append(' ') - lines.append(' ') - lines.append(' ') - for aoc in (fda.affirmation_codes or []): - lines.append(f' {_xml_escape(aoc.aoc_code or "")}') - lines.append(' ') - lines.append(' ') - - # DotLine: comentado en Clarion para SCAF — no se emite - - # FccLine (misma estructura que SCAII) - if line.fcc_key: - lines.append(' ') - lines.append(' ') - lines.append(' ') - lines.append(f' {_xml_escape(line.fcc_key)}') - lines.append(' ') - lines.append(' ') - lines.append(' ') - lines.append(' ') - lines.append(' ') - - lines.append(' ') - - -def _build_entry_invoices_scaf( - db: Session, - manifest_num: str, - lines: list, -) -> tuple[int, int]: - """ - Genera los de facturas SCAF definitivas dentro de . - Equivale a GENERACION_XML_RBSYSTEMS_EXPO_SCAF de Clarion (QFacExp/QEqeMaq). - Discriminador: download_def == True (DESCARGADEF). - ManufactureId viene del proveedor de la factura (CliPro), no del manifiesto. - Retorna (num_facturas, num_partidas). - """ - facturas = ( - db.query(InvoiceHeader) - .join(InvoiceComplianceMx, InvoiceComplianceMx.invoice_id == InvoiceHeader.id) - .options( - joinedload(InvoiceHeader.compliance_mx), - joinedload(InvoiceHeader.financials), - joinedload(InvoiceHeader.logistics), - ) - .filter( - InvoiceComplianceMx.manifest_number == manifest_num, - InvoiceHeader.status == "processed", - InvoiceHeader.download_def == True, - ) - .all() - ) - - total_facturas = 0 - total_partidas = 0 - - for factura in facturas: - items = _query_invoice_items_scaf(db, factura.id) - if not items: - continue # factura sin partidas SCAF, es SCAII — la omitimos - - cmx = factura.compliance_mx - fin = factura.financials - log = factura.logistics - - # ManufactureId: proveedor de la factura (Clarion: CliPro:ManufacterID) - provider = _lookup_provider(db, cmx.provider_id if cmx else None) - manufacturer_id = (provider.programs.manufacturer_id or "") if provider and provider.programs else "" - - sold_to = _lookup_provider(db, cmx.sold_to_id if cmx else None) - consignee_key = (sold_to.programs.broker or "") if sold_to and sold_to.programs else "" - - invoice_date = factura.invoice_date.strftime("%m/%d/%Y") if factura.invoice_date else "" - freight = f"{float(fin.freight or 0):.2f}" if fin else "0.00" - total_packages = str(fin.total_packages or 0) if fin else "0" - incoterm = _xml_escape((log.incoterm or "") if log else "") - - lines.append(' ') - lines.append(f' {_xml_escape(factura.invoice_number or "")}') - lines.append(f' {_xml_escape(consignee_key)}') - lines.append(f' {invoice_date}') - lines.append(f' {_xml_escape(manufacturer_id)}') - lines.append(f' {freight}') - lines.append(f' {total_packages}') - lines.append(f' {incoterm}') - - for item in items: - _append_invoice_line_scaf(db, item, lines) - total_partidas += len(items) - - lines.append(' ') - total_facturas += 1 - - return total_facturas, total_partidas - - -def _build_entry_invoices_tem_scaf( - db: Session, - manifest_num: str, - lines: list, -) -> tuple[int, int]: - """ - Genera los de facturas SCAF temporales dentro de . - Equivale a GENERACION_XML_RBSYSTEMS_EXPO_TEM_SCAF de Clarion (QFacExpRep/QEqeMaqRep). - Discriminador: download_def IS NOT True (DESCARGADEF = False/NULL → temporal). - XML idéntico a SCAF definitivo; solo difiere la fuente de datos. - Retorna (num_facturas, num_partidas). - """ - facturas = ( - db.query(InvoiceHeader) - .join(InvoiceComplianceMx, InvoiceComplianceMx.invoice_id == InvoiceHeader.id) - .options( - joinedload(InvoiceHeader.compliance_mx), - joinedload(InvoiceHeader.financials), - joinedload(InvoiceHeader.logistics), - ) - .filter( - InvoiceComplianceMx.manifest_number == manifest_num, - InvoiceHeader.status == "processed", - InvoiceHeader.download_def.isnot(True), - ) - .all() - ) - - total_facturas = 0 - total_partidas = 0 - - for factura in facturas: - items = _query_invoice_items_scaf(db, factura.id) - if not items: - continue # factura sin partidas SCAF temporal — omitir - - cmx = factura.compliance_mx - fin = factura.financials - log = factura.logistics - - # ManufactureId: proveedor de la factura (Clarion: CliPro:ManufacterID) - provider = _lookup_provider(db, cmx.provider_id if cmx else None) - manufacturer_id = (provider.programs.manufacturer_id or "") if provider and provider.programs else "" - - sold_to = _lookup_provider(db, cmx.sold_to_id if cmx else None) - consignee_key = (sold_to.programs.broker or "") if sold_to and sold_to.programs else "" - - invoice_date = factura.invoice_date.strftime("%m/%d/%Y") if factura.invoice_date else "" - freight = f"{float(fin.freight or 0):.2f}" if fin else "0.00" - total_packages = str(fin.total_packages or 0) if fin else "0" - incoterm = _xml_escape((log.incoterm or "") if log else "") - - lines.append(' ') - lines.append(f' {_xml_escape(factura.invoice_number or "")}') - lines.append(f' {_xml_escape(consignee_key)}') - lines.append(f' {invoice_date}') - lines.append(f' {_xml_escape(manufacturer_id)}') - lines.append(f' {freight}') - lines.append(f' {total_packages}') - lines.append(f' {incoterm}') - - for item in items: - _append_invoice_line_scaf(db, item, lines) - total_partidas += len(items) - - lines.append(' ') - total_facturas += 1 - - return total_facturas, total_partidas - - -def _validate_client_provider_impo( - db: Session, - provider: Optional[ClientProvider], - provider_db_id: str, - role: str, - issues: list, -) -> tuple[str, str]: - """ - Valida campos obligatorios de un CliPro para XML IMPO. - Equivale a los bloques IF ERRORCODE()=35 / CliPro:BrokerImpo='' de Clarion. - Retorna (ame_state, ame_country). - """ - if not provider: - issues.append( - f"No existe el {role} (ID: {provider_db_id}) — el archivo puede estar incorrecto. " - f"Solución: capturar la clave con datos obligatorios (Broker Impo, País, Estado)." - ) - return "", "" - - # Clave visible: short_name equivale a CliPro:Cliente en Clarion - display_key = provider.short_name or provider.name or provider_db_id - - import_broker = (provider.programs.import_broker or "") if provider.programs else "" - if not import_broker: - issues.append( - f"Falta capturar la clave de Broker de Importación del {role} '{display_key}'. " - f"Solución: capturar la información correspondiente al {role}." - ) - - m3_country = (provider.address.country or "") if provider.address else "" - ame_country = "" - if not m3_country: - issues.append( - f"Falta capturar la clave del País del {role} '{display_key}'. " - f"Solución: capturar la información correspondiente al {role}." - ) - else: - ame_country = _ame_country(db, m3_country) - - m3_state = (provider.address.state or "") if provider.address else "" - ame_state = "" - if not m3_state: - issues.append( - f"Falta capturar la descripción del Estado del {role} '{display_key}'. " - f"Solución: capturar la información correspondiente al {role}." - ) - elif m3_country: - state_obj = db.query(State).filter(State.m3_key == m3_country, State.description == m3_state).first() - if not state_obj: - issues.append( - f"No existe el estado '{m3_state}' con País '{m3_country}' para el {role} '{display_key}' — " - f"el archivo puede estar incorrecto. " - f"Solución: dar de alta el estado con clave americana en el catálogo de Países/Estados." - ) - elif not (state_obj.ame_key or ""): - issues.append( - f"Falta capturar la clave americana del Estado '{m3_state}'. " - f"Solución: capturar la información en el catálogo de Países/Estados." - ) - else: - ame_state = state_obj.ame_key - - return ame_state, ame_country - - -def _validate_transporter_impo( - log, - transporter: Optional[Transporter], - invoice_number: str, - issues: list, -) -> str: - """ - Valida transportista para XML IMPO. Equivale a IF MatFim:Transportista='' de Clarion. - Retorna loader_code (SCAC). - """ - if not log or not log.carrier_int_id: - issues.append( - f"Falta capturar la clave de Transportista en la Factura '{invoice_number}'. " - f"Solución: capturar o seleccionar la clave de Transportista en el encabezado de la factura." - ) - return "" - - if not transporter: - issues.append( - f"No existe el Transportista (ID: {log.carrier_int_id}) en catálogo para la Factura '{invoice_number}' — " - f"el archivo puede estar incorrecto. " - f"Solución: capturar el transportista con SCAC Code, Nombre y RFC." - ) - return "" - - scac = transporter.loader_code or "" - if not scac: - issues.append( - f"Falta capturar el Código del Cargador del Transportista (SCAC Code) " - f"para la Factura '{invoice_number}'. " - f"Solución: capturar la información correspondiente al transportista." - ) - return scac - - -class XmlRbSystemsService: - - def _exporter_name(self, db: Session) -> str: - empresa = db.query(GEmpresa).first() - return (empresa.name or "") if empresa else "" - - def generar_impo_temp( - self, db: Session, request: XmlRbSystemsImpoRequest - ) -> XmlRbSystemsResponse: - """ - Genera XML RB Systems para Importación Temporal (IT). - Estructura: + por factura
/. - Equivale a GENERACION_XML_RBSYSTEMS_IMPO_TEMP de Clarion. - """ - try: - root = ET.Element("Data") - issues: list[str] = [] - - facturas = ( - db.query(InvoiceHeader) - .options( - joinedload(InvoiceHeader.logistics), - joinedload(InvoiceHeader.compliance_mx), - ) - .filter(InvoiceHeader.invoice_number.in_(request.invoice_numbers)) - .all() - ) - - if not facturas: - return XmlRbSystemsResponse( - success=False, - message="No se encontraron facturas con los números proporcionados", - ) - - first_cmx = facturas[0].compliance_mx - first_prov = _lookup_provider(db, first_cmx.provider_id if first_cmx else None) - first_prov_name = (first_prov.name or "") if first_prov else "" - - app_info = ET.SubElement(root, "ApplicationInformation") - _sub(app_info, "ExporterName", first_prov_name) - _sub(app_info, "SoftwareProvider", "ADUANASOFT") - _sub(app_info, "Module", "SED") - _sub(app_info, "Version", "1.0") - _sub(app_info, "Action", "A") - - cuenta_facturas = 0 - cuenta_partidas = 0 - - for factura in facturas: - cmx = factura.compliance_mx - log = factura.logistics - inv_num = factura.invoice_number or "" - - provider = _lookup_provider(db, cmx.provider_id if cmx else None) - consignee = _lookup_provider(db, cmx.sold_to_id if cmx else None) - provider_key = str(cmx.provider_id or "") if cmx else "" - consignee_key = str(cmx.sold_to_id or "") if cmx else "" - - export_state, _ = _validate_client_provider_impo(db, provider, provider_key, "Proveedor", issues) - state_dest, country_dest = _validate_client_provider_impo(db, consignee, consignee_key, "Consignado/Vendido A", issues) - - transporter: Optional[Transporter] = None - if log and log.carrier_int_id: - transporter = ( - db.query(Transporter) - .filter(Transporter.transporter_id == log.carrier_int_id) - .first() - ) - scac = _validate_transporter_impo(log, transporter, inv_num, issues) - - vehicle: Optional[Vehicle] = None - if log and log.transport_int_id: - vehicle = ( - db.query(Vehicle) - .filter(Vehicle.vehicle_id == log.transport_int_id) - .first() - ) - vin = (vehicle.transport_identifier or "") if vehicle else "" - - export_broker = (provider.programs.import_broker or "") if provider and provider.programs else "" - consignee_broker = (consignee.programs.import_broker or "") if consignee and consignee.programs else "" - export_date = factura.invoice_date.strftime("%m/%d/%y") if factura.invoice_date else "" - - header = ET.SubElement(root, "Header") - _sub(header, "CustomerReference", inv_num) - _sub(header, "HazMat", "N") - _sub(header, "Exporter", export_broker) - _sub(header, "Consignee", consignee_broker) - _sub(header, "MOT", (log.transport_mode or "") if log else "") - _sub(header, "ExportDate", export_date) - _sub(header, "CountryDestination", country_dest) - _sub(header, "StateDestination", state_dest) - _sub(header, "ExportPort", request.exit_port or "") - _sub(header, "ExportState", export_state) - _sub(header, "Container", (log.transport_id or "") if log else "") - _sub(header, "Seal", (log.seal_number or "") if log else "") - _sub(header, "SCAC", scac) - - items = _query_invoice_items(db, factura.id) - detail = ET.SubElement(root, "Detail") - invoice_el = ET.SubElement(detail, "Invoice") - _sub(invoice_el, "InvoiceNumber", inv_num) - - for line in items: - raw_hts = (line.customs.american_fraction or "") if line.customs else "" - qty = str(line.quantity.quantity or "") if line.quantity else "" - gross_weight = _format_weight(line.quantity.gross_weight if line.quantity else None) - value = str(line.financial.value_usd or "") if line.financial else "" - - unit1 = "PC" - if line.unit_of_measure_info: - unit1 = line.unit_of_measure_info.american_code or line.uma_key or "PC" - elif line.uma_key: - unit1 = line.uma_key - - eccn = (line.part_info.eccn or "") if line.part_info else "" - lic_num = (line.part_info.exclusion_symbol or "") if line.part_info else "" - - hts_el = ET.SubElement(invoice_el, "HTS") - _sub(hts_el, "HTSNumber", _strip_hts(raw_hts)) - _sub(hts_el, "LicenseCode", "C33") - _sub(hts_el, "OriginIndicator", "D") - _sub(hts_el, "Qty1", qty) - _sub(hts_el, "Unit1", unit1) - _sub(hts_el, "Qty2", "") - _sub(hts_el, "Unit2", "") - _sub(hts_el, "Value", value) - _sub(hts_el, "GrossWeight", gross_weight) - _sub(hts_el, "ECCN", eccn) - _sub(hts_el, "LicenseNumber", lic_num) - _sub(hts_el, "DDTC", "") - - if vin: - vd = ET.SubElement(hts_el, "VehicleDetail") - vi = ET.SubElement(vd, "VehicleInformation") - _sub(vi, "VIN", vin) - _sub(vi, "TitleNumber", "") - _sub(vi, "IDQualifier", "") - _sub(vi, "TitleStateCode", "") - else: - _sub(hts_el, "VehicleDetail", "") - - cuenta_facturas += 1 - cuenta_partidas += len(items) - - xml_str = _pretty_xml(root) - filename = f"XML_RB_IMP_TEMP_{date.today().strftime('%Y%m%d')}.xml" - msg = f"{cuenta_facturas} factura(s), {cuenta_partidas} partida(s) generadas" - if issues: - msg += f" — {len(issues)} inconsistencia(s)" - - return XmlRbSystemsResponse( - success=True, - message=msg, - archivo_generado=filename, - content=xml_str, - cuenta_facturas=cuenta_facturas, - cuenta_partidas=cuenta_partidas, - inconsistencias=issues, - ) - - except Exception as e: - import traceback - traceback.print_exc() - return XmlRbSystemsResponse(success=False, message=str(e)) - - def generar_impo_def( - self, db: Session, request: XmlRbSystemsImpoRequest - ) -> XmlRbSystemsResponse: - """ - Genera XML RB Systems para Importación Definitiva (ID / SComprasMexID). - Estructura XML idéntica a IMPO_TEMP; diferencia: siempre presente. - Equivale a GENERACION_XML_RBSYSTEMS_IMPO_DEF de Clarion. - """ - try: - root = ET.Element("Data") - issues: list[str] = [] - - facturas = ( - db.query(InvoiceHeader) - .options( - joinedload(InvoiceHeader.logistics), - joinedload(InvoiceHeader.compliance_mx), - ) - .filter(InvoiceHeader.invoice_number.in_(request.invoice_numbers)) - .all() - ) - - if not facturas: - return XmlRbSystemsResponse( - success=False, - message="No se encontraron facturas con los números proporcionados", - ) - - first_cmx = facturas[0].compliance_mx - first_prov = _lookup_provider(db, first_cmx.provider_id if first_cmx else None) - first_prov_name = (first_prov.name or "") if first_prov else "" - - app_info = ET.SubElement(root, "ApplicationInformation") - _sub(app_info, "ExporterName", first_prov_name) - _sub(app_info, "SoftwareProvider", "ADUANASOFT") - _sub(app_info, "Module", "SED") - _sub(app_info, "Version", "1.0") - _sub(app_info, "Action", "A") - - cuenta_facturas = 0 - cuenta_partidas = 0 - - for factura in facturas: - cmx = factura.compliance_mx - log = factura.logistics - inv_num = factura.invoice_number or "" - - provider = _lookup_provider(db, cmx.provider_id if cmx else None) - consignee = _lookup_provider(db, cmx.sold_to_id if cmx else None) - provider_key = str(cmx.provider_id or "") if cmx else "" - consignee_key = str(cmx.sold_to_id or "") if cmx else "" - - export_state, _ = _validate_client_provider_impo(db, provider, provider_key, "Proveedor", issues) - state_dest, country_dest = _validate_client_provider_impo(db, consignee, consignee_key, "Consignado/Vendido A", issues) - - transporter: Optional[Transporter] = None - if log and log.carrier_int_id: - transporter = ( - db.query(Transporter) - .filter(Transporter.transporter_id == log.carrier_int_id) - .first() - ) - scac = _validate_transporter_impo(log, transporter, inv_num, issues) - - vehicle: Optional[Vehicle] = None - if log and log.transport_int_id: - vehicle = ( - db.query(Vehicle) - .filter(Vehicle.vehicle_id == log.transport_int_id) - .first() - ) - vin = (vehicle.transport_identifier or "") if vehicle else "" - - export_broker = (provider.programs.import_broker or "") if provider and provider.programs else "" - consignee_broker = (consignee.programs.import_broker or "") if consignee and consignee.programs else "" - export_date = factura.invoice_date.strftime("%m/%d/%y") if factura.invoice_date else "" - - header = ET.SubElement(root, "Header") - _sub(header, "CustomerReference", inv_num) - _sub(header, "HazMat", "N") - _sub(header, "Exporter", export_broker) - _sub(header, "Consignee", consignee_broker) - _sub(header, "MOT", (log.transport_mode or "") if log else "") - _sub(header, "ExportDate", export_date) - _sub(header, "CountryDestination", country_dest) - _sub(header, "StateDestination", state_dest) - _sub(header, "ExportPort", request.exit_port or "") - _sub(header, "ExportState", export_state) - _sub(header, "Container", (log.transport_id or "") if log else "") - _sub(header, "Seal", (log.seal_number or "") if log else "") - _sub(header, "SCAC", scac) - - items = _query_invoice_items(db, factura.id) - detail = ET.SubElement(root, "Detail") - invoice_el = ET.SubElement(detail, "Invoice") - _sub(invoice_el, "InvoiceNumber", inv_num) - - for line in items: - raw_hts = (line.customs.american_fraction or "") if line.customs else "" - qty = str(line.quantity.quantity or "") if line.quantity else "" - gross_weight = _format_weight(line.quantity.gross_weight if line.quantity else None) - value = str(line.financial.value_usd or "") if line.financial else "" - - unit1 = "PC" - if line.unit_of_measure_info: - unit1 = line.unit_of_measure_info.american_code or line.uma_key or "PC" - elif line.uma_key: - unit1 = line.uma_key - - eccn = (line.part_info.eccn or "") if line.part_info else "" - lic_num = (line.part_info.exclusion_symbol or "") if line.part_info else "" - - hts_el = ET.SubElement(invoice_el, "HTS") - _sub(hts_el, "HTSNumber", _strip_hts(raw_hts)) - _sub(hts_el, "LicenseCode", "C33") - _sub(hts_el, "OriginIndicator", "D") - _sub(hts_el, "Qty1", qty) - _sub(hts_el, "Unit1", unit1) - _sub(hts_el, "Qty2", "") - _sub(hts_el, "Unit2", "") - _sub(hts_el, "Value", value) - _sub(hts_el, "GrossWeight", gross_weight) - _sub(hts_el, "ECCN", eccn) - _sub(hts_el, "LicenseNumber", lic_num) - _sub(hts_el, "DDTC", "") - - # IMPO_DEF: VehicleDetail siempre presente (incluso con VIN vacío) - vd = ET.SubElement(hts_el, "VehicleDetail") - vi = ET.SubElement(vd, "VehicleInformation") - _sub(vi, "VIN", vin) - _sub(vi, "TitleNumber", "") - _sub(vi, "IDQualifier", "") - _sub(vi, "TitleStateCode", "") - - cuenta_facturas += 1 - cuenta_partidas += len(items) - - xml_str = _pretty_xml(root) - filename = f"XML_RB_IMP_DEF_{date.today().strftime('%Y%m%d')}.xml" - msg = f"{cuenta_facturas} factura(s), {cuenta_partidas} partida(s) generadas" - if issues: - msg += f" — {len(issues)} inconsistencia(s)" - - return XmlRbSystemsResponse( - success=True, - message=msg, - archivo_generado=filename, - content=xml_str, - cuenta_facturas=cuenta_facturas, - cuenta_partidas=cuenta_partidas, - inconsistencias=issues, - ) - - except Exception as e: - import traceback - traceback.print_exc() - return XmlRbSystemsResponse(success=False, message=str(e)) - - def generar_impo( - self, db: Session, request: XmlRbSystemsImpoRequest - ) -> XmlRbSystemsResponse: - try: - exporter_name = self._exporter_name(db) - shipment_set = ET.Element("ShipmentSet") - - cuenta_facturas = 0 - cuenta_partidas = 0 - - facturas = ( - db.query(InvoiceHeader) - .options( - joinedload(InvoiceHeader.logistics), - joinedload(InvoiceHeader.compliance_mx), - ) - .filter(InvoiceHeader.invoice_number.in_(request.invoice_numbers)) - .all() - ) - - for factura in facturas: - items = _query_invoice_items(db, factura.id) - n = _build_shipment( - shipment_set, exporter_name, factura, items, - entry_port=request.entry_port or "", - exit_port=request.exit_port or "", - ) - cuenta_facturas += 1 - cuenta_partidas += n - - xml_str = _pretty_xml(shipment_set) - filename = f"XML_RB_IMP_{date.today().strftime('%Y%m%d')}.xml" - - return XmlRbSystemsResponse( - success=True, - message=f"{cuenta_facturas} factura(s), {cuenta_partidas} partida(s) generadas", - archivo_generado=filename, - content=xml_str, - cuenta_facturas=cuenta_facturas, - cuenta_partidas=cuenta_partidas, - ) - - except Exception as e: - import traceback - traceback.print_exc() - return XmlRbSystemsResponse(success=False, message=str(e)) - - def generar_expo( - self, db: Session, request: XmlRbSystemsExpoRequest - ) -> XmlRbSystemsResponse: - """ - Genera XML RB Systems para Exportación (Entry/Manifest/Emanifest). - Input: números de manifiesto. Un bloque por manifiesto. - Equivale a GENERACION_XML_RBSYSTEMS_EXPO + EXPO_SCAII de Clarion. - """ - empresa = db.query(GEmpresa).first() - company_name = (empresa.name or "") if empresa else "" - company_broker = (empresa.broker_company or "") if empresa else "" - - full_content_parts: list[str] = [] - cuenta_manifiestos = 0 - cuenta_partidas = 0 - - for manifest_num in request.manifest_numbers: - manifiesto = ( - db.query(Manifest) - .filter(Manifest.manifest_number == manifest_num) - .first() - ) - if not manifiesto: - continue - - # Transportista → SCAC - transporter: Optional[Transporter] = None - if manifiesto.carrier_code: - transporter = ( - db.query(Transporter) - .filter(Transporter.transporter_key == manifiesto.carrier_code) - .first() - ) - - # EnviadoPor / ConsignadoA - sent_by = _lookup_provider_by_key(db, manifiesto.sent_by) - consigned_to = _lookup_provider_by_key(db, manifiesto.consigned_to) - - sent_by_broker = (sent_by.programs.broker or "") if sent_by and sent_by.programs else "" - consigned_broker = (consigned_to.programs.broker or "") if consigned_to and consigned_to.programs else "" - - clean_num = _clean_manifest_number(manifest_num) - entry_date_str = _format_clarion_date(manifiesto.entry_date) - - # Conductor - manifest_driver_row = ( - db.query(ManifestDriver) - .filter(ManifestDriver.manifest_number == manifest_num) - .first() - ) - driver: Optional[Driver] = None - if manifest_driver_row and manifiesto.carrier_code: - driver = ( - db.query(Driver) - .filter( - Driver.transporter_key == manifiesto.carrier_code, - Driver.driver_name == manifest_driver_row.driver_name, - ) - .first() - ) - - driver_badge = (driver.badge_number or "") if driver else "" - driver_first = (driver.first_name or "") if driver else "" - driver_last = (driver.last_name or "") if driver else "" - driver_ace = (driver.ace_id or "") if driver else "" - - # Trailer - trailer: Optional[Trailer] = None - if manifiesto.trailer_number: - trailer = ( - db.query(Trailer) - .filter(Trailer.trailer_number == manifiesto.trailer_number) - .first() - ) - trailer_state = _ame_state_from_ame_country( - db, - (trailer.country or "") if trailer else None, - (trailer.state or "") if trailer else None, - ) - - # Transporte/tractor (GTransportes — buscado por NumTrailer) - vehicle: Optional[Vehicle] = None - if manifiesto.trailer_number: - vehicle = ( - db.query(Vehicle) - .filter(Vehicle.vehicle_key == manifiesto.trailer_number) - .first() - ) - vehicle_state = _ame_state_from_ame_country( - db, - (vehicle.country or "") if vehicle else None, - (vehicle.state or "") if vehicle else None, - ) - - lines: list[str] = [] - lines.append('') - lines.append('') - lines.append('') - - lines.append(' ') - lines.append(f' {company_name}') - lines.append(f' {company_broker}') - lines.append(' ADUANASOFT') - lines.append(' ENTRY') - lines.append(' 1.0') - lines.append(' A') - lines.append(' ') - - lines.append(' ') - lines.append(f' {manifest_num[:3]}') - lines.append(f' {clean_num[3:10]}') - lines.append(f' {manifest_num[-1] if manifest_num else ""}') - lines.append(f' {manifiesto.manifest_type or "01"}') - lines.append(f' {manifiesto.transport_mode or ""}') - lines.append(f' {(transporter.loader_code or "") if transporter else ""}') - lines.append(f' {sent_by_broker}') - lines.append(f' {consigned_broker}') - lines.append(f' {manifiesto.entry_port or ""}') - lines.append(f' {manifiesto.entry_port_loc or ""}') - lines.append(f' {entry_date_str}') - lines.append(f' {entry_date_str}') - lines.append(f' {manifiesto.foreign_exit_port or ""}') - lines.append(f' {entry_date_str}') - - if manifiesto.transport_mode == "40": - niu = _get_niu_for_manifest(db, manifest_num) - lines.append(f' {niu[:5]}') - else: - lines.append(' ') - - lines.append(' ') - lines.append(' N') - lines.append(' N') - lines.append(f' {manifiesto.trailer_number or ""}') - - lines.append(' ') - mfr_id = (sent_by.programs.manufacturer_id or "") if sent_by and sent_by.programs else "" - _, n_scaii = _build_entry_invoices_scaii(db, manifest_num, mfr_id, lines) - _, n_scaf = _build_entry_invoices_scaf(db, manifest_num, lines) - _, n_tem_scaf = _build_entry_invoices_tem_scaf(db, manifest_num, lines) - cuenta_partidas += n_scaii + n_scaf + n_tem_scaf - lines.append(' ') - lines.append(' ') - - # Manifest/Fast - lines.append(' ') - lines.append(' ') - lines.append(f' {driver_badge}') - lines.append(f' {(driver_first + " " + driver_last).strip()}') - lines.append(f' {manifiesto.trailer_number or ""}') - lines.append(f' {(trailer.plate_number or "") if trailer else ""}') - lines.append(f' {trailer_state}') - lines.append(f' {(vehicle.plate_number or "") if vehicle else ""}') - lines.append(f' {vehicle_state}') - lines.append(' ') - lines.append(' ') - lines.append(' ') - lines.append(' ') - lines.append(' ') - - # Emanifest (opcional) - if request.include_emanifest: - consignee_state_str = "" - consignee_country_str = "" - consignee_name = (consigned_to.name or "") if consigned_to else "" - consignee_addr = "" - consignee_city = "" - consignee_zip = "" - - if consigned_to and consigned_to.address: - addr = consigned_to.address - consignee_addr = addr.streets or "" - consignee_city = addr.city or "" - consignee_zip = addr.postal_code or "" - consignee_country_str = _ame_country(db, addr.country) - consignee_state_str = _ame_state(db, addr.country, addr.state) - - lines.append(' ') - lines.append(' ') - lines.append(f' {sent_by_broker}') - lines.append(f' {manifiesto.foreign_exit_port or ""}') - lines.append(f' {manifiesto.entry_port or ""}') - desc = (manifiesto.description or "")[:45] - lines.append(f' {desc}') - lines.append(' ') - lines.append(' ') - lines.append(' ') - lines.append(f' {clean_num[:10]}') - lines.append(' ') - lines.append(f' {consignee_name}') - lines.append(f'
{consignee_addr}
') - lines.append(f' {consignee_city}') - lines.append(f' {consignee_state_str}') - lines.append(f' {consignee_zip}') - lines.append(f' {consignee_country_str}') - lines.append('
') - lines.append(' ') - lines.append(f' {(vehicle.ace_vehicle_key or "") if vehicle else ""}') - lines.append(f' {(vehicle.seal or "") if vehicle else ""}') - lines.append(' ') - lines.append(' ') - lines.append(f' {(trailer.ace_trailer_number or "") if trailer else ""}') - lines.append(f' {(trailer.trailer_number or "") if trailer else ""}') - lines.append(f' {(trailer.trailer_type_key or "") if trailer else ""}') - lines.append(f' {(trailer.plate_number or "") if trailer else ""}') - lines.append(f' {(trailer.country or "") if trailer else ""}') - lines.append(f' {trailer_state}') - lines.append(f' {(trailer.seal or "") if trailer else ""}') - lines.append(' ') - lines.append(' ') - lines.append(f' {(driver_first + " " + driver_last).strip()}') - lines.append(f' {driver_ace}') - lines.append(' ') - lines.append('
') - lines.append('
') - - lines.append('
') - full_content_parts.append("\n".join(lines)) - cuenta_manifiestos += 1 - - filename = f"XML_RB_EXP_{request.manifest_numbers[0] if request.manifest_numbers else 'EXPO'}_{date.today().strftime('%Y%m%d')}.xml" - - return XmlRbSystemsResponse( - success=True, - message=f"{cuenta_manifiestos} manifiesto(s), {cuenta_partidas} partida(s) generadas", - archivo_generado=filename, - content="\n".join(full_content_parts), - cuenta_facturas=cuenta_manifiestos, - cuenta_partidas=cuenta_partidas, - ) diff --git a/backend/api/v1/modules/a76/reports/xml_rb_systems/task.py b/backend/api/v1/modules/a76/reports/xml_rb_systems/task.py deleted file mode 100644 index e9396903..00000000 --- a/backend/api/v1/modules/a76/reports/xml_rb_systems/task.py +++ /dev/null @@ -1,62 +0,0 @@ -from core.celery_app import celery_app -from core.database import get_core_db as get_db -from .service import XmlRbSystemsService -from .schemas import XmlRbSystemsImpoRequest, XmlRbSystemsExpoRequest - - -@celery_app.task(name="generar_xml_rb_systems_impo_temp_async", bind=True) -def generar_xml_rb_systems_impo_temp_async(self, request_data: dict, tenant_id: int): - try: - db = next(get_db()) - request = XmlRbSystemsImpoRequest(**request_data) - response = XmlRbSystemsService().generar_impo_temp(db, request) - result = response.model_dump() - if response.content: - import base64 - result["content"] = base64.b64encode(response.content.encode("utf-8")).decode("utf-8") - result["file_name"] = response.archivo_generado - result["media_type"] = "application/xml" - return result - except Exception as e: - self.update_state(state="FAILURE", meta={"exc_type": type(e).__name__, "exc_message": str(e)}) - raise e - - -@celery_app.task(name="generar_xml_rb_systems_impo_async", bind=True) -def generar_xml_rb_systems_impo_async(self, request_data: dict, tenant_id: int): - try: - db = next(get_db()) - request = XmlRbSystemsImpoRequest(**request_data) - response = XmlRbSystemsService().generar_impo(db, request) - result = response.model_dump() - if response.content: - import base64 - result["content"] = base64.b64encode(response.content.encode("utf-8")).decode("utf-8") - result["file_name"] = response.archivo_generado - result["media_type"] = "application/xml" - return result - except Exception as e: - import traceback - traceback.print_exc() - self.update_state(state="FAILURE", meta={"exc_type": type(e).__name__, "exc_message": str(e)}) - raise e - - -@celery_app.task(name="generar_xml_rb_systems_expo_async", bind=True) -def generar_xml_rb_systems_expo_async(self, request_data: dict, tenant_id: int): - try: - db = next(get_db()) - request = XmlRbSystemsExpoRequest(**request_data) - response = XmlRbSystemsService().generar_expo(db, request) - result = response.model_dump() - if response.content: - import base64 - result["content"] = base64.b64encode(response.content.encode("utf-8")).decode("utf-8") - result["file_name"] = response.archivo_generado - result["media_type"] = "application/xml" - return result - except Exception as e: - import traceback - traceback.print_exc() - self.update_state(state="FAILURE", meta={"exc_type": type(e).__name__, "exc_message": str(e)}) - raise e diff --git a/backend/api/v1/modules/a76/router.py b/backend/api/v1/modules/a76/router.py deleted file mode 100644 index 9f00f43f..00000000 --- a/backend/api/v1/modules/a76/router.py +++ /dev/null @@ -1,216 +0,0 @@ -""" -Router principal de API v1 -Agrega todos los módulos de la aplicación -""" - -from fastapi import APIRouter - -from .customs_brokers.routes import router as customs_broker_router - -# Importar routers de módulos -from .general_catalogs.router import router as general_catalogs_router -from .invoices.routes import router as invoices_router -from .invoices.imports.process.routes import router as invoice_process_router -from .invoices.imports.revert.routes import router as invoice_revert_router -from .items.routes import router as items_router -from .classes.routes import router as classes_router - -from .clients_and_providers import router as client_and_provider_router -from .layouts_csv.facturas.routes import router as imports_router -from .layouts_csv.exportacion.routes import router as exportacion_imports_router -from .layouts_csv.cambio_regimen_regularizacion.routes import router as cambio_regimen_regularizacion_imports_router -from .csv_templates.routes import router as csv_templates_router -from .invoice_settings.routes import router as invoice_settings_router -from .item_presets.routes import router as item_presets_router -from .transportation.drivers.routes import router as drivers_router -from .doc_types_dig.routes import router as doc_types_dig_router -from .rule_octave.fractions.routes import router as fraction_rule_octave_router -from .rule_octave.country.routes import router as country_rule_oct_router -from .parts import router as parts_router -from .boms import router as boms_router -from .pedmientos.router import router as pedimentos_router -from .permission_rule_oct.routes import router as permission_rule_oct_router -from .transportation.trailers.routes import router as trailers_router -from .transportation.transporters.routes import router as transporters_router -from .transportation.vehicles.routes import router as vehicles_router -from api.v1.modules.public.reference_data.material_types.routes import router as material_types_router -from .factura_cove.routes import router as factura_cove_router - -# --- NUEVO IMPORT PARA REPORTES DE FACTURAS --- -from .reports.importacion.facturas.routes import router as invoices_reports_router -from .reports.importacion.consolidados.routes import router as consolidated_reports_router -from .reports.importacion.packing_list.routes import router as packing_list_router -from .reports.exportacion.aviso_consolidado.routes import router as aviso_consolidado_export_router -from .reports.exportacion.partes_descargadas.routes import router as downloaded_parts_reports_router -from .reports.movements.invoices.routes import router as movement_invoices_router -from .reports.movements.saldos.routes import router as movement_saldos_router -from .reports.movements.vencimiento.routes import router as movement_vencimiento_router -from .reports.exportacion.descargo.routes import router as discharge_reports_router -from .reports.exportacion.transmission.MAINX30.routes import router as transmission_router -from .reports.importacion.transmission.temporal.MAINX30.routes import router as transmission_temporal_router -from .reports.importacion.transmission.definitive.MAINX30.routes import router as transmission_definitive_router -from .reports.importacion.winsaai.router import router as winsaai_router -from .reports.xml_rb_systems.routes import router as xml_rb_systems_router -from .reports.xml_optima.routes import router as xml_optima_router -from .app_settings.routes import router as app_settings_router - -from .manifests.manifest.routes import router as manifests_router -from .manifests.driver.routes import router as manifest_drivers_router -from .manifests.manifiesto_anexo.routes import router as manifest_anexos_router -from .expediente_archivos.routes import router as expediente_archivos_router - - -# Router principal -router = APIRouter() - -# Registrar módulos -router.include_router(general_catalogs_router, prefix="/a76", tags=["a76 / general_catalogs"]) -router.include_router(invoices_router, prefix="/a76", tags=["a76 / invoices"]) -router.include_router(invoice_process_router, prefix="/a76", tags=["a76 / invoices"]) -router.include_router(invoice_revert_router, prefix="/a76", tags=["a76 / invoices"]) -router.include_router(items_router, prefix="/a76", tags=["a76 / items"]) -router.include_router(imports_router, prefix="/a76/imports", tags=["a76 / imports"]) -router.include_router(exportacion_imports_router, prefix="/a76/imports/exportacion", tags=["a76 / imports / exportacion"]) -router.include_router(cambio_regimen_regularizacion_imports_router, prefix="/a76/imports/cambio-regimen-regularizacion", tags=["a76 / imports / cambio_regimen_regularizacion"]) -router.include_router(csv_templates_router, prefix="/a76/csv-templates", tags=["a76 / csv_templates"]) -router.include_router(invoice_settings_router) -router.include_router(item_presets_router, prefix="/a76/item-presets", tags=["a76 / item_presets"]) -router.include_router(pedimentos_router, prefix="/a76") -router.include_router(client_and_provider_router, prefix="/a76", tags=["a76 / clients_and_providers"]) -router.include_router(classes_router, prefix="/a76/classes", tags=["a76 / classes"]) -router.include_router(parts_router, prefix="/a76", tags=["a76 / parts"]) -router.include_router(boms_router, prefix="/a76/boms", tags=["a76 / boms"]) -router.include_router(permission_rule_oct_router, prefix="/a76", tags=["a76 / permission_rule_oct"]) -router.include_router(fraction_rule_octave_router, prefix="/a76", tags=["a76 / fraction_rule_octave"]) -router.include_router(country_rule_oct_router, prefix="/a76", tags=["a76 / country_rule_oct"]) -router.include_router(trailers_router, prefix="/a76/transportation", tags=["a76 / trailers"]) -router.include_router(customs_broker_router, prefix="/a76", tags=["a76 / customs_broker"]) -router.include_router(doc_types_dig_router, prefix="/a76", tags=["a76 / document_types_digitization"]) -router.include_router(drivers_router, prefix="/a76", tags=["a76 / drivers"]) -router.include_router(transporters_router, prefix="/a76", tags=["a76 / transporters"]) -router.include_router(vehicles_router, prefix="/a76/transportation", tags=["a76 / vehicles"]) -router.include_router(factura_cove_router, prefix="/a76/factura-cove", tags=["a76 / factura_cove"]) -router.include_router(expediente_archivos_router, prefix="/a76", tags=["a76 / expediente-archivos"]) - -# Registrar router de tipos de material públicos -router.include_router( - material_types_router, - prefix="/public/reference-data", - tags=["Reference Data"] -) - -# --- REGISTRO DE RUTAS DE REPORTES --- -router.include_router( - invoices_reports_router, - prefix="/a76/reports/importacion/facturas", - tags=["a76 / reports"] -) - -router.include_router( - consolidated_reports_router, - prefix="/a76/reports/importacion/consolidados", - tags=["a76 / reports"] -) - -router.include_router( - packing_list_router, - prefix="/a76/reports/importacion/packing-lists", - tags=["a76 / reports"] -) - -router.include_router( - aviso_consolidado_export_router, - prefix="/a76/reports/exportacion/aviso_consolidado", - tags=["a76 / reports"] -) - -router.include_router( - downloaded_parts_reports_router, - prefix="/a76/reports/exportacion/partes-descargadas", - tags=["a76 / reports"] -) - -router.include_router( - - movement_invoices_router, - prefix="/a76/reports/movements/invoices", - tags=["a76 / reports"] -) - -router.include_router( - movement_saldos_router, - prefix="/a76/reports/movements/saldos", - tags=["a76 / reports"] -) - -router.include_router( - movement_vencimiento_router, - prefix="/a76/reports/movements/vencimiento", - tags=["a76 / reports"] -) - -router.include_router( - discharge_reports_router, - prefix="/a76/reports/exportacion/descargo", - tags=["a76 / reports"] -) - -router.include_router( - manifests_router, - prefix="/a76", - tags=["a76 / manifests"] -) - -router.include_router( - manifest_drivers_router, - prefix="/a76", - tags=["a76 / manifests"] -) - -router.include_router( - manifest_anexos_router, - prefix="/a76", - tags=["a76 / manifests"] -) - -router.include_router( - transmission_router, - prefix="/a76/reports/exportacion/transmission", - tags=["a76 / reports"] -) - -router.include_router( - transmission_temporal_router, - prefix="/a76/reports/importacion/transmission/temporal", - tags=["a76 / reports"] -) - -router.include_router( - transmission_definitive_router, - prefix="/a76/reports/importacion/transmission/definitive", - tags=["a76 / reports"] -) - -router.include_router( - winsaai_router, - prefix="/a76/reports/importacion/winsaai", - tags=["a76 / reports"] -) - -router.include_router( - xml_rb_systems_router, - prefix="/a76/reports/xml-rb-systems", - tags=["a76 / reports"] -) - -router.include_router( - xml_optima_router, - prefix="/a76/reports/xml-optima", - tags=["a76 / reports"] -) - -router.include_router(app_settings_router) - -# Registrar router de bitácora -from .audit_log.router import router as audit_log_router -router.include_router(audit_log_router, prefix="/a76/audit-log", tags=["Audit Log"]) diff --git a/backend/api/v1/modules/a76/rule_octave/balances/__init__.py b/backend/api/v1/modules/a76/rule_octave/balances/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/backend/api/v1/modules/a76/rule_octave/balances/models.py b/backend/api/v1/modules/a76/rule_octave/balances/models.py deleted file mode 100644 index 7ef83c83..00000000 --- a/backend/api/v1/modules/a76/rule_octave/balances/models.py +++ /dev/null @@ -1,56 +0,0 @@ -from decimal import Decimal -from typing import Optional - -from sqlalchemy import Integer, Numeric, PrimaryKeyConstraint, String -from sqlalchemy.orm import Mapped, mapped_column - -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base - - -class OctaveBalance(Base, TenantScopedMixin, TimestampMixin): - """ - Saldos de Regla Octava por factura/partida. - Paridad: SSaldosReglaOctava (Clarion SCAII). - - La PK es compuesta natural, igual que ROctSal_PKFaParPaiTiSeROProSisLin. - Se agregan tenant_id y company_id al inicio para el scope multi-tenant. - """ - - __tablename__ = "octave_balance" - __table_args__ = ( - PrimaryKeyConstraint( - "tenant_id", - "company_id", - "invoice_import", - "part_number", - "origin_country", - "fraction_type", - "sector", - "octave_permit", - "origin", - "system", - "line", - name="pk_octave_balance", - ), - {"schema": "a76"}, - ) - - # ── PK compuesta ────────────────────────────────────────────────────────── - invoice_import: Mapped[str] = mapped_column(String(15)) # FACTURAIMPO - part_number: Mapped[str] = mapped_column(String(70)) # NUMPARTE - origin_country: Mapped[str] = mapped_column(String(3)) # PAISORIGEN - fraction_type: Mapped[str] = mapped_column(String(7)) # TIPOFRACIMPO - sector: Mapped[str] = mapped_column(String(8)) # SECTOR - octave_permit: Mapped[str] = mapped_column(String(100)) # PERMISOROCTAVA - origin: Mapped[str] = mapped_column(String(3)) # PROCEDENCIA ('TEM') - system: Mapped[str] = mapped_column(String(5)) # SISTEMA ('fixed_asset | inventory') - line: Mapped[int] = mapped_column(Integer) # LINEA - - # ── Datos ───────────────────────────────────────────────────────────────── - quantity_stock: Mapped[Optional[Decimal]] = mapped_column(Numeric(19, 8)) # CANTEXITENCIA - unit_of_measure: Mapped[Optional[str]] = mapped_column(String(5)) # UNIMED - class_code: Mapped[Optional[str]] = mapped_column(String(8)) # CLASE - import_fraction: Mapped[Optional[str]] = mapped_column(String(10)) # FRACCIONIMPO - ro_fraction: Mapped[Optional[str]] = mapped_column(String(10)) # FRACCIONROCTAVA - value_me: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # VALORME diff --git a/backend/api/v1/modules/a76/rule_octave/country/dto.py b/backend/api/v1/modules/a76/rule_octave/country/dto.py deleted file mode 100644 index 8352bb4d..00000000 --- a/backend/api/v1/modules/a76/rule_octave/country/dto.py +++ /dev/null @@ -1,21 +0,0 @@ -""" -DTOs for CountryRuleOct. -""" - -from pydantic import BaseModel - - -class CountryRuleOctBaseDTO(BaseModel): - permission: str - line: int - fraction: str - country_code: str - - -class CountryRuleOctCreateDTO(CountryRuleOctBaseDTO): - pass - - -class CountryRuleOctResponseDTO(CountryRuleOctBaseDTO): - class Config: - from_attributes = True diff --git a/backend/api/v1/modules/a76/rule_octave/country/models.py b/backend/api/v1/modules/a76/rule_octave/country/models.py deleted file mode 100644 index fd1fce50..00000000 --- a/backend/api/v1/modules/a76/rule_octave/country/models.py +++ /dev/null @@ -1,46 +0,0 @@ -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base -from sqlalchemy import ( - ForeignKeyConstraint, - Integer, - PrimaryKeyConstraint, - String, - UniqueConstraint, -) -from sqlalchemy.orm import Mapped, mapped_column - - -class CountryRuleOct(Base, TenantScopedMixin, TimestampMixin): - __tablename__ = "country_rule_oct" - __table_args__ = ( - PrimaryKeyConstraint("id", name="country_rule_oct_pkey"), - ForeignKeyConstraint( - ["tenant_id", "company_id", "permission", "line", "fraction"], - [ - "a76.fraction_rule_octave.tenant_id", - "a76.fraction_rule_octave.company_id", - "a76.fraction_rule_octave.permission", - "a76.fraction_rule_octave.line", - "a76.fraction_rule_octave.fraction", - ], - ondelete="CASCADE", - name="fk_country_rule_oct_frac_octava", - ), - UniqueConstraint( - "tenant_id", - "company_id", - "permission", - "line", - "fraction", - "country_code", - name="uq_country_rule_oct_permission_line_fraction_country", - ), - {"schema": "a76"}, - ) - - id: Mapped[int] = mapped_column(Integer, primary_key=True) - - permission: Mapped[str] = mapped_column(String(20)) - line: Mapped[int] = mapped_column() - fraction: Mapped[str] = mapped_column(String(10)) - country_code: Mapped[str] = mapped_column(String(3)) diff --git a/backend/api/v1/modules/a76/rule_octave/country/routes.py b/backend/api/v1/modules/a76/rule_octave/country/routes.py deleted file mode 100644 index a1551276..00000000 --- a/backend/api/v1/modules/a76/rule_octave/country/routes.py +++ /dev/null @@ -1,98 +0,0 @@ -from typing import List - -from core.database import get_core_db -from core.security import get_current_user -from fastapi import APIRouter, Depends, HTTPException, status -from sqlalchemy.orm import Session - -from .dto import CountryRuleOctCreateDTO, CountryRuleOctResponseDTO -from .services import CountryRuleOctService - -router = APIRouter(prefix="/country-rule-oct") - - -@router.get("/", response_model=List[CountryRuleOctResponseDTO]) -async def list_countries( - db: Session = Depends(get_core_db), current_user: dict = Depends(get_current_user) -): - """ - List all CountryRuleOct entries. - """ - # Validate access to the tenant and company - tenant_id = current_user.get("tenant_id") - company_id = current_user.get("company_id") - - if not tenant_id or not company_id: - raise HTTPException( - status_code=403, detail="Access denied: Tenant or Company not found" - ) - - return db.query(CountryRuleOctService).all() - - -@router.get( - "/{permission}/{line}/{fraction}/{country_code}", - response_model=CountryRuleOctResponseDTO, -) -async def read_country_rule( - permission: str, - line: int, - fraction: str, - country_code: str, - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - """ - Get a specific CountryRuleOct by its composite key. - """ - # Validate access to the tenant and company - tenant_id = current_user.get("tenant_id") - company_id = current_user.get("company_id") - - if not tenant_id or not company_id: - raise HTTPException( - status_code=403, detail="Access denied: Tenant or Company not found" - ) - - country = CountryRuleOctService.get_country_by_keys( - db, permission, line, fraction, country_code - ) - if not country: - raise HTTPException(status_code=404, detail="CountryRuleOct not found") - return country - - -@router.post( - "/", response_model=CountryRuleOctResponseDTO, status_code=status.HTTP_201_CREATED -) -async def create_country_rule( - country_data: CountryRuleOctCreateDTO, - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - """ - Create a new CountryRuleOct entry. - """ - return CountryRuleOctService.create_country_rule(db, country_data) - - -@router.delete( - "/{permission}/{line}/{fraction}/{country_code}", - status_code=status.HTTP_204_NO_CONTENT, -) -async def delete_country_rule( - permission: str, - line: int, - fraction: str, - country_code: str, - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - """ - Delete a CountryRuleOct by its composite key. - """ - country = CountryRuleOctService.delete_country_rule( - db, permission, line, fraction, country_code - ) - if not country: - raise HTTPException(status_code=404, detail="CountryRuleOct not found") diff --git a/backend/api/v1/modules/a76/rule_octave/country/services.py b/backend/api/v1/modules/a76/rule_octave/country/services.py deleted file mode 100644 index 6a355b7e..00000000 --- a/backend/api/v1/modules/a76/rule_octave/country/services.py +++ /dev/null @@ -1,44 +0,0 @@ -""" -Service layer for CountryRuleOct. -""" - -from sqlalchemy.orm import Session - -from . import dto, models - - -class CountryRuleOctService: - @staticmethod - def get_country_by_keys( - db: Session, permission: str, line: int, fraction: str, country_code: str - ): - return ( - db.query(models.CountryRuleOct) - .filter( - models.CountryRuleOct.permission == permission, - models.CountryRuleOct.line == line, - models.CountryRuleOct.fraction == fraction, - models.CountryRuleOct.country_code == country_code, - ) - .first() - ) - - @staticmethod - def create_country_rule(db: Session, country_data: dto.CountryRuleOctCreateDTO): - new_country = models.CountryRuleOct(**country_data.dict()) - db.add(new_country) - db.commit() - db.refresh(new_country) - return new_country - - @staticmethod - def delete_country_rule( - db: Session, permission: str, line: int, fraction: str, country_code: str - ): - country = CountryRuleOctService.get_country_by_keys( - db, permission, line, fraction, country_code - ) - if country: - db.delete(country) - db.commit() - return country diff --git a/backend/api/v1/modules/a76/rule_octave/country/test_country_rule_oct.py b/backend/api/v1/modules/a76/rule_octave/country/test_country_rule_oct.py deleted file mode 100644 index 93ffbd21..00000000 --- a/backend/api/v1/modules/a76/rule_octave/country/test_country_rule_oct.py +++ /dev/null @@ -1,36 +0,0 @@ -import pytest -from fastapi import FastAPI -from fastapi.testclient import TestClient - -from .routes import router - -app = FastAPI() -app.include_router(router) -client = TestClient(app) - - -@pytest.mark.usefixtures("client", "access_token") -def test_list_country_rules(client, access_token): - headers = {"Authorization": f"Bearer {access_token}"} - response = client.get("/country-rule-oct/", headers=headers) - assert response.status_code == 200 - assert "items" in response.json() - assert "page" in response.json() - assert "page_size" in response.json() - - -@pytest.mark.usefixtures("client", "access_token") -def test_get_country_rule_not_found(client, access_token): - headers = {"Authorization": f"Bearer {access_token}"} - response = client.get("/country-rule-oct/invalid_id", headers=headers) - assert response.status_code == 404 - - -def test_create_country_rule_forbidden(): - response = client.post("/country-rule-oct/", json={"rule": "Test Rule"}) - assert response.status_code in (403, 405, 404) - - -def test_update_country_rule_forbidden(): - response = client.put("/country-rule-oct/1", json={"rule": "Updated Rule"}) - assert response.status_code in (403, 405, 404) diff --git a/backend/api/v1/modules/a76/rule_octave/fractions/dto.py b/backend/api/v1/modules/a76/rule_octave/fractions/dto.py deleted file mode 100644 index 562ef34d..00000000 --- a/backend/api/v1/modules/a76/rule_octave/fractions/dto.py +++ /dev/null @@ -1,28 +0,0 @@ -""" -DTOs for FractionRuleOctave. -""" - -from typing import Optional - -from pydantic import BaseModel - - -class FractionRuleOctaveBaseDTO(BaseModel): - PERMISSION: str - LINE: int - FRACTION: str - QUOTA_AMOUNT: Optional[float] - USED_AMOUNT: Optional[float] - QUOTA_VALUE: Optional[float] - USED_VALUE: Optional[float] - UNIT_COST_ME: Optional[float] - UNIT_MEASURE: Optional[str] - - -class FractionRuleOctaveCreateDTO(FractionRuleOctaveBaseDTO): - pass - - -class FractionRuleOctaveResponseDTO(FractionRuleOctaveBaseDTO): - class Config: - from_attributes = True diff --git a/backend/api/v1/modules/a76/rule_octave/fractions/models.py b/backend/api/v1/modules/a76/rule_octave/fractions/models.py deleted file mode 100644 index 0ee65a57..00000000 --- a/backend/api/v1/modules/a76/rule_octave/fractions/models.py +++ /dev/null @@ -1,40 +0,0 @@ -from decimal import Decimal -from typing import Optional - -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base -from sqlalchemy import Integer, Numeric, String, UniqueConstraint -from sqlalchemy.orm import Mapped, mapped_column - - -class FractionRuleOctave(Base, TenantScopedMixin, TimestampMixin): - """ - Fracciones por permiso de Regla Octava. - Paridad: GFracROctava (Clarion SCAII). - PK natural: (permission, line, fraction) — se usa junto a tenant/company. - """ - - __tablename__ = "fraction_rule_octave" - __table_args__ = ( - UniqueConstraint( - "tenant_id", - "company_id", - "permission", - "line", - "fraction", - name="uq_fraction_rule_octave_permission_line_fraction", - ), - {"schema": "a76"}, - ) - - id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) - - permission: Mapped[str] = mapped_column(String(20)) # PERMISO - line: Mapped[int] = mapped_column(Integer) # LINEA - fraction: Mapped[str] = mapped_column(String(10)) # FRACCION - quota_quantity: Mapped[Optional[Decimal]] = mapped_column(Numeric(19, 8)) # CANTIDADCUPO - quantity_used: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # CANTUSADA - quota_value: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # VALORCUPO - value_used: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # VALORUSADA - unit_cost_me: Mapped[Optional[Decimal]] = mapped_column(Numeric(23, 8)) # COSTOUNITARIOME - unit_of_measure: Mapped[Optional[str]] = mapped_column(String(5)) # UNIMED diff --git a/backend/api/v1/modules/a76/rule_octave/fractions/routes.py b/backend/api/v1/modules/a76/rule_octave/fractions/routes.py deleted file mode 100644 index 2e09474e..00000000 --- a/backend/api/v1/modules/a76/rule_octave/fractions/routes.py +++ /dev/null @@ -1,112 +0,0 @@ -from typing import List - -from core.database import get_core_db -from core.security import get_current_user -from fastapi import APIRouter, Depends, HTTPException, status -from sqlalchemy.orm import Session - -from .dto import FractionRuleOctaveCreateDTO, FractionRuleOctaveResponseDTO -from .services import FractionRuleOctaveService - -router = APIRouter(prefix="/fraction_rule_octave") - - -@router.get("/", response_model=List[FractionRuleOctaveResponseDTO]) -async def list_fractions( - db: Session = Depends(get_core_db), current_user: dict = Depends(get_current_user) -): - """ - List all FractionRuleOctave entries. - """ - # Validate access to the tenant and company - tenant_id = current_user.get("tenant_id") - company_id = current_user.get("company_id") - - if not tenant_id or not company_id: - raise HTTPException( - status_code=403, detail="Access denied: Tenant or Company not found" - ) - - return db.query(FractionRuleOctaveService).all() - - -@router.get( - "/{permission}/{line}/{fraction}", response_model=FractionRuleOctaveResponseDTO -) -async def read_fraction( - permission: str, - line: int, - fraction: str, - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - """ - Get a specific FractionRuleOctave by its composite key. - """ - # Validate access to the tenant and company - tenant_id = current_user.get("tenant_id") - company_id = current_user.get("company_id") - - if not tenant_id or not company_id: - raise HTTPException( - status_code=403, detail="Access denied: Tenant or Company not found" - ) - - frac = FractionRuleOctaveService.get_fraction_by_permission_line( - db, permission, line, fraction - ) - if not frac: - raise HTTPException(status_code=404, detail="FractionRuleOctave not found") - return frac - - -@router.post( - "/", - response_model=FractionRuleOctaveResponseDTO, - status_code=status.HTTP_201_CREATED, -) -async def create_frac( - frac_data: FractionRuleOctaveCreateDTO, - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - """ - Create a new FractionRuleOctave entry. - """ - # Validate access to the tenant and company - tenant_id = current_user.get("tenant_id") - company_id = current_user.get("company_id") - - if not tenant_id or not company_id: - raise HTTPException( - status_code=403, detail="Access denied: Tenant or Company not found" - ) - - return FractionRuleOctaveService.create_frac(db, frac_data) - - -@router.delete( - "/{permission}/{line}/{fraction}", status_code=status.HTTP_204_NO_CONTENT -) -async def delete_fraction( - permission: str, - line: int, - fraction: str, - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - """ - Delete a FractionRuleOctave by its composite key. - """ - # Validate access to the tenant and company - tenant_id = current_user.get("tenant_id") - company_id = current_user.get("company_id") - - if not tenant_id or not company_id: - raise HTTPException( - status_code=403, detail="Access denied: Tenant or Company not found" - ) - - frac = FractionRuleOctaveService.delete_fraction(db, permission, line, fraction) - if not frac: - raise HTTPException(status_code=404, detail="FractionRuleOctave not found") diff --git a/backend/api/v1/modules/a76/rule_octave/fractions/services.py b/backend/api/v1/modules/a76/rule_octave/fractions/services.py deleted file mode 100644 index bd8d1dc9..00000000 --- a/backend/api/v1/modules/a76/rule_octave/fractions/services.py +++ /dev/null @@ -1,41 +0,0 @@ -from sqlalchemy.orm import Session - -from . import dto, models - -""" -Service layer for FractionRuleOctave. -""" - - -class FractionRuleOctaveService: - @staticmethod - def get_fraction_by_permission_line( - db: Session, permission: str, line: int, fraction: str - ): - return ( - db.query(models.FractionRuleOctave) - .filter( - models.FractionRuleOctave.permission == permission, - models.FractionRuleOctave.line == line, - models.FractionRuleOctave.fraction == fraction, - ) - .first() - ) - - @staticmethod - def create_frac(db: Session, frac_data: dto.FractionRuleOctaveCreateDTO): - new_frac = models.FractionRuleOctave(**frac_data.model_dump()) - db.add(new_frac) - db.commit() - db.refresh(new_frac) - return new_frac - - @staticmethod - def delete_fraction(db: Session, permission: str, line: int, fraction: str): - frac = FractionRuleOctaveService.get_fraction_by_permission_line( - db, permission, line, fraction - ) - if frac: - db.delete(frac) - db.commit() - return frac diff --git a/backend/api/v1/modules/a76/rule_octave/fractions/test_fraction_rule_octave.py b/backend/api/v1/modules/a76/rule_octave/fractions/test_fraction_rule_octave.py deleted file mode 100644 index 5ec8416e..00000000 --- a/backend/api/v1/modules/a76/rule_octave/fractions/test_fraction_rule_octave.py +++ /dev/null @@ -1,36 +0,0 @@ -import pytest -from fastapi import FastAPI -from fastapi.testclient import TestClient - -from .routes import router - -app = FastAPI() -app.include_router(router) -client = TestClient(app) - - -@pytest.mark.usefixtures("client", "access_token") -def test_list_fraction_rules(client, access_token): - headers = {"Authorization": f"Bearer {access_token}"} - response = client.get("/fraction_rule_octave/", headers=headers) - assert response.status_code == 200 - assert "items" in response.json() - assert "page" in response.json() - assert "page_size" in response.json() - - -@pytest.mark.usefixtures("client", "access_token") -def test_get_fraction_rule_not_found(client, access_token): - headers = {"Authorization": f"Bearer {access_token}"} - response = client.get("/fraction_rule_octave/invalid_id", headers=headers) - assert response.status_code == 404 - - -def test_create_fraction_rule_forbidden(): - response = client.post("/fraction_rule_octave/", json={"rule": "Test Rule"}) - assert response.status_code in (403, 405, 404) - - -def test_update_fraction_rule_forbidden(): - response = client.put("/fraction_rule_octave/1", json={"rule": "Updated Rule"}) - assert response.status_code in (403, 405, 404) diff --git a/backend/api/v1/modules/a76/rule_octave/permissions/models.py b/backend/api/v1/modules/a76/rule_octave/permissions/models.py deleted file mode 100644 index f2ffefc2..00000000 --- a/backend/api/v1/modules/a76/rule_octave/permissions/models.py +++ /dev/null @@ -1,32 +0,0 @@ -from typing import Optional - -from sqlalchemy import DateTime, Integer, String, UniqueConstraint -from sqlalchemy.orm import Mapped, mapped_column - -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base - -class OctavePermission(Base, TenantScopedMixin, TimestampMixin): - """ - Permisos para la Regla Octava. - Paridad: GPermisoReglaOct (Clarion SCAII). - """ - - __tablename__ = "permission_rule_octave" - __table_args__ = ( - UniqueConstraint( - "tenant_id", - "company_id", - "permission", - name="uq_permissions_rule_octave_permission", - ), - {"schema": "a76"}, - ) - - id: Mapped[int] = mapped_column(Integer, primary_key=True, autoincrement=True) - - permission: Mapped[str] = mapped_column(String(20), nullable=False) # PERMISO - start_date: Mapped[Optional[DateTime]] = mapped_column(DateTime()) # FECHAINICIO - end_date: Mapped[Optional[DateTime]] = mapped_column(DateTime()) # FECHAFINAL - sector: Mapped[Optional[str]] = mapped_column(String(8)) # SECTOR - system: Mapped[Optional[str]] = mapped_column(String(5)) # SISTEMA diff --git a/backend/api/v1/modules/a76/transportation/catalog_parity.py b/backend/api/v1/modules/a76/transportation/catalog_parity.py deleted file mode 100644 index 700977ec..00000000 --- a/backend/api/v1/modules/a76/transportation/catalog_parity.py +++ /dev/null @@ -1,380 +0,0 @@ -""" -Validación de paridad con import CSV (mismas reglas y fk_loader) para CRUD Transportes. -""" -from __future__ import annotations - -from typing import Any, Dict, List, Optional, Set, Tuple - -from sqlalchemy.orm import Session - -from api.v1.common.catalog_validation_errors import CatalogValidationError - -LINE = 1 - - -def _raise_if_errors(errors: List[Dict[str, Any]]) -> None: - if errors: - raise CatalogValidationError(errors) - - -# --- Trailers --- - - -def trailer_fields_to_csv_row(d: Dict[str, Any]) -> Dict[str, Any]: - return { - "NUMERO TRAILER": (d.get("trailer_number") or "").strip(), - "CLAVE ACE": (d.get("ace_trailer_number") or "").strip() if d.get("ace_trailer_number") is not None else "", - "TIPO TRAILER": (d.get("trailer_type_key") or "").strip() if d.get("trailer_type_key") is not None else "", - "PRECINTO": (d.get("seal") or "").strip() if d.get("seal") is not None else "", - "CODIGO DE ENTIDAD": (d.get("entity_code") or "").strip() if d.get("entity_code") is not None else "", - "PLACAS": (d.get("plate_number") or "").strip() if d.get("plate_number") is not None else "", - "ESTADO": (d.get("state") or "").strip() if d.get("state") is not None else "", - "PAIS": (d.get("country") or "").strip() if d.get("country") is not None else "", - "CLAVE CONTENEDOR": (d.get("container_key") or "").strip() if d.get("container_key") is not None else "", - } - - -def trailer_model_to_row(tr) -> Dict[str, Any]: - return trailer_fields_to_csv_row( - { - "trailer_number": tr.trailer_number, - "ace_trailer_number": tr.ace_trailer_number, - "trailer_type_key": tr.trailer_type_key, - "seal": tr.seal, - "entity_code": tr.entity_code, - "plate_number": tr.plate_number, - "state": tr.state, - "country": tr.country, - "container_key": tr.container_key, - } - ) - - -def validate_trailer_row_for_api( - tenant_id: int, - company_id: int, - row: Dict[str, Any], - *, - is_update: bool, - existing_trailer_numbers: Set[str], -) -> None: - from api.v1.modules.a76.layouts_csv.trailers.common.fk_loader import load_trailers_fk_sets - from api.v1.modules.a76.layouts_csv.trailers.validators.create import validate_row_trailer - - ( - valid_trailer_type_keys, - valid_country_ame, - state_descriptions_upper, - state_country_set, - state_ame_to_description, - ) = load_trailers_fk_sets(tenant_id, company_id) - - clave = (row.get("NUMERO TRAILER") or "").strip() - existing_norm = {x.strip() for x in existing_trailer_numbers if x} - actualizar = is_update and bool(clave and clave in existing_norm) - - errs = validate_row_trailer( - row, - LINE, - actualizar=actualizar, - existing_trailer_numbers=existing_norm, - valid_trailer_type_keys=valid_trailer_type_keys, - valid_country_ame=valid_country_ame, - state_descriptions_upper=state_descriptions_upper, - state_country_set=state_country_set, - state_ame_to_description=state_ame_to_description, - ) - _raise_if_errors(errs) - - -# --- Vehicles --- - - -def _fmt_insurance_date(val: Any) -> str: - if val is None: - return "" - if isinstance(val, int): - return str(val) - return str(val).strip() - - -def _fmt_monto(val: Any) -> str: - if val is None: - return "" - if isinstance(val, float): - return str(val) - return str(val).strip() - - -def vehicle_fields_to_csv_row(d: Dict[str, Any]) -> Dict[str, Any]: - return { - "CLAVE": (d.get("vehicle_key") or "").strip(), - "CLAVE ACE": (d.get("ace_vehicle_key") or "").strip() if d.get("ace_vehicle_key") is not None else "", - "CLAVE TRANSPORTE": (d.get("transporter_key") or "").strip() if d.get("transporter_key") is not None else "", - "VIN": (d.get("series") or "").strip() if d.get("series") is not None else "", - "TIPO TRANSPORTE": (d.get("transport_type") or "").strip() if d.get("transport_type") is not None else "", - "CODIGO DE ENTIDAD": (d.get("entity_code") or "").strip() if d.get("entity_code") is not None else "", - "TRANSPONDEDOR": (d.get("transponder_number") or "").strip() if d.get("transponder_number") is not None else "", - "NUMERO DOT": (d.get("dot_number") or "").strip() if d.get("dot_number") is not None else "", - "PLACAS": (d.get("plate_number") or "").strip() if d.get("plate_number") is not None else "", - "CIUDAD": (d.get("city") or "").strip() if d.get("city") is not None else "", - "ESTADO": (d.get("state") or "").strip() if d.get("state") is not None else "", - "PAIS": (d.get("country") or "").strip() if d.get("country") is not None else "", - "PRECINTO": (d.get("seal") or "").strip() if d.get("seal") is not None else "", - "EMPRESA ASEGURADORA": (d.get("insurance_company_name") or "").strip() - if d.get("insurance_company_name") is not None - else "", - "NUM. ASEGURADORA": (d.get("insurance_number") or "").strip() if d.get("insurance_number") is not None else "", - "MONTO ASEGURADO": _fmt_monto(d.get("insurance_amount")), - "FECHA DE ASEGURADORA": _fmt_insurance_date(d.get("insurance_date")), - } - - -def vehicle_model_to_row(v) -> Dict[str, Any]: - return vehicle_fields_to_csv_row( - { - "vehicle_key": v.vehicle_key, - "ace_vehicle_key": v.ace_vehicle_key, - "transporter_key": v.transporter_key, - "series": v.series, - "transport_type": v.transport_type, - "entity_code": v.entity_code, - "transponder_number": v.transponder_number, - "dot_number": v.dot_number, - "plate_number": v.plate_number, - "city": v.city, - "state": v.state, - "country": v.country, - "seal": v.seal, - "insurance_company_name": v.insurance_company_name, - "insurance_number": v.insurance_number, - "insurance_amount": float(v.insurance_amount) if v.insurance_amount is not None else None, - "insurance_date": v.insurance_date, - } - ) - - -def validate_vehicle_transporter_key( - db: Session, - tenant_id: int, - company_id: int, - transporter_key: Optional[str], -) -> None: - if transporter_key is None or not str(transporter_key).strip(): - return - from api.v1.modules.a76.transportation.transporters.services import TransporterService - - t = TransporterService.get_by_id_ignore_case( - db, str(transporter_key).strip(), tenant_id, company_id - ) - if not t: - _raise_if_errors( - [ - { - "line": LINE, - "col": "CLAVE TRANSPORTE", - "msg": f"El transportista '{transporter_key}' no existe en el catálogo de esta empresa.", - } - ] - ) - - -def validate_vehicle_row_for_api( - db: Session, - tenant_id: int, - company_id: int, - row: Dict[str, Any], - *, - is_update: bool, - existing_vehicle_keys: Set[str], -) -> None: - from api.v1.modules.a76.layouts_csv.vehicles.common.fk_loader import load_vehicles_fk_sets - from api.v1.modules.a76.layouts_csv.vehicles.validators.create import validate_row_vehicle - - tk = (row.get("CLAVE TRANSPORTE") or "").strip() - if tk: - validate_vehicle_transporter_key(db, tenant_id, company_id, tk) - - ( - valid_transport_codes, - valid_country_ame, - state_descriptions_upper, - state_country_set, - ) = load_vehicles_fk_sets(tenant_id, company_id) - - clave = (row.get("CLAVE") or "").strip() - existing_norm = {x.strip() for x in existing_vehicle_keys if x} - actualizar = is_update and bool(clave and clave in existing_norm) - - errs = validate_row_vehicle( - row, - LINE, - actualizar=actualizar, - existing_vehicle_keys=existing_norm, - valid_transport_codes=valid_transport_codes, - valid_country_ame=valid_country_ame, - state_descriptions_upper=state_descriptions_upper, - state_country_set=state_country_set, - ) - _raise_if_errors(errs) - - -# --- Transporters --- - - -def transporter_fields_to_csv_row(d: Dict[str, Any]) -> Dict[str, Any]: - def s(k: str) -> str: - v = d.get(k) - if v is None: - return "" - return str(v).strip() - - return { - "CLAVE TRANSPORTISTA": s("transporter_key"), - "NOMBRE": s("name"), - "NOMBRE CORTO": s("short_name"), - "RESPONSABLE": s("responsible"), - "RFC": s("rfc"), - "CALLES": s("streets"), - "CODIGO POSTAL": s("postal_code"), - "CIUDAD": s("city"), - "ESTADO": s("state"), - "PAIS": s("country"), - "CODIGO CARGADOR": s("loader_code"), - "CODIGO CAAT": s("caat_code"), - "CODIGO TRANS": s("transport_code"), - "TIPO INTERFASE TRANS": s("transport_interface_type"), - "SERVIDOR FTP": s("ftp_server"), - "USUARIO FTP": s("ftp_user"), - "CLAVE ACCESO FTP": s("ftp_password"), - "DIRECTORIO FTP": s("ftp_directory"), - } - - -def transporter_model_to_row(t) -> Dict[str, Any]: - return transporter_fields_to_csv_row( - { - "transporter_key": t.transporter_key, - "name": t.name, - "short_name": t.short_name, - "responsible": t.responsible, - "rfc": t.rfc, - "streets": t.streets, - "postal_code": t.postal_code, - "city": t.city, - "state": t.state, - "country": t.country, - "loader_code": t.loader_code, - "caat_code": t.caat_code, - "transport_code": t.transport_code, - "transport_interface_type": t.transport_interface_type, - "ftp_server": t.ftp_server, - "ftp_user": t.ftp_user, - "ftp_password": t.ftp_password, - "ftp_directory": t.ftp_directory, - } - ) - - -def validate_transporter_row_for_api( - db: Session, - tenant_id: int, - company_id: int, - row: Dict[str, Any], - *, - is_update: bool, - existing_transporter_keys: Set[str], -) -> None: - from api.v1.modules.a76.layouts_csv.transportistas.common.fk_loader import load_transportistas_fk_sets - from api.v1.modules.a76.layouts_csv.transportistas.validators.create import validate_row_transporter - - ( - existing_keys_loaded, - valid_country_ame, - state_descriptions_upper, - state_country_set, - ) = load_transportistas_fk_sets(tenant_id, company_id, db=db) - - clave = (row.get("CLAVE TRANSPORTISTA") or "").strip().upper() - # existing set from loader is uppercased keys for this company - existing_norm = existing_keys_loaded | {x.strip().upper() for x in existing_transporter_keys if x} - actualizar = is_update and bool(clave and clave in existing_norm) - - errs = validate_row_transporter( - row, - LINE, - actualizar=actualizar, - existing_transporter_keys=existing_norm, - valid_country_ame=valid_country_ame, - state_descriptions_upper=state_descriptions_upper, - state_country_set=state_country_set, - ) - _raise_if_errors(errs) - - -# --- Drivers --- - - -def driver_fields_to_csv_row(d: Dict[str, Any]) -> Dict[str, Any]: - def s(k: str) -> str: - v = d.get(k) - if v is None: - return "" - return str(v).strip() - - line = d.get("line") - line_s = str(line) if line is not None else "" - - return { - "TRANSPORTISTA": s("transporter_key"), - "LINEA": line_s, - "CLAVE CONDUCTOR": s("driver_name"), - "LICENCIA": s("license_number"), - "PERMISO LINEA EXPRESS": s("express_line_id"), - "IDENTIFICACION ACE": s("ace_id"), - "FECHA NACIMIENTO": str(d.get("birth_date")) if d.get("birth_date") is not None else "", - "SEXO": s("gender"), - "PAIS NACIMIENTO": s("birth_country"), - "TRANSPORTA MAT. PELIGROSO?": s("hazardous_material_auth"), - "PERMISO MAT. PELIGROSO": s("hazardous_material_state"), - "NOMBRE(S)": s("first_name"), - "APELLIDO PATERNO": s("last_name"), - "FORMA IDENTIFICACION 1": s("id_key1"), - "NUM. IDENTIFICACION 1": s("id_number1"), - "ESTADO": s("id_state1"), - "PAIS": s("id_country1"), - "FORMA IDENTIFICACION 2": s("id_key2"), - "NUM. IDENTIFICACION 2": s("id_number2"), - "ESTADO 2": s("id_state2"), - "PAIS 2": s("id_country2"), - } - - -def validate_driver_row_for_api( - tenant_id: int, - company_id: int, - row: Dict[str, Any], - *, - is_update: bool, - existing_driver_keys: Set[Tuple[str, int]], -) -> None: - from api.v1.modules.a76.layouts_csv.drivers.common.fk_loader import load_drivers_fk_sets - from api.v1.modules.a76.layouts_csv.drivers.validators.create import validate_row_driver - - valid_transporter_keys, valid_country_ame, _ = load_drivers_fk_sets(tenant_id, company_id) - - transporter_key = (row.get("TRANSPORTISTA") or "").strip().upper() - from api.v1.modules.a76.layouts_csv.drivers.common.common_validators import parse_int - - line = parse_int(row.get("LINEA")) - actualizar = is_update and bool(transporter_key and line is not None) and (transporter_key, line) in existing_driver_keys - - errs = validate_row_driver( - row, - LINE, - actualizar=actualizar, - existing_driver_keys=existing_driver_keys, - valid_transporter_keys=valid_transporter_keys, - valid_country_ame=valid_country_ame, - ) - _raise_if_errors(errs) diff --git a/backend/api/v1/modules/a76/transportation/drivers/dto.py b/backend/api/v1/modules/a76/transportation/drivers/dto.py deleted file mode 100644 index 5366d209..00000000 --- a/backend/api/v1/modules/a76/transportation/drivers/dto.py +++ /dev/null @@ -1,72 +0,0 @@ -from typing import Optional - -from pydantic import BaseModel, Field - - -TRANSPORTER_KEY_MAX_LENGTH = 30 - - -class DriverBaseDTO(BaseModel): - transporter_key: str = Field(..., max_length=TRANSPORTER_KEY_MAX_LENGTH) - driver_id: Optional[int] = None - line: int - driver_name: Optional[str] = None - license_number: Optional[str] = None - express_line_id: Optional[str] = None - ace_id: Optional[str] = None - birth_date: Optional[int] = None - gender: Optional[str] = None - birth_country: Optional[str] = None - hazardous_material_auth: Optional[str] = None - hazardous_material_state: Optional[str] = None - first_name: Optional[str] = None - last_name: Optional[str] = None - id_key1: Optional[str] = None - id_number1: Optional[str] = None - id_state1: Optional[str] = None - id_country1: Optional[str] = None - id_key2: Optional[str] = None - id_number2: Optional[str] = None - id_state2: Optional[str] = None - id_country2: Optional[str] = None - badge_number: Optional[str] = None - class_type: Optional[str] = None - unique_badge_number: Optional[str] = None - company_id: int - tenant_id: int - - -class DriverCreateDTO(DriverBaseDTO): - pass - - -class DriverUpdateDTO(BaseModel): - """All fields optional; transporter_key, line, company_id, tenant_id are not updated.""" - - driver_name: Optional[str] = None - license_number: Optional[str] = None - express_line_id: Optional[str] = None - ace_id: Optional[str] = None - birth_date: Optional[int] = None - gender: Optional[str] = None - birth_country: Optional[str] = None - hazardous_material_auth: Optional[str] = None - hazardous_material_state: Optional[str] = None - first_name: Optional[str] = None - last_name: Optional[str] = None - id_key1: Optional[str] = None - id_number1: Optional[str] = None - id_state1: Optional[str] = None - id_country1: Optional[str] = None - id_key2: Optional[str] = None - id_number2: Optional[str] = None - id_state2: Optional[str] = None - id_country2: Optional[str] = None - badge_number: Optional[str] = None - class_type: Optional[str] = None - unique_badge_number: Optional[str] = None - - -class DriverResponseDTO(DriverBaseDTO): - class Config: - from_attributes = True diff --git a/backend/api/v1/modules/a76/transportation/drivers/models.py b/backend/api/v1/modules/a76/transportation/drivers/models.py deleted file mode 100644 index 57aed494..00000000 --- a/backend/api/v1/modules/a76/transportation/drivers/models.py +++ /dev/null @@ -1,42 +0,0 @@ -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base -from sqlalchemy import BigInteger, Column, ForeignKey, ForeignKeyConstraint, Integer, String - - -class Driver(Base, TenantScopedMixin, TimestampMixin): - __tablename__ = "driver" - __table_args__ = ( - {"schema": "a76"}, - ) - - transporter_key = Column( - String(30), - ForeignKey("a76.transporter.transporter_key", ondelete="CASCADE"), - primary_key=True, - nullable=False, - ) - line = Column(Integer, primary_key=True, nullable=False) - # Internal integer surrogate ID. The frontend continues to use transporter_key/line. - driver_id = Column(BigInteger, nullable=False, unique=True, index=True) - driver_name = Column(String(80), nullable=True) - license_number = Column(String(29), nullable=True) - express_line_id = Column(String(17), nullable=True) - ace_id = Column(String(20), nullable=True) - birth_date = Column(Integer, nullable=True) - gender = Column(String(1), nullable=True) - birth_country = Column(String(3), nullable=True) - hazardous_material_auth = Column(String(2), nullable=True) - hazardous_material_state = Column(String(30), nullable=True) - first_name = Column(String(20), nullable=True) - last_name = Column(String(20), nullable=True) - id_key1 = Column(String(40), nullable=True) - id_number1 = Column(String(20), nullable=True) - id_state1 = Column(String(30), nullable=True) - id_country1 = Column(String(3), nullable=True) - id_key2 = Column(String(40), nullable=True) - id_number2 = Column(String(20), nullable=True) - id_state2 = Column(String(30), nullable=True) - id_country2 = Column(String(3), nullable=True) - badge_number = Column(String(20), nullable=True) - class_type = Column(String(1), nullable=True) - unique_badge_number = Column(String(100), nullable=True) diff --git a/backend/api/v1/modules/a76/transportation/drivers/routes.py b/backend/api/v1/modules/a76/transportation/drivers/routes.py deleted file mode 100644 index 4382a4d0..00000000 --- a/backend/api/v1/modules/a76/transportation/drivers/routes.py +++ /dev/null @@ -1,160 +0,0 @@ -from typing import Any, Dict, List - -from core.database import get_core_db -from core.security import get_current_user, validate_access_to_resource -from fastapi import APIRouter, Depends, HTTPException, Query, status - -from api.v1.common.catalog_validation_errors import CatalogValidationError -from sqlalchemy.orm import Session - -from .dto import DriverCreateDTO, DriverResponseDTO, DriverUpdateDTO -from .models import Driver -from .services import DriverService -from api.v1.modules.a76.transportation.transporters.services import TransporterService -from api.v1.modules.a76.transportation.transporters.models import Transporter -from api.v1.modules.a76.layouts_csv.drivers.routes import router as imports_router -from sqlalchemy import func - -import logging -logger = logging.getLogger(__name__) - -router = APIRouter(prefix="/drivers") - -# CSV import (upload -> scan -> status -> commit) -router.include_router(imports_router, prefix="/imports", tags=["a76 / drivers / csv_import"]) - - -@router.get("/", response_model=Dict[str, Any]) -async def list_drivers( - company_id: int = Query(..., description="Company ID for filtering"), - page: int = Query(1, ge=1, description="Page number"), - page_size: int = Query(50, ge=1, le=100, description="Page size"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - tenant_id = validate_access_to_resource(db, company_id, current_user, required_permissions=["drivers.view"]) - drivers = DriverService.list_drivers(db, str(company_id), tenant_id) - total = len(drivers) - - # Aplicar paginación manualmente - skip = (page - 1) * page_size - paginated_drivers = drivers[skip : skip + page_size] - - return { - "items": [DriverResponseDTO.model_validate(driver) for driver in paginated_drivers], - "total": total, - "page": page, - "page_size": page_size, - } - - -@router.get("/{transporter_key}/{line}", response_model=DriverResponseDTO) -async def read_driver( - transporter_key: str, - line: int, - company_id: int = Query(..., description="Company ID for filtering"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - tenant_id = validate_access_to_resource(db, company_id, current_user, required_permissions=["drivers.view"]) - driver = DriverService.get_driver_by_key_and_line( - db, transporter_key, line, str(company_id), tenant_id - ) - if not driver: - raise HTTPException(status_code=404, detail="Driver not found") - return driver - - -@router.post("/", response_model=DriverResponseDTO, status_code=status.HTTP_201_CREATED) -async def create_driver( - driver_data: DriverCreateDTO, - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - # Validar acceso a la empresa del cuerpo - tenant_id = validate_access_to_resource( - db, driver_data.company_id, current_user, required_permissions=["drivers.create"] - ) - tk = (driver_data.transporter_key or "").strip() - # Buscar transportista: primero exacto, luego ignorando mayúsculas - transporter = TransporterService.get_by_id( - db, tk, tenant_id, driver_data.company_id - ) - if not transporter: - transporter = TransporterService.get_by_id_ignore_case( - db, tk, tenant_id, driver_data.company_id - ) - if not transporter: - # Diagnóstico: ¿existe ese transportista con otra empresa/tenant? - any_with_key = ( - db.query(Transporter) - .filter(func.upper(Transporter.transporter_key) == tk.upper()) - .limit(1) - .first() - ) - logger.warning( - "Driver create: transporter not found. key=%r tenant_id=%s company_id=%s; " - "any_transporter_with_key=%s (other_tenant=%s other_company=%s)", - tk, tenant_id, driver_data.company_id, - getattr(any_with_key, "transporter_key", None) if any_with_key else None, - getattr(any_with_key, "tenant_id", None) if any_with_key else None, - getattr(any_with_key, "company_id", None) if any_with_key else None, - ) - raise HTTPException( - status_code=status.HTTP_400_BAD_REQUEST, - detail=f"El transportista {tk} no existe en el catálogo de esta empresa. Crea primero el transportista o elige uno existente.", - ) - # Usar la clave tal como está en BD (mismo caso) - driver_data.transporter_key = transporter.transporter_key - try: - return DriverService.create_driver(db, driver_data) - except CatalogValidationError as e: - raise HTTPException( - status_code=422, - detail={"message": str(e), "errors": e.errors}, - ) - - -@router.put("/{transporter_key}/{line}", response_model=DriverResponseDTO) -async def update_driver( - transporter_key: str, - line: int, - driver_data: DriverUpdateDTO, - company_id: int = Query(..., description="Company ID for filtering"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - tenant_id = validate_access_to_resource(db, company_id, current_user, required_permissions=["drivers.edit"]) - try: - driver = DriverService.update_driver( - db, - transporter_key, - line, - str(company_id), - tenant_id, - driver_data, - ) - except CatalogValidationError as e: - raise HTTPException( - status_code=422, - detail={"message": str(e), "errors": e.errors}, - ) - if not driver: - raise HTTPException(status_code=404, detail="Driver not found") - return driver - - -@router.delete("/{transporter_key}/{line}", status_code=status.HTTP_204_NO_CONTENT) -async def delete_driver( - transporter_key: str, - line: int, - company_id: int = Query(..., description="Company ID for filtering"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - tenant_id = validate_access_to_resource(db, company_id, current_user, required_permissions=["drivers.delete"]) - driver = DriverService.delete_driver( - db, transporter_key, line, str(company_id), tenant_id - ) - if not driver: - raise HTTPException(status_code=404, detail="Driver not found") diff --git a/backend/api/v1/modules/a76/transportation/drivers/services.py b/backend/api/v1/modules/a76/transportation/drivers/services.py deleted file mode 100644 index 76cc7f55..00000000 --- a/backend/api/v1/modules/a76/transportation/drivers/services.py +++ /dev/null @@ -1,136 +0,0 @@ -from typing import List, Optional - -from sqlalchemy import text -from sqlalchemy.orm import Session - -from . import dto, models -from api.v1.modules.a76.transportation.catalog_parity import ( - driver_fields_to_csv_row, - validate_driver_row_for_api, -) - -DRIVER_ID_SEQ = "a76.driver_driver_id_seq" - - -def allocate_driver_id(db: Session) -> int: - """Next surrogate driver_id (sequence from migration ca7d3c4e8b2a).""" - return db.execute(text(f"SELECT nextval('{DRIVER_ID_SEQ}')")).scalar() - - -class DriverService: - @staticmethod - def list_drivers( - db: Session, company_id: str, tenant_id: Optional[str] = None - ) -> List[models.Driver]: - query = db.query(models.Driver).filter(models.Driver.company_id == company_id) - if tenant_id: - query = query.filter(models.Driver.tenant_id == tenant_id) - return query.all() - - @staticmethod - def get_driver_by_key_and_line( - db: Session, - transporter_key: str, - line: int, - company_id: str, - tenant_id: Optional[str] = None, - ) -> Optional[models.Driver]: - query = db.query(models.Driver).filter( - models.Driver.transporter_key == transporter_key, - models.Driver.line == line, - models.Driver.company_id == company_id, - ) - if tenant_id: - query = query.filter(models.Driver.tenant_id == tenant_id) - return query.first() - - @staticmethod - def create_driver(db: Session, driver_data: dto.DriverCreateDTO): - data = driver_data.model_dump() - validate_driver_row_for_api( - int(driver_data.tenant_id), - int(driver_data.company_id), - driver_fields_to_csv_row(data), - is_update=False, - existing_driver_keys=set(), - ) - if data.get("driver_id") is None: - data["driver_id"] = allocate_driver_id(db) - new_driver = models.Driver(**data) - db.add(new_driver) - db.commit() - db.refresh(new_driver) - return new_driver - - @staticmethod - def update_driver( - db: Session, - transporter_key: str, - line: int, - company_id: str, - tenant_id: Optional[str], - data: dto.DriverUpdateDTO, - ) -> Optional[models.Driver]: - driver = DriverService.get_driver_by_key_and_line( - db, transporter_key, line, company_id, tenant_id - ) - if not driver: - return None - update_data = data.model_dump(exclude_unset=True) - merged = { - "transporter_key": driver.transporter_key, - "line": driver.line, - "driver_name": driver.driver_name, - "license_number": driver.license_number, - "express_line_id": driver.express_line_id, - "ace_id": driver.ace_id, - "birth_date": driver.birth_date, - "gender": driver.gender, - "birth_country": driver.birth_country, - "hazardous_material_auth": driver.hazardous_material_auth, - "hazardous_material_state": driver.hazardous_material_state, - "first_name": driver.first_name, - "last_name": driver.last_name, - "id_key1": driver.id_key1, - "id_number1": driver.id_number1, - "id_state1": driver.id_state1, - "id_country1": driver.id_country1, - "id_key2": driver.id_key2, - "id_number2": driver.id_number2, - "id_state2": driver.id_state2, - "id_country2": driver.id_country2, - "badge_number": driver.badge_number, - "class_type": driver.class_type, - "unique_badge_number": driver.unique_badge_number, - } - merged.update(update_data) - tid = int(driver.tenant_id) - cid = int(driver.company_id) - validate_driver_row_for_api( - tid, - cid, - driver_fields_to_csv_row(merged), - is_update=True, - existing_driver_keys={(driver.transporter_key.strip().upper(), driver.line)}, - ) - for key, value in update_data.items(): - setattr(driver, key, value) - db.commit() - db.refresh(driver) - return driver - - @staticmethod - def delete_driver( - db: Session, - transporter_key: str, - line: int, - company_id: str, - tenant_id: Optional[str] = None, - ) -> Optional[models.Driver]: - driver = DriverService.get_driver_by_key_and_line( - db, transporter_key, line, company_id, tenant_id - ) - if driver: - db.delete(driver) - db.commit() - return driver diff --git a/backend/api/v1/modules/a76/transportation/trailers/dto.py b/backend/api/v1/modules/a76/transportation/trailers/dto.py deleted file mode 100644 index 5eaaafab..00000000 --- a/backend/api/v1/modules/a76/transportation/trailers/dto.py +++ /dev/null @@ -1,73 +0,0 @@ -from typing import Optional - -from pydantic import BaseModel, Field, field_validator - - -class TrailerBaseDTO(BaseModel): - trailer_number: str = Field(..., description="Trailer number (primary identifier)") - trailer_id: Optional[int] = Field(None, description="Internal trailer integer ID") - ace_trailer_number: Optional[str] = None - trailer_type_key: Optional[str] = None - seal: Optional[str] = None - entity_code: Optional[str] = None - plate_number: Optional[str] = None - state: Optional[str] = None - country: Optional[str] = None - container_key: Optional[str] = None - - @field_validator("trailer_number", mode="before") - @classmethod - def strip_trailer_number(cls, v: object) -> object: - if isinstance(v, str): - return v.strip() - return v - - @field_validator("trailer_type_key", mode="before") - @classmethod - def trailer_type_key_optional_fk(cls, v: object) -> Optional[str]: - """Empty string from JSON must become NULL for FK; normalize case for catalog match.""" - if v is None: - return None - if not isinstance(v, str): - return None - s = v.strip().upper() - return None if s == "" else s - - @field_validator( - "ace_trailer_number", - "seal", - "entity_code", - "plate_number", - "state", - "country", - "container_key", - mode="before", - ) - @classmethod - def empty_optional_str_to_none(cls, v: object) -> Optional[str]: - """JSON often sends ''; nullable columns should get NULL, not ''.""" - if v is None: - return None - if not isinstance(v, str): - return None - s = v.strip() - return None if s == "" else s - - -class TrailerCreateDTO(TrailerBaseDTO): - """Schema for creating a trailer""" - pass - - -class TrailerUpdateDTO(TrailerBaseDTO): - """Schema for updating a trailer""" - trailer_number: Optional[str] = Field(None, description="Trailer number (cannot be modified)") - - -class TrailerResponseDTO(TrailerBaseDTO): - """Schema for trailer response""" - company_id: int - tenant_id: int - - class Config: - from_attributes = True diff --git a/backend/api/v1/modules/a76/transportation/trailers/models.py b/backend/api/v1/modules/a76/transportation/trailers/models.py deleted file mode 100644 index 19e7b1c9..00000000 --- a/backend/api/v1/modules/a76/transportation/trailers/models.py +++ /dev/null @@ -1,24 +0,0 @@ -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base -from sqlalchemy import BigInteger, Column, ForeignKey, ForeignKeyConstraint, String - - -class Trailer(Base, TenantScopedMixin, TimestampMixin): - __tablename__ = "trailer" - __table_args__ = ( - {"schema": "a76"}, - ) - - trailer_number = Column(String(20), primary_key=True, nullable=False) - # Internal integer identifier (surrogate). The frontend continues to use trailer_number. - trailer_id = Column(BigInteger, nullable=False, unique=True, index=True) - ace_trailer_number = Column(String(10), nullable=True) - trailer_type_key = Column( - String(2), ForeignKey("public.trailer_type.trailer_type_key"), nullable=True - ) - seal = Column(String(15), nullable=True) - entity_code = Column(String(1), nullable=True) - plate_number = Column(String(17), nullable=True) - state = Column(String(30), nullable=True) - country = Column(String(3), nullable=True) - container_key = Column(String(3), nullable=True) diff --git a/backend/api/v1/modules/a76/transportation/trailers/routes.py b/backend/api/v1/modules/a76/transportation/trailers/routes.py deleted file mode 100644 index af7e37b1..00000000 --- a/backend/api/v1/modules/a76/transportation/trailers/routes.py +++ /dev/null @@ -1,36 +0,0 @@ -from fastapi import APIRouter - -from api.v1.common.tenant_crud_routes import TenantCRUDRoutes - -from .dto import TrailerCreateDTO, TrailerResponseDTO, TrailerUpdateDTO -from .services import TrailerService -from api.v1.modules.a76.layouts_csv.trailers.routes import router as imports_router - -# Main router: trailers CRUD + CSV imports -router = APIRouter() - -# CSV import (upload → scan → status → commit) -router.include_router(imports_router, prefix="/trailers/imports", tags=["a76 / trailers / csv_import"]) - -# CRUD routes -crud_router = TenantCRUDRoutes( - service=TrailerService, - create_schema=TrailerCreateDTO, - update_schema=TrailerUpdateDTO, - response_schema=TrailerResponseDTO, - prefix="/trailers", - tags=[], - resource_name="Trailer", - id_name="trailer_number", - id_type=str, - enable_list=True, - enable_filters=True, - default_page_size=50, - max_page_size=100, - list_permissions=["trailers.view"], - get_permissions=["trailers.view"], - create_permissions=["trailers.create"], - update_permissions=["trailers.edit"], - delete_permissions=["trailers.delete"], -).router -router.include_router(crud_router) diff --git a/backend/api/v1/modules/a76/transportation/trailers/services.py b/backend/api/v1/modules/a76/transportation/trailers/services.py deleted file mode 100644 index c009de94..00000000 --- a/backend/api/v1/modules/a76/transportation/trailers/services.py +++ /dev/null @@ -1,153 +0,0 @@ -from typing import Optional, Tuple, List, Dict, Any - -from sqlalchemy.orm import Session -from sqlalchemy import text - -from . import dto, models -from api.v1.modules.a76.transportation.catalog_parity import ( - trailer_fields_to_csv_row, - validate_trailer_row_for_api, -) - -TRAILER_ID_SEQ = "a76.trailer_trailer_id_seq" - - -def allocate_trailer_id(db: Session) -> int: - """Next surrogate trailer_id (sequence from migration ca7d3c4e8b2a).""" - return db.execute(text(f"SELECT nextval('{TRAILER_ID_SEQ}')")).scalar() - - -class TrailerService: - """Service for Trailer CRUD operations with tenant support""" - - @staticmethod - def get_all( - db: Session, - tenant_id: int, - company_id: int, - skip: int = 0, - limit: int = 50, - filters: Optional[Dict[str, Any]] = None, - ) -> Tuple[List[models.Trailer], int]: - """Get all trailers for a tenant/company with pagination""" - query = db.query(models.Trailer).filter( - models.Trailer.tenant_id == tenant_id, - models.Trailer.company_id == company_id, - ) - - # Apply filters if provided - if filters: - if filters.get("trailer_number"): - query = query.filter( - models.Trailer.trailer_number.ilike(f"%{filters['trailer_number']}%") - ) - if filters.get("plate_number"): - query = query.filter( - models.Trailer.plate_number.ilike(f"%{filters['plate_number']}%") - ) - if filters.get("trailer_type_key"): - query = query.filter( - models.Trailer.trailer_type_key == filters["trailer_type_key"] - ) - - total = query.count() - trailers = query.offset(skip).limit(limit).all() - - return trailers, total - - @staticmethod - def get_by_id( - db: Session, trailer_number: str, tenant_id: int, company_id: int - ) -> Optional[models.Trailer]: - """Get trailer by trailer_number""" - return ( - db.query(models.Trailer) - .filter( - models.Trailer.trailer_number == trailer_number, - models.Trailer.tenant_id == tenant_id, - models.Trailer.company_id == company_id, - ) - .first() - ) - - @staticmethod - def create( - db: Session, - trailer_data: dto.TrailerCreateDTO, - tenant_id: int, - company_id: int, - ) -> models.Trailer: - """Create a new trailer""" - data = trailer_data.model_dump() - validate_trailer_row_for_api( - tenant_id, - company_id, - trailer_fields_to_csv_row(data), - is_update=False, - existing_trailer_numbers=set(), - ) - if data.get("trailer_id") is None: - data["trailer_id"] = allocate_trailer_id(db) - new_trailer = models.Trailer( - **data, tenant_id=tenant_id, company_id=company_id - ) - db.add(new_trailer) - db.commit() - db.refresh(new_trailer) - return new_trailer - - @staticmethod - def update( - db: Session, - trailer_number: str, - tenant_id: int, - trailer_data: dto.TrailerUpdateDTO, - company_id: int, - ) -> Optional[models.Trailer]: - """Update a trailer""" - trailer = TrailerService.get_by_id(db, trailer_number, tenant_id, company_id) - if not trailer: - return None - - # Update fields (excluding trailer_number as it's the primary key) - update_data = trailer_data.model_dump( - exclude_unset=True, exclude={"trailer_number"} - ) - merged = { - "trailer_number": trailer.trailer_number, - "ace_trailer_number": trailer.ace_trailer_number, - "trailer_type_key": trailer.trailer_type_key, - "seal": trailer.seal, - "entity_code": trailer.entity_code, - "plate_number": trailer.plate_number, - "state": trailer.state, - "country": trailer.country, - "container_key": trailer.container_key, - } - merged.update(update_data) - validate_trailer_row_for_api( - tenant_id, - company_id, - trailer_fields_to_csv_row(merged), - is_update=True, - existing_trailer_numbers={trailer_number.strip()}, - ) - for field, value in update_data.items(): - setattr(trailer, field, value) - - db.commit() - db.refresh(trailer) - return trailer - - @staticmethod - def delete( - db: Session, trailer_number: str, tenant_id: int, company_id: int - ) -> bool: - """Delete a trailer""" - trailer = TrailerService.get_by_id(db, trailer_number, tenant_id, company_id) - if not trailer: - return False - - db.delete(trailer) - db.commit() - return True diff --git a/backend/api/v1/modules/a76/transportation/transporters/dto.py b/backend/api/v1/modules/a76/transportation/transporters/dto.py deleted file mode 100644 index 969fed6e..00000000 --- a/backend/api/v1/modules/a76/transportation/transporters/dto.py +++ /dev/null @@ -1,46 +0,0 @@ -from typing import Optional - -from pydantic import BaseModel, Field - - -class TransporterBaseDTO(BaseModel): - transporter_key: str = Field(..., description="Transporter key (primary identifier)") - transporter_id: Optional[int] = Field(None, description="Internal transporter integer ID") - name: Optional[str] = None - short_name: Optional[str] = None - responsible: Optional[str] = None - rfc: Optional[str] = None - streets: Optional[str] = None - postal_code: Optional[str] = None - city: Optional[str] = None - state: Optional[str] = None - country: Optional[str] = None - loader_code: Optional[str] = None - caat_code: Optional[str] = None - transport_code: Optional[str] = None - transport_interface_type: Optional[str] = None - ftp_server: Optional[str] = None - ftp_user: Optional[str] = None - ftp_password: Optional[str] = None - ftp_directory: Optional[str] = None - filler_code: Optional[str] = None - has_express_line: Optional[bool] = False - - -class TransporterCreateDTO(TransporterBaseDTO): - """Schema for creating a transporter""" - pass - - -class TransporterUpdateDTO(TransporterBaseDTO): - """Schema for updating a transporter""" - transporter_key: Optional[str] = Field(None, description="Transporter key (cannot be modified)") - - -class TransporterResponseDTO(TransporterBaseDTO): - """Schema for transporter response""" - company_id: int - tenant_id: int - - class Config: - from_attributes = True diff --git a/backend/api/v1/modules/a76/transportation/transporters/models.py b/backend/api/v1/modules/a76/transportation/transporters/models.py deleted file mode 100644 index ec50d643..00000000 --- a/backend/api/v1/modules/a76/transportation/transporters/models.py +++ /dev/null @@ -1,33 +0,0 @@ -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base -from sqlalchemy import BigInteger, Boolean, Column, ForeignKeyConstraint, String - - -class Transporter(Base, TenantScopedMixin, TimestampMixin): - __tablename__ = "transporter" - __table_args__ = ( - {"schema": "a76"}, - ) - - transporter_key = Column(String(23), primary_key=True, nullable=False) - # Internal integer identifier (surrogate). The frontend continues to use transporter_key. - transporter_id = Column(BigInteger, nullable=False, unique=True, index=True) - name = Column(String(256), nullable=True) - short_name = Column(String(10), nullable=True) - responsible = Column(String(100), nullable=True) - rfc = Column(String(30), nullable=True) - streets = Column(String(100), nullable=True) - postal_code = Column(String(15), nullable=True) - city = Column(String(30), nullable=True) - state = Column(String(30), nullable=True) - country = Column(String(3), nullable=True) - loader_code = Column(String(9), nullable=True) - caat_code = Column(String(49), nullable=True) - transport_code = Column(String(8), nullable=True) - transport_interface_type = Column(String(20), nullable=True) - ftp_server = Column(String(200), nullable=True) - ftp_user = Column(String(200), nullable=True) - ftp_password = Column(String(100), nullable=True) - ftp_directory = Column(String(1000), nullable=True) - filler_code = Column(String(20), nullable=True) - has_express_line = Column(Boolean, nullable=False, server_default="false") diff --git a/backend/api/v1/modules/a76/transportation/transporters/routes.py b/backend/api/v1/modules/a76/transportation/transporters/routes.py deleted file mode 100644 index 0b787d35..00000000 --- a/backend/api/v1/modules/a76/transportation/transporters/routes.py +++ /dev/null @@ -1,31 +0,0 @@ -from fastapi import APIRouter -from api.v1.common.tenant_crud_routes import TenantCRUDRoutes -from .dto import TransporterCreateDTO, TransporterResponseDTO, TransporterUpdateDTO -from .services import TransporterService -from api.v1.modules.a76.layouts_csv.transportistas.routes import router as imports_router - -router = APIRouter() -router.include_router(imports_router, prefix="/transporters/imports", tags=["a76 / transporters / csv_import"]) - -crud_router = TenantCRUDRoutes( - service=TransporterService, - create_schema=TransporterCreateDTO, - update_schema=TransporterUpdateDTO, - response_schema=TransporterResponseDTO, - prefix="/transporters", - tags=[], - resource_name="Transporter", - id_name="transporter_key", - id_type=str, - enable_list=True, - enable_filters=True, - default_page_size=50, - max_page_size=100, - list_permissions=["transporters.view"], - get_permissions=["transporters.view"], - create_permissions=["transporters.create"], - update_permissions=["transporters.edit"], - delete_permissions=["transporters.delete"], -).router - -router.include_router(crud_router) \ No newline at end of file diff --git a/backend/api/v1/modules/a76/transportation/transporters/services.py b/backend/api/v1/modules/a76/transportation/transporters/services.py deleted file mode 100644 index a5494e8f..00000000 --- a/backend/api/v1/modules/a76/transportation/transporters/services.py +++ /dev/null @@ -1,194 +0,0 @@ -from typing import Optional, Tuple, List, Dict, Any - -import logging -from sqlalchemy.orm import Session -from sqlalchemy import func, text - -from . import dto, models -from api.v1.modules.a76.transportation.catalog_parity import ( - transporter_fields_to_csv_row, - validate_transporter_row_for_api, -) - -logger = logging.getLogger(__name__) - -TRANSPORTER_ID_SEQ = "a76.transporter_transporter_id_seq" - - -def allocate_transporter_id(db: Session) -> int: - """Next surrogate transporter_id (sequence from migration ca7d3c4e8b2a).""" - return db.execute(text(f"SELECT nextval('{TRANSPORTER_ID_SEQ}')")).scalar() - - -class TransporterService: - """Service for Transporter CRUD operations with tenant support""" - - @staticmethod - def get_all( - db: Session, - tenant_id: int, - company_id: int, - skip: int = 0, - limit: int = 50, - filters: Optional[Dict[str, Any]] = None, - ) -> Tuple[List[models.Transporter], int]: - """Get all transporters for a tenant/company with pagination""" - query = db.query(models.Transporter).filter( - models.Transporter.tenant_id == tenant_id, - models.Transporter.company_id == company_id, - ) - - # Apply filters if provided - if filters: - if filters.get("transporter_key"): - query = query.filter( - models.Transporter.transporter_key.ilike(f"%{filters['transporter_key']}%") - ) - if filters.get("name"): - query = query.filter( - models.Transporter.name.ilike(f"%{filters['name']}%") - ) - if filters.get("rfc"): - query = query.filter( - models.Transporter.rfc.ilike(f"%{filters['rfc']}%") - ) - - total = query.count() - transporters = query.offset(skip).limit(limit).all() - - return transporters, total - - @staticmethod - def get_by_id( - db: Session, transporter_key: str, tenant_id: int, company_id: int - ) -> Optional[models.Transporter]: - """Get transporter by transporter_key (exact match after strip).""" - key = (transporter_key or "").strip() - if not key: - return None - return ( - db.query(models.Transporter) - .filter( - models.Transporter.transporter_key == key, - models.Transporter.tenant_id == tenant_id, - models.Transporter.company_id == company_id, - ) - .first() - ) - - @staticmethod - def get_by_id_ignore_case( - db: Session, transporter_key: str, tenant_id: int, company_id: int - ) -> Optional[models.Transporter]: - """Get transporter by transporter_key (case-insensitive) for given tenant/company.""" - key = (transporter_key or "").strip() - if not key: - return None - return ( - db.query(models.Transporter) - .filter( - func.upper(models.Transporter.transporter_key) == key.upper(), - models.Transporter.tenant_id == tenant_id, - models.Transporter.company_id == company_id, - ) - .first() - ) - - @staticmethod - def create( - db: Session, - transporter_data: dto.TransporterCreateDTO, - tenant_id: int, - company_id: int, - ) -> models.Transporter: - """Create a new transporter""" - data = transporter_data.model_dump() - validate_transporter_row_for_api( - db, - tenant_id, - company_id, - transporter_fields_to_csv_row(data), - is_update=False, - existing_transporter_keys=set(), - ) - if data.get("transporter_id") is None: - data["transporter_id"] = allocate_transporter_id(db) - new_transporter = models.Transporter( - **data, tenant_id=tenant_id, company_id=company_id - ) - db.add(new_transporter) - db.commit() - db.refresh(new_transporter) - return new_transporter - - @staticmethod - def update( - db: Session, - transporter_key: str, - tenant_id: int, - transporter_data: dto.TransporterUpdateDTO, - company_id: int, - ) -> Optional[models.Transporter]: - """Update a transporter""" - transporter = TransporterService.get_by_id( - db, transporter_key, tenant_id, company_id - ) - if not transporter: - return None - - # Update fields (excluding transporter_key as it's the primary key) - update_data = transporter_data.model_dump( - exclude_unset=True, exclude={"transporter_key"} - ) - merged = { - "transporter_key": transporter.transporter_key, - "name": transporter.name, - "short_name": transporter.short_name, - "responsible": transporter.responsible, - "rfc": transporter.rfc, - "streets": transporter.streets, - "postal_code": transporter.postal_code, - "city": transporter.city, - "state": transporter.state, - "country": transporter.country, - "loader_code": transporter.loader_code, - "caat_code": transporter.caat_code, - "transport_code": transporter.transport_code, - "transport_interface_type": transporter.transport_interface_type, - "ftp_server": transporter.ftp_server, - "ftp_user": transporter.ftp_user, - "ftp_password": transporter.ftp_password, - "ftp_directory": transporter.ftp_directory, - "filler_code": transporter.filler_code, - "has_express_line": transporter.has_express_line, - } - merged.update(update_data) - validate_transporter_row_for_api( - db, - tenant_id, - company_id, - transporter_fields_to_csv_row(merged), - is_update=True, - existing_transporter_keys={transporter_key.strip().upper()}, - ) - for field, value in update_data.items(): - setattr(transporter, field, value) - - db.commit() - db.refresh(transporter) - return transporter - - @staticmethod - def delete( - db: Session, transporter_key: str, tenant_id: int, company_id: int - ) -> bool: - """Delete a transporter""" - transporter = TransporterService.get_by_id( - db, transporter_key, tenant_id, company_id - ) - if not transporter: - return False - - db.delete(transporter) - db.commit() - return True diff --git a/backend/api/v1/modules/a76/transportation/vehicles/dto.py b/backend/api/v1/modules/a76/transportation/vehicles/dto.py deleted file mode 100644 index c4cb6c8a..00000000 --- a/backend/api/v1/modules/a76/transportation/vehicles/dto.py +++ /dev/null @@ -1,52 +0,0 @@ -from typing import Optional - -from pydantic import BaseModel, Field - - -class VehicleBaseDTO(BaseModel): - vehicle_key: str = Field(..., description="Vehicle key (primary identifier)") - vehicle_id: Optional[int] = Field(None, description="Internal vehicle integer ID") - ace_vehicle_key: Optional[str] = None - transporter_key: Optional[str] = None - transport_identifier: Optional[str] = None - transport_type: Optional[str] = None - entity_code: Optional[str] = None - transponder_number: Optional[str] = None - dot_number: Optional[str] = None - plate_number: Optional[str] = None - city: Optional[str] = None - state: Optional[str] = None - country: Optional[str] = None - seal: Optional[str] = None - insurance_company_name: Optional[str] = None - insurance_number: Optional[str] = None - insurance_amount: Optional[float] = None - insurance_date: Optional[int] = None - box_number: Optional[str] = None - brand: Optional[str] = None - year: Optional[str] = None - series: Optional[str] = None - description: Optional[str] = None - engine_number: Optional[str] = None - sct_permission: Optional[str] = None - color: Optional[str] = None - container_key: Optional[str] = None - - -class VehicleCreateDTO(VehicleBaseDTO): - """Schema for creating a vehicle""" - pass - - -class VehicleUpdateDTO(VehicleBaseDTO): - """Schema for updating a vehicle""" - vehicle_key: Optional[str] = Field(None, description="Vehicle key (cannot be modified)") - - -class VehicleResponseDTO(VehicleBaseDTO): - """Schema for vehicle response""" - company_id: int - tenant_id: int - - class Config: - from_attributes = True diff --git a/backend/api/v1/modules/a76/transportation/vehicles/models.py b/backend/api/v1/modules/a76/transportation/vehicles/models.py deleted file mode 100644 index 0bbbb5d9..00000000 --- a/backend/api/v1/modules/a76/transportation/vehicles/models.py +++ /dev/null @@ -1,39 +0,0 @@ -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base -from sqlalchemy import DECIMAL, Column, Integer, BigInteger, String - - -class Vehicle(Base, TenantScopedMixin, TimestampMixin): - __tablename__ = "vehicle" - __table_args__ = ( - {"schema": "a76"}, - ) - - vehicle_key = Column(String(14), primary_key=True, nullable=False) - # Internal integer identifier (surrogate). The frontend continues to use vehicle_key. - vehicle_id = Column(BigInteger, nullable=False, unique=True, index=True) - ace_vehicle_key = Column(String(10), nullable=True) - transporter_key = Column(String(23), nullable=True) - transport_identifier = Column(String(30), nullable=True) - transport_type = Column(String(2), nullable=True) - entity_code = Column(String(1), nullable=True) - transponder_number = Column(String(16), nullable=True) - dot_number = Column(String(8), nullable=True) - plate_number = Column(String(17), nullable=True) - city = Column(String(30), nullable=True) - state = Column(String(30), nullable=True) - country = Column(String(3), nullable=True) - seal = Column(String(49), nullable=True) - insurance_company_name = Column(String(30), nullable=True) - insurance_number = Column(String(20), nullable=True) - insurance_amount = Column(DECIMAL(13, 2), nullable=True) - insurance_date = Column(Integer, nullable=True) - box_number = Column(String(300), nullable=True) - brand = Column(String(20), nullable=True) - year = Column(String(4), nullable=True) - series = Column(String(30), nullable=True) - description = Column(String(100), nullable=True) - engine_number = Column(String(50), nullable=True) - sct_permission = Column(String(40), nullable=True) - color = Column(String(20), nullable=True) - container_key = Column(String(3), nullable=True) diff --git a/backend/api/v1/modules/a76/transportation/vehicles/routes.py b/backend/api/v1/modules/a76/transportation/vehicles/routes.py deleted file mode 100644 index 48c1c897..00000000 --- a/backend/api/v1/modules/a76/transportation/vehicles/routes.py +++ /dev/null @@ -1,36 +0,0 @@ -from fastapi import APIRouter - -from api.v1.common.tenant_crud_routes import TenantCRUDRoutes - -from .dto import VehicleCreateDTO, VehicleResponseDTO, VehicleUpdateDTO -from .services import VehicleService -from api.v1.modules.a76.layouts_csv.vehicles.routes import router as imports_router - -# Main router: vehicles CRUD + CSV imports -router = APIRouter() - -# CSV import (upload → scan → status → commit) -router.include_router(imports_router, prefix="/vehicles/imports", tags=["a76 / vehicles / csv_import"]) - -# CRUD routes -crud_router = TenantCRUDRoutes( - service=VehicleService, - create_schema=VehicleCreateDTO, - update_schema=VehicleUpdateDTO, - response_schema=VehicleResponseDTO, - prefix="/vehicles", - tags=[], - resource_name="Vehicle", - id_name="vehicle_key", - id_type=str, - enable_list=True, - enable_filters=True, - default_page_size=50, - max_page_size=100, - list_permissions=["vehicles.view"], - get_permissions=["vehicles.view"], - create_permissions=["vehicles.create"], - update_permissions=["vehicles.edit"], - delete_permissions=["vehicles.delete"], -).router -router.include_router(crud_router) diff --git a/backend/api/v1/modules/a76/transportation/vehicles/services.py b/backend/api/v1/modules/a76/transportation/vehicles/services.py deleted file mode 100644 index 6b1f2cc1..00000000 --- a/backend/api/v1/modules/a76/transportation/vehicles/services.py +++ /dev/null @@ -1,172 +0,0 @@ -from typing import Optional, Tuple, List, Dict, Any - -from sqlalchemy.orm import Session -from sqlalchemy import text - -from . import dto, models -from api.v1.modules.a76.transportation.catalog_parity import ( - vehicle_fields_to_csv_row, - validate_vehicle_row_for_api, -) - -VEHICLE_ID_SEQ = "a76.vehicle_vehicle_id_seq" - - -def allocate_vehicle_id(db: Session) -> int: - """Next surrogate vehicle_id (sequence from migration ca7d3c4e8b2a).""" - return db.execute(text(f"SELECT nextval('{VEHICLE_ID_SEQ}')")).scalar() - - -class VehicleService: - """Service for Vehicle CRUD operations with tenant support""" - - @staticmethod - def get_all( - db: Session, - tenant_id: int, - company_id: int, - skip: int = 0, - limit: int = 50, - filters: Optional[Dict[str, Any]] = None, - ) -> Tuple[List[models.Vehicle], int]: - """Get all vehicles for a tenant/company with pagination""" - query = db.query(models.Vehicle).filter( - models.Vehicle.tenant_id == tenant_id, - models.Vehicle.company_id == company_id, - ) - - # Apply filters if provided - if filters: - if filters.get("vehicle_key"): - query = query.filter( - models.Vehicle.vehicle_key.ilike(f"%{filters['vehicle_key']}%") - ) - if filters.get("plate_number"): - query = query.filter( - models.Vehicle.plate_number.ilike(f"%{filters['plate_number']}%") - ) - if filters.get("transport_type"): - query = query.filter( - models.Vehicle.transport_type == filters["transport_type"] - ) - - total = query.count() - vehicles = query.offset(skip).limit(limit).all() - - return vehicles, total - - @staticmethod - def get_by_id( - db: Session, vehicle_key: str, tenant_id: int, company_id: int - ) -> Optional[models.Vehicle]: - """Get vehicle by vehicle_key""" - return ( - db.query(models.Vehicle) - .filter( - models.Vehicle.vehicle_key == vehicle_key, - models.Vehicle.tenant_id == tenant_id, - models.Vehicle.company_id == company_id, - ) - .first() - ) - - @staticmethod - def create( - db: Session, - vehicle_data: dto.VehicleCreateDTO, - tenant_id: int, - company_id: int, - ) -> models.Vehicle: - """Create a new vehicle""" - data = vehicle_data.model_dump() - validate_vehicle_row_for_api( - db, - tenant_id, - company_id, - vehicle_fields_to_csv_row(data), - is_update=False, - existing_vehicle_keys=set(), - ) - if data.get("vehicle_id") is None: - data["vehicle_id"] = allocate_vehicle_id(db) - new_vehicle = models.Vehicle( - **data, tenant_id=tenant_id, company_id=company_id - ) - db.add(new_vehicle) - db.commit() - db.refresh(new_vehicle) - return new_vehicle - - @staticmethod - def update( - db: Session, - vehicle_key: str, - tenant_id: int, - vehicle_data: dto.VehicleUpdateDTO, - company_id: int, - ) -> Optional[models.Vehicle]: - """Update a vehicle""" - vehicle = VehicleService.get_by_id(db, vehicle_key, tenant_id, company_id) - if not vehicle: - return None - - # Update fields (excluding vehicle_key as it's the primary key) - update_data = vehicle_data.model_dump(exclude_unset=True, exclude={"vehicle_key"}) - merged = { - "vehicle_key": vehicle.vehicle_key, - "ace_vehicle_key": vehicle.ace_vehicle_key, - "transporter_key": vehicle.transporter_key, - "transport_identifier": vehicle.transport_identifier, - "transport_type": vehicle.transport_type, - "entity_code": vehicle.entity_code, - "transponder_number": vehicle.transponder_number, - "dot_number": vehicle.dot_number, - "plate_number": vehicle.plate_number, - "city": vehicle.city, - "state": vehicle.state, - "country": vehicle.country, - "seal": vehicle.seal, - "insurance_company_name": vehicle.insurance_company_name, - "insurance_number": vehicle.insurance_number, - "insurance_amount": float(vehicle.insurance_amount) - if vehicle.insurance_amount is not None - else None, - "insurance_date": vehicle.insurance_date, - "box_number": vehicle.box_number, - "brand": vehicle.brand, - "year": vehicle.year, - "series": vehicle.series, - "description": vehicle.description, - "engine_number": vehicle.engine_number, - "sct_permission": vehicle.sct_permission, - "color": vehicle.color, - "container_key": vehicle.container_key, - } - merged.update(update_data) - validate_vehicle_row_for_api( - db, - tenant_id, - company_id, - vehicle_fields_to_csv_row(merged), - is_update=True, - existing_vehicle_keys={vehicle_key.strip()}, - ) - for field, value in update_data.items(): - setattr(vehicle, field, value) - - db.commit() - db.refresh(vehicle) - return vehicle - - @staticmethod - def delete( - db: Session, vehicle_key: str, tenant_id: int, company_id: int - ) -> bool: - """Delete a vehicle""" - vehicle = VehicleService.get_by_id(db, vehicle_key, tenant_id, company_id) - if not vehicle: - return False - - db.delete(vehicle) - db.commit() - return True diff --git a/backend/api/v1/modules/core/auth/routes.py b/backend/api/v1/modules/core/auth/routes.py index 059d03dc..f5663407 100644 --- a/backend/api/v1/modules/core/auth/routes.py +++ b/backend/api/v1/modules/core/auth/routes.py @@ -335,7 +335,7 @@ async def sso_exchange( ): """ Canjea un relay token de un solo uso (generado por el Hub) por KC tokens. - Llamado server-side desde la página /auth/sso del frontend de Anexo76. + Llamado server-side desde la página /auth/sso del frontend. Establece cookies HttpOnly con los tokens y devuelve el resultado. """ service = AuthService(db) @@ -361,3 +361,62 @@ async def sso_exchange( path="/", ) return tokens + + +# --------------------------------------------------------------------------- +# Dev-only local auth — solo disponible cuando DEV_LOCAL_AUTH=True +# --------------------------------------------------------------------------- +@router.post("/dev-login") +async def dev_login(): + """ + Genera un token local firmado con SECRET_KEY para desarrollo sin Keycloak/Hub. + Disponible únicamente cuando DEV_LOCAL_AUTH=True en el entorno. + """ + from datetime import datetime, timezone, timedelta + from jose import jwt as jose_jwt + from core.config import settings + + if not settings.DEV_LOCAL_AUTH: + raise HTTPException(status_code=404, detail="Not found") + + now = datetime.now(timezone.utc) + payload = { + "sub": "dev-local-user", + "email": settings.DEV_LOCAL_AUTH_EMAIL, + "preferred_username": settings.DEV_LOCAL_AUTH_EMAIL.split("@")[0], + "name": settings.DEV_LOCAL_AUTH_NAME, + "tenant_id": settings.DEV_LOCAL_AUTH_TENANT_ID, + "tenant_slug": "dev", + "company_id": settings.DEV_LOCAL_AUTH_COMPANY_ID, + "roles": ["super_admin"], + "permissions": [], + "allowed_systems": ["fixed_asset", "inventory"], + "dev_local": True, + "iat": now, + "exp": now + timedelta(hours=8), + } + token = jose_jwt.encode(payload, settings.SECRET_KEY, algorithm="HS256") + return {"access_token": token, "token_type": "bearer"} + + +@router.get("/my-companies") +async def get_my_companies( + current_user: dict = Depends(get_current_user), +): + """ + Retorna las compañías accesibles para el usuario actual. + STUB: implementa con tu modelo de compañías. + En dev-local retorna una compañía ficticia para que el dashboard funcione. + """ + from core.config import settings + + if settings.DEV_LOCAL_AUTH and current_user.get("dev_local"): + return [{ + "id": settings.DEV_LOCAL_AUTH_COMPANY_ID, + "name": "Empresa Dev Local", + "tenant_id": settings.DEV_LOCAL_AUTH_TENANT_ID, + "is_active": True, + }] + + # Implementa aquí la consulta real a tu tabla de compañías. + return [] diff --git a/backend/api/v1/modules/core/auth/service.py b/backend/api/v1/modules/core/auth/service.py index 6a1ea384..524db10b 100644 --- a/backend/api/v1/modules/core/auth/service.py +++ b/backend/api/v1/modules/core/auth/service.py @@ -188,17 +188,7 @@ class AuthService: force=True, ) - # AUDIT LOG: Login Success - try: - from api.v1.modules.a76.audit_log.services.service import AuditService - AuditService.log_login( - db=self.db, - username=login_data.username, - ip_address=ip_address, - user_agent=user_agent - ) - except Exception as e: - logger.error(f"Failed to audit login: {e}") + # AUDIT LOG: implementa tu servicio de auditoría aquí si lo necesitas. return TokenResponseDTO(**data) diff --git a/backend/api/v1/modules/core/dashboard/service.py b/backend/api/v1/modules/core/dashboard/service.py index f68015e1..5aa91c84 100644 --- a/backend/api/v1/modules/core/dashboard/service.py +++ b/backend/api/v1/modules/core/dashboard/service.py @@ -1,428 +1,43 @@ """ -Servicio para generar estadísticas y métricas del dashboard +Servicio del dashboard — STUB. +Implementa las métricas de tu proyecto aquí. """ -from typing import List, Dict, Optional -from datetime import datetime, timedelta from sqlalchemy.orm import Session -from sqlalchemy import func, and_, desc -from api.v1.modules.a76.invoices.models import ( - InvoiceHeader, - InvoiceComplianceMx, - OperationType, -) -from api.v1.modules.a76.items.models import LineItem -from api.v1.modules.a76.clients_and_providers.models import ( - ClientProvider, - ClientOrProviderEnum, -) -from api.v1.modules.a76.general_catalogs.company.models import Company -from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos - -from .dto import ( - DashboardStats, - KPIMetric, - ActivityItem, - ChartDataPoint, - OperationsOverview, - InventoryMetrics, -) +from .dto import DashboardStats, KPIMetric, OperationsOverview, InventoryMetrics class DashboardService: - """Servicio para generar estadísticas del dashboard""" + """Stub — reemplaza con las consultas de tu proyecto.""" def __init__(self, db: Session, tenant_id: int, company_id: int): self.db = db self.tenant_id = tenant_id self.company_id = company_id - def _calculate_trend(self, current: int, previous: int) -> tuple[float, str]: - """Calcula el cambio porcentual y la tendencia""" - if previous == 0: - return 0.0, "stable" if current == 0 else "up" - - change = ((current - previous) / previous) * 100 - - if abs(change) < 1: - trend = "stable" - elif change > 0: - trend = "up" - else: - trend = "down" - - return round(change, 2), trend - - def get_invoice_metrics(self) -> tuple[KPIMetric, List[ChartDataPoint]]: - """Obtiene métricas de facturas""" - # Total de facturas actuales - current_total = ( - self.db.query(func.count(InvoiceHeader.id)) - .filter( - InvoiceHeader.tenant_id == self.tenant_id, - InvoiceHeader.company_id == self.company_id, - ) - .scalar() - or 0 - ) - - # Total del mes anterior para comparación - last_month = (datetime.utcnow() - timedelta(days=30)).date() - previous_total = ( - self.db.query(func.count(InvoiceHeader.id)) - .filter( - InvoiceHeader.tenant_id == self.tenant_id, - InvoiceHeader.company_id == self.company_id, - InvoiceHeader.invoice_date < last_month, - ) - .scalar() - or 0 - ) - - percentage, trend = self._calculate_trend(current_total, previous_total) - - kpi = KPIMetric( - label="Total Facturas", - value=current_total, - previous_value=previous_total, - percentage_change=percentage, - trend=trend, - ) - - # Facturas por mes (últimos 6 meses) - six_months_ago = (datetime.utcnow() - timedelta(days=180)).date() - - monthly_data = ( - self.db.query( - func.date_trunc("month", InvoiceHeader.invoice_date).label("month"), - func.count(InvoiceHeader.id).label("count"), - ) - .filter( - InvoiceHeader.tenant_id == self.tenant_id, - InvoiceHeader.company_id == self.company_id, - InvoiceHeader.invoice_date >= six_months_ago, - ) - .group_by("month") - .order_by("month") - .all() - ) - - chart_data = [ - ChartDataPoint( - label=row.month.strftime("%b %Y") if row.month else "Unknown", - value=float(row.count), - ) - for row in monthly_data - ] - - return kpi, chart_data - - def get_client_provider_metrics( - self, - ) -> tuple[KPIMetric, KPIMetric, List[ChartDataPoint], List[ChartDataPoint]]: - """Obtiene métricas de clientes y proveedores""" - # Total clientes - total_clients = ( - self.db.query(func.count(ClientProvider.id)) - .filter( - ClientProvider.tenant_id == self.tenant_id, - ClientProvider.company_id == self.company_id, - ClientProvider.client_or_provider.in_( - [ClientOrProviderEnum.CLIENT, ClientOrProviderEnum.BOTH] - ), - ) - .scalar() - or 0 - ) - - # Total proveedores - total_providers = ( - self.db.query(func.count(ClientProvider.id)) - .filter( - ClientProvider.tenant_id == self.tenant_id, - ClientProvider.company_id == self.company_id, - ClientProvider.client_or_provider.in_( - [ClientOrProviderEnum.PROVIDER, ClientOrProviderEnum.BOTH] - ), - ) - .scalar() - or 0 - ) - - client_kpi = KPIMetric( - label="Total Clientes", value=total_clients, trend="stable" - ) - - provider_kpi = KPIMetric( - label="Total Proveedores", value=total_providers, trend="stable" - ) - - # Top 5 clientes por número de facturas - # Los clientes están en InvoiceComplianceMx.sold_to_id - top_clients = ( - self.db.query( - ClientProvider.name, func.count(InvoiceHeader.id).label("invoice_count") - ) - .join( - InvoiceComplianceMx, - InvoiceComplianceMx.sold_to_id == ClientProvider.id, - isouter=True, - ) - .join( - InvoiceHeader, - and_( - InvoiceHeader.id == InvoiceComplianceMx.invoice_id, - InvoiceHeader.tenant_id == self.tenant_id, - InvoiceHeader.company_id == self.company_id, - ), - isouter=True, - ) - .filter( - ClientProvider.tenant_id == self.tenant_id, - ClientProvider.company_id == self.company_id, - ClientProvider.client_or_provider.in_( - [ClientOrProviderEnum.CLIENT, ClientOrProviderEnum.BOTH] - ), - ) - .group_by(ClientProvider.id, ClientProvider.name) - .order_by(desc("invoice_count")) - .limit(5) - .all() - ) - - top_clients_data = [ - ChartDataPoint( - label=row.name or f"Cliente {i+1}", value=float(row.invoice_count or 0) - ) - for i, row in enumerate(top_clients) - ] - - # Top 5 proveedores - # Los proveedores están en InvoiceComplianceMx.provider_id - top_providers = ( - self.db.query( - ClientProvider.name, func.count(InvoiceHeader.id).label("invoice_count") - ) - .join( - InvoiceComplianceMx, - InvoiceComplianceMx.provider_id == ClientProvider.id, - isouter=True, - ) - .join( - InvoiceHeader, - and_( - InvoiceHeader.id == InvoiceComplianceMx.invoice_id, - InvoiceHeader.tenant_id == self.tenant_id, - InvoiceHeader.company_id == self.company_id, - ), - isouter=True, - ) - .filter( - ClientProvider.tenant_id == self.tenant_id, - ClientProvider.company_id == self.company_id, - ClientProvider.client_or_provider.in_( - [ClientOrProviderEnum.PROVIDER, ClientOrProviderEnum.BOTH] - ), - ) - .group_by(ClientProvider.id, ClientProvider.name) - .order_by(desc("invoice_count")) - .limit(5) - .all() - ) - - top_providers_data = [ - ChartDataPoint( - label=row.name or f"Proveedor {i+1}", - value=float(row.invoice_count or 0), - ) - for i, row in enumerate(top_providers) - ] - - return client_kpi, provider_kpi, top_clients_data, top_providers_data - - def get_items_metrics(self) -> KPIMetric: - """Obtiene métricas de items/productos""" - total_items = ( - self.db.query(func.count(LineItem.id)) - .filter( - LineItem.tenant_id == self.tenant_id, LineItem.company_id == self.company_id - ) - .scalar() - or 0 - ) - - return KPIMetric(label="Total Items", value=total_items, trend="stable") - - def get_operations_by_type(self) -> List[ChartDataPoint]: - """Obtiene distribución de operaciones por tipo""" - ops_data = ( - self.db.query( - InvoiceHeader.operation_type, - func.count(InvoiceHeader.id).label("count"), - ) - .filter( - InvoiceHeader.tenant_id == self.tenant_id, - InvoiceHeader.company_id == self.company_id, - ) - .group_by(InvoiceHeader.operation_type) - .all() - ) - - type_labels = { - OperationType.IMP: "Importación", - OperationType.EXP: "Exportación", - OperationType.SM_IN: "Entrada SM", - OperationType.SM_OUT: "Salida SM", - OperationType.CTM_SEND: "Envío CTM", - OperationType.CTM_RECEIVE: "Recibo CTM", - } - - return [ - ChartDataPoint( - label=type_labels.get(row.operation_type, str(row.operation_type)), - value=float(row.count), - ) - for row in ops_data - if row.operation_type - ] - - def get_recent_activity(self, limit: int = 10) -> List[ActivityItem]: - """Obtiene actividad reciente del sistema""" - activities = [] - - # Facturas recientes - recent_invoices = ( - self.db.query(InvoiceHeader) - .filter( - InvoiceHeader.tenant_id == self.tenant_id, - InvoiceHeader.company_id == self.company_id, - ) - .order_by(desc(InvoiceHeader.created_at)) - .limit(limit) - .all() - ) - - for invoice in recent_invoices: - activities.append( - ActivityItem( - id=invoice.id, - type="invoice", - title=f"Factura {invoice.invoice_number or invoice.id}", - description=f"Operación: {invoice.operation_type if invoice.operation_type else 'N/A'}", - timestamp=invoice.created_at, - status="active", - icon="FileText", - ) - ) - - # Ordenar por timestamp - activities.sort(key=lambda x: x.timestamp, reverse=True) - - return activities[:limit] - - def get_pedimentos_metrics(self) -> tuple[KPIMetric, List[ChartDataPoint]]: - """Obtiene métricas de pedimentos""" - # Total de pedimentos actuales - current_total = ( - self.db.query(func.count(Pedimentos.id)) - .filter( - Pedimentos.tenant_id == self.tenant_id, - Pedimentos.company_id == self.company_id, - ) - .scalar() - or 0 - ) - - # Total del mes anterior para comparación - last_month = datetime.utcnow() - timedelta(days=30) - previous_total = ( - self.db.query(func.count(Pedimentos.id)) - .filter( - Pedimentos.tenant_id == self.tenant_id, - Pedimentos.company_id == self.company_id, - Pedimentos.created_at < last_month, - ) - .scalar() - or 0 - ) - - percentage, trend = self._calculate_trend(current_total, previous_total) - - kpi = KPIMetric( - label="Total Pedimentos", - value=current_total, - previous_value=previous_total, - percentage_change=percentage, - trend=trend, - ) - - # Pedimentos por mes (últimos 6 meses) - six_months_ago = datetime.utcnow() - timedelta(days=180) - - monthly_data = ( - self.db.query( - func.date_trunc("month", Pedimentos.created_at).label("month"), - func.count(Pedimentos.id).label("count"), - ) - .filter( - Pedimentos.tenant_id == self.tenant_id, - Pedimentos.company_id == self.company_id, - Pedimentos.created_at >= six_months_ago, - ) - .group_by("month") - .order_by("month") - .all() - ) - - chart_data = [ - ChartDataPoint( - label=row.month.strftime("%b %Y") if row.month else "Unknown", - value=float(row.count), - ) - for row in monthly_data - ] - - return kpi, chart_data - - def get_complete_dashboard_stats(self) -> DashboardStats: - """Genera estadísticas completas para el dashboard""" - - # Obtener nombre de la compañía - company = ( - self.db.query(Company) - .filter(Company.id == self.company_id, Company.tenant_id == self.tenant_id) - .first() - ) - - # Obtener todas las métricas - invoice_kpi, invoices_chart = self.get_invoice_metrics() - client_kpi, provider_kpi, top_clients, top_providers = ( - self.get_client_provider_metrics() - ) - items_kpi = self.get_items_metrics() - operations_chart = self.get_operations_by_type() - recent_activity = self.get_recent_activity() - - # Obtener métricas de pedimentos - pedimento_kpi, pedimentos_chart = self.get_pedimentos_metrics() - - # KPI de aprobaciones pendientes - pending_kpi = KPIMetric(label="Pendientes", value=0, trend="stable") - + def get_stats(self) -> DashboardStats: + empty_kpi = KPIMetric(label="", value=0, trend="stable") return DashboardStats( - total_invoices=invoice_kpi, - total_pedimentos=pedimento_kpi, - total_clients=client_kpi, - total_providers=provider_kpi, - active_items=items_kpi, - pending_approvals=pending_kpi, - invoices_by_month=invoices_chart, - pedimentos_by_month=pedimentos_chart, - operations_by_type=operations_chart, - top_clients=top_clients, - top_providers=top_providers, - recent_activity=recent_activity, company_id=self.company_id, - company_name=company.name if company else None, + generated_at="", + total_invoices=empty_kpi, + total_pedimentos=empty_kpi, + total_clients=empty_kpi, + total_providers=empty_kpi, + active_items=empty_kpi, + pending_approvals=empty_kpi, + invoices_by_month=[], + operations_by_type=[], + top_clients=[], + top_providers=[], + recent_activity=[], + ) + + def get_operations_overview(self) -> OperationsOverview: + return OperationsOverview(total_operations=0, by_type={}, by_status={}) + + def get_inventory_metrics(self) -> InventoryMetrics: + return InventoryMetrics( + total_items=0, items_in_stock=0, items_low_stock=0, by_category={} ) diff --git a/backend/api/v1/modules/core/invite_codes/models.py b/backend/api/v1/modules/core/invite_codes/models.py index 2a1bcdde..c7aebe04 100644 --- a/backend/api/v1/modules/core/invite_codes/models.py +++ b/backend/api/v1/modules/core/invite_codes/models.py @@ -1,4 +1,4 @@ -"""Modelo de código de invitación reutilizable para registro en Anexo76.""" +"""Modelo de código de invitación reutilizable para registro.""" from datetime import datetime from typing import Optional diff --git a/backend/api/v1/modules/core/invite_codes/service.py b/backend/api/v1/modules/core/invite_codes/service.py index 955275ef..7f5f9c41 100644 --- a/backend/api/v1/modules/core/invite_codes/service.py +++ b/backend/api/v1/modules/core/invite_codes/service.py @@ -1,4 +1,4 @@ -"""Servicio de códigos de invitación reutilizables para Anexo76.""" +"""Servicio de códigos de invitación reutilizables.""" import logging import random @@ -63,16 +63,8 @@ class InviteCodeService: raise HTTPException(status_code=404, detail="Tenant no encontrado") if data.company_id is not None: - from api.v1.modules.a76.general_catalogs.company.models import Company - - company = ( - self.db.query(Company) - .filter( - Company.id == data.company_id, - Company.tenant_id == tenant.id, - ) - .first() - ) + # Implementa la validación de company con tu modelo de compañía. + company = None if not company: raise HTTPException( status_code=404, @@ -141,7 +133,7 @@ class InviteCodeService: payload: dict = { "code": code, - "allowed_systems": ["anexo76"], + "allowed_systems": [], "role": data.role, } if data.max_uses is not None: diff --git a/backend/api/v1/modules/core/invites/models.py b/backend/api/v1/modules/core/invites/models.py index f0ea7623..b4f1d06a 100644 --- a/backend/api/v1/modules/core/invites/models.py +++ b/backend/api/v1/modules/core/invites/models.py @@ -1,4 +1,4 @@ -"""Modelo de token de invitación local para registro de usuarios en Anexo76.""" +"""Modelo de token de invitación local para registro de usuarios.""" from datetime import datetime from typing import Optional diff --git a/backend/api/v1/modules/core/invites/service.py b/backend/api/v1/modules/core/invites/service.py index 9a138f70..2935e6c3 100644 --- a/backend/api/v1/modules/core/invites/service.py +++ b/backend/api/v1/modules/core/invites/service.py @@ -1,4 +1,4 @@ -"""Servicio de invitaciones de usuarios para Anexo76.""" +"""Servicio de invitaciones de usuarios.""" import hashlib import logging @@ -214,7 +214,7 @@ class InviteService: msg = MIMEMultipart("alternative") msg["From"] = f"{settings.SMTP_FROM_NAME} <{settings.SMTP_USER}>" msg["To"] = to_email - msg["Subject"] = f"Invitación para unirse a {tenant_name} en Anexo76" + msg["Subject"] = f"Invitación para unirse a {tenant_name} en Mi Aplicación" html = f""" @@ -222,7 +222,7 @@ class InviteService:
-

Anexo76

+

Mi Aplicación

Sistema de gestión aduanal

@@ -231,7 +231,7 @@ class InviteService:

Has recibido una invitación para unirte a {tenant_name} - en Anexo76. Haz clic en el botón para crear tu cuenta. + en Mi Aplicación. Haz clic en el botón para crear tu cuenta.

None: - """Mantiene core.user_tenants alineado con asignaciones RBAC de la app.""" - try: - from api.v1.modules.a76.general_catalogs.company.models import Company - from core.security import _ensure_user_tenant_for_company - - company = ( - self.db.query(Company).filter(Company.id == company_id).first() - ) - if company: - _ensure_user_tenant_for_company( - self.db, user_id, company.tenant_id, company_id - ) - except Exception as e: - logger.warning("user_tenants sync skipped: %s", e) + """STUB — implementa con el modelo de compañía de tu proyecto.""" def _resolve_tenant_id_for_company(self, company_id: int) -> Optional[int]: """ @@ -64,13 +51,8 @@ class PermissionService: return None try: - from api.v1.modules.a76.general_catalogs.company.models import Company - - company = ( - self.db.query(Company).filter(Company.id == company_id).first() - ) - if company and company.tenant_id: - return int(company.tenant_id) + # Sin modelo de compañía en la plantilla — implementa la consulta aquí. + pass except Exception as exc: logger.warning( "resolve_tenant_id_for_company_failed", @@ -488,15 +470,8 @@ class PermissionService: sync_res = self.sync_permissions() logger.info(f"Bootstrap: Sincronización completa. {sync_res.get('synced', 0)} nuevos, {sync_res.get('total_registered', 0)} totales.") - # 1. Obtener el tenant_id - tenant_id = 1 - try: - from api.v1.modules.a76.general_catalogs.company.models import Company - company = self.db.query(Company).filter(Company.id == company_id).first() - if company: - tenant_id = company.tenant_id - except: - pass + # 1. Obtener el tenant_id (implementa con tu modelo de compañía) + tenant_id = self.db.info.get(RLS_TENANT_KEY) or 1 # 2. Buscar si ya existe el rol "super_admin" admin_role = self.db.query(CompanyRole).filter( diff --git a/backend/api/v1/modules/core/permissions/test_permissions.py b/backend/api/v1/modules/core/permissions/test_permissions.py deleted file mode 100644 index 183e0dd2..00000000 --- a/backend/api/v1/modules/core/permissions/test_permissions.py +++ /dev/null @@ -1,424 +0,0 @@ -""" -Tests unitarios para el módulo de permisos multi-tenant. -Ejecutar con: pytest backend/api/v1/modules/core/permissions/test_permissions.py -v -""" - -import pytest -from datetime import datetime, timedelta -from sqlalchemy import create_engine -from sqlalchemy.orm import sessionmaker, Session -from core.database import Base -from api.v1.modules.core.permissions.models import ( - Permission, - ClientRole, - RolePermission, - UserClientRole, - UserClientPermission, -) -from api.v1.modules.core.permissions.service import PermissionService - - -# ============================================================================ -# FIXTURES -# ============================================================================ - - -@pytest.fixture -def db_session(): - """Crea una sesión de base de datos en memoria para testing.""" - engine = create_engine("sqlite:///:memory:") - Base.metadata.create_all(engine) - Session = sessionmaker(bind=engine) - session = Session() - - yield session - - session.close() - - -@pytest.fixture -def sample_permissions(db_session: Session): - """Crea permisos de ejemplo.""" - permissions = [ - Permission( - code="invoice.view", - description="Ver facturas", - module="invoice", - action="view", - ), - Permission( - code="invoice.create", - description="Crear facturas", - module="invoice", - action="create", - ), - Permission( - code="invoice.edit", - description="Editar facturas", - module="invoice", - action="edit", - ), - Permission( - code="invoice.delete", - description="Eliminar facturas", - module="invoice", - action="delete", - ), - Permission( - code="user.view", description="Ver usuarios", module="user", action="view" - ), - ] - - for perm in permissions: - db_session.add(perm) - - db_session.commit() - return permissions - - -@pytest.fixture -def sample_role(db_session: Session, sample_permissions): - """Crea un rol de ejemplo con permisos.""" - role = ClientRole( - client_id=1, name="Administrador", code="admin", description="Acceso completo" - ) - db_session.add(role) - db_session.commit() - - # Asignar permisos al rol - for perm in sample_permissions[:3]: # invoice.view, create, edit - role_perm = RolePermission(client_role_id=role.id, permission_id=perm.id) - db_session.add(role_perm) - - db_session.commit() - return role - - -# ============================================================================ -# TESTS DE PermissionService -# ============================================================================ - - -class TestPermissionService: - """Tests para el servicio de permisos.""" - - def test_get_permissions_from_role(self, db_session, sample_role): - """Test: obtener permisos desde roles asignados.""" - # Asignar rol a usuario - user_role = UserClientRole( - user_id="user-123", client_id=1, client_role_id=sample_role.id - ) - db_session.add(user_role) - db_session.commit() - - # Verificar permisos - service = PermissionService(db_session) - permissions = service.get_user_permissions("user-123", 1) - - assert "invoice.view" in permissions - assert "invoice.create" in permissions - assert "invoice.edit" in permissions - assert "invoice.delete" not in permissions # No está en el rol - - def test_has_permission_single(self, db_session, sample_role): - """Test: verificar un permiso específico.""" - user_role = UserClientRole( - user_id="user-123", client_id=1, client_role_id=sample_role.id - ) - db_session.add(user_role) - db_session.commit() - - service = PermissionService(db_session) - - assert service.has_permission("user-123", 1, "invoice.view") is True - assert service.has_permission("user-123", 1, "invoice.delete") is False - - def test_has_all_permissions(self, db_session, sample_role): - """Test: verificar múltiples permisos (todos requeridos).""" - user_role = UserClientRole( - user_id="user-123", client_id=1, client_role_id=sample_role.id - ) - db_session.add(user_role) - db_session.commit() - - service = PermissionService(db_session) - - # Tiene todos - assert ( - service.has_all_permissions( - "user-123", 1, ["invoice.view", "invoice.create"] - ) - is True - ) - - # Falta uno - assert ( - service.has_all_permissions( - "user-123", 1, ["invoice.view", "invoice.delete"] - ) - is False - ) - - def test_has_any_permission(self, db_session, sample_role): - """Test: verificar múltiples permisos (al menos uno).""" - user_role = UserClientRole( - user_id="user-123", client_id=1, client_role_id=sample_role.id - ) - db_session.add(user_role) - db_session.commit() - - service = PermissionService(db_session) - - # Tiene al menos uno - assert ( - service.has_any_permission( - "user-123", 1, ["invoice.view", "invoice.delete"] - ) - is True - ) - - # No tiene ninguno - assert ( - service.has_any_permission("user-123", 1, ["invoice.delete", "user.view"]) - is False - ) - - def test_direct_permission_grant(self, db_session, sample_permissions): - """Test: conceder permiso directo a usuario.""" - service = PermissionService(db_session) - - # Conceder permiso directo - user_perm = service.grant_direct_permission( - user_id="user-123", client_id=1, permission_code="invoice.delete" - ) - - assert user_perm.is_granted is True - assert user_perm.is_active is True - - # Verificar que el usuario tiene el permiso - permissions = service.get_user_permissions("user-123", 1) - assert "invoice.delete" in permissions - - def test_direct_permission_revoke( - self, db_session, sample_role, sample_permissions - ): - """Test: revocar permiso que viene de un rol.""" - # Asignar rol con permisos - user_role = UserClientRole( - user_id="user-123", client_id=1, client_role_id=sample_role.id - ) - db_session.add(user_role) - db_session.commit() - - service = PermissionService(db_session) - - # Verificar que tiene el permiso del rol - assert service.has_permission("user-123", 1, "invoice.view") is True - - # Revocar explícitamente - invoice_view = ( - db_session.query(Permission) - .filter(Permission.code == "invoice.view") - .first() - ) - - user_perm = UserClientPermission( - user_id="user-123", - client_id=1, - permission_id=invoice_view.id, - is_granted=False, # Revocado - ) - db_session.add(user_perm) - db_session.commit() - - # Ahora NO debe tener el permiso - assert service.has_permission("user-123", 1, "invoice.view") is False - - def test_permission_expiration(self, db_session, sample_permissions): - """Test: permisos con fecha de expiración.""" - service = PermissionService(db_session) - - # Permiso expirado - expired_perm = service.grant_direct_permission( - user_id="user-123", - client_id=1, - permission_code="invoice.delete", - expires_at=datetime.utcnow() - timedelta(days=1), # Ayer - ) - - # No debe aparecer en los permisos - permissions = service.get_user_permissions("user-123", 1) - assert "invoice.delete" not in permissions - - # Permiso futuro - future_perm = service.grant_direct_permission( - user_id="user-456", - client_id=1, - permission_code="invoice.delete", - expires_at=datetime.utcnow() + timedelta(days=7), # En 7 días - ) - - # Debe aparecer - permissions = service.get_user_permissions("user-456", 1) - assert "invoice.delete" in permissions - - def test_assign_role_to_user(self, db_session, sample_role): - """Test: asignar un rol a un usuario.""" - service = PermissionService(db_session) - - user_role = service.assign_role_to_user( - user_id="user-789", - client_id=1, - role_id=sample_role.id, - assigned_by="admin-001", - ) - - assert user_role.user_id == "user-789" - assert user_role.client_id == 1 - assert user_role.client_role_id == sample_role.id - assert user_role.assigned_by == "admin-001" - assert user_role.is_active is True - - def test_assign_role_invalid_client(self, db_session, sample_role): - """Test: intentar asignar rol de otro cliente falla.""" - service = PermissionService(db_session) - - with pytest.raises(ValueError, match="not found for client"): - service.assign_role_to_user( - user_id="user-789", - client_id=999, # Cliente incorrecto - role_id=sample_role.id, - ) - - def test_multiple_roles_same_user(self, db_session, sample_permissions): - """Test: usuario con múltiples roles combina permisos.""" - # Crear dos roles diferentes - role1 = ClientRole(client_id=1, code="viewer", name="Viewer") - role2 = ClientRole(client_id=1, code="editor", name="Editor") - db_session.add_all([role1, role2]) - db_session.commit() - - # Role1: solo view - rp1 = RolePermission( - client_role_id=role1.id, - permission_id=sample_permissions[0].id, # invoice.view - ) - - # Role2: create y edit - rp2 = RolePermission( - client_role_id=role2.id, - permission_id=sample_permissions[1].id, # invoice.create - ) - rp3 = RolePermission( - client_role_id=role2.id, - permission_id=sample_permissions[2].id, # invoice.edit - ) - db_session.add_all([rp1, rp2, rp3]) - db_session.commit() - - # Asignar ambos roles al usuario - ur1 = UserClientRole(user_id="user-multi", client_id=1, client_role_id=role1.id) - ur2 = UserClientRole(user_id="user-multi", client_id=1, client_role_id=role2.id) - db_session.add_all([ur1, ur2]) - db_session.commit() - - # Verificar que tiene permisos de ambos roles - service = PermissionService(db_session) - permissions = service.get_user_permissions("user-multi", 1) - - assert "invoice.view" in permissions - assert "invoice.create" in permissions - assert "invoice.edit" in permissions - assert "invoice.delete" not in permissions - - def test_inactive_role_no_permissions(self, db_session, sample_role): - """Test: roles inactivos no otorgan permisos.""" - # Asignar rol activo - user_role = UserClientRole( - user_id="user-inactive", client_id=1, client_role_id=sample_role.id - ) - db_session.add(user_role) - db_session.commit() - - service = PermissionService(db_session) - - # Verificar que tiene permisos - assert service.has_permission("user-inactive", 1, "invoice.view") is True - - # Desactivar el rol - sample_role.is_active = False - db_session.commit() - - # Ahora no debe tener permisos - assert service.has_permission("user-inactive", 1, "invoice.view") is False - - def test_get_user_roles(self, db_session, sample_role): - """Test: obtener roles de un usuario.""" - user_role = UserClientRole( - user_id="user-roles", client_id=1, client_role_id=sample_role.id - ) - db_session.add(user_role) - db_session.commit() - - service = PermissionService(db_session) - roles = service.get_user_roles("user-roles", 1) - - assert len(roles) == 1 - assert roles[0].code == "admin" - assert roles[0].name == "Administrador" - - -# ============================================================================ -# TESTS DE MODELOS -# ============================================================================ - - -class TestModels: - """Tests para los modelos de base de datos.""" - - def test_permission_creation(self, db_session): - """Test: crear un permiso.""" - perm = Permission( - code="test.action", - description="Permiso de prueba", - module="test", - action="action", - ) - db_session.add(perm) - db_session.commit() - - assert perm.id is not None - assert perm.is_active is True - assert perm.created_at is not None - - def test_client_role_unique_constraint(self, db_session): - """Test: código de rol debe ser único por cliente.""" - role1 = ClientRole(client_id=1, code="admin", name="Admin 1") - db_session.add(role1) - db_session.commit() - - # Intentar crear otro con mismo código en mismo cliente - role2 = ClientRole(client_id=1, code="admin", name="Admin 2") - db_session.add(role2) - - with pytest.raises(Exception): # Violación de constraint - db_session.commit() - - def test_role_permission_relationship( - self, db_session, sample_role, sample_permissions - ): - """Test: relación entre roles y permisos.""" - # El rol ya tiene permisos del fixture - assert len(sample_role.role_permissions) > 0 - - # Verificar que los permisos están relacionados - permission_codes = [rp.permission.code for rp in sample_role.role_permissions] - assert "invoice.view" in permission_codes - - -# ============================================================================ -# EJECUTAR TESTS -# ============================================================================ - -if __name__ == "__main__": - pytest.main([__file__, "-v"]) diff --git a/backend/api/v1/modules/core/tasks_tracking/models.py b/backend/api/v1/modules/core/tasks_tracking/models.py index dc86a33a..72f9ff77 100644 --- a/backend/api/v1/modules/core/tasks_tracking/models.py +++ b/backend/api/v1/modules/core/tasks_tracking/models.py @@ -32,7 +32,7 @@ class TaskRun(Base): Integer, ForeignKey("core.tenants.id"), nullable=False, index=True ) company_id: Mapped[int | None] = mapped_column( - Integer, ForeignKey("a76.company.id"), nullable=True, index=True + Integer, nullable=True, index=True ) requested_by_user: Mapped[str | None] = mapped_column(String(255), nullable=True) task_name: Mapped[str] = mapped_column(String(255), nullable=False) diff --git a/backend/api/v1/modules/core/users/service.py b/backend/api/v1/modules/core/users/service.py index f34474bc..5abab26f 100644 --- a/backend/api/v1/modules/core/users/service.py +++ b/backend/api/v1/modules/core/users/service.py @@ -193,15 +193,10 @@ class UserService: user_data = hub_response.json() user_id = user_data.get("user_id") - # Obtener company_id si no se proporcionó + # Obtener company_id — implementa con tu modelo de compañía si company_id es None. if not self.company_id: - from api.v1.modules.a76.general_catalogs.company.models import Company - company = self.db.query(Company).filter(Company.tenant_id == self.tenant_id).first() - if not company: - raise HTTPException(status_code=400, detail="No company found") - company_id = company.id - else: - company_id = self.company_id + raise HTTPException(status_code=400, detail="company_id requerido") + company_id = self.company_id # Crear relación local user_tenant = UserTenant( diff --git a/backend/api/v1/modules/a24/inv/bom/__init__.py b/backend/api/v1/modules/example/__init__.py similarity index 100% rename from backend/api/v1/modules/a24/inv/bom/__init__.py rename to backend/api/v1/modules/example/__init__.py diff --git a/backend/api/v1/modules/example/dto.py b/backend/api/v1/modules/example/dto.py new file mode 100644 index 00000000..a795983b --- /dev/null +++ b/backend/api/v1/modules/example/dto.py @@ -0,0 +1,21 @@ +from pydantic import BaseModel, ConfigDict + + +class ItemCreate(BaseModel): + name: str + description: str | None = None + + +class ItemUpdate(BaseModel): + name: str | None = None + description: str | None = None + + +class ItemResponse(BaseModel): + model_config = ConfigDict(from_attributes=True) + + id: int + name: str + description: str | None + tenant_id: int + company_id: int diff --git a/backend/api/v1/modules/example/models.py b/backend/api/v1/modules/example/models.py new file mode 100644 index 00000000..3dfefb66 --- /dev/null +++ b/backend/api/v1/modules/example/models.py @@ -0,0 +1,16 @@ +from sqlalchemy import Integer, String, Text +from sqlalchemy.orm import Mapped, mapped_column + +from api.v1.common.base_models import TenantScopedMixin, TimestampMixin +from core.database import Base + + +class Item(Base, TenantScopedMixin, TimestampMixin): + """Modelo de ejemplo — renombra y ajusta a tu entidad de negocio.""" + + __tablename__ = "example_items" + __table_args__ = {"schema": "public"} + + id: Mapped[int] = mapped_column(Integer, primary_key=True, index=True) + name: Mapped[str] = mapped_column(String(255), nullable=False) + description: Mapped[str | None] = mapped_column(Text, nullable=True) diff --git a/backend/api/v1/modules/example/routes.py b/backend/api/v1/modules/example/routes.py new file mode 100644 index 00000000..a179b785 --- /dev/null +++ b/backend/api/v1/modules/example/routes.py @@ -0,0 +1,65 @@ +from fastapi import APIRouter, Depends, Query, status +from sqlalchemy.orm import Session + +from core.database import get_core_db +from core.security import get_current_user + +from .dto import ItemCreate, ItemResponse, ItemUpdate +from . import service + +router = APIRouter() + + +@router.get("/items", response_model=list[ItemResponse]) +def list_items( + company_id: int = Query(..., description="Company ID"), + current_user: dict = Depends(get_current_user), + db: Session = Depends(get_core_db), +): + tenant_id = current_user["tenant_id"] + return service.get_items(db, tenant_id, company_id) + + +@router.get("/items/{item_id}", response_model=ItemResponse) +def get_item( + item_id: int, + company_id: int = Query(..., description="Company ID"), + current_user: dict = Depends(get_current_user), + db: Session = Depends(get_core_db), +): + tenant_id = current_user["tenant_id"] + return service.get_item(db, item_id, tenant_id, company_id) + + +@router.post("/items", response_model=ItemResponse, status_code=status.HTTP_201_CREATED) +def create_item( + payload: ItemCreate, + company_id: int = Query(..., description="Company ID"), + current_user: dict = Depends(get_current_user), + db: Session = Depends(get_core_db), +): + tenant_id = current_user["tenant_id"] + return service.create_item(db, payload, tenant_id, company_id) + + +@router.patch("/items/{item_id}", response_model=ItemResponse) +def update_item( + item_id: int, + payload: ItemUpdate, + company_id: int = Query(..., description="Company ID"), + current_user: dict = Depends(get_current_user), + db: Session = Depends(get_core_db), +): + tenant_id = current_user["tenant_id"] + return service.update_item(db, item_id, payload, tenant_id, company_id) + + +@router.delete("/items/{item_id}", status_code=status.HTTP_204_NO_CONTENT) +def delete_item( + item_id: int, + company_id: int = Query(..., description="Company ID"), + current_user: dict = Depends(get_current_user), + db: Session = Depends(get_core_db), +): + tenant_id = current_user["tenant_id"] + service.delete_item(db, item_id, tenant_id, company_id) diff --git a/backend/api/v1/modules/example/service.py b/backend/api/v1/modules/example/service.py new file mode 100644 index 00000000..abfe7564 --- /dev/null +++ b/backend/api/v1/modules/example/service.py @@ -0,0 +1,48 @@ +from fastapi import HTTPException, status +from sqlalchemy.orm import Session + +from .dto import ItemCreate, ItemUpdate +from .models import Item + + +def get_items(db: Session, tenant_id: int, company_id: int) -> list[Item]: + return ( + db.query(Item) + .filter(Item.tenant_id == tenant_id, Item.company_id == company_id, Item.deleted_at.is_(None)) + .all() + ) + + +def get_item(db: Session, item_id: int, tenant_id: int, company_id: int) -> Item: + item = ( + db.query(Item) + .filter(Item.id == item_id, Item.tenant_id == tenant_id, Item.company_id == company_id, Item.deleted_at.is_(None)) + .first() + ) + if not item: + raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail="Item no encontrado") + return item + + +def create_item(db: Session, payload: ItemCreate, tenant_id: int, company_id: int) -> Item: + item = Item(**payload.model_dump(), tenant_id=tenant_id, company_id=company_id) + db.add(item) + db.commit() + db.refresh(item) + return item + + +def update_item(db: Session, item_id: int, payload: ItemUpdate, tenant_id: int, company_id: int) -> Item: + item = get_item(db, item_id, tenant_id, company_id) + for field, value in payload.model_dump(exclude_unset=True).items(): + setattr(item, field, value) + db.commit() + db.refresh(item) + return item + + +def delete_item(db: Session, item_id: int, tenant_id: int, company_id: int) -> None: + item = get_item(db, item_id, tenant_id, company_id) + from datetime import datetime, timezone + item.deleted_at = datetime.now(timezone.utc) + db.commit() diff --git a/backend/api/v1/modules/public/__init__.py b/backend/api/v1/modules/public/__init__.py deleted file mode 100644 index e69de29b..00000000 diff --git a/backend/api/v1/modules/public/reference_data/agency_tariff_codes/dto.py b/backend/api/v1/modules/public/reference_data/agency_tariff_codes/dto.py deleted file mode 100644 index d0a49b3e..00000000 --- a/backend/api/v1/modules/public/reference_data/agency_tariff_codes/dto.py +++ /dev/null @@ -1,12 +0,0 @@ -from pydantic import BaseModel, ConfigDict, Field - - -class AgencyTariffCodeDTO(BaseModel): - id: int - tariff_flag_code: str = Field(..., max_length=10) - agency_code: str = Field(..., max_length=10) - requirement_level: str = Field(..., min_length=1, max_length=1) - program_code: str = Field(..., max_length=10) - definition: str = Field(..., max_length=500) - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/public/reference_data/agency_tariff_codes/models.py b/backend/api/v1/modules/public/reference_data/agency_tariff_codes/models.py deleted file mode 100644 index 78d12bcd..00000000 --- a/backend/api/v1/modules/public/reference_data/agency_tariff_codes/models.py +++ /dev/null @@ -1,21 +0,0 @@ -from core.database import Base -from sqlalchemy import PrimaryKeyConstraint, String, Integer -from sqlalchemy.orm import Mapped, mapped_column - - -class AgencyTariffCode(Base): - __tablename__ = "agency_tariff_codes" - __table_args__ = ( - PrimaryKeyConstraint("id", name="agency_tariff_codes_pkey"), - {"schema": "public", "extend_existing": True}, - ) - - id: Mapped[int] = mapped_column(Integer, primary_key=True) - tariff_flag_code: Mapped[str] = mapped_column(String(10), nullable=False, index=True) - agency_code: Mapped[str] = mapped_column(String(10), nullable=False, index=True) - requirement_level: Mapped[str] = mapped_column(String(1), nullable=False) # M o R - program_code: Mapped[str] = mapped_column(String(10), nullable=False, index=True) - definition: Mapped[str] = mapped_column(String(500), nullable=False) - - def __repr__(self): - return f"" diff --git a/backend/api/v1/modules/public/reference_data/agency_tariff_codes/routes.py b/backend/api/v1/modules/public/reference_data/agency_tariff_codes/routes.py deleted file mode 100644 index a4300667..00000000 --- a/backend/api/v1/modules/public/reference_data/agency_tariff_codes/routes.py +++ /dev/null @@ -1,126 +0,0 @@ -from typing import Any, Dict - -from core.database import get_core_db -from core.security import get_current_user, has_role -from fastapi import APIRouter, Depends, HTTPException, Query -from sqlalchemy import or_ -from sqlalchemy.orm import Session - -from .dto import AgencyTariffCodeDTO -from .models import AgencyTariffCode -from ..rbac import require_ref_view - -router = APIRouter(prefix="/agency-tariff-codes") - - -@router.get( - "/", - response_model=Dict[str, Any], - dependencies=[Depends(require_ref_view("ref_pedimento_codes.view"))], -) -async def list_agency_tariff_codes( - page: int = Query(1, ge=1, description="Número de página"), - page_size: int = Query(50, ge=1, le=1000, description="Tamaño de página"), - agency: str = Query(None, description="Filtrar por código de agencia"), - q: str = Query(None, description="Búsqueda general"), - search: str = Query(None, description="Término de búsqueda"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - skip = (page - 1) * page_size - query = db.query(AgencyTariffCode) - - if search: - search_filter = f"%{search}%" - query = query.filter( - or_( - AgencyTariffCode.tariff_flag_code.ilike(search_filter), - AgencyTariffCode.agency_code.ilike(search_filter), - AgencyTariffCode.requirement_level.ilike(search_filter), - AgencyTariffCode.program_code.ilike(search_filter), - AgencyTariffCode.definition.ilike(search_filter) - ) - ) - - if agency: - query = query.filter(AgencyTariffCode.agency_code == agency) - - if q: - search_term = f"%{q}%" - query = query.filter( - or_( - AgencyTariffCode.tariff_flag_code.ilike(search_term), - AgencyTariffCode.agency_code.ilike(search_term), - AgencyTariffCode.program_code.ilike(search_term), - AgencyTariffCode.definition.ilike(search_term) - ) - ) - - items = query.offset(skip).limit(page_size).all() - total = query.count() - return { - "items": [AgencyTariffCodeDTO.model_validate(obj) for obj in items], - "total": total, - "page": page, - "page_size": page_size, - } - - -@router.get( - "/{id}", - response_model=AgencyTariffCodeDTO, - dependencies=[Depends(require_ref_view("ref_pedimento_codes.view"))], -) -async def get_agency_tariff_code( - id: int, - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - obj = db.query(AgencyTariffCode).filter(AgencyTariffCode.id == id).first() - if not obj: - raise HTTPException(status_code=404, detail="Not found") - return obj - - -@router.post("/", response_model=AgencyTariffCodeDTO, status_code=201) -async def create_agency_tariff_code( - data: AgencyTariffCodeDTO, - db: Session = Depends(get_core_db), - current_user: dict = Depends(has_role("admin")), -): - obj = AgencyTariffCode(**data.dict()) - db.add(obj) - db.commit() - db.refresh(obj) - return obj - - -@router.put("/{id}", response_model=AgencyTariffCodeDTO) -async def update_agency_tariff_code( - id: int, - data: AgencyTariffCodeDTO, - db: Session = Depends(get_core_db), - current_user: dict = Depends(has_role("admin")), -): - obj = db.query(AgencyTariffCode).filter(AgencyTariffCode.id == id).first() - if not obj: - raise HTTPException(status_code=404, detail="Not found") - for field, value in data.dict().items(): - setattr(obj, field, value) - db.commit() - db.refresh(obj) - return obj - - -@router.delete("/{id}", status_code=204) -async def delete_agency_tariff_code( - id: int, - db: Session = Depends(get_core_db), - current_user: dict = Depends(has_role("admin")), -): - obj = db.query(AgencyTariffCode).filter(AgencyTariffCode.id == id).first() - if not obj: - raise HTTPException(status_code=404, detail="Not found") - db.delete(obj) - db.commit() - return None diff --git a/backend/api/v1/modules/public/reference_data/agency_tariff_codes/seed.py b/backend/api/v1/modules/public/reference_data/agency_tariff_codes/seed.py deleted file mode 100644 index e7ad5ad5..00000000 --- a/backend/api/v1/modules/public/reference_data/agency_tariff_codes/seed.py +++ /dev/null @@ -1,118 +0,0 @@ -import logging -from sqlalchemy.orm import Session -from .models import AgencyTariffCode - -logger = logging.getLogger(__name__) - -AGENCY_TARIFF_CODES_DATA = [ - {"id": 1, "tariff_flag_code": "EP1", "agency_code": "EPA", "requirement_level": "M", "program_code": "ODS", "definition": "Ozone Depleting Substances specific data may be required"}, - {"id": 2, "tariff_flag_code": "EP2", "agency_code": "EPA", "requirement_level": "R", "program_code": "ODS", "definition": "Ozone Depleting Substances specific data is required"}, - {"id": 3, "tariff_flag_code": "EP3", "agency_code": "EPA", "requirement_level": "M", "program_code": "VNE", "definition": "Vehicle and Engines specific data may be required"}, - {"id": 4, "tariff_flag_code": "EP4", "agency_code": "EPA", "requirement_level": "R", "program_code": "VNE", "definition": "Vehicle and Engines specific data is required"}, - {"id": 5, "tariff_flag_code": "EP5", "agency_code": "EPA", "requirement_level": "M", "program_code": "PS1", "definition": "Pesticides specific data may be required"}, - {"id": 6, "tariff_flag_code": "EP5", "agency_code": "EPA", "requirement_level": "M", "program_code": "PS2", "definition": "Pesticides specific data may be required"}, - {"id": 7, "tariff_flag_code": "EP5", "agency_code": "EPA", "requirement_level": "M", "program_code": "PS3", "definition": "Pesticides specific data may be required"}, - {"id": 8, "tariff_flag_code": "EP6", "agency_code": "EPA", "requirement_level": "R", "program_code": "PS1", "definition": "Pesticides specific data is required"}, - {"id": 9, "tariff_flag_code": "EP6", "agency_code": "EPA", "requirement_level": "R", "program_code": "PS2", "definition": "Pesticides specific data is required"}, - {"id": 10, "tariff_flag_code": "EP6", "agency_code": "EPA", "requirement_level": "R", "program_code": "PS3", "definition": "Pesticides specific data is required"}, - {"id": 11, "tariff_flag_code": "EP7", "agency_code": "EPA", "requirement_level": "M", "program_code": "TS1", "definition": "Toxic Substances Control Act specific data may be required"}, - {"id": 12, "tariff_flag_code": "EP7", "agency_code": "EPA", "requirement_level": "M", "program_code": "TS2", "definition": "Toxic Substances Control Act specific data may be required"}, - {"id": 13, "tariff_flag_code": "EP8", "agency_code": "EPA", "requirement_level": "R", "program_code": "TS1", "definition": "Toxic Substances Control Act specific data is required"}, - {"id": 14, "tariff_flag_code": "EP8", "agency_code": "EPA", "requirement_level": "R", "program_code": "TS2", "definition": "Toxic Substances Control Act specific data is required"}, - {"id": 15, "tariff_flag_code": "FS3", "agency_code": "FSI", "requirement_level": "M", "program_code": "FSI", "definition": "FSIS data may be required. Applicable to all FSIS programs"}, - {"id": 16, "tariff_flag_code": "FS4", "agency_code": "FSI", "requirement_level": "R", "program_code": "FSI", "definition": "FSIS data is required. Applicable to all FSIS programs"}, - {"id": 17, "tariff_flag_code": "NM1", "agency_code": "NMF", "requirement_level": "M", "program_code": "370", "definition": "370 specific data may be required"}, - {"id": 18, "tariff_flag_code": "NM2", "agency_code": "NMF", "requirement_level": "R", "program_code": "370", "definition": "370 specific data is required"}, - {"id": 19, "tariff_flag_code": "NM3", "agency_code": "NMF", "requirement_level": "M", "program_code": "AMR", "definition": "Antarctic Marine Living Resources specific data may be required"}, - {"id": 20, "tariff_flag_code": "NM4", "agency_code": "NMF", "requirement_level": "R", "program_code": "AMR", "definition": "Antarctic Marine Living Resources is required"}, - {"id": 21, "tariff_flag_code": "NM5", "agency_code": "NMF", "requirement_level": "M", "program_code": "HMS", "definition": "Highly Migratory Species specific data may be required"}, - {"id": 22, "tariff_flag_code": "NM6", "agency_code": "NMF", "requirement_level": "R", "program_code": "HMS", "definition": "Highly Migratory Species specific data is required"}, - {"id": 23, "tariff_flag_code": "DT1", "agency_code": "NHT", "requirement_level": "M", "program_code": "MV", "definition": "DOT/National Highway Traffic Safety Administration HS-7 data may be required"}, - {"id": 24, "tariff_flag_code": "DT1", "agency_code": "NHT", "requirement_level": "M", "program_code": "REI", "definition": "DOT/National Highway Traffic Safety Administration HS-7 data may be required"}, - {"id": 25, "tariff_flag_code": "DT1", "agency_code": "NHT", "requirement_level": "M", "program_code": "TPE", "definition": "DOT/National Highway Traffic Safety Administration HS-7 data may be required"}, - {"id": 26, "tariff_flag_code": "DT1", "agency_code": "NHT", "requirement_level": "M", "program_code": "OEI", "definition": "DOT/National Highway Traffic Safety Administration HS-7 data may be required"}, - {"id": 27, "tariff_flag_code": "DT1", "agency_code": "NHT", "requirement_level": "M", "program_code": "OFF", "definition": "DOT/National Highway Traffic Safety Administration HS-7 data may be required"}, - {"id": 28, "tariff_flag_code": "DT2", "agency_code": "NHT", "requirement_level": "R", "program_code": "MVS", "definition": "DOT/National Highway Traffic Safety Administration HS-7 data is required"}, - {"id": 29, "tariff_flag_code": "DT2", "agency_code": "NHT", "requirement_level": "R", "program_code": "REI", "definition": "DOT/National Highway Traffic Safety Administration HS-7 data is required"}, - {"id": 30, "tariff_flag_code": "DT2", "agency_code": "NHT", "requirement_level": "R", "program_code": "TPE", "definition": "DOT/National Highway Traffic Safety Administration HS-7 data is required"}, - {"id": 31, "tariff_flag_code": "DT2", "agency_code": "NHT", "requirement_level": "R", "program_code": "OEI", "definition": "DOT/National Highway Traffic Safety Administration HS-7 data is required"}, - {"id": 32, "tariff_flag_code": "DT2", "agency_code": "NHT", "requirement_level": "R", "program_code": "OFF", "definition": "DOT/National Highway Traffic Safety Administration HS-7 data is required"}, - {"id": 33, "tariff_flag_code": "AL1", "agency_code": "APH", "requirement_level": "M", "program_code": "APL", "definition": "Lacey Act specific data may be required"}, - {"id": 34, "tariff_flag_code": "AL2", "agency_code": "APH", "requirement_level": "R", "program_code": "APL", "definition": "Lacey Act specific data is required"}, - {"id": 35, "tariff_flag_code": "FD1", "agency_code": "FDA", "requirement_level": "M", "program_code": "BIO", "definition": "FDA data may be required 801(a)"}, - {"id": 36, "tariff_flag_code": "FD1", "agency_code": "FDA", "requirement_level": "M", "program_code": "COS", "definition": "FDA data may be required 801(a)"}, - {"id": 37, "tariff_flag_code": "FD1", "agency_code": "FDA", "requirement_level": "M", "program_code": "DEV", "definition": "FDA data may be required 801(a)"}, - {"id": 38, "tariff_flag_code": "FD1", "agency_code": "FDA", "requirement_level": "M", "program_code": "DRU", "definition": "FDA data may be required 801(a)"}, - {"id": 39, "tariff_flag_code": "FD1", "agency_code": "FDA", "requirement_level": "M", "program_code": "FOO", "definition": "FDA data may be required 801(a)"}, - {"id": 40, "tariff_flag_code": "FD1", "agency_code": "FDA", "requirement_level": "M", "program_code": "RAD", "definition": "FDA data may be required 801(a)"}, - {"id": 41, "tariff_flag_code": "FD1", "agency_code": "FDA", "requirement_level": "M", "program_code": "TOB", "definition": "FDA data may be required 801(a)"}, - {"id": 42, "tariff_flag_code": "FD1", "agency_code": "FDA", "requirement_level": "M", "program_code": "VME", "definition": "FDA data may be required 801(a)"}, - {"id": 43, "tariff_flag_code": "FD2", "agency_code": "FDA", "requirement_level": "R", "program_code": "BIO", "definition": "FDA data Required 801(a)"}, - {"id": 44, "tariff_flag_code": "FD2", "agency_code": "FDA", "requirement_level": "R", "program_code": "COS", "definition": "FDA data Required 801(a)"}, - {"id": 45, "tariff_flag_code": "FD2", "agency_code": "FDA", "requirement_level": "R", "program_code": "DEV", "definition": "FDA data Required 801(a)"}, - {"id": 46, "tariff_flag_code": "FD2", "agency_code": "FDA", "requirement_level": "R", "program_code": "DRU", "definition": "FDA data Required 801(a)"}, - {"id": 47, "tariff_flag_code": "FD2", "agency_code": "FDA", "requirement_level": "R", "program_code": "FOO", "definition": "FDA data Required 801(a)"}, - {"id": 48, "tariff_flag_code": "FD2", "agency_code": "FDA", "requirement_level": "R", "program_code": "RAD", "definition": "FDA data Required 801(a)"}, - {"id": 49, "tariff_flag_code": "FD2", "agency_code": "FDA", "requirement_level": "R", "program_code": "TOB", "definition": "FDA data Required 801(a)"}, - {"id": 50, "tariff_flag_code": "FD2", "agency_code": "FDA", "requirement_level": "R", "program_code": "VME", "definition": "FDA data Required 801(a)"}, - {"id": 51, "tariff_flag_code": "FD3", "agency_code": "FDA", "requirement_level": "M", "program_code": "BIO", "definition": "FDA Prior Notice Data may be required 801(m)"}, - {"id": 52, "tariff_flag_code": "FD3", "agency_code": "FDA", "requirement_level": "M", "program_code": "COS", "definition": "FDA Prior Notice Data may be required 801(m)"}, - {"id": 53, "tariff_flag_code": "FD3", "agency_code": "FDA", "requirement_level": "M", "program_code": "DEV", "definition": "FDA Prior Notice Data may be required 801(m)"}, - {"id": 54, "tariff_flag_code": "FD3", "agency_code": "FDA", "requirement_level": "M", "program_code": "DRU", "definition": "FDA Prior Notice Data may be required 801(m)"}, - {"id": 55, "tariff_flag_code": "FD3", "agency_code": "FDA", "requirement_level": "M", "program_code": "FOO", "definition": "FDA Prior Notice Data may be required 801(m)"}, - {"id": 56, "tariff_flag_code": "FD3", "agency_code": "FDA", "requirement_level": "M", "program_code": "RAD", "definition": "FDA Prior Notice Data may be required 801(m)"}, - {"id": 57, "tariff_flag_code": "FD3", "agency_code": "FDA", "requirement_level": "M", "program_code": "TOB", "definition": "FDA Prior Notice Data may be required 801(m)"}, - {"id": 58, "tariff_flag_code": "FD3", "agency_code": "FDA", "requirement_level": "M", "program_code": "VME", "definition": "FDA Prior Notice Data may be required 801(m)"}, - {"id": 59, "tariff_flag_code": "FD4", "agency_code": "FDA", "requirement_level": "R", "program_code": "BIO", "definition": "FDA Prior Notice Data is required 801(m)"}, - {"id": 60, "tariff_flag_code": "FD4", "agency_code": "FDA", "requirement_level": "R", "program_code": "COS", "definition": "FDA Prior Notice Data is required 801(m)"}, - {"id": 61, "tariff_flag_code": "FD4", "agency_code": "FDA", "requirement_level": "R", "program_code": "DEV", "definition": "FDA Prior Notice Data is required 801(m)"}, - {"id": 62, "tariff_flag_code": "FD4", "agency_code": "FDA", "requirement_level": "R", "program_code": "DRU", "definition": "FDA Prior Notice Data is required 801(m)"}, - {"id": 63, "tariff_flag_code": "FD4", "agency_code": "FDA", "requirement_level": "R", "program_code": "FOO", "definition": "FDA Prior Notice Data is required 801(m)"}, - {"id": 64, "tariff_flag_code": "FD4", "agency_code": "FDA", "requirement_level": "R", "program_code": "RAD", "definition": "FDA Prior Notice Data is required 801(m)"}, - {"id": 65, "tariff_flag_code": "FD4", "agency_code": "FDA", "requirement_level": "R", "program_code": "TOB", "definition": "FDA Prior Notice Data is required 801(m)"}, - {"id": 66, "tariff_flag_code": "FD4", "agency_code": "FDA", "requirement_level": "R", "program_code": "VME", "definition": "FDA Prior Notice Data is required 801(m)"}, - {"id": 67, "tariff_flag_code": "AM1", "agency_code": "AMS", "requirement_level": "M", "program_code": "EG", "definition": "USDA/Agricultural Marketing Service Data related to egg products may be required"}, - {"id": 68, "tariff_flag_code": "AM2", "agency_code": "AMS", "requirement_level": "R", "program_code": "EG", "definition": "USDA/Agricultural Marketing Service Data Related to shell eggs is required"}, - {"id": 69, "tariff_flag_code": "AM3", "agency_code": "AMS", "requirement_level": "M", "program_code": "MO", "definition": "USDA/Agricultural Marketing Service Data Related to marketing orders may be required"}, - {"id": 70, "tariff_flag_code": "AM4", "agency_code": "AMS", "requirement_level": "R", "program_code": "MO", "definition": "USDA/Agricultural Marketing Service Data Related to marketing orders is required"}, - {"id": 71, "tariff_flag_code": "AM6", "agency_code": "AMS", "requirement_level": "R", "program_code": "PN", "definition": "USDA/Agriculture Marketing Service Data related to peanuts is required"}, - {"id": 72, "tariff_flag_code": "TB1", "agency_code": "TTB", "requirement_level": "M", "program_code": "BER", "definition": "TTB data may be required. Applicable to all TTB programs."}, - {"id": 73, "tariff_flag_code": "TB1", "agency_code": "TTB", "requirement_level": "M", "program_code": "WIN", "definition": "TTB data may be required. Applicable to all TTB programs."}, - {"id": 74, "tariff_flag_code": "TB1", "agency_code": "TTB", "requirement_level": "M", "program_code": "DSP", "definition": "TTB data may be required. Applicable to all TTB programs."}, - {"id": 75, "tariff_flag_code": "TB1", "agency_code": "TTB", "requirement_level": "M", "program_code": "TOB", "definition": "TTB data may be required. Applicable to all TTB programs."}, - {"id": 76, "tariff_flag_code": "TB2", "agency_code": "TTB", "requirement_level": "R", "program_code": "BER", "definition": "TTB data is required. Applicable to all TTB programs."}, - {"id": 77, "tariff_flag_code": "TB2", "agency_code": "TTB", "requirement_level": "R", "program_code": "WIN", "definition": "TTB data is required. Applicable to all TTB programs."}, - {"id": 78, "tariff_flag_code": "TB2", "agency_code": "TTB", "requirement_level": "R", "program_code": "DSP", "definition": "TTB data is required. Applicable to all TTB programs."}, - {"id": 79, "tariff_flag_code": "TB2", "agency_code": "TTB", "requirement_level": "R", "program_code": "TOB", "definition": "TTB data is required. Applicable to all TTB programs."}, - {"id": 80, "tariff_flag_code": "AQ1", "agency_code": "APH", "requirement_level": "M", "program_code": "AAC", "definition": "APHIS data may be required"}, - {"id": 81, "tariff_flag_code": "AQ1", "agency_code": "APH", "requirement_level": "M", "program_code": "APQ", "definition": "APHIS data may be required"}, - {"id": 82, "tariff_flag_code": "AQ1", "agency_code": "APH", "requirement_level": "M", "program_code": "AVS", "definition": "APHIS data may be required"}, - {"id": 83, "tariff_flag_code": "AQ1", "agency_code": "APH", "requirement_level": "M", "program_code": "ABS", "definition": "APHIS data may be required"}, - {"id": 84, "tariff_flag_code": "AQ2", "agency_code": "APH", "requirement_level": "R", "program_code": "AAC", "definition": "APHIS data is required"}, - {"id": 85, "tariff_flag_code": "AQ2", "agency_code": "APH", "requirement_level": "R", "program_code": "APQ", "definition": "APHIS data is required"}, - {"id": 86, "tariff_flag_code": "AQ2", "agency_code": "APH", "requirement_level": "R", "program_code": "AVS", "definition": "APHIS data is required"}, - {"id": 87, "tariff_flag_code": "AQ2", "agency_code": "APH", "requirement_level": "R", "program_code": "ABS", "definition": "APHIS data is required"}, - {"id": 88, "tariff_flag_code": "OM1", "agency_code": "OMC", "requirement_level": "M", "program_code": "OMC", "definition": "Office of Marine Conservation data may be required"}, - {"id": 89, "tariff_flag_code": "OM2", "agency_code": "OMC", "requirement_level": "R", "program_code": "OMC", "definition": "Office of Marine Conservation data is required"}, - {"id": 90, "tariff_flag_code": "FW2", "agency_code": "FWS", "requirement_level": "R", "program_code": "FWS", "definition": "U.S. Fish and Wildlife Service data is required"}, - {"id": 91, "tariff_flag_code": "DE1", "agency_code": "DEA", "requirement_level": "M", "program_code": "DEA", "definition": "U.S. Drug Enforcement Administration data may be required"}, -] - -def seed_agency_tariff_codes(db: Session): - """Seed Agency Tariff Codes catalog data""" - logger.info("Seeding Agency Tariff Codes...") - for item in AGENCY_TARIFF_CODES_DATA: - db_item = db.query(AgencyTariffCode).filter(AgencyTariffCode.id == item["id"]).first() - if not db_item: - logger.info(f"Adding agency tariff code: {item['id']}") - new_item = AgencyTariffCode(**item) - db.add(new_item) - else: - # Update fields if changed - db_item.tariff_flag_code = item["tariff_flag_code"] - db_item.agency_code = item["agency_code"] - db_item.requirement_level = item["requirement_level"] - db_item.program_code = item["program_code"] - db_item.definition = item["definition"] - db.commit() - logger.info("Agency Tariff Codes seeding completed.") diff --git a/backend/api/v1/modules/public/reference_data/carta_porte_codes/cartaPorte.csv b/backend/api/v1/modules/public/reference_data/carta_porte_codes/cartaPorte.csv deleted file mode 100644 index 299c6cef..00000000 --- a/backend/api/v1/modules/public/reference_data/carta_porte_codes/cartaPorte.csv +++ /dev/null @@ -1,28695 +0,0 @@ -SYSID,CODIGO,DESCRIPCION,PALABRASSIMILARES,MATPELIGROSO,FECHAINICIOVIGENCIA,FECHAFINVIGENCIA -108,10141608,Arneses o sus accesorios,,0,80509,0 -191,10151900,"Semillas, bulbos, plántulas y esquejes de flores","Semillas, bulbos, vástagos y codos de flores",0,80509,0 -315,10202110,Rosal vivo carla,,0,80509,0 -444,10202310,Rosal vivo bling bling,,0,80509,0 -558,10211730,Astromelia viva snowball,Lirio de campo vivo snowball,0,80509,0 -640,10215445,Azucena viva oriental dizzy,Lili viva oriental dizzy,0,80509,0 -723,10216906,Matiola viva rosada,Alhelí viva rosada,0,80509,0 -826,10252034,Orquídea phalaenopsis viva mambo,,0,80509,0 -934,10302475,Rosal cortado fresco tinto,,0,80509,0 -1040,10312206,Aster cortada fresca japonés rosado claro,Estrella cortada fresca japonés rosado claro,0,80509,0 -1109,10316002,Pimiento mezclado cortado fresco ornamental,Paprika ornamental mezclada cortada fresca,0,80509,0 -1195,10317419,Flor de cera cortada fresca perla,,0,80509,0 -1298,10342100,Bouquet cortado fresco de flores tropicales,,0,80509,0 -1380,10426012,Penacho dulce cortada seca,,0,80509,0 -1486,10502010,Follaje fresco cortado de olivo,,0,80509,0 -1579,10502956,Skimmia fresca cortada,,0,80509,0 -1707,11111811,Haydita,,0,80509,0 -1832,11151712,Hilado de papel,Hilo de papel,0,80509,0 -1949,11191606,Destrozos automovilísticos para chatarra o desecho,Demolición o desguace de automóviles para los residuos o desechos,0,80509,0 -2092,12141907,Boro b,,"0,1",80509,0 -2208,12162902,Agentes de control de asfaltenos de parafina modificados cristal,,0,80509,0 -2326,12352000,Compuestos aromáticos y alifáticos,,"0,1",80509,0 -2453,13101906,Poliuretano termoestable pur,,0,80509,0 -2570,13111202,Películas de poliuretano,,0,80509,0 -2687,14111823,Formatos o libros de control,,0,80509,0 -2807,15121517,Jabones lubricantes,,0,80509,0 -2919,20111608,Martillos de poder,,0,80509,0 -3018,20121320,Uniones de cizalla,"Empalmes de esquileo, Juntas de corte",0,80509,0 -3120,20121706,Rebosamientos,,0,80509,0 -3222,20122318,Herramientas direccionales de cable de recuperación,,0,80509,0 -3320,20122804,Taladros en barcazas,,0,80509,0 -3427,20142900,Tanques y recipientes almacenadores,,0,80509,0 -3533,21102300,Equipo para invernadero,,0,80509,0 -3645,22101800,Grúas de elevación,,0,80509,0 -3760,23131604,Sierras para hacer facetas o pulir caras,,0,80509,0 -3863,23152111,Pantalla vibratoria de separación de alambre tejido,,0,80509,0 -3980,23153506,Sistema de pintura de fosfato o “e coat”,,0,80509,0 -4093,23211106,Implantador de iones,,0,80509,0 -4211,23241904,Máquinas para dar formas internas al metal,,0,80509,0 -4323,23261503,Máquina para la manufactura de objeto laminado,,0,80509,0 -4424,23281902,Limpiador químico de metal,,0,80509,0 -4541,24101713,Aparato de cangilones,Transportadores de cangilones,0,80509,0 -4647,24111804,Tanques de calibración,,0,80509,0 -4757,24131507,Congelador de golpe,Abatidor de temperatura,"0,1",80509,0 -4867,25101921,Carros para transporte de líquidos exceptuando petróleo,Camiones furgones,0,80509,0 -4984,25111912,Recogedores de botavaras,Enrolladores de vela mayor,0,80509,0 -5101,25171716,Líneas de freno,Mangueras de frenos,0,80509,0 -5199,25172609,Faldón anti – fango,,0,80509,0 -5300,25174213,Ensambles de cilindro de dirección,,0,80509,0 -5391,25191500,Sistemas y equipo de apoyo para transporte aéreo,,0,80509,0 -5481,25191820,Multímetro de motor,,0,80509,0 -5578,25202605,Intercambiadores de calor del avión,,0,80509,0 -5681,26101727,Anillo de pistón,,0,80509,0 -5794,26111609,Generador de turbina de gas,,0,80509,0 -5912,26121608,Cable aéreo,,0,80509,0 -5913,26121609,Cable de redes,,0,80509,0 -5914,26121610,Cable de bronce,,0,80509,0 -5915,26121611,Cable desnudo,,0,80509,0 -5916,26121612,Cable forrado pero no aislado,,0,80509,0 -5917,26121613,Cable aislado o forrado,,0,80509,0 -5918,26121614,Cable de construcción,,0,80509,0 -5919,26121615,Cable para ser enterrado de forma directa,,0,80509,0 -5920,26121616,Cable de telecomunicaciones,,0,80509,0 -5921,26121617,Cable triaxial,,0,80509,0 -5922,26121618,Cable de poliqueno reticulado,,0,80509,0 -5923,26121619,Cable de floropolímero,,0,80509,0 -5924,26121620,Cable para interconexiones,,0,80509,0 -5925,26121621,Cable de kaptan,,0,80509,0 -5926,26121622,Cable de poliamida,,0,80509,0 -5927,26121623,Cable de radiofrecuencia (rf),,0,80509,0 -5928,26121624,Cable plano o de cinta,,0,80509,0 -5929,26121628,Cables blindados,,0,80509,0 -5930,26121629,Cable de alimentación,,0,80509,0 -5931,26121630,Accesorios de cable,,0,80509,0 -5932,26121631,Cable coaxial exterior de planta,,0,80509,0 -5933,26121632,Cable de comunicaciones exterior de planta,Cable de planta exterior,0,80509,0 -5934,26121633,Cable de telecomunicaciones exterior de planta,Cable de planta exterior,0,80509,0 -5935,26121634,Cable de cobre,,0,80509,0 -5936,26121635,Rollos de cable,,0,80509,0 -5937,26121636,Cables de alimentación,,0,80509,0 -5938,26121637,Cable de fibra óptica de exterior,,0,80509,0 -5939,26121638,Materiales de engaste,,0,80509,0 -5940,26121639,Multi – cables combinados o a la medida,,0,80509,0 -5941,26121640,Cables resistentes al calor,,0,80509,0 -5942,26121641,Cables de instalación,,0,80509,0 -5943,26121642,Cable de instrumentación,,0,80509,0 -5944,26121643,Cable eléctrico reforzado de acero desnudo,,0,80509,0 -5945,26121644,Cable eléctrico de aluminio desnudo,,0,80509,0 -5946,26121645,Cable eléctrico de cobre desnudo,,0,80509,0 -5947,26121646,Cable de seguimiento y máquina de minería,,0,80509,0 -5948,26121647,Ensamble de cable de seguimiento y máquina de minería,,0,80509,0 -5949,26121648,Kit de terminación de cable de alto voltaje,Paquete de terminación de cable de alto voltaje,0,80509,0 -5950,26121649,Kit de unión de cable de alto voltaje,Paquete de unión de cables de alta tensión,0,80509,0 -5951,26121650,Conjunto de cable de fibra óptica,,0,80509,0 -5952,26121651,Cable so resistente al aceite,,0,80509,0 -5953,26121652,Conjunto de cordón eléctrico,,0,80509,0 -5954,26121653,Cable toll,,0,80509,0 -5955,26121654,Cable de frecuencia de carrier,,0,80509,0 -5956,26121655,Cable de fibra óptica simplex y dúplex,,0,80509,0 -5957,26121656,Cable de fibra óptica submarina,,0,80509,0 -5958,26121657,Cable de fibra óptica retardante de fuego,,0,80509,0 -5959,26121658,Cable de transmisión de aluminio conductor resistente al movimiento,,0,80509,0 -5960,26121659,Cable de bajada de servicio cuádruple de aluminio conductor,,0,80509,0 -5961,26121660,Cable de bajada de servicio dúplex de aluminio conductor,,0,80509,0 -5795,26111610,Generador selsyn,,0,80509,0 -5796,26111611,Generador auxiliar,,0,80509,0 -5797,26111612,Generador de impulso,,0,80509,0 -5798,26111613,Generador de marea,,0,80509,0 -5799,26111700,"Baterías, pilas y accesorios",,"0,1",80509,0 -5800,26111701,Baterías recargables,Pilas recacrgables,"0,1",80509,0 -5801,26111702,Pilas alcalinas,Baterías alcalinas,"0,1",80509,0 -5802,26111703,Baterías para vehículos,,"0,1",80509,0 -5803,26111704,Cargadores de baterías,Cargadores de pilas,"0,1",80509,0 -5804,26111705,Pilas secas,,"0,1",80509,0 -5805,26111706,Pilas electrónicas,,"0,1",80509,0 -5806,26111707,Baterías de plomo-ácido,,"0,1",80509,0 -5807,26111708,Baterías de ferroníquel,,"0,1",80509,0 -5808,26111709,Baterías de níquel-cadmio,,"0,1",80509,0 -5809,26111710,Bloques de pilas específicas para productos,,"0,1",80509,0 -5810,26111711,Baterías de litio,,"0,1",80509,0 -5811,26111712,Baterías de níquel- hidrógeno,,"0,1",80509,0 -5812,26111713,Baterías térmicas,,"0,1",80509,0 -5813,26111714,Zinc aire,"Batería mecánicamente recargables, Batería no recargable","0,1",80509,0 -5814,26111715,Batería de carbono zinc,,"0,1",80509,0 -5815,26111716,Batería de oxido de mercurio,,"0,1",80509,0 -5816,26111717,Baterías del manganeso,,"0,1",80509,0 -5817,26111718,Baterías de óxido de plata,Pila de plata,"0,1",80509,0 -5818,26111719,Probadores de baterías,Probadores de pilas,"0,1",80509,0 -5819,26111720,Soportes de batería,Soportes de pilas,"0,1",80509,0 -5820,26111721,Baterías de níquel-hidruro metálico,,"0,1",80509,0 -5821,26111722,Adaptador de batería o accesorios,Adaptador de pilas o accesorios,"0,1",80509,0 -5822,26111723,"Puertas, tapas o estantes de baterías",Tapas o cajas para pilas,"0,1",80509,0 -5823,26111724,Kits de herramientas para baterías,,"0,1",80509,0 -5824,26111725,Baterías de níquel-cloruro de sodio,,"0,1",80509,0 -5825,26111726,Unidad de balasto de batería fluorescente,,"0,1",80509,0 -5826,26111727,Descargador de baterías,Descargador de pilas,"0,1",80509,0 -5827,26111728,Celda estándar,,"0,1",80509,0 -5828,26111729,Unidad de cargador de batería de auto,Unidad de carga automática de la batería,"0,1",80509,0 -5829,26111800,Componentes de la transmisión,,0,80509,0 -5830,26111801,Correas en v,"Accionamientos de correa en v, Bandas en v",0,80509,0 -5831,26111802,Correas de distribución de engranaje,,0,80509,0 -5832,26111803,Correas redondas,,0,80509,0 -5833,26111804,Correas planas,,0,80509,0 -5834,26111805,Tensores de correa,,0,80509,0 -5835,26111806,Poleas de transmisión,,0,80509,0 -5836,26111807,Polea de distribución,,0,80509,0 -5837,26111808,Trantorque,,0,80509,0 -5838,26111809,Defensas de correa,,0,80509,0 -5839,26111810,Bridas de la polea de distribución,,0,80509,0 -5840,26111811,Banda sincrónica,,0,80509,0 -5841,26111812,Polea sincrónica,,0,80509,0 -5842,26111813,Polea de velocidad variable,,0,80509,0 -5843,26111814,Banda hexagonal,,0,80509,0 -5844,26111815,Polea de cono,,0,80509,0 -5845,26111816,Polea plana,,0,80509,0 -5846,26111900,Embragues,,0,80509,0 -5847,26111901,Embragues de placa,Clutch,0,80509,0 -5848,26111902,Embragues de diafragma,,0,80509,0 -5849,26111903,Embrague centrífugo,,0,80509,0 -5850,26111904,Embrague semi centrífugo,,0,80509,0 -5851,26111905,Embrague de rueda libre,,0,80509,0 -5852,26111907,Acoplamiento de fluido,,0,80509,0 -5853,26111908,Embragues de leva,,0,80509,0 -5854,26111909,Embragues eléctricos,,0,80509,0 -5855,26111910,Embragues hidráulicos,,0,80509,0 -5856,26111911,Embrague neumático,,0,80509,0 -5857,26111912,Embrague mecánico,,0,80509,0 -5858,26111913,Unidad de detención de embrague,,0,80509,0 -5859,26111914,Embrague de mandíbula,,0,80509,0 -5860,26111915,Embrague automático,,0,80509,0 -5861,26111916,Embrague semi automático,,0,80509,0 -5862,26111917,Embrague electromagnético,,0,80509,0 -5863,26112000,Piezas y accesorios de embragues,,0,80509,0 -5864,26112001,Placa de presión,,0,80509,0 -5865,26112002,Placa impulsada,,0,80509,0 -5866,26112003,Placas de embrague,,0,80509,0 -5867,26112004,Kits de reparación del embrague,Paquete de reparación de embrague,0,80509,0 -5868,26112100,Sistemas de frenado industriales,,0,80509,0 -5869,26112101,Sistemas de frenos neumáticos o de aire,,0,80509,0 -5870,26112102,Sistemas de frenos hidráulicos,,0,80509,0 -5871,26112103,Sistemas de frenos mecánicos,,0,80509,0 -5872,26112104,Conjuntos de embrague de frenado,,0,80509,0 -5873,26112105,Sistemas de frenado eléctrico,,0,80509,0 -5874,26121500,Alambre eléctrico,,0,80509,0 -5875,26121501,Alambre calentador,,0,80509,0 -5876,26121505,Alambre para artefactos,,0,80509,0 -5877,26121507,Alambre para radio o televisión,,0,80509,0 -5878,26121508,Alambre para automoción o aviación,,0,80509,0 -5879,26121509,Alambre para imanes,,0,80509,0 -5880,26121510,Alambre de trole,,0,80509,0 -5881,26121514,Alambre subterráneo,,0,80509,0 -5882,26121515,Alambre de silicio-amianto (sa),,0,80509,0 -5883,26121517,Hilo de cobre,,0,80509,0 -5884,26121519,Alambre de aluminio revestido de cobre,,0,80509,0 -5885,26121520,Alambre de cobre-acero,,0,80509,0 -5886,26121521,Alambre de bronce,,0,80509,0 -5887,26121522,Alambre pelado,,0,80509,0 -5888,26121523,Alambre forrado pero no aislado,,0,80509,0 -5889,26121524,Alambre aislado o forrado,,0,80509,0 -5890,26121532,Alambre para interconexiones,,0,80509,0 -5891,26121533,Alambre de kaptan,,0,80509,0 -5892,26121534,Alambre de poliamida,,0,80509,0 -5893,26121536,Cordón de extensión,,0,80509,0 -5894,26121538,Conjunto de cable,,0,80509,0 -5895,26121539,Cables para cableado,,0,80509,0 -5896,26121540,Cable galvanizado,Alambre galvanizado,0,80509,0 -5897,26121541,Conductores de bus,,0,80509,0 -5898,26121542,Cable de instalación,,0,80509,0 -5899,26121543,Cable resistente al calor,,0,80509,0 -5900,26121544,Cable de bajada,,0,80509,0 -5901,26121545,Cable portátil eléctrico,,0,80509,0 -5902,26121546,Cable de cobre recubierto de estaño,,0,80509,0 -5903,26121547,Cable de bronce,,0,80509,0 -5904,26121548,Cable rectangular,,0,80509,0 -5905,26121600,Cables eléctricos y accesorios,,0,80509,0 -5906,26121601,Cable de calentamiento,,0,80509,0 -5907,26121602,Cable submarino,,0,80509,0 -5908,26121603,Cable de mando,,0,80509,0 -5909,26121604,Cable para señales,Cable HDMI,0,80509,0 -5910,26121606,Cable coaxial,,0,80509,0 -5911,26121607,Cable de fibra óptica,,0,80509,0 -5962,26121661,Cable de aluminio conductor subterráneo de entrada de servicio,,0,80509,0 -6278,27111829,Paralelo óptico,,0,80509,0 -6279,27111900,Herramientas gruesas y de acabado,,0,80509,0 -6280,27111901,Cortafríos,Cincel corta frío,0,80509,0 -6281,27111903,Cepillos de carpintero,,0,80509,0 -6282,27111904,Raspa,,0,80509,0 -6283,27111905,Esmeriladoras,,0,80509,0 -6284,27111906,Cinceles de madera,,0,80509,0 -6285,27111907,Cepillos de alambre,,0,80509,0 -6286,27111908,Piedras o herramientas o equipos de afilar,,0,80509,0 -6287,27111909,Espátulas,,0,80509,0 -6288,27111910,Buriles,Cuchilla para torno,0,80509,0 -6289,27111911,Formones,Cinceles de corte transversal,0,80509,0 -6290,27111912,Perforador de madera,,0,80509,0 -6291,27111913,Arreglador de bordes,Recortador de bordes,0,80509,0 -6292,27111914,Cincel de punta de bola,Cincel de punta,0,80509,0 -6293,27111915,Cincel de cantera,,0,80509,0 -6294,27111916,Removedor de techos,,0,80509,0 -6295,27111917,Aplicador de goma,,0,80509,0 -6296,27111918,Lija manual,,0,80509,0 -6297,27111919,Lima bastarda,,0,80509,0 -6298,27111920,Lima de segundo corte,,0,80509,0 -6299,27111921,Lima de corte liso,,0,80509,0 -6300,27111922,Lima de corte en cruz,,0,80509,0 -6301,27111923,Lima de sierra de cadena,Afiladora de dientes de motosierra,0,80509,0 -6302,27111924,Lima de corte raspa,,0,80509,0 -6303,27111925,Lima de aguja,,0,80509,0 -6304,27111926,Lima plana de aluminio,,0,80509,0 -6305,27111927,Lima de aluminio semicircular,,0,80509,0 -6306,27111928,Lima rallo de queso,,0,80509,0 -6307,27111929,Lima de mano plana,,0,80509,0 -6308,27111930,Lima redonda,,0,80509,0 -6309,27111931,Lima semicircular,,0,80509,0 -6310,27111932,Lima de escuadra,,0,80509,0 -6311,27111933,Lima triangular de tres escuadras,,0,80509,0 -6312,27111934,Lima plana cónica,,0,80509,0 -6313,27111935,Lima de navaja,,0,80509,0 -6314,27111936,Lima de guarda,,0,80509,0 -6315,27111937,Lima laminada,,0,80509,0 -6316,27111938,Lima de torno de ángulo largo,,0,80509,0 -6317,27111939,Lima semicircular para tubería,,0,80509,0 -6318,27111940,Lima de sierra maderera,,0,80509,0 -6319,27111941,Lima de sierra cónica,,0,80509,0 -6320,27111942,Lima de sierra de extremo doble,,0,80509,0 -6321,27111943,Lima de granjero,,0,80509,0 -6322,27111944,Lima de precisión,,0,80509,0 -6323,27111945,Lima curva para grabar troqueles,,0,80509,0 -6324,27111946,Lima curva de platería,,0,80509,0 -6325,27111948,Raspador semicircular de madera,,0,80509,0 -6326,27111949,Raspador semicircular de gabinete,,0,80509,0 -6327,27111950,Raspador circular de madera,,0,80509,0 -6328,27111951,Raspador de zapatas de cuatro en mano,,0,80509,0 -6329,27111952,Raspador de caballos,,0,80509,0 -6330,27111953,Raspador de peletero,,0,80509,0 -6331,27111954,Escariador cónico de vástago de puente,Escariador vástago de puente,0,80509,0 -6332,27111955,Escariador redondo de vástago de puente,Escariador redondeado,0,80509,0 -6333,27111956,Bruñidor,,0,80509,0 -6334,27111957,Limpiador de boquillas,,0,80509,0 -6335,27111958,Lapeador de mano,,0,80509,0 -6336,27112000,"Herramientas manuales de jardinería, agricultura y forestación","Herramientas manuales de floricultura, cultivo y reforestación",0,80509,0 -6337,27112001,Machetes,,0,80509,0 -6338,27112002,Azadones,,0,80509,0 -6339,27112003,Rastrillos,,0,80509,0 -6340,27112004,Palas,,0,80509,0 -6341,27112005,Hachas,,0,80509,0 -6342,27112006,Guadañas,,0,80509,0 -6343,27112007,Tijeras de podar,Tijeras para podar pasto,0,80509,0 -6344,27112008,Azadas,Azadillas,0,80509,0 -6345,27112009,Raspadores,,0,80509,0 -6346,27112010,Horquilla de jardín,,0,80509,0 -6347,27112011,Mangos de herramientas,,0,80509,0 -6348,27112012,Criba jardinera,Cernidor de jardinería,0,80509,0 -6349,27112013,Excavadora de hoyos para postes,Perforadora de hoyos para postes,0,80509,0 -6350,27112014,Cortadora de pasto,Desbrozadora para pasto,0,80509,0 -6351,27112015,Escarificador de prados,Podadora de pasto,0,80509,0 -6352,27112016,Tijeras para setos,Tijeras podadoras de arbustos,0,80509,0 -6353,27112017,Barras de cavar o barretones,,0,80509,0 -6354,27112018,Plantador de bulbos,,0,80509,0 -6355,27112019,Sierra podadora,,0,80509,0 -6356,27112020,Hoz,,0,80509,0 -6357,27112021,Aireador de césped,,0,80509,0 -6358,27112022,Cizalla para bordes,Cortadoras de borde,0,80509,0 -6359,27112023,Cultivador a mano,,0,80509,0 -6360,27112024,Juego de herramientas para jardín en miniatura,,0,80509,0 -6361,27112025,Azada de mango corto,Azadillas de mango corto,0,80509,0 -6362,27112026,Cuña de mango,,0,80509,0 -6363,27112027,Escoba de jardín,Rastrillo,0,80509,0 -6364,27112028,Descortezadora de árboles,,0,80509,0 -6365,27112029,Regadera de jardín,,0,80509,0 -6366,27112030,Gancho podador,,0,80509,0 -6367,27112031,Cedazo de jardín,,0,80509,0 -6368,27112032,Azuela,Hacha con hoja curveada,0,80509,0 -6369,27112033,Juego de herramientas de jardín,,0,80509,0 -6370,27112034,Cortador de raíces,,0,80509,0 -6371,27112035,Cortador de setos,,0,80509,0 -6372,27112036,Trituradora de jardín,Trituradora de ramas,0,80509,0 -6373,27112037,Cortador de brochas,,0,80509,0 -6374,27112038,Sierra de cadena de jardín,,0,80509,0 -6375,27112039,Podadora de 2 manos,,0,80509,0 -6376,27112040,Sonda de suelo,,0,80509,0 -6377,27112041,Cuchillo de injertos,Navaja para injertar,0,80509,0 -6378,27112042,Medidor de ancho de troncos,Instrumentos de medición forestal,0,80509,0 -6379,27112043,Herramienta para diagnóstico de árboles o resistógrafo,,0,80509,0 -6380,27112044,Juego de inyecciones para troncos de árboles,,0,80509,0 -6381,27112045,Cortador de césped,,0,80509,0 -6382,27112046,Recogedor de pasto cortado,Recolector de pasto cortado,0,80509,0 -6383,27112047,Cortador de ramas altas,,0,80509,0 -6384,27112100,Herramientas de sujeción y fijación,,0,80509,0 -6385,27112101,Dobladores de tubos,Roladora de tubo,0,80509,0 -6386,27112102,Tornillos de banco,,0,80509,0 -6387,27112103,Pinza de mano,,0,80509,0 -6388,27112104,Tenazas,,0,80509,0 -6389,27112105,Pinzas,,0,80509,0 -6390,27112106,Alicates de guardalínea,Pinzas de electricista,0,80509,0 -6391,27112107,Alicates boquianchos ajustables,Pinzas de boquilla ajustable,0,80509,0 -6392,27112108,Alicates de punta de aguja,Pinzas de punta de aguja,0,80509,0 -5963,26121662,Cable de aluminio conductor de entrada de servicio,,0,80509,0 -6499,27112408,Removedor industrial de ganchos,,0,80509,0 -6500,27112409,Pistola remachadora explosiva,Pistola remachadora expansiva,0,80509,0 -6501,27112410,Pistola para amarrar cables,Pistola para atadura de cables,0,80509,0 -6502,27112500,Herramientas para apalancar y curvar,,0,80509,0 -6503,27112501,Herramientas curvatubos,Dobladoras para tubo,0,80509,0 -6504,27112502,Palancas,Barra para palanquear,0,80509,0 -6505,27112503,Dobladoras de tubos,,0,80509,0 -6506,27112504,Cuñas,,0,80509,0 -6507,27112505,Ganchos para empaquetaduras,Ganchos de embalaje,0,80509,0 -6508,27112506,Manchas de impresión,,0,80509,0 -6509,27112507,Varilla patecabra,Barreta pata de cabra,0,80509,0 -6510,27112508,Doblador de cables,,0,80509,0 -6511,27112600,Herramientas para precintar,Herramientas para asegurar y etiquetar,0,80509,0 -6512,27112601,Espátulas para enmasillar,,0,80509,0 -6513,27112602,Herramientas de calafateado,,0,80509,0 -6514,27112603,Herramienta de inserción,,0,80509,0 -6515,27112604,Recipiente para plastilina,Cuenco de masilla,0,80509,0 -6516,27112700,Herramientas mecánicas,,0,80509,0 -6517,27112701,Fuelles eléctricos,,0,80509,0 -6518,27112702,Pulidoras eléctricas,,0,80509,0 -6519,27112703,Taladradoras eléctricas,,0,80509,0 -6520,27112704,Amoladora eléctricas,,0,80509,0 -6521,27112705,Martillos de demolición,,0,80509,0 -6522,27112706,Cepillos eléctricos,,0,80509,0 -6523,27112707,Cuchillas eléctricas,,0,80509,0 -6524,27112708,Lijadoras eléctricas,,0,80509,0 -6525,27112709,Sierras eléctricas,,0,80509,0 -6526,27112710,Pistolas de tornillos eléctricas,,0,80509,0 -6527,27112711,Pistolas de grapas eléctricas,,0,80509,0 -6528,27112712,Ribeteadoras eléctricas,,0,80509,0 -6529,27112713,Aprietatuercas neumático de percusión,,0,80509,0 -6530,27112714,Pistolas de calafateo eléctricas,,0,80509,0 -6531,27112715,Martillos cinceladores eléctricos,,0,80509,0 -6532,27112716,Pistolas de clavos eléctricas,,0,80509,0 -6533,27112717,Pistolas de calor,,0,80509,0 -6534,27112718,Grabadores,,0,80509,0 -6535,27112719,Pistolas para engomar,,0,80509,0 -6536,27112720,Herramientas de par de torsión,,0,80509,0 -6537,27112721,Juntador de capas de material,,0,80509,0 -6538,27112722,Biseladora,,0,80509,0 -6539,27112723,Cizallas eléctricas,,0,80509,0 -6540,27112724,Pellizcadoras eléctricas,,0,80509,0 -6541,27112725,Ribeteadora eléctrica,,0,80509,0 -6542,27112726,Cortador de varillas eléctrico,,0,80509,0 -6543,27112727,Pistola de mortero,,0,80509,0 -6544,27112728,Soplete eléctrico,,0,80509,0 -6545,27112729,Conector de tubos,,0,80509,0 -6546,27112730,Pistola de grasa eléctrica,,0,80509,0 -6547,27112731,Tijeras eléctricas,,0,80509,0 -6548,27112732,Doblador de tubos eléctrico,,0,80509,0 -6549,27112733,Cortador de ángulos,,0,80509,0 -6550,27112734,Sierra de vaivén,,0,80509,0 -6551,27112735,Sierra de espiral,,0,80509,0 -6552,27112736,Cortador de muros,,0,80509,0 -6553,27112737,Lija orbital,,0,80509,0 -6554,27112738,Lija de detalle,,0,80509,0 -6555,27112739,Lija de banda,,0,80509,0 -6556,27112740,Lija de banca,,0,80509,0 -6557,27112741,Lima lija o lija dedo,,0,80509,0 -6558,27112742,Lija de disco,,0,80509,0 -6559,27112743,Sierra de brazo elástico,,0,80509,0 -6560,27112744,Sierra neumática,,0,80509,0 -6561,27112745,Sierra de concreto,,0,80509,0 -6562,27112746,Sierra de cadena,,0,80509,0 -6563,27112747,Sierra recíproca,,0,80509,0 -6564,27112748,Sierra ingleteadora,,0,80509,0 -6565,27112749,Pulidora de ángulo,,0,80509,0 -6566,27112750,Herramienta compresora a batería,,0,80509,0 -6567,27112800,Conexiones de herramientas y accesorios,,0,80509,0 -6568,27112802,Hojas de sierra,Discos cortadores,0,80509,0 -6569,27112803,Fresadoras con mango,"Brocas de fresado, Matrices",0,80509,0 -6570,27112804,Troqueles o punzones de estampar,,0,80509,0 -6571,27112805,Troqueles de roscado,,0,80509,0 -6572,27112806,Machos de roscado,Machuelos,0,80509,0 -6573,27112807,Cuñas,,0,80509,0 -6574,27112808,Anillos metálicos,Sujetadores de collar para brocas y troqueles,0,80509,0 -6575,27112809,Portaherramientas,,0,80509,0 -6576,27112810,Equipos para reparar roscas,,0,80509,0 -6577,27112811,Árboles,Mandril industrial,0,80509,0 -6578,27112812,Avellanadores,,0,80509,0 -6579,27112813,Vara de extensión,,0,80509,0 -6580,27112814,Brocas de destornillador,,0,80509,0 -6581,27112815,Brocas para poner tuercas,,0,80509,0 -6582,27112818,Sombreretes o revestimientos de prensas de tornillo,,0,80509,0 -6583,27112819,Cuchillas de corte para encuadernación,,0,80509,0 -6584,27112820,Troqueles de herramienta engarzadora de lengüetas,,0,80509,0 -6585,27112821,Brocas de buriladora,,0,80509,0 -6586,27112822,Adaptadores de cubo,,0,80509,0 -6587,27112823,Cadenas de corte,,0,80509,0 -6588,27112824,Portabrocas,,0,80509,0 -6589,27112825,Juegos de plantillas de herramienta,Plantilla ajustable de medición,0,80509,0 -6590,27112826,Caladora,,0,80509,0 -6591,27112827,Dispositivos para imantar y viceversa,Imantador para herramientas,0,80509,0 -6592,27112828,Enchufes de impacto,Casquillos de impacto,0,80509,0 -6593,27112829,Anexos de llave de impacto y sus accesorios,"Llaves de impacto, acoplamientos y accesorios",0,80509,0 -6594,27112830,Accesorios y suministros de destornillador,"Desarmador, suministros y accesorios",0,80509,0 -6595,27112831,Accesorios y suministros de llave,"Llave inglesa, suministros y accesorios",0,80509,0 -6596,27112832,Anexos t accesorios de enchufe,,0,80509,0 -6597,27112833,Estuche de brocas,,0,80509,0 -6598,27112834,Estuche de roscador manual de tubos,,0,80509,0 -6599,27112835,Escariador de máquina,,0,80509,0 -6600,27112836,Juego de brocas de destornillador,,0,80509,0 -6601,27112837,Escariador manual,,0,80509,0 -6602,27112838,Disco de corte,,0,80509,0 -6603,27112839,Broca de cincel,,0,80509,0 -6604,27112840,Broca guía,,0,80509,0 -6605,27112841,Broca manual para metal,,0,80509,0 -6606,27112842,Broca manual para madera,,0,80509,0 -6607,27112843,Broca manual para piedra,,0,80509,0 -6608,27112844,Placa de lapeado,,0,80509,0 -6609,27112845,Juego de brocas,,0,80509,0 -6610,27112846,Adaptador de pistola de calor,,0,80509,0 -6611,27112847,Estuche de troqueles,,0,80509,0 -6612,27112900,Herramientas dispensadoras,,0,80509,0 -6393,27112109,Herramientas magnéticas,Herramientas imantadas,0,80509,0 -6394,27112110,Pinzas de anillo de retención,Pinzas para anillo de retención,0,80509,0 -6395,27112111,Alicates de lagarto,Pinzas de lagarto,0,80509,0 -6396,27112112,Tenazas de ranura y lengüeta,Pinzas de ranura y lengüeta,0,80509,0 -6397,27112113,Alicates de articulación movible o de ranura,Pinzas de junta ajustable o ranura,0,80509,0 -6398,27112114,Pinzas de corte diagonal,,0,80509,0 -6399,27112115,Pinzas de cerrado,Pinzas de presión,0,80509,0 -6400,27112116,Pinzas de cerca,Pinzas para cercas,0,80509,0 -6401,27112117,Tenazas de corte final,Pinzas para corte final,0,80509,0 -6402,27112119,Cambiador de bombilla de luz,Brazo largo para cambiar focos,0,80509,0 -6403,27112120,Grapas c,Prensa C,0,80509,0 -6404,27112121,Grapa de ángulo,Abrazaderas de ángulo,0,80509,0 -6405,27112122,Alicates de hoja metálica,Alicates de chapa,0,80509,0 -6406,27112123,Prensa de mesa,,0,80509,0 -6407,27112124,Tensionadores,,0,80509,0 -6408,27112125,Alicates de punta redonda,Pinzas de punta,0,80509,0 -6409,27112126,Alicates planos,Pinzas de punta plana,0,80509,0 -6410,27112127,Llaves de cinta,,0,80509,0 -6411,27112128,Alicates de punta curvada,Pinzas de punta curva,0,80509,0 -6412,27112129,Abrazaderas de mango redondo,,0,80509,0 -6413,27112130,Abrazaderas de tres garras,,0,80509,0 -6414,27112131,Abrazadera para la apertura de mandíbula,,0,80509,0 -6415,27112132,Abrazaderas de fijación,,0,80509,0 -6416,27112133,Abrazaderas con mango en t,Abrazaderas de manillar en T,0,80509,0 -6417,27112134,Alicates de punta larga,Pinzas de punta larga,0,80509,0 -6418,27112135,Alicates aislados,Pinzas aisladas,0,80509,0 -6419,27112136,Alicates que no sacan chispa,Pinzas antichispas,0,80509,0 -6420,27112137,Juego de alicates,Juego de pinzas,0,80509,0 -6421,27112138,Enderezador manual de tubos,,0,80509,0 -6422,27112139,Alicates de combinación,Pinzas de combinación,0,80509,0 -6423,27112140,Alicates circlip,Pinzas circlip,0,80509,0 -6424,27112141,Alicates de vidrio,Cortadores de vidrio,0,80509,0 -6425,27112142,Alicates muli – herramienta,Alicates múltiple,0,80509,0 -6426,27112143,Alicates para cortar baldosas,Cortadoras de loza,0,80509,0 -6427,27112144,Alicates de nariz doblada,,0,80509,0 -6428,27112145,Alicates de articulación movible,Alicates de unión deslizante,0,80509,0 -6429,27112146,Tornillo de base pivote,Tornillo de sujeción,0,80509,0 -6430,27112147,Alicates de engaste,Alicates de crimpado,0,80509,0 -6431,27112148,Alicates electrónicos,,0,80509,0 -6432,27112149,Alicates electrostáticos,,0,80509,0 -6433,27112150,Alicates de joyería,Pinzas de joyero,0,80509,0 -6434,27112151,Alicates pelacables,Pinzas pela cables,0,80509,0 -6435,27112152,Almeja triclover,,0,80509,0 -6436,27112153,Engrapadora de banda,,0,80509,0 -6437,27112154,Tornillo fresador,,0,80509,0 -6438,27112155,Expansor de anillo de pistón,,0,80509,0 -6439,27112156,Tornillo de mano,,0,80509,0 -6440,27112157,Tornillo de taladro,,0,80509,0 -6441,27112158,Tornillo de pata,,0,80509,0 -6442,27112159,Tornillo de cadena,,0,80509,0 -6443,27112160,Torcedor de cables,Tornillo de alambre,0,80509,0 -6444,27112161,Herramienta para empacar cables,Herramienta para sujeción de cable,0,80509,0 -6445,27112162,Unión para reglas,,0,80509,0 -6446,27112200,Herramientas de albañilería y concreto,,0,80509,0 -6447,27112201,Palustres,Cuchara para mezcla,0,80509,0 -6448,27112202,Palustres o llanas de madera,,0,80509,0 -6449,27112203,Bordeadoras,,0,80509,0 -6450,27112205,Vibradores de concreto,,0,80509,0 -6451,27112206,Tabla de mortero,,0,80509,0 -6452,27112207,Ariete de mano,,0,80509,0 -6453,27112208,Pica,Copiador de perfiles para corte,0,80509,0 -6454,27112209,Pala de mampostería,,0,80509,0 -6455,27112210,Unidor de ladrillos,,0,80509,0 -6456,27112211,Llenador de uniones,,0,80509,0 -6457,27112212,Herramienta de frente de brocal,Herramienta de bordillo,0,80509,0 -6458,27112213,Chute de concreto,Tolva de concreto,0,80509,0 -6459,27112215,Alisador de bordes,Alisador de bordes para concreto,0,80509,0 -6460,27112216,Marcador de baldosas,Marcador de azulejos,0,80509,0 -6461,27112217,Partidor de baldosas,Cortador de azulejos,0,80509,0 -6462,27112218,Sierra eléctrica para baldosas,Sierra eléctrica de azulejos,0,80509,0 -6463,27112219,Pellizcador de baldosas,Pinzas para azulejo,0,80509,0 -6464,27112220,Sierra de mano para baldosas,Sierra de mano para azulejo,0,80509,0 -6465,27112221,Broca para baldosas,Broca especial para azulejo,0,80509,0 -6466,27112222,Cortador de baldosas,Cortador de azulejos,0,80509,0 -6467,27112223,Lima para baldosas,Lima para azulejo,0,80509,0 -6468,27112224,Copiador de perfiles para corte,,0,80509,0 -6469,27112225,Esparcidor muescado,Espaciador entallado,0,80509,0 -6470,27112226,Palustre para baldosas,Cuchara para azulejos,0,80509,0 -6471,27112227,Esparcidor de lechada,,0,80509,0 -6472,27112228,Removedor de lechada,,0,80509,0 -6473,27112229,Cincel para remover baldosas,Cincel de cuña para remover azulejo,0,80509,0 -6474,27112230,Llenador de juntas,Puntero tuck para relleno de juntas,0,80509,0 -6475,27112231,Herramienta para escalones,,0,80509,0 -6476,27112232,Ranuradora,,0,80509,0 -6477,27112300,Herramientas de marcar,Herramienta de sellado y marcado,0,80509,0 -6478,27112301,Hierro de marcar,Fierro quemador,0,80509,0 -6479,27112302,Punzones o alwznas,,0,80509,0 -6480,27112303,Punzón de trazar,,0,80509,0 -6481,27112304,Líneas de marcar con tiza,,0,80509,0 -6482,27112305,Marcadores o soportes de metal,,0,80509,0 -6483,27112306,Sellos de metal,,0,80509,0 -6484,27112307,Tizas para trazar líneas,,0,80509,0 -6485,27112308,Carrito marcador,,0,80509,0 -6486,27112309,Lápiz marcador,,0,80509,0 -6487,27112310,Soporte de tiza,Sujetadores de gis,0,80509,0 -6488,27112311,Tiza para marcar,Gis para marcar,0,80509,0 -6489,27112312,Medidor superficial,Medidor de superficies,0,80509,0 -6490,27112313,Marcador de dimensiones,,0,80509,0 -6491,27112400,Herramientas de inserción y ajuste,,0,80509,0 -6492,27112401,Pistolas de grapas,,0,80509,0 -6493,27112402,Ribeteadoras,Maquinas de coser u orleadora,0,80509,0 -6494,27112403,Colocadores de abrazaderas,,0,80509,0 -6495,27112404,Herramientas para poner anclajes,Herramienta de fijación,0,80509,0 -6496,27112405,Calentadores de pernos,,0,80509,0 -6497,27112406,Pistolas de etiquetado,Pistola etiquetadora,0,80509,0 -6498,27112407,Desprendedor de etiqueta de seguridad,,0,80509,0 -6845,30102309,Perfiles de cobre,,0,80509,0 -6846,30102310,Perfiles de latón,,0,80509,0 -6847,30102311,Perfiles de bronce,,0,80509,0 -6848,30102312,Perfiles de zinc,,0,80509,0 -6849,30102313,Perfiles de estaño,,0,80509,0 -6850,30102314,Perfiles de plomo,,0,80509,0 -6851,30102315,Perfiles de plástico,,0,80509,0 -6852,30102316,Perfiles de caucho,,0,80509,0 -6853,30102400,Varillas,,0,80509,0 -6854,30102401,Varillas de aleación ferrosa,,0,80509,0 -6855,30102402,Varillas de aleación no ferrosa,,0,80509,0 -6856,30102403,Varillas de hierro,,0,80509,0 -6857,30102404,Varillas de acero,,0,80509,0 -6858,30102405,Varillas de acero inoxidable,,0,80509,0 -6859,30102406,Varillas de aluminio,,0,80509,0 -6860,30102407,Varillas de magnesio,,0,80509,0 -6861,30102408,Varillas de titanio,,0,80509,0 -6862,30102409,Varillas de cobre,,0,80509,0 -6863,30102410,Varillas de latón,,0,80509,0 -6864,30102411,Varillas de bronce,,0,80509,0 -6865,30102412,Varillas de zinc,,0,80509,0 -6866,30102413,Varillas de estaño,,0,80509,0 -6867,30102414,Varillas de plomo,,0,80509,0 -6868,30102415,Varillas de plástico,,0,80509,0 -6869,30102416,Varillas de caucho,,0,80509,0 -6870,30102417,Varillas de níquel,,0,80509,0 -6871,30102800,Pilotaje,,0,80509,0 -6872,30102801,Pilares de aluminio,,0,80509,0 -6873,30102802,Pilares de concreto,,0,80509,0 -6874,30102803,Pilares de acero,,0,80509,0 -6875,30102804,Pilares de madera,,0,80509,0 -6876,30102900,Pilares,,0,80509,0 -6877,30102901,Postes de cemento o concreto,,0,80509,0 -6878,30102903,Postes de metal,,0,80509,0 -6879,30102904,Postes de madera,,0,80509,0 -6880,30102905,Postes de plástico,,0,80509,0 -6881,30102906,Postes de fibra de vidrio,,0,80509,0 -6882,30102907,Postes de madera tratada,,0,80509,0 -6883,30103100,Rieles,,0,80509,0 -6884,30103101,Rieles de acero,,0,80509,0 -6885,30103102,Rieles de aluminio,,0,80509,0 -6886,30103103,Rieles de metal,,0,80509,0 -6887,30103104,Rieles de madera,,0,80509,0 -6888,30103200,Enrejado,,0,80509,0 -6889,30103201,Rejilla de acero,,0,80509,0 -6890,30103202,Rejilla de acero inoxidable,,0,80509,0 -6891,30103203,Rejilla de aluminio,,0,80509,0 -6892,30103204,Rejilla de fibra de vidrio,,0,80509,0 -6893,30103205,Rejilla de hierro,,0,80509,0 -6894,30103206,Rejilla de plástico,,0,80509,0 -6895,30103207,Rejilla laminada,,0,80509,0 -6896,30103208,Rejilla de madera,,0,80509,0 -6897,30103500,Alma de panal,Núcleo de panal,0,80509,0 -6898,30103501,Núcleo de panal de aluminio,,0,80509,0 -6899,30103502,Núcleo de panal de magnesio,,0,80509,0 -6900,30103503,Núcleo de panal de espuma,,0,80509,0 -6901,30103504,Núcleo de panal de plástico,,0,80509,0 -6902,30103505,Núcleo de panal de madera,,0,80509,0 -6903,30103506,Núcleo de panal de metal ferroso,,0,80509,0 -6904,30103507,Núcleo de panal de bronce,,0,80509,0 -6905,30103508,Núcleo de panal de cobre,,0,80509,0 -6906,30103509,Núcleo de panal de acero,,0,80509,0 -6907,30103510,Núcleo de panal de plomo,,0,80509,0 -6908,30103511,Núcleo de panal de zinc,,0,80509,0 -6909,30103512,Núcleo de panal de titanio,,0,80509,0 -6910,30103513,Núcleo de panal de latón,,0,80509,0 -6911,30103514,Núcleo de panal de metal no ferroso,,0,80509,0 -6912,30103515,Núcleo de panal de metales preciosos,,0,80509,0 -6913,30103600,Productos estructurales,Solera,0,80509,0 -6914,30103601,Vigas de madera,,0,80509,0 -6915,30103602,Vigas compuestas de madera,,0,80509,0 -6916,30103603,Madera para marcos,,0,80509,0 -6917,30103604,Revestimiento o láminas de madera,,0,80509,0 -6918,30103605,Tablones de madera,,0,80509,0 -6919,30103606,Armazones de madera,Estructuras de madera,0,80509,0 -6920,30103607,Viguetas de madera,,0,80509,0 -6921,30103608,Postes de madera o postes telefónicos,,0,80509,0 -6922,30103609,Tabla de tabique de ventilación,,0,80509,0 -6923,30103610,Bloque de soporte en minas,,0,80509,0 -6924,30103611,Esteras de troncos,,0,80509,0 -6925,30103612,Vínculos de ferrocarril,,0,80509,0 -6926,30103613,Elemento prefabricado de hyadita,,0,80509,0 -6927,30103614,Vigueta de placa de acero,,0,80509,0 -6928,30103615,Viga de placa,Vigas planas,0,80509,0 -6929,30103616,Marco compuesto,Marco rígido compuesto,0,80509,0 -6930,30103617,Marco de madera,,0,80509,0 -6931,30103618,Marco de acero,,0,80509,0 -6932,30103619,Elemento de concreto prefabricado,Elemento prefabricado de hormigón,0,80509,0 -6933,30103620,Bloque de soporte en minas tratado,,0,80509,0 -6934,30103621,Vínculo de ferrocarril no tratado,,0,80509,0 -6935,30103622,Troncos cuadrados,Madera cuadrada,0,80509,0 -6936,30103623,Barra o malla de refuerzo,,0,80509,0 -6937,30103700,Trenza,,0,80509,0 -6938,30103701,Trenza de acero inoxidable,,0,80509,0 -6939,30103800,Fibras y filamentos mecánicos,,0,80509,0 -6940,30103801,Fibra de acero,,0,80509,0 -6941,30103802,Fibra de estaño,,0,80509,0 -6942,30103803,Fibra de aluminio,,0,80509,0 -6943,30103804,Fibra de aleación,,0,80509,0 -6944,30103900,Ejes,,0,80509,0 -6945,30103901,Socavón con cabeza,,0,80509,0 -6946,30111500,Concreto y morteros,Cemento y morteros,0,80509,0 -6947,30111501,Concreto aireado,Cemento aireado,0,80509,0 -6948,30111502,Concreto conductor,Cemento conductivo,0,80509,0 -6949,30111503,Concreto aislante,Cemento aislante,0,80509,0 -6950,30111504,Morteros,,0,80509,0 -6951,30111505,Concreto premezclado,Cemento premezclado,0,80509,0 -6952,30111506,Lechada,Mezcla de cemento,0,80509,0 -6953,30111507,Concreto curado en suelo,Suelo cemento,0,80509,0 -6954,30111508,Concreto permeable al agua,Cemento permeable,0,80509,0 -6955,30111509,Concreto basado en asfalto,Hormigón asfáltico,0,80509,0 -6956,30111600,Cemento y cal,,0,80509,0 -6957,30111601,Cemento,,0,80509,0 -6958,30111602,Cal clorada,,0,80509,0 -6959,30111603,Cal hidráulica,,0,80509,0 -6960,30111604,Cal apagada,,0,80509,0 -6961,30111605,Cal magra,,0,80509,0 -6962,30111607,Cal viva,,0,80509,0 -6963,30111700,Yesos,,0,80509,0 -6964,30111701,Enlucido de yeso,Yeso fino para enlucir,0,80509,0 -6965,30111800,Agregados,,0,80509,0 -6966,30111801,Agregado natural,,0,80509,0 -6967,30111802,Agregado artificial,Agregados manufacturados,0,80509,0 -6968,30111900,Elementos para reforzar concreto,,0,80509,0 -6969,30111901,Fibra para reforzar concreto,,0,80509,0 -6970,30111902,Junta de expansión de construcción,Juntas de dilatación de construcción,0,80509,0 -6971,30111903,Lámina de malla de alambre,Armex,0,80509,0 -6972,30121500,Derivados bituminosos,Materiales bituminosos,1,80509,0 -6973,30121501,Alquitrán de carbón hulla,,"0,1",80509,0 -6974,30121503,Creosota,,0,80509,0 -6975,30121504,Bitumen,Betún,0,80509,0 -6976,30121600,Asfaltos,,"0,1",80509,0 -6977,30121601,Asfalto,,"0,1",80509,0 -6978,30121602,Brea,,"0,1",80509,0 -6979,30121603,Gilsonita,,0,80509,0 -6980,30121604,Residuos de alquitrán o petróleo,,1,80509,0 -6981,30121605,Marcos de pozo con tapas del registro,Aro y tapa para registro,0,80509,0 -6982,30121700,Materiales de construcción de caminos y carrileras,,0,80509,0 -6983,30121701,Geomalla,,0,80509,0 -6984,30121702,Geotextil,Geotejido,0,80509,0 -6985,30121703,Riel de puente,Viga carril de puente,0,80509,0 -6986,30121704,Losa de concreto,Forjado,0,80509,0 -6987,30121705,Losa de pavimento,,0,80509,0 -6988,30121706,Borde de concreto,Bordillos de hormigón,0,80509,0 -6989,30121707,Tablero de protección contra el ruido,,0,80509,0 -6990,30121708,Baldosa de ladrillo,Loseta tipo ladrillo,0,80509,0 -6991,30121709,Roca triturada sin grado,,0,80509,0 -6992,30121710,Piedra de pavimento,Piedra pavimentada,0,80509,0 -6993,30121711,Borde de piedra natural,,0,80509,0 -6994,30121712,Cubierta de poste,Cubierta para poste,0,80509,0 -6995,30121713,Tapa de manhole,"Tapa de alcantarilla, Tapa para registro",0,80509,0 -6996,30121714,Caja de manhole,Caja de registro,0,80509,0 -6997,30121715,Junta de expansión de puente,Juntas de dilatación de puentes,0,80509,0 -6998,30121716,Montura de asiento de puente,,0,80509,0 -6999,30121717,Baranda,Barandilla,0,80509,0 -7000,30121718,Cerca de seguridad y red cara la caída de rocas,,0,80509,0 -7001,30121719,Separador de seguridad de calle,Barreras de contención,0,80509,0 -7002,30121800,Materiales de arquitectura paisajística,,0,80509,0 -7003,30121801,Rejilla para árbol,,0,80509,0 -7004,30121802,Varilla de protección de árbol,Protector de árboles de varilla,0,80509,0 -7005,30121803,Prado artificial,Césped artificial,0,80509,0 -7006,30131500,Bloques,,0,80509,0 -7007,30131502,Bloques de concreto,Blocks de concreto,0,80509,0 -7008,30131503,Bloques de piedra,,0,80509,0 -7009,30131504,Bloques de cerámica,,0,80509,0 -7010,30131505,Bloques de haydita,,0,80509,0 -7011,30131506,Losa de roca natural,,0,80509,0 -7012,30131507,Bloque de concreto liviano,,0,80509,0 -7013,30131508,Bloque de vidrio,,0,80509,0 -7014,30131509,Bloque a prueba de sonido,Bloque de absorción acústica de sonido,0,80509,0 -7015,30131510,Bloque de concreto para revestimiento,Bloque de hormigón para revestimiento,0,80509,0 -7016,30131511,Unidad de concreto de escollera,Bloques de concreto para malecón,0,80509,0 -7017,30131512,Bloque de concreto aireado de peso liviano esterilizado en autoclave,Bloque de hormigón celular aireado autoclavado,0,80509,0 -7018,30131513,Bloque de caucho,,0,80509,0 -7019,30131514,Bloque de culvert construido de concreto reforzado,Bloque de alcantarilla construido de hormigón armado,0,80509,0 -7020,30131515,Bloque de madera,,0,80509,0 -7021,30131516,Bloque braille,,0,80509,0 -7022,30131517,Bloque de adobe,,0,80509,0 -7023,30131600,Ladrillos,,0,80509,0 -7024,30131602,Ladrillos de cerámica,,0,80509,0 -7025,30131603,Ladrillos de concreto,"Tabicón, Tabique",0,80509,0 -7026,30131604,Ladrillos de piedra,,0,80509,0 -7027,30131605,Ladrillo sílico – calcáreo,,0,80509,0 -7028,30131606,Ladrillo de loess,,0,80509,0 -7029,30131607,Ladrillo de arcilla,,0,80509,0 -7030,30131608,Ladrillo vitrificado con sal,,0,80509,0 -7031,30131609,Ladrillo manufacturado,,0,80509,0 -7032,30131610,Ladrillo antifuego,Ladrillo corta fuego,0,80509,0 -7033,30131700,Azulejos y baldosas,,0,80509,0 -7034,30131702,Losas o baldosas de piedra,,0,80509,0 -7035,30131703,Losas o baldosas de concreto,,0,80509,0 -7036,30131704,Losas o baldosas de cerámica,,0,80509,0 -7037,30131705,Lápidas,,0,80509,0 -7038,30131706,Losas o baldosas de metal,,0,80509,0 -7039,30141500,Aislamiento térmico,,0,80509,0 -7040,30141501,Burletes,,0,80509,0 -7041,30141503,Aislamiento de espuma,,0,80509,0 -7042,30141505,Revestimiento de aislamiento térmico,,0,80509,0 -7043,30141508,Aislamiento de fibra,Aislamiento de fibra de vidrio,0,80509,0 -7044,30141510,Barrido de puerta,Sello de barrido de puerta,0,80509,0 -7045,30141511,Película de ventana,,0,80509,0 -7046,30141512,Kits de aislamiento térmico,,0,80509,0 -7047,30141513,Ladrillos aislantes térmicos,,0,80509,0 -7048,30141514,Aislación de poliestireno expandido eps,,0,80509,0 -7049,30141515,Aislación de poliestireno extruido xps,,0,80509,0 -7050,30141516,Aislación de lana mineral,,0,80509,0 -7051,30141600,Aislamiento especial,,0,80509,0 -7052,30141601,Aislación acústica,,0,80509,0 -7053,30141603,Escudos térmicas,,0,80509,0 -7054,30141604,Aislación por recubrimiento aerosol,,0,80509,0 -7055,30141605,Material a prueba de incendios,Material de aislamiento resistente al calor,0,80509,0 -7056,30141700,Capas de sellamiento de aislamiento,,0,80509,0 -7057,30141701,Capa sellante de tabla de asfalto,,0,80509,0 -7058,30141702,Capa sellante plástica,,0,80509,0 -7059,30141703,Capa sellante ce caucho,Capa sellante de caucho,0,80509,0 -7060,30151500,Material para tejados y techos,,0,80509,0 -7061,30151501,Techado prearmado,Techos de rollo,0,80509,0 -7062,30151502,Valles de techo,,0,80509,0 -7063,30151503,Textiles de techado,,0,80509,0 -7064,30151505,Membranas para techos,,0,80509,0 -7065,30151507,Huelgos,,0,80509,0 -7066,30151508,Tejas de asfalto,,0,80509,0 -7067,30151509,Armazones,Bloque de soporte de goma,0,80509,0 -7068,30151510,Tejados de pizarra,,0,80509,0 -7069,30151511,Tejas de concreto,,0,80509,0 -7070,30151512,Tejas de ladrillo,,0,80509,0 -7071,30151513,Tejas de cerámica,,0,80509,0 -7072,30151514,Tejas metálicas,,0,80509,0 -7073,30151515,Tejas de madera,,0,80509,0 -7074,30151700,Canalones de tejado y accesorios,,0,80509,0 -7075,30151701,Boquillas de bajantes,,0,80509,0 -7076,30151702,Dinteles para goteo,Tapones de goteo,0,80509,0 -7077,30151703,Canaletas,,0,80509,0 -7078,30151704,Bloques contra salpicaduras,Remates contra el agua,0,80509,0 -7079,30151800,Materiales para revestimiento de paredes y exterior,,0,80509,0 -7080,30151801,Postigos,Contraventana,0,80509,0 -6719,27131615,Generador neumático de vacío,,0,80509,0 -6720,27131700,Cilindros neumáticos y componentes,,0,80509,0 -6721,27131701,Cilindros neumáticos,,0,80509,0 -6722,27131702,Accesorios de vástago de cilindro neumático,,0,80509,0 -6723,27131703,Pistones de cilindro neumático,,0,80509,0 -6724,27131704,Vástagos de pistón de cilindro neumático,,0,80509,0 -6725,27131705,Cuerpo de cilindro neumático,,0,80509,0 -6726,27131706,Capacetes de cilindro neumático,,0,80509,0 -6727,27131707,Kits de reparación de cilindro neumático o sus componentes,,0,80509,0 -6728,27131708,Soportes de montaje para cilindros neumáticos,,0,80509,0 -6729,27131709,Anillos amortiguadores de cilindro neumático,,0,80509,0 -6730,27141000,Herramientas de carrocería,,0,80509,0 -6731,27141001,Herramientas de recorte o moldeo,,0,80509,0 -6732,27141100,Herramientas de suspensión,,0,80509,0 -6733,27141101,Extractor del volante,,0,80509,0 -6734,30101500,Ángulos,,0,80509,0 -6735,30101501,Ángulos de aleación ferrosa,,0,80509,0 -6736,30101502,Ángulos de aleación no ferrosa,,0,80509,0 -6737,30101503,Ángulos de hierro,,0,80509,0 -6738,30101504,Ángulos de acero,,0,80509,0 -6739,30101505,Ángulos de acero inoxidable,,0,80509,0 -6740,30101506,Ángulos de aluminio,,0,80509,0 -6741,30101507,Ángulos de magnesio,,0,80509,0 -6742,30101508,Ángulos de titanio,,0,80509,0 -6743,30101509,Ángulos de cobre,,0,80509,0 -6744,30101510,Ángulos de latón,,0,80509,0 -6745,30101511,Ángulos de bronce,,0,80509,0 -6746,30101512,Ángulos de zinc,,0,80509,0 -6747,30101513,Ángulos de estaño,,0,80509,0 -6748,30101514,Ángulos de plomo,,0,80509,0 -6749,30101515,Ángulos de plástico,,0,80509,0 -6750,30101516,Ángulos de caucho,,0,80509,0 -6751,30101517,Ángulos de metales preciosos,,0,80509,0 -6752,30101700,Vigas,,0,80509,0 -6753,30101701,Vigas de aleación ferrosa,,0,80509,0 -6754,30101702,Vigas de aleación no ferrosa,,0,80509,0 -6755,30101703,Vigas de hierro,,0,80509,0 -6756,30101704,Vigas de acero,,0,80509,0 -6757,30101705,Vigas de acero inoxidable,,0,80509,0 -6758,30101706,Vigas de aluminio,,0,80509,0 -6759,30101707,Vigas de magnesio,,0,80509,0 -6760,30101708,Vigas de titanio,,0,80509,0 -6761,30101709,Vigas de cobre,,0,80509,0 -6762,30101710,Vigas de latón,,0,80509,0 -6763,30101711,Vigas de bronce,,0,80509,0 -6764,30101712,Vigas de zinc,,0,80509,0 -6765,30101713,Vigas de estaño,,0,80509,0 -6766,30101714,Vigas de plomo,,0,80509,0 -6767,30101715,Vigas de plástico,,0,80509,0 -6768,30101716,Vigas de caucho,,0,80509,0 -6769,30101717,Vigas de concreto,,0,80509,0 -6770,30101718,Vigas de metales preciosos,,0,80509,0 -6771,30101719,Vigas de haydita,,0,80509,0 -6772,30101720,Vigas de triplex,,0,80509,0 -6773,30101800,Conductos,,0,80509,0 -6774,30101801,Conductos de aleación ferrosa,,0,80509,0 -6775,30101802,Conductos de aleación no ferrosa,,0,80509,0 -6776,30101803,Conductos de hierro,,0,80509,0 -6777,30101804,Conductos de acero,,0,80509,0 -6778,30101805,Conductos de acero inoxidable,,0,80509,0 -6779,30101806,Conductos de aluminio,,0,80509,0 -6780,30101807,Conductos de magnesio,,0,80509,0 -6781,30101808,Conductos de titanio,,0,80509,0 -6782,30101809,Conductos de cobre,,0,80509,0 -6783,30101810,Conductos de latón,,0,80509,0 -6784,30101811,Conductos de bronce,,0,80509,0 -6785,30101812,Conductos de zinc,,0,80509,0 -6786,30101813,Conductos de estaño,,0,80509,0 -6787,30101814,Conductos de plomo,,0,80509,0 -6788,30101815,Conductos de plástico,,0,80509,0 -6789,30101816,Conductos de caucho,,0,80509,0 -6790,30101817,Conductos de metales preciosos,,0,80509,0 -6791,30101818,Conductos de fibra de vidrio,,0,80509,0 -6792,30102000,Hoja,,0,80509,0 -6793,30102001,Lámina de aleación ferrosa,,0,80509,0 -6794,30102002,Lámina de aleación no ferrosa,,0,80509,0 -6795,30102003,Lámina de hierro,,0,80509,0 -6796,30102004,Lámina de acero,,0,80509,0 -6797,30102005,Lámina de acero inoxidable,,0,80509,0 -6798,30102006,Lámina de aluminio,,0,80509,0 -6799,30102007,Lámina de magnesio,,0,80509,0 -6800,30102008,Lámina de titanio,,0,80509,0 -6801,30102009,Lámina de cobre,,0,80509,0 -6802,30102010,Lámina de latón,,0,80509,0 -6803,30102011,Lámina de bronce,,0,80509,0 -6804,30102012,Lámina de zinc,,0,80509,0 -6805,30102013,Lámina de estaño,,0,80509,0 -6806,30102014,Lámina de plomo,,0,80509,0 -6807,30102015,Lámina de plástico,,0,80509,0 -6808,30102016,Lámina de tántalo,,0,80509,0 -6809,30102200,Plancha,,0,80509,0 -6810,30102201,Placa de aleación ferrosa,,0,80509,0 -6811,30102202,Placa de aleación no ferrosa,,0,80509,0 -6812,30102203,Placa de hierro,,0,80509,0 -6813,30102204,Placa de acero,,0,80509,0 -6814,30102205,Placa de acero inoxidable,,0,80509,0 -6815,30102206,Placa de aluminio,,0,80509,0 -6816,30102207,Placa de magnesio,Placa de aleación de magnesio,0,80509,0 -6817,30102208,Placa de titanio,,0,80509,0 -6818,30102209,Placa de cobre,,0,80509,0 -6819,30102210,Placa de latón,,0,80509,0 -6820,30102211,Placa de bronce,,0,80509,0 -6821,30102212,Placa de zinc,Plancha de zinc para revestimiento,0,80509,0 -6822,30102213,Placa de estaño,,0,80509,0 -6823,30102214,Placa de plomo,Plancha de plomo,0,80509,0 -6824,30102215,Placa de plástico,,0,80509,0 -6825,30102216,Placa de caucho,,0,80509,0 -6826,30102217,Placa de concreto,,0,80509,0 -6827,30102218,Placa de metales preciosos,,0,80509,0 -6828,30102220,Placa de níquel,,0,80509,0 -6829,30102221,Placa de fibra,,0,80509,0 -6830,30102222,Placa de viruta,,0,80509,0 -6831,30102223,Placa de enchape de madera,,0,80509,0 -6832,30102224,Placa de tablero de papel o madera (coreboard),,0,80509,0 -6833,30102225,Placa de corcho,,0,80509,0 -6834,30102226,Placa de butilo,,0,80509,0 -6835,30102227,Placa de tántalo,,0,80509,0 -6836,30102300,Perfiles,,0,80509,0 -6837,30102301,Perfiles de aleación ferrosa,,0,80509,0 -6838,30102302,Perfiles de aleación no ferrosa,,0,80509,0 -6839,30102303,Perfiles de hierro,,0,80509,0 -6840,30102304,Perfiles de acero,,0,80509,0 -6841,30102305,Perfiles de acero inoxidable,,0,80509,0 -6842,30102306,Perfiles de aluminio,,0,80509,0 -6843,30102307,Perfiles de magnesio,Perfiles de aleación de magnesio,0,80509,0 -6844,30102308,Perfiles de titanio,,0,80509,0 -7422,30263601,Barra de acero al carbón serie sae 1000 estirada en frío,,0,80509,0 -7423,30263602,Barra de acero al carbón serie sae 1100 estirada en frío,,0,80509,0 -7424,30263603,Barra de acero al carbón serie sae 1200 estirada en frío,,0,80509,0 -7425,30263604,Barra de acero al carbón serie sae 1500 estirada en frío,,0,80509,0 -7426,30263605,Barra de acero al carbón serie sae 1000 enrollada en caliente,,0,80509,0 -7427,30263606,Barra de acero al carbón serie sae 1100 enrollada en caliente,,0,80509,0 -7428,30263607,Barra de acero al carbón serie sae 1200 enrollada en caliente,,0,80509,0 -7429,30263608,Barra de acero al carbón serie sae 1500 enrollada en caliente,,0,80509,0 -7430,30263700,Barras de aleación de acero,,0,80509,0 -7431,30263701,Barra de aleación de acero serie sae 4000 estirada en frío,,0,80509,0 -7432,30263702,Barra de aleación de acero serie sae 5000 estirada en frío,,0,80509,0 -7433,30263703,Barra de aleación de acero serie sae 6000 estirada en frío,,0,80509,0 -7434,30263704,Barra de aleación de acero serie sae 8000 estirada en frío,,0,80509,0 -7435,30263705,Barra de aleación de acero serie sae 9000 estirada en frío,,0,80509,0 -7436,30263706,Barra de aleación de acero serie sae 4000 enrollada en caliente,,0,80509,0 -7437,30263707,Barra de aleación de acero serie sae 5000 enrollada en caliente,,0,80509,0 -7438,30263708,Barra de aleación de acero serie sae 6000 enrollada en caliente,,0,80509,0 -7439,30263709,Barra de aleación de acero serie sae 8000 enrollada en caliente,,0,80509,0 -7440,30263710,Barra de aleación de acero serie sae 9000 enrollada en caliente,,0,80509,0 -7441,30263800,Barras de acero de herramienta,Barras de acero para herramientas,0,80509,0 -7442,30263801,Barra de acero de herramienta estirada en frío,,0,80509,0 -7443,30263802,Barra de acero de herramienta enrollada en caliente,,0,80509,0 -7444,30263900,Barras de acero especial,,0,80509,0 -7445,30263901,Barra de acero de alta resistencia baja aleación hsla enrollada en caliente,Barra de acero laminado en caliente de alta resistencia y baja aleación HSLA,0,80509,0 -7446,30264000,Láminas de acero al carbón,Hojas de acero al carbono,0,80509,0 -7447,30264001,Lámina de acero al carbón serie sae 1000 enrollada en caliente,Hoja laminada en caliente de acero al carbono SAE 1000,0,80509,0 -7448,30264002,Lámina de acero al carbón serie sae 1100 enrollada en caliente,Hoja laminada en caliente de acero al carbono SAE 1100,0,80509,0 -7449,30264003,Lámina de acero al carbón serie sae 1200 enrollada en caliente,Hoja laminada en caliente de acero al carbono SAE 1200,0,80509,0 -7450,30264004,Lámina de acero al carbón serie sae 1500 enrollada en caliente,Hoja laminada en caliente de acero al carbono SAE 1500,0,80509,0 -7451,30264005,Lámina de acero al carbón electro galvanizada serie sae 1000 enrollada en caliente,Hoja laminada en caliente de acero al carbono SAE 1000 galvanizada,0,80509,0 -7452,30264006,Lámina de acero al carbón electro galvanizada serie sae 1100 enrollada en caliente,Hoja laminada en caliente de acero al carbono SAE 1100 galvanizada,0,80509,0 -7453,30264007,Lámina de acero al carbón electro galvanizada serie sae 1200 enrollada en caliente,Hoja laminada en caliente de acero al carbono SAE 1200 galvanizada,0,80509,0 -7454,30264008,Lámina de acero al carbón electro galvanizada serie sae 1500 enrollada en caliente,Hoja laminada en caliente de acero al carbono SAE 1500 galvanizada,0,80509,0 -7455,30264009,Lámina de acero al carbón inmersión en caliente galvanizada serie sae 1000 enrollada en caliente,Hoja laminada en caliente de acero al carbono SAE 1000 galvanizada en caliente,0,80509,0 -7456,30264010,Lámina de acero al carbón inmersión en caliente galvanizada serie sae 1100 enrollada en caliente,Hoja laminada en caliente de acero al carbono SAE 1100 galvanizada en caliente,0,80509,0 -7457,30264011,Lámina de acero al carbón inmersión en caliente galvanizada serie sae 1200 enrollada en caliente,Hoja laminada en caliente de acero al carbono SAE 1200 galvanizada en caliente,0,80509,0 -7458,30264012,Lámina de acero al carbón inmersión en caliente galvanizada serie sae 1500 enrollada en caliente,Hoja laminada en caliente de acero al carbono SAE 1500 galvanizada en caliente,0,80509,0 -7459,30264013,Lámina de acero al carbón serie sae 1000 enrollada en frío,Hoja laminada en frío de acero al carbono SAE 1000,0,80509,0 -7460,30264014,Lámina de acero al carbón serie sae 1100 enrollada en frío,Hoja laminada en frío de acero al carbono SAE 1100,0,80509,0 -7461,30264015,Lámina de acero al carbón serie sae 1200 enrollada en frío,Hoja laminada en frío de acero al carbono SAE 1200,0,80509,0 -7462,30264016,Lámina de acero al carbón serie sae 1500 enrollada en frío,Hoja laminada en frío de acero al carbono SAE 1500,0,80509,0 -7463,30264017,Lámina de acero al carbón electro galvanizada serie sae 1000 enrollada en frío,Hoja laminada en frío de acero al carbono SAE 1000 galvanizada,0,80509,0 -7464,30264018,Lámina de acero al carbón electro galvanizada serie sae 1100 enrollada en frío,Hoja laminada en frío de acero al carbono SAE 1100 galvanizada,0,80509,0 -7465,30264019,Lámina de acero al carbón electro galvanizada serie sae 1200 enrollada en frío,Hoja laminada en frío de acero al carbono SAE 1200 galvanizada,0,80509,0 -7466,30264020,Lámina de acero al carbón electro galvanizada serie sae 1500 enrollada en frío,Hoja laminada en frío de acero al carbono SAE 1500 galvanizada,0,80509,0 -7467,30264021,Lámina de acero al carbón inmersión en caliente galvanizada serie sae 1000 enrollada en frío,Hoja laminada en frío de acero al carbono SAE 1000 galvanizada en caliente,0,80509,0 -7468,30264022,Lámina de acero al carbón inmersión en caliente galvanizada serie sae 1100 enrollada en frío,Hoja laminada en frío de acero al carbono SAE 1100 galvanizada en caliente,0,80509,0 -7469,30264023,Lámina de acero al carbón inmersión en caliente galvanizada serie sae 1200 enrollada en frío,Hoja laminada en frío de acero al carbono SAE 1200 galvanizada en caliente,0,80509,0 -7470,30264024,Lámina de acero al carbón inmersión en caliente galvanizada serie sae 1500 enrollada en frío,Hoja laminada en frío de acero al carbono SAE 1500 galvanizada en caliente,0,80509,0 -7471,30264200,Láminas de acero de herramienta,,0,80509,0 -7472,30264201,Lámina de acero de alta resistencia baja aleación hsla enrollada en caliente,Hoja laminada en caliente de alta resistencia de acero de baja aleación HSLA,0,80509,0 -7473,30264202,Lámina de acero de alta resistencia baja aleación hsla enrollada en frío,Hoja laminada en frío HSLA de baja resistencia,0,80509,0 -7474,30264203,Lámina de acero aluminizado,Hoja de acero aluminizado,0,80509,0 -7475,30264204,Lámina de acero corrugada,Hoja de acero corrugado,0,80509,0 -7476,30264300,Láminas de acero especial,Hojas de acero especial,0,80509,0 -7477,30264301,Alambre de acero perforado,Rollo de acero perforado,0,80509,0 -7478,30264302,Alambre de acero embebido,Rollo de acero en relieve,0,80509,0 -7479,30264303,Alambre de acero aluminizado,Rollo de acero aluminizado,0,80509,0 -7480,30264304,Alambre de acero inmersión en caliente galvanizado,Rollo de acero galvanizado en caliente,0,80509,0 -7481,30264305,Alambre de acero electro galvanizado,Rollo de acero electro galvanizado,0,80509,0 -7482,30264400,"Lingotes, bandas, palanquillas y alambres de al carbón","Alambre al carbón laminado en caliente sin tratamiento, Alambre de púas, Alambre recocido, Alambrón ,",0,80509,0 -7483,30264401,Alambre de acero al carbón serie sae 1100 enrollada en caliente,Rollo laminado en caliente de acero al carbono SAE 1100,0,80509,0 -7484,30264402,Alambre de acero al carbón serie sae 1200 enrollada en caliente,Rollo laminado en caliente de acero al carbono SAE 1200,0,80509,0 -7485,30264403,Alambre de acero al carbón serie sae 1500 enrollada en caliente,Rollo laminado en caliente de acero al carbono SAE 1500,0,80509,0 -7486,30264404,Banda de acero al carbón serie sae 1100 enrollada en frío,Rollo laminado en frío de acero al carbono SAE 1100,0,80509,0 -7487,30264405,Banda de acero al carbón serie sae 1200 enrollada en frío,Rollo laminado en frío de acero al carbono SAE 1200,0,80509,0 -7488,30264406,Banda de acero al carbón serie sae 1500 enrollada en frío,Rollo laminado en frío de acero al carbono SAE 1500,0,80509,0 -7489,30264407,Banda de acero al carbón serie sae 1100 enrollada en caliente,Rollo laminado en caliente de acero al carbono SAE 1100,0,80509,0 -7490,30264408,Banda de acero al carbón serie sae 1200 enrollada en caliente,Rollo laminado en caliente de acero al carbono SAE 1200,0,80509,0 -7491,30264409,Banda de acero al carbón serie sae 1500 enrollada en caliente,Rollo laminado en caliente de acero al carbono SAE 1500,0,80509,0 -7492,30264410,Palanquilla de acero al carbón enrollado en caliente,Palanquilla laminada en caliente de acero al carbono,0,80509,0 -7493,30264411,Palanquilla de acero al carbón enrollado en frío,Palanquilla laminada en frío de acero al carbono,0,80509,0 -7494,30264412,Lingote de acero al carbón enrollado en caliente,Lingote de acero al carbono laminado en caliente,0,80509,0 -7495,30264413,Lingote de acero al carbón enrollado en frío,Lingote laminado en frío de acero al carbono,0,80509,0 -7496,30264500,Barras de acero inoxidable,,0,80509,0 -7497,30264501,Barra de acero inoxidable serie sae 200 estirada en frío,,0,80509,0 -7498,30264502,Barra de acero inoxidable serie sae 300 estirada en frío,,0,80509,0 -7499,30264503,Barra de acero inoxidable serie sae 400 estirada en frío,,0,80509,0 -7500,30264504,Barra de acero inoxidable serie sae 200 enrollada en caliente,,0,80509,0 -7501,30264505,Barra de acero inoxidable serie sae 300 enrollada en caliente,,0,80509,0 -7502,30264506,Barra de acero inoxidable serie sae 400 enrollada en caliente,,0,80509,0 -7503,30264600,Láminas de acero inoxidable,Hojas de acero inoxidable,0,80509,0 -7504,30264601,Lámina de acero inoxidable serie sae 200 enrollada en caliente,Hojas de acero inoxidable laminada en caliente serie sae 200,0,80509,0 -7505,30264602,Lámina de acero inoxidable serie sae 300 enrollada en caliente,Hojas de acero inoxidable laminada en caliente serie sae 300,0,80509,0 -7506,30264603,Lámina de acero inoxidable serie sae 400 enrollada en caliente,Hojas de acero inoxidable laminada en caliente serie sae 400,0,80509,0 -7507,30264604,Lámina de acero inoxidable serie sae 200 enrollada en frío,Hojas de acero inoxidable laminada en frío SAE 200,0,80509,0 -7508,30264605,Lámina de acero inoxidable serie sae 300 enrollada en frío,Hojas de acero inoxidable laminada en frío SAE 300,0,80509,0 -7509,30264606,Lámina de acero inoxidable serie sae 400 enrollada en frío,Hojas de acero inoxidable laminada en frío SAE 400,0,80509,0 -7510,30264700,Alambre de acero inoxidable,,0,80509,0 -7511,30264701,Alambre de acero inoxidable serie sae 300 enrollada en caliente,Rollo laminado en caliente de acero inoxidable SAE 300,0,80509,0 -7512,30264702,Alambre de acero inoxidable serie sae 400 enrollada en caliente,Rollo laminado en caliente de acero inoxidable SAE 400,0,80509,0 -7513,30264703,Alambre de acero inoxidable serie sae 200 enrollada en frío,Rollo laminada en frío de acero inoxidable SAE 200,0,80509,0 -7514,30264704,Alambre de acero inoxidable serie sae 300 enrollada en frío,Rollo laminada en frío de acero inoxidable SAE 300,0,80509,0 -7515,30264705,Alambre de acero inoxidable serie sae 400 enrollada en frío,Rollo laminada en frío de acero inoxidable SAE 400,0,80509,0 -7516,30264800,Bandas de acero inoxidable,Tiras de acero inoxidable,0,80509,0 -7517,30264801,Banda de acero inoxidable serie sae 200 enrollada en caliente,Tira laminada en caliente de acero inoxidable SAE 200,0,80509,0 -7518,30264802,Banda de acero inoxidable serie sae 300 enrollada en caliente,Tira laminada en caliente de acero inoxidable SAE 300,0,80509,0 -7519,30264803,Banda de acero inoxidable serie sae 400 enrollada en caliente,Tira laminada en caliente de acero inoxidable SAE 400,0,80509,0 -7520,30264804,Banda de acero inoxidable serie sae 200 enrollada en frío,Tira laminada en frío de acero inoxidable SAE 200,0,80509,0 -7521,30264805,Banda de acero inoxidable serie sae 300 enrollada en frío,Tira laminada en frío de acero inoxidable SAE 300,0,80509,0 -7522,30264806,Banda de acero inoxidable serie sae 400 enrollada en frío,Tira laminada en frío de acero inoxidable SAE 400,0,80509,0 -7523,30264900,Barras de aluminio,,0,80509,0 -7524,30264901,Barra de aluminio serie sae 1000 estirada en frío,,0,80509,0 -7525,30264902,Barra de aluminio serie sae 1000 enrollada en caliente,,0,80509,0 -7526,30264903,Barra de aluminio serie sae 2000 estirada en frío,,0,80509,0 -7527,30264904,Barra de aluminio serie sae 2000 enrollada en caliente,,0,80509,0 -7528,30264905,Barra de aluminio serie sae 3000 estirada en frío,,0,80509,0 -7529,30264906,Barra de aluminio serie sae 3000 enrollada en caliente,,0,80509,0 -7530,30264907,Barra de aluminio serie sae 4000 estirada en frío,,0,80509,0 -7531,30264908,Barra de aluminio serie sae 4000 enrollada en caliente,,0,80509,0 -7532,30264909,Barra de aluminio serie sae 5000 estirada en frío,,0,80509,0 -7533,30264910,Barra de aluminio serie sae 5000 enrollada en caliente,,0,80509,0 -7534,30264911,Barra de aluminio serie sae 6000 estirada en frío,,0,80509,0 -7535,30264912,Barra de aluminio serie sae 6000 enrollada en caliente,,0,80509,0 -7536,30265000,Láminas de aluminio,Hojas de aluminio,0,80509,0 -7537,30265001,Lámina de aluminio serie sae 1000 enrollada en frío,Hoja de aluminio laminada en frío SAE 1000,0,80509,0 -7538,30265002,Lámina de aluminio serie sae 1000 enrollada en caliente,Hoja de aluminio laminada en caliente SAE 1000,0,80509,0 -7539,30265003,Lámina de aluminio serie sae 2000 enrollada en frío,Hoja de aluminio laminada en frío SAE 2000,0,80509,0 -7540,30265004,Lámina de aluminio serie sae 2000 enrollada en caliente,Hoja de aluminio laminada en caliente SAE 2000,0,80509,0 -7541,30265005,Lámina de aluminio serie sae 3000 enrollada en frío,Hoja de aluminio laminada en frío SAE 3000,0,80509,0 -7542,30265006,Lámina de aluminio serie sae 3000 enrollada en caliente,Hoja de aluminio laminada en caliente SAE 3000,0,80509,0 -7543,30265007,Lámina de aluminio serie sae 4000 enrollada en frío,Hoja de aluminio laminada en frío SAE 4000,0,80509,0 -7544,30265008,Lámina de aluminio serie sae 4000 enrollada en caliente,Hoja de aluminio laminada en caliente SAE 4000,0,80509,0 -7545,30265009,Lámina de aluminio serie sae 5000 enrollada en frío,Hoja de aluminio laminada en frío SAE 5000,0,80509,0 -7546,30265010,Lámina de aluminio serie sae 5000 enrollada en caliente,Hoja de aluminio laminada en caliente SAE 5000,0,80509,0 -7547,30265011,Lámina de aluminio serie sae 6000 enrollada en frío,Hoja de aluminio laminada en frío SAE 6000,0,80509,0 -7548,30265012,Lámina de aluminio serie sae 6000 enrollada en caliente,Hoja de aluminio corrugado,0,80509,0 -7549,30265100,Alambre de aluminio,,0,80509,0 -7550,30265101,Alambre de aluminio serie sae 1000 enrollada en caliente,Rollo de aluminio laminado en caliente serie SAE 1000,0,80509,0 -7551,30265102,Alambre de aluminio serie sae 2000 enrollada en caliente,Rollo de aluminio laminado en caliente serie SAE 2000,0,80509,0 -7552,30265103,Alambre de aluminio serie sae 3000 enrollada en caliente,Rollo de aluminio laminado en caliente serie SAE 3000,0,80509,0 -7553,30265104,Alambre de aluminio serie sae 4000 enrollada en caliente,Rollo de aluminio laminado en caliente serie SAE 4000,0,80509,0 -7554,30265105,Alambre de aluminio serie sae 5000 enrollada en caliente,Rollo de aluminio laminado en caliente serie SAE 5000,0,80509,0 -7555,30265106,Alambre de aluminio serie sae 6000 enrollada en caliente,Rollo de aluminio laminado en caliente serie SAE 6000,0,80509,0 -7556,30265200,Bandas y lingotes de aluminio,Tiras y lingotes de aluminio,0,80509,0 -7557,30265201,Banda de aluminio,Tiras de aluminio,0,80509,0 -7558,30265202,Lingote de aluminio,,0,80509,0 -7559,30265300,Barras de hierro,,0,80509,0 -7560,30265301,Barra fundida de hierro dúctil,,0,80509,0 -7561,30265302,Barra fundida de hierro gris,,0,80509,0 -7562,30265303,Barra fundida de hierro blanco,,0,80509,0 -7563,30265304,Barra fundida de hierro maleable,,0,80509,0 -7564,30265400,Láminas de hierro,,0,80509,0 -7565,30265401,Lámina de hierro dúctil enrollada en caliente,,0,80509,0 -7566,30265403,Lámina de hierro blanco enrollada en caliente,,0,80509,0 -7567,30265404,Lámina de hierro maleable enrollada en caliente,,0,80509,0 -7568,30265602,Lámina de aleación ferrosa,,0,80509,0 -7569,30265700,"Lingotes, bandas, palanquillas y alambres de aleaciones ferrosas","Lingotes, tiras, palanquillas y rollos de aleaciones ferrosas",0,80509,0 -7570,30265701,Alambre de aleación ferrosa,Rollo de aleación ferrosa,0,80509,0 -7571,30265702,Banda de aleación ferrosa,Tira de aleación ferrosa,0,80509,0 -7572,30265703,Palanquilla de aleación ferrosa,,0,80509,0 -7573,30265704,Lingote de extrusión de aleación ferrosa,,0,80509,0 -7574,30265800,Barras y láminas plásticas,Barras y hojas de plástico,0,80509,0 -7575,30265801,Barra de plástico,,0,80509,0 -7576,30265802,Lámina de plástico,Hoja de plástico,0,80509,0 -7577,30265803,Lámina corrugada de plástico,Hoja de plástico corrugado,0,80509,0 -7578,30265900,Alambre y bandas de plástico,,0,80509,0 -7579,30265901,Alambre de plástico,Rollo de plástico,0,80509,0 -7580,30265902,Banda de plástico,Tira de plástico,0,80509,0 -7581,30266200,"Lingotes, bandas, palanquillas y alambres de metales preciosos y metales especiales",,0,80509,0 -7582,30266201,Alambre de grafito,Rollo de grafito,0,80509,0 -7583,30266202,Alambre de aleación no ferrosa,Rollo de aleación no ferrosa,0,80509,0 -7584,30266203,Alambre de metal precioso,Rollo de metal precioso,0,80509,0 -7585,30266204,Banda de aleación no ferrosa,Tira de aleación no ferrosa,0,80509,0 -7586,30266205,Palanquilla de aleación no ferrosa,,0,80509,0 -7587,30266206,Palanquilla de metal precioso,,0,80509,0 -7588,30266207,Lingote de metal precioso,,0,80509,0 -7589,30266208,Lingote de antimonio,,0,80509,0 -7590,30266209,Lingote de cadmio,,0,80509,0 -7591,30266210,Lingote de circonio,,0,80509,0 -7592,30266211,Lingote de cobalto,,0,80509,0 -7593,30266212,Lingote de molibdeno,,0,80509,0 -7594,30266213,lingote de arsénico,,0,80509,0 -7595,30266214,Lingote de bismuto,,0,80509,0 -7596,30266215,Lingote de indio,,0,80509,0 -7597,30266216,Lingote de extrusión de aleación no ferrosa,,0,80509,0 -7598,30266300,Materiales estructurales de madera,,0,80509,0 -7599,30266301,Barra de madera,,0,80509,0 -7600,30266400,Barras y láminas de materiales especiales,,0,80509,0 -7601,30266401,Barra de caucho,,0,80509,0 -7602,30266402,Lámina de caucho,Hoja de caucho,0,80509,0 -7603,30266403,Lámina de caucho espuma,Hoja de caucho espuma,0,80509,0 -7604,30266404,Lámina de caucho y corcho,Hoja de caucho y corcho,0,80509,0 -7605,30266405,Lámina de fibra comprimida,Hoja de fibra comprimida,0,80509,0 -7606,30266406,Lámina de fibra comprimida con metal insertado,Hoja de fibra comprimida con metal insertado,0,80509,0 -7607,30266407,Lámina de fibra y caucho,Hoja de fibra y caucho,0,80509,0 -7608,30266408,Lámina de asbesto,Hoja de asbesto,0,80509,0 -7609,30266410,Barra de tántalo,Barra de Tantalio,0,80509,0 -7610,30266500,"Lingotes, bandas, palanquillas y alambres de materiales especiales",,0,80509,0 -7611,30266501,Banda de caucho,Tira de caucho,0,80509,0 -7612,30266502,Alambre de fibra comprimida,Rollo de fibra comprimida,0,80509,0 -7613,30266503,Alambre de fibra y caucho,Rollo de fibra y caucho,0,80509,0 -7614,30266504,Banda de tántalo,Tira de Tantalio,0,80509,0 -7615,30266700,Trasmisiones,,0,80509,0 -7616,30266701,Transmisión de acero,"Barra de transmisión de acero, Eje de transmisión de acero",0,80509,0 -7617,30266702,Transmisión de acero inoxidable,"Barra de transmisión de acero inoxidable, Eje de transmisión de acero inoxidable",0,80509,0 -7618,31101600,Moldeos en arena,,0,80509,0 -7619,31101601,Fundición en arena de aleación no ferrosa,,0,80509,0 -7620,31101602,Fundición en arena de aleación ferrosa,,0,80509,0 -7621,31101603,Fundición en arena de acero,,0,80509,0 -7622,31101604,Fundición en arena de acero inoxidable,,0,80509,0 -7623,31101606,Fundición en arena de aluminio,,0,80509,0 -7624,31101607,Fundición en arena de magnesio,,0,80509,0 -7625,31101608,Fundición en arena de titanio,,0,80509,0 -7626,31101609,Fundición en arena de berilio,,0,80509,0 -7627,31101610,Fundición en arena de cobre,,0,80509,0 -7628,31101611,Fundición en arena de latón,,0,80509,0 -7629,31101612,Fundición en arena de bronce,,0,80509,0 -7630,31101613,Fundición en arena de zinc,,0,80509,0 -7631,31101614,Fundición en arena de estaño,,0,80509,0 -7632,31101615,Fundición en arena de plomo,,0,80509,0 -7633,31101616,Fundición en arena de metal precioso,,0,80509,0 -7634,31101617,Fundición en arena de hierro gris,,0,80509,0 -7635,31101700,Piezas fundidas en molde permanente,,0,80509,0 -7636,31101701,Objetos de aleación no ferrosa fundidos en molde fijo,Piezas de aleación no ferrosa fundidas en molde permanente,0,80509,0 -7637,31101702,Objetos de aleación ferrosa fundidos en molde fijo,Piezas de aleación ferrosa fundidas en molde permanente,0,80509,0 -7638,31101703,Objetos de acero fundidos en molde fijo,Piezas de acero fundidas en molde permanente,0,80509,0 -7639,31101704,Objetos de acero inoxidable fundidos en molde fijo,Piezas de acero inoxidable fundidas en molde permanente,0,80509,0 -7640,31101705,Objetos de hierro fundidos en molde fijo,Piezas de hierro fundidas en molde permanente,0,80509,0 -7641,31101706,Objetos de aluminio fundidos en molde fijo,Piezas de aluminio fundidas en molde permanente,0,80509,0 -7642,31101707,Objetos de magnesio fundidos en molde fijo,Piezas de magnesio fundidas en molde permanente,0,80509,0 -7643,31101708,Objetos de titanio fundidos en molde fijo,Piezas de titanio fundidas en molde permanente,0,80509,0 -7644,31101709,Objetos de berilio fundidos en molde fijo,Piezas de berilio fundidas en molde permanente,0,80509,0 -7645,31101710,Objetos de cobre fundidos en molde fijo,Piezas de cobre fundidas en molde permanente,0,80509,0 -7646,31101711,Objetos de latón fundidos en molde fijo,Piezas de latón fundidas en molde permanente,0,80509,0 -7647,31101712,Objetos de bronce fundidos en molde fijo,Piezas de bronce fundidas en molde permanente,0,80509,0 -7648,31101713,Objetos de zinc fundidos en molde fijo,Piezas de zinc fundidas en molde permanente,0,80509,0 -7649,31101714,Objetos de estaño fundidos en molde fijo,Piezas de estaño fundidas en molde permanente,0,80509,0 -7650,31101715,Objetos de plomo fundidos en molde fijo,Piezas de plomo fundidas en molde permanente,0,80509,0 -7651,31101716,Objetos de metal precioso fundidos en molde fijo,Piezas de metal precioso fundidas en molde permanente,0,80509,0 -7652,31101800,Piezas fundidas por moldeo en cáscara,Piezas fundidas en moldes de fundición de cáscara,0,80509,0 -7653,31101801,Objetos de aleación no ferrosa fundidos por moldeo en cáscara,Piezas de aleación no ferrosa fundidas en molde de cáscara,0,80509,0 -7654,31101802,Objetos de aleación ferrosa fundidos por moldeo en cáscara,Piezas de aleación ferrosa fundidas en molde de cáscara,0,80509,0 -7655,31101803,Objetos de acero fundidos por moldeo en cáscara,Piezas de acero fundidas en molde de cáscara,0,80509,0 -7656,31101804,Objetos de acero inoxidable fundidos por moldeo en cáscara,Piezas de acero inoxidable fundidas en molde de cáscara,0,80509,0 -7657,31101806,Objetos de aluminio fundidos por moldeo en cáscara,Piezas de aluminio fundidas en molde de cáscara,0,80509,0 -7658,31101807,Objetos de magnesio fundidos por moldeo en cáscara,Piezas de magnesio fundidas en molde de cáscara,0,80509,0 -7659,31101808,Objetos de titanio fundidos por moldeo en cáscara,Piezas de titanio fundidas en molde de cáscara,0,80509,0 -7660,31101809,Objetos de berilio fundidos por moldeo en cáscara,Piezas de berilio fundidas en molde de cáscara,0,80509,0 -7661,31101810,Objetos de cobre fundidos por moldeo en cáscara,Piezas de cobre fundidas en molde de cáscara,0,80509,0 -7662,31101811,Objetos de latón fundidos por moldeo en cáscara,Piezas de latón fundidas en molde de cáscara,0,80509,0 -7663,31101812,Objetos de bronce fundidos por moldeo en cáscara,Piezas de bronce fundidas en molde de cáscara,0,80509,0 -7664,31101813,Objetos de zinc fundidos por moldeo en cáscara,Piezas de zinc fundidas en molde de cáscara,0,80509,0 -7665,31101814,Objetos de estaño fundidos por moldeo en cáscara,Piezas de estaño fundidas en molde de cáscara,0,80509,0 -7666,31101815,Objetos de plomo fundidos por moldeo en cáscara,Piezas de plomo fundidas en molde de cáscara,0,80509,0 -7667,31101816,Objetos de metal precioso fundidos por moldeo en cáscara,Piezas de metal precioso fundidas en molde de cáscara,0,80509,0 -7668,31101817,Objetos de hierro dúctil fundidos por moldeo en cáscara,Piezas de hierro fundidas en molde de cáscara,0,80509,0 -7669,31101900,Piezas fundidas a la cera perdida,,0,80509,0 -7670,31101901,Objetos de aleación no ferrosa fundidos a la cera perdida,Piezas de aleación no ferrosa fundidas a la cera perdida,0,80509,0 -7671,31101902,Objetos de aleación ferrosa fundidos a la cera perdida,Piezas de aleación ferrosa fundidas a la cera perdida,0,80509,0 -7672,31101903,Objetos de acero fundidos a la cera perdida,Piezas de acero fundidas a la cera perdida,0,80509,0 -7673,31101904,Objetos de acero inoxidable fundidos a la cera perdida,Piezas de acero inoxidable fundidas a la cera perdida,0,80509,0 -7674,31101905,Objetos de hierro fundidos a la cera perdida,Piezas de hierro fundidas a la cera perdida,0,80509,0 -7675,31101906,Objetos de aluminio fundidos a la cera perdida,Piezas de aluminio fundidas a la cera perdida,0,80509,0 -7676,31101907,Objetos de magnesio fundidos a la cera perdida,Piezas de magnesio fundidas a la cera perdida,0,80509,0 -7677,31101908,Objetos de zinc fundidos a la cera perdida,Piezas de zinc fundidas a la cera perdida,0,80509,0 -7678,31101909,Objetos de estaño fundidos a la cera perdida,Piezas de estaño fundidas a la cera perdida,0,80509,0 -7679,31101910,Objetos de plomo fundidos a la cera perdida,Piezas de plomo fundidas a la cera perdida,0,80509,0 -7680,31101911,Objetos de metal precioso fundidos a la cera perdida,Piezas de metal precioso fundidas a la cera perdida,0,80509,0 -7681,31101912,Objetos de titanio fundidos a la cera perdida,Piezas de titanio fundidas a la cera perdida,0,80509,0 -7682,31101914,Objetos de aleación de acero fundidos a la cera perdida,Piezas de aleación de acero fundidas a la cera perdida,0,80509,0 -7683,31102000,Piezas de fundición centrífuga,Fundiciones centrífugas,0,80509,0 -7684,31102001,Objetos de fundición centrífuga de aleación no ferrosa,Piezas de aleación no ferrosa hechas mediante fundición centrifuga,0,80509,0 -7685,31102002,Objetos de fundición centrífuga de aleación ferrosa,Piezas de aleación ferrosa hechas mediante fundición centrifuga,0,80509,0 -7686,31102003,Objetos de fundición centrífuga de acero,Piezas de acero hechas mediante fundición centrifuga,0,80509,0 -7687,31102004,Objetos de fundición centrífuga de acero inoxidable,Piezas de acero inoxidable hechas mediante fundición centrifuga,0,80509,0 -7688,31102005,Objetos de fundición centrífuga de hierro,Piezas de hierro hechas mediante fundición centrifuga,0,80509,0 -7689,31102006,Objetos de fundición centrífuga de aluminio,Piezas de aluminio hechas mediante fundición centrifuga,0,80509,0 -7690,31102007,Objetos de fundición centrífuga de magnesio,Piezas de magnesio hechas mediante fundición centrifuga,0,80509,0 -7691,31102008,Objetos de fundición centrífuga de titanio,Piezas de titanio hechas mediante fundición centrifuga,0,80509,0 -7692,31102009,Objetos de fundición centrífuga de berilio,Piezas de berilio hechas mediante fundición centrifuga,0,80509,0 -7693,31102010,Objetos de fundición centrífuga de cobre,Piezas de cobre hechas mediante fundición centrifuga,0,80509,0 -7694,31102011,Objetos de fundición centrífuga de latón,Piezas de latón hechas mediante fundición centrifuga,0,80509,0 -7695,31102012,Objetos de fundición centrífuga de bronce,Piezas de bronce hechas mediante fundición centrifuga,0,80509,0 -7696,31102013,Objetos de fundición centrífuga de zinc,Piezas de zinc hechas mediante fundición centrifuga,0,80509,0 -7697,31102014,Objetos de fundición centrífuga de estaño,Piezas de estaño hechas mediante fundición centrifuga,0,80509,0 -7698,31102015,Objetos de fundición centrífuga de plomo,Piezas de plomo hechas mediante fundición centrifuga,0,80509,0 -7699,31102016,Objetos de fundición centrífuga de metal precioso,Piezas de metal precioso hechas mediante fundición centrifuga,0,80509,0 -7700,31102200,Piezas fundidas en molde de grafito,Fundiciones en molde de grafito,0,80509,0 -7701,31102201,Objetos de aleación no ferrosa fundidos en molde de grafito,Piezas de aleación no ferrosa fundidas en molde de grafito,0,80509,0 -7702,31102202,Objetos de aleación ferrosa fundidos en molde de grafito,Piezas de aleación ferrosa fundidas en molde de grafito,0,80509,0 -7703,31102203,Objetos de acero fundidos en molde de grafito,Piezas de acero fundidas en molde de grafito,0,80509,0 -7704,31102204,Objetos de acero inoxidable fundidos en molde de grafito,Piezas de acero inoxidable fundidas en molde de grafito,0,80509,0 -7705,31102205,Objetos de hierro fundidos en molde de grafito,Piezas de hierro fundidas en molde de grafito,0,80509,0 -7706,31102206,Objetos de aluminio fundidos en molde de grafito,Piezas de aluminio fundidas en molde de grafito,0,80509,0 -7707,31102207,Objetos de magnesio fundidos en molde de grafito,Piezas de magnesio fundidas en molde de grafito,0,80509,0 -7708,31102208,Objetos de titanio fundidos en molde de grafito,Piezas de titanio fundidas en molde de grafito,0,80509,0 -7709,31102209,Objetos de berilio fundidos en molde de grafito,Piezas de berilio fundidas en molde de grafito,0,80509,0 -7710,31102210,Objetos de cobre fundidos en molde de grafito,Piezas de cobre fundidas en molde de grafito,0,80509,0 -7711,31102211,Objetos de latón fundidos en molde de grafito,Piezas de latón fundidas en molde de grafito,0,80509,0 -7712,31102212,Objetos de bronce fundidos en molde de grafito,Piezas de bronce fundidas en molde de grafito,0,80509,0 -7713,31102213,Objetos de zinc fundidos en molde de grafito,Piezas de zinc fundidas en molde de grafito,0,80509,0 -7714,31102214,Objetos de estaño fundidos en molde de grafito,Piezas de estaño fundidas en molde de grafito,0,80509,0 -7715,31102215,Objetos de plomo fundidos en molde de grafito,Piezas de plomo fundidas en molde de grafito,0,80509,0 -7716,31102216,Objetos de metal precioso fundidos en molde de grafito,Piezas de metal precioso fundidas en molde de grafito,0,80509,0 -7717,31102300,Piezas fundidas en molde de yeso,,0,80509,0 -7718,31102301,Objetos de aleación no ferrosa fundidos en molde de yeso,Piezas de aleación no ferrosa fundidas en molde de yeso,0,80509,0 -7719,31102302,Objetos de aleación ferrosa fundidos en molde de yeso,Piezas de aleación ferrosa fundidas en molde de yeso,0,80509,0 -7720,31102303,Objetos de acero fundidos en molde de yeso,Piezas de acero fundidas en molde de yeso,0,80509,0 -7721,31102304,Objetos de acero inoxidable fundidos en molde de yeso,Piezas de acero inoxidable fundidas en molde de yeso,0,80509,0 -7722,31102305,Objetos de hierro fundidos en molde de yeso,Piezas de hierro fundidas en molde de yeso,0,80509,0 -7723,31102306,Objetos de aluminio fundidos en molde de yeso,Piezas de aluminio fundidas en molde de yeso,0,80509,0 -7724,31102307,Objetos de magnesio fundidos en molde de yeso,Piezas de magnesio fundidas en molde de yeso,0,80509,0 -7725,31102308,Objetos de titanio fundidos en molde de yeso,Piezas de titanio fundidas en molde de yeso,0,80509,0 -7726,31102309,Objetos de berilio fundidos en molde de yeso,Piezas de berilio fundidas en molde de yeso,0,80509,0 -7727,31102310,Objetos de cobre fundidos en molde de yeso,Piezas de cobre fundidas en molde de yeso,0,80509,0 -7728,31102311,Objetos de latón fundidos en molde de yeso,Piezas de latón fundidas en molde de yeso,0,80509,0 -7729,31102312,Objetos de bronce fundidos en molde de yeso,Piezas de bronce fundidas en molde de yeso,0,80509,0 -7730,31102313,Objetos de zinc fundidos en molde de yeso,Piezas de zinc fundidas en molde de yeso,0,80509,0 -7731,31102314,Objetos de estaño fundidos en molde de yeso,Piezas de estaño fundidas en molde de yeso,0,80509,0 -7732,31102315,Objetos de plomo fundidos en molde de yeso,Piezas de plomo fundidas en molde de yeso,0,80509,0 -7733,31102316,Objetos de metal precioso fundidos en molde de yeso,Piezas de metal precioso fundidas en molde de yeso,0,80509,0 -7734,31102400,Fundiciones en V,Fundición en proceso V,0,80509,0 -7735,31102401,Objetos de aleación no ferrosa fundidos por proceso en v,Piezas de aleación no ferrosa fundidas por proceso V,0,80509,0 -7736,31102402,Objetos de aleación ferrosa fundidos por proceso en v,Piezas de aleación ferrosa fundidas por proceso V,0,80509,0 -7737,31102403,Objetos de acero fundidos por proceso en v,Piezas de acero fundidas por proceso V,0,80509,0 -7738,31102404,Objetos de acero inoxidable fundidos por proceso en v,Piezas de acero inoxidable por proceso V,0,80509,0 -7739,31102405,Objetos de hierro fundidos por proceso en v,Piezas de hierro fundidas por proceso V,0,80509,0 -7740,31102406,Objetos de aluminio fundidos por proceso en v,Piezas de aluminio fundidas por proceso V,0,80509,0 -7741,31102407,Objetos de magnesio fundidos por proceso en v,Piezas de magnesio fundidas por proceso V,0,80509,0 -7742,31102408,Objetos de titanio fundidos por proceso en v,Piezas de titanio fundidas por proceso V,0,80509,0 -7743,31102409,Objetos de berilio fundidos por proceso en v,Piezas de berilio fundidas por proceso V,0,80509,0 -7744,31102410,Objetos de cobre fundidos por proceso en v,Piezas de cobre fundidas por proceso V,0,80509,0 -7745,31102411,Objetos de latón fundidos por proceso en v,Piezas de latón fundidas por proceso V,0,80509,0 -7746,31102412,Objetos de bronce fundidos por proceso en v,Piezas de bronce fundidas por proceso V,0,80509,0 -7747,31102413,Objetos de zinc fundidos por proceso en v,Piezas de zinc fundidas por proceso V,0,80509,0 -7748,31102414,Objetos de estaño fundidos por proceso en v,Piezas de estaño fundidas por proceso V,0,80509,0 -7749,31102415,Objetos de plomo fundidos por proceso en v,Piezas de plomo fundidas por proceso V,0,80509,0 -7750,31102416,Objetos de metal precioso fundidos por proceso en v,Piezas de metal precioso fundidas por proceso V,0,80509,0 -7751,31102600,Fundiciones con troquel y ensambles de fundiciones de alta presión,Fundiciones en molde y ensambles de fundiciones de alta presión,0,80509,0 -7752,31102601,Objetos de aluminio de alta presión fundidos en molde fijo,Piezas de aluminio fundidos en molde a alta presión,0,80509,0 -7753,31102602,Objetos de aluminio de alta presión moldeados con troquel,Piezas de zinc fundidos en molde a alta presión,0,80509,0 -7754,31102603,Objetos de zinc de alta presión moldeados con troquel,Piezas de magnesio fundidos en molde a alta presión,0,80509,0 -7755,31102900,Moldeos con troquel y ensambles de fundiciones permanentes a gravedad,Fundiciones en molde y ensambles de fundiciones en moldes permanentes a gravedad,0,80509,0 -7756,31102901,Objetos de aluminio moldeados por gravedad permanente,Piezas de aluminio fundidas en moldes permanentes a gravedad,0,80509,0 -7757,31111500,Extrusiones de perfiles,,0,80509,0 -7758,31111501,Extrusiones de perfiles de aluminio,,0,80509,0 -7759,31111502,Extrusiones de perfiles de berilio,,0,80509,0 -7760,31111503,Extrusiones de perfiles de latón,,0,80509,0 -7761,31111504,Extrusiones de perfiles de bronce,,0,80509,0 -7762,31111505,Extrusiones de perfiles de cobre,,0,80509,0 -7763,31111506,Extrusiones de perfiles de aleación ferrosa,,0,80509,0 -7764,31111507,Extrusiones de perfiles de plomo,,0,80509,0 -7765,31111508,Extrusiones de perfiles de magnesio,,0,80509,0 -7766,31111509,Extrusiones de perfiles de aleación no ferrosa,,0,80509,0 -7767,31111510,Extrusiones de perfiles de plástico,,0,80509,0 -7768,31111511,Extrusiones de perfiles de metal precioso,,0,80509,0 -7769,31111512,Extrusiones de perfiles de caucho,,0,80509,0 -7770,31111513,Extrusiones de perfiles de acero inoxidable,,0,80509,0 -7771,31111514,Extrusiones de perfiles de acero,,0,80509,0 -7772,31111515,Extrusiones de perfiles de estaño,,0,80509,0 -7773,31111516,Extrusiones de perfiles de titanio,,0,80509,0 -7774,31111517,Extrusiones de perfiles de zinc,,0,80509,0 -7775,31111600,Extrusiones por percusión,Extrusiones por impacto,0,80509,0 -7776,31111601,Extrusiones por impacto de aluminio,Extrusiones de perfiles de aluminio por impacto,0,80509,0 -7777,31111602,Extrusiones por impacto de berilio,Extrusiones de perfiles de berilio por impacto,0,80509,0 -7778,31111603,Extrusiones por impacto de latón,Extrusiones de perfiles de latón por impacto,0,80509,0 -7779,31111604,Extrusiones por impacto de bronce,Extrusiones de perfiles de bronce por impacto,0,80509,0 -7780,31111605,Extrusiones por impacto de cobre,Extrusiones de perfiles de cobre por impacto,0,80509,0 -7781,31111606,Extrusiones por impacto de aleación ferrosa,Extrusiones de perfiles de aleación ferrosa por impacto,0,80509,0 -7782,31111607,Extrusiones por impacto de plomo,Extrusiones de perfiles de plomo por impacto,0,80509,0 -7783,31111608,Extrusiones por impacto de magnesio,Extrusiones de perfiles de magnesio por impacto,0,80509,0 -7784,31111609,Extrusiones por impacto de aleación no ferrosa,Extrusiones de perfiles de aleación no ferrosa por impacto,0,80509,0 -7785,31111610,Extrusiones por impacto de plástico,Extrusiones de perfiles de plástico por impacto,0,80509,0 -7786,31111611,Extrusiones por impacto de metal precioso,Extrusiones de perfiles de metal precioso por impacto,0,80509,0 -7787,31111612,Extrusiones por impacto de caucho,Extrusiones de perfiles de caucho por impacto,0,80509,0 -7788,31111613,Extrusiones por impacto de acero inoxidable,Extrusiones de perfiles de acero inoxidable por impacto,0,80509,0 -7789,31111614,Extrusiones por impacto de acero,Extrusiones de perfiles de acero por impacto,0,80509,0 -7790,31111615,Extrusiones por impacto de estaño,Extrusiones de perfiles de estaño por impacto,0,80509,0 -7791,31111616,Extrusiones por impacto de titanio,Extrusiones de perfiles de titanio por impacto,0,80509,0 -7792,31111617,Extrusiones por impacto de zinc,Extrusiones de perfiles de zinc por impacto,0,80509,0 -7793,31121000,Piezas fundidas mecanizadas de proceso v,Piezas fundidas por proceso v mecanizadas,0,80509,0 -7794,31121001,Objetos fundidos maquinados por proceso v de aleaciones no ferrosas,Piezas de aleaciones no ferrosas fundidas por proceso v mecanizadas,0,80509,0 -7795,31121002,Objetos fundidos maquinados por proceso v de aleaciones ferrosas,Piezas de aleaciones ferrosas fundidas por proceso v mecanizadas,0,80509,0 -7796,31121003,Objetos fundidos maquinados por proceso v de acero,Piezas de acero fundidas por proceso v mecanizadas,0,80509,0 -7797,31121004,Objetos fundidos maquinados por proceso v de acero inoxidable,Piezas de acero inoxidable fundidas por proceso v mecanizadas,0,80509,0 -7798,31121005,Objetos fundidos maquinados por proceso v de hierro,Piezas de hierro fundidas por proceso v mecanizadas,0,80509,0 -7799,31121006,Objetos fundidos maquinados por proceso v de aluminio,Piezas de aluminio fundidas por proceso v mecanizadas,0,80509,0 -7800,31121007,Objetos fundidos maquinados por proceso v de magnesio,Piezas de magnesio fundidas por proceso v mecanizadas,0,80509,0 -7801,31121008,Objetos fundidos maquinados por proceso v de titanio,Piezas de titanio fundidas por proceso v mecanizadas,0,80509,0 -7802,31121009,Objetos fundidos maquinados por proceso v de berilio,Piezas de berilio fundidas por proceso v mecanizadas,0,80509,0 -7803,31121010,Objetos fundidos maquinados por proceso v de cobre,Piezas de cobre fundidas por proceso v mecanizadas,0,80509,0 -7804,31121011,Objetos fundidos maquinados por proceso v de latón,Piezas de latón fundidas por proceso v mecanizadas,0,80509,0 -7805,31121012,Objetos fundidos maquinados por proceso v de bronce,Piezas de bronce fundidas por proceso v mecanizadas,0,80509,0 -7806,31121013,Objetos fundidos maquinados por proceso v de zinc,Piezas de zinc fundidas por proceso v mecanizadas,0,80509,0 -7807,31121014,Objetos fundidos maquinados por proceso v de estaño,Piezas de estaño fundidas por proceso v mecanizadas,0,80509,0 -7808,31121015,Objetos fundidos maquinados por proceso v de plomo,Piezas de plomo fundidas por proceso v mecanizadas,0,80509,0 -7809,31121016,Objetos fundidos maquinados por proceso v de metal precioso,Piezas de metal precioso fundidas por proceso v mecanizadas,0,80509,0 -7810,31121017,Objetos fundidos maquinados por proceso v de compuestos,Piezas de compuestos fundidas por proceso v mecanizadas,0,80509,0 -7811,31121018,Objetos fundidos maquinados por proceso v de aleación de níquel,Piezas de aleación de níquel fundidas por proceso v mecanizadas,0,80509,0 -7812,31121019,Objetos fundidos maquinados por proceso v no metálico,Piezas no metálicas fundidas por proceso v mecanizadas,0,80509,0 -7813,31121100,Fundiciones a troquel mecanizadas,Fundiciones en molde mecanizadas,0,80509,0 -7814,31121101,Objetos fundidos maquinados con troquel de aleaciones no ferrosas,Piezas de aluminio fundidas en molde mecanizadas,0,80509,0 -7815,31121102,Objetos fundidos maquinados con troquel de aleaciones ferrosas,Piezas de aleaciones ferrosas fundidas en molde mecanizadas,0,80509,0 -7816,31121103,Objetos fundidos maquinados con troquel de acero,Piezas de hierro fundidas en molde mecanizadas,0,80509,0 -7817,31121104,Objetos fundidos maquinados con troquel de acero inoxidable,Piezas de aleación no ferrosa fundidas en molde mecanizadas,0,80509,0 -7818,31121105,Objetos fundidos maquinados con troquel de hierro,Piezas de acero inoxidable fundidas en molde mecanizadas,0,80509,0 -7819,31121106,Objetos fundidos maquinados con troquel de aluminio,Piezas de acero fundidas en molde mecanizadas,0,80509,0 -7820,31121107,Objetos fundidos maquinados con troquel de magnesio,Piezas de magnesio fundidas en molde mecanizadas,0,80509,0 -7821,31121108,Objetos fundidos maquinados con troquel de titanio,Piezas de zinc fundidas en molde mecanizadas,0,80509,0 -7822,31121109,Objetos fundidos maquinados con troquel de berilio,Piezas de estaño fundidas en molde mecanizadas,0,80509,0 -7823,31121110,Objetos fundidos maquinados con troquel de cobre,Piezas de titanio fundidas en molde mecanizadas,0,80509,0 -7824,31121111,Objetos fundidos maquinados con troquel de latón,Piezas de berilio fundidas en molde mecanizadas,0,80509,0 -7825,31121112,Objetos fundidos maquinados con troquel de bronce,Piezas de metal precioso fundidas en molde mecanizadas,0,80509,0 -7826,31121113,Objetos fundidos maquinados con troquel de zinc,Piezas de cobre fundidas en molde mecanizadas,0,80509,0 -7827,31121114,Objetos fundidos maquinados con troquel de estaño,Piezas de plomo fundidas en molde mecanizadas,0,80509,0 -7828,31121115,Objetos fundidos maquinados con troquel de plomo,Piezas de latón fundidas en molde mecanizadas,0,80509,0 -7829,31121116,Objetos fundidos maquinados con troquel de metal precioso,Piezas de bronce fundidas en molde mecanizadas,0,80509,0 -7830,31121117,Objetos fundidos maquinados con troquel de compuestos,Piezas de compuestos fundidas en molde mecanizadas,0,80509,0 -7831,31121118,Objetos fundidos maquinados con troquel de aleación de níquel,Piezas de aleaciones de níquel fundidas en molde mecanizadas,0,80509,0 -7832,31121119,Objetos fundidos maquinados con troquel no metálico,Piezas no metálicas fundidas en molde mecanizadas,0,80509,0 -7833,31121120,Objetos de aluminio de alta presión fundidos maquinados con troquel,Piezas de aluminio fundidas a alta presión en molde mecanizadas,0,80509,0 -7834,31121121,Ensamble de objetos de aluminio de alta presión fundidos maquinados con troquel,Ensamble de piezas de aluminio fundidas a alta presión en molde mecanizadas,0,80509,0 -7835,31121122,Objetos de magnesio de alta presión fundidos maquinados con troquel,Piezas de magnesio fundidas a alta presión en molde mecanizadas,0,80509,0 -7836,31121123,Ensamble de objetos de magnesio de alta presión fundidos maquinados con troquel,Ensamble de piezas de magnesio fundidas a alta presión en molde mecanizadas,0,80509,0 -7837,31121124,Objetos de zinc de alta presión fundidos maquinados con troquel,Piezas de zinc fundidas a alta presión en molde mecanizadas,0,80509,0 -7838,31121125,Ensamble de objetos de zinc de alta presión fundidos maquinados con troquel,Ensamble de piezas de zinc fundidas a alta presión en molde mecanizadas,0,80509,0 -7839,31121200,Fundiciones en arena mecanizadas,,0,80509,0 -7840,31121201,Objetos maquinados de aleación no ferrosa fundidos en arena,Piezas de aleación no ferrosa fundidas a la arena mecanizadas,0,80509,0 -7841,31121202,Objetos maquinados de aleación ferrosa fundidos en arena,Piezas de aleación ferrosa fundidas a la arena mecanizadas,0,80509,0 -7842,31121203,Objetos maquinados de acero fundidos en arena,Piezas de acero fundidas a la arena mecanizadas,0,80509,0 -7843,31121204,Objetos maquinados de acero inoxidable fundidos en arena,Piezas de acero inoxidable fundidas a la arena mecanizadas,0,80509,0 -7844,31121205,Objetos maquinados de hierro fundidos en arena,Piezas de hierro fundidas a la arena mecanizadas,0,80509,0 -7845,31121206,Objetos maquinados de aluminio fundidos en arena,Piezas de aluminio fundidas a la arena mecanizadas,0,80509,0 -7846,31121207,Objetos maquinados de magnesio fundidos en arena,Piezas de magnesio fundidas a la arena mecanizadas,0,80509,0 -7847,31121208,Objetos maquinados de titanio fundidos en arena,Piezas de titanio fundidas a la arena mecanizadas,0,80509,0 -7848,31121209,Objetos maquinados de berilio fundidos en arena,Piezas de berilio fundidas a la arena mecanizadas,0,80509,0 -7849,31121210,Objetos maquinados de cobre fundidos en arena,Piezas de cobre fundidas a la arena mecanizadas,0,80509,0 -7850,31121211,Objetos maquinados de latón fundidos en arena,Piezas de latón fundidas a la arena mecanizadas,0,80509,0 -7851,31121212,Objetos maquinados de bronce fundidos en arena,Piezas de bronce fundidas a la arena mecanizadas,0,80509,0 -7852,31121213,Objetos maquinados de cinc fundidos en arena,Piezas de zinc fundidas a la arena mecanizadas,0,80509,0 -7853,31121214,Objetos maquinados de estaño fundidos en arena,Piezas de estaño fundidas a la arena mecanizadas,0,80509,0 -7854,31121215,Objetos maquinados de plomo fundidos en arena,Piezas de plomo fundidas a la arena mecanizadas,0,80509,0 -7855,31121216,Objetos maquinados de metal precioso fundidos en arena,Piezas de metal precioso fundidas a la arena mecanizadas,0,80509,0 -7856,31121217,Objetos maquinados de compuestos fundidos en arena,Piezas de compuesto fundidas a la arena mecanizadas,0,80509,0 -7857,31121218,Objetos maquinados de aleación de níquel fundidos en arena,Piezas de aleación de níquel fundidas a la arena mecanizadas,0,80509,0 -7858,31121219,Objetos maquinados no metálicos fundidos en arena,Piezas no metálicas fundidas a la arena mecanizadas,0,80509,0 -7859,31121220,Objetos maquinados de aluminio fundidos en arena,Pieza de aluminio fundida a la arena mecanizadas,0,80509,0 -7860,31121221,Ensamble de objetos maquinados de aluminio fundidos en arena,Ensamble de piezas de aluminio fundidos a la arena mecanizadas,0,80509,0 -7861,31121222,Objetos maquinados de hierro dúctil fundidos en arena,Piezas de hierro dúctil fundidos a la arena mecanizadas,0,80509,0 -7862,31121223,Ensamble de objetos maquinados de hierro dúctil fundidos en arena,Ensamble de piezas de hierro dúctil fundidos a la arena mecanizadas,0,80509,0 -7863,31121224,Objetos maquinados de hierro gris fundidos en arena,Piezas de hierro gris fundidos a la arena mecanizadas,0,80509,0 -7864,31121225,Ensamble de objetos maquinados de hierro gris fundidos en arena,Ensamble de piezas de hierro gris fundidos a la arena mecanizadas,0,80509,0 -7865,31121226,Objetos maquinados de hierro maleable fundidos en arena,Piezas de hierro maleable fundidos a la arena mecanizadas,0,80509,0 -7866,31121227,Ensamble de objetos maquinados de hierro maleable fundidos en arena,Ensamble de piezas de hierro maleable fundidos a la arena mecanizadas,0,80509,0 -7867,31121300,Fundiciones de molde permanente mecanizadas,,0,80509,0 -7868,31121301,Objetos maquinados en molde permanente de aleación no ferrosa fundidos,Piezas de aleación no ferrosa fundidas en molde permanente mecanizadas,0,80509,0 -7869,31121302,Objetos maquinados en molde permanente de aleación ferrosa fundidos,Piezas de aleación ferrosa fundidas en molde permanente mecanizadas,0,80509,0 -7870,31121303,Objetos maquinados en molde permanente de acero fundidos,Piezas de acero fundidas en molde permanente mecanizadas,0,80509,0 -7871,31121304,Objetos maquinados en molde permanente de acero inoxidable fundidos,Piezas de acero inoxidable fundidas en molde permanente mecanizadas,0,80509,0 -7872,31121305,Objetos maquinados en molde permanente de hierro fundidos,Piezas de hierro fundidas en molde permanente mecanizadas,0,80509,0 -7873,31121306,Objetos maquinados en molde permanente de aluminio fundidos,Piezas de aluminio fundidas en molde permanente mecanizadas,0,80509,0 -7874,31121307,Objetos maquinados en molde permanente de magnesio fundidos,Piezas de magnesio fundidas en molde permanente mecanizadas,0,80509,0 -7875,31121308,Objetos maquinados en molde permanente de titanio fundidos,Piezas de titanio fundidas en molde permanente mecanizadas,0,80509,0 -7876,31121309,Objetos maquinados en molde permanente de berilio fundidos,Piezas de berilio fundidas en molde permanente mecanizadas,0,80509,0 -7877,31121310,Objetos maquinados en molde permanente de cobre fundidos,Piezas de cobre fundidas en molde permanente mecanizadas,0,80509,0 -7878,31121311,Objetos maquinados en molde permanente de latón fundidos,Piezas de latón fundidas en molde permanente mecanizadas,0,80509,0 -7879,31121312,Objetos maquinados en molde permanente de bronce fundidos,Piezas de bronce fundidas en molde permanente mecanizadas,0,80509,0 -7880,31121313,Objetos maquinados en molde permanente de cinc fundidos,Piezas de zinc fundidas en molde permanente mecanizadas,0,80509,0 -7881,31121314,Objetos maquinados en molde permanente de estaño fundidos,Piezas de estaño fundidas en molde permanente mecanizadas,0,80509,0 -7882,31121315,Objetos maquinados en molde permanente de plomo fundidos,Piezas de plomo fundidas en molde permanente mecanizadas,0,80509,0 -7883,31121316,Objetos maquinados en molde permanente de metal precioso fundidos,Piezas de metal precioso fundidas en molde permanente mecanizadas,0,80509,0 -7884,31121317,Objetos maquinados en molde permanente de compuestos fundidos,Piezas de compuestos fundidas en molde permanente mecanizadas,0,80509,0 -7885,31121318,Objetos maquinados en molde permanente de aleación de níquel fundidos,Piezas de aleación de níquel fundidas en molde permanente mecanizadas,0,80509,0 -7886,31121319,Objetos maquinados en molde permanente no metálicos fundidos,Piezas no metálicas fundidas en molde permanente mecanizadas,0,80509,0 -7887,31121320,Objetos maquinados en molde permanente de aluminio de baja presión fundidos,Piezas de aluminio fundidas a baja presión en molde permanente mecanizadas,0,80509,0 -7888,31121321,Ensamble de objetos maquinados en molde permanente de aluminio de baja presión fundidos,Ensamble de piezas de aluminio fundidas a baja presión en molde permanente mecanizadas,0,80509,0 -7889,31121700,Piezas fundidas centrífugas mecanizadas,,0,80509,0 -7890,31121701,Objetos maquinados centrifugados de aleación no ferrosa fundidos,Piezas de aleación no ferrosa hechas mediante fundición centrífuga mecanizadas,0,80509,0 -7891,31121702,Objetos maquinados centrifugados de aleación ferrosa fundidos,Piezas de aleación ferrosa hechas mediante fundición centrífuga mecanizadas,0,80509,0 -7892,31121703,Objetos maquinados centrifugados de acero fundidos,Piezas de acero hechas mediante fundición centrífuga mecanizadas,0,80509,0 -7893,31121704,Objetos maquinados centrifugados de acero inoxidable fundidos,Piezas de acero inoxidable hechas mediante fundición centrífuga mecanizadas,0,80509,0 -7894,31121705,Objetos maquinados centrifugados de hierro fundidos,Piezas de hierro hechas mediante fundición centrífuga mecanizadas,0,80509,0 -7895,31121706,Objetos maquinados centrifugados de aluminio fundidos,Piezas de aluminio hechas mediante fundición centrífuga mecanizadas,0,80509,0 -7896,31121707,Objetos maquinados centrifugados de magnesio fundidos,Piezas de magnesio hechas mediante fundición centrífuga mecanizadas,0,80509,0 -7897,31121708,Objetos maquinados centrifugados de titanio fundidos,Piezas de titanio hechas mediante fundición centrífuga mecanizadas,0,80509,0 -7898,31121709,Objetos maquinados centrifugados de berilio fundidos,Piezas de berilio hechas mediante fundición centrífuga mecanizadas,0,80509,0 -7899,31121710,Objetos maquinados centrifugados de cobre fundidos,Piezas de cobre hechas mediante fundición centrífuga mecanizadas,0,80509,0 -7900,31121711,Objetos maquinados centrifugados de latón fundidos,Piezas de latón hechas mediante fundición centrífuga mecanizadas,0,80509,0 -7901,31121712,Objetos maquinados centrifugados de bronce fundidos,Piezas de bronce hechas mediante fundición centrífuga mecanizadas,0,80509,0 -7902,31121713,Objetos maquinados centrifugados de cinc fundidos,Piezas de zinc hechas mediante fundición centrífuga mecanizadas,0,80509,0 -7903,31121714,Objetos maquinados centrifugados de estaño fundidos,Piezas de estaño hechas mediante fundición centrífuga mecanizadas,0,80509,0 -7904,31121715,Objetos maquinados centrifugados de plomo fundidos,Piezas de plomo hechas mediante fundición centrífuga mecanizadas,0,80509,0 -7905,31121716,Objetos maquinados centrifugados de metal precioso fundidos,Piezas de metal precioso hechas mediante fundición centrífuga mecanizadas,0,80509,0 -7906,31121717,Objetos maquinados centrifugados de compuestos fundidos,Piezas de compuesto hechas mediante fundición centrífuga mecanizadas,0,80509,0 -7907,31121718,Objetos maquinados centrifugados de aleación de níquel fundidos,Piezas de aleación de níquel hechas mediante fundición centrífuga mecanizadas,0,80509,0 -7908,31121719,Objetos maquinados centrifugados no metálicos fundidos,Piezas no metálicas hechas mediante fundición centrífuga mecanizadas,0,80509,0 -7909,31132000,Componentes de metal en polvo,,"0,1",80509,0 -7910,31132001,Componentes de metal ferroso pulverizado,Materia de metal ferroso en polvo,"0,1",80509,0 -7911,31132002,Partes de metal no ferroso pulverizado,"Fracción de metal no ferroso en polvo, Pedazo de metal no ferroso en polvo, Trozo de metal no ferroso en polv","0,1",80509,0 -7912,31132100,Forjas de acero,Fragua de acero,0,80509,0 -7913,31132101,Forja de acero maquinado forjado en frío,Fragua de acero forjado en frío mecanizado,0,80509,0 -7914,31132102,Forja de acero tratado con calor forjado en frío,Fragua de acero tratado con calor forjado en frío,0,80509,0 -7915,31132103,Forja de acero maquinado y tratado con calor forjado en frío,Fragua de acero tratado con calor forjado en frío mecanizado,0,80509,0 -7916,31132104,Forja de acero tratado con calor y dimensionado en frío forjado en frío,Fragua de acero tratado con calor forjado y dimensionado en frío,0,80509,0 -7917,31132105,Forja de acero maquinado forjado en tibio,Fragua de acero forjado en tibio mecanizado,0,80509,0 -7918,31132106,Forja de acero tratado con calor forjado en tibio,Fragua de acero tratado con calor forjado en tibio,0,80509,0 -7919,31132107,Forja de acero maquinado y tratado con calor forjado en tibio,Fragua de acero tratado con calor forjado en tibio mecanizado,0,80509,0 -7920,31132108,Forja de acero tratado con calor y dimensionado en frío forjado en tibio,Fragua de acero tratado con calor forjado en tibio y dimensionado en frío,0,80509,0 -7921,31132109,Forja de acero maquinado forjado en caliente,Fragua de acero forjado en caliente mecanizado,0,80509,0 -7922,31132110,Forja de acero tratado con calor forjado en caliente,Fragua de acero tratado y forjado con calor,0,80509,0 -7923,31132111,Forja de acero maquinado y tratado con calor forjado en caliente,Fragua de acero tratado y forjado con calor mecanizado,0,80509,0 -7924,31132112,Forja de acero tratado con calor y dimensionado en frío forjado en caliente,Fragua de acero tratado y forjado con calor y dimensionado en frío,0,80509,0 -7925,31132300,Forjas de aluminio,Fragua de aluminio,0,80509,0 -7926,31132301,Forja de aluminio maquinado forjado en frío,Fragua de aluminio forjado en frío mecanizado,0,80509,0 -7927,31132302,Forja de aluminio tratado con calor forjado en frío,Fragua de aluminio tratado con calor forjado en frío,0,80509,0 -7928,31132303,Forja de aluminio maquinado y tratado con calor forjado en frío,Fragua de aluminio tratado con calor forjado en frío mecanizado,0,80509,0 -7929,31132304,Forja de aluminio tratado con calor y dimensionado en frío forjado en frío,Fragua de aluminio tratado con calor forjado en frío y dimensionado en frío,0,80509,0 -7930,31132305,Forja de aluminio maquinado forjado en tibio,Fragua de aluminio forjado en tibio mecanizado,0,80509,0 -7931,31132306,Forja de aluminio tratado con calor forjado en tibio,Fragua de aluminio tratado con calor forjado en tibio,0,80509,0 -7932,31132307,Forja de aluminio maquinado y tratado con calor forjado en tibio,Fragua de aluminio tratado con calor forjado en tibio mecanizado,0,80509,0 -7933,31132308,Forja de aluminio tratado con calor y dimensionado en frío forjado en tibio,Fragua de aluminio tratado con calor forjado en tibio y dimensionado en frío,0,80509,0 -7934,31132700,Forjas de hierro,Fragua de hierro,0,80509,0 -7935,31132701,Forja de hierro maquinado forjado en frío,Fragua de hierro forjado en frío mecanizado,0,80509,0 -7936,31132702,Forja de hierro tratado con calor forjado en frío,Fragua de hierro tratado con calor forjado en frío,0,80509,0 -7937,31132703,Forja de hierro maquinado y tratado con calor forjado en frío,Fragua de hierro tratado con calor forjado en frío mecanizado,0,80509,0 -7938,31132704,Forja de hierro tratado con calor y dimensionado en frío forjado en frío,Fragua de hierro tratado con calor forjado y dimensionado en frío,0,80509,0 -7939,31132705,Forja de hierro maquinado forjado en tibio,Fragua de hierro forjado en tibio mecanizado,0,80509,0 -7940,31132706,Forja de hierro tratado con calor forjado en tibio,Fragua de hierro tratado con calor forjado en tibio,0,80509,0 -7941,31132707,Forja de hierro maquinado y tratado con calor forjado en tibio,Fragua de hierro tratado con calor forjado en tibio mecanizado,0,80509,0 -7942,31132708,Forja de hierro tratado con calor y dimensionado en frío forjado en tibio,Fragua de hierro tratado con calor forjado en tibio y dimensionado en frío,0,80509,0 -7943,31132709,Forja de hierro maquinado forjado en caliente,Fragua de hierro forjado en calor mecanizados,0,80509,0 -7944,31132710,Forja de hierro tratado con calor forjado en caliente,Fragua de hierro tratado y forjado con calor,0,80509,0 -7945,31132711,Forja de hierro maquinado y tratado con calor forjado en caliente,Fragua de hierro tratado y forjado con calor mecanizados,0,80509,0 -7946,31132712,Forja de hierro tratado con calor y dimensionado en frío forjado en caliente,Fragua de hierro tratado y forjado con calor y dimensionado en frío,0,80509,0 -7947,31132900,Forjas de titanio,Fragua de titanio,0,80509,0 -7948,31132901,Forja de titanio maquinado forjado en frío,Fragua de titanio forjado en frío mecanizado,0,80509,0 -7949,31132902,Forja de titanio tratado con calor forjado en frío,Fragua de titanio tratado con calor forjado en frío,0,80509,0 -7950,31132903,Forja de titanio maquinado y tratado con calor forjado en frío,Fragua de titanio tratado con calor forjado en frío mecanizado,0,80509,0 -7951,31132904,Forja de titanio tratado con calor y dimensionado en frío forjado en frío,Fragua de titanio tratado con calor forjado y dimensionado en frío,0,80509,0 -7952,31132905,Forja de titanio maquinado forjado en tibio,Fragua de titanio forjado en tibio mecanizado,0,80509,0 -7953,31132906,Forja de titanio tratado con calor forjado en tibio,Fragua de titanio tratado con calor forjado en tibio,0,80509,0 -7954,31132907,Forja de titanio maquinado y tratado con calor forjado en tibio,Fragua de titanio tratado con calor forjado en tibio mecanizado,0,80509,0 -7955,31132908,Forja de titanio tratado con calor y dimensionado en frío forjado en tibio,Fragua de titanio tratado con calor forjado en tibio y dimensionado en frío,0,80509,0 -7956,31132909,Forja de titanio maquinado forjado en caliente,Fragua de titanio forjado en calor mecanizados,0,80509,0 -7957,31132910,Forja de titanio tratado con calor forjado en caliente,Fragua de titanio tratado y forjado con calor,0,80509,0 -7958,31132911,Forja de titanio maquinado y tratado con calor forjado en caliente,Fragua de titanio tratado y forjado con calor mecanizados,0,80509,0 -7959,31132912,Forja de titanio tratado con calor y dimensionado en frío forjado en caliente,Fragua de titanio inoxidable tratado y forjado con calor y dimensionado en frío,0,80509,0 -7960,31133100,Forjas de cobre,Fragua de cobre,0,80509,0 -7961,31133101,Forja de cobre maquinado forjado en frío,Fragua de cobre forjado en frío mecanizado,0,80509,0 -7962,31133102,Forja de cobre tratado con calor forjado en frío,Fragua de cobre tratado con calor forjado en frío,0,80509,0 -7963,31133103,Forja de cobre maquinado y tratado con calor forjado en frío,Fragua de cobre tratado con calor forjado en frío mecanizado,0,80509,0 -7964,31133104,Forja de cobre tratado con calor y dimensionado en frío forjado en frío,Fragua de cobre tratado con calor forjado y dimensionado en frío,0,80509,0 -7965,31133105,Forja de cobre maquinado forjado en tibio,Fragua de cobre forjado en tibio mecanizado,0,80509,0 -7966,31133106,Forja de cobre tratado con calor forjado en tibio,Fragua de cobre tratado con calor forjado en tibio,0,80509,0 -7967,31133107,Forja de cobre maquinado y tratado con calor forjado en tibio,Fragua de cobre tratado con calor forjado en tibio mecanizado,0,80509,0 -7968,31133108,Forja de cobre tratado con calor y dimensionado en frío forjado en tibio,Fragua de cobre tratado con calor forjado en tibio y dimensionado en frío,0,80509,0 -7969,31133109,Forja de cobre maquinado forjado en caliente,Fragua de cobre forjado en calor mecanizados,0,80509,0 -7970,31133110,Forja de cobre tratado con calor forjado en caliente,Fragua de cobre tratado y forjado con calor,0,80509,0 -7971,31133111,Forja de cobre maquinado y tratado con calor forjado en caliente,Fragua de cobre tratado y forjado con calor mecanizados,0,80509,0 -7972,31133112,Forja de cobre tratado con calor y dimensionado en frío forjado en caliente,Fragua de cobre inoxidable tratado y forjado con calor y dimensionado en frío,0,80509,0 -7973,31133200,Forjas de zinc,Fragua de cinc,0,80509,0 -7974,31133201,Forja de zinc maquinado forjado en frío,Fragua de cinc forjado en frío mecanizado,0,80509,0 -7975,31133202,Forja de zinc tratado con calor forjado en frío,Fragua de cinc tratado con calor forjado en frío,0,80509,0 -7976,31133203,Forja de zinc maquinado y tratado con calor forjado en frío,Fragua de cinc tratado con calor forjado en frío mecanizado,0,80509,0 -7977,31133204,Forja de zinc tratado con calor y dimensionado en frío forjado en frío,Fragua de cinc tratado con calor forjado y dimensionado en frío,0,80509,0 -7978,31133205,Forja de zinc maquinado forjado en tibio,Fragua de cinc forjado en tibio mecanizado,0,80509,0 -7979,31133206,Forja de zinc tratado con calor forjado en tibio,Fragua de cinc tratado con calor forjado en tibio,0,80509,0 -7980,31133207,Forja de zinc maquinado y tratado con calor forjado en tibio,Fragua de cinc tratado con calor forjado en tibio mecanizado,0,80509,0 -7981,31133208,Forja de zinc tratado con calor y dimensionado en frío forjado en tibio,Fragua de cinc tratado con calor forjado en tibio y dimensionado en frío,0,80509,0 -7982,31133209,Forja de zinc maquinado forjado en caliente,Fragua de cinc forjado en calor mecanizados,0,80509,0 -7983,31133210,Forja de zinc tratado con calor forjado en caliente,Fragua de cinc tratado y forjado con calor,0,80509,0 -7984,31133211,Forja de zinc maquinado y tratado con calor forjado en caliente,Fragua de cinc tratado y forjado con calor mecanizados,0,80509,0 -7985,31133212,Forja de zinc tratado con calor y dimensionado en frío forjado en caliente,Fragua de cinc inoxidable tratado y forjado con calor y dimensionado en frío,0,80509,0 -7986,31133500,Forjas de plomo,Fragua de plomo,0,80509,0 -7987,31133501,Forja de plomo maquinado forjado en frío,Fragua de plomo forjado en frío mecanizado,0,80509,0 -7988,31133502,Forja de plomo tratado con calor forjado en frío,Fragua de plomo tratado con calor forjado en frío,0,80509,0 -7989,31133503,Forja de plomo maquinado y tratado con calor forjado en frío,Fragua de plomo tratado con calor forjado en frío mecanizado,0,80509,0 -7990,31133504,Forja de plomo tratado con calor y dimensionado en frío forjado en frío,Fragua de plomo tratado con calor forjado y dimensionado en frío,0,80509,0 -7991,31133505,Forja de plomo maquinado forjado en tibio,Fragua de plomo forjado en tibio mecanizado,0,80509,0 -7992,31133506,Forja de plomo tratado con calor forjado en tibio,Fragua de plomo tratado con calor forjado en tibio,0,80509,0 -7993,31133507,Forja de plomo maquinado y tratado con calor forjado en tibio,Fragua de plomo tratado con calor forjado en tibio mecanizado,0,80509,0 -7994,31133508,Forja de plomo tratado con calor y dimensionado en frío forjado en tibio,Fragua de plomo tratado con calor forjado en tibio y dimensionado en frío,0,80509,0 -7995,31133509,Forja de plomo maquinado forjado en caliente,Fragua de plomo forjado en calor mecanizados,0,80509,0 -7996,31133510,Forja de plomo tratado con calor forjado en caliente,Fragua de plomo tratado y forjado con calor,0,80509,0 -7997,31133511,Forja de plomo maquinado y tratado con calor forjado en caliente,Fragua de plomo tratado y forjado con calor mecanizados,0,80509,0 -7998,31133512,Forja de plomo tratado con calor y dimensionado en frío forjado en caliente,Fragua de plomo inoxidable tratado y forjado con calor y dimensionado en frío,0,80509,0 -7999,31133700,Metales en polvos y aleaciones de metal,Metales y aleaciones de metal pulverizados,0,80509,0 -8000,31133701,Fricción material de metal en polvo y aleación de metal,Material de fricción de metal y aleaciones de metal pulverizados,0,80509,0 -8001,31133702,Contacto eléctrico de metal en polvo y aleación de metal,Contacto eléctrico de metal y aleaciones de metal pulverizados,0,80509,0 -8002,31133703,Carburo cementado de metal en polvo y aleación de metal,Carburo cementado de metal y aleaciones de metal pulverizados,0,80509,0 -8003,31133704,Matriz metal diamante de metal en polvo y aleación de metal,Matriz de metal de diamante de metal y aleaciones de metal pulverizados,0,80509,0 -8004,31133705,Recubrimiento conformal de silicona,Revestimiento conformal de silicón,0,80509,0 -8005,31133706,Recubrimiento conformal de acrílico,Revestimiento conformal de acrílico,0,80509,0 -8006,31133707,Recubrimiento conformal de poliuretano,Revestimiento conformal de poliuretano,0,80509,0 -8007,31133708,Recubrimiento conformal de epóxido,Revestimiento conformal de epóxido,0,80509,0 -8008,31133709,Recubrimiento conformal de parilene,Revestimiento conformal de pariele,0,80509,0 -8009,31133710,Sellante de silicona,Sellante de silicón,0,80509,0 -8010,31133711,Sellante de poliuretano,,0,80509,0 -8011,31141500,Molduras por inyección,,0,80509,0 -8012,31141501,Molduras por inyección de plástico,,0,80509,0 -8013,31141502,Molduras por inyección de caucho,,0,80509,0 -8014,31141503,Molduras por inyección de vidrio,,0,80509,0 -8015,31141600,Molduras al vacío,,0,80509,0 -8016,31141601,Molduras al vacío de plástico,,0,80509,0 -8017,31141602,Molduras al vacío de caucho,,0,80509,0 -8018,31141603,Molduras al vacío de vidrio,,0,80509,0 -8019,31141700,Moldeados por inyección de aire,Molduras por soplado,0,80509,0 -8020,31141701,Moldeados plásticos por inyección de aire,Moldura plásticos por soplado,0,80509,0 -8021,31141702,Moldeados de caucho por inyección de aire,Moldura de caucho por soplado,0,80509,0 -8022,31141800,Moldeos por inyección y reacción (RIM),Moldura de inyección con reacción RIM,0,80509,0 -8023,31141801,Moldeados de inyección de reacción de plástico,Moldura de inyección con reacción de plástico,0,80509,0 -8024,31141802,Moldeados de inyección de reacción de caucho,Moldura de inyección con reacción de caucho,0,80509,0 -8025,31141900,Moldeados de inyección con inserción,Moldura de inyección con inserción,0,80509,0 -8026,31141901,Moldeado plástico con inserción previa a la inyección,Moldura de plástico de inyección con inserción,0,80509,0 -8027,31142000,Ensambles de moldeados termoplásticos,Ensambles de moldura termoplásticos,0,80509,0 -8028,31142001,Ensamble de moldeado termoplástico por compresión,Ensambles de moldura termoplásticos por compresión,0,80509,0 -8029,31142002,Ensamble de moldeado termoplástico por inmersión,Ensambles de moldura termoplásticos por inmersión,0,80509,0 -8030,31142003,Ensamble de moldeado termoplástico por aire,Ensambles de moldura termoplásticos por aire,0,80509,0 -8031,31142004,Ensamble de moldeado termoplástico por inyección,Ensambles de moldura termoplásticos por inyección,0,80509,0 -8032,31142005,Ensamble de moldeado termoplástico por inyección asistida por gas,Ensambles de moldura termoplásticos por inyección asistida por gas,0,80509,0 -8033,31142006,Ensamble de moldeado termoplástico por inyección de alta precisión,Ensambles de moldura termoplásticos por inyección de alta precisión,0,80509,0 -8034,31142007,Ensamble de moldeado termoplástico por transferencia,Ensambles de moldura termoplásticos por transferencia,0,80509,0 -8035,31142800,Moldeados por inmersión,,0,80509,0 -8036,31142801,Moldeado por inmersión de plástico o plastisol,,0,80509,0 -8037,31142802,Moldeado por inmersión de látex,,0,80509,0 -8038,31142803,Moldeado por inmersión de neopreno,,0,80509,0 -8039,31142804,Moldeado por inmersión de uretano,,0,80509,0 -8040,31142900,Moldeados decorados en el molde por inyección,,0,80509,0 -8041,31142901,Aplique en molde para moldeado por inyección,Aplicación en molde para moldear por inyección,0,80509,0 -8042,31142902,Tinta en molde para traspaso por moldeo por inyección,,0,80509,0 -8043,31142903,Molde para moldeado por inyección de color,,0,80509,0 -8044,31142904,Moldeado por inyección de disparos múltiples,,0,80509,0 -8045,31143100,Ensambles de moldeado termoplástico terminados,,0,80509,0 -8046,31143101,Ensamble de moldeado termoplástico terminado por inyección,Ensamble de molde de polímero terminado por inyección,0,80509,0 -8047,31143102,Ensamble de moldeado termoplástico terminado por compresión,Ensamble de molde de polímero terminado por compresión,0,80509,0 -8048,31143103,Ensamble de moldeado termoplástico terminado por inyección asistida por gas,Ensamble de molde de polímero terminado por inyección asistida por gas,0,80509,0 -8049,31151500,Cuerdas,,0,80509,0 -8050,31151501,Cuerda de algodón,"Lazo de algodón, Soga de algodón",0,80509,0 -8051,31151502,Cuerda de poliéster,"Lazo de poliéster, Soga de poliéster",0,80509,0 -8052,31151503,Cuerda de polipropileno,"Lazo de polipropileno, Soga de polipropileno",0,80509,0 -8053,31151504,Cuerda de nylon,"Lazo de nylon, Soga de nylon",0,80509,0 -8054,31151505,Cable de acero,,0,80509,0 -8055,31151506,Cuerda de cáñamo,,0,80509,0 -8056,31151507,Cuerda o pita,,0,80509,0 -8057,31151508,Cabuya,Cuerda de ixtle,0,80509,0 -8058,31151509,Cuerda de caucho,Soga de caucho,0,80509,0 -8059,31151510,Cuerda de rayón,Soga de rayón,0,80509,0 -8060,31151511,Cuerda de lino,Soga de lino,0,80509,0 -8061,31151512,Cuerda de polietileno,,0,80509,0 -8062,31151513,Cuerda de fibra de coco,,0,80509,0 -8063,31151514,Cuerda de yute,,0,80509,0 -8064,31151515,Cuerda de esparto,Mecate,0,80509,0 -8065,31151516,Cuerda de rafia,Mecate,0,80509,0 -8066,31151517,Cuerda de aloe,,0,80509,0 -8067,31151518,Cuerda impregnada,,0,80509,0 -8068,31151519,Cuerda de látex,,0,80509,0 -8069,31151520,Cuerda de vinylon,Cuerda de vinalón,0,80509,0 -8070,31151521,Cuerda de asbesto,,0,80509,0 -8071,31151600,Cadenas,,0,80509,0 -8072,31151601,Cadenas de seguridad,,0,80509,0 -8073,31151603,Cadenas de rodillos,,0,80509,0 -8074,31151604,Cadenas resistentes de bobina,,0,80509,0 -8075,31151605,Cadenas de banda,,0,80509,0 -8076,31151606,"Cadena ""jack""",,0,80509,0 -8077,31151607,Cadenas corrientes,,0,80509,0 -8078,31151608,Cadenas de bola,,0,80509,0 -8079,31151609,Eslabones de cadena,,0,80509,0 -8080,31151610,Cadena de pasador hueco,Cadena de rodillos,0,80509,0 -8081,31151611,Cadena silenciosa,,0,80509,0 -8082,31151612,Cadena de bloque,,0,80509,0 -8083,31151613,Cadena de ingeniería,,0,80509,0 -8084,31151614,Cadena transportadora,,0,80509,0 -8085,31151615,Cadena de rodillo o sobre placa,,0,80509,0 -8086,31151616,Cadena tipo muela,,0,80509,0 -8087,31151617,Cadena draga línea,,0,80509,0 -8088,31151618,Cadena de muro alto,,0,80509,0 -8089,31151619,Cadena de escalón de escalera eléctrica,,0,80509,0 -8090,31151700,Cable mecánico,,0,80509,0 -8091,31151702,Cables de control no eléctrico,,0,80509,0 -8092,31151703,Cables elevadores,,0,80509,0 -8093,31151704,Cable-vías,,0,80509,0 -8094,31151705,Cable de acero no eléctrico,,0,80509,0 -8095,31151706,Cable de cobre no eléctrico,,0,80509,0 -8096,31151707,Cable de aluminio no eléctrico,,0,80509,0 -8097,31151708,Cable guy,,0,80509,0 -8098,31151709,Cable de hierro no eléctrico,,0,80509,0 -8099,31151710,Cable sintético,,0,80509,0 -8100,31151800,Alambre mecánico,,0,80509,0 -8101,31151803,Cuerda de acero para piano,,0,80509,0 -8102,31151804,Alambre de grapa,,0,80509,0 -8103,31151805,Alambre de multi – filamentos,,0,80509,0 -8104,31151806,Alambre laminado,,0,80509,0 -8105,31151900,Correas,,0,80509,0 -8106,31151901,Correas de metal,,0,80509,0 -8107,31151902,Correas de cuero,,0,80509,0 -8108,31151903,Correas de fibra,,0,80509,0 -8109,31151904,Correas plásticas,,0,80509,0 -8110,31151905,Correas de caucho,,0,80509,0 -8111,31151906,Correas para amarrar los raches,Correa de amarre con trinquete,0,80509,0 -8112,31152000,Cable de seguridad,,0,80509,0 -8113,31152001,Alambre de navaja,Correa de amarre con gancho,0,80509,0 -8114,31152002,Alambre de púas,Alambre navaja,0,80509,0 -8115,31152100,Cordones,,0,80509,0 -8116,31152101,Cordón de algodón,,0,80509,0 -8117,31152102,Cordón de nylon,,0,80509,0 -8118,31152103,Cordón metálico,,0,80509,0 -8119,31152104,Cordón de silicona,,0,80509,0 -8120,31152105,Cordón de látex,,0,80509,0 -8121,31152106,Cordón de poliéster,,0,80509,0 -8122,31152107,Cordón de neopreno,,0,80509,0 -8123,31152108,Cordón de polipropileno,,0,80509,0 -8124,31152109,Cordón de caucho,,0,80509,0 -8125,31152110,Cordón de asbesto,,0,80509,0 -8126,31152111,Cordón de fibra de vidrio,,0,80509,0 -8127,31152112,Cordón de fibra cerámica,,0,80509,0 -8128,31152200,Alambres no eléctricos de hierro y acero,,0,80509,0 -8129,31152201,Cable de acero bajo en carbono,,0,80509,0 -8130,31152202,Cable de acero semiduro,Alambre recocido,0,80509,0 -8131,31152203,Cable de acero inoxidable,Alambre de acero semiduro,0,80509,0 -8132,31152204,Cable de acero trenzado,Alambre de acero inoxidable,0,80509,0 -8133,31152205,Cable de acero templado con aceite,Alambre de acero tipo trenzado,0,80509,0 -8134,31152206,Cable de acero recubierto,,0,80509,0 -8135,31152207,Cable de acero de concreto pretensado,,0,80509,0 -8136,31152208,Cable de acero de concreto pretensado trenzado,,0,80509,0 -8137,31152209,Alambre de hierro,,0,80509,0 -8138,31152300,Alambres no eléctricos de metales no ferrosos,,0,80509,0 -8139,31152301,Alambre de plomo o aleación de plomo,,0,80509,0 -8140,31152302,Alambre de níquel o aleación de níquel,,0,80509,0 -8141,31152303,Alambre de aleación nicromo,,0,80509,0 -8142,31152304,Alambre de cobre o aleación de cobre,,0,80509,0 -8143,31152305,Alambre de aluminio o aleación de aluminio,,0,80509,0 -8144,31152306,Alambre de estaño o aleación de estaño,,0,80509,0 -8145,31152307,Alambre de titanio o aleación de titanio,,0,80509,0 -8146,31161500,Tornillos,,0,80509,0 -8147,31161501,Tornillos de perno,,0,80509,0 -8148,31161502,Tornillos de anclaje,,0,80509,0 -8149,31161503,Clavo-tornillo,Pija,0,80509,0 -8150,31161504,Tornillos de máquina,Tornillo con punta de clavo,0,80509,0 -8151,31161505,Tornillos de presión,,0,80509,0 -8152,31161506,Tornillos para lámina metálica,,0,80509,0 -8153,31161507,Tornillos roscadores,,0,80509,0 -8154,31161508,Tornillos de rosca para madera,,0,80509,0 -8155,31161509,Tornillos para drywall,,0,80509,0 -8156,31161510,Tornillo cautivo,"Pija, Tornillos para tablaroca",0,80509,0 -8157,31161511,Tornillos de apriete,Pija,0,80509,0 -8158,31161512,Tornillos de rosca para laminados,,0,80509,0 -8159,31161513,Tornillos de cabeza perdida,,0,80509,0 -8160,31161514,Tornillos de soldadura,,0,80509,0 -8161,31161516,Tornillo de orejas,,0,80509,0 -8162,31161517,Tornillo de hombros,,0,80509,0 -8163,31161518,Tornillo de enchufe,,0,80509,0 -8164,31161519,Tornillo de balde elevador,,0,80509,0 -8165,31161520,Tornillo de cabeza hexagonal,,0,80509,0 -8166,31161521,Tornillo de carruaje,,0,80509,0 -8167,31161522,Tornillo de ojo,,0,80509,0 -8168,31161523,Tronillo de ala,Tornillo mariposa,0,80509,0 -8169,31161524,Tornillo decorativo,,0,80509,0 -8170,31161525,Tornillo para plástico,,0,80509,0 -8171,31161526,Tornillo de concreto,,0,80509,0 -8172,31161527,Tornillo en t o cabeza de martillo,,0,80509,0 -8173,31161528,Tornillo auto – entrante,,0,80509,0 -8174,31161529,Tornillo de gato,Tornillo barrenado,0,80509,0 -8175,31161530,Tornillo grub,Tornillo mecánico de gato,0,80509,0 -8176,31161531,Tornillo especializado,Tornillo para excavar,0,80509,0 -8177,31161532,Tornillo allen,,0,80509,0 -8178,31161533,Tornillo con arandela ensamblada,,0,80509,0 -8179,31161534,Tornillo de rosca trapezoidal,"Pija, Tornillo con rondana",0,80509,0 -8180,31161600,Pernos,,0,80509,0 -8181,31161601,Pernos de anclaje,,0,80509,0 -8182,31161602,Pernos ciegos,Armella,0,80509,0 -8183,31161603,Pernos de carruaje,,0,80509,0 -8184,31161604,Pernos de horquilla,Tornillo carrocería,0,80509,0 -8185,31161605,Pernos de cilindro,,0,80509,0 -8186,31161606,Cerrojos de puerta,,0,80509,0 -8187,31161607,Pernos de expansión,,0,80509,0 -8188,31161608,Tirafondos,Taquete de expansión,0,80509,0 -8189,31161609,Tornillo de fiador,Perno tirafondo,0,80509,0 -8190,31161610,Pernos de ojo,,0,80509,0 -8191,31161611,Pernos de sujeción,,0,80509,0 -8192,31161612,Pernos de espiga o de reborde,,0,80509,0 -8193,31161613,Pernos de tensión,,0,80509,0 -8194,31161614,Pernos estructurales,,0,80509,0 -8195,31161616,Pernos en u,,0,80509,0 -8196,31161617,Pernos de ala,Abrazadera tipo U,0,80509,0 -8197,31161618,Varilla roscada,Tornillo mariposa,0,80509,0 -8198,31161619,Pernos prisioneros,Varilla barrenada,0,80509,0 -8199,31161620,Pernos de cabeza hexagonal,,0,80509,0 -8200,31161621,Pernos elevadores,,0,80509,0 -8201,31161622,Perno de cizalla,,0,80509,0 -8202,31161623,Perno de cable,Tornillo de cizalla,0,80509,0 -8203,31161624,Perno de resina,,0,80509,0 -8204,31161625,Perno de carrilera,,0,80509,0 -8205,31161626,Perno sems de cabeza plana,,0,80509,0 -8206,31161627,Ensamble de pernos,,0,80509,0 -8207,31161628,Perno de cabeza cuadrada,,0,80509,0 -8208,31161629,Perno de cabeza redonda,,0,80509,0 -8209,31161630,Perno en blanco,,0,80509,0 -8210,31161631,Perno de hombro,,0,80509,0 -8211,31161632,Perno de roca,,0,80509,0 -8212,31161633,Perno de estufa,,0,80509,0 -8213,31161634,Perno de sobre – cuello,,0,80509,0 -8214,31161635,Perno con arandela ensamblada,,0,80509,0 -8215,31161636,Perno de soldadura,,0,80509,0 -8216,31161637,Perno de cabeza de enchufe,,0,80509,0 -8217,31161638,Perno t,,0,80509,0 -8218,31161639,Perno de gancho,,0,80509,0 -8219,31161640,Perno garfio,,0,80509,0 -8220,31161700,Tuercas,,0,80509,0 -8221,31161701,Tuercas de anclaje,,0,80509,0 -8222,31161702,Tuercas de rodamiento,,0,80509,0 -8223,31161703,Tuercas ciegas,Tuerca balero,0,80509,0 -8224,31161704,Tuercas de cañón,,0,80509,0 -8225,31161705,Tuerca tapa,,0,80509,0 -8226,31161706,Tuercas cautivas,,0,80509,0 -8227,31161707,Tuercas almenadas,,0,80509,0 -8228,31161708,Tuercas de canal,,0,80509,0 -8229,31161709,Tuercas sujetadoras,,0,80509,0 -8230,31161710,Tuercas de expansión,,0,80509,0 -8231,31161711,Tuercas de ojo,,0,80509,0 -8232,31161712,Tuercas de brida,,0,80509,0 -8233,31161713,Tuercas de manguera,,0,80509,0 -8234,31161714,Tuercas de inserción,,0,80509,0 -8235,31161716,Contratuercas,,0,80509,0 -8236,31161717,Tuercas de aletas,,0,80509,0 -8237,31161718,Tuercas de fiador,Tuercas de mariposa,0,80509,0 -8238,31161719,Tuercas giratorias,,0,80509,0 -8239,31161720,Tuercas limitadoras,,0,80509,0 -8240,31161721,Tuercas de resorte,Tuercas unión macho con macho,0,80509,0 -8241,31161722,Tuercas de unión,,0,80509,0 -8242,31161723,Tuercas de placa con rosca,,0,80509,0 -8243,31161724,Tuercas de prensa,Tuerca de clip,0,80509,0 -8244,31161725,Tuercas abrazaderas,,0,80509,0 -8245,31161726,Tuercas domo,,0,80509,0 -8246,31161727,Tuercas hexagonales,,0,80509,0 -8247,31161728,Tuercas de acople,,0,80509,0 -8248,31161729,Tuercas estriadas,,0,80509,0 -8249,31161730,Tuercas cuadradas,,0,80509,0 -8250,31161731,Tuercas soldables,,0,80509,0 -8251,31161732,Tuerca medialuna,,0,80509,0 -8252,31161733,Tuerca en t o ranura de tuerca en t,,0,80509,0 -8253,31161734,Tuerca redonda con ranura,,0,80509,0 -8254,31161735,Tuerca de jaula,,0,80509,0 -8255,31161736,Tapa de tuerca decorativa,,0,80509,0 -8256,31161737,Tuerca de cizalla,,0,80509,0 -8257,31161738,Tuerca keps o k,,0,80509,0 -8258,31161739,Tuerca de soldadura,Tuerca dentada,0,80509,0 -8259,31161740,Tuerca perno,,0,80509,0 -8260,31161741,Tuerca de llave interna,Tornillo con tuerca,0,80509,0 -8261,31161800,Arandelas,Rondanas,0,80509,0 -8262,31161801,Arandelas de seguridad,Rondanas de seguridad,0,80509,0 -8263,31161802,Arandelas achaflanadas,,0,80509,0 -8264,31161803,Arandelas de fijación,Rondanas de fijación,0,80509,0 -8265,31161804,Arandelas curvas,Rondanas curvas,0,80509,0 -8266,31161805,Arandelas aislantes eléctricas,Rondanas aislantes,0,80509,0 -8267,31161806,Arandelas de acabado,Rondanas de acabado,0,80509,0 -8268,31161807,Arandelas planas,Rondanas planas,0,80509,0 -8269,31161808,Arandelas abiertas,Rondanas abiertas,0,80509,0 -8270,31161809,Arandelas reductoras,Rondanas reductoras,0,80509,0 -8271,31161810,Arandelas de separación,Rondanas de separación,0,80509,0 -8272,31161811,Arandelas de resorte,Rondanas de resorte,0,80509,0 -8273,31161812,Arandelas cuadradas,Rondanas cuadradas,0,80509,0 -8274,31161813,Arandelas giratorias,Rondanas giratorias,0,80509,0 -8275,31161814,Arandelas de empuje,Rondanas de empuje,0,80509,0 -8276,31161815,Arandelas de tope,Rondanas de tope,0,80509,0 -8277,31161816,Espaciadores y separadores,,0,80509,0 -8278,31161817,Arandelas cónicas,Empaque cónico,0,80509,0 -8279,31161818,Arandelas de sellado,Empaque de sellado,0,80509,0 -8280,31161819,Juegos de arandelas,,0,80509,0 -8281,31161820,Arandelas en domo o esféricas,,0,80509,0 -8282,31161821,Arandela roscada,Rondana roscada,0,80509,0 -8283,31161822,Arandelas con dientes de seguridad,,0,80509,0 -8284,31161823,Arandela distribuidora,,0,80509,0 -8285,31161824,Arandela para perno de techo,,0,80509,0 -8286,31161825,Arandela belleville,,0,80509,0 -8287,31161826,Arandela dentada,,0,80509,0 -8288,31161827,Arandela recortada,,0,80509,0 -8289,31161828,Arandela con ranura,,0,80509,0 -8290,31161829,Arandela retenedora,,0,80509,0 -8291,31161830,Arandelas de ola,,0,80509,0 -8292,31161831,Arandela reductora de vibración,,0,80509,0 -8293,31161832,Arandelas avellanadas,,0,80509,0 -8294,31161833,Arandela empaque,,0,80509,0 -8295,31161834,Arandelas de copa,,0,80509,0 -8296,31161835,Arandelas de pestaña,,0,80509,0 -8297,31161836,Arandelas de placa de anclaje,,0,80509,0 -8298,31161837,Arandelas de división,,0,80509,0 -8299,31161838,Arandelas lenguadas,,0,80509,0 -8300,31161900,Muelles,,0,80509,0 -8301,31161901,Muelles helicoidales,Resortes,0,80509,0 -8302,31161902,Muelles,,0,80509,0 -8303,31161903,Muelles espirales,,0,80509,0 -8304,31161904,Resortes de compresión,,0,80509,0 -8305,31161905,Resortes para estampas,,0,80509,0 -8306,31161906,Muelles de disco,,0,80509,0 -8307,31161907,Muelles de extensión,,0,80509,0 -8308,31161908,Muelles de torsión,,0,80509,0 -8309,31161909,Muelle en ola,,0,80509,0 -8310,31161910,Resorte de alambre,,0,80509,0 -8311,31161911,Ensamble de resorte,,0,80509,0 -8312,31161912,Resorte de válvula inyectora,,0,80509,0 -8313,31162000,Clavos,,0,80509,0 -8314,31162001,Chinches,Tachuelas,0,80509,0 -8315,31162002,Clavos de sombrerete,Remaches,0,80509,0 -8316,31162003,Clavos de acabado,,0,80509,0 -8317,31162004,Clavos de mampostería,Clavos para concreto u hormigón,0,80509,0 -8318,31162005,Clavos para tejados,,0,80509,0 -8319,31162006,Clavos de alambre,,0,80509,0 -8320,31162007,Clavos de tapicería,,0,80509,0 -8321,31162008,Pasadores de arrastre,,0,80509,0 -8322,31162009,Clavo de fibra cemento,,0,80509,0 -8323,31162010,Clavo de herradura,,0,80509,0 -8324,31162011,Puntilla decorativa,,0,80509,0 -8325,31162012,Clavo de levantamientos topográficos,,0,80509,0 -8326,31162013,Clave de botes,,0,80509,0 -8327,31162014,Clavo ferroviario,,0,80509,0 -8328,31162100,Anclas,,0,80509,0 -8329,31162101,Anclajes de concreto,,0,80509,0 -8330,31162102,Anclajes de cuña,,0,80509,0 -8331,31162103,Anclajes de pared,,0,80509,0 -8332,31162104,Anclajes de tornillo,,0,80509,0 -8333,31162105,Anclajes de resina,,0,80509,0 -8334,31162106,Anclaje de tubería,,0,80509,0 -8335,31162107,Anclajes de expansión de clavo,,0,80509,0 -8336,31162108,Anclajes de amarre,,0,80509,0 -8337,31162109,Anclaje de cimentación,,0,80509,0 -8338,31162200,Remaches,,0,80509,0 -8339,31162201,Remaches ciegos,,0,80509,0 -8340,31162202,Remaches de corona,,0,80509,0 -8341,31162203,Remaches de cabeza plana,,0,80509,0 -8342,31162204,Remaches completos,,0,80509,0 -8343,31162205,Remaches de trinquetes,,0,80509,0 -8344,31162206,Remaches de estañador,,0,80509,0 -8345,31162207,Remaches de compresión,,0,80509,0 -8346,31162208,Remaches de tonelero,,0,80509,0 -8347,31162209,Remaches de botón,,0,80509,0 -8348,31162210,Remaches articulados,,0,80509,0 -8349,31162211,Remaches sólidos,,0,80509,0 -8350,31162212,Remache con mandril,,0,80509,0 -8351,31162213,Remache aerodinámico,,0,80509,0 -8352,31162214,Remache semi – tubular,,0,80509,0 -8353,31162215,Remache tubular,,0,80509,0 -8354,31162216,Remache de hombro,,0,80509,0 -8355,31162300,Ferretería de montaje,,0,80509,0 -8356,31162301,Perfiles de montaje,,0,80509,0 -8357,31162303,Barras de montaje,,0,80509,0 -8358,31162304,Regletas de montaje,,0,80509,0 -8359,31162305,Abrazaderas de montaje,,0,80509,0 -8360,31162306,Soportes colgantes de montaje,,0,80509,0 -8361,31162307,Placas de montaje,,0,80509,0 -8362,31162308,Paneles de montaje,,0,80509,0 -8363,31162309,Estantes de montaje,,0,80509,0 -8364,31162310,Correas de montaje,,0,80509,0 -8365,31162311,Bujes de pared,,0,80509,0 -8366,31162312,Pasadores de montaje,,0,80509,0 -8367,31162313,Kits de montaje,,0,80509,0 -8368,31162314,Espaciador de baldosas,,0,80509,0 -8369,31162400,Fijadores varios,,0,80509,0 -8370,31162401,Aros interiores,,0,80509,0 -8371,31162402,Cerraduras,,0,80509,0 -8372,31162403,Goznes o bisagras,,0,80509,0 -8373,31162404,Grapas,,0,80509,0 -8374,31162405,Tensores,,0,80509,0 -8375,31162406,Cierres para zunchado,Sujetador de estructuras verticales.,0,80509,0 -8376,31162407,Pestillo,"Cerrojo, Pasador",0,80509,0 -8377,31162409,Pasador de horquilla,,0,80509,0 -8378,31162410,Pasadores estriados,,0,80509,0 -8379,31162411,Anillos elásticos,,0,80509,0 -8380,31162412,Horquilla,,0,80509,0 -8381,31162413,Cierre de presión,,0,80509,0 -8382,31162414,Abrazadera,,0,80509,0 -8383,31162415,Uñeta,,0,80509,0 -8384,31162416,Pasadores de conexión o acoplamiento,,0,80509,0 -8385,31162417,Pasadores de alineación,,0,80509,0 -8386,31162418,Ataduras de torsión,,0,80509,0 -8387,31162419,Anillos y eslabones,,0,80509,0 -8388,31162420,Bisagra de ingeniería,,0,80509,0 -8389,31162421,Trampa,,0,80509,0 -8390,31162500,Soportes y puntales,Soportería,0,80509,0 -8391,31162501,Soportes para estanterías,,0,80509,0 -8392,31162502,Soportes en escuadra,,0,80509,0 -8393,31162503,Puntales,,0,80509,0 -8394,31162504,Soportes para accesorios eléctricos,,0,80509,0 -8395,31162505,Soportes de montaje magnético,,0,80509,0 -8396,31162506,Soporte de pared,,0,80509,0 -8397,31162507,Soportes de piñón,,0,80509,0 -8398,31162508,Soporte de eje de ojo,,0,80509,0 -8399,31162600,Ganchos,,0,80509,0 -8400,31162601,Ganchos giratorios,,0,80509,0 -8401,31162602,Ganchos de resorte,,0,80509,0 -8402,31162603,Ganchos en s,,0,80509,0 -8403,31162604,Ganchos de seguridad,,0,80509,0 -8404,31162605,Ganchos de suspensión,,0,80509,0 -8405,31162606,Ganchos en j,,0,80509,0 -8406,31162607,Ganchos de alambre para riostras,,0,80509,0 -8407,31162608,Ganchos de grúa,,0,80509,0 -8408,31162609,Ganchos de atornillar,,0,80509,0 -8409,31162610,Ganchos de tablero,,0,80509,0 -8410,31162611,Gancho de deslizamiento,,0,80509,0 -8411,31162612,Gancho cuadrado,,0,80509,0 -8412,31162613,Gancho en forma de 8,,0,80509,0 -8413,31162614,Gancho fijo,,0,80509,0 -8414,31162700,Roldanas pivotantes y Ruedas,,0,80509,0 -8415,31162701,Ruedas para muebles,,0,80509,0 -8416,31162702,Ruedas,,0,80509,0 -8417,31162703,Deslizadoras,,0,80509,0 -8418,31162704,Puntas de rodillo,Rodillos con puntas,0,80509,0 -8419,31162800,Ferretería en general,,0,80509,0 -8420,31162801,Chapas o pomos,,0,80509,0 -8421,31162802,Insertos,Tubería para insertar,0,80509,0 -8422,31162803,Grilletes,,0,80509,0 -8423,31162804,Topes de puerta,,0,80509,0 -8424,31162805,Cable dedal,,0,80509,0 -8425,31162806,Cubiertas de tornillos,,0,80509,0 -8426,31162807,Palancas,,0,80509,0 -8427,31162808,Barras de pánico,,0,80509,0 -8428,31162809,Pasadores posicionadores,,0,80509,0 -8429,31162810,Empalmes o placas de unión,,0,80509,0 -8430,31162811,Collar del eje,,0,80509,0 -8431,31162812,Mangas de prensado,,0,80509,0 -8432,31162813,Ganchos para cerda de ropa,,0,80509,0 -8433,31162814,Kit de sujetadores varios,,0,80509,0 -8434,31162815,Calce,,0,80509,0 -8435,31162816,Hilera,,0,80509,0 -8436,31162817,Soporte de cuerda de ropa,,0,80509,0 -8437,31162818,Ánodo anti corrosión,,0,80509,0 -8438,31162819,Palanca de control,,0,80509,0 -8439,31162900,Mordazas,,0,80509,0 -8440,31162901,Abrazaderas de espiga,,0,80509,0 -8441,31162902,Abrazaderas de resorte,,0,80509,0 -8442,31162903,Abrazaderas de tornillo,,0,80509,0 -8443,31162904,Abrazadera de cable metálico,,0,80509,0 -8444,31162905,Abrazadera para viga doble t,,0,80509,0 -8445,31162906,Abrazaderas de manguera o tubo,,0,80509,0 -8446,31162907,Abrazadera de extensión,,0,80509,0 -8447,31162908,Abrazadera en miniatura,,0,80509,0 -8448,31162909,Abrazadera isofónica,,0,80509,0 -8449,31162910,Abrazadera de taladro,,0,80509,0 -8450,31162911,Abrazadera de tornillo manual,,0,80509,0 -8451,31162912,Abrazadera de banda,,0,80509,0 -8452,31162913,Abrazadera de banco,,0,80509,0 -8453,31162914,Abrazadera de red,,0,80509,0 -8454,31162915,Abrazadera de ingletes,,0,80509,0 -8455,31162916,Abrazadera de acción rápida,,0,80509,0 -8456,31162917,Abrazadera de agarre rápido,,0,80509,0 -8457,31162918,Abrazadera de potencia,,0,80509,0 -8458,31162919,Abrazadera de velocidad,,0,80509,0 -8459,31162920,Abrazadera de marco de fotos,,0,80509,0 -8460,31163000,Embragues,,0,80509,0 -8461,31163001,Acoples elastoméricos,,0,80509,0 -8462,31163002,Acoples por engranaje,,0,80509,0 -8463,31163003,Acoples metálicos,,0,80509,0 -8464,31163004,Acoples en miniatura,,0,80509,0 -8465,31163005,Manguitos de acoplamiento,,0,80509,0 -8466,31163006,Acoples de rejilla,,0,80509,0 -8467,31163007,Acoples de cadena,,0,80509,0 -8468,31163008,Acoples de disco,,0,80509,0 -8469,31163009,Acoples rígidos,,0,80509,0 -8470,31163010,Acoples de araña,,0,80509,0 -8471,31163011,Acoples de mordaza,,0,80509,0 -8472,31163012,Acoples medios,,0,80509,0 -8473,31163013,Acoples de ejes,,0,80509,0 -8474,31163014,Acoples giratorio,,0,80509,0 -8475,31163015,Acoples oldham,,0,80509,0 -8476,31163016,Acoples de varilla,,0,80509,0 -8477,31163017,Varilla de acople,,0,80509,0 -8478,31163018,Acoples de fuelle,,0,80509,0 -8479,31163019,Acoples magnéticos,,0,80509,0 -8480,31163020,Acoples de brida,,0,80509,0 -8481,31163021,Acople universal flexible,,0,80509,0 -8482,31163022,Acoples de abrazadera,,0,80509,0 -8483,31163023,Acoples de mariposa,,0,80509,0 -8484,31163024,Acoples de brida tipo flexible,,0,80509,0 -8485,31163025,Acoples de mariposa partida,,0,80509,0 -8486,31163026,Acoples de media vuelta,,0,80509,0 -8487,31163027,Acoples de cadena giratoria,,0,80509,0 -8488,31163028,Acoples de rejilla de tipo flexible,,0,80509,0 -8489,31163029,Acoples de clip de fricción,,0,80509,0 -8490,31163030,Acoples duraflex,,0,80509,0 -8491,31163031,Acoples de vendedores,,0,80509,0 -8492,31163032,Adapatador eléctrico de acoples,,0,80509,0 -8493,31163100,Conectores,,0,80509,0 -8494,31163101,Desconectores rápidos,,0,80509,0 -8495,31163102,Férula,,0,80509,0 -8496,31163103,Conector de remolque,,0,80509,0 -8497,31163200,Herraje de retención,,0,80509,0 -8498,31163201,Pasadores de resorte,,0,80509,0 -8499,31163202,Anillos de retención,,0,80509,0 -8500,31163203,Pasador de espiga,,0,80509,0 -8501,31163204,Pasador de horquilla,,0,80509,0 -8502,31163205,Pasadores de cono,,0,80509,0 -8503,31163207,Horquillas de eje o de disco,,0,80509,0 -8504,31163208,Bloquecillo,,0,80509,0 -8505,31163209,Soportes o retenes del rodamiento,,0,80509,0 -8506,31163210,Collares de retención,,0,80509,0 -8507,31163211,Grapas de retención,,0,80509,0 -8508,31163212,Pasadores roscados,,0,80509,0 -8509,31163213,Pasadores de pivote,,0,80509,0 -8510,31163214,Pasadores de deslizamiento,,0,80509,0 -8511,31163215,Pasadores estriados,,0,80509,0 -8512,31163216,Colgador de cable,,0,80509,0 -8513,31163217,Bujes de bloqueo de tubo de desconexión rápida,,0,80509,0 -8514,31163218,Manga para adaptación de rodamiento,,0,80509,0 -8515,31163219,Pasador cilíndrico,,0,80509,0 -8516,31163220,Juego de pines diversos,,0,80509,0 -8517,31163221,Llave de pasador,,0,80509,0 -8518,31163222,Llave cuadrada,,0,80509,0 -8519,31163223,Llave rectangular,,0,80509,0 -8520,31163224,Llave cónica,,0,80509,0 -8521,31163225,Llave de broche de cuña,,0,80509,0 -8522,31163226,Anillo de seguridad,,0,80509,0 -8523,31163227,Anillo en eje (flinger),,0,80509,0 -8524,31163228,Anillo partido,,0,80509,0 -8525,31163229,Clip retenedor metálico,,0,80509,0 -8526,31163230,Clip retenedor plástico,Duola retenedora de plástico,0,80509,0 -8527,31163231,Collar de retención de tubos,Abrazadera,0,80509,0 -8528,31163300,Espaciadores,,0,80509,0 -8529,31163301,Montante de dos extremos,Separador doble broche,0,80509,0 -8530,31163302,Montante para soldar,"Perno de soldadura, Perno para sujetar",0,80509,0 -8531,31163303,Montante de bola,Separador de bola,0,80509,0 -8532,31163304,Montante complejo,Remache espaciador compuesto,0,80509,0 -8533,31163400,Redes de metal y estructuras de cribado,,0,80509,0 -8534,31163401,Gavión,,0,80509,0 -8535,31171500,Rodamientos,Baleros,0,80509,0 -8536,31171501,Rodamientos embridados,,0,80509,0 -8537,31171502,Rodamientos radiales,Baleros con pestaña,0,80509,0 -8538,31171503,Rodamientos de rueda,Baleros para rueda,0,80509,0 -8539,31171504,Rodamientos de balineras,Baleros de balines,0,80509,0 -8540,31171505,Rodamientos de rodillos,Baleros de rodillos,0,80509,0 -8541,31171506,Rodamientos lineales,Baleros lineales,0,80509,0 -8542,31171507,Rodamientos de empuje,"Baleros de empuje, Baleros de presión",0,80509,0 -8543,31171508,Rodamientos de cabeza de biela,Balero para biela,0,80509,0 -8544,31171509,Rodamientos de manguito interior,Balero de manguito,0,80509,0 -8545,31171510,Rodamientos esféricos,Balero esféricos,0,80509,0 -8546,31171511,Soportes de cojinetes,Soportes de cojinetes para baleros,0,80509,0 -8547,31171512,Rodamiento de agujas,"Balancines o semibalancines, Balero de agujas",0,80509,0 -8548,31171513,Rodamientos colgaderos,Baleros colgantes,0,80509,0 -8549,31171515,Rodamientos simples,Baleros simples,0,80509,0 -8550,31171516,Rodamientos cónicos,Baleros cónicos,0,80509,0 -8551,31171518,Jaula de rodamiento,Jaula para baleros,0,80509,0 -8552,31171519,Carcasas o pedestales de rodamientos,,0,80509,0 -8553,31171520,Muñones de rodamientos,,0,80509,0 -8554,31171522,Rodamientos magnéticos,,0,80509,0 -8555,31171523,Rodamientos neumáticos,Baleros neumáticos,0,80509,0 -8556,31171524,Tapas de rodamientos,Tapas de baleros,0,80509,0 -8557,31171525,Revestimientos de rodamiento,,0,80509,0 -8558,31171526,Almohadillas de rodamiento,Empaque de balero,0,80509,0 -8559,31171527,Conos de rodamiento,,0,80509,0 -8560,31171528,Rodamiento partido,,0,80509,0 -8561,31171529,Sombreretes de rodamiento,,0,80509,0 -8562,31171530,Rodamiento de seguimiento de levas,,0,80509,0 -8563,31171531,Rodamiento de contacto angular,,0,80509,0 -8564,31171532,Rodamiento auto – alineante,,0,80509,0 -8565,31171533,Rodamiento y marco ajustables,,0,80509,0 -8566,31171534,Rodamiento de base cónica,,0,80509,0 -8567,31171535,Rodamiento de cartucho,,0,80509,0 -8568,31171536,Rodamiento de insertar,,0,80509,0 -8569,31171537,Rodamiento de combinación,,0,80509,0 -8570,31171538,Rodamiento de cuatro puntos,,0,80509,0 -8571,31171539,Balinera,,0,80509,0 -8572,31171540,Aguja de rodamiento,,0,80509,0 -8573,31171541,Rodillo de rodamiento,,0,80509,0 -8574,31171542,Rodamiento tensionador,,0,80509,0 -8575,31171543,Rodamiento de flexión,,0,80509,0 -8576,31171544,Bloque de rodamiento plummer,,0,80509,0 -8577,31171545,Cubierta de rodamiento,,0,80509,0 -8578,31171546,Rodamiento reversible,,0,80509,0 -8579,31171547,Rodamiento cilíndrico,,0,80509,0 -8580,31171548,Manguito de retiro de rodamiento,,0,80509,0 -8581,31171549,Rodamiento de joyería,,0,80509,0 -8582,31171550,Rodamiento combinado de rodillo y balinera,,0,80509,0 -8583,31171551,Rodamiento sin aceite,,0,80509,0 -8584,31171552,Rodamiento de balinera de empuje,,0,80509,0 -8585,31171553,Rodamiento de rodillo de empuje,,0,80509,0 -8586,31171554,Rodamiento guía,,0,80509,0 -8587,31171555,Ensamble de adaptador de rodamiento de rodillos,,0,80509,0 -8588,31171556,Rodamiento inserto de alto desempeño,,0,80509,0 -8589,31171557,Buje de chumacera,,0,80509,0 -8590,31171558,Rodillo de movimiento lineal y giratorio,,0,80509,0 -8591,31171559,Placa de bloqueo para rodamiento de rodillo,,0,80509,0 -8592,31171560,Anillo de presión de bloqueo para rodamiento de rodillo,,0,80509,0 -8593,31171561,Manguito adaptador para rodamiento de rodillo,,0,80509,0 -8594,31171562,Rodamiento inserto con bloqueo de tornillo,,0,80509,0 -8595,31171563,Rodamiento de insertar con collar de bloqueo excéntrico,,0,80509,0 -8596,31171564,Rodamiento de insertar con anillo interior estándar,,0,80509,0 -8597,31171600,Cojinetes,,0,80509,0 -8598,31171603,Bujes de taladro,,0,80509,0 -8599,31171604,Bujes pilotos,,0,80509,0 -8600,31171605,Bujes de eje,,0,80509,0 -8601,31171606,Bujes de brida,,0,80509,0 -8602,31171607,Buje de desgaste,,0,80509,0 -8603,31171608,Buje en blanco,,0,80509,0 -8604,31171609,Manga de buje,,0,80509,0 -8605,31171610,Buje forjado en frío,,0,80509,0 -8606,31171700,Engranajes,,0,80509,0 -8607,31171704,Engranajes de fricción,,0,80509,0 -8608,31171706,Engranajes cónicos,,0,80509,0 -8609,31171707,Engranajes rectos,,0,80509,0 -8610,31171708,Engranajes biselados,,0,80509,0 -8611,31171709,Engranajes cremallera,,0,80509,0 -8612,31171710,Engranajes piñón,,0,80509,0 -8613,31171711,Engranajes de anillo,,0,80509,0 -8614,31171712,Engranajes de tornillo sin fin,,0,80509,0 -8615,31171713,Engranajes laterales,,0,80509,0 -8616,31171714,Engranajes helicoidales,,0,80509,0 -8617,31171715,Engranajes cilíndricos,,0,80509,0 -8618,31171716,Engranajes en espina de pescado,,0,80509,0 -8619,31171800,Ruedas industriales,,0,80509,0 -8620,31171801,Ruedas dentadas,,0,80509,0 -8621,31171802,Ruedas motrices,,0,80509,0 -8622,31171803,Rueda voladora,Rueda volante,0,80509,0 -8623,31171804,Poleas,,0,80509,0 -8624,31171805,Cepillos de rueda,,0,80509,0 -8625,31171806,Ruedas de rodillos,,0,80509,0 -8626,31171900,Ruedas dentadas,,0,80509,0 -8627,31171901,Ruedas dentadas para cadena de rodillos,,0,80509,0 -8628,31171902,Ruedas dentadas para cadena de ingeniería,,0,80509,0 -8629,31171903,Ruedas dentadas para cadena de transporte,,0,80509,0 -8630,31171904,Ruedas dentadas para cadena silenciosa,,0,80509,0 -8631,31171905,Rueda dentada o polea de cadena,,0,80509,0 -8632,31181700,Embalajes y empaquetaduras,,0,80509,0 -8633,31181701,Empaques,"Embalaje, Paquetería",0,80509,0 -8634,31181702,Prensaestopas,Embalaje de fibra sintética,0,80509,0 -8635,31181703,Deflectores de aceite,,0,80509,0 -8636,31181704,Caja de relleno,Caja para empacar,0,80509,0 -8637,31181705,Anillo de sello de soporte,Junta de anillo de soporte,0,80509,0 -8638,31181800,Cubiertas automotrices,,0,80509,0 -8639,31181801,Manija de palanca de cambios de transmisión automotriz,Cubierta de palanca de cambios,0,80509,0 -8640,31181802,Cubierta de bujía,,0,80509,0 -8641,31191500,Abrasivos y medios de abrasivo,,0,80509,0 -8642,31191501,Papeles abrasivos,Papel de lija,0,80509,0 -8643,31191502,Pulidor,,0,80509,0 -8644,31191504,Telas abrasivas,"Fibra abrasiva, Paño abrasivo",0,80509,0 -8645,31191505,Almohadillas abrasivas,Esponja abrasiva,0,80509,0 -8646,31191506,Discos abrasivos,,0,80509,0 -8647,31191507,Cintas abrasivas,,0,80509,0 -8648,31191508,Polvo de diamante,,0,80509,0 -8649,31191509,Pulidores abrasivos,,0,80509,0 -8650,31191510,Piedras abrasivas,,0,80509,0 -8651,31191511,Virutas de acero,,0,80509,0 -8652,31191512,Chorro de balines o perdigones,Chorreado abrasivo,0,80509,0 -8653,31191513,Cuentas de vidrio,,0,80509,0 -8654,31191514,Medio amortiguador,Maquina de pulido,0,80509,0 -8655,31191515,Malla abrasiva,,0,80509,0 -8656,31191516,Rollos de cartucho abrasivo,,0,80509,0 -8657,31191517,Planchas de esmeril,,0,80509,0 -8658,31191518,Carburo de tungsteno,Carburo de wolframio,0,80509,0 -8659,31191519,Tambores abrasivos,,0,80509,0 -8660,31191520,Abrasivo estrella,,0,80509,0 -8661,31191521,Cepillo abrasivo,,0,80509,0 -8662,31191600,Ruedas abrasivas,,0,80509,0 -8663,31191601,Ruedas abrasivas cúbicas de nitrato borozon,,0,80509,0 -8664,31191602,Ruedas abrasivas de diamante,,0,80509,0 -8665,31201500,Cinta adhesiva,,0,80509,0 -8666,31201501,Cinta de ductos,Cinta americana,0,80509,0 -8667,31201502,Cinta aislante eléctrica,Cinta de aislar o aislante,0,80509,0 -8668,31201503,Cinta de enmascarar,Masking,0,80509,0 -8669,31201504,Cinta para alfombras,,0,80509,0 -8670,31201505,Cinta doble faz,,0,80509,0 -8671,31201506,Cinta de bísmalemida,,0,80509,0 -8672,31201507,Cinta de fibra de vidrio,,0,80509,0 -8673,31201508,Cinta de grafito,,0,80509,0 -8674,31201509,Cinta de nylon,,0,80509,0 -8675,31201510,Cinta impregnada de resina,,0,80509,0 -8676,31201511,Cinta de malla metálica,,0,80509,0 -8677,31201512,Cinta transparente,Diurex,0,80509,0 -8678,31201513,Cintas antideslizantes de seguridad,,0,80509,0 -8679,31201514,Cinta de sellado de hilo de poli tetrafluoretileno (ptfe),Cinta de teflón,0,80509,0 -8680,31201515,Cintas de papel,,0,80509,0 -8681,31201516,Cinta reflectiva,,0,80509,0 -8682,31201517,Cinta para empaquetar,Cinta canela,0,80509,0 -8683,31201518,Cinta conductora de electricidad,,0,80509,0 -8684,31201519,Cinta para reparar tubería o manguera,,0,80509,0 -8685,31201520,Cinta para marcar los pasillos,,0,80509,0 -8686,31201521,Cinta metálica,,0,80509,0 -8687,31201522,Cinta de transferencia adhesiva,,0,80509,0 -8688,31201523,Cinta de tela,,0,80509,0 -8689,31201524,Cinta para codificación de color,,0,80509,0 -8690,31201525,Cinta de vinilo,,0,80509,0 -8691,31201526,Cinta magnética,,0,80509,0 -8692,31201527,Cintas de espuma,,0,80509,0 -8693,31201528,Cinta de montaje,,0,80509,0 -8694,31201529,Cinta de perfilar,,0,80509,0 -8695,31201530,Cinta de papel de aluminio,,0,80509,0 -8696,31201531,Cinta de polietileno,,0,80509,0 -8697,31201532,Cinta de cloruro de polivinilo pvc,"Cinta adhesiva de PVC, Cinta pvc para tuberia",0,80509,0 -8698,31201533,Cinta de hoja de plomo,,0,80509,0 -8699,31201534,Cinta de caucho,,0,80509,0 -8700,31201535,Cinta para empalmes de cables,,0,80509,0 -8701,31201536,Cinta de lanilla de polietileno,,0,80509,0 -8702,31201537,Cinta de arrancar,Cinta de rasgado para empaques,0,80509,0 -8703,31201600,Otros adhesivos y selladores,,"0,1",80509,0 -8704,31201601,Adhesivos químicos,,"0,1",80509,0 -8705,31201602,Pastas,,0,80509,0 -8706,31201603,Gomas,Pegamento de cola,0,80509,0 -8707,31201604,Cementos de caucho,,"0,1",80509,0 -8708,31201605,Masillas,Plastilina epóxica,"0,1",80509,0 -8709,31201606,Calafateos,,"0,1",80509,0 -8710,31201607,Aglomerante epoxy,Resina epoxídica,"0,1",80509,0 -8711,31201608,Adhesivos de espuma,,"0,1",80509,0 -8712,31201609,Adhesivos de termo impregnación,,"0,1",80509,0 -8713,31201610,Pegamentos,Lápiz adhesivo,"0,1",80509,0 -8714,31201611,Adhesivos de lámina,,"0,1",80509,0 -8715,31201612,Selladores de rosca,,"0,1",80509,0 -8716,31201613,Adhesivo reusable,,"0,1",80509,0 -8717,31201614,Lacre,Pasta laca,"0,1",80509,0 -8718,31201615,Activadores del adhesivo,,"0,1",80509,0 -8719,31201616,Adhesivos líquidos,Pegamento líquido,"0,1",80509,0 -8720,31201617,Cementos disolventes,,"0,1",80509,0 -8721,31201618,Compuesto de unión de drywall,,"0,1",80509,0 -8722,31201619,Adhesivo instantáneo,,"0,1",80509,0 -8723,31201620,Adhesivo reactivo,,"0,1",80509,0 -8724,31201621,Adhesivo en aerosol,,"0,1",80509,0 -8725,31201622,Adhesivo multi – propósito,,"0,1",80509,0 -8726,31201623,Adhesivo de contacto,,"0,1",80509,0 -8727,31201624,Adhesivo de montura,,"0,1",80509,0 -8728,31201625,Base de masilla,,"0,1",80509,0 -8729,31201626,Adhesivo sensible a la presión psa,,"0,1",80509,0 -8730,31201627,Adhesivo anaeróbico,,"0,1",80509,0 -8731,31201628,Sellante de impregnación,,"0,1",80509,0 -8732,31201629,Encapsulación de silicona,,"0,1",80509,0 -8733,31201630,Sellante de espuma,,"0,1",80509,0 -8734,31201631,Compuesto sellante,,"0,1",80509,0 -8735,31201632,Adhesivo de silicona,,"0,1",80509,0 -8736,31201633,Adhesivo de fluorocarbono,,"0,1",80509,0 -8737,31201634,Adhesivo acrílico anaeróbico,,"0,1",80509,0 -8738,31201635,Adhesivo de poliuretano,,"0,1",80509,0 -8739,31201636,Adhesivo de cianoacrilato,,"0,1",80509,0 -8740,31201637,Material de comunicación tipo espuma para detener el agua,Espuma contra fugas de agua,"0,1",80509,0 -8741,31211500,Pinturas y tapa poros,,"0,1",80509,0 -8742,31211501,Pinturas de esmalte,,"0,1",80509,0 -8743,31211502,Pinturas de agua,,"0,1",80509,0 -8744,31211503,Pinturas basadas en pigmentos,,"0,1",80509,0 -8745,31211504,Pinturas de revestimiento,,"0,1",80509,0 -8746,31211505,Pinturas de aceite,,"0,1",80509,0 -8747,31211506,Pinturas de látex,,"0,1",80509,0 -8748,31211507,Pinturas en aerosol,,"0,1",80509,0 -8749,31211508,Pinturas acrílicas,,"0,1",80509,0 -8750,31211509,Bases para esmalte,,"0,1",80509,0 -8751,31211510,Bases de poliuretano,,"0,1",80509,0 -8752,31211511,Bases de uretano,,"0,1",80509,0 -8753,31211512,Bases de látex,,"0,1",80509,0 -8754,31211513,Pintura de marcado invertido,,"0,1",80509,0 -8755,31211514,Compuesto de galvanización en frío,,"0,1",80509,0 -8756,31211515,Pintura basada en disolvente,Pinturas de aceite,"0,1",80509,0 -8757,31211516,Pintura de terminado al horno,,"0,1",80509,0 -8758,31211517,Pintura de terminado patrón,Pintura de acabados y patrones,"0,1",80509,0 -8759,31211518,Pintura antióxido,Pintura anticorrosión,"0,1",80509,0 -8760,31211519,Pintura de aluminio,,"0,1",80509,0 -8761,31211520,Pintura anti – incrustante,Pintura anti incrustante,"0,1",80509,0 -8762,31211521,Pintura premezclada,,"0,1",80509,0 -8763,31211522,Pintura de trabajo pesado,Pintura revestimiento,"0,1",80509,0 -8764,31211600,Aditivos para pinturas,,"0,1",80509,0 -8765,31211601,Lechada de cal con cola para blanquear paredes,Revestimiento de cal,"0,1",80509,0 -8766,31211602,Lechada de cal con cola para blanquear paredes,Materiales y acabados texturizados,"0,1",80509,0 -8767,31211603,Secantes de pintura,Secador de pintura,"0,1",80509,0 -8768,31211604,Diluyentes para pinturas,,"0,1",80509,0 -8769,31211605,Agentes antideslizantes,,"0,1",80509,0 -8770,31211606,Agentes niveladores,Aditivos para aplanar,"0,1",80509,0 -8771,31211607,Inhibidor ultravioleta,"Aditivo anti radiación ultravioleta, Aditivo anti-UV","0,1",80509,0 -8772,31211700,Acabados en general,,0,80509,0 -8773,31211701,Vitrificados,Esmalte,0,80509,0 -8774,31211702,Lustres,Aditivos abrillantadores,0,80509,0 -8775,31211703,Lacas,,0,80509,0 -8776,31211704,Sellantes,Selladores,0,80509,0 -8777,31211705,Barniz de laca,,0,80509,0 -8778,31211706,Tinturas,"Pinturas para teñir, Tintes colorantes",0,80509,0 -8779,31211707,Barnices,Barniz,0,80509,0 -8780,31211708,Recubrimiento de polvo,,0,80509,0 -8781,31211709,Tinturas de terminación,Colorantes para acabado,0,80509,0 -8782,31211800,Disolventes y diluyentes para pinturas,,"0,1",80509,0 -8783,31211801,Removedores de pintura o barniz,,"0,1",80509,0 -8784,31211802,Compuestos para arrancar pintura o barniz,,"0,1",80509,0 -8785,31211803,Diluyentes para pinturas y barnices,Thinner,"0,1",80509,0 -8786,31211900,Aplicadores de pintura y accesorios para pintar,,0,80509,0 -8787,31211901,Paños para herramientas,,0,80509,0 -8788,31211902,Herramientas para bordes,,0,80509,0 -8789,31211903,Equipo para protección,,0,80509,0 -8790,31211904,Brochas,,0,80509,0 -8791,31211905,Mezcladores de pintura,,0,80509,0 -8792,31211906,Rodillos de pintar,,0,80509,0 -8793,31211908,Pistolas de pintar,,0,80509,0 -8794,31211909,Bandejas de pintura,,0,80509,0 -8795,31211910,Guantes para pintar,,0,80509,0 -8796,31211912,Varillas telescópicas,Extensión telescópica para rodillo,0,80509,0 -8797,31211913,Boquillas de pintura,"Caps para aerosol, Válvulas para aerosol",0,80509,0 -8798,31211914,Cepillos de aire,Aerógrafo,0,80509,0 -8799,31211915,Coladores de pintura,,0,80509,0 -8800,31211916,Forradores de bandeja de pintura,,0,80509,0 -8801,31211917,Cubiertas para rodillos de pintura,,0,80509,0 -8802,31211918,Accesorio o máscara de pintura,,0,80509,0 -8803,31221600,Productos para curtidos,,0,80509,0 -8804,31221601,Extractos inorgánicos para curtiembre,Extractos inorgánicos para curtir piel,0,80509,0 -8805,31221602,Extractos orgánicos de origen animal para curtiembre,Extractos orgánicos de origen animal para curtir piel,0,80509,0 -8806,31221603,Extractos orgánicos de origen vegetal para curtiembre,Extractos orgánicos de origen vegetal para curtir piel,0,80509,0 -8807,31231100,Material en barra labrada,,0,80509,0 -8808,31231101,Aluminio en barra labrada,,0,80509,0 -8809,31231102,Berilio en barra labrada,,0,80509,0 -8810,31231103,Latón en barra labrada,,0,80509,0 -8811,31231104,Bronce en barra labrada,,0,80509,0 -8812,31231105,Cobre en barra labrada,,0,80509,0 -8813,31231106,Hierro en barra labrada,,0,80509,0 -8814,31231107,Plomo en barra labrada,,0,80509,0 -8815,31231108,Magnesio en barra labrada,,0,80509,0 -8816,31231109,Metal precioso en barra labrada,,0,80509,0 -8817,31231110,Acero inoxidable en barra labrada,,0,80509,0 -8818,31231111,Estaño en barra labrada,,0,80509,0 -8819,31231112,Titanio en barra labrada,,0,80509,0 -8820,31231113,Cinc en barra labrada,,0,80509,0 -8821,31231114,Aleación no ferrosa en barra labrada,,0,80509,0 -8822,31231115,Aleación ferrosa en barra labrada,,0,80509,0 -8823,31231116,Acero en barra labrada,,0,80509,0 -8824,31231117,Compuestos en barra labrada,,0,80509,0 -8825,31231118,Aleación de níquel en barra labrada,,0,80509,0 -8826,31231119,Material no metálico en barra labrada,,0,80509,0 -8827,31231120,Acero plata en barra labrada,,0,80509,0 -8828,31231200,Material en placas labrado,,0,80509,0 -8829,31231201,Aluminio en placa labrada,,0,80509,0 -8830,31231202,Berilio en placa labrada,,0,80509,0 -8831,31231203,Latón en placa labrada,,0,80509,0 -8832,31231204,Bronce en placa labrada,,0,80509,0 -8833,31231205,Cobre en placa labrada,,0,80509,0 -8834,31231206,Hierro en placa labrada,,0,80509,0 -8835,31231207,Plomo en placa labrada,,0,80509,0 -8836,31231208,Magnesio en placa labrada,,0,80509,0 -8837,31231209,Metal precioso en placa labrada,,0,80509,0 -8838,31231210,Acero inoxidable en placa labrada,,0,80509,0 -8839,31231211,Estaño en placa labrada,,0,80509,0 -8840,31231212,Titanio en placa labrada,,0,80509,0 -8841,31231213,Cinc en placa labrada,,0,80509,0 -8842,31231214,Aleación no ferrosa en placa labrada,,0,80509,0 -8843,31231215,Aleación ferrosa en placa labrada,,0,80509,0 -8844,31231216,Acero en placa labrada,,0,80509,0 -8845,31231217,Compuestos en placa labrada,,0,80509,0 -8846,31231218,Aleación de níquel en placa labrada,,0,80509,0 -8847,31231219,Material no metálico en placa labrada,,0,80509,0 -8848,31231400,Suplementos,,0,80509,0 -8849,31231401,Platina de latón,,0,80509,0 -8850,31231402,Platina de acero,,0,80509,0 -8851,31231403,Platina de acero inoxidable,,0,80509,0 -8852,31231404,Platina de aluminio,,0,80509,0 -8853,31231405,Platina de cobre,,0,80509,0 -8854,31241500,Lentes y prismas,,0,80509,0 -8855,31241501,Lentes,,0,80509,0 -8856,31241502,Prismas,,0,80509,0 -8857,31241600,Cristales ópticos,,0,80509,0 -8858,31241601,Cristales de filtro,Cristales bloqueadores,0,80509,0 -8859,31241602,Discos de cristal,,0,80509,0 -8860,31241603,Vidrio moldeado,,0,80509,0 -8861,31241604,Cristales de prismas,,0,80509,0 -8862,31241605,Cristales de silicio,,0,80509,0 -8863,31241606,Cristales de germanio,,0,80509,0 -8864,31241607,Barras redondas,,0,80509,0 -8865,31241608,Barras cuadradas,,0,80509,0 -8866,31241609,Cristales indicadores de muestra,,0,80509,0 -8867,31241610,Blancos de material óptico infrarrojo,Cristal liso,0,80509,0 -8868,31241700,Espejos,,0,80509,0 -8869,31241701,Espejos torneados con herramienta de diamante,,0,80509,0 -8870,31241702,Espejos metálicos,,0,80509,0 -8871,31241703,Espejos parabólicos,,0,80509,0 -8872,31241704,Espejos sin revestimiento,,0,80509,0 -8873,31241705,Espejos para láser,,0,80509,0 -8874,31241706,Espejos planos,,0,80509,0 -8875,31241800,Filtros ópticos,,0,80509,0 -8876,31241801,Filtros ópticos de banda ancha,,0,80509,0 -8877,31241802,Filtros de gradiente,,0,80509,0 -8878,31241803,Filtros infrarrojos,,0,80509,0 -8879,31241804,Filtros de láser,,0,80509,0 -8880,31241805,Filtros de banda estrecha,,0,80509,0 -8881,31241806,Filtros de película de plástico,,0,80509,0 -8882,31241807,Filtros visuales,,0,80509,0 -8883,31241808,Filtros dicróicos,,0,80509,0 -8884,31241809,Filtros de lyot,,0,80509,0 -8885,31241810,Filtros de gelatina,,0,80509,0 -8886,31241811,Filtros de color,,0,80509,0 -8887,31241812,Filtros polarizantes,,0,80509,0 -8888,31241813,Filtros de reflexión,,0,80509,0 -8889,31241814,Filtros de succión,,0,80509,0 -8890,31241815,Filtros de interferencia,,0,80509,0 -8891,31242100,Accesorios de dispositivos ópticos,,0,80509,0 -8892,31242101,Monturas ópticas,,0,80509,0 -8893,31242103,Aberturas o ranuras ópticas,,0,80509,0 -8894,31242104,Soportes o carriles ópticos,,0,80509,0 -8895,31242105,Identificadores de fibra óptica,,0,80509,0 -8896,31242106,Recubrimientos ópticos,,0,80509,0 -8897,31242107,Tablero de pruebas ópticas,,0,80509,0 -8898,31242200,Componentes ópticos variados,,0,80509,0 -8899,31242201,Divisores del haz óptico,,0,80509,0 -8900,31242202,Polarizadores,,0,80509,0 -8901,31242203,Despolarizadores,,0,80509,0 -8902,31242204,Difusores ópticos,,0,80509,0 -8903,31242205,Retardadores ópticos,,0,80509,0 -8904,31242206,Vidrios ópticamente planos,,0,80509,0 -8905,31242207,Montajes ópticos experimentales,,0,80509,0 -8906,31242208,Picadores ópticos,,0,80509,0 -8907,31251500,Activadores,,0,80509,0 -8908,31251501,Activadores eléctricos,,0,80509,0 -8909,31251502,Activadores electrónicos,,0,80509,0 -8910,31251503,Activadores hidráulicos,,0,80509,0 -8911,31251504,Activadores neumáticos,,0,80509,0 -8912,31251505,Servoválvula,,0,80509,0 -8913,31251506,Activadores por engranajes,,0,80509,0 -8914,31251507,Activadores giratorios,,0,80509,0 -8915,31251508,Activadores fotoeléctricos,,0,80509,0 -8916,31251509,Activadores electromagnéticos,,0,80509,0 -8917,31251510,Solenoides,,0,80509,0 -8918,31251511,Activadores lineales,,0,80509,0 -8919,31251512,Activadores automotrices hvac,,0,80509,0 -8920,31251513,Activadores de compuerta,,0,80509,0 -8921,31251514,Activador electromecánico de aeronaves,,0,80509,0 -8922,31251600,Componentes de robot,,0,80509,0 -8923,31251601,Efectores finales robóticos,,0,80509,0 -8924,31261500,Cubiertas y cajas,,0,80509,0 -8925,31261501,Cubiertas y carcasas de plástico,,0,80509,0 -8926,31261502,Cubiertas y carcasas de metal,,0,80509,0 -8927,31261503,Cubiertas y carcasas de acero,,0,80509,0 -8928,31261504,Carcasas de cajas de cambios,,0,80509,0 -8929,31261505,Carcasas de embrague,,0,80509,0 -8930,31261600,Cascos y envolturas,,0,80509,0 -8931,31261601,Envoltorios o recubrimientos de plástico,Armazón o cubierta de plástico,0,80509,0 -8932,31261602,Envoltorios o recubrimientos de metal,Armazón o cubierta metálica,0,80509,0 -8933,31261603,Envoltorios o recubrimientos de acero,Armazón o cubierta de acero,0,80509,0 -8934,31261700,Cerramientos o alojamientos insonorizantes,,0,80509,0 -8935,31261701,Carcasa insonorizante de máquina,,0,80509,0 -8936,31261702,Cerramiento insonorizante de conjunto de generador montado,,0,80509,0 -8937,31261703,Carcasa insonorizante de bomba,,0,80509,0 -8938,31261704,Carcasa insonorizante de admisión de aire,,0,80509,0 -8939,31271600,Piezas hechas con torno de roscar,,0,80509,0 -8940,31271601,Piezas hechas en torno de roscar de metal,,0,80509,0 -8941,31271602,Piezas hechas en torno de roscar no metales,,0,80509,0 -8942,31281500,Componentes estampados,Elementos para estampados,0,80509,0 -8943,31281502,Componentes de aluminio estampados,,0,80509,0 -8944,31281503,Componentes de aleaciones ferrosas estampados,,0,80509,0 -8945,31281504,Componentes de hierro estampados,,0,80509,0 -8946,31281505,Componentes de aleaciones no ferrosas estampados,,0,80509,0 -8947,31281506,Componentes de acero inoxidable estampados,,0,80509,0 -8948,31281507,Componentes de acero al carbono estampados,,0,80509,0 -8949,31281508,Componentes de magnesio estampados,,0,80509,0 -8950,31281509,Componentes de zinc estampados,,0,80509,0 -8951,31281510,Componentes de estaño estampados,,0,80509,0 -8952,31281511,Componentes de titanio estampados,,0,80509,0 -8953,31281512,Componentes de berilio estampados,,0,80509,0 -8954,31281513,Componentes de metal precioso estampados,,0,80509,0 -8955,31281514,Componentes de cobre estampados,,0,80509,0 -8956,31281515,Componentes de plomo estampados,,0,80509,0 -8957,31281516,Componentes de latón estampados,,0,80509,0 -8958,31281517,Componentes de bronce estampados,,0,80509,0 -8959,31281518,Componentes compuestos estampados,,0,80509,0 -8960,31281519,Componentes de aleación de níquel estampados,,0,80509,0 -8961,31281520,Componentes no metálicos estampados,,0,80509,0 -8962,31281521,Estampados recubiertos,,0,80509,0 -8963,31281522,Componente de berilio cobre estampado,Elemento para estampado de cobre berilio,0,80509,0 -8964,31281523,Componente estampado de cobre latón plateado,Elemento para estampado de cobre latón plateado,0,80509,0 -8965,31281524,Componente estampado de aluminio sae serie 1000,Elemento para estampado de aluminio sae serie 1000,0,80509,0 -8966,31281525,Componente estampado de aluminio sae serie 3000,Elemento para estampado de aluminio sae serie 3000,0,80509,0 -8967,31281526,Componente estampado de aluminio sae serie 5000,Elemento para estampado de aluminio sae serie 5000,0,80509,0 -8968,31281527,Componente estampado de aluminio sae serie 6000,Elemento para estampado de aluminio sae serie 6000,0,80509,0 -8969,31281528,Componente estampado de inconel,Elemento para estampado de inconel,0,80509,0 -8970,31281529,Componente estampado de acero al carbón sae serie 1000 enrollado en frío recubierto de estaño,,0,80509,0 -8971,31281530,Componente estampado de acero al carbón sae serie 1000 enrollado en caliente,,0,80509,0 -8972,31281531,Componente estampado de aleación de aleación de acero enrollada en caliente,,0,80509,0 -8973,31281532,Componente estampado de acero al carbón sae serie 1000 enrollado en frío,,0,80509,0 -8974,31281533,Componente estampado de acero inoxidable sae serie 200 enrollado en frío,,0,80509,0 -8975,31281534,Componente estampado de acero inoxidable sae serie 300 enrollado en frío,,0,80509,0 -8976,31281535,Componente estampado de acero inoxidable sae serie 400 enrollado en frío,,0,80509,0 -8977,31281536,Componente estampado de aleación de aleación de acero enrollada en frío,,0,80509,0 -8978,31281537,Componente estampado de acero al carbón sae serie 1000 enrollado en frío galvanizado en caliente por inmersión,,0,80509,0 -8979,31281538,Componente estampado de acero al carbón sae serie 1000 enrollado en frío electro galvanizado,,0,80509,0 -8980,31281700,Componentes soldados,,0,80509,0 -8981,31281701,Componentes de metal soldado,Elementos de metal soldado,0,80509,0 -8982,31281800,Componentes perforados,,0,80509,0 -8983,31281801,Componentes de aluminio perforados,,0,80509,0 -8984,31281802,Componentes de aleación ferrosa perforados,,0,80509,0 -8985,31281803,Componentes de hierro perforados,,0,80509,0 -8986,31281804,Componentes de aleaciones no ferrosas perforados,,0,80509,0 -8987,31281805,Componentes de acero inoxidable perforados,,0,80509,0 -8988,31281806,Componentes de acero al carbono perforados,,0,80509,0 -8989,31281807,Componentes compuestos perforados,Compuestos perforados,0,80509,0 -8990,31281808,Componentes de aleación de níquel perforados,Aleación de níquel perforado,0,80509,0 -8991,31281809,Componentes no metálicos perforados,,0,80509,0 -8992,31281810,Componentes de titanio perforados,,0,80509,0 -8993,31281811,Componentes de berilio perforados,,0,80509,0 -8994,31281812,Componentes de metal precioso perforados,,0,80509,0 -8995,31281813,Componentes de cobre perforados,,0,80509,0 -8996,31281814,Componentes de plomo perforados,,0,80509,0 -8997,31281815,Componentes de latón perforados,,0,80509,0 -8998,31281816,Componentes de bronce perforados,,0,80509,0 -8999,31281817,Componentes de magnesio perforados,,0,80509,0 -9000,31281818,Componentes de zinc perforados,,0,80509,0 -9001,31281819,Componentes de estaño perforados,,0,80509,0 -9002,31282000,Componentes hidro formados,,0,80509,0 -9003,31282001,Componentes de aluminio hidroformados,,0,80509,0 -9004,31282002,Componentes de berilio hidroformados,,0,80509,0 -9005,31282003,Componentes de latón hidroformados,,0,80509,0 -9006,31282004,Componentes de bronce hidroformados,,0,80509,0 -9007,31282005,Componentes compuestos hidroformados,,0,80509,0 -9008,31282006,Componentes de cobre hidroformados,,0,80509,0 -9009,31282007,Componentes de aleación ferrosa hidroformados,,0,80509,0 -9010,31282008,Componentes de hierro hidroformados,,0,80509,0 -9011,31282009,Componentes de plomo hidroformados,,0,80509,0 -9012,31282010,Componentes de magnesio hidroformados,,0,80509,0 -9013,31282011,Componentes de aleación de níquel hidroformados,,0,80509,0 -9014,31282012,Componentes de aleación no ferrosa hidroformados,,0,80509,0 -9015,31282013,Componentes no metálicos hidroformados,,0,80509,0 -9016,31282014,Componentes de metal precioso hidroformados,,0,80509,0 -9017,31282015,Componentes de acero inoxidable hidroformados,,0,80509,0 -9018,31282016,Componentes de acero hidroformados,,0,80509,0 -9019,31282017,Componentes de estaño hidroformados,,0,80509,0 -9020,31282018,Componentes de titanio hidroformados,,0,80509,0 -9021,31282019,Componentes de zinc hidroformados,,0,80509,0 -9022,31282100,Componentes formados con rotación,,0,80509,0 -9023,31282101,Componentes de aluminio formados en torno,,0,80509,0 -9024,31282102,Componentes de berilio formados en torno,,0,80509,0 -9025,31282103,Componentes de latón formados en torno,,0,80509,0 -9026,31282104,Componentes de bronce formados en torno,,0,80509,0 -9027,31282105,Componentes compuestos formados en torno,,0,80509,0 -9028,31282106,Componentes de cobre formados en torno,,0,80509,0 -9029,31282107,Componentes de aleación ferrosa formados en torno,,0,80509,0 -9030,31282108,Componentes de hierro formados en torno,,0,80509,0 -9031,31282109,Componentes de plomo formados en torno,,0,80509,0 -9032,31282110,Componentes de magnesio formados en torno,,0,80509,0 -9033,31282111,Componentes de aleación de níquel formados en torno,,0,80509,0 -9034,31282112,Componentes de aleación no ferrosa formados en torno,,0,80509,0 -9035,31282113,Componentes no metálicos formados en torno,,0,80509,0 -9036,31282114,Componentes de metal precioso formados en torno,,0,80509,0 -9037,31282115,Componentes de acero inoxidable formados en torno,,0,80509,0 -9038,31282116,Componentes de acero formados en torno,,0,80509,0 -9039,31282117,Componentes de estaño formados en torno,,0,80509,0 -9040,31282118,Componentes de titanio formados en torno,,0,80509,0 -9041,31282119,Componentes de zinc formados en torno,,0,80509,0 -9042,31282200,Componentes formados con rodillo,,0,80509,0 -9043,31282201,Componentes de aluminio formados enrollados,,0,80509,0 -9044,31282202,Componentes de berilio formados enrollados,,0,80509,0 -9045,31282203,Componentes de latón formados enrollados,,0,80509,0 -9046,31282204,Componentes de bronce formados enrollados,,0,80509,0 -9047,31282205,Componentes compuestos formados enrollados,,0,80509,0 -9048,31282206,Componentes de cobre formados enrollados,,0,80509,0 -9049,31282207,Componentes de aleación ferrosa formados enrollados,,0,80509,0 -9050,31282208,Componentes de hierro formados enrollados,,0,80509,0 -9051,31282209,Componentes de plomo formados enrollados,,0,80509,0 -9052,31282210,Componentes de magnesio formados enrollados,,0,80509,0 -9053,31282211,Componentes de aleación de níquel formados enrollados,,0,80509,0 -9054,31282212,Componentes de aleación no ferrosa formados enrollados,,0,80509,0 -9055,31282213,Componentes no metálicos formados enrollados,,0,80509,0 -9056,31282214,Componentes de metal precioso formados enrollados,,0,80509,0 -9057,31282215,Componentes de acero inoxidable formados enrollados,,0,80509,0 -9058,31282216,Componentes de acero formados enrollados,,0,80509,0 -9059,31282217,Componentes de estaño formados enrollados,,0,80509,0 -9060,31282218,Componentes de titanio formados enrollados,,0,80509,0 -9061,31282219,Componentes de zinc formados enrollados,,0,80509,0 -9062,31282300,Componentes formados por estiramiento por presión,,0,80509,0 -9063,31282301,Componentes de aluminio formados por estiramiento por presión,,0,80509,0 -9064,31282302,Componentes de berilio formados por estiramiento por presión,,0,80509,0 -9065,31282303,Componentes de latón formados por estiramiento por presión,,0,80509,0 -9066,31282304,Componentes de bronce formados por estiramiento por presión,,0,80509,0 -9067,31282305,Componentes compuestos formados por estiramiento por presión,,0,80509,0 -9068,31282306,Componentes de cobre formados por estiramiento por presión,,0,80509,0 -9069,31282307,Componentes de aleación ferrosa formados por estiramiento por presión,,0,80509,0 -9070,31282308,Componentes de hierro formados por estiramiento por presión,,0,80509,0 -9071,31282309,Componentes de plomo formados por estiramiento por presión,,0,80509,0 -9072,31282310,Componentes de magnesio formados por estiramiento por presión,,0,80509,0 -9073,31282311,Componentes de aleación de níquel formados por estiramiento por presión,,0,80509,0 -9074,31282312,Componentes de aleación no ferrosa formados por estiramiento por presión,,0,80509,0 -9075,31282313,Componentes no metálicos formados por estiramiento por presión,,0,80509,0 -9076,31282314,Componentes de metal precioso formados por estiramiento por presión,,0,80509,0 -9077,31282315,Componentes de acero inoxidable formados por estiramiento por presión,,0,80509,0 -9078,31282316,Componentes de acero formados por estiramiento por presión,,0,80509,0 -9079,31282317,Componentes de estaño formados por estiramiento por presión,,0,80509,0 -9080,31282318,Componentes de titanio formados por estiramiento por presión,,0,80509,0 -9081,31282319,Componentes de zinc formados por estiramiento por presión,,0,80509,0 -9082,31291200,Estiramientos por presión de impacto labrados,,0,80509,0 -9083,31291201,Componentes de aluminio maquinados por extrusión de impacto,,0,80509,0 -9084,31291202,Componentes de berilio maquinados por extrusión de impacto,,0,80509,0 -9085,31291203,Componentes de latón maquinados por extrusión de impacto,,0,80509,0 -9086,31291204,Componentes de bronce maquinados por extrusión de impacto,,0,80509,0 -9087,31291205,Componentes de cobre maquinados por extrusión de impacto,,0,80509,0 -9088,31291206,Componentes de aleación ferrosa maquinados por extrusión de impacto,,0,80509,0 -9089,31291207,Componentes de plomo maquinados por extrusión de impacto,,0,80509,0 -9090,31291208,Componentes de magnesio maquinados por extrusión de impacto,,0,80509,0 -9091,31291209,Componentes de aleación no ferrosa maquinados por extrusión de impacto,,0,80509,0 -9092,31291210,Componentes plásticos maquinados por extrusión de impacto,,0,80509,0 -9093,31291211,Componentes de metal precioso maquinados por extrusión de impacto,,0,80509,0 -9094,31291212,Componentes de caucho maquinados por extrusión de impacto,,0,80509,0 -9095,31291213,Componentes de acero inoxidable maquinados por extrusión de impacto,,0,80509,0 -9096,31291214,Componentes de acero maquinados por extrusión de impacto,,0,80509,0 -9097,31291215,Componentes de estaño maquinados por extrusión de impacto,,0,80509,0 -9098,31291216,Componentes de titanio maquinados por extrusión de impacto,,0,80509,0 -9099,31291217,Componentes de zinc maquinados por extrusión de impacto,,0,80509,0 -9100,31291218,Componentes compuestos maquinados por extrusión de impacto,,0,80509,0 -9101,31291219,Componentes de aleación de níquel maquinados por extrusión de impacto,,0,80509,0 -9102,31291220,Componentes no metálicos maquinados por extrusión de impacto,,0,80509,0 -9103,31291400,Estiramientos por presión en caliente labrados,,0,80509,0 -9104,31291401,Componentes de aluminio maquinados por extrusión en caliente,,0,80509,0 -9105,31291402,Componentes de berilio maquinados por extrusión en caliente,,0,80509,0 -9106,31291403,Componentes de latón maquinados por extrusión en caliente,,0,80509,0 -9107,31291404,Componentes de bronce maquinados por extrusión en caliente,,0,80509,0 -9108,31291405,Componentes de cobre maquinados por extrusión en caliente,,0,80509,0 -9109,31291406,Componentes de aleación ferrosa maquinados por extrusión en caliente,,0,80509,0 -9110,31291407,Componentes de plomo maquinados por extrusión en caliente,,0,80509,0 -9111,31291408,Componentes de magnesio maquinados por extrusión en caliente,,0,80509,0 -9112,31291409,Componentes de aleación no ferrosa maquinados por extrusión en caliente,,0,80509,0 -9113,31291410,Componentes plásticos maquinados por extrusión en caliente,,0,80509,0 -9114,31291411,Componentes de metal precioso maquinados por extrusión en caliente,,0,80509,0 -9115,31291412,Componentes de caucho maquinados por extrusión en caliente,,0,80509,0 -9116,31291413,Componentes de acero inoxidable maquinados por extrusión en caliente,,0,80509,0 -9117,31291414,Componentes de acero maquinados por extrusión en caliente,,0,80509,0 -9118,31291415,Componentes de estaño maquinados por extrusión en caliente,,0,80509,0 -9119,31291416,Componentes de titanio maquinados por extrusión en caliente,,0,80509,0 -9120,31291417,Componentes de zinc maquinados por extrusión en caliente,,0,80509,0 -9121,31291418,Componentes compuestos maquinados por extrusión en caliente,,0,80509,0 -9122,31291419,Componentes de aleación de níquel maquinados por extrusión en caliente,,0,80509,0 -9123,31291420,Componentes no metálicos maquinados por extrusión en caliente,,0,80509,0 -9124,31321100,Conjuntos ligados de material en barras,,0,80509,0 -9125,31321101,Ensambles de barras pegadas de aluminio,,0,80509,0 -9126,31321102,Ensambles de barras pegadas de acero al carbono,,0,80509,0 -9127,31321103,Ensambles de barras pegadas de aleación hast x,Ensambles de barras pegadas de aleación hastelloy x,0,80509,0 -9128,31321104,Ensambles de barras pegadas de inconel,,0,80509,0 -9129,31321105,Ensambles de barras pegadas de acero de aleación baja,,0,80509,0 -9130,31321106,Ensambles de barras pegadas no metálica,,0,80509,0 -9131,31321109,Ensambles de barras pegadas de acero inoxidable,,0,80509,0 -9132,31321110,Ensambles de barras pegadas de titanio,,0,80509,0 -9133,31321111,Ensambles de barras pegadas de aleación wasp,,0,80509,0 -9134,31321112,Ensambles de barras pegadas de cobre,,0,80509,0 -9135,31321113,Ensambles de barras pegadas de latón,,0,80509,0 -9136,31321400,Conjuntos soldados o soldados en fuerte de material en barras,,0,80509,0 -9137,31321401,Ensambles de barras soldadas con soldadura fuerte o débil de aluminio,,0,80509,0 -9138,31321402,Ensambles de barras soldadas con soldadura fuerte o débil de acero al carbono,,0,80509,0 -9139,31321403,Ensambles de barras soldadas con soldadura fuerte o débil de aleación hast x,Ensambles de barras soldadas con soldadura fuerte o débil de aleación hastelloy x,0,80509,0 -9140,31321404,Ensambles de barras soldadas con soldadura fuerte o débil de inconel,,0,80509,0 -9141,31321405,Ensambles de barras soldadas con soldadura fuerte o débil de acero de aleación baja,,0,80509,0 -9142,31321406,Ensambles de barras soldadas con soldadura fuerte o débil no metálica,Ensambles de barras soldadas con soldadura fuerte o débil plástica,0,80509,0 -9143,31321409,Ensambles de barras soldadas con soldadura fuerte o débil de acero inoxidable,,0,80509,0 -9144,31321410,Ensambles de barras soldadas con soldadura fuerte o débil de titanio,,0,80509,0 -9145,31321411,Ensambles de barras soldadas con soldadura fuerte o débil de aleación wasp,,0,80509,0 -9146,31321412,Ensambles de barras soldadas con soldadura fuerte o débil de cobre,,0,80509,0 -9147,31321413,Ensambles de barras soldadas con soldadura fuerte o débil de latón,,0,80509,0 -9148,31331100,Conjuntos estructurales con acabado ligado,,0,80509,0 -9149,31331101,Ensambles estructurales pegados de aluminio,,0,80509,0 -9150,31331102,Ensambles estructurales pegados de acero al carbono,,0,80509,0 -9151,31331103,Ensambles estructurales pegados de aleación hast x,Ensambles estructurales pegados de aleación hastelloy x,0,80509,0 -9152,31331104,Ensambles estructurales pegados de inconel,,0,80509,0 -9153,31331105,Ensambles estructurales pegados de acero de aleación baja,,0,80509,0 -9154,31331106,Ensambles estructurales pegados no metálica,Ensambles estructurales pegados de plástico,0,80509,0 -9155,31331109,Ensambles estructurales pegados de acero inoxidable,,0,80509,0 -9156,31331110,Ensambles estructurales pegados de titanio,,0,80509,0 -9157,31331111,Ensambles estructurales pegados de aleación wasp,,0,80509,0 -9158,31331112,Ensambles estructurales pegados de cobre,,0,80509,0 -9159,31331113,Ensambles estructurales pegados de latón,,0,80509,0 -9160,31331200,Conjuntos estructurales empernados,,0,80509,0 -9161,31331201,Ensambles estructurales atornillados de aluminio,,0,80509,0 -9162,31331202,Ensambles estructurales atornillados de acero al carbono,,0,80509,0 -9163,31331203,Ensambles estructurales atornillados de aleación hast x,,0,80509,0 -9164,31331204,Ensambles estructurales atornillados de inconel,,0,80509,0 -9165,31331205,Ensambles estructurales atornillados de acero de aleación baja,,0,80509,0 -9166,31331206,Ensambles estructurales atornillados no metálica,,0,80509,0 -9167,31331209,Ensambles estructurales atornillados de acero inoxidable,,0,80509,0 -9168,31331210,Ensambles estructurales atornillados de titanio,,0,80509,0 -9169,31331211,Ensambles estructurales atornillados de aleación wasp,,0,80509,0 -9170,31331212,Ensambles estructurales atornillados de cobre,,0,80509,0 -9171,31331213,Ensambles estructurales atornillados de latón,,0,80509,0 -9172,31331300,Conjuntos estructurales soldado sónico,,0,80509,0 -9173,31331301,Ensambles estructurales con soldadura sónica de aluminio,,0,80509,0 -9174,31331302,Ensambles estructurales con soldadura sónica de acero al carbono,,0,80509,0 -9175,31331303,Ensambles estructurales con soldadura sónica de aleación hast x,Ensambles estructurales con soldadura sónica de aleación hastelloy x,0,80509,0 -9176,31331304,Ensambles estructurales con soldadura sónica de inconel,,0,80509,0 -9177,31331305,Ensambles estructurales con soldadura sónica de acero de aleación baja,,0,80509,0 -9178,31331306,Ensambles estructurales con soldadura sónica no metálica,Ensambles estructurales con soldadura sónica plástica,0,80509,0 -9179,31331309,Ensambles estructurales con soldadura sónica de acero inoxidable,,0,80509,0 -9180,31331310,Ensambles estructurales con soldadura sónica de titanio,,0,80509,0 -9181,31331311,Ensambles estructurales con soldadura sónica de aleación wasp,,0,80509,0 -9182,31331312,Ensambles estructurales con soldadura sónica de cobre,,0,80509,0 -9183,31331313,Ensambles estructurales con soldadura sónica de latón,,0,80509,0 -9184,31331400,Conjuntos estructurales soldados con ultra violeta UV,,0,80509,0 -9185,31331401,Ensambles estructurales con soldadura ultra violeta de aluminio,,0,80509,0 -9186,31331402,Ensambles estructurales con soldadura ultra violeta de acero al carbono,,0,80509,0 -9187,31331403,Ensambles estructurales con soldadura ultra violeta de aleación hast x,Ensambles estructurales con soldadura ultra violeta de aleación hastelloy x,0,80509,0 -9188,31331404,Ensambles estructurales con soldadura ultra violeta de inconel,,0,80509,0 -9189,31331405,Ensambles estructurales con soldadura ultra violeta de acero de aleación baja,,0,80509,0 -9190,31331406,Ensambles estructurales con soldadura ultra violeta no metálica,Ensambles estructurales con soldadura ultra violeta plástica,0,80509,0 -9191,31331409,Ensambles estructurales con soldadura ultra violeta de acero inoxidable,,0,80509,0 -9192,31331410,Ensambles estructurales con soldadura ultra violeta de titanio,,0,80509,0 -9193,31331411,Ensambles estructurales con soldadura ultra violeta de aleación wasp,,0,80509,0 -9194,31331412,Ensambles estructurales con soldadura ultra violeta de cobre,,0,80509,0 -9195,31331413,Ensambles estructurales con soldadura ultra violeta de latón,,0,80509,0 -9196,31331600,Conjuntos estructurales soldado o soldado con latón,,0,80509,0 -9197,31331601,Ensambles estructurales con soldadura de fuerte o débil de aluminio,,0,80509,0 -9198,31331602,Ensambles estructurales con soldadura de fuerte o débil de acero al carbono,,0,80509,0 -9199,31331603,Ensambles estructurales con soldadura de fuerte o débil de aleación hast x,Ensambles estructurales con soldadura de fuerte o débil de aleación hastelloy x,0,80509,0 -9200,31331604,Ensambles estructurales con soldadura de fuerte o débil de inconel,,0,80509,0 -9201,31331605,Ensambles estructurales con soldadura de fuerte o débil de acero de aleación baja,,0,80509,0 -9202,31331606,Ensambles estructurales con soldadura de fuerte o débil no metálica,Ensambles estructurales con soldadura de fuerte o débil plástica,0,80509,0 -9203,31331609,Ensambles estructurales con soldadura de fuerte o débil de acero inoxidable,,0,80509,0 -9204,31331610,Ensambles estructurales con soldadura de fuerte o débil de titanio,,0,80509,0 -9205,31331611,Ensambles estructurales con soldadura de fuerte o débil de aleación wasp,,0,80509,0 -9206,31331612,Ensambles estructurales con soldadura de fuerte o débil de cobre,,0,80509,0 -9207,31331613,Ensambles estructurales con soldadura de fuerte o débil de latón,,0,80509,0 -9208,31331700,Conjuntos estructurales remachados,,0,80509,0 -9209,31331701,Ensambles estructurales remachados de aluminio,,0,80509,0 -9210,31331702,Ensambles estructurales remachados de acero al carbono,,0,80509,0 -9211,31331703,Ensambles estructurales remachados de aleación hast x,Ensambles estructurales remachados de aleación hastelloy x,0,80509,0 -9212,31331704,Ensambles estructurales remachados de inconel,,0,80509,0 -9213,31331705,Ensambles estructurales remachados de acero de aleación baja,,0,80509,0 -9214,31331706,Ensambles estructurales remachados no metálica,Ensambles estructurales remachados de plástico,0,80509,0 -9215,31331709,Ensambles estructurales remachados de acero inoxidable,,0,80509,0 -9216,31331710,Ensambles estructurales remachados de titanio,,0,80509,0 -9217,31331711,Ensambles estructurales remachados de aleación wasp,,0,80509,0 -9218,31331712,Ensambles estructurales remachados de cobre,,0,80509,0 -9219,31331713,Ensambles estructurales remachados de latón,,0,80509,0 -9220,31341100,Conjuntos de placa soldado o soldado con latón,,0,80509,0 -9221,31341101,Ensambles de láminas soldadas con soldadura fuerte o débil de aluminio,,0,80509,0 -9222,31341102,Ensambles de láminas soldadas con soldadura fuerte o débil de acero al carbono,,0,80509,0 -9223,31341103,Ensambles de láminas soldadas con soldadura fuerte o débil de aleación hast x,Ensambles de láminas soldadas con soldadura fuerte o débil de aleación hastelloy x,0,80509,0 -9224,31341104,Ensambles de láminas soldadas con soldadura fuerte o débil de inconel,,0,80509,0 -9225,31341105,Ensambles de láminas soldadas con soldadura fuerte o débil de acero de aleación baja,,0,80509,0 -9226,31341106,Ensambles de láminas soldadas con soldadura fuerte o débil no metálica,Ensambles de láminas soldadas con soldadura fuerte o débil plástica,0,80509,0 -9227,31341109,Ensambles de láminas soldadas con soldadura fuerte o débil de acero inoxidable,,0,80509,0 -9228,31341110,Ensambles de láminas soldadas con soldadura fuerte o débil de titanio,,0,80509,0 -9229,31341111,Ensambles de láminas soldadas con soldadura fuerte o débil de aleación wasp,,0,80509,0 -9230,31341112,Ensambles de láminas soldadas con soldadura fuerte o débil de cobre,,0,80509,0 -9231,31341113,Ensambles de láminas soldadas con soldadura fuerte o débil de latón,,0,80509,0 -9232,31351200,Conjuntos de tubería soldada o soldada con latón,,0,80509,0 -9233,31351201,Ensambles de tubos soldados con soldadura fuerte o débil de aluminio,,0,80509,0 -9234,31351202,Ensambles de tubos soldados con soldadura fuerte o débil de acero al carbono,,0,80509,0 -9235,31351203,Ensambles de tubos soldados con soldadura fuerte o débil de aleación hast x,Ensambles de tubos soldados con soldadura fuerte o débil de aleación hastelloy x,0,80509,0 -9236,31351204,Ensambles de tubos soldados con soldadura fuerte o débil de inconel,,0,80509,0 -9237,31351205,Ensambles de tubos soldados con soldadura fuerte o débil de acero de aleación baja,,0,80509,0 -9238,31351206,Ensambles de tubos soldados con soldadura fuerte o débil no metálica,Ensambles de tubos soldados con soldadura fuerte o débil plástica,0,80509,0 -9239,31351209,Ensambles de tubos soldados con soldadura fuerte o débil de acero inoxidable,,0,80509,0 -9240,31351210,Ensambles de tubos soldados con soldadura fuerte o débil de titanio,,0,80509,0 -9241,31351211,Ensambles de tubos soldados con soldadura fuerte o débil de aleación wasp,,0,80509,0 -9242,31351212,Ensambles de tubos soldados con soldadura fuerte o débil de cobre,,0,80509,0 -9243,31351213,Ensambles de tubos soldados con soldadura fuerte o débil de latón,,0,80509,0 -9244,31351400,Conjuntos de tubería ligada,,0,80509,0 -9245,31351401,Ensambles de tubos pegados de aluminio,,0,80509,0 -9246,31351402,Ensambles de tubos pegados de acero al carbono,,0,80509,0 -9247,31351403,Ensambles de tubos pegados de aleación hast x,Ensambles de tubos pegados de aleación hastelloy x,0,80509,0 -9248,31351404,Ensambles de tubos pegados de inconel,,0,80509,0 -9249,31351405,Ensambles de tubos pegados de acero de aleación baja,,0,80509,0 -9250,31351406,Ensambles de tubos pegados no metálica,ensambles de tubos pegados de plástico,0,80509,0 -9251,31351409,Ensambles de tubos pegados de acero inoxidable,,0,80509,0 -9252,31351410,Ensambles de tubos pegados de titanio,,0,80509,0 -9253,31351411,Ensambles de tubos pegados de aleación wasp,,0,80509,0 -9254,31351412,Ensambles de tubos pegados de cobre,,0,80509,0 -9255,31351413,Ensambles de tubos pegados de latón,,0,80509,0 -9256,31351500,Conjuntos de tubería empernados,,0,80509,0 -9257,31351501,Ensambles de tubos atornillados de aluminio,,0,80509,0 -9258,31351502,Ensambles de tubos atornillados de acero al carbono,,0,80509,0 -9259,31351503,Ensambles de tubos atornillados de aleación hast x,,0,80509,0 -9260,31351504,Ensambles de tubos atornillados de inconel,,0,80509,0 -9261,31351505,Ensambles de tubos atornillados de acero de aleación baja,,0,80509,0 -9262,31351506,Ensambles de tubos atornillados no metálica,,0,80509,0 -9263,31351509,Ensambles de tubos atornillados de acero inoxidable,,0,80509,0 -9264,31351510,Ensambles de tubos atornillados de titanio,,0,80509,0 -9265,31351511,Ensambles de tubos atornillados de aleación wasp,,0,80509,0 -9266,31351512,Ensambles de tubos atornillados de cobre,,0,80509,0 -9267,31351513,Ensambles de tubos atornillados de latón,,0,80509,0 -9268,31361100,Conjuntos de placa ligados,,0,80509,0 -9269,31361101,Ensambles de placas soldadas de aluminio,,0,80509,0 -9270,31361102,Ensambles de placas soldadas de acero al carbono,,0,80509,0 -9271,31361103,Ensambles de placas soldadas de aleación hast x,Ensambles de placas soldadas de aleación hastelloy x,0,80509,0 -9272,31361104,Ensambles de placas soldadas de inconel,,0,80509,0 -9273,31361105,Ensambles de placas soldadas de acero de aleación baja,,0,80509,0 -9274,31361106,Ensambles de placas soldadas no metálica,Ensambles de placas soldadas de plástico,0,80509,0 -9275,31361109,Ensambles de placas soldadas de acero inoxidable,,0,80509,0 -9276,31361110,Ensambles de placas soldadas de titanio,,0,80509,0 -9277,31361111,Ensambles de placas soldadas de aleación wasp,,0,80509,0 -9278,31361112,Ensambles de placas soldadas de cobre,,0,80509,0 -9279,31361113,Ensambles de placas soldadas de latón,,0,80509,0 -9280,31361200,Conjuntos de placa empernados,,0,80509,0 -9281,31361201,Ensambles de placas atornilladas de aluminio,,0,80509,0 -9282,31361202,Ensambles de placas atornilladas de acero al carbono,,0,80509,0 -9283,31361203,Ensambles de placas atornilladas de aleación hast x,Ensambles de placas atornilladas de aleación hastelloy x,0,80509,0 -9284,31361204,Ensambles de placas atornilladas de inconel,,0,80509,0 -9285,31361205,Ensambles de placas atornilladas de acero de aleación baja,,0,80509,0 -9286,31361206,Ensambles de placas atornilladas no metálica,Ensambles de placas atornilladas de plástico,0,80509,0 -9287,31361209,Ensambles de placas atornilladas de acero inoxidable,,0,80509,0 -9288,31361210,Ensambles de placas atornilladas de titanio,,0,80509,0 -9289,31361211,Ensambles de placas atornilladas de aleación wasp,,0,80509,0 -9290,31361212,Ensambles de placas atornilladas de cobre,,0,80509,0 -9291,31361213,Ensambles de placas atornilladas de latón,,0,80509,0 -9292,31371000,Productos de fibra de cerámica,,0,80509,0 -9293,31371001,Paneles aislantes,,0,80509,0 -9294,31371002,Lana aislante,,0,80509,0 -9295,31371003,Sábanas refractarias,,0,80509,0 -9296,31371100,Ladrillos refractarios,,0,80509,0 -9297,31371101,Ladrillo de mulita,,0,80509,0 -9298,31371102,Ladrillos de silimanita,,0,80509,0 -9299,31371103,Ladrillos resistentes al ácido,,0,80509,0 -9300,31371104,Ladrillos de sílice,,0,80509,0 -9301,31371105,Ladrillos de alta alúmina,,0,80509,0 -9302,31371106,Ladrillos de silicato de calcio,,0,80509,0 -9303,31371107,Ladrillos con formas,,0,80509,0 -9304,31371108,Ladrillos de arcilla de fuego,,0,80509,0 -9305,31371200,Compuestos para moldear,,0,80509,0 -9306,31371201,Moldeables densos,,0,80509,0 -9307,31371202,Moldeables de aislación,,0,80509,0 -9308,31371203,Moldeables bajos en cemento,,0,80509,0 -9309,31371204,Moldeables resistentes a ácidos o a álcalis,,0,80509,0 -9310,31371205,Moldeables resistentes a abrasión,,0,80509,0 -9311,31371206,Moldeables sic,,0,80509,0 -9312,31371207,Moldeables de auto flujo,,0,80509,0 -9313,31371208,Moldeable tabular alúmina,,0,80509,0 -9314,31371209,Moldeable resistente a la erosión,,0,80509,0 -9315,31371210,Moldeable de alta alúmina,,0,80509,0 -9316,31371211,Moldeable de arcilla de fuego,,0,80509,0 -9317,31371212,Moldeable de servicio especial,,0,80509,0 -9318,31371300,Refractarios formados,,0,80509,0 -9319,31371301,Bloques porosos,,0,80509,0 -9320,31371302,Boquillas de zircón,,0,80509,0 -9321,31381100,Imanes fundidos y ensambles magnéticos,,0,80509,0 -9322,31381101,Imán isotrópico fundido maquinado de ferrita,,0,80509,0 -9323,31381102,Imán isotrópico fundido maquinado de bario ferrita,,0,80509,0 -9324,31381103,Imán isotrópico fundido maquinado de estroncio ferrita,,0,80509,0 -9325,31381104,Imán isotrópico fundido maquinado de neodimio,,0,80509,0 -9326,31381105,Imán isotrópico fundido maquinado de samario cobalto,,0,80509,0 -9327,31381106,Imán isotrópico fundido maquinado de aluminio níquel cobalto ferroso,,0,80509,0 -9328,31381107,Imán anisotrópico fundido maquinado de ferrita,,0,80509,0 -9329,31381108,Imán anisotrópico fundido maquinado de bario ferrita,,0,80509,0 -9330,31381109,Imán anisotrópico fundido maquinado de estroncio ferrita,,0,80509,0 -9331,31381110,Imán anisotrópico fundido maquinado de samario cobalto,,0,80509,0 -9332,31381111,Imán anisotrópico fundido maquinado de aluminio níquel cobalto ferroso,,0,80509,0 -9333,31381112,"Imán isotrópico fundido, maquinado y recubierto de ferrita",,0,80509,0 -9334,31381113,"Imán isotrópico fundido, maquinado y recubierto de bario ferrita",,0,80509,0 -9335,31381114,"Imán isotrópico fundido, maquinado y recubierto de estroncio ferrita",,0,80509,0 -9336,31381115,"Imán isotrópico fundido, maquinado y recubierto de neodimio",,0,80509,0 -9337,31381116,"Imán isotrópico fundido, maquinado y recubierto de samario cobalto",,0,80509,0 -9338,31381117,"Imán isotrópico fundido, maquinado y recubierto de aluminio níquel cobalto ferroso",,0,80509,0 -9339,31381118,"Imán anisotrópico fundido, maquinado y recubierto de ferrita",,0,80509,0 -9340,31381119,"Imán anisotrópico fundido, maquinado y recubierto de bario ferrita",,0,80509,0 -9341,31381120,"Imán anisotrópico fundido, maquinado y recubierto de estroncio ferrita",,0,80509,0 -9342,31381121,"Imán anisotrópico fundido, maquinado y recubierto de samario cobalto",,0,80509,0 -9343,31381122,"Imán anisotrópico fundido, maquinado y recubierto de aluminio níquel cobalto ferroso",,0,80509,0 -9344,31381123,Imán isotrópico fundido recubierto de ferrita,,0,80509,0 -9345,31381124,Imán isotrópico fundido recubierto de bario ferrita,,0,80509,0 -9346,31381125,Imán isotrópico fundido recubierto de estroncio ferrita,,0,80509,0 -9347,31381126,Imán isotrópico fundido recubierto de neodimio,,0,80509,0 -9348,31381127,Imán isotrópico fundido recubierto de samario cobalto,,0,80509,0 -9349,31381128,Imán isotrópico fundido recubierto de aluminio níquel cobalto ferroso,,0,80509,0 -9350,31381129,Imán anisotrópico fundido recubierto de ferrita,,0,80509,0 -9351,31381130,Imán anisotrópico fundido recubierto de bario ferrita,,0,80509,0 -9352,31381131,Imán anisotrópico fundido recubierto de estroncio ferrita,,0,80509,0 -9353,31381132,Imán anisotrópico fundido recubierto de samario cobalto,,0,80509,0 -9354,31381133,Imán anisotrópico fundido recubierto de aluminio níquel cobalto ferroso,,0,80509,0 -9355,31381134,Herramienta isotrópica fundida maquinada de ferrita,,0,80509,0 -9356,31381135,Herramienta isotrópica fundida maquinada de bario ferrita,,0,80509,0 -9357,31381136,Herramienta isotrópica fundida maquinada de estroncio ferrita,,0,80509,0 -9358,31381137,Herramienta isotrópica fundida maquinada de neodimio,,0,80509,0 -9359,31381138,Herramienta isotrópica fundida maquinada de samario cobalto,,0,80509,0 -9360,31381139,Herramienta isotrópica fundida maquinada de aluminio níquel cobalto ferroso,,0,80509,0 -9361,31381140,Herramienta anisotrópica fundida maquinada de ferrita,,0,80509,0 -9362,31381141,Herramienta anisotrópica fundida maquinada de bario ferrita,,0,80509,0 -9363,31381142,Herramienta anisotrópica fundida maquinada de estroncio ferrita,,0,80509,0 -9364,31381143,Herramienta anisotrópica fundida maquinada de samario cobalto,,0,80509,0 -9365,31381144,Herramienta anisotrópica fundida maquinada de aluminio níquel cobalto ferroso,,0,80509,0 -9366,31381145,Ensamble de imán isotrópico fundido de ferrita,,0,80509,0 -9367,31381146,Ensamble de imán isotrópico fundido de bario ferrita,,0,80509,0 -9368,31381147,Ensamble de imán isotrópico fundido de estroncio ferrita,,0,80509,0 -9369,31381148,Ensamble de imán isotrópico fundido de neodimio,,0,80509,0 -9370,31381149,Ensamble de imán isotrópico fundido de samario cobalto,,0,80509,0 -9371,31381150,Ensamble de imán isotrópico fundido de aluminio níquel cobalto ferroso,,0,80509,0 -9372,31381151,Ensamble de imán anisotrópico fundido de ferrita,,0,80509,0 -9373,31381152,Ensamble de imán anisotrópico fundido de bario ferrita,,0,80509,0 -9374,31381153,Ensamble de imán anisotrópico fundido de estroncio ferrita,,0,80509,0 -9375,31381154,Ensamble de imán anisotrópico fundido de samario cobalto,,0,80509,0 -9376,31381155,Ensamble de imán anisotrópico fundido de aluminio níquel cobalto ferroso,,0,80509,0 -9377,31381200,Imanes sinterizados y ensambles magnéticos,,0,80509,0 -9378,31381201,Imán isotrópico sinterizado maquinado de ferrita,,0,80509,0 -9379,31381202,Imán isotrópico sinterizado maquinado de bario ferrita,,0,80509,0 -9380,31381203,Imán isotrópico sinterizado maquinado de estroncio ferrita,,0,80509,0 -9381,31381204,Imán isotrópico sinterizado maquinado de neodimio,,0,80509,0 -9382,31381205,Imán isotrópico sinterizado maquinado de samario cobalto,,0,80509,0 -9383,31381206,Imán isotrópico sinterizado maquinado de aluminio níquel cobalto ferroso,,0,80509,0 -9384,31381207,Imán anisotrópico sinterizado maquinado de ferrita,,0,80509,0 -9385,31381208,Imán anisotrópico sinterizado maquinado de bario ferrita,,0,80509,0 -9386,31381209,Imán anisotrópico sinterizado maquinado de estroncio ferrita,,0,80509,0 -9387,31381210,Imán anisotrópico sinterizado maquinado de samario cobalto,,0,80509,0 -9388,31381211,Imán anisotrópico sinterizado maquinado de aluminio níquel cobalto ferroso,,0,80509,0 -9389,31381212,"Imán isotrópico sinterizado, maquinado y recubierto de ferrita",,0,80509,0 -9390,31381213,"Imán isotrópico sinterizado, maquinado y recubierto de bario ferrita",,0,80509,0 -9391,31381214,"Imán isotrópico sinterizado, maquinado y recubierto de estroncio ferrita",,0,80509,0 -9392,31381215,"Imán isotrópico sinterizado, maquinado y recubierto de neodimio",,0,80509,0 -9393,31381216,"Imán isotrópico sinterizado, maquinado y recubierto de samario cobalto",,0,80509,0 -9394,31381217,"Imán isotrópico sinterizado, maquinado y recubierto de aluminio níquel cobalto ferroso",,0,80509,0 -9395,31381218,"Imán anisotrópico sinterizado, maquinado y recubierto de ferrita",,0,80509,0 -9396,31381219,"Imán anisotrópico sinterizado, maquinado y recubierto de bario ferrita",,0,80509,0 -9397,31381220,"Imán anisotrópico sinterizado, maquinado y recubierto de estroncio ferrita",,0,80509,0 -9398,31381221,"Imán anisotrópico sinterizado, maquinado y recubierto de samario cobalto",,0,80509,0 -9399,31381222,"Imán anisotrópico sinterizado, maquinado y recubierto de aluminio níquel cobalto ferroso",,0,80509,0 -9400,31381223,Imán isotrópico sinterizado recubierto de ferrita,,0,80509,0 -9401,31381224,Imán isotrópico sinterizado recubierto de bario ferrita,,0,80509,0 -9402,31381225,Imán isotrópico sinterizado recubierto de estroncio ferrita,,0,80509,0 -9403,31381226,Imán isotrópico sinterizado recubierto de neodimio,,0,80509,0 -9404,31381227,Imán isotrópico sinterizado recubierto de samario cobalto,,0,80509,0 -9405,31381228,Imán isotrópico sinterizado recubierto de aluminio níquel cobalto ferroso,,0,80509,0 -9406,31381229,Imán anisotrópico sinterizado recubierto de ferrita,,0,80509,0 -9407,31381230,Imán anisotrópico sinterizado recubierto de bario ferrita,,0,80509,0 -9408,31381231,Imán anisotrópico sinterizado recubierto de estroncio ferrita,,0,80509,0 -9409,31381232,Imán anisotrópico sinterizado recubierto de samario cobalto,,0,80509,0 -9410,31381233,Imán anisotrópico sinterizado recubierto de aluminio níquel cobalto ferroso,,0,80509,0 -9411,31381234,Herramienta isotrópica sinterizada maquinada de ferrita,,0,80509,0 -9412,31381235,Herramienta isotrópica sinterizada maquinada de bario ferrita,,0,80509,0 -9413,31381236,Herramienta isotrópica sinterizada maquinada de estroncio ferrita,,0,80509,0 -9414,31381237,Herramienta isotrópica sinterizada maquinada de neodimio,,0,80509,0 -9415,31381238,Herramienta isotrópica sinterizada maquinada de samario cobalto,,0,80509,0 -9416,31381239,Herramienta isotrópica sinterizada maquinada de aluminio níquel cobalto ferroso,,0,80509,0 -9417,31381240,Herramienta anisotrópica sinterizada maquinada de ferrita,,0,80509,0 -9418,31381241,Herramienta anisotrópica sinterizada maquinada de bario ferrita,,0,80509,0 -9419,31381242,Herramienta anisotrópica sinterizada maquinada de estroncio ferrita,,0,80509,0 -9420,31381243,Herramienta anisotrópica sinterizada maquinada de samario cobalto,,0,80509,0 -9421,31381244,Herramienta anisotrópica sinterizada maquinada de aluminio níquel cobalto ferroso,,0,80509,0 -9422,31381245,Ensamble de imán isotrópico sinterizado de ferrita,,0,80509,0 -9423,31381246,Ensamble de imán isotrópico sinterizado de bario ferrita,,0,80509,0 -9424,31381247,Ensamble de imán isotrópico sinterizado de estroncio ferrita,,0,80509,0 -9425,31381248,Ensamble de imán isotrópico sinterizado de neodimio,,0,80509,0 -9426,31381249,Ensamble de imán isotrópico sinterizado de samario cobalto,,0,80509,0 -9427,31381250,Ensamble de imán isotrópico sinterizado de aluminio níquel cobalto ferroso,,0,80509,0 -9428,31381251,Ensamble de imán anisotrópico sinterizado de ferrita,,0,80509,0 -9429,31381252,Ensamble de imán anisotrópico sinterizado de bario ferrita,,0,80509,0 -9430,31381253,Ensamble de imán anisotrópico sinterizado de estroncio ferrita,,0,80509,0 -9431,31381254,Ensamble de imán anisotrópico sinterizado de samario cobalto,,0,80509,0 -9432,31381255,Ensamble de imán anisotrópico sinterizado de aluminio níquel cobalto ferroso,,0,80509,0 -9433,31381400,Imanes pegados con plástico y ensambles magnéticos,,0,80509,0 -9434,31381401,Imán isotrópico pegado con plástico maquinado de ferrita,,0,80509,0 -9435,31381402,Imán isotrópico pegado con plástico maquinado de bario ferrita,,0,80509,0 -9436,31381403,Imán isotrópico pegado con plástico maquinado de estroncio ferrita,,0,80509,0 -9437,31381404,Imán isotrópico pegado con plástico maquinado de neodimio,,0,80509,0 -9438,31381405,Imán isotrópico pegado con plástico maquinado de samario cobalto,,0,80509,0 -9439,31381406,Imán isotrópico pegado con plástico maquinado de aluminio níquel cobalto ferroso,,0,80509,0 -9440,31381407,Imán anisotrópico pegado con plástico maquinado de ferrita,,0,80509,0 -9441,31381408,Imán anisotrópico pegado con plástico maquinado de bario ferrita,,0,80509,0 -9442,31381409,Imán anisotrópico pegado con plástico maquinado de estroncio ferrita,,0,80509,0 -9443,31381410,Imán anisotrópico pegado con plástico maquinado de samario cobalto,,0,80509,0 -9444,31381411,Imán anisotrópico pegado con plástico maquinado de aluminio níquel cobalto ferroso,,0,80509,0 -9445,31381412,"Imán isotrópico pegado con plástico, maquinado y recubierto de ferrita",,0,80509,0 -9446,31381413,"Imán isotrópico pegado con plástico, maquinado y recubierto de bario ferrita",,0,80509,0 -9447,31381414,"Imán isotrópico pegado con plástico, maquinado y recubierto de estroncio ferrita",,0,80509,0 -9448,31381415,"Imán isotrópico pegado con plástico, maquinado y recubierto de neodimio",,0,80509,0 -9449,31381416,"Imán isotrópico pegado con plástico, maquinado y recubierto de samario cobalto",,0,80509,0 -9450,31381417,"Imán isotrópico pegado con plástico, maquinado y recubierto de aluminio níquel cobalto ferroso",,0,80509,0 -9451,31381418,"Imán anisotrópico pegado con plástico, maquinado y recubierto de ferrita",,0,80509,0 -9452,31381419,"Imán anisotrópico pegado con plástico, maquinado y recubierto de bario ferrita",,0,80509,0 -9453,31381420,"Imán anisotrópico pegado con plástico, maquinado y recubierto de estroncio ferrita",,0,80509,0 -9454,31381421,"Imán anisotrópico pegado con plástico, maquinado y recubierto de samario cobalto",,0,80509,0 -9455,31381422,"Imán anisotrópico pegado con plástico, maquinado y recubierto de aluminio níquel cobalto ferroso",,0,80509,0 -9456,31381423,Imán isotrópico pegado con plástico recubierto de ferrita,,0,80509,0 -9457,31381424,Imán isotrópico pegado con plástico recubierto de bario ferrita,,0,80509,0 -9458,31381425,Imán isotrópico pegado con plástico recubierto de estroncio ferrita,,0,80509,0 -9459,31381426,Imán isotrópico pegado con plástico recubierto de neodimio,,0,80509,0 -9460,31381427,Imán isotrópico pegado con plástico recubierto de samario cobalto,,0,80509,0 -9461,31381428,Imán isotrópico pegado con plástico recubierto de aluminio níquel cobalto ferroso,,0,80509,0 -9462,31381429,Imán anisotrópico pegado con plástico recubierto de ferrita,,0,80509,0 -9463,31381430,Imán anisotrópico pegado con plástico recubierto de bario ferrita,,0,80509,0 -9464,31381431,Imán anisotrópico pegado con plástico recubierto de estroncio ferrita,,0,80509,0 -9465,31381432,Imán anisotrópico pegado con plástico recubierto de samario cobalto,,0,80509,0 -9466,31381433,Imán anisotrópico pegado con plástico recubierto de aluminio níquel cobalto ferroso,,0,80509,0 -9467,31381434,Herramienta isotrópica pegada con plástico maquinada de ferrita,,0,80509,0 -9468,31381435,Herramienta isotrópica pegada con plástico maquinada de bario ferrita,,0,80509,0 -9469,31381436,Herramienta isotrópica pegada con plástico maquinada de estroncio ferrita,,0,80509,0 -9470,31381437,Herramienta isotrópica pegada con plástico maquinada de neodimio,,0,80509,0 -9471,31381438,Herramienta isotrópica pegada con plástico maquinada de samario cobalto,,0,80509,0 -9472,31381439,Herramienta isotrópica pegada con plástico maquinada de aluminio níquel cobalto ferroso,,0,80509,0 -9473,31381440,Herramienta anisotrópica pegada con plástico maquinada de ferrita,,0,80509,0 -9474,31381441,Herramienta anisotrópica pegada con plástico maquinada de bario ferrita,,0,80509,0 -9475,31381442,Herramienta anisotrópica pegada con plástico maquinada de estroncio ferrita,,0,80509,0 -9476,31381443,Herramienta anisotrópica pegada con plástico maquinada de samario cobalto,,0,80509,0 -9477,31381444,Herramienta anisotrópica pegada con plástico maquinada de aluminio níquel cobalto ferroso,,0,80509,0 -9478,31381445,Ensamble de imán isotrópico pegado con plástico de ferrita,,0,80509,0 -9479,31381446,Ensamble de imán isotrópico pegado con plástico de bario ferrita,,0,80509,0 -9480,31381447,Ensamble de imán isotrópico pegado con plástico de estroncio ferrita,,0,80509,0 -9481,31381448,Ensamble de imán isotrópico pegado con plástico de neodimio,,0,80509,0 -9482,31381449,Ensamble de imán isotrópico pegado con plástico de samario cobalto,,0,80509,0 -9483,31381450,Ensamble de imán isotrópico pegado con plástico de aluminio níquel cobalto ferroso,,0,80509,0 -9484,31381451,Ensamble de imán anisotrópico pegado con plástico de ferrita,,0,80509,0 -9485,31381452,Ensamble de imán anisotrópico pegado con plástico de bario ferrita,,0,80509,0 -9486,31381453,Ensamble de imán anisotrópico pegado con plástico de estroncio ferrita,,0,80509,0 -9487,31381454,Ensamble de imán anisotrópico pegado con plástico de samario cobalto,,0,80509,0 -9488,31381455,Ensamble de imán anisotrópico pegado con plástico de aluminio níquel cobalto ferroso,,0,80509,0 -9489,31381500,Imanes moldeados por inyección y ensambles magnéticos,,0,80509,0 -9490,31381501,Imán isotrópico pegado con plástico moldeado por inyección maquinado de ferrita,,0,80509,0 -9491,31381502,Imán isotrópico pegado con plástico moldeado por inyección maquinado de bario ferrita,,0,80509,0 -9492,31381503,Imán isotrópico pegado con plástico moldeado por inyección maquinado de estroncio ferrita,,0,80509,0 -9493,31381504,Imán isotrópico pegado con plástico moldeado por inyección maquinado de neodimio,,0,80509,0 -9494,31381505,Imán isotrópico pegado con plástico moldeado por inyección maquinado de samario cobalto,,0,80509,0 -9495,31381506,Imán isotrópico pegado con plástico moldeado por inyección maquinado de aluminio níquel cobalto ferroso,,0,80509,0 -9496,31381507,Imán anisotrópico pegado con plástico moldeado por inyección maquinado de ferrita,,0,80509,0 -9497,31381508,Imán anisotrópico pegado con plástico moldeado por inyección maquinado de bario ferrita,,0,80509,0 -9498,31381509,Imán anisotrópico pegado con plástico moldeado por inyección maquinado de estroncio ferrita,,0,80509,0 -9499,31381510,Imán anisotrópico pegado con plástico moldeado por inyección maquinado de samario cobalto,,0,80509,0 -9500,31381511,Imán anisotrópico pegado con plástico moldeado por inyección maquinado de aluminio níquel cobalto ferroso,,0,80509,0 -9501,31381512,"Imán isotrópico pegado con plástico moldeado por inyección, maquinado y recubierto de ferrita",,0,80509,0 -9502,31381513,"Imán isotrópico pegado con plástico moldeado por inyección, maquinado y recubierto de bario ferrita",,0,80509,0 -9503,31381514,"Imán isotrópico pegado con plástico moldeado por inyección, maquinado y recubierto de estroncio ferrita",,0,80509,0 -9504,31381515,"Imán isotrópico pegado con plástico moldeado por inyección, maquinado y recubierto de neodimio",,0,80509,0 -9505,31381516,"Imán isotrópico pegado con plástico moldeado por inyección, maquinado y recubierto de samario cobalto",,0,80509,0 -9506,31381517,"Imán isotrópico pegado con plástico moldeado por inyección, maquinado y recubierto de aluminio níquel cobalto ferroso",,0,80509,0 -9507,31381518,"Imán anisotrópico pegado con plástico moldeado por inyección, maquinado y recubierto de ferrita",,0,80509,0 -9508,31381519,"Imán anisotrópico pegado con plástico moldeado por inyección, maquinado y recubierto de bario ferrita",,0,80509,0 -9509,31381520,"Imán anisotrópico pegado con plástico moldeado por inyección, maquinado y recubierto de estroncio ferrita",,0,80509,0 -9510,31381521,"Imán anisotrópico pegado con plástico moldeado por inyección, maquinado y recubierto de samario cobalto",,0,80509,0 -9511,31381522,"Imán anisotrópico pegado con plástico moldeado por inyección, maquinado y recubierto de aluminio níquel cobalto ferroso",,0,80509,0 -9512,31381523,Imán isotrópico pegado con plástico moldeado por inyección recubierto de ferrita,,0,80509,0 -9513,31381524,Imán isotrópico pegado con plástico moldeado por inyección recubierto de bario ferrita,,0,80509,0 -9514,31381525,Imán isotrópico pegado con plástico moldeado por inyección recubierto de estroncio ferrita,,0,80509,0 -9515,31381526,Imán isotrópico pegado con plástico moldeado por inyección recubierto de neodimio,,0,80509,0 -9516,31381527,Imán isotrópico pegado con plástico moldeado por inyección recubierto de samario cobalto,,0,80509,0 -9517,31381528,Imán isotrópico pegado con plástico moldeado por inyección recubierto de aluminio níquel cobalto ferroso,,0,80509,0 -9518,31381529,Imán anisotrópico pegado con plástico moldeado por inyección recubierto de ferrita,,0,80509,0 -9519,31381530,Imán anisotrópico pegado con plástico moldeado por inyección recubierto de bario ferrita,,0,80509,0 -9520,31381531,Imán anisotrópico pegado con plástico moldeado por inyección recubierto de estroncio ferrita,,0,80509,0 -9521,31381532,Imán anisotrópico pegado con plástico moldeado por inyección recubierto de samario cobalto,,0,80509,0 -9522,31381533,Imán anisotrópico pegado con plástico moldeado por inyección recubierto de aluminio níquel cobalto ferroso,,0,80509,0 -9523,31381534,Herramienta isotrópica pegada con plástico moldeada por inyección maquinada de ferrita,,0,80509,0 -9524,31381535,Herramienta isotrópica pegada con plástico moldeada por inyección maquinada de bario ferrita,,0,80509,0 -9525,31381536,Herramienta isotrópica pegada con plástico moldeada por inyección maquinada de estroncio ferrita,,0,80509,0 -9526,31381537,Herramienta isotrópica pegada con plástico moldeada por inyección maquinada de neodimio,,0,80509,0 -9527,31381538,Herramienta isotrópica pegada con plástico moldeada por inyección maquinada de samario cobalto,,0,80509,0 -9528,31381539,Herramienta isotrópica pegada con plástico moldeada por inyección maquinada de aluminio níquel cobalto ferroso,,0,80509,0 -9529,31381540,Herramienta anisotrópica pegada con plástico moldeada por inyección maquinada de ferrita,,0,80509,0 -9530,31381541,Herramienta anisotrópica pegada con plástico moldeada por inyección maquinada de bario ferrita,,0,80509,0 -9531,31381542,Herramienta anisotrópica pegada con plástico moldeada por inyección maquinada de estroncio ferrita,,0,80509,0 -9532,31381543,Herramienta anisotrópica pegada con plástico moldeada por inyección maquinada de samario cobalto,,0,80509,0 -9533,31381544,Herramienta anisotrópica pegada con plástico moldeada por inyección maquinada de aluminio níquel cobalto ferroso,,0,80509,0 -9534,31381545,Ensamble de imán isotrópico pegado con plástico moldeado por inyección de ferrita,,0,80509,0 -9535,31381546,Ensamble de imán isotrópico pegado con plástico moldeado por inyección de bario ferrita,,0,80509,0 -9536,31381547,Ensamble de imán isotrópico pegado con plástico moldeado por inyección de estroncio ferrita,,0,80509,0 -9537,31381548,Ensamble de imán isotrópico pegado con plástico moldeado por inyección de neodimio,,0,80509,0 -9538,31381549,Ensamble de imán isotrópico pegado con plástico moldeado por inyección de samario cobalto,,0,80509,0 -9539,31381550,Ensamble de imán isotrópico pegado con plástico moldeado por inyección de aluminio níquel cobalto ferroso,,0,80509,0 -9540,31381551,Ensamble de imán anisotrópico pegado con plástico moldeado por inyección de ferrita,,0,80509,0 -9541,31381552,Ensamble de imán anisotrópico pegado con plástico moldeado por inyección de bario ferrita,,0,80509,0 -9542,31381553,Ensamble de imán anisotrópico pegado con plástico moldeado por inyección de estroncio ferrita,,0,80509,0 -9543,31381554,Ensamble de imán anisotrópico pegado con plástico moldeado por inyección de samario cobalto,,0,80509,0 -9544,31381555,Ensamble de imán anisotrópico pegado con plástico moldeado por inyección de aluminio níquel cobalto ferroso,,0,80509,0 -9545,31391500,Maquinados de precisión estándar,,0,80509,0 -9546,31391501,Maquinado de precisión estándar de plástico,,0,80509,0 -9547,31391502,Maquinado de precisión estándar de cerámica,,0,80509,0 -9548,31391503,Maquinado de precisión estándar de acero,,0,80509,0 -9549,31391504,Maquinado de precisión estándar de acero inoxidable,,0,80509,0 -9550,31391505,Maquinado de precisión estándar de aluminio,,0,80509,0 -9551,31391506,Maquinado de precisión estándar de latón,,0,80509,0 -9552,31391600,Maquinados de precisión media,,0,80509,0 -9553,31391601,Maquinado de precisión media de plástico,,0,80509,0 -9554,31391602,Maquinado de precisión media de cerámica,,0,80509,0 -9555,31391603,Maquinado de precisión media de acero,,0,80509,0 -9556,31391604,Maquinado de precisión media de acero inoxidable,,0,80509,0 -9557,31391605,Maquinado de precisión media de aluminio,,0,80509,0 -9558,31391606,Maquinado de precisión media de latón,,0,80509,0 -9559,31391700,Maquinados de precisión alta,,0,80509,0 -9560,31391701,Maquinado de alta precisión de plástico,,0,80509,0 -9561,31391702,Maquinado de alta precisión de cerámica,,0,80509,0 -9562,31391703,Maquinado de alta precisión de acero,,0,80509,0 -9563,31391704,Maquinado de alta precisión de acero inoxidable,,0,80509,0 -9564,31391705,Maquinado de alta precisión de aluminio,,0,80509,0 -9565,31391706,Maquinado de alta precisión de latón,,0,80509,0 -9566,31401500,Empaques Moldeados,,0,80509,0 -9567,31401501,Empaque moldeado de caucho,,0,80509,0 -9568,31401502,Empaque moldeado de plástico,,0,80509,0 -9569,31401503,Empaque moldeado o – ring,Juntas moldeadas con anillo tórico,0,80509,0 -9570,31401504,Empaque moldeado de interferencia electromagnética,,0,80509,0 -9571,31401505,Empaque moldeado de silicona,,0,80509,0 -9572,31401506,Empaque moldeado inflable,,0,80509,0 -9573,31401507,Kit de empaque moldeado,,0,80509,0 -9574,31401600,Empaques troquelados,,0,80509,0 -9575,31401601,Empaque troquelado de caucho,,0,80509,0 -9576,31401602,Empaque troquelado de plástico,,0,80509,0 -9577,31401603,Empaque troquelado de textil,,0,80509,0 -9578,31401604,Empaque troquelado de corcho,,0,80509,0 -9579,31401605,Empaque troquelado de interferencia electromagnética o emi,,0,80509,0 -9580,31401606,Empaque troquelado de silicona,,0,80509,0 -9581,31401607,Empaque troquelado de fibra comprimida,,0,80509,0 -9582,31401608,Empaque troquelado de bi – material,,0,80509,0 -9583,31401609,Empaque troquelado de polietifluroetileno,,0,80509,0 -9584,31401610,Kit de empaque troquelado,,0,80509,0 -9585,31401700,Empaques estampados,,0,80509,0 -9586,31401701,Empaque de caucho estampado recubierto de acero,,0,80509,0 -9587,31401702,Empaque estampado de plástico,,0,80509,0 -9588,31401703,Empaque estampado de caucho,,0,80509,0 -9589,31401704,Empaque estampado de metal,,0,80509,0 -9590,31401705,Empaque estampado de textil,,0,80509,0 -9591,31401706,Empaque estampado de corcho,,0,80509,0 -9592,31401707,Empaque estampado atornillado,,0,80509,0 -9593,31401708,Kit de empaque estampado,Juego de juntas estampadas,0,80509,0 -9594,31401800,Empaques cortados con chorro de agua,,0,80509,0 -9595,31401801,Empaque de interferencia electromagnética cortado con chorro de agua,,0,80509,0 -9596,31401802,Empaque de caucho cortado con chorro de agua,,0,80509,0 -9597,31401803,Empaque de plástico cortado con chorro de agua,,0,80509,0 -9598,31401804,Empaque de textil cortado con chorro de agua,,0,80509,0 -9599,31401805,Empaque de corcho cortado con chorro de agua,,0,80509,0 -9600,31401806,Empaque de silicona cortado con chorro de agua,,0,80509,0 -9601,31401807,Kit de empaque cortado con chorro de agua,Juego de juntas de corte por chorro de agua,0,80509,0 -9602,31401900,Empaques líquidos,,0,80509,0 -9603,31401901,Empaque líquido de poli acrilato,,0,80509,0 -9604,31401902,Empaque líquido de silicona,,0,80509,0 -9605,31401903,Empaque líquido de solvente,,0,80509,0 -9606,31401904,Empaque líquido anaeróbico,,0,80509,0 -9607,31411500,Sellos torneados,,0,80509,0 -9608,31411501,Sello torneado de caucho,,0,80509,0 -9609,31411502,Sello torneado de politetrafluroetileno,,0,80509,0 -9610,31411503,Sello torneado de neopreno,,0,80509,0 -9611,31411504,Sello torneado de poliacrilato,,0,80509,0 -9612,31411505,Sello torneado metálico,,0,80509,0 -9613,31411506,Kit de sello torneado,Juego de sellos de corte de torno,0,80509,0 -9614,31411600,Sellos troquelados,,0,80509,0 -9615,31411601,Sello troquelado de caucho,,0,80509,0 -9616,31411602,Sello troquelado de espuma,,0,80509,0 -9617,31411603,Sello troquelado de plástico,,0,80509,0 -9618,31411604,Kit de sello troquelado,Juego de sellos troquelados,0,80509,0 -9619,31411605,Sello troquelado de silicona,,0,80509,0 -9620,31411700,Sellos mecánicos,,0,80509,0 -9621,31411701,Sello mecánico de empuje,,0,80509,0 -9622,31411702,Sello mecánico de fuelle,,0,80509,0 -9623,31411703,Kit de sello mecánico,Juego de sellos mecánicos,0,80509,0 -9624,31411704,Sello v – ring,,0,80509,0 -9625,31411705,Sello de labio,,0,80509,0 -9626,31411800,Sellos de diafragma,,0,80509,0 -9627,31411801,Sello de diafragma soldado,,0,80509,0 -9628,31411802,Sello de diafragma de abrazadera,,0,80509,0 -9629,31411803,Sello de diafragma de cápsula,,0,80509,0 -9630,31411804,Sello de diafragma de caucho,,0,80509,0 -9631,31411805,Sello de diafragma de fibra reforzada,,0,80509,0 -9632,31411900,Sellos moldeados,,0,80509,0 -9633,31411901,Sello moldeado de caucho,,0,80509,0 -9634,31411902,Sello moldeado de plástico,,0,80509,0 -9635,31411903,Sello moldeado de precisión,,0,80509,0 -9636,31411904,Sello moldeado de caucho sobre metal o plástico,,0,80509,0 -9637,31411905,Sello moldeado de silicona,,0,80509,0 -9638,31421500,Sellos sinterizados,,0,80509,0 -9639,31421501,Filtro sinterizado estándar de hierro cobre,,0,80509,0 -9640,31421502,Filtro sinterizado estándar compuesto de hierro magnético suave,,0,80509,0 -9641,31421503,Filtro sinterizado de alta temperatura compuesto de hierro magnético suave,,0,80509,0 -9642,31421504,Filtro sinterizado estándar de acero inoxidable,,0,80509,0 -9643,31421505,Filtro sinterizado de alta temperatura de acero inoxidable,,0,80509,0 -9644,31421506,Filtro sinterizado compacto estándar de acero inoxidable,,0,80509,0 -9645,31421507,Filtro sinterizado estándar de níquel cobalto,,0,80509,0 -9646,31421508,Filtro sinterizado de alta temperatura de níquel cobalto,,0,80509,0 -9647,31421509,Filtro sinterizado compacto estándar de níquel cobalto,,0,80509,0 -9648,31421510,Filtro sinterizado estándar de aluminio,,0,80509,0 -9649,31421511,Filtro sinterizado alta temperatura de níquel cobalto,,0,80509,0 -9650,31421512,Filtro sinterizado estándar de base de cobre,,0,80509,0 -9651,31421513,Filtro sinterizado estándar de aleación de titanio,,0,80509,0 -9652,31421514,Filtro sinterizado de alta temperatura de aleación de titanio,,0,80509,0 -9653,31421515,Filtro sinterizado estándar de berilio,,0,80509,0 -9654,31421516,Filtro sinterizado de alta temperatura de berilio,,0,80509,0 -9655,31421517,Filtro sinterizado estándar de metal refractario,,0,80509,0 -9656,31421518,Filtro sinterizado de alta temperatura de metal refractario,,0,80509,0 -9657,31421519,Filtro sinterizado estándar de cerámica,,0,80509,0 -9658,31421520,Filtro sinterizado de alta temperatura de cerámica,,0,80509,0 -9659,31421521,Filtro sinterizado estándar de polvo de hierro puro,,0,80509,0 -9660,31421522,Filtro sinterizado de alta temperatura de polvo de hierro puro,,0,80509,0 -9661,32101500,Conjuntos de circuitos y componentes de radiofrecuencia (RF),,0,80509,0 -9662,32101502,Montajes de circuitos impresos (pca),Montajes de circuitos impresos en placa,0,80509,0 -9663,32101503,Conjuntos de circuitos mixtos,,0,80509,0 -9664,32101504,Ensamblajes de circuitos montados en superficie,,0,80509,0 -9665,32101505,Ensamblajes de circuitos electro plateados,,0,80509,0 -9666,32101506,Tarjetas de circuito de doble cara,,0,80509,0 -9667,32101507,Tarjetas de tablero de conectores de circuitos,,0,80509,0 -9668,32101508,Tarjetas de circuito multi capa,,0,80509,0 -9669,32101509,Tarjetas de circuito de una cara,,0,80509,0 -9670,32101510,Tablero de cables impreso,,0,80509,0 -9671,32101512,Demoduladores,,0,80509,0 -9672,32101513,Conjuntos de circuitos de aplicaciones específicas,,0,80509,0 -9673,32101514,Amplificadores,,0,80509,0 -9674,32101515,Atenuadores,,0,80509,0 -9675,32101516,Circuladores,,0,80509,0 -9676,32101517,Acopladores,,0,80509,0 -9677,32101518,Líneas de retardo,,0,80509,0 -9678,32101519,Detectores,,0,80509,0 -9679,32101520,Cargas simuladas,,0,80509,0 -9680,32101521,Filtros de radiofrecuencia (rf),,0,80509,0 -9681,32101522,Aisladores,,0,80509,0 -9682,32101523,Mezcladores,,0,80509,0 -9683,32101524,Variadores de fase,,0,80509,0 -9684,32101525,Multiplexores,,0,80509,0 -9685,32101526,Amplificadores de tubos de onda,,0,80509,0 -9686,32101527,Terminaciones,,0,80509,0 -9687,32101528,Moduladores,,0,80509,0 -9688,32101529,Divisores o splitters,,0,80509,0 -9689,32101530,Combinado de radio frecuencia rf,Combinado de radiofrecuencia (RF),0,80509,0 -9690,32101531,Pad atenuador de red,,0,80509,0 -9691,32101532,Sustrato cerámico,,0,80509,0 -9692,32101533,Transformador de salid,,0,80509,0 -9693,32101534,Trampa de ondas,,0,80509,0 -9694,32101535,Circuito de fase modulada,,0,80509,0 -9695,32101536,Compensador de desbalance de fase y factor de potencia,,0,80509,0 -9696,32101537,Repetidor de sincro,,0,80509,0 -9697,32101538,Estabilizador de impedancia,,0,80509,0 -9698,32101539,Selector de frecuencia,,0,80509,0 -9699,32101540,Resolvedor,,0,80509,0 -9700,32101541,Transmisor de sincro,,0,80509,0 -9701,32101542,Receptor de sincro,,0,80509,0 -9702,32101543,Controlador e indicador de calibre,,0,80509,0 -9703,32101544,Oscilador de frecuencia,,0,80509,0 -9704,32101545,Bobina de antena,,0,80509,0 -9705,32101546,Multiplicador de frecuencia,,0,80509,0 -9706,32101547,Limitador,,0,80509,0 -9707,32101548,Discriminador,,0,80509,0 -9708,32101600,Circuitos integrados,,0,80509,0 -9709,32101601,Memoria de acceso aleatorio (ram),,0,80509,0 -9710,32101602,Memoria ram dinámica (dram),Memoria dinámica de acceso aleatorio (DRAM),0,80509,0 -9711,32101603,Memoria ram estática (sram),Memoria estática de acceso aleatorio (SRAM),0,80509,0 -9712,32101604,Memoria rom programable (prom),Memoria de lectura programable (PROM),0,80509,0 -9713,32101605,Memoria rom programable borrable (eprom),Memoria de sólo lectura programable borrable (EPROM),0,80509,0 -9714,32101606,Memoria rom programable borrable eléctricamente (eeprom),Memoria de sólo lectura programable borrable electrónicamente (EEPROM),0,80509,0 -9715,32101607,Circuitos integrados monolíticos de memoria (mmic),Circuitos integrados de memoria monolítica (MMIC),0,80509,0 -9716,32101608,Memoria de sólo lectura (rom),Memoria sólo de lectura (ROM),0,80509,0 -9717,32101609,Circuitos integrados de aplicaciones específicas (asic),Circuitos integrados específicos de la aplicación (ASIC),0,80509,0 -9718,32101611,Lógica de matriz programable (pal),,0,80509,0 -9719,32101612,Lógica de matriz de puertas (gal),,0,80509,0 -9720,32101613,Lógica transistor-transistor (ttl),,0,80509,0 -9721,32101614,Lógica de emisor acoplado (ecl),,0,80509,0 -9722,32101615,Tecnología mos bipolar (bimos),Tecnología de semiconductores de óxido metálico o bipolar (BIMOS),0,80509,0 -9723,32101616,Tecnología cmos bipolar (bicmos),Tecnología de semiconductores de óxido metálico complementario bipolar (BICMOS),0,80509,0 -9724,32101617,Tarjetas inteligentes,,0,80509,0 -9725,32101618,Circuitos integrados ordenados hacia arriba,,0,80509,0 -9726,32101619,Circuitos integrados lineales,,0,80509,0 -9727,32101620,Circuitos integrados digitales,,0,80509,0 -9728,32101621,Memoria dram síncrona (sdram),Memoria síncrona dinámica de acceso aleatorio (SDRAM),0,80509,0 -9729,32101622,Memoria flash,Tarjeta de memoria para celular,0,80509,0 -9730,32101623,Memoria rdram (rambus),Memoria de acceso aleatorio dinámica rambus (RDRAM),0,80509,0 -9731,32101624,Memoria sgram,,0,80509,0 -9732,32101625,Circuitos integrados de accionamiento o control por motor,,0,80509,0 -9733,32101626,Microprocesadores,,0,80509,0 -9734,32101627,Osciladores de reloj,,0,80509,0 -9735,32101628,Microcontroladores,,0,80509,0 -9736,32101629,Amplificadores operacionales,,0,80509,0 -9737,32101630,Circuitos integrados de regulador de tensión,,0,80509,0 -9738,32101631,Circuitos integrados de comparador de tensión,,0,80509,0 -9739,32101632,Circuitos integrados de temporizador,,0,80509,0 -9740,32101633,Puertas lógicas,,0,80509,0 -9741,32101634,Circuitos basculantes,,0,80509,0 -9742,32101635,Registros de desplazamiento,,0,80509,0 -9743,32101636,Procesador de señal digital (dsp),,0,80509,0 -9744,32101637,Procesadores de red,,0,80509,0 -9745,32101638,Circuito integrado de aplicación específica,,0,80509,0 -9746,32101639,Microcontrolador de 8 bits,,0,80509,0 -9747,32101640,Microcontrolador de 16 bits,,0,80509,0 -9748,32101641,Procesador de señal digital de extremo bajo dsp,,0,80509,0 -9749,32101642,Microcontrolador de rango medio de 32 bits,,0,80509,0 -9750,32101643,Circuito integrado lógico programable,,0,80509,0 -9751,32101644,Procesador de señal digital de extremo alto dsp,,0,80509,0 -9752,32101645,Microcontrolador de rango alto de 32 bits,,0,80509,0 -9753,32101646,Memoria flash paralelo (nor),Memoria flash paralela (NOR),0,80509,0 -9754,32101647,Memoria flash serial (nor),,0,80509,0 -9755,32101648,Memoria flash (nand),,0,80509,0 -9756,32101649,Circuito integrado estándar análogo o lineal,,0,80509,0 -9757,32101650,Circuito integrado de amplificador de audio análogo,,0,80509,0 -9758,32101651,Circuito integrado de amplificador de audio digital,,0,80509,0 -9759,32101652,Circuito integrado lógico estándar,,0,80509,0 -9760,32101653,Circuito integrado microondas,,0,80509,0 -9761,32101654,Circuito integrado bus transceptor,,0,80509,0 -9762,32101655,Circuito integrado de servicio de radio audio digital satelital,,0,80509,0 -9763,32101656,Circuito integrado de sistema de posicionamiento geográfico gps,,0,80509,0 -9764,32101657,Circuito integrado de sintonizador,,0,80509,0 -9765,32101658,Circuito integrado de acelerador de gráficos,,0,80509,0 -9766,32101659,Circuito integrado de exhibición de drivers,,0,80509,0 -9767,32101660,Circuito integrado de sensor de tasa angular,,0,80509,0 -9768,32101661,Circuito integrado acelerómetro,,0,80509,0 -9769,32101662,Circuito integrado efecto hall,,0,80509,0 -9770,32101663,Circuito integrado sensor temperatura infrarroja ir,Circuito integrado del sensor infrarrojo de la temperatura del IR,0,80509,0 -9771,32101664,Circuito integrado ultrasónico,,0,80509,0 -9772,32101665,Circuito integrado de sensor de imagen,,0,80509,0 -9773,32101666,Circuito integrado de procesador de videos o media,,0,80509,0 -9774,32101667,Circuito integrado de procesador de visión,,0,80509,0 -9775,32101668,Circuito integrado condecs,Circuito integrado de códec,0,80509,0 -9776,32101669,Circuito integrado de protocolo de comunicación,,0,80509,0 -9777,32101670,Circuito integrado de transponedor,,0,80509,0 -9778,32101671,Circuito integrado de sensor de presión,,0,80509,0 -9779,32101672,Bastidor de conductores,,0,80509,0 -9780,32111500,Diodos,,0,80509,0 -9781,32111501,Diodos de microondas,,0,80509,0 -9782,32111502,Diodos zener,,0,80509,0 -9783,32111503,Diodos emisores de luz (led),,0,80509,0 -9784,32111504,Diodos schottky,,0,80509,0 -9785,32111505,Diodos con efecto túnel,,0,80509,0 -9786,32111506,Diodos fotosensibles,,0,80509,0 -9787,32111507,Diodos de capacitancia variable,,0,80509,0 -9788,32111508,Diodos solares,,0,80509,0 -9789,32111509,Diodos de energía,,0,80509,0 -9790,32111510,Diodos de radiofrecuencia (rf),,0,80509,0 -9791,32111511,Diodos de señal pequeña,,0,80509,0 -9792,32111512,Diodo láser,,0,80509,0 -9793,32111513,Diodo varactor,,0,80509,0 -9794,32111514,Diodo pin,,0,80509,0 -9795,32111515,Diodo óptico,,0,80509,0 -9796,32111600,Transistores,,0,80509,0 -9797,32111601,Transistores fotosensibles,,0,80509,0 -9798,32111602,Transistor de efecto de campo (fet),,0,80509,0 -9799,32111603,Transistores mos de efecto de campo (mosfet),Transistores de efecto de campo de silicona de óxido metálico (MOSFET),0,80509,0 -9800,32111604,Chips de transistor,,0,80509,0 -9801,32111607,Transistores bipolares de radiofrecuencia (rf) o darlington,,0,80509,0 -9802,32111608,Transistores uniempalme,,0,80509,0 -9803,32111609,Transistores bipolares de puerta aislada (igbt),,0,80509,0 -9804,32111610,Transistores de efecto de campo de unión (jfet),,0,80509,0 -9805,32111611,Transistores bipolares de unión (bjt),,0,80509,0 -9806,32111612,Transistor de efecto de campo de potencia,,0,80509,0 -9807,32111613,Transistor bipolar o de radio frecuencia bipolar,,0,80509,0 -9808,32111614,Transistor de efecto de campo inteligente,,0,80509,0 -9809,32111615,Transistor de efecto de campo de señal pequeña,,0,80509,0 -9810,32111616,Transistor de efecto de campo de radio frecuencia,,0,80509,0 -9811,32111700,Aparatos semiconductores,,0,80509,0 -9812,32111701,Células fotovoltaicas,,0,80509,0 -9813,32111702,Tiristores,,0,80509,0 -9814,32111703,Diacs,Diodo para corriente alterna,0,80509,0 -9815,32111704,Triacs,Tríodo para corriente alterna,0,80509,0 -9816,32111705,Aisladores acoplados ópticos,,0,80509,0 -9817,32111706,Osciladores a cristal,,0,80509,0 -9818,32111707,Suspector de semiconductor,,0,80509,0 -9819,32111708,Red de adaptación de impedancia,,0,80509,0 -9820,32111709,Red de compensación de temperatura,,0,80509,0 -9821,32121500,Capacitores,,0,80509,0 -9822,32121501,Capacitores fijos,,0,80509,0 -9823,32121502,Capacitores o varactores variables,,0,80509,0 -9824,32121503,Capacitores ajustables pre - ajustados,,0,80509,0 -9825,32121504,Redes de capacitores,,0,80509,0 -9826,32121505,Capacitor fijo de aluminio electrolítico,,0,80509,0 -9827,32121506,Capacitor cerámico fijo,,0,80509,0 -9828,32121507,Capacitor de película fijo,,0,80509,0 -9829,32121508,Capacitor fijo de tántalo,,0,80509,0 -9830,32121509,Capacitor de aire,,0,80509,0 -9831,32121510,Capacitor de gas,,0,80509,0 -9832,32121511,Capacitor de aceite,,0,80509,0 -9833,32121512,Capacitor de mica,,0,80509,0 -9834,32121513,Capacitor de vacío,,0,80509,0 -9835,32121514,Capacitor de papel,,0,80509,0 -9836,32121515,Capacitor de papel metalizado,,0,80509,0 -9837,32121600,Resistores,,0,80509,0 -9838,32121602,Resistores fusibles,,0,80509,0 -9839,32121603,Resistores o varistores variables,,0,80509,0 -9840,32121607,Redes de resistores,,0,80509,0 -9841,32121609,Resistores fijos,,0,80509,0 -9842,32121610,Termistor,,0,80509,0 -9843,32121611,Placa restrictora o restrictor de aire,,0,80509,0 -9844,32121612,Resistor montado en superficie,,0,80509,0 -9845,32121613,Resistor embobinado,Resistencia bobinada,0,80509,0 -9846,32121614,Resistor de óxido de película de metal,,0,80509,0 -9847,32121615,Resistor de película de carbono,,0,80509,0 -9848,32121616,Resistor recortador,,0,80509,0 -9849,32121617,Resistor de potencia,,0,80509,0 -9850,32121618,Resistor de película de metal,,0,80509,0 -9851,32121700,Componentes discretos,,0,80509,0 -9852,32121701,Rectificadores,,0,80509,0 -9853,32121702,Inductores,,0,80509,0 -9854,32121703,Ferritas,,0,80509,0 -9855,32121704,Convertidores estáticos,,0,80509,0 -9856,32121705,Inversores,,0,80509,0 -9857,32121706,Redes r/c de resistores o capacitores,,0,80509,0 -9858,32121707,Rectificador controlado de silicona,,0,80509,0 -9859,32121708,Rectificador de puente,,0,80509,0 -9860,32121709,Inductor de embobinado,,0,80509,0 -9861,32121710,Inductor multi capa,,0,80509,0 -9862,32121711,Inductor de estrangulador,,0,80509,0 -9863,32121712,Bobina de alta frecuencia,,0,80509,0 -9864,32131000,"Accesorios, materias primas y piezas de componentes electrónicos",,0,80509,0 -9865,32131001,Piletas térmicas,,0,80509,0 -9866,32131002,Matrices de semiconductor,,0,80509,0 -9867,32131003,Pastillas de semiconductor,,0,80509,0 -9868,32131005,Paquetes de circuitos integrados,,0,80509,0 -9869,32131006,Soportes o zócalos de circuito integrado,,0,80509,0 -9870,32131007,Soportes de componentes discretos,,0,80509,0 -9871,32131008,Compuestos disipadores de calor,,0,80509,0 -9872,32131009,Aisladores para disipadores de calor,,0,80509,0 -9873,32131010,Tarjetas sencillas de circuitos impresos,,0,80509,0 -9874,32131011,Cubiertas de circuitos integrados,,0,80509,0 -9875,32131012,Objetivos de erosión superficial,,0,80509,0 -9876,32131013,Máscara de foto,,0,80509,0 -9877,32131014,"Tablero de circuito impreso flexible, desnudo",,0,80509,0 -9878,32131015,"Tablero de circuito impreso por un solo lado, desnudo",,0,80509,0 -9879,32131016,"Tablero de circuito impreso por un ambos lados, desnudo",,0,80509,0 -9880,32131017,"Tablero de circuito impreso multicapa, desnudo",,0,80509,0 -9881,32131018,"Tablero de circuito impreso con capa pesada de bronce, desnudo",,0,80509,0 -9882,32131019,"Tablero de circuito impreso electroplateado con oro, desnudo",,0,80509,0 -9883,32131020,Tablero de circuito impreso ensamblado flexible,,0,80509,0 -9884,32131021,Sustrato cerámico combustión combinada a baja temperatura,,0,80509,0 -9885,32131022,Cerámica para sustratos de tableros de circuitos impresos pcb,Cerámica para placas de circuitos impresos (PCB),0,80509,0 -9886,32131023,Soporte de dispositivos eléctricos o electrónicos,,0,80509,0 -9887,32141000,Tubos electrónicos,,0,80509,0 -9888,32141001,Tubos de rayos catódicos,,0,80509,0 -9889,32141002,Klistrones,Válvula de vacío,0,80509,0 -9890,32141003,Magnetrones,,0,80509,0 -9891,32141004,Tubos de ondas progresivas,,0,80509,0 -9892,32141005,Tubos de disco sellado,,0,80509,0 -9893,32141006,Resnatrones,Resnatron,0,80509,0 -9894,32141007,Tiratrones,,0,80509,0 -9895,32141008,Ignitrones,,0,80509,0 -9896,32141009,Tubos fotoeléctricos,,0,80509,0 -9897,32141010,Tubos fotomultiplicadores,,0,80509,0 -9898,32141011,Tubos receptores de televisión o cámara,,0,80509,0 -9899,32141012,Tubo de diodo,,0,80509,0 -9900,32141013,Tubo de tríodo,,0,80509,0 -9901,32141014,Tubos de tetrodo,,0,80509,0 -9902,32141015,Tubos de pentodo,,0,80509,0 -9903,32141016,Tubos múltiples,,0,80509,0 -9904,32141017,Tubo contador,,0,80509,0 -9905,32141018,Tubo de rayo de salida,,0,80509,0 -9906,32141019,Tubo electrón de microondas,,0,80509,0 -9907,32141020,Tubo convertidor de frecuencia,,0,80509,0 -9908,32141021,Tubo rectificador,,0,80509,0 -9909,32141022,Tubo regulador electrónico de voltaje,,0,80509,0 -9910,32141100,Piezas y accesorios de tubos electrónicos,,0,80509,0 -9911,32141101,Cátodos o emisores,,0,80509,0 -9912,32141102,Elementos de ánodo,,0,80509,0 -9913,32141103,Elementos de rejilla,,0,80509,0 -9914,32141104,Elementos deflectores,,0,80509,0 -9915,32141105,Obturadores o fundas de tubo,,0,80509,0 -9916,32141106,Bases de tubo,,0,80509,0 -9917,32141107,Zócalos de tubo,,0,80509,0 -9918,32141108,Clavijas de electrodo,,0,80509,0 -9919,32141109,Portaelectrodos,,0,80509,0 -9920,32141110,Pieza de polo magnético,,0,80509,0 -9921,32151500,Dispositivos de control de indicación y de señalización,,0,80509,0 -9922,32151501,Módulo de sonido de control,,0,80509,0 -9923,32151502,Módulo de luz,,0,80509,0 -9924,32151503,Apilamiento de luz,,0,80509,0 -9925,32151504,Ecualizador de circuito electrónico,,0,80509,0 -9926,32151600,Subsistemas programables de controlador lógico,,0,80509,0 -9927,32151601,Subsistema i/o chasis controlador lógico programable,,0,80509,0 -9928,32151602,Subsistema i/o chasis controlador lógico programable distribuido en gabinete,,0,80509,0 -9929,32151603,Subsistema i/o chasis controlador lógico programable distribuido en la máquina,,0,80509,0 -9930,32151700,Controladores lógicos programables,,0,80509,0 -9931,32151701,Dispositivo de unión control de red,,0,80509,0 -9932,32151702,Interfaz pc red control,,0,80509,0 -9933,32151703,Accesorios de controlador lógico programable,,0,80509,0 -9934,32151704,Chasis de controlador lógico programable,,0,80509,0 -9935,32151705,Módulo de controlador lógico programable,,0,80509,0 -9936,32151706,Suministro de energía de controlador lógico programable,,0,80509,0 -9937,32151707,Dispositivo de programación del controlador lógico programable,,0,80509,0 -9938,32151800,Dispositivos de control de seguridad,,0,80509,0 -9939,32151801,Interruptor de carga iec,,0,80509,0 -9940,32151802,Módulo de control de seguridad,,0,80509,0 -9941,32151803,Sistema de aislación de seguridad,,0,80509,0 -9942,32151804,Cortina y scanner de luz de seguridad,Escáner y barrera de seguridad,0,80509,0 -9943,32151805,Estera (mat) y borde de seguridad,,0,80509,0 -9944,32151900,Dispositivos de automatización de control de la conectividad,,0,80509,0 -9945,32151901,Conector de paso de mamparo,,0,80509,0 -9946,32151902,Conjunto de cables del sistema de control,,0,80509,0 -9947,32151903,Caja de distribución del sistema de control,,0,80509,0 -9948,32151904,Cable de conexión del sistema de control,,0,80509,0 -9949,32151905,Receptáculo del sistema de control,,0,80509,0 -9950,32151906,Splitter del sistema de control,,0,80509,0 -9951,32151907,Cable en y del sistema de control,,0,80509,0 -9952,32151908,Cableado del sistema de control,,0,80509,0 -9953,32151909,Conector desmontable de campo,,0,80509,0 -9954,32151910,Sistema de control del panel de paso,,0,80509,0 -9955,39101600,Lámparas y bombillas,Lámparas y focos,0,80509,0 -9956,39101601,Lámparas halógenas,Focos halógenos,0,80509,0 -9957,39101602,Lámparas médicas,,0,80509,0 -9958,39101603,Lámparas solares,,0,80509,0 -9959,39101605,Lámparas fluorescentes,Focos fluorescentes,0,80509,0 -9960,39101608,Luz asómbrica o scialytica de operación,Lámpara de operación asómbrica o cialítica,0,80509,0 -9961,39101609,Lámparas de escenario o estudio,,0,80509,0 -9962,39101612,Lámparas incandescentes,Focos incandescentes,0,80509,0 -9963,39101613,Lámparas infrarrojas,,0,80509,0 -9964,39101614,Lámparas de haluro- metálico,,0,80509,0 -9965,39101615,Lámparas de vapor de mercurio,,0,80509,0 -9966,39101616,Lámparas de rayos ultravioleta (uv),,0,80509,0 -9967,39101617,Lámparas de sodio de alta presión hid,,0,80509,0 -9968,39101618,Lámparas de neón,Focos de neón,0,80509,0 -9969,39101619,Lámparas compactas fluorescentes cfl,,0,80509,0 -9970,39101620,Lámparas de inducción,,0,80509,0 -9971,39101621,Lámparas de sodio de baja presión hid,,0,80509,0 -9972,39101622,Lámparas en miniatura,,0,80509,0 -9973,39101623,Lámparas de rayo sellado,,0,80509,0 -9974,39101624,Luz negra,,0,80509,0 -9975,39101625,Lámparas de xenón,,0,80509,0 -9976,39101626,Lámparas de criptón,,0,80509,0 -9977,39101627,Lámparas de arco,,0,80509,0 -9978,39101800,Componentes y accesorios de Lámpara,,0,80509,0 -9979,39101801,Filamento de lámpara,,0,80509,0 -9980,39101802,Vidrio de lámpara,,0,80509,0 -9981,39101803,Base de lámpara,Socket para foco,0,80509,0 -9982,39101804,Ignitor de lámpara de sodio de alta presión,,0,80509,0 -9983,39101805,Capacitor seco de haluro de metal,,0,80509,0 -9984,39101806,Capacitor seco de lámpara de sodio de alta presión,,0,80509,0 -9985,39101900,Balastos de lámparas y transformadores de lámparas,,0,80509,0 -9986,39101901,Balasto fluorescente,,0,80509,0 -9987,39101902,Balasto de alta intensidad de descarga hid,,0,80509,0 -9988,39101903,Sistema de inducción de iluminación,,0,80509,0 -9989,39101904,Transformador de iluminación de bajo voltaje,,0,80509,0 -9990,39101905,Balasto de neón,Balastro de neón,0,80509,0 -9991,39101906,Transformador de lámpara reductora,,0,80509,0 -9992,39101907,Balasto de alta intensidad de descarga electrónica ehid,Balastro de electrónico de alta intensidad descarga EHID,0,80509,0 -9993,39111500,Iluminación de interiores y artefactos,,0,80509,0 -9994,39111501,Artefactos fluorescentes,,0,80509,0 -9995,39111503,Dispositivos de pared,,0,80509,0 -9996,39111504,Sistemas de iluminación de escenario o estudio,,0,80509,0 -9997,39111505,Iluminación empotrada,,0,80509,0 -9998,39111506,Arañas de luces,Candelabros,0,80509,0 -9999,39111507,Artefactos de escritorio,,0,80509,0 -10000,39111508,Alumbrado de pista,,0,80509,0 -10001,39111509,Lámparas de pie,,0,80509,0 -10002,39111510,Lámparas de mesa,,0,80509,0 -10003,39111512,Luces de banco de laboratorio,,0,80509,0 -10004,39111515,Artefactos para lámpara proyectada hacia abajo,,0,80509,0 -10005,39111520,Artefactos de alumbrado halógeno,,0,80509,0 -10006,39111521,Plafones,,0,80509,0 -10007,39111522,Iluminación colgante,,0,80509,0 -10008,39111524,Accesorio de alta intensidad de descarga hid,,0,80509,0 -10009,39111525,Accesorio incandescente,,0,80509,0 -10010,39111527,Accesorio de iluminación solar,,0,80509,0 -10011,39111528,Accesorio para debajo de gabinete,,0,80509,0 -10012,39111529,Gobo de iluminación,,0,80509,0 -10013,39111530,Accesorio para lámpara proyectada hacia abajo,,0,80509,0 -10014,39111531,Orientación para iluminación comercial,,0,80509,0 -10015,39111532,Accesorio fluorescente para grandes áreas,,0,80509,0 -10016,39111533,Banda de iluminación,Tira fluorescente,0,80509,0 -10017,39111534,Luminaria fluorescente con lente,,0,80509,0 -10018,39111535,Accesorio parabólico,,0,80509,0 -10019,39111536,Iluminador de tocador,Iluminación de vanidad,0,80509,0 -10020,39111537,Dispositivo para iluminaciones deportivas,,0,80509,0 -10021,39111538,Dispositivo para iluminaciones de garaje o marquesina,,0,80509,0 -10022,39111539,Caja de iluminación,,0,80509,0 -10023,39111540,Sistema de iluminación de auto – elevación,,0,80509,0 -10024,39111541,Accesorio de dispositivo de iluminación interior,,0,80509,0 -10025,39111600,Iluminación exterior y artefactos,,0,80509,0 -10026,39111603,Alumbrado de la vía pública,,0,80509,0 -10027,39111605,Iluminación paisajística,,0,80509,0 -10028,39111606,Alumbrado submarino,,0,80509,0 -10029,39111608,Alumbrado de zonas residenciales,,0,80509,0 -10030,39111609,"Linternas de queroseno, propano o butano","Lámpara de queroseno, propano o butano",0,80509,0 -10031,39111610,Linternas,,0,80509,0 -10032,39111611,Reflectores,,0,80509,0 -10033,39111612,Iluminación de áreas,,0,80509,0 -10034,39111613,Iluminación de seguridad,,0,80509,0 -10035,39111614,Dispositivo de iluminación de buzones,,0,80509,0 -10036,39111615,Luces exteriores para iluminación puntual instaladas en el suelo,,0,80509,0 -10037,39111616,Accesorios de iluminación exterior,,0,80509,0 -10038,39111700,Alumbrado de emergencia,,0,80509,0 -10039,39111703,Luces de tormenta,,0,80509,0 -10040,39111705,Barras fluorescentes o de iluminación,,0,80509,0 -10041,39111706,Luces de emergencia o estroboscópicas (licuadoras),,0,80509,0 -10042,39111707,Unidad de salida de combo de luz,,0,80509,0 -10043,39111708,Señal iluminada de salida de emergencia,Señal luminosa de salida de emergencia,0,80509,0 -10044,39111709,Unidad de luz de emergencia,,0,80509,0 -10045,39111710,Accesorios de iluminación de emergencia,,0,80509,0 -10046,39111711,Kit de conversión de salida de luz,,0,80509,0 -10047,39111712,Galería de montaje de luz de salida,,0,80509,0 -10048,39111713,Dispositivo de iluminación remota,,0,80509,0 -10049,39111714,Bengala de señalización,,"0,1",80509,0 -10050,39111800,Accesorios de iluminación,,0,80509,0 -10051,39111801,Balastos de lámparas,,0,80509,0 -10052,39111802,Cubiertas de lámpara,,0,80509,0 -10053,39111803,Enchufes de lámparas,,0,80509,0 -10054,39111806,Cajas de iluminación,,0,80509,0 -10055,39111808,Parrillas,,0,80509,0 -10056,39111809,Filtros acondicionadores de luz,,0,80509,0 -10057,39111810,Interruptor de lámpara,,0,80509,0 -10058,39111811,Líneas férreas electrificadas,,0,80509,0 -10059,39111812,Pantallas de lámparas,,0,80509,0 -10060,39111813,Brazos de lámparas,,0,80509,0 -10061,39111814,Kit de brida de iluminación de marco de cielorraso,Juego de tapa de luz de marco de techo,0,80509,0 -10062,39111815,Protector de lámpara y de dispositivo de lámpara,,0,80509,0 -10063,39111816,Lente de lámpara,,0,80509,0 -10064,39111817,Accesorio para montaje de lámpara,,0,80509,0 -10065,39111818,Reflector de lámpara,,0,80509,0 -10066,39111819,Accesorio de seguridad de lámpara,,0,80509,0 -10067,39111820,Kit de ensamble de cableado de lámpara,,0,80509,0 -10068,39111821,Kit de retro – adaptación de iluminación,,0,80509,0 -10069,39111822,Soporte de pared para iluminación,,0,80509,0 -10070,39111823,Tapa para poste de luz,,0,80509,0 -10071,39111824,Lente difusor de iluminación,,0,80509,0 -10072,39111825,Lámpara con bisagras,,0,80509,0 -10073,39111826,Juego de cables de iluminación,,0,80509,0 -10074,39111827,Reflector o redireccionador de iluminación,,0,80509,0 -10075,39111828,Dispositivo de puerta de iluminación,,0,80509,0 -10076,39111829,Cubierta del canal de iluminación,,0,80509,0 -10077,39111830,Brazo de soporte de iluminación,,0,80509,0 -10078,39111900,Señalización e iluminación de emplazamientos peligrosos,,0,80509,0 -10079,39111903,Iluminación de minería subterránea,,0,80509,0 -10080,39111904,Dispositivo de ubicación marina,Dispositivo de localización marina,0,80509,0 -10081,39111905,Dispositivo de iluminación de sala blanca,,0,80509,0 -10082,39111906,Dispositivo de luz de servicio en condiciones difíciles,,0,80509,0 -10083,39111907,Dispositivo de luz hermética de vapor,,0,80509,0 -10084,39111908,Dispositivo de iluminación protector contra vandalismo,,0,80509,0 -10085,39111909,Accesorio de dispositivo de iluminación de ambiente especial,,0,80509,0 -10086,39112000,Proyectores móviles,,0,80509,0 -10087,39112001,Torre de luz,,0,80509,0 -10088,39112002,Carro de iluminación,,0,80509,0 -10089,39112003,Stand de iluminación,,0,80509,0 -10090,39112004,Luz para puertos de cargue,,0,80509,0 -10091,39112005,Luz fluorescente portátil,,0,80509,0 -10092,39112006,Lámpara de mano portátil,,0,80509,0 -10093,39112007,Lámpara de mano portátil de descarga de alta intensidad hid,,0,80509,0 -10094,39112008,Luz incandescente portátil,,0,80509,0 -10095,39112009,Luz de mecánicos,,0,80509,0 -10096,39112010,Luz de cuerda,,0,80509,0 -10097,39112011,Árbol o hilo de luz,,0,80509,0 -10098,39112012,Luz manual o extensión,,0,80509,0 -10099,39112013,Accesorio de iluminación portátil y temporal,,0,80509,0 -10100,39112100,Iluminación óptica,,0,80509,0 -10101,39112101,Iluminación de fibra óptica,,0,80509,0 -10102,39112102,Iluminación óptica de diodo emisor de luz led,,0,80509,0 -10103,39112200,Dispositivos de efectos especiales,,0,80509,0 -10104,39112201,Aparato de efecto atmosférico,,0,80509,0 -10105,39112202,Máquina de burbujas,,0,80509,0 -10106,39112300,Iluminación y accesorios de escenarios y estudios,,0,80509,0 -10107,39112301,Cambiador de color,,0,80509,0 -10108,39112302,Filtros de color,,0,80509,0 -10109,39112303,Soportes y marcos de filtros,,0,80509,0 -10110,39112304,Soportes gobo,,0,80509,0 -10111,39112305,Accesorios de rotadores gobo y efecto de movimiento de luz,,0,80509,0 -10112,39112306,Diafragma iris,,0,80509,0 -10113,39112307,Barras de iluminación,,0,80509,0 -10114,39112308,Sillas iluminadas,,0,80509,0 -10115,39112309,Bola de disco o bola de espejos,,0,80509,0 -10116,39112400,Dispositivos para manejo de luces y control de escenarios y estudios,,0,80509,0 -10117,39112401,Dimers y accesorios,,0,80509,0 -10118,39112402,Consolas y accesorios para el control de iluminación,,0,80509,0 -10119,39112403,Suministro de energía y unidades de control de iluminación,,0,80509,0 -10120,39112500,Luminarias de escenarios y estudios,,0,80509,0 -10121,39112501,Luz de foco,,0,80509,0 -10122,39112502,Luz de seguir,,0,80509,0 -10123,39112503,Luces móviles,,0,80509,0 -10124,39112504,Spots de latas par y pasadores,Lámparas parcan y pinspot,0,80509,0 -10125,39112505,Luminarias tipo perfil,,0,80509,0 -10126,39112506,Bancos spot,,0,80509,0 -10127,39112507,Luces de franjas,,0,80509,0 -10128,39112508,Luminarias ultravioleta y de luz negra,Lámparas ultravioleta y de luz negra,0,80509,0 -10129,39112600,Dispositivos de iluminación no - eléctrica,,0,80509,0 -10130,39112601,Lámpara de alcohol,,0,80509,0 -10131,39112602,Candelero,,0,80509,0 -10132,39112603,Lámpara de kerosene o de propano o de gas natural o de butano,,0,80509,0 -10133,39112604,Velas de cera,,0,80509,0 -10134,39121000,Equipamiento para distribución y conversión de alimentación,,0,80509,0 -10135,39121001,Transformadores de distribución de potencia,,0,80509,0 -10136,39121002,Transformadores de suministro de potencia,,0,80509,0 -10137,39121003,Transformadores de instrumentos,,0,80509,0 -10138,39121004,Unidades de suministro de energía,Fuente de poder,0,80509,0 -10139,39121006,Adaptadores o inversores de potencia,,0,80509,0 -10140,39121007,Conversores de frecuencia,,0,80509,0 -10141,39121008,Conversores de señales,,0,80509,0 -10142,39121009,Reguladores eléctricos o de potencia,,0,80509,0 -10143,39121010,Bobinas magnéticas,,0,80509,0 -10144,39121011,Fuentes ininterrumpibles de potencia,,0,80509,0 -10145,39121012,Estrangulador,,0,80509,0 -10146,39121013,Convertidores rotativos eléctricos,,0,80509,0 -10147,39121014,Bancos de capacitores,,0,80509,0 -10148,39121015,Reactores,,0,80509,0 -10149,39121016,Anillos colectores,,0,80509,0 -10150,39121017,Unidades de distribución de alimentación (pdus),Unidades de distribución de energía (PDU),0,80509,0 -10151,39121018,Barreras de seguridad intrínseca,,0,80509,0 -10152,39121019,Dispositivos de acoplamiento por inducción,,0,80509,0 -10153,39121020,Acondicionadores de señal,,0,80509,0 -10154,39121021,Unidades servo,,0,80509,0 -10155,39121022,Transformadores electrónicos,,0,80509,0 -10156,39121023,Compensadores var estática,,0,80509,0 -10157,39121024,Condensadores síncronos,,0,80509,0 -10158,39121025,Transformadores buck – boost,,0,80509,0 -10159,39121026,Transformadores de control de potencia,,0,80509,0 -10160,39121027,Transformador encapsulado,,0,80509,0 -10161,39121028,Transformador de mitigación armónica,,0,80509,0 -10162,39121029,Transformador de aislación,,0,80509,0 -10163,39121030,Transformador montado en pad,,0,80509,0 -10164,39121031,Strip de salida del suministro eléctrico,,0,80509,0 -10165,39121032,Transformador de corriente,,0,80509,0 -10166,39121033,Transformador potencial,,0,80509,0 -10167,39121034,Transformador de corriente de fase cero,,0,80509,0 -10168,39121035,Compensador de arranque de motor,,0,80509,0 -10169,39121036,Transformador de radio frecuencia rf,,0,80509,0 -10170,39121037,Transformador de limite,,0,80509,0 -10171,39121038,Yugo deflector,Yugo de desviación,0,80509,0 -10172,39121039,Suministro de potencia de celda de combustible,,0,80509,0 -10173,39121040,Ahorrador de energía eléctrica,,0,80509,0 -10174,39121041,Transformador de voltaje constante,,0,80509,0 -10175,39121042,Transformador de filamento,,0,80509,0 -10176,39121043,Transformador rotativo,,0,80509,0 -10177,39121044,Transformador de audio frecuencia,,0,80509,0 -10178,39121045,Transformador de modulación,,0,80509,0 -10179,39121046,Transformador para igualar impedancia,,0,80509,0 -10180,39121047,Transformador de frecuencia intermedia,,0,80509,0 -10181,39121100,Centros de control y distribución y accesorios,,0,80509,0 -10182,39121101,Centros de carga,,0,80509,0 -10183,39121102,Tomas o centros de medidores,,0,80509,0 -10184,39121103,Paneles,,0,80509,0 -10185,39121104,Centros de control de motor,,0,80509,0 -10186,39121105,Sistemas de conmutadores,,0,80509,0 -10187,39121106,Sistemas de control o vigilancia de potencia,,0,80509,0 -10188,39121107,Sistemas de control de iluminación,,0,80509,0 -10189,39121108,Accesorios del panel de control o distribución,,0,80509,0 -10190,39121109,Transformadores de transmisión,,0,80509,0 -10191,39121110,Tablero de interruptor de circuito,,0,80509,0 -10192,39121111,Tablero de fusibles,,0,80509,0 -10193,39121112,Tablero de bajo voltaje de corriente alterna y directa ac dc,,0,80509,0 -10194,39121113,Centro de control de motor de bajo voltaje,,0,80509,0 -10195,39121114,Centro de control de motor de medio voltaje,,0,80509,0 -10196,39121115,Tablero de controles de medio voltaje,,0,80509,0 -10197,39121116,Tablero de bajo voltaje,,0,80509,0 -10198,39121117,Bus bar,Barra colectora,0,80509,0 -10199,39121300,"Cuadros, registros y menaje para electricidad",,0,80509,0 -10200,39121301,Cerramientos del panel de control o distribución,,0,80509,0 -10201,39121302,Placas o cubiertas de cerramiento,,0,80509,0 -10202,39121303,Cajas eléctricas,,0,80509,0 -10203,39121304,Cubiertas de cajas eléctricas,,0,80509,0 -10204,39121305,Cajas a prueba de intemperie,,0,80509,0 -10205,39121306,Cajas de conmutadores,,0,80509,0 -10206,39121307,Cajas de suelo,,0,80509,0 -10207,39121308,Cajas de toma de corriente,,0,80509,0 -10208,39121309,Cajas eléctricas especiales,,0,80509,0 -10209,39121310,Cajas de uso general,,0,80509,0 -10210,39121311,Accesorios eléctricos,,0,80509,0 -10211,39121312,Bujes eléctricos,,0,80509,0 -10212,39121313,Bridas de techo,,0,80509,0 -10213,39121314,Cerramiento a prueba de explosión,,0,80509,0 -10214,39121315,Caja de dispositivos,,0,80509,0 -10215,39121316,Caja de cielorraso,,0,80509,0 -10216,39121317,Platillo de cielorraso,,0,80509,0 -10217,39121318,Cerramiento del interruptor de circuito,,0,80509,0 -10218,39121319,Cerramiento de transformador de corriente,,0,80509,0 -10219,39121320,Extensión de caja eléctrica,,0,80509,0 -10220,39121321,Ferretería y accesorios de caja eléctrica,,0,80509,0 -10221,39121322,Partición de caja eléctrica,,0,80509,0 -10222,39121323,Cerramiento de consola eléctrica,,0,80509,0 -10223,39121324,Cerramiento eléctrico de propósito general,,0,80509,0 -10224,39121325,Cerramiento de unidad de interfaz de operador eléctrico,,0,80509,0 -10225,39121326,Cerramiento de botón eléctrico,,0,80509,0 -10226,39121327,Cerramiento de terminal eléctrico,,0,80509,0 -10227,39121328,Caja de perforación calificada de fuego,,0,80509,0 -10228,39121329,Gabinete de fusibles,,0,80509,0 -10229,39121330,Caja de distribución eléctrica,,0,80509,0 -10230,39121331,Caja de ubicación peligrosa,,0,80509,0 -10231,39121332,Caja de unión eléctrica,,0,80509,0 -10232,39121333,Caja de mampostería,,0,80509,0 -10233,39121334,Cerramiento eléctrico modular,,0,80509,0 -10234,39121335,Gabinete de terminaciones telefónicas,,0,80509,0 -10235,39121336,Cerramiento eléctrico subterráneo,Recinto eléctrico subterráneo,0,80509,0 -10236,39121337,Compuesto de sellamiento eléctrico,,0,80509,0 -10237,39121400,"Lengüetas de conexión, conectadores y terminales",,0,80509,0 -10238,39121402,Enchufes eléctricos,,0,80509,0 -10239,39121403,Enchufe de bloqueo,Bloqueo para enchufes,0,80509,0 -10240,39121404,Manguitos eléctricos,Manguito de empalme,0,80509,0 -10241,39121405,Terminales de cable o alambre,Terminal conector de empalme,0,80509,0 -10242,39121406,Receptáculos eléctricos,,0,80509,0 -10243,39121407,Strips de conexiones,,0,80509,0 -10244,39121408,Conexiones mecánicas,,0,80509,0 -10245,39121409,Conectores de cables eléctricos,,0,80509,0 -10246,39121410,Bloques de terminales,,0,80509,0 -10247,39121412,Conectores de soporte posterior,,0,80509,0 -10248,39121413,Conectores circulares,,0,80509,0 -10249,39121414,Conectores coaxiales,,0,80509,0 -10250,39121415,Conectores planos,,0,80509,0 -10251,39121416,Tapas de conectores eléctricos,,0,80509,0 -10252,39121419,Conexiones flexibles,,0,80509,0 -10253,39121420,Conectores herméticos,,0,80509,0 -10254,39121421,Ensambles de conectores,,0,80509,0 -10255,39121423,Conectores de resorte,,0,80509,0 -10256,39121424,Tapas del bloque de terminales,,0,80509,0 -10257,39121425,Separador de tablero de terminales,PCB terminal,0,80509,0 -10258,39121426,Puente de conexión,,0,80509,0 -10259,39121427,Bobinadoras de cable,,0,80509,0 -10260,39121428,Bobinadoras eléctricas,,0,80509,0 -10261,39121431,Conectores estancos de cables,,0,80509,0 -10262,39121432,Terminales eléctricos,,0,80509,0 -10263,39121433,Conectores de radiofrecuencia (rf),,0,80509,0 -10264,39121434,Conectores de tubos metálicos eléctricos (emt),,0,80509,0 -10265,39121435,Hilos o cables de conexión,,0,80509,0 -10266,39121436,Electrodos,,0,80509,0 -10267,39121437,Patines de toma de corriente,,0,80509,0 -10268,39121438,Conector automático de hilos o cables,,0,80509,0 -10269,39121440,Cable de extensión eléctrica,Extensión eléctrica,0,80509,0 -10270,39121441,Cable de puente eléctrico,Cable pasa corriente,0,80509,0 -10271,39121442,Ensamble de puerto eléctrico,,0,80509,0 -10272,39121443,Conector de bus subterráneo eléctrico,Conector de bus,0,80509,0 -10273,39121444,Enchufe y módulo rj,,0,80509,0 -10274,39121445,Torsión en conector de cable,,0,80509,0 -10275,39121446,Equipo conector de terminal de cable,Conector para cable,0,80509,0 -10276,39121447,Desconexión de terminal de cable,Terminal hembra de desconexión,0,80509,0 -10277,39121448,Conector de cable o alambre de compresión,Conector de compresión de cable,0,80509,0 -10278,39121449,Empalme de cable o alambre de compresión,Empalme de compresión de cables,0,80509,0 -10279,39121450,Cabeza de martillo,Perno de cabeza de martillo,0,80509,0 -10280,39121451,Conectores tap off,Acoplador direccional,0,80509,0 -10281,39121452,Unión de cable,Articulación para cable,0,80509,0 -10282,39121453,Unión de cangrejo,Conector de cangrejo,0,80509,0 -10283,39121454,Terminal de batería,,0,80509,0 -10284,39121455,Conector de molde de inserción eléctrica,,0,80509,0 -10285,39121456,Conector de subestación,,0,80509,0 -10286,39121457,Conector exotérmico,Soldadura exotérmica,0,80509,0 -10287,39121458,Reductor de compresión de cable o alambre,Terminal compresión para cable,0,80509,0 -10288,39121459,Placa de circuito impreso pcb con prensa conector de cabecera,,0,80509,0 -10289,39121460,Conector con filtro de cabecera,,0,80509,0 -10290,39121461,Sello de conector y cable,Conector de la glándula,0,80509,0 -10291,39121462,Conector de telecomunicaciones,,0,80509,0 -10292,39121463,Conector para enchufe,,0,80509,0 -10293,39121464,Codo de tubo metálico eléctrico emt,,0,80509,0 -10294,39121465,Trenza flexible,,0,80509,0 -10295,39121466,Terminal y tapa de conexión mecánica,,0,80509,0 -10296,39121467,Enchufe y tapa de conexión mecánica,,0,80509,0 -10297,39121500,"Conmutadores, controles y relés y accesorios",,0,80509,0 -10298,39121521,Controles de motor de arranque,,0,80509,0 -10299,39121522,Contactos eléctricos,,0,80509,0 -10300,39121523,Temporizadores,,0,80509,0 -10301,39121524,Fotocontroles,,0,80509,0 -10302,39121527,Codificadores,,0,80509,0 -10303,39121528,Sensores fotoeléctricos,,0,80509,0 -10304,39121529,Contactores,Contactores electromagnéticos,0,80509,0 -10305,39121534,Luces indicadoras o indicadores luminosos,,0,80509,0 -10306,39121544,Piezas de luces indicadoras o accesorios,,0,80509,0 -10307,39121545,Paradas de emergencia,Botón paro de emergencia,0,80509,0 -10308,39121551,Piezas de reflector,,0,80509,0 -10309,39121552,Módulos de control eléctrico,,0,80509,0 -10310,39121555,Control de contador,,0,80509,0 -10311,39121561,Estación colgante de control,,0,80509,0 -10312,39121565,Estación de control de montaje de superficie,,0,80509,0 -10313,39121568,Reactor de derivación,,0,80509,0 -10314,39121569,Reacción en serie,Reactores en serie,0,80509,0 -10315,39121570,Re - cerrador,"Interruptor, Reconectador",0,80509,0 -10316,39121600,Dispositivos y accesorios para la protección de circuitos,,0,80509,0 -10317,39121601,Breakers de circuito,,0,80509,0 -10318,39121602,Breakers de circuito magnético,,0,80509,0 -10319,39121603,Micro disyuntores,,0,80509,0 -10320,39121604,Fusibles de retardo,,0,80509,0 -10321,39121605,Fusibles de tapón,,0,80509,0 -10322,39121606,Fusibles de cartucho,,0,80509,0 -10323,39121607,Fusibles de cuerpo de vidrio,,0,80509,0 -10324,39121609,Microfusibles,,0,80509,0 -10325,39121610,Supresor de ondas,,0,80509,0 -10326,39121611,Fusibles de cerámicas,,0,80509,0 -10327,39121612,Fusibles de cuchilla,,0,80509,0 -10328,39121613,Conjuntos o dispositivos de polo a tierra,,0,80509,0 -10329,39121614,Disyuntores de pérdida a tierra,,0,80509,0 -10330,39121615,Breakers de circuito de aire,,0,80509,0 -10331,39121616,Breakers de circuito de caja moldeada,,0,80509,0 -10332,39121617,Piezas de fusible o accesorios,,0,80509,0 -10333,39121618,Alambre para fusible,,0,80509,0 -10334,39121619,Fusibles tipo botella,,0,80509,0 -10335,39121620,Materiales transientes de protección,,0,80509,0 -10336,39121621,Accesorios y aparatos de protección contra rayos,,0,80509,0 -10337,39121622,Fusible automotriz,,0,80509,0 -10338,39121623,Fusible electrónico,,0,80509,0 -10339,39121624,Soporte de fusible,,0,80509,0 -10340,39121625,Fusible de alta velocidad,,0,80509,0 -10341,39121626,Fusible de medio voltaje,,0,80509,0 -10342,39121627,Fusible de potencia,,0,80509,0 -10343,39121628,Fusible semiconductor,,0,80509,0 -10344,39121629,Bloque de fusibles,,0,80509,0 -10345,39121630,Clip de fusible,,0,80509,0 -10346,39121631,Interruptor de circuito de alto voltaje lleno de aceite,,0,80509,0 -10347,39121632,Protector de redes,,0,80509,0 -10348,39121633,Interruptor de circuito,,0,80509,0 -10349,39121634,Protector de sobretensiones,,0,80509,0 -10350,39121635,Regulador de voltaje,,0,80509,0 -10351,39121636,Limitador de corriente,,0,80509,0 -10352,39121637,Supresor de arco,,0,80509,0 -10353,39121638,Corte de fusible,,0,80509,0 -10354,39121639,Interruptor de vacío,,0,80509,0 -10355,39121640,Breaker de circuito eléctrico,,0,80509,0 -10356,39121641,Fusible de gancho,,0,80509,0 -10357,39121642,Fusible de tornillo,,0,80509,0 -10358,39121643,Panel protector de sobrecorriente,,0,80509,0 -10359,39121644,Breaker de circuito de alta velocidad,,0,80509,0 -10360,39121645,Breaker de circuito de aceite,,0,80509,0 -10361,39121646,Breaker de circuito de gas,,0,80509,0 -10362,39121647,Descargador electrostático,,0,80509,0 -10363,39121648,Aditivo de tierra,,0,80509,0 -10364,39121700,Ferretería eléctrica y suministros,,0,80509,0 -10365,39121701,Soportes eléctricos,,0,80509,0 -10366,39121702,Clips para cables,,0,80509,0 -10367,39121703,Enlaces de cables,,0,80509,0 -10368,39121704,Placas de pared,,0,80509,0 -10369,39121705,Grapas para cables,,0,80509,0 -10370,39121706,Bujes de transformadores,,0,80509,0 -10371,39121707,Clavos de tabla de arneses,,0,80509,0 -10372,39121708,Riel din,,0,80509,0 -10373,39121709,Atadura de mango,,0,80509,0 -10374,39121710,Receptáculo multiplicador eléctrico,,0,80509,0 -10375,39121717,Montaje de fijador de cable,,0,80509,0 -10376,39121718,Kits de empalme de cables,,0,80509,0 -10377,39121719,Protectores,,0,80509,0 -10378,39121720,Asas de transformadores,,0,80509,0 -10379,39121721,Aislantes eléctricos,,0,80509,0 -10380,39121722,Dispositivo para sacar alambres y cables,,0,80509,0 -10381,39121723,Tubo encogible al calor,,0,80509,0 -10382,39121724,Artículos para montaje de conector,,0,80509,0 -10383,39121725,Mango de cable eléctrico,,0,80509,0 -10384,39121726,Atador de cables eléctricos,,0,80509,0 -10385,39121727,Varilla aislante eléctrica,,0,80509,0 -10386,39121728,Tubo de aislamiento,,0,80509,0 -10387,39121729,Juego de cables eléctricos,,0,80509,0 -10388,39121730,Roseta eléctrica,,0,80509,0 -10389,39121731,Equipo para atar cables,,0,80509,0 -10390,39121800,Instalaciones de Edificios Inteligentes - IBI,,0,80509,0 -10391,39121801,Unidades o dispositivos de control,,0,80509,0 -10392,39121802,Unidades o dispositivos inactivos,,0,80509,0 -10393,39121803,Sistema de control de construcción ambiental,,0,80509,0 -10394,39121900,Dispositivos y accesorios de seguridad eléctrica,,0,80509,0 -10395,39121901,Bloqueo de breaker de circuito,,0,80509,0 -10396,39121902,Cerramiento de bloqueo,,0,80509,0 -10397,39121903,Bloqueo de pasador y candado,,0,80509,0 -10398,39121904,Kit y estación de bloqueo,,0,80509,0 -10399,39121905,Dispositivo de bloqueo múltiple,,0,80509,0 -10400,39121906,Bloqueo de enchufe y cable,,0,80509,0 -10401,39121907,Bloqueo de receptáculo,,0,80509,0 -10402,39121908,Bloqueo de interruptor,,0,80509,0 -10403,39121909,Bloqueo de válvula,,0,80509,0 -10404,39121910,Marcador de voltaje,,0,80509,0 -10405,39122000,Unidades eléctricas de velocidades variables,,0,80509,0 -10406,39122001,Unidad ac invertida,Inversor accionamiento AC,0,80509,0 -10407,39122002,Unidad dc de control de motor,,0,80509,0 -10408,39122003,Unidad de servo control,,0,80509,0 -10409,39122100,Equipo de transmisión y distribución eléctrica,,0,80509,0 -10410,39122101,Potheads de cable para subestaciones,,0,80509,0 -10411,39122102,Cambiador de tomas,,0,80509,0 -10412,39122103,Poste de utilidad,,0,80509,0 -10413,39122104,Cable galvanizado de refuerzo,,0,80509,0 -10414,39122105,Espaciador eléctrico,,0,80509,0 -10415,39122106,Abrazadera para cruceta,,0,80509,0 -10416,39122107,Bloque de madera de cableado,,0,80509,0 -10417,39122108,Varilla de anclaje para cable galvanizado de refuerzo,,0,80509,0 -10418,39122109,Cruceta de acero tipo cuadrado,,0,80509,0 -10419,39122110,Arandela de conexión para línea eléctrica aérea,,0,80509,0 -10420,39122111,Dispositivo para dividir la línea eléctrica del carro,,0,80509,0 -10421,39122112,Ojo del juego de aislamiento,,0,80509,0 -10422,39122113,Cruceta del poste de utilidad,,0,80509,0 -10423,39122114,Toma de vía de bus eléctrico o toma de bus,,0,80509,0 -10424,39122115,Varilla de armadura eléctrica,,0,80509,0 -10425,39122116,Ajuste de vínculo de línea aérea,,0,80509,0 -10426,39122117,Tensión yugo de polo de suspensión,,0,80509,0 -10427,39122118,Conector de bloque de cable eléctrico,,0,80509,0 -10428,39122119,Torre de acero de transmisión eléctrica,,0,80509,0 -10429,39122120,Banda de polo de utilidad,,0,80509,0 -10430,39122200,Interruptores eléctricos y accesorios,,0,80509,0 -10431,39122201,Interruptor de código,,0,80509,0 -10432,39122202,Interruptor de cuchilla,Switch de cuchillas,0,80509,0 -10433,39122203,Interruptor sensible,,0,80509,0 -10434,39122204,Interruptor de vueltas,,0,80509,0 -10435,39122205,Interruptores de seguridad,Switch de seguridad,0,80509,0 -10436,39122206,Reductores o dimers,,0,80509,0 -10437,39122207,Interruptores de tambor,,0,80509,0 -10438,39122208,Interruptores de tiempos,,0,80509,0 -10439,39122209,Interruptores de resorte,,0,80509,0 -10440,39122210,Interruptores automáticos por caída de presión,,0,80509,0 -10441,39122211,Interruptores de volquete,,0,80509,0 -10442,39122212,Conmutadores de botón deslizante,,0,80509,0 -10443,39122213,Interruptores de límite,,0,80509,0 -10444,39122214,Interruptores de controlador,,0,80509,0 -10445,39122215,Interruptores variables,,0,80509,0 -10446,39122216,Interruptores de botón,,0,80509,0 -10447,39122217,Interruptores giratorios,,0,80509,0 -10448,39122218,Interruptor no fusible,Switch no fusible,0,80509,0 -10449,39122219,Interruptor nivel o flotador,,0,80509,0 -10450,39122220,Interruptor de radio frecuencia rf,,0,80509,0 -10451,39122221,Parte o accesorio de interruptor,Partes y accesorios de switch,0,80509,0 -10452,39122222,Interruptor de pie,,0,80509,0 -10453,39122223,Interruptor de flujo,,0,80509,0 -10454,39122224,Interruptor de cerradura,,0,80509,0 -10455,39122225,Interruptor de mercurio,,0,80509,0 -10456,39122226,Interruptor rocker,,0,80509,0 -10457,39122227,Interruptor o control joystick,,0,80509,0 -10458,39122228,Interruptor de vacío,,0,80509,0 -10459,39122229,Interruptor de temperatura,,0,80509,0 -10460,39122230,Interruptor de proximidad,,0,80509,0 -10461,39122231,Interruptor dispositivo de combinación,,0,80509,0 -10462,39122232,Interruptor de combinación,,0,80509,0 -10463,39122233,Interruptor de desconexión,,0,80509,0 -10464,39122234,Interruptor de bloqueo,,0,80509,0 -10465,39122235,Interruptor de sistema de cableado modular,,0,80509,0 -10466,39122236,Interruptor de sensor de ocupación o movimiento,,0,80509,0 -10467,39122237,Interruptor de montura de panel,,0,80509,0 -10468,39122238,Interruptor de fotocelda,,0,80509,0 -10469,39122239,Interruptor de halar,,0,80509,0 -10470,39122240,Interruptor de velocidad,,0,80509,0 -10471,39122241,Interruptor de lengüeta,,0,80509,0 -10472,39122242,Interruptor de suelo,,0,80509,0 -10473,39122243,Micro – interruptor,,0,80509,0 -10474,39122244,Interruptor de corte,,0,80509,0 -10475,39122245,Interruptor magnético,,0,80509,0 -10476,39122246,Interruptor de potenciómetro eléctrico,,0,80509,0 -10477,39122247,Interruptor de potenciómetro automotriz hvac,,0,80509,0 -10478,39122248,Interruptor de distribuidor de señal de potencia,,0,80509,0 -10479,39122249,Interruptor de control de timón,,0,80509,0 -10480,39122250,Interruptor selector de cambio de transmisión automática,,0,80509,0 -10481,39122251,Interruptor detent,,0,80509,0 -10482,39122300,Relés eléctricos y accesorios,Relevadores eléctricos y accesorios,0,80509,0 -10483,39122301,Relé buchholtz,Relevador buchholz,0,80509,0 -10484,39122302,Relé de distancia,Relevador de distancia,0,80509,0 -10485,39122303,Relé multicontacto,Relevador multicontacto,0,80509,0 -10486,39122304,Relé de lengüeta,Relevador de lengüeta,0,80509,0 -10487,39122305,Relé de medidor,Relevador de medidor,0,80509,0 -10488,39122306,Relé de tierra direccional,Relevador de tierra direccional,0,80509,0 -10489,39122307,Relé auxiliar,Relevador auxiliar,0,80509,0 -10490,39122308,Relé de índice diferencial,Relevador de índice diferencial,0,80509,0 -10491,39122309,Relé subterráneo,Relevador subterráneo,0,80509,0 -10492,39122310,Relé selector de tierra,Relevador selector de tierra,0,80509,0 -10493,39122311,Relé de presión,Relevador de presión,0,80509,0 -10494,39122312,Relé de polaridad,Relevador de polaridad,0,80509,0 -10495,39122313,Relé trip free,Relevador sin disparo,0,80509,0 -10496,39122314,Relé de bloqueo,Relevador de bloqueo,0,80509,0 -10497,39122315,Relé electromagnético,Relevador electromagnético,0,80509,0 -10498,39122316,Relé de re – cerrado,Relevador de reconexión,0,80509,0 -10499,39122317,Relé de aseguramiento,Relevador de aseguramiento,0,80509,0 -10500,39122318,Relé de frecuencia,Relevador de frecuencia,0,80509,0 -10501,39122319,Relé de corriente directa,Relevador de corriente directa,0,80509,0 -10502,39122320,Relé horizontal,Relevador horizontal,0,80509,0 -10503,39122321,Relé no polarizado,Relevador no polarizado,0,80509,0 -10504,39122322,Relé de voltaje de corriente directa,Relevador de voltaje de corriente directa,0,80509,0 -10505,39122323,Relé digital multi – función,Relevador digital multifunción,0,80509,0 -10506,39122324,Relés de potencia,Relevadores de potencia,0,80509,0 -10507,39122325,Relés universales,Relevadores universales,0,80509,0 -10508,39122326,Relés de clavija bipolar,Relevadores de clavija bipolar,0,80509,0 -10509,39122327,Relés de voltaje alterno,Relevadores de voltaje alterno,0,80509,0 -10510,39122328,Relés de mercurio,Relevadores de mercurio,0,80509,0 -10511,39122329,Relés de acción diferida,Relevadores de acción diferida,0,80509,0 -10512,39122330,Relés de sobrecarga,Relevadores de sobrecarga,0,80509,0 -10513,39122331,Relés de control,Relevadores de control,0,80509,0 -10514,39122332,Relés de interrupción de fase,Relevadores de interrupción de fase,0,80509,0 -10515,39122333,Relés de estado sólido,Relevadores de estado sólido,0,80509,0 -10516,39122334,Módulo de relés múltiples o de placa de relés,,0,80509,0 -10517,39122335,Base o toma de relé,,0,80509,0 -10518,39122336,Relé intermitente de luz direccional,,0,80509,0 -10519,39122337,Relé de tablero de circuito impreso,,0,80509,0 -10520,39131500,Marcadores de alambre y dispositivos para marcar alambre,,0,80509,0 -10521,39131501,Marcador de cable de pinza,,0,80509,0 -10522,39131502,Marcador de cable de encogimiento por calor,,0,80509,0 -10523,39131503,Marcador de cable de deslizar,,0,80509,0 -10524,39131504,Marcador de cable de identificación,,0,80509,0 -10525,39131505,Herramienta e impresora para rotular cable,,0,80509,0 -10526,39131506,Libro de marcador de cable,,0,80509,0 -10527,39131507,Tarjeta de arcador de cable,Marcador para cable y alambre,0,80509,0 -10528,39131508,Rollo y dispensador de arcador de cable,Rollo y dispensador de marcador de cable,0,80509,0 -10529,39131509,Marcador de cable de escribir,,0,80509,0 -10530,39131600,Dispositivos de protección de alambre,,0,80509,0 -10531,39131601,Tubo corrugado para cableado interno,,0,80509,0 -10532,39131602,Manga trenzada expandible,,0,80509,0 -10533,39131603,Perfil pasacable,Borde del ojal de caucho,0,80509,0 -10534,39131604,Empaque de espiral,,0,80509,0 -10535,39131605,Piso de cables track or guard,,0,80509,0 -10536,39131606,Placa de clavos para protección de cable,,0,80509,0 -10537,39131607,Cordón para amarrar cables,,0,80509,0 -10538,39131608,Sistema de sellamiento de ductos,,0,80509,0 -10539,39131700,"Conductos eléctricos, electroductos y cables aéreos",,0,80509,0 -10540,39131701,Electroducto,,0,80509,0 -10541,39131702,Dispositivos y accesorios de electroducto,,0,80509,0 -10542,39131703,Escalerilla de cables,,0,80509,0 -10543,39131704,Bandeja de cables,,0,80509,0 -10544,39131705,Dispositivos y accesorios de bandeja de cable,,0,80509,0 -10545,39131706,Conducto eléctrico,,0,80509,0 -10546,39131707,Acoples de conducto eléctrico,,0,80509,0 -10547,39131708,Cuerpo de ajuste de conducto eléctrico,,0,80509,0 -10548,39131709,Cableado eléctrico o conductos de cables,,0,80509,0 -10549,39131710,Cables aéreos,,0,80509,0 -10550,39131711,Dispositivos y accesorios de cableado eléctrico,,0,80509,0 -10551,39131712,Ducto de cableado,,0,80509,0 -10552,39131713,Dispositivos y accesorios de ducto de cableado,,0,80509,0 -10553,39131714,Canaletas para cables,,0,80509,0 -10554,39131715,Conducto subterráneo de manguera,,0,80509,0 -10555,39131716,Manipulador de cable,,0,80509,0 -10556,39131717,Codo de conducto eléctrico,,0,80509,0 -10557,39131718,Boquilla de conducto eléctrico,,0,80509,0 -10558,39131719,Tubería metálica eléctrica emt,,0,80509,0 -10559,39131720,Tubería metálica eléctrica emt roscada,,0,80509,0 -10560,40101500,Ventilación,,0,80509,0 -10561,40101501,Colectores de aire,,0,80509,0 -10562,40101502,Extractores de aire,,0,80509,0 -10563,40101503,Rejilla de ventilación,,0,80509,0 -10564,40101504,Compuertas de ventilación,,0,80509,0 -10565,40101505,Difusores de aire,,0,80509,0 -10566,40101506,Tubos de ventilación,,0,80509,0 -10567,40101507,Tubería de ventilación en espiral,,0,80509,0 -10568,40101508,Cortina de ventilación,,0,80509,0 -10569,40101509,Clavos para sistemas de ventilación,,0,80509,0 -10570,40101510,Tubería de ventilación,,0,80509,0 -10571,40101511,Colgador para tubos de ventilación,,0,80509,0 -10572,40101512,Dispositivos para tubos de ventilación,,0,80509,0 -10573,40101600,Circulación del aire y piezas y accesorios,,0,80509,0 -10574,40101601,Secadores,,0,80509,0 -10575,40101602,Circuladores de aire,,0,80509,0 -10576,40101603,Impulsores,,0,80509,0 -10577,40101604,Ventiladores,,0,80509,0 -10578,40101605,Protectores o sus accesorios para ventiladores,,0,80509,0 -10579,40101606,Dispositivos de seguridad para ventiladores,,0,80509,0 -10580,40101607,Válvula de control de volumen de aire,,0,80509,0 -10581,40101608,Unidad de filtrado para ventiladores,,0,80509,0 -10582,40101609,Ventiladores de techo,,0,80509,0 -10583,40101610,Kit de ventiladores de techo,,0,80509,0 -10584,40101700,Enfriamiento,,0,80509,0 -10585,40101701,Aires acondicionados,Aire acondicionado,0,80509,0 -10586,40101702,Intercambiadores de enfriado,,0,80509,0 -10587,40101703,Enfriadores de evaporación,,0,80509,0 -10588,40101704,Unidades de condensación,,0,80509,0 -10589,40101705,Ensamblajes de tubos capilares,,0,80509,0 -10590,40101706,Obturadores de aire acondicionado,,0,80509,0 -10591,40101707,Accesorios para torres de enfriamiento,,0,80509,0 -10592,40101708,Unidad de ventilador de bobina,,0,80509,0 -10593,40101709,Unidad de manejo de aire,,0,80509,0 -10594,40101710,Enfriador de reciprocidad,,0,80509,0 -10595,40101711,Enfriador de líquido centrífugo,,0,80509,0 -10596,40101712,Enfriador de tornillo,,0,80509,0 -10597,40101713,Enfriador de absorción,,0,80509,0 -10598,40101714,Enfriador calentador de absorción,,0,80509,0 -10599,40101715,Cámara de temperatura y humedad constantes,,0,80509,0 -10600,40101716,Torre enfriadora,,0,80509,0 -10601,40101717,Ahorrador de energía para aire acondicionado,,0,80509,0 -10602,40101718,Bobina enfriadora,,0,80509,0 -10603,40101719,Receptor de líquido refrigerante,,0,80509,0 -10604,40101720,Unidad de enfriamiento,,0,80509,0 -10605,40101721,Condensador de agua enfriada,,0,80509,0 -10606,40101722,Condensador de vapor,,0,80509,0 -10607,40101800,Equipo de calefacción y piezas y accesorios,,0,80509,0 -10608,40101801,Radiadores,,0,80509,0 -10609,40101802,Intercambiadores de calor,,0,80509,0 -10610,40101805,Calefacción,,0,80509,0 -10611,40101806,Bombas de calor,,0,80509,0 -10612,40101807,Unidades de calefacción solar,,0,80509,0 -10613,40101808,Estufas de calefacción,,0,80509,0 -10614,40101809,Calentadores de circulación,,0,80509,0 -10615,40101810,Calentadores de conductos de bobina,,0,80509,0 -10616,40101811,Calentadores de convección,,0,80509,0 -10617,40101812,Intercambiadores divididos,,0,80509,0 -10618,40101813,Intercambiadores de doble división,,0,80509,0 -10619,40101814,Calentadores de aletas tubulares,,0,80509,0 -10620,40101815,Calentadores de inmersión,,0,80509,0 -10621,40101816,Intercambiadores de teteras,,0,80509,0 -10622,40101817,Intercambiadores de una vía,,0,80509,0 -10623,40101818,Calentadores de procesamiento de aire,,0,80509,0 -10624,40101819,Calentadores de espacio,,0,80509,0 -10625,40101820,Intercambiadores divididos,,0,80509,0 -10626,40101821,Calentadores de banda,,0,80509,0 -10627,40101822,Calentadores tubulares,,0,80509,0 -10628,40101823,Intercambiadores de dos vías,,0,80509,0 -10629,40101824,Calentador de cuarzo,,0,80509,0 -10630,40101825,Calentadores de agua para uso doméstico,,0,80509,0 -10631,40101826,Calentadores de agua comerciales,,0,80509,0 -10632,40101827,Calentadores de fibra cerámica,,0,80509,0 -10633,40101828,Calentadores de cartucho,,0,80509,0 -10634,40101829,Calentadores de tiro,,0,80509,0 -10635,40101830,Elementos de calefacción,,0,80509,0 -10636,40101831,Calentadores de inducción,,0,80509,0 -10637,40101832,Puertas para equipos de calefacción,,0,80509,0 -10638,40101833,Encendedor para calderas o calentadores,,0,80509,0 -10639,40101834,Quemadores (fogones),,0,80509,0 -10640,40101835,Plato intercambiador de calor,,0,80509,0 -10641,40101836,Intercambiador de calor de concha y de tubo,,0,80509,0 -10642,40101837,Chimenea,,0,80509,0 -10643,40101838,Economizador,,0,80509,0 -10644,40101839,Placas difusoras,,0,80509,0 -10645,40101840,Calentador de película,,0,80509,0 -10646,40101841,Pre calentador de aire,,0,80509,0 -10647,40101842,Dispositivo para derretir hielo,,0,80509,0 -10648,40101843,Horno eléctrico,,0,80509,0 -10649,40101844,Horno de alta frecuencia de inducción al vacío,,0,80509,0 -10650,40101845,Horno de reverberación,,0,80509,0 -10651,40101846,Horno de baño de sal,Horno de tratamiento térmico,0,80509,0 -10652,40101847,Horno eléctrico de carbonización y nitrificación,,0,80509,0 -10653,40101848,Sistema de calefacción de aire forzado,,0,80509,0 -10654,40101849,Cabezal de distribución de agua caliente,,0,80509,0 -10655,40101850,Horno crematorio,,0,80509,0 -10656,40101900,Control de humedad,,0,80509,0 -10657,40101901,Vaporizadores,,0,80509,0 -10658,40101902,Deshumidificadores,,0,80509,0 -10659,40101903,Humidificadores,,0,80509,0 -10660,40102000,Calderas,,0,80509,0 -10661,40102001,Quemadores de tubo radiante,,0,80509,0 -10662,40102002,Quemadores de tubo de agua,,0,80509,0 -10663,40102003,Quemadores eléctricos,,0,80509,0 -10664,40102004,Quemadores operados con gas natural,,0,80509,0 -10665,40102005,Quemadores operados con gas propano,,0,80509,0 -10666,40102006,Quemadores operados con calor sobrante,,0,80509,0 -10667,40102007,Mini quemadores de aceite,,0,80509,0 -10668,40102100,Chimeneas y accesorios,,0,80509,0 -10669,40102101,Chimeneas operadas con leña,,0,80509,0 -10670,40102102,Chimenea a gas de ventilador b,,0,80509,0 -10671,40102103,Chimenea a gas de ventilador directo,,0,80509,0 -10672,40102104,Chimenea a gas de ventilador libre,,0,80509,0 -10673,40102105,Chimenea eléctrica,,0,80509,0 -10674,40102106,Fachadas de chimeneas,,0,80509,0 -10675,40102107,Repisas para chimeneas,,0,80509,0 -10676,40141600,Válvulas,,0,80509,0 -10677,40141602,Válvulas de aguja,,0,80509,0 -10678,40141603,Válvulas neumáticas,,0,80509,0 -10679,40141604,Válvulas de seguridad,,0,80509,0 -10680,40141605,Válvulas solenoides,,0,80509,0 -10681,40141606,Válvulas de relevo,,0,80509,0 -10682,40141607,Válvulas de bola,,0,80509,0 -10683,40141608,Válvulas hidráulicas,,0,80509,0 -10684,40141609,Válvulas de control,,0,80509,0 -10685,40141610,Válvulas de flotación,,0,80509,0 -10686,40141611,Válvulas de globo,,0,80509,0 -10687,40141612,Válvulas de expansión,,0,80509,0 -10688,40141613,Válvulas de compuerta,,0,80509,0 -10689,40141615,Válvulas de lengüeta,,0,80509,0 -10690,40141616,Partes o accesorios para válvulas,,0,80509,0 -10691,40141617,Válvulas esféricas de ángulo,,0,80509,0 -10692,40141618,Válvulas de seguridad de bola,,0,80509,0 -10693,40141619,Válvulas de mariposa con diseño de casquillo,,0,80509,0 -10694,40141620,Válvulas de mariposa con diseño de disco,,0,80509,0 -10695,40141621,Válvulas de diafragma,,0,80509,0 -10696,40141622,Válvulas de seguridad en línea,,0,80509,0 -10697,40141623,Válvulas tipo compuerta,,0,80509,0 -10698,40141624,Válvulas con pistones lubricados,,0,80509,0 -10699,40141625,Válvulas purgadoras de sedimentos (barro o lodo),,0,80509,0 -10700,40141626,Válvulas con pistón no lubricado,,0,80509,0 -10701,40141627,Válvulas de orificio,,0,80509,0 -10702,40141628,Válvulas piloto,,0,80509,0 -10703,40141629,Válvulas de pinzamiento,,0,80509,0 -10704,40141630,Válvulas de seguridad de pistón,,0,80509,0 -10705,40141631,Válvulas de bombeo,,0,80509,0 -10706,40141632,Válvulas de monitoreo,,0,80509,0 -10707,40141633,Válvulas deslizantes,,0,80509,0 -10708,40141634,Válvulas de control de rotación,,0,80509,0 -10709,40141635,Válvulas de turbina,,0,80509,0 -10710,40141636,Kits de válvulas,,0,80509,0 -10711,40141637,Válvulas de control de disco,,0,80509,0 -10712,40141638,Válvulas alternadoras,,0,80509,0 -10713,40141639,Válvulas de llenado del inodoro,,0,80509,0 -10714,40141640,Válvulas de inodoro,,0,80509,0 -10715,40141641,Válvulas solenoides,,0,80509,0 -10716,40141642,Válvulas de asiento (o auxiliares),,0,80509,0 -10717,40141643,Válvulas de contención,,0,80509,0 -10718,40141644,Válvulas de esclusa rotativas,,0,80509,0 -10719,40141645,Vástagos de válvulas,,0,80509,0 -10720,40141646,Soportes (asientos) de válvulas,,0,80509,0 -10721,40141647,Válvulas de segmento,,0,80509,0 -10722,40141648,Válvulas magnéticas,,0,80509,0 -10723,40141649,Ensamblaje de bobina de válvula de control direccional,,0,80509,0 -10724,40141650,Válvula de gas comprimido,,0,80509,0 -10725,40141651,Válvula de aire,,0,80509,0 -10726,40141652,Válvula reductora de presión,,0,80509,0 -10727,40141653,Válvula de compuerta,,0,80509,0 -10728,40141654,Válvula de regulación,,0,80509,0 -10729,40141655,Válvula de vacío,,0,80509,0 -10730,40141656,Válvula de brida tipo mariposa,,0,80509,0 -10731,40141657,Válvula de control de rociadores,,0,80509,0 -10732,40141658,Válvula de bloqueo y sangrado,,0,80509,0 -10733,40141659,Válvula horizontal de elevación,,0,80509,0 -10734,40141700,Material de ferretería y accesorios,,0,80509,0 -10735,40141719,Adaptadores para plomería,,0,80509,0 -10736,40141720,Conectores para plomería,,0,80509,0 -10737,40141725,Ganchos (soportes) para plomería,,0,80509,0 -10738,40141726,Hidrantes,,0,80509,0 -10739,40141727,Plomería de ventilación,,0,80509,0 -10740,40141731,Boquillas,,0,80509,0 -10741,40141732,Centradores (arañas) para plomería,,0,80509,0 -10742,40141734,Conectores para mangueras,Adaptadores de manguera,0,80509,0 -10743,40141735,Embudos,,0,80509,0 -10744,40141736,Graseras (de lubricación),,0,80509,0 -10745,40141737,Diafragmas,,0,80509,0 -10746,40141738,Tapones de drenaje (de aceite),,0,80509,0 -10747,40141739,Tapas de desagüe,,0,80509,0 -10748,40141740,Llave de la gasolina,,0,80509,0 -10749,40141741,Orificios de ajuste,,0,80509,0 -10750,40141742,Atomizadores,,0,80509,0 -10751,40141743,Puntas o capas de boquillas,,0,80509,0 -10752,40141744,Cuencos de drenaje,,0,80509,0 -10753,40141745,Fusibles de alta temperatura,,0,80509,0 -10754,40141746,Mirillas (indicadores de nivel),,0,80509,0 -10755,40141747,Trampa de grasas,,0,80509,0 -10756,40141749,Interruptor de vacío,,0,80509,0 -10757,40141750,Brazo (de tubería),,0,80509,0 -10758,40141751,Codos (de tubería),,0,80509,0 -10759,40141755,Resorte para evitar movimiento de tubería,,0,80509,0 -10760,40141756,Caja de accesorios para conectar tubos,,0,80509,0 -10761,40141757,Escudetes,Ojo de cerradura,0,80509,0 -10762,40141758,Canal de drenaje,,0,80509,0 -10763,40141759,Desagües de piso,Drenaje de piso,0,80509,0 -10764,40141760,Hidrante anti congelamiento,,0,80509,0 -10765,40141761,Múltiple (colector) de tubería,,0,80509,0 -10766,40141762,Dispositivo protector del medidor de agua,,0,80509,0 -10767,40141763,Supresor de golpes de ariete,,0,80509,0 -10768,40141764,Ensamblaje de mangueras,,0,80509,0 -10769,40141765,Separador de drenaje,,0,80509,0 -10770,40141766,Regulador de cierre con asiento para trabajos de agua,,0,80509,0 -10771,40141767,Zapatas para tubería,,0,80509,0 -10772,40141768,Adaptador desmontable,,0,80509,0 -10773,40141769,Caja o cámara de válvulas,,0,80509,0 -10774,40141770,Máquina de fusión de tubos,,0,80509,0 -10775,40141900,Conductos,,0,80509,0 -10776,40141901,Conductos flexibles,,0,80509,0 -10777,40141902,Conductos rígidos,,0,80509,0 -10778,40141903,Conductos o red de conductos de manganeso,,0,80509,0 -10779,40141904,Conductos o red de conductos de aleación ferrosa,,0,80509,0 -10780,40141905,Conductos o red de conductos de titanio,,0,80509,0 -10781,40141906,Conductos o red de conductos de latón,,0,80509,0 -10782,40141907,Conductos o red de conductos de latón,,0,80509,0 -10783,40141908,Conductos o red de conductos de plomo,,0,80509,0 -10784,40141909,Conductos o red de conductos de bronce,,0,80509,0 -10785,40141910,Conductos o red de conductos de zinc,,0,80509,0 -10786,40141911,Conductos o red de conductos de acero,,0,80509,0 -10787,40141912,Conductos o red de conductos de hierro,,0,80509,0 -10788,40141913,Conductos o red de conductos de cemento,,0,80509,0 -10789,40141914,Conductos o red de conductos de plástico,,0,80509,0 -10790,40141915,Conductos o red de conductos de caucho,,0,80509,0 -10791,40141916,Conductos o red de conductos de vidrio,,0,80509,0 -10792,40141917,Conductos o red de conductos de piedra,,0,80509,0 -10793,40141918,Conductos o red de conductos de aleación no ferrosa,,0,80509,0 -10794,40141919,Conductos o red de conductos de aluminio,,0,80509,0 -10795,40141920,Conductos o red de conductos de acero inoxidable,,0,80509,0 -10796,40141921,Conductos o red de conductos de metales preciosos,,0,80509,0 -10797,40141922,Conductos o red de conductos de cobre,,0,80509,0 -10798,40141923,Conductos o red de conductos de látex,,0,80509,0 -10799,40141924,Conductos o red de conductos de nylon,,0,80509,0 -10800,40142000,Mangueras,,0,80509,0 -10801,40142001,Mangueras de ácido,,0,80509,0 -10802,40142002,Mangueras de aire,,0,80509,0 -10803,40142003,Mangueras de perforación,,0,80509,0 -10804,40142004,Mangueras marítimas,,0,80509,0 -10805,40142005,Mangueras para manipular material,,0,80509,0 -10806,40142006,Mangueras de aceite,,0,80509,0 -10807,40142007,Mangueras especiales,,0,80509,0 -10808,40142008,Mangueras de agua,,0,80509,0 -10809,40142009,"Mangueras multipropósito de aire, agua y gas",,0,80509,0 -10810,40142010,Mangueras recubiertas de fluoropolímero,,0,80509,0 -10811,40142011,Manguera química,,0,80509,0 -10812,40142012,Manguera para alimentos y bebidas,,0,80509,0 -10813,40142013,Mangueras para minería,,0,80509,0 -10814,40142014,Mangueras de petróleo,,0,80509,0 -10815,40142015,Mangueras para limpieza al vapor,,0,80509,0 -10816,40142016,Mangueras al vacío,,0,80509,0 -10817,40142017,Mangueras de soldadura,,0,80509,0 -10818,40142018,Mangueras para rociar,,0,80509,0 -10819,40142019,Mangueras de conductos,,0,80509,0 -10820,40142020,Mangueras hidráulicas,,0,80509,0 -10821,40142021,Mangueras de polvo de piedra,,0,80509,0 -10822,40142022,Rollos de mangueras,,0,80509,0 -10823,40142200,Reguladores de gas y fluido,,0,80509,0 -10824,40142201,Reguladores de gas,,0,80509,0 -10825,40142202,Reguladores de fluido,,0,80509,0 -10826,40142203,Kits de reparación de reguladores de fluido,,0,80509,0 -10827,40142204,Regulador de oxígeno para soldadura,,0,80509,0 -10828,40142205,Regulador de acetileno para soldadura,,0,80509,0 -10829,40142206,Compuerta de regulación de flujo,,0,80509,0 -10830,40142207,Regulador del nivel del agua,,0,80509,0 -10831,40142208,Mezclador de gas,,0,80509,0 -10832,40142500,Interceptores y coladores,,0,80509,0 -10833,40142501,Filtros (coladores) de líquido,,0,80509,0 -10834,40142502,Trampas de líquido,,0,80509,0 -10835,40142503,Trampas de vapor,,0,80509,0 -10836,40142504,Filtros (coladores) de vapor,,0,80509,0 -10837,40142505,Filtros (coladores) en y,,0,80509,0 -10838,40142506,Filtros (coladores) en t,,0,80509,0 -10839,40142507,Filtros (coladores) cónicos,,0,80509,0 -10840,40142508,Filtros (coladores) de canasta,,0,80509,0 -10841,40142509,Trampa de aceite,,0,80509,0 -10842,40142510,Trampa de vapor termostática,,0,80509,0 -10843,40142511,Trampa de vapor termodinámica,,0,80509,0 -10844,40142512,Respiradero,,0,80509,0 -10845,40142513,Trampa p,,0,80509,0 -10846,40142514,Trampa j,,0,80509,0 -10847,40142515,Trampa s,,0,80509,0 -10848,40151500,Bombas,,0,80509,0 -10849,40151501,Bombas de aire,,0,80509,0 -10850,40151502,Bombas de vacío,,0,80509,0 -10851,40151503,Bombas centrífugas,,0,80509,0 -10852,40151504,Bombas de circulación,,0,80509,0 -10853,40151505,Bombas dosificadoras,,0,80509,0 -10854,40151506,Bombas de mano,,0,80509,0 -10855,40151507,Bombas de irrigación,,0,80509,0 -10856,40151508,Bombas de barro,,0,80509,0 -10857,40151509,Bombas recíprocas,,0,80509,0 -10858,40151510,Bombas de agua,,0,80509,0 -10859,40151511,Bombas para pozos,,0,80509,0 -10860,40151512,Bombas para sumideros,,0,80509,0 -10861,40151513,Bombas sumergibles,,0,80509,0 -10862,40151514,Bombas de vapor,,0,80509,0 -10863,40151515,Bombas solenoides,,0,80509,0 -10864,40151516,Bombas de corte,,0,80509,0 -10865,40151517,Bombas de alcantarillado,,0,80509,0 -10866,40151518,Bombas no selladas,,0,80509,0 -10867,40151519,Bombas sanitarias,,0,80509,0 -10868,40151520,Bombas de muestreo,,0,80509,0 -10869,40151521,Bombas giratorias,,0,80509,0 -10870,40151522,Bombas de osmosis inversa,,0,80509,0 -10871,40151523,Bombas de desplazamiento positivo,,0,80509,0 -10872,40151524,Bombas de aceite,,0,80509,0 -10873,40151525,Bombas de lodo,,0,80509,0 -10874,40151526,Bombas de turbina,,0,80509,0 -10875,40151527,Bombas de émbolo,,0,80509,0 -10876,40151528,Bombas oscilantes,,0,80509,0 -10877,40151529,Bombas de tambor,,0,80509,0 -10878,40151530,Bombas de dragado,,0,80509,0 -10879,40151531,Bombas para remover agua,,0,80509,0 -10880,40151532,Bombas de combustible,,0,80509,0 -10881,40151533,Bombas hidráulicas,,0,80509,0 -10882,40151534,Bombas criogénicas,,0,80509,0 -10883,40151546,Bombas de partición axial,,0,80509,0 -10884,40151547,Bombas de pozo profundo,,0,80509,0 -10885,40151548,Bombas de diafragma,,0,80509,0 -10886,40151549,Bombas de doble diafragma,,0,80509,0 -10887,40151550,Bombas dúplex,,0,80509,0 -10888,40151551,Bombas de engranaje,,0,80509,0 -10889,40151552,Bombas de metraje o inyección o proporcionales,,0,80509,0 -10890,40151553,Bombas de cavidad progresiva,,0,80509,0 -10891,40151554,Bombas de pistón,,0,80509,0 -10892,40151555,Bombas de resalte rotatorias,,0,80509,0 -10893,40151556,Bombas de leva rotatorias,,0,80509,0 -10894,40151557,Bombas de pistón rotatorias,,0,80509,0 -10895,40151558,Bombas de tuerca,,0,80509,0 -10896,40151559,Bombas simplex,,0,80509,0 -10897,40151560,Bombas de paleta deslizante,,0,80509,0 -10898,40151561,Bombas triplex,,0,80509,0 -10899,40151562,Bombas de tornillo,,0,80509,0 -10900,40151563,Sets de bombas contra incendio,,0,80509,0 -10901,40151564,Bombas químicas,,0,80509,0 -10902,40151565,Bombas de macerar,,0,80509,0 -10903,40151566,Bomba elevadora de presión,,0,80509,0 -10904,40151567,Bomba de alta temperatura,,0,80509,0 -10905,40151568,Bomba de pulpa,,0,80509,0 -10906,40151569,Bomba monoflex,,0,80509,0 -10907,40151570,Bomba de flujo mixto,,0,80509,0 -10908,40151571,Bomba longitudinal,,0,80509,0 -10909,40151572,Bomba de elevación de aire,,0,80509,0 -10910,40151573,Bomba de magneto,,0,80509,0 -10911,40151574,Bomba eyectora,,0,80509,0 -10912,40151575,Bomba enfriadora sin cepillo,,0,80509,0 -10913,40151576,Bomba eléctrica,,0,80509,0 -10914,40151577,Bomba de lechada,,0,80509,0 -10915,40151578,Bomba de aire para buceo,,0,80509,0 -10916,40151579,Bomba de gravedad,,0,80509,0 -10917,40151580,Bomba lubricante,,0,80509,0 -10918,40151600,Compresores,,0,80509,0 -10919,40151601,Compresores de aire,,0,80509,0 -10920,40151602,Compresores de flujo axiales,,0,80509,0 -10921,40151603,Compresores de diafragma,,0,80509,0 -10922,40151604,Compresores de gas,,0,80509,0 -10923,40151605,Compresores de motor,,0,80509,0 -10924,40151606,Compresores recíprocos,,0,80509,0 -10925,40151607,Compresores refrigerantes,,0,80509,0 -10926,40151608,Compresores rotativos,,0,80509,0 -10927,40151609,Compresores de tuerca,,0,80509,0 -10928,40151611,Compresores de barril,,0,80509,0 -10929,40151612,Compresores centrífugos,,0,80509,0 -10930,40151613,Compresores de combinación,,0,80509,0 -10931,40151614,Compresores semi radiales,,0,80509,0 -10932,40151615,Turbo compresores,,0,80509,0 -10933,40151616,Kits de compresores,,0,80509,0 -10934,40151700,Piezas y accesorios de bomba,,0,80509,0 -10935,40151701,Carcasas para bombas,,0,80509,0 -10936,40151712,Empaques para bombas,,0,80509,0 -10937,40151713,Revestimientos para bombas,,0,80509,0 -10938,40151714,Barriles para bombas,,0,80509,0 -10939,40151715,Poleas para bombas,,0,80509,0 -10940,40151716,Cabezas para bombas,,0,80509,0 -10941,40151717,Discos para bombas,,0,80509,0 -10942,40151718,Partes de repuesto para bombas de lodo,,0,80509,0 -10943,40151719,Partes de repuesto para bombas de alcantarillado,,0,80509,0 -10944,40151720,Partes de repuesto para bombas sumergibles,,0,80509,0 -10945,40151721,Partes de repuesto para bombas de agua,,0,80509,0 -10946,40151722,Partes de repuesto para bombas de pozo,,0,80509,0 -10947,40151723,Partes de repuesto para bombas de sumidero,,0,80509,0 -10948,40151724,Partes de repuesto para bombas dosificadoras,,0,80509,0 -10949,40151725,Partes de repuesto para bombas centrífugas,,0,80509,0 -10950,40151726,Partes de repuesto para bombas de circulación,,0,80509,0 -10951,40151727,Partes de repuesto para bombas rotatorias,,0,80509,0 -10952,40151728,Kits de reparación de bombas,,0,80509,0 -10953,40151729,Estator de bombas,,0,80509,0 -10954,40151730,Impulsores de bombas,,0,80509,0 -10955,40151731,Rotores de bombas,,0,80509,0 -10956,40151732,Ejes de bombas,,0,80509,0 -10957,40151733,Placas de asiento de bombas,,0,80509,0 -10958,40151734,Placas de base de bombas,,0,80509,0 -10959,40151735,Ensamblaje de columnas de bombas,,0,80509,0 -10960,40151736,Tubos de columna de bombas,,0,80509,0 -10961,40151737,Ensamblaje de caja de bombas,,0,80509,0 -10962,40151738,Campana de succión de bombas,,0,80509,0 -10963,40151739,Partes para bombas recíprocas,,0,80509,0 -10964,40151800,Partes o accesorios de compresores,,0,80509,0 -10965,40151801,Partes para compresores rotatorios,,0,80509,0 -10966,40151802,Partes para compresores de aire,,0,80509,0 -10967,40151803,Partes para compresores recíprocos,,0,80509,0 -10968,40151804,Partes para compresores centrífugos,,0,80509,0 -10969,40161500,Filtros,,0,80509,0 -10970,40161501,Filtros al vacío,,0,80509,0 -10971,40161502,Filtros de agua,,0,80509,0 -10972,40161503,Recolectores de polvo,,0,80509,0 -10973,40161504,Filtros de aceite,,0,80509,0 -10974,40161505,Filtros de aire,,0,80509,0 -10975,40161506,Maquinaria de filtrado,,0,80509,0 -10976,40161507,Membranas de filtrado,,0,80509,0 -10977,40161508,Filtros de bolsa,,0,80509,0 -10978,40161509,Filtros de absorción,,0,80509,0 -10979,40161511,Filtros coalescentes,,0,80509,0 -10980,40161512,Filtros electrónicos,,0,80509,0 -10981,40161513,Filtros de combustible,,0,80509,0 -10982,40161514,Filtros para tuberías de gas,,0,80509,0 -10983,40161515,Filtros hidráulicos,,0,80509,0 -10984,40161516,Filtros en línea,,0,80509,0 -10985,40161517,Filtros de luz,,0,80509,0 -10986,40161518,Filtros de microfibra,,0,80509,0 -10987,40161519,Filtros de panel,,0,80509,0 -10988,40161520,Filtros de aletas radiales,,0,80509,0 -10989,40161521,Bases para filtros,,0,80509,0 -10990,40161522,Aletas para filtros,,0,80509,0 -10991,40161524,Filtros de pintura,,0,80509,0 -10992,40161525,Contenedores para filtros,,0,80509,0 -10993,40161526,Retenedores o accesorios para filtros,,0,80509,0 -10994,40161527,Kits de reparación de filtros,,0,80509,0 -10995,40161528,Filtros de arena,,0,80509,0 -10996,40161529,Filtros de tubo,,0,80509,0 -10997,40161530,Filtros de cabina de aire,,0,80509,0 -10998,40161531,Ensamblaje de filtros,,0,80509,0 -10999,40161532,Malla para filtros,,0,80509,0 -11000,40161533,Filtros para desechos sólidos,,0,80509,0 -11001,40161600,Purificación,,0,80509,0 -11002,40161601,Neutralizador (depurador) de aire,,0,80509,0 -11003,40161602,Limpiadores de aire,,0,80509,0 -11004,40161603,Máquinas limpiadoras de tubos y tuberías,,0,80509,0 -11005,40161604,Regenerador de aceite,,0,80509,0 -11006,40161605,Torre desodorizante,,0,80509,0 -11007,40161606,Generador de ozono para limpiar el aire,,0,80509,0 -11008,40161607,Sistema para desulfurar el gas de combustión,,0,80509,0 -11009,40161608,Esterilizador de aire,,0,80509,0 -11010,40161700,Separadores,,0,80509,0 -11011,40161701,Centrífugas,,0,80509,0 -11012,40161702,Lavador húmedo,,0,80509,0 -11013,40161703,Eliminadores de niebla,,0,80509,0 -11014,40161704,Hidrociclones,,0,80509,0 -11015,40161705,Tamiz separador de partículas líquidas,,0,80509,0 -11016,40161800,Medios de filtrado,,0,80509,0 -11017,40161801,Medios de textiles metálicos,,0,80509,0 -11018,40161802,Fieltros prensados,,0,80509,0 -11019,40161803,Papeles filtrantes,,0,80509,0 -11020,40161804,Ayudas filtrantes,Ayudas de filtro,0,80509,0 -11021,40161805,Paño filtrante,,0,80509,0 -11022,40161806,Malla filtrante,,0,80509,0 -11023,40161807,Elemento (dispositivo poroso) filtrante,,0,80509,0 -11024,40161808,Medio biológico filtrante,,0,80509,0 -11025,40161809,Cartucho filtrante,,0,80509,0 -11026,40171500,Tubos y tuberías comerciales,,0,80509,0 -11027,40171501,Tubo de acero al carbono soldado para uso comercial,,0,80509,0 -11028,40171502,Tubo de acero al carbono sin uniones para uso comercial,,0,80509,0 -11029,40171503,Tubo de hierro dúctil para uso comercial,,0,80509,0 -11030,40171504,Tubo de aleación de alto níquel para uso comercial,,0,80509,0 -11031,40171505,Tubo de acero de alto rendimiento para uso comercial,,0,80509,0 -11032,40171506,Tubo de aleación ferrosa para uso comercial,,0,80509,0 -11033,40171507,Tubo de aluminio para uso comercial,,0,80509,0 -11034,40171508,Tubo de latón para uso comercial,,0,80509,0 -11035,40171509,Tubo de bronce para uso comercial,,0,80509,0 -11036,40171510,Tubo de concreto para uso comercial,,0,80509,0 -11037,40171511,Tubo de cobre para uso comercial,,0,80509,0 -11038,40171512,Tubo de hierro forjado para uso comercial,,0,80509,0 -11039,40171513,Concentrador de tubo de hierro forjado ensamblado para uso comercial,,0,80509,0 -11040,40171514,Tubo de plomo para uso comercial,,0,80509,0 -11041,40171515,Tubo de magnesio para uso comercial,,0,80509,0 -11042,40171516,Tubo no ferroso para uso comercial,,0,80509,0 -11043,40171517,Tubo pvc para uso comercial,,0,80509,0 -11044,40171518,Tubo cpvc para uso comercial,,0,80509,0 -11045,40171519,Tubo abs para uso comercial,,0,80509,0 -11046,40171520,Tubo hdpe para uso comercial,,0,80509,0 -11047,40171521,Tubo de acero inoxidable soldado para uso comercial,,0,80509,0 -11048,40171522,Tubo de acero inoxidable sin uniones para uso comercial,,0,80509,0 -11049,40171523,Tubo de latón para uso comercial,,0,80509,0 -11050,40171524,Tubo de titanio para uso comercial,,0,80509,0 -11051,40171525,Tubo de zinc para uso comercial,,0,80509,0 -11052,40171526,Tubo de vidrio para uso comercial,,0,80509,0 -11053,40171527,Tubo galvanizado corrugado soldado para uso comercial,,0,80509,0 -11054,40171600,Tubos y tuberías industriales,,0,80509,0 -11055,40171601,Tubo de acero al carbono soldado para uso industrial,,0,80509,0 -11056,40171602,Tubo de acero al carbono sin uniones para uso industrial,,0,80509,0 -11057,40171603,Tubo de hierro dúctil para uso industrial,,0,80509,0 -11058,40171604,Tubo de aleación de alto níquel para uso industrial,,0,80509,0 -11059,40171605,Tubo de acero de alto rendimiento para uso industrial,,0,80509,0 -11060,40171606,Tubo de aleación ferrosa para uso industrial,,0,80509,0 -11061,40171607,Tubo de aluminio para uso industrial,,0,80509,0 -11062,40171608,Tubo de latón para uso industrial,,0,80509,0 -11063,40171609,Tubo de bronce para uso industrial,,0,80509,0 -11064,40171610,Tubo de concreto para uso industrial,,0,80509,0 -11065,40171611,Tubo de cobre para uso industrial,,0,80509,0 -11066,40171612,Tubo de hierro forjado para uso industrial,,0,80509,0 -11067,40171613,Concentrador de tubo de hierro forjado ensamblado para uso industrial,,0,80509,0 -11068,40171614,Tubo de plomo para uso industrial,,0,80509,0 -11069,40171615,Tubo de magnesio para uso industrial,,0,80509,0 -11070,40171616,Tubo no ferroso para uso industrial,,0,80509,0 -11071,40171617,Tubo pvc para uso industrial,,0,80509,0 -11072,40171618,Tubo cpvc para uso industrial,,0,80509,0 -11073,40171619,Tubo abs para uso industrial,,0,80509,0 -11074,40171620,Tubo hdpe para uso industrial,,0,80509,0 -11075,40171621,Tubo de acero inoxidable soldado para uso industrial,,0,80509,0 -11076,40171622,Tubo de acero inoxidable sin uniones para uso industrial,,0,80509,0 -11077,40171623,Tubo de latón para uso industrial,,0,80509,0 -11078,40171624,Tubo de titanio para uso industrial,,0,80509,0 -11079,40171625,Tubo de zinc para uso industrial,,0,80509,0 -11080,40171626,Tubo de vidrio para uso industrial,,0,80509,0 -11081,40171700,Adaptadores de tubos,,0,80509,0 -11082,40171701,Adaptador de tubo de latón,,0,80509,0 -11083,40171702,Adaptador de tubo de acero al carbono,,0,80509,0 -11084,40171703,Adaptador de tubo de hierro forjado,,0,80509,0 -11085,40171704,Adaptador de tubo de hierro dúctil,,0,80509,0 -11086,40171705,Adaptador de tubo de acero forjado,,0,80509,0 -11087,40171706,Adaptador de tubo de hierro maleable,,0,80509,0 -11088,40171707,Adaptador de tubo de acero inoxidable,,0,80509,0 -11089,40171708,Adaptador de tubo plástico pvc,,0,80509,0 -11090,40171709,Adaptador de tubo plástico cpvc,,0,80509,0 -11091,40171710,Adaptador de tubo plástico abs,,0,80509,0 -11092,40171711,Adaptador de tubo plástico hdpe,,0,80509,0 -11093,40171900,Bridas de respaldo para tubos,,0,80509,0 -11094,40171901,Brida de respaldo para tubos de acero al carbono,,0,80509,0 -11095,40171902,Brida de respaldo para tubos de acero forjado,,0,80509,0 -11096,40171903,Brida de respaldo para tubos de acero inoxidable,,0,80509,0 -11097,40171904,Brida de respaldo para tubos de hierro dúctil,,0,80509,0 -11098,40171905,Brida de respaldo para tubos de cobre,,0,80509,0 -11099,40172000,Deflectores para tubos,,0,80509,0 -11100,40172001,Deflectores para tubos de latón,Coples para tubos de latón,0,80509,0 -11101,40172002,Deflectores para tubos de hierro dúctil,Coples para tubos de hierro dúctil,0,80509,0 -11102,40172003,Deflectores para tubos de acero forjado,Coples para tubos de acero forjado,0,80509,0 -11103,40172004,Deflectores para tubos de acero inoxidable,Coples para tubos de acero inoxidable,0,80509,0 -11104,40172005,Deflectores para tubos de plástico pvc,Coples para tubos de plástico PVC,0,80509,0 -11105,40172006,Deflectores para tubos de plástico cpvc,Coples para tubos de plástico cpvc,0,80509,0 -11106,40172007,Deflectores para tubos de plástico abs,Coples para tubos de plástico ABS,0,80509,0 -11107,40172008,Deflectores para tubos de plástico hdpe,Coples para tubos de plástico hdpe,0,80509,0 -11108,40172100,Codos para tubos,,0,80509,0 -11109,40172101,Codos para tubos de hierro forjado,,0,80509,0 -11110,40172102,Codos para tubos de hierro maleable,,0,80509,0 -11111,40172103,Codos para tubos de cobre,,0,80509,0 -11112,40172200,Bridas ciegas para tubos,,0,80509,0 -11113,40172201,Bridas ciegas para tubos de acero al carbono,,0,80509,0 -11114,40172202,Bridas ciegas para tubos de hierro forjado,,0,80509,0 -11115,40172203,Bridas ciegas para tubos de hierro dúctil,,0,80509,0 -11116,40172204,Bridas ciegas para tubos de acero forjado,,0,80509,0 -11117,40172205,Bridas ciegas para tubos de acero inoxidable,,0,80509,0 -11118,40172206,Bridas ciegas para tubos de cobre,,0,80509,0 -11119,40172300,Casquillos para tubos,,0,80509,0 -11120,40172301,Casquillos para tubos de latón,,0,80509,0 -11121,40172302,Casquillos para tubos de acero al carbono,,0,80509,0 -11122,40172303,Casquillos para tubos de hierro forjado,,0,80509,0 -11123,40172304,Casquillos para tubos de hierro dúctil,,0,80509,0 -11124,40172305,Casquillos para tubos de acero forjado,,0,80509,0 -11125,40172306,Casquillos para tubos de hierro maleable,,0,80509,0 -11126,40172307,Casquillos para tubos de acero inoxidable,,0,80509,0 -11127,40172308,Casquillos para tubos de plástico pvc,,0,80509,0 -11128,40172309,Casquillos para tubos de plástico cpvc,,0,80509,0 -11129,40172310,Casquillos para tubos de plástico abs,,0,80509,0 -11130,40172311,Casquillos para tubos de plástico hdpe,,0,80509,0 -11131,40172312,Casquillos para tubos de cobre,,0,80509,0 -11132,40172313,Casquillos para tubos de caucho,,0,80509,0 -11133,40172400,Tapas para tubos,,0,80509,0 -11134,40172401,Tapa de tubo de latón,,0,80509,0 -11135,40172402,Tapa de tubo de acero al carbono,,0,80509,0 -11136,40172403,Tapa de tubo de hierro forjado,,0,80509,0 -11137,40172404,Tapa de tubo de hierro dúctil,,0,80509,0 -11138,40172405,Tapa de tubo de acero forjado,,0,80509,0 -11139,40172406,Tapa de tubo de hierro maleable,,0,80509,0 -11140,40172407,Tapa de tubo de acero inoxidable,,0,80509,0 -11141,40172408,Tapa de tubo de plástico pvc,,0,80509,0 -11142,40172409,Tapa de tubo de plástico cpvc,,0,80509,0 -11143,40172410,Tapa de tubo de plástico abs,,0,80509,0 -11144,40172411,Tapa de tubo de plástico hdpe,,0,80509,0 -11145,40172412,Tapa de tubo de cobre,,0,80509,0 -11146,40172413,Tapa de tubo de caucho,,0,80509,0 -11147,40172500,Conectores de tubos,,0,80509,0 -11148,40172501,Conector de tubo de latón,,0,80509,0 -11149,40172502,Conector de tubo de acero al carbono,,0,80509,0 -11150,40172503,Conector de tubo de hierro forjado,,0,80509,0 -11151,40172504,Conector de tubo de hierro dúctil,,0,80509,0 -11152,40172505,Conector de tubo de acero forjado,,0,80509,0 -11153,40172506,Conector de tubo de hierro maleable,,0,80509,0 -11154,40172507,Conector de tubo de acero inoxidable,,0,80509,0 -11155,40172508,Conector de tubo de plástico pvc,,0,80509,0 -11156,40172509,Conector de tubo de plástico cpvc,,0,80509,0 -11157,40172510,Conector de tubo de plástico abs,,0,80509,0 -11158,40172511,Conector de tubo de plástico hdpe,,0,80509,0 -11159,40172512,Conector de tubo de plástico vidrio reforzado termoestable,,0,80509,0 -11160,40172513,Conector de tubo flexible,,0,80509,0 -11161,40172514,Conector de tubo pre aislado,,0,80509,0 -11162,40172515,Conector de tubo de poli butileno,,0,80509,0 -11163,40172516,Conector de tubo de polietileno,,0,80509,0 -11164,40172517,Conector de tubo de polipropileno,,0,80509,0 -11165,40172518,Conector de tubo de acero revestido,,0,80509,0 -11166,40172519,Conector de tubo de aleación de aluminio y de aluminio,,0,80509,0 -11167,40172520,Conector de tubo de clorhidrato de polivinilo rígido,,0,80509,0 -11168,40172521,Conector de tubo de cobre y de aleación de cobre,,0,80509,0 -11169,40172522,Uniones de tubo flexible,,0,80509,0 -11170,40172600,Acoples de tubos,,0,80509,0 -11171,40172601,Acoples de tubos de latón,,0,80509,0 -11172,40172602,Acoples de tubos de acero al carbono,,0,80509,0 -11173,40172603,Acoples de tubos de hierro forjado,,0,80509,0 -11174,40172604,Acoples de tubos de hierro dúctil,,0,80509,0 -11175,40172605,Acoples de tubos de acero forjado,,0,80509,0 -11176,40172606,Acoples de tubos de hierro maleable,,0,80509,0 -11177,40172607,Acoples de tubos de acero inoxidable,,0,80509,0 -11178,40172608,Acoples de tubos de plástico pvc,,0,80509,0 -11179,40172609,Acoples de tubos de plástico cpvc,,0,80509,0 -11180,40172610,Acoples de tubos abs,,0,80509,0 -11181,40172611,Acoples de tubos de plástico hdpe,,0,80509,0 -11182,40172612,Acoples de tubos de cobre,,0,80509,0 -11183,40172700,Cruces de tubos,,0,80509,0 -11184,40172701,Cruces de tubos de latón,,0,80509,0 -11185,40172702,Cruces de tubos de acero al carbono,,0,80509,0 -11186,40172703,Cruces de tubos de hierro forjado,,0,80509,0 -11187,40172704,Cruces de tubos de hierro dúctil,,0,80509,0 -11188,40172705,Cruces de tubos de acero forjado,,0,80509,0 -11189,40172706,Cruces de tubos de hierro maleable,,0,80509,0 -11190,40172707,Cruces de tubos de acero inoxidable,,0,80509,0 -11191,40172708,Cruces de tubos de plástico pvc,,0,80509,0 -11192,40172709,Cruces de tubos de plástico cpvc,,0,80509,0 -11193,40172710,Cruces de tubos de plástico abs,,0,80509,0 -11194,40172711,Cruces de tubos de plástico hdpe,,0,80509,0 -11195,40172712,Cruces de tubos de cobre,,0,80509,0 -11196,40172800,Codos de tubos,,0,80509,0 -11197,40172801,Codos para tubos de latón,,0,80509,0 -11198,40172802,Codos para tubos de acero al carbono,,0,80509,0 -11199,40172803,Codos para tubos de hierro forjado,,0,80509,0 -11200,40172804,Codos para tubos de hierro dúctil,,0,80509,0 -11201,40172805,Codos para tubos de acero forjado,,0,80509,0 -11202,40172806,Codos para tubos de hierro maleable,,0,80509,0 -11203,40172807,Codos para tubos de acero inoxidable,,0,80509,0 -11204,40172808,Codos para tubos de plástico pvc,,0,80509,0 -11205,40172809,Codos para tubos de plástico cpvc,,0,80509,0 -11206,40172810,Codos para tubos de plástico abs,,0,80509,0 -11207,40172811,Codos para tubos de plástico hdpe,,0,80509,0 -11208,40172812,Codos para tubos de cobre,,0,80509,0 -11209,40172900,Uniones de extensión de tubos,Uniones o niples de extensión de tubos,0,80509,0 -11210,40172901,Uniones de extensión para tubos de acero al carbono,Uniones o niples de extensión para tubos de acero al carbono,0,80509,0 -11211,40172902,Uniones de extensión para tubos de hierro forjado,Uniones o niples de extensión para tubos de hierro forjado,0,80509,0 -11212,40172903,Uniones de extensión para tubos de hierro dúctil,Uniones o niples de extensión para tubos de hierro dúctil,0,80509,0 -11213,40172904,Uniones de extensión para tubos de acero forjado,Uniones o niples de extensión para tubos de acero forjado,0,80509,0 -11214,40172905,Uniones de extensión para tubos de acero inoxidable,Uniones o niples de extensión para tubos de acero inoxidable,0,80509,0 -11215,40172906,Uniones de extensión para tubos de plástico pvc,Uniones o niples de extensión para tubos de plástico PVC,0,80509,0 -11216,40172907,Uniones de extensión para tubos de plástico cpvc,Uniones o niples de extensión para tubos de plástico cpvc,0,80509,0 -11217,40172908,Uniones de extensión para tubos de plástico abs,Uniones o niples de extensión para tubos de plástico ABS,0,80509,0 -11218,40172909,Uniones de extensión para tubos de plástico hdpe,Uniones o niples de extensión para tubos de plástico hdpe,0,80509,0 -11219,40172910,Uniones de extensión para tubos de caucho,Uniones o niples de extensión para tubos de caucho,0,80509,0 -11220,40172911,Uniones de extensión para tubos de cobre,Uniones o niples de extensión para tubos de cobre,0,80509,0 -11221,40173000,Semiacoplamientos de tubería,Reducciones para tubos,0,80509,0 -11222,40173001,Semiacoplamientos para tubos de latón,Reducciones de latón,0,80509,0 -11223,40173002,Semiacoplamientos para tubos de acero al carbono,Reducciones de acero al carbono,0,80509,0 -11224,40173003,Semiacoplamientos para tubos de acero forjado,Reducciones de acero forjado,0,80509,0 -11225,40173004,Semiacoplamientos para tubos de hierro maleable,Reducciones de hierro maleable,0,80509,0 -11226,40173005,Semiacoplamientos para tubos de acero inoxidable,Reducciones de acero inoxidable,0,80509,0 -11227,40173006,Semiacoplamientos para tubos de plástico pvc,Reducciones de plástico PVC,0,80509,0 -11228,40173007,Semiacoplamientos para tubos de plástico cpvc,Reducciones de plástico cpvc,0,80509,0 -11229,40173008,Semiacoplamientos para tubos de plástico abs,Reducciones de plástico ABS,0,80509,0 -11230,40173009,Semiacoplamientos para tubos de plástico hdpe,Reducciones de plástico hdpe,0,80509,0 -11231,40173100,Brida de junta de empalme para tubos,Juntas de unión para tubos,0,80509,0 -11232,40173101,Brida de junta de empalme para tubos de acero al carbono,Junta de unión para tubos de acero al carbono,0,80509,0 -11233,40173102,Brida de junta de empalme para tubos de acero forjado,Junta de unión para tubos de acero forjado,0,80509,0 -11234,40173103,Brida de junta de empalme para tubos de acero inoxidable,Junta de unión para tubos de acero inoxidable,0,80509,0 -11235,40173104,Brida de junta de empalme para tubos de cobre,Junta de unión para tubos de cobre,0,80509,0 -11236,40173200,Bridas cuello largas para soldar,Juntas de cuello largo para soldar,0,80509,0 -11237,40173201,Bridas cuello largas para soldar para tubos de acero al carbono,Junta de cuello largo para soldar para tubos de acero al carbono,0,80509,0 -11238,40173202,Bridas cuello largas para soldar para tubos de acero forjado,Junta de cuello largo para soldar para tubos de acero forjado,0,80509,0 -11239,40173203,Bridas cuello largas para soldar para tubos de acero inoxidable,Junta de cuello largo para soldar para tubos de acero inoxidable,0,80509,0 -11240,40173300,Boquillas de tubos,,0,80509,0 -11241,40173301,Boquillas para tubos de latón,,0,80509,0 -11242,40173302,Boquillas para tubos de acero al carbono,,0,80509,0 -11243,40173303,Boquillas para tubos de acero inoxidable,,0,80509,0 -11244,40173304,Boquillas para tubos de plástico pvc,,0,80509,0 -11245,40173305,Boquillas para tubos de plástico cpvc,,0,80509,0 -11246,40173306,Boquillas para tubos de plástico abs,,0,80509,0 -11247,40173307,Boquillas para tubos de plástico hdpe,,0,80509,0 -11248,40173400,Bridas de plato,,0,80509,0 -11249,40173401,Bridas de plato para tubos de latón,,0,80509,0 -11250,40173402,Bridas de plato para tubos de acero al carbono,,0,80509,0 -11251,40173403,Bridas de plato para tubos de hierro forjado,,0,80509,0 -11252,40173404,Bridas de plato para tubos de hierro dúctil,,0,80509,0 -11253,40173405,Bridas de plato para tubos de hierro maleable,,0,80509,0 -11254,40173406,Bridas de plato para tubos de acero inoxidable,,0,80509,0 -11255,40173407,Bridas de plato para tubos de plástico abs,,0,80509,0 -11256,40173408,Bridas de plato para tubos de plástico pvc,,0,80509,0 -11257,40173409,Bridas de plato para tubos de cobre,,0,80509,0 -11258,40173500,Tapones para tubos,,0,80509,0 -11259,40173501,Tapones para tubos de latón,,0,80509,0 -11260,40173502,Tapones para tubos de acero al carbono,,0,80509,0 -11261,40173503,Tapones para tubos de hierro forjado,,0,80509,0 -11262,40173504,Tapones para tubos de hierro dúctil,,0,80509,0 -11263,40173505,Tapones para tubos de acero forjado,,0,80509,0 -11264,40173506,Tapones para tubos de hierro maleable,,0,80509,0 -11265,40173507,Tapones para tubos de acero inoxidable,,0,80509,0 -11266,40173508,Tapones para tubos de plástico pvc,,0,80509,0 -11267,40173509,Tapones para tubos de plástico cpvc,,0,80509,0 -11268,40173510,Tapones para tubos de plástico abs,,0,80509,0 -11269,40173511,Tapones para tubos de plástico hdpe,,0,80509,0 -11270,40173512,Tapones para tubos de cobre,,0,80509,0 -11271,40173513,Tapones para tubos de caucho,,0,80509,0 -11272,40173600,Acoplamientos reductores para tubos,Coples reductores,0,80509,0 -11273,40173601,Acoplamiento reductor para tubos de latón,Cople reductor para tubos de latón,0,80509,0 -11274,40173602,Acoplamiento reductor para tubos de acero al carbono,Cople reductor para tubos de acero al carbono,0,80509,0 -11275,40173603,Acoplamiento reductor para tubos de hierro forjado,Cople reductor para tubos de hierro forjado,0,80509,0 -11276,40173604,Acoplamiento reductor para tubos de hierro dúctil,Cople reductor para tubos de hierro dúctil,0,80509,0 -11277,40173605,Acoplamiento reductor para tubos de acero forjado,Cople reductor para tubos de acero forjado,0,80509,0 -11278,40173606,Acoplamiento reductor para tubos de hierro maleable,Cople reductor para tubos de hierro maleable,0,80509,0 -11279,40173607,Acoplamiento reductor para tubos de acero inoxidable,Cople reductor para tubos de acero inoxidable,0,80509,0 -11280,40173608,Acoplamiento reductor para tubos de plástico pvc,Cople reductor para tubos de plástico PVC,0,80509,0 -11281,40173609,Acoplamiento reductor para tubos de plástico cpvc,Cople reductor para tubos de plástico cpvc,0,80509,0 -11282,40173610,Acoplamiento reductor para tubos de plástico abs,,0,80509,0 -11283,40173611,Acoplamiento reductor para tubos de plástico hdpe,,0,80509,0 -11284,40173612,Acoplamiento reductor para tubos de caucho,Cople reductor para tubos de caucho,0,80509,0 -11285,40173613,Acoplamiento reductor para tubos de cobre,Cople reductor para tubos de cobre,0,80509,0 -11286,40173800,Abrazaderas de reparación para tubos,,0,80509,0 -11287,40173801,Abrazadera de reparación para tubos de hierro forjado,,0,80509,0 -11288,40173802,Abrazadera de reparación para tubos de hierro dúctil,,0,80509,0 -11289,40173803,Abrazadera de reparación para tubos de acero forjado,,0,80509,0 -11290,40173804,Abrazadera de reparación para tubos de acero inoxidable,,0,80509,0 -11291,40173805,Abrazadera de reparación para tubos de plástico pvc,,0,80509,0 -11292,40173806,Abrazadera de reparación para tubos de plástico cpvc,,0,80509,0 -11293,40173807,Abrazadera de reparación para tubos de plástico abs,,0,80509,0 -11294,40173808,Abrazadera de reparación para tubos de plástico hdpe,,0,80509,0 -11295,40173900,Disco de ruptura para tubos,,0,80509,0 -11296,40173901,Disco de ruptura para tubos de acero al carbono,,0,80509,0 -11297,40173902,Disco de ruptura para tubos de hierro forjado,,0,80509,0 -11298,40173903,Disco de ruptura para tubos de hierro dúctil,,0,80509,0 -11299,40173904,Disco de ruptura para tubos de acero forjado,,0,80509,0 -11300,40173905,Disco de ruptura para tubos de hierro maleable,,0,80509,0 -11301,40173906,Disco de ruptura para tubos de acero inoxidable,,0,80509,0 -11302,40173907,Disco de ruptura para tubos de plástico pvc,,0,80509,0 -11303,40173908,Disco de ruptura para tubos de plástico cpvc,,0,80509,0 -11304,40173909,Disco de ruptura para tubos de plástico abs,,0,80509,0 -11305,40173910,Disco de ruptura para tubos de plástico hdpe,,0,80509,0 -11306,40174100,Brida encajada por soldadura para tubos,,0,80509,0 -11307,40174101,Brida encajada por soldadura para tubos de acero al carbono,,0,80509,0 -11308,40174102,Brida encajada por soldadura para tubos de acero forjado,,0,80509,0 -11309,40174103,Brida encajada por soldadura para tubos de acero inoxidable,,0,80509,0 -11310,40174104,Brida encajada por soldadura para tubos de plástico pvc,,0,80509,0 -11311,40174105,Brida encajada por soldadura para tubos de plástico cpvc,,0,80509,0 -11312,40174106,Brida encajada por soldadura para tubos de plástico abs,,0,80509,0 -11313,40174107,Brida encajada por soldadura para tubos de plástico hdpe,,0,80509,0 -11314,40174108,Brida encajada por soldadura para tubos de cobre,,0,80509,0 -11315,40174300,Espaciadores para tubos,Separadores para tubos,0,80509,0 -11316,40174301,Espaciador para tubos de latón,Separador para tubos de latón,0,80509,0 -11317,40174302,Espaciador para tubos de acero al carbono,Separador para tubos de acero al carbono,0,80509,0 -11318,40174303,Espaciador para tubos de hierro forjado,Separador para tubos de hierro forjado,0,80509,0 -11319,40174304,Espaciador para tubos de hierro dúctil,Separador para tubos de hierro dúctil,0,80509,0 -11320,40174305,Espaciador para tubos de acero forjado,Separador para tubos de acero forjado,0,80509,0 -11321,40174306,Espaciador para tubos de acero inoxidable,Separador para tubos de acero inoxidable,0,80509,0 -11322,40174307,Espaciador para tubos de plástico pvc,Separador para tubos de plástico PVC,0,80509,0 -11323,40174308,Espaciador para tubos de plástico cpvc,Separador para tubos de plástico cpvc,0,80509,0 -11324,40174309,Espaciador para tubos de plástico abs,Separador para tubos de plástico ABS,0,80509,0 -11325,40174310,Espaciador para tubos de plástico hdpe,Separador para tubos de plástico hdpe,0,80509,0 -11326,40174400,Bridas ciegas para tubos,,0,80509,0 -11327,40174401,Bridas ciegas para tubos de acero al carbono,,0,80509,0 -11328,40174402,Bridas ciegas para tubos de acero forjado,,0,80509,0 -11329,40174403,Bridas ciegas para tubos de acero forjado,,0,80509,0 -11330,40174500,Pivotes o uniones rotativas para tubos,,0,80509,0 -11331,40174501,Pivotes o uniones rotativas para tubos de latón,,0,80509,0 -11332,40174502,Pivotes o uniones rotativas para tubos de plástico pvc,,0,80509,0 -11333,40174503,Pivotes o uniones rotativas para tubos de plástico cpvc,,0,80509,0 -11334,40174504,Pivotes o uniones rotativas para tubos de plástico abs,,0,80509,0 -11335,40174505,Pivotes o uniones rotativas para tubos de plástico hdpe,,0,80509,0 -11336,40174506,Pivotes o uniones rotativas para tubos de cobre,,0,80509,0 -11337,40174600,T para tubos,Conexiones en T para tubos,0,80509,0 -11338,40174601,T para tubos de latón,Conexión en T para tubos de latón,0,80509,0 -11339,40174602,T para tubos de acero al carbono,Conexión en T para tubos de acero al carbono,0,80509,0 -11340,40174603,T para tubos de hierro forjado,Conexión en T para tubos de hierro forjado,0,80509,0 -11341,40174604,T para tubos de hierro dúctil,Conexión en T para tubos de hierro dúctil,0,80509,0 -11342,40174605,T para tubos de acero forjado,Conexión en T para tubos de acero forjado,0,80509,0 -11343,40174606,T para tubos de hierro maleable,Conexión en T para tubos de hierro maleable,0,80509,0 -11344,40174607,T para tubos de acero inoxidable,Conexión en T para tubos de acero inoxidable,0,80509,0 -11345,40174608,T para tubos de plástico pvc,Conexión en T para tubos de plástico PVC,0,80509,0 -11346,40174609,T para tubos de plástico cpvc,Conexión en T para tubos de plástico cpvc,0,80509,0 -11347,40174610,T para tubos de plástico abs,Conexión en T para tubos de plástico ABS,0,80509,0 -11348,40174611,T para tubos de plástico hdpe,Conexión en T para tubos de plástico hdpe,0,80509,0 -11349,40174612,T para tubos de cobre,Conexión en T para tubos de cobre,0,80509,0 -11350,40174700,Brida roscada para tubos,,0,80509,0 -11351,40174701,Brida roscada para tubos de latón,,0,80509,0 -11352,40174702,Brida roscada para tubos de acero al carbono,,0,80509,0 -11353,40174703,Brida roscada para tubos de hierro forjado,,0,80509,0 -11354,40174704,Brida roscada para tubos de hierro dúctil,,0,80509,0 -11355,40174705,Brida roscada para tubos de acero forjado,,0,80509,0 -11356,40174706,Brida roscada para tubos de hierro maleable,,0,80509,0 -11357,40174707,Brida roscada para tubos de acero inoxidable,,0,80509,0 -11358,40174708,Brida roscada para tubos de plástico pvc,,0,80509,0 -11359,40174709,Brida roscada para tubos de plástico cpvc,,0,80509,0 -11360,40174710,Brida roscada para tubos de plástico abs,,0,80509,0 -11361,40174711,Brida roscada para tubos de plástico hdpe,,0,80509,0 -11362,40174800,Bridas de inodoro para tubos,,0,80509,0 -11363,40174801,Bridas de inodoro para tubos de acero al carbono,,0,80509,0 -11364,40174802,Bridas de inodoro para tubos de hierro forjado,,0,80509,0 -11365,40174803,Bridas de inodoro para tubos de plástico pvc,,0,80509,0 -11366,40174804,Bridas de inodoro para tubos de plástico cpvc,,0,80509,0 -11367,40174805,Bridas de inodoro para tubos de plástico abs,,0,80509,0 -11368,40174806,Bridas de inodoro para tubos de plástico hdpe,,0,80509,0 -11369,40174807,Bridas de inodoro para tubos de latón,,0,80509,0 -11370,40174808,Bridas de inodoro para tubos de cobre,,0,80509,0 -11371,40174900,Uniones para tubos,,0,80509,0 -11372,40174901,Uniones para tubos de latón,,0,80509,0 -11373,40174902,Uniones para tubos de acero al carbono,,0,80509,0 -11374,40174903,Uniones para tubos de hierro forjado,,0,80509,0 -11375,40174904,Uniones para tubos de hierro dúctil,,0,80509,0 -11376,40174905,Uniones para tubos de acero forjado,,0,80509,0 -11377,40174906,Uniones para tubos de hierro maleable,,0,80509,0 -11378,40174907,Uniones para tubos de acero inoxidable,,0,80509,0 -11379,40174908,Uniones para tubos de plástico pvc,,0,80509,0 -11380,40174909,Uniones para tubos de plástico cpvc,,0,80509,0 -11381,40174910,Uniones para tubos de plástico abs,,0,80509,0 -11382,40174911,Uniones para tubos de plástico hdpe,,0,80509,0 -11383,40174912,Uniones para tubos de cobre,,0,80509,0 -11384,40175000,Bridas cuello para tubos,,0,80509,0 -11385,40175001,Bridas cuello para tubos de acero al carbono,,0,80509,0 -11386,40175002,Bridas cuello para tubos de acero forjado,,0,80509,0 -11387,40175003,Bridas cuello para tubos de acero inoxidable,,0,80509,0 -11388,40175200,Y de conexión para tubos,Conexiones en Y para tubos,0,80509,0 -11389,40175201,Y de conexión para tubos de latón,Conexión en Y para tubos de latón,0,80509,0 -11390,40175202,Y de conexión para tubos de acero al carbono,Conexión en Y para tubos de acero al carbono,0,80509,0 -11391,40175203,Y de conexión para tubos de hierro forjado,Conexión en Y para tubos de hierro forjado,0,80509,0 -11392,40175204,Y de conexión para tubos de hierro dúctil,Conexión en Y para tubos de hierro dúctil,0,80509,0 -11393,40175205,Y de conexión para tubos de acero forjado,Conexión en Y para tubos de acero forjado,0,80509,0 -11394,40175206,Y de conexión para tubos de hierro maleable,Conexión en Y para tubos de hierro maleable,0,80509,0 -11395,40175207,Y de conexión para tubos de acero inoxidable,Conexión en Y para tubos de acero inoxidable,0,80509,0 -11396,40175208,Y de conexión para tubos de plástico pvc,Conexión en Y para tubos de plástico PVC,0,80509,0 -11397,40175209,Y de conexión para tubos de plástico cpvc,Conexión en Y para tubos de plástico cpvc,0,80509,0 -11398,40175210,Y de conexión para tubos de plástico abs,Conexión en Y para tubos de plástico ABS,0,80509,0 -11399,40175211,Y de conexión para tubos de plástico hdpe,Conexión en Y para tubos de plástico hdpe,0,80509,0 -11400,40175212,Y de conexión para tubos de cobre,Conexión en Y para tubos de cobre,0,80509,0 -11401,40175300,Accesorios y bridas especializados para tubos,,0,80509,0 -11402,40175301,Bridas de amoniaco,Bridas para amoniaco,0,80509,0 -11403,40175302,Bridas de orificio,,0,80509,0 -11404,40175303,Salidas para ramas de tubería,,0,80509,0 -11405,40175304,Cajas de conexión de tubos,,0,80509,0 -11406,40175305,Insertos de tubos,Tapones de tubos,0,80509,0 -11407,40175306,Laterales de tubos,,0,80509,0 -11408,40175307,Sillas de tubos,Monturas y soportes para tubos,0,80509,0 -11409,40175308,Remates de secciones de tuberías,Remates para secciones de tuberías,0,80509,0 -11410,40175309,Bridas de paleta para tubos,,0,80509,0 -11411,40181700,Tubos de aluminio soldados,,0,80509,0 -11412,40181701,Tubo doblado de aluminio soldado,Tubo curvado de aluminio soldado,0,80509,0 -11413,40181702,Tubo perforado de aluminio soldado,Tubo agujereado de aluminio soldado,0,80509,0 -11414,40181703,Tubo de remate final de aluminio soldado,,0,80509,0 -11415,40181704,Tubo multipuerto de aluminio soldado,,0,80509,0 -11416,40181705,Tubo biselado de aluminio soldado,,0,80509,0 -11417,40181706,Ensamblaje de tubos de aluminio soldado,,0,80509,0 -11418,40181800,Tubos de acero soldados,,0,80509,0 -11419,40181801,Tubo doblado de acero soldado,Tubo curvado de acero soldado,0,80509,0 -11420,40181802,Tubo perforado de acero soldado,Tubo agujerado de acero soldado,0,80509,0 -11421,40181803,Tubo de remate final de acero soldado,,0,80509,0 -11422,40181804,Tubo multipuerto de acero soldado,,0,80509,0 -11423,40181805,Tubo biselado de acero soldado,,0,80509,0 -11424,40181806,Ensamblaje de tubos de acero soldado,,0,80509,0 -11425,40181900,Tubos de acero inoxidable soldados,,0,80509,0 -11426,40181901,Tubo doblado de acero inoxidable soldado,Tubo curvado de acero inoxidable soldado,0,80509,0 -11427,40181902,Tubo perforado de acero inoxidable soldado,Tubo agujereado de acero inoxidable soldado,0,80509,0 -11428,40181903,Tubo de remate final de acero inoxidable soldado,,0,80509,0 -11429,40181904,Tubo multipuerto de acero inoxidable soldado,,0,80509,0 -11430,40181905,Tubo biselado de acero inoxidable soldado,,0,80509,0 -11431,40181906,Ensamblaje de tubos de acero inoxidable soldado,,0,80509,0 -11432,40182000,Tubos de cobre sin uniones,,0,80509,0 -11433,40182001,Tubo doblado de cobre sin uniones,Tubo curvado de cobre sin uniones,0,80509,0 -11434,40182002,Tubo perforado de cobre sin uniones,Tubo agujerado de cobre sin uniones,0,80509,0 -11435,40182003,Tubo de remate final de cobre sin uniones,,0,80509,0 -11436,40182004,Tubo multipuerto de cobre sin uniones,,0,80509,0 -11437,40182005,Tubo biselado de cobre sin uniones,,0,80509,0 -11438,40182006,Ensamblaje de tubos de cobre sin uniones,,0,80509,0 -11439,40182300,Tubos de acero sin uniones,,0,80509,0 -11440,40182301,Tubo doblado de acero sin uniones,Tubo curvado de acero sin uniones,0,80509,0 -11441,40182302,Tubo perforado de acero sin uniones,Tubo agujerado de acero sin uniones,0,80509,0 -11442,40182303,Tubo de remate final de acero sin uniones,,0,80509,0 -11443,40182304,Tubo multipuerto de acero sin uniones,,0,80509,0 -11444,40182305,Tubo biselado de acero sin uniones,,0,80509,0 -11445,40182306,Ensamblaje de tubos de acero sin uniones,,0,80509,0 -11446,40182400,Tubos de acero inoxidable sin uniones,,0,80509,0 -11447,40182401,Tubo doblado de acero inoxidable sin uniones,Tubo curvado de acero inoxidable sin uniones,0,80509,0 -11448,40182402,Tubo perforado de acero inoxidable sin uniones,Tubo agujerado de acero inoxidable sin uniones,0,80509,0 -11449,40182403,Tubo de remate final de acero inoxidable sin uniones,,0,80509,0 -11450,40182404,Tubo multipuerto de acero inoxidable sin uniones,,0,80509,0 -11451,40182405,Tubo biselado de acero inoxidable sin uniones,,0,80509,0 -11452,40182406,Ensamblaje de tubos de acero inoxidable sin uniones,,0,80509,0 -11453,40182700,Tubos de aluminio extruidos,,0,80509,0 -11454,40182701,Tubo doblado de aluminio extruido,Tubo curvado de aluminio extruido,0,80509,0 -11455,40182702,Tubo perforado de aluminio extruido,Tubo agujerado de aluminio extruido,0,80509,0 -11456,40182703,Tubo de remate final de aluminio extruido,,0,80509,0 -11457,40182704,Tubo multipuerto de aluminio extruido,,0,80509,0 -11458,40182705,Tubo biselado de aluminio extruido,,0,80509,0 -11459,40182706,Ensamblaje de tubos de aluminio extruido,,0,80509,0 -11460,40182707,Tubería cubierta de aluminio extruido,,0,80509,0 -11461,40182900,Tubos de acero inoxidable extruidos,,0,80509,0 -11462,40182901,Tubo doblado de acero inoxidable extruido,Tubo curvado de acero inoxidable extruido,0,80509,0 -11463,40182902,Tubo perforado de acero inoxidable extruido,Tubo agujerado de acero inoxidable extruido,0,80509,0 -11464,40182903,Tubo de remate final de acero inoxidable extruido,,0,80509,0 -11465,40182904,Tubo multipuerto de acero inoxidable extruido,,0,80509,0 -11466,40182905,Tubo biselado de acero inoxidable extruido,,0,80509,0 -11467,40182906,Ensamblaje de tubos de acero inoxidable extruido,,0,80509,0 -11468,40183000,Tuberías de caucho y plástico,,0,80509,0 -11469,40183001,Tubería de caucho,,0,80509,0 -11470,40183002,Tubería de plástico pvc,,0,80509,0 -11471,40183003,Tubería de plástico cpvc,,0,80509,0 -11472,40183004,Tubería de plástico hdpe,,0,80509,0 -11473,40183005,Tubería de caucho de baja presión,,0,80509,0 -11474,40183006,Tubería termo contraíble,,0,80509,0 -11475,40183007,Tubería de espuma de caucho,,0,80509,0 -11476,40183008,Tubería de poliuretano pur,,0,80509,0 -11477,40183009,Tubería de manga trenzada,,0,80509,0 -11478,40183010,Tubería de alta presión con ensamblaje,,0,80509,0 -11479,40183100,Accesorios de tubos,,0,80509,0 -11480,40183101,Codo de tubo,,0,80509,0 -11481,40183102,“t” de tubo,Tubo en T,0,80509,0 -11482,40183103,Unión de tubo,,0,80509,0 -11483,40183104,Tapón de tubo,,0,80509,0 -11484,40183105,Boquilla de tubo,,0,80509,0 -11485,40183106,Pistón de tubo,,0,80509,0 -11486,40183107,Acople de tubo,,0,80509,0 -11487,40183108,Casquillo de tubo,,0,80509,0 -11488,40183109,Adaptador de tubo,,0,80509,0 -11489,40183110,Conector de tubo,,0,80509,0 -11490,40183111,Cruz de tubo,Tubo en cruz,0,80509,0 -11491,40183112,Reductor de tubo,,0,80509,0 -11492,41101500,"Equipo y suministros para la mezcla, la dispersión y la homogeneización en laboratorio",,0,80509,0 -11493,41101502,Bolsas “stomachers”,Homogeneizador con palas o bolsas para laboratorio,0,80509,0 -11494,41101503,Rociadores de laboratorio,Pulverizadores de laboratorio,0,80509,0 -11495,41101504,Homogeneizadores,,0,80509,0 -11496,41101505,Celdas de presión francesas,Homogeneizador celular por presión (prensa francesa),0,80509,0 -11497,41101515,Latas medidoras de líquido,Bidones,0,80509,0 -11498,41101516,Homogeneizadores dobles,Homogeneizador de tejidos,0,80509,0 -11499,41101518,Mezcladores o emulsificadores de laboratorio,,0,80509,0 -11500,41101700,"Equipo de perforación, amoladura, corte, trituración y prensado para laboratorio",,0,80509,0 -11501,41101701,Molinos de laboratorio,Molinos para laboratorio,0,80509,0 -11502,41101702,Pilones y morteros,,0,80509,0 -11503,41101703,Moledoras de tejidos,Homogeneizador para muestras pequeñas,0,80509,0 -11504,41101705,Trituradoras o pulverizadoras de laboratorio,,0,80509,0 -11505,41101706,Desintegradores de laboratorio,,0,80509,0 -11506,41101707,Prensas de laboratorio,,0,80509,0 -11507,41101708,Esmeriladora o pulidora de laboratorio,,0,80509,0 -11508,41101709,Mezcladora de asfalto y concreto de laboratorio,Mezclador de laboratorio para asfalto y concreto (hormigón),0,80509,0 -11509,41101800,Equipo de física de electrones y de estado sólido para laboratorio,,0,80509,0 -11510,41101801,Pistolas de electrones,,0,80509,0 -11511,41101802,Generadores de rayos x,,0,80509,0 -11512,41101803,Coulómetros,,0,80509,0 -11513,41101804,Electroscopios,,0,80509,0 -11514,41101805,Flujómetros,,0,80509,0 -11515,41101806,Magnetómetros,,0,80509,0 -11516,41101807,Aparatos de difracción de electrones,,0,80509,0 -11517,41101808,Aparatos de difracción de neutrones,,0,80509,0 -11518,41101809,Aparatos de difracción óptica,,0,80509,0 -11519,41101810,Difractómetros,,0,80509,0 -11520,41101811,Micro analizador de sondeo de electrones por rayos x,,0,80509,0 -11521,41101812,Acelerador de partículas,,0,80509,0 -11522,41101813,Controlador de equipos de rayos x de laboratorio,,0,80509,0 -11523,41102400,Equipo de calefacción y secadores para laboratorio,,0,80509,0 -11524,41102401,Quemadores de gas,Mecheros de gas,0,80509,0 -11525,41102402,Quemadores de alcohol,Mecheros de alcohol,0,80509,0 -11526,41102403,Incineradores de laboratorio,,0,80509,0 -11527,41102404,Calentadores de laboratorio,,0,80509,0 -11528,41102405,Revestimientos o cintas calentadoras,,0,80509,0 -11529,41102406,Hornillas eléctricas de laboratorio,,0,80509,0 -11530,41102407,Gabinetes calentadores,,0,80509,0 -11531,41102410,Secadoras infrarrojas,,0,80509,0 -11532,41102412,Secadores de aire caliente,,0,80509,0 -11533,41102421,Cámaras de reciclaje de temperatura o recicladores térmicos,"Cámara de ciclos de temperatura, Termocicladores",0,80509,0 -11534,41102422,Baños secos o bloques calentadores,,0,80509,0 -11535,41102423,Hornillas eléctricas agitadoras,,0,80509,0 -11536,41102424,Calentadores de deslizamiento,Calentadores de muestras,0,80509,0 -11537,41102425,Secadores de deslizamiento,Secador de muestras,0,80509,0 -11538,41102426,Equipos o accesorios para calentar o secar,,0,80509,0 -11539,41102427,Torre secadora,,0,80509,0 -11540,41102428,Papel altamente absorbente,,0,80509,0 -11541,41102429,Sellador por calor de unidades de tubos de muestras de sangre,,0,80509,0 -11542,41102500,Equipo entomológico para laboratorio y accesorios,,0,80509,0 -11543,41102501,Contenedores de insectos para laboratorio,,0,80509,0 -11544,41102502,Instalaciones de crianza para entomología,,0,80509,0 -11545,41102503,Textiles o redes para entomología,,0,80509,0 -11546,41102504,Equipo de entomología para clavar especímenes,,0,80509,0 -11547,41102505,Materiales de ensamblaje para entomología,,0,80509,0 -11548,41102506,Bandejas de entomología,,0,80509,0 -11549,41102507,Equipo para recolectar especímenes de entomología,,0,80509,0 -11550,41102508,Aspiradoras para entomología,,0,80509,0 -11551,41102509,Cucharones para entomología,,0,80509,0 -11552,41102510,Mono copas para entomología,,0,80509,0 -11553,41102511,Trampas pegajosas para entomología,,0,80509,0 -11554,41102512,Kits de pruebas para insectos,,0,80509,0 -11555,41102513,Unidades de despliegue para entomología,,0,80509,0 -11556,41102600,Equipo y accesorios para laboratorio animal,,0,80509,0 -11557,41102601,Jaulas para animales pequeños para laboratorio,,0,80509,0 -11558,41102602,Equipo de acuarios,,0,80509,0 -11559,41102603,Suministros para identificación de animales,,0,80509,0 -11560,41102604,Dispositivos para atrapar animales,,0,80509,0 -11561,41102605,Sistemas de aireación de peces,,0,80509,0 -11562,41102606,Restricciones o arneses para animales para laboratorio,,0,80509,0 -11563,41102607,Agujas para alimentar animales,,0,80509,0 -11564,41102608,Equipos para pruebas de animales,,0,80509,0 -11565,41102609,Pistola anestésica,,0,80509,0 -11566,41102900,Equipo de histología,,0,80509,0 -11567,41102901,Estaciones de incrustación de tejidos,,0,80509,0 -11568,41102902,Moldes de incrustación,,0,80509,0 -11569,41102903,Cápsulas de incrustación,,0,80509,0 -11570,41102904,Compuestos de incrustación,,0,80509,0 -11571,41102905,Aparatos de teñido histológico,,0,80509,0 -11572,41102909,Procesadores de tejidos,,0,80509,0 -11573,41102910,Aparatos de cultivo de tejidos,,0,80509,0 -11574,41102911,Cuchillos o sujeta cuchillos o cuchillas histológicos,,0,80509,0 -11575,41102912,Cuchillos marcadores de vidrio histológicos,,0,80509,0 -11576,41102913,Afiladores o correas o compuestos histológicos,,0,80509,0 -11577,41102914,Desintegradores ultrasónicos,,0,80509,0 -11578,41102915,Estaciones de muestreo y disección histológica,,0,80509,0 -11579,41102916,Micrótomos,,0,80509,0 -11580,41102917,Cuchillas para micrótomos,,0,80509,0 -11581,41102918,Forros deslizantes para laboratorio,,0,80509,0 -11582,41102919,Recicladores de solventes,,0,80509,0 -11583,41102920,Casetes para tejidos para histología,,0,80509,0 -11584,41102921,Parafina para histología,,0,80509,0 -11585,41102922,Equipo de forros deslizantes automatizado,,0,80509,0 -11586,41102923,Contador de celdas somático,,0,80509,0 -11587,41102924,Etiquetador de casetes de tejidos automatizado,,0,80509,0 -11588,41102925,Arcador permanente resistente a la formalina y al solvente para histología,Fijadores histológicos,0,80509,0 -11589,41102926,Tolva de casetes de tejidos para histología,,0,80509,0 -11590,41102927,Implemento para procesar tejidos para histología,,0,80509,0 -11591,41103000,Equipo de enfriamiento para laboratorio,,0,80509,0 -11592,41103001,Sondas planas enfriadoras refrigeradas,,0,80509,0 -11593,41103003,Criostatos,,0,80509,0 -11594,41103004,Hornos de circulación por ventilador,,0,80509,0 -11595,41103005,Gabinetes o congeladores ultra fríos o ultra bajos independientes,,0,80509,0 -11596,41103006,Congeladores criogénicos o de nitrógeno líquido,,0,80509,0 -11597,41103007,Unidades de enfriamiento o circuladores de agua fría,,0,80509,0 -11598,41103008,Módulos enfriadores refrigerados,,0,80509,0 -11599,41103010,Refrigeradores para bancos de sangre,,0,80509,0 -11600,41103011,Refrigeradores para propósitos generales o neveras congeladores,,0,80509,0 -11601,41103012,Refrigeradores o neveras congeladores para almacenar material inflamable,,0,80509,0 -11602,41103013,Refrigeradores o neveras congeladores a prueba de explosiones,,0,80509,0 -11603,41103014,Refrigeradores de cromatografía,,0,80509,0 -11604,41103015,Congeladores para bancos de sangre,,0,80509,0 -11605,41103017,Congeladores para almacenar material inflamable,,0,80509,0 -11606,41103019,Congeladores para almacenar plasma,,0,80509,0 -11607,41103020,Congeladores de cofre ultra fríos o ultra bajos,,0,80509,0 -11608,41103021,Congeladores planos para laboratorio,,0,80509,0 -11609,41103022,Transporte o almacenamiento frío,,0,80509,0 -11610,41103023,Enfriadores para laboratorio,,0,80509,0 -11611,41103024,Trampas frías,,0,80509,0 -11612,41103025,Accesorios para equipos enfriadores de laboratorio,,0,80509,0 -11613,41103026,Cubeta de hielo o contenedor enfriador para mesa de trabajo,,0,80509,0 -11614,41103027,Cabina de almacenamiento de unidades de sangre,,0,80509,0 -11615,41103200,Equipo de lavado de laboratorio,,0,80509,0 -11616,41103201,Lavadoras de ingeniería química,,0,80509,0 -11617,41103202,Máquinas lavadoras para laboratorio,,0,80509,0 -11618,41103203,Lavadoras de pipetas,,0,80509,0 -11619,41103205,Estantes o accesorios para máquinas lavadoras,,0,80509,0 -11620,41103206,Detergentes de lavado para laboratorios,,0,80509,0 -11621,41103207,Lavadoras de micro placas,,0,80509,0 -11622,41103208,Lavadoras de celdas de bancos de sangre,,0,80509,0 -11623,41103209,Botellas de lavado de laboratorios,,0,80509,0 -11624,41103210,Esterilizadores uv ultravioleta para laboratorios,Sistema esterilizador con filtro UV,0,80509,0 -11625,41103211,Equipos para el tratamiento de aguas residuales de laboratorio,,0,80509,0 -11626,41103212,Cepillo limpiador de cristalería de laboratorio,,0,80509,0 -11627,41103300,Equipo de mecánica de fluidos,,0,80509,0 -11628,41103301,Contadores centellantes líquidos,,0,80509,0 -11629,41103302,Hidrómetros ácidos de batería,,0,80509,0 -11630,41103303,Densitómetros,,0,80509,0 -11631,41103305,Equipos de alto vacío,,0,80509,0 -11632,41103306,Equipos de vacío neumático,,0,80509,0 -11633,41103307,Equipos de vacío o vapor de mercurio,,0,80509,0 -11634,41103308,Aparatos de combustión de alto vacío,,0,80509,0 -11635,41103309,Equipos de análisis de inyección de flujo,,0,80509,0 -11636,41103310,Instrumentos para medir la concentración de gas o vapor,,0,80509,0 -11637,41103311,Manómetros,,0,80509,0 -11638,41103312,Viscosímetros,,0,80509,0 -11639,41103313,Indicadores de profundidad,,0,80509,0 -11640,41103314,Aparatos de estimación de estructura microscópica,,0,80509,0 -11641,41103315,Aparatos de estimación de fuerza de una solución,,0,80509,0 -11642,41103316,Picnómetros,,0,80509,0 -11643,41103317,Instrumentos para medir la tensión de la superficie,,0,80509,0 -11644,41103318,Densitómetro nuclear,,0,80509,0 -11645,41103319,Instrumento para medir la concentración,,0,80509,0 -11646,41103320,Instrumento para medir la densidad,,0,80509,0 -11647,41103321,Aparatos para medir la estabilidad de cuerpos flotantes,,0,80509,0 -11648,41103322,Evaluador de corriente de aire,Anemómetro,0,80509,0 -11649,41103323,Flujómetro acústico de canal abierto,,0,80509,0 -11650,41103324,Generador de ondas para laboratorio,,0,80509,0 -11651,41103325,Aparato de visualización de flujo de fluido,,0,80509,0 -11652,41103326,Túnel de viento,,0,80509,0 -11653,41103327,Medidor de corriente,,0,80509,0 -11654,41103400,Equipo de acondicionamiento ambiental para laboratorio,,0,80509,0 -11655,41103401,Pantallas de control de contaminación,,0,80509,0 -11656,41103403,Equipos de control de aire microbiológico,,0,80509,0 -11657,41103406,Cajas de guantes de aislamiento,,0,80509,0 -11658,41103407,Cámara anaeróbica,,0,80509,0 -11659,41103408,Alcance refrigerado para cámaras ambientales o de cultivo,,0,80509,0 -11660,41103409,Alcance caliente para cámaras ambientales o de cultivo,,0,80509,0 -11661,41103410,Alcance refrigerado y caliente para cámaras ambientales y de cultivo,,0,80509,0 -11662,41103411,Camino refrigerado para cámaras ambientales o de cultivo,,0,80509,0 -11663,41103412,Camino caliente para cámaras ambientales o de cultivo,,0,80509,0 -11664,41103413,Camino refrigerado y caliente para cámaras ambientales o de cultivo,,0,80509,0 -11665,41103414,Accesorios para equipos acondicionadores de ambiente para laboratorios,,0,80509,0 -11666,41103415,Bancos limpios,,0,80509,0 -11667,41103416,Cámara de ciclos de temperatura,,0,80509,0 -11668,41103417,Monitor de calidad del aire interior,,0,80509,0 -11669,41103418,Cámara ambiental de temperatura y humedad a la que se puede entrar,Cámara climática,0,80509,0 -11670,41103419,Cámara de polución ambiental,Cámara de contaminación ambiental,0,80509,0 -11671,41103420,Cámara de ozono ambiental,,0,80509,0 -11672,41103421,Cámara de explosión ambiental,,0,80509,0 -11673,41103500,Recintos cerrados de laboratorio y accesorios,,0,80509,0 -11674,41103501,Ebullómetro,,0,80509,0 -11675,41103502,Caperuzas o cajones para gases,,0,80509,0 -11676,41103504,Gabinetes o estaciones para flujo laminar,,0,80509,0 -11677,41103506,Cerramientos pcr,,0,80509,0 -11678,41103507,Cerramientos hepa filtrados,,0,80509,0 -11679,41103508,Cerramientos carbono filtrados,,0,80509,0 -11680,41103509,Estropajos para laboratorio,,0,80509,0 -11681,41103510,Secadores para laboratorio,,0,80509,0 -11682,41103511,Accesorios de cerramiento para laboratorio,,0,80509,0 -11683,41103512,Eliminadores de estática,,0,80509,0 -11684,41103513,Cerramientos para cultivo de tejidos,,0,80509,0 -11685,41103514,Generador de vapor para laboratorio,,0,80509,0 -11686,41103515,Generador de gas para laboratorio,,0,80509,0 -11687,41103516,Capucha de ventilación,,0,80509,0 -11688,41103700,Cubetas de laboratorio,,0,80509,0 -11689,41103701,Baños de circulación,,0,80509,0 -11690,41103702,Baños termostáticos,,0,80509,0 -11691,41103703,Baños múltiples,,0,80509,0 -11692,41103704,Baños biológicos,,0,80509,0 -11693,41103705,Baños para órganos,,0,80509,0 -11694,41103706,Baños de agua,,0,80509,0 -11695,41103707,Baños de aceite,,0,80509,0 -11696,41103708,Baños de arena,,0,80509,0 -11697,41103709,Baños refrigerados,,0,80509,0 -11698,41103710,Baños de agua de agitación orbital,,0,80509,0 -11699,41103711,Baños de agua de agitación recíproca,,0,80509,0 -11700,41103712,Circuladores de inmersión,,0,80509,0 -11701,41103713,Baños de viscosidad,,0,80509,0 -11702,41103714,Baños de flotación de tejidos,,0,80509,0 -11703,41103715,Accesorios o suministros para baños de laboratorio,,0,80509,0 -11704,41103716,Baños para descongelar plasma de bancos de sangre,,0,80509,0 -11705,41103717,Baños para cultivos de tejidos,,0,80509,0 -11706,41103800,Equipo y suministros para la mezcla y la agitación por sacudida o varillas en laboratorio,,0,80509,0 -11707,41103801,Mezcladores de laboratorio,,0,80509,0 -11708,41103802,Mezcladores de rodillo,,0,80509,0 -11709,41103803,Mesas para revolver,,0,80509,0 -11710,41103804,Equipo multi banco o de floculación,,0,80509,0 -11711,41103805,Vibradores para laboratorio,,0,80509,0 -11712,41103806,Agitador magnético,,0,80509,0 -11713,41103807,Mezcladores de toque para laboratorio,,0,80509,0 -11714,41103808,Mezcladores de plaqueta,,0,80509,0 -11715,41103809,Mezcladores químicos o de hematología,,0,80509,0 -11716,41103810,Agitadores de techo,,0,80509,0 -11717,41103811,Sacudidores orbitales,,0,80509,0 -11718,41103812,Sacudidores recíprocos,,0,80509,0 -11719,41103813,Sacudidores de rotación,,0,80509,0 -11720,41103814,Mezcladores de vértice,,0,80509,0 -11721,41103815,Rotadores de tubos,,0,80509,0 -11722,41103816,Accesorios o aditamentos para mezcladores o sacudidores,,0,80509,0 -11723,41103817,Reactor de laboratorio,,0,80509,0 -11724,41103900,Centrifugadoras de laboratorio y accesorios,,0,80509,0 -11725,41103901,Micro centrífugas,,0,80509,0 -11726,41103902,Micro centrífugas refrigeradas,,0,80509,0 -11727,41103903,Centrífugas de mesa,,0,80509,0 -11728,41103904,Centrífugas de mesa refrigeradas,,0,80509,0 -11729,41103905,Centrífugas de piso,,0,80509,0 -11730,41103906,Centrífugas de piso refrigeradas,,0,80509,0 -11731,41103907,Ultra centrífugas,,0,80509,0 -11732,41103908,Centrífugas de vacío,,0,80509,0 -11733,41103909,Rotores de centrífugas,,0,80509,0 -11734,41103910,Baldes centrífugos,,0,80509,0 -11735,41103911,Adaptadores para centrífugas,,0,80509,0 -11736,41103912,Cepillos para centrífugas,,0,80509,0 -11737,41103913,Accesorios para centrífugas de laboratorio,,0,80509,0 -11738,41103914,Citocentrífuga,,0,80509,0 -11739,41104000,Equipo de toma de muestras,,"0,1",80509,0 -11740,41104001,Cambiadores de muestras,,"0,1",80509,0 -11741,41104002,Oxidante de muestras,,"0,1",80509,0 -11742,41104003,Línea de preparación de muestras,,"0,1",80509,0 -11743,41104004,Bombas de preparación de muestras,,"0,1",80509,0 -11744,41104005,Achicadores para laboratorio,,"0,1",80509,0 -11745,41104006,Coliwasas (muestreadores de desechos líquidos de compostaje),,"0,1",80509,0 -11746,41104007,Muestreadores de agua,,"0,1",80509,0 -11747,41104008,Muestreadores o colectores de aire,,"0,1",80509,0 -11748,41104009,Bombas muestreadoras de aire,,"0,1",80509,0 -11749,41104010,Kits de reactivos para usar con muestreadores de aire,,"0,1",80509,0 -11750,41104011,Filtros u otras partes de repuesto para muestreadores,,"0,1",80509,0 -11751,41104012,Recogedores o frascos para polvo,,"0,1",80509,0 -11752,41104013,Muestreadores de dióxido de sulfuro o de humo,,"0,1",80509,0 -11753,41104014,Aplicadores de muestras,,"0,1",80509,0 -11754,41104015,Equipo de análisis de muestras de plantas,,"0,1",80509,0 -11755,41104016,Muestreadores de polución de aire,,"0,1",80509,0 -11756,41104017,Contenedores para muestras,,"0,1",80509,0 -11757,41104018,Preparaciones para extracción de fase sólida,,"0,1",80509,0 -11758,41104019,Colectores de muestras,,"0,1",80509,0 -11759,41104020,Bandeja de flujo de elemento calcino,,"0,1",80509,0 -11760,41104021,Colector de fracciones,,"0,1",80509,0 -11761,41104022,Formador de muestras,,"0,1",80509,0 -11762,41104100,"Contenedores de recogida y transporte de especímenes, y suministros",,0,80509,0 -11763,41104101,Sobres o empaques para especímenes o láminas de muestras,,0,80509,0 -11764,41104102,Lancetas,,0,80509,0 -11765,41104103,Calentadores de pivote,,0,80509,0 -11766,41104104,Torniquetes,,0,80509,0 -11767,41104105,Bolsas para recolectar o transportar especímenes,,0,80509,0 -11768,41104106,Bandejas o accesorios de flebotomía,,0,80509,0 -11769,41104107,Tubos de recolección o contenedores de sangre al vacío,,0,80509,0 -11770,41104108,Tubos de recolección o contenedores de sangre no al vacío,,0,80509,0 -11771,41104109,Bolsas de recolección de unidades de sangre,,0,80509,0 -11772,41104110,Botellas de cultivo de sangre,,0,80509,0 -11773,41104111,Kits o contenedores de recolección de citologías,,0,80509,0 -11774,41104112,Contenedores de recolección de orina,,0,80509,0 -11775,41104114,Contenedores de recolección frepp sepp,,0,80509,0 -11776,41104115,Contenedores de recolección de filtro de suero,,0,80509,0 -11777,41104116,Contenedores de recolección o transporte de frotis,,0,80509,0 -11778,41104117,Porta especímenes,,0,80509,0 -11779,41104118,Contenedor de recolección de especímenes,,0,80509,0 -11780,41104119,Contenedores de recolección de tejido óseo,,0,80509,0 -11781,41104120,Tubos de tasa de sedimentación,,0,80509,0 -11782,41104121,Contenedores con medio químico para recolección de heces,,0,80509,0 -11783,41104122,Contenedores sin medio químico para recolección de heces,,0,80509,0 -11784,41104123,Aparatos o contenedores para recolección de esputos,,0,80509,0 -11785,41104124,Bandejas de biopsia de médula de hueso para laboratorio,,0,80509,0 -11786,41104125,Contenedor de preservación de especímenes para histología o patología,,0,80509,0 -11787,41104126,Contenedor de especímenes de histología o patología,,0,80509,0 -11788,41104127,Alimentador para laboratorio,,0,80509,0 -11789,41104128,Recolector de especímenes de cultivo sin frotis,,0,80509,0 -11790,41104129,Tarjeta de recolección de monitoreo de desórdenes metabólicos neonatales,,0,80509,0 -11791,41104200,Equipo y suministros de purificación de agua para laboratorio,,0,80509,0 -11792,41104201,Reactivos de purificación del agua,,0,80509,0 -11793,41104202,Equipos de deionización o desmineralización,,0,80509,0 -11794,41104203,Equipos de intercambio de base,,0,80509,0 -11795,41104204,Equipos de ósmosis inversa,,0,80509,0 -11796,41104205,Unidades ultra violeta de purificación de agua,,0,80509,0 -11797,41104206,Sistemas de agua ultra pura,,0,80509,0 -11798,41104207,Sistemas de análisis de agua,,0,80509,0 -11799,41104208,Deshidratadores,,0,80509,0 -11800,41104209,Desoxidantes,,0,80509,0 -11801,41104210,Disolventes,,0,80509,0 -11802,41104211,Suavizantes,,0,80509,0 -11803,41104212,Cartuchos de filtración de agua,,0,80509,0 -11804,41104213,Agua destilada o deionizada,,0,80509,0 -11805,41104300,Equipo de fermentación,,0,80509,0 -11806,41104301,Unidades de fermentación estándar,,0,80509,0 -11807,41104302,Aparatos de cultivo continuo,,0,80509,0 -11808,41104303,Jarros o accesorios anaeróbicos,,0,80509,0 -11809,41104304,Sistemas digestivos,,0,80509,0 -11810,41104305,Condensadores (espesantes),,0,80509,0 -11811,41104306,Equipos para cultivos in vitro,,0,80509,0 -11812,41104307,Equipos para fermentación microbiológica,,0,80509,0 -11813,41104308,Sistemas o suministros para cultivos ambientales anaeróbicos,,0,80509,0 -11814,41104400,Equipo de incubación de laboratorio,,0,80509,0 -11815,41104401,Incubadoras para uso general de convección de gravedad,,0,80509,0 -11816,41104402,Incubadoras de uso general de aire forzado o convección mecánica,,0,80509,0 -11817,41104403,Incubadoras para cultivo de tejidos,,0,80509,0 -11818,41104404,Incubadoras de demanda de oxígeno biológico bod enfriadas,,0,80509,0 -11819,41104405,Incubadoras de agitación,,0,80509,0 -11820,41104406,Incubadoras planas,,0,80509,0 -11821,41104407,Incubadoras de cámara única de dióxido de carbono recubierta de agua,,0,80509,0 -11822,41104408,Incubadoras de cámara dual de dióxido de carbono recubierta de agua,,0,80509,0 -11823,41104409,Incubadoras de cámara única de dióxido de carbono recubierta de agua con control de humedad,,0,80509,0 -11824,41104410,Incubadoras de cámara dual de dióxido de carbono recubierta de agua con control de humedad,,0,80509,0 -11825,41104411,Incubadoras de cámara única de dióxido de carbono de pared seca,,0,80509,0 -11826,41104412,Incubadoras de cámara dual de dióxido de carbono de pared seca,,0,80509,0 -11827,41104413,Incubadoras de cámara única de dióxido de carbono de pared seca con control de humedad,,0,80509,0 -11828,41104414,Incubadoras de cámara dual de dióxido de carbono de pared seca con control de humedad,,0,80509,0 -11829,41104415,Incubadoras de cámara única de tres gases recubierta de agua,,0,80509,0 -11830,41104416,Incubadoras de cámara dual de tres gases recubierta de agua,,0,80509,0 -11831,41104417,Incubadoras de cámara única de tres gases recubierta de agua con control de humedad,,0,80509,0 -11832,41104418,Incubadoras de cámara dual de tres gases recubierta de agua con control de humedad,,0,80509,0 -11833,41104419,Incubadoras de cámara única de tres gases de pared seca,,0,80509,0 -11834,41104420,Incubadoras de cámara dual de tres gases de pared seca,,0,80509,0 -11835,41104421,Incubadoras de cámara única de tres gases de pared seca con control de humedad,,0,80509,0 -11836,41104422,Incubadoras de cámara dual de tres gases de pared seca con control de humedad,,0,80509,0 -11837,41104423,Incubadoras refrigeradas,,0,80509,0 -11838,41104424,Accesorios para incubadoras,,0,80509,0 -11839,41104425,Incubadora de dióxido de carbono,,0,80509,0 -11840,41104426,Aparatos para preparación de medios,,0,80509,0 -11841,41104427,Incubadora de plaqueta,,0,80509,0 -11842,41104500,Estufas de laboratorio y accesorios,,0,80509,0 -11843,41104501,Hornos de convección mecánica para laboratorio,,0,80509,0 -11844,41104502,Hornos de convección de gravedad,,0,80509,0 -11845,41104503,Hornos de envejecimiento,,0,80509,0 -11846,41104504,Hornos de limpieza de espacio,Horno de laboratorio,0,80509,0 -11847,41104505,Recipientes de cuarzo para horno para laboratorio,,0,80509,0 -11848,41104506,Hornos de seguridad para laboratorios,,0,80509,0 -11849,41104507,Hornos microondas para laboratorio,,0,80509,0 -11850,41104508,Secadoras de inducción,,0,80509,0 -11851,41104509,Hornos al vacío,,0,80509,0 -11852,41104510,Hornos o gabinetes secadores,,0,80509,0 -11853,41104511,Hornos o incubadoras de hibridación,,0,80509,0 -11854,41104512,Accesorios para hornos de laboratorio,,0,80509,0 -11855,41104513,Horno para derretir magnesio,,0,80509,0 -11856,41104600,Hornos de laboratorio y accesorios,,0,80509,0 -11857,41104601,Hornos de caja para laboratorio,,0,80509,0 -11858,41104602,Hornos de caja programables,,0,80509,0 -11859,41104603,Hornos de tubo,,0,80509,0 -11860,41104604,Hornos de tubo programables,,0,80509,0 -11861,41104605,Hornos de crisol,,0,80509,0 -11862,41104606,Hornos de crisol programables,,0,80509,0 -11863,41104607,Consola de control de calderas,,0,80509,0 -11864,41104608,Consola de control de calderas programables,,0,80509,0 -11865,41104609,Hornos de seguridad para laboratorio,,0,80509,0 -11866,41104610,Crisoles para hornos de laboratorio,,0,80509,0 -11867,41104611,Aislamiento de repuesto para calderas de laboratorio,,0,80509,0 -11868,41104612,Accesorios para calderas de laboratorio,,0,80509,0 -11869,41104613,Prensa caliente para laboratorio,,0,80509,0 -11870,41104800,"Equipo y suministros de laboratorio para el vertido, la destilación, la evaporación y la extracción",,0,80509,0 -11871,41104801,Unidades de redomas o retortas,,0,80509,0 -11872,41104802,Unidades de bi destilación,Unidades de destilación Bi,0,80509,0 -11873,41104803,Evaporadores para laboratorio,Evaporador para laboratorio,0,80509,0 -11874,41104804,Evaporadores de vacío o rotatorios,Evaporador de vacío o rotatorios,0,80509,0 -11875,41104805,Evaporadores de purga de nitrógeno,Evaporador de purga de nitrógeno,0,80509,0 -11876,41104806,Equipo de extracción para laboratorios,,0,80509,0 -11877,41104807,Extractores de grasa,,0,80509,0 -11878,41104808,Extractores de fibra cruda,,0,80509,0 -11879,41104809,Unidad de análisis de sedimentación,,0,80509,0 -11880,41104810,Aparatos de fraccionamiento,,0,80509,0 -11881,41104811,Fraccionadores de densidad gradiente,,0,80509,0 -11882,41104812,Pipetas o columnas o accesorios de destilación,,0,80509,0 -11883,41104813,Componentes de reflujo,,0,80509,0 -11884,41104814,Condensadores intercambiadores de calor para laboratorio,,0,80509,0 -11885,41104815,Aparatos “kjeldahl” para la determinación de nitrógeno,,0,80509,0 -11886,41104816,Concentradores centrífugos o de vacío,,0,80509,0 -11887,41104817,Casquillos de extracción,,0,80509,0 -11888,41104818,Empaques estructurados (para uso en columnas de destilación y reactivos químicos),,0,80509,0 -11889,41104819,Cosechadora de células,Cultivo celular,0,80509,0 -11890,41104820,Aparatos de destilación fraccional para laboratorios,,0,80509,0 -11891,41104821,Concentrador de muestras estáticas,,0,80509,0 -11892,41104900,Equipo y suministros de filtrado para laboratorio,,0,80509,0 -11893,41104901,Filtros de línea para laboratorio,,0,80509,0 -11894,41104902,Equipos de filtración de gel,,0,80509,0 -11895,41104903,Equipos de ultra filtración,,0,80509,0 -11896,41104904,Filtros de células aglomeradas,,0,80509,0 -11897,41104905,Equipos de filtración de canal angosto,,0,80509,0 -11898,41104906,Equipos de filtración de ósmosis inversa,,0,80509,0 -11899,41104907,Equipos de filtración molecular,,0,80509,0 -11900,41104908,Elementos de filtración de cartucho para laboratorios,,0,80509,0 -11901,41104909,Sujeta filtros o ciclones para laboratorios,,0,80509,0 -11902,41104910,Filtros multi hoja o de prensa para laboratorios,,0,80509,0 -11903,41104911,Sistemas de filtración de aire para laboratorios,,0,80509,0 -11904,41104912,Filtro de prensas de fluido,,0,80509,0 -11905,41104913,Filtros de bio separación,,0,80509,0 -11906,41104914,Tazas o botellas de filtración,,0,80509,0 -11907,41104915,Filtros de cápsulas,,0,80509,0 -11908,41104916,Filtros centrífugos,,0,80509,0 -11909,41104917,Filtros ambientales para laboratorio,,0,80509,0 -11910,41104918,Filtros de vidrio para laboratorio,,0,80509,0 -11911,41104919,Filtros hepa para laboratorio,,0,80509,0 -11912,41104920,Filtros de hibridación,,0,80509,0 -11913,41104921,Filtros de membrana para laboratorio,,0,80509,0 -11914,41104922,Filtros de jeringa,,0,80509,0 -11915,41104923,Filtros de plato multipocillo,,0,80509,0 -11916,41104924,Filtros de microbiología,,0,80509,0 -11917,41104925,Hardware de filtración o accesorios para laboratorio,,0,80509,0 -11918,41104926,Filtro de cama de sílice,,0,80509,0 -11919,41104927,Pantallas de soporte para filtros,,0,80509,0 -11920,41104928,Receptor de botellas para laboratorio,,0,80509,0 -11921,41104929,Papeles filtrantes para laboratorio,,0,80509,0 -11922,41104930,Cernidor molecular,Tamiz Moleculares,0,80509,0 -11923,41105000,Equipo y suministros de tamizado y cribado para laboratorio,,0,80509,0 -11924,41105001,Separadores para laboratorio,,0,80509,0 -11925,41105002,Equipo de tamizaje para laboratorio,,0,80509,0 -11926,41105003,Cernidores de prueba,,0,80509,0 -11927,41105100,Bombas y conductos de laboratorio,,0,80509,0 -11928,41105101,Bombas de vacío para laboratorio,Bomba de vacío para laboratorio,0,80509,0 -11929,41105102,Bombas peristálticas,Bomba peristálticas,0,80509,0 -11930,41105103,Bombas centrífugas para laboratorio,Bomba centrífugas para laboratorio,0,80509,0 -11931,41105104,Bombas de jeringa,Bomba de jeringa,0,80509,0 -11932,41105105,Bombas de medición,Bomba de medición,0,80509,0 -11933,41105106,Bombas de cromatografía,Bomba de cromatografía,0,80509,0 -11934,41105107,Bombas de tambor para laboratorio,Bomba de tambor para laboratorio,0,80509,0 -11935,41105108,Tubos de uso general para laboratorio,Tubo de uso general para laboratorio,0,80509,0 -11936,41105109,Bombas de aleta rotativa,Bomba de aleta rotativa,0,80509,0 -11937,41105200,Equipo de teñido de portaobjetos de laboratorio y accesorios,,0,80509,0 -11938,41105201,Equipo para teñir muestras de histología o citología,,0,80509,0 -11939,41105202,Equipo para teñir muestras de hematología,,0,80509,0 -11940,41105203,Equipo para teñir muestras de microbiología,,0,80509,0 -11941,41105204,Accesorios para equipos para teñir muestras de laboratorio,,0,80509,0 -11942,41105205,Equipo para preparar micro muestras,,0,80509,0 -11943,41105206,Equipo inmuno-histo-químico automático para teñir,,0,80509,0 -11944,41105207,Accesorios para equipo inmuno-histo-químico automático para teñir,,0,80509,0 -11945,41105208,Estante y bandeja para teñir muestras para laboratorio,,0,80509,0 -11946,41105209,Set para teñir muestras manualmente multi departamental,,0,80509,0 -11947,41105300,Sistemas de electroforesis y transferencia para laboratorio y suministros,,0,80509,0 -11948,41105301,Cajas de gel,,0,80509,0 -11949,41105302,Secadores de gel,,0,80509,0 -11950,41105303,Suministros para el sistema de energía de electroforesis,,0,80509,0 -11951,41105304,Transiluminadores,,0,80509,0 -11952,41105305,Accesorios para el sistema de electroforesis,,0,80509,0 -11953,41105307,Instrumental para electroforesis capilar,,0,80509,0 -11954,41105308,Capilares o cartuchos,,0,80509,0 -11955,41105309,Kits o reactivos para electroforesis capilar,,0,80509,0 -11956,41105310,Accesorios para bloquear o transferir,,0,80509,0 -11957,41105311,Aparatos para bloquear o transferir,,0,80509,0 -11958,41105312,Peinillas o platos o espaciadores o bandejas,,0,80509,0 -11959,41105313,Casetes de detección o accesorios relacionados,,0,80509,0 -11960,41105314,Sistemas de documentación de gel,,0,80509,0 -11961,41105315,Accesorios de documentación de gel,,0,80509,0 -11962,41105316,Entrecruzadores ultravioleta,,0,80509,0 -11963,41105317,Reactivos para preparar geles de agarosa,,0,80509,0 -11964,41105318,Geles de agarosa prefabricados,,0,80509,0 -11965,41105319,Reactivos para preparar gel poliacrilamida,,0,80509,0 -11966,41105320,Geles de poliacrilamida pre fabricados,,0,80509,0 -11967,41105321,Tintura para geles ácido nucleicos,,0,80509,0 -11968,41105322,Tintura para geles poliacrilamidas,,0,80509,0 -11969,41105323,Zonas o soluciones prefabricadas para electroforesis,,0,80509,0 -11970,41105324,Sondas para ácido desoxirribonucleico dna o ácido ribonucleico rna,,0,80509,0 -11971,41105325,Placas micropocillo para hibridación de ácido desoxirribonucleico dna o ácido ribonucleico rna,,0,80509,0 -11972,41105326,Agentes reactivos o zonas para hibridación,,0,80509,0 -11973,41105327,Nucleótidos u oligómeros conjugados,,0,80509,0 -11974,41105328,Northern blot o southern blot o western blot prefabricadas,,0,80509,0 -11975,41105329,Agentes bloqueadores,,0,80509,0 -11976,41105330,Proteínas de control o lisados celulares o lisados de tejidos,,0,80509,0 -11977,41105331,Reactivos o kits o sustratos de detección quimio fluorescente de proteínas,,0,80509,0 -11978,41105332,Reactivos o kits o sustratos de detección quimio luminiscente de proteínas,,0,80509,0 -11979,41105333,Reactivos o kits o sustratos de detección cromogénica de proteínas,,0,80509,0 -11980,41105334,Marcadores de cuantificación de ácido desoxirribonucleico dna,,0,80509,0 -11981,41105335,Marcadores de tamaño o estándares de ácido desoxirribonucleico dna,,0,80509,0 -11982,41105336,Marcadores de foco isoeléctrico ief,,0,80509,0 -11983,41105337,Marcadores de electroforesis de proteína,,0,80509,0 -11984,41105338,Marcadores o estándares de ácido ribonucleico rna,,0,80509,0 -11985,41105339,Membranas de transferencia por adsorción,,0,80509,0 -11986,41105340,Sistema de electroforesis,,0,80509,0 -11987,41105341,Película de auto radiografía,,0,80509,0 -11988,41105500,"Kits y componentes de extracción, purificación y cuantificación de ácidos nucleicos",,0,80509,0 -11989,41105501,Kits de extracción de gel o limpiadores de ácido desoxirribonucleico dna,,0,80509,0 -11990,41105502,Kits para extrae ácido desoxirribonucleico dna de alimentos,,0,80509,0 -11991,41105503,Sistemas de electro elución,,0,80509,0 -11992,41105504,Kits de purificación de ácido desoxirribonucleico dna genómico,,0,80509,0 -11993,41105505,Sistemas de selección de alto rendimiento hts en purificación de ácido nucleico,,0,80509,0 -11994,41105506,Kits para purificación de ácido ribonucleico mensajero mrna,,0,80509,0 -11995,41105507,Glóbulos magnéticos para aislar ácido nucleico,,0,80509,0 -11996,41105508,Co-precipitantes de ácidos nucleicos,,0,80509,0 -11997,41105509,Kits de cuantificación de ácidos nucleicos,,0,80509,0 -11998,41105510,Kits de purificación de fagos de ácido desoxirribonucleico dna,,0,80509,0 -11999,41105511,Kits para extracción de plásmidos de ácido desoxirribonucleico dna de la levadura,,0,80509,0 -12000,41105512,Kit de purificación de plásmidos o cósmidos o cromosomas bacterianos artificiales bac,,0,80509,0 -12001,41105513,Kits de purificación de ácido nucleico etiquetado,,0,80509,0 -12002,41105514,Reactivos para extracción o precipitación o re suspensión de ácido nucleico,,0,80509,0 -12003,41105515,Materiales de estabilización o limpieza de ácido ribonucleico,,0,80509,0 -12004,41105516,Kits de extracción de gel de ácido ribonucleico rna,,0,80509,0 -12005,41105517,Kits para extracción de ácido nucleico de células o tejidos de plantas,,0,80509,0 -12006,41105518,Kits de purificación de ácido ribonucleico rna total,,0,80509,0 -12007,41105519,Kits de purificación de ácido desoxirribonucleico dna viral,,0,80509,0 -12008,41105520,Kits de purificación de ácido ribonucleico viral,,0,80509,0 -12009,41105521,Sistema de detección de ácido desoxirribonucleico dna,,0,80509,0 -12010,41105900,Bibliotecas y material relacionado,,0,80509,0 -12011,41105901,Tejidos animales o fluidos corporales,,0,80509,0 -12012,41105902,Muestrarios de ácido desoxirribonucleico dna complementario,,0,80509,0 -12013,41105903,Kits de síntesis de ácido desoxirribonucleico dna complementario,,0,80509,0 -12014,41105904,Muestrarios genómicos,,0,80509,0 -12015,41105905,Kits de construcción de muestrarios,,0,80509,0 -12016,41105906,Muestrarios de despliegue de proteínas o péptidos,,0,80509,0 -12017,41105907,Dos muestrarios o sistemas de dos híbridos,,0,80509,0 -12018,41105908,Kits de empaques virales,,0,80509,0 -12019,41105909,Sintetizador de péptidos,,0,80509,0 -12020,41106100,Kits de análisis de ácido desoxirribonucleico (ADN),,0,80509,0 -12021,41106101,Kits citogenéticos,,0,80509,0 -12022,41106102,Kits de kits de exhibición o sustracción diferenciales,,0,80509,0 -12023,41106103,Kits de tipificación de ácido desoxirribonucleico dna,,0,80509,0 -12024,41106104,Pruebas de protección nucleasa,,0,80509,0 -12025,41106200,"Medios, kits y equipo de propagación y transformación de microorganismos",,0,80509,0 -12026,41106201,Antimicóticos,,0,80509,0 -12027,41106202,Celdas competentes de bacterias,,0,80509,0 -12028,41106203,Kits de transformación de bacterias,,0,80509,0 -12029,41106204,Medio agar embotellado o en bandas para bacterias,,0,80509,0 -12030,41106205,Mezclas de suplemento brent para levadura,,0,80509,0 -12031,41106206,Mezclas de suplemento completo para levadura,,0,80509,0 -12032,41106207,Medio discoide dictiostelium,Medio discoide dictyostelium,0,80509,0 -12033,41106208,Cubetas de electroporación,,0,80509,0 -12034,41106209,Mezclas de suplemento hollenberg para levaduras,,0,80509,0 -12035,41106210,Medios o suplementos para schizosaccharomyces pombe (levadura de fisión),,0,80509,0 -12036,41106211,Ingredientes o aditivos de medio para schizosaccharomyces pombe,Ingredientes o aditivos de medio para levadura de fisión,0,80509,0 -12037,41106212,Ingredientes o aditivos de medio para bacterias,,0,80509,0 -12038,41106213,Medio seco premezclado,,0,80509,0 -12039,41106214,Reactivos para preparar bacterias competentes,,0,80509,0 -12040,41106215,Reactivos para preparar levadura competente,,0,80509,0 -12041,41106216,Medios ricos para levadura,,0,80509,0 -12042,41106217,Platos especiales para bacterias,,0,80509,0 -12043,41106218,Medio seco premezclado especializado,,0,80509,0 -12044,41106219,Mezclas suplementarias completas sintéticas para levadura,,0,80509,0 -12045,41106220,Medio sintético para levadura,,0,80509,0 -12046,41106221,Células competentes para levadura,,0,80509,0 -12047,41106222,Kits de transformación de levadura,,0,80509,0 -12048,41106223,Bases de nitrógeno para levadura ynb o variantes de bases de nitrógeno para levaduras ynb,,0,80509,0 -12049,41106300,Productos de reacción en cadena de la polimerasa (PCR) y reacción en cadena de transcriptasa inversa de la polimerasa (RT PCR),,0,80509,0 -12050,41106301,Trifosfatos deoxinucleotidos dntps,,0,80509,0 -12051,41106302,Kits de reacción en cadena polimerasa específica para genes,,0,80509,0 -12052,41106303,Kits de purificación de reacción en cadena de polimerasa,,0,80509,0 -12053,41106304,Kits para cuantificación de ácido ribonucleico mensajero mrna mediante reacción en cadena de polimerasa pcr,,0,80509,0 -12054,41106305,Nucleótidos,,0,80509,0 -12055,41106306,Tapones para reacción en cadena de polimerasa pcr,,0,80509,0 -12056,41106307,Productos para optimizar la reacción en cadena de polimerasa pcr,,0,80509,0 -12057,41106308,Base para reacción en cadena de polimerasa pcr o reacción en cadena de polimerasa transcripta inversa rt pcr,,0,80509,0 -12058,41106309,Ácido desoxirribonucleico complementario cdna pre-preparado,,0,80509,0 -12059,41106310,Ácido desoxirribonucleico adn genómico purificado,,0,80509,0 -12060,41106311,Ácidos ribonucleicos rna purificados,,0,80509,0 -12061,41106312,Productos de tecnología de rápida amplificación o terminaciones de ácido desoxirribonucleico complementario race,,0,80509,0 -12062,41106313,Kits de reacción en cadena de polimerasa transcriptasa inversa rt pcr,,0,80509,0 -12063,41106314,Kits o polimerasas de ácido desoxirribonucleico dna termoestable,,0,80509,0 -12064,41106400,"Iniciadores, enlaces y adaptadores",,0,80509,0 -12065,41106401,Adaptadores o enlazadores,,0,80509,0 -12066,41106402,Bases misceláneas,,0,80509,0 -12067,41106403,Bases de secuenciación,,0,80509,0 -12068,41106500,Productos de expresión proteínica,,0,80509,0 -12069,41106501,Kits de expresión bacteriana,,0,80509,0 -12070,41106502,Reactivos de transfección eucariótica,,0,80509,0 -12071,41106503,Inductores o reguladores,,0,80509,0 -12072,41106504,Células de insectos,,0,80509,0 -12073,41106505,Kits de expresión de insectos,,0,80509,0 -12074,41106506,Medio para insectos,,0,80509,0 -12075,41106507,Reactivos o suplementos para medio para insectos,,0,80509,0 -12076,41106508,Kits de expresión celular mamífera,,0,80509,0 -12077,41106509,Células mamíferas,,0,80509,0 -12078,41106510,Kits para extracción de proteína de células o tejidos de mamíferos,,0,80509,0 -12079,41106511,Kits para extracción de proteína de bacterias,,0,80509,0 -12080,41106512,Kits para extracción de proteína de levadura,,0,80509,0 -12081,41106513,Reportero de ensayo genético,,0,80509,0 -12082,41106514,Líneas celulares mamíferas estables,,0,80509,0 -12083,41106515,Kits de expresión de levadura,,0,80509,0 -12084,41106516,Consumibles de expresión de enzimas,,0,80509,0 -12085,41106700,Equipos y accesorios de laboratorio botánico,,0,80509,0 -12086,41106701,Medidor de área de hojas,,0,80509,0 -12087,41106702,Aparato de medición de fotosíntesis,,0,80509,0 -12088,41106703,Instrumento de medición de crecimiento de las plantas o auxanómetro,,0,80509,0 -12089,41106704,Instrumento de medición de la clorofila,,0,80509,0 -12090,41106705,Fitotrón,,0,80509,0 -12091,41106706,Minirizotrón,,0,80509,0 -12092,41106707,Dispositivo de inspección de la nutrición de la vegetación,,0,80509,0 -12093,41106708,Set de dispositivos para recolección de plantas,,0,80509,0 -12094,41111500,Instrumentos de medición del peso,,0,80509,0 -12095,41111501,Balanzas de carga superior electrónicos,,0,80509,0 -12096,41111502,Balanzas de laboratorio,,0,80509,0 -12097,41111503,Balanzas mecánicas,,0,80509,0 -12098,41111504,Balanzas de resorte tensor,,0,80509,0 -12099,41111505,Pesas de calibración o sets de pesas,,0,80509,0 -12100,41111506,Básculas para pesar animales,,0,80509,0 -12101,41111507,Básculas de mesa,,0,80509,0 -12102,41111508,Básculas para medir el peso corporal,,0,80509,0 -12103,41111509,Básculas de piso o de plataforma,,0,80509,0 -12104,41111510,Básculas postales,,0,80509,0 -12105,41111511,Básculas de camión o riel,,0,80509,0 -12106,41111512,Balanzas de triple haz,,0,80509,0 -12107,41111513,Balanzas de humedad,,0,80509,0 -12108,41111515,Contenedores o tazones o barcos o papeles para pesar,,0,80509,0 -12109,41111516,Accesorios para instrumentos de medición de peso,,0,80509,0 -12110,41111517,Balanzas analíticas,,0,80509,0 -12111,41111518,Básculas de carga axial,,0,80509,0 -12112,41111519,Básculas de grúa,,0,80509,0 -12113,41111520,Báscula de peso de correa transportadora,,0,80509,0 -12114,41111521,Termogravímeto,,0,80509,0 -12115,41111522,Básculas de tolva,,0,80509,0 -12116,41111523,Prototipo de peso,,0,80509,0 -12117,41111524,Báscula indicadora de precio,,0,80509,0 -12118,41111525,Báscula empacadora automática,,0,80509,0 -12119,41111526,Báscula de selección automática,,0,80509,0 -12120,41111600,"Instrumentos de medida de longitud, espesor o distancia",,0,80509,0 -12121,41111601,Micrómetros,,0,80509,0 -12122,41111602,Podómetros,,0,80509,0 -12123,41111603,Telémetros o buscadores de rango,,0,80509,0 -12124,41111604,Reglas,,0,80509,0 -12125,41111605,Medidoras de tensión,,0,80509,0 -12126,41111606,Telurómetros,,0,80509,0 -12127,41111607,Medidores o contadores de roscas,,0,80509,0 -12128,41111613,Metros de distancia,,0,80509,0 -12129,41111614,Medidores de altura,,0,80509,0 -12130,41111615,Sistemas láser de medición,,0,80509,0 -12131,41111616,Ruedas medidoras para distancias,,0,80509,0 -12132,41111617,Medidor de espesores,,0,80509,0 -12133,41111618,Set de bloques de patrón longitudinal,,0,80509,0 -12134,41111619,Calibrador go nogo,,0,80509,0 -12135,41111620,Cuña de etalón,,0,80509,0 -12136,41111621,Calibradores,,0,80509,0 -12137,41111622,Calibradores micrómetros,,0,80509,0 -12138,41111623,Dispositivos de medición de grosor,,0,80509,0 -12139,41111624,Medidor de orificios o alexómetro,,0,80509,0 -12140,41111625,Curvímetro,,0,80509,0 -12141,41111626,Bloque de medición,,0,80509,0 -12142,41111627,Bloque en v,,0,80509,0 -12143,41111628,Medidor de alambre,,0,80509,0 -12144,41111629,Sensor de posición de cable o alambre de extensión linear,,0,80509,0 -12145,41111630,Indicador de dial o medidor de dial,,0,80509,0 -12146,41111631,Medidor de radio,,0,80509,0 -12147,41111632,Medidor de cilindros,,0,80509,0 -12148,41111633,Micrómetro de aire,,0,80509,0 -12149,41111634,Micrómetro eléctrico,,0,80509,0 -12150,41111635,Comparador de bloques de patrón longitudinal,,0,80509,0 -12151,41111636,Equipo de medición de diámetro de cilindro,,0,80509,0 -12152,41111637,Taza probadora cónica,,0,80509,0 -12153,41111638,Probador de circunferencias de cigarrillos,,0,80509,0 -12154,41111639,Medidor de formas y profundidad de la cabeza de los tornillos,,0,80509,0 -12155,41111640,Medidor de soldadura,,0,80509,0 -12156,41111641,Medidor de paso de rosca,,0,80509,0 -12157,41111642,Medidor de espacio entre uniones de rieles,,0,80509,0 -12158,41111643,Calibrador de alambre,,0,80509,0 -12159,41111644,Medidor de pernos,,0,80509,0 -12160,41111645,Barra sinusoidal,,0,80509,0 -12161,41111646,Medidor telescópico,,0,80509,0 -12162,41111647,Medidor de orificios,,0,80509,0 -12163,41111648,Medidor cónico,,0,80509,0 -12164,41111649,Medidor de profundidad de llantas,,0,80509,0 -12165,41111650,Medidor de perforación,,0,80509,0 -12166,41111651,Catetómetro,,0,80509,0 -12167,41111700,Instrumentos y accesorios de visión y observación,,0,80509,0 -12168,41111701,Microscopios iónicos,,0,80509,0 -12169,41111702,Microscopios monoculares,,0,80509,0 -12170,41111703,Microscopios de disección de luz o de estéreo,,0,80509,0 -12171,41111704,Iluminadores para microscopios,,0,80509,0 -12172,41111705,Objetivos para microscopios,Lentes objetivos para microscopios,0,80509,0 -12173,41111706,Elementos de sujeción de fotos para microscopios,,0,80509,0 -12174,41111707,Proyectores de perfil,,0,80509,0 -12175,41111708,Elementos de sujeción de video para microscopios,,0,80509,0 -12176,41111709,Microscopios compuestos de luz binocular,,0,80509,0 -12177,41111710,Combinación de microscopios de luz y electrones,,0,80509,0 -12178,41111711,Microscopios de electrones,,0,80509,0 -12179,41111712,Microscopios invertidos,,0,80509,0 -12180,41111713,Magnificadores,,0,80509,0 -12181,41111714,Lupas,,0,80509,0 -12182,41111715,Telescopios,,0,80509,0 -12183,41111716,Equipos de inspección boroscópica,,0,80509,0 -12184,41111717,Binoculares,,0,80509,0 -12185,41111718,Microscopios metalúrgicos,,0,80509,0 -12186,41111719,Microscopios de campo oscuro,,0,80509,0 -12187,41111720,Microscopios de escáner de electrones,,0,80509,0 -12188,41111721,Microscopios de transmisión de electrones,,0,80509,0 -12189,41111722,Microscopios fluorescentes,,0,80509,0 -12190,41111723,"Microscopios de escáner de luz, disco giratorio o escáner de láser",,0,80509,0 -12191,41111724,Microscopios de escáner de sonda,,0,80509,0 -12192,41111725,Microscopios de polarización,,0,80509,0 -12193,41111726,Microscopios acústicos,,0,80509,0 -12194,41111727,Microscopios de proyección,,0,80509,0 -12195,41111728,Microscopios de campo ancho,,0,80509,0 -12196,41111729,Microscopios oculares,,0,80509,0 -12197,41111730,Condensadores de microscopios,,0,80509,0 -12198,41111731,Colectores de microscopios,,0,80509,0 -12199,41111733,Tubos de microscopios,,0,80509,0 -12200,41111734,Microscopios de fases,,0,80509,0 -12201,41111735,Microscopios de fases automáticos,,0,80509,0 -12202,41111736,Cubiertas para microscopios,,0,80509,0 -12203,41111737,Videoscopios,,0,80509,0 -12204,41111738,Fibroscopios,,0,80509,0 -12205,41111739,Bombillos de repuesto para microscopios de laboratorio,,0,80509,0 -12206,41111740,Sistema de inspección óptica automática,,0,80509,0 -12207,41111741,Equipo de microscopio de contraste de interferencia diferencial,,0,80509,0 -12208,41111742,Periscopio,,0,80509,0 -12209,41111743,Autocolimador,,0,80509,0 -12210,41111744,Micrómetro microscópico,,0,80509,0 -12211,41111745,Micromanipulador,,0,80509,0 -12212,41111746,Palanca óptica,,0,80509,0 -12213,41111747,Fibroscopio industrial,,0,80509,0 -12214,41111748,Microscopio de imagen multimedia,,0,80509,0 -12215,41111749,Microscopio de contraste de fase,,0,80509,0 -12216,41111800,Equipo de examen no destructivo,,0,80509,0 -12217,41111801,Equipo de examen de corriente parásita,,0,80509,0 -12218,41111802,Equipo de examen de penetración líquida,,0,80509,0 -12219,41111803,Equipo de examen de partículas magnéticas,,0,80509,0 -12220,41111804,Equipo de examen ultrasónico,,0,80509,0 -12221,41111805,Equipo de examen de radiografía co 60,,0,80509,0 -12222,41111806,Equipo de examen de radiografía cs 137,,0,80509,0 -12223,41111807,Equipo de examen de radiografía ir 192,,0,80509,0 -12224,41111808,Equipo de examen de radiografía de rayos x,,0,80509,0 -12225,41111809,Equipo de prueba de goteo,,0,80509,0 -12226,41111810,Estación de equipos de pruebas de calor,,0,80509,0 -12227,41111811,Estación de equipos de pruebas de frío,,0,80509,0 -12228,41111812,Equipo de examen de radiografía de rayos gamma,,0,80509,0 -12229,41111813,Iluminador para ver radiografías para uso industrial,,0,80509,0 -12230,41111814,Equipo de examen de radiografía de neutrones,,0,80509,0 -12231,41111815,Detector de metales de refuerzo,,0,80509,0 -12232,41111816,Instrumento de medición de desplazamiento,,0,80509,0 -12233,41111817,Sistema de medición del sabor del arroz,,0,80509,0 -12234,41111818,Reactivo de examen no destructivo,,0,80509,0 -12235,41111819,Probador de cable de acero,,0,80509,0 -12236,41111820,Detector de fluorescencia,,0,80509,0 -12237,41111900,Instrumentos indicadores y de registro,,0,80509,0 -12238,41111901,Contadores,Contadores mecánicos,0,80509,0 -12239,41111902,Contadores electrónicos,Contadores electrónicos,0,80509,0 -12240,41111903,Detectores de metales,Detectores de metales,0,80509,0 -12241,41111904,Columnas electrónicas,Columnas electrónicas de medición,0,80509,0 -12242,41111905,Sondas de medición electrónicas,Sondas o puntas de prueba electrónicas,0,80509,0 -12243,41111906,Grabadoras de tablas,Registrador de gráficos,0,80509,0 -12244,41111907,Grabadoras de lectura digital,,0,80509,0 -12245,41111908,Grabadoras gráficas,,0,80509,0 -12246,41111909,Grabadoras de cintas magnéticas,,0,80509,0 -12247,41111910,Grabadoras multifunción,,0,80509,0 -12248,41111911,Grabadoras oscilo gráficas,,0,80509,0 -12249,41111912,Grabadoras sicológicas,,0,80509,0 -12250,41111913,Grabadoras de punta de trazado,,0,80509,0 -12251,41111914,Servo grabadoras,,0,80509,0 -12252,41111915,Sensores bi metálicos,Sensores bimetálicos,0,80509,0 -12253,41111916,Sensores de no contacto,Sensores sin contacto,0,80509,0 -12254,41111917,Probadores digitales,,0,80509,0 -12255,41111918,Instruments giroscópicos,,0,80509,0 -12256,41111919,Aparatos de detección para objetos no metálicos,,0,80509,0 -12257,41111920,Máquinas de medición de coordenadas cmm,Máquina de medición de coordenadas (CMM),0,80509,0 -12258,41111921,Sensores de velocidad,,0,80509,0 -12259,41111922,Sensores de fallas de lámparas,,0,80509,0 -12260,41111923,Sensor de pistones pre-arranque,,0,80509,0 -12261,41111924,Sensores de oxígeno,,0,80509,0 -12262,41111926,Sensores de proximidad,,0,80509,0 -12263,41111927,Sensores de presión,,0,80509,0 -12264,41111928,Sensores de corriente,,0,80509,0 -12265,41111929,Detectores de radiación,,0,80509,0 -12266,41111930,Sensores de corriente eléctrica,,0,80509,0 -12267,41111931,Sensores de flujo,,0,80509,0 -12268,41111932,Detectores de escape de líquidos,,0,80509,0 -12269,41111933,Sensores de carga eléctrica,,0,80509,0 -12270,41111934,Sensores de fuerza o de torsión,,0,80509,0 -12271,41111935,Sensores de inclinación,,0,80509,0 -12272,41111936,Sensores de imagen de semiconductor de óxido de metal complementario cmos,Sensor de imagen CMOS,0,80509,0 -12273,41111937,Sensores giratorios de posición,,0,80509,0 -12274,41111938,Sensores o transmisores de nivel,,0,80509,0 -12275,41111939,Sensores acústicos,,0,80509,0 -12276,41111940,Sensores de color,,0,80509,0 -12277,41111941,Sensores olfativos,,0,80509,0 -12278,41111942,Sensores de opacidad o polvo o visibilidad,,0,80509,0 -12279,41111943,Sensores de resistencia o conductividad eléctrica,,0,80509,0 -12280,41111944,Sensores de admisión eléctricos,,0,80509,0 -12281,41111945,Sensores de posición linear,,0,80509,0 -12282,41111946,Sensores de inductancia eléctrica,,0,80509,0 -12283,41111947,Esferos de registro de tablas,Plumas grabadoras de gráficos,0,80509,0 -12284,41111948,Contadores de células diferenciales hematológicas manuales o electrónicos,Contadores diferenciales de células sanguíneas (manuales o electrónicos),0,80509,0 -12285,41111949,Cronómetro,,0,80509,0 -12286,41111950,Indicador de nivel,,0,80509,0 -12287,41111951,Indicador de humedad,,0,80509,0 -12288,41111952,Sensor de sulfuro de hidrógeno,,0,80509,0 -12289,41111953,Sensor de armado de inflador de restricción (bolsa de aire) suplementario,Sensor del sistema suplementario de restricción inflable (SIR),0,80509,0 -12290,41111954,Sensor líquido,Sensor de líquidos,0,80509,0 -12291,41111955,Sensor solar,,0,80509,0 -12292,41111956,Sensor de temperatura infrarrojo,,0,80509,0 -12293,41111957,Sensor de presión de aceite,,0,80509,0 -12294,41111958,Sensor de presión ambiental múltiple,,0,80509,0 -12295,41111959,Sensor de presión de combustible,,0,80509,0 -12296,41111960,Sensor ultrasónico,,0,80509,0 -12297,41111961,Sensor de ocupación,Sensores de movimiento,0,80509,0 -12298,41111962,Sensor de desgaste de frenos,,0,80509,0 -12299,41111963,Sensor de nivel de combustible,,0,80509,0 -12300,41111964,Sensor de tensión del cinturón de seguridad,,0,80509,0 -12301,41111965,Sensor de agua,,0,80509,0 -12302,41111966,Sensor de humedad,,0,80509,0 -12303,41111967,Sensor de cabeceo largo lateral,,0,80509,0 -12304,41111968,Sensor de tasa angular,,0,80509,0 -12305,41111969,Sensor de control de voltaje,,0,80509,0 -12306,41111970,Sensor de temperatura,,0,80509,0 -12307,41111971,Contador binario,,0,80509,0 -12308,41111972,Sensor de inflador de restricción (bolsa de aire) suplementario sir o de ensamblaje de bobina de la bolsa de aire,,0,80509,0 -12309,41111973,Contador de partículas,,0,80509,0 -12310,41111974,Probador sensor de calor,Sensor probador de calor,0,80509,0 -12311,41111975,Monitor de campo magnético,,0,80509,0 -12312,41111976,Sistema de chequeo métrico de agua,,0,80509,0 -12313,41111977,Contador de iones,,0,80509,0 -12314,41111978,Lector de tubo microhematocrito manual,,0,80509,0 -12315,41112100,Transductores,,0,80509,0 -12316,41112101,Cristales piezoeléctricos,,0,80509,0 -12317,41112103,Sensores de fibra,,0,80509,0 -12318,41112104,Transductores de audio,Transductor electroacústico,0,80509,0 -12319,41112105,Transmisores de temperatura,,0,80509,0 -12320,41112106,Transmisores de humedad,,0,80509,0 -12321,41112107,Transductores electro neumáticos,,0,80509,0 -12322,41112108,Celdas de carga,,0,80509,0 -12323,41112109,Transductor de corriente,,0,80509,0 -12324,41112110,Transductor de presión,,0,80509,0 -12325,41112111,Transductor de desplazamiento,,0,80509,0 -12326,41112112,Transductor de energía eléctrica,,0,80509,0 -12327,41112113,Transductor de energía mecánica,,0,80509,0 -12328,41112114,Transmisor termohigrómetro,,0,80509,0 -12329,41112200,Instrumentos de medida de temperatura y calor,,0,80509,0 -12330,41112201,Calorímetros,,0,80509,0 -12331,41112202,Equipos de rastreo de calor,,0,80509,0 -12332,41112203,Grabadoras de punto de fusión,,0,80509,0 -12333,41112204,Pirómetros,,0,80509,0 -12334,41112205,Reguladores de temperatura,,0,80509,0 -12335,41112206,Termo cúpulas,Sensor para medir la temperatura,0,80509,0 -12336,41112207,Termógrafos,,0,80509,0 -12337,41112209,Termostatos,,0,80509,0 -12338,41112210,Termómetros de lectura remota,,0,80509,0 -12339,41112211,Termómetros de resistencia,,0,80509,0 -12340,41112212,Termómetros de superficie,,0,80509,0 -12341,41112213,Termómetros de mano,,0,80509,0 -12342,41112214,Controles de temperatura criogénicos,,0,80509,0 -12343,41112215,Controladores de temperatura humidificadores,Humidificadores para el control de temperatura,0,80509,0 -12344,41112216,Termopozos,,0,80509,0 -12345,41112217,Termo cabezas,Sensor de temperatura en cabeza de cilindro (Thermoheads),0,80509,0 -12346,41112219,Sondas termopares,,0,80509,0 -12347,41112220,Termómetros de refrigerador o congelador de laboratorio,,0,80509,0 -12348,41112221,Termómetros de incubadora de laboratorio,,0,80509,0 -12349,41112222,Indicador de temperatura,,0,80509,0 -12350,41112223,Indicador compuesto,,0,80509,0 -12351,41112224,Termómetro infrarrojo,,0,80509,0 -12352,41112225,Detector de temperatura de resistencia,,0,80509,0 -12353,41112226,Sensor termopar de baja temperatura,,0,80509,0 -12354,41112227,Sensor termopar de alta temperatura,,0,80509,0 -12355,41112228,Analizador de eficiencia de combustión,,0,80509,0 -12356,41112229,Medidor de calor,,0,80509,0 -12357,41112230,Calibrador pirómetro,Calibrador de pirómetro,0,80509,0 -12358,41112231,Probador de nube de aceite y de punto de vertido,,0,80509,0 -12359,41112232,Instrumento medidor de punto de congelación,,0,80509,0 -12360,41112233,Instrumento medidor de punto de derretimiento,,0,80509,0 -12361,41112234,Flujómetro de calor,,0,80509,0 -12362,41112235,Instrumento para medir calor específico,,0,80509,0 -12363,41112236,Probador de punto de ablandamiento del asfalto,,0,80509,0 -12364,41112237,Termómetro de punto de rocío,,0,80509,0 -12365,41112238,Probador de punto de caída de temperatura,,0,80509,0 -12366,41112239,Termómetro de vidrio,,0,80509,0 -12367,41112240,Banda de verificación de temperatura de la unidad de sangre,,0,80509,0 -12368,41112300,Instrumentos de medición de la humedad,,0,80509,0 -12369,41112301,Higrómetros,,0,80509,0 -12370,41112302,Sicrómetros,,0,80509,0 -12371,41112303,Probadores de humedad de temperatura,,0,80509,0 -12372,41112304,Medidores de rocío,,0,80509,0 -12373,41112305,Controlador de humedad,,0,80509,0 -12374,41112306,Calibrador de humedad,,0,80509,0 -12375,41112307,Probador de retención de agua por el cemento,,0,80509,0 -12376,41112400,Instrumentos de medida y control de la presión,,0,80509,0 -12377,41112401,Indicadores de profundidad,,0,80509,0 -12378,41112402,Manostatos,,0,80509,0 -12379,41112403,Indicadores de presión,,0,80509,0 -12380,41112404,Reguladores de presión,,0,80509,0 -12381,41112405,Grabadoras de presión o de vacío,,0,80509,0 -12382,41112406,Indicadores de vacío,,0,80509,0 -12383,41112407,Instrumentos o controles de nivel de líquido,,0,80509,0 -12384,41112408,Intensificadores de presión,,0,80509,0 -12385,41112409,Escáneres de presión,,0,80509,0 -12386,41112410,Transmisores de presión,,0,80509,0 -12387,41112411,Controladores de presión,,0,80509,0 -12388,41112412,Indicador de presión,,0,80509,0 -12389,41112413,Indicador de presión diferencial,,0,80509,0 -12390,41112414,Calibrador de presión,,0,80509,0 -12391,41112415,Aparato de warburg,Manómetro de Warburg,0,80509,0 -12392,41112416,Probador de presión de destrucción o rompimiento,,0,80509,0 -12393,41112417,Instrumento medidor de la presión de vapor,Isoteniscopio,0,80509,0 -12394,41112418,Probador de presión capilar,,0,80509,0 -12395,41112419,Medidor de resistencia de inhalación de los filtros de cigarrillos,,0,80509,0 -12396,41112420,Indicador de caída de presión,,0,80509,0 -12397,41112421,Probador de dureza de las frutas,,0,80509,0 -12398,41112422,Probador de presión hidráulica,,0,80509,0 -12399,41112423,Altímetro de presión,,0,80509,0 -12400,41112500,Instrumentos de medición y observación del caudal de fluidos,,0,80509,0 -12401,41112501,Flujómetros,,0,80509,0 -12402,41112502,Reómetros,,0,80509,0 -12403,41112503,Rotámetros,Rotámetros o flujometros,0,80509,0 -12404,41112504,Medidores de agua,Medidor de agua (Hidrómetro),0,80509,0 -12405,41112505,Repuestos para medidores de agua,,0,80509,0 -12406,41112506,Venturis,Medidor de Flujo Venturi,0,80509,0 -12407,41112508,Indicadores de gas,,0,80509,0 -12408,41112509,Monitores de temperatura y velocidad del aire,,0,80509,0 -12409,41112510,Indicadores visuales de flujo,,0,80509,0 -12410,41112511,Ventanas para visualizar el flujo,,0,80509,0 -12411,41112512,Computadores o totalizadores de flujo,Computadoras o totalizadores de flujo,0,80509,0 -12412,41112513,Plato de orificio,Plato o placa de orificio,0,80509,0 -12413,41112514,Indicadores de aceite,,0,80509,0 -12414,41112516,Transmisores de flujo,,0,80509,0 -12415,41112517,Flujómetros ópticos y accesorios,,0,80509,0 -12416,41112518,Equipo para probar la eficiencia de las bombas,,0,80509,0 -12417,41112519,Indicador de piloto,Manómetros de tubo de Pitot,0,80509,0 -12418,41112520,Medidor de flujo de bombas contra incendios,,0,80509,0 -12419,41112521,Columna de calibración,,0,80509,0 -12420,41112800,Equipo e instrumentos relacionados con el transporte,,0,80509,0 -12421,41112801,Velocímetros,,0,80509,0 -12422,41112802,Tacómetros,,0,80509,0 -12423,41112803,Discos para tacómetros,,0,80509,0 -12424,41112804,Equipo de medición de la abrasión de los rieles,,0,80509,0 -12425,41112805,Instrumento de medición del hundimiento de los rieles,,0,80509,0 -12426,41112806,Instrumento de medición de la separación entre las uniones de los rieles,,0,80509,0 -12427,41112807,Instrumento de medición de la fuerza de unión de los travesaños de los rieles,,0,80509,0 -12428,41112808,Probador de los vagones eléctricos,,0,80509,0 -12429,41112809,Probador de taxímetros,,0,80509,0 -12430,41112900,Equipo e instrumentos de navegación,,0,80509,0 -12431,41112901,Compases para encontrar la dirección,Brújulas,0,80509,0 -12432,41112902,Instrumentos de navegación por radio,,0,80509,0 -12433,41112903,Sextantes,Sextante astronómico,0,80509,0 -12434,41112904,Dispositivos de control complejo,,0,80509,0 -12435,41112905,Faro infrarrojo,,0,80509,0 -12436,41112906,Faro de radio,,0,80509,0 -12437,41112907,Boya de radio,,0,80509,0 -12438,41113000,Instrumentos de suministros evaluación química,,0,80509,0 -12439,41113001,Controladores de analizador digital,,0,80509,0 -12440,41113002,Analizadores de bioluminiscencia o quimioluminiscencia,,0,80509,0 -12441,41113003,Analizadores de electro gravimetría,,0,80509,0 -12442,41113004,Analizadores de ionización de la llama,,0,80509,0 -12443,41113005,Analizadores de iones,,0,80509,0 -12444,41113006,Analizadores de radiometría,,0,80509,0 -12445,41113007,Analizadores de acceso aleatorio,,0,80509,0 -12446,41113008,Analizadores de cintigrafía,,0,80509,0 -12447,41113009,Analizadores térmicos diferenciales,,0,80509,0 -12448,41113010,Analizadores térmicos de gravimetría,,0,80509,0 -12449,41113023,Equipo de partición de geles,,0,80509,0 -12450,41113024,Hidrómetros,,0,80509,0 -12451,41113025,Monocromadores,,0,80509,0 -12452,41113026,Nefelómetros,,0,80509,0 -12453,41113027,Osmómetros,,0,80509,0 -12454,41113029,Polarógrafos,,0,80509,0 -12455,41113030,Escáner radiocromatográfico,,0,80509,0 -12456,41113031,Sacarímetros,,0,80509,0 -12457,41113033,Volúmetros,,0,80509,0 -12458,41113034,Tiras o papeles para prueba de ph,,0,80509,0 -12459,41113035,Tiras o papeles para pruebas químicas,,0,80509,0 -12460,41113036,Micro placas,,0,80509,0 -12461,41113037,Lectores de micro placas,,0,80509,0 -12462,41113038,Alcoholímetro,,0,80509,0 -12463,41113039,Accesorios para osmómetros,,0,80509,0 -12464,41113040,Contador de colonias (de microorganismos),,0,80509,0 -12465,41113041,Analizador elemental,,0,80509,0 -12466,41113042,Analizador de leche,,0,80509,0 -12467,41113043,Sistema de determinación de la fibra de la dieta,,0,80509,0 -12468,41113044,Hidrómetro de alcohol,,0,80509,0 -12469,41113045,Probador de película de aceite,,0,80509,0 -12470,41113046,Probador de adhesión,,0,80509,0 -12471,41113047,Probador de características de la espuma del aceite,,0,80509,0 -12472,41113048,Probador de resistencia del color,,0,80509,0 -12473,41113049,Analizador de combustión,,0,80509,0 -12474,41113050,Detector de arsénico,,0,80509,0 -12475,41113051,Probador de reducción de oxidación,,0,80509,0 -12476,41113052,Probador de tiza o pigmento que suelta la pintura seca,,0,80509,0 -12477,41113053,Probador de prevención de óxido,,0,80509,0 -12478,41113054,Probador de demulsibilidad,Probador de separabilidad del agua,0,80509,0 -12479,41113055,Probador de goma en el combustible,,0,80509,0 -12480,41113056,Cable de experimentación de llama,,0,80509,0 -12481,41113057,Densitómetro de lodo,,0,80509,0 -12482,41113100,Analizadores y monitores de gas,,0,80509,0 -12483,41113101,Analizadores de emisión exhaustiva de automóviles,Analizadores de emisiones de escape para automóviles,0,80509,0 -12484,41113102,Analizadores de combustión catalítica,,0,80509,0 -12485,41113103,Analizadores de absorción de gas químico,,0,80509,0 -12486,41113104,Explosímetros,,0,80509,0 -12487,41113105,Analizadores o detectores de hidrocarburos,,0,80509,0 -12488,41113106,Analizadores de absorción de infrarrojos o ultravioleta,,0,80509,0 -12489,41113107,Analizadores de gas nitrógeno,,0,80509,0 -12490,41113108,Analizadores de óxido de nitrógeno,,0,80509,0 -12491,41113109,Equipo orsat,Equipo orsat,0,80509,0 -12492,41113110,Analizadores de gas de oxígeno,,0,80509,0 -12493,41113111,Analizadores de ozono,,0,80509,0 -12494,41113112,Analizadores de susceptibilidad paramagnética,,0,80509,0 -12495,41113113,Detectores o analizadores de dióxido de sulfuro,,0,80509,0 -12496,41113114,Analizadores de conductividad térmica,,0,80509,0 -12497,41113115,Detectores de radón,,0,80509,0 -12498,41113116,Tubos detectores de gas,,0,80509,0 -12499,41113117,Monitores simples de gas,,0,80509,0 -12500,41113118,Monitores múltiples de gas,,0,80509,0 -12501,41113119,Analizadores de dióxido de carbono disuelto,,0,80509,0 -12502,41113120,Analizador de monóxido de carbono,,0,80509,0 -12503,41113121,Sistema analizador de vapor de gas de vehículos,,0,80509,0 -12504,41113122,Olfatómetro,,0,80509,0 -12505,41113123,Probador de humo,,0,80509,0 -12506,41113124,Respirómetro,,0,80509,0 -12507,41113300,"Analizadores de líquidos, sólidos y elementos",,0,80509,0 -12508,41113301,Analizadores ácidos o de base,,0,80509,0 -12509,41113302,Albuminómetros,,0,80509,0 -12510,41113304,Analizadores de bauxita,,0,80509,0 -12511,41113305,Analizadores de calcio,,0,80509,0 -12512,41113306,Analizadores de cloruro,,0,80509,0 -12513,41113308,Analizadores de electrolitos,,0,80509,0 -12514,41113309,Analizadores de enzimas,,0,80509,0 -12515,41113310,Analizadores de ácidos grasos,,0,80509,0 -12516,41113311,Lámpara detectora de haluro,,0,80509,0 -12517,41113312,Analizadores de lactato,,0,80509,0 -12518,41113313,Instrumentos de prueba de aceite mineral,,0,80509,0 -12519,41113314,Analizadores monitores de contenido de aceite,,0,80509,0 -12520,41113315,Analizadores de carbono orgánico,,0,80509,0 -12521,41113316,Equipo de prueba de petróleo,,0,80509,0 -12522,41113318,Analizadores de uranio,,0,80509,0 -12523,41113319,Analizadores de agua,,0,80509,0 -12524,41113320,Kit de prueba de aceite lubricante,,0,80509,0 -12525,41113321,Probador de pintura,,0,80509,0 -12526,41113322,Analizador de nitrógeno o nitrato o nitrito,,0,80509,0 -12527,41113323,Analizadores de azúcar,,0,80509,0 -12528,41113324,Probador de fuerza de ocultamiento de la pintura,,0,80509,0 -12529,41113325,Probador de posibilidad de lavar la pintura,,0,80509,0 -12530,41113326,Instrumento medidor del grano de la pintura,,0,80509,0 -12531,41113327,Aplicador de película de pintura,,0,80509,0 -12532,41113328,Analizador de mercurio,,0,80509,0 -12533,41113329,Probador de sólidos suspendidos ss,,0,80509,0 -12534,41113330,Instrumento para medir el espesor del lodo,,0,80509,0 -12535,41113331,Probador de congelación y descongelación,,0,80509,0 -12536,41113332,Analizador de raciones líquidas,Analizador de relación de líquidos,0,80509,0 -12537,41113333,Analizador de potencial zeta,,0,80509,0 -12538,41113334,Equipo de prueba de pintura de revestimiento,,0,80509,0 -12539,41113335,Analizador de componentes múltiples de compuestos disueltos,,0,80509,0 -12540,41113336,Probador de número de emulsiones de vapor,,0,80509,0 -12541,41113337,Probador de tamaño del grano o de dispersión,,0,80509,0 -12542,41113338,Medidor de demanda de oxígeno bioquímico bod,,0,80509,0 -12543,41113339,Medidor de demanda de oxígeno químico cod,,0,80509,0 -12544,41113400,Instrumentos de evaluación nuclear,,0,80509,0 -12545,41113401,Contadores alfa,,0,80509,0 -12546,41113402,Contadores alfa beta,,0,80509,0 -12547,41113403,Contadores beta,,0,80509,0 -12548,41113404,Contadores beta gamma,,0,80509,0 -12549,41113405,Contadores gama,,0,80509,0 -12550,41113406,Medidores kvp,,0,80509,0 -12551,41113407,Micro analizadores de rayos x,,0,80509,0 -12552,41113600,Equipo de medición y comprobación eléctrica,,0,80509,0 -12553,41113601,Amperímetros,,0,80509,0 -12554,41113602,Medidores de fase,,0,80509,0 -12555,41113603,Puentes de laboratorio,,0,80509,0 -12556,41113604,Medidores de capacitancia,,0,80509,0 -12557,41113605,Derivatógrafos de análisis térmico,,0,80509,0 -12558,41113606,Indicadores de monitoreo de congelamiento,,0,80509,0 -12559,41113607,Monitores de estrés de calor,,0,80509,0 -12560,41113608,Contadores de coincidencia o no coincidencia,,0,80509,0 -12561,41113611,Medidores de diafonía,,0,80509,0 -12562,41113612,Probadores de resistencia de la tierra,,0,80509,0 -12563,41113613,Grabadoras de valor eléctrico,,0,80509,0 -12564,41113614,Medidores de campo electromagnético,,0,80509,0 -12565,41113615,Electrómetros,,0,80509,0 -12566,41113616,Cargas electrónicas,,0,80509,0 -12567,41113617,Equipos de medición de la fuerza del campo,,0,80509,0 -12568,41113618,Instrumentos de medición de ganancia,,0,80509,0 -12569,41113619,Galvanómetros,,0,80509,0 -12570,41113620,Cable de detección de alto voltaje,,0,80509,0 -12571,41113621,Medidores de impedancia,,0,80509,0 -12572,41113622,Bobinas o cajas de inductancias calibradas,,0,80509,0 -12573,41113623,Medidores de resistencia al aislamiento,,0,80509,0 -12574,41113624,Medidores de aislamiento,,0,80509,0 -12575,41113625,Cámaras de ionización,,0,80509,0 -12576,41113626,Ionómetros,,0,80509,0 -12577,41113627,Medidores de circuito de línea a tierra,Probadores de lazo de tierra de línea,0,80509,0 -12578,41113628,Megohmetros,,0,80509,0 -12579,41113629,Medidores de filtración de microondas,,0,80509,0 -12580,41113630,Multímetros,,0,80509,0 -12581,41113631,Ohmetros,,0,80509,0 -12582,41113632,Oscilógrafos,,0,80509,0 -12583,41113633,Potenciómetros,,0,80509,0 -12584,41113634,Medidores q,,0,80509,0 -12585,41113635,Equipo de medición de resistencia calibrada,,0,80509,0 -12586,41113636,Generadores de nivel,,0,80509,0 -12587,41113637,Medidores de voltaje o de corriente,,0,80509,0 -12588,41113638,Osciloscopios,,0,80509,0 -12589,41113639,Acelerómetros,,0,80509,0 -12590,41113640,Medidores de vatios,,0,80509,0 -12591,41113641,Probadores de circuitos gfi,,0,80509,0 -12592,41113642,Probador de circuitos,,0,80509,0 -12593,41113644,Trazador de circuitos,,0,80509,0 -12594,41113645,Dispositivos de filtración de tierra,,0,80509,0 -12595,41113646,Calibrador o simulador de temperatura,,0,80509,0 -12596,41113647,Calibrador o simulador de frecuencia,,0,80509,0 -12597,41113649,Sofómetro,,0,80509,0 -12598,41113650,Probador funcional,,0,80509,0 -12599,41113651,Tablero de medición de resistencia,,0,80509,0 -12600,41113652,Tablero de ensamblaje de cables,,0,80509,0 -12601,41113653,Probador de circuito en serie,,0,80509,0 -12602,41113654,Medidor de circuito de corriente,,0,80509,0 -12603,41113655,Set de prueba de generador,Set de prueba de generador,0,80509,0 -12604,41113656,Probador de servo sistema,,0,80509,0 -12605,41113657,Probador de disyuntor (interruptor),,0,80509,0 -12606,41113658,Probador de durabilidad del interruptor,,0,80509,0 -12607,41113659,Medidor de energía reactiva (var),,0,80509,0 -12608,41113660,Probador de oscilador,,0,80509,0 -12609,41113661,Probador de regulador de voltaje,,0,80509,0 -12610,41113662,Resonador de cavidades,,0,80509,0 -12611,41113663,Probador transformador,,0,80509,0 -12612,41113664,Indicador de secuencia de fase,,0,80509,0 -12613,41113665,Probador de resistencia de contacto,,0,80509,0 -12614,41113666,Probador de pararrayos,,0,80509,0 -12615,41113667,Equipo de prueba del medidor de vatios hora,,0,80509,0 -12616,41113668,Probador de capacidad,,0,80509,0 -12617,41113669,Detector de línea de voltaje,,0,80509,0 -12618,41113670,Equipo de prueba de resistencia,,0,80509,0 -12619,41113671,Divisor de corriente,,0,80509,0 -12620,41113672,Probador de relé,Probador de relevadores,0,80509,0 -12621,41113673,Medidor de panel y tablero de control,,0,80509,0 -12622,41113674,Probador de máquina pulidora,,0,80509,0 -12623,41113675,Equipo de prueba de control de frecuencia,,0,80509,0 -12624,41113676,Probador de bajo voltaje,,0,80509,0 -12625,41113677,Probador de resistencia de voltaje,,0,80509,0 -12626,41113681,"Probador combi (voltaje, corriente, frecuencia y otras)",,0,80509,0 -12627,41113682,Probador de corriente eléctrica,,0,80509,0 -12628,41113683,Probador de lámparas,,0,80509,0 -12629,41113684,Calibrador medidor de voltaje y corriente,,0,80509,0 -12630,41113686,Medidor de pulso,Oxímetro de pulso,0,80509,0 -12631,41113687,Máquina para probar freno,,0,80509,0 -12632,41113688,Equipo para probar convertidores,,0,80509,0 -12633,41113689,Equipo para probar controladores,,0,80509,0 -12634,41113700,Instrumentos de medición y comprobación de comunicación electrónica,,0,80509,0 -12635,41113701,Probador de tubos de rayos catódicos,,0,80509,0 -12636,41113702,Comparadores,Comparadores analógicos o digitales,0,80509,0 -12637,41113703,Acoplamiento direccional,,0,80509,0 -12638,41113704,Probadores de circuitos integrados,,0,80509,0 -12639,41113705,Probadores de estado lógico,,0,80509,0 -12640,41113706,Probadores de semiconductores,,0,80509,0 -12641,41113707,Probadores de circuitos de transistores,,0,80509,0 -12642,41113708,Medidores de energía,,0,80509,0 -12643,41113709,Medidores de modulación,,0,80509,0 -12644,41113710,Medidor de nivel,,0,80509,0 -12645,41113711,Analizadores de red,,0,80509,0 -12646,41113712,Probadores de cintas,,0,80509,0 -12647,41113713,Probadores de velocidad de la cinta,,0,80509,0 -12648,41113714,Diferenciador,,0,80509,0 -12649,41113715,Probadores de redes digitales de servicios integrados isdn,,0,80509,0 -12650,41113716,Localizadores de fallas de fibra óptica,,0,80509,0 -12651,41113717,Fuentes de prueba de fibra óptica,,0,80509,0 -12652,41113718,Analizadores de protocolo,,0,80509,0 -12653,41113719,Probador de pérdida óptica,,0,80509,0 -12654,41113720,Equipo de prueba de intensidad del tráfico,,0,80509,0 -12655,41113721,Instrumento de medición de señal de video,,0,80509,0 -12656,41113722,Probador de interferencia electromagnética (emi),,0,80509,0 -12657,41113723,Probador de equipo de radio,,0,80509,0 -12658,41113724,Set de prueba de teléfonos,Set de prueba de teléfonos,0,80509,0 -12659,41113725,Medidor de energía óptica,,0,80509,0 -12660,41113726,Probador de resistencia al calor,,0,80509,0 -12661,41113727,Medidor de distorsión,,0,80509,0 -12662,41113728,Cámara ambiental de escudo electromagnético,,0,80509,0 -12663,41113729,Medidor de salida del amplificador,,0,80509,0 -12664,41113730,Probador de televisiones,,0,80509,0 -12665,41113731,Probador de cristales,,0,80509,0 -12666,41113732,Probador de amplificadores,,0,80509,0 -12667,41113733,Probador de antenas,,0,80509,0 -12668,41113734,Medidor de desviación de frecuencia,,0,80509,0 -12669,41113735,Probador de equipo de microondas,,0,80509,0 -12670,41113736,Refractómetro electrónico y de comunicación,,0,80509,0 -12671,41113737,Medidor de ruido,,0,80509,0 -12672,41113738,Probador de sobrecarga de la línea de comunicación,,0,80509,0 -12673,41113739,Probador de susceptibilidad electromagnética,,0,80509,0 -12674,41113740,"Probador de cable de video, voz y datos",,0,80509,0 -12675,41113800,"Instrumentos geofísicos, geotécnicos e hidrológicos",,0,80509,0 -12676,41113801,Compases geológicos,"Brújula de geólogo, Compases geológicos",0,80509,0 -12677,41113802,Aparatos de prospección geológica,,0,80509,0 -12678,41113803,Instrumentos geofísicos electromagnéticos,,0,80509,0 -12679,41113804,Instrumentos geofísicos de gravedad,,0,80509,0 -12680,41113805,Instrumentos geofísicos de polarización inducida ip,,0,80509,0 -12681,41113806,Instrumentos geofísicos magnetómetros,,0,80509,0 -12682,41113807,Instrumentos geofísicos de resistividad,,0,80509,0 -12683,41113808,Gravímetros,Gravitómetro,0,80509,0 -12684,41113809,Radar de penetración del suelo,,0,80509,0 -12685,41113810,Equipo de prueba hidrotérmica,,0,80509,0 -12686,41113811,Probador de resistencia sólida,,0,80509,0 -12687,41113812,Probador de fricción del suelo,,0,80509,0 -12688,41113813,Agregado estándar,,0,80509,0 -12689,41113814,Probador de límite plástico del suelo,,0,80509,0 -12690,41113815,Probador del límite de encogimiento del suelo,,0,80509,0 -12691,41113816,Probador de consolidación,,0,80509,0 -12692,41113817,Probador del límite líquido del suelo,,0,80509,0 -12693,41113818,Probador de resistencia de la placa,Probador de rodamientos de placas,0,80509,0 -12694,41113819,Analizador de suelos,,0,80509,0 -12695,41113820,Probador de gravedad específica agregada,Probador de gravedad específica agregada,0,80509,0 -12696,41113821,Probador de penetración del suelo,,0,80509,0 -12697,41113822,Probador de tasa de resistencia california,,0,80509,0 -12698,41113823,Probador de expansión de espécimen,Probador de expansión de muestras,0,80509,0 -12699,41113824,Probador de permeabilidad de cabeza de suelo,,0,80509,0 -12700,41113825,Analizador de agregado del suelo,,0,80509,0 -12701,41113826,Analizador de textura del suelo,,0,80509,0 -12702,41113827,Aparato de compresión no confinada del suelo,,0,80509,0 -12703,41113828,Aparato de cono de densidad de la arena,,0,80509,0 -12704,41113829,Equipo de recolección de suelo bajo el agua,,0,80509,0 -12705,41113830,Instrumento para medir el peso de la unidad de agregado,,0,80509,0 -12706,41113831,Instrumento para medir el agua superficial en la superficie del agregado,,0,80509,0 -12707,41113900,Equipo para medición de suelos,,0,80509,0 -12708,41113901,Instrumentos de medición de perforación,,0,80509,0 -12709,41113902,Probadores de disolución o desintegración,,0,80509,0 -12710,41113903,Aparatos medidores del tamaño de partículas,,0,80509,0 -12711,41113904,Penetrómetros,,0,80509,0 -12712,41113905,Aparatos para probar permeabilidad,,0,80509,0 -12713,41113906,Aparatos para estimar permeabilidad o porosidad,,0,80509,0 -12714,41113907,Porosímetros,,0,80509,0 -12715,41113908,Aparatos para probar arena,Aparatos para probar arena,0,80509,0 -12716,41113909,Aparatos muestreadores del núcleo del suelo,,0,80509,0 -12717,41113910,Kits de probadores del suelo,,0,80509,0 -12718,41114200,Instrumentos de agrimensión,,0,80509,0 -12719,41114201,Cintas medidoras,,0,80509,0 -12720,41114202,Varillas medidoras,,0,80509,0 -12721,41114203,Tablas medidoras,,0,80509,0 -12722,41114204,Teodolitos,,0,80509,0 -12723,41114205,Estacas de localización,,0,80509,0 -12724,41114206,Centro de localización,,0,80509,0 -12725,41114207,Estación total,,0,80509,0 -12726,41114208,Instrumento de longimetría,,0,80509,0 -12727,41114209,Taquímetro o taqueómetro,,0,80509,0 -12728,41114210,Nivel óptico,,0,80509,0 -12729,41114211,Altímetro,,0,80509,0 -12730,41114212,Estereoscopio de espejo,,0,80509,0 -12731,41114213,Comparador de coordinadas,Comparador de coordinadas,0,80509,0 -12732,41114214,Plantilla de encuestas,,0,80509,0 -12733,41114215,Dendrómetro,,0,80509,0 -12734,41114216,Alidada,,0,80509,0 -12735,41114217,Línea de plomo,,0,80509,0 -12736,41114218,Planímetros,,0,80509,0 -12737,41114219,Brazo de la plomada,,0,80509,0 -12738,41114220,Tránsito,,0,80509,0 -12739,41114221,Escuadra óptica,,0,80509,0 -12740,41114400,Instrumentos meteorológicos,,0,80509,0 -12741,41114401,Anemómetros,,0,80509,0 -12742,41114402,Barómetros,,0,80509,0 -12743,41114403,Grabadoras de precipitación o evaporación,Registradores de precipitación o evaporación,0,80509,0 -12744,41114404,Aparatos de radiosonda,,0,80509,0 -12745,41114405,Grabadoras de lluvia,,0,80509,0 -12746,41114406,Aparatos para observar la precipitación o evaporación en la superficie,,0,80509,0 -12747,41114407,Aparatos para observar la radiación solar en la superficie,Aparatos para observar la radiación solar superficial,0,80509,0 -12748,41114408,Aparatos para observar la temperatura o humedad en la superficie,,0,80509,0 -12749,41114409,Aparatos para observar el viento en la superficie,,0,80509,0 -12750,41114410,Estaciones meteorológicas,,0,80509,0 -12751,41114411,Accesorios para instrumentos de meteorología,,0,80509,0 -12752,41114412,Sistemas sonoros de radio acústica,,0,80509,0 -12753,41114413,Ceilómetro,,0,80509,0 -12754,41114414,Equipo receptor de satélite meteorológico,,0,80509,0 -12755,41114415,Sistema de análisis de rayos,,0,80509,0 -12756,41114416,Grabadora o escáner de tabla meteorológica,,0,80509,0 -12757,41114417,Robot boya meteorológica,,0,80509,0 -12758,41114418,Sistema para recibir y analizar información del satélite meteorológico,,0,80509,0 -12759,41114419,Sistema de alerta del nivel bajo del corte de viento,Sistema de alerta del nivel bajo del corte de viento,0,80509,0 -12760,41114420,Calibrador de barómetros,,0,80509,0 -12761,41114421,Calibrador de higrómetros,,0,80509,0 -12762,41114422,Calibrador de medidor de lluvia,,0,80509,0 -12763,41114423,Calibrador de sistema automático de clima,,0,80509,0 -12764,41114424,Calibrador de anemómetros,,0,80509,0 -12765,41114425,Calibrador de actinómetros,,0,80509,0 -12766,41114426,Interruptor barométrico,,0,80509,0 -12767,41114427,Veletas,,0,80509,0 -12768,41114500,Instrumentos mecánicos,,0,80509,0 -12769,41114501,Dinamómetros,,0,80509,0 -12770,41114502,Elastómetros,,0,80509,0 -12771,41114503,Extensómetros,,0,80509,0 -12772,41114504,Instrumentos de medición de densidad,,0,80509,0 -12773,41114505,Instrumentos de prueba de redondez,,0,80509,0 -12774,41114506,Esferómetros,,0,80509,0 -12775,41114507,Maquinas de prueba de resortes,,0,80509,0 -12776,41114508,Probadores de superficie,,0,80509,0 -12777,41114509,Tensiómetros,"Baumanómetro, Esfigmomanómetro, Esfingomanómetron",0,80509,0 -12778,41114510,Limitador de torsión,,0,80509,0 -12779,41114511,Probador de suavidad,,0,80509,0 -12780,41114512,Probador de balance rotatorio de alta velocidad,,0,80509,0 -12781,41114513,Instrumento de medición de excentricidad,,0,80509,0 -12782,41114514,Instrumento de medición de vibración de torsión,,0,80509,0 -12783,41114515,Probador de fuerza centrífuga,,0,80509,0 -12784,41114516,Instrumento de medición de fuerza de corte,,0,80509,0 -12785,41114517,Probador de hinchazón hidráulica,,0,80509,0 -12786,41114518,Probador de soporte de cremallera,,0,80509,0 -12787,41114519,Sistema de remolque y carrito,,0,80509,0 -12788,41114520,Probador de caída,,0,80509,0 -12789,41114521,Probador de compresor de aire,,0,80509,0 -12790,41114522,Probador de engranajes,,0,80509,0 -12791,41114523,Barra de prueba,,0,80509,0 -12792,41114524,Probador hidráulico,,0,80509,0 -12793,41114525,Probador de filtros de aceite,,0,80509,0 -12794,41114526,Probador de limpiador de aire,,0,80509,0 -12795,41114527,Probador de correa,,0,80509,0 -12796,41114528,Instrumento de medición del mecanismo del movimiento de planeación,,0,80509,0 -12797,41114529,Probador de llantas en movimiento,,0,80509,0 -12798,41114530,Probador del estrés del pegante,,0,80509,0 -12799,41114600,"Instrumentos de verificación de metales, metalurgia y materiales estructurales",,0,80509,0 -12800,41114601,Probadores de abrasión,,0,80509,0 -12801,41114602,Probadores de compresión,,0,80509,0 -12802,41114603,Instrumentos para probar concreto o cemento,,0,80509,0 -12803,41114604,Probadores de corrosión,,0,80509,0 -12804,41114605,Detectores de grietas o corrosión,,0,80509,0 -12805,41114606,Probadores de fuga,,0,80509,0 -12806,41114607,Máquinas para probar ductilidad,,0,80509,0 -12807,41114608,Probadores de fatiga,,0,80509,0 -12808,41114609,Aparatos probadores de forja,,0,80509,0 -12809,41114610,Aparatos probadores de fundición,,0,80509,0 -12810,41114611,Probadores de dureza,,0,80509,0 -12811,41114612,Probadores de impacto,,0,80509,0 -12812,41114613,Marco de carga,,0,80509,0 -12813,41114614,Instrumentos para probar metales,,0,80509,0 -12814,41114615,Instrumentos probadores foto elásticos,,0,80509,0 -12815,41114616,Indicadores de resistencia al estrés,,0,80509,0 -12816,41114617,Probadores de relajación,,0,80509,0 -12817,41114618,Instrumentos de medición de rugosidad,,0,80509,0 -12818,41114619,Probadores de fuerza de corte,,0,80509,0 -12819,41114620,Aparatos para probar choque,,0,80509,0 -12820,41114621,Probadores de tensión,,0,80509,0 -12821,41114622,Probadores de torsión,,0,80509,0 -12822,41114623,Máquinas para pruebas de flexión o transversales,,0,80509,0 -12823,41114624,Probadores de vibración,,0,80509,0 -12824,41114625,Probadores de desgaste,,0,80509,0 -12825,41114626,Aparatos para probar soldaduras,Aparatos para pruebas de soldadura,0,80509,0 -12826,41114627,Aro de calibración,,0,80509,0 -12827,41114628,Probador de plasticidad,,0,80509,0 -12828,41114629,Probador de distorsión por calor,,0,80509,0 -12829,41114630,Probador de choque térmico,,0,80509,0 -12830,41114631,Probador de estrés,,0,80509,0 -12831,41114632,Probador de fuerza tensora,,0,80509,0 -12832,41114633,Probador de expansión térmica,,0,80509,0 -12833,41114634,Probador erichsen,,0,80509,0 -12834,41114635,Probador avalador de joyas,Probador de evaluación de joyas,0,80509,0 -12835,41114636,Instrumento de medición de condición moldeadora de polímeros,Instrumento de medición de condiciones de moldeo de polímero,0,80509,0 -12836,41114637,Aparatos de prueba de haces de luz,Aparatos de prueba de haz de luz,0,80509,0 -12837,41114638,Probador de carga,,0,80509,0 -12838,41114639,Probador de flujo de cemento,,0,80509,0 -12839,41114640,Equipo de medición de plano de carretera,,0,80509,0 -12840,41114641,Equipo de curar cemento para laboratorio,,0,80509,0 -12841,41114642,Detector de perforación,,0,80509,0 -12842,41114643,Probador de estabilidad del cemento,,0,80509,0 -12843,41114644,Instrumento para medir aire en el concreto,,0,80509,0 -12844,41114645,Probador de longitud de mortero,,0,80509,0 -12845,41114646,Probador de aguja vicat,,0,80509,0 -12846,41114647,Probador de asentamiento,,0,80509,0 -12847,41114648,Probador de vibración del cemento o concreto,,0,80509,0 -12848,41114649,Probador de permeabilidad del mortero,,0,80509,0 -12849,41114650,Molde cilíndrico de concreto,,0,80509,0 -12850,41114700,"Instrumentos para comprobación de papel, madera y tejidos",,0,80509,0 -12851,41114701,Instrumentos de prueba de cartón,,0,80509,0 -12852,41114702,Probadores de resistencia de textiles,,0,80509,0 -12853,41114703,Instrumentos para probar cuero,Instrumentos para probar cuero,0,80509,0 -12854,41114704,Instrumentos para probar papel,,0,80509,0 -12855,41114705,Instrumentos para probar textiles,,0,80509,0 -12856,41114706,Instrumentos para probar madera,,0,80509,0 -12857,41114707,Probador de fuerza de textiles o papel,,0,80509,0 -12858,41114708,Probador de permeabilidad de vapor de agua,,0,80509,0 -12859,41114709,Instrumento para probar yam (textil sintético),,0,80509,0 -12860,41114710,Probador de transmisión térmica,,0,80509,0 -12861,41114711,Probador de caída de textiles,,0,80509,0 -12862,41114712,Probador de repelencia de agua para textiles,,0,80509,0 -12863,41114713,Probador de recuperación de arrugas para textiles,,0,80509,0 -12864,41114714,Probador de maduración de textiles,Probador de madurez textil,0,80509,0 -12865,41114715,"Probador de permeabilidad al aire, papel o textiles",,0,80509,0 -12866,41114716,Probador de encogimiento de textiles,,0,80509,0 -12867,41114717,Probador de cocción de textiles,Probador para hornear textiles,0,80509,0 -12868,41114718,Probador de grado de formación de motas para textiles,Probador de grado pilling textil,0,80509,0 -12869,41114719,Máquina probadora de hojas de papel,,0,80509,0 -12870,41114720,Probador de absorción de agua de papel y paño,,0,80509,0 -12871,41114721,Probador de longitud de fibra,,0,80509,0 -12872,41114722,Probador de resistencia al rompimiento,Probador de resistencia al rompimiento,0,80509,0 -12873,41114723,Probador de fuerza de estallido,,0,80509,0 -12874,41115100,Instrumentos para comprobación de carbón y mineral,,0,80509,0 -12875,41115101,Instrumentos para probar carbón,,0,80509,0 -12876,41115102,Goniómetro,,0,80509,0 -12877,41115200,Sistemas y componentes de radar y sonar,,0,80509,0 -12878,41115201,Sistemas de vigilancia basados en radar,,0,80509,0 -12879,41115202,Polarrotores (elementos centrales de la antena),,0,80509,0 -12880,41115203,Radar meteorológico,,0,80509,0 -12881,41115300,Equipo de generación y medición de luz y ondas,,0,80509,0 -12882,41115301,Medidores de absorción de luz,,0,80509,0 -12883,41115302,Cámaras anecóicas,,0,80509,0 -12884,41115303,Analizadores de frecuencia,,0,80509,0 -12885,41115304,Contadores o temporizadores o divisores de frecuencia,,0,80509,0 -12886,41115305,Medidores de frecuencia eléctrica,,0,80509,0 -12887,41115306,Interferómetros,,0,80509,0 -12888,41115307,Láseres,,0,80509,0 -12889,41115308,Medidores de luz,,0,80509,0 -12890,41115309,Luxómetros,,0,80509,0 -12891,41115310,Set de calibración óptica,,0,80509,0 -12892,41115311,Fotómetros,,0,80509,0 -12893,41115312,Polarímetros o refractómetros de mesa,,0,80509,0 -12894,41115313,Polarímetros o refractómetros manuales,,0,80509,0 -12895,41115314,Polarímetros,,0,80509,0 -12896,41115315,Polariscopios,,0,80509,0 -12897,41115316,Reflectómetros,,0,80509,0 -12898,41115317,Estroboscopios,,0,80509,0 -12899,41115318,Colorímetros,,0,80509,0 -12900,41115319,Lectores de tubo o disco,,0,80509,0 -12901,41115320,Generadores de señales,,0,80509,0 -12902,41115321,Imágenes infrarrojas,Imágenes infrarrojas,0,80509,0 -12903,41115322,Analizadores de rayos láser,,0,80509,0 -12904,41115323,Generador de funciones,,0,80509,0 -12905,41115324,Sensor ultravioleta,,0,80509,0 -12906,41115325,Generador de imágenes térmico,,0,80509,0 -12907,41115326,Colimador óptico,Mira telescópica,0,80509,0 -12908,41115327,Equipo de medición foto eléctrica,Equipo de medición fotoeléctrico,0,80509,0 -12909,41115328,Sintetizador de forma de onda,,0,80509,0 -12910,41115329,Equipo de medición de propagación de radioonda,Equipo de medición de la propagación de ondas radioeléctricas,0,80509,0 -12911,41115330,Medidor de opacidad,,0,80509,0 -12912,41115331,Instrumento medidor de brillo,,0,80509,0 -12913,41115332,Medidor de largo de onda óptica,,0,80509,0 -12914,41115333,Instrumento medidor de fotones,,0,80509,0 -12915,41115334,Probador de blancura,,0,80509,0 -12916,41115335,Vectorscopio,,0,80509,0 -12917,41115336,Lámpara de rayos ultravioleta para laboratorio,,0,80509,0 -12918,41115337,Instrumento de medición de ondas,,0,80509,0 -12919,41115338,Medidor de iluminancia,,0,80509,0 -12920,41115339,Elipsómetro,,0,80509,0 -12921,41115400,Equipo espectroscópico,,0,80509,0 -12922,41115401,Espectrofluorímetros o fluorímetros,,0,80509,0 -12923,41115402,Espectrógrafos,,0,80509,0 -12924,41115403,Espectrómetros,,0,80509,0 -12925,41115404,Espectrómetros de masa,,0,80509,0 -12926,41115405,Espectrómetros de protón,,0,80509,0 -12927,41115406,Espectrofotómetros,,0,80509,0 -12928,41115407,Espectrómetros de absorción atómica aa,,0,80509,0 -12929,41115408,Espectrómetros infrarrojos,,0,80509,0 -12930,41115409,Espectrómetros de resonancia magnética nuclear nmr,,0,80509,0 -12931,41115411,Espectrómetros de plasma acoplado inductivamente icp,,0,80509,0 -12932,41115412,Espectro bolómetro,,0,80509,0 -12933,41115413,Citómetro de flujo,,0,80509,0 -12934,41115414,Sistema de espectroscopia de rayo radio,,0,80509,0 -12935,41115415,Sistema de espectroscopio de electrones para análisis químico,,0,80509,0 -12936,41115416,Espectrómetro de resonancia de giro de electrones,,0,80509,0 -12937,41115500,Equipo de generación y medición de sonido,,0,80509,0 -12938,41115501,Sonares,,0,80509,0 -12939,41115502,Sonómetros,,0,80509,0 -12940,41115503,Aparatos para medir el sonido o medidores de decibeles,,0,80509,0 -12941,41115507,Analizador de voz,,0,80509,0 -12942,41115508,Detector de sonido,,0,80509,0 -12943,41115509,Detector de cables y tubos subterráneos,,0,80509,0 -12944,41115510,Detector de fuga de tubos de agua,,0,80509,0 -12945,41115511,Probador de audífonos,,0,80509,0 -12946,41115512,Emisor de onda acústica,,0,80509,0 -12947,41115513,Generador acústico,,0,80509,0 -12948,41115514,Sonoboya,,0,80509,0 -12949,41115515,Hidrófono,,0,80509,0 -12950,41115600,Instrumentos y accesorios de medición electroquímica,,0,80509,0 -12951,41115601,Equipo de análisis volumétrico karl fischer,Valorador volumétrico Karl Fischer,0,80509,0 -12952,41115602,Equipo de análisis volumétrico,,0,80509,0 -12953,41115603,Medidores de ph,,0,80509,0 -12954,41115604,Electrodos de ph,,0,80509,0 -12955,41115606,Medidores de electrodos selectivos de iones ise,,0,80509,0 -12956,41115607,Tiras de prueba de iones selectivos,,0,80509,0 -12957,41115608,Electrodos selectivos de iones,,0,80509,0 -12958,41115609,Medidores de conductividad,,0,80509,0 -12959,41115610,Celdas de conductividad,,0,80509,0 -12960,41115611,Medidores de oxígeno disuelto,,0,80509,0 -12961,41115612,Sondas de oxigeno disuelto,,0,80509,0 -12962,41115613,Medidor de salinidad,,0,80509,0 -12963,41115614,Transmisores de ph,,0,80509,0 -12964,41115615,Controlador de ph,,0,80509,0 -12965,41115700,Instrumentos y accesorios de medición cromatográfica,,0,80509,0 -12966,41115701,Detectores cromatográficos,,0,80509,0 -12967,41115702,Escáneres cromatográficos,,0,80509,0 -12968,41115703,Gases cromatográficos,Gases cromatográficos,0,80509,0 -12969,41115704,Iones cromatográficos,Iones cromatográficos,0,80509,0 -12970,41115705,Cromatógrafos líquidos,,0,80509,0 -12971,41115706,Cromatógrafos de capa delgada,,0,80509,0 -12972,41115707,Cromatógrafos para cromatografía de líquido de alta presión,,0,80509,0 -12973,41115708,Cromatógrafos de capa delgada de alta presión tlc,,0,80509,0 -12974,41115709,Columnas para cromatografía líquida de alta presión hplc,,0,80509,0 -12975,41115710,Columnas para cromatografía de gas,Columnas para cromatografía de gas,0,80509,0 -12976,41115711,Columnas para cromatografía líquida lc,Columnas para cromatografía líquida lc,0,80509,0 -12977,41115712,Columnas para extracción de fase sólida spe,Columnas SPE de extracción en fase sólida,0,80509,0 -12978,41115713,Tanques para cromatografía de capa delgada,,0,80509,0 -12979,41115714,Auto muestreadores,,0,80509,0 -12980,41115715,Inyectores,,0,80509,0 -12981,41115716,Accesorios para cromatografía líquida,,0,80509,0 -12982,41115717,Accesorios para cromatografía de gas,,0,80509,0 -12983,41115718,Inyector de paredes de membrana,,0,80509,0 -12984,41115719,Revestimientos para cromatografía de gas,,0,80509,0 -12985,41115720,Tubos para cromatografía,,0,80509,0 -12986,41115721,Sistema de cromatografía líquida de proteínas rápidas fplc,,0,80509,0 -12987,41115722,Columna para cromatografía líquida de proteínas rápidas fplc,,0,80509,0 -12988,41115723,Cromatógrafo de fluido súper crítico,,0,80509,0 -12989,41115800,"Analizadores, accesorios y suministros clínicos y diagnósticos",,0,80509,0 -12990,41115801,Analizadores de amino ácidos,,0,80509,0 -12991,41115802,Accesorios o suministros para analizadores de amino ácidos,,0,80509,0 -12992,41115803,Analizadores de bancos de sangre,,0,80509,0 -12993,41115804,Accesorios o suministros para analizadores de bancos de sangre,,0,80509,0 -12994,41115805,Analizadores de gas en la sangre,,0,80509,0 -12995,41115806,Accesorios o suministros para analizadores de gas en la sangre,,0,80509,0 -12996,41115807,Analizadores químicos,,0,80509,0 -12997,41115808,Accesorios o suministros para analizadores químico,,0,80509,0 -12998,41115809,Analizadores de coagulación,,0,80509,0 -12999,41115810,Accesorios o suministros para analizadores de coagulación,,0,80509,0 -13000,41115811,Analizadores de secuencia desoxirribonucleica,,0,80509,0 -13001,41115812,Accesorios o suministros para analizadores de secuencia desoxirribonucleica,,0,80509,0 -13002,41115813,Analizadores de toxicología,,0,80509,0 -13003,41115814,Accesorios o suministros para analizadores de toxicología,,0,80509,0 -13004,41115815,Analizadores de hematología,,0,80509,0 -13005,41115816,Accesorios o suministros para analizadores de hematología,,0,80509,0 -13006,41115817,Analizadores de histología,,0,80509,0 -13007,41115818,Accesorios o suministros para analizadores de histología,,0,80509,0 -13008,41115819,Analizadores de inmunología,,0,80509,0 -13009,41115820,Accesorios o suministros para analizadores de inmunología,,0,80509,0 -13010,41115821,Analizadores de microbiología,,0,80509,0 -13011,41115822,Accesorios o suministros para analizadores de microbiología,,0,80509,0 -13012,41115823,Analizadores de proteína,,0,80509,0 -13013,41115824,Accesorios o suministros para analizadores de proteína,,0,80509,0 -13014,41115825,Analizadores radio isotópicos,,0,80509,0 -13015,41115826,Accesorios o suministros para analizadores radio isotópicos,,0,80509,0 -13016,41115827,Analizadores de orina,"Prueba de embarazo, Prueba rápida antidoping",0,80509,0 -13017,41115828,Accesorios o suministros para analizadores de orina,,0,80509,0 -13018,41115829,Analizadores de productos cárnicos o lácteos,,0,80509,0 -13019,41115830,Analizadores de glucosa,Glucómetros,0,80509,0 -13020,41115831,Estante o revistero para la manipulación de micro placas,,0,80509,0 -13021,41115832,Equipo para detección de antibióticos,,0,80509,0 -13022,41115833,Analizador de piel,,0,80509,0 -13023,41115834,Accesorios o suministros para citómetro de flujo,,0,80509,0 -13024,41115835,Contador automático de androginia y fertilidad,,0,80509,0 -13025,41115836,Accesorios o suministros para contador automático de androginia y fertilidad,,0,80509,0 -13026,41115837,Contador y medidor de tamaño de partículas,,0,80509,0 -13027,41115838,Accesorios o suministros para contador y medidor de tamaño de partículas,,0,80509,0 -13028,41115839,Analizador de unidad procesadora de aféresis de banco de sangre y donantes,,0,80509,0 -13029,41115840,Accesorios o suministros para analizador de unidad procesadora de aféresis de banco de sangre y donantes,,0,80509,0 -13030,41116000,Reactivos de analizadores clínicos y diagnósticos,,0,80509,0 -13031,41116001,Reactivos analizadores de amino ácidos,,0,80509,0 -13032,41116002,Reactivos analizadores de bancos de sangre,,0,80509,0 -13033,41116003,Reactivos analizadores de gas en la sangre,,0,80509,0 -13034,41116004,Reactivos analizadores de química,,0,80509,0 -13035,41116005,Reactivos analizadores de coagulación,,0,80509,0 -13036,41116006,Reactivos analizadores de secuencia de ácido desoxirribonucleico dna,,0,80509,0 -13037,41116007,Reactivos analizadores de toxicología,,0,80509,0 -13038,41116008,Reactivos analizadores de hematología,,0,80509,0 -13039,41116009,Reactivos analizadores de histología,,0,80509,0 -13040,41116010,Reactivos analizadores de inmunología,,0,80509,0 -13041,41116011,Reactivos analizadores de microbiología,,0,80509,0 -13042,41116012,Reactivos analizadores de proteínas,,0,80509,0 -13043,41116013,Reactivos analizadores radio isotópicos,,0,80509,0 -13044,41116014,Reactivos analizadores de análisis de orina,,0,80509,0 -13045,41116015,Reactivos o anticuerpos analizadores de citometría de flujo,,0,80509,0 -13046,41116016,Reactivo analizador de electrolitos,,0,80509,0 -13047,41116017,Reactivo contador automático de androginia y fertilidad,,0,80509,0 -13048,41116018,Reactivo analizador contador y medidor de tamaño de partículas,,0,80509,0 -13049,41116100,"Kits de ensayos manuales, controles de calidad, calibradores y normativas",,0,80509,0 -13050,41116101,Kits o suministros de prueba de bancos de sangre,,0,80509,0 -13051,41116102,Reactivos o soluciones de bancos de sangre,,0,80509,0 -13052,41116103,Controles de calidad o calibradores o estándares de bancos de sangre,,0,80509,0 -13053,41116104,Kits o suministros para pruebas químicas,,0,80509,0 -13054,41116105,Reactivos o soluciones químicas,,0,80509,0 -13055,41116106,Tiras de prueba o papel de prueba químico,,0,80509,0 -13056,41116107,Controles de calidad o calibradores o estándares químicos,,0,80509,0 -13057,41116108,Kits o suministros para pruebas de coagulación,,0,80509,0 -13058,41116109,Reactivos o soluciones de coagulación,,0,80509,0 -13059,41116110,Controles de calidad o calibradores o estándares de coagulación,,0,80509,0 -13060,41116111,Kits o suministros para pruebas de citología,,0,80509,0 -13061,41116112,Controles de calidad o calibradores o estándares de citología,,0,80509,0 -13062,41116113,Reactivos o soluciones o tinturas para citología,,0,80509,0 -13063,41116116,Kits o suministros para pruebas ambientales,,0,80509,0 -13064,41116117,Reactivos o soluciones o tinturas ambientales,,0,80509,0 -13065,41116118,Kits o suministros para pruebas de alimentos,,0,80509,0 -13066,41116119,Reactivos o soluciones o tinturas para kits de pruebas de alimentos,,0,80509,0 -13067,41116120,Kits o suministros para pruebas de hematología,,0,80509,0 -13068,41116121,Reactivos o soluciones o tinturas para hematología,,0,80509,0 -13069,41116122,Controles de calidad o calibradores o estándares para hematología,,0,80509,0 -13070,41116123,Kits o suministros para pruebas de histología,,0,80509,0 -13071,41116124,Reactivos o soluciones o tinturas para histología,,0,80509,0 -13072,41116125,Controles de calidad o calibradores o estándares para histología,,0,80509,0 -13073,41116126,Kits o suministros para pruebas de inmunología o serología,,0,80509,0 -13074,41116127,Reactivos o soluciones o tinturas para inmunología o serología,,0,80509,0 -13075,41116128,Controles de calidad o calibradores o estándares para inmunología o serología,,0,80509,0 -13076,41116129,Kits o suministros para pruebas de microbiología o bacteriología,,0,80509,0 -13077,41116130,Reactivos o soluciones o tinturas para microbiología o bacteriología,,0,80509,0 -13078,41116131,Discos o paneles de identificación o sensibilidad para microbiología o bacteriología,,0,80509,0 -13079,41116132,Controles de calidad o calibradores o estándares para microbiología o bacteriología,,0,80509,0 -13080,41116133,Kits o suministros para pruebas de biología molecular,,0,80509,0 -13081,41116134,Reactivos o soluciones o tinturas para biología molecular,,0,80509,0 -13082,41116135,Controles de calidad o calibradores o estándares para biología molecular,,0,80509,0 -13083,41116136,Kits o suministros para análisis de orina,,0,80509,0 -13084,41116137,Reactivos o soluciones o tinturas para análisis de orina,,0,80509,0 -13085,41116138,Tiras para análisis de orina,,0,80509,0 -13086,41116139,Controles de calidad o calibradores o estándares de análisis de orina,,0,80509,0 -13087,41116141,Reactivos o soluciones o tinturas para parasitología o micología,,0,80509,0 -13088,41116142,Medio para parasitología o micología,Medio para parasitología o micología,0,80509,0 -13089,41116143,Controles de calidad o calibradores o estándares de parasitología o micología,,0,80509,0 -13090,41116144,Kits o suministros para pruebas de virología,,0,80509,0 -13091,41116145,Controles de calidad o calibradores o estándares para virología,,0,80509,0 -13092,41116146,Kits o suministros para pruebas de toxicología,Kits de prueba de toxicología o suministros,0,80509,0 -13093,41116147,Controles de calidad o calibradores o estándares para toxicología,,0,80509,0 -13094,41116148,Kits o suministros para pruebas de citometría de flujo,,0,80509,0 -13095,41116150,Kits o suministros para pruebas de androgénesis y fertilidad,,0,80509,0 -13096,41116152,Controles de calidad o calibradores o estándares para citometría de flujo,,0,80509,0 -13097,41116153,Controles de calidad o calibradores o estándares para inmunohistoquímica,,0,80509,0 -13098,41116154,Controles de calidad o calibradores o estándares para contar y determinar el tamaño de las partículas,,0,80509,0 -13099,41116155,Medio de crecimiento de cultura de células y biología molecular,,0,80509,0 -13100,41116156,Reactivos o soluciones o tinturas de virología,,0,80509,0 -13101,41116157,Medio de cultura de tejidos y células de virología,Medio de cultura de tejidos y células de virología,0,80509,0 -13102,41116200,Suministros y equipo para lugares de tratamiento de pacientes,,0,80509,0 -13103,41116201,Monitores o medidores de glucosa,,0,80509,0 -13104,41116202,Monitores o medidores de colesterol,,0,80509,0 -13105,41116203,Accesorios para monitores o medidores,,0,80509,0 -13106,41116205,Kits de pruebas rápidas,,0,80509,0 -13107,41116206,Bebidas de diagnóstico para pruebas de laboratorio,,0,80509,0 -13108,41116207,Monitores o medidores de hemoglobina o hematocritos,,0,80509,0 -13109,41116208,Monitores o medidores de parámetros múltiples de química sanguínea,,0,80509,0 -13110,41116209,Monitores o medidores de múltiples parámetros de coagulación de la sangre,,0,80509,0 -13111,41116210,Monitores o medidores analizadores de aliento de desorden metabólico,,0,80509,0 -13112,41116211,Analizador de aliento de etanol,,0,80509,0 -13113,41116212,Controles de calidad o calibradores o estándares para monitores o medidores,,0,80509,0 -13114,41116400,Instrumentos de medición de la aceleración y la vibración,,0,80509,0 -13115,41116401,Martillos de impacto,,0,80509,0 -13116,41116500,Piezas y accesorios de instrumentos,,0,80509,0 -13117,41116501,Diales o kits de diales de medición,,0,80509,0 -13118,41116502,Clip eléctrico,,0,80509,0 -13119,41121500,Equipo y suministros de pipetas y manipulación de líquidos,,0,80509,0 -13120,41121501,Sistemas de manipulación líquida automática o robótica,,0,80509,0 -13121,41121502,Diluidores de laboratorio,,0,80509,0 -13122,41121503,Pipeta de desplazamiento de aire multicanal manuales,,0,80509,0 -13123,41121504,Pipeta de desplazamiento de aire de un solo canal manuales,,0,80509,0 -13124,41121505,Pipeta de desplazamiento positivo de un solo canal manuales,,0,80509,0 -13125,41121506,Pipeta repetidoras de un solo canal manuales,,0,80509,0 -13126,41121507,Pipeta de un solo canal electrónicas,,0,80509,0 -13127,41121508,Pipeta multicanales electrónicas,,0,80509,0 -13128,41121509,Pipetas pasteur o de transferencia,,0,80509,0 -13129,41121510,Pipetas volumétricas,,0,80509,0 -13130,41121511,Pipetas serológicas,,0,80509,0 -13131,41121513,Pipetas de caída,,0,80509,0 -13132,41121514,Bombas de pipetas,,0,80509,0 -13133,41121515,Bombillos de pipetas,Bombillos de pipetas,0,80509,0 -13134,41121516,Dispensadores de tapas de botella,,0,80509,0 -13135,41121517,Insertos o accesorios para pipeteras,,0,80509,0 -13136,41121600,Puntas de pipeta,,0,80509,0 -13137,41121601,Filtro de boquilla de pipeta,,0,80509,0 -13138,41121602,Puntas de pipeta de barrera de aerosol,,0,80509,0 -13139,41121603,Puntas de pipeta de baja retención,,0,80509,0 -13140,41121604,Puntas de pipeta de referencia,,0,80509,0 -13141,41121605,Puntas de pipeta ultra micro,,0,80509,0 -13142,41121606,Puntas de pipeta de carga de gel,,0,80509,0 -13143,41121607,Puntas de pipeta universales,,0,80509,0 -13144,41121608,Puntas de pipeta robóticas,,0,80509,0 -13145,41121609,Puntas de pipeta de volumen variable,,0,80509,0 -13146,41121700,Tubos de ensayo,,0,80509,0 -13147,41121701,Tubos de ensayo general o multipropósito,,0,80509,0 -13148,41121702,Tubos micro centrífugos,,0,80509,0 -13149,41121703,Tubos centrífugos,,0,80509,0 -13150,41121704,Tubos criogénicos,,0,80509,0 -13151,41121705,Tubos de resonancia magnética nuclear nmr,,0,80509,0 -13152,41121706,Tubos de cultivo,,0,80509,0 -13153,41121707,Tubos de pruebas de separador,,0,80509,0 -13154,41121708,Tubos de pruebas de anti coagulación,,0,80509,0 -13155,41121709,Tubos capilares o hematocritos,,0,80509,0 -13156,41121710,Cierres o tapas para tubos de ensayo,,0,80509,0 -13157,41121711,Tubos o accesorios para pruebas de análisis de orina,,0,80509,0 -13158,41121712,Tubos viscosímetros,,0,80509,0 -13159,41121713,Analizador de tamaño de arena,,0,80509,0 -13160,41121800,Artículos de vidrio o plástico y suministros generales de laboratorio,,0,80509,0 -13161,41121801,Vasos de observación para laboratorio,,0,80509,0 -13162,41121802,Varillas para revolver para laboratorio,,0,80509,0 -13163,41121803,Vasos de precipitados para laboratorio,,0,80509,0 -13164,41121804,Redomas para laboratorio,,0,80509,0 -13165,41121805,Cilindros graduados para laboratorio,,0,80509,0 -13166,41121806,Frascos para laboratorio,,0,80509,0 -13167,41121807,Ampollas para laboratorio,,0,80509,0 -13168,41121808,Buretas para laboratorio,,0,80509,0 -13169,41121809,Embudos para laboratorio,,0,80509,0 -13170,41121810,Platos o frascos para tintura para laboratorio,,0,80509,0 -13171,41121811,Kits de micro química para laboratorio,,0,80509,0 -13172,41121812,Platos para laboratorio,,0,80509,0 -13173,41121813,Cubetas,,0,80509,0 -13174,41121814,Tapas o forros o forros deslizantes para laboratorio,,0,80509,0 -13175,41121815,Adaptadores o conectores o instalaciones para laboratorio,,0,80509,0 -13176,41121816,Tubo de vidrio para laboratorio,,0,80509,0 -13177,41121818,Botella de almacenamiento para laboratorio,,0,80509,0 -13178,41121819,Botella de gotero y tapa de gotero,,0,80509,0 -13179,41122000,Jeringuillas de laboratorio o de muestreo,,0,80509,0 -13180,41122001,Jeringas de cromatografía,,0,80509,0 -13181,41122002,Agujas para jeringas de cromatografía,,0,80509,0 -13182,41122003,Adaptadores o accesorios para jeringa,,0,80509,0 -13183,41122004,Jeringas para muestras,,0,80509,0 -13184,41122100,Suministros de cultivo de tejido y cribado de alta definición,,0,80509,0 -13185,41122101,Platos o placas petri,,0,80509,0 -13186,41122102,Platos multi pocillo,Platos multi pocillo,0,80509,0 -13187,41122103,Espátulas de células,,0,80509,0 -13188,41122104,Frascos de cultivo de tejidos,,0,80509,0 -13189,41122105,Botellas de cultivo rotatorias,,0,80509,0 -13190,41122106,Dispositivos de inoculación,,0,80509,0 -13191,41122107,Platos o placas o insertos recubiertos para cultivo de tejidos,,0,80509,0 -13192,41122108,Presillos o agujas para inoculación microbiológica,,0,80509,0 -13193,41122109,Almohadilla de petri,,0,80509,0 -13194,41122110,Dispensador de almohadillas de petri,,0,80509,0 -13195,41122200,Crisoles,,0,80509,0 -13196,41122201,Crisoles de vidrio,,0,80509,0 -13197,41122202,Crisoles cerámicos,,0,80509,0 -13198,41122203,Crisoles de metal,,0,80509,0 -13199,41122400,Instrumentos de laboratorio,,0,80509,0 -13200,41122401,"Barras giratorias, barras para revolver o gotas para revolver magnéticas",,0,80509,0 -13201,41122402,Recuperadores magnéticos de barras giratorias o recuperadores de barras giratorias,,0,80509,0 -13202,41122403,Espátulas para laboratorio,,0,80509,0 -13203,41122404,Tenazas para laboratorio,,0,80509,0 -13204,41122405,Fórceps para laboratorio,,0,80509,0 -13205,41122406,Cuchillos para laboratorio,,0,80509,0 -13206,41122407,Escalpelos para laboratorio,,0,80509,0 -13207,41122409,Herramientas para laboratorio,,0,80509,0 -13208,41122410,Película sellante para laboratorio,,0,80509,0 -13209,41122411,Cronómetros o relojes para laboratorio,,0,80509,0 -13210,41122412,Sellantes de tubos para laboratorio,,0,80509,0 -13211,41122413,Pinzas para laboratorio,,0,80509,0 -13212,41122414,Cuchara para laboratorio,,0,80509,0 -13213,41122415,Cristales de bioseparación centrífuga,,0,80509,0 -13214,41122500,"Corchos, tapones y accesorios de laboratorio",,0,80509,0 -13215,41122501,Corchos para laboratorio,,0,80509,0 -13216,41122502,Tapones para laboratorio,,0,80509,0 -13217,41122503,Porta corchos para laboratorio,,0,80509,0 -13218,41122600,Portaobjetos de laboratorio y suministros,,0,80509,0 -13219,41122601,Portaobjetos para microscopios,,0,80509,0 -13220,41122602,Portaobjetos de microscopio,,0,80509,0 -13221,41122603,Papel de lentes para microscopio,,0,80509,0 -13222,41122604,Hemocitómetros,,0,80509,0 -13223,41122605,Aceite de inmersión para microscopios,,0,80509,0 -13224,41122606,Dispensadores de portaobjetos para microscopio,,0,80509,0 -13225,41122607,Solución limpiadora de lentes para microscopio,,0,80509,0 -13226,41122700,Cintas y etiquetas de laboratorio,,0,80509,0 -13227,41122701,Etiquetas para portaobjetos o especímenes,,0,80509,0 -13228,41122702,Cintas etiquetadoras,,0,80509,0 -13229,41122703,Cintas de seguridad,,0,80509,0 -13230,41122704,Cintas contra alteración,Cintas de seguridad contra alteración,0,80509,0 -13231,41122800,"Estantes, soportes y bandejas de laboratorio",,0,80509,0 -13232,41122801,Estantes o soportes para pipetas,,0,80509,0 -13233,41122802,Estantes para portaobjetos de microscopios,,0,80509,0 -13234,41122803,Estantes o soportes para tubos para sedimentación,,0,80509,0 -13235,41122804,Estantes para tubos de ensayo,,0,80509,0 -13236,41122805,Estantes para secado,,0,80509,0 -13237,41122806,Estantes para unidades de criopreservación,,0,80509,0 -13238,41122807,Bandejas para disección,,0,80509,0 -13239,41122808,Bandejas para propósitos generales,Bandejas para propósitos generales,0,80509,0 -13240,41122809,Estantes para placas petri,,0,80509,0 -13241,41122810,Estantes de almacenamiento de reactivos para la mesa de trabajo,,0,80509,0 -13242,41122811,Dispensador de tubos de cultivo y prueba,,0,80509,0 -13243,41123000,Desecadores y desecantes de laboratorio,,0,80509,0 -13244,41123001,Deshidratadores de frasco,,0,80509,0 -13245,41123002,Deshidratadores de gabinete,,0,80509,0 -13246,41123003,Desecantes,,0,80509,0 -13247,41123004,Deshidratadores de vacío,,0,80509,0 -13248,41123005,Contenedor de almacenamiento de disco desecante microbiológicamente susceptible,,0,80509,0 -13249,41123300,Contenedores y armarios de almacenamiento general para laboratorio,,0,80509,0 -13250,41123302,Cajas o folders para portaobjetos para microscopios,,0,80509,0 -13251,41123303,Gabinetes para portaobjetos para microscopios,Gabinetes para portaobjetos para microscopios,0,80509,0 -13252,41123304,Cajas de almacenamiento criogénicas,Cajas para almacenamiento de viales criogénicos,0,80509,0 -13253,41123305,Gabinetes para casetes de tejidos o histología,Gabinetes para casetes de tejidos o histología,0,80509,0 -13254,41123306,Accesorios para casetes de tejidos o histología,Accesorios para casetes de tejidos o histología,0,80509,0 -13255,41123400,Dispositivos de dosificación,,0,80509,0 -13256,41123401,Tazas dosificadoras,,0,80509,0 -13257,41123402,Cucharas dosificadoras,,0,80509,0 -13258,41123403,Goteros dosificadores,,0,80509,0 -13259,42121500,Equipo veterinario,,0,80509,0 -13260,42121501,Probadores de presión sanguínea para uso veterinario,,0,80509,0 -13261,42121502,Probador quimiógrafo para uso veterinario,Probador quimógrafo para uso veterinario,0,80509,0 -13262,42121503,Probador pirogénico para uso veterinario,,0,80509,0 -13263,42121504,Equipo estereotóxico para uso veterinario,Equipo esterotáctico para uso veterinario,0,80509,0 -13264,42121505,Electrocardiógrafo ecg para uso veterinario,,0,80509,0 -13265,42121506,Sets de instrumentos quirúrgicos para uso veterinario,,0,80509,0 -13266,42121507,Unidades o accesorios de inyección o succión para uso veterinario,,0,80509,0 -13267,42121508,Sets de botellas para uso veterinario,Sets de botellas para uso veterinario,0,80509,0 -13268,42121509,Termómetros clínicos para uso veterinario,,0,80509,0 -13269,42121510,Cajas de instrumentos o accesorios para uso veterinario,,0,80509,0 -13270,42121511,Estuches enrollables para instrumentos o accesorios de uso veterinario,,0,80509,0 -13271,42121512,Espéculos para uso veterinario,,0,80509,0 -13272,42121513,Instrumentos de castración para uso veterinario,,0,80509,0 -13273,42121514,Kits de fijación externa para uso veterinario,Kits de fijación externa para uso veterinario,0,80509,0 -13274,42121515,Limas o cortaúñas para uso veterinario,,0,80509,0 -13275,42121600,Productos veterinarios,,0,80509,0 -13276,42121601,Productos gastrointestinales para uso veterinario,Medicamentos contra problemas gastrointestinales de uso veterinario,0,80509,0 -13277,42121602,Productos para sangre o formación de sangre para uso veterinario,Productos para sangre o formación de sangre para uso veterinario,0,80509,0 -13278,42121603,Productos para el sistema respiratorio para uso veterinario,Medicamentos para el sistema respiratorio para uso veterinario,0,80509,0 -13279,42121604,Productos para el sistema musculo esquelético o nervioso para uso veterinario,Medicamentos para el sistema musculo esquelético o nervioso para uso veterinario,0,80509,0 -13280,42121605,Productos para el sistema cardiovascular para uso veterinario,Medicamentos para el sistema cardiovascular para uso veterinario,0,80509,0 -13281,42121606,Productos dermatológicos o anti protozoarios para uso veterinario,Medicamentos dermatológicos o anti protozoarios para uso veterinario,0,80509,0 -13282,42121607,Productos para el sistema sexual genital urinario u hormonales para uso veterinario,Medicamentos para el sistema sexual genital urinario u hormonales para uso veterinario,0,80509,0 -13283,42121608,Productos dentales para uso veterinario,Productos dentales para uso veterinario,0,80509,0 -13284,42121700,Mobiliario clínico veterinario,,0,80509,0 -13285,42121701,Tablas quirúrgicas para uso veterinario,,0,80509,0 -13286,42121702,Armarios de almacenamiento para uso veterinario,,0,80509,0 -13287,42121800,Equipo y suministros para inseminación artificial veterinaria,,0,80509,0 -13288,42121801,Máquina para inseminación artificial,,0,80509,0 -13289,42121802,Instrumento para recolección de semen,,0,80509,0 -13290,42121803,Estimulador de eyaculación eléctrico,,0,80509,0 -13291,42121804,Instrumento para empaquetar semen,,0,80509,0 -13292,42131500,Vestuario para pacientes,,0,80509,0 -13293,42131501,Petos para pacientes,Petos para pacientes,0,80509,0 -13294,42131502,Gorras para pacientes,,0,80509,0 -13295,42131503,Capas de examen para pacientes,,0,80509,0 -13296,42131504,Batas para pacientes,,0,80509,0 -13297,42131505,Camisas o chalecos para pacientes bebés,,0,80509,0 -13298,42131506,Chaquetas para pacientes,Chaquetas para pacientes,0,80509,0 -13299,42131507,Pantuflas para pacientes,,0,80509,0 -13300,42131508,Pijamas para pacientes,,0,80509,0 -13301,42131509,Batas de hospital,,0,80509,0 -13302,42131510,Pantalones para pacientes,,0,80509,0 -13303,42131511,Prenda para hidroterapia,,0,80509,0 -13304,42131512,Gorra o sombrero o material para gorra para paciente bebé,,0,80509,0 -13305,42131600,Vestuario para el personal sanitario y artículos relacionados,,0,80509,0 -13306,42131601,Delantales o petos para personal médico,Delantales o petos para personal médico,0,80509,0 -13307,42131602,Cobertores de barba para personal médico,Cubierta de barba para personal médico,0,80509,0 -13308,42131603,Blusas o blusones para personal médico,Blusas o blusones para personal médico,0,80509,0 -13309,42131604,Gorro de quirófano para personal médico,,0,80509,0 -13310,42131605,Overoles para personal médico,,0,80509,0 -13311,42131606,Máscaras quirúrgicas o de aislamiento para personal médico,Cubrebocas,0,80509,0 -13312,42131607,Chaquetas o batas para personal médico,,0,80509,0 -13313,42131608,Vestidos para cirugía para personal médico,Ropa de cirugía para personal médico,0,80509,0 -13314,42131609,Cubiertas para zapatos para personal médico,,0,80509,0 -13315,42131610,Protección de mangas para personal médico,,0,80509,0 -13316,42131611,Gorros o capuchas para cirujano,,0,80509,0 -13317,42131612,Batas de aislamiento para personal médico,,0,80509,0 -13318,42131613,Protectores de ojos o visores para personal médico,,0,80509,0 -13319,42131700,Prendas textiles quirúrgicas,,0,80509,0 -13320,42131701,Cortinas de cirugía,,0,80509,0 -13321,42131702,Batas de cirugía,,0,80509,0 -13322,42131704,Toallas de cirugía,Toallas quirúrgicas,0,80509,0 -13323,42131705,Pantalones (“leggings”) de cirugía,,0,80509,0 -13324,42131706,Vestidos enteros de cirugía,Vestidos enteros de cirugía,0,80509,0 -13325,42131707,Vestidos o cascos o máscaras faciales o accesorios de aislamiento de cirugía,Trajes de aislamiento quirúrgico o cascos o mascarillas o accesorios,0,80509,0 -13326,42131708,Paquete o kit para limpieza quirúrgica o rotación de salas quirúrgicas,,0,80509,0 -13327,42132100,Ropa blanca del hospital,,0,80509,0 -13328,42132101,Protectores de colchón o silla para hospital,,0,80509,0 -13329,42132102,Sábanas elásticas médicas,,0,80509,0 -13330,42132103,Cortinas de barrera para pacientes,,0,80509,0 -13331,42132104,Almohadas antimicrobianas para hospital,,0,80509,0 -13332,42132105,Sábanas para hospital,,0,80509,0 -13333,42132106,Colchas o cubrecamas para hospital,,0,80509,0 -13334,42132107,Cobijas para hospital,,0,80509,0 -13335,42132108,Fundas protectoras de almohada para hospital,,0,80509,0 -13336,42132200,Guantes y accesorios médicos,,0,80509,0 -13337,42132201,Cajas o dispensadores de guantes médicos,,0,80509,0 -13338,42132202,Protector de caucho para dedos.,Protectores de látex para dedos,0,80509,0 -13339,42132203,Guantes de examen o para procedimientos no quirúrgicos,,0,80509,0 -13340,42132204,Recubrimientos interiores para guantes médicos,Recubrimientos interiores para guantes médicos,0,80509,0 -13341,42132205,Guantes de cirugía,Guantes quirúrgicos,0,80509,0 -13342,42141500,Bolas de algodón y copitos aplicadores,,0,80509,0 -13343,42141501,Bolas o fibra de algodón,,0,80509,0 -13344,42141502,Palitos (copitos) con punta de fibra,"Hisopo, Palitos (copitos) con punta de fibra",0,80509,0 -13345,42141503,Toallitas de preparación de la piel,,0,80509,0 -13346,42141504,Aplicadores o absorbentes medicados,,0,80509,0 -13347,42141600,Palanganas y Bacinillas de cama y orinales y equipos de ingreso,,0,80509,0 -13348,42141601,Kits de admisión para el cuidado del paciente,,0,80509,0 -13349,42141602,Patos (bacinillas) para uso general,,0,80509,0 -13350,42141603,Recipientes para emesis (vómito),,0,80509,0 -13351,42141605,Recipientes o cuencos para soluciones o mezclas médicas,,0,80509,0 -13352,42141606,Recipientes multipropósito para usos médicos,Recipientes multifuncionales para uso médico,0,80509,0 -13353,42141607,Orinales de uso general para pacientes,,0,80509,0 -13354,42141608,Bolsas o recubrimientos para patos (bacinillas),,0,80509,0 -13355,42141700,Productos para la prevención de decúbito,,0,80509,0 -13356,42141701,Sistemas de presión alterna,,0,80509,0 -13357,42141702,Marcos o elevadores de cobijas,,0,80509,0 -13358,42141703,Cunas para extremidades,,0,80509,0 -13359,42141704,Recubrimientos para colchones,,0,80509,0 -13360,42141705,Almohadones o almohadillas o almohadas para la posición del paciente,,0,80509,0 -13361,42141800,Equipo de electroterapia,,0,80509,0 -13362,42141801,Unidades de combinación de electroterapia,,0,80509,0 -13363,42141802,Electrodos o accesorios para electroterapia,,0,80509,0 -13364,42141803,Cables o alambres de plomo para electroterapia,,0,80509,0 -13365,42141804,Simuladores galvánicos o farádicos,,0,80509,0 -13366,42141805,Estimuladores o kits neuromusculares,,0,80509,0 -13367,42141806,Unidades de diatermia de onda corta,,0,80509,0 -13368,42141807,Unidades de estimulación nerviosa eléctrica transcutánea,,0,80509,0 -13369,42141808,Electrodos de diatermia,,0,80509,0 -13370,42141809,Sets de estimulador muscular,,0,80509,0 -13371,42141810,Electrodo de iontoforesis,,0,80509,0 -13372,42141811,Sistema de terapia de ondas de choque extracorpóreas,,0,80509,0 -13373,42141812,Generador de electricidad estática para estimulación eléctrica,,0,80509,0 -13374,42141813,Dispositivo electro convulsivo,,0,80509,0 -13375,42141814,Estimulador de electricidad estática,,0,80509,0 -13376,42141815,Estimulador de ondas electromagnéticas de alta frecuencia,,0,80509,0 -13377,42141816,Estimulador de terapia electromagnética,,0,80509,0 -13378,42141817,Estimulador de pulso magnético,,0,80509,0 -13379,42141900,Suministros para la administración de enema,,0,80509,0 -13380,42141901,Baldes para enema,,0,80509,0 -13381,42141902,Bolsas para enema,,0,80509,0 -13382,42141903,Kits o accesorios para enema,,0,80509,0 -13383,42141904,Tubos o tapas o pinzas para enema,,0,80509,0 -13384,42141905,Jabones para enema,,0,80509,0 -13385,42142000,"Instrumentos de grado bajo ""floor grade""",,0,80509,0 -13386,42142001,Fórceps o homeostatos de bajo grado,Pinzas homeostáticas,0,80509,0 -13387,42142002,Cuchillo de bajo grado,,0,80509,0 -13388,42142003,Manijas para cuchillo de bajo grado,,0,80509,0 -13389,42142004,Corta uñas de bajo grado,Pinza tipo alicata,0,80509,0 -13390,42142005,Sujeta agujas de bajo grado,Porta agujas,0,80509,0 -13391,42142006,Retractores de bajo grado,,0,80509,0 -13392,42142007,Tijeras de bajo grado,,0,80509,0 -13393,42142100,Productos de terapia de frío y de calor,,0,80509,0 -13394,42142101,Cubiertas para productos para terapia de calor o frío,,0,80509,0 -13395,42142102,Unidades o accesorios de enfriamiento para almacenamiento frío para uso médico,Unidades o sistemas de calentamiento o enfriamiento terapéutico,0,80509,0 -13396,42142103,Lámparas de calor o sus accesorios para uso médico,,0,80509,0 -13397,42142104,Hidro coladores o sus accesorios para uso médico,,0,80509,0 -13398,42142105,Unidades o sistemas de calentamiento o enfriamiento terapéutico,Unidades o sistemas de calefacción o refrigeración terapéutica,0,80509,0 -13399,42142106,Cobijas o cortinas de calentamiento o enfriamiento terapéutico,Mantas o cortinas para calefacción o refrigeración terapéutica,0,80509,0 -13400,42142107,Sistema y accesorios de terapia de compresión crio terapéutica,,0,80509,0 -13401,42142108,Almohadillas o compresas o bolsas de calentamiento o enfriamiento terapéutico,Almohadillas o compresas o bolsas de calefacción o refrigeración terapéuticao,0,80509,0 -13402,42142109,Guantes de terapia para terapia de calor o frío,Guantes para tratamiento terapéutico con frío o calor,0,80509,0 -13403,42142110,Botellas para terapia de calor o frío,Bolsa terapéuticas de agua caliente o fría,0,80509,0 -13404,42142111,Bolsas o almohadas de hielo terapéuticas,,0,80509,0 -13405,42142112,Baños de parafina terapéuticos o sus accesorios,,0,80509,0 -13406,42142113,Máscaras para senos nasales terapéuticas,Antifaz terapéutico para senos nasales,0,80509,0 -13407,42142114,Pantalones terapéuticos para terapia fría o caliente,,0,80509,0 -13408,42142119,Aparatos de hipotermia,,0,80509,0 -13409,42142120,Manga terapéutica para terapia de crio compresión,,0,80509,0 -13410,42142121,Gabinete de vapor húmedo,,0,80509,0 -13411,42142200,Productos de hidroterapia,,0,80509,0 -13412,42142201,Baños o tanques de hidroterapia para las extremidades,,0,80509,0 -13413,42142202,Baños o tanques de hidroterapia para inmersión total del cuerpo,,0,80509,0 -13414,42142203,Accesorios para baños o tanques de hidroterapia,,0,80509,0 -13415,42142204,Asientos para baños de hidroterapia,,0,80509,0 -13416,42142400,Productos médicos de vacío o de succión,,0,80509,0 -13417,42142401,Solidificadores de fluidos para uso médico,,0,80509,0 -13418,42142402,Cánulas o tubos o accesorios de succión para uso médico,,0,80509,0 -13419,42142403,Contenedores de succión para uso médico,,0,80509,0 -13420,42142404,Aplicaciones al vacío o de succión para uso médico,Aparatos médicos de succión o de vacío,0,80509,0 -13421,42142406,Sets o kits de succión para uso médico,,0,80509,0 -13422,42142407,Estuches para cánulas de succión para uso médico,,0,80509,0 -13423,42142500,Agujas de inyección y aspiración y accesorios,,0,80509,0 -13424,42142501,Agujas para amniocentesis,,0,80509,0 -13425,42142502,Agujas para anestesia,,0,80509,0 -13426,42142503,Agujas arteriales,Catéteres arteriales,0,80509,0 -13427,42142504,Agujas para biopsia,,0,80509,0 -13428,42142505,Soportes para agujas de recolección de sangre,Porta aguja de extracción de sangre,0,80509,0 -13429,42142506,Agujas romas,Agujas romas,0,80509,0 -13430,42142507,Agujas mariposa,,0,80509,0 -13431,42142509,Bandejas o accesorios epidurales,,0,80509,0 -13432,42142510,Agujas de filtro,,0,80509,0 -13433,42142511,Tapas o dispositivos protectores o accesorios para agujas,,0,80509,0 -13434,42142512,Agujas de fístula,Agujas para fístula,0,80509,0 -13435,42142513,Agujas para procedimientos de radiología,,0,80509,0 -13436,42142514,Bandejas o agujas espinales,,0,80509,0 -13437,42142515,Agujas de tubo al vacío,Agujas de tuvo de vacío,0,80509,0 -13438,42142516,Agujas de transferencia,,0,80509,0 -13439,42142517,Tubos de extensión,,0,80509,0 -13440,42142518,Productos o accesorios aspiradores para biopsia,,0,80509,0 -13441,42142519,Guías para agujas,,0,80509,0 -13442,42142520,Alfileres dispensadores,,0,80509,0 -13443,42142521,Agujas para recolección de sangre,,0,80509,0 -13444,42142522,Agujas quirúrgicas para oídos o narices o gargantas,,0,80509,0 -13445,42142523,Agujas hipodérmicas,,0,80509,0 -13446,42142524,Agujas o sets para grabar,,0,80509,0 -13447,42142525,Agujas de irrigación,,0,80509,0 -13448,42142526,Protectores de agujas,,0,80509,0 -13449,42142527,Alambres para limpiar agujas,,0,80509,0 -13450,42142528,Agujas o sets para punción del esternón,,0,80509,0 -13451,42142529,Bandejas para agujas o porta agujas,,0,80509,0 -13452,42142530,Agujas para procedimientos diagnósticos,,0,80509,0 -13453,42142531,Contenedores o carritos o accesorios para desecho de agujas o cuchillas u otros objetos afilados,,0,80509,0 -13454,42142532,Agujas o kits o accesorios para pericardiocentesis,,0,80509,0 -13455,42142533,Agujas para administración de puertos intravenosos o arteriales,,0,80509,0 -13456,42142534,Aguja interósea,,0,80509,0 -13457,42142535,Aguja o bandeja para bloqueo nervioso anestésico,,0,80509,0 -13458,42142536,Aguja y catéter para toracentesis o paracentesis,,0,80509,0 -13459,42142537,Set o bandeja para toracentesis,,0,80509,0 -13460,42142538,Set o bandeja para paracentesis,,0,80509,0 -13461,42142600,Jeringas y accesorios,,0,80509,0 -13462,42142601,Jeringas para aspiración o irrigación médica,,0,80509,0 -13463,42142602,Peras de caucho para usos médicos,,0,80509,0 -13464,42142603,Jeringas de cartucho para uso médico,,0,80509,0 -13465,42142604,Jeringas de punta de catéter para uso médico,,0,80509,0 -13466,42142605,Jeringas para oídos para uso médico,,0,80509,0 -13467,42142606,Jeringas de entrega medida para uso médico,,0,80509,0 -13468,42142607,Micro jeringas para uso médico,,0,80509,0 -13469,42142608,Jeringas sin agujas para uso médico,,0,80509,0 -13470,42142609,Jeringas con agujas para uso médico,,0,80509,0 -13471,42142610,Jeringas de medicación líquida oral,,0,80509,0 -13472,42142611,Jeringas de tuberculina,,0,80509,0 -13473,42142612,Sets de jeringas de irrigación,,0,80509,0 -13474,42142613,Pistolas de inyección,,0,80509,0 -13475,42142614,Aparatos o accesorios para inyección hipodérmica,,0,80509,0 -13476,42142615,Accesorios para jeringas,,0,80509,0 -13477,42142616,Jeringas de recolección de sangre,,0,80509,0 -13478,42142617,Jeringas de fuente,,0,80509,0 -13479,42142618,Kits de jeringas de análisis de gas en la sangre,,0,80509,0 -13480,42142619,Jeringa de lavado pre llenada,,0,80509,0 -13481,42142620,Controlador o pistola de inyección de aguja interósea,,0,80509,0 -13482,42142700,Suministros urológicos,,0,80509,0 -13483,42142701,Catéteres urinarios supra púbicos,,0,80509,0 -13484,42142702,Catéteres urinarios uretrales,,0,80509,0 -13485,42142703,Tapones o pinzas para catéteres urinarios,,0,80509,0 -13486,42142704,Bolsas o medidores para drenaje urinario,,0,80509,0 -13487,42142705,Tiras o sujetadores para bolsas de drenaje urinario,,0,80509,0 -13488,42142706,Bandejas o paquetes o kits para procedimientos urológicos,,0,80509,0 -13489,42142707,Tubos de irrigación urinaria,,0,80509,0 -13490,42142708,Adaptadores para instrumentos de urología,,0,80509,0 -13491,42142709,Accesorios o sets para nefrostomía,,0,80509,0 -13492,42142710,Tubos o accesorios para drenaje urinario,,0,80509,0 -13493,42142711,Sets de auscultación uretral,,0,80509,0 -13494,42142712,Sets o accesorios de remoción de cálculos,,0,80509,0 -13495,42142713,Cubiertas o sets urológicos,,0,80509,0 -13496,42142714,Percoladores urológicos,,0,80509,0 -13497,42142715,Kits o accesorios de cateterización urológica,,0,80509,0 -13498,42142716,Dispositivo pesario,,0,80509,0 -13499,42142717,Catéter o drenaje para nefrostomía,,0,80509,0 -13500,42142718,Dispositivo de soporte y alargamiento del pene,,0,80509,0 -13501,42142719,Uretrotomo,,0,80509,0 -13502,42142720,Catéter urinario externo masculino,,0,80509,0 -13503,42142721,Alambre guía o alambre de deslizamiento urológico,,0,80509,0 -13504,42142800,Suministros y equipos para terapia vascular y de compresión,,0,80509,0 -13505,42142801,Dispositivos o tubos para compresión secuencial vascular,,0,80509,0 -13506,42142802,Prendas o soporte para compresión o vasculares,,0,80509,0 -13507,42142900,Corrección de la visión o gafas cosméticas y productos relacionados,,0,80509,0 -13508,42142901,Anteojos,,0,80509,0 -13509,42142902,Lentes para anteojos,,0,80509,0 -13510,42142903,Monturas para anteojos,,0,80509,0 -13511,42142904,Hardware para anteojos,,0,80509,0 -13512,42142905,Anteojos de sol,,0,80509,0 -13513,42142906,Estuches para anteojos,,0,80509,0 -13514,42142907,Pañitos para limpiar anteojos,,0,80509,0 -13515,42142908,Kits para limpiar anteojos,,0,80509,0 -13516,42142909,Retenedores de anteojos,,0,80509,0 -13517,42142910,Estuches para lentes de contacto,,0,80509,0 -13518,42142911,Insertores o removedores de lentes de contacto,,0,80509,0 -13519,42142912,Medidores de radio de lentes de contacto,,0,80509,0 -13520,42142913,Lentes de contacto,,0,80509,0 -13521,42142914,Solución limpiadora o humidificadora de lentes de contacto,,0,80509,0 -13522,42143100,Equipo y suministros de obstetricia y ginecología,,0,80509,0 -13523,42143101,Catéteres o kits de cateterización intrauterina,,0,80509,0 -13524,42143102,Dispositivos o accesorios uterinos,,0,80509,0 -13525,42143103,Dispositivos o accesorios contraceptivos intrauterinos,,0,80509,0 -13526,42143104,Kits o accesorios para amniocentesis,,0,80509,0 -13527,42143105,Unidades o accesorios de extracción obstétrica,,0,80509,0 -13528,42143106,Kits o accesorios de drenaje ginecológico,,0,80509,0 -13529,42143107,Perinómetro,,0,80509,0 -13530,42143108,Cámara de descompresión abdominal,,0,80509,0 -13531,42143200,Equipo y suministros de tratamiento de la fertilidad y la esterilidad,,0,80509,0 -13532,42143201,Kits o accesorios de preparación de semen,,0,80509,0 -13533,42143202,Productos o kits o accesorios para el tratamiento de la impotencia,,0,80509,0 -13534,42143203,Catéter de inseminación artificial,,0,80509,0 -13535,42143204,Catéter de transferencia de embriones,,0,80509,0 -13536,42143400,Equipo y suministros de control de hiperhidrosis,,0,80509,0 -13537,42143401,Equipos o accesorios para el control del sudor,,0,80509,0 -13538,42143500,Productos y accesorios de otorrinolaringología,,0,80509,0 -13539,42143501,Ayudas para la limpieza de moldes auditivos,,0,80509,0 -13540,42143502,Dispositivos o accesorios para irrigación nasal,,0,80509,0 -13541,42143503,Dispositivos o bombas para el control de la hemorragia nasal,,0,80509,0 -13542,42143504,Moldes auditivos o sus accesorios,,0,80509,0 -13543,42143505,Kits de reparación de moldes auditivos,,0,80509,0 -13544,42143506,Sets filiformes para la trompa de eustaquio,,0,80509,0 -13545,42143507,Paquetes de medicación de oídos,,0,80509,0 -13546,42143508,Capuchas de viento para oídos,,0,80509,0 -13547,42143509,Pegantes o cementos para moldes auditivos,,0,80509,0 -13548,42143510,Pantallas protectoras de viento para oídos,,0,80509,0 -13549,42143511,Ganchos de cera de oídos,,0,80509,0 -13550,42143512,Mechas para oídos,,0,80509,0 -13551,42143513,Instrumentos o accesorios otológicos,,0,80509,0 -13552,42143514,Sistema para lavado de oídos,,0,80509,0 -13553,42143600,Sujeciones y accesorios,,0,80509,0 -13554,42143601,Chalecos y chaquetas de fuerza (de restricción),,0,80509,0 -13555,42143602,Elementos de restricción de cintura y torso,,0,80509,0 -13556,42143603,Elementos de restricción de extremidades,,0,80509,0 -13557,42143604,Elementos de restricción de cabeza no para ems,,0,80509,0 -13558,42143605,Correas o hebillas o accesorios o suministros de restricción,,0,80509,0 -13559,42143606,Elementos de restricción para todo el cuerpo,,0,80509,0 -13560,42143607,Sensores o alarmas o accesorios de movimiento de pacientes,,0,80509,0 -13561,42143608,Dispositivos o accesorios para estabilización o prevención de caídas de pacientes,,0,80509,0 -13562,42143609,Tablas o accesorios para restricción de pacientes,,0,80509,0 -13563,42143700,Sistemas y accesorios de fototerapia,,0,80509,0 -13564,42143701,Circuladores de aire o accesorios para fototerapia,,0,80509,0 -13565,42143702,Cobijas para fototerapia,,0,80509,0 -13566,42143703,Bombillos para fototerapia,,0,80509,0 -13567,42143704,Esteras de luz para fototerapia,,0,80509,0 -13568,42143705,Dispositivos de protección de pacientes para fototerapia,,0,80509,0 -13569,42143706,Unidades eléctricas para fototerapia,,0,80509,0 -13570,42143707,Calentadores de camas o accesorios para fototerapia,,0,80509,0 -13571,42143708,Chaleco para fototerapia,,0,80509,0 -13572,42143709,Radiador de luz visible,,0,80509,0 -13573,42143900,Productos para la incontinencia fecal,,0,80509,0 -13574,42143901,Bolsa o accesorios de recolección de incontinencia fecal,,0,80509,0 -13575,42143902,Sistema o kit de manejo fecal,,0,80509,0 -13576,42143903,Catéter de colon o tubo rectal,,0,80509,0 -13577,42144000,Partes y accesorios para dispositivos auditivos externos,,0,80509,0 -13578,42144001,Procesador de sonido externo para dispositivos auditivos implantados,,0,80509,0 -13579,42144002,Cable externo para dispositivos auditivos implantados,,0,80509,0 -13580,42144003,Audífonos procesadores de sonido externos para dispositivos auditivos implantados,,0,80509,0 -13581,42144004,Magneto externo para dispositivos auditivos implantados,,0,80509,0 -13582,42144005,Micrófono externo para dispositivos auditivos implantados,,0,80509,0 -13583,42151500,Suministros y equipos de odontología cosmética,,0,80509,0 -13584,42151501,Luces de curación o accesorios para odontología estética,,0,80509,0 -13585,42151502,Pozos mezcladores para odontología estética,,0,80509,0 -13586,42151503,Coronas o formas de coronas,,0,80509,0 -13587,42151504,Laca dental,,0,80509,0 -13588,42151505,Suministros de grabado de dientes,,0,80509,0 -13589,42151506,Suministros blanqueadores o decolorantes de dientes,,0,80509,0 -13590,42151507,Sombras dentales,,0,80509,0 -13591,42151600,Instrumentos y dispositivos dentales y de subespecialidad,,0,80509,0 -13592,42151601,Accesorios o partes de repuesto para instrumentos dentales,,0,80509,0 -13593,42151602,Bandas para matriz dental,,0,80509,0 -13594,42151603,Herramientas de colocación de hidróxido de calcio,,0,80509,0 -13595,42151604,Herramientas de colocación de compuestos,,0,80509,0 -13596,42151605,Removedores de coronas o puentes,,0,80509,0 -13597,42151606,Talladores de amalgamas dentales,,0,80509,0 -13598,42151607,Casetes para instrumentos dentales,,0,80509,0 -13599,42151608,Bandejas o cubetas para instrumentos dentales,,0,80509,0 -13600,42151609,Esterillas para instrumentos dentales,,0,80509,0 -13601,42151610,Alicates dentales,,0,80509,0 -13602,42151611,Cepillos operativos dentales,,0,80509,0 -13603,42151612,Retractores dentales,,0,80509,0 -13604,42151613,Pulidores dentales,,0,80509,0 -13605,42151614,Fresas dentales,,0,80509,0 -13606,42151615,Unidades crio quirúrgicas dentales,,0,80509,0 -13607,42151616,Medidores de profundidad dentales,,0,80509,0 -13608,42151617,Taladros o brocas dentales,,0,80509,0 -13609,42151618,Elevadores dentales,,0,80509,0 -13610,42151619,Excavadoras dentales,,0,80509,0 -13611,42151620,Limas o curetas dentales,,0,80509,0 -13612,42151621,Instrumentos para contorno de calzas dentales,,0,80509,0 -13613,42151622,Protectores de dedos para uso odontológico,,0,80509,0 -13614,42151623,Fórceps dentales,,0,80509,0 -13615,42151624,Piezas manuales o accesorios para uso odontológico,,0,80509,0 -13616,42151625,Instrumentos de higiene dental,,0,80509,0 -13617,42151626,Accesorios para afilar instrumentos dentales,,0,80509,0 -13618,42151627,Espejos o mangos de espejo para uso odontológico,,0,80509,0 -13619,42151628,Lozas mezcladoras para uso odontológico,,0,80509,0 -13620,42151629,Organizadores dentales,,0,80509,0 -13621,42151630,Instrumentos de colocación para uso odontológico,,0,80509,0 -13622,42151631,Sondas dentales,,0,80509,0 -13623,42151632,Escariadores dentales,,0,80509,0 -13624,42151633,Instrumentos de empaque de cordones de retracción para uso odontológico,,0,80509,0 -13625,42151634,Picos de punta de raíz para uso dental,,0,80509,0 -13626,42151635,Eyectores de saliva o dispositivos de succión oral o suministros dentales,,0,80509,0 -13627,42151636,Raspadores dentales o accesorios,,0,80509,0 -13628,42151637,Escalas dentales,,0,80509,0 -13629,42151638,Tijeras dentales,,0,80509,0 -13630,42151639,Espátulas dentales,,0,80509,0 -13631,42151640,Pinzas dentales,,0,80509,0 -13632,42151641,Grabadores de cera para uso odontológico,,0,80509,0 -13633,42151642,Cuchillos de gingivectomía,,0,80509,0 -13634,42151643,Cortadoras de margen dental,,0,80509,0 -13635,42151644,Soportes para la boca para uso odontológico,,0,80509,0 -13636,42151645,Kits o bandejas operativas reusables pre ensambladas para uso odontológico,,0,80509,0 -13637,42151646,Dispositivos protectores para dientes,,0,80509,0 -13638,42151647,Sujetadores absorbentes para uso odontológico,,0,80509,0 -13639,42151648,Calibradores para uso odontológico,,0,80509,0 -13640,42151650,Herramientas de detección de fracturas para uso odontológico,,0,80509,0 -13641,42151651,Separadores de dientes para uso odontológico,,0,80509,0 -13642,42151652,Introductor de espigas para uso odontológico,,0,80509,0 -13643,42151653,Doblador de espigas para uso odontológico,,0,80509,0 -13644,42151654,Guías para uso odontológico,,0,80509,0 -13645,42151655,Probadores de nervio o de vitalidad para uso odontológico,,0,80509,0 -13646,42151656,Esparcidores para uso odontológico,,0,80509,0 -13647,42151657,Tubos o accesorios para uso odontológico,,0,80509,0 -13648,42151658,Enhebradores de seda dental,,0,80509,0 -13649,42151659,Kit de extracción de partes de dientes,,0,80509,0 -13650,42151660,Aplicadores o absorbentes para uso odontológico,,0,80509,0 -13651,42151661,Estuches o bolsas para instrumentos dentales,,0,80509,0 -13652,42151662,Electrodos de anestesia o repuestos para uso odontológico,,0,80509,0 -13653,42151663,Cuñas o sets para uso odontológico,,0,80509,0 -13654,42151664,Discos cortadores o separadores para uso odontológico,,0,80509,0 -13655,42151665,Calibradores o accesorios para uso odontológico,,0,80509,0 -13656,42151666,Sujetadores bucales para uso odontológico,,0,80509,0 -13657,42151667,Matrices o sets para uso odontológico,,0,80509,0 -13658,42151668,Enrolladores de instrumentos para instrumentos o accesorios dentales,,0,80509,0 -13659,42151669,Deshidratadores para uso odontológico,,0,80509,0 -13660,42151670,Portadores de calor para uso odontológico,,0,80509,0 -13661,42151671,Calzas o kits de calzas para sistemas post dentales,,0,80509,0 -13662,42151672,Expansores para uso odontológico,,0,80509,0 -13663,42151673,Martillos para uso odontológico,,0,80509,0 -13664,42151674,Sujetadores de fresas para uso odontológico,,0,80509,0 -13665,42151675,Kits de restauración para uso odontológico,,0,80509,0 -13666,42151676,Cuchillos para uso odontológico,,0,80509,0 -13667,42151677,Kits indicadores de presión para uso odontológico,,0,80509,0 -13668,42151678,Sujetadores para almohadillas o láminas mezcladoras,,0,80509,0 -13669,42151679,Bolsas mezcladoras para uso odontológico,,0,80509,0 -13670,42151680,Topes o accesorios endodónticos,,0,80509,0 -13671,42151681,Sets o accesorios de anestesia para uso odontológico,,0,80509,0 -13672,42151682,Broca endodóntica,,0,80509,0 -13673,42151683,Arco facial,,0,80509,0 -13674,42151700,Muebles para la clínica dental,,0,80509,0 -13675,42151701,Sillas para examen dental o partes relacionadas o accesorios,,0,80509,0 -13676,42151702,Taburetes para uso odontológico,,0,80509,0 -13677,42151703,Gabinetes para uso odontológico,,0,80509,0 -13678,42151704,Mesas o accesorios para uso odontológico,,0,80509,0 -13679,42151705,Sets de mobiliarios combinados para procedimientos dentales,Sets de mobiliarios combinados para procedimientos dentales,0,80509,0 -13680,42151800,Rellenos y acabados dentales y suministros para pulir,,0,80509,0 -13681,42151801,Portadores de amalgamas,,0,80509,0 -13682,42151802,Obturadores o puntas o accesorios para uso odontológico,,0,80509,0 -13683,42151803,Cápsulas de amalgama para uso odontológico,,0,80509,0 -13684,42151804,Empaquetadores de cable para uso odontológico,,0,80509,0 -13685,42151805,Discos pulidores o de terminado para uso odontológico,,0,80509,0 -13686,42151806,Tiras pulidoras o de terminado para uso odontológico,,0,80509,0 -13687,42151807,Puntas pulidoras o de terminado para uso odontológico,,0,80509,0 -13688,42151808,Compuestos brilladores para uso odontológico,,0,80509,0 -13689,42151809,Paños comprimibles para uso odontológico,Paños comprimidos para uso odontológico,0,80509,0 -13690,42151810,Kits pulidores o de terminado para uso odontológico,,0,80509,0 -13691,42151811,Copas o sets pulidores para uso odontológico,,0,80509,0 -13692,42151812,Resortes para abrasión o máquinas pulidoras para uso odontológico,,0,80509,0 -13693,42151813,Tubos de calzas para uso odontológico,,0,80509,0 -13694,42151814,Adaptadores de placas de sujeción para tallado o máquinas pulidoras para uso odontológico,,0,80509,0 -13695,42151815,Conos para tallado o máquinas pulidoras para uso odontológico,,0,80509,0 -13696,42151816,Portadores de lámina de oro,,0,80509,0 -13697,42151900,Equipo y suministros para higiene dental y cuidado preventivo,,0,80509,0 -13698,42151901,Cepillos para paladares o prótesis dentales,,0,80509,0 -13699,42151902,Kits de profilaxis para uso odontológico,,0,80509,0 -13700,42151903,Copas o contenedores para prótesis dentales,,0,80509,0 -13701,42151904,Soluciones o tabletas reveladoras,,0,80509,0 -13702,42151905,Geles o enjuagues de fluoruro,Fluoruro de sodio o enjuagues bucales,0,80509,0 -13703,42151906,Tabletas o gotas de fluoruro,,0,80509,0 -13704,42151907,Tanques de profilaxis para uso odontológico,,0,80509,0 -13705,42151908,Frascos o sets para prótesis dentales,,0,80509,0 -13706,42151909,Pastas o kits de prevención dental,,0,80509,0 -13707,42151910,Dispositivos o accesorios para limpiar dientes,,0,80509,0 -13708,42151911,Sets acondicionadores de tejido dental,,0,80509,0 -13709,42151912,Aerosoles endodónticos,Aerosoles endodónticos,0,80509,0 -13710,42152000,Equipo y suministros para hacer imágenes dentales,,0,80509,0 -13711,42152001,Sujetadores de aleta de mordida,,0,80509,0 -13712,42152002,Bloques o aletas o tabletas de mordida para uso odontológico,,0,80509,0 -13713,42152003,Procesadores de película para uso odontológico,,0,80509,0 -13714,42152004,Colgadores de película radiológica para uso odontológico,Colgadores para película de rayos x para uso odontológico,0,80509,0 -13715,42152005,Sujetadores de película radiológica para uso odontológico,Sujetadores de película radiológica para uso odontológico,0,80509,0 -13716,42152006,Monturas de película radiológica para uso odontológico,,0,80509,0 -13717,42152007,Duplicadores de rayos x para uso odontológico,,0,80509,0 -13718,42152008,Unidades de rayos x para uso odontológico,,0,80509,0 -13719,42152009,Visores o accesorios de rayos x para uso odontológico,,0,80509,0 -13720,42152010,Película radiológica para uso odontológico,,0,80509,0 -13721,42152011,Adaptadores de película de rayos x para uso odontológico,,0,80509,0 -13722,42152012,Partes o kits o accesorios para aparatos de rayos x para uso odontológico,Partes o kits o accesorios para aparatos de rayos x para uso odontológico,0,80509,0 -13723,42152013,Analizadores de película radiográfica para uso odontológico,,0,80509,0 -13724,42152014,Trazador radioactivo para uso odontológico,,0,80509,0 -13725,42152100,Equipo y suministros dentales para impresión y formar,,0,80509,0 -13726,42152101,Anillos de molde o suministros relacionados para uso odontológico,,0,80509,0 -13727,42152102,Formadores para uso odontológico,,0,80509,0 -13728,42152103,Adhesivos para bandejas de impresión para uso odontológico,,0,80509,0 -13729,42152104,Limpiadores de bandejas de impresión para uso odontológico,,0,80509,0 -13730,42152105,Bandejas de impresión para uso odontológico,,0,80509,0 -13731,42152106,Cuchillos para yeso para uso odontológico,,0,80509,0 -13732,42152107,Instrumentos de encerado para uso odontológico,,0,80509,0 -13733,42152108,Jeringas de material de impresión o accesorios para uso odontológico,,0,80509,0 -13734,42152109,Baños de agua para material de impresión o accesorios para uso odontológico,,0,80509,0 -13735,42152110,Dispositivos para marcar dientes,,0,80509,0 -13736,42152111,Endurecedores de material de impresión para uso odontológico,,0,80509,0 -13737,42152112,Instrumentos de secado o accesorios para uso odontológico,,0,80509,0 -13738,42152113,Kits de formación de base para prótesis dentales,,0,80509,0 -13739,42152114,Kits de entrega de materiales de impresión para uso odontológico,,0,80509,0 -13740,42152115,Canastas de impresión para uso odontológico,,0,80509,0 -13741,42152200,Equipo y suministros dentales de laboratorio y de esterilización,,0,80509,0 -13742,42152201,Láminas dentales,,0,80509,0 -13743,42152202,Unidades de abrasión por aire de laboratorio dental,,0,80509,0 -13744,42152203,Quemadores o antorchas de laboratorio dental,,0,80509,0 -13745,42152204,"Máquinas para enyesar, sus partes o accesorios de laboratorio dental",,0,80509,0 -13746,42152205,Unidades de curado de laboratorio dental,,0,80509,0 -13747,42152206,Tintes de laboratorio dental,,0,80509,0 -13748,42152207,Recolectores de polvo de laboratorio dental,,0,80509,0 -13749,42152208,Máquinas o accesorios de laboratorio dental,,0,80509,0 -13750,42152209,Hornos de uso odontológico para laboratorios,,0,80509,0 -13751,42152210,Chapas de oro de laboratorio dental,Piezas o suministros de oro para laboratorio dental,0,80509,0 -13752,42152211,Tornos o accesorios de laboratorio dental,,0,80509,0 -13753,42152212,Bordeadoras de modelos de laboratorio dental,,0,80509,0 -13754,42152213,Modelos de laboratorio dentales,,0,80509,0 -13755,42152214,Trampas de yeso de laboratorio dental,,0,80509,0 -13756,42152215,Lijadoras de chorro de arena o suministros de laboratorio dental,Arenadora dental o sus suministros para laboratorio,0,80509,0 -13757,42152216,Máquinas soldadoras o suministros de laboratorio dental,,0,80509,0 -13758,42152217,Unidades de vacío o suministros de laboratorio dental,,0,80509,0 -13759,42152218,Vibradores de uso odontológico para laboratorios,,0,80509,0 -13760,42152219,Unidades de encerado de laboratorio dental,,0,80509,0 -13761,42152220,Piedras dentales para laboratorios,,0,80509,0 -13762,42152221,Protectores de moldes o estuches de laboratorio dental,,0,80509,0 -13763,42152222,Crisoles para máquinas de fundición de uso odontológico,,0,80509,0 -13764,42152223,Unidades o accesorios de procesamiento de resinas de laboratorio dental,,0,80509,0 -13765,42152224,Cantimploras de laboratorio dental,,0,80509,0 -13766,42152400,Materiales dentales,,0,80509,0 -13767,42152401,Aleaciones para amalgamas de uso odontológico,,0,80509,0 -13768,42152402,Materiales protectores de la boca para atletas,,0,80509,0 -13769,42152403,Aleaciones de molibdeno cromo cobalto fundido para implantes dentales,,0,80509,0 -13770,42152404,Materiales de impresión hidrocoloides irreversibles reversibles combinados,,0,80509,0 -13771,42152405,Plásticos para coronas o puentes,,0,80509,0 -13772,42152406,Pastas abrasivas de uso odontológico,,0,80509,0 -13773,42152407,Polvos abrasivos de uso odontológico,,0,80509,0 -13774,42152408,Puntos absorbentes de uso odontológico,,0,80509,0 -13775,42152409,Materiales de impresión agar de uso odontológico,,0,80509,0 -13776,42152410,Materiales de impresión alginadas de uso odontológico,,0,80509,0 -13777,42152411,Aleaciones de fundición de metal de base de uso odontológico,,0,80509,0 -13778,42152412,Ceras de placa de base de uso odontológico,,0,80509,0 -13779,42152413,Aleaciones de soldadura fuerte de uso odontológico,,0,80509,0 -13780,42152414,Aleaciones de fundición de uso odontológico,,0,80509,0 -13781,42152415,Cerámicas de uso odontológico,,0,80509,0 -13782,42152416,Materiales de duplicación de uso odontológico,,0,80509,0 -13783,42152417,Inversiones de sílice etilo de uso odontológico,,0,80509,0 -13784,42152418,Productos de yeso de uso odontológico,,0,80509,0 -13785,42152419,Materiales de impresión de pasta de óxido de zinc eugenol de uso odontológico,,0,80509,0 -13786,42152420,Ceras de fundición para incrustaciones de uso odontológico,,0,80509,0 -13787,42152421,Mercurio de uso odontológico,,0,80509,0 -13788,42152422,Puntos obturadores de uso odontológico,,0,80509,0 -13789,42152423,Sellantes de huecos o fisuras de uso odontológico,Selladores de huecos o fisuras de uso odontológico,0,80509,0 -13790,42152424,Cementos de base de agua de uso odontológico,Cementos dentales a base de agua,0,80509,0 -13791,42152425,Resinas de base para prótesis dentales,,0,80509,0 -13792,42152426,Resinas de forrado temporales de base para prótesis dentales,,0,80509,0 -13793,42152427,Resinas de reparación de cura fría para prótesis dentales,,0,80509,0 -13794,42152428,Resinas de relleno directo,,0,80509,0 -13795,42152429,Materiales elastoméricos para impresiones dentales,,0,80509,0 -13796,42152430,Materiales de relleno endodóntico,,0,80509,0 -13797,42152431,Elastómeros de prótesis maxilofacial extra oral,,0,80509,0 -13798,42152432,Inversiones de fundición de yeso unido para aleaciones de oro,,0,80509,0 -13799,42152433,Película radiográfica dental intraoral,,0,80509,0 -13800,42152434,Amalgamantes para uso odontológico,,0,80509,0 -13801,42152435,Materiales elastómeros para ortodoncia,,0,80509,0 -13802,42152436,Inversiones unidas de fosfato,,0,80509,0 -13803,42152437,Dientes de porcelana,,0,80509,0 -13804,42152438,Materiales refractarios de tinte,,0,80509,0 -13805,42152439,Materiales resistentes de recubrimiento para prótesis dentales removibles,,0,80509,0 -13806,42152440,Inversiones de soldadura,,0,80509,0 -13807,42152441,Dientes de polímeros sintéticos,,0,80509,0 -13808,42152442,Titanio sin aleación para implantes dentales,,0,80509,0 -13809,42152443,Cementos de óxido de zinc eugenol y de no eugenol,,0,80509,0 -13810,42152444,Líquido de retracción gingival,,0,80509,0 -13811,42152445,Sets de partes anteriores o posteriores de dientes,Sets de partes anteriores o posteriores de dientes,0,80509,0 -13812,42152446,Recubrimientos o materiales adelgazantes o sets para caries dentales,Revestimientos para cavidad dental y diluyentes,0,80509,0 -13813,42152447,Tintes o suministros de uso odontológico,,0,80509,0 -13814,42152449,Catalizadores de material de impresión de uso odontológico,,0,80509,0 -13815,42152450,Compuestos limpiadores de instrumentos de uso odontológico,,0,80509,0 -13816,42152451,Compuestos indicadores de presión de uso odontológico,,0,80509,0 -13817,42152452,Kits o accesorios de aislamiento de pastas de uso odontológico,,0,80509,0 -13818,42152453,Compuestos restauradores de uso odontológico,,0,80509,0 -13819,42152454,Protectores de nervios dentales,,0,80509,0 -13820,42152455,Estantes o soportes para productos abrasivos de uso odontológico,,0,80509,0 -13821,42152456,Agentes enmascaradores de uso odontológico,,0,80509,0 -13822,42152457,Kits de cementación de uso odontológico,,0,80509,0 -13823,42152458,Espaciadores de tintes de uso odontológico,,0,80509,0 -13824,42152459,Kits post endodoncia,,0,80509,0 -13825,42152460,Compuestos de recubrimiento para prótesis dentales,,0,80509,0 -13826,42152461,Compuestos de recubrimiento para modelos dentales,,0,80509,0 -13827,42152462,Kits de revestimiento de prótesis dentales,,0,80509,0 -13828,42152463,Solventes de cera de uso odontológico,,0,80509,0 -13829,42152464,Kits para implantes dentales,,0,80509,0 -13830,42152465,Lubricantes dentales,,0,80509,0 -13831,42152466,Probador de ajuste de material,,0,80509,0 -13832,42152468,Mezclador de material de impresión dental de alginato operado eléctricamente,,0,80509,0 -13833,42152469,Mezclador de material de impresión dental basado en caucho operado eléctricamente,,0,80509,0 -13834,42152470,Resina para aplicación de ortodoncia,,0,80509,0 -13835,42152500,Suministros dentales generales,,0,80509,0 -13836,42152501,Kits o bandejas desechables pre ensambladas de uso odontológico,,0,80509,0 -13837,42152502,Baberos de uso odontológico,,0,80509,0 -13838,42152503,Suministros hembra de uso odontológico,Presas de goma de uso odontológico,0,80509,0 -13839,42152504,Recubrimientos de uso odontológico,,0,80509,0 -13840,42152505,Forros para el apoyacabezas de la silla de examen de uso odontológico,,0,80509,0 -13841,42152506,Bolitas absorbentes de uso odontológico,,0,80509,0 -13842,42152507,Rollos de uso odontológico,,0,80509,0 -13843,42152508,Jeringas o agujas o jeringas con agujas de uso odontológico,,0,80509,0 -13844,42152509,Cubiertas desechables para bandejas de uso odontológico,,0,80509,0 -13845,42152510,Estuches o cubiertas para instrumentos o sets de uso odontológico,,0,80509,0 -13846,42152511,Platos de uso odontológico,,0,80509,0 -13847,42152512,Pinzas de uso odontológico,,0,80509,0 -13848,42152513,Tazas medidoras de uso odontológico,,0,80509,0 -13849,42152514,Contenedores o accesorios de materiales de desecho de uso odontológico,,0,80509,0 -13850,42152516,Tazones mezcladores de uso odontológico,,0,80509,0 -13851,42152517,Sujetadores o dispensadores de servilletas de uso odontológico,,0,80509,0 -13852,42152518,Dispensadores de materiales de uso odontológico,,0,80509,0 -13853,42152519,Kits de accesorios para jeringas de uso odontológico,,0,80509,0 -13854,42152520,Escupideras de uso odontológico,,0,80509,0 -13855,42152521,Pantógrafo,,0,80509,0 -13856,42152600,Suministros específicos a la operación dental,,0,80509,0 -13857,42152601,Papeles articulados para operación o productos relacionados de uso odontológico,,0,80509,0 -13858,42152602,Barreras de control de infección para operaciones de uso odontológico,,0,80509,0 -13859,42152603,Tornillos de retención o productos relacionados para operaciones de uso odontológico,,0,80509,0 -13860,42152604,Cordones de retracción para operaciones de uso odontológico,,0,80509,0 -13861,42152605,Sets de sialografía de uso odontológico,,0,80509,0 -13862,42152606,Tablillas o sets de uso odontológico,,0,80509,0 -13863,42152608,Cartuchos de ligadura para ortodoncia,,0,80509,0 -13864,42152700,Equipo y suministros ortodoncia y prostodónticos,,0,80509,0 -13865,42152701,Articuladores o accesorios de uso odontológico,,0,80509,0 -13866,42152702,Soluciones hemostáticas de uso odontológico,,0,80509,0 -13867,42152703,Tornillos o fijadores o suministros relacionados de uso odontológico,,0,80509,0 -13868,42152704,Retenedores de uso odontológico,,0,80509,0 -13869,42152705,Pinzas de aplicación para ortodoncia,,0,80509,0 -13870,42152706,"Frenillos ""brackets"" para ortodoncia",,0,80509,0 -13871,42152707,Tubos bucales para ortodoncia,,0,80509,0 -13872,42152708,Bobinas de resorte para ortodoncia,,0,80509,0 -13873,42152709,Elásticos para ortodoncia,,0,80509,0 -13874,42152710,Elastómeros para ortodoncia,,0,80509,0 -13875,42152711,Tornillos de expansión para ortodoncia,,0,80509,0 -13876,42152712,Bandas molares para ortodoncia,,0,80509,0 -13877,42152713,Alicates para ortodoncia,,0,80509,0 -13878,42152714,Estuches retenedores para ortodoncia,,0,80509,0 -13879,42152715,Bandas de colocación para ortodoncia,,0,80509,0 -13880,42152716,Topes o bloqueos para ortodoncia,,0,80509,0 -13881,42152717,Alambre para ortodoncia,,0,80509,0 -13882,42152718,Arco de alambre para ortodoncia,,0,80509,0 -13883,42152800,Equipo y suministro periodontal,,0,80509,0 -13884,42152801,Cinceles para periodoncia,,0,80509,0 -13885,42152802,Limas para periodoncia,,0,80509,0 -13886,42152803,Separadores para periodoncia,,0,80509,0 -13887,42152804,Instrumentos de prueba de implante periodontal a interface de hueso,,0,80509,0 -13888,42152805,Instrumentos de control para periodoncia,,0,80509,0 -13889,42152806,Disectores de tejido para periodoncia,,0,80509,0 -13890,42152807,Pastas protectoras para periodoncia,,0,80509,0 -13891,42152808,Curetas para periodoncia,,0,80509,0 -13892,42152809,Cuchillos para periodoncia,,0,80509,0 -13893,42152810,Membranas para periodoncia,,0,80509,0 -13894,42161500,Equipo de diálisis peritoneal y de equilibrio y suministros,,0,80509,0 -13895,42161501,Sets de transferencia de diálisis peritoneal ambulatoria continua capd,,0,80509,0 -13896,42161502,Calentadores de dialisato peritoneal,,0,80509,0 -13897,42161503,Sets de administración o cateterización de diálisis peritoneal,,0,80509,0 -13898,42161504,Adaptadores o pinzas o conectores de catéter para diálisis peritoneal,,0,80509,0 -13899,42161505,Bolsas o contenedores de drenaje de diálisis peritoneal,,0,80509,0 -13900,42161506,Derivaciones o catéteres o dispositivos de acceso permanente para diálisis peritoneal,,0,80509,0 -13901,42161507,Soluciones para diálisis peritoneal,,0,80509,0 -13902,42161508,Correas o arneses para unidades de diálisis peritoneal,,0,80509,0 -13903,42161509,Unidades de diálisis peritoneal,,0,80509,0 -13904,42161510,Kits de lavado para diálisis peritoneal,,0,80509,0 -13905,42161600,Equipo de hemodiálisis extracorpórea y suministros,,0,80509,0 -13906,42161601,Kits o sets o accesorios de administración de hemodiálisis,,0,80509,0 -13907,42161602,Muestreadores de bolsas de sangre para hemodiálisis,,0,80509,0 -13908,42161603,Aparato de demanda de oxígeno en la sangre para hemodiálisis,,0,80509,0 -13909,42161604,Sillas para hemodiálisis,,0,80509,0 -13910,42161605,Medidores de conductividad de dialisato para hemodiálisis,,0,80509,0 -13911,42161606,Detectores de nivel de dialisato para hemodiálisis,,0,80509,0 -13912,42161607,Bombas de presión de dialisato para hemodiálisis,,0,80509,0 -13913,42161608,Soluciones de dialisato para hemodiálisis,,0,80509,0 -13914,42161609,Tanques de dialisato para hemodiálisis,,0,80509,0 -13915,42161610,Tubos de dialisato para hemodiálisis,,0,80509,0 -13916,42161611,Baños calentadores de dialisato para hemodiálisis,,0,80509,0 -13917,42161612,Filtros de celuloide de dializador para hemodiálisis,,0,80509,0 -13918,42161613,Filtros de colodión de dializador para hemodiálisis,,0,80509,0 -13919,42161614,Filtros de filamento hueco de dializador para hemodiálisis,,0,80509,0 -13920,42161615,Sistemas de reprocesamiento de dializador para hemodiálisis,,0,80509,0 -13921,42161616,"Detectores de burbujas de aire o espuma o coágulos, o trampas o alarmas para unidades de hemodiálisis",,0,80509,0 -13922,42161617,Monitores de presión arterial para unidades de hemodiálisis,,0,80509,0 -13923,42161618,Pinzas de línea de sangre para unidades de hemodiálisis,,0,80509,0 -13924,42161619,Tapas para válvulas de sangre para unidades de hemodiálisis,,0,80509,0 -13925,42161620,Bombas de sangre para unidades de hemodiálisis,,0,80509,0 -13926,42161621,Tapas para válvulas de dialisato unidades de hemodiálisis,,0,80509,0 -13927,42161622,Desinfectantes o limpiadores para unidades de hemodiálisis,,0,80509,0 -13928,42161623,Bombas de infusión de heparina unidades de hemodiálisis,,0,80509,0 -13929,42161624,Controladores de agujas únicas para unidades de hemodiálisis,,0,80509,0 -13930,42161625,Convertidores de un solo paso para unidades de hemodiálisis,,0,80509,0 -13931,42161626,Estantes o soportes o carritos para unidades de hemodiálisis,,0,80509,0 -13932,42161627,Monitores de temperatura para unidades de hemodiálisis,,0,80509,0 -13933,42161628,Equipos de prueba para unidades de hemodiálisis,,0,80509,0 -13934,42161629,Filtros transductores para unidades de hemodiálisis,,0,80509,0 -13935,42161630,Protectores transductores para unidades de hemodiálisis,,0,80509,0 -13936,42161631,Sistemas de purificación del agua para unidades de hemodiálisis,,0,80509,0 -13937,42161632,Unidades de hemodiálisis,,0,80509,0 -13938,42161633,Membranas dializadoras para aparatos de hemodiálisis,,0,80509,0 -13939,42161634,Bandejas de procedimiento o accesorios para hemodiálisis,,0,80509,0 -13940,42161635,Cartuchos para aparatos de hemodiálisis,,0,80509,0 -13941,42161700,Equipo de hemofiltración y suministros,,0,80509,0 -13942,42161701,Hemofiltro,,0,80509,0 -13943,42161702,Bolsas de recolección de hemofiltración,,0,80509,0 -13944,42161703,Puertos de infusión para hemofiltración,,0,80509,0 -13945,42161704,Puertos de muestreo para hemofiltración,,0,80509,0 -13946,42161900,Equipo y suministros para aféresis terapéutica,,0,80509,0 -13947,42161901,Kit para procedimientos de aféresis terapéutica,,0,80509,0 -13948,42171600,"Productos de sacar, inmovilizar y trasladar de servicios médicos de urgencia",,0,80509,0 -13949,42171601,Camillas o accesorios para evacuación aérea,,0,80509,0 -13950,42171602,Camillas o accesorios para ambulancias,,0,80509,0 -13951,42171603,Prendas anti choque,,0,80509,0 -13952,42171604,Camillas canasta o accesorios,,0,80509,0 -13953,42171605,Ojales de cable de rescate,,0,80509,0 -13954,42171606,Férulas inflables para servicios médicos de emergencia,,0,80509,0 -13955,42171607,Collares cervicales o de extracción de víctimas para servicios médicos de emergencia,,0,80509,0 -13956,42171608,Inmovilizadores de cabeza para servicios médicos de emergencia,,0,80509,0 -13957,42171609,Correas de retención o espinales para servicios médicos de emergencia,,0,80509,0 -13958,42171610,Inmovilizadores de torso para servicios médicos de emergencia,,0,80509,0 -13959,42171611,Literas o camillas o accesorios para respuesta de emergencia,,0,80509,0 -13960,42171612,Camillas o accesorios redondas,,0,80509,0 -13961,42171613,Tablas espinales,,0,80509,0 -13962,42171614,Tubos o contenedores para rescate en agua,,0,80509,0 -13963,42172000,Kits para los servicios médicos de urgencias y campo,,"0,1",80509,0 -13964,42172001,Kits de primeros auxilios para servicios médicos de emergencia,"Botiquín médico, Kits de primeros auxilios para servicios médicos de emergencia","0,1",80509,0 -13965,42172002,Kits de primera respuesta para servicios médicos de emergencia,Maletín paramédico para emergencias pre-hospitalarias,"0,1",80509,0 -13966,42172003,Kits intravenosos iv para servicios médicos de emergencia,,"0,1",80509,0 -13967,42172004,Kits de soporte de vida para servicios médicos de emergencia,,"0,1",80509,0 -13968,42172005,Kits de trauma de respuesta de larga distancia ldr para servicios médicos de emergencia,,"0,1",80509,0 -13969,42172006,Kits obstétricos para servicios médicos de emergencia,,"0,1",80509,0 -13970,42172007,Kits de oxígeno o resucitación para servicios médicos de emergencia,,"0,1",80509,0 -13971,42172008,Kits de rapel para servicios médicos de emergencia,,"0,1",80509,0 -13972,42172009,Kits de búsqueda y rescate para servicios médicos de emergencia,,"0,1",80509,0 -13973,42172010,Kits de trauma para servicios médicos de emergencia,,"0,1",80509,0 -13974,42172011,Kits para técnicos médicos de emergencia emt,,"0,1",80509,0 -13975,42172012,Kits de ventriculostomía para servicios médicos de emergencia,,"0,1",80509,0 -13976,42172013,Kits de evacuación para servicios médicos de emergencia,,"0,1",80509,0 -13977,42172014,Kits o suministros de transporte de pacientes para servicios médicos de emergencia,,"0,1",80509,0 -13978,42172015,Kits de tratamiento dental para servicios médicos de emergencia,,"0,1",80509,0 -13979,42172016,Kits de fracturas para servicios médicos de emergencia,,"0,1",80509,0 -13980,42172017,Kits de equipos médicos de laboratorio o de campo o productos relacionados,,"0,1",80509,0 -13981,42172018,Kit de lavado gástrico,,"0,1",80509,0 -13982,42181500,Evaluación diagnóstica y productos de examen de uso general,,0,80509,0 -13983,42181501,Depresores de lengua o cuchillos o baja lenguas,Abatelenguas,0,80509,0 -13984,42181502,Transiluminadores para exámenes de uso médico,,0,80509,0 -13985,42181503,Lubricantes o gelatinas personales o para examen,,0,80509,0 -13986,42181504,Sets de hemacitómetros,,0,80509,0 -13987,42181505,Sets o accesorios de muestreadores de células endometriales,,0,80509,0 -13988,42181506,Kits de determinación de asalto sexual,,0,80509,0 -13989,42181507,Bandas para sujetar el espejo a la frente o accesorios para exámenes médicos,,0,80509,0 -13990,42181508,Pañitos limpiadores para equipo diagnóstico,,0,80509,0 -13991,42181509,Sets de examen físico para cirujanos de vuelo,,0,80509,0 -13992,42181510,Medidores de ictericia transcutáneos,,0,80509,0 -13993,42181511,Evaluadores de impotencia masculina,,0,80509,0 -13994,42181512,Cucharas para examen de portadores de tifoidea,,0,80509,0 -13995,42181513,Kits de irrigación de los senos nasales,,0,80509,0 -13996,42181514,Fotómetros de hemoglobina,,0,80509,0 -13997,42181515,Bandejas de procedimiento para exámenes,,0,80509,0 -13998,42181516,Unidades o accesorios de electromiografía emg,,0,80509,0 -13999,42181518,Estuche para sets de instrumentos médicos o sus accesorios,,0,80509,0 -14000,42181519,Portador de gotas,,0,80509,0 -14001,42181520,Medidor de presión intratoráxica,,0,80509,0 -14002,42181521,Monitor de presión de fluido espinal,,0,80509,0 -14003,42181522,Aparato para análisis de manera de caminar,,0,80509,0 -14004,42181523,Sistema de prueba y evaluación isocinética,,0,80509,0 -14005,42181524,Analizador de rigidez,,0,80509,0 -14006,42181525,Ataxiágrafo,,0,80509,0 -14007,42181526,Algesímetro,,0,80509,0 -14008,42181527,Dinamómetro médico,,0,80509,0 -14009,42181528,Dispositivo para medir la resistencia de la piel,,0,80509,0 -14010,42181529,Estesiómetro,,0,80509,0 -14011,42181530,Sistema de inducción de sudor de iontoforesis,,0,80509,0 -14012,42181531,Instrumento medidor de conductividad eléctrica de la piel,,0,80509,0 -14013,42181532,Medidor de respuesta galvánica de la piel,,0,80509,0 -14014,42181533,Dispositivo de medición de la velocidad de conducción del nervio,,0,80509,0 -14015,42181534,Electroglotógrafo,,0,80509,0 -14016,42181535,Condicionador de señales electrónicas fisiológicas,,0,80509,0 -14017,42181536,Dispositivo eléctrico de detección de caries,,0,80509,0 -14018,42181537,Instrumento para medir la fuerza de la mordida para uso odontológico,,0,80509,0 -14019,42181538,Dispositivo medidor del sonido de oclusión para uso odontológico,,0,80509,0 -14020,42181600,Unidades de presión sanguínea y productos relacionados,,0,80509,0 -14021,42181601,Unidades de presión de sangre aneroides,,0,80509,0 -14022,42181602,Unidades de presión de sangre electrónicas,,0,80509,0 -14023,42181603,Unidades de presión de sangre de mercurio,,0,80509,0 -14024,42181604,Válvulas o peras inflables de liberación de presión del aire en la sangre,,0,80509,0 -14025,42181605,Mangas o vejigas de presión de la sangre,,0,80509,0 -14026,42181606,Mangueras de inflar o mangueras neumáticas o adaptadores de presión de sangre,,0,80509,0 -14027,42181607,Unidades de registro de presión de sangre,,0,80509,0 -14028,42181608,Accesorios para instrumentos de medición de presión de sangre,,0,80509,0 -14029,42181609,Kits de domo de monitoreo de presión de sangre,,0,80509,0 -14030,42181610,Kits de mangas de presión de sangre,,0,80509,0 -14031,42181611,Manómetro de presión venosa central cvp,,0,80509,0 -14032,42181612,Soportes móviles de presión de sangre,,0,80509,0 -14033,42181700,Unidades de electrocardiografía (ECG) y productos relacionados,,0,80509,0 -14034,42181701,Unidades de electrocardiografía ekg,,0,80509,0 -14035,42181702,Adaptadores o cables o conductores para electrocardiografía ekg,,0,80509,0 -14036,42181703,Probadores de cables o conductores o unidades de electrocardiografía ekg,,0,80509,0 -14037,42181704,Calibradores o reglas de electrocardiografía ekg,,0,80509,0 -14038,42181705,Registros gráficos de electrocardiografía ekg,,0,80509,0 -14039,42181706,Pantallas de monitor para electrocardiografía ekg,,0,80509,0 -14040,42181707,Electrodos de tira o anillo para electrocardiografía ekg neonatal,,0,80509,0 -14041,42181708,Electrodos de parche para electrocardiografía ekg,,0,80509,0 -14042,42181710,Probadores de superficie de electrodos para electrocardiografía ekg,,0,80509,0 -14043,42181711,Sistemas telefónicos receptores o transmisores para electrocardiografía ekg,,0,80509,0 -14044,42181712,Analizadores de unidad de electrocardiografía ekg,,0,80509,0 -14045,42181713,Sistemas de monitoreo de electrocardiografía ekg continua de larga duración o holter,,0,80509,0 -14046,42181714,Kits de accesorios de monitoreo para electrocardiografía ekg,,0,80509,0 -14047,42181715,Soluciones o cremas para electrodos,,0,80509,0 -14048,42181716,Accesorios para electrocardiografía ekg,,0,80509,0 -14049,42181717,Bombillos de electrodos para electrocardiografía ekg,,0,80509,0 -14050,42181718,Esferos para registro de electrocardiografía ekg,,0,80509,0 -14051,42181719,Transmisor o telemetría o accesorios para electrocardiografía ekg,,0,80509,0 -14052,42181720,Sistema de monitoreo de estrés por ejercicio,,0,80509,0 -14053,42181721,Esfigmógrafo,,0,80509,0 -14054,42181722,Analizador de ondas de pulso,,0,80509,0 -14055,42181800,Oxímetros de pulso,,0,80509,0 -14056,42181801,Unidades para oxímetros de pulso,,0,80509,0 -14057,42181802,Cables para oxímetros de pulso,,0,80509,0 -14058,42181803,Sondas o sensores para oxímetros de pulso,,0,80509,0 -14059,42181804,Accesorios para sondas o sensores para oxímetros de pulso,,0,80509,0 -14060,42181805,Accesorios para unidades de oxímetros de pulso,,0,80509,0 -14061,42181900,Unidades de control de cuidado intenso y productos relacionados,,0,80509,0 -14062,42181901,Unidades o accesorios para cuidado intensivo fetal o monitoreo materno,,0,80509,0 -14063,42181902,Unidades o accesorios para monitoreo de presión intracraneal icp,,0,80509,0 -14064,42181903,Unidades o accesorios para monitoreo de salida cardiaca co,,0,80509,0 -14065,42181904,Unidades o accesorios para unidades de signos vitales multi parámetro,,0,80509,0 -14066,42181905,Cables para monitor transductor para uso médico,,0,80509,0 -14067,42181906,Catéteres para monitoreo de presión intrauterina,,0,80509,0 -14068,42181907,Aparatos para metabolismo basal,,0,80509,0 -14069,42181908,Sets o accesorios de monitoreo de presión intra compartimientos,,0,80509,0 -14070,42181910,Kits intrauterinos transcervicales,,0,80509,0 -14071,42181911,Electrodos para cuero cabelludo fetal,,0,80509,0 -14072,42181912,Monitor electroencefalográfico fetal,,0,80509,0 -14073,42182000,Especulo y scopios y accesorios para uso del reconocimiento diagnóstico médico,,0,80509,0 -14074,42182001,Espéculos para examen anal o rectal,,0,80509,0 -14075,42182002,Anoscopios o proctoscopios,,0,80509,0 -14076,42182003,Colposcopios o vaginoscopios o accesorios,,0,80509,0 -14077,42182004,Dermatoscopios,,0,80509,0 -14078,42182005,Oftalmoscopios u otoscopios o sets de escopios,,0,80509,0 -14079,42182006,Espéculos para examen de laringe o bucofaríngeo,,0,80509,0 -14080,42182007,Bombillos o lámparas de escopios para exámenes médicos,,0,80509,0 -14081,42182008,Manijas de escopios o cargadores de escopios para exámenes médicos,,0,80509,0 -14082,42182009,Puntas de espéculos para escopios o dispensadores de puntas de espéculos para uso médico,,0,80509,0 -14083,42182010,Sujetadores o soportes para espéculos para exámenes médicos,,0,80509,0 -14084,42182011,Espéculos o puntas de dilatación o dispensadores de puntas para exámenes médicos,,0,80509,0 -14085,42182012,Espéculos o dilatadores para exámenes nasales,,0,80509,0 -14086,42182013,Espéculos para examen vaginal,,0,80509,0 -14087,42182014,Accesorios para otoscopios u oftalmoscopios,,0,80509,0 -14088,42182015,Espéculos para otoscopia,,0,80509,0 -14089,42182016,Nasofaringoscopios o accesorios,,0,80509,0 -14090,42182017,Sets de espéculos para oído,,0,80509,0 -14091,42182018,Broncoscopios o accesorios,,0,80509,0 -14092,42182019,Kits de dilatación,,0,80509,0 -14093,42182020,Angioscopios o accesorios,,0,80509,0 -14094,42182100,Estetoscopio y productos relacionados,,0,80509,0 -14095,42182101,Estetoscopios electrónicos o accesorios,,0,80509,0 -14096,42182102,Dopplers vasculares de mano o accesorios,,0,80509,0 -14097,42182103,Estetoscopio acústico para uso médico o accesorios,,0,80509,0 -14098,42182104,Estetoscopio de cabeza,,0,80509,0 -14099,42182105,Cubiertas para estetoscopios de cabeza,,0,80509,0 -14100,42182106,Fonocardiográfos estetoscópicos,,0,80509,0 -14101,42182107,Estetoscopios auriculares,,0,80509,0 -14102,42182108,Termorreguladores de pacientes,,0,80509,0 -14103,42182200,Termómetros médicos y accesorios,,0,80509,0 -14104,42182201,Termómetros electrónicos para uso médico,,0,80509,0 -14105,42182202,Termómetros de fibra óptica para uso médico,,0,80509,0 -14106,42182203,Estuches o forros para transportar termómetros para uso médico,,0,80509,0 -14107,42182204,Estantes para termómetros para uso médico,,0,80509,0 -14108,42182205,Fundas para puntas o sondas de termómetros para uso médico,,0,80509,0 -14109,42182206,Termómetros de mercurio para uso médico,,0,80509,0 -14110,42182207,Monitores de temperatura continua o de tendencia de temperatura del paciente,,0,80509,0 -14111,42182208,Bandas de temperatura del paciente,,0,80509,0 -14112,42182209,Sondas para termómetros,,0,80509,0 -14113,42182210,Termómetros de vidrio no de mercurio para uso médico,,0,80509,0 -14114,42182211,Sistema de termografía infrarroja,,0,80509,0 -14115,42182212,Termómetro de cristal líquido,,0,80509,0 -14116,42182300,Productos del examen neurológico,,0,80509,0 -14117,42182301,Molinillos de diagnóstico para uso médico,,0,80509,0 -14118,42182302,Martillos o martillos quirúrgicos para reflejos,,0,80509,0 -14119,42182303,Tarjetas de examen neurosiquiátrico,,0,80509,0 -14120,42182304,Sets o kits de pruebas de diagnóstico sicológico,,0,80509,0 -14121,42182305,Sets para procedimientos mielográficos,,0,80509,0 -14122,42182306,Discriminadores neurológicos,,0,80509,0 -14123,42182307,Alfileres neurológicos,,0,80509,0 -14124,42182308,Electroencefalógrafo eeg o accesorios,,0,80509,0 -14125,42182310,Electromiógrafos,,0,80509,0 -14126,42182311,Sensores neurológicos,,0,80509,0 -14127,42182312,Electrodos o sets para electromiógrafos,,0,80509,0 -14128,42182313,Sets para diagnóstico neurológico,,0,80509,0 -14129,42182314,Dispositivo de retroalimentación biológica,,0,80509,0 -14130,42182315,Analizador de espectro electroencefalográfico,,0,80509,0 -14131,42182316,Detector de respuesta evocada,,0,80509,0 -14132,42182400,Productos para probar el oír,,0,80509,0 -14133,42182401,Audiómetros o accesorios,,0,80509,0 -14134,42182402,Vibradores de hueso audiométricos o analizadores de oído medio,,0,80509,0 -14135,42182403,Cabinas audiométricas o cámaras acústicas para pruebas de audición,,0,80509,0 -14136,42182404,Sets de calibración de unidades de evaluación de función auditiva,,0,80509,0 -14137,42182405,Unidades de evaluación de función auditiva,,0,80509,0 -14138,42182406,Grabadoras gráficas de pruebas auditivas,,0,80509,0 -14139,42182407,Fenestrómetros de oído,,0,80509,0 -14140,42182408,Electrococleógrafos,,0,80509,0 -14141,42182409,Analizadores de audífonos o sistemas de prueba,,0,80509,0 -14142,42182410,Estuches para diapasones para uso médico,,0,80509,0 -14143,42182411,Martillos para diapasones para uso médico,,0,80509,0 -14144,42182412,Diapasones para uso médico,,0,80509,0 -14145,42182413,Sets de diapasones para uso médico,,0,80509,0 -14146,42182414,Analizadores de tinnitus (acúfenos),,0,80509,0 -14147,42182415,Tubos de toynbee para diagnóstico,,0,80509,0 -14148,42182416,Timpanómetros o sus accesorios,,0,80509,0 -14149,42182417,Cordones de audiometría,,0,80509,0 -14150,42182418,Guías para fenestrómetro,,0,80509,0 -14151,42182419,Aparatos para control de audición,,0,80509,0 -14152,42182420,Sondas aurales,,0,80509,0 -14153,42182421,Calibradores de tapones o accesorios,,0,80509,0 -14154,42182422,Bolsas inflables para oído,,0,80509,0 -14155,42182600,Lámparas o luces de examen médico,,0,80509,0 -14156,42182601,Luces o lámparas de pie para exámenes médicos,,0,80509,0 -14157,42182602,Luces o lámparas instaladas para exámenes médicos,,0,80509,0 -14158,42182603,Luces de techo o lámparas de techo o accesorios para exámenes médicos,,0,80509,0 -14159,42182604,Linternas para exámenes médicos,,0,80509,0 -14160,42182700,Dispositivos de medición de talla de examen médico,,0,80509,0 -14161,42182701,Goniómetros,,0,80509,0 -14162,42182702,Cintas de medición para uso médico,,0,80509,0 -14163,42182703,Reglas de medición de altura de pacientes,,0,80509,0 -14164,42182704,Evaluadores de dobleces de piel,,0,80509,0 -14165,42182705,Analizador de composición del cuerpo,,0,80509,0 -14166,42182706,Malla o dispositivo de medición de heridas,,0,80509,0 -14167,42182707,Inclinómetro,,0,80509,0 -14168,42182800,Básculas médicas de peso,,0,80509,0 -14169,42182801,Básculas de pañales,,0,80509,0 -14170,42182802,Básculas de bebés,,0,80509,0 -14171,42182803,Básculas de mesa o cama para pacientes para uso general,,0,80509,0 -14172,42182804,Básculas de asiento para pacientes,,0,80509,0 -14173,42182805,Básculas de piso para pacientes,,0,80509,0 -14174,42182806,Básculas de eslinga para pacientes,,0,80509,0 -14175,42182807,Básculas de plataforma para sillas de ruedas,,0,80509,0 -14176,42182808,Forros o revestimientos para básculas de pesaje,,0,80509,0 -14177,42183000,Productos de examen diagnóstico oftálmico,,0,80509,0 -14178,42183001,Carteleras para examen de ojos o tarjetas de visión,,0,80509,0 -14179,42183002,Topógrafos corneales,,0,80509,0 -14180,42183003,Exoftalmómetros,,0,80509,0 -14181,42183004,Queratoscopios,,0,80509,0 -14182,42183005,Colorímetros oftálmicos,,0,80509,0 -14183,42183006,Distómetros oftálmicos,,0,80509,0 -14184,42183007,Tambores oftálmicos o sus accesorios,,0,80509,0 -14185,42183008,Eutiscopios oftálmicos,,0,80509,0 -14186,42183009,Lentes de prueba del ojo o sus accesorios para uso oftálmico,,0,80509,0 -14187,42183010,Lensómetros para uso oftálmico,,0,80509,0 -14188,42183011,Perímetros para uso oftálmico,,0,80509,0 -14189,42183012,Fotómetros para uso oftálmico,,0,80509,0 -14190,42183013,Prismas para uso oftálmico,,0,80509,0 -14191,42183014,Retinoscopios para uso oftálmico,,0,80509,0 -14192,42183015,Lámparas de hendidura para uso oftálmico,,0,80509,0 -14193,42183016,Espectrofotómetros para uso oftálmico,,0,80509,0 -14194,42183017,Espéculos para uso oftálmico,,0,80509,0 -14195,42183018,Tonómetros o accesorios para uso oftálmico,,0,80509,0 -14196,42183019,Transiluminadores para uso oftálmico,,0,80509,0 -14197,42183020,Trazadores de campo visual para uso oftálmico,,0,80509,0 -14198,42183021,Analizadores de función visual para uso oftálmico,,0,80509,0 -14199,42183022,Visiómetros para uso oftálmico,,0,80509,0 -14200,42183023,Oftalmómetros,,0,80509,0 -14201,42183024,Mesas para instrumentos o accesorios para uso oftálmico,,0,80509,0 -14202,42183026,Oftalmodinamómetros,,0,80509,0 -14203,42183027,Kits o accesorios de pruebas de monitoreo tangente,,0,80509,0 -14204,42183028,Accesorios para retinoscopio para uso oftálmico,,0,80509,0 -14205,42183029,Unidades de forópter,,0,80509,0 -14206,42183030,Oclusores de ojos,,0,80509,0 -14207,42183031,Sets o accesorios de placas pseudoisocromáticas,,0,80509,0 -14208,42183032,Taquistoscopio,,0,80509,0 -14209,42183033,Sets de ajuste de anteojos,,0,80509,0 -14210,42183034,Estereoscopios para probar la visión,,0,80509,0 -14211,42183035,Queratómetros refractores de combinación,,0,80509,0 -14212,42183036,Placas base para oftalmómetros,,0,80509,0 -14213,42183037,Proyectores de cuadros o accesorios,,0,80509,0 -14214,42183038,Almohadillas de uso oftálmico,,0,80509,0 -14215,42183039,Sujetadores de lentes oftálmicos,,0,80509,0 -14216,42183040,Herramientas o accesorios para optómetras,,0,80509,0 -14217,42183041,Linternas de pruebas de percepción del color,,0,80509,0 -14218,42183042,Aparatos de percepción de profundidad,,0,80509,0 -14219,42183043,Bombillos para oftalmómetros,,0,80509,0 -14220,42183044,Pantallas tangentes,,0,80509,0 -14221,42183045,Sistemas de electroretinograma,,0,80509,0 -14222,42183046,Sets o accesorios de prueba de visión binocular,,0,80509,0 -14223,42183047,Soportes miradores para pruebas de agudeza visual,,0,80509,0 -14224,42183048,Barras de fijación de niños para uso oftálmico,,0,80509,0 -14225,42183049,Cámara de fundus,,0,80509,0 -14226,42183050,Nistagmógrafo,,0,80509,0 -14227,42183051,Pupilómetro,,0,80509,0 -14228,42183052,Regla de punto cercano,,0,80509,0 -14229,42183053,Medidor de distancia entre pupilas,,0,80509,0 -14230,42183054,Pletismografía ocular,,0,80509,0 -14231,42183055,Contador de células endoteliales,,0,80509,0 -14232,42183056,Cámara anterior,,0,80509,0 -14233,42183057,Monitor de movimiento del ojo,,0,80509,0 -14234,42183058,Instrumento láser de campo de visión,,0,80509,0 -14235,42183059,Foto simulador,,0,80509,0 -14236,42183060,Tiras de schirmer,,0,80509,0 -14237,42183061,Aplicadores de presión ocular,,0,80509,0 -14238,42183062,Cámara de iris,,0,80509,0 -14239,42183063,Unidad de electrólisis oftálmica,,0,80509,0 -14240,42183064,Lentes de contacto terapéuticos,,0,80509,0 -14241,42183065,Lentes de contacto prescritos,,0,80509,0 -14242,42183066,Cámara corneal,,0,80509,0 -14243,42183067,Lentes de examen de diagnóstico de visión para uso oftálmico,,0,80509,0 -14244,42191500,Manejo de materiales de facilidad médica y equipo de distribución,,0,80509,0 -14245,42191501,Sistema de tubo neumático clínico,,0,80509,0 -14246,42191502,Bandejas o cubiertas para medicinas,,0,80509,0 -14247,42191600,Sistemas de construcción de facilidad médica,,0,80509,0 -14248,42191601,Iluminación para habitaciones de pacientes o accesorios,,0,80509,0 -14249,42191602,Iluminación para salas de cirugía o accesorios,,0,80509,0 -14250,42191603,Paneles de instrumentos para equipos hospitalarios,,0,80509,0 -14251,42191604,Brazos de monitoreo clínico,,0,80509,0 -14252,42191605,Columnas para equipos eléctricos hospitalarios,,0,80509,0 -14253,42191606,Brazos de techo para instalaciones médicas,,0,80509,0 -14254,42191607,Cortinas de cubículo o pantallas o hardware de rieles de cortinas para pacientes,,0,80509,0 -14255,42191608,Controles de enfermería o monitores de salida,,0,80509,0 -14256,42191609,Sistemas de pared principal para uso clínico,,0,80509,0 -14257,42191610,Módulos de trabajo para uso clínico,,0,80509,0 -14258,42191611,Módulos o sistemas de comunicación de enfermería,,0,80509,0 -14259,42191612,Sistemas de intercomunicación hospitalaria,,0,80509,0 -14260,42191613,Transmisor de seguridad para bebés o niños,,0,80509,0 -14261,42191700,Productos de gas de hospital,,"0,1",80509,0 -14262,42191701,Pistas de servicio de electricidad o gas para uso médico,,0,80509,0 -14263,42191702,Columnas de entrega de gas para uso médico,,0,80509,0 -14264,42191703,Salidas de gas para uso médico,,0,80509,0 -14265,42191704,Sistemas de compresión de aire gas para uso médico,,0,80509,0 -14266,42191705,Alarmas de gas para uso médico,,0,80509,0 -14267,42191706,Colector de gas para uso medico,,"0,1",80509,0 -14268,42191707,Sistemas de vacío para uso médico,,0,80509,0 -14269,42191708,Gabinetes de control de presión de aire para uso médico,,0,80509,0 -14270,42191709,Válvulas de cierre de gas o cajas de válvulas para uso médico,,0,80509,0 -14271,42191710,Carros o puestos o accesorios para cilindros de gas para uso médico,,0,80509,0 -14272,42191711,Tanques de aire comprimido o accesorios para uso quirúrgico,,"0,1",80509,0 -14273,42191800,Camas de paciente y accesorios,,0,80509,0 -14274,42191801,Mesas para encima de la cama o accesorios,,0,80509,0 -14275,42191802,Incubadoras o calentadores de bebés para uso clínico,,0,80509,0 -14276,42191803,Moisés o cunas o camas pediátricas o accesorios,,0,80509,0 -14277,42191804,Barandas para camas para uso médico o quirúrgico,,0,80509,0 -14278,42191805,Columnas suspendidas para uso médico,,0,80509,0 -14279,42191806,Barras de trapecio para uso clínico,,0,80509,0 -14280,42191807,Camas o accesorios de cuidado del paciente para uso general,,0,80509,0 -14281,42191808,Camas o accesorios de cuidado del paciente para cuidado especial,,0,80509,0 -14282,42191809,Almohadillas o bombas de presión alterna,,0,80509,0 -14283,42191810,Colchones o accesorios para el cuidado del paciente,,0,80509,0 -14284,42191811,Cunas de posicionamiento para bebés,,0,80509,0 -14285,42191812,Kits de suministros de incubadoras para bebés,,0,80509,0 -14286,42191813,Forros de catre para el cuidado del paciente,,0,80509,0 -14287,42191814,Accesorios para incubadora o calentadora de bebés para uso clínico,,0,80509,0 -14288,42191900,Armarios clínicos,,0,80509,0 -14289,42191901,Gabinetes clínicos para junto a la cama o accesorios,,0,80509,0 -14290,42191902,Armarios para uso hospitalario,,0,80509,0 -14291,42191903,Gabinetes de monitoreo para uso médico,,0,80509,0 -14292,42191904,Gabinetes o cajas fuertes para narcóticos,,0,80509,0 -14293,42191905,Gabinetes de tratamiento para uso médico,,0,80509,0 -14294,42191906,Gabinetes calentadores de cobijas o soluciones,,0,80509,0 -14295,42191907,Gabinetes o muebles de almacenamiento de instrumentos para uso médico,,0,80509,0 -14296,42191908,Carritos de historias clínicas o accesorios,,0,80509,0 -14297,42191909,Gabinete computarizado dispensador de medicamentos,,0,80509,0 -14298,42192000,Mesas de examen o de procedimiento clínico,,0,80509,0 -14299,42192001,Mesas de examen o procedimientos médicos para uso general,,0,80509,0 -14300,42192002,Accesorios para mesas de examen o procedimientos médicos para uso general excluyendo sábanas para cubrirlas,,0,80509,0 -14301,42192003,Mesas para masajes de terapia física,,0,80509,0 -14302,42192100,Asientos y taburetes clínicos y productos relacionados,,0,80509,0 -14303,42192101,Asientos o accesorios para sacar sangre o flebotomía,,0,80509,0 -14304,42192102,Reclinadoras o accesorios para uso hospitalario,,0,80509,0 -14305,42192103,Asientos para pacientes,,0,80509,0 -14306,42192104,Taburetes médicos y accesorios,,0,80509,0 -14307,42192106,Asientos para visitantes de instalaciones médicas,,0,80509,0 -14308,42192107,Asientos de examen clínico o accesorios,,0,80509,0 -14309,42192200,Productos para transporte de paciente,,0,80509,0 -14310,42192201,Camillas con ruedas o accesorios para el transporte de pacientes,,0,80509,0 -14311,42192202,Elevadores de camillas o de tijeras,,0,80509,0 -14312,42192203,Asientos geriátricos o accesorios,,0,80509,0 -14313,42192204,Incubadoras para el transporte de pacientes o accesorios,,0,80509,0 -14314,42192205,Accesorios para carritos de pacientes,,0,80509,0 -14315,42192206,Carritos de pacientes,,0,80509,0 -14316,42192207,Camillas para pacientes o accesorios para camillas,,0,80509,0 -14317,42192208,Accesorios para sillas de ruedas,,0,80509,0 -14318,42192209,Rampas para sillas de ruedas,,0,80509,0 -14319,42192210,Sillas de ruedas,,0,80509,0 -14320,42192211,Tablas para mover pacientes o sus accesorios,,0,80509,0 -14321,42192212,Esterilla o sábana para transferencia de pacientes,,0,80509,0 -14322,42192213,Unidad de sistema de calefacción para mantener o evacuar pacientes o accesorios,,0,80509,0 -14323,42192214,Elevador de sillas de ruedas,,0,80509,0 -14324,42192300,Elevadores de paciente,,0,80509,0 -14325,42192301,Elevadores de pacientes o accesorios,,0,80509,0 -14326,42192302,Elevadores hidráulicos o accesorios para uso clínico,,0,80509,0 -14327,42192303,Asientos suspendidos o eslingas para pacientes,,0,80509,0 -14328,42192304,Montacargas de techo para pacientes,,0,80509,0 -14329,42192305,Eslingas para bebés o accesorios,Canguros para bebés o accesorios,0,80509,0 -14330,42192400,Transporte de equipo médico y traslado de productos,,0,80509,0 -14331,42192401,Carritos de emergencia o resucitación,,0,80509,0 -14332,42192402,Carritos específicos para equipos de diagnóstico o monitoreo,,0,80509,0 -14333,42192403,Carritos de aislamiento para uso médico,,0,80509,0 -14334,42192404,Carritos o accesorios para uso médico,,0,80509,0 -14335,42192405,Puestos móviles de irrigación,,0,80509,0 -14336,42192406,Carritos para transportar orinales,,0,80509,0 -14337,42192500,Protectores para equipo médico,,0,80509,0 -14338,42192501,Forros para equipos médicos,,0,80509,0 -14339,42192502,Bolsas para equipos médicos,,0,80509,0 -14340,42192600,Dispositivos de dosificación y medición de medicamentos y suministros,,0,80509,0 -14341,42192601,Moldes de supositorios,,0,80509,0 -14342,42192602,Dispensadores de pastillas o medicamentos o accesorios,,0,80509,0 -14343,42192603,Tazas o botellas para administrar medicamentos o accesorios,,0,80509,0 -14344,42192604,Sistemas o accesorios para la entrega de drogas,Sistemas o accesorios para la entrega de drogas,0,80509,0 -14345,42192605,Kits de encapsulamiento de aneurismas,,0,80509,0 -14346,42192606,Soplador de polvos,Nebulizador manual,0,80509,0 -14347,42201500,Sistemas de tomografía informatizada (TAC o CT) médica y productos relacionados,,0,80509,0 -14348,42201501,Instalación de unidad estacionaria completa para tomografía computarizada ct o cat para uso médico,,0,80509,0 -14349,42201502,Unidades móviles o transportables o unidad de camión para tomografía computarizada ct o cat para uso médico,,0,80509,0 -14350,42201503,Componentes de sistema tridimensional para tomografía computarizada ct o cat para uso médico,,0,80509,0 -14351,42201504,Componentes de hueso de contenido mineral para tomografía computarizada ct o cat para uso médico,,0,80509,0 -14352,42201505,Consolas para tomografía computarizada ct o cat para uso médico,,0,80509,0 -14353,42201507,Componentes de sistema helicoidal para tomografía computarizada ct o cat para uso médico,,0,80509,0 -14354,42201508,Monitores para tomografía computarizada ct o cat para uso médico,,0,80509,0 -14355,42201509,Acondicionadores de energía para tomografía computarizada ct o cat para uso médico,,0,80509,0 -14356,42201510,Simuladores o dispositivos de aseguramiento de calidad o de calibración para tomografía computarizada ct o cat para uso médico,,0,80509,0 -14357,42201511,Escáneres o tubos para tomografía computarizada ct o cat para uso médico,,0,80509,0 -14358,42201512,Mesas o puestos o asientos para tomografía computarizada ct o cat para uso médico,,0,80509,0 -14359,42201513,Componentes de sistema ultra rápido para tomografía computarizada ct o cat para uso médico,,0,80509,0 -14360,42201700,Productos de hacer imágenes de ultrasonido médico y de doppler y de eco,,0,80509,0 -14361,42201701,Ultrasonido cardiaco o doppler o unidades de eco o cardioscopios,,0,80509,0 -14362,42201702,Ultrasonido o unidades de eco fetales o ginecológicas,,0,80509,0 -14363,42201703,Ultrasonido o unidades de eco mamográficas,,0,80509,0 -14364,42201704,Densitómetros de hueso por ultrasonido para uso médico,,0,80509,0 -14365,42201705,Forros para ultrasonido o doppler o eco sonda para uso médico,,0,80509,0 -14366,42201706,Sondas para ultrasonido o doppler o eco para uso médico,,0,80509,0 -14367,42201707,Calentadores de gel para ultrasonido o doppler o eco para uso médico,,0,80509,0 -14368,42201708,Geles para ultrasonido o doppler o eco para uso médico,,0,80509,0 -14369,42201709,Monitores para ultrasonido o doppler o eco para uso médico,,0,80509,0 -14370,42201710,Impresoras para ultrasonido o doppler o eco para uso médico,,0,80509,0 -14371,42201711,Transductores o accesorios para ultrasonido o doppler o eco para uso médico,,0,80509,0 -14372,42201712,Unidades de ultrasonido o doppler o eco pulso o ecografía de diagnóstico general para uso médico,,0,80509,0 -14373,42201713,Componentes tridimensionales para ultrasonido o doppler o eco para uso médico,,0,80509,0 -14374,42201714,Tensiómetros,,0,80509,0 -14375,42201715,Sondas o accesorios para ultrasonido o eco vaginal,,0,80509,0 -14376,42201716,Unidades de ultrasonido vascular,,0,80509,0 -14377,42201717,Almohadillas de transmisión de ultrasonido para uso médico,,0,80509,0 -14378,42201718,Escáneres oftálmicos de ultrasonido para uso médico,,0,80509,0 -14379,42201719,Lociones de escáner por ultrasonido para uso médico,,0,80509,0 -14380,42201800,Productos radiográficos de diagnóstico médico,,0,80509,0 -14381,42201801,Películas de rayos x para cardiología,,0,80509,0 -14382,42201802,Equipos de rayos x para cardiología,,0,80509,0 -14383,42201803,Equipos de rayos x para mamografías,,0,80509,0 -14384,42201804,Unidades de brazo c de rayos x para uso médico,,0,80509,0 -14385,42201805,Equipo de cine fluoroscopio para uso médico,,0,80509,0 -14386,42201806,Equipo de radiología y fluoroscopia rf para uso médico,,0,80509,0 -14387,42201807,Escáneres de radioisótopos para uso médico,,0,80509,0 -14388,42201808,Rejillas móviles de rayos x para uso médico,,0,80509,0 -14389,42201809,Inyectores o accesorios de agentes de contraste para imágenes para uso médico,,0,80509,0 -14390,42201810,Casetes o película de rayos x de uso general para uso médico,,0,80509,0 -14391,42201811,Dispositivos de aseguramiento de calidad o calibración de rayos x para uso médico,,0,80509,0 -14392,42201812,Mesas o puestos o asientos o gabinetes o accesorios de rayos x para uso médico,,0,80509,0 -14393,42201813,Unidades de tomografía de rayos x para uso médico,,0,80509,0 -14394,42201814,Tubos de rayos x para uso médico,,0,80509,0 -14395,42201815,Unidades de rayos x de uso diagnóstico general para uso médico,,0,80509,0 -14396,42201816,Unidades de xerorradiografía para uso médico,,0,80509,0 -14397,42201817,Densitómetros de hueso de rayos x,,0,80509,0 -14398,42201818,Cuñas de pisada y trompos de combinación para equipo radiográfico,,0,80509,0 -14399,42201819,Colgadores o sus accesorios para películas de rayos x,,0,80509,0 -14400,42201820,Rejillas de equipo radiográfico para uso médico,,0,80509,0 -14401,42201821,Sujeta casetes de película radiográfica,,0,80509,0 -14402,42201822,Estuches o fundas o accesorios para equipos de rayos x para uso médico,,0,80509,0 -14403,42201823,Unidad de tubo y transformador de rayos x para uso médico,,0,80509,0 -14404,42201824,Sets de artrografía para uso médico,,0,80509,0 -14405,42201825,Insertos de tubo de aparatos de rayos x para uso médico,,0,80509,0 -14406,42201826,Kits de reparación de aparatos de rayos x para uso médico,,0,80509,0 -14407,42201827,Kits de reparación de carpa de cuarto oscuro de rayos x para uso médico,,0,80509,0 -14408,42201828,Filtros de aparatos de rayos x para uso médico,,0,80509,0 -14409,42201829,Localizadores radiográficos,,0,80509,0 -14410,42201830,Pantallas intensificadoras de rayos x para uso médico,,0,80509,0 -14411,42201831,Tapas de casetes o películas de rayos x para uso médico,,0,80509,0 -14412,42201832,Cubiertas de casetes o película radiográfica,,0,80509,0 -14413,42201833,Cambiadores de casetes o película radiográfica,,0,80509,0 -14414,42201834,Ensamblajes rectificadores de aparatos de rayos x radiográficos para uso médico,,0,80509,0 -14415,42201835,Ensamblajes de unidades de tubos de aparatos de rayos x para uso médico,,0,80509,0 -14416,42201836,Ensamblajes de bandas de compresión de aparatos de rayos x para uso médico,,0,80509,0 -14417,42201837,Enfriadores de agua de rayos x para uso médico,,0,80509,0 -14418,42201838,Catéteres o kits de catéteres de enteroclisis para uso médico,,0,80509,0 -14419,42201839,Bandejas de procedimientos de imágenes para uso médico,,0,80509,0 -14420,42201840,Dispositivos sellantes vasculares,,0,80509,0 -14421,42201841,Papeles de rayos x diagnósticos para uso médico,,0,80509,0 -14422,42201843,Marcador de piel de rayos x para uso médico,,0,80509,0 -14423,42201844,Regla diagnóstica de rayos x para uso médico,,0,80509,0 -14424,42201845,Funda o inserto o sobre para película de radiología,,0,80509,0 -14425,42201846,Colimador de rayos x radiográfico y fluoroscópico,,0,80509,0 -14426,42201847,Sistema móvil de rayos x,,0,80509,0 -14427,42201848,Sistema digital de imágenes de rayos x,,0,80509,0 -14428,42201849,Escáner fluorescente,,0,80509,0 -14429,42201850,Sistema de tomografía nuclear,,0,80509,0 -14430,42201851,Contador beta o gama para uso clínico,,0,80509,0 -14431,42201852,Pantalla fluorescente,,0,80509,0 -14432,42203400,Productos para hacer imágenes vasculares y para cardiología intervencionista y laboratorios de cateterización cardiaca,,0,80509,0 -14433,42203401,Stents coronarios,,0,80509,0 -14434,42203402,Catéteres o sets de diagnóstico o intervención vascular,,0,80509,0 -14435,42203403,Introductores de catéteres o sets de diagnóstico o intervención vascular,,0,80509,0 -14436,42203404,Alambre guía para imágenes vasculares,,0,80509,0 -14437,42203405,Catéter inflable para angioplastia,,0,80509,0 -14438,42203406,Dispositivos de remoción de catéteres o sets de diagnóstico o intervención vascular,,0,80509,0 -14439,42203407,Sets de entrega de medios de contraste para angiografía,,0,80509,0 -14440,42203408,Kits de fundas cardiovasculares,,0,80509,0 -14441,42203409,Valvulotomos angioscópicos,,0,80509,0 -14442,42203410,Funda de catéter cardiovascular,,0,80509,0 -14443,42203411,Carritos para catéteres,,0,80509,0 -14444,42203412,Stents periféricos,,0,80509,0 -14445,42203413,Bobina vascular,,0,80509,0 -14446,42203414,Filtro vascular,,0,80509,0 -14447,42203415,Equipo de laboratorio de cateterización cardíaca,,0,80509,0 -14448,42203416,Catéter de ablación cardíaca,,0,80509,0 -14449,42203417,Endógrafo,,0,80509,0 -14450,42203418,Catéter para arterectomía,,0,80509,0 -14451,42203419,Stents biliares,,0,80509,0 -14452,42203420,Lazo para imágenes vasculares,,0,80509,0 -14453,42203421,Bandejas o paquetes para diagnóstico o intervención vascular,,0,80509,0 -14454,42203422,Pegante de embolización,,0,80509,0 -14455,42203423,Esfera de embolización,,0,80509,0 -14456,42203424,Dispositivo de cierre de sitio de procedimiento de cateterización,,0,80509,0 -14457,42203425,Catéter para embolectomía trombectomía,,0,80509,0 -14458,42203426,Dispositivo de torsión de alambre de guía para angiografía,,0,80509,0 -14459,42203427,Aguja de introducción para angiografía,,0,80509,0 -14460,42203428,Válvula de control y válvula hemostática para angiografía,,0,80509,0 -14461,42203429,Catéter para pericardiocentesis,,0,80509,0 -14462,42203430,Catéter de mapeo para electrofisiología,,0,80509,0 -14463,42203500,Marcapasos cardiacos o productos relacionados,,0,80509,0 -14464,42203501,Generador de marcapasos cardíaco o marcapasos de terapia de re sincronización cardíaca,,0,80509,0 -14465,42203502,Cables o electrodos o accesorios para marcapasos cardíacos,,0,80509,0 -14466,42203503,Introductores de cables o sets para marcapasos cardíacos,,0,80509,0 -14467,42203504,Grabadora cardíaca,,0,80509,0 -14468,42203505,Desfibrilador cardiovertidor implantable icd o desfibrilador para terapia de re sincronización cardíaca crt-d,,0,80509,0 -14469,42203506,Cables o electrodos de desfibrilador cardiovertidor implantable icd o desfibrilador para terapia de re sincronización cardíaca crt-d,,0,80509,0 -14470,42203600,Productos de archivar y información de toma de imágenes radiológicos médicos,,0,80509,0 -14471,42203601,Equipos de sistema de red de imágenes digitales de defensa din,,0,80509,0 -14472,42203602,Equipos de sistema estándar de comunicaciones de imágenes digitales en medicina dicom,,0,80509,0 -14473,42203603,Sistemas de computador para archivo de fotografías médicas pacs,,0,80509,0 -14474,42203604,Hardware de sistema de archivo de película de rayos x para usos médicos,,0,80509,0 -14475,42203605,Software de sistema de archivo de película de rayos x para usos médicos,,0,80509,0 -14476,42203606,Sistemas de telerradiografía,,0,80509,0 -14477,42203700,Suministros y equipo de procesado de toma de imágenes médicos,,0,80509,0 -14478,42203701,Estampadores de luz del día para películas de rayos x o impresoras de identificación para uso médico,,0,80509,0 -14479,42203702,Procesadores para luz del día o cuarto oscuro de imágenes médicas,,0,80509,0 -14480,42203703,Cajas de paso de seguridad de rayos x para uso médico,,0,80509,0 -14481,42203704,Toners o desarrolladores para uso médico,,0,80509,0 -14482,42203705,Impresoras láser en seco para imágenes o reproductores de imágenes para uso médico,,0,80509,0 -14483,42203706,Kits de químicos para procesar películas de rayos x de uso médico,,0,80509,0 -14484,42203707,Equipos o suministros de cuarto oscuro de rayos x para uso médico,,0,80509,0 -14485,42203708,Fijadores para procesar película de rayos x para uso médico,,0,80509,0 -14486,42203709,Sets de calibradores para sistemas de combinación de desplegadores e impresoras de rayos x,,0,80509,0 -14487,42203710,Marcadores de películas de rayos x para uso médico,,0,80509,0 -14488,42204000,Productos de protección y resguardo radiológico médico,,0,80509,0 -14489,42204001,Estantes para delantales de protección radiológica para uso médico,,0,80509,0 -14490,42204002,Delantales o máscaras o cortinas de protección radiológica para uso médico,,0,80509,0 -14491,42204003,Contenedores portátiles de protección para materiales radiológicos radiactivos para uso médico,,0,80509,0 -14492,42204004,Auriculares de protección radiológica para uso médico,,0,80509,0 -14493,42204005,Guantes de protección radiológica para uso médico,,0,80509,0 -14494,42204006,Pantallas de pie o portátiles de protección radiológica para uso médico,,0,80509,0 -14495,42204007,Paneles instalados de piso o techo o pared de protección radiológica para uso médico,,0,80509,0 -14496,42204008,Cámaras o habitaciones o cajas fuertes de protección radiológica para uso médico,,0,80509,0 -14497,42204009,Anteojos de protección radiológica para uso médico,,0,80509,0 -14498,42211500,Ayuda de ambulación y traslado y posicionar para personas físicamente discapacitadas,,0,80509,0 -14499,42211501,Bastones o accesorios para bastones,,0,80509,0 -14500,42211502,Muletas o accesorios para muletas,,0,80509,0 -14501,42211503,Dispositivos de posicionamiento,,0,80509,0 -14502,42211504,Levantadores o ayudas para ponerse de pie,,0,80509,0 -14503,42211505,Accesorios para caminadores o andadores,,0,80509,0 -14504,42211506,Caminadores o andadores,,0,80509,0 -14505,42211507,Dispositivos de cuerpo entero para deslizarse o voltearse,,0,80509,0 -14506,42211508,Dispositivos o accesorios de movilidad multifuncional,,0,80509,0 -14507,42211509,Cascos protectores de cabeza o cara o dispositivos o accesorios para los discapacitados físicamente,,0,80509,0 -14508,42211600,Baño y ayudas de baño para personas físicamente discapacitadas,,0,80509,0 -14509,42211601,Tablas para bañarse para los discapacitados físicamente,,0,80509,0 -14510,42211602,Cepillos de baño o esponjas o estropajos para los discapacitados físicamente,,0,80509,0 -14511,42211603,Elevadores para baño o accesorios para los discapacitados físicamente,,0,80509,0 -14512,42211604,Guantes de baño para los discapacitados físicamente,,0,80509,0 -14513,42211605,Almohadas de baño para los discapacitados físicamente,,0,80509,0 -14514,42211606,Cómodas o accesorios para los discapacitados físicamente,,0,80509,0 -14515,42211607,Asientos de inodoro elevados para los discapacitados físicamente,,0,80509,0 -14516,42211608,Barras de sujeción o barandas para la tina para los discapacitados físicamente,,0,80509,0 -14517,42211610,Asientos o sillas para la ducha o el baño para los discapacitados físicamente,,0,80509,0 -14518,42211611,Baños de asiento para los discapacitados físicamente,,0,80509,0 -14519,42211612,Soportes de brazos para el inodoro para los discapacitados físicamente,,0,80509,0 -14520,42211613,Marcos para el inodoro para los discapacitados físicamente,,0,80509,0 -14521,42211614,Asientos de inodoro para los discapacitados físicamente,,0,80509,0 -14522,42211615,Levantadores de asientos de inodoro para los discapacitados físicamente,,0,80509,0 -14523,42211616,Ayudas de higiene o estimulación de tocador para los discapacitados físicamente,,0,80509,0 -14524,42211617,Bancos de transferencia para los discapacitados físicamente,,0,80509,0 -14525,42211618,Alarma de pulso de mojada en la cama para los discapacitados físicamente,,0,80509,0 -14526,42211619,Jabón protector de ojos y oídos para los discapacitados físicamente,,0,80509,0 -14527,42211620,Bandeja o contenedor de champú para los discapacitados físicamente,,0,80509,0 -14528,42211800,Ayuda para vestirse y asearse para personas físicamente discapacitadas,,0,80509,0 -14529,42211801,Ganchos para abotonarse para los discapacitados físicamente,,0,80509,0 -14530,42211802,Kits para vestirse para los discapacitados físicamente,,0,80509,0 -14531,42211803,Palos para vestirse para los discapacitados físicamente,,0,80509,0 -14532,42211804,Cepillos de pelo o peinillas para los discapacitados físicamente,,0,80509,0 -14533,42211805,Espejo de inspección para los discapacitados físicamente,,0,80509,0 -14534,42211806,Aplicadores de lociones para los discapacitados físicamente,,0,80509,0 -14535,42211807,Cuidado de la boca para los discapacitados físicamente,,0,80509,0 -14536,42211808,Cortaúñas o limas para los discapacitados físicamente,,0,80509,0 -14537,42211809,Ganchos para sujetarse los pantalones para los discapacitados físicamente,,0,80509,0 -14538,42211810,Calzadores para los discapacitados físicamente,,0,80509,0 -14539,42211811,Hormas de zapatos para los discapacitados físicamente,,0,80509,0 -14540,42211812,Media o ayudas para apilar para los discapacitados físicamente,,0,80509,0 -14541,42211813,Jaladores de cremalleras para los discapacitados físicamente,,0,80509,0 -14542,42211900,Ayuda de preparación de comida y bebida y alimentos para personas físicamente discapacitadas,,0,80509,0 -14543,42211901,Materiales anti resbalón para los discapacitados físicamente,,0,80509,0 -14544,42211902,Dispositivos de ayuda para cocinar para los discapacitados físicamente,,0,80509,0 -14545,42211903,Abre latas para los físicamente discapacitados,,0,80509,0 -14546,42211904,Picadores para los discapacitados físicamente,,0,80509,0 -14547,42211905,Tazas o tazones (mugs) para los discapacitados físicamente,,0,80509,0 -14548,42211906,Sujetadores de cubiertos o de utensilios para los discapacitados físicamente,,0,80509,0 -14549,42211907,Cubiertos o utensilios para los discapacitados físicamente,,0,80509,0 -14550,42211908,Tablas para cortar o pelar para los discapacitados físicamente,,0,80509,0 -14551,42211909,Sujetadores de bebida para los discapacitados físicamente,,0,80509,0 -14552,42211910,Baberos o recogedores de comida para los discapacitados físicamente,,0,80509,0 -14553,42211911,Forros para comida para los discapacitados físicamente,,0,80509,0 -14554,42211912,Sujetadores para dispositivos de cocina para los discapacitados físicamente,,0,80509,0 -14555,42211913,Temporizadores digitales jumbo para los discapacitados físicamente,,0,80509,0 -14556,42212200,Auxilio de manejo de medicamentos para personas físicamente discapacitadas,,0,80509,0 -14557,42212201,Trituradores o partidores de píldoras para los discapacitados físicamente,,0,80509,0 -14558,42212202,Organizadores de píldoras para los discapacitados físicamente,,0,80509,0 -14559,42212203,Recordadores de píldoras para los discapacitados físicamente,,0,80509,0 -14560,42212204,Exprimidores de tubos para los discapacitados físicamente,,0,80509,0 -14561,42212300,Ayuda para llegar o agarrar para personas físicamente discapacitadas,,0,80509,0 -14562,42212301,Materiales o dispositivos de agarre para los discapacitados físicamente,,0,80509,0 -14563,42212302,Sujetadores para los discapacitados físicamente,,0,80509,0 -14564,42212303,Abridores de contenedores para los discapacitados físicamente,,0,80509,0 -14565,42212304,Alcanzadores para los discapacitados físicamente,,0,80509,0 -14566,42221500,Accesorios y catéteres y cánulas intravenosas y arteriales,,0,80509,0 -14567,42221501,Catéteres de línea arterial,,0,80509,0 -14568,42221502,Válvulas de descarga de catéteres arteriales de línea continua,,0,80509,0 -14569,42221503,Catéteres venosos centrales,,0,80509,0 -14570,42221504,Catéteres intravenosos periféricos para uso general,,0,80509,0 -14571,42221505,Catéteres pediátricos o intravenosos para venas del cuero cabelludo o arteriales,,0,80509,0 -14572,42221506,Catéteres umbilicales,,0,80509,0 -14573,42221507,Kits de arranque intravenoso o arterial sin catéter,,0,80509,0 -14574,42221508,Kits de cuidado de la piel para catéter arterial o intravenoso,,0,80509,0 -14575,42221509,Bandejas para catéteres intravenosos o arteriales,,0,80509,0 -14576,42221512,Cánulas intravenosas o arteriales y accesorios,,0,80509,0 -14577,42221513,Kits de cateterización cardiovascular,,0,80509,0 -14578,42221514,Catéter de arteria pulmonar,,0,80509,0 -14579,42221515,Kit de reparación de catéter venoso central,,0,80509,0 -14580,42221516,Oclusor de la punta del catéter,,0,80509,0 -14581,42221517,Catéter para hemodiálisis,,0,80509,0 -14582,42221518,Material de vendaje de férulas,,0,80509,0 -14583,42221600,Tubería y juegos de administración arterial o intravenoso y productos relacionados,,0,80509,0 -14584,42221601,Detectores de burbujas de aire para administración arterial o intravenosa,,0,80509,0 -14585,42221602,Puertos o lugares de inyección o tapas o protectores para administración arterial o intravenosa,,0,80509,0 -14586,42221603,Tubos de extensión arteriales o intravenosos,,0,80509,0 -14587,42221604,Adaptadores o conectores o candados o tapas o protectores para tubos arteriales o intravenosos,,0,80509,0 -14588,42221605,Válvulas de chequeo de tubos arteriales o intravenosos,,0,80509,0 -14589,42221606,Pinzas de tubos intravenosos o arteriales,,0,80509,0 -14590,42221607,Filtros o pantallas para tubos arteriales o intravenosos de uso general,,0,80509,0 -14591,42221608,Etiquetas o cintas de identificación de tubos intravenosos o arteriales,,0,80509,0 -14592,42221609,Sets de administración de tubos intravenosos o arteriales,,0,80509,0 -14593,42221610,Puertos o tapas o protectores de entrada de espiga de tubos intravenosos,,0,80509,0 -14594,42221611,Espigas o tapas o protectores de tubos intravenosos,,0,80509,0 -14595,42221612,Puertos de inyección o llaves de paso o colectores sin aguja de tubos arteriales o intravenosos,,0,80509,0 -14596,42221613,Cierres o broches de extensión de transferencia de tubos intravenosos,,0,80509,0 -14597,42221614,Kits de administración de tubos intravenosos con catéter,,0,80509,0 -14598,42221615,Tubos intravenosos de medicación secundaria,,0,80509,0 -14599,42221616,Sets de extensión arterial o intravenosa,,0,80509,0 -14600,42221617,Calentadores de resistencia para agujas,,0,80509,0 -14601,42221618,Bandejas para procedimientos arteriales o intravenosos,,0,80509,0 -14602,42221619,Kit colector de inyecciones sin aguja,,0,80509,0 -14603,42221700,Bolsas de infusión y recipientes y productos relacionados intravenosos y arteriales,,0,80509,0 -14604,42221701,Bolsas o contenedores de infusión arterial o intravenosa de único puerto,,0,80509,0 -14605,42221702,Bolsas o contenedores de transferencia de infusión arterial o intravenosa,,0,80509,0 -14606,42221703,Calentadores de fluido de infusión arterial o intravenosa,,0,80509,0 -14607,42221704,Bolsas de infusión de presión arterial o intravenosa,,0,80509,0 -14608,42221705,Ensamblajes de vial de infusión de analgésicos,,0,80509,0 -14609,42221706,Picos de bolsas o contenedores de transferencia de infusión arterial o intravenosa,,0,80509,0 -14610,42221707,Sets o kits de infusión de analgésicos,,0,80509,0 -14611,42221900,Productos de regulación y medida del flujo intravenoso o arterial,,0,80509,0 -14612,42221901,Flujómetros o reguladores intravenosos calibrados por dial,,0,80509,0 -14613,42221902,Contadores o reguladores de goteo intravenoso,,0,80509,0 -14614,42221903,Detectores ultrasónicos de flujo de sangre,,0,80509,0 -14615,42221904,Flujómetro de sangre electromagnético,,0,80509,0 -14616,42221905,Flujómetro de sangre láser,,0,80509,0 -14617,42221906,Flujómetro de sangre de resonancia magnética,,0,80509,0 -14618,42222000,"Bombas de infusión intravenosa, así como sus analizadores, sensores y accesorios",,0,80509,0 -14619,42222001,Bombas de infusión intravenosa de uso general,,0,80509,0 -14620,42222002,Bombas de infusión de jeringa intravenosa,,0,80509,0 -14621,42222003,Bombas de infusión intravenosa multicanal,,0,80509,0 -14622,42222004,Bombas de infusión controlada de analgésicos para pacientes,,0,80509,0 -14623,42222005,Partes o accesorios de bombas intravenosas,,0,80509,0 -14624,42222006,Analizadores o sensores de bombas de infusión intravenosas,,0,80509,0 -14625,42222007,Transductores de bombas de infusión intravenosas,,0,80509,0 -14626,42222008,Kits o accesorios de bombas de infusión,,0,80509,0 -14627,42222009,Actuador de jeringa para un inyector,,0,80509,0 -14628,42222100,Sistemas de transporte y suspensión de equipo arterial y intravenoso,,0,80509,0 -14629,42222101,Colgadores de equipos de línea arterial o intravenosa,,0,80509,0 -14630,42222102,Rieles o colgadores montados para sistemas de infusión intravenosa por gravedad,,0,80509,0 -14631,42222103,Postes de infusión intravenosa para sillas de ruedas,,0,80509,0 -14632,42222104,Postes o puestos de línea arterial o intravenosa,,0,80509,0 -14633,42222300,Productos de transfusión y administración de sangre,,0,80509,0 -14634,42222301,Kits de administración de transfusión de sangre,,0,80509,0 -14635,42222302,Filtros o accesorios o pantallas para transfusión de sangre,,0,80509,0 -14636,42222303,Sistemas de identificación para administración o transfusión de sangre,,0,80509,0 -14637,42222304,Tubos para administración o transfusión de sangre,,0,80509,0 -14638,42222305,Pinzas para tubos de administración o transfusión de sangre,,0,80509,0 -14639,42222306,Sistemas de recolección de desechos para administración o transfusión de sangre,,0,80509,0 -14640,42222307,Sistemas de calentamiento o transfusión de sangre,,0,80509,0 -14641,42222308,Bolsas o contenedores para administración o transfusión de sangre,,0,80509,0 -14642,42222309,Sistemas de conservación para administración o transfusión de sangre,,0,80509,0 -14643,42231500,Suministros y equipo de alimentación enteral,,0,80509,0 -14644,42231501,Bombas de infusión para alimentación enteral,,0,80509,0 -14645,42231502,Sets de administración de alimentación enteral,,0,80509,0 -14646,42231503,Sets o bandejas de irrigación para alimentación enteral,,0,80509,0 -14647,42231504,Bolsas o contenedores para nutrición enteral,,0,80509,0 -14648,42231505,Adaptadores o conectores o extensiones para alimentación enteral,,0,80509,0 -14649,42231506,Dispositivos para asegurar tubos naso entéricos,,0,80509,0 -14650,42231507,Cepillos para limpiar tubos enterales,,0,80509,0 -14651,42231508,Válvulas de fijación de alimentación enteral,,0,80509,0 -14652,42231509,Cámaras de pesaje de nutrición enteral,,0,80509,0 -14653,42231510,Sets de tubos de bombeo de infusión para alimentación enteral,,0,80509,0 -14654,42231600,Accesorios o dispositivos de acceso de jejunostomía y gastronomía,,0,80509,0 -14655,42231601,Tubos de gastrostomía para uso general,,0,80509,0 -14656,42231602,Tubos para gastrostomía endoscópica percutánea,,0,80509,0 -14657,42231603,Tubos para yeyunostomía,,0,80509,0 -14658,42231604,Botones de acceso gástrico,,0,80509,0 -14659,42231605,Sujetadores de tubos de gastrostomía o yeyunostomía,,0,80509,0 -14660,42231606,Kits de descompresión del colon,,0,80509,0 -14661,42231608,Sets de catéter y aguja para yeyunostomía,,0,80509,0 -14662,42231609,Kits de tubos de alimentación para gastrostomía,,0,80509,0 -14663,42231800,Fórmulas y productos para apoyo nutritivo,,0,80509,0 -14664,42231801,Fórmulas de suplementos para adultos de uso general,,0,80509,0 -14665,42231802,Fórmulas de suplementos pediátricos,,0,80509,0 -14666,42231803,Fórmulas de suplementos específicos para enfermedades de adultos,,0,80509,0 -14667,42231804,Fórmulas de suplementos específicos para enfermedades pediátricas,,0,80509,0 -14668,42231805,Barras nutricionales o pudín u otros suplementos,,0,80509,0 -14669,42231806,Espesantes de comidas o bebidas nutricionales para uso médico,,0,80509,0 -14670,42231807,Chupos de teteros,,0,80509,0 -14671,42231808,Botellas de alimentación o accesorios,,0,80509,0 -14672,42231900,Equipo y suministros de lactancia materna,,0,80509,0 -14673,42231901,Bombas de seno o sus accesorios,,0,80509,0 -14674,42231902,Conchas o protectores de senos,,0,80509,0 -14675,42231903,Kits de bombas de senos,,0,80509,0 -14676,42231904,Almohada para lactancia,,0,80509,0 -14677,42232000,"Trituradores de pastillas, cortapastillas y productos relacionados",,0,80509,0 -14678,42232001,Pulverizadores de tabletas o accesorios,,0,80509,0 -14679,42232002,Dispensadores o accesorios de pulverizadores de tabletas,,0,80509,0 -14680,42232003,Cortadores de tabletas o accesorios,,0,80509,0 -14681,42241500,Suministros de vaciado de escayola y tablillas,,0,80509,0 -14682,42241501,Zapatos de yeso,,0,80509,0 -14683,42241502,Materiales para acolchonar yesos o tablillas,,0,80509,0 -14684,42241503,Protectores de yesos o tablillas,,0,80509,0 -14685,42241504,Media o revestimientos para yeso o tablillas,,0,80509,0 -14686,42241505,Rollos o cintas para enyesar para uso ortopédico,,0,80509,0 -14687,42241506,Material de yeso para tablillas para uso ortopédico,,0,80509,0 -14688,42241507,Sistemas de tablillas ortopédicas,,0,80509,0 -14689,42241509,Componentes de ortesis termoplástica,,0,80509,0 -14690,42241510,Kits o materiales para tablillas termoplásticas,,0,80509,0 -14691,42241511,Sets de tablillas de tracción,,0,80509,0 -14692,42241512,Materiales de unión de yesos o tablillas,,0,80509,0 -14693,42241513,Estuches para transportar y almacenar tablillas o tablillas pre cortadas o sistemas de tablillas,,0,80509,0 -14694,42241514,Rollos de instrumentos para sets o accesorios de tablillas,,0,80509,0 -14695,42241515,Estuches para tablillas o accesorios,,0,80509,0 -14696,42241516,Marcador para material de entablillado,,0,80509,0 -14697,42241517,Esparcidor de yeso,,0,80509,0 -14698,42241600,Equipo y piezas y accesorios de vaciado de escayola,,0,80509,0 -14699,42241601,Carritos para yeso o tablillas,,0,80509,0 -14700,42241602,Cortadores o sierras para yeso,,0,80509,0 -14701,42241603,Sistemas de remoción de yeso,,0,80509,0 -14702,42241604,Vacíos de yeso,,0,80509,0 -14703,42241606,Bandejas de impresión de yeso,,0,80509,0 -14704,42241607,Puestos de yeso,,0,80509,0 -14705,42241700,Productos blandos ortopédicos para la extremidad inferior,,0,80509,0 -14706,42241701,Fieltros de recubrimiento ortopédico para tobillo o pie,,0,80509,0 -14707,42241702,Fieltros de recubrimiento ortopédico para cadera,,0,80509,0 -14708,42241703,Soportes de abrazaderas de rodilla o de abrazaderas de rodilla articuladas,,0,80509,0 -14709,42241704,Inmovilizadores o vendas artroscópicas para rodilla,,0,80509,0 -14710,42241705,Fieltros de recubrimiento ortopédico para pierna o accesorios,,0,80509,0 -14711,42241706,Productos ortóticos o para el cuidado de los pies,,0,80509,0 -14712,42241707,Abrazaderas para caminar,,0,80509,0 -14713,42241708,Almohadas para fractura del fémur,,0,80509,0 -14714,42241709,Zapatos post operatorios o accesorios,,0,80509,0 -14715,42241800,Productos blando ortopédicos para el torso y extremidad superior,,0,80509,0 -14716,42241801,Fieltros de recubrimiento ortopédico para el brazo,,0,80509,0 -14717,42241802,Fieltros de recubrimiento ortopédico para la espalda o lumbares o para el sacro,,0,80509,0 -14718,42241803,Collares cervicales o abrazaderas para el cuello,,0,80509,0 -14719,42241804,Fieltros de recubrimiento ortopédico para la clavícula,,0,80509,0 -14720,42241805,Fieltros de recubrimiento ortopédico para el codo,,0,80509,0 -14721,42241806,Fieltros de recubrimiento ortopédico para el antebrazo o la muñeca o el pulgar,,0,80509,0 -14722,42241807,Fieltros de recubrimiento ortopédico para la mano o el dedo,,0,80509,0 -14723,42241808,Fieltros de recubrimiento ortopédico para las costillas o el abdomen,,0,80509,0 -14724,42241809,Fieltros de recubrimiento ortopédico para los hombros,,0,80509,0 -14725,42241811,Faja para hernias,,0,80509,0 -14726,42242000,"Dispositivos, accesorios y suministros protésicos",,0,80509,0 -14727,42242001,Dispositivos prostéticos para las extremidades inferiores,,0,80509,0 -14728,42242002,Dispositivos prostéticos para las extremidades superiores,,0,80509,0 -14729,42242003,Dispositivos o accesorios de sujeción prostética,,0,80509,0 -14730,42242004,Media o forro prostético de muñón,,0,80509,0 -14731,42242100,Suministros y accesorios de tracción ortopédica,,0,80509,0 -14732,42242101,Suministros de tracción de brazo,,0,80509,0 -14733,42242102,Suministros de tracción de mano o dedos,,0,80509,0 -14734,42242103,Suministros de tracción de cabeza o cuello,,0,80509,0 -14735,42242104,Suministros de tracción de piernas,,0,80509,0 -14736,42242105,Carritos móviles de tracción,,0,80509,0 -14737,42242106,Suministros de tracción de pelvis o espalda,,0,80509,0 -14738,42242107,Suministros de terapia de tracción de perno,,0,80509,0 -14739,42242108,Hardware o pesas de tracción ortopédica,,0,80509,0 -14740,42242109,Fieltros de recubrimiento de tracción ortopédica para uso general,,0,80509,0 -14741,42242300,Equipo y suministros ortopédicos,,0,80509,0 -14742,42242301,Bombas de cicatriz ortopédica,,0,80509,0 -14743,42242302,Aplicaciones ortopédicas de miembro superior,,0,80509,0 -14744,42251500,Productos de terapia y evaluación sensoria y perceptual y dexteridad y cognitiva,,0,80509,0 -14745,42251501,Productos de enseñanza para vestirse,,0,80509,0 -14746,42251502,Productos para evaluación o pruebas cognitivas o de destreza o de percepción o sensoriales,,0,80509,0 -14747,42251503,Juegos terapéuticos,,0,80509,0 -14748,42251504,Tableros perforados o tableros de actividades de uso terapéutico,,0,80509,0 -14749,42251505,Rompecabezas de uso terapéutico,,0,80509,0 -14750,42251506,Cajas de decoración de uso terapéutico,,0,80509,0 -14751,42251600,Equipo y dispositivos de ejercicio de rehabilitación,,0,80509,0 -14752,42251601,Vigas de balance o de soporte o mecedoras para rehabilitación o terapia,,0,80509,0 -14753,42251602,Dispositivos para escalar para rehabilitación o terapia,,0,80509,0 -14754,42251603,Dispositivos o accesorios de moción pasiva continua cpm,,0,80509,0 -14755,42251604,Patines de ejercicio de extremidades para rehabilitación o terapia,,0,80509,0 -14756,42251605,Pedales de ejercicio para rehabilitación o terapia,,0,80509,0 -14757,42251606,Tablas para ejercicios sin gravedad para rehabilitación o terapia,,0,80509,0 -14758,42251607,Poleas o accesorios para rehabilitación o terapia,,0,80509,0 -14759,42251608,Bandas de ejercicio o masilla o tubos o accesorios para rehabilitación o terapia,,0,80509,0 -14760,42251610,Pelotas o accesorios terapéuticos,,0,80509,0 -14761,42251611,Dispositivos de movimiento vestibular para rehabilitación o terapia,,0,80509,0 -14762,42251612,Pesas o sets o accesorios para rehabilitación o terapia,,0,80509,0 -14763,42251613,Bandas caminadoras ejercitadoras para rehabilitación o terapia,,0,80509,0 -14764,42251614,Bolsas de arena o sets de bolsas de arena para rehabilitación o terapia,,0,80509,0 -14765,42251615,Cinturones de pesas o kits para rehabilitación o terapia,,0,80509,0 -14766,42251616,Vibradores eléctricos para rehabilitación o terapia,,0,80509,0 -14767,42251617,Asientos acolchonados o accesorios terapéuticos,,0,80509,0 -14768,42251618,Ejercitadoras de muñecas para rehabilitación o terapia,,0,80509,0 -14769,42251619,Arcilla cerámica terapéutica o accesorios,,0,80509,0 -14770,42251620,Esterillas o plataformas para rehabilitación o terapia,,0,80509,0 -14771,42251700,Productos para entrenamiento del modo de andar,,0,80509,0 -14772,42251701,Cinturones de manera de caminar para rehabilitación o terapia,,0,80509,0 -14773,42251702,Rampas de entrenamiento para rehabilitación o terapia,,0,80509,0 -14774,42251703,Escaleras de entrenamiento para rehabilitación o terapia,,0,80509,0 -14775,42251705,Barras paralelas para rehabilitación o terapia,,0,80509,0 -14776,42251706,Caminadoras o bicicletas o ejercitadoras de manera de caminar,,0,80509,0 -14777,42251800,Equipo para hacer dura el trabajo para rehabilitación o terapia,,0,80509,0 -14778,42251801,Tableros de salto o accesorios para rehabilitación o terapia,,0,80509,0 -14779,42251802,Cajas de levantar o accesorios para rehabilitación o terapia,,0,80509,0 -14780,42251803,Árboles de tubos o accesorios para rehabilitación o terapia,,0,80509,0 -14781,42251804,Carros de halar o empujar o accesorios para rehabilitación o terapia,,0,80509,0 -14782,42251805,Mesas o estaciones de trabajo o accesorios para rehabilitación o terapia,,0,80509,0 -14783,42261600,Suministros y equipo de autopsia,,0,80509,0 -14784,42261601,Recolectores de polvo de huesos,,0,80509,0 -14785,42261602,Bolsa para cadáver de uso médico,,0,80509,0 -14786,42261604,Apoya cabezas para autopsias,,0,80509,0 -14787,42261605,Tablas de cuerpo para autopsias,,0,80509,0 -14788,42261606,Balanzas colgantes para autopsias,,0,80509,0 -14789,42261607,Bolsas o contenedores para especímenes para autopsias,,0,80509,0 -14790,42261608,Kits de enfermedades infecciosas para autopsias,,0,80509,0 -14791,42261609,Cintas o brazaletes de identificación post mortem,,0,80509,0 -14792,42261610,Aspiradoras o tubos de vacío para recolección de fluidos para autopsias,,0,80509,0 -14793,42261611,Termómetros rectales post mortem,,0,80509,0 -14794,42261612,Dispositivos para enderezar dedos post mortem,,0,80509,0 -14795,42261613,Kits de construcción de tejidos de cadáveres,,0,80509,0 -14796,42261800,Equipo y suministros de transporte y almacenaje del cadáver,,0,80509,0 -14797,42261801,Gabinetes de almacenamiento de cadáveres,,0,80509,0 -14798,42261805,Cuartos refrigeradores para la morgue,,0,80540,0 -14799,42261806,Congeladores para morgues,,0,80540,0 -14800,42261807,Carritos de autopsia,,0,80540,0 -14801,42261809,Dispositivos de elevación o transferencia de cadáveres,,0,80540,0 -14802,42261810,Contenedores para transporte de cuerpos,,0,80540,0 -14803,42261902,Máscaras anti putrefacción,,0,80509,0 -14804,42261903,Kits o suministros de detección de sangre post mortem,,0,80509,0 -14805,42261904,Kits de recolección de evidencia biológica,,0,80509,0 -14806,42262008,Protectores de ojos,,0,80540,0 -14807,42271500,Productos para el control respiratorio,,0,80509,0 -14808,42271501,Monitores de apnea o accesorios,,0,80509,0 -14809,42271502,Monitores de gas arterial o accesorios,,0,80509,0 -14810,42271503,Monitores de óxido de carbono exhalado o suministros,,0,80509,0 -14811,42271504,Estetoscopios esofágicos,,0,80509,0 -14812,42271505,Kits de monitoreo respiratorio o sus accesorios,,0,80509,0 -14813,42271506,Monitores de oxígeno o suministros,,0,80509,0 -14814,42271600,Productos de comprobación y tratamiento de las funciones pulmonares,,0,80509,0 -14815,42271601,Pletismógrafos de cuerpo,,0,80509,0 -14816,42271602,Espirómetros o sus accesorios o suministros,,0,80509,0 -14817,42271603,Pantallas de función pulmonar para junto a la cama,,0,80509,0 -14818,42271604,Flujómetros de pico,,0,80509,0 -14819,42271605,Calculadoras de función pulmonar,,0,80509,0 -14820,42271606,Dispositivos de calibración pulmonar,,0,80509,0 -14821,42271607,Tubos de función pulmonar o accesorios,,0,80509,0 -14822,42271608,Productos de prueba de estrés pulmonar,,0,80509,0 -14823,42271609,Monitores de estudio de sueño o accesorios,,0,80509,0 -14824,42271610,Monitores transcutáneos o productos relacionados,,0,80509,0 -14825,42271611,Monitores de ventilación pulmonar,,0,80509,0 -14826,42271612,Analizadores o monitores de gas pulmonar,,0,80509,0 -14827,42271613,Monitores de presión pulmonar,,0,80509,0 -14828,42271614,Monitores de temperatura respiratoria,,0,80509,0 -14829,42271615,Neumotacos,,0,80509,0 -14830,42271616,Filtros de monitoreo de función pulmonar,,0,80509,0 -14831,42271617,Percusores de pecho,,0,80509,0 -14832,42271618,Estilógrafos grabadores para espirómetros,,0,80509,0 -14833,42271619,Entrenador de músculos de inhalación,,0,80509,0 -14834,42271620,Monitor de agua en los pulmones,,0,80509,0 -14835,42271621,Dispositivo de terapia de presión de exhalación positiva pep,,0,80509,0 -14836,42271622,Chaleco de despeje de vías aéreas,,0,80509,0 -14837,42271700,Sistemas y dispositivos de terapia de entrega de oxígeno,,"0,1",80509,0 -14838,42271701,Cilindros de gas o dispositivos relacionados para uso médico,,"0,1",80509,0 -14839,42271702,Concentradores de oxígeno,,0,80509,0 -14840,42271703,Mezcladores de aire de oxígeno,,0,80509,0 -14841,42271704,Temporizadores de oxígeno,,0,80509,0 -14842,42271705,Conectores o adaptadores de suministro de oxígeno,,0,80509,0 -14843,42271706,Compresores para terapia respiratoria,,0,80509,0 -14844,42271707,Sensores de flujo o reguladores o componentes,,0,80509,0 -14845,42271708,Máscaras de oxígeno o partes para uso médico,,0,80509,0 -14846,42271709,Cánulas nasales para uso médico,,0,80509,0 -14847,42271710,Catéteres nasales o kits de cateterización para uso médico,,0,80509,0 -14848,42271711,Capuchas para la cabeza para uso médico,,0,80509,0 -14849,42271712,Carpas de aerosol para uso médico,,0,80509,0 -14850,42271713,Cámaras hiperbáricas para uso médico,,0,80509,0 -14851,42271714,Incubadoras para uso médico,,0,80509,0 -14852,42271715,Tubos o conectores de oxígeno para uso médico,,0,80509,0 -14853,42271716,Estuches para insufladores nasales,,0,80509,0 -14854,42271717,Inhaladores o sets,,0,80509,0 -14855,42271718,Accesorios para productos de sistemas de entrega de oxígeno para terapia o sus suministros,,0,80509,0 -14856,42271719,Insuflador de oxígeno o sus accesorios,,0,80509,0 -14857,42271720,Convertidores de oxígeno líquido,,0,80509,0 -14858,42271721,Filtros de concentradores de oxígeno,,0,80509,0 -14859,42271722,Computador de toma de oxígeno,,0,80509,0 -14860,42271800,Productos de terapia aerosol y humedad respiratoria,,0,80509,0 -14861,42271801,Humidificadores o vaporizadores respiratorios,,0,80509,0 -14862,42271802,Nebulizadores o accesorios,,0,80509,0 -14863,42271803,Sets de transferencia para terapia respiratoria,,0,80509,0 -14864,42271900,Productos para la gestión de la vía aérea,,0,80509,0 -14865,42271901,Vías aéreas faríngeas,,0,80509,0 -14866,42271902,Tubos esofágicos,,0,80509,0 -14867,42271903,Tubos endotraqueales,,0,80509,0 -14868,42271904,Tubos de traqueotomía,,0,80509,0 -14869,42271905,Tubos endobronquiales,,0,80509,0 -14870,42271906,Kits de reparación de tubos endotraqueales o de traqueotomía,,0,80509,0 -14871,42271907,Productos de aspiradores respiratorios o accesorios,,0,80509,0 -14872,42271908,Sujetadores de vías aéreas artificiales,,0,80509,0 -14873,42271909,Accesorios de vías aéreas artificiales,,0,80509,0 -14874,42271910,Sets endotraqueales o de traqueotomía,,0,80509,0 -14875,42271911,Kits de manómetros respiratorios,,0,80509,0 -14876,42271912,Tubos nasofaríngeos,,0,80509,0 -14877,42271913,Vías aéreas faríngeas o kits de vías aéreas,,0,80509,0 -14878,42271914,Mordazas de presión de vías aéreas,,0,80509,0 -14879,42271915,Accesorios de traqueotomía,,0,80509,0 -14880,42271916,Faringómetro o accesorios,,0,80509,0 -14881,42272000,Suministros de intubación,,0,80509,0 -14882,42272001,Laringoscopios o accesorios,,0,80509,0 -14883,42272002,Aerosoles laríngeos,,0,80509,0 -14884,42272003,Bloques de mordida,,0,80509,0 -14885,42272004,Estiletes de intubación,,0,80509,0 -14886,42272005,Fórceps de intubación,,0,80509,0 -14887,42272006,Introductores,,0,80509,0 -14888,42272007,Herramientas de doblado,,0,80509,0 -14889,42272008,Sondas o guías de intubación,,0,80509,0 -14890,42272009,Detectores de dióxido de carbono para pacientes,,0,80509,0 -14891,42272011,Catéteres de succión o sus accesorios,,0,80540,0 -14892,42272016,Quillas laríngeas o accesorios,,0,80509,0 -14893,42272017,Componentes o accesorios de intubación,,0,80509,0 -14894,42272200,Ventiladores y accesorios de presión mecánica positiva,,0,80509,0 -14895,42272201,Máquinas respiradoras de presión positiva intermitente ippb,,0,80509,0 -14896,42272202,Máquinas de presión de aire positivo continuo no invasivo,,0,80509,0 -14897,42272203,Máquinas de doble nivel no invasivas,,0,80509,0 -14898,42272204,Ventiladores de transporte,,0,80509,0 -14899,42272205,Ventiladores para cuidados intensivos de adultos o pediátricos,,0,80509,0 -14900,42272206,Ventiladores para cuidado intensivo de bebés,,0,80509,0 -14901,42272207,Ventiladores de alta frecuencia,,0,80509,0 -14902,42272208,Ventiladores de cuidado en casa,,0,80509,0 -14903,42272209,Circuitos de ventiladores o de respiración,,0,80509,0 -14904,42272210,Bolsas de circuito de respiración,,0,80509,0 -14905,42272211,Productos de hiperventilación,,0,80509,0 -14906,42272212,Válvulas de presión de exhalación y positiva peep,,0,80509,0 -14907,42272213,Máscaras o correas de presión de vías aéreas positivas continuas cpap,,0,80509,0 -14908,42272214,Conectores o adaptadores o válvulas de circuitos,,0,80509,0 -14909,42272215,Suministros de chequeo de ventiladores,,0,80509,0 -14910,42272216,Termómetros de ventiladores,,0,80509,0 -14911,42272217,Trampas de agua para ventiladores,,0,80509,0 -14912,42272218,Puertos o líneas de muestreo de gas para ventiladores,,0,80509,0 -14913,42272219,Intercambiadores o filtros de calor o humedad para ventiladores,,0,80509,0 -14914,42272220,Accesorios para ventiladores,,0,80509,0 -14915,42272221,Productos de humidificación para ventiladores,,0,80509,0 -14916,42272222,Productos de retiro del tubo para ventiladores,,0,80509,0 -14917,42272223,Accesorios o suministros para respiradores,,0,80509,0 -14918,42272224,Kits de circuitos para ventiladores,,0,80509,0 -14919,42272225,Accesorios de presión para vías aéreas positivas de doble nivel bi pap,,0,80509,0 -14920,42272300,Suministros de resucitadores,,0,80509,0 -14921,42272301,Resucitadores manuales,,0,80509,0 -14922,42272302,Resucitadores neumáticos,,0,80509,0 -14923,42272303,Máscaras o accesorios de resucitación,,0,80509,0 -14924,42272304,Componentes o accesorios de resucitación,,0,80509,0 -14925,42272305,Conectores de resucitación,,0,80509,0 -14926,42272306,Kits de resucitación,,0,80509,0 -14927,42272307,Estuches para aparatos o accesorios de resucitación,,0,80509,0 -14928,42272500,"Aparatos de anestesia, y accesorios y suministros",,0,80509,0 -14929,42272501,Aparatos de gas de anestesia,,0,80509,0 -14930,42272502,Unidades de absorción para aparatos de gas de anestesia,,0,80509,0 -14931,42272503,Inhaladores de anestesia o unidades de inhalación o accesorios,,0,80509,0 -14932,42272505,Tubos para aparatos de gas de anestesia o ensamblajes de tubos o ajustes de tubos o accesorios,,0,80509,0 -14933,42272506,Filtros de pantalla para aparatos de anestesia,,0,80509,0 -14934,42272507,Control de temperatura para aparatos de anestesia,,0,80509,0 -14935,42272508,Bomba intratecal,,0,80509,0 -14936,42272509,Calibradores para máquina de anestesia o accesorios,,0,80509,0 -14937,42272510,Kits o accesorios para actualización de máquina de anestesia,,0,80509,0 -14938,42281500,Equipo y accesorios de esterilizadores y de autoclave,,0,80509,0 -14939,42281501,Esterilizadores químicos o de gas,,0,80509,0 -14940,42281502,Esterilizadores de aire seco o de aire caliente,,0,80509,0 -14941,42281503,Esterilizadores de filtro,,0,80509,0 -14942,42281504,Esterilizadores de bolas de vidrio,,0,80509,0 -14943,42281505,Manijas de elevación para contenedores o bandejas de esterilización,,0,80509,0 -14944,42281506,Dispositivos o accesorios eléctricos para limpieza de instrumentos,,0,80509,0 -14945,42281507,Esterilizadores de radiación,,0,80509,0 -14946,42281508,Autoclaves o esterilizadores de vapor,,0,80509,0 -14947,42281509,Contenedores o bandejas de esterilización,,0,80509,0 -14948,42281510,Ganchos o estantes o sujetadores para instrumentos de esterilización,,0,80509,0 -14949,42281511,Lámparas de esterilización,,0,80509,0 -14950,42281512,Tapas de esterilización,,0,80509,0 -14951,42281513,Placas de identificación de esterilización,,0,80509,0 -14952,42281514,Calentadores sanitarios o accesorios,,0,80509,0 -14953,42281515,Gabinetes de esterilización,,0,80509,0 -14954,42281516,Filtros de esterilización,,0,80509,0 -14955,42281517,Sistemas de recuperación de agua de esterilización,,0,80509,0 -14956,42281518,Barras para cánulas de esterilización,,0,80509,0 -14957,42281519,Esterilizadores de agujas,,0,80509,0 -14958,42281521,Sets de esterilización,,0,80509,0 -14959,42281522,Instrumentos de esterilización o insertos de estuches de esterilización,,0,80509,0 -14960,42281523,Mangas de filtro de esterilización,,0,80509,0 -14961,42281524,Adaptadores o ensamblajes de adaptadores de esterilización,,0,80509,0 -14962,42281525,Aireador de gas o químico o de radiación de esterilización,,0,80509,0 -14963,42281526,Prueba de indicador biológico para incubadoras,,0,80509,0 -14964,42281527,Concentrado o casete o cartucho de esterilizante de esterilización,,0,80509,0 -14965,42281528,Accesorios para esterilizador químico o de gas,,0,80509,0 -14966,42281529,Accesorios para esterilizador de aire seco o de aire caliente,,0,80509,0 -14967,42281530,Accesorios para esterilizador o autoclave de vapor,,0,80509,0 -14968,42281531,Esterilizador ultravioleta,,0,80509,0 -14969,42281532,Esterilizador de plasma de gas,,0,80509,0 -14970,42281600,Soluciones de desinfectantes y esterilización en frío,,0,80509,0 -14971,42281603,Desinfectante o esterilizador de instrumentos,,0,80509,0 -14972,42281604,Desinfectantes de superficies para uso médico,,0,80509,0 -14973,42281605,Compuestos anti fijación para uso médico,,0,80509,0 -14974,42281606,Fumigadores de gas para uso médico,,0,80509,0 -14975,42281700,Soluciones y equipo de limpieza pre- esterilización,,0,80509,0 -14976,42281701,Limpiadores de cámara para autoclaves o esterilizadores,,0,80509,0 -14977,42281702,Baldes de remojo desinfectantes,Cubetas de remojo desinfectantes,0,80509,0 -14978,42281703,Kits de cuidado de instrumentos,,0,80509,0 -14979,42281704,Limpiadores o detergentes para instrumentos,,0,80509,0 -14980,42281705,Equipo de lavado desinfectante para equipos e instrumentos de uso médico,,0,80509,0 -14981,42281706,Lubricantes o leche para instrumentos,,0,80509,0 -14982,42281707,Almohadillas para remover manchas de los instrumentos,,0,80509,0 -14983,42281708,Limpiadores de carros de esterilización,,0,80509,0 -14984,42281709,Cepillos de limpieza de esterilización,,0,80509,0 -14985,42281710,Desodorantes de esterilización,,0,80509,0 -14986,42281711,Desincrustadores de esterilización,,0,80509,0 -14987,42281712,Equipo de limpieza ultrasónica,,0,80509,0 -14988,42281713,Baldes de escurrido de esterilización,Cubetas de escurrido para esterilización,0,80509,0 -14989,42281800,Controles y indicadores de esterilización,,0,80509,0 -14990,42281801,Tiras de prueba de desinfección,,0,80509,0 -14991,42281802,Etiquetas de esterilización,,0,80509,0 -14992,42281803,Kits biológicos de esterilización,,0,80509,0 -14993,42281804,Controles de esterilización,,0,80509,0 -14994,42281805,Registros indicadores de esterilización,,0,80509,0 -14995,42281806,Tiras indicadoras de esterilización,,0,80509,0 -14996,42281807,Cintas indicadoras de esterilización,,0,80509,0 -14997,42281808,Papeles u hojas de esterilización,,0,80509,0 -14998,42281809,Sobres de almacenamiento de registros de esterilización,,0,80509,0 -14999,42281810,Paquetes de prueba y accesorios de esterilización,,0,80509,0 -15000,42281811,Candado para contenedor de esterilización,,0,80509,0 -15001,42281900,Suministros de empaquetar y envoltura de esterilización,,0,80509,0 -15002,42281901,Sujetadores o carritos para envoltorios o sobres de esterilización,,0,80509,0 -15003,42281902,Fundas o sobre fundas de esterilización,,0,80509,0 -15004,42281903,Cubiertas contra el polvo de esterilización,,0,80509,0 -15005,42281904,Bolsas de esterilización,,0,80509,0 -15006,42281905,Selladores en caliente de esterilización,,0,80509,0 -15007,42281906,Bandas de instrumentos de esterilización,,0,80509,0 -15008,42281907,Protectores de instrumentos de esterilización,,0,80509,0 -15009,42281908,Recubrimientos de bandejas de instrumentos de esterilización,,0,80509,0 -15010,42281909,Pistolas o cintas o esferos etiquetadores de esterilización,,0,80509,0 -15011,42281912,Toallas de esterilización,,0,80509,0 -15012,42281913,Tunos de esterilización,,0,80509,0 -15013,42281914,Contenedores desechables de esterilización,,0,80509,0 -15014,42281915,Rollos de esterilización,,0,80509,0 -15015,42281916,Talegos de esterilización,,0,80509,0 -15016,42291500,Instrumentos quirúrgicos y productos relacionados para biopsia de los huesos,,0,80509,0 -15017,42291501,Molinos para biopsia de hueso para uso quirúrgico o productos relacionados,,0,80509,0 -15018,42291502,Trepanadores para biopsia de hueso para uso quirúrgico,,0,80509,0 -15019,42291600,Instrumentos quirúrgicos de cortar y lazos y productos relacionados,,0,80509,0 -15020,42291601,Escalpelos o cuchillos o manijas de cuchillos láser para uso quirúrgico,,0,80509,0 -15021,42291602,Tornillos o cables o alfileres o instrumentos para cortar alambre para uso quirúrgico,,0,80509,0 -15022,42291603,Fórceps para cortar hueso para uso quirúrgico,,0,80509,0 -15023,42291604,Sierras de mano o sierras de alambre o manijas para sierras para huesos para uso quirúrgico,,0,80509,0 -15024,42291605,Brocas para uso quirúrgico,,0,80509,0 -15025,42291606,Cinceles o perforadoras para uso quirúrgico,,0,80509,0 -15026,42291607,Curetas o lanzaderas para uso quirúrgico,,0,80509,0 -15027,42291608,Bloques o tablas o plataformas de cortado para uso quirúrgico,,0,80509,0 -15028,42291609,Pinzas para uso quirúrgico,,0,80509,0 -15029,42291610,Planos para uso quirúrgico,,0,80509,0 -15030,42291611,Raspadores para uso quirúrgico,,0,80509,0 -15031,42291612,Tenazas para uso quirúrgico,,0,80509,0 -15032,42291613,Escalpelos o cuchillos o cuchillas o trepanadores o accesorios para uso quirúrgico,,0,80509,0 -15033,42291614,Tijeras para uso quirúrgico,,0,80509,0 -15034,42291615,Tijeras para uso quirúrgico,,0,80509,0 -15035,42291616,Pinzas o cables de pinzas para uso quirúrgico,,0,80509,0 -15036,42291617,Espitas para uso quirúrgico,,0,80509,0 -15037,42291619,Tomos (pinzas de resección) para uso quirúrgico,,0,80509,0 -15038,42291620,Trocadores quirúrgicos para uso general o accesorios,,0,80509,0 -15039,42291621,Removedores de anillos de los dedos,,0,80509,0 -15040,42291622,Adenotomos,,0,80509,0 -15041,42291623,Periosteotomos,,0,80509,0 -15042,42291624,Meniscotomos,,0,80509,0 -15043,42291625,Instrumentos de incisión del talón de los bebés,,0,80509,0 -15044,42291627,Taladros quirúrgicos o sus accesorios,,0,80509,0 -15045,42291700,Taladros quirúrgicos de mano y escariadores y instrumentos de punción y accesorios y productos relacionados,,0,80509,0 -15046,42291701,Brocas de mano o de torsión o kits de brocas o accesorios para uso quirúrgico,,0,80509,0 -15047,42291702,Escariadores o punzones de mano para uso quirúrgico,,0,80509,0 -15048,42291703,Perforadoras para uso quirúrgico,,0,80509,0 -15049,42291704,Punzones o sujeta punzones o accesorios para uso quirúrgico,,0,80509,0 -15050,42291705,Adaptadores de escariadores para uso quirúrgico,,0,80509,0 -15051,42291707,Kits de craneotomía,,0,80509,0 -15052,42291708,Brocas o accesorios para uso quirúrgico,,0,80509,0 -15053,42291709,Cuchillas de sierra o accesorios para uso quirúrgico,,0,80509,0 -15054,42291710,Gancho amniótico,,0,80509,0 -15055,42291800,Abrazaderas quirúrgicas y fórceps y ligadores quirúrgicos y instrumentos relacionados,,0,80509,0 -15056,42291801,Aplicadores de ligantes de bandas o bandas o productos relacionados para uso quirúrgico,,0,80509,0 -15057,42291802,Pinzas o ganchos o fórceps o accesorios para uso quirúrgico,,0,80509,0 -15058,42291803,Pinzas o fórceps láser para uso quirúrgico,,0,80509,0 -15059,42291804,Pinzas o campana de circuncisión,,0,80509,0 -15060,42291805,Pinza de cordón umbilical de recién nacidos,,0,80509,0 -15061,42292100,Insertadores y extractores quirúrgicos y productos relacionados,,0,80509,0 -15062,42292101,Insertores o kits de insertores para uso quirúrgico,,0,80509,0 -15063,42292102,Extractores para uso quirúrgico,,0,80509,0 -15064,42292103,Porta tubos o llaves para uso quirúrgico,,0,80509,0 -15065,42292300,Hierros de flexión y herramientas de plegar y tenazas y tensionadores y torceduras quirúrgicos y productos relacionados,,0,80509,0 -15066,42292301,Instrumentos para doblar para uso quirúrgico,,0,80509,0 -15067,42292302,Engarzadores para uso quirúrgico,,0,80509,0 -15068,42292303,Alicates para uso quirúrgico,,0,80509,0 -15069,42292304,Tensores para uso quirúrgico,,0,80509,0 -15070,42292305,Agarres para uso quirúrgico,,0,80509,0 -15071,42292306,Fórceps o tenazas de alambre para uso quirúrgico,,0,80509,0 -15072,42292307,Llaves para uso quirúrgico,,0,80509,0 -15073,42292600,Dilatadores y sondas y ranuras quirúrgicas y productos relacionados,,0,80509,0 -15074,42292601,Dilatadores o accesorios para uso quirúrgico,,0,80509,0 -15075,42292602,Muescas para uso quirúrgico,,0,80509,0 -15076,42292603,Sondas o directores para uso quirúrgico,,0,80509,0 -15077,42292700,Disectores y Elevadores y Piquetas quirúrgicos y productos relacionados,,0,80509,0 -15078,42292701,Disectores para uso quirúrgico,,0,80509,0 -15079,42292702,Levantadores o niveladores para uso quirúrgico,,0,80509,0 -15080,42292703,Elevadores para uso quirúrgico,,0,80509,0 -15081,42292704,Picas para uso quirúrgico,,0,80509,0 -15082,42292800,Instrumentos quirúrgicos de marcar,,0,80509,0 -15083,42292801,Instrumentos de marcación oftálmica,,0,80509,0 -15084,42292802,Instrumentos de marcación quirúrgica para uso general,,0,80509,0 -15085,42292803,Dispositivo de sellamiento y marcado de biopsias,,0,80509,0 -15086,42292900,Instrumentos quirúrgicos de cerrar la sutura de tejido y productos relacionados,,0,80509,0 -15087,42292901,Instrumentos de cerclaje para uso quirúrgico,,0,80509,0 -15088,42292902,Sujetadores de aguja láser para uso quirúrgico,,0,80509,0 -15089,42292903,Sujetadores de agujas quirúrgicas para uso general,,0,80509,0 -15090,42292904,Suturas quirúrgicas o pasadores de alambre o productos relacionados,,0,80509,0 -15091,42292907,Sistemas para estirar la piel,,0,80509,0 -15092,42292908,Dispositivos de costura de bolsa para uso quirúrgico,,0,80509,0 -15093,42293100,Retractores quirúrgicos y productos relacionados,,0,80509,0 -15094,42293101,Retractores láser para uso quirúrgico,,0,80509,0 -15095,42293102,Ganchos de retracción para uso quirúrgico,,0,80509,0 -15096,42293103,Retractores de fibra óptica iluminados para uso quirúrgico,,0,80509,0 -15097,42293104,Mordazas o accesorios para uso quirúrgico,,0,80509,0 -15098,42293105,Retractores de inclinación para uso quirúrgico,,0,80509,0 -15099,42293106,Sets de retractores para uso quirúrgico,,0,80509,0 -15100,42293107,Retractores quirúrgicos para uso general,,0,80509,0 -15101,42293108,Estabilizadores para uso quirúrgico,,0,80509,0 -15102,42293109,Protectores de tejidos para uso quirúrgico,,0,80509,0 -15103,42293110,Retractores ortopédicos,,0,80509,0 -15104,42293111,Retractores oftálmicos,,0,80509,0 -15105,42293112,Retractores torácicos o cardiovasculares,,0,80509,0 -15106,42293113,Retractores de vena,,0,80509,0 -15107,42293114,Retractores bucales,,0,80509,0 -15108,42293115,Retractores traqueales,,0,80509,0 -15109,42293116,Retractores rectales,,0,80509,0 -15110,42293117,Retractores gastrointestinales,,0,80509,0 -15111,42293118,Retractores uterinos,,0,80509,0 -15112,42293119,Retractores abdominales,,0,80509,0 -15113,42293120,Retractores de columna o nervios,,0,80509,0 -15114,42293121,Retractores de glándulas,,0,80509,0 -15115,42293122,Retractores de oído,,0,80509,0 -15116,42293123,Retractores para cirugía plástica,,0,80509,0 -15117,42293124,Retractores de nervios,,0,80509,0 -15118,42293125,Retractores de esternón,,0,80509,0 -15119,42293126,Retractores para amputación,,0,80509,0 -15120,42293127,Retractores de tejidos,,0,80509,0 -15121,42293128,Retractores de piel,,0,80509,0 -15122,42293129,Retractores para micro cirugía,,0,80509,0 -15123,42293130,Retractores de pulmón,,0,80509,0 -15124,42293131,Retractores de párpado,,0,80509,0 -15125,42293132,Retractores de dedos,,0,80509,0 -15126,42293133,Anillos de retracción para uso quirúrgico,,0,80509,0 -15127,42293134,Retractores cervicales,,0,80509,0 -15128,42293135,Retractores de labios,,0,80509,0 -15129,42293136,Adaptadores de retractores,,0,80509,0 -15130,42293137,Cuchillas retractoras ortopédicas,,0,80509,0 -15131,42293138,Retractores para urología o sus accesorios para uso quirúrgico,,0,80509,0 -15132,42293139,Accesorios para retractores,,0,80509,0 -15133,42293300,Distractores y esparcidores y separadores quirúrgicos y productos relacionados,,0,80509,0 -15134,42293301,Distractores o accesorios para uso quirúrgico,,0,80509,0 -15135,42293302,Separadores para uso quirúrgico,,0,80509,0 -15136,42293303,Espéculos para uso quirúrgico,,0,80509,0 -15137,42293304,Extensores para uso quirúrgico,,0,80509,0 -15138,42293500,Cánulas y puntas y estiletes de irrigación y succión quirúrgica y productos relacionados,,0,80509,0 -15139,42293501,Piezas de mano de irrigación o succión o cánulas o puntas o productos relacionados para uso quirúrgico,,0,80509,0 -15140,42293502,Cánulas o puntas de succión o irrigación láser o productos relacionados para uso quirúrgico,,0,80509,0 -15141,42293503,Dispositivos o curetas de extracción al vacío o productos relacionados para uso quirúrgico,,0,80509,0 -15142,42293504,Suministros o accesorios de irrigación o aspiración oftálmica,,0,80509,0 -15143,42293505,Sondas de drenaje de succión para uso quirúrgico,,0,80509,0 -15144,42293506,Bulbos de succión para uso quirúrgico,,0,80509,0 -15145,42293507,Reservorios de succión para uso quirúrgico,,0,80509,0 -15146,42293508,Suministros o accesorios de irrigación o aspiración de oído nariz y garganta ent,,0,80509,0 -15147,42293509,Sets o accesorios de irrigación para uso quirúrgico,,0,80509,0 -15148,42293600,Bougies quirúrgicos y sondas y obturadores y productos relacionados,,0,80509,0 -15149,42293601,Bujías para uso quirúrgico,,0,80509,0 -15150,42293602,Obturadores para uso quirúrgico,,0,80509,0 -15151,42293603,Sondas para uso quirúrgico,,0,80509,0 -15152,42294000,Espátulas y cucharas y cucharones quirúrgicos y productos relacionados,,0,80509,0 -15153,42294001,Cucharas hondas para uso quirúrgico,,0,80509,0 -15154,42294002,Espátulas para uso quirúrgico,,0,80509,0 -15155,42294003,Cucharas para uso quirúrgico,,0,80509,0 -15156,42294200,Juegos de instrumentos y sistemas y bandejas quirúrgicos,,0,80509,0 -15157,42294201,Sets de instrumentos quirúrgicos torácicos o cardiovasculares,,0,80509,0 -15158,42294202,Sets o sistemas de instrumentos de fijación externa,,0,80509,0 -15159,42294203,Sets de instrumentos quirúrgicos generales,,0,80509,0 -15160,42294204,"Sets de instrumentos para micro cirugía, cirugía plástica o cirugía delicada",,0,80509,0 -15161,42294205,Sets de instrumentos neuroquirúrgicos o espinales,,0,80509,0 -15162,42294206,Sets de instrumentos para cirugía oftálmica,,0,80509,0 -15163,42294207,Sets de instrumentos para cirugía maxilofacial,,0,80509,0 -15164,42294208,Sistemas de instrumentos para revisión ortopédica o completa de articulaciones,,0,80509,0 -15165,42294209,Sistemas de instrumentos para fijación de trauma ortopédico,,0,80509,0 -15166,42294210,Sets de instrumentos para cirugía otolaringológica,,0,80509,0 -15167,42294211,Bandejas de procedimientos o instrumentos especiales o a la medida para uso quirúrgico,,0,80509,0 -15168,42294212,Sets de instrumentos para cirugía urológica,,0,80509,0 -15169,42294213,Sets de instrumentos para cirugía por laparoscopia,,0,80509,0 -15170,42294214,Sets de instrumentos para cirugía de traqueotomía,,0,80509,0 -15171,42294215,Sets de instrumentos para cirugía de craneotomía,,0,80509,0 -15172,42294216,Sets de instrumentos para cirugía de angiografía,,0,80509,0 -15173,42294217,Sets de instrumentos para cirugía gastroscópica,,0,80509,0 -15174,42294218,Sets de instrumentos para cirugía de oído nariz y garganta ent,,0,80509,0 -15175,42294219,Sets de instrumentos para cirugía ortopédica,,0,80509,0 -15176,42294220,Sistemas de recuperación y entrega de sangre,,0,80509,0 -15177,42294500,Instrumentos de especialidad oftálmica y productos relacionados,,0,80509,0 -15178,42294501,Conformadores o protectores para cirugía oftálmica,,0,80509,0 -15179,42294502,Pesas de párpados para cirugía oftálmica,,0,80509,0 -15180,42294503,Anillos de fijación para cirugía oftálmica,,0,80509,0 -15181,42294504,Instrumentos de membrana intraocular para cirugía oftálmica,,0,80509,0 -15182,42294505,Placas de párpado para cirugía oftálmica,,0,80509,0 -15183,42294506,Rotadores de núcleo para cirugía oftálmica,,0,80509,0 -15184,42294507,Fresas o manijas o removedores de anillos de óxido para uso oftálmico,,0,80509,0 -15185,42294508,Puntas de aguja de irrigación o aspiración para uso oftálmico,,0,80509,0 -15186,42294509,Agujas para cirugía oftálmica,,0,80509,0 -15187,42294510,Esponjas para cirugía oftálmica,,0,80509,0 -15188,42294511,Cuchillos o cuchillas o tijeras o accesorios para cirugía oftálmica,,0,80509,0 -15189,42294512,Protectores de ojos o sus accesorios,,0,80509,0 -15190,42294513,Kits de vitrectomía oftálmica,,0,80509,0 -15191,42294514,Sondas de borrado hemostático,,0,80509,0 -15192,42294515,Pulidores de lentes oftálmicos,,0,80509,0 -15193,42294516,Sujetadores de ojos o sus accesorios,,0,80509,0 -15194,42294517,Insertos ópticos,,0,80509,0 -15195,42294518,Sets de ajuste de insertos ópticos,,0,80509,0 -15196,42294519,Cucharas o curetas oftálmicas,,0,80509,0 -15197,42294520,Sujetadores de lentes o accesorios para uso oftálmico,,0,80509,0 -15198,42294521,Componentes de combado escleral,,0,80509,0 -15199,42294522,Sets de punctum de tapón para uso oftálmico,,0,80509,0 -15200,42294523,Suministros para cirugía plástica oftálmica o sus productos relacionados,,0,80509,0 -15201,42294524,Magnetos de ojos para cirugía oftálmica,,0,80509,0 -15202,42294525,Sets de instrumentos médicos oftálmicos,,0,80509,0 -15203,42294526,Dilatadores o sets de lagrimales,,0,80509,0 -15204,42294527,Gas intraocular,,0,80509,0 -15205,42294528,Agente visco elástico o dispositivo visco quirúrgico,,0,80509,0 -15206,42294529,Cistotomo,,0,80509,0 -15207,42294530,Lente de vitrectomía,,0,80509,0 -15208,42294531,Lente láser para uso oftálmico,,0,80509,0 -15209,42294700,Equipo de perfusión de corazón abierto y monitores y accesorios y productos relacionados,,0,80509,0 -15210,42294701,Máquinas o accesorios de corazón y pulmones,,0,80509,0 -15211,42294702,Bombas y accesorios de balón intra aórtico,,0,80509,0 -15212,42294703,Dispositivos o accesorios de succión intra cardíaca,,0,80509,0 -15213,42294704,Filtros de perfusión o productos relacionados,,0,80509,0 -15214,42294705,Monitores de parámetros de sangre de perfusión o accesorios o productos relacionados,,0,80509,0 -15215,42294706,Trampas de burbujas de perfusión,,0,80509,0 -15216,42294707,Sets de cardioplegia de perfusión,,0,80509,0 -15217,42294708,Reservorios de cardiotomía de perfusión,,0,80509,0 -15218,42294709,Sistemas centrífugos de perfusión o accesorios,,0,80509,0 -15219,42294710,Equipos o accesorios calentadores o enfriadores o duales calentadores o enfriadores de perfusión,,0,80509,0 -15220,42294712,Monitores de saturación de oxígeno o hematocritos de perfusión o accesorios,,0,80509,0 -15221,42294713,Oxigenadores de perfusión o accesorios,,0,80509,0 -15222,42294714,Cabezas de bombas de perfusión,,0,80509,0 -15223,42294715,Tubos de empaque de bombas de perfusión,,0,80509,0 -15224,42294716,Reservorios venosos de perfusión,,0,80509,0 -15225,42294717,Dispositivos de asistencia ventricular,,0,80509,0 -15226,42294718,Bombas de perfusión,,0,80509,0 -15227,42294719,Reservorios cardiovasculares,,0,80509,0 -15228,42294720,Agujas de monitoreo de temperatura,,0,80509,0 -15229,42294721,Punzones aórticos,,0,80509,0 -15230,42294722,Sets de drenaje ventricular,,0,80509,0 -15231,42294723,Kits de inserción de bomba de inflar intra aórtica,,0,80509,0 -15232,42294724,Introductor de bomba de inflar intra aórtica,,0,80509,0 -15233,42294800,Endoscopios y accesorios y productos relacionados,,0,80509,0 -15234,42294801,Endoscopios rígidos o accesorios o productos relacionados,,0,80509,0 -15235,42294802,Endoscopios flexibles o accesorios o productos relacionados,,0,80509,0 -15236,42294803,Cistouretroscopios,,0,80509,0 -15237,42294804,Resectoscopios,,0,80509,0 -15238,42294805,Laparascopios o telescopios laparoscópicos,,0,80509,0 -15239,42294806,Cistoscopios,,0,80509,0 -15240,42294807,Esfinterómetros endoscópicos,,0,80509,0 -15241,42294808,Esofagoscopios o sus accesorios,,0,80509,0 -15242,42294900,Instrumentos endoscópicos y suministros accesorios y productos relacionados,,0,80509,0 -15243,42294901,Posicionadores o sujetadores de endoscopios o instrumentos,,0,80509,0 -15244,42294902,Aplicadores o elevadores de endoscopios,,0,80509,0 -15245,42294903,Agujas de aspiración o biopsia endoscópica,,0,80509,0 -15246,42294904,Bloques de mordida o correas endoscópicas,,0,80509,0 -15247,42294905,Cepillos de limpieza endoscópica o productos relacionados,,0,80509,0 -15248,42294906,Instrumentos de cortar para uso endoscópico,,0,80509,0 -15249,42294907,Cepillos de citología endoscópica o microbiología,,0,80509,0 -15250,42294908,Pinzas o disectores o agarraderas o fórceps o ligantes endoscópicos,,0,80509,0 -15251,42294909,Dilatadores o dispositivos inflables endoscópicos o productos relacionados,,0,80509,0 -15252,42294910,Electrodos o cables endoscópicos,,0,80509,0 -15253,42294911,Kits de fijación endoscópica,,0,80509,0 -15254,42294912,Sistemas o accesorios de manejo de fluido endoscópico,,0,80509,0 -15255,42294913,Balones hemostáticos o agujas o tubos o accesorios endoscópicos,,0,80509,0 -15256,42294914,Sets de instrumentos endoscópicos,,0,80509,0 -15257,42294915,Desplegadores de instrumentos endoscópicos,,0,80509,0 -15258,42294916,Filtros de insuflación endoscópica,,0,80509,0 -15259,42294917,Agujas de insuflación endoscópica,,0,80509,0 -15260,42294918,Tubos de insuflación endoscópica,,0,80509,0 -15261,42294919,Introductores o alambres guía o alambres de deslizamiento endoscópicos,,0,80509,0 -15262,42294920,Instrumentos o accesorios láser endoscópicos,,0,80509,0 -15263,42294921,Sistemas de empujadores de nudos o de entrega endoscópicos,,0,80509,0 -15264,42294922,Ligantes endoscópicos,,0,80509,0 -15265,42294923,Manipuladores endoscópicos,,0,80509,0 -15266,42294924,Instrumentos o accesorios manuales monopolares o bipolares para endoscopia o productos relacionados,,0,80509,0 -15267,42294925,Agujas o punzones para endoscopia,,0,80509,0 -15268,42294926,Sobre tubos para endoscopia,,0,80509,0 -15269,42294927,Paquetes o bandejas o kits de instrumentos para endoscopia,,0,80509,0 -15270,42294928,Sondas para endoscopia,,0,80509,0 -15271,42294929,Retractores para endoscopia,,0,80509,0 -15272,42294930,Pinzas o alambres de pinzas o accesorios para endoscopia,,0,80509,0 -15273,42294931,Fórceps o dispositivos de recuperación de especímenes para endoscopia,,0,80509,0 -15274,42294933,Puntas de irrigación o succión o sondas de coagulación o accesorios para endoscopia,,0,80509,0 -15275,42294934,Dispositivos de sutura para endoscopia,,0,80509,0 -15276,42294935,Trocar o funda u obturador o cánula para endoscopia,,0,80509,0 -15277,42294936,Elementos de trabajo o canales de trabajo para endoscopia,,0,80509,0 -15278,42294937,Dispositivos reductores de empañe para endoscopia o espejos,,0,80509,0 -15279,42294938,Tapas sellantes para endoscopia,,0,80509,0 -15280,42294939,Válvulas o accesorios para endoscopia,,0,80509,0 -15281,42294940,Convertidores para endoscopia,,0,80509,0 -15282,42294941,Sets de drenaje biliar para endoscopia,,0,80509,0 -15283,42294942,Sellos de instrumentos para endoscopia,,0,80509,0 -15284,42294943,Unidades de válvulas para endoscopia,,0,80509,0 -15285,42294944,Kits de accesorios para endoscopia,,0,80509,0 -15286,42294945,Esponjas para endoscopia,,0,80509,0 -15287,42294946,Mordazas para endoscopia,,0,80509,0 -15288,42294947,Diafragmas para endoscopia,,0,80509,0 -15289,42294948,Piezas bucales para endoscopia,,0,80509,0 -15290,42294949,Manijas de guía de alambre para endoscopia,,0,80509,0 -15291,42294950,Brocas o taladros para endoscopia,,0,80509,0 -15292,42294951,Sets de instrumentos para uniones pequeñas para endoscopia,,0,80509,0 -15293,42294952,Recuperadores o sets para endoscopia,,0,80509,0 -15294,42294953,Extractores para endoscopia,,0,80509,0 -15295,42294954,Dispositivos o accesorios para remover especímenes o tejidos para endoscopia,,0,80509,0 -15296,42294955,Ganchos o accesorios para endoscopia,,0,80509,0 -15297,42294956,Localizadores de guía de alambre para endoscopia,,0,80509,0 -15298,42294957,Cuchillas de afeitar o accesorios para endoscopia,,0,80509,0 -15299,42294958,Aditamentos y accesorios de cortar y sellar vasos para endoscopia,,0,80509,0 -15300,42295000,Equipo endoscópico y accesorios y productos relacionados,,0,80509,0 -15301,42295001,Unidades o accesorios de mantenimiento para endoscopia,,0,80509,0 -15302,42295002,Gabinetes de almacenamiento o accesorios para endoscopia,,0,80509,0 -15303,42295003,Colgadores de pared o accesorios para endoscopia,,0,80509,0 -15304,42295004,Carritos de equipos o procedimientos o accesorios para endoscopia,,0,80509,0 -15305,42295005,Sets de equipos endoscópicos,,0,80509,0 -15306,42295006,Unidades de sonda de calor o sondas de calor o accesorios para endoscopia,,0,80509,0 -15307,42295007,Sistemas de imágenes o accesorios para endoscopia,,0,80509,0 -15308,42295008,Unidades o accesorios de insuflación o distensión para endoscopia,,0,80509,0 -15309,42295009,Fuentes de luz quirúrgica o accesorios para endoscopia,,0,80509,0 -15310,42295010,Impresoras o accesorios para endoscopia,,0,80509,0 -15311,42295011,Video cámaras o grabadoras o adaptadores o accesorios para endoscopia,,0,80509,0 -15312,42295012,Botellas de agua o accesorios para endoscopia,,0,80509,0 -15313,42295013,Protectores o cubiertas de puntas de endoscopios,,0,80509,0 -15314,42295014,Estuches de instrumentos para endoscopia,,0,80509,0 -15315,42295015,Lentes endoscópicos,,0,80509,0 -15316,42295016,Video cable quirúrgico o endoscópico,,0,80509,0 -15317,42295100,Equipo quirúrgico y accesorios y productos relacionados,,0,80509,0 -15318,42295101,Puestos de lavamanos para uso quirúrgico,,0,80509,0 -15319,42295102,Equipos o accesorios para criocirugía,,0,80509,0 -15320,42295103,Mesas o accesorios para procedimientos de cesáreas o salas de partos o productos relacionados,,0,80509,0 -15321,42295104,Equipo electro quirúrgico o electro cauterizante,,0,80509,0 -15322,42295105,Mesas de instrumentos para uso quirúrgico u obstétrico o accesorios o productos relacionados,,0,80509,0 -15323,42295106,Bandejas mayo para el canto o puestos mayo para uso quirúrgico o sus accesorios,,0,80509,0 -15324,42295107,Carritos para estuches de salas de cirugía o carritos de procedimiento o gabinetes de pared o accesorios,,0,80509,0 -15325,42295108,Mesas de fractura de pacientes para salas de cirugía o mesas ortopédicas o accesorios o productos relacionados,,0,80509,0 -15326,42295109,Baldes para salas de cirugía o accesorios o productos relacionados,,0,80509,0 -15327,42295111,Dispositivos de posicionamiento de los pacientes para salas de cirugía o accesorios,,0,80509,0 -15328,42295112,Mesas de procedimientos para salas de cirugía o accesorios o productos relacionados,,0,80509,0 -15329,42295114,Equipos de facoemulsificación o extrusión o accesorios para cirugía oftálmica,,0,80509,0 -15330,42295115,Taburetes de cirujanos o accesorios,,0,80509,0 -15331,42295116,Escalones para salas de cirugía o accesorios,,0,80509,0 -15332,42295118,Equipos de bombas de irrigación para uso quirúrgico o lavado pulsado o accesorios con o sin succión,,0,80509,0 -15333,42295119,Láseres para uso quirúrgico o accesorios,,0,80509,0 -15334,42295120,Litotriptores o accesorios para uso quirúrgico,,0,80509,0 -15335,42295121,Microscopios o lupas o magnificadores o accesorios para uso quirúrgico,,0,80509,0 -15336,42295122,Torniquetes neumáticos o eléctricos o accesorios para uso quirúrgico,,0,80509,0 -15337,42295123,Máquinas de succión o extractores al vacío o aspiradores quirúrgicos ultrasónicos o reguladores o accesorios para uso quirúrgico,,0,80509,0 -15338,42295124,Evacuadores de humo o accesorios para uso quirúrgico,,0,80509,0 -15339,42295125,Mesas urológicas o accesorios para uso quirúrgico,,0,80509,0 -15340,42295126,Equipos de cirugía fragmatoma de retina vítrea para cirugía oftálmica,,0,80509,0 -15341,42295127,Equipos o accesorios de microcirugía,,0,80509,0 -15342,42295128,Instrumentos quirúrgicos maxilofaciales o accesorios,,0,80509,0 -15343,42295129,Dispensadores de medicamentos para salas de cirugía o productos relacionados,,0,80509,0 -15344,42295130,Tubos o accesorios de conexión de instrumentos quirúrgicos,,0,80509,0 -15345,42295131,Puestos o accesorios de equipos quirúrgicos,,0,80509,0 -15346,42295132,Estuches o accesorios para equipos quirúrgicos,,0,80509,0 -15347,42295133,Estantes de tornillos ortopédicos o accesorios,,0,80509,0 -15348,42295134,Sets o accesorios de suministro quirúrgico general,,0,80509,0 -15349,42295135,Dispositivos o accesorios de control ortopédico,,0,80509,0 -15350,42295136,Instrumentos de control para uso quirúrgico,,0,80509,0 -15351,42295137,Equipos o suministros o accesorios para gastroenterología,,0,80509,0 -15352,42295138,Dispositivos o accesorios de dilatación urológica para uso quirúrgico,,0,80509,0 -15353,42295139,Rollos de instrumentos para instrumentos quirúrgicos o accesorios,,0,80509,0 -15354,42295140,Gabinetes o muebles para instrumentos quirúrgicos,,0,80509,0 -15355,42295141,Kit de salvamento de sangre intra operativa,,0,80509,0 -15356,42295142,Máquina de salvamento de sangre intra operativa,,0,80509,0 -15357,42295143,Accesorios o aditamentos electro quirúrgicos o electro cauterizantes,,0,80509,0 -15358,42295144,Unidad de microondas quirúrgico,,0,80509,0 -15359,42295145,Depilador tipo aguja,,0,80509,0 -15360,42295146,Depilador tipo pinza,,0,80509,0 -15361,42295147,Instrumento de ultrasonido para uso quirúrgico,,0,80509,0 -15362,42295148,Sistema de navegación para uso quirúrgico,,0,80509,0 -15363,42295200,Equipo quirúrgico de energía y accesorios y productos relacionados,,0,80509,0 -15364,42295201,Dermotomos o aparatos para dermoabrasión o dermoreticulador para uso quirúrgico o accesorios,,0,80509,0 -15365,42295202,Sierras eléctricas o de pila o neumáticas o colocadores de tornillos o accesorios para uso quirúrgico,,0,80509,0 -15366,42295203,Sets o accesorios de equipos eléctricos para uso quirúrgico,,0,80509,0 -15367,42295204,Escariadores para uso quirúrgico,,0,80509,0 -15368,42295205,Equipos de afeitado o piezas de mano o cuchillas o accesorios para uso quirúrgico,,0,80509,0 -15369,42295206,Unidades de cirugía endoscópica,,0,80509,0 -15370,42295207,Guía o manga para taladro de uso quirúrgico,,0,80509,0 -15371,42295400,Suministros quirúrgicos auxiliares,,0,80509,0 -15372,42295401,Lápices de cauterización operados mediante pilas de uso quirúrgico,,0,80509,0 -15373,42295402,Esferos para marcar de uso quirúrgico,,0,80509,0 -15374,42295405,Introductores o ganchos de guía o guías de alambre o deslizadores de alambre para cirugía no endoscópica o procedimientos de corazón abierto,,0,80509,0 -15375,42295406,Esponja especial para laparotomía o detectable por rayos x o de uso quirúrgico,,0,80509,0 -15376,42295407,Máscaras para pacientes de uso quirúrgico,,0,80509,0 -15377,42295408,Cepillos para manos para cirujanos o soluciones o accesorios,,0,80509,0 -15378,42295409,Aplicadores de uso quirúrgico,,0,80509,0 -15379,42295410,Sets o paquetes de cuencas de uso quirúrgico,,0,80509,0 -15380,42295411,Sujetadores o interruptores de cuchilla de uso quirúrgico,,0,80509,0 -15381,42295413,Cepillos de canal de uso quirúrgico,,0,80509,0 -15382,42295414,Paquetes para procedimientos especiales o a la medida de uso quirúrgico,,0,80509,0 -15383,42295415,Fundas para equipos de uso quirúrgico,,0,80509,0 -15384,42295416,Evacuadores de uso quirúrgico,,0,80509,0 -15385,42295417,Fundas o manijas para luces de uso quirúrgico,,0,80509,0 -15386,42295418,Esterillas magnéticas de uso quirúrgico,,0,80509,0 -15387,42295419,Estimuladores de nervios o accesorios de uso quirúrgico,,0,80509,0 -15388,42295420,Catéteres endoscópicos o quirúrgicos o kits de cateterización o bolsas de drenaje,,0,80509,0 -15389,42295421,Lavado de preparación o soluciones de pintura para uso quirúrgico,,0,80509,0 -15390,42295422,Lavado o kits de preparación del paciente para uso quirúrgico,,0,80509,0 -15391,42295423,Mostradores de instrumentos afilados o esponjas para uso quirúrgico,,0,80509,0 -15392,42295424,Kits de afeitado o cuchillas de preparación o corta uñas para uso quirúrgico,,0,80509,0 -15393,42295425,Palitos o lápices o cristales de nitrato de plata para uso quirúrgico,,0,80509,0 -15394,42295426,Trampas o contenedores de recolección de especímenes para uso quirúrgico,,0,80509,0 -15395,42295427,Cepillos para instrumentos estériles o estiletes instrumentales o pañitos limpiadores de instrumentos para uso quirúrgico,,0,80509,0 -15396,42295428,Tubos o accesorios de irrigación o succión para uso quirúrgico,,0,80509,0 -15397,42295431,Cortinas transparentes de incisión o bolsas para instrumentos para uso quirúrgico,,0,80509,0 -15398,42295433,Catéteres urológicos o accesorios para uso quirúrgico,,0,80509,0 -15399,42295435,Protectores o escudos para oídos,,0,80509,0 -15400,42295436,Anillos de anastomosis,,0,80509,0 -15401,42295437,Sets de utensilios o gabinetes o accesorios para uso quirúrgico,,0,80509,0 -15402,42295439,Alisadores de hueso para uso ortopédico,,0,80509,0 -15403,42295440,Adaptadores de catéteres endoscópicos,,0,80509,0 -15404,42295441,Sets de recuperación para uso quirúrgico,,0,80509,0 -15405,42295445,Insertos de bandejas para aparatos quirúrgicos,,0,80509,0 -15406,42295446,Retenedores de órganos internos,,0,80509,0 -15407,42295448,Guardas de salpicaduras o accesorios para uso quirúrgico,,0,80509,0 -15408,42295450,Cargadores de baldes o accesorios para uso quirúrgico,,0,80509,0 -15409,42295451,Guantes de preparación para uso quirúrgico,,0,80509,0 -15410,42295452,Tazas de preparación para uso quirúrgico,,0,80509,0 -15411,42295453,Drenajes o sets o accesorios para uso quirúrgico,,0,80509,0 -15412,42295454,Protectores de mano para uso quirúrgico,,0,80509,0 -15413,42295455,Kits de resina para reparación craneal,,0,80509,0 -15414,42295456,Fieltros para uso quirúrgico,,0,80509,0 -15415,42295457,Lentes o capuchas de cascos para uso quirúrgico,,0,80509,0 -15416,42295458,Equipos para secar o empolvar guantes quirúrgicos,,0,80509,0 -15417,42295459,Dispositivos para decantar fluidos para uso quirúrgico,,0,80509,0 -15418,42295460,Capas protectoras para implantes ortopédicos,,0,80509,0 -15419,42295461,Pegamento para tejidos o sistemas o aplicadores o accesorios,,0,80509,0 -15420,42295462,Catéteres uro dinámicos o accesorios,,0,80509,0 -15421,42295463,Catéteres uretrales,,0,80509,0 -15422,42295464,Solución para preservación de órganos,,0,80509,0 -15423,42295465,Sistema de monitoreo neurofisiológico,,0,80509,0 -15424,42295466,Cemento para huesos para uso quirúrgico,,0,80509,0 -15425,42295467,Mezcladora de cemento para huesos para uso quirúrgico,,0,80509,0 -15426,42295468,Inyector de cemento para huesos o accesorios para uso quirúrgico,,0,80509,0 -15427,42295469,Kits de cemento para huesos o accesorios para uso quirúrgico,,0,80509,0 -15428,42295470,Sistema de concentración de plaquetas,,0,80509,0 -15429,42295471,Sistema de aspiración de médula ósea,,0,80509,0 -15430,42295500,Implantes quirúrgico y ensanchadores y extensores y alambres quirúrgicos y productos relacionados,,0,80509,0 -15431,42295502,Implantes de tejido humano,,0,80509,0 -15432,42295503,Puertos de infusión implantables o accesorios,,0,80509,0 -15433,42295505,Implantes oftálmicos,,0,80509,0 -15434,42295506,Implantes maxilofaciales bucales o sets,,0,80509,0 -15435,42295508,Implantes para otolaringología o sets,,0,80509,0 -15436,42295509,Implantes plásticos o cosméticos o expansores de tejidos o sets,,0,80509,0 -15437,42295510,Barreras de adhesión para uso quirúrgico,,0,80509,0 -15438,42295511,Estimuladores de hueso para uso quirúrgico,,0,80509,0 -15439,42295512,Derivaciones implantables o extensores de derivaciones para uso quirúrgico,,0,80509,0 -15440,42295513,Productos de malla o de barreras de tejido para uso quirúrgico,,0,80509,0 -15441,42295514,Láminas de silicona,,0,80509,0 -15442,42295515,Implantes de tejidos sintéticos,,0,80509,0 -15443,42295516,Implantes urológicos o sets,,0,80509,0 -15444,42295517,Extensores cardiovasculares,,0,80509,0 -15445,42295518,Protectores de ganchos,,0,80509,0 -15446,42295519,Kit de preparación para prótesis de pene,,0,80509,0 -15447,42295520,Kit de ventriculostomía,,0,80509,0 -15448,42295521,Implante gastrointestinal,,0,80509,0 -15449,42295522,Estimulador de nervios implantable,,0,80509,0 -15450,42295523,Implante o eslinga ginecológica,,0,80509,0 -15451,42295524,Lente intraocular iol,,0,80509,0 -15452,42295525,Implante de tejido biológico,,0,80509,0 -15453,42295526,Sistema de implantes de pelo,,0,80509,0 -15454,42295800,Sistemas y accesorios de termo - ablación quirúrgica,,0,80509,0 -15455,42295801,Casete de ablación térmica para uso quirúrgico,,0,80509,0 -15456,42295802,Set de tubos de ablación térmica para uso quirúrgico,,0,80509,0 -15457,42295903,Stents urológicos,,0,80509,0 -15458,42296000,Implantes cardiovasculares,,0,80509,0 -15459,42296001,Válvula aórtica,,0,80509,0 -15460,42296002,Conducto cardiovascular,,0,80509,0 -15461,42296003,Válvula mitral,,0,80509,0 -15462,42296004,Válvula tricúspide,,0,80509,0 -15463,42296005,Anillo de anuloplastia,,0,80509,0 -15464,42296006,Gancho vascular,,0,80509,0 -15465,42296007,Oclusor cardiovascular,,0,80509,0 -15466,42296008,Válvula pulmonar,,0,80509,0 -15467,42301500,Ayudas para formación médica,,0,80509,0 -15468,42301501,Modelos humanos anatómicos para educación o entrenamiento médico,,0,80509,0 -15469,42301502,Maniquíes humanos anatómicos para educación o entrenamiento médico,,0,80509,0 -15470,42301503,Ayudas de entrenamiento para resucitación cardiopulmonar cpr,,0,80509,0 -15471,42301504,Kits para educación o entrenamiento médico,,0,80509,0 -15472,42301505,Portapapeles para enfermeras o médicos,,0,80509,0 -15473,42301506,Estetoscopios de doble audífono,,0,80509,0 -15474,42301507,Videos de entrenamiento para la educación del personal médico,,0,80509,0 -15475,42301508,Videos de operación o de instrucciones para equipos médicos,,0,80509,0 -15476,42311500,Vendas y vendajes y productos relacionados,,0,80509,0 -15477,42311501,Clips para vendajes o compresas,"Ganchos para vendas elásticas, Material de curación",0,80509,0 -15478,42311502,Kits de inicio de vendajes o compresas,"Juego de vendas, vendajes y gasas, Material de curación",0,80509,0 -15479,42311503,Enrolladores de vendajes,Rodillos para vendas,0,80509,0 -15480,42311504,Vendajes o compresas para el cuidado de quemaduras,,0,80509,0 -15481,42311505,Vendajes o compresas para uso general,"Material de curación, Vendas de uso general",0,80509,0 -15482,42311506,Vendas o compresas de compresión o presión,,0,80509,0 -15483,42311508,Bandejas para compresas,,0,80509,0 -15484,42311510,Vendajes de espuma,,0,80509,0 -15485,42311511,Vendajes de gasa,"Gasas de uso general, Material de curación",0,80509,0 -15486,42311512,Esponjas de gasa,,0,80509,0 -15487,42311513,Compresas de gel,,0,80509,0 -15488,42311514,Vendajes germicidas,,0,80509,0 -15489,42311515,Vendajes hidrocoloides,,0,80509,0 -15490,42311517,Adhesivos líquidos para vendajes o compresas,,0,80509,0 -15491,42311518,Parches o almohadillas para los ojos para uso médico,,0,80509,0 -15492,42311519,Tiras no adhesivas para uso médico,,0,80509,0 -15493,42311520,Cintas no adhesivas para uso médico,,0,80509,0 -15494,42311521,Compresas de presión negativa,Compresas de presión negativa,0,80509,0 -15495,42311522,Compresas oclusivas,,0,80509,0 -15496,42311523,Vendajes de pasta,Vendas de yeso,0,80509,0 -15497,42311524,Compresas de petrolato,,0,80509,0 -15498,42311527,Compresas de película transparente,,0,80509,0 -15499,42311528,Sistemas de compresas húmedas,,0,80509,0 -15500,42311531,Cubiertas para compresas,,0,80509,0 -15501,42311537,Tijeras para vendajes o sus suministros,,0,80509,0 -15502,42311538,Kits de hiperalimentación,,0,80509,0 -15503,42311539,Aplicadores de vendajes,,0,80509,0 -15504,42311540,Compresas de alginato,,0,80509,0 -15505,42311541,Compresas biológicas,,0,80509,0 -15506,42311542,Compresas de control de olor,,0,80509,0 -15507,42311543,Retenedor de vendajes,Retenedor de vendajes,0,80509,0 -15508,42311700,Cintas adhesivas médicas y quirúrgicas y productos relacionados para el uso en especialidades,,0,80509,0 -15509,42311702,Cintas umbilicales para bebés,,0,80509,0 -15510,42311703,Cintas médicas o quirúrgicas para pegar la piel,,0,80509,0 -15511,42311704,Dispensadores de cinta médica o quirúrgica,,0,80509,0 -15512,42311705,Removedores de cinta médica o quirúrgica,,0,80509,0 -15513,42311707,Cintas de tejido para uso quirúrgico,,0,80509,0 -15514,42311708,Cintas adherentes médicas o quirúrgicas para uso general,,0,80509,0 -15515,42312000,Cierre de tejidos médicos y productos relacionados,,0,80509,0 -15516,42312001,Cierres de mariposa para piel,,0,80509,0 -15517,42312002,Clips para cierre de piel,,0,80509,0 -15518,42312003,Tiras de cierre para la piel o para heridas,,0,80509,0 -15519,42312004,Removedores de pegamento o adhesivo para uso médico,,0,80509,0 -15520,42312005,Adhesivos o pegamentos de cierre de piel para uso médico,,0,80509,0 -15521,42312006,Aplicadores de clip para uso interno para uso médico,,0,80509,0 -15522,42312007,Clips de uso interno para uso médico,,0,80509,0 -15523,42312008,Removedores de ganchos o clips para uso médico,,0,80509,0 -15524,42312009,Cosedoras para uso interno,,0,80509,0 -15525,42312010,Cosedoras para cierre de piel,,0,80509,0 -15526,42312011,Ganchos para uso interno,,0,80509,0 -15527,42312012,Ganchos para cierre de piel,,0,80509,0 -15528,42312014,Kits de cierre de tejidos o bandejas o paquetes o sets,,0,80509,0 -15529,42312100,Suministros de ostomía y productos no quirúrgicos de drenaje de heridas,,0,80509,0 -15530,42312101,Adhesivos para aplicación en ostomía,,0,80509,0 -15531,42312102,Aplicaciones para ostomía,,0,80509,0 -15532,42312103,Limpiadores o desodorantes para ostomía,,0,80509,0 -15533,42312104,Suministros de recolección para ostomía,,0,80509,0 -15534,42312105,Kits de barreras de piel o cuidado protector para ostomía,,0,80509,0 -15535,42312106,Insertos para ostomía,,0,80509,0 -15536,42312107,Obleas para ostomía,,0,80509,0 -15537,42312108,Bolsas de drenaje para ostomía,,0,80509,0 -15538,42312109,Anillos de bolsa para ostomía,,0,80509,0 -15539,42312110,Kits de inicio para ostomía,,0,80509,0 -15540,42312111,Fundas de bolsa para ostomía,,0,80509,0 -15541,42312112,Cinturón o accesorios para ostomía,,0,80509,0 -15542,42312113,Tapones de bolsa para ostomía,,0,80509,0 -15543,42312115,Mangas de irrigación para ostomía,,0,80509,0 -15544,42312200,Sutura y productos relacionados,,0,80509,0 -15545,42312201,Suturas,,0,80509,0 -15546,42312202,Kits o bandejas o paquetes o sets para sutura,,0,80509,0 -15547,42312203,Botones o puentes o accesorios relacionados para sutura,,0,80509,0 -15548,42312204,Botas y dispositivos de captura para sutura,,0,80509,0 -15549,42312205,Carritos o estantes para sutura,,0,80509,0 -15550,42312206,Agujas para sutura,,0,80509,0 -15551,42312207,Kits de remoción o bandejas o paquetes o sets para sutura,,0,80509,0 -15552,42312208,Removedores de suturas,,0,80509,0 -15553,42312300,Productos para limpiar la herida y desbridamiento,,0,80509,0 -15554,42312301,Absorbentes para limpieza de heridas,,0,80509,0 -15555,42312302,Esponjas de debridación,,0,80509,0 -15556,42312303,Sistemas de lavado pulsado o accesorios relacionados para el tratamiento de heridas,,0,80509,0 -15557,42312304,Productos de debridación autolítica para uso médico,,0,80509,0 -15558,42312305,Productos de debridación enzimática para uso médico,,0,80509,0 -15559,42312306,Productos de debridación mecánica para uso médico,,0,80509,0 -15560,42312307,Productos de debridación quirúrgica para uso médico,,0,80509,0 -15561,42312309,Sistemas de irrigación de heridas,,0,80509,0 -15562,42312310,Botellas limpiadoras,,0,80509,0 -15563,42312311,Kits de desinfectantes,,0,80509,0 -15564,42312312,Bandejas de cuidado de heridas o de limpieza,,0,80509,0 -15565,42312313,Soluciones de limpieza de heridas,Soluciones antisépticas,0,80509,0 -15566,42312400,Productos para la curación de heridas,,0,80509,0 -15567,42312401,Relleno de alginato de calcio para heridas,,0,80509,0 -15568,42312402,Férulas o stents nasales,,0,80509,0 -15569,42312403,Tiras de relleno para cuidado de heridas,,0,80509,0 -15570,42312500,"Dispositivos de sujeción de heridas, y accesorios y suministros",,0,80509,0 -15571,42312501,Ligaduras de soporte mamario,,0,80509,0 -15572,42312502,Ligaduras abdominales,,0,80509,0 -15573,42312503,Soportes para escroto,,0,80509,0 -15574,42312504,Prenda de soporte facial,,0,80509,0 -15575,42312600,Productos de terapia de presión negativa para heridas,,0,80509,0 -15576,42312601,Equipo de terapia de presión negativa para heridas,,0,80509,0 -15577,42312602,Kits o sistemas de terapia de presión negativa para heridas,,0,80509,0 -15578,42321500,Implantes de trauma ortopédico,,0,80509,0 -15579,42321501,Alambre quirúrgico ortopédico,,0,80509,0 -15580,42321502,Guía de alambre ortopédico o gancho guía o varilla guía,,0,80509,0 -15581,42321503,Extensor de gancho óseo o sustituto,,0,80509,0 -15582,42321504,Clavo intramedular,,0,80509,0 -15583,42321505,Placas óseas,,0,80509,0 -15584,42321506,Destornillador óseo,,0,80509,0 -15585,42321507,Set de fijación interna,,0,80509,0 -15586,42321508,Tuercas ortopédicas,,0,80509,0 -15587,42321509,Lavadora ortopédica,,0,80509,0 -15588,42321510,Tapa ortopédica,,0,80509,0 -15589,42321512,Pernos de fijación ósea,,0,80509,0 -15590,42321513,Perno o alambre de fijación temporal,,0,80509,0 -15591,42321514,Grapa de fijación ósea,,0,80509,0 -15592,42321515,Hoja de sierra intramedular,,0,80509,0 -15593,42321516,Tornillo de cierre intramedular,,0,80509,0 -15594,42321600,Implantes espinales,,0,80509,0 -15595,42321601,Disco intervertebral,,0,80509,0 -15596,42321602,Cable espinal,,0,80509,0 -15597,42321603,Balón de cifoplastia,,0,80509,0 -15598,42321604,Dispositivo de unión cruzada espinal,,0,80509,0 -15599,42321605,Armazón intracorpórea espinal,,0,80509,0 -15600,42321606,Dispositivo de descompresión espinal,,0,80509,0 -15601,42321607,Gancho espinal,,0,80509,0 -15602,42321608,Dispositivo de fusión intracorpórea,,0,80509,0 -15603,42321609,Varilla espinal,,0,80509,0 -15604,42321610,Destornillador espinal,,0,80509,0 -15605,42321611,Placa espinal,,0,80509,0 -15606,42321612,Anillo de manga o tapa de extremo espinal,,0,80509,0 -15607,42321613,Tornillo múltiple o bloqueador espinal,,0,80509,0 -15608,42321614,Perno espinal,,0,80509,0 -15609,42321615,Silla o cuna espinal,,0,80509,0 -15610,42321616,Conector o acoplador espinal,,0,80509,0 -15611,42321617,Grapa espinal,,0,80509,0 -15612,42321618,Lavadora espinal,,0,80509,0 -15613,42321619,Restrictor de cemento espinal,,0,80509,0 -15614,42321620,Perno espinal,,0,80509,0 -15615,42321621,Tornillo o alambre de fijación espinal temporal,,0,80509,0 -15616,42321622,Kit o sistema de implante espinal,,0,80509,0 -15617,42321700,Implantes de cadera,,0,80509,0 -15618,42321701,Tapa acetabular de una pieza,,0,80509,0 -15619,42321702,Concha acetabular,,0,80509,0 -15620,42321703,Revestimiento de taza acetabular,,0,80509,0 -15621,42321704,Cabeza femoral,,0,80509,0 -15622,42321705,Centralizador distal de tallo femoral,,0,80509,0 -15623,42321706,Extensión de vástago de la cadera femoral,,0,80509,0 -15624,42321707,Kit o sistema de reemplazo total de cadera,,0,80509,0 -15625,42321708,Vástago de la cadera femoral,,0,80509,0 -15626,42321709,Prótesis hemiartroplástica de cadera de una pieza,,0,80509,0 -15627,42321710,Adaptador de componente de cadera modular femoral,,0,80509,0 -15628,42321711,Componente femoral resurgente,,0,80509,0 -15629,42321712,Centralizador proximal de tallo femoral,,0,80509,0 -15630,42321713,Tornillo acetabular,,0,80509,0 -15631,42321714,Aumento acetabular,,0,80509,0 -15632,42321715,Componente de cadera bipolar o unipolar,,0,80509,0 -15633,42321716,Restrictor de cemento de canal femoral,,0,80509,0 -15634,42321717,Tornillo componente de cadera,,0,80509,0 -15635,42321718,Armazón acetabular,,0,80509,0 -15636,42321719,Malla acetabular,,0,80509,0 -15637,42321720,Cubierta de orificio de tornillo acetabular,,0,80509,0 -15638,42321721,Eliminador o tapón de orificio acetabular,,0,80509,0 -15639,42321722,Anillo acetabular,,0,80509,0 -15640,42321723,Cuello femoral,,0,80509,0 -15641,42321800,Implantes de rodilla,,0,80509,0 -15642,42321801,Componente de rodilla femoral,,0,80509,0 -15643,42321802,Tallo de extensión de rodilla femoral,,0,80509,0 -15644,42321803,Tallo de extensión de tibia,,0,80509,0 -15645,42321804,Implante de rótula,,0,80509,0 -15646,42321805,Placa de base para la tibia,,0,80509,0 -15647,42321806,Inserto para la tibia,,0,80509,0 -15648,42321808,Kit o sistema de remplazo total de rodilla,,0,80509,0 -15649,42321809,Aumento de tibia o fémur o rótula,,0,80509,0 -15650,42321810,Tornillo de placa de base de la tibia,,0,80509,0 -15651,42321811,Tornillo de conexión de aumento,,0,80509,0 -15652,42321812,Espaciador de articulación de rodilla,,0,80509,0 -15653,42321813,Cuña de rodilla femoral,,0,80509,0 -15654,42321900,Implantes de hombro,,0,80509,0 -15655,42321902,Cabeza de húmero,,0,80509,0 -15656,42321903,Tallo de húmero,,0,80509,0 -15657,42321904,Componente de húmero resurgente,,0,80509,0 -15658,42321905,Esfera glenoidea,,0,80509,0 -15659,42321906,Componente de fijación glenoidea,,0,80509,0 -15660,42321907,Revestimiento o protector de inserto de húmero,,0,80509,0 -15661,42321908,Restrictor de cemento para el canal humeral,,0,80509,0 -15662,42321909,Concha o receptáculo humeral,,0,80509,0 -15663,42321910,Centralizador o manga de tallo humeral,,0,80509,0 -15664,42321911,Cuerpo humeral,,0,80509,0 -15665,42321912,Kit o sistema de remplazo total o parcial de hombro,,0,80509,0 -15666,42322000,Implantes ortopédicos de articulaciones distales,,0,80509,0 -15667,42322001,Implante de articulación de tobillo,,0,80509,0 -15668,42322002,Implante de articulación de muñeca,,0,80509,0 -15669,42322003,Implante de articulación de codo,,0,80509,0 -15670,42322004,Implante de articulación de dedo,,0,80509,0 -15671,42322005,Implante de articulación de dedos del pie,,0,80509,0 -15672,42322006,Varilla o espaciador de tendones,,0,80509,0 -15673,42322200,Productos para la fijación de tejidos blandos,,0,80509,0 -15674,42322201,Tornillo de interferencia,,0,80509,0 -15675,42322202,Anclaje no suturado de tejidos blandos,,0,80509,0 -15676,42322203,Anclaje de sutura,,0,80509,0 -15677,42322204,Lavador de fijación de tejidos blandos,,0,80509,0 -15678,42322205,Perno de fijación de tejidos blandos,,0,80509,0 -15679,42322206,Grapa de tejidos blandos,,0,80509,0 -15680,43191500,Dispositivos de comunicación personal,,0,80509,0 -15681,43191501,Teléfonos móviles,Celular o Smartphone,0,80509,0 -15682,43191502,Busca personas,,0,80509,0 -15683,43191503,Teléfonos de pago,,0,80509,0 -15684,43191504,Teléfonos fijos,,0,80509,0 -15685,43191505,Máquinas contestadoras,,0,80509,0 -15686,43191507,Teléfonos para propósitos especiales,,0,80509,0 -15687,43191508,Teléfonos digitales,,0,80509,0 -15688,43191509,Teléfonos análogos,,0,80509,0 -15689,43191510,Radios de dos vías,,0,80509,0 -15690,43191511,Teléfonos ip,,0,80509,0 -15691,43191512,Teléfonos inalámbricos de telecomunicaciones inalámbricas digitales optimizadas dect,,0,80509,0 -15692,43191513,Video teléfono,,0,80509,0 -15693,43191514,Teléfono satelital,,0,80509,0 -15694,43191515,Teléfono manos libres,,0,80509,0 -15695,43191516,Sistema de conmutador telefónico,,0,80509,0 -15696,43191600,Partes o accesorios de dispositivos de comunicación personal,,0,80509,0 -15697,43191601,Placas frontales de teléfonos móviles,"Display para celular o Smartphone, Micas para celular o Smartphone",0,80509,0 -15698,43191602,Marcadores de teléfonos,,0,80509,0 -15699,43191603,Cables de extensión para teléfonos,,0,80509,0 -15700,43191604,Placas frontales de teléfonos,,0,80509,0 -15701,43191605,Cables para auriculares de teléfonos,,0,80509,0 -15702,43191606,Auriculares de teléfonos,Diademas para teléfonos,0,80509,0 -15703,43191607,Almohadillas para el oído o el parlante de los auriculares de teléfonos,,0,80509,0 -15704,43191608,Tubos de voz para los auriculares de teléfonos,,0,80509,0 -15705,43191609,Teléfonos de diadema,,0,80509,0 -15706,43191610,Soportes o sujetadores o puestos de comunicación personal,,0,80509,0 -15707,43191611,Protectores de líneas telefónicas,,0,80509,0 -15708,43191612,Descansa teléfonos,,0,80509,0 -15709,43191614,Convertidores de voz para teléfonos,,0,80509,0 -15710,43191615,Set telefónico manos libres para vehículos,,0,80509,0 -15711,43191616,Consola central para teléfonos,,0,80509,0 -15712,43191618,Unidades de grabación de conversaciones,,0,80509,0 -15713,43191619,Dispositivos de señalización para teléfonos,,0,80509,0 -15714,43191621,Adaptadores de auriculares,,0,80509,0 -15715,43191622,Módulos o accesorios de busca personas,,0,80509,0 -15716,43191623,Teléfono de pago con mecanismos,,0,80509,0 -15717,43191624,Recipientes de monedas para teléfonos de pago,,0,80509,0 -15718,43191625,Cajas de monedas para teléfonos de pago,,0,80509,0 -15719,43191626,Tolvas para teléfonos de pago,,0,80509,0 -15720,43191627,Puertas de seguridad para las cajas de monedas de teléfonos de pago,,0,80509,0 -15721,43191628,Protectores de micrófono para teléfonos de pago,,0,80509,0 -15722,43191629,Pieles o placas frontales para notebook o palm,Carcasas o micas para tabletas,0,80509,0 -15723,43191630,Kits de iniciación para teléfonos móviles,,0,80509,0 -15724,43191631,Clavijas de adaptadores o kits de campo o kits de viaje para teléfonos o módems,Cargadores de celular para vehículos,0,80509,0 -15725,43191632,Antenas para teléfonos,,0,80509,0 -15726,43191633,Subconjuntos para conmutadores telefónicos,,0,80509,0 -15727,43191634,Tablero de extensión para conmutador telefónico,,0,80509,0 -15728,43201400,Tarjetas del sistema,,0,80509,0 -15729,43201401,Tarjetas aceleradoras de gráficas o video,,0,80509,0 -15730,43201402,Tarjetas de módulos de memoria,,0,80509,0 -15731,43201403,Tarjetas de módem,,0,80509,0 -15732,43201404,Tarjetas de interface de red,,0,80509,0 -15733,43201405,Tarjetas receptoras de red óptica,,0,80509,0 -15734,43201406,Tarjetas transmisoras de red óptica,,0,80509,0 -15735,43201407,Tarjetas periféricas controladoras,,0,80509,0 -15736,43201409,Tarjetas de interface de red inalámbrica,,0,80509,0 -15737,43201410,Tarjetas o puertos de interruptor,,0,80509,0 -15738,43201411,Tarjeta de interconexión de componente periférico pci,,0,80509,0 -15739,43201412,Tarjeta pcmcia o pc,,0,80509,0 -15740,43201413,Tarjeta debanda magnética,,0,80509,0 -15741,43201414,Protector de disco duro,,0,80509,0 -15742,43201415,Tarjeta de módulo de identidad del suscriptor sim,,0,80509,0 -15743,43201416,Tablero de adquisición de datos,,0,80509,0 -15744,43201417,Tableros multimedia integrados,,0,80509,0 -15745,43201500,Módulos o interfaces de procesadores de tablero (board) del sistema,,0,80509,0 -15746,43201501,Tarjetas de interface de telecomunicaciones de modo de transferencia asincrónico atm,,0,80509,0 -15747,43201502,Tarjetas de aceleración de audio,,0,80509,0 -15748,43201503,Procesadores de unidad de procesamiento central cpu,,0,80509,0 -15749,43201507,Placas secundarias,,0,80509,0 -15750,43201508,Módulo de fibra de compensación de dispersión dcfm,,0,80509,0 -15751,43201509,Módulos de intercambio de comunicación de datos,,0,80509,0 -15752,43201513,Placa central de microordenador.,,0,80509,0 -15753,43201522,Tarjetas de puertos paralelos,,0,80509,0 -15754,43201531,Tarjetas de captura de video,,0,80509,0 -15755,43201533,Interfaces de interface digital de instrumentos musicales midi,,0,80509,0 -15756,43201534,Interfaces de componentes de intercambio codec,,0,80509,0 -15757,43201535,Puertos seriales infrarrojos,,0,80509,0 -15758,43201537,Servidores de impresoras,,0,80509,0 -15759,43201538,Enfriadores de unidades de procesamiento central,,0,80509,0 -15760,43201539,Procesador central controlador de consola,,0,80509,0 -15761,43201540,Convertidor de canales,,0,80509,0 -15762,43201541,Procesador central de interface canal a canal,,0,80509,0 -15763,43201542,Unidad de control,,0,80509,0 -15764,43201543,Procesador central de instalación de acoplamientos,,0,80509,0 -15765,43201544,Convertidor o controlador de bus de interface,,0,80509,0 -15766,43201545,Tarjetas de fax,,0,80509,0 -15767,43201546,Tarjetas de audio conferencia,,0,80509,0 -15768,43201547,Tarjetas de voz,,0,80509,0 -15769,43201549,Interruptores de interface de bus,,0,80509,0 -15770,43201550,Dispositivo de sincronización de paquete de datos de red,,0,80509,0 -15771,43201552,Adaptadores para hardware o telefonía,,0,80509,0 -15772,43201553,Convertidores de transceptores y medios,,0,80509,0 -15773,43201554,Sintonizadores de televisión personal por computador pc tv,,0,80509,0 -15774,43201555,Sintonizadores de radio para pc,,0,80509,0 -15775,43201556,Adaptadores de interconexión de sistemas para pequeños computadores scsi,,0,80509,0 -15776,43201557,Controladores de arreglo redundante de discos independientes raid,,0,80509,0 -15777,43201558,Controlador de canales de fibra,,0,80509,0 -15778,43201559,Tarjetas de puerto serial,,0,80509,0 -15779,43201560,Coprocesador,,0,80509,0 -15780,43201600,Componentes del chasis,,0,80509,0 -15781,43201601,Carcasas de computadoras,,0,80509,0 -15782,43201602,Carcasas de equipos de red,,0,80509,0 -15783,43201603,Componentes de apilamiento de carcasas,,0,80509,0 -15784,43201604,Bahías o canastas para equipos electrónicos,,0,80509,0 -15785,43201605,Expansores,,0,80509,0 -15786,43201608,Marcos de drive removibles,,0,80509,0 -15787,43201609,Bandejas o ensamblajes para almacenamiento de dispositivos,,0,80509,0 -15788,43201610,Placas traseras o paneles o ensamblajes,,0,80509,0 -15789,43201611,Bastidores (dispositivos de montaje) para computadores,,0,80509,0 -15790,43201612,Placas frontales para computadores,,0,80509,0 -15791,43201614,Extensores de consola,,0,80509,0 -15792,43201615,Kits de cubiertas para bahías de drives,,0,80509,0 -15793,43201616,Torres de arreglo de drives de discos duros,,0,80509,0 -15794,43201617,Armazones para tarjetas,,0,80509,0 -15795,43201618,Componente de rack de computador,,0,80509,0 -15796,43201619,Ventilador enfriador de computador,,0,80509,0 -15797,43201800,Dispositivos de almacenamiento,,0,80509,0 -15798,43201801,Unidades de disco flexible,,0,80509,0 -15799,43201802,Series de disco duro,,0,80509,0 -15800,43201803,Unidades de disco duro,,0,80509,0 -15801,43201806,Series de cintas,,0,80509,0 -15802,43201807,Unidades de cintas,,0,80509,0 -15803,43201808,Disco compacto cd de sólo lectura,,0,80509,0 -15804,43201809,Disco compacto cd de lectura y escritura,,0,80509,0 -15805,43201810,Disco versátil digital dvd de sólo lectura,,0,80509,0 -15806,43201811,Disco versátil digital dvd de lectura y escritura,,0,80509,0 -15807,43201812,Unidades de magneto óptico mo,,0,80509,0 -15808,43201813,Unidades de medios removibles de alta capacidad,,0,80509,0 -15809,43201814,Equipos electrónicos de duplicación de medios o información,,0,80509,0 -15810,43201815,Unidades de lectura y escritura de arquitectura de interconexión de componentes periféricos,,0,80509,0 -15811,43201816,Unidades ópticas de ultra densidad udo o autocargardores o bibliotecas,,0,80509,0 -15812,43201817,Unidades de disco compacto cd,,0,80509,0 -15813,43201818,Unidades de discos digitales de video dvd,,0,80509,0 -15814,43201819,Disco versátil digital de alta definición hd dvd de sólo lectura,,0,80509,0 -15815,43201820,Disco versátil digital de alta definición hd dvd de lectura y escritura,,0,80509,0 -15816,43201821,Disco de blu-ray bd de sólo lectura,,0,80509,0 -15817,43201822,Disco de blu-ray br de lectura y escritura,,0,80509,0 -15818,43201823,Reproductor de unidades multimedia de disco duro hdd,,0,80509,0 -15819,43201825,Publicador de discos,,0,80509,0 -15820,43201826,Series de memoria de discos compactos cd de sólo lectura,,0,80509,0 -15821,43201827,Dispositivo de almacenamiento de disco duro portátil,,0,80509,0 -15822,43201828,Audio disco de video digital dvd a,,0,80509,0 -15823,43201829,Video disco de video digital dvd v,,0,80509,0 -15824,43201830,Unidad de estado sólido ssd,,0,80509,0 -15825,43201831,Unidad de memoria de telecomunicaciones,,0,80509,0 -15826,43201832,Cargador de discos (juke box) compactos u ópticos,,0,80509,0 -15827,43201833,Sistema de conversión de digital a análogo,,0,80509,0 -15828,43201834,Controlador de dispositivo de almacenamiento,,0,80509,0 -15829,43201835,Dispositivo de almacenamiento unido a la red nas,,0,80509,0 -15830,43201900,Accesorios de dispositivos de almacenamiento,,0,80509,0 -15831,43201902,Cambiadores de discos ópticos,,0,80509,0 -15832,43201903,Bibliotecas de unidades de cinta,,0,80509,0 -15833,43201904,Audio video de video discos digitales dvd av,,0,80509,0 -15834,43202000,Dispositivos de almacenamiento extraíbles,,0,80509,0 -15835,43202001,Discos compactos cd,,0,80509,0 -15836,43202002,Cintas en blanco,,0,80509,0 -15837,43202003,Discos versátiles digitales dvd,,0,80509,0 -15838,43202004,Discos flexibles,,0,80509,0 -15839,43202005,Tarjeta flash de almacenamiento de memoria,,0,80509,0 -15840,43202006,Discos magneto ópticos mo,,0,80509,0 -15841,43202007,Discos en blanco de medios removibles de gran capacidad,,0,80509,0 -15842,43202008,Discos en blanco de alta densidad óptica udo,,0,80509,0 -15843,43202009,Cintas de audio en blanco,,0,80509,0 -15844,43202010,Unidad pen o flash,,0,80509,0 -15845,43202100,Accesorios de dispositivos de almacenamiento extraíbles,,0,80509,0 -15846,43202101,Estuches para discos compactos,,0,80509,0 -15847,43202102,Estuches para discos flexibles,,0,80509,0 -15848,43202103,Sujetadores de almacenadores de multimedia,,0,80509,0 -15849,43202104,Almacenadores para video cintas de escáner de hélice vertical vhs o accesorios,,0,80509,0 -15850,43202105,Gabinetes para medios múltiples,,0,80509,0 -15851,43202106,Estuches o billeteras para medios portátiles,,0,80509,0 -15852,43202107,Maleta para discos compactos o discos digitales versátiles cd/dvd,,0,80509,0 -15853,43202108,Estuche para tarjeta de memoria,,0,80509,0 -15854,43202200,Subconjuntos para dispositivos electrónicos,,0,80509,0 -15855,43202201,Partes de piezas de teléfono,,0,80509,0 -15856,43202202,Generadores de timbres de teléfono,,0,80509,0 -15857,43202204,Timbres externos o sus partes,,0,80509,0 -15858,43202205,Teclas o teclados,,0,80509,0 -15859,43202206,Componentes de dispositivo de entrada o unidad de almacenamiento,,0,80509,0 -15860,43202207,Brazos y ensamblaje de cabezal,,0,80509,0 -15861,43202208,Ensamblajes de plomo,,0,80509,0 -15862,43202209,Ensamblajes de apilamiento de cabezales,,0,80509,0 -15863,43202210,Tope de protección anti choque,,0,80509,0 -15864,43202211,Platos o discos,,0,80509,0 -15865,43202212,Ensamblajes de cabezal de lectura y escritura,,0,80509,0 -15866,43202213,Unidades de disco de motor,,0,80509,0 -15867,43202214,Ensamblajes de regleta,,0,80509,0 -15868,43202215,Cable de teclado o mouse,,0,80509,0 -15869,43202216,Cable de conexión de impresora,,0,80509,0 -15870,43202217,Sujetador de cables de computador,,0,80509,0 -15871,43202218,Cubierta de cables de computador,,0,80509,0 -15872,43202219,Ensamblaje de cabezales de control de aire acondicionado de ventilación de automotores hvac,,0,80509,0 -15873,43202220,Módulo de energía de aire acondicionado de ventilación de automotores hvac,,0,80509,0 -15874,43202221,Ensamblaje de resistencias de aire acondicionado de ventilación de automotores hvac,,0,80509,0 -15875,43202222,Cables de computador,Cables de computadora,0,80509,0 -15876,43211500,Computadores,,0,80509,0 -15877,43211501,Servidores de computador,Servidores de computadoras,0,80509,0 -15878,43211502,Servidores de computador de gama alta,Servidores de computadoras de gama alta,0,80509,0 -15879,43211503,Computadores notebook,Computadoras notebook,0,80509,0 -15880,43211504,Asistentes personales digitales pda u organizadores,Asistentes personales digitales pda u organizadores,0,80509,0 -15881,43211505,Terminal de punto de venta pos,,0,80509,0 -15882,43211506,Computadores de cliente ligero,Ordenadores ligeros,0,80509,0 -15883,43211507,Computadores de escritorio,"Computadora de escritorio, Equipo de cómputo",0,80509,0 -15884,43211508,Computadores personales,"Computadoras personales o laptop, Equipo de cómputo",0,80509,0 -15885,43211509,Computadores de tableta,Tabletas,0,80509,0 -15886,43211510,Consola central o terminales básicos (no inteligentes),,0,80509,0 -15887,43211511,Dispositivos de computación de vestir,,0,80509,0 -15888,43211512,Computadores centrales,Computadores centrales,0,80509,0 -15889,43211513,Computadores personales ultra móviles,Computadoras personales ultrabook,0,80509,0 -15890,43211514,Kioscos de computadores,Kioscos de computadoras,0,80509,0 -15891,43211515,Estaciones de trabajo para computadores,Estaciones de trabajo para computadores,0,80509,0 -15892,43211516,Minicomputadores,Minicomputadoras,0,80509,0 -15893,43211517,Computadores análogos,Computadores análogos,0,80509,0 -15894,43211518,Computadores de múltiples pantallas,Computadores de múltiples pantallas,0,80509,0 -15895,43211600,Accesorios de computador,,0,80509,0 -15896,43211601,Cajas de interruptores de computador,Cajas de interruptores de computadora,0,80509,0 -15897,43211602,Estaciones de acoplamiento,,0,80509,0 -15898,43211603,Replicadores de puertos,,0,80509,0 -15899,43211604,Cajas de interruptores periféricos,,0,80509,0 -15900,43211605,Actualizaciones de procesadores de señales,,0,80509,0 -15901,43211606,Kits de multimedia,,0,80509,0 -15902,43211607,Parlantes de computador,"Altavoz para computadora, Bocina para computadora",0,80509,0 -15903,43211608,Equipo codificador y decodificador,,0,80509,0 -15904,43211609,Concentrador de bus serial universal o conectores,,0,80509,0 -15905,43211610,Kits de países o localización para computadores,,0,80509,0 -15906,43211611,Kits de arranque de mano pda,,0,80509,0 -15907,43211612,Kits de accesorios de computador,Kits de accesorios para computadoras,0,80509,0 -15908,43211613,Puestos para computadores o notebooks,Puestos para computadoras o notebooks,0,80509,0 -15909,43211614,Adaptador de bus serial universal usb bluetooth,,0,80509,0 -15910,43211615,Puerto de expansión de computador notebook,Puerto de expansión de computadora notebook,0,80509,0 -15911,43211616,Sujetador de asistente personal digital pda,,0,80509,0 -15912,43211617,Cable de extensión de bus serial universal usb,,0,80509,0 -15913,43211618,Módem de radio servicio de paquete general de bus serial universal,,0,80509,0 -15914,43211619,Estuche para transportar computadores notebook,Estuche para transportar computadoras notebook,0,80509,0 -15915,43211700,Dispositivos informáticos de entrada de datos,,0,80509,0 -15916,43211701,Equipo de lectura de código de barras,,0,80509,0 -15917,43211702,Lectores y codificadores de banda magnética,,0,80509,0 -15918,43211704,Equipo de reconocimiento de billetes,,0,80509,0 -15919,43211705,Almohadillas o joy sticks de juegos,,0,80509,0 -15920,43211706,Teclados,,0,80509,0 -15921,43211707,Lápiz (stylus) para computador de luz,,0,80509,0 -15922,43211708,Mouse o bola de seguimiento para computador,,0,80509,0 -15923,43211709,Lápiz (stylus) de presión,,0,80509,0 -15924,43211710,Dispositivos de identificación de radio frecuencia,,0,80509,0 -15925,43211711,Escáneres,,0,80509,0 -15926,43211712,Tabletas de gráficos,,0,80509,0 -15927,43211713,Almohadillas (pads) táctil (touch),,0,80509,0 -15928,43211714,Equipos de identificación biométrica,,0,80509,0 -15929,43211715,Terminales portátiles de ingreso de información,,0,80509,0 -15930,43211717,Sistemas de reconocimiento de caracteres ópticos,,0,80509,0 -15931,43211718,Sistemas de visión basados en cámaras para recolección automática de información,,0,80509,0 -15932,43211719,Micrófonos de voz para computadores,,0,80509,0 -15933,43211720,Terminales de pago para puntos de venta,,0,80509,0 -15934,43211721,Lectores de tarjetas perforadas,,0,80509,0 -15935,43211722,Escáneres de tarjetas de presentación,,0,80509,0 -15936,43211723,Equipos de votación electrónica o de conteo de votos,,0,80509,0 -15937,43211724,Kit periférico para computador,,0,80509,0 -15938,43211725,Limpiador de discos compactos o de discos versátiles digitales cd/dvd,,0,80509,0 -15939,43211726,Esferográfico digital,,0,80509,0 -15940,43211727,Dispositivo magnético de reconocimiento de caracteres en tinta micr,,0,80509,0 -15941,43211728,Terminal simple de tarjetas inteligentes,,0,80509,0 -15942,43211729,Lector de marcas ópticas,,0,80509,0 -15943,43211730,Sistema de adquisición de datos,,0,80509,0 -15944,43211731,Analizador de datos,,0,80509,0 -15945,43211732,Lector de películas digitales,,0,80509,0 -15946,43211800,Accesorios de dispositivos de entrada de datos de computador,,0,80509,0 -15947,43211801,Forros de dispositivos de ingreso de datos al computador,,0,80509,0 -15948,43211802,Almohadillas (pads) para mouse,,0,80509,0 -15949,43211803,Pieles (películas transparentes) para teclados,,0,80509,0 -15950,43211804,Cajones o estantes para tableros,,0,80509,0 -15951,43211805,Dispositivos para almacenamiento de kits de servicio,,0,80509,0 -15952,43211806,Descansa muñecas para teclado,,0,80509,0 -15953,43211807,Descansa muñecas para mouse,,0,80509,0 -15954,43211900,Monitores y pantallas de computador,,0,80509,0 -15955,43211901,Monitores para tubos de rayo catódico crt,,0,80509,0 -15956,43211902,Paneles o monitores de pantalla de cristal líquido lcd,,0,80509,0 -15957,43211903,Monitores de pantalla táctil (touch),,0,80509,0 -15958,43211904,Paneles de pantalla de plasma pdp,,0,80509,0 -15959,43211905,Pantallas emisoras de luz orgánica,,0,80509,0 -15960,43211906,Pantallas de caracteres,,0,80509,0 -15961,43211907,Cascos de realidad virtual,,0,80509,0 -15962,43211908,Pantallas de vacío fluorescente,,0,80509,0 -15963,43211909,Pantallas emisoras de diodos de poli luz,,0,80509,0 -15964,43211910,Mecanismo de inclinación de pantallas,,0,80509,0 -15965,43211911,Vidrio de pantalla táctil (touch),,0,80509,0 -15966,43211912,Película de pantalla táctil (touch),,0,80509,0 -15967,43211913,Pantalla activa lcd,,0,80509,0 -15968,43211914,Pantalla pasiva lcd,,0,80509,0 -15969,43212000,Accesorios de pantallas de computador,,0,80509,0 -15970,43212001,Protectores de brillo para pantallas de computador,,0,80509,0 -15971,43212002,Brazos o soportes para monitores,,0,80509,0 -15972,43212100,Impresoras de computador,,0,80509,0 -15973,43212101,Impresoras de banda,,0,80509,0 -15974,43212102,Impresoras de matriz de puntos,,0,80509,0 -15975,43212104,Impresoras de inyección de tinta,,0,80509,0 -15976,43212105,Impresoras láser,,0,80509,0 -15977,43212106,Impresoras de matriz de líneas,,0,80509,0 -15978,43212107,Impresoras de plotter,,0,80509,0 -15979,43212108,Impresoras de cinta térmica,,0,80509,0 -15980,43212109,Impresoras de etiquetas para bolsas,,0,80509,0 -15981,43212110,Impresoras de múltiples funciones,,0,80509,0 -15982,43212111,Impresoras de tiquetes aéreos o pases de abordaje atb,,0,80509,0 -15983,43212112,Impresoras de recibos para puntos de venta pos,,0,80509,0 -15984,43212113,Impresoras de discos compactos cd o de etiquetado,,0,80509,0 -15985,43212114,Impresoras de imágenes digitales,,0,80509,0 -15986,43212115,Impresoras de códigos de barra,,0,80509,0 -15987,43212116,Impresoras de etiquetas de identificación de radio frecuencia rfid,,0,80509,0 -15988,43212117,Controlador para impresoras,,0,80509,0 -15989,43212200,Sistemas de manejo de almacenamiento de datos de computador,,0,80509,0 -15990,43212201,Sistema de almacenamiento virtual,,0,80509,0 -15991,43221500,Sistemas de gestión de llamadas o accesorios,,0,80509,0 -15992,43221501,Sistemas de atención automatizada,,0,80509,0 -15993,43221502,Distribuidor automático de llamadas acd,,0,80509,0 -15994,43221503,Alto parlantes para telecomunicaciones,,0,80509,0 -15995,43221504,Central telefónica interna pbx,,0,80509,0 -15996,43221505,Identificador de llamadas telefónicas autónoma,,0,80509,0 -15997,43221506,Consola de teleconferencias,,0,80509,0 -15998,43221507,Auto marcadores,,0,80509,0 -15999,43221508,Campos de lámpara de teléfono ocupado,,0,80509,0 -16000,43221509,Sistemas de contabilización de llamadas telefónicas,,0,80509,0 -16001,43221510,Desviador o retransmisor de llamadas telefónicas,,0,80509,0 -16002,43221513,Secuenciadores de llamadas telefónicas,,0,80509,0 -16003,43221514,Unidades de respaldo de marcación telefónica,,0,80509,0 -16004,43221515,Dispositivos para compartir líneas telefónicas,,0,80509,0 -16005,43221516,Monitores de estado de líneas telefónicas,,0,80509,0 -16006,43221517,Unidades de observación de servicio de equipos de telefonía,,0,80509,0 -16007,43221518,Dispositivos para restringir llamadas para equipos de telefonía,,0,80509,0 -16008,43221519,Sistemas de correo de voz,,0,80509,0 -16009,43221520,Equipos interactivos de reconocimiento de voz,,0,80509,0 -16010,43221521,Unidades de acceso remoto de telecomunicaciones,,0,80509,0 -16011,43221522,Equipos de teleconferencia,,0,80509,0 -16012,43221523,Grabadora de música o mensaje en espera,,0,80509,0 -16013,43221524,Adaptador de música en espera,,0,80509,0 -16014,43221525,Sistemas de intercomunicación,,0,80509,0 -16015,43221526,Sistemas de entrada de teléfono,,0,80509,0 -16016,43221527,Protector de “chuzadas” (acceso ilegal) de cable,,0,80509,0 -16017,43221528,Unidad de señales de supervisión,,0,80509,0 -16018,43221529,Medidor de llamadas,,0,80509,0 -16019,43221530,Dispositivo de señales de conmutador,,0,80509,0 -16020,43221700,Equipo fijo de red y componentes,,0,80509,0 -16021,43221701,Equipo central de televisión,,0,80509,0 -16022,43221702,Equipo de acceso de televisión,,0,80509,0 -16023,43221703,Antenas de televisión,,0,80509,0 -16024,43221704,Equipo central de radio,,0,80509,0 -16025,43221705,Equipo de radio acceso,,0,80509,0 -16026,43221706,Antenas de radio,,0,80509,0 -16027,43221707,Equipo central de microondas,,0,80509,0 -16028,43221708,Equipo de acceso de microondas,,0,80509,0 -16029,43221709,Antenas de microondas,,0,80509,0 -16030,43221710,Equipo central de satélite,,0,80509,0 -16031,43221711,Equipo de acceso de satélite,,0,80509,0 -16032,43221712,Antenas de satélite,,0,80509,0 -16033,43221713,Equipo central de onda corta,,0,80509,0 -16034,43221714,Equipo de acceso de onda corta,,0,80509,0 -16035,43221715,Antenas de onda corta,,0,80509,0 -16036,43221716,Equipo central de buscapersonas,,0,80509,0 -16037,43221717,Equipo de acceso de buscapersonas,,0,80509,0 -16038,43221718,Antenas de radar,,0,80509,0 -16039,43221719,Antenas de aeronaves,,0,80509,0 -16040,43221720,Antenas de automotores,,0,80509,0 -16041,43221721,Equipo de comunicación de información de radio frecuencia,,0,80509,0 -16042,43221722,Antena de sistema de posicionamiento geográfico gps,,0,80509,0 -16043,43221723,Antena de celular,,0,80509,0 -16044,43221724,Generador sincronizado de tiempo,,0,80509,0 -16045,43221725,Divisor de señales,,0,80509,0 -16046,43221726,Reflector para antenas,,0,80509,0 -16047,43221727,Accesorio para antenas,,0,80509,0 -16048,43221728,Radomos o domo para radares,,0,80509,0 -16049,43221729,Antenas portátiles,,0,80509,0 -16050,43221730,Transmisor de telecomunicaciones por cable,,0,80509,0 -16051,43221731,Sistema de comunicación bajo el agua,,0,80509,0 -16052,43221732,Unidad de control de altavoces,,0,80509,0 -16053,43221733,Sistema de medición de lectura automática remota,,0,80509,0 -16054,43221800,Dispositivos de red óptica,,0,80509,0 -16055,43221801,Amplificadores ópticos,,0,80509,0 -16056,43221802,Filtros de redes o comunicaciones ópticas,,0,80509,0 -16057,43221803,Adaptadores ópticos,,0,80509,0 -16058,43221804,Láseres de red óptica,,0,80509,0 -16059,43221805,Equipo de red de modo de transferencia asincrónica atm,,0,80509,0 -16060,43221806,Equipos de red de red óptica sincrónica sonet,,0,80509,0 -16061,43221807,Filtros multiplexados de división de longitud de onda densa de telecomunicaciones dwdm,,0,80509,0 -16062,43221808,Equipos de telecomunicaciones jerárquicas digitales sincrónicas sdh,,0,80509,0 -16063,43221809,Transmisor óptico,,0,80509,0 -16064,43221810,Multiplexador óptico,,0,80509,0 -16065,43221811,Interruptor óptico,,0,80509,0 -16066,43222500,Equipo de seguridad de red,,0,80509,0 -16067,43222501,Equipo de seguridad de red cortafuegos (firewall),,0,80509,0 -16068,43222502,Equipo de seguridad de redes vpn,,0,80509,0 -16069,43222503,Equipos de seguridad de evaluación de vulnerabilidad,,0,80509,0 -16070,43222504,Transmisor de interferencias de radio,,0,80509,0 -16071,43222600,Equipo de servicio de red,,0,80509,0 -16072,43222602,Equipo de centro distribuidor de cables,,0,80509,0 -16073,43222604,Equipo de red de entrega de contenido,,0,80509,0 -16074,43222605,Puerta de acceso de redes,,0,80509,0 -16075,43222606,Kit de inicio de nodo de servicio de internet,,0,80509,0 -16076,43222607,Equipo de motor de caché,,0,80509,0 -16077,43222608,Repetidores de red,,0,80509,0 -16078,43222609,Enrutadores (routers) de red,,0,80509,0 -16079,43222610,Concentradores o hubs de servicio de red,,0,80509,0 -16080,43222611,Unidades de servicio de canales o información de red,,0,80509,0 -16081,43222612,Interruptores de red,,0,80509,0 -16082,43222615,Interruptor de área de almacenamiento de red san,,0,80509,0 -16083,43222619,Equipo de video de red,,0,80509,0 -16084,43222620,Interruptor multi servicios,,0,80509,0 -16085,43222621,Interruptor de contenido,,0,80509,0 -16086,43222622,Dispositivo para balancear la carga del servidor,,0,80509,0 -16087,43222623,Equipos de conexión cruzada digital dcx,,0,80509,0 -16088,43222624,Equipos de conexión cruzada óptica,,0,80509,0 -16089,43222625,Servidores de acceso,,0,80509,0 -16090,43222626,Módems de cable,,0,80509,0 -16091,43222627,Dispositivos de acceso de redes digitales de servicios integrados isdn,,0,80509,0 -16092,43222628,Módems,,0,80509,0 -16093,43222629,Bancos de módems,,0,80509,0 -16094,43222630,Unidades de acceso multi estaciones,,0,80509,0 -16095,43222631,Estaciones base de fidelidad inalámbricas,,0,80509,0 -16096,43222632,Agregadores de banda ancha,,0,80509,0 -16097,43222633,Adaptadores de manejo remoto,,0,80509,0 -16098,43222634,Dispositivo de monitoreo o manejo de redes,,0,80509,0 -16099,43222635,Kit de actualización de equipo de redes,,0,80509,0 -16100,43222636,Motor de aplicación de redes,,0,80509,0 -16101,43222637,Red óptica pasiva de gigabytes gpon,,0,80509,0 -16102,43222638,Equipos de red de línea dedicada manejada mlln,,0,80509,0 -16103,43222639,Hardware de subsistemas multimedia de protocolo de internet ip,,0,80509,0 -16104,43222640,Punto de acceso inalámbrico,,0,80509,0 -16105,43222641,Dispositivo de protocolo de internet compartido,,0,80509,0 -16106,43222642,Interruptor de enrutador (router),,0,80509,0 -16107,43222643,Probador de cable de red,,0,80509,0 -16108,43222644,Controlador de tráfico de red,,0,80509,0 -16109,43222700,Equipo telegráfico,,0,80509,0 -16110,43222701,Teclas de telégrafo,,0,80509,0 -16111,43222702,Electro magnetos de telégrafo,,0,80509,0 -16112,43222703,Receptor acústico de telégrafo,Sondas para telégrafo,0,80509,0 -16113,43222704,Conmutador de telégrafo,,0,80509,0 -16114,43222800,Equipo de telefonía,,0,80509,0 -16115,43222801,Equipo de conexión cruzada digital dcx de banda estrecha o de banda ancha,Equipos DCX de banda ancha o banda estrecha de interconexión digital,0,80509,0 -16116,43222802,Equipo de circuito de conmutador,Equipos de conmutadores de circuitos,0,80509,0 -16117,43222803,Cargador de circuito digital dlc,Porta bobina digital DLC,0,80509,0 -16118,43222805,Equipo de central telefónica privada pbx,Equipo de central telefónica privada pbx,0,80509,0 -16119,43222806,Bloques de conexiones,Bloque de conexión rápida para telefonía,0,80509,0 -16120,43222811,Unidades de alarma de equipos de telefonía,,0,80509,0 -16121,43222813,Kits de partes de conmutadores telefónicos,,0,80509,0 -16122,43222814,Kits de instalación o modificación de equipos de telecomunicaciones,,0,80509,0 -16123,43222815,Unidades de terminales de telecomunicaciones,,0,80509,0 -16124,43222816,Operadores de introducción de datos de telefonía,,0,80509,0 -16125,43222817,Repetidores de telecomunicaciones,Repetidores de telecomunicaciones,0,80509,0 -16126,43222818,Estructura de terminal de distribución de teléfono,,0,80509,0 -16127,43222819,Paneles de conexión de puertos,Paneles de conexión de puertos,0,80509,0 -16128,43222820,Equipo electrónico para cancelar eco de voz,Equipo electrónico para cancelar eco de voz,0,80509,0 -16129,43222822,Multiplexor de división de tiempo tdm,,0,80509,0 -16130,43222823,Multiplexor de división de onda wdm,,0,80509,0 -16131,43222824,Enrolladores de cables aéreos,,0,80509,0 -16132,43222825,Kits de modificación de teléfonos,,0,80509,0 -16133,43222826,Terminal portadora,,0,80509,0 -16134,43222827,Multiplexor o mux,,0,80509,0 -16135,43222900,Accesorios de equipo de telefonía,,0,80509,0 -16136,43222901,Acondicionadores de línea,Reguladores de voltaje,0,80509,0 -16137,43222902,Secadores de aire para cables de telefonía,,0,80509,0 -16138,43222903,Torre o soporte para telecomunicaciones,,0,80509,0 -16139,43223100,Componentes y equipo de infraestructura de redes móviles y digitales,,0,80509,0 -16140,43223101,Equipos y componentes de red básica móvil gsm 2g,,0,80509,0 -16141,43223102,Equipos y componentes de red de acceso inalámbrica gsm 2g,,0,80509,0 -16142,43223103,Equipos y componentes de red básica móvil gprs 2.5g,,0,80509,0 -16143,43223104,Equipos y componentes de red de acceso inalámbrica gprs 2.5g,,0,80509,0 -16144,43223105,Equipos y componentes de red básica móvil umts 3g,,0,80509,0 -16145,43223106,Equipos y componentes de red de acceso inalámbrica 3g umts,,0,80509,0 -16146,43223107,Equipos y componentes de red básica móvil wlan,,0,80509,0 -16147,43223108,Equipos y componentes de acceso inalámbrico wlan,,0,80509,0 -16148,43223109,Equipo de interrupción ssp in,,0,80509,0 -16149,43223110,Equipo básico móvil in,,0,80509,0 -16150,43223111,Equipos y componentes de red básica móvil oss,,0,80509,0 -16151,43223112,Equipos y componentes de red de acceso inalámbrica oss,,0,80509,0 -16152,43223113,Antenas lan umt gsm,,0,80509,0 -16153,43223300,Dispositivos y equipos para instalación de conectividad de redes y Datacom,,0,80509,0 -16154,43223301,Sistema y accesorios de conexión cruzada de comunicación de datos,,0,80509,0 -16155,43223302,Etiqueta de comunicación de datos,,0,80509,0 -16156,43223303,Cable de conexión de comunicación de datos,,0,80509,0 -16157,43223305,Ensamblaje de paneles de manejo de cables de red,,0,80509,0 -16158,43223306,Gabinete o cerramiento para sistemas de red,,0,80509,0 -16159,43223307,Caja de cables para sistemas de red,,0,80509,0 -16160,43223308,Estantes para equipos de sistemas de red,,0,80509,0 -16161,43223309,Panel de conexión,"Bahía de rutas, Panel de conexiones",0,80509,0 -16162,43223310,Conector de fibra óptica,,0,80509,0 -16163,43223311,Atenuador de fibra óptica,,0,80509,0 -16164,43223312,Pela cables de fibra óptica,Pinza pela cables de fibra óptica,0,80509,0 -16165,43223313,Engarzador de fibra óptica,Pinza engarzador de fibra óptica,0,80509,0 -16166,43223314,Dispositivo para pulir fibra óptica,Lustrador de fibra óptica,0,80509,0 -16167,43223315,Set de cuchillas para herramientas de fibra óptica,Set de cuchillas para herramientas de fibra óptica,0,80509,0 -16168,43223316,Punzón de fibra óptica,,0,80509,0 -16169,43223317,Removedor de búfer de fibra óptica,Removedor de tampón de fibra óptica,0,80509,0 -16170,43223318,Emisor de fibra óptica,,0,80509,0 -16171,43223319,Detector de fibra óptica,,0,80509,0 -16172,43223320,Probador de continuidad de fibra óptica,,0,80509,0 -16173,43223321,Clip conector de fibra óptica,Clip conector de fibra óptica,0,80509,0 -16174,43223322,Gancho de soporte de fibra óptica,,0,80509,0 -16175,43223323,Adaptador de fibra óptica,,0,80509,0 -16176,43223324,Película pulidora de fibra óptica,,0,80509,0 -16177,43223325,Hisopos para fibra óptica,,0,80509,0 -16178,43223326,Cable de conexión de fibra óptica,,0,80509,0 -16179,43223327,Localizador de fallas de fibra óptica,,0,80509,0 -16180,43223328,Panel de conexión de fibra óptica,Panel de conexiones de fibra óptica,0,80509,0 -16181,43223329,Fuente de luz de fibra óptica,Fuente de luz de fibra óptica,0,80509,0 -16182,43223330,Herramientas de ensamblaje de conector de fibra óptica,"Herramientas de montaje de conector de fibra óptica, Instrumentos de ensamble de conector de fibra óptica",0,80509,0 -16183,43223331,Inserto de fibra óptica,,0,80509,0 -16184,43223332,Kit de herramientas de fibra óptica,,0,80509,0 -16185,43223333,Lente de alcance de fibra óptica,Ocular de alcance de fibra óptica,0,80509,0 -16186,43223334,Set de herramientas y tintes de fibra óptica,,0,80509,0 -16187,43223335,Cable de música de fibra óptica,Cable de música de fibra óptica,0,80509,0 -16188,43223336,Adaptador de alcance de fibra óptica,,0,80509,0 -16189,43223337,Cerramiento de fibra óptica,Cerramiento de fibra óptica,0,80509,0 -16190,43223338,Cerramiento de conexión de fibra óptica,Cierre de conexión para fibra óptica,0,80509,0 -16191,43223339,Caja de distribución de fibra óptica,,0,80509,0 -16192,43223340,Empalmadora de fusión de fibra óptica,,0,80509,0 -16193,43223341,Combinadora,Combinadora,0,80509,0 -16194,43223342,Kit de conexión y guía de onda,Kit de conexión y guía de onda,0,80509,0 -16195,43223343,Herramienta de “punchdown” de red,Herramienta de perforación de red,0,80509,0 -16196,43231500,Software funcional específico de la empresa,,0,80509,0 -16197,43231501,Software de mesa de ayuda o centro de llamadas (call center),,0,80509,0 -16198,43231503,Software de adquisiciones,,0,80509,0 -16199,43231505,Software de recursos humanos,Software de recursos humanos,0,80509,0 -16200,43231506,Software de logística de planeación de requerimiento de materiales y cadena de suministros,Programa de logística de planeación de requerimiento de materiales y cadena de suministros,0,80509,0 -16201,43231507,Software de manejo de proyectos,Software de manejo de proyectos,0,80509,0 -16202,43231508,Software de manejo de inventarios,Software de manejo de inventarios,0,80509,0 -16203,43231509,Software de barras de códigos,Software de barras de códigos,0,80509,0 -16204,43231510,Software para hacer etiquetas,Software para hacer etiquetas,0,80509,0 -16205,43231511,Software de sistemas expertos,Software de sistemas expertos,0,80509,0 -16206,43231512,Software de manejo de licencias,Programa de manejo de licencias,0,80509,0 -16207,43231513,Software para oficinas,Software para oficinas,0,80509,0 -16208,43231514,Software de ventas y mercadeo,Software de ventas y mercadeo,0,80509,0 -16209,43231515,Software de envío y embarque,Software de envío y embarque,0,80509,0 -16210,43231600,Software de planificación de recursos empresariales (ERP) y contabilidad financiera,,0,80509,0 -16211,43231601,Software de contabilidad,Software de contabilidad,0,80509,0 -16212,43231602,Software de planeación de recursos del negocio erp,Software de planeación de recursos del negocio erp,0,80509,0 -16213,43231603,Software de preparación tributaria,Software de preparación tributaria,0,80509,0 -16214,43231604,Software de análisis financiero,Software de análisis financiero,0,80509,0 -16215,43231605,Software de contabilidad de tiempo,,0,80509,0 -16216,43232000,Software de entretenimiento o juegos de computador,,0,80509,0 -16217,43232001,Juegos de acción,Videojuego de acción,0,80509,0 -16218,43232002,Juegos de aventuras,Videojuego de aventuras,0,80509,0 -16219,43232003,Juegos de deportes,Videojuegos de deportes,0,80509,0 -16220,43232004,Software familiar,Programa familiar,0,80509,0 -16221,43232005,Software de edición de música o sonido,Software de edición de música o sonido,0,80509,0 -16222,43232300,Software de consultas y gestión de datos,,0,80509,0 -16223,43232301,Software de categorización o clasificación,Software de categorización o clasificación,0,80509,0 -16224,43232302,Software de agrupamiento de recursos,Software de agrupamiento de recursos,0,80509,0 -16225,43232303,Software de manejo de relaciones con el cliente crm,Software de manejo de relaciones con el cliente crm,0,80509,0 -16226,43232304,Software de sistemas de manejo de base datos,Software de sistemas de manejo de base datos,0,80509,0 -16227,43232305,Software de reportes de bases de datos,"Programa de reportes de bases de datos, Software de reportes de bases de datos",0,80509,0 -16228,43232306,Software de interface y preguntas de usuario de base de datos,Software de interface y preguntas de usuario de base de datos,0,80509,0 -16229,43232307,Software de extracción de datos,Software de extracción de datos,0,80509,0 -16230,43232309,Software de recuperación o búsqueda de información,Software de recuperación o búsqueda de información,0,80509,0 -16231,43232310,Software de manejo de metadata,"Programa de manejo de datos, Software de manejo de metadata",0,80509,0 -16232,43232311,Software de manejo de base de datos orientada al objeto,"Programa de manejo de base de datos orientada al objeto, Software de manejo de base de datos orientada al objeto",0,80509,0 -16233,43232312,Software de servidor de portales,Software de servidor de portales,0,80509,0 -16234,43232313,Software de servidor de transacciones,Software de servidor de transacciones,0,80509,0 -16235,43232400,Programas de desarrollo,,0,80509,0 -16236,43232401,Software de manejo de configuraciones,"Programa de administración de configuraciones, Programa de manejo de configuraciones",0,80509,0 -16237,43232402,Software de entorno de desarrollo,Software de entorno de desarrollo,0,80509,0 -16238,43232403,Software de integración de aplicaciones de empresas,Programa de integración de aplicaciones de empresas,0,80509,0 -16239,43232404,Software de desarrollo de interface de usuario gráfica,Software de desarrollo de interface de usuario gráfica,0,80509,0 -16240,43232405,Software de desarrollo orientado a objetos o componentes,Software de desarrollo orientado a objetos o componentes,0,80509,0 -16241,43232406,Software de pruebas de programas,Software de pruebas de programas,0,80509,0 -16242,43232407,Software de arquitectura de sistemas y análisis de requerimientos,Software de arquitectura de sistemas y análisis de requerimientos,0,80509,0 -16243,43232408,Software de desarrollo de plataformas web,Software de desarrollo de plataformas web,0,80509,0 -16244,43232409,Software para compilar y descompilar,Software para compilar y descompilar,0,80509,0 -16245,43232500,Software educativo o de referencia,,0,80509,0 -16246,43232501,Software de idiomas extranjeros (traductores),"Programa para traducciones, Software de idiomas extranjeros (traductores)",0,80509,0 -16247,43232502,Software de entrenamiento basado en computadores,Software de entrenamiento basado en computadores,0,80509,0 -16248,43232503,Correctores de ortografía,,0,80509,0 -16249,43232504,Software de navegación de rutas,Software de navegación de rutas,0,80509,0 -16250,43232505,Software educacional multimedios,Software educacional multimedios,0,80509,0 -16251,43232506,Software de enciclopedias,Software de enciclopedias,0,80509,0 -16252,43232507,Software de diccionarios,Software de diccionarios,0,80509,0 -16253,43232508,Software de libretas de teléfonos,"Programa de la agenda telefónica, Programa de libretas de teléfonos",0,80509,0 -16254,43232600,Software específico para la industria,,0,80509,0 -16255,43232601,Software de soporte de aviación en tierra,Software de soporte de aviación en tierra,0,80509,0 -16256,43232602,Software de pruebas de aviación,Software de pruebas de aviación,0,80509,0 -16257,43232603,Software de manejo de instalaciones,Software de manejo de instalaciones,0,80509,0 -16258,43232604,Software de diseño asistido de computador cad,"Programa CAD de diseño asistido por ordenador, Software de diseño asistido de computador cad",0,80509,0 -16259,43232605,Software analítico o científico,Software analítico o científico,0,80509,0 -16260,43232606,Software de cumplimiento (compliance),Software de cumplimiento (compliance),0,80509,0 -16261,43232607,Software de control de vuelos,Software de control de vuelos,0,80509,0 -16262,43232608,Software de control industrial,"Programa de supervisión industrial, Programa de Vigilancia industrial, Software de control industrial",0,80509,0 -16263,43232609,Software de bibliotecas,Software de bibliotecas,0,80509,0 -16264,43232610,Software médico,,0,80509,0 -16265,43233200,Software de seguridad y protección,,0,80509,0 -16266,43233201,Software de servidor de autenticación,Software de servidor de autenticación,0,80509,0 -16267,43233203,Software de manejo de seguridad de red o de redes privadas virtuales vpn,"Programa de manejo de seguridad de red o de redes privadas virtuales vpn, Software de manejo de seguridad de red o de redes privadas virtuales vpn",0,80509,0 -16268,43233204,Software de equipos de seguridad de red y de redes privadas virtuales vpn,Software de equipos de seguridad de red y de redes privadas virtuales vpn,0,80509,0 -16269,43233205,Software de seguridad de transacciones y de protección contra virus,,0,80509,0 -16270,43233400,Software de controladores de dispositivos y utilidades,,0,80509,0 -16271,43233401,Software de servidor de discos compactos cd,Software de servidor de discos compactos cd,0,80509,0 -16272,43233402,Software de conversión de información,"Programa de conversión de información, Programa de transformación de información, Software de conversión de información",0,80509,0 -16273,43233403,Software de compresión de información,"Programa de contracción de información, Programa para comprimir información, Software de compresión de información",0,80509,0 -16274,43233404,Software discos compactos cd o dvd o tarjetas de sonido,"Software o controladores para unidades de CD, DVD o tarjeta de sonido",0,80509,0 -16275,43233405,Software de controladores o sistemas de dispositivos,Software de controladores o sistemas de dispositivos,0,80509,0 -16276,43233406,Software de controladores de ethernet,Software de controladores de ethernet,0,80509,0 -16277,43233407,Software de controladores de tarjetas de gráficos,Software de controladores de tarjetas de gráficos,0,80509,0 -16278,43233410,Software de controladores de impresoras,Software de controladores de impresoras,0,80509,0 -16279,43233411,Software de protectores de pantalla,Software de protectores de pantalla,0,80509,0 -16280,43233413,Software de reconocimiento de voz,Software de reconocimiento de voz,0,80509,0 -16281,43233414,Software de carga de almacenamiento de medios,Software de carga de almacenamiento de medios,0,80509,0 -16282,43233415,Software de respaldo o archivo,Software de respaldo o archivo,0,80509,0 -16283,43233416,Plataformas de codificación – decodificación,,0,80509,0 -16284,43233417,Componentes de software de reconocimiento de escritura manuscrita,Componentes del programa de reconocimiento de escritura a mano,0,80509,0 -16285,43233418,Controladores de memoria,,0,80509,0 -16286,43233419,Plataformas de multimedia,,0,80509,0 -16287,43233420,Software de conversión de texto a voz,Software de conversión de texto a voz,0,80509,0 -16288,43233421,Controladores de video,,0,80509,0 -16289,43233600,Software de equipo eléctrico,,0,80509,0 -16290,43233601,Software de controlador del motor,Programa de controlador del motor,0,80509,0 -16291,43233602,Software de monitor de energía,Software de monitor de energía,0,80509,0 -16292,43233603,Software de control lógico programable,Programa de control lógico programable,0,80509,0 -16293,44101500,Máquinas de duplicación,,0,80509,0 -16294,44101501,Fotocopiadoras,,0,80509,0 -16295,44101503,Máquinas multifuncionales,,0,80509,0 -16296,44101504,Emisores (senders) digitales,,0,80509,0 -16297,44101505,Duplicadores digitales,,0,80509,0 -16298,44101506,Máquinas de interruptor de fax,,0,80509,0 -16299,44101507,Máquina de fax de inyección de tinta,,0,80509,0 -16300,44101508,Máquina de fax láser,,0,80509,0 -16301,44101509,Máquina de fax térmica,,0,80509,0 -16302,44101510,Fax o radiofax climático,,0,80509,0 -16303,44101600,Máquinas para procesamiento de papel y accesorios,,0,80509,0 -16304,44101601,Máquinas cortadoras de papel o accesorios,,0,80509,0 -16305,44101602,Máquinas perforadoras o para unir papel,Máquinas para perforar o encuadernar papel,0,80509,0 -16306,44101603,Máquinas trituradoras de papel o accesorios,,0,80509,0 -16307,44101604,Tablas de protección de base,Tableros de protección base,0,80509,0 -16308,44101605,Máquinas para emparejar papel,Máquinas para nivelar papel,0,80509,0 -16309,44101606,Máquinas para clasificar papel,,0,80509,0 -16310,44101607,Máquinas de prensa de papel,,0,80509,0 -16311,44101700,"Accesorios para impresoras, fotocopiadoras y aparatos de fax",,0,80509,0 -16312,44101701,Opciones de color o actualizaciones,,0,80509,0 -16313,44101702,Bandejas de impresión a doble cara,Bandejas de impresión a doble cara,0,80509,0 -16314,44101703,Unidades de impresión a doble cara,Unidades de impresión dúplex,0,80509,0 -16315,44101704,Terminales de facsímil,,0,80509,0 -16316,44101705,Bandejas o alimentadores de máquinas de oficina,,0,80509,0 -16317,44101706,Unidades de fotoconductores o imágenes,,0,80509,0 -16318,44101707,Unidades de grapadoras,,0,80509,0 -16319,44101708,Filtros de ozono,,0,80509,0 -16320,44101709,Ensamblajes de réplica,,0,80509,0 -16321,44101710,Ensamblajes magnéticos de recogida,,0,80509,0 -16322,44101711,Compresor de ensamblaje,,0,80509,0 -16323,44101712,Apiladores de buzón de correo,,0,80509,0 -16324,44101713,Contadores de copias,,0,80509,0 -16325,44101714,Unidades de facsímil para máquinas de oficina,,0,80509,0 -16326,44101715,Cubiertas de platinas,,0,80509,0 -16327,44101716,Unidades de perforación de orificios,,0,80509,0 -16328,44101718,Adaptadores infrarrojos,,0,80509,0 -16329,44101719,Accesorios de copiado o escaneado,,0,80509,0 -16330,44101720,Fuentes de lenguaje,Fuentes de lenguaje,0,80509,0 -16331,44101721,Husos de medios,,0,80509,0 -16332,44101722,Actualizaciones de energía de buzón de correo multi recipientes,Actualizaciones de energía de buzón de correo multi recipientes,0,80509,0 -16333,44101723,Buzones de correo multi recipientes,,0,80509,0 -16334,44101724,Actualizaciones de multi funciones,Actualizaciones multifuncionales,0,80509,0 -16335,44101725,Gabinetes de impresora,,0,80509,0 -16336,44101726,Actualizaciones de emulación de impresoras,,0,80509,0 -16337,44101727,Puestos para impresoras,,0,80509,0 -16338,44101728,Alimentadores de rollos,,0,80509,0 -16339,44101729,Apiladores de salida,,0,80509,0 -16340,44101730,Clasificador automático de documentos,,0,80509,0 -16341,44101800,Máquinas calculadoras y accesorios,,0,80509,0 -16342,44101802,Máquinas sumadoras,,0,80509,0 -16343,44101803,Máquinas contabilizadoras,Máquinas de contabilidad,0,80509,0 -16344,44101804,Cajas registradoras,Máquinas registradoras,0,80509,0 -16345,44101805,Cintas para calculadoras,Carrete de cinta para calculadora,0,80509,0 -16346,44101806,Cintas para cajas registradoras,Carrete de cinta para máquinas registradoras,0,80509,0 -16347,44101807,Calculadoras de bolsillo,,0,80509,0 -16348,44101808,Calculadoras científicas,,0,80509,0 -16349,44101809,Calculadoras de escritorio,,0,80509,0 -16350,44101810,Calculadoras impresoras,,0,80509,0 -16351,44101900,Máquinas para endosar y escribir cheques,,0,80509,0 -16352,44101901,Máquinas de endoso de cheques,,0,80509,0 -16353,44101902,Máquinas para escribir cheques,,0,80509,0 -16354,44101903,Máquinas de actualización de chequeras o tiqueteras,,0,80509,0 -16355,44102000,Suministros para plastificado,,0,80509,0 -16356,44102001,Película de laminación,,0,80509,0 -16357,44102002,Bolsas de laminadores,,0,80509,0 -16358,44102003,Láminas de transferencia,,0,80509,0 -16359,44102004,Láminas creativas,,0,80509,0 -16360,44102300,Empaquetadoras,,0,80509,0 -16361,44102301,Máquinas de agrupación,,0,80509,0 -16362,44102302,Máquinas para envolver paquetes,,0,80509,0 -16363,44102303,Prensas de sellamiento,Prensas de sellamiento,0,80509,0 -16364,44102304,Máquinas selladoras,,0,80509,0 -16365,44102305,Tensionadores o selladoras para zunchado,Tensores o selladores de fleje y cintillo,0,80509,0 -16366,44102306,Máquinas para amarrar,Máquinas para sujetar,0,80509,0 -16367,44102307,Intercaladores,,0,80509,0 -16368,44102400,Etiquetadoras,,0,80509,0 -16369,44102402,Máquinas para fechar o numerar,,0,80509,0 -16370,44102403,Máquinas de prensa de identificación id,,0,80509,0 -16371,44102404,Máquinas para aplicar de etiquetas,Máquinas etiquetadoras,0,80509,0 -16372,44102405,Máquinas para hacer etiquetas,,0,80509,0 -16373,44102406,Equipos para rotulado,,0,80509,0 -16374,44102407,Grabadora de relieve para cinta,,0,80509,0 -16375,44102408,Sistemas de etiquetado automático,,0,80509,0 -16376,44102409,Sistemas de etiquetado semiautomáticos,,0,80509,0 -16377,44102411,Dispensadores de etiquetas,,0,80509,0 -16378,44102412,Cartuchos de etiquetas adhesivas,,0,80509,0 -16379,44102413,Kit de sistema de etiquetado de discos versátiles digitales o discos compactos cd/dvd,,0,80509,0 -16380,44102414,Estampilla auto abastecedora de tinta,,0,80509,0 -16381,44102600,Máquinas de escribir y accesorios,,0,80509,0 -16382,44102602,Máquinas de escribir,,0,80509,0 -16383,44102603,Ruedas de margarita para impresoras,,0,80509,0 -16384,44102604,Máquinas de estenotipio,,0,80509,0 -16385,44102605,Máquinas para dictado,,0,80509,0 -16386,44102606,Cinta de máquinas de escribir,,0,80509,0 -16387,44102607,Procesadores de palabras,,0,80509,0 -16388,44102608,Elementos de impresión para máquinas de escribir,"Dispositivos de impresión para máquinas de escribir, Elementos de impresión para máquinas",0,80509,0 -16389,44102609,Kits de accesorios o suministros para máquinas de escribir,,0,80509,0 -16390,44102610,Kits de inicio para máquinas de escribir,,0,80509,0 -16391,44102800,Máquinas encuadernadoras y plastificadoras,,0,80509,0 -16392,44102801,Laminadoras,,0,80509,0 -16393,44102802,Máquinas de encuadernación térmica,"Máquinas para empastado térmico, Máquinas para encuadernado térmico",0,80509,0 -16394,44102803,Máquina de encuadernación en espiral,Máquina de encuadernado en espiral,0,80509,0 -16395,44102804,Máquina de encuadernación de peinilla,Máquina de encuadernado de peine,0,80509,0 -16396,44102805,Máquina perforadora de encuadernación,Máquina perforadora para encuadernado,0,80509,0 -16397,44102806,Máquina de encuadernación de alambre,Máquina para encuadernado de alambre,0,80509,0 -16398,44102900,Accesorios de máquinas de oficina,,0,80509,0 -16399,44102901,Kits de viaje para máquinas de oficina,,0,80509,0 -16400,44102902,Accesorios de almacenamiento para máquinas de almacenamiento,Accesorios de almacenamiento para máquinas de oficina,0,80509,0 -16401,44102903,Limpiadoras de cinta,Limpiadoras de cinta,0,80509,0 -16402,44102904,Aerosol de aire comprimido,,0,80509,0 -16403,44102905,Pequeñas bolsas de papel de sales absorbentes húmedas,,0,80509,0 -16404,44102906,Kits de limpieza de computadores o equipos de oficina,"Kits de limpieza de computadores o equipos de oficina, Kits de limpieza de ordenadores o equipos de oficina",0,80509,0 -16405,44102907,Forros para equipos para protegerlos del polvo,"Cubiertas antipolvo para equipos, Fundas antipolvo para equipos",0,80509,0 -16406,44102908,Limpiadores de discos compactos o removedores de rayones,,0,80509,0 -16407,44102909,Limpiadores de unidades de discos compactos,,0,80509,0 -16408,44102910,Laminadora,,0,80509,0 -16409,44102911,Pañitos limpiadores para máquinas de oficina,Paños de limpieza para máquinas de oficina,0,80509,0 -16410,44102912,Soluciones limpiadoras para equipos de oficina,Soluciones de limpieza para equipos de oficina,0,80509,0 -16411,44102913,Destructor de discos compactos,,0,80509,0 -16412,44103000,Fusores y Accesorios,,0,80509,0 -16413,44103001,Almohadillas o filtros limpiadores de fusores,,0,80509,0 -16414,44103002,Aceite de fusores,,0,80509,0 -16415,44103003,Limpiador de fusores,,0,80509,0 -16416,44103004,Fusores,,0,80509,0 -16417,44103005,Lámparas o ensamblajes de fusores,,0,80509,0 -16418,44103100,"Suministros para impresora, fax y fotocopiadora",,0,80509,0 -16419,44103101,"Correas de impresoras, fax o fotocopiadoras","Correas de impresoras, fax o fotocopiadoras",0,80509,0 -16420,44103103,Tóner para impresoras o fax,,0,80509,0 -16421,44103104,Rollos de transferencia,,0,80509,0 -16422,44103105,Cartuchos de tinta,,0,80509,0 -16423,44103106,Barras de tinta,,0,80509,0 -16424,44103107,Suministros de limpieza de impresoras o faxes o fotocopiadoras,,0,80509,0 -16425,44103108,Reveladores para impresoras o fotocopiadoras,,0,80509,0 -16426,44103109,Tambores para impresoras o faxes o fotocopiadoras,,0,80509,0 -16427,44103110,Cabezales de impresión,,0,80509,0 -16428,44103111,Rollos de tinta,Rodillos de tinta,0,80509,0 -16429,44103112,Cinta de impresora,,0,80509,0 -16430,44103113,Kits de correctores de fase o inyección de tinta,,0,80509,0 -16431,44103114,Kits de recubrimiento de inyección de tinta,,0,80509,0 -16432,44103116,Kit para impresora,,0,80509,0 -16433,44103117,Cintas de fax,,0,80509,0 -16434,44103118,Película de transparencia,,0,80509,0 -16435,44103119,Papel de transferencia en caliente para copiadoras,Papel térmico para copiadoras,0,80509,0 -16436,44103120,Recolectores de tóner,,0,80509,0 -16437,44103121,"Guías de rollo para impresoras, faxes o fotocopiadoras",,0,80509,0 -16438,44103122,Bandas de impresión,,0,80509,0 -16439,44103123,Lápices de plotter,Plumas para plotter,0,80509,0 -16440,44103124,Cinta térmica,,0,80509,0 -16441,44103125,Kit de mantenimiento de impresoras,,0,80509,0 -16442,44103126,Tinta de máquina duplicadora digital,,0,80509,0 -16443,44103200,Máquinas y accesorios de registrar la hora para la oficina,,0,80509,0 -16444,44103201,Máquina de tarjetas de tiempo,,0,80509,0 -16445,44103202,Máquinas estampadoras de tiempo,,0,80509,0 -16446,44103203,Cinta de repuesto para máquinas de tarjetas de tiempo,,0,80509,0 -16447,44103205,Tarjetas u hojas de tiempo,,0,80509,0 -16448,44103206,Máquina de huellas para control de tiempo y de acceso,Biométrico para control de tiempo y acceso,0,80509,0 -16449,44103500,Suministros de máquinas de encuadernar,,0,80509,0 -16450,44103502,Tapas de encuadernación,"Cubiertas de encuadernación, Pastas de encuadernación",0,80509,0 -16451,44103503,Lomos o cierres de encuadernación,Lomos o cierres de encuadernación,0,80509,0 -16452,44103504,Alambres o espirales de encuadernación,Bobinas de encuadernación o bucles de alambre,0,80509,0 -16453,44103505,Peinillas o tiras de encuadernación,Peinillas o tiras de encuadernación,0,80509,0 -16454,44103506,Cinta de encuadernación,Cinta de encuadernación,0,80509,0 -16455,44103507,Kits de encuadernación,,0,80509,0 -16456,44103508,Punzones de tinta de encuadernación,,0,80509,0 -16457,44103600,Equipo de eliminación de casetes y accesorios,,0,80509,0 -16458,44103601,Trituradores de casetes o cintas,,0,80509,0 -16459,44111500,Agendas y accesorios,,0,80509,0 -16460,44111501,Sujetadores o dispensadores de mensajes,,0,80509,0 -16461,44111502,Organizadores de cajones de escritorio,,0,80509,0 -16462,44111503,Organizadores o bandejas para el escritorio,,0,80509,0 -16463,44111506,Sujetadores o dispensadores de papeles o tacos,,0,80509,0 -16464,44111507,Sujeta libros,,0,80509,0 -16465,44111509,Sujetadores de esferos o lápices,"Lapiceras, Portalápices",0,80509,0 -16466,44111510,Organizadores o accesorios de colgar,,0,80509,0 -16467,44111511,Sistemas de despliegue o sus accesorios,,0,80509,0 -16468,44111512,Estante de literatura,"Anaquel de literatura, Estantería de literatura, Librero",0,80509,0 -16469,44111513,Soportes para diarios o calendarios,,0,80509,0 -16470,44111514,Estantes u organizadores para estampillas,Estantes u organizadores para estampillas,0,80509,0 -16471,44111515,Cajas u organizadores de almacenamiento de archivos,Caja de almacenamiento de libros,0,80509,0 -16472,44111516,Organizadores personales,Agendas,0,80509,0 -16473,44111517,Puestos de estudio,"Mueble de estudio, Mueble de trabajo, Puesto de trabajo",0,80509,0 -16474,44111518,Sujetadores de tarjetas de presentación,,0,80509,0 -16475,44111519,Repisas de distribución de documentos,,0,80509,0 -16476,44111520,Almohadillas o protectores de superficie,,0,80509,0 -16477,44111521,Sujetadores de copias,"Broche sujetador de archivo, Clip, Clip sujetador de papel tipo mariposa",0,80509,0 -16478,44111522,Sujetadores de libros,,0,80509,0 -16479,44111600,Suministros para el manejo de efectivo,,0,80509,0 -16480,44111601,Bolsas para billetes o billeteras,,0,80509,0 -16481,44111603,Sorteadores de monedas,Sorteadores de monedas,0,80509,0 -16482,44111604,Envoltorios para monedas o cintas para billetes,,0,80509,0 -16483,44111605,Cajas para efectivo o tiquetes,Cajas para efectivo o Maquinas registradoras,0,80509,0 -16484,44111606,Bandejas de cajas de efectivo,,0,80509,0 -16485,44111607,Archivos de cheques,Chequera,0,80509,0 -16486,44111608,Bancos de monedas,,0,80509,0 -16487,44111609,Detectores de billetes falsos o suministros,,0,80509,0 -16488,44111610,Bandejas de monedas,,0,80509,0 -16489,44111611,Clips para billetes,Pinza para billetes,0,80509,0 -16490,44111612,Sellos para bolsas de monedas,Tira plástica para bolsa de monedas,0,80509,0 -16491,44111613,Sujetadores de tarjetas de crédito,Sujetadores de tarjetas de crédito,0,80509,0 -16492,44111614,Estantes de correas de billetes,,0,80509,0 -16493,44111615,Bolsas de depósito,,0,80509,0 -16494,44111616,Separadores de cheques,,0,80509,0 -16495,44111617,Máquinas de inspección de monedas,,0,80509,0 -16496,44111618,Tarjetas de teléfono público o tarjetas de teléfono,,0,80509,0 -16497,44111800,Suministros de dibujo,,0,80509,0 -16498,44111801,Ayudas para esténciles o textos,,0,80509,0 -16499,44111802,Película de dibujo,Película de dibujo,0,80509,0 -16500,44111803,Compases,,0,80509,0 -16501,44111804,Papeles de dibujo,,0,80509,0 -16502,44111805,Curvas,,0,80509,0 -16503,44111806,Transportadores,,0,80509,0 -16504,44111807,Escalas,,0,80509,0 -16505,44111808,Reglas t,,0,80509,0 -16506,44111809,Plantillas,,0,80509,0 -16507,44111810,Triángulos,,0,80509,0 -16508,44111812,Kits o sets de dibujo,,0,80509,0 -16509,44111813,Puntos o cintas de dibujo,,0,80509,0 -16510,44111814,Forros de protección para superficies de trabajo,,0,80509,0 -16511,44111815,Forros para mesas de dibujo,,0,80509,0 -16512,44111816,Máquina de dibujo multiplexora,,0,80509,0 -16513,44111817,Divisor de dibujo,,0,80509,0 -16514,44111818,Eidógrafo,Pantógrafo,0,80509,0 -16515,44111900,Tableros,,0,80509,0 -16516,44111901,Tableros de planeación o accesorios,,0,80509,0 -16517,44111902,Tableros electrónicos de copia o accesorios,,0,80509,0 -16518,44111903,Caballetes o accesorios,,0,80509,0 -16519,44111904,Tableros de cartas o accesorios,,0,80509,0 -16520,44111905,Tableros de borrado en seco o accesorios,,0,80509,0 -16521,44111906,Tableros de tiza o accesorios,Pizarrón de gis,0,80509,0 -16522,44111907,Tableros de noticias o accesorios,,0,80509,0 -16523,44111908,Tableros magnéticos o accesorios,,0,80509,0 -16524,44111909,Kits o accesorios para limpieza de tableros,,0,80509,0 -16525,44111910,Rieles o sujetadores para colgar tableros,,0,80509,0 -16526,44111911,Tableros blancos interactivos o accesorios,Pizarrones blancos interactivos o accesorios,0,80509,0 -16527,44111912,Borradores para tableros blancos,Borradores para pizarrones blancos,0,80509,0 -16528,44111913,Borradores de pila para tableros blancos,Borradores de pila para tableros blancos,0,80509,0 -16529,44111914,Tabla de soporte para escribir,,0,80509,0 -16530,44112000,Sistemas de planificación,,0,80509,0 -16531,44112001,Libretas de direcciones o repuestos,,0,80509,0 -16532,44112002,Calendarios,,0,80509,0 -16533,44112005,Libretas de citas o repuestos,,0,80509,0 -16534,44112006,Diarios o repuestos,,0,80509,0 -16535,44112007,Cajas de sugerencias,,0,80509,0 -16536,44112008,Planeadores de pared o repuestos,,0,80509,0 -16537,44121500,Suministros de correo,,0,80509,0 -16538,44121501,Tubos de sobres,Tubos de sobres,0,80509,0 -16539,44121503,Sobres,,0,80509,0 -16540,44121504,Sobres de ventana,,0,80509,0 -16541,44121505,Sobres especiales,,0,80509,0 -16542,44121506,Sobres estándar,,0,80509,0 -16543,44121507,Sobres de catálogos o de gancho,,0,80509,0 -16544,44121508,Repositorios para mensajes,,0,80509,0 -16545,44121509,Bolsas para correo,,0,80509,0 -16546,44121510,Sellos para correo,,0,80509,0 -16547,44121511,Cajas para correo,,0,80509,0 -16548,44121512,Tapas de tubos para correo,,0,80509,0 -16549,44121513,Estampillas postales,,0,80509,0 -16550,44121600,Suministros de escritorio,,0,80509,0 -16551,44121604,Estampillas,,0,80509,0 -16552,44121605,Dispensadores de cinta,,0,80509,0 -16553,44121611,Punzones para papel u ojales,Perforadoras,0,80509,0 -16554,44121612,Cortadoras de papel o repuestos,,0,80509,0 -16555,44121613,Removedores de grapas (saca ganchos),Quita grapas,0,80509,0 -16556,44121614,Campanas de llamada,Timbre de llamada,0,80509,0 -16557,44121615,Grapadoras,,0,80509,0 -16558,44121617,Abridor manual de cartas,Abrecartas,0,80509,0 -16559,44121618,Tijeras,,0,80509,0 -16560,44121619,Tajalápices manuales,Sacapuntas,0,80509,0 -16561,44121620,Protector de plástico para dedos,Dedal de plástico,0,80509,0 -16562,44121621,Almohadillas para escritorio o sus accesorios,,0,80509,0 -16563,44121622,Humidificadores,Cera de dedos para contar,0,80509,0 -16564,44121623,Abridor de cartas mecánico,Abridor de cartas mecánico,0,80509,0 -16565,44121624,Herramientas de grabado en relieve,Puntas de grabado,0,80509,0 -16566,44121625,Pisa papeles,,0,80509,0 -16567,44121626,Removedor de adhesivo,,0,80509,0 -16568,44121627,Marcadores de libros,Separadores para libros,0,80509,0 -16569,44121628,Contenedores o dispensadores de clips,Portaclips con imán,0,80509,0 -16570,44121630,Kit de cosedora,Kit de cosedora,0,80509,0 -16571,44121631,Dispensadores de goma o repuestos,Dispensadores de rollos adhesivos y repuestos,0,80509,0 -16572,44121632,Afilador de tijeras,,0,80509,0 -16573,44121633,Dispensadores de estampillas postales,,0,80509,0 -16574,44121634,Rollos adhesivos,,0,80509,0 -16575,44121635,Husos para cinta adhesiva,Carrete para cinta adhesiva,0,80509,0 -16576,44121636,Tajalápices eléctricos,Sacapuntas eléctrico,0,80509,0 -16577,44121700,Instrumentos de escritura,,0,80509,0 -16578,44121701,Bolígrafos,,0,80509,0 -16579,44121702,Sets de esferos o lápices,Sets de bolígrafos o lápices,0,80509,0 -16580,44121703,Estilógrafos,,0,80509,0 -16581,44121704,Esferos de punta redonda,Bolígrafos de punta redonda,0,80509,0 -16582,44121705,Lápices mecánicos,Lapiceros,0,80509,0 -16583,44121706,Lápices de madera,,0,80509,0 -16584,44121707,Lápices de colores,,0,80509,0 -16585,44121708,Marcadores,,0,80509,0 -16586,44121709,Crayolas,,0,80509,0 -16587,44121710,Tiza para escribir o accesorios,Gis para escribir o accesorios,0,80509,0 -16588,44121711,Rotuladores,Plumines,0,80509,0 -16589,44121712,Repuestos de marcadores,,0,80509,0 -16590,44121713,Plumas de estilógrafos,,0,80509,0 -16591,44121714,Asideras para lápices o esferos,Goma ergonómica para lápices,0,80509,0 -16592,44121715,Combinaciones de esfero y lápiz,Bolígrafo multifunción con lápiz,0,80509,0 -16593,44121716,Resaltadores,Marcatextos,0,80509,0 -16594,44121717,Combinación de esfero y resaltador,Combinación de esfero y resaltador,0,80509,0 -16595,44121718,Sets de esferos asegurados,Sets de esferos asegurados,0,80509,0 -16596,44121719,Estilógrafos desechables,,0,80509,0 -16597,44121720,Esferos multi función,Bolígrafos multifunción,0,80509,0 -16598,44121721,Esfero de tinta borrable,Bolígrafo de tinta borrable,0,80509,0 -16599,44121800,Medios de corrección,,0,80509,0 -16600,44121801,Película o cinta de corrección,Corrector de cinta,0,80509,0 -16601,44121802,Fluido de corrección,Corrector líquido,0,80509,0 -16602,44121804,Borradores,Goma para borrar,0,80509,0 -16603,44121805,Esferos de corrección,Lápiz corrector,0,80509,0 -16604,44121806,Repuestos para esferos de corrección,Repuestos para lápiz corrector,0,80509,0 -16605,44121807,Repuestos para borradores,,0,80509,0 -16606,44121808,Borradores eléctricos,Borradores eléctricos,0,80509,0 -16607,44121809,Sujeta borrador,Sujeta borrador,0,80509,0 -16608,44121900,Repuestos de tinta y minas de lápices,,0,80509,0 -16609,44121902,Repuestos de minas,Puntillas de grafito,0,80509,0 -16610,44121904,Repuestos de tinta,,0,80509,0 -16611,44121905,Almohadillas de tinta o estampillas,,0,80509,0 -16612,44121906,Cartuchos para rotuladores,,0,80509,0 -16613,44121907,Repuestos de tinta para estilógrafos,,0,80509,0 -16614,44121908,Repuestos de tinta para bolígrafos,,0,80509,0 -16615,44122000,"Carpetas de archivo, carpetas y separadores",,0,80509,0 -16616,44122001,Archivos para tarjetas de índex,Carpetas para archivos con índice,0,80509,0 -16617,44122002,Protectores de hojas,,0,80509,0 -16618,44122003,Carpetas,,0,80509,0 -16619,44122005,Cubiertas para revistas o libros,,0,80509,0 -16620,44122008,Índices de fichas,,0,80509,0 -16621,44122009,Archivos para tarjetas rotativas o de presentación,,0,80509,0 -16622,44122010,Separadores,Separadores de hojas,0,80509,0 -16623,44122011,Folders,,0,80509,0 -16624,44122012,Portapapeles,,0,80509,0 -16625,44122013,Cubiertas para informes,,0,80509,0 -16626,44122014,Levantadores de hojas,,0,80509,0 -16627,44122015,Respaldos para archivos,,0,80509,0 -16628,44122016,Sujetador de documentos,Tabla sujetadora de documentos,0,80509,0 -16629,44122017,Folders de colgar o accesorios,Bastidor para carpetas colgantes de archivo,0,80509,0 -16630,44122018,Insertos o pestañas para archivos,,0,80509,0 -16631,44122019,Bolsillos para archivos o accesorios,Protector de hojas para carpeta,0,80509,0 -16632,44122020,Bolsillos para tarjetas,Protector de tarjetas,0,80509,0 -16633,44122021,Álbumes de estampillas,,0,80509,0 -16634,44122022,Accesorios de carpetas de folders,,0,80509,0 -16635,44122023,Tabletas gráficas para arquitectura,,0,80509,0 -16636,44122024,Manijas de carpetas,,0,80509,0 -16637,44122025,Bolsillos de carpetas o accesorios,,0,80509,0 -16638,44122026,Garras para papel,,0,80509,0 -16639,44122027,Folders de archivo expandibles,,0,80509,0 -16640,44122028,Canales para montar carpetas,,0,80509,0 -16641,44122029,Folders de clasificación,,0,80509,0 -16642,44122030,Kit de folders,Paquete de folders,0,80509,0 -16643,44122031,Marco de rieles laterales para colgar folders,,0,80509,0 -16644,44122032,Folders de conferencias,,0,80509,0 -16645,44122033,Archivo de acordeón para folders,,0,80509,0 -16646,44122034,Separador de páginas de libros,,0,80509,0 -16647,44122100,Suministros de sujeción,,0,80509,0 -16648,44122101,Cauchos,Ligas,0,80509,0 -16649,44122103,Sujetadores de cierre,,0,80509,0 -16650,44122104,Clips para papel,,0,80509,0 -16651,44122105,Clips para carpetas o bulldog,Broches para hojas perforadas,0,80509,0 -16652,44122106,Alfileres o taches,,0,80509,0 -16653,44122107,Grapas,,0,80509,0 -16654,44122109,Sujetador de aro y bucle,,0,80509,0 -16655,44122110,Monturas adhesivas,,0,80509,0 -16656,44122111,Refuerzos para orificios,Refuerzos adhesivos,0,80509,0 -16657,44122112,Sujetadores de cabeza redonda,,0,80509,0 -16658,44122113,Sujetadores de etiquetas,,0,80509,0 -16659,44122114,Postes roscados,,0,80509,0 -16660,44122115,Esquinas adhesivas,,0,80509,0 -16661,44122116,Clips de bolsas,,0,80509,0 -16662,44122117,Anillos para libros,,0,80509,0 -16663,44122118,Sujetadores de pinza,,0,80509,0 -16664,44122119,Sujetadores auto adhesivos,,0,80509,0 -16665,44122120,Postes de carpetas,,0,80509,0 -16666,44122121,Clips de pared o tablero,,0,80509,0 -16667,44122122,Tachuelas magnéticas,,0,80509,0 -16668,45101500,Maquinaria y equipo de imprenta,,0,80509,0 -16669,45101501,Impresoras heliográficas,,0,80509,0 -16670,45101502,Prensas de impresión de offset,,0,80509,0 -16671,45101503,Equipos de impresión tipográfica,,0,80509,0 -16672,45101504,Equipos litográficos,,0,80509,0 -16673,45101505,Máquinas impresoras de fotograbados,,0,80509,0 -16674,45101506,Máquinas de serigrafía,,0,80509,0 -16675,45101507,Prensas impresoras,,0,80509,0 -16676,45101508,Máquinas perforadoras,,0,80509,0 -16677,45101509,Impresora rotativa ultravioleta uv,,0,80509,0 -16678,45101510,Impresora flexo gráfica,,0,80509,0 -16679,45101511,Impresora de inyección de tinta para aplicaciones de impresión comercial,,0,80509,0 -16680,45101512,Impresora de transferencia térmica para aplicaciones de impresión comercial,,0,80509,0 -16681,45101513,Impresora de estampado en caliente,,0,80509,0 -16682,45101514,Impresora de almohadillas,,0,80509,0 -16683,45101515,Impresora básica,,0,80509,0 -16684,45101516,Clichés de impresión de almohadillas,,0,80509,0 -16685,45101517,Prensa de pruebas de offset,,0,80509,0 -16686,45101518,Prensa de platinas de impresión,,0,80509,0 -16687,45101600,Accesorios para máquinas de imprenta,,0,80509,0 -16688,45101602,Equipos de cuarto oscuro de offset,,0,80509,0 -16689,45101603,Consumibles de impresión de offset,,0,80509,0 -16690,45101604,Procesadores de platinas de impresión de offset,,0,80509,0 -16691,45101606,Procesadores de película de offset,,0,80509,0 -16692,45101607,Lámparas de arco de serigrafía,,0,80509,0 -16693,45101608,Pantallas de serigrafía,,0,80509,0 -16694,45101609,Bastidores de impresión de serigrafía,,0,80509,0 -16695,45101610,Armazones de impresión al vacío de serigrafía,,0,80509,0 -16696,45101611,Escobillas de serigrafía,,0,80509,0 -16697,45101612,Plecas (cuchillas doctor),,0,80509,0 -16698,45101700,Accesorios de imprenta,,0,80509,0 -16699,45101701,Ensambladoras de impresión,,0,80509,0 -16700,45101702,Guillotinas de impresión,,0,80509,0 -16701,45101703,Intercaladores o desintercaladores de impresión,,0,80509,0 -16702,45101704,Cortadoras de impresión,,0,80509,0 -16703,45101705,Bordeadoras de impresión,,0,80509,0 -16704,45101706,Punzones de impresión,,0,80509,0 -16705,45101707,Platinas de impresión,,0,80509,0 -16706,45101708,Leznas de impresión,,0,80509,0 -16707,45101709,Película de impresión,,0,80509,0 -16708,45101900,Equipo y accesorios de laboratorio de imprenta,,0,80509,0 -16709,45101901,Máquinas de anillado de libros,,0,80509,0 -16710,45101902,Clisadores,,0,80509,0 -16711,45101903,Máquinas de perforación de papel,,0,80509,0 -16712,45101904,Alargadores,,0,80509,0 -16713,45101905,Tableros de dibujo o retoque,,0,80509,0 -16714,45111500,"Atriles, sistemas de sonido y accesorios",,0,80509,0 -16715,45111501,Atriles autónomos,,0,80509,0 -16716,45111502,Atriles de mesa,,0,80509,0 -16717,45111503,Mazos o bloques de sonido,,0,80509,0 -16718,45111504,Componentes de luz o energía o datos para atriles,,0,80509,0 -16719,45111600,Proyectores y suministros,,0,80509,0 -16720,45111601,Señaladores,Señalador laser y punteros,0,80509,0 -16721,45111602,Lámparas de proyección,,0,80509,0 -16722,45111603,Pantallas o desplegadores para proyección,,0,80509,0 -16723,45111604,Proyectores de filminas,,0,80509,0 -16724,45111605,Equipos de transparencias o suministros,,0,80509,0 -16725,45111606,Paneles de proyección de despliegue de cristal líquido,,0,80509,0 -16726,45111607,Proyectores de techo,,0,80509,0 -16727,45111608,Proyectores de películas,,0,80509,0 -16728,45111609,Proyectores multimedia,,0,80509,0 -16729,45111610,Epidiascopios,Proyector de opacos,0,80509,0 -16730,45111612,Controles de disolución,,0,80509,0 -16731,45111613,Proyectores de tubo de rayo catódico,,0,80509,0 -16732,45111614,Proyectores de despliegue de cristal líquido,,0,80509,0 -16733,45111615,Lentes de proyección,,0,80509,0 -16734,45111616,Proyectores de video,,0,80509,0 -16735,45111617,Proyector de techo o carritos de video,,0,80509,0 -16736,45111618,Cajas de luces de presentaciones,,0,80509,0 -16737,45111619,Bobina para películas,,0,80509,0 -16738,45111620,Copiadora de películas de filminas,Copiadora de películas de filminas,0,80509,0 -16739,45111700,"Equipo de composición y presentación de sonido, hardware y controladores",,0,80509,0 -16740,45111701,Dispositivos de audición asistida,,0,80509,0 -16741,45111702,Cajas de conectores de audio,,0,80509,0 -16742,45111703,Centros de escucha,Estéreo modular,0,80509,0 -16743,45111704,Consolas de mezclado de audio,,0,80509,0 -16744,45111705,Sistemas de comunicación pública,,0,80509,0 -16745,45111706,Combinación de mezclador y amplificador de audio,,0,80509,0 -16746,45111707,Conector y caja de escenario de equipo de audio,Cajas de conexión para empotrar en escenarios,0,80509,0 -16747,45111708,Controlador de dominio de transmisión,,0,80509,0 -16748,45111709,Amplificador remoto,,0,80509,0 -16749,45111710,Unidad de timbre y sirena,,0,80509,0 -16750,45111711,Deflector (bafle) de micrófono,,0,80509,0 -16751,45111712,Duplicador de cintas,Duplicadores de casete,0,80509,0 -16752,45111713,Amplificador de distribución de audio,,0,80509,0 -16753,45111714,Convertidor de audio de análogo a digital ad,,0,80509,0 -16754,45111715,Convertidor de audio de digital a análogo da,,0,80509,0 -16755,45111716,Estación de audio digital daw,,0,80509,0 -16756,45111717,Reverberador,,0,80509,0 -16757,45111718,Unidad de retardo,,0,80509,0 -16758,45111719,Monitor de audio,,0,80509,0 -16759,45111720,Analizador de espectro de audio,,0,80509,0 -16760,45111721,Grabadora de cintas bobina a bobina,,0,80509,0 -16761,45111722,Lector de casetes,,0,80509,0 -16762,45111723,Grabadora de discos duros,,0,80509,0 -16763,45111724,Cabezas magnéticas,,0,80509,0 -16764,45111800,"Equipo de presentación de vídeo y de mezcla de vídeo y sonido, hardware y controladores",,0,80509,0 -16765,45111801,Sistemas de control de medios,,0,80509,0 -16766,45111802,Soportes para televisiones,,0,80509,0 -16767,45111803,Convertidores de barrido (scan),,0,80509,0 -16768,45111804,Duplicadores de línea,,0,80509,0 -16769,45111805,Editores de video,,0,80509,0 -16770,45111806,Sistemas de aprendizaje a distancia,,0,80509,0 -16771,45111807,Interfaces,,0,80509,0 -16772,45111808,Controles de iluminación,,0,80509,0 -16773,45111809,Accesorios de soporte de televisiones,,0,80509,0 -16774,45111810,Presentadores visuales,,0,80509,0 -16775,45111811,Monitor de precisión de video,,0,80509,0 -16776,45111812,Dispositivo de despliegue de información visual,,0,80509,0 -16777,45111813,Operador de introducción de datos corriente abajo,,0,80509,0 -16778,45111814,Equipo de efectos de video digital dve,,0,80509,0 -16779,45111815,Consola de audio video,,0,80509,0 -16780,45111816,Generador de logos,,0,80509,0 -16781,45111817,Generador de caracteres,,0,80509,0 -16782,45111818,Reducidor de ruido de videos,,0,80509,0 -16783,45111819,Mezclador de videos,,0,80509,0 -16784,45111820,Amplificador de procesamiento de videos,,0,80509,0 -16785,45111821,Generador de sincronización,,0,80509,0 -16786,45111822,Convertidor de tasa de aspecto de televisión,,0,80509,0 -16787,45111823,Convertidor de estándares de video,,0,80509,0 -16788,45111824,Convertidor de sincronización,,0,80509,0 -16789,45111825,Sincronizador de marco,,0,80509,0 -16790,45111826,Codificador de grupo de expertos de películas animadas mpeg,,0,80509,0 -16791,45111827,Verificador de video cintas,,0,80509,0 -16792,45111828,Indicador (prompter),,0,80509,0 -16793,45111829,Cámara de proceso,,0,80509,0 -16794,45111900,"Equipos, hardware y controladores para sistemas de teleconferencia y videoconferencia",,0,80509,0 -16795,45111901,Sistemas de audio conferencias,,0,80509,0 -16796,45111902,Sistemas de video conferencias,,0,80509,0 -16797,45121500,Cámaras,,0,80509,0 -16798,45121501,Cámaras fijas,,0,80509,0 -16799,45121502,Cámaras de impresión instantánea,,0,80509,0 -16800,45121503,Cámaras desechables,,0,80509,0 -16801,45121504,Cámaras digitales,,0,80509,0 -16802,45121505,Cámaras cinematográficas,,0,80509,0 -16803,45121506,Cámaras de video conferencia,,0,80509,0 -16804,45121510,Cámaras aéreas,,0,80509,0 -16805,45121511,Cámaras de alta velocidad,,0,80509,0 -16806,45121512,Cámaras para debajo del agua,,0,80509,0 -16807,45121513,Cámaras de offset,,0,80509,0 -16808,45121514,Cámara fotocopiadora,,0,80509,0 -16809,45121515,Cámaras grabadoras o video cámaras manuales,,0,80509,0 -16810,45121516,Cámaras grabadoras o video cámaras digitales,,0,80509,0 -16811,45121517,Cámaras para documentos,,0,80509,0 -16812,45121518,Kits de cámaras,,0,80509,0 -16813,45121519,Cámaras de baja luz,,0,80509,0 -16814,45121520,Cámaras de web,,0,80509,0 -16815,45121521,Cámaras de inspección,,0,80509,0 -16816,45121522,Cámaras infrarrojas,,0,80509,0 -16817,45121523,Cámaras astronómicas,,0,80509,0 -16818,45121600,Accesorios para cámaras,,0,80509,0 -16819,45121601,Flashes o iluminación para cámaras,,0,80509,0 -16820,45121602,Trípodes para cámaras,Tripiés para cámara,0,80509,0 -16821,45121603,Lentes para cámaras,,0,80509,0 -16822,45121604,Oclusores para cámaras,,0,80509,0 -16823,45121605,Marcos de pantalla,,0,80509,0 -16824,45121606,Arneses para cámaras,,0,80509,0 -16825,45121607,Bloques o sujetadores para cámaras,,0,80509,0 -16826,45121608,Ensamblajes para cámaras,,0,80509,0 -16827,45121609,Abrazaderas para cámaras,,0,80509,0 -16828,45121610,Cables para cámaras,,0,80509,0 -16829,45121611,Cubiertas para lentes,,0,80509,0 -16830,45121612,Mesas para cámaras,,0,80509,0 -16831,45121613,Contenedores o forros para cámaras,"Estuches para cámaras, Fundas para cámaras",0,80509,0 -16832,45121614,Kits de reacondicionamiento,,0,80509,0 -16833,45121615,Anillos para cámaras,,0,80509,0 -16834,45121616,Cabezales panorámicos,,0,80509,0 -16835,45121617,Bolsas para cámaras,"Estuche para cámaras, Mochilas para cámaras",0,80509,0 -16836,45121618,Adaptadores de lentes para cámaras,,0,80509,0 -16837,45121619,Billeteras para “picture card”,,0,80509,0 -16838,45121620,Adaptadores de electricidad para cámaras,,0,80509,0 -16839,45121621,Adaptadores para “picture card”,,0,80509,0 -16840,45121622,Limpiadores para lentes de cámara,,0,80509,0 -16841,45121623,Controladores de cámara,,0,80509,0 -16842,45121624,Reflector de luz para fotografía,,0,80509,0 -16843,45121625,Disparador,,0,80509,0 -16844,45121626,Respaldo de cámara digital (dispositivo que permite editar e imprimir),,0,80509,0 -16845,45121627,Difusor de flash de cámara,,0,80509,0 -16846,45121628,Filtro de lente de cámara,,0,80509,0 -16847,45121629,Motor de arrastre de cámara,,0,80509,0 -16848,45121630,Reflector de parasol de cámara,,0,80509,0 -16849,45121700,Equipo de procesamiento fotográfico,,0,80509,0 -16850,45121701,Secadores de película,,0,80509,0 -16851,45121702,Lavadores de película,,0,80509,0 -16852,45121703,Cortadores de película,,0,80509,0 -16853,45121704,Editores de película,,0,80509,0 -16854,45121705,Alargadores fotográficos,,0,80509,0 -16855,45121706,Cortadoras o bordeadoras de fotografías,,0,80509,0 -16856,45121707,Secadora de impresión de fotografías,,0,80509,0 -16857,45121708,Lavadora de impresión de fotografías,,0,80509,0 -16858,45121709,Procesador de película automático,,0,80509,0 -16859,45121710,Tambor de hojas de película,,0,80509,0 -16860,45121711,Controlador de color,,0,80509,0 -16861,45121712,Escritorio de edición de película,,0,80509,0 -16862,45121713,Máquina de inspección de película,,0,80509,0 -16863,45121714,Lector de película,,0,80509,0 -16864,45121715,Colgador de película,,0,80509,0 -16865,45121716,Impresora de contacto,,0,80509,0 -16866,45121717,Sistema de platinas de película,,0,80509,0 -16867,45121718,Densitómetro de película,,0,80509,0 -16868,45121719,Kiosco de impresión de fotografías digitales,,0,80509,0 -16869,45121720,Cargador de película,,0,80509,0 -16870,45131500,Película para cámara fotográfica,,0,80509,0 -16871,45131501,Película de color,,0,80509,0 -16872,45131502,Película de blanco y negro,,0,80509,0 -16873,45131503,Película de fotografía instantánea,,0,80509,0 -16874,45131505,Película de rayos x,,0,80509,0 -16875,45131506,Película de filminas,Película de filminas,0,80509,0 -16876,45131507,Micro filmado procesado,,0,80509,0 -16877,45131508,Película de negativos,,0,80509,0 -16878,45131509,Película reconocedora de color,,0,80509,0 -16879,45131510,Tinte fotográfico,,0,80509,0 -16880,45131700,Almacenamiento para soportes audiovisuales,,0,80509,0 -16881,45131701,Bandejas u organizadores para filminas,Organizador de diapositivas,0,80509,0 -16882,45141500,Productos químicos para elaboración fotográfica,,0,80509,0 -16883,45141501,Solución reveladora,,0,80509,0 -16884,45141502,Fijadores,Solución fijadora para fotografías,0,80509,0 -16885,45141503,Kit de procesamiento de revelado de fotografías,,0,80509,0 -16886,45141504,Solución de optimización y corrección de fotografías,,0,80509,0 -16887,46101500,Armas de fuego,,1,80509,0 -16888,46101501,Ametralladoras,"Escopeta, Metralleta, Subfusil",1,80509,0 -16889,46101502,Escopetas para la policía p seguridad,Fusil,1,80509,0 -16890,46101503,Rifles militares,,1,80509,0 -16891,46101504,Pistolas,,1,80509,0 -16892,46101505,Rifles de aire o pistolas de aire,"Rifles de municiones, Rifles de salva",1,80509,0 -16893,46101506,Partes de revólveres o pistolas,,1,80509,0 -16894,46101600,Munición,,1,80509,0 -16895,46101601,Municiones de defensa u orden público,,1,80509,0 -16896,46101800,Accesorios de armas y municiones,,1,80509,0 -16897,46101801,Estuches para revólveres,"Estuches para pistolas, Fundas para armas de fuego","0,1",80509,0 -16898,46101802,Cinturón para cartuchos,"Canana para cartuchos, Carrillera para cartuchos","0,1",80509,0 -16899,46111700,Sensores de rayos infrarrojos (IR),,0,80509,0 -16900,46111701,Enfriadores infrarrojos ir,,0,80509,0 -16901,46111702,Detectores infrarrojos ir,Sensores infrarrojos ir,0,80509,0 -16902,46111703,Iluminadores infrarrojos ir,,0,80509,0 -16903,46111704,Receptores infrarrojos ir,Recibidores infrarrojos ir,0,80509,0 -16904,46111705,Telescopios infrarrojos ir,Telescopios infrarrojos ir,0,80509,0 -16905,46151500,Equipo de control de masas,,0,80509,0 -16906,46151501,Barricadas,"Muros, Parapetos",0,80509,0 -16907,46151502,Cascos anti motines,,0,80509,0 -16908,46151503,Escudos anti motines,Protecciones antimotines,0,80509,0 -16909,46151504,Armadura para el cuerpo,Vestidura para el cuerpo,0,80509,0 -16910,46151505,Barreras,,0,80509,0 -16911,46151506,Bastones anti motines,"Cachavas anti motines, Garrotes anti motines, Macanas anti",0,80509,0 -16912,46151507,Sistema de control de filas,Sistema de manejo de filas,0,80509,0 -16913,46151600,Equipo de seguridad y control,,0,80509,0 -16914,46151601,Esposas,"Esposas, Grilletes",0,80509,0 -16915,46151602,Cachiporras,"Cachiporras, Garrotes",0,80509,0 -16916,46151604,Analizadores de alcohol,"Alcoholímetro, Alcohómetro",0,80509,0 -16917,46151605,Detectores de armas o explosivos y suministros,,0,80509,0 -16918,46151606,Kits de pruebas de narcóticos,"Kits de pruebas de drogas, Kits de pruebas de sedantes",0,80509,0 -16919,46151607,Cabos de amarre de seguridad,"Aparejos de amarre de seguridad, Destorcedores de amarre de seguridad",0,80509,0 -16920,46151608,Dispositivos de protección contra bombas y suministros,Dispositivos y suministros de protección contra bombas,0,80509,0 -16921,46151609,Discriminador de documentos de identificación,Discriminador de tarjeta de identificación,0,80509,0 -16922,46151700,Equipo forense y accesorios y suministros,,0,80509,0 -16923,46151702,Aplicadores o cepillos de huellas dactilares,,0,80509,0 -16924,46151703,Tinta de huellas dactilares,,0,80509,0 -16925,46151704,Removedores de tinta de huellas dactilares,,0,80509,0 -16926,46151705,Rodillos de tinta para huellas dactilares o de palma de la mano,,0,80509,0 -16927,46151706,Kit de impresión latente de huellas dactilares,,0,80509,0 -16928,46151707,Levantadores de huellas dactilares,,0,80509,0 -16929,46151708,Magnificadores para uso forense,,0,80509,0 -16930,46151709,Esferos de marcado de huellas dactilares,Marcadores de huellas dactilares,0,80509,0 -16931,46151710,Polvos de huellas dactilares,,0,80509,0 -16932,46151711,Levantadores de huellas plantares,,0,80509,0 -16933,46151712,Estaciones de trabajo químico para uso forense,,0,80509,0 -16934,46151713,Químicos de impresiones latentes para uso forense,,0,80509,0 -16935,46151714,Gabinetes de secado de evidencia,,0,80509,0 -16936,46151715,Equipo de huellas dactilares,,0,80509,0 -16937,46151716,Detector de mentiras,,0,80509,0 -16938,46151900,Equipo y accesorios y suministros para el control químico biológico,,0,80509,0 -16939,46151901,Remolque de descontaminación biológica química,,0,80509,0 -16940,46161500,Control de tráfico,,0,80509,0 -16941,46161501,Sistemas de señalización para aeropuertos,Sistemas de señalización para aeropuertos,0,80509,0 -16942,46161502,Sistemas de señalización para ferrocarriles,Sistemas de señalización para ferrocarriles,0,80509,0 -16943,46161503,Sistemas de señalización marinos,Sistemas de señalización marítima,0,80509,0 -16944,46161504,Señales de tráfico,Señalamientos de tránsito vehicular,0,80509,0 -16945,46161505,Medidores de estacionamiento,Parquímetros,0,80509,0 -16946,46161506,Máquinas para derretir nieve o hielo,,0,80509,0 -16947,46161507,Cintas o cadenas de barrera,,0,80509,0 -16948,46161508,Conos o delineadores de tráfico,,0,80509,0 -16949,46161509,Paradas de velocidad,,0,80509,0 -16950,46161510,Sistemas de barrera para puertas,Sistemas de barrera para puertas,0,80509,0 -16951,46161511,Balizas de tráfico,"Faro para control de tráfico, Semáforos",0,80509,0 -16952,46161512,Cadenas de tráfico,,0,80509,0 -16953,46161513,Cerca de seguridad de tráfico,"Barrera de seguridad de tráfico, Malla de seguridad de tráfico",0,80509,0 -16954,46161514,Montantes de carretera cats eye,Montantes ojo de gato,0,80509,0 -16955,46161515,Amortiguador de choques,"Cojín de choque, Muro de contención",0,80509,0 -16956,46161516,Sistema de reconocimiento de placas,Sistema de reconocimiento de placas,0,80509,0 -16957,46161517,Riel de división de carriles,,0,80509,0 -16958,46161518,Tambor canalizador de control de tráfico,Barril de tráfico,0,80509,0 -16959,46161519,Cinta de carriles de tráfico,Cinta de marcado de carril,0,80509,0 -16960,46161520,Señales de mensaje variable,,0,80509,0 -16961,46161521,Luz de advertencia de entrada y salida de estacionamientos,,0,80509,0 -16962,46161522,Luz de advertencia de cercanía de estacionamientos,,0,80509,0 -16963,46161523,Máquina para derretir nieve en estacionamientos,Máquina para fundir nieve en estacionamientos,0,80509,0 -16964,46161524,Analizador de flujo de tráfico,,0,80509,0 -16965,46161525,Cable de bucle para detección de vehículos,Cable de bucle para detección de vehículos,0,80509,0 -16966,46161526,Sensor de peatones,,0,80509,0 -16967,46161527,Sensor de flujo de tráfico,,0,80509,0 -16968,46161528,Luz de advertencia para aeropuertos,,0,80509,0 -16969,46161529,Dispositivo de guía de voz para personas invidentes,,0,80509,0 -16970,46161530,Puerta de barrera de estacionamiento,Barrera de estacionamiento,0,80509,0 -16971,46161531,Espejo de seguridad de tráfico,,0,80509,0 -16972,46161600,Seguridad del Agua,,0,80509,0 -16973,46161601,Líneas de flotadores,Líneas de flotadores,0,80509,0 -16974,46161602,Salvavidas,,0,80509,0 -16975,46161603,Alarmas de piscina,,0,80509,0 -16976,46161604,Chalecos o protectores salvavidas,Chalecos salvavidas,0,80509,0 -16977,46161605,Boya salvavidas,,0,80509,0 -16978,46161700,Equipo y accesorios de rescate,,0,80509,0 -16979,46161701,Línea salvavidas descendente,,0,80509,0 -16980,46161702,Bolsa de aire de rescate,,0,80509,0 -16981,46161703,Esterilla aérea de seguridad,Esterilla aérea de seguridad,0,80509,0 -16982,46161704,Lanzador de línea de rescate,,0,80509,0 -16983,46161705,Set de equipos hidráulicos de rescate,Set de equipos hidráulicos de rescate,0,80509,0 -16984,46161706,Tienda de aire,,0,80509,0 -16985,46161707,Abridor de puertas de rescate,,0,80509,0 -16986,46161708,Red de rescate,Red de rescate,0,80509,0 -16987,46161710,Luz de rescate,,0,80509,0 -16988,46161711,Detector de vida,,0,80509,0 -16989,46161712,Alerta de seguridad personal,,0,80509,0 -16990,46161713,Dispositivo de señales de humo,Dispositivo de señales de humo,0,80509,0 -16991,46161714,Línea salvavidas,,0,80509,0 -16992,46161715,Equipo de rescate en espacios confinados,,0,80509,0 -16993,46171500,"Cerraduras, elementos de seguridad y accesorios",,0,80509,0 -16994,46171501,Candados,,0,80509,0 -16995,46171502,Candados de cable,,0,80509,0 -16996,46171503,Sets de candados,,0,80509,0 -16997,46171504,Candados de botón para oprimir,,0,80509,0 -16998,46171505,Llaves,,0,80509,0 -16999,46171506,Cajas fuertes,,0,80509,0 -17000,46171507,Barras de seguridad,,0,80509,0 -17001,46171508,Candados de números,Candados de combinación,0,80509,0 -17002,46171509,Gabinetes u organizadores con llave,Armarios y organizadores con llave,0,80509,0 -17003,46171510,Candados de tiempo,,0,80509,0 -17004,46171511,Dispositivos de bloqueo,Aparatos de bloqueo,0,80509,0 -17005,46171512,Candados de instrumentos,Cerraduras de instrumentos,0,80509,0 -17006,46171513,Levas de bloqueo,,0,80509,0 -17007,46171514,Cadenas de seguridad o accesorios,Cadenas o accesorios de seguridad,0,80509,0 -17008,46171515,Cadenas de llaves o estuches de llaves,Llaveros o estuches para llaves,0,80509,0 -17009,46171516,Guardas para puertas,Protectores de puertas,0,80509,0 -17010,46171517,Señales de chapa,,0,80509,0 -17011,46171518,Placa de impacto eléctrica,Placa eléctrica,0,80509,0 -17012,46171519,Accesorio cilíndrico para candados,,0,80509,0 -17013,46171520,Candados eléctricos,,0,80509,0 -17014,46171521,Placa de impacto no eléctrica,Caja de cerradura,0,80509,0 -17015,46171522,Cerramientos para candados,,0,80509,0 -17016,46171523,Sistema de bloqueo mecánico,,0,80509,0 -17017,46171524,Candado de llave de tarjeta,Cerradura con tarjeta,0,80509,0 -17018,46171600,Equipo de vigilancia y detección,,0,80509,0 -17019,46171602,Alarmas de seguridad,,0,80509,0 -17020,46171603,Temporizadores de reloj,,0,80509,0 -17021,46171604,Sistemas de alarma,Sistemas de seguridad,0,80509,0 -17022,46171605,Timbres de puerta,,0,80509,0 -17023,46171606,Sirenas,Alarmas,0,80509,0 -17024,46171607,Timbres de zumbido,,0,80509,0 -17025,46171608,Detectores de movimiento,Sensores de movimiento,0,80509,0 -17026,46171609,Espejos convexos de seguridad,,0,80509,0 -17027,46171610,Cámaras de seguridad,Videocámaras de seguridad,0,80509,0 -17028,46171611,Sistemas de identificación de video,Dispositivos de identificación de video,0,80509,0 -17029,46171612,Monitores de video,Pantallas de video,0,80509,0 -17030,46171613,Detectores de gas,,0,80509,0 -17031,46171615,Cámaras de optimización de luz o dispositivos de visión,Cámaras o dispositivos de visión que mejoran la luz,0,80509,0 -17032,46171616,Detectores de radar,,0,80509,0 -17033,46171617,Ojos (mirillas) para puertas,,0,80509,0 -17034,46171618,Campanas para puertas,Timbres,0,80509,0 -17035,46171619,Sistemas de seguridad o de control de acceso,,0,80509,0 -17036,46171620,Cortinas de luz de seguridad,,0,80509,0 -17037,46171621,Grabadoras de video o audio de vigilancia,,0,80509,0 -17038,46171622,Sistema de televisión de circuito cerrado cctv,Sistema de circuito cerrado de televisión,0,80509,0 -17039,46171623,Alarma de seguridad para el hogar,,0,80509,0 -17040,46171624,Sistema de rayos x para inspección de equipaje,,0,80509,0 -17041,46171625,Sistema de monitoreo de comunicaciones por radio,Sistema de control de comunicaciones por radio,0,80509,0 -17042,46171626,Equipo de visión nocturna,"Gafas de visión nocturna, Lentes de visión nocturna",0,80509,0 -17043,46171627,Sistema de señalización inalámbrica para sordos,Sistema de señalización inalámbrica para sordos,0,80509,0 -17044,46171628,Indicador automático de la hora,Relojes digitales,0,80509,0 -17045,46171629,Espejo de señales,,0,80509,0 -17046,46171630,Sensor de nivel de agua y lluvia para vehículos,Sensor de nivel de agua y lluvia para vehículos,0,80509,0 -17047,46171631,Alarma sísmica,,0,80509,0 -17048,46171632,Sensor infrarrojo pasivo,Detector infrarrojo pasivo,0,80509,0 -17049,46171633,Detector de metales de seguridad,,0,80509,0 -17050,46171634,Papel detector de agentes químicos,,0,80509,0 -17051,46171635,Detector de agentes químicos,,0,80509,0 -17052,46171636,Equipo de transmisión de facsímil de seguridad,,0,80509,0 -17053,46171637,Dispositivo de interceptación electrónica,Dispositivo de interceptación electrónica,0,80509,0 -17054,46171638,Detector y alarma de radiación,,0,80509,0 -17055,46171639,Video compensador de tráfico,,0,80509,0 -17056,46171640,Sistema de monitoreo automático de tráfico,,0,80509,0 -17057,46181500,Ropa de seguridad,,0,80509,0 -17058,46181501,Delantales protectores,"Batas de protección, Guardapolvos de protección, Mandiles de protección",0,80509,0 -17059,46181502,Chalecos anti balas,,0,80509,0 -17060,46181503,Overoles de protección,Prenda de uso industrial,0,80509,0 -17061,46181504,Guantes de protección,Guantes protectores,0,80509,0 -17062,46181505,Rodilleras de protección,,0,80509,0 -17063,46181506,Ponchos de protección,Impermeables,0,80509,0 -17064,46181507,Chalecos de seguridad,Prendas de seguridad,0,80509,0 -17065,46181508,Ropa para demorar el fuego,Ropa ignífuga o de antifuego,0,80509,0 -17066,46181509,Ropa de protección contra materiales peligrosos,,0,80509,0 -17067,46181512,Ropa para cuartos de limpieza,Ropa para la limpieza de habitaciones,0,80509,0 -17068,46181514,Protectores de codos,,0,80509,0 -17069,46181516,Mangas de seguridad,,0,80509,0 -17070,46181517,Vestidos de aislamiento o de flotación,Vestidos de aislamiento o de flotación,0,80509,0 -17071,46181518,Ropa resistente al calor,,0,80509,0 -17072,46181520,Protectores de piernas,Protectores para las extremidades,0,80509,0 -17073,46181522,Capuchas de seguridad,"Capuchones de seguridad, Gorros de seguridad",0,80509,0 -17074,46181526,Camisas protectoras,Camisas de protección,0,80509,0 -17075,46181527,Pantalones protectores,Pantalones de seguridad,0,80509,0 -17076,46181528,Vestido protector,Traje de protección,0,80509,0 -17077,46181529,Ropa aislante para entornos fríos,,0,80509,0 -17078,46181530,Forros protectores de dedos,Dediles de protección,0,80509,0 -17079,46181531,Ropa reflectora o accesorios,,0,80509,0 -17080,46181532,Batas de laboratorio,"Delantales de laboratorio, Mandiles de laboratorio",0,80509,0 -17081,46181533,Batas protectoras,"Delantales de protección, Mandiles de protección",0,80509,0 -17082,46181534,Muñequeras protectoras,,0,80509,0 -17083,46181535,Medias o medias largas protectoras,Calcetines o medias de protección,0,80509,0 -17084,46181536,Guantes anti cortadas,Mitones anti cortadas,0,80509,0 -17085,46181537,Guantes aislantes,Mitones aislantes,0,80509,0 -17086,46181538,Guantes térmicos,Mitones térmicos,0,80509,0 -17087,46181539,Guantes anti vibraciones,Mitones anti vibraciones,0,80509,0 -17088,46181540,Guantes de soldadura,Mitones de soldadura,0,80509,0 -17089,46181541,Guantes resistentes a los químicos,Mitones resistentes a los químicos,0,80509,0 -17090,46181542,Mitones protectores,Guantes de protección para deporte,0,80509,0 -17091,46181543,Chaqueta o gabardina impermeable,Chaquetas o impermeables,0,80509,0 -17092,46181544,Pantalones impermeable,Pantalones aislantes,0,80509,0 -17093,46181545,Vestido impermeable,Traje impermeable,0,80509,0 -17094,46181546,Capa impermeable,,0,80509,0 -17095,46181547,Cubiertas de capa impermeables,,0,80509,0 -17096,46181548,Delantal de soldadura,"Bata para soldadura, Mandil para soldadura",0,80509,0 -17097,46181549,Bufandas,Polaina de cuello,0,80509,0 -17098,46181550,Bufandas protectoras,Bandas protectoras,0,80509,0 -17099,46181551,Chaqueta de malla protectora,Chamarra de malla para protección,0,80509,0 -17100,46181552,Lámpara de cabeza para minería,"Faro de cabeza para minería, Foco de cabeza para minería",0,80509,0 -17101,46181600,Calzado de protección,,0,80509,0 -17102,46181601,Calzado para demorar el fuego,"Calzado para combatir el fuego, Calzado para demorar el fuego",0,80509,0 -17103,46181602,Calzado protector contra materiales peligrosos,Calzado protector de materiales peligrosos,0,80509,0 -17104,46181603,Calzado para cuartos de limpieza,,0,80509,0 -17105,46181604,Botas de seguridad,,0,80509,0 -17106,46181605,Zapatos de seguridad,,0,80509,0 -17107,46181606,Forros para calzado,Cubiertas para calzado,0,80509,0 -17108,46181607,Zuecos protectores,Calzado zueco de protección,0,80509,0 -17109,46181608,Sandalias protectoras,,0,80509,0 -17110,46181609,Plantillas protectoras,Plantillas de protección,0,80509,0 -17111,46181610,Zapatos aislados para clima frío,Zapatos aislados para clima frío,0,80509,0 -17112,46181611,Botas impermeables,Calzado impermeable,0,80509,0 -17113,46181612,Botas militares,Calzado militar,0,80509,0 -17114,46181613,Botas para escalar montañas,Botas de montaña,0,80509,0 -17115,46181700,Protectores de cara y cabeza,,0,80509,0 -17116,46181701,Cascos,,0,80509,0 -17117,46181702,Escudos faciales,,0,80509,0 -17118,46181703,Máscaras de soldadura,,0,80509,0 -17119,46181704,Cascos de seguridad,,0,80509,0 -17120,46181705,Cascos para motociclistas,,0,80509,0 -17121,46181706,Partes de cascos o accesorios,,0,80509,0 -17122,46181707,Partes de escudos faciales o accesorios,,0,80509,0 -17123,46181708,Red protectora para el cabello,,0,80509,0 -17124,46181709,Kit de protección facial,,0,80509,0 -17125,46181710,Capucha protectora,,0,80509,0 -17126,46181711,Casco de soldadura,,0,80509,0 -17127,46181800,Protección y accesorios para la visión,,0,80509,0 -17128,46181801,Sujetadores o estuches de anteojos,"Fundas para lentes, Soportes o estuches para lentes",0,80509,0 -17129,46181802,Anteojos de seguridad,Lentes de seguridad,0,80509,0 -17130,46181803,Protectores de ojos,,0,80509,0 -17131,46181804,Gafas protectoras,,0,80509,0 -17132,46181805,Filtros de despliegue de video,Filtros de visualización de vídeo,0,80509,0 -17133,46181806,Limpiadores de lentes,Limpiadores de gafas,0,80509,0 -17134,46181808,Cubiertas protectoras para gafas de seguridad,Tapas protectoras para gafas de seguridad,0,80509,0 -17135,46181809,Ligas para protectores de ojos,Ligas para protectores de ojos,0,80509,0 -17136,46181810,Lavadores de ojos o estaciones para lavado de ojos,,0,80509,0 -17137,46181811,Lentes protectores,,0,80509,0 -17138,46181900,Protectores auditivos,,0,80509,0 -17139,46181901,Tapones de oídos,Tapones para los oídos,0,80509,0 -17140,46181902,Tapa oídos,Bloqueadores de sonido,0,80509,0 -17141,46181903,Partes de repuesto o accesorios para tapa oídos,Piezas o accesorios de repuesto para tapa oídos,0,80509,0 -17142,46181904,Dispensador de tapones para oídos,,0,80509,0 -17143,46182000,Protección de la respiración,,0,80509,0 -17144,46182001,Máscaras o accesorios,"Caretas o accesorios, Mascarillas antipolvo",0,80509,0 -17145,46182002,Respiradores,,0,80509,0 -17146,46182003,Máscaras de gas,"Máscaras y caretas antigás, Mascarillas antigases",0,80509,0 -17147,46182004,Aparatos respiratorios auto contenidos que suministran aire para respirar o accesorios,,0,80509,0 -17148,46182005,Filtros o accesorios para máscaras o respiradores,Filtros o accesorios para caretas o respiradores,0,80509,0 -17149,46182006,Películas protectoras,,0,80509,0 -17150,46182007,Sistema de respirador accionado purificador de aire papr o accesorios,Sistema de purificación de aire PAPRs o accesorios,0,80509,0 -17151,46182100,Equipo y suministros antiestáticos,,0,80509,0 -17152,46182101,Muñequeras antiestáticas,"Brazaletes antiestáticos, Pulseras antiestáticas",0,80509,0 -17153,46182102,Correas de conexión a tierra para el talón,Tirantes de conexión a tierra para el talón,0,80509,0 -17154,46182103,Hardware de conexión a tierra,Abrazadera para barra de cobre,0,80509,0 -17155,46182104,Esterillas de piso antiestáticas,Alfombrillas antiestáticas,0,80509,0 -17156,46182105,Esterillas de mesa de trabajo antiestáticas,Esterillas de mesa de trabajo antiestáticas,0,80509,0 -17157,46182106,Cinturones antiestáticos,,0,80509,0 -17158,46182107,Kits de mantenimiento antiestáticos,,0,80509,0 -17159,46182108,Correas antiestáticas para los dedos de los pies,,0,80509,0 -17160,46182200,Equipo de apoyo ergonómico,,0,80509,0 -17161,46182201,Cinturones de soporte de la espalda,"Ceñidores de soporte para la espalda, Fajas de soporte para la espalda, Pretinas de soporte para la espalda",0,80509,0 -17162,46182202,Soportes para codos,,0,80509,0 -17163,46182203,Descansos de soporte para la espalda,,0,80509,0 -17164,46182204,Férulas para la muñeca,,0,80509,0 -17165,46182205,Descansos para los pies,Reposapiés,0,80509,0 -17166,46182206,Descansos para las muñecas,Respaldo de muñeca,0,80509,0 -17167,46182207,Soportes para las pantorrillas,Soportes para las pantorrillas,0,80509,0 -17168,46182208,Plantillas para zapatos,Plantillas de calzado,0,80509,0 -17169,46182209,Soportes para las rodillas,Rodilleras,0,80509,0 -17170,46182210,Protector o férula para los muslos,,0,80509,0 -17171,46182211,Protector lumbar o férula de espalda,Protector dorsal o férula de espalda,0,80509,0 -17172,46182212,Protector o soporte de hombro,Protector o soporte de hombro,0,80509,0 -17173,46182213,Almohada cervical,Cojín cervical,0,80509,0 -17174,46182300,Protección anti caída y equipo de rescate,,0,80509,0 -17175,46182301,Salvavidas o equipo salvavidas,Equipo de salvamento,0,80509,0 -17176,46182302,Acollador de protección contra caídas,Cordón de protección contra caídas,0,80509,0 -17177,46182303,Rebobinadores de arneses de seguridad,Rebobinadores de arneses de seguridad,0,80509,0 -17178,46182304,Conector de anclaje,Dispositivo de anclaje,0,80509,0 -17179,46182305,Acollador de auto retracción,Cordón autoextraíble,0,80509,0 -17180,46182306,Arneses o cinturones de seguridad,Armadura de seguridad o cinturones,0,80509,0 -17181,46182307,Arneses de evacuación,,0,80509,0 -17182,46182308,Agarres para halar,,0,80509,0 -17183,46182309,Escaleras de cuerda de seguridad y escaleras de cable de acero,,0,80509,0 -17184,46182310,Dispositivos mecánicos para conectar cargas a las cuerdas,Adaptadores mecánicos para conectar cargas a las cuerdas,0,80509,0 -17185,46182311,Dispositivos de detención,Adaptadores de detención,0,80509,0 -17186,46182312,Anillos para disminuir el desgaste de la cuerda,Anillos para reducir el deterioro de la cuerda,0,80509,0 -17187,46182313,Bucle metálico para conectar componentes en sistemas críticos de seguridad,,0,80509,0 -17188,46182314,Eslingas de seguridad,Cinchas de seguridad,0,80509,0 -17189,46182315,Barandas de caucho,Barandillas de caucho,0,80509,0 -17190,46182400,Equipo de limpieza de seguridad y materiales de descontaminación,,0,80509,0 -17191,46182401,Ducha de descontaminación,,0,80509,0 -17192,46182402,Unidades de lavado de seguridad,,0,80509,0 -17193,46182403,Cabina de limpieza,,0,80509,0 -17194,46182404,Armario (locker) de limpieza,"Estante de limpieza, Mueble de limpieza, Ropero de limpieza, Vitrina de limpieza",0,80509,0 -17195,46182405,Ducha de aire,,0,80509,0 -17196,46182406,Caja de paso de aire,,0,80509,0 -17197,46182500,Armas o dispositivos de seguridad personal,,0,80509,0 -17198,46182501,Repelentes para ataques caninos,"Gas lacrimógeno , Gas pimienta en aerosol",0,80509,0 -17199,46182502,Luz de seguridad personal,,0,80509,0 -17200,46182503,Alarma de bajo voltaje,,0,80509,0 -17201,46182504,Panel de control de alarmas contra incendio,,0,80509,0 -17202,46182505,Detector de fuga a tierra y alarma contra incendios,,0,80509,0 -17203,46182506,Equipo de protección contra interferencia electromagnética emi,,0,80509,0 -17204,46182507,Material de protección contra interferencia electromagnética emi,,0,80509,0 -17205,46191500,Prevención contra incendios,,0,80509,0 -17206,46191501,Detectores de humo,,0,80509,0 -17207,46191502,Detectores de calor,,0,80509,0 -17208,46191503,Recubrimientos o plastilinas o sellantes resistentes al calor,,0,80509,0 -17209,46191504,Detectores de llama,,0,80509,0 -17210,46191505,Sistemas de alarma contra incendios,,0,80509,0 -17211,46191506,Extintor de llamas,,0,80509,0 -17212,46191507,Sistema pasivo de detención del fuego,,0,80509,0 -17213,46191508,Sistema de monitoreo de cinturón,,0,80509,0 -17214,46191509,Detector de vapor tóxico,,0,80509,0 -17215,46191510,Dispositivo de observación térmica tod,,0,80509,0 -17216,46191511,Detector de punto caliente del incendio,,0,80509,0 -17217,46191600,Equipo contra incendios,,"0,1",80509,0 -17218,46191601,Extintores,Extintor de incendios,"0,1",80509,0 -17219,46191602,Sistemas de rociado para incendio,,0,80509,0 -17220,46191603,Mangueras o boquillas para incendios,Mangueras o toberas de incendio,0,80509,0 -17221,46191604,Cobijas para incendios,Mantas de fuego,0,80509,0 -17222,46191605,Herramientas manuales de supresión de incendios,,0,80509,0 -17223,46191606,Espuma de supresión de incendios o compuestos similares,,0,80509,0 -17224,46191607,Aparatos respiratorios para incendios,,0,80509,0 -17225,46191608,Sistema de supresión de incendios,Sistema de eliminación de incendios,0,80509,0 -17226,46191609,Equipos de escape de incendios,,0,80509,0 -17227,46191610,Cabezales de rociadores de incendios,,0,80509,0 -17228,46191611,Carretilla para la manguera contra incendios,,0,80509,0 -17229,46191612,Bomba de agua portátil,,0,80509,0 -17230,46191613,Agente extinguidor de incendios,,0,80509,0 -17231,46191614,Proveedor de espuma líquida contra incendios,,0,80509,0 -17232,46191615,Tubo vertical de alimentación de agua contra incendios,Tubo vertical de suministro de agua de incendio,0,80509,0 -17233,46191616,Tanques de agua para extinguir incendios para helicópteros,,0,80509,0 -17234,46191617,Lavadora de fuego para mangueras,,0,80509,0 -17235,46191618,Base para extintores de fuego,,0,80509,0 -17236,46191619,Monitor de agua para incendios,,0,80509,0 -17237,46191620,Ventilador de escape de humo,,0,80509,0 -17238,46191621,Caja de almacenamiento de extintores de incendios,Caja de almacenamiento de extinguidores,0,80509,0 -17239,46201000,Equipo de entrenamiento en seguridad pública,,0,80509,0 -17240,46201001,Maniquí de rescate,,0,80509,0 -17241,46201002,Sistema de simulación de entrenamiento en manejo de desastres,,0,80509,0 -17242,46201100,Equipo de entrenamiento de armas pequeñas,,0,80509,0 -17243,46201101,Rifle de imitación,"Escopeta de imitación, Máuser de imitación, Rifle de imitación",0,80509,0 -17244,46201102,Campo de pruebas de disparo,"Campo de pruebas de disparo, Campo de tiro o disparo",0,80509,0 -17245,47101500,Equipo para el tratamiento y suministro de agua,,0,80509,0 -17246,47101501,Equipo de carbón activado,,0,80509,0 -17247,47101502,Equipo de remoción de amoniaco,,0,80509,0 -17248,47101503,Equipo de filtración de carbón,,0,80509,0 -17249,47101504,Equipo de remoción de bacterias,,0,80509,0 -17250,47101505,Equipo de manejo de cloro,,0,80509,0 -17251,47101506,Equipo de control de corrosión,,0,80509,0 -17252,47101507,Cámaras de sílice,,0,80509,0 -17253,47101508,Equipos de desalinización,,0,80509,0 -17254,47101509,Equipos de fluorización,,0,80509,0 -17255,47101510,Equipos de remoción de hierro,,0,80509,0 -17256,47101511,Equipos de intercambio de hierro,,0,80509,0 -17257,47101512,Mezcladores o agitadores,,0,80509,0 -17258,47101513,Generadores de oxígeno,,0,80509,0 -17259,47101514,Equipos de purificación de agua,,0,80509,0 -17260,47101516,Medidores de turbiedad,,0,80509,0 -17261,47101517,Equipos de desinfección ultravioleta,,0,80509,0 -17262,47101518,Acondicionadores de agua,,0,80509,0 -17263,47101519,Accesorios para suavizar el agua,,0,80509,0 -17264,47101521,Equipos de ultra filtrado,,0,80509,0 -17265,47101522,Sistemas empacados de tratamiento de agua,,0,80509,0 -17266,47101523,Tanques de recolección,,0,80509,0 -17267,47101524,Equipos de compostaje de barro o algas,,0,80509,0 -17268,47101525,Equipo de desagüe,,0,80509,0 -17269,47101526,Máquinas de peletización de lodo,,0,80509,0 -17270,47101527,Trituradores de lodo,,0,80509,0 -17271,47101528,Secadores para tratamiento de agua,,0,80509,0 -17272,47101529,Incineradores,,0,80509,0 -17273,47101530,Equipo de control de olores,,0,80509,0 -17274,47101531,Tanques sépticos,,0,80509,0 -17275,47101532,Tanques de asentamiento,,0,80509,0 -17276,47101533,Estaciones de elevación,,0,80509,0 -17277,47101534,Distribuidores de aguas residuales,,0,80509,0 -17278,47101535,Equipo de disposición de lodo,,0,80509,0 -17279,47101536,Recolectores de lodo,,0,80509,0 -17280,47101537,Equipo acondicionador de lodo,,0,80509,0 -17281,47101538,Tanques digestores de lodo o aguas residuales,,0,80509,0 -17282,47101539,Equipo de remoción de lodo o aguas residuales,,0,80509,0 -17283,47101540,Alimentador de químicos,,0,80509,0 -17284,47101541,Equipo de ozono de tratamiento de aguas,,0,80509,0 -17285,47101542,Equipo de flotación de aire,,0,80509,0 -17286,47101543,Aireador de superficie,,0,80509,0 -17287,47101544,Difusor de aire para tratamiento de aguas residuales,,0,80509,0 -17288,47101545,Contactor biológico rotatorio rbc,,0,80509,0 -17289,47101546,Sistema de remoción de arena,,0,80509,0 -17290,47101547,Dispositivo de lavado de superficies,,0,80509,0 -17291,47101548,Consola de control de filtro,,0,80509,0 -17292,47101549,Filtro de gravedad,,0,80509,0 -17293,47101550,Filtro de compresión,,0,80509,0 -17294,47101551,Filtro de flujo ascendente,,0,80509,0 -17295,47101552,Filtro de cama movible,,0,80509,0 -17296,47101553,Filtro de fibra,,0,80509,0 -17297,47101554,Probador de jarro,,0,80509,0 -17298,47101555,Tabique de interceptación,,0,80509,0 -17299,47101556,Bandeja de goteo de agua,,0,80509,0 -17300,47101557,Plato inclinado para asentamiento,,0,80509,0 -17301,47101558,Removedor de espuma,,0,80509,0 -17302,47101559,Equipo de remoción de mugre,,0,80509,0 -17303,47101560,Equipo de bajo drenaje de filtración,,0,80509,0 -17304,47101561,Equipo de tratamiento de adulteración,,0,80509,0 -17305,47101562,Pantalla de tambor,,0,80509,0 -17306,47101563,Separador coalescente de aguas residuales,,0,80509,0 -17307,47101564,Triturador de desperdicios,,0,80509,0 -17308,47101565,Quemador de gas de desperdicios,,0,80509,0 -17309,47101566,Filtro de medios múltiples,,0,80509,0 -17310,47101567,Máquina de electrodiálisis,,0,80509,0 -17311,47101568,Unidad de limpieza de piscinas,,0,80509,0 -17312,47101569,Control plano de aguas residuales,,0,80509,0 -17313,47101570,Cuneta de oxidación,,0,80509,0 -17314,47101571,Unidad de neutralización de gas de cloro,,0,80509,0 -17315,47101572,Control de disco,,0,80509,0 -17316,47101573,Floculador,,0,80509,0 -17317,47101574,Reactor de lotes de secuenciación,,0,80509,0 -17318,47101600,Consumibles para el tratamiento de agua,,"0,1",80509,0 -17319,47101601,Alguicidas,,"0,1",80509,0 -17320,47101602,Anti incrustante,,"0,1",80509,0 -17321,47101603,Limpiadores anti calcáreos,,"0,1",80509,0 -17322,47101604,Químicos de alimentación de calderas,,"0,1",80509,0 -17323,47101605,Químicos de remoción bacteriana,,"0,1",80509,0 -17324,47101606,Químicos de control de corrosión,,"0,1",80509,0 -17325,47101607,Químicos de control de olor,,"0,1",80509,0 -17326,47101608,Floculantes,,"0,1",80509,0 -17327,47101609,Microbicidas,,"0,1",80509,0 -17328,47101610,Compuestos para suavizar el agua,,"0,1",80509,0 -17329,47101611,Demulsificantes,,"0,1",80509,0 -17330,47101612,Polielectrolitos,,"0,1",80509,0 -17331,47101613,Soluciones de neutralización,,"0,1",80509,0 -17332,47101614,Agente espesante de lodo,,"0,1",80509,0 -17333,47101615,Químico de fluorización del agua,,"0,1",80509,0 -17334,47111500,Equipo de lavado y secado,,0,80509,0 -17335,47111501,Máquinas lavadoras o secadoras combinadas tipo lavandería,,0,80509,0 -17336,47111502,Máquinas lavadoras tipo lavandería,,0,80509,0 -17337,47111503,Secadoras de ropa,,0,80509,0 -17338,47111505,Puestos para equipos de lavandería,,0,80509,0 -17339,47111506,Extractor centrífugo de lavandería,,0,80509,0 -17340,47111507,Puesto de secado de lavandería,,0,80509,0 -17341,47111508,Tabla de manchas de lavandería,,0,80509,0 -17342,47111509,Mesa de trabajo de lavandería,,0,80509,0 -17343,47111600,Equipo de planchado,,0,80509,0 -17344,47111601,Máquinas para planchar o prensas,,0,80509,0 -17345,47111602,Máquinas para doblar,,0,80509,0 -17346,47111603,Máquinas de vapor para planchar,,0,80509,0 -17347,47121500,Carritos y accesorios para limpieza,,0,80509,0 -17348,47121501,Carritos de portero,,0,80509,0 -17349,47121502,Accesorios de carrito de portero,,0,80509,0 -17350,47121600,Máquinas y accesorios para pisos,,0,80509,0 -17351,47121602,Aspiradoras,,0,80509,0 -17352,47121603,Brilladoras de pisos,,0,80509,0 -17353,47121604,Aspiradoras de combinación secas o húmedas,,0,80509,0 -17354,47121605,Depuradores para pisos,,0,80509,0 -17355,47121606,Barredoras de alfombras,,0,80509,0 -17356,47121607,Suministros o accesorios de aspiradoras,,0,80509,0 -17357,47121608,Almohadillas de máquinas de piso,,0,80509,0 -17358,47121609,Equipo de limpieza de alfombras,,0,80509,0 -17359,47121610,Máquina para lavar pisos,,0,80509,0 -17360,47121611,Raspadores de pisos,,0,80509,0 -17361,47121612,Barredoras para pisos,,0,80509,0 -17362,47121613,Accesorios para brilladoras de pisos,,0,80509,0 -17363,47121700,Envases y accesorios para residuos,,0,80509,0 -17364,47121701,Bolsas de basura,,0,80509,0 -17365,47121702,Contenedores de desperdicios o revestimientos rígidos,,0,80509,0 -17366,47121703,Urnas de incineración o accesorios,,0,80509,0 -17367,47121704,Tapas de contenedores de basura,,0,80509,0 -17368,47121705,Bolsas de arena para urnas de incineración,,0,80509,0 -17369,47121706,Ceniceros,,0,80509,0 -17370,47121707,Bolsas para mareo,,0,80509,0 -17371,47121708,Bolsas higiénicas,,0,80509,0 -17372,47121709,Contenedor de residuos peligrosos,,0,80509,0 -17373,47121800,Equipo de limpieza,,0,80509,0 -17374,47121801,Plumeros para limpiar el polvo,,0,80509,0 -17375,47121802,Removedores de pelusa,,0,80509,0 -17376,47121803,Esponjas o esponjillas,,0,80509,0 -17377,47121804,Baldes para limpieza,,0,80509,0 -17378,47121805,Limpiadores de presión o de vapor,,0,80509,0 -17379,47121806,Escurridor de trapero,,0,80509,0 -17380,47121807,Émbolo del lavaplatos o inodoro,,0,80509,0 -17381,47121808,Equipo de limpieza de drenajes o tubos,Equipo de limpieza de desagüe o tubería,0,80509,0 -17382,47121809,Sartenes desengrasantes,,0,80509,0 -17383,47121810,Dispensador de trapos para limpiar,Dispensador de trapos de limpieza,0,80509,0 -17384,47121811,Máquinas para limpiar ductos,Máquinas de limpieza de ductos,0,80509,0 -17385,47121812,Raspadores de limpieza,,0,80509,0 -17386,47121813,Cuchillas de repuesto para raspadores,,0,80509,0 -17387,47121814,Separador de polvo,,0,80509,0 -17388,47121815,Limpiador de piscinas,,0,80509,0 -17389,47121900,Accesorios de equipo de limpieza,,0,80509,0 -17390,47121901,Cartucheras para esponjas o esponjillas,,0,80509,0 -17391,47121902,Accesorios para esponjas o esponjillas,,0,80509,0 -17392,47121903,Accesorios para limpiadores de vapor o presión,,0,80509,0 -17393,47131500,Trapos y paños de limpieza,,0,80509,0 -17394,47131501,Trapos,"Franelas, Jergas para limpieza",0,80509,0 -17395,47131502,Pañitos o toallas para limpiar,Paños o toallitas de limpieza,0,80509,0 -17396,47131503,Gamuzas o cueros para lavar,,0,80509,0 -17397,47131600,"Escobas, traperos, cepillos y accesorios",,0,80509,0 -17398,47131601,Cepillos o recogedores para polvo,,0,80509,0 -17399,47131602,Almohadillas para restregar,,0,80509,0 -17400,47131603,Esponjas,Fibras para restregar,0,80509,0 -17401,47131604,Escobas,Estropajo,0,80509,0 -17402,47131605,Cepillos de limpieza,,0,80509,0 -17403,47131608,Cepillos de baño,,0,80509,0 -17404,47131609,Manijas de escobas o traperos,Mangos de escobas o trapeadores,0,80509,0 -17405,47131610,Aplicador de terminado para pisos,Aplicador de acabado de suelos,0,80509,0 -17406,47131611,Recogedor de basura,,0,80509,0 -17407,47131612,Cauchos de repuesto,Bombas destapacaños,0,80509,0 -17408,47131613,Sujetador de traperos o escobas,Porta escobas,0,80509,0 -17409,47131614,Pinzas para equipos de limpieza,,0,80509,0 -17410,47131615,Cabezas de escoba,,0,80509,0 -17411,47131616,Sujetadores de pañitos de limpieza,,0,80509,0 -17412,47131617,Traperos para polvo,Trapeadores para polvo,0,80509,0 -17413,47131618,Traperos húmedos,Trapeadores húmedos,0,80509,0 -17414,47131619,Cabezas de traperos,Cabezas de trapeadores,0,80509,0 -17415,47131700,Suministros para aseos,,0,80509,0 -17416,47131701,Dispensadores de toallas de papel,,0,80509,0 -17417,47131702,Dispensadores de productos sanitarios,Dispensadores de artículos sanitarios,0,80509,0 -17418,47131703,Receptáculos para residuos sanitarios,Recipientes de residuos sanitarios,0,80509,0 -17419,47131704,Dispensadores institucionales de jabón o loción,,0,80509,0 -17420,47131705,Accesorios para urinales o inodoros,"Accesorios para mingitorios, Accesorios para urinarios",0,80509,0 -17421,47131706,Dispensadores de ambientadores,,0,80509,0 -17422,47131707,Secadores de manos institucionales,,0,80509,0 -17423,47131709,Dispensadores de pañuelos faciales,,0,80509,0 -17424,47131710,Dispensadores de papel higiénico,,0,80509,0 -17425,47131711,Dispensadores de limpiador,,0,80509,0 -17426,47131800,Soluciones de limpieza y desinfección,,0,80509,0 -17427,47131801,Limpiadores de pisos,,0,80509,0 -17428,47131802,Terminados o ceras para pisos,,0,80509,0 -17429,47131803,Desinfectantes para uso doméstico,,0,80509,0 -17430,47131804,Limpiadores de amoniaco,,0,80509,0 -17431,47131805,Limpiadores de propósito general,"Ceras limpiadoras, Pulidores",0,80509,0 -17432,47131806,Pulidores o ceras para muebles,,0,80509,0 -17433,47131807,Blanqueadores,"Cloro, Hipoclorito de sodio, Lejí",0,80509,0 -17434,47131808,Germicida seco,,0,80509,0 -17435,47131809,Productos para limpiar o brillar zapatos,Artículos para limpieza o pulido de calzado,0,80509,0 -17436,47131810,Productos para el lavaplatos,Detergente o jabón para trastes,0,80509,0 -17437,47131811,Productos de lavandería,"Detergente o jabón para ropa, Suavizante de telas",0,80509,0 -17438,47131812,Refrescador de aire,,0,80509,0 -17439,47131813,Limpiador de pantallas,,0,80509,0 -17440,47131814,Limpiadores o pulidores de metales,Limpiadores o abrillantadores de metales,0,80509,0 -17441,47131815,Limpiador de drenajes,,0,80509,0 -17442,47131816,Desodorantes,Aromatizantes de ambientes,0,80509,0 -17443,47131817,Protectores de uso doméstico o para automotores,Protectores domésticos o automotrices,0,80509,0 -17444,47131818,Antiséptico de aire,Desinfectante de aire,0,80509,0 -17445,47131819,Limpiadores cáusticos,,0,80509,0 -17446,47131820,Limpiadores derivados del petróleo,,0,80509,0 -17447,47131821,Compuestos desengrasantes,,0,80509,0 -17448,47131822,Compuestos para remover carbón,,0,80509,0 -17449,47131823,Descongelantes o deshieladores,,"0,1",80509,0 -17450,47131824,Limpiadores de vidrio o ventanas,,"0,1",80509,0 -17451,47131825,Limpiadores de superficie de contacto,,"0,1",80509,0 -17452,47131826,Limpiadores de alfombras o tapizados,,"0,1",80509,0 -17453,47131827,Limpiadores o removedores de manchas,Quitamanchas,"0,1",80509,0 -17454,47131828,Limpiadores de automotores,,"0,1",80509,0 -17455,47131829,Limpiadores de baños,,"0,1",80509,0 -17456,47131830,Limpiadores de muebles,,"0,1",80509,0 -17457,47131831,Ácido muriático,,"0,1",80509,0 -17458,47131832,Productos anti polvo,Productos contra el polvo,"0,1",80509,0 -17459,47131833,Antisépticos para uso en alimentos,Desinfectantes de alimentos,"0,1",80509,0 -17460,47131834,Soluciones para limpiar joyas,Limpiadores de plata y oro,"0,1",80509,0 -17461,47131835,Agente desulfurizante,,"0,1",80509,0 -17462,47131900,Absorbentes,,0,80509,0 -17463,47131901,Almohadillas absorbentes,,0,80509,0 -17464,47131902,Absorbentes granulares,,0,80509,0 -17465,47131904,Medias absorbentes,Calcetines absorbentes,0,80509,0 -17466,47131905,Kits para derrames,,0,80509,0 -17467,47131906,Bandejas absorbentes,,0,80509,0 -17468,47131907,Escobas absorbentes,,0,80509,0 -17469,47131908,Almohadas absorbentes,,0,80509,0 -17470,47131909,Almohadillas o rollos absorbentes,,0,80509,0 -17471,47131910,Polímero súper absorbente,,0,80509,0 -17472,47132100,Kits de limpieza,,0,80509,0 -17473,47132101,Kits de limpieza industrial,,0,80509,0 -17474,47132102,Kits de limpieza para uso general,,0,80509,0 -17475,48101500,Equipo para cocinar o calentar,,0,80509,0 -17476,48101501,Baños maría para uso comercial,,0,80509,0 -17477,48101502,Hornos de barbecue para uso comercial,"Hornos de barbacoa para uso comercial, Parrillas de asado para uso comercial",0,80509,0 -17478,48101503,Asadores de uso comercial,,0,80509,0 -17479,48101504,Parrillas de carbón para uso comercial,"Asadores de carbón para uso comercial, Potros de carbón para uso comercial",0,80509,0 -17480,48101505,Cafeteras o máquinas para hacer té helado de uso comercial,Molinillos o máquinas para hacer té helado de uso comercial,0,80509,0 -17481,48101506,Calentadoras de café para uso comercial,Calentadores de café para uso comercial,0,80509,0 -17482,48101507,Hornos de convección para uso comercial,"Cocinas de convección para uso comercial, Fogones de convección para uso comercial",0,80509,0 -17483,48101508,Tostadoras sinfín para uso comercial,,0,80509,0 -17484,48101509,Freidoras para uso comercial,,0,80509,0 -17485,48101510,Calentadoras de comida para uso comercial,Calentadores de comida para uso comercial,0,80509,0 -17486,48101511,Planchas para uso comercial,,0,80509,0 -17487,48101512,Parrillas metálicas para uso comercial,Asadores metálicos para uso comercial,0,80509,0 -17488,48101513,Lámparas de calor para uso comercial,,0,80509,0 -17489,48101514,Ollas de vapor de alta presión para uso comercial,Vaporeras de alta presión para uso comercial,0,80509,0 -17490,48101515,Parrillas para perros calientes para uso comercial,Parrillas de hot dog de uso comercial,0,80509,0 -17491,48101516,Hornos microondas para uso comercial,,0,80509,0 -17492,48101517,Hornos para uso comercial,,0,80509,0 -17493,48101518,Ollas para cocinar pasta para uso comercial,"Cacerolas para cocinar pasta para uso comercial, Cazuelas para cocinar pasta para uso comercial",0,80509,0 -17494,48101519,Hornos de pizza para uso comercial,Fogones de pizza para uso comercial,0,80509,0 -17495,48101520,Máquinas de maíz pira para uso comercial,Máquinas para hacer palomitas de maíz para uso comercial,0,80509,0 -17496,48101521,Planchas de estufa para uso comercial,,0,80509,0 -17497,48101522,Asadores para uso comercial,,0,80509,0 -17498,48101523,Ahumadores u hornos para ahumar para uso comercial,,0,80509,0 -17499,48101524,Ollas de vapor para uso comercial,,0,80509,0 -17500,48101525,Tostadoras para uso comercial,,0,80509,0 -17501,48101526,Hierros para waffles para uso comercial,,0,80509,0 -17502,48101527,Barbecues,"Asadores, Parrillas",0,80509,0 -17503,48101528,Máquinas de crepes para uso comercial,,0,80509,0 -17504,48101529,Ollas de presión o freidoras de presión,Ollas exprés o freidoras de presión,0,80509,0 -17505,48101530,Ollas arroceras para uso comercial,Arroceras para uso comercial,0,80509,0 -17506,48101531,Ollas o calderas para salmón para uso comercial,,0,80509,0 -17507,48101532,Máquinas o accesorios para algodón dulce para uso comercial,,0,80509,0 -17508,48101533,Horno de combinación para uso comercial,,0,80509,0 -17509,48101534,Tabla de hervir para uso comercial,,0,80509,0 -17510,48101535,Parrillas “salamandra”,"Asadores ""salamandra""",0,80509,0 -17511,48101536,Horno de pastelería para uso comercial,,0,80509,0 -17512,48101537,Horno sinfín para uso comercial,,0,80509,0 -17513,48101538,Parrilla de doble contacto para uso comercial,Contacto de Prensa Parrilla,0,80509,0 -17514,48101539,Unidad de filtración de grasa para uso comercial,Unidad de filtración de aceite para uso comercial,0,80509,0 -17515,48101540,Unidad de calentamiento wok para uso comercial,Sartén wok para uso comercial,0,80509,0 -17516,48101541,Almacenador y escurridor de papas fritas para uso comercial,,0,80509,0 -17517,48101542,Tetera revestida de inclinación para uso comercial,,0,80509,0 -17518,48101543,Sartén revestido para uso comercial,,0,80509,0 -17519,48101544,Sartén para usar directamente en el calor para uso comercial,,0,80509,0 -17520,48101545,Puesto para horno para uso comercial,,0,80509,0 -17521,48101546,Máquina de decocción para uso comercial,,0,80509,0 -17522,48101600,Equipos para preparado de alimentos,,0,80509,0 -17523,48101601,Mezcladores para uso comercial,Mezcladoras de uso comercial,0,80509,0 -17524,48101602,Abrelatas eléctricos para uso comercial,,0,80509,0 -17525,48101603,Cortadores o cortadores en cubos o en dados para uso comercial,,0,80509,0 -17526,48101604,Moledoras de café para uso comercial,"Molinillos de café para uso comercial, Trituradoras de café para uso comercial",0,80509,0 -17527,48101605,Moledoras de alimentos para uso comercial,,0,80509,0 -17528,48101606,Rallos para uso comercial,Ralladores de uso comercial,0,80509,0 -17529,48101607,Exprimidores de jugo para uso comercial,Exprimidores de uso comercial,0,80509,0 -17530,48101608,Licuadoras para uso comercial,,0,80509,0 -17531,48101609,Máquinas de pasta para uso comercial,,0,80509,0 -17532,48101610,Peladores para uso comercial,,0,80509,0 -17533,48101611,Escalas para uso comercial,"Balanzas de uso comercial, Básculas de uso comercial",0,80509,0 -17534,48101612,Procesadores de alimentos para uso comercial,,0,80509,0 -17535,48101613,Máquinas de amasar para uso comercial,,0,80509,0 -17536,48101614,Sets o bolsas para hielo para uso comercial,Bolsas de hielo para uso comercial,0,80509,0 -17537,48101615,Lavadoras de platos para uso comercial,Lavavajillas de uso comercial,0,80509,0 -17538,48101616,Tajadores de alimentos para uso comercial,Cortadores de alimentos para uso comercial,0,80509,0 -17539,48101617,Espátulas plásticas para uso comercial,Espátulas flexibles de plástico para uso comercial,0,80509,0 -17540,48101618,Partes de máquinas para lavar platos para uso comercial,Partes de máquinas lavavajillas para uso comercial,0,80509,0 -17541,48101619,Sistema de esterilización de agua,,0,80509,0 -17542,48101700,Distribuidores automáticos de comida y bebida,,0,80509,0 -17543,48101701,Dispensador de bebidas carbonatadas,Máquinas de bebidas carbonatadas,0,80509,0 -17544,48101702,Dispensador de bebidas no carbonatadas,Máquinas de bebidas no carbonatadas,0,80509,0 -17545,48101703,Dispensadores de leche,,0,80509,0 -17546,48101704,Bombas de melaza (syrup),Bombas de melaza (jarabe),0,80509,0 -17547,48101705,Máquinas de cappuccino o expreso,Dispensador de cappuccino o expreso,0,80509,0 -17548,48101706,Máquinas de leche malteada,,0,80509,0 -17549,48101707,Máquinas de helados suaves,Dispensadores de helados suaves,0,80509,0 -17550,48101708,Máquinas de granizados,Dispensador de bebidas granizadas,0,80509,0 -17551,48101709,Dispensadores de hielo,Máquinas dispensadoras de hielo,0,80509,0 -17552,48101710,Fuentes o contenedores de burbujeo de bebidas,,0,80509,0 -17553,48101711,Dispensadores de agua embotellada o accesorios,,0,80509,0 -17554,48101712,Dispensadores de tazas,,0,80509,0 -17555,48101713,Mezcladores de cócteles o accesorios,Coctelera o accesorios,0,80509,0 -17556,48101714,Dispensadores de agua caliente,,0,80509,0 -17557,48101715,Máquina ralladora de hielo,Ralladores de hielo,0,80509,0 -17558,48101716,Purificador de agua,Purificadores de agua,0,80509,0 -17559,48101800,Menaje y utensilios de cocina,,0,80509,0 -17560,48101801,Cubiertos para uso comercial,,0,80509,0 -17561,48101802,Moldes para uso comercial,"Gelatinero, flanero y moldes para pastel",0,80509,0 -17562,48101803,Cucharones para uso comercial,,0,80509,0 -17563,48101804,Tazas medidoras para uso comercial,,0,80509,0 -17564,48101805,Tazones para mezclar para uso comercial,,0,80509,0 -17565,48101806,Latas de ponqués o pies para uso comercial,Platos para panqués o pasteles de uso comercial,0,80509,0 -17566,48101807,Latas de pizza para uso comercial,Platos para pizza de uso comercial,0,80509,0 -17567,48101808,Sartenes de salsa o para sofreír para uso comercial,Sartenes para salsas y para saltear de uso comercial,0,80509,0 -17568,48101809,Ollas para salsas o cocción para uso comercial,,0,80509,0 -17569,48101810,Tapas para sartenes u ollas para uso comercial,Tapas para cacerolas u ollas para uso comercial,0,80509,0 -17570,48101811,Rodillos de amasar para uso comercial,,0,80509,0 -17571,48101812,Coladeras para uso comercial,Filtros de uso comercial,0,80509,0 -17572,48101813,Batidoras manuales para uso comercial,,0,80509,0 -17573,48101814,Woks para uso comercial,,0,80509,0 -17574,48101815,Cucharas de servir para uso comercial,,0,80509,0 -17575,48101816,Bolsa para cubiertos para uso comercial,,0,80509,0 -17576,48101817,Equipos o moldes para decorar ponqués,Moldes para decorar panqués,0,80509,0 -17577,48101818,Desinfectante de vajillas,,0,80509,0 -17578,48101819,Esterilizador de manos,Esterilizador para manos,0,80509,0 -17579,48101820,Campana extractora para uso comercial,Campana de cocina para uso comercial,0,80509,0 -17580,48102000,Mobiliario de restaurantes,,0,80509,0 -17581,48102001,Sillas para restaurantes,,0,80509,0 -17582,48102002,Cabinas para restaurantes,Muebles para restaurantes,0,80509,0 -17583,48102003,Barras de ensalada,,0,80509,0 -17584,48102004,Superficies de mesa,,0,80509,0 -17585,48102005,Taburetes de bar,"Asientos de bar, Banquillos de bar",0,80509,0 -17586,48102006,Bares permanentes,,0,80509,0 -17587,48102007,Bares portátiles,,0,80509,0 -17588,48102008,Mesas para condimentos,,0,80509,0 -17589,48102009,Mesas para servir o dispensar comidas,,0,80509,0 -17590,48102010,Carrito para recoger tazas,Carrito de servicio,0,80509,0 -17591,48102100,Suministros y equipo de manipulación y almacenamiento,,0,80509,0 -17592,48102101,Mostradores,Vitrinas,0,80509,0 -17593,48102102,Mostradores para mantener caliente la comida,"Contenedor de alimentos calientes, Vitrinas para mantener caliente la comida",0,80509,0 -17594,48102103,Mostradores refrigerados,,0,80509,0 -17595,48102104,Mostradores para helados,Vitrina para helados,0,80509,0 -17596,48102105,Equipos para enfriar vasos,Equipo refrigerador de vasos,0,80509,0 -17597,48102106,Contenedores fríos,,0,80509,0 -17598,48102107,Guantes para abastecimiento de comidas por encargo (catering) o dispensadores de guantes,,0,80509,0 -17599,48102108,Papel aluminio para envolver alimentos,,0,80509,0 -17600,48102109,Papel plástico para envolver alimentos,,0,80509,0 -17601,48111000,Máquinas expendedoras de líquidos,,0,80509,0 -17602,48111001,Máquinas dispensadoras de porciones únicas con tazas,Máquinas dispensadoras porciones individuales con tazas,0,80509,0 -17603,48111002,Maquinas dispensadoras de cantidades grandes,Máquinas dispensadoras de cantidades a granel,0,80509,0 -17604,48111100,Máquinas expendedoras de piezas/unidades,,0,80509,0 -17605,48111101,Máquinas expendedoras de botellas o latas,,0,80509,0 -17606,48111102,Máquinas de bolas de dulce o novedades para niños,,0,80509,0 -17607,48111103,Máquinas exhibidoras de botanas o productos en paquetes pequeños,Máquinas de presentación de productos de aperitivos o pequeños paquetes,0,80509,0 -17608,48111104,Máquinas expendedoras de comidas a la carta,Máquinas expendedoras de alimentos a la carta,0,80509,0 -17609,48111105,Máquinas de confitería helada,,0,80509,0 -17610,48111106,Dispensadores de ítems de acomodación personal,,0,80509,0 -17611,48111107,Máquinas de cigarrillos,Dispensadoras de cigarrillos,0,80509,0 -17612,48111108,Dispensadores de medicamentos,,0,80509,0 -17613,48111109,Máquina para lustrar calzado,,0,80509,0 -17614,48111400,Máquinas expendedoras de dinero efectivo,,0,80509,0 -17615,48111401,Máquinas de cajero automático atm,Cajeros automáticos,0,80509,0 -17616,48111402,Máquinas para cambiar billetes en monedas,,0,80509,0 -17617,48111403,Máquinas para cambiar moneda extranjera,Máquinas de cambio de moneda extranjera,0,80509,0 -17618,48111404,Equipos de transferencia electrónica de fondos para puntos de venta,,0,80509,0 -17619,48111405,Accesorios para máquinas de cajero automático,Accesorios para cajeros automáticos,0,80509,0 -17620,48111406,Sistema de numeración de filas,,0,80509,0 -17621,48121100,Máquinas de Juego operadas con monedas,,0,80509,0 -17622,48121101,Máquinas de póker o tragamonedas,,0,80509,0 -17623,48121102,Máquinas de lotería,,0,80509,0 -17624,48121200,Mesas de Juego y Juegos,,0,80509,0 -17625,48121201,Ruedas de ruleta,,0,80509,0 -17626,48121202,Mesas de naipes,,0,80509,0 -17627,48131500,Productos de entierro o tumbas,Productos funerarios,0,80509,0 -17628,48131501,Cajas de cenizas funerarias o urnas de cremación,,0,80509,0 -17629,48131502,Ataúdes,,0,80509,0 -17630,48131503,Mortajas,Féretros,0,80509,0 -17631,48131504,Lápidas,Vestiduras,0,80509,0 -17632,48131505,Tabla de ofrecimientos en piedra,,0,80509,0 -17633,49101600,Coleccionables,,0,80509,0 -17634,49101601,Antigüedades,,0,80509,0 -17635,49101602,Recuerdos (souvenirs),,0,80509,0 -17636,49101603,Colecciones de monedas,,0,80509,0 -17637,49101604,Colecciones de estampillas,,0,80509,0 -17638,49101605,Alfombras antiguas,,0,80509,0 -17639,49101606,Artículos excavados,,0,80509,0 -17640,49101607,Colecciones de historietas cómicas,Colecciones de cómics,0,80509,0 -17641,49101608,Instrumentos musicales antiguos,,0,80509,0 -17642,49101609,Ornamentos o decoraciones,Adornos o decoraciones,0,80509,0 -17643,49101611,Amuletos,,0,80509,0 -17644,49101612,Hologramas,,0,80509,0 -17645,49101613,Cristales de vidrio,,0,80509,0 -17646,49101614,Máscaras rituales o para teatro,,0,80509,0 -17647,49101700,Premios,,0,80509,0 -17648,49101701,Medallas,,0,80509,0 -17649,49101702,Trofeos,,0,80509,0 -17650,49101704,Placas,,0,80509,0 -17651,49101705,Certificados,,0,80509,0 -17652,49101706,Premios de fotografía,,0,80509,0 -17653,49101707,Certificado de logro,Reconocimiento,0,80509,0 -17654,49101708,Coronas,,0,80509,0 -17655,49101709,Tabletas de conmemoración,,0,80509,0 -17656,49121500,Equipos para camping y exteriores,,0,80509,0 -17657,49121502,Almohadas para dormir para acampar,,0,80509,0 -17658,49121503,Carpas,"Carpas para eventos, Tiendas de campaña",0,80509,0 -17659,49121504,Sleeping bags (bolsas para dormir),,0,80509,0 -17660,49121505,Cajas de hielo,Hieleras,0,80509,0 -17661,49121506,Kit de reparación de carpas,,0,80509,0 -17662,49121507,Colchones neumáticos,Colchones de aire,0,80509,0 -17663,49121508,Mosquiteros,,0,80509,0 -17664,49121509,Estufas para acampar o para exteriores,,0,80509,0 -17665,49121510,Enfriadores de bebidas,Refrigeradores de bebidas,0,80509,0 -17666,49121511,Reverberos para acampar o exteriores,Cocinillas para acampar o exteriores,0,80509,0 -17667,49121512,Estacas para carpas,,0,80509,0 -17668,49121513,Tornillos para carpas,,0,80509,0 -17669,49121600,Muebles de camping,,0,80509,0 -17670,49121601,Asientos o taburetes para acampar,"Banquillos para acampar, Sillas para acampar",0,80509,0 -17671,49121602,Mesas para acampar,,0,80509,0 -17672,49121603,Catres para acampar,Cama para acampar,0,80509,0 -17673,49131500,Aparejos de pesca,,0,80509,0 -17674,49131501,Cañas de pescar,,0,80509,0 -17675,49131502,Hilo de pesca,Cuerda de pesca,0,80509,0 -17676,49131503,Carreteles de pesca,Carretes de pescar,0,80509,0 -17677,49131504,Cebos para pesca,Señuelos de pesca,0,80509,0 -17678,49131505,Carnadas,Trampa para pesca,0,80509,0 -17679,49131506,Pesas para pesca,Plomos para pesca,0,80509,0 -17680,49131600,Productos de cacería,,"0,1",80509,0 -17681,49131601,Llamadas de animales,,"0,1",80509,0 -17682,49131602,Señuelos para deportes,,"0,1",80509,0 -17683,49131603,Trampas para deportes,,"0,1",80509,0 -17684,49131604,Escopetas para deportes,,1,80509,0 -17685,49131605,Rifles para deportes,,1,80509,0 -17686,49131606,Munición para deportes,,1,80509,0 -17687,49131607,Cañones de escopetas,,1,80509,0 -17688,49141500,Escafandras autónomas y tubos de respiración,,0,80509,0 -17689,49141501,Compensadores de flotación,,0,80509,0 -17690,49141502,Tanques de buceo,,0,80509,0 -17691,49141503,Reguladores para buceo,,0,80509,0 -17692,49141504,Instrumentos de buceo o accesorios,Accesorios para buceo,0,80509,0 -17693,49141505,Máscaras o aletas o esnórqueles,,0,80509,0 -17694,49141506,Trajes isotérmicos,Trajes de buceo,0,80509,0 -17695,49141508,Botas para buceo,Trajes secos de buceo,0,80509,0 -17696,49141509,Capucha para buceo,,0,80509,0 -17697,49141600,Equipos y accesorios para windsurf y natación,,0,80509,0 -17698,49141602,Tablas de esquí acuático o tablas para arrodillarse o boogieboards,,0,80509,0 -17699,49141603,Esquís acuáticos o accesorios,,0,80509,0 -17700,49141604,Equipos de windsurfing,Equipos de tabla a vela,0,80509,0 -17701,49141605,Tablas de surf,,0,80509,0 -17702,49141606,Anteojos de natación o aletas de natación,Lentes o aletas de natación,0,80509,0 -17703,49141607,Equipo de paravelismo,Equipo de parapente,0,80509,0 -17704,49161500,Equipos para deportes de campo,,0,80509,0 -17705,49161501,Toboganes de bloqueo para futbol,,0,80509,0 -17706,49161502,Guantes de beisbol,"Manilla de béisbol, Manoplas de béisbol",0,80509,0 -17707,49161503,Pelotas de beisbol,,0,80509,0 -17708,49161504,Balones de futbol,,0,80509,0 -17709,49161505,Balones de soccer,,0,80509,0 -17710,49161506,Bates de beisbol,,0,80509,0 -17711,49161507,Bases de beisbol,,0,80509,0 -17712,49161508,Máquinas para lanzar (pitching),,0,80509,0 -17713,49161509,Balones de softbol,,0,80509,0 -17714,49161510,Muñecos para jugadas de futbol,,0,80509,0 -17715,49161511,Palos de lacrosse,,0,80509,0 -17716,49161512,Pelotas de lacrosse,Bolas de lacrosse,0,80509,0 -17717,49161513,Palos de hockey de campo,,0,80509,0 -17718,49161514,Bolas de hockey de campo,,0,80509,0 -17719,49161515,Pelotas de equipos de balonmano,,0,80509,0 -17720,49161516,Sets escolares de equipos de balonmano,,0,80509,0 -17721,49161517,Equipo protector para beisbol o softbol,Equipo de protección para béisbol o softbol,0,80509,0 -17722,49161518,Ayudas para batear en beisbol,,0,80509,0 -17723,49161519,Defensas traseras o cercas para beisbol,,0,80509,0 -17724,49161520,Bates de softbol,,0,80509,0 -17725,49161521,Guantes de softbol,"Manilla de softbol, Manopla de softbol",0,80509,0 -17726,49161522,Tees de patada para futbol,,0,80509,0 -17727,49161523,Equipo de futbol de bandera,"Accesorios para futbol bandera, Accesorios para tocho bandera",0,80509,0 -17728,49161524,Equipo de marcado de campo para soccer,Accesorios de marcado de campo para soccer,0,80509,0 -17729,49161525,Equipo protector para soccer,,0,80509,0 -17730,49161526,Ayudas de entrenamiento para soccer,Accesorios de entrenamiento para soccer,0,80509,0 -17731,49161527,Palo de gateball,,0,80509,0 -17732,49161600,Equipos para deportes de raqueta y pista,,0,80509,0 -17733,49161601,Raquetas de racquetball,,0,80509,0 -17734,49161602,Raquetas de bádminton,,0,80509,0 -17735,49161603,Pelotas de básquetbol,Balones de básquetbol,0,80509,0 -17736,49161604,Pelotas de tenis,,0,80509,0 -17737,49161605,Pelotas de racquet,,0,80509,0 -17738,49161606,Pelotas de squash,,0,80509,0 -17739,49161607,Raquetas de tenis,,0,80509,0 -17740,49161608,Balones de voleibol,Pelotas de voleibol,0,80509,0 -17741,49161609,Gallitos de bádminton,,0,80509,0 -17742,49161610,Raquetas de squash,,0,80509,0 -17743,49161611,Ayudas de entrenamiento para tenis,Accesorios para entrenamiento de tenis,0,80509,0 -17744,49161612,Equipo de cancha de tenis,,0,80509,0 -17745,49161613,Almacenamiento para balones o redes de voleibol,"Bolsa de almacenamiento para balones o redes de voleibol, Contenedor de almacenamiento para balones o redes de voleibol",0,80509,0 -17746,49161614,Estándares gimnásticos para voleibol,,0,80509,0 -17747,49161615,Sistemas de juego completo para basquetbol,Sistemas de juego completo para baloncesto,0,80509,0 -17748,49161616,Equipo protector para hockey de piso,Accesorios de protección para hockey de piso,0,80509,0 -17749,49161617,Postes para red,,0,80509,0 -17750,49161618,Espiros,,0,80509,0 -17751,49161619,Cuerdas para raquetas,,0,80509,0 -17752,49161620,Agarres (“grips”) para raquetas,Agarraderas de raqueta,0,80509,0 -17753,49161621,Máquinas para cuerdas de raqueta,,0,80509,0 -17754,49161700,Equipos para deportes de pista,,0,80509,0 -17755,49161701,Jabalinas,Lanzas,0,80509,0 -17756,49161702,Barras para saltar,,0,80509,0 -17757,49161703,Discos,Disco de lanzamiento,0,80509,0 -17758,49161704,Balas de lanzamiento,,0,80509,0 -17759,49161705,Garrochas,Pértigas,0,80509,0 -17760,49161706,Obstáculos,Bloques y obstáculos,0,80509,0 -17761,49161707,Bastones,Bastones de relevo,0,80509,0 -17762,49161708,Barra de salto y garrocha,Barra de salto y pértiga,0,80509,0 -17763,49161709,Círculo de lanzamiento de disco,,0,80509,0 -17764,49161710,Martillo para lanzar,,0,80509,0 -17765,49161711,Círculo de lanzamiento de bala,,0,80509,0 -17766,49171500,Equipos de gimnasia,,0,80509,0 -17767,49171501,Barras o postes gimnásticos,Barras de gimnasia o vigas,0,80509,0 -17768,49171502,Cuerdas o anillos o accesorios para trepar gimnásticos,,0,80509,0 -17769,49171503,Equipo de garrocha para uso gimnástico,,0,80509,0 -17770,49171504,Trampolines para uso gimnástico,,0,80509,0 -17771,49171505,Equipo de equilibrio,,0,80509,0 -17772,49171506,Caballo de borrén delantero para uso gimnástico,,0,80509,0 -17773,49171507,Club indio para uso gimnástico,,0,80509,0 -17774,49171508,Trampolín de salto o “beatboard” para uso gimnástico,,0,80509,0 -17775,49171509,Caja de salto para uso gimnástico,,0,80509,0 -17776,49171600,Equipos de boxeo,,0,80509,0 -17777,49171601,Rings para boxeo,,0,80509,0 -17778,49171602,Sacos de boxeo,,0,80509,0 -17779,49171603,Guantes de boxeo,Costal Para Boxeo,0,80509,0 -17780,49171604,Campana de boxeo,,0,80509,0 -17781,49181500,Equipos y juegos de mesa,,0,80509,0 -17782,49181501,Mesas de billar,,0,80509,0 -17783,49181502,Tacos de pool,Palo de billar,0,80509,0 -17784,49181503,Juegos de tejo,,0,80509,0 -17785,49181504,Juegos de pinball,,0,80509,0 -17786,49181505,Bolas de billar,,0,80509,0 -17787,49181506,Mesas para hockey aéreo o accesorios,,0,80509,0 -17788,49181507,Mesas de tenis (ping pong),,0,80509,0 -17789,49181508,Raquetas de ping pong,,0,80509,0 -17790,49181509,Bolas de ping pong,,0,80509,0 -17791,49181510,Mesas de foosball,Mesas de futbolito,0,80509,0 -17792,49181511,Bolas de foosball,Pelotas de futbolín,0,80509,0 -17793,49181512,Jugadores de repuesto para foosball,"Jugadores de repuesto para foosball, Muñecos de repuesto para foosball",0,80509,0 -17794,49181513,Puntas de tacos de billar,,0,80509,0 -17795,49181514,Tiza de billar,,0,80509,0 -17796,49181515,Estantes de billar,"Anaqueles de billar, Muebles de billar",0,80509,0 -17797,49181600,Equipo y juegos de tiro al blanco,,0,80509,0 -17798,49181601,Objetivos para tiro con arco,,0,80509,0 -17799,49181602,Arcos,,0,80509,0 -17800,49181603,Flechas,,0,80509,0 -17801,49181605,Tableros de dardos,,0,80509,0 -17802,49181606,Equipo de tiro al plato,,0,80509,0 -17803,49181607,Objetivos para lanzar,Objetivos de lanzamiento,0,80509,0 -17804,49181608,Cuerdas para arcos,,0,80509,0 -17805,49181609,Guantes para tiro con arco,Mitones para tiro con arco,0,80509,0 -17806,49181610,Protectores de brazos para tiro con arco,,0,80509,0 -17807,49181611,Puestos de objetivos para tiro con arco,Bases de objetivos para tiro con arco,0,80509,0 -17808,49181612,Defensas traseras para tiro con arco,,0,80509,0 -17809,49181613,Gukgung (arco coreano),,0,80509,0 -17810,49181614,Ballesta para tiro con arco,,0,80509,0 -17811,49201500,Equipo de entrenamiento para aeróbicos,,0,80509,0 -17812,49201501,Trotadoras,Caminadoras,0,80509,0 -17813,49201502,Escaladores de escaleras,,0,80509,0 -17814,49201503,Bicicletas estáticas,,0,80509,0 -17815,49201504,Máquinas de remos,,0,80509,0 -17816,49201512,Lazos para saltar,Cuerdas para saltar,0,80509,0 -17817,49201513,Trampolines para ejercicios,,0,80509,0 -17818,49201514,Pelotas para ejercicios,Bola de la gimnasia,0,80509,0 -17819,49201515,Equipos de paso para aeróbicos (“step”),Equipo aeróbico de paso,0,80509,0 -17820,49201516,Bicicletas elípticas,,0,80509,0 -17821,49201517,Máquina de inversión,Tabla de Inversión,0,80509,0 -17822,49201518,Barras de pared,Barras de dominadas montable en pared,0,80509,0 -17823,49201519,Torbellino de cuerpo,,0,80509,0 -17824,49201520,Set de paletas para viaje,,0,80509,0 -17825,49201600,Equipo de entrenamiento de pesas y resistencia,,0,80509,0 -17826,49201601,Pesas,,0,80509,0 -17827,49201602,Pesas de 280 libras,,0,80509,0 -17828,49201603,Máquinas de resistencia para la parte inferior del cuerpo,,0,80509,0 -17829,49201604,Bancos o estantes para pesas,Soportes para pesas,0,80509,0 -17830,49201605,Máquinas de resistencia para la parte superior del cuerpo,,0,80509,0 -17831,49201606,Pesas de estado físico,,0,80509,0 -17832,49201607,Máquinas de pilates,,0,80509,0 -17833,49201608,Máquina para dar fuerza de agarre,,0,80509,0 -17834,49201609,Bandas de resistencia,Cintas de resistencia,0,80509,0 -17835,49201610,Tubos de resistencia,,0,80509,0 -17836,49201611,Multi gimnasios,,0,80509,0 -17837,49201612,Pesas de entrenamiento,,0,80509,0 -17838,49211600,Equipo de golf,,0,80509,0 -17839,49211601,Talegas de golf,,0,80509,0 -17840,49211602,Bolas de golf,,0,80509,0 -17841,49211603,Tacos de golf,,0,80509,0 -17842,49211604,Tees de golf,,0,80509,0 -17843,49211605,Forros para las cabezas de los tacos de golf,Cubiertas para las cabezas de los tacos de golf,0,80509,0 -17844,49211606,Guantes de golf,,0,80509,0 -17845,49211607,Divot fijador,,0,80509,0 -17846,49211608,Golfscopios,,0,80509,0 -17847,49211609,Compañero de putting para golf,,0,80509,0 -17848,49211800,Equipo de clase de educación física,,0,80509,0 -17849,49211801,Equipo de paracaidismo,,0,80509,0 -17850,49211802,Aros de hula o equipos de hula,,0,80509,0 -17851,49211803,Equipos de orientación,,0,80509,0 -17852,49211804,Materiales o marcadores de identificación de equipos,,0,80509,0 -17853,49211805,Cuerdas,,0,80509,0 -17854,49211806,Almacenamiento de equipos de educación física,,0,80509,0 -17855,49211807,Herramientas de evaluación para educación física,,0,80509,0 -17856,49211808,Herramienta para medir la obesidad,,0,80509,0 -17857,49211809,Herramienta para medir el cuerpo,,0,80509,0 -17858,49211810,Pistola de radar para medir la velocidad,Radar para medir la velocidad,0,80509,0 -17859,49211811,Ergómetro,,0,80509,0 -17860,49211812,Dispositivo de medición de la flexión del tronco,,0,80509,0 -17861,49211813,Medidor de saltos,,0,80509,0 -17862,49211814,Instrumento de medición de fatiga,,0,80509,0 -17863,49211815,Sistema de medición de reacción de todo el cuerpo,,0,80509,0 -17864,49211816,Analizador de carreras,,0,80509,0 -17865,49211817,Instrumento de medición de fuerza muscular,,0,80509,0 -17866,49211818,Balón medicinal,,0,80509,0 -17867,49211819,Sistema de análisis de movimiento,,0,80509,0 -17868,49211820,Dinamómetro de agarre,,0,80509,0 -17869,49211821,"Dinamómetro de espalda, piernas y pecho",,0,80509,0 -17870,49211822,Kit de evaluación de músculos de la mano,,0,80509,0 -17871,49211823,Instrumento de medición para el cuerpo humano,,0,80509,0 -17872,49211824,Kit de evaluación de postura,,0,80509,0 -17873,49211825,Dispositivo de prueba de inteligencia,,0,80509,0 -17874,49211826,Equipo de medición de características corporales,,0,80509,0 -17875,49211827,Probador de movimiento de dedos,,0,80509,0 -17876,49211828,Probador de estabilidad de la mano,,0,80509,0 -17877,49211829,Analizador de circulación sanguínea,,0,80509,0 -17878,49211830,Probador de paso lateral,,0,80509,0 -17879,49211831,Probador de abdomen (sentadillas),,0,80509,0 -17880,49211832,Probador de equilibrio,,0,80509,0 -17881,49211833,Probador de resistencia de corazón y pulmones,,0,80509,0 -17882,49221500,Accesorios para deporte,,0,80509,0 -17883,49221501,Tableros de anotaciones para deportes,Marcadores deportivos,0,80509,0 -17884,49221502,Porterías,,0,80509,0 -17885,49221503,Equipos de seguridad deportiva excepto para la cabeza,,0,80509,0 -17886,49221504,Artículos de seguridad para la cabeza para uso deportivo,Cascos deportivos de seguridad,0,80509,0 -17887,49221505,Mallas o redes para deportes,,0,80509,0 -17888,49221506,Almohadillas o acolchado para deportes,Cojines o acolchado para deportes,0,80509,0 -17889,49221507,Tableros de basquetbol,Tableros de baloncesto,0,80509,0 -17890,49221508,Aros de basquetbol,Aro de baloncesto,0,80509,0 -17891,49221509,Patines o cuchillas de patines,,0,80509,0 -17892,49221510,Gorras deportivas,,0,80509,0 -17893,49221511,Bolsas para equipos deportivos,Maletas para equipos deportivos,0,80509,0 -17894,49221512,Patinetas,,0,80509,0 -17895,49221513,Banca para jugadores,,0,80509,0 -17896,49221514,Asiento o puesto para el árbitro,Silla o stand de arbitro,0,80509,0 -17897,49221515,Bomba para inflar balones,,0,80509,0 -17898,49221516,Pistola de arranque,,0,80509,0 -17899,49221517,Bloque de arranque,,0,80509,0 -17900,49221518,Postes de mallas,,0,80509,0 -17901,49221519,Espada de esgrima,"Florete para esgrima, Sable para esgrima",0,80509,0 -17902,49221520,Puesto de herramientas atléticas,,0,80509,0 -17903,49221521,Carrito de productos deportivos,,0,80509,0 -17904,49221522,Maniquí de entrenamiento atlético,,0,80509,0 -17905,49221523,Podio de premiación,,0,80509,0 -17906,49221524,Chaqueta para esgrima,,0,80509,0 -17907,49221525,Tubo interior de caucho para atletismo,,0,80509,0 -17908,49221526,Cinturón para levantar pesas,Fajas para levantar pesas,0,80509,0 -17909,49221527,Espada de bambú,Sable de bambú,0,80509,0 -17910,49221528,Tablero trasero de basquetbol,Tablero trasero de baloncesto,0,80509,0 -17911,49221529,Máquina de juzgamiento de esgrima,,0,80509,0 -17912,49221530,Temporizador para deportes,,0,80509,0 -17913,49221531,Poste para columpiarse,,0,80509,0 -17914,49221532,Sistema de juegos para patios de recreo,,0,80509,0 -17915,49221533,Tablas flotantes para natación,Tabla para natación,0,80509,0 -17916,49241500,Equipo de parques infantiles,,0,80509,0 -17917,49241501,Columpios para patios de recreo,Columpios para parques infantiles,0,80509,0 -17918,49241502,Aparatos de trepar para patios de recreo,Aparatos de escalamiento para juegos,0,80509,0 -17919,49241503,Carruseles para patios de recreo,Carruseles para parques infantiles,0,80509,0 -17920,49241504,Rodaderos para patios de recreo,,0,80509,0 -17921,49241505,Balanzas para patios de recreo,,0,80509,0 -17922,49241506,Túneles para patios de recreo,,0,80509,0 -17923,49241507,Cajas de arena para patios de recreo,,0,80509,0 -17924,49241508,Graderías para patios de recreo,,0,80509,0 -17925,49241509,Equipo para escalar muros,Equipo para trepar muros,0,80509,0 -17926,49241510,Equipos para escalar lazos,Equipos de escalada de cuerda,0,80509,0 -17927,49241511,Pérgolas,,0,80509,0 -17928,49241700,Equipo y suministros de piscina y spa,,0,80509,0 -17929,49241701,Trampolines,,0,80509,0 -17930,49241702,Rodaderos de piscina,,0,80509,0 -17931,49241703,Soplador de spa,,0,80509,0 -17932,49241704,Kit o soluciones de prueba de agua,,0,80509,0 -17933,49241705,Limpiador automático de piscinas,,0,80509,0 -17934,49241706,Cobija solar,Manta solar,0,80509,0 -17935,49241707,Caldera para piscinas o spa,Calentador para piscinas o spa,0,80509,0 -17936,49241708,Generador de ozono,,0,80509,0 -17937,49241709,Rieles para cortinas solares,,0,80509,0 -17938,49241710,Almohada para spa,Cojín para spa,0,80509,0 -17939,49241711,Cubiertas para spa,,0,80509,0 -17940,49241712,Químicos para piscinas o piscinas de hidromasaje,,0,80509,0 -17941,49241800,Equipos de sauna,,0,80509,0 -17942,49241801,Caldera para sauna,,0,80509,0 -17943,50101700,Frutos secos,,0,80509,0 -17944,50101716,Nueces y semillas enteras,,0,80509,0 -17945,50101717,Nueces y semillas sin cascara,,0,80509,0 -17946,50111500,Carne y aves de corral,,0,80509,0 -17947,50111513,"Carne, mínimamente procesada sin aditivos",,0,80509,0 -17948,50111514,"Cerdo, mínimamente procesado sin aditivos","Chanchos, mínimamente procesado sin aditivos, Cochinillos, mínimamente procesado sin aditivos, Cochinos, mínimamente procesado sin aditivos, Cuche, mínimamente procesado sin aditivos, Cuinos, mínimamente procesado sin aditivos, Gorrinos, mínimamente procesado sin aditivos, Lechones, mínimamente procesado sin aditivos, Marranos, mínimamente procesado sin aditivos, Porcinos, mínimamente procesado sin aditivos, Puercos, mínimamente procesado sin aditivos,Lechones, mínimamente procesado sin aditivos",0,80509,0 -17949,50111515,"Pollo, mínimamente procesado sin aditivos","Gallina, mínimamente procesada sin aditivos",0,80509,0 -17950,50111516,"Carne, mínimamente procesado con aditivos",,0,80509,0 -17951,50111517,"Ternera, mínimamente procesado sin aditivos",,0,80509,0 -17952,50111518,"Ternera, mínimamente procesado con aditivos",,0,80509,0 -17953,50111519,"Cerdo, mínimamente procesado con aditivos","Chanchos, mínimamente procesado con aditivos, Cochinillos, mínimamente procesado con aditivos, Cochinos, mínimamente procesado con aditivos, Cuche, mínimamente procesado con aditivos, Cuinos, mínimamente procesado con aditivos, Gorrinos, mínimamente procesado con aditivos, Lechones, mínimamente procesado con aditivos, Marranos, mínimamente procesado con aditivos, Porcinos, mínimamente procesado con aditivos, Puercos, mínimamente procesado con aditivos,Lechones, mínimamente procesado con aditivos",0,80509,0 -17954,50111520,"Pollo, mínimamente procesado con aditivos","Gallina, mínimamente procesado con aditivos",0,80509,0 -17955,50111521,"Pavo, mínimamente procesado sin aditivos",,0,80509,0 -17956,50111522,"Pavo, mínimamente procesado con aditivos",,0,80509,0 -17957,50111523,"Cordero , mínimamente procesado sin aditivos",,0,80509,0 -17958,50111524,"Cordero , mínimamente procesado con aditivos",,0,80509,0 -17959,50111525,"Chivo, mínimamente procesado sin aditivos",,0,80509,0 -17960,50111526,"Chivo, mínimamente procesado con aditivos",,0,80509,0 -17961,50111527,"Carne especializada, mínimamente procesada sin aditivos",,0,80509,0 -17962,50111528,"Carne especializada , mínimamente procesado con aditivos",,0,80509,0 -17963,50111529,"Aves especializadas, mínimamente procesada sin aditivos",,0,80509,0 -17964,50111530,"Aves especializadas, mínimamente procesada con aditivos",,0,80509,0 -17965,50111531,"Carne especial mixta, mínimamente procesada sin aditivos",,0,80509,0 -17966,50111532,"Carne especial mixta, mínimamente procesada con aditivos",,0,80509,0 -17967,50111533,"Pato, mínimamente procesada con aditivos",,0,80509,0 -17968,50111534,"Pato, mínimamente procesada sin aditivos",,0,80509,0 -17969,50111535,"Caballo, mínimamente procesada sin aditivos","Equino, mínimamente procesada sin aditivos",0,80509,0 -17970,50111536,"Caballo, mínimamente procesada con aditivos","Equino, mínimamente procesada con aditivos",0,80509,0 -17971,50111537,"Conejo, mínimamente procesada sin aditivos",,0,80509,0 -17972,50111538,"Conejo, , mínimamente procesada con aditivos",,0,80509,0 -17973,50111539,"Rana,, mínimamente procesada sin aditivos",,0,80509,0 -17974,50111540,"Rana, mínimamente procesada con aditivos",,0,80509,0 -17975,50111541,"Caracol de tierra, mínimamente procesada sin aditivos",,0,80509,0 -17976,50111542,"Caracol de tierra, mínimamente procesada con aditivos",,0,80509,0 -17977,50111543,"Culebra, mínimamente procesada sin aditivos","Víbora, mínimamente procesada sin aditivos",0,80509,0 -17978,50111544,"Culebra, mínimamente procesada con aditivos","Víbora, mínimamente procesada con aditivos",0,80509,0 -17979,50111545,"Tortuga, mínimamente procesada sin aditivos",,0,80509,0 -17980,50111546,"Tortuga, mínimamente procesada con aditivos",,0,80509,0 -17981,50111547,"Bisonte o búfalo, mínimamente procesada sin aditivos",,0,80509,0 -17982,50111548,"Bisonte o búfalo, mínimamente procesada con aditivos",,0,80509,0 -17983,50111549,"Llama o alpaca, mínimamente procesada sin aditivos",,0,80509,0 -17984,50112000,Carnes procesadas y preparadas,,0,80509,0 -17985,50112004,"Carne, procesada sin aditivos",,0,80509,0 -17986,50112005,"Carne, procesada con aditivos",,0,80509,0 -17987,50112006,Ternera. procesada sin aditivos,,0,80509,0 -17988,50112007,"Ternera, procesada con aditivos",,0,80509,0 -17989,50112008,"Cerdo, procesado sin aditivos","Chanchos, procesado sin aditivos, Cochinillos, procesado sin aditivos, Cochinos, procesado sin aditivos, Cuche , procesado sin aditivos, Cuinos, procesado sin aditivos, Gorrinos, procesado sin aditivos, Lechones, procesado sin aditivos, Marranos, procesado sin aditivos, Porcinos, procesado sin aditivos, Puercos, procesado sin aditivos,Lechones, procesado sin aditivos",0,80509,0 -17990,50112009,"Cerdo, procesado con aditivos","Chanchos, procesado con aditivos, Cochinillos, procesado con aditivos, Cochinos, procesado con aditivos, Cuche , procesado con aditivos, Cuinos, procesado con aditivos, Gorrinos, procesado con aditivos, Jamón de cerdo, Lechones, procesado con aditivos, Marranos, procesado con aditivos, Porcinos, procesado con aditivos, Puercos, procesado con aditivos, , Jamón de cerdo",0,80509,0 -17991,50112010,"Pollo, procesado sin aditivos","Gallina, procesado sin aditivos",0,80509,0 -17992,50112011,"Pollo, procesado con aditivos","Gallina, procesado con aditivos",0,80509,0 -17993,50112012,"Pavo, procesado sin aditivos",,0,80509,0 -17994,50112013,"Pavo, procesado con aditivos",Jamón de pavo,0,80509,0 -17995,50112014,"Cordero, procesado sin aditivos",,0,80509,0 -17996,50112015,"Cordero, procesado con aditivos",,0,80509,0 -17997,50112016,Chivo. procesado sin aditivos,,0,80509,0 -17998,50112017,"Chivo, procesado con aditivos",,0,80509,0 -17999,50112018,"Carne especializada, procesada sin aditivo",,0,80509,0 -18000,50112019,"Carne especializada, procesada con aditivo",,0,80509,0 -18001,50112020,"Aves especializadas, procesada sin aditivos",,0,80509,0 -18002,50112021,"Aves especializadas, procesada con aditivos",,0,80509,0 -18003,50112022,"Carne especializada mixta, procesada sin aditivos",,0,80509,0 -18004,50112023,"Carne especializada mixta, procesada con aditivos",,0,80509,0 -18005,50112024,"Pato, procesado con aditivos",,0,80509,0 -18006,50112025,"Pato, procesado sin aditivos",,0,80509,0 -18007,50112026,"Caballo, procesado sin aditivos","Equino, procesado sin aditivos",0,80509,0 -18008,50112027,"Caballo, procesado con aditivos","Equino, procesado con aditivos",0,80509,0 -18009,50112028,"Conejo, procesado sin aditivos",,0,80509,0 -18010,50112029,"Conejo, procesado con aditivos",,0,80509,0 -18011,50112030,"Rana, procesado sin aditivos",,0,80509,0 -18012,50112031,"Rana, procesado con aditivos",,0,80509,0 -18013,50112032,"Caracol de tierra, procesado sin aditivos",,0,80509,0 -18014,50112033,"Caracol de tierra, procesado con aditivo",,0,80509,0 -18015,50112034,"Culebra, procesado, sin aditivos","Víbora, procesado, sin aditivos",0,80509,0 -18016,50112035,"Culebra, procesado con aditivos","Víbora, procesado con aditivos",0,80509,0 -18017,50112036,"Tortuga, procesada sin aditivos",,0,80509,0 -18018,50112037,"Tortuga, procesada con aditivos",,0,80509,0 -18019,50112038,"Visón o búfalo, procesado sin aditivos",,0,80509,0 -18020,50112039,"Visón o búfalo, procesado con aditivos",,0,80509,0 -18021,50112040,"Llama o alpaca, procesado sin aditivos",,0,80509,0 -18022,50112041,"Llama o alpaca, procesado con aditivos",,0,80509,0 -18023,50112042,"Ganso, procesado sin aditivos",,0,80509,0 -18024,50112043,"Ganso, procesado con aditivos",,0,80509,0 -18025,50112044,"Avestruz, procesado sin aditivos",,0,80509,0 -18026,50112045,"Avestruz, procesado con aditivos",,0,80509,0 -18027,50112046,"Jabalí, procesado sin aditivos",,0,80509,0 -18028,50112047,"Jabalí, procesado con aditivos",,0,80509,0 -18029,50112048,"Venado, procesado sin aditivos",,0,80509,0 -18030,50112049,"Venado, procesado con aditivos",,0,80509,0 -18031,50112050,"Faisán, procesado sin aditivos",,0,80509,0 -18032,50112051,"Faisán, procesado con aditivos",,0,80509,0 -18033,50112052,"Codorniz, procesado sin aditivos",,0,80509,0 -18034,50112053,"Codorniz, procesado con aditivos",,0,80509,0 -18035,50121500,Pescado,,"0,1",80509,0 -18036,50121537,Pescado congelado,,"0,1",80509,0 -18037,50121538,Pescado almacenado en repisa,"Atún enlatado, Salmón enlatado, Sardina enlatad","0,1",80509,0 -18038,50121539,Pescado fresco,,"0,1",80509,0 -18039,50121600,Marisco fresco,,0,80509,0 -18040,50121611,Mariscos frescos,,0,80509,0 -18041,50121612,Mariscos congelados,,0,80509,0 -18042,50121613,Mariscos almacenados en repisa,,0,80509,0 -18043,50121700,Invertebrados acuáticos,,0,80509,0 -18044,50121705,Invertebrados acuáticos frescos,"Almejas, Ostiones, Pulpo",0,80509,0 -18045,50121706,Invertebrados acuáticos congelados,"Almejas, Ostiones, Pulpo",0,80509,0 -18046,50121707,Invertebrados acuáticos almacenado en repisa,"Almejas, Ostiones, Pulpo",0,80509,0 -18047,50121900,Pescados y mariscos preservados en sal,,0,80509,0 -18048,50121901,Pulpo en escabeche,,0,80509,0 -18049,50121902,Huevos de abadejo salado,,0,80509,0 -18050,50121903,Camarón salado,,0,80509,0 -18051,50131600,Huevos y sustitutos,,0,80509,0 -18052,50131609,Huevos preparados,,0,80509,0 -18053,50131612,Huevos en la cascara de gallina,Huevos de gallina en cáscara,0,80509,0 -18054,50131613,Huevos en la cascara de pato,Huevos de pato en cáscara,0,80509,0 -18055,50131614,Huevos en la cascara de emú,Huevos de emú en cáscara,0,80509,0 -18056,50131615,Huevos en la cascara de ganso,Huevos de ganso en cáscara,0,80509,0 -18057,50131616,Huevos en la cascara de gallineta,Huevos de gallina de guinea en cáscara,0,80509,0 -18058,50131617,Huevos en la cascara de avestruz,Huevos de avestruz en cáscara,0,80509,0 -18059,50131618,Huevos en la cascara codorniz,Huevos de codorniz en cáscara,0,80509,0 -18060,50131619,Huevos en la cascara de ñandú,Huevos de ñandú en cáscara,0,80509,0 -18061,50131620,Huevos en la cascara de paloma,Huevos de paloma en cáscara,0,80509,0 -18062,50131621,Huevos en la cascara de pavo,Huevos de pavo en cáscara,0,80509,0 -18063,50131622,Huevos en la cascara de pájaros especializados,Huevos de otras aves en cáscara,0,80509,0 -18064,50131623,Huevos con cascara sin clasificación de gallina,Huevos criollos de gallina en cáscara (nido),0,80509,0 -18065,50131624,Huevos con cascara sin clasificación de aves que son distintas de gallina,Huevos criollos de otras aves en cáscara (nido),0,80509,0 -18066,50131625,Huevos industriales revisados y sucios de gallina,Huevos sucios de gallina,0,80509,0 -18067,50131626,Huevos industriales revisados y sucios que son distintas de gallina,Huevos sucios de otras aves,0,80509,0 -18068,50131627,Huevos cocidos de gallina,,0,80509,0 -18069,50131628,Huevos cocidos distintos de gallina,,0,80509,0 -18070,50131629,Productos derivados de huevos y sustitutos de gallina,,0,80509,0 -18071,50131630,Huevos de gallina deshidratados,,0,80509,0 -18072,50131631,Productos derivados de huevos y sustitutos de aves distinto a gallina,,0,80509,0 -18073,50131632,Huevos distinto a gallina deshidratados,,0,80509,0 -18074,50131633,Extracto de huevo de gallina,,0,80509,0 -18075,50131634,Extracto de huevo de aves distinto a gallina,,0,80509,0 -18076,50131635,Imitación de huevo,,0,80509,0 -18077,50131700,Productos de leche y mantequilla,Productos lácteos y mantequilla,0,80509,0 -18078,50131701,Productos de leche o mantequilla frescos,"Crema, Crema de leche, Lácteos probióticos, Nata, Suero de mantequilla, Yogurt",0,80509,0 -18079,50131702,Productos de leche o mantequilla de estante,"Crema, Leche condensada, Leche evaporada, Leche maternizada, Leche saborizada, Lácteos probióticos, Nata, Suero de mantequilla, YogurtNata",0,80509,0 -18080,50131703,Productos de leche o mantequilla congelados,"Base para helado, Mantecado",0,80509,0 -18081,50131704,Leche en polvo,,0,80509,0 -18082,50131705,Proteína de suero de leche,,0,80509,0 -18083,50131706,Lactosa,"Leche cruda, Leche de otros animales, Leche de vaca, Leche fresca ,",0,80509,0 -18084,50131800,Queso,,0,80509,0 -18085,50131801,Queso natural,,0,80509,0 -18086,50131802,Queso procesado,,0,80509,0 -18087,50131803,Imitación de queso,,0,80509,0 -18088,50151500,Grasas y aceites vegetales comestibles,,0,80509,0 -18089,50151513,Aceites vegetales o de planta comestibles,"Aceite de ajonjolí, Aceite de canola (colza), Aceite de coco, Aceite de cártamo, Aceite de girasol, Aceite de linaza, Aceite de maíz, Aceite de olivo (oliva), Aceite de soya, Margarina,Aceite de maíz",0,80509,0 -18090,50151514,Grasas saturadas de vegetales o plantas comestibles,,0,80509,0 -18091,50151515,Leche de soya,,0,80509,0 -18092,50151600,Grasas y aceites animales comestibles,,0,80509,0 -18093,50151604,Aceites animal comestibles,"Animales marinos, Hígado de bacalao",0,80509,0 -18094,50151605,Grasa saturada animal comestibles,"Manteca, Sebo",0,80509,0 -18095,50161500,"Chocolates, azúcares, edulcorantes productos",,0,80509,0 -18096,50161509,Azucares naturales o productos endulzantes,"Azúcar moscabada o morena, Inulina de maguey o agave, Piloncill",0,80509,0 -18097,50161510,Endulzantes artificiales,"Azúcar refinada, Edulcorantes artificiales, Sustitutos de azúca",0,80509,0 -18098,50161511,Chocolate o sustituto de chocolate,,0,80509,0 -18099,50161512,Almíbar,Jarabe,0,80509,0 -18100,50161800,Productos de confitería,Dulces de confite,0,80509,0 -18101,50161813,"Chocolate o sustituto de chocolate, confite",,0,80509,0 -18102,50161814,"Azúcar o sustituto de azúcar, confite",,0,80509,0 -18103,50161815,Goma de mascar,,0,80509,0 -18104,50161900,Productos de azúcar,,0,80509,0 -18105,50171500,Hierbas y especias y extractos,Especias o extractos,0,80509,0 -18106,50171548,Hierbas frescas,,0,80509,0 -18107,50171550,Especies o extractos,"Ajo molido, Anís, Azafrán, Canela, Cebolla molida, Clavo de olor, Comino, Curry, Cúrcuma, Hierbas de olor, Pimienta, Tomillo, Comino",0,80509,0 -18108,50171551,Sal de mesa,,0,80509,0 -18109,50171552,Mezcla para adobar,,0,80509,0 -18110,50171553,Polvo de pimentón rojo,"Chile piquín molido,2",0,80509,0 -18111,50171554,Tallo de hierbas y semillas para infusiones,,0,80509,0 -18112,50171700,Vinagres y vinos de cocinar,,0,80509,0 -18113,50171707,Vinagres,,0,80509,0 -18114,50171708,Vinos para cocinar,,0,80509,0 -18115,50171800,Salsas y condimentos y productos para untar,,0,80509,0 -18116,50171830,Salsas o condimentos o cremas de untar o marinados,"Aderezo de mayonesa, Mayonesa, Mostaza, 13",0,80509,0 -18117,50171831,Salsas para cocinar,,0,80509,0 -18118,50171832,Salsas para ensaladas o dips,"Cátsup, Queso amarillo para fundir, Salsa de tomate, Salsa picante",0,80509,0 -18119,50171833,Cremas de untar saladas o patés,,0,80509,0 -18120,50171834,Pasta de frijol chino,,0,80509,0 -18121,50171900,Salmuera y salsa y aceitunas,,0,80509,0 -18122,50171901,Encurtidos,"Aceitunas, Cebollitas, Chiles en vinagre, Elotitos, Zanahorias baby,-1",0,80509,0 -18123,50171902,Condimento,,0,80509,0 -18124,50171904,Conserva,"Conservas de frutas,-1",0,80509,0 -18125,50172000,Productos de fríjol de soya fermentados,,0,80509,0 -18126,50172001,Salsa soya,,0,80509,0 -18127,50172003,Pasta de frijol de soya,,0,80509,0 -18128,50172004,Pasta mixta basada en soya,,0,80509,0 -18129,50172005,Mejoo o frijol de soya fermentado,,0,80509,0 -18130,50172006,Queso de soya,,0,80509,0 -18131,50181700,Suministros y mezclas para hornear,,0,80509,0 -18132,50181708,Mezclas para hornear,"Harinas para repostería, Levadura en polvo, , -",0,80509,0 -18133,50181709,Suministros para hornear,"Empaques, Envolturas, Moldes,",0,80509,0 -18134,50181900,"Pan, galletas y pastelitos dulces",,0,80509,0 -18135,50181901,Pan fresco,"Bigotes, Bolillos, Bísquets, Cemitas, Churros, Cocoles, Conchas, Cuernitos, Donas, Orejas, Pan de feria, Pan de muerto, Conchas, Pan integral, Pan salado, Panquecitos, Roscas de reyes, Teleras, Trenzas,",0,80509,0 -18136,50181902,Pan congelado,Pan congelado precocido,0,80509,0 -18137,50181903,Galletas sencillas de sal,,0,80509,0 -18138,50181904,Pan seco o cascaras de pan o pan tostado ( crotones),,0,80509,0 -18139,50181905,Galletas de dulce,Surtido de galletas,0,80509,0 -18140,50181906,Pan de repisa,"Bigotes, Bolillos, Bísquets, Cemitas, Churros, Cocoles, Conchas, Cuernitos, Donas, Orejas, Pan de feria, Pan de muerto, Conchas, Pan integral, Pan salado, Panquecitos, Roscas de reyes, Teleras, Trenzas,",0,80509,0 -18141,50181907,Maza congelada para galletas,Masa congelada para galletas,0,80509,0 -18142,50181909,Galletas de soda,Masa congelada para pan,0,80509,0 -18143,50182000,Tartas y empanadas y pastas,Galletas horneadas saladas,0,80509,0 -18144,50182001,Ponqués pasteles o biscochos frescos,Panqués y pasteles frescos,0,80509,0 -18145,50182002,Ponqués pasteles o biscochos congelados,Panqués y pasteles congelados,0,80509,0 -18146,50182003,Maza para pastelería congelada,Masa para pastelería congelada,0,80509,0 -18147,50182004,Maza para galletas de soda congelada,Masa para galletas de soda congelada,0,80509,0 -18148,50182005,Galletas de arroz,,0,80509,0 -18149,50191500,Sopas y estofados,,0,80509,0 -18150,50191505,Sopas o sudados preparados fresco,"Estofados preparados frescos, Sopa de verduras",0,80509,0 -18151,50191506,Sopas o sudados preparados congelados,Estafados preparados congelados,0,80509,0 -18152,50191507,Sopas o sudados preparados de repisa,Estofados preparados de repisa,0,80509,0 -18153,50192100,Botanas,,0,80509,0 -18154,50192109,Papas fritas de talego o mezclas,Botanas surtidas,0,80509,0 -18155,50192110,Nueces o fruta disecada,Nueces y frutas secas,0,80509,0 -18156,50192111,Carne seca o procesada,,0,80509,0 -18157,50192112,Maíz pira,"Maíz palomero, Maíz para palomitas, Palomitas de maí",0,80509,0 -18158,50192113,Mariscos y filete de pescado seco,,0,80509,0 -18159,50192300,Postres y decoraciones de postres,,0,80509,0 -18160,50192301,Postres preparados,"Flanes, Fresas con crema, Gelatina",0,80509,0 -18161,50192302,Complementos de postres,,0,80509,0 -18162,50192303,Helado de sabor o helado o postre de helado o yogurt congelado,"Cubos de hielo de sabores, Paletas de hielo",0,80509,0 -18163,50192304,Conos o copas de helado comestibles,,0,80509,0 -18164,50192400,Mermeladas y gelatinas y pastas de untar de nuez y dulce y conservas de fruta,,0,80509,0 -18165,50192401,Mermeladas o preservativos de fruta,"Mermeladas, jaleas o conservas de fruta",0,80509,0 -18166,50192402,Mantequilla de nueces o mixto,Mantequilla de nueces mixtas,0,80509,0 -18167,50192403,Miel,"Miel de abeja, Miel de agave, Miel de mague",0,80509,0 -18168,50192404,Cristales de gelatina o mermelada,"Grenetina, Polvo para preparar flan, Polvo para preparar gelatin",0,80509,0 -18169,50192405,Gelatina real procesada,Productos procesados de jalea real,0,80509,0 -18170,50192406,Gelatina de almidón de nuez,Jaleas de bellotas,0,80509,0 -18171,50192500,Sándwiches y panecillos con relleno,Bocadillos,0,80509,0 -18172,50192501,Emparedados frescos,"Bocadillos frescos, Sándwiches frescos",0,80509,0 -18173,50192502,Emparedados congelados,"Bocadillos congelados, Sándwiches congelados",0,80509,0 -18174,50192503,Rellenos frescos para emparedados,"Rellenos frescos para bocadillos, Rellenos frescos para sándwiches",0,80509,0 -18175,50192504,Rellenos congelados para emparedados,"Rellenos congelados para bocadillos, Rellenos congelados para sándwiches",0,80509,0 -18176,50192600,Acompañamientos preparados,,0,80509,0 -18177,50192601,Papas preparadas frescas o arroz o pasta o relleno,,0,80509,0 -18178,50192602,Papas preparadas y congeladas o arroz o pasta o relleno,,0,80509,0 -18179,50192603,Papas preparadas de repisa o arroz o pasta o relleno,,0,80509,0 -18180,50192700,Platos combinados empaquetados,,0,80509,0 -18181,50192701,Comidas combinadas frescas,,0,80509,0 -18182,50192702,Comidas combinadas congeladas,,0,80509,0 -18183,50192703,Comidas combinadas de repisa,,0,80509,0 -18184,50192800,Empanadas y quiches y pasteles,,0,80509,0 -18185,50192801,Pasteles de sal frescos,"Empanadas frescas, Pastes frescos",0,80509,0 -18186,50192802,Pasteles de sal congelados,"Empanadas congeladas, Pastes congelados",0,80509,0 -18187,50192803,Pasteles de sal de repisa,"Empanadas de repisa, Pastes de repisa",0,80509,0 -18188,50192900,Pasta o tallarines natural,,0,80509,0 -18189,50192901,Pasta sencilla o fideos,,0,80509,0 -18190,50192902,Pasta o fideos de repisa,,0,80509,0 -18191,50193000,Bebidas y Comidas Infantiles,,0,80509,0 -18192,50193001,Comida para infante,Alimentos infantiles,0,80509,0 -18193,50193002,Bebidas para infantes,Bebidas infantiles,0,80509,0 -18194,50193100,Materiales y mezclas instantáneas,"Arcones navideños, Despensas básicas",0,80509,0 -18195,50193101,Mezcla de botanas instantáneas,Pastas para elaborar botanas,0,80509,0 -18196,50193102,Mezcla de postres,Ingredientes preparados para postres,0,80509,0 -18197,50193103,Mezcla de salsa,Ingredientes preparados para salsas,0,80509,0 -18198,50193104,Base para sopas,"Caldillo de tomate, Caldo de camarón, Caldo de pollo, Caldo de res, Consome de pollo, Cremas, Pure de tomate, Sazonadores, Pure de tomate",0,80509,0 -18199,50193105,Mezcla para rebosar o de pan,Empanizadores,0,80509,0 -18200,50193106,Puré instantáneo,,0,80509,0 -18201,50193107,Puré de papa instantáneo,,0,80509,0 -18202,50193108,Puré preparado de varios vegetales,,0,80509,0 -18203,50193200,Ensaladas preparadas,,0,80509,0 -18204,50193201,Ensalada fresca preparada,,0,80509,0 -18205,50193202,Ensalada preparada congelada,,0,80509,0 -18206,50193203,Ensalada fresca de repisa,,0,80509,0 -18207,50201700,Café y té,,0,80509,0 -18208,50201706,Café,Café molido,0,80509,0 -18209,50201707,Sustituto de café,,0,80509,0 -18210,50201708,Bebida de café,,0,80509,0 -18211,50201709,Café instantáneo,Café soluble,0,80509,0 -18212,50201710,Té de hoja,,0,80509,0 -18213,50201711,Té instantáneo,,0,80509,0 -18214,50201712,Bebidas de té,,0,80509,0 -18215,50201713,Bolsas de té,Sobres de té,0,80509,0 -18216,50201714,Cremas no lácteas,,0,80509,0 -18217,50201715,Té de frutas,,0,80509,0 -18218,50202200,Bebidas alcohólicas,"Bebidas fermentadas y destiladas, Pulque","0,1",80509,0 -18219,50202201,Cerveza,,"0,1",80509,0 -18220,50202202,Cidra,Sidra,"0,1",80509,0 -18221,50202203,Vino,,"0,1",80509,0 -18222,50202204,Vino fortificado,,"0,1",80509,0 -18223,50202205,Vino espumoso,,"0,1",80509,0 -18224,50202206,Licor destilado,"Aguardiente o charanda, Brandy, Coñac, Ginebra, Licor de cocuy, Mezcal, Ron, Tequila, Vodka, Whisky,Ron","0,1",80509,0 -18225,50202207,Cocteles de alcohol o bebidas mixtas,,"0,1",80509,0 -18226,50202208,Takju - vino de arroz coreano,,"0,1",80509,0 -18227,50202209,Soju - bebida destilada de grano,,"0,1",80509,0 -18228,50202210,Vino de arroz,,"0,1",80509,0 -18229,50202300,Bebidas no alcohólicas,,0,80509,0 -18230,50202301,Agua,"Agua embotellada, Agua potable, Agua purificad",0,80509,0 -18231,50202302,Hielo,,0,80509,0 -18232,50202303,Jugos congelados,,0,80509,0 -18233,50202304,Jugos de repisa,Jugos de fruta naturales o con conservadores envasados,0,80509,0 -18234,50202305,Jugo fresco,,0,80509,0 -18235,50202306,Refrescos,,0,80509,0 -18236,50202307,Bebida de chocolate o malta u otros,,0,80509,0 -18237,50202308,Cocteles libre de alcohol o mezcla de bebidas,,0,80509,0 -18238,50202309,Bebidas deportivas o de energía,,0,80509,0 -18239,50202310,Agua mineral,,0,80509,0 -18240,50202311,Bebida mixta de polvo,"Concentrados y jarabes para preparar bebidas saborizadas, Polvo de sabores para preparar aguas",0,80509,0 -18241,50202400,Jugos o concentrados de cítricos frescos,,0,80509,0 -18242,50202401,Jugo de mandarina clementina,,0,80509,0 -18243,50202403,Jugo de kumquat,Jugo de naranjos chinos (kumquats),0,80509,0 -18244,50202404,Jugo de limón,,0,80509,0 -18245,50202405,Jugo de limón amarillo,,0,80509,0 -18246,50202406,Jugo de lima,,0,80509,0 -18247,50202407,Jugo de mandarina,,0,80509,0 -18248,50202408,Jugo de tangelo minneola,Jugo de mandarina y pomelo (minneola),0,80509,0 -18249,50202409,Jugo de naranja,,0,80509,0 -18250,50202410,Jugo de pomelo,Jugo de toronja,0,80509,0 -18251,50202411,Jugo de satsuma,Jugo de tangerina satsuma,0,80509,0 -18252,50202412,Jugo de tangelo,,0,80509,0 -18253,50202413,Jugo de tangerina,,0,80509,0 -18254,50202414,Jugo de temple,Jugo de mandarina royal,0,80509,0 -18255,50202415,Jugo de ugli,Jugo de mandarina y pomelo (ugli),0,80509,0 -18256,50202416,Concentrado de mandarina clementina,,0,80509,0 -18257,50202418,Concentrado de kumquat,Concentrado de naranjos chinos (kumquats),0,80509,0 -18258,50202419,Concentrado de limón,,0,80509,0 -18259,50202420,Concentrado de limón amarillo,,0,80509,0 -18260,50202421,Concentrado de mandarina,,0,80509,0 -18261,50202422,Concentrado de minneola,Concentrado de mandarina y pomelo (minneola),0,80509,0 -18262,50202423,Concentrado de naranja,,0,80509,0 -18263,50202424,Concentrado de pomelo,Concentrado de toronja,0,80509,0 -18264,50202425,Concentrado de satsuma,Concentrado de tangerina satsuma,0,80509,0 -18265,50202426,Concentrado de tangelo,,0,80509,0 -18266,50202427,Concentrado de mandarina,,0,80509,0 -18267,50202428,Concentrado de temple,Concentrado de mandarina royal,0,80509,0 -18268,50202429,Concentrado de ugli,Concentrado de mandarina y pomelo (ugli),0,80509,0 -18269,50202500,Jugos o concentrados de bayas frescas,,0,80509,0 -18270,50202501,Concentrado de mora,,0,80509,0 -18271,50202502,Concentrado de agras,Concentrado de frutos azules del bosque,0,80509,0 -18272,50202503,Concentrado de arándano,Concentrado de arándano rojo,0,80509,0 -18273,50202504,Concentrado de uva,,0,80509,0 -18274,50202505,Concentrado de frambuesa,,0,80509,0 -18275,50202506,Concentrado de fresa,,0,80509,0 -18276,50202507,Jugo de boysenberry,Jugo de zarzamora boysen,0,80509,0 -18277,50202508,Jugo de grosella,,0,80509,0 -18278,50202509,Jugo de mora,,0,80509,0 -18279,50202510,Jugo de agras,Jugo de frutos azules del bosque,0,80509,0 -18280,50202511,Jugo de frambuesa,,0,80509,0 -18281,50202512,Jugo de fresa,,0,80509,0 -18282,50202513,Jugo de uva,,0,80509,0 -18283,50202600,Jugos o concentrados de especies con pepitas frescas,,0,80509,0 -18284,50202601,Jugo de manzana,,0,80509,0 -18285,50202602,Jugo de pera,,0,80509,0 -18286,50202700,Jugos o concentrados de especies con una semilla grande frescas,,0,80509,0 -18287,50202701,Jugo de albaricoque,Jugo de chabacano,0,80509,0 -18288,50202702,Jugo de cereza,,0,80509,0 -18289,50202703,Jugo de nectarina,,0,80509,0 -18290,50202704,Jugo de durazno,,0,80509,0 -18291,50202705,Jugo de ciruela,,0,80509,0 -18292,50202706,Jugo de ciruela pasa,,0,80509,0 -18293,50202800,Jugos o concentrados de especies tropicales frescas,,0,80509,0 -18294,50202801,Concentrado de crema de coco,,0,80509,0 -18295,50202802,Concentrado de fruta de kiwi,Concentrado de kiwi,0,80509,0 -18296,50202803,Concentrado de mango,,0,80509,0 -18297,50202804,Concentrado de maracuyá,,0,80509,0 -18298,50202805,Concentrado de piña,,0,80509,0 -18299,50202806,Concentrado de carambola,Concentrado de fruta de estrella,0,80509,0 -18300,50211502,Cigarrillos o cigarros,,0,80509,0 -18301,50211503,Tabaco para pipa o tabaco de hoja,,0,80509,0 -18302,50211504,Tabaco para mascar,,0,80509,0 -18303,50211505,Cigarrillos herbales,,0,80509,0 -18304,50211506,Rapé,Tabaco para aspirar,0,80509,0 -18305,50211600,"Productos, suministros y accesorios para tabaco",,0,80509,0 -18306,50211607,Papel para cigarrillos o filtros,Papel de liar,0,80509,0 -18307,50211608,Encendedores o mecha,,0,80509,0 -18308,50211609,Pipas para fumar,,0,80509,0 -18309,50211610,Limpiadores de tabaco para pipas,,0,80509,0 -18310,50211611,Juego de fumador,,0,80509,0 -18311,50211612,Encendedores para cigarrillos,,0,80509,0 -18312,50221000,Legumbres,,0,80509,0 -18313,50221001,Granos,,0,80509,0 -18314,50221002,Harina,,0,80509,0 -18315,50221100,Cereales,"Granola, Muesli",0,80509,0 -18316,50221101,Grano de cereal,,0,80509,0 -18317,50221102,Grano de harina,,0,80509,0 -18318,50221200,Cereales procesados,,0,80509,0 -18319,50221201,Cereal caliente o listo para comer,,0,80509,0 -18320,50221202,Barras de desayuno o de salud,Barras nutritivas,0,80509,0 -18321,50221300,Harina y productos de molinos,"Masa, Mole en pasta, Mole en polvo, Nixtamal, Tortillas de harina, Tortillas de maíz, Tortillas de maíz azul, Tortillas de nopal, Tortillas integrales, Tostadas, Tostadas de maíz horneadas, Totopos, Tortillas de maíz azul",0,80509,0 -18322,50221301,Harina vegetal,,0,80509,0 -18323,50221302,Malta de cebada,,0,80509,0 -18324,50221303,Almidón o harina comestible,"Harina de fécula de maíz, Maicena",0,80509,0 -18325,50221304,Harina de papa,,0,80509,0 -18326,50301500,Manzanas,,0,80509,0 -18327,50301501,Manzanas akane,Manzana akane,0,80509,0 -18328,50301502,Manzana ambrosia,,0,80509,0 -18329,50301503,Manzanas api,Manzana api,0,80509,0 -18330,50301504,Manzanas baldwin,Manzana baldwin,0,80509,0 -18331,50301505,Manzanas brabum,Manzana brabum,0,80509,0 -18332,50301506,Manzanas bramley,Manzana bramley,0,80509,0 -18333,50301507,Manzana joven bramley,,0,80509,0 -18334,50301508,Manzana calville blanche d’hiver,,0,80509,0 -18335,50301509,Manzana cameo,,0,80509,0 -18336,50301510,Manzana charles ross,,0,80509,0 -18337,50301511,Manzana codlin,,0,80509,0 -18338,50301512,Manzana cortland,,0,80509,0 -18339,50301513,Manzana costard,,0,80509,0 -18340,50301514,Manzana court pendu plat,,0,80509,0 -18341,50301515,Manzana cox´s orange pippin,,0,80509,0 -18342,50301516,Manzana crab,,0,80509,0 -18343,50301517,Manzana crispin,,0,80509,0 -18344,50301518,Manzana delicious,,0,80509,0 -18345,50301519,Manzana duchess,,0,80509,0 -18346,50301520,Manzana earlidorado,,0,80509,0 -18347,50301521,Manzana early mcintosh,,0,80509,0 -18348,50301522,Manzana elstar,,0,80509,0 -18349,50301523,Manzana imperio,,0,80509,0 -18350,50301524,Manzana flor de kent,,0,80509,0 -18351,50301525,Manzana fuji,,0,80509,0 -18352,50301526,Manzana gala,,0,80509,0 -18353,50301527,Manzana gascoyne´s scarlet,,0,80509,0 -18354,50301528,Manzana gilliflower,,0,80509,0 -18355,50301529,Manzana ginger dorado,,0,80509,0 -18356,50301530,Manzana gladstone,,0,80509,0 -18357,50301531,Manzana gloster,,0,80509,0 -18358,50301532,Manzana dorado supreme,,0,80509,0 -18359,50301533,Manzana dorado delicious,,0,80509,0 -18360,50301534,Manzana dorado nobel,,0,80509,0 -18361,50301535,Manzana granny smith,,0,80509,0 -18362,50301536,Manzana gravenstain,,0,80509,0 -18363,50301537,Manzana greening,,0,80509,0 -18364,50301538,Manzana greensleeves,,0,80509,0 -18365,50301539,Manzana honeycrisp,,0,80509,0 -18366,50301540,Manzana howgate wonder,,0,80509,0 -18367,50301541,Manzana rojo ida,,0,80509,0 -18368,50301542,Manzana james grives,,0,80509,0 -18369,50301543,Manzana jersey mac,,0,80509,0 -18370,50301544,Manzana jester,,0,80509,0 -18371,50301545,Manzana jonadorado,,0,80509,0 -18372,50301546,Manzana jonamac,,0,80509,0 -18373,50301547,Manzana jonathan,,0,80509,0 -18374,50301548,Manzana katy,,0,80509,0 -18375,50301549,Manzana kidd´s orange rojo,,0,80509,0 -18376,50301550,Manzana dama,,0,80509,0 -18377,50301551,Manzana ley de roma,,0,80509,0 -18378,50301552,Manzana laxton,,0,80509,0 -18379,50301553,Manzana lord derby,,0,80509,0 -18380,50301554,Manzana macoun,,0,80509,0 -18381,50301555,Manzana mcintosh,,0,80509,0 -18382,50301556,Manzana mutsu,,0,80509,0 -18383,50301557,Manzana newtown pippin,,0,80509,0 -18384,50301558,Manzana northen spy,,0,80509,0 -18385,50301559,Manzana orleans reinette,,0,80509,0 -18386,50301560,Manzana ozark dorado,,0,80509,0 -18387,50301561,Manzana pacific rose,,0,80509,0 -18388,50301562,Manzana paula rojo,,0,80509,0 -18389,50301563,Manzana peamain,,0,80509,0 -18390,50301564,Manzana dama rosada,,0,80509,0 -18391,50301565,Manzana pippin,,0,80509,0 -18392,50301566,Manzana pitmaston piña,,0,80509,0 -18393,50301567,Manzana pomme dápi,,0,80509,0 -18394,50301568,Manzana prime dorado,,0,80509,0 -18395,50301569,Manzana rojo astrachan,,0,80509,0 -18396,50301570,Manzana rojo boscoop,,0,80509,0 -18397,50301571,Manzana jefe rojo,,0,80509,0 -18398,50301572,Manzana rojo delecioso,,0,80509,0 -18399,50301573,Manzana rojo gravenstain,,0,80509,0 -18400,50301574,Manzana roma roja,,0,80509,0 -18401,50301575,Manzana rojo stayman,,0,80509,0 -18402,50301576,Manzana rojo york,,0,80509,0 -18403,50301577,Manzana reinette,,0,80509,0 -18404,50301578,Manzana bellesa de roma,,0,80509,0 -18405,50301579,Manzana russet,,0,80509,0 -18406,50301580,Manzana belleza sierra,,0,80509,0 -18407,50301581,Manzana spartan,,0,80509,0 -18408,50301582,Manzana stark crimson,,0,80509,0 -18409,50301583,Manzana starrey,,0,80509,0 -18410,50301584,Manzana stayman,,0,80509,0 -18411,50301585,Manzana stayman winesap,,0,80509,0 -18412,50301586,Manzana summer rambo,,0,80509,0 -18413,50301587,Manzana tsugaru,,0,80509,0 -18414,50301588,Manzanaveine onzas,,0,80509,0 -18415,50301589,Manzana tydeman rojo,,0,80509,0 -18416,50301590,Manzana vistabella,,0,80509,0 -18417,50301591,Manzana weatley,,0,80509,0 -18418,50301592,Manzana blanco joanetine,,0,80509,0 -18419,50301593,Manzana blanco transparente,,0,80509,0 -18420,50301594,Manzana winesap,,0,80509,0 -18421,50301595,Manzana worcester,,0,80509,0 -18422,50301596,Manzana york imperial,,0,80509,0 -18423,50301597,Manzana anna,,0,80509,0 -18424,50301700,Bananos,Plátanos,0,80509,0 -18425,50301701,Banana apple,Plátano apple,0,80509,0 -18426,50301702,Banana bebe,Plátano bebe,0,80509,0 -18427,50301703,Banana burro,Plátano burro,0,80509,0 -18428,50301704,Bananas cavendish,Plátano cavendish,0,80509,0 -18429,50301705,Bananas dominico,Plátano dominico,0,80509,0 -18430,50301706,Bananas green,Plátano green,0,80509,0 -18431,50301707,Bananas gros michel,Plátano gros michel,0,80509,0 -18432,50301708,Bananas lacatan,Plátano lacatan,0,80509,0 -18433,50301709,Bananas dedo de dama,Plátano dedo de dama,0,80509,0 -18434,50301710,Bananas manzano,Plátano manzano,0,80509,0 -18435,50301711,Banana mysore,Plátano mysore,0,80509,0 -18436,50301712,Banana pisang mas,Plátano pisang mas,0,80509,0 -18437,50301713,Bananas rojo,Plátano rojo,0,80509,0 -18438,50301714,Bananas saba,Plátano saba,0,80509,0 -18439,50301715,Bananas sucrier,Plátano sucrier,0,80509,0 -18440,50301716,Bananas palillo,Plátano palillo,0,80509,0 -18441,50301717,Bananas purple,Plátano purple,0,80509,0 -18442,50301718,Bananas isla,Plátano isla,0,80509,0 -18443,50302000,Moras,Zarzamoras,0,80509,0 -18444,50302001,Mora apache,Zarzamora apache,0,80509,0 -18445,50302002,Mora negro satin,Zarzamora negro satín,0,80509,0 -18446,50302003,Mora,Zarzamora,0,80509,0 -18447,50302004,Mora cherokee,Zarzamora cherokee,0,80509,0 -18448,50302005,Mora chester,Zarzamora chéster,0,80509,0 -18449,50302006,Mora dirksen,Zarzamora dirksen,0,80509,0 -18450,50302007,Bayas josta,Zarzamora josta,0,80509,0 -18451,50302008,Bayas logan,Zarzamora logan,0,80509,0 -18452,50302009,Bayas marion,Zarzamora marion,0,80509,0 -18453,50302010,Mora navaho,Zarzamora navaho,0,80509,0 -18454,50302011,Baya nectar,Zarzamora néctar,0,80509,0 -18455,50302012,Mora olallie,Zarzamora olallie,0,80509,0 -18456,50302013,Baya tay,Zarzamora tay,0,80509,0 -18457,50302014,Mora thomless hull,Zarzamora thomless hull,0,80509,0 -18458,50302015,Baya young,Zarzamora young,0,80509,0 -18459,50302100,Arándanos de mirto,Mirtillos,0,80509,0 -18460,50302101,Arandanos bog,Mirtillo bog,0,80509,0 -18461,50302102,Arandanos dwarf,Mirtillo enano,0,80509,0 -18462,50302103,Arandanos mountain,Mirtillo de montaña,0,80509,0 -18463,50302104,Arandanos oval-leaved,Mirtillo oval-leaved,0,80509,0 -18464,50302500,Cerezas,,0,80509,0 -18465,50302501,Cereza amarelle,,0,80509,0 -18466,50302502,Cereza brooks,,0,80509,0 -18467,50302503,Cereza bigarreu,,0,80509,0 -18468,50302504,Cereza bing,,0,80509,0 -18469,50302505,Cereza blach republic,,0,80509,0 -18470,50302506,Cereza negro schmidt,,0,80509,0 -18471,50302507,Cereza negrotartarian,,0,80509,0 -18472,50302508,Cereza fiesta bing,,0,80509,0 -18473,50302509,Cereza garnet,,0,80509,0 -18474,50302510,Cereza rey,,0,80509,0 -18475,50302511,Cereza chapman,,0,80509,0 -18476,50302512,Cereza lapin,,0,80509,0 -18477,50302513,Cereza larian,,0,80509,0 -18478,50302514,Cereza dark guines,,0,80509,0 -18479,50302515,Cereza montmorency,,0,80509,0 -18480,50302516,Cereza duque,,0,80509,0 -18481,50302517,Cereza early rivers,,0,80509,0 -18482,50302518,Cereza ruby bing,,0,80509,0 -18483,50302519,Cereza santine,,0,80509,0 -18484,50302520,Cereza geans/guines,,0,80509,0 -18485,50302521,Cereza sonata,,0,80509,0 -18486,50302522,Cereza lambert,,0,80509,0 -18487,50302523,Cereza stella,,0,80509,0 -18488,50302524,Cereza sweethart,,0,80509,0 -18489,50302525,Cereza tartarian,,0,80509,0 -18490,50302527,Cereza maraschino,,0,80509,0 -18491,50302528,Cereza van,,0,80509,0 -18492,50302529,Cereza morello,,0,80509,0 -18493,50302530,Cereza royal ann,,0,80509,0 -18494,50302531,Cereza ranier,,0,80509,0 -18495,50302532,Cereza royal,,0,80509,0 -18496,50302533,Cereza verde,,0,80509,0 -18497,50302700,Arándanos,Arándanos rojos,0,80509,0 -18498,50302701,Arandano ben lear,Arándano rojo ben lear,0,80509,0 -18499,50302702,Arandano early negro,Arándano rojo early negro,0,80509,0 -18500,50302703,Arandano gryclesik,Arándano rojo gryclesik,0,80509,0 -18501,50302704,Arandano howe,Arándano rojo howe,0,80509,0 -18502,50302705,Bayas lingon,Arándano rojo lingon,0,80509,0 -18503,50302706,Arandano mcfarling,Arándano rojo mcfarling,0,80509,0 -18504,50302707,Arandano mauntain,Arándano rojo mauntain,0,80509,0 -18505,50302708,Arandano pilgrim,Arándano rojo pilgrim,0,80509,0 -18506,50302709,Arandano searless,Arándano rojo searless,0,80509,0 -18507,50302710,Arandano stevens,Arándano rojo stevens,0,80509,0 -18508,50302900,Dátiles,,0,80509,0 -18509,50302901,Dátiles asharasi,,0,80509,0 -18510,50302902,Dátiles barhi o barhee,,0,80509,0 -18511,50302903,Dátiles deglet noor,,0,80509,0 -18512,50302904,Dátiles fardh,,0,80509,0 -18513,50302905,Dátiles gundila,,0,80509,0 -18514,50302906,Dátiles halawi/halawy,,0,80509,0 -18515,50302907,Dátiles hilali,,0,80509,0 -18516,50302908,Dátiles khadrawi/khadrawy,,0,80509,0 -18517,50302909,Dátiles khalas,,0,80509,0 -18518,50302910,Dátiles khustawi,,0,80509,0 -18519,50302911,Dátiles khidri,,0,80509,0 -18520,50302912,Dátiles medjool/ medjol,,0,80509,0 -18521,50302913,Dátiles mactoum,,0,80509,0 -18522,50302914,Dátiles neghal,,0,80509,0 -18523,50302915,Dátiles yatimeh,,0,80509,0 -18524,50302916,Dátiles zahidi,,0,80509,0 -18525,50303000,Pitahayas,,0,80509,0 -18526,50303001,"Pitahaya roja, rosada",,0,80509,0 -18527,50303002,"Pitahaya roja, amarillo",,0,80509,0 -18528,50303100,Brevas,Higos,0,80509,0 -18529,50303101,Breva bardajic,Higo bardajic,0,80509,0 -18530,50303102,Breva brown turkey,Higo brown turkey,0,80509,0 -18531,50303103,Breva calimyma,Higo calimyma,0,80509,0 -18532,50303104,Breva conadria,Higo conadria,0,80509,0 -18533,50303105,Breva dottado,Higo dottado,0,80509,0 -18534,50303106,Breva kadota,Higo kadota,0,80509,0 -18535,50303107,Breva mediterránea,Higo mediterránea,0,80509,0 -18536,50303108,Breva mision,Higo misión,0,80509,0 -18537,50303109,Breva amyma,Higo amyma,0,80509,0 -18538,50303110,Breva verdona,Higo verdona,0,80509,0 -18539,50303111,Breva rey blanco,Higo rey blanco,0,80509,0 -18540,50303300,Toronjas,,0,80509,0 -18541,50303301,Toronja burgundi,,0,80509,0 -18542,50303302,Toronja duncan,,0,80509,0 -18543,50303303,Toronja foster,,0,80509,0 -18544,50303304,Toronja marsh,,0,80509,0 -18545,50303305,Toronja nueva zelandia,,0,80509,0 -18546,50303306,Toronja rio rojo,,0,80509,0 -18547,50303307,Toronja ruby rojo,,0,80509,0 -18548,50303308,Toronja star ruby,,0,80509,0 -18549,50303309,Toronja triumph,,0,80509,0 -18550,50303400,Uvas de mesa,,0,80509,0 -18551,50303401,Uva alicante,,0,80509,0 -18552,50303402,Uva almeira,,0,80509,0 -18553,50303403,Uva alphonse lavalle,,0,80509,0 -18554,50303404,Uva otoño rey grapes,,0,80509,0 -18555,50303405,Uva otoño royal,,0,80509,0 -18556,50303406,Uva otoño sin pepa,,0,80509,0 -18557,50303407,Uva baresana,,0,80509,0 -18558,50303408,Uva barlinka,,0,80509,0 -18559,50303409,Uva beaty sin pepa,,0,80509,0 -18560,50303410,Uva negro beauty sin pepa,,0,80509,0 -18561,50303411,Uva negro emerald,,0,80509,0 -18562,50303412,Uva negro gigante,,0,80509,0 -18563,50303413,Uva negro globo,,0,80509,0 -18564,50303414,Uva negro monnukka,,0,80509,0 -18565,50303415,Uva negro perla,,0,80509,0 -18566,50303416,Uva negro sin semilla,,0,80509,0 -18567,50303417,Uva bonheur,,0,80509,0 -18568,50303418,Uva calmeria,,0,80509,0 -18569,50303419,Uva cardinal,,0,80509,0 -18570,50303420,Uva catawba,,0,80509,0 -18571,50303421,Uva chasselas/doradoen chasselas,,0,80509,0 -18572,50303422,Uva christmas rose,,0,80509,0 -18573,50303423,Uva concord,,0,80509,0 -18574,50303424,Uva concord sin pepa,,0,80509,0 -18575,50303425,Uva crimson sin pepa,,0,80509,0 -18576,50303426,Uva dauphine,,0,80509,0 -18577,50303427,Uva daleware,,0,80509,0 -18578,50303428,Uva early muscat,,0,80509,0 -18579,50303429,Uva early sweet,,0,80509,0 -18580,50303430,Uva esmerald sin pepa,,0,80509,0 -18581,50303431,Uva emperatriz,,0,80509,0 -18582,50303432,Uva emperor,,0,80509,0 -18583,50303433,Uva empress,,0,80509,0 -18584,50303434,Uva exotic,,0,80509,0 -18585,50303435,Uva fantasia,,0,80509,0 -18586,50303436,Uva fantasy sin pepa,,0,80509,0 -18587,50303437,Uva llama,,0,80509,0 -18588,50303438,Uva llama sin pepa,,0,80509,0 -18589,50303439,Uva llama tokay,,0,80509,0 -18590,50303440,Uva flaming rojo,,0,80509,0 -18591,50303441,Uva galaxy sin pepa,,0,80509,0 -18592,50303442,Uva gamay,,0,80509,0 -18593,50303443,Uva dorada,,0,80509,0 -18594,50303444,Uva hanepot o honeypot,,0,80509,0 -18595,50303445,Uva italia,,0,80509,0 -18596,50303446,Uva jade sin pepa,,0,80509,0 -18597,50303447,Uva jubilee,,0,80509,0 -18598,50303448,Uva rey ruby,,0,80509,0 -18599,50303449,Uva kyoho,,0,80509,0 -18600,50303450,Uva la rochelle,,0,80509,0 -18601,50303451,Uva dedo de dama,,0,80509,0 -18602,50303452,Uva late sin pepa,,0,80509,0 -18603,50303453,Uva majestic sin pepa,,0,80509,0 -18604,50303454,Uva malaga,,0,80509,0 -18605,50303455,Uva marro sin pepa,,0,80509,0 -18606,50303456,Uva muscadine,,0,80509,0 -18607,50303457,Uva muscat llama,,0,80509,0 -18608,50303458,Uva muscat,,0,80509,0 -18609,50303459,Uva muscat sin semilla,,0,80509,0 -18610,50303460,Uva napoleon,,0,80509,0 -18611,50303461,Uva nigeria,,0,80509,0 -18612,50303462,Uva new cross,,0,80509,0 -18613,50303463,Uva nibella,,0,80509,0 -18614,50303464,Uva niagara,,0,80509,0 -18615,50303465,Uva olivette,,0,80509,0 -18616,50303466,Uva perlette,,0,80509,0 -18617,50303467,Uva perlon,,0,80509,0 -18618,50303468,Uva prima negro sin pepa,,0,80509,0 -18619,50303469,Uva princess,,0,80509,0 -18620,50303470,Uva reina,,0,80509,0 -18621,50303471,Uva roja blush,,0,80509,0 -18622,50303472,Uva roja globe,,0,80509,0 -18623,50303473,Uva roja malaga,,0,80509,0 -18624,50303474,Uva roja sin pepa,,0,80509,0 -18625,50303475,Uva regina,,0,80509,0 -18626,50303476,Uva ribier,,0,80509,0 -18627,50303477,Uva rosita,,0,80509,0 -18628,50303478,Uva rouge,,0,80509,0 -18629,50303479,Uva royal negra sin pepa,,0,80509,0 -18630,50303480,Uva ruby roja sin pepa,,0,80509,0 -18631,50303481,Uva ryby sin pepa,,0,80509,0 -18632,50303482,Uva scarlet royal,,0,80509,0 -18633,50303483,Uva scuppemong,,0,80509,0 -18634,50303484,Uva sugarose,,0,80509,0 -18635,50303485,Uva sugartirteen,,0,80509,0 -18636,50303486,Uva sugraone,,0,80509,0 -18637,50303487,Uva sugrasixteen,,0,80509,0 -18638,50303488,Uva sultana sun roja,,0,80509,0 -18639,50303489,Uva verano royal,,0,80509,0 -18640,50303490,Uva sunset,,0,80509,0 -18641,50303491,Uva superior sin semilla,,0,80509,0 -18642,50303492,Uva thompson sin semilla,,0,80509,0 -18643,50303493,Uva tokaylpinot gris,,0,80509,0 -18644,50303494,Uva waltman cross,,0,80509,0 -18645,50303495,Uva blanca sin pepa,,0,80509,0 -18646,50303496,Uva zante current,,0,80509,0 -18647,50303497,Uva quebranta,,0,80509,0 -18648,50303498,Uva burgundi,,0,80509,0 -18649,50303499,Uva torontel,,0,80509,0 -18650,50303500,Uvas pasas,,0,80509,0 -18651,50303501,Uva corinth negra,,0,80509,0 -18652,50303502,Uva canner,,0,80509,0 -18653,50303503,Uva dovine,,0,80509,0 -18654,50303504,Uva fiesta,,0,80509,0 -18655,50303505,Uva selme pete,,0,80509,0 -18656,50303506,Uva sultana,,0,80509,0 -18657,50303700,Guayabas,,0,80509,0 -18658,50303701,Guayaba beaumont,,0,80509,0 -18659,50303702,Guayaba carrley,,0,80509,0 -18660,50303703,Guayaba lucida,,0,80509,0 -18661,50303704,Guayaba piña,,0,80509,0 -18662,50303800,Arándanos Huckleberry,Arándanos azules,0,80509,0 -18663,50303801,Arándano negro invierno,Arándano azul negro invierno,0,80509,0 -18664,50303802,Arándano cascade,Arándano azul cascade,0,80509,0 -18665,50303803,Arándano enano,Arándano azul enano,0,80509,0 -18666,50303804,Arándano montaña,Arándano azul montaña,0,80509,0 -18667,50303805,Arándano rojo,Arándano azul red,0,80509,0 -18668,50303900,Kiwis,,0,80509,0 -18669,50303901,Fruta de kiwi ananasnaja,Kiwi ananasnaja,0,80509,0 -18670,50303902,Fruta de kiwi belleza del ártico,Kiwi belleza del ártico,0,80509,0 -18671,50303903,Fruta de kiwi blake,Kiwi blake,0,80509,0 -18672,50303904,Fruta de kiwi hayward,Kiwi hayward,0,80509,0 -18673,50303905,Fruta de kiwi issai,Kiwi issai,0,80509,0 -18674,50303906,Fruta de kiwi siberiana,Kiwi siberiano,0,80509,0 -18675,50304100,Limones,,0,80509,0 -18676,50304101,Limón baboon,,0,80509,0 -18677,50304102,Limón bearss sicilian,,0,80509,0 -18678,50304103,Limón cameron highland,,0,80509,0 -18679,50304104,Limón escondido,,0,80509,0 -18680,50304105,Limón eureka,,0,80509,0 -18681,50304106,Limón lisbon,,0,80509,0 -18682,50304107,Limón meyer,,0,80509,0 -18683,50304108,Limón volkaner,,0,80509,0 -18684,50304200,Limas,,0,80509,0 -18685,50304201,Lima limón dulce indio,,0,80509,0 -18686,50304202,Lima limón,,0,80509,0 -18687,50304203,Lima limón mandarina,,0,80509,0 -18688,50304204,Lima limón filipino,,0,80509,0 -18689,50304205,Lima limón tahitian,,0,80509,0 -18690,50304206,Lima limón bearss,,0,80509,0 -18691,50304207,Lima limón persa,,0,80509,0 -18692,50304208,Lima limón sin pepas,Lima limón sin semillas,0,80509,0 -18693,50304400,Mandarinas,,0,80509,0 -18694,50304401,Naranjas clauselinas,,0,80509,0 -18695,50304402,Mandarinas clementinas,,0,80509,0 -18696,50304403,Naranja mandarina cleopatra,,0,80509,0 -18697,50304404,Mandarina dancy,,0,80509,0 -18698,50304405,Naranja ellensday,,0,80509,0 -18699,50304406,Naranja fairchild,,0,80509,0 -18700,50304407,Naranja fallglo,,0,80509,0 -18701,50304408,Naranja fortune,,0,80509,0 -18702,50304409,Mandarina naranja fremont,,0,80509,0 -18703,50304410,Naranja fremont,,0,80509,0 -18704,50304411,Naranja doradoen nugget,,0,80509,0 -18705,50304412,Mandarina naranja miel,,0,80509,0 -18706,50304413,Naranja miel,,0,80509,0 -18707,50304414,Mandarina miel,,0,80509,0 -18708,50304415,Naranja tanjelo honeybelle,,0,80509,0 -18709,50304416,Naranja mandarina rey,,0,80509,0 -18710,50304417,Naranja kinnow,,0,80509,0 -18711,50304418,Naranja andarinalee,,0,80509,0 -18712,50304419,Naranja makokkee,,0,80509,0 -18713,50304420,Naranja malvasion,,0,80509,0 -18714,50304421,Naranja mandarina mediterráneo,,0,80509,0 -18715,50304422,Naranja tangelo minneola,,0,80509,0 -18716,50304423,Naranja monica,,0,80509,0 -18717,50304424,Naranja murcott honey,,0,80509,0 -18718,50304425,Naranja murcott tangors,,0,80509,0 -18719,50304426,Naranja mandarina natsudaidai,,0,80509,0 -18720,50304427,Naranja mandarina natsumikan,,0,80509,0 -18721,50304428,Naranja tanjelo nocatee,,0,80509,0 -18722,50304429,Naranja tanjelo orlando,,0,80509,0 -18723,50304430,Mandarina ortanique,,0,80509,0 -18724,50304431,Naranja mandarina page,,0,80509,0 -18725,50304432,Naranja pixie,,0,80509,0 -18726,50304433,Naranja mandarina ponkan bantangas,,0,80509,0 -18727,50304434,Naranja reyna,,0,80509,0 -18728,50304435,Naranja robinson,,0,80509,0 -18729,50304436,Naranja saltenitas,,0,80509,0 -18730,50304437,Naranja tangelo sampson,,0,80509,0 -18731,50304438,Naranja mandarina satsuma,,0,80509,0 -18732,50304439,Naranja mandarina sunburst,,0,80509,0 -18733,50304440,Tangelo,,0,80509,0 -18734,50304441,Naranja mandarina,,0,80509,0 -18735,50304442,Naranja templo,,0,80509,0 -18736,50304443,Naranja thornton,,0,80509,0 -18737,50304444,Mandarina wekiwa,,0,80509,0 -18738,50304445,Mandarina wilkins,,0,80509,0 -18739,50304446,Mandarina willowleaf mediterranea,,0,80509,0 -18740,50304500,Mangos,,0,80509,0 -18741,50304501,Mango alphonso,,0,80509,0 -18742,50304502,Mango ataulfo,,0,80509,0 -18743,50304503,Mango criollo,,0,80509,0 -18744,50304504,Mango edwards,,0,80509,0 -18745,50304505,Mango francine,,0,80509,0 -18746,50304506,Mango francis,,0,80509,0 -18747,50304507,Mango gandaria,,0,80509,0 -18748,50304508,Mango haden,,0,80509,0 -18749,50304509,Mangos irwin,,0,80509,0 -18750,50304510,Mangos keitt,,0,80509,0 -18751,50304511,Mangos kent,,0,80509,0 -18752,50304512,Mangos kesar,,0,80509,0 -18753,50304513,Mangos kuini,,0,80509,0 -18754,50304514,Mangos manila super,,0,80509,0 -18755,50304515,Mangos manila,,0,80509,0 -18756,50304516,Mangos mayaguez,,0,80509,0 -18757,50304517,Mangos mulgoba,,0,80509,0 -18758,50304518,Mangos oro,,0,80509,0 -18759,50304519,Mangos palmer,,0,80509,0 -18760,50304520,Mango parvin,,0,80509,0 -18761,50304521,Mango sandersha,,0,80509,0 -18762,50304522,Mangos sensation,,0,80509,0 -18763,50304523,Mango smith,,0,80509,0 -18764,50304524,Mango tomy atkin,,0,80509,0 -18765,50304525,Mango van dyke,,0,80509,0 -18766,50304600,Melones,,0,80509,0 -18767,50304601,Melon allsweet,,0,80509,0 -18768,50304602,Melón athens,,0,80509,0 -18769,50304603,Melón diamante negro,,0,80509,0 -18770,50304604,Melón cal dulce,,0,80509,0 -18771,50304605,Melons cantaloupe,,0,80509,0 -18772,50304606,Melón carnical,,0,80509,0 -18773,50304607,Melón casaba,,0,80509,0 -18774,50304608,Melón cavaillon,,0,80509,0 -18775,50304609,Melón charentais,,0,80509,0 -18776,50304610,Sandia charleston gray,,0,80509,0 -18777,50304611,Melón crenshaw,,0,80509,0 -18778,50304612,Melón crimson sweet,,0,80509,0 -18779,50304613,Melón dixie lee,,0,80509,0 -18780,50304614,Melón eclipse,,0,80509,0 -18781,50304615,Melón ein dór,,0,80509,0 -18782,50304616,Melón fiesta,,0,80509,0 -18783,50304617,Melón galia,,0,80509,0 -18784,50304618,Melón gaya,,0,80509,0 -18785,50304619,Melón hami,,0,80509,0 -18786,50304620,Melón honey dew,,0,80509,0 -18787,50304621,Melón icebox,,0,80509,0 -18788,50304622,Melón ida pride,,0,80509,0 -18789,50304623,Melón juan canary,,0,80509,0 -18790,50304624,Melón jubileo,,0,80509,0 -18791,50304625,Melón jubilation,,0,80509,0 -18792,50304626,Melón kaki / kakri,,0,80509,0 -18793,50304627,Melón kiwano,,0,80509,0 -18794,50304628,Melon koreano,Melón coreano,0,80509,0 -18795,50304629,Melón long grey,Melón largo gris,0,80509,0 -18796,50304630,Melón mayan,,0,80509,0 -18797,50304631,Melón micky lee,,0,80509,0 -18798,50304632,Melón mirage,,0,80509,0 -18799,50304633,Sandia luna y estrellas,,0,80509,0 -18800,50304634,Melón ogen,,0,80509,0 -18801,50304635,Melón patriot,,0,80509,0 -18802,50304636,Melón peacook,,0,80509,0 -18803,50304637,Melón pepino,,0,80509,0 -18804,50304638,Melón persa,,0,80509,0 -18805,50304639,Melón picnic,,0,80509,0 -18806,50304640,Melón piel de sapo,,0,80509,0 -18807,50304641,Melón piña,,0,80509,0 -18808,50304642,Melón quetzali,,0,80509,0 -18809,50304643,Melón goblin rojo,,0,80509,0 -18810,50304644,Melón regency,,0,80509,0 -18811,50304645,Melón royal mejestic,,0,80509,0 -18812,50304646,Melón estrella royal,,0,80509,0 -18813,50304647,Melón royal dulce,,0,80509,0 -18814,50304648,Malon santa claus,"Melón piel de sapo, Melón santa claus",0,80509,0 -18815,50304649,Melón sharlyn,,0,80509,0 -18816,50304650,Melón español,,0,80509,0 -18817,50304651,Melón sprite,,0,80509,0 -18818,50304652,Melón starbright,,0,80509,0 -18819,50304653,Melón stars and stripes,,0,80509,0 -18820,50304654,Melón sugar bebe,,0,80509,0 -18821,50304655,Sandia sugar bebe,,0,80509,0 -18822,50304656,Melón sunsweet,,0,80509,0 -18823,50304657,Sandia sweet heart sin semilla,,0,80509,0 -18824,50304658,Melón tentación,,0,80509,0 -18825,50304659,Melón tiger bebe,,0,80509,0 -18826,50304660,Melón tuscan type,,0,80509,0 -18827,50304661,Sandia bebe amarillo,,0,80509,0 -18828,50304900,Nectarinas,,0,80509,0 -18829,50304901,Durazno april glo,,0,80509,0 -18830,50304902,Durazno arctic mist,,0,80509,0 -18831,50304903,Durazno artic snow,,0,80509,0 -18832,50304904,Durazno artic star,,0,80509,0 -18833,50304905,Durazno artic sweet,,0,80509,0 -18834,50304906,Durazno artic glo,,0,80509,0 -18835,50304907,Durazno august fire,,0,80509,0 -18836,50304908,Durazno august pearl,,0,80509,0 -18837,50304909,Durazno august rojo,,0,80509,0 -18838,50304910,Durazno august estrella,,0,80509,0 -18839,50304911,Durazno big john,,0,80509,0 -18840,50304912,Durazno bright pearl,,0,80509,0 -18841,50304913,Durazno diamante bright,,0,80509,0 -18842,50304914,Durazno diamante ray,,0,80509,0 -18843,50304915,Durazno earligo,,0,80509,0 -18844,50304916,Durazno diamante temprano,,0,80509,0 -18845,50304917,Durazno fairlane,,0,80509,0 -18846,50304918,Durazno fantasia,,0,80509,0 -18847,50304919,Durazno perla fuego,,0,80509,0 -18848,50304920,Durazno fuego dulce,,0,80509,0 -18849,50304921,Durazno llamakist,,0,80509,0 -18850,50304922,Durazno tipo plano,,0,80509,0 -18851,50304923,Durazno delicia de jardín,,0,80509,0 -18852,50304924,Durazno mina de oro,,0,80509,0 -18853,50304925,Durazno perla grande,,0,80509,0 -18854,50304926,Durazno hardirojo,,0,80509,0 -18855,50304927,Durazno miel de fuego,,0,80509,0 -18856,50304928,Durazno julio rojo,,0,80509,0 -18857,50304929,Durazno kay perla,,0,80509,0 -18858,50304930,Durazno key dulce,,0,80509,0 -18859,50304931,Durazno diamante mayo,,0,80509,0 -18860,50304932,Durazno mayfire,,0,80509,0 -18861,50304933,Durazno mayglo,,0,80509,0 -18862,50304934,Durazno mericrest,,0,80509,0 -18863,50304935,Durazno diamante rojo,,0,80509,0 -18864,50304936,Durazno oro rojo,,0,80509,0 -18865,50304937,Durazno jim rojo,,0,80509,0 -18866,50304938,Durazno roy rojo,,0,80509,0 -18867,50304939,Durazno rio rojo,,0,80509,0 -18868,50304940,Durazno diamante de rosa,,0,80509,0 -18869,50304941,Durazno royal glo,,0,80509,0 -18870,50304942,Durazno diamante ryby,,0,80509,0 -18871,50304943,Durazno ruby dulce,,0,80509,0 -18872,50304944,Durazno joya ruddy,,0,80509,0 -18873,50304945,Durazno septiembre rojo,,0,80509,0 -18874,50304946,Durazno reina de nieve,,0,80509,0 -18875,50304947,Durazno primavera clara,,0,80509,0 -18876,50304948,Durazno primavera roja,,0,80509,0 -18877,50304949,Durazno verano rojiso,,0,80509,0 -18878,50304950,Durazno verano claro,,0,80509,0 -18879,50304951,Durazno verano diamante,,0,80509,0 -18880,50304952,Durazno verano fuego,,0,80509,0 -18881,50304953,Durazno verano grande,,0,80509,0 -18882,50304954,Durazno sunglo,,0,80509,0 -18883,50304955,Durazno fuego zee,,0,80509,0 -18884,50304956,Durazno zee glo,,0,80509,0 -18885,50304957,Durazno zeegrand,,0,80509,0 -18886,50305000,Naranjas,,0,80509,0 -18887,50305001,Naranja áfrica agria,,0,80509,0 -18888,50305002,Naranja dulce amber,,0,80509,0 -18889,50305003,Naranja argentina agria,,0,80509,0 -18890,50305004,Naranja bahianinha,,0,80509,0 -18891,50305005,Naranja bergamot,,0,80509,0 -18892,50305006,Naranja berna,,0,80509,0 -18893,50305007,Naranja bigaradier apepu,,0,80509,0 -18894,50305008,Naranja agria dulce daidai,,0,80509,0 -18895,50305009,Naranja blonde,,0,80509,0 -18896,50305010,Naranja sangr,,0,80509,0 -18897,50305011,Naranja california navel,,0,80509,0 -18898,50305012,Naranja cara cara,,0,80509,0 -18899,50305013,Naranja chinotto,,0,80509,0 -18900,50305014,Naranja sueño nevel,,0,80509,0 -18901,50305015,Naranja gou tou,,0,80509,0 -18902,50305016,Naranja hamlin,,0,80509,0 -18903,50305017,Naranja jaffa,,0,80509,0 -18904,50305018,Naranja jincheng,,0,80509,0 -18905,50305019,Naranja k-temprano,,0,80509,0 -18906,50305020,Naranja kona,,0,80509,0 -18907,50305021,Naranja navel tarde,,0,80509,0 -18908,50305022,Naranja valencia tarde,,0,80509,0 -18909,50305023,Naranja limequat,,0,80509,0 -18910,50305024,Naranja mar,,0,80509,0 -18911,50305025,Naranja maledorado,,0,80509,0 -18912,50305026,Naranja moro,,0,80509,0 -18913,50305027,Naranja moro tabacco,,0,80509,0 -18914,50305028,Naranja navel,,0,80509,0 -18915,50305029,Naranja navelina,,0,80509,0 -18916,50305030,Naranja oro blanco,,0,80509,0 -18917,50305031,Naranja osceola,,0,80509,0 -18918,50305032,Naranja parson carmelito,,0,80509,0 -18919,50305033,Naranja pera,,0,80509,0 -18920,50305034,Naranja pummulo,,0,80509,0 -18921,50305035,Naranja rhode rojo,,0,80509,0 -18922,50305036,Naranja roble,,0,80509,0 -18923,50305037,Naranja salustianas,,0,80509,0 -18924,50305038,Naranja sanguine,,0,80509,0 -18925,50305039,Naranja sanguinelli,,0,80509,0 -18926,50305040,Naranja cevilla,,0,80509,0 -18927,50305041,Naranja shamouti jaffa,,0,80509,0 -18928,50305042,Naranja tunis,,0,80509,0 -18929,50305043,Naranja valencia,,0,80509,0 -18930,50305044,Naranja washington navel,,0,80509,0 -18931,50305100,Papayas,,0,80509,0 -18932,50305101,Papaya verde cocinar,,0,80509,0 -18933,50305102,Papaya maradol,,0,80509,0 -18934,50305103,Papaya amarillo mexicano,,0,80509,0 -18935,50305104,Papaya montaña,,0,80509,0 -18936,50305105,Papaya solo,,0,80509,0 -18937,50305106,Papaya tainung,,0,80509,0 -18938,50305300,Duraznos,,0,80509,0 -18939,50305301,Durazno escudo amber,,0,80509,0 -18940,50305302,Durazno nieve de abril,,0,80509,0 -18941,50305303,Durazno dama de agosto,,0,80509,0 -18942,50305304,Durazno llama de otoño,,0,80509,0 -18943,50305305,Durazno dama de otoño,,0,80509,0 -18944,50305306,Durazno babcock,,0,80509,0 -18945,50305307,Durazno brittany lane,,0,80509,0 -18946,50305308,Durazno cary mac,,0,80509,0 -18947,50305309,Durazno clasic,,0,80509,0 -18948,50305310,Durazno dulce del campo,,0,80509,0 -18949,50305311,Durazno escudo de cielo,,0,80509,0 -18950,50305312,Durazno dama crimson,,0,80509,0 -18951,50305313,Durazno príncipe corona,,0,80509,0 -18952,50305314,Durazno sol david,,0,80509,0 -18953,50305315,Durazno princesa diamante,,0,80509,0 -18954,50305316,Durazno earlrich,,0,80509,0 -18955,50305317,Durazno majestuosa temprana,,0,80509,0 -18956,50305318,Durazno early treat,,0,80509,0 -18957,50305319,Durazno dama elegante,,0,80509,0 -18958,50305320,Durazno emperatriz,,0,80509,0 -18959,50305321,Durazno encoré,,0,80509,0 -18960,50305322,Durazno dama fancy,,0,80509,0 -18961,50305323,Durazno príncipe de fuego,,0,80509,0 -18962,50305324,Durazno escudo de llama,,0,80509,0 -18963,50305325,Durazno tipo plano,,0,80509,0 -18964,50305326,Durazno escudo de sabor,,0,80509,0 -18965,50305327,Durazno príncipe florida,,0,80509,0 -18966,50305328,Durazno luna llena,,0,80509,0 -18967,50305329,Durazno harvester,,0,80509,0 -18968,50305330,Durazno princesa de hielo,,0,80509,0 -18969,50305331,Durazno princesa de marfil,,0,80509,0 -18970,50305332,Durazno princesa reina jersey,,0,80509,0 -18971,50305333,Durazno john henry,,0,80509,0 -18972,50305334,Durazno príncipe de junio,,0,80509,0 -18973,50305335,Durazno kaweah,,0,80509,0 -18974,50305336,Durazno klondike,,0,80509,0 -18975,50305337,Durazno lindo,,0,80509,0 -18976,50305338,Durazno loring,,0,80509,0 -18977,50305339,Durazno majestuoso,,0,80509,0 -18978,50305340,Durazno o’henry,,0,80509,0 -18979,50305341,Durazno escudo de reina,,0,80509,0 -18980,50305342,Durazno dama roja,,0,80509,0 -18981,50305343,Durazno globo rojo,,0,80509,0 -18982,50305344,Durazno cielo rojo,,0,80509,0 -18983,50305345,Durazno redtop,,0,80509,0 -18984,50305346,Durazno regina,,0,80509,0 -18985,50305347,Durazno dama rica,,0,80509,0 -18986,50305348,Durazno mayo rico,,0,80509,0 -18987,50305349,Durazno gloria real,,0,80509,0 -18988,50305350,Durazno dama real,,0,80509,0 -18989,50305351,Durazno nieve de septiembre,,0,80509,0 -18990,50305352,Durazno sol de septiembre,,0,80509,0 -18991,50305353,Durazno gema sierra,,0,80509,0 -18992,50305354,Durazno angel de nieve,,0,80509,0 -18993,50305355,Durazno gema de nieve,,0,80509,0 -18994,50305356,Durazno rey de nieve,,0,80509,0 -18995,50305357,Durazno dama de primavera,,0,80509,0 -18996,50305358,Durazno nieve de primavera,,0,80509,0 -18997,50305359,Durazno escudo de primavera,,0,80509,0 -18998,50305360,Durazno dulce gigante,,0,80509,0 -18999,50305361,Durazno dama de azúcar,,0,80509,0 -19000,50305362,Durazno brillo de sol,,0,80509,0 -19001,50305363,Durazno sunhigh,,0,80509,0 -19002,50305364,Durazno dama super,,0,80509,0 -19003,50305365,Durazno super rico,,0,80509,0 -19004,50305366,Durazno surecrop,,0,80509,0 -19005,50305367,Durazno sueño duñce,,0,80509,0 -19006,50305368,Durazno septiembre dulce,,0,80509,0 -19007,50305369,Durazno vista,,0,80509,0 -19008,50305370,Durazno dama blanca,,0,80509,0 -19009,50305371,Durazno dama zee,,0,80509,0 -19010,50305400,Peras,,0,80509,0 -19011,50305401,Peras abate fetel,,0,80509,0 -19012,50305402,Peras anjou,,0,80509,0 -19013,50305403,Pera asiatica,,0,80509,0 -19014,50305404,Pera bartlett,,0,80509,0 -19015,50305405,Pera best ever,,0,80509,0 -19016,50305406,Pera beth,,0,80509,0 -19017,50305407,Pera beurre,,0,80509,0 -19018,50305408,Pera bosc,,0,80509,0 -19019,50305409,Pera clapp favorita,,0,80509,0 -19020,50305410,Pera comice,,0,80509,0 -19021,50305411,Pera concorde,,0,80509,0 -19022,50305412,Pera conference,,0,80509,0 -19023,50305413,Pera crimson rojo,,0,80509,0 -19024,50305414,Peras d’ anjou,,0,80509,0 -19025,50305415,Pera dr jules guyot,,0,80509,0 -19026,50305416,Peras early,,0,80509,0 -19027,50305417,Peras emperador carmelito,,0,80509,0 -19028,50305418,Peras forelle,,0,80509,0 -19029,50305419,Pera mantequilla francesa,,0,80509,0 -19030,50305420,Pera glou morceau,,0,80509,0 -19031,50305421,Pera hosui,,0,80509,0 -19032,50305422,Pera mantequilla italiana,,0,80509,0 -19033,50305423,Pera jargonelle,,0,80509,0 -19034,50305424,Pera juno,,0,80509,0 -19035,50305425,Para kaiserlouise bonne de jersey,,0,80509,0 -19036,50305426,Pera keiffer,,0,80509,0 -19037,50305427,Pera rey royal,,0,80509,0 -19038,50305428,Pera limonera,,0,80509,0 -19039,50305429,Pera merton pride,,0,80509,0 -19040,50305430,Pera mauntain bartlette,,0,80509,0 -19041,50305431,Pera oliver de serrers,,0,80509,0 -19042,50305432,Pera onward,,0,80509,0 -19043,50305433,Pera packham´s triumph,,0,80509,0 -19044,50305434,Pera paraíso,,0,80509,0 -19045,50305435,Pera passe crassane,,0,80509,0 -19046,50305436,Pera perry,,0,80509,0 -19047,50305437,Pera bartlette rojo,,0,80509,0 -19048,50305438,Pera dánjou rojo,,0,80509,0 -19049,50305439,Pera rocha,,0,80509,0 -19050,50305440,Pera rosey rojo,,0,80509,0 -19051,50305441,Pera rosy roja,,0,80509,0 -19052,50305442,Pera majestuosa royal,,0,80509,0 -19053,50305443,Pera ruby rojo,,0,80509,0 -19054,50305444,Pera santa maria,,0,80509,0 -19055,50305445,Pera seckel,,0,80509,0 -19056,50305446,Pera sensación,,0,80509,0 -19057,50305447,Pera crimson estrella,,0,80509,0 -19058,50305448,Pera crimson stark,,0,80509,0 -19059,50305449,Pera bartlette de verano,,0,80509,0 -19060,50305450,Pera verano dorado,,0,80509,0 -19061,50305451,Pera sol dorado,,0,80509,0 -19062,50305452,Pera sunprite,,0,80509,0 -19063,50305453,Pera taylors dorado,,0,80509,0 -19064,50305454,Pera taylors rojo,,0,80509,0 -19065,50305455,Pera tientsin,,0,80509,0 -19066,50305456,Pera tosca,,0,80509,0 -19067,50305457,Pera warden,,0,80509,0 -19068,50305458,Pera williams bon chretien,,0,80509,0 -19069,50305459,Pera williams,,0,80509,0 -19070,50305460,Pera nelis de invierno,,0,80509,0 -19071,50305600,Piñas,,0,80509,0 -19072,50305601,Piña chirimoya,Atemoya,0,80509,0 -19073,50305602,Piña dorada,,0,80509,0 -19074,50305603,Piña hilo,,0,80509,0 -19075,50305604,Piña kona sugarloaf,,0,80509,0 -19076,50305605,Piña reina natal,,0,80509,0 -19077,50305606,Piña pernabuco,,0,80509,0 -19078,50305607,Piña español rojo,,0,80509,0 -19079,50305608,Piña cayen suave,,0,80509,0 -19080,50305609,Piña sugarloaf,,0,80509,0 -19081,50305610,Piña variegated,,0,80509,0 -19082,50305800,Ciruelas,,0,80509,0 -19083,50305801,Ciruela joya amber,,0,80509,0 -19084,50305802,Ciruela angeleno,,0,80509,0 -19085,50305803,Ciruela aurora,,0,80509,0 -19086,50305804,Ciruela otoño bonito,,0,80509,0 -19087,50305805,Ciruela gigante de otoño,,0,80509,0 -19088,50305806,Ciruela orgullo de otoño,,0,80509,0 -19089,50305807,Ciruela rosa de otoño,,0,80509,0 -19090,50305808,Ciruela playero,,0,80509,0 -19091,50305809,Ciruela betty anne,,0,80509,0 -19092,50305810,Ciruela belleza negra,,0,80509,0 -19093,50305811,Ciruela bullase negra,,0,80509,0 -19094,50305812,Ciruela diamante negro,,0,80509,0 -19095,50305813,Ciruela gigante negro,,0,80509,0 -19096,50305814,Ciruela hielo negro,,0,80509,0 -19097,50305815,Ciruela esplendor negro,,0,80509,0 -19098,50305816,Ciruela ámbar negro,,0,80509,0 -19099,50305817,Ciruela vino tinto,,0,80509,0 -19100,50305818,Ciruela carsbad,,0,80509,0 -19101,50305819,Ciruela casselman,,0,80509,0 -19102,50305820,Ciruela catalina,,0,80509,0 -19103,50305821,Ciruela damson,,0,80509,0 -19104,50305822,Ciruela dolly,,0,80509,0 -19105,50305823,Ciruela earliqueen,,0,80509,0 -19106,50305824,Ciruela rosa early,,0,80509,0 -19107,50305825,Ciruela ébano mayo,,0,80509,0 -19108,50305826,Ciruela ébano,,0,80509,0 -19109,50305827,Ciruela corazón de elefante,,0,80509,0 -19110,50305828,Ciruela belleza de esmeralda,,0,80509,0 -19111,50305829,Ciruela emperatriz,,0,80509,0 -19112,50305830,Ciruela libertad,,0,80509,0 -19113,50305831,Ciruela friar,,0,80509,0 -19114,50305832,Ciruela rojo gar,,0,80509,0 -19115,50305833,Ciruela gobernador,,0,80509,0 -19116,50305834,Ciruela rosa grande,,0,80509,0 -19117,50305835,Ciruela green gage,Ciruela calibre verde,0,80509,0 -19118,50305836,Ciruela greengage,,0,80509,0 -19119,50305837,Ciruela hiromi,,0,80509,0 -19120,50305838,Ciruela hiromi rojo,,0,80509,0 -19121,50305839,Ciruela vacacion,,0,80509,0 -19122,50305840,Ciruela howard sol,,0,80509,0 -19123,50305841,Ciruela tipo interspecific,,0,80509,0 -19124,50305842,Ciruela jamaiquina,,0,80509,0 -19125,50305843,Ciruela joanna rojo,,0,80509,0 -19126,50305844,Ciruela kelsey,,0,80509,0 -19127,50305845,Ciruela jaime rey,,0,80509,0 -19128,50305846,Ciruela laroda,,0,80509,0 -19129,50305847,Ciruela rosa tarde,,0,80509,0 -19130,50305848,Ciruela rosa linda,,0,80509,0 -19131,50305849,Ciruela estrella solitaria,,0,80509,0 -19132,50305850,Ciruela mariposa,,0,80509,0 -19133,50305851,Ciruela mercado negro,,0,80509,0 -19134,50305852,Ciruela mercado rojo,,0,80509,0 -19135,50305853,Ciruela maribel,,0,80509,0 -19136,50305854,Ciruelas sol de octubre,,0,80509,0 -19137,50305855,Ciruela owen t,,0,80509,0 -19138,50305856,Ciruela perdrigon,,0,80509,0 -19139,50305857,Ciruela placer rosado,,0,80509,0 -19140,50305858,Ciruela presidente,,0,80509,0 -19141,50305859,Ciruela hora prima,Ciruela prime time,0,80509,0 -19142,50305860,Ciruela majestad purpura,,0,80509,0 -19143,50305861,Ciruela reina rosa,,0,80509,0 -19144,50305862,Ciruela quetsch,,0,80509,0 -19145,50305863,Ciruela belleza roja,,0,80509,0 -19146,50305864,Ciruela camino rojo,,0,80509,0 -19147,50305865,Ciruela ram rojo,,0,80509,0 -19148,50305866,Ciruela rosa roja,,0,80509,0 -19149,50305867,Ciruela rojo rico,,0,80509,0 -19150,50305868,Ciruela romero,,0,80509,0 -19151,50305869,Ciruela diamante rojo,,0,80509,0 -19152,50305870,Ciruela rojo real,,0,80509,0 -19153,50305871,Ciruela royal zee,,0,80509,0 -19154,50305872,Ciruela roysum,,0,80509,0 -19155,50305873,Ciruela santa rosa,,0,80509,0 -19156,50305874,Ciruela zafiro,,0,80509,0 -19157,50305875,Ciruela sloe,,0,80509,0 -19158,50305876,Ciruela sta catherine,,0,80509,0 -19159,50305877,Ciruela bullase blanco,,0,80509,0 -19160,50305878,Ciruela creole,,0,80509,0 -19161,50306200,Frambuesas,,0,80509,0 -19162,50306201,Frambuesa americana roja,,0,80509,0 -19163,50306202,Frambuesa bailey queensland,,0,80509,0 -19164,50306203,Frambuesa negra,,0,80509,0 -19165,50306204,Frambuesa oscura,,0,80509,0 -19166,50306205,Frambuesa deliciosa,,0,80509,0 -19167,50306206,Frambuesa enano focke,,0,80509,0 -19168,50306207,Frambuesa focke hojagris,,0,80509,0 -19169,50306208,Frambuesa focke fresa,,0,80509,0 -19170,50306209,Frambuesa focke amarillo himalaya,,0,80509,0 -19171,50306210,Frambuesa dorado,,0,80509,0 -19172,50306211,Frambuesa gris nuevo mexico,,0,80509,0 -19173,50306212,Frambuesa jepson blancobark,,0,80509,0 -19174,50306213,Frambuesa kellogs san diego,,0,80509,0 -19175,50306214,Frambuesa leucodemis blancobark,,0,80509,0 -19176,50306215,Frambuesa munz cuyamaca,,0,80509,0 -19177,50306216,Frambuesa peck bartons,,0,80509,0 -19178,50306217,Frambuesa flor morada,,0,80509,0 -19179,50306218,Frambuesa roadside,,0,80509,0 -19180,50306219,Frambuesa san diego,,0,80509,0 -19181,50306220,Frambuesa nieve,,0,80509,0 -19182,50306221,Frambuesa pico de nieve,,0,80509,0 -19183,50306222,Frambuesa hoja de fresa,,0,80509,0 -19184,50306223,Frambuesa cultivo dulce,,0,80509,0 -19185,50306224,Frambuesa tor y gris blancobark,,0,80509,0 -19186,50306225,Frambuesa caribe,,0,80509,0 -19187,50306226,Frambuesa blancobark,,0,80509,0 -19188,50306227,Frambuesa vino,,0,80509,0 -19189,50306228,Frambuesa himalaya amarillo,,0,80509,0 -19190,50306229,Frambuesa yu-shan,,0,80509,0 -19191,50306500,Sapotes,,0,80509,0 -19192,50306501,Sapote blanco,Zapote blanco,0,80509,0 -19193,50306502,Sapote negro,Zapote negro,0,80509,0 -19194,50306700,Fresas,,0,80509,0 -19195,50306701,Fresa chandler,,0,80509,0 -19196,50306702,Fresa rumbo junio,,0,80509,0 -19197,50306703,Fresa rumbo siempre,,0,80509,0 -19198,50307000,Frutas Nominant,"Frutas predominantes, Caña de azúcar, Tejocote, Xoconostle ,",0,80509,0 -19199,50307001,Cupania,Ackee,0,80509,0 -19200,50307002,Babaco,Carica pentágona,0,80509,0 -19201,50307003,Banana flor,Flor de plátano,0,80509,0 -19202,50307004,Baobab,,0,80509,0 -19203,50307005,Naranja agria,,0,80509,0 -19204,50307006,Canistel,Zapote amarillo,0,80509,0 -19205,50307007,Baya nublosa,Mora de los pantanos,0,80509,0 -19206,50307008,Coco,,0,80509,0 -19207,50307009,Baya drew,Baya de gota,0,80509,0 -19208,50307010,Durian,Durio,0,80509,0 -19209,50307011,Baya elder,Baya de sauco,0,80509,0 -19210,50307012,Feijoa,Guayabo del Brasil,0,80509,0 -19211,50307013,Mora,,0,80509,0 -19212,50307014,Espino,Majuelo,0,80509,0 -19213,50307015,Baya miel,Madreselva azul,0,80509,0 -19214,50307016,Jaca,Yaca,0,80509,0 -19215,50307017,Jambolan,,0,80509,0 -19216,50307018,Jujube,Azufaifa,0,80509,0 -19217,50307019,Liches,Lichi,0,80509,0 -19218,50307020,Mangostinos,Mangostán,0,80509,0 -19219,50307021,Nísperos,Medlar,0,80509,0 -19220,50307022,Mombines,Hobo,0,80509,0 -19221,50307023,Monstera,Cerimán,0,80509,0 -19222,50307024,Pepinos,Pepino dulce,0,80509,0 -19223,50307025,Platano,Plátano macho,0,80509,0 -19224,50307026,Peras en escabeche,Tuna,0,80509,0 -19225,50307027,Mamonsillo,Guaya,0,80509,0 -19226,50307028,Rambután,Achotillo,0,80509,0 -19227,50307029,Manzana rosa,Pero de agua,0,80509,0 -19228,50307030,Rosele,Flor de jamaica,0,80509,0 -19229,50307031,Baya rowan,Serba,0,80509,0 -19230,50307032,Baya buckhom de mar,Espino amarillo,0,80509,0 -19231,50307033,Baya plata,Olivo de otoño,0,80509,0 -19232,50307034,Baya sorbete,Serba,0,80509,0 -19233,50307035,Guanabana,,0,80509,0 -19234,50307036,Manzana estrella,Cainito,0,80509,0 -19235,50307037,Tamarindo,,0,80509,0 -19236,50307038,Camu camu,,0,80509,0 -19237,50307039,Lúcuma,,0,80509,0 -19238,50307040,Araza,Guayaba amazónica,0,80509,0 -19239,50307041,Copoazu,Cacao blanco,0,80509,0 -19240,50307042,Poma rosa,Pomarrosa,0,80509,0 -19241,50307043,Aguaje,Moriche,0,80509,0 -19242,50307044,Cocona,Túpiro,0,80509,0 -19243,50307045,Guayaba,Jinicuil,0,80509,0 -19244,50307046,Carambola,Fruta de estrella,0,80509,0 -19245,50307200,Aceitunas,,0,80509,0 -19246,50307201,Aceitunas agrinion,Aceituna agrinion,0,80509,0 -19247,50307202,Aceitunas aleppo,Aceituna aleppo,0,80509,0 -19248,50307203,Aceitunas alfonso,Aceituna alfonso,0,80509,0 -19249,50307204,Aceitunas amfisa,Aceituna amfisa,0,80509,0 -19250,50307205,Aceitunas arauco,Aceituna arauco,0,80509,0 -19251,50307206,Aceitunas arbequina,Aceituna arbequina,0,80509,0 -19252,50307207,Aceitunas atlanta,Aceituna atlanta,0,80509,0 -19253,50307208,Aceitunas cerignola,Aceituna cerignola,0,80509,0 -19254,50307209,Aceitunas cracked provencal,Aceituna cracked provencal,0,80509,0 -19255,50307210,Aceitunas empeltre,Aceituna empeltre,0,80509,0 -19256,50307211,Aceitunas gaeta,Aceituna gaeta,0,80509,0 -19257,50307212,Aceitunas hondoelia,Aceituna hondoelia,0,80509,0 -19258,50307213,Aceitunas kalamata,Aceituna kalamata,0,80509,0 -19259,50307214,Aceitunas kura,Aceituna kura,0,80509,0 -19260,50307215,Aceitunas ligurian,Aceituna ligurian,0,80509,0 -19261,50307216,Aceitunas lucque,Aceituna lucque,0,80509,0 -19262,50307217,Aceitunas lugano,Aceituna lugano,0,80509,0 -19263,50307218,Aceitunas masanilla,Aceituna manzanilla,0,80509,0 -19264,50307219,Aceitunas marche,Aceituna marche,0,80509,0 -19265,50307220,Aceitunas misión,Aceituna misión,0,80509,0 -19266,50307221,Aceitunas nafplion verde,Aceituna nafplion verde,0,80509,0 -19267,50307222,Aceitunas nicoise,Aceituna nicoise,0,80509,0 -19268,50307223,Aceitunas nyons,Aceituna nyons,0,80509,0 -19269,50307224,Aceitunas picholine,Aceituna picholine,0,80509,0 -19270,50307225,Aceitunas ponentine,Aceituna ponentine,0,80509,0 -19271,50307226,Aceitunas royal,Aceituna royal,0,80509,0 -19272,50307227,Aceitunas seracena,Aceituna seracena,0,80509,0 -19273,50307228,Aceitunas sevillano,Aceituna sevillano,0,80509,0 -19274,50307229,Aceitunas sicilian,Aceituna sicilian,0,80509,0 -19275,50307230,Aceitunas toscanella,Aceituna toscanella,0,80509,0 -19276,50307500,Subproductos de frutas frescas,,0,80509,0 -19277,50307501,Cascara de frutas cítricas,,0,80509,0 -19278,50307502,Extracto de naranja para mezclar en agua,,0,80509,0 -19279,50307503,Pulpa,,0,80509,0 -19280,50311700,Bananos orgánicos,Plátanos orgánicos,0,80509,0 -19281,50311701,Banana apple orgánica,Plátano apple orgánico,0,80509,0 -19282,50311702,Banana baby orgánica,Plátano baby orgánico,0,80509,0 -19283,50311703,Banana burro orgánica,Plátano burro orgánico,0,80509,0 -19284,50311704,Bananas cavendish orgánica,Plátano cavendish orgánico,0,80509,0 -19285,50311705,Bananas dominico orgánica,Plátano dominico orgánico,0,80509,0 -19286,50311706,Bananas verde orgánica,Plátano verde orgánico,0,80509,0 -19287,50311707,Bananas gros michel orgánica,Plátano gros michel orgánico,0,80509,0 -19288,50311708,Bananas lacatan orgánica,Plátano lacatan orgánico,0,80509,0 -19289,50311709,Bananas lady finger orgánica,Plátano lady finger orgánico,0,80509,0 -19290,50311710,Bananas manzano orgánica,Plátano manzano orgánico,0,80509,0 -19291,50311711,Banana mysore orgánica,Plátano mysore orgánico,0,80509,0 -19292,50311712,Banana pisang mas orgánica,Plátano pisang mas orgánico,0,80509,0 -19293,50311713,Bananas rojo orgánica,Plátano rojo orgánico,0,80509,0 -19294,50311714,Bananas saba orgánica,Plátano saba orgánico,0,80509,0 -19295,50311715,Bananas sucrier orgánica,Plátano sucrier orgánico,0,80509,0 -19296,50312000,Moras orgánicas,Zarzamoras orgánicas,0,80509,0 -19297,50312001,Mora apache orgánica,Zarzamora apache orgánica,0,80509,0 -19298,50312002,Mora negro satin orgánica,Zarzamora negro satín orgánica,0,80509,0 -19299,50312003,Mora orgánica,Zarzamora orgánica,0,80509,0 -19300,50312004,Mora cherokee orgánica,Zarzamora cherokee orgánica,0,80509,0 -19301,50312005,Mora chester orgánica,Zarzamora chéster orgánica,0,80509,0 -19302,50312006,Mora dirksen orgánica,Zarzamora dirksen orgánica,0,80509,0 -19303,50312007,Bayas josta orgánica,Zarzamora josta orgánica,0,80509,0 -19304,50312008,Bayas logan orgánica,Zarzamora logan orgánica,0,80509,0 -19305,50312009,Bayas marion orgánica,Zarzamora marion orgánica,0,80509,0 -19306,50312010,Mora navaho orgánica,Zarzamora navaho orgánica,0,80509,0 -19307,50312011,Baya nectar orgánica,Zarzamora néctar orgánica,0,80509,0 -19308,50312012,Mora olallie orgánica,Zarzamora olallie orgánica,0,80509,0 -19309,50312013,Baya tay orgánica,Zarzamora tay orgánica,0,80509,0 -19310,50312014,Mora thomless hull orgánica,Zarzamora thomless hull orgánica,0,80509,0 -19311,50312015,Baya young orgánicos,Zarzamora young orgánicos,0,80509,0 -19312,50312700,Arándanos orgánicos,Arándanos rojos orgánicos,0,80509,0 -19313,50312701,Arandano ben lear orgánica,Arándano rojo ben lear orgánico,0,80509,0 -19314,50312702,Arandano early negro orgánica,Arándano rojo early negro orgánico,0,80509,0 -19315,50312703,Arandano grycleski orgánica,Arándano rojo grycleski orgánico,0,80509,0 -19316,50312704,Arandano howe orgánica,Arándano rojo howe orgánico,0,80509,0 -19317,50312705,Bayas lingon orgánica,Arándano rojo lingon orgánico,0,80509,0 -19318,50312706,Arandano mcfarling orgánica,Arándano rojo mcfarling orgánico,0,80509,0 -19319,50312707,Arandano mauntain orgánica,Arándano rojo mauntain orgánico,0,80509,0 -19320,50312708,Arandano pilgrim orgánica,Arándano rojo pilgrim orgánico,0,80509,0 -19321,50312709,Arandano searless orgánica,Arándano rojo searless orgánico,0,80509,0 -19322,50312710,Arandano stevens orgánica,Arándano rojo stevens orgánico,0,80509,0 -19323,50313400,Uvas de mesa orgánicas,,0,80509,0 -19324,50313401,Uva alicante orgánica,,0,80509,0 -19325,50313402,Uva almeira orgánica,,0,80509,0 -19326,50313403,Uva alphonse lavalle orgánica,,0,80509,0 -19327,50313404,Uva otoño rey grapes orgánica,,0,80509,0 -19328,50313405,Uva otoño royal orgánica,,0,80509,0 -19329,50313406,Uva otoño sin pepa orgánica,,0,80509,0 -19330,50313407,Uva baresana orgánica,,0,80509,0 -19331,50313408,Uva barlinka orgánica,,0,80509,0 -19332,50313409,Uva beaty seedless orgánica,,0,80509,0 -19333,50313410,Uva negro beauty sin pepa orgánica,,0,80509,0 -19334,50313411,Uva negro emerald orgánica,,0,80509,0 -19335,50313412,Uva negro giant orgánica,,0,80509,0 -19336,50313413,Uva negro globo orgánica,,0,80509,0 -19337,50313414,Uva negro monnukka orgánica,,0,80509,0 -19338,50313415,Uva negro perla orgánica,,0,80509,0 -19339,50313416,Uva negro sin pepa orgánica,,0,80509,0 -19340,50313417,Uva bonheur orgánica,,0,80509,0 -19341,50313418,Uva calmeria orgánica,,0,80509,0 -19342,50313419,Uva cardinal orgánica,,0,80509,0 -19343,50313420,Uva catawba orgánica,,0,80509,0 -19344,50313421,Uva chasselas/doradoen chasselas orgánica,,0,80509,0 -19345,50313422,Uva christmas rose orgánica,,0,80509,0 -19346,50313423,Uva concord orgánica,,0,80509,0 -19347,50313424,Uva concord sin pepa orgánica,,0,80509,0 -19348,50313425,Uva crimson sin pepa orgánica,,0,80509,0 -19349,50313426,Uva dauphine orgánica,,0,80509,0 -19350,50313427,Uva daleware orgánica,,0,80509,0 -19351,50313428,Uva early muscat orgánica,,0,80509,0 -19352,50313429,Uva early sweet orgánica,,0,80509,0 -19353,50313430,Uva esmeralda sin pepa orgánica,,0,80509,0 -19354,50313431,Uva emperatriz orgánica,,0,80509,0 -19355,50313432,Uva emperor orgánica,,0,80509,0 -19356,50313433,Uva empress orgánica,,0,80509,0 -19357,50313434,Uva exótica orgánica,,0,80509,0 -19358,50313435,Uva fantasia orgánica,,0,80509,0 -19359,50313436,Uva fantasy sin pepa orgánica,,0,80509,0 -19360,50313437,Uva llama orgánica,,0,80509,0 -19361,50313438,Uva llama sin pepa orgánica,,0,80509,0 -19362,50313439,Uva llama tokay orgánica,,0,80509,0 -19363,50313440,Uva flaming rojo orgánica,,0,80509,0 -19364,50313441,Uva galaxy sin pepa orgánica,,0,80509,0 -19365,50313442,Uva gamay orgánica,,0,80509,0 -19366,50313443,Uva dorada orgánica,,0,80509,0 -19367,50313444,Uva hanepot o honeypot orgánica,,0,80509,0 -19368,50313445,Uva italia orgánica,,0,80509,0 -19369,50313446,Uva jade sin pepa orgánica,,0,80509,0 -19370,50313447,Uva jubilee orgánica,,0,80509,0 -19371,50313448,Uva rey ruby orgánica,,0,80509,0 -19372,50313449,Uva kyoho orgánica,,0,80509,0 -19373,50313450,Uva la rochelle orgánica,,0,80509,0 -19374,50313451,Uva lady finger orgánica,,0,80509,0 -19375,50313452,Uva late sin pepa orgánica,,0,80509,0 -19376,50313453,Uva majestic sin pepa orgánica,,0,80509,0 -19377,50313454,Uva malaga orgánica,,0,80509,0 -19378,50313455,Uva marro sin pepa orgánica,,0,80509,0 -19379,50313456,Uva muscadine orgánica,,0,80509,0 -19380,50313457,Uva muscat llama orgánica,,0,80509,0 -19381,50313458,Uva muscat orgánica,,0,80509,0 -19382,50313459,Uva muscat sin papa orgánica,,0,80509,0 -19383,50313460,Uva napoleon orgánica,,0,80509,0 -19384,50313461,Uva nigeria orgánica,,0,80509,0 -19385,50313462,Uva new cross orgánica,,0,80509,0 -19386,50313463,Uva nibella orgánica,,0,80509,0 -19387,50313464,Uva niagara orgánica,,0,80509,0 -19388,50313465,Uva olivette orgánica,,0,80509,0 -19389,50313466,Uva perlette orgánica,,0,80509,0 -19390,50313467,Uva perlon orgánica,,0,80509,0 -19391,50313468,Uva prima negro sin pepa orgánica,,0,80509,0 -19392,50313469,Uva prncess orgánica,,0,80509,0 -19393,50313470,Uva reina orgánica,,0,80509,0 -19394,50313471,Uva roja blush orgánica,,0,80509,0 -19395,50313472,Uva roja globe orgánica,,0,80509,0 -19396,50313473,Uva roja malaga orgánica,,0,80509,0 -19397,50313474,Uva roja sin pepa orgánica,,0,80509,0 -19398,50313475,Uva regina orgánica,,0,80509,0 -19399,50313476,Uva ribier orgánica,,0,80509,0 -19400,50313477,Uva rosita orgánica,,0,80509,0 -19401,50313478,Uva rouge orgánica,,0,80509,0 -19402,50313479,Uva royal negra sin pepa orgánica,,0,80509,0 -19403,50313480,Uva ruby roja sin pepa orgánica,,0,80509,0 -19404,50313481,Uva ryby sin pepa orgánica,,0,80509,0 -19405,50313482,Uva scarlet royal orgánica,,0,80509,0 -19406,50313483,Uva scuppemong orgánica,,0,80509,0 -19407,50313484,Uva sugarose orgánica,,0,80509,0 -19408,50313485,Uva sugartirteen orgánica,,0,80509,0 -19409,50313486,Uva sugraone orgánica,,0,80509,0 -19410,50313487,Uva sugrasixteen orgánica,,0,80509,0 -19411,50313488,Uva sultana sun roja orgánica,,0,80509,0 -19412,50313489,Uva verano royal orgánica,,0,80509,0 -19413,50313490,Uva atardecer orgánica,,0,80509,0 -19414,50313491,Uva superior sin pepa orgánica,,0,80509,0 -19415,50313492,Uva thompson sin pepa orgánica,,0,80509,0 -19416,50313493,Uva tokaylpinot gris orgánica,,0,80509,0 -19417,50313494,Uva waltman cross orgánica,,0,80509,0 -19418,50313495,Uva blanca sin pepa orgánica,,0,80509,0 -19419,50313496,Uva zante current orgánica,,0,80509,0 -19420,50313600,Uvas para vino orgánicas,,0,80509,0 -19421,50313601,Uva uva alicante bouschet orgánica,,0,80509,0 -19422,50313602,Uva barbera orgánica,,0,80509,0 -19423,50313603,Uva burger orgánica,,0,80509,0 -19424,50313604,Uva cabemet franc orgánica,,0,80509,0 -19425,50313605,Uva cabenet sauvignon orgánica,,0,80509,0 -19426,50313606,Uva carignane orgánica,,0,80509,0 -19427,50313607,Uva carnelian orgánica,,0,80509,0 -19428,50313608,Uva catarratto orgánica,,0,80509,0 -19429,50313609,Uva centurian orgánica,,0,80509,0 -19430,50313610,Uva charbono orgánica,,0,80509,0 -19431,50313611,Uva chardonnay orgánica,,0,80509,0 -19432,50313612,Uva chenin blanco orgánica,,0,80509,0 -19433,50313613,Uva cinsaut orgánica,,0,80509,0 -19434,50313614,Uva dolcetto orgánica,,0,80509,0 -19435,50313615,Uva emerald riesling orgánica,,0,80509,0 -19436,50313616,Uva french colombard orgánica,,0,80509,0 -19437,50313617,Uva gamay o napa orgánica,,0,80509,0 -19438,50313618,Uva gamay beaujolais orgánica,,0,80509,0 -19439,50313619,Uva gewurztraminer orgánica,,0,80509,0 -19440,50313620,Uva grenache orgánica,,0,80509,0 -19441,50313621,Uva grenache blanc orgánica,,0,80509,0 -19442,50313622,Uva lagrein orgánica,,0,80509,0 -19443,50313623,Uva lambrusco orgánica,,0,80509,0 -19444,50313624,Uva malbec orgánica,,0,80509,0 -19445,50313625,Uva malvasía bianca orgánica,,0,80509,0 -19446,50313626,Uva marsanne orgánica,,0,80509,0 -19447,50313627,Uva mataro orgánica,,0,80509,0 -19448,50313628,Uva merlot orgánica,,0,80509,0 -19449,50313629,Uva meunier orgánica,,0,80509,0 -19450,50313630,Uva missiom orgánica,,0,80509,0 -19451,50313631,Uva montepulciano orgánica,,0,80509,0 -19452,50313632,Uva muscat blanc orgánica,,0,80509,0 -19453,50313633,Uva muscat hamburg orgánica,,0,80509,0 -19454,50313634,Uva muscat alexandria orgánica,,0,80509,0 -19455,50313635,Uva muscat naranja orgánica,,0,80509,0 -19456,50313636,Uva nebbiolo orgánica,,0,80509,0 -19457,50313637,Uva palomino orgánica,,0,80509,0 -19458,50313638,Uva petit verdot orgánica,,0,80509,0 -19459,50313639,Uva petite sirah orgánica,,0,80509,0 -19460,50313640,Uva pinot blanc orgánica,,0,80509,0 -19461,50313641,Uva pinot gris orgánica,,0,80509,0 -19462,50313642,Uva pinot noir orgánica,,0,80509,0 -19463,50313643,Uva primitivo orgánica,,0,80509,0 -19464,50313644,Uva roussane orgánica,,0,80509,0 -19465,50313645,Uva real orgánica,,0,80509,0 -19466,50313646,Uva rubirojo orgánica,,0,80509,0 -19467,50313647,Uva ruby cabenet orgánica,,0,80509,0 -19468,50313648,Uva salvador orgánica,,0,80509,0 -19469,50313649,Uva sangiovese orgánica,,0,80509,0 -19470,50313650,Uva sauvignon blanc orgánica,,0,80509,0 -19471,50313651,Uva sauvignon musque orgánica,,0,80509,0 -19472,50313652,Uva semillon orgánica,,0,80509,0 -19473,50313653,Uva souzao orgánica,,0,80509,0 -19474,50313654,Uva st emilion orgánica,,0,80509,0 -19475,50313655,Uva symohony orgánica,,0,80509,0 -19476,50313656,Uva syrah orgánica,,0,80509,0 -19477,50313657,Uva tannat orgánica,,0,80509,0 -19478,50313658,Uva temor anillo orgánica,,0,80509,0 -19479,50313659,Uvas teroldego orgánica,,0,80509,0 -19480,50313660,Uvas tocai friulano orgánica,,0,80509,0 -19481,50313661,Uvas touriga nacional orgánica,,0,80509,0 -19482,50313662,Uvas triplett blanc orgánica,,0,80509,0 -19483,50313663,Uvas viognier orgánica,,0,80509,0 -19484,50313664,Uvas blanco riesling orgánica,,0,80509,0 -19485,50313665,Uvas zinfandel orgánica,,0,80509,0 -19486,50314200,Limas orgánicas,,0,80509,0 -19487,50314201,Lima limón indian sweet orgánico,,0,80509,0 -19488,50314202,Lima limón orgánico,,0,80509,0 -19489,50314203,Lima limón mandarin orgánico,,0,80509,0 -19490,50314204,Lima limón philippine orgánico,,0,80509,0 -19491,50314205,Lima limón tahitian orgánico,,0,80509,0 -19492,50314206,Lima limón bearss orgánico,,0,80509,0 -19493,50314207,Lima limón persa orgánico,,0,80509,0 -19494,50314208,Lima limón sin pepas orgánico,Lima limón sin semilla orgánico,0,80509,0 -19495,50314500,Mangos orgánicos,,0,80509,0 -19496,50314501,Mango alphonso orgánico,,0,80509,0 -19497,50314502,Mango ataulfo orgánico,,0,80509,0 -19498,50314503,Mango criollo orgánico,,0,80509,0 -19499,50314504,Mango edwards orgánico,,0,80509,0 -19500,50314505,Mango francine orgánico,,0,80509,0 -19501,50314506,Mango francis orgánico,,0,80509,0 -19502,50314507,Mango gandaria orgánico,,0,80509,0 -19503,50314508,Mango haden orgánico,,0,80509,0 -19504,50314509,Mangos irwin orgánico,,0,80509,0 -19505,50314510,Mangos keitt orgánico,,0,80509,0 -19506,50314511,Mangos kent orgánico,,0,80509,0 -19507,50314512,Mangos kesar orgánico,,0,80509,0 -19508,50314513,Mangos kuini orgánico,,0,80509,0 -19509,50314514,Mangos manila super orgánico,,0,80509,0 -19510,50314515,Mangos manila orgánico,,0,80509,0 -19511,50314516,Mangos mayaguez orgánico,,0,80509,0 -19512,50314517,Mangos mulgoba orgánico,,0,80509,0 -19513,50314518,Mangos oro orgánico,,0,80509,0 -19514,50314519,Mangos palmer orgánico,,0,80509,0 -19515,50314520,Mango parvin orgánico,,0,80509,0 -19516,50314521,Mango sandersha orgánico,,0,80509,0 -19517,50314522,Mangos sensation orgánico,,0,80509,0 -19518,50314523,Mango smith orgánico,,0,80509,0 -19519,50314524,Mango tomy atkin orgánico,,0,80509,0 -19520,50314525,Mango van dyke orgánico,,0,80509,0 -19521,50314600,Melones orgánicos,,0,80509,0 -19522,50314601,Melon allsweet orgánico,,0,80509,0 -19523,50314602,Melón athens orgánico,,0,80509,0 -19524,50314603,Melón diamante negro orgánico,,0,80509,0 -19525,50314604,Melón cal dulce orgánico,,0,80509,0 -19526,50314605,Melons cantaloupe orgánico,,0,80509,0 -19527,50314606,Melón carnical orgánico,,0,80509,0 -19528,50314607,Melón casaba orgánico,,0,80509,0 -19529,50314608,Melón cavaillon orgánico,,0,80509,0 -19530,50314609,Melón charentais orgánico,,0,80509,0 -19531,50314610,Sandia charleston gray orgánico,,0,80509,0 -19532,50314611,Melón crenshaw orgánico,,0,80509,0 -19533,50314612,Melón crimson sweet orgánico,,0,80509,0 -19534,50314613,Melón dixie lee orgánico,,0,80509,0 -19535,50314614,Melón eclipse orgánico,,0,80509,0 -19536,50314615,Melón ein dór orgánico,,0,80509,0 -19537,50314616,Melón fiesta orgánico,,0,80509,0 -19538,50314617,Melón galia orgánico,,0,80509,0 -19539,50314618,Melón gaya orgánico,,0,80509,0 -19540,50314619,Melón hami orgánico,,0,80509,0 -19541,50314620,Melón miel dew orgánico,,0,80509,0 -19542,50314621,Melón hielo orgánico,,0,80509,0 -19543,50314622,Melón ida orgullo orgánico,,0,80509,0 -19544,50314623,Melón juan canary orgánico,,0,80509,0 -19545,50314624,Melón jubileo orgánico,,0,80509,0 -19546,50314625,Melón jubilación orgánico,,0,80509,0 -19547,50314626,Melón kaki / kakri orgánico,,0,80509,0 -19548,50314627,Melón kiwano orgánico,,0,80509,0 -19549,50314628,Melon koreano orgánico,Melón coreano orgánico,0,80509,0 -19550,50314629,Melón long grey orgánico,Melón largo gris orgánico,0,80509,0 -19551,50314630,Melón mayan orgánico,,0,80509,0 -19552,50314631,Melón micky lee orgánico,,0,80509,0 -19553,50314632,Melón mirage orgánico,,0,80509,0 -19554,50314633,Sandia luna y estrellas orgánico,,0,80509,0 -19555,50314634,Melón ogen orgánico,,0,80509,0 -19556,50314635,Melón patriot orgánico,,0,80509,0 -19557,50314636,Melón peacook orgánico,,0,80509,0 -19558,50314637,Melón pepino orgánico,,0,80509,0 -19559,50314638,Melón persian orgánico,,0,80509,0 -19560,50314639,Melón picnic orgánico,,0,80509,0 -19561,50314640,Melón piel de sapo orgánico,,0,80509,0 -19562,50314641,Melón piña orgánico,,0,80509,0 -19563,50314642,Melón quetzali orgánico,,0,80509,0 -19564,50314643,Melón goblin rojo orgánico,,0,80509,0 -19565,50314644,Melón regency orgánico,,0,80509,0 -19566,50314645,Melón royal mejestic orgánico,,0,80509,0 -19567,50314646,Melón royal star orgánico,,0,80509,0 -19568,50314647,Melón dulce royal orgánico,,0,80509,0 -19569,50314648,Malon santa claus orgánico,"Melón piel de sapo orgánico, Melón santa claus orgánico",0,80509,0 -19570,50314649,Melón sharlyn orgánico,,0,80509,0 -19571,50314650,Melón español orgánico,,0,80509,0 -19572,50314651,Melón sprite orgánic orgánico,,0,80509,0 -19573,50314652,Melón starbright orgánico,,0,80509,0 -19574,50314653,Melón stars and stripes orgánico,,0,80509,0 -19575,50314654,Melón bebe de azúcar orgánico,,0,80509,0 -19576,50314655,Sandia bebe de azúcar orgánica,,0,80509,0 -19577,50314656,Melón dulce sol orgánico,,0,80509,0 -19578,50314657,Sandia corazón dulce sin semilla orgánica,,0,80509,0 -19579,50314658,Melón tentación orgánico,,0,80509,0 -19580,50314659,Melón bebé tigre orgánico,,0,80509,0 -19581,50314660,Melón tuscan tipo orgánico,,0,80509,0 -19582,50314661,Sandia baby amarillo orgánica,,0,80509,0 -19583,50316200,Frambuesas orgánicas,,0,80509,0 -19584,50316201,Frambuesa americana roja orgánica,,0,80509,0 -19585,50316202,Frambuesa bailey queensland orgánica,,0,80509,0 -19586,50316203,Frambuesa negra orgánica,,0,80509,0 -19587,50316204,Frambuesa oscura orgánica,,0,80509,0 -19588,50316205,Frambuesa deliciosa orgánica,,0,80509,0 -19589,50316206,Frambuesa enano focke orgánica,,0,80509,0 -19590,50316207,Frambuesa focke hojagris orgánica,,0,80509,0 -19591,50316208,Frambuesa focke fresa orgánica,,0,80509,0 -19592,50316209,Frambuesa focke amarillo himalaya orgánica,,0,80509,0 -19593,50316210,Frambuesa dorado orgánic,,0,80509,0 -19594,50316211,Frambuesa gris nuevo mexico orgánica,,0,80509,0 -19595,50316212,Frambuesa jepson blancobark orgánica,,0,80509,0 -19596,50316213,Frambuesa kellogs san diego orgánica,,0,80509,0 -19597,50316214,Frambuesa leucodemis blancobark orgánica,,0,80509,0 -19598,50316215,Frambuesa munz cuyamaca orgánica,,0,80509,0 -19599,50316216,Frambuesa peck bartons orgánica,,0,80509,0 -19600,50316217,Frambuesa flor morada orgánica,,0,80509,0 -19601,50316218,Frambuesa roadside orgánica,,0,80509,0 -19602,50316219,Frambuesa san diego orgánica,,0,80509,0 -19603,50316220,Frambuesa nieve orgánica,,0,80509,0 -19604,50316221,Frambuesa pico de nieve orgánica,,0,80509,0 -19605,50316222,Frambuesa hoja de fresa orgánica,,0,80509,0 -19606,50316223,Frambuesa cultivo dulce orgánica,,0,80509,0 -19607,50316224,Frambuesa tor y gris blancobark orgánica,,0,80509,0 -19608,50316225,Frambuesa caribe orgánica,,0,80509,0 -19609,50316226,Frambuesa blancobark orgánica,,0,80509,0 -19610,50316227,Frambuesa vino orgánica,,0,80509,0 -19611,50316228,Frambuesa himalaya amarillo orgánica,,0,80509,0 -19612,50316229,Frambuesa yu-shan orgánica,,0,80509,0 -19613,50316700,Fresas orgánicas,,0,80509,0 -19614,50316701,Fresa chandler orgánica,,0,80509,0 -19615,50316702,Fresa rumbo junio orgánica,Fresa temporada de junio orgánica,0,80509,0 -19616,50316703,Fresa rumbo siempre orgánica,Fresa de todo el año orgánica,0,80509,0 -19617,50317000,Frutas Nominant orgánicas,"Frutas predominantes orgánicas, Tejocote, Xoconostl",0,80509,0 -19618,50317001,Cupania orgánica,Ackee orgánica,0,80509,0 -19619,50317002,Babaco orgánico,Carica pentágona orgánica,0,80509,0 -19620,50317003,Banana flor orgánica,Flor de plátano orgánica,0,80509,0 -19621,50317004,Baobab orgánica,,0,80509,0 -19622,50317005,Naranja agria orgánica,,0,80509,0 -19623,50317006,Canistel orgánica,Zapote amarillo orgánico,0,80509,0 -19624,50317007,Baya nublosa orgánica,Mora de los pantanos orgánica,0,80509,0 -19625,50317008,Coco orgánico,,0,80509,0 -19626,50317009,Baya drew orgánica,Baya de gota orgánica,0,80509,0 -19627,50317010,Durian orgánica,Durio orgánico,0,80509,0 -19628,50317011,Baya elder orgánica,Baya de sauco orgánica,0,80509,0 -19629,50317012,Feijoa orgánica,Guayabo del Brasil orgánico,0,80509,0 -19630,50317013,Mora orgánica,,0,80509,0 -19631,50317014,Cretaegus orgánica,Majuelo orgánico,0,80509,0 -19632,50317015,Baya miel orgánica,Madreselva azul orgánica,0,80509,0 -19633,50317016,Jaca orgánica,Yaca orgánica,0,80509,0 -19634,50317017,Jambolan orgánica,,0,80509,0 -19635,50317018,Jujube orgánica,Azufaifa orgánica,0,80509,0 -19636,50317019,Liches orgánicos,Lichi orgánico,0,80509,0 -19637,50317020,Mangostinos orgánicos,Mangostán orgánico,0,80509,0 -19638,50317021,Nísperos orgánicos,Medlar orgánico,0,80509,0 -19639,50317022,Mombines orgánica,Hobo orgánico,0,80509,0 -19640,50317023,Monstera orgánica,Cerimán orgánico,0,80509,0 -19641,50317024,Pepinos orgánico,Pepino dulce orgánico,0,80509,0 -19642,50317025,Platono orgánico,Plátano macho orgánico,0,80509,0 -19643,50317026,Peras en escabeche orgánica,Tuna orgánica,0,80509,0 -19644,50317027,Mamonsillo orgánico,Guaya orgánica,0,80509,0 -19645,50317028,Rambután orgánico,Achotillo orgánico,0,80509,0 -19646,50317029,Manzana rosa orgánica,Pero de agua orgánico,0,80509,0 -19647,50317030,Rosele orgánica,Flor de jamaica orgánica,0,80509,0 -19648,50317031,Baya rowan orgánica,Serba orgánica,0,80509,0 -19649,50317032,Baya buckhom de mar orgánica,Espino amarillo orgánico,0,80509,0 -19650,50317033,Baya plata orgánica,Olivo de otoño orgánico,0,80509,0 -19651,50317034,Baya sorbete orgánico,Serba orgánica,0,80509,0 -19652,50317035,Guanabana orgánica,,0,80509,0 -19653,50317036,Manzana estrella orgánica,Cainito orgánico,0,80509,0 -19654,50317037,Tamarindo orgánico,,0,80509,0 -19655,50321500,Manzanas secas,,0,80509,0 -19656,50321501,Manzanas akane secas,Manzana akane secas,0,80509,0 -19657,50321502,Manzana ambrosia seca,,0,80509,0 -19658,50321503,Manzanas api secas,Manzana api secas,0,80509,0 -19659,50321504,Manzanas baldwin secas,Manzana baldwin secas,0,80509,0 -19660,50321505,Manzanas brabum secas,Manzana brabum secas,0,80509,0 -19661,50321506,Manzanas bramley secas,Manzana bramley secas,0,80509,0 -19662,50321507,Manzana joven bramley sec,,0,80509,0 -19663,50321508,Manzana calville blanche d’hiver sec,,0,80509,0 -19664,50321509,Manzana cameo seca,,0,80509,0 -19665,50321510,Manzana charles ross seca,,0,80509,0 -19666,50321511,Manzana codlin seca,,0,80509,0 -19667,50321512,Manzana cortland seca,,0,80509,0 -19668,50321513,Manzana costard seca,,0,80509,0 -19669,50321514,Manzana court pendu plat seca,,0,80509,0 -19670,50321515,Manzana cox´s orange pippin seca,,0,80509,0 -19671,50321516,Manzana crab seca,,0,80509,0 -19672,50321517,Manzana crispin seca,,0,80509,0 -19673,50321518,Manzana delisioso seca,,0,80509,0 -19674,50321519,Manzana duchess seca,,0,80509,0 -19675,50321520,Manzana earlidorado seca,,0,80509,0 -19676,50321521,Manzana early mcintosh seca,,0,80509,0 -19677,50321522,Manzana elstar seca,,0,80509,0 -19678,50321523,Manzana empire seca,,0,80509,0 -19679,50321524,Manzana flor de seca,,0,80509,0 -19680,50321525,Manzana fiji seca,,0,80509,0 -19681,50321526,Manzana gala seca,,0,80509,0 -19682,50321527,Manzana gascoyne´s scarlet seca,,0,80509,0 -19683,50321528,Manzana gillyflower seca,,0,80509,0 -19684,50321529,Manzana ginger dorado seca,,0,80509,0 -19685,50321530,Manzana gladstone seca,,0,80509,0 -19686,50321531,Manzana gloster seca,,0,80509,0 -19687,50321532,Manzana dorado supreme seca,,0,80509,0 -19688,50321533,Manzana doradoen delisioso seca,,0,80509,0 -19689,50321534,Manzana doradoen nobel seca,,0,80509,0 -19690,50321535,Manzana granny smith seca,,0,80509,0 -19691,50321536,Manzana gravenstain seca,,0,80509,0 -19692,50321537,Manzana verdeing seca,,0,80509,0 -19693,50321538,Manzana verdesleeves seca,,0,80509,0 -19694,50321539,Manzana honeycrisp seca,,0,80509,0 -19695,50321540,Manzana howgate wonder seca,,0,80509,0 -19696,50321541,Manzana roja ida seca,,0,80509,0 -19697,50321542,Manzana james grives seca,,0,80509,0 -19698,50321543,Manzana jersey mac seca,,0,80509,0 -19699,50321544,Manzana jaster seca,,0,80509,0 -19700,50321545,Manzana jonadorado seca,,0,80509,0 -19701,50321546,Manzana jonamac seca,,0,80509,0 -19702,50321547,Manzana jonathan seca,,0,80509,0 -19703,50321548,Manzana katy seca,,0,80509,0 -19704,50321549,Manzana kidd´s orange roja seca,,0,80509,0 -19705,50321550,Manzana lady seca,,0,80509,0 -19706,50321551,Manzana law roma seca,,0,80509,0 -19707,50321552,Manzana laxton seca,,0,80509,0 -19708,50321553,Manzana lord derby seca,,0,80509,0 -19709,50321554,Manzana macoun seca,,0,80509,0 -19710,50321555,Manzana mcintosh seca,,0,80509,0 -19711,50321556,Manzana mutsu seca,,0,80509,0 -19712,50321557,Manzana newtown pippin seca,,0,80509,0 -19713,50321558,Manzana northen spy seca seca,,0,80509,0 -19714,50321559,Manzana orleans reinette seca,,0,80509,0 -19715,50321560,Manzana ozark dorado seca,,0,80509,0 -19716,50321561,Manzana pacific rose seca,,0,80509,0 -19717,50321562,Manzana paula roja seca,,0,80509,0 -19718,50321563,Manzana peamain seca,,0,80509,0 -19719,50321564,Manzana pink lady seca,,0,80509,0 -19720,50321565,Manzana pippin seca,,0,80509,0 -19721,50321566,Manzana pitmaston pineapple seca,,0,80509,0 -19722,50321567,Manzana pomme d api seca,,0,80509,0 -19723,50321568,Manzana prime dorado seca,,0,80509,0 -19724,50321569,Manzana roja astrakhan seca,,0,80509,0 -19725,50321570,Manzana roja boscoop seca,,0,80509,0 -19726,50321571,Manzana roja chief seca,,0,80509,0 -19727,50321572,Manzana roja delisioso seca,,0,80509,0 -19728,50321573,Manzana roja grvenstain seca,,0,80509,0 -19729,50321574,Manzana roja roma seca,,0,80509,0 -19730,50321575,Manzana roja stayman seca,,0,80509,0 -19731,50321576,Manzana roja york seca,,0,80509,0 -19732,50321577,Manzana reinette seca,,0,80509,0 -19733,50321578,Manzana roma beuty seca,,0,80509,0 -19734,50321579,Manzana russet seca,,0,80509,0 -19735,50321580,Manzana sierra beaty seca,,0,80509,0 -19736,50321581,Manzana spartan seca,,0,80509,0 -19737,50321582,Manzana stark crimson seca,,0,80509,0 -19738,50321583,Manzana starrey seca,,0,80509,0 -19739,50321584,Manzana stayman seca,,0,80509,0 -19740,50321585,Manzana stayman winesap seca,,0,80509,0 -19741,50321586,Manzana summer rambo seca,,0,80509,0 -19742,50321587,Manzana tsugaru seca,,0,80509,0 -19743,50321588,Manzana twenty ounce seca,,0,80509,0 -19744,50321589,Manzana tydeman roja seca,,0,80509,0 -19745,50321590,Manzana vistabella seca,,0,80509,0 -19746,50321591,Manzana weatley seca,,0,80509,0 -19747,50321592,Manzana blanco joanetine seca,,0,80509,0 -19748,50321593,Manzana blanco transparent seca,,0,80509,0 -19749,50321594,Manzana winesap seca,,0,80509,0 -19750,50321595,Manzana worcester seca,,0,80509,0 -19751,50321596,Manzana york imperial seca,,0,80509,0 -19752,50321600,Albaricoques secos,Chabacanos secos,0,80509,0 -19753,50321601,Abaricoques ambercot seca,Chabacano ambercot seco,0,80509,0 -19754,50321602,Albaricoques apache seca,Chabacano apache seco,0,80509,0 -19755,50321603,Albaricoques brittany dorado seca,Chabacano brittany dorado seco,0,80509,0 -19756,50321604,Albaricoque negro seca,Chabacano negro seco,0,80509,0 -19757,50321605,Albaricoque blenheim seca,Chabacano blenheim seco,0,80509,0 -19758,50321606,Albaricoque bonny seca,Chabacano bonny seco,0,80509,0 -19759,50321607,Albaricoque biluda seca,Chabacano biluda seco,0,80509,0 -19760,50321608,Albaricoque castlebrite seca,Chabacano castlebrite seco,0,80509,0 -19761,50321609,Albaricoque clutha dorado seca,Chabacano clutha dorado seco,0,80509,0 -19762,50321610,Albaricoque clutha sun seca,Chabacano clutha sun seco,0,80509,0 -19763,50321611,Albaricoque derby royal seca,Chabacano derby royal seco,0,80509,0 -19764,50321612,Albaricoque dina seca,Chabacano dina seco,0,80509,0 -19765,50321613,Albaricoque earlicot seca,Chabacano earlicot seco,0,80509,0 -19766,50321614,Albaricoque earliman seca,Chabacano earliman seco,0,80509,0 -19767,50321615,Albaricoque early bright seca,Chabacano early bright seco,0,80509,0 -19768,50321616,Albaricoque flaming dorado seca,Chabacano flaming dorado seco,0,80509,0 -19769,50321617,Albaricoque frenso seca,Chabacano frenso seco,0,80509,0 -19770,50321618,Albaricoque dorado bright seca,Chabacano dorado bright seco,0,80509,0 -19771,50321619,Albaricoque dorado barra seca,Chabacano dorado barra seco,0,80509,0 -19772,50321620,Albaricoque dorado en dulce seca,Chabacano dorado en dulce seco,0,80509,0 -19773,50321621,Albaricoque dorado rico seca,Chabacano dorado rico seco,0,80509,0 -19774,50321622,Albaricoque helena seca,Chabacano helena seco,0,80509,0 -19775,50321623,Albaricoque honeycot seca,Chabacano seco,0,80509,0 -19776,50321624,Albaricoque imperial seca,Chabacano imperial seco,0,80509,0 -19777,50321625,Albaricoque jordanne seca,Chabacano jordanne seco,0,80509,0 -19778,50321626,Albaricoque jumbo cot seca,Chabacano jumbo cot seco,0,80509,0 -19779,50321627,Albaricoque kandy kot seca,Chabacano kandy kot seco,0,80509,0 -19780,50321628,Albaricoque katy seca,Chabacano katy seco,0,80509,0 -19781,50321629,Albaricoque rey seca,Chabacano rey seco,0,80509,0 -19782,50321630,Albaricoque lambertin seca,Chabacano lambertin seco,0,80509,0 -19783,50321631,Albaricoque loma seca,Chabacano loma seco,0,80509,0 -19784,50321632,Albaricoque lulu belle seca,Chabacano lulu belle seco,0,80509,0 -19785,50321633,Albaricoque modesto seca,Chabacano modesto seco,0,80509,0 -19786,50321634,Albaricoque moorpark seca,Chabacano moorpark seco,0,80509,0 -19787,50321635,Albaricoque orangeroja seca,Chabacano orange red seco,0,80509,0 -19788,50321636,Albaricoque palstein seca,Chabacano palstein seco,0,80509,0 -19789,50321637,Albaricoque patterson seca,Chabacano patterson seco,0,80509,0 -19790,50321638,Albaricoque perfection seca,Chabacano perfection seco,0,80509,0 -19791,50321639,Albaricoque poppy seca,Chabacano poppy seco,0,80509,0 -19792,50321640,Albaricoque poppycot seca,Chabacano poppy cot seco,0,80509,0 -19793,50321641,Albaricoque reina seca,Chabacano reina seco,0,80509,0 -19794,50321642,Albaricoque riland seca,Chabacano riland seco,0,80509,0 -19795,50321643,Albaricoque rival seca,Chabacano rival seco,0,80509,0 -19796,50321644,Albaricoque robada seca,Chabacano robada seco,0,80509,0 -19797,50321645,Albaricoque royal seca,Chabacano royal seco,0,80509,0 -19798,50321646,Albaricoque royal blenheim seca,Chabacano royal blenheim seco,0,80509,0 -19799,50321647,Albaricoque royal orange seca,Chabacano royal orange seco,0,80509,0 -19800,50321648,Albaricoque dota de sol seca,Chabacano dota de sol seco,0,80509,0 -19801,50321649,Albaricoque tilton seca,Chabacano tilton seco,0,80509,0 -19802,50321650,Albaricoque tomcot seca,Chabacano tomcot seco,0,80509,0 -19803,50321651,Albaricoque tracy seca,Chabacano tracy seco,0,80509,0 -19804,50321652,Albaricoque tri gem seca,Chabacano tri gem seco,0,80509,0 -19805,50321653,Albaricoque valley dorado seca,Chabacano valley dorado seco,0,80509,0 -19806,50321654,Albaricoque westley seca,Chabacano westley seco,0,80509,0 -19807,50321655,Albaricoque york seca,Chabacano york seco,0,80509,0 -19808,50321700,Bananos secos,Plátanos secos,0,80509,0 -19809,50321701,Banana manzana seca,Plátano apple seco,0,80509,0 -19810,50321702,Banana bebe seca,Plátano bebe seco,0,80509,0 -19811,50321703,Banana burro seca,Plátano burro seco,0,80509,0 -19812,50321704,Bananas cavendish seca,Plátano cavendish seco,0,80509,0 -19813,50321705,Bananas dominico seca,Plátano dominico seco,0,80509,0 -19814,50321706,Bananas verde seca,Plátano verde seco,0,80509,0 -19815,50321707,Bananas gros michel seca,Plátano gros michel seco,0,80509,0 -19816,50321708,Bananas lacatan seca,Plátano lacatan seco,0,80509,0 -19817,50321709,Bananas dedo de dama seca,Plátano dedo de dama seco,0,80509,0 -19818,50321710,Bananas manzano seca,Plátano manzano seco,0,80509,0 -19819,50321711,Banana mysore seca,Plátano mysore seco,0,80509,0 -19820,50321712,Banana pisang mas seca,Plátano pisang mas seco,0,80509,0 -19821,50321713,Bananas roja seca,Plátano roja seco,0,80509,0 -19822,50321714,Bananas saba seca,Plátano saba seco,0,80509,0 -19823,50321715,Bananas sucrier seca,Plátano sucrier seco,0,80509,0 -19824,50322500,Cerezas secas,,0,80509,0 -19825,50322501,Cereza amarelle seca,,0,80509,0 -19826,50322502,Cereza brooks seca,,0,80509,0 -19827,50322503,Cereza bigarreu seca,,0,80509,0 -19828,50322504,Cereza bing seca,,0,80509,0 -19829,50322505,Cereza blach republic seca,,0,80509,0 -19830,50322506,Cereza negro schmidt seca,,0,80509,0 -19831,50322507,Cereza negro tartarian seca,,0,80509,0 -19832,50322508,Cereza fiesta bing seca,,0,80509,0 -19833,50322509,Cereza garnet seca,,0,80509,0 -19834,50322510,Cereza rey seca,,0,80509,0 -19835,50322511,Cereza chapman seca,,0,80509,0 -19836,50322512,Cereza lapin seca,,0,80509,0 -19837,50322513,Cereza larian seca,,0,80509,0 -19838,50322514,Cereza dark guines seca,,0,80509,0 -19839,50322515,Cereza montmorency seca,,0,80509,0 -19840,50322516,Cereza duque seca,,0,80509,0 -19841,50322517,Cereza early rivers seca,,0,80509,0 -19842,50322518,Cereza ruby bing seca,,0,80509,0 -19843,50322519,Cereza santine seca,,0,80509,0 -19844,50322520,Cereza geans/guines seca,,0,80509,0 -19845,50322521,Cereza sonata seca,,0,80509,0 -19846,50322522,Cereza lambert seca,,0,80509,0 -19847,50322523,Cereza stella seca,,0,80509,0 -19848,50322524,Cereza sweethart seca,,0,80509,0 -19849,50322525,Cereza tartarian seca,,0,80509,0 -19850,50322527,Cereza maraschino seca,,0,80509,0 -19851,50322528,Cereza van seca,,0,80509,0 -19852,50322529,Cereza morello seca,,0,80509,0 -19853,50322530,Cereza ann real seca,,0,80509,0 -19854,50322531,Cereza ranier seca,,0,80509,0 -19855,50322532,Cereza real seca,,0,80509,0 -19856,50322700,Arándanos secos,Arándanos rojos secos,0,80509,0 -19857,50322701,Arandano ben lear seca,Arándano rojo ben lear seco,0,80509,0 -19858,50322702,Arandano early negro seca,Arándano rojo early negro seco,0,80509,0 -19859,50322703,Arandano gryclesik seca,Arándano rojo gryclesik seco,0,80509,0 -19860,50322704,Arandano howe seca,Arándano rojo howe seco,0,80509,0 -19861,50322705,Bayas lingon seca,Arándano rojo lingon seco,0,80509,0 -19862,50322706,Arandano mcfarling seca,Arándano rojo mcfarling seco,0,80509,0 -19863,50322707,Arandano mauntain seca,Arándano rojo mauntain seco,0,80509,0 -19864,50322708,Arandano pilgrim seca,Arándano rojo pilgrim seco,0,80509,0 -19865,50322709,Arandano searless seca,Arándano rojo searless seco,0,80509,0 -19866,50322710,Arandano stevens seca,Arándano rojo stevens seco,0,80509,0 -19867,50323400,Uvas de mesa secas,,0,80509,0 -19868,50323401,Uva alicante seca,,0,80509,0 -19869,50323402,Uva almeira seca,,0,80509,0 -19870,50323403,Uva alphonse lavalle seca,,0,80509,0 -19871,50323404,Uva otoño rey grapes seca,,0,80509,0 -19872,50323405,Uva otoño royal seca,,0,80509,0 -19873,50323406,Uva otoño sin pepa seca,,0,80509,0 -19874,50323407,Uva baresana seca,,0,80509,0 -19875,50323408,Uva barlinka seca,,0,80509,0 -19876,50323409,Uva beaty seedless seca,,0,80509,0 -19877,50323410,Uva negro beauty sin pepa seca,,0,80509,0 -19878,50323411,Uva negro esmeralda seca,,0,80509,0 -19879,50323412,Uva negro gigante seca,,0,80509,0 -19880,50323413,Uva negro globo seca,,0,80509,0 -19881,50323414,Uva negro monnukka seca,,0,80509,0 -19882,50323415,Uva negro perla seca,,0,80509,0 -19883,50323416,Uva negro sin pepa seca,,0,80509,0 -19884,50323417,Uva bonheur seca,,0,80509,0 -19885,50323418,Uva calmeria seca,,0,80509,0 -19886,50323419,Uva cardinal seca,,0,80509,0 -19887,50323420,Uva catawba seca,,0,80509,0 -19888,50323421,Uva chasselas/doradoen chasselas seca,,0,80509,0 -19889,50323422,Uva christmas rose seca,,0,80509,0 -19890,50323423,Uva concord seca,,0,80509,0 -19891,50323424,Uva concord sin pepa seca,,0,80509,0 -19892,50323425,Uva crimson sin pepa seca,,0,80509,0 -19893,50323426,Uva dauphine seca,,0,80509,0 -19894,50323427,Uva daleware seca,,0,80509,0 -19895,50323428,Uva early muscat seca,,0,80509,0 -19896,50323429,Uva early sweet seca,,0,80509,0 -19897,50323430,Uva esmeralda sin pepa seca,,0,80509,0 -19898,50323431,Uva emperatriz seca,,0,80509,0 -19899,50323432,Uva emperor seca,,0,80509,0 -19900,50323433,Uva empress seca,,0,80509,0 -19901,50323434,Uva exótica seca,,0,80509,0 -19902,50323435,Uva fantasia seca,,0,80509,0 -19903,50323436,Uva fantasy sin pepa seca,,0,80509,0 -19904,50323437,Uva llama seca,,0,80509,0 -19905,50323438,Uva llama sin pepa seca,,0,80509,0 -19906,50323439,Uva llama tokay seca,,0,80509,0 -19907,50323440,Uva ardiente roja seca,,0,80509,0 -19908,50323441,Uva galaxy sin pepa seca,,0,80509,0 -19909,50323442,Uva gamay seca,,0,80509,0 -19910,50323443,Uva dorada seca,,0,80509,0 -19911,50323444,Uva hanepot o honeypot seca,,0,80509,0 -19912,50323445,Uva italia seca,,0,80509,0 -19913,50323446,Uva jade sin pepa seca,,0,80509,0 -19914,50323447,Uva jubilee seca,,0,80509,0 -19915,50323448,Uva rey ruby seca,,0,80509,0 -19916,50323449,Uva kyoho seca,,0,80509,0 -19917,50323450,Uva la rochelle seca,,0,80509,0 -19918,50323451,Uva dedo de dama seca,,0,80509,0 -19919,50323452,Uva late sin pepa seca,,0,80509,0 -19920,50323453,Uva majestic sin pepa seca,,0,80509,0 -19921,50323454,Uva malaga seca,,0,80509,0 -19922,50323455,Uva marro sin pepa seca,,0,80509,0 -19923,50323456,Uva muscadine seca,,0,80509,0 -19924,50323457,Uva muscat llama seca,,0,80509,0 -19925,50323458,Uva muscat seca,,0,80509,0 -19926,50323459,Uva muscat sin papa seca,,0,80509,0 -19927,50323460,Uva napoleon seca,,0,80509,0 -19928,50323461,Uva nigeria seca,,0,80509,0 -19929,50323462,Uva new cross seca,,0,80509,0 -19930,50323463,Uva nibella seca,,0,80509,0 -19931,50323464,Uva niagara seca,,0,80509,0 -19932,50323465,Uva olivette seca,,0,80509,0 -19933,50323466,Uva perlette seca,,0,80509,0 -19934,50323467,Uva perlon seca,,0,80509,0 -19935,50323468,Uva prima negro sin pepa seca,,0,80509,0 -19936,50323469,Uva prncess seca,,0,80509,0 -19937,50323470,Uva reina seca,,0,80509,0 -19938,50323471,Uva roja blush seca,,0,80509,0 -19939,50323472,Uva roja globe seca,,0,80509,0 -19940,50323473,Uva roja malaga seca,,0,80509,0 -19941,50323474,Uva roja sin pepa seca,,0,80509,0 -19942,50323475,Uva regina seca,,0,80509,0 -19943,50323476,Uva ribier seca,,0,80509,0 -19944,50323477,Uva rosita seca,,0,80509,0 -19945,50323478,Uva rouge seca,,0,80509,0 -19946,50323479,Uva royal negra sin pepa seca,,0,80509,0 -19947,50323480,Uva ruby roja sin pepa seca,,0,80509,0 -19948,50323481,Uva ryby sin pepa seca,,0,80509,0 -19949,50323482,Uva scarlet royal seca,,0,80509,0 -19950,50323483,Uva scuppemong seca,,0,80509,0 -19951,50323484,Uva sugarose seca,,0,80509,0 -19952,50323485,Uva sugartirteen seca,,0,80509,0 -19953,50323486,Uva sugraone seca,,0,80509,0 -19954,50323487,Uva sugrasixteen seca,,0,80509,0 -19955,50323488,Uva sultana sun roja seca,,0,80509,0 -19956,50323489,Uva verano royal seca,,0,80509,0 -19957,50323490,Uva atardecer seca,,0,80509,0 -19958,50323491,Uva superior sin pepa seca,,0,80509,0 -19959,50323492,Uva thompson sin pepa seca,,0,80509,0 -19960,50323493,Uva tokaylpinot gris seca,,0,80509,0 -19961,50323494,Uva waltman cross seca,,0,80509,0 -19962,50323495,Uva blanca sin semilla seca,,0,80509,0 -19963,50323496,Uva zante current seca,,0,80509,0 -19964,50323500,Uvas pasas secas,,0,80509,0 -19965,50323501,Uva corinth negra seca,,0,80509,0 -19966,50323502,Uva canner seca,,0,80509,0 -19967,50323503,Uva dovine seca,,0,80509,0 -19968,50323504,Uva fiesta seca,,0,80509,0 -19969,50323505,Uva selme pete seca,,0,80509,0 -19970,50323506,Uva sultana seca,,0,80509,0 -19971,50323800,Arándanos Huckleberry secos,Arándanos azules secos,0,80509,0 -19972,50323801,Arándano invierno negro seco,Arándano azul invierno negro seco,0,80509,0 -19973,50323802,Arándano cascada seco,Arándano azul cascada seco,0,80509,0 -19974,50323803,Arándano enano seco,Arándano azul enano seco,0,80509,0 -19975,50323804,Arándano montaña seco,Arándano azul montaña seco,0,80509,0 -19976,50323805,Arándano rojo seco,Arándano azul red seco,0,80509,0 -19977,50323900,Kiwis secos,,0,80509,0 -19978,50323901,Fruta de kiwi ananasnaja seca,Kiwi ananasnaja seco,0,80509,0 -19979,50323902,Fruta de kiwi belleza del ártico seca,Kiwi belleza del ártico seco,0,80509,0 -19980,50323903,Fruta de kiwi blake seca,Kiwi blake seco,0,80509,0 -19981,50323904,Fruta de kiwi hayward seca,Kiwi hayward seco,0,80509,0 -19982,50323905,Fruta de kiwi issai seca,Kiwi issai seco,0,80509,0 -19983,50323906,Fruta de kiwi siberiana seca,Kiwi siberiano seco,0,80509,0 -19984,50324500,Mangos secos,,0,80509,0 -19985,50324501,Mango alphonso seco,,0,80509,0 -19986,50324502,Mango ataulfo seco,,0,80509,0 -19987,50324503,Mango criollo seco,,0,80509,0 -19988,50324504,Mango edwards seco,,0,80509,0 -19989,50324505,Mango francine seco,,0,80509,0 -19990,50324506,Mango francis seco,,0,80509,0 -19991,50324507,Mango gandaria seco,,0,80509,0 -19992,50324508,Mango haden seco,,0,80509,0 -19993,50324509,Mangos irwin seco,,0,80509,0 -19994,50324510,Mangos keitt seco,,0,80509,0 -19995,50324511,Mangos kent seco,,0,80509,0 -19996,50324512,Mangos kesar seco,,0,80509,0 -19997,50324513,Mangos kuini seco,,0,80509,0 -19998,50324514,Mangos manila super seco,,0,80509,0 -19999,50324515,Mangos manila seco,,0,80509,0 -20000,50324516,Mangos mayaguez seco,,0,80509,0 -20001,50324517,Mangos mulgoba seco,,0,80509,0 -20002,50324518,Mangos oro seco,,0,80509,0 -20003,50324519,Mangos palmer seco,,0,80509,0 -20004,50324520,Mango parvin seco,,0,80509,0 -20005,50324521,Mango sandersha seco,,0,80509,0 -20006,50324522,Mangos sensation seco,,0,80509,0 -20007,50324523,Mango smith seco,,0,80509,0 -20008,50324524,Mango tomy atkin seco,,0,80509,0 -20009,50324525,Mango van dyke seco,,0,80509,0 -20010,50324600,Melones secos,,0,80509,0 -20011,50324601,Melon allsweet seco,,0,80509,0 -20012,50324602,Melón athens seco,,0,80509,0 -20013,50324603,Melón diamante negro seco,,0,80509,0 -20014,50324604,Melón cal sweet seco,,0,80509,0 -20015,50324605,Melons cantaloupe seco,,0,80509,0 -20016,50324606,Melón carnical seco,,0,80509,0 -20017,50324607,Melón casaba seco,,0,80509,0 -20018,50324608,Melón cavaillon seco,,0,80509,0 -20019,50324609,Melón charentais seco,,0,80509,0 -20020,50324610,Sandia charleston gray seco,,0,80509,0 -20021,50324611,Melón crenshaw seco,,0,80509,0 -20022,50324612,Melón crimson sweet seco,,0,80509,0 -20023,50324613,Melón dixie lee seco,,0,80509,0 -20024,50324614,Melón eclipse seco,,0,80509,0 -20025,50324615,Melón ein dór seco,,0,80509,0 -20026,50324616,Melón fiesta seco,,0,80509,0 -20027,50324617,Melón galia seco,,0,80509,0 -20028,50324618,Melón gaya seco,,0,80509,0 -20029,50324619,Melón hami seco,,0,80509,0 -20030,50324620,Melón miel dew seco,,0,80509,0 -20031,50324621,Melón hielo seco,,0,80509,0 -20032,50324622,Melón ida orgullo seco,,0,80509,0 -20033,50324623,Melón juan canary seco,,0,80509,0 -20034,50324624,Melón jubileo seco,,0,80509,0 -20035,50324625,Melón jubilación seco,,0,80509,0 -20036,50324626,Melón kaki / kakri seco,,0,80509,0 -20037,50324627,Melón kiwano seco,,0,80509,0 -20038,50324628,Melon koreano seco,Melón coreano seco,0,80509,0 -20039,50324629,Melón long grey seco,Melón largo gris seco,0,80509,0 -20040,50324630,Melón mayan seco,,0,80509,0 -20041,50324631,Melón micky lee seco,,0,80509,0 -20042,50324632,Melón mirage seco,,0,80509,0 -20043,50324633,Sandia luna y estrellas seco,,0,80509,0 -20044,50324634,Melón ogen seco,,0,80509,0 -20045,50324635,Melón patriot seco,,0,80509,0 -20046,50324636,Melón pavo real seco,,0,80509,0 -20047,50324637,Melón pepino seco,,0,80509,0 -20048,50324638,Melón persian seco,,0,80509,0 -20049,50324639,Melón picnic seco,,0,80509,0 -20050,50324640,Melón piel de sapo seco,,0,80509,0 -20051,50324641,Melón piña seco,,0,80509,0 -20052,50324642,Melón quetzali seco,,0,80509,0 -20053,50324643,Melón goblin rojo seco,,0,80509,0 -20054,50324644,Melón regency seco,,0,80509,0 -20055,50324645,Melón royal mejestic seco,,0,80509,0 -20056,50324646,Melón royal star seco,,0,80509,0 -20057,50324647,Melón dulce royal seco,,0,80509,0 -20058,50324648,Malon santa claus seco,"Melón piel de sapo seco, Melón santa claus seco",0,80509,0 -20059,50324649,Melón sharlyn seco,,0,80509,0 -20060,50324650,Melón español seco,,0,80509,0 -20061,50324651,Melón sprite sec seco,,0,80509,0 -20062,50324652,Melón starbright seco,,0,80509,0 -20063,50324653,Melón stars and stripes seco,,0,80509,0 -20064,50324654,Melón bebe de azúcar seco,,0,80509,0 -20065,50324655,Sandia bebe de azúcar seca,,0,80509,0 -20066,50324656,Melón dulce sol seco,,0,80509,0 -20067,50324657,Sandia corazón dulce sin semilla seca,,0,80509,0 -20068,50324658,Melón tentación seco,,0,80509,0 -20069,50324659,Melón bebe tigre seco,,0,80509,0 -20070,50324660,Melón tuscan tipo seco,,0,80509,0 -20071,50324661,Sandia bebe amarillo seca,,0,80509,0 -20072,50325100,Papayas secas,,0,80509,0 -20073,50325101,Papaya verde cocinar seca,,0,80509,0 -20074,50325102,Papaya maradol seca,,0,80509,0 -20075,50325103,Papaya amarillo mexicano seca,,0,80509,0 -20076,50325104,Papaya montaña seca,,0,80509,0 -20077,50325105,Papaya solo seca,,0,80509,0 -20078,50325106,Papaya tainung seca,,0,80509,0 -20079,50325300,Duraznos secos,,0,80509,0 -20080,50325301,Durazno escudo amber seco,,0,80509,0 -20081,50325302,Durazno nieve de abril seco,,0,80509,0 -20082,50325303,Durazno dama de agosto seco,,0,80509,0 -20083,50325304,Durazno llama de otoño seco,,0,80509,0 -20084,50325305,Durazno dama de otoño seco,,0,80509,0 -20085,50325306,Durazno babcock seco,,0,80509,0 -20086,50325307,Durazno brittany lane seco,,0,80509,0 -20087,50325308,Durazno cary mac seco,,0,80509,0 -20088,50325309,Durazno clásica seco,,0,80509,0 -20089,50325310,Durazno dulce del campo seco,,0,80509,0 -20090,50325311,Durazno escudo de cielo seco,,0,80509,0 -20091,50325312,Durazno dama crimson seco,,0,80509,0 -20092,50325313,Durazno príncipe corona seco,,0,80509,0 -20093,50325314,Durazno sol david seco,,0,80509,0 -20094,50325315,Durazno princesa diamante seco,,0,80509,0 -20095,50325316,Durazno earlrich seco,,0,80509,0 -20096,50325317,Durazno majestuosa temprana seco,,0,80509,0 -20097,50325318,Durazno early treat seco,,0,80509,0 -20098,50325319,Durazno dama elegante seco,,0,80509,0 -20099,50325320,Durazno emperatriz seco,,0,80509,0 -20100,50325321,Durazno encoré seco,,0,80509,0 -20101,50325322,Durazno dama elegante seco,,0,80509,0 -20102,50325323,Durazno príncipe de fuego seco,,0,80509,0 -20103,50325324,Durazno escudo de llama seco,,0,80509,0 -20104,50325325,Durazno tipo plano seco,,0,80509,0 -20105,50325326,Durazno escudo de sabor seco,,0,80509,0 -20106,50325327,Durazno príncipe florida seco,,0,80509,0 -20107,50325328,Durazno luna llena seco,,0,80509,0 -20108,50325329,Durazno harvester seco,,0,80509,0 -20109,50325330,Durazno princesa de hielo seco,,0,80509,0 -20110,50325331,Durazno princesa de marfil seco,,0,80509,0 -20111,50325332,Durazno princesa reina jersey seco,,0,80509,0 -20112,50325333,Durazno john henry seco,,0,80509,0 -20113,50325334,Durazno príncipe de junio seco,,0,80509,0 -20114,50325335,Durazno kaweah seco,,0,80509,0 -20115,50325336,Durazno klondike seco,,0,80509,0 -20116,50325337,Durazno lindo seco,,0,80509,0 -20117,50325338,Durazno loring seco,,0,80509,0 -20118,50325339,Durazno majestuoso seco,,0,80509,0 -20119,50325340,Durazno o’henry seco,,0,80509,0 -20120,50325341,Durazno escudo de reina seco,,0,80509,0 -20121,50325342,Durazno dama roja seco,,0,80509,0 -20122,50325343,Durazno globo rojo seco,,0,80509,0 -20123,50325344,Durazno cielo rojo seco,,0,80509,0 -20124,50325345,Durazno redtop seco,,0,80509,0 -20125,50325346,Durazno regina seco,,0,80509,0 -20126,50325347,Durazno dama rica seco,,0,80509,0 -20127,50325348,Durazno mayo rico seco,,0,80509,0 -20128,50325349,Durazno gloria real seco,,0,80509,0 -20129,50325350,Durazno dama real seco,,0,80509,0 -20130,50325351,Durazno nieve de septiembre seco,,0,80509,0 -20131,50325352,Durazno sol de septiembre seco,,0,80509,0 -20132,50325353,Durazno gema sierra seco,,0,80509,0 -20133,50325354,Durazno angel de nieve seco,,0,80509,0 -20134,50325355,Durazno gema de nieve seco,,0,80509,0 -20135,50325356,Durazno rey de nieve seco,,0,80509,0 -20136,50325357,Durazno dama de primavera seco,,0,80509,0 -20137,50325358,Durazno nieve de primavera seco,,0,80509,0 -20138,50325359,Durazno escudo de primavera seco,,0,80509,0 -20139,50325360,Durazno dulce gigante seco,,0,80509,0 -20140,50325361,Durazno dama de azúcar seco,,0,80509,0 -20141,50325362,Durazno brillo de sol seco,,0,80509,0 -20142,50325363,Durazno sunhigh seco,,0,80509,0 -20143,50325364,Durazno dama super seco,,0,80509,0 -20144,50325365,Durazno super rico seco,,0,80509,0 -20145,50325366,Durazno surecrop seco,,0,80509,0 -20146,50325367,Durazno sueño dulce seco,,0,80509,0 -20147,50325368,Durazno septiembre dulce seco,,0,80509,0 -20148,50325369,Durazno vista seco,,0,80509,0 -20149,50325370,Durazno dama blanca seco,,0,80509,0 -20150,50325371,Durazno dama zee seco,,0,80509,0 -20151,50325400,Peras secas,,0,80509,0 -20152,50325401,Peras abate fetel seca,,0,80509,0 -20153,50325402,Peras anjou seca,,0,80509,0 -20154,50325403,Pera asiática seca,,0,80509,0 -20155,50325404,Pera bartlett seca,,0,80509,0 -20156,50325405,Pera best ever seca,,0,80509,0 -20157,50325406,Pera beth seca,,0,80509,0 -20158,50325407,Pera beurre seca,,0,80509,0 -20159,50325408,Pera bosc seca,,0,80509,0 -20160,50325409,Pera clapp favorita seca,,0,80509,0 -20161,50325410,Pera comice seca,,0,80509,0 -20162,50325411,Pera concorde seca,,0,80509,0 -20163,50325412,Pera conference seca,,0,80509,0 -20164,50325413,Pera crimson rojo seca,,0,80509,0 -20165,50325414,Peras d’ anjou seca,,0,80509,0 -20166,50325415,Pera dr jules guyot seca,,0,80509,0 -20167,50325416,Peras early seca,,0,80509,0 -20168,50325417,Peras emperador carmelito seca,,0,80509,0 -20169,50325418,Peras forelle seca,,0,80509,0 -20170,50325419,Pera mantequilla francesa seca,,0,80509,0 -20171,50325420,Pera glou morceau seca,,0,80509,0 -20172,50325421,Pera hosui seca,,0,80509,0 -20173,50325422,Pera mantequilla italiana seca,,0,80509,0 -20174,50325423,Pera jargonelle seca,,0,80509,0 -20175,50325424,Pera juno seca,,0,80509,0 -20176,50325425,Para kaiserlouise bonne de jersey seca,,0,80509,0 -20177,50325426,Pera keiffer seca,,0,80509,0 -20178,50325427,Pera rey royal seca,,0,80509,0 -20179,50325428,Pera limonera seca,,0,80509,0 -20180,50325429,Pera merton pride seca,,0,80509,0 -20181,50325430,Pera mauntain bartlette seca,,0,80509,0 -20182,50325431,Pera oliver de serrers seca,,0,80509,0 -20183,50325432,Pera onward seca,,0,80509,0 -20184,50325433,Pera packham´s triumph seca,,0,80509,0 -20185,50325434,Pera paraíso seca,,0,80509,0 -20186,50325435,Pera passe crassane seca,,0,80509,0 -20187,50325436,Pera perry seca,,0,80509,0 -20188,50325437,Pera bartlett rojo seca,,0,80509,0 -20189,50325438,Pera dánjou rojo seca,,0,80509,0 -20190,50325439,Pera rocha seca,,0,80509,0 -20191,50325440,Pera rosey rojo seca,,0,80509,0 -20192,50325441,Pera rosy roja seca,,0,80509,0 -20193,50325442,Pera majestuosa royal seca,,0,80509,0 -20194,50325443,Pera ruby rojo seca,,0,80509,0 -20195,50325444,Pera santa maria seca,,0,80509,0 -20196,50325445,Pera seckel seca,,0,80509,0 -20197,50325446,Pera sensación seca,,0,80509,0 -20198,50325447,Pera crimson estrella seca,,0,80509,0 -20199,50325448,Pera crimson stark seca,,0,80509,0 -20200,50325449,Pera bartlette de verano seca,,0,80509,0 -20201,50325450,Pera verano dorado seca,,0,80509,0 -20202,50325451,Pera sol dorado seca,,0,80509,0 -20203,50325452,Pera sunprite seca,,0,80509,0 -20204,50325453,Pera taylors dorado seca,,0,80509,0 -20205,50325454,Pera taylors rojo seca,,0,80509,0 -20206,50325455,Pera tientsin seca,,0,80509,0 -20207,50325456,Pera tosca seca,,0,80509,0 -20208,50325457,Pera warden seca,,0,80509,0 -20209,50325458,Pera williams bon chretien seca,,0,80509,0 -20210,50325459,Pera williams seca,,0,80509,0 -20211,50325460,Pera nelis de invierno seca,,0,80509,0 -20212,50325600,Piñas secas,,0,80509,0 -20213,50325601,Piña chirimoya seca,,0,80509,0 -20214,50325602,Piña dorada seca,,0,80509,0 -20215,50325603,Piña hilo seca,,0,80509,0 -20216,50325604,Piña kona sugarloaf seca,,0,80509,0 -20217,50325605,Piña reina natal seca,,0,80509,0 -20218,50325606,Piña pernabuco seca,,0,80509,0 -20219,50325607,Piña español rojo seca,,0,80509,0 -20220,50325608,Piña cayen suave seca,,0,80509,0 -20221,50325609,Piña sugarloaf seca,,0,80509,0 -20222,50325610,Piña variegated seca,,0,80509,0 -20223,50325800,Ciruelas secas,,0,80509,0 -20224,50325801,Ciruela joya amber seca,,0,80509,0 -20225,50325802,Ciruela angeleno seca,,0,80509,0 -20226,50325803,Ciruela aurora seca,,0,80509,0 -20227,50325804,Ciruela otoño bonito seca,,0,80509,0 -20228,50325805,Ciruela gigante de otoño seca,,0,80509,0 -20229,50325806,Ciruela orgullo de otoño seca,,0,80509,0 -20230,50325807,Ciruela rosa de otoño seca,,0,80509,0 -20231,50325808,Ciruela playero seca,,0,80509,0 -20232,50325809,Ciruela betty anne seca,,0,80509,0 -20233,50325810,Ciruela belleza negra seca,,0,80509,0 -20234,50325811,Ciruela bullase negra seca,,0,80509,0 -20235,50325812,Ciruela diamante negro seca,,0,80509,0 -20236,50325813,Ciruela gigante negro seca,,0,80509,0 -20237,50325814,Ciruela hielo negro seca,,0,80509,0 -20238,50325815,Ciruela esplendor negro seca,,0,80509,0 -20239,50325816,Ciruela ámbar negro seca,,0,80509,0 -20240,50325817,Ciruela vino tinto seca,,0,80509,0 -20241,50325818,Ciruela carlsbad seca,,0,80509,0 -20242,50325819,Ciruela casselman seca,,0,80509,0 -20243,50325820,Ciruela catalina seca,,0,80509,0 -20244,50325821,Ciruela damson seca,,0,80509,0 -20245,50325822,Ciruela dolly seca,,0,80509,0 -20246,50325823,Ciruela earlireina seca,,0,80509,0 -20247,50325824,Ciruela rosa early seca,,0,80509,0 -20248,50325825,Ciruela ébano mayo seca,,0,80509,0 -20249,50325826,Ciruela ébano seca,,0,80509,0 -20250,50325827,Ciruela corazón de elefante seca,,0,80509,0 -20251,50325828,Ciruela belleza de esmeralda seca,,0,80509,0 -20252,50325829,Ciruela emperatriz seca,,0,80509,0 -20253,50325830,Ciruela libertad seca,,0,80509,0 -20254,50325831,Ciruela friar seca,,0,80509,0 -20255,50325832,Ciruela rojo gar seca,,0,80509,0 -20256,50325833,Ciruela gobernador seca,,0,80509,0 -20257,50325834,Ciruela rosa grande seca,,0,80509,0 -20258,50325835,Ciruela green gage seca,Ciruela calibre verde seca,0,80509,0 -20259,50325836,Ciruela greengage seca,,0,80509,0 -20260,50325837,Ciruela hiromi seca,,0,80509,0 -20261,50325838,Ciruela hiromi rojo seca,,0,80509,0 -20262,50325839,Ciruela vacacion seca,,0,80509,0 -20263,50325840,Ciruela howard sol seca,,0,80509,0 -20264,50325841,Ciruela tipo interspecific seca,,0,80509,0 -20265,50325842,Ciruela jamaico seca,,0,80509,0 -20266,50325843,Ciruela joanna rojo seca,,0,80509,0 -20267,50325844,Ciruela kelsey seca,,0,80509,0 -20268,50325845,Ciruela jaime rey seca,,0,80509,0 -20269,50325846,Ciruela laroda seca,,0,80509,0 -20270,50325847,Ciruela rosa tarde seca,,0,80509,0 -20271,50325848,Ciruela rosa linda seca,,0,80509,0 -20272,50325849,Ciruela estrella solitaria seca,,0,80509,0 -20273,50325850,Ciruela mariposa seca,,0,80509,0 -20274,50325851,Ciruela mercado negro seca,,0,80509,0 -20275,50325852,Ciruela mercado rojo seca,,0,80509,0 -20276,50325853,Ciruela maribel seca,,0,80509,0 -20277,50325854,Ciruelas sol de octubre seca,,0,80509,0 -20278,50325855,Ciruela owen t seca,,0,80509,0 -20279,50325856,Ciruela perdrigon seca,,0,80509,0 -20280,50325857,Ciruela placer rosado seca,,0,80509,0 -20281,50325858,Ciruela presidente seca,,0,80509,0 -20282,50325859,Ciruela hora prima seca,Ciruela prime time seca,0,80509,0 -20283,50325860,Ciruela majestad purpura seca,,0,80509,0 -20284,50325861,Ciruela reina rosa seca,,0,80509,0 -20285,50325862,Ciruela quetsch seca,,0,80509,0 -20286,50325863,Ciruela belleza roja seca,,0,80509,0 -20287,50325864,Ciruela camino rojo seca,,0,80509,0 -20288,50325865,Ciruela ram rojo seca,,0,80509,0 -20289,50325866,Ciruela rosa roja seca,,0,80509,0 -20290,50325867,Ciruela rojo rico seca,,0,80509,0 -20291,50325868,Ciruela romaro seca,,0,80509,0 -20292,50325869,Ciruela diamante rojo seca,,0,80509,0 -20293,50325870,Ciruela rojo royal seca,,0,80509,0 -20294,50325871,Ciruela royal zee seca,,0,80509,0 -20295,50325872,Ciruela roysum seca,,0,80509,0 -20296,50325873,Ciruela santa rosa seca,,0,80509,0 -20297,50325874,Ciruela zafiro seca,,0,80509,0 -20298,50325875,Ciruela sloe seca,,0,80509,0 -20299,50325876,Ciruela sta catherine seca,,0,80509,0 -20300,50325877,Ciruela bullase blanco seca,,0,80509,0 -20301,50326800,Manzanas dulces secas,Saramuyos secos,0,80509,0 -20302,50326801,Manzana kampong malve dulce seca,Saramuyo kampong mauve seco,0,80509,0 -20303,50326802,Manzana dulce sin pepa seca,Saramuyo sin semilla seco,0,80509,0 -20304,50326803,Manzana dulce thai lessand seca,Saramuyo thai lessand seco,0,80509,0 -20305,50327000,Frutas Nominant secas,Frutas predominantes secas,0,80509,0 -20306,50327001,Cupania seca,Ackee seca,0,80509,0 -20307,50327002,Babaco seco,Carica pentágona seca,0,80509,0 -20308,50327003,Banana flor seca,Flor de plátano seca,0,80509,0 -20309,50327004,Baobab seca,,0,80509,0 -20310,50327005,Naranja agria seca,,0,80509,0 -20311,50327006,Canistel seca,Zapote amarillo seco,0,80509,0 -20312,50327007,Baya nublosa seca,Mora de los pantanos seca,0,80509,0 -20313,50327008,Coco seco,,0,80509,0 -20314,50327009,Baya drew seca,Baya de gota seca,0,80509,0 -20315,50327010,Durian seca,Durio seco,0,80509,0 -20316,50327011,Baya elder seca,Baya de sauco seca,0,80509,0 -20317,50327012,Feijoa seca,Guayabo del Brasil seco,0,80509,0 -20318,50327013,Mora seca,,0,80509,0 -20319,50327014,Cretaegus seca,Majuelo seco,0,80509,0 -20320,50327015,Baya miel seca,Madreselva azul seca,0,80509,0 -20321,50327016,Jaca seca,Yaca seca,0,80509,0 -20322,50327017,Jambolan seca,,0,80509,0 -20323,50327018,Jujube seca,Azufaifa seca,0,80509,0 -20324,50327019,Liches secos,Lichi seco,0,80509,0 -20325,50327020,Mangostinos secos,Mangostán seco,0,80509,0 -20326,50327021,Medlar seca,,0,80509,0 -20327,50327022,Mombines seca,Hobo seco,0,80509,0 -20328,50327023,Monstera seca,Cerimán seco,0,80509,0 -20329,50327024,Pepinos seco,Pepino dulce seco,0,80509,0 -20330,50327025,Platono seco,Plátano macho seco,0,80509,0 -20331,50327026,Peras en escabeche seca,Tuna seca,0,80509,0 -20332,50327027,Mamonsillo seco,Guaya seca,0,80509,0 -20333,50327028,Rambután seco,Achotillo seco,0,80509,0 -20334,50327029,Manzana rosa seca,Pero de agua seco,0,80509,0 -20335,50327030,Rosele seca,Flor de jamaica seca,0,80509,0 -20336,50327031,Baya rowan seca,Serba seca,0,80509,0 -20337,50327032,Baya buckhom de mar seca,Espino amarillo seco,0,80509,0 -20338,50327033,Baya plata seca,Olivo de otoño seco,0,80509,0 -20339,50327034,Baya sorbete seco,Serba seca,0,80509,0 -20340,50327035,Guanabana seca,,0,80509,0 -20341,50327036,Manzana estrella seca,Cainito seco,0,80509,0 -20342,50327037,Tamarindo seco,,0,80509,0 -20343,50334500,Mangos orgánicos secos,,0,80509,0 -20344,50334501,Mango alphonso orgánico seco,,0,80509,0 -20345,50334502,Mango ataulfo orgánico seco,,0,80509,0 -20346,50334503,Mango criollo orgánico seco,,0,80509,0 -20347,50334504,Mango edwards orgánico seco,,0,80509,0 -20348,50334505,Mango francine orgánico seco,,0,80509,0 -20349,50334506,Mango francis orgánico seco,,0,80509,0 -20350,50334507,Mango gandaria orgánico seco,,0,80509,0 -20351,50334508,Mango haden orgánico seco,,0,80509,0 -20352,50334509,Mangos irwin orgánico seco,,0,80509,0 -20353,50334510,Mangos keitt orgánico seco,,0,80509,0 -20354,50334511,Mangos kent orgánico seco,,0,80509,0 -20355,50334512,Mangos kesar orgánico seco,,0,80509,0 -20356,50334513,Mangos kuini orgánico seco,,0,80509,0 -20357,50334514,Mangos manila super orgánico seco,,0,80509,0 -20358,50334515,Mangos manila orgánico seco,,0,80509,0 -20359,50334516,Mangos mayaguez orgánico seco,,0,80509,0 -20360,50334517,Mangos mulgoba orgánico seco,,0,80509,0 -20361,50334518,Mangos oro orgánico seco,,0,80509,0 -20362,50334519,Mangos palmer orgánico seco,,0,80509,0 -20363,50334520,Mango parvin orgánico seco,,0,80509,0 -20364,50334521,Mango sandersha orgánico seco,,0,80509,0 -20365,50334522,Mangos sensation orgánico seco,,0,80509,0 -20366,50334523,Mango smith orgánico seco,,0,80509,0 -20367,50334524,Mango tomy atkin orgánico seco,,0,80509,0 -20368,50334525,Mango van dyke orgánico seco,,0,80509,0 -20369,50335300,Duraznos orgánicos secos,,0,80509,0 -20370,50335301,Durazno escudo amber orgánico seco,,0,80509,0 -20371,50335302,Durazno nieve de abril orgánico seco,,0,80509,0 -20372,50335303,Durazno dama de agosto orgánico seco,,0,80509,0 -20373,50335304,Durazno llama de otoño orgánico seco,,0,80509,0 -20374,50335305,Durazno dama de otoño orgánico seco,,0,80509,0 -20375,50335306,Durazno babcock orgánico seco,,0,80509,0 -20376,50335307,Durazno brittany lane orgánico seco,,0,80509,0 -20377,50335308,Durazno cary mac orgánico seco,,0,80509,0 -20378,50335309,Durazno clásica orgánico seco,,0,80509,0 -20379,50335310,Durazno dulce del campo orgánico seco,,0,80509,0 -20380,50335311,Durazno escudo de cielo orgánico seco,,0,80509,0 -20381,50335312,Durazno dama crimson orgánico seco,,0,80509,0 -20382,50335313,Durazno príncipe corona orgánico seco,,0,80509,0 -20383,50335314,Durazno sol david orgánico seco,,0,80509,0 -20384,50335315,Durazno princesa diamante orgánico seco,,0,80509,0 -20385,50335316,Durazno earlrich orgánico seco,,0,80509,0 -20386,50335317,Durazno majestuosa temprana orgánico seco,,0,80509,0 -20387,50335318,Durazno early treat orgánico seco,,0,80509,0 -20388,50335319,Durazno dama elegante orgánico seco,,0,80509,0 -20389,50335320,Durazno emperatriz orgánico seco,,0,80509,0 -20390,50335321,Durazno encoré orgánico seco,,0,80509,0 -20391,50335322,Durazno dama elegante orgánico seco,,0,80509,0 -20392,50335323,Durazno príncipe de fuego orgánico seco,,0,80509,0 -20393,50335324,Durazno escudo de llama orgánico seco,,0,80509,0 -20394,50335325,Durazno tipo plano orgánico seco,,0,80509,0 -20395,50335326,Durazno escudo de sabor orgánico seco,,0,80509,0 -20396,50335327,Durazno príncipe florida orgánico seco,,0,80509,0 -20397,50335328,Durazno luna llena orgánico seco,,0,80509,0 -20398,50335329,Durazno harvester orgánico seco,,0,80509,0 -20399,50335330,Durazno princesa de hielo orgánico seco,,0,80509,0 -20400,50335331,Durazno princesa de marfil orgánico seco,,0,80509,0 -20401,50335332,Durazno princesa reina jersey orgánico seco,,0,80509,0 -20402,50335333,Durazno john henry orgánico seco,,0,80509,0 -20403,50335334,Durazno príncipe de junio orgánico seco,,0,80509,0 -20404,50335335,Durazno kaweah orgánico seco,,0,80509,0 -20405,50335336,Durazno klondike orgánico seco,,0,80509,0 -20406,50335337,Durazno lindo orgánico seco,,0,80509,0 -20407,50335338,Durazno loring orgánico seco,,0,80509,0 -20408,50335339,Durazno majestuoso orgánico seco,,0,80509,0 -20409,50335340,Durazno o’henry orgánico seco,,0,80509,0 -20410,50335341,Durazno escudo de reina orgánico seco,,0,80509,0 -20411,50335342,Durazno dama roja orgánico seco,,0,80509,0 -20412,50335343,Durazno globo rojo orgánico seco,,0,80509,0 -20413,50335344,Durazno cielo rojo orgánico seco,,0,80509,0 -20414,50335345,Durazno redtop orgánico seco,,0,80509,0 -20415,50335346,Durazno regina orgánico seco,,0,80509,0 -20416,50335347,Durazno dama rica orgánico seco,,0,80509,0 -20417,50335348,Durazno mayo rico orgánico seco,,0,80509,0 -20418,50335349,Durazno gloria royal orgánico seco,,0,80509,0 -20419,50335350,Durazno dama royal orgánico seco,,0,80509,0 -20420,50335351,Durazno nieve de septiembre orgánico seco,,0,80509,0 -20421,50335352,Durazno sol de septiembre orgánico seco,,0,80509,0 -20422,50335353,Durazno gema sierra orgánico seco,,0,80509,0 -20423,50335354,Durazno angel de nieve orgánico seco,,0,80509,0 -20424,50335355,Durazno gema de nieve orgánico seco,,0,80509,0 -20425,50335356,Durazno rey de nieve orgánico seco,,0,80509,0 -20426,50335357,Durazno dama de primavera orgánico seco,,0,80509,0 -20427,50335358,Durazno nieve de primavera orgánico seco,,0,80509,0 -20428,50335359,Durazno escudo de primavera orgánico seco,,0,80509,0 -20429,50335360,Durazno dulce gigante orgánico seco,,0,80509,0 -20430,50335361,Durazno dama de azúcar orgánico seco,,0,80509,0 -20431,50335362,Durazno brillo de sol orgánico seco,,0,80509,0 -20432,50335363,Durazno sunhigh orgánico seco,,0,80509,0 -20433,50335364,Durazno dama super orgánico seco,,0,80509,0 -20434,50335365,Durazno super rico orgánico seco,,0,80509,0 -20435,50335366,Durazno surecrop orgánico seco,,0,80509,0 -20436,50335367,Durazno sueño dulce orgánico seco,,0,80509,0 -20437,50335368,Durazno septiembre dulce orgánico seco,,0,80509,0 -20438,50335369,Durazno vista orgánico seco,,0,80509,0 -20439,50335370,Durazno dama blanca orgánico seco,,0,80509,0 -20440,50335371,Durazno dama zee orgánico seco,,0,80509,0 -20441,50337000,Frutas Nominant orgánicas secas,Frutas predominantes orgánicas secas,0,80509,0 -20442,50337001,Cupania orgánicas secas,Ackee orgánicas secas,0,80509,0 -20443,50337002,Babaco orgánico seco,Carica pentágona orgánica seca,0,80509,0 -20444,50337003,Banana flor orgánicas secas,Flor de plátano orgánica seca,0,80509,0 -20445,50337004,Baobab orgánicas secas,,0,80509,0 -20446,50337005,Naranja agria orgánicas secas,,0,80509,0 -20447,50337006,Canistel orgánicas secas,Zapote amarillo orgánico seco,0,80509,0 -20448,50337007,Baya nublosa orgánicas secas,Mora de los pantanos orgánicas secas,0,80509,0 -20449,50337008,Coco orgánico seco,,0,80509,0 -20450,50337009,Baya drew orgánicas secas,Baya de gota orgánicas secas,0,80509,0 -20451,50337010,Durian orgánicas secas,Durio orgánico seco,0,80509,0 -20452,50337011,Baya elder orgánicas secas,Baya de sauco orgánicas secas,0,80509,0 -20453,50337012,Feijoa orgánicas secas,Guayabo del Brasil orgánico seco,0,80509,0 -20454,50337013,Mora orgánicas secas,,0,80509,0 -20455,50337014,Cretaegus orgánicas secas,Majuelo orgánico seco,0,80509,0 -20456,50337015,Baya miel orgánicas secas,Madreselva azul orgánica seca,0,80509,0 -20457,50337016,Jaca orgánicas secas,Yaca orgánica seca,0,80509,0 -20458,50337017,Jambolan orgánicas secas,,0,80509,0 -20459,50337018,Jujube orgánicas secas,Azufaifa orgánica seca,0,80509,0 -20460,50337019,Liches orgánico secos secos,Lichi orgánico seco,0,80509,0 -20461,50337020,Mangostinos orgánico secos secos,Mangostán orgánico seco,0,80509,0 -20462,50337021,Medlar orgánicas secas,,0,80509,0 -20463,50337022,Mombines orgánicas secas,Hobo orgánico seco,0,80509,0 -20464,50337023,Monstera orgánicas secas,Cerimán orgánico seco,0,80509,0 -20465,50337024,Pepinos orgánico seco,Pepino dulce orgánico seco,0,80509,0 -20466,50337025,Platono orgánico seco,Plátano macho orgánico seco,0,80509,0 -20467,50337026,Peras en escabeche orgánicas secas,Tuna orgánica seca,0,80509,0 -20468,50337027,Mamonsillo orgánico seco,Guaya orgánica seca,0,80509,0 -20469,50337028,Rambután orgánico seco,Achotillo orgánico seco,0,80509,0 -20470,50337029,Manzana rosa orgánicas secas,Pero de agua orgánico seco,0,80509,0 -20471,50337030,Rosele orgánicas secas,Flor de jamaica orgánica seca,0,80509,0 -20472,50337031,Baya rowan orgánicas secas,Serba orgánica seca,0,80509,0 -20473,50337032,Baya buckhom de mar orgánicas secas,Espino amarillo orgánico seco,0,80509,0 -20474,50337033,Baya plata orgánicas secas,Olivo de otoño orgánico seco,0,80509,0 -20475,50337034,Baya sorbete orgánico seco,Serba orgánica seca,0,80509,0 -20476,50337035,Wanabana orgánicas secas,Guanábana orgánica seca,0,80509,0 -20477,50337036,Manzana estrella orgánicas secas,Cainito orgánico seco,0,80509,0 -20478,50337037,Tamarindo orgánico seco,,0,80509,0 -20479,50341700,Bananos congelados,Plátanos congelados,0,80509,0 -20480,50341701,Bananas manzana congeladas,Plátano apple congelado,0,80509,0 -20481,50341702,Bananas bebe congeladas,Plátano bebe congelado,0,80509,0 -20482,50341703,Bananas burro congeladas,Plátano burro congelado,0,80509,0 -20483,50341704,Bananas cavendish congeladas,Plátano cavendish congelado,0,80509,0 -20484,50341705,Bananas dominico congeladas,Plátano dominico congelado,0,80509,0 -20485,50341706,Bananas green congeladas,Plátano green congelado,0,80509,0 -20486,50341707,Bananas gros michel congeladas,Plátano gros michel congelado,0,80509,0 -20487,50341708,Bananas lacatan congeladas,Plátano lacatan congelado,0,80509,0 -20488,50341709,Bananas dedo de dama congeladas,Plátano dedo de dama congelado,0,80509,0 -20489,50341710,Bananas manzano congeladas,Plátano manzano congelado,0,80509,0 -20490,50341711,Bananas mysore congeladas,Plátano mysore congelado,0,80509,0 -20491,50341712,Bananas pisang mas congeladas,Plátano pisang mas congelado,0,80509,0 -20492,50341713,Bananas rojo congeladas,Plátano rojo congelado,0,80509,0 -20493,50341714,Bananas saba congeladas,Plátano saba congelado,0,80509,0 -20494,50341715,Bananas sucrier congeladas,Plátano sucrier congelado,0,80509,0 -20495,50342000,Moras congeladas,Zarzamoras congeladas,0,80509,0 -20496,50342001,Mora apache congeladas,Zarzamora apache congeladas,0,80509,0 -20497,50342002,Mora negro satín congeladas,Zarzamora negro satín congeladas,0,80509,0 -20498,50342003,Mora congeladas,Zarzamora congeladas,0,80509,0 -20499,50342004,Mora cherokee congeladas,Zarzamora cherokee congeladas,0,80509,0 -20500,50342005,Mora chester congeladas,Zarzamora chéster congeladas,0,80509,0 -20501,50342006,Mora dirksen congeladas,Zarzamora dirksen congeladas,0,80509,0 -20502,50342007,Bayas josta congeladas,Zarzamora josta congeladas,0,80509,0 -20503,50342008,Bayas logan congeladas,Zarzamora logan congeladas,0,80509,0 -20504,50342009,Bayas marion congeladas,Zarzamora marion congeladas,0,80509,0 -20505,50342010,Mora navaho congeladas,Zarzamora navaho congeladas,0,80509,0 -20506,50342011,Bayanectar congeladas,Zarzamora néctar congeladas,0,80509,0 -20507,50342012,Mora olallie congeladas,Zarzamora olallie congeladas,0,80509,0 -20508,50342013,Bayatay congeladas,Zarzamora tay congeladas,0,80509,0 -20509,50342014,Mora thomless hull congeladas,Zarzamora thomless hull congeladas,0,80509,0 -20510,50342015,Bayayoung congeladas,Zarzamora young congeladas,0,80509,0 -20511,50343700,Guayabas congeladas,,0,80509,0 -20512,50343701,Guayaba beaumont congeladas,,0,80509,0 -20513,50343702,Guayaba carrley congeladas,,0,80509,0 -20514,50343703,Guayaba lucida congeladas,,0,80509,0 -20515,50343704,Guayaba piña congeladas,,0,80509,0 -20516,50343800,Arándanos Huckleberry congelados,Arándanos azules congelados,0,80509,0 -20517,50343801,Arándano invierno negro congelado,Arándano azul invierno negro congelado,0,80509,0 -20518,50343802,Arándano cascada congelado,Arándano azul cascada congelado,0,80509,0 -20519,50343803,Arándano enano congelado,Arándano azul enano congelado,0,80509,0 -20520,50343804,Arándano montaña congelado,Arándano azul montaña congelado,0,80509,0 -20521,50343805,Arándano rojo congelado,Arándano azul red congelado,0,80509,0 -20522,50344500,Mangos congelados,,0,80509,0 -20523,50344501,Mango alphonso congelado,,0,80509,0 -20524,50344502,Mango ataulfo congelado,,0,80509,0 -20525,50344503,Mango criollo congelado,,0,80509,0 -20526,50344504,Mango edwards congelado,,0,80509,0 -20527,50344505,Mango francine congelado,,0,80509,0 -20528,50344506,Mango francis congelado,,0,80509,0 -20529,50344507,Mango gandaria congelado,,0,80509,0 -20530,50344508,Mango haden congelado,,0,80509,0 -20531,50344509,Mangos irwin congelado,,0,80509,0 -20532,50344510,Mangos keitt congelado,,0,80509,0 -20533,50344511,Mangos kent congelado,,0,80509,0 -20534,50344512,Mangos kesar congelado,,0,80509,0 -20535,50344513,Mangos kuini congelado,,0,80509,0 -20536,50344514,Mangos manila super congelado,,0,80509,0 -20537,50344515,Mangos manila congelado,,0,80509,0 -20538,50344516,Mangos mayaguez congelado,,0,80509,0 -20539,50344517,Mangos mulgoba congelado,,0,80509,0 -20540,50344518,Mangos oro congelado,,0,80509,0 -20541,50344519,Mangos palmer congelado,,0,80509,0 -20542,50344520,Mango parvin congelado,,0,80509,0 -20543,50344521,Mango sandersha congelado,,0,80509,0 -20544,50344522,Mangos sensation congelado,,0,80509,0 -20545,50344523,Mango smith congelado,,0,80509,0 -20546,50344524,Mango tomy atkin congelado,,0,80509,0 -20547,50344525,Mango van dyke congelado,,0,80509,0 -20548,50344600,Melones congelados,,0,80509,0 -20549,50344601,Melon allsweet congelado,,0,80509,0 -20550,50344602,Melón athens congelado,,0,80509,0 -20551,50344603,Melón diamante negro congelado,,0,80509,0 -20552,50344604,Melón cal sweet congelado,,0,80509,0 -20553,50344605,Melons cantaloupe congelado,,0,80509,0 -20554,50344606,Melón carnical congelado,,0,80509,0 -20555,50344607,Melón casaba congelado,,0,80509,0 -20556,50344608,Melón cavaillon congelado,,0,80509,0 -20557,50344609,Melón charentais congelado,,0,80509,0 -20558,50344610,Sandia charleston gray congelado,,0,80509,0 -20559,50344611,Melón crenshaw congelado,,0,80509,0 -20560,50344612,Melón crimson sweet congelado,,0,80509,0 -20561,50344613,Melón dixie lee congelado,,0,80509,0 -20562,50344614,Melón eclipse congelado,,0,80509,0 -20563,50344615,Melón ein dór congelado,,0,80509,0 -20564,50344616,Melón fiesta congelado,,0,80509,0 -20565,50344617,Melón galia congelado,,0,80509,0 -20566,50344618,Melón gaya congelado,,0,80509,0 -20567,50344619,Melón hami congelado,,0,80509,0 -20568,50344620,Melón miel dew congelado,,0,80509,0 -20569,50344621,Melón hielo congelado,,0,80509,0 -20570,50344622,Melón ida orgullo congelado,,0,80509,0 -20571,50344623,Melón juan canary congelado,,0,80509,0 -20572,50344624,Melón jubileo congelado,,0,80509,0 -20573,50344625,Melón jubilación congelado,,0,80509,0 -20574,50344626,Melón kaki / kakri congelado,,0,80509,0 -20575,50344627,Melón kiwano congelado,,0,80509,0 -20576,50344628,Melon koreano congelado,Melón coreano congelado,0,80509,0 -20577,50344629,Melón long grey congelado,Melón largo gris congelado,0,80509,0 -20578,50344630,Melón mayan congelado,,0,80509,0 -20579,50344631,Melón micky lee congelado,,0,80509,0 -20580,50344632,Melón mirage congelado,,0,80509,0 -20581,50344633,Sandia luna y estrellas congelado,,0,80509,0 -20582,50344634,Melón ogen congelado,,0,80509,0 -20583,50344635,Melón patriot congelado,,0,80509,0 -20584,50344636,Melón pavo real congelado,,0,80509,0 -20585,50344637,Melón pepino congelado,,0,80509,0 -20586,50344638,Melón persian congelado,,0,80509,0 -20587,50344639,Melón picnic congelado,,0,80509,0 -20588,50344640,Melón piel de sapo congelado,,0,80509,0 -20589,50344641,Melón piña congelado,,0,80509,0 -20590,50344642,Melón quetzali congelado,,0,80509,0 -20591,50344643,Melón goblin rojo congelado,,0,80509,0 -20592,50344644,Melón regency congelado,,0,80509,0 -20593,50344645,Melón royal mejestic congelado,,0,80509,0 -20594,50344646,Melón royal star congelado,,0,80509,0 -20595,50344647,Melón dulce royal congelado,,0,80509,0 -20596,50344648,Malon santa claus congelado,"Melón piel de sapo congelado, Melón santa claus congelado",0,80509,0 -20597,50344649,Melón sharlyn congelado,,0,80509,0 -20598,50344650,Melón español congelado,,0,80509,0 -20599,50344651,Melón sprite congelado,,0,80509,0 -20600,50344652,Melón starbright congelado,,0,80509,0 -20601,50344653,Melón stars and stripes congelado,,0,80509,0 -20602,50344654,Melón bebe de azúcar congelado,,0,80509,0 -20603,50344655,Sandia bebe de azúcar congeladas,,0,80509,0 -20604,50344656,Melón dulce sol congelado,,0,80509,0 -20605,50344657,Sandia corazón dulce sin semilla congeladas,,0,80509,0 -20606,50344658,Melón tentación congelado,,0,80509,0 -20607,50344659,Melón tigre bebe congelado,,0,80509,0 -20608,50344660,Melón tuscan tipo congelado,,0,80509,0 -20609,50344661,Sandia bebe amarillo congeladas,,0,80509,0 -20610,50344700,Moras mulberry congeladas,Zarzamoras mulberry congeladas,0,80509,0 -20611,50344701,Mora negra congeladas,Zarzamora negra congeladas,0,80509,0 -20612,50344702,Mora blanca congeladas,Zarzamora blanca congeladas,0,80509,0 -20613,50345100,Papayas congeladas,,0,80509,0 -20614,50345101,Papaya verde cocinar congeladas,,0,80509,0 -20615,50345102,Papaya maradol congeladas,,0,80509,0 -20616,50345103,Papaya amarillo mexicano congeladas,,0,80509,0 -20617,50345104,Papaya montaña congeladas,,0,80509,0 -20618,50345105,Papaya solo congeladas,,0,80509,0 -20619,50345106,Papaya tainung congeladas,,0,80509,0 -20620,50345300,Duraznos congelados,,0,80509,0 -20621,50345301,Durazno escudo amber congelado,,0,80509,0 -20622,50345302,Durazno nieve de abril congelado,,0,80509,0 -20623,50345303,Durazno dama de agosto congelado,,0,80509,0 -20624,50345304,Durazno llama de otoño congelado,,0,80509,0 -20625,50345305,Durazno dama de otoño congelado,,0,80509,0 -20626,50345306,Durazno babcock congelado,,0,80509,0 -20627,50345307,Durazno brittany lane congelado,,0,80509,0 -20628,50345308,Durazno cary mac congelado,,0,80509,0 -20629,50345309,Durazno clásica congelado,,0,80509,0 -20630,50345310,Durazno dulce del campo congelado,,0,80509,0 -20631,50345311,Durazno escudo de cielo congelado,,0,80509,0 -20632,50345312,Durazno dama crimson congelado,,0,80509,0 -20633,50345313,Durazno príncipe corona congelado,,0,80509,0 -20634,50345314,Durazno sol david congelado,,0,80509,0 -20635,50345315,Durazno princesa diamante congelado,,0,80509,0 -20636,50345316,Durazno earlrich congelado,,0,80509,0 -20637,50345317,Durazno majestuosa temprana congelado,,0,80509,0 -20638,50345318,Durazno early treat congelado,,0,80509,0 -20639,50345319,Durazno dama elegante congelado,,0,80509,0 -20640,50345320,Durazno emperatriz congelado,,0,80509,0 -20641,50345321,Durazno encoré congelado,,0,80509,0 -20642,50345322,Durazno dama elegante congelado,,0,80509,0 -20643,50345323,Durazno príncipe de fuego congelado,,0,80509,0 -20644,50345324,Durazno escudo de llama congelado,,0,80509,0 -20645,50345325,Durazno tipo plano congelado,,0,80509,0 -20646,50345326,Durazno escudo de sabor congelado,,0,80509,0 -20647,50345327,Durazno príncipe florida congelado,,0,80509,0 -20648,50345328,Durazno luna llena congelado,,0,80509,0 -20649,50345329,Durazno harvester congelado,,0,80509,0 -20650,50345330,Durazno princesa de hielo congelado,,0,80509,0 -20651,50345331,Durazno princesa de marfil congelado,,0,80509,0 -20652,50345332,Durazno princesa reina jersey congelado,,0,80509,0 -20653,50345333,Durazno john henry congelado,,0,80509,0 -20654,50345334,Durazno príncipe de junio congelado,,0,80509,0 -20655,50345335,Durazno kaweah congelado,,0,80509,0 -20656,50345336,Durazno klondike congelado,,0,80509,0 -20657,50345337,Durazno lindo congelado,,0,80509,0 -20658,50345338,Durazno loring congelado,,0,80509,0 -20659,50345339,Durazno majestuoso congelado,,0,80509,0 -20660,50345340,Durazno o’henry congelado,,0,80509,0 -20661,50345341,Durazno escudo de reina congelado,,0,80509,0 -20662,50345342,Durazno dama roja congelado,,0,80509,0 -20663,50345343,Durazno globo rojo congelado,,0,80509,0 -20664,50345344,Durazno cielo rojo congelado,,0,80509,0 -20665,50345345,Durazno redtop congelado,,0,80509,0 -20666,50345346,Durazno regina congelado,,0,80509,0 -20667,50345347,Durazno dama rica congelado,,0,80509,0 -20668,50345348,Durazno mayo rico vo,,0,80509,0 -20669,50345349,Durazno gloria royal congelado,,0,80509,0 -20670,50345350,Durazno dama royal congelado,,0,80509,0 -20671,50345351,Durazno nieve de septiembre congelado,,0,80509,0 -20672,50345352,Durazno sol de septiembre congelado,,0,80509,0 -20673,50345353,Durazno gema sierra congelado,,0,80509,0 -20674,50345354,Durazno angel de nieve congelado,,0,80509,0 -20675,50345355,Durazno gema de nieve congelado,,0,80509,0 -20676,50345356,Durazno rey de nieve congelado,,0,80509,0 -20677,50345357,Durazno dama de primavera congelado,,0,80509,0 -20678,50345358,Durazno nieve de primavera congelado,,0,80509,0 -20679,50345359,Durazno escudo de primavera congelado,,0,80509,0 -20680,50345360,Durazno dulce gigante congelado,,0,80509,0 -20681,50345361,Durazno dama de azúcar congelado,,0,80509,0 -20682,50345362,Durazno brillo de sol congelado,,0,80509,0 -20683,50345363,Durazno sunhigh congelado,,0,80509,0 -20684,50345364,Durazno dama super congelado,,0,80509,0 -20685,50345365,Durazno super rico congelado,,0,80509,0 -20686,50345366,Durazno surecrop congelado,,0,80509,0 -20687,50345367,Durazno sueño dulce congelado,,0,80509,0 -20688,50345368,Durazno septiembre dulce congelado,,0,80509,0 -20689,50345369,Durazno vista congelado,,0,80509,0 -20690,50345370,Durazno dama blanca congelado,,0,80509,0 -20691,50345371,Durazno dama zee congelado,,0,80509,0 -20692,50345600,Piñas congeladas,,0,80509,0 -20693,50345601,Piña chirimoya congeladas,Atemoya congelada,0,80509,0 -20694,50345602,Piña dorada congeladas,,0,80509,0 -20695,50345603,Piña hilo congeladas,,0,80509,0 -20696,50345604,Piña kona sugarloaf congeladas,,0,80509,0 -20697,50345605,Piña reina natal congeladas,,0,80509,0 -20698,50345606,Piña pernabuco congeladas,,0,80509,0 -20699,50345607,Piña español rojo congeladas,,0,80509,0 -20700,50345608,Piña cayen suave congeladas,,0,80509,0 -20701,50345609,Piña sugarloaf congeladas,,0,80509,0 -20702,50345610,Piña variegated congeladas,,0,80509,0 -20703,50346200,Frambuesas congeladas,,0,80509,0 -20704,50346201,Frambuesa americana roja congeladas,,0,80509,0 -20705,50346202,Frambuesa bailey queensland congeladas,,0,80509,0 -20706,50346203,Frambuesa negra congeladas,,0,80509,0 -20707,50346204,Frambuesa oscura congeladas,,0,80509,0 -20708,50346205,Frambuesa deliciosa congeladas,,0,80509,0 -20709,50346206,Frambuesa enano focke congeladas,,0,80509,0 -20710,50346207,Frambuesa focke hojagris congeladas,,0,80509,0 -20711,50346208,Frambuesa focke fresa congeladas,,0,80509,0 -20712,50346209,Frambuesa focke amarillo himalaya congeladas,,0,80509,0 -20713,50346210,Frambuesa dorado congelado,,0,80509,0 -20714,50346211,Frambuesa gris nuevo mexico congeladas,,0,80509,0 -20715,50346212,Frambuesa jepson blancobark congeladas,,0,80509,0 -20716,50346213,Frambuesa kellogs san diego congeladas,,0,80509,0 -20717,50346214,Frambuesa leucodemis blancobark congeladas,,0,80509,0 -20718,50346215,Frambuesa munz cuyamaca congeladas,,0,80509,0 -20719,50346216,Frambuesa peck bartons congeladas,,0,80509,0 -20720,50346217,Frambuesa flor morada congeladas,,0,80509,0 -20721,50346218,Frambuesa roadside congeladas,,0,80509,0 -20722,50346219,Frambuesa san diego congeladas,,0,80509,0 -20723,50346220,Frambuesa nieve congeladas,,0,80509,0 -20724,50346221,Frambuesa pico de nieve congeladas,,0,80509,0 -20725,50346222,Frambuesa hoja de fresa congeladas,,0,80509,0 -20726,50346223,Frambuesa cultivo dulce congeladas,,0,80509,0 -20727,50346224,Frambuesa tor y gris blancobark congeladas,,0,80509,0 -20728,50346225,Frambuesa caribe congeladas,,0,80509,0 -20729,50346226,Frambuesa blancobark congeladas,,0,80509,0 -20730,50346227,Frambuesa vino congeladas,,0,80509,0 -20731,50346228,Frambuesa himalaya amarillo congeladas,,0,80509,0 -20732,50346229,Frambuesa yu-shan congeladas,,0,80509,0 -20733,50346700,Fresas congeladas,,0,80509,0 -20734,50346701,Fresa chandler congeladas,,0,80509,0 -20735,50346702,Fresa rumbo junio congeladas,Fresa temporada de junio congelada,0,80509,0 -20736,50346703,Fresa rumbo siempre congeladas,Fresa de todo el año congelada,0,80509,0 -20737,50351700,Bananos orgánicos congelados,Plátanos orgánicos congelados,0,80509,0 -20738,50351701,Bananas manzana congelada orgánica,Plátano apple congelado orgánico,0,80509,0 -20739,50351702,Bananas bebe congelada orgánica,Plátano bebe congelado orgánico,0,80509,0 -20740,50351703,Bananas burro congelada orgánica,Plátano burro congelado orgánico,0,80509,0 -20741,50351704,Bananas cavendish congelada orgánica,Plátano cavendish congelado orgánico,0,80509,0 -20742,50351705,Bananas dominico congelada orgánica,Plátano dominico congelado orgánico,0,80509,0 -20743,50351706,Bananas verde congelada orgánica,Plátano verde congelado orgánico,0,80509,0 -20744,50351707,Bananas gros michel congelada orgánica,Plátano gros michel congelado orgánico,0,80509,0 -20745,50351708,Bananas lacatan congelada orgánica,Plátano lacatan congelado orgánico,0,80509,0 -20746,50351709,Bananas dedo de dama congelada orgánica,Plátano dedo de dama congelado orgánico,0,80509,0 -20747,50351710,Bananas manzano congelada orgánica,Plátano manzano congelado orgánico,0,80509,0 -20748,50351711,Bananas mysore congelada orgánica,Plátano mysore congelado orgánico,0,80509,0 -20749,50351712,Bananas pisang mas congelada orgánica,Plátano pisang mas congelado orgánico,0,80509,0 -20750,50351713,Bananas roja congelada orgánica,Plátano roja congelado orgánico,0,80509,0 -20751,50351714,Bananas saba congelada orgánica,Plátano saba congelado orgánico,0,80509,0 -20752,50351715,Bananas sucrier congelada orgánica,Plátano sucrier congelado orgánico,0,80509,0 -20753,50354500,Mangos orgánicos congelados,,0,80509,0 -20754,50354501,Mango alphonso congelado orgánico,,0,80509,0 -20755,50354502,Mango ataulfo congelado orgánico,,0,80509,0 -20756,50354503,Mango criollo congelado orgánico,,0,80509,0 -20757,50354504,Mango edwards congelado orgánico,,0,80509,0 -20758,50354505,Mango francine congelado orgánico,,0,80509,0 -20759,50354506,Mango francis congelado orgánico,,0,80509,0 -20760,50354507,Mango gandaria congelado orgánico,,0,80509,0 -20761,50354508,Mango haden congelado orgánico,,0,80509,0 -20762,50354509,Mangos irwin congelado orgánico,,0,80509,0 -20763,50354510,Mangos keitt congelado orgánico,,0,80509,0 -20764,50354511,Mangos kent congelado orgánico,,0,80509,0 -20765,50354512,Mangos kesar congelado orgánico,,0,80509,0 -20766,50354513,Mangos kuini congelado orgánico,,0,80509,0 -20767,50354514,Mangos manila super congelado orgánico,,0,80509,0 -20768,50354515,Mangos manila congelado orgánico,,0,80509,0 -20769,50354516,Mangos mayaguez congelado orgánico,,0,80509,0 -20770,50354517,Mangos mulgoba congelado orgánico,,0,80509,0 -20771,50354518,Mangos oro congelado orgánico,,0,80509,0 -20772,50354519,Mangos palmer congelado orgánico,,0,80509,0 -20773,50354520,Mango parvin congelado orgánico,,0,80509,0 -20774,50354521,Mango sandersha congelado orgánico,,0,80509,0 -20775,50354522,Mangos sensation congelado orgánico,,0,80509,0 -20776,50354523,Mango smith congelado orgánico,,0,80509,0 -20777,50354524,Mango tomy atkin congelado orgánico,,0,80509,0 -20778,50354525,Mango van dyke congelado orgánico,,0,80509,0 -20779,50356700,Fresas orgánicas congeladas,,0,80509,0 -20780,50356701,Fresa chandler congelada orgánica,,0,80509,0 -20781,50356702,Fresa rumbo junio congelada orgánica,Fresa temporada de junio congelada orgánica,0,80509,0 -20782,50356703,Fresa rumbo siempre congelada orgánica,Fresa de todo el año congelada orgánica,0,80509,0 -20783,50361600,Albaricoques en lata o en frasco,Chabacanos en lata o en frasco,0,80509,0 -20784,50361601,Abaricoques ambercot en lata o en frasco,Chabacano ambercot en lata o en frasco,0,80509,0 -20785,50361602,Albaricoques apache en lata o en frasco,Chabacano apache en lata o en frasco,0,80509,0 -20786,50361603,Albaricoques brittany dorado en lata o en frasco,Chabacano brittany dorado en lata o en frasco,0,80509,0 -20787,50361604,Albaricoque black en lata o en frasco,Chabacano black en lata o en frasco,0,80509,0 -20788,50361605,Albaricoque blenheim en lata o en frasco,Chabacano blenheim en lata o en frasco,0,80509,0 -20789,50361606,Albaricoque bonny en lata o en frasco,Chabacano bonny en lata o en frasco,0,80509,0 -20790,50361607,Albaricoque biluda en lata o en frasco,Chabacano biluda en lata o en frasco,0,80509,0 -20791,50361608,Albaricoque castlebrite en lata o en frasco,Chabacano castlebrite en lata o en frasco,0,80509,0 -20792,50361609,Albaricoque clutha gold en lata o en frasco,Chabacano clutha gold en lata o en frasco,0,80509,0 -20793,50361610,Albaricoque clutha sun en lata o en frasco,Chabacano clutha sun en lata o en frasco,0,80509,0 -20794,50361611,Albaricoque derby royal en lata o en frasco,Chabacano derby royal en lata o en frasco,0,80509,0 -20795,50361612,Albaricoque dina en lata o en frasco,Chabacano dina en lata o en frasco,0,80509,0 -20796,50361613,Albaricoque earlicot en lata o en frasco,Chabacano earlicot en lata o en frasco,0,80509,0 -20797,50361614,Albaricoque earliman en lata o en frasco,Chabacano earliman en lata o en frasco,0,80509,0 -20798,50361615,Albaricoque early bright en lata o en frasco,Chabacano early bright en lata o en frasco,0,80509,0 -20799,50361616,Albaricoque dorado en llamas en lata o en frasco,Chabacano dorado en llamas en lata o en frasco,0,80509,0 -20800,50361617,Albaricoque frenso en lata o en frasco,Chabacano frenso en lata o en frasco,0,80509,0 -20801,50361618,Albaricoque dorado claro en lata o en frasco,Chabacano dorado claro en lata o en frasco,0,80509,0 -20802,50361619,Albaricoque barra dorada en lata o en frasco,Chabacano barra dorada en lata o en frasco,0,80509,0 -20803,50361620,Albaricoque dorada dulce en lata o en frasco,Chabacano dorada dulce en lata o en frasco,0,80509,0 -20804,50361621,Albaricoque dorada rica en lata o en frasco,Chabacano dorada rica en lata o en frasco,0,80509,0 -20805,50361622,Albaricoque helena en lata o en frasco,Chabacano helena en lata o en frasco,0,80509,0 -20806,50361623,Albaricoque honeycot en lata o en frasco,Chabacano en lata o en frasco,0,80509,0 -20807,50361624,Albaricoque imperial en lata o en frasco,Chabacano imperial en lata o en frasco,0,80509,0 -20808,50361625,Albaricoque jordanne en lata o en frasco,Chabacano jordanne en lata o en frasco,0,80509,0 -20809,50361626,Albaricoque jumbo cot en lata o en frasco,Chabacano jumbo cot en lata o en frasco,0,80509,0 -20810,50361627,Albaricoque kandy kot en lata o en frasco,Chabacano kandy kot en lata o en frasco,0,80509,0 -20811,50361628,Albaricoque katy en lata o en frasco,Chabacano katy en lata o en frasco,0,80509,0 -20812,50361629,Albaricoque rey en lata o en frasco,Chabacano rey en lata o en frasco,0,80509,0 -20813,50361630,Albaricoque lambertin en lata o en frasco,Chabacano lambertin en lata o en frasco,0,80509,0 -20814,50361631,Albaricoque loma en lata o en frasco,Chabacano loma en lata o en frasco,0,80509,0 -20815,50361632,Albaricoque lulu belle en lata o en frasco,Chabacano lulu belle en lata o en frasco,0,80509,0 -20816,50361633,Albaricoque modesto en lata o en frasco,Chabacano modesto en lata o en frasco,0,80509,0 -20817,50361634,Albaricoque moorpark en lata o en frasco,Chabacano moorpark en lata o en frasco,0,80509,0 -20818,50361635,Albaricoque orangered en lata o en frasco,Chabacano orange red en lata o en frasco,0,80509,0 -20819,50361636,Albaricoque palstein en lata o en frasco,Chabacano palstein en lata o en frasco,0,80509,0 -20820,50361637,Albaricoque patterson en lata o en frasco,Chabacano patterson en lata o en frasco,0,80509,0 -20821,50361638,Albaricoque perfection en lata o en frasco,Chabacano perfection en lata o en frasco,0,80509,0 -20822,50361639,Albaricoque poppy en lata o en frasco,Chabacano poppy en lata o en frasco,0,80509,0 -20823,50361640,Albaricoque poppycot en lata o en frasco,Chabacano poppy cot en lata o en frasco,0,80509,0 -20824,50361641,Albaricoque reina en lata o en frasco,Chabacano reina en lata o en frasco,0,80509,0 -20825,50361642,Albaricoque riland en lata o en frasco,Chabacano riland en lata o en frasco,0,80509,0 -20826,50361643,Albaricoque rival en lata o en frasco,Chabacano rival en lata o en frasco,0,80509,0 -20827,50361644,Albaricoque robada en lata o en frasco,Chabacano robada en lata o en frasco,0,80509,0 -20828,50361645,Albaricoque royal en lata o en frasco,Chabacano royal en lata o en frasco,0,80509,0 -20829,50361646,Albaricoque royal blenheim en lata o en frasco,Chabacano royal blenheim en lata o en frasco,0,80509,0 -20830,50361647,Albaricoque royal orange en lata o en frasco,Chabacano royal orange en lata o en frasco,0,80509,0 -20831,50361648,Albaricoque gota de sol en lata o en frasco,Chabacano gota de sol en lata o en frasco,0,80509,0 -20832,50361649,Albaricoque tilton en lata o en frasco,Chabacano tilton en lata o en frasco,0,80509,0 -20833,50361650,Albaricoque tomcot en lata o en frasco,Chabacano tomcot en lata o en frasco,0,80509,0 -20834,50361651,Albaricoque tracy en lata o en frasco,Chabacano tracy en lata o en frasco,0,80509,0 -20835,50361652,Albaricoque tri gem en lata o en frasco,Chabacano tri gem en lata o en frasco,0,80509,0 -20836,50361653,Albaricoque valle dorado en lata o en frasco,Chabacano valle dorado en lata o en frasco,0,80509,0 -20837,50361654,Albaricoque westley en lata o en frasco,Chabacano westley en lata o en frasco,0,80509,0 -20838,50361655,Albaricoque york en lata o en frasco,Chabacano york en lata o en frasco,0,80509,0 -20839,50362500,Cerezas en lata o en frasco,,0,80509,0 -20840,50362501,Cereza amarelle en lata o en frasco,,0,80509,0 -20841,50362502,Cereza brooks en lata o en frasco,,0,80509,0 -20842,50362503,Cereza bigarreu en lata o en frasco,,0,80509,0 -20843,50362504,Cereza bing en lata o en frasco,,0,80509,0 -20844,50362505,Cereza blach republic en lata o en frasco,,0,80509,0 -20845,50362506,Cereza negra schmidt en lata o en frasco,,0,80509,0 -20846,50362507,Cereza blacktartarian en lata o en frasco,,0,80509,0 -20847,50362508,Cereza fiesta bing en lata o en frasco,,0,80509,0 -20848,50362509,Cereza garnet en lata o en frasco,,0,80509,0 -20849,50362510,Cereza king en lata o en frasco,,0,80509,0 -20850,50362511,Cereza champagne en lata o en frasco,,0,80509,0 -20851,50362512,Cereza lapin en lata o en frasco,,0,80509,0 -20852,50362513,Cereza larian en lata o en frasco,,0,80509,0 -20853,50362514,Cereza dark guines en lata o en frasco,,0,80509,0 -20854,50362515,Cereza montmorency en lata o en frasco,,0,80509,0 -20855,50362516,Cereza duque en lata o en frasco,,0,80509,0 -20856,50362517,Cereza early rivers en lata o en frasco,,0,80509,0 -20857,50362518,Cereza ruby bing en lata o en frasco,,0,80509,0 -20858,50362519,Cereza santine en lata o en frasco,,0,80509,0 -20859,50362520,Cereza geans/guines en lata o en frasco,,0,80509,0 -20860,50362521,Cereza sonata en lata o en frasco,,0,80509,0 -20861,50362522,Cereza lambert en lata o en frasco,,0,80509,0 -20862,50362523,Cereza stella en lata o en frasco,,0,80509,0 -20863,50362524,Cereza sweethart en lata o en frasco,,0,80509,0 -20864,50362525,Cereza tartarian en lata o en frasco,,0,80509,0 -20865,50362527,Cereza maraschino en lata o en frasco,,0,80509,0 -20866,50362528,Cereza van en lata o en frasco,,0,80509,0 -20867,50362529,Cereza morello en lata o en frasco,,0,80509,0 -20868,50362530,Cereza royal ann en lata o en frasco,,0,80509,0 -20869,50362531,Cereza ranier en lata o en frasco,,0,80509,0 -20870,50362532,Cereza royal en lata o en frasco,,0,80509,0 -20871,50363700,Guayabas en lata o en frasco,,0,80509,0 -20872,50363701,Guayabas beaumont en lata o en frasco,,0,80509,0 -20873,50363702,Guayabas carrley en lata o en frasco,,0,80509,0 -20874,50363703,Guayabas lucida en lata o en frasco,,0,80509,0 -20875,50363704,Guayaba piña en lata o en frasco,,0,80509,0 -20876,50364500,Mangos en lata o en frasco,,0,80509,0 -20877,50364501,Mango alphonso en lata o en frasco,,0,80509,0 -20878,50364502,Mango ataulfo en lata o en frasco,,0,80509,0 -20879,50364503,Mango criollo en lata o en frasco,,0,80509,0 -20880,50364504,Mango edwards en lata o en frasco,,0,80509,0 -20881,50364505,Mango francine en lata o en frasco,,0,80509,0 -20882,50364506,Mango francis en lata o en frasco,,0,80509,0 -20883,50364507,Mango gandaria en lata o en frasco,,0,80509,0 -20884,50364508,Mango haden en lata o en frasco,,0,80509,0 -20885,50364509,Mangos irwin en lata o en frasco,,0,80509,0 -20886,50364510,Mangos keitt en lata o en frasco,,0,80509,0 -20887,50364511,Mangos kent en lata o en frasco,,0,80509,0 -20888,50364512,Mangos kesar en lata o en frasco,,0,80509,0 -20889,50364513,Mangos kuini en lata o en frasco,,0,80509,0 -20890,50364514,Mangos manila super en lata o en frasco,,0,80509,0 -20891,50364515,Mangos manila en lata o en frasco,,0,80509,0 -20892,50364516,Mangos mayaguez en lata o en frasco,,0,80509,0 -20893,50364517,Mangos mulgoba en lata o en frasco,,0,80509,0 -20894,50364518,Mangos oro en lata o en frasco,,0,80509,0 -20895,50364519,Mangos palmer en lata o en frasco,,0,80509,0 -20896,50364520,Mango parvin en lata o en frasco,,0,80509,0 -20897,50364521,Mango sandersha en lata o en frasco,,0,80509,0 -20898,50364522,Mangos sensation en lata o en frasco,,0,80509,0 -20899,50364523,Mango smith en lata o en frasco,,0,80509,0 -20900,50364524,Mango tomy atkin en lata o en frasco,,0,80509,0 -20901,50364525,Mango van dyke en lata o en frasco,,0,80509,0 -20902,50365300,Duraznos en lata o en frasco,,0,80509,0 -20903,50365301,Durazno escudo amber en lata o en frasco,,0,80509,0 -20904,50365302,Durazno nieve de abril en lata o en frasco,,0,80509,0 -20905,50365303,Durazno dama de agosto en lata o en frasco,,0,80509,0 -20906,50365304,Durazno llama de otoño en lata o en frasco,,0,80509,0 -20907,50365305,Durazno dama de otoño en lata o en frasco,,0,80509,0 -20908,50365306,Durazno babcock en lata o en frasco,,0,80509,0 -20909,50365307,Durazno brittany lane en lata o en frasco,,0,80509,0 -20910,50365308,Durazno cary mac en lata o en frasco,,0,80509,0 -20911,50365309,Durazno clásica en lata o en frasco,,0,80509,0 -20912,50365310,Durazno dulce del campo en lata o en frasco,,0,80509,0 -20913,50365311,Durazno escudo de cielo en lata o en frasco,,0,80509,0 -20914,50365312,Durazno dama crimson en lata o en frasco,,0,80509,0 -20915,50365313,Durazno príncipe corona en lata o en frasco,,0,80509,0 -20916,50365314,Durazno sol david en lata o en frasco,,0,80509,0 -20917,50365315,Durazno princesa diamante en lata o en frasco,,0,80509,0 -20918,50365316,Durazno earlrich en lata o en frasco,,0,80509,0 -20919,50365317,Durazno majestuosa temprana en lata o en frasco,,0,80509,0 -20920,50365318,Durazno early treat en lata o en frasco,,0,80509,0 -20921,50365319,Durazno dama elegante en lata o en frasco,,0,80509,0 -20922,50365320,Durazno emperatriz en lata o en frasco,,0,80509,0 -20923,50365321,Durazno encoré en lata o en frasco,,0,80509,0 -20924,50365322,Durazno dama elegante en lata o en frasco,,0,80509,0 -20925,50365323,Durazno príncipe de fuego en lata o en frasco,,0,80509,0 -20926,50365324,Durazno escudo de llama en lata o en frasco,,0,80509,0 -20927,50365325,Durazno tipo plano en lata o en frasco,,0,80509,0 -20928,50365326,Durazno escudo de sabor en lata o en frasco,,0,80509,0 -20929,50365327,Durazno príncipe florida en lata o en frasco,,0,80509,0 -20930,50365328,Durazno luna llena en lata o en frasco,,0,80509,0 -20931,50365329,Durazno harvester en lata o en frasco,,0,80509,0 -20932,50365330,Durazno princesa de hielo en lata o en frasco,,0,80509,0 -20933,50365331,Durazno princesa de marfil en lata o en frasco,,0,80509,0 -20934,50365332,Durazno princesa reina jersey en lata o en frasco,,0,80509,0 -20935,50365333,Durazno john henry en lata o en frasco,,0,80509,0 -20936,50365334,Durazno príncipe de junio en lata o en frasco,,0,80509,0 -20937,50365335,Durazno kaweah en lata o en frasco,,0,80509,0 -20938,50365336,Durazno klondike en lata o en frasco,,0,80509,0 -20939,50365337,Durazno lindo en lata o en frasco,,0,80509,0 -20940,50365338,Durazno loring en lata o en frasco,,0,80509,0 -20941,50365339,Durazno majestuoso en lata o en frasco,,0,80509,0 -20942,50365340,Durazno o’henry en lata o en frasco,,0,80509,0 -20943,50365341,Durazno escudo de reina en lata o en frasco,,0,80509,0 -20944,50365342,Durazno dama roja en lata o en frasco,,0,80509,0 -20945,50365343,Durazno globo rojo en lata o en frasco,,0,80509,0 -20946,50365344,Durazno cielo rojo en lata o en frasco,,0,80509,0 -20947,50365345,Durazno redtop en lata o en frasco,,0,80509,0 -20948,50365346,Durazno regina en lata o en frasco,,0,80509,0 -20949,50365347,Durazno dama rica en lata o en frasco,,0,80509,0 -20950,50365348,Durazno mayo rico en lata o en frasco,,0,80509,0 -20951,50365349,Durazno gloria royal en lata o en frasco,,0,80509,0 -20952,50365350,Durazno dama royal en lata o en frasco,,0,80509,0 -20953,50365351,Durazno nieve de septiembre en lata o en frasco,,0,80509,0 -20954,50365352,Durazno sol de septiembre en lata o en frasco,,0,80509,0 -20955,50365353,Durazno gema sierra en lata o en frasco,,0,80509,0 -20956,50365354,Durazno angel de nieve en lata o en frasco,,0,80509,0 -20957,50365355,Durazno gema de nieve en lata o en frasco,,0,80509,0 -20958,50365356,Durazno rey de nieve en lata o en frasco,,0,80509,0 -20959,50365357,Durazno dama de primavera en lata o en frasco,,0,80509,0 -20960,50365358,Durazno nieve de primavera en lata o en frasco,,0,80509,0 -20961,50365359,Durazno escudo de primavera en lata o en frasco,,0,80509,0 -20962,50365360,Durazno dulce gigante en lata o en frasco,,0,80509,0 -20963,50365361,Durazno dama de azúcar en lata o en frasco,,0,80509,0 -20964,50365362,Durazno brillo de sol en lata o en frasco,,0,80509,0 -20965,50365363,Durazno sunhigh en lata o en frasco,,0,80509,0 -20966,50365364,Durazno dama super en lata o en frasco,,0,80509,0 -20967,50365365,Durazno super rico en lata o en frasco,,0,80509,0 -20968,50365366,Durazno surecrop en lata o en frasco,,0,80509,0 -20969,50365367,Durazno sueño dulce en lata o en frasco,,0,80509,0 -20970,50365368,Durazno septiembre dulce en lata o en frasco,,0,80509,0 -20971,50365369,Durazno vista en lata o en frasco,,0,80509,0 -20972,50365370,Durazno dama blanca en lata o en frasco,,0,80509,0 -20973,50365371,Durazno dama zee en lata o en frasco,,0,80509,0 -20974,50365600,Piñas en lata o en frasco,,0,80509,0 -20975,50365601,Piña chirimoya en lata o en frasco,Atemoya en lata o en frasco,0,80509,0 -20976,50365602,Piña dorada en lata o en frasco,,0,80509,0 -20977,50365603,Piña hilo en lata o en frasco,,0,80509,0 -20978,50365604,Piña kona sugarloaf en lata o en frasco,,0,80509,0 -20979,50365605,Piña reina natal en lata o en frasco,,0,80509,0 -20980,50365606,Piña pernabuco en lata o en frasco,,0,80509,0 -20981,50365607,Piña español rojo en lata o en frasco,,0,80509,0 -20982,50365608,Piña cayen suave en lata o en frasco,,0,80509,0 -20983,50365609,Piña sugarloaf en lata o en frasco,,0,80509,0 -20984,50365610,Piña variegated en lata o en frasco,,0,80509,0 -20985,50365800,Ciruelas en lata o en frasco,,0,80509,0 -20986,50365801,Ciruela joya amber en lata o en frasco,,0,80509,0 -20987,50365802,Ciruela angeleno en lata o en frasco,,0,80509,0 -20988,50365803,Ciruela aurora en lata o en frasco,,0,80509,0 -20989,50365804,Ciruela otoño bonito en lata o en frasco,,0,80509,0 -20990,50365805,Ciruela gigante de otoño en lata o en frasco,,0,80509,0 -20991,50365806,Ciruela orgullo de otoño en lata o en frasco,,0,80509,0 -20992,50365807,Ciruela rosa de otoño en lata o en frasco,,0,80509,0 -20993,50365808,Ciruela playero en lata o en frasco,,0,80509,0 -20994,50365809,Ciruela betty anne en lata o en frasco,,0,80509,0 -20995,50365810,Ciruela belleza negra en lata o en frasco,,0,80509,0 -20996,50365811,Ciruela bullase negra en lata o en frasco,,0,80509,0 -20997,50365812,Ciruela diamante negro en lata o en frasco,,0,80509,0 -20998,50365813,Ciruela gigante negro en lata o en frasco,,0,80509,0 -20999,50365814,Ciruela hielo negro en lata o en frasco,,0,80509,0 -21000,50365815,Ciruela esplendor negro en lata o en frasco,,0,80509,0 -21001,50365816,Ciruela ámbar negro en lata o en frasco,,0,80509,0 -21002,50365817,Ciruela purpura en lata o en frasco,,0,80509,0 -21003,50365818,Ciruela carsbad en lata o en frasco,,0,80509,0 -21004,50365819,Ciruela casselman en lata o en frasco,,0,80509,0 -21005,50365820,Ciruela catalina en lata o en frasco,,0,80509,0 -21006,50365821,Ciruela damson en lata o en frasco,,0,80509,0 -21007,50365822,Ciruela dolly en lata o en frasco,,0,80509,0 -21008,50365823,Ciruela earliqueen en lata o en frasco,,0,80509,0 -21009,50365824,Ciruela rosa early en lata o en frasco,,0,80509,0 -21010,50365825,Ciruela ébano mayo en lata o en frasco,,0,80509,0 -21011,50365826,Ciruela ébano en lata o en frasco,,0,80509,0 -21012,50365827,Ciruela corazón de elefante en lata o en frasco,,0,80509,0 -21013,50365828,Ciruela belleza de esmeralda en lata o en frasco,,0,80509,0 -21014,50365829,Ciruela emperatriz en lata o en frasco,,0,80509,0 -21015,50365830,Ciruela libertad en lata o en frasco,,0,80509,0 -21016,50365831,Ciruela friar en lata o en frasco,,0,80509,0 -21017,50365832,Ciruela rojo gar en lata o en frasco,,0,80509,0 -21018,50365833,Ciruela gobernador en lata o en frasco,,0,80509,0 -21019,50365834,Ciruela rosa grande en lata o en frasco,,0,80509,0 -21020,50365835,Ciruela calibre verde en lata o en frasco,,0,80509,0 -21021,50365836,Ciruela calibre verde en lata o en frasco,Ciruela greengage en lata o en frasco,0,80509,0 -21022,50365837,Ciruela hiromi en lata o en frasco,,0,80509,0 -21023,50365838,Ciruela hiromi rojo en lata o en frasco,,0,80509,0 -21024,50365839,Ciruela vacacion en lata o en frasco,,0,80509,0 -21025,50365840,Ciruela howard sol en lata o en frasco,,0,80509,0 -21026,50365841,Ciruela tipo interspecific en lata o en frasco,,0,80509,0 -21027,50365842,Ciruela jamaico en lata o en frasco,,0,80509,0 -21028,50365843,Ciruela joanna rojo en lata o en frasco,,0,80509,0 -21029,50365844,Ciruela kelsey en lata o en frasco,,0,80509,0 -21030,50365845,Ciruela jaime rey en lata o en frasco,,0,80509,0 -21031,50365846,Ciruela laroda en lata o en frasco,,0,80509,0 -21032,50365847,Ciruela rosa tarde en lata o en frasco,,0,80509,0 -21033,50365848,Ciruela rosa linda en lata o en frasco,,0,80509,0 -21034,50365849,Ciruela estrella solitaria en lata o en frasco,,0,80509,0 -21035,50365850,Ciruela mariposa en lata o en frasco,,0,80509,0 -21036,50365851,Ciruela mercado negro en lata o en frasco,,0,80509,0 -21037,50365852,Ciruela mercado rojo en lata o en frasco,,0,80509,0 -21038,50365853,Ciruela maribel en lata o en frasco,,0,80509,0 -21039,50365854,Ciruelas sol de octubre en lata o en frasco,,0,80509,0 -21040,50365855,Ciruela owen t en lata o en frasco,,0,80509,0 -21041,50365856,Ciruela perdrigon en lata o en frasco,,0,80509,0 -21042,50365857,Ciruela placer rosado en lata o en frasco,,0,80509,0 -21043,50365858,Ciruela presidente en lata o en frasco,,0,80509,0 -21044,50365859,Ciruela primetime en lata o en frasco,,0,80509,0 -21045,50365860,Ciruela majestad purpura en lata o en frasco,,0,80509,0 -21046,50365861,Ciruela reina rosa en lata o en frasco,,0,80509,0 -21047,50365862,Ciruela quetsch en lata o en frasco,,0,80509,0 -21048,50365863,Ciruela belleza roja en lata o en frasco,,0,80509,0 -21049,50365864,Ciruela camino rojo en lata o en frasco,,0,80509,0 -21050,50365865,Ciruela ram rojo en lata o en frasco,,0,80509,0 -21051,50365866,Ciruela rosa roja en lata o en frasco,,0,80509,0 -21052,50365867,Ciruela rojo rico en lata o en frasco,,0,80509,0 -21053,50365868,Ciruela romero en lata o en frasco,,0,80509,0 -21054,50365869,Ciruela diamante rojo en lata o en frasco,,0,80509,0 -21055,50365870,Ciruela rojo royal en lata o en frasco,,0,80509,0 -21056,50365871,Ciruela royal zee en lata o en frasco,,0,80509,0 -21057,50365872,Ciruela roysum en lata o en frasco,,0,80509,0 -21058,50365873,Ciruela santa rosa en lata o en frasco,,0,80509,0 -21059,50365874,Ciruela zafiro en lata o en frasco,,0,80509,0 -21060,50365875,Ciruela sloe en lata o en frasco,,0,80509,0 -21061,50365876,Ciruela sta catherine en lata o en frasco,,0,80509,0 -21062,50365877,Ciruela bullase blanco en lata o en frasco,,0,80509,0 -21063,50366100,Membrillos en lata o en frasco,,0,80509,0 -21064,50366101,Membrillo campeón en lata o en frasco,,0,80509,0 -21065,50366102,Membrillo piña en lata o en frasco,,0,80509,0 -21066,50366103,Membrillo smyma en lata o en frasco,,0,80509,0 -21067,50366800,Manzanas dulces en lata o en frasco,Saramuyos en lata o en frasco,0,80509,0 -21068,50366801,Manzana kampong malve dulce en lata o en frasco,Saramuyo kampong mauve en lata o en frasco,0,80509,0 -21069,50366802,Manzana dulce sin pepa en lata o en frasco,Saramuyo sin semilla en lata o en frasco,0,80509,0 -21070,50366803,Manzana dulce thai lessand en lata o en frasco,Saramuyo thai lessand en lata o en frasco,0,80509,0 -21071,50366900,Tamarillos en lata o en frasco,Tomate de árbol en lata o en frasco,0,80509,0 -21072,50366901,Tamarindo amberlea dorado en lata o en frasco,Tomate de árbol amberlea dorado en lata o en frasco,0,80509,0 -21073,50366902,Tamarindo calvo dorado en lata o en frasco,Tomate de árbol calvo dorado en lata o en frasco,0,80509,0 -21074,50366903,Tamarindo mina de oro en lata o en frasco,Tomate de árbol mina de oro en lata o en frasco,0,80509,0 -21075,50366904,Tamarindo oratia rojo en lata o en frasco,Tomate de árbol oratia rojo en lata o en frasco,0,80509,0 -21076,50366905,Tamarindo beau rojo en lata o en frasco,Tomate de árbol beau rojo en lata o en frasco,0,80509,0 -21077,50366906,Tamarindo delicia roja en lata o en frasco,Tomate de árbol delicia roja en lata o en frasco,0,80509,0 -21078,50367000,Frutas Nominant en lata o en frasco,Frutas predominantes en lata o en frasco,0,80509,0 -21079,50367001,Cupania en lata o en frasco,Ackee en lata o en frasco,0,80509,0 -21080,50367002,Babaco en lata o en frasco,Carica pentágona en lata o en frasco,0,80509,0 -21081,50367003,Banana flor en lata o en frasco,Flor de plátano en lata o en frasco,0,80509,0 -21082,50367004,Baobab en lata o en frasco,,0,80509,0 -21083,50367005,Naranja agria en lata o en frasco,,0,80509,0 -21084,50367006,Canistel en lata o en frasco,Zapote amarillo en lata o en frasco,0,80509,0 -21085,50367007,Baya nublosa en lata o en frasco,Mora de los pantanos en lata o en frasco,0,80509,0 -21086,50367008,Coco en lata o en frasco,,0,80509,0 -21087,50367009,Baya drew en lata o en frasco,Baya de gota en lata o en frasco,0,80509,0 -21088,50367010,Durian en lata o en frasco,Durio en lata o en frasco,0,80509,0 -21089,50367011,Baya elder en lata o en frasco,Baya de sauco en lata o en frasco,0,80509,0 -21090,50367012,Feijoa en lata o en frasco,Guayabo del Brasil en lata o en frasco,0,80509,0 -21091,50367013,Mora en lata o en frasco,,0,80509,0 -21092,50367014,Cretaegus en lata o en frasco,Majuelo en lata o en frasco,0,80509,0 -21093,50367015,Baya miel en lata o en frasco,Madreselva azul en lata o en frasco,0,80509,0 -21094,50367016,Jaca en lata o en frasco,Yaca en lata o en frasco,0,80509,0 -21095,50367017,Jambolan en lata o en frasco,,0,80509,0 -21096,50367018,Jujube en lata o en frasco,Azufaifa en lata o en frasco,0,80509,0 -21097,50367019,Liches en lata o en frascos orgánicos,Lichi en lata o en frascos orgánicos,0,80509,0 -21098,50367020,Mangostinos en lata o en frascos orgánicos,Mangostán en lata o en frascos orgánicos,0,80509,0 -21099,50367021,Medlar en lata o en frasco,,0,80509,0 -21100,50367022,Mombines en lata o en frasco,Hobo en lata o en frasco,0,80509,0 -21101,50367023,Monstera en lata o en frasco,Cerimán en lata o en frasco,0,80509,0 -21102,50367024,Pepinos en lata o en frasco,Pepino dulce en lata o en frasco,0,80509,0 -21103,50367025,Platono en lata o en frasco,Plátano macho en lata o en frasco,0,80509,0 -21104,50367026,Peras en escabeche en lata o en frasco,Tuna en lata o en frasco,0,80509,0 -21105,50367027,Mamonsillo en lata o en frasco,Guaya en lata o en frasco,0,80509,0 -21106,50367028,Rambután en lata o en frasco,Achotillo en lata o en frasco,0,80509,0 -21107,50367029,Manzana rosa en lata o en frasco,Pero de agua en lata o en frasco,0,80509,0 -21108,50367030,Rosele en lata o en frasco,Flor de jamaica en lata o en frasco,0,80509,0 -21109,50367031,Baya rowan en lata o en frasco,Serba en lata o en frasco,0,80509,0 -21110,50367032,Baya buckhom de mar en lata o en frasco,Espino amarillo en lata o en frasco,0,80509,0 -21111,50367033,Baya plata en lata o en frasco,Olivo de otoño en lata o en frasco,0,80509,0 -21112,50367034,Baya sorbete en lata o en frasco,Serba en lata o en frasco,0,80509,0 -21113,50367035,Guanábana en lata o en frasco,,0,80509,0 -21114,50367036,Manzana estrella en lata o en frasco,Cainito en lata o en frasco,0,80509,0 -21115,50367037,Tamarindo en lata o en frasco,,0,80509,0 -21116,50367200,Aceitunas en lata o en frasco,,0,80509,0 -21117,50367201,Aceitunas agrinion en lata o en frasco,Aceituna agrinion en lata o en frasco,0,80509,0 -21118,50367202,Aceitunas aleppo en lata o en frasco,Aceituna aleppo en lata o en frasco,0,80509,0 -21119,50367203,Aceitunas alfonso en lata o en frasco,Aceituna alfonso en lata o en frasco,0,80509,0 -21120,50367204,Aceitunas amfisa en lata o en frasco,Aceituna amfisa en lata o en frasco,0,80509,0 -21121,50367205,Aceitunas arauco en lata o en frasco,Aceituna arauco en lata o en frasco,0,80509,0 -21122,50367206,Aceitunas arbequina en lata o en frasco,Aceituna arbequina en lata o en frasco,0,80509,0 -21123,50367207,Aceitunas atlanta en lata o en frasco,Aceituna atlanta en lata o en frasco,0,80509,0 -21124,50367208,Aceitunas cerignola en lata o en frasco,Aceituna cerignola en lata o en frasco,0,80509,0 -21125,50367209,Aceitunas cracked provencal en lata o en frasco,Aceituna cracked provencal en lata o en frasco,0,80509,0 -21126,50367210,Aceitunas empeltre en lata o en frasco,Aceituna empeltre en lata o en frasco,0,80509,0 -21127,50367211,Aceitunas gaeta en lata o en frasco,Aceituna gaeta en lata o en frasco,0,80509,0 -21128,50367212,Aceitunas hondoelia en lata o en frasco,Aceituna hondoelia en lata o en frasco,0,80509,0 -21129,50367213,Aceitunas kalamata en lata o en frasco,Aceituna kalamata en lata o en frasco,0,80509,0 -21130,50367214,Aceitunas kura en lata o en frasco,Aceituna kura en lata o en frasco,0,80509,0 -21131,50367215,Aceitunas ligurian en lata o en frasco,Aceituna ligurian en lata o en frasco,0,80509,0 -21132,50367216,Aceitunas lucque en lata o en frasco,Aceituna lucque en lata o en frasco,0,80509,0 -21133,50367217,Aceitunas lugano en lata o en frasco,Aceituna lugano en lata o en frasco,0,80509,0 -21134,50367218,Aceitunas manzanilla en lata o en frasco,Aceituna manzanilla en lata o en frasco,0,80509,0 -21135,50367219,Aceitunas marche en lata o en frasco,Aceituna marche en lata o en frasco,0,80509,0 -21136,50367220,Aceitunas misión en lata o en frasco,Aceituna misión en lata o en frasco,0,80509,0 -21137,50367221,Aceitunas nafplion verde en lata o en frasco,Aceituna nafplion verde en lata o en frasco,0,80509,0 -21138,50367222,Aceitunas nicoise en lata o en frasco,Aceituna nicoise en lata o en frasco,0,80509,0 -21139,50367223,Aceitunas nyons en lata o en frasco,Aceituna nyons en lata o en frasco,0,80509,0 -21140,50367224,Aceitunas picholine en lata o en frasco,Aceituna picholine en lata o en frasco,0,80509,0 -21141,50367225,Aceitunas ponentine en lata o en frasco,Aceituna ponentine en lata o en frasco,0,80509,0 -21142,50367226,Aceitunas royal en lata o en frasco,Aceituna royal en lata o en frasco,0,80509,0 -21143,50367227,Aceitunas seracena en lata o en frasco,Aceituna seracena en lata o en frasco,0,80509,0 -21144,50367228,Aceitunas sevillano en lata o en frasco,Aceituna sevillano en lata o en frasco,0,80509,0 -21145,50367229,Aceitunas sicilian en lata o en frasco,Aceituna sicilian en lata o en frasco,0,80509,0 -21146,50367230,Aceitunas toscanella en lata o en frasco,Aceituna toscanella en lata o en frasco,0,80509,0 -21147,50371500,Manzanas orgánicas en lata o en frasco,,0,80509,0 -21148,50371501,Manzanas akane orgánica en lata o en frascos,,0,80509,0 -21149,50371502,Manzana ambrosia orgánica en lata o en frasco,,0,80509,0 -21150,50371503,Manzanas api orgánica en lata o en frascos,,0,80509,0 -21151,50371504,Manzanas baldwin orgánica en lata o en frasco,,0,80509,0 -21152,50371505,Manzanas brabum orgánica en lata o en frasco,,0,80509,0 -21153,50371506,Manzanas bramley orgánica en lata o en frasco,,0,80509,0 -21154,50371507,Manzana joven bramley orgánica en lata o en frasco,,0,80509,0 -21155,50371508,Manzana calville blanche d’hiver orgánica en lata o en frasco,,0,80509,0 -21156,50371509,Manzana cameo orgánica en lata o en frasco,,0,80509,0 -21157,50371510,Manzana charles ross orgánica en lata o en frasco,,0,80509,0 -21158,50371511,Manzana codlin orgánica en lata o en frasco,,0,80509,0 -21159,50371512,Manzana cortland orgánica en lata o en frasco,,0,80509,0 -21160,50371513,Manzana costard orgánica en lata o en frasco,,0,80509,0 -21161,50371514,Manzana court pendu plat orgánica en lata o en frasco,,0,80509,0 -21162,50371515,Manzana cox´s orange pippin orgánica en lata o en frasco,,0,80509,0 -21163,50371516,Manzana crab orgánica en lata o en frasco,,0,80509,0 -21164,50371517,Manzana crispin orgánica en lata o en frasco,,0,80509,0 -21165,50371518,Manzana delicious orgánica en lata o en frasco,,0,80509,0 -21166,50371519,Manzana duquesa orgánica en lata o en frasco,,0,80509,0 -21167,50371520,Manzana earligold orgánica en lata o en frasco,,0,80509,0 -21168,50371521,Manzana early mcintosh orgánica en lata o en frasco,,0,80509,0 -21169,50371522,Manzana elstar orgánica en lata o en frasco,,0,80509,0 -21170,50371523,Manzana empire orgánica en lata o en frasco,,0,80509,0 -21171,50371524,Manzana flor de kent orgánica en lata o en frasco,,0,80509,0 -21172,50371525,Manzana fiji orgánica en lata o en frasco,,0,80509,0 -21173,50371526,Manzana gala orgánica en lata o en frasco,,0,80509,0 -21174,50371527,Manzana gascoyne´s scarlet orgánica en lata o en frasco,,0,80509,0 -21175,50371528,Manzana gillyflower orgánica en lata o en frasco,,0,80509,0 -21176,50371529,Manzana ginger gold orgánica en lata o en frasco,,0,80509,0 -21177,50371530,Manzana gladstone orgánica en lata o en frasco,,0,80509,0 -21178,50371531,Manzana gloster orgánica en lata o en frasco,,0,80509,0 -21179,50371532,Manzana gold supreme orgánica en lata o en frasco,,0,80509,0 -21180,50371533,Manzana golden delicious orgánica en lata o en frasco,,0,80509,0 -21181,50371534,Manzana golden nobel orgánica en lata o en frasco,,0,80509,0 -21182,50371535,Manzana granny smith orgánica en lata o en frasco,,0,80509,0 -21183,50371536,Manzana gravenstain orgánica en lata o en frasco,,0,80509,0 -21184,50371537,Manzana greening orgánica en lata o en frasco,,0,80509,0 -21185,50371538,Manzana greensleeves orgánica en lata o en frasco,,0,80509,0 -21186,50371539,Manzana honeycrisp orgánica en lata o en frasco,,0,80509,0 -21187,50371540,Manzana howgate wonder orgánica en lata o en frasco,,0,80509,0 -21188,50371541,Manzana red ida orgánica en lata o en frasco,,0,80509,0 -21189,50371542,Manzana james grives orgánica en lata o en frasco,,0,80509,0 -21190,50371543,Manzana jersey mac orgánica en lata o en frasco,,0,80509,0 -21191,50371544,Manzana jaster orgánica en lata o en frasco,,0,80509,0 -21192,50371545,Manzana jonagold orgánica en lata o en frasco,,0,80509,0 -21193,50371546,Manzana jonamac orgánica en lata o en frasco,,0,80509,0 -21194,50371547,Manzana jonathan orgánica en lata o en frasco,,0,80509,0 -21195,50371548,Manzana katy orgánica en lata o en frasco,,0,80509,0 -21196,50371549,Manzana kidd´s orange red orgánica en lata o en frasco,,0,80509,0 -21197,50371550,Manzana lady orgánica en lata o en frasco,,0,80509,0 -21198,50371551,Manzana law rome orgánica en lata o en frasco,,0,80509,0 -21199,50371552,Manzana laxton orgánica en lata o en frasco,,0,80509,0 -21200,50371553,Manzana lord derby orgánica en lata o en frasco,,0,80509,0 -21201,50371554,Manzana macoun orgánica en lata o en frasco,,0,80509,0 -21202,50371555,Manzana mcintosh orgánica en lata o en frasco,,0,80509,0 -21203,50371556,Manzana mutsu orgánica en lata o en frasco,,0,80509,0 -21204,50371557,Manzana newtown pippin orgánica en lata o en frasco,,0,80509,0 -21205,50371558,Manzana northen spy orgánica en lata o en frasco,,0,80509,0 -21206,50371559,Manzana orleans reinette orgánica en lata o en frasco,,0,80509,0 -21207,50371560,Manzana ozark gold orgánica en lata o en frasco,,0,80509,0 -21208,50371561,Manzana rosa pacifica orgánica en lata o en frasco,,0,80509,0 -21209,50371562,Manzana paula roja orgánica en lata o en frasco,,0,80509,0 -21210,50371563,Manzana peamain orgánica en lata o en frasco,,0,80509,0 -21211,50371564,Manzana dama rosada orgánica en lata o en frasco,,0,80509,0 -21212,50371565,Manzana pippin orgánica en lata o en frasco,,0,80509,0 -21213,50371566,Manzana pitmaston pineapple orgánica en lata o en frasco,,0,80509,0 -21214,50371567,Manzana pomme dápi orgánica en lata o en frasco,,0,80509,0 -21215,50371568,Manzana prime gold orgánica en lata o en frasco,,0,80509,0 -21216,50371569,Manzana rojo astrakhan orgánica en lata o en frasco,,0,80509,0 -21217,50371570,Manzana rojo boscoop orgánica en lata o en frasco,,0,80509,0 -21218,50371571,Manzana rojo jefe orgánica en lata o en frasco,,0,80509,0 -21219,50371572,Manzana delicia rojo orgánica en lata o en frasco,,0,80509,0 -21220,50371573,Manzana rojo grvenstain orgánica en lata o en frasco,,0,80509,0 -21221,50371574,Manzana roma orgánica en lata o en frasco,,0,80509,0 -21222,50371575,Manzana rojo stayman orgánica en lata o en frasco,,0,80509,0 -21223,50371576,Manzana rojo york orgánica en lata o en frasco,,0,80509,0 -21224,50371577,Manzana reinette orgánica en lata o en frasco,,0,80509,0 -21225,50371578,Manzana belleza romana orgánica en lata o en frasco,,0,80509,0 -21226,50371579,Manzana russet orgánica en lata o en frasco,,0,80509,0 -21227,50371580,Manzana sierra belleza orgánica en lata o en frasco,,0,80509,0 -21228,50371581,Manzana spartan orgánica en lata o en frasco,,0,80509,0 -21229,50371582,Manzana stark crimson orgánica en lata o en frasco,,0,80509,0 -21230,50371583,Manzana starking orgánica en lata o en frasco,,0,80509,0 -21231,50371584,Manzana stayman orgánica en lata o en frasco,,0,80509,0 -21232,50371585,Manzana stayman winesap orgánica en lata o en frasco,,0,80509,0 -21233,50371586,Manzana summer rambo orgánica en lata o en frasco,,0,80509,0 -21234,50371587,Manzana tsugaru orgánica en lata o en frasco,,0,80509,0 -21235,50371588,Manzana twenty ounce orgánica en lata o en frasco,,0,80509,0 -21236,50371589,Manzana tydeman red orgánica en lata o en frasco,,0,80509,0 -21237,50371590,Manzana vistabella orgánica en lata o en frasco,,0,80509,0 -21238,50371591,Manzana weatley orgánica en lata o en frasco,,0,80509,0 -21239,50371592,Manzana blanca joanetine orgánica en lata o en frasco,,0,80509,0 -21240,50371593,Manzana blanca transparent orgánica en lata o en frasco,,0,80509,0 -21241,50371594,Manzana winesap orgánica en lata o en frasco,,0,80509,0 -21242,50371595,Manzana worcester orgánica en lata o en frasco,,0,80509,0 -21243,50371596,Manzana york imperial orgánica en lata o en frasco,,0,80509,0 -21244,50374500,Mangos orgánicos en lata o en frasco,,0,80509,0 -21245,50374501,Mango alphonso orgánico en lata o en frasco,,0,80509,0 -21246,50374502,Mango ataulfo orgánico en lata o en frasco,,0,80509,0 -21247,50374503,Mango criollo orgánico en lata o en frasco,,0,80509,0 -21248,50374504,Mango edwards orgánico en lata o en frasco,,0,80509,0 -21249,50374505,Mango francine orgánico en lata o en frasco,,0,80509,0 -21250,50374506,Mango francis orgánico en lata o en frasco,,0,80509,0 -21251,50374507,Mango gandaria orgánico en lata o en frasco,,0,80509,0 -21252,50374508,Mango haden orgánico en lata o en frasco,,0,80509,0 -21253,50374509,Mangos irwin orgánico en lata o en frasco,,0,80509,0 -21254,50374510,Mangos keitt orgánico en lata o en frasco,,0,80509,0 -21255,50374511,Mangos kent orgánico en lata o en frasco,,0,80509,0 -21256,50374512,Mangos kesar orgánico en lata o en frasco,,0,80509,0 -21257,50374513,Mangos kuini orgánico en lata o en frasco,,0,80509,0 -21258,50374514,Mangos manila super orgánico en lata o en frasco,,0,80509,0 -21259,50374515,Mangos manila orgánico en lata o en frasco,,0,80509,0 -21260,50374516,Mangos mayaguez orgánico en lata o en frasco,,0,80509,0 -21261,50374517,Mangos mulgoba orgánico en lata o en frasco,,0,80509,0 -21262,50374518,Mangos oro orgánico en lata o en frasco,,0,80509,0 -21263,50374519,Mangos palmer orgánico en lata o en frasco,,0,80509,0 -21264,50374520,Mango parvin orgánico en lata o en frasco,,0,80509,0 -21265,50374521,Mango sandersha orgánico en lata o en frasco,,0,80509,0 -21266,50374522,Mangos sensation orgánico en lata o en frasco,,0,80509,0 -21267,50374523,Mango smith orgánico en lata o en frasco,,0,80509,0 -21268,50374524,Mango tomy atkin orgánico en lata o en frasco,,0,80509,0 -21269,50374525,Mango van dyke orgánico en lata o en frasco,,0,80509,0 -21270,50376700,Fresas orgánicas en lata o en frasco,,0,80509,0 -21271,50376701,Fresa chandler orgánica en lata o en frasco,,0,80509,0 -21272,50376702,Fresa rumbo junio orgánica en lata o en frasco,Fresa temporada de junio orgánica en lata o en frasco,0,80509,0 -21273,50376703,Fresa rumbo siempre orgánica en lata o en frasco,Fresa de todo el año orgánica en lata o en frasco,0,80509,0 -21274,50377000,Frutas Nominant orgánicas en lata o en frasco,Frutas predominantes orgánicas en lata o en frasco,0,80509,0 -21275,50377001,Cupania orgánica en lata o en frasco,Ackee orgánica en lata o en frasco,0,80509,0 -21276,50377002,Babaco orgánico en lata o en frasco,Carica pentágona orgánica en lata o en frasco,0,80509,0 -21277,50377003,Banana flor orgánica en lata o en frasco,Flor de plátano orgánica en lata o en frasco,0,80509,0 -21278,50377004,Baobab orgánica en lata o en frasco,,0,80509,0 -21279,50377005,Naranja agria orgánica en lata o en frasco,,0,80509,0 -21280,50377006,Canistel orgánica en lata o en frasco,Zapote amarillo orgánico en lata o en frasco,0,80509,0 -21281,50377007,Baya nublosa orgánica en lata o en frasco,Mora de los pantanos orgánica en lata o en frasco,0,80509,0 -21282,50377008,Coco orgánico en lata o en frasco,,0,80509,0 -21283,50377009,Baya drew orgánica en lata o en frasco,Baya de gota orgánica en lata o en frasco,0,80509,0 -21284,50377010,Durian orgánica en lata o en frasco,Durio orgánico en lata o en frasco,0,80509,0 -21285,50377011,Baya elder orgánica en lata o en frasco,Baya de sauco orgánica en lata o en frasco,0,80509,0 -21286,50377012,Feijoa orgánica en lata o en frasco,Guayabo del Brasil orgánico en lata o en frasco,0,80509,0 -21287,50377013,Mora orgánica en lata o en frasco,,0,80509,0 -21288,50377014,Cretaegus orgánica en lata o en frasco,Majuelo orgánico en lata o en frasco,0,80509,0 -21289,50377015,Baya miel orgánica en lata o en frasco,Madreselva azul orgánica en lata o en frasco,0,80509,0 -21290,50377016,Jaca orgánica en lata o en frasco,Yaca orgánica en lata o en frasco,0,80509,0 -21291,50377017,Jambolan orgánica en lata o en frasco,,0,80509,0 -21292,50377018,Jujube orgánica en lata o en frasco,Azufaifa orgánica en lata o en frasco,0,80509,0 -21293,50377019,Liches orgánico en lata o en frascos en lata o en frasco,Lichi orgánico en lata o en frascos en lata o en frasco,0,80509,0 -21294,50377020,Mangostinos orgánico en lata o en frascos en lata o en frasco,Mangostán orgánico en lata o en frascos en lata o en frasco,0,80509,0 -21295,50377021,Medlar orgánica en lata o en frasco,,0,80509,0 -21296,50377022,Mombines orgánica en lata o en frasco,Hobo orgánico en lata o en frasco,0,80509,0 -21297,50377023,Monstera orgánica en lata o en frasco,Cerimán orgánico en lata o en frasco,0,80509,0 -21298,50377024,Pepinos orgánico en lata o en frasco,Pepino dulce orgánico en lata o en frasco,0,80509,0 -21299,50377025,Platono orgánico en lata o en frasco,Plátano macho orgánico en lata o en frasco,0,80509,0 -21300,50377026,Peras en escabeche orgánica en lata o en frasco,Tuna orgánica en lata o en frasco,0,80509,0 -21301,50377027,Mamonsillo orgánico en lata o en frasco,Guaya orgánico en lata o en frasco,0,80509,0 -21302,50377028,Rambután orgánico en lata o en frasco,Achotillo orgánico en lata o en frasco,0,80509,0 -21303,50377029,Manzana rosa orgánica en lata o en frasco,Pero de agua orgánica en lata o en frasco,0,80509,0 -21304,50377030,Rosele orgánica en lata o en frasco,Flor de jamaica orgánica en lata o en frasco,0,80509,0 -21305,50377031,Baya rowan orgánica en lata o en frasco,Serba orgánica en lata o en frasco,0,80509,0 -21306,50377032,Baya buckhom de mar orgánica en lata o en frasco,Espino amarillo orgánica en lata o en frasco,0,80509,0 -21307,50377033,Baya plata orgánica en lata o en frasco,Olivo de otoño orgánica en lata o en frasco,0,80509,0 -21308,50377034,Baya sorbete orgánico en lata o en frasco,Serba orgánico en lata o en frasco,0,80509,0 -21309,50377035,Wanabana orgánica en lata o en frasco,Guanábana orgánica en lata o en frasco,0,80509,0 -21310,50377036,Manzana estrella orgánica en lata o en frasco,Cainito orgánica en lata o en frasco,0,80509,0 -21311,50377037,Tamarindo orgánico en lata o en frasco,,0,80509,0 -21312,50381600,Purés de bayas,,0,80509,0 -21313,50381601,Puré de fresa,,0,80509,0 -21314,50381602,Puré de mora,,0,80509,0 -21315,50381700,Purés de frutas tropicales,,0,80509,0 -21316,50381701,Puré de mango,,0,80509,0 -21317,50381702,Puré de kiwi,,0,80509,0 -21318,50401500,Alcachofas,,0,80509,0 -21319,50401501,Alcachofa brittany,,0,80509,0 -21320,50401502,Alcachofa cantonesa,,0,80509,0 -21321,50401503,Alcachofa francesa,,0,80509,0 -21322,50401504,Alcachofa globo verde,,0,80509,0 -21323,50401505,Alcachofa gros camus de bretaña,,0,80509,0 -21324,50401506,Alcachofa midi,,0,80509,0 -21325,50401507,Alcachofa globo morado,,0,80509,0 -21326,50401508,Alcachofa morado cecilia,,0,80509,0 -21327,50401509,Alcachofa romanesco,,0,80509,0 -21328,50401510,Alcachofa espinoso sardo,,0,80509,0 -21329,50401511,Alcachofa vert de laon,,0,80509,0 -21330,50401512,Alcachofa violeta de chiggiia,,0,80509,0 -21331,50401513,Alcachofa violeta de toscana,,0,80509,0 -21332,50401600,Espárragos,,0,80509,0 -21333,50401601,Espárragos connovers colossal,,0,80509,0 -21334,50401602,Espárragos franklin,,0,80509,0 -21335,50401603,Espárragos mamut gigante,,0,80509,0 -21336,50401604,Espárragos lucullus,,0,80509,0 -21337,50401605,Espárragos martha washington,,0,80509,0 -21338,50401700,Aguacates,,0,80509,0 -21339,50401701,Aguacate ajax b – 7,,0,80509,0 -21340,50401702,Aguacate arue,,0,80509,0 -21341,50401703,Aguacate bacon,,0,80509,0 -21342,50401704,Aguacate benik,,0,80509,0 -21343,50401705,Aguacate bernecker,,0,80509,0 -21344,50401706,Aguacate beta,,0,80509,0 -21345,50401707,Aguacate biondo,,0,80509,0 -21346,50401708,Aguacate príncipe negro,,0,80509,0 -21347,50401709,Aguacate blair,,0,80509,0 -21348,50401710,Aguacate blair cabina,,0,80509,0 -21349,50401711,Aguacate cabina 1,,0,80509,0 -21350,50401712,Aguacate cabina 3,,0,80509,0 -21351,50401713,Aguacate cabina 5,,0,80509,0 -21352,50401714,Aguacate cabina 7,,0,80509,0 -21353,50401715,Aguacate cabina 8,,0,80509,0 -21354,50401716,Aguacate brooks 1978,,0,80509,0 -21355,50401717,Aguacate brookslate,,0,80509,0 -21356,50401718,Aguacate california haas,,0,80509,0 -21357,50401719,Aguacate catalina,,0,80509,0 -21358,50401720,Aguacate chica,,0,80509,0 -21359,50401721,Aguacate choquette,,0,80509,0 -21360,50401722,Aguacate cristina,,0,80509,0 -21361,50401723,Aguacate collison,,0,80509,0 -21362,50401724,Aguacate donnie,,0,80509,0 -21363,50401725,Aguacate dr dupis numero 2,,0,80509,0 -21364,50401726,Aguacate dr. dupie,,0,80509,0 -21365,50401727,Aguacate ettinger,,0,80509,0 -21366,50401728,Aguacate fuch,,0,80509,0 -21367,50401729,Aguacate fuch gwen,,0,80509,0 -21368,50401730,Aguacate fuerte,,0,80509,0 -21369,50401731,Aguacate gotham,,0,80509,0 -21370,50401732,Aguacate gossman,,0,80509,0 -21371,50401733,Aguacate guatemala sin pepa,,0,80509,0 -21372,50401734,Aguacate hall,,0,80509,0 -21373,50401735,Aguacate hardee,,0,80509,0 -21374,50401736,Aguacate haas,,0,80509,0 -21375,50401737,Aguacate herman,,0,80509,0 -21376,50401738,Aguacate hickson,,0,80509,0 -21377,50401739,Aguacate k-5,,0,80509,0 -21378,50401740,Aguacate k- 9,,0,80509,0 -21379,50401741,Aguacate cordero haas,,0,80509,0 -21380,50401742,Aguacate leona,,0,80509,0 -21381,50401743,Aguacate leona linda,,0,80509,0 -21382,50401744,Aguacate lisa p,,0,80509,0 -21383,50401745,Aguacate lisa loretta,,0,80509,0 -21384,50401746,Aguacate loretta,,0,80509,0 -21385,50401747,Aguacate lula,,0,80509,0 -21386,50401748,Aguacate lula mascarthur,,0,80509,0 -21387,50401749,Aguacate marcus,,0,80509,0 -21388,50401750,Aguacate melendez,,0,80509,0 -21389,50401751,Aguacate meya,,0,80509,0 -21390,50401752,Aguacate miguel p,,0,80509,0 -21391,50401753,Aguacate monroe,,0,80509,0 -21392,50401754,Aguacate murrieta verde,,0,80509,0 -21393,50401755,Aguacate nabal,,0,80509,0 -21394,50401756,Aguacate nadir,,0,80509,0 -21395,50401757,Aguacate nesbitt,,0,80509,0 -21396,50401758,Aguacate peterson,,0,80509,0 -21397,50401759,Aguacate pinelli,,0,80509,0 -21398,50401760,Aguacate pinkerton,,0,80509,0 -21399,50401761,Aguacate pollock,,0,80509,0 -21400,50401762,Aguacate puebla,,0,80509,0 -21401,50401763,Aguacate reed,,0,80509,0 -21402,50401764,Aguacaterue,,0,80509,0 -21403,50401765,Aguacateruehle,,0,80509,0 -21404,50401766,Aguacate ryan,,0,80509,0 -21405,50401767,Aguacate semil,,0,80509,0 -21406,50401768,Aguacate semil 43,,0,80509,0 -21407,50401769,Aguacate simonds,,0,80509,0 -21408,50401770,Aguacate simpson,,0,80509,0 -21409,50401771,Aguacate taylor,,0,80509,0 -21410,50401772,Aguacate tonelada,,0,80509,0 -21411,50401773,Aguacate torre,,0,80509,0 -21412,50401774,Aguacate torre li,,0,80509,0 -21413,50401775,Aguacate trapp,,0,80509,0 -21414,50401776,Aguacate semilla de caribe,,0,80509,0 -21415,50401777,Aguacate wagner,,0,80509,0 -21416,50401778,Aguacate waldin,,0,80509,0 -21417,50401779,Aguacate wurtz,,0,80509,0 -21418,50401780,Aguacate zio p,,0,80509,0 -21419,50401781,Aguacate ziu,,0,80509,0 -21420,50401782,Aguacate zultano,,0,80509,0 -21421,50401800,Fríjoles,,0,80509,0 -21422,50401801,Frijoles anasazi ®,,0,80509,0 -21423,50401802,Frijoles appaloosa,,0,80509,0 -21424,50401803,Frijoles azuki,,0,80509,0 -21425,50401804,Frijoles barlotti,,0,80509,0 -21426,50401805,Frijoles appaloosa negra,,0,80509,0 -21427,50401806,Frijoles negros,,0,80509,0 -21428,50401807,Frijoles gran negros,,0,80509,0 -21429,50401808,Frijoles shackamaxon negro,,0,80509,0 -21430,50401809,Frijoles ojo negro,,0,80509,0 -21431,50401810,Frijoles bobby,,0,80509,0 -21432,50401811,Frijoles bolita,,0,80509,0 -21433,50401812,Frijoles esposa perezosa carmelita,,0,80509,0 -21434,50401813,Frijoles calipso,,0,80509,0 -21435,50401814,Frijoles canelini,,0,80509,0 -21436,50401815,Frijoles castor,,0,80509,0 -21437,50401816,Frijoles amarillo chino,,0,80509,0 -21438,50401817,Frijoles lengua de dragón,,0,80509,0 -21439,50401818,Frijoles soldado europeo,,0,80509,0 -21440,50401819,Frijoles fava,,0,80509,0 -21441,50401820,Frijoles flageolet,,0,80509,0 -21442,50401821,Frijoles horticultura francesa,,0,80509,0 -21443,50401822,Frijoles marina francesa,,0,80509,0 -21444,50401823,Frijoles coco gigante blanco,,0,80509,0 -21445,50401824,Frijoles verdes,,0,80509,0 -21446,50401825,Frijoles romano verde,,0,80509,0 -21447,50401826,Frijoles guar gum,,0,80509,0 -21448,50401827,Frijoles haricot,,0,80509,0 -21449,50401828,Frijoles hyacinth,,0,80509,0 -21450,50401829,Frijoles tipo italiano,,0,80509,0 -21451,50401830,Frijoles asombro jackson,,0,80509,0 -21452,50401831,Frijoles ganado jacobs,,0,80509,0 -21453,50401832,Frijoles asombro kentucky,,0,80509,0 -21454,50401833,Frijoles riñón,,0,80509,0 -21455,50401834,Frijoles lima,,0,80509,0 -21456,50401835,Frijoles madera,,0,80509,0 -21457,50401836,Frijoles medula,,0,80509,0 -21458,50401837,Frijoles mat,,0,80509,0 -21459,50401838,Frijoles monstoler ganzo salvaje,,0,80509,0 -21460,50401839,Frijoles mortgage lifter,,0,80509,0 -21461,50401840,Frijoles polilla,,0,80509,0 -21462,50401841,Frijoles mung,,0,80509,0 -21463,50401842,Frijoles munsi wolf,,0,80509,0 -21464,50401843,Frijoles nuna,,0,80509,0 -21465,50401844,Frijoles pinto,,0,80509,0 -21466,50401845,Frijoles runner,,0,80509,0 -21467,50401846,String beans,"Ayecote, Fríjol gordo",0,80509,0 -21468,50401847,Frijoles habichuelas,"Ejote, Fríjol tierno",0,80509,0 -21469,50401848,Frijoles tamarind,,0,80509,0 -21470,50401849,Frijoles tonka,,0,80509,0 -21471,50401850,Frijoles cera,,0,80509,0 -21472,50401851,Frijoles winged,,0,80509,0 -21473,50401852,Frijoles largos,,0,80509,0 -21474,50401853,Frijoles peruanos canario,,0,80509,0 -21475,50401900,Remolachas,Betabeles,0,80509,0 -21476,50401901,Remolacha accion,Betabel acción,0,80509,0 -21477,50401902,Remolacha albina vereduna,Betabel albina vereduna,0,80509,0 -21478,50401903,Remolacha barababiotela de chiggia,Betabel barababiotela de chiggia,0,80509,0 -21479,50401904,Remolacha boltardy,Betabel boltardy,0,80509,0 -21480,50401905,Remolacha bonel,Betabel bonel,0,80509,0 -21481,50401906,Remolacha burpees dorado,Betabel burpees dorado,0,80509,0 -21482,50401907,Remolacha cheltenham tapa verde,Betabel cheltenham tapa verde,0,80509,0 -21483,50401908,Remolacha cheltenham mono,Betabel cheltenham mono,0,80509,0 -21484,50401909,Remolacha chioggia,Betabel chioggia,0,80509,0 -21485,50401910,Remolacha cilindra,Betabel cilindra,0,80509,0 -21486,50401911,Remolacha dégypte,Betabel dégypte,0,80509,0 -21487,50401912,Remolacha detroit 2 rojo oscuro,Betabel detroit 2 rojo oscuro,0,80509,0 -21488,50401913,Remolacha detroit 2 bala chiquita,Betabel detroit 2 bala chiquita,0,80509,0 -21489,50401914,Remolacha egipcia plana,Betabel egipcia plana,0,80509,0 -21490,50401915,Remolacha raíz de nabo egipcio,Betabel raíz de nabo egipcio,0,80509,0 -21491,50401916,Remolacha fomanova,Betabel fomanova,0,80509,0 -21492,50401917,Remolacha forono,Betabel forono,0,80509,0 -21493,50401918,Remolacha monaco,Betabel mónaco,0,80509,0 -21494,50401919,Remolacha monograma,Betabel monograma,0,80509,0 -21495,50401920,Remolacha pronto,Betabel pronto,0,80509,0 -21496,50401921,Remolacha regalía,Betabel regalía,0,80509,0 -21497,50401922,Remolacha dulce,Betabel dulce,0,80509,0 -21498,50402000,Brócolis,,0,80509,0 -21499,50402001,Brócolini,,0,80509,0 -21500,50402002,Brócoli romanesco,,0,80509,0 -21501,50402003,Brócoli raab,,0,80509,0 -21502,50402004,Brócoli chino,,0,80509,0 -21503,50402100,Repollitos de Bruselas,Coles de Bruselas,0,80509,0 -21504,50402101,Repollitas de bruselas citade,Coles de Bruselas citade,0,80509,0 -21505,50402102,Repollitas de bruselas falstaff,Coles de Bruselas falstaff,0,80509,0 -21506,50402103,Repollitas de bruselas oliver,Coles de Bruselas oliver,0,80509,0 -21507,50402104,Repollitas de bruselas peer gynt,Coles de Bruselas peer gynt,0,80509,0 -21508,50402105,Repollitas de bruselas rampart,Coles de Bruselas rampart,0,80509,0 -21509,50402106,Repollitas de bruselas rubine,Coles de Bruselas rubine,0,80509,0 -21510,50402107,Repollitas de bruselas widgeon,Coles de Bruselas widgeon,0,80509,0 -21511,50402200,Cebollas de ensalada,Cebollas de cambray,0,80509,0 -21512,50402201,Cebolla belstville ensalada,Cebolla de cambray belstville,0,80509,0 -21513,50402202,Cebolla feast ensalada,Cebolla de cambray feast,0,80509,0 -21514,50402203,Cebolla ishikura ensalada,Cebolla de cambray ishikura,0,80509,0 -21515,50402204,Cebolla mercado kyoto,Cebolla de cambray mercado kyoto,0,80509,0 -21516,50402205,Cebolla barba roja ensalada,Cebolla de cambray barba roja,0,80509,0 -21517,50402206,Cebolla amigo rojo ensalada,Cebolla de cambray amigo rojo,0,80509,0 -21518,50402207,Cebolla santa claus,Cebolla de cambray santa claus,0,80509,0 -21519,50402208,Cebolla tokyo ensalada,Cebolla de cambray tokyo,0,80509,0 -21520,50402209,Cebolla lisbon blanca ensalada,Cebolla de cambray lisbon blanca,0,80509,0 -21521,50402210,Cebolla invierno blanca ensalada,Cebolla de cambray invierno blanca,0,80509,0 -21522,50402211,Cebolla final de invierno ensalada,Cebolla de cambray final de invierno,0,80509,0 -21523,50402300,Repollos,Coles,0,80509,0 -21524,50402301,Repollo negro,Col negro,0,80509,0 -21525,50402302,Repollo savoy,Col savoy,0,80509,0 -21526,50402303,Repollo zorrillo,Col zorrillo,0,80509,0 -21527,50402304,Repollo blanco,Col blanco,0,80509,0 -21528,50402305,Repollo morado,Col morado,0,80509,0 -21529,50402500,Zanahorias,,0,80509,0 -21530,50402501,Zanahoria amsterdam,,0,80509,0 -21531,50402502,Zanahoria rey de otoño,,0,80509,0 -21532,50402503,Zanahoria berlicum,,0,80509,0 -21533,50402504,Zanahoria chantenay,,0,80509,0 -21534,50402505,Zanahoria nantes,,0,80509,0 -21535,50402506,Zanahoria mercado de parís,,0,80509,0 -21536,50402507,Zanahoria bebé,,0,80509,0 -21537,50402600,Coliflores,,0,80509,0 -21538,50402601,Coliflor todo el año,,0,80509,0 -21539,50402602,Coliflor alverda,,0,80509,0 -21540,50402603,Otoño gigante 3,,0,80509,0 -21541,50402604,Coliflordok elgon,,0,80509,0 -21542,50402605,Coliflor bola de nieve temprana,,0,80509,0 -21543,50402606,Coliflor luz de lima,,0,80509,0 -21544,50402607,Coliflor minaret,,0,80509,0 -21545,50402608,Coliflor buque de naranja,,0,80509,0 -21546,50402609,Coliflor capa morada,,0,80509,0 -21547,50402610,Coliflor bola de nieve,,0,80509,0 -21548,50402611,Coliflor invierno walcheren 3,,0,80509,0 -21549,50402612,Coliflor piedra blanca,,0,80509,0 -21550,50402700,Apios,,0,80509,0 -21551,50402701,Apio celebridad,,0,80509,0 -21552,50402702,Apio,,0,80509,0 -21553,50402703,Apio chino,,0,80509,0 -21554,50402704,Apio dinant francés,,0,80509,0 -21555,50402705,Apio gigante rosado,,0,80509,0 -21556,50402706,Apio gigante rojo,,0,80509,0 -21557,50402707,Apio gigante blanco,,0,80509,0 -21558,50402708,Apio dorado auto escalado,,0,80509,0 -21559,50402709,"Apio ""greensleeves""",,0,80509,0 -21560,50402710,Apio hopkins fenlander,,0,80509,0 -21561,50402711,Apio torre marfil,,0,80509,0 -21562,50402712,Apio lathom blanqueado,,0,80509,0 -21563,50402713,Apio sopa de amsterdam,,0,80509,0 -21564,50402714,Apio estándar cargado,,0,80509,0 -21565,50402715,Apio triunfo alto utah,,0,80509,0 -21566,50402800,Acelgas,,0,80509,0 -21567,50402801,Acelga luz brillante,,0,80509,0 -21568,50402802,Acelga gigante fordhook,,0,80509,0 -21569,50402803,Acelga luculus,,0,80509,0 -21570,50402804,Acelga espinaca perpetua,,0,80509,0 -21571,50402805,Acelga ruibarbo,,0,80509,0 -21572,50402806,Acelga suizo,,0,80509,0 -21573,50402807,Acelga volcán,,0,80509,0 -21574,50402808,Acelga rey blanco,,0,80509,0 -21575,50403000,Repollos chinos,Col china,0,80509,0 -21576,50403001,Bok choy,,0,80509,0 -21577,50403002,Bok choy enano,,0,80509,0 -21578,50403003,Flor de repollo chino,Flor de col china,0,80509,0 -21579,50403004,Choy sum,,0,80509,0 -21580,50403005,Col (bok choy) enano,,0,80509,0 -21581,50403006,Col (bok choy) fengshan,,0,80509,0 -21582,50403007,Col (bok choy) jade pagoda,,0,80509,0 -21583,50403008,Col (bok choy) kasumi,,0,80509,0 -21584,50403009,Col (bok choy) nerva,,0,80509,0 -21585,50403010,Col (bok choy) rosette,,0,80509,0 -21586,50403011,Col (bok choy) ruffles,,0,80509,0 -21587,50403012,Col (bok choy) hoja santo serrated,,0,80509,0 -21588,50403013,Col (bok choy) shangahai,,0,80509,0 -21589,50403014,Shantung,,0,80509,0 -21590,50403015,Repollo tip top,Col china tip top,0,80509,0 -21591,50403016,Yau choy sum,,0,80509,0 -21592,50403100,Cebollines,,0,80509,0 -21593,50403101,Cebollín chino,,0,80509,0 -21594,50403200,Maíces,,0,80509,0 -21595,50403201,Maíz aloha,,0,80509,0 -21596,50403202,Maíz alpine,,0,80509,0 -21597,50403203,Maíz ambrosia,,0,80509,0 -21598,50403204,Maíz argent,,0,80509,0 -21599,50403205,Maíz aspen,,0,80509,0 -21600,50403206,Maíz avalancha,,0,80509,0 -21601,50403207,Maíz biqueen,,0,80509,0 -21602,50403208,Maíz bodacious,,0,80509,0 -21603,50403209,Maíz mantequilla y azúcar,,0,80509,0 -21604,50403210,Maíz calico belle,,0,80509,0 -21605,50403211,Maíz camelot,,0,80509,0 -21606,50403212,Maíz challengercrisp ‘n dulce,,0,80509,0 -21607,50403213,Maíz campeón,,0,80509,0 -21608,50403214,Maíz algodón de azúcar,,0,80509,0 -21609,50403215,Maíz dártagnan,,0,80509,0 -21610,50403216,Maíz dazzle,,0,80509,0 -21611,50403217,Maíz diamante y oro,,0,80509,0 -21612,50403218,Maíz divinidad,,0,80509,0 -21613,50403219,Maíz delicia doble,,0,80509,0 -21614,50403220,Maíz gema doble,,0,80509,0 -21615,50403221,Maíz earlivee,,0,80509,0 -21616,50403222,Maíz temprano extra dulce,,0,80509,0 -21617,50403223,Maíz excel,,0,80509,0 -21618,50403224,Maíz cruz dorada bantam,,0,80509,0 -21619,50403225,Maíz miel y crema,,0,80509,0 -21620,50403226,Maíz miel y perla,,0,80509,0 -21621,50403227,Maíz miel dulce,,0,80509,0 -21622,50403228,Maíz hudson,,0,80509,0 -21623,50403229,Maíz dorado illini,,0,80509,0 -21624,50403230,Maíz illini extra dulce,,0,80509,0 -21625,50403231,Maíz increíble,,0,80509,0 -21626,50403232,Maíz lochief,,0,80509,0 -21627,50403233,Maíz jubileo,,0,80509,0 -21628,50403234,Maíz jupbileo super dulce,,0,80509,0 -21629,50403235,Maíz confite korn,,0,80509,0 -21630,50403236,Maíz beso y acuso,,0,80509,0 -21631,50403237,Maíz lancelot,,0,80509,0 -21632,50403238,Maíz dulce maple,,0,80509,0 -21633,50403239,Maíz medley,,0,80509,0 -21634,50403240,Maíz merlin,,0,80509,0 -21635,50403241,Maíz milagro,,0,80509,0 -21636,50403242,Maíz nk – 199,,0,80509,0 -21637,50403243,Maíz durazno y crema,,0,80509,0 -21638,50403244,Maíz blanco perla,,0,80509,0 -21639,50403245,Maíz pegasus,,0,80509,0 -21640,50403246,Maíz fenomenal,,0,80509,0 -21641,50403247,Maíz dama de platino,,0,80509,0 -21642,50403248,Maíz precioso,,0,80509,0 -21643,50403249,Maíz pristine,,0,80509,0 -21644,50403250,Maíz rápido,,0,80509,0 -21645,50403251,Maíz radiante,,0,80509,0 -21646,50403252,Maíz seneca brave,,0,80509,0 -21647,50403253,Maíz amanecer seneca,,0,80509,0 -21648,50403254,Maíz horizonte seneca,,0,80509,0 -21649,50403255,Maíz brillo de estrella seneca,,0,80509,0 -21650,50403256,Maíz noche blanca seneca,,0,80509,0 -21651,50403257,Maíz showcase,,0,80509,0 -21652,50403258,Maíz reina plateada,,0,80509,0 -21653,50403259,Maíz bella nieve,,0,80509,0 -21654,50403260,Maíz nieve de primavera,,0,80509,0 -21655,50403261,Maíz premio de primavera,,0,80509,0 -21656,50403262,Maíz azúcar y oro,,0,80509,0 -21657,50403263,Maíz rollo de azúcar,,0,80509,0 -21658,50403264,Maíz nieve de azúcar,,0,80509,0 -21659,50403265,Maíz baile de sol,,0,80509,0 -21660,50403266,Maíz estrella tell,,0,80509,0 -21661,50403267,Maíz terminador,,0,80509,0 -21662,50403268,Maíz tesoro,,0,80509,0 -21663,50403269,Maíz tuxedo,,0,80509,0 -21664,50403300,Berros,,0,80509,0 -21665,50403301,Berros de tierra,,0,80509,0 -21666,50403302,Nasturtium,,0,80509,0 -21667,50403303,Berros de agua,,0,80509,0 -21668,50403304,Berros de invierno,,0,80509,0 -21669,50403400,Cocombros,Pepinos cohombro,0,80509,0 -21670,50403401,Cocombro arena,Pepino cohombro arena,0,80509,0 -21671,50403402,Cocombro americano,Pepino cohombro americano,0,80509,0 -21672,50403403,Cocombro atena,Pepino cohombro atena,0,80509,0 -21673,50403404,Cocombro blanco lungo de parigi,Pepino cohombro blanco lungo de parigi,0,80509,0 -21674,50403405,Cocombro verde burpless buen sabor,Pepino cohombro verde burpless buen sabor,0,80509,0 -21675,50403406,Cocombro chicago pickling,Pepino cohombro chicago pickling,0,80509,0 -21676,50403407,Cocombro manzana cristal,Pepino cohombro manzana cristal,0,80509,0 -21677,50403408,Cocombro limón cristal,Pepino cohombro limón cristal,0,80509,0 -21678,50403409,Cocombro danimas,Pepino cohombro danimas,0,80509,0 -21679,50403410,Cocombro gherkin,Pepino cohombro gherkin,0,80509,0 -21680,50403411,Cocombro hokus,Pepino cohombro hokus,0,80509,0 -21681,50403412,Cocombro japonés,Pepino cohombro japonés,0,80509,0 -21682,50403413,Cocombro karela,Pepino cohombro karela,0,80509,0 -21683,50403414,Cocombro korila,Pepino cohombro korila,0,80509,0 -21684,50403415,Cocombro verde largo mejorado,Pepino cohombro verde largo mejorado,0,80509,0 -21685,50403416,Cocombro mas mercado,Pepino cohombro mas mercado,0,80509,0 -21686,50403417,Cocombro enano,Pepino cohombro enano,0,80509,0 -21687,50403418,Cocombro pickling nacional,Pepino cohombro pickling nacional,0,80509,0 -21688,50403419,Cocombro persa,Pepino cohombro persa,0,80509,0 -21689,50403420,Cocombro telégrafo,Pepino cohombro telégrafo,0,80509,0 -21690,50403421,Cocombro telégrafo mejorado,Pepino cohombro telégrafo mejorado,0,80509,0 -21691,50403422,Cocombro vert de massy cornichon,Pepino cohombro vert de massy cornichon,0,80509,0 -21692,50403423,Cocombro yamato,Pepino cohombro yamato,0,80509,0 -21693,50403500,Berenjenas,,0,80509,0 -21694,50403501,Berenjena bambino,,0,80509,0 -21695,50403502,Berenjena belleza negra,,0,80509,0 -21696,50403503,Berenjena negra enorme,,0,80509,0 -21697,50403504,Berenjena chino,,0,80509,0 -21698,50403505,Berenjena huevo de pascua,,0,80509,0 -21699,50403506,Berenjena filipino,,0,80509,0 -21700,50403507,Berenjena mercado de florida,,0,80509,0 -21701,50403508,Berenjena india,,0,80509,0 -21702,50403509,Berenjena italiano,,0,80509,0 -21703,50403510,Berenjena japonés,,0,80509,0 -21704,50403511,Berenjena morado largo,,0,80509,0 -21705,50403512,Berenjena rayas larga,,0,80509,0 -21706,50403513,Berenjena mercado de dinero,,0,80509,0 -21707,50403514,Berenjena ova,,0,80509,0 -21708,50403515,Berenjena arveja,,0,80509,0 -21709,50403516,Berenjena tom bajito,,0,80509,0 -21710,50403517,Berenjena siciliano,,0,80509,0 -21711,50403518,Berenjena tailandés,,0,80509,0 -21712,50403519,Berenjena violeta de florencia,,0,80509,0 -21713,50403520,Berenjena blanco,,0,80509,0 -21714,50403700,Hinojos,,0,80509,0 -21715,50403701,Hinojo cantino,,0,80509,0 -21716,50403702,Hinojo fino,,0,80509,0 -21717,50403703,Hinojoherald,,0,80509,0 -21718,50403704,Hinojo perfección,,0,80509,0 -21719,50403705,Hinojo sirio,,0,80509,0 -21720,50403706,Hinojo florencia dulce,,0,80509,0 -21721,50403707,Hinojo tardo,,0,80509,0 -21722,50403800,Ajos,,0,80509,0 -21723,50403801,Ajo tarde california,,0,80509,0 -21724,50403802,Ajo tallos chinos,,0,80509,0 -21725,50403803,Ajo cebollin,,0,80509,0 -21726,50403804,Ajo germidor,,0,80509,0 -21727,50403805,Ajo guardado largo,,0,80509,0 -21728,50403806,Ajo ramson,,0,80509,0 -21729,50403807,Ajo rocambola,,0,80509,0 -21730,50403808,Ajo rosa de lautrec,,0,80509,0 -21731,50403809,Ajo solent blanco,,0,80509,0 -21732,50403810,Ajo morado español,,0,80509,0 -21733,50403811,Ajo valentina italiana,,0,80509,0 -21734,50403900,Calabazas,,0,80509,0 -21735,50403901,Petula,Luffa,0,80509,0 -21736,50403902,Melon amargo,,0,80509,0 -21737,50403903,Botella calabaza,Mate,0,80509,0 -21738,50403904,Calabash gounds,Calabaza de peregrino,0,80509,0 -21739,50403905,Melón peludo,,0,80509,0 -21740,50403906,Calabaza amarga,,0,80509,0 -21741,50403907,Loofah suave,Zapatillo suave,0,80509,0 -21742,50403908,Serpiente de calabaza,Chichinda,0,80509,0 -21743,50403909,Calabaza larga amarga,,0,80509,0 -21744,50403910,Calabaza tinda,,0,80509,0 -21745,50403911,Calabaza tindoori,,0,80509,0 -21746,50404000,Guisantes,Chícharos,0,80509,0 -21747,50404001,Guisante chino,Chícharo chino,0,80509,0 -21748,50404002,Guisante inglés,Chícharo inglés,0,80509,0 -21749,50404003,Guisante de jardín,Chícharo de jardín,0,80509,0 -21750,50404004,Guisante nieve,Chícharo nieve,0,80509,0 -21751,50404006,Guisante snap dulce,Chícharo snap dulce,0,80509,0 -21752,50404100,Hierbas,"Perejil, Yerbabuena o hierbabuena",0,80509,0 -21753,50404101,Albahaca,,0,80509,0 -21754,50404102,Laurel,,0,80509,0 -21755,50404103,Borraja,,0,80509,0 -21756,50404104,Alcaravea,,0,80509,0 -21757,50404105,Perifollo,,0,80509,0 -21758,50404106,Cilantro,,0,80509,0 -21759,50404107,Chipolinos,,0,80509,0 -21760,50404108,Hoja de curry,,0,80509,0 -21761,50404109,Eneldo,,0,80509,0 -21762,50404110,Epazote,,0,80509,0 -21763,50404111,Fenogreco,,0,80509,0 -21764,50404112,Limonaria,Té de limón,0,80509,0 -21765,50404113,Mejorana,,0,80509,0 -21766,50404114,Menta,,0,80509,0 -21767,50404115,Orégano,,0,80509,0 -21768,50404116,Pápalo,,0,80509,0 -21769,50404117,Pápalo delgado,,0,80509,0 -21770,50404118,Perilla,Albahaca japonesa,0,80509,0 -21771,50404119,Recao,,0,80509,0 -21772,50404120,Romero,,0,80509,0 -21773,50404121,Salvia,,0,80509,0 -21774,50404122,Salsifí,Barba cabruna,0,80509,0 -21775,50404123,Satureja,Ajedreas,0,80509,0 -21776,50404124,Estragón,Tarragón,0,80509,0 -21777,50404125,Tomillo,,0,80509,0 -21778,50404126,Tumeric,Cúrcuma,0,80509,0 -21779,50404127,Verdolaga,,0,80509,0 -21780,50404200,Coles,,0,80509,0 -21781,50404201,Col rizada,,0,80509,0 -21782,50404202,Repollo verde,,0,80509,0 -21783,50404300,Colinabos,Colirrábanos,0,80509,0 -21784,50404301,Colinabo estrella azur,Colirrábano estrella azur,0,80509,0 -21785,50404302,Colinabo verde viena,Colirrábano verde viena,0,80509,0 -21786,50404303,Colinabo lanro,Colirrábano lanro,0,80509,0 -21787,50404304,Colinabo viena morada,Colirrábano viena morada,0,80509,0 -21788,50404305,Colinabo trero rowel,Colirrábano trero rowel,0,80509,0 -21789,50404306,Colinabo viena blanca,Colirrábano viena blanca,0,80509,0 -21790,50404400,Puerros,Poros,0,80509,0 -21791,50404401,Puerro cobra gigante otoño,Poro cobra gigante otoño,0,80509,0 -21792,50404402,Puerro otoño mamut 2,Poro otoño mamut 2,0,80509,0 -21793,50404403,Puerro blue de solaise,Poro blue de solaise,0,80509,0 -21794,50404404,Puerro cortina,Poro cortina,0,80509,0 -21795,50404405,Puerro prelina,Poro prelina,0,80509,0 -21796,50404406,Puerro salvaje,Poro salvaje,0,80509,0 -21797,50404500,Lentejas,,0,80509,0 -21798,50404501,Lenteja beluga,,0,80509,0 -21799,50404502,Lenteja verde francesa,,0,80509,0 -21800,50404503,Lenteja verde,,0,80509,0 -21801,50404504,Lenteja crimson pequeña,,0,80509,0 -21802,50404505,Lenteja pardina española,,0,80509,0 -21803,50404506,Lenteja roja separada,,0,80509,0 -21804,50404507,Lenteja amarilla separad,,0,80509,0 -21805,50404508,Lenteja tarahumara rosada,,0,80509,0 -21806,50404600,Lechugas,,0,80509,0 -21807,50404601,Lechuga bibb,,0,80509,0 -21808,50404602,Lechuga boston,,0,80509,0 -21809,50404603,Lechuga frisee,,0,80509,0 -21810,50404604,Lechuga rosa lola,,0,80509,0 -21811,50404605,Lechuga mixta masculina,,0,80509,0 -21812,50404606,Lechuga mizuna,,0,80509,0 -21813,50404607,Lechuga hoja roja,,0,80509,0 -21814,50404608,Lechuga oja roja roble,,0,80509,0 -21815,50404609,Lechuga romana ruby,,0,80509,0 -21816,50404610,Lechuga romana bebé roja,,0,80509,0 -21817,50404611,Lechuga cabeza de mantequilla,,0,80509,0 -21818,50404612,Lechuga china,Lechuga orejona,0,80509,0 -21819,50404613,Lechuga cabeza tostada,,0,80509,0 -21820,50404614,Lechuga hoja verde,,0,80509,0 -21821,50404615,Lechuga iceberg,,0,80509,0 -21822,50404616,Lechuga cordero,,0,80509,0 -21823,50404617,Lechuga hoja suelta,,0,80509,0 -21824,50404618,Lechuga mache,,0,80509,0 -21825,50404619,Lechuga boston roja,,0,80509,0 -21826,50404620,Lechuga cabeza roja,,0,80509,0 -21827,50404621,Lechuga romana,,0,80509,0 -21828,50404622,Lechuga mostza roja rusa,,0,80509,0 -21829,50404623,Lechuga tatsoi,,0,80509,0 -21830,50404800,Hongos o champiñones,,0,80509,0 -21831,50404801,Champiñón trompeta negra,,0,80509,0 -21832,50404802,Champiñón carmelito,,0,80509,0 -21833,50404803,Champiñón,,0,80509,0 -21834,50404804,Champiñón chanterelle,,0,80509,0 -21835,50404805,Champiñón cremini,,0,80509,0 -21836,50404806,Champiñón enoki,,0,80509,0 -21837,50404807,Champiñón puercoespín,,0,80509,0 -21838,50404808,Champiñón gallina del bosque,,0,80509,0 -21839,50404809,Champiñón langosta,,0,80509,0 -21840,50404810,Champiñón morels,,0,80509,0 -21841,50404811,Champiñón ostra,,0,80509,0 -21842,50404812,Champiñón pleurotus,,0,80509,0 -21843,50404813,Champiñón pompom,,0,80509,0 -21844,50404814,Champiñón porcieni,,0,80509,0 -21845,50404815,Champiñón portobello,,0,80509,0 -21846,50404816,Champiñón shitake,,0,80509,0 -21847,50404817,Champiñón shimeji,,0,80509,0 -21848,50404818,Champiñón san jorge,,0,80509,0 -21849,50404819,Champiñón blanco,,0,80509,0 -21850,50404820,Champiñón tompeta blanca,,0,80509,0 -21851,50404821,Champiñón woodear,,0,80509,0 -21852,50404822,Champiñón seta,,0,80509,0 -21853,50404823,Champión tonku,,0,80509,0 -21854,50404900,Mostazas,,0,80509,0 -21855,50404902,Mostaza ajo,,0,80509,0 -21856,50404903,Mostaza hoja gigante,,0,80509,0 -21857,50404905,Mostaza sureño,,0,80509,0 -21858,50404906,Mostaza corazón envuelto,,0,80509,0 -21859,50405101,Lámpara china,,0,80509,0 -21860,50405104,Tomatillo,,0,80509,0 -21861,50405200,Okras,,0,80509,0 -21862,50405201,Okra artista,,0,80509,0 -21863,50405202,Okra vinotinto,,0,80509,0 -21864,50405203,Okra sin columna clemson,,0,80509,0 -21865,50405204,Okra verde enano vaina larga,,0,80509,0 -21866,50405205,Okra mamut sin columna,,0,80509,0 -21867,50405206,Okra terciopelo rojo,,0,80509,0 -21868,50405207,Okra estrella de david reliquia,,0,80509,0 -21869,50405300,Cebollas,,0,80509,0 -21870,50405301,Cebolla albion,,0,80509,0 -21871,50405302,Cebolla alisia craig,,0,80509,0 -21872,50405303,Cebolla hirviendo,,0,80509,0 -21873,50405304,Cebolla bufalo,,0,80509,0 -21874,50405305,Cebolla bulbo,,0,80509,0 -21875,50405306,Cebolla cremosa,,0,80509,0 -21876,50405307,Cebolla amarillo expreso o – x,,0,80509,0 -21877,50405308,Cebolla kelsae,,0,80509,0 -21878,50405309,Cebolla gigante marshal globo fen,,0,80509,0 -21879,50405310,Cebolla perla,,0,80509,0 -21880,50405311,Cebolla baron rojo,,0,80509,0 -21881,50405312,Cebolla roja,Cebolla morada,0,80509,0 -21882,50405313,Cebolla rijnsberger,,0,80509,0 -21883,50405314,Cebolla senshyu semi – globo amarillo,,0,80509,0 -21884,50405315,Cebolla sturon,,0,80509,0 -21885,50405316,Cebolla sturgatter,,0,80509,0 -21886,50405317,Cebolla dulce,,0,80509,0 -21887,50405318,Cebolla torpedo o rojo italiana,,0,80509,0 -21888,50405319,Cebolla roja guardada,,0,80509,0 -21889,50405320,Cebollablanca guardada,,0,80509,0 -21890,50405321,Cebollaamarilla guardada,,0,80509,0 -21891,50405322,Cebollarosada,,0,80509,0 -21892,50405323,Cebolla verde,,0,80509,0 -21893,50405500,Maníes,Cacahuates,0,80509,0 -21894,50405501,Maní bambara,Cacahuate bambara,0,80509,0 -21895,50405502,Maní florunner,Cacahuate florunner,0,80509,0 -21896,50405503,Maní husa / kerstings,Cacahuate husa / kerstings,0,80509,0 -21897,50405504,Maní español,Cacahuate español,0,80509,0 -21898,50405505,Maní valencia,Cacahuate valencia,0,80509,0 -21899,50405506,Maní virginia,Cacahuate virginia,0,80509,0 -21900,50405600,Pimientos,Chiles,0,80509,0 -21901,50405601,Pimentón ají,Ají,0,80509,0 -21902,50405602,Pimentón árbol,Chile de árbol,0,80509,0 -21903,50405603,Pimentón queso,,0,80509,0 -21904,50405604,Pimentón chilaca,Chilaca,0,80509,0 -21905,50405605,Pimentón cubanelles,Chile cubanelle o pimienta italiana,0,80509,0 -21906,50405606,Pimentón fresno,Chile fresno,0,80509,0 -21907,50405607,Pimentón kapia,Chile kapia,0,80509,0 -21908,50405608,Pimentón coreano,Chile cheongyang,0,80509,0 -21909,50405609,Pimentón manzano,Chile manzano o de cera,0,80509,0 -21910,50405610,Pimentón melrose,,0,80509,0 -21911,50405611,Pimentón chile amarillo,Chile peruano amarillo,0,80509,0 -21912,50405612,Pimentón aji dulce,Ají dulce,0,80509,0 -21913,50405613,Pimentón anaheim,,0,80509,0 -21914,50405614,Pimentón ancho,Chile ancho o mulato,0,80509,0 -21915,50405615,Pimentón campana,Chile campana,0,80509,0 -21916,50405616,Pimentón cascabel,Chile cascabel,0,80509,0 -21917,50405617,Pimentón cayenne,Pimienta de Cayena o roja,0,80509,0 -21918,50405618,Pimentón anfitrión cereza,,0,80509,0 -21919,50405619,Pimentón chitespin,Chile chiltepín,0,80509,0 -21920,50405620,Pimentón dedo picante,Chile Jwala,0,80509,0 -21921,50405621,Pimentón guajillo,Chile guajillo,0,80509,0 -21922,50405622,Pimentón guerro,Chile güero,0,80509,0 -21923,50405623,Pimentón habanero,Chile habanero,0,80509,0 -21924,50405624,Pimentón cera de hungria,Pimentón húngaro,0,80509,0 -21925,50405625,Pimentón jalapeño,"Chile cuaresmeño, Chile jalapeño",0,80509,0 -21926,50405626,Pimentón picante largo,Chile cola de rata,0,80509,0 -21927,50405627,Pimentón marasol,Chile mirasol,0,80509,0 -21928,50405628,Pimentón pasilla,Chile pasilla,0,80509,0 -21929,50405630,Pimentón pequin,,0,80509,0 -21930,50405631,Pimentón pimiento,Chile piquín,0,80509,0 -21931,50405632,Pimentón poblano,Pimiento morrón,0,80509,0 -21932,50405633,Pimentón scotch bonnet,Chile poblano,0,80509,0 -21933,50405634,Pimentón serrano,Pimientos Bonney o rojos Caribe,0,80509,0 -21934,50405635,Pimentón tabasco,"Chile serrano, Chile verde",0,80509,0 -21935,50405636,Pimentón tai,Chile tabasco,0,80509,0 -21936,50405637,Pimentón tepin,Chile ojo de pájaro,0,80509,0 -21937,50405638,Pimentón chili amaucho,Chile mosquito o catarina,0,80509,0 -21938,50405639,Pimentón chili mochero,Chile arnaucho,0,80509,0 -21939,50405640,Pimentón chili limo,Chile mochero,0,80509,0 -21940,50405700,Papas,Chile limo,0,80509,0 -21941,50405701,Papa blanca larga,,0,80509,0 -21942,50405702,Papa redonda blanca,,0,80509,0 -21943,50405703,Papa redonda roja,Papa de cambray,0,80509,0 -21944,50405704,Papa russet,,0,80509,0 -21945,50405705,Papa morada,,0,80509,0 -21946,50405706,Papa amarilla,,0,80509,0 -21947,50405707,Papa nueva,,0,80509,0 -21948,50405708,Papa especial,,0,80509,0 -21949,50405709,Papa huayro,,0,80509,0 -21950,50405710,Papa peruanita,,0,80509,0 -21951,50405711,Papa yunggay,,0,80509,0 -21952,50405900,Vegetales marinos,,0,80509,0 -21953,50405901,Agar-agar,,0,80509,0 -21954,50405902,Arame,,0,80509,0 -21955,50405903,Alga dulse,,0,80509,0 -21956,50405904,Habichuelas vert de mer,,0,80509,0 -21957,50405905,Hijiki,,0,80509,0 -21958,50405906,Musgo irlandes,,0,80509,0 -21959,50405907,Quelpo,,0,80509,0 -21960,50405908,Laver,,0,80509,0 -21961,50405909,Nori,,0,80509,0 -21962,50405910,Alga roja,,0,80509,0 -21963,50405911,Kale de mar,,0,80509,0 -21964,50405913,Algas de mar,,0,80509,0 -21965,50405914,Spirulina,,0,80509,0 -21966,50405915,Susabi nuri,,0,80509,0 -21967,50405916,Wakame,,0,80509,0 -21968,50406200,Espinacas,,0,80509,0 -21969,50406201,Espinaca americana,,0,80509,0 -21970,50406202,Espinaca bloomsdale,,0,80509,0 -21971,50406203,Espinaca invierno gigante,,0,80509,0 -21972,50406204,Espinaca horenso,,0,80509,0 -21973,50406205,Espinaca cuarto de cordero,,0,80509,0 -21974,50406206,Espinaca malabar,,0,80509,0 -21975,50406207,Espinaca medania,,0,80509,0 -21976,50406208,Espinaca orach,,0,80509,0 -21977,50406209,Espinaca savoy,,0,80509,0 -21978,50406210,Espinaca hoja de sigma,,0,80509,0 -21979,50406211,Espinaca espacio,,0,80509,0 -21980,50406212,Espinaca trinidad,,0,80509,0 -21981,50406213,Espinaca salvaje,,0,80509,0 -21982,50406224,Espinaca nueva zelandia,,0,80509,0 -21983,50406225,Espinaca iceplant,,0,80509,0 -21984,50406300,Calabazas,,0,80509,0 -21985,50406301,Calabaza boston,,0,80509,0 -21986,50406302,Calabaza buttemut,,0,80509,0 -21987,50406303,Calabaza costata romanesca,,0,80509,0 -21988,50406304,Calabaza crookneck,,0,80509,0 -21989,50406305,Calabaza cucuzza,,0,80509,0 -21990,50406306,Calabaza delicata,,0,80509,0 -21991,50406307,Calabaza delicioso,,0,80509,0 -21992,50406308,Calabaza verano dorado cuello torsido,,0,80509,0 -21993,50406309,Calabaza prolífico temprano cuello recto,,0,80509,0 -21994,50406310,Calabaza oro,,0,80509,0 -21995,50406311,Calabaza jack pequeño,,0,80509,0 -21996,50406312,Calabaza campo de kentucky,,0,80509,0 -21997,50406313,Calabaza marrow,,0,80509,0 -21998,50406314,Calabaza medio oriente,,0,80509,0 -21999,50406315,Calabaza miniatura,,0,80509,0 -22000,50406316,Calabaza orangetti,,0,80509,0 -22001,50406317,Calabaza pattypan,,0,80509,0 -22002,50406318,Calabaza rondini,,0,80509,0 -22003,50406319,Calabaza redonda,,0,80509,0 -22004,50406320,Calabaza espagueti,,0,80509,0 -22005,50406321,Calabaza estripeti,,0,80509,0 -22006,50406322,Calabaza molde dulce,,0,80509,0 -22007,50406323,Calabaza empanada dulce,,0,80509,0 -22008,50406324,Calabaza premio triple,,0,80509,0 -22009,50406325,Calabaza waltham butternut,,0,80509,0 -22010,50406326,Calabaza arbusto scallop amarillo,,0,80509,0 -22011,50406327,Calabaza cuello recto amarillo,,0,80509,0 -22012,50406328,Calabaza zafiro,,0,80509,0 -22013,50406329,Calabaza zucchini,,0,80509,0 -22014,50406400,Batatas,Camotes,0,80509,0 -22015,50406401,Batata beauregard,Camote beauregard,0,80509,0 -22016,50406402,Batata centennial,Camote centennial,0,80509,0 -22017,50406403,Batata diane,Camote diane,0,80509,0 -22018,50406404,Batata dulce garnet,Camote dulce garnet,0,80509,0 -22019,50406405,Batata georgia dulce rojo,Camote georgia dulce rojo,0,80509,0 -22020,50406406,Batata dorado,Camote dorado,0,80509,0 -22021,50406407,Batata hanna,Camote hanna,0,80509,0 -22022,50406408,Batata japonés,Camote japonés,0,80509,0 -22023,50406409,Batata jersey,Camote jersey,0,80509,0 -22024,50406410,Batata joya,Camote joya,0,80509,0 -22025,50406411,Batata meryland roja,Camote meryland roja,0,80509,0 -22026,50406412,Batata nema dorado,Camote nema dorado,0,80509,0 -22027,50406413,Batata o´henry,Camote o´henry,0,80509,0 -22028,50406414,Batata okinawan,Camote okinawan,0,80509,0 -22029,50406415,Batata naranja,Camote naranja,0,80509,0 -22030,50406416,Batata oriental,Camote oriental,0,80509,0 -22031,50406417,Batata jersey rojo,Camote jersey rojo,0,80509,0 -22032,50406418,Batata mar rojo,Camote mar rojo,0,80509,0 -22033,50406419,Batata brillo rojo,Camote brillo rojo,0,80509,0 -22034,50406420,Batata jersey amarillo,Camote jersey amarillo,0,80509,0 -22035,50406421,Batata morado,Camote morado,0,80509,0 -22036,50406500,Tomates,Jitomates,0,80509,0 -22037,50406501,Tomates ailsa craig,Jitomate ailsa craig,0,80509,0 -22038,50406502,Tomates alicante,Jitomate alicante,0,80509,0 -22039,50406503,Tomates ciruela negra,Jitomate ciruela negra,0,80509,0 -22040,50406504,Tomates vino de brandy,Jitomate brandy,0,80509,0 -22041,50406505,Tomates bella cereza,Jitomate cherry belle,0,80509,0 -22042,50406506,Tomates cereza,Jitomate cherry,0,80509,0 -22043,50406507,Tomates delicioso,Jitomate delicioso,0,80509,0 -22044,50406508,Tomates dombito,Jitomate dombito,0,80509,0 -22045,50406509,Tomates delicia del jardín,Jitomate delicia del jardín,0,80509,0 -22046,50406510,Tomates uva,Jitomate uva,0,80509,0 -22047,50406511,Tomates verde,Tomate verde,0,80509,0 -22048,50406512,Tomates super marmande,Jitomate super marmande,0,80509,0 -22049,50406513,Tomates rayas naturales marvel,Jitomate rayas naturales marvel,0,80509,0 -22050,50406514,Tomates minibel,Jitomate minibel,0,80509,0 -22051,50406515,Tomates oaxacan,Jitomate oaxaca,0,80509,0 -22052,50406516,Tomates alerta roja,Jitomate alerta roja,0,80509,0 -22053,50406517,Tomates roma vf,Jitomate roma vf,0,80509,0 -22054,50406518,Tomates marzano zan,Jitomate san marzano,0,80509,0 -22055,50406519,Tomates shirley,Jitomate shirley,0,80509,0 -22056,50406520,Tomates siberia,Jitomate siberia,0,80509,0 -22057,50406521,Tomates super beefsteak,Jitomate super beefsteak,0,80509,0 -22058,50406522,Tomates tigerella,Jitomate tigerella,0,80509,0 -22059,50406523,Tomates tiny tim,Jitomate tiny tim,0,80509,0 -22060,50406524,Tomates tumbler,Jitomate tumbler,0,80509,0 -22061,50406525,Tomates cocktail amarillo,,0,80509,0 -22062,50406526,Tomates forma de pera amarillo,,0,80509,0 -22063,50406527,Tomates perfección amarilla,,0,80509,0 -22064,50406700,Auyamas,Calabazas gigantes,0,80509,0 -22065,50406701,Calabaza acorn,Calabaza gigante acorn,0,80509,0 -22066,50406702,Calabaza gigante atlántico,Calabaza gigante atlántico,0,80509,0 -22067,50406703,Calabaza banana rosado,Calabaza gigante banana rosado,0,80509,0 -22068,50406704,Calabaza max grande,Calabaza gigante max,0,80509,0 -22069,50406705,Calabaza,Calabaza de castilla,0,80509,0 -22070,50406706,Calabaza carnaval,Calabaza gigante carnaval,0,80509,0 -22071,50406707,Auyama queso,Calabaza gigante queso,0,80509,0 -22072,50406708,Calabaza príncipe coronado,Calabaza gigante príncipe coronado,0,80509,0 -22073,50406709,Calabaza curcibita,Calabaza gigante curcibita,0,80509,0 -22074,50406710,Calabaza cushaw,Calabaza gigante cushaw,0,80509,0 -22075,50406711,Calabaza gigante auyama,,0,80509,0 -22076,50406712,Calabaza hubbard,Calabaza gigante hubbard,0,80509,0 -22077,50406713,Calabaza jerrahdale,Calabaza gigante jerrahdale,0,80509,0 -22078,50406714,Calabaza kabocha,Calabaza gigante kabocha,0,80509,0 -22079,50406715,Calabaza queensland azul,Calabaza gigante queensland azul,0,80509,0 -22080,50406716,Calabaza rouge vif détampes,Calabaza gigante rouge vif détampes,0,80509,0 -22081,50406717,Calabaza turban turco,Calabaza gigante turban turco,0,80509,0 -22082,50406718,Calabaza valenciano,Calabaza gigante valenciano,0,80509,0 -22083,50406719,Calabaza warted hubbard,Calabaza gigante warted hubbard,0,80509,0 -22084,50406720,Auyama whangaparoa,Calabaza gigante whangaparoa,0,80509,0 -22085,50406721,Auyama china,Calabaza gigante china,0,80509,0 -22086,50406722,Auyama loche,Calabaza gigante loche,0,80509,0 -22087,50406723,Auyama macre,Calabaza gigante macre,0,80509,0 -22088,50407000,Vegetales Nominant,"Vegetales predominantes, Nopal",0,80509,0 -22089,50407001,Alfalfa,,0,80509,0 -22090,50407002,Hojas de aloe,"Hoja de sábila, Penca de sábila",0,80509,0 -22091,50407003,Apio,,0,80509,0 -22092,50407004,Arrurruz,,0,80509,0 -22093,50407005,Punta de flecha,Sagittaria,0,80509,0 -22094,50407006,Rúcula,,0,80509,0 -22095,50407007,Arum,Taro,0,80509,0 -22096,50407008,Raíz de bambú,,0,80509,0 -22097,50407009,Hojas de platano,,0,80509,0 -22098,50407010,Batatas,,0,80509,0 -22099,50407011,Brotes de frijol,,0,80509,0 -22100,50407012,Top de remolacha,Betabel,0,80509,0 -22101,50407013,Melón agrio,Balsamina,0,80509,0 -22102,50407014,Bayas de alcaparra,,0,80509,0 -22103,50407015,Carob,Algarrobo,0,80509,0 -22104,50407016,Cha-om,,0,80509,0 -22105,50407017,Chayote o guatila,,0,80509,0 -22106,50407018,Garbanzo,,0,80509,0 -22107,50407019,Verde de crisantemos,,0,80509,0 -22108,50407020,Verde de diente de león,,0,80509,0 -22109,50407021,Diente de león,,0,80509,0 -22110,50407022,Dasheen,,0,80509,0 -22111,50407023,Puntas de alverja,,0,80509,0 -22112,50407024,Diakon,Rábano,0,80509,0 -22113,50407025,Donqua,Ginseng hembra,0,80509,0 -22114,50407026,Helecho,Brotes de helecho,0,80509,0 -22115,50407027,Gai choi,Mostaza castaña,0,80509,0 -22116,50407028,Gailon,Brécol chino,0,80509,0 -22117,50407029,Galanga,Jengibre azul,0,80509,0 -22118,50407030,Raíz de jengibre,,0,80509,0 -22119,50407031,Gobo,Garduña,0,80509,0 -22120,50407032,Brote de lúpulo,,0,80509,0 -22121,50407033,Rabano blanco,Rábano de caballo,0,80509,0 -22122,50407034,Jicama,,0,80509,0 -22123,50407035,Kuduz,Kudzu,0,80509,0 -22124,50407036,Bulbo de margarita,Bulbo de lili chino,0,80509,0 -22125,50407037,Linkok,,0,80509,0 -22126,50407038,Lo bok,,0,80509,0 -22127,50407039,Frijol largo,Fríjol serpiente,0,80509,0 -22128,50407040,Raíz de lotus,Rosa del Nilo,0,80509,0 -22129,50407041,Hojas de maguey,"Hoja de agave, Penca de maguey",0,80509,0 -22130,50407042,Mallows,Malva,0,80509,0 -22131,50407043,Sapote mamey,Mamey,0,80509,0 -22132,50407044,Moap,,0,80509,0 -22133,50407045,Moo,,0,80509,0 -22134,50407046,Moqua,,0,80509,0 -22135,50407047,Opos,,0,80509,0 -22136,50407048,Corazon de palma,Palmito,0,80509,0 -22137,50407049,Paprika,,0,80509,0 -22138,50407050,Purslane,Verdolaga,0,80509,0 -22139,50407051,Raddichios,Achicoria morada,0,80509,0 -22140,50407052,Sinquas,Zapallo,0,80509,0 -22141,50407053,Frijol de soya,,0,80509,0 -22142,50407054,Spoonwart,Hierba del escorbuto,0,80509,0 -22143,50407055,Uva tassele hyacinth,,0,80509,0 -22144,50407056,Taro,Tales,0,80509,0 -22145,50407057,Hoja de taro,,0,80509,0 -22146,50407058,Tallo de taro,,0,80509,0 -22147,50407059,Tapeguaje,,0,80509,0 -22148,50407060,Verde tierno,,0,80509,0 -22149,50407061,Tindora,,0,80509,0 -22150,50407062,Árbol de cebolla,Cebollas egipcias,0,80509,0 -22151,50407063,Udo,,0,80509,0 -22152,50407064,Agua castaño,Castaña de agua china,0,80509,0 -22153,50407065,Agua espinaca,Akankong,0,80509,0 -22154,50407066,Yumpi,Mapuey,0,80509,0 -22155,50407067,Yautia,,0,80509,0 -22156,50407068,Yu choy,,0,80509,0 -22157,50407070,Yuca,,0,80509,0 -22158,50407071,Caigua,Achocha,0,80509,0 -22159,50407100,Alverjas dulces,Chícharos,0,80509,0 -22160,50407101,Alverja biquini,Chícharo biquini,0,80509,0 -22161,50407102,Alverja cavalier,Chícharo cavalier,0,80509,0 -22162,50407103,Alverja margarita,Chícharo margarita,0,80509,0 -22163,50407104,Alverja darfon,Chícharo darfon,0,80509,0 -22164,50407105,"Alverja ""early onward""","Chícharo ""early onward""",0,80509,0 -22165,50407106,Alverja primer feltham,Chícharo primer feltham,0,80509,0 -22166,50407107,Alverja verde hurst shaft,Chícharo verde hurst shaft,0,80509,0 -22167,50407108,Guisante oregón sugar pod,Chícharo oregón sugar pod,0,80509,0 -22168,50407109,Alverja príncipe alberto,Chícharo príncipe alberto,0,80509,0 -22169,50407110,Alverja reuzensuiker,Chícharo reuzensuiker,0,80509,0 -22170,50407200,Tubérculos,,0,80509,0 -22171,50407201,Arracacha,,0,80509,0 -22172,50407202,Maca,,0,80509,0 -22173,50407203,Oca,,0,80509,0 -22174,50407204,Olluco,,0,80509,0 -22175,50407205,Mashua,,0,80509,0 -22176,50411800,Fríjoles orgánicos,,0,80509,0 -22177,50411801,Frijoles anasazi™,,0,80509,0 -22178,50411802,Frijoles appaloosa orgánicos,,0,80509,0 -22179,50411803,Frijoles azuki orgánicos,,0,80509,0 -22180,50411804,Frijoles barlotti orgánicos,,0,80509,0 -22181,50411805,Frijoles appaloosa negra orgánicos,,0,80509,0 -22182,50411806,Frijoles negros orgánicos,,0,80509,0 -22183,50411807,Frijoles gran negros orgánicos,,0,80509,0 -22184,50411808,Frijoles shackamaxon negro orgánicos,,0,80509,0 -22185,50411809,Frijoles ojo negro orgánicos,,0,80509,0 -22186,50411810,Frijoles bobby orgánicos,,0,80509,0 -22187,50411811,Frijoles bolita orgánicos,,0,80509,0 -22188,50411812,Frijoles esposa perezosa carmelita orgánicos,,0,80509,0 -22189,50411813,Frijoles calipso orgánicos,,0,80509,0 -22190,50411814,Frijoles canelini orgánicos,,0,80509,0 -22191,50411815,Frijoles castor orgánicos,,0,80509,0 -22192,50411816,Frijoles amarillo chino orgánicos,,0,80509,0 -22193,50411817,Frijoles lengua de dragón orgánicos,,0,80509,0 -22194,50411818,Frijoles soldado europeo orgánicos,,0,80509,0 -22195,50411819,Frijoles fava orgánicos,,0,80509,0 -22196,50411820,Frijoles flageolet orgánicos,,0,80509,0 -22197,50411821,Frijoles horticultura francesa orgánicos,,0,80509,0 -22198,50411822,Frijoles marina francesa orgánicos,,0,80509,0 -22199,50411823,Frijoles coco gigante blanco orgánicos,,0,80509,0 -22200,50411824,Frijoles verdes orgánicos,,0,80509,0 -22201,50411825,Frijoles romano verde orgánicos,,0,80509,0 -22202,50411826,Frijoles guar gum orgánicos,,0,80509,0 -22203,50411827,Frijoles haricot orgánicos,,0,80509,0 -22204,50411828,Frijoles hyacinth orgánicos,,0,80509,0 -22205,50411829,Frijoles tipo italiano orgánicos,,0,80509,0 -22206,50411830,Frijoles asombro jackson orgánicos,,0,80509,0 -22207,50411831,Frijoles ganado jacobs orgánicos,,0,80509,0 -22208,50411832,Frijoles asombro kentucky orgánicos,,0,80509,0 -22209,50411833,Frijoles riñón orgánicos,,0,80509,0 -22210,50411834,Frijoles lima orgánicos,,0,80509,0 -22211,50411835,Frijoles madera orgánicos,,0,80509,0 -22212,50411836,Frijoles medula orgánicos,,0,80509,0 -22213,50411837,Frijoles mat orgánicos,,0,80509,0 -22214,50411838,Frijoles monstoler ganzo salvaje orgánicos,,0,80509,0 -22215,50411839,Frijoles mortgage lifter orgánicos,,0,80509,0 -22216,50411840,Frijoles polilla orgánicos,,0,80509,0 -22217,50411841,Frijoles mung orgánicos,,0,80509,0 -22218,50411842,Frijoles munsi wolf orgánicos,,0,80509,0 -22219,50411843,Frijoles nuna orgánicos,,0,80509,0 -22220,50411844,Frijoles pinto orgánicos,,0,80509,0 -22221,50411845,Frijoles runner orgánicos,,0,80509,0 -22222,50411846,Frijoles string orgánicos,"Ayecote orgánico, Fríjol gordo orgánico",0,80509,0 -22223,50411847,Frijoles habichuelas orgánicos,"Ejote orgánico, Fríjol tierno orgánico",0,80509,0 -22224,50411848,Frijoles tamarind orgánicos,,0,80509,0 -22225,50411849,Frijoles tonka orgánicos,,0,80509,0 -22226,50411850,Frijoles cera orgánicos,,0,80509,0 -22227,50411851,Frijoles winged orgánicos,,0,80509,0 -22228,50411852,Frijoles largos orgánicos,,0,80509,0 -22229,50411900,Remolachas orgánicas,Betabeles orgánicos,0,80509,0 -22230,50411901,Remolacha acción orgánicos,Betabel acción orgánicos,0,80509,0 -22231,50411902,Remolacha albina vereduna orgánica,Betabel albina vereduna orgánico,0,80509,0 -22232,50411903,Remolacha barababiotela de chiggia orgánica,Betabel barababiotela de chiggia orgánico,0,80509,0 -22233,50411904,Remolacha boltardy orgánica,Betabel boltardy orgánico,0,80509,0 -22234,50411905,Remolacha bonel orgánica,Betabel bonel orgánico,0,80509,0 -22235,50411906,Remolacha burpees dorado orgánica,Betabel burpees dorado orgánico,0,80509,0 -22236,50411907,Remolacha cheltenham tapa verde orgánica,Betabel cheltenham tapa verde orgánico,0,80509,0 -22237,50411908,Remolacha cheltenham mono orgánica,Betabel cheltenham mono orgánico,0,80509,0 -22238,50411909,Remolacha chioggia orgánica,Betabel chioggia orgánico,0,80509,0 -22239,50411910,Remolacha cilindra orgánica,Betabel cilindra orgánico,0,80509,0 -22240,50411911,Remolacha dégypte orgánica,Betabel dégypte orgánico,0,80509,0 -22241,50411912,Remolacha detroit 2 rojo oscuro orgánica,Betabel detroit 2 rojo oscuro orgánico,0,80509,0 -22242,50411913,Remolacha detroit 2 bala chiquita orgánica,Betabel detroit 2 bala chiquita orgánico,0,80509,0 -22243,50411914,Remolacha egipcia plana orgánica,Betabel egipcia plana orgánico,0,80509,0 -22244,50411915,Remolacha raíz de nabo egipcio orgánica,Betabel raíz de nabo egipcio orgánico,0,80509,0 -22245,50411916,Remolacha fomanova orgánica,Betabel fomanova orgánico,0,80509,0 -22246,50411917,Remolacha forono orgánica,Betabel forono orgánico,0,80509,0 -22247,50411918,Remolacha monaco orgánica,Betabel mónaco orgánico,0,80509,0 -22248,50411919,Remolacha monograma orgánica,Betabel monograma orgánico,0,80509,0 -22249,50411920,Remolacha pronto orgánica,Betabel pronto orgánico,0,80509,0 -22250,50411921,Remolacha regalía orgánica,Betabel regalía orgánico,0,80509,0 -22251,50411922,Remolacha dulce orgánica,Betabel dulce orgánico,0,80509,0 -22252,50412000,Brócolis orgánicas,,0,80509,0 -22253,50412001,Brócoli orgánico,,0,80509,0 -22254,50412002,Brócoli romanesco orgánico,,0,80509,0 -22255,50412003,Brócoli raab orgánico,,0,80509,0 -22256,50412004,Brócoli chino orgánico,,0,80509,0 -22257,50412100,Repollitos de Bruselas orgánicas,Coles de Bruselas orgánicas,0,80509,0 -22258,50412101,Repollitas de bruselas citade orgánicas,Coles de Bruselas citade orgánicas,0,80509,0 -22259,50412102,Repollitas de bruselas falstaff orgánicas,Coles de Bruselas falstaff orgánicas,0,80509,0 -22260,50412103,Repollitas de bruselas oliver orgánicas,Coles de Bruselas oliver orgánicas,0,80509,0 -22261,50412104,Repollitas de bruselas peer gynt orgánicas,Coles de Bruselas peer gynt orgánicas,0,80509,0 -22262,50412105,Repollitas de bruselas rampart orgánicas,Coles de Bruselas rampart orgánicas,0,80509,0 -22263,50412106,Repollitas de bruselas rubine orgánicas,Coles de Bruselas rubine orgánicas,0,80509,0 -22264,50412107,Repollitas de bruselas widgeon orgánicas,Coles de Bruselas widgeon orgánicas,0,80509,0 -22265,50412800,Acelgas orgánicas,,0,80509,0 -22266,50412801,Acelga fordhook gigante orgánica,,0,80509,0 -22267,50412802,Acelga gigantelucullus orgánica,,0,80509,0 -22268,50412803,Acelga espinaca perpetua orgánica,,0,80509,0 -22269,50412804,Acelga ruibarbo perpetua orgánica,,0,80509,0 -22270,50412805,Acelga suizo orgánica,,0,80509,0 -22271,50412806,Acelga volcan orgánica,,0,80509,0 -22272,50412807,Acelga rey blanco orgánica,,0,80509,0 -22273,50413100,Cebollines orgánicos,,0,80509,0 -22274,50413101,Cebollín chino orgánico,,0,80509,0 -22275,50413300,Cocombros orgánicos,Pepinos cohombro orgánicos,0,80509,0 -22276,50413301,Cocombro arena orgánico,Pepino cohombro arena orgánico,0,80509,0 -22277,50413302,Cocombro americano orgánico,Pepino cohombro americano orgánico,0,80509,0 -22278,50413303,Cocombro atena orgánico,Pepino cohombro atena orgánico,0,80509,0 -22279,50413304,Cocombro blanco lungo de parigi orgánico,Pepino cohombro blanco lungo de parigi orgánico,0,80509,0 -22280,50413305,Cocombro verde burpless buen sabor orgánico,Pepino cohombro verde burpless buen sabor orgánico,0,80509,0 -22281,50413306,Cocombro chicago pickling orgánico,Pepino cohombro chicago pickling orgánico,0,80509,0 -22282,50413307,Cocombro manzana cristal orgánico,Pepino cohombro manzana cristal orgánico,0,80509,0 -22283,50413308,Cocombro limón cristal orgánico,Pepino cohombro limón cristal orgánico,0,80509,0 -22284,50413309,Cocombro danimas orgánico,Pepino cohombro danimas orgánico,0,80509,0 -22285,50413310,Cocombro gherkin orgánico,Pepino cohombro gherkin orgánico,0,80509,0 -22286,50413311,Cocombro hokus orgánico,Pepino cohombro hokus orgánico,0,80509,0 -22287,50413312,Cocombro japonés orgánico,Pepino cohombro japonés orgánico,0,80509,0 -22288,50413313,Cocombro karela orgánico,Pepino cohombro karela orgánico,0,80509,0 -22289,50413314,Cocombro korila orgánico,Pepino cohombro korila orgánico,0,80509,0 -22290,50413315,Cocombro verde largo mejorado orgánico,Pepino cohombro verde largo mejorado orgánico,0,80509,0 -22291,50413316,Cocombro mas mercado orgánico,Pepino cohombro mas mercado orgánico,0,80509,0 -22292,50413317,Cocombro enano orgánico,Pepino cohombro enano orgánico,0,80509,0 -22293,50413318,Cocombro pickling nacional orgánico,Pepino cohombro pickling nacional orgánico,0,80509,0 -22294,50413319,Cocombro persa orgánico,Pepino cohombro persa orgánico,0,80509,0 -22295,50413320,Cocombro telégrafo orgánico,Pepino cohombro telégrafo orgánico,0,80509,0 -22296,50413321,Cocombro telégrafo mejorado orgánico,Pepino cohombro telégrafo mejorado orgánico,0,80509,0 -22297,50413322,Cocombro vert de massy cornichon orgánico,Pepino cohombro vert de massy cornichon orgánico,0,80509,0 -22298,50413323,Cocombro yamato orgánico,Pepino cohombro yamato orgánico,0,80509,0 -22299,50413400,Berenjenas orgánicas,,0,80509,0 -22300,50413401,Berenjena bambino orgánica,,0,80509,0 -22301,50413402,Berenjena belleza negra orgánica,,0,80509,0 -22302,50413403,Berenjena negra enorme orgánica,,0,80509,0 -22303,50413404,Berenjena chino orgánica,,0,80509,0 -22304,50413405,Berenjena huevo de pascua orgánica,,0,80509,0 -22305,50413406,Berenjena filipino orgánica,,0,80509,0 -22306,50413407,Berenjena mercado de florida orgánica,,0,80509,0 -22307,50413408,Berenjena india orgánica,,0,80509,0 -22308,50413409,Berenjena italiano orgánica,,0,80509,0 -22309,50413410,Berenjena japonés orgánica,,0,80509,0 -22310,50413411,Berenjena morado largo orgánica,,0,80509,0 -22311,50413412,Berenjena rayas larga orgánica,,0,80509,0 -22312,50413413,Berenjena mercado de dinero orgánica,,0,80509,0 -22313,50413414,Berenjena ova orgánica,,0,80509,0 -22314,50413415,Berenjena arveja orgánica,,0,80509,0 -22315,50413416,Berenjena tom bajito orgánica,,0,80509,0 -22316,50413417,Berenjena siciliano orgánica,,0,80509,0 -22317,50413418,Berenjena tailandés orgánica,,0,80509,0 -22318,50413419,Berenjena violeta de florencia orgánica,,0,80509,0 -22319,50413420,Berenjena blanco orgánica,,0,80509,0 -22320,50413800,Calabazas orgánicas,,0,80509,0 -22321,50413801,Petula orgánica,Luffa orgánica,0,80509,0 -22322,50413802,Melon amargo orgánico,,0,80509,0 -22323,50413803,Botella calabaza orgánica,Mate orgánico,0,80509,0 -22324,50413804,Calabash gounds orgánico,Calabaza de peregrino orgánica,0,80509,0 -22325,50413805,Melón peludo orgánico,,0,80509,0 -22326,50413806,Calabaza amarga orgánica,,0,80509,0 -22327,50413807,Loofah suave orgánico,Zapatillo suave orgánico,0,80509,0 -22328,50413808,Serpiente de calabaza orgánica,Chichinda orgánica,0,80509,0 -22329,50413809,Calabaza larga amarga orgánica,,0,80509,0 -22330,50413810,Calabaza tinda orgánica,,0,80509,0 -22331,50413811,Calabaza tindoori orgánica,,0,80509,0 -22332,50413900,Guisantes orgánicos,Chícharos orgánicos,0,80509,0 -22333,50413901,Guisante chino orgánica,Chícharo chino orgánico,0,80509,0 -22334,50413902,Guisante inglés orgánica,Chícharo inglés orgánico,0,80509,0 -22335,50413903,Guisante de jardín orgánica,Chícharo de jardín orgánico,0,80509,0 -22336,50413904,Guisante nieve orgánica,Chícharo nieve orgánico,0,80509,0 -22337,50413906,Guisante snap dulce orgánica,Chícharo snap dulce orgánico,0,80509,0 -22338,50414000,Hierbas orgánicas,"Perejil orgánico, Yerbabuena o hierbabuena orgánica",0,80509,0 -22339,50414001,Albahaca orgánica,,0,80509,0 -22340,50414002,Laurel orgánico,,0,80509,0 -22341,50414003,Borraja orgánica,,0,80509,0 -22342,50414004,Alcaravea orgánica,,0,80509,0 -22343,50414005,Perifollo orgánico,,0,80509,0 -22344,50414006,Cilantro orgánico,,0,80509,0 -22345,50414007,Chipolinos orgánicos,,0,80509,0 -22346,50414008,Hoja de curry orgánica,,0,80509,0 -22347,50414009,Eneldo orgánico,,0,80509,0 -22348,50414010,Epazote orgánico,,0,80509,0 -22349,50414011,Fenogreco orgánico,,0,80509,0 -22350,50414012,Limonaria orgánica,Té de limón orgánico,0,80509,0 -22351,50414013,Mejorana orgánica,,0,80509,0 -22352,50414014,Menta orgánica,,0,80509,0 -22353,50414015,Orégano orgánico,,0,80509,0 -22354,50414016,Pápalo orgánico,,0,80509,0 -22355,50414017,Pápalo delgado orgánico,,0,80509,0 -22356,50414018,Perilla orgánica,Albahaca japonesa orgánica,0,80509,0 -22357,50414019,Recao orgánico,,0,80509,0 -22358,50414020,Romero orgánico,,0,80509,0 -22359,50414021,Salvia orgánica,,0,80509,0 -22360,50414022,Salsifí orgánica,Barba cabruna orgánica,0,80509,0 -22361,50414023,Satureja orgánica,Ajedreas orgánica,0,80509,0 -22362,50414024,Estragón orgánico,Tarragón orgánico,0,80509,0 -22363,50414025,Tomillo orgánico,,0,80509,0 -22364,50414026,Tumeric orgánico,Cúrcuma orgánica,0,80509,0 -22365,50414027,Verdolaga orgánica,,0,80509,0 -22366,50414500,Lechugas orgánicas,,0,80509,0 -22367,50414501,Lechuga bibb orgánica,,0,80509,0 -22368,50414502,Lechuga boston orgánica,,0,80509,0 -22369,50414503,Lechuga frisee orgánica,,0,80509,0 -22370,50414504,Lechuga rosa lola orgánica,,0,80509,0 -22371,50414505,Lechuga mixta masculina orgánica,,0,80509,0 -22372,50414506,Lechuga mizuna orgánica,,0,80509,0 -22373,50414507,Lechuga hoja roja orgánica,,0,80509,0 -22374,50414508,Lechuga oja roja roble orgánica,,0,80509,0 -22375,50414509,Lechuga romana ruby orgánica,,0,80509,0 -22376,50414510,Lechuga romana bebé roja orgánica,,0,80509,0 -22377,50414511,Lechuga cabeza de mantequilla orgánica,,0,80509,0 -22378,50414512,Lechuga china orgánica,Lechuga orejona orgánica,0,80509,0 -22379,50414513,Lechuga cabeza tostada orgánica,,0,80509,0 -22380,50414514,Lechuga hoja verde orgánica,,0,80509,0 -22381,50414515,Lechuga iceberg orgánica,,0,80509,0 -22382,50414516,Lechuga cordero orgánica,,0,80509,0 -22383,50414517,Lechuga hoja suelta orgánica,,0,80509,0 -22384,50414518,Lechuga mache orgánica,,0,80509,0 -22385,50414519,Lechuga boston roja orgánica,,0,80509,0 -22386,50414520,Lechuga cabeza roja orgánica,,0,80509,0 -22387,50414521,Lechuga romana orgánica,,0,80509,0 -22388,50414522,Lechuga mostza roja rusa orgánica,,0,80509,0 -22389,50414523,Lechuga tatsoi orgánica,,0,80509,0 -22390,50414700,Hongos o champiñones orgánicos,,0,80509,0 -22391,50414701,Champiñón trompeta negra orgánico,,0,80509,0 -22392,50414702,Champiñón carmelito orgánico,,0,80509,0 -22393,50414703,Champiñón orgánico,,0,80509,0 -22394,50414704,Champiñón chanterelle orgánico,,0,80509,0 -22395,50414705,Champiñón cremini orgánico,,0,80509,0 -22396,50414706,Champiñón enoki orgánico,,0,80509,0 -22397,50414707,Champiñón puercoespín orgánico,,0,80509,0 -22398,50414708,Champiñón gallina del bosque orgánico,,0,80509,0 -22399,50414709,Champiñón langosta orgánico,,0,80509,0 -22400,50414710,Champiñón morels orgánico,,0,80509,0 -22401,50414711,Champiñón ostra orgánico,,0,80509,0 -22402,50414712,Champiñón pleurotus orgánico,,0,80509,0 -22403,50414713,Champiñón pompom orgánico,,0,80509,0 -22404,50414714,Champiñón porcieni orgánico,,0,80509,0 -22405,50414715,Champiñón portobello orgánico,,0,80509,0 -22406,50414716,Champiñón shitake orgánico,,0,80509,0 -22407,50414717,Champiñón shimeji orgánico,,0,80509,0 -22408,50414718,Champiñón san jorge orgánico,,0,80509,0 -22409,50414719,Champiñón blanco orgánico,,0,80509,0 -22410,50414720,Champiñón trompeta blanca orgánico,,0,80509,0 -22411,50414721,Champiñón woodear orgánico,,0,80509,0 -22412,50415100,Okras orgánicas,,0,80509,0 -22413,50415101,Okra artista orgánica,,0,80509,0 -22414,50415102,Okra vinotinto orgánica,,0,80509,0 -22415,50415103,Okra sin columna clemson orgánica,,0,80509,0 -22416,50415104,Okra verde enano vaina larga orgánica,,0,80509,0 -22417,50415105,Okra mamut sin columna orgánica,,0,80509,0 -22418,50415106,Okra terciopelo rojo orgánica,,0,80509,0 -22419,50415107,Okra estrella de david reliquia orgánica,,0,80509,0 -22420,50415200,Cebollas orgánicas,,0,80509,0 -22421,50415201,Cebolla albion orgánica,,0,80509,0 -22422,50415202,Cebolla alisia craig orgánica,,0,80509,0 -22423,50415203,Cebolla hirviendo orgánica,,0,80509,0 -22424,50415204,Cebolla bufalo orgánica,,0,80509,0 -22425,50415205,Cebolla bulbo orgánica,,0,80509,0 -22426,50415206,Cebolla cremosa orgánica,,0,80509,0 -22427,50415207,Cebolla amarillo expreso o – x orgánica,,0,80509,0 -22428,50415208,Cebolla kelsae orgánica,,0,80509,0 -22429,50415209,Cebolla gigante marshal globo fen orgánica,,0,80509,0 -22430,50415210,Cebolla perla orgánica,,0,80509,0 -22431,50415211,Cebolla baron rojo orgánica,,0,80509,0 -22432,50415212,Cebolla roja orgánica,,0,80509,0 -22433,50415213,Cebolla rijnsberger orgánica,,0,80509,0 -22434,50415214,Cebolla senshyu semi – globo amarillo orgánica,,0,80509,0 -22435,50415215,Cebolla sturon orgánica,,0,80509,0 -22436,50415216,Cebolla stuttgarter orgánica,,0,80509,0 -22437,50415217,Cebolla dulce orgánica,,0,80509,0 -22438,50415218,Cebolla torpedo o rojo italiana orgánica,,0,80509,0 -22439,50415219,Cebolla roja guardada orgánica,,0,80509,0 -22440,50415220,Cebolla blanca guardada orgánica,,0,80509,0 -22441,50415221,Cebolla amarilla guardada orgánica,,0,80509,0 -22442,50415500,Pimientos orgánicos,Chiles orgánicos,0,80509,0 -22443,50415501,Pimentón ají orgánico,Ají orgánico,0,80509,0 -22444,50415502,Pimentón árbol orgánico,Chile de árbol orgánico,0,80509,0 -22445,50415503,Pimentón queso orgánico,,0,80509,0 -22446,50415504,Pimentón chilaca orgánico,Chilaca orgánico,0,80509,0 -22447,50415505,Pimentón cubanelles orgánico,Chile cubanelle o pimienta italiana orgánica,0,80509,0 -22448,50415506,Pimentón fresno orgánico,Chile fresno orgánico,0,80509,0 -22449,50415507,Pimentón kapia orgánico,Chile kapia orgánico,0,80509,0 -22450,50415508,Pimentón coreano orgánico,Chile cheongyang orgánico,0,80509,0 -22451,50415509,Pimentón manzano orgánico,Chile manzano o de cera orgánico,0,80509,0 -22452,50415510,Pimentón melrose orgánico,,0,80509,0 -22453,50415511,Pimentón chile amarillo orgánico,Chile peruano amarillo orgánico,0,80509,0 -22454,50415512,Pimentón aji dulce orgánico,Ají dulce orgánico,0,80509,0 -22455,50415513,Pimentón anaheim orgánico,,0,80509,0 -22456,50415514,Pimentón ancho orgánico,Chile ancho o mulato orgánico,0,80509,0 -22457,50415515,Pimentón campana orgánico,Chile campana orgánico,0,80509,0 -22458,50415516,Pimentón cascabel orgánico,Chile cascabel orgánico,0,80509,0 -22459,50415517,Pimentón cayenne orgánico,Pimienta de Cayena o roja orgánica,0,80509,0 -22460,50415518,Pimentón anfitrión cereza orgánico,,0,80509,0 -22461,50415519,Pimentón chitespin orgánico,Chile chiltepín orgánico,0,80509,0 -22462,50415520,Pimentón dedo picante orgánico,Chile Jwala orgánico,0,80509,0 -22463,50415521,Pimentón guajillo orgánico,Chile guajillo orgánico,0,80509,0 -22464,50415522,Pimentón guerro orgánico,Chile güero orgánico,0,80509,0 -22465,50415523,Pimentón habanero orgánico,Chile habanero orgánico,0,80509,0 -22466,50415524,Pimentón cera de hungria orgánico,Pimentón húngaro orgánico,0,80509,0 -22467,50415525,Pimentón jalapeño orgánico,"Chile cuaresmeño orgánico, Chile jalapeño orgánico",0,80509,0 -22468,50415526,Pimentón picante largo orgánico,Chile cola de rata orgánico,0,80509,0 -22469,50415527,Pimentón marasol orgánico,Chile mirasol orgánico,0,80509,0 -22470,50415528,Pimentón pasilla orgánico,Chile pasilla orgánico,0,80509,0 -22471,50415529,Pimentón peperoncini orgánico,,0,80509,0 -22472,50415530,Pimentón pequin orgánico,Chile piquín orgánico,0,80509,0 -22473,50415531,Pimentón pimiento orgánico,Pimiento morrón orgánico,0,80509,0 -22474,50415532,Pimentón poblano orgánico,Chile poblano orgánico,0,80509,0 -22475,50415533,Pimentón scotch bonnet orgánico,Pimientos Bonney o rojos Caribe orgánicos,0,80509,0 -22476,50415534,Pimentón serrano orgánico,"Chile serrano orgánico, Chile verde orgánico",0,80509,0 -22477,50415535,Pimentón tabasco orgánico,Chile tabasco orgánico,0,80509,0 -22478,50415536,Pimentón tai orgánico,Chile ojo de pájaro orgánico,0,80509,0 -22479,50415537,Pimentón tepin orgánico,Chile mosquito o catarina orgánico,0,80509,0 -22480,50416100,Espinacas orgánicas,,0,80509,0 -22481,50416101,Espinaca americana orgánica,,0,80509,0 -22482,50416102,Espinaca bloomsdale orgánica,,0,80509,0 -22483,50416103,Espinaca invierno gigante orgánica,,0,80509,0 -22484,50416104,Espinaca horenso orgánica,,0,80509,0 -22485,50416105,Espinaca cuarto de cordero orgánica,,0,80509,0 -22486,50416106,Espinaca malabar orgánica,,0,80509,0 -22487,50416107,Espinaca medania orgánica,,0,80509,0 -22488,50416108,Espinaca orach orgánica,,0,80509,0 -22489,50416109,Espinaca savoy orgánica,,0,80509,0 -22490,50416110,Espinaca hoja de sigma orgánica,,0,80509,0 -22491,50416111,Espinaca espacio orgánica,,0,80509,0 -22492,50416112,Espinaca trinidad orgánica,,0,80509,0 -22493,50416113,Espinaca salvaje orgánica,,0,80509,0 -22494,50416114,Espinaca nueva zelandia orgánica,,0,80509,0 -22495,50416115,Espinaca iceplant orgánica,,0,80509,0 -22496,50416200,Calabazas orgánicas,,0,80509,0 -22497,50416201,Calabaza boston orgánica,,0,80509,0 -22498,50416202,Calabaza buttemut orgánica,,0,80509,0 -22499,50416203,Calabaza costata romanesca orgánica,,0,80509,0 -22500,50416204,Calabaza crookneck orgánica,,0,80509,0 -22501,50416205,Calabaza cucuzza orgánica,,0,80509,0 -22502,50416206,Calabaza delicata orgánica,,0,80509,0 -22503,50416207,Calabaza delisioso orgánica,,0,80509,0 -22504,50416208,Calabaza verano dorado cuello torsido orgánica,,0,80509,0 -22505,50416209,Calabaza prolífico temprano cuello recto orgánica,,0,80509,0 -22506,50416210,Calabaza oro orgánica,,0,80509,0 -22507,50416211,Calabaza jack pequeño orgánica,,0,80509,0 -22508,50416212,Calabaza campo de kentucky orgánica,,0,80509,0 -22509,50416213,Calabaza marrow orgánica,,0,80509,0 -22510,50416214,Calabaza medio oriente orgánica,,0,80509,0 -22511,50416215,Calabaza miniatura orgánica,,0,80509,0 -22512,50416216,Calabaza orangetti orgánica,,0,80509,0 -22513,50416217,Calabaza pattypan orgánica,,0,80509,0 -22514,50416218,Calabaza rondini orgánica,,0,80509,0 -22515,50416219,Calabaza redonda orgánica,,0,80509,0 -22516,50416220,Calabaza espagueti orgánica,,0,80509,0 -22517,50416221,Calabaza estripeti orgánica,,0,80509,0 -22518,50416222,Calabaza molde dulce orgánica,,0,80509,0 -22519,50416223,Calabaza empanada dulce orgánica,,0,80509,0 -22520,50416224,Calabaza premio triple orgánica,,0,80509,0 -22521,50416225,Calabaza waltham butternut orgánica,,0,80509,0 -22522,50416226,Calabaza arbusto scallop amarillo orgánica,,0,80509,0 -22523,50416227,Calabaza cuello recto amarillo orgánica,,0,80509,0 -22524,50416228,Calabaza zafiro orgánica,,0,80509,0 -22525,50416229,Calabaza zucchini orgánica,,0,80509,0 -22526,50416400,Tomates orgánicos,Jitomates orgánicos,0,80509,0 -22527,50416401,Tomates ailsa craig orgánicos,Jitomate ailsa craig orgánico,0,80509,0 -22528,50416402,Tomates alicante orgánicos,Jitomate alicante orgánico,0,80509,0 -22529,50416403,Tomates ciruela negra orgánicos,Jitomate ciruela negra orgánico,0,80509,0 -22530,50416404,Tomates vino de brandy orgánicos,Jitomate vino de brandy orgánico,0,80509,0 -22531,50416405,Tomates bella cereza orgánicos,Jitomate bella cereza orgánico,0,80509,0 -22532,50416406,Tomates cereza orgánicos,Jitomate cereza orgánico,0,80509,0 -22533,50416407,Tomates delicioso orgánicos,Jitomate delicioso orgánico,0,80509,0 -22534,50416408,Tomates dombito orgánicos,Jitomate dombito orgánico,0,80509,0 -22535,50416409,Tomates delicia del jardín orgánicos,Jitomate delicia del jardín orgánico,0,80509,0 -22536,50416410,Tomates uva orgánicos,Jitomate uva orgánico,0,80509,0 -22537,50416411,Tomates verde orgánicos,Tomate verde orgánico,0,80509,0 -22538,50416412,Tomates super marmande orgánicos,Jitomate super marmande orgánico,0,80509,0 -22539,50416413,Tomates rayas naturales marvel orgánicos,Jitomate rayas naturales marvel orgánico,0,80509,0 -22540,50416414,Tomates minibel orgánicos,Jitomate minibel orgánico,0,80509,0 -22541,50416415,Tomates oaxacan orgánicos,Jitomate oaxaca orgánico,0,80509,0 -22542,50416416,Tomates alerta roja orgánicos,Jitomate alerta roja orgánico,0,80509,0 -22543,50416417,Tomates roma vf orgánicos,Jitomate roma vf orgánico,0,80509,0 -22544,50416418,Tomates marzano zan orgánicos,Jitomate marzano zan orgánico,0,80509,0 -22545,50416419,Tomates shirley orgánicos,Jitomate shirley orgánico,0,80509,0 -22546,50416420,Tomates siberia orgánicos,Jitomate siberia orgánico,0,80509,0 -22547,50416421,Tomates super beefsteak orgánicos,Jitomate super beefsteak orgánico,0,80509,0 -22548,50416422,Tomates tigerella orgánicos,Jitomate tigerella orgánico,0,80509,0 -22549,50416423,Tomates tiny tim orgánicos,Jitomate tiny tim orgánico,0,80509,0 -22550,50416424,Tomates tumbler orgánicos,Jitomate tumbler orgánico,0,80509,0 -22551,50416425,Tomates cocktail amarillo orgánicos,,0,80509,0 -22552,50416426,Tomates forma de pera amarillo orgánicos,,0,80509,0 -22553,50416427,Tomates perfección amarilla orgánicos,,0,80509,0 -22554,50416600,Auyamas orgánicas,Calabazas gigantes orgánicas,0,80509,0 -22555,50416601,Calabaza acorn orgánica,Calabaza gigante acorn orgánica,0,80509,0 -22556,50416602,Calabaza gigante atlántico orgánica,Calabaza gigante atlántico orgánica,0,80509,0 -22557,50416603,Calabaza banana rosado orgánica,Calabaza gigante banana rosado orgánica,0,80509,0 -22558,50416604,Calabaza max grande orgánica,Calabaza gigante max orgánica,0,80509,0 -22559,50416605,Calabaza orgánica,Calabaza de castilla orgánica,0,80509,0 -22560,50416606,Calabaza carnaval orgánica,Calabaza gigante carnaval orgánica,0,80509,0 -22561,50416607,Auyama queso orgánica,Calabaza gigante queso orgánica,0,80509,0 -22562,50416608,Calabaza príncipe coronado orgánica,Calabaza gigante príncipe coronado orgánica,0,80509,0 -22563,50416609,Calabaza curcibita orgánica,Calabaza gigante curcibita orgánica,0,80509,0 -22564,50416610,Calabaza cushaw orgánica,Calabaza gigante cushaw orgánica,0,80509,0 -22565,50416611,Calabaza gigante auyama orgánica,,0,80509,0 -22566,50416612,Calabaza hubbard orgánica,Calabaza gigante hubbard orgánica,0,80509,0 -22567,50416613,Calabaza jerrahdale orgánica,Calabaza gigante jerrahdale orgánica,0,80509,0 -22568,50416614,Calabaza kabocha orgánica,Calabaza gigante kabocha orgánica,0,80509,0 -22569,50416615,Calabaza queensland azul orgánica,Calabaza gigante queensland azul orgánica,0,80509,0 -22570,50416616,Calabaza rouge vif détampes orgánica,Calabaza gigante rouge vif détampes orgánica,0,80509,0 -22571,50416617,Calabaza turban turco orgánica,Calabaza gigante turban turco orgánica,0,80509,0 -22572,50416618,Calabaza valenciano orgánica,Calabaza gigante valenciano orgánica,0,80509,0 -22573,50416619,Calabaza warted hubbard orgánica,Calabaza gigante warted hubbard orgánica,0,80509,0 -22574,50416620,Auyama whangaparoa orgánica,Calabaza gigante whangaparoa orgánica,0,80509,0 -22575,50416800,Ñames orgánicos,,0,80509,0 -22576,50416801,Maíz aloha orgánico,,0,80509,0 -22577,50416802,Maíz alpine orgánico,,0,80509,0 -22578,50416803,Maíz ambrosia orgánico,,0,80509,0 -22579,50416804,Maíz argent orgánico,,0,80509,0 -22580,50416805,Maíz aspen orgánico,,0,80509,0 -22581,50416806,Maíz avalancha orgánico,,0,80509,0 -22582,50416807,Maíz biqueen orgánico,,0,80509,0 -22583,50416808,Maíz bodacious orgánico,,0,80509,0 -22584,50416809,Maíz mantequilla y azúcar orgánico,,0,80509,0 -22585,50416810,Maíz calico belle orgánico,,0,80509,0 -22586,50416811,Maíz camelot orgánico,,0,80509,0 -22587,50416812,Maíz challengercrisp ‘n dulce orgánico,,0,80509,0 -22588,50416813,Maíz campeón orgánico,,0,80509,0 -22589,50416814,Maíz algodón de azúcar orgánico,,0,80509,0 -22590,50416815,Maíz dártagnan orgánico,,0,80509,0 -22591,50416816,Maíz dazzle orgánico,,0,80509,0 -22592,50416817,Maíz diamante y oro orgánico,,0,80509,0 -22593,50416818,Maíz divinidad orgánico,,0,80509,0 -22594,50416819,Maíz delicia doble orgánico,,0,80509,0 -22595,50416820,Maíz gema doble orgánico,,0,80509,0 -22596,50416821,Maíz earlivee orgánico,,0,80509,0 -22597,50416822,Maíz temprano extra dulce orgánico,,0,80509,0 -22598,50416823,Maíz excel orgánico,,0,80509,0 -22599,50416824,Maíz cruz dorada bantam orgánico,,0,80509,0 -22600,50416825,Maíz miel y crema orgánico,,0,80509,0 -22601,50416826,Maíz miel y perla orgánico,,0,80509,0 -22602,50416827,Maíz miel dulce orgánico,,0,80509,0 -22603,50416828,Maíz hudson orgánico,,0,80509,0 -22604,50416829,Maíz dorado illini orgánico,,0,80509,0 -22605,50416830,Maíz illini extra dulce orgánico,,0,80509,0 -22606,50416831,Maíz increíble orgánico,,0,80509,0 -22607,50416832,Maíz lochief orgánico,,0,80509,0 -22608,50416833,Maíz jubileo orgánico,,0,80509,0 -22609,50416834,Maíz jupbileo super dulce orgánico,,0,80509,0 -22610,50416835,Maíz confite korn orgánico,,0,80509,0 -22611,50416836,Maíz beso y acuso orgánico,,0,80509,0 -22612,50416837,Maíz lancelot orgánico,,0,80509,0 -22613,50416838,Maíz dulce maple orgánico,,0,80509,0 -22614,50416839,Maíz medley orgánico,,0,80509,0 -22615,50416840,Maíz merlin orgánico,,0,80509,0 -22616,50416841,Maíz milagro orgánico,,0,80509,0 -22617,50416842,Maíz nk – 199 orgánico,,0,80509,0 -22618,50416843,Maíz durazno y crema orgánico,,0,80509,0 -22619,50416844,Maíz blanco perla orgánico,,0,80509,0 -22620,50416845,Maíz pegasus orgánico,,0,80509,0 -22621,50416846,Maíz fenomenal orgánico,,0,80509,0 -22622,50416847,Maíz dama de platino orgánico,,0,80509,0 -22623,50416848,Maíz precioso orgánico,,0,80509,0 -22624,50416849,Maíz pristine orgánico,,0,80509,0 -22625,50416850,Maíz rápido orgánico,,0,80509,0 -22626,50416851,Maíz radiante orgánico,,0,80509,0 -22627,50416852,Maíz seneca brave orgánico,,0,80509,0 -22628,50416853,Maíz amanecer seneca orgánico,,0,80509,0 -22629,50416854,Maíz horizonte seneca orgánico,,0,80509,0 -22630,50416855,Maíz brillo de estrella seneca orgánico,,0,80509,0 -22631,50416856,Maíz noche blanca seneca orgánico,,0,80509,0 -22632,50416857,Maíz showcase orgánico,,0,80509,0 -22633,50416858,Maíz reina plateada orgánico,,0,80509,0 -22634,50416859,Maíz bella nieve orgánico,,0,80509,0 -22635,50416860,Maíz nieve de primavera orgánico,,0,80509,0 -22636,50416861,Maíz premio de primavera orgánico,,0,80509,0 -22637,50416862,Maíz azúcar y oro orgánico,,0,80509,0 -22638,50416863,Maíz rollo de azúcar orgánico,,0,80509,0 -22639,50416864,Maíz nieve de azúcar orgánico,,0,80509,0 -22640,50416865,Maíz baile de sol orgánico,,0,80509,0 -22641,50416866,Maíz estrella tell orgánico,,0,80509,0 -22642,50416867,Maíz terminador orgánico,,0,80509,0 -22643,50416868,Maíz tesoro orgánico,,0,80509,0 -22644,50416869,Maíz tuxedo orgánico,,0,80509,0 -22645,50417000,Vegetales Nominant,"Vegetales predominantes orgánicos, Nopal orgánico",0,80509,0 -22646,50417001,Alfafa orgánico,,0,80509,0 -22647,50417002,Hojas de aloe orgánica,"Hoja de sábila orgánica, Penca de sábila orgánica",0,80509,0 -22648,50417003,Apio orgánico,,0,80509,0 -22649,50417004,Arrurruz orgánica,,0,80509,0 -22650,50417005,Punta de flecha orgánica,Sagittaria orgánica,0,80509,0 -22651,50417006,rúcula orgánica,,0,80509,0 -22652,50417007,Arum orgánica,Taro orgánico,0,80509,0 -22653,50417008,Raíz de bambú orgánica,,0,80509,0 -22654,50417009,Hojas de platano orgánica,,0,80509,0 -22655,50417010,Batatas orgánica,,0,80509,0 -22656,50417011,Brotes de frijol orgánico,,0,80509,0 -22657,50417012,Top de remolacha orgánico,Betabel orgánico,0,80509,0 -22658,50417013,Melón agrio orgánico,Balsamina orgánica,0,80509,0 -22659,50417014,Bayas de alcaparra orgánicas,,0,80509,0 -22660,50417015,Carob orgánico,Algarrobo orgánico,0,80509,0 -22661,50417016,Cha-om orgánico,,0,80509,0 -22662,50417017,Chayote o guatila orgánico,,0,80509,0 -22663,50417018,Garbanzo orgánico,,0,80509,0 -22664,50417019,Verde de crisantemos orgánico,,0,80509,0 -22665,50417020,Verde de diente de león orgánico,,0,80509,0 -22666,50417021,Diente de león orgánico,,0,80509,0 -22667,50417022,Dasheen orgánico,,0,80509,0 -22668,50417023,Puntas de alverja orgánicas,,0,80509,0 -22669,50417024,Diakon orgánico,Rábano orgánico,0,80509,0 -22670,50417025,Donqua orgánica,Ginseng hembra orgánica,0,80509,0 -22671,50417026,Helecho orgánico,Brotes de helecho orgánico,0,80509,0 -22672,50417027,Gai choi orgánico,Mostaza castaña orgánica,0,80509,0 -22673,50417028,Gailon orgánico,Brécol chino orgánico,0,80509,0 -22674,50417029,Galanga orgánico,Jengibre azul orgánico,0,80509,0 -22675,50417030,Raíz de jengibre orgánico,,0,80509,0 -22676,50417031,Gobo orgánico,Garduña orgánica,0,80509,0 -22677,50417032,Brote de lúpulo orgánico,,0,80509,0 -22678,50417033,Rabano blanco orgánico,Rábano de caballo orgánico,0,80509,0 -22679,50417034,Jicama orgánica,,0,80509,0 -22680,50417035,Kuduz orgánico,Kudzu orgánico,0,80509,0 -22681,50417036,Bulbo de margarita orgánico,Bulbo de lili chino orgánico,0,80509,0 -22682,50417037,Linkok orgánico,,0,80509,0 -22683,50417038,Lo bok orgánico,,0,80509,0 -22684,50417039,Frijol largo orgánico,Fríjol serpiente orgánico,0,80509,0 -22685,50417040,Raíz de lotus orgánico,Rosa del Nilo orgánica,0,80509,0 -22686,50417041,Hojas de maguey orgánicas,"Hoja de agave orgánica, Penca de maguey orgánica",0,80509,0 -22687,50417042,Mallows orgánico,Malva orgánica,0,80509,0 -22688,50417043,Sapote mamey orgánico,Mamey orgánico,0,80509,0 -22689,50417044,Moap orgánico,,0,80509,0 -22690,50417045,Moo orgánico,,0,80509,0 -22691,50417046,Moqua orgánica,,0,80509,0 -22692,50417047,Opos orgánico,,0,80509,0 -22693,50417048,Corazon de palma orgánico,Palmito orgánico,0,80509,0 -22694,50417049,Paprika orgánica,,0,80509,0 -22695,50417050,Purslane orgánico,Verdolaga orgánica,0,80509,0 -22696,50417051,Raddichios orgánico,Achicoria morada orgánica,0,80509,0 -22697,50417052,Sinquas orgánico,Zapallo orgánico,0,80509,0 -22698,50417053,Frijol de soya orgánicos,,0,80509,0 -22699,50417054,Spoonwart orgánico,Hierba del escorbuto orgánica,0,80509,0 -22700,50417055,Uva tassele hyacinth orgánico,,0,80509,0 -22701,50417056,Taro orgánico,Tales orgánica,0,80509,0 -22702,50417057,Hoja de taro orgánico,,0,80509,0 -22703,50417058,Tallo de taro orgánico,,0,80509,0 -22704,50417059,Tapeguaje orgánico,,0,80509,0 -22705,50417060,Verde tierno orgánico,,0,80509,0 -22706,50417061,Tindora orgánico,,0,80509,0 -22707,50417062,Árbol de cebolla orgánico,Cebollas egipcias orgánica,0,80509,0 -22708,50417063,Udo orgánico,,0,80509,0 -22709,50417064,Agua castaño orgánica,Castaña de agua china orgánica,0,80509,0 -22710,50417065,Agua espinaca orgánica,Akankong orgánico,0,80509,0 -22711,50417066,Yumpi orgánico,Mapuey orgánico,0,80509,0 -22712,50417067,Yautia orgánico,,0,80509,0 -22713,50417068,Yu choy orgánico,,0,80509,0 -22714,50417069,Yuca orgánica,,0,80509,0 -22715,50421500,Alcachofas Secas,,0,80509,0 -22716,50421501,Alcachofa brittany seca,,0,80509,0 -22717,50421502,Alcachofa cantonesa seca,,0,80509,0 -22718,50421503,Alcachofa francesa seca,,0,80509,0 -22719,50421504,Alcachofa globo verde seca,,0,80509,0 -22720,50421505,Alcachofa gros camus de bretaña seca,,0,80509,0 -22721,50421506,Alcachofa midi seca,,0,80509,0 -22722,50421507,Alcachofa globo morado seca,,0,80509,0 -22723,50421508,Alcachofa morado cecilia seca,,0,80509,0 -22724,50421509,Alcachofa romanesco seca,,0,80509,0 -22725,50421510,Alcachofa espinoso sardo seca,,0,80509,0 -22726,50421511,Alcachofa vert de laon seca,,0,80509,0 -22727,50421512,Alcachofa violeta de chiggiia seca,,0,80509,0 -22728,50421513,Alcachofa violeta de toscana seca,,0,80509,0 -22729,50421800,Fríjoles Secos,,0,80509,0 -22730,50421801,Frijoles anasazi ® secos,,0,80509,0 -22731,50421802,Frijoles appaloosa secos,,0,80509,0 -22732,50421803,Frijoles azuki secos,,0,80509,0 -22733,50421804,Frijoles barlotti secos,,0,80509,0 -22734,50421805,Frijoles appaloosa negra secos,,0,80509,0 -22735,50421806,Frijoles negros secos,,0,80509,0 -22736,50421807,Frijoles gran negros secos,,0,80509,0 -22737,50421808,Frijoles shackamaxon negro secos,,0,80509,0 -22738,50421809,Frijoles ojo negro secos,,0,80509,0 -22739,50421810,Frijoles bobby secos,,0,80509,0 -22740,50421811,Frijoles bolita secos,,0,80509,0 -22741,50421812,Frijoles esposa perezosa carmelita secos,,0,80509,0 -22742,50421813,Frijoles calipso secos,,0,80509,0 -22743,50421814,Frijoles canelini secos,,0,80509,0 -22744,50421815,Frijoles castor secos,,0,80509,0 -22745,50421816,Frijoles amarillo chino secos,,0,80509,0 -22746,50421817,Frijoles lengua de dragón secos,,0,80509,0 -22747,50421818,Frijoles soldado europeo secos,,0,80509,0 -22748,50421819,Frijoles fava secos,,0,80509,0 -22749,50421820,Frijoles flageolet secos,,0,80509,0 -22750,50421821,Frijoles horticultura francesa secos,,0,80509,0 -22751,50421822,Frijoles marina francesa secos,,0,80509,0 -22752,50421823,Frijoles coco gigante blanco secos,,0,80509,0 -22753,50421824,Frijoles verdes secos,,0,80509,0 -22754,50421825,Frijoles romano verde secos,,0,80509,0 -22755,50421826,Frijoles guar gum secos,,0,80509,0 -22756,50421827,Frijoles haricot secos,,0,80509,0 -22757,50421828,Frijoles hyacinth secos,,0,80509,0 -22758,50421829,Frijoles tipo italiano secos,,0,80509,0 -22759,50421830,Frijoles asombro jackson secos,,0,80509,0 -22760,50421831,Frijoles ganado jacobs secos,,0,80509,0 -22761,50421832,Frijoles asombro kentucky secos,,0,80509,0 -22762,50421833,Frijoles riñón secos,,0,80509,0 -22763,50421834,Frijoles lima secos,,0,80509,0 -22764,50421835,Frijoles madera secos,,0,80509,0 -22765,50421836,Frijoles medula secos,,0,80509,0 -22766,50421837,Frijoles mat secos,,0,80509,0 -22767,50421838,Frijoles monstoler ganzo salvaje secos,,0,80509,0 -22768,50421839,Frijoles mortgage lifter secos,,0,80509,0 -22769,50421840,Frijoles polilla secos,,0,80509,0 -22770,50421841,Frijoles mung secos,,0,80509,0 -22771,50421842,Frijoles munsi wolf secos,,0,80509,0 -22772,50421843,Frijoles nuna secos,,0,80509,0 -22773,50421844,Frijoles pinto secos,,0,80509,0 -22774,50421845,Frijoles runner secos,,0,80509,0 -22775,50421846,String beans secos,"Ayecote seco, Fríjol gordo seco",0,80509,0 -22776,50421847,Frijoles habichuelas secos,"Ejote seco, Fríjol tierno seco",0,80509,0 -22777,50421848,Frijoles tamarind secos,,0,80509,0 -22778,50421849,Frijoles tonka secos,,0,80509,0 -22779,50421850,Frijoles cera secos,,0,80509,0 -22780,50421851,Frijoles winged secos,,0,80509,0 -22781,50421852,Frijoles largos secos,,0,80509,0 -22782,50422500,Zanahorias Secas,,0,80509,0 -22783,50422501,Zanahoria amsterdam seca,,0,80509,0 -22784,50422502,Zanahoria rey de otoño seca,,0,80509,0 -22785,50422503,Zanahoria berlicum seca,,0,80509,0 -22786,50422504,Zanahoria chantenay seca,,0,80509,0 -22787,50422505,Zanahoria nantes seca,,0,80509,0 -22788,50422506,Zanahoria mercado de parís seca,,0,80509,0 -22789,50423200,Maíces Secos,,0,80509,0 -22790,50423201,Maíz aloha seco,,0,80509,0 -22791,50423202,Maíz alpine seco,,0,80509,0 -22792,50423203,Maíz ambrosia seco,,0,80509,0 -22793,50423204,Maíz argent seco,,0,80509,0 -22794,50423205,Maíz aspen seco,,0,80509,0 -22795,50423206,Maíz avalancha seco,,0,80509,0 -22796,50423207,Maíz biqueen seco,,0,80509,0 -22797,50423208,Maíz bodacious seco,,0,80509,0 -22798,50423209,Maíz mantequilla y azúcar seco,,0,80509,0 -22799,50423210,Maíz calico belle seco,,0,80509,0 -22800,50423211,Maíz camelot seco,,0,80509,0 -22801,50423212,Maíz challengercrisp ‘n dulce seco,,0,80509,0 -22802,50423213,Maíz campeón seco,,0,80509,0 -22803,50423214,Maíz algodón de azúcar seco,,0,80509,0 -22804,50423215,Maíz dártagnan seco,,0,80509,0 -22805,50423216,Maíz dazzle seco,,0,80509,0 -22806,50423217,Maíz diamante y oro seco,,0,80509,0 -22807,50423218,Maíz divinidad seco,,0,80509,0 -22808,50423219,Maíz delicia doble seco,,0,80509,0 -22809,50423220,Maíz gema doble seco,,0,80509,0 -22810,50423221,Maíz earlivee seco,,0,80509,0 -22811,50423222,Maíz temprano extra dulce seco,,0,80509,0 -22812,50423223,Maíz excel seco,,0,80509,0 -22813,50423224,Maíz cruz dorada bantam seco,,0,80509,0 -22814,50423225,Maíz miel y crema seco,,0,80509,0 -22815,50423226,Maíz miel y perla seco,,0,80509,0 -22816,50423227,Maíz miel dulce seco,,0,80509,0 -22817,50423228,Maíz hudson seco,,0,80509,0 -22818,50423229,Maíz dorado illini seco,,0,80509,0 -22819,50423230,Maíz illini extra dulce seco,,0,80509,0 -22820,50423231,Maíz increíble seco,,0,80509,0 -22821,50423232,Maíz lochief seco,,0,80509,0 -22822,50423233,Maíz jubileo seco,,0,80509,0 -22823,50423234,Maíz jupbileo super dulce seco,,0,80509,0 -22824,50423235,Maíz confite korn seco,,0,80509,0 -22825,50423236,Maíz beso y acuso seco,,0,80509,0 -22826,50423237,Maíz lancelot seco,,0,80509,0 -22827,50423238,Maíz dulce maple seco,,0,80509,0 -22828,50423239,Maíz medley seco,,0,80509,0 -22829,50423240,Maíz merlin seco,,0,80509,0 -22830,50423241,Maíz milagro seco,,0,80509,0 -22831,50423242,Maíz nk – 199 seco,,0,80509,0 -22832,50423243,Maíz durazno y crema seco,,0,80509,0 -22833,50423244,Maíz blanco perla seco,,0,80509,0 -22834,50423245,Maíz pegasus seco,,0,80509,0 -22835,50423246,Maíz fenomenal seco,,0,80509,0 -22836,50423247,Maíz dama de platino seco,,0,80509,0 -22837,50423248,Maíz precioso seco,,0,80509,0 -22838,50423249,Maíz pristine seco,,0,80509,0 -22839,50423250,Maíz rápido seco,,0,80509,0 -22840,50423251,Maíz radiante seco,,0,80509,0 -22841,50423252,Maíz seneca brave seco,,0,80509,0 -22842,50423253,Maíz amanecer seneca seco,,0,80509,0 -22843,50423254,Maíz horizonte seneca seco,,0,80509,0 -22844,50423255,Maíz brillo de estrella seneca seco,,0,80509,0 -22845,50423256,Maíz noche blanca seneca seco,,0,80509,0 -22846,50423257,Maíz showcase seco,,0,80509,0 -22847,50423258,Maíz reina plateada seco,,0,80509,0 -22848,50423259,Maíz bella nieve seco,,0,80509,0 -22849,50423260,Maíz nieve de primavera seco,,0,80509,0 -22850,50423261,Maíz premio de primavera seco,,0,80509,0 -22851,50423262,Maíz azúcar y oro seco,,0,80509,0 -22852,50423263,Maíz rollo de azúcar seco,,0,80509,0 -22853,50423264,Maíz nieve de azúcar seco,,0,80509,0 -22854,50423265,Maíz baile de sol seco,,0,80509,0 -22855,50423266,Maíz estrella tell seco,,0,80509,0 -22856,50423267,Maíz terminador seco,,0,80509,0 -22857,50423268,Maíz tesoro seco,,0,80509,0 -22858,50423269,Maíz tuxedo seco,,0,80509,0 -22859,50423700,Hinojos Secos,,0,80509,0 -22860,50423701,Hinojo cantino seco,,0,80509,0 -22861,50423702,Hinojo fino seco,,0,80509,0 -22862,50423703,Hinojoherald seco,,0,80509,0 -22863,50423704,Hinojo perfección seco,,0,80509,0 -22864,50423705,Hinojo sirio seco,,0,80509,0 -22865,50423706,Hinojo florencia dulce seco,,0,80509,0 -22866,50423707,Hinojo tardo seco,,0,80509,0 -22867,50423800,Ajos Secos,,0,80509,0 -22868,50423801,Ajo tarde california seco,,0,80509,0 -22869,50423802,Ajo tallos chinos seco,,0,80509,0 -22870,50423803,Ajo cebollin seco,,0,80509,0 -22871,50423804,Ajo germidor seco,,0,80509,0 -22872,50423805,Ajo guardado largo seco,,0,80509,0 -22873,50423806,Ajo ramson seco,,0,80509,0 -22874,50423807,Ajo rocambola seco,,0,80509,0 -22875,50423808,Ajo rosa de lautrec seco,,0,80509,0 -22876,50423809,Ajo solent blanco seco,,0,80509,0 -22877,50423810,Ajo morado español seco,,0,80509,0 -22878,50423811,Ajo valentina italiana seco,,0,80509,0 -22879,50423900,Calabazas Secas,,0,80509,0 -22880,50423901,Petula seca,Luffa seca,0,80509,0 -22881,50423902,Melon amargo seco,,0,80509,0 -22882,50423903,Botella calabaza seca,Acocote seco,0,80509,0 -22883,50423904,Calabash gounds seca,Guaje o jícaro seco,0,80509,0 -22884,50423905,Melón peludo seco,,0,80509,0 -22885,50423906,Calabaza amarga seca,,0,80509,0 -22886,50423907,Loofah suave seco,"Esponja vegetal, Zacate vegetal",0,80509,0 -22887,50423908,Serpiente de calabaza seca,Chichinda seca,0,80509,0 -22888,50423909,Calabaza larga amarga seca,,0,80509,0 -22889,50423910,Calabaza tinda seca,,0,80509,0 -22890,50423911,Calabaza tindoori seca,,0,80509,0 -22891,50424000,Guisantes Secas,Chícharos secos,0,80509,0 -22892,50424001,Guisante chino seca,Chícharo chino seco,0,80509,0 -22893,50424002,Guisante inglés seca,Chícharo inglés seco,0,80509,0 -22894,50424003,Guisante de jardín seca,Chícharo de jardín seco,0,80509,0 -22895,50424004,Guisante nieve seca,Chícharo nieve seco,0,80509,0 -22896,50424005,Guisante snap dulce seca,Chícharo snap dulce seco,0,80509,0 -22897,50424100,Hierbas Secas,"Perejil seco, Yerbabuena o hierbabuena seca",0,80509,0 -22898,50424101,Albahaca seca,,0,80509,0 -22899,50424102,Laurel seco,,0,80509,0 -22900,50424103,Borraja seca,,0,80509,0 -22901,50424104,Alcaravea seca,,0,80509,0 -22902,50424105,Perifollo seco,,0,80509,0 -22903,50424106,Cilantro seco,,0,80509,0 -22904,50424107,Chipolinos seco,,0,80509,0 -22905,50424108,Hoja de curry seca,,0,80509,0 -22906,50424109,Eneldo seco,,0,80509,0 -22907,50424110,Epazote seco,,0,80509,0 -22908,50424111,Fenogreco seco,,0,80509,0 -22909,50424112,Limonaria seca,Té de limón seco,0,80509,0 -22910,50424113,Mejorana seca,,0,80509,0 -22911,50424114,Menta seca,,0,80509,0 -22912,50424115,Orégano seco,,0,80509,0 -22913,50424116,Pápalo seco,,0,80509,0 -22914,50424117,Pápalo delgado seco,,0,80509,0 -22915,50424118,Perilla seca,Albahaca japonesa seca,0,80509,0 -22916,50424119,Recao seco,,0,80509,0 -22917,50424120,Romero seco,,0,80509,0 -22918,50424121,Salvia seca,,0,80509,0 -22919,50424122,Salsifí seca,Barba cabruna seca,0,80509,0 -22920,50424123,Satureja seca,Ajedreas seca,0,80509,0 -22921,50424124,Estragón seco,Tarragón seco,0,80509,0 -22922,50424125,Tomillo seco,,0,80509,0 -22923,50424126,Tumeric seco,Cúrcuma seca,0,80509,0 -22924,50424127,Verdolaga seca,,0,80509,0 -22925,50424500,Lentejas Secas,,0,80509,0 -22926,50424501,Lenteja beluga seca,,0,80509,0 -22927,50424502,Lenteja verde francesa seca,,0,80509,0 -22928,50424503,Lenteja verde seca,,0,80509,0 -22929,50424504,Lenteja crimson pequeña seca,,0,80509,0 -22930,50424505,Lenteja pardina española seca,,0,80509,0 -22931,50424506,Lenteja roja separada seca,,0,80509,0 -22932,50424507,Lenteja amarilla separad seca,,0,80509,0 -22933,50424508,Lenteja tarahumara rosada seca,,0,80509,0 -22934,50424900,Mostazas Secas,,0,80509,0 -22935,50424901,Mostaza bambú seca,,0,80509,0 -22936,50424902,Mostaza ajo seca,,0,80509,0 -22937,50424903,Mostaza hoja gigante seca,,0,80509,0 -22938,50424904,Mostaza rojo en nieve seca,,0,80509,0 -22939,50424905,Mostaza sureño seca,,0,80509,0 -22940,50424906,Mostaza corazón envuelto seca,,0,80509,0 -22941,50425200,Cebollas Secas,,0,80509,0 -22942,50425201,Cebolla albion seca,,0,80509,0 -22943,50425202,Cebolla alisia craig seca,,0,80509,0 -22944,50425203,Cebolla hirviendo seca,,0,80509,0 -22945,50425204,Cebolla bufalo seca,,0,80509,0 -22946,50425205,Cebolla bulbo seca,,0,80509,0 -22947,50425206,Cebolla cremosa seca,,0,80509,0 -22948,50425207,Cebolla amarillo expreso o – x seca,,0,80509,0 -22949,50425208,Cebolla kelsae seca,,0,80509,0 -22950,50425209,Cebolla gigante marshal globo fen seca,,0,80509,0 -22951,50425210,Cebolla perla seca,,0,80509,0 -22952,50425211,Cebolla baron rojo seca,,0,80509,0 -22953,50425212,Cebolla roja seca,Cebolla morada seca,0,80509,0 -22954,50425213,Cebolla rijnsberger seca,,0,80509,0 -22955,50425214,Cebolla senshyu semi – globo amarillo seca,,0,80509,0 -22956,50425215,Cebolla sturon seca,,0,80509,0 -22957,50425216,Cebolla sturgatter seca,,0,80509,0 -22958,50425217,Cebolla dulce seca,,0,80509,0 -22959,50425218,Cebolla torpedo o rojo italiana seca,,0,80509,0 -22960,50425219,Cebolla roja guardada seca,,0,80509,0 -22961,50425220,Cebolla blanca guardada seca,,0,80509,0 -22962,50425221,Cebolla amarilla guardada seca,,0,80509,0 -22963,50425300,Alverjas Secas,Cacahuates secos,0,80509,0 -22964,50425301,Maní bambara seco,Cacahuate bambara seco,0,80509,0 -22965,50425302,Maní florunner seco,Cacahuate florunner seco,0,80509,0 -22966,50425303,Maní husa / kerstings seco,Cacahuate husa / kerstings seco,0,80509,0 -22967,50425304,Maní español seco,Cacahuate español seco,0,80509,0 -22968,50425305,Maní valencia seco,Cacahuate valencia seco,0,80509,0 -22969,50425306,Maní virginia seco,Cacahuate virginia seco,0,80509,0 -22970,50425400,Maníes Secos,Cacahuates secos,0,80509,0 -22971,50425401,Alverja casco morado seca,Cacahuate casco morado seco,0,80509,0 -22972,50425402,Alverja pinkeye seca,Cacahuate pinkeye seco,0,80509,0 -22973,50425403,Alverja crowder seca,Cacahuate crowder seco,0,80509,0 -22974,50425404,Alverja acre blanco seca,Cacahuate acre blanco seco,0,80509,0 -22975,50425405,Alverja ojo negro seca,Cacahuate ojo negro seco,0,80509,0 -22976,50425406,Alverja crema zipper seca,Cacahuate crema zipper seco,0,80509,0 -22977,50425500,Pimientos Secos,Chiles secos,0,80509,0 -22978,50425501,Pimentón ají seco,Ají seco,0,80509,0 -22979,50425502,Pimentón árbol seco,Chile de árbol seco,0,80509,0 -22980,50425503,Pimentón queso seco,,0,80509,0 -22981,50425504,Pimentón chilaca seco,Chilaca,0,80509,0 -22982,50425505,Pimentón cubanelles seco,Chile cubanelle o pimienta italiana seca,0,80509,0 -22983,50425506,Pimentón fresno seco,Chile fresno seco,0,80509,0 -22984,50425507,Pimentón kapia seco,Chile kapia seco,0,80509,0 -22985,50425508,Pimentón coreano seco,Chile cheongyang seco,0,80509,0 -22986,50425509,Pimentón manzano seco,Chile manzano o de cera,0,80509,0 -22987,50425510,Pimentón melrose seco,,0,80509,0 -22988,50425511,Pimentón chile amarillo seco,Chile peruano amarillo seco,0,80509,0 -22989,50425512,Pimentón aji dulce seco,Ají dulce seco,0,80509,0 -22990,50425513,Pimentón anaheim seco,,0,80509,0 -22991,50425514,Pimentón ancho seco,Chile ancho o mulato,0,80509,0 -22992,50425515,Pimentón campana seco,Chile campana seco,0,80509,0 -22993,50425516,Pimentón cascabel seco,Chile cascabel,0,80509,0 -22994,50425517,Pimentón cayenne seco,Pimienta de Cayena o roja seca,0,80509,0 -22995,50425518,Pimentón anfitrión cereza seco,,0,80509,0 -22996,50425519,Pimentón chitespin seco,Chile chiltepín seco,0,80509,0 -22997,50425520,Pimentón dedo picante seco,Chile Jwala seco,0,80509,0 -22998,50425521,Pimentón guajillo seco,Chile guajillo,0,80509,0 -22999,50425522,Pimentón guerro seco,Chile güero seco,0,80509,0 -23000,50425523,Pimentón habanero seco,Chile habanero seco,0,80509,0 -23001,50425524,Pimentón cera de hungria seco,Pimentón húngaro seco,0,80509,0 -23002,50425525,Pimentón jalapeño seco,Chile chipotle o morita,0,80509,0 -23003,50425526,Pimentón picante largo seco,Chile cola de rata seco,0,80509,0 -23004,50425527,Pimentón marasol seco,Chile mirasol,0,80509,0 -23005,50425528,Pimentón pasilla seco,Chile pasilla,0,80509,0 -23006,50425529,Pimentón peperoncini seco,,0,80509,0 -23007,50425530,Pimentón pequin seco,Chile piquín seco,0,80509,0 -23008,50425531,Pimentón pimiento seco,Pimiento morrón seco,0,80509,0 -23009,50425532,Pimentón poblano seco,Chile poblano,0,80509,0 -23010,50425533,Pimentón scotch bonnet seco,Pimientos Bonney o rojos Caribe seco,0,80509,0 -23011,50425534,Pimentón serrano seco,"Chile serrano, Chile verde",0,80509,0 -23012,50425535,Pimentón tabasco seco,Chile tabasco seco,0,80509,0 -23013,50425536,Pimentón tai seco,Chile ojo de pájaro seco,0,80509,0 -23014,50425537,Pimentón tepin seco,Chile mosquito o catarina,0,80509,0 -23015,50425538,Pimentón panca chilli seco,Chile panca seco,0,80509,0 -23016,50425800,Vegetales marinos Secos,,0,80509,0 -23017,50425801,Agar-agar seco,,0,80509,0 -23018,50425802,Arame seca,,0,80509,0 -23019,50425803,Alga dulce seca,,0,80509,0 -23020,50425804,Habichuelas vert de mer seca,,0,80509,0 -23021,50425805,Hijiki seca,,0,80509,0 -23022,50425806,Musgo irlandés seca,,0,80509,0 -23023,50425807,Quelpo seco,,0,80509,0 -23024,50425808,Laver seco,,0,80509,0 -23025,50425809,Nori seco,,0,80509,0 -23026,50425810,Alga roja seca,,0,80509,0 -23027,50425811,Kale de mar seca,,0,80509,0 -23028,50425812,Lechuga de mar seca,,0,80509,0 -23029,50425813,Algas de mar seca,,0,80509,0 -23030,50425814,Spirulina seca,,0,80509,0 -23031,50425815,Susabi nuri seca,,0,80509,0 -23032,50425816,Wakame seca,,0,80509,0 -23033,50426300,Batatas Secas,Camotes secos,0,80509,0 -23034,50426301,Batata beauregard seca,Camote beauregard seca,0,80509,0 -23035,50426302,Batata centennial seca,Camote centennial seca,0,80509,0 -23036,50426303,Batata diane seca,Camote diane seca,0,80509,0 -23037,50426304,Batata dulce garnet seca,Camote dulce garnet seca,0,80509,0 -23038,50426305,Batata georgia dulce rojo seca,Camote georgia dulce rojo seca,0,80509,0 -23039,50426306,Batata dorado seca,Camote dorado seca,0,80509,0 -23040,50426307,Batata hanna seca,Camote hanna seca,0,80509,0 -23041,50426308,Batata japonés seca,Camote japonés seca,0,80509,0 -23042,50426309,Batata jersey seca,Camote jersey seca,0,80509,0 -23043,50426310,Batata joya seca,Camote joya seca,0,80509,0 -23044,50426311,Batata meryland roja seca,Camote meryland roja seca,0,80509,0 -23045,50426312,Batata nema dorado seca,Camote nema dorado seca,0,80509,0 -23046,50426313,Batata o´henry seca,Camote o´henry seca,0,80509,0 -23047,50426314,Batata okinawan seca,Camote okinawan seca,0,80509,0 -23048,50426315,Batata naranja seca,Camote naranja seca,0,80509,0 -23049,50426316,Batata oriental seca,Camote oriental seca,0,80509,0 -23050,50426317,Batata jersey rojo seca,Camote jersey rojo seca,0,80509,0 -23051,50426318,Batata mar rojo seca,Camote mar rojo seca,0,80509,0 -23052,50426319,Batata brillo rojo seca,Camote brillo rojo seca,0,80509,0 -23053,50426320,Batata jersey amarillo seca,Camote jersey amarillo seca,0,80509,0 -23054,50426400,Tomates Secos,Jitomates Secos,0,80509,0 -23055,50426401,Tomates ailsa craig seco,Jitomate ailsa craig seco,0,80509,0 -23056,50426402,Tomates alicante seco,Jitomate alicante seco,0,80509,0 -23057,50426403,Tomates ciruela negra seco,Jitomate ciruela negra seco,0,80509,0 -23058,50426404,Tomates vino de brandy seco,Jitomate vino de brandy seco,0,80509,0 -23059,50426405,Tomates bella cereza seco,Jitomate bella cereza seco,0,80509,0 -23060,50426406,Tomates cereza seco,Jitomate cereza seco,0,80509,0 -23061,50426407,Tomates delicioso seco,Jitomate delicioso seco,0,80509,0 -23062,50426408,Tomates dombito seco,Jitomate dombito seco,0,80509,0 -23063,50426409,Tomates delicia del jardín seco,Jitomate delicia del jardín seco,0,80509,0 -23064,50426410,Tomates uva seco,Jitomate uva seco,0,80509,0 -23065,50426411,Tomates verde seco,Tomate verde seco,0,80509,0 -23066,50426412,Tomates super marmande seco,Jitomate super marmande seco,0,80509,0 -23067,50426413,Tomates rayas naturales marvel seco,Jitomate rayas naturales marvel seco,0,80509,0 -23068,50426414,Tomates minibel seco,Jitomate minibel seco,0,80509,0 -23069,50426415,Tomates oaxacan seco,Jitomate oaxaca seco,0,80509,0 -23070,50426416,Tomates alerta roja seco,Jitomate alerta roja seco,0,80509,0 -23071,50426417,Tomates roma vf seco,Jitomate roma vf seco,0,80509,0 -23072,50426418,Tomates marzano zan seco,Jitomate marzano zan seco,0,80509,0 -23073,50426419,Tomates shirley seco,Jitomate shirley seco,0,80509,0 -23074,50426420,Tomates siberia seco,Jitomate siberia seco,0,80509,0 -23075,50426421,Tomates super beefsteak seco,Jitomate super beefsteak seco,0,80509,0 -23076,50426422,Tomates tigerella seco,Jitomate tigerella seco,0,80509,0 -23077,50426423,Tomates tiny tim seco,Jitomate tiny tim seco,0,80509,0 -23078,50426424,Tomates tumbler seco,Jitomate tumbler seco,0,80509,0 -23079,50426425,Tomates cocktail amarillo seco,,0,80509,0 -23080,50426426,Tomates forma de pera amarillo seco,,0,80509,0 -23081,50426427,Tomates perfección amarilla seco,,0,80509,0 -23082,50426800,Vegetales Nominant Secos,"Vegetales predominantes secos, Nopal seco",0,80509,0 -23083,50426801,Alfalfa seca,,0,80509,0 -23084,50426802,Hojas de aloe seca,"Hoja de sábila seca, Penca de sábila seca",0,80509,0 -23085,50426803,Apio seco,,0,80509,0 -23086,50426804,Arrurruz seca,,0,80509,0 -23087,50426805,Punta de flecha seca,Sagittaria seca,0,80509,0 -23088,50426806,rúcula seca,,0,80509,0 -23089,50426807,Arum seca,Taro seco,0,80509,0 -23090,50426808,Raíz de bambú seca,,0,80509,0 -23091,50426809,Hojas de plátano seca,,0,80509,0 -23092,50426810,Batatas seca,,0,80509,0 -23093,50426811,Brotes de frijol secos,,0,80509,0 -23094,50426812,Top de remolacha seca,Betabel seco,0,80509,0 -23095,50426813,Melón agrio seco,Balsamina seca,0,80509,0 -23096,50426814,Bayas de alcaparra secas,,0,80509,0 -23097,50426815,Carob seco,Algarrobo seco,0,80509,0 -23098,50426816,Cha-om seco,,0,80509,0 -23099,50426817,Chayote o guatila seco,,0,80509,0 -23100,50426818,Garbanzo seco,,0,80509,0 -23101,50426819,Verde de crisantemos seco,,0,80509,0 -23102,50426820,Verde de diente de león seco,,0,80509,0 -23103,50426821,Diente de león seco,,0,80509,0 -23104,50426822,Dasheen seco,,0,80509,0 -23105,50426823,Puntas de alverja seco,,0,80509,0 -23106,50426824,Diakon seco,Rábano seco,0,80509,0 -23107,50426825,Donqua seca,Ginseng hembra seca,0,80509,0 -23108,50426826,Helecho seco,Brotes de helecho seco,0,80509,0 -23109,50426827,Gai choi seco,Mostaza castaña seca,0,80509,0 -23110,50426828,Gailon seco,Brécol chino seco,0,80509,0 -23111,50426829,Galanga seca,Jengibre azul seco,0,80509,0 -23112,50426830,Raíz de jengibre seca,,0,80509,0 -23113,50426831,Gobo seco,Garduña seca,0,80509,0 -23114,50426832,Brote de lúpulo seco,,0,80509,0 -23115,50426833,Rabano blanco seco,Rábano de caballo seco,0,80509,0 -23116,50426834,Jicama seca,,0,80509,0 -23117,50426835,Kuduz seca,Kudzu seco,0,80509,0 -23118,50426836,Bulbo de margarita seco,Bulbo de lili chino seco,0,80509,0 -23119,50426837,Linkok seco,,0,80509,0 -23120,50426838,Lo bok seco,,0,80509,0 -23121,50426839,Frijol largo seco,Fríjol serpiente seco,0,80509,0 -23122,50426840,Raíz de lotus seco,Rosa del Nilo seca,0,80509,0 -23123,50426841,Hojas de maguey seca,"Hoja de agave seca, Penca de maguey seca",0,80509,0 -23124,50426842,Mallows seco,Malva seca,0,80509,0 -23125,50426843,Sapote mamey seco,Mamey seco,0,80509,0 -23126,50426844,Moap seco,,0,80509,0 -23127,50426845,Moo seco,,0,80509,0 -23128,50426846,Moqua seca,,0,80509,0 -23129,50426847,Opos secos,,0,80509,0 -23130,50426848,Corazon de palma seco,Palmito seco,0,80509,0 -23131,50426849,Paprika seca,,0,80509,0 -23132,50426850,Purslane seca,Verdolaga seca,0,80509,0 -23133,50426851,Raddichios seca,Achicoria morada seca,0,80509,0 -23134,50426852,Sinquas seca,Zapallo seco,0,80509,0 -23135,50426853,Frijol de soya secos,,0,80509,0 -23136,50426854,Spoonwart seco,Hierba del escorbuto seca,0,80509,0 -23137,50426855,Uva tassele hyacinth seco,,0,80509,0 -23138,50426856,Taro seco,Tales seca,0,80509,0 -23139,50426857,Hoja de taro seca,,0,80509,0 -23140,50426858,Tallo de taro seco,,0,80509,0 -23141,50426859,Tapeguaje seco,,0,80509,0 -23142,50426860,Verde tierno seco,,0,80509,0 -23143,50426861,Tindora seco,,0,80509,0 -23144,50426862,Árbol de cebolla seco,Cebollas egipcias seca,0,80509,0 -23145,50426863,Udo seco,,0,80509,0 -23146,50426864,Agua castaño seco,Castaña de agua china seca,0,80509,0 -23147,50426865,Yumpi seco,Akankong seco,0,80509,0 -23148,50426866,Yautia seco,Mapuey seco,0,80509,0 -23149,50426867,Yu choy seco,,0,80509,0 -23150,50426868,Yuca seca,,0,80509,0 -23151,50433800,Ajos orgánicos secos,,0,80509,0 -23152,50433801,Ajo tarde california orgánico seco,,0,80509,0 -23153,50433802,Ajo tallos chinos orgánico seco,,0,80509,0 -23154,50433803,Ajo cebollin orgánico seco,,0,80509,0 -23155,50433804,Ajo germidor orgánico seco,,0,80509,0 -23156,50433805,Ajo guardado largo orgánico seco,,0,80509,0 -23157,50433806,Ajo ramson orgánico seco,,0,80509,0 -23158,50433807,Ajo rocambola orgánico seco,,0,80509,0 -23159,50433808,Ajo rosa de lautrec orgánico seco,,0,80509,0 -23160,50433809,Ajo solent blanco orgánico seco,,0,80509,0 -23161,50433810,Ajo morado español orgánico seco,,0,80509,0 -23162,50433811,Ajo valentina italiana orgánico seco,,0,80509,0 -23163,50434100,Hierbas orgánicas secas,"Perejil orgánico seco, Yerbabuena o hierbabuena orgánica seca",0,80509,0 -23164,50434101,Albahaca orgánica seca,,0,80509,0 -23165,50434102,Laurel orgánico seco,,0,80509,0 -23166,50434103,Borraja orgánica seca,,0,80509,0 -23167,50434104,Alcaravea orgánica seca,,0,80509,0 -23168,50434105,Perifollo orgánico seco,,0,80509,0 -23169,50434106,Cilantro orgánico seco,,0,80509,0 -23170,50434107,Chipolinos orgánico seco,,0,80509,0 -23171,50434108,Hoja de curry orgánica seca,,0,80509,0 -23172,50434109,Eneldo orgánico seco,,0,80509,0 -23173,50434110,Epazote orgánico seco,,0,80509,0 -23174,50434111,Fenogreco orgánico seco,,0,80509,0 -23175,50434112,Limonaria orgánica seca,Té de limón orgánico seco,0,80509,0 -23176,50434113,Mejorana orgánica seca,,0,80509,0 -23177,50434114,Menta orgánica seca,,0,80509,0 -23178,50434115,Orégano orgánico seco,,0,80509,0 -23179,50434116,Pápalo orgánico seco,,0,80509,0 -23180,50434117,Pápalo delgado orgánico seco,,0,80509,0 -23181,50434118,Perilla orgánica seca,Albahaca japonesa orgánica seca,0,80509,0 -23182,50434119,Recao orgánico seco,,0,80509,0 -23183,50434120,Romero orgánico seco,,0,80509,0 -23184,50434121,Salvia orgánica seca,,0,80509,0 -23185,50434122,Salsifí orgánica seca,Barba cabruna orgánica seca,0,80509,0 -23186,50434123,Satureja orgánica seca,Ajedreas orgánica seca,0,80509,0 -23187,50434124,Estragón orgánico seco,Tarragón orgánico seco,0,80509,0 -23188,50434125,Tomillo orgánico seco,,0,80509,0 -23189,50434126,Tumeric orgánico seco,Cúrcuma orgánica seca,0,80509,0 -23190,50434127,Verdolaga orgánica seca,,0,80509,0 -23191,50435200,Cebollas orgánicas secas,,0,80509,0 -23192,50435201,Cebolla albion orgánica seca,,0,80509,0 -23193,50435202,Cebolla alisia craig orgánica seca,,0,80509,0 -23194,50435203,Cebolla hirviendo orgánica seca,,0,80509,0 -23195,50435204,Cebolla bufalo orgánica seca,,0,80509,0 -23196,50435205,Cebolla bulbo orgánica seca,,0,80509,0 -23197,50435206,Cebolla cremosa orgánica seca,,0,80509,0 -23198,50435207,Cebolla amarillo expreso o – x orgánica seca,,0,80509,0 -23199,50435208,Cebolla kelsae orgánica seca,,0,80509,0 -23200,50435209,Cebolla gigante marshal globo fen orgánica seca,,0,80509,0 -23201,50435210,Cebolla perla orgánica seca,,0,80509,0 -23202,50435211,Cebolla baron rojo orgánica seca,,0,80509,0 -23203,50435212,Cebolla roja orgánica seca,Cebolla morada orgánica seca,0,80509,0 -23204,50435213,Cebolla rijnsberger orgánica seca,,0,80509,0 -23205,50435214,Cebolla senshyu semi – globo amarillo orgánica seca,,0,80509,0 -23206,50435215,Cebolla sturon orgánica seca,,0,80509,0 -23207,50435216,Cebolla sturgatter orgánica seca,,0,80509,0 -23208,50435217,Cebolla dulce orgánica seca,,0,80509,0 -23209,50435218,Cebolla torpedo o rojo italiana orgánica seca,,0,80509,0 -23210,50435219,Cebolla roja guardada orgánica seca,,0,80509,0 -23211,50435220,Cebolla blanca guardada orgánica seca,,0,80509,0 -23212,50435221,Cebolla amarilla guardada orgánica seca,,0,80509,0 -23213,50435800,Vegetales marinos orgánicos secos,,0,80509,0 -23214,50435801,Agar-agar orgánico seco,,0,80509,0 -23215,50435802,Arame orgánica seca,,0,80509,0 -23216,50435803,Alga dulce orgánica seca,,0,80509,0 -23217,50435804,Habichuelas vert de mer orgánica seca,,0,80509,0 -23218,50435805,Hijiki orgánica seca,,0,80509,0 -23219,50435806,Musgo irlandés orgánica seca,,0,80509,0 -23220,50435807,Quelpo orgánico seco,,0,80509,0 -23221,50435808,Laver orgánico seco,,0,80509,0 -23222,50435809,Nori orgánico seco,,0,80509,0 -23223,50435810,Alga roja orgánica seca,,0,80509,0 -23224,50435811,Kale de mar orgánica seca,,0,80509,0 -23225,50435812,Lechuga de mar orgánica seca,,0,80509,0 -23226,50435813,Algas de mar orgánica seca,,0,80509,0 -23227,50435814,Spirulina orgánica seca,,0,80509,0 -23228,50435815,Susabi nuri orgánica seca,,0,80509,0 -23229,50435816,Wakame orgánica seca,,0,80509,0 -23230,50436800,Vegetales Nominant orgánicos secos,"Vegetales predominantes orgánicos secos, Nopal orgánico seco",0,80509,0 -23231,50436801,Alfalfa orgánica seca,,0,80509,0 -23232,50436802,Hojas de aloe orgánica seca,"Hoja de sábila orgánica seca, Penca de sábila orgánica seca",0,80509,0 -23233,50436803,Apio orgánico seco,,0,80509,0 -23234,50436804,Arrurruz orgánica seca,,0,80509,0 -23235,50436805,Punta de flecha orgánica seca,Sagittaria orgánica seca,0,80509,0 -23236,50436806,rúcula orgánica seca,,0,80509,0 -23237,50436807,Arum orgánica seca,Taro orgánico seco,0,80509,0 -23238,50436808,Raíz de bambú orgánica seca,,0,80509,0 -23239,50436809,Hojas de plátano orgánica seca,,0,80509,0 -23240,50436810,Batatas orgánica seca,,0,80509,0 -23241,50436811,Brotes de frijol orgánicos secos,,0,80509,0 -23242,50436812,Top de remolacha orgánica seca,Betabel orgánico seco,0,80509,0 -23243,50436813,Melón agrio orgánico seco,Balsamina orgánica seca,0,80509,0 -23244,50436814,Bayas de alcaparra orgánicas secas,,0,80509,0 -23245,50436815,Carob orgánico seco,Algarrobo orgánico seco,0,80509,0 -23246,50436816,Cha-om orgánico seco,,0,80509,0 -23247,50436817,Chayote o guatila orgánico seco,,0,80509,0 -23248,50436818,Garbanzo orgánico seco,,0,80509,0 -23249,50436819,Verde de crisantemos orgánico seco,,0,80509,0 -23250,50436820,Verde de diente de león orgánico seco,,0,80509,0 -23251,50436821,Diente de león orgánico seco,,0,80509,0 -23252,50436822,Dasheen orgánico seco,,0,80509,0 -23253,50436823,Puntas de alverja orgánico seco,,0,80509,0 -23254,50436824,Diakon orgánico seco,Rábano orgánico seco,0,80509,0 -23255,50436825,Donqua orgánica seca,Ginseng hembra orgánica seca,0,80509,0 -23256,50436826,Helecho orgánico seco,Brotes de helecho orgánico seco,0,80509,0 -23257,50436827,Gai choi orgánico seco,Mostaza castaña orgánica seca,0,80509,0 -23258,50436828,Gailon orgánico seco,Brécol chino orgánico seco,0,80509,0 -23259,50436829,Galanga orgánica seca,Jengibre azul orgánico seco,0,80509,0 -23260,50436830,Raíz de jengibre orgánica seca,,0,80509,0 -23261,50436831,Gobo orgánico seco,Garduña orgánica seca,0,80509,0 -23262,50436832,Brote de lúpulo orgánico seco,,0,80509,0 -23263,50436833,Rabano blanco orgánico seco,Rábano de caballo orgánico seco,0,80509,0 -23264,50436834,Jicama orgánica seca,,0,80509,0 -23265,50436835,Kuduz orgánica seca,Kudzu orgánico seco,0,80509,0 -23266,50436836,Bulbo de margarita orgánico seco,Bulbo de lili chino orgánico seco,0,80509,0 -23267,50436837,Linkok orgánico seco,,0,80509,0 -23268,50436838,Lo bok orgánico seco,,0,80509,0 -23269,50436839,Frijol largo orgánico seco,Fríjol serpiente orgánico seco,0,80509,0 -23270,50436840,Raíz de lotus orgánico seco,Rosa del Nilo orgánica seca,0,80509,0 -23271,50436841,Hojas de maguey orgánica seca,"Hoja de agave orgánica seca, Penca de maguey orgánica seca",0,80509,0 -23272,50436842,Mallows orgánico seco,Malva orgánica seca,0,80509,0 -23273,50436843,Sapote mamey orgánico seco,Mamey orgánico seco,0,80509,0 -23274,50436844,Moap orgánico seco,,0,80509,0 -23275,50436845,Moo orgánico seco,,0,80509,0 -23276,50436846,Moqua orgánica seca,,0,80509,0 -23277,50436847,Opos orgánicos secos,,0,80509,0 -23278,50436848,Corazon de palma orgánico seco,Palmito orgánico seco,0,80509,0 -23279,50436849,Paprika orgánica seca,,0,80509,0 -23280,50436850,Purslane orgánica seca,Verdolaga orgánica seca,0,80509,0 -23281,50436851,Raddichios orgánica seca,Achicoria morada orgánica seca,0,80509,0 -23282,50436852,Sinquas orgánica seca,Zapallo orgánico seco,0,80509,0 -23283,50436853,Frijol de soya orgánicos secos,,0,80509,0 -23284,50436854,Spoonwart orgánico seco,Hierba del escorbuto orgánica seca,0,80509,0 -23285,50436855,Uva tassele hyacinth orgánico seco,,0,80509,0 -23286,50436856,Taro orgánico seco,Tales orgánica seca,0,80509,0 -23287,50436857,Hoja de taro orgánica seca,,0,80509,0 -23288,50436858,Tallo de taro orgánico seco,,0,80509,0 -23289,50436859,Tapeguaje orgánico seco,,0,80509,0 -23290,50436860,Verde tierno orgánico seco,,0,80509,0 -23291,50436861,Tindora orgánico seco,,0,80509,0 -23292,50436862,Árbol de cebolla orgánico seco,Cebollas egipcias orgánica seca,0,80509,0 -23293,50436863,Udo orgánico seco,,0,80509,0 -23294,50436864,Agua castaño orgánico seco,Castaña de agua china orgánica seca,0,80509,0 -23295,50436865,Yumpi orgánico seco,Akankong orgánico seco,0,80509,0 -23296,50436866,Yautia orgánico seco,Mapuey orgánico seco,0,80509,0 -23297,50436867,Yu choy orgánico seco,,0,80509,0 -23298,50436868,Yuca orgánica seca,,0,80509,0 -23299,50441700,Aguacates congelados,,0,80509,0 -23300,50441701,Aguacate ajax b – 7 congelado,,0,80509,0 -23301,50441702,Aguacate arue congelado,,0,80509,0 -23302,50441703,Aguacate bacon congelado,,0,80509,0 -23303,50441704,Aguacate benik congelado,,0,80509,0 -23304,50441705,Aguacate bernecker congelado,,0,80509,0 -23305,50441706,Aguacate beta congelado,,0,80509,0 -23306,50441707,Aguacate biondo congelado,,0,80509,0 -23307,50441708,Aguacate príncipe negro congelado,,0,80509,0 -23308,50441709,Aguacate blair congelado,,0,80509,0 -23309,50441710,Aguacate blair cabina congelado,,0,80509,0 -23310,50441711,Aguacate cabina 1 congelado,,0,80509,0 -23311,50441712,Aguacate cabina 3 congelado,,0,80509,0 -23312,50441713,Aguacate cabina 5 congelado,,0,80509,0 -23313,50441714,Aguacate cabina 7 congelado,,0,80509,0 -23314,50441715,Aguacate cabina 8 congelado,,0,80509,0 -23315,50441716,Aguacate brooks 1978 congelado,,0,80509,0 -23316,50441717,Aguacate brookslate congelado,,0,80509,0 -23317,50441718,Aguacate california haas congelado,,0,80509,0 -23318,50441719,Aguacate catalina congelado,,0,80509,0 -23319,50441720,Aguacate chica congelado,,0,80509,0 -23320,50441721,Aguacate choquette congelado,,0,80509,0 -23321,50441722,Aguacate cristina congelado,,0,80509,0 -23322,50441723,Aguacate collison congelado,,0,80509,0 -23323,50441724,Aguacate donnie congelado,,0,80509,0 -23324,50441725,Aguacate dr dupis numero 2 congelado,,0,80509,0 -23325,50441726,Aguacate dr. dupie congelado,,0,80509,0 -23326,50441727,Aguacate ettinger congelado,,0,80509,0 -23327,50441728,Aguacate fuch congelado,,0,80509,0 -23328,50441729,Aguacate fuch gwen congelado,,0,80509,0 -23329,50441730,Aguacate fuerte congelado,,0,80509,0 -23330,50441731,Aguacate gotham congelado,,0,80509,0 -23331,50441732,Aguacate gossman congelado,,0,80509,0 -23332,50441733,Aguacate guatemala sin pepa congelado,,0,80509,0 -23333,50441734,Aguacate hall congelado,,0,80509,0 -23334,50441735,Aguacate hardee congelado,,0,80509,0 -23335,50441736,Aguacate haas congelado,,0,80509,0 -23336,50441737,Aguacate herman congelado,,0,80509,0 -23337,50441738,Aguacate hickson congelado,,0,80509,0 -23338,50441739,Aguacate k-5 congelado,,0,80509,0 -23339,50441740,Aguacate k- 9 congelado,,0,80509,0 -23340,50441741,Aguacate cordero haas congelado,,0,80509,0 -23341,50441742,Aguacate leona congelado,,0,80509,0 -23342,50441743,Aguacate leona linda congelado,,0,80509,0 -23343,50441744,Aguacate lisa p congelado,,0,80509,0 -23344,50441745,Aguacate lisa loretta congelado,,0,80509,0 -23345,50441746,Aguacate loretta congelado,,0,80509,0 -23346,50441747,Aguacate lula congelado,,0,80509,0 -23347,50441748,Aguacate lula mascarthur congelado,,0,80509,0 -23348,50441749,Aguacate marcus congelado,,0,80509,0 -23349,50441750,Aguacate melendez congelado,,0,80509,0 -23350,50441751,Aguacate meya congelado,,0,80509,0 -23351,50441752,Aguacate miguel p congelado,,0,80509,0 -23352,50441753,Aguacate monroe congelado,,0,80509,0 -23353,50441754,Aguacate murrieta verde congelado,,0,80509,0 -23354,50441755,Aguacate nabal congelado,,0,80509,0 -23355,50441756,Aguacate nadir congelado,,0,80509,0 -23356,50441757,Aguacate nesbitt congelado,,0,80509,0 -23357,50441758,Aguacate peterson congelado,,0,80509,0 -23358,50441759,Aguacate pinelli congelado,,0,80509,0 -23359,50441760,Aguacate pinkerton congelado,,0,80509,0 -23360,50441761,Aguacate pollock congelado,,0,80509,0 -23361,50441762,Aguacate puebla congelado,,0,80509,0 -23362,50441763,Aguacate reed congelado,,0,80509,0 -23363,50441764,Aguacaterue congelado,,0,80509,0 -23364,50441765,Aguacateruehle congelado,,0,80509,0 -23365,50441766,Aguacate ryan congelado,,0,80509,0 -23366,50441767,Aguacate semil congelado,,0,80509,0 -23367,50441768,Aguacate semil 43 congelado,,0,80509,0 -23368,50441769,Aguacate simonds congelado,,0,80509,0 -23369,50441770,Aguacate simpson congelado,,0,80509,0 -23370,50441771,Aguacate taylor congelado,,0,80509,0 -23371,50441772,Aguacate tonelada congelado,,0,80509,0 -23372,50441773,Aguacate torre congelado,,0,80509,0 -23373,50441774,Aguacate torre li congelado,,0,80509,0 -23374,50441775,Aguacate trapp congelado,,0,80509,0 -23375,50441776,Aguacate semilla de caribe congelado,,0,80509,0 -23376,50441777,Aguacate wagner congelado,,0,80509,0 -23377,50441778,Aguacate waldin congelado,,0,80509,0 -23378,50441779,Aguacate wurtz congelado,,0,80509,0 -23379,50441780,Aguacate zio p congelado,,0,80509,0 -23380,50441781,Aguacate ziu congelado,,0,80509,0 -23381,50441782,Aguacate zultano congelado,,0,80509,0 -23382,50442000,Brócolis congeladas,,0,80509,0 -23383,50442001,Brócolini congelado,,0,80509,0 -23384,50442002,Brócoli romanesco congelado,,0,80509,0 -23385,50442003,Brócoli raab congelado,,0,80509,0 -23386,50442004,Brócoli chino congelado,,0,80509,0 -23387,50442500,Zanahorias congeladas,,0,80509,0 -23388,50442501,Zanahoria amsterdam congelada,,0,80509,0 -23389,50442502,Zanahoria rey de otoño congelada,,0,80509,0 -23390,50442503,Zanahoria berlicum congelada,,0,80509,0 -23391,50442504,Zanahoria chantenay congelada,,0,80509,0 -23392,50442505,Zanahoria nantes congelada,,0,80509,0 -23393,50442506,Zanahoria mercado de parís congelada,,0,80509,0 -23394,50442600,Coliflores congeladas,,0,80509,0 -23395,50442601,Coliflor todo el año congelada,,0,80509,0 -23396,50442602,Coliflor alverda congelada,,0,80509,0 -23397,50442603,coliflor otoño gigante 3 congelada,,0,80509,0 -23398,50442604,Coliflo rdok elgon congelada,,0,80509,0 -23399,50442605,Coliflor bola de nieve temprana congelada,,0,80509,0 -23400,50442606,Coliflor luz de lima congelada,,0,80509,0 -23401,50442607,Coliflor minaret congelada,,0,80509,0 -23402,50442608,Coliflor buque de naranja congelada,,0,80509,0 -23403,50442609,Coliflor capa morada congelada,,0,80509,0 -23404,50442610,Coliflor bola de nieve congelada,,0,80509,0 -23405,50442611,Coliflor invierno walcheren 3 congelada,,0,80509,0 -23406,50442612,Coliflor piedra blanca congelada,,0,80509,0 -23407,50443200,Maíces congelados,,0,80509,0 -23408,50443201,Maíz aloha congelado,,0,80509,0 -23409,50443202,Maíz alpine congelado,,0,80509,0 -23410,50443203,Maíz ambrosia congelado,,0,80509,0 -23411,50443204,Maíz argent congelado,,0,80509,0 -23412,50443205,Maíz aspen congelado,,0,80509,0 -23413,50443206,Maíz avalancha congelado,,0,80509,0 -23414,50443207,Maíz biqueen congelado,,0,80509,0 -23415,50443208,Maíz bodacious congelado,,0,80509,0 -23416,50443209,Maíz mantequilla y azúcar congelado,,0,80509,0 -23417,50443210,Maíz calico belle congelado,,0,80509,0 -23418,50443211,Maíz camelot congelado,,0,80509,0 -23419,50443212,Maíz challengercrisp ‘n dulce congelado,,0,80509,0 -23420,50443213,Maíz campeón congelado,,0,80509,0 -23421,50443214,Maíz algodón de azúcar congelado,,0,80509,0 -23422,50443215,Maíz dártagnan congelado,,0,80509,0 -23423,50443216,Maíz dazzle congelado,,0,80509,0 -23424,50443217,Maíz diamante y oro congelado,,0,80509,0 -23425,50443218,Maíz divinidad congelado,,0,80509,0 -23426,50443219,Maíz delicia doble congelado,,0,80509,0 -23427,50443220,Maíz gema doble congelado,,0,80509,0 -23428,50443221,Maíz earlivee congelado,,0,80509,0 -23429,50443222,Maíz temprano extra dulce congelado,,0,80509,0 -23430,50443223,Maíz excel congelado,,0,80509,0 -23431,50443224,Maíz cruz dorada bantam congelado,,0,80509,0 -23432,50443225,Maíz miel y crema congelado,,0,80509,0 -23433,50443226,Maíz miel y perla congelado,,0,80509,0 -23434,50443227,Maíz miel dulce congelado,,0,80509,0 -23435,50443228,Maíz hudson congelado,,0,80509,0 -23436,50443229,Maíz dorado illini congelado,,0,80509,0 -23437,50443230,Maíz illini extra dulce congelado,,0,80509,0 -23438,50443231,Maíz increíble congelado,,0,80509,0 -23439,50443232,Maíz lochief congelado,,0,80509,0 -23440,50443233,Maíz jubileo congelado,,0,80509,0 -23441,50443234,Maíz jupbileo super dulce congelado,,0,80509,0 -23442,50443235,Maíz confite korn congelado,,0,80509,0 -23443,50443236,Maíz beso y acuso congelado,,0,80509,0 -23444,50443237,Maíz lancelot congelado,,0,80509,0 -23445,50443238,Maíz dulce maple congelado,,0,80509,0 -23446,50443239,Maíz medley congelado,,0,80509,0 -23447,50443240,Maíz merlin congelado,,0,80509,0 -23448,50443241,Maíz milagro congelado,,0,80509,0 -23449,50443242,Maíz nk – 199 congelado,,0,80509,0 -23450,50443243,Maíz durazno y crema congelado,,0,80509,0 -23451,50443244,Maíz blanco perla congelado,,0,80509,0 -23452,50443245,Maíz pegasus congelado,,0,80509,0 -23453,50443246,Maíz fenomenal congelado,,0,80509,0 -23454,50443247,Maíz dama de platino congelado,,0,80509,0 -23455,50443248,Maíz precioso congelado,,0,80509,0 -23456,50443249,Maíz pristine congelado,,0,80509,0 -23457,50443250,Maíz rápido congelado,,0,80509,0 -23458,50443251,Maíz radiante congelado,,0,80509,0 -23459,50443252,Maíz seneca brave congelado,,0,80509,0 -23460,50443253,Maíz amanecer seneca congelado,,0,80509,0 -23461,50443254,Maíz horizonte seneca congelado,,0,80509,0 -23462,50443255,Maíz brillo de estrella seneca congelado,,0,80509,0 -23463,50443256,Maíz noche blanca seneca congelado,,0,80509,0 -23464,50443257,Maíz showcase congelado,,0,80509,0 -23465,50443258,Maíz reina plateada congelado,,0,80509,0 -23466,50443259,Maíz bella nieve congelado,,0,80509,0 -23467,50443260,Maíz nieve de primavera congelado,,0,80509,0 -23468,50443261,Maíz premio de primavera congelado,,0,80509,0 -23469,50443262,Maíz azúcar y oro congelado,,0,80509,0 -23470,50443263,Maíz rollo de azúcar congelado,,0,80509,0 -23471,50443264,Maíz nieve de azúcar congelado,,0,80509,0 -23472,50443265,Maíz baile de sol congelado,,0,80509,0 -23473,50443266,Maíz estrella tell congelado,,0,80509,0 -23474,50443267,Maíz terminador congelado,,0,80509,0 -23475,50443268,Maíz tesoro congelado,,0,80509,0 -23476,50443269,Maíz tuxedo congelado,,0,80509,0 -23477,50445200,Cebollas congeladas,,0,80509,0 -23478,50445201,Cebolla albion congelada,,0,80509,0 -23479,50445202,Cebolla alisia craig congelada,,0,80509,0 -23480,50445203,Cebolla hirviendo congelada,,0,80509,0 -23481,50445204,Cebolla bufalo congelada,,0,80509,0 -23482,50445205,Cebolla bulbo congelada,,0,80509,0 -23483,50445206,Cebolla cremosa congelada,,0,80509,0 -23484,50445207,Cebolla amarillo expreso o – x congelada,,0,80509,0 -23485,50445208,Cebolla kelsae congelada,,0,80509,0 -23486,50445209,Cebolla gigante marshal globo fen congelada,,0,80509,0 -23487,50445210,Cebolla perla congelada,,0,80509,0 -23488,50445211,Cebolla baron rojo congelada,,0,80509,0 -23489,50445212,Cebolla roja congelada,Cebolla morada congelada,0,80509,0 -23490,50445213,Cebolla rijnsberger congelada,,0,80509,0 -23491,50445214,Cebolla senshyu semi – globo amarillo congelada,,0,80509,0 -23492,50445215,Cebolla sturon congelada,,0,80509,0 -23493,50445216,Cebolla sturgatter congelada,,0,80509,0 -23494,50445217,Cebolla dulce congelada,,0,80509,0 -23495,50445218,Cebolla torpedo o rojo italiana congelada,,0,80509,0 -23496,50445219,Cebolla roja guardada congelada,,0,80509,0 -23497,50445220,Cebolla blanca guardada congelada,,0,80509,0 -23498,50445221,Cebolla amarilla guardada congelada,,0,80509,0 -23499,50445500,Pimientos congelados,Chiles congelados,0,80509,0 -23500,50445501,Pimentón ají congelado,Ají congelado,0,80509,0 -23501,50445502,Pimentón árbol congelado,Chile de árbol congelado,0,80509,0 -23502,50445503,Pimentón queso congelado,,0,80509,0 -23503,50445504,Pimentón chilaca congelado,Chilaca congelado,0,80509,0 -23504,50445505,Pimentón cubanelles congelado,Chile cubanelle o pimienta italiana congelada,0,80509,0 -23505,50445506,Pimentón fresno congelado,Chile fresno congelado,0,80509,0 -23506,50445507,Pimentón kapia congelado,Chile kapia congelado,0,80509,0 -23507,50445508,Pimentón coreano congelado,Chile cheongyang congelado,0,80509,0 -23508,50445509,Pimentón manzano congelado,Chile manzano o de cera congelado,0,80509,0 -23509,50445510,Pimentón melrose congelado,,0,80509,0 -23510,50445511,Pimentón chile amarillo congelado,Chile peruano amarillo congelado,0,80509,0 -23511,50445512,Pimentón aji dulce congelado,Ají dulce congelado,0,80509,0 -23512,50445513,Pimentón anaheim congelado,,0,80509,0 -23513,50445514,Pimentón ancho congelado,Chile ancho o mulato congelado,0,80509,0 -23514,50445515,Pimentón campana congelado,Chile campana congelado,0,80509,0 -23515,50445516,Pimentón cascabel congelado,Chile cascabel congelado,0,80509,0 -23516,50445517,Pimentón cayenne congelado,Pimienta de Cayena o roja congelada,0,80509,0 -23517,50445518,Pimentón anfitrión cereza congelado,,0,80509,0 -23518,50445519,Pimentón chitespin congelado,Chile chiltepín congelado,0,80509,0 -23519,50445520,Pimentón dedo picante congelado,Chile Jwala congelado,0,80509,0 -23520,50445521,Pimentón guajillo congelado,Chile guajillo congelado,0,80509,0 -23521,50445522,Pimentón guerro congelado,Chile güero congelado,0,80509,0 -23522,50445523,Pimentón habanero congelado,Chile habanero congelado,0,80509,0 -23523,50445524,Pimentón cera de hungria congelado,Pimentón húngaro congelado,0,80509,0 -23524,50445525,Pimentón jalapeño congelado,"Chile cuaresmeño congelado, Chile jalapeño, seco es chipotle o morita congelado",0,80509,0 -23525,50445526,Pimentón picante largo congelado,Chile cola de rata congelado,0,80509,0 -23526,50445527,Pimentón marasol congelado,Chile mirasol congelado,0,80509,0 -23527,50445528,Pimentón pasilla congelado,Chile pasilla congelado,0,80509,0 -23528,50445529,Pimentón peperoncini congelado,,0,80509,0 -23529,50445530,Pimentón pequin congelado,Chile piquín congelado,0,80509,0 -23530,50445531,Pimentón pimiento congelado,Pimiento morrón congelado,0,80509,0 -23531,50445532,Pimentón poblano congelado,Chile poblano congelado,0,80509,0 -23532,50445533,Pimentón scotch bonnet congelado,Pimientos Bonney o rojos Caribe congelado,0,80509,0 -23533,50445534,Pimentón serrano congelado,"Chile serrano congelado, Chile verde congelado",0,80509,0 -23534,50445535,Pimentón tabasco congelado,Chile tabasco congelado,0,80509,0 -23535,50445536,Pimentón tai congelado,Chile ojo de pájaro congelado,0,80509,0 -23536,50445537,Pimentón tepin congelado,Chile mosquito o catarina congelado,0,80509,0 -23537,50445600,Papas congeladas,,0,80509,0 -23538,50445601,Papa blanca larga congelada,,0,80509,0 -23539,50445602,Papa redonda blanca congelada,,0,80509,0 -23540,50445603,Papa redonda roja congelada,Papa de cambray congelada,0,80509,0 -23541,50445604,Papa russet congelada,,0,80509,0 -23542,50445605,Papa morada congelada,,0,80509,0 -23543,50445606,Papa amarilla congelada,,0,80509,0 -23544,50445607,Papa nueva congelada,,0,80509,0 -23545,50445608,Papa especial congelada,,0,80509,0 -23546,50445609,Papa coctel congelada,,0,80509,0 -23547,50445610,Papa peruanita congelada,,0,80509,0 -23548,50445611,Papa huayro congelada,,0,80509,0 -23549,50446200,Calabazas congeladas,,0,80509,0 -23550,50446201,Calabaza boston congelada,,0,80509,0 -23551,50446202,Calabaza buttemut congelada,,0,80509,0 -23552,50446203,Calabaza costata romanesca congelada,,0,80509,0 -23553,50446204,Calabaza crookneck congelada,,0,80509,0 -23554,50446205,Calabaza cucuzza congelada,,0,80509,0 -23555,50446206,Calabaza delicata congelada,,0,80509,0 -23556,50446207,Calabaza delisioso congelada,,0,80509,0 -23557,50446208,Calabaza verano dorado cuello torcido congelada,,0,80509,0 -23558,50446209,Calabaza prolífico temprano cuello recto congelada,,0,80509,0 -23559,50446210,Calabaza oro congelada,,0,80509,0 -23560,50446211,Calabaza jack pequeño congelada,,0,80509,0 -23561,50446212,Calabaza campo de kentucky congelada,,0,80509,0 -23562,50446213,Calabaza marrow congelada,,0,80509,0 -23563,50446214,Calabaza medio oriente congelada,,0,80509,0 -23564,50446215,Calabaza miniatura congelada,,0,80509,0 -23565,50446216,Calabaza orangetti congelada,,0,80509,0 -23566,50446217,Calabaza pattypan congelada,,0,80509,0 -23567,50446218,Calabaza rondini congelada,,0,80509,0 -23568,50446219,Calabaza redonda congelada,,0,80509,0 -23569,50446220,Calabaza espagueti congelada,,0,80509,0 -23570,50446221,Calabaza estripeti congelada,,0,80509,0 -23571,50446222,Calabaza molde dulce congelada,,0,80509,0 -23572,50446223,Calabaza empanada dulce congelada,,0,80509,0 -23573,50446224,Calabaza premio triple congelada,,0,80509,0 -23574,50446225,Calabaza waltham butternut congelada,,0,80509,0 -23575,50446226,Calabaza arbusto scallop amarillo congelada,,0,80509,0 -23576,50446227,Calabaza cuello recto amarillo congelada,,0,80509,0 -23577,50446228,Calabaza zafiro congelada,,0,80509,0 -23578,50446229,Calabaza zucchini congelada,,0,80509,0 -23579,50446800,Vegetales Nominant congelados,"Vegetales predominantes congelados, Nopal congelado",0,80509,0 -23580,50446801,Alfalfa congelada,,0,80509,0 -23581,50446802,Hojas de aloe congelada,"Hoja de sábila congelada, Penca de sábila congelada",0,80509,0 -23582,50446803,Apio congelado,,0,80509,0 -23583,50446804,Arrurruz congelada,,0,80509,0 -23584,50446805,Punta de flecha congelada,Sagittaria congelada,0,80509,0 -23585,50446806,rúcula congelada,,0,80509,0 -23586,50446807,Arum congelada,Taro congelado,0,80509,0 -23587,50446808,Raíz de bambú congelada,,0,80509,0 -23588,50446809,Hojas de plátano congelada,,0,80509,0 -23589,50446810,Batatas congelada,,0,80509,0 -23590,50446811,Brotes de frijol congelados,,0,80509,0 -23591,50446812,Top de remolacha congelada,Betabel congelado,0,80509,0 -23592,50446813,Melón agrio congelado,Balsamina congelada,0,80509,0 -23593,50446814,Bayas de alcaparra congeladas,,0,80509,0 -23594,50446815,Carob congelado,Algarrobo congelado,0,80509,0 -23595,50446816,Cha-om congelado,,0,80509,0 -23596,50446817,Chayote o guatila congelado,,0,80509,0 -23597,50446818,Garbanzo congelado,,0,80509,0 -23598,50446819,Verde de crisantemos congelado,,0,80509,0 -23599,50446820,Verde de diente de león congelado,,0,80509,0 -23600,50446821,Diente de león congelado,,0,80509,0 -23601,50446822,Dasheen congelado,,0,80509,0 -23602,50446823,Puntas de alverja congelado,,0,80509,0 -23603,50446824,Diakon congelado,Rábano congelado,0,80509,0 -23604,50446825,Donqua congelada,Ginseng hembra congelada,0,80509,0 -23605,50446826,Helecho congelado,Brotes de helecho congelado,0,80509,0 -23606,50446827,Gai choi congelado,Mostaza castaña congelada,0,80509,0 -23607,50446828,Gailon congelado,Brécol chino congelado,0,80509,0 -23608,50446829,Galanga congelada,Jengibre azul congelado,0,80509,0 -23609,50446830,Raíz de jengibre congelada,,0,80509,0 -23610,50446831,Gobo congelado,Garduña congelada,0,80509,0 -23611,50446832,Brote de lúpulo congelado,,0,80509,0 -23612,50446833,Rabano blanco congelado,Rábano de caballo congelado,0,80509,0 -23613,50446834,Jicama congelada,,0,80509,0 -23614,50446835,Kuduz congelada,Kudzu congelado,0,80509,0 -23615,50446836,Bulbo de margarita congelado,Bulbo de lili chino congelado,0,80509,0 -23616,50446837,Linkok congelado,,0,80509,0 -23617,50446838,Lo bok congelado,,0,80509,0 -23618,50446839,Frijol largo congelado,Fríjol serpiente congelado,0,80509,0 -23619,50446840,Raíz de lotus congelado,Rosa del Nilo congelada,0,80509,0 -23620,50446841,Hojas de maguey congelada,"Hoja de agave congelada, Penca de maguey congelada",0,80509,0 -23621,50446842,Mallows congelado,Malva congelada,0,80509,0 -23622,50446843,Sapote mamey congelado,Mamey congelada,0,80509,0 -23623,50446844,Moap congelado,,0,80509,0 -23624,50446845,Moo congelado,,0,80509,0 -23625,50446846,Moqua congelada,,0,80509,0 -23626,50446847,Opos congelados,,0,80509,0 -23627,50446848,Corazon de palma congelado,Palmito congelado,0,80509,0 -23628,50446849,Paprika congelada,,0,80509,0 -23629,50446850,Purslane congelada,Verdolaga congelada,0,80509,0 -23630,50446851,Raddichios congelada,Achicoria morada congelada,0,80509,0 -23631,50446852,Sinquas congelada,Zapallo congelado,0,80509,0 -23632,50446853,Frijol de soya congelados,,0,80509,0 -23633,50446854,Spoonwart congelado,Hierba del escorbuto congelada,0,80509,0 -23634,50446855,Uva tassele hyacinth congelado,,0,80509,0 -23635,50446856,Taro congelado,Tales congelada,0,80509,0 -23636,50446857,Hoja de taro congelada,,0,80509,0 -23637,50446858,Tallo de taro congelado,,0,80509,0 -23638,50446859,Tapeguaje congelado,,0,80509,0 -23639,50446860,Verde tierno congelado,,0,80509,0 -23640,50446861,Tindora congelado,,0,80509,0 -23641,50446862,Árbol de cebolla congelado,Cebollas egipcias congelada,0,80509,0 -23642,50446863,Udo congelado,,0,80509,0 -23643,50446864,Agua castaño congelado,Castaña de agua china congelada,0,80509,0 -23644,50446865,Yumpi congelado,Akankong congelado,0,80509,0 -23645,50446866,Yautia congelado,Mapuey congelado,0,80509,0 -23646,50446867,Yu choy congelado,,0,80509,0 -23647,50446868,Yuca congelada,,0,80509,0 -23648,50451700,Aguacates orgánicos congelados,,0,80509,0 -23649,50451701,Aguacate ajax b – 7 orgánico congelado,,0,80509,0 -23650,50451702,Aguacate arue orgánico congelado,,0,80509,0 -23651,50451703,Aguacate bacon orgánico congelado,,0,80509,0 -23652,50451704,Aguacate benik orgánico congelado,,0,80509,0 -23653,50451705,Aguacate bernecker orgánico congelado,,0,80509,0 -23654,50451706,Aguacate beta orgánico congelado,,0,80509,0 -23655,50451707,Aguacate biondo orgánico congelado,,0,80509,0 -23656,50451708,Aguacate príncipe negro orgánico congelado,,0,80509,0 -23657,50451709,Aguacate blair orgánico congelado,,0,80509,0 -23658,50451710,Aguacate blair cabina orgánico congelado,,0,80509,0 -23659,50451711,Aguacate cabina 1 orgánico congelado,,0,80509,0 -23660,50451712,Aguacate cabina 3 orgánico congelado,,0,80509,0 -23661,50451713,Aguacate cabina 5 orgánico congelado,,0,80509,0 -23662,50451714,Aguacate cabina 7 orgánico congelado,,0,80509,0 -23663,50451715,Aguacate cabina 8 orgánico congelado,,0,80509,0 -23664,50451716,Aguacate brooks 1978 orgánico congelado,,0,80509,0 -23665,50451717,Aguacate brookslate orgánico congelado,,0,80509,0 -23666,50451718,Aguacate california haas orgánico congelado,,0,80509,0 -23667,50451719,Aguacate catalina orgánico congelado,,0,80509,0 -23668,50451720,Aguacate chica orgánico congelado,,0,80509,0 -23669,50451721,Aguacate choquette orgánico congelado,,0,80509,0 -23670,50451722,Aguacate cristina orgánico congelado,,0,80509,0 -23671,50451723,Aguacate collison orgánico congelado,,0,80509,0 -23672,50451724,Aguacate donnie orgánico congelado,,0,80509,0 -23673,50451725,Aguacate dr dupis numero 2 orgánico congelado,,0,80509,0 -23674,50451726,Aguacate dr. dupie orgánico congelado,,0,80509,0 -23675,50451727,Aguacate ettinger orgánico congelado,,0,80509,0 -23676,50451728,Aguacate fuch orgánico congelado,,0,80509,0 -23677,50451729,Aguacate fuch gwen orgánico congelado,,0,80509,0 -23678,50451730,Aguacate fuerte orgánico congelado,,0,80509,0 -23679,50451731,Aguacate gotham orgánico congelado,,0,80509,0 -23680,50451732,Aguacate gossman orgánico congelado,,0,80509,0 -23681,50451733,Aguacate guatemala sin pepa orgánico congelado,,0,80509,0 -23682,50451734,Aguacate hall orgánico congelado,,0,80509,0 -23683,50451735,Aguacate hardee orgánico congelado,,0,80509,0 -23684,50451736,Aguacate haas orgánico congelado,,0,80509,0 -23685,50451737,Aguacate herman orgánico congelado,,0,80509,0 -23686,50451738,Aguacate hickson orgánico congelado,,0,80509,0 -23687,50451739,Aguacate k-5 orgánico congelado,,0,80509,0 -23688,50451740,Aguacate k- 9 orgánico congelado,,0,80509,0 -23689,50451741,Aguacate cordero haas orgánico congelado,,0,80509,0 -23690,50451742,Aguacate leona orgánico congelado,,0,80509,0 -23691,50451743,Aguacate leona linda orgánico congelado,,0,80509,0 -23692,50451744,Aguacate lisa p orgánico congelado,,0,80509,0 -23693,50451745,Aguacate lisa loretta orgánico congelado,,0,80509,0 -23694,50451746,Aguacate loretta orgánico congelado,,0,80509,0 -23695,50451747,Aguacate lula orgánico congelado,,0,80509,0 -23696,50451748,Aguacate lula mascarthur orgánico congelado,,0,80509,0 -23697,50451749,Aguacate marcus orgánico congelado,,0,80509,0 -23698,50451750,Aguacate melendez orgánico congelado,,0,80509,0 -23699,50451751,Aguacate meya orgánico congelado,,0,80509,0 -23700,50451752,Aguacate miguel p orgánico congelado,,0,80509,0 -23701,50451753,Aguacate monroe orgánico congelado,,0,80509,0 -23702,50451754,Aguacate murrieta verde orgánico congelado,,0,80509,0 -23703,50451755,Aguacate nabal orgánico congelado,,0,80509,0 -23704,50451756,Aguacate nadir orgánico congelado,,0,80509,0 -23705,50451757,Aguacate nesbitt orgánico congelado,,0,80509,0 -23706,50451758,Aguacate peterson orgánico congelado,,0,80509,0 -23707,50451759,Aguacate pinelli orgánico congelado,,0,80509,0 -23708,50451760,Aguacate pinkerton orgánico congelado,,0,80509,0 -23709,50451761,Aguacate pollock orgánico congelado,,0,80509,0 -23710,50451762,Aguacate puebla orgánico congelado,,0,80509,0 -23711,50451763,Aguacate reed orgánico congelado,,0,80509,0 -23712,50451764,Aguacaterue orgánico congelado,,0,80509,0 -23713,50451765,Aguacateruehle orgánico congelado,,0,80509,0 -23714,50451766,Aguacate ryan orgánico congelado,,0,80509,0 -23715,50451767,Aguacate semil orgánico congelado,,0,80509,0 -23716,50451768,Aguacate semil 43 orgánico congelado,,0,80509,0 -23717,50451769,Aguacate simonds orgánico congelado,,0,80509,0 -23718,50451770,Aguacate simpson orgánico congelado,,0,80509,0 -23719,50451771,Aguacate taylor orgánico congelado,,0,80509,0 -23720,50451772,Aguacate tonelada orgánico congelado,,0,80509,0 -23721,50451773,Aguacate torre orgánico congelado,,0,80509,0 -23722,50451774,Aguacate torre li orgánico congelado,,0,80509,0 -23723,50451775,Aguacate trapp orgánico congelado,,0,80509,0 -23724,50451776,Aguacate semilla de caribe orgánico congelado,,0,80509,0 -23725,50451777,Aguacate wagner orgánico congelado,,0,80509,0 -23726,50451778,Aguacate waldin orgánico congelado,,0,80509,0 -23727,50451779,Aguacate wurtz orgánico congelado,,0,80509,0 -23728,50451780,Aguacate zio p orgánico congelado,,0,80509,0 -23729,50451781,Aguacate ziu orgánico congelado,,0,80509,0 -23730,50451782,Aguacate zultano orgánico congelado,,0,80509,0 -23731,50452500,Zanahorias orgánicas congeladas,,0,80509,0 -23732,50452501,Zanahoria amsterdam orgánica congelada,,0,80509,0 -23733,50452502,Zanahoria rey de otoño orgánica congelada,,0,80509,0 -23734,50452503,Zanahoria berlicum orgánica congelada,,0,80509,0 -23735,50452504,Zanahoria chantenay orgánica congelada,,0,80509,0 -23736,50452505,Zanahoria nantes orgánica congelada,,0,80509,0 -23737,50452506,Zanahoria mercado de parís orgánica congelada,,0,80509,0 -23738,50461500,Alcachofas en lata o en frasco,,0,80509,0 -23739,50461501,Alcachofa brittany enlatada o envasada,,0,80509,0 -23740,50461502,Alcachofa cantonesa enlatada o envasada,,0,80509,0 -23741,50461503,Alcachofa francesa enlatada o envasada,,0,80509,0 -23742,50461504,Alcachofa globo verde enlatada o envasada,,0,80509,0 -23743,50461505,Alcachofa gros camus de bretaña enlatada o envasada,,0,80509,0 -23744,50461506,Alcachofa midi enlatada o envasada,,0,80509,0 -23745,50461507,Alcachofa globo morado enlatada o envasada,,0,80509,0 -23746,50461508,Alcachofa morado cecilia enlatada o envasada,,0,80509,0 -23747,50461509,Alcachofa romanesco enlatada o envasada,,0,80509,0 -23748,50461510,Alcachofa espinoso sardo enlatada o envasada,,0,80509,0 -23749,50461511,Alcachofa vert de laon enlatada o envasada,,0,80509,0 -23750,50461512,Alcachofa violeta de chiggiia enlatada o envasada,,0,80509,0 -23751,50461513,Alcachofa violeta de toscana enlatada o envasada,,0,80509,0 -23752,50461600,Espárragos en lata o en frasco,,0,80509,0 -23753,50461601,Espárragos connovers colosal enlatados o envasados,,0,80509,0 -23754,50461602,Espárragos franklin enlatados o envasados,,0,80509,0 -23755,50461603,Espárragos mamut gigante enlatados o envasados,,0,80509,0 -23756,50461604,Espárragos lucullus enlatados o envasados,,0,80509,0 -23757,50461605,Espárragos martha washington enlatados o envasados,,0,80509,0 -23758,50461700,Aguacates en lata o en frasco,,0,80509,0 -23759,50461701,Aguacate ajax b – 7 enlatado o envasado,,0,80509,0 -23760,50461702,Aguacate arue enlatado o envasado,,0,80509,0 -23761,50461703,Aguacate bacon enlatado o envasado,,0,80509,0 -23762,50461704,Aguacate benik enlatado o envasado,,0,80509,0 -23763,50461705,Aguacate bernecker enlatado o envasado,,0,80509,0 -23764,50461706,Aguacate beta enlatado o envasado,,0,80509,0 -23765,50461707,Aguacate biondo enlatado o envasado,,0,80509,0 -23766,50461708,Aguacate príncipe negro enlatado o envasado,,0,80509,0 -23767,50461709,Aguacate blair enlatado o envasado,,0,80509,0 -23768,50461710,Aguacate blair cabina enlatado o envasado,,0,80509,0 -23769,50461711,Aguacate cabina 1 enlatado o envasado,,0,80509,0 -23770,50461712,Aguacate cabina 3 enlatado o envasado,,0,80509,0 -23771,50461713,Aguacate cabina 5 enlatado o envasado,,0,80509,0 -23772,50461714,Aguacate cabina 7 enlatado o envasado,,0,80509,0 -23773,50461715,Aguacate cabina 8 enlatado o envasado,,0,80509,0 -23774,50461716,Aguacate brooks 1978 enlatado o envasado,,0,80509,0 -23775,50461717,Aguacate brookslate enlatado o envasado,,0,80509,0 -23776,50461718,Aguacate california haas enlatado o envasado,,0,80509,0 -23777,50461719,Aguacate catalina enlatado o envasado,,0,80509,0 -23778,50461720,Aguacate chica enlatado o envasado,,0,80509,0 -23779,50461721,Aguacate choquette enlatado o envasado,,0,80509,0 -23780,50461722,Aguacate cristina enlatado o envasado,,0,80509,0 -23781,50461723,Aguacate collison enlatado o envasado,,0,80509,0 -23782,50461724,Aguacate donnie enlatado o envasado,,0,80509,0 -23783,50461725,Aguacate dr dupis numero 2 enlatado o envasado,,0,80509,0 -23784,50461726,Aguacate dr. dupie enlatado o envasado,,0,80509,0 -23785,50461727,Aguacate ettinger enlatado o envasado,,0,80509,0 -23786,50461728,Aguacate fuch enlatado o envasado,,0,80509,0 -23787,50461729,Aguacate fuch gwen enlatado o envasado,,0,80509,0 -23788,50461730,Aguacate fuerte enlatado o envasado,,0,80509,0 -23789,50461731,Aguacate gotham enlatado o envasado,,0,80509,0 -23790,50461732,Aguacate gossman enlatado o envasado,,0,80509,0 -23791,50461733,Aguacate guatemala sin pepa enlatado o envasado,,0,80509,0 -23792,50461734,Aguacate hall enlatado o envasado,,0,80509,0 -23793,50461735,Aguacate hardee enlatado o envasado,,0,80509,0 -23794,50461736,Aguacate haas enlatado o envasado,,0,80509,0 -23795,50461737,Aguacate herman enlatado o envasado,,0,80509,0 -23796,50461738,Aguacate hickson enlatado o envasado,,0,80509,0 -23797,50461739,Aguacate k-5 enlatado o envasado,,0,80509,0 -23798,50461740,Aguacate k- 9 enlatado o envasado,,0,80509,0 -23799,50461741,Aguacate cordero haas enlatado o envasado,,0,80509,0 -23800,50461742,Aguacate leona enlatado o envasado,,0,80509,0 -23801,50461743,Aguacate leona linda enlatado o envasado,,0,80509,0 -23802,50461744,Aguacate lisa p enlatado o envasado,,0,80509,0 -23803,50461745,Aguacate lisa loretta enlatado o envasado,,0,80509,0 -23804,50461746,Aguacate loretta enlatado o envasado,,0,80509,0 -23805,50461747,Aguacate lula enlatado o envasado,,0,80509,0 -23806,50461748,Aguacate lula mascarthur enlatado o envasado,,0,80509,0 -23807,50461749,Aguacate marcus enlatado o envasado,,0,80509,0 -23808,50461750,Aguacate melendez enlatado o envasado,,0,80509,0 -23809,50461751,Aguacate meya enlatado o envasado,,0,80509,0 -23810,50461752,Aguacate miguel p enlatado o envasado,,0,80509,0 -23811,50461753,Aguacate monroe enlatado o envasado,,0,80509,0 -23812,50461754,Aguacate murrieta verde enlatado o envasado,,0,80509,0 -23813,50461755,Aguacate nabal enlatado o envasado,,0,80509,0 -23814,50461756,Aguacate nadir enlatado o envasado,,0,80509,0 -23815,50461757,Aguacate nesbitt enlatado o envasado,,0,80509,0 -23816,50461758,Aguacate peterson enlatado o envasado,,0,80509,0 -23817,50461759,Aguacate pinelli enlatado o envasado,,0,80509,0 -23818,50461760,Aguacate pinkerton enlatado o envasado,,0,80509,0 -23819,50461761,Aguacate pollock enlatado o envasado,,0,80509,0 -23820,50461762,Aguacate puebla enlatado o envasado,,0,80509,0 -23821,50461763,Aguacate reed enlatado o envasado,,0,80509,0 -23822,50461764,Aguacaterue enlatado o envasado,,0,80509,0 -23823,50461765,Aguacateruehle enlatado o envasado,,0,80509,0 -23824,50461766,Aguacate ryan enlatado o envasado,,0,80509,0 -23825,50461767,Aguacate semil enlatado o envasado,,0,80509,0 -23826,50461768,Aguacate semil 43 enlatado o envasado,,0,80509,0 -23827,50461769,Aguacate simonds enlatado o envasado,,0,80509,0 -23828,50461770,Aguacate simpson enlatado o envasado,,0,80509,0 -23829,50461771,Aguacate taylor enlatado o envasado,,0,80509,0 -23830,50461772,Aguacate tonelada enlatado o envasado,,0,80509,0 -23831,50461773,Aguacate torre enlatado o envasado,,0,80509,0 -23832,50461774,Aguacate torre li enlatado o envasado,,0,80509,0 -23833,50461775,Aguacate trapp enlatado o envasado,,0,80509,0 -23834,50461776,Aguacate semilla de caribe enlatado o envasado,,0,80509,0 -23835,50461777,Aguacate wagner enlatado o envasado,,0,80509,0 -23836,50461778,Aguacate waldin enlatado o envasado,,0,80509,0 -23837,50461779,Aguacate wurtz enlatado o envasado,,0,80509,0 -23838,50461780,Aguacate zio p enlatado o envasado,,0,80509,0 -23839,50461781,Aguacate ziu enlatado o envasado,,0,80509,0 -23840,50461782,Aguacate zultano enlatado o envasado,,0,80509,0 -23841,50461800,Fríjoles en lata o en frasco,,0,80509,0 -23842,50461801,Frijoles anasazi ® enlatados o envasados,,0,80509,0 -23843,50461802,Frijoles appaloosa enlatados o envasados,,0,80509,0 -23844,50461803,Frijoles azuki enlatados o envasados,,0,80509,0 -23845,50461804,Frijoles barlotti enlatados o envasados,,0,80509,0 -23846,50461805,Frijoles appaloosa negra enlatados o envasados,,0,80509,0 -23847,50461806,Frijoles negros enlatados o envasados,,0,80509,0 -23848,50461807,Frijoles gran negros enlatados o envasados,,0,80509,0 -23849,50461808,Frijoles shackamaxon negro enlatados o envasados,,0,80509,0 -23850,50461809,Frijoles ojo negro enlatados o envasados,,0,80509,0 -23851,50461810,Frijoles bobby enlatados o envasados,,0,80509,0 -23852,50461811,Frijoles bolita enlatados o envasados,,0,80509,0 -23853,50461812,Frijoles esposa perezosa carmelita enlatados o envasados,,0,80509,0 -23854,50461813,Frijoles calipso enlatados o envasados,,0,80509,0 -23855,50461814,Frijoles canelini enlatados o envasados,,0,80509,0 -23856,50461815,Frijoles castor enlatados o envasados,,0,80509,0 -23857,50461816,Frijoles amarillo chino enlatados o envasados,,0,80509,0 -23858,50461817,Frijoles lengua de dragón enlatados o envasados,,0,80509,0 -23859,50461818,Frijoles soldado europeo enlatados o envasados,,0,80509,0 -23860,50461819,Frijoles fava enlatados o envasados,,0,80509,0 -23861,50461820,Frijoles flageolet enlatados o envasados,,0,80509,0 -23862,50461821,Frijoles horticultura francesa enlatados o envasados,,0,80509,0 -23863,50461822,Frijoles marina francesa enlatados o envasados,,0,80509,0 -23864,50461823,Frijoles coco gigante blanco enlatados o envasados,,0,80509,0 -23865,50461824,Frijoles verdes enlatados o envasados,,0,80509,0 -23866,50461825,Frijoles romano verde enlatados o envasados,,0,80509,0 -23867,50461826,Frijoles guar gum enlatados o envasados,,0,80509,0 -23868,50461827,Frijoles haricot enlatados o envasados,,0,80509,0 -23869,50461828,Frijoles hyacinth enlatados o envasados,,0,80509,0 -23870,50461829,Frijoles tipo italiano enlatados o envasados,,0,80509,0 -23871,50461830,Frijoles asombro jackson enlatados o envasados,,0,80509,0 -23872,50461831,Frijoles ganado jacobs enlatados o envasados,,0,80509,0 -23873,50461832,Frijoles asombro kentucky enlatados o envasados,,0,80509,0 -23874,50461833,Frijoles riñón enlatados o envasados,,0,80509,0 -23875,50461834,Frijoles lima enlatados o envasados,,0,80509,0 -23876,50461835,Frijoles madera enlatados o envasados,,0,80509,0 -23877,50461836,Frijoles medula enlatados o envasados,,0,80509,0 -23878,50461837,Frijoles mat enlatados o envasados,,0,80509,0 -23879,50461838,Frijoles monstoler ganzo salvaje enlatados o envasados,,0,80509,0 -23880,50461839,Frijoles mortgage lifter enlatados o envasados,,0,80509,0 -23881,50461840,Frijoles polilla enlatados o envasados,,0,80509,0 -23882,50461841,Frijoles mung enlatados o envasados,,0,80509,0 -23883,50461842,Frijoles munsi wolf enlatados o envasados,,0,80509,0 -23884,50461843,Frijoles nuna enlatados o envasados,,0,80509,0 -23885,50461844,Frijoles pinto enlatados o envasados,,0,80509,0 -23886,50461845,Frijoles runner enlatados o envasados,,0,80509,0 -23887,50461846,String beans enlatados o envasados,"Ayecote enlatado o envasado, Fríjol gordo enlatado o envasado",0,80509,0 -23888,50461847,Frijoles habichuelas enlatados o envasados,"Ejote enlatado o envasado, Fríjol tierno enlatado o envasado",0,80509,0 -23889,50461848,Frijoles tamarind enlatados o envasados,,0,80509,0 -23890,50461849,Frijoles tonka enlatados o envasados,,0,80509,0 -23891,50461850,Frijoles cera enlatados o envasados,,0,80509,0 -23892,50461851,Frijoles winged enlatados o envasados,,0,80509,0 -23893,50461852,Frijoles largos enlatados o envasados,,0,80509,0 -23894,50462200,Cebollas de ensalada en lata o en frasco,Cebollas de cambray en lata o en frasco,0,80509,0 -23895,50462201,Cebolla belstville ensalada enlatada o envasada,Cebolla de cambray belstville enlatada o envasada,0,80509,0 -23896,50462202,Cebolla feast ensalada enlatada o envasada,Cebolla de cambray feast enlatada o envasada,0,80509,0 -23897,50462203,Cebolla ishikura ensalada enlatada o envasada,Cebolla de cambray ishikura enlatada o envasada,0,80509,0 -23898,50462204,Cebolla mercado kyoto enlatada o envasada,Cebolla de cambray mercado kyoto enlatada o envasada,0,80509,0 -23899,50462205,Cebolla barba roja ensalada enlatada o envasada,Cebolla de cambray barba roja enlatada o envasada,0,80509,0 -23900,50462206,Cebolla amigo rojo ensalada enlatada o envasada,Cebolla de cambray amigo rojo enlatada o envasada,0,80509,0 -23901,50462207,Cebolla santa claus enlatada o envasada,Cebolla de cambray santa claus enlatada o envasada,0,80509,0 -23902,50462208,Cebolla tokyo ensalada enlatada o envasada,Cebolla de cambray tokyo enlatada o envasada,0,80509,0 -23903,50462209,Cebolla lisbon blanca ensalada enlatada o envasada,Cebolla de cambray lisbon blanca enlatada o envasada,0,80509,0 -23904,50462210,Cebolla invierno blanca ensalada enlatada o envasada,Cebolla de cambray invierno blanca enlatada o envasada,0,80509,0 -23905,50462211,Cebolla final de invierno ensalada enlatada o envasada,Cebolla de cambray final de invierno enlatada o envasada,0,80509,0 -23906,50462500,Zanahorias en lata o en frasco,,0,80509,0 -23907,50462501,Zanahoria amsterdam enlatada o envasada,,0,80509,0 -23908,50462502,Zanahoria rey de otoño enlatada o envasada,,0,80509,0 -23909,50462503,Zanahoria berlicum enlatada o envasada,,0,80509,0 -23910,50462504,Zanahoria chantenay enlatada o envasada,,0,80509,0 -23911,50462505,Zanahoria nantes enlatada o envasada,,0,80509,0 -23912,50462506,Zanahoria mercado de parís enlatada o envasada,,0,80509,0 -23913,50462800,Acelgas en lata o en frasco,,0,80509,0 -23914,50462801,Acelga luz brillante enlatada o envasada,,0,80509,0 -23915,50462802,Acelga gigante fordhook enlatada o envasada,,0,80509,0 -23916,50462803,Acelga luculus enlatada o envasada,,0,80509,0 -23917,50462804,Acelga espinaca perpetua enlatada o envasada,,0,80509,0 -23918,50462805,Acelga ruibarbo enlatada o envasada,,0,80509,0 -23919,50462806,Acelga suizo enlatada o envasada,,0,80509,0 -23920,50462807,Acelga volcán enlatada o envasada,,0,80509,0 -23921,50462808,Acelga rey blanco enlatada o envasada,,0,80509,0 -23922,50463100,Cebollines en lata o en frasco,,0,80509,0 -23923,50463101,Cebollín chino enlatado o envasado,,0,80509,0 -23924,50463200,Maíces en lata o en frasco,,0,80509,0 -23925,50463201,Maíz aloha enlatado o envasado,,0,80509,0 -23926,50463202,Maíz alpine enlatado o envasado,,0,80509,0 -23927,50463203,Maíz ambrosia enlatado o envasado,,0,80509,0 -23928,50463204,Maíz argent enlatado o envasado,,0,80509,0 -23929,50463205,Maíz aspen enlatado o envasado,,0,80509,0 -23930,50463206,Maíz avalancha enlatado o envasado,,0,80509,0 -23931,50463207,Maíz biqueen enlatado o envasado,,0,80509,0 -23932,50463208,Maíz bodacious enlatado o envasado,,0,80509,0 -23933,50463209,Maíz mantequilla y azúcar enlatado o envasado,,0,80509,0 -23934,50463210,Maíz calico belle enlatado o envasado,,0,80509,0 -23935,50463211,Maíz camelot enlatado o envasado,,0,80509,0 -23936,50463212,Maíz challengercrisp ‘n dulce enlatado o envasado,,0,80509,0 -23937,50463213,Maíz campeón enlatado o envasado,,0,80509,0 -23938,50463214,Maíz algodón de azúcar enlatado o envasado,,0,80509,0 -23939,50463215,Maíz dártagnan enlatado o envasado,,0,80509,0 -23940,50463216,Maíz dazzle enlatado o envasado,,0,80509,0 -23941,50463217,Maíz diamante y oro enlatado o envasado,,0,80509,0 -23942,50463218,Maíz divinidad enlatado o envasado,,0,80509,0 -23943,50463219,Maíz delicia doble enlatado o envasado,,0,80509,0 -23944,50463220,Maíz gema doble enlatado o envasado,,0,80509,0 -23945,50463221,Maíz earlivee enlatado o envasado,,0,80509,0 -23946,50463222,Maíz temprano extra dulce enlatado o envasado,,0,80509,0 -23947,50463223,Maíz excel enlatado o envasado,,0,80509,0 -23948,50463224,Maíz cruz dorada bantam enlatado o envasado,,0,80509,0 -23949,50463225,Maíz miel y crema enlatado o envasado,,0,80509,0 -23950,50463226,Maíz miel y perla enlatado o envasado,,0,80509,0 -23951,50463227,Maíz miel dulce enlatado o envasado,,0,80509,0 -23952,50463228,Maíz hudson enlatado o envasado,,0,80509,0 -23953,50463229,Maíz dorado illini enlatado o envasado,,0,80509,0 -23954,50463230,Maíz illini extra dulce enlatado o envasado,,0,80509,0 -23955,50463231,Maíz increíble enlatado o envasado,,0,80509,0 -23956,50463232,Maíz lochief enlatado o envasado,,0,80509,0 -23957,50463233,Maíz jubileo enlatado o envasado,,0,80509,0 -23958,50463234,Maíz jupbileo super dulce enlatado o envasado,,0,80509,0 -23959,50463235,Maíz confite korn enlatado o envasado,,0,80509,0 -23960,50463236,Maíz beso y acuso enlatado o envasado,,0,80509,0 -23961,50463237,Maíz lancelot enlatado o envasado,,0,80509,0 -23962,50463238,Maíz dulce maple enlatado o envasado,,0,80509,0 -23963,50463239,Maíz medley enlatado o envasado,,0,80509,0 -23964,50463240,Maíz merlin enlatado o envasado,,0,80509,0 -23965,50463241,Maíz milagro enlatado o envasado,,0,80509,0 -23966,50463242,Maíz nk – 199 enlatado o envasado,,0,80509,0 -23967,50463243,Maíz durazno y crema enlatado o envasado,,0,80509,0 -23968,50463244,Maíz blanco perla enlatado o envasado,,0,80509,0 -23969,50463245,Maíz pegasus enlatado o envasado,,0,80509,0 -23970,50463246,Maíz fenomenal enlatado o envasado,,0,80509,0 -23971,50463247,Maíz dama de platino enlatado o envasado,,0,80509,0 -23972,50463248,Maíz precioso enlatado o envasado,,0,80509,0 -23973,50463249,Maíz pristine enlatado o envasado,,0,80509,0 -23974,50463250,Maíz rápido enlatado o envasado,,0,80509,0 -23975,50463251,Maíz radiante enlatado o envasado,,0,80509,0 -23976,50463252,Maíz seneca brave enlatado o envasado,,0,80509,0 -23977,50463253,Maíz amanecer seneca enlatado o envasado,,0,80509,0 -23978,50463254,Maíz horizonte seneca enlatado o envasado,,0,80509,0 -23979,50463255,Maíz brillo de estrella seneca enlatado o envasado,,0,80509,0 -23980,50463256,Maíz noche blanca seneca enlatado o envasado,,0,80509,0 -23981,50463257,Maíz showcase enlatado o envasado,,0,80509,0 -23982,50463258,Maíz reina plateada enlatado o envasado,,0,80509,0 -23983,50463259,Maíz bella nieve enlatado o envasado,,0,80509,0 -23984,50463260,Maíz nieve de primavera enlatado o envasado,,0,80509,0 -23985,50463261,Maíz premio de primavera enlatado o envasado,,0,80509,0 -23986,50463262,Maíz azúcar y oro enlatado o envasado,,0,80509,0 -23987,50463263,Maíz rollo de azúcar enlatado o envasado,,0,80509,0 -23988,50463264,Maíz nieve de azúcar enlatado o envasado,,0,80509,0 -23989,50463265,Maíz baile de sol enlatado o envasado,,0,80509,0 -23990,50463266,Maíz estrella tell enlatado o envasado,,0,80509,0 -23991,50463267,Maíz terminador enlatado o envasado,,0,80509,0 -23992,50463268,Maíz tesoro enlatado o envasado,,0,80509,0 -23993,50463269,Maíz tuxedo enlatado o envasado,,0,80509,0 -23994,50463800,Ajos en lata o en frasco,,0,80509,0 -23995,50463801,Ajo tarde california enlatado o envasado,,0,80509,0 -23996,50463802,Ajo tallos chinos enlatado o envasado,,0,80509,0 -23997,50463803,Ajo cebollin enlatado o envasado,,0,80509,0 -23998,50463804,Ajo germidor enlatado o envasado,,0,80509,0 -23999,50463805,Ajo guardado largo enlatado o envasado,,0,80509,0 -24000,50463806,Ajo ramson enlatado o envasado,,0,80509,0 -24001,50463807,Ajo rocambola enlatado o envasado,,0,80509,0 -24002,50463808,Ajo rosa de lautrec enlatado o envasado,,0,80509,0 -24003,50463809,Ajo solent blanco enlatado o envasado,,0,80509,0 -24004,50463810,Ajo morado español enlatado o envasado,,0,80509,0 -24005,50463811,Ajo valentina italiana enlatado o envasado,,0,80509,0 -24006,50464000,Guisantes en lata o en frasco,Chícharos en lata o en frasco,0,80509,0 -24007,50464001,Guisante chino enlatada o envasada,Chícharo chino enlatado o envasado,0,80509,0 -24008,50464002,Guisante inglés enlatada o envasada,Chícharo inglés enlatado o envasado,0,80509,0 -24009,50464003,Guisante de jardín enlatada o envasada,Chícharo de jardín enlatado o envasado,0,80509,0 -24010,50464004,Guisante nieve enlatada o envasada,Chícharo nieve enlatado o envasado,0,80509,0 -24011,50464005,Guisante snap dulce enlatada o envasada,Chícharo snap dulce enlatado o envasado,0,80509,0 -24012,50464100,Hierbas en lata o en frasco,"Perejil enlatado o envasado, Yerbabuena o hierbabuena enlatada o envasada",0,80509,0 -24013,50464101,Albahaca enlatada o envasada,,0,80509,0 -24014,50464102,Laurel enlatado o envasado,,0,80509,0 -24015,50464103,Borraja enlatada o envasada,,0,80509,0 -24016,50464104,Alcaravea enlatada o envasada,,0,80509,0 -24017,50464105,Perifollo enlatado o envasado,,0,80509,0 -24018,50464106,Cilantro enlatado o envasado,,0,80509,0 -24019,50464107,Chipolinos enlatado o envasado,,0,80509,0 -24020,50464108,Hoja de curry enlatada o envasada,,0,80509,0 -24021,50464109,Eneldo enlatado o envasado,,0,80509,0 -24022,50464110,Epazote enlatado o envasado,,0,80509,0 -24023,50464111,Fenogreco enlatado o envasado,,0,80509,0 -24024,50464112,Limonaria enlatada o envasada,Té de limón enlatado o envasado,0,80509,0 -24025,50464113,Mejorana enlatada o envasada,,0,80509,0 -24026,50464114,Menta enlatada o envasada,,0,80509,0 -24027,50464115,Orégano enlatado o envasado,,0,80509,0 -24028,50464116,Pápalo enlatado o envasado,,0,80509,0 -24029,50464117,Pápalo delgado enlatado o envasado,,0,80509,0 -24030,50464118,Perilla enlatada o envasada,Albahaca japonesa enlatada o envasada,0,80509,0 -24031,50464119,Recao enlatado o envasado,,0,80509,0 -24032,50464120,Romero enlatado o envasado,,0,80509,0 -24033,50464121,Salvia enlatada o envasada,,0,80509,0 -24034,50464122,Salsifí enlatada o envasada,Barba cabruna enlatada o envasada,0,80509,0 -24035,50464123,Satureja enlatada o envasada,Ajedreas enlatada o envasada,0,80509,0 -24036,50464124,Estragón enlatado o envasado,Tarragón enlatado o envasado,0,80509,0 -24037,50464125,Tomillo enlatado o envasado,,0,80509,0 -24038,50464126,Tumeric enlatado o envasado,Cúrcuma enlatada o envasada,0,80509,0 -24039,50464127,Verdolaga enlatada o envasada,,0,80509,0 -24040,50464800,Hongos o champiñones en lata o en frasco,,0,80509,0 -24041,50464801,Champiñón trompeta negra enlatado o envasado,,0,80509,0 -24042,50464802,Champiñón carmelito enlatado o envasado,,0,80509,0 -24043,50464803,Champiñón enlatado o envasado,,0,80509,0 -24044,50464804,Champiñón chanterelle enlatado o envasado,,0,80509,0 -24045,50464805,Champiñón cremini enlatado o envasado,,0,80509,0 -24046,50464806,Champiñón enoki enlatado o envasado,,0,80509,0 -24047,50464807,Champiñón puercoespín enlatado o envasado,,0,80509,0 -24048,50464808,Champiñón gallina del bosque enlatado o envasado,,0,80509,0 -24049,50464809,Champiñón langosta enlatado o envasado,,0,80509,0 -24050,50464810,Champiñón morels enlatado o envasado,,0,80509,0 -24051,50464811,Champiñón ostra enlatado o envasado,,0,80509,0 -24052,50464812,Champiñón pleurotus enlatado o envasado,,0,80509,0 -24053,50464813,Champiñón pompom enlatado o envasado,,0,80509,0 -24054,50464814,Champiñón porcieni enlatado o envasado,,0,80509,0 -24055,50464815,Champiñón portobello enlatado o envasado,,0,80509,0 -24056,50464816,Champiñón shitake enlatado o envasado,,0,80509,0 -24057,50464817,Champiñón shimeji enlatado o envasado,,0,80509,0 -24058,50464818,Champiñón san jorge enlatado o envasado,,0,80509,0 -24059,50464819,Champiñón blanco enlatado o envasado,,0,80509,0 -24060,50464820,Champiñón tompeta blanca enlatado o envasado,,0,80509,0 -24061,50464821,Champiñón woodear enlatado o envasado,,0,80509,0 -24062,50464900,Mostazas en lata o en frasco,,0,80509,0 -24063,50464901,Mostaza bambú enlatada o envasada,,0,80509,0 -24064,50464902,Mostaza ajo enlatada o envasada,,0,80509,0 -24065,50464903,Mostaza hoja gigante enlatada o envasada,,0,80509,0 -24066,50464904,Mostaza rojo en nieve enlatada o envasada,,0,80509,0 -24067,50464905,Mostaza sureño enlatada o envasada,,0,80509,0 -24068,50464906,Mostaza corazón envuelto enlatada o envasada,,0,80509,0 -24069,50465000,Solanáceas (nightshades) en lata o en frasco,,0,80509,0 -24070,50465001,Lámpara china enlatada o envasada,,0,80509,0 -24071,50465002,Mora de jardín,,0,80509,0 -24072,50465003,Naranjilla orgánica enlatadas o envasadas,,0,80509,0 -24073,50465004,Tomatillo enlatada o envasada,,0,80509,0 -24074,50465200,Cebollas en lata o en frasco,,0,80509,0 -24075,50465201,Cebolla albion enlatada o envasada,,0,80509,0 -24076,50465202,Cebolla alisia craig enlatada o envasada,,0,80509,0 -24077,50465203,Cebolla hirviendo enlatada o envasada,,0,80509,0 -24078,50465204,Cebolla bufalo enlatada o envasada,,0,80509,0 -24079,50465205,Cebolla bulbo enlatada o envasada,,0,80509,0 -24080,50465206,Cebolla cremosa enlatada o envasada,,0,80509,0 -24081,50465207,Cebolla amarillo expreso o – x enlatada o envasada,,0,80509,0 -24082,50465208,Cebolla kelsae enlatada o envasada,,0,80509,0 -24083,50465209,Cebolla gigante marshal globo fen enlatada o envasada,,0,80509,0 -24084,50465210,Cebolla perla enlatada o envasada,,0,80509,0 -24085,50465211,Cebolla baron rojo enlatada o envasada,,0,80509,0 -24086,50465212,Cebolla roja enlatada o envasada,Cebolla morada enlatada o envasada,0,80509,0 -24087,50465213,Cebolla rijnsberger enlatada o envasada,,0,80509,0 -24088,50465214,Cebolla senshyu semi – globo amarillo enlatada o envasada,,0,80509,0 -24089,50465215,Cebolla sturon enlatada o envasada,,0,80509,0 -24090,50465216,Cebolla sturgatter enlatada o envasada,,0,80509,0 -24091,50465217,Cebolla dulce enlatada o envasada,,0,80509,0 -24092,50465218,Cebolla torpedo o rojo italiana enlatada o envasada,,0,80509,0 -24093,50465219,Cebolla roja guardada enlatada o envasada,,0,80509,0 -24094,50465220,Cebolla blanca guardada enlatada o envasada,,0,80509,0 -24095,50465221,Cebolla amarilla guardada enlatada o envasada,,0,80509,0 -24096,50465300,Maníes en lata o en frasco,Cacahuates en lata o en frasco,0,80509,0 -24097,50465301,Maní bambara enlatado o envasado,Cacahuate bambara enlatado o envasado,0,80509,0 -24098,50465302,Maní florunner enlatado o envasado,Cacahuate florunner enlatado o envasado,0,80509,0 -24099,50465303,Maní husa / kerstings enlatado o envasado,Cacahuate husa / kerstings enlatado o envasado,0,80509,0 -24100,50465304,Maní español enlatado o envasado,Cacahuate español enlatado o envasado,0,80509,0 -24101,50465305,Maní valencia enlatado o envasado,Cacahuate valencia enlatado o envasado,0,80509,0 -24102,50465306,Maní virginia enlatado o envasado,Cacahuate virginia enlatado o envasado,0,80509,0 -24103,50465500,Pimientos en lata o en frasco,Chiles en lata o en frasco,0,80509,0 -24104,50465501,Pimentón ají enlatado o envasado,Ají enlatado o envasado,0,80509,0 -24105,50465502,Pimentón árbol enlatado o envasado,Chile de árbol enlatado o envasado,0,80509,0 -24106,50465503,Pimentón queso enlatado o envasado,,0,80509,0 -24107,50465504,Pimentón chilaca enlatado o envasado,Chilaca enlatado o envasado,0,80509,0 -24108,50465505,Pimentón cubanelles enlatado o envasado,Chile cubanelle o pimienta italiana enlatada o envasada,0,80509,0 -24109,50465506,Pimentón fresno enlatado o envasado,Chile fresno enlatado o envasado,0,80509,0 -24110,50465507,Pimentón kapia enlatado o envasado,Chile kapia enlatado o envasado,0,80509,0 -24111,50465508,Pimentón coreano enlatado o envasado,Chile cheongyang enlatado o envasado,0,80509,0 -24112,50465509,Pimentón manzano enlatado o envasado,Chile manzano o de cera enlatado o envasado,0,80509,0 -24113,50465510,Pimentón melrose enlatado o envasado,,0,80509,0 -24114,50465511,Pimentón chile amarillo enlatado o envasado,Chile peruano amarillo enlatado o envasado,0,80509,0 -24115,50465512,Pimentón aji dulce enlatado o envasado,Ají dulce enlatado o envasado,0,80509,0 -24116,50465513,Pimentón anaheim enlatado o envasado,,0,80509,0 -24117,50465514,Pimentón ancho enlatado o envasado,Chile ancho o mulato enlatado o envasado,0,80509,0 -24118,50465515,Pimentón campana enlatado o envasado,Chile campana enlatado o envasado,0,80509,0 -24119,50465516,Pimentón cascabel enlatado o envasado,Chile cascabel enlatado o envasado,0,80509,0 -24120,50465517,Pimentón cayenne enlatado o envasado,Pimienta de Cayena o roja enlatada o envasada,0,80509,0 -24121,50465518,Pimentón anfitrión cereza enlatado o envasado,,0,80509,0 -24122,50465519,Pimentón chitespin enlatado o envasado,Chile chiltepín enlatado o envasado,0,80509,0 -24123,50465520,Pimentón dedo picante enlatado o envasado,Chile Jwala enlatado o envasado,0,80509,0 -24124,50465521,Pimentón guajillo enlatado o envasado,Chile guajillo enlatado o envasado,0,80509,0 -24125,50465522,Pimentón guerro enlatado o envasado,Chile güero enlatado o envasado,0,80509,0 -24126,50465523,Pimentón habanero enlatado o envasado,Chile habanero enlatado o envasado,0,80509,0 -24127,50465524,Pimentón cera de hungria enlatado o envasado,Pimentón húngaro enlatado o envasado,0,80509,0 -24128,50465525,Pimentón jalapeño enlatado o envasado,"Chile cuaresmeño enlatado o envasado, Chile jalapeño, seco es chipotle o morita enlatado o envasado",0,80509,0 -24129,50465526,Pimentón picante largo enlatado o envasado,Chile cola de rata enlatado o envasado,0,80509,0 -24130,50465527,Pimentón marasol enlatado o envasado,Chile mirasol enlatado o envasado,0,80509,0 -24131,50465528,Pimentón pasilla enlatado o envasado,Chile pasilla enlatado o envasado,0,80509,0 -24132,50465529,Pimentón peperoncini enlatado o envasado,,0,80509,0 -24133,50465530,Pimentón pequin enlatado o envasado,Chile piquín enlatado o envasado,0,80509,0 -24134,50465531,Pimentón pimiento enlatado o envasado,Pimiento morrón enlatado o envasado,0,80509,0 -24135,50465532,Pimentón poblano enlatado o envasado,Chile poblano enlatado o envasado,0,80509,0 -24136,50465533,Pimentón scotch bonnet enlatado o envasado,Pimientos Bonney o rojos Caribe enlatado o envasado,0,80509,0 -24137,50465534,Pimentón serrano enlatado o envasado,"Chile serrano enlatado o envasado, Chile verde enlatado o envasado",0,80509,0 -24138,50465535,Pimentón tabasco enlatado o envasado,Chile tabasco enlatado o envasado,0,80509,0 -24139,50465536,Pimentón tai enlatado o envasado,Chile ojo de pájaro enlatado o envasado,0,80509,0 -24140,50465537,Pimentón tepin enlatado o envasado,Chile mosquito o catarina enlatado o envasado,0,80509,0 -24141,50465600,Papas en lata o en frasco,,0,80509,0 -24142,50465601,Papa blanca larga enlatada o envasada,,0,80509,0 -24143,50465602,Papa redonda blanca enlatada o envasada,,0,80509,0 -24144,50465603,Papa redonda roja enlatada o envasada,Papa de cambray enlatada o envasada,0,80509,0 -24145,50465604,Papa russet enlatada o envasada,,0,80509,0 -24146,50465605,Papa morada enlatada o envasada,,0,80509,0 -24147,50465606,Papa amarilla enlatada o envasada,,0,80509,0 -24148,50465607,Papa nueva enlatada o envasada,,0,80509,0 -24149,50465608,Papa especial enlatada o envasada,,0,80509,0 -24150,50465800,Vegetales marinos en lata o en frasco,,0,80509,0 -24151,50465801,Agar-agar enlatado o envasado,,0,80509,0 -24152,50465802,Arame enlatada o envasada,,0,80509,0 -24153,50465803,Alga dulce enlatada o envasada,,0,80509,0 -24154,50465804,Habichuelas vert de mer enlatada o envasada,,0,80509,0 -24155,50465805,Hijiki enlatada o envasada,,0,80509,0 -24156,50465806,Musgo irlandés enlatada o envasada,,0,80509,0 -24157,50465807,Quelpo enlatado o envasado,,0,80509,0 -24158,50465808,Laver enlatado o envasado,,0,80509,0 -24159,50465809,Nori enlatado o envasado,,0,80509,0 -24160,50465810,Alga roja enlatada o envasada,,0,80509,0 -24161,50465811,Kale de mar enlatada o envasada,,0,80509,0 -24162,50465812,Lechuga de mar enlatada o envasada,,0,80509,0 -24163,50465813,Algas de mar enlatada o envasada,,0,80509,0 -24164,50465814,Spirulina enlatada o envasada,,0,80509,0 -24165,50465815,Susabi nuri enlatada o envasada,,0,80509,0 -24166,50465816,Wakame enlatada o envasada,,0,80509,0 -24167,50466400,Tomates en lata o en frasco,Jitomates en lata o en frasco,0,80509,0 -24168,50466401,Tomates ailsa craig enlatado o envasado,Jitomate ailsa craig enlatado o envasado,0,80509,0 -24169,50466402,Tomates alicante enlatado o envasado,Jitomate alicante enlatado o envasado,0,80509,0 -24170,50466403,Tomates ciruela negra enlatado o envasado,Jitomate ciruela negra enlatado o envasado,0,80509,0 -24171,50466404,Tomates vino de brandy enlatado o envasado,Jitomate vino de brandy enlatado o envasado,0,80509,0 -24172,50466405,Tomates bella cereza enlatado o envasado,Jitomate bella cereza enlatado o envasado,0,80509,0 -24173,50466406,Tomates cereza enlatado o envasado,Jitomate cereza enlatado o envasado,0,80509,0 -24174,50466407,Tomates delicioso enlatado o envasado,Jitomate delicioso enlatado o envasado,0,80509,0 -24175,50466408,Tomates dombito enlatado o envasado,Jitomate dombito enlatado o envasado,0,80509,0 -24176,50466409,Tomates delicia del jardín enlatado o envasado,Jitomate delicia del jardín enlatado o envasado,0,80509,0 -24177,50466410,Tomates uva enlatado o envasado,Jitomate uva enlatado o envasado,0,80509,0 -24178,50466411,Tomates verde enlatado o envasado,Tomate verde enlatado o envasado,0,80509,0 -24179,50466412,Tomates super marmande enlatado o envasado,Jitomate super marmande enlatado o envasado,0,80509,0 -24180,50466413,Tomates rayas naturales marvel enlatado o envasado,Jitomate rayas naturales marvel enlatado o envasado,0,80509,0 -24181,50466414,Tomates minibel enlatado o envasado,Jitomate minibel enlatado o envasado,0,80509,0 -24182,50466415,Tomates oaxacan enlatado o envasado,Jitomate oaxaca enlatado o envasado,0,80509,0 -24183,50466416,Tomates alerta roja enlatado o envasado,Jitomate alerta roja enlatado o envasado,0,80509,0 -24184,50466417,Tomates roma vf enlatado o envasado,Jitomate roma vf enlatado o envasado,0,80509,0 -24185,50466418,Tomates marzano zan enlatado o envasado,Jitomate marzano zan enlatado o envasado,0,80509,0 -24186,50466419,Tomates shirley enlatado o envasado,Jitomate shirley enlatado o envasado,0,80509,0 -24187,50466420,Tomates siberia enlatado o envasado,Jitomate siberia enlatado o envasado,0,80509,0 -24188,50466421,Tomates super beefsteak enlatado o envasado,Jitomate super beefsteak enlatado o envasado,0,80509,0 -24189,50466422,Tomates tigerella enlatado o envasado,Jitomate tigerella enlatado o envasado,0,80509,0 -24190,50466423,Tomates tiny tim enlatado o envasado,Jitomate tiny tim enlatado o envasado,0,80509,0 -24191,50466424,Tomates tumbler enlatado o envasado,Jitomate tumbler enlatado o envasado,0,80509,0 -24192,50466425,Tomates cocktail amarillo enlatado o envasado,,0,80509,0 -24193,50466426,Tomates forma de pera amarillo enlatado o envasado,,0,80509,0 -24194,50466427,Tomates perfección amarilla enlatado o envasado,,0,80509,0 -24195,50466800,Vegetales Nominant en lata o en frasco,"Vegetales predominantes en lata o en frasco, Nopal en lata o en frasco",0,80509,0 -24196,50466801,Alfalfa enlatada o envasada,,0,80509,0 -24197,50466802,Hojas de aloe enlatada o envasada,"Hoja de sábila enlatada o envasada, Penca de sábila enlatada o envasada",0,80509,0 -24198,50466803,Apio enlatado o envasado,,0,80509,0 -24199,50466804,Arrurruz enlatada o envasada,,0,80509,0 -24200,50466805,Punta de flecha enlatada o envasada,Sagittaria enlatada o envasada,0,80509,0 -24201,50466806,rúcula enlatada o envasada,,0,80509,0 -24202,50466807,Arum enlatada o envasada,Taro enlatado o envasado,0,80509,0 -24203,50466808,Raíz de bambú enlatada o envasada,,0,80509,0 -24204,50466809,Hojas de plátano enlatada o envasada,,0,80509,0 -24205,50466810,Batatas enlatada o envasada,,0,80509,0 -24206,50466811,Brotes de frijol enlatados o envasados,,0,80509,0 -24207,50466812,Top de remolacha enlatada o envasada,Betabel enlatado o envasado,0,80509,0 -24208,50466813,Melón agrio enlatado o envasado,Balsamina enlatada o envasada,0,80509,0 -24209,50466814,Bayas de alcaparra orgánica enlatadas o envasadas,,0,80509,0 -24210,50466815,Carob enlatado o envasado,Algarrobo enlatado o envasado,0,80509,0 -24211,50466816,Cha-om enlatado o envasado,,0,80509,0 -24212,50466817,Chayote o guatila enlatado o envasado,,0,80509,0 -24213,50466818,Garbanzo enlatado o envasado,,0,80509,0 -24214,50466819,Verde de crisantemos enlatado o envasado,,0,80509,0 -24215,50466820,Verde de diente de león enlatado o envasado,,0,80509,0 -24216,50466821,Diente de león enlatado o envasado,,0,80509,0 -24217,50466822,Dasheen enlatado o envasado,,0,80509,0 -24218,50466823,Puntas de alverja enlatado o envasado,,0,80509,0 -24219,50466824,Diakon enlatado o envasado,Rábano enlatado o envasado,0,80509,0 -24220,50466825,Donqua enlatada o envasada,Ginseng hembra enlatada o envasada,0,80509,0 -24221,50466826,Helecho enlatado o envasado,Brotes de helecho enlatado o envasado,0,80509,0 -24222,50466827,Gai choi enlatado o envasado,Mostaza castaña enlatada o envasada,0,80509,0 -24223,50466828,Gailon enlatado o envasado,Brécol chino enlatado o envasado,0,80509,0 -24224,50466829,Galanga enlatada o envasada,Jengibre azul enlatado o envasado,0,80509,0 -24225,50466830,Raíz de jengibre enlatada o envasada,,0,80509,0 -24226,50466831,Gobo enlatado o envasado,Garduña enlatada o envasada,0,80509,0 -24227,50466832,Brote de lúpulo enlatado o envasado,,0,80509,0 -24228,50466833,Rabano blanco enlatado o envasado,Rábano de caballo enlatado o envasado,0,80509,0 -24229,50466834,Jicama enlatada o envasada,,0,80509,0 -24230,50466835,Kuduz enlatada o envasada,Kudzu enlatado o envasado,0,80509,0 -24231,50466836,Bulbo de margarita enlatado o envasado,Bulbo de lili chino enlatado o envasado,0,80509,0 -24232,50466837,Linkok enlatado o envasado,,0,80509,0 -24233,50466838,Lo bok enlatado o envasado,,0,80509,0 -24234,50466839,Frijol largo enlatado o envasado,Fríjol serpiente enlatado o envasado,0,80509,0 -24235,50466840,Raíz de lotus enlatado o envasado,Rosa del Nilo enlatada o envasada,0,80509,0 -24236,50466841,Hojas de maguey enlatada o envasada,"Hoja de agave enlatada o envasada, Penca de maguey enlatada o envasada",0,80509,0 -24237,50466842,Mallows enlatado o envasado,Malva enlatada o envasada,0,80509,0 -24238,50466843,Sapote mamey enlatado o envasado,Mamey enlatado o envasado,0,80509,0 -24239,50466844,Moap enlatado o envasado,,0,80509,0 -24240,50466845,Moo enlatado o envasado,,0,80509,0 -24241,50466846,Moqua enlatada o envasada,,0,80509,0 -24242,50466847,Opos enlatados o envasados,,0,80509,0 -24243,50466848,Corazon de palma enlatado o envasado,Palmito enlatado o envasado,0,80509,0 -24244,50466849,Paprika enlatada o envasada,,0,80509,0 -24245,50466850,Purslane enlatada o envasada,Verdolaga enlatada o envasada,0,80509,0 -24246,50466851,Raddichios enlatada o envasada,Achicoria morada enlatada o envasada,0,80509,0 -24247,50466852,Sinquas enlatada o envasada,Zapallo enlatado o envasado,0,80509,0 -24248,50466853,Frijol de soya enlatados o envasados,,0,80509,0 -24249,50466854,Spoonwart enlatado o envasado,Hierba del escorbuto enlatada o envasada,0,80509,0 -24250,50466855,Uva tassele hyacinth enlatado o envasado,,0,80509,0 -24251,50466856,Taro enlatado o envasado,Tales enlatado o envasado,0,80509,0 -24252,50466857,Hoja de taro enlatada o envasada,,0,80509,0 -24253,50466858,Tallo de taro enlatado o envasado,,0,80509,0 -24254,50466859,Tapeguaje enlatado o envasado,,0,80509,0 -24255,50466860,Verde tierno enlatado o envasado,,0,80509,0 -24256,50466861,Tindora enlatado o envasado,,0,80509,0 -24257,50466862,Árbol de cebolla enlatado o envasado,Cebollas egipcias enlatada o envasada,0,80509,0 -24258,50466863,Udo enlatado o envasado,,0,80509,0 -24259,50466864,Agua castaño enlatado o envasado,Castaña de agua china enlatada o envasada,0,80509,0 -24260,50466865,Yumpi enlatado o envasado,Akankong enlatado o envasado,0,80509,0 -24261,50466866,Yautia enlatado o envasado,Mapuey enlatado o envasado,0,80509,0 -24262,50466867,Yu choy enlatado o envasado,,0,80509,0 -24263,50466868,Yuca enlatada o envasada,,0,80509,0 -24264,50467000,Kimchis dulces en lata o en frasco,Col agria (kimchi coreano) en lata o en frasco,0,80509,0 -24265,50467001,Baechukimchis,Baechukimchis,0,80509,0 -24266,50467002,Rábanos jóvenes kimichi,Col agria (kimchi coreano) joven,0,80509,0 -24267,50467003,Rábano de agua kimichi,Col agria (kimchi coreano) de agua,0,80509,0 -24268,50467004,Rábanos joven escabeche kimichi,Col agria (kimchi coreano) joven en escabeche,0,80509,0 -24269,50467005,Rábanos cortados kimichi,Col agria (kimchi coreano) cortados,0,80509,0 -24270,50467006,Jangjorims enlatado,,0,80509,0 -24271,50467007,Atún enlatada,Atún enlatado,0,80509,0 -24272,50473200,Maíces orgánicos en lata o en frasco,,0,80509,0 -24273,50473201,Maíz aloha orgánico enlatado o envasado,,0,80509,0 -24274,50473202,Maíz alpine orgánico enlatado o envasado,,0,80509,0 -24275,50473203,Maíz ambrosia orgánico enlatado o envasado,,0,80509,0 -24276,50473204,Maíz argent orgánico enlatado o envasado,,0,80509,0 -24277,50473205,Maíz aspen orgánico enlatado o envasado,,0,80509,0 -24278,50473206,Maíz avalancha orgánico enlatado o envasado,,0,80509,0 -24279,50473207,Maíz biqueen orgánico enlatado o envasado,,0,80509,0 -24280,50473208,Maíz bodacious orgánico enlatado o envasado,,0,80509,0 -24281,50473209,Maíz mantequilla y azúcar orgánico enlatado o envasado,,0,80509,0 -24282,50473210,Maíz calico belle orgánico enlatado o envasado,,0,80509,0 -24283,50473211,Maíz camelot orgánico enlatado o envasado,,0,80509,0 -24284,50473212,Maíz challengercrisp ‘n dulce orgánico enlatado o envasado,,0,80509,0 -24285,50473213,Maíz campeón orgánico enlatado o envasado,,0,80509,0 -24286,50473214,Maíz algodón de azúcar orgánico enlatado o envasado,,0,80509,0 -24287,50473215,Maíz dártagnan orgánico enlatado o envasado,,0,80509,0 -24288,50473216,Maíz dazzle orgánico enlatado o envasado,,0,80509,0 -24289,50473217,Maíz diamante y oro orgánico enlatado o envasado,,0,80509,0 -24290,50473218,Maíz divinidad orgánico enlatado o envasado,,0,80509,0 -24291,50473219,Maíz delicia doble orgánico enlatado o envasado,,0,80509,0 -24292,50473220,Maíz gema doble orgánico enlatado o envasado,,0,80509,0 -24293,50473221,Maíz earlivee orgánico enlatado o envasado,,0,80509,0 -24294,50473222,Maíz temprano extra dulce orgánico enlatado o envasado,,0,80509,0 -24295,50473223,Maíz excel orgánico enlatado o envasado,,0,80509,0 -24296,50473224,Maíz cruz dorada bantam orgánico enlatado o envasado,,0,80509,0 -24297,50473226,Maíz miel y perla orgánico enlatado o envasado,,0,80509,0 -24298,50473227,Maíz miel dulce orgánico enlatado o envasado,,0,80509,0 -24299,50473228,Maíz hudson orgánico enlatado o envasado,,0,80509,0 -24300,50473229,Maíz dorado illini orgánico enlatado o envasado,,0,80509,0 -24301,50473230,Maíz illini extra dulce orgánico enlatado o envasado,,0,80509,0 -24302,50473231,Maíz increíble orgánico enlatado o envasado,,0,80509,0 -24303,50473232,Maíz lochief orgánico enlatado o envasado,,0,80509,0 -24304,50473233,Maíz jubileo orgánico enlatado o envasado,,0,80509,0 -24305,50473234,Maíz jupbileo super dulce orgánico enlatado o envasado,,0,80509,0 -24306,50473235,Maíz confite korn orgánico enlatado o envasado,,0,80509,0 -24307,50473236,Maíz beso y acuso orgánico enlatado o envasado,,0,80509,0 -24308,50473237,Maíz lancelot orgánico enlatado o envasado,,0,80509,0 -24309,50473238,Maíz dulce maple orgánico enlatado o envasado,,0,80509,0 -24310,50473239,Maíz medley orgánico enlatado o envasado,,0,80509,0 -24311,50473240,Maíz merlin orgánico enlatado o envasado,,0,80509,0 -24312,50473241,Maíz milagro orgánico enlatado o envasado,,0,80509,0 -24313,50473242,Maíz nk – 199 orgánico enlatado o envasado,,0,80509,0 -24314,50473243,Maíz durazno y crema orgánico enlatado o envasado,,0,80509,0 -24315,50473244,Maíz blanco perla orgánico enlatado o envasado,,0,80509,0 -24316,50473245,Maíz pegasus orgánico enlatado o envasado,,0,80509,0 -24317,50473246,Maíz fenomenal orgánico enlatado o envasado,,0,80509,0 -24318,50473247,Maíz dama de platino orgánico enlatado o envasado,,0,80509,0 -24319,50473248,Maíz precioso orgánico enlatado o envasado,,0,80509,0 -24320,50473249,Maíz pristine orgánico enlatado o envasado,,0,80509,0 -24321,50473250,Maíz rápido orgánico enlatado o envasado,,0,80509,0 -24322,50473251,Maíz radiante orgánico enlatado o envasado,,0,80509,0 -24323,50473252,Maíz seneca brave orgánico enlatado o envasado,,0,80509,0 -24324,50473253,Maíz amanecer seneca orgánico enlatado o envasado,,0,80509,0 -24325,50473254,Maíz horizonte seneca orgánico enlatado o envasado,,0,80509,0 -24326,50473255,Maíz brillo de estrella seneca orgánico enlatado o envasado,,0,80509,0 -24327,50473256,Maíz noche blanca seneca orgánico enlatado o envasado,,0,80509,0 -24328,50473257,Maíz showcase orgánico enlatado o envasado,,0,80509,0 -24329,50473258,Maíz reina plateada orgánico enlatado o envasado,,0,80509,0 -24330,50473259,Maíz bella nieve orgánico enlatado o envasado,,0,80509,0 -24331,50473260,Maíz nieve de primavera orgánico enlatado o envasado,,0,80509,0 -24332,50473261,Maíz premio de primavera orgánico enlatado o envasado,,0,80509,0 -24333,50473262,Maíz azúcar y oro orgánico enlatado o envasado,,0,80509,0 -24334,50473263,Maíz rollo de azúcar orgánico enlatado o envasado,,0,80509,0 -24335,50473264,Maíz nieve de azúcar orgánico enlatado o envasado,,0,80509,0 -24336,50473265,Maíz baile de sol orgánico enlatado o envasado,,0,80509,0 -24337,50473266,Maíz estrella tell orgánico enlatado o envasado,,0,80509,0 -24338,50473267,Maíz terminador orgánico enlatado o envasado,,0,80509,0 -24339,50473268,Maíz tesoro orgánico enlatado o envasado,,0,80509,0 -24340,50473269,Maíz tuxedo orgánico enlatado o envasado,,0,80509,0 -24341,50475525,Pimentón jalapeño orgánico enlatado o envasado,,0,80509,0 -24342,50475600,Papas orgánicas en lata o en frasco,,0,80509,0 -24343,50475601,Papa blanca larga orgánica enlatada o envasada,,0,80509,0 -24344,50475602,Papa redonda blanca orgánica enlatada o envasada,,0,80509,0 -24345,50475603,Papa redonda roja orgánica enlatada o envasada,Papa de cambray orgánica enlatada o envasada,0,80509,0 -24346,50475604,Papa russet orgánica enlatada o envasada,,0,80509,0 -24347,50475605,Papa morada orgánica enlatada o envasada,,0,80509,0 -24348,50475606,Papa amarilla orgánica enlatada o envasada,,0,80509,0 -24349,50475607,Papa nueva orgánica enlatada o envasada,,0,80509,0 -24350,50475608,Papa especial orgánica enlatada o envasada,,0,80509,0 -24351,50475800,Vegetales marinos orgánicos en lata o en frasco,,0,80509,0 -24352,50475801,Agar-agar orgánico enlatado o envasado,,0,80509,0 -24353,50475802,Arame orgánica enlatada o envasada,,0,80509,0 -24354,50475803,Alga dulce orgánica enlatada o envasada,,0,80509,0 -24355,50475804,Habichuelas vert de mer orgánica enlatada o envasada,,0,80509,0 -24356,50475805,Hijiki orgánica enlatada o envasada,,0,80509,0 -24357,50475806,Musgo irlandés orgánica enlatada o envasada,,0,80509,0 -24358,50475807,Quelpo orgánico enlatado o envasado,,0,80509,0 -24359,50475808,Laver orgánico enlatado o envasado,,0,80509,0 -24360,50475809,Nori orgánico enlatado o envasado,,0,80509,0 -24361,50475810,Alga roja orgánica enlatada o envasada,,0,80509,0 -24362,50475811,Kale de mar orgánica enlatada o envasada,,0,80509,0 -24363,50475812,Lechuga de mar orgánica enlatada o envasada,,0,80509,0 -24364,50475813,Algas de mar orgánica enlatada o envasada,,0,80509,0 -24365,50475814,Spirulina orgánica enlatada o envasada,,0,80509,0 -24366,50475815,Susabi nuri orgánica enlatada o envasada,,0,80509,0 -24367,50475816,Wakame orgánica enlatada o envasada,,0,80509,0 -24368,50476800,Vegetales Nominant orgánicos en lata o en frasco,"Vegetales predominantes orgánicos en lata o en frasco, Nopal orgánico en lata o en frasco",0,80509,0 -24369,50476801,Alfalfa orgánica enlatada o envasada,,0,80509,0 -24370,50476802,Hojas de aloe orgánica enlatada o envasada,"Hojas de sábila orgánica enlatada o envasada, Penca de sábila orgánica enlatada o envasada",0,80509,0 -24371,50476803,Apio orgánico enlatado o envasado,,0,80509,0 -24372,50476804,Arrurruz orgánica enlatada o envasada,,0,80509,0 -24373,50476805,Punta de flecha orgánica enlatada o envasada,Sagittaria orgánica enlatada o envasada,0,80509,0 -24374,50476806,rúcula orgánica enlatada o envasada,,0,80509,0 -24375,50476807,Arum orgánica enlatada o envasada,Taro orgánico enlatado o envasado,0,80509,0 -24376,50476808,Raíz de bambú orgánica enlatada o envasada,,0,80509,0 -24377,50476809,Hojas de plátano orgánica enlatada o envasada,,0,80509,0 -24378,50476810,Batatas orgánica enlatada o envasada,,0,80509,0 -24379,50476811,Brotes de frijol orgánicos enlatados o envasados,,0,80509,0 -24380,50476812,Top de remolacha orgánica enlatada o envasada,Betabel orgánico enlatado o envasado,0,80509,0 -24381,50476813,Melón agrio orgánico enlatado o envasado,Balsamina orgánica enlatada o envasada,0,80509,0 -24382,50476814,Bayas de alcaparra orgánica orgánicas enlatadas o envasadas,,0,80509,0 -24383,50476815,Carob orgánico enlatado o envasado,Algarrobo orgánico enlatado o envasado,0,80509,0 -24384,50476816,Cha-om orgánico enlatado o envasado,,0,80509,0 -24385,50476817,Chayote o guatila orgánico enlatado o envasado,,0,80509,0 -24386,50476818,Garbanzo orgánico enlatado o envasado,,0,80509,0 -24387,50476819,Verde de crisantemos orgánico enlatado o envasado,,0,80509,0 -24388,50476820,Verde de diente de león orgánico enlatado o envasado,,0,80509,0 -24389,50476821,Diente de león orgánico enlatado o envasado,,0,80509,0 -24390,50476822,Dasheen orgánico enlatado o envasado,,0,80509,0 -24391,50476823,Puntas de alverja orgánico enlatado o envasado,,0,80509,0 -24392,50476824,Diakon orgánico enlatado o envasado,Rábano orgánico enlatado o envasado,0,80509,0 -24393,50476825,Donqua orgánica enlatada o envasada,Ginseng hembra orgánica enlatada o envasada,0,80509,0 -24394,50476826,Helecho orgánico enlatado o envasado,Brotes de helecho orgánico enlatado o envasado,0,80509,0 -24395,50476827,Gai choi orgánico enlatado o envasado,Mostaza castaña orgánica enlatada o envasada,0,80509,0 -24396,50476828,Gailon orgánico enlatado o envasado,Brécol chino orgánico enlatado o envasado,0,80509,0 -24397,50476829,Galanga orgánica enlatada o envasada,Jengibre azul orgánico enlatado o envasado,0,80509,0 -24398,50476830,Raíz de jengibre orgánica enlatada o envasada,,0,80509,0 -24399,50476831,Gobo orgánico enlatado o envasado,Garduña orgánica enlatada o envasada,0,80509,0 -24400,50476832,Brote de lúpulo orgánico enlatado o envasado,,0,80509,0 -24401,50476833,Rabano blanco orgánico enlatado o envasado,Rábano de caballo orgánico enlatado o envasado,0,80509,0 -24402,50476834,Jicama orgánica enlatada o envasada,,0,80509,0 -24403,50476835,Kuduz orgánica enlatada o envasada,Kudzu orgánico enlatado o envasado,0,80509,0 -24404,50476836,Bulbo de margarita orgánico enlatado o envasado,Bulbo de lili chino orgánico enlatado o envasado,0,80509,0 -24405,50476837,Linkok orgánico enlatado o envasado,,0,80509,0 -24406,50476838,Lo bok orgánico enlatado o envasado,,0,80509,0 -24407,50476839,Frijol largo orgánico enlatado o envasado,Fríjol serpiente orgánico enlatado o envasado,0,80509,0 -24408,50476840,Raíz de lotus orgánico enlatado o envasado,Rosa del Nilo orgánica enlatada o envasada,0,80509,0 -24409,50476841,Hojas de maguey orgánica enlatada o envasada,"Hoja de agave orgánica enlatada o envasada, Penca de maguey orgánica enlatada o envasada",0,80509,0 -24410,50476842,Mallows orgánico enlatado o envasado,Malva orgánica enlatada o envasada,0,80509,0 -24411,50476843,Sapote mamey orgánico enlatado o envasado,Mamey orgánico enlatado o envasado,0,80509,0 -24412,50476844,Moap orgánico enlatado o envasado,,0,80509,0 -24413,50476845,Moo orgánico enlatado o envasado,,0,80509,0 -24414,50476846,Moqua orgánica enlatada o envasada,,0,80509,0 -24415,50476847,Opos orgánicos enlatados o envasados,,0,80509,0 -24416,50476848,Corazon de palma orgánico enlatado o envasado,Palmito orgánico enlatado o envasado,0,80509,0 -24417,50476849,Paprika orgánica enlatada o envasada,,0,80509,0 -24418,50476850,Purslane orgánica enlatada o envasada,Verdolaga orgánica enlatada o envasada,0,80509,0 -24419,50476851,Raddichios orgánica enlatada o envasada,Achicoria morada orgánica enlatada o envasada,0,80509,0 -24420,50476852,Sinquas orgánica enlatada o envasada,Zapallo orgánico enlatado o envasado,0,80509,0 -24421,50476853,Frijol de soya orgánicos enlatados o envasados,,0,80509,0 -24422,50476854,Spoonwart orgánico enlatado o envasado,Hierba del escorbuto orgánica enlatada o envasada,0,80509,0 -24423,50476855,Uva tassele hyacinth orgánico enlatado o envasado,,0,80509,0 -24424,50476856,Taro orgánico enlatado o envasado,Tales orgánico enlatado o envasado,0,80509,0 -24425,50476857,Hoja de taro orgánica enlatada o envasada,,0,80509,0 -24426,50476858,Tallo de taro orgánico enlatado o envasado,,0,80509,0 -24427,50476859,Tapeguaje orgánico enlatado o envasado,,0,80509,0 -24428,50476860,Verde tierno orgánico enlatado o envasado,,0,80509,0 -24429,50476861,Tindora orgánico enlatado o envasado,,0,80509,0 -24430,50476862,Árbol de cebolla orgánico enlatado o envasado,Cebollas egipcias orgánica enlatada o envasada,0,80509,0 -24431,50476863,Udo orgánico enlatado o envasado,,0,80509,0 -24432,50476864,Agua castaño orgánico enlatado o envasado,Castaña de agua china orgánica enlatada o envasada,0,80509,0 -24433,50476865,Yumpi orgánico enlatado o envasado,Akankong orgánico enlatado o envasado,0,80509,0 -24434,50476866,Yautia orgánico enlatado o envasado,Mapuey orgánico enlatado o envasado,0,80509,0 -24435,50476867,Yu choy orgánico enlatado o envasado,,0,80509,0 -24436,50476868,Yuca orgánica enlatada o envasada,,0,80509,0 -24437,51101500,Antibióticos,,0,80509,0 -24438,51101503,Cloranfenicol,Clorfenamina,0,80509,0 -24439,51101504,Clindamicina,Clindamina,0,80509,0 -24440,51101507,Penicilina,"Bencilpenicilina, Ácido benzilpenicilínico",0,80509,0 -24441,51101508,Sulfonamidas antibióticas,Sulfonamidas antibióticas,0,80509,0 -24442,51101509,Tetraciclina,,0,80509,0 -24443,51101510,Oxitetraciclina,,0,80509,0 -24444,51101511,Amoxicilina,,0,80509,0 -24445,51101512,Cloxacilina,,0,80509,0 -24446,51101513,Neomicina,,0,80509,0 -24447,51101514,Sulfato framicetina,,0,80509,0 -24448,51101515,Clorhidrato de lincomicina,,0,80509,0 -24449,51101516,Gramicidina,,0,80509,0 -24450,51101518,Ofloxacina,,0,80509,0 -24451,51101519,Tiamfenicol,,0,80509,0 -24452,51101521,Pristinamicina,,0,80509,0 -24453,51101522,Claritromicina,,0,80509,0 -24454,51101523,Clorquinaldol,,0,80509,0 -24455,51101524,Bacitracina zinc,,0,80509,0 -24456,51101525,Peróxido de benzoilo,,"0,1",80509,0 -24457,51101526,Polimixinas,,0,80509,0 -24458,51101527,Colistina metansulfonato,,0,80509,0 -24459,51101528,Tirotricina,,0,80509,0 -24460,51101530,Trimetoprima,Trimetoprima,0,80509,0 -24461,51101531,Estreptograminas,,0,80509,0 -24462,51101532,Teicoplanina,,0,80509,0 -24463,51101533,Rifamicina,,0,80509,0 -24464,51101534,Ceftibuten,,0,80509,0 -24465,51101535,Cefradina,,0,80509,0 -24466,51101536,Moxifloxacina clorhidrato,Clorhidrato de moxifloxacina,0,80509,0 -24467,51101537,Lomefloxacina cloridrato,Clorhidrato de lomefloxacina,0,80509,0 -24468,51101538,Levofloxacina,Lecofloxacina,0,80509,0 -24469,51101539,Grepafloxacina clorhidrato,Clorhidrato de grapafloxacina,0,80509,0 -24470,51101540,Gatifloxacina,,0,80509,0 -24471,51101541,Enoxacina,,0,80509,0 -24472,51101542,Ciprofloxacina,,0,80509,0 -24473,51101543,Cefapirina,,0,80509,0 -24474,51101544,Loracarbef,,0,80509,0 -24475,51101545,Ceftizoxima,,0,80509,0 -24476,51101546,Norfloxacina,,0,80509,0 -24477,51101547,Cefprozil,,0,80509,0 -24478,51101548,Fosfomicina trometamol,,0,80509,0 -24479,51101549,Linezolida,,0,80509,0 -24480,51101550,Cefalexina,,0,80509,0 -24481,51101551,Ceftriaxona,,0,80509,0 -24482,51101552,Ceftazidima,,0,80509,0 -24483,51101553,Alatrofloxacina,,0,80509,0 -24484,51101554,Dicloxacilina sódica,,0,80509,0 -24485,51101555,Aztreonam,,0,80509,0 -24486,51101556,Minociclina,,0,80509,0 -24487,51101557,Doxiciclina,,0,80509,0 -24488,51101558,Demeclociclina,,0,80509,0 -24489,51101559,Clortetraciclina,,0,80509,0 -24490,51101560,Ticarcilina,,0,80509,0 -24491,51101561,Piperacilina,,0,80509,0 -24492,51101562,Oxacilina sódica,,0,80509,0 -24493,51101563,Mesilato de trovafloxacina,,0,80509,0 -24494,51101564,Mezlocilina,,0,80509,0 -24495,51101565,Cefpodoxima proxetil,,0,80509,0 -24496,51101566,Carbenicilina,,0,80509,0 -24497,51101567,Ampicilina,,0,80509,0 -24498,51101568,Troleandomicina,,0,80509,0 -24499,51101569,Roxitromicina,,0,80509,0 -24500,51101570,Eritromicina,,0,80509,0 -24501,51101571,Diritromicina,,0,80509,0 -24502,51101572,Azitromicina,,0,80509,0 -24503,51101573,Cefuroxima,,0,80509,0 -24504,51101574,Furazolidona,,0,80509,0 -24505,51101575,Nafcilina sódica,,0,80509,0 -24506,51101576,Cefalotina,,0,80509,0 -24507,51101577,Cefdinir,,0,80509,0 -24508,51101578,Cefazolina,,0,80509,0 -24509,51101579,Cefamandol,,0,80509,0 -24510,51101580,Cefadroxilo,,0,80509,0 -24511,51101581,Cefaclor,,0,80509,0 -24512,51101582,Tobramicina,,0,80509,0 -24513,51101583,Cefditoren,,0,80509,0 -24514,51101584,Gentamicina,,0,80509,0 -24515,51101585,Netilmicina,Sulfato de netilmicina,0,80509,0 -24516,51101586,Amikacina,,0,80509,0 -24517,51101587,Kanamicina,,0,80509,0 -24518,51101588,Sparfloxacina,,0,80509,0 -24519,51101589,Carbapenémicos incluyendo tienamicinas,,0,80509,0 -24520,51101590,Quinupristina,,0,80509,0 -24521,51101591,Vancomicina,,0,80509,0 -24522,51101592,Cefoxitina,,0,80509,0 -24523,51101593,Cefotaxima,"Cefotaxima, Cefotaxime",0,80509,0 -24524,51101594,Cefepima,Cefepime,0,80509,0 -24525,51101595,Cefotetan,,0,80509,0 -24526,51101596,Cefoperazona,,0,80509,0 -24527,51101597,Mupirocina,,0,80509,0 -24528,51101598,Cefonicida,,0,80509,0 -24529,51101599,Cefixima,,0,80509,0 -24530,51101600,"Amebicidas, Tricomonacidas y Antiprotozoarios",,0,80509,0 -24531,51101601,Atovacuona,Atuvacuona,0,80509,0 -24532,51101602,Clorhidrato de eflomitina,,0,80509,0 -24533,51101603,Metronidazol,,0,80509,0 -24534,51101604,Antimoniato de meglumina,,0,80509,0 -24535,51101606,Rifapentina,,0,80509,0 -24536,51101607,Óxido de calcio,,1,80509,0 -24537,51101610,Clorocresol,,0,80509,0 -24538,51101611,Meropenem,,0,80509,0 -24539,51101612,Polynoxylin,Polinoxilina,0,80509,0 -24540,51101613,Isetionato de pentamidina,,0,80509,0 -24541,51101614,Furoato de diloxanida,,0,80509,0 -24542,51101616,Melarsoprol,,0,80509,0 -24543,51101617,Tinidazol,Tindamax,0,80509,0 -24544,51101618,Taurolidina,,0,80509,0 -24545,51101619,Secnidazol,,0,80509,0 -24546,51101620,Iodoquinol,,0,80509,0 -24547,51101624,Clorhidrato de metronidazol,,0,80509,0 -24548,51101625,Sulfato de paromomicina,,0,80509,0 -24549,51101629,Glucuronato de trimetrexato,,0,80509,0 -24550,51101630,Isetionato de propamidina,,0,80509,0 -24551,51101631,Nitazoxanida,,0,80509,0 -24552,51101632,Faropenem de sodio,,0,80509,0 -24553,51101700,Antihelmínticos y otros antiparasitarios,Vermífugo,0,80509,0 -24554,51101701,Albendazol,,0,80509,0 -24555,51101702,Mebendazol,"Lomper, Ovex, Vermo",0,80509,0 -24556,51101703,Oxamniquina,,0,80509,0 -24557,51101704,Citrato de piperazina,,0,80509,0 -24558,51101705,Praziquantel,,0,80509,0 -24559,51101706,Pamoato de pirantel,,0,80509,0 -24560,51101707,Tiabendazol,,0,80509,0 -24561,51101708,Sulfanilamida,,0,80509,0 -24562,51101709,Niclosamida,"Niclosamida, Niclosamide",0,80509,0 -24563,51101710,Piperazina,Dietilendiamina,1,80509,0 -24564,51101711,Citrato de dietilcarbamazina,,0,80509,0 -24565,51101712,Tiocianoacetato de isobornilo,,0,80509,0 -24566,51101713,Antiparasitario tópico malatión,,0,80509,0 -24567,51101714,Metilparabeno,Metil p-hidroxibenzoato,0,80509,0 -24568,51101715,Antiparasitario tópico permetrina,,0,80509,0 -24569,51101716,Tetracloroetileno,,1,80509,0 -24570,51101717,Ivermectina,,0,80509,0 -24571,51101718,Benzoato de bencilo,,0,80509,0 -24572,51101719,Butóxido de piperonilo,,0,80509,0 -24573,51101720,Lindano,"Gama-hexaclorociclohexano, Gamexane, Gammalli",0,80509,0 -24574,51101721,Febantel,,0,80509,0 -24575,51101800,Fungicidas,,0,80509,0 -24576,51101801,Anfotericina b,,0,80509,0 -24577,51101802,Clorhidrato de butenafina,,0,80509,0 -24578,51101803,Nitrato de butoconazol,,0,80509,0 -24579,51101804,Ciclopiroxolamina,,0,80509,0 -24580,51101805,Clotrimazol,,0,80509,0 -24581,51101806,Nitrato de econazol,,0,80509,0 -24582,51101807,Fluconazol,,0,80509,0 -24583,51101808,Flucitosina,,0,80509,0 -24584,51101809,Griseofulvina,,0,80509,0 -24585,51101810,Itraconazol,Voriconazol,0,80509,0 -24586,51101811,Ketoconazol,,0,80509,0 -24587,51101812,Miconazol,,0,80509,0 -24588,51101813,Clorhidrato de naftifina,,0,80509,0 -24589,51101814,Natamicina,Pimaricina,0,80509,0 -24590,51101815,Nistatina,,0,80509,0 -24591,51101816,Nitrato de oxiconazol,,0,80509,0 -24592,51101817,Nitrato de sulconazol,,0,80509,0 -24593,51101818,Clorhidrato de terbinafina,,0,80509,0 -24594,51101819,Nitrato de terconazol,,0,80509,0 -24595,51101820,Tioconazol,,0,80509,0 -24596,51101821,Tolnaftato,,0,80509,0 -24597,51101824,Ácido undecilénico,,0,80509,0 -24598,51101825,Ciclopirox,,0,80509,0 -24599,51101826,Sulfato de estreptomicina,,0,80509,0 -24600,51101827,Isoconazol,,0,80509,0 -24601,51101828,Terconazol,,0,80509,0 -24602,51101829,Undecilenato de calcio,,0,80509,0 -24603,51101830,Ácido octanoico,Ácido caprílico,0,80509,0 -24604,51101831,Triacetina,"Triacetato de glicerina, Triglicérido triacetoxy propano",0,80509,0 -24605,51101832,Voriconazol,,0,80509,0 -24606,51101834,Nitrato de miconazol,,0,80509,0 -24607,51101835,Acetato de caspofungina,,0,80509,0 -24608,51101836,Propionato de sodio,Propanoato de sodio,0,80509,0 -24609,51101837,Clorhidrato de amorolfina,,0,80509,0 -24610,51101900,Medicamentos contra la malaria,,0,80509,0 -24611,51101901,Aminoquinolinas,,0,80509,0 -24612,51101902,Clorhidrato de mefloquina,,0,80509,0 -24613,51101903,Fosfato de primaquina,,0,80509,0 -24614,51101904,Sulfato de quinina,,0,80509,0 -24615,51101905,Cloroquina,,0,80509,0 -24616,51101906,Proguanil,,0,80509,0 -24617,51101907,Pirimetamina,,0,80509,0 -24618,51101908,Artemeter,,0,80509,0 -24619,51101909,Clorhidrato de cloroquina,,0,80509,0 -24620,51101910,Fosfato de cloroquina,,0,80509,0 -24621,51101911,Clorhidrato de halofantrina,,0,80509,0 -24622,51101912,Sulfato de hidroxicloroquina,,0,80509,0 -24623,51101913,Dihidrocloruro amodiaquina,,0,80509,0 -24624,51101914,Artesunato de sodio,Artesunato Sódico,0,80509,0 -24625,51101915,Lumefantrina o benflumetol,"Benfumetol, Lumefantrina",0,80509,0 -24626,51102000,Medicamentos antituberculosos,,0,80509,0 -24627,51102001,Cicloserina,,0,80509,0 -24628,51102002,Clorhidrato de etambutol,,0,80509,0 -24629,51102003,Isoniazida,Isoniacida,0,80509,0 -24630,51102004,Rifubutina,Rifabutina,0,80509,0 -24631,51102005,Rifampicina,,0,80509,0 -24632,51102006,Guayacol,Guaiacol,0,80509,0 -24633,51102007,Tiacetazona,,0,80509,0 -24634,51102008,Pirazinamida,,0,80509,0 -24635,51102009,Sulfato de capreomicina,,0,80509,0 -24636,51102010,Rifabutina,,0,80509,0 -24637,51102100,Leprostáticos,,0,80509,0 -24638,51102101,Clofazimina,,0,80509,0 -24639,51102102,Dapsona,,0,80509,0 -24640,51102200,Antiinfecciosos y analgésicos urinarios,,0,80509,0 -24641,51102201,Cinoxacina,,0,80509,0 -24642,51102202,Clorhidrato de flavoxato,,0,80509,0 -24643,51102203,Hipurato de metenamina,,0,80509,0 -24644,51102204,Mandelato de metenamina,,0,80509,0 -24645,51102205,Ácido nalidíxico,,0,80509,0 -24646,51102206,Nitrofurantoina,,0,80509,0 -24647,51102207,Cloruro de oxibutinina,,0,80509,0 -24648,51102208,Pentosano polisulfato sódico,,0,80509,0 -24649,51102209,Clorhidrato de fenazopiridina,,0,80509,0 -24650,51102211,Alfuzosina hidrocloruro,"Clorhidrato de Alfuzosina, Hidrocloruro de Alfuzosina",0,80509,0 -24651,51102212,Ácido acetohidroxámico,,0,80509,0 -24652,51102213,Sulfato de cefpiroma,,0,80509,0 -24653,51102214,Fenazopiridina,,0,80509,0 -24654,51102300,Medicamentos antivirales,,0,80509,0 -24655,51102301,Aciclovir,,0,80509,0 -24656,51102302,Clorhidrato de amantadina,,0,80509,0 -24657,51102304,Didanosina,DDL,0,80509,0 -24658,51102305,Famciclovir,,0,80509,0 -24659,51102306,Foscarnet sódico,Foscavir,0,80509,0 -24660,51102307,Ganciclovir sódico,"Cytovene IV, Ganciclovir, Glanicicovir Sódic",0,80509,0 -24661,51102308,Idoxuridina,,0,80509,0 -24662,51102309,Sulfato de indinavir,Indinavir,0,80509,0 -24663,51102310,Lamivudina,,0,80509,0 -24664,51102311,Nevirapina,Viramune,0,80509,0 -24665,51102312,Ribavirina,Virazole,0,80509,0 -24666,51102313,Clorhidrato de rimantadina,,0,80509,0 -24667,51102314,Ritonavir,,0,80509,0 -24668,51102315,Mesilato de saquinavir,SQV,0,80509,0 -24669,51102316,Estavidina,D4T,0,80509,0 -24670,51102317,Trifluradina,,0,80509,0 -24671,51102318,Clorhidrato de valaciclovir,"HCI de valaciclovir, Valtrex",0,80509,0 -24672,51102319,Vidarabina,,0,80509,0 -24673,51102320,Zalcitabina,,0,80509,0 -24674,51102321,Zidovudina,"AZT, Azidotimidina",0,80509,0 -24675,51102322,Sulfato de abacavir,,0,80509,0 -24676,51102323,Cidofovir,,0,80509,0 -24677,51102324,Mesilato de delavirdina,,0,80509,0 -24678,51102325,Docosanol,,0,80509,0 -24679,51102326,Efavirenz,,0,80509,0 -24680,51102327,Amprenavir,,0,80509,0 -24681,51102328,Fomivirsen sódico,,0,80509,0 -24682,51102329,Mesilato de nelfinavir,NFV,0,80509,0 -24683,51102330,Penciclovir,,0,80509,0 -24684,51102331,Saquinavir,,0,80509,0 -24685,51102332,Fumarato de disoproxilo tenofovir,"TDF, Tenofovir",0,80509,0 -24686,51102333,Hidrocloruro de valganciclovir,"Clorhidrato de valganciclovir, HCI de valganciclovir, Valganciclovi",0,80509,0 -24687,51102334,Zanamivir,,0,80509,0 -24688,51102335,Sodio de aciclovir,"Aciclovir, Aciclovir sódico, Zovira",0,80509,0 -24689,51102336,Moroxidina,,0,80509,0 -24690,51102337,Adefovir dipivoxil,,0,80509,0 -24691,51102338,Oseltamivir,,0,80509,0 -24692,51102339,Ganciclovir,,0,80509,0 -24693,51102340,Estavudina,,0,80509,0 -24694,51102341,Valaciclovir,,0,80509,0 -24695,51102342,Valganciclovir,,0,80509,0 -24696,51102343,"Estavudina, lamivudina y nevirapina",,0,80509,0 -24697,51102344,Lamivudine and zidovudine,Lamivudina y zidovudina,0,80509,0 -24698,51102345,Lopinavir y ritonavir,,0,80509,0 -24699,51102346,"Zidovudina, neviparina and lamivudina","Zidovudina, nevirapina y lamivudina",0,80509,0 -24700,51102400,Oftálmico,,0,80509,0 -24701,51102402,Hipromelosa,Hidroxipropilmetilcelulos,0,80509,0 -24702,51102403,Sulfacetamida,,0,80509,0 -24703,51102500,Antihelmínticos veterinarios,,0,80509,0 -24704,51102501,Naftenato de cobre,,0,80509,0 -24705,51102502,Toltrazuril,,0,80509,0 -24706,51102503,Flumetrina,,0,80509,0 -24707,51102505,Triclorfon,,0,80509,0 -24708,51102506,Oxfendazol,,0,80509,0 -24709,51102507,Clorhidrato de piperidolato,,0,80509,0 -24710,51102600,Antibióticos veterinarios,,0,80509,0 -24711,51102601,Enrofloxacina,,0,80509,0 -24712,51102700,Antisépticos,Antimicrobianos,0,80509,0 -24713,51102701,Clofoctol,,0,80509,0 -24714,51102702,Agua estéril para irrigación,,0,80509,0 -24715,51102705,Cetrimida,,0,80509,0 -24716,51102706,Ácido acético antiséptico,,0,80509,0 -24717,51102707,Gluconato de clorhexidina,,0,80509,0 -24718,51102708,Formaldehído antiséptico,,0,80509,0 -24719,51102709,Peróxido de hidrógeno antiséptico,Agua oxigenada,0,80509,0 -24720,51102710,Antisépticos basados en alcohol o acetona,,0,80509,0 -24721,51102711,Oxiquinolina,,0,80509,0 -24722,51102712,Antisépticos fenólicos,,0,80509,0 -24723,51102713,Povidona yodada,"Polividona yodada, Povidona, Yodopolivinilpirrolidona",0,80509,0 -24724,51102714,Solución de cloruro sódico para irrigación,,0,80509,0 -24725,51102715,Clioquinol,,0,80509,0 -24726,51102717,Nitrofurazona,,0,80509,0 -24727,51102718,Nitrato de plata,,1,80509,0 -24728,51102719,Yodoformo,"Triyoduro de carbono, Triyoduro de metilo",0,80509,0 -24729,51102720,Cloroxilenol,"Cloro-dimetilfenol, Para-cloro-meta-xilenol",0,80509,0 -24730,51102721,Ictamol,,0,80509,0 -24731,51102722,Geles o soluciones tópicas de yodo,,1,80509,0 -24732,51102723,Merbromina,Mercurocromo,0,80509,0 -24733,51102724,Cloruro de benzalconio,,0,80509,0 -24734,51102725,Hexilresorcinol,,0,80509,0 -24735,51102726,Peróxido de carbamida,Peróxido de urea,0,80509,0 -24736,51102727,Yoduro de timol,,0,80509,0 -24737,51102728,Perborato sódico,,"0,1",80509,0 -24738,51102729,Cloruro de cetilpiridinio,,0,80509,0 -24739,51102730,Isetionato de hexamidina,,0,80509,0 -24740,51111500,Agentes Alquilante,Agentes contra el cáncer,0,80509,0 -24741,51111501,Amifostina,"Amifostine, Ethyol",0,80509,0 -24742,51111502,Busulfán,,0,80509,0 -24743,51111503,Carboplatino,,0,80509,0 -24744,51111504,Carmustina,,0,80509,0 -24745,51111505,Clorambucil,Clorambucilo,0,80509,0 -24746,51111506,Cisplatino,Cis-diaminodicloroplatino,0,80509,0 -24747,51111507,Ciclofosfamida,"Citofosfana, Citoxan, Endoxan, Genuxal, Neosar",0,80509,0 -24748,51111508,Dacarbazina,Dacarbacina,0,80509,0 -24749,51111509,Ifosfamida,,0,80509,0 -24750,51111510,Lomustina,,0,80509,0 -24751,51111511,Clorhidrato de mecloretamina,"Clorhidrato de mecloretamina, Clormetina, Mecloretamin",0,80509,0 -24752,51111512,Melfalán,,0,80509,0 -24753,51111513,Mesna,,0,80509,0 -24754,51111514,Pipobroman,,0,80509,0 -24755,51111515,Estreptozocina,,0,80509,0 -24756,51111516,Tiotepa,"TESPA, Thiophosphoamide, Tiotep",0,80509,0 -24757,51111517,Mostaza de uracilo,,0,80509,0 -24758,51111518,Clorhidrato de melfalan,,0,80509,0 -24759,51111519,Temozolomida,,0,80509,0 -24760,51111520,Clorhidrato de procarbazina,,0,80509,0 -24761,51111521,Altretamina,,0,80509,0 -24762,51111522,Ceftriaxona sódica,,0,80509,0 -24763,51111600,Antimetabolitos,,0,80509,0 -24764,51111601,Cladribina,"2-Clorodeoxiadenosina, Cladribina",0,80509,0 -24765,51111602,Citarabina,"Arabinosilcitosina, Citarabina",0,80509,0 -24766,51111603,Floxuridina,,0,80509,0 -24767,51111604,Fosfato de fludarabina,"Fludarabina, Fludarabín fosfato",0,80509,0 -24768,51111605,Fluorouracilo,,0,80509,0 -24769,51111606,Hidroxiurea,,0,80509,0 -24770,51111609,Mercaptopurina,"6-mercaptopurina, Mercaptopurina",0,80509,0 -24771,51111610,Metotrexato,MTX,0,80509,0 -24772,51111611,Tenipósido,,0,80509,0 -24773,51111612,Tioguanina,"2-amino-6-mercaptopurina, 6-Tioguanina",0,80509,0 -24774,51111613,Lenograstim,,0,80509,0 -24775,51111614,Etopósido,VP-16,0,80509,0 -24776,51111615,Glutatión,,0,80509,0 -24777,51111616,Capecitabina,,0,80509,0 -24778,51111617,Clorhidrato de gemcitabina,,0,80509,0 -24779,51111618,Metotrexato de sodio,Ametopterina,0,80509,0 -24780,51111700,Antibióticos antineoplásicos,Agentes antitumorales,0,80509,0 -24781,51111701,Sulfato de bleomicina,Blenoxane,0,80509,0 -24782,51111702,Dactinomicina,Actinomicina-D,0,80509,0 -24783,51111703,Daunorubicinas,"Daunomicina, Deunorrubicina",0,80509,0 -24784,51111704,Mitomicina,,0,80509,0 -24785,51111705,Mitotano,,0,80509,0 -24786,51111706,Clorhidrato de mitoxantrona,,0,80509,0 -24787,51111707,Pentostatina,"Desoxicoformicina, Pentostatina",0,80509,0 -24788,51111708,Plicamicina,"Mitramicina, Mitrhacin",0,80509,0 -24789,51111709,Sulfato de vincristina,,0,80509,0 -24790,51111710,Epirubicina,,0,80509,0 -24791,51111711,Clorhidrato de doxorubicina,,0,80509,0 -24792,51111712,Pirarubicina,,0,80509,0 -24793,51111713,Alemtuzumab,,0,80509,0 -24794,51111714,Clorhidrato de doxorrubicina liposomal,,0,80509,0 -24795,51111715,Epirubicina hidrocloruro,Epirubicina,0,80509,0 -24796,51111716,Rituximab,,0,80509,0 -24797,51111717,Trastuzumab,,0,80509,0 -24798,51111718,Valrubicina,,0,80509,0 -24799,51111719,Clorhidrato de idarrubicina,,0,80509,0 -24800,51111720,Infliximab,,0,80509,0 -24801,51111721,Ibritumomab tiuxetan,,0,80509,0 -24802,51111722,Imatinib,,0,80509,0 -24803,51111723,Leuprorelina,,0,80509,0 -24804,51111800,Hormonas y antihormonas,,0,80509,0 -24805,51111801,Anastrozol,Arimidex,0,80509,0 -24806,51111802,Bicalutamida,,0,80509,0 -24807,51111803,Fosfato sódico de estramustina,Estramusitina,0,80509,0 -24808,51111804,Flutamida,,0,80509,0 -24809,51111805,Acetato de goserelina,,0,80509,0 -24810,51111806,Clorhidrato de irinotecan,,0,80509,0 -24811,51111807,Acetato de leuprolide,"Eligard, Lupron, Viadu",0,80509,0 -24812,51111808,Nilutamida,,0,80509,0 -24813,51111809,Tamoxifeno,,0,80509,0 -24814,51111810,Testolactona,,0,80509,0 -24815,51111811,Clorhidrato de topotecan,Topotecán,0,80509,0 -24816,51111812,Sulfato de vinblastina,"Velban, Velsar",0,80509,0 -24817,51111813,Sulfato de vincristina,Vincristina,0,80509,0 -24818,51111814,Tartrato de vinorelbina,Navelbina,0,80509,0 -24819,51111815,Triptorelina,,0,80509,0 -24820,51111816,Buserelina,,0,80509,0 -24821,51111817,Citrato de toremifeno,,0,80509,0 -24822,51111818,Pamoato de triptorelina,,0,80509,0 -24823,51111819,Fulvestrant,"Faslodex, Fulvestrant",0,80509,0 -24824,51111820,Letrozol,Femara,0,80509,0 -24825,51111821,Citrato de tamoxifeno,Tamoxifeno,0,80509,0 -24826,51111822,Oxaliplatino,,0,80509,0 -24827,51111823,Ácido pamidrónico,,0,80509,0 -24828,51111824,Exemestano,Aromasin,0,80509,0 -24829,51111825,Erlotinib,Terceva,0,80509,0 -24830,51111826,Mesilato de gefitinib,,0,80509,0 -24831,51111827,Tibolona,,0,80509,0 -24832,51111828,Ciproterona,,0,80509,0 -24833,51111900,Productos antitumorales naturales,,0,80509,0 -24834,51111901,Asparaginasa,"Erwinia L-asparaguinasa, L-asparaguinasa",0,80509,0 -24835,51111902,Docetaxel,Taxotere,0,80509,0 -24836,51111904,Paclitaxel,"Onxal, Paclitaxel, Taxo",0,80509,0 -24837,51111905,Porfímero sódico,Photofrin,0,80509,0 -24838,51111906,Pegaspargasa,"Oncaspar, PEG-L-asparaginasa",0,80509,0 -24839,51111907,Fenilbutirato de sodio,,0,80509,0 -24840,51121500,Agentes antiarritmia,Agentes para el ritmo cardiaco,0,80509,0 -24841,51121501,Adenosina,,0,80509,0 -24842,51121502,Digoxina,,0,80509,0 -24843,51121503,Gluconato de quinidina,,0,80509,0 -24844,51121504,Flecainida,,0,80509,0 -24845,51121506,Clorhidrato de sotalol,,0,80509,0 -24846,51121507,Clorhidrato de hidroquinidina,,0,80509,0 -24847,51121509,Propafenona,,0,80509,0 -24848,51121510,Disopiramida,,0,80509,0 -24849,51121511,Clorhidrato de amiodarona,,0,80509,0 -24850,51121512,Tosilato de bretilio,,0,80509,0 -24851,51121513,Fosfato de disopiramida,,0,80509,0 -24852,51121514,Dofetilida,,0,80509,0 -24853,51121515,Acetato de flecainida,,0,80509,0 -24854,51121516,Ibutilida fumarato,,0,80509,0 -24855,51121517,Clorhidrato de mexiletina,,0,80509,0 -24856,51121518,Clorhidrato de moricizina,,0,80509,0 -24857,51121519,Clorhidrato de procainamida,,0,80509,0 -24858,51121520,Clorhidrato de propafenona,,0,80509,0 -24859,51121521,Quinidina poligalacturonato,,0,80509,0 -24860,51121522,Clorhidrato de tocainida,,0,80509,0 -24861,51121523,Sulfato de quinidina,,0,80509,0 -24862,51121600,Medicamentos antiangina,,0,80509,0 -24863,51121601,Nitrato de amilo,,1,80509,0 -24864,51121602,Dinitrato de isosorbida,,"0,1",80509,0 -24865,51121603,Nitroglicerina,"Trinitoglicerina, Trinitrato de glicerito",1,80509,0 -24866,51121604,Tetranitrato de pentaeritrito,Pentrita,0,80509,0 -24867,51121607,Nicorandi,,0,80509,0 -24868,51121608,Clorhidrato de nicardipina,,0,80509,0 -24869,51121609,Molsidomina,,0,80509,0 -24870,51121610,Pralidoxima,,0,80509,0 -24871,51121611,Trimetazidina,Idaptan,0,80509,0 -24872,51121614,Mononitrato de isosorbida,,0,80509,0 -24873,51121615,Clorhidrato de bepridil,,0,80509,0 -24874,51121616,Clorhidrato de isoxsuprina,,0,80509,0 -24875,51121617,Ranolazina,,0,80509,0 -24876,51121700,Medicamentos antihipertensivos,,0,80509,0 -24877,51121701,Diazóxido,Proglycem,0,80509,0 -24878,51121702,Terazosina,,0,80509,0 -24879,51121703,Captopril,,0,80509,0 -24880,51121704,Lisinopril,,0,80509,0 -24881,51121705,Felodipino,,0,80509,0 -24882,51121706,Isradipina,,0,80509,0 -24883,51121707,Verapamilo,,0,80509,0 -24884,51121708,Metildopa,Alfametildopa,0,80509,0 -24885,51121709,Carvedilol,,0,80509,0 -24886,51121710,Losartán potásico,,0,80509,0 -24887,51121711,Minoxidil,,0,80509,0 -24888,51121713,Clorhidrato de diltiazem,,0,80509,0 -24889,51121714,Vincamina,,0,80509,0 -24890,51121715,Enalapril,,0,80509,0 -24891,51121716,Piretanida,,0,80509,0 -24892,51121717,Mesilato de eprosartán,,0,80509,0 -24893,51121718,Clorhidrato de clonidina,,0,80509,0 -24894,51121721,Tartrato de metoprolol,,0,80509,0 -24895,51121722,Reserpina,,0,80509,0 -24896,51121724,Naftidrofurilo oxalato,"Nafronil, Nafronil oxalato",0,80509,0 -24897,51121725,Bisoprolol fumarato,,0,80509,0 -24898,51121726,Indapamida,,0,80509,0 -24899,51121727,Penbutolol,,0,80509,0 -24900,51121728,Prazosina,"Hypovase, Minipress, Pressin, Vasoflex ,",0,80509,0 -24901,51121729,Celiprolol,,0,80509,0 -24902,51121730,Ramipril,,0,80509,0 -24903,51121731,Trandolapril,,0,80509,0 -24904,51121732,Combinación de trandolapril y clorhidrato de verapamilo,Tarka (nombre comercial de las capsulas),0,80509,0 -24905,51121733,Valsartán,,0,80509,0 -24906,51121734,Nicotinato de xantinol,Niacinato de xantinol,0,80509,0 -24907,51121735,Candesartán cilexetilo,,0,80509,0 -24908,51121737,Clorhidrato de benazepril,"Benazepril, Lotensin",0,80509,0 -24909,51121738,Deserpidina,,0,80509,0 -24910,51121739,Clorhidrato de verapamilo,,0,80509,0 -24911,51121740,Nisoldipino,,0,80509,0 -24912,51121741,Diclorhidrato de mibefradil,,0,80509,0 -24913,51121742,Diltiazem malato,,0,80509,0 -24914,51121743,Besilato de amlodipina,Norvac (nombre comercial),0,80509,0 -24915,51121744,Maleato de enalpril,,0,80509,0 -24916,51121745,Dihidrato de enalaprilato,,0,80509,0 -24917,51121746,Mesilato de fenoldopam,,0,80509,0 -24918,51121747,Fosinopril sódico,,0,80509,0 -24919,51121748,Acetato de guanabenz,,0,80509,0 -24920,51121749,Sulfato de guanadrel,,0,80509,0 -24921,51121750,Sulfato de guanetidina,,0,80509,0 -24922,51121751,Clorhidrato de guanfacina,,0,80509,0 -24923,51121752,Hidralazina hidrocloruro,,0,80509,0 -24924,51121753,Irbesartán,,0,80509,0 -24925,51121754,Clorhidrato de mecamilamina,,0,80509,0 -24926,51121755,Clorhidrato de metildopato,Alfametildopa,0,80509,0 -24927,51121756,Metirosina,"AMPT, a-Metiltirosina",0,80509,0 -24928,51121757,Clorhidrato de moexipril,,0,80509,0 -24929,51121758,Nitroprusiato de sodio,,0,80509,0 -24930,51121759,Olmesartán medoxomilo,,0,80509,0 -24931,51121760,Perindopril erbumina,,0,80509,0 -24932,51121761,Clorhidrato de quinapril,,0,80509,0 -24933,51121762,Rauwolfia serpentina,Reserpina,0,80509,0 -24934,51121763,Telmisartán,,0,80509,0 -24935,51121764,Quinapril,,0,80509,0 -24936,51121765,Metoprolol,,0,80509,0 -24937,51121766,Atomoxetina,,0,80509,0 -24938,51121767,Nebivolol,,0,80509,0 -24939,51121768,Benazepril,,0,80509,0 -24940,51121769,Perindopril,,0,80509,0 -24941,51121770,Cilazapril,,0,80509,0 -24942,51121771,Fosinopril,,0,80509,0 -24943,51121772,Gugulsterona,,0,80509,0 -24944,51121773,Clorhidrato de lercanidipina,,0,80509,0 -24945,51121774,Levosimendán,Simdax,0,80509,0 -24946,51121775,Tirofibán,,0,80509,0 -24947,51121777,Trimetazidina dihidrocloruro,,0,80509,0 -24948,51121778,Rilmenidina,"Dorzolamida, Timol",0,80509,0 -24949,51121779,Clevidipina,Cleviprex,0,80509,0 -24950,51121780,Losartán,Cozaar,0,80509,0 -24951,51121800,Agentes antihiperlipidémicos/hipocolesterolémicos,,0,80509,0 -24952,51121801,Fluvastatina sódica,,0,80509,0 -24953,51121802,Lovastatina,,0,80509,0 -24954,51121803,Simvastatina,,0,80509,0 -24955,51121804,Clofibrato,,0,80509,0 -24956,51121805,Gemfibrozilo,,0,80509,0 -24957,51121806,Pravastatina de sodio,,0,80509,0 -24958,51121807,Polidocanol,Aethoxisclerol,0,80509,0 -24959,51121808,Probucol,,0,80509,0 -24960,51121809,Fenofibrato,,0,80509,0 -24961,51121810,Atorvastatina de calcio,,0,80509,0 -24962,51121811,Cerivastatina sódica,,0,80509,0 -24963,51121812,Colina,,0,80509,0 -24964,51121813,Bitartrato de colina,,0,80509,0 -24965,51121814,Inositol,,0,80509,0 -24966,51121815,Racemetionina,,0,80509,0 -24967,51121816,Fosfatidilcolina,"Lecitina, Polienilfosfatidilcolina",0,80509,0 -24968,51121817,Colestiramina,,0,80509,0 -24969,51121818,Atorvastatina,,0,80509,0 -24970,51121819,Ezetimiba,,0,80509,0 -24971,51121820,Sulfato de condroitina a,Condroitín sulfato,0,80509,0 -24972,51121821,Pravastatina,,0,80509,0 -24973,51121822,Mevastatina,,0,80509,0 -24974,51121823,Rosuvastatina,,0,80509,0 -24975,51121900,Medicamentos usados para insuficiencia cardíaca congestiva,,0,80509,0 -24976,51121901,Lactato de inamrinona,,0,80509,0 -24977,51121902,Lactato de milrinona,,0,80509,0 -24978,51121903,Digitoxina,"Digitalin, Digitofilina",0,80509,0 -24979,51121904,Nifedipina,,0,80509,0 -24980,51121905,Enoximona,,0,80509,0 -24981,51121906,Betaína,Trimetilglicina,0,80509,0 -24982,51121907,Nesiritida,,0,80509,0 -24983,51121908,Hoja de digital en polvo o preparados,,0,80509,0 -24984,51121909,Eplerenona,,0,80509,0 -24985,51121910,Lanatosido c,Isolánido,0,80509,0 -24986,51122100,Vasodilatadores,,0,80509,0 -24987,51122101,Mesilato de dihidroergocristina,,0,80509,0 -24988,51122102,Clorhidrato de buflomedil,,0,80509,0 -24989,51122103,Clorhidrato de linsidomina,,0,80509,0 -24990,51122104,Papaverina,,0,80509,0 -24991,51122105,Pentifilina,"Oxipentafilina, Vazofilina",0,80509,0 -24992,51122107,Tartrato de ifenprodil,,0,80509,0 -24993,51122108,Mesilatos ergoloides,"Co-dergocrina mesilato, Dihidroergotoxina",0,80509,0 -24994,51122109,Clorhidrato de papaverina,,0,80509,0 -24995,51122110,Nimodipina,Nimodipino,0,80509,0 -24996,51122111,Nitrito de amilo,,1,80509,0 -24997,51122112,Alprostadil,"Caveryect, Muse, Prolisin",0,80509,0 -24998,51122113,Ácido nicotínico,"Niacina, Vitamina B3, Vitamina P",0,80509,0 -24999,51122200,Fármacos contra la hipotensión,,0,80509,0 -25000,51122201,Ornipresina,,0,80509,0 -25001,51131500,Medicamentos antianémicos,,0,80509,0 -25002,51131501,Fumarato ferroso,,0,80509,0 -25003,51131502,Gluconato ferroso,,0,80509,0 -25004,51131503,Sulfato ferroso,Sulfato de hierro (II),"0,1",80509,0 -25005,51131504,Pidolato de magnesio,,0,80509,0 -25006,51131505,Oximetolona,Anadrol,0,80509,0 -25007,51131506,Eritropoyetina,,0,80509,0 -25008,51131507,Darbepoetina alfa,,0,80509,0 -25009,51131508,Epoetina alfa,,0,80509,0 -25010,51131509,Citrato de amonio férrico,,0,80509,0 -25011,51131510,Polipéptido de hierro heme,Polipéptido hierro hemo,0,80509,0 -25012,51131511,Hierro polisacárido,Polisacárido de hierro,0,80509,0 -25013,51131512,Pirofosfato férrico,,0,80509,0 -25014,51131513,Cacodilato ferroso,,0,80509,0 -25015,51131514,Carbonilo de hierro,"Hierro pentacarbonilo, Pentacarbonilo de hierro",0,80509,0 -25016,51131515,Hemina o hematina,,0,80509,0 -25017,51131516,Hierro sacarosa,,0,80509,0 -25018,51131517,Ácido fólico,"Folacina, Vitamina B9, Ácido pteroil-L-glutámic",0,80509,0 -25019,51131518,Sulfato ferroso y ácido fólico,,0,80509,0 -25020,51131600,Anticoagulantes,,0,80509,0 -25021,51131601,Aprotinina,,0,80509,0 -25022,51131602,Heparina de calcio,,0,80509,0 -25023,51131603,Heparina sódica,,0,80509,0 -25024,51131604,Warfarina sódica,,0,80509,0 -25025,51131605,Citrato de sodio,,0,80509,0 -25026,51131606,Apolato de sodio,,0,80509,0 -25027,51131607,Enoxaparina sódica,,0,80509,0 -25028,51131608,Lepirudina,,0,80509,0 -25029,51131609,Desirudina,,0,80509,0 -25030,51131610,Ardeparina de sodio,,0,80509,0 -25031,51131611,Dalteparina sódica,,0,80509,0 -25032,51131612,Danaparoide sódico,,0,80509,0 -25033,51131613,Dicumarol,,0,80509,0 -25034,51131614,Anisindiona,,0,80509,0 -25035,51131615,Fondaparinux sódico,,0,80509,0 -25036,51131616,Tinzaparina sódica,,0,80509,0 -25037,51131617,Solución anticoagulante citrato fosfato dextrosa,,0,80509,0 -25038,51131700,Trombolíticos/inhibidores de agregación de plaquetas,,0,80509,0 -25039,51131701,Abciximab,,0,80509,0 -25040,51131702,Alteplasa,,0,80509,0 -25041,51131703,Estreptoquinasa,,0,80509,0 -25042,51131704,Uroquinasa,Activador del plasminógeno tipo uroquinasa,0,80509,0 -25043,51131705,Dipiridamol,Persantin,0,80509,0 -25044,51131706,Argatroban,,0,80509,0 -25045,51131707,Bivalirudina,,0,80509,0 -25046,51131708,Cilostazol,,0,80509,0 -25047,51131709,Bisulfato de clopidogrel,,0,80509,0 -25048,51131710,Eptifibatida,,0,80509,0 -25049,51131711,Tenecteplasa,,0,80509,0 -25050,51131712,Clorhidrato de ticlopidina,,0,80509,0 -25051,51131713,Monohidrato hidrocloruro tirofibán,Clorhidrato de tirofibán monohidratado,0,80509,0 -25052,51131714,Anistreplasa,,0,80509,0 -25053,51131715,Clorhidrato de anagrelida,,0,80509,0 -25054,51131716,Reteplasa,,0,80509,0 -25055,51131800,Agentes hemostáticos sistémicos y coagulantes,,0,80509,0 -25056,51131801,Fibrinógeno,,0,80509,0 -25057,51131802,Factores antihemofílicos o globulinas,,0,80509,0 -25058,51131803,Trombina,,0,80509,0 -25059,51131804,Oprelvekina,,0,80509,0 -25060,51131805,Etamsilato,,0,80509,0 -25061,51131806,Subsulfato férrico,,0,80509,0 -25062,51131807,Morruato de sodio,,0,80509,0 -25063,51131808,Ácido aminocaproico,,0,80509,0 -25064,51131809,Dobesilato de calcio,,0,80509,0 -25065,51131810,Parche hemostático,,0,80509,0 -25066,51131811,Ácido tranexámico,,0,80509,0 -25067,51131900,Sustitutos de plasma sanguíneo y extensores y expansores,,0,80509,0 -25068,51131901,Poligelina,,0,80509,0 -25069,51131903,Gelatina,,0,80509,0 -25070,51131904,Plasma sanguíneo humano,,0,80509,0 -25071,51131905,Dextrano,,0,80509,0 -25072,51131906,Pentastarch,,0,80509,0 -25073,51131907,Fracción de proteína de plasma humano,,0,80509,0 -25074,51131908,Hetastarch,,0,80509,0 -25075,51131909,Albúmina humana,,0,80509,0 -25076,51131910,Polividona o povidona,"Iodopovidona, Polividona yodada, Yodopolivinilpirrolidona",0,80509,0 -25077,51132000,Agentes hemorreológicos,,0,80509,0 -25078,51132001,Pentoxifilina,,0,80509,0 -25079,51141500,Anticonvulsivos,Antiepilépticos,0,80509,0 -25080,51141501,Acetazolamida,,0,80509,0 -25081,51141502,Clonazepam,,0,80509,0 -25082,51141503,Felbamato,,0,80509,0 -25083,51141504,Lamotrigina,,0,80509,0 -25084,51141505,Fenobarbital,Fenobarbitona,0,80509,0 -25085,51141506,Fenobarbital sódico,,0,80509,0 -25086,51141507,Fenitoína,Difenilhidantoína,0,80509,0 -25087,51141508,Etosuximida,,0,80509,0 -25088,51141509,Vigabatrina,,0,80509,0 -25089,51141510,Barbexaclona,,0,80509,0 -25090,51141511,Urbanyl,,0,80509,0 -25091,51141512,Clobazam,,0,80509,0 -25092,51141513,Carbamazepina,,0,80509,0 -25093,51141514,Zonisamida,,0,80509,0 -25094,51141515,Etotoína,,0,80509,0 -25095,51141516,Fosfenitoína sódica,,0,80509,0 -25096,51141517,Gabapentina,,0,80509,0 -25097,51141518,Levetiracetam,,0,80509,0 -25098,51141519,Mefenitoina,,0,80509,0 -25099,51141520,Mefobarbital,Mebaral,0,80509,0 -25100,51141521,Metsuximida,,0,80509,0 -25101,51141522,Oxcarbazepina,,0,80509,0 -25102,51141523,Parametadiona,,0,80509,0 -25103,51141524,Fenacemida,,0,80509,0 -25104,51141525,Fenitoína sódica,,0,80509,0 -25105,51141526,Primidona,,0,80509,0 -25106,51141527,Clorhidrato de tiagabina,,0,80509,0 -25107,51141528,Topiramato,,0,80509,0 -25108,51141529,Trimetadiona,,0,80509,0 -25109,51141530,Valproato de sodio,Valproato sódico,0,80509,0 -25110,51141531,Ácido valproico,Valproato de magnesio,0,80509,0 -25111,51141532,Acetazolamida sódica,,0,80509,0 -25112,51141533,Divalproex sódico,,0,80509,0 -25113,51141534,Pregabalina,,0,80509,0 -25114,51141535,Cabergolina,,0,80509,0 -25115,51141536,Citalopram,,0,80509,0 -25116,51141537,Clorazepato,,0,80509,0 -25117,51141538,Donepezilo,Aricep,0,80509,0 -25118,51141539,Duloxetina,,0,80509,0 -25119,51141541,Memantina,,0,80509,0 -25120,51141542,Midazolam,,0,80509,0 -25121,51141543,Ziprasidona,,0,80509,0 -25122,51141600,Antidepresivos,,0,80509,0 -25123,51141601,Amitriptilina,,0,80509,0 -25124,51141602,Clorhidrato de doxepina,,0,80509,0 -25125,51141603,Pamoato de imipramina,,0,80509,0 -25126,51141604,Mirtazapina,,0,80509,0 -25127,51141605,Hidrocloruro de paroxetina,,0,80509,0 -25128,51141606,Clorhidrato de trazodona,,0,80509,0 -25129,51141607,Maleato de fluvoxamina,,0,80509,0 -25130,51141608,Amitriptilinoxida,,0,80509,0 -25131,51141609,Sulfato de tranilcipromina,,0,80509,0 -25132,51141610,Clorhidrato de desipramina,,0,80509,0 -25133,51141611,Trimipramina mesilato,,0,80509,0 -25134,51141612,Sibutramina,Reductil,0,80509,0 -25135,51141613,Clorhidrato de carpipramine,,0,80509,0 -25136,51141614,Clomipramina,,0,80509,0 -25137,51141615,Trimipramina,,0,80509,0 -25138,51141616,Clorhidrato de clomipramina,,0,80509,0 -25139,51141617,Bromhidrato de citalopram,,0,80509,0 -25140,51141618,Clorhidrato de fluoxetina,Prozac,0,80509,0 -25141,51141619,Hidrocloruro de sertralina,,0,80509,0 -25142,51141620,Mosto de st john,"Corazoncillo, Hierba de San Juan, Hipericón, Hipérico ,",0,80509,0 -25143,51141621,Clorhidrato de imipramina,,0,80509,0 -25144,51141622,Isocarboxazida,Marplan,0,80509,0 -25145,51141623,Clorhidrato de caprotilina,Ludiomil,0,80509,0 -25146,51141624,Clorhidrato de nefazodona,,0,80509,0 -25147,51141625,Clorhidrato de nortriptilina,,0,80509,0 -25148,51141626,Clorhidrato de protriptilina,,0,80509,0 -25149,51141627,Maleato de trimipramina,,0,80509,0 -25150,51141628,Nortriptilina,,0,80509,0 -25151,51141629,Amoxapina,,0,80509,0 -25152,51141630,Tiroides,,0,80509,0 -25153,51141631,Hidrocloruro de bupropión,,0,80509,0 -25154,51141632,Sulfato de fenelzina,,0,80509,0 -25155,51141633,Oxalato de escitalopram,Escitalopram,0,80509,0 -25156,51141634,Aripiprazol,,0,80509,0 -25157,51141635,Clorhidrato de duloxetina,,0,80509,0 -25158,51141636,Venlafaxina hcl,,0,80509,0 -25159,51141637,Mirtazepina,,0,80509,0 -25160,51141638,Venlafaxina,,0,80509,0 -25161,51141639,Desvenlafaxina,O-desmetilvenlafaxina,0,80509,0 -25162,51141640,Moclobemida,Aurorix,0,80509,0 -25163,51141700,Agentes antipsicóticos,,0,80509,0 -25164,51141701,Fenotiazinas,"Dibenzotiazina, Tiodifenilamina",0,80509,0 -25165,51141702,Haloperidol,,0,80509,0 -25166,51141703,Olanzapina,,0,80509,0 -25167,51141704,Risperidona,Risperdal,0,80509,0 -25168,51141705,Nicergolina,Sermion,0,80509,0 -25169,51141706,Citicolina,"Ceraxon, Somazina",0,80509,0 -25170,51141707,Clorhidrato de buspirona,,0,80509,0 -25171,51141708,Clordiazepóxido,,0,80509,0 -25172,51141709,Flufenazina,,0,80509,0 -25173,51141710,Periciazina,,0,80509,0 -25174,51141711,Levomepromazina,,0,80509,0 -25175,51141712,Zotepina,,0,80509,0 -25176,51141713,Palmitato de pipotiazina,,0,80509,0 -25177,51141714,Piracetam,,0,80509,0 -25178,51141715,Clozapina,,0,80509,0 -25179,51141716,Decanoato de haloperidol,,0,80509,0 -25180,51141717,Lactato de haloperidol,,0,80509,0 -25181,51141718,Clorhidrato de loxapina,,0,80509,0 -25182,51141719,Succinato de loxapina,,0,80509,0 -25183,51141720,Clorhidrato de molindona,,0,80509,0 -25184,51141721,Pimozida,,0,80509,0 -25185,51141722,Fumarato de quetiapina,Quetidin,0,80509,0 -25186,51141723,Tiotixeno,,0,80509,0 -25187,51141724,Clorhidrato de tiotixeno,,0,80509,0 -25188,51141725,Clorhidrato de ziprasidona,,0,80509,0 -25189,51141726,Clorhidrato de triflupromazina,,0,80509,0 -25190,51141727,Mesilato de ziprasidona,,0,80509,0 -25191,51141728,Droperidol,Dehidrobenzolperidol,0,80509,0 -25192,51141729,Clorhidrato de tioridazina,,0,80509,0 -25193,51141730,Amisulprida,,0,80509,0 -25194,51141731,Modafinilo,,0,80509,0 -25195,51141732,Clorpromazina,,0,80509,0 -25196,51141800,Hipnóticos,,0,80509,0 -25197,51141801,Pentobarbital,,0,80509,0 -25198,51141802,Secobarbital sódico,,0,80509,0 -25199,51141803,Hidrato de cloral,,0,80509,0 -25200,51141804,Estazolam,,0,80509,0 -25201,51141805,Triazolam,,0,80509,0 -25202,51141806,Butobarbital,,0,80509,0 -25203,51141807,Flunitrazepam,Rofenol,0,80509,0 -25204,51141808,Tartrato de zolpidem,,0,80509,0 -25205,51141809,Loprazolam mesilato,,0,80509,0 -25206,51141810,Zoplicona,,0,80509,0 -25207,51141811,Amobarbital sódico,,0,80509,0 -25208,51141812,Didrocloruro de dexmedetomidina,,0,80509,0 -25209,51141813,Etclorvinol,Placidyl,0,80509,0 -25210,51141814,Clorhidrato de flurazepam,,0,80509,0 -25211,51141815,Glutetimida,Doriden,0,80509,0 -25212,51141816,Paraldehído,Acetaldehído,0,80509,0 -25213,51141817,Quazepam,,0,80509,0 -25214,51141818,Triptófano,,0,80509,0 -25215,51141819,Zaleplon,,0,80509,0 -25216,51141820,Pentobarbital sódico,,0,80509,0 -25217,51141821,Doxilamina succinato,,0,80509,0 -25218,51141822,Butabarbital,,0,80509,0 -25219,51141823,Eszopiclona,,0,80509,0 -25220,51141900,Tranquilizantes y fármacos ansiolíticos y antimaniacos,,0,80509,0 -25221,51141903,Carbonato de litio,Carbonato litítico,0,80509,0 -25222,51141904,Citrato de litio,,0,80509,0 -25223,51141907,Sulpirida,,0,80509,0 -25224,51141908,Tiaprida,,0,80509,0 -25225,51141910,Bromazepam,,0,80509,0 -25226,51141911,Tofisopam,,0,80509,0 -25227,51141912,Hidrocloruro de chlordiazepoxide,,0,80509,0 -25228,51141913,Clormezanone,,0,80509,0 -25229,51141914,Clorazapato dipotásico,,0,80509,0 -25230,51141915,Halazepam,,0,80509,0 -25231,51141916,Lorazepam,Acetato de L-lorazepam,0,80509,0 -25232,51141917,Oxazepam,,0,80509,0 -25233,51141918,Meprobamato,,0,80509,0 -25234,51141919,Alprazolam,"Niravam, Xanax",0,80509,0 -25235,51141920,Diazepam,Valium,0,80509,0 -25236,51141921,Clorhidrato de midazoloam,,0,80509,0 -25237,51141922,Temazepam,,0,80509,0 -25238,51142000,Analgésicos no narcóticos/antipiréticos,,0,80509,0 -25239,51142001,Acetaminofén,"Acetaminofeno, Paracetamol",0,80509,0 -25240,51142002,Ácido acetilsalicílico,Aspirina,0,80509,0 -25241,51142003,Mesalamina,,0,80509,0 -25242,51142004,Meprobromato,,0,80509,0 -25243,51142005,Butetisalicilato de metilo,,0,80509,0 -25244,51142006,Oxaceprol,,0,80509,0 -25245,51142009,Metamizol sódico,,0,80509,0 -25246,51142010,Acetanilida,"Acetamidobenceno, Acetilaminobenceno",0,80509,0 -25247,51142011,Salicilato de magnesio,,0,80509,0 -25248,51142012,Ácido mefenámico,,0,80509,0 -25249,51142013,Salicilamida,O-hidroxibenzamida,0,80509,0 -25250,51142014,Tiosalicilato de sodio,,0,80509,0 -25251,51142015,Salicilato de trietanolamina,,0,80509,0 -25252,51142016,Solución de antipirina y benzocaína,,0,80509,0 -25253,51142017,Salsalato o ácido salicil salicílico,,0,80509,0 -25254,51142018,Inyección de alcohol deshidratado,,0,80509,0 -25255,51142100,Fármacos antiinflamatorios no esteroideos (NSAID),,0,80509,0 -25256,51142101,Auranofin,,0,80509,0 -25257,51142102,Carpofen,Carprofeno,0,80509,0 -25258,51142103,Diclofenaco potásico,,0,80509,0 -25259,51142104,Diclofenaco sódico,,0,80509,0 -25260,51142105,Flurbiprofeno,,0,80509,0 -25261,51142106,Ibuprofeno,Brufen,0,80509,0 -25262,51142107,Indometacina,,0,80509,0 -25263,51142108,Ketoprofeno,,0,80509,0 -25264,51142109,Naproxeno,,0,80509,0 -25265,51142110,Naproxeno sódico,,0,80509,0 -25266,51142111,Oxaprozin,,0,80509,0 -25267,51142112,Sulindaco,Sulindac,0,80509,0 -25268,51142113,Suprofeno,,0,80509,0 -25269,51142114,Clorhidrato de bencidamina,,0,80509,0 -25270,51142116,Salicilato de dietilamina,,0,80509,0 -25271,51142117,Dietilamina diclofenaco,,0,80509,0 -25272,51142118,Nimesulida,,0,80509,0 -25273,51142119,Benorilato,,0,80509,0 -25274,51142120,Serrapeptasa,,0,80509,0 -25275,51142121,Diclofenaco,Voltaren,0,80509,0 -25276,51142122,Auriotomalato de sodio,,0,80509,0 -25277,51142123,Ketorolaco trometamol,,0,80509,0 -25278,51142124,Etodolac,Etodolaco,0,80509,0 -25279,51142125,Etofenamato,,0,80509,0 -25280,51142126,Floctafenina,,0,80509,0 -25281,51142127,Emorfazona,,0,80509,0 -25282,51142128,Piroxicam,,0,80509,0 -25283,51142129,Ácido tiaprofénico,,0,80509,0 -25284,51142130,Leflunomida,,0,80509,0 -25285,51142131,Celecoxib,Celebrex,0,80509,0 -25286,51142132,Aurotioglucosa,,0,80509,0 -25287,51142133,Bromfenac sódico,,0,80509,0 -25288,51142134,Salicilato de colina,,0,80509,0 -25289,51142135,Fenoprofen cálcico,,0,80509,0 -25290,51142136,Flurbiprofen sódico,,0,80509,0 -25291,51142137,Indometacina trihidrato sódica,,0,80509,0 -25292,51142138,Ketorolac trometamina,,0,80509,0 -25293,51142139,Meclofenamato sódico,,0,80509,0 -25294,51142140,Meloxicam,Mobic,0,80509,0 -25295,51142141,Nabumetona,,0,80509,0 -25296,51142142,Rofecoxib,,0,80509,0 -25297,51142143,Tolmetin sódico,,0,80509,0 -25298,51142144,Valdecoxib,,0,80509,0 -25299,51142145,Adalimumab,,0,80509,0 -25300,51142146,Diflunisal,,0,80509,0 -25301,51142147,Anakinra,Kineret,0,80509,0 -25302,51142148,Hialuronato de sodio,,0,80509,0 -25303,51142149,Glucosamina,,0,80509,0 -25304,51142150,Acamprosato calcio,Acetil homotaurinato de calcio,0,80509,0 -25305,51142151,Aceclofenaco,,0,80509,0 -25306,51142152,Deracoxib,,0,80509,0 -25307,51142153,Etoricoxib,Arcoxia,0,80509,0 -25308,51142154,Lumiracoxib,,0,80509,0 -25309,51142155,Clonixinato lisina,,0,80509,0 -25310,51142200,Analgésicos narcóticos,,0,80509,0 -25311,51142201,Butorfanol tartrato,,0,80509,0 -25312,51142202,Fosfato codeina,,0,80509,0 -25313,51142203,Sulfato codeina,,0,80509,0 -25314,51142205,Clorhidrato meperidina,,0,80509,0 -25315,51142206,Sulfato de morfina,Roxanol,0,80509,0 -25316,51142207,Oxicodona,,0,80509,0 -25317,51142208,Citrato sufentanilo,,0,80509,0 -25318,51142209,Clorhidrato nefopam,,0,80509,0 -25319,51142210,Clorhidrato etilmorfina,,0,80509,0 -25320,51142211,Hidrocodona,,0,80509,0 -25321,51142212,Dihidrocodeina resinato,,0,80509,0 -25322,51142213,Napsilato de propoxifeno,,0,80509,0 -25323,51142214,Clorhidrato de alfentanil,,0,80509,0 -25324,51142215,Hidrocloruro de buprenorfina,,0,80509,0 -25325,51142216,Dezocina,,0,80509,0 -25326,51142217,Dihidrocodéina tartrato,,0,80509,0 -25327,51142218,Dihidrocodéina,,0,80509,0 -25328,51142219,Fentanilo,,0,80509,0 -25329,51142220,Citrato de fentanilo,,0,80509,0 -25330,51142221,Bitartrato de hidrocodona,,0,80509,0 -25331,51142222,Hidrocloruro de hidromorfona,,0,80509,0 -25332,51142223,Polistirex de hidrocodona,,0,80509,0 -25333,51142224,Clorhidrato de acetato de levometadil,,0,80509,0 -25334,51142225,Tartrato de levorfanol,,0,80509,0 -25335,51142226,Clorhidrato de metadona,,0,80509,0 -25336,51142227,Clorhidrato de oxicodona,,0,80509,0 -25337,51142228,Clorhidrato de oximorfona,,0,80509,0 -25338,51142229,Clorhidrato de pentazocina,,0,80509,0 -25339,51142230,Lactato de pentazocina,,0,80509,0 -25340,51142231,Clorhidrato de propoxifeno,,0,80509,0 -25341,51142232,Clorhidrato de remifentanilo,,0,80509,0 -25342,51142233,Codeína,Metilmorfina,0,80509,0 -25343,51142234,Bitartrato de dihidrocodeína,,0,80509,0 -25344,51142235,Clorhidrato de tramadol,,0,80509,0 -25345,51142237,Dextropropoxifeno,,0,80509,0 -25346,51142300,Antagonistas narcóticos,,0,80509,0 -25347,51142301,Clorhidrato de nalmefeno,,0,80509,0 -25348,51142302,Hidrocloruro de naloxona,,0,80509,0 -25349,51142303,Naltrexona,Naltraxona,0,80509,0 -25350,51142304,Clorhidrato de nalbufina,,0,80509,0 -25351,51142305,Roxatidina,,0,80509,0 -25352,51142306,Clorhidrato de atipamezol,,0,80509,0 -25353,51142400,Medicamentos para dolores de cabeza vasculares y migraña,,0,80509,0 -25354,51142401,Maleato de metisergida,,0,80509,0 -25355,51142402,Succinato de sumatriptán,,0,80509,0 -25356,51142403,Tartrato de ergotamina,,0,80509,0 -25357,51142404,Dihidroergotamina,,0,80509,0 -25358,51142405,Combinación de ácido acetilsalicílico paracetamol,,0,80509,0 -25359,51142406,Almotriptán malato,,0,80509,0 -25360,51142407,Mesilato de dihidroergotamina,,0,80509,0 -25361,51142408,Succinato de frovatriptan,,0,80509,0 -25362,51142409,Isometepteno,,0,80509,0 -25363,51142410,Naratriptán hidrocloruro,,0,80509,0 -25364,51142411,Benzoato de rizatriptán,,0,80509,0 -25365,51142412,Bromhidrato de eletriptan,,0,80509,0 -25366,51142413,Zolmitriptán,,0,80509,0 -25367,51142414,Sumatriptán,,0,80509,0 -25368,51142415,Combinación de acetaminofén butalbital,,0,80509,0 -25369,51142500,Medicamentos contra el Parkinson,,0,80509,0 -25370,51142501,Mesilato de bromocriptina,,0,80509,0 -25371,51142502,Carbidopa,,0,80509,0 -25372,51142503,Levodopa,,0,80509,0 -25373,51142504,Clorhidrato de selegilina,,0,80509,0 -25374,51142505,Biperideno,,0,80509,0 -25375,51142506,Clorhidrato de biperideno,,0,80509,0 -25376,51142507,Mesilato de pergolida,,0,80509,0 -25377,51142508,Dihidrocloruro de pramipexol,,0,80509,0 -25378,51142509,Clorhidrato de ropinirol,,0,80509,0 -25379,51142510,Tolcapone,,0,80509,0 -25380,51142511,Entacapona,,0,80509,0 -25381,51142512,Tropacine,,0,80509,0 -25382,51142513,Rasagilina,Azilect,0,80509,0 -25383,51142514,Levodopa y carbidopa,,0,80509,0 -25384,51142600,Estimulantes/anoréxicos,,0,80509,0 -25385,51142601,Sulfato de anfetamina,Anfetamina,0,80509,0 -25386,51142602,Fenfluramina,,0,80509,0 -25387,51142603,Mazindol,,0,80509,0 -25388,51142604,Pemolina,,0,80509,0 -25389,51142605,Tartrato de fendimetrazina,,0,80509,0 -25390,51142606,Clorhidrato de pipradol,,0,80509,0 -25391,51142607,Fenproporex,,0,80509,0 -25392,51142608,Trimetilxantina,,0,80509,0 -25393,51142609,Clorhidrato de anfepramona,Dietilpropion,0,80509,0 -25394,51142610,Cafeína,"Guaranina, Mateína, Teín",0,80509,0 -25395,51142611,Clorhidrato de dexfenfluramina,,0,80509,0 -25396,51142612,Clorhidrato de dietilpropión,,0,80509,0 -25397,51142613,Clorhidrato de fenfluramina,,0,80509,0 -25398,51142614,Clorhidrato de benzfetamina,,0,80509,0 -25399,51142615,Clorhidrato de fentermina,,0,80509,0 -25400,51142616,Fentermina de resina,,0,80509,0 -25401,51142617,Clorhidrato de sibutramina monohidratada,,0,80509,0 -25402,51142618,Clorhidrato de metilfenidato,,0,80509,0 -25403,51142619,Espíritu aromático de amoníaco,,0,80509,0 -25404,51142700,Fármacos antiinflamatorios no esteroideos veterinarios (VNSAID),,0,80509,0 -25405,51142701,Fenilbutazona,,0,80509,0 -25406,51142702,Ácido meclofenámico,,0,80509,0 -25407,51142800,Fármacos de tratamiento de la esclerosis lateral amiotrófica (ELA),,0,80509,0 -25408,51142801,Riluzol,,0,80509,0 -25409,51142900,"Fármacos anestésicos, complementos relacionados y analépticos",,0,80509,0 -25410,51142901,Cloroformo,"Triclorometano, Tricloruro de metilo",1,80509,0 -25411,51142902,Mezcla eutéctica de anestésicos locales,,0,80509,0 -25412,51142903,Ropivacaína,,0,80509,0 -25413,51142904,Lidocaína,Xilocaína,0,80509,0 -25414,51142905,Bupivacaína,,0,80509,0 -25415,51142906,Clorhidrato de ametocaína,,0,80509,0 -25416,51142907,Oxibuprocaína,,0,80509,0 -25417,51142908,Lignocaina,,0,80509,0 -25418,51142909,Benzocaína,4-aminobenzoato,0,80509,0 -25419,51142910,Prilocaina,,0,80509,0 -25420,51142911,Clorhidrato de quinisocaina,,0,80509,0 -25421,51142912,Dibucaina,,0,80509,0 -25422,51142913,Halotano,,0,80509,0 -25423,51142914,Clorhidrato de amilocaína,,0,80509,0 -25424,51142915,Septocaina,,0,80509,0 -25425,51142916,Hialuronidasa,,0,80509,0 -25426,51142917,Mepivacaína,,0,80509,0 -25427,51142918,Articaína,,0,80509,0 -25428,51142919,Isoflurano,,0,80509,0 -25429,51142920,Procaina,Novocaina,0,80509,0 -25430,51142921,Tiopental sódico,,0,80509,0 -25431,51142922,Clorhidrato de benoxinato,,0,80509,0 -25432,51142923,Butambeno,,0,80509,0 -25433,51142924,Clorobutanol,,0,80509,0 -25434,51142925,Clorhidrato de cloroprocaína,,0,80509,0 -25435,51142927,Desflurano,,0,80509,0 -25436,51142928,Clorhidrato de diclonina,,0,80509,0 -25437,51142929,Enflurano,,0,80509,0 -25438,51142930,Éter,,1,80509,0 -25439,51142931,Cloruro de etilo,"Cloroetano, Monocloroetano",1,80509,0 -25440,51142932,Clorhidrato de etidocaína,,0,80509,0 -25441,51142933,Etomidato,,0,80509,0 -25442,51142934,Clorhidrato de ketamina,,0,80509,0 -25443,51142935,Levobupivacaína hidrocloruro,,0,80509,0 -25444,51142936,Levonordefrina,,0,80509,0 -25445,51142937,Clorhidrato de lidocaína,,0,80509,0 -25446,51142938,Metohexital sódico,,0,80509,0 -25447,51142939,Clorhidrato de pramoxina,,0,80509,0 -25448,51142940,Clorhidrato de proparacaína,,0,80509,0 -25449,51142941,Propofol,,0,80509,0 -25450,51142942,Sevoflurano,,0,80509,0 -25451,51142943,Tetracaína,,0,80509,0 -25452,51142944,Clorhidrato de tetracaina,,0,80509,0 -25453,51142945,Clorhidrato de doxapram,Dopram,0,80509,0 -25454,51142946,Metoxiflurano,,0,80509,0 -25455,51142947,Clorhidrato de cincocaína,,0,80509,0 -25456,51142948,Diclorotetrafluoroetano,,0,80509,0 -25457,51151500,Colinérgicos y inhibidores de la colinesterasa,Agentes para el sistema nervioso parasimpático,0,80509,0 -25458,51151501,Cloruro de betanecol,,0,80509,0 -25459,51151502,Cloruro de edrofonio,Tensilon,0,80509,0 -25460,51151503,Fisostigmina,,0,80509,0 -25461,51151504,Nitrato de pilocarpina,,0,80509,0 -25462,51151505,Clorhidrato de tropatepina,,0,80509,0 -25463,51151506,Inhibidor de la esterasa,,0,80509,0 -25464,51151507,Cloruro de ambenonio,,0,80509,0 -25465,51151508,Dexpantenol,Pro-vitamina B5,0,80509,0 -25466,51151509,Donepezilo clorhidrato,,0,80509,0 -25467,51151510,Bromhidrato de galantamina,,0,80509,0 -25468,51151511,Bromuro de mepenzolato,,0,80509,0 -25469,51151512,Metilsulfato de neostigmina,,0,80509,0 -25470,51151513,Bromuro de neostigmina,,0,80509,0 -25471,51151514,Bromuro de piridostigmina,,0,80509,0 -25472,51151515,Tartrato de rivastigmina,,0,80509,0 -25473,51151516,Clorhidrato de tacrina,,0,80509,0 -25474,51151517,Clorhidrato de cevimelina,Evoxac,0,80509,0 -25475,51151518,Lecitina o fosfatidilcolina,,0,80509,0 -25476,51151600,Agentes bloqueadores colinérgicos,Agentes bloqueadores del sistema nervioso parasimpático,0,80509,0 -25477,51151601,Sulfato de atropina,,0,80509,0 -25478,51151602,Mesilato de benztropina,Mesilato de benzotropina,0,80509,0 -25479,51151603,Clorhidrato de prociclidina,,0,80509,0 -25480,51151604,Clorhidrato de trihexifenidilo,,0,80509,0 -25481,51151605,Clorhidrato de ciclopentolato,,0,80509,0 -25482,51151606,Tropicamida,,0,80509,0 -25483,51151607,Bromuro de clidinio,,0,80509,0 -25484,51151608,Clorhidrato de diciclomina,,0,80509,0 -25485,51151609,Bromuro de propantelina,,0,80509,0 -25486,51151610,Alcaloides de belladona,,0,80509,0 -25487,51151611,Glicopirrolato,,0,80509,0 -25488,51151612,Sulfato de hiosciamina,,0,80509,0 -25489,51151613,Bromhidrato de escopolamina,,0,80509,0 -25490,51151614,Homatropina bromhidrato,,0,80509,0 -25491,51151615,Caramifeno,,0,80509,0 -25492,51151616,Atropina,,0,80509,0 -25493,51151700,Simpatomiméticos (adrenérgicos),,0,80509,0 -25494,51151701,Albuterol,Salbutamol,0,80509,0 -25495,51151702,Tartrato de brimonidina,,0,80509,0 -25496,51151703,Epinefrina,Adrenalina,0,80509,0 -25497,51151704,Borato de epinefrina,,0,80509,0 -25498,51151705,Clorhidrato de epinefrina,,0,80509,0 -25499,51151706,Isoproterenol,Isoprenalina,0,80509,0 -25500,51151707,Sulfato de isoproterenol,,0,80509,0 -25501,51151708,Bitartrato de levarterenol,,0,80509,0 -25502,51151709,Fenilpropanolamina clorhidrato,,0,80509,0 -25503,51151710,Clorhidrato de fenilefrina,,0,80509,0 -25504,51151711,Sulfato de terbutalina,,0,80509,0 -25505,51151712,Clorhidrato de arbutamina,,0,80509,0 -25506,51151713,Clorhidrato de cinamedrina,,0,80509,0 -25507,51151714,Sulfato de d-anfetamina,,0,80509,0 -25508,51151715,Sulfato de efedrina,,0,80509,0 -25509,51151716,Clorhidrato de isoproterenol,,0,80509,0 -25510,51151717,Sulfato de mefentermina,,0,80509,0 -25511,51151718,Bitartrato de metaraminol,,0,80509,0 -25512,51151719,Pseudoefedrina,Seudoefedrina,0,80509,0 -25513,51151720,Xinafoato de salmeterol,,0,80509,0 -25514,51151721,Clorhidrato de midodrina,,0,80509,0 -25515,51151722,Clorhidrato de levalbuterol,,0,80509,0 -25516,51151723,Clorhidrato de tetrahidrozolina,,0,80509,0 -25517,51151724,Clorhidrato de nafazolina,,0,80509,0 -25518,51151725,Dipivefrina,,0,80509,0 -25519,51151726,Fenilpropanolamina,Fenilpropalamina,0,80509,0 -25520,51151727,Norepinefrina bitartrato,,0,80509,0 -25521,51151728,Bromhidrato hidroxianfetamina,,0,80509,0 -25522,51151730,Epinefrina bitartrato,Adrenalina tartrato,0,80509,0 -25523,51151731,Sulfato de metaproterenol,,0,80509,0 -25524,51151732,Clorhidrato de dobutamina,,0,80509,0 -25525,51151733,Clorhidrato de dipivefrina,,0,80509,0 -25526,51151734,Sulfato de albuterol,,0,80509,0 -25527,51151735,Aspartato de anfetamina,,0,80509,0 -25528,51151736,Mesilato de isoetarina,,0,80509,0 -25529,51151737,Clorhidrato de dopamina,,0,80509,0 -25530,51151738,Fumarato de formoterol,,0,80509,0 -25531,51151739,Clorhidrato de metoxamina,,0,80509,0 -25532,51151740,Clorhidrato de isoetarina,,0,80509,0 -25533,51151741,Efedrina,,0,80509,0 -25534,51151742,Fenilefrina,"Metaoxedrina, Neooxedrina",0,80509,0 -25535,51151743,Clorhidrato de pseudoefedrina,,0,80509,0 -25536,51151744,Hidrocloruro de efedrina,,0,80509,0 -25537,51151745,Tanato de pseudoefedrina,,0,80509,0 -25538,51151746,Sulfato de pseudoefedrina,,0,80509,0 -25539,51151747,Hidrocloruro de racepinephrine,,0,80509,0 -25540,51151748,Nafazolina,,0,80509,0 -25541,51151749,Dextroanfetamina,Dexedrine,0,80509,0 -25542,51151750,Arformoterol,,0,80509,0 -25543,51151800,Agentes bloqueadores adrenérgicos,,0,80509,0 -25544,51151801,Atenolol,,0,80509,0 -25545,51151802,Hidrocloruro de esmolol,,0,80509,0 -25546,51151803,Nadolol,,0,80509,0 -25547,51151804,Pindolol,,0,80509,0 -25548,51151805,Maleato de timolol,,0,80509,0 -25549,51151810,Mesilato de fentolamina,,0,80509,0 -25550,51151811,Hidrocloruro de yohimbina,,0,80509,0 -25551,51151812,Hidrocloruro de propranolol,,0,80509,0 -25552,51151813,Acebutolol,,0,80509,0 -25553,51151814,Hidrocloruro de betaxolol,,0,80509,0 -25554,51151815,Hidrocloruro de tolazolina,,0,80509,0 -25555,51151816,Hidrocloruro de prazosín,,0,80509,0 -25556,51151817,Hidrocloruro de tamsulosina,,0,80509,0 -25557,51151818,Hidrocloruro de carteolol,,0,80509,0 -25558,51151819,Hidrocloruro de terazosina,,0,80509,0 -25559,51151820,Hidrocloruro de fenoxibenzamina,,0,80509,0 -25560,51151821,Sulfato de penbutolol,,0,80509,0 -25561,51151822,Succinato de metoprolol,Metoprolol,0,80509,0 -25562,51151823,Hidrocloruro de labetalol,,0,80509,0 -25563,51151824,Mesilato de doxazosina,,0,80509,0 -25564,51151825,Hidrocloruro de dapiprazol,,0,80509,0 -25565,51151900,Relajantes músculo-esqueléticos de acción centrada,,0,80509,0 -25566,51151901,Baclofeno,Lioresal,0,80509,0 -25567,51151902,Clorzoxazona,,0,80509,0 -25568,51151903,Dantroleno sódico,,0,80509,0 -25569,51151904,Metocarbamol,Robaxin,0,80509,0 -25570,51151905,Carisoprodol,,0,80509,0 -25571,51151907,Clorhidrato de ritodrina,,0,80509,0 -25572,51151908,Citrato de orfenadrina,,0,80509,0 -25573,51151910,Nonivamida,,0,80509,0 -25574,51151911,Suxametonio,Vanillilamida de ácido pelargónico,0,80509,0 -25575,51151912,Tiocolchicósido,Succinilcolina,0,80509,0 -25576,51151913,Clorfenesina carbamato,,0,80509,0 -25577,51151914,Clorhidrato de ciclobenzaprina,,0,80509,0 -25578,51151915,Metaxalona,,0,80509,0 -25579,51151916,Cloruro de succinilcolina,,0,80509,0 -25580,51151917,Clorhidrato de tizanidina,Cloruro de suxametonio,0,80509,0 -25581,51151918,Alfuzosina,,0,80509,0 -25582,51151919,Hci cropiverina,,0,80509,0 -25583,51151920,Tolterodina,,0,80509,0 -25584,51152000,Agentes bloqueadores neuromusculares,,0,80509,0 -25585,51152001,Besilato de atracurio,,0,80509,0 -25586,51152002,Cloruro de mivacurio,,0,80509,0 -25587,51152003,Bromuro de rocuronio,,0,80509,0 -25588,51152004,Bromuro de vecuronio,,0,80509,0 -25589,51152005,Toxina botulínica,,0,80509,0 -25590,51152006,Cisatracurio,,0,80509,0 -25591,51152007,Cloruro de doxacurio,,0,80509,0 -25592,51152008,Yoduro de metocurina,,0,80509,0 -25593,51152009,Bromuro de pancuronio,,0,80509,0 -25594,51152010,Bromuro de rapacuronio,,0,80509,0 -25595,51152011,Cloruro de tubocurarina,,0,80509,0 -25596,51152012,Bromuro de pipecuronio,,0,80509,0 -25597,51161500,Antiasmáticos,,0,80509,0 -25598,51161501,Mesilato de bitolterol,,0,80509,0 -25599,51161502,Nedocromil sódico,,0,80509,0 -25600,51161503,Acetato de pirbuterol,,0,80509,0 -25601,51161504,Aminofilina,,0,80509,0 -25602,51161505,Teofilina,Teofilina,0,80509,0 -25603,51161506,Terbutalina,,0,80509,0 -25604,51161507,Formoterol,,0,80509,0 -25605,51161508,Sulfato de salbutamol,,0,80509,0 -25606,51161510,Teofilina,Etofilina,0,80509,0 -25607,51161511,Cromoglicato de sodio,Cromoglicato de sodio,0,80509,0 -25608,51161513,Difilina,Diprofilina,0,80509,0 -25609,51161514,Oxtrifilina,,0,80509,0 -25610,51161515,Montelukast sódico,,0,80509,0 -25611,51161516,Zafirlukast,,0,80509,0 -25612,51161517,Zileutón,,0,80509,0 -25613,51161518,Hidrato de pranlukast,,0,80509,0 -25614,51161519,Carmoterol,,0,80509,0 -25615,51161520,Ciclesonida,,0,80509,0 -25616,51161521,Cilomilast,,0,80509,0 -25617,51161522,Tulobuterol,,0,80509,0 -25618,51161523,Clorhidrato de reproterol,,0,80509,0 -25619,51161524,Bromuro de tiotropio,,0,80509,0 -25620,51161525,Beclometasona,,0,80509,0 -25621,51161600,Antihistamínicos (bloqueadores H1),Antialérgicos (bloqueadores H1),0,80509,0 -25622,51161601,Astemizol,,0,80509,0 -25623,51161602,Fumarato de clemastina,,0,80509,0 -25624,51161603,Maleato de dexclorfeniramina,,0,80509,0 -25625,51161605,Clorhidrato de levocabastina,,0,80509,0 -25626,51161606,Loratadina,Civeran,0,80509,0 -25627,51161607,Terfenadina,,0,80509,0 -25628,51161608,Clorhidrato de betahistina,,0,80509,0 -25629,51161609,Hidrocloruro de triprolidina,,0,80509,0 -25630,51161610,Prometazina,,0,80509,0 -25631,51161611,Oxomemazina,,0,80509,0 -25632,51161612,Ebastina,,0,80509,0 -25633,51161613,Mesilato de dihidroergotoxina,,0,80509,0 -25634,51161614,Clorhidrato de meclozina,,0,80509,0 -25635,51161615,Cetirizina,Alercet,0,80509,0 -25636,51161616,Betahistina,Betaserc,0,80509,0 -25637,51161617,Clorhidrato de buclicina,,0,80509,0 -25638,51161618,Alimemazina,,0,80509,0 -25639,51161619,Mepiramina maleato,,0,80509,0 -25640,51161620,Difenhidramina,Dimedrol,0,80509,0 -25641,51161621,Clorhidrato de fexofenadina,,0,80509,0 -25642,51161622,Cromoglicato de sodio,Cromolín sódico,0,80509,0 -25643,51161623,Hidroxizina pamoato,,0,80509,0 -25644,51161624,Maleato de azatadina,,0,80509,0 -25645,51161625,Azelastina hidrocloruro,,0,80509,0 -25646,51161626,Bosentán,,0,80509,0 -25647,51161627,Maleato de bromfeniramina,,0,80509,0 -25648,51161628,Maleato de carbinoxamina,,0,80509,0 -25649,51161629,Hidrocloruro de cetirizina,,0,80509,0 -25650,51161630,Maleato de clorfenamina,Clorfeniramina,0,80509,0 -25651,51161631,Tanato de clorfenamina,,0,80509,0 -25652,51161632,Clorhidrato de ciproheptadina,,0,80509,0 -25653,51161633,Desloratadina,,0,80509,0 -25654,51161634,Maleato de dexbromfeniramina,,0,80509,0 -25655,51161635,Clorhidrato de difenhidramina,,0,80509,0 -25656,51161636,Tanato de difenhidramina,,0,80509,0 -25657,51161637,Clorhidrato de hidroxizina,,0,80509,0 -25658,51161638,Fumarato de ketotifeno,,0,80509,0 -25659,51161639,Clorhidrato de olopatadina,Olopatadina,0,80509,0 -25660,51161640,Tartrato de fenindamina,,0,80509,0 -25661,51161646,Acrivastina,,0,80509,0 -25662,51161647,Bromfeniramina,,0,80509,0 -25663,51161648,Difumarato de emedastina,,0,80509,0 -25664,51161649,Lodoxamida trometamina,,0,80509,0 -25665,51161650,Clorhidrato de prometazina,,0,80509,0 -25666,51161651,Potasio de pemirolast,,0,80509,0 -25667,51161652,Mizolastina,,0,80509,0 -25668,51161653,Clorhidrato de epinastina,,0,80509,0 -25669,51161654,Levocetirizina dihidrocloruro,,0,80509,0 -25670,51161655,Levocetirizina hemihidrato,,0,80509,0 -25671,51161700,Medicamentos para alteraciones del tracto respiratorio,,0,80509,0 -25672,51161701,Acetilcisteína,,0,80509,0 -25673,51161702,Beractant,,0,80509,0 -25674,51161703,Budesonida,,0,80509,0 -25675,51161704,Palmitato de colfoscerilo,,0,80509,0 -25676,51161705,Bromuro de ipratropio,,0,80509,0 -25677,51161706,Sobrerol,,0,80509,0 -25678,51161707,Clorhidrato de bamifilina,,0,80509,0 -25679,51161708,Dornasa alfa,,0,80509,0 -25680,51161709,Poractant alfa,,0,80509,0 -25681,51161710,Calfactant,,0,80509,0 -25682,51161800,"Productos para tos, resfriados y antialérgicos",,0,80509,0 -25683,51161801,Benzonatato,,0,80509,0 -25684,51161802,Guaifenesina,,0,80509,0 -25685,51161803,Mentol,,0,80509,0 -25686,51161805,Carbocisteína,S-carboximetilcisteína,0,80509,0 -25687,51161806,Cloruro de amonio,"Clorhidrato amónico, Sal de amoniaco","0,1",80509,0 -25688,51161808,Dextrometorfano,,0,80509,0 -25689,51161809,Citrato de oxolamina,,0,80509,0 -25690,51161810,Folcodina,,0,80509,0 -25691,51161811,Bromhexina,Ambroxol,0,80509,0 -25692,51161812,Combinación de acetaminofen y clorfeniramina,,0,80509,0 -25693,51161813,Guayacolsulfonato de potasio,,0,80509,0 -25694,51161814,Bitartrato de fenilpropanolamina,,0,80509,0 -25695,51161815,Tanato de carbetapentano,,0,80509,0 -25696,51161817,Clorhidrato de dextrometorfano,,0,80509,0 -25697,51161818,Dextrometorfano polistirex,,0,80509,0 -25698,51161819,Tanato de dextrometorfano,,0,80509,0 -25699,51161820,Aceite de eucalipto o eucaliptol,,0,80509,0 -25700,51161900,Descongestionantes nasales,,0,80509,0 -25701,51161901,Clorhidrato de oximetazolina,Oximetazolina,0,80509,0 -25702,51161903,Clorhidrato de xilometazolina,,0,80509,0 -25703,51171500,Antiácidos y antiflatulentos,,0,80509,0 -25704,51171501,Carbonato de calcio,,0,80509,0 -25705,51171502,Magaldrato,,0,80509,0 -25706,51171503,Hidróxido de magnesio,,0,80509,0 -25707,51171504,Antiácidos de bicarbonato de sodio,,0,80509,0 -25708,51171505,Simeticona,,0,80509,0 -25709,51171507,Hidrotalcita,,0,80509,0 -25710,51171508,Carbonato de magnesio,,0,80509,0 -25711,51171509,Clorhidrato de betaína,Hidrocloruro de betaína,0,80509,0 -25712,51171510,Trisilicato de magnesio,,0,80509,0 -25713,51171511,Hidróxido de aluminio,,0,80509,0 -25714,51171513,Dihidroxialuminio-carbonato de sodio,,"0,1",80509,0 -25715,51171600,Laxantes,,0,80509,0 -25716,51171601,Cáscara sagrada,,0,80509,0 -25717,51171602,Docusato de calcio,,0,80509,0 -25718,51171603,Docusato potásico,,0,80509,0 -25719,51171604,Docusato de sodio,,0,80509,0 -25720,51171605,Lactulosa,,0,80509,0 -25721,51171606,Sulfato de magnesio,Sulfato magnésico,0,80509,0 -25722,51171607,Mucílago hidrófilo del psilio,,0,80509,0 -25723,51171608,Glicerina,Glicerol,0,80509,0 -25724,51171609,Ácido dehidrocólico,,0,80509,0 -25725,51171610,Sen o senósidos,,0,80509,0 -25726,51171611,Estimulante bisacodilo,,0,80509,0 -25727,51171612,Metilcelulosa,,0,80509,0 -25728,51171613,Aloína,,0,80509,0 -25729,51171614,Bisacodyl,,0,80509,0 -25730,51171615,Calcio policarbofilo,,0,80509,0 -25731,51171616,Casantranol,,0,80509,0 -25732,51171617,Aceite de ricino,Aceite de castor,0,80509,0 -25733,51171618,Monohidrato de cisaprida,,0,80509,0 -25734,51171619,Supositorios de glicerina,,0,80509,0 -25735,51171620,Citrato de magnesio,,0,80509,0 -25736,51171621,Clorhidrato de metoclopramida,,0,80509,0 -25737,51171622,Fosfato de sodio,,0,80509,0 -25738,51171623,Fenolftaleína,,0,80509,0 -25739,51171624,Semilla de psyllium,,0,80509,0 -25740,51171626,Bitartrato de potasio,Cremor tártaro,0,80509,0 -25741,51171627,Tartrato de sodio y potasio,Sal de Seignette,0,80509,0 -25742,51171628,Fosfato de potasio,,0,80509,0 -25743,51171629,Sulfato de sodio,Tetraoxidosulfato de disodio,"0,1",80509,0 -25744,51171630,Aceite mineral,,0,80509,0 -25745,51171631,Polietilenglicol laxante,,0,80509,0 -25746,51171632,Cloruro de magnesio,Dicloruro de magnesio,"0,1",80509,0 -25747,51171633,Lubiprostona,,0,80509,0 -25748,51171700,Antidiarréicos,,0,80509,0 -25749,51171701,Clorhidrato de difenoxina,,0,80509,0 -25750,51171702,Hidroclorato de loperamida,,0,80509,0 -25751,51171703,Paregorico,,0,80509,0 -25752,51171704,Nifuroxazida,,0,80509,0 -25753,51171706,Atapulgita,Palygorskita,0,80509,0 -25754,51171707,Subsalicilato de bismuto,,0,80509,0 -25755,51171708,Clorhidrato de difenoxilato,,0,80509,0 -25756,51171709,Saccharomyces boulardii,,0,80509,0 -25757,51171711,Caolín y pectina,,0,80509,0 -25758,51171712,Pectina purificada con acidophilus,,0,80509,0 -25759,51171800,"Agentes antivértigo, antinauseantes y antieméticos",,0,80509,0 -25760,51171801,Dronabinol,Delta-9-tetrahidrocannabinol,0,80509,0 -25761,51171802,Clorhidrato de granisetrón,,0,80509,0 -25762,51171803,Clorhidrato de meclizina,,0,80509,0 -25763,51171804,Clorhidrato de ondansetrón,,0,80509,0 -25764,51171805,Clorhidrato de trimetobenzamida,,0,80509,0 -25765,51171806,Metoclopramida,Metoclol,0,80509,0 -25766,51171807,Cinarizina,,0,80509,0 -25767,51171808,Clorhidrato de difenidol,Difenidol,0,80509,0 -25768,51171809,Ciclizina,,0,80509,0 -25769,51171811,Combinacion de dextrosa fructosa y ácido fosfórico,,0,80509,0 -25770,51171812,Proclorperazina,,0,80509,0 -25771,51171813,Metopimazina,,0,80509,0 -25772,51171814,Mesilato de dolasetrón,Anzemet,0,80509,0 -25773,51171815,Clorhidrato de ciclizina,,0,80509,0 -25774,51171816,Ondansetrón,Zofran,0,80509,0 -25775,51171817,Edisilato de proclorperazina,,0,80509,0 -25776,51171818,Maleato de proclorperazina,,0,80509,0 -25777,51171819,Maleato de tietilperacina,,0,80509,0 -25778,51171820,Dimenhidrinato,,0,80509,0 -25779,51171821,Aprepitant,,0,80509,0 -25780,51171822,Clorhidrato de palonosetrón,,0,80509,0 -25781,51171823,Granisetrón base,,0,80509,0 -25782,51171900,Fármacos antiúlcera y otros fármacos gastrointestinales (GI) relacionados,,0,80509,0 -25783,51171901,Cimetidina,,0,80509,0 -25784,51171902,Famotidina,,0,80509,0 -25785,51171903,Nizatidina,,0,80509,0 -25786,51171904,Clorhidrato de ranitidina,,0,80509,0 -25787,51171905,Cisapride,Cisaprida,0,80509,0 -25788,51171906,Lansoprazol,,0,80509,0 -25789,51171907,Clordiazepóxido clorhidrato o combinación de bromuro clinidio,Librax,0,80509,0 -25790,51171908,Misoprostol,Misodel,0,80509,0 -25791,51171909,Omeprazol,,0,80509,0 -25792,51171910,Pancreatina,,0,80509,0 -25793,51171911,Sucralfato,,0,80509,0 -25794,51171912,Clorhidrato de cimetidina,,0,80509,0 -25795,51171913,Esomeprazol magnesico trihidrato,Esomeprazol,0,80509,0 -25796,51171914,Pantoprazol sódico sesquihidratado,,0,80509,0 -25797,51171915,Pantoprazol sódico,,0,80509,0 -25798,51171916,Rabeprazol sódico,,0,80509,0 -25799,51171917,Citrato de bismuto ranitidina,,0,80509,0 -25800,51171918,Carbonato de aluminio,,0,80509,0 -25801,51171919,Amlexanox,,0,80509,0 -25802,51171920,Pantopra,,0,80509,0 -25803,51171921,Doramectina,,0,80509,0 -25804,51171922,Maleato de tegaserod,,0,80509,0 -25805,51171923,Racecadotrilo,,0,80509,0 -25806,51171924,Ramosetron,,0,80509,0 -25807,51171925,Rebamipida,,0,80509,0 -25808,51171926,Sevelamer,,0,80509,0 -25809,51172000,Medicamentos para enfermedades de la vesícula biliar,,0,80509,0 -25810,51172001,Quenodiol,Ácido quenodesoxicólico,0,80509,0 -25811,51172002,Monoctanoina,,0,80509,0 -25812,51172003,Ursodiol,Ácido ursodesoxicólico,0,80509,0 -25813,51172004,Dimetilsulfóxido,"Metil-sulfóxido, Sulfinilbismetano",0,80509,0 -25814,51172100,Antiespasmódicos,Relajantes musculares,0,80509,0 -25815,51172101,Clorhidrato de mebeverina,,0,80509,0 -25816,51172102,Clorhidrato de dicicloverina,Sediclon,0,80509,0 -25817,51172103,Clorhidrato de pitofenona,,0,80509,0 -25818,51172105,Floroglucinol,,0,80509,0 -25819,51172106,Clorhidrato de oxibutinina,,0,80509,0 -25820,51172107,Butilbromuro de hioscina,Bromuro de butilescopolamina,0,80509,0 -25821,51172108,Clorhidrato de viquidil,,0,80509,0 -25822,51172109,Trimebutina,,0,80509,0 -25823,51172110,Clorhidrato de prozapina,,0,80509,0 -25824,51172111,Alverina,,0,80509,0 -25825,51181500,Agentes antidiabéticos y agentes hiperglicémicos,,0,80509,0 -25826,51181501,Acarbosa,,0,80509,0 -25827,51181502,Acetohexamida,,0,80509,0 -25828,51181503,Clorpropamida,,0,80509,0 -25829,51181504,Glimepirida,,0,80509,0 -25830,51181505,Glipizida,,0,80509,0 -25831,51181506,Insulina,,0,80509,0 -25832,51181508,Glucagón,,0,80509,0 -25833,51181509,Gliclazida,,0,80509,0 -25834,51181510,Clorhidrato de fenformina,,0,80509,0 -25835,51181511,Buformina,,0,80509,0 -25836,51181513,Nateglinida,Starlix,0,80509,0 -25837,51181514,Extracto de páncreas,,0,80509,0 -25838,51181515,Tolbutamida,,0,80509,0 -25839,51181516,Glibenclamida o gliburida,,0,80509,0 -25840,51181517,Hidrocloruro de metformina,,0,80509,0 -25841,51181519,Miglitol,,0,80509,0 -25842,51181520,Clorhidrato de pioglitazona,,0,80509,0 -25843,51181521,Repaglinida,Prandin,0,80509,0 -25844,51181522,Maleato de rosiglitazona,Avandia,0,80509,0 -25845,51181523,Tolazamida,,0,80509,0 -25846,51181524,Troglitazona,,0,80509,0 -25847,51181525,Mosaprida,,0,80509,0 -25848,51181526,Epalrestat,Sal epalrestat,0,80509,0 -25849,51181527,Voglibosa,,0,80509,0 -25850,51181528,Exenatida,,0,80509,0 -25851,51181600,Medicamentos tiroideas y antitiroideas,,0,80509,0 -25852,51181601,Levotiroxina sódica,,0,80509,0 -25853,51181602,Liotironina sódica,,0,80509,0 -25854,51181603,Liotrix,T3/T4 Liotrix,0,80509,0 -25855,51181604,Tirotropina,"Hormona tirotrópica, TSH",0,80509,0 -25856,51181605,Metimazol,Tiamazol,0,80509,0 -25857,51181606,Propiltiouracilo,"6-n-propiltiouracilo, PTU",0,80509,0 -25858,51181607,Yoduro de potasio,Yoduro potásico,0,80509,0 -25859,51181608,Levotiroxina,"L-tiroxina, Levotiroximarycelisna",0,80509,0 -25860,51181609,Yoduro de sodio,Yoduro sódico,0,80509,0 -25861,51181700,Corticosteroides,,0,80509,0 -25862,51181701,Betametasona,,0,80509,0 -25863,51181702,Corticotropina,Adrenocorticotropa,0,80509,0 -25864,51181703,Cosintropina,,0,80509,0 -25865,51181704,Dexametasona,,0,80509,0 -25866,51181705,Flunisolida,,0,80509,0 -25867,51181706,Hidrocortisona,Cortisol,0,80509,0 -25868,51181707,Metilprednisolona,,0,80509,0 -25869,51181708,Prednisolona,,0,80509,0 -25870,51181709,Triamcinolona,,0,80509,0 -25871,51181710,Desoximetasona,,0,80509,0 -25872,51181711,Meprednisona,Beta metilprednisona acetato,0,80509,0 -25873,51181712,Ácido risedrónico,Risedronato,0,80509,0 -25874,51181713,Prednisona,,0,80509,0 -25875,51181714,Prednicarbato,,0,80509,0 -25876,51181715,Cortisona,17-hidroxi-11-corticosterona,0,80509,0 -25877,51181716,Cortivazol,,0,80509,0 -25878,51181717,Acetato de desoxicortona,,0,80509,0 -25879,51181718,Deflazacort,,0,80509,0 -25880,51181719,Acetónido de fluocinolona,,0,80509,0 -25881,51181720,Diacetato de diflorasona,,0,80509,0 -25882,51181721,Corticorelina ovina triflutato,,0,80509,0 -25883,51181722,Fluticasona,,0,80509,0 -25884,51181723,Acetato de hidrocortisona,,0,80509,0 -25885,51181724,Hidrocortisona buteprato,,0,80509,0 -25886,51181725,Butirato de hidrocortisona,,0,80509,0 -25887,51181726,Fosfato sódico de hidrocortisona,,0,80509,0 -25888,51181727,Valerato de hidrocortisona,,0,80509,0 -25889,51181728,Acetato de metilprednisolona,,0,80509,0 -25890,51181729,Succinato sódico de metilprednisolona,,0,80509,0 -25891,51181730,Acetato de prednisolona,,0,80509,0 -25892,51181731,Acetato de triamcinolona,,0,80509,0 -25893,51181732,Diacetato de triamcinolona,,0,80509,0 -25894,51181733,Hexacetonido de triamcinolona,,0,80509,0 -25895,51181734,Dipropionato de alclometasona,,0,80509,0 -25896,51181735,Propionato de halobetasol,,0,80509,0 -25897,51181736,Fluorometolona,,0,80509,0 -25898,51181737,Halcinonida,,0,80509,0 -25899,51181738,Acetato de fludrocortisona,,0,80509,0 -25900,51181739,Rimexolona,,0,80509,0 -25901,51181740,Fosfato sódico de dexametasona,,0,80509,0 -25902,51181741,Loteprednol etabonato,,0,80509,0 -25903,51181742,Propionato de clobetasol,,0,80509,0 -25904,51181743,Pivalato de clocortolona,,0,80509,0 -25905,51181744,Fluocinonida,,0,80509,0 -25906,51181745,Flurandrenolida,,0,80509,0 -25907,51181746,Amcinonida,,0,80509,0 -25908,51181747,Probutato de hidrocortisona,,0,80509,0 -25909,51181748,Medrisona,,0,80509,0 -25910,51181749,Furoato de mometasona,,0,80509,0 -25911,51181750,Desonida,,0,80509,0 -25912,51181751,Fosfato sódico de prednisolona,,0,80509,0 -25913,51181752,Dipropionato de beclometasona,,0,80509,0 -25914,51181753,Acetato de cortisona,,0,80509,0 -25915,51181754,Dipropionato de betametasona,,0,80509,0 -25916,51181755,Valerato de betametasona,,0,80509,0 -25917,51181756,Acetato de melengestrol,,0,80509,0 -25918,51181800,"Estrógenos, progesteronas y anticonceptivos internos",,0,80509,0 -25919,51181801,Clorotrianiseno,,0,80509,0 -25920,51181802,Estrona,"Cetohidroxiestrina, Foliculina, Hormona folicula",0,80509,0 -25921,51181803,Estrógenos conjugados,,0,80509,0 -25922,51181804,Estropipato,,0,80509,0 -25923,51181805,Levonorgestrel,D-norgestrel,0,80509,0 -25924,51181806,Acetato de megestrol,,0,80509,0 -25925,51181807,Etinilestradiol,,0,80509,0 -25926,51181808,Mestranol,,0,80509,0 -25927,51181810,Acetato de clormadinona,,0,80509,0 -25928,51181811,Desogestrel y etinilestradiol,,0,80509,0 -25929,51181812,Dienestrol,,0,80509,0 -25930,51181813,Estradiol,,0,80509,0 -25931,51181814,Etisterona,,0,80509,0 -25932,51181815,Estradiol y acetato de noretisterona,,0,80509,0 -25933,51181816,Hidroxiestrona diacetato,,0,80509,0 -25934,51181817,Promestrieno,,0,80509,0 -25935,51181818,Progesterona,"Pregn-4-en-3,20-diona",0,80509,0 -25936,51181819,Promegestona,,0,80509,0 -25937,51181820,Gestrinona,,0,80509,0 -25938,51181821,Cipionato de estradiol,,0,80509,0 -25939,51181822,Valerato de estradiol,,0,80509,0 -25940,51181823,Estrógenos esterificados,,0,80509,0 -25941,51181824,Diacetato de etinodiol,,0,80509,0 -25942,51181825,Dietilestilbestrol,,0,80509,0 -25943,51181826,Caproato de hidroxiprogesterona,,0,80509,0 -25944,51181827,Acetato de medroxiprogesterona,17-acetato de medroxiprogesterona,0,80509,0 -25945,51181828,Noretindrona,19-noretisterona,0,80509,0 -25946,51181829,Acetato de noretindrona,Acetato de noretisterona,0,80509,0 -25947,51181830,Norgestimato,,0,80509,0 -25948,51181831,Norgestrel,,0,80509,0 -25949,51181832,Etonogestrel y etinil estradiol,Anillo anticonceptivo,0,80509,0 -25950,51181833,Etinil-estradiol y diacetato de etinodiol,,0,80509,0 -25951,51181834,Noretindrona y etinil estradiol,,0,80509,0 -25952,51181835,Altrenogest,,0,80509,0 -25953,51181836,Gestodeno,,0,80509,0 -25954,51181900,Hormonas gonadotrópicas y estimulantes e inhibidores ováricos,,0,80509,0 -25955,51181901,Gonadotropina coriónica,Gonadotrofina coriónica humana,0,80509,0 -25956,51181902,Danazol,,0,80509,0 -25957,51181903,Acetato de gonadorelina,Gonadorelina acetato,0,80509,0 -25958,51181904,Acetato de histrelina,Implante de histrelina,0,80509,0 -25959,51181905,Menotropinas,,0,80509,0 -25960,51181906,Urofolitropina,,0,80509,0 -25961,51181908,Fosfestrol de sodio,,0,80509,0 -25962,51181911,Citrato de clomifeno,"Fenil-1,2-difenil-2-cloroetileno citrato",0,80509,0 -25963,51181912,Folitropina,Hormona foliculoestimul,0,80509,0 -25964,51181913,Clorhidrato de gonadorelina,,0,80509,0 -25965,51181914,Linestrenol,,0,80509,0 -25966,51182000,Andrógenos e inhibidores androgénicos,,0,80509,0 -25967,51182001,Finasterida,,0,80509,0 -25968,51182002,Testosterona,,0,80509,0 -25969,51182003,Cipionato de testosterona,,0,80509,0 -25970,51182004,Enantato de testosterona,Primoteston,0,80509,0 -25971,51182005,Propionato de testosterona,Testosterona propionato,0,80509,0 -25972,51182006,Acetato de trembolona,,0,80509,0 -25973,51182007,Metandriol,,0,80509,0 -25974,51182008,Fluoximesterona,Halotestin,0,80509,0 -25975,51182009,Metiltestosterona,Metandren,0,80509,0 -25976,51182010,Decanoato de nandrolona,,0,80509,0 -25977,51182011,Fenilpropionato de nandrolona,,0,80509,0 -25978,51182012,Prasterona,Dehidroepiandrosterona,0,80509,0 -25979,51182013,Stanozolol,Estanozolol,0,80509,0 -25980,51182014,Dutasteride,Dutasterida,0,80509,0 -25981,51182100,Hormonas de la pituitaria posterior,,0,80509,0 -25982,51182101,Acetato de desmopresina,,0,80509,0 -25983,51182102,Vasopresina,Hormona antidiurética,0,80509,0 -25984,51182200,Medicamentos para la inducción del parto,,0,80509,0 -25985,51182201,Dinoprostona,,0,80509,0 -25986,51182202,Maleato de metilergonovina,,0,80509,0 -25987,51182203,Oxitocina,,0,80509,0 -25988,51182204,Maleato de ergonovina,Occitocina,0,80509,0 -25989,51182300,Hormonas del crecimiento y sus inhibidores,,0,80509,0 -25990,51182301,Somatrem,Protropin,0,80509,0 -25991,51182302,Somatropina,Hormona somatotropina,0,80509,0 -25992,51182303,Somatostatina,Hormona inhibidora de la liberación de somatotropina,0,80509,0 -25993,51182304,Acetato de octreotida,,0,80509,0 -25994,51182400,Sales de calcio y reguladores del calcio,,0,80509,0 -25995,51182401,Cloruro de calcio,Cloruro cálcico,0,80509,0 -25996,51182403,Gluconato de calcio,Gluconato cálcico,0,80509,0 -25997,51182404,Lactato de calcio,Lactato Cálcico,0,80509,0 -25998,51182405,Fosfato de calcio dibase,,0,80509,0 -25999,51182406,Alendronato sódico,Ácido alendrónico,0,80509,0 -26000,51182407,Calcitonina,,0,80509,0 -26001,51182408,Fosfato sódico de celulosa,Fosfato sódico de celulosa,0,80509,0 -26002,51182409,Nitrato de galio,,0,80509,0 -26003,51182410,Etidronato disódico,,0,80509,0 -26004,51182411,Nandrolona,,0,80509,0 -26005,51182412,Fosfato calcio tribásico,,0,80509,0 -26006,51182413,Calcio fosfato de glicerol,Glicerofosfato de calcio,0,80509,0 -26007,51182415,Ácido zoledrónico,,0,80509,0 -26008,51182416,Pamidronato disódico,,0,80509,0 -26009,51182417,Risedronato de sodio,,0,80509,0 -26010,51182418,Acetato de calcio,,0,80509,0 -26011,51182419,Glucoheptonato de calcio o gluceptato de calcio,,0,80509,0 -26012,51182420,Dihidrotaquisterol,,0,80509,0 -26013,51182421,Nitrendipino,,0,80509,0 -26014,51182422,Benidipina hcl,,0,80509,0 -26015,51182423,Ibandronato,Ácido ibandrónico,0,80509,0 -26016,51182424,Raloxifeno hcl,,0,80509,0 -26017,51191500,Diuréticos,,0,80509,0 -26018,51191501,Clorotiazida,,0,80509,0 -26019,51191502,Clortalidona,Talamodina,0,80509,0 -26020,51191503,Metolazona,,0,80509,0 -26021,51191504,Bumetanida,,0,80509,0 -26022,51191505,Etacrinato de sodio,,0,80509,0 -26023,51191506,Clorhidrato de amilorida,,0,80509,0 -26024,51191507,Espironolactona,,0,80509,0 -26025,51191508,Triamtereno,,0,80509,0 -26026,51191509,Manitol,,0,80509,0 -26027,51191510,Furosemida,,0,80509,0 -26028,51191511,Canrenoato de potasio,Canrenoato potásico,0,80509,0 -26029,51191512,Lasitone,,0,80509,0 -26030,51191513,Bendroflumetiazida,,0,80509,0 -26031,51191514,Ácido etacrínico,,0,80509,0 -26032,51191515,Hidroclorotiazida,HCTZ,0,80509,0 -26033,51191516,Hidroflumetiazida,,0,80509,0 -26034,51191517,Isosorbide,,0,80509,0 -26035,51191518,Meticlotiazida,,0,80509,0 -26036,51191519,Pamabrom,,0,80509,0 -26037,51191520,Politiazida,,0,80509,0 -26038,51191521,Torasemida,Torsemida,0,80509,0 -26039,51191522,Triclormetiazida,,0,80509,0 -26040,51191523,Diurético osmótico urea,,0,80509,0 -26041,51191600,Electrolitos,,0,80509,0 -26042,51191601,Dextrosa,Glucosa,0,80509,0 -26043,51191602,Electrolitos de cloruro de sodio,,0,80509,0 -26044,51191603,Alimentación parenteral total o soluciones apt nutricionales,Solución total nutricional parenteral,0,80509,0 -26045,51191604,Solución ringer lactato,,0,80509,0 -26046,51191605,Agua estéril para inyección,,0,80509,0 -26047,51191606,Cloruro cúprico,Cloruro de cobre,0,80509,0 -26048,51191700,Agentes alcalinizadores,,0,80509,0 -26049,51191701,Trometamina,Trometamina ketorolaco,0,80509,0 -26050,51191702,Lactato de sodio,,0,80509,0 -26051,51191703,Acetato de sodio,Diacetato de sodio,"0,1",80509,0 -26052,51191704,Soluciones electrolíticas múltiples,,0,80509,0 -26053,51191705,Combinación de ácido cítrico y citrato de potasio,,0,80509,0 -26054,51191706,Combinación de ácido cítrico y citrato de sodio,,0,80509,0 -26055,51191800,Sales de potasio,,0,80509,0 -26056,51191801,Bicarbonato de potasio,Carbonato de potasa,0,80509,0 -26057,51191802,Cloruro de potasio,,0,80509,0 -26058,51191803,Gluconato de potasio,,0,80509,0 -26059,51191804,Acetato de potasio,Sal potásica,"0,1",80509,0 -26060,51191805,Suplemento de potasio,,0,80509,0 -26061,51191900,Suplementos dietéticos y productos de terapia alimenticia,Suplementos alimenticios o nutricionales,0,80509,0 -26062,51191901,Residuo de germen de maíz,,0,80509,0 -26063,51191902,Suplementos de amino ácidos,,0,80509,0 -26064,51191903,Pentermina,Fentermina,0,80509,0 -26065,51191904,Fosfolípidos,,0,80509,0 -26066,51191905,Suplementos vitamínicos,,0,80509,0 -26067,51191906,Solución de rehidratación oral,,0,80509,0 -26068,51191907,Amonio molibdato,Heptamolibdato de Amonio,0,80509,0 -26069,51191908,Orlistat,Tetrahidrolipstatina,0,80509,0 -26070,51191909,Tiamina,Vitamina B1,0,80509,0 -26071,51191910,Calcitriol,,0,80509,0 -26072,51191911,Retinol,"Antixeroftálmica, Vitamina A",0,80509,0 -26073,51201500,Inmunodepresores,,0,80509,0 -26074,51201501,Azatioprina,,0,80509,0 -26075,51201502,Ciclosporina,,0,80509,0 -26076,51201503,Mofetilo micofenolato,,0,80509,0 -26077,51201504,Tacrolimus,Tacrolimús,0,80509,0 -26078,51201505,Antilinfocitos,,0,80509,0 -26079,51201506,Defibrotide,,0,80509,0 -26080,51201507,Aldesleukina,Interleukin-2,0,80509,0 -26081,51201508,Basiliximab,Simulect,0,80509,0 -26082,51201509,Daclizumab,,0,80509,0 -26083,51201510,Imiquimod,,0,80509,0 -26084,51201511,Muromonab cd3,,0,80509,0 -26085,51201512,Clorhidrato de micofenolato de mofetilo,,0,80509,0 -26086,51201513,Peginterferón,Interferón pegilado,0,80509,0 -26087,51201514,Pimecrolimus,,0,80509,0 -26088,51201515,Sirolimus,Rapamicina,0,80509,0 -26089,51201516,Antilinfocito o inmunoglobulina linfocítica,,0,80509,0 -26090,51201517,Hidrocloruro de guanidina,,0,80509,0 -26091,51201518,Azatioprina sódica,,0,80509,0 -26092,51201519,Alefacept,,0,80509,0 -26093,51201600,"Vacunas, antígenos y toxoides",,0,80509,0 -26094,51201601,Antígeno de ántrax,,0,80509,0 -26095,51201602,Antígeno brucélico,,0,80509,0 -26096,51201603,Vacuna contra el cólera,,0,80509,0 -26097,51201604,Difteria,Vacuna contra la difteria,0,80509,0 -26098,51201605,Vacuna contra el virus de la encefalitis,,0,80509,0 -26099,51201606,Influenza hemofílica,Vacuna contra la influenza hemofilica,0,80509,0 -26100,51201607,Vacuna contra el virus de hepatitis b,,0,80509,0 -26101,51201608,Vacuna contra el virus de la influenza,,0,80509,0 -26102,51201609,Vacuna contra el virus del sarampión,,0,80509,0 -26103,51201610,Vacuna meningococo,,0,80509,0 -26104,51201611,Morbillivirus,Vacuna contra morvillivirus,0,80509,0 -26105,51201612,Vacuna contra el virus de las paperas,,0,80509,0 -26106,51201613,Parotiditis,Paperas,0,80509,0 -26107,51201614,Vacuna contra la pertussis,,0,80509,0 -26108,51201615,Vacuna contra el neumococo,,0,80509,0 -26109,51201616,Vacuna contra el virus del polio,,0,80509,0 -26110,51201617,Vacuna contra la rabia,,0,80509,0 -26111,51201618,Rotavirus,Vacuna contra el rotavirus,0,80509,0 -26112,51201619,Vacuna contra el virus de la rubeola,,0,80509,0 -26113,51201620,Viruela,Vacuna contra la viruela,0,80509,0 -26114,51201621,Toxoide tetánico,,0,80509,0 -26115,51201622,Tuberculosis,Vacuna contra la tuberculosis,0,80509,0 -26116,51201623,Vacuna contra el tifo,Vacuna contra el tifoidea,0,80509,0 -26117,51201624,Vacuna contra el virus de la varicela,,0,80509,0 -26118,51201625,Vacuna contra la fiebre amarilla,,0,80509,0 -26119,51201626,Hepatitis a,Vacuna preventiva contra hepatitis,0,80509,0 -26120,51201627,Vacuna contra la hemofilia b,,0,80509,0 -26121,51201628,"Vacuna contra los virus del sarampión, paperas y rubeola",,0,80509,0 -26122,51201629,Difteria y toxoide tetánico adsorbido,,0,80509,0 -26123,51201631,Vacuna contra difteria y toxoide tetánico y pertussis acelular,,0,80509,0 -26124,51201632,Difteria y tétano y pertussis celular,,0,80509,0 -26125,51201633,Influenza b hemofílica con difteria y tétano y pertussis acelular,,0,80509,0 -26126,51201634,Influenza b hemofílica con difteria y tétano y pertussis celular conjugadas,,0,80509,0 -26127,51201635,Vacuna contra el virus de la hepatitis a,,0,80509,0 -26128,51201636,Vacuna contra la enfermedad de lyme (borreliosis),,0,80509,0 -26129,51201638,Vacuna contra la plaga,,0,80509,0 -26130,51201639,Vacuna contra el estafilococo,,0,80509,0 -26131,51201646,Vacuna contra el virus del sarampión y la rubeola,,0,80509,0 -26132,51201647,Vacuna bcg,,0,80509,0 -26133,51201648,Vacuna contra el virus de la rubeola y las paperas,,0,80509,0 -26134,51201649,Vacuna contra el virus del papiloma humano,,0,80509,0 -26135,51201700,Vacunas aviares,,0,80509,0 -26136,51201701,Hipocruposis,,0,80509,0 -26137,51201702,E coli,Escherichia coli,0,80509,0 -26138,51201703,Gumboro,Vacuna contra el gumboro,0,80509,0 -26139,51201704,Bronquitis infecciosa aviar,Vacuna contra la bronquitis infecciosa,0,80509,0 -26140,51201705,Newcastle,Vacuna contra el newcastle,0,80509,0 -26141,51201800,Agentes inmunoestimulantes,,0,80509,0 -26142,51201801,Inmunoglobulinas bacterianas,,0,80509,0 -26143,51201802,Filgrastim,,0,80509,0 -26144,51201803,Pelfigrastim,Neulasta,0,80509,0 -26145,51201804,Sargramostim,,0,80509,0 -26146,51201805,Inmunoglobulina rho d,RhoGAM,0,80509,0 -26147,51201806,Inmunoglobulina o gamma igg,,0,80509,0 -26148,51201807,Inmunoglobulinas virales,,0,80509,0 -26149,51201808,Clorhidrato levamisol,L-Tetramisol clorhidrato,0,80509,0 -26150,51201809,Interferon,,0,80509,0 -26151,51201810,Galsulfasa,,0,80509,0 -26152,51201811,Talidomida,,0,80509,0 -26153,51201900,Agentes de la esclerosis múltiple (EM),,0,80509,0 -26154,51201901,Acetato de glatiramer,,0,80509,0 -26155,51211500,Medicamentos antigota,,0,80509,0 -26156,51211501,Alopurinol,,0,80509,0 -26157,51211502,Colchicina,Colquicina,0,80509,0 -26158,51211503,Probenecid,,0,80509,0 -26159,51211504,Sulfinpirazona,,0,80509,0 -26160,51211505,Benzbromarona,,0,80509,0 -26161,51211600,Antídotos y eméticos,,0,80509,0 -26162,51211601,Deferoxamina mesilato,,0,80509,0 -26163,51211602,Dexrazoxano,,0,80509,0 -26164,51211603,Digoxina inmune fab,,0,80509,0 -26165,51211604,Dimercaprol,,0,80509,0 -26166,51211605,Edetato disódico,Edatamil calcio disódico,0,80509,0 -26167,51211606,Flumazenil,Flumazenilo,0,80509,0 -26168,51211607,Ipecacuana,,0,80509,0 -26169,51211608,Penicilamina,,0,80509,0 -26170,51211609,Protamina sulfato,,0,80509,0 -26171,51211610,Sulfonato poliestireno sódico,,0,80509,0 -26172,51211611,Trientina,Trietilentetramina,0,80509,0 -26173,51211612,Leucovorina,Ácido folínico,0,80509,0 -26174,51211613,Sorbitol,,0,80509,0 -26175,51211615,Azul de metileno,,0,80509,0 -26176,51211616,Anti venenos,,0,80509,0 -26177,51211617,Leucovorina cálcica,Leucovorina cálcica,0,80509,0 -26178,51211618,Antídoto de carbón activado,,0,80509,0 -26179,51211619,Edetato cálcico disódico,,0,80509,0 -26180,51211620,Fomepizol,,0,80509,0 -26181,51211621,Clorhidrato pralidoxima,,0,80509,0 -26182,51211622,Tiosulfato de sodio,,0,80509,0 -26183,51211623,Succimer,Ácido dimercaptosuccínico,0,80509,0 -26184,51211624,Sulfato de cobre,Sulfato cúprico,0,80509,0 -26185,51211625,Exopeptidasa g2,,0,80509,0 -26186,51211900,Fármacos anestésicos veterinarios,,0,80509,0 -26187,51211901,Xilacina,,0,80509,0 -26188,51212000,Fármacos herbales,"Fármacos homeopáticos, Medicamentos homeopáticos en general.",0,80509,0 -26189,51212001,Aceite etérico,Aceite esencial,0,80509,0 -26190,51212002,Extracto de centella asiática,,0,80509,0 -26191,51212003,Extracto de ipecacuana,Jarabe de ipecacuana,0,80509,0 -26192,51212004,Aesculus,Castaña de indias,0,80509,0 -26193,51212005,Aconitum napellus (acónito común),,0,80509,0 -26194,51212006,Valeriana,Valeriana medicinal,0,80509,0 -26195,51212007,Ajo,,0,80509,0 -26196,51212008,Celidonia mayor,Golondrina,0,80509,0 -26197,51212009,Saponaria,"Hierba jabonera, Jabonaria",0,80509,0 -26198,51212010,Prímula,Primaveras,0,80509,0 -26199,51212011,Pimpinella,,0,80509,0 -26200,51212012,Cilantro,,0,80509,0 -26201,51212013,Hedera hélix,Hieda común,0,80509,0 -26202,51212014,Goma arábiga,,0,80509,0 -26203,51212015,Boneset,,0,80509,0 -26204,51212016,Echinacea,,0,80509,0 -26205,51212017,Acónito,,0,80509,0 -26206,51212018,Coccus cacti,,0,80509,0 -26207,51212020,Menta de gato,Nébeda,0,80509,0 -26208,51212021,Ginseng,Panax ginseng,0,80509,0 -26209,51212022,Asafétida,Ferula assafoetida,0,80509,0 -26210,51212023,Arándano,,0,80509,0 -26211,51212024,Árnica,,0,80509,0 -26212,51212025,Bálsamo del perú,,0,80509,0 -26213,51212026,Eufrasia,,0,80509,0 -26214,51212027,Ginkgo biloba,Árbol de los cuarenta escudos,0,80509,0 -26215,51212028,Hydrastis,Hidrastis,0,80509,0 -26216,51212029,Gotu kola,Centella asiática,0,80509,0 -26217,51212030,Palma enana americana (saw palmetto),Palmito salvaje,0,80509,0 -26218,51212031,Pygeum africano,Extracto lipofílico de ciruelo africano,0,80509,0 -26219,51212032,Raíz de angélica,,0,80509,0 -26220,51212033,Violeta geneciana,Violeta de metilo,0,80509,0 -26221,51212034,Capsaicina,Capsicina,0,80509,0 -26222,51212035,Leche de cardo,Cardo mariano,0,80509,0 -26223,51212036,Cohosh negro,Raíz de culebra negra,0,80509,0 -26224,51212100,Fármacos dentales,,0,80509,0 -26225,51212101,Floruro de sodio,,0,80509,0 -26226,51212200,Fármacos contra las adicciones,,0,80509,0 -26227,51212201,Disulfiram,,0,80509,0 -26228,51212202,Nicotina,,0,80509,0 -26229,51212203,Nicotina polacrilina,,0,80509,0 -26230,51212204,Vareniclina,,0,80509,0 -26231,51212300,Radiofármacos y agentes de diagnóstico,,0,80509,0 -26232,51212301,Aminohipurato sódico,,0,80509,0 -26233,51212302,Aceite etiodizado,,0,80509,0 -26234,51212303,Sodio fluoresceína,,0,80509,0 -26235,51212304,Indigotindisulfonato sódico,,0,80509,0 -26236,51212305,Indocianina verde,,0,80509,0 -26237,51212306,Cloruro de metacolina,,0,80509,0 -26238,51212307,Metirapona,,0,80509,0 -26239,51212308,Sodio de rosa de bengala i 131,,0,80509,0 -26240,51212309,Loxaglato de meglumina,,0,80509,0 -26241,51212310,Kit de preparación radio farmacéutica,,0,80509,0 -26242,51212311,Iopamidol,,0,80509,0 -26243,51212400,Fármacos para tratar la disfunción sexual,,0,80509,0 -26244,51212401,Sildenafil citrato,Viagra,0,80509,0 -26245,51212402,Clorhidrato de vardenafil,,0,80509,0 -26246,51212403,Dapoxetina,,0,80509,0 -26247,51212404,Tadalafil,Cialis,0,80509,0 -26248,51212500,Medio de contraste para imágenes médicas,,0,80509,0 -26249,51212501,Diazoato,,0,80509,0 -26250,51212502,Iodixanol,,0,80509,0 -26251,51212503,Sulfato de bario,,0,80509,0 -26252,51212504,Gadolinio,,0,80509,0 -26253,51212505,Ioversol,,0,80509,0 -26254,51241000,Preparados aurales,,0,80509,0 -26255,51241001,Dioctil sulfosuccinato de sodio,,0,80509,0 -26256,51241002,Oleato polipéptido de trietanolamina,,0,80509,0 -26257,51241100,Agentes oftálmicos,,0,80509,0 -26258,51241101,Cloruro acetilcolina,,0,80509,0 -26259,51241102,Clorhidrato de apraclonidina,,0,80509,0 -26260,51241103,Bimatoprost,,0,80509,0 -26261,51241104,Brinzolamida,,0,80509,0 -26262,51241105,Carbachol,"Carbacol, Carbamilcolina",0,80509,0 -26263,51241106,Demecario bromuro,,0,80509,0 -26264,51241107,Dorzolimato clorhidrato,,0,80509,0 -26265,51241108,Iodeto de ecotiofato,Yoduro de Ecotiofato,0,80509,0 -26266,51241109,Borato de epinephryl,,0,80509,0 -26267,51241110,Latanoprost,,0,80509,0 -26268,51241111,Clorhidrato de levobunolol,,0,80509,0 -26269,51241112,Metipranolol,,0,80509,0 -26270,51241113,Sulfato fisostigmina,,0,80509,0 -26271,51241114,Clorhidrato de pilocarpina,,0,80509,0 -26272,51241115,Timolol,,0,80509,0 -26273,51241116,Travoprost,,0,80509,0 -26274,51241117,Isopropil unoprostone,,0,80509,0 -26275,51241118,Diclorfenamida,Diclofenamida,0,80509,0 -26276,51241119,Metalzolamida,,0,80509,0 -26277,51241120,Lágrimas artificiales,,0,80509,0 -26278,51241121,Alcohol polivinílico,,0,80509,0 -26279,51241200,Agentes dermatológicos,,0,80509,0 -26280,51241201,Ácido salicílico,Salix,0,80509,0 -26281,51241202,Ácido azelaico,Ácido nonanodioico,0,80509,0 -26282,51241203,Subcarbonato de bismuto,,0,80509,0 -26283,51241204,Ácido bórico,Ácido trioxobórico,0,80509,0 -26284,51241205,Calamina,,0,80509,0 -26285,51241206,Cloroxina,,0,80509,0 -26286,51241207,Preparaciones tópicas de brea de hulla,,0,80509,0 -26287,51241208,Cremas o ungüentos hidrofilacios,,0,80509,0 -26288,51241209,Hidroquinona,,0,80509,0 -26289,51241210,Salicilato de metilo,,0,80509,0 -26290,51241211,Minoxidil solución tópica,,0,80509,0 -26291,51241212,Papaina,,0,80509,0 -26292,51241213,Preparación tópica de brea de pino,,0,80509,0 -26293,51241214,Podofilox,,0,80509,0 -26294,51241215,Resina podofilina,,0,80509,0 -26295,51241216,Piritionato de zinc,,0,80509,0 -26296,51241217,Resorcinol,"Benceno-1,3-diol",0,80509,0 -26297,51241218,Sulfato de selenio,Disulfuro de selenio,0,80509,0 -26298,51241219,Tazaroteno,,0,80509,0 -26299,51241220,Tretinoina,Ácido holo-transretinoico,0,80509,0 -26300,51241221,Tripsina cristalizada,,0,80509,0 -26301,51241222,Dimeticona,,0,80509,0 -26302,51241223,Calcipotriene,Calcipotrieno,0,80509,0 -26303,51241224,Antralina o ditranol,,0,80509,0 -26304,51241225,Preparaciones tópicas de alcanfor,,0,80509,0 -26305,51241226,Preparaciones tópicas de urea,,0,80509,0 -26306,51241227,Preparaciones tópicas de aceite de turpentina,,0,80509,0 -26307,51241228,Ácido láctico,Lactato,0,80509,0 -26308,51241229,Solución sustituta de la saliva,,0,80509,0 -26309,51241230,Niacinamida,,0,80509,0 -26310,51241231,Acitetrina,,0,80509,0 -26311,51241232,Isotetrinoina,,0,80509,0 -26312,51241233,Adapaleno,,0,80509,0 -26313,51241234,Ácido fusídico,,0,80509,0 -26314,51241300,Astringentes,,0,80509,0 -26315,51241301,Acetato de aluminio,Acetato alumínico,0,80509,0 -26316,51241302,Hamamelis o escoba de bruja,,0,80509,0 -26317,51241303,Acetato de zinc,,0,80509,0 -26318,51241304,Alumbre de amonio,,0,80509,0 -26319,51241305,Ácido tánico,,0,80509,0 -26320,51251000,Metabolitos,,0,80509,0 -26321,51251001,Butafosfan,,0,80509,0 -26322,51251002,Ractopamina,,0,80509,0 -26323,52101500,Alfombras y felpudos,,0,80509,0 -26324,52101501,Alfombras orientales,,0,80509,0 -26325,52101502,Alfombras,,0,80509,0 -26326,52101503,Alfombras de lana,,0,80509,0 -26327,52101504,Alfombras de algodón,,0,80509,0 -26328,52101505,Alfombras sintéticas,,0,80509,0 -26329,52101506,Alfombras trenzadas,,0,80509,0 -26330,52101507,Tapetes de baño,,0,80509,0 -26331,52101508,Tapetes de entrada,,0,80509,0 -26332,52101509,Tapetes decorativos,,0,80509,0 -26333,52101510,Tapetes anti fatiga,,0,80509,0 -26334,52101511,Tapetes de caucho o vinilo,,0,80509,0 -26335,52101512,Esterillas para sillas,Colchoneta de silla,0,80509,0 -26336,52101513,Protector de carpetas,Protector de alfombra,0,80509,0 -26337,52101514,Tapetes de fibra de vidrio,,0,80509,0 -26338,52101515,Cojín de piso,,0,80509,0 -26339,52101516,Alfombra de estera de palma,Alfombra de paja,0,80509,0 -26340,52121500,Ropa de cama,,0,80509,0 -26341,52121501,Colchas,,0,80509,0 -26342,52121502,Edredones,,0,80509,0 -26343,52121503,Forros para edredones,,0,80509,0 -26344,52121504,Forros para colchones,,0,80509,0 -26345,52121505,Almohadas,,0,80509,0 -26346,52121506,Rellenos para colchones,,0,80509,0 -26347,52121507,Plumones,Colchoneta de plumas,0,80509,0 -26348,52121508,Cobijas,,0,80509,0 -26349,52121509,Sábanas,,0,80509,0 -26350,52121510,Volantes de cama,,0,80509,0 -26351,52121511,Fundas de edredón,,0,80509,0 -26352,52121512,Fundas de almohada,,0,80509,0 -26353,52121513,Cobertores,,0,80509,0 -26354,52121514,Fundas para asientos,,0,80509,0 -26355,52121600,Mantelerías de cocina y mesa y accesorios,,0,80509,0 -26356,52121601,Limpiones,Paño de cocina,0,80509,0 -26357,52121602,Servilletas,,0,80509,0 -26358,52121603,Tiras de mesa,Camino de mesa,0,80509,0 -26359,52121604,Manteles,,0,80509,0 -26360,52121605,Guantes de horno o coge ollas para uso doméstico,,0,80509,0 -26361,52121606,Individuales de mesa,,0,80509,0 -26362,52121607,Faldas de mesa,,0,80509,0 -26363,52121608,Clips para faldas de mesa,Pinzas para faldas de mesa,0,80509,0 -26364,52121700,Toallas,,0,80509,0 -26365,52121701,Toallas de baño,,0,80509,0 -26366,52121702,Toallas playeras,,0,80509,0 -26367,52121703,Paños para lavar,,0,80509,0 -26368,52121704,Toallas de manos,,0,80509,0 -26369,52121705,Toallas para la cara,,0,80509,0 -26370,52131500,Cortinas y ropaje,,0,80509,0 -26371,52131501,Cortinas,,0,80509,0 -26372,52131503,Colgaduras,Colgantes para cortina,0,80509,0 -26373,52131600,Persianas,,0,80509,0 -26374,52131601,Persianas venecianas,,0,80509,0 -26375,52131602,Persianas enrollables,,0,80509,0 -26376,52131603,Postigos interiores,,0,80509,0 -26377,52131604,Persianas verticales,,0,80509,0 -26378,52131700,Accesorios y ferretería para tratamiento de ventanas,,0,80509,0 -26379,52131701,Cenefas,,0,80509,0 -26380,52131702,Varillas para cortinas,Barra para cortina,0,80509,0 -26381,52131703,Remates para varillas,Remate para barra de cortina,0,80509,0 -26382,52131704,Anillos o ganchos para cortinas,,0,80509,0 -26383,52131705,Rieles metálicos para cortinas,,0,80509,0 -26384,52141500,Electrodomésticos para cocina,,0,80509,0 -26385,52141501,Neveras para uso doméstico,,0,80509,0 -26386,52141502,Hornos microondas para uso doméstico,,0,80509,0 -26387,52141503,Trituradores de basura para uso doméstico,,0,80509,0 -26388,52141504,Fogones para uso doméstico,,0,80509,0 -26389,52141505,Lavadoras de platos para uso doméstico,,0,80509,0 -26390,52141506,Congeladores para uso doméstico,,0,80509,0 -26391,52141507,Congeladores verticales para uso doméstico,,0,80509,0 -26392,52141508,Congeladores horizontales para uso doméstico,,0,80509,0 -26393,52141509,Combinación de neveras y congeladores para uso doméstico,,0,80509,0 -26394,52141510,Aire acondicionado portátil para uso doméstico,,0,80509,0 -26395,52141511,Exprimidores de jugo para uso doméstico,,0,80509,0 -26396,52141512,Planchas para waffles para uso doméstico,,0,80509,0 -26397,52141513,Abrelatas eléctricos para uso doméstico,,0,80509,0 -26398,52141514,Procesadores de alimentos para uso doméstico,,0,80509,0 -26399,52141515,Compactadores de basura para uso doméstico,,0,80509,0 -26400,52141516,Freidoras para uso doméstico,,0,80509,0 -26401,52141517,Máquinas de maíz pira para uso doméstico,Máquinas de palomitas de maíz para uso doméstico,0,80509,0 -26402,52141518,Máquinas para uso doméstico para hacer pan,,0,80509,0 -26403,52141519,Hornos convencionales para uso doméstico,,0,80509,0 -26404,52141520,Mezcladoras para uso doméstico,Batidoras para uso doméstico,0,80509,0 -26405,52141521,Hornos tostadores para uso doméstico,,0,80509,0 -26406,52141522,Tostadoras para uso doméstico,,0,80509,0 -26407,52141523,Teteras eléctricas para uso doméstico,,0,80509,0 -26408,52141524,Licuadoras para uso doméstico,,0,80509,0 -26409,52141525,Hornillas para uso doméstico,Estufa,0,80509,0 -26410,52141526,Cafeteras para uso doméstico,,0,80509,0 -26411,52141527,Cuchillos eléctricos para uso doméstico,,0,80509,0 -26412,52141528,Woks eléctricos para uso doméstico,,0,80509,0 -26413,52141529,Moledoras de café para uso doméstico,,0,80509,0 -26414,52141530,Partes de máquinas lavaplatos,,0,80509,0 -26415,52141531,Tajadores de alimentos para uso doméstico,Rebanadora,0,80509,0 -26416,52141532,Sartenes eléctricos para uso doméstico,,0,80509,0 -26417,52141533,Planchas eléctricas para uso doméstico,,0,80509,0 -26418,52141534,Sandwicheras eléctricas para uso doméstico,,0,80509,0 -26419,52141535,Parrillas eléctricas interiores para uso doméstico,,0,80509,0 -26420,52141536,Pizelles o máquinas para hacer galletas para uso doméstico,,0,80509,0 -26421,52141537,Olla de cocción lenta para uso doméstico,,0,80509,0 -26422,52141538,Calienta comidas para uso doméstico,,0,80509,0 -26423,52141539,Máquinas para hacer té para uso doméstico,,0,80509,0 -26424,52141540,Sellador de bolsas al vacío,,0,80509,0 -26425,52141541,Esterilizador de teteros para uso doméstico,,0,80509,0 -26426,52141542,Moledor de carne para uso doméstico,,0,80509,0 -26427,52141543,Aparato eléctrico para cocinar huevos,,0,80509,0 -26428,52141544,Aparato para uso doméstico para diversas formas de cocción,,0,80509,0 -26429,52141545,Placa de cocina para uso doméstico,,0,80509,0 -26430,52141546,Extractor de cocina para uso doméstico,,0,80509,0 -26431,52141547,Triturador o moledor de sal y pimienta para uso doméstico,,0,80509,0 -26432,52141548,“Pierrade” para uso doméstico,Plancha de piedra para uso doméstico,0,80509,0 -26433,52141549,“Raclette” para uso doméstico,Plancha de acero para uso doméstico,0,80509,0 -26434,52141550,Máquina para uso doméstico para hacer yogurt,,0,80509,0 -26435,52141551,Horno de vapor para uso doméstico,,0,80509,0 -26436,52141552,Horno asador para uso doméstico,,0,80509,0 -26437,52141553,Olla arrocera eléctrica para uso doméstico,,0,80509,0 -26438,52141554,Congelador “kimchi” para uso doméstico,,0,80509,0 -26439,52141600,Lavadoras y suministros de uso doméstico,,0,80509,0 -26440,52141601,Lavadoras de ropa para uso doméstico,,0,80509,0 -26441,52141602,Secadoras de ropa para uso doméstico,,0,80509,0 -26442,52141603,Planchas de ropa para uso doméstico,,0,80509,0 -26443,52141604,Secadoras de calzado,,0,80509,0 -26444,52141605,Canastas de ropa,,0,80509,0 -26445,52141606,Cestas de ropa,,0,80509,0 -26446,52141607,Compuestos para remover arrugas de los textiles,,0,80509,0 -26447,52141608,Plancha de vapor para ropa,,0,80509,0 -26448,52141609,Dispositivo para uso doméstico para remover manchas,,0,80509,0 -26449,52141610,Gabinete de planchado para uso doméstico,,0,80509,0 -26450,52141611,Lavadero,,0,80509,0 -26451,52141700,Aparatos de baño domésticos,,0,80509,0 -26452,52141701,Cepillos de dientes eléctricos para uso doméstico,,0,80509,0 -26453,52141703,Secadores de pelo para uso doméstico,,0,80509,0 -26454,52141704,Máquinas de afeitar eléctricas para uso doméstico,,0,80509,0 -26455,52141705,Partes de afeitadoras o removedores de vello,"Cabezas para rastrillos, Navajas para rastrillos",0,80509,0 -26456,52141706,Secadores de uñas,,0,80509,0 -26457,52141707,Secadores de toallas,,0,80509,0 -26458,52141708,Pinzas para rizar el cabello,,0,80509,0 -26459,52141800,Otros electrodomésticos,,0,80509,0 -26460,52141801,Máquinas de coser para uso doméstico,,0,80509,0 -26461,52141802,Calentadores de espacios para uso doméstico,,0,80509,0 -26462,52141804,Ventiladores eléctricos para uso doméstico,,0,80509,0 -26463,52141805,Biombos plegables,Mampara plegable,0,80509,0 -26464,52141806,Peceras o tanques para peces,,0,80509,0 -26465,52141807,Abanicos de mano,,0,80509,0 -26466,52151500,Utensilios de cocina desechables domésticos,,0,80509,0 -26467,52151501,Utensilios de cocina desechables para uso doméstico,,0,80509,0 -26468,52151502,Platos desechables para uso doméstico,,0,80509,0 -26469,52151503,Cubiertos desechables para uso doméstico,,0,80509,0 -26470,52151504,Tazas o vasos o tapas desechables para uso doméstico,,0,80509,0 -26471,52151505,Agitadores desechables para uso doméstico,,0,80509,0 -26472,52151506,Contenedores de alimentos desechables para uso doméstico,,0,80509,0 -26473,52151507,Pitillos desechables para uso doméstico,Popotes desechables para uso doméstico,0,80509,0 -26474,52151600,Utensilios de cocina domésticos,,0,80509,0 -26475,52151601,Rodillos para uso doméstico,Rodillos para masa de uso doméstico,0,80509,0 -26476,52151602,Tazones mezcladores para uso doméstico,,0,80509,0 -26477,52151603,Ralladores para uso doméstico,,0,80509,0 -26478,52151604,Coladores o coladeras para uso doméstico,,0,80509,0 -26479,52151605,Destapadores o abrelatas para uso doméstico,,0,80509,0 -26480,52151606,Tablas para cortar para uso doméstico,,0,80509,0 -26481,52151607,Tazas medidoras para uso doméstico,,0,80509,0 -26482,52151608,Brochas para rociar alimentos,,0,80509,0 -26483,52151609,Peladora de vegetales,Pelador de vegetales,0,80509,0 -26484,52151610,Cortadores de galletas,Moldes de galletas,0,80509,0 -26485,52151611,Pinzas de cocina para uso doméstico,Tenazas de cocina para uso doméstico,0,80509,0 -26486,52151612,Batidoras de alambre de cocina para uso doméstico,Agitador de alambre de cocina para uso doméstico,0,80509,0 -26487,52151613,Raspadores de comida para uso doméstico,Espátula de comida para uso doméstico,0,80509,0 -26488,52151614,Rejillas enfriadoras para uso doméstico,Rejilla de enfriamiento de alambre para uso doméstico,0,80509,0 -26489,52151615,Cortadores de pizza para uso doméstico,,0,80509,0 -26490,52151616,Espátulas de cocina para uso doméstico,"Palas de cocina para uso doméstico, Volteador de cocina para uso doméstico",0,80509,0 -26491,52151617,Cucharas de madera para uso doméstico,,0,80509,0 -26492,52151618,Paletas de madera para hornear para uso doméstico,,0,80509,0 -26493,52151619,Mezclador de masa para uso doméstico,,0,80509,0 -26494,52151620,Cernedor para uso doméstico,Colador para uso doméstico,0,80509,0 -26495,52151621,Estampador para galletas para uso doméstico,Selladores domésticos de galletas,0,80509,0 -26496,52151622,Pistola pastelera para uso doméstico,,0,80509,0 -26497,52151623,Prensa para galletas para uso doméstico,,0,80509,0 -26498,52151624,Afiladores de cuchillos para uso doméstico,,0,80509,0 -26499,52151625,Cortadores de galletas para uso doméstico,,0,80509,0 -26500,52151626,Mandolina para uso doméstico,,0,80509,0 -26501,52151627,Exprimidor de ajo para uso doméstico,,0,80509,0 -26502,52151628,Tajador de huevos para uso doméstico,Cortadora de huevos para uso doméstico,0,80509,0 -26503,52151629,Separador de huevos para uso doméstico,,0,80509,0 -26504,52151630,Tajadora de queso para uso doméstico,Ralladora de queso para uso doméstico,0,80509,0 -26505,52151631,Molino de alimentos para uso doméstico,,0,80509,0 -26506,52151632,Embudos de cocina para uso doméstico,,0,80509,0 -26507,52151633,Herramientas para adobar para uso doméstico,Equipo para adobar de uso doméstico,0,80509,0 -26508,52151634,Descorazonadores de manzanas para uso doméstico,,0,80509,0 -26509,52151635,Sacabocados para melón o mantequilla para uso doméstico,,0,80509,0 -26510,52151636,Palas o cucharas para alimentos para uso doméstico,,0,80509,0 -26511,52151637,Tallador de calabazas para uso doméstico,,0,80509,0 -26512,52151638,Cepillo para vegetales para uso doméstico,,0,80509,0 -26513,52151639,Batidora de huevos para uso doméstico,,0,80509,0 -26514,52151640,Estante para secar pasta para uso doméstico,,0,80509,0 -26515,52151641,Batidora de crema para uso doméstico,,0,80509,0 -26516,52151642,Prensa para masa para uso doméstico,"Maquina para hacer tortillas, Tortilladora manual",0,80509,0 -26517,52151643,Máquina para hacer ravioli para uso doméstico,Maquina para hacer ravioles,0,80509,0 -26518,52151644,Rociadores de rocío o de gatillo para uso doméstico,,0,80509,0 -26519,52151645,Pincel pastelero para uso doméstico,,0,80509,0 -26520,52151646,Pesas para cocina o dietas para uso doméstico,Báscula de cocina,0,80509,0 -26521,52151647,Relojes (temporizadores) de cocina para uso doméstico,,0,80509,0 -26522,52151648,Termómetros de alimentos o cocina para uso doméstico,,0,80509,0 -26523,52151649,Guía para tajar pan para uso doméstico,Guía para rebanar pan para uso doméstico,0,80509,0 -26524,52151650,Escurridores para uso doméstico,,0,80509,0 -26525,52151651,Cucharas de medida para uso doméstico,,0,80509,0 -26526,52151652,Exprimidores de limones,,0,80509,0 -26527,52151653,Cuchillos para ostras,,0,80509,0 -26528,52151654,Pasapurés para papas,Machacador de papas,0,80509,0 -26529,52151655,Centrifugador de ensaladas,,0,80509,0 -26530,52151656,Tajador de aguacates,Cortadora de aguacate,0,80509,0 -26531,52151657,Antorchas culinarias,Soplete para cocinar,0,80509,0 -26532,52151658,Cuchillo para masas,,0,80509,0 -26533,52151659,Ablandador de carnes manual,,0,80509,0 -26534,52151660,Cuchara para spaghetti,,0,80509,0 -26535,52151661,Protector de salpicaduras para botellas,,0,80509,0 -26536,52151662,Colador de malla,,0,80509,0 -26537,52151663,Mortero para moler alimentos,"Metate, Molcajete",0,80509,0 -26538,52151700,Cubertería y cuchillería de uso doméstico,,0,80509,0 -26539,52151701,Utensilios para servir para uso doméstico,,0,80509,0 -26540,52151702,Cuchillos para uso doméstico,,0,80509,0 -26541,52151703,Tenedores para uso doméstico,,0,80509,0 -26542,52151704,Cucharas para uso doméstico,,0,80509,0 -26543,52151705,Apoya cucharas,,0,80509,0 -26544,52151706,Palillos,,0,80509,0 -26545,52151707,Set de cuchillos para uso doméstico,,0,80509,0 -26546,52151708,Cuchillos para mantequilla,,0,80509,0 -26547,52151709,Set de cubiertos,,0,80509,0 -26548,52151800,Batería de cocina doméstica,,0,80509,0 -26549,52151801,Cacerolas de hierro fundido con tapa para uso doméstico,,0,80509,0 -26550,52151802,Sartenes para uso doméstico,,0,80509,0 -26551,52151803,Cacerolas para uso doméstico,,0,80509,0 -26552,52151804,Teteras para uso doméstico,,0,80509,0 -26553,52151805,Woks para uso doméstico,Wok chino para uso doméstico,0,80509,0 -26554,52151806,Vaporeras para uso doméstico,,0,80509,0 -26555,52151807,Ollas para uso doméstico,,0,80509,0 -26556,52151808,Ollas a presión para uso doméstico,Olla exprés para uso doméstico,0,80509,0 -26557,52151809,Sartenes para sofreír para uso doméstico,,0,80509,0 -26558,52151810,Samovares para uso doméstico,Tetera rusa para uso doméstico,0,80509,0 -26559,52151811,Planchas para uso doméstico,,0,80509,0 -26560,52151812,Cacerolas para baño maría,,0,80509,0 -26561,52151813,Protector de salpicaduras para uso doméstico,,0,80509,0 -26562,52151900,Utensilios para hornear domésticos,,0,80509,0 -26563,52151901,Moldes para muffins para uso doméstico,,0,80509,0 -26564,52151902,Cacerola para hornear para uso doméstico,,0,80509,0 -26565,52151903,Moldes para ponqués o pies para uso doméstico,Moldes para panqué o pay para uso doméstico,0,80509,0 -26566,52151905,Bandejas de horno para uso doméstico,,0,80509,0 -26567,52151906,Cacerolas para parrilla para uso doméstico,,0,80509,0 -26568,52151907,Moldes para hornear para uso doméstico,,0,80509,0 -26569,52151908,Cacerolas para pizza para uso doméstico,,0,80509,0 -26570,52151909,Cacerolas para hacer tortillas para uso doméstico,,0,80509,0 -26571,52152000,"Platos, utensilios para servir y recipientes para almacenar",Biberones o mamilas para bebé,0,80509,0 -26572,52152001,Jarras para uso doméstico,,0,80509,0 -26573,52152002,Contenedores para almacenar alimentos para uso doméstico,,0,80509,0 -26574,52152003,Tazones de ponche para uso doméstico,Jarros de ponche para uso doméstico,0,80509,0 -26575,52152004,Platos para uso doméstico,,0,80509,0 -26576,52152005,Platos pequeños para uso doméstico,,0,80509,0 -26577,52152006,Bandejas o fuentes para uso doméstico,,0,80509,0 -26578,52152007,Tazones para servir para uso doméstico,,0,80509,0 -26579,52152008,Teteras o cafeteras para uso doméstico,,0,80509,0 -26580,52152009,Soperas o ensaladeras para uso doméstico,,0,80509,0 -26581,52152010,Frascos al vacío para uso doméstico,,0,80509,0 -26582,52152011,Partes internas de frascos al vacío,,0,80509,0 -26583,52152012,Cubetas para hielo,Charola para cubos de hielo,0,80509,0 -26584,52152013,"Saleros, pimenteros o especieros",,0,80509,0 -26585,52152014,Sets de garrafas,,0,80509,0 -26586,52152015,Plato para ponqués con tapa para uso doméstico,Plato de pastel con tapa para uso doméstico,0,80509,0 -26587,52152016,Set de servicio de mesa para uso doméstico,,0,80509,0 -26588,52152017,Arca de madera para guardar arroz,Caja de madera para almacenamiento de arroz,0,80509,0 -26589,52152018,Loncheras,,0,80509,0 -26590,52152100,Cristalería de uso doméstico,,0,80509,0 -26591,52152101,Tazas de café o té para uso doméstico,,0,80509,0 -26592,52152102,Vasos para beber para uso doméstico,,0,80509,0 -26593,52152103,Tazones (“mugs”) para uso doméstico,,0,80509,0 -26594,52152104,Copas para uso doméstico,,0,80509,0 -26595,52152200,Lavado de platos y accesorios de almacenamiento de platos,,0,80509,0 -26596,52152201,Papel para forrar repisas,,0,80509,0 -26597,52152202,Escurridor de platos,,0,80509,0 -26598,52152203,Cepillo dispensador de jabón,,0,80509,0 -26599,52152204,Mesa para preparar alimentos,,0,80509,0 -26600,52152300,Suministros de cocina doméstica,,0,80509,0 -26601,52152301,Líquido limpiador de hornos para uso doméstico,,0,80509,0 -26602,52161500,Equipos audiovisuales,,0,80509,0 -26603,52161502,Reproductor de casetes o grabadora,,0,80509,0 -26604,52161505,Televisores,,0,80509,0 -26605,52161507,Radios reloj,,0,80509,0 -26606,52161508,Reproductores de discos laser,,0,80509,0 -26607,52161509,Sistemas de estéreo portátiles,,0,80509,0 -26608,52161510,Sistemas de audio de alta fidelidad para el hogar,,0,80509,0 -26609,52161511,Radios,,0,80509,0 -26610,52161512,Altoparlantes,Altavoz,0,80509,0 -26611,52161513,"Combinación de televisor, vhs y grabadora dvd",,0,80509,0 -26612,52161514,Audífonos,,0,80509,0 -26613,52161515,Reproductores o grabadoras de discos compactos,,0,80509,0 -26614,52161516,Reproductores o grabadoras de video discos digitales,,0,80509,0 -26615,52161517,Ecualizadores,,0,80509,0 -26616,52161518,Receptores de sistemas de posicionamiento global,,0,80509,0 -26617,52161520,Micrófonos,,0,80509,0 -26618,52161521,Receptores de multimedia,,0,80509,0 -26619,52161522,Escáneres de radio frecuencia,,0,80509,0 -26620,52161523,Transmisores o receptores de radio frecuencia,,0,80509,0 -26621,52161524,Receptores de radio,,0,80509,0 -26622,52161525,Control remoto,,0,80509,0 -26623,52161526,Receptores de satélite,,0,80509,0 -26624,52161527,Altavoces activos “subwoofer””,,0,80509,0 -26625,52161529,Reproductores o grabadoras de video casetes,,0,80509,0 -26626,52161531,Radio fonógrafos,,0,80509,0 -26627,52161532,Sistemas de karaoke,,0,80509,0 -26628,52161533,Megáfonos,,0,80509,0 -26629,52161534,Grabadora de chip de circuito integrado ic,,0,80509,0 -26630,52161535,Grabadoras de voz digitales,,0,80509,0 -26631,52161536,Grabadoras o reproductores de mini discos,,0,80509,0 -26632,52161537,Borradores magnéticos de almacenamiento de medios,,0,80509,0 -26633,52161538,Aparatos para rebobinar video cintas,,0,80509,0 -26634,52161539,"Combinación de reproductor de video disco digital dvd, disco video casete vcd, disco compacto cd",,0,80509,0 -26635,52161540,Mezcladores de video,,0,80509,0 -26636,52161541,Mezcladores de audio,,0,80509,0 -26637,52161542,Pantallas de plasma,,0,80509,0 -26638,52161543,Reproductores o grabadoras mp3,,0,80509,0 -26639,52161544,Borradores de casetes de audio o video,,0,80509,0 -26640,52161545,Grabadoras de video digital,,0,80509,0 -26641,52161546,Sintonizadores de televisión,,0,80509,0 -26642,52161547,Amplificadores de audio,,0,80509,0 -26643,52161548,Consola de audio,,0,80509,0 -26644,52161549,Marco para fotografías digitales,,0,80509,0 -26645,52161550,Video grabadora personal pvr,,0,80509,0 -26646,52161551,Micrófono inalámbrico y sistema de amplificación de instrumentos,,0,80509,0 -26647,52161552,Receptor de luz,,0,80509,0 -26648,52161553,Reproductor o grabadora de video disco blu ray,,0,80509,0 -26649,52161554,Audífono,Manos libres,0,80509,0 -26650,52161555,Set combinado portátil de video multimedia,,0,80509,0 -26651,52161556,Distribuidor de video,,0,80509,0 -26652,52161557,Consola fija de juegos de computador,,0,80509,0 -26653,52161558,Consola portátil de juegos de computador,,0,80509,0 -26654,52161559,Gafas tridimensionales 3d para uso con televisores en 3d y computadoras equipadas con gráficos en 3d,,0,80509,0 -26655,52161560,Sistema de cine en casa,,0,80509,0 -26656,52161600,Accesorios de equipo audiovisual,,0,80509,0 -26657,52161601,Almacenador de casetes,,0,80509,0 -26658,52161602,Limpiadores de cabezas de audio o video,,0,80509,0 -26659,52161603,Adaptador de video casetes compactos,,0,80509,0 -26660,52161604,Adaptadores de enchufes hembra de audífonos,,0,80509,0 -26661,52161605,Accesorios para reproductores portátiles de medios,,0,80509,0 -26662,52161606,Estuches o cubiertas para altavoces,,0,80509,0 -26663,52161607,Dispositivo para almacenar discos de vinilo,,0,80509,0 -26664,52161608,Caja divisora (“splitter”) scart,,0,80509,0 -26665,52161609,Amplificador de señal de discos de vinilo,,0,80509,0 -26666,52161610,Soporte para parlantes,,0,80509,0 -26667,52161611,Soporte para micrófonos,,0,80509,0 -26668,52171000,Tratamientos de pared de baño,,0,80509,0 -26669,52171001,Repisa u organizador colgante para elementos de aseo personal,,0,80509,0 -26670,52171002,Espejo para baño,,0,80509,0 -26671,53101500,"Pantalones de deporte, pantalones y pantalones cortos",Prendas de vestir,0,80509,0 -26672,53101501,Pantalones largos o cortos o pantalonetas para niño,"Pants, pantalones y shorts para niño",0,80509,0 -26673,53101502,Pantalones largos o cortos o pantalonetas para hombre,"Pants, pantalones y shorts para hombre",0,80509,0 -26674,53101503,Pantalones largos o cortos o pantalonetas para niña,"Pants, pantalones y shorts para niña",0,80509,0 -26675,53101504,Pantalones largos o cortos o pantalonetas para mujer,"Pants, pantalones y shorts para mujer",0,80509,0 -26676,53101505,Pantalones largos o cortos o pantalonetas para bebés,"Pants, pantalones y shorts para bebés",0,80509,0 -26677,53101600,Faldas y blusas,Prendas de vestir,0,80509,0 -26678,53101601,Camisas para niño,Playeras para niño,0,80509,0 -26679,53101602,Camisas para hombre,Playeras para hombre,0,80509,0 -26680,53101603,Camisas o blusas para niña,Playeras para niña,0,80509,0 -26681,53101604,Camisas o blusas para mujer,Playeras para mujer,0,80509,0 -26682,53101605,Camisas o blusas para bebé,Playeras para bebé,0,80509,0 -26683,53101700,Suéteres,Prendas de vestir,0,80509,0 -26684,53101701,Sweaters para niño,Suéteres para niño,0,80509,0 -26685,53101702,Sweaters para hombre,Suéteres para hombre,0,80509,0 -26686,53101703,Sweaters para niña,Suéteres para niña,0,80509,0 -26687,53101704,Sweaters para mujer,Suéteres para mujer,0,80509,0 -26688,53101705,Sweaters para bebé,Suéteres para bebé,0,80509,0 -26689,53101800,Abrigos y chaquetas,Prendas de vestir,0,80509,0 -26690,53101801,Abrigos o chaquetas para niño,Chamarras para niño,0,80509,0 -26691,53101802,Abrigos o chaquetas para hombre,Chamarras para hombre,0,80509,0 -26692,53101803,Abrigos o chaquetas para niña,Chamarras para niña,0,80509,0 -26693,53101804,Abrigos o chaquetas para mujer,Chamarras para mujer,0,80509,0 -26694,53101805,Abrigos o chaquetas para bebé,Chamarras para bebé,0,80509,0 -26695,53101900,Trajes,Prendas de vestir,0,80509,0 -26696,53101901,Trajes para niño,,0,80509,0 -26697,53101902,Trajes para hombre,,0,80509,0 -26698,53101903,Trajes para niña,,0,80509,0 -26699,53101904,Trajes para mujer,,0,80509,0 -26700,53101905,Trajes para bebé,,0,80509,0 -26701,53102000,"Vestidos, faldas, saris y kimonos",Prendas de vestir,0,80509,0 -26702,53102001,Vestidos o faldas o saris o kimonos para niña,,0,80509,0 -26703,53102002,Vestidos o faldas o saris o kimonos para para mujer,,0,80509,0 -26704,53102003,Vestidos o faldas o saris o kimonos para bebé,,0,80509,0 -26705,53102100,Batas y monos,Prendas de vestir,0,80509,0 -26706,53102101,Overoles o monos para niño,,0,80509,0 -26707,53102102,Overoles o monos para hombre,,0,80509,0 -26708,53102103,Overoles o monos para niña,,0,80509,0 -26709,53102104,Overoles o monos para mujer,,0,80509,0 -26710,53102105,Overoles o monos para bebé,,0,80509,0 -26711,53102200,Ropa folclórica,Prendas de vestir,0,80509,0 -26712,53102201,Vestidos folclóricos para niño,,0,80509,0 -26713,53102202,Vestidos folclóricos para hombre,,0,80509,0 -26714,53102203,Vestidos folclóricos para niña,,0,80509,0 -26715,53102204,Vestidos folclóricos para mujer,,0,80509,0 -26716,53102205,Vestidos folclóricos para bebé,,0,80509,0 -26717,53102300,Ropa interior,Prendas de vestir,0,80509,0 -26718,53102301,Camisetas interiores,,0,80509,0 -26719,53102302,Combinaciones,Braslis o trusa,0,80509,0 -26720,53102303,Calzoncillos,,0,80509,0 -26721,53102304,Brassieres,,0,80509,0 -26722,53102305,Pañales para bebé,,0,80509,0 -26723,53102306,Pañales para adulto,,0,80509,0 -26724,53102307,Prendas para dar forma al cuerpo,Fajas para cuerpo,0,80509,0 -26725,53102308,Forros para pañales,,0,80509,0 -26726,53102309,Rellenos para busto,,0,80509,0 -26727,53102310,Forros para rellenos para busto,,0,80509,0 -26728,53102400,Medias y calcetines,Prendas de vestir,0,80509,0 -26729,53102401,Medias largas,,0,80509,0 -26730,53102402,Calcetines,,0,80509,0 -26731,53102403,Medias veladas,,0,80509,0 -26732,53102404,Medias veladas gruesas (“tights”),,0,80509,0 -26733,53102500,Accesorios de vestir,Prendas de vestir,0,80509,0 -26734,53102501,Cinturones o tirantes,,0,80509,0 -26735,53102502,Corbatas o pañoletas o bufandas,,0,80509,0 -26736,53102503,Sombreros,,0,80509,0 -26737,53102504,Guantes o mitones,,0,80509,0 -26738,53102505,Sombrillas,,0,80509,0 -26739,53102506,Bandas para el sudor,,0,80509,0 -26740,53102507,Ganchos para colgar la ropa,,0,80509,0 -26741,53102508,Bandas para los brazos,,0,80509,0 -26742,53102509,Ligas,,0,80509,0 -26743,53102510,Borlas,,0,80509,0 -26744,53102511,Badanas,Pañuelo para la cabeza,0,80509,0 -26745,53102512,Pañuelos,,0,80509,0 -26746,53102513,Bandas para la cabeza,,0,80509,0 -26747,53102514,Protectores de bolsillos,,0,80509,0 -26748,53102515,Forros para botones,,0,80509,0 -26749,53102516,Gorras,,0,80509,0 -26750,53102517,Sujeta corbatas,,0,80509,0 -26751,53102518,Cheurones,Chevron,0,80509,0 -26752,53102519,Golas,Bufandas con gorros o pecheras,0,80509,0 -26753,53102520,Hombreras o charreteras,,0,80509,0 -26754,53102600,Ropa de dormir,Prendas de vestir,0,80509,0 -26755,53102601,Pijamas o camisas de dormir o batas para niño,,0,80509,0 -26756,53102602,Pijamas o camisas de dormir o batas para hombre,,0,80509,0 -26757,53102603,Pijamas o camisas de dormir o batas para niña,,0,80509,0 -26758,53102604,Pijamas o camisas de dormir o batas para mujer,,0,80509,0 -26759,53102605,Pijamas o camisas de dormir o batas para bebé,,0,80509,0 -26760,53102606,Batas de baño,,0,80509,0 -26761,53102700,Uniformes,Prendas de vestir,0,80509,0 -26762,53102701,Uniformes militares,Trajes militares,0,80509,0 -26763,53102702,Uniformes de agentes de aduna,Trajes para agentes aduanales,0,80509,0 -26764,53102703,Uniformes de policías,Trajes de policías,0,80509,0 -26765,53102704,Uniformes institucionales para preparación de alimentos o servicio,,0,80509,0 -26766,53102705,Uniformes de colegio,,0,80509,0 -26767,53102706,Uniformes de personal de seguridad,,0,80509,0 -26768,53102707,Batas de doctor,,0,80509,0 -26769,53102708,Uniformes de enfermera,,0,80509,0 -26770,53102709,Uniformes de personal de ambulancias,,0,80509,0 -26771,53102710,Uniformes corporativos,,0,80509,0 -26772,53102711,Batas para personal de peluquerías,,0,80509,0 -26773,53102712,Uniformes de paramédicos,,0,80509,0 -26774,53102713,Uniformes de acomodadores,,0,80509,0 -26775,53102714,Togas judiciales,,0,80509,0 -26776,53102715,Uniformes de oficiales de cárceles,,0,80509,0 -26777,53102716,Uniformes de presos,,0,80509,0 -26778,53102800,Trajes de baño,Prendas de vestir,0,80509,0 -26779,53102801,Vestidos de baño para hombre,Trajes de baño para hombre,0,80509,0 -26780,53102802,Vestidos de baño para mujer,Trajes de baño para mujer,0,80509,0 -26781,53102803,Vestidos de baño para niño,Trajes de baño para niño,0,80509,0 -26782,53102804,Vestidos de baño para niña,Trajes de baño para niña,0,80509,0 -26783,53102805,Vestidos de baño para bebé,Trajes de baño para bebé,0,80509,0 -26784,53102900,Prendas de deporte,Prendas de vestir,0,80509,0 -26785,53102901,Ropa atlética para mujer,Ropa deportiva para mujer,0,80509,0 -26786,53102902,Ropa atlética para hombre,Ropa deportiva para hombre,0,80509,0 -26787,53102903,Ropa atlética para niño,Ropa deportiva para niño,0,80509,0 -26788,53102904,Ropa atlética para niña,Ropa deportiva para niña,0,80509,0 -26789,53103000,Camisetas,Prendas de vestir,0,80509,0 -26790,53103001,Camisetas (t-shirts) para hombre,,0,80509,0 -26791,53103100,Chalecos,Prendas de vestir,0,80509,0 -26792,53103101,Chalecos para hombre,,0,80509,0 -26793,53103200,Ropas desechables,Prendas de vestir,0,80509,0 -26794,53103201,Bata de trabajo desechable,,0,80509,0 -26795,53111500,Botas,,0,80509,0 -26796,53111501,Botas para hombre,,0,80509,0 -26797,53111502,Botas para mujer,,0,80509,0 -26798,53111503,Botas para niño,,0,80509,0 -26799,53111504,Botas para niña,,0,80509,0 -26800,53111505,Botas para bebé,,0,80509,0 -26801,53111600,Zapatos,,0,80509,0 -26802,53111601,Zapatos para hombre,,0,80509,0 -26803,53111602,Zapatos para mujer,,0,80509,0 -26804,53111603,Zapatos para niño,,0,80509,0 -26805,53111604,Zapatos para niña,,0,80509,0 -26806,53111605,Zapatos para bebé,,0,80509,0 -26807,53111700,Zapatillas,,0,80509,0 -26808,53111701,Pantuflas para hombre,,0,80509,0 -26809,53111702,Pantuflas para mujer,,0,80509,0 -26810,53111703,Pantuflas para niño,,0,80509,0 -26811,53111704,Pantuflas para niña,,0,80509,0 -26812,53111705,Pantuflas para bebé,,0,80509,0 -26813,53111800,Sandalias,,0,80509,0 -26814,53111801,Sandalias para hombre,,0,80509,0 -26815,53111802,Sandalias para mujer,,0,80509,0 -26816,53111803,Sandalias para niño,,0,80509,0 -26817,53111804,Sandalias para niña,,0,80509,0 -26818,53111805,Sandalias para bebé,,0,80509,0 -26819,53111900,Calzado deportivo,,0,80509,0 -26820,53111901,Calzado atlético para hombre,Calzado deportivo para hombre,0,80509,0 -26821,53111902,Calzado atlético para mujer,Calzado deportivo para mujer,0,80509,0 -26822,53111903,Calzado atlético para niño,Calzado deportivo para niño,0,80509,0 -26823,53111904,Calzado atlético para niña,Calzado deportivo para niña,0,80509,0 -26824,53111905,Calzado atlético para bebé,Calzado deportivo para bebé,0,80509,0 -26825,53112000,Accesorios para el calzado,,0,80509,0 -26826,53112001,Calzadores,,0,80509,0 -26827,53112002,Cordones para zapatos,Agujetas para zapatos,0,80509,0 -26828,53112003,Almohadillas para talones,,0,80509,0 -26829,53112005,Aparato para medir el pie,,0,80509,0 -26830,53112100,Chanclos,Choclo,0,80509,0 -26831,53112101,Zapatones para hombre,Choclo para hombre,0,80509,0 -26832,53112102,Zapatones para mujer,Choclo para mujer,0,80509,0 -26833,53112103,Zapatones para niño,Choclo para niño,0,80509,0 -26834,53112104,Zapatones para niña,Choclo para niña,0,80509,0 -26835,53112105,Zapatones para bebé,Choclo para bebé,0,80509,0 -26836,53121500,Maletas,,0,80509,0 -26837,53121501,Porta trajes,,0,80509,0 -26838,53121502,Sets de maletas,,0,80509,0 -26839,53121503,Piezas individuales de maletas,,0,80509,0 -26840,53121600,"Monederos, bolsos de mano y bolsas",,0,80509,0 -26841,53121601,Bolsos o carteras,,0,80509,0 -26842,53121602,Tulas,,0,80509,0 -26843,53121603,Morrales,,0,80509,0 -26844,53121605,Monederos,,0,80509,0 -26845,53121606,Estuches para labiales,,0,80509,0 -26846,53121607,Cigarrilleras,,0,80509,0 -26847,53121608,Bolsas para compras,,0,80509,0 -26848,53121700,Carteras,,0,80509,0 -26849,53121701,Maletines,,0,80509,0 -26850,53121702,Maletines (“attaches”),,0,80509,0 -26851,53121704,Portafolios,,0,80509,0 -26852,53121705,Estuches para equipos,,0,80509,0 -26853,53121706,Maletines para computador,,0,80509,0 -26854,53121800,Juegos y accesorios de viaje,,0,80509,0 -26855,53121801,Kits de viaje,,0,80509,0 -26856,53121802,Carritos de viaje,,0,80509,0 -26857,53121803,Cepillos de ropa,,0,80509,0 -26858,53121804,Estuches de maquillaje o manicure,,0,80509,0 -26859,53131500,Dentales,,0,80509,0 -26860,53131501,Enjuague bucal,,0,80509,0 -26861,53131502,Dentífrico,"Pasta de dientes, Pasta dental, Pasta para diente",0,80509,0 -26862,53131503,Cepillos de dientes,,0,80509,0 -26863,53131504,Seda dental,Hilo dental,0,80509,0 -26864,53131505,Chupos o chupetes para bebé,Chupones para bebé,0,80509,0 -26865,53131506,Kits dentales,,0,80509,0 -26866,53131507,Palillos de dientes (mondadientes),,0,80509,0 -26867,53131508,Tabletas para limpiar dentaduras postizas,,0,80509,0 -26868,53131509,Refrescadores de aliento,Refrescantes para boca,0,80509,0 -26869,53131510,Pegante para dentaduras postizas,"Adhesivo de dentadura, Pegamento para dentaduras postizas",0,80509,0 -26870,53131600,Baño y cuerpo,,0,80509,0 -26871,53131601,Gorros de baño,,0,80509,0 -26872,53131602,Artículos para el cuidado del cabello,"Acondicionadores, Cremas para peinar, Desenredadores, Gel, Mouse, Spray",0,80509,0 -26873,53131603,Afeitadoras,Rastrillos,0,80509,0 -26874,53131604,Cepillos o peinillas para el cabello,Peines para el cabello,0,80509,0 -26875,53131605,Kits de tocador,,0,80509,0 -26876,53131606,Desodorantes,,0,80509,0 -26877,53131607,Lociones o aceites para manos o cuerpo,,0,80509,0 -26878,53131608,Jabones,,0,80509,0 -26879,53131609,Productos de protección solar,,0,80509,0 -26880,53131610,Artículos para el cuidado de los ojos,,0,80509,0 -26881,53131611,Cremas de afeitar,,0,80509,0 -26882,53131612,Geles de baño,,0,80509,0 -26883,53131613,Productos para el cuidado de la piel,"Bloqueadores solares, Cremas, Exfoliantes, Lociones corporales, Zacate o mallas para baño",0,80509,0 -26884,53131614,Productos para el cuidado de los pies,Talcos,0,80509,0 -26885,53131615,Productos para la higiene femenina,"Desodorantes, Jabones, Tampones, Toallas sanitarias ,",0,80509,0 -26886,53131616,Cremas o lociones para farmacéuticas,,0,80509,0 -26887,53131617,Implementos para la manicure,,0,80509,0 -26888,53131618,Implementos para la pedicura,,0,80509,0 -26889,53131619,Cosméticos,,0,80509,0 -26890,53131620,Perfumes o colonias o fragancias,,0,80509,0 -26891,53131621,Corta uñas,,0,80509,0 -26892,53131622,Condones,Preservativos,0,80509,0 -26893,53131623,Productos depilatorios o para remover vello,,0,80509,0 -26894,53131624,Paños limpiadores desechables,,0,80509,0 -26895,53131625,Redecillas para el cabello o la barba,,0,80509,0 -26896,53131626,Desinfectante de manos,Gel antibacterial para manos,0,80509,0 -26897,53131627,Limpiador de manos,,0,80509,0 -26898,53131628,Champús,Shampoo,0,80509,0 -26899,53131629,Kits de maquillaje,,0,80509,0 -26900,53131630,Bálsamo labial,,0,80509,0 -26901,53131631,Tatuajes,,0,80509,0 -26902,53131632,Marrones calientes,Tubos calientes para cabello,0,80509,0 -26903,53131633,Hebillas para el pelo,"Broches para el cabello, Pasadores para el cabello",0,80509,0 -26904,53131634,Productos químicos protectores,,0,80509,0 -26905,53131635,Brochas de afeitar,,0,80509,0 -26906,53131636,Agua de rosas,,0,80509,0 -26907,53131637,Almohadillas de lactancia,,0,80509,0 -26908,53131638,Esmalte para uñas,,0,80509,0 -26909,53131639,Protectores para incontinencia urinaria,,0,80509,0 -26910,53131640,Removedor de esmalte,Acetona para las uñas,0,80509,0 -26911,53131641,Gelatina de petróleo,Vaselina,0,80509,0 -26912,53131642,Bata o capa para cortar el pelo para barberías o peluquerías,,0,80509,0 -26913,53131643,Cortador de pelo eléctrico,Rasuradora eléctricas para cabello,0,80509,0 -26914,53131644,Aceites esenciales de aromaterapia,,0,80509,0 -26915,53131645,Instrumento o pistola para perforar el lóbulo de la oreja,,0,80509,0 -26916,53131646,Removedor de espinillas,,0,80509,0 -26917,53131647,Set de higiene y cuidado personal de bebés,Talcos para bebé,0,80509,0 -26918,53131700,Equipo para masaje terapéutico,,0,80509,0 -26919,53131701,Unidad masajeadora manual,,0,80509,0 -26920,53131702,Máquina masajeadora,,0,80509,0 -26921,53131800,Accesorios y materiales para equipo de tatuajes,,0,80509,0 -26922,53131801,Tinta para tatuajes,,0,80509,0 -26923,53131802,Aguja para tatuajes,,0,80509,0 -26924,53141500,Afianzadores de costura,Artículos de mercería,0,80509,0 -26925,53141501,Alfileres rectos,,0,80509,0 -26926,53141502,Imperdibles,"Seguritos, Seguros para ropa",0,80509,0 -26927,53141503,Cremalleras,Cierres,0,80509,0 -26928,53141504,Hebillas,,0,80509,0 -26929,53141505,Botones,,0,80509,0 -26930,53141506,Cierres,Cierres o broches de presión,0,80509,0 -26931,53141507,Broches,,0,80509,0 -26932,53141508,Remaches para ropa,,0,80509,0 -26933,53141600,Suministros de costura variados,Artículos de mercería,0,80509,0 -26934,53141601,Alfileteros,,0,80509,0 -26935,53141602,Kits de costura,,0,80509,0 -26936,53141603,Dedales,,0,80509,0 -26937,53141604,Patrones de costura,,0,80509,0 -26938,53141605,Agujas de costura,,0,80509,0 -26939,53141606,Bobinas o sujeta bobinas,,0,80509,0 -26940,53141607,Indicadores de costura,,0,80509,0 -26941,53141608,Aguja pasa cintas,,0,80509,0 -26942,53141609,Pasa presillas,,0,80509,0 -26943,53141610,Sujetadores de tiza para sastres o textiles,,0,80509,0 -26944,53141611,Marcadores de textiles o lápices para textiles o tiza para textiles,,0,80509,0 -26945,53141612,Rueda aserrada seguidora de patrones,,0,80509,0 -26946,53141613,Papel calcante,Papel transfer para estampar,0,80509,0 -26947,53141614,Agujas de bordado,,0,80509,0 -26948,53141615,Agujas de tejer,,0,80509,0 -26949,53141616,Lanzaderas para telares,,0,80509,0 -26950,53141617,Lanzaderas para tejidos de algodón,,0,80509,0 -26951,53141618,Lienzo para bordar,Telas para bordar,0,80509,0 -26952,53141619,Varas magnéticas,,0,80509,0 -26953,53141620,Corta costuras,,0,80509,0 -26954,53141621,Enhebrador de agujas,,0,80509,0 -26955,53141622,Regla para sastrería,,0,80509,0 -26956,53141623,Refuerzo o cerrado líquido para hilo,,0,80509,0 -26957,53141624,Textiles o agujas para punto de cruz,,0,80509,0 -26958,53141625,Diseños para punto de cruz,,0,80509,0 -26959,53141626,Aro para bordado,,0,80509,0 -26960,53141627,Agujas de croché,,0,80509,0 -26961,53141628,Herramientas para hilvanar acolchados,,0,80509,0 -26962,53141629,Alfileres para acolchados,,0,80509,0 -26963,53141630,Tableros o almohadillas para cortar patrones,,0,80509,0 -26964,54101500,Joyería fina,,0,80509,0 -26965,54101501,Cadenas de oro o plata o platino,,0,80509,0 -26966,54101502,Collares de joyería fina,,0,80509,0 -26967,54101503,Anillos de joyería fina,,0,80509,0 -26968,54101504,Aretes de joyería fina,,0,80509,0 -26969,54101505,Joyas finas para el cuerpo,,0,80509,0 -26970,54101506,Brazaletes de joyería fina,,0,80509,0 -26971,54101507,Tiaras,,0,80509,0 -26972,54101508,Revestimientos de joyería fina para reducir el tamaño de un anillo,,0,80509,0 -26973,54101509,Mancornas de joyería fina,,0,80509,0 -26974,54101510,Mariposas para aretes de joyería fina,,0,80509,0 -26975,54101511,Prendedores de joyería fina,,0,80509,0 -26976,54101512,Aros de prendedores para pasar cadenas de joyería fina,,0,80509,0 -26977,54101513,Pisacorbatas,,0,80509,0 -26978,54101600,Bisutería,,0,80509,0 -26979,54101601,Brazaletes,,0,80509,0 -26980,54101602,Collares,,0,80509,0 -26981,54101603,Anillos,,0,80509,0 -26982,54101604,Aretes,,0,80509,0 -26983,54101605,Joyas para el cuerpo,,0,80509,0 -26984,54101700,Herramientas y materiales de joyería,,0,80509,0 -26985,54101701,Compuestos conservantes,,0,80509,0 -26986,54101702,Bloques para producir hemisferios (“dapping punches”),,0,80509,0 -26987,54101703,Molinos de alambre,,0,80509,0 -26988,54101704,Mandriles para joyería,,0,80509,0 -26989,54101705,Medidores de tamaño para anillos,,0,80509,0 -26990,54101706,Materiales o accesorios plásticos amigables maleables a bajas temperaturas,,0,80509,0 -26991,54111500,Relojes de pulsera o bolsillo,,0,80509,0 -26992,54111501,Relojes de pulso,,0,80509,0 -26993,54111502,Relojes de bolsillo,,0,80509,0 -26994,54111503,Relojes cronómetros,,0,80509,0 -26995,54111504,Cronómetros marinos,,0,80509,0 -26996,54111505,Relojes para bucear,,0,80509,0 -26997,54111600,Relojes de pared o de mesa,,0,80509,0 -26998,54111601,Relojes de pared,,0,80509,0 -26999,54111602,Relojes de mesa o repisa,,0,80509,0 -27000,54111603,Relojes verticales,,0,80509,0 -27001,54111604,Relojes de arena,,0,80509,0 -27002,54111605,Relojes maestros electrónicos,,0,80509,0 -27003,54111606,Relojes esclavos electrónicos,,0,80509,0 -27004,54111700,Piezas de relojes de pulsera o de pared o pie o accesorios,,0,80509,0 -27005,54111701,Manecillas para reloj,,0,80509,0 -27006,54111702,Cristales para reloj,,0,80509,0 -27007,54111703,Placas o puentes para relojes,,0,80509,0 -27008,54111704,Correas o bandas o pulseras para relojes,,0,80509,0 -27009,54111705,Estuches para relojes,,0,80509,0 -27010,54111706,Sujetadores para relojes,,0,80509,0 -27011,54111707,Abre estuches para relojes,,0,80509,0 -27012,54111708,Péndulos para relojes,,0,80509,0 -27013,54111709,Kits para reparar relojes,,0,80509,0 -27014,54121500,Piedras preciosas,,0,80509,0 -27015,54121501,Diamantes,,0,80509,0 -27016,54121502,Esmeraldas,,0,80509,0 -27017,54121503,Rubíes,,0,80509,0 -27018,54121504,Zafiros,,0,80509,0 -27019,54121600,Piedras semipreciosas,,0,80509,0 -27020,54121601,Granates,,0,80509,0 -27021,54121602,Jades,,0,80509,0 -27022,54121603,Ópalos,,0,80509,0 -27023,54121700,Perlas,,0,80509,0 -27024,54121701,Perlas cultivadas,,0,80509,0 -27025,54121702,Perlas naturales,,0,80509,0 -27026,55101500,Publicaciones impresas,,0,80509,0 -27027,55101501,Cartas de navegación o atlas o mapas,,0,80509,0 -27028,55101502,Directorios,,0,80509,0 -27029,55101503,Catálogos,,0,80509,0 -27030,55101504,Periódicos,,0,80509,0 -27031,55101506,Revistas,,0,80509,0 -27032,55101507,Libros para pintar o colorear o ilustrar para niños,,0,80509,0 -27033,55101509,Textos educacionales o vocacionales,,0,80509,0 -27034,55101510,Libros para entretenimiento,,0,80509,0 -27035,55101513,Tarjetas para intercambiar,,0,80509,0 -27036,55101514,Partituras,Libro de notas musicales,0,80509,0 -27037,55101515,Material promocional o reportes anuales,,0,80509,0 -27038,55101516,Manuales operativos o de instrucciones,,0,80509,0 -27039,55101517,Dibujos dimensionales o de tolerancia,,0,80509,0 -27040,55101518,Diagramas o dibujos técnicos,,0,80509,0 -27041,55101519,Publicaciones periódicas,,0,80509,0 -27042,55101520,Hojas o folletos de instrucciones,,0,80509,0 -27043,55101521,Manuales de propietario o usuario,,0,80509,0 -27044,55101522,Globos terrestres o celestes,,0,80509,0 -27045,55101523,Libros de ejercicios,,0,80509,0 -27046,55101524,Libros de referencia,,0,80509,0 -27047,55101525,Enciclopedias,,0,80509,0 -27048,55101526,Diccionarios,,0,80509,0 -27049,55101527,Cancioneros,,0,80509,0 -27050,55101528,Libros religiosos,,0,80509,0 -27051,55101529,Chequeras o libretas de cuentas,,0,80509,0 -27052,55101530,Libros de códigos,,0,80509,0 -27053,55101531,Códigos (legales),,0,80509,0 -27054,55101532,Álbumes de graduación o anuarios,,0,80509,0 -27055,55111500,Publicaciones electrónicas y música,,0,80509,0 -27056,55111501,Directorios electrónicos,,0,80509,0 -27057,55111502,Diccionarios electrónicos,,0,80509,0 -27058,55111503,Enciclopedias electrónicas,,0,80509,0 -27059,55111504,Catálogos electrónicos,,0,80509,0 -27060,55111505,Libros en cintas o en discos compactos,,0,80509,0 -27061,55111506,Revistas electrónicas,,0,80509,0 -27062,55111507,Periódicos electrónicos,,0,80509,0 -27063,55111508,Cartas de navegación o mapas o atlas electrónicos,,0,80509,0 -27064,55111509,Música de fondo,,0,80509,0 -27065,55111510,Películas de cine en celuloide,,0,80509,0 -27066,55111511,Películas de cine en video cinta,,0,80509,0 -27067,55111512,Música en cintas o discos compactos,,0,80509,0 -27068,55111513,Textos electrónicos educacionales o vocacionales,,0,80509,0 -27069,55111514,Películas de cine en discos de video digital dvd,,0,80509,0 -27070,55111515,Audio cinta de material educacional,,0,80509,0 -27071,55121500,Rótulos,,0,80509,0 -27072,55121501,Etiquetas para el equipaje,,0,80509,0 -27073,55121502,Etiquetas de seguridad,,0,80509,0 -27074,55121503,Etiquetas de identificación,,0,80509,0 -27075,55121504,Etiquetas para llaves,,0,80509,0 -27076,55121505,Porta etiquetas o accesorios,,0,80509,0 -27077,55121506,Etiquetas de precio,,0,80509,0 -27078,55121507,Etiquetas de bolsas de té o café,,0,80509,0 -27079,55121600,Etiquetas,,0,80509,0 -27080,55121601,Kits para remover etiquetas,,0,80509,0 -27081,55121602,Etiquetas para ropa,,0,80509,0 -27082,55121604,Etiquetas para latas o botellas,,0,80509,0 -27083,55121605,Etiquetas de direcciones o de correo,,0,80509,0 -27084,55121606,Etiquetas auto adhesivas,,0,80509,0 -27085,55121607,Calcomanías,,0,80509,0 -27086,55121608,Etiquetas de códigos de barra,,0,80509,0 -27087,55121609,Etiquetas para paquetes,,0,80509,0 -27088,55121610,Etiquetas numeradas consecutivamente,,0,80509,0 -27089,55121611,Cintas para hacer etiquetas,,0,80509,0 -27090,55121612,Etiquetas para impresoras,,0,80509,0 -27091,55121613,Etiquetas de códigos de color,,0,80509,0 -27092,55121614,Etiquetas removibles,,0,80509,0 -27093,55121615,Puntos o flechas adhesivas,,0,80509,0 -27094,55121616,Banderas auto adhesivas,,0,80509,0 -27095,55121617,Protectores de etiquetas,,0,80509,0 -27096,55121618,Porta etiquetas,,0,80509,0 -27097,55121619,Etiquetas no adhesivas,,0,80509,0 -27098,55121620,Etiquetas multipropósito,,0,80509,0 -27099,55121621,Sellos notariales,,0,80509,0 -27100,55121622,Etiquetas criogénicas,,0,80509,0 -27101,55121700,Señalización,,0,80509,0 -27102,55121701,Placas con inscripción metálicas,,0,80509,0 -27103,55121702,Placas con inscripción no metálicas,,0,80509,0 -27104,55121703,Señales iluminadas,,0,80509,0 -27105,55121704,Señales de seguridad,,0,80509,0 -27106,55121705,Señales auto adhesivas,,0,80509,0 -27107,55121706,Pancartas,,0,80509,0 -27108,55121707,Señales magnéticas,,0,80509,0 -27109,55121708,Señales de neón,,0,80509,0 -27110,55121710,Signos de tráfico,,0,80509,0 -27111,55121712,Señales direccionales,,0,80509,0 -27112,55121713,Señales de punto de venta,,0,80509,0 -27113,55121714,Pendones,,0,80509,0 -27114,55121715,Banderas o accesorios,,0,80509,0 -27115,55121716,Señales de madera,,0,80509,0 -27116,55121717,Placas de identificación de máquinas,,0,80509,0 -27117,55121718,Señales informativas,,0,80509,0 -27118,55121719,Componentes de señalización,,0,80509,0 -27119,55121720,Emblemas,,0,80509,0 -27120,55121721,Caracteres de señalización,,0,80509,0 -27121,55121725,Kits de señalización,,0,80509,0 -27122,55121726,Paneles de identificación,,0,80509,0 -27123,55121728,Cubiertas de señales,,0,80509,0 -27124,55121729,Indicadores,,0,80509,0 -27125,55121730,Señales de accidente,,0,80509,0 -27126,55121731,Marcadores de identificación,,0,80509,0 -27127,55121732,Divisores de tamaño,,0,80509,0 -27128,55121734,Láminas de material para señales,,0,80509,0 -27129,55121735,Señales temporales,,0,80509,0 -27130,55121800,Documentos de identificación,,0,80509,0 -27131,55121801,Discos de impuestos de vehículos,,0,80509,0 -27132,55121802,Tarjetas o bandas de identificación o productos similares,,0,80509,0 -27133,55121803,Pasaportes,,0,80509,0 -27134,55121804,Gafetes o porta gafetes,,0,80509,0 -27135,55121806,Kits de bandas de identificación personal o accesorios,,0,80509,0 -27136,55121807,Porta productos de identificación o accesorios,,0,80509,0 -27137,55121808,Clip para gafete,,0,80509,0 -27138,55121900,Señalizaciones,,0,80509,0 -27139,55121901,Columnas de publicidad,,0,80509,0 -27140,55121902,Stands o puestos,,0,80509,0 -27141,55121903,Señales de mensajes portátiles,,0,80509,0 -27142,55121904,Carteleras,,0,80509,0 -27143,55121905,Astas o partes o accesorios,,0,80509,0 -27144,55121906,Stands para banderas,,0,80509,0 -27145,55121907,Tableros de avisos,,0,80509,0 -27146,55121908,Soportes o stands para señales,,0,80509,0 -27147,56101500,Muebles,,0,80509,0 -27148,56101501,Stands,"Burós, Esquineros",0,80509,0 -27149,56101502,Sofás,"Salas, Sillones",0,80509,0 -27150,56101503,Armarios para abrigos,,0,80509,0 -27151,56101504,Asientos,,0,80509,0 -27152,56101505,Centros de entretenimiento,,0,80509,0 -27153,56101506,Futones,,0,80509,0 -27154,56101507,Bibliotecas,Libreros,0,80509,0 -27155,56101508,Colchones o sets para dormir,,0,80509,0 -27156,56101509,Vestidores o armarios,"Clóset, Roperos",0,80509,0 -27157,56101510,Divisiones,Muros divisorios,0,80509,0 -27158,56101513,Cunas o accesorios,,0,80509,0 -27159,56101514,Taburetes,Bancos,0,80509,0 -27160,56101515,Camas,,0,80509,0 -27161,56101516,Cómodas,Cajonera,0,80509,0 -27162,56101518,Estanterías de pared,,0,80509,0 -27163,56101519,Mesas,,0,80509,0 -27164,56101520,Casilleros (“lockers”),,0,80509,0 -27165,56101521,Cabeceras o pies de cama,,0,80509,0 -27166,56101522,Sillas de brazos,,0,80509,0 -27167,56101523,Paragüeros o soportes para paraguas,,0,80509,0 -27168,56101524,Mesas de plancha,Tablas o burros para planchar,0,80509,0 -27169,56101525,Forros para mesas de plancha,Forros para tablas o burros para planchar,0,80509,0 -27170,56101526,Bar refrigerador,Frigobar,0,80509,0 -27171,56101527,Secadores de linos tipo hogar,,0,80509,0 -27172,56101528,Plantas artificiales,"Arreglos florales artificiales, Flores artificiales",0,80509,0 -27173,56101529,Revisteros,,0,80509,0 -27174,56101530,Gabinetes de almacenamiento,"Alacenas, Vitrinas",0,80509,0 -27175,56101531,Zapateras,,0,80509,0 -27176,56101532,Set de muebles,"Juego de muebles para baños, Juego de muebles para cocinas, Juego de muebles para jardín, Juego de muebles para recamaras, Juego de muebles para salas",0,80509,0 -27177,56101533,Descansa brazos,,0,80509,0 -27178,56101535,Mesas de ruedas,,0,80509,0 -27179,56101536,Trípodes para instrumentos,"Atriles, Tripie para instrumentos musicales",0,80509,0 -27180,56101537,Tocadores,,0,80509,0 -27181,56101538,Mesas o bufetes para el comedor,Mueble auxiliar de comedor,0,80509,0 -27182,56101539,Armazones o partes o accesorios para camas,"Bastidores para camas, Tambores para camas",0,80509,0 -27183,56101540,Solterones,Percheros,0,80509,0 -27184,56101541,Ventiladores de colchones,,0,80509,0 -27185,56101542,Asientos plegables,,0,80509,0 -27186,56101543,Mesa de comedor,,0,80509,0 -27187,56101544,Espejos,Espejos para muebles,0,80509,0 -27188,56101545,Espejos decorativos,,0,80509,0 -27189,56101600,Muebles para el exterior,,0,80509,0 -27190,56101601,Paraguas para jardín,,0,80509,0 -27191,56101602,Sillas para jardín,,0,80509,0 -27192,56101603,Mesas para jardín o mesas para picnic,,0,80509,0 -27193,56101604,Columpios para jardín,,0,80509,0 -27194,56101605,Bancos para jardín,,0,80509,0 -27195,56101606,Materas,Macetas,0,80509,0 -27196,56101607,Secadora de ropa para exteriores,,0,80509,0 -27197,56101608,Estantes para bicicletas,,0,80509,0 -27198,56101609,Macetas de arcilla,,0,80509,0 -27199,56101610,Soportes para macetas,,0,80509,0 -27200,56101700,Muebles de oficina,,0,80509,0 -27201,56101701,Cajoneras o estanterías,,0,80509,0 -27202,56101702,Gabinetes de archivo o accesorios,,0,80509,0 -27203,56101703,Escritorios,,0,80509,0 -27204,56101704,Bases para mesas,,0,80509,0 -27205,56101705,Vitrinas,,0,80509,0 -27206,56101706,Mesas de conferencia,,0,80509,0 -27207,56101707,Mesas de dibujo,,0,80509,0 -27208,56101708,Archivadores móviles,,0,80509,0 -27209,56101710,Carritos o soportes para proyectores,,0,80509,0 -27210,56101711,Conectores de muebles modulares,,0,80509,0 -27211,56101712,Pedestales,,0,80509,0 -27212,56101713,Puestos (mesas) laterales de escritorios,Escritorios en L,0,80509,0 -27213,56101714,Estantes para carpetas de información,,0,80509,0 -27214,56101715,Organizadores o clasificadores de correspondencia,,0,80509,0 -27215,56101716,Gavetas organizadoras para el escritorio,,0,80509,0 -27216,56101717,Eleva mesas,,0,80509,0 -27217,56101718,Estantes para periódicos,,0,80509,0 -27218,56101719,Mesas auxiliares,,0,80509,0 -27219,56101800,Accesorios y muebles de bebé y niño,,0,80509,0 -27220,56101803,Coches de bebé,Carriolas para bebé,0,80509,0 -27221,56101804,Cunas o corrales o accesorios,,0,80509,0 -27222,56101805,Asientos de bebé para el carro,,0,80509,0 -27223,56101806,Asientos de comer (para bebés) o accesorios,Periqueras para bebé,0,80509,0 -27224,56101807,Asientos rebotadores para bebés,,0,80509,0 -27225,56101808,Columpios o rebotadores o accesorios,,0,80509,0 -27226,56101809,Micas o bacinillas,"Asientos entrenadores, Bacinicas entrenadoras, Nica entrenador",0,80509,0 -27227,56101810,Bañeras o tinas para bebés,,0,80509,0 -27228,56101811,Cunas,,0,80509,0 -27229,56101812,Mesas para cambiar al bebé o accesorios,,0,80509,0 -27230,56101813,Asientos para el baño para bebés,,0,80509,0 -27231,56101900,Piezas de mobiliario y accesorios,Jaladeras,0,80509,0 -27232,56101901,Tapas de muebles o superficies de trabajo,,0,80509,0 -27233,56101902,Discos movibles para muebles,,0,80509,0 -27234,56101903,Protectores o almohadillas o copas para las patas de los muebles,,0,80509,0 -27235,56101904,Bases o patas o extensiones de patas para muebles,,0,80509,0 -27236,56101905,Ensamblajes o secciones de paneles,,0,80509,0 -27237,56101906,Tablas de extensión para mesas,,0,80509,0 -27238,56101907,Fundas para muebles,,0,80509,0 -27239,56111500,Sitios de trabajo y paquetes para la oficina,,0,80509,0 -27240,56111501,Paquetes de muebles de recepción para oficinas,,0,80509,0 -27241,56111502,Paquetes de muebles para ejecutivos no modulares,,0,80509,0 -27242,56111503,Paquetes de muebles para ejecutivos modulares,,0,80509,0 -27243,56111504,Paquetes de muebles de gerencia no modulares,,0,80509,0 -27244,56111505,Paquetes de muebles de gerencia modulares,,0,80509,0 -27245,56111506,Paquetes de muebles para personal no modulares,,0,80509,0 -27246,56111507,Paquetes de muebles para personal modulares,,0,80509,0 -27247,56111508,Paquetes de muebles para técnicos no modulares,,0,80509,0 -27248,56111509,Paquetes de muebles para técnicos modulares,,0,80509,0 -27249,56111510,Paquetes de muebles secretariales no modulares,,0,80509,0 -27250,56111511,Paquetes de muebles secretariales modulares,,0,80509,0 -27251,56111512,Paquetes de muebles para recepción no modulares,,0,80509,0 -27252,56111513,Paquetes de muebles de salas de juntas no modulares,,0,80509,0 -27253,56111514,Paquetes de muebles de mostrador modulares,,0,80509,0 -27254,56111600,Sistemas de paneles,,0,80509,0 -27255,56111601,Biombos (mamparas) para sistemas de paneles,,0,80509,0 -27256,56111602,Almacenamiento para sistemas de paneles,,0,80509,0 -27257,56111603,Organizadores para sistemas de paneles,,0,80509,0 -27258,56111604,Superficies de trabajo para sistemas de paneles,,0,80509,0 -27259,56111606,Partes o accesorios para sistemas de paneles,,0,80509,0 -27260,56111700,Casegood y sistemas no modulares,,0,80509,0 -27261,56111701,Escritorios no modulares,,0,80509,0 -27262,56111702,Cajoneras o estanterías no modulares,,0,80509,0 -27263,56111703,Almacenamiento no modular,,0,80509,0 -27264,56111704,Organizadores no modular,,0,80509,0 -27265,56111705,Iluminación o electricidad o componentes de información no modulares,,0,80509,0 -27266,56111706,Partes o accesorios no modulares,,0,80509,0 -27267,56111707,Repisas no modulares,,0,80509,0 -27268,56111800,Muebles independientes,,0,80509,0 -27269,56111801,Iluminación o electricidad o componentes de información de pie,,0,80509,0 -27270,56111802,Mesas individuales (sin apoyo),,0,80509,0 -27271,56111803,Almacenamiento individual (sin apoyo),,0,80509,0 -27272,56111804,Organizadores individual (sin apoyo),,0,80509,0 -27273,56111805,Partes o accesorios individuales (sin apoyo),,0,80509,0 -27274,56111806,Silla giratoria para peluquería,,0,80509,0 -27275,56111900,Muebles industriales,,0,80509,0 -27276,56111903,Unidades de almacenamiento industriales,,0,80509,0 -27277,56111904,Organizadores industriales,,0,80509,0 -27278,56111905,Partes o accesorios industriales,,0,80509,0 -27279,56111906,Gabinetes o cajones o estantes industriales,,0,80509,0 -27280,56111907,Carritos de herramientas industriales,,0,80509,0 -27281,56112000,Muebles de apoyo de la computadora,,0,80509,0 -27282,56112001,Iluminación o electricidad o componentes de información de soporte para computadores,,0,80509,0 -27283,56112002,Superficies de trabajo de soporte para computadores,,0,80509,0 -27284,56112003,Accesorios de almacenamiento de soporte para computadores,,0,80509,0 -27285,56112004,Organizadores de soporte para computadores,,0,80509,0 -27286,56112005,Partes o accesorios de soporte para computadores,,0,80509,0 -27287,56112100,Asientos,,0,80509,0 -27288,56112101,Silletería para auditorios o estadios o uso especiales,"Asientos para auditorios o estadios o uso especiales, Butacas para auditorios o estadios o uso especiales",0,80509,0 -27289,56112102,Sillas para grupos de trabajo,,0,80509,0 -27290,56112103,Sillas para visitantes,,0,80509,0 -27291,56112104,Sillas para ejecutivos,,0,80509,0 -27292,56112105,Sillas para descansar,,0,80509,0 -27293,56112106,Sillas altas (taburetes),,0,80509,0 -27294,56112107,Partes o accesorios para sillas,,0,80509,0 -27295,56112108,Combinación de asiento con escritorio,,0,80509,0 -27296,56112109,Bancos,,0,80509,0 -27297,56112110,Sillas para músicos,,0,80509,0 -27298,56112111,Sillas de seguridad para bebés,,0,80509,0 -27299,56112200,Sistemas de escritorios,,0,80509,0 -27300,56112201,Biombos (separadores) para escritorio,,0,80509,0 -27301,56112202,Componentes para almacenamiento de escritorio,,0,80509,0 -27302,56112203,Componentes organizadores par escritorio,,0,80509,0 -27303,56112204,Superficies de trabajo,,0,80509,0 -27304,56112205,"Componentes de iluminación, electricidad o información",,0,80509,0 -27305,56112206,Partes y accesorios relacionados con sistemas de escritorio,,0,80509,0 -27306,56112300,Partes y accesorios para asientos de auditorios o estadios o de uso especial,,0,80509,0 -27307,56112301,Espaldares,Respaldos,0,80509,0 -27308,56112302,Armazones para fijar asientos,,0,80509,0 -27309,56112303,Pivotes (ejes) para asientos,,0,80509,0 -27310,56112304,Cojín del asiento,,0,80509,0 -27311,56121000,Mobiliario de biblioteca,,0,80509,0 -27312,56121001,Carritos para libros,,0,80509,0 -27313,56121002,Escritorios o componentes de circulación para bibliotecarios,,0,80509,0 -27314,56121003,Muebles para devolver libros,,0,80509,0 -27315,56121004,Unidades de catálogo de tarjetas,,0,80509,0 -27316,56121005,Estanterías para diccionarios,,0,80509,0 -27317,56121006,Bancas tapizadas,,0,80509,0 -27318,56121007,Tablas de acceso público,,0,80509,0 -27319,56121008,Unidades para ojear libros,Muebles para hojear libros,0,80509,0 -27320,56121009,Mesas de lectura inclinadas,,0,80509,0 -27321,56121010,Kioscos de libros,,0,80509,0 -27322,56121011,Exhibidores de discos compactos o de audio casetes para bibliotecas,,0,80509,0 -27323,56121012,Stands de islas giratorias,,0,80509,0 -27324,56121014,Bolsas o estantes para bolsas,,0,80509,0 -27325,56121015,Estantes móviles para libros,,0,80509,0 -27326,56121100,Mobiliario de clase de arte,,0,80509,0 -27327,56121101,Caballetes,,0,80509,0 -27328,56121102,Mesas de dibujo para estudiantes,,0,80509,0 -27329,56121200,Mobiliario de sala de primeros auxilios,,0,80509,0 -27330,56121201,Camilla de primeros auxilios,,0,80509,0 -27331,56121300,Accesorios de instalaciones educativas generales,,0,80509,0 -27332,56121301,Escenarios pequeños (por ejemplo para un coro),,0,80509,0 -27333,56121302,Carritos para mover mesas o asientos,,0,80509,0 -27334,56121303,Esteras de caucho para pisos,Alfombra de caucho para pisos,0,80509,0 -27335,56121304,Mesas para planos,,0,80509,0 -27336,56121400,Mobiliario de cafetería y comedor,,0,80509,0 -27337,56121401,Mesas móviles para bancos,,0,80509,0 -27338,56121402,Mesas móviles para taburetes,,0,80509,0 -27339,56121403,Mesas móviles,,0,80509,0 -27340,56121500,Mobiliario general de aula,,0,80509,0 -27341,56121501,Mesas para actividades,,0,80509,0 -27342,56121502,Asientos para aulas de clase,,0,80509,0 -27343,56121503,Bancos para aulas de clase,,0,80509,0 -27344,56121504,Taburetes para aulas de clase,,0,80509,0 -27345,56121505,Mesas para aulas de clase,,0,80509,0 -27346,56121506,Pupitres,,0,80509,0 -27347,56121507,Bancas cubículo (pupitres con bloqueo visual para evitar distracciones),,0,80509,0 -27348,56121508,Pupitres de computador para estudiantes,,0,80509,0 -27349,56121509,Mesas de computador para estudiantes,,0,80509,0 -27350,56121510,Pupitres de profesor,,0,80509,0 -27351,56121600,Mobiliario para el tiempo de descanso y ocio creativo para guardería infantil e instalaciones para niños,,0,80509,0 -27352,56121601,Sets de sala de tamaño de niños,,0,80509,0 -27353,56121602,Sofás de tamaño de niños,,0,80509,0 -27354,56121603,Sillones de tamaño de niños,,0,80509,0 -27355,56121604,Pufs de tamaño de niños,,0,80509,0 -27356,56121605,Biombos de poca altura o paneles para jugar,,0,80509,0 -27357,56121606,Esterillas de descanso para niños,Alfombras de descanso para niños,0,80509,0 -27358,56121607,Estantes o percheros para esterillas de descanso para niños,,0,80509,0 -27359,56121608,Catres para niños,,0,80509,0 -27360,56121609,Cargadores de catres para niños,Cuna portátil,0,80509,0 -27361,56121610,Centros de actividades para catres para niños,,0,80509,0 -27362,56121611,Mesas para bebés,,0,80509,0 -27363,56121700,Módulos de almacenamiento general y de libros para las aulas,,0,80509,0 -27364,56121701,Unidades de almacenamiento general,,0,80509,0 -27365,56121702,Unidades de almacenamiento de libros,,0,80509,0 -27366,56121703,Cubos (modulares) para guardar juguetes,,0,80509,0 -27367,56121704,Gabinetes institucionales de almacenamiento,,0,80509,0 -27368,56121800,Mobiliario y accesorios de aulas vocacionales,,0,80509,0 -27369,56121801,Gabinetes para almacenamiento de herramientas de educación técnico o gabinetes con herramientas,,0,80509,0 -27370,56121802,Gabinetes para almacenamiento de herramientas de taller general o gabinetes con herramientas,,0,80509,0 -27371,56121803,Gabinetes para almacenamiento de herramientas para trabajar en madera o gabinetes con herramientas,,0,80509,0 -27372,56121804,Escritorio técnico para instructores,,0,80509,0 -27373,56121805,Archivos planos,,0,80509,0 -27374,56121900,Mobiliario de demostración,,0,80509,0 -27375,56121901,Mesas de demostración de máquinas de coser,,0,80509,0 -27376,56121902,Stands exhibidores,,0,80509,0 -27377,56121903,Gabinete de preservación de reliquias,,0,80509,0 -27378,56122000,Muebles de laboratorio,,0,80509,0 -27379,56122001,Bancas de laboratorio,,0,80509,0 -27380,56122002,Unidades o accesorios de almacenamiento para laboratorios,,0,80509,0 -27381,56122003,Puestos de trabajo para laboratorios,,0,80509,0 -27382,56122004,Unidades de bases para lavamanos,,0,80509,0 -27383,56131500,Maniquíes y formas,,0,80509,0 -27384,56131501,Formas de busto,,0,80509,0 -27385,56131502,Formas de cabezas,,0,80509,0 -27386,56131503,Formas de cuello,,0,80509,0 -27387,56131504,Formas de cuerpo completo o maniquíes,,0,80509,0 -27388,56131600,Exhibidores y accesorios auto soportados para mercancías,,0,80509,0 -27389,56131601,Stands de piso,,0,80509,0 -27390,56131602,Postes de exhibición individuales,,0,80509,0 -27391,56131603,Mostradores de venta,,0,80509,0 -27392,56131604,Componentes de centros de pinturas de colores,,0,80509,0 -27393,56131605,Tablas de madera para exhibir productos de pintura,,0,80509,0 -27394,56131606,Abanico para exhibir muestras de pintura,,0,80509,0 -27395,56131607,Selección de inspiración de color,,0,80509,0 -27396,56131700,"Aparatos, estantes, sistemas y accesorios para instalación de mercancías",,0,80509,0 -27397,56131701,Barras para colgar artículos,,0,80509,0 -27398,56131702,Canastas para mercancías,,0,80509,0 -27399,56131703,Ganchos o cascadas exhibidoras,,0,80509,0 -27400,56141500,Adornos cerámicos para el hogar,,0,80509,0 -27401,56141501,Tazones de cerámica,,0,80509,0 -27402,56141502,Platos grandes o pequeños de cerámica,,0,80509,0 -27403,56141503,Vasos de cerámica,,0,80509,0 -27404,56141504,Estatuillas o figuras de cerámica,,0,80509,0 -27405,56141600,Adornos de vidrio para el hogar,,0,80509,0 -27406,56141601,Tazones de vidrio,,0,80509,0 -27407,56141602,Vasos de vidrio,,0,80509,0 -27408,56141603,Platos grandes o pequeños de vidrio,,0,80509,0 -27409,56141604,Estatuillas o figuras de vidrio,,0,80509,0 -27410,60101000,Kits de matemáticas,,0,80509,0 -27411,60101001,Kits de matemáticas para sumar,,0,80509,0 -27412,60101002,Kits de matemáticas para dividir,,0,80509,0 -27413,60101003,Kits de matemáticas de fracciones,,0,80509,0 -27414,60101004,Kits de matemáticas para bachillerato básico,,0,80509,0 -27415,60101005,Kits de matemáticas para la primera infancia,,0,80509,0 -27416,60101006,Kits de matemáticas para mediciones,,0,80509,0 -27417,60101007,Kits de matemáticas para multiplicar,,0,80509,0 -27418,60101008,Kits de matemáticas para primaria,,0,80509,0 -27419,60101009,Kits de matemáticas para restar,,0,80509,0 -27420,60101010,Kits de matemáticas para bachillerato,,0,80509,0 -27421,60101100,Materiales electrónicos de aprendizaje,,0,80509,0 -27422,60101101,Lectores electrónicos de tarjetas,,0,80509,0 -27423,60101102,Materiales de aprendizaje electrónico basados en el plan de estudios,,0,80509,0 -27424,60101103,Globos terráqueos electrónicos,,0,80509,0 -27425,60101104,Máquinas electrónicas de pruebas,,0,80509,0 -27426,60101300,Adhesivos educativos y suministros,,0,80509,0 -27427,60101301,Adhesivos basados en la biblia,,0,80509,0 -27428,60101302,Adhesivos gigantes,,0,80509,0 -27429,60101304,Adhesivos de fotos,,0,80509,0 -27430,60101305,Adhesivos de recompensa,,0,80509,0 -27431,60101306,Adhesivos perfumados,,0,80509,0 -27432,60101307,Adhesivos de formas,,0,80509,0 -27433,60101308,Adhesivos brillantes,,0,80509,0 -27434,60101309,Adhesivos de recompensa con forma de estrella,,0,80509,0 -27435,60101310,Surtidos de adhesivos,,0,80509,0 -27436,60101311,Libros de adhesivos,,0,80509,0 -27437,60101312,Cajas de adhesivos,,0,80509,0 -27438,60101313,Calcomanías para tatuaje,,0,80509,0 -27439,60101314,Tarjetas didácticas de sumas,,0,80509,0 -27440,60101315,Tarjetas didácticas de cultura general,Biografías y monografías,0,80509,0 -27441,60101316,Tarjetas didácticas en blanco,,0,80509,0 -27442,60101317,Tarjetas didácticas de divisiones,,0,80509,0 -27443,60101318,Tarjetas didácticas electrónicas,,0,80509,0 -27444,60101319,Tarjetas didácticas de equivalencias,,0,80509,0 -27445,60101320,Tarjetas didácticas de fracciones,,0,80509,0 -27446,60101321,Tarjetas didácticas de mayor que o menor que,,0,80509,0 -27447,60101322,Tarjetas didácticas de multiplicaciones,,0,80509,0 -27448,60101323,Tarjetas didácticas de restas,,0,80509,0 -27449,60101324,Tarjetas didácticas del alfabeto,,0,80509,0 -27450,60101325,Tarjetas didácticas de construcción de palabras,,0,80509,0 -27451,60101326,Tarjetas didácticas de fonética,,0,80509,0 -27452,60101327,Tarjetas didácticas de escritura o escritura a mano,,0,80509,0 -27453,60101328,Tarjetas didácticas de los números,,0,80509,0 -27454,60101329,Tarjetas didácticas del dinero,,0,80509,0 -27455,60101330,Tarjetas didácticas de la hora,,0,80509,0 -27456,60101331,Tarjetas de los estados (departamentos),,0,80509,0 -27457,60101400,Premios de la clase,,0,80509,0 -27458,60101401,Insignias,,0,80509,0 -27459,60101402,Botones de premio,,0,80509,0 -27460,60101403,Coronas de celebración,,0,80509,0 -27461,60101404,Joyas de recompense,,0,80509,0 -27462,60101405,Cintas o escarapelas para el salón de clases,Distintivos para premiación,0,80509,0 -27463,60101600,Diplomas o certificados educativos,,0,80509,0 -27464,60101601,Certificados basados en la biblia,,0,80509,0 -27465,60101602,Certificados en blanco,,0,80509,0 -27466,60101603,Marcos para certificados,,0,80509,0 -27467,60101604,Soportes para certificados,,0,80509,0 -27468,60101605,Cintas para certificados,,0,80509,0 -27469,60101606,Diplomas,,0,80509,0 -27470,60101607,Certificados de idioma extranjero,,0,80509,0 -27471,60101608,Certificados de buena actitud,Certificados de buena conducta,0,80509,0 -27472,60101609,Certificados específicos de grado,,0,80509,0 -27473,60101610,Certificados específicos de asignatura,,0,80509,0 -27474,60101700,Materiales de recursos del profesor,,0,80509,0 -27475,60101701,Libros de recursos para la evaluación,,0,80509,0 -27476,60101702,Calendarios o recortables,,0,80509,0 -27477,60101703,Materiales pedagógicos para la formación del carácter,,0,80509,0 -27478,60101704,Libros de actividades en clase,,0,80509,0 -27479,60101705,Materiales pedagógicos para el pensamiento crítico,,0,80509,0 -27480,60101706,Guías para planes de estudios integrados,,0,80509,0 -27481,60101707,Guías para el plan de estudios,,0,80509,0 -27482,60101708,Gráficos de tela,,0,80509,0 -27483,60101709,Materiales para flanelógrafos,,0,80509,0 -27484,60101710,Regalos del educador,,0,80509,0 -27485,60101711,Sellos para calificar,,0,80509,0 -27486,60101712,Pases (permisos) para salir al pasillo,,0,80509,0 -27487,60101713,Materiales pedagógicos para la educación en el hogar,,0,80509,0 -27488,60101714,Recursos para tareas en casa,,0,80509,0 -27489,60101715,Libros de ideas,,0,80509,0 -27490,60101716,Materiales para tableros magnéticos,,0,80509,0 -27491,60101717,Placas o etiquetas de identificación,,0,80509,0 -27492,60101718,Libros de planificación del profesor,,0,80509,0 -27493,60101719,Cartelera con bolsillos,,0,80509,0 -27494,60101720,Postales de comunicación del profesor,,0,80509,0 -27495,60101721,Libros de recursos profesionales para el profesor,,0,80509,0 -27496,60101722,Libros de calificaciones de la clase del profesor,,0,80509,0 -27497,60101723,Diagramas de asientos de los estudiantes en el salón de clases,,0,80509,0 -27498,60101724,Carpetas o formularios del profesor suplente,,0,80509,0 -27499,60101725,Libros de actividades o recursos tecnológicos,,0,80509,0 -27500,60101726,Guías de referencia sobre tecnología,,0,80509,0 -27501,60101727,Materiales pedagógicos para hacer exámenes,,0,80509,0 -27502,60101728,Materiales de pedagógicos para unidades temáticas,,0,80509,0 -27503,60101729,Kits de enseñanza para bachillerato básico,,0,80509,0 -27504,60101730,Manuales de laboratorio,,0,80509,0 -27505,60101731,Rollos o tiras de frases,,0,80509,0 -27506,60101732,Punteros,,0,80509,0 -27507,60101800,Recursos educativos de las clases religiosas,,0,80509,0 -27508,60101801,Guías de referencia bíblica,,0,80509,0 -27509,60101802,Obras de teatro basadas en la biblia,,0,80509,0 -27510,60101803,Libros de actividades o recursos basados en la biblia,,0,80509,0 -27511,60101804,Libros de recursos para actividades en la escuela dominical,,0,80509,0 -27512,60101805,Recursos para escuelas bíblicas de vacaciones,,0,80509,0 -27513,60101806,Emblemas o símbolos sagrados,,0,80509,0 -27514,60101807,Rosarios,,0,80509,0 -27515,60101808,Ruedas de oración,,0,80509,0 -27516,60101809,Suministros o kits de productos religiosos,,0,80509,0 -27517,60101810,Patenas,,0,80509,0 -27518,60101811,Vestimentas,,0,80509,0 -27519,60101900,Recursos y materiales de destreza alfabética,,0,80509,0 -27520,60101901,Libros de actividades del alfabeto,,0,80509,0 -27521,60101902,Cubos con el alfabeto,,0,80509,0 -27522,60101903,Cintas adhesivas de escritorio con el alfabeto,,0,80509,0 -27523,60101904,Kits del alfabeto,,0,80509,0 -27524,60101905,Fichas con letras del alfabeto,,0,80509,0 -27525,60101906,Postales con el alfabeto,,0,80509,0 -27526,60101907,Guías de referencia del alfabeto,,0,80509,0 -27527,60101908,Libros de recursos del alfabeto,,0,80509,0 -27528,60101909,Sellos del alfabeto,,0,80509,0 -27529,60101910,Tarjetas del alfabeto para murales,,0,80509,0 -27530,60101911,Alfabetos táctiles,,0,80509,0 -27531,60102000,Materiales de recursos de creación de palabras y accesorios,,0,80509,0 -27532,60102001,Espejos para uso logopédico,Espejos para terapia de lenguaje,0,80509,0 -27533,60102002,Materiales pedagógicos para ortografía,,0,80509,0 -27534,60102003,Libros de actividades para construcción de palabras,,0,80509,0 -27535,60102004,Kits para construcción de palabras,,0,80509,0 -27536,60102005,Libros de recursos para construcción de palabras,,0,80509,0 -27537,60102006,Fichas para construcción de palabras,,0,80509,0 -27538,60102007,Murales de palabras,,0,80509,0 -27539,60102300,Libros de lectura y recursos,,0,80509,0 -27540,60102301,Libros de actividades de lectura,,0,80509,0 -27541,60102302,Libros de lectura para principiantes,,0,80509,0 -27542,60102303,Libros de literatura infantil basados en la biblia,,0,80509,0 -27543,60102304,Libros de literatura infantil,,0,80509,0 -27544,60102305,Habilidades para la lectura crítica,,0,80509,0 -27545,60102306,Flanelógrafos,Franelógrafos,0,80509,0 -27546,60102307,Libros de recursos de poesía,,0,80509,0 -27547,60102309,Kits o materiales de desarrollo de la lectura,,0,80509,0 -27548,60102310,Libros de recursos de lectura,,0,80509,0 -27549,60102311,Unidades temáticas de lectura,,0,80509,0 -27550,60102312,Libros de recursos o actividades de vocabulario.,,0,80509,0 -27551,60102400,Recursos de cuenta y temprano aprendizaje de matemáticas y accesorios,,0,80509,0 -27552,60102401,Abacos,Ábacos,0,80509,0 -27553,60102402,Libros de recursos o actividades para trabajar con material didáctico manipulable de matemáticas temprana,Libros de actividades y material didáctico manipulable de matemáticas temprana,0,80509,0 -27554,60102403,Tarjetas de actividades para trabajar material didáctico manipulable de matemáticas temprana,Tarjetas de actividades para trabajar matemáticas temprana,0,80509,0 -27555,60102404,Cuentas o sets de actividades con cuentas para matemáticas temprana,,0,80509,0 -27556,60102405,Contadores o sets de actividades con contadores para matemáticas temprana,Contadores o sets de actividades para matemáticas temprana,0,80509,0 -27557,60102406,Bandejas o boles para contar o clasificar para matemáticas tempranas,,0,80509,0 -27558,60102407,Cordones o sets de cordones para matemáticas temprana,Cordones o sets para matemáticas temprana,0,80509,0 -27559,60102408,Material didáctico manipulable de vinculación o sets de actividades de vinculación para matemáticas temprana,Material didáctico manipulable o sets de actividades de vinculación para matemáticas temprana,0,80509,0 -27560,60102409,Fichas para juegos de matemáticas,,0,80509,0 -27561,60102410,Tarjetas con números,,0,80509,0 -27562,60102411,Modelos con formas de números o accesorios,,0,80509,0 -27563,60102412,Tableros perforados para matemáticas básicas,,0,80509,0 -27564,60102413,Ganchos para tablero perforado para matemáticas temprana,,0,80509,0 -27565,60102414,Material didácticos manipulativos de clasificación o sets de actividades de clasificación para matemáticas temprana,,0,80509,0 -27566,60102500,Recursos y materiales de numeración y operaciones matemáticas básicas,,0,80509,0 -27567,60102501,Libros de recursos o de actividades de la suma,,0,80509,0 -27568,60102502,Modelos de las operaciones básicas,Libros de las operaciones básicas,0,80509,0 -27569,60102503,Guías de referencia de las operaciones básicas,,0,80509,0 -27570,60102504,Libros de recursos o actividades de la división,,0,80509,0 -27571,60102505,Libros de recursos o actividades de la multiplicación,,0,80509,0 -27572,60102506,Libros de recursos o actividades de la resta,,0,80509,0 -27573,60102507,Cintas adhesivas de escritorio con los números,,0,80509,0 -27574,60102508,Tableros o gráficas del uno al cien,,0,80509,0 -27575,60102509,Fichas con números del uno al cien,,0,80509,0 -27576,60102510,Kits de números,,0,80509,0 -27577,60102511,Rectas numéricas,,0,80509,0 -27578,60102512,libros de recursos o actividades de numeración,,0,80509,0 -27579,60102513,Dominós,,0,80509,0 -27580,60102600,"Materiales de recursos del profesor de resolución de problemas, datos, posibilidades y probabilidades",,0,80509,0 -27581,60102601,Fichas o contadores de dos caras,,0,80509,0 -27582,60102602,Pirinolas,,0,80509,0 -27583,60102603,Juegos de dados,,0,80509,0 -27584,60102604,Libros de recursos o actividades de probabilidades,,0,80509,0 -27585,60102605,Libros de recursos o actividades de lógica,,0,80509,0 -27586,60102606,Bloques de atributos,Bloques de atributos lógicos,0,80509,0 -27587,60102607,Tarjetas de actividades de bloques de atributos,Tarjetas de actividades de bloques de atributos lógicos,0,80509,0 -27588,60102608,Libros de recursos o actividades de atributos,Libros de recursos o actividades de atributos lógicos,0,80509,0 -27589,60102609,Esterilla de representación gráfica,Alfombrilla para representación gráfica,0,80509,0 -27590,60102610,Libros de recursos o actividades de representación gráfica,Representación gráfica de libros de actividades o de recursos,0,80509,0 -27591,60102611,Juegos de lógica,,0,80509,0 -27592,60102612,Kits o sets de atributos,Kits o sets de atributos lógicos,0,80509,0 -27593,60102613,Libros de recursos o actividades de resolución de problemas,,0,80509,0 -27594,60102614,Tarjetas de actividades de resolución de problemas,,0,80509,0 -27595,60102700,"Materiales de recursos del profesor de pensamiento lógico, percepción espacial, identificación de coincidencias y patrones",,0,80509,0 -27596,60102701,Libros de recursos o actividades de bloques geométricos o de bloques para patrones,Libros o actividades de bloques geométricos o de bloques para patrones,0,80509,0 -27597,60102702,Bloques para patrones,,0,80509,0 -27598,60102703,Tarjetas de patrones o actividades de bloques para patrones,,0,80509,0 -27599,60102704,Sets de actividades o juegos de bloques para patrones,,0,80509,0 -27600,60102705,Adhesivos de bloques para patrones,,0,80509,0 -27601,60102706,Espejo de bloques para patrones,Patrón de espejo de bloques,0,80509,0 -27602,60102707,Afiches o tablas de bloques para patrones,,0,80509,0 -27603,60102708,Bloques lógicos,,0,80509,0 -27604,60102709,Tarjetas de patrones o actividades de bloques lógicos,,0,80509,0 -27605,60102710,Sets de actividades de bloques lógicos,,0,80509,0 -27606,60102711,Libros de recursos o actividades del tangram,,0,80509,0 -27607,60102712,Tarjetas de patrones o actividades del tangram,,0,80509,0 -27608,60102713,Sets de actividades de rompecabezas tangram,,0,80509,0 -27609,60102714,Rompecabezas tangram,,0,80509,0 -27610,60102800,Materiales de recursos del profesor de valor de posición y base diez,,0,80509,0 -27611,60102801,Bloques de base diez,,0,80509,0 -27612,60102802,Libros de recursos o actividades de base diez o valor posicional,,0,80509,0 -27613,60102803,Tarjetas de actividades de base diez o valor posicional,,0,80509,0 -27614,60102804,Sellos de caucho de base diez,Sellos de goma de base diez,0,80509,0 -27615,60102805,Cuadrículas de valor posicional,,0,80509,0 -27616,60102806,Sets de actividades o juegos de valor posicional,,0,80509,0 -27617,60102807,Modelos o accesorios de valor posicional,,0,80509,0 -27618,60102900,Materiales de recursos del profesor del dinero y la hora,,0,80509,0 -27619,60102901,Libros de recursos o actividades sobre el dinero,,0,80509,0 -27620,60102902,Billetes de juego para la clase,,0,80509,0 -27621,60102903,Monedas de juego para la clase,,0,80509,0 -27622,60102904,Cubos o dados de monedas,,0,80509,0 -27623,60102905,Dinero magnético,,0,80509,0 -27624,60102906,Filminas de billetes,Billetes de juguete,0,80509,0 -27625,60102907,Filminas de monedas,Monedas de juguete,0,80509,0 -27626,60102908,Rompecabezas del dinero,,0,80509,0 -27627,60102909,Sellos de caucho del dinero,Sellos de goma de dinero,0,80509,0 -27628,60102910,Alcancía de monedas,,0,80509,0 -27629,60102911,Kits o juegos del dinero,Juegos de dinero,0,80509,0 -27630,60102912,Cajas registradoras de juguete,,0,80509,0 -27631,60102913,Guías de referencia del dinero,,0,80509,0 -27632,60102914,Libros de recursos o actividades de la hora,,0,80509,0 -27633,60102915,Sellos de caucho de la hora,Sellos de goma de la hora,0,80509,0 -27634,60102916,Kits de la hora,,0,80509,0 -27635,60102917,Guías de referencia de la hora,,0,80509,0 -27636,60103000,"Fracciones, pentominós y materiales de enseñanza de decimales",,0,80509,0 -27637,60103001,Círculos o cuadrados de fracciones,,0,80509,0 -27638,60103002,Cuadrados de decimales,,0,80509,0 -27639,60103003,Libros de actividades de fracciones,,0,80509,0 -27640,60103004,Barras de fracciones,,0,80509,0 -27641,60103005,Gráficos de fracciones,,0,80509,0 -27642,60103006,Dados de fracciones,,0,80509,0 -27643,60103007,Juegos de fracciones,,0,80509,0 -27644,60103008,Kits de fracciones,,0,80509,0 -27645,60103009,Fichas con fracciones,,0,80509,0 -27646,60103010,Discos con fracciones,,0,80509,0 -27647,60103012,Libro de actividades de pentominós,,0,80509,0 -27648,60103013,Tarjetas de patrones de pentominós,,0,80509,0 -27649,60103100,Materiales de recursos del profesor de geometría,,0,80509,0 -27650,60103101,Libros de recursos o actividades de geometría,,0,80509,0 -27651,60103102,Afiches o cuadros de geometría,Posters o carteles de geometría,0,80509,0 -27652,60103103,Tableros geométricos,,0,80509,0 -27653,60103104,Sets de construcción de formas geométricas,,0,80509,0 -27654,60103105,Tableros geométricos de doble cara,,0,80509,0 -27655,60103106,Kits de actividades o juegos para tableros geométricos,,0,80509,0 -27656,60103107,Bandas elásticas para tableros geométricos,,0,80509,0 -27657,60103108,Tarjetas de actividades de tableros geométricos,,0,80509,0 -27658,60103109,Instrumentos de dibujo geométrico para la pizarra,,0,80509,0 -27659,60103110,Guías de referencia de geometría,,0,80509,0 -27660,60103111,Espejo geométrico,,0,80509,0 -27661,60103112,Modelos geométricos sólidos,,0,80509,0 -27662,60103400,Recursos de habilidad de lectura de mapas y geografía y accesorios,,0,80509,0 -27663,60103401,Afiches o carteles de geografía,Posters o carteles de geografía,0,80509,0 -27664,60103402,Libros de recursos o actividades de los continentes,,0,80509,0 -27665,60103403,Materiales de aprendizaje electrónico de ciencias sociales,,0,80509,0 -27666,60103404,Guías de referencia de geografía,,0,80509,0 -27667,60103405,Estantes para mapas,,0,80509,0 -27668,60103406,Kits de plantillas de mapas,,0,80509,0 -27669,60103407,Mapas murales portátiles,,0,80509,0 -27670,60103408,Libros de recursos o actividades de geografía,,0,80509,0 -27671,60103409,Medidores de mapas,,0,80509,0 -27672,60103410,Material didáctico para la enseñanza de mapas,,0,80509,0 -27673,60103600,Recursos y actividades multiculturales,,0,80509,0 -27674,60103601,Recursos de civilizaciones de la antigüedad,,0,80509,0 -27675,60103602,"Recursos de tradiciones, rituales o costumbres",,0,80509,0 -27676,60103603,Recursos de diversidad étnica,,0,80509,0 -27677,60103604,Recursos de genealogía,Recursos para árboles genealógicos,0,80509,0 -27678,60103605,Recursos para festivos multiculturales,Recursos y actividades para celebraciones multiculturales,0,80509,0 -27679,60103606,Unidades temáticas multiculturales,,0,80509,0 -27680,60103700,Recursos de lenguas extranjeras,,0,80509,0 -27681,60103701,Recursos para aprender a hablar español,,0,80509,0 -27682,60103702,Recursos para aprender a hablar francés,,0,80509,0 -27683,60103703,Recursos para aprender a hablar alemán,,0,80509,0 -27684,60103704,Recursos para aprender a hablar inglés,,0,80509,0 -27685,60103705,Recursos para aprender a hablar latín,,0,80509,0 -27686,60103706,Recursos para aprender a hablar italiano,,0,80509,0 -27687,60103800,Recursos de enseñanza de historia,,0,80509,0 -27688,60103801,Libros de recursos de historia africana,,0,80509,0 -27689,60103802,Afiches o cuadros de historia,Posters o carteles de historia,0,80509,0 -27690,60103803,Libros de recursos de historia europea,,0,80509,0 -27691,60103804,Mapas históricos,,0,80509,0 -27692,60103805,Unidades temáticas de historia,,0,80509,0 -27693,60103806,Tarjetas de fotos de historia,,0,80509,0 -27694,60103807,Libros de recursos de historia,,0,80509,0 -27695,60103808,Recursos de historia de la mujer,,0,80509,0 -27696,60103809,Recursos de historia mundial,,0,80509,0 -27697,60103900,"Organismos vivos, especímenes conservados y materiales relacionados",,0,80509,0 -27698,60103903,Modelos de anfibios,,0,80509,0 -27699,60103904,Cultivos de hongos,,0,80509,0 -27700,60103905,Kits o materiales de cultivos,,0,80509,0 -27701,60103906,Cultivos de protozoos,,0,80509,0 -27702,60103907,Plantas de acuario,,0,80509,0 -27703,60103908,Plantas de terrario,,0,80509,0 -27704,60103909,Invertebrados vivos,,0,80509,0 -27705,60103911,Vertebrados vivos,,0,80509,0 -27706,60103915,Kits o materiales de disección,,0,80509,0 -27707,60103916,Embriones conservados,,0,80509,0 -27708,60103918,Biosferas,Biodomos,0,80509,0 -27709,60103919,Kits o materiales para experimentos de biología,,0,80509,0 -27710,60103920,Kits o materiales para tinción,Juegos o materiales para coloración,0,80509,0 -27711,60103921,Especímenes de ciclo vital preservado,Ciclos de vida de ejemplares conservados,0,80509,0 -27712,60103922,Guías de referencia de biología,,0,80509,0 -27713,60103923,Espécimen de esqueleto o hueso o concha,,0,80509,0 -27714,60103924,Libros de recursos o actividades de biología,,0,80509,0 -27715,60103925,Kits de estudio o actividades de biología,,0,80509,0 -27716,60103926,Afiches o cuadros de biología,Posters o carteles de biología,0,80509,0 -27717,60103927,Especímenes preservados de cuerpos u órganos de plantas,,0,80509,0 -27718,60103928,Tarjetas de fotos o actividades de biología,,0,80509,0 -27719,60103929,Especímenes del ciclo vital de plantas,Ciclos de vida de plantas conservados,0,80509,0 -27720,60103930,Combinación de especímenes y organismos,,0,80509,0 -27721,60103931,"Especímenes del cuerpo animal, partes u órganos","Ejemplares del cuerpo animal, partes u órganos",0,80509,0 -27722,60103932,Exhibiciones de ecosistemas,,0,80509,0 -27723,60103933,"Especímenes del cuerpo humano, partes u órganos","Ejemplares del cuerpo humano, partes u órganos",0,80509,0 -27724,60103934,Cultivos de tejidos,,0,80509,0 -27725,60103936,Sets o diagramas de anatomía,,0,80509,0 -27726,60104100,Sistemas del cuerpo y materiales relacionados,,0,80509,0 -27727,60104101,"Modelos del cuerpo humano, partes u órganos",,0,80509,0 -27728,60104102,Modelos de células,,0,80509,0 -27729,60104103,Kits de enseñanza de células,,0,80509,0 -27730,60104104,Kits de enseñanza de los sistemas del cuerpo,,0,80509,0 -27731,60104105,Materiales de enseñanza de los sistemas del cuerpo,,0,80509,0 -27732,60104106,"Modelos del cuerpo de plantas, partes u órganos",,0,80509,0 -27733,60104107,"Modelos del cuerpo de animales, partes u órganos",,0,80509,0 -27734,60104200,"Pruebas del agua, conservación y ecología",,0,80509,0 -27735,60104201,Productos químicos de prueba del agua,,0,80509,0 -27736,60104202,Kits de muestreo y pruebas del agua,,0,80509,0 -27737,60104203,Modelos del agua,,0,80509,0 -27738,60104204,Materiales de ecología acuática,,0,80509,0 -27739,60104400,Geología y ciencias de la tierra,,0,80509,0 -27740,60104401,Sets de especímenes de roca,Juegos de ejemplares de roca,0,80509,0 -27741,60104402,Especímenes de roca,Ejemplares de roca,0,80509,0 -27742,60104403,Fósiles,,0,80509,0 -27743,60104404,Modelos de accidentes geográficos,,0,80509,0 -27744,60104405,Modelos de fósiles,,0,80509,0 -27745,60104406,Herramientas para geología de campo,,0,80509,0 -27746,60104407,Mesas de simulación de corrientes de agua,,0,80509,0 -27747,60104408,Kits de estudio de geología,,0,80509,0 -27748,60104409,Aparato para la formación de nubes,,0,80509,0 -27749,60104410,Aparato para observación lunar,Telescopio,0,80509,0 -27750,60104411,Simulador del tiempo atmosférico,,0,80509,0 -27751,60104412,Aparato de acumulación de tierra,,0,80509,0 -27752,60104413,Aparato para la fuerza de coriolis,,0,80509,0 -27753,60104414,Globo terrestre,Globo terráqueo,0,80509,0 -27754,60104415,Modelo mineral,,0,80509,0 -27755,60104416,Cilindro rotatorio,,0,80509,0 -27756,60104500,"Química, electroquímica y microquímica",,0,80509,0 -27757,60104501,Afiches o cuadros de la tabla periódica,Posters o carteles de la tabla periódica,0,80509,0 -27758,60104502,Kits de análisis del consumidor,,0,80509,0 -27759,60104503,Kits para clase de química,,0,80509,0 -27760,60104504,Kits de demostración de química,,0,80509,0 -27761,60104505,Modelos atómicos,,0,80509,0 -27762,60104506,Modelos moleculares,,0,80509,0 -27763,60104507,Herramientas para demostración electroquímica,,0,80509,0 -27764,60104508,Kits de electroquímica,,0,80509,0 -27765,60104509,Pilas de combustible,,0,80509,0 -27766,60104511,Instrumentos de microquímica,,0,80509,0 -27767,60104512,Aparato para la gota de aceite de millikan,,0,80509,0 -27768,60104600,Materiales de física mecánica,,0,80509,0 -27769,60104601,Mesas de fuerzas,,0,80509,0 -27770,60104602,Modelos de gravedad o sets de modelos,,0,80509,0 -27771,60104604,Planos inclinados,,0,80509,0 -27772,60104605,Aparato de fricción,,0,80509,0 -27773,60104606,Carros de física,,0,80509,0 -27774,60104607,Equipo de péndulo,,0,80509,0 -27775,60104608,Equipo de torque,,0,80509,0 -27776,60104609,Aparato de movimiento de proyectiles,,0,80509,0 -27777,60104610,Mesas de aire,,0,80509,0 -27778,60104611,Equipos de aire,,0,80509,0 -27779,60104612,Aparato para luz o foto,,0,80509,0 -27780,60104613,Hemisferio de magdeburgo,,0,80509,0 -27781,60104614,Aparato del anillo de newton,,0,80509,0 -27782,60104615,Pista de aire,,0,80509,0 -27783,60104616,Aparato de fuerza centrípeta,,0,80509,0 -27784,60104617,Aparato del modulo elástico de tensión,,0,80509,0 -27785,60104618,Demostrador newtoniano,,0,80509,0 -27786,60104619,Temporizador de registros,,0,80509,0 -27787,60104700,Materiales de física de energía y electricidad,,0,80509,0 -27788,60104701,Dispositivos de recolección solar,,0,80509,0 -27789,60104702,Kits solares,,0,80509,0 -27790,60104703,Kits de demostración de energía,,0,80509,0 -27791,60104704,Kits de clases de energía,,0,80509,0 -27792,60104705,Kits de demostración de materia,,0,80509,0 -27793,60104706,Kits de clases de materia,,0,80509,0 -27794,60104707,Manómetros,,0,80509,0 -27795,60104708,Aparato de difusión de gases,,0,80509,0 -27796,60104709,Aparato de conducción de calor,,0,80509,0 -27797,60104710,Aparato de radiación y convección térmica,,0,80509,0 -27798,60104711,Aparato para la ley de hooke,,0,80509,0 -27799,60104712,Aparato de frank-hertz,,0,80509,0 -27800,60104713,Aparato de expansión de líquidos,,0,80509,0 -27801,60104714,Aparato para la ley de boyle,,0,80509,0 -27802,60104715,Aparato para el principio de pascal,,0,80509,0 -27803,60104716,Aparato para el principio de arquímedes,,0,80509,0 -27804,60104717,Aparato para el teorema de bernoulli,,0,80509,0 -27805,60104718,Aparato de bombeo centrífugo,,0,80509,0 -27806,60104719,Tubo de ventury,Tubo de venturi,0,80509,0 -27807,60104720,Aparato para la medición del número de reynolds,,0,80509,0 -27808,60104721,Aparato del momento de inercia,,0,80509,0 -27809,60104722,Aparato para medir la fuerza gravitacional,,0,80509,0 -27810,60104723,Balanza de torsión,,0,80509,0 -27811,60104724,Analizador del sistema de refrigeración,,0,80509,0 -27812,60104726,Aparato para la ley de torricelli,,0,80509,0 -27813,60104800,Materiales de física del sonido y las ondas,,0,80509,0 -27814,60104801,Generadores de ondas,,0,80509,0 -27815,60104802,Tanques de ondas,,0,80509,0 -27816,60104803,Fuentes de ondas,,0,80509,0 -27817,60104804,Sets de demostración de ondas,,0,80509,0 -27818,60104805,Diapasones,,0,80509,0 -27819,60104806,Demostradores del efecto doppler,,0,80509,0 -27820,60104807,Equipo de resonancia,,0,80509,0 -27821,60104808,Fonómetros,Sonómetro,0,80509,0 -27822,60104809,Aparato de ondas,,0,80509,0 -27823,60104810,Espectroscopios,,0,80509,0 -27824,60104811,Diagramas del espectro,,0,80509,0 -27825,60104812,Kits de demostración de la luz,,0,80509,0 -27826,60104813,Cartas o muestras de colores,,0,80509,0 -27827,60104814,Radiómetro,,0,80509,0 -27828,60104815,Equipo de refracción o reflexión,,0,80509,0 -27829,60104816,Sets o kits ópticos,,0,80509,0 -27830,60104817,Aparato del tanque ripple,,0,80509,0 -27831,60104818,Aparato de la constante de planck,,0,80509,0 -27832,60104819,Lámpara de balmer,,0,80509,0 -27833,60104820,Aparato de kundt,,0,80509,0 -27834,60104821,Aparato del efecto fotoeléctrico,,0,80509,0 -27835,60104822,Set de banco óptico elemental,,0,80509,0 -27836,60104823,Oscilador armónico simple,,0,80509,0 -27837,60104824,Aparato de óptica de ondas,Instrumento óptico,0,80509,0 -27838,60104825,Aparato de holografía,Dispositivo holográfico,0,80509,0 -27839,60104826,Espejo mágico óptico,,0,80509,0 -27840,60104827,Equipo para medición de distancias focales,Medidor de distancias focales,0,80509,0 -27841,60104900,Materiales de física de electricidad,,0,80509,0 -27842,60104901,Generadores de van de graff,,0,80509,0 -27843,60104902,Aparato de electrostática,Generador electrostático,0,80509,0 -27844,60104903,Kits de electrostática,,0,80509,0 -27845,60104904,Kits de electricidad,,0,80509,0 -27846,60104905,Tableros de demostración de electricidad,,0,80509,0 -27847,60104906,Kits de baterías,,0,80509,0 -27848,60104907,Generadores portátiles,,0,80509,0 -27849,60104908,Aparato de electromagnetismo,,0,80509,0 -27850,60104909,Aparato de magnetismo,,0,80509,0 -27851,60104910,Electroimanes,,0,80509,0 -27852,60104911,Campanas eléctricas o accesorios,,0,80509,0 -27853,60104912,Alambres o cables eléctricos,,0,80509,0 -27854,60104913,Aparato para la ley de ohm,Óhmetro U ohmímetro,0,80509,0 -27855,60104914,Tubo de descarga,,0,80509,0 -27856,60104915,Aparato para experimentos de transformadores,,0,80509,0 -27857,60104916,Aparato del efecto termoeléctrico,,0,80509,0 -27858,60104917,Aparato de voltaje constante,,0,80509,0 -27859,60104918,Aparato fotoeléctrico,,0,80509,0 -27860,60104919,Botella de leyden,,0,80509,0 -27861,60104920,Cámara de nubes,Cámara de niebla,0,80509,0 -27862,60104921,Aparato del efecto zeeman,,0,80509,0 -27863,60104922,Aparato para la ley de coulomb,,0,80509,0 -27864,60104923,Descargador,,0,80509,0 -27865,60104924,Aparato para la ley de faraday,,0,80509,0 -27866,60104925,Aparato para la ley de fleming,,0,80509,0 -27867,60104926,Aparato del efecto hall,Sensor de efecto hall,0,80509,0 -27868,60104927,Sistema de pruebas de armaduras,,0,80509,0 -27869,60104928,Tubo de crookes,,0,80509,0 -27870,60104929,Generador de campo magnético rotatorio,,0,80509,0 -27871,60104930,Dispositivo de demostración de luz fluorescente,,0,80509,0 -27872,60105000,Materiales de física nuclear,,0,80509,0 -27873,60105001,Sets de radioactividad,,0,80509,0 -27874,60105002,Contadores geiger,Detector de radiaciones,0,80509,0 -27875,60105003,Aparato de electrones,,0,80509,0 -27876,60105005,Diapositivas de física nuclear,Transparencias de física nuclear,0,80509,0 -27877,60105006,Diagramas de física nuclear,Gráficos de física nuclear,0,80509,0 -27878,60105104,Kits de aviones,,0,80509,0 -27879,60105400,"Materiales educativos de vida independiente, administración del dinero y economía doméstica",,0,80509,0 -27880,60105401,Materiales de enseñanza del manejo del dinero o de las finanzas personales,,0,80509,0 -27881,60105402,Materiales de enseñanza de habilidades de consumo y comparación de precios,,0,80509,0 -27882,60105403,Materiales de enseñanza de vida independiente,,0,80509,0 -27883,60105404,Materiales de enseñanza para la comprensión de los créditos o préstamos de consumo,,0,80509,0 -27884,60105405,Materiales de enseñanza de comparación o cobertura de seguros,,0,80509,0 -27885,60105406,Materiales de enseñanza para compra de casa,,0,80509,0 -27886,60105407,Materiales de enseñanza para alquiler de apartamento,,0,80509,0 -27887,60105408,Materiales de enseñanza para compra de carro,,0,80509,0 -27888,60105409,Materiales de enseñanza de publicidad o comercialización de marcas,,0,80509,0 -27889,60105410,Materiales de enseñanza de habilidades para la vida en familia o para el establecimiento de relaciones,,0,80509,0 -27890,60105411,Materiales de enseñanza para el desarrollo de la autoestima y el autoconcepto,,0,80509,0 -27891,60105412,Materiales de enseñanza de prevención de la violencia o de educación para la evasión de la violencia,,0,80509,0 -27892,60105413,Materiales de enseñanza de formación para el manejo de la ira,,0,80509,0 -27893,60105414,Materiales educativos para la enseñanza de la paciencia,,0,80509,0 -27894,60105415,Materiales de enseñanza de formación de la tolerancia,,0,80509,0 -27895,60105416,Materiales de enseñanza de seguridad personal,,0,80509,0 -27896,60105417,Materiales de enseñanza de resolución de conflictos personales,,0,80509,0 -27897,60105418,Guías de asesoramiento práctico para adolescentes,,0,80509,0 -27898,60105419,Materiales de enseñanza de desarrollo de aptitudes sociales,,0,80509,0 -27899,60105420,Materiales de enseñanza de modales o etiqueta o cortesía,,0,80509,0 -27900,60105421,Materiales de enseñanza para entender o tratar con la diversidad cultural,,0,80509,0 -27901,60105422,Materiales de enseñanza para interpretar el lenguaje corporal,,0,80509,0 -27902,60105423,Materiales de enseñanza para el desarrollo de la capacidad de adaptación,,0,80509,0 -27903,60105424,Materiales de enseñanza de comprensión del servicio a la comunidad,,0,80509,0 -27904,60105425,Materiales de enseñanza para el desarrollo de habilidades de negación,,0,80509,0 -27905,60105426,Materiales de enseñanza de habilidades de responsabilidad o toma de decisiones,,0,80509,0 -27906,60105427,Materiales de enseñanza de comprensión de los derechos legales de los adolescentes,,0,80509,0 -27907,60105428,Materiales de enseñanza de las repercusiones de la deserción escolar,,0,80509,0 -27908,60105429,Videos de las relaciones interraciales,,0,80509,0 -27909,60105500,Materiales educativos de diseño del hogar y de interiores,,0,80509,0 -27910,60105501,Materiales de enseñanza de feng shui,,0,80509,0 -27911,60105502,Materiales de enseñanza para utilizar color o pintura para la decoración del hogar,,0,80509,0 -27912,60105503,Materiales de enseñanza para la planificación o diseño del hogar,,0,80509,0 -27913,60105504,Materiales de enseñanza de diseño de jardines,,0,80509,0 -27914,60105505,Materiales de enseñanza para la decoración o amueblamiento del hogar,,0,80509,0 -27915,60105600,"Materiales educativos de preparación de alimentos, nutrición y educación sobre la salud",,0,80509,0 -27916,60105601,Materiales de enseñanza de dietas balanceadas o pautas alimentarias,,0,80509,0 -27917,60105602,Materiales de enseñanza de habilidades de planificación de menús para planes de estudios de nutrición,,0,80509,0 -27918,60105603,Materiales de enseñanza de comprensión del etiquetado nutricional,,0,80509,0 -27919,60105604,Materiales de enseñanza de compra de alimentos,,0,80509,0 -27920,60105605,Módulos de demostración de opciones de alimentos saludables,,0,80509,0 -27921,60105606,Materiales de enseñanza de comprensión de los efectos de las grasas alimenticias,,0,80509,0 -27922,60105607,Materiales de enseñanza de comprensión del vegetarianismo,,0,80509,0 -27923,60105608,Libros de cocina o recetarios,,0,80509,0 -27924,60105609,Materiales de enseñanza de educación acerca de los trastornos alimenticios,,0,80509,0 -27925,60105610,Materiales de enseñanza sobre el control de peso o el ejercicio,,0,80509,0 -27926,60105611,Materiales de enseñanza de la medición de sólidos o líquidos en la cocina,,0,80509,0 -27927,60105612,Materiales de enseñanza de equivalencias o matemáticas en la cocina,,0,80509,0 -27928,60105613,Materiales de enseñanza sobre los utensilios de cocina,,0,80509,0 -27929,60105614,Materiales de enseñanza sobre la higiene o la seguridad en la cocina,,0,80509,0 -27930,60105615,Materiales de enseñanza sobre la inocuidad de los alimentos,,0,80509,0 -27931,60105616,Materiales de enseñanza sobre actividades de la ciencia de los alimentos,,0,80509,0 -27932,60105617,Materiales de enseñanza de habilidades culinarias,,0,80509,0 -27933,60105618,Materiales de enseñanza de etiqueta o modales en la mesa,,0,80509,0 -27934,60105619,Materiales de enseñanza sobre cómo poner la mesa,,0,80509,0 -27935,60105620,Materiales de enseñanza de capacitación de los servicios de alimentación,,0,80509,0 -27936,60105621,Materiales de enseñanza de educación acerca del abuso de drogas o tabaco o alcohol,,0,80509,0 -27937,60105622,Simuladores para fumar,Simuladores para fumadores,0,80509,0 -27938,60105623,Materiales de enseñanza de comprensión de las adicciones o cómo evitarlas,,0,80509,0 -27939,60105624,Materiales de enseñanza de los síntomas de depresión en adolescentes,,0,80509,0 -27940,60105625,Materiales de enseñanza de capacitación para evitar el suicidio de adolescentes,,0,80509,0 -27941,60105626,Materiales de enseñanza para lidiar con el estrés,,0,80509,0 -27942,60105700,Álbumes de recuerdos y suministros,,0,80509,0 -27943,60105701,Álbumes de recuerdos,Álbumes fotográficos,0,80509,0 -27944,60105702,Tornillos de extensión para álbumes de recuerdos,Extensiones para álbumes de recuerdos,0,80509,0 -27945,60105703,Papel para álbumes de recuerdos,,0,80509,0 -27946,60105704,Barras de pegante libres de ácido,,0,80509,0 -27947,60105705,Cinta pegante libre de ácido,,0,80509,0 -27948,60105800,Materiales educativos de diseño textil y de ropa,,0,80509,0 -27949,60105801,Materiales de enseñanza de habilidades para la costura,,0,80509,0 -27950,60105802,Materiales para proyectos de costura,,0,80509,0 -27951,60105803,Materiales de enseñanza de comprensión de la confección o hechura de ropa,,0,80509,0 -27952,60105804,Materiales de enseñanza de diseño o modas de ropa,,0,80509,0 -27953,60105805,Materiales de enseñanza de análisis de colores personales,,0,80509,0 -27954,60105806,Materiales de enseñanza de los principios básicos de la comercialización de la moda o la venta al por menor,,0,80509,0 -27955,60105807,Materiales de enseñanza de la ciencia de los tejidos o las fibras,,0,80509,0 -27956,60105808,Materiales de enseñanza del cuidado o mantenimiento o lavado de la ropa,,0,80509,0 -27957,60105809,Materiales para la enseñanza del arte del color en el diseño de telas,,0,80509,0 -27958,60105810,Materiales de enseñanza sobre pinturas y tintes para telas,,0,80509,0 -27959,60105811,Materiales de enseñanza para proyectos de acolchado,,0,80509,0 -27960,60105900,"Materiales educativos sobre las relaciones y el sexo, el embarazo de adolescentes, aptitudes de crianza y desarrollo del niño",,0,80509,0 -27961,60105901,Materiales de enseñanza de educación sexual o enfermedades de transmisión sexual,,0,80509,0 -27962,60105902,Materiales de enseñanza de recursos de nutrición prenatal o abuso fetal,,0,80509,0 -27963,60105903,Materiales de enseñanza de habilidades para la crianza de los hijos,,0,80509,0 -27964,60105904,Materiales de enseñanza sobre el desarrollo de niño,,0,80509,0 -27965,60105905,Materiales de enseñanza de comprensión de la violación en una cita o las habilidades para salir en cita o el acoso,,0,80509,0 -27966,60105906,Materiales de enseñanza de educación sobre el parto,,0,80509,0 -27967,60105907,"Materiales de enseñanza sobre el embarazo, desde la concepción hasta el parto",,0,80509,0 -27968,60105908,Materiales de enseñanza de comprensión de los riesgos de defectos de nacimiento,,0,80509,0 -27969,60105909,Simuladores de embarazo,Chaleco simulador de embarazo,0,80509,0 -27970,60105910,Simuladores de bebés y accesorios,Simuladores infantiles y accesorios,0,80509,0 -27971,60105911,Materiales de enseñanza de formación sobre puericultura,,0,80509,0 -27972,60105912,Materiales de enseñanza de comprensión del abuso infantil físico o emocional,,0,80509,0 -27973,60105913,Materiales de enseñanza para padres sobre educación de habilidades para la disciplina,,0,80509,0 -27974,60105914,Materiales de enseñanza de seguridad del hogar o de protección para evitar usos indebidos por los niños,,0,80509,0 -27975,60105915,Materiales de enseñanza sobre resucitación cardiopulmonar o apoyo básico de vida,,0,80509,0 -27976,60105916,Materiales de enseñanza de comprensión de las enfermedades de la infancia,,0,80509,0 -27977,60105917,Materiales de enseñanza de comprensión del trastorno por déficit de atención con hiperactividad,,0,80509,0 -27978,60105918,Materiales de enseñanza para el cuidador de niños,,0,80509,0 -27979,60105919,Materiales de enseñanza para el cuidado de niños,,0,80509,0 -27980,60106000,"Guías de planes de estudio, proyectos, actividades y lecciones",,0,80509,0 -27981,60106001,Libros de ideas o recursos del plan de estudios de bachillerato básico,,0,80509,0 -27982,60106002,Libros de ideas o recursos del plan de estudios de bachillerato,,0,80509,0 -27983,60106003,Proyectos de estudio independiente de economía doméstica,,0,80509,0 -27984,60106004,Recursos o guías de proyectos o actividades de economía doméstica,,0,80509,0 -27985,60106100,Materiales de enseñanza vocacional,,0,80509,0 -27986,60106101,Materiales didácticos sobre el automóvil,,0,80509,0 -27987,60106102,Materiales didácticos de construcción,,0,80509,0 -27988,60106103,Materiales didácticos de diseño o delineación,,0,80509,0 -27989,60106104,Materiales didácticos de electricidad o electrónica,,0,80509,0 -27990,60106105,Materiales didácticos de artes gráficas o fotografía,,0,80509,0 -27991,60106106,Materiales didácticos de horticultura,,0,80509,0 -27992,60106107,Materiales didácticos de manufactura,,0,80509,0 -27993,60106108,Materiales didácticos de seguridad o peligro,,0,80509,0 -27994,60106109,Materiales didácticos de soldadura,,0,80509,0 -27995,60106200,Materiales de enseñanza de tecnología,,0,80509,0 -27996,60106201,Materiales didácticos de agricultura,,0,80509,0 -27997,60106202,Materiales didácticos de biotecnología,,0,80509,0 -27998,60106203,Materiales didácticos de comunicaciones,,0,80509,0 -27999,60106204,Materiales didácticos de informática,,0,80509,0 -28000,60106205,Materiales didácticos de energía o electricidad,,0,80509,0 -28001,60106206,Materiales didácticos del medio ambiente,,0,80509,0 -28002,60106207,Materiales didácticos de materiales,,0,80509,0 -28003,60106208,Materiales didácticos de medicina,,0,80509,0 -28004,60106209,Materiales didácticos de transporte,,0,80509,0 -28005,60106210,Materiales didácticos de sistemas de armas,,0,80509,0 -28006,60106211,Materiales didácticos del motor o sus partes,,0,80509,0 -28007,60106212,Materiales didácticos de instrumentos de navegación,,0,80509,0 -28008,60106213,Materiales didácticos de mecánica de fluidos o máquinas,,0,80509,0 -28009,60106214,Materiales didácticos de robótica,,0,80509,0 -28010,60106215,Materiales didácticos de sistemas de refrigeración,,0,80509,0 -28011,60106400,Equipo de enseñanza de electrónica y suministros,,0,80509,0 -28012,60106401,Kits de electrónica,,0,80509,0 -28013,60106402,Suministros para la enseñanza de electrónica,,0,80509,0 -28014,60106500,Suministros religiosos,,0,80509,0 -28015,60106501,Estatua budista,,0,80509,0 -28016,60106502,Estatua de la santa madre y el niño,Estatua de la Virgen María y el niño Dios,0,80509,0 -28017,60106503,Cruz eclesiástica,,0,80509,0 -28018,60106504,Servicio de mesa para ritos fúnebres,,0,80509,0 -28019,60106505,Quemador de incienso,,0,80509,0 -28020,60106506,Harubang,Estatua Harubang,0,80509,0 -28021,60111000,Pósteres o gráficos del aula,,0,80509,0 -28022,60111001,Paquetes de gráficos,,0,80509,0 -28023,60111002,Gráficos para el salón de clase,,0,80509,0 -28024,60111003,Afiches o sets para el salón de clases,Carteles o sets para el salón de clases,0,80509,0 -28025,60111004,Afiches de creación personal,Carteles de creación personal,0,80509,0 -28026,60111005,Soportes de gráficos o accesorios,,0,80509,0 -28027,60111100,Sets de tablón de anuncios,,0,80509,0 -28028,60111101,Sets de carteleras grandes,,0,80509,0 -28029,60111102,Sets de carteleras del calendario,,0,80509,0 -28030,60111103,Sets de carteleras para la primera infancia,,0,80509,0 -28031,60111104,Sets de carteleras de idiomas,,0,80509,0 -28032,60111105,Sets carteleras de matemáticas,,0,80509,0 -28033,60111106,Sets de carteleras multipropósito,,0,80509,0 -28034,60111107,Sets de carteleras de ciencias,,0,80509,0 -28035,60111108,Sets de carteleras de temporada,,0,80509,0 -28036,60111109,Sets de carteleras de ciencias sociales,,0,80509,0 -28037,60111200,Guardas y remates de tablón de anuncios,,0,80509,0 -28038,60111201,Pendones para el salón de clase,Estandarte para el salón de clases,0,80509,0 -28039,60111202,Paquetes de bordes,Marcos y encajes decorativos,0,80509,0 -28040,60111203,Carteles para el salón de clase,,0,80509,0 -28041,60111204,Bordes o ribetes corrugados,,0,80509,0 -28042,60111205,Bordes o ribetes festoneados y troquelados con formas,Encajes o ribetes festoneados y troquelados con formas,0,80509,0 -28043,60111206,Bordes o ribetes brillantes,Encajes o ribetes brillantes,0,80509,0 -28044,60111207,Bordes o ribetes rectos,Encajes o ribetes rectos,0,80509,0 -28045,60111208,Almacenaje de bordes o ribetes,Almacenaje de encajes o ribetes,0,80509,0 -28046,60111300,Letras y números decorativos,,0,80509,0 -28047,60111301,Bloques con letras o números,,0,80509,0 -28048,60111302,Letras o números sueltos,,0,80509,0 -28049,60111303,Letras o números en cursiva,,0,80509,0 -28050,60111304,Letras o números autoadhesivos,,0,80509,0 -28051,60111305,Letras o números brillantes,,0,80509,0 -28052,60111306,Letras o números para calcar,,0,80509,0 -28053,60111400,Decoraciones especiales para el aula y accesorios decorativos,,0,80509,0 -28054,60111401,Kits de decoración para el salón de clase,,0,80509,0 -28055,60111402,Decoraciones para puertas,,0,80509,0 -28056,60111403,Móviles,,0,80509,0 -28057,60111404,Decoraciones de dos caras,Móviles para niños,0,80509,0 -28058,60111405,Decoraciones para adherir a las ventanas,Decoración adherible de doble cara,0,80509,0 -28059,60111407,Sistemas decorativos de almacenaje,Pegatinas para ventanas,0,80509,0 -28060,60111408,Cintas o cintas onduladas decorativas,,0,80509,0 -28061,60111409,Botones decorativos,,0,80509,0 -28062,60111410,Figuras o cuerdas decorativas,,0,80509,0 -28063,60111411,Aerosoles decorativos,,0,80509,0 -28064,60121000,Arte,,0,80509,0 -28065,60121001,Pinturas,,0,80509,0 -28066,60121002,Esculturas,,0,80509,0 -28067,60121003,Estatuas,,0,80509,0 -28068,60121004,Retratos,,0,80509,0 -28069,60121005,Dibujos,,0,80509,0 -28070,60121006,Cuadros,,0,80509,0 -28071,60121007,Litografías,,0,80509,0 -28072,60121008,Afiches,,0,80509,0 -28073,60121009,Vasija decorativa,,0,80509,0 -28074,60121010,Pergaminos,,0,80509,0 -28075,60121011,Fotografías,,0,80509,0 -28076,60121012,Adhesivos decorativos,Calcomanías decorativas,0,80509,0 -28077,60121013,Decoración artística para paredes,Pegatinas de pared,0,80509,0 -28078,60121014,Frutas secas decorativas,,0,80509,0 -28079,60121015,Adhesivos decorativos en vinilo,,0,80509,0 -28080,60121100,"Lienzos, películas, tableros y papeles de artista",,0,80509,0 -28081,60121101,Papel para dibujo de sulfito,Papel sulfito,0,80509,0 -28082,60121102,Papel para dibujo de pasta de madera triturada,Papel de dibujo,0,80509,0 -28083,60121103,Papel para dibujo de calcar o pergamino,Papel vegetal,0,80509,0 -28084,60121104,Papel bond para para dibujo,Papel bond,0,80509,0 -28085,60121105,Papel de dibujo para carboncillo o pastel,,0,80509,0 -28086,60121106,Papel brístol para dibujo,,0,80509,0 -28087,60121107,Hojas de papel para acuarela,,0,80509,0 -28088,60121108,Cuadernos de papel para acuarela,,0,80509,0 -28089,60121109,Blocs de papel para acuarela,,0,80509,0 -28090,60121110,Papel para pintura dactilar,,0,80509,0 -28091,60121111,Cartulina de sulfito,,0,80509,0 -28092,60121112,Cartulina de pasta de madera triturada,Cartulina de papel,0,80509,0 -28093,60121113,Cartulina metalizada,,0,80509,0 -28094,60121114,Papeles para manualidades de origami,,0,80509,0 -28095,60121115,Confeti de plástico o de papel,,0,80509,0 -28096,60121116,Papel crepé para manualidades,,0,80509,0 -28097,60121117,Papel de seda para manualidades,Papel de china,0,80509,0 -28098,60121118,Papel corrugado para manualidades,,0,80509,0 -28099,60121119,Papel con impresión de patrones para manualidades,,0,80509,0 -28100,60121120,Papel para manualidades autoadhesivo,,0,80509,0 -28101,60121121,Papel fluorescente,,0,80509,0 -28102,60121123,Papel hecho a mano,,0,80509,0 -28103,60121124,Papel kraft,Papel estraza,0,80509,0 -28104,60121125,Paneles de lienzo,,0,80509,0 -28105,60121126,Lienzo preestirado,,0,80509,0 -28106,60121127,Lienzo imprimado,,0,80509,0 -28107,60121128,Lienzo sin imprimar,,0,80509,0 -28108,60121129,Paneles de madera comprimida,,0,80509,0 -28109,60121130,Cuadernos de papel imitación lienzo,,0,80509,0 -28110,60121131,Papel japonés para grabado,,0,80509,0 -28111,60121132,Papel para grabado para huecograbado o litografía,,0,80509,0 -28112,60121133,Papel para grabado para impresión xilográfica,,0,80509,0 -28113,60121134,Papel metálico,,0,80509,0 -28114,60121135,"Películas de acetato, vinilo o poliéster",,0,80509,0 -28115,60121136,Películas de celofán,,0,80509,0 -28116,60121137,Hojas de acrílico,,0,80509,0 -28117,60121138,Tableros de ilustración,,0,80509,0 -28118,60121139,Láminas de paspartú,,0,80509,0 -28119,60121140,Tablero de montaje,,0,80509,0 -28120,60121141,Tablero de montaje de núcleo de espuma,,0,80509,0 -28121,60121142,Cartón duro o cartón de colores de dos caras,,0,80509,0 -28122,60121143,Tablero para presentaciones,,0,80509,0 -28123,60121144,Papeles borrador para arte,,0,80509,0 -28124,60121145,Carteles borrador para arte,,0,80509,0 -28125,60121146,Accesorios borrador para arte,,0,80509,0 -28126,60121147,Papel brillante,,0,80509,0 -28127,60121148,Cartones de colores,,0,80509,0 -28128,60121149,Papel vegetal prensado,,0,80509,0 -28129,60121150,Papel de construcción,,0,80509,0 -28130,60121151,Accesorios o tableros para dibujo o bosquejo,,0,80509,0 -28131,60121152,Tablillas de escritura,,0,80509,0 -28132,60121153,Hojas de transferencia,,0,80509,0 -28133,60121200,"Pintura del aula y de bellas artes, medios, aplicadores y accesorios",,0,80509,0 -28134,60121201,Pintura témpera líquida tradicional,,0,80509,0 -28135,60121202,Pintura témpera líquida contemporánea,,0,80509,0 -28136,60121203,Pintura témpera en polvo,,0,80509,0 -28137,60121204,Pintura témpera lavable,,0,80509,0 -28138,60121205,Pastillas de témpera,,0,80509,0 -28139,60121206,Pintura líquida para cuerpo o cara,,0,80509,0 -28140,60121207,Pintura en pasta para cuerpo o cara,,0,80509,0 -28141,60121208,Pintura de marcador para cuerpo o cara,,0,80509,0 -28142,60121209,Pintura de tatuaje provisional,Pintura de henna,0,80509,0 -28143,60121210,Pintura dactilar lavable,,0,80509,0 -28144,60121211,Pintura acrílica estilo escolar,,0,80509,0 -28145,60121212,Pintura acrílica para aerógrafo,,0,80509,0 -28146,60121213,Pinturas o medios al oleo sintéticos tratados con calor,,0,80509,0 -28147,60121214,Pinturas o medios al óleo solubles en agua,,0,80509,0 -28148,60121215,Pintura removible de baja viscosidad para vidrio o cerámica,,0,80509,0 -28149,60121216,Pintura permanente de baja viscosidad para vidrio o cerámica,,0,80509,0 -28150,60121217,Pintura gel removible de alta viscosidad para vidrio o cerámica,,0,80509,0 -28151,60121218,Pintura gel permanente de alta viscosidad para vidrio o cerámica,,0,80509,0 -28152,60121219,Pintura para cerámica o vidrio horneado,,0,80509,0 -28153,60121220,Pintura para vidrio o cerámica de sistema de marcador,,0,80509,0 -28154,60121221,Pintura de acuarela de platillo,,0,80509,0 -28155,60121222,Pintura de acuarela en tubo,,0,80509,0 -28156,60121223,Pintura de acuarela líquida,,0,80509,0 -28157,60121224,Pintura de enmascarar para acuarela líquida,,0,80509,0 -28158,60121225,Medios para pintura de acuarela,,0,80509,0 -28159,60121226,Pinceles para acuarela,,0,80509,0 -28160,60121227,Pinceles orientales,,0,80509,0 -28161,60121228,Pinceles de utilidad,,0,80509,0 -28162,60121229,Pinceles especializados,,0,80509,0 -28163,60121230,Pinceles de caballete,,0,80509,0 -28164,60121231,Espátulas de paleta,,0,80509,0 -28165,60121232,Rodillos para impresión a mano,,0,80509,0 -28166,60121233,Sellos de esponja,,0,80509,0 -28167,60121234,Espátulas para aplicación de pintura,,0,80509,0 -28168,60121235,Pipetas para mezclar pinturas o tintes,Pipetas de plástico,0,80509,0 -28169,60121236,Peinillas o utensilios para aplicación de pintura o tinta,Peines o herramientas para la aplicación de pintura o tinta,0,80509,0 -28170,60121237,Paletas para mezclar pintura o tinta,,0,80509,0 -28171,60121238,Vasijas para pintura o para mezclar o almacenar pintura,,0,80509,0 -28172,60121239,Botellas o tazas para pintura,,0,80509,0 -28173,60121241,Productos de limpieza de utensilios o pinceles,,0,80509,0 -28174,60121242,Delantales para pintar,,0,80509,0 -28175,60121243,Blusones para artistas,Delantales para artistas,0,80509,0 -28176,60121244,Tiras extensoras,,0,80509,0 -28177,60121245,Extensores de lienzo,,0,80509,0 -28178,60121246,Caballetes metálicos,,0,80509,0 -28179,60121247,Caballetes de madera,,0,80509,0 -28180,60121248,Caballetes de sobremesa,,0,80509,0 -28181,60121249,Caballetes de presentación,,0,80509,0 -28182,60121250,Portafolio para dibujos,,0,80509,0 -28183,60121251,Pintura al gouache,Pintura acrílica,0,80509,0 -28184,60121252,Bandejas para pinturas,,0,80509,0 -28185,60121253,Aerógrafos para arte,,0,80509,0 -28186,60121300,Productos de corte de manualidades y arte,,0,80509,0 -28187,60121301,Guillotinas para cortar papel,,0,80509,0 -28188,60121302,Cortadora de paspartú,,0,80509,0 -28189,60121303,Cuchillas para paspartú,,0,80509,0 -28190,60121304,Espátulas de artista,,0,80509,0 -28191,60121305,Cortador rotativo de tela o papel,,0,80509,0 -28192,60121306,Cortadoras de papel en círculos u óvalos,,0,80509,0 -28193,60121400,Enmarcado de cuadro,,0,80509,0 -28194,60121401,Marcos de madera preensamblados,Portaretratos de madera preensamblados,0,80509,0 -28195,60121402,Marcos de secciones de madera,,0,80509,0 -28196,60121403,Marcos metálicos preensamblados,Portaretratos metálicos preensamblados,0,80509,0 -28197,60121404,Marcos de secciones de metal,,0,80509,0 -28198,60121405,Marcos ajustables,Portaretratos ajustables,0,80509,0 -28199,60121406,Marcos de plástico,Portaretratos de plástico,0,80509,0 -28200,60121407,Marcos en forma de cubo transparente,,0,80509,0 -28201,60121408,Clavadoras de puntillas o accesorios para marcos,,0,80509,0 -28202,60121409,Caja de ingletes,,0,80509,0 -28203,60121410,Dispositivos para colgar cuadros,,0,80509,0 -28204,60121411,Paneles acrílicos para marcos,,0,80509,0 -28205,60121412,Paneles de vidrio para marcos,,0,80509,0 -28206,60121413,Organizadores o álbumes para fotos,,0,80509,0 -28207,60121414,Monturas magnéticas para marcos,,0,80509,0 -28208,60121415,Kits de marcos,,0,80509,0 -28209,60121500,"Utensilios de dibujo, suministros y accesorios",,0,80509,0 -28210,60121501,Marcadores a base de agua,Plumones a base de agua,0,80509,0 -28211,60121502,Marcadores de base disolvente,Plumones de base disolvente,0,80509,0 -28212,60121503,Marcadores lavables,Plumones lavables,0,80509,0 -28213,60121504,Marcadores para caligrafía,Plumones para caligrafía,0,80509,0 -28214,60121505,Marcadores de tela,Plumones para tele,0,80509,0 -28215,60121506,Marcadores metálicos,Plumones metálicos,0,80509,0 -28216,60121507,Marcadores de témpera o tiza para ventanas,Marcador de tiza borrable,0,80509,0 -28217,60121508,Marcadores de pintura,,0,80509,0 -28218,60121509,Crayones de cera,,0,80509,0 -28219,60121510,Crayones de base de soya,,0,80509,0 -28220,60121511,Crayones especializados,,0,80509,0 -28221,60121512,Crayones de acuarela,,0,80509,0 -28222,60121513,Pastel seco,,0,80509,0 -28223,60121514,Pastel de tiza,Pintura al pastel,0,80509,0 -28224,60121515,Pastel a base de aceite,,0,80509,0 -28225,60121516,Carboncillo,,0,80509,0 -28226,60121517,Carboncillo vine,,0,80509,0 -28227,60121518,Lápices de grafito,,0,80509,0 -28228,60121519,Lápices de colores para dibujar de base de cera,,0,80509,0 -28229,60121520,Carboncillos,Carboncillo comprimido,0,80509,0 -28230,60121521,Lápices de acuarela,,0,80509,0 -28231,60121522,Bolígrafos de base acuosa,,0,80509,0 -28232,60121523,Bolígrafos permanentes,,0,80509,0 -28233,60121524,Bolígrafos de gel,Lápices de carboncillo,0,80509,0 -28234,60121525,Rapidógrafos,Estilógrafo,0,80509,0 -28235,60121526,Bolígrafos para caligrafía,,0,80509,0 -28236,60121531,Borradores de lápices color rosados,,0,80509,0 -28237,60121532,Borradores de goma moldeable,,0,80509,0 -28238,60121533,Borradores de vinilo,Estilógrafo,0,80509,0 -28239,60121534,Borradores de plástico,,0,80509,0 -28240,60121535,Borradores de goma,,0,80509,0 -28241,60121537,Plumillas o sus accesorios,,0,80509,0 -28242,60121538,Kits de caligrafía,,0,80509,0 -28243,60121539,Fijadores de dibujo,,0,80509,0 -28244,60121540,Telas de dibujo,,0,80509,0 -28245,60121600,Materiales de estudio,,0,80509,0 -28246,60121601,Maniquíes de madera,,0,80509,0 -28247,60121603,Placas de fricción de plástico,,0,80509,0 -28248,60121604,Accesorios para ayudas de estudio,,0,80509,0 -28249,60121605,Modelos anatómicos,,0,80509,0 -28250,60121606,Pantallas de fondo,,0,80509,0 -28251,60121700,Suministros y accesorios de grabados,,0,80509,0 -28252,60121701,Sellos de estampación de caucho,Sellos de caucho,0,80509,0 -28253,60121702,Almohadilla para sellos de estampación de caucho,Almohadilla para sellos de caucho,0,80509,0 -28254,60121703,Accesorios para estampación de caucho,Accesorios para sellos de caucho,0,80509,0 -28255,60121704,Linóleo para impresión xilográfica,,0,80509,0 -28256,60121705,Bloques de madera para impresión,,0,80509,0 -28257,60121706,Bloques sintéticos para impresión,,0,80509,0 -28258,60121707,Accesorios para impresión xilográfica,,0,80509,0 -28259,60121708,Planchas de huecograbado o litografía,,0,80509,0 -28260,60121709,Mantillas para huecograbado o litografía,,0,80509,0 -28261,60121710,Limpiones para huecograbado o litografía,Tintas a base de agua,0,80509,0 -28262,60121711,Planchas calientes para huecograbado o litografía,,0,80509,0 -28263,60121712,Prensas de impresión para huecograbado o litografía,,0,80509,0 -28264,60121713,Rodillos y aplanadoras de impresión,,0,80509,0 -28265,60121714,Utensilios de grabado para huecograbado,,0,80509,0 -28266,60121715,Estaciones de impresión o pantallas para serigrafía,,0,80509,0 -28267,60121716,Accesorios para serigrafía,,0,80509,0 -28268,60121717,Punzones para grabado,,0,80509,0 -28269,60121718,Extendedores de tinta de impresión,,0,80509,0 -28270,60121800,Tintas de dibujo e impresión,,0,80509,0 -28271,60121801,Tintas para afiches a base de agua,,0,80509,0 -28272,60121802,Tintas acrílicas a base de agua,,0,80509,0 -28273,60121803,Tintas para serigrafía a base de aceite,,0,80509,0 -28274,60121804,Tintas para textiles a base de agua,,0,80509,0 -28275,60121805,Tintas para textiles a base de aceite,,0,80509,0 -28276,60121806,Tintas de sublimación para grabado,,0,80509,0 -28277,60121807,Tintas para huecograbado o litografía a base de aceite,,0,80509,0 -28278,60121808,Tintas para monoimpresión a base de aceite,,0,80509,0 -28279,60121809,Tintas para monoimpresión a base de agua,,0,80509,0 -28280,60121810,Tintas para dibujar a base de agua,,0,80509,0 -28281,60121811,Tintas para dibujar de base solvente,,0,80509,0 -28282,60121812,Tintas para caligrafía,,0,80509,0 -28283,60121813,Tintas para serigrafía,,0,80509,0 -28284,60121814,Barnices litográficos,,0,80509,0 -28285,60121900,Materiales de decoración de telas y arte textil y suministros,,0,80509,0 -28286,60121901,Muselina,,0,80509,0 -28287,60121902,Fieltro,,0,80509,0 -28288,60121903,Pieles para manualidades,,0,80509,0 -28289,60121904,Mezclas de algodón,,0,80509,0 -28290,60121905,Lienzos estampables,,0,80509,0 -28291,60121906,Estampables presensibilizados,,0,80509,0 -28292,60121907,Estampables de algodón,,0,80509,0 -28293,60121908,Estampables mezclados,,0,80509,0 -28294,60121909,Ceras para batik,,0,80509,0 -28295,60121910,Accesorios para batik,,0,80509,0 -28296,60121911,Tela para batik,,0,80509,0 -28297,60121912,Difuminadores,,0,80509,0 -28298,60122000,"Equipo de tejido, pespunteado y cosido y accesorios",,0,80509,0 -28299,60122002,Accesorios para tejer,,0,80509,0 -28300,60122003,Agujas para coser a mano,,0,80509,0 -28301,60122004,Kits de manualidades con cuerda,,0,80509,0 -28302,60122005,Telares manuales,,0,80509,0 -28303,60122006,Telares de mesa,,0,80509,0 -28304,60122007,Telares de piso,,0,80509,0 -28305,60122008,Cordón plástico (rexlace),,0,80509,0 -28306,60122009,Accesorios de encordado o acordonado,,0,80509,0 -28307,60122100,Velas artesanales,,0,80509,0 -28308,60122101,Mechas para la fabricación de velas,,0,80509,0 -28309,60122102,Moldes para la fabricación de velas,,0,80509,0 -28310,60122103,Accesorios para la fabricación de velas,,0,80509,0 -28311,60122200,Ebanistería,,0,80509,0 -28312,60122201,Materiales para artesanías en madera,,0,80509,0 -28313,60122202,Materiales para acabados,,0,80509,0 -28314,60122203,Herramientas para quemado de madera,,0,80509,0 -28315,60122204,Herramientas para tallar,,0,80509,0 -28316,60122400,Suministros de vidrieras de colores,,0,80509,0 -28317,60122401,Fragmentos de vidrios de colores,,0,80509,0 -28318,60122402,Herramientas o accesorios para vidrios de colores,,0,80509,0 -28319,60122500,Manualidades de papel y papel artesanal,,0,80509,0 -28320,60122501,Utensilios para dar formas al papel,,0,80509,0 -28321,60122502,Marcos de papel,,0,80509,0 -28322,60122503,Platos o bandejas de papel,,0,80509,0 -28323,60122504,Filtros de papel,,0,80509,0 -28324,60122506,Blondas de papel,,0,80509,0 -28325,60122507,Bastidores o moldes para papel hecho a mano,,0,80509,0 -28326,60122508,Láminas absorbentes o fieltros para papel hecho a mano,,0,80509,0 -28327,60122509,Pulpa o materias primas para papel hecho a mano,,0,80509,0 -28328,60122600,Mosaicos,,0,80509,0 -28329,60122601,Azulejos para mosaico,,0,80509,0 -28330,60122602,Moldes para mosaicos,,0,80509,0 -28331,60122603,Herramientas para mosaicos,,0,80509,0 -28332,60122604,Accesorios para mosaicos,,0,80509,0 -28333,60122700,Pinturas de esmaltado y accesorios,,0,80509,0 -28334,60122701,Pinturas o medios para esmaltar,,0,80509,0 -28335,60122702,Formas de cobre,,0,80509,0 -28336,60122703,Accesorios para esmaltar,,0,80509,0 -28337,60122704,Hornos para esmaltar,,0,80509,0 -28338,60122800,Máscaras,,0,80509,0 -28339,60122801,Formas para hechura de máscaras,,0,80509,0 -28340,60122900,Cuentas o accesorios de enhebrado de cuentas,,0,80509,0 -28341,60122901,Cuentas pequeñas,,0,80509,0 -28342,60122902,Cuentas tipo “pony”,,0,80509,0 -28343,60122903,Cuentas de madera,,0,80509,0 -28344,60122904,Cuentas de paja,,0,80509,0 -28345,60122905,Cuentas de cerámica,,0,80509,0 -28346,60122906,Cuentas de vidrio,,0,80509,0 -28347,60122907,Cuentas surtidas o de decoración,,0,80509,0 -28348,60122908,Accesorios de cuentas,,0,80509,0 -28349,60122909,Cuentas de plástico,,0,80509,0 -28350,60123000,Artesanía de espuma,,0,80509,0 -28351,60123001,Formas de icopor,"Formas de espuma de poliestireno EPS, Formas de poliestireno expandido EPS, Formas de unice",0,80509,0 -28352,60123002,Utensilios para artesanía de espuma,,0,80509,0 -28353,60123100,Artesanía de cañas de felpilla,,0,80509,0 -28354,60123101,Cañas de felpilla grandes,,0,80509,0 -28355,60123102,Cañas de felpilla de algodón,,0,80509,0 -28356,60123103,Cañas de felpilla abultadas,,0,80509,0 -28357,60123200,Materiales de artesanía de cintas,,0,80509,0 -28358,60123201,Lazos de papel,,0,80509,0 -28359,60123202,Cintas de seda,,0,80509,0 -28360,60123203,Cintas sintéticas,,0,80509,0 -28361,60123204,Cintas decorativas,,0,80509,0 -28362,60123300,Pompones de artesanía,,0,80509,0 -28363,60123301,Pompones acrílicos para manualidades,,0,80509,0 -28364,60123302,Pompones brillantes para manualidades,,0,80509,0 -28365,60123303,Cuentas de pompones para manualidades,,0,80509,0 -28366,60123400,Materiales de ojos móviles,,0,80509,0 -28367,60123401,Ojos móviles no autoadhesivos,,0,80509,0 -28368,60123402,Ojos móviles autoadhesivos,,0,80509,0 -28369,60123403,Ojos móviles decorativos,,0,80509,0 -28370,60123500,Materiales de artesanía de cuero,,0,80509,0 -28371,60123501,Materiales de cuero o de acordonados de cuero,,0,80509,0 -28372,60123502,Accesorios de cuero,,0,80509,0 -28373,60123600,Purpurina,Brillantina,0,80509,0 -28374,60123601,Pegante de purpurina,Pegamento con brillantina,0,80509,0 -28375,60123602,Puntos de purpurina,Brillantina,0,80509,0 -28376,60123603,Joyas de purpurina,Joyas con brillantina,0,80509,0 -28377,60123604,Purpurina de plástico,Brillantina de plástico,0,80509,0 -28378,60123605,Purpurina iridiscente,Brillantina iridiscente,0,80509,0 -28379,60123606,Purpurina metálica,Brillantina metálica,0,80509,0 -28380,60123700,Materiales de artesanía de macramé y accesorios,,0,80509,0 -28381,60123701,Cordón de macramé,,0,80509,0 -28382,60123702,Cuentas para macramé,,0,80509,0 -28383,60123703,Accesorios para macramé,,0,80509,0 -28384,60123900,Lentejuelas y ribetes,,0,80509,0 -28385,60123901,Lentejuelas o ribetes decorativos,,0,80509,0 -28386,60124000,Suministros de artesanía del corcho y accesorios,,0,80509,0 -28387,60124001,Planchas de corcho,,0,80509,0 -28388,60124002,Tapones de corcho o accesorios,,0,80509,0 -28389,60124100,Materiales de proyecto multicultural y accesorios,,0,80509,0 -28390,60124102,Productos de artesanía multicultural,,0,80509,0 -28391,60124300,"Compuestos de modelado y arcilla, equipo de cerámica y accesorios",,0,80509,0 -28392,60124301,Arcilla húmeda cocida en horno,,0,80509,0 -28393,60124302,Arcilla seca cocida en horno,,0,80509,0 -28394,60124303,Mobiliario para hornos,,0,80509,0 -28395,60124304,Hornos para a cocción de cerámica,,0,80509,0 -28396,60124305,Accesorios de hornos para la cocción de cerámica,,0,80509,0 -28397,60124306,Tornos de alfarero para cerámicas hechas a mano,,0,80509,0 -28398,60124307,Extrusoras para materiales de modelado,,0,80509,0 -28399,60124308,Conos para hornos de cocción,,0,80509,0 -28400,60124309,Paletas de alfarería,,0,80509,0 -28401,60124310,Tornos de decoración para alfarería,,0,80509,0 -28402,60124311,Utensilios para arcilla o modelado,,0,80509,0 -28403,60124312,Azulejos de cerámica cocidos,,0,80509,0 -28404,60124313,Recipientes para almacenamiento de arcilla,,0,80509,0 -28405,60124314,Compuestos para modelado plastificados no endurecibles,,0,80509,0 -28406,60124315,Compuestos para modelado no endurecibles a base de aceite,,0,80509,0 -28407,60124316,Compuestos para modelado o arcilla secados al aire,,0,80509,0 -28408,60124317,Masa para modelado,,0,80509,0 -28409,60124318,Papel maché,,0,80509,0 -28410,60124319,Compuestos para modelado especializados,,0,80509,0 -28411,60124320,Compuestos de yeso,,0,80509,0 -28412,60124321,Compuestos para modelado o arcilla de endurecimiento en horno,,0,80509,0 -28413,60124322,Compuestos para modelado de plástico,,0,80509,0 -28414,60124323,Moldes para dar forma a los compuestos para modelado,,0,80509,0 -28415,60124324,Kits para modelado de arcilla,,0,80509,0 -28416,60124325,Arcilla de papel,,0,80509,0 -28417,60124400,Metales de artesanía,,0,80509,0 -28418,60124401,Hoja fina de metal de cobre,Lámina de cobre para repujado,0,80509,0 -28419,60124402,Hoja fina de metal de aluminio,Lámina de aluminio para repujado,0,80509,0 -28420,60124403,Alambre de aluminio,,0,80509,0 -28421,60124404,Hoja fina de metal de latón,Lámina de latón para repujado,0,80509,0 -28422,60124406,Placas o láminas de plata,,0,80509,0 -28423,60124407,Alambre de plata,,0,80509,0 -28424,60124408,Bolitas de peltre,,0,80509,0 -28425,60124409,Lingotes de peltre,,0,80509,0 -28426,60124410,Placas de láminas de bronce,,0,80509,0 -28427,60124411,"Alambre de cobre ""nu gold""",,0,80509,0 -28428,60124412,Alambre suave galvanizado,,0,80509,0 -28429,60124500,Suministros y accesorios de escultura,,0,80509,0 -28430,60124501,Envoltura de yeso,,0,80509,0 -28431,60124502,Resinas para vaciado,,0,80509,0 -28432,60124503,Accesorios para esculturas,,0,80509,0 -28433,60124504,Repisas para rompecabezas,,0,80509,0 -28434,60124505,Ampollas o accesorios,,0,80509,0 -28435,60124506,Utensilios o moldes o juguetes de plástico para arena o agua,,0,80509,0 -28436,60124507,Arena para jugar,,0,80509,0 -28437,60124508,Centros de actividades o mesas de arena o agua,,0,80509,0 -28438,60124509,Sets de vehículos,Esculturas de vehículos,0,80509,0 -28439,60124510,Sets de vías de agua,,0,80509,0 -28440,60124511,Herramientas de juguete o kits de herramientas de juguete,,0,80509,0 -28441,60124512,Escúter,Patín del diablo,0,80509,0 -28442,60124513,Bolsitas de fríjoles (beanbags),,0,80509,0 -28443,60124514,Juguetes táctiles,,0,80509,0 -28444,60124515,Juguetes cognitivos,,0,80509,0 -28445,60131000,Instrumentos de teclado,,0,80509,0 -28446,60131001,Pianos,,0,80509,0 -28447,60131002,Acordeones,,0,80509,0 -28448,60131003,Órganos musicales,,0,80509,0 -28449,60131004,Celestas,,0,80509,0 -28450,60131005,Melodión,Melódica,0,80509,0 -28451,60131006,Sintetizador,,0,80509,0 -28452,60131007,Melódica,,0,80509,0 -28453,60131100,Instrumentos de metal,,0,80509,0 -28454,60131101,Trompetas,,0,80509,0 -28455,60131102,Trombones,,0,80509,0 -28456,60131103,Sousafones,Sousáfono,0,80509,0 -28457,60131104,Saxofones,,0,80509,0 -28458,60131105,Silbatos,,0,80509,0 -28459,60131106,Clarines,,0,80509,0 -28460,60131107,Saxhorno,Saxcorno,0,80509,0 -28461,60131108,Cornos franceses,Trompa,0,80509,0 -28462,60131109,Melófonos,,0,80509,0 -28463,60131110,Trompas alto,Trompa tenor,0,80509,0 -28464,60131111,Bombardinos barítono,,0,80509,0 -28465,60131112,Fiscornos,,0,80509,0 -28466,60131113,Nabal,,0,80509,0 -28467,60131114,Tuba,,0,80509,0 -28468,60131115,Eufonio,,0,80509,0 -28469,60131116,Tubáfono,,0,80509,0 -28470,60131200,Instrumentos de viento de madera,,0,80509,0 -28471,60131201,Clarinetes,,0,80509,0 -28472,60131202,Oboes,,0,80509,0 -28473,60131203,Flautas musicales,,0,80509,0 -28474,60131204,Flautines,,0,80509,0 -28475,60131205,Cornetas musicales,,0,80509,0 -28476,60131206,Gaitas,,0,80509,0 -28477,60131207,Armónicas,,0,80509,0 -28478,60131208,Mirlitones,Turuta,0,80509,0 -28479,60131209,Cornos ingleses,Corno inglés,0,80509,0 -28480,60131210,Ocarinas,,0,80509,0 -28481,60131211,Daegum,,0,80509,0 -28482,60131212,Danso,Tanso,0,80509,0 -28483,60131213,Tungso,Tongso,0,80509,0 -28484,60131214,Taepyeongso,,0,80509,0 -28485,60131215,Jeok,,0,80509,0 -28486,60131216,Dangjeok,,0,80509,0 -28487,60131217,Dangpirie,,0,80509,0 -28488,60131218,Sepiri,Piri,0,80509,0 -28489,60131219,So,,0,80509,0 -28490,60131220,Yak,,0,80509,0 -28491,60131221,Junggeum,,0,80509,0 -28492,60131222,Ji,,0,80509,0 -28493,60131223,Sogeum,,0,80509,0 -28494,60131224,Hyangpiri,,0,80509,0 -28495,60131225,Saenghwang,,0,80509,0 -28496,60131226,Hun,,0,80509,0 -28497,60131227,Nagak,,0,80509,0 -28498,60131228,Fagot,,0,80509,0 -28499,60131229,Cornamusa,,0,80509,0 -28500,60131230,Contrafagot,,0,80509,0 -28501,60131231,Sarrusofón,,0,80509,0 -28502,60131232,Corno sirena,Sirena,0,80509,0 -28503,60131233,Flauta dulce,,0,80509,0 -28504,60131234,Clave,,0,80509,0 -28505,60131235,Quena,Flauta de caña,0,80509,0 -28506,60131236,Quenacho,,0,80509,0 -28507,60131237,Zampoña,,0,80509,0 -28508,60131300,Instrumentos de cuerda,,0,80509,0 -28509,60131301,Clavecines,,0,80509,0 -28510,60131302,Clavicordios,,0,80509,0 -28511,60131303,Guitarras,,0,80509,0 -28512,60131304,Violines,,0,80509,0 -28513,60131305,Arpas,,0,80509,0 -28514,60131306,Banyos,Banjo,0,80509,0 -28515,60131307,Mandolinas,,0,80509,0 -28516,60131308,Violonchelos,,0,80509,0 -28517,60131309,Bajos,,0,80509,0 -28518,60131310,Gayageum,,0,80509,0 -28519,60131311,Geomungo,,0,80509,0 -28520,60131312,Ajaeng,,0,80509,0 -28521,60131313,Geum,,0,80509,0 -28522,60131314,Dangbipa,Bipa,0,80509,0 -28523,60131315,Daejaeng,,0,80509,0 -28524,60131316,Seul,,0,80509,0 -28525,60131317,Wagonghu,,0,80509,0 -28526,60131318,Hyangbipa,,0,80509,0 -28527,60131319,Haegeum,,0,80509,0 -28528,60131320,Sogonghu,,0,80509,0 -28529,60131321,Sugonghu,,0,80509,0 -28530,60131322,Yangkum,,0,80509,0 -28531,60131323,Wolgeum,,0,80509,0 -28532,60131324,Ukelele,,0,80509,0 -28533,60131325,Viola,,0,80509,0 -28534,60131326,Instrumento nacional morin khuur,Morin juur,0,80509,0 -28535,60131327,Yochin,,0,80509,0 -28536,60131328,Yatga,,0,80509,0 -28537,60131329,Shanz,Schanz,0,80509,0 -28538,60131400,Instrumentos de percusión,,0,80509,0 -28539,60131401,Platillos,,0,80509,0 -28540,60131402,Campanas,,0,80509,0 -28541,60131403,Panderetas,,0,80509,0 -28542,60131404,Castañuelas,,0,80509,0 -28543,60131405,Tambores,,0,80509,0 -28544,60131406,Xilófonos,,0,80509,0 -28545,60131407,Vibráfonos,Vibes,0,80509,0 -28546,60131408,Jing,,0,80509,0 -28547,60131409,Janggu,,0,80509,0 -28548,60131410,Kkwaengwari,"Gong coreano, Kkwaenggwari",0,80509,0 -28549,60131411,Buk,,0,80509,0 -28550,60131412,Galgo,,0,80509,0 -28551,60131413,Geongo,,0,80509,0 -28552,60131414,Gyobanggo,,0,80509,0 -28553,60131415,Banghyang,,0,80509,0 -28554,60131416,Sakko,,0,80509,0 -28555,60131417,Sogo,,0,80509,0 -28556,60131418,Yonggo,,0,80509,0 -28557,60131419,Ulla,,0,80509,0 -28558,60131420,Eunggo,,0,80509,0 -28559,60131421,Jabara,,0,80509,0 -28560,60131422,Jeolgo,,0,80509,0 -28561,60131423,Jwago,,0,80509,0 -28562,60131424,Jingo,,0,80509,0 -28563,60131425,Teukkyeong,,0,80509,0 -28564,60131426,Teukjong,,0,80509,0 -28565,60131427,Pyeongyeong,,0,80509,0 -28566,60131428,Pyeonjong,,0,80509,0 -28567,60131429,Eo,,0,80509,0 -28568,60131430,Nogo,,0,80509,0 -28569,60131431,Nodo,,0,80509,0 -28570,60131432,Noego,,0,80509,0 -28571,60131433,Noedo,,0,80509,0 -28572,60131434,Bak,,0,80509,0 -28573,60131435,Bu,,0,80509,0 -28574,60131436,Yeonggo,,0,80509,0 -28575,60131437,Yeongdo,,0,80509,0 -28576,60131438,Junggo,,0,80509,0 -28577,60131439,Chuk,,0,80509,0 -28578,60131440,Mugo,,0,80509,0 -28579,60131441,Carrillón,Xilófono,0,80509,0 -28580,60131442,Maracas,,0,80509,0 -28581,60131443,Cabasas brasil,,0,80509,0 -28582,60131444,Timbales,,0,80509,0 -28583,60131445,Bongoes,Bongos,0,80509,0 -28584,60131446,Congas,,0,80509,0 -28585,60131447,Armónica de metal,Lira de metal,0,80509,0 -28586,60131448,Marimbas,,0,80509,0 -28587,60131449,Triángulo instrumental,,0,80509,0 -28588,60131450,Maracas,Huevo rítmico,0,80509,0 -28589,60131451,Güiros,,0,80509,0 -28590,60131452,Agitadores (shakers) musicales,Ganzá,0,80509,0 -28591,60131453,Gong de madera,,0,80509,0 -28592,60131454,Campanas (instrumento),Campanas musicales,0,80509,0 -28593,60131455,Gong de bronce o latón,,0,80509,0 -28594,60131456,Tom-tom,Tambor,0,80509,0 -28595,60131457,Timbal,,0,80509,0 -28596,60131458,Palo de lluvia,,0,80509,0 -28597,60131459,Tinya,,0,80509,0 -28598,60131460,Ton-ton,Batería,0,80509,0 -28599,60131500,Piezas y accesorios de instrumentos musicales,,0,80509,0 -28600,60131501,Metrónomos,,0,80509,0 -28601,60131502,Lengüetas,,0,80509,0 -28602,60131503,Cuerdas o plectros para instrumentos,,0,80509,0 -28603,60131504,Clavijas de afinación,,0,80509,0 -28604,60131505,Bases o soportes de partituras para instrumentos musicales,,0,80509,0 -28605,60131506,Accesorios para instrumentos de cuerda,,0,80509,0 -28606,60131507,Accesorio para instrumentos de percusión,,0,80509,0 -28607,60131508,Cajas musicales o mecanismos,,0,80509,0 -28608,60131509,Boquillas,,0,80509,0 -28609,60131510,Fundas o estuches o accesorios para instrumentos musicales,,0,80509,0 -28610,60131511,Sordinas,Silenciadores musicales,0,80509,0 -28611,60131512,Barras de afinación,,0,80509,0 -28612,60131513,Batutas,,0,80509,0 -28613,60131514,Almohadillas de flautín,,0,80509,0 -28614,60131515,Unidad de efectos para instrumentos musicales,,0,80509,0 -28615,60131516,Afinador de instrumentos electrónicos,Metróno de afinación electrónico,0,80509,0 -28616,60131517,Estuche o maleta de mano protectora para instrumentos musicales,Estuche o maleta para instrumentos musicales,0,80509,0 -28617,60131518,Correa o arnés para instrumentos musicales,,0,80509,0 -28618,60131519,Pedestal para instrumentos musicales,Soporte o atril para instrumentos musicales,0,80509,0 -28619,60131520,Baqueta,,0,80509,0 -28620,60131600,Sets de instrumentos musicales,,0,80509,0 -28621,60131601,Sets de grupos de acompañamiento,Conjunto de ritmos para bandas,0,80509,0 -28622,60131700,Instrumentos musicales de sonidos alternativos,,0,80509,0 -28623,60131701,Tubos de percusión,Tubo sonoro diatónico,0,80509,0 -28624,60131702,Golpecitos de disco,,0,80509,0 -28625,60131703,Cascos de caballos,Pezuña de caballo,0,80509,0 -28626,60131800,Accesorios de música y danza,,0,80509,0 -28627,60131801,Velos para danza,,0,80509,0 -28628,60131802,Palitos de ritmo,Palos de percusión,0,80509,0 -28629,60131803,Aros o bastones de ritmo,Ula ula o cintas para baile,0,80509,0 -28630,60141000,Juguetes,,0,80509,0 -28631,60141001,Globos o pelotas de juguete,,0,80509,0 -28632,60141002,Muñecas,,0,80509,0 -28633,60141003,Casas de muñecas,,0,80509,0 -28634,60141004,Animales o títeres de peluche,,0,80509,0 -28635,60141005,Casas de juego,,0,80509,0 -28636,60141006,Bloques de construcción,,0,80509,0 -28637,60141007,Juguetes para montar,,0,80509,0 -28638,60141008,Juguetes para jalar,,0,80509,0 -28639,60141009,Kits de ciencias para niños,,0,80509,0 -28640,60141010,Vehículos de juguete,,0,80509,0 -28641,60141011,Trenes de juguete,,0,80509,0 -28642,60141012,Juguetes inflables,,0,80509,0 -28643,60141013,Partes o accesorios de muñecas,,0,80509,0 -28644,60141014,Yoyós,,0,80509,0 -28645,60141015,Cometas,Papalotes,0,80509,0 -28646,60141016,Tazos,,0,80509,0 -28647,60141017,Caleidoscopios,,0,80509,0 -28648,60141018,Pompones,,0,80509,0 -28649,60141019,Piñatas,,0,80509,0 -28650,60141020,Bumeranes,,0,80509,0 -28651,60141021,Discos voladores,,0,80509,0 -28652,60141022,Baldes de juguete,,0,80509,0 -28653,60141023,Juguetes para el baño,,0,80509,0 -28654,60141024,Sonajeros,Sonajas,0,80509,0 -28655,60141025,Armas de juguete,,0,80509,0 -28656,60141026,Trompos,,0,80509,0 -28657,60141100,Juegos,,0,80509,0 -28658,60141101,Juegos educativos,,0,80509,0 -28659,60141102,Juegos de mesa,,0,80509,0 -28660,60141103,Naipes,,0,80509,0 -28661,60141104,Juegos de video,,0,80509,0 -28662,60141105,Rompecabezas,,0,80509,0 -28663,60141106,Dados,,0,80509,0 -28664,60141107,Bingo,,0,80509,0 -28665,60141108,Juegos clásicos,,0,80509,0 -28666,60141109,Juegos colaborativos,,0,80509,0 -28667,60141110,Juegos de estrategia,,0,80509,0 -28668,60141111,Accesorios para juegos,,0,80509,0 -28669,60141112,Libros de juegos,,0,80509,0 -28670,60141113,Juegos de lotería,,0,80509,0 -28671,60141114,Juegos de memoria,,0,80509,0 -28672,60141115,Kits de juegos,,0,80509,0 -28673,60141116,Juego baduk o go y accesorios,Juego de go y accesorios,0,80509,0 -28674,60141117,Juego del lut o de cuatro palos y accesorios,Juego de loto de cuatro palos y accesorios,0,80509,0 -28675,60141118,Juego janggi y accesorios,Juego de ajedrez coreano y accesorios,0,80509,0 -28676,60141200,Equipo de juegos activos y accesorios,,0,80509,0 -28677,60141201,equipo para motricidad gruesa o equilibrio,Juegos de motricidad gruesa o equilibrio,0,80509,0 -28678,60141202,Piscinas de pelotas y accesorios,Alberca de pelotas y accesorios,0,80509,0 -28679,60141203,Casitas de juego,Cabañas de juguete,0,80509,0 -28680,60141204,Triciclos o carretillas,Velocípedo de tres ruedas,0,80509,0 -28681,60141205,zonas acolchadas de juego,Equipos acolchados de juego,0,80509,0 -28682,60141300,Sistemas de construcción y bloques infantiles,Bloques y sistemas de edificaciones para niños,0,80509,0 -28683,60141302,Sets de construcción,Kit de construcción,0,80509,0 -28684,60141303,Esteras para juegos,Alfombra para juegos,0,80509,0 -28685,60141304,Sistemas de trenes o accesorios,Rieles de juguete o accesorios,0,80509,0 -28686,60141305,Bloques de unidades,Bloques de madera para construcción,0,80509,0 -28687,60141306,Vehículos de juego,Automóviles de juguete,0,80509,0 -28688,60141307,Animales de juguete,Criaturas de juguete,0,80509,0 -28689,60141400,Equipo de representación teatral y accesorios,,0,80509,0 -28690,60141401,Disfraces o accesorios,,0,80509,0 -28691,60141402,Centros para vestirse,Vestidores para niños,0,80509,0 -28692,60141403,Unidades o accesorios para el cuidado de la casa,Artículos de limpieza para el cuidado de la casa,0,80509,0 -28693,60141404,Platos de comida de juguete o accesorios,,0,80509,0 -28694,60141405,Kits o materiales para juegos de simulación,Botiquín de primeros auxilios de juguete,0,80509,0 -7081,30151802,Recubrimiento,,0,80509,0 -7082,30151803,Empalmes de revestimiento de paredes,,0,80509,0 -7083,30151805,Estucado,,0,80509,0 -7084,30151806,Muros de bloque de vidrio,,0,80509,0 -7085,30151807,Materiales para terminados exteriores,,0,80509,0 -7086,30151808,Molde de ladrillo,,0,80509,0 -7087,30151900,Materiales y productos para acabados,,0,80509,0 -7088,30151901,Toldos,Cubierta de lona,0,80509,0 -7089,30151902,Marquesinas,,0,80509,0 -7090,30152000,Cercado,,0,80509,0 -7091,30152001,Cercado de metal,,0,80509,0 -7092,30152002,Cercado de madera,,0,80509,0 -7093,30152003,Vallado de fibrocemento,,0,80509,0 -7094,30161500,Materiales para acabados de paredes,,0,80509,0 -7095,30161501,Fibra prensada,Fibra de madera prensada,0,80509,0 -7096,30161502,Papel de colgadura,Papel tapiz,0,80509,0 -7097,30161503,Drywall,Tablaroca,0,80509,0 -7098,30161504,Protectores de esquinas,,0,80509,0 -7099,30161505,Paneles o empanelado,,0,80509,0 -7100,30161508,Rodillo de papel de colgadura,Rodillo de papel tapiz,0,80509,0 -7101,30161509,Tabla de yeso,,0,80509,0 -7102,30161510,Cobertura de pared plástica,Revestimiento de pared de plástico,0,80509,0 -7103,30161511,Tela de pared,Tejidos para pared,0,80509,0 -7104,30161600,Materiales para techos,,0,80509,0 -7105,30161601,Baldosines acústicos para techos,Azulejos de techo acústicos,0,80509,0 -7106,30161602,Paneles para techos,Lamina panel para techo,0,80509,0 -7107,30161603,Encofrados,,0,80509,0 -7108,30161604,Sistemas de cielo raso,,0,80509,0 -7109,30161700,Suelos,,0,80509,0 -7110,30161701,Alfombrado,,0,80509,0 -7111,30161702,Pisos de madera,,0,80509,0 -7112,30161703,Linóleo,,0,80509,0 -7113,30161705,Pisos de caucho,,0,80509,0 -7114,30161706,Pisos de baldosa o piedra,,0,80509,0 -7115,30161707,Pisos de vinilo,,0,80509,0 -7116,30161708,Alfombras de nudos,,0,80509,0 -7117,30161709,Alfombras de penachos,,0,80509,0 -7118,30161710,Pisos laminados,,0,80509,0 -7119,30161711,Alfombras para exteriores,,0,80509,0 -7120,30161712,Perfiles laminados para suelos,,0,80509,0 -7121,30161713,Almohadilla para alfombras,,0,80509,0 -7122,30161714,Suelo de corcho,,0,80509,0 -7123,30161715,Enrejados de madera,,0,80509,0 -7124,30161717,Pisos para accesos,,0,80509,0 -7125,30161718,Piso de acero antideslizante,,0,80509,0 -7126,30161719,Fieltro para alfombras,,0,80509,0 -7127,30161720,Panel de piso,,0,80509,0 -7128,30161721,Umbral de piso,,0,80509,0 -7129,30161800,Ebanistería,,0,80509,0 -7130,30161801,Armarios,,0,80509,0 -7131,30161803,Armarios domésticos,,0,80509,0 -7132,30161804,Armarios de escuela,,0,80509,0 -7133,30161806,Closet,,0,80509,0 -7134,30161807,Cajón,,0,80509,0 -7135,30161808,Ensamble para deslizamiento del cajón,,0,80509,0 -7136,30161809,Almohadillas contra cierre brusco,,0,80509,0 -7137,30161810,Gabinete tri – view,,0,80509,0 -7138,30161900,Moldeado y carpintería mecánica,,0,80509,0 -7139,30161901,Puertas de rejilla,,0,80509,0 -7140,30161902,Columnas,,0,80509,0 -7141,30161903,Paneles de madera,,0,80509,0 -7142,30161904,Cornisas,,0,80509,0 -7143,30161905,Cercos de puertas,,0,80509,0 -7144,30161906,Moldes,,0,80509,0 -7145,30161908,Piezas de escaleras,,0,80509,0 -7146,30162000,Molduras y carpintería,,0,80509,0 -7147,30162001,Laminado de alta presión,,0,80509,0 -7148,30162002,Laminado especial,,0,80509,0 -7149,30162003,Laminado de metal,,0,80509,0 -7150,30162004,Laminado de reborde,,0,80509,0 -7151,30162005,Lámina térmica rígida o rtf,,0,80509,0 -7152,30162100,Laminados interiores,,0,80509,0 -7153,30162101,Escaleras de acero,,0,80509,0 -7154,30162102,Escaleras de madera,,0,80509,0 -7155,30162103,Escaleras de concreto,,0,80509,0 -7156,30162104,Arista del escalón,Escalón,0,80509,0 -7157,30162200,Mostradores,,0,80509,0 -7158,30162201,Mesón laminado,Mesón de cocina laminado,0,80509,0 -7159,30162202,Mesón de mármol cultivado,Mesón de cocina de mármol cultivado,0,80509,0 -7160,30162203,Mesón de superficie sólida,Masón de cocina de superficie sólida,0,80509,0 -7161,30162204,Mesón de granito,Masón de cocina de granito,0,80509,0 -7162,30162300,Accesorios para gabinetes,,0,80509,0 -7163,30162301,Piezas o ensamble de “lazy susan”,,0,80509,0 -7164,30162302,Engaste de rejilla o cajón para condimentos,,0,80509,0 -7165,30162303,Piezas o ensamble de caneca de la basura de sacar,Piezas para contenedor de basura,0,80509,0 -7166,30162304,Piezas o ensamble de despensa,Accesorios para muebles de despensa,0,80509,0 -7167,30162305,Piezas o ensamble de bandeja de inclinar,Piezas para cajón de inclinación hacia afuera,0,80509,0 -7168,30162306,Piezas o ensamble de montura de puerta o toalla deslizable,,0,80509,0 -7169,30162307,Piezas o ensamble de ancheta de sacar o voltear,,0,80509,0 -7170,30162308,Tapa o ensamble de caja para el pan,,0,80509,0 -7171,30162309,Estante para copas,,0,80509,0 -7172,30162310,Bandera rodante,Cajón deslizable para bajo mesa,0,80509,0 -7173,30162311,Recipiente de almacenamiento de deslizar o su ensamble,,0,80509,0 -7174,30162400,Muros divisorios,,0,80509,0 -7175,30162401,Pared plegable,,0,80509,0 -7176,30162402,Pantalla o biombo o cubículo,,0,80509,0 -7177,30162403,Sistema de pared interior,,0,80509,0 -7178,30162404,Pared de cuarto frío,Pared de almacenamiento en frio,0,80509,0 -7179,30171500,Puertas,,0,80509,0 -7180,30171501,Puertas de cristal,,0,80509,0 -7181,30171502,Puertas de pantalla,,0,80509,0 -7182,30171503,Puertas rodantes,,0,80509,0 -7183,30171504,Puertas de madera,,0,80509,0 -7184,30171505,Puertas de metal,,0,80509,0 -7185,30171506,Contrapuertas,,0,80509,0 -7186,30171507,Marcos de puertas o quicios,,0,80509,0 -7187,30171508,Puertas corrediza empotrable,,0,80509,0 -7188,30171509,Puertas giratorias,,0,80509,0 -7189,30171510,Puertas automáticas,,0,80509,0 -7190,30171511,Puerta de vaivén,,0,80509,0 -7191,30171512,Abridores de puertas,Puerta con brazo hidráulico,0,80509,0 -7192,30171513,Rodapiés,Placa de puerta o protector,0,80509,0 -7193,30171514,Cerradores de puertas,Brazo hidráulico,0,80509,0 -7194,30171515,Puerta de inspección,,0,80509,0 -7195,30171516,Pantalla para puertas,,0,80509,0 -7196,30171517,Puertas a prueba de sonido,Puertas aislantes de sonido,0,80509,0 -7197,30171518,Puertas imperfectas,,0,80509,0 -7198,30171519,Puertas anti – explosión,,0,80509,0 -7199,30171520,Loop de puerta y ventana,,0,80509,0 -7200,30171521,Puerta hermética contra agua,Puerta contra inundación,0,80509,0 -7201,30171522,Puerta hermética sin circulación de aire,,0,80509,0 -7202,30171523,Puerta de escape de incendio,,0,80509,0 -7203,30171524,Puerta de protección contra rayos,,0,80509,0 -7204,30171525,Puerta de presión,,0,80509,0 -7205,30171526,Puerta plegable,,0,80509,0 -7206,30171600,Ventanas,,0,80509,0 -7207,30171604,Ventanas de guillotina,,0,80509,0 -7208,30171605,Ventanas de apertura horizontal,,0,80509,0 -7209,30171606,Ventanas giratorias,,0,80509,0 -7210,30171607,Ventanas con hoja de desplazamiento horizontal,,0,80509,0 -7211,30171608,Ventanas basculantes o de montante,Ventanas de inclinación o travesaño,0,80509,0 -7212,30171609,Ventanas fijas,,0,80509,0 -7213,30171610,Miradores,Ventanas de bahía,0,80509,0 -7214,30171611,Ventana de miradores,Ventanas de arco,0,80509,0 -7215,30171612,Ventanas salientes,Ventanas proyectadas,0,80509,0 -7216,30171613,Paredes de vidrio,"Cancelería, Canceles",0,80509,0 -7217,30171614,Pantallas de ventana,Película protectora para cristales de ventana,0,80509,0 -7218,30171615,Ventanas francesas o puerta – ventanas,,0,80509,0 -7219,30171700,Productos de cristal,,0,80509,0 -7220,30171701,Adoquines,,0,80509,0 -7221,30171703,Vidrio biselado,,0,80509,0 -7222,30171704,Vidrio con plomo,,0,80509,0 -7223,30171705,Vidrio laminado,,0,80509,0 -7224,30171706,Vidrio templado,,0,80509,0 -7225,30171707,Vidrio de seguridad,,0,80509,0 -7226,30171708,Vidrio flotado,,0,80509,0 -7227,30171709,Vidrio con alambre incrustado,Vidrio armado,0,80509,0 -7228,30171710,Vidrio de aislación,,0,80509,0 -7229,30171711,Vidrio corrugado,,0,80509,0 -7230,30171712,Vitrales,,0,80509,0 -7231,30171800,Claraboyas,Tragaluces,0,80509,0 -7232,30171801,Claraboyas fijas,Tragaluz fijo,0,80509,0 -7233,30171802,Claraboyas con ventilación,Tragaluz con ventilación,0,80509,0 -7234,30171803,Claraboyas de tubo,Tragaluz tubular,0,80509,0 -7235,30171804,Tapa de humo,,0,80509,0 -7236,30171900,Marcos de ventanas,,0,80509,0 -7237,30171901,Marcos para ventanas de guillotina,,0,80509,0 -7238,30171902,Marcos para ventanas de apertura horizontal,,0,80509,0 -7239,30171903,Marcos para ventanas giratorias,,0,80509,0 -7240,30171904,Marcos para ventanas con hojas de desplazamiento horizontal,,0,80509,0 -7241,30171905,Marcos para ventanas basculantes o de montante,Marcos de ventana de inclinación o travesaño,0,80509,0 -7242,30171906,Marcos para ventanas fijas,,0,80509,0 -7243,30171907,Alfeizar o antepecho,Travesaño de ventana,0,80509,0 -7244,30171908,Arco de ventana,,0,80509,0 -7245,30172000,Puertas,,0,80509,0 -7246,30172001,Puerta de barra sencilla,Puerta de una sola barra,0,80509,0 -7247,30172002,Puerta de doble barra,Puerta de doble barra,0,80509,0 -7248,30172100,Puertas para garaje y operadores,,0,80509,0 -7249,30172101,Puerta de garaje pan,,0,80509,0 -7250,30172102,Puerta de garaje en sándwich aislada,Puerta de garaje de panel sándwich,0,80509,0 -7251,30172103,Puerta de garaje de madera,,0,80509,0 -7252,30172104,Puerta de garaje de carroza,Puerta de garaje de carro,0,80509,0 -7253,30172105,Puerta de garaje de pantalla,,0,80509,0 -7254,30172106,Puerta de garaje de de pvc,,0,80509,0 -7255,30172107,Puerta de garaje de eslabones de cadena,,0,80509,0 -7256,30172108,Puerta de garaje rodante,,0,80509,0 -7257,30172109,Componente de puerta de garaje,,0,80509,0 -7258,30172110,Accesorio de puerta de garaje,,0,80509,0 -7259,30172111,Operador completo de puerta de garaje,,0,80509,0 -7260,30172112,Componente de operador de puerta de garaje,,0,80509,0 -7261,30172113,Accesorio de operador de puerta de garaje,,0,80509,0 -7262,30181500,Porcelana sanitaria,,0,80509,0 -7263,30181501,Tinas o bañeras,,0,80509,0 -7264,30181502,Bidés,Bidet,0,80509,0 -7265,30181503,Duchas,Regadera,0,80509,0 -7266,30181504,Lavamanos,Lavabo,0,80509,0 -7267,30181505,Inodoros o excusados,Cuarto de baño,0,80509,0 -7268,30181506,Orinales,,0,80509,0 -7269,30181507,Cerramientos para bañeras o duchas,Bañera,0,80509,0 -7270,30181508,Divisiones de baños,,0,80509,0 -7271,30181511,Inodoros o excusados,,0,80509,0 -7272,30181514,Tapa de tanque del inodoro,,0,80509,0 -7273,30181515,Tanque del inodoro,,0,80509,0 -7274,30181516,Sauna,,0,80509,0 -7275,30181517,Jacuzzi,,0,80509,0 -7276,30181600,Instalaciones residenciales no sanitarias,Accesorios para cuarto de baño,0,80509,0 -7277,30181601,Jabonera,,0,80509,0 -7278,30181602,Toallero,,0,80509,0 -7279,30181603,Bizcocho o asiento del inodoro,Asiento de inodoro,0,80509,0 -7280,30181604,Tapa del bizcocho,Tapa de inodoro,0,80509,0 -7281,30181605,Desagüe,,0,80509,0 -7282,30181606,Ganchos de baño,,0,80509,0 -7283,30181607,Cortina de baño o su ensamble,,0,80509,0 -7284,30181608,Varilla de ducha,,0,80509,0 -7285,30181609,Soporte para frascos de ducha,,0,80509,0 -7286,30181610,Soporte de papel higiénico,,0,80509,0 -7287,30181611,Soporte de cepillos de dientes o vaso,,0,80509,0 -7288,30181612,Gancho para la máquina afeitadora,,0,80509,0 -7289,30181613,Faldón de la tina o del jacuzzi,,0,80509,0 -7290,30181614,Dispensador de jabón,,0,80509,0 -7291,30181700,Grifos,Llaves,0,80509,0 -7292,30181701,Llave de registro,"Fregadero, Llave de paso, Tarj",0,80509,0 -7293,30181800,"Cabezas, chorros y partes y accesorios de grifos y duchas","Grifería y cabezales de ducha, chorros y piezas y accesorios",0,80509,0 -7294,30181801,Pomo de ducha,Regadera,0,80509,0 -7295,30181802,Aireador de la ducha,Filtro aireador para regadera,0,80509,0 -7296,30181803,Ducha teléfono,Regadera de extensión manual,0,80509,0 -7297,30181804,Grifo,Llaves para regadera,0,80509,0 -7298,30181805,Ducha combinada fija y teléfono,Sistema de regadera mixta,0,80509,0 -7299,30181806,Chorros,Bañera de hidromasaje,0,80509,0 -7300,30181807,Boquilla,Tubo de bañera con desviador,0,80509,0 -7301,30181808,Válvulas ocultas,Válvulas para regaderas,0,80509,0 -7302,30181809,Kit de reparación de duchas teléfono,Juego de reparación para ducha manual,0,80509,0 -7303,30181810,Ajuste de grifo,,0,80509,0 -7304,30181811,Kit de reparación de grifo,,0,80509,0 -7305,30181812,Rociador de cuerpo,Regaderas corporales,0,80509,0 -7306,30191500,Escaleras y andamios,,0,80509,0 -7307,30191501,Escaleras,,0,80509,0 -7308,30191502,Andamios,,0,80509,0 -7309,30191505,Taburete escalonado,Banquillo,0,80509,0 -7310,30191506,Escalón de plataforma,Escaleras de plataforma,0,80509,0 -7311,30191507,Escalera de combinación,Escalera combinada extensible,0,80509,0 -7312,30191508,Escalera de techo,Escaleras plegables para techo,0,80509,0 -7313,30191509,Escalera de escape de incendios,Escalera de escape contra incendios,0,80509,0 -7314,30191510,Escalera de extinguidor de incendios,Escalera para sofocar incendios,0,80509,0 -7315,30191600,Escaleras y accesorios de andamiaje,,0,80509,0 -7316,30191601,Barandas,Barandilla,0,80509,0 -7317,30191602,Estabilizadores de andamiaje,,0,80509,0 -7318,30191603,Piso del andamiaje,,0,80509,0 -7319,30191604,Varillas cruzadas del andamio,,0,80509,0 -7320,30191605,Marco del andamio,,0,80509,0 -7321,30191606,Escalera del andamio,,0,80509,0 -7322,30191607,Contrapeso del andamio,,0,80509,0 -7323,30191608,Ménsula del andamio,,0,80509,0 -7324,30191609,Plataforma del andamio,,0,80509,0 -7325,30191610,Pescante del andamio,,0,80509,0 -7326,30191611,Dispositivo de fijación del andamio,,0,80509,0 -7327,30191612,Cuerda de alambre del andamio,,0,80509,0 -7328,30191613,Plataforma de la escalera,,0,80509,0 -7329,30191614,Bandeja de la escalera,,0,80509,0 -7330,30191615,Descanso de la escalera,,0,80509,0 -7331,30191616,Red de seguridad de construcción,,0,80509,0 -7332,30191617,Cerramiento de seguridad de la construcción,,0,80509,0 -7333,30191700,Cobertizos y remolques de construcción,,0,80509,0 -7334,30191701,Caseta de construcción,,0,80509,0 -7335,30191702,Tráiler de construcción,,0,80509,0 -7336,30191800,Equipos y materiales para construcciones temporales y apoyo al mantenimiento,,0,80509,0 -7337,30191801,Formaleta estructural,,0,80509,0 -7338,30191802,Accesorio de formaleta estructural,,0,80509,0 -7339,30191803,Espaciador estructural,,0,80509,0 -7340,30241500,Componentes de consolidación de estructuras portátiles,,0,80509,0 -7341,30241501,Conector de marcos de tubos,,0,80509,0 -7342,30241502,Placa de anclaje,,0,80509,0 -7343,30241503,Anillo de soporte,,0,80509,0 -7344,30241504,Placa base,,0,80509,0 -7345,30241505,Cúpula,,0,80509,0 -7346,30241506,Campana funicular,,0,80509,0 -7347,30241507,Aguilón,,0,80509,0 -7348,30241508,Poste grúa y accesorios,,0,80509,0 -7349,30241509,Sección de mástil y poste rey,,0,80509,0 -7350,30241510,Poste lateral,,0,80509,0 -7351,30241511,Estaca o clavija,,0,80509,0 -7352,30241512,Travesaño,,0,80509,0 -7353,30241513,Marco en a de acero,,0,80509,0 -7354,30241514,Sujetador estructural,,0,80509,0 -7355,30241515,Herramienta de alineación estructural,,0,80509,0 -7356,30241600,"Componentes estructurales de tribunas, gradas y escalera",,0,80509,0 -7357,30241601,Tarima y accesorios de la tribuna,,0,80509,0 -7358,30241602,Escalón,,0,80509,0 -7359,30241603,Durmiente,,0,80509,0 -7360,30241604,Elevador de escalones,,0,80509,0 -7361,30241700,"Componentes soporte y cobertura de tiendas, carpas y estructuras de membrana",,0,80509,0 -7362,30241701,Viga o riel o tubería de marco,,0,80509,0 -7363,30241702,Sección de lienzo de estructura portátil,,0,80509,0 -7364,30251500,Estructuras de soporte de techos bajo tierra,,0,80509,0 -7365,30251501,Perno y armazón de combinación de techo,,0,80509,0 -7366,30251502,Placa de techo,,0,80509,0 -7367,30251503,Cápsula o cartucho de resina adhesiva,,0,80509,0 -7368,30251504,Celda de expansión,,0,80509,0 -7369,30251505,Estera de nervadura,,0,80509,0 -7370,30261500,Barras de latón,,0,80509,0 -7371,30261501,Barra de latón enrollado en caliente c 360,,0,80509,0 -7372,30261502,Barra de latón forjado en caliente c 360,,0,80509,0 -7373,30261503,Barra de latón enrollado en caliente c 464,,0,80509,0 -7374,30261504,Barra de latón forjado en caliente c 464,,0,80509,0 -7375,30261505,Barra de latón estirado en frío c 464,,0,80509,0 -7376,30261600,Láminas de latón,,0,80509,0 -7377,30261601,Lámina de latón enrollado en caliente c 360,,0,80509,0 -7378,30261602,Lámina de latón forjado en caliente c 464,,0,80509,0 -7379,30261603,Lámina de latón enrollado en frío c 464,,0,80509,0 -7380,30261700,"Lingotes, bandas, palanquillas y alambres de latón",,0,80509,0 -7381,30261701,Alambre de latón,,0,80509,0 -7382,30261702,Banda de latón,,0,80509,0 -7383,30261703,Palanquilla de latón,,0,80509,0 -7384,30261704,Lingote de latón,,0,80509,0 -7385,30262200,Barras de cobre,,0,80509,0 -7386,30262201,Barra de cobre estirada en frío,,0,80509,0 -7387,30262202,Barra de cobre enrollada en caliente,,0,80509,0 -7388,30262400,"Lingotes, bandas, palanquillas y alambres de cobre",,0,80509,0 -7389,30262401,Alambre de cobre,,0,80509,0 -7390,30262402,Banda de cobre enrollada en caliente,,0,80509,0 -7391,30262403,Banda de cobre enrollada en frío,,0,80509,0 -7392,30262404,Palanquilla de cobre,,0,80509,0 -7393,30262405,Lingote de cobre,,0,80509,0 -7394,30262500,Barras de bronce,,0,80509,0 -7395,30262501,Barra de bronce fósforo enrollada en caliente,,0,80509,0 -7396,30262502,Barra de bronce fósforo enrollada en frío,,0,80509,0 -7397,30262503,Barra de bronce aluminio enrollada en caliente,,0,80509,0 -7398,30262504,Barra de bronce aluminio estirada en frío,,0,80509,0 -7399,30262505,Barra de bronce silicona enrollada en caliente,,0,80509,0 -7400,30262506,Barra de bronce silicona estirada en frío,,0,80509,0 -7401,30262800,Barras y láminas de zinc,,0,80509,0 -7402,30262801,Barra de zinc enrollada en caliente,,0,80509,0 -7403,30262802,Lámina de zinc enrollada en caliente,,0,80509,0 -7404,30262900,"Lingotes, bandas, palanquillas y alambres de zinc",,0,80509,0 -7405,30262901,Alambre de zinc,,0,80509,0 -7406,30262902,Banda de zinc,,0,80509,0 -7407,30262903,Palanquilla de zinc,,0,80509,0 -7408,30262904,Lingote de zinc,,0,80509,0 -7409,30263000,Barras de estaño,,0,80509,0 -7410,30263001,Barra de estaño estirada en frío,,0,80509,0 -7411,30263002,Barra de estaño enrollada en caliente,,0,80509,0 -7412,30263200,"Lingotes, bandas, palanquillas y alambres de estaño",,0,80509,0 -7413,30263201,Alambre de estaño,,0,80509,0 -7414,30263202,Banda de estaño,,0,80509,0 -7415,30263203,Lingote de estaño,,0,80509,0 -7416,30263500,"Lingotes, bandas, palanquillas y alambres de plomo",,0,80509,0 -7417,30263501,Alambre de plomo,,0,80509,0 -7418,30263502,Banda de plomo,,0,80509,0 -7419,30263503,Palanquilla de plomo,,0,80509,0 -7420,30263504,Lingote de plomo,,0,80509,0 -7421,30263600,Barras de acero al carbón,,0,80509,0 -6613,27112901,Pistolas de grasa,,0,80509,0 -6614,27112902,Chimeneas industriales,,0,80509,0 -6615,27112903,Rociador manual,,0,80509,0 -6616,27112904,Pistola de resina,,0,80509,0 -6617,27112905,Lata de aceite,,0,80509,0 -6618,27112906,Pistolas de calafateado,,0,80509,0 -6619,27112907,Separadores de difusión,,0,80509,0 -6620,27112908,Pistola de aceite,,0,80509,0 -6621,27112909,Trapero para techos,,0,80509,0 -6622,27112910,Almacenamiento portátil para desperdicio de aceite,,0,80509,0 -6623,27112911,Copa de aceite,,0,80509,0 -6624,27112912,Cambiador de aceite,,0,80509,0 -6625,27112913,Lubricador de aceite,,0,80509,0 -6626,27112914,Lubricador de grasa,,0,80509,0 -6627,27112915,Dispensador de gas natural,,0,80509,0 -6628,27112916,Dispensador de combustible líquido o bomba de gasolina,,0,80509,0 -6629,27113000,Cepillos,,0,80509,0 -6630,27113001,Cepillos de aruñar,Cepillo de alambre,0,80509,0 -6631,27113002,Cepillos de tubo,Escobilla de tubo,0,80509,0 -6632,27113003,Cepillos de aplicar,Cepillo para impermeabilizar,0,80509,0 -6633,27113004,Cepillos de extensor,Rodillo para impermeabilizar,0,80509,0 -6634,27113005,Cepillo de techado,Escoba cepillo,0,80509,0 -6635,27113100,Herramientas de arrastre,,0,80509,0 -6636,27113101,Cinta guía,,0,80509,0 -6637,27113102,Tirador de fusible,Extractor de fusibles,0,80509,0 -6638,27113103,Buril,Cuchilla de corte,0,80509,0 -6639,27113104,Extractores de estacas,Removedor de estaca,0,80509,0 -6640,27113105,Extensor de resortes,,0,80509,0 -6641,27113200,Juegos de herramientas,,0,80509,0 -6642,27113201,Conjuntos generales de herramientas,,0,80509,0 -6643,27113202,Kit de herramientas para ajustar rodamiento,,0,80509,0 -6644,27113203,Kit de herramienta para computadores,,0,80509,0 -6645,27113204,Kits de electricista,,0,80509,0 -6646,27113300,Herramientas manuales de precisión,,0,80509,0 -6647,27113301,Resortes de retorno a presión,,0,80509,0 -6648,27121500,Prensas hidráulicas,,0,80509,0 -6649,27121501,Suspensiones a presión retornables,,0,80509,0 -6650,27121502,Bastidores de prensa hidráulica,,0,80509,0 -6651,27121503,Columnas de prensa hidráulica,,0,80509,0 -6652,27121504,Prensa hidráulica industrial,,0,80509,0 -6653,27121600,Pistones y cilindros hidráulicos,,0,80509,0 -6654,27121601,Pistones de cilindro,,0,80509,0 -6655,27121602,Cilindros hidráulicos,,0,80509,0 -6656,27121603,Vástagos de pistón de cilindro hidráulico,,0,80509,0 -6657,27121604,Kits de reparación de cilindro hidráulico o sus componentes,,0,80509,0 -6658,27121605,Cuerpos de cilindro hidráulico,,0,80509,0 -6659,27121606,Soportes de montaje para cilindros hidráulicos,,0,80509,0 -6660,27121700,Accesorios de tubería y manga hidráulica,,0,80509,0 -6661,27121701,Conectores hidráulicos rápidos,Coples hidráulicos rápidos,0,80509,0 -6662,27121702,Injertos o injertos dobles hidráulicos,Injertos dobles hidráulicos,0,80509,0 -6663,27121703,Férulas,Ferrules,0,80509,0 -6664,27121704,Uniones hidráulicas,Acoples hidráulicos,0,80509,0 -6665,27121705,Codos hidráulicos o de compresión,Codos hidráulicos de alta compresión,0,80509,0 -6666,27121706,Tuercas de férula,,0,80509,0 -6667,27121707,Conectores de pliegue,Conectores de cripado,0,80509,0 -6668,27121800,Herramientas hidráulicas,,0,80509,0 -6669,27121801,Levantadores de cubierta de manhole,,0,80509,0 -6670,27121802,Acumuladores hidráulicos,,0,80509,0 -6671,27121803,Almejas hidráulicas,,0,80509,0 -6672,27121804,Cizallas hidráulicas,,0,80509,0 -6673,27121805,Extractor hidráulico,,0,80509,0 -6674,27121806,Doblador de tubos hidráulico,,0,80509,0 -6675,27121807,Halador hidráulico,Extractor hidráulico,0,80509,0 -6676,27121808,Cortador de cadenas hidráulico,,0,80509,0 -6677,27121809,Herramienta hidráulica para romper tuercas,Herramienta hidráulica de corte de tuerca,0,80509,0 -6678,27121810,Herramienta engastadora hidráulica manual,Herramienta hidráulica de crimpado manual,0,80509,0 -6679,27121811,Accesorio de herramienta engastadora hidráulica,Accesorio de herramienta crimpado hidráulica,0,80509,0 -6680,27121812,Remoto hidráulico,Mando a distancia hidráulico,0,80509,0 -6681,27131500,Herramientas neumáticas,,0,80509,0 -6682,27131501,Llaves de impacto neumático,Pistola neumática de impacto,0,80509,0 -6683,27131502,Pistola de aire comprimido,,0,80509,0 -6684,27131504,Martillo neumático,Martillo cincelador neumático,0,80509,0 -6685,27131505,Taladro neumático,Taladro de impacto neumático,0,80509,0 -6686,27131506,Clavadora de clavos neumática,Clavadora neumática,0,80509,0 -6687,27131507,Máquinas de lijado neumáticas,,0,80509,0 -6688,27131508,Motores neumáticos,Motores de aire comprimido,0,80509,0 -6689,27131509,Acumuladores neumáticos,Acumulador de aire comprimido,0,80509,0 -6690,27131510,Depósitos de acumulador,,0,80509,0 -6691,27131511,Rectificadoras neumáticas,,0,80509,0 -6692,27131512,Destornillador neumático,,0,80509,0 -6693,27131513,Lima neumática,,0,80509,0 -6694,27131514,Alicates neumáticos,Alicates neumático de corte,0,80509,0 -6695,27131515,Llave neumática de no – impacto,Llave de impacto,0,80509,0 -6696,27131516,Lápiz grabador neumático,,0,80509,0 -6697,27131517,Grapadora neumática industrial,,0,80509,0 -6698,27131518,Herramienta neumática para remover vidrios de autos,Pistola neumática para remover o desprender parabrisas,0,80509,0 -6699,27131519,Punzón neumático o de aire,,0,80509,0 -6700,27131520,Cortador neumático de varillas,,0,80509,0 -6701,27131521,Ribeteador neumático,,0,80509,0 -6702,27131522,Lija neumática miniatura,,0,80509,0 -6703,27131523,Martillo neumático,,0,80509,0 -6704,27131524,Avellanadora neumática,Maquina avellanadora de tubo neumático,0,80509,0 -6705,27131525,Cizallas neumáticas,,0,80509,0 -6706,27131526,Prensa neumática,,0,80509,0 -6707,27131527,Juego de herramientas neumáticas,,0,80509,0 -6708,27131528,Sacador de puntillas neumático,,0,80509,0 -6709,27131600,Accesorios de aire y conectores,,0,80509,0 -6710,27131601,Múltiples de aire,,0,80509,0 -6711,27131603,Reguladores de aire,Reguladores de aire comprimido,0,80509,0 -6712,27131604,Lubricadores neumáticos,,0,80509,0 -6713,27131605,Cortina de aire,,0,80509,0 -6714,27131608,Conos o almohadillas de vacío,,0,80509,0 -6715,27131609,Silenciadores neumáticos,,0,80509,0 -6716,27131610,Kits de reparación del engrasador o el regulador,,0,80509,0 -6717,27131613,Acoplamiento neumático,,0,80509,0 -6718,27131614,Adaptadores neumáticos,,0,80509,0 -5964,26121663,Cable sencillo de aluminio conductor de servicio,,0,80509,0 -5965,26121664,Alambre de aluminio conductor recubierto de polietileno,,0,80509,0 -5966,26121665,Cable de aluminio de bajada de servicio triple,,0,80509,0 -5967,26121666,Conjunto de cable de comunicación,,0,80509,0 -5968,26121700,Cableado preformado,,0,80509,0 -5969,26121701,Cableado preformado de panel,,0,80509,0 -5970,26121702,Cableado preformado troncal,,0,80509,0 -5971,26121703,Cableado preformado de comunicación,,0,80509,0 -5972,26121704,Arnés de alambrado especial,,0,80509,0 -5973,26121706,Conjunto coaxial,,0,80509,0 -5974,26121707,Conjunto de cable plano flexible,,0,80509,0 -5975,26121708,Conjunto de cable de batería,,0,80509,0 -5976,26121709,Conjunto de cordón embobinado,,0,80509,0 -5977,26121710,Arnés de cableado sensor oxígeno,Arnés cableado de sensor de oxigeno,0,80509,0 -5978,26121711,Varilla de pistón,,0,80509,0 -5979,26121800,Cable automotriz,,0,80509,0 -5980,26121802,Cable automotriz de núcleo sencillo de 60 voltios clase a,,0,80509,0 -5981,26121803,Cable automotriz de núcleo sencillo de 60 voltios clase b,,0,80509,0 -5982,26121804,Cable automotriz de núcleo sencillo de 60 voltios clase c,,0,80509,0 -5983,26121805,Cable automotriz de núcleo sencillo de 60 voltios clase d,,0,80509,0 -5984,26121806,Cable automotriz de núcleo sencillo de 60 voltios clase e,,0,80509,0 -5985,26121807,Cable automotriz de núcleo sencillo de 60 voltios clase f,,0,80509,0 -5986,26121808,Cable automotriz de núcleo sencillo de 60 voltios clase g,,0,80509,0 -5987,26121809,Cable automotriz de núcleo sencillo de 60 voltios clase h,,0,80509,0 -5988,26121810,Cable automotriz de núcleo sencillo de 600 voltios clase a,,0,80509,0 -5989,26121811,Cable automotriz de núcleo sencillo de 600 voltios clase b,,0,80509,0 -5990,26121812,Cable automotriz de núcleo sencillo de 600 voltios clase c,,0,80509,0 -5991,26121813,Cable automotriz de núcleo sencillo de 600 voltios clase d,,0,80509,0 -5992,26121814,Cable automotriz de núcleo sencillo de 600 voltios clase e,,0,80509,0 -5993,26121815,Cable automotriz de núcleo sencillo de 600 voltios clase f,,0,80509,0 -5994,26121816,Cable automotriz de núcleo sencillo de 600 voltios clase g,,0,80509,0 -5995,26121817,Cable automotriz de núcleo sencillo de 600 voltios clase h,,0,80509,0 -5996,26121818,Cable automotriz de núcleo múltiple de 60 voltios clase a,,0,80509,0 -5997,26121819,Cable automotriz de núcleo múltiple de 60 voltios clase b,,0,80509,0 -5998,26121820,Cable automotriz de núcleo múltiple de 60 voltios clase c,,0,80509,0 -5999,26121821,Cable automotriz de núcleo múltiple de 60 voltios clase d,,0,80509,0 -6000,26121822,Cable automotriz de núcleo múltiple de 60 voltios clase e,,0,80509,0 -6001,26121823,Cable automotriz de núcleo múltiple de 60 voltios clase f,,0,80509,0 -6002,26121824,Cable automotriz de núcleo múltiple de 60 voltios clase g,,0,80509,0 -6003,26121825,Cable automotriz de núcleo múltiple de 60 voltios clase h,,0,80509,0 -6004,26121826,Cable automotriz de núcleo múltiple de 600 voltios clase a,,0,80509,0 -6005,26121827,Cable automotriz de núcleo múltiple de 600 voltios clase b,,0,80509,0 -6006,26121828,Cable automotriz de núcleo múltiple de 600 voltios clase c,,0,80509,0 -6007,26121829,Cable automotriz de núcleo múltiple de 600 voltios clase d,,0,80509,0 -6008,26121830,Cable automotriz de núcleo múltiple de 600 voltios clase e,,0,80509,0 -6009,26121831,Cable automotriz de núcleo múltiple de 600 voltios clase f,,0,80509,0 -6010,26121832,Cable automotriz de núcleo múltiple de 600 voltios clase g,,0,80509,0 -6011,26121833,Cable automotriz de núcleo múltiple de 600 voltios clase h,,0,80509,0 -6012,26121834,Cable automotriz trenzado de 60 voltios clase a,,0,80509,0 -6013,26121835,Cable automotriz trenzado de 60 voltios clase b,,0,80509,0 -6014,26121836,Cable automotriz trenzado de 60 voltios clase c,,0,80509,0 -6015,26121837,Cable automotriz trenzado de 60 voltios clase d,,0,80509,0 -6016,26121838,Cable automotriz trenzado de 60 voltios clase e,,0,80509,0 -6017,26121839,Cable automotriz trenzado de 60 voltios clase f,,0,80509,0 -6018,26121840,Cable automotriz trenzado de 60 voltios clase g,,0,80509,0 -6019,26121841,Cable automotriz trenzado de 60 voltios clase h,,0,80509,0 -6020,26121842,Cable automotriz trenzado de 600 voltios clase a,,0,80509,0 -6021,26121843,Cable automotriz trenzado de 600 voltios clase b,,0,80509,0 -6022,26121844,Cable automotriz trenzado de 600 voltios clase c,,0,80509,0 -6023,26121845,Cable automotriz trenzado de 600 voltios clase d,,0,80509,0 -6024,26121846,Cable automotriz trenzado de 600 voltios clase e,,0,80509,0 -6025,26121847,Cable automotriz trenzado de 600 voltios clase f,,0,80509,0 -6026,26121848,Cable automotriz trenzado de 600 voltios clase g,,0,80509,0 -6027,26121849,Cable automotriz trenzado de 600 voltios clase h,,0,80509,0 -6028,26121850,Cable automotriz de núcleo múltiple seleccionado de 60 voltios,,0,80509,0 -6029,26121851,Cable automotriz de núcleo múltiple seleccionado de 600 voltios,,0,80509,0 -6030,26121852,Cable automotriz de núcleo de ignición,Cable de encendido de automóvil,0,80509,0 -6031,26131500,Centrales eléctricas,,0,80509,0 -6032,26131501,Centrales eléctricas de diesel,,0,80509,0 -6033,26131502,Centrales eléctricas geotérmicas,,0,80509,0 -6034,26131503,Centrales hidroeléctricas,,0,80509,0 -6035,26131504,Centrales de gas,,0,80509,0 -6036,26131505,Centrales de energía marina,,0,80509,0 -6037,26131506,Centrales de energía a petróleo,,0,80509,0 -6038,26131507,Centrales de energía solar,,0,80509,0 -6039,26131508,Centrales termoeléctricas,,0,80509,0 -6040,26131509,Central de energía eólica,,0,80509,0 -6041,26131510,Central térmica,,0,80509,0 -6042,26131600,Equipo de cribado o estructuras de tubo de escape,,0,80509,0 -6043,26131601,Pantallas de agua móviles,,0,80509,0 -6044,26131602,Travesaños corredizos,,0,80509,0 -6045,26131603,Rejillas de agua,,0,80509,0 -6046,26131604,Filtros fijos,,0,80509,0 -6047,26131605,Estructuras de toma,,0,80509,0 -6048,26131606,Chimenea de acero,,0,80509,0 -6049,26131607,Chimenea de concreto,,0,80509,0 -6050,26131608,Chimeneas de ventilación o antorchas,,0,80509,0 -6051,26131609,Chimeneas de admisión,Pilas de admisión de escape,0,80509,0 -6052,26131610,Chimeneas de derivación,,0,80509,0 -6053,26131611,Secciones de silenciador,,0,80509,0 -6054,26131612,Conductos de salida de escape,,0,80509,0 -6055,26131613,Juntas de dilatación de conductos de escape,Juntas de dilatación de escape,0,80509,0 -6056,26131614,Amortiguadores de cierre de la chimenea,,0,80509,0 -6057,26131615,Amortiguadores de desviación de escape,,0,80509,0 -6058,26131616,Amortiguadores de aislamiento de escape,,0,80509,0 -6059,26131617,Amortiguador de veleta,,0,80509,0 -6060,26131618,Amortiguador de cabezal,,0,80509,0 -6061,26131700,Equipo de detección o vigilancia de producción de energía,,0,80509,0 -6062,26131701,Detectores de gases inflamables o peligrosos para generadores,,0,80509,0 -6063,26131702,Detectores de llama de sistemas de combustión de turbina de gas,,0,80509,0 -6064,26131800,Equipo de control de producción de energía,,0,80509,0 -6065,26131801,Paneles de control eléctrico para generadores,,0,80509,0 -6066,26131802,Paneles de control de compresores,,0,80509,0 -6067,26131803,Paneles de protección o control de generadores,,0,80509,0 -6068,26131804,Paneles de control de turbina de gas,,0,80509,0 -6069,26131807,Paneles de control de turbinas de vapor,,0,80509,0 -6070,26131808,Conmutadores de control de carga de subestación,,0,80509,0 -6071,26131811,Reactores de limitación de intensidad,,0,80509,0 -6072,26131812,Conmutadores con aislamiento de gas,,0,80509,0 -6073,26131813,Conmutadores de desconexión de estaciones de maniobra,,0,80509,0 -6074,26131814,Disipador de sobretensiones de estaciones de maniobra,Pararrayos de interruptor,0,80509,0 -6075,26141700,Equipo para dosimetría,,0,80509,0 -6076,26141701,Dosímetros de cámara de ionización,,0,80509,0 -6077,26141702,Dosímetros,,0,80509,0 -6078,26141703,Sistemas de dosimetría de patrón secundario,,0,80509,0 -6079,26141704,Dosímetros fantasma,,0,80509,0 -6080,26142300,Equipos protectores contra la radiación,,0,80509,0 -6081,26142302,Blindajes de plomo,,0,80509,0 -6082,26142303,Distintivo de película,,0,80509,0 -6083,26142304,Equipo radiográfico,,0,80509,0 -6084,26142306,Recipientes blindados contra la radiación,,0,80509,0 -6085,26142307,Cámaras de plomo para protección contra la radiación,,0,80509,0 -6086,26142308,Ladrillos de plomo para protección contra la radiación,,0,80509,0 -6087,26142310,Cajas selladas con guantes para protección contra la radiación,,0,80509,0 -6088,26142311,Ventanas para blindaje contra la radiación,,0,80509,0 -6089,26142312,Plomo para blindaje contra la radiación,,0,80509,0 -6090,26142400,Equipo para residuos radiactivos,,0,80509,0 -6091,26142401,Compactadores o incineradores para el tratamiento de residuos radiactivos,,0,80509,0 -6092,26142402,Absorbentes de radiaciones nucleares,Absorbentes de radiación nuclear,0,80509,0 -6093,26142403,"Evaporadores, concentradores o secadores de energía atómica",,0,80509,0 -6094,26142404,Sistemas de inter - bloqueo de las puertas,Sistema de bloqueo de puertas,0,80509,0 -6095,26142405,Sistemas de dosificación de residuos radiactivos,,0,80509,0 -6096,26142406,Sistemas de solidificación de residuos radiactivos,,0,80509,0 -6097,26142407,Sistemas de eliminación de residuos radiactivos,,0,80509,0 -6098,26142408,Instalaciones para el tratamiento de residuos radiactivos,,0,80509,0 -6099,27111500,Herramientas de corte y engarzado y punzones,,0,80509,0 -6100,27111501,Hojas de cuchillo,,0,80509,0 -6101,27111502,Navajas de afeitar,,0,80509,0 -6102,27111503,Cuchillos de diversas aplicaciones,Cuchillos para uso general,0,80509,0 -6103,27111504,Navajas de bolsillo,,0,80509,0 -6104,27111505,Sets para manicure,Estuche de manicure,0,80509,0 -6105,27111506,Cizallas,,0,80509,0 -6106,27111507,Cortadores de metal,Cúter o cutter,0,80509,0 -6107,27111508,Sierras,,0,80509,0 -6108,27111509,Barrenas,,0,80509,0 -6109,27111510,Herramientas para desforrar,Herramienta para cortar,0,80509,0 -6110,27111511,Cortadores de alambres,Pinzas de cortar alambre,0,80509,0 -6111,27111512,Cortadores de pernos,Pinzas cortadoras de pernos,0,80509,0 -6112,27111513,Cortadores de manguera,,0,80509,0 -6113,27111514,Cortadores de vidrio,,0,80509,0 -6114,27111515,Taladro de mano,,0,80509,0 -6115,27111516,Alicates de perforación,Perforadora,0,80509,0 -6116,27111517,Dispensadores o juegos de hojas de cuchilla,,0,80509,0 -6117,27111518,Herramienta para engastar y doblar alambre,,0,80509,0 -6118,27111519,Tijeras para estaño,,0,80509,0 -6119,27111520,Rompetuercas,,0,80509,0 -6120,27111521,Recortadoras de chapa de uña vibratoria,,0,80509,0 -6121,27111522,Herramienta para aboquillar o ensanchar,,0,80509,0 -6122,27111523,Raspador de vidrio,,0,80509,0 -6123,27111524,Cortadora de ribetes,Cortadora de rollos,0,80509,0 -6124,27111525,Tenazas,,0,80509,0 -6125,27111526,Herramienta para quitar rebabas,,0,80509,0 -6126,27111527,Cortador de tacos,,0,80509,0 -6127,27111529,Tijeras aisladas,,0,80509,0 -6128,27111530,Tijeras de pescador,,0,80509,0 -6129,27111531,Tijeras para el pelo,Tijeras para el cabello,0,80509,0 -6130,27111532,Tijeras de joyería,,0,80509,0 -6131,27111533,Tijeras de carnicero,,0,80509,0 -6132,27111534,Compás de corte,,0,80509,0 -6133,27111535,Tijeras de alambre,,0,80509,0 -6134,27111536,Cortador de círculos,,0,80509,0 -6135,27111537,Broca para madera,,0,80509,0 -6136,27111538,Broca forstner,,0,80509,0 -6137,27111539,Broca para madera plana,,0,80509,0 -6138,27111540,Broca para baldosa,,0,80509,0 -6139,27111541,Punta guía de bola,,0,80509,0 -6140,27111542,Broca de punto de espuela,,0,80509,0 -6141,27111543,Broca de mampostería,,0,80509,0 -6142,27111544,Sierra de hender,Serrucho de Hender,0,80509,0 -6143,27111545,Sierra de hender biselada,,0,80509,0 -6144,27111546,Sierra de panel,,0,80509,0 -6145,27111547,Sierra de pisos,,0,80509,0 -6146,27111548,Sierra tenon,,0,80509,0 -6147,27111549,Sierra para herraduras,,0,80509,0 -6148,27111550,Sierra de moño,,0,80509,0 -6149,27111551,Sierra de troncos,,0,80509,0 -6150,27111552,Segueta,,0,80509,0 -6151,27111553,Sierra de dos hombres,,0,80509,0 -6152,27111554,Afilador de cuchillas,,0,80509,0 -6153,27111555,Punzón central,,0,80509,0 -6154,27111556,Punzón hueco,,0,80509,0 -6155,27111557,Punzón cónico,,0,80509,0 -6156,27111558,Pinzón de pasador paralelo,,0,80509,0 -6157,27111559,Sierra para metales,,0,80509,0 -6158,27111560,Mini alicates,,0,80509,0 -6159,27111561,Caja de ingletear,Caja de mitre,0,80509,0 -6160,27111562,Cortadora de cables,,0,80509,0 -6161,27111600,Herramientas de perfilar,,0,80509,0 -6162,27111601,Mazas de hierro,Mazos de hierro,0,80509,0 -6163,27111602,Martillos,,0,80509,0 -6164,27111603,Yunques,,0,80509,0 -6165,27111604,Hachas de mano,,0,80509,0 -6166,27111605,Picas,,0,80509,0 -6167,27111607,Herramientas de recalcar,Herramientas de estampado,0,80509,0 -6168,27111608,Enderezadores manuales de cables,,0,80509,0 -6169,27111609,Enderezadores eléctricos de cables,,0,80509,0 -6170,27111610,Componentes de martillo y mazo,,0,80509,0 -6171,27111611,Martillo para picar piedra,,0,80509,0 -6172,27111612,Martillo o mazo que no saca chispas,Mazo,0,80509,0 -6173,27111613,Hachuela que no sacan chispas,Hacha antichispas,0,80509,0 -6174,27111614,Pica que no saca chispas,Pica antichispas,0,80509,0 -6175,27111615,Almádena o martillo macho,Martillo antichispas,0,80509,0 -6176,27111616,Martillo de bola,,0,80509,0 -6177,27111617,Martillo de caucho,,0,80509,0 -6178,27111618,Martillo de garra,,0,80509,0 -6179,27111619,Martillo de bola de cruz y derecho,,0,80509,0 -6180,27111620,Martillo de bola de pasador,,0,80509,0 -6181,27111621,Martillo mazo,,0,80509,0 -6182,27111622,Martillo de carpintería,,0,80509,0 -6183,27111623,Tapador de botellas manual,,0,80509,0 -6184,27111700,Llaves inglesas y guías,,0,80509,0 -6185,27111701,Destornilladores,Desarmadores,0,80509,0 -6186,27111702,Llaves para tuercas,,0,80509,0 -6187,27111703,Juegos de enchufes,,0,80509,0 -6188,27111704,Enchufes,,0,80509,0 -6189,27111705,Llaves de tuercas de boca cerrada,,0,80509,0 -6190,27111706,Llave de tuercas de boca abierta,,0,80509,0 -6191,27111707,Llaves ajustables,"Llaves inglesas, Llaves perico",0,80509,0 -6192,27111708,Llaves para tubos,,0,80509,0 -6193,27111709,Extractor de tornillos,,0,80509,0 -6194,27111710,Llaves allen,Llaves hexagonales,0,80509,0 -6195,27111711,Trinquetes,,0,80509,0 -6196,27111712,Extractores,,0,80509,0 -6197,27111713,Llaves de combinación,,0,80509,0 -6198,27111714,Llaves de especialidad,,0,80509,0 -6199,27111715,Llaves de torsión,,0,80509,0 -6200,27111716,Llave torx,,0,80509,0 -6201,27111717,Extractores de tubería,,0,80509,0 -6202,27111718,Extractores de grifo,,0,80509,0 -6203,27111720,Llave manual en t para grifos,,0,80509,0 -6204,27111721,Manivelas,,0,80509,0 -6205,27111722,Troqueleras,,0,80509,0 -6206,27111723,Llaves de tubo,,0,80509,0 -6207,27111724,Llaves de gancho,,0,80509,0 -6208,27111725,Llaves tubulares de desplazamiento,,0,80509,0 -6209,27111726,Llaves de tuercas,,0,80509,0 -6210,27111727,Llaves macho con mango en t,Llave para taladro,0,80509,0 -6211,27111728,Juego de destornilladores,Juego de desarmadores,0,80509,0 -6212,27111729,Juego de llaves,Juego de llaves inglesas,0,80509,0 -6213,27111730,Llave giratoria de dos extremos,,0,80509,0 -6214,27111731,Destornillador de precisión,Desarmador de precisión,0,80509,0 -6215,27111732,Destornillador que no saca chispas,Desarmador antichispas,0,80509,0 -6216,27111733,Destornillador de mango flexible,Desarmador de varilla flexible,0,80509,0 -6217,27111734,Destornillador de múltiples puntas,Desarmador de punta múltiple,0,80509,0 -6218,27111735,Destornillador de trinquete,Desarmador de trinquete,0,80509,0 -6219,27111736,Destornillador aislado,Desarmador aislado,0,80509,0 -6220,27111737,Destornillador manual automático de empujar,Desarmador semiautomático,0,80509,0 -6221,27111738,Destornillador tester de voltaje,Desarmador probador de voltaje,0,80509,0 -6222,27111739,Destornillador de torque,Desarmador de torsión,0,80509,0 -6223,27111740,Destornillador telescópico,Desarmador telescópico,0,80509,0 -6224,27111741,Destornillador de mango intercambiable,Desarmador de varilla intercambiable,0,80509,0 -6225,27111742,Destornillador de impacto,Desarmador de impacto,0,80509,0 -6226,27111743,Llave de anillos,Llave inglesa,0,80509,0 -6227,27111744,Llave de compensación de anillos,Llave anular,0,80509,0 -6228,27111745,Llave de anillo dividido,Llave inglesa doble,0,80509,0 -6229,27111746,Llave bulldog,,0,80509,0 -6230,27111747,Llave de caja,,0,80509,0 -6231,27111748,Llave de huella,,0,80509,0 -6232,27111749,Llave de tubo o stillson,,0,80509,0 -6233,27111750,Pinza de presión,,0,80509,0 -6234,27111751,Llave de grifos,Llave de grifo,0,80509,0 -6235,27111752,Llave de obstrucción,Llave angular,0,80509,0 -6236,27111753,Llave de trinquete,,0,80509,0 -6237,27111754,Llave en s,,0,80509,0 -6238,27111755,Llave en y,,0,80509,0 -6239,27111756,Llave de pata de cuervo,Llave de pata,0,80509,0 -6240,27111757,Llave de golpe especial o slugging,Llave de giro,0,80509,0 -6241,27111758,Llave dinamométrica de disparo automático,Palanca de Leva,0,80509,0 -6242,27111759,Llave de cuchilla,,0,80509,0 -6243,27111760,Llave de tuercas de llantas,,0,80509,0 -6244,27111761,Llave de bomba de agua,,0,80509,0 -6245,27111762,Llave de trinquete de cabeza flexible,,0,80509,0 -6246,27111763,Llave allen,,0,80509,0 -6247,27111764,Guías de broca,,0,80509,0 -6248,27111765,Llave para destapar tambores,Llave para abrir tambos,0,80509,0 -6249,27111766,Llave de rin cruzado,,0,80509,0 -6250,27111767,Destornillador de compensación o en “l”,Destornillador,0,80509,0 -6251,27111800,Herramientas de medida y bocetaje,,0,80509,0 -6252,27111801,Cintas métricas,Flexómetro,0,80509,0 -6253,27111802,Escalas,,0,80509,0 -6254,27111803,Escuadras,,0,80509,0 -6255,27111804,Plomadas,,0,80509,0 -6256,27111806,Calibrador de clavos,Medidor de clavos,0,80509,0 -6257,27111807,Bordes rectos,,0,80509,0 -6258,27111809,Biseles,,0,80509,0 -6259,27111810,Buscadores de pernos,,0,80509,0 -6260,27111811,Regla doblable,Regla flexible,0,80509,0 -6261,27111812,Regla larga,,0,80509,0 -6262,27111813,Línea de plomada,Línea plomada,0,80509,0 -6263,27111814,Espejo de inspección,,0,80509,0 -6264,27111815,Escuadra de combinación,,0,80509,0 -6265,27111816,Juego de bola indicadora,Juego de bolas gage,0,80509,0 -6266,27111817,Regla de encogimiento,,0,80509,0 -6267,27111818,Soporte del indicador del,Soporte indicador de cuadrante,0,80509,0 -6268,27111819,Espécimen de comparación de rugosidad,Galgas de comparación de rugosidad,0,80509,0 -6269,27111820,Escuadra maestra,,0,80509,0 -6270,27111821,Escuadra cilíndrica,,0,80509,0 -6271,27111822,Instrumento de medición de ángulos,,0,80509,0 -6272,27111823,Calibrador de centro,,0,80509,0 -6273,27111824,Calibrador de ángulo,,0,80509,0 -6274,27111825,Placa de seno,,0,80509,0 -6275,27111826,Placa de superficie de precisión,,0,80509,0 -6276,27111827,Placa de ángulo ajustable,,0,80509,0 -6277,27111828,Placa de ángulo fijo,,0,80509,0 -4094,23231000,Descortezadoras y accesorios,,0,80509,0 -4095,23231001,Portaherramientas,,0,80509,0 -4096,23231002,Herramienta de carburo,,0,80509,0 -4097,23231100,Sierras de cinta y accesorios,,0,80509,0 -4098,23231101,Rueda de sierra de cinta,,0,80509,0 -4099,23231102,Guía de sierra,,0,80509,0 -4100,23231200,Sierras circulares y accesorios,,0,80509,0 -4101,23231201,Espaciador de sierra,,0,80509,0 -4102,23231202,Eje de sierra,,0,80509,0 -4103,23231300,Canteadoras,,0,80509,0 -4104,23231301,Rodillo alimentador,,0,80509,0 -4105,23231302,Rodillo de sujeción,,0,80509,0 -4106,23231400,Sierras de recorte y accesorios,,0,80509,0 -4107,23231401,Guía de alineación de la madera,,0,80509,0 -4108,23231402,Sierra cero,,0,80509,0 -4109,23231500,Clasificadoras de maderos y accesorios,,0,80509,0 -4110,23231501,Barras portasierra,,0,80509,0 -4111,23231502,Eslinga de contenedor,,0,80509,0 -4112,23231600,Apiladoras de maderos y accesorios,,0,80509,0 -4113,23231601,Soporte de reborde,,0,80509,0 -4114,23231602,Rodillo de terminación,,0,80509,0 -4115,23231700,Secaderos de madera y equipo y accesorios,,0,80509,0 -4116,23231701,Deflector,,0,80509,0 -4117,23231900,Cepilladoras y accesorios,,0,80509,0 -4118,23231901,Lecho base,,0,80509,0 -4119,23231902,Placa trasera,,0,80509,0 -4120,23231903,Guía refrigerada por agua,,0,80509,0 -4121,23232000,Terminadoras de madera y accesorios,,0,80509,0 -4122,23232001,Porta cuchillas,,0,80509,0 -4123,23232100,Sierras retestadoras y accesorios,,0,80509,0 -4124,23232101,Soporte guía ajustable,,0,80509,0 -4125,23232200,Empalmadoras por entalladura múltiple y accesorios,,0,80509,0 -4126,23232201,Boquilla de pegamento,,0,80509,0 -4127,23241400,Máquinas rectificadoras de metales,,0,80509,0 -4128,23241401,Rectificador de banco,,0,80509,0 -4129,23241402,Máquina de rectificar superficies,,0,80509,0 -4130,23241403,Maquina de rectificación interna,,0,80509,0 -4131,23241404,Maquina de rectificación externa,,0,80509,0 -4132,23241405,Maquina de rectificación sin centro,,0,80509,0 -4133,23241406,Maquina ultrasónica,,0,80509,0 -4134,23241407,Máquina de rectificación cilíndrica,,0,80509,0 -4135,23241408,Máquina de rectificación de cuchillo,,0,80509,0 -4136,23241409,Máquina de rectificación de perfil óptico,,0,80509,0 -4137,23241410,Máquina de rectificación de tornillo sinfín,,0,80509,0 -4138,23241411,Máquina pulidora,,0,80509,0 -4139,23241500,Máquinas cortadoras de metales,,0,80509,0 -4140,23241501,Equipo de maquinado abrasivo a chorro,,0,80509,0 -4141,23241502,Máquina electromecánica ecm,,0,80509,0 -4142,23241503,Máquina de cortado con llama,,0,80509,0 -4143,23241504,Herramienta de corte de engranajes,,0,80509,0 -4144,23241505,Máquina láser para cortar,,0,80509,0 -4145,23241506,Máquina de corte plasma,,0,80509,0 -4146,23241507,Máquina de aserrado y corte,,0,80509,0 -4147,23241508,Máquina de corte con chorro de agua,,0,80509,0 -4148,23241509,Máquina de sierra de banda para metal,,0,80509,0 -4149,23241510,Máquina cizalladora de metal,,0,80509,0 -4150,23241511,Máquina para hacer roscas,,0,80509,0 -4151,23241600,Herramientas cortadoras de metales,,0,80509,0 -4152,23241601,Herramienta perforador,,0,80509,0 -4153,23241602,Punzón,,0,80509,0 -4154,23241603,Máquina para avellanado,,0,80509,0 -4155,23241605,Carburo de perforación,,0,80509,0 -4156,23241606,Taladros de acero de alta velocidad,,0,80509,0 -4157,23241608,Cortador de quemador,,0,80509,0 -4158,23241609,Cuchillos y biseles,,0,80509,0 -4159,23241610,Cortador de tubos,,0,80509,0 -4160,23241611,Escariador,,0,80509,0 -4161,23241612,Cortador de formas,,0,80509,0 -4162,23241613,Cortador de cepillado,,0,80509,0 -4163,23241614,Fresadora sólida,,0,80509,0 -4164,23241615,Grifos,,0,80509,0 -4165,23241616,Cortador de cable o alambre,,0,80509,0 -4166,23241617,Escariador de hoyos para pines,,0,80509,0 -4167,23241618,Escariador alineador de hoyos,,0,80509,0 -4168,23241619,Escariador de tubos,,0,80509,0 -4169,23241620,Escariador de concha,,0,80509,0 -4170,23241621,Cuchilla de sierra de varilla,,0,80509,0 -4171,23241622,Cortador de ángulo sencillo,,0,80509,0 -4172,23241623,Fresadora lateral,,0,80509,0 -4173,23241624,Fresadora cilíndrica plana,,0,80509,0 -4174,23241625,Fresadora de concha para extremos,,0,80509,0 -4175,23241626,Cortador convexo,,0,80509,0 -4176,23241627,Cortador cóncavo,,0,80509,0 -4177,23241628,Cortador redondeador de esquinas,,0,80509,0 -4178,23241629,Sierra para hacer ranuras en metal,,0,80509,0 -4179,23241630,Cortador de ranuras en t,,0,80509,0 -4180,23241631,Cortador de asiento woodruff,,0,80509,0 -4181,23241632,Cortador de cola de milano,,0,80509,0 -4182,23241633,Cortador conformador de engranajes,,0,80509,0 -4183,23241634,Taladro largo,,0,80509,0 -4184,23241635,Taladro de precisión,,0,80509,0 -4185,23241636,Taladros de herrero,,0,80509,0 -4186,23241637,Taladro de hoyo profundo,,0,80509,0 -4187,23241638,Cortador de hoyos con punta de carburo,,0,80509,0 -4188,23241639,Taladro de huso,,0,80509,0 -4189,23241640,Taladro multi – pasos,,0,80509,0 -4190,23241641,Taladro multi paso hex,,0,80509,0 -4191,23241642,Roscadora de velocidad,,0,80509,0 -4192,23241643,Escariador largo,,0,80509,0 -4193,23241644,Láser de dióxido de carbono,,0,80509,0 -4194,23241645,Escariador limador de precisión de agujeros,,0,80509,0 -4195,23241646,Escariador ajustable,,0,80509,0 -4196,23241647,Escariador cónico de agujero de precisión,,0,80509,0 -4197,23241800,Máquinas taladradoras de metales,,0,80509,0 -4198,23241801,Taladro radial o prensa de taladro,,0,80509,0 -4199,23241802,Taladro de segmentos,,0,80509,0 -4200,23241803,Taladro de cañón,,0,80509,0 -4201,23241804,Taladro controlado numéricamente,,0,80509,0 -4202,23241805,Máquina de taladro de mesa rotativa,,0,80509,0 -4203,23241806,Máquina de taladro de múltiples cabezas de huso,,0,80509,0 -4204,23241807,Máquina de taladro de brazo radial,,0,80509,0 -4205,23241808,Máquina de taladro de hoyo profundo,,0,80509,0 -4206,23241809,Máquina de taladro de banco,,0,80509,0 -4207,23241900,Máquinas perforadoras de metales,,0,80509,0 -4208,23241901,Máquina perforadora original,,0,80509,0 -4209,23241902,Máquina perforadora con plantilla,,0,80509,0 -4210,23241903,Máquinas para dar formas al metal,,0,80509,0 -4212,23241905,Máquinas para dar formas al metal en la superficie,,0,80509,0 -4213,23241906,Máquina perforadora vertical,,0,80509,0 -4214,23242100,Accesorios para máquinas de cortar metales,,0,80509,0 -4215,23242101,Cortadora de barras o varillas,,0,80509,0 -4216,23242102,Soporte de electrodos,,0,80509,0 -4217,23242103,Relieve de forma,,0,80509,0 -4218,23242104,Herramientas o brocas de formado,,0,80509,0 -4219,23242105,Inserto indexable,,0,80509,0 -4220,23242106,Cuerpos o soportes de herramientas indexables,,0,80509,0 -4221,23242107,Inserto de carburo,,0,80509,0 -4222,23242108,Inserto de cerámica,,0,80509,0 -4223,23242109,Inserto de diamante,,0,80509,0 -4224,23242110,Inserto de acero,,0,80509,0 -4225,23242111,Torreta de torno,,0,80509,0 -4226,23242112,Cuchilla de sierra de banda para cortar metal,,0,80509,0 -4227,23242113,Cuchilla de sierra circular para cortar metal,,0,80509,0 -4228,23242114,Soportes firmes,,0,80509,0 -4229,23242115,Tailstock,,0,80509,0 -4230,23242116,Anexo de máquina de enroscado,,0,80509,0 -4231,23242117,Anexo de máquina de hacer roscas,,0,80509,0 -4232,23242118,Herramienta de moleteado,,0,80509,0 -4233,23242119,Broca de torno,,0,80509,0 -4234,23242120,Base de tabla de máquina,,0,80509,0 -4235,23242300,Tornos y centros de torneado,,0,80509,0 -4236,23242301,Máquina automática de barra,,0,80509,0 -4237,23242302,Torno o máquina sujetadora automática,,0,80509,0 -4238,23242303,Centro giratorio horizontal,,0,80509,0 -4239,23242304,Torno para duplicar contornos o trazadora,,0,80509,0 -4240,23242305,Torno de torreta,,0,80509,0 -4241,23242306,Centro giratorio vertical,,0,80509,0 -4242,23242307,Tornillo de avance,,0,80509,0 -4243,23242308,Máquina centradora,,0,80509,0 -4244,23242400,Centros de maquinado,,0,80509,0 -4245,23242401,Centro maquinado horizontal,,0,80509,0 -4246,23242402,Centro maquinado universal o multi - tarea,,0,80509,0 -4247,23242403,Centro maquinado vertical,,0,80509,0 -4248,23242500,Máquinas para el fresado de metales,,0,80509,0 -4249,23242501,Máquina fresadora de lecho,,0,80509,0 -4250,23242502,Máquina fresadora de puente,,0,80509,0 -4251,23242503,Máquina fresadora de columna y rodilla,,0,80509,0 -4252,23242504,Máquina fresadora de pórtico,,0,80509,0 -4253,23242505,Máquina fresadora de perfilado y duplicación,,0,80509,0 -4254,23242506,Máquina fresadora de columna desplazada,,0,80509,0 -4255,23242507,Máquina fresadora universal,,0,80509,0 -4256,23242508,Máquina fresadora de copia,,0,80509,0 -4257,23242509,Máquina fresadora de torreta,,0,80509,0 -4258,23242510,Máquina fresadora de estilo planeador,,0,80509,0 -4259,23242511,Máquina fresadora de extremo,,0,80509,0 -4260,23242600,Maquinas para el cortado de metales especializados o diversos,,0,80509,0 -4261,23242601,Máquina biseladora,,0,80509,0 -4262,23242602,Máquina de bruñido,,0,80509,0 -4263,23242603,Máquina para hacer chaflanes,,0,80509,0 -4264,23242604,Máquina para lapeado,,0,80509,0 -4265,23242605,Máquina pulidora de metal,,0,80509,0 -4266,23242606,Máquina de procesamiento en capas delgadas,,0,80509,0 -4267,23242607,Máquina de súper - terminado,,0,80509,0 -4268,23242608,Preseteador de herramientas,,0,80509,0 -4269,23242609,Máquina para perfeccionado,,0,80509,0 -4270,23242610,Maquina de dimensionamiento de hoyos de diamante,,0,80509,0 -4271,23242611,Máquina para hacer bordes,,0,80509,0 -4272,23242612,Máquina para grabado de metales,,0,80509,0 -4273,23242613,Máquina para hacer tornillos hembra,,0,80509,0 -4274,23242614,Máquina para hacer llavería,,0,80509,0 -4275,23242615,Máquina para hacer surcos en metal,,0,80509,0 -4276,23251500,Maquinaria para tubulares doblados,,0,80509,0 -4277,23251501,Freno de prensa hidráulica,,0,80509,0 -4278,23251502,Freno de prensa manual,,0,80509,0 -4279,23251503,Máquina para el doblado de metal,,0,80509,0 -4280,23251504,Doblador de tangentes,,0,80509,0 -4281,23251505,Máquina dobladora de tubos,,0,80509,0 -4282,23251506,Terminador de puntas de tubos,,0,80509,0 -4283,23251507,Doblador de alas,,0,80509,0 -4284,23251508,Máquina dobladora de barras de acero,,0,80509,0 -4285,23251509,Máquina para reducir diámetro de alambres,,0,80509,0 -4286,23251600,Máquinas para enrollado de metales,,0,80509,0 -4287,23251601,Máquina enrolladora de hebras,,0,80509,0 -4288,23251602,Máquina para el formado de láminas metálicas,,0,80509,0 -4289,23251603,Prensa de rodillos,,0,80509,0 -4290,23251700,Máquinas de forjado,,0,80509,0 -4291,23251701,Prensa de forja de cizalla,,0,80509,0 -4292,23251702,Prensa de forja de impresión y troquel cerrado,,0,80509,0 -4293,23251703,Prensa de forja de troquel abierto,,0,80509,0 -4294,23251704,Máquinas de forjado radial,,0,80509,0 -4295,23251705,Máquina de forjado rollo,,0,80509,0 -4296,23251706,Rebabas giratorias,Fresas rotativas,0,80509,0 -4297,23251707,Prensa de recortado,Prensas de recorte,0,80509,0 -4298,23251708,Máquina de forjado de alta velocidad,,0,80509,0 -4299,23251709,Máquina de forjado de martillo de aire,,0,80509,0 -4300,23251710,Máquina de forjado de martillo de resorte,,0,80509,0 -4301,23251711,Máquina de forjado de martillo a vapor,,0,80509,0 -4302,23251712,Máquina de forjado de martillo de caída,,0,80509,0 -4303,23251713,Máquina de forjado de terminaciones prensadas,Máquina forjadora de esmerilado,0,80509,0 -4304,23251714,Forja de compresión o prensa de manivela,,0,80509,0 -4305,23251800,Troqueles y herramientas para el formado de metales,,0,80509,0 -4306,23251801,Troquel de doblado,,0,80509,0 -4307,23251802,Troquel de corte,,0,80509,0 -4308,23251803,Troquel de fundición a presión,,0,80509,0 -4309,23251804,Troquel de terminado de bordes,,0,80509,0 -4310,23251805,Troquel de forja,,0,80509,0 -4311,23251806,Troquel de extrusión de metal,,0,80509,0 -4312,23251807,Sello de metal,,0,80509,0 -4313,23251808,Mandril para el doblado de tubos,,0,80509,0 -4314,23251809,Troquel giratorio,,0,80509,0 -4315,23251810,Troquel de estampado,,0,80509,0 -4316,23251811,Troquel de regla de acero,,0,80509,0 -4317,23251812,Troquel de roscado,,0,80509,0 -4318,23251813,Troquel de cableado,,0,80509,0 -4319,23251814,Troquel de dibujo,,0,80509,0 -4320,23261500,Máquinas para prototipos rápidos,,0,80509,0 -4321,23261501,Máquina de modelado por deposición con fusible fdm,,0,80509,0 -4322,23261502,Máquina de método de chorro de tinta,,0,80509,0 -4324,23261504,Máquina láser para la formación de polvo,,0,80509,0 -4325,23261505,Máquina láser selectiva de sinterización,,0,80509,0 -4326,23261506,Máquina de estero – litografía,,0,80509,0 -4327,23261507,Máquina impresora tridimensional,,0,80509,0 -4328,23271400,Máquinas soldadoras,,0,80509,0 -4329,23271401,Máquina para soldadura arco sumergida,,0,80509,0 -4330,23271402,Máquina de soldadura ultrasónica,,0,80509,0 -4331,23271403,Máquina de soldadura de proyección,,0,80509,0 -4332,23271404,Máquina de soldadura arco plasma,,0,80509,0 -4333,23271405,Máquina de soldadura láser,,0,80509,0 -4334,23271406,Máquina de soldadura aislada,,0,80509,0 -4335,23271407,Máquina de soldadura de gas inerte de tungsteno,,0,80509,0 -4336,23271408,Máquina de soldadura de gas inerte de metal,,0,80509,0 -4337,23271409,Máquina de soldadura de barra o soldadura arco metálico protegida,,0,80509,0 -4338,23271410,Rectificador de soldadura,,0,80509,0 -4339,23271411,Soldadura plástica,,0,80509,0 -4340,23271412,Soldadura termita,,0,80509,0 -4341,23271413,Máquina de soldadura de rayo electrón ebw,,0,80509,0 -4342,23271414,Máquina de soldadura electro escoria esw,,0,80509,0 -4343,23271415,Máquina de soldadura de contacto o presión fría,,0,80509,0 -4344,23271416,Máquina de soldadura de fricción fw,,0,80509,0 -4345,23271417,Máquina de soldadura con estaño,,0,80509,0 -4346,23271418,Máquina de soldadura por junta eléctrica,,0,80509,0 -4347,23271419,Soldadura con recalado,,0,80509,0 -4348,23271420,Soldadura de cordón,Soldadura de costura,0,80509,0 -4349,23271421,Soldadura de cuchilla de sierra de banda,,0,80509,0 -4350,23271422,Soldadura de montantes o soldadura arco de montantes,,0,80509,0 -4351,23271423,Soldadura de arco de corriente alterna ac,,0,80509,0 -4352,23271424,Soldadura de arco de corriente directa dc,,0,80509,0 -4353,23271500,Maquinaria de soldadura fuerte,,0,80509,0 -4354,23271501,Máquina de soldadura fuerte para hornos,,0,80509,0 -4355,23271502,Máquina de soldadura fuerte por inducción,,0,80509,0 -4356,23271600,Maquinaria de soldadura débil,,0,80509,0 -4357,23271601,Máquina de soldadura débil para hornos,,0,80509,0 -4358,23271602,Máquina de soldadura por inducción,,0,80509,0 -4359,23271603,Hierro de soldadura débil,,0,80509,0 -4360,23271604,Máquina de soldadura de onda,,0,80509,0 -4361,23271605,Horno de reflujo,,0,80509,0 -4362,23271606,Máquina de soldadura débil selectiva,,0,80509,0 -4363,23271607,Máquina automática de soldadura débil,,0,80509,0 -4364,23271700,"Accesorios para soldar, soldadura fuerte y soldadura débil",,0,80509,0 -4365,23271701,Soplete,,0,80509,0 -4366,23271702,Soplete grande,,0,80509,0 -4367,23271703,Anillo de soldadura,,0,80509,0 -4368,23271704,Pistola para des – soldar,,0,80509,0 -4369,23271705,Bomba para des – soldar,,0,80509,0 -4370,23271706,Punta para des – soldar,,0,80509,0 -4371,23271707,Aparato para soldar o soldar fuerte o cortar gas,,0,80509,0 -4372,23271708,Barra indicadora de temperatura,,0,80509,0 -4373,23271709,Generador de soldadura,,0,80509,0 -4374,23271710,Lima limpiadora de soldadura o soldadura fuerte,Limpiador de puntas de soldadura,0,80509,0 -4375,23271711,Punta de soldadura o corte,,0,80509,0 -4376,23271712,Kit de soldadura o soldadura débil,,0,80509,0 -4377,23271713,Manipulador y posicionador de soldadura,,0,80509,0 -4378,23271714,Pantalla de soldadura,Mascara de soldar,0,80509,0 -4379,23271715,Cuchilla perfiladora de punta de soldadura,,0,80509,0 -4380,23271716,Accesorios de perfilación de punta de soldadura,,0,80509,0 -4381,23271717,Supresor de retorno,Pararrayos de retroceso,0,80509,0 -4382,23271718,Protector para soldadura,Manta de soldadura,0,80509,0 -4383,23271800,"Suministros para soldar, soldadura fuerte y soldadura débil",,0,80509,0 -4384,23271801,Espray anti – salpicaduras,Aerosol contra salpicaduras para soldadura,0,80509,0 -4385,23271802,Fundente de soldadura fuerte,Decapantes para soldadura fuerte,0,80509,0 -4386,23271803,Desoldador de trenza,,0,80509,0 -4387,23271804,Varilla de soldadura fuerte o soldadura de gas,Barra de soldadura o soldadura de gas,0,80509,0 -4388,23271805,Polvo de hierro,,0,80509,0 -4389,23271806,Cautín,Soldador eléctrico,0,80509,0 -4390,23271807,Fluido de soldadura,Líquido de soldadura,0,80509,0 -4391,23271808,Fundente de soldadura débil,Flujo de soldadura débil,0,80509,0 -4392,23271809,Pinta de soldadura,Punta de soldadura,0,80509,0 -4393,23271810,Electrodo de soldadura,Varilla de soldadura,0,80509,0 -4394,23271811,Fundente de soldadura,Flujo de soldadura,0,80509,0 -4395,23271812,Barra de soldadura,,0,80509,0 -4396,23271813,Alambre de soldadura,,0,80509,0 -4397,23271814,Electrodo de corte,Electrodo para corte y biselado,0,80509,0 -4398,23271815,Barra de cautín,Barra de soldadura,0,80509,0 -4399,23271816,Pasta de cautín,Pasta de soldadura,0,80509,0 -4400,23271817,Polvo de cautín,Soldadura en polvo,0,80509,0 -4401,23271818,Alambre de cautín,Alambre para soldar,0,80509,0 -4402,23271819,Fundente de unión de metal,,0,80509,0 -4403,23271820,Estabilizador de humedad de electrodo de soldadura,,0,80509,0 -4404,23271821,Soplete de soldadura,,0,80509,0 -4405,23281500,Máquinas para recubrir o platear,,0,80509,0 -4406,23281501,Máquina anodizdora,Máquina de anodización,0,80509,0 -4407,23281502,Máquina de baño electrolítico,,0,80509,0 -4408,23281503,Máquina de rociado térmico,,0,80509,0 -4409,23281504,Máquina de plateado de barril,,0,80509,0 -4410,23281600,Máquinas para tratamiento de calor,,0,80509,0 -4411,23281601,Máquina de inducción de calentamiento,,0,80509,0 -4412,23281602,Máquina de enfriamiento rápido,Máquina de quench,0,80509,0 -4413,23281603,Horno para endurecimiento durante tratamiento de calor,Horno de endurecimiento por edad de tratamiento térmico,0,80509,0 -4414,23281700,Máquinas para tratamiento mecánico de superficie,,0,80509,0 -4415,23281701,Máquina de revestimiento,,0,80509,0 -4416,23281702,Máquina de perdigonado,,0,80509,0 -4417,23281703,Máquina de arena (sand blast),Máquina de chorro de arena,0,80509,0 -4418,23281704,Máquina de electro descarga para endurecimiento de superficies,,0,80509,0 -4419,23281800,Accesorios y herramientas para el tratamiento de metal,,0,80509,0 -4420,23281801,Parrilla de galvanoplastia,Estante galvanoplastia,0,80509,0 -4421,23281802,Parrilla de deposición de calor físico,Estante de deposición física de vapor (PVD),0,80509,0 -4422,23281900,Maquinaria y suministros para la limpieza del metal,,0,80509,0 -4423,23281901,Limpiador electroquímico de metal,,0,80509,0 -4425,23281903,Limpiador de metal mecánico o ultrasónico,,0,80509,0 -4426,23281904,Equipo de lavado y limpiado acuoso,,0,80509,0 -4427,23281905,Equipo de desengrase térmico,,0,80509,0 -4428,23291500,Herramientas de perforación industrial,,0,80509,0 -4429,23291501,Taladro soldado,,0,80509,0 -4430,23291502,Taladro de parte superior o frontal intercambiable,,0,80509,0 -4431,23291503,Taladro de inserto indexable,,0,80509,0 -4432,23291504,Molino de parte superior o frontal intercambiable,,0,80509,0 -4433,23291700,Herramientas de escariado industrial,,0,80509,0 -4434,23291701,Cuchilla de escariador,,0,80509,0 -4435,23291702,Escariador soldado,,0,80509,0 -4436,23291703,Escariador sólido,,0,80509,0 -4437,23291704,Escariador indexable,,0,80509,0 -4438,23291800,Herramientas de roscado industrial,,0,80509,0 -4439,23291801,Soporte de roscado axial,,0,80509,0 -4440,23291802,Soporte de roscado externo,,0,80509,0 -4441,23291803,Soporte de roscado interno,,0,80509,0 -4442,23291804,Inserto de roscado,,0,80509,0 -4443,23291900,Herramientas de barrenado industrial,,0,80509,0 -4444,23291901,Cabeza de perforación,,0,80509,0 -4445,23291902,Herramienta de perforación burda,,0,80509,0 -4446,23301500,"Maquinaria, equipos y accesorios para el trabajo de alambre",,0,80509,0 -4447,23301501,Equipo para cortado de cable y alambre y ensamble de terminales,,0,80509,0 -4448,24101500,Camiones industriales,,0,80509,0 -4449,24101501,Carretas,,0,80509,0 -4450,24101502,Vehículos de transporte a grane,Transportadores a granel,0,80509,0 -4451,24101503,Vehículo dolly,Remolque dolly,0,80509,0 -4452,24101504,Carretones de mano o accesorios,Carros de mano o accesorios,0,80509,0 -4453,24101505,Camiones de pallets,Carritos con plataforma,0,80509,0 -4454,24101506,Carretas de empujar,,0,80509,0 -4455,24101507,Carretillas,,0,80509,0 -4456,24101508,Plataformas rodantes,,0,80509,0 -4457,24101509,Vagones,,0,80509,0 -4458,24101510,Contenedor de basura plástico,,0,80509,0 -4459,24101511,Trolleys de estante,Carritos de estantería,0,80509,0 -4460,24101512,Bugies eléctricos,Calesa eléctrica,0,80509,0 -4461,24101513,Remolcador,Remolcador,0,80509,0 -4462,24101514,Camión plataforma,Camión de plataforma,0,80509,0 -4463,24101515,Camión pórtico,,0,80509,0 -4464,24101516,Plataforma auto propulsada,,0,80509,0 -4465,24101517,Tractor de remolque eléctrico,,0,80509,0 -4466,24101600,Equipo de izaje y accesorios,,0,80509,0 -4467,24101601,Ascensores,,0,80509,0 -4468,24101602,Montacargas,,0,80509,0 -4469,24101603,Elevadores forklift,Carretilla elevadora,0,80509,0 -4470,24101604,Elevadores,,0,80509,0 -4471,24101605,Equipo de carga,,0,80509,0 -4472,24101606,Apiladoras,,0,80509,0 -4473,24101608,Malacates o güinches,Malacates o winches,0,80509,0 -4474,24101609,Inclinadores,,0,80509,0 -4475,24101610,Manipuladores,,0,80509,0 -4476,24101611,Eslingas,,0,80509,0 -4477,24101612,Gatos,,0,80509,0 -4478,24101613,Bloques o poleas,,0,80509,0 -4479,24101614,Sacos de aire para cargar,,0,80509,0 -4480,24101615,Rampas de carga,,0,80509,0 -4481,24101616,Dispositivo debajo de gancho,,0,80509,0 -4482,24101617,Elevador de tijera,Mesa elevadora,0,80509,0 -4483,24101618,Instalador de tuberías,,0,80509,0 -4484,24101619,Puentes grúas,,0,80509,0 -4485,24101620,Camión grúas,,0,80509,0 -4486,24101621,Grúas todo terreno,,0,80509,0 -4487,24101622,Grúas para terrenos difíciles,,0,80509,0 -4488,24101623,Torre grúas,,0,80509,0 -4489,24101624,Grúas hidráulicas sobre camión,,0,80509,0 -4490,24101625,Camión grúas convencionales,,0,80509,0 -4491,24101626,Escaleras mecánicas o cintas rodantes,,0,80509,0 -4492,24101627,Troleys de viga,Carros de viga,0,80509,0 -4493,24101628,Horquillas ajustables,,0,80509,0 -4494,24101629,Accesorios o suministros de elevador forklift o transportador vertical,Accesorios o suministros de montacargas o ascensores,0,80509,0 -4495,24101630,Grúas de taller,,0,80509,0 -4496,24101631,Tazas de succión,,0,80509,0 -4497,24101632,Izajes laterales,,0,80509,0 -4498,24101633,Tambores de elevación,,0,80509,0 -4499,24101634,Sacos de cadena,,0,80509,0 -4500,24101635,Gatos de tornillo,,0,80509,0 -4501,24101636,Bolsas de contrapeso y contrapesos,,0,80509,0 -4502,24101637,Eslinga de material de nylon,,0,80509,0 -4503,24101638,Eslinga de cuerda,,0,80509,0 -4504,24101639,Molinete,,0,80509,0 -4505,24101640,Unidad de engranaje transversal,,0,80509,0 -4506,24101641,Grillete,,0,80509,0 -4507,24101642,Grúa flotante,,0,80509,0 -4508,24101643,Trolly de lavado y secado,Carritos de limpieza y lavandería,0,80509,0 -4509,24101644,Ascensor de cocina,,0,80509,0 -4510,24101645,Grúa sobre orugas,,0,80509,0 -4511,24101646,Grúa ferroviaria,,0,80509,0 -4512,24101647,Almeja para izaje de material,,0,80509,0 -4513,24101648,Almeja para izaje de placas,,0,80509,0 -4514,24101649,Grúa güinche,Grúa de cabrestante,0,80509,0 -4515,24101650,Guy derrick,Torre derrick,0,80509,0 -4516,24101651,Cabrestante,,0,80509,0 -4517,24101652,Grúa de mástil,,0,80509,0 -4518,24101653,Puentegrúa,,0,80509,0 -4519,24101654,Grúa gantry o de pórtico,,0,80509,0 -4520,24101655,Sistema de parqueadero vertical,,0,80509,0 -4521,24101656,Grúa halada por tractor,Grúa tractor,0,80509,0 -4522,24101657,Grúa montada sobre tractor,,0,80509,0 -4523,24101658,Imán de izaje,,0,80509,0 -4524,24101659,Gato o dolly móvil vertical,Vehículo conector o plataforma móvil,0,80509,0 -4525,24101660,Elevador de baterías,,0,80509,0 -4526,24101661,Elevador de vehículo,,0,80509,0 -4527,24101662,Cubierta de güinche,Cubierta del cabrestante,0,80509,0 -4528,24101700,Transportadores y accesorios,,0,80509,0 -4529,24101701,Rodillo transportador,,0,80509,0 -4530,24101702,Transbordadores a bolas,Tablas de transferencia de bolas,0,80509,0 -4531,24101703,Acumuladoras de rocas,,0,80509,0 -4532,24101704,Alimentadores para banda transportadora,,0,80509,0 -4533,24101705,Tornillo de banda transportadora,,0,80509,0 -4534,24101706,Troles o vagonetas y accesorios,,0,80509,0 -4535,24101707,Banda transportadora sobre rieles,,0,80509,0 -4536,24101708,Banda transportadora extensible,,0,80509,0 -4537,24101709,Transportador de rodillos,,0,80509,0 -4538,24101710,Paradas de paquetes,,0,80509,0 -4539,24101711,Tornamesas,Placas giratorias,0,80509,0 -4540,24101712,Bandas transportadoras,,0,80509,0 -4542,24101714,Bandas transportadoras aéreas,,0,80509,0 -4543,24101715,Sistema de bandas transportadoras,,0,80509,0 -4544,24101716,Poleas de banda transportadora,,0,80509,0 -4545,24101717,Rodillos de banda transportadora,,0,80509,0 -4546,24101718,Sujeciones de la banda transportadora,,0,80509,0 -4547,24101719,Cepillos de la banda transportadora,,0,80509,0 -4548,24101721,Cubierta de trolley,,0,80509,0 -4549,24101722,Banda transportadora de cadena,,0,80509,0 -4550,24101723,Rodillos o tambores motorizados,,0,80509,0 -4551,24101724,Bastidores de banda transportadora,,0,80509,0 -4552,24101725,Articulaciones o eslabones de banda transportadora,,0,80509,0 -4553,24101726,Revestimiento de la banda transportadora,,0,80509,0 -4554,24101727,Banda transportadora vibratoria,,0,80509,0 -4555,24101728,Soportes de la banda transportadora,,0,80509,0 -4556,24101729,Balde de elevador de cangilones,Cucharon elevador,0,80509,0 -4557,24101730,Banda transportadora de tornillo,,0,80509,0 -4558,24101731,Faldón de la banda transportadora,Junta de la cinta transportadora,0,80509,0 -4559,24101732,Soporte del rodillo de la banda transportadora,,0,80509,0 -4560,24101733,Soporte del rodillo de retorno de la banda transportadora,,0,80509,0 -4561,24101734,Raspador en v,,0,80509,0 -4562,24101735,Deslizador de aire,,0,80509,0 -4563,24101736,Sistema de transporte de fase densa,,0,80509,0 -4564,24101737,Sistema de transporte de fase liviana,,0,80509,0 -4565,24101738,Compuerta de barra,,0,80509,0 -4566,24101739,Unidad receptora de granel,,0,80509,0 -4567,24101740,Banda transportadora de ángulo alto,,0,80509,0 -4568,24101741,Raspador interno de banda,,0,80509,0 -4569,24101742,Raspador externo de banda,,0,80509,0 -4570,24101743,Socavón final de banda transportadora,Eje trasero del transportador,0,80509,0 -4571,24101744,Socavón de inicio de banda transportadora,Eje del cabezal del transportador,0,80509,0 -4572,24101745,Banda transportadora sobre ruedas,,0,80509,0 -4573,24101746,Banda transportadora portátil,,0,80509,0 -4574,24101747,Soporte de banda transportadora,,0,80509,0 -4575,24101748,Banda transportadora tripper o disparadora,,0,80509,0 -4576,24101749,Sistema de transporte por bandas,,0,80509,0 -4577,24101750,Banda transportadora aérea,,0,80509,0 -4578,24101800,Equipo para muelles,,0,80509,0 -4579,24101801,Niveladores para muelles,,0,80509,0 -4580,24101802,Retenedoras para muelles,,0,80509,0 -4581,24101803,Rampas para muelles,,0,80509,0 -4582,24101804,Puertas de cintas,Puertas de tira,0,80509,0 -4583,24101805,Parachoques para muelles,Parachoques de los muelles,0,80509,0 -4584,24101806,Escaleras para muelles,,0,80509,0 -4585,24101807,Planchas para muelles,,0,80509,0 -4586,24101808,Cuñas para ruedas,Calzos para ruedas,0,80509,0 -4587,24101809,Carriles de muelle o accesorios,,0,80509,0 -4588,24101900,Equipo para manejo de canecas,,0,80509,0 -4589,24101901,Abridores de canecas,Abridores de tambor,0,80509,0 -4590,24101902,Agarradores para canecas,Agarre de tambor,0,80509,0 -4591,24101903,Elevadores de canecas,Elevadores de tambor,0,80509,0 -4592,24101904,Soportes para canecas,Cunas de tambor,0,80509,0 -4593,24101905,Cubierta de derrames,,0,80509,0 -4594,24101906,Recuperadores de canecas,,0,80509,0 -4595,24101907,Soportes de contención de derrames,,0,80509,0 -4596,24101908,Caballete para canecas,Estantería y estaciones de trasiego,0,80509,0 -4597,24102000,Disposición en estantes y almacenamiento,,0,80509,0 -4598,24102001,Sistemas de estanterías para equipo electrónico montado sobre estantes,Sistemas de bastidor para equipos electrónicos de montaje en bastidor,0,80509,0 -4599,24102002,Manipuladores para recipientes,,0,80509,0 -4600,24102004,Estanterías para almacenaje,,0,80509,0 -4601,24102005,Sistemas automatizados de almacenaje o recuperación,,0,80509,0 -4602,24102006,Bancos de trabajo,,0,80509,0 -4603,24102007,Carretes de almacenamiento de estantes,,0,80509,0 -4604,24102008,Soportes de carretes portátiles,,0,80509,0 -4605,24102009,Rieles montados sobre pared,,0,80509,0 -4606,24102010,Estantería de vidrio,,0,80509,0 -4607,24102011,Accesorio de biblioteca,Estante y accesorios para libros,0,80509,0 -4608,24102100,Equipo y suministros para almacenaje,,0,80509,0 -4609,24102101,Equipo para manejo de carga,Equipos de manipulación de cargas,0,80509,0 -4610,24102102,Carrusel de bodegaje,Carruseles de almacén,0,80509,0 -4611,24102103,Encajadoras de bodegaje,Encajonadoras de almacén,0,80509,0 -4612,24102104,Despaletizadoras,,0,80509,0 -4613,24102105,Paletizadoras,,0,80509,0 -4614,24102106,Equipo de retractilado industrial,Equipos de embalaje de plástico,0,80509,0 -4615,24102107,Maquinaria para encartonar,,0,80509,0 -4616,24102108,Compactadores de empaque,,0,80509,0 -4617,24102109,Ganchos para sacos,,0,80509,0 -4618,24102200,Herramientas para envasado,,0,80509,0 -4619,24102201,Dispensadores de película elástica,,0,80509,0 -4620,24102202,Dispensadores de cinta para sellar cajas,,0,80509,0 -4621,24102203,Herramientas o equipos para sellar bolsas,,0,80509,0 -4622,24102204,Dispensador de zunchos,Distribuidor de flejes,0,80509,0 -4623,24102208,Infladores de aire,,0,80509,0 -4624,24102209,Máquina para cortar moldes,Troqueladora,0,80509,0 -4625,24102210,Prensa de embalar,Prensa de empacado,0,80509,0 -4626,24102300,Vehículos guiados automáticos AGV,,0,80509,0 -4627,24102301,Vehículo automático guiado por códigos de barras,,0,80509,0 -4628,24102302,Vehículo automático guiado a través del cableado,Vehículos guiados automáticos AGV,0,80509,0 -4629,24111500,Bolsas,,0,80509,0 -4630,24111501,Bolsas de lona,,0,80509,0 -4631,24111502,Bolsas de papel,,0,80509,0 -4632,24111503,Bolsas plásticas,Bolsas de plástico,0,80509,0 -4633,24111505,Contendedores de granel intermedios flexibles,,0,80509,0 -4634,24111506,Redes o bolsas de lavandería,,0,80509,0 -4635,24111507,Bolsas de herramientas,,0,80509,0 -4636,24111508,Bolsas de carpa,,0,80509,0 -4637,24111509,Bolsas de agua,,0,80509,0 -4638,24111510,Bolsas y paquetes de cuerda,,0,80509,0 -4639,24111511,Bolsas de malla de fibra,,0,80509,0 -4640,24111512,Bolsas de paja,,0,80509,0 -4641,24111513,Bolsas de algodón,,0,80509,0 -4642,24111514,Bolsas con cremallera,,0,80509,0 -4643,24111800,Tanques y cilindros y sus accesorios,,0,80509,0 -4644,24111801,Reservorios,Embalse,0,80509,0 -4645,24111802,Tanques o cilindros de aire o gas,,0,80509,0 -4646,24111803,Tanques de almacenamiento,,0,80509,0 -4648,24111805,Tanques de productos químicos,,0,80509,0 -4649,24111806,Tanques de inmersión,,0,80509,0 -4650,24111807,Tanques de expansión,,0,80509,0 -4651,24111808,Tanques de almacenaje de combustible,,0,80509,0 -4652,24111809,Tanques de procesamiento,,0,80509,0 -4653,24111810,Tanques de almacenamiento de agua,Tinaco,0,80509,0 -4654,24111811,Bombonas,Tanque de almacenamiento,0,80509,0 -4655,24111812,Pileta de contención,,0,80509,0 -4656,24111813,Tanques de lavado,,0,80509,0 -4657,24111814,Tanques de agua caliente,,0,80509,0 -4658,24111815,Tanques de condensado de vapor,,0,80509,0 -4659,24111816,Tanque o cilindro de gas licuado,Tanque estacionario,0,80509,0 -4660,24111817,Silo de cemento,Deposito de cemento,0,80509,0 -4661,24111818,Sello de tanque,,0,80509,0 -4662,24112000,Arcones y cestas,,0,80509,0 -4663,24112003,Tolvas no metálicas,Contenedores no metálicos,0,80509,0 -4664,24112004,Tolvas metálicas,Contenedores metálicos,0,80509,0 -4665,24112005,Canastas metálicas,,0,80509,0 -4666,24112006,Canastas no metálicas,,0,80509,0 -4667,24112007,Estante para el manejo de material,,0,80509,0 -4668,24112100,"Toneles, cubas y bidones",,0,80509,0 -4669,24112101,Toneles,,0,80509,0 -4670,24112102,Barriles,,0,80509,0 -4671,24112108,Tambores metálicos,Tambos metálicos,0,80509,0 -4672,24112109,Tambores no metálicos,Tambos no metálicos,0,80509,0 -4673,24112110,Contenedores intermedios a granel,,0,80509,0 -4674,24112111,Estabilizador,,0,80509,0 -4675,24112112,Tapa de tambor,,0,80509,0 -4676,24112200,Latas y cubos,,0,80509,0 -4677,24112204,Cubos metálicos,Cubetas metálicas,0,80509,0 -4678,24112205,Cubos no metálicos,Cubetas no metálicas,0,80509,0 -4679,24112206,Bidones metálicos para líquido inflamable,Latas metálicas con líquido inflamable,0,80509,0 -4680,24112207,Bidones no metálicos para líquido inflamable,Latas no metálicas con líquido inflamable,0,80509,0 -4681,24112208,Conjunto pulverizador,Equipo de rociado,0,80509,0 -4682,24112209,Contenedor de paredes rectas,Bidón,0,80509,0 -4683,24112400,"Cofres, armarios y baúles de almacenaje",,0,80509,0 -4684,24112401,"Cofres, cajas o armarios para herramientas",,0,80509,0 -4685,24112402,Armarios para materiales peligrosos,Gabinetes de materiales peligrosos,0,80509,0 -4686,24112403,Cinturón para herramientas,,0,80509,0 -4687,24112404,Caja,,0,80509,0 -4688,24112406,Compartimentos de caja o estantería,Divisiones de caja o estantería,0,80509,0 -4689,24112407,Buzones,,0,80509,0 -4690,24112408,Cajas antiestática,,0,80509,0 -4691,24112409,Tapas para cajas,,0,80509,0 -4692,24112411,Estuches para transporte,Baúl para transportar,0,80509,0 -4693,24112412,Estuches para transporte de equipo,Baúl para transporte de equipo,0,80509,0 -4694,24112413,Cajas o estuches para guardar banderas,,0,80509,0 -4695,24112414,Caja de almacenamiento refrigerado,,0,80509,0 -4696,24112415,Urnas de votos,Urna electoral,0,80509,0 -4697,24112500,Cartón ondulado y otros materiales para distribución,,0,80509,0 -4698,24112501,Cartones acanalados ranurados,,0,80509,0 -4699,24112502,Cajas de transporte cortadas con troquel de una sola pieza,,0,80509,0 -4700,24112503,Cartones acanalados de transporte cortados con troquel con tapas separadas,,0,80509,0 -4701,24112504,Cajas moldeadas,,0,80509,0 -4702,24112505,Formas de cartón corrugado,,0,80509,0 -4703,24112600,contenedores para líquidos,,0,80509,0 -4704,24112601,Jarras,,0,80509,0 -4705,24112602,Frascos,,0,80509,0 -4706,24112700,Estibas,,0,80509,0 -4707,24112701,Pallets de madera,Tarimas de madera,0,80509,0 -4708,24112702,Pallets plásticos,Tarimas de plástico,0,80509,0 -4709,24112800,Contenedores de carga,,0,80509,0 -4710,24112801,Contenedor de carga seca,,0,80509,0 -4711,24112802,Contenedor de carga open top,Contenedor superior abierto,0,80509,0 -4712,24112803,Contenedor de carga de plataforma,,0,80509,0 -4713,24112804,Contenedor de carga flatrack,Contenedor de carga plana,0,80509,0 -4714,24112805,Contenedor de carga de temperatura controlada,Contenedor de carga controlado por temperatura,0,80509,0 -4715,24112900,Embalajes,,0,80509,0 -4716,24112901,Embalaje de madera,,0,80509,0 -4717,24112902,Embalaje de plástico,,0,80509,0 -4718,24113000,Lámina termo - retráctil,,0,80509,0 -4719,24113001,Lámina termo - retráctil corrogada,Hoja de deslizamiento ondulado,0,80509,0 -4720,24113002,Lámina termo - retráctil de cartón o cartulina sólida,Hoja de deslizamiento de cartón o cartulina sólida,0,80509,0 -4721,24113003,Lámina termo - retráctil plástica,Hoja de deslizamiento de plástico,0,80509,0 -4722,24121500,"Cajas, bolsas y sacos para empaquetado",,0,80509,0 -4723,24121502,Sacos o bolsas para empacar,,0,80509,0 -4724,24121503,Cajas para empacar,,0,80509,0 -4725,24121504,Empaques cardados,,0,80509,0 -4726,24121506,Cajas conductivas,,0,80509,0 -4727,24121507,Cajas instaladas rígidas,,0,80509,0 -4728,24121508,Cartones de huevos,,0,80509,0 -4729,24121509,Bandejas para empacar,,0,80509,0 -4730,24121510,Empaques para bolsas de te,,0,80509,0 -4731,24121511,Cartón de empacar,,0,80509,0 -4732,24121512,Ladrillos de hielo o “cold packs”,Embalaje en frío o ladrillo de hielo,0,80509,0 -4733,24121513,Estuche para empacar,,0,80509,0 -4734,24121800,Latas para envasado,,"0,1",80509,0 -4735,24121801,Latas de aerosol,,"0,1",80509,0 -4736,24121802,Latas de pintura o barniz,,"0,1",80509,0 -4737,24121803,Latas de bebida,,"0,1",80509,0 -4738,24121804,Latas de comida,,"0,1",80509,0 -4739,24121805,Latas de acero,,"0,1",80509,0 -4740,24121806,Latas de aluminio,,"0,1",80509,0 -4741,24121807,Recipientes de plástico,,"0,1",80509,0 -4742,24121808,Recipientes de fibra de cartón,,"0,1",80509,0 -4743,24122000,Botellas,,0,80509,0 -4744,24122001,Botellas para apretar,,0,80509,0 -4745,24122002,Botellas de plástico,,0,80509,0 -4746,24122003,Botellas de cristal,,0,80509,0 -4747,24122004,Tapones o tapas,,0,80509,0 -4748,24122005,Botellas de aplicador,,0,80509,0 -4749,24122006,Aplicador,,0,80509,0 -4750,24131500,Refrigeradores industriales,,"0,1",80509,0 -4751,24131501,Refrigerador y congelador combinado,,"0,1",80509,0 -4752,24131502,Refrigeradores de nitrógeno líquido,,"0,1",80509,0 -4753,24131503,Cuartos fríos,Cuarto de almacenamiento en frio,"0,1",80509,0 -4754,24131504,Contenedores refrigerados,,"0,1",80509,0 -4755,24131505,Buques refrigerados,,"0,1",80509,0 -4756,24131506,Tanques refrigerados,,"0,1",80509,0 -4758,24131508,Gabinete refrigerado,Refrigerador de gabinete,"0,1",80509,0 -4759,24131509,Refrigerador bajo el mostrador,,"0,1",80509,0 -4760,24131510,Refrigerador de mostrador,,"0,1",80509,0 -4761,24131511,Gabinete de refrigeración para pastelería,Vitrina de refrigeración para pasteles,"0,1",80509,0 -4762,24131512,Estación de preparación con gabinete refrigerado,,"0,1",80509,0 -4763,24131513,Refrigerante,,"0,1",80509,0 -4764,24131514,Unidad de almacenamiento de pre - enfriamiento y frío,Unidad de prerefrigeración y almacenamiento en frío,"0,1",80509,0 -4765,24131600,Congeladores industriales,,0,80509,0 -4766,24131601,Congeladores horizontales,,0,80509,0 -4767,24131602,Congeladores verticales,,0,80509,0 -4768,24131603,Congeladores a bajas temperaturas,,0,80509,0 -4769,24131604,Equipo de secado por congelación,,0,80509,0 -4770,24131605,Cuartos fríos,Congeladores de almacenamiento,0,80509,0 -4771,24131606,Congeladores de placa,,0,80509,0 -4772,24131607,Congeladores de golpe de frío,Refrigeradores industriales,0,80509,0 -4773,24131608,Gabinete de congelación para pastelería,,0,80509,0 -4774,24131609,Congelador bajo el mostrador,Bajomostradores de congelación,0,80509,0 -4775,24131610,Congelador de mostrador,,0,80509,0 -4776,24131900,Máquinas de hacer el hielo,,0,80509,0 -4777,24131901,Máquinas para hacer cubos de hielo,,0,80509,0 -4778,24131902,Máquinas para hacer bloques de hielo,,0,80509,0 -4779,24131903,Máquinas para hacer hojuelas de hielo,Máquinas industriales para copos de hielo,0,80509,0 -4780,24141500,Suministros para seguridad y protección,,0,80509,0 -4781,24141501,Película elástica para envoltura,,0,80509,0 -4782,24141502,Láminas elásticas de embalaje,,0,80509,0 -4783,24141504,Sellos de seguridad a prueba de manipulación,,0,80509,0 -4784,24141506,Encerados,,0,80509,0 -4785,24141507,Película anti-estática para empacar,,0,80509,0 -4786,24141508,Soporte angular de cartón,Soporte de esquinas de cartón,0,80509,0 -4787,24141510,Protectores de cables,,0,80509,0 -4788,24141511,Amarres de carga,,0,80509,0 -4789,24141512,Absorbentes de empaquetado,,0,80509,0 -4790,24141513,Empaques blíster,Empaques de ampolla de plástico,0,80509,0 -4791,24141514,Películas para empacar,,0,80509,0 -4792,24141515,Red de protección,,0,80509,0 -4793,24141516,Desecante,Gel sílice,0,80509,0 -4794,24141517,Polifilm,Película de polietileno,0,80509,0 -4795,24141518,Láminas separadoras,Hoja separadora,0,80509,0 -4796,24141519,Bandas o correas de acero para empacar,,0,80509,0 -4797,24141520,Bandas o correas no metálicas para empacar,,0,80509,0 -4798,24141600,Suministros de acolchados,,0,80509,0 -4799,24141601,Empaque de burbujas,,0,80509,0 -4800,24141602,Materiales de termoformado,,0,80509,0 -4801,24141603,Amortiguación,,0,80509,0 -4802,24141604,Relleno,Materiales de acolchado,0,80509,0 -4803,24141605,Materiales de empaque rellenos de aire,,0,80509,0 -4804,24141606,Bolitas de poliestireno,,0,80509,0 -4805,24141607,Separadores de cartón,,0,80509,0 -4806,24141608,Protecciones externas,,0,80509,0 -4807,24141700,Tubos y núcleos de embalaje y etiquetas y accesorios,,0,80509,0 -4808,24141701,Tubos o núcleos de papel,,0,80509,0 -4809,24141702,Tapones de extremidad tubo o núcleo,,0,80509,0 -4810,24141703,Piezas de papel,,0,80509,0 -4811,24141704,Instrucciones o insertos impresos,,0,80509,0 -4812,24141705,Tubos plegables,,0,80509,0 -4813,24141706,Carrete,,0,80509,0 -4814,24141707,Carretel,,0,80509,0 -4815,24141708,Asas de paquetes,,0,80509,0 -4816,24141709,Tubos o tapones de cápsulas,,0,80509,0 -4817,24141710,Etiqueta de alambre,,0,80509,0 -4818,25101500,Vehículos de pasajeros,,0,80509,0 -4819,25101501,Minibuses,Minibús,0,80509,0 -4820,25101502,Autobuses,Camiones de pasajeros,0,80509,0 -4821,25101503,Carros,Automóviles,0,80509,0 -4822,25101504,Station wagons,Vagones,0,80509,0 -4823,25101505,Minivans o vans,Furgonetas,0,80509,0 -4824,25101506,Limosinas,,0,80509,0 -4825,25101507,Camiones ligeros o vehículos utilitarios deportivos,,0,80509,0 -4826,25101508,Carro deportivo,,0,80509,0 -4827,25101509,Carro eléctrico,,0,80509,0 -4828,25101600,Vehículos de transporte de productos y materiales,,0,80509,0 -4829,25101601,Volquetas,Camión de volteo,0,80509,0 -4830,25101602,Remolques,,0,80509,0 -4831,25101604,Camiones de reparto,,0,80509,0 -4832,25101609,Camiones de manipulación de sedimento y aguas residuales,,0,80509,0 -4833,25101610,Carrotanques,Camiones para transportar líquidos,0,80509,0 -4834,25101611,Camiones de carga,,0,80509,0 -4835,25101612,Camiones concreteros,Camión de concreto,0,80509,0 -4836,25101613,Camiones para bombear concreto,Camiones bomba de hormigón,0,80509,0 -4837,25101700,Vehículos de protección y salvamento,,0,80509,0 -4838,25101701,Camiones de bomberos y de rescate,,0,80509,0 -4839,25101702,Carros policiales,,0,80509,0 -4840,25101703,Ambulancias,,0,80509,0 -4841,25101800,Bicicletas de motor,,0,80509,0 -4842,25101801,Motocicletas,Motoneta,0,80509,0 -4843,25101802,Scooters,Patín del diablo,0,80509,0 -4844,25101803,Velomotores,"Bicicleta eléctrica, Ciclomotor",0,80509,0 -4845,25101804,Sidecar de motocicleta,Vehículo lateral para motocicletas,0,80509,0 -4846,25101900,Vehículos especializados o de recreo,,0,80509,0 -4847,25101901,Tractores agrícolas,,0,80509,0 -4848,25101902,Casas rodantes,,0,80509,0 -4849,25101903,Trineos motorizados o motos de nieve,,0,80509,0 -4850,25101904,Carritos de golf,,0,80509,0 -4851,25101905,Vehículos todoterreno de ruedas o de tracción,,0,80509,0 -4852,25101906,Karts,Go karts,0,80509,0 -4853,25101907,Remolques de camping o caravanas,Casa rodante,0,80509,0 -4854,25101908,Cuatrimoto,,0,80509,0 -4855,25101909,Vehículos anfibios,,0,80509,0 -4856,25101910,Camiones aspersores de agua,Pipas de agua,0,80509,0 -4857,25101911,Camión con plataforma aérea de trabajo,Camión con plataforma de trabajo,0,80509,0 -4858,25101912,Camión escalera,,0,80509,0 -4859,25101913,Camión refrigerador,,0,80509,0 -4860,25101914,Carro de la basura o vehículo de recolección de desperdicios,Camiones de basura,0,80509,0 -4861,25101915,Camión petrolero,,0,80509,0 -4862,25101916,Camión tolva,,0,80509,0 -4863,25101917,Camabaja,,0,80509,0 -4864,25101918,Vehículo eléctrico de vecindario,Vehículo eléctrico vecinal (NEV),0,80509,0 -4865,25101919,Barredora de calles,,0,80509,0 -4866,25101920,Carro de seguridad,,0,80509,0 -4868,25101922,Camión de reparación de calles,,0,80509,0 -4869,25101923,Tractor de orugas,,0,80509,0 -4870,25101924,Vehículo limpiador de barandas,Camión para limpieza de barandillas vehiculares,0,80509,0 -4871,25101925,Camión de signos,,0,80509,0 -4872,25101926,Camión quitanieves,,0,80509,0 -4873,25101927,Camión esparcidor de sal,,0,80509,0 -4874,25101928,Camión de medios móvil,,0,80509,0 -4875,25101929,Van oficina,Furgoneta de oficina móvil,0,80509,0 -4876,25101930,Camión con equipo de iluminación,,0,80509,0 -4877,25101931,Van para radiodifusión externa,,0,80509,0 -4878,25101932,Camión para controlar multitudes,,0,80509,0 -4879,25101933,Camión grúa,,0,80509,0 -4880,25101934,Coche fúnebre,Carroza fúnebre,0,80509,0 -4881,25101935,Camión rociador de líquidos,,0,80509,0 -4882,25101936,Camión de vacío,,0,80509,0 -4883,25101937,Bus para tratamiento y exámenes médicos,Autobús de examen y tratamiento médico,0,80509,0 -4884,25101938,Sangre – móvil,Unidad móvil de donación de sangre,0,80509,0 -4885,25102000,Vehículos de guerra,,0,80509,0 -4886,25102001,Tanques,,0,80509,0 -4887,25102002,Carros de combate acorazados,,0,80509,0 -4888,25102003,Artillería motorizada,,0,80509,0 -4889,25102100,Camiones tractores,,0,80509,0 -4890,25102101,Tractores delanteros de cabina baja,,0,80509,0 -4891,25102102,Tractomulas de nariz alargada con cama,,0,80509,0 -4892,25102103,Tractomulas de nariz alargada sin cama,,0,80509,0 -4893,25102104,Tractomulas con cabina encima del motor con cama,,0,80509,0 -4894,25102105,Tractomulas con cabina encima del motor sin cama,,0,80509,0 -4895,25102106,Cabezote,Cabeza del tractor,0,80509,0 -4896,25111500,Embarcaciones marítimas comerciales,,0,80509,0 -4897,25111501,Barcos pesqueros,,0,80509,0 -4898,25111502,Botes de pesca,,0,80509,0 -4899,25111503,Buques de carga o de contenedores,,0,80509,0 -4900,25111504,Embarcación para dragados,Nave de dragado,0,80509,0 -4901,25111505,Buques cisterna,Buque tanque,0,80509,0 -4902,25111506,Remolcadores,,0,80509,0 -4903,25111507,Barcazas,,0,80509,0 -4904,25111508,Transbordadores de pasajeros o vehículos,Transbordador marítimo,0,80509,0 -4905,25111509,Buques de cruceros,Barcos de crucero,0,80509,0 -4906,25111510,Naves de salvamento,,0,80509,0 -4907,25111511,Barco de tripulación de gas o petróleo,,0,80509,0 -4908,25111512,Barco de trabajo de gas o petróleo,,0,80509,0 -4909,25111513,Barco para sísmica,,0,80509,0 -4910,25111514,Rompehielos,,0,80509,0 -4911,25111515,Carguero de gas líquido,,0,80509,0 -4912,25111516,Bote de caucho inflable,,0,80509,0 -4913,25111517,Batiscafo (hovercraft),Aerodeslizador,0,80509,0 -4914,25111518,Buque hospital,,0,80509,0 -4915,25111519,Bote de carreras,,0,80509,0 -4916,25111520,Boya,Baliza flotante,0,80509,0 -4917,25111521,Ancla marina,,0,80509,0 -4918,25111522,Carguero tanquero químico,Buque de carga con tanque químico,0,80509,0 -4919,25111523,Buque fábrica,,0,80509,0 -4920,25111524,Buque granelero,,0,80509,0 -4921,25111525,Buque roll on – roll off,Buques de remolque de autos,0,80509,0 -4922,25111526,Buque taladro,,0,80509,0 -4923,25111527,Bote de remos,,0,80509,0 -4924,25111528,Bote de cuarentena,,0,80509,0 -4925,25111529,Buque de meteorología,,0,80509,0 -4926,25111530,Bote de piloto,,0,80509,0 -4927,25111531,Barco para levantamientos hidrográficos,,0,80509,0 -4928,25111532,Nave para recolección de escombros,,0,80509,0 -4929,25111533,Buque de entrenamiento,,0,80509,0 -4930,25111534,Buque de investigación,,0,80509,0 -4931,25111535,Buque para el tendido de cable,,0,80509,0 -4932,25111600,Embarcaciones de seguridad y salvamento,,0,80509,0 -4933,25111601,Lanchas o balsas salvavidas,,0,80509,0 -4934,25111602,Nave para apagar incendios,,0,80509,0 -4935,25111603,Buques o botes de rescate,,0,80509,0 -4936,25111700,Embarcaciones militares,,0,80509,0 -4937,25111701,Submarinos,,0,80509,0 -4938,25111702,Portaviones,,0,80509,0 -4939,25111703,Barcos de munición,,0,80509,0 -4940,25111704,Barcos de asalto anfibio,,0,80509,0 -4941,25111705,Muelles anfibios de transporte,,0,80509,0 -4942,25111706,Buques anfibios de comando,,0,80509,0 -4943,25111707,Buques de comando,,0,80509,0 -4944,25111708,Buques de guerra,,0,80509,0 -4945,25111709,Destructores,,0,80509,0 -4946,25111710,Buques de desembarco,,0,80509,0 -4947,25111711,Buques de apoyo de combate rápido,,0,80509,0 -4948,25111712,Fragatas,,0,80509,0 -4949,25111713,Buques petroleros de escuadra,,0,80509,0 -4950,25111714,Embarcación de desembarque de uso general,,0,80509,0 -4951,25111715,Embarcación mecanizada o de uso general,,0,80509,0 -4952,25111716,Buques buscaminas,,0,80509,0 -4953,25111717,Buques dragaminas,,0,80509,0 -4954,25111718,Embarcaciones de patrulla costera,,0,80509,0 -4955,25111719,Buques nodriza para submarinos,,0,80509,0 -4956,25111720,Botes para desembarco de tanques,,0,80509,0 -4957,25111721,Embarcaciones de desembarque aerodeslizadas,,0,80509,0 -4958,25111722,Barco guardacostas,,0,80509,0 -4959,25111723,Bote de patrulla pesquera,,0,80509,0 -4960,25111724,Nave de entrenamiento de pesca,,0,80509,0 -4961,25111725,Nave de investigación de pesca,,0,80509,0 -4962,25111726,Nave para el control de la contaminación marina,,0,80509,0 -4963,25111800,Embarcaciones de recreo,,0,80509,0 -4964,25111801,Embarcaciones a vela de recreo,,0,80509,0 -4965,25111802,Lanchas de recreo a motor,,0,80509,0 -4966,25111803,Lanchas de recreo a remo,,0,80509,0 -4967,25111804,Canoas o kayaks,,0,80509,0 -4968,25111805,Artefactos a motor de uso personal,,0,80509,0 -4969,25111806,Balsas,,0,80509,0 -4970,25111807,Lanchas pequeñas,,0,80509,0 -4971,25111808,Yates,,0,80509,0 -4972,25111900,Sistemas y premontajes de embarcaciones marítimas,,0,80509,0 -4973,25111901,Sistemas de comunicaciones de embarcaciones marítimas,Sistemas de comunicaciones marinas,0,80509,0 -4974,25111902,Hélices marítimas,Hélices para barcos,0,80509,0 -4975,25111903,Velas,,0,80509,0 -4976,25111904,Remos,,0,80509,0 -4977,25111905,Sistemas balísticos de la marina,,0,80509,0 -4978,25111906,Cuñas de ancla,,0,80509,0 -4979,25111907,Cables de ancla,,0,80509,0 -4980,25111908,Recuperadores de ancla,,0,80509,0 -4981,25111909,Rodillos de ancla,,0,80509,0 -4982,25111910,Rampas de embarcaciones,,0,80509,0 -4983,25111911,Bicheros,,0,80509,0 -4985,25111913,Escotillas de cubierta,,0,80509,0 -4986,25111914,Argollas de amarre,,0,80509,0 -4987,25111915,Escaleras de muelle,,0,80509,0 -4988,25111916,Señales de teñido de emergencia,Señales visuales de socorro,0,80509,0 -4989,25111917,Alabantes,Bolardos De Amarre,0,80509,0 -4990,25111918,Sistemas de recogido de velas,,0,80509,0 -4991,25111919,Protectores de la quilla,,0,80509,0 -4992,25111920,Defensas,Guardabarros marinos,0,80509,0 -4993,25111921,Cuellos de ganso marinos,,0,80509,0 -4994,25111922,Cabos de amarre,,0,80509,0 -4995,25111923,Bloqueos de remos,,0,80509,0 -4996,25111924,Reflectores de radar,,0,80509,0 -4997,25111925,Timones,,0,80509,0 -4998,25111926,Refuerzos de velas,,0,80509,0 -4999,25111927,Botavaras,,0,80509,0 -5000,25111928,Fundas de velas,,0,80509,0 -5001,25111929,Sacas,,0,80509,0 -5002,25111930,Palos de espinaquer,,0,80509,0 -5003,25111931,Plataformas de popa,,0,80509,0 -5004,25111932,Cortavientos,,0,80509,0 -5005,25111933,Relojes de mareas,,0,80509,0 -5006,25111934,Cañas de timones,,0,80509,0 -5007,25111935,Berlingas,,0,80509,0 -5008,25111936,Propulsor marino,,0,80509,0 -5009,25111937,Protector de mástil,,0,80509,0 -5010,25111938,Generador marino de agua fresca,,0,80509,0 -5011,25111939,Engranaje de dirección marino,,0,80509,0 -5012,25111940,Simulador de navegación marina,,0,80509,0 -5013,25121500,Locomotoras y vagonetas eléctricas,,0,80509,0 -5014,25121501,Locomotoras diesel de carga,,0,80509,0 -5015,25121502,Locomotoras eléctricas de carga,,0,80509,0 -5016,25121503,Locomotoras diesel de pasajeros,,0,80509,0 -5017,25121504,Locomotoras eléctricas de pasajeros,,0,80509,0 -5018,25121600,Vagones de ferrocarril y tranvía,,0,80509,0 -5019,25121601,Vagones de carga,,0,80509,0 -5020,25121602,Vagones cisterna,,0,80509,0 -5021,25121603,Vagones de pasajeros,,0,80509,0 -5022,25121604,Vagones tolva o góndolas,,0,80509,0 -5023,25121605,Tranvías,,0,80509,0 -5024,25121700,Equipo y sistemas de apoyo ferroviario,,0,80509,0 -5025,25121701,Sistemas de cambiavías,,0,80509,0 -5026,25121702,Traviesas,,0,80509,0 -5027,25121703,Vías férreas,,0,80509,0 -5028,25121704,Eclisas,,0,80509,0 -5029,25121705,Enganches de rieles,,0,80509,0 -5030,25121706,Aparatos de tracción,,0,80509,0 -5031,25121707,Conjuntos de bogie,,0,80509,0 -5032,25121708,Riel férreo pesado,,0,80509,0 -5033,25121709,Riel férreo pesado de segundo uso,,0,80509,0 -5034,25121710,Riel férreo liviano,,0,80509,0 -5035,25121711,Riel férreo liviano de segundo uso,,0,80509,0 -5036,25121712,Amarre férreo de acero,,0,80509,0 -5037,25121713,Contacto eléctrico de rieles,,0,80509,0 -5038,25121714,Conmutador férreo de giro de rieles pesados,,0,80509,0 -5039,25121715,Conmutador férreo de giro de rieles livianos,,0,80509,0 -5040,25121716,Amarre férreo de madera,,0,80509,0 -5041,25121717,Unión eléctrica de rieles,,0,80509,0 -5042,25131600,Aeronave de alas giratorias civil y comercial,,0,80509,0 -5043,25131601,Helicópteros de pasajeros,,0,80509,0 -5044,25131602,Helicópteros de carga,,0,80509,0 -5045,25131603,Helicópteros agrícolas,,0,80509,0 -5046,25131604,Helicópteros médicos o de rescate,,0,80509,0 -5047,25131605,Aeronave de entrenamiento de ala rotativa o helicóptero de entrenamiento,,0,80509,0 -5048,25132000,Avión de recreo,,0,80509,0 -5049,25132001,Aerodeslizadores,,0,80509,0 -5050,25132002,Globos aerostáticos,,0,80509,0 -5051,25132003,Planeadores,,0,80509,0 -5052,25132004,Parapente,,0,80509,0 -5053,25132005,Aviones ultralivianos,,0,80509,0 -5054,25151700,Satélites,,0,80509,0 -5055,25151701,Satélites de comunicación,,0,80509,0 -5056,25151702,Satélites meteorológicos,,0,80509,0 -5057,25151703,Satélites militares,,0,80509,0 -5058,25151704,Satélites científicos o de investigación,,0,80509,0 -5059,25151705,Satélites de navegación,,0,80509,0 -5060,25151706,Satélites geoestacionarios,,0,80509,0 -5061,25151707,Satélites de órbita cercana a la tierra,,0,80509,0 -5062,25151708,Satélites de órbita sincronizada con el sol,,0,80509,0 -5063,25151709,Satélites geo – sincronizados,Satélites geosincrónicos,0,80509,0 -5064,25161500,Vehículos a pedal,,0,80509,0 -5065,25161501,Bicicletas de turismo,,0,80509,0 -5066,25161502,Monociclos,,0,80509,0 -5067,25161503,Triciclos,,0,80509,0 -5068,25161504,Bicicletas tándem,,0,80509,0 -5069,25161505,Bicicletas de montaña,,0,80509,0 -5070,25161506,Bicicletas de carreras,,0,80509,0 -5071,25161507,Bicicletas,,0,80509,0 -5072,25161508,Bicicletas reclinadas,,0,80509,0 -5073,25161509,Bicicletas para niños,,0,80509,0 -5074,25161510,Bicicletas de cuatro ruedas o cuadriciclos,,0,80509,0 -5075,25171500,Limpiaparabrisas,Refacciones automotrices o autopartes,0,80509,0 -5076,25171502,limpiaparabrisas para automóviles,,0,80509,0 -5077,25171503,Limpiaparabrisas para locomotoras,Limpiaparabrisas para tren,0,80509,0 -5078,25171504,Limpiaparabrisas marítimos,Limpiadores de parabrisas marinos,0,80509,0 -5079,25171505,Limpiaparabrisas de camión,,0,80509,0 -5080,25171506,Bomba de lava parabrisas,Bomba de agua para limpiaparabrisas,0,80509,0 -5081,25171507,Cuchillas limpiadoras,Pluma limpiaparabrisas,0,80509,0 -5082,25171508,Motor del limpiaparabrisas,,0,80509,0 -5083,25171600,Sistemas de descarchado y antiniebla,Refacciones automotrices o autopartes,0,80509,0 -5084,25171602,Sistemas de desescarchado y antiniebla para automóviles,,0,80509,0 -5085,25171603,Sistemas de desescarchado y antiniebla para trenes,,0,80509,0 -5086,25171700,Sistemas de frenado y componentes,Refacciones automotrices o autopartes,0,80509,0 -5087,25171702,Sistemas de frenado para automóviles,,0,80509,0 -5088,25171703,Sistemas de frenado para trenes,,0,80509,0 -5089,25171704,Chutes de arrastre,,0,80509,0 -5090,25171705,Rotores,,0,80509,0 -5091,25171706,Calibrador de frenaje,Cáliper de freno,0,80509,0 -5092,25171707,Freno de tambor,Pinzas de freno,0,80509,0 -5093,25171708,Freno de disco,Balatas,0,80509,0 -5094,25171709,Freno enfriado con líquido,,0,80509,0 -5095,25171710,Cilindros principales,,0,80509,0 -5096,25171711,Cilindros esclavos,,0,80509,0 -5097,25171712,Zapatas de freno de tambor,,0,80509,0 -5098,25171713,Almohadillas de discos de freno,"Almohadillas para balatas, Pastillas de freno de disco",0,80509,0 -5099,25171714,Tambor de freno,,0,80509,0 -5100,25171715,Rotores de frenos de disco,,0,80509,0 -5102,25171717,Pistones de freno,,0,80509,0 -5103,25171718,Kits de reparación de frenos,,0,80509,0 -5104,25171719,Refuerzos de frenado,,0,80509,0 -5105,25171720,Freno de circuito dual,,0,80509,0 -5106,25171721,Freno mecánico o de parqueo,Freno de mano,0,80509,0 -5107,25171722,Tornillo de rosca de purga del freno,,0,80509,0 -5108,25171723,Sistema de frenos antibloqueo – ABS,,0,80509,0 -5109,25171724,Bobina del sistema de frenos antibloqueo – ABS,,0,80509,0 -5110,25171725,Freno de banda,,0,80509,0 -5111,25171900,Ruedas y acabados,Refacciones automotrices o autopartes,0,80509,0 -5112,25171901,Rines o ruedas para automóviles,,0,80509,0 -5113,25171902,Ruedas para trenes,,0,80509,0 -5114,25171903,Rines o ruedas para camiones,,0,80509,0 -5115,25171905,Válvulas de neumáticos,Pivotes para llantas,0,80509,0 -5116,25171906,Cubiertas de llantas de automóviles,,0,80509,0 -5117,25172000,Componentes de sistema de suspensión,Refacciones automotrices o autopartes,0,80509,0 -5118,25172001,Sistemas de suspensión para automóviles,,0,80509,0 -5119,25172002,Sistemas de suspensión para camiones,,0,80509,0 -5120,25172003,Amortiguadores para camiones,,0,80509,0 -5121,25172004,Amortiguadores para automóviles,,0,80509,0 -5122,25172005,Sistemas de suspensión para trenes,,0,80509,0 -5123,25172007,Puntales,,0,80509,0 -5124,25172009,Buje de automóvil,,0,80509,0 -5125,25172010,Barra anti - ladeo,Barra de sacudimiento,0,80509,0 -5126,25172011,Amortiguadores de choque,Amortiguadores,0,80509,0 -5127,25172012,Placa deslizante,,0,80509,0 -5128,25172013,Brazo de control de automóvil,,0,80509,0 -5129,25172014,Sistema activo de suspensión,,0,80509,0 -5130,25172100,Componentes y sistemas de seguridad de vehículo,Refacciones automotrices o autopartes,0,80509,0 -5131,25172101,Airbags,Bolsas de aire,0,80509,0 -5132,25172104,Cinturones de seguridad,,0,80509,0 -5133,25172105,Sistemas para evitar choques,,0,80509,0 -5134,25172106,Sistemas sensores de impacto,,0,80509,0 -5135,25172108,Apoyacabezas,Cabeceras de automóviles,0,80509,0 -5136,25172109,Latas de propulsor de airbags,Botes de propulsor de bolsa de aire,0,80509,0 -5137,25172110,Pito de vehículo,Claxon de vehículo,0,80509,0 -5138,25172111,Sistema remoto de bloqueo,Sistema de cierre remoto,0,80509,0 -5139,25172112,Sistemas de control de la estabilidad del vehículo,,0,80509,0 -5140,25172113,Sistemas de control de la tracción del vehículo,,0,80509,0 -5141,25172114,Bloqueadores de ruedas,Birlos para llantas,0,80509,0 -5142,25172115,Sistema de bloqueo central,Cierre centralizado,0,80509,0 -5143,25172116,Cadenas de llantas para nieve,,0,80509,0 -5144,25172117,Ensamble de cubierta de airbag,Ensamble de cubierta de la bolsa de aire,0,80509,0 -5145,25172118,Ensamble de tela de airbag,Ensamble de tela de la bolsa de aire,0,80509,0 -5146,25172119,Tela de airbag,Tela de la bolsa de aire,0,80509,0 -5147,25172120,Inflador de airbag,,0,80509,0 -5148,25172121,Inflador de airbag del conductor,Inflador de la bolsa de aire del conductor,0,80509,0 -5149,25172122,Inflador de airbag del pasajero,Inflador de la bolsa de aire del pasajero,0,80509,0 -5150,25172123,Inflador de airbag de rodilla,Inflador de la bolsa de aire de rodilla,0,80509,0 -5151,25172124,Inflador de airbag lateral,Inflador de la bolsa de aire lateral,0,80509,0 -5152,25172125,Inflador de airbag de cortina,Inflador de la bolsa de aire de cortina,0,80509,0 -5153,25172126,Tejido de cinturones de seguridad,,0,80509,0 -5154,25172127,Sensor de peso de asientos,,0,80509,0 -5155,25172128,Sensor de tensión del cinturón de seguridad bts,,0,80509,0 -5156,25172129,Sistema de información del conductor,Tablero de información del conductor,0,80509,0 -5157,25172130,Control de acceso del vehículo sin llave o fob,Transmisor fob,0,80509,0 -5158,25172131,Inmovilizador,Bastón,0,80509,0 -5159,25172200,Puertas para vehículos,Refacciones automotrices o autopartes,0,80509,0 -5160,25172201,Puertas de automotores desmontables,,0,80509,0 -5161,25172203,Puertas de automotores,,0,80509,0 -5162,25172204,Puertas de persiana para camiones,Puertas enrollables para camiones,0,80509,0 -5163,25172205,Compuertas inferiores o puertas elevadoras,Plataformas elevadoras,0,80509,0 -5164,25172300,Ventanas y parabrisas para vehículos,Refacciones automotrices o autopartes,0,80509,0 -5165,25172301,Parabrisas para automotores,,0,80509,0 -5166,25172303,Ventanas para automotores,Ventanas automotrices,0,80509,0 -5167,25172304,Cremallera de ventana o ensamble regulatorio,Ensamble de ventana o regulador de ventana,0,80509,0 -5168,25172305,Parabrisas para vehículo de dos ruedas,,0,80509,0 -5169,25172400,Depósitos y sistemas de combustible,Refacciones automotrices o autopartes,0,80509,0 -5170,25172404,Sistemas de almacenaje de combustible híbrido,,0,80509,0 -5171,25172405,Sistemas de inyección de combustible,,0,80509,0 -5172,25172406,Tanques de combustible,,0,80509,0 -5173,25172407,Elementos respiraderos,Elementos de evacuación,0,80509,0 -5174,25172408,Tapas de aceite o combustible,,0,80509,0 -5175,25172409,Módulo de combustible,Bomba de gasolina,0,80509,0 -5176,25172410,Inyector o anillo de combustible,,0,80509,0 -5177,25172411,Flotador de tanque de combustible,,0,80509,0 -5178,25172500,Neumáticos y cámaras de neumáticos,Refacciones automotrices o autopartes,0,80509,0 -5179,25172502,Neumático para llantas de automóviles,,0,80509,0 -5180,25172503,Llantas para camiones pesados,,0,80509,0 -5181,25172504,Llantas para automóviles o camionetas,,0,80509,0 -5182,25172505,Llantas de bicicleta,,0,80509,0 -5183,25172506,Neumáticos de bicicleta,,0,80509,0 -5184,25172507,Cordón de llanta,Cable del neumático,0,80509,0 -5185,25172508,Labrado de llanta,Dibujo de las llantas,0,80509,0 -5186,25172509,Neumáticos para llantas de camiones pesados,,0,80509,0 -5187,25172510,Neumático de espuma,,0,80509,0 -5188,25172511,Kit de reparación de llantas,,0,80509,0 -5189,25172512,Llanta de motocicleta,,0,80509,0 -5190,25172600,Acabados y revestimientos exterior para vehículos,Refacciones automotrices o autopartes,0,80509,0 -5191,25172601,Acabados para automotores,,0,80509,0 -5192,25172602,Guardabarros,Salpicadera de moto,0,80509,0 -5193,25172603,Parachoques para automotores,Paragolpes automotrices,0,80509,0 -5194,25172604,Espejos retrovisores,,0,80509,0 -5195,25172605,Parrillas de vehículos,,0,80509,0 -5196,25172606,Capós de vehículos,Cofre de vehículos,0,80509,0 -5197,25172607,Paneles laterales de vehículos,,0,80509,0 -5198,25172608,Tableros,,0,80509,0 -5200,25172610,Placa de matrícula,,0,80509,0 -5201,25172611,Guarda – bómper,Protector de parachoques del vehículo,0,80509,0 -5202,25172700,Sistemas de control medioambiental,Refacciones automotrices o autopartes,0,80509,0 -5203,25172702,Sistemas de control medioambiental espacial,,0,80509,0 -5204,25172703,Sistemas de control medioambiental marítimo,,0,80509,0 -5205,25172704,Sistema control temperatura vehículo,Sistemas de climatización de vehículos,0,80509,0 -5206,25172705,Cerca para aceite,,0,80509,0 -5207,25172706,Protector de limo,,0,80509,0 -5208,25172707,Separador de aceite,,0,80509,0 -5209,25172708,Máquina para el control de algas,,0,80509,0 -5210,25172709,Aire acondicionado vehicular,,0,80509,0 -5211,25172710,Calentador vehicular,,0,80509,0 -5212,25172711,Purificador de aire para vehículos,,0,80509,0 -5213,25172800,Sistemas y componentes hidráulicos,Refacciones automotrices o autopartes,0,80509,0 -5214,25172802,Sistemas hidráulicos para automotores,,0,80509,0 -5215,25172803,Sistemas hidráulicos marítimos,,0,80509,0 -5216,25172900,Iluminación exterior para vehículos,Refacciones automotrices o autopartes,0,80509,0 -5217,25172901,Iluminación exterior para automóviles,,0,80509,0 -5218,25172903,Iluminación exterior para vagones de tren,,0,80509,0 -5219,25172904,Iluminación exterior para buques o barcos,,0,80509,0 -5220,25172905,Sistema para lavar o limpiar la farola delantera,Limpiaparabrisas o lavaparabrisas,0,80509,0 -5221,25172906,Reflectores,,0,80509,0 -5222,25172907,Luz frontal del vehículo,Faros de automóviles,0,80509,0 -5223,25172908,Luz trasera del vehículo,Calaveras de automóviles,0,80509,0 -5224,25173000,Iluminación interior para vehículos,Refacciones automotrices o autopartes,0,80509,0 -5225,25173001,Iluminación interior para automóviles,,0,80509,0 -5226,25173003,Iluminación interior para vagones de tren,,0,80509,0 -5227,25173004,Iluminación interior para buques o barcos,,0,80509,0 -5228,25173005,Ensamblajes de iluminación de las placas,Conjuntos de placa de luz,0,80509,0 -5229,25173006,Lente no recubierto para iluminación interior del vehículo,Lente de iluminación interior del vehículo no revestido,0,80509,0 -5230,25173007,Lente recubierto para iluminación interior del vehículo,Lente de iluminación interior de vehículo revestido,0,80509,0 -5231,25173008,Tubo de luz de vehículo,,0,80509,0 -5232,25173100,Sistemas y componentes de localización y navegación,Refacciones automotrices o autopartes,0,80509,0 -5233,25173107,Sistemas de posicionamiento global de vehículos,Sistema de Localización global de vehículos,0,80509,0 -5234,25173108,Sistemas de navegación vehicular,Sistema GPS,0,80509,0 -5235,25173300,Sistemas de control principal,Refacciones automotrices o autopartes,0,80509,0 -5236,25173303,Sistemas de computador,Sistemas informáticos para automóviles,0,80509,0 -5237,25173304,Sistemas electrónicos de ignición,,0,80509,0 -5238,25173700,Controles de emisión y de escape,Refacciones automotrices o autopartes,0,80509,0 -5239,25173701,Convertidores catalíticos,Catalizador,0,80509,0 -5240,25173702,Silenciadores de exhosto o resonadores,,0,80509,0 -5241,25173703,Colector de escape,,0,80509,0 -5242,25173704,Adaptadores de silenciadores,,0,80509,0 -5243,25173705,Amortiguadores de chispas,,0,80509,0 -5244,25173706,Alfombra catalítica,,0,80509,0 -5245,25173707,Sustrato catalítico,,0,80509,0 -5246,25173708,Recubrimiento y solución para desechos catalíticos,,0,80509,0 -5247,25173800,Sistemas de tren de transmisión,Refacciones automotrices o autopartes,0,80509,0 -5248,25173801,Ejes de manejo,,0,80509,0 -5249,25173802,Ejes no de manejo,,0,80509,0 -5250,25173803,Carcasas de eje,,0,80509,0 -5251,25173804,Husillos de giro de eje,,0,80509,0 -5252,25173805,Diferenciales,,0,80509,0 -5253,25173806,Juntas de velocidad constante,,0,80509,0 -5254,25173807,Ejes de cardán,,0,80509,0 -5255,25173808,Equipo de reparar ejes,,0,80509,0 -5256,25173809,Buje de eje,,0,80509,0 -5257,25173810,Juntas de cardán,,0,80509,0 -5258,25173811,Ejes de tracción,,0,80509,0 -5259,25173812,Transmisiones manuales,,0,80509,0 -5260,25173813,Transmisiones automáticas,,0,80509,0 -5261,25173815,Cables de embrague,,0,80509,0 -5262,25173816,Componentes hidráulicos de embrague,,0,80509,0 -5263,25173817,Cadenas de los engranajes conductores,Cadenas de transmisión,0,80509,0 -5264,25173818,Eje propulsor,,0,80509,0 -5265,25173819,Cigüeñal de la hélice marina,,0,80509,0 -5266,25173820,Transmisión continuamente variable,,0,80509,0 -5267,25173821,Ensamblaje de unidad de transferencia,,0,80509,0 -5268,25173900,Componentes eléctricos,Refacciones automotrices o autopartes,0,80509,0 -5269,25173901,Ignición,Encendido,0,80509,0 -5270,25173902,Arranque del motor,,0,80509,0 -5271,25173903,Componente de corte y protección eléctrica al interior del vehículo,,0,80509,0 -5272,25174000,Sistema de refrigerar de motor,Refacciones automotrices o autopartes,"0,1",80509,0 -5273,25174001,Ventilador,,"0,1",80509,0 -5274,25174002,Radiadores de motor,,"0,1",80509,0 -5275,25174003,Tapas de radiador,,"0,1",80509,0 -5276,25174004,Refrigerante de motor,,"0,1",80509,0 -5277,25174005,Indicadores de aguja del tablero,,"0,1",80509,0 -5278,25174006,Ensamble de manguera de agua refrigerante,,"0,1",80509,0 -5279,25174100,Sistemas de tejado,Refacciones automotrices o autopartes,0,80509,0 -5280,25174101,Salida de emergencia de vehículos,,0,80509,0 -5281,25174102,Techos permanentes convertibles,,0,80509,0 -5282,25174103,Techos duros desmontables,,0,80509,0 -5283,25174104,Techos blandos desmontables,,0,80509,0 -5284,25174105,Sistemas de rejillas de techo,Sistemas de barras de techo,0,80509,0 -5285,25174106,Sunroofs o techos corredizos,Quemacocos,0,80509,0 -5286,25174107,Deflectores de viento,,0,80509,0 -5287,25174200,Sistema de dirección,Refacciones automotrices o autopartes,0,80509,0 -5288,25174201,Sacos de dirección,Visor de viento,0,80509,0 -5289,25174202,Suspensión de dirección,,0,80509,0 -5290,25174203,Junta de bola,,0,80509,0 -5291,25174204,Sistema de dirección hidráulica,Rótula de dirección,0,80509,0 -5292,25174205,Bielas,,0,80509,0 -5293,25174206,Contramanivela,,0,80509,0 -5294,25174207,Varillas de dirección,,0,80509,0 -5295,25174208,Eje de bloqueo,,0,80509,0 -5296,25174209,Piñones,,0,80509,0 -5297,25174210,Cable de afinamiento de dirección hidráulica,Cable de ajuste de dirección hidráulica,0,80509,0 -5298,25174211,Timones o volantes,Volantes,0,80509,0 -5299,25174212,Ensambles de columna de dirección,,0,80509,0 -5301,25174214,Timones o volantes de madera,Volante de madera,0,80509,0 -5302,25174215,Timones o volantes de cuero,Volante de cuero,0,80509,0 -5303,25174216,Brazo articulado de dirección,,0,80509,0 -5304,25174217,Engranaje de dirección de vehículo,Equipo de dirección de vehículos,0,80509,0 -5305,25174400,Sistemas del interior de vehículos,Refacciones automotrices o autopartes,0,80509,0 -5306,25174401,Biseles,,0,80509,0 -5307,25174402,Consolas,,0,80509,0 -5308,25174403,Paneles de puerta,,0,80509,0 -5309,25174404,Tapizado del techo,,0,80509,0 -5310,25174405,Grupos de instrumentos,,0,80509,0 -5311,25174406,Paneles de instrumentos,,0,80509,0 -5312,25174407,Pedales,,0,80509,0 -5313,25174408,Puntos para energía o para encendedores,Puertos de alimentación o encendedores,0,80509,0 -5314,25174409,Visores de sol,Viseras de sol,0,80509,0 -5315,25174410,Componentes y sistemas de sonido del vehículo,Sistema de audio para el vehículo,0,80509,0 -5316,25174411,Acolchado de caucho de la llave,,0,80509,0 -5317,25174412,Aplique decorativo del vehículo,Aplicación decorativa para vehículos,0,80509,0 -5318,25174413,Escudo o emblema interior del vehículo,,0,80509,0 -5319,25174414,Tacógrafo,Tacómetro de un automóvil,0,80509,0 -5320,25174415,Indicador de la presión de aceite del vehículo,,0,80509,0 -5321,25174416,Encendedor de cigarrillos del vehículo,,0,80509,0 -5322,25174417,Interruptor de combinación del vehículo,,0,80509,0 -5323,25174418,Tapetes del vehículo,,0,80509,0 -5324,25174419,Sistema de audio video del vehículo,,0,80509,0 -5325,25174420,Cenicero del vehículo,,0,80509,0 -5326,25174421,Cortina del vehículo,,0,80509,0 -5327,25174600,Sistemas de asientos del vehículo,Refacciones automotrices o autopartes,0,80509,0 -5328,25174601,Fundas de asientos,,0,80509,0 -5329,25174602,Cojinería de asientos,Cojines de asiento,0,80509,0 -5330,25174603,Marcos de asientos,,0,80509,0 -5331,25174604,Silla de timonel de bote,Asiento del timón o del barco,0,80509,0 -5332,25174605,Silla de motocicleta,,0,80509,0 -5333,25174700,Componentes de ciclo no motorizado o accesorios,Componentes de bicicletas o accesorios,0,80509,0 -5334,25174701,Pedales de bicicleta,,0,80509,0 -5335,25174702,Rayos de bicicleta,,0,80509,0 -5336,25174703,Caja trasera de motocicleta,,0,80509,0 -5337,25174704,Canasta para vehículo de dos ruedas,,0,80509,0 -5338,25174705,Caja para llevar el correo en vehículo de dos ruedas,,0,80509,0 -5339,25174800,Componentes y sistemas de vehículos especializados,Refacciones automotrices o autopartes,0,80509,0 -5340,25174801,Estabilizador de camión de succión o gato de piso,,0,80509,0 -5341,25174802,Boom para vehículos de rescate o de remolque,,0,80509,0 -5342,25174803,Gancho de remolcar,,0,80509,0 -5343,25174804,Mástil lateral para vehículo,,0,80509,0 -5344,25174805,Aspersor de arena para vehículo,Esparcidor de arena o sal para vehículos,0,80509,0 -5345,25174806,Pijama para vehículo a la medida,Funda cubre auto,0,80509,0 -5346,25174807,Bisagra de vehículo,,0,80509,0 -5347,25174808,Caja de almacenamiento de accesorio para vehículos,,0,80509,0 -5348,25174809,Escalera de vehículo,,0,80509,0 -5349,25174810,Gato de vehículo,,0,80509,0 -5350,25174900,Aisladores y amortiguadores de vibración de vehículos,Refacciones automotrices o autopartes,0,80509,0 -5351,25174901,Aislador y amortiguador moldeado para la vibración del vehículo,Amortiguador y aislador de vibraciones de vehículos moldeados,0,80509,0 -5352,25174902,Aislador y amortiguador extruido para la vibración del vehículo,Amortiguador y aislador de vibraciones del vehículo extruido,0,80509,0 -5353,25174903,Aislador y amortiguador fundido para la vibración del vehículo,Amortiguador y aislador de vibraciones,0,80509,0 -5354,25175000,Sistemas de cargado eléctrico de vehículos,Refacciones automotrices o autopartes,0,80509,0 -5355,25175001,Cargador portátil para vehículo eléctrico,,0,80509,0 -5356,25175002,Estación de carga de vehículo eléctrico,,0,80509,0 -5357,25175003,Kit para cargar vehículo eléctrico,,0,80509,0 -5358,25181600,Chasis automotriz,Chasis para automotor,0,80509,0 -5359,25181601,Chasis para automóviles,Armazón para automóviles,0,80509,0 -5360,25181602,Chasis para camiones,Armazón para camiones,0,80509,0 -5361,25181603,Bastidores de motos,Chasis para motos,0,80509,0 -5362,25181604,Carrocería de camión de carga,,0,80509,0 -5363,25181605,Carrocería de volqueta,Carrocería para camión de volteo,0,80509,0 -5364,25181606,Carrocería de tráiler,Plataforma de tráiler,0,80509,0 -5365,25181607,Cabina de tractor,,0,80509,0 -5366,25181608,Bastidor de vehículo,,0,80509,0 -5367,25181609,Tráiler granelero,Tráiler para remolque granelero,0,80509,0 -5368,25181610,Tráiler para carga aérea,Tráiler para remolcar carga aérea,0,80509,0 -5369,25181611,Tráiler dolly,Vehículo sin motor para conexión,0,80509,0 -5370,25181612,Tráiler agrícola,Remolque agrícola,0,80509,0 -5371,25181700,Remolques para productos y materiales,Vehículos para traslado de productos y materiales,0,80509,0 -5372,25181701,Tráiler para contenedores,Tráiler para remolque de contenedores,0,80509,0 -5373,25181702,Tráiler cama alta,Tráiler con plataforma,0,80509,0 -5374,25181703,Remolques para ganado,,0,80509,0 -5375,25181704,Remolques carrotanque sin temperatura controlada,Tanques de almacenamiento sin temperatura controlada,0,80509,0 -5376,25181705,Remolques carrotanque con temperatura controlada,Tanques de almacenamiento con temperatura controlada,0,80509,0 -5377,25181706,Remolques contenedor con temperatura controlada,Contenedores de temperatura controlada,0,80509,0 -5378,25181707,Remolques para transporte de automóviles (niñeras),"Madrinas para transporte de automóviles, Plataforma para transporte de automóviles",0,80509,0 -5379,25181708,Enganches de remolque,Gancho de remolque,0,80509,0 -5380,25181709,Pala cargadora,"Cuchara cargadora, Pala cargadora frontal, Pala mecánic",0,80509,0 -5381,25181710,Placas de matrícula de tráiler,Placas de tráiler,0,80509,0 -5382,25181711,Remolque de motonieve,Trineo remolque,0,80509,0 -5383,25181712,Remolque de motocicleta,,0,80509,0 -5384,25181713,Remolque de botes,,0,80509,0 -5385,25181714,Semi – remolque,Caja de remolque,0,80509,0 -5386,25181715,Tráiler volqueta,"Remolque góndola, Remolque volquete, Tráiler de volte",0,80509,0 -5387,25181716,Chasís tráiler,Armazón para tráiler,0,80509,0 -5388,25181717,Tráiler cama baja,"Remolque para cama baja, Tráiler con plataforma baja",0,80509,0 -5389,25181718,Tráiler con carrete de cable,Tráiler con remolque de carrete de cable,0,80509,0 -5390,25181719,Tráiler refrigerado,Tráiler con remolque de refrigeración,0,80509,0 -5392,25191501,Sistemas de entrenamiento de apoyo terrestre,,0,80509,0 -5393,25191502,Sistemas de ensayo o mantenimiento de apoyo terrestre,,0,80509,0 -5394,25191503,Sistemas integrados de información de mantenimiento,,0,80509,0 -5395,25191504,Simuladores de vuelo para aviones,Simuladores o entrenadores de vuelo de aeronaves,0,80509,0 -5396,25191505,Medios de carga y descarga para aviones,Palet de carga y descarga de aviones,0,80509,0 -5397,25191506,Equipo para recargar combustible de aviones,Equipo de reabastecimiento de combustible de aviones,0,80509,0 -5398,25191507,Equipo para deshielo de aviones,Equipos de deshielo de aeronaves,0,80509,0 -5399,25191508,Pasarela telescópica para aviones,"Puente de espera, embarque o llegada para aviones",0,80509,0 -5400,25191509,Tractores remolcadores para aviones,Remolcador de aviones,0,80509,0 -5401,25191510,Unidades de potencia para pista aérea,,0,80509,0 -5402,25191511,Equipo de lavabos para aeronaves,Equipo de servicio de lavado para aviones,0,80509,0 -5403,25191512,Escalerillas transportables o rodantes,Escaleras transportables o rodantes,0,80509,0 -5404,25191513,Kit de mantenimiento de vehículo de soporte en tierra,,0,80509,0 -5405,25191514,Barra de remolque de avión,Barra de remolque para avión ligero,0,80509,0 -5406,25191515,Sistema de aproximación controlado por tierra,Sistema de aterrizaje instrumental,0,80509,0 -5407,25191516,Unidad neumática para arranque de aeronaves,Unidad de arranque de aire de la aeronave,0,80509,0 -5408,25191517,Camión calefactor y de aire acondicionado para aeronaves,,0,80509,0 -5409,25191518,Equipo sensor de pruebas aeronáuticas,Sistema de pruebas aeronáuticas,0,80509,0 -5410,25191519,Adaptador de remolcado de aviones,,0,80509,0 -5411,25191520,Cámara de prueba ambiental,,0,80509,0 -5412,25191521,Sistema retráctil de soporte de cable gancho,,0,80509,0 -5413,25191522,Set de mantenimiento de motor de aeronave,,0,80509,0 -5414,25191523,Tractor para remolcar contenedores de equipaje aéreo,Remolcador o tractor de equipaje de avión,0,80509,0 -5415,25191524,Banda transportadora para cargar equipaje en el avión,,0,80509,0 -5416,25191525,Camión para cargar combustible al avión,,0,80509,0 -5417,25191700,Equipo para el mantenimiento de vehículo,,0,80509,0 -5418,25191701,Equipos de balanceo de llantas,Equipos de balanceo de neumáticos,0,80509,0 -5419,25191702,Equipos de alineación de llantas,Equipos de alineación de neumáticos,0,80509,0 -5420,25191703,Máquinas para cambiar llantas,"Cambiadores de neumáticos, Desmontadora de llantas",0,80509,0 -5421,25191704,Pedestales de vehículo o motor,Soporte de vehículo o motor,0,80509,0 -5422,25191705,Balanza de pistón y varilla de motor,Balanza de presión y motor de levas,0,80509,0 -5423,25191706,Gato para levantar material rodante férreo,Colector elevador de material rodante ferroviario,0,80509,0 -5424,25191707,Mesa ferroviaria para levantar motor y componente,,0,80509,0 -5425,25191708,Balanceo de carburador,,0,80509,0 -5426,25191709,Ajustador de potencia,Regulador de potencia,0,80509,0 -5427,25191710,Lapeadora de válvulas,,0,80509,0 -5428,25191711,Pulidora de válvulas,Pasta pulidora de válvulas,0,80509,0 -5429,25191712,Cortador de asientos de válvulas,Cortador de asientos de obturador,0,80509,0 -5430,25191713,Dispositivo para reciclar anticongelante,,0,80509,0 -5431,25191714,Torno para frenos de tambor,,0,80509,0 -5432,25191715,Pulidora de zapatas de freno,,0,80509,0 -5433,25191716,Alicate para resortes de freno,,0,80509,0 -5434,25191717,Máquina para enderezar carrocerías de vehículos,,0,80509,0 -5435,25191718,Carrito para reparar carros por debajo,,0,80509,0 -5436,25191719,Llave para tuercas de llantas,,0,80509,0 -5437,25191720,Llave de bujías,,0,80509,0 -5438,25191721,Limpiador de bujías,,0,80509,0 -5439,25191722,Juego de herramientas de motor,,0,80509,0 -5440,25191723,Calentador de pistones,,0,80509,0 -5441,25191724,Kit para reconstruir transmisión automática,,0,80509,0 -5442,25191725,Máquina perforadora para automóviles,,0,80509,0 -5443,25191726,Máquina perfeccionadora para automóviles,,0,80509,0 -5444,25191727,Alineador de varillas de conexión,,0,80509,0 -5445,25191728,Balanceador de cigüeñal,Balanceador de eje con engranaje,0,80509,0 -5446,25191729,Pulidora de cigüeñal,,0,80509,0 -5447,25191730,Luz de toma de tiempos,Luz de sincronización,0,80509,0 -5448,25191731,Limpiador de convertidor de torque,,0,80509,0 -5449,25191732,Perno roscado para pistón,,0,80509,0 -5450,25191733,Pulidora de pistones,,0,80509,0 -5451,25191734,Compresor de resorte de válvula,Compresor de muelles de válvula,0,80509,0 -5452,25191735,Compresor de anillo de pistón,,0,80509,0 -5453,25191736,Simulador de conducción de vehículo,,0,80509,0 -5454,25191737,Inflador automático de llantas,Inflador automático de neumáticos,0,80509,0 -5455,25191738,Abridor de llantas,Esparcidor de neumáticos,0,80509,0 -5456,25191739,Tester de simulación de engranajes de dirección,,0,80509,0 -5457,25191740,Máquina para lavar vehículos,Lavadora de vehículos,0,80509,0 -5458,25191741,Máquina para lavar trenes,Lavadora de ferrocarril,0,80509,0 -5459,25191742,Máquina para lavar partes de vehículos,Lavadora de partes de vehículos,0,80509,0 -5460,25191743,Máquina para lavar componentes de ferrocarril,Lavadora de componentes ferroviarios,0,80509,0 -5461,25191800,Equipo de medición y prueba de vehículos,,0,80509,0 -5462,25191801,Probador de inducidos,,0,80509,0 -5463,25191802,Tacómetro de ángulo de contacto,,0,80509,0 -5464,25191803,Tester de humo diesel,Probador de humo diésel,0,80509,0 -5465,25191804,Probador de radiador,Probador de presión del radiador,0,80509,0 -5466,25191805,Probador de distribuidor de motor,,0,80509,0 -5467,25191806,Hidrómetro de batería,,0,80509,0 -5468,25191807,Detector de fugas de aceite de rodamiento,,0,80509,0 -5469,25191808,Probador de freno,,0,80509,0 -5470,25191809,Probador de ángulo de deslizamiento,,0,80509,0 -5471,25191810,Dinamómetro de chasis,,0,80509,0 -5472,25191811,Probador del velocímetro,,0,80509,0 -5473,25191812,Probador de bloqueo,,0,80509,0 -5474,25191813,Tester de brecha de bujías,Indicador de espacio entre bujías,0,80509,0 -5475,25191814,Tester de bujías,Probador de bujías,0,80509,0 -5476,25191815,Tester del inducido,Probador de armadura,0,80509,0 -5477,25191816,Tester del resorte tipo compresión,Comprobador de muelles de compresión,0,80509,0 -5478,25191817,Medidor de presión de la compresión,,0,80509,0 -5479,25191818,Tester del elemento limpiador de aire,Probador de elemento de filtro de aire,0,80509,0 -5480,25191819,Tester de simulación de los frenos anti bloqueo abs,Sistema de frenado antibloqueo probador de simulación ABS,0,80509,0 -5482,25191821,Tester de simulador de motor,Probador de simulación de motor,0,80509,0 -5483,25191822,Tester de inyector de motor,Probador del inyector del motor,0,80509,0 -5484,25191823,Lecho de prueba de motor,Banco de prueba del motor,0,80509,0 -5485,25191824,Probador de puesta a punto del motor,,0,80509,0 -5486,25191825,Medidor de consumo de combustible,,0,80509,0 -5487,25191826,Tester de fugas de aceite,Comprobador de fugas de aceite,0,80509,0 -5488,25191827,Tester de simulación de suspensión de control electrónico ecs,,0,80509,0 -5489,25191828,Tester de transmisión automática,Probador de transmisión automática,0,80509,0 -5490,25191829,Sistema de inspección de control electrónico,,0,80509,0 -5491,25191830,Tester de luz frontal,Probador de faros,0,80509,0 -5492,25191831,Tester del regulador de ventanas del vehículo,,0,80509,0 -5493,25191832,Medidor de vacío para mantenimiento de vehículo,Válvula de vacío para servicio de vehículos,0,80509,0 -5494,25191833,Medidor de avance del camber,Calibrador de caster,0,80509,0 -5495,25191834,Tester de resistencia de bobina de condensación o ohm,Resistencia del condensador de bobina o ohm,0,80509,0 -5496,25191835,Válvula de presión de ruedas,,0,80509,0 -5497,25191836,Medidor de convergencia de las ruedas delanteras,,0,80509,0 -5498,25191837,Medidor de radio de giro,,0,80509,0 -5499,25191838,Sistema de inspección de vehículos automotores,,0,80509,0 -5500,25201500,Fuselaje y componentes de avión,,0,80509,0 -5501,25201501,Spoilers de avión,Aerofrenos,0,80509,0 -5502,25201502,Aletas de avión,,0,80509,0 -5503,25201503,Estabilizadores horizontales de avión,,0,80509,0 -5504,25201504,Aletas canard de avión,,0,80509,0 -5505,25201505,Listones de avión,,0,80509,0 -5506,25201506,Flaps o transmisión de flaps de avión,Aletas o transmisión de aletas de avión,0,80509,0 -5507,25201507,Timones de avión,Timones de dirección,0,80509,0 -5508,25201508,Elevadores de avión,,0,80509,0 -5509,25201509,Alerones de avión,,0,80509,0 -5510,25201510,Propulsores de avión,,0,80509,0 -5511,25201511,Alas de avión,,0,80509,0 -5512,25201512,Fuselajes de avión,,0,80509,0 -5513,25201513,Cúpulas protectoras de la antena de avión,,0,80509,0 -5514,25201514,Rotores de avión,,0,80509,0 -5515,25201515,Ventilador de elevación de avión,,0,80509,0 -5516,25201516,Escudetes de avión,,0,80509,0 -5517,25201517,Muebles de avión,,0,80509,0 -5518,25201518,Dobladores de avión,,0,80509,0 -5519,25201519,Nervaduras de avión,,0,80509,0 -5520,25201520,Largueros de avión,,0,80509,0 -5521,25201521,Aleta compensadora para aspa de hélice,,0,80509,0 -5522,25201522,Vaina universal de helicóptero,,0,80509,0 -5523,25201600,Sistemas de situación y navegación aeroespacial y componentes,,0,80509,0 -5524,25201601,Sistemas de control digital del altitud del avión,,0,80509,0 -5525,25201602,Faros de navegación de avión,,0,80509,0 -5526,25201603,Sistemas de seguimiento de aire a tierra,,0,80509,0 -5527,25201604,Sistemas de guía aeronáutica,,0,80509,0 -5528,25201605,Controles de volante de avión,,0,80509,0 -5529,25201606,Sistemas de control de la altitud del nave espacial,,0,80509,0 -5530,25201700,Sistemas relacionados con las comunicaciones de vuelo,,0,80509,0 -5531,25201701,Sistemas de comunicación del avión,,0,80509,0 -5532,25201702,Registradores de datos de vuelo,Caja negra,0,80509,0 -5533,25201703,Contramedidas de avión,Contramedidas Infrarrojas,0,80509,0 -5534,25201704,Sistemas de codificación o de decodificación,,0,80509,0 -5535,25201705,Sistemas de telemetría de aeronaves,,0,80509,0 -5536,25201706,Electrónica de interfaz del avión,,0,80509,0 -5537,25201707,Giroscopio del avión,,0,80509,0 -5538,25201708,Cámaras de avión,,0,80509,0 -5539,25201709,Sondas o sensores de avión,,0,80509,0 -5540,25201710,Guías de onda del avión,,0,80509,0 -5541,25202100,Instrumentación de vuelo,,0,80509,0 -5542,25202101,Indicadores de la cabina aeroespacial,,0,80509,0 -5543,25202102,Medidores de la cabina aeroespacial,Medidores de la cabina de vuelo,0,80509,0 -5544,25202103,Paneles de despliegue aeroespacial de la cabina,,0,80509,0 -5545,25202104,Paneles de interruptores aeroespaciales de la cabina,,0,80509,0 -5546,25202105,Presentación visual de cabeza levantada (hud),,0,80509,0 -5547,25202200,Sistemas del aterrizaje y de frenos del avión,,0,80509,0 -5548,25202201,Sistemas de frenar del avión,Sistemas de frenado de aeronaves,0,80509,0 -5549,25202202,Rampas de arrastre de avión,"Carro de arrastre de avión, Remolcador de aviones",0,80509,0 -5550,25202203,Ruedas de avión,,0,80509,0 -5551,25202204,Montajes del tren de aterrizaje,,0,80509,0 -5552,25202205,Llantas de avión,Neumáticos de avión,0,80509,0 -5553,25202206,Controles anti – deslizamiento de avión,,0,80509,0 -5554,25202207,Contrafuerte del tren de aterrizaje,"Brazo de arrastre del tren de aterrizaje, Estribo del tren de aterrizaje",0,80509,0 -5555,25202400,Sistemas y depósitos de combustible del avión,,0,80509,0 -5556,25202401,Tanques interiores de combustible del avión,Depósitos de combustible interno de las aeronaves,0,80509,0 -5557,25202402,Depósitos desechables de combustible del avión,,0,80509,0 -5558,25202403,Tanques de propelente del avión,Tanque propulsor de aeronaves,0,80509,0 -5559,25202404,Sistemas híbridos de almacenaje de combustible de avión,,0,80509,0 -5560,25202405,Sistemas de manejo de combustible del avión,,0,80509,0 -5561,25202406,Post – impulsores,Sistema de propulsión,0,80509,0 -5562,25202500,Equipo de avión,,0,80509,0 -5563,25202501,Sistemas hidráulicos de avión,,0,80509,0 -5564,25202502,Iluminación exterior de avión,,0,80509,0 -5565,25202503,Iluminación interior de avión,,0,80509,0 -5566,25202504,Limpiaparabrisas de avión,,0,80509,0 -5567,25202505,Sistemas de descongelación o sistemas de desempañar a bordo de avión,,0,80509,0 -5568,25202506,Puertas de avión,,0,80509,0 -5569,25202507,Ventanas de avión,,0,80509,0 -5570,25202508,Parabrisas de avión,,0,80509,0 -5571,25202509,Montajes de choque del avión,Soporte de choque para aeronaves,0,80509,0 -5572,25202510,Conjuntos de anillo colector del avión,Anillo colector de avión,0,80509,0 -5573,25202600,Sistemas y componentes del control ambiental del avión,,0,80509,0 -5574,25202601,Controladores ambientales del avión,,0,80509,0 -5575,25202602,Reguladores ambientales del avión,,0,80509,0 -5576,25202603,Turbinas para enfriar el avión,Turbinas de enfriamiento de aeronaves,0,80509,0 -5577,25202604,Ventiladores para enfriar el avión,Ventiladores de enfriamiento de aeronaves,0,80509,0 -5579,25202606,Separadores del agua del avión,,0,80509,0 -5580,25202607,Equipo del oxígeno del avión,,0,80509,0 -5581,25202700,Acumuladores del avión,,0,80509,0 -5582,25202701,Acumuladores hidráulicos del avión,Acumuladores hidráulicos de aeronaves,0,80509,0 -5583,25202702,Acumuladores neumáticos del avión,,0,80509,0 -5584,26101100,Motores eléctricos de corriente alterna AC,,0,80509,0 -5585,26101101,Motor ac automotriz,"Motor de corriente alterna para automóvil, Motor eléctrico de corriente alterna para automóvil",0,80509,0 -5586,26101102,Motor ac de freno,Motor de corriente alterna de freno,0,80509,0 -5587,26101103,Motor ac de trabajo de granja,Motor de corriente alterna de trabajo de granja,0,80509,0 -5588,26101105,Motor ac de calefacción y enfriamiento,Motor de corriente alterna de calefacción y enfriamiento,0,80509,0 -5589,26101106,Motor ac inversor,Motor de corriente alterna inversor,0,80509,0 -5590,26101107,Motor ac de bomba,Motor de corriente alterna de bomba,0,80509,0 -5591,26101108,Motor ac de compresor,Motor de corriente alterna de compresor,0,80509,0 -5592,26101109,Motor ac sincrónico,Motor de corriente alterna sincrónico,0,80509,0 -5593,26101110,Motor ac multi – velocidad,Motor de corriente alterna multi velocidad,0,80509,0 -5594,26101111,Motor ac de de lavadora a presión,Motor de corriente alterna de lavado a presión,0,80509,0 -5595,26101112,Motor ac de propósito general,Motor de corriente alterna de propósito general,0,80509,0 -5596,26101113,Motor ac sumergible,Motor de corriente alterna sumergible,0,80509,0 -5597,26101114,Motor ac de inducción,Motor de corriente alterna de inducción,0,80509,0 -5598,26101115,Motor ac multi – fase,Motor de corriente alterna multi fase,0,80509,0 -5599,26101116,Motor ac de fase sencilla,Motor de corriente alterna de fase sencilla,0,80509,0 -5600,26101117,Motor ac de eje motriz,Motor de corriente alterna de eje motriz,0,80509,0 -5601,26101200,Motores eléctricos de corriente directa DC,,0,80509,0 -5602,26101201,Motor dc en derivación,Motor de corriente directa en derivación,0,80509,0 -5603,26101202,Motor dc de pasos,Motor de corriente directa de pasos,0,80509,0 -5604,26101203,Motor dc sin núcleo,Motor de corriente directa sin núcleo,0,80509,0 -5605,26101204,Motor dc con excitación en derivación,Motor de corriente directa con excitación en derivación,0,80509,0 -5606,26101205,Servo motor dc,Servomotor de corriente directa,0,80509,0 -5607,26101206,Motor dc de ángulo de torque limitado,Motor de corriente directa de ángulo de torque limitado,0,80509,0 -5608,26101207,Motor dc linear,Motor de corriente directa linear,0,80509,0 -5609,26101208,Motor dc de imán permanente,Motor de corriente directa de imán permanente,0,80509,0 -5610,26101209,Motor dc sin escobillas,Motor de corriente directa sin escobillas,0,80509,0 -5611,26101210,Motor dc de excitación compuesta,Motor de corriente directa de excitación compuesta,0,80509,0 -5612,26101211,Motor ultrasónico o de vibración,,0,80509,0 -5613,26101212,Motor de huso,,0,80509,0 -5614,26101300,Motores no eléctricos,,0,80509,0 -5615,26101301,Motor de aire,Motor de arranque,0,80509,0 -5616,26101302,Alternador,,0,80509,0 -5617,26101303,Dinamotor,,0,80509,0 -5618,26101304,Motor hidráulico,,0,80509,0 -5619,26101306,Motor de cohete líquido,,0,80509,0 -5620,26101309,Motor de cohete sólido,,0,80509,0 -5621,26101310,Motor de torque,,0,80509,0 -5622,26101311,Motor universal,,0,80509,0 -5623,26101312,Motor de pistón axial,,0,80509,0 -5624,26101313,Motor de pistón radial,,0,80509,0 -5625,26101400,Componentes de motores o generadores,,0,80509,0 -5626,26101401,Inducido,Bobina de arranque,0,80509,0 -5627,26101402,Adaptador de base de motor,,0,80509,0 -5628,26101403,Freno de motor,,0,80509,0 -5629,26101404,Escobilla de motor,,0,80509,0 -5630,26101405,Bobina de motor,,0,80509,0 -5631,26101406,Montura o base de motor,,0,80509,0 -5632,26101408,Poste de motor,,0,80509,0 -5633,26101409,Rotor de motor,,0,80509,0 -5634,26101410,Estator de motor,,0,80509,0 -5635,26101411,Laminación de motor,,0,80509,0 -5636,26101412,Kit de reparación de motor,,0,80509,0 -5637,26101413,Carcasa de motor,,0,80509,0 -5638,26101414,Transportador de escobillas de motor,,0,80509,0 -5639,26101415,Conmutador de motor,,0,80509,0 -5640,26101500,Motores,,"0,1",80509,0 -5641,26101501,Motores hidráulicos,,"0,1",80509,0 -5642,26101502,Motores neumáticos,,"0,1",80509,0 -5643,26101503,Motores a gas,,"0,1",80509,0 -5644,26101504,Motores diesel,,"0,1",80509,0 -5645,26101505,Motores de vapor,,"0,1",80509,0 -5646,26101506,Motores de turbina,,"0,1",80509,0 -5647,26101507,Motores de turbina con hélice,,"0,1",80509,0 -5648,26101508,Motores térmicos,,"0,1",80509,0 -5649,26101509,Motores hidroeléctricos,,"0,1",80509,0 -5650,26101510,Máquinas rotativas,,"0,1",80509,0 -5651,26101511,Motores de turbina hidráulica,,"0,1",80509,0 -5652,26101512,Motores turbohélice,,"0,1",80509,0 -5653,26101513,Kit de reparación de motores,,"0,1",80509,0 -5654,26101514,Motor de jet,,"0,1",80509,0 -5655,26101515,Motor fuera de borda a gasolina,,"0,1",80509,0 -5656,26101700,Accesorios y componentes de motor,,0,80509,0 -5657,26101701,Quemadores para aeronaves,,0,80509,0 -5658,26101702,Compresores de motor de avión,Compresores de motores de aeronaves,0,80509,0 -5659,26101703,Difusores de motor de avión,,0,80509,0 -5660,26101704,Monturas de motor,Soportes del motor,0,80509,0 -5661,26101705,Gatos de tornillo de los ejes motores del avión,,0,80509,0 -5662,26101706,Unidades de transmisión del avión,,0,80509,0 -5663,26101707,Ejes compensadores,,0,80509,0 -5664,26101708,Seguidores de la leva,,0,80509,0 -5665,26101709,Elevadores de árbol de levas,,0,80509,0 -5666,26101710,Carburadores,,0,80509,0 -5667,26101711,Bielas,,0,80509,0 -5668,26101712,Válvulas de ventilación del cárter,,0,80509,0 -5669,26101713,Culata de cilindro,Tapa del cilindro,0,80509,0 -5670,26101715,Tapas de motor,,0,80509,0 -5671,26101716,Forjas del motor,,0,80509,0 -5672,26101717,Calentadores de motor,,0,80509,0 -5673,26101718,Sistemas de ignición de motor,,0,80509,0 -5674,26101719,Súper cargadores,,0,80509,0 -5675,26101720,Turbocargador,,0,80509,0 -5676,26101721,Poleas del motor,,0,80509,0 -5677,26101723,Lata de vapor de combustible,,0,80509,0 -5678,26101724,Tapón brillante,,0,80509,0 -5679,26101725,Medidores de varilla de aceite,"Bayoneta para medir aceite, Varilla del nivel de aceite",0,80509,0 -5680,26101726,Coladores de aceite,Filtros de aceite,0,80509,0 -5682,26101728,Tubos de varilla de empuje,,0,80509,0 -5683,26101729,Bolas de brazo oscilante,Brazo oscilante articulación de bolas buje,0,80509,0 -5684,26101730,Ejes de brazo oscilante,Soporte suspensión,0,80509,0 -5685,26101731,Brazos oscilantes,,0,80509,0 -5686,26101732,Bujía de encendido,,0,80509,0 -5687,26101733,Chorro de carburador,,0,80509,0 -5688,26101734,Diafragmas de carburador,,0,80509,0 -5689,26101735,Batea de aceite,"Bandeja recolectora de aceite, Colector de aceite",0,80509,0 -5690,26101736,Pistones,,0,80509,0 -5691,26101737,Cadena de distribución,,0,80509,0 -5692,26101738,Múltiples de entrada,,0,80509,0 -5693,26101740,Inyectores de combustible,,0,80509,0 -5694,26101741,Manguitos de motor,Mangueras de calefacción para motor,0,80509,0 -5695,26101742,Distribuidores de inyección de combustible,,0,80509,0 -5696,26101743,Válvulas de motor,,0,80509,0 -5697,26101747,Varas de empuje,,0,80509,0 -5698,26101748,Volante de inercia del motor,,0,80509,0 -5699,26101749,Cigüeñal,,0,80509,0 -5700,26101750,Válvula de estrangulación,,0,80509,0 -5701,26101751,Controles electrónicos de motor,,0,80509,0 -5702,26101754,Asiento de válvula del motor,,0,80509,0 -5703,26101755,Guía de válvula,,0,80509,0 -5704,26101756,Adaptadores del carburador,,0,80509,0 -5705,26101757,Accesorios de las bujías,,0,80509,0 -5706,26101758,Ajustadores del balancín,,0,80509,0 -5707,26101759,Adaptadores del motor de arranque,,0,80509,0 -5708,26101760,Vástagos del estárter,,0,80509,0 -5709,26101761,Tapones del árbol de levas,,0,80509,0 -5710,26101762,Varillaje de los componentes del motor,,0,80509,0 -5711,26101763,Tapones del anticongelante,,0,80509,0 -5712,26101764,Revestimiento de los cilindros,,0,80509,0 -5713,26101765,Amortiguadores de vibración,,0,80509,0 -5714,26101766,Reguladores,,0,80509,0 -5715,26101767,Estator de turbina,,0,80509,0 -5716,26101768,Eje de turbina,,0,80509,0 -5717,26101769,Bobina de ignición,,0,80509,0 -5718,26101770,Control electrónico de motor para motores a gasolina,Control electrónico de motor a gasolina,0,80509,0 -5719,26101771,Control electrónico de motor para motores diesel,Control electrónico de motor diésel,0,80509,0 -5720,26101772,Limpiador de inyector,,0,80509,0 -5721,26101773,Válvula de medición proporcional diesel,Válvula dosificadora proporcional diésel,0,80509,0 -5722,26101774,Válvula solenoide ara purgar lata,Válvula solenoide de purga de cárter,0,80509,0 -5723,26101775,Válvula de alta presión diesel,,0,80509,0 -5724,26101776,Bobina del inmovilizador,,0,80509,0 -5725,26101777,Bobina del inyector de combustible gasolina,,0,80509,0 -5726,26101778,Bobina del inyector de combustible diesel,,0,80509,0 -5727,26101779,Arnés pasacables para cableado,Arnés de cableado,0,80509,0 -5728,26101780,Inyector de aceite o válvula de medición,,0,80509,0 -5729,26101781,Pasador de pistón,,0,80509,0 -5730,26101782,Distribuidor de ignición,,0,80509,0 -5731,26101783,Unión de husillos hidráulicos,,0,80509,0 -5732,26101784,Araña del diferencial,,0,80509,0 -5733,26101785,Elemento colador de aceite,Colador de aceite,0,80509,0 -5734,26101786,Alza válvulas,Varilla levanta válvulas,0,80509,0 -5735,26101787,Válvula y bastidor de admisión,,0,80509,0 -5736,26101900,Componentes de motor de combustión interna,,0,80509,0 -5737,26101903,Árbol de distribución,,0,80509,0 -5738,26101904,Boquilla de inyección de combustible,,0,80509,0 -5739,26101905,Bloque de cilindros,,0,80509,0 -5740,26111500,Transmisión de energía cinética,,0,80509,0 -5741,26111503,Dispositivos de velocidad regulables,,0,80509,0 -5742,26111504,Correas de la transmisión,Bandas de transmisión,0,80509,0 -5743,26111505,Cadenas de la transmisión,,0,80509,0 -5744,26111506,Aparatos de movimiento rectilíneo,,0,80509,0 -5745,26111508,Despegues de energía,,0,80509,0 -5746,26111509,Culatas de transmisión,,0,80509,0 -5747,26111510,Árboles de transmisión,,0,80509,0 -5748,26111512,Ejes,,0,80509,0 -5749,26111513,Cadenas transmisoras de potencia,,0,80509,0 -5750,26111514,Uniones de charnela,Uniones para codos,0,80509,0 -5751,26111515,Servo controlador,,0,80509,0 -5752,26111516,Transmisión escalonada o transmisión stepper o graduador escalonado,,0,80509,0 -5753,26111517,Portaengranajes,,0,80509,0 -5754,26111518,Puntal tensor,,0,80509,0 -5755,26111519,Convertidores de torque,,0,80509,0 -5756,26111520,Muñones,,0,80509,0 -5757,26111521,Cabeza del impulsor,,0,80509,0 -5758,26111522,Conjunto del impulsor,,0,80509,0 -5759,26111523,Topes de retención,,0,80509,0 -5760,26111524,Unidades de engranajes,,0,80509,0 -5761,26111525,Transmisiones de tambor,,0,80509,0 -5762,26111527,Sistemas de control de movimiento integrados,,0,80509,0 -5763,26111528,Transmisiones hidrostáticas,,0,80509,0 -5764,26111529,Transmisiones hidrocinéticas,,0,80509,0 -5765,26111530,Leva de transmisión,,0,80509,0 -5766,26111531,Manguitos de la transmisión,,0,80509,0 -5767,26111532,Soportes o conjuntos del eje,,0,80509,0 -5768,26111533,Tensores de cadena,,0,80509,0 -5769,26111534,Cubos de la transmisión,,0,80509,0 -5770,26111535,Tornillos esféricos o conjuntos de tornillos esféricos,,0,80509,0 -5771,26111536,Reductor de velocidad montado sobre eje,,0,80509,0 -5772,26111537,Reductor de velocidad con tornillo,,0,80509,0 -5773,26111538,Reductor de velocidad de hélice,,0,80509,0 -5774,26111539,Reductor de velocidad de hélice y tornillo,,0,80509,0 -5775,26111540,Reductor de velocidad planetario,,0,80509,0 -5776,26111541,Reductor de velocidad cicloidal,,0,80509,0 -5777,26111542,Reductor aumentador de velocidad de bisel,,0,80509,0 -5778,26111543,Reductor de velocidad de tracción,,0,80509,0 -5779,26111544,Controlador de velocidad,,0,80509,0 -5780,26111545,Adaptador de freno de tambor,,0,80509,0 -5781,26111546,Disco de turbina,,0,80509,0 -5782,26111547,Cubo de apriete,,0,80509,0 -5783,26111548,Palanca de cambios,,0,80509,0 -5784,26111549,Escalón del eje,,0,80509,0 -5785,26111600,Generadores de potencia,,0,80509,0 -5786,26111601,Generadores diesel,,0,80509,0 -5787,26111602,Generadores hidroeléctricos,,0,80509,0 -5788,26111603,Generadores eólicos,,0,80509,0 -5789,26111604,Generadores a gas,,0,80509,0 -5790,26111605,Generadores térmicos,,0,80509,0 -5791,26111606,Generadores hidráulicos,,0,80509,0 -5792,26111607,Generadores solares,,0,80509,0 -5793,26111608,Generadores de vapor,,0,80509,0 -2327,12352001,Alcanos,,0,80509,0 -2328,12352002,Alquenos,,0,80509,0 -2329,12352003,Alquinos,,0,80509,0 -2330,12352005,Compuestos aromáticos o heterocíclicos,,"0,1",80509,0 -2331,12352100,Derivados orgánicos y compuestos sustituidos,,"0,1",80509,0 -2332,12352101,Compuestos halogenados orgánicos,,"0,1",80509,0 -2333,12352102,Compuestos orgánicos nitro o nitroso,,"0,1",80509,0 -2334,12352103,Compuestos órgano – metálicos,,"0,1",80509,0 -2335,12352104,Alcoholes o sus sustitutos,,1,80509,0 -2336,12352105,Alcoholes tio (mercaptanos),,1,80509,0 -2337,12352106,Ácidos orgánicos o sus sustitutos,,1,80509,0 -2338,12352107,Sales orgánicas o sus sustitutos,,"0,1",80509,0 -2339,12352108,Esteres o sus sustitutos,,0,80509,0 -2340,12352111,Amidas o imidas,,"0,1",80509,0 -2341,12352112,Esteres o sus sustitutos,,0,80509,0 -2342,12352113,Tioéteres,,0,80509,0 -2343,12352114,Aldehídos o sus sustitutos,,1,80509,0 -2344,12352115,Cetonas o quinonas o sus sustitutos,,1,80509,0 -2345,12352116,Amines o imines o sus sustitutos,,0,80509,0 -2346,12352117,Cianuros o isocianuros,,1,80509,0 -2347,12352118,Cianatos o isocianatos o tiocianatos o isotiocianatos,,1,80509,0 -2348,12352119,Óxidos orgánicos,,"0,1",80509,0 -2349,12352120,Peróxidos orgánicos,,1,80509,0 -2350,12352121,Hidróxidos orgánicos,,0,80509,0 -2351,12352123,Ureidos o purinas o sus derivados,,0,80509,0 -2352,12352124,Azo compuestos o sus sustitutos,,0,80509,0 -2353,12352126,Oximas,,0,80509,0 -2354,12352127,Hidracinas o hidracidas o sus sustitutos,,0,80509,0 -2355,12352128,Fosfinas,,0,80509,0 -2356,12352129,Amidinas o imidinas,,0,80509,0 -2357,12352130,Intermedios de acrilato o metacrilato,,0,80509,0 -2358,12352131,Pirrolidona,,0,80509,0 -2359,12352132,Triclosán,,0,80509,0 -2360,12352133,Condicionador de congelación,,0,80509,0 -2361,12352135,Glicol propileno,,0,80509,0 -2362,12352136,Glicol trietileno,,0,80509,0 -2363,12352137,Glicol etileno,,0,80509,0 -2364,12352200,Productos bioquímicos,,0,80509,0 -2365,12352201,Carbohidratos o sus derivados,,0,80509,0 -2366,12352202,Proteínas,,0,80509,0 -2367,12352203,Anticuerpos,,0,80509,0 -2368,12352204,Enzimas,,0,80509,0 -2369,12352205,Nutrientes,,0,80509,0 -2370,12352206,Tejidos,,"0,1",80509,0 -2371,12352207,Cultivos y fluidos,,0,80509,0 -2372,12352208,Ácido nucleico,,0,80509,0 -2373,12352209,Aminoácidos o sus derivados,,0,80509,0 -2374,12352210,Alcaloides,,1,80509,0 -2375,12352211,Grasas o lípidos,,0,80509,0 -2376,12352212,Terpenoides,,0,80509,0 -2377,12352300,Compuestos inorgánicos,,"0,1",80509,0 -2378,12352301,Ácidos inorgánicos,,"0,1",80509,0 -2379,12352302,Sales metálicas inorgánicas,,"0,1",80509,0 -2380,12352303,Óxidos inorgánicos,,"0,1",80509,0 -2381,12352304,Peróxidos inorgánicos,,1,80509,0 -2382,12352305,Hidróxidos inorgánicos,,"0,1",80509,0 -2383,12352306,Hídridos inorgánicos,,"0,1",80509,0 -2384,12352307,Halidos ácidos o sus sustitutos,,0,80509,0 -2385,12352308,Silicatos,,0,80509,0 -2386,12352309,Sílice,,0,80509,0 -2387,12352310,Siliconas,,0,80509,0 -2388,12352311,Alúmina y otros compuestos de aluminio,,"0,1",80509,0 -2389,12352312,Permanganato de potasio,,"0,1",80509,0 -2390,12352313,Ácido trifluoroacético tfa,,1,80509,0 -2391,12352314,Sulfato de amonio,,"0,1",80509,0 -2392,12352315,Caucho de silicona líquida,,"0,1",80509,0 -2393,12352316,Hidróxido de sodio,,1,80509,0 -2394,12352317,Fluoruro de aluminio,,"0,1",80509,0 -2395,12352318,Fluoruro de calcio,,"0,1",80509,0 -2396,12352319,Hidróxido de calcio,,"0,1",80509,0 -2397,12352320,Hidróxido de potasio,,1,80509,0 -2398,12352321,Glicol acetileno,,"0,1",80509,0 -2399,12352400,Mezclas,,"0,1",80509,0 -2400,12352401,Mezclas químicas orgánicas,,1,80509,0 -2401,12352402,Mezclas químicas inorgánicas,,1,80509,0 -2402,12352500,Fijadores,,0,80509,0 -2403,12352501,Formaldehidos,,0,80509,0 -2404,12352502,Glutarales,,0,80509,0 -2405,12352503,Taninos,,0,80509,0 -2406,12352600,Compuestos fluorados,,0,80509,0 -2407,13101500,Caucho natural,,0,80509,0 -2408,13101501,Caucho látex,Hule,0,80509,0 -2409,13101502,Caucho crepe,,0,80509,0 -2410,13101503,Caucho hoja ahumada,,0,80509,0 -2411,13101504,Caucho espuma natural,,0,80509,0 -2412,13101505,Caucho bloque o borona,Goma de borrar de caucho,0,80509,0 -2413,13101600,Caucho procesado y sintético,,0,80509,0 -2414,13101601,Caucho vulcanizado,,0,80509,0 -2415,13101602,Caucho clorado,,0,80509,0 -2416,13101603,Caucho clorhidrato,,0,80509,0 -2417,13101604,Caucho ciclizado,,0,80509,0 -2418,13101605,Caucho isomerizado,,0,80509,0 -2419,13101606,Caucho termoplástico,,0,80509,0 -2420,13101607,Compuesto de caucho,,0,80509,0 -2421,13101608,Caucho reclamado,Caucho reciclado o reutilizado,0,80509,0 -2422,13101700,Elastómeros,,0,80509,0 -2423,13101701,Butadieno acilonitrilo nbr,,0,80509,0 -2424,13101702,Nitrilo altamente saturado nhbr,,0,80509,0 -2425,13101703,Fluorocarbono fcm,,0,80509,0 -2426,13101704,Propileno etileno epe,,0,80509,0 -2427,13101705,Butadieno estireno,,0,80509,0 -2428,13101706,Clorpreno cr,,0,80509,0 -2429,13101707,Isopreno isobutileno iir / xiir,,0,80509,0 -2430,13101708,Silicona vmq y pmq y pvmq,,0,80509,0 -2431,13101709,Fluorosilicona fvmq,,0,80509,0 -2432,13101710,Poliacrilato acm,,0,80509,0 -2433,13101711,Etileno acrílico aem,,0,80509,0 -2434,13101712,Polietileno clorosulfonatado csm,,0,80509,0 -2435,13101713,Cloropolietileno cm,,0,80509,0 -2436,13101714,Epicloridrina,,0,80509,0 -2437,13101715,Polisopreno natural nr,,0,80509,0 -2438,13101716,Polisopreno sintético ir,,0,80509,0 -2439,13101717,Poliéster uretano au,,0,80509,0 -2440,13101718,Poliéster uretano eu,,0,80509,0 -2441,13101719,Polibutadieno br,,0,80509,0 -2442,13101720,Poli éter bloque amida peba,Bloque de poliéster de amida o PEBA,0,80509,0 -2443,13101721,Estireno bloque copolímero tes,,0,80509,0 -2444,13101722,Copoliester,,0,80509,0 -2445,13101723,Termoplástico,,0,80509,0 -2446,13101724,Poliolefínico,,0,80509,0 -2447,13101725,Etileno propileno dieno epdm,Dieno de etileno propileno o EPDM,0,80509,0 -2448,13101900,Plásticos termoformados,,0,80509,0 -2449,13101902,Fenólico pf,,0,80509,0 -2450,13101903,Poliéster no saturado up,,0,80509,0 -2451,13101904,Urea uf,,0,80509,0 -2452,13101905,Melamina mf,,0,80509,0 -2454,13102000,Plásticos termoplásticos,,"0,1",80509,0 -2455,13102001,Acrilonitrilo butadieno estireno abs,,"0,1",80509,0 -2456,13102002,Aleaciones de acrilonitrilo butadieno estireno abs,,"0,1",80509,0 -2457,13102003,Polímero acetal,,"0,1",80509,0 -2458,13102005,Acrilonitrilo estireno acrílico asa,,"0,1",80509,0 -2459,13102006,Aleaciones de acrilonitrilo estireno acrílico asa,,"0,1",80509,0 -2460,13102008,Fluoropolímeros ptfe,,0,80509,0 -2461,13102010,Polímero cristal líquido lcp,,"0,1",80509,0 -2462,13102011,Nylons poliamida pa,,0,80509,0 -2463,13102012,(no hay sugerencias) pbt,Tereftalato de polibutileno PBT,0,80509,0 -2464,13102013,Policarbonato pc,,0,80509,0 -2465,13102014,Polietereterquetona peek,Polieteretercetona PEEK,0,80509,0 -2466,13102016,Polietersulfona pes,,0,80509,0 -2467,13102017,Polietileno de baja densidad hdpe,,0,80509,0 -2468,13102018,Polietileno de alta densidad ldpe,,0,80509,0 -2469,13102019,Polietileno de media densidad mdpe,,0,80509,0 -2470,13102020,Polietileno tereftalato pet,,0,80509,0 -2471,13102022,Polipropileno pp,,0,80509,0 -2472,13102024,Óxido de polifenileno ppo,,0,80509,0 -2473,13102025,Sulfuro de polifenileno pps,,0,80509,0 -2474,13102026,Poliestireno (icopor) ps,,0,80509,0 -2475,13102027,Poliestireno de alto impacto hips,"Espuma de poliestireno EPS, Poliestireno expandido EPS, Unice",0,80509,0 -2476,13102028,Polisulfona psu,,0,80509,0 -2477,13102029,Poliuretano termoplástico rígido rptu,,0,80509,0 -2478,13102030,Cloruro de polivinilo pvc,,0,80509,0 -2479,13102031,Éter de polifenilene ppe,,"0,1",80509,0 -2480,13102032,Poliolefina termoplástica tpo,,0,80509,0 -2481,13111000,Resinas,,0,80509,0 -2482,13111001,Epoxi,Resina de epoxi o poliepóxido,"0,1",80509,0 -2483,13111002,Resina fenólica,,"0,1",80509,0 -2484,13111003,Resina polester no saturada,,"0,1",80509,0 -2485,13111004,Resina estireno butadieno acrilonitrilo,,"0,1",80509,0 -2486,13111005,Resina acrílica estireno acrilonitrilo,,"0,1",80509,0 -2487,13111006,Resina aleación acrílica estireno acrilonitrilo,,"0,1",80509,0 -2488,13111007,Resina fliuoropolímero,,"0,1",80509,0 -2489,13111008,Resina acetato vinilo etileno,,"0,1",80509,0 -2490,13111009,Resina polímero cristal líquido,,"0,1",80509,0 -2491,13111010,Nylon,,0,80509,0 -2492,13111011,Tetralftalato polibutileno,,0,80509,0 -2493,13111012,Resina de policarbonato,,"0,1",80509,0 -2494,13111013,Resina polietereterquetona,,"0,1",80509,0 -2495,13111014,Resina polieterimida,,"0,1",80509,0 -2496,13111015,Resina polietersulfona,,"0,1",80509,0 -2497,13111016,Polietileno,,0,80509,0 -2498,13111017,Resina terftalato polietileno,,"0,1",80509,0 -2499,13111018,Resina polimida,,"0,1",80509,0 -2500,13111019,Resina polipropileno,,"0,1",80509,0 -2501,13111020,Resina poliftalamida,,"0,1",80509,0 -2502,13111021,Óxido de polietileno,,0,80509,0 -2503,13111022,Resina sulfuro de polifenilene,,"0,1",80509,0 -2504,13111023,Resina de poliestireno,,"0,1",80509,0 -2505,13111024,Resina de polisulfona,,"0,1",80509,0 -2506,13111025,Resina de cloruro de polivinilo,,"0,1",80509,0 -2507,13111026,Resina de acrilonitrilo de estireno,,"0,1",80509,0 -2508,13111027,Formaldehído de urea,,"0,1",80509,0 -2509,13111028,Alquido,,0,80509,0 -2510,13111029,Formaldehído de melamina,,"0,1",80509,0 -2511,13111030,Poliacetal,,0,80509,0 -2512,13111031,Poliamida,,0,80509,0 -2513,13111032,Grupo alilo,,0,80509,0 -2514,13111033,Ácido acrílico de etileno,,"0,1",80509,0 -2515,13111034,Compuesto cloruro de polivinilo,,0,80509,0 -2516,13111035,Solución de vinilo,,"0,1",80509,0 -2517,13111036,Fenoxi,,"0,1",80509,0 -2518,13111037,Resina compuesta,,"0,1",80509,0 -2519,13111038,Pirolidina polivinilo,,0,80509,0 -2520,13111039,Tereftalato de polietileno o glicol modificado,,0,80509,0 -2521,13111040,Fijador de hidrocarburo,,"0,1",80509,0 -2522,13111041,Mezclas de policarbonato,,0,80509,0 -2523,13111042,Alcohol polivinilo,,0,80509,0 -2524,13111043,Butiral polivinilo,,0,80509,0 -2525,13111044,Compuesto de moldeo de poliéster,,0,80509,0 -2526,13111045,Acetato de polivinilo,,0,80509,0 -2527,13111046,Éter de polivinilo,,0,80509,0 -2528,13111047,Polivinilo formal,,0,80509,0 -2529,13111048,Acrílico de estireno,,"0,1",80509,0 -2530,13111049,Polímeros de propileno de etileno,,"0,1",80509,0 -2531,13111050,Óxido de polipropileno,,"0,1",80509,0 -2532,13111051,Éter de polipropileno,,"0,1",80509,0 -2533,13111052,Sulfona de polipropileno,,"0,1",80509,0 -2534,13111053,Polimetilacrilato,,0,80509,0 -2535,13111054,Anhídrido maléico de estireno,,"0,1",80509,0 -2536,13111055,Poliestireno sindiotáctico,,0,80509,0 -2537,13111056,Cloruro de polivinilo clorado,,0,80509,0 -2538,13111057,Poliéster termoplástico,,0,80509,0 -2539,13111058,Resinas de indeno,,"0,1",80509,0 -2540,13111059,Resinas plásticas,,"0,1",80509,0 -2541,13111060,Resinas de petróleo,,"0,1",80509,0 -2542,13111061,Resinas de poliuretano,,"0,1",80509,0 -2543,13111062,Resinas de polieter,,"0,1",80509,0 -2544,13111063,Resinas recicladas,,"0,1",80509,0 -2545,13111064,Resinas acrílicas,,"0,1",80509,0 -2546,13111065,Resinas celulosas,,"0,1",80509,0 -2547,13111066,Resinas de polipropileno,,"0,1",80509,0 -2548,13111067,Alcohol etileno vinilo,,"0,1",80509,0 -2549,13111068,Polietileno lineal de baja densidad,,0,80509,0 -2550,13111069,Resina de poliacrilonitrilo,,"0,1",80509,0 -2551,13111070,Resina de poliamida – imida,,"0,1",80509,0 -2552,13111071,Resina de poliarileterquetona,Resina de poliariletercetona,"0,1",80509,0 -2553,13111072,Resina de polibenzimidasola,,"0,1",80509,0 -2554,13111073,Resina de polimetilpenteno,,"0,1",80509,0 -2555,13111074,Fluoruro de polivinildeno,,"0,1",80509,0 -2556,13111075,Aleación estireno uradieno acrilonitrilo policarbonato pc abs,,0,80509,0 -2557,13111076,Polietileno entrecruzado pex,,0,80509,0 -2558,13111077,Poliamida 6 – 12,,0,80509,0 -2559,13111078,Poliamida 6 – 6,,0,80509,0 -2560,13111079,Poliamida 4 – 6,,0,80509,0 -2561,13111080,Nylon poliamida de alta temperatura htn,,0,80509,0 -2562,13111081,Poliamida 12,,0,80509,0 -2563,13111082,Poliamida 6,,0,80509,0 -2564,13111100,Colofonia,,"0,1",80509,0 -2565,13111101,Rosina de madera,"Colofonia de madera, Resina de madera",0,80509,0 -2566,13111102,Rosina de goma,Resina de goma,0,80509,0 -2567,13111103,Rosina aceite de pino,Resina de aceite de pino,0,80509,0 -2568,13111200,Películas,,0,80509,0 -2569,13111201,Películas de polietileno,Resina de aceite alto,0,80509,0 -2571,13111203,Películas de acetato,,0,80509,0 -2572,13111204,Películas de acrílico,,0,80509,0 -2573,13111205,Películas coextruídas,,0,80509,0 -2574,13111206,Películas de fluoropolímero,,0,80509,0 -2575,13111207,Películas metalizadas,,0,80509,0 -2576,13111208,Películas de nylon,,0,80509,0 -2577,13111209,Películas de policarbonato,,0,80509,0 -2578,13111210,Películas de poliéster,,0,80509,0 -2579,13111211,Películas de polipropileno,,0,80509,0 -2580,13111212,Polipropileno orientado axialmente,,0,80509,0 -2581,13111213,Películas de poliamida,,0,80509,0 -2582,13111214,Películas de poliestireno,,0,80509,0 -2583,13111215,Películas de cloruro de polivinilo flexible,,0,80509,0 -2584,13111216,Películas de cloruro de polivinilo rígido,,0,80509,0 -2585,13111217,Películas de alcohol vinilo etileno,,0,80509,0 -2586,13111218,Películas de cloruro de polivinilo,,0,80509,0 -2587,13111219,Películas de alcohol polivinilo,,0,80509,0 -2588,13111220,Películas de recubiertas de silicona,,0,80509,0 -2589,13111300,Espumas,,0,80509,0 -2590,13111301,Espumas de poliolefina,,0,80509,0 -2591,13111302,Espumas de poli éter,,0,80509,0 -2592,13111303,Espumas de silicona,,0,80509,0 -2593,13111304,Espumas de terpolímero propileno etileno,,0,80509,0 -2594,13111305,Espumas de neopreno,,0,80509,0 -2595,13111306,Espumas de cloruro de polivinilo,,0,80509,0 -2596,13111307,Espumas de caucho,,0,80509,0 -2597,13111308,Espumas de poliestireno,,0,80509,0 -2598,14101500,Materias primas,,0,80509,0 -2599,14101501,Pulpa de papel,,0,80509,0 -2600,14111500,Papel de imprenta y papel de escribir,,0,80509,0 -2601,14111501,Papel cebolla,,0,80509,0 -2602,14111502,Papel vitela,,0,80509,0 -2603,14111503,Papel pergamino,,0,80509,0 -2604,14111504,Papel en formas continuas,,0,80509,0 -2605,14111505,Papel mimeógrafo,,0,80509,0 -2606,14111506,Papel para impresión de computadores,,0,80509,0 -2607,14111507,Papel para impresora o fotocopiadora,,0,80509,0 -2608,14111508,Papel para fax,,0,80509,0 -2609,14111509,Papel membreteado,,0,80509,0 -2610,14111510,Papel para plotter,,0,80509,0 -2611,14111511,Papel de escritura,,0,80509,0 -2612,14111512,Papel para gráficos,,0,80509,0 -2613,14111513,Papel de libro,,0,80509,0 -2614,14111514,Blocs o cuadernos de papel,,0,80509,0 -2615,14111515,Papel para sumadora o máquina registradora,,0,80509,0 -2616,14111516,Repuestos de papel para cuaderno,,0,80509,0 -2617,14111518,Tarjetas de índice,,0,80509,0 -2618,14111519,Papeles cartulina,,0,80509,0 -2619,14111520,Papel secante,Calca,0,80509,0 -2620,14111523,Papel calcante,,0,80509,0 -2621,14111524,Papel folio,,0,80509,0 -2622,14111525,Papel multipropósito,,0,80509,0 -2623,14111526,Papel libretas o libros de mensajes telefónicos,Cuadernos de mensajes telefónicos,0,80509,0 -2624,14111527,Papel autocopiante,,0,80509,0 -2625,14111528,Papel magnético,,0,80509,0 -2626,14111529,Rollos de télex,,0,80509,0 -2627,14111530,Papel de notas autoadhesivas,,0,80509,0 -2628,14111531,Papel libros o cuadernos para bitácoras,,0,80509,0 -2629,14111532,Papel kits de papeles surtidos,,0,80509,0 -2630,14111533,Papel cuadernillos o formularios de exámenes,,0,80509,0 -2631,14111534,Papeles para notación musical o manuscritos,Cuadernos pautados o pentagramados,0,80509,0 -2632,14111535,Papeles para telégrafo,,0,80509,0 -2633,14111536,Tarjetas de préstamo de bibliotecas,,0,80509,0 -2634,14111537,Etiquetas de papel,,0,80509,0 -2635,14111538,Papel digital,,0,80509,0 -2636,14111539,Papel de monitoreo o calcado o registro médico,,0,80509,0 -2637,14111540,Papel de estampilla,,0,80509,0 -2638,14111541,Papel lector de marcas ópticas,Papel para lector de marcas ópticas,0,80509,0 -2639,14111542,Papel coreano para papelería,,0,80509,0 -2640,14111543,Piedra de tinta,,0,80509,0 -2641,14111600,Papel fantasía,,0,80509,0 -2642,14111601,Papel o bolsas o cajas de regalo,,0,80509,0 -2643,14111604,Tarjetas de presentación,,0,80509,0 -2644,14111605,"Tarjetas postales, de saludo o de notas",,0,80509,0 -2645,14111606,Papel para artes o artesanías,,0,80509,0 -2646,14111607,Tableros para afiches,Cartulinas,0,80509,0 -2647,14111608,Certificados de regalo,,0,80509,0 -2648,14111609,Papel de cubierta,,0,80509,0 -2649,14111610,Papel de construcción,,0,80509,0 -2650,14111611,Tarjetas de invitación o de anuncio,,0,80509,0 -2651,14111613,Papel de pancartas,,0,80509,0 -2652,14111614,Papel o tejidos de álbum,,0,80509,0 -2653,14111615,Papeles de afiche,Papel para cartel o póster,0,80509,0 -2654,14111616,Papeles para forrar,,0,80509,0 -2655,14111617,Papel leathack (verjurado),Papel estampado,0,80509,0 -2656,14111618,Papel kent,Papel de dibujo,0,80509,0 -2657,14111700,Productos de papel para uso personal,,0,80509,0 -2658,14111701,Pañuelos faciales,,0,80509,0 -2659,14111702,Cubiertos de asientos de sanitario,,0,80509,0 -2660,14111703,Toallas de papel,,0,80509,0 -2661,14111704,Papel higiénico,,0,80509,0 -2662,14111705,Servilletas de papel,,0,80509,0 -2663,14111706,Manteles de papel,,0,80509,0 -2664,14111800,Papeles de uso comercial,,0,80509,0 -2665,14111801,Boletas o rollos de boletería,,0,80509,0 -2666,14111802,Recibos o libros de recibos,,0,80509,0 -2667,14111803,Comprobantes,,0,80509,0 -2668,14111804,Facturas o libros de facturas,,0,80509,0 -2669,14111805,Cheques o chequeras,,0,80509,0 -2670,14111806,Formularios o cuestionarios de negocios,,0,80509,0 -2671,14111807,Libros comerciales para múltiples usos,,0,80509,0 -2672,14111808,Formatos contables o libros de contabilidad,,0,80509,0 -2673,14111809,Formas o libros de conocimientos de embarque,,0,80509,0 -2674,14111810,Formatos o libros de personal,,0,80509,0 -2675,14111811,Formatos o libros de ventas,,0,80509,0 -2676,14111812,Formatos o libros de inventarios,,0,80509,0 -2677,14111813,Formatos o libros de correspondencia,,0,80509,0 -2678,14111814,Formatos o libros de impuestos,,0,80509,0 -2679,14111815,Tarjetas de identificación,,0,80509,0 -2680,14111816,Tarjetas de huellas digitales de solicitante,,0,80509,0 -2681,14111817,Formato de verificación de depósito,,0,80509,0 -2682,14111818,Papel térmico,,0,80509,0 -2683,14111819,Formularios p libros de reservas,,0,80509,0 -2684,14111820,Formas o cupones de juegos de azar,,0,80509,0 -2685,14111821,Formatos o libros de órdenes,,0,80509,0 -2686,14111822,Formatos o libros de entregas,,0,80509,0 -2688,14111824,Libreta de prescripciones farmacéuticas,,0,80509,0 -2689,14111825,Menú,,0,80509,0 -2690,14111826,Certificados de nacimiento,,0,80509,0 -2691,14111827,Certificados de defunción,,0,80509,0 -2692,14111828,Papelería comercial membreteada,,0,80509,0 -2693,14121500,Cartón y papel para embalaje,,0,80509,0 -2694,14121501,Cartón blanqueado,,0,80509,0 -2695,14121502,Cartón no blanqueado,,0,80509,0 -2696,14121503,Cartón,,0,80509,0 -2697,14121504,Papel de empaque,,0,80509,0 -2698,14121505,Material de fibra,,0,80509,0 -2699,14121506,Fibra corrugada,,0,80509,0 -2700,14121507,Papel inhibidor de la corrosión volátil o vci,,0,80509,0 -2701,14121600,Papel de seda,,0,80509,0 -2702,14121601,Papel crepé sin blanquear,,0,80509,0 -2703,14121602,Papel crepé semi blanqueado,,0,80509,0 -2704,14121603,Pañuelos de papel resistentes a la humedad,,0,80509,0 -2705,14121604,Pañuelos de papel libres de ácido,,0,80509,0 -2706,14121605,Pañuelos de papel kraft,,0,80509,0 -2707,14121700,Papeles laminados,,0,80509,0 -2708,14121701,Papeles adheridos con película,,0,80509,0 -2709,14121702,Papeles cilindro o papel pesado multi – capas,,0,80509,0 -2710,14121703,Hojas de papel aluminio laminado,,0,80509,0 -2711,14121800,Papeles recubiertos,,0,80509,0 -2712,14121801,Papeles recubiertos de arcilla,,0,80509,0 -2713,14121802,Papeles recubiertos de polietileno,,0,80509,0 -2714,14121803,Papeles recubiertos de poliéster,,0,80509,0 -2715,14121804,Papeles recubiertos de silicona,,0,80509,0 -2716,14121805,Papeles recubiertos tratados con látex,,0,80509,0 -2717,14121806,Papel de parafinado,,0,80509,0 -2718,14121807,Papel mantequilla,,0,80509,0 -2719,14121808,Papel para congelador,,0,80509,0 -2720,14121809,Papel de enmascarar,,0,80509,0 -2721,14121810,Papeles carbón,,0,80509,0 -2722,14121811,Papeles de copiado sensibilizados,,0,80509,0 -2723,14121812,Papel de fotografía,,0,80509,0 -2724,14121900,Papel prensa y de offset,,0,80509,0 -2725,14121901,Papel periódico estándar,,0,80509,0 -2726,14121902,Papel periódico de colores,,0,80509,0 -2727,14121903,Papel periódico de alto brillo,,0,80509,0 -2728,14121904,Papel offset,,0,80509,0 -2729,14121905,Papeles tímpano,Papeles Tympan,0,80509,0 -2730,14122100,Papeles de base sin recubrir,,0,80509,0 -2731,14122101,Papel kraft súper calandrado,,0,80509,0 -2732,14122102,Papel kraft terminado o satinado en máquina,,0,80509,0 -2733,14122103,Papel no recubierto no tratado,,0,80509,0 -2734,14122104,Papel crepé no tratado,,0,80509,0 -2735,14122105,Papel crepé tratado con látex,,0,80509,0 -2736,14122106,Papel no recubierto tratado con látex,,0,80509,0 -2737,14122107,Papel base corrugado,,0,80509,0 -2738,14122200,Papeles especializados de uso industrial,,"0,1",80509,0 -2739,14122201,Papel para germinación de semillas,,"0,1",80509,0 -2740,14122202,Papel para bolsas de te,,"0,1",80509,0 -2741,15101500,Petróleo y Destilados,,1,80509,0 -2742,15101502,Kerosene,,1,80509,0 -2743,15101503,Nafta,,1,80509,0 -2744,15101504,Combustible de aviación,,1,80509,0 -2745,15101505,Combustible diesel,,1,80509,0 -2746,15101506,Gasolina,,1,80509,0 -2747,15101507,Benceno,hidrocarburo aromático,1,80509,0 -2748,15101508,Petróleo crudo,,1,80509,0 -2749,15101509,Combustible marítimo,,1,80509,0 -2750,15101510,Condensado,,1,80509,0 -2751,15101511,Etanol,,1,80509,0 -2752,15101512,Coque de petróleo o petcoke,,1,80509,0 -2753,15101513,Combustible diesel para uso fuera de carretera,,1,80509,0 -2754,15101514,Gasolina regular menor a 91 octanos,,1,80509,0 -2755,15101515,Gasolina premium mayor o igual a 91 octanos,,1,80509,0 -2756,15101600,Combustibles sólidos o gelificados,,1,80509,0 -2757,15101601,Carbón sub – bituminosos o débil,,1,80509,0 -2758,15101602,Lignito,Carbón mineral,1,80509,0 -2759,15101603,Turba,Carbón ligero,1,80509,0 -2760,15101604,Coque,,1,80509,0 -2761,15101605,Carbón de leña o vegetal,,1,80509,0 -2762,15101606,Combustibles de alcohol gelatinoso,,1,80509,0 -2763,15101607,Hexaminas,,1,80509,0 -2764,15101608,Trioxanos,,1,80509,0 -2765,15101609,Briqueta,Bloque sólido combustible,1,80509,0 -2766,15101610,Carbón de cascara de coco,,1,80509,0 -2767,15101611,Antracita o carbón duro,,1,80509,0 -2768,15101612,Carbón metalúrgico,,1,80509,0 -2769,15101613,Carbón sin procesar,,1,80509,0 -2770,15101614,Carbón energético,,1,80509,0 -2771,15101700,Aceites combustibles,,0,80509,0 -2772,15101701,Fuel oil de calefacción # 2,Combustible de calefacción no. 2,0,80509,0 -2773,15101702,Fuel oils pesados residuales # 4 ó # 6,"Aceites combustibles residuales pesados no. 4 o no. 6, Combustóleo no. 4 o no. 6, Petróleos residuales pesados no. 4 o no.",0,80509,0 -2774,15101800,Combustibles o biocombustibles líquidos basados en plantas,,0,80509,0 -2775,15101801,Biodiesel,,0,80509,0 -2776,15111500,Combustibles gaseosos,,1,80509,0 -2777,15111501,Propano,,1,80509,0 -2778,15111502,Metano,,1,80509,0 -2779,15111503,Propileno,,1,80509,0 -2780,15111504,Etileno,,1,80509,0 -2781,15111505,Butano,,1,80509,0 -2782,15111506,Acetileno,,1,80509,0 -2783,15111507,Gas de agua o gas productor,,1,80509,0 -2784,15111508,Gas de carbón,,1,80509,0 -2785,15111509,Gas metilacetileno propadieno mapp,,1,80509,0 -2786,15111510,Gas licuado de petróleo,Gas LP,1,80509,0 -2787,15111511,Gas natural licuado gnl,,1,80509,0 -2788,15111512,Gas natural,,1,80509,0 -2789,15111700,Aditivos para carburante,,0,80509,0 -2790,15111701,Espesantes de combustible,,0,80509,0 -2791,15111702,Inhibidores de hielo para sistemas de combustibles,,0,80509,0 -2792,15121500,Preparados lubricantes,,0,80509,0 -2793,15121501,Aceite motor,,0,80509,0 -2794,15121502,Aceite de corte,,0,80509,0 -2795,15121503,Aceite de engranajes,,0,80509,0 -2796,15121504,Aceite hidráulico,,0,80509,0 -2797,15121505,Aceite de transformador o aislador,,0,80509,0 -2798,15121508,Aceite de transmisión,,0,80509,0 -2799,15121509,Aceite de frenos,,0,80509,0 -2800,15121510,Anti – excoriación,,0,80509,0 -2801,15121511,Pastas de ensamble,,0,80509,0 -2802,15121512,Anti adhesivos,,0,80509,0 -2803,15121513,Lubricantes de grafito,,0,80509,0 -2804,15121514,Lubricantes espray,,0,80509,0 -2805,15121515,Compuestos anti – adherentes o anti – manchas,,0,80509,0 -2806,15121516,Eliminador de fugas,,0,80509,0 -2808,15121518,Fluidos de amortiguación,,0,80509,0 -2809,15121519,Aceites de lubricación de relojes,,0,80509,0 -2810,15121520,Lubricantes de propósito general,,0,80509,0 -2811,15121521,Aceites para lubricación de bombas,,0,80509,0 -2812,15121522,Aceites para lubricación de armas,,0,80509,0 -2813,15121523,Fluidos para preparación de lentes,,0,80509,0 -2814,15121524,Aceites de templado,,0,80509,0 -2815,15121525,Aceite atemperante,Aceites de enfriamiento,0,80509,0 -2816,15121526,Lubricantes para equipo de procesamiento de alimentos,,0,80509,0 -2817,15121527,Aceite de turbina,,0,80509,0 -2818,15121528,Fluido hidráulico resistente al fuego,,0,80509,0 -2819,15121529,Aceite para máquinas de refrigeración,,0,80509,0 -2820,15121530,Aceite o fluido de transferencia de calor,,0,80509,0 -2821,15121800,Anticorrosivos,,0,80509,0 -2822,15121801,Repelente de humedad,,0,80509,0 -2823,15121802,Lubricante anti – corrosión,,0,80509,0 -2824,15121803,Removedor de óxido,,0,80509,0 -2825,15121804,Preparación contra óxido,,0,80509,0 -2826,15121805,Pastas anti – soldado,,0,80509,0 -2827,15121806,Aceites penetrantes,,0,80509,0 -2828,15121807,Anticongelante,,0,80509,0 -2829,15121900,Grasas,,0,80509,0 -2830,15121901,Grasa de silicona,,0,80509,0 -2831,15121902,Grasa,,0,80509,0 -2832,15121903,Grasa fluoropolímero,,0,80509,0 -2833,15121904,Grasa de lana,,0,80509,0 -2834,15121905,Grasa térmica,,0,80509,0 -2835,15131500,Combustible nuclear,,1,80509,0 -2836,15131502,Uranio empobrecido,,1,80509,0 -2837,15131503,Uranio enriquecido,,1,80509,0 -2838,15131504,Iridio,,1,80509,0 -2839,15131505,Plutonio enriquecido,,1,80509,0 -2840,15131506,Plutonio empobrecido,,1,80509,0 -2841,20101500,Equipo de corte,,0,80509,0 -2842,20101501,Equipo de minería continua,,0,80509,0 -2843,20101502,Cizallas de pared alta,,0,80509,0 -2844,20101503,Cortadores de carbón,,0,80509,0 -2845,20101504,Cortadores de roca,,0,80509,0 -2846,20101505,Cadena de corte para minería,,0,80509,0 -2847,20101506,Barra de corte,,0,80509,0 -2848,20101600,Cribas y equipos de alimentación,,0,80509,0 -2849,20101601,Pantallas,,0,80509,0 -2850,20101602,Alimentadores,,0,80509,0 -2851,20101603,Pantalla de hoyo de desagüe,,0,80509,0 -2852,20101617,Motorreductor,,0,80509,0 -2853,20101618,Canaleta alimentadora,,0,80509,0 -2854,20101619,Banda alimentadora para pesar,,0,80509,0 -2855,20101620,Vibro – alimentador electromagnético,,0,80509,0 -2856,20101621,Vibro – alimentador electromecánico,,0,80509,0 -2857,20101700,"Trituradoras, quebrantadoras y amoladores",,0,80509,0 -2858,20101701,Trituradores de roca,,0,80509,0 -2859,20101702,Trituradores de rollo,,0,80509,0 -2860,20101703,Trituradores de cono,,0,80509,0 -2861,20101704,Trituradores giratorios,,0,80509,0 -2862,20101705,Trituradores de impacto,,0,80509,0 -2863,20101706,Trituradores de mandíbula,,0,80509,0 -2864,20101707,Plantas de trituración,,0,80509,0 -2865,20101708,Molino de varilla,,0,80509,0 -2866,20101709,Molino de bola,,0,80509,0 -2867,20101710,Maquinaria de pulverización,,0,80509,0 -2868,20101711,Rompedores de roca,,0,80509,0 -2869,20101712,Moledores de roca,,0,80509,0 -2870,20101713,Moledoras de ciclón o vórtex,"Molinos de ciclón o vórtices, Rectificadoras de ciclones o vórtices",0,80509,0 -2871,20101714,Placas de mandíbula,,0,80509,0 -2872,20101715,Broca trituradora,,0,80509,0 -2873,20101716,Martillo triturador,,0,80509,0 -2874,20101800,Sistemas mecanizados de soporte en tierra,,0,80509,0 -2875,20101801,Apernador de cable,,0,80509,0 -2876,20101802,Apernador de tijera,,0,80509,0 -2877,20101803,Apernador de boom,,0,80509,0 -2878,20101804,Equipo para aplicar concreto lanzado o shotcrete,"Equipo de proyección de hormigón proyectado, Equipos de pulverización de hormigón proyectado",0,80509,0 -2879,20101805,Repuestos o accesorios de sistema mecanizado de soporte en tierra,,0,80509,0 -2880,20101810,Conmutadores,,0,80509,0 -2881,20101900,Sistemas secundarios de ruptura de roca,,0,80509,0 -2882,20101901,Sistema blockholer o taladro y cargue,Sistemas de perforación y carga,0,80509,0 -2883,20101902,Sistemas de impacto repetitivo,,0,80509,0 -2884,20101903,Repuestos o accesorios de sistema secundario de ruptura de roca,,0,80509,0 -2885,20102000,Sistemas de exploración y desarrollo,,0,80509,0 -2886,20102001,Taladros de barreno profundo en el hoyo ith o abajo del hoyo dth,,0,80509,0 -2887,20102002,Taladros de barreo profundo de martillo superior,,0,80509,0 -2888,20102003,Jumbos neumáticos para perforación de pozos,Eje neumático hundimiento jumbos,0,80509,0 -2889,20102004,Jumbos hidráulicos para perforación de pozos,,0,80509,0 -2890,20102005,Jumbos neumáticos de desarrollo horizontal,,0,80509,0 -2891,20102006,Jumbos hidráulicos de desarrollo horizontal,,0,80509,0 -2892,20102007,Taladros de núcleo,,0,80509,0 -2893,20102008,Repuestos o accesorios de sistema de exploración o desarrollo,,0,80509,0 -2894,20102100,Barrenas de rocas,,0,80509,0 -2895,20102101,Taladros de roca neumáticos,,0,80509,0 -2896,20102102,Taladros de roca hidráulicos,,0,80509,0 -2897,20102103,Taladros de roca manuales,,0,80509,0 -2898,20102104,Repuestos y accesorios de taladros de roca,,0,80509,0 -2899,20102105,Broca o barrena de acero,,0,80509,0 -2900,20102106,Broca o barrena de vacío,,0,80509,0 -2901,20102300,Vehículos de servicio de minería subterránea,,0,80509,0 -2902,20102301,Transporte de personal,,0,80509,0 -2903,20102302,Vehículos grúa,,0,80509,0 -2904,20102303,Cama bajas para el transporte de material,,0,80509,0 -2905,20102304,Transportadores de carga a granel,,0,80509,0 -2906,20102305,Vehículos utilitarios,,0,80509,0 -2907,20102306,Vehículos de plataforma elevable o elevadores de tijera,,0,80509,0 -2908,20102307,Repuestos o accesorios de vehículo de servicio minero subterráneo,,0,80509,0 -2909,20111500,Equipo de exploración y perforación,,0,80509,0 -2910,20111504,Equipos para perforar pozos de agua,,0,80509,0 -2911,20111505,Equipo para la exploración de uranio,,0,80509,0 -2912,20111600,Maquinaria de perforación y explotación,,0,80509,0 -2913,20111601,Maquinaria de sondeo o de perforación,,0,80509,0 -2914,20111602,Maquinaria de ensamble en fondo de pozo,,0,80509,0 -2915,20111603,Martillos perforadores,,0,80509,0 -2916,20111604,Taladro sobre orugas,,0,80509,0 -2917,20111606,Vibradores neumáticos,,0,80509,0 -2918,20111607,Maquinaria para hacer túneles,,0,80509,0 -2920,20111609,Taladro de platina,,0,80509,0 -2921,20111610,Maquinaria de inspección de alcantarillas,,0,80509,0 -2922,20111611,Taladros giratorios,,0,80509,0 -2923,20111612,Taladros de perforación de pozos,,0,80509,0 -2924,20111613,Taladros de barreno profundo,Taladros para agujeros profundos,0,80509,0 -2925,20111614,Brocas industriales,,0,80509,0 -2926,20111615,Perforadora de avance,,0,80509,0 -2927,20111616,Torre de perforación,,0,80509,0 -2928,20111617,Carros de perforación,,0,80509,0 -2929,20111618,Caña de pecar de fondo de pozo,,0,80509,0 -2930,20111619,Cono de perforación de pozos,,0,80509,0 -2931,20111620,Agarre de martillo,,0,80509,0 -2932,20111621,Oscilador de revestimiento,,0,80509,0 -2933,20111622,Taladro de circulación reversa,Taladro de circulación inversa,0,80509,0 -2934,20111623,Almeja de diafragma de muro,Cubierta de pared de diafragma,0,80509,0 -2935,20111700,Accesorios de perforación y explotación,,0,80509,0 -2936,20111701,Instrumentos audiovisuales para inspección de pozos,,0,80509,0 -2937,20111702,Tapones o anclajes de tubos,,0,80509,0 -2938,20111703,Recubrimientos de perforación,,0,80509,0 -2939,20111704,Pantallas de perforación,,0,80509,0 -2940,20111705,Puntos de pozo,,0,80509,0 -2941,20111706,Cuñas de perforación,,0,80509,0 -2942,20111707,Adaptadores de herramientas de perforación,,0,80509,0 -2943,20111708,Columnas de perforación,,0,80509,0 -2944,20111709,Herramientas o kits de accesorios para perforar pozos,,0,80509,0 -2945,20111710,Kit de reparación de tapones y tuberías de perforación,,0,80509,0 -2946,20111711,Partes y accesorios de tuberías de perforación,,0,80509,0 -2947,20111712,Tubo de lavado,,0,80509,0 -2948,20111713,Mástil de perforación,,0,80509,0 -2949,20111714,Conector de tubería flexible,,0,80509,0 -2950,20111715,Recubrimiento de tajo del sitio del pozo,,0,80509,0 -2951,20121000,Equipo acidificante,,0,80509,0 -2952,20121001,Unidades de mezcla acidificante,,0,80509,0 -2953,20121002,Sensores de densidad acidificante,,0,80509,0 -2954,20121003,Unidades de bombeo acidificante,"Maquina de bombeo acidificante, Unidades de bombeo de acidificación",0,80509,0 -2955,20121004,Unidades acidificantes,"Maquina acidificantes, Unidades de acidificación",0,80509,0 -2956,20121005,Tubería de aire acidificante,,0,80509,0 -2957,20121006,Inyectores de bola acidificantes,,0,80509,0 -2958,20121007,Equipo de ácido líquido a granel,,0,80509,0 -2959,20121008,Cajas de gota acidificante,,0,80509,0 -2960,20121009,Medidores de acidificación,,0,80509,0 -2961,20121010,Cajas de unión de acidificación,"Cajas de conexiones acidificantes, Cajas de empalme de acidificación",0,80509,0 -2962,20121011,Sensores de presión de acidificación,,0,80509,0 -2963,20121012,Tubería de proceso acidificante,,0,80509,0 -2964,20121013,Uniones estrechas de acidificación,,0,80509,0 -2965,20121014,Pivotes de acidificación,,0,80509,0 -2966,20121015,Hierros de tratamiento de acidificación,,0,80509,0 -2967,20121016,Protectores de árboles de acidificación,,0,80509,0 -2968,20121100,Equipo para cementar,,0,80509,0 -2969,20121101,Unidades de mezclado,Maquina de mezclado,0,80509,0 -2970,20121102,Tapones de puente,,0,80509,0 -2971,20121103,Equipo de cemento líquido a granel,,0,80509,0 -2972,20121104,Equipo de material de cemento a granel,Equipo de materiales a granel de cemento,0,80509,0 -2973,20121105,Sensores de densidad del cemento,,0,80509,0 -2974,20121106,Unidades de flotación de cemento a granel,Maquina de flotación de cemento a granel,0,80509,0 -2975,20121107,Herramientas de flotación de cemento,,0,80509,0 -2976,20121108,Tapones limpiadores de equipo de flotación de cemento,,0,80509,0 -2977,20121109,Unidades de bombeo de cemento,Maquina de bombeo de cemento,0,80509,0 -2978,20121110,Retenedores de cemento,,0,80509,0 -2979,20121111,Centralizadores,,0,80509,0 -2980,20121112,Acoples de pestillo expreso,,0,80509,0 -2981,20121113,Collares de flotación,,0,80509,0 -2982,20121114,Zapatas de flotación,,0,80509,0 -2983,20121115,Herramientas para cementación de campos petroleros,,0,80509,0 -2984,20121116,Tapones recuperables de cementación,,0,80509,0 -2985,20121118,Cabezales de cemento bajo el mar,,0,80509,0 -2986,20121119,Cabezales de cemento en superficie,,0,80509,0 -2987,20121120,Centralizador de arco,,0,80509,0 -2988,20121121,Centralizador de cuchilla,,0,80509,0 -2989,20121122,Centralizador sub de arco,,0,80509,0 -2990,20121123,Kit de herramientas de cementación,Equipo de herramientas de cementación,0,80509,0 -2991,20121124,Kit de equipo de flotación de cemento,Equipo de flotación de cemento,0,80509,0 -2992,20121125,Kit de retención de cemento,Equipo retención de cemento,0,80509,0 -2993,20121126,Partes y accesorios del centralizador,,0,80509,0 -2994,20121127,Estabilizador,,0,80509,0 -2995,20121128,Herramienta de torque y arrastre de reducción,,0,80509,0 -2996,20121129,Partes y accesorios de la herramienta de torque y arrastre de reducción,,0,80509,0 -2997,20121130,Partes y accesorios del tapón de puente,,0,80509,0 -2998,20121300,Equipo de control de arena,,0,80509,0 -2999,20121301,Tapones obturadores,Unidades de bombeo de estimulación,0,80509,0 -3000,20121302,Flotadores,,0,80509,0 -3001,20121303,Sistemas frac pack,,0,80509,0 -3002,20121304,Sistemas paquete de gravilla,Sistemas de embalaje Frac,0,80509,0 -3003,20121305,Zapatas guía,Sistemas de paquete de Frac,0,80509,0 -3004,20121306,Boquillas de acometida,Sistemas de embalaje de grava,0,80509,0 -3005,20121307,Acoples de conformación,Sistemas de paquete de grava,0,80509,0 -3006,20121308,Rebosamientos de tuberías de producción,,0,80509,0 -3007,20121309,Obturadores de control de arena,,0,80509,0 -3008,20121310,Equipo de granel líquido para control de arena,,0,80509,0 -3009,20121311,Equipo que usa unidades de soporte para control de arena a granel,Equipos de apoyo a granel de control de arena,0,80509,0 -3010,20121312,Sensores de densidad de control de arena,,0,80509,0 -3011,20121313,Unidades múltiples de control de arena,Maquina múltiple de control de arena,0,80509,0 -3012,20121314,Equipo de transporte de arena usando unidades de soporte,,0,80509,0 -3013,20121315,Unidades control de bombeo de arena,,0,80509,0 -3014,20121316,Pantallas de control de arena,,0,80509,0 -3015,20121317,Unidades de mezclado de lechada control de arena,Maquina de mezclado de lechada control de arena,0,80509,0 -3016,20121318,Detectores de arena,,0,80509,0 -3017,20121319,Localizadores de ensamble de sello,,0,80509,0 -3019,20121321,Herramientas de levantamiento de manga,,0,80509,0 -3020,20121322,Mangas deslizantes,Herramientas de desplazamiento para sistemas de manguitos,0,80509,0 -3021,20121323,Cuerdas de velocidad,Cadenas de velocidad,0,80509,0 -3022,20121324,Anillo de control de arena,,0,80509,0 -3023,20121325,Patrón de tubo muescado,"Patrón de tubería ranurada, Patrón de tubo ranurado",0,80509,0 -3024,20121326,Partes y accesorios de pantalla de control de arena,,0,80509,0 -3025,20121400,Herramientas y equipo de terminación,Herramientas y equipo de acabado,0,80509,0 -3026,20121401,Subs de atrapado de bola,,0,80509,0 -3027,20121402,Uniones de voladura,,0,80509,0 -3028,20121403,Boquillas de voladura,,0,80509,0 -3029,20121404,Tapón calibrador de cierre,,0,80509,0 -3030,20121405,Dispositivos de circulación de producción,,0,80509,0 -3031,20121406,Equipo de prueba de culminación,,0,80509,0 -3032,20121407,Protectores de línea de control,,0,80509,0 -3033,20121408,Herramientas de deflexión,,0,80509,0 -3034,20121409,Uniones de expansión de culminación,,0,80509,0 -3035,20121410,Acoples de flujo,,0,80509,0 -3036,20121411,Equipo de elevación de gas,,0,80509,0 -3037,20121412,Herramientas de estabilización de colgador,,0,80509,0 -3038,20121413,Bombas hidráulicas de culminación,,0,80509,0 -3039,20121414,Herramientas hidráulicas de curado,,0,80509,0 -3040,20121415,Sistemas de inyección,,0,80509,0 -3041,20121416,Boquillas de estabilización,,0,80509,0 -3042,20121417,Colgadores de línea,,0,80509,0 -3043,20121418,Herramientas de halar tapones,,0,80509,0 -3044,20121419,Herramientas de correr tapones,,0,80509,0 -3045,20121420,Tapones de producción,,0,80509,0 -3046,20121421,Equipos de bombeo a través de la línea de flujo,,0,80509,0 -3047,20121422,Uniones de seguridad de culminación,,0,80509,0 -3048,20121423,Ensambles de sello de culminación,,0,80509,0 -3049,20121424,Sello hoyo u hoyo pulido,,0,80509,0 -3050,20121425,Mandriles de bolsillos laterales,,0,80509,0 -3051,20121427,Válvulas de seguridad sub – superficie,,0,80509,0 -3052,20121428,Uniones de desplazamiento,,0,80509,0 -3053,20121429,Anclajes de tubería,,0,80509,0 -3054,20121430,Ensamblajes de flujo paralelo,,0,80509,0 -3055,20121431,Tapón inflable,"Embalador inflable, Empacador inflable",0,80509,0 -3056,20121432,Partes y accesorios de válvula de control de pozo,,0,80509,0 -3057,20121433,Kit de reparación de culminación de hoyo revestido,"Equipo de reparación de culminación de hoyo revestido, Equipo de reparación de terminación de entubado",0,80509,0 -3058,20121434,Kit de reparación de manga deslizante,Equipo de reparación de mango deslizante,0,80509,0 -3059,20121435,Kit adaptador de fraguado,"Equipo adaptador de ajuste, Equipo adaptador de fraguado",0,80509,0 -3060,20121436,Partes y accesorios para el sistema de muelle de pistón,,0,80509,0 -3061,20121437,Partes y accesorios para las válvulas de seguridad de sub – superficie,,0,80509,0 -3062,20121438,Partes y accesorios para válvula de muelle de gas,,0,80509,0 -3063,20121439,Mandril tapón de producción,,0,80509,0 -3064,20121440,Sub tapón de revestimiento del anillo,,0,80509,0 -3065,20121441,Herramienta de fraguado del revestimiento,,0,80509,0 -3066,20121442,Bloque de arrastre,,0,80509,0 -3067,20121443,Kit de herramientas de fraguado de tapón de revestimiento,Equipo de herramientas de fraguado de tapón de revestimiento,0,80509,0 -3068,20121444,Kit de reparación del sistema de revestimiento,Equipo de reparación del sistema de revestimiento,0,80509,0 -3069,20121445,Accesorios y partes,"Accesorios y piezas de empacador de producción, Piezas y accesorios de embalaje de producción",0,80509,0 -3070,20121446,Tapón superior revestimiento,,0,80509,0 -3071,20121447,Collar fraguado del revestimiento,,0,80509,0 -3072,20121448,Junk bonnet,,0,80509,0 -3073,20121449,Equipo de muelle de pistón,,0,80509,0 -3074,20121450,Mandril muelle de gas,,0,80509,0 -3075,20121451,Válvula muelle de gas,,0,80509,0 -3076,20121500,Herramientas convencionales de perforación,,0,80509,0 -3077,20121501,Preventor de reventones,,0,80509,0 -3078,20121502,Controles del preventor de reventones,,0,80509,0 -3079,20121503,Raspadores de revestimiento,Raspadores de carcasa,0,80509,0 -3080,20121504,Collar de perforación,,0,80509,0 -3081,20121505,Equipo de núcleos,,0,80509,0 -3082,20121506,Protectores de rosca de tubería de perforación,,0,80509,0 -3083,20121507,Uniones de herramientas de tubería de perforación,,0,80509,0 -3084,20121508,Tubo de perforación,,0,80509,0 -3085,20121509,Anillos de calibre,,0,80509,0 -3086,20121510,Abridores de hoyos,,0,80509,0 -3087,20121511,Escariadores de hoyos,Escariadores de orificios,0,80509,0 -3088,20121513,Amortiguadores de fondo de pozo,,0,80509,0 -3089,20121514,Subs de perforación,Submarinos de perforación,0,80509,0 -3090,20121515,Estabilizadores de fondo de pozo,,0,80509,0 -3091,20121516,Propulsores,,0,80509,0 -3092,20121517,Partes y accesorios de escariadores de hoyos de perforación,,0,80509,0 -3093,20121518,Cabeza de control giratorio,,0,80509,0 -3094,20121519,Partes y accesorios de la cabeza de control giratorio,,0,80509,0 -3095,20121520,Partes y accesorios del preventor de reventones,,0,80509,0 -3096,20121521,Partes y accesorios de los amortiguadores de fondo de pozo,,0,80509,0 -3097,20121522,Partes y accesorios de los raspadores de revestimiento,,0,80509,0 -3098,20121523,Tarro de perforación,,0,80509,0 -3099,20121524,Partes y accesorios del tarro de perforación,,0,80509,0 -3100,20121600,Brocas de barrena,,0,80509,0 -3101,20121601,Brocas de cortadora fija,,0,80509,0 -3102,20121602,Brocas de diamante natural,,0,80509,0 -3103,20121603,Brocas de boquilla,,0,80509,0 -3104,20121604,Brocas pdc,,0,80509,0 -3105,20121605,Brocas cónicas de rodillo de botón de inserción,,0,80509,0 -3106,20121606,Brocas de rodillo de diente de acero,,0,80509,0 -3107,20121607,Brocas de núcleo,,0,80509,0 -3108,20121608,Brocas de bloque,,0,80509,0 -3109,20121609,Brocas de minería continuas,,0,80509,0 -3110,20121610,Brocas de alimentador,,0,80509,0 -3111,20121611,Broca de tamaño grande,,0,80509,0 -3112,20121612,Broca de techo,,0,80509,0 -3113,20121613,Accesorios de broca,,0,80509,0 -3114,20121700,Herramientas de pesca,,0,80509,0 -3115,20121701,Bumper subs,,0,80509,0 -3116,20121702,Parches de revestimiento,,0,80509,0 -3117,20121703,Impulsores de tarro,,0,80509,0 -3118,20121704,Subs de chatarra,,0,80509,0 -3119,20121705,Molinos o zapatas de quemado,,0,80509,0 -3121,20121707,Lanzas de pesca en campo petrolero,,0,80509,0 -3122,20121708,Herramientas de pesca no especificada,,0,80509,0 -3123,20121709,Tubo y extensión de lavado de pesca,,0,80509,0 -3124,20121710,Extensión de rebosamiento,,0,80509,0 -3125,20121711,Sub de pesca,,0,80509,0 -3126,20121712,Partes y accesorios de parche de revestimiento,,0,80509,0 -3127,20121713,Buje de operación de tubo de lavado,,0,80509,0 -3128,20121714,Bloque de impresión de pesca,,0,80509,0 -3129,20121715,Kit de pesca de sistema de cable de acero,Equipo de pesca de sistema de línea de alambre,0,80509,0 -3130,20121716,Partes y accesorios de la canasta de chatarra,,0,80509,0 -3131,20121717,Pescante de agarre,,0,80509,0 -3132,20121718,Control de pescante,,0,80509,0 -3133,20121719,Tapón de pescante,,0,80509,0 -3134,20121720,Partes y accesorios de pescante,,0,80509,0 -3135,20121721,Imán de pesca,,0,80509,0 -3136,20121722,Grifo de ajuste,,0,80509,0 -3137,20121723,Agarre de lanza de pescar,,0,80509,0 -3138,20121724,Partes y accesorios de lanza de pescar,,0,80509,0 -3139,20121725,Cortador de revestimiento,,0,80509,0 -3140,20121726,Grifo de caja,,0,80509,0 -3141,20121727,Canasta de desperdicios,,0,80509,0 -3142,20121728,Tarro de pesca,,0,80509,0 -3143,20121800,Equipo de perforación direccional,,0,80509,0 -3144,20121801,Herramientas de geo - dirección,,0,80509,0 -3145,20121802,Motores de lodo,,0,80509,0 -3146,20121803,Herramientas dirigibles giratorias,,0,80509,0 -3147,20121804,Sistemas de control de superficie de perforación direccional,,0,80509,0 -3148,20121805,Herramientas de perforación direccional de hoyo derecho,,0,80509,0 -3149,20121806,Registro mientras herramientas perforan lwd,Registro de herramientas de perforación LWD,0,80509,0 -3150,20121807,Partes y accesorios de registro mientras herramientas perforan lwd,,0,80509,0 -3151,20121808,Estabilizador de perforación direccional,,0,80509,0 -3152,20121809,Partes y accesorios de herramientas dirigibles giratorias,,0,80509,0 -3153,20121810,Sub de taladro direccional,,0,80509,0 -3154,20121811,Hélice de perforación direccional,,0,80509,0 -3155,20121812,Collar de perforación direccional,,0,80509,0 -3156,20121813,Partes y accesorios de motores de lodo,,0,80509,0 -3157,20121900,Equipo de medir y registro del pozo,,0,80509,0 -3158,20121901,Herramientas acústicas,,0,80509,0 -3159,20121902,Instrumentos de control de perforación o lodo,,0,80509,0 -3160,20121903,Herramientas de medición de desempeño de perforación,,0,80509,0 -3161,20121904,Equipo de medición de flujo,,0,80509,0 -3162,20121905,Herramientas de resonancia magnética nuclear,,0,80509,0 -3163,20121906,Herramientas nucleares,,0,80509,0 -3164,20121907,Equipos de registro de producción,,0,80509,0 -3165,20121908,Herramientas de resistividad,,0,80509,0 -3166,20121909,Sistemas de levantamiento,,0,80509,0 -3167,20121910,Sistemas de telemetría,,0,80509,0 -3168,20121911,Herramientas ultrasónicas,,0,80509,0 -3169,20121912,Equipo de presión de registro de fondo de pozo,,0,80509,0 -3170,20121913,Equipo de prueba de registro de fondo de pozo,,0,80509,0 -3171,20121914,Unidades de registro de pozo,,0,80509,0 -3172,20121915,Registro de densidad de granel,,0,80509,0 -3173,20121916,Cable de fondo de pozo de sensor óptico,,0,80509,0 -3174,20121917,Mandril y accesorios de sensor óptico,,0,80509,0 -3175,20121918,Cable de superficie de sensor óptico,,0,80509,0 -3176,20121919,Herramientas y accesorios de inspección de revestimiento,,0,80509,0 -3177,20121920,Herramientas y accesorios de indicador de punto libre,,0,80509,0 -3178,20121921,Medidor de radiación,,0,80509,0 -3179,20121922,Partes y accesorios de herramienta de rayos gamma,,0,80509,0 -3180,20121923,Herramientas y partes de imaginería de pozo,Herramienta y piezas de imagen de pozo,0,80509,0 -3181,20122000,Equipo de prueba y Accesorios,,0,80509,0 -3182,20122001,Barra de desplazamiento,,0,80509,0 -3183,20122002,Mangas de desplazamiento,,0,80509,0 -3184,20122003,Conejo de desplazamiento,,0,80509,0 -3185,20122004,Accesorios de prueba,,0,80509,0 -3186,20122005,Boquillas de prueba,,0,80509,0 -3187,20122006,Tapones de prueba,,0,80509,0 -3188,20122100,Equipo de Perforación,,"0,1",80509,0 -3189,20122101,Pistolas de cápsulas,,"0,1",80509,0 -3190,20122102,Pistolas de revestimiento,,"0,1",80509,0 -3191,20122103,Cabezas de despliegue,,"0,1",80509,0 -3192,20122104,Explosivos de perforación,,"0,1",80509,0 -3193,20122105,Cabezas de disparo,,"0,1",80509,0 -3194,20122106,Adaptadores de pistola,,"0,1",80509,0 -3195,20122107,Pistola de disparo de alta densidad,,"0,1",80509,0 -3196,20122108,Tapones bull de perforación,,"0,1",80509,0 -3197,20122109,Herramientas de poner tapones,,"0,1",80509,0 -3198,20122110,Equipo de posicionamiento de perforación,,"0,1",80509,0 -3199,20122111,Pistolas festoneadas,,"0,1",80509,0 -3200,20122112,Subs en tándem,,"0,1",80509,0 -3201,20122114,Pistola de perforación de tubos completa,,"0,1",80509,0 -3202,20122115,Subs ventilación bajo balance,,"0,1",80509,0 -3203,20122213,Separadores de pruebas de pozo,,"0,1",80509,0 -3204,20122300,Equipo de Slickline - Cable de Recuperación,,0,80509,0 -3205,20122301,Cabezas de adaptador de cable de recuperación,,0,80509,0 -3206,20122302,Retardo de envío del cable de recuperación,,0,80509,0 -3207,20122303,Guías de campana de cable de recuperación,,0,80509,0 -3208,20122304,Cajas ciegas de cable de recuperación,,0,80509,0 -3209,20122305,Equipo de presión de fondo de hoyo de cable de recuperación,,0,80509,0 -3210,20122306,Herramientas de calibración de cable de recuperación,,0,80509,0 -3211,20122307,Volcado de cemento de rescate de equipos cable de recuperación,,0,80509,0 -3212,20122308,Cortadores químicos de cable de recuperación,,0,80509,0 -3213,20122309,Centralizadores de cadena de herramientas de almeja de cable de recuperación,,0,80509,0 -3214,20122310,Centralizadores de cable de acero de almeja de cable de recuperación,Pinza Slickline en centralizadores de cadena de herramienta,0,80509,0 -3215,20122311,Localizadores de collar de cable de recuperación,,0,80509,0 -3216,20122312,Colectores de cable de recuperación,,0,80509,0 -3217,20122313,Herramientas de colisión de cable de recuperación,,0,80509,0 -3218,20122314,Cruces de cable de recuperación,,0,80509,0 -3219,20122315,Equipo de medición de profundidad de cable de recuperación,,0,80509,0 -3220,20122316,Frasco dewar de cable de recuperación,,0,80509,0 -3221,20122317,Herramientas del dipmeter de cable de recuperación,,0,80509,0 -3223,20122319,Herramientas de vaya con el diablo de cable de recuperación,,0,80509,0 -3224,20122320,Abridores de hoyos de cable de recuperación,,0,80509,0 -3225,20122321,Cortadores jet de cable de recuperación,,0,80509,0 -3226,20122322,Disparos de desperdicios de cable de recuperación,,0,80509,0 -3227,20122323,Herramientas de kickover de cable de recuperación,,0,80509,0 -3228,20122324,Uniones de nudillos de cable de recuperación,,0,80509,0 -3229,20122325,Bloques de impresión de cable de recuperación,,0,80509,0 -3230,20122326,Mandriles de ubicación de cable de recuperación,,0,80509,0 -3231,20122327,Mandriles de bloqueo de cable de recuperación,,0,80509,0 -3232,20122328,Lubricadores de cable de recuperación,,0,80509,0 -3233,20122329,Achicadores mecánicos de cable de recuperación,,0,80509,0 -3234,20122330,Plugback mecánico de cable de recuperación,,0,80509,0 -3235,20122331,Otras herramientas de cable de recuperación,,0,80509,0 -3236,20122332,Raspadores de parafina de cable de recuperación,,0,80509,0 -3237,20122333,Enchufe de línea de cable de recuperación,,0,80509,0 -3238,20122334,Tenazas para halar o correr el cable de recuperación,Pinzas Slickline para correr y tirar,0,80509,0 -3239,20122335,Herramientas para cortar el cable de recuperación,,0,80509,0 -3240,20122336,Roldanas o bloques de piso de la cable de recuperación,,0,80509,0 -3241,20122338,Accesorios de herramientas para halar el cable de recuperación,Accesorios para herramientas de tracción Slickline,0,80509,0 -3242,20122339,Herramientas para halar el cable de recuperación,Herramientas de tracción Slickline,0,80509,0 -3243,20122340,Herramientas para correr el cable de recuperación,,0,80509,0 -3244,20122341,Unidades de cable de recuperación,,0,80509,0 -3245,20122342,Alambre del cable de recuperación,,0,80509,0 -3246,20122343,Herramientas sónicas del cable de recuperación,,0,80509,0 -3247,20122344,Barras espaciadoras del cable de recuperación,,0,80509,0 -3248,20122345,Terminales de prensado del cable de recuperación,,0,80509,0 -3249,20122346,Dispositivos de tensión del cable de recuperación,,0,80509,0 -3250,20122347,Tapones de tubos de cable de recuperación,,0,80509,0 -3251,20122348,Herramientas ultrasónicas cable de recuperación,,0,80509,0 -3252,20122349,Agarres del cable de recuperación,,0,80509,0 -3253,20122350,Tarros del cable de recuperación,,0,80509,0 -3254,20122351,Raspadores del cable de recuperación,,0,80509,0 -3255,20122352,Lanza del cable de recuperación,,0,80509,0 -3256,20122353,Tallos del cable de recuperación,,0,80509,0 -3257,20122354,Válvulas del cable de recuperación,,0,80509,0 -3258,20122356,Preventores del cable de recuperación,,0,80509,0 -3259,20122357,Aceleradores de tarro del cable de recuperación,,0,80509,0 -3260,20122358,Dardo de prueba,,0,80509,0 -3261,20122359,Accesorios y partes de herramientas de corrida del cable de recuperación,,0,80509,0 -3262,20122360,Punzón de la línea de cable,,0,80509,0 -3263,20122361,Válvula de pie,,0,80509,0 -3264,20122362,Guía de campana de la línea de cable,,0,80509,0 -3265,20122363,Hallador de cable,,0,80509,0 -3266,20122364,Cadena de herramientas de línea de cable,,0,80509,0 -3267,20122365,Partes y accesorios del centralizador de cable de recuperación,,0,80509,0 -3268,20122366,Descentralizador magnético,,0,80509,0 -3269,20122367,Herramienta de alado de la línea de cable,,0,80509,0 -3270,20122368,Mandril de la línea de cable,,0,80509,0 -3271,20122369,Herramienta de establecer línea de cable,,0,80509,0 -3272,20122370,Cruce de línea de cable,,0,80509,0 -3273,20122371,Centralizador de cable de recuperación,,0,80509,0 -3274,20122372,Acople de pivote de línea de cable,,0,80509,0 -3275,20122373,Partes y accesorios de herramienta de kickover del cable de recuperación,,0,80509,0 -3276,20122400,Equipo de sistemas de producción,,0,80509,0 -3277,20122401,Dispositivo golpeador de cable,,0,80509,0 -3278,20122402,Evaporadores de producción de campo de petróleo,,0,80509,0 -3279,20122403,Probadores hipot,,0,80509,0 -3280,20122404,Maquinas de vueltas de campo de petróleo,,0,80509,0 -3281,20122405,Elevadores finales de motor,,0,80509,0 -3282,20122406,Probadores dieléctricos de aceite,,0,80509,0 -3283,20122407,Unidades de llenado al vacío de aceite,,0,80509,0 -3284,20122408,Enderezadores de eje de producción de campo petrolero,,0,80509,0 -3285,20122409,Bobina de producción de campo de petróleo,,0,80509,0 -3286,20122410,Analizadores de vibración,,0,80509,0 -3287,20122500,Equipo para tubería flexible,,0,80509,0 -3288,20122501,Herramientas de arenadora,,0,80509,0 -3289,20122502,Unidades de camión grúa de tubería flexible,,0,80509,0 -3290,20122503,Unidades de tubería flexible,Cespol,0,80509,0 -3291,20122504,Paquetes de manguera de tubería flexible,,0,80509,0 -3292,20122505,Sistemas inflables de tubería flexible,,0,80509,0 -3293,20122506,Cabezales de inyector de tubería flexible,,0,80509,0 -3294,20122507,Equipo de elevación de tubería flexible,,0,80509,0 -3295,20122508,Casas de operadores,,0,80509,0 -3296,20122509,Fuentes de poder de tubería flexible,,0,80509,0 -3297,20122510,Rollos de tubería flexible,,0,80509,0 -3298,20122511,Carretes de embobinar tubería flexible,,0,80509,0 -3299,20122512,Guías de tubería,,0,80509,0 -3300,20122513,Enganches de boca de pozo,,0,80509,0 -3301,20122514,Estructuras de soporte de boca de pozo,,0,80509,0 -3302,20122515,Tubería flexible de campo petrolero,,0,80509,0 -3303,20122516,Cadena de herramientas de tubería flexible,,0,80509,0 -3304,20122518,Centralizador de tubería flexible,,0,80509,0 -3305,20122700,Mercancía tubular para campo petrolero,,0,80509,0 -3306,20122701,Revestimiento para campo petrolero,,0,80509,0 -3307,20122702,Acoples para campo petrolero,,0,80509,0 -3308,20122703,Tubería corta para campo petrolero,,0,80509,0 -3309,20122704,Tubería para campo petrolero,,0,80509,0 -3310,20122705,Recubrimiento de tubería para campo petrolero,,0,80509,0 -3311,20122706,Revestimiento de conductor,,0,80509,0 -3312,20122707,Equipo para operar el revestimiento para conductor,,0,80509,0 -3313,20122708,Cruces de la tubería de perforación,,0,80509,0 -3314,20122709,Protectores de rosca para campo petrolero,,0,80509,0 -3315,20122710,Dispositivo para parar el revestimiento,,0,80509,0 -3316,20122800,Equipo y plataformas de reacondicionamiento y perforación,,0,80509,0 -3317,20122801,Agitadores de lodos,,0,80509,0 -3318,20122802,Depósitos de lodos,,0,80509,0 -3319,20122803,Equipo de aire para perforaciones,,0,80509,0 -3321,20122806,Desviadores de fluido,,0,80509,0 -3322,20122807,Aparejos de maniobras,,0,80509,0 -3323,20122808,Equipo para lecho de perforación,,0,80509,0 -3324,20122809,Pivotes de perforación,,0,80509,0 -3325,20122810,Barco – taladro de perforación,,0,80509,0 -3326,20122811,Elevadores de taladro de perforación,,0,80509,0 -3327,20122812,Estribos del taladro de perforación,,0,80509,0 -3328,20122813,Unidades de reacondicionamiento hidráulico,,0,80509,0 -3329,20122814,Sistemas de gatos para mover el taladro de perforación,,0,80509,0 -3330,20122815,Taladro de perforación marina movido con gatos,,0,80509,0 -3331,20122816,Bujes kelly,,0,80509,0 -3332,20122817,Válvulas kelly,,0,80509,0 -3333,20122818,Escobillas kelly,,0,80509,0 -3334,20122819,Kellys,,0,80509,0 -3335,20122820,Taladro de perforación en tierra,,0,80509,0 -3336,20122821,Equipo de limpieza de lodo,,0,80509,0 -3337,20122822,Múltiples para lodo,,0,80509,0 -3338,20122823,Mezcladoras de lodo,,0,80509,0 -3339,20122824,Equipo para manejo de tubería,,0,80509,0 -3340,20122825,Plataforma de taladro de perforación,,0,80509,0 -3341,20122826,Pivote de energía o transmisión superior,,0,80509,0 -3342,20122827,Patines para taladro,,0,80509,0 -3343,20122828,Elevadores del taladro de perforación,,0,80509,0 -3344,20122829,Mesas giratorias de taladro de perforación,,0,80509,0 -3345,20122830,Plataformas auto – elevadoras para el reacondicionamiento,,0,80509,0 -3346,20122831,Taladros de perforación semi – sumergibles,,0,80509,0 -3347,20122832,Deslizadores del lecho de perforación,,0,80509,0 -3348,20122833,Tenazas de reposición,,0,80509,0 -3349,20122834,Llave doble automática,,0,80509,0 -3350,20122835,Equipo viajero,,0,80509,0 -3351,20122836,Barcos de reacondicionamiento,,0,80509,0 -3352,20122837,Taladros de reacondicionamiento,,0,80509,0 -3353,20122838,Separadores de lodo (lutita),,0,80509,0 -3354,20122839,Desgasificadores de lodo,,0,80509,0 -3355,20122840,Bloques de corona,,0,80509,0 -3356,20122841,Bloques móviles,,0,80509,0 -3357,20122842,Desarenadores de lodo,,0,80509,0 -3358,20122843,Separadores de granos finos de lodo,,0,80509,0 -3359,20122844,Tenazas de energía,Pinzas eléctricas,0,80509,0 -3360,20122845,Partes y accesorios para el manejo de tubería,,0,80509,0 -3361,20122846,Trampolín,,0,80509,0 -3362,20122847,Sub y enchufe de elevador,,0,80509,0 -3363,20122848,Dispositivo de arreglo horizontal o unidad de trepidación,,0,80509,0 -3364,20122849,Araña de revestimiento de pozo,,0,80509,0 -3365,20122851,Unidad de potencia hidráulica,,0,80509,0 -3366,20122900,Equipo de registrar datos de superficie,,0,80509,0 -3367,20122901,Conductos de registrar datos de superficie,,0,80509,0 -3368,20122902,Sensores de registrar datos de superficie,,0,80509,0 -3369,20122903,Unidades de registrar datos de superficie,,0,80509,0 -3370,20123200,Equipo de expansión en profundidad,,0,80509,0 -3371,20123201,Herramienta ranurada de ajuste expandible,,0,80509,0 -3372,20123202,Kit de reparación expandible,,0,80509,0 -3373,20123203,Revestimiento expandible,,0,80509,0 -3374,20131000,Lodo de perforar y materiales,,0,80509,0 -3375,20131001,Agentes de control de filtración,,0,80509,0 -3376,20131002,Espaciadores de fluido,,0,80509,0 -3377,20131003,Agentes de circulación perdida,,0,80509,0 -3378,20131004,Lodos con base de petróleo,,0,80509,0 -3379,20131005,Aumentadores de la rata de penetración,,0,80509,0 -3380,20131006,Fluidos centradores,,0,80509,0 -3381,20131007,Lodos con base sintética,,0,80509,0 -3382,20131008,Agentes para adelgazar el lodo,,0,80509,0 -3383,20131009,Lodos con base en agua,,0,80509,0 -3384,20141000,Equipo de cabeza de pozo,,0,80509,0 -3385,20141001,Actuadores de boca de pozo,,0,80509,0 -3386,20141002,Bombas de balancín de boca de pozo,,0,80509,0 -3387,20141003,Líneas de flujo de boca de pozo,,0,80509,0 -3388,20141004,Válvulas de compuerta de boca de pozo,,0,80509,0 -3389,20141005,Reguladores de producción de boca de pozo,,0,80509,0 -3390,20141006,Accesorios de boca de pozo o flujo debajo de la superficie de boca de pozo,,0,80509,0 -3391,20141007,Accesorios de boca de pozo o flujo de superficie de boca de pozo,,0,80509,0 -3392,20141008,Válvulas de seguridad de la superficie de boca de pozo,,0,80509,0 -3393,20141011,Adaptador de cabeza de tubería,,0,80509,0 -3394,20141014,Carrete del cabezal del revestimiento,,0,80509,0 -3395,20141015,Conexiones en t o en cruz de la boca de pozo,,0,80509,0 -3396,20141016,Base de llegada de la boca de pozo,,0,80509,0 -3397,20141017,Cuerpo de transporte boca de pozo,,0,80509,0 -3398,20141018,Colgador de boca de pozo,,0,80509,0 -3399,20141300,Anclas y bombas de chorro de barrenas hacia abajo,,0,80509,0 -3400,20141301,Bombas de chorro de fondo de pozo,,0,80509,0 -3401,20141302,Partes y accesorios de bombas de chorro de fondo de pozo,,0,80509,0 -3402,20141303,Tubería de perforación,,0,80509,0 -3403,20141500,Bombas de barrenas hacia abajo,,0,80509,0 -3404,20141501,Bombas eléctricas de fondo de pozo,,0,80509,0 -3405,20141502,Bomba de fondo de pozo de cavidad progresiva,,0,80509,0 -3406,20141700,Plataformas de almacenaje y producción a poca distancia de la costa,,0,80509,0 -3407,20141701,Plataformas fijas de producción costa afuera,,0,80509,0 -3408,20141702,Plataformas flotantes de producción costa afuera,,0,80509,0 -3409,20141703,Plataformas flotantes de almacenamiento costa afuera,,0,80509,0 -3410,20141704,Plataformas flotantes de brazo de tensión de producción costa afuera,,0,80509,0 -3411,20141705,Plataformas flotantes de brazo de tensión de almacenamiento costa afuera,,0,80509,0 -3412,20142200,Calentadores de línea,,0,80509,0 -3413,20142201,Calentadores de línea eléctrica,,0,80509,0 -3414,20142700,Unidades de bombear,,0,80509,0 -3415,20142701,Gatos de la varilla de bombeo,,0,80509,0 -3416,20142702,Bombas de barra,,0,80509,0 -3417,20142703,Bombas de barra mecánica,,0,80509,0 -3418,20142704,Ensamble de marco de bombeo y extensión,,0,80509,0 -3419,20142705,Ensamble de biela,,0,80509,0 -3420,20142706,Ensamble de ecualizador pitman,,0,80509,0 -3421,20142707,Ensamble de cabezote,,0,80509,0 -3422,20142708,Ensamble de puesto samson,,0,80509,0 -3423,20142709,Ensamble de viga de caminar,,0,80509,0 -3424,20142710,Ensamble de barra de colgador,,0,80509,0 -3425,20142800,Separadores de producción,,0,80509,0 -3426,20142801,Separadores de agua petróleo,,0,80509,0 -3428,20142901,Tanques almacenadores de petróleo,,0,80509,0 -3429,20142902,Tanque de fibra de vidrio,,0,80509,0 -3430,20142903,Tanque de acero,,0,80509,0 -3431,20142904,Tanque plástico,,0,80509,0 -3432,20142905,Tanque receptor de aire,,0,80509,0 -3433,20143000,Vástagos de succión,,0,80509,0 -3434,20143001,Vástagos de succión de aleación de acero,,0,80509,0 -3435,20143002,Varillas cortas,,0,80509,0 -3436,20143003,Vástago continuo de succión,,0,80509,0 -3437,20143004,Pin de terminación roscada de vástago continuo de succión,,0,80509,0 -3438,20143005,Acople deslizante de vástago continuo de succión,,0,80509,0 -3439,21101500,Maquinaria agrícola para preparación del suelo,,0,80509,0 -3440,21101501,Arados,,0,80509,0 -3441,21101502,Pulverizadores,,0,80509,0 -3442,21101503,Cultivadoras,,0,80509,0 -3443,21101504,Desmalezadoras,,0,80509,0 -3444,21101505,Máquinas desyerbadoras,,0,80509,0 -3445,21101506,Máquinas aplanadoras o niveladoras,,0,80509,0 -3446,21101507,Rodillos agrícolas,,0,80509,0 -3447,21101508,Rodillos para prados o terrenos deportivos,,0,80509,0 -3448,21101509,Máquina para drenaje de zanjas,,0,80509,0 -3449,21101511,Chorros de riego,,0,80509,0 -3450,21101512,Gastos generales de riego,,0,80509,0 -3451,21101513,Discos,,0,80509,0 -3452,21101514,Arados de subsuelo,,0,80509,0 -3453,21101516,Sembradoras,,0,80509,0 -3454,21101517,Motocultivador giratorio o de potencia,,0,80509,0 -3455,21101518,Esterilizador de suelo,,0,80509,0 -3456,21101519,Inyector de suelo,,0,80509,0 -3457,21101520,Lanzallamas,,0,80509,0 -3458,21101521,Mezclador de fertilizante,,0,80509,0 -3459,21101522,Arado de bordes o arado,,0,80509,0 -3460,21101523,Sistema automatizado de manejo de maquinaria agrícola,,0,80509,0 -3461,21101600,Maquinaria agrícola para siembra y plantado,,0,80509,0 -3462,21101601,Plantadoras,,0,80509,0 -3463,21101602,Transplantadoras,,0,80509,0 -3464,21101603,Sembradoras de grano,,0,80509,0 -3465,21101604,Sembradoras de semillas,,0,80509,0 -3466,21101605,Equipo para tratamiento de semillas,,0,80509,0 -3467,21101606,Excavadoras de agujeros,,0,80509,0 -3468,21101607,Remolque de sembradora,,0,80509,0 -3469,21101608,Gabinete o cámara para crecimiento de plantas,,0,80509,0 -3470,21101609,Cama de semillas (semillero),,0,80509,0 -3471,21101610,Máquina de cubrimiento del suelo,,0,80509,0 -3472,21101611,Controlador de tasa de flujo de fertilizante,,0,80509,0 -3473,21101612,Germinador de semillas,,0,80509,0 -3474,21101700,Maquinaria agrícola para cosechar,,0,80509,0 -3475,21101701,Cortadoras de pasto,,0,80509,0 -3476,21101702,Máquina segadora de heno,,0,80509,0 -3477,21101703,Cosechadoras,,0,80509,0 -3478,21101704,Cosechadoras “trilladoras” o mixtas,,0,80509,0 -3479,21101705,Trilladoras,,0,80509,0 -3480,21101706,Separadores de cultivos,,0,80509,0 -3481,21101707,Piezas de cosechadora o accesorios,,0,80509,0 -3482,21101708,Piezas de segadora o accesorios,,0,80509,0 -3483,21101709,Embaladora,,0,80509,0 -3484,21101710,Secadora de cosecha,,0,80509,0 -3485,21101711,Desgranadora,,0,80509,0 -3486,21101712,Máquina automática cortadora y empaquetadora de flores,,0,80509,0 -3487,21101800,Aparatos dispersores o aspersores para agricultura,,0,80509,0 -3488,21101801,Rociadores,,0,80509,0 -3489,21101802,Guarda polvos,,0,80509,0 -3490,21101803,Aspersores de agua,,0,80509,0 -3491,21101804,Dispersores o distribuidores de fertilizante,,0,80509,0 -3492,21101805,Generadores de niebla o neblina,,0,80509,0 -3493,21101806,Compostadores,,0,80509,0 -3494,21101807,Equipo y suministros para polinización,,0,80509,0 -3495,21101808,Equipo para protección contra las heladas,,0,80509,0 -3496,21101809,Sistema para la fumigación del grano,,0,80509,0 -3497,21101900,Equipo para aves de corral y ganado,,0,80509,0 -3498,21101901,Ordeñadoras,,0,80509,0 -3499,21101902,Equipo para cría de ganado,,0,80509,0 -3500,21101903,Incubadoras o polleras para aves de corral,,0,80509,0 -3501,21101904,Mezcladoras de forraje,,0,80509,0 -3502,21101905,Equipo para identificación de ganado,,0,80509,0 -3503,21101906,Equipo de inspección o recolección de huevos,,0,80509,0 -3504,21101907,Máquinas para abrevar animales,,0,80509,0 -3505,21101908,Tanques refrigeradores de leche,,0,80509,0 -3506,21101909,Equipo para el esquilar o peluquear de animales,,0,80509,0 -3507,21101910,Peine de lana,,0,80509,0 -3508,21101911,Compostador para gallinaza,,0,80509,0 -3509,21101912,Espacio para inmovilizar ganado,,0,80509,0 -3510,21101913,Cortador de picos,,0,80509,0 -3511,21101914,Cerca eléctrica para ganado,,0,80509,0 -3512,21101915,Medidor de grasa en la espalda de los cerdos,"Medidor de grasa en la espalda de los chanchos, Medidor de grasa en la espalda de los cochinillos, Medidor de grasa en la espalda de los cochinos, Medidor de grasa en la espalda de los cuche, Medidor de grasa en la espalda de los cuinos, Medidor de grasa en la espalda de los gorrinos, Medidor de grasa en la espalda de los lechones, Medidor de grasa en la espalda de los marranos, Medidor de grasa en la espalda de los porcinos, Medidor de grasa en la espalda de los puercos,Medidor de grasa en la espalda de los lechones",0,80509,0 -3513,21102000,"Maquinaria agrícola para limpieza, selección o clasificación",,0,80509,0 -3514,21102001,"Máquinas limpiadoras de semillas, grano o legumbres secas",,0,80509,0 -3515,21102002,"Máquinas seleccionadoras de semillas, grano o legumbres secas",,0,80509,0 -3516,21102003,"Clasificadoras de semillas, grano o legumbres secas",,0,80509,0 -3517,21102004,Equipo para limpieza y descascarillado de arroz,,0,80509,0 -3518,21102005,Molino para trituración,,0,80509,0 -3519,21102006,Molinos de martillo,,0,80509,0 -3520,21102007,Clasificadora de frutas,,0,80509,0 -3521,21102008,Equipo para limpiar o descascarar cebada,,0,80509,0 -3522,21102100,Maquinaria y equipo para transformación agrícola,,0,80509,0 -3523,21102101,Máquinas agrícolas de briqueta,,0,80509,0 -3524,21102102,Expulsor de aceite de semilla,,0,80509,0 -3525,21102200,Maquinaria y equipo para silvicultura,,0,80509,0 -3526,21102201,Descortezadoras,,0,80509,0 -3527,21102202,Equipo de explotación forestal,,0,80509,0 -3528,21102203,Equipo de reforestación,,0,80509,0 -3529,21102204,Sierras para silvicultura,,0,80509,0 -3530,21102205,Deslizadores forestales,,0,80509,0 -3531,21102206,Barrenos para aumento forestal,,0,80509,0 -3532,21102207,Hipsómetro para silvicultura,,0,80509,0 -3534,21102301,Equipo de riego para invernadero,,0,80509,0 -3535,21102302,Materas para invernadero,,0,80509,0 -3536,21102303,Equipo de ventilación para invernadero,,0,80509,0 -3537,21102304,Equipo aislante para invernadero,,0,80509,0 -3538,21102305,Material para sombras para agricultura,,0,80509,0 -3539,21102306,Inyector de fertilizante hidropónico,,0,80509,0 -3540,21102400,Equipo para insectos,,0,80509,0 -3541,21102401,Equipo para apicultura,,0,80509,0 -3542,21102402,Equipo para gusanos de seda,,0,80509,0 -3543,21102403,Equipo para la cría de mariposas,,0,80509,0 -3544,21102404,Equipo para la cría de escarabajos,,0,80509,0 -3545,21111500,Equipo de pesca comercial,,0,80509,0 -3546,21111501,Anzuelos para pesca comercial,,0,80509,0 -3547,21111502,Carretes para pesca comercial,,0,80509,0 -3548,21111503,Aparejos para pesca comercial,,0,80509,0 -3549,21111504,Redes para pesca comercial,,0,80509,0 -3550,21111506,Flotadores para pesca comercial,,0,80509,0 -3551,21111507,Plomos o pesos comerciales,,0,80509,0 -3552,21111508,Transportadores de redes de pesca,,0,80509,0 -3553,21111600,Equipo para acuicultura,,0,80509,0 -3554,21111601,Equipo para piscicultura marina,,0,80509,0 -3555,21111602,Suministros para piscicultura,,0,80509,0 -3556,22101500,Maquinaria para trabajo de desmonte,,0,80509,0 -3557,22101501,Cargadores frontales,,0,80509,0 -3558,22101502,Niveladoras,,0,80509,0 -3559,22101504,Piloteadoras,,0,80509,0 -3560,22101505,Aplanadoras,Apisonadora,0,80509,0 -3561,22101507,Bateadoras,,0,80509,0 -3562,22101508,Máquinas para abrir zanjas,Excavadora de zanjas,0,80509,0 -3563,22101509,Retroexcavadoras,"Excavadora mixta, Retrocargadora",0,80509,0 -3564,22101511,Compactadores,,0,80509,0 -3565,22101513,Dragalíneas,Dragalina,0,80509,0 -3566,22101514,Dragas,,0,80509,0 -3567,22101516,Excavadoras de fosos,Perforadora de pozos,0,80509,0 -3568,22101518,Raspadores elevadores,,0,80509,0 -3569,22101519,Máquina giratoria con cazoleta de rastrillos abiertas,,0,80509,0 -3570,22101520,Máquina giratoria con rastrillos elevadores,,0,80509,0 -3571,22101521,Rastrilladora arrastrada,,0,80509,0 -3572,22101522,Buldóceres de orugas,,0,80509,0 -3573,22101523,Buldóceres de ruedas,,0,80509,0 -3574,22101524,Excavadoras móviles,,0,80509,0 -3575,22101525,Excavadoras de ruedas,,0,80509,0 -3576,22101526,Excavadoras de orugas,,0,80509,0 -3577,22101527,Transportes integrados de carga,,0,80509,0 -3578,22101528,Cargadores de ruedas,,0,80509,0 -3579,22101529,Cargadores sobre patines con dirección,,0,80509,0 -3580,22101530,Raspadores abiertos,,0,80509,0 -3581,22101531,Cañones para quitar nieve,,0,80509,0 -3582,22101532,Cargadores de orugas,Cargadores de cadenas,0,80509,0 -3583,22101533,Arrancadoras de troncos,,0,80509,0 -3584,22101534,Excavadoras de campaña,,0,80509,0 -3585,22101535,Extractor de pilotes,,0,80509,0 -3586,22101536,Lavador de llantas,,0,80509,0 -3587,22101537,Cortadora de pilotes de concreto,,0,80509,0 -3588,22101538,Quitanieves,,0,80509,0 -3589,22101600,Equipo de pavimentación,,0,80509,0 -3590,22101602,Equipo de apisonamiento,,0,80509,0 -3591,22101603,Ensanchadores de carreteras,,0,80509,0 -3592,22101604,Placas vibradoras,,0,80509,0 -3593,22101605,Acabadoras de asfalto,,0,80509,0 -3594,22101606,Esparcidoras de gravilla,,0,80509,0 -3595,22101607,Pavimentadoras,,0,80509,0 -3596,22101608,Aplanadoras en frío,,0,80509,0 -3597,22101609,Mezcladoras de material de pavimentación,,0,80509,0 -3598,22101610,Distribuidoras de agregados,,0,80509,0 -3599,22101611,Distribuidoras de material bituminoso,,0,80509,0 -3600,22101612,Escarificadoras de carreteras,,0,80509,0 -3601,22101613,Aplanadoras calentadoras de la superficie de la carretera,,0,80509,0 -3602,22101614,Revestimientos de pavimento de concreto,,0,80509,0 -3603,22101615,Trituradoras de pavimento,,0,80509,0 -3604,22101616,Máquinas para hacer andenes,,0,80509,0 -3605,22101617,Máquinas de relleno,,0,80509,0 -3606,22101618,Equipos de preparación de superficies de rodamiento o mecanismos para su colocación,,0,80509,0 -3607,22101619,Máquinas pulidoras,,0,80509,0 -3608,22101620,Máquinas de limpieza o acabado de juntas,,0,80509,0 -3609,22101621,Distribuidora de asfalto,,0,80509,0 -3610,22101622,Recicladora de asfalto,,0,80509,0 -3611,22101623,Máquina pavimentadora y de terminados,,0,80509,0 -3612,22101624,Máquina para marcar calles,,0,80509,0 -3613,22101625,Máquina pulidora de concreto,,0,80509,0 -3614,22101626,Planta mezcladora de asfalto,,0,80509,0 -3615,22101627,Fusor de asfalto,,0,80509,0 -3616,22101628,Lavadora de agregados,,0,80509,0 -3617,22101629,Secadora de agregados,,0,80509,0 -3618,22101630,Removedor de marcas de calles,,0,80509,0 -3619,22101631,Guía de piloteadora,,0,80509,0 -3620,22101700,Componentes de equipo pesado,,0,80509,0 -3621,22101701,Palas excavadoras,,0,80509,0 -3622,22101702,Palas mecánicas para el de movimiento de tierra o sus piezas o accesorios,,0,80509,0 -3623,22101703,Cuchillas o dientes u otros filos cortantes,,0,80509,0 -3624,22101704,Escarificadores,,0,80509,0 -3625,22101705,Orugas: eslabones o zapatas o sus piezas,"Orugas, eslabones o zapatas o sus piezas",0,80509,0 -3626,22101706,Cucharas de pala,,0,80509,0 -3627,22101707,Cables de retención,,0,80509,0 -3628,22101708,Extractores,,0,80509,0 -3629,22101709,Cucharones bivalvos,,0,80509,0 -3630,22101710,Remolques quitanieves,,0,80509,0 -3631,22101711,Herramientas de trituración de pavimento o accesorios,,0,80509,0 -3632,22101712,Herramientas de piloteadoras o sus piezas o accesorios,,0,80509,0 -3633,22101713,Brazo de retroexcavadora o secciones del brazo,,0,80509,0 -3634,22101714,Kits de reparación o piezas de apisonadora,,0,80509,0 -3635,22101715,Plantas o alimentadoras de dosificación,,0,80509,0 -3636,22101716,Kits de conversión de maquinaria de construcción,,0,80509,0 -3637,22101717,Vertederas de movimiento de tierra,,0,80509,0 -3638,22101718,Sistemas de control de aplanadora,,0,80509,0 -3639,22101719,Portaherramientas de corredera transversal de aplanadora,,0,80509,0 -3640,22101720,Zapatas perfiladoras de zanjadora,,0,80509,0 -3641,22101721,Brocas y accesorios de la hincadora de estacas,,0,80509,0 -3642,22101722,Cuchara dentada,Dientes del cucharón,0,80509,0 -3643,22101723,Cortador lateral de cuchara,,0,80509,0 -3644,22101724,Cincel cortador hidráulico,,0,80509,0 -3646,22101801,Manlift o elevador de personal,Plataforma elevadora,0,80509,0 -3647,22101802,Elevador de plataforma,,0,80509,0 -3648,22101803,Elevador de boom articulado,,0,80509,0 -3649,22101804,Elevador de boom telescópico,,0,80509,0 -3650,22101900,Maquinaria y accesorios de construcción de edificios,,0,80509,0 -3651,22101901,Mezcladoras o plantas de concreto,,0,80509,0 -3652,22101902,"Mezcladoras de argamasa, pañete o mortero",,0,80509,0 -3653,22101903,Mezcladoras de grada giratoria,,0,80509,0 -3654,22101904,Máquinas de curado,,0,80509,0 -3655,22101905,Distribuidoras de concreto,,0,80509,0 -3656,22101906,Equipo de apuntalamiento,,0,80509,0 -3657,22101907,Puntales de zanja,,0,80509,0 -3658,22101908,Drenaje de cartón o drenaje de pabilo,,0,80509,0 -3659,22102000,Equipo y maquinaria de demolición de edificios,,0,80509,0 -3660,22102001,Kits de equipo de demolición,,0,80509,0 -3661,22102002,Bola para demolición,Bola de derribo,0,80509,0 -3662,23101500,"Maquinaria para trabajar madera, piedra, cerámica y similares",,0,80509,0 -3663,23101501,Caladoras,Sierra de Vaivén,0,80509,0 -3664,23101502,Taladros,,0,80509,0 -3665,23101504,Máquinas dobladoras,,0,80509,0 -3666,23101506,Esmeriladoras,"Amoladora de banco, Esmeril de banco",0,80509,0 -3667,23101507,Troqueladoras,,0,80509,0 -3668,23101508,Cortadoras,,0,80509,0 -3669,23101509,Lijadoras,,0,80509,0 -3670,23101510,Pulidoras,,0,80509,0 -3671,23101511,Máquinas para tornear,,0,80509,0 -3672,23101512,Sierras mecánicas,,0,80509,0 -3673,23101513,Fresadoras,,0,80509,0 -3674,23101514,Cepilladoras,,0,80509,0 -3675,23101515,Máquinas para grabar,,0,80509,0 -3676,23101516,Máquina de martillar con la peña de cuentas de vidrio,,0,80509,0 -3677,23101517,Máquinas de sanblasteado,,0,80509,0 -3678,23101518,Máquina de picar con chorro de perdigones,,0,80509,0 -3679,23101519,Robots,,0,80509,0 -3680,23101520,Máquinas Ram de electro descarga,,0,80509,0 -3681,23101521,Máquina de descarga de electrodo de cátodo de alambre,,0,80509,0 -3682,23101522,Rompevirutas,,0,80509,0 -3683,23101525,Lijadora de huso oscilante,,0,80509,0 -3684,23101528,Lijadora de tambor,,0,80509,0 -3685,23101529,Rodillo esparcidor de goma,,0,80509,0 -3686,23101530,Máquina cola de milano,,0,80509,0 -3687,23101531,Máquina combinada de carpintería,,0,80509,0 -3688,23101532,Máquina espigadora,,0,80509,0 -3689,23101533,Prensa fría,,0,80509,0 -3690,23101534,Aplicador de banda de orillos,,0,80509,0 -3691,23101535,Máquina para unir espesores distintos,,0,80509,0 -3692,23101536,Máquina de clavijas,,0,80509,0 -3693,23101537,Lijadora de banda y disco,,0,80509,0 -3694,23101538,Desbastadora para carpintería,,0,80509,0 -3695,23111600,Maquinaria de hidrotratamiento,,0,80509,0 -3696,23111601,Hidrotratador de nafta,Hidrotratadora de nafta,0,80509,0 -3697,23111602,Hidrotratador de destilado,Hidrotratadora de destilado,0,80509,0 -3698,23111603,Hidrotratador de alimentación catalítica,Hidrotratadora de alimentación catalítica,0,80509,0 -3699,23111604,Hidrotratador de lubricantes,Hidrotratadora de lubricantes,0,80509,0 -3700,23111605,Hidrotratador de gasolina,Hidrotratadora de gasolina,0,80509,0 -3701,23111606,Hidrotratador de aceite residual,Hidrotratadora de aceite residual,0,80509,0 -3702,23121500,Maquinaria y accesorios para transformados textiles,,0,80509,0 -3703,23121501,Maquinaria para bordados,,0,80509,0 -3704,23121502,Maquinaria para afelpado,,0,80509,0 -3705,23121503,Devanadoras o encarretadoras,,0,80509,0 -3706,23121504,Máquinas de Torción,,0,80509,0 -3707,23121505,Máquina para pegar puntadas,,0,80509,0 -3708,23121506,Tejedoras,,0,80509,0 -3709,23121507,Urdidoras,,0,80509,0 -3710,23121508,Máquinas de acabados,,0,80509,0 -3711,23121509,Husos,,0,80509,0 -3712,23121510,Máquinas para hacer encaje,,0,80509,0 -3713,23121511,Máquinas para lavar textiles,,0,80509,0 -3714,23121512,Máquinas cardadoras,,0,80509,0 -3715,23121513,Enrolladoras de ensamble de hilaza,,0,80509,0 -3716,23121514,Cortadora de hilaza,,0,80509,0 -3717,23121515,Máquina levantadora,,0,80509,0 -3718,23121516,Máquina quemadora,,0,80509,0 -3719,23121517,Máquina enrolladora fina,,0,80509,0 -3720,23121518,Máquina para desenrollar hebras,,0,80509,0 -3721,23121519,Sistema de hilado de laboratorio,,0,80509,0 -3722,23121520,Máquina combadora,,0,80509,0 -3723,23121600,"Maquinaria, equipo y accesorios para trabajo textil",,0,80509,0 -3724,23121601,Máquinas para forrar botones,,0,80509,0 -3725,23121602,Máquinas para coser botones,,0,80509,0 -3726,23121603,Máquinas de hacer ojales,,0,80509,0 -3727,23121604,Máquinas cortadoras de tela,,0,80509,0 -3728,23121605,Máquinas para rellenar cojines,,0,80509,0 -3729,23121606,Máquinas plegadoras o rebobinadoras,,0,80509,0 -3730,23121607,Máquinas blanqueadoras,,0,80509,0 -3731,23121608,Máquinas para doblar telas o paños,,0,80509,0 -3732,23121609,Máquinas bobinadoras o desenrolladoras,,0,80509,0 -3733,23121610,Máquinas para teñir,,0,80509,0 -3734,23121611,Máquinas cortadoras o festoneadoras,,0,80509,0 -3735,23121612,Agujas para máquina de cose,,0,80509,0 -3736,23121613,Máquinas para procesar seda,,0,80509,0 -3737,23121614,Máquinas de coser,,0,80509,0 -3738,23121615,Mesas para cortar telas,,0,80509,0 -3739,23121616,Probadoras de tinturas,,0,80509,0 -3740,23131500,"Equipo y suministros de trituración, lijado y pulido",,0,80509,0 -3741,23131501,Compuestos abrasivos,,0,80509,0 -3742,23131502,Ruedas de fieltro,,0,80509,0 -3743,23131503,Ruedas de esmerilado,,0,80509,0 -3744,23131504,Compuestos para brillado,,0,80509,0 -3745,23131505,Cabezas de pulir,,0,80509,0 -3746,23131506,Ruedas para pulir,,0,80509,0 -3747,23131507,Tela para lijar,,0,80509,0 -3748,23131508,Tambores para lijar,,0,80509,0 -3749,23131509,Cilindros o brilladoras,,0,80509,0 -3750,23131510,Suministros o medios de cilindro,,0,80509,0 -3751,23131511,Pivotes de agua,,0,80509,0 -3752,23131512,Bandejas de agua,,0,80509,0 -3753,23131513,Bloques para lijar,,0,80509,0 -3754,23131514,Piedras montadas,,0,80509,0 -3755,23131515,Rectificadora de piedra de moler,,0,80509,0 -3756,23131600,Equipo y accesorios de facetaje,,0,80509,0 -3757,23131601,Accesorios para hacer facetas o pulir caras,,0,80509,0 -3758,23131602,Máquinas para hacer facetas o pulir caras,,0,80509,0 -3759,23131603,Pulidoras para hacer facetas o pulir caras,,0,80509,0 -3761,23131700,Equipo del cabbing,,0,80509,0 -3762,23131701,Accesorios para hacer cabuchones,Accesorios para hacer cabujones,0,80509,0 -3763,23131702,Cinturones para hacer cabuchones,Cinturones para hacer cabujones,0,80509,0 -3764,23131703,Discos para hacer cabuchones,Discos para hacer cabujones,0,80509,0 -3765,23131704,Máquinas para hacer cabuchones,Máquinas para hacer cabujones,0,80509,0 -3766,23141600,Maquinaria y accesorios para el preparado del cuero,,0,80509,0 -3767,23141601,Máquinas para descarnar cuero,,0,80509,0 -3768,23141602,Máquinas para curtir cuero,,0,80509,0 -3769,23141603,Máquinas para teñir cuero,,0,80509,0 -3770,23141604,Máquinas para desengrasar cuero,,0,80509,0 -3771,23141605,Prensadoras de cuero,,0,80509,0 -3772,23141606,Máquina escurridora,,0,80509,0 -3773,23141607,Impermeable,,0,80509,0 -3774,23141608,Máquina para quitar el pelo,,0,80509,0 -3775,23141700,Maquinaria y accesorios para trabajar y reparar cuero,,0,80509,0 -3776,23141701,Máquinas cortadoras de cuero,,0,80509,0 -3777,23141702,Máquinas ribeteadoras de cuero,,0,80509,0 -3778,23141703,Máquinas clavadoras de cuero,,0,80509,0 -3779,23141704,Equipo para talabartería,Equipo para guarnicionería,0,80509,0 -3780,23151500,"Maquinaria, equipo y suministros para la elaboración de caucho y plástico",,0,80509,0 -3781,23151501,Máquinas de moldeo por soplado,,0,80509,0 -3782,23151502,Máquinas revestidoras,,0,80509,0 -3783,23151503,Extrusoras,,0,80509,0 -3784,23151504,Máquinas de moldeo por inyección,,0,80509,0 -3785,23151506,Prensas de caucho o plástico,,0,80509,0 -3786,23151507,Máquinas termo modeladoras,,0,80509,0 -3787,23151508,Máquinas para moldear al vacío,,0,80509,0 -3788,23151509,Vulcanizadoras,,0,80509,0 -3789,23151510,Maquinaria para cortar plástico,,0,80509,0 -3790,23151511,Maquinaria para triturar plástico,,0,80509,0 -3791,23151512,Molinos de caucho o plástico,,0,80509,0 -3792,23151513,Moldes de extrusión de caucho o plástico,,0,80509,0 -3793,23151514,Moldes de inyección para plástico,,0,80509,0 -3794,23151515,Moldes de termoformar,,0,80509,0 -3795,23151516,Pasadores del eyector,,0,80509,0 -3796,23151517,Cilindro imd para decoración en molde,,0,80509,0 -3797,23151518,Equipo de moldeo por inmersión,,0,80509,0 -3798,23151519,Equipo de moldeado y procesamiento de uretano,,0,80509,0 -3799,23151520,Equipo de moldeado giratorio,,0,80509,0 -3800,23151600,"Maquinaria, equipo y suministros para la industria del cemento, cerámica y vidrio",,0,80509,0 -3801,23151601,Sopladores o secadores,,0,80509,0 -3802,23151602,Trituradoras,,0,80509,0 -3803,23151603,Máquinas de soldadura por fusión o de estirado de vidrio,,0,80509,0 -3804,23151604,Esmeriladoras p pulidoras,,0,80509,0 -3805,23151606,Máquinas moldeadoras de cemento o cerámica o vidrio o similar,,0,80509,0 -3806,23151607,Prensas,,0,80509,0 -3807,23151608,Tamices,,0,80509,0 -3808,23151609,Instrumento para soplar vidrio,,0,80509,0 -3809,23151610,Extractor micropipeta,,0,80509,0 -3810,23151700,"Maquinaria, equipo y suministros de la industria óptica",,0,80509,0 -3811,23151701,Máquinas para pulir lentes,,0,80509,0 -3812,23151702,Equipo de medición de lentes,,0,80509,0 -3813,23151703,Equipo de esmerilado de lentes,,0,80509,0 -3814,23151704,Equipos de ensayo de lentes,,0,80509,0 -3815,23151705,Equipo para recubrir el vacío óptico,,0,80509,0 -3816,23151800,"Maquinaria, equipo y suministros de la industria farmacéutica",,0,80509,0 -3817,23151801,Equipo para llenado de ampolletas,,0,80509,0 -3818,23151802,"Colocadores de tapas, insertadores de algodón o aplicadores de sellos de seguridad",,0,80509,0 -3819,23151803,Máquinas encapsuladoras,,0,80509,0 -3820,23151804,"Reactores, fermentadores o digestores",,0,80509,0 -3821,23151805,Máquinas dosificadoras de barrena para llenado o cerrado hermético,,0,80509,0 -3822,23151806,Filtros o ultrafiltros farmacéuticos,,0,80509,0 -3823,23151807,Secadores por congelación o liofilizadores,,0,80509,0 -3824,23151808,Granuladores,,0,80509,0 -3825,23151809,Máquinas farmacéuticas de tamizado,,0,80509,0 -3826,23151810,Máquinas de procesado o llenado estéril o aséptico,,0,80509,0 -3827,23151811,Máquinas para hacer ensayos de tabletas o cápsulas,,0,80509,0 -3828,23151812,Contadores de tabletas,,0,80509,0 -3829,23151813,Máquinas de hacer tabletas,,0,80509,0 -3830,23151814,Equipo de producción de vacunas,,0,80509,0 -3831,23151816,Columnas de cromatografía,,0,80509,0 -3832,23151817,Medios de cromatografía,,0,80509,0 -3833,23151818,Dispositivos de prueba de esterilidad,,0,80509,0 -3834,23151819,Comprobadores de integridad de los filtros,,0,80509,0 -3835,23151821,Adaptador de cartucho filtro,,0,80509,0 -3836,23151822,Adaptadores o conectores o accesorios para soportes de filtros farmacéuticos,,0,80509,0 -3837,23151823,Radiofármaco de diagnóstico,,0,80509,0 -3838,23151824,Equipos o accesorios para hacer compuestos farmacológicos,,0,80509,0 -3839,23151825,Unidad de tanque para filtrar y mezclar,,0,80509,0 -3840,23151900,"Maquinaria, equipo y suministros para la elaboración y el procesamiento de papel",,0,80509,0 -3841,23151901,Cortadores,,0,80509,0 -3842,23151902,Rajadores,,0,80509,0 -3843,23151903,Máquinas de lavado o para sacar el agua,,0,80509,0 -3844,23151904,Rebobinadoras,,0,80509,0 -3845,23151905,Máquinas para hacer pulpa de madera,,0,80509,0 -3846,23151906,Calandrias para hacer papel o cartón,,0,80509,0 -3847,23151907,Cedazo o tambor de pantalla,,0,80509,0 -3848,23151908,Refinador mecánico de pulpa,,0,80509,0 -3849,23151909,Máquina para hacer pulpa,,0,80509,0 -3850,23151910,Flotador,,0,80509,0 -3851,23151911,Máquina de papel,,0,80509,0 -3852,23152100,Maquinaria y equipo de separación,,0,80509,0 -3853,23152101,Pantallas o piezas o equipo vibratorios de separación,,0,80509,0 -3854,23152102,Pantallas o piezas o equipo estacionario de separación,,0,80509,0 -3855,23152103,Pantallas o piezas o equipo de clasificación neumática,,0,80509,0 -3856,23152104,Equipo o piezas y pantallas de separación centrífuga,,0,80509,0 -3857,23152105,Vibrador electromecánico,,0,80509,0 -3858,23152106,Pantalla vibratoria de separación de perfil uretano,,0,80509,0 -3859,23152107,Pantalla vibratoria de separación de alambre de perfil,,0,80509,0 -3860,23152108,Pantalla vibratoria de separación de la estampadora,,0,80509,0 -3861,23152109,Pantalla vibratoria de separación de la estampadora recubierta de uretano,,0,80509,0 -3862,23152110,Pantalla vibratoria de separación de alambre de uretano o caucho,,0,80509,0 -3864,23152112,Pantalla vibratoria de separación de alambre tejido recubierto de uretano,,0,80509,0 -3865,23152113,Tamiz vibratorio,,0,80509,0 -3866,23152200,Mesa y estantes para fabricación,,0,80509,0 -3867,23152201,Mesas rotatorias,,0,80509,0 -3868,23152202,Bancos de apilado,,0,80509,0 -3869,23152203,Bancos de pruebas de componentes o motores,,0,80509,0 -3870,23152204,Resguardo de la máquina,,0,80509,0 -3871,23152205,Mesas de sierra de banda,,0,80509,0 -3872,23152206,Protección de barrera,,0,80509,0 -3873,23152900,Maquinaria empacadora,,0,80509,0 -3874,23152901,Maquinaria de envolver,,0,80509,0 -3875,23152902,"Maquinaria de formar, rellenar o sellar",,0,80509,0 -3876,23152903,Empacadora al vacío,,0,80509,0 -3877,23152904,Tolvas para empacar,,0,80509,0 -3878,23152905,Máquinas para el formado de cartón,,0,80509,0 -3879,23152906,Maquinas para encintar,,0,80509,0 -3880,23152907,Suministros para maquinaria de empaque,,0,80509,0 -3881,23152908,Máquina para lavar botellas,,0,80509,0 -3882,23152909,Máquina para organizar materias primas,,0,80509,0 -3883,23152910,Máquina para hacer bolsas automáticas,,0,80509,0 -3884,23153000,"Dispositivos y sistemas de guía, posicionamiento y sujeción",,0,80509,0 -3885,23153001,Plantilla de medición,,0,80509,0 -3886,23153002,Plantilla de guía,,0,80509,0 -3887,23153003,Plantilla maestro,,0,80509,0 -3888,23153004,Plantilla de aguja,,0,80509,0 -3889,23153005,Plantilla de eje,,0,80509,0 -3890,23153006,Plantilla de chequeo,,0,80509,0 -3891,23153007,Plantilla de instalación,,0,80509,0 -3892,23153008,Plantilla de cámara,,0,80509,0 -3893,23153009,Plantilla de captación,,0,80509,0 -3894,23153010,Plantilla de eliminación,,0,80509,0 -3895,23153011,Plantilla de boquilla,,0,80509,0 -3896,23153012,Plantilla de deslizamiento,,0,80509,0 -3897,23153013,Plantilla de centrado,,0,80509,0 -3898,23153014,Plantilla de inspección,,0,80509,0 -3899,23153015,Plantilla de alimentador,,0,80509,0 -3900,23153016,Plantilla de embrague,,0,80509,0 -3901,23153017,Plantilla de alineación,,0,80509,0 -3902,23153018,Plantilla de posicionamiento,,0,80509,0 -3903,23153019,Placa elevadora,,0,80509,0 -3904,23153020,Guía de cinta,,0,80509,0 -3905,23153021,Mordaza de alimentación de cinta,,0,80509,0 -3906,23153022,Alimentador mecánico,,0,80509,0 -3907,23153023,Arnés del alimentador,,0,80509,0 -3908,23153024,Mordaza de alimentación,,0,80509,0 -3909,23153025,Agarrador mecánico,,0,80509,0 -3910,23153026,Mordaza de soporte,,0,80509,0 -3911,23153027,Conjunto de mordaza,,0,80509,0 -3912,23153028,Mordaza estacionaria,,0,80509,0 -3913,23153029,Bloque de plantilla,,0,80509,0 -3914,23153030,Guías de desplazamiento lineal,,0,80509,0 -3915,23153031,Plantillas de medición,,0,80509,0 -3916,23153032,Plantilla de anillo,,0,80509,0 -3917,23153033,Placa de garganta,,0,80509,0 -3918,23153034,Rieles de máquina,,0,80509,0 -3919,23153035,Placa,,0,80509,0 -3920,23153036,Placas de cerrojo,,0,80509,0 -3921,23153037,Conjunto de rodillos de alimentación,,0,80509,0 -3922,23153038,Caballete,,0,80509,0 -3923,23153039,Carro de riel de maquinado,,0,80509,0 -3924,23153100,Componentes y accesorios de maquinaria industrial,,0,80509,0 -3925,23153101,Tope de paso,,0,80509,0 -3926,23153103,Almohadilla de topa,,0,80509,0 -3927,23153129,Limpiador de la vía de máquina,,0,80509,0 -3928,23153130,Soportes de máquina o aisladores de vibración,,0,80509,0 -3929,23153131,Placas o barras o cintas de desgaste,,0,80509,0 -3930,23153132,Deflectores de polvo,,0,80509,0 -3931,23153133,Diente de rueda de cadena,,0,80509,0 -3932,23153134,Rodillos de avance o de arrastre,,0,80509,0 -3933,23153135,Cubiertas de rodillos de avance,,0,80509,0 -3934,23153136,Conductores de aserrín,,0,80509,0 -3935,23153137,Cubiertas guardapolvos de máquina,,0,80509,0 -3936,23153138,Cabezales de corte o desbastado,,0,80509,0 -3937,23153139,Camas guía,,0,80509,0 -3938,23153140,Brazos articulados,,0,80509,0 -3939,23153141,Alambre de máquina de electro descarga edm,,0,80509,0 -3940,23153142,Fuelles de acordeón,,0,80509,0 -3941,23153143,Boquillas de rociado,,0,80509,0 -3942,23153144,Bloque de rociado,,0,80509,0 -3943,23153145,Pistola de rociado,,0,80509,0 -3944,23153200,Robótica,,0,80509,0 -3945,23153201,Robots de pintura,,0,80509,0 -3946,23153202,Robots para levantar o poner,,0,80509,0 -3947,23153203,Robots de sellado adhesivo,,0,80509,0 -3948,23153204,Robots de soldadura,,0,80509,0 -3949,23153205,Robot para atender maquinaria,,0,80509,0 -3950,23153206,Robot para remover material,,0,80509,0 -3951,23153207,Robot de limpieza,,0,80509,0 -3952,23153400,Ensambladoras,,0,80509,0 -3953,23153401,Sistemas de aplicación de pegante o adhesivo,,0,80509,0 -3954,23153402,Accesorios de ensamblaje,,0,80509,0 -3955,23153403,Ensamblaje especializado,,0,80509,0 -3956,23153404,Sistemas de ensamblaje para vehículos de chasis (vo),,0,80509,0 -3957,23153405,Ensamblaje de componentes ilimitados,,0,80509,0 -3958,23153406,Líneas completas de tren motor,,0,80509,0 -3959,23153407,Dispositivo de montaje de superficies,,0,80509,0 -3960,23153408,Prueba de llenado,,0,80509,0 -3961,23153409,Multihusillo fijo de corredera roscada,,0,80509,0 -3962,23153410,Patines de carrocería,,0,80509,0 -3963,23153411,Inflado de montaje de llantas,,0,80509,0 -3964,23153412,Cizalla de guillotina,,0,80509,0 -3965,23153413,Aplicación de recepción (pu) de inserción de vidrio,,0,80509,0 -3966,23153414,Brazos articulados de movimiento giratorio,,0,80509,0 -3967,23153415,Acoplamiento automático del chasis,,0,80509,0 -3968,23153416,Componentes flexibles,,0,80509,0 -3969,23153417,Maquinas diversas para ensamble,,0,80509,0 -3970,23153418,Accesorios o herramientas par ensamble a la medida,,0,80509,0 -3971,23153419,Máquina de estación sencilla a la medida,,0,80509,0 -3972,23153420,Máquina para el ensamble de cables y alambres,,0,80509,0 -3973,23153421,Máquina torcedora o hebradora,,0,80509,0 -3974,23153500,Sistemas de pintura,,0,80509,0 -3975,23153501,Sistemas de aplicación de pintura,,0,80509,0 -3976,23153502,Caseta para reparación de pintura,,0,80509,0 -3977,23153503,Hornos de sistemas de pintura,,0,80509,0 -3978,23153504,Ingeniería o disposición de la planta de pintura,,0,80509,0 -3979,23153505,Sistema de pintura llave en mano,,0,80509,0 -3981,23153507,Taller de pintura variado,,0,80509,0 -3982,23153508,Sistemas diversos de pintura,,0,80509,0 -3983,23153600,Máquinas para el marcado de partes,,0,80509,0 -3984,23153601,Máquinas para el arcado al ácido,Máquinas de marcado ácido,0,80509,0 -3985,23153602,Maquinas para el marcado láser,,0,80509,0 -3986,23153603,Máquinas para el marcado pinstamp,,0,80509,0 -3987,23153604,Herramienta para el marcado láser,,0,80509,0 -3988,23153605,Máquina para el marcado con puntos,,0,80509,0 -3989,23153606,Máquina para el marcado por electro – grabado,,0,80509,0 -3990,23153607,Máquina para el marcado por chorro de tinta,,0,80509,0 -3991,23153608,Máquina para el marcado de tubos electrónicos,,0,80509,0 -3992,23153700,Equipo de aseguramiento o torque de precisión,,0,80509,0 -3993,23153701,Torquímetro de impacto,,0,80509,0 -3994,23153702,Torquímetro,,0,80509,0 -3995,23161500,Máquinas y equipo de fundición,,0,80509,0 -3996,23161501,Fuelles de fundición,,0,80509,0 -3997,23161502,Quemadores de fundición,,0,80509,0 -3998,23161503,Horno de secado del núcleo,,0,80509,0 -3999,23161506,Crisoles de fundición,,0,80509,0 -4000,23161507,Convertidores para fundición,,0,80509,0 -4001,23161510,Máquinas fundidoras,,0,80509,0 -4002,23161514,Prensas de dimensionamiento o grabado en relieve,,0,80509,0 -4003,23161516,Máquina de troqueles de fundición,,0,80509,0 -4004,23161517,Maquina moldeadora fundidora,,0,80509,0 -4005,23161600,Suministros de fundición,,0,80509,0 -4006,23161601,Fuelles de fundición,,0,80509,0 -4007,23161602,Arcillas de fundición,,0,80509,0 -4008,23161603,Frascos de fundición,,0,80509,0 -4009,23161605,Cucharas de fundición,,0,80509,0 -4010,23161606,Troqueles de fundición,,0,80509,0 -4011,23161607,Arena de fundición,,0,80509,0 -4012,23161608,Palas de fundición,,0,80509,0 -4013,23161700,Troqueles y herramientas de fundición,,0,80509,0 -4014,23161701,Caja para la formación de núcleos de arena,,0,80509,0 -4015,23161702,Patrón de formación de troqueles de arena,,0,80509,0 -4016,23181500,Maquinaria para la elaboración de alimentos,,0,80509,0 -4017,23181501,Maquinaria de llenado,,0,80509,0 -4018,23181502,Maquinaria de molido,,0,80509,0 -4019,23181504,Maquinaria de tamizado,,0,80509,0 -4020,23181505,Maquinaria para deshidratación,,0,80509,0 -4021,23181506,Maquinaria de lavado,,0,80509,0 -4022,23181507,Maquinaria de triturado,,0,80509,0 -4023,23181508,Máquinas blanqueadoras,,0,80509,0 -4024,23181509,Maquinaria de clasificación,,0,80509,0 -4025,23181510,Atadoras de carne,,0,80509,0 -4026,23181511,Conformadora,,0,80509,0 -4027,23181512,Enfriadora,,0,80509,0 -4028,23181513,Preespolvoreadora,,0,80509,0 -4029,23181514,Máquina para elaboración de queso,,0,80509,0 -4030,23181515,Máquina para elaborar tofu (queso de soya),,0,80509,0 -4031,23181516,Máquina para elaborar fideos,,0,80509,0 -4032,23181517,Máquina para elaborar frijol de soya fermentado,,0,80509,0 -4033,23181600,Maquinaria para corte de alimentos,,0,80509,0 -4034,23181601,Maquinaria para cortar en cubos,,0,80509,0 -4035,23181602,Máquina tajadora,,0,80509,0 -4036,23181603,Máquina picadora,,0,80509,0 -4037,23181604,Máquina cortadora,,0,80509,0 -4038,23181605,Máquina ralladora,,0,80509,0 -4039,23181606,Máquina peladora,,0,80509,0 -4040,23181700,Maquinaria para cocinar o ahumar alimentos,,0,80509,0 -4041,23181701,Máquina para ahumar,,0,80509,0 -4042,23181702,Maquinaria para asar,,0,80509,0 -4043,23181703,Maquinaria para cocinar,,0,80509,0 -4044,23181704,Máquina para cocinar al vapor,,0,80509,0 -4045,23181705,Máquina para esterilizar alimentos,,0,80509,0 -4046,23181800,Maquinaria industrial para elaboración de bebidas,,0,80509,0 -4047,23181801,Equipo y suministros para elaboración de café,,0,80509,0 -4048,23181802,Maquinaria para elaboración de zumos,,0,80509,0 -4049,23181803,Máquinas para hacer hielo,,0,80509,0 -4050,23181804,Máquinas para hacer helados,,0,80509,0 -4051,23181805,Partes y accesorios de máquinas para hacer hielo,,0,80509,0 -4052,23181806,Esterilizador de bebidas,,0,80509,0 -4053,23191000,Mezcladores por tandas,,0,80509,0 -4054,23191001,Mezcladora cambiadora,,0,80509,0 -4055,23191002,Mezcladora de cuchilla helicoidal,,0,80509,0 -4056,23191003,Mezcladora con doble brazo amasador,,0,80509,0 -4057,23191004,Mezcladoras intensivas,,0,80509,0 -4058,23191005,Mezcladoras de rodillo,,0,80509,0 -4059,23191006,Mezcladora de arena,,0,80509,0 -4060,23191100,Mezcladores continuos,,0,80509,0 -4061,23191101,Mezcladora de tornillo sencillo,,0,80509,0 -4062,23191102,Mezcladora de tornillo doble,,0,80509,0 -4063,23191200,Piezas de mezcladora y accesorios,,0,80509,0 -4064,23191201,Rastrillos de mezcladora,,0,80509,0 -4065,23191202,Cuchillas de mezcladora,,0,80509,0 -4066,23201000,Sistemas de contacto líquido gas,,0,80509,0 -4067,23201001,Columnas de placa,,0,80509,0 -4068,23201002,Columnas cargadas,,0,80509,0 -4069,23201003,Contacto de líquido disperso,,0,80509,0 -4070,23201004,Columna de pared mojada,,0,80509,0 -4071,23201005,Columnas de burbuja,,0,80509,0 -4072,23201006,Columna de destilación,,0,80509,0 -4073,23201007,Columna cargada de destilación,,0,80509,0 -4074,23201008,Bandeja de destilación,,0,80509,0 -4075,23201100,Absorción e intercambio de iones,,0,80509,0 -4076,23201101,Recipiente absorbente,,0,80509,0 -4077,23201102,Recipiente absorbente a presión ambiental,,0,80509,0 -4078,23201200,Equipo de secado industrial,,0,80509,0 -4079,23201201,Secadores atomizadores,,0,80509,0 -4080,23201202,Secadores de aire,,0,80509,0 -4081,23201203,Secadores de lecho fluido,,0,80509,0 -4082,23201204,Equipo para el secado de comida,,0,80509,0 -4083,23211000,Maquinaria de ensamblaje electrónico y equipo de soporte,,0,80509,0 -4084,23211001,Colocadores de chips,,0,80509,0 -4085,23211002,Maquinaria suministradora de pegamento,,0,80509,0 -4086,23211003,Maquina de inserción de terminales,,0,80509,0 -4087,23211100,Maquinaria de procesamiento y fabricación electrónica,,0,80509,0 -4088,23211101,Sistemas de procesamiento de semiconductores,,0,80509,0 -4089,23211102,Máquina para hacer tarjetas de circuitos impresos,,0,80509,0 -4090,23211103,Pegador de alambre al wafer,,0,80509,0 -4091,23211104,Monitor de inspección de chip semiconductor,,0,80509,0 -4092,23211105,Dispositivo para sellar porosidad o impregnación de vacío,,0,80509,0 -641,10215446,Azucena viva oriental fireball,Lili viva oriental fireball,0,80509,0 -642,10215447,Azucena viva oriental gluhwein,Lili viva oriental gluhwein,0,80509,0 -643,10215448,Azucena viva oriental goldband,Lili viva oriental goldband,0,80509,0 -644,10215449,Azucena viva oriental halifax,Lili viva oriental halifax,0,80509,0 -645,10215450,Azucena viva oriental kathryn,Lili viva oriental kathryn,0,80509,0 -646,10215451,Azucena viva oriental kyoto,Lili viva oriental kyoto,0,80509,0 -647,10215452,Azucena viva oriental la mancha,Lili viva oriental la mancha,0,80509,0 -648,10215453,Azucena viva oriental medusa,Lili viva oriental medusa,0,80509,0 -649,10215454,Azucena viva oriental montezuma,Lili viva oriental montezuma,0,80509,0 -650,10215455,Azucena viva oriental muscadet,Lili viva oriental muscadet,0,80509,0 -651,10215456,Azucena viva oriental nippon,Lili viva oriental nippon,0,80509,0 -652,10215457,Azucena viva oriental opus one,Lili viva oriental opus one,0,80509,0 -653,10215458,Azucena viva oriental pompeii,Lili viva oriental pompeii,0,80509,0 -654,10215459,Azucena viva oriental rialto,Lili viva oriental rialto,0,80509,0 -655,10215460,Azucena viva oriental robina,Lili viva oriental robina,0,80509,0 -656,10215461,Azucena viva oriental rousilon,Lili viva oriental rousilon,0,80509,0 -657,10215462,Azucena viva oriental siberia,Lili viva oriental siberia,0,80509,0 -658,10215463,Azucena viva oriental sorbonne,Lili viva oriental sorbonne,0,80509,0 -659,10215464,Azucena viva oriental starfighter,Lili viva oriental starfighter,0,80509,0 -660,10215465,Azucena viva oriental stargazer,Lili viva oriental stargazer,0,80509,0 -661,10215466,Azucena viva oriental sumatra,Lili viva oriental sumatra,0,80509,0 -662,10215467,Azucena viva oriental time out,Lili viva oriental time out,0,80509,0 -663,10215468,Azucena viva oriental tom pouche,Lili viva oriental tom pouche,0,80509,0 -664,10215469,Azucena viva oriental tropical,Lili viva oriental tropical,0,80509,0 -665,10215470,Azucena viva oriental white cup,Lili viva oriental white cup,0,80509,0 -666,10215471,Azucena viva oriental white merostar,Lili viva oriental white merostar,0,80509,0 -667,10215472,Azucena viva oriental white montana,Lili viva oriental white montana,0,80509,0 -668,10215473,Azucena viva oriental white stargazer,Lili viva oriental white stargazer,0,80509,0 -669,10215474,Azucena viva oriental yellow band,Lili viva oriental yellow band,0,80509,0 -670,10215475,Azucena viva oriental yellow dream,Lili viva oriental yellow dream,0,80509,0 -671,10215476,Azucena viva oriental yellow queen,Lili viva oriental yellow queen,0,80509,0 -672,10215477,Azucena viva oriental yellow star,Lili viva oriental yellow star,0,80509,0 -673,10215478,Azucena viva oriental yeloween,Lili viva oriental yelloween,0,80509,0 -674,10215479,Azucena viva ot red dutch,Lili viva ot red dutch,0,80509,0 -675,10215480,Azucena viva sonata nimph,Lili viva sonata nimph,0,80509,0 -676,10215481,Azucena viva sonata shocking,Lili viva sonata shocking,0,80509,0 -677,10215482,Azucena viva sonata triumphateer,Lili viva sonata triumphateer,0,80509,0 -678,10215483,Azucena viva sunset longiflorum e híbrido asiática,Lili viva sunset longiflorum e híbrido asiática,0,80509,0 -679,10215484,Azucena viva de agua,Lili viva de agua,0,80509,0 -680,10215600,Lisianthus vivo,Eustomas vivas,0,80509,0 -681,10215601,Lisianthus vivo crema,Eustoma viva crema,0,80509,0 -682,10215602,Lisianthus vivo rosado fuerte,Eustoma viva rosado fuerte,0,80509,0 -683,10215603,Lisianthus vivo verde,Eustoma viva verde,0,80509,0 -684,10215604,Lisianthus vivo lavanda,Eustoma viva lavanda,0,80509,0 -685,10215605,Lisianthus vivo rosado claro,Eustoma viva rosado claro,0,80509,0 -686,10215606,Lisianthus vivo mini blanco,Eustoma viva mini blanco,0,80509,0 -687,10215607,Lisianthus vivo durazno,Eustoma viva durazno,0,80509,0 -688,10215608,Lisianthus vivo rosado con borde blanco,Eustoma viva rosado con borde blanco,0,80509,0 -689,10215609,Lisianthus vivo púrpura,Eustoma viva púrpura,0,80509,0 -690,10215610,Lisianthus vivo púrpura con borde blanco,Eustoma viva púrpura con borde blanco,0,80509,0 -691,10215611,Lisianthus vivo blanco con borde rosado,Eustoma viva blanco con borde rosado,0,80509,0 -692,10215612,Lisianthus vivo blanco,Eustoma viva blanco,0,80509,0 -693,10215613,Lisianthus vivo blanco con borde púrpura,Eustoma viva blanco con borde púrpura,0,80509,0 -694,10216100,Estrella de Belén viva,,0,80509,0 -695,10216101,Estrella de belén viva arábica,,0,80509,0 -696,10216102,Estrella de belén viva dubium anaranjada,,0,80509,0 -697,10216103,Estrella de belén viva umbellada,,0,80509,0 -698,10216104,Estrella de belén viva dubium blanca,,0,80509,0 -699,10216105,Estrella de belén viva dubium amarilla,,0,80509,0 -700,10216500,Escabiosa viva,Flores del acerico vivas,0,80509,0 -701,10216501,Escabiosa viva anual,Flor del acerico viva anual,0,80509,0 -702,10216502,Escabiosa viva negra,Flor del acerico viva negra,0,80509,0 -703,10216503,Escabiosa viva caucásica azul,Flor del acerico viva caucásica azul,0,80509,0 -704,10216504,Escabiosa viva caucásica rosada,Flor del acerico viva caucásica rosada,0,80509,0 -705,10216505,Escabiosa viva caucásica vainas,Flor del acerico viva caucásica vainas,0,80509,0 -706,10216506,Escabiosa viva caucásica blanca,Flor del acerico viva caucásica blanca,0,80509,0 -707,10216507,Escabiosa viva fresa,Flor del acerico viva fresa,0,80509,0 -708,10216800,Romero del pantano viva,Siemprevivas azules vivas,0,80509,0 -709,10216801,Romero del pantano viva azul,Siempreviva azul viva color azul,0,80509,0 -710,10216802,Romero del pantano viva lavanda,Siempreviva azul viva lavanda,0,80509,0 -711,10216803,Romero del pantano viva durazno,Siempreviva azul viva durazno,0,80509,0 -712,10216804,Romero del pantano viva rosada,Siempreviva azul viva rosada,0,80509,0 -713,10216805,Romero del pantano viva púrpura,Siempreviva azul viva púrpura,0,80509,0 -714,10216806,Romero del pantano viva espuma de mar,Siempreviva azul viva espuma de mar,0,80509,0 -715,10216807,Romero del pantano viva blanca,Siempreviva azul viva blanca,0,80509,0 -716,10216808,Romero del pantano viva amarilla,Siempreviva azul viva amarilla,0,80509,0 -717,10216900,Matiola viva,Alhelís vivas,0,80509,0 -718,10216901,Matiola viva apricot,Alhelí viva apricot,0,80509,0 -719,10216902,Matiola viva crema,Alhelí viva crema,0,80509,0 -720,10216903,Matiola viva fucsia,Alhelí viva fucsia,0,80509,0 -721,10216904,Matiola viva lavanda,Alhelí viva lavanda,0,80509,0 -722,10216905,Matiola viva lavanda claro,Alhelí viva lavanda claro,0,80509,0 -724,10216907,Matiola viva púrpura,Alhelí viva púrpura,0,80509,0 -725,10216908,Matiola viva rojo rubí,Alhelí viva rojo rubí,0,80509,0 -726,10216909,Matiola viva enamorada rosda,Alhelí viva enamorada rosada,0,80509,0 -727,10216910,Matiola viva blanca,Alhelí viva blanca,0,80509,0 -728,10217400,Flor de cera viva,,0,80509,0 -729,10217401,Flor de cera viva alba,,0,80509,0 -730,10217402,Flor de cera viva bicolor,,0,80509,0 -731,10217403,Flor de cera viva chichilla,,0,80509,0 -732,10217404,Flor de cera viva reina danzante,,0,80509,0 -733,10217405,Flor de cera viva dinamarca,,0,80509,0 -734,10217406,Flor de cera viva denmar,,0,80509,0 -735,10217407,Flor de cera viva híbrida pastel flor gema,,0,80509,0 -736,10217408,Flor de cera viva híbrida rosada flor gem,,0,80509,0 -737,10217409,Flor de cera viva híbrida blanca rubia,,0,80509,0 -738,10217410,Flor de cera viva híbrida eric john,,0,80509,0 -739,10217411,Flor de cera viva híbrida dama pintada,,0,80509,0 -740,10217412,Flor de cera viva híbrida revelación,,0,80509,0 -741,10217413,Flor de cera viva híbrida bola de nieve,,0,80509,0 -742,10217414,Flor de cera viva juriens brook,,0,80509,0 -743,10217415,Flor de cera viva lady stephanie rosada,,0,80509,0 -744,10217416,Flor de cera viva madonna,,0,80509,0 -745,10217417,Flor de cera viva mini blanca,,0,80509,0 -746,10217418,Flor de cera viva anaranjada,,0,80509,0 -747,10217419,Flor de cera viva perla,,0,80509,0 -748,10217420,Flor de cera viva pixie moon,,0,80509,0 -749,10217421,Flor de cera viva orgullo púrpura,,0,80509,0 -750,10217422,Flor de cera viva roja,,0,80509,0 -751,10217423,Flor de cera viva wanaroo,,0,80509,0 -752,10217424,Flor de cera viva amarilla,,0,80509,0 -753,10221500,Agapanto vivo,Flor del amor viva,0,80509,0 -754,10221501,Agapanto vivo azul,Flor del amor viva azul,0,80509,0 -755,10221502,Agapanto vivo blanco,Flor del amor viva blanco,0,80509,0 -756,10222700,Catalea viva,Calatheas vivas,0,80509,0 -757,10222701,Calatea viva cigarro,Calathea viva cigarro,0,80509,0 -758,10222702,Calatea hielo verde,Calathea viva hielo verde,0,80509,0 -759,10222703,Calatea serpiente cascabel,Calathea viva serpiente cascabel,0,80509,0 -760,10223800,Manzanilla viva,,0,80509,0 -761,10223801,Manzanilla viva sencilla vegmo,,0,80509,0 -762,10223802,Manzanilla viva doble blanca,,0,80509,0 -763,10223803,Manzanilla viva bola de nieve,,0,80509,0 -764,10223804,Manzanilla viva blanca,,0,80509,0 -765,10224900,Maraca viva,,0,80509,0 -766,10224901,Maraca viva marrón,,0,80509,0 -767,10224902,Maraca viva shampoo ginger,,0,80509,0 -768,10226100,Sello de salomón (polygonato) viva,,0,80509,0 -769,10226101,Sello de salomón (polygonato) falso viva,,0,80509,0 -770,10226102,Sello de salomón (polygonato) variegado viva,,0,80509,0 -771,10241500,Clavel vivo de floración sencilla,,0,80509,0 -772,10241501,Clavel vivo de floración sencilla burgundy bicolor,,0,80509,0 -773,10241502,Clavel vivo de floración sencilla burgundy,,0,80509,0 -774,10241503,Clavel vivo de floración sencilla cinderella,,0,80509,0 -775,10241504,Clavel vivo de floración sencilla crema bicolor,,0,80509,0 -776,10241505,Clavel vivo de floración sencilla crema,,0,80509,0 -777,10241506,Clavel vivo de floración sencilla verde o prado,,0,80509,0 -778,10241507,Clavel vivo de floración sencilla rosado fuerte,,0,80509,0 -779,10241508,Clavel vivo de floración sencilla verde claro,,0,80509,0 -780,10241509,Clavel vivo de floración sencilla rosado claro,,0,80509,0 -781,10241510,Clavel vivo de floración sencilla anaranjado bicolor,,0,80509,0 -782,10241511,Clavel vivo de floración sencilla anaranjado,,0,80509,0 -783,10241512,Clavel vivo de floración sencilla durazno,,0,80509,0 -784,10241513,Clavel vivo de floración sencilla menta bicolor,,0,80509,0 -785,10241514,Clavel vivo de floración sencilla rosado bicolor,,0,80509,0 -786,10241515,Clavel vivo de floración sencilla rosado,,0,80509,0 -787,10241516,Clavel vivo de floración sencilla púrpura bicolor,,0,80509,0 -788,10241517,Clavel vivo de floración sencilla rojo bicolor,,0,80509,0 -789,10241518,Clavel vivo de floración sencilla rojo,,0,80509,0 -790,10241519,Clavel vivo de floración sencilla blanco,,0,80509,0 -791,10241520,Clavel vivo de floración sencilla amarillo,,0,80509,0 -792,10252000,Orquídea phalaenopsis viva,,0,80509,0 -793,10252001,Orquídea phalaenopsis viva amabilis,,0,80509,0 -794,10252002,Orquídea phalaenopsis viva amboinensis,,0,80509,0 -795,10252003,Orquídea phalaenopsis viva afrodita,,0,80509,0 -796,10252004,Orquídea phalaenopsis viva appendiculata,,0,80509,0 -797,10252005,Orquídea phalaenopsis viva bastianii,,0,80509,0 -798,10252006,Orquídea phalaenopsis viva bellina,,0,80509,0 -799,10252007,Orquídea phalaenopsis viva borneensis,,0,80509,0 -800,10252008,Orquídea phalaenopsis viva braceana,,0,80509,0 -801,10252009,Orquídea phalaenopsis viva buyssoniana,,0,80509,0 -802,10252010,Orquídea phalaenopsis viva celebensis,,0,80509,0 -803,10252011,Orquídea phalaenopsis viva chibae,,0,80509,0 -804,10252012,Orquídea phalaenopsis viva cochlearis,,0,80509,0 -805,10252013,Orquídea phalaenopsis viva corningiana,,0,80509,0 -806,10252014,Orquídea phalaenopsis viva cornu-cervi,,0,80509,0 -807,10252015,Orquídea phalaenopsis viva deliciosa,,0,80509,0 -808,10252016,Orquídea phalaenopsis viva doweryënsis,,0,80509,0 -809,10252017,Orquídea phalaenopsis viva equestris,,0,80509,0 -810,10252018,Orquídea phalaenopsis viva fasciata,,0,80509,0 -811,10252019,Orquídea phalaenopsis viva fimbriata,,0,80509,0 -812,10252020,Orquídea phalaenopsis viva floresensis,,0,80509,0 -813,10252021,Orquídea phalaenopsis viva fuscata,,0,80509,0 -814,10252022,Orquídea phalaenopsis viva gibbosa,,0,80509,0 -815,10252023,Orquídea phalaenopsis viva hainanensis,,0,80509,0 -816,10252024,Orquídea phalaenopsis viva hieroglyphica,,0,80509,0 -817,10252025,Orquídea phalaenopsis viva honghenensis,,0,80509,0 -818,10252026,Orquídea phalaenopsis viva inscriptiosinensis,,0,80509,0 -819,10252027,Orquídea phalaenopsis viva javanica,,0,80509,0 -820,10252028,Orquídea phalaenopsis viva kunstleri,,0,80509,0 -821,10252029,Orquídea phalaenopsis viva lamelligera,,0,80509,0 -822,10252030,Orquídea phalaenopsis viva lindenii,,0,80509,0 -823,10252031,Orquídea phalaenopsis viva lobbii,,0,80509,0 -824,10252032,Orquídea phalaenopsis viva lowii,,0,80509,0 -825,10252033,Orquídea phalaenopsis viva lueddemanniana,,0,80509,0 -827,10252035,Orquídea phalaenopsis viva luteola,,0,80509,0 -828,10252036,Orquídea phalaenopsis viva maculata,,0,80509,0 -829,10252037,Orquídea phalaenopsis viva malipoensis,,0,80509,0 -830,10252038,Orquídea phalaenopsis viva mannii,,0,80509,0 -831,10252039,Orquídea phalaenopsis viva mariae,,0,80509,0 -832,10252040,Orquídea phalaenopsis viva micholitzii,,0,80509,0 -833,10252041,Orquídea phalaenopsis viva modesta,,0,80509,0 -834,10252042,Orquídea phalaenopsis viva mysorensis,,0,80509,0 -835,10252043,Orquídea phalaenopsis viva pallens,,0,80509,0 -836,10252044,Orquídea phalaenopsis viva pantherina,,0,80509,0 -837,10252045,Orquídea phalaenopsis viva parishii,,0,80509,0 -838,10252046,Orquídea phalaenopsis viva petelotii,,0,80509,0 -839,10252047,Orquídea phalaenopsis viva philippinensis,,0,80509,0 -840,10252048,Orquídea phalaenopsis viva pulcherrima,,0,80509,0 -841,10252049,Orquídea phalaenopsis viva pulchra,,0,80509,0 -842,10252050,Orquídea phalaenopsis viva regnieriana,,0,80509,0 -843,10252051,Orquídea phalaenopsis viva reichenbachiana,,0,80509,0 -844,10252052,Orquídea phalaenopsis viva nivacolor,,0,80509,0 -845,10252053,Orquídea phalaenopsis viva sanderiana,,0,80509,0 -846,10252054,Orquídea phalaenopsis viva schilleriana,,0,80509,0 -847,10252055,Orquídea phalaenopsis viva speciosa,,0,80509,0 -848,10252056,Orquídea phalaenopsis viva stobartiana,,0,80509,0 -849,10252057,Orquídea phalaenopsis viva stuartiana,,0,80509,0 -850,10252058,Orquídea phalaenopsis viva sumatrana,,0,80509,0 -851,10252059,Orquídea phalaenopsis viva taenialis,,0,80509,0 -852,10252060,Orquídea phalaenopsis viva tetraspis,,0,80509,0 -853,10252061,Orquídea phalaenopsis viva venosa,,0,80509,0 -854,10252062,Orquídea phalaenopsis viva violácea,,0,80509,0 -855,10252063,Orquídea phalaenopsis viva viridis,,0,80509,0 -856,10252064,Orquídea phalaenopsis viva wilsonii,,0,80509,0 -857,10252065,Orquídea phalaenopsis viva zebrina,,0,80509,0 -858,10252067,Orquídea phalaenopsis viva labio lavanda,,0,80509,0 -859,10302400,Rosas cortadas frescas rojos o burgundy,,0,80509,0 -860,10302401,Rosal cortado fresco african dawn,,0,80509,0 -861,10302402,Rosal cortado fresco amada,,0,80509,0 -862,10302403,Rosal cortado fresco black baccara,,0,80509,0 -863,10302404,Rosal cortado fresco black beauty,,0,80509,0 -864,10302405,Rosal cortado fresco black finess o black magic,,0,80509,0 -865,10302406,Rosal cortado fresco black magic,,0,80509,0 -866,10302407,Rosal cortado fresco bohemian o pasarela,,0,80509,0 -867,10302408,Rosal cortado fresco breathless,,0,80509,0 -868,10302409,Rosal cortado fresco caballero,,0,80509,0 -869,10302410,Rosal cortado fresco carrera,,0,80509,0 -870,10302411,Rosal cortado fresco charlene,,0,80509,0 -871,10302412,Rosal cortado fresco charlotte,,0,80509,0 -872,10302413,Rosal cortado fresco cherry lady,,0,80509,0 -873,10302414,Rosal cortado fresco cherry love,,0,80509,0 -874,10302415,Rosal cortado fresco classy rose,,0,80509,0 -875,10302416,Rosal cortado fresco colorado velvet,,0,80509,0 -876,10302417,Rosal cortado fresco corazón,,0,80509,0 -877,10302418,Rosal cortado fresco corrida,,0,80509,0 -878,10302419,Rosal cortado fresco dynamite,,0,80509,0 -879,10302420,Rosal cortado fresco eurored,,0,80509,0 -880,10302421,Rosal cortado fresco fashion,,0,80509,0 -881,10302422,Rosal cortado fresco fire and ice,,0,80509,0 -882,10302423,Rosal cortado fresco first red,,0,80509,0 -883,10302424,Rosal cortado fresco forever young,,0,80509,0 -884,10302425,Rosal cortado fresco freedom,,0,80509,0 -885,10302426,Rosal cortado fresco freestyle,,0,80509,0 -886,10302427,Rosal cortado fresco friendship,,0,80509,0 -887,10302428,Rosal cortado fresco gospel,,0,80509,0 -888,10302429,Rosal cortado fresco graffity,,0,80509,0 -889,10302430,Rosal cortado fresco grand gala,,0,80509,0 -890,10302431,Rosal cortado fresco grand prix,,0,80509,0 -891,10302432,Rosal cortado fresco grand classe,,0,80509,0 -892,10302433,Rosal cortado fresco hearts,,0,80509,0 -893,10302434,Rosal cortado fresco heat,,0,80509,0 -894,10302435,Rosal cortado fresco hocus pocus,,0,80509,0 -895,10302436,Rosal cortado fresco lady in red,,0,80509,0 -896,10302437,Rosal cortado fresco latin lady,,0,80509,0 -897,10302438,Rosal cortado fresco legend,,0,80509,0 -898,10302439,Rosal cortado fresco lulu,,0,80509,0 -899,10302440,Rosal cortado fresco luna rossa,,0,80509,0 -900,10302441,Rosal cortado fresco luxor,,0,80509,0 -901,10302442,Rosal cortado fresco madame delbard o carola,,0,80509,0 -902,10302443,Rosal cortado fresco miss paris,,0,80509,0 -903,10302444,Rosal cortado fresco nicole,,0,80509,0 -904,10302445,Rosal cortado fresco night fever,,0,80509,0 -905,10302446,Rosal cortado fresco obsession,,0,80509,0 -906,10302447,Rosal cortado fresco opium,,0,80509,0 -907,10302448,Rosal cortado fresco paz,,0,80509,0 -908,10302449,Rosal cortado fresco preference,,0,80509,0 -909,10302450,Rosal cortado fresco red berlin,,0,80509,0 -910,10302451,Rosal cortado fresco red bull,,0,80509,0 -911,10302452,Rosal cortado fresco red calypso,,0,80509,0 -912,10302453,Rosal cortado fresco red diamond,,0,80509,0 -913,10302454,Rosal cortado fresco red fantasy,,0,80509,0 -914,10302455,Rosal cortado fresco red france,,0,80509,0 -915,10302456,Rosal cortado fresco red intuition,,0,80509,0 -916,10302457,Rosal cortado fresco red jewel,,0,80509,0 -917,10302458,Rosal cortado fresco red magic,,0,80509,0 -918,10302459,Rosal cortado fresco red one,,0,80509,0 -919,10302460,Rosal cortado fresco red paris,,0,80509,0 -920,10302461,Rosal cortado fresco red princess,,0,80509,0 -921,10302462,Rosal cortado fresco red sensation o colorad,,0,80509,0 -922,10302463,Rosal cortado fresco red unique,,0,80509,0 -923,10302464,Rosal cortado fresco rockefeller,,0,80509,0 -924,10302465,Rosal cortado fresco romeo,,0,80509,0 -925,10302466,Rosal cortado fresco rouge baiser,,0,80509,0 -926,10302467,Rosal cortado fresco roulette,,0,80509,0 -927,10302468,Rosal cortado fresco royal massai,,0,80509,0 -928,10302469,Rosal cortado fresco royal red,,0,80509,0 -929,10302470,Rosal cortado fresco samurai,,0,80509,0 -930,10302471,Rosal cortado fresco sexy red,,0,80509,0 -931,10302472,Rosal cortado fresco starfire,,0,80509,0 -932,10302473,Rosal cortado fresco tango,,0,80509,0 -933,10302474,Rosal cortado fresco tiger tail,,0,80509,0 -935,10302476,Rosal cortado fresco top secret,,0,80509,0 -936,10302477,Rosal cortado fresco vital,,0,80509,0 -937,10302478,Rosal cortado fresco wisdom,,0,80509,0 -938,10302479,Rosal cortado fresco xantia,,0,80509,0 -939,10302480,Rosal cortado fresco xcite,,0,80509,0 -940,10302700,Rosas cortadas frescas amarillos,,0,80509,0 -941,10302701,Rosal cortado fresco aalsmeer gold,,0,80509,0 -942,10302702,Rosal cortado fresco alina,,0,80509,0 -943,10302703,Rosal cortado fresco ambiance,,0,80509,0 -944,10302704,Rosal cortado fresco aquarel,,0,80509,0 -945,10302705,Rosal cortado fresco autumn,,0,80509,0 -946,10302706,Rosal cortado fresco brasil,,0,80509,0 -947,10302707,Rosal cortado fresco candle light,,0,80509,0 -948,10302708,Rosal cortado fresco cantata o cantate,,0,80509,0 -949,10302709,Rosal cortado fresco capriccio,,0,80509,0 -950,10302710,Rosal cortado fresco caribbean,,0,80509,0 -951,10302711,Rosal cortado fresco circus,,0,80509,0 -952,10302712,Rosal cortado fresco citran,,0,80509,0 -953,10302713,Rosal cortado fresco concorde,,0,80509,0 -954,10302714,Rosal cortado fresco conga,,0,80509,0 -955,10302715,Rosal cortado fresco deja vu,,0,80509,0 -956,10302716,Rosal cortado fresco desire,,0,80509,0 -957,10302717,Rosal cortado fresco donia sol,,0,80509,0 -958,10302718,Rosal cortado fresco dueto,,0,80509,0 -959,10302719,Rosal cortado fresco erin,,0,80509,0 -960,10302720,Rosal cortado fresco exotic curiosa,,0,80509,0 -961,10302721,Rosal cortado fresco feria,,0,80509,0 -962,10302722,Rosal cortado fresco fire bird,,0,80509,0 -963,10302723,Rosal cortado fresco florida,,0,80509,0 -964,10302724,Rosal cortado fresco friendly,,0,80509,0 -965,10302725,Rosal cortado fresco gallinda,,0,80509,0 -966,10302726,Rosal cortado fresco geisha,,0,80509,0 -967,10302727,Rosal cortado fresco gelbe,,0,80509,0 -968,10302728,Rosal cortado fresco gelosia o yellow flame,,0,80509,0 -969,10302729,Rosal cortado fresco gold rush,,0,80509,0 -970,10302730,Rosal cortado fresco gold star,,0,80509,0 -971,10302731,Rosal cortado fresco gold strike,,0,80509,0 -972,10302732,Rosal cortado fresco golda,,0,80509,0 -973,10302733,Rosal cortado fresco golden fashion,,0,80509,0 -974,10302734,Rosal cortado fresco golden gate,,0,80509,0 -975,10302735,Rosal cortado fresco gran dorado,,0,80509,0 -976,10302736,Rosal cortado fresco helio,,0,80509,0 -977,10302737,Rosal cortado fresco high and exotic,,0,80509,0 -978,10302738,Rosal cortado fresco high and yellow,,0,80509,0 -979,10302739,Rosal cortado fresco high and yellow magic,,0,80509,0 -980,10302740,Rosal cortado fresco high society,,0,80509,0 -981,10302741,Rosal cortado fresco hummer,,0,80509,0 -982,10302742,Rosal cortado fresco idole o elle,,0,80509,0 -983,10302743,Rosal cortado fresco inti,,0,80509,0 -984,10302744,Rosal cortado fresco jet set,,0,80509,0 -985,10302745,Rosal cortado fresco judy,,0,80509,0 -986,10302746,Rosal cortado fresco jupiter,,0,80509,0 -987,10302747,Rosal cortado fresco konfetti,,0,80509,0 -988,10302748,Rosal cortado fresco kyara o kira,,0,80509,0 -989,10302749,Rosal cortado fresco latin beauty,,0,80509,0 -990,10302750,Rosal cortado fresco latin spirit,,0,80509,0 -991,10302751,Rosal cortado fresco latina,,0,80509,0 -992,10302752,Rosal cortado fresco lina,,0,80509,0 -993,10302753,Rosal cortado fresco lindsey,,0,80509,0 -994,10302754,Rosal cortado fresco male,,0,80509,0 -995,10302755,Rosal cortado fresco marie clare,,0,80509,0 -996,10302756,Rosal cortado fresco marisa,,0,80509,0 -997,10302757,Rosal cortado fresco matchball,,0,80509,0 -998,10302758,Rosal cortado fresco melon,,0,80509,0 -999,10302759,Rosal cortado fresco mohana,,0,80509,0 -1000,10302760,Rosal cortado fresco okie dokie,,0,80509,0 -1001,10302761,Rosal cortado fresco pailine,,0,80509,0 -1002,10302762,Rosal cortado fresco parrot,,0,80509,0 -1003,10302763,Rosal cortado fresco rio d oro,,0,80509,0 -1004,10302764,Rosal cortado fresco salami,,0,80509,0 -1005,10302765,Rosal cortado fresco santa fe,,0,80509,0 -1006,10302766,Rosal cortado fresco skyline,,0,80509,0 -1007,10302767,Rosal cortado fresco sonrisa,,0,80509,0 -1008,10302768,Rosal cortado fresco star ambiance,,0,80509,0 -1009,10302769,Rosal cortado fresco starbust,,0,80509,0 -1010,10302770,Rosal cortado fresco sun king,,0,80509,0 -1011,10302771,Rosal cortado fresco sunmaster,,0,80509,0 -1012,10302772,Rosal cortado fresco sunny milva,,0,80509,0 -1013,10302773,Rosal cortado fresco sushi,,0,80509,0 -1014,10302774,Rosal cortado fresco tabasco,,0,80509,0 -1015,10302775,Rosal cortado fresco tara,,0,80509,0 -1016,10302776,Rosal cortado fresco tresor 2000,,0,80509,0 -1017,10302777,Rosal cortado fresco ooty,,0,80509,0 -1018,10302778,Rosal cortado fresco yellow coral,,0,80509,0 -1019,10302779,Rosal cortado fresco yellow finess,,0,80509,0 -1020,10302780,Rosal cortado fresco yellow submarine,,0,80509,0 -1021,10302781,Rosal cortado fresco yellow sunset,,0,80509,0 -1022,10302782,Rosal cortado fresco yelllow timeless,,0,80509,0 -1023,10311800,Amaranto cortado fresco,,0,80509,0 -1024,10311801,Amaranto cortado fresco verde colgante,,0,80509,0 -1025,10311802,Amaranto cortado fresco rojo colgante,,0,80509,0 -1026,10311803,Amaranto cortado fresco bronce erguido,,0,80509,0 -1027,10311804,Amaranto cortado fresco verde erguido,,0,80509,0 -1028,10311805,Amaranto cortado fresco rojo erguido,,0,80509,0 -1029,10312100,Asclepia cortada fresca,Algodoncillos cortados frescos,0,80509,0 -1030,10312101,Asclepia cortada fresca lavanda,Algodoncillo cortado fresco lavanda,0,80509,0 -1031,10312102,Asclepia cortada fresca moby dick,Algodoncillo cortado fresco moby dick,0,80509,0 -1032,10312103,Asclepia cortada fresca tuberosa,Algodoncillo cortado fresco tuberosa,0,80509,0 -1033,10312104,Asclepia cortada fresca blanca,Algodoncillo cortado fresco blanca,0,80509,0 -1034,10312200,Aster cortada fresca,Estrellas cortadas frescas,0,80509,0 -1035,10312201,Aster cortada fresca beauty,Estrella cortada fresca beauty,0,80509,0 -1036,10312202,Aster cortada fresca japonés azul,Estrella cortada fresca japonés azul,0,80509,0 -1037,10312203,Aster cortada fresca japonés verde,Estrella cortada fresca japonés verde,0,80509,0 -1038,10312204,Aster cortada fresca japonés rosado fuerte,Estrella cortada fresca japonés rosado fuerte,0,80509,0 -1039,10312205,Aster cortada fresca japonés lavanda,Estrella cortada fresca japonés lavanda,0,80509,0 -1041,10312207,Aster cortada fresca japonés durazno,Estrella cortada fresca japonés durazno,0,80509,0 -1042,10312208,Aster cortada fresca japonés rosado,Estrella cortada fresca japonés rosado,0,80509,0 -1043,10312209,Aster cortada fresca japonés púrpura,Estrella cortada fresca japonés púrpura,0,80509,0 -1044,10312210,Aster cortada fresca japonés rojo,Estrella cortada fresca japonés rojo,0,80509,0 -1045,10312211,Aster cortada fresca japonés spider,Estrella cortada fresca japonés spider,0,80509,0 -1046,10312212,Aster cortada fresca japonés blanco,Estrella cortada fresca japonés blanco,0,80509,0 -1047,10312213,Aster cortada fresca novi belgii rosado fuerte,Estrella cortada fresca novi belgii rosado fuerte,0,80509,0 -1048,10312214,Aster cortada fresca novi belgii lavanda,Estrella cortada fresca novi belgii lavanda,0,80509,0 -1049,10312215,Aster cortada fresca novi belgii rosado,Estrella cortada fresca novi belgii rosado,0,80509,0 -1050,10312216,Aster cortada fresca novi belgii púrpura,Estrella cortada fresca novi belgii púrpura,0,80509,0 -1051,10312217,Aster cortada fresca novi belgii blanco,Estrella cortada fresca novi belgii blanco,0,80509,0 -1052,10312218,Aster cortada fresca solidago,Estrella cortada fresca solidago,0,80509,0 -1053,10312219,Aster cortada fresca spider,Estrella cortada fresca spider,0,80509,0 -1054,10313200,Clavel (dianthus) cortado fresco,Clavelinas cortadas frescas,0,80509,0 -1055,10313201,Clavel (dianthus) cortado fresco chocolate,Clavelina cortada fresca chocolate,0,80509,0 -1056,10313202,Clavel (dianthus) cortado fresco fucsia,Clavelina cortada fresca fucsia,0,80509,0 -1057,10313203,Clavel (dianthus) cortado fresco bola verde,Clavelina cortada fresca bola verde,0,80509,0 -1058,10313204,Clavel (dianthus) cortado fresco rosado fuerte,Clavelina cortada fresca rosado fuerte,0,80509,0 -1059,10313205,Clavel (dianthus) cortado fresco lavanda,Clavelina cortada fresca lavanda,0,80509,0 -1060,10313206,Clavel (dianthus) cortado fresco frambuesa,Clavelina cortada fresca frambuesa,0,80509,0 -1061,10313207,Clavel (dianthus) cortado fresco rojo,Clavelina cortada fresca rojo,0,80509,0 -1062,10313208,Clavel (dianthus) cortado fresco rosado,Clavelina cortada fresca rosado,0,80509,0 -1063,10313900,Margarita cortada fresca,Gerberas cortadas frescas,0,80509,0 -1064,10313901,Margarita cortada fresca crema de centro negro,Gerbera cortada fresca crema de centro negro,0,80509,0 -1065,10313902,Margarita cortada fresca crema,Gerbera cortada fresca crema,0,80509,0 -1066,10313903,Margarita cortada fresca dorada,Gerbera cortada fresca dorada,0,80509,0 -1067,10313904,Margarita cortada fresca rosado fuerte,Gerbera cortada fresca rosado fuerte,0,80509,0 -1068,10313905,Margarita cortada fresca rosado claro,Gerbera cortada fresca rosado claro,0,80509,0 -1069,10313906,Margarita cortada fresca magenta,Gerbera cortada fresca magenta,0,80509,0 -1070,10313907,Margarita cortada fresca mini coral,Gerbera cortada fresca mini coral,0,80509,0 -1071,10313908,Margarita cortada fresca mini fucsia,Gerbera cortada fresca mini fucsia,0,80509,0 -1072,10313909,Margarita cortada fresca mini rosado fuerte,Gerbera cortada fresca mini rosado fuerte,0,80509,0 -1073,10313910,Margarita cortada fresca mini anaranjado claro de centro negro,Gerbera cortada fresca mini anaranjado claro de centro negro,0,80509,0 -1074,10313911,Margarita cortada fresca mini anaranjado de centro negro,Gerbera cortada fresca mini anaranjado de centro negro,0,80509,0 -1075,10313912,Margarita cortada fresca mini anaranjado,Gerbera cortada fresca mini anaranjado,0,80509,0 -1076,10313913,Margarita cortada fresca mini rojo de centro negro,Gerbera cortada fresca mini rojo de centro negro,0,80509,0 -1077,10313914,Margarita cortada fresca mini blanco,Gerbera cortada fresca mini blanco,0,80509,0 -1078,10313915,Margarita cortada fresca mini amarillo claro de centro negro,Gerbera cortada fresca mini amarillo claro de centro negro,0,80509,0 -1079,10313916,Margarita cortada fresca anaranjado de centro negro,Gerbera cortada fresca anaranjado de centro negro,0,80509,0 -1080,10313917,Margarita cortada fresca anaranjada,Gerbera cortada fresca anaranjada,0,80509,0 -1081,10313918,Margarita cortada fresca durazno de centro negro,Gerbera cortada fresca durazno de centro negro,0,80509,0 -1082,10313919,Margarita cortada fresca durazno,Gerbera cortada fresca durazno,0,80509,0 -1083,10313920,Margarita cortada fresca rosada de centro negro,Gerbera cortada fresca rosada de centro negro,0,80509,0 -1084,10313921,Margarita cortada fresca rosada,Gerbera cortada fresca rosada,0,80509,0 -1085,10313922,Margarita cortada fresca roja de centro negro,Gerbera cortada fresca roja de centro negro,0,80509,0 -1086,10313923,Margarita cortada fresca roja,Gerbera cortada fresca roja,0,80509,0 -1087,10313924,Margarita cortada fresca spider durazno,Gerbera cortada fresca spider durazno,0,80509,0 -1088,10313925,Margarita cortada fresca spider roja,Gerbera cortada fresca spider roja,0,80509,0 -1089,10313926,Margarita cortada fresca terracota,Gerbera cortada fresca terracota,0,80509,0 -1090,10313927,Margarita cortada fresca blanca de centro negro,Gerbera cortada fresca blanca de centro negro,0,80509,0 -1091,10313928,Margarita cortada fresca blanca,Gerbera cortada fresca blanca,0,80509,0 -1092,10313929,Margarita cortada fresca amarilla,Gerbera cortada fresca amarilla,0,80509,0 -1093,10314400,Gypsophilia cortada fresca,Nubes cortadas frescas,0,80509,0 -1094,10314401,Gypsophilia cortada fresca bambino,Nube cortada fresca bambino,0,80509,0 -1095,10314402,Gypsophilia cortada fresca million stars,Nube cortada fresca million stars,0,80509,0 -1096,10314403,Gypsophilia cortada fresca mirabella,Nube cortada fresca mirabella,0,80509,0 -1097,10314404,Gypsophilia cortada fresca new love,Nube cortada fresca new love,0,80509,0 -1098,10314405,Gypsophilia cortada fresca orión,Nube cortada fresca orión,0,80509,0 -1099,10314406,Gypsophilia cortada fresca perfecta,Nube cortada fresca perfecta,0,80509,0 -1100,10315100,Delfinio cortado fresco,Delphinium (larkspurs) cortados frescos,0,80509,0 -1101,10315101,Delfinio cortada fresca azul nube,Delphinium (larkspurs) cortado fresco azul nube,0,80509,0 -1102,10315102,Delfinio cortada fresca rosado fuerte,Delphinium (larkspurs) cortado fresco rosado fuerte,0,80509,0 -1103,10315103,Delfinio cortada fresca lavanda,Delphinium (larkspurs) cortado fresco lavanda,0,80509,0 -1104,10315104,Delfinio cortada fresca rosado claro,Delphinium (larkspurs) cortado fresco rosado claro,0,80509,0 -1105,10315105,Delfinio cortada fresca púrpura,Delphinium (larkspurs) cortado fresco púrpura,0,80509,0 -1106,10315106,Delfinio cortada fresca blanca,Delphinium (larkspurs) cortado fresco blanca,0,80509,0 -1107,10316000,Pimientos ornamentales cortados frescos,Paprikas ornamentales cortadas frescas,0,80509,0 -1108,10316001,Pimiento chile cortado fresco ornamental,Paprika ornamental cortada fresca,0,80509,0 -1110,10316003,Pimiento anaranjado cortado fresco ornamental,Paprika ornamental anaranjada cortada fresca,0,80509,0 -1111,10316004,Pimiento rojo cortado fresco ornamental,Paprika ornamental roja cortada fresca,0,80509,0 -1112,10316005,Pimiento amarillo cortado fresco ornamental,Paprika ornamental amarilla cortada fresca,0,80509,0 -1113,10316500,Escabiosa cortada fresca,Flores del acerico cortadas frescas,0,80509,0 -1114,10316501,Escabiosa cortada fresca anual,Flor del acerico cortada fresca anual,0,80509,0 -1115,10316502,Escabiosa cortada fresca negra,Flor del acerico cortada fresca negra,0,80509,0 -1116,10316503,Escabiosa cortada fresca caucásica azul,Flor del acerico cortada fresca caucásica azul,0,80509,0 -1117,10316504,Escabiosa cortada fresca caucásica rosada,Flor del acerico cortada fresca caucásica rosada,0,80509,0 -1118,10316505,Escabiosa cortada fresca caucásica vainas,Flor del acerico cortada fresca caucásica vainas,0,80509,0 -1119,10316506,Escabiosa cortada fresca caucásica blanca,Flor del acerico cortada fresca caucásica blanca,0,80509,0 -1120,10316507,Escabiosa cortada fresca fresa,Flor del acerico cortada fresca fresa,0,80509,0 -1121,10316700,Boca de dragón cortada fresca,,0,80509,0 -1122,10316701,Boca de dragón cortada fresca bicolor,,0,80509,0 -1123,10316702,Boca de dragón cortada fresca burgundy,,0,80509,0 -1124,10316703,Boca de dragón cortada fresca rosado fuerte,,0,80509,0 -1125,10316704,Boca de dragón cortada fresca lavanda,,0,80509,0 -1126,10316705,Boca de dragón cortada fresca anaranjado claro,,0,80509,0 -1127,10316706,Boca de dragón cortada fresca rosado claro,,0,80509,0 -1128,10316707,Boca de dragón cortada fresca anaranjado,,0,80509,0 -1129,10316708,Boca de dragón cortada fresca blanco,,0,80509,0 -1130,10316709,Boca de dragón cortada fresca amarillo,,0,80509,0 -1131,10316800,Romero del pantano cortada fresca,Siemprevivas azules cortadas frescas,0,80509,0 -1132,10316801,Romero del pantano cortada fresca azul,Siempreviva azul cortada fresca azul,0,80509,0 -1133,10316802,Romero del pantano cortada fresca lavanda,Siempreviva azul cortada fresca lavanda,0,80509,0 -1134,10316803,Romero del pantano cortada fresca durazno,Siempreviva azul cortada fresca durazno,0,80509,0 -1135,10316804,Romero del pantano cortada fresca rosada,Siempreviva azul cortada fresca rosada,0,80509,0 -1136,10316805,Romero del pantano cortada fresca púrpura,Siempreviva azul cortada fresca púrpura,0,80509,0 -1137,10316806,Romero del pantano cortada fresca espuma de mar,Siempreviva azul cortada fresca espuma de mar,0,80509,0 -1138,10316807,Romero del pantano cortada fresca blanca,Siempreviva azul cortada fresca blanca,0,80509,0 -1139,10316808,Romero del pantano cortada fresca amarilla,Siempreviva azul cortada fresca amarilla,0,80509,0 -1140,10316900,Matiola cortada fresca,Alhelís cortadas frescas,0,80509,0 -1141,10316901,Matiola cortada fresca apricot,Alhelí cortada fresca apricot,0,80509,0 -1142,10316902,Matiola cortada fresca crema,Alhelí cortada fresca crema,0,80509,0 -1143,10316903,Matiola cortada fresca fucsia,Alhelí cortada fresca fucsia,0,80509,0 -1144,10316904,Matiola cortada fresca lavanda,Alhelí cortada fresca lavanda,0,80509,0 -1145,10316905,Matiola cortada fresca lavanda claro,Alhelí cortada fresca lavanda claro,0,80509,0 -1146,10316906,Matiola cortada fresca rosada,Alhelí cortada fresca rosada,0,80509,0 -1147,10316907,Matiola cortada fresca púrpura,Alhelí cortada fresca púrpura,0,80509,0 -1148,10316908,Matiola cortada fresca rojo rubí,Alhelí cortada fresca rojo rubí,0,80509,0 -1149,10316909,Matiola cortada fresca enamorada rosda,Alhelí cortada fresca enamorada rosada,0,80509,0 -1150,10316910,Matiola cortada fresca blanca,Alhelí cortada fresca blanca,0,80509,0 -1151,10317000,Girasol cortada fresca,,0,80509,0 -1152,10317001,Girasol cortada fresca tinte festivo,,0,80509,0 -1153,10317002,Girasol cortada fresca mahogany,,0,80509,0 -1154,10317003,Girasol cortada fresca rayo de sol,,0,80509,0 -1155,10317004,Girasol cortada fresca brillo del sol,,0,80509,0 -1156,10317005,Girasol cortada fresca salpicada de sol,,0,80509,0 -1157,10317006,Girasol cortada fresca oso de peluche,,0,80509,0 -1158,10317100,Guisante de olor cortado fresco,,0,80509,0 -1159,10317101,Guisante de olor cortado fresco verde teñido,,0,80509,0 -1160,10317102,Guisante de olor cortado fresco rosado fuerte,,0,80509,0 -1161,10317103,Guisante de olor cortado fresco lavanda,,0,80509,0 -1162,10317104,Guisante de olor cortado fresco rosado claro,,0,80509,0 -1163,10317105,Guisante de olor cortado fresco anaranjado,,0,80509,0 -1164,10317106,Guisante de olor cortado fresco durazno teñido,,0,80509,0 -1165,10317107,Guisante de olor cortado fresco púrpura,,0,80509,0 -1166,10317108,Guisante de olor cortado fresco blanco,,0,80509,0 -1167,10317200,Cardo cortado fresco,,0,80509,0 -1168,10317201,Cardo cortado fresco alpinum,,0,80509,0 -1169,10317202,Cardo cortado fresco echinops,,0,80509,0 -1170,10317203,Cardo cortado fresco eryngium árabe,,0,80509,0 -1171,10317204,Cardo cortado fresco eryngium azul,,0,80509,0 -1172,10317205,Cardo cortado fresco eryngium orión,,0,80509,0 -1173,10317206,Cardo cortado fresco eryngium frambuesa,,0,80509,0 -1174,10317207,Cardo cortado fresco eryngium supernova,,0,80509,0 -1175,10317208,Cardo cortado fresco eryngium campanita,,0,80509,0 -1176,10317400,Flor de cera cortada fresca,,0,80509,0 -1177,10317401,Flor de cera cortada fresca alba,,0,80509,0 -1178,10317402,Flor de cera cortada fresca bicolor,,0,80509,0 -1179,10317403,Flor de cera cortada fresca chichilla,,0,80509,0 -1180,10317404,Flor de cera cortada fresca reina danzante,,0,80509,0 -1181,10317405,Flor de cera cortada fresca dinamarca,,0,80509,0 -1182,10317406,Flor de cera cortada fresca denmar,,0,80509,0 -1183,10317407,Flor de cera cortada fresca híbrida pastel flor gema,,0,80509,0 -1184,10317408,Flor de cera cortada fresca híbrida rosada flor gema,,0,80509,0 -1185,10317409,Flor de cera cortada fresca híbrida blanca rubia,,0,80509,0 -1186,10317410,Flor de cera cortada fresca híbrida eric john,,0,80509,0 -1187,10317411,Flor de cera cortada fresca híbrida dama pintada,,0,80509,0 -1188,10317412,Flor de cera cortada fresca híbrida revelación,,0,80509,0 -1189,10317413,Flor de cera cortada fresca híbrida bola de nieve,,0,80509,0 -1190,10317414,Flor de cera cortada fresca juriens brook,,0,80509,0 -1191,10317415,Flor de cera cortada fresca lady stephanie rosada,,0,80509,0 -1192,10317416,Flor de cera cortada fresca madonna,,0,80509,0 -1193,10317417,Flor de cera cortada fresca mini blanca,,0,80509,0 -1194,10317418,Flor de cera cortada fresca anaranjada,,0,80509,0 -1,01010101,No existe en el catálogo,Fondos y Valores,"0,1",80540,0 -2,10101500,Animales vivos de granja,,0,80509,0 -3,10101501,Gatos vivos,,0,80509,0 -4,10101502,Perros,,0,80509,0 -5,10101504,Visón,,0,80509,0 -6,10101505,Ratas,,0,80509,0 -7,10101506,Caballos,"Equinos, Potrancas, Potras, Potrillos, Potros, Yeguas",0,80509,0 -8,10101507,Ovejas,"Borregos, Carneros",0,80509,0 -9,10101508,Cabras,"Borregos cimarrones, Cabritos, Cabros, Carnero de las Rocosas, Chivas, Chivatos, Chivos, Irascos, Machos cabríos, Chivos",0,80509,0 -10,10101509,Asnos,"Borricos, Burros",0,80509,0 -11,10101510,Ratones,,0,80509,0 -12,10101511,Cerdos,"Cerdo montés, Chanchos, Chanchos almizcleros, Chanchos de monte, Cochinillos, Cochinos, Cochinos de monte, Cuche, Cuinos, Gorrinos, Jabalíes americanos, Lechones, Cochinos de monte, Pecaríes, Porcinos, Puercos, Puercos de monte, Tayatos",0,80509,0 -13,10101512,Conejos,Liebres,0,80509,0 -14,10101513,Cobayas o conejillos de indias,"Chanchito de Indias, Cobayos, Cuilo, Cuyo",0,80509,0 -15,10101514,Primates,"Changos, Chimpancés, Gorilas, Lémures, Macacos, Micos, Monos, Orangutanes, Simios, Monos",0,80509,0 -16,10101515,Armadillos,Toches,0,80509,0 -17,10101516,Ganado vacuno,"Bueyes, Búfalos, Cebús, Otros vacunos o bovinos, Toros, Vacas",0,80509,0 -18,10101517,Camellos,Dromedarios,0,80509,0 -19,10101600,Pájaros y aves de corral vivos,,0,80509,0 -20,10101601,Pollos vivos,"Gallinas, Gallinas de guinea, Gallos, Pintadas, Pollitos",0,80509,0 -21,10101602,Patos vivos,,0,80509,0 -22,10101603,Pavos vivos,"Gallipavos, Guajolotes, Pavos salvajes",0,80509,0 -23,10101604,Gansos vivos,,0,80509,0 -24,10101605,Faisanes vivos,,0,80509,0 -25,10101700,Peces vivos,,0,80509,0 -26,10101701,Salmones vivos,,0,80509,0 -27,10101702,Trucha viva,,0,80509,0 -28,10101703,Tilapia viva,Mojarra,0,80509,0 -29,10101704,Carpa viva,,0,80509,0 -30,10101705,Anguilas vivas,,0,80509,0 -31,10101800,Mariscos e invertebrados acuáticos vivos,,0,80509,0 -32,10101801,Camarón vivo,,0,80509,0 -33,10101802,Almejas vivas,,0,80509,0 -34,10101803,Mejillones vivos,,0,80509,0 -35,10101804,Ostras vivas,,0,80509,0 -36,10101805,Cangrejos vivos,,0,80509,0 -37,10101806,Abulones vivos,Orejas de mar,0,80509,0 -38,10101807,Pulpo vivo,Octópodo,0,80509,0 -39,10101808,Calamar vivo,,0,80509,0 -40,10101809,Sanguijuelas,"Hirudineas, Hirudos, Hirudíneo",0,80509,0 -41,10101900,Insectos vivos,,0,80509,0 -42,10101901,Mariposas,,0,80509,0 -43,10101902,Escarabajos,"Mariquitas, Sanjuaneros",0,80509,0 -44,10101903,Abejas,,0,80509,0 -45,10101904,Gusanos de seda,"Crisálida, Larva, Orug",0,80509,0 -46,10111300,"Accesorios, equipo y tratamientos para los animales domésticos","Accesorios, equipo y medicamentos para animales domésticos",0,80509,0 -47,10111301,Juguetes para mascotas,,0,80509,0 -48,10111302,Productos para el aseo y cuidado de mascotas,,0,80509,0 -49,10111303,Equipo para el manejo de desperdicios de las mascotas,,0,80509,0 -50,10111304,Tazones o equipo para alimentación de mascotas,,0,80509,0 -51,10111305,Tratamientos medicados para mascotas,Tratamientos medicinales para mascotas,0,80509,0 -52,10111306,Kits para el entrenamiento de mascotas domésticas,,0,80509,0 -53,10111307,Cobijas para mascotas,"Mantas para mascotas, Sábanas para mascotas",0,80509,0 -54,10121500,Pienso para ganado,Alimento para ganado,0,80509,0 -55,10121501,Salvado de trigo puro,,0,80509,0 -56,10121502,Avena para forraje,,0,80509,0 -57,10121503,Maíz para forraje,,0,80509,0 -58,10121504,Sorgo para forraje,,0,80509,0 -59,10121505,Heno,,0,80509,0 -60,10121506,Tortas oleaginosas,Torta de aceite para ganado,0,80509,0 -61,10121507,Forraje compuesto,,0,80509,0 -62,10121600,Alimento para pájaros y aves de corral,,0,80509,0 -63,10121601,Alimento vivo para aves,,0,80509,0 -64,10121602,Alpiste,,0,80509,0 -65,10121603,Botanas o comida recreacional para aves,,0,80509,0 -66,10121604,Alimento avícola,,0,80509,0 -67,10121700,Alimento para peces,,0,80509,0 -68,10121701,Salmuera fresca o congelada,,0,80509,0 -69,10121702,Alimento granulado para peces,,0,80509,0 -70,10121703,Alimento en hojuelas para peces,,0,80509,0 -71,10121800,Alimento para perros y gatos,,0,80509,0 -72,10121801,Comida seca para perros,,0,80509,0 -73,10121802,Comida húmeda para perros,,0,80509,0 -74,10121803,Leche para perros o gatos,,0,80509,0 -75,10121804,Comida seca para gatos,,0,80509,0 -76,10121805,Comida húmeda para gatos,,0,80509,0 -77,10121806,Botanas o comida recreacional para gatos o perros,,0,80509,0 -78,10121900,Alimento para roedores,,0,80509,0 -79,10121901,Comida granulada para roedores,,0,80509,0 -80,10122100,Comida para animales variados,Comida para diversos animales,0,80509,0 -81,10122101,Comida para cerdos,"Comida para chanchos, Comida para cochinillos, Comida para cochinos, Comida para cuche, Comida para cuinos, Comida para gorrinos, Comida para lechones, Comida para marranos, Comida para porcinos, Comida para puercos, Comida para lechones",0,80509,0 -82,10122102,Comida para visones,,0,80509,0 -83,10122103,Comida para monos,,0,80509,0 -84,10122104,Comida para conejos,,0,80509,0 -85,10131500,Cobertizos para animales,,0,80509,0 -86,10131506,Establos para ganado,,0,80509,0 -87,10131507,Casas para mascotas domesticadas,,0,80509,0 -88,10131508,Camas para mascotas,,0,80509,0 -89,10131600,Recipientes para animales,Contenedores de comida para animales,0,80509,0 -90,10131601,Jaulas o sus accesorios,,0,80509,0 -91,10131602,Perreras,,0,80509,0 -92,10131603,Equipaje para el transporte de animales,,0,80509,0 -93,10131604,Correas para perros,,0,80509,0 -94,10141500,Talabartería,,0,80509,0 -95,10141501,Sillas de montar,,0,80509,0 -96,10141502,Fustas y látigos,,0,80509,0 -97,10141503,Herraduras para caballo,,0,80509,0 -98,10141504,Herraduras para mula,Guarniciones para caballerías,0,80509,0 -99,10141505,Sillín o pelero,,0,80509,0 -100,10141600,Arneses,,0,80509,0 -101,10141601,Bridas,Correas para caballos,0,80509,0 -102,10141602,Yugos,,0,80509,0 -103,10141603,Bocados para caballos,Embocaduras para caballo,0,80509,0 -104,10141604,Riendas,Correas,0,80509,0 -105,10141605,Estribos,,0,80509,0 -106,10141606,Correas o traíllas,,0,80509,0 -107,10141607,Arneses de cuello para animales,,0,80509,0 -1196,10317420,Flor de cera cortada fresca pixie moon,,0,80509,0 -1197,10317421,Flor de cera cortada fresca orgullo púrpura,,0,80509,0 -1198,10317422,Flor de cera cortada fresca roja,,0,80509,0 -1199,10317423,Flor de cera cortada fresca wanaroo,,0,80509,0 -1200,10317424,Flor de cera cortada fresca amarilla,,0,80509,0 -1201,10321800,angélica cortada fresca,,0,80509,0 -1202,10321801,Angélica cortada fresca gigas,,0,80509,0 -1203,10321802,Angélica cortada fresca sylvestris,,0,80509,0 -1204,10322700,Catalea cortada fresca,Calatheas cortadas frescas,0,80509,0 -1205,10322701,Calatea cortada fresca cigarro,Calathea cortada fresca cigarro,0,80509,0 -1206,10322702,Calatea cortada fresca hielo verde,Calathea cortada fresca hielo verde,0,80509,0 -1207,10322703,Calatea cortada fresca serpiente cascabel,Calathea cortada fresca serpiente cascabel,0,80509,0 -1208,10326000,Especies individuales o variedades de flores cortadas frescas,"Arreglos florales frescos, Coronas funerarias, Flor de cempasúchil cortada fresc",0,80509,0 -1209,10326001,Agrsotema cortada fresca,,0,80509,0 -1210,10326002,Kniphophia o assegai poker cortada fresca,,0,80509,0 -1211,10326003,Bellis perennis cortada fresca,,0,80509,0 -1212,10326004,Campana de irlanda o molucella cortada fresca,,0,80509,0 -1213,10326005,Ave del paraíso cortada fresca,,0,80509,0 -1214,10326006,Novia sonrojada cortada fresca,,0,80509,0 -1215,10326007,Buddleia o arbusto mariposa cortada fresca,,0,80509,0 -1216,10326008,Bupleurum griffithii cortada fresca,,0,80509,0 -1217,10326009,California ginesta cortada fresca,,0,80509,0 -1218,10326010,Callicarpa púrpura cortada fresca,,0,80509,0 -1219,10326011,Campanilla blanca cortada fresca,,0,80509,0 -1220,10326012,Penacho dulce cortada fresca,,0,80509,0 -1221,10326013,Cariopteris cortada fresca,,0,80509,0 -1222,10326014,Centaurea o marco polo cortada fresca,,0,80509,0 -1223,10326015,Linterna china cortada fresca,,0,80509,0 -1224,10326016,Clematis recta purpurea cortada fresca,,0,80509,0 -1225,10326017,Cleome spinosa cortada fresca,,0,80509,0 -1226,10326018,Coreopsis cortada fresca,,0,80509,0 -1227,10326019,Farolitos de la virgen azul cortada fresca,,0,80509,0 -1228,10326020,Cosmos chocolate cortada fresca,,0,80509,0 -1229,10326021,Cotinus coggygria cortada fresca,,0,80509,0 -1230,10326022,Craspedia o billy bolas cortada fresca,,0,80509,0 -1231,10326023,Deutzia alta cortada fresca,,0,80509,0 -1232,10326024,Diosma cortada fresca,,0,80509,0 -1233,10326025,Echeveria suculenta chupahuevos cortada fresca,,0,80509,0 -1234,10326026,Echinacea purpurea cortada fresca,,0,80509,0 -1235,10326027,Edelweiss cortada fresca,,0,80509,0 -1236,10326028,Erythronium pagoda cortada fresca,,0,80509,0 -1237,10326029,Flor de eucalipto cortada fresca,,0,80509,0 -1238,10326030,Eucaris o lirio del amazonas cortado fresco,,0,80509,0 -1239,10326031,Eucomis o lirio de piña cortado fresco,,0,80509,0 -1240,10326032,Eupatorium maculatum cortado fresco,,0,80509,0 -1241,10326033,Filipendula cortada fresca,,0,80509,0 -1242,10326034,Digitalis cortado fresco,,0,80509,0 -1243,10326035,Gilia globo cortada fresca,,0,80509,0 -1244,10326036,Globularia de ojo azul cortada fresca,,0,80509,0 -1245,10326037,Washington hawthorne cortada fresca,,0,80509,0 -1246,10326038,Helenio cortada fresca,,0,80509,0 -1247,10326039,Helianto cortada fresca,,0,80509,0 -1248,10326040,Herperis matronalis cortada fresca,,0,80509,0 -1249,10326041,Houttuynia cordata chameleon cortada fresca,,0,80509,0 -1250,10326042,Jacinto con bulbo cortada fresca,,0,80509,0 -1251,10326043,Maiz indio cortado fresco,,0,80509,0 -1252,10326044,Aro cortado fresco,,0,80509,0 -1253,10326045,Arbol del cielo japonés cortado fresco,,0,80509,0 -1254,10326046,Enredadera de jazmin florecida cortada fresca,,0,80509,0 -1255,10326047,Jatropha curcas o pólvora cortada fresca,,0,80509,0 -1256,10326048,Knautia cortada fresca,,0,80509,0 -1257,10326049,Kochia sedifolia cortada fresca,,0,80509,0 -1258,10326050,Lachenalia romaud cortada fresca,,0,80509,0 -1259,10326051,Flor de oereja de oveja cortada fresca,,0,80509,0 -1260,10326052,Lavanda cortada fresca,,0,80509,0 -1261,10326053,Leucocoryne speciosa cortada fresca,,0,80509,0 -1262,10326054,Lythrum cortada fresca,,0,80509,0 -1263,10326055,Malva zebrina cortada fresca,,0,80509,0 -1264,10326056,Margarita blanca cortada fresca,,0,80509,0 -1265,10326057,Montbretia amarilla cortada fresca,,0,80509,0 -1266,10326058,Nebelia cortada fresca,,0,80509,0 -1267,10326059,Nicotiana cortada fresca,,0,80509,0 -1268,10326060,Nigella damascena o amor en la niebla cortada fresca,,0,80509,0 -1269,10326061,Vainas nigella cortada fresca,,0,80509,0 -1270,10326062,Orquídea de monja cortada fresca,,0,80509,0 -1271,10326063,Orquídea verde paphiopedilum,,0,80509,0 -1272,10326064,Paranomus cortada fresca,,0,80509,0 -1273,10326065,Penstemon husker rojo cortado fresco,,0,80509,0 -1274,10326066,Manzana peruana cortada fresca,Cactus cortado fresco,0,80509,0 -1275,10326067,Phlomis sarnia cortada fresca,,0,80509,0 -1276,10326068,Didiscus o flor de encaje rosada cortada fresca,,0,80509,0 -1277,10326069,Platycodon o flor globo cortada fresca,,0,80509,0 -1278,10326070,Retzia capensis cortada fresca,,0,80509,0 -1279,10326071,Ricino común cortado fresco,,0,80509,0 -1280,10326072,Nieve en el monte cortada fresca,,0,80509,0 -1281,10326073,Solidago teñida cortada fresca,,0,80509,0 -1282,10326074,Scilla blanca cortada fresca,Cebolla albarrana cortada fresca (ornamental),0,80509,0 -1283,10326075,Stachys byzantina cortada fresca,,0,80509,0 -1284,10326076,Flor de paja cortada fresca,,0,80509,0 -1285,10326077,Oscularia suculenta cortada fresca,,0,80509,0 -1286,10326078,Flor de tillasandia cortada fresca,,0,80509,0 -1287,10326079,Triteleia cortada fresca,,0,80509,0 -1288,10326080,Tritoma naranja o chuzo caliente cortada fresca roja,,0,80509,0 -1289,10326081,Veronicastrum virginiana cortada fresca,,0,80509,0 -1290,10326082,Bromelia vriesea splendens cortada fresca,Pluma de indio cortada fresca,0,80509,0 -1291,10326084,Hipericim o hierba de san juan cortada fresca,,0,80509,0 -1292,10326085,Spirea cortada fresca,,0,80509,0 -1293,10326086,Yerba de san bonifacio cortada fresca,,0,80509,0 -1294,10341500,Bouquet cortado fresco de rosa,,0,80509,0 -1295,10341501,Bouquet cortado fresco de rosa azul o lavanda o púrpura,,0,80509,0 -1296,10341600,Bouquet cortado fresco de clavel,,0,80509,0 -1297,10341601,Bouquet cortado fresco de clavel burgundi bi color,,0,80509,0 -1299,10342101,Bouquet cortado fresco de orquidea dendrobium,,0,80509,0 -1300,10342200,Bouquet cortado fresco floral mixto,,0,80509,0 -1301,10342201,Bouquet cortado fresco de rosa clavel peonía,,0,80509,0 -1302,10411700,Astromelia cortada seca,Lirios de campo cortados secos,0,80509,0 -1303,10411701,Astromelia cortada seca agropoli,Lirio de campo cortado seco agropoli,0,80509,0 -1304,10411702,Astromelia cortada seca bourgogne,Lirio de campo cortado seco bourgogne,0,80509,0 -1305,10411703,Astromelia cortada seca cairo,Lirio de campo cortado seco cairo,0,80509,0 -1306,10411704,Astromelia cortada seca charmes,Lirio de campo cortado seco charmes,0,80509,0 -1307,10411705,Astromelia cortada seca cherry bay,Lirio de campo cortado seco cherry bay,0,80509,0 -1308,10411706,Astromelia cortada seca cherry white,Lirio de campo cortado seco cherry white,0,80509,0 -1309,10411707,Astromelia cortada seca dame blanche,Lirio de campo cortado seco dame blanche,0,80509,0 -1310,10411708,Astromelia cortada seca diamond,Lirio de campo cortado seco diamond,0,80509,0 -1311,10411709,Astromelia cortada seca gran canaria,Lirio de campo cortado seco gran canaria,0,80509,0 -1312,10411710,Astromelia cortada seca harlekijn,Lirio de campo cortado seco harlekijn,0,80509,0 -1313,10411711,Astromelia cortada seca indian summer,Lirio de campo cortado seco indian summer,0,80509,0 -1314,10411712,Astromelia cortada seca jamaica,Lirio de campo cortado seco jamaica,0,80509,0 -1315,10411713,Astromelia cortada seca macondo,Lirio de campo cortado seco macondo,0,80509,0 -1316,10411714,Astromelia cortada seca mistique,Lirio de campo cortado seco mistique,0,80509,0 -1317,10411715,Astromelia cortada seca my fair,Lirio de campo cortado seco my fair,0,80509,0 -1318,10411716,Astromelia cortada seca new cairo,Lirio de campo cortado seco new cairo,0,80509,0 -1319,10411717,Astromelia cortada seca nice,Lirio de campo cortado seco nice,0,80509,0 -1320,10411718,Astromelia cortada seca orange bowl,Lirio de campo cortado seco orange bowl,0,80509,0 -1321,10411719,Astromelia cortada seca orange queens,Lirio de campo cortado seco orange queens,0,80509,0 -1322,10411720,Astromelia cortada seca orange sun,Lirio de campo cortado seco orange sun,0,80509,0 -1323,10411721,Astromelia cortada seca paris,Lirio de campo cortado seco parís,0,80509,0 -1324,10411722,Astromelia cortada seca picasso,Lirio de campo cortado seco picasso,0,80509,0 -1325,10411723,Astromelia cortada seca pink panther,Lirio de campo cortado seco pink panther,0,80509,0 -1326,10411724,Astromelia cortada seca prima donna,Lirio de campo cortado seco prima donna,0,80509,0 -1327,10411725,Astromelia cortada seca red silhouette,Lirio de campo cortado seco red silhouette,0,80509,0 -1328,10411726,Astromelia cortada seca sacha,Lirio de campo cortado seco sacha,0,80509,0 -1329,10411727,Astromelia cortada seca salmón,Lirio de campo cortado seco salmón,0,80509,0 -1330,10411728,Astromelia cortada seca santiago,Lirio de campo cortado seco santiago,0,80509,0 -1331,10411729,Astromelia cortada seca senna,Lirio de campo cortado seco senna,0,80509,0 -1332,10411730,Astromelia cortada seca snowball,Lirio de campo cortado seco snowball,0,80509,0 -1333,10411731,Astromelia cortada seca sublime,Lirio de campo cortado seco sublime,0,80509,0 -1334,10411732,Astromelia cortada seca tropicana,Lirio de campo cortado seco tropicana,0,80509,0 -1335,10411733,Astromelia cortada seca virginia,Lirio de campo cortado seco virginia,0,80509,0 -1336,10411734,Astromelia cortada seca blanca,Lirio de campo cortado seco blanca,0,80509,0 -1337,10411800,Amaranto cortado seco,,0,80509,0 -1338,10411801,Amaranto cortado seco verde colgante,,0,80509,0 -1339,10411802,Amaranto cortado seco rojo colgante,,0,80509,0 -1340,10411803,Amaranto cortado seco bronce erguido,,0,80509,0 -1341,10411804,Amaranto cortado seco verde erguido,,0,80509,0 -1342,10411805,Amaranto cortado seco rojo erguido,,0,80509,0 -1343,10414000,Jengibre cortado seco,Flores de jengibre cortadas secas,0,80509,0 -1344,10414001,Jengibre cortado seco indonesio,Flor de jengibre cortada seca indonesio,0,80509,0 -1345,10414002,Jengibre cortado seco jungle king rosado,Flor de jengibre cortada seca jungle king rosado,0,80509,0 -1346,10414003,Jengibre cortado seco jungle king rojo,Flor de jengibre cortada seca jungle king rojo,0,80509,0 -1347,10414004,Jengibre cortado seco rosado,Flor de jengibre cortada seca rosado,0,80509,0 -1348,10414005,Jengibre cortado seco rojo,Flor de jengibre cortada seca rojo,0,80509,0 -1349,10414006,Jengibre cortado seco torch,Flor de jengibre cortada seca torch,0,80509,0 -1350,10416000,Pimientos ornamentales cortado secos,Paprikas ornamentales cortadas secas,0,80509,0 -1351,10416001,Pimiento chile cortado seco ornamental,Paprika ornamental cortada seca,0,80509,0 -1352,10416002,Pimiento mezclado cortado seco ornamental,Paprika ornamental mezclada cortada seca,0,80509,0 -1353,10416003,Pimiento anaranjado cortado seco ornamental,Paprika ornamental anaranjada cortada seca,0,80509,0 -1354,10416004,Pimiento rojo cortado seco ornamental,Paprika ornamental roja cortada seca,0,80509,0 -1355,10416005,Pimiento amarillo cortado seco ornamental,Paprika ornamental amarilla cortada seca,0,80509,0 -1356,10417000,Girasol cortada seca,,0,80509,0 -1357,10417001,Girasol cortada seca tinte festivo,,0,80509,0 -1358,10417002,Girasol cortada seca mahogany,,0,80509,0 -1359,10417003,Girasol cortada seca rayo de sol,,0,80509,0 -1360,10417004,Girasol cortada seca brillo del sol,,0,80509,0 -1361,10417005,Girasol cortada seca salpicada de sol,,0,80509,0 -1362,10417006,Girasol cortada seca oso de peluche,,0,80509,0 -1363,10425100,Mimosa cortada seca,,0,80509,0 -1364,10425101,Mimosa cortada seca azul o púrpura,,0,80509,0 -1365,10425102,Mimosa dedo cortada seca,,0,80509,0 -1366,10425103,Mimosa cortada seca floribunda o italia,,0,80509,0 -1367,10425104,Mimosa cortada seca mirandola,,0,80509,0 -1368,10426000,Especies individuales o variedades de flores cortadas secas,"Arreglos florales secos, Flor de cempasúchil cortada seca",0,80509,0 -1369,10426001,Agrsotema cortada seca,,0,80509,0 -1370,10426002,Kniphophia o assegai poker cortada seca,,0,80509,0 -1371,10426003,Bellis perennis cortada seca,,0,80509,0 -1372,10426004,Campana de irlanda o molucella cortada seca,,0,80509,0 -1373,10426005,Ave del paraíso cortada seca,,0,80509,0 -1374,10426006,Novia sonrojada cortada seca,,0,80509,0 -1375,10426007,Buddleia o arbusto mariposa cortada seca,,0,80509,0 -1376,10426008,Bupleurum griffithii cortada seca,,0,80509,0 -1377,10426009,California ginesta cortada seca,,0,80509,0 -1378,10426010,Callicarpa púrpura cortada seca,,0,80509,0 -1379,10426011,Campanilla blanca cortada seca,,0,80509,0 -1381,10426013,Cariopteris cortada seca,,0,80509,0 -1382,10426014,Centaurea o marco polo cortada seca,,0,80509,0 -1383,10426015,Linterna china cortada seca,,0,80509,0 -1384,10426016,Clematis recta purpurea cortada seca,,0,80509,0 -1385,10426017,Cleome spinosa cortada seca,,0,80509,0 -1386,10426018,Coreopsis cortada seca,,0,80509,0 -1387,10426019,Farolitos de la virgen azul cortada seca,,0,80509,0 -1388,10426020,Cosmos chocolate cortada seca,,0,80509,0 -1389,10426021,Cotinus coggygria cortada seca,,0,80509,0 -1390,10426022,Craspedia o billy bolas cortada seca,,0,80509,0 -1391,10426023,Deutzia alta cortada seca,,0,80509,0 -1392,10426024,Diosma cortada seca,,0,80509,0 -1393,10426025,Echeveria suculenta chupahuevos cortada seca,,0,80509,0 -1394,10426026,Echinacea purpurea cortada seca,,0,80509,0 -1395,10426027,Edelweiss cortada seca,,0,80509,0 -1396,10426028,Erythronium pagoda cortada seca,,0,80509,0 -1397,10426029,Flor de eucalipto cortada seca,,0,80509,0 -1398,10426030,Eucaris o lirio del amazonas cortado seco,,0,80509,0 -1399,10426031,Eucomis o lirio de piña cortado seco,,0,80509,0 -1400,10426032,Eupatorium maculatum cortado seco,,0,80509,0 -1401,10426033,Filipendula cortada seca,,0,80509,0 -1402,10426034,Digitalis cortado seco,,0,80509,0 -1403,10426035,Gilia globo cortada seca,,0,80509,0 -1404,10426036,Globularia de ojo azul cortada seca,,0,80509,0 -1405,10426037,Washington hawthorne cortada seca,,0,80509,0 -1406,10426038,Helenio cortada seca,,0,80509,0 -1407,10426039,Helianto cortada seca,,0,80509,0 -1408,10426040,Herperis matronalis cortada seca,,0,80509,0 -1409,10426041,Houttuynia cordata chameleon cortada seca,,0,80509,0 -1410,10426042,Jacinto con bulbo cortada seca,,0,80509,0 -1411,10426043,Maiz indio cortado seco,,0,80509,0 -1412,10426044,Jack in the pulpit cortado seco,,0,80509,0 -1413,10426045,Arbol del cielo japonés cortado seco,,0,80509,0 -1414,10426046,Enredadera de jazmin florecida cortada seca,,0,80509,0 -1415,10426047,Jatropha curcas o pólvora cortada seca,,0,80509,0 -1416,10426048,Knautia cortada seca,,0,80509,0 -1417,10426049,Kochia sedifolia cortada seca,,0,80509,0 -1418,10426050,Lachenalia romaud cortada seca,,0,80509,0 -1419,10426051,Flor de oreja de oveja cortada seca,,0,80509,0 -1420,10426052,Lavanda cortada seca,,0,80509,0 -1421,10426053,Leucocoryne speciosa cortada seca,,0,80509,0 -1422,10426054,Lythrum cortada seca,,0,80509,0 -1423,10426055,Malva zebrina cortada seca,,0,80509,0 -1424,10426056,Margarita blanca cortada seca,,0,80509,0 -1425,10426057,Montbretia amarilla cortada seca,,0,80509,0 -1426,10426058,Nebelia cortada seca,,0,80509,0 -1427,10426059,Nicotiana cortada seca,,0,80509,0 -1428,10426060,Nigella damascena o amor en la niebla cortada seca,,0,80509,0 -1429,10426061,Nigella cortada seca,,0,80509,0 -1430,10426062,Orquídea de monja cortada seca,,0,80509,0 -1431,10426063,Orquídea verde paphiopedilum cortada seca,,0,80509,0 -1432,10426064,Paranomus cortada seca,,0,80509,0 -1433,10426065,Penstemon husker rojo cortado seco,,0,80509,0 -1434,10426066,Manzana peruana cortada seca,Cactus cortado seco,0,80509,0 -1435,10426067,Phlomis sarnia cortada seca,,0,80509,0 -1436,10426068,Didiscus o flor de encaje rosada cortada seca,,0,80509,0 -1437,10426069,Platycodon o flor globo cortada seca,,0,80509,0 -1438,10426070,Retzia capensis cortada seca,,0,80509,0 -1439,10426071,Ricino común cortado seco,,0,80509,0 -1440,10426072,Nieve en el monte cortada seca,,0,80509,0 -1441,10426073,Solidago teñida cortada seca,,0,80509,0 -1442,10426074,Scilla blanca cortada seca,Cebolla albarrana cortada seca (ornamental),0,80509,0 -1443,10426075,Stachys byzantina cortada seca,,0,80509,0 -1444,10426076,Flor de paja cortada seca,,0,80509,0 -1445,10426077,Oscularia suculenta cortada seca,,0,80509,0 -1446,10426078,Flor de tillasandia cortada seca,,0,80509,0 -1447,10426079,Triteleia cortada seca,,0,80509,0 -1448,10426080,Tritoma naranja o chuzo caliente cortada seca roja,,0,80509,0 -1449,10426081,Veronicastrum virginiana cortada seca,,0,80509,0 -1450,10426082,Bromelia vriesea splendens cortada seca,Pluma de indio cortada seca,0,80509,0 -1451,10426084,Hipericim o hierba de san juan cortada seca,,0,80509,0 -1452,10426085,Spirea cortada seca,,0,80509,0 -1453,10426086,Yerba de san bonifacio cortada seca,,0,80509,0 -1454,10426100,Sello de salomón (polygonato) cortada seca,,0,80509,0 -1455,10426101,Sello de salomón (polygonato) falso cortada seca,,0,80509,0 -1456,10426102,Sello de salomón (polygonato) variegado cortada seca,,0,80509,0 -1457,10501600,Chili verde cortado fresco,,0,80509,0 -1458,10501601,Chili verde cortado fresco avellana,,0,80509,0 -1459,10501602,Chili verde cortado fresco romerillo,,0,80509,0 -1460,10501603,Chili verde cortado fresco pacarilla,,0,80509,0 -1461,10501604,Chili verde cortado fresco musgo,,0,80509,0 -1462,10501800,Hojas de eucalipto cortadas prescas,,0,80509,0 -1463,10501801,Eucalipto cortado fresco bebé azul,,0,80509,0 -1464,10501802,Eucalipto cortado fresco alto bonsai,,0,80509,0 -1465,10501803,Eucalipto cortado fresco pluma,,0,80509,0 -1466,10501804,Eucalipto cortado fresco gunnii,,0,80509,0 -1467,10501805,Eucalipto cortado fresco parvifolia,,0,80509,0 -1468,10501806,Eucalipto cortado fresco preservado,,0,80509,0 -1469,10501807,Eucalipto cortado fresco sin semilla,,0,80509,0 -1470,10501808,Eucalipto cortado fresco sin semilla y sin hojas,,0,80509,0 -1471,10501809,Eucalipto cortado fresco sin semillas llorón,,0,80509,0 -1472,10501810,Eucalipto cortado fresco dólar de plata,,0,80509,0 -1473,10501811,Eucalipto cortado fresco alto espiral,,0,80509,0 -1474,10501812,Eucalipto cortado fresco azul verdadero,,0,80509,0 -1475,10501813,Eucalipto cortado fresco sauce sin semillas,,0,80509,0 -1476,10502000,Hojas o follajes cortados frescos,,0,80509,0 -1477,10502001,Follaje fresco cortado de melaluca australiana,,0,80509,0 -1478,10502002,Follaje fresco cortado de lepto azul,,0,80509,0 -1479,10502003,Follaje fresco cortado de corylus avellana,,0,80509,0 -1480,10502004,Follaje fresco cortado de hosta verde,,0,80509,0 -1481,10502005,Follaje fresco cortado de hosta variegada,,0,80509,0 -1482,10502006,Follaje fresco cortado de kunzia,,0,80509,0 -1483,10502007,Follaje fresco cortado de jade,,0,80509,0 -1484,10502008,Follaje fresco cortado de magnolia,,0,80509,0 -1485,10502009,Follaje fresco cortado de meyeril o foxtail,,0,80509,0 -1487,10502011,Follaje fresco cortado de pistacho,,0,80509,0 -1488,10502012,Follaje fresco cortado de ciruelo,,0,80509,0 -1489,10502013,Follaje fresco cortado de robellini,,0,80509,0 -1490,10502014,Follaje fresco cortado de viburnum,,0,80509,0 -1491,10502015,Follaje fresco cortado de xanadu,,0,80509,0 -1492,10502016,Follaje fresco cortado de sanderiana,,0,80509,0 -1493,10502017,Follaje fresco cortado de gardenia,,0,80509,0 -1494,10502100,Pastos costados frescos,Pastos ornamentales cortados frescos,0,80509,0 -1495,10502101,Pasto fresco cortado lirio florida,Pasto ornamental cortado fresco lirio florida,0,80509,0 -1496,10502102,Pasto fresco cortado variegado,Pasto ornamental cortado fresco variegado,0,80509,0 -1497,10502103,Pasto fresco cortado oso,Pasto ornamental cortado fresco oso,0,80509,0 -1498,10502104,Pasto fresco cortado bambú,Pasto ornamental cortado fresco bambú,0,80509,0 -1499,10502105,Pasto fresco cortado caña,Pasto ornamental cortado fresco caña,0,80509,0 -1500,10502106,Pasto fresco cortado descorchador,Pasto ornamental cortado fresco descorchador,0,80509,0 -1501,10502107,Pasto fresco cortado pluma,Pasto ornamental cortado fresco pluma,0,80509,0 -1502,10502108,Pasto fresco cortado flexi,Pasto ornamental cortado fresco flexi,0,80509,0 -1503,10502109,Pasto fresco cortado fuente,Pasto ornamental cortado fresco fuente,0,80509,0 -1504,10502110,Pasto fresco cortado carrizo,Pasto ornamental cortado fresco carrizo,0,80509,0 -1505,10502111,Pasto fresco cortado millet,Pasto ornamental cortado fresco millet,0,80509,0 -1506,10502112,Pasto fresco cortado millet negro,Pasto ornamental cortado fresco millet negro,0,80509,0 -1507,10502113,Pasto fresco cortado milo,Pasto ornamental cortado fresco milo,0,80509,0 -1508,10502114,Pasto fresco cortado avena variegada,Pasto ornamental cortado fresco avena variegada,0,80509,0 -1509,10502115,Pasto fresco cortado phalaris canariensis,Pasto ornamental cortado fresco phalaris canariensis,0,80509,0 -1510,10502116,Pasto fresco cortado ciruela púrpiura,Pasto ornamental cortado fresco ciruela púrpura,0,80509,0 -1511,10502117,Pasto fresco cortado cascabel,Pasto ornamental cortado fresco cascabel,0,80509,0 -1512,10502118,Pasto fresco cortado cinta,Pasto ornamental cortado fresco cinta,0,80509,0 -1513,10502119,Pasto fresco cortado avena marina,Pasto ornamental cortado fresco avena marina,0,80509,0 -1514,10502120,Pasto fresco cortado steelgrass,Pasto ornamental cortado fresco steelgrass,0,80509,0 -1515,10502121,Pasto fresco cortado cebra variegado,Pasto ornamental cortado fresco cebra variegado,0,80509,0 -1516,10502122,Pasto fresco cortado panicum o jungla,Pasto ornamental cortado fresco panicum o jungla,0,80509,0 -1517,10502123,Pasto fresco cortado pampas,Pasto ornamental cortado fresco pampas,0,80509,0 -1518,10502124,Pasto fresco cortado pimienta,Pasto ornamental cortado fresco pimienta,0,80509,0 -1519,10502125,Pasto fresco cortado cebra,Pasto ornamental cortado fresco cebra,0,80509,0 -1520,10502500,Mirto cortado fresco,Follajes de mirtillos,0,80509,0 -1521,10502501,Mirto fresco cortado enano,Follaje de mirtillo fresco cortado enano,0,80509,0 -1522,10502502,Mirto fresco cortado variegado,Follaje de mirtillo fresco cortado variegado,0,80509,0 -1523,10502900,Hojas frescas cortadas de especies sencillas,,0,80509,0 -1524,10502901,Acanto o cordone fresca cortada,,0,80509,0 -1525,10502902,Pluma de acacia púrpura fresca cortada,,0,80509,0 -1526,10502903,Adina rubella fresca cortada,,0,80509,0 -1527,10502904,Agonis flexuosa fresca cortada,,0,80509,0 -1528,10502905,Azara dentata fresca cortada,,0,80509,0 -1529,10502906,Arbusto barker fresca cortada,,0,80509,0 -1530,10502907,Corona de laurel fresca cortada,,0,80509,0 -1531,10502908,Llama azul fresca cortada,,0,80509,0 -1532,10502909,Buxus africana fresca cortada,,0,80509,0 -1533,10502910,Laurel fresca cortada,,0,80509,0 -1534,10502911,Hoja de camelia fresca cortada,,0,80509,0 -1535,10502912,Cedro dorado de nueva zelandia fresca cortada,,0,80509,0 -1536,10502913,Ceratopetalum de nueva zelandia fresca cortada,,0,80509,0 -1537,10502914,Choisya ternata fresca cortada,,0,80509,0 -1538,10502915,Cocculus fresca cortada,,0,80509,0 -1539,10502916,Croto fresca cortada,,0,80509,0 -1540,10502917,Datura fresca cortada,,0,80509,0 -1541,10502918,Dusty miller fresca cortada,,0,80509,0 -1542,10502919,Elegia capensis fresca cortada,,0,80509,0 -1543,10502920,Hoja de euonymous fresca cortada,,0,80509,0 -1544,10502921,Hoja de lino fresca cortada,,0,80509,0 -1545,10502922,Hoja de galax fresca cortada,,0,80509,0 -1546,10502923,Hoja de oro fresca cortada,,0,80509,0 -1547,10502924,Hoja de gravillea fresca cortada,,0,80509,0 -1548,10502925,Hoja de pasto cola de caballo o culebra fresca cortada,,0,80509,0 -1549,10502926,Acacia hoja de navaja fresca cortada,,0,80509,0 -1550,10502927,Hoja de laurel fresca cortada,,0,80509,0 -1551,10502928,Hoja de cuero fresca cortada,,0,80509,0 -1552,10502929,Hoja de ligularia fresca cortada,,0,80509,0 -1553,10502930,Hoja de ligularia argentea fresca cortada,,0,80509,0 -1554,10502931,Lophomyrtus fresca cortada,,0,80509,0 -1555,10502932,Lycopodium o pino princresa fresca cortada,,0,80509,0 -1556,10502933,Mahonia aquifolium fresca cortada,,0,80509,0 -1557,10502934,Hoja de nagi fresca cortada,,0,80509,0 -1558,10502935,Palma de abanico palmetto fresca cortada,,0,80509,0 -1559,10502936,Papyrus umbrella florida fresca cortada,,0,80509,0 -1560,10502937,Pennycress fresca cortada,,0,80509,0 -1561,10502938,Photinia robin rojo fresca cortada,,0,80509,0 -1562,10502939,Diablo de nueve cortezas o physocarpus fresca cortada,,0,80509,0 -1563,10502940,Pimelea nivea fresca cortada,,0,80509,0 -1564,10502941,Hoja de pistacho fresca cortada,,0,80509,0 -1565,10502942,Plumosa fresca cortada,,0,80509,0 -1566,10502943,Podocarpus fresca cortada,,0,80509,0 -1567,10502944,Pokeweed fresca cortada,,0,80509,0 -1568,10502945,Photinia de punta roja fresca cortada,,0,80509,0 -1569,10502946,Rhamnus o buckthorn variegada fresca cortada,,0,80509,0 -1570,10502947,Rododendro en flor fresca cortada,,0,80509,0 -1571,10502948,Hoja de rosio fresca cortada,,0,80509,0 -1572,10502949,Ruscus florida largo fresca cortada,,0,80509,0 -1573,10502950,Ruscus italiana fresca cortada,,0,80509,0 -1574,10502951,Salal u hoja de limón fresca cortada,,0,80509,0 -1575,10502952,Ruscifolia sarcococca fresca cortada,,0,80509,0 -1576,10502953,Cheflera fresca cortada,,0,80509,0 -1577,10502954,Avena de mar uniola latifolia fresca cortada,,0,80509,0 -1578,10502955,Tomillo plateado fresca cortada,,0,80509,0 -1580,10502957,Springeri fresca cortada,,0,80509,0 -1581,10502958,Tulepods fresca cortada,,0,80509,0 -1582,10502959,Weigelia fresca cortada,,0,80509,0 -1583,11101500,Minerales,,0,80509,0 -1584,11101501,Mica,,0,80509,0 -1585,11101502,Lija o esmeril,Esmeril mineral,0,80509,0 -1586,11101503,Cuarzo,Óxido de silicio,0,80509,0 -1587,11101504,Pirita,Disulfuro de hierro,0,80509,0 -1588,11101505,Azufre o sulfuro,,1,80509,0 -1589,11101506,Tiza,"Barra de yeso, Creta, Gi",0,80509,0 -1590,11101507,Grafito,,0,80509,0 -1591,11101508,Dolomita,Caliza de magnesio,0,80509,0 -1592,11101509,Magnesita,Giobertita,0,80509,0 -1593,11101510,Asbesto (amianto),,0,80509,0 -1594,11101511,Calcio,,1,80509,0 -1595,11101512,Borato,,1,80509,0 -1596,11101513,Criolita,Hexafluoroaluminato de sodio,0,80509,0 -1597,11101514,Feldespato,,0,80509,0 -1598,11101515,Leucita,,0,80509,0 -1599,11101516,Nefelita,Nefelina,0,80509,0 -1600,11101517,Esteatita,Piedra jabón,0,80509,0 -1601,11101518,Talco,,0,80509,0 -1602,11101519,Diamante en bruto,,0,80509,0 -1603,11101520,Granates,,0,80509,0 -1604,11101521,Carburo de silicio,"Carborundio, Carborundo",1,80509,0 -1605,11101522,Carbón activado,Carbón activo,1,80509,0 -1606,11101523,Mulita,"Porcelainita, Porcelanita",0,80509,0 -1607,11101524,Fluorita,"Espato flúor, Fluorina",0,80509,0 -1608,11101525,Kieserita,,0,80509,0 -1609,11101526,Carnalita,Cornalina,0,80509,0 -1610,11101527,Sílice de diatomeas,Tierra de diatomeas,0,80509,0 -1611,11101528,Óxido de aluminio activado,,1,80509,0 -1612,11101529,Zeolita,,0,80509,0 -1613,11101530,Calcita,,0,80509,0 -1614,11101531,Serpentina,Serpentina (mineral),0,80509,0 -1615,11101532,Peridoto,"Crisolito, Olivino",0,80509,0 -1616,11101533,Pirofilita,,0,80509,0 -1617,11101534,Diamante pulido,,0,80509,0 -1618,11101600,Minerales metálicos,,"0,1",80509,0 -1619,11101601,Mineral de hierro,Mena de hierro,"0,1",80509,0 -1620,11101602,Mineral de titanio,Mena de titanio,1,80509,0 -1621,11101603,Mineral de uranio,Mena de uranio,1,80509,0 -1622,11101604,Mineral de cobre,Mena de cobre,"0,1",80509,0 -1623,11101605,Mineral de aluminio,Mena de aluminio,"0,1",80509,0 -1624,11101606,Mineral de níquel,Mena de níquel,"0,1",80509,0 -1625,11101607,Mineral de plata,Mena de plata,"0,1",80509,0 -1626,11101608,Mineral de plomo,Mena de plomo,1,80509,0 -1627,11101609,Mineral de zinc,Mena de zinc,0,80509,0 -1628,11101610,Mineral de estaño,Mena de estaño,"0,1",80509,0 -1629,11101611,Mineral de manganeso,Mena de manganeso,"0,1",80509,0 -1630,11101612,Mineral de cromo,Mena de cromo,"0,1",80509,0 -1631,11101613,Mineral de tungsteno o wolframio,Mena de tungsteno o wolframio,"0,1",80509,0 -1632,11101614,Mineral de molibdeno,Mena de molibdeno,"0,1",80509,0 -1633,11101615,Mineral de cobalto,Mena de cobalto,"0,1",80509,0 -1634,11101616,Mineral de oro,Mena de oro,0,80509,0 -1635,11101617,Mineral de tántalo,Mena de tántalo,0,80509,0 -1636,11101618,Mineral de platino,Mena de platino,0,80509,0 -1637,11101619,Mineral de vermiculita,Mena de vermiculita,0,80509,0 -1638,11101620,Mineral de torio,Mena de torio,0,80509,0 -1639,11101621,Mineral de cianita,Mena de cianita,0,80509,0 -1640,11101622,Mineral de antimonio,Mena de antimonio,1,80509,0 -1641,11101623,Mineral de zirconio,Mena de zirconio,0,80509,0 -1642,11101624,Mineral de magnetita,Mena de magnetita,0,80509,0 -1643,11101625,Mineral de bauxita,Mena de bauxita,0,80509,0 -1644,11101700,Metales de Base,,"0,1",80509,0 -1645,11101701,Escoria o ceniza,,0,80509,0 -1646,11101702,Grafito natural,,0,80509,0 -1647,11101704,Acero,,0,80509,0 -1648,11101705,Aluminio,,"0,1",80509,0 -1649,11101706,Níquel,,"0,1",80509,0 -1650,11101708,Bismuto,,0,80509,0 -1651,11101709,Antimonio,,1,80509,0 -1652,11101710,Cadmio,,"0,1",80509,0 -1653,11101711,Aleación no ferrosa,,"0,1",80509,0 -1654,11101712,Aleación ferrosa,,"0,1",80509,0 -1655,11101713,Hierro,,"0,1",80509,0 -1656,11101714,Plomo,,"0,1",80509,0 -1657,11101716,Estaño,,"0,1",80509,0 -1658,11101717,Indio,Indio (metal),0,80509,0 -1659,11101718,Paladio,,0,80509,0 -1660,11101719,Zinc,,0,80509,0 -1661,11101800,Metales preciosos,,0,80509,0 -1662,11101801,Oro,,0,80509,0 -1663,11101802,Plata,,0,80509,0 -1664,11101803,Platino,,0,80509,0 -1665,11101900,"Productos minerales intermedios, procesados y refinados",,"0,1",80509,0 -1666,11101901,Concentrado de mineral de hierro,Mineral de hierro desmenuzado,"0,1",80509,0 -1667,11101902,Concentrado de níquel,,"0,1",80509,0 -1668,11101903,Briqueta o compacto de níquel,,"0,1",80509,0 -1669,11101904,Mate de níquel,Níquel mate,"0,1",80509,0 -1670,11101905,Aluminio liquido,,"0,1",80509,0 -1671,11101906,Concentrado de cobre,,"0,1",80509,0 -1672,11101907,Cátodo de cobre,,"0,1",80509,0 -1673,11101908,Sínter o perdigones de manganeso,Toba de manganeso,"0,1",80509,0 -1674,11111500,Barro y Tierra,,0,80509,0 -1675,11111501,Tierra,,0,80509,0 -1676,11111502,Material de relleno,Tierra para relleno,0,80509,0 -1677,11111503,Capa vegetal,Suelo orgánico,0,80509,0 -1678,11111504,Tierra de cristal,Tierra de hidrogel,0,80509,0 -1679,11111505,Suelo artificial,,0,80509,0 -1680,11111600,Piedra,,0,80509,0 -1681,11111601,Yeso,Piedra de yeso,0,80509,0 -1682,11111602,Travertino,,0,80509,0 -1683,11111603,Alabastro,,0,80509,0 -1684,11111604,Granito,,0,80509,0 -1685,11111605,Mármol,,0,80509,0 -1686,11111606,Pizarra,Roca de pizarra,0,80509,0 -1687,11111607,Arenisca,Psamita,0,80509,0 -1688,11111608,Caliza,,0,80509,0 -1689,11111609,Basalto,,0,80509,0 -1690,11111610,Piedra pómez,"Liparita, Pumita",0,80509,0 -1691,11111611,Gravilla,Grava,0,80509,0 -1692,11111612,Caliza en polvo,Polvo de piedra caliza o polvo de mina de roca,0,80509,0 -1693,11111613,Wollastonita,,0,80509,0 -1694,11111700,Arena,,0,80509,0 -1695,11111701,Arena de sílice,,0,80509,0 -1696,11111800,Arcillas,,0,80509,0 -1697,11111801,Terracota,,0,80509,0 -1698,11111802,Arcilla refractaria,,0,80509,0 -1699,11111803,Caolín u otras arcillas caolínicas,,0,80509,0 -1700,11111804,Bentonita,,0,80509,0 -1701,11111805,Andalucita,,0,80509,0 -1702,11111806,Mullita,,0,80509,0 -1703,11111807,Chamota,,0,80509,0 -1704,11111808,Arcilla común,,0,80509,0 -1705,11111809,Arcilla de bola,,0,80509,0 -1706,11111810,Tierra de fuller,,0,80509,0 -1708,11111812,Vermiculita,,0,80509,0 -1709,11121500,Savia,,0,80509,0 -1710,11121502,Resina,,0,80509,0 -1711,11121503,Laca,,0,80509,0 -1712,11121600,Madera,,0,80509,0 -1713,11121603,Troncos,,0,80509,0 -1714,11121604,Madera blanda,,0,80509,0 -1715,11121605,Ratán,,0,80509,0 -1716,11121606,Corcho,,0,80509,0 -1717,11121607,Pulpa de madera,,0,80509,0 -1718,11121608,Bambú,,0,80509,0 -1719,11121610,Maderas duras,,0,80509,0 -1720,11121612,Médula de madera,,0,80509,0 -1721,11121614,Madera de teca,,0,80509,0 -1722,11121615,Madera de álamo,,0,80509,0 -1723,11121616,Madera de pino,,0,80509,0 -1724,11121617,Madera de roble,,0,80509,0 -1725,11121618,Madera de arce,,0,80509,0 -1726,11121619,Madera de cerezo,,0,80509,0 -1727,11121620,Madera de tornillo,,0,80509,0 -1728,11121700,Productos secundarios de la explotación forestal,,0,80509,0 -1729,11121701,Aserrín,Viruta de madera,0,80509,0 -1730,11121702,Astillas de madera,,0,80509,0 -1731,11121703,Musgo de turba,"Esfagno, Musgo de turbera",0,80509,0 -1732,11121705,Corteza,,0,80509,0 -1733,11121706,Composta de madera,,0,80509,0 -1734,11121707,Viruta de madera,,0,80509,0 -1735,11121708,Harina de madera,,0,80509,0 -1736,11121709,Bálsamos,,0,80509,0 -1737,11121710,Brea de madera,Alquitrán de madera,0,80509,0 -1738,11121800,Fibras de plantas,,"0,1",80509,0 -1739,11121801,Cáñamo,,"0,1",80509,0 -1740,11121802,Algodón,,"0,1",80509,0 -1741,11121803,Lino,,"0,1",80509,0 -1742,11121804,Yute,,"0,1",80509,0 -1743,11121805,Sisal,"Fibra de maguey, Henequén de agave","0,1",80509,0 -1744,11121806,Fibra de coco,,"0,1",80509,0 -1745,11121807,Fibras de abacá,Cáñamo de manila,"0,1",80509,0 -1746,11121808,Ramio,,"0,1",80509,0 -1747,11121809,Paja,,"0,1",80509,0 -1748,11121810,Mijo común o ixtle o piasava,,"0,1",80509,0 -1749,11121900,Productos de perfumería,,"0,1",80509,0 -1750,11121901,Raíz de regaliz,,"0,1",80509,0 -1751,11122000,Productos de madera diseñados,,0,80509,0 -1752,11122001,Triplex,"Madera contrachapada, Triplay",0,80509,0 -1753,11122002,Tablex,Tablero aglomerados,0,80509,0 -1754,11122003,Corrugado,Fibra de madera de densidad media,0,80509,0 -1755,11122004,Enchapes de madera,,0,80509,0 -1756,11122005,Madera laminada engomada,Madera laminada encolada,0,80509,0 -1757,11122006,Madera tratada,,0,80509,0 -1758,11131500,"Cuero, pieles o materiales textiles de animales",,0,80509,0 -1759,11131501,Plumas,,0,80509,0 -1760,11131502,Pieles,,0,80509,0 -1761,11131503,Pelo animal,,0,80509,0 -1762,11131504,Cueros,,0,80509,0 -1763,11131505,Mohair sin procesar,,0,80509,0 -1764,11131506,Lana sin procesar,,0,80509,0 -1765,11131507,Gamuza,,0,80509,0 -1766,11131508,Cuero de grano completo,,0,80509,0 -1767,11131600,Otros productos de animales,,0,80509,0 -1768,11131602,Semen,,0,80509,0 -1769,11131603,Excreciones,,0,80509,0 -1770,11131604,Huevas de pescado,,0,80509,0 -1771,11131605,Huesos,,0,80509,0 -1772,11131607,Embriones,,0,80509,0 -1773,11131608,Artículos de concha,,0,80509,0 -1774,11141600,Desechos no metálicos y chatarra,Productos reciclables,"0,1",80509,0 -1775,11141601,Desecho o desperdicio textil,Productos textiles,"0,1",80509,0 -1776,11141602,Desecho o desperdicio plástico,Productos plásticos,"0,1",80509,0 -1777,11141603,Desechos de aceite,Productos de aceite,"0,1",80509,0 -1778,11141604,Desechos de papel,Productos de papel,"0,1",80509,0 -1779,11141605,Desecho o desperdicios de vidrio,Productos de vidrio,"0,1",80509,0 -1780,11141606,Desecho o desperdicios de madera,Productos de madera,"0,1",80509,0 -1781,11141607,Desecho o desperdicios de caucho,Productos de caucho,"0,1",80509,0 -1782,11141608,Desecho o desperdicios peligrosos,,"0,1",80509,0 -1783,11141609,Composición de cuero,Productos compuestos de cuero,"0,1",80509,0 -1784,11141610,Desecho o desperdicios de cuero,Productos de cuero,"0,1",80509,0 -1785,11141700,Desperdicios o restos de alimentos y tabaco,,"0,1",80509,0 -1786,11141701,Desecho o desperdicios de alimentos,,"0,1",80509,0 -1787,11141702,Desecho o desperdicios de tabaco,,"0,1",80509,0 -1788,11151500,Fibras,,0,80509,0 -1789,11151501,Fibras de acetato,Fibras de acetato de celulosa,0,80509,0 -1790,11151502,Fibras de nylon,,0,80509,0 -1791,11151503,Fibras de poliéster,,0,80509,0 -1792,11151504,Fibras de acrílico,,0,80509,0 -1793,11151505,Fibras de viscosas,,0,80509,0 -1794,11151506,Fibras de rayón,,0,80509,0 -1795,11151507,Fibras de algodón,,0,80509,0 -1796,11151508,Fibras de lana,,0,80509,0 -1797,11151509,Fibras de seda,,0,80509,0 -1798,11151510,Fibras vegetales,,0,80509,0 -1799,11151511,Fibras de polipropileno,,0,80509,0 -1800,11151512,Fibras de vidrio,,0,80509,0 -1801,11151513,Fibras cerámicas,,0,80509,0 -1802,11151514,Fibras de polímero de aramida,,0,80509,0 -1803,11151515,Fibras de asbesto,,0,80509,0 -1804,11151516,Fibras de poliuretano o spándex,Fibra de licra,0,80509,0 -1805,11151517,Fibras de alcohol polivinilo,Fibras de alcohol polivinílico,0,80509,0 -1806,11151518,Fibras de polietileno,,0,80509,0 -1807,11151519,Fibras de lana de roca o lana mineral,,0,80509,0 -1808,11151600,Hebras,,0,80509,0 -1809,11151601,Hebra de algodón,,0,80509,0 -1810,11151602,Hebra de seda,,0,80509,0 -1811,11151603,Hebra de poliéster,,0,80509,0 -1812,11151604,Hebra de poliamida,,0,80509,0 -1813,11151605,Hebra de bismaleimida,,0,80509,0 -1814,11151606,Hebra de fibra de vidrio,,0,80509,0 -1815,11151607,Hebra de grafito,,0,80509,0 -1816,11151608,Hebra de nylon,,0,80509,0 -1817,11151609,Hebra de resina impregnada,,0,80509,0 -1818,11151610,Hebra de caucho o látex,,0,80509,0 -1819,11151611,Hebra de spandex,,0,80509,0 -1820,11151612,Hebra de asbesto,,0,80509,0 -1821,11151700,Hilos,,0,80509,0 -1822,11151701,Hilado de lana,Hilo de lana,0,80509,0 -1823,11151702,Hilado de algodón,Hilo de algodón,0,80509,0 -1824,11151703,Hilado de poliéster,Hilo de poliéster,0,80509,0 -1825,11151704,Hilado de acrílico,Hilo de acrílico,0,80509,0 -1826,11151705,Hilado de seda,Hilo de seda,0,80509,0 -1827,11151706,Hilado de ramie,Hilo de ramio,0,80509,0 -1828,11151708,Hilado de pelo animal,Hilo de pelo animal,0,80509,0 -1829,11151709,Hilado de sintético,Hilo de sintético,0,80509,0 -1830,11151710,Hilado de yute,Hilo de yute,0,80509,0 -1831,11151711,Hilado de fibra de coco,Hilo de fibra de coco,0,80509,0 -1833,11151713,Hilado de cáñamo,Hilo cáñamo,0,80509,0 -1834,11151714,Hilado de vidrio,Hilo de vidrio,0,80509,0 -1835,11151715,Hilado de lino,Hilo de lino,0,80509,0 -1836,11151716,Hilado mezclado,Hilo mezclado,0,80509,0 -1837,11161500,Tejidos de seda,,0,80509,0 -1838,11161501,Textiles de seda de tejido simple,Textiles de seda de tejido sencillo,0,80509,0 -1839,11161502,Textiles de seda de tejido jacquard,,0,80509,0 -1840,11161503,Textiles de lana,,0,80509,0 -1841,11161504,Textiles de tejido de pana,,0,80509,0 -1842,11161600,Tejidos de lana,,0,80509,0 -1843,11161601,Tejidos de lana en tafetán,,0,80509,0 -1844,11161602,Tejidos de lana urdidos a la jacquard,,0,80509,0 -1845,11161603,Tejidos de lana de punto,,0,80509,0 -1846,11161604,Tejidos de lana de ligamento de sarga,,0,80509,0 -1847,11161700,Tejidos de algodón,,0,80509,0 -1848,11161701,Textiles de algodón de tejido simple,,0,80509,0 -1849,11161702,Textiles de algodón de tejido de pana,Textiles de algodón de tejido sarga,0,80509,0 -1850,11161703,Tela de algodón oxford,,0,80509,0 -1851,11161704,Textiles de algodón tejido,Textiles de punto de algodón,0,80509,0 -1852,11161705,Textiles de terciopelo de algodón,,0,80509,0 -1853,11161800,Tejidos sintéticos,,0,80509,0 -1854,11161801,Textil sintético de tejido sintético,,0,80509,0 -1855,11161802,Textil sintético de tejido jacquard,,0,80509,0 -1856,11161803,Textil sintético de tejido dobby,,0,80509,0 -1857,11161804,Textil sintético tejido,,0,80509,0 -1858,11161805,Textil sintético de terciopelo,,0,80509,0 -1859,11161806,Textil sintético de tejido de pana,,0,80509,0 -1860,11161807,Textil sintético de felpa,,0,80509,0 -1861,11162000,Tejidos de materiales vegetales no de algodón,,0,80509,0 -1862,11162001,Textil de fibra vegetal tejida distinta de algodón simple,,0,80509,0 -1863,11162002,Textil de fibra vegetal tejida distinta de algodón,,0,80509,0 -1864,11162003,Tela de arpillera o cáñamo o yute,,0,80509,0 -1865,11162100,Tejidos o telas especiales,,"0,1",80509,0 -1866,11162101,Tela o textil de queso,,"0,1",80509,0 -1867,11162102,Tela o textil de bismaleimida,,"0,1",80509,0 -1868,11162104,Tela o textil de grafito,,"0,1",80509,0 -1869,11162105,Tela o textil de vidrio,,"0,1",80509,0 -1870,11162107,Tela de resina impregnada,,"0,1",80509,0 -1871,11162108,Tela malla de alambre,,"0,1",80509,0 -1872,11162109,Encaje,,"0,1",80509,0 -1873,11162110,Malla red,,"0,1",80509,0 -1874,11162111,Malla,,"0,1",80509,0 -1875,11162112,Telas revestidas,,"0,1",80509,0 -1876,11162113,Telas para tapicería,,"0,1",80509,0 -1877,11162114,Telas o cintas de sujeción por contacto,,"0,1",80509,0 -1878,11162115,Trenza elástica,,"0,1",80509,0 -1879,11162116,Tela de fique o estopa,,"0,1",80509,0 -1880,11162117,Tela de caucho,,"0,1",80509,0 -1881,11162118,Tela de hilado de papel,Tela de hilo de papel,"0,1",80509,0 -1882,11162119,Tela de calcado,,"0,1",80509,0 -1883,11162120,Tela de cernido,,"0,1",80509,0 -1884,11162121,Artículos de pasamanería y ornamentales,,"0,1",80509,0 -1885,11162122,Tela de sujeción,,"0,1",80509,0 -1886,11162123,Tela de cinta,Listón,"0,1",80509,0 -1887,11162124,Tela de fieltro,,"0,1",80509,0 -1888,11162125,Tela de correas,,"0,1",80509,0 -1889,11162126,Tela acolchada,,"0,1",80509,0 -1890,11162127,Tela de camuflaje,,"0,1",80509,0 -1891,11162128,Tela de paracaídas,,"0,1",80509,0 -1892,11162129,Tela marquisette,,"0,1",80509,0 -1893,11162130,Tela dosel,,"0,1",80509,0 -1894,11162131,Tela para ribeteado,,"0,1",80509,0 -1895,11162132,Tela de damasco,,"0,1",80509,0 -1896,11162133,Tela de satín,,"0,1",80509,0 -1897,11162200,Telas no tejidas,,0,80509,0 -1898,11162201,Telas no tejidas ligadas térmicamente,,0,80509,0 -1899,11162202,Telas no tejidas hidroentrelazadas,,0,80509,0 -1900,11162300,Cueros,,0,80509,0 -1901,11162301,Cuero de gamuza,,0,80509,0 -1902,11162302,Cuero de cabra,,0,80509,0 -1903,11162303,Cuero de oveja,,0,80509,0 -1904,11162304,Cuero acharolado,,0,80509,0 -1905,11162305,Cuero de vaca,,0,80509,0 -1906,11162306,Cuero de cerdo,"Cuero de chanchos, Cuero de cochinillos, Cuero de cochinos, Cuero de cuche, Cuero de cuinos, Cuero de gorrinos, Cuero de lechones, Cuero de marranos, Cuero de porcinos, Cuero de puercos,Cuero de lechones",0,80509,0 -1907,11162307,Cuero sintético o de imitación,,0,80509,0 -1908,11162308,Cuero de búfalo,,0,80509,0 -1909,11162309,Cuero de reptil,,0,80509,0 -1910,11162310,Cuero de caballo,,0,80509,0 -1911,11162311,Cuero de becerro,Cuero de ternera,0,80509,0 -1912,11162400,Guata,,0,80509,0 -1913,11162401,Guata de algodón,,0,80509,0 -1914,11162402,Guata sintética,,0,80509,0 -1915,11171500,Aceros básicos,,0,80509,0 -1916,11171501,Acero e24-2 o a37-2,,0,80509,0 -1917,11171600,Aleaciones de acero inoxidable,,0,80509,0 -1918,11171601,Aleación de acero inoxidable 304,,0,80509,0 -1919,11171602,Aleación de acero inoxidable 304i,,0,80509,0 -1920,11171603,Aleación de acero inoxidable 316,,0,80509,0 -1921,11171604,Aleación de ferroníquel,,0,80509,0 -1922,11172000,Aleaciones de aluminio,,"0,1",80509,0 -1923,11172001,Aleación de aluminio 7178,,"0,1",80509,0 -1924,11172002,Aluminio refundido,Refundición de aluminio,"0,1",80509,0 -1925,11172003,Aleación de hierro y aluminio,,"0,1",80509,0 -1926,11172200,Aleaciones con base de magnesio,,"0,1",80509,0 -1927,11172201,Aleación de aluminio y manganeso,,"0,1",80509,0 -1928,11181500,Óxidos metálicos no férreos,,0,80509,0 -1929,11181501,Óxido de molibdeno,,0,80509,0 -1930,11181502,Óxido de titanio,,0,80509,0 -1931,11181503,Óxido de indio,,0,80509,0 -1932,11181504,Óxido de estaño,,0,80509,0 -1933,11181505,Arcilla de sellado,,0,80509,0 -1934,11181506,Óxido de vanadio,,0,80509,0 -1935,11181507,Óxido de níquel,,0,80509,0 -1936,11181508,Óxido de manganeso,,0,80509,0 -1937,11191500,Cuerpos sólidos de metal,,0,80509,0 -1938,11191501,Sólidos de níquel,,0,80509,0 -1939,11191502,Sólidos de acero básico,,0,80509,0 -1940,11191503,Sólidos de aleación ferrosa,,0,80509,0 -1941,11191504,Sólidos de aleación no ferrosa,,0,80509,0 -1942,11191505,Sólidos de súper aleación,,0,80509,0 -1943,11191600,Chatarra,,0,80509,0 -1944,11191601,Chatarra de níquel,,0,80509,0 -1945,11191602,Chatarra de acero básico,,0,80509,0 -1946,11191603,Chatarra de aleación ferrosa,,0,80509,0 -1947,11191604,Chatarra de aleación no ferrosa,,0,80509,0 -1948,11191605,Chatarra de súper aleación,,0,80509,0 -1950,11191607,Chatarra de cobre,,0,80509,0 -1951,11191608,Chatarra de plomo,,0,80509,0 -1952,11191609,Chatarra de zinc,,0,80509,0 -1953,11191610,Chatarra de aluminio,,0,80509,0 -1954,11191611,Chatarra de estaño,,0,80509,0 -1955,11191800,Subproductos del metal,,0,80509,0 -1956,11191801,Escoria de manganeso,Desecho de manganeso,0,80509,0 -1957,11191802,Sulfuro de cobre,,0,80509,0 -1958,11191803,Sulfuro de níquel,,0,80509,0 -1959,12131500,Explosivos,,1,80509,0 -1960,12131501,Dinamita,,1,80509,0 -1961,12131502,Cartuchos explosivos,,1,80509,0 -1962,12131503,Explosivos propelentes,,1,80509,0 -1963,12131504,Cargas explosivas,,1,80509,0 -1964,12131505,Explosivos plásticos,,1,80509,0 -1965,12131506,Explosivos aluminizados,,1,80509,0 -1966,12131507,Explosivos de nitrato de amonio,,1,80509,0 -1967,12131508,Explosivos de polvo de nitroglicerina,,1,80509,0 -1968,12131509,Nitrato de amonio y fuel oil - anfo,,1,80509,0 -1969,12131600,Pirotecnia,,0,80509,0 -1970,12131601,Fuegos artificiales,,0,80509,0 -1971,12131602,Señales de niebla,,0,80509,0 -1972,12131603,Cohetes de lluvia,,0,80509,0 -1973,12131604,Antorchas,,0,80509,0 -1974,12131605,Materiales pirotécnicos para el teatro o la televisión,,0,80509,0 -1975,12131700,Encendedores,,1,80509,0 -1976,12131701,Estopines,,1,80509,0 -1977,12131702,Detonadores,,1,80509,0 -1978,12131703,Mechas explosivas,,1,80509,0 -1979,12131704,Iniciadores explosivos,,1,80509,0 -1980,12131705,Fulminantes explosivos,,1,80509,0 -1981,12131706,Fósforos,,1,80509,0 -1982,12131707,Encendedores,,1,80509,0 -1983,12131708,Caja detonadora,,1,80509,0 -1984,12131709,Apisonadores de explosivos,,1,80509,0 -1985,12131800,Propulsores,,0,80509,0 -1986,12131801,Propelente en polvo,Propulsores en polvo,0,80509,0 -1987,12131802,Propelente sólido,Propulsores sólido,0,80509,0 -1988,12131803,Propelente de pistola,Propulsores de pistola,0,80509,0 -1989,12131804,Propelente de alta energía,Propulsores de alta energía,0,80509,0 -1990,12131805,Propelente en gel,Propulsores en gel,0,80509,0 -1991,12131806,Propelente híbridos,Propulsores híbridos,0,80509,0 -1992,12141500,Metales de tierra,,1,80509,0 -1993,12141501,Berilio be,,1,80509,0 -1994,12141502,Magnesio mg,,1,80509,0 -1995,12141503,Calcio ca,,1,80509,0 -1996,12141504,Estroncio sr,,"0,1",80509,0 -1997,12141505,Bario ba,,1,80509,0 -1998,12141506,Radio ra,,0,80509,0 -1999,12141600,Metales de tierra rara,,0,80509,0 -2000,12141601,Cerio ce,,1,80509,0 -2001,12141602,Disprosio dy,,0,80509,0 -2002,12141603,Erbio er,,0,80509,0 -2003,12141604,Europio eu,,0,80509,0 -2004,12141605,Gadolinio gd,,0,80509,0 -2005,12141606,Holmio ho,,0,80509,0 -2006,12141607,Lantano la,,0,80509,0 -2007,12141608,Lutecio lu,,0,80509,0 -2008,12141609,Neodimio nd,,0,80509,0 -2009,12141610,Praseodimio pr,,0,80509,0 -2010,12141611,Promecio pm,,0,80509,0 -2011,12141612,Samario sm,,0,80509,0 -2012,12141613,Escandio sc,,0,80509,0 -2013,12141614,Terbio tb,,0,80509,0 -2014,12141615,Tulio tm,,0,80509,0 -2015,12141616,Iterbio yb,,0,80509,0 -2016,12141617,Itrio y,,0,80509,0 -2017,12141700,Metales de transición,,0,80509,0 -2018,12141701,Actinio ac,,0,80509,0 -2019,12141702,Aluminio al,,"0,1",80509,0 -2020,12141703,Americio am,,0,80509,0 -2021,12141704,Antimonio sb,,"0,1",80509,0 -2022,12141705,Berkelio bk,,0,80509,0 -2023,12141706,Bismuto bi,,0,80509,0 -2024,12141707,Cadmio ca,,"0,1",80509,0 -2025,12141708,Californio cf,,0,80509,0 -2026,12141709,Cromo cr,,"0,1",80509,0 -2027,12141710,Cobalto co,,"0,1",80509,0 -2028,12141711,Cobre cu,,"0,1",80509,0 -2029,12141712,Curio cm,,0,80509,0 -2030,12141713,Einstenio es,,0,80509,0 -2031,12141714,Fermio fm,,0,80509,0 -2032,12141715,Galio ga,,1,80509,0 -2033,12141716,Germanio ge,,0,80509,0 -2034,12141717,Oro au,,0,80509,0 -2035,12141718,Hafnio hf,,"0,1",80509,0 -2036,12141719,Indio in,,0,80509,0 -2037,12141720,Iridio ir,,0,80509,0 -2038,12141721,Hierro fe,,"0,1",80509,0 -2039,12141722,Lawrencio lr,,0,80509,0 -2040,12141723,Plomo pb,,"0,1",80509,0 -2041,12141724,Manganeso mn,,"0,1",80509,0 -2042,12141725,Mendelevio md,,0,80509,0 -2043,12141726,Mercurio hg,,1,80509,0 -2044,12141727,Molibdeno mo,,"0,1",80509,0 -2045,12141728,Neptunio np,,0,80509,0 -2046,12141729,Níquel ni,,"0,1",80509,0 -2047,12141730,Niobio nb,,0,80509,0 -2048,12141731,Nobelio no,,0,80509,0 -2049,12141732,Osmio os,,"0,1",80509,0 -2050,12141733,Paladio pd,,0,80509,0 -2051,12141734,Platino pyt,,0,80509,0 -2052,12141735,Plutonio pu,,0,80509,0 -2053,12141736,Protactinio pa,,0,80509,0 -2054,12141737,Renio re,,0,80509,0 -2055,12141738,Rodio rh,,0,80509,0 -2056,12141739,Rutenio ru,,0,80509,0 -2057,12141740,Plata ag,,"0,1",80509,0 -2058,12141741,Tántalo ta,,0,80509,0 -2059,12141742,Tecnecio te,,0,80509,0 -2060,12141743,Talio tl,,"0,1",80509,0 -2061,12141744,Torio th,,0,80509,0 -2062,12141745,Estaño sn,,"0,1",80509,0 -2063,12141746,Titanio ti,,"0,1",80509,0 -2064,12141747,Tungsteno w,,"0,1",80509,0 -2065,12141748,Uranio u,,1,80509,0 -2066,12141749,Vanadio v,,1,80509,0 -2067,12141750,Zinc zn,,0,80509,0 -2068,12141751,Zirconio zr,,0,80509,0 -2069,12141752,Bohrio bh,,0,80509,0 -2070,12141753,Dubnio db,,0,80509,0 -2071,12141754,Hassio hs,,0,80509,0 -2072,12141755,Rutherfordio rf,,0,80509,0 -2073,12141756,Seaborgio sg,,0,80509,0 -2074,12141757,Ununnilium uum,,0,80509,0 -2075,12141758,Unununium uuu,,0,80509,0 -2076,12141759,Ununbium uub,,0,80509,0 -2077,12141760,Polonio po,,0,80509,0 -2078,12141800,Metales álcalis,,0,80509,0 -2079,12141801,Cesio cs,,1,80509,0 -2080,12141802,Francio fm,,0,80509,0 -2081,12141803,Litio li,,1,80509,0 -2082,12141804,Potasio k,,1,80509,0 -2083,12141805,Rubidio rb,,1,80509,0 -2084,12141806,Sodio na,,1,80509,0 -2085,12141900,No-metales y gases elementales y puros,,0,80509,0 -2086,12141901,Cloro cl,,1,80509,0 -2087,12141902,Hidrógeno h,,1,80509,0 -2088,12141903,Nitrógeno n,,"0,1",80509,0 -2089,12141904,Oxígeno o,,1,80509,0 -2090,12141905,Flúor f,,"0,1",80509,0 -2091,12141906,Arsénico as,,1,80509,0 -2093,12141908,Carbono c,,"0,1",80509,0 -2094,12141909,Fósforo p,,"0,1",80509,0 -2095,12141910,Selenio se,,"0,1",80509,0 -2096,12141911,Silicona si,,0,80509,0 -2097,12141912,Azufre s,,1,80509,0 -2098,12141913,Telurio te,,"0,1",80509,0 -2099,12141914,Astato at,,0,80509,0 -2100,12141915,Bromo br,,1,80509,0 -2101,12141916,Yodo i,,1,80509,0 -2102,12142000,Gases nobles,,0,80509,0 -2103,12142001,Gas xenón xe,,1,80509,0 -2104,12142002,Gas radón rn,,0,80509,0 -2105,12142003,Gas criptón kr,,1,80509,0 -2106,12142004,Gas argón ar,,1,80509,0 -2107,12142005,Gas helio he,,1,80509,0 -2108,12142006,Gas neón ne,,1,80509,0 -2109,12142100,Gases naturales y gases mixtos,,0,80509,0 -2110,12142101,Gases compuestos de hidrógeno,,"0,1",80509,0 -2111,12142102,Gases compuestos clorados,,"0,1",80509,0 -2112,12142103,Amoniaco,,"0,1",80509,0 -2113,12142104,Gas dióxido de carbono,Anhídrido carbónico,"0,1",80509,0 -2114,12142105,Aire industrial,,"0,1",80509,0 -2115,12142106,Mezclas de gas inerte,Mezclas de gases inertes,"0,1",80509,0 -2116,12142107,Sulfato de hidrógeno,"Gas de alcantarilla, Ácido hidrosulfúrico","0,1",80509,0 -2117,12142108,Monóxido de carbono,Gas carbonoso,"0,1",80509,0 -2118,12142200,Isótopos,,0,80509,0 -2119,12142201,Disolvente deuterado,,0,80509,0 -2120,12142202,Agua pesada,Agua deuterada,0,80509,0 -2121,12142203,Fuentes alfa,,0,80509,0 -2122,12142204,Fuentes beta,,0,80509,0 -2123,12142205,Fuentes de cobalto,,0,80509,0 -2124,12142206,Fuentes gamma,,0,80509,0 -2125,12142207,Fuentes radio – isótopo,Fuentes de radioisótopos,0,80509,0 -2126,12142208,Fuentes de calibración,,0,80509,0 -2127,12161500,Indicadores y reactivos,,0,80509,0 -2128,12161501,Etiquetas de afinidad,Inhibidores de enzimas,0,80509,0 -2129,12161502,Agentes de entrecruzamiento,,0,80509,0 -2130,12161503,Kits de reactivos,,0,80509,0 -2131,12161504,Reactivos sulfhidrilo,,0,80509,0 -2132,12161505,Agentes de intercalación,,0,80509,0 -2133,12161506,Agentes de desvío,Desviadores químicos,0,80509,0 -2134,12161507,Reactivo cupferrón,,0,80509,0 -2135,12161600,Catalizadores,,"0,1",80509,0 -2136,12161601,Catalizadores ácidos,,"0,1",80509,0 -2137,12161602,Catalizadores de combustión,,"0,1",80509,0 -2138,12161603,Catalizadores a la medida,Catalizadores personalizado,"0,1",80509,0 -2139,12161604,Catalizadores de ruptura,,"0,1",80509,0 -2140,12161605,Catalizadores fe tratamiento,Catalizadores para tratamiento,"0,1",80509,0 -2141,12161606,Catalizadores reformadores,Reformado catalítico,"0,1",80509,0 -2142,12161700,Amortiguadores,,0,80509,0 -2143,12161701,Mezclas de anfólito,,0,80509,0 -2144,12161702,Soluciones reguladoras de bicarbonato,,0,80509,0 -2145,12161703,Otras soluciones reguladoras,Otras disoluciones amortiguadoras,0,80509,0 -2146,12161704,Soluciones reguladoras ácidas,Disoluciones amortiguadoras ácidas,0,80509,0 -2147,12161705,Soluciones reguladoras básicas,Disoluciones amortiguadoras básicas,0,80509,0 -2148,12161706,Soluciones reguladoras neutrales,Disoluciones amortiguadoras neutrales,0,80509,0 -2149,12161800,Coloides,,0,80509,0 -2150,12161801,Geles,,0,80509,0 -2151,12161802,Suspensiones,,0,80509,0 -2152,12161803,Aerosoles,,0,80509,0 -2153,12161804,Emulsiones,,0,80509,0 -2154,12161805,Agentes gelificantes naturales,Agentes aglutinante hidrosoluble natural,0,80509,0 -2155,12161806,Agentes gelificantes sintéticos,Agentes aglutinante hidrosoluble sintético,0,80509,0 -2156,12161807,Estabilizadores de gel,,0,80509,0 -2157,12161808,Agentes de suspensión,,0,80509,0 -2158,12161809,Gel de silicona,Silicona en gel,0,80509,0 -2159,12161900,Surfactantes,,0,80509,0 -2160,12161901,Agentes anti – espuma,,0,80509,0 -2161,12161902,Surfactantes detergentes,Detergentes tensoactivos,0,80509,0 -2162,12161903,Agentes de espuma,Agentes espumantes,0,80509,0 -2163,12161904,Agentes de dispersión,,0,80509,0 -2164,12161905,Enjuagues,,0,80509,0 -2165,12161906,Agentes de humectación,Agentes humectantes,0,80509,0 -2166,12161907,Aditivos de inundación de agua,,0,80509,0 -2167,12161908,Alkil sulfatos,Sulfato de alquilo,0,80509,0 -2168,12161909,Betaínas,,0,80509,0 -2169,12161910,Sulfatos de éter,,0,80509,0 -2170,12161911,Cuaternarios,,0,80509,0 -2171,12161912,Sultaína,,0,80509,0 -2172,12161913,Coadyuvante de rociado,Adyuvante de rociado,0,80509,0 -2173,12162000,Plastificantes,,0,80509,0 -2174,12162002,Poliméricos,Polímeros,0,80509,0 -2175,12162003,Aceites agrícolas,,0,80509,0 -2176,12162004,Sulfonamidas,Sulfas,0,80509,0 -2177,12162005,Glutaratos,,0,80509,0 -2178,12162006,Plastificante ester aromático,Plastificadores de éster aromático,0,80509,0 -2179,12162100,Retardadores de llama,,0,80509,0 -2180,12162101,Retardantes brominados,Retardantes de llama bromados,0,80509,0 -2181,12162200,Anti-oxidantes,Ácido alfa lipoico (ALA),0,80509,0 -2182,12162201,Ácido ascórbico,Vitamina C,0,80509,0 -2183,12162202,Beta caroteno,Caroteno,0,80509,0 -2184,12162203,Butilhidroxianisol,Hidroxibutilanisol,0,80509,0 -2185,12162204,Butilado hidroxitolueno,Butil hidroxitolueno,0,80509,0 -2186,12162205,Citrato de calcio,"Citrato cálcico, Sal amarga",0,80509,0 -2187,12162206,Cantaxantina,,0,80509,0 -2188,12162207,Melatonina,,0,80509,0 -2189,12162208,Ácido nordihidroguaiarético,,0,80509,0 -2190,12162209,Galato de propilo,,0,80509,0 -2191,12162210,Silibinina,Silibina,0,80509,0 -2192,12162211,Dióxido de azufre,,1,80509,0 -2193,12162212,Ubicquinona o coenzima q10,,0,80509,0 -2194,12162300,Agentes de curación,,0,80509,0 -2195,12162301,Agentes de curado transmitidos por el agua,Agentes de curado de cemento,0,80509,0 -2196,12162302,Aceleradores de cemento,Aditivo acelerador de fraguado,0,80509,0 -2197,12162303,Retardantes de cemento,Aditivos retardadores de fraguado,0,80509,0 -2198,12162304,Acelerador adhesivo,,0,80509,0 -2199,12162305,Aditivo de concreto,,0,80509,0 -2200,12162700,Aditivos de pérdida de fluido,,0,80509,0 -2201,12162701,Aditivos de pérdida de fluido de polímeros modificados,,0,80509,0 -2202,12162702,Aditivos de pérdida de fluido de polímeros naturales,,0,80509,0 -2203,12162800,Reductores de fricción,,0,80509,0 -2204,12162801,Reductores de fricción aniónico,,0,80509,0 -2205,12162802,Reductores de fricción catódica,,0,80509,0 -2206,12162900,Agentes de control de parafina asfaltenos,,0,80509,0 -2207,12162901,Agentes de control de asfaltenos de parafina tipo solvente,,0,80509,0 -109,10141609,Sujetadores,,0,80509,0 -110,10141610,Bozales,,0,80509,0 -111,10141611,Soportes para correas,,0,80509,0 -112,10151500,Semillas y plántulas vegetales,Semillas y vástagos vegetales,0,80509,0 -113,10151501,Semillas o plántulas de fríjol,"Semillas o vástago primario de fríjol, Semillas o vástago primario de fríjol blanco, Semillas o vástago primario de fríjol negro, Semillas o vástago primario de fríjol rojo adzuki",0,80509,0 -114,10151502,Semillas o plántulas de zanahoria,Semillas o vástago primario de zanahoria,0,80509,0 -115,10151503,Semillas o plántulas de apio,Semillas o vástago primario de apio,0,80509,0 -116,10151504,Semillas o plántulas de chiles,Semillas o vástago primario de chiles,0,80509,0 -117,10151505,Semillas o plántulas de calabacín,Semillas o vástago primario de calabacín,0,80509,0 -118,10151506,Semillas o plántulas de alverja,"Semillas o vástago primario de chícharo, Semillas o vástago primario de guisante",0,80509,0 -119,10151507,Semillas o plántulas de pepino cohombro,Semillas o vástago primario de pepino cohombro,0,80509,0 -120,10151508,Semillas o plántulas de berenjena,Semillas o vástago primario de berenjena,0,80509,0 -121,10151509,Semillas o plántulas de endivias,Semillas o vástago primario de endivias,0,80509,0 -122,10151510,Semillas o plántulas de ajo,Semillas o vástago primario de ajo,0,80509,0 -123,10151511,Semillas o plántulas de puerro,Semillas o vástago primario de puerro,0,80509,0 -124,10151512,Semillas o plántulas de lechuga,Semillas o vástago primario de lechuga,0,80509,0 -125,10151513,Semillas o plántulas de maíz,Semillas o vástago primario de maíz,0,80509,0 -126,10151514,Semillas o plántulas de melón,Semillas o vástago primario de melón,0,80509,0 -127,10151515,Semillas o plántulas cebolla,Semillas o vástago primario de cebolla,0,80509,0 -128,10151516,Semillas o plántulas de soya,Semillas o vástago primario de soya,0,80509,0 -129,10151517,Semillas o plántulas de espinaca,Semillas o vástago primario de espinaca,0,80509,0 -130,10151518,Semillas o plántulas de tomate,Semillas o vástago primario de tomate,0,80509,0 -131,10151519,Semillas o plántulas de nabo,Semillas o vástago primario de nabo,0,80509,0 -132,10151520,Semillas o plántulas de acelga,Semillas o vástago primario de acelga,0,80509,0 -133,10151521,Semillas o plántulas de pimiento morrón,Semillas o vástago primario de pimiento morrón,0,80509,0 -134,10151522,Semillas o plántulas de remolacha,Semillas o vástago primario de betabel,0,80509,0 -135,10151523,Semillas o plántulas de coliflor,Semillas o vástago primario de coliflor,0,80509,0 -136,10151524,Semillas o plántulas de perejil,Semillas o vástago primario de perejil,0,80509,0 -137,10151525,Semillas o plántulas de brócoli,Semillas o vástago primario de brócoli,0,80509,0 -138,10151526,Semillas o plántulas de repollo,Semillas o vástago primario de col,0,80509,0 -139,10151527,Semillas o plántulas de papa,Semillas o vástago primario de patata,0,80509,0 -140,10151528,Semillas o plántulas de batata,Semillas o vástago primario de camote,0,80509,0 -141,10151529,Semillas o plántulas de calabaza,Semillas o vástago primario de calabaza,0,80509,0 -142,10151530,Semillas o plántulas de rábano,"Semillas o vástago primario de rabanito, Semillas o vástago primario de rábano",0,80509,0 -143,10151531,Semillas o plántulas de repollitos de bruselas,Semillas o vástago primario de col de Bruselas,0,80509,0 -144,10151532,Semillas o plántulas de ahuyama,Semillas o vástago primario de calabaza de castilla,0,80509,0 -145,10151533,Semillas o plántulas de okra,Semillas o vástago primario de okra,0,80509,0 -146,10151534,Semillas o plántulas de melón cantalupo,Semillas o vástago primario de melón cantalupo,0,80509,0 -147,10151535,Semillas o plántulas de maní,Semillas o vástago primario de cacahuate,0,80509,0 -148,10151536,Semillas o plántulas de caigua,Semillas o vástago primario de caigua,0,80509,0 -149,10151537,Semillas o plántulas de espárrago,Semillas o vástago primario de espárrago,0,80509,0 -150,10151538,Semillas o plántulas de garbanzo,Semillas o vástago primario de garbanzo,0,80509,0 -151,10151539,Semillas o plántulas de haba,Semillas o vástago primario de haba,0,80509,0 -152,10151600,Semillas de cereales,,0,80509,0 -153,10151601,Semillas de trigo,,0,80509,0 -154,10151602,Semillas de canola,Semillas de colza,0,80509,0 -155,10151603,Semillas de cebada,,0,80509,0 -156,10151604,Semillas de mijo,,0,80509,0 -157,10151605,Semillas de avena,,0,80509,0 -158,10151606,Semillas de ajonjolí,Semillas de sésamo,0,80509,0 -159,10151607,Semillas de linaza,Semillas de lino,0,80509,0 -160,10151608,Semillas de aceite de ricino,Semillas de ricino,1,80509,0 -161,10151609,Semillas de maíz,,0,80509,0 -162,10151610,Semillas de centeno,,0,80509,0 -163,10151611,Semillas de sorgo,,0,80509,0 -164,10151612,Semillas o plántulas de kiwicha,Semillas o vástagos de amaranto,0,80509,0 -165,10151613,Semillas o plántulas de quínoa,Semillas o vástagos de quinua,0,80509,0 -166,10151700,Semillas y plántulas de hierba y forraje,Semillas y vástagos de hierba y forraje,0,80509,0 -167,10151701,Semillas o plántulas de arroz,,0,80509,0 -168,10151702,Semillas o plántulas de trébol,,0,80509,0 -169,10151703,Semillas o plántulas de alfalfa,,0,80509,0 -170,10151704,Semillas o plántulas de pasto,Semillas o vástagos para césped,0,80509,0 -171,10151705,Semillas o plántulas de veza (gachas / guija),,0,80509,0 -172,10151706,Semillas o plántulas de guar,,0,80509,0 -173,10151800,Semillas y plántulas de especias,Semillas y vástagos de especias,0,80509,0 -174,10151801,Semillas o plántulas de pimienta,,0,80509,0 -175,10151802,Semillas o plántulas de vainilla,,0,80509,0 -176,10151803,Semillas o plántulas de canela,,0,80509,0 -177,10151804,Semillas o plántulas de clavo de olor,,0,80509,0 -178,10151805,Semillas o plántulas de cilantro,,0,80509,0 -179,10151806,Semillas o plántulas de jengibre,,0,80509,0 -180,10151807,Semillas o plántulas de azafrán,,0,80509,0 -181,10151808,Semillas o plántulas de tomillo,,0,80509,0 -182,10151809,Semillas o plántulas de curry,Semillas o vástagos de curri,0,80509,0 -183,10151810,Semillas o plántulas de mostaza,,0,80509,0 -184,10151811,Semillas o plántulas de ginseng,,0,80509,0 -185,10151812,Semillas o plántulas de champiñones,,0,80509,0 -186,10151813,Semillas o plántulas de sacha inchi,,0,80509,0 -187,10151814,Semillas o plántulas de achiote,,0,80509,0 -188,10151815,Semillas o plántulas de kudzu,,0,80509,0 -189,10151816,Semillas o plántulas de albahaca,,0,80509,0 -190,10151817,Semillas o plántulas de anís,Semillas o vástago de badiana,0,80509,0 -2209,12162903,Agentes de control de asfaltenos de parafina tipo dispersante,,0,80509,0 -2210,12163100,Anti lodo (sludger),,0,80509,0 -2211,12163101,Aditivos anti – lodos,,0,80509,0 -2212,12163102,Desfloculante,,0,80509,0 -2213,12163300,Agentes de expansión,,0,80509,0 -2214,12163301,Agentes de expansión de cemento,,0,80509,0 -2215,12163400,Extensores,,0,80509,0 -2216,12163401,Extensores de cemento,,0,80509,0 -2217,12163500,Sellador de pozo petrolífero,,0,80509,0 -2218,12163501,Sellantes de cemento,,0,80509,0 -2219,12163600,Inhibidores de corrosión,,0,80509,0 -2220,12163601,Inhibidores de corrosión en la producción de petróleo,,0,80509,0 -2221,12163602,Inhibidores de corrosión en la producción de gas,,0,80509,0 -2222,12163800,Limpiadores químicos,,"0,1",80509,0 -2223,12163801,Eliminadores de sulfuro de hidrógeno,,"0,1",80509,0 -2224,12163802,Eliminadores de oxígeno,,"0,1",80509,0 -2225,12163900,Reguladores de sarro,,0,80509,0 -2226,12163901,Inhibidores de incrustaciones,,0,80509,0 -2227,12163902,Removedores o convertidores de incrustaciones,,0,80509,0 -2228,12164000,Bactericidas,,0,80509,0 -2229,12164001,Microbicidas registrados,,0,80509,0 -2230,12164100,In situ,,0,80509,0 -2231,12164101,Aditivos in situ,,0,80509,0 -2232,12164102,Aditivos ácidos,,0,80509,0 -2233,12164200,Retardadores,,0,80509,0 -2234,12164201,Inhibidores anti – corrosión,,0,80509,0 -2235,12164300,Controladores de hierro,,0,80509,0 -2236,12164301,Aditivos de control de hierro,,0,80509,0 -2237,12164302,Aditivos secuestradores de hierro,,0,80509,0 -2238,12164303,Inhibidor de la corrosión de hierro,,0,80509,0 -2239,12164400,No emulsificadoras,,0,80509,0 -2240,12164401,Aditivos no emulsificantes,,0,80509,0 -2241,12164500,Aditivos de alimentos y fármacos,,0,80509,0 -2242,12164501,Preservativos de alimentos,,0,80509,0 -2243,12164502,sabores o extractos,,0,80509,0 -2244,12164503,Aditivos de fragancia,,"0,1",80509,0 -2245,12164504,Endulzantes,,0,80509,0 -2246,12164505,Excipiente,,0,80509,0 -2247,12164508,Desintegrante,,0,80509,0 -2248,12164509,Agente antiapelmazante,,0,80509,0 -2249,12164600,"Modificadores de papel, plástico y tela",,0,80509,0 -2250,12164601,Agente de dimensionamiento,,0,80509,0 -2251,12164602,Agente de espesamiento,,0,80509,0 -2252,12164603,Abrillantador óptico,Agente blanqueador óptico,0,80509,0 -2253,12164604,Agente antiestática,,0,80509,0 -2254,12164605,Micro – floculante,,0,80509,0 -2255,12164606,Estabilizador de luz y ultravioleta,,0,80509,0 -2256,12164800,Agentes reductores de agua,,0,80509,0 -2257,12164801,Agente reductor de agua en cemento,,0,80509,0 -2258,12164802,Agente detenedor o hinchador de agua,,0,80509,0 -2259,12164803,Repelente estructural de agua,,0,80509,0 -2260,12164900,Agentes de impermeabilización,,0,80509,0 -2261,12164901,Recubrimiento impermeable de uretano,Revestimiento impermeable de uretano,0,80509,0 -2262,12164902,Recubrimiento impermeable de asfalto,Revestimiento impermeable de asfalto,0,80509,0 -2263,12164903,Recubrimiento impermeable epoxi,Revestimiento impermeable epoxi,0,80509,0 -2264,12164904,Recubrimiento impermeable inorgánico,Revestimiento impermeable inorgánico,0,80509,0 -2265,12164905,Agente de impermeabilización,"Aditivo de hidrófugo, Aditivo impermeabilizante",0,80509,0 -2266,12165000,Agentes anti encogimiento,,0,80509,0 -2267,12165001,Concreto anti – contracción del cemento,,0,80509,0 -2268,12165100,Químicos para mejorar la recuperación de petróleo,,0,80509,0 -2269,12165101,Gel de polímero,,0,80509,0 -2270,12165102,Surfactante dispersante,Tensioactivos dispersante,0,80509,0 -2271,12165103,Surfactante polímero álcali asp,Tensioactivos polímero álcali ASP,0,80509,0 -2272,12165104,Polímero surfactante sp,Polímero surfactante SP,0,80509,0 -2273,12171500,Tintes,,0,80509,0 -2274,12171501,Colorantes fluorescentes,,"0,1",80509,0 -2275,12171502,Colorantes ftaleínicos,,"0,1",80509,0 -2276,12171503,Colorantes rosanilina,,"0,1",80509,0 -2277,12171504,Colorantes fdc seguros para alimentos o cosméticos,Colorantes FDC para alimentos o medicamentos o cosméticos,"0,1",80509,0 -2278,12171505,Lacado,,0,80509,0 -2279,12171506,Colorantes naturales,,"0,1",80509,0 -2280,12171507,Colorantes de azufre,,"0,1",80509,0 -2281,12171508,Colorantes de cuba o tina,,"0,1",80509,0 -2282,12171509,Colorantes reactivos,,"0,1",80509,0 -2283,12171510,Colorantes solventes,,"0,1",80509,0 -2284,12171511,Colorantes ácidos,,"0,1",80509,0 -2285,12171600,Pigmentos,,0,80509,0 -2286,12171602,Óxidos metálicos inorgánicos,,0,80509,0 -2287,12171603,Negro carbono,,0,80509,0 -2288,12171604,Dióxido de titanio,,0,80509,0 -2289,12171605,Pigmentos orgánicos,,0,80509,0 -2290,12171606,Zeaxantina,,0,80509,0 -2291,12171607,Verde parís,,0,80509,0 -2292,12171608,Caput mortuum (púrpura de cardenal),,0,80509,0 -2293,12171609,Verde cadmio,,0,80509,0 -2294,12171610,Anaranjado cadmio,,0,80509,0 -2295,12171611,Amarillo cadmio,,0,80509,0 -2296,12171612,Rojo cadmio,,0,80509,0 -2297,12171613,Rojo ocre,,0,80509,0 -2298,12171614,Amarillo ocre,,0,80509,0 -2299,12171615,Sanguina,,0,80509,0 -2300,12171616,Azul de prusia,,0,80509,0 -2301,12171617,Rojo veneciano,,0,80509,0 -2302,12171618,Verde cromo,,0,80509,0 -2303,12171619,Auerolina (amarillo de cobalto),,0,80509,0 -2304,12171620,Amarillo cromo,,0,80509,0 -2305,12171621,Pigmento fluorescente,,0,80509,0 -2306,12171700,Compuestos de colores y dispersiones,,0,80509,0 -2307,12171701,Concentrado de color de polímero,,"0,1",80509,0 -2308,12171702,Dispersiones de pigmento,,"0,1",80509,0 -2309,12171703,Tintas,,"0,1",80509,0 -2310,12181500,Ceras,,0,80509,0 -2311,12181501,Ceras sintéticas,,0,80509,0 -2312,12181502,Ceras naturales,,0,80509,0 -2313,12181503,Parafinas,,0,80509,0 -2314,12181504,Petrolatos,,0,80509,0 -2315,12181600,Aceites,,0,80509,0 -2316,12181601,Aceites sintéticos,,0,80509,0 -2317,12181602,Aceites naturales,,0,80509,0 -2318,12191500,Solventes hidrocarbonatos,,"0,1",80509,0 -2319,12191501,Disolventes aromáticos,,"0,1",80509,0 -2320,12191502,Disolventes alifáticos,,"0,1",80509,0 -2321,12191503,Fenoles o sus sustitutos y derivados,,"0,1",80509,0 -2322,12191504,Alcanos cíclicos,,0,80509,0 -2323,12191600,Solventes oxigenados,,"0,1",80509,0 -2324,12191601,Solventes de alcohol,,"0,1",80509,0 -2325,12191602,Solventes activos,,"0,1",80509,0 -192,10151901,"Semillas, bulbos, plántulas o esquejes de tulipán",,0,80509,0 -193,10151902,"Semillas, plántulas o esquejes de rosa",,0,80509,0 -194,10151903,"Semillas, bulbos, plántulas o esquejes de narciso",,0,80509,0 -195,10151904,Semillas de girasol,"Semillas de acahual, Semillas de mirasol",0,80509,0 -196,10151905,Bulbos o tallos de jacinto,,0,80509,0 -197,10151906,Bulbos de lirio,,0,80509,0 -198,10151907,Semillas o plántulas de veza,,0,80509,0 -199,10152000,Semillas y esquejes de árboles y arbustos,,0,80509,0 -200,10152001,Semillas o esquejes de árboles frutales,,0,80509,0 -201,10152002,Semillas o esquejes de coníferas,,0,80509,0 -202,10152003,Semillas o esquejes de árboles de frutos secos,,0,80509,0 -203,10152004,Plántulas de latifoliados,,0,80509,0 -204,10152005,Plántulas de coníferas,,0,80509,0 -205,10152006,Semillas o yemas de pino,,0,80509,0 -206,10152007,Semillas o yemas de algarrobo,,0,80509,0 -207,10152100,Residuos que no sean de piensos,Residuos que no sean alimento,0,80509,0 -208,10152101,Extracción de los residuos de semilla de babool,,0,80509,0 -209,10152102,Residuos de semilla de colza,,0,80509,0 -210,10152103,Residuo de semillas de linaza,,0,80509,0 -211,10152104,Torta de neem,,0,80509,0 -212,10152200,Semillas de cultivos fibrosos y semilleros,,0,80509,0 -213,10152201,Semillas o plántulas de algodón,,0,80509,0 -214,10152202,Semillas o plántulas de lino,Semillas o vástago de linaza,0,80509,0 -215,10152300,Plántulas y semillas de leguminosas,,0,80509,0 -216,10161500,Árboles y arbustos,,0,80509,0 -217,10161501,Aceitunos,,0,80509,0 -218,10161502,Cafetos,,0,80509,0 -219,10161503,Cacaoteros,Cocoa,0,80509,0 -220,10161504,Manzanos,,0,80509,0 -221,10161505,Peros,,0,80509,0 -222,10161506,Naranjos,,0,80509,0 -223,10161507,Rododendros,Azaleas,0,80509,0 -224,10161508,Plantas de te,,0,80509,0 -225,10161509,Coníferas,,0,80509,0 -226,10161510,Abetos rojos (píceas),Abetos falsos,0,80509,0 -227,10161511,Pinos,,0,80509,0 -228,10161512,Abetos,,0,80509,0 -229,10161513,Palmeras,,0,80509,0 -230,10161514,Casuarina,Roble hembra,0,80509,0 -231,10161515,Ciprés,,0,80509,0 -232,10161516,Eucalipto,,0,80509,0 -233,10161517,Árbol de quinua,Árbol de quina,0,80509,0 -234,10161518,Magnolio,,0,80509,0 -235,10161519,Mioporo,Siempreverde,0,80509,0 -236,10161520,Acalifa,Cola de zorro roja,0,80509,0 -237,10161521,Tecomaria capensis o madreselva del cabo,Bignonia roja,0,80509,0 -238,10161522,Arbusto croton bolaina,Croton variegado,0,80509,0 -239,10161523,Abutillón,Malva,0,80509,0 -240,10161524,Ficus,Higuera,0,80509,0 -241,10161525,Lúcumo,,0,80509,0 -242,10161526,Aguacatal,,0,80509,0 -243,10161527,Guanábano,,0,80509,0 -244,10161528,Carambolo,Membrillo torombolo,0,80509,0 -245,10161529,Ciruelo,,0,80509,0 -246,10161530,Árbol de quince,Árbol de membrillo,0,80509,0 -247,10161600,Plantas florales,"Agave, Caña de azúcar, Mague",0,80509,0 -248,10161602,Poinsettias,Nochebuena,0,80509,0 -249,10161604,Azaleas,,0,80509,0 -250,10161605,Cactos,"Cactus, Nopal",0,80509,0 -251,10161606,Ageratum púrpura,Agérato mexicano,0,80509,0 -252,10161800,Plantas sin flor,,0,80509,0 -253,10161801,Helechos,,0,80509,0 -254,10161802,Hiedras,,0,80509,0 -255,10161803,Filodendros,Trepadora,0,80509,0 -256,10161804,Líquenes,,0,80509,0 -257,10161805,Vides,"Parra, Vid",0,80509,0 -258,10161900,Productos florales secos,,0,80509,0 -259,10161901,Vainas secas,,0,80509,0 -260,10161902,Follaje seco,,0,80509,0 -261,10161903,Helechos secos,,0,80509,0 -262,10161905,Ramas y tallos secos,,0,80509,0 -263,10161906,Penachos de gramíneas secos,,0,80509,0 -264,10161907,Flores secas prensadas,,0,80509,0 -265,10161908,Pétalos secos,,0,80509,0 -266,10171500,Abonos orgánicos y nutrientes para plantas,,0,80509,0 -267,10171501,Estiércol o guano,,0,80509,0 -268,10171502,Hormonas para plantas,,0,80509,0 -269,10171503,Harina de pescado,,0,80509,0 -270,10171504,Abono,,0,80509,0 -271,10171505,Nutriente foliar,,0,80509,0 -272,10171506,Humus,,0,80509,0 -273,10171600,Abonos químicos y nutrientes para plantas,,1,80509,0 -274,10171601,Fertilizante nitrogenado,,1,80509,0 -275,10171602,Fertilizante de potasio,,1,80509,0 -276,10171603,Fertilizante de fósforo,,1,80509,0 -277,10171604,Fertilizante de sulfuro,,1,80509,0 -278,10171605,Mezclas de nitrógeno – fósforo – potasio – npk,,1,80509,0 -279,10171606,Fertilizarte de sílice puro,,1,80509,0 -280,10171607,Fertilizante con magnesio,,1,80509,0 -281,10171608,Fertilizante micro elemento,,1,80509,0 -282,10171609,Fertilizante fosfato de sílice,,1,80509,0 -283,10171610,Fertilizante potasio de sílice,,1,80509,0 -284,10171611,Fertilizante de calcio,,1,80509,0 -285,10171700,Herbicidas,,1,80509,0 -286,10171701,Matamalezas,,1,80509,0 -287,10171702,Fungicidas,,1,80509,0 -288,10171800,Acondicionadores de suelos,,0,80509,0 -289,10171801,Acondicionador orgánico de suelos,,0,80509,0 -290,10171802,Acondicionador inorgánico de suelos,,0,80509,0 -291,10191500,Pesticidas o repelentes de plagas,,1,80509,0 -292,10191506,Mata – roedores,Veneno para roedores,1,80509,0 -293,10191507,Repelentes de aves,,1,80509,0 -294,10191508,Protectores contra termitas,,1,80509,0 -295,10191509,Insecticidas,,1,80509,0 -296,10191510,Abamectina,,1,80509,0 -297,10191511,Fipronil,,1,80509,0 -298,10191700,Dispositivos para control de plagas,,0,80509,0 -299,10191701,Trampas para control animal,,0,80509,0 -300,10191703,Trampas para el control de insectos voladores,,0,80509,0 -301,10191704,Matamoscas,,0,80509,0 -302,10191705,Lazos,,0,80509,0 -303,10191706,Cepos,Ratoneras,0,80509,0 -304,10191707,Repelente ultrasónico de pestes,Ahuyentador Ultrasónico,0,80509,0 -305,10202100,Rosales vivos anaranjados,,0,80509,0 -306,10202101,Rosal vivo alhambra,,0,80509,0 -307,10202102,Rosal vivo aloha,,0,80509,0 -308,10202103,Rosal vivo amber,,0,80509,0 -309,10202104,Rosal vivo apache,,0,80509,0 -310,10202105,Rosal vivo arabia,,0,80509,0 -311,10202106,Rosal vivo bengala,,0,80509,0 -312,10202107,Rosal vivo bibi,,0,80509,0 -313,10202108,Rosal vivo caramba,,0,80509,0 -314,10202109,Rosal vivo caramella,,0,80509,0 -316,10202111,Rosal vivo cartagena,,0,80509,0 -317,10202112,Rosal vivo chanson,,0,80509,0 -318,10202113,Rosal vivo charmer,,0,80509,0 -319,10202114,Rosal vivo cherry brandy,,0,80509,0 -320,10202115,Rosal vivo chilis,,0,80509,0 -321,10202116,Rosal vivo cinnamon,,0,80509,0 -322,10202117,Rosal vivo colandro,,0,80509,0 -323,10202118,Rosal vivo coral sea,,0,80509,0 -324,10202119,Rosal vivo corvette o red corvette,,0,80509,0 -325,10202120,Rosal vivo dark milva,,0,80509,0 -326,10202121,Rosal vivo donna,,0,80509,0 -327,10202122,Rosal vivo dreamer,,0,80509,0 -328,10202123,Rosal vivo el dorado,,0,80509,0 -329,10202124,Rosal vivo el toro,,0,80509,0 -330,10202125,Rosal vivo elena,,0,80509,0 -331,10202126,Rosal vivo ensueño,,0,80509,0 -332,10202127,Rosal vivo euforia,,0,80509,0 -333,10202128,Rosal vivo exótica,,0,80509,0 -334,10202129,Rosal vivo fancy amazon,,0,80509,0 -335,10202130,Rosal vivo fiction,,0,80509,0 -336,10202131,Rosal vivo finess,,0,80509,0 -337,10202132,Rosal vivo flameco,,0,80509,0 -338,10202133,Rosal vivo free spirit,,0,80509,0 -339,10202134,Rosal vivo gelato,,0,80509,0 -340,10202135,Rosal vivo gypsy curiosa,,0,80509,0 -341,10202136,Rosal vivo high and magic,,0,80509,0 -342,10202137,Rosal vivo high and orange magic,,0,80509,0 -343,10202138,Rosal vivo iguana o alegra,,0,80509,0 -344,10202139,Rosal vivo impulse,,0,80509,0 -345,10202140,Rosal vivo indian femma,,0,80509,0 -346,10202141,Rosal vivo indian sunset,,0,80509,0 -347,10202142,Rosal vivo karusso,,0,80509,0 -348,10202143,Rosal vivo kerio,,0,80509,0 -349,10202144,Rosal vivo kiki,,0,80509,0 -350,10202145,Rosal vivo latin circus,,0,80509,0 -351,10202146,Rosal vivo leonisa,,0,80509,0 -352,10202147,Rosal vivo lipstick,,0,80509,0 -353,10202148,Rosal vivo lobita,,0,80509,0 -354,10202149,Rosal vivo luca,,0,80509,0 -355,10202150,Rosal vivo manitou,,0,80509,0 -356,10202151,Rosal vivo mariana,,0,80509,0 -357,10202152,Rosal vivo marjan o pk sensation,,0,80509,0 -358,10202153,Rosal vivo milonga,,0,80509,0 -359,10202154,Rosal vivo milva,,0,80509,0 -360,10202155,Rosal vivo miracle,,0,80509,0 -361,10202156,Rosal vivo mirage,,0,80509,0 -362,10202157,Rosal vivo monte carlo,,0,80509,0 -363,10202158,Rosal vivo movie star,,0,80509,0 -364,10202159,Rosal vivo nikita,,0,80509,0 -365,10202160,Rosal vivo orange flame,,0,80509,0 -366,10202161,Rosal vivo orange france,,0,80509,0 -367,10202162,Rosal vivo orange intuition,,0,80509,0 -368,10202163,Rosal vivo orange unique,,0,80509,0 -369,10202164,Rosal vivo orangine u orangina,,0,80509,0 -370,10202165,Rosal vivo papaya,,0,80509,0 -371,10202166,Rosal vivo pareo,,0,80509,0 -372,10202167,Rosal vivo peach sherbet,,0,80509,0 -373,10202168,Rosal vivo queensday,,0,80509,0 -374,10202169,Rosal vivo rosselle,,0,80509,0 -375,10202170,Rosal vivo royal circus,,0,80509,0 -376,10202171,Rosal vivo sari,,0,80509,0 -377,10202172,Rosal vivo sensual,,0,80509,0 -378,10202173,Rosal vivo soap,,0,80509,0 -379,10202174,Rosal vivo sombrero,,0,80509,0 -380,10202175,Rosal vivo spicy,,0,80509,0 -381,10202176,Rosal vivo star 2000,,0,80509,0 -382,10202177,Rosal vivo summer versilia,,0,80509,0 -383,10202178,Rosal vivo trixx,,0,80509,0 -384,10202179,Rosal vivo tropical amazon,,0,80509,0 -385,10202180,Rosal vivo utopia,,0,80509,0 -386,10202181,Rosal vivo valentine,,0,80509,0 -387,10202182,Rosal vivo verano,,0,80509,0 -388,10202183,Rosal vivo versilia,,0,80509,0 -389,10202184,Rosal vivo voodoo,,0,80509,0 -390,10202185,Rosal vivo wow,,0,80509,0 -391,10202186,Rosal vivo yabadabadoo,,0,80509,0 -392,10202200,Rosales vivos durazno,,0,80509,0 -393,10202201,Rosal vivo alejandra,,0,80509,0 -394,10202202,Rosal vivo azafran,,0,80509,0 -395,10202203,Rosal vivo big fun,,0,80509,0 -396,10202204,Rosal vivo cabaret,,0,80509,0 -397,10202205,Rosal vivo capuccino,,0,80509,0 -398,10202206,Rosal vivo carpe diem,,0,80509,0 -399,10202207,Rosal vivo cosima,,0,80509,0 -400,10202208,Rosal vivo cumbia,,0,80509,0 -401,10202209,Rosal vivo dream,,0,80509,0 -402,10202210,Rosal vivo epoca,,0,80509,0 -403,10202211,Rosal vivo fado,,0,80509,0 -404,10202212,Rosal vivo femma,,0,80509,0 -405,10202213,Rosal vivo guajira,,0,80509,0 -406,10202214,Rosal vivo high and arena,,0,80509,0 -407,10202215,Rosal vivo high and dandy,,0,80509,0 -408,10202216,Rosal vivo high and lucky,,0,80509,0 -409,10202217,Rosal vivo high and peach,,0,80509,0 -410,10202218,Rosal vivo imagination,,0,80509,0 -411,10202219,Rosal vivo isis,,0,80509,0 -412,10202220,Rosal vivo joy o light versilia,,0,80509,0 -413,10202221,Rosal vivo juliet ausjameson,,0,80509,0 -414,10202222,Rosal vivo la parisienne,,0,80509,0 -415,10202223,Rosal vivo la perla,,0,80509,0 -416,10202224,Rosal vivo lovita sunblaze,,0,80509,0 -417,10202225,Rosal vivo malilena o marilena,,0,80509,0 -418,10202226,Rosal vivo monyna,,0,80509,0 -419,10202227,Rosal vivo nectarine,,0,80509,0 -420,10202228,Rosal vivo oriental curiosa,,0,80509,0 -421,10202229,Rosal vivo osiana,,0,80509,0 -422,10202230,Rosal vivo peach avalanche,,0,80509,0 -423,10202231,Rosal vivo peach deja vu,,0,80509,0 -424,10202232,Rosal vivo picanto,,0,80509,0 -425,10202233,Rosal vivo prima donna,,0,80509,0 -426,10202234,Rosal vivo sheril,,0,80509,0 -427,10202235,Rosal vivo sirocco,,0,80509,0 -428,10202236,Rosal vivo tamara,,0,80509,0 -429,10202237,Rosal vivo taxo,,0,80509,0 -430,10202238,Rosal vivo trust,,0,80509,0 -431,10202239,Rosal vivo valencia,,0,80509,0 -432,10202240,Rosal vivo vinci,,0,80509,0 -433,10202241,Rosal vivo wanda,,0,80509,0 -434,10202300,Rosales vivos rosados,,0,80509,0 -435,10202301,Rosal vivo aerobic,,0,80509,0 -436,10202302,Rosal vivo after party,,0,80509,0 -437,10202303,Rosal vivo amsterdam,,0,80509,0 -438,10202304,Rosal vivo aqua rose,,0,80509,0 -439,10202305,Rosal vivo attache,,0,80509,0 -440,10202306,Rosal vivo attitude,,0,80509,0 -441,10202307,Rosal vivo ballet,,0,80509,0 -442,10202308,Rosal vivo belami,,0,80509,0 -443,10202309,Rosal vivo bella voo o belle vue,,0,80509,0 -445,10202311,Rosal vivo blushing akito,,0,80509,0 -446,10202312,Rosal vivo brooke,,0,80509,0 -447,10202313,Rosal vivo bugatti,,0,80509,0 -448,10202314,Rosal vivo cadillac,,0,80509,0 -449,10202315,Rosal vivo carnaval,,0,80509,0 -450,10202316,Rosal vivo cereza,,0,80509,0 -451,10202317,Rosal vivo charming unique,,0,80509,0 -452,10202318,Rosal vivo cherry o,,0,80509,0 -453,10202319,Rosal vivo ciciolina,,0,80509,0 -454,10202320,Rosal vivo classic cezanne,,0,80509,0 -455,10202321,Rosal vivo clasic duett,,0,80509,0 -456,10202322,Rosal vivo cosmiq,,0,80509,0 -457,10202323,Rosal vivo dark engagement,,0,80509,0 -458,10202324,Rosal vivo daytona,,0,80509,0 -459,10202325,Rosal vivo dekora,,0,80509,0 -460,10202326,Rosal vivo dolores,,0,80509,0 -461,10202327,Rosal vivo eliza,,0,80509,0 -462,10202328,Rosal vivo flash baccara,,0,80509,0 -463,10202329,Rosal vivo full house,,0,80509,0 -464,10202330,Rosal vivo funky,,0,80509,0 -465,10202331,Rosal vivo giliane,,0,80509,0 -466,10202332,Rosal vivo gran europe,,0,80509,0 -467,10202333,Rosal vivo habari,,0,80509,0 -468,10202334,Rosal vivo hanseat,,0,80509,0 -469,10202335,Rosal vivo high and amazing,,0,80509,0 -470,10202336,Rosal vivo high and bonita,,0,80509,0 -471,10202337,Rosal vivo high and booming,,0,80509,0 -472,10202338,Rosal vivo high and fantasy,,0,80509,0 -473,10202339,Rosal vivo high and rich,,0,80509,0 -474,10202340,Rosal vivo hot lady,,0,80509,0 -475,10202341,Rosal vivo hot princess,,0,80509,0 -476,10202342,Rosal vivo inspiration,,0,80509,0 -477,10202343,Rosal vivo jeimy,,0,80509,0 -478,10202344,Rosal vivo kachita,,0,80509,0 -479,10202345,Rosal vivo karen,,0,80509,0 -480,10202346,Rosal vivo kenji,,0,80509,0 -481,10202347,Rosal vivo kiko,,0,80509,0 -482,10202348,Rosal vivo laser,,0,80509,0 -483,10202349,Rosal vivo latin duett,,0,80509,0 -484,10202350,Rosal vivo latin fever,,0,80509,0 -485,10202351,Rosal vivo lifestyle,,0,80509,0 -486,10202352,Rosal vivo light orlando,,0,80509,0 -487,10202353,Rosal vivo lovely dreams,,0,80509,0 -488,10202354,Rosal vivo loyalty,,0,80509,0 -489,10202355,Rosal vivo malibu,,0,80509,0 -490,10202356,Rosal vivo mata-hari,,0,80509,0 -491,10202357,Rosal vivo memphis,,0,80509,0 -492,10202358,Rosal vivo mi amor,,0,80509,0 -493,10202359,Rosal vivo miami,,0,80509,0 -494,10202360,Rosal vivo michelle,,0,80509,0 -495,10202361,Rosal vivo mikaela,,0,80509,0 -496,10202362,Rosal vivo orchestra,,0,80509,0 -497,10202363,Rosal vivo orlando,,0,80509,0 -498,10202364,Rosal vivo osadia,,0,80509,0 -499,10202365,Rosal vivo paeonia freelander,,0,80509,0 -500,10202366,Rosal vivo paula,,0,80509,0 -501,10202367,Rosal vivo pavarotti,,0,80509,0 -502,10202368,Rosal vivo pink intuition,,0,80509,0 -503,10202369,Rosal vivo poison,,0,80509,0 -504,10202370,Rosal vivo princess,,0,80509,0 -505,10202371,Rosal vivo queen mary,,0,80509,0 -506,10202372,Rosal vivo raphaela,,0,80509,0 -507,10202373,Rosal vivo raspberry ice,,0,80509,0 -508,10202374,Rosal vivo ravel,,0,80509,0 -509,10202375,Rosal vivo riviera,,0,80509,0 -510,10202376,Rosal vivo sade,,0,80509,0 -511,10202377,Rosal vivo sashimi,,0,80509,0 -512,10202378,Rosal vivo shanya,,0,80509,0 -513,10202379,Rosal vivo shocking versilia,,0,80509,0 -514,10202380,Rosal vivo solitaire,,0,80509,0 -515,10202381,Rosal vivo something different,,0,80509,0 -516,10202382,Rosal vivo splendid renate,,0,80509,0 -517,10202383,Rosal vivo star,,0,80509,0 -518,10202384,Rosal vivo sweet candia,,0,80509,0 -519,10202385,Rosal vivo sweet moments,,0,80509,0 -520,10202386,Rosal vivo sweet unique,,0,80509,0 -521,10202387,Rosal vivo taboo,,0,80509,0 -522,10202388,Rosal vivo timona,,0,80509,0 -523,10202389,Rosal vivo topaz,,0,80509,0 -524,10202390,Rosal vivo vogue,,0,80509,0 -525,10202391,Rosal vivo voila,,0,80509,0 -526,10202392,Rosal vivo wild one,,0,80509,0 -527,10202393,Rosal vivo yves piaget,,0,80509,0 -528,10211700,Astromelia viva,Lirios de campo vivos,0,80509,0 -529,10211701,Astromelia viva agropoli,Lirio de campo vivo agropoli,0,80509,0 -530,10211702,Astromelia viva bourgogne,Lirio de campo vivo bourgogne,0,80509,0 -531,10211703,Astromelia viva cairo,Lirio de campo vivo cairo,0,80509,0 -532,10211704,Astromelia viva charmes,Lirio de campo vivo charmes,0,80509,0 -533,10211705,Astromelia viva cherry bay,Lirio de campo vivo cherry bay,0,80509,0 -534,10211706,Astromelia viva cherry white,Lirio de campo vivo cherry white,0,80509,0 -535,10211707,Astromelia viva dame blanche,Lirio de campo vivo dame blanche,0,80509,0 -536,10211708,Astromelia viva diamond,Lirio de campo vivo diamond,0,80509,0 -537,10211709,Astromelia viva gran canaria,Lirio de campo vivo gran canaria,0,80509,0 -538,10211710,Astromelia viva harlekijn,Lirio de campo vivo harlekijn,0,80509,0 -539,10211711,Astromelia viva indian summer,Lirio de campo vivo indian summer,0,80509,0 -540,10211712,Astromelia viva jamaica,Lirio de campo vivo jamaica,0,80509,0 -541,10211713,Astromelia viva macondo,Lirio de campo vivo macondo,0,80509,0 -542,10211714,Astromelia viva mistique,Lirio de campo vivo mistique,0,80509,0 -543,10211715,Astromelia viva my fair,Lirio de campo vivo my fair,0,80509,0 -544,10211716,Astromelia viva new cairo,Lirio de campo vivo new cairo,0,80509,0 -545,10211717,Astromelia viva nice,Lirio de campo vivo nice,0,80509,0 -546,10211718,Astromelia viva orange bowl,Lirio de campo vivo orange bowl,0,80509,0 -547,10211719,Astromelia viva orange queens,Lirio de campo vivo orange queens,0,80509,0 -548,10211720,Astromelia viva orange sun,Lirio de campo vivo orange sun,0,80509,0 -549,10211721,Astromelia viva paris,Lirio de campo vivo parís,0,80509,0 -550,10211722,Astromelia viva picasso,Lirio de campo vivo picasso,0,80509,0 -551,10211723,Astromelia viva pink panther,Lirio de campo vivo pink panther,0,80509,0 -552,10211724,Astromelia viva prima donna,Lirio de campo vivo prima donna,0,80509,0 -553,10211725,Astromelia viva red silhouette,Lirio de campo vivo red silhouette,0,80509,0 -554,10211726,Astromelia viva sacha,Lirio de campo vivo sacha,0,80509,0 -555,10211727,Astromelia viva salmon,Lirio de campo vivo salmón,0,80509,0 -556,10211728,Astromelia viva santiago,Lirio de campo vivo santiago,0,80509,0 -557,10211729,Astromelia viva senna,Lirio de campo vivo senna,0,80509,0 -559,10211731,Astromelia viva sublime,Lirio de campo vivo sublime,0,80509,0 -560,10211732,Astromelia viva tropicana,Lirio de campo vivo tropicana,0,80509,0 -561,10211733,Astromelia viva virginia,Lirio de campo vivo virginia,0,80509,0 -562,10211734,Astromelia viva white,Lirio de campo vivo white,0,80509,0 -563,10211800,Amaranto vivo,,0,80509,0 -564,10211801,Amaranto vivo verde colgante,,0,80509,0 -565,10211802,Amaranto vivo rojo colgante,,0,80509,0 -566,10211803,Amaranto vivo bonce erguido,,0,80509,0 -567,10211804,Amaranto vivo verde erguido,,0,80509,0 -568,10211805,Amaranto vivo rojo erguido,,0,80509,0 -569,10213200,Clavel (dianthus) vivo,Clavelinas vivas,0,80509,0 -570,10213201,Clavel (dianthus) vivo chocolate,Clavelina viva chocolate,0,80509,0 -571,10213202,Clavel (dianthus) vivo fucsia,Clavelina viva fucsia,0,80509,0 -572,10213203,Clavel (dianthus) vivo bola verde,Clavelina viva bola verde,0,80509,0 -573,10213204,Clavel (dianthus) vivo rosado fuerte,Clavelina viva rosado fuerte,0,80509,0 -574,10213205,Clavel (dianthus) vivo lavanda,Clavelina viva lavanda,0,80509,0 -575,10213206,Clavel (dianthus) vivo frambuesa,Clavelina viva frambuesa,0,80509,0 -576,10213207,Clavel (dianthus) vivo rojo,Clavelina viva rojo,0,80509,0 -577,10213208,Clavel (dianthus) vivo rosado,Clavelina viva rosado,0,80509,0 -578,10213700,Ajedrezada viva,,0,80509,0 -579,10213701,Ajedrezada viva acmopelata,,0,80509,0 -580,10213702,Ajedrezada viva assyriaca,,0,80509,0 -581,10213703,Ajedrezada viva assyriaca uva vulpis,,0,80509,0 -582,10213704,Ajedrezada viva elysee,,0,80509,0 -583,10213705,Ajedrezada viva imperialis anaranjada,,0,80509,0 -584,10213706,Ajedrezada viva imperialis amarilla,,0,80509,0 -585,10213707,Ajedrezada viva meleagris,,0,80509,0 -586,10213708,Ajedrezada viva michailowski,,0,80509,0 -587,10213709,Ajedrezada viva uva vulpis,,0,80509,0 -588,10214000,Jengibre vivo,Flores de jengibre vivas,0,80509,0 -589,10214001,Jengibre vivo indonesio,Flor de jengibre viva indonesio,0,80509,0 -590,10214002,Jengibre vivo jungle king rosado,Flor de jengibre viva jungle king rosado,0,80509,0 -591,10214003,Jengibre vivo jungle king rojo,Flor de jengibre viva jungle king rojo,0,80509,0 -592,10214004,Jengibre vivo rosado,Flor de jengibre viva rosado,0,80509,0 -593,10214005,Jengibre vivo rojo,Flor de jengibre viva rojo,0,80509,0 -594,10214006,Jengibre vivo torch,Flor de jengibre viva torch,0,80509,0 -595,10215400,Azucena viva,Lili viva,0,80509,0 -596,10215401,Azucena viva alteza longiflorum e híbrido asiática,Lili viva alteza longiflorum e híbrido asiática,0,80509,0 -597,10215402,Azucena viva black out,Lili viva black out,0,80509,0 -598,10215403,Azucena viva rosado oscuro,Lili viva rosado oscuro,0,80509,0 -599,10215404,Azucena viva leéctrica asiática,Lili viva eléctrica asiática,0,80509,0 -600,10215405,Azucena viva festival asiática,Lili viva festival asiática,0,80509,0 -601,10215406,Azucena viva ginebra asiática,Lili viva ginebra asiática,0,80509,0 -602,10215407,Azucena viva rosado claro asiática,Lili viva rosado claro asiática,0,80509,0 -603,10215408,Azucena viva colombina asiática,Lili viva colombina asiática,0,80509,0 -604,10215409,Azucena viva miss américa púrpura asiática,Lili viva miss américa púrpura asiática,0,80509,0 -605,10215410,Azucena viva monte negro asiática,Lili viva monte negro asiática,0,80509,0 -606,10215411,Azucena viva anaranjada asiática,Lili viva anaranjada asiática,0,80509,0 -607,10215412,Azucena viva durazno cannes asiática,Lili viva durazno cannes asiática,0,80509,0 -608,10215413,Azucena viva rosada asiática,Lili viva rosada asiática,0,80509,0 -609,10215414,Azucena viva sancerre asiátia,Lili viva sancerre asiática,0,80509,0 -610,10215415,Azucena viva white dream asiática,Lili viva white dream asiática,0,80509,0 -611,10215416,Azucena viva amarilla asiática,Lili viva amarilla asiática,0,80509,0 -612,10215417,Azucena viva diamante brillante longiflorum e híbrido asiática,Lili viva diamante brillante longiflorum e híbrido asiática,0,80509,0 -613,10215418,Azucena viva brindisi longiflorum e híbrido asiática,Lili viva brindisi longiflorum e híbrido asiática,0,80509,0 -614,10215419,Azucena viva carmine longiflorum e híbrido asiática,Lili viva carmine longiflorum e híbrido asiática,0,80509,0 -615,10215420,Azucena viva cinnabar longiflorum e híbrido asiática,Lili viva cinnabar longiflorum e híbrido asiática,0,80509,0 -616,10215421,Azucena viva club longiflorum e híbrido asiática,Lili viva club longiflorum e híbrido asiática,0,80509,0 -617,10215422,Azucena viva discovery longiflorum e híbrido asiática,Lili viva discovery longiflorum e híbrido asiática,0,80509,0 -618,10215423,Azucena viva de pascua,Lili viva de pascua,0,80509,0 -619,10215424,Azucena viva isis longiflorum e híbrido asiática,Lili viva isis longiflorum e híbrido asiática,0,80509,0 -620,10215425,Azucena viva la hybrid justice longiflorum e híbrido asiática,Lili viva la hybrid justice longiflorum e híbrido asiática,0,80509,0 -621,10215426,Azucena viva lace longiflorum e híbrido asiática,Lili viva lace longiflorum e híbrido asiática,0,80509,0 -622,10215427,Azucena viva lirio de los valles,Lili viva lirio de los valles,0,80509,0 -623,10215428,Azucena viva love longiflorum e híbrido asiática,Lili viva love longiflorum e híbrido asiática,0,80509,0 -624,10215429,Azucena viva menorca longiflorum e híbrido asiática,Lili viva menorca longiflorum e híbrido asiática,0,80509,0 -625,10215430,Azucena viva oriental acapulco,Lili viva oriental acapulco,0,80509,0 -626,10215431,Azucena viva oriental albion,Lili viva oriental albión,0,80509,0 -627,10215432,Azucena viva oriental argentina,Lili viva oriental argentina,0,80509,0 -628,10215433,Azucena viva oriental auratum,Lili viva oriental auratum,0,80509,0 -629,10215434,Azucena viva oriental barbaresco,Lili viva oriental barbaresco,0,80509,0 -630,10215435,Azucena viva oriental bernini,Lili viva oriental bernini,0,80509,0 -631,10215436,Azucena viva oriental beseno,Lili viva oriental beseno,0,80509,0 -632,10215437,Azucena viva oriental broadway,Lili viva oriental broadway,0,80509,0 -633,10215438,Azucena viva oriental canada,Lili viva oriental canadá,0,80509,0 -634,10215439,Azucena viva oriental casablanca,Lili viva oriental casablanca,0,80509,0 -635,10215440,Azucena viva oriental chili,Lili viva oriental chili,0,80509,0 -636,10215441,Azucena viva oriental chrystal,Lili viva oriental chrystal,0,80509,0 -637,10215442,Azucena viva oriental cobra,Lili viva oriental cobra,0,80509,0 -638,10215443,Azucena viva oriental conca d’ or,Lili viva oriental conca d ivor,0,80509,0 -639,10215444,Azucena viva oriental cote d’ ivor,Lili viva oriental cote d ivor,0,80509,0 diff --git a/backend/api/v1/modules/public/reference_data/carta_porte_codes/dto.py b/backend/api/v1/modules/public/reference_data/carta_porte_codes/dto.py deleted file mode 100644 index fdf8ad34..00000000 --- a/backend/api/v1/modules/public/reference_data/carta_porte_codes/dto.py +++ /dev/null @@ -1,14 +0,0 @@ -from pydantic import BaseModel, ConfigDict, Field -from typing import Optional - - -class CartaPorteDTO(BaseModel): - id: int - code: str = Field(..., max_length=20) - description: str = Field(..., max_length=2000) - similar_words: Optional[str] = Field(None, max_length=2000) - is_hazardous: int = 0 - start_date: Optional[str] = None - end_date: Optional[str] = None - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/public/reference_data/carta_porte_codes/models.py b/backend/api/v1/modules/public/reference_data/carta_porte_codes/models.py deleted file mode 100644 index c23ade0b..00000000 --- a/backend/api/v1/modules/public/reference_data/carta_porte_codes/models.py +++ /dev/null @@ -1,21 +0,0 @@ -from core.database import Base -from sqlalchemy import String, Integer -from sqlalchemy.orm import Mapped, mapped_column - - -class CartaPorte(Base): - __tablename__ = "carta_porte_codes" - __table_args__ = ( - {"schema": "public", "extend_existing": True}, - ) - - id: Mapped[int] = mapped_column(Integer, primary_key=True) - code: Mapped[str] = mapped_column(String(20), nullable=False, index=True) - description: Mapped[str] = mapped_column(String(2000), nullable=False) - similar_words: Mapped[str] = mapped_column(String(2000), nullable=True) - is_hazardous: Mapped[int] = mapped_column(Integer, default=0) - start_date: Mapped[str] = mapped_column(String(20), nullable=True) - end_date: Mapped[str] = mapped_column(String(20), nullable=True) - - def __repr__(self): - return f"" diff --git a/backend/api/v1/modules/public/reference_data/carta_porte_codes/routes.py b/backend/api/v1/modules/public/reference_data/carta_porte_codes/routes.py deleted file mode 100644 index c95bb0cf..00000000 --- a/backend/api/v1/modules/public/reference_data/carta_porte_codes/routes.py +++ /dev/null @@ -1,76 +0,0 @@ -from typing import Any, Dict - -from core.database import get_core_db -from core.security import get_current_user, has_role -from fastapi import APIRouter, Depends, HTTPException, Query -from fastapi import APIRouter, Depends, HTTPException, Query -from sqlalchemy import or_ -from sqlalchemy.orm import Session - -from .dto import CartaPorteDTO -from .models import CartaPorte -from ..rbac import require_ref_view - -router = APIRouter(prefix="/carta-porte") - - -@router.get( - "/", - response_model=Dict[str, Any], - dependencies=[Depends(require_ref_view("ref_transport_types.view"))], -) -async def list_carta_porte( - page: int = Query(1, ge=1), - page_size: int = Query(50, ge=1, le=100), - q: str = Query(None, description="Buscar por código o descripción"), - search: str = Query(None, description="Término de búsqueda"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - skip = (page - 1) * page_size - query = db.query(CartaPorte) - - if search: - search_filter = f"%{search}%" - query = query.filter( - or_( - CartaPorte.code.ilike(search_filter), - CartaPorte.description.ilike(search_filter) - ) - ) - - if q: - search_filter = f"%{q}%" - query = query.filter( - or_( - CartaPorte.code.ilike(search_filter), - CartaPorte.description.ilike(search_filter), - CartaPorte.similar_words.ilike(search_filter) - ) - ) - - total = query.count() - items = query.offset(skip).limit(page_size).all() - - return { - "items": [CartaPorteDTO.model_validate(obj) for obj in items], - "total": total, - "page": page, - "page_size": page_size, - } - - -@router.get( - "/{id}", - response_model=CartaPorteDTO, - dependencies=[Depends(require_ref_view("ref_transport_types.view"))], -) -async def get_carta_porte( - id: int, - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - obj = db.query(CartaPorte).filter(CartaPorte.id == id).first() - if not obj: - raise HTTPException(status_code=404, detail="Not found") - return obj diff --git a/backend/api/v1/modules/public/reference_data/carta_porte_codes/seed.py b/backend/api/v1/modules/public/reference_data/carta_porte_codes/seed.py deleted file mode 100644 index 580e5425..00000000 --- a/backend/api/v1/modules/public/reference_data/carta_porte_codes/seed.py +++ /dev/null @@ -1,48 +0,0 @@ -import csv -import os -from sqlalchemy.orm import Session -from api.v1.modules.a76.layouts_csv.common import csv_reader as common_csv_reader -from .models import CartaPorte - -def seed_carta_porte(db: Session): - csv_path = os.path.join(os.path.dirname(__file__), "cartaPorte.csv") - if not os.path.exists(csv_path): - print(f"File not found: {csv_path}") - return - - # Check if already seeded to avoid duplicates - if db.query(CartaPorte).first(): - print("Carta Porte catalog already has data, skipping...") - return - - print("Seeding Carta Porte catalog (this might take a while)...") - - encoding = common_csv_reader.detect_text_encoding(csv_path) - with open(csv_path, mode='r', encoding=encoding) as f: - # User provided comma-separated data - reader = csv.DictReader(f) - - batch_size = 1000 - batch = [] - - for row in reader: - # SYSID CODIGO DESCRIPCION PALABRASSIMILARES MATPELIGROSO FECHAINICIOVIGENCIA FECHAFINVIGENCIA - obj = CartaPorte( - id=int(row['SYSID']), - code=row['CODIGO'], - description=row['DESCRIPCION'], - similar_words=row.get('PALABRASSIMILARES'), - is_hazardous=int(row['MATPELIGROSO']) if row['MATPELIGROSO'].isdigit() else 0, - start_date=row.get('FECHAINICIOVIGENCIA'), - end_date=row.get('FECHAFINVIGENCIA') - ) - batch.append(obj) - - if len(batch) >= batch_size: - db.bulk_save_objects(batch) - db.commit() - batch = [] - - if batch: - db.bulk_save_objects(batch) - db.commit() diff --git a/backend/api/v1/modules/public/reference_data/code_pedimento_regimens/dto.py b/backend/api/v1/modules/public/reference_data/code_pedimento_regimens/dto.py deleted file mode 100644 index b36e3d0e..00000000 --- a/backend/api/v1/modules/public/reference_data/code_pedimento_regimens/dto.py +++ /dev/null @@ -1,12 +0,0 @@ -from typing import Optional - -from pydantic import BaseModel, ConfigDict, Field - - -class CodePedimentoRegimenDTO(BaseModel): - id: Optional[int] = None - pedimento_code: str = Field(..., min_length=1, max_length=3) - regimen_code: Optional[str] = Field(None, min_length=1, max_length=3) - type_code: Optional[str] = Field(None, min_length=1, max_length=1) - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/public/reference_data/code_pedimento_regimens/models.py b/backend/api/v1/modules/public/reference_data/code_pedimento_regimens/models.py deleted file mode 100644 index 9896d4e7..00000000 --- a/backend/api/v1/modules/public/reference_data/code_pedimento_regimens/models.py +++ /dev/null @@ -1,44 +0,0 @@ -from typing import TYPE_CHECKING, Optional - -from core.database import Base -from sqlalchemy import ForeignKeyConstraint, Integer, PrimaryKeyConstraint, String -from sqlalchemy.orm import Mapped, mapped_column, relationship - -if TYPE_CHECKING: - from ..pedimento_codes.models import PedimentoCode - from ..pedimento_regimens.models import RegimenPedimento - - -class CodePedimentoRegimen(Base): - __tablename__ = "code_pedimento_regimens" # GClavePedRegimen - __table_args__ = ( - ForeignKeyConstraint( - ["pedimento_code"], ["public.pedimento_codes.code"], name="fk_codeped" - ), - ForeignKeyConstraint( - ["regimen_code"], ["public.pedimento_regimens.code"], name="fk_regimenped" - ), - PrimaryKeyConstraint("id", name="clave_pedimento_regimens_pkey"), - {"schema": "public", "extend_existing": True}, - ) - - id: Mapped[int] = mapped_column(Integer) - pedimento_code: Mapped[str] = mapped_column(String(3), nullable=False) - regimen_code: Mapped[Optional[str]] = mapped_column( - String(3), nullable=False) - type_code: Mapped[Optional[str]] = mapped_column( - String(1) - ) # si aplica un tipo de relación - - # Relaciones ORM - # GClavePed - pedimento: Mapped["PedimentoCode"] = relationship( - "PedimentoCode", back_populates="regimens" - ) - # GRegimenPed - regimen: Mapped[Optional["RegimenPedimento"]] = relationship( - "RegimenPedimento", back_populates="claves_pedimento" - ) - - def __repr__(self): - return f"" diff --git a/backend/api/v1/modules/public/reference_data/code_pedimento_regimens/routes.py b/backend/api/v1/modules/public/reference_data/code_pedimento_regimens/routes.py deleted file mode 100644 index c8ca4618..00000000 --- a/backend/api/v1/modules/public/reference_data/code_pedimento_regimens/routes.py +++ /dev/null @@ -1,116 +0,0 @@ -from typing import Any, Dict - -from core.database import get_core_db -from core.security import get_current_user, has_role -from fastapi import APIRouter, Depends, HTTPException, Query -from sqlalchemy.orm import Session -from sqlalchemy import or_ - -from .dto import CodePedimentoRegimenDTO -from .models import CodePedimentoRegimen -from ..rbac import require_ref_view - -router = APIRouter(prefix="/code-pedimento-regimens") - - -@router.get( - "/", - response_model=Dict[str, Any], - dependencies=[Depends(require_ref_view("ref_pedimento_regimens.view"))], -) -def list_code_pedimento_regimens( - page: int = Query(1, ge=1, description="Número de página"), - page_size: int = Query(50, ge=1, le=1000, description="Tamaño de página"), - code: str = Query(None, description="Filter by code"), - regime: str = Query(None, description="Filter by regime"), - type: str = Query(None, description="Filter by type"), - search: str = Query(None, description="Término de búsqueda"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - skip = (page - 1) * page_size - query = db.query(CodePedimentoRegimen) - - if search: - search_filter = f"%{search}%" - query = query.filter( - or_( - CodePedimentoRegimen.pedimento_code.ilike(search_filter) - ) - ) - - if code is not None: - query = query.filter(CodePedimentoRegimen.pedimento_code == code) - if regime is not None: - query = query.filter(CodePedimentoRegimen.regimen_code == regime) - if type is not None: - query = query.filter(CodePedimentoRegimen.type_code == type) - - items = query.offset(skip).limit(page_size).all() - total = query.count() - return { - "items": [CodePedimentoRegimenDTO.model_validate(obj) for obj in items], - "total": total, - "page": page, - "page_size": page_size, - } - - -@router.get( - "/{id}", - response_model=CodePedimentoRegimenDTO, - dependencies=[Depends(require_ref_view("ref_pedimento_regimens.view"))], -) -def get_code_pedimento_regimen( - id: int, - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - obj = db.query(CodePedimentoRegimen).filter(CodePedimentoRegimen.id == id).first() - if not obj: - raise HTTPException(status_code=404, detail="Not found") - return CodePedimentoRegimenDTO.model_validate(obj) - - -@router.post("/", response_model=CodePedimentoRegimenDTO, status_code=201) -def create_code_pedimento_regimen( - data: CodePedimentoRegimenDTO, - db: Session = Depends(get_core_db), - current_user: dict = Depends(has_role("admin")), -): - obj = CodePedimentoRegimen(**data.model_dump()) - db.add(obj) - db.commit() - db.refresh(obj) - return CodePedimentoRegimenDTO.model_validate(obj) - - -@router.put("/{id}", response_model=CodePedimentoRegimenDTO) -def update_code_pedimento_regimen( - id: int, - data: CodePedimentoRegimenDTO, - db: Session = Depends(get_core_db), - current_user: dict = Depends(has_role("admin")), -): - obj = db.query(CodePedimentoRegimen).filter(CodePedimentoRegimen.id == id).first() - if not obj: - raise HTTPException(status_code=404, detail="Not found") - for field, value in data.model_dump().items(): - setattr(obj, field, value) - db.commit() - db.refresh(obj) - return CodePedimentoRegimenDTO.model_validate(obj) - - -@router.delete("/{id}", status_code=204) -def delete_code_pedimento_regimen( - id: int, - db: Session = Depends(get_core_db), - current_user: dict = Depends(has_role("admin")), -): - obj = db.query(CodePedimentoRegimen).filter(CodePedimentoRegimen.id == id).first() - if not obj: - raise HTTPException(status_code=404, detail="Not found") - db.delete(obj) - db.commit() - return None diff --git a/backend/api/v1/modules/public/reference_data/code_pedimento_regimens/seed.py b/backend/api/v1/modules/public/reference_data/code_pedimento_regimens/seed.py deleted file mode 100644 index 83e2b16c..00000000 --- a/backend/api/v1/modules/public/reference_data/code_pedimento_regimens/seed.py +++ /dev/null @@ -1,123 +0,0 @@ -seed = [ - ("A4", "DFI", "E"), - ("A4", "DFI", "I"), - ("A5", "DFI", "E"), - ("A5", "DFI", "I"), - ("F2", "DFI", "I"), - ("F8", "DFI", "E"), - ("F8", "DFI", "I"), - ("F9", "DFI", "E"), - ("F9", "DFI", "I"), - ("V3", "DFI", "E"), - ("V3", "DFI", "I"), - ("V8", "DFI", "I"), - ("BM", "ETE", "E"), - ("BO", "ETE", "E"), - ("R1", "ETE", "E"), - ("AJ", "ETR", "E"), - ("BA", "ETR", "E"), - ("BF", "ETR", "E"), - ("BR", "ETR", "E"), - ("R1", "ETR", "E"), - ("V4", "ETR", "E"), - ("A1", "EXD", "E"), - ("BB", "EXD", "E"), - ("CT", "EXD", "E"), - ("D1", "EXD", "E"), - ("F4", "EXD", "E"), - ("G1", "EXD", "E"), - ("G6", "EXD", "E"), - ("G7", "EXD", "E"), - ("GC", "EXD", "E"), - ("H1", "EXD", "E"), - ("H8", "EXD", "E"), - ("I1", "EXD", "E"), - ("J1", "EXD", "E"), - ("J2", "EXD", "E"), - ("K1", "EXD", "E"), - ("K2", "EXD", "E"), - ("K3", "EXD", "E"), - ("L1", "EXD", "E"), - ("R1", "EXD", "E"), - ("RT", "EXD", "E"), - ("S2", "EXD", "E"), - ("T1", "EXD", "E"), - ("V1", "EXD", "E"), - ("V2", "EXD", "E"), - ("V5", "EXD", "E"), - ("V6", "EXD", "E"), - ("V7", "EXD", "E"), - ("V8", "EXD", "E"), - ("V9", "EXD", "E"), - ("A1", "IMD", "I"), - ("A3", "IMD", "I"), - ("C1", "IMD", "I"), - ("C2", "IMD", "I"), - ("C3", "IMD", "I"), - ("D1", "IMD", "I"), - ("F3", "IMD", "I"), - ("F4", "IMD", "I"), - ("F5", "IMD", "I"), - ("G1", "IMD", "I"), - ("G2", "IMD", "I"), - ("G9", "IMD", "E"), - ("GC", "IMD", "I"), - ("H1", "IMD", "I"), - ("H8", "IMD", "I"), - ("I1", "IMD", "I"), - ("K1", "IMD", "I"), - ("L1", "IMD", "I"), - ("P1", "IMD", "I"), - ("R1", "IMD", "I"), - ("S2", "IMD", "I"), - ("T1", "IMD", "I"), - ("V2", "IMD", "I"), - ("V5", "IMD", "I"), - ("V6", "IMD", "I"), - ("V9", "IMD", "I"), - ("VF", "IMD", "I"), - ("VU", "IMD", "I"), - ("A2", "ITE", "I"), - ("A8", "ITE", "I"), - ("AA", "ITE", "I"), - ("AF", "ITE", "I"), - ("E1", "ITE", "I"), - ("E3", "ITE", "I"), - ("H3", "ITE", "I"), - ("IN", "ITE", "I"), - ("R1", "ITE", "I"), - ("V1", "ITE", "I"), - ("A6", "ITR", "I"), - ("A7", "ITR", "I"), - ("A9", "ITR", "I"), - ("AD", "ITR", "I"), - ("AF", "ITR", "I"), - ("AJ", "ITR", "I"), - ("BA", "ITR", "I"), - ("BC", "ITR", "I"), - ("BD", "ITR", "I"), - ("BE", "ITR", "I"), - ("BH", "ITR", "I"), - ("BI", "ITR", "I"), - ("BO", "ITR", "I"), - ("BP", "ITR", "I"), - ("E2", "ITR", "I"), - ("E4", "ITR", "I"), - ("H3", "ITR", "I"), - ("R1", "ITR", "I"), - ("V1", "ITR", "I"), - ("V4", "ITR", "I"), - ("V7", "ITR", "I"), - ("J3", "RFE", "E"), - ("M1", "RFE", "I"), - ("M2", "RFE", "I"), - ("G8", "RFS", "E"), - ("J4", "RFS", "E"), - ("M3", "RFS", "I"), - ("M4", "RFS", "I"), - ("M5", "RFS", "I"), - ("T3", "TRA", "I"), - ("T6", "TRA", "E"), - ("T7", "TRA", "I"), - ("T9", "TRA", "I"), -] diff --git a/backend/api/v1/modules/public/reference_data/code_pedimento_regimens/test_code_pedimento_regimens.py b/backend/api/v1/modules/public/reference_data/code_pedimento_regimens/test_code_pedimento_regimens.py deleted file mode 100644 index 4f9f6041..00000000 --- a/backend/api/v1/modules/public/reference_data/code_pedimento_regimens/test_code_pedimento_regimens.py +++ /dev/null @@ -1,44 +0,0 @@ -import pytest -from api.v1.modules.public.reference_data.code_pedimento_regimens.routes import router -from fastapi import FastAPI -from fastapi.testclient import TestClient - -app = FastAPI() -app.include_router(router) -client = TestClient(app) - - -@pytest.mark.usefixtures("client", "access_token") -def test_list_code_pedimento_regimens(client, access_token): - headers = {"Authorization": f"Bearer {access_token}"} - response = client.get("/code-pedimento-regimens/", headers=headers) - assert response.status_code == 200 - assert "items" in response.json() - assert "page" in response.json() - assert "page_size" in response.json() - - -@pytest.mark.usefixtures("client", "access_token") -def test_get_code_pedimento_regimen_not_found(client, access_token): - headers = {"Authorization": f"Bearer {access_token}"} - response = client.get("/code-pedimento-regimens/999999", headers=headers) - assert response.status_code == 404 - - -def test_create_code_pedimento_regimen_forbidden(): - response = client.post( - "/code-pedimento-regimens/", json={"id": 999999, "description": "Test"} - ) - assert response.status_code in (403, 405, 404) - - -def test_update_code_pedimento_regimen_forbidden(): - response = client.put( - "/code-pedimento-regimens/999999", json={"id": 999999, "description": "Test"} - ) - assert response.status_code in (403, 405, 404) - - -def test_delete_code_pedimento_regimen_forbidden(): - response = client.delete("/code-pedimento-regimens/999999") - assert response.status_code in (403, 405, 404) diff --git a/backend/api/v1/modules/public/reference_data/conftest.py b/backend/api/v1/modules/public/reference_data/conftest.py deleted file mode 100644 index 58e30506..00000000 --- a/backend/api/v1/modules/public/reference_data/conftest.py +++ /dev/null @@ -1,20 +0,0 @@ -import pytest -from api.v1.modules.public.reference_data.transport_types.routes import router -from fastapi import FastAPI -from fastapi.testclient import TestClient - - -@pytest.fixture(scope="session") -def access_token(): - # Reemplaza este token por uno válido generado por Keycloak - return "aqui-va-tu-token-valido" - - -@pytest.fixture(scope="session") -def client(): - from api.v1.modules.public.reference_data.countries.routes import router as countries_router - from api.v1.modules.public.reference_data.transport_types.routes import router as transport_types_router - app = FastAPI() - app.include_router(countries_router) - app.include_router(transport_types_router) - return TestClient(app) diff --git a/backend/api/v1/modules/public/reference_data/containers/dto.py b/backend/api/v1/modules/public/reference_data/containers/dto.py deleted file mode 100644 index e16fea52..00000000 --- a/backend/api/v1/modules/public/reference_data/containers/dto.py +++ /dev/null @@ -1,8 +0,0 @@ -from pydantic import BaseModel, ConfigDict, Field - - -class ContainerDTO(BaseModel): - key: str = Field(..., min_length=1, max_length=3) - description: str - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/public/reference_data/containers/models.py b/backend/api/v1/modules/public/reference_data/containers/models.py deleted file mode 100644 index c6081c34..00000000 --- a/backend/api/v1/modules/public/reference_data/containers/models.py +++ /dev/null @@ -1,21 +0,0 @@ -from core.database import Base -from sqlalchemy import PrimaryKeyConstraint, String -from sqlalchemy.orm import Mapped, mapped_column - - -class Container(Base): - __tablename__ = "containers" # GContenedores - __table_args__ = ( - PrimaryKeyConstraint("key", name="containers_pkey"), - {"extend_existing": True}, # opcional - ) - - key: Mapped[str] = mapped_column( - String(3), primary_key=True, nullable=False - ) # mantiene ceros iniciales - description: Mapped[str] = mapped_column( - String(500), nullable=False - ) # descripción legal en español - - def __repr__(self): - return f"" diff --git a/backend/api/v1/modules/public/reference_data/containers/routes.py b/backend/api/v1/modules/public/reference_data/containers/routes.py deleted file mode 100644 index be6659f0..00000000 --- a/backend/api/v1/modules/public/reference_data/containers/routes.py +++ /dev/null @@ -1,106 +0,0 @@ -from typing import Any, Dict - -from core.database import get_core_db -from core.security import get_current_user, has_role -from fastapi import APIRouter, Depends, HTTPException, Query -from sqlalchemy.orm import Session -from sqlalchemy import or_ - -from .dto import ContainerDTO -from .models import Container -from ..rbac import require_ref_view - -router = APIRouter(prefix="/containers") - - -@router.get( - "/", - response_model=Dict[str, Any], - dependencies=[Depends(require_ref_view("ref_containers.view"))], -) -async def list_containers( - page: int = Query(1, ge=1, description="Número de página"), - page_size: int = Query(50, ge=1, le=100, description="Tamaño de página"), - search: str = Query(None, description="Término de búsqueda"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - skip = (page - 1) * page_size - query = db.query(Container) - - if search: - search_filter = f"%{search}%" - query = query.filter( - or_( - Container.key.ilike(search_filter), - Container.description.ilike(search_filter) - ) - ) - items = query.offset(skip).limit(page_size).all() - total = query.count() - return { - "items": [ContainerDTO.model_validate(obj) for obj in items], - "total": total, - "page": page, - "page_size": page_size, - } - - -@router.get( - "/{key}", - response_model=ContainerDTO, - dependencies=[Depends(require_ref_view("ref_containers.view"))], -) -async def get_container( - key: str, - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - obj = db.query(Container).filter(Container.key == key).first() - if not obj: - raise HTTPException(status_code=404, detail="Not found") - return obj - - -@router.post("/", response_model=ContainerDTO, status_code=201) -async def create_container( - data: ContainerDTO, - db: Session = Depends(get_core_db), - current_user: dict = Depends(has_role("admin")), -): - obj = Container(**data.dict()) - db.add(obj) - db.commit() - db.refresh(obj) - return obj - - -@router.put("/{key}", response_model=ContainerDTO) -async def update_container( - key: str, - data: ContainerDTO, - db: Session = Depends(get_core_db), - current_user: dict = Depends(has_role("admin")), -): - obj = db.query(Container).filter(Container.key == key).first() - if not obj: - raise HTTPException(status_code=404, detail="Not found") - for field, value in data.dict().items(): - setattr(obj, field, value) - db.commit() - db.refresh(obj) - return obj - - -@router.delete("/{key}", status_code=204) -async def delete_container( - key: str, - db: Session = Depends(get_core_db), - current_user: dict = Depends(has_role("admin")), -): - obj = db.query(Container).filter(Container.key == key).first() - if not obj: - raise HTTPException(status_code=404, detail="Not found") - db.delete(obj) - db.commit() - return None diff --git a/backend/api/v1/modules/public/reference_data/containers/seed.py b/backend/api/v1/modules/public/reference_data/containers/seed.py deleted file mode 100644 index 84ceb7bf..00000000 --- a/backend/api/v1/modules/public/reference_data/containers/seed.py +++ /dev/null @@ -1,74 +0,0 @@ -seed = [ - ("1", "CONTENEDOR ESTANDAR 20' (STANDARD CONTAINER 20')."), - ("2", "CONTENEDOR ESTANDAR 40' (STANDARD CONTAINER 40')."), - ("3", "CONTENEDOR ESTANDAR DE CUBO ALTO 40' (HIGH CUBE STANDARD CONTAINER 40')."), - ("4", "CONTENEDOR TAPA DURA 20’ (HARDTOP CONTAINER 20')."), - ("5", "CONTENEDOR TAPA DURA 40’ (HARDTOP CONTAINER 40')."), - ("6", "CONTENEDOR TAPA ABIERTA 20’ (OPEN TOP CONTAINER 20')."), - ("7", "CONTENEDOR TAPA ABIERTA 40' (OPEN TOP CONTAINER 40')."), - ("8", "FLAT 20' (FLAT 20')."), - ("9", "FLAT 40' (FLAT 40')."), - ("10", "PLATAFORMA 20' (PLATFORM 20')."), - ("11", "PLATAFORMA 40' (PLATFORM 40')."), - ("12", "CONTENEDOR VENTILADO 20’ (VENTILATED CONTAINER 20')."), - ("13", "CONTENEDOR TERMICO 20' (INSULATED CONTAINER 20')."), - ("14", "CONTENEDOR TERMICO 40' (INSULATED CONTAINER 40')."), - ("15", "CONTENEDOR REFRIGERANTE 20’ (REFRIGERATED CONTAINER 20')."), - ("16", "CONTENEDOR REFRIGERANTE 40’ (REFRIGERATED CONTAINER 40')."), - ( - "17", - "CONTENEDOR REFRIGERANTE CUBO ALTO 40’ (HIGH CUBE REFRIGERATED CONTAINER 40').", - ), - ("18", "CONTENEDOR CARGA A GRANEL 20’ (BULK CONTAINER 20')."), - ("19", "CONTENEDOR TIPO TANQUE 20’ (TANK CONTAINER 20')."), - ("20", "CONTENEDOR ESTANDAR 45' (STANDARD CONTAINER 45')."), - ("21", "CONTENEDOR ESTANDAR 48' (STANDARD CONTAINER 48')."), - ("22", "CONTENEDOR ESTANDAR 53' (STANDARD CONTAINER 53')."), - ("23", "CONTENEDOR ESTANDAR 8' (STANDARD CONTAINER 8')."), - ("24", "CONTENEDOR ESTANDAR 10' (STANDARD CONTAINER 10')."), - ("25", "CONTENEDOR ESTANDAR DE CUBO ALTO 45’ (HIGH CUBE STANDARD CONTAINER 45’)."), - ("26", "SEMIRREMOLQUE CON RACKS PARA ENVASES DE BEBIDAS."), - ("27", "SEMIRREMOLQUE CUELLO DE GANZO."), - ("28", "SEMIRREMOLQUE TOLVA CUBIERTO."), - ("29", "SEMIRREMOLQUE TOLVA (ABIERTO)."), - ("30", "AUTO-TOLVA CUBIERTO/DESCARGA NEUMATICA."), - ("31", "SEMIRREMOLQUE CHASIS."), - ("32", "SEMIRREMOLQUE AUTOCARGABLE (CON SISTEMA DE ELEVACION)."), - ("33", "SEMIRREMOLQUE CON TEMPERATURA CONTROLADA."), - ("34", "SEMIRREMOLQUE CORTO TRASERO."), - ("35", "SEMIRREMOLQUE DE CAMA BAJA."), - ("36", "PLATAFORMA DE 28’."), - ("37", "PLATAFORMA DE 45’."), - ("38", "PLATAFORMA DE 48’."), - ("39", "SEMIRREMOLQUE PARA TRANSPORTE DE CABALLOS."), - ("40", "SEMIRREMOLQUE PARA TRANSPORTE DE GANADO."), - ("41", "SEMIRREMOLQUE TANQUE (LIQUIDOS)/SIN CALEFACCION/SIN AISLAR."), - ("42", "SEMIRREOLQUE TANQUE (LIQUIDOS)/CON CALEFACCION/SIN AISLAR."), - ("43", "SEMIRREMOLQUE TANQUE (LIQUIDOS)/SIN CALEFACCION/AISLADO."), - ("44", "SEMIRREMOLQUE TANQUE (LIQUIDOS)/CON CALEFACCION/AISLADO."), - ("45", "SEMIRREMOLQUE TANQUE (GAS)/SIN CALEFACCION/SIN AISLAR."), - ("46", "SEMIRREMOLQUE TANQUE (GAS)/CON CALEFACCION/SIN AISLAR."), - ("47", "SEMIRREMOLQUE TANQUE (GAS)/SIN CALEFACCION/AISLADO."), - ("48", "SEMIRREMOLQUE TANQUE (GAS)/CON CALEFACCION/AISLADO."), - ("49", "SEMIRREMOLQUE TANQUE (QUIMICOS)/SIN CALEFACCION/SIN AISLAR."), - ("50", "SEMIRREMOLQUE TANQUE (QUIMICOS)/CON CALEFACCION/SIN AISLAR."), - ("51", "SEMIRREMOLQUE TANQUE (QUIMICOS)/SIN CALEFACCION/AISLADO."), - ("52", "SEMIRREMOLQUE TANQUE (QUIMICOS)/CON CALEFACCION/AISLADO."), - ("53", "SEMIRREMOLQUE GONDOLA-CERRADA."), - ("54", "SEMIRREMOLQUE GONDOLA-ABIERTA."), - ("55", "SEMIRREMOLQUE TIPO CAJA CERRADA 48’."), - ("56", "SEMIRREMOLQUE TIPO CAJA CERRADA 53’."), - ("57", "SEMIRREMOLQUE TIPO CAJA REFRIGERADA 48’."), - ("58", "SEMIRREMOLQUE TIPO CAJA REFRIGERADA 53’."), - ("59", "DOBLE SEMIRREMOLQUE."), - ("60", "OTROS."), - ("61", "TANQUE 20’."), - ("62", "TANQUE 40’."), - ("63", "CARRO DE FERROCARRIL"), - ("64", "HIGH CUBE 20'"), - ("65", "AUTOMÓVIL"), - ("66", "CAMIÓN UNITARIO DE DOS EJES"), - ("67", "CAMIÓN UNITARIO DE TRES EJES"), - ("68", "VEHÍCULOS CON CAPACIDAD DE CARGA DE HASTA 3.5. TONELADAS"), - ("69", "TRACTOCAMIÓN"), -] diff --git a/backend/api/v1/modules/public/reference_data/containers/test_containers.py b/backend/api/v1/modules/public/reference_data/containers/test_containers.py deleted file mode 100644 index 3527be0c..00000000 --- a/backend/api/v1/modules/public/reference_data/containers/test_containers.py +++ /dev/null @@ -1,40 +0,0 @@ -import pytest -from api.v1.modules.public.reference_data.containers.routes import router -from fastapi import FastAPI -from fastapi.testclient import TestClient - -app = FastAPI() -app.include_router(router) -client = TestClient(app) - - -@pytest.mark.usefixtures("client", "access_token") -def test_list_containers(client, access_token): - headers = {"Authorization": f"Bearer {access_token}"} - response = client.get("/containers/", headers=headers) - assert response.status_code == 200 - assert "items" in response.json() - assert "page" in response.json() - assert "page_size" in response.json() - - -@pytest.mark.usefixtures("client", "access_token") -def test_get_container_not_found(client, access_token): - headers = {"Authorization": f"Bearer {access_token}"} - response = client.get("/containers/invalid_key", headers=headers) - assert response.status_code == 404 - - -def test_create_container_forbidden(): - response = client.post("/containers/", json={"key": "TST", "description": "Test"}) - assert response.status_code in (403, 405, 404) - - -def test_update_container_forbidden(): - response = client.put("/containers/TST", json={"key": "TST", "description": "Test"}) - assert response.status_code in (403, 405, 404) - - -def test_delete_container_forbidden(): - response = client.delete("/containers/TST") - assert response.status_code in (403, 405, 404) diff --git a/backend/api/v1/modules/public/reference_data/countries/dto.py b/backend/api/v1/modules/public/reference_data/countries/dto.py deleted file mode 100644 index 3b197100..00000000 --- a/backend/api/v1/modules/public/reference_data/countries/dto.py +++ /dev/null @@ -1,11 +0,0 @@ -from pydantic import BaseModel, ConfigDict, Field - - -class CountryDTO(BaseModel): - m3_key: str = Field(..., min_length=1, max_length=3) - mex_key: str = Field(..., min_length=1, max_length=2) - ame_key: str = Field(..., min_length=1, max_length=2) - description_es: str - description_en: str - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/public/reference_data/countries/models.py b/backend/api/v1/modules/public/reference_data/countries/models.py deleted file mode 100644 index 7ddcf47c..00000000 --- a/backend/api/v1/modules/public/reference_data/countries/models.py +++ /dev/null @@ -1,29 +0,0 @@ -from core.database import Base -from sqlalchemy import Index, PrimaryKeyConstraint, String -from sqlalchemy.orm import Mapped, mapped_column - - -class Country(Base): - __tablename__ = "countries" # GPaises - __table_args__ = ( - PrimaryKeyConstraint("m3_key", name="countries_pkey"), - Index("ak_country_ame", "ame_key", unique=True), - {"schema": "public", "extend_existing": True}, # opcional - ) - - m3_key: Mapped[str] = mapped_column( - String(3), primary_key=True, nullable=False) # clave M3 - mex_key: Mapped[str] = mapped_column( - String(2), nullable=False) # clave país México - ame_key: Mapped[str] = mapped_column( - String(2), nullable=False - ) # clave país América / regional - description_es: Mapped[str] = mapped_column( - String(50), nullable=False - ) # nombre oficial en español - description_en: Mapped[str] = mapped_column( - String(50), nullable=False - ) # nombre en inglés para UI - - def __repr__(self): - return f"" diff --git a/backend/api/v1/modules/public/reference_data/countries/routes.py b/backend/api/v1/modules/public/reference_data/countries/routes.py deleted file mode 100644 index 28550fa6..00000000 --- a/backend/api/v1/modules/public/reference_data/countries/routes.py +++ /dev/null @@ -1,108 +0,0 @@ -from typing import Any, Dict - -from core.database import get_core_db -from core.security import get_current_user, has_role -from fastapi import APIRouter, Depends, HTTPException, Query -from sqlalchemy.orm import Session - -from .dto import CountryDTO -from .models import Country -from ..rbac import require_ref_view - -router = APIRouter(prefix="/countries") - - -@router.get( - "/", - response_model=Dict[str, Any], - dependencies=[Depends(require_ref_view("ref_countries.view"))], -) -async def list_countries( - page: int = Query(1, ge=1, description="Número de página"), - page_size: int = Query(50, ge=1, le=100, description="Tamaño de página"), - search: str = Query(None, description="Término de búsqueda"), - db: Session = Depends(get_core_db), -): - """Lista de países protegida por permiso ref_countries.view""" - skip = (page - 1) * page_size - query = db.query(Country) - - if search: - search_filter = f"%{search}%" - query = query.filter( - (Country.m3_key.ilike(search_filter)) | - (Country.mex_key.ilike(search_filter)) | - (Country.ame_key.ilike(search_filter)) | - (Country.description_es.ilike(search_filter)) | - (Country.description_en.ilike(search_filter)) - ) - - total = query.count() - items = query.offset(skip).limit(page_size).all() - - return { - "items": [CountryDTO.model_validate(obj) for obj in items], - "total": total, - "page": page, - "page_size": page_size, - } - - -@router.get( - "/{m3_key}", - response_model=CountryDTO, - dependencies=[Depends(require_ref_view("ref_countries.view"))], -) -async def get_country( - m3_key: str, - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - obj = db.query(Country).filter(Country.m3_key == m3_key).first() - if not obj: - raise HTTPException(status_code=404, detail="Not found") - return obj - - -@router.post("/", response_model=CountryDTO, status_code=201) -async def create_country( - data: CountryDTO, - db: Session = Depends(get_core_db), - current_user: dict = Depends(has_role("admin")), -): - obj = Country(**data.dict()) - db.add(obj) - db.commit() - db.refresh(obj) - return obj - - -@router.put("/{m3_key}", response_model=CountryDTO) -async def update_country( - m3_key: str, - data: CountryDTO, - db: Session = Depends(get_core_db), - current_user: dict = Depends(has_role("admin")), -): - obj = db.query(Country).filter(Country.m3_key == m3_key).first() - if not obj: - raise HTTPException(status_code=404, detail="Not found") - for field, value in data.dict().items(): - setattr(obj, field, value) - db.commit() - db.refresh(obj) - return obj - - -@router.delete("/{m3_key}", status_code=204) -async def delete_country( - m3_key: str, - db: Session = Depends(get_core_db), - current_user: dict = Depends(has_role("admin")), -): - obj = db.query(Country).filter(Country.m3_key == m3_key).first() - if not obj: - raise HTTPException(status_code=404, detail="Not found") - db.delete(obj) - db.commit() - return None diff --git a/backend/api/v1/modules/public/reference_data/countries/seed.py b/backend/api/v1/modules/public/reference_data/countries/seed.py deleted file mode 100644 index bba72c82..00000000 --- a/backend/api/v1/modules/public/reference_data/countries/seed.py +++ /dev/null @@ -1,647 +0,0 @@ -seed = [ - ( - "ABW", - "A0", - "AW", - "Aruba (Territorio Holandes de Ultramar)", - "Aruba (Netherlands Territory)", - ), - ( - "AFG", - "A1", - "AF", - "Afganistan (Emirato Islamico De)", - "Afghanistan (Islamic Emirate of)", - ), - ("AGO", "A8", "AO", "Angola ( Republica De )", "Angola (People's Republic of )"), - ("AIA", "AI", "AI", "Anguila", "Anguilla"), - ( - "ALB", - "A2", - "AL", - "Albania ( Republica De)", - "Albania (People's Socialist Republic)", - ), - ("AND", "A7", "AD", "Andorra (Principado De)", "Andorra (Principated of)"), - ( - "ANT", - "B1", - "AN", - "Antillas Neerlandesas (Terr. Holandes de Ultramar)", - "Antilles Netherlands", - ), - ("ARE", "G6", "AE", "Emiratos Arabes Unidos", "United Arab Emirates"), - ("ARG", "B4", "AR", "Argentina ( Republica )", "Argentina (Republic of)"), - ("ARM", "AM", "AM", "Armenia (Republica De)", "Armenia (Republic of)"), - ( - "ATG", - "A9", - "AG", - "Antigua Y Barbuda (Com. Britanica de Naciones)", - "Antigua & Barbuda (Brithish Community)", - ), - ("AUS", "B5", "AU", "Australia ( Comunidad De )", "Australia (Community of)"), - ("AUT", "B6", "AT", "Austria ( Republica De )", "Austria (Republic of)"), - ("AZE", "AZ", "AZ", "Azerbaijan (Republica Azerbaijani)", "Azerbaijan"), - ("BDI", "D2", "BI", "Burundi ( Republica De )", "Burundi (Republic of)"), - ("BEL", "C2", "BE", "Belgica ( Reino De )", "Belgium (Kingdom of)"), - ("BEN", "F9", "BJ", "Benin ( Republica De)", "Benin (People's Republic of)"), - ("BFA", "A6", "BF", "Burkina Faso", "Burkina Faso"), - ( - "BGD", - "B9", - "BD", - "Bangladesh ( Republica Popular De )", - "Bangladesh (People's Republic of)", - ), - ("BGR", "D1", "BG", "Bulgaria ( Republica De )", "Bulgaria (Republic of)"), - ("BHR", "B8", "BH", "Bahrein ( Estado De )", "Bahrain (State of)"), - ("BHS", "B7", "BS", "Bahamas( Comunidad De Las )", "Bahamas (Community of the )"), - ("BIH", "X7", "BA", "Bosnia y Herzegovina", "Bosnia & Herzegovina"), - ("BLR", "BY", "BL", "Bielorrusia (Republica De)", "Belarus (Republic of)"), - ("BLZ", "C3", "BZ", "Belice", "Belize"), - ("BMU", "C4", "BM", "Bermudas", "Bermuda"), - ("BOL", "C6", "BO", "Bolivia ( Republica De )", "Bolivia (Republic of)"), - ( - "BRA", - "C8", - "BR", - "Brasil (Republica Federativa De)", - "Brazil (Federative Republic of)", - ), - ( - "BRB", - "C1", - "BB", - "Barbados (Comunidad Britanica de Naciones)", - "Barbados (Brithish Community of Nations)", - ), - ("BRN", "C9", "BN", "Brunei (Estado De)(Residencia de Paz)", "Brunei (State of)"), - ("BTN", "D3", "BT", "Butan (Reino De )", "Bhutan (Royal Goverment of)"), - ("BUR", "BU", "BU", "Burma ( Birmania )", "Burma (Birmany)"), - ("BWA", "C7", "BW", "Bostwana ( Republica De )", "Botswana (Republic of)"), - ("CAF", "CF", "RB", "Republica Centro Africana", "Central African Republic"), - ("CAN", "D9", "CA", "Canada", "Canada"), - ( - "CCK", - "E3", - "CC", - "Cocos ( Keeling, Islas Australianas)", - "Cocos Keeling Islands (Australian Island", - ), - ("CHE", "U8", "CH", "Suiza (Confederacion)", "Switzerland (Confederation)"), - ("CHL", "F6", "CL", "Chile ( Republica De )", "Chile (Republic of)"), - ( - "CHN", - "Z3", - "CN", - "China ( Republica Popular) Derogado", - "China (People's Republic of)", - ), - ("CIA", "E2", "VA", "Ciudad Del Vaticano ( Estado De La )", "Vatican City State"), - ( - "CIV", - "F1", - "CT", - "Costa de Marfil (Republica De La)", - "Ivory Coast (Republic of)", - ), - ("CMR", "D8", "CM", "Camerun ( Republica Del )", "Cameroon (Republic of the)"), - ("COG", "E6", "CG", "Congo ( Republica Del )", " Congo (Republic of the)"), - ("COK", "E7", "CK", "Cook ( Islas )", "Cook Islands"), - ("COL", "E4", "CO", "Colombia ( Republica De )", "Colombia (Republic of)"), - ("COM", "E5", "KM", "Comoras ( Islas )", "Comoros Islands"), - ("CPV", "D4", "CV", "Cabo Verde ( Republica De )", "Cape Verde (Republic of)"), - ("CRI", "F2", "CR", "Costa Rica ( Republica De )", "Costa Rica (Republic of)"), - ("CUB", "F3", "CU", "Cuba ( Republica De )", "Cuba (Republic of)"), - ( - "CUR", - "D0", - "UR", - "Curazao (Terr. Holandes De Ultramar)", - "Curazao (Netherlands Territory)", - ), - ("CXI", "N8", "CX", "Navidad ( Christmas ) ( Islas )", "Christmas Islands"), - ("CYM", "D6", "KY", "Caiman ( Islas )", "Cayman Islands"), - ("CYP", "F8", "CY", "Chipre ( Republica De )", "Cyprus (Island of)"), - ("CZE", "CZ", "CZ", "Republica Checa", "Czech Federative Republic"), - ( - "DEU", - "A4", - "DE", - "Alemania ( Republica Federal De )", - "Germany (Federal Republic of)", - ), - ("DJI", "V4", "DJ", "Djibouti ( Republica De )", "Djibouti (Republic of)"), - ("DMA", "G2", "DM", "Dominica ( Comunidad De )", "Dominica (Community of)"), - ("DNK", "G1", "DK", "Dinamarca ( Reino De )", "Denmark (Kingdom of)"), - ("DOM", "S2", "DO", "Republica Dominicana", "Dominican Republic"), - ( - "DSM", - "FM", - "FM", - "Estado Federado De Micronesia", - "Micronesia Federated State of", - ), - ( - "DZA", - "B3", - "DZ", - "Argelia ( Republica Democratica y Popular de) Dero", - "Argelia (People's Democratic Republic)", - ), - ("ECU", "G3", "EC", "Ecuador ( Republica Del)", "Ecuador (Republic of the)"), - ("EGY", "G4", "EG", "Egipto ( Republica Arabe De )", "Egypt (Arab Republic of)"), - ("EMU", "EU", "EU", "Comunidad Europea", "European Economic Community"), - ("ERI", "ER", "ER", "Eritrea (Estado De)", "Eritrea (State of)"), - ( - "ESH", - "EH", - "EH", - "Sahara Occidental (Rep. Arabe Saharavi Dem.)", - "Western Sahara (Arab Democratic Rep.)", - ), - ("ESP", "G7", "ES", "España ( Reino De )", "Spain (Kingdom of)"), - ("EST", "G0", "EE", "Estonia (Republica De)", "Estonia (Republic of)"), - ( - "ETH", - "G9", - "ET", - "Etiopia ( Republica Democratica Federal)", - "Ethiopia (Federal Democratic Republic)", - ), - ("FIN", "H4", "FI", "Finlandia ( Republica De )", "Finland (Republic of)"), - ("FJI", "H1", "FJ", "Fidji (Republica De )", "Fiji Islands"), - ("FLK", "FK", "IV", "Islas Malvinas (R.U.)", "Malvine Islands"), - ("FRA", "H5", "FR", "Francia (Republica Francesa)", "France (Republic)"), - ( - "FXA", - "TF", - "TF", - "Territorios Franceses Austriales y Antarticos", - "French Territory of Antartic Austral", - ), - ("GAB", "H6", "GA", "Gabonesa ( Republica )", "Gabonese Republic"), - ( - "GBR", - "R9", - "GB", - "Reino Unido de la Gran Bretaña e Irlanda del Norte", - "United Kingdom (Great Britain, Ireland N", - ), - ("GEO", "GE", "GE", "Georgia (Republica De)", "Georgia (Republic of)"), - ("GHA", "H8", "GH", "Ghana ( Republica De )", "Ghana (Republic of)"), - ("GIB", "GI", "GI", "Gibraltar (R.U.)", "Gibraltar (U. K.)"), - ("GIN", "I8", "GN", "Guinea ( Republica De )", "Guinea (Republic of)"), - ( - "GLP", - "I4", - "GP", - "Guadalupe (Departamento De)", - "Guadeloupe (French Caribean Dependences)", - ), - ("GMB", "H7", "GM", "Gambia ( Republica De La)", "Gambia (Republic of)"), - ( - "GNB", - "J1", - "GW", - "Guinea-Bissau ( Republica De )", - "Guinea-Bissau (Republic of)", - ), - ( - "GNQ", - "I9", - "GQ", - "Guinea Ecuatorial ( Republica De )", - "Equatorial Guinea (Republic of)", - ), - ( - "GRC", - "I2", - "GR", - "Grecia (Republica Helenica)", - "Greece (Helenical Republic of)", - ), - ("GRD", "I1", "GD", "Granada", "Grenada"), - ("GRL", "GL", "GL", "Groenlandia (Dinamarca)", "Greenland (Denmark)"), - ("GTM", "I6", "GT", "Guatemala ( Republica De )", "Guatemala (Republic of)"), - ("GUF", "I7", "GF", "Guyana Francesa", "French Guyana"), - ("GUM", "I5", "GU", "Guam ( E.U.A )", "Guam (U.S.A.)"), - ( - "GUY", - "J2", - "GY", - "Guyana ( Republica Cooperativa De )", - "Guyana (Cooperative Republic of)", - ), - ("GZA", "GZ", "GZ", "Franja De Gaza", "Gaza Strip"), - ( - "HKG", - "J6", - "HK", - "Hong Kong (Region Admiva. Especial de la Rep. )", - "Hong Kong (Territory of)", - ), - ("HND", "J5", "HN", "Honduras ( Republica De )", "Honduras (Republic of)"), - ("HRV", "HR", "HR", "Croacia (Republica De)", "Croatia (Republic of)"), - ("HTI", "J3", "HT", "Haiti ( Republica De )", "Haiti (Republic of)"), - ("HUN", "J7", "HU", "Hungria ( Republica De )", "Hungary (Republic of)"), - ("IDN", "J9", "ID", "Indonesia ( Republica De )", "Indonesia (Republic of)"), - ("IND", "J8", "IN", "India ( Republica De)", "India (Republic of the)"), - ("IRL", "K3", "IE", "Irlanda ( Republica De )", "Ireland (Republic of)"), - ( - "IRN", - "K2", - "IR", - "Iran ( Republica Islamica Del )", - "Iran (Islamic Republic of)", - ), - ("IRQ", "K1", "IQ", "Irak ( Republica De )", "Iraq (Republic of)"), - ("ISL", "K4", "IS", "Islandia ( Republica De )", "Iceland (Republic of)"), - ("ISR", "K5", "IL", "Israel ( Estado De )", "Israel (State of)"), - ("ITA", "K6", "IT", "Italia (Republica Italiana)", "Italy (Republic)"), - ("JAM", "K7", "JM", "Jamaica", "Jamaica"), - ( - "JOR", - "L1", - "JO", - "Jordania ( Reino Hachemita De )", - "Jordan (Hachemite Kingdom of)", - ), - ("JPN", "K9", "JP", "Japon", "Japan"), - ("KAZ", "KZ", "KZ", "Kazakhstan (Republica de)", "Kazakhstan"), - ("KCD", "Z9", "PD", "Paises No Declarados", "Not Declared Countries"), - ("KEN", "L2", "KE", "Kenya ( Republica De )", "Kenya (Republic of)"), - ("KGZ", "KG", "KG", "Kyrgyzstan (Republica Kirgyzia)", "Kyrgyzstan"), - ("KHM", "D7", "KH", "Camboya (Reino de)", "Cambodia"), - ("KIR", "L0", "KI", "Kiribati (Republica de)", "Kiribati"), - ( - "KNA", - "S9", - "KN", - "San Cristobal Y Nieves (Fed. de)(San Kitts-Nevis)", - "St. Christopher - Nevis", - ), - ("KOR", "E8", "KR", "Corea (Republica De)(Corea del Sur)", "Korea Republic of"), - ("KWT", "L3", "KW", "Kuwait (Estado de)", "kuwait"), - ( - "LAO", - "L4", - "LA", - "Republica Democratica Popular Laos", - "Laos (People's Democratic Republic of)", - ), - ("LBN", "L7", "LB", "Libano (Republica de)", "Lebanon"), - ("LBR", "L8", "LR", "Liberia ( Republica De )", "Liberia (Republic of)"), - ( - "LBY", - "L9", - "LY", - "Libia (Jamahiriya Libia Araba Pop. Soc.)", - "Lybia (Arab Jamahiriya)", - ), - ("LCA", "T4", "LC", "Santa Lucia", "Saint Lucia"), - ("LHM", "HM", "HM", "Islas Heard Y Mcdonald", "Heard & MacDonald Islands"), - ( - "LIE", - "L5", - "LI", - "Liechtenstein (Principado de)", - "Liechtenstein (Principated of)", - ), - ( - "LKA", - "U4", - "LK", - "Sri Lanka ( Republica Democratica Soc.)", - "Sri Lanka (Socialist Democratic Republic", - ), - ("LSO", "L6", "LS", "Lesotho ( Reino De )", "Lesotho (Kingdom of)"), - ("LTU", "Y2", "LT", "Lituania (Republica de)", "Lithuania"), - ( - "LUX", - "M0", - "LU", - "Luxemburgo ( Gran Ducado De)", - "Luxembourg (Great Ducated of)", - ), - ("LVA", "Y1", "LV", "Letonia (Republica de)", "Latvia"), - ("MAC", "M1", "MO", "Macao", "Macau"), - ("MAR", "M8", "MR", "Marruecos ( Reino De )", "Morocco (Kingdom of)"), - ("MCO", "N0", "MC", "Monaco (Principado De)", "Monaco (Principated of)"), - ("MDA", "MD", "MD", "Moldavia (Republica de)", "Moldova"), - ( - "MDG", - "M2", - "MG", - "Madagascar ( Republica De)", - "Madagascar (Democratic Republic of)", - ), - ("MDV", "M5", "MV", "Maldivas ( Republica De )", "Maldives (Republic of the)"), - ("MEX", "N3", "MX", "Mexico (Estados Unidos Mexicanos)", "Mexico"), - ("MHL", "MH", "MH", "Islas Marshall", "Marshall Islands"), - ( - "MKD", - "MK", - "MK", - "Macedonia (Antigua Rep. Yugoslava De)", - "Macedonia (Old Yugoslavian Republic)", - ), - ("MLI", "M6", "ML", "Mali ( Republica De )", "Mali (Republic of)"), - ("MLT", "M7", "MT", "Malta ( Republica De )", "Malta and Gozo (Republic of)"), - ("MMR", "C5", "MM", "Myanmar ( Union De )", "Myammar (Union of)"), - ("MNE", "ME", "ME", "Montenegro", ""), - ("MNG", "N4", "MN", "Mongolia", "Mongolia (People's Republic of)"), - ( - "MNP", - "MP", - "IM", - "Islas Marianas Septentrionales", - "Marianes Septentrional Islands", - ), - ( - "MOZ", - "N6", - "MZ", - "Mozambique ( Republica De)", - "Mozambique (People's Republic of)", - ), - ( - "MRT", - "N2", - "RT", - "Mauritania ( Republica Islamica De )", - "Mauritania (Islamic Republic of)", - ), - ("MSR", "N5", "MS", "Monserrat ( Isla )", "Montserrat Island"), - ( - "MTQ", - "M9", - "MQ", - "Martinica (Departamento de) (Francia)", - "Martinique (Department of)", - ), - ("MUS", "N1", "MU", "Mauricio ( Republica De )", "Mauritius (State of)"), - ("MWI", "M4", "MW", "Malawi ( Republica De )", "Malawi (Republic of)"), - ("MYS", "M3", "MY", "Malasia", "Malaysia (Federation of)"), - ("NAM", "P0", "NA", "Namibia ( Republica De )", "Namibia (Republic of)"), - ( - "NCA", - "P7", - "TE", - "Terr. Frances Ultramar Nueva Caledonia", - "French Territory of New Caledonia", - ), - ("NCL", "NC", "NC", "Nueva Caledonia (Terr.Frances de Ultramar)", "New Caledonia"), - ("NER", "P2", "NE", "Niger ( Republica De)", "Niger (Federal Republic of)"), - ("NFK", "P5", "NF", "Norfolk ( Isla )", "Norfolk Island"), - ( - "NGA", - "P3", - "NG", - "Nigeria ( Republica Federal De)", - "Nigeria (Federal Republic of)", - ), - ("NIC", "P1", "NI", "Nicaragua ( Republica De )", "Nicaragua (Republic of)"), - ("NIU", "P4", "NU", "Nive ( Isla )", "Nive Island"), - ("NOR", "P6", "NO", "Noruega ( Reino De )", "Norway (Kingdom of)"), - ("NPL", "N9", "NP", "Nepal ( Reino de )", "Nepal (Republic of)"), - ("NRU", "N7", "NR", "Nauru", "Nauru"), - ("NZL", "P9", "NZ", "Nueva Zelandia", "New Zealand"), - ("OMN", "Q2", "OM", "Oman (Sultanato De )", "Oman (Sultanate of)"), - ( - "PAK", - "Q7", - "PK", - "Pakistan ( Republica Islamica De )", - "Pakistan (Islamic Republic of)", - ), - ("PAN", "Q8", "PA", "Panama ( Republica De )", "Panama (Republic of)"), - ( - "PCN", - "R3", - "PN", - "Pitcairns ( Islas Dependencia Britanica )", - "Pitcairn Island (Brithish Dependence)", - ), - ("PER", "R2", "PE", "Peru ( Republica Del )", "Peru (Republic of )"), - ( - "PHL", - "H3", - "PH", - "Filipinas ( Republica De Las )", - "Philippines (Republic of the)", - ), - ( - "PIK", - "Q3", - "PI", - "Pacifico Islas Del ( Admon. E.U.A. )", - "Pacific Islands (U.S.A. Administration)", - ), - ("PLW", "PW", "PW", "Palau (Republica De)", "Palau (Republic of)"), - ( - "PNG", - "P8", - "PP", - "Papua Nueva Guinea (Edo. Independiente de)", - "Papua New Guinea (Independent State of)", - ), - ("POL", "R5", "PL", "Polonia ( Republica De )", "Poland (Republic of)"), - ( - "PRI", - "R7", - "PR", - "Puerto Rico (Edo.Libre Asociado de la Com. de) Der", - "Puerto Rico (Free Asociated State of)", - ), - ( - "PRK", - "E9", - "KP", - "Corea ( Rep. Pop. Dem.de)(Corea del Norte)", - "Korea (North)(People's Democratic Rep.of", - ), - ("PRT", "R6", "PT", "Portugal (Republica Portuguesa)", "Portugal (Republic of)"), - ("PRY", "R1", "PY", "Paraguay ( Republica Del )", "Paraguay (Republic of)"), - ("PSE", "PS", "PS", "Palestina", ""), - ("PTY", "Z2", "ZO", "Zona Del Canal De Panama", "Zone of the Panama's Channel"), - ("PYF", "R4", "PF", "Polinesia Francesa", "French Polynesia"), - ("QAT", "R8", "QA", "Qatar ( Estado De )", "Qatar (State of)"), - ( - "REU", - "S3", - "RE", - "Reunion (Departamento de la) ( Francia)", - "Reunion Islands (French Department)", - ), - ("RKE", "E1", "RK", "Canal Islas del ( Islas Normandas )", "Channel Islands"), - ("ROM", "S5", "RO", "Rumania", "Romania (Republic of)"), - ( - "RUH", - "NT", - "NT", - "Zona Neutral Iraq-Arabia Saudita", - "Neutral Zone of Iraq - Saudi Arabia", - ), - ("RUS", "RU", "RU", "Rusia (Federacion Rusa)", "Russia (Federation)"), - ("RWA", "S6", "RW", "Republica Ruandesa", "Rwanda"), - ("SAU", "B2", "SA", "Arabia Saudita ( Reino De )", "Saudi Arabia (Kingdom of)"), - ("SDN", "U6", "SD", "Sudan ( Republica Del )", "Sudan (Democratic Republic of)"), - ("SEN", "T6", "SN", "Senegal ( Republica Del )", "Senegal (Republic of the)"), - ("SGP", "U1", "SG", "Singapur ( Republica De )", "Singapore (Republic of)"), - ("SHN", "T3", "SH", "Santa Elena", "St. Helena"), - ( - "SJM", - "SJ", - "SJ", - "Islas Svalbard Y Jan Mayen (Noruega)", - "Svalbard & Jan Mayen Islands", - ), - ( - "SLB", - "SB", - "SB", - "Islas Salomon (Com. Britanica de Naciones)", - "Solomon Islands (Brithish Community)", - ), - ("SLE", "T8", "SL", "Sierra Leona ( Republica De )", "Sierra Leone (Republic of)"), - ("SLV", "G5", "SV", "El Salvador ( Republica De )", "El Salvador (Republic of)"), - ( - "SMR", - "T0", - "SM", - "San Marino (Serenisima Republica De)", - "San Marino (Republic of", - ), - ("SOM", "U3", "SO", "Somalia", "Somalia (Democratic Republic of)"), - ("SPM", "T1", "PM", "San Pedro Y Miquelon", "St. Pierre and Miquelon"), - ("SRB", "RS", "RS", "Republica de Serbia", ""), - ( - "STP", - "T5", - "ST", - "Santo Tome Y Principe (Rep. Democratica de)", - "Sao Tome and Principe (Dem. Rep.)", - ), - ("SUR", "U9", "SR", "Suriname ( Republica De )", "Surinam (Republic of)"), - ("SVK", "SK", "SK", "Republica Eslovaca", "Slovakia (Republic)"), - ("SVN", "SI", "SI", "Eslovenia (Republica De)", "Slovenia (Republic of)"), - ("SWE", "U7", "SE", "Suecia ( Reino De )", "Sweden (Kingdom of)"), - ("SWZ", "V0", "SZ", "Swazilandia ( Reino De )", "Swaziland (Kingdom of)"), - ( - "SYC", - "T7", - "SC", - "Seychelles (Republica De Las)", - "Seychelles (Republic of the)", - ), - ("SYR", "U2", "SY", "Siria ( Republica Arabe )", "Syrian Arab Republic"), - ("TCA", "W3", "TC", "Turcas Y Caicos ( Islas )", "Turks and Caicos Islands"), - ("TCD", "F4", "TD", "Chad ( Republica De )", "Chad (Republic of)"), - ("TGO", "V7", "TG", "Togo (Republica Togolesa)", "Togo (Republic)"), - ("THA", "V1", "TH", "Tailandia ( Reino De )", "Thailand (Kingdom of)"), - ("TJK", "TJ", "TJ", "Tadjikistan (Republica De)", "Tadjikistan (Republic of)"), - ("TKL", "TK", "TK", "Islas Tokelau", "Tokelau Islands"), - ("TKM", "TM", "TM", "Turkmenistan (Republica De)", "Turkmenistan (Republic of)"), - ("TMP", "TP", "TP", "Timor Oriental", "East Timor"), - ("TON", "TO", "TO", "Tonga (Reino De)", "Tonga (Kingdom of)"), - ( - "TTO", - "W1", - "TT", - "Trinidad Y Tobago ( Republica De )", - "Trinidad and Tobago (Republic of)", - ), - ("TUN", "W2", "TN", "Tunez ( Republica De )", "Tunisia (Republic of)"), - ("TUR", "W4", "TR", "Turquia ( Republica De )", "Turkey (Republic of)"), - ( - "TUV", - "TV", - "TV", - "Tuvalu (Comunidad Britanica de Naciones)", - "Tuvalu (Brithish Community of Nations)", - ), - ("TWN", "F7", "TW", "Taiwan (Republica de China)", "Taiwan"), - ( - "TZA", - "V2", - "TZ", - "Tanzania ( Republica Unida De )", - "Tanzania United Republic of", - ), - ("UGA", "W5", "UG", "Uganda ( Republica De )", "Uganda (Republic of)"), - ("UKR", "UA", "UA", "Ucrania", "Ukraine"), - ( - "URY", - "W7", - "UY", - "Uruguay ( Republica Oriental Del )", - "Uruguay (Eastern Republic of the)", - ), - ("USA", "G8", "US", "Estados Unidos de America", "United States of America"), - ("UZB", "Y4", "UZ", "Uzbejistan (Republica de)", "Uzbekistan (Republic)"), - ( - "VCT", - "T2", - "VC", - "San Vicente Y Las Granadinas", - "St. Vincent and the Grenadines", - ), - ("VEN", "W8", "VE", "Venezuela ( Republica De )", "Venezuela (Republic of)"), - ("VGB", "X2", "VG", "Virgenes Islas ( Britanicas )", "Virgin Islands (British)"), - ( - "VIR", - "X3", - "VI", - "Virgenes Islas ( Norteamericanas )", - "Virgin Islands (American)", - ), - ( - "VNM", - "W9", - "VN", - "Vietnam ( Republica Socialista De )", - "Vietnam (Socialist Republic of)", - ), - ("VUT", "Q1", "VU", "Vanuatu", "Vanuatu"), - ("WLF", "WF", "WF", "Islas Wallis Y Futuna", "Wallis & Futuna Islands"), - ( - "WSM", - "S8", - "WS", - "Samoa (Estado Independiente de)", - "Western Samoa (Independent State)", - ), - ( - "XCH", - "V3", - "IO", - "Territorios Britanicos Del Oceano Indico", - "Brithish Territory of the Indic Ocean", - ), - ("YEM", "YE", "YE", "Yemen (Republica De)", "Yemen (Republic of)"), - ( - "YUG", - "X8", - "YU", - "Yugoslavia (Republica Federal de)", - "Yugoslavia (Federal Republic of)", - ), - ( - "ZAF", - "U5", - "ZA", - "Sudafrica ( Republica De ) Derogado", - "South Africa (Republic of)", - ), - ("ZMB", "Z1", "ZM", "Zambia ( Republica De )", "Zambia (Republic of)"), - ("ZWE", "S4", "ZW", "Zimbabwe ( Republica De )", "Zimbabwe (Republic of)"), - ( - "ZYA", - "J4", - "NL", - "Paises Bajos ( Reino De Los )(Holanda)", - "Netherlands (Kingdom of)(Holand)", - ), -] diff --git a/backend/api/v1/modules/public/reference_data/countries/test_countries.py b/backend/api/v1/modules/public/reference_data/countries/test_countries.py deleted file mode 100644 index 603217ac..00000000 --- a/backend/api/v1/modules/public/reference_data/countries/test_countries.py +++ /dev/null @@ -1,46 +0,0 @@ -import pytest -from api.v1.modules.public.reference_data.countries.routes import router -from fastapi import FastAPI -from fastapi.testclient import TestClient - -app = FastAPI() -app.include_router(router) -client = TestClient(app) - - -@pytest.mark.usefixtures("client", "access_token") -def test_list_countries_with_search(client, access_token): - headers = {"Authorization": f"Bearer {access_token}"} - # Search for a known country from seed, e.g., "Mexico" or "MEX" - response = client.get("/countries/?search=Mexico", headers=headers) - assert response.status_code == 200 - data = response.json() - assert "items" in data - # Depending on seed data, there should be at least one item if "Mexico" exists - if data["items"]: - for item in data["items"]: - assert "mexico" in item["description_es"].lower() or "mexico" in item["description_en"].lower() - - -@pytest.mark.usefixtures("client", "access_token") -def test_get_country_not_found(client, access_token): - headers = {"Authorization": f"Bearer {access_token}"} - response = client.get("/countries/invalid_key", headers=headers) - assert response.status_code == 404 - - -def test_create_country_forbidden(): - response = client.post("/countries/", json={"m3_key": "TST", "description": "Test"}) - assert response.status_code in (403, 405, 404) - - -def test_update_country_forbidden(): - response = client.put( - "/countries/TST", json={"m3_key": "TST", "description": "Test"} - ) - assert response.status_code in (403, 405, 404) - - -def test_delete_country_forbidden(): - response = client.delete("/countries/TST") - assert response.status_code in (403, 405, 404) diff --git a/backend/api/v1/modules/public/reference_data/currency_types/dto.py b/backend/api/v1/modules/public/reference_data/currency_types/dto.py deleted file mode 100644 index b78be839..00000000 --- a/backend/api/v1/modules/public/reference_data/currency_types/dto.py +++ /dev/null @@ -1,9 +0,0 @@ -from pydantic import BaseModel, ConfigDict, Field - - -class CurrencyTypeDTO(BaseModel): - code: str = Field(..., min_length=1, max_length=3) - currency_name: str - country_description: str - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/public/reference_data/currency_types/models.py b/backend/api/v1/modules/public/reference_data/currency_types/models.py deleted file mode 100644 index b4b507ea..00000000 --- a/backend/api/v1/modules/public/reference_data/currency_types/models.py +++ /dev/null @@ -1,24 +0,0 @@ -from core.database import Base -from sqlalchemy import PrimaryKeyConstraint, String -from sqlalchemy.orm import Mapped, mapped_column - - -class CurrencyType(Base): - __tablename__ = "currency_types" # GTiposMoneda - __table_args__ = ( - PrimaryKeyConstraint("code", name="currency_types_pkey"), - {"schema": "public", "extend_existing": True}, - ) - - code: Mapped[str] = mapped_column( - String(3), primary_key=True, nullable=False - ) # código ISO o clave de moneda - currency_name: Mapped[str] = mapped_column( - String(15), nullable=False - ) # nombre de la moneda (por ejemplo: Peso, Dollar) - country_description: Mapped[str] = mapped_column( - String(50) - ) # país asociado o descripción del país - - def __repr__(self): - return f"" diff --git a/backend/api/v1/modules/public/reference_data/currency_types/routes.py b/backend/api/v1/modules/public/reference_data/currency_types/routes.py deleted file mode 100644 index bf658a36..00000000 --- a/backend/api/v1/modules/public/reference_data/currency_types/routes.py +++ /dev/null @@ -1,107 +0,0 @@ -from typing import Any, Dict - -from core.database import get_core_db -from core.security import get_current_user, has_role -from fastapi import APIRouter, Depends, HTTPException, Query -from sqlalchemy.orm import Session -from sqlalchemy import or_ - -from .dto import CurrencyTypeDTO -from .models import CurrencyType -from ..rbac import require_ref_view - -router = APIRouter(prefix="/currency-types") - - -@router.get( - "/", - response_model=Dict[str, Any], - dependencies=[Depends(require_ref_view("ref_currency_types.view"))], -) -async def list_currency_types( - page: int = Query(1, ge=1, description="Número de página"), - page_size: int = Query(50, ge=1, le=100, description="Tamaño de página"), - search: str = Query(None, description="Término de búsqueda"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - skip = (page - 1) * page_size - query = db.query(CurrencyType) - - if search: - search_filter = f"%{search}%" - query = query.filter( - or_( - CurrencyType.code.ilike(search_filter), - CurrencyType.currency_name.ilike(search_filter), - CurrencyType.country_description.ilike(search_filter) - ) - ) - items = query.offset(skip).limit(page_size).all() - total = query.count() - return { - "items": [CurrencyTypeDTO.model_validate(obj) for obj in items], - "total": total, - "page": page, - "page_size": page_size, - } - - -@router.get( - "/{code}", - response_model=CurrencyTypeDTO, - dependencies=[Depends(require_ref_view("ref_currency_types.view"))], -) -async def get_currency_type( - code: str, - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - obj = db.query(CurrencyType).filter(CurrencyType.code == code).first() - if not obj: - raise HTTPException(status_code=404, detail="Not found") - return obj - - -@router.post("/", response_model=CurrencyTypeDTO, status_code=201) -async def create_currency_type( - data: CurrencyTypeDTO, - db: Session = Depends(get_core_db), - current_user: dict = Depends(has_role("admin")), -): - obj = CurrencyType(**data.dict()) - db.add(obj) - db.commit() - db.refresh(obj) - return obj - - -@router.put("/{code}", response_model=CurrencyTypeDTO) -async def update_currency_type( - code: str, - data: CurrencyTypeDTO, - db: Session = Depends(get_core_db), - current_user: dict = Depends(has_role("admin")), -): - obj = db.query(CurrencyType).filter(CurrencyType.code == code).first() - if not obj: - raise HTTPException(status_code=404, detail="Not found") - for field, value in data.dict().items(): - setattr(obj, field, value) - db.commit() - db.refresh(obj) - return obj - - -@router.delete("/{code}", status_code=204) -async def delete_currency_type( - code: str, - db: Session = Depends(get_core_db), - current_user: dict = Depends(has_role("admin")), -): - obj = db.query(CurrencyType).filter(CurrencyType.code == code).first() - if not obj: - raise HTTPException(status_code=404, detail="Not found") - db.delete(obj) - db.commit() - return None diff --git a/backend/api/v1/modules/public/reference_data/currency_types/seed.py b/backend/api/v1/modules/public/reference_data/currency_types/seed.py deleted file mode 100644 index 86262be9..00000000 --- a/backend/api/v1/modules/public/reference_data/currency_types/seed.py +++ /dev/null @@ -1,96 +0,0 @@ -seed = [ - ("AED", "DIRHAM", "EM. ARABES UNIDOS"), - ("ALL", "LEK", "ALBANIA"), - ("ANG", "FLORIN", "ANTILLAS HOLAN"), - ("ARP", "PESO", "ARGENTINA"), - ("AUD", "DOLAR", "AUSTRALIA"), - ("BBD", "DOLAR", "BARBADOS"), - ("BGL", "LEV", "BULGARIA"), - ("BHD", "DINAR", "BAHRAIN"), - ("BMD", "DOLAR", "BERMUDA"), - ("BOP", "BOLIVIANO", "BOLIVIA"), - ("BRC", "REAL", "BRASIL"), - ("BSD", "DOLAR", "BAHAMAS"), - ("BZD", "DOLAR", "BELICE"), - ("CAD", "DOLAR", "CANADA"), - ("CHF", "FRANCO", "SUIZA"), - ("CLP", "PESO", "CHILE"), - ("CNE", "YUAN EXTRACONTI", "CHINA E"), - ("CNY", "YUAN CONTINENTA", "CHINA"), - ("COP", "PESO", "COLOMBIA"), - ("CRC", "COLON", "COSTA RICA"), - ("CSK", "CORONA", "REPUBLICA CHECA"), - ("CUP", "PESO", "CUBA"), - ("DKK", "CORONA", "DINAMARCA"), - ("DOP", "PESO", "REPUBLICA DOMINICANA"), - ("DZD", "DINAR", "ARGELIA"), - ("ECS", "DOLAR", "ECUADOR"), - ("EGP", "LIBRA", "EGIPTO"), - ("ETB", "BIRR", "ETIOPIA"), - ("EUR", "EURO", "U. MON. EUROPEA"), - ("FJD", "DOLAR", "FIDJI"), - ("GHC", "CEDI", "GHANA"), - ("GTO", "QUETZAL", "GUATEMALA"), - ("GYD", "DOLAR", "GUYANA"), - ("HKD", "DOLAR", "HONG KONG"), - ("HNL", "LEMPIRA", "HONDURAS"), - ("HRK", "KUNA", "CROACIA (REPÚBLICA DE)"), - ("HTG", "GOURDE", "HAITI"), - ("HUF", "FORIN", "HUNGRIA"), - ("IDR", "RUPIA", "INDONESIA"), - ("ILS", "SHEKEL", "ISRAEL"), - ("INR", "RUPIA", "INDIA"), - ("IQD", "DINAR", "IRAK"), - ("IRR", "RIYAL", "IRAN"), - ("ISK", "CORONA", "ISLANDIA"), - ("JMD", "DOLAR", "JAMAICA"), - ("JOD", "DINAR", "JORDANIA"), - ("JPY", "YEN", "JAPON"), - ("KES", "CHELIN", "KENYA"), - ("KPW", "WON", "COREA DEL NORTE"), - ("KRW", "WON", "COREA DEL SUR"), - ("KWD", "DINAR", "KUWAIT"), - ("LBP", "LIBRA", "LIBANO"), - ("LKR", "RUPIA", "SRI-LANKA"), - ("LTT", "LITAS", "LITUANIA"), - ("LYD", "DINAR", "LIBIA"), - ("MAD", "DIRHAM", "MARRUECOS"), - ("MXN", "PESO", "MEXICO"), - ("MYR", "RINGGIT", "MALASIA"), - ("NGN", "NAIRA", "NIGERIA (FED)"), - ("NIC", "CORDOBA", "NICARAGUA"), - ("NOK", "CORONA", "NORUEGA"), - ("NZD", "DOLAR", "NUEVA ZELANDA"), - ("PAB", "BALBOA", "PANAMA"), - ("PES", "N. SOL", "PERU"), - ("PHP", "PESO", "FILIPINAS"), - ("PKR", "RUPIA", "PAKISTAN"), - ("PLZ", "ZLOTY", "POLONIA"), - ("PYG", "GUARANI", "PARAGUAY"), - ("ROL", "LEU", "RUMANIA"), - ("RSD", "DINAR", "REPUBLICA DE SERBIA"), - ("RUR", "RUBLO", "FED. RUSA"), - ("SAR", "RIYAL", "ARABIA SAUDITA"), - ("SEK", "CORONA", "SUECIA"), - ("SGD", "DOLAR", "SINGAPUR"), - ("SRG", "DOLAR", "SURINAM"), - ("STG", "LIBRA ESTERLINA", "GRAN BRETAÑA"), - ("SVC", "COLON", "EL SALVADOR"), - ("SYP", "LIBRA", "SIRIA"), - ("THB", "BAHT", "TAILANDIA"), - ("TRL", "LIRA", "TURQUIA"), - ("TTD", "DOLAR", "TRINIDAD Y TOBAGO"), - ("TWD", "NUEVO DOLAR", "TAIWAN"), - ("TZS", "CHELIN", "TANZANIA"), - ("UAK", "HRYVNA", "UCRANIA"), - ("USD", "DOLAR", "E.U.A."), - ("UYP", "PESO", "URUGUAY"), - ("VEB", "BOLIVAR FUERTE", "VENEZUELA"), - ("VND", "DONG", "VIETNAM"), - ("XOF", "FRANCO", "AFRICA CENTRAL"), - ("XXX", "OTRAS MONEDAS", "LOS DEMAS PAISES"), - ("YDD", "RIAL", "YEMEN (DEM. POP.)"), - ("YUD", "DINAR", "YUGOSLAVIA"), - ("ZAR", "RAND", "UNION SUDAFRICANA"), - ("ZRZ", "FRANCO", "REPUBLICA DEMOCRATICA DEL CONGO"), -] diff --git a/backend/api/v1/modules/public/reference_data/currency_types/test_currency_types.py b/backend/api/v1/modules/public/reference_data/currency_types/test_currency_types.py deleted file mode 100644 index 6e98d40a..00000000 --- a/backend/api/v1/modules/public/reference_data/currency_types/test_currency_types.py +++ /dev/null @@ -1,44 +0,0 @@ -import pytest -from api.v1.modules.public.reference_data.currency_types.routes import router -from fastapi import FastAPI -from fastapi.testclient import TestClient - -app = FastAPI() -app.include_router(router) -client = TestClient(app) - - -@pytest.mark.usefixtures("client", "access_token") -def test_list_currency_types(client, access_token): - headers = {"Authorization": f"Bearer {access_token}"} - response = client.get("/currency-types/", headers=headers) - assert response.status_code == 200 - assert "items" in response.json() - assert "page" in response.json() - assert "page_size" in response.json() - - -@pytest.mark.usefixtures("client", "access_token") -def test_get_currency_type_not_found(client, access_token): - headers = {"Authorization": f"Bearer {access_token}"} - response = client.get("/currency-types/invalid_code", headers=headers) - assert response.status_code == 404 - - -def test_create_currency_type_forbidden(): - response = client.post( - "/currency-types/", json={"code": "TST", "description": "Test"} - ) - assert response.status_code in (403, 405, 404) - - -def test_update_currency_type_forbidden(): - response = client.put( - "/currency-types/TST", json={"code": "TST", "description": "Test"} - ) - assert response.status_code in (403, 405, 404) - - -def test_delete_currency_type_forbidden(): - response = client.delete("/currency-types/TST") - assert response.status_code in (403, 405, 404) diff --git a/backend/api/v1/modules/public/reference_data/customs_sections/dto.py b/backend/api/v1/modules/public/reference_data/customs_sections/dto.py deleted file mode 100644 index 410d5028..00000000 --- a/backend/api/v1/modules/public/reference_data/customs_sections/dto.py +++ /dev/null @@ -1,8 +0,0 @@ -from pydantic import BaseModel, ConfigDict, Field - - -class CustomsSectionDTO(BaseModel): - customs_code: str = Field(..., min_length=1, max_length=3) - section_name: str - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/public/reference_data/customs_sections/models.py b/backend/api/v1/modules/public/reference_data/customs_sections/models.py deleted file mode 100644 index 011967b7..00000000 --- a/backend/api/v1/modules/public/reference_data/customs_sections/models.py +++ /dev/null @@ -1,17 +0,0 @@ -from core.database import Base -from sqlalchemy import PrimaryKeyConstraint, String -from sqlalchemy.orm import mapped_column - - -class CustomsSection(Base): - __tablename__ = "customs_sections" # GAduanaSec - __table_args__ = ( - PrimaryKeyConstraint("customs_code", name="customs_code_pkey"), - {"schema": "public", "extend_existing": True}, - ) - - customs_code = mapped_column(String(3), nullable=False) - section_name = mapped_column(String(255), nullable=False) - - def __repr__(self): - return f"" diff --git a/backend/api/v1/modules/public/reference_data/customs_sections/routes.py b/backend/api/v1/modules/public/reference_data/customs_sections/routes.py deleted file mode 100644 index a78f3a6c..00000000 --- a/backend/api/v1/modules/public/reference_data/customs_sections/routes.py +++ /dev/null @@ -1,118 +0,0 @@ -from typing import Any, Dict - -from core.database import get_core_db -from core.security import get_current_user, has_role -from fastapi import APIRouter, Depends, HTTPException, Query -from sqlalchemy.orm import Session -from sqlalchemy import or_ - -from .dto import CustomsSectionDTO -from .models import CustomsSection -from ..rbac import require_ref_view - -router = APIRouter(prefix="/customs-sections") - - -@router.get( - "/", - response_model=Dict[str, Any], - dependencies=[Depends(require_ref_view("ref_customs_sections.view"))], -) -def list_customs_sections( - page: int = Query(1, ge=1, description="Número de página"), - page_size: int = Query(50, ge=1, le=100, description="Tamaño de página"), - search: str = Query(None, description="Término de búsqueda"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - skip = (page - 1) * page_size - query = db.query(CustomsSection) - - if search: - search_filter = f"%{search}%" - query = query.filter( - or_( - CustomsSection.customs_code.ilike(search_filter), - CustomsSection.section_name.ilike(search_filter) - ) - ) - items = query.offset(skip).limit(page_size).all() - total = query.count() - return { - "items": [CustomsSectionDTO.model_validate(obj) for obj in items], - "total": total, - "page": page, - "page_size": page_size, - } - - -@router.get( - "/{customs_code}", - response_model=CustomsSectionDTO, - dependencies=[Depends(require_ref_view("ref_customs_sections.view"))], -) -def get_customs_section( - customs_code: str, - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - obj = ( - db.query(CustomsSection) - .filter(CustomsSection.customs_code == customs_code) - .first() - ) - if not obj: - raise HTTPException(status_code=404, detail="Not found") - return obj - - -@router.post("/", response_model=CustomsSectionDTO, status_code=201) -def create_customs_section( - data: CustomsSectionDTO, - db: Session = Depends(get_core_db), - current_user: dict = Depends(has_role("admin")), -): - obj = CustomsSection(**data.dict()) - db.add(obj) - db.commit() - db.refresh(obj) - return obj - - -@router.put("/{customs_code}", response_model=CustomsSectionDTO) -def update_customs_section( - customs_code: str, - data: CustomsSectionDTO, - db: Session = Depends(get_core_db), - current_user: dict = Depends(has_role("admin")), -): - obj = ( - db.query(CustomsSection) - .filter(CustomsSection.customs_code == customs_code) - .first() - ) - if not obj: - raise HTTPException(status_code=404, detail="Not found") - for field, value in data.dict().items(): - setattr(obj, field, value) - db.commit() - db.refresh(obj) - return obj - - -@router.delete("/{customs_code}", status_code=204) -def delete_customs_section( - customs_code: str, - db: Session = Depends(get_core_db), - current_user: dict = Depends(has_role("admin")), -): - obj = ( - db.query(CustomsSection) - .filter(CustomsSection.customs_code == customs_code) - .first() - ) - if not obj: - raise HTTPException(status_code=404, detail="Not found") - db.delete(obj) - db.commit() - return None diff --git a/backend/api/v1/modules/public/reference_data/customs_sections/seed.py b/backend/api/v1/modules/public/reference_data/customs_sections/seed.py deleted file mode 100644 index e649d707..00000000 --- a/backend/api/v1/modules/public/reference_data/customs_sections/seed.py +++ /dev/null @@ -1,170 +0,0 @@ -seed = [ - ("01", "AEROPUERTO INTERNACIONAL GENERAL JUAN N. ALVAREZ, ACAPULCO, GUERRERO."), - ("010", "ACAPULCO, ACAPULCO DE JUAREZ, GUERRERO."), - ("012", "AEROPUERTO INTERNACIONAL GENERAL JUAN N. ALVAREZ, ACAPULCO, GUERRERO."), - ("020", "AGUA PRIETA, AGUA PRIETA, SONORA."), - ("050", "SUBTENIENTE LOPEZ, SUBTENIENTE LOPEZ, QUINTANA ROO."), - ( - "051", - "SUBTENIENTE LOPEZ II „CHACTEMAL“, OTHÓN P. BLANCO, CHETUMAL, QUINTANA ROO.", - ), - ("060", "CIUDAD DEL CARMEN, CIUDAD DEL CARMEN, CAMPECHE."), - ("063", "SEYBAPLAYA, CHAMPOTON, CAMPECHE."), - ("070", "CIUDAD JUAREZ, CIUDAD JUAREZ, CHIHUAHUA."), - ("071", "PUENTE INTERNACIONAL ZARAGOZA-ISLETA, CIUDAD JUAREZ, CHIHUAHUA."), - ("072", "SAN JERONIMO-SANTA TERESA, CIUDAD JUAREZ, CHIHUAHUA."), - ("073", "AEROPUERTO INTERNACIONAL ABRAHAM GONZALEZ, CIUDAD JUAREZ, CHIHUAHUA."), - ("074", "GUADALUPE-TORNILLO, GUADALUPE DE BRAVO, CHIHUAHUA."), - ("080", "COATZACOALCOS, COATZACOALCOS, VERACRUZ."), - ("110", "ENSENADA, ENSENADA, BAJA CALIFORNIA."), - ("120", "GUAYMAS, GUAYMAS, SONORA."), - ( - "121", - "AEROPUERTO INTERNACIONAL GENERAL IGNACIO PESQUEIRA GARCIA, HERMOSILLO, SONORA.", - ), - ( - "123", - "CIUDAD OBREGON ADYACENTE AL AEROPUERTO DE CIUDAD OBREGON, CAJEME, SONORA.", - ), - ("140", "LA PAZ, LA PAZ, BAJA CALIFORNIA SUR."), - ("142", "SAN JOSE DEL CABO, LOS CABOS, BAJA CALIFORNIA SUR."), - ("143", "CABO SAN LUCAS, LOS CABOS, BAJA CALIFORNIA SUR."), - ("144", "SANTA ROSALIA, MULEGE, BAJA CALIFORNIA SUR."), - ("145", "LORETO, LORETO, BAJA CALIFORNIA SUR."), - ("147", "PICHILINGÜE, LA PAZ, BAJA CALIFORNIA SUR."), - ("160", "MANZANILLO, MANZANILLO, COLIMA."), - ("161", "ARMERÍA, ARMERÍA, COLIMA."), - ("17", "AEROPUERTO INTERNACIONAL GENERAL SERVANDO CANALES, MATAMOROS, TAMAULIPAS."), - ("170", "MATAMOROS, MATAMOROS, TAMAULIPAS."), - ("171", "LUCIO BLANCO-LOS INDIOS, MATAMOROS, TAMAULIPAS."), - ("172", "SECCION ADUANERA FERROVIARIA DE MATAMOROS."), - ("180", "MAZATLAN, MAZATLAN, SINALOA."), - ("183", "TOPOLOBAMPO, AHOME, SINALOA."), - ("184", "AEROPUERTO INTERNACIONAL DE CULIACAN, CULIACAN, SINALOA."), - ("190", "MEXICALI, MEXICALI, BAJA CALIFORNIA."), - ("192", "LOS ALGODONES, MEXICALI, BAJA CALIFORNIA."), - ("193", "SAN FELIPE, MEXICALI, BAJA CALIFORNIA."), - ("200", "MÉXICO, CIUDAD DE MÉXICO."), - ( - "202", - "IMPORTACION Y EXPORTACION DE CONTENEDORES, DELEGACION AZCAPOTZALCO, CIUDAD DE MÉXICO.", - ), - ("220", "NACO, NACO, SONORA."), - ("230", "NOGALES, NOGALES, SONORA."), - ("231", "SASABE, SARIC, SONORA."), - ( - "24", - "AEROPUERTO INTERNACIONAL DE NUEVO LAREDO „QUETZALCOATL“, NUEVO LAREDO, TAMAULIPAS.", - ), - ("240", "NUEVO LAREDO, NUEVO LAREDO, TAMAULIPAS."), - ("250", "OJINAGA, OJINAGA, CHIHUAHUA."), - ("260", "PUERTO PALOMAS, PUERTO PALOMAS, CHIHUAHUA."), - ("27", "RIO ESCONDIDO, NAVA, COAHUILA."), - ("270", "PIEDRAS NEGRAS, PIEDRAS NEGRAS, COAHUILA."), - ("271", "AEROPUERTO INTERNACIONAL PLAN DE GUADALUPE, RAMOS ARIZPE, COAHUILA."), - ("280", "PROGRESO, PROGRESO, YUCATAN."), - ("282", "AEROPUERTO INTERNACIONAL LIC. MANUEL CRESCENCIO REJON, MERIDA, YUCATAN."), - ("300", "CIUDAD REYNOSA, CIUDAD REYNOSA, TAMAULIPAS."), - ("302", "LAS FLORES, RIO BRAVO, TAMAULIPAS."), - ( - "304", - "AEROPUERTO INTERNACIONAL GENERAL. LUCIO BLANCO, CIUDAD REYNOSA, TAMAULIPAS.", - ), - ("305", "RIO BRAVO-DONNA, RIO BRAVO, TAMAULIPAS."), - ("306", "ANZALDUAS, CIUDAD REYNOSA, TAMAULIPAS."), - ("310", "SALINA CRUZ, SALINA CRUZ, OAXACA."), - ("311", "AEROPUERTO INTERNACIONAL DE OAXACA, SANTA CRUZ XOXOCOTLAN, OAXACA."), - ("330", "SAN LUIS RIO COLORADO, SAN LUIS RIO COLORADO, SONORA."), - ("340", "CIUDAD MIGUEL ALEMAN, CIUDAD MIGUEL ALEMAN, TAMAULIPAS."), - ("342", "GUERRERO, GUERRERO, TAMAULIPAS."), - ("37", "AEROPUERTO INTERNACIONAL DE TAPACHULA, TAPACHULA, CHIAPAS."), - ("370", "CIUDAD HIDALGO, CIUDAD HIDALGO, CHIAPAS."), - ("372", "CIUDAD TALISMAN, TUXTLA CHICO, CHIAPAS."), - ("375", "PUERTO CHIAPAS, TAPACHULA, CHIAPAS."), - ("376", "CIUDAD CUAUHTEMOC, FRONTERA COMALAPA, CHIAPAS."), - ("380", "TAMPICO, TAMPICO, TAMAULIPAS."), - ("390", "TECATE, TECATE, BAJA CALIFORNIA."), - ("400", "TIJUANA, TIJUANA, BAJA CALIFORNIA."), - ( - "402", - "AEROPUERTO INTERNACIONAL GENERAL ABELARDO L. RODRIGUEZ, TIJUANA, BAJA CALIFORNIA.", - ), - ("420", "TUXPAN, TUXPAN DE RODRIGUEZ CANO, VERACRUZ."), - ("421", "TUXPAN, TUXPAN, VERACRUZ."), - ("430", "VERACRUZ, VERACRUZ, VERACRUZ."), - ( - "432", - "AEROPUERTO INTERNACIONAL GENERAL HERIBERTO JARA CORONA, VERACRUZ, VERACRUZ.", - ), - ("440", "CIUDAD ACUÑA, CIUDAD ACUÑA, COAHUILA."), - ("460", "TORREON, TORREON, COAHUILA."), - ("461", "AEROPUERTO DE TORREÓN, COAHUILA DE ZARAGOZA."), - ("462", "GOMEZ PALACIO, GOMEZ PALACIO, DURANGO."), - ("463", "AEROPUERTO INTERNACIONAL GENERAL GUADALUPE VICTORIA, DURANGO, DURANGO."), - ("470", "AEROPUERTO INTERNACIONAL DE LA CIUDAD DE MEXICO."), - ( - "471", - "SATELITE, PARA IMPORTACION Y EXPORTACION POR VIA AEREA, AEROPUERTO INTERNACIONAL BENITO JUAREZ DE LA CIUDAD DE MEXICO.", - ), - ( - "472", - "CENTRO POSTAL MECANIZADO, POR VIA POSTAL Y POR TRAFICO AEREO, AEROPUERTO INTERNACIONAL BENITO JUAREZ DE LA CIUDAD DE MEXICO.", - ), - ("480", "GUADALAJARA, TLACOMULCO DE ZUÑIGA, JALISCO."), - ("481", "PUERTO VALLARTA, PUERTO VALLARTA, JALISCO."), - ("484", "TERMINAL INTERMODAL FERROVIARIA, GUADALAJARA, JALISCO."), - ("50", "SONORA, PITIQUITO, SONORA."), - ("500", "SONOYTA, SONOYTA, SONORA."), - ("501", "SAN EMETERIO, GENERAL PLUTARCO ELIAS CALLES, SONORA."), - ("510", "LAZARO CARDENAS, LAZARO CARDENAS, MICHOACAN."), - ( - "511", - "AEROPUERTO INTERNACIONAL IXTAPA-ZIHUATANEJO, ZIHUATANEJO DE AZUETA, GUERRERO.", - ), - ("520", "MONTERREY, GENERAL MARIANO ESCOBEDO, NUEVO LEON."), - ("521", "AEROPUERTO INTERNACIONAL GENERAL MARIANO ESCOBEDO, APODACA, NUEVO LEON."), - ("523", "SALINAS VICTORIA A (TERMINAL FERROVIARIA), SALINAS VICTORIA, NUEVO LEON."), - ("524", "GENERAL ESCOBEDO, GENERAL ESCOBEDO, NUEVO LEON."), - ("525", "SALINAS VICTORIA B (INTERPUERTO), SALINAS VICTORIA, NUEVO LEON."), - ("530", "CANCUN, CANCUN, QUINTANA ROO."), - ("532", "AEROPUERTO INTERNACIONAL DE COZUMEL, COZUMEL, QUINTANA ROO."), - ("533", "PUERTO MORELOS, BENITO JUAREZ, QUINTANA ROO."), - ("640", "QUERETARO, EL MARQUES Y COLON, QUERETARO."), - ("647", "HIDALGO, ATOTONILCO DE TULA, HIDALGO."), - ("650", "TOLUCA, TOLUCA, ESTADO DE MEXICO."), - ("651", "SAN CAYETANO MORELOS, TOLUCA, ESTADO DE MÉXICO"), - ("670", "CHIHUAHUA, CHIHUAHUA, CHIHUAHUA."), - ("671", "PARQUE INDUSTRIAL LAS AMERICAS, CHIHUAHUA, CHIHUAHUA."), - ( - "672", - "AEROPUERTO INTERNACIONAL GENERAL ROBERTO FIERRO VILLALOBOS, CHIHUAHUA, CHIHUAHUA.", - ), - ("73", "CHICALOTE, SAN FRANCISCO DE LOS ROMO, AGUASCALIENTES."), - ("730", "AGUASCALIENTES, AGUASCALIENTES, AGUASCALIENTES."), - ("731", "PARQUE MULTIMODAL INTERPUERTO, SAN LUIS POTOSI, SAN LUIS POTOSI."), - ("732", "AEROPUERTO INTERNACIONAL GENERAL LEOBARDO C. RUIZ, EN CALERA ZACATECAS."), - ( - "733", - "AEROPUERTO INTERNACIONAL PONCIANO ARRIAGA, SOLEDAD DE GRACIANO SANCHEZ, SAN LUIS POTOSI.", - ), - ("734", "LA PILA-VILLA, VILLA DE REYES, SAN LUIS POTOSI."), - ( - "735", - "AEROPUERTO INTERNACIONAL LIC. JESUS TERAN PEREDO, AGUASCALIENTES, AGUASCALIENTES.", - ), - ("750", "PUEBLA, HEROICA PUEBLA DE ZARAGOZA, PUEBLA."), - ("751", "CUERNAVACA, JIUTEPEC, MORELOS."), - ("754", "AEROPUERTO INTERNACIONAL HERMANOS SERDAN, HUEJOTZINGO, PUEBLA."), - ("800", "COLOMBIA, COLOMBIA, NUEVO LEON."), - ("810", "ALTAMIRA, ALTAMIRA, TAMAULIPAS."), - ("820", "CIUDAD CAMARGO, CIUDAD CAMARGO, TAMAULIPAS."), - ("830", "DOS BOCAS, PARAISO, TABASCO."), - ( - "831", - "AEROPUERTO INTERNACIONAL C.P.A. CARLOS ROVIROSA PEREZ, CIUDAD DE VILLAHERMOSA, CENTRO, TABASCO.", - ), - ("834", "EL CEIBO, TENOSIQUE, TABASCO."), - ("840", "GUANAJUATO, SILAO, GUANAJUATO."), - ("841", "CELAYA, CELAYA, GUANAJUATO."), - ("842", "AEROPUERTO INTERNACIONAL DE GUANAJUATO, SILAO, GUANAJUATO."), -] diff --git a/backend/api/v1/modules/public/reference_data/customs_sections/test_customs_sections.py b/backend/api/v1/modules/public/reference_data/customs_sections/test_customs_sections.py deleted file mode 100644 index c32571af..00000000 --- a/backend/api/v1/modules/public/reference_data/customs_sections/test_customs_sections.py +++ /dev/null @@ -1,44 +0,0 @@ -import pytest -from api.v1.modules.public.reference_data.customs_sections.routes import router -from fastapi import FastAPI -from fastapi.testclient import TestClient - -app = FastAPI() -app.include_router(router) -client = TestClient(app) - - -@pytest.mark.usefixtures("client", "access_token") -def test_list_customs_sections(client, access_token): - headers = {"Authorization": f"Bearer {access_token}"} - response = client.get("/customs-sections/", headers=headers) - assert response.status_code == 200 - assert "items" in response.json() - assert "page" in response.json() - assert "page_size" in response.json() - - -@pytest.mark.usefixtures("client", "access_token") -def test_get_customs_section_not_found(client, access_token): - headers = {"Authorization": f"Bearer {access_token}"} - response = client.get("/customs-sections/invalid_code", headers=headers) - assert response.status_code == 404 - - -def test_create_customs_section_forbidden(): - response = client.post( - "/customs-sections/", json={"customs_code": "TST", "description": "Test"} - ) - assert response.status_code in (403, 405, 404) - - -def test_update_customs_section_forbidden(): - response = client.put( - "/customs-sections/TST", json={"customs_code": "TST", "description": "Test"} - ) - assert response.status_code in (403, 405, 404) - - -def test_delete_customs_section_forbidden(): - response = client.delete("/customs-sections/TST") - assert response.status_code in (403, 405, 404) diff --git a/backend/api/v1/modules/public/reference_data/customs_warehouses/dto.py b/backend/api/v1/modules/public/reference_data/customs_warehouses/dto.py deleted file mode 100644 index 57bb73db..00000000 --- a/backend/api/v1/modules/public/reference_data/customs_warehouses/dto.py +++ /dev/null @@ -1,9 +0,0 @@ -from pydantic import BaseModel, ConfigDict, Field - - -class CustomsWarehouseDTO(BaseModel): - key: str = Field(..., min_length=1, max_length=3) - customs: str - fiscalized_warehouse: str - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/public/reference_data/customs_warehouses/models.py b/backend/api/v1/modules/public/reference_data/customs_warehouses/models.py deleted file mode 100644 index f6c54d05..00000000 --- a/backend/api/v1/modules/public/reference_data/customs_warehouses/models.py +++ /dev/null @@ -1,22 +0,0 @@ -from core.database import Base -from sqlalchemy import PrimaryKeyConstraint, String -from sqlalchemy.orm import Mapped, mapped_column - - -class CustomsWarehouse(Base): - __tablename__ = "customs_warehouses" # GRecintos - __table_args__ = ( - PrimaryKeyConstraint("key", "customs", name="pk_customs_warehouse"), - {"schema": "public", "extend_existing": True}, # opcional - ) - - key: Mapped[str] = mapped_column( - String(3), nullable=False) # clave del recinto - customs: Mapped[str] = mapped_column( - String(100), nullable=False) # aduana asociada - fiscalized_warehouse: Mapped[str] = mapped_column( - String(1000) - ) # recintos fiscalizados (valor legal) - - def __repr__(self): - return f"" diff --git a/backend/api/v1/modules/public/reference_data/customs_warehouses/routes.py b/backend/api/v1/modules/public/reference_data/customs_warehouses/routes.py deleted file mode 100644 index 3e7211e3..00000000 --- a/backend/api/v1/modules/public/reference_data/customs_warehouses/routes.py +++ /dev/null @@ -1,122 +0,0 @@ -from typing import Any, Dict - -from core.database import get_core_db -from core.security import get_current_user, has_role -from fastapi import APIRouter, Depends, HTTPException, Query -from sqlalchemy.orm import Session -from sqlalchemy import or_ - -from .dto import CustomsWarehouseDTO -from .models import CustomsWarehouse -from ..rbac import require_ref_view - -router = APIRouter(prefix="/customs-warehouses") - - -@router.get( - "/", - response_model=Dict[str, Any], - dependencies=[Depends(require_ref_view("ref_customs_warehouses.view"))], -) -def list_customs_warehouses( - page: int = Query(1, ge=1, description="Número de página"), - page_size: int = Query(50, ge=1, le=100, description="Tamaño de página"), - search: str = Query(None, description="Término de búsqueda"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - skip = (page - 1) * page_size - query = db.query(CustomsWarehouse) - - if search: - search_filter = f"%{search}%" - query = query.filter( - or_( - CustomsWarehouse.key.ilike(search_filter), - CustomsWarehouse.customs.ilike(search_filter), - CustomsWarehouse.fiscalized_warehouse.ilike(search_filter) - ) - ) - items = query.offset(skip).limit(page_size).all() - total = query.count() - return { - "items": [CustomsWarehouseDTO.model_validate(obj) for obj in items], - "total": total, - "page": page, - "page_size": page_size, - } - - -@router.get( - "/{key}/{customs}", - response_model=CustomsWarehouseDTO, - dependencies=[Depends(require_ref_view("ref_customs_warehouses.view"))], -) -def get_customs_warehouse( - key: str, - customs: str, - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - obj = ( - db.query(CustomsWarehouse) - .filter(CustomsWarehouse.key == key, CustomsWarehouse.customs == customs) - .first() - ) - if not obj: - raise HTTPException(status_code=404, detail="Not found") - return obj - - -@router.post("/", response_model=CustomsWarehouseDTO, status_code=201) -def create_customs_warehouse( - data: CustomsWarehouseDTO, - db: Session = Depends(get_core_db), - current_user: dict = Depends(has_role("admin")), -): - obj = CustomsWarehouse(**data.dict()) - db.add(obj) - db.commit() - db.refresh(obj) - return obj - - -@router.put("/{key}/{customs}", response_model=CustomsWarehouseDTO) -def update_customs_warehouse( - key: str, - customs: str, - data: CustomsWarehouseDTO, - db: Session = Depends(get_core_db), - current_user: dict = Depends(has_role("admin")), -): - obj = ( - db.query(CustomsWarehouse) - .filter(CustomsWarehouse.key == key, CustomsWarehouse.customs == customs) - .first() - ) - if not obj: - raise HTTPException(status_code=404, detail="Not found") - for field, value in data.dict().items(): - setattr(obj, field, value) - db.commit() - db.refresh(obj) - return obj - - -@router.delete("/{key}/{customs}", status_code=204) -def delete_customs_warehouse( - key: str, - customs: str, - db: Session = Depends(get_core_db), - current_user: dict = Depends(has_role("admin")), -): - obj = ( - db.query(CustomsWarehouse) - .filter(CustomsWarehouse.key == key, CustomsWarehouse.customs == customs) - .first() - ) - if not obj: - raise HTTPException(status_code=404, detail="Not found") - db.delete(obj) - db.commit() - return None diff --git a/backend/api/v1/modules/public/reference_data/customs_warehouses/seed.py b/backend/api/v1/modules/public/reference_data/customs_warehouses/seed.py deleted file mode 100644 index af899d8f..00000000 --- a/backend/api/v1/modules/public/reference_data/customs_warehouses/seed.py +++ /dev/null @@ -1,251 +0,0 @@ -seed = [ - ("1 ", "Acapulco", "Administración Portuaria Integral de Acapulco, S.A. de C.V."), - ( - "10 ", - "Aeropuerto Internacional de la Ciudad de México", - "Cargo Service Center de México, S.A. de C.V.", - ), - ( - "12 ", - "Aeropuerto Internacional de la Ciudad de México", - "DHL Express México, S.A. de C.V.", - ), - ( - "14 ", - "Aeropuerto Internacional de la Ciudad de México", - "Lufthansa Cargo Servicios Logísticos de México, S.A. de C.V.", - ), - ("145", "México", "Ferrocarril y Terminal de Valle de México, S.A. de C.V."), - ("146", "Veracruz", "Cargill de México, S.A. de C.V."), - ( - "147", - "Aeropuerto Internacional de la Ciudad de México", - "Braniff Transport Carga, S.A. de C.V.", - ), - ( - "148", - "Nuevo Laredo", - "Inspecciones Fitosanitarias y Aduaneras de Nuevo Laredo, S.A. de C.V.", - ), - ("149", "Nuevo Laredo", "PG Servicios de Logística, S.C."), - ( - "15 ", - "Aeropuerto Internacional de la Ciudad de México", - "Tramitadores Asociados de Aerocarga, S.A. de C.V.", - ), - ("150", "Piedras Negras", "Mercurio Cargo, S.A. de C.V."), - ("151", "Colombia", "S.R. Asesores Aduanales de Nuevo Laredo, S.C."), - ( - "154", - "Monterrey", - "Federal Express Holdings (México) y Compañía, S.N.C. de C.V.", - ), - ("155", "Ciudad Hidalgo", "Corporativo de Servicios del Sureste, S.A. de C.V."), - ("158", "Monterrey", "Aeropuerto de Monterrey, S.A. de C.V."), - ( - "16 ", - "Aeropuerto Internacional de la Ciudad de México", - "Transportación México Express, S.A. de C.V.", - ), - ("160", "Manzanillo", "Frigorífico de Manzanillo, S.A. de C.V."), - ("161", "Colombia", "Santos Esquivel y Compañía, S.C."), - ("162", "Guadalajara", "Ferrocarril Mexicano, S.A. de C.V."), - ("164", "Monterrey", "United Parcel Service de México, S.A. de C.V."), - ( - "165", - "Aguascalientes", - "Centros de Intercambio de Carga Express Estafeta, S.A. de C.V.", - ), - ("166", "Altamira", "Administración Portuaria Integral de Altamira, S.A. de C.V."), - ("167", "Ciudad Juárez", "Accel, Recinto Fiscalizado, S.A. de C.V."), - ( - "17 ", - "Aeropuerto Internacional de la Ciudad de México", - "United Parcel Service de México, S.A. de C.V.", - ), - ("171", "Chihuahua", "Aeropuerto de Chihuahua, S.A. de C.V."), - ("172", "Veracruz", "Servicios Especiales Portuarios, S.A. de C.V."), - ("173", "Lázaro Cárdenas", "UTTSA, S.A. de C.V."), - ("174", "Ciudad Juárez", "Aeropuerto de Ciudad Juárez, S.A. de C.V."), - ("175", "Cancún", "Caribbean Logistics, S.A. de C.V."), - ("176", "Colombia", "Centro de Carga y Descarga de Colombia, S.A. de C.V."), - ("177", "Nogales", "Grupo Inmobiliario Maymar, S.A. de C.V."), - ("178", "Colombia", "Grupo Coordinador de Importadores, S.A. de C.V."), - ("179", "Altamira", "D.A. Hinojosa Terminal Multiusos, S.A. de C.V."), - ("18 ", "Aeropuerto Internacional de la Ciudad de México", "Varig de México, S.A."), - ("180", "Altamira", "Inmobiliaria Portuaria de Altamira, S.A. de C.V."), - ( - "182", - "Veracruz", - "Servicios, Maniobras y Almacenamientos de Veracruz, S.A. de C.V.", - ), - ("184", "Progreso", "Terminal de Contenedores de Yucatán, S.A. de C.V."), - ("186", "Nuevo Laredo", "Logis Servicios Comerciales, S.A. de C.V."), - ("187", "Manzanillo", "Tecnoadministración del Pacífico, S.A. de C.V."), - ("19 ", "Altamira", "Altamira Terminal Multimodal, S.A. de C.V."), - ("195", "Nuevo Laredo", "Logística de Nuevo Laredo, S.A. de C.V."), - ("196", "Guanajuato", "Ferrocarril Mexicano, S.A. de C.V."), - ("197", "Toluca", "Vamos a México, S.A. de C.V."), - ("198", "Puebla", "A/WTC Puebla, S.A. de C.V."), - ("199", "Lázaro Cárdenas", "Promotora Inmobiliaria del Balsas, S.A. de C.V."), - ("20 ", "Altamira", "Altamira Terminal Portuaria, S.A. de C.V."), - ("200", "Lázaro Cárdenas", "L.C. Terminal Portuaria de Contenedores, S.A. de C.V."), - ("201", "Altamira", "Cooper T. Smith de México, S.A. de C.V."), - ("203", "Altamira", "Grupo Castañeda, S.A. de C.V."), - ("204", "Monterrey", "Ferrocarril Mexicano, S.A. de C.V."), - ("210", "Querétaro", "Terminal Logistics, S.A. de C.V."), - ( - "211", - "Aeropuerto Internacional de la Ciudad de México", - "World Express Cargo de México, S.A. de C.V.", - ), - ( - "212", - "Piedras Negras", - "Consultores de Logística en Comercio Exterior, S.A. de C.V.", - ), - ("214", "Altamira", "Possehl México, S.A. de C.V."), - ("215", "Tampico", "Refitam, S.A. de C.V."), - ("217", "Veracruz", "SSA México, S.A. de C.V."), - ("218", "Tampico", "Administración Portuaria Integral de Tampico, S.A. de C.V."), - ("219", "Tampico", "Administración Portuaria Integral de Tampico, S.A. de C.V."), - ("22 ", "Altamira", "Infraestructura Portuaria Mexicana, S.A. de C.V."), - ("221", "Lázaro Cárdenas", "Promotora Inmobiliaria del Balsas, S.A. de C.V."), - ("222", "Matamoros", "Puerto Los Indios, S.A. de C.V."), - ("223", "Monterrey", "DHL Express México, S.A. de C.V."), - ("224", "Aguascalientes", "Nafta Rail, S.A. de C.V."), - ( - "225", - "Altamira", - "Integradora de Servicios, Transporte y Almacenaje, S.A. de C.V.", - ), - ("226", "Nuevo Laredo", "DAF, Delivery After Frontier, S.A. de C.V."), - ("227", "Matamoros", "Profesionales Mexicanos del Comercio Exterior, S.C."), - ("228", "Guadalajara", "CLA Guadalajara, S.A. de C.V."), - ("229", "Manzanillo", "Maniobras Integradas del Puerto, S.A. de C.V."), - ( - "23 ", - "Coatzacoalcos", - "Administración Portuaria Integral de Coatzacoalcos, S.A. de C.V.", - ), - ("230", "Querétaro", "Terminal Intermodal Logística de Hidalgo, S.A.P.I. de C.V."), - ("231", "Lázaro Cárdenas", "Terminales Portuarias del Pacífico, S.A.P.I. de C.V."), - ("232", "Lázaro Cárdenas", "Arcelormittal Portuarios, S.A. de C.V."), - ("233", "Veracruz", "Excellence Sea & Land Logistics, S.A. de C.V."), - ("234", "Ciudad Juárez", "Accel Comercial, S.A. de C.V."), - ("236", "Lázaro Cárdenas", "Jade Logistic & Commerce, S.A. de C.V."), - ("238", "Cancún", "Cargo RF, S.A. de C.V."), - ("239", "Monterrey", "Controladora de Terminales México, S.A. de C.V."), - ("24 ", "Coatzacoalcos", "Vopak Terminals México, S.A. de C.V."), - ("240", "Nuevo Laredo", "Loginspecs, S.C."), - ("241", "Manzanillo", "Contecon Manzanillo, S.A. de C.V."), - ("25 ", "Colombia", "Dicex Integraciones, S.A. de C.V."), - ("26 ", "Colombia", "Mex Securit, S.A. de C.V."), - ("27 ", "Ensenada", "Ensenada International Terminal, S.A. de C.V."), - ("28 ", "Guadalajara", "Almacenadora GWTC, S.A. de C.V."), - ( - "29 ", - "Guadalajara", - "Federal Express Holdings (México) y Compañía, S.N.C. de C.V.", - ), - ( - "3 ", - "Aeropuerto Internacional de la Ciudad de México", - "Aerovías de México, S.A. de C.V.", - ), - ("30 ", "Guaymas", "Administración Portuaria Integral de Guaymas, S.A. de C.V."), - ( - "31 ", - "Lázaro Cárdenas", - "Administración Portuaria Integral de Lázaro Cárdenas, S.A. de C.V.", - ), - ("33 ", "Lázaro Cárdenas", "Aarhuskarlshamn México, S.A. de C.V."), - ( - "35 ", - "Manzanillo", - "Administración Portuaria Integral de Manzanillo, S.A. de C.V.", - ), - ("36 ", "Manzanillo", "Comercializadora La Junta, S.A. de C.V."), - ("38 ", "Manzanillo", "Operadora de la Cuenca del Pacífico, S.A. de C.V."), - ("39 ", "Manzanillo", "SSA México, S.A. de C.V."), - ( - "4 ", - "Aeropuerto Internacional de la Ciudad de México", - "AAACESA Almacenes Fiscalizados, S.A. de C.V.", - ), - ("40 ", "Manzanillo", "Terminal Internacional de Manzanillo, S.A. de C.V."), - ("42 ", "Mazatlán", "Administración Portuaria Integral de Mazatlán, S.A. de C.V."), - ( - "43 ", - "Mazatlán", - "Administración Portuaria Integral de Topolobampo, S.A. de C.V.", - ), - ("44 ", "Monterrey", "Braniff Air Freight and Company, S.A. de C.V."), - ("45 ", "Monterrey", "Kansas City Southern de México, S.A. de C.V."), - ("46 ", "Nogales", "Servicios de Almacén Fiscalizado de Nogales, S.A. de C.V."), - ("47 ", "Progreso", "Administración Portuaria Integral de Progreso, S.A. de C.V."), - ("49 ", "Progreso", "Grupo de Desarrollo del Sureste, S.A. de C.V."), - ( - "5 ", - "Aeropuerto Internacional de la Ciudad de México", - "México Cargo Handling, S.A. de C.V.", - ), - ("50 ", "Progreso", "Multisur, S.A. de C.V."), - ("51 ", "Querétaro", "Servicios Integrales y Desarrollo GMG, S.A. de C.V."), - ("52 ", "Reynosa", "Recintos Fiscalizados de Noreste, S.A. de C.V."), - ( - "53 ", - "Salina Cruz", - "Administración Portuaria Integral de Salina Cruz, S.A. de C.V.", - ), - ( - "54 ", - "Cancún", - "Administración Portuaria Integral de Quintana Roo, S.A. de C.V.", - ), - ("56 ", "Toluca", "Braniff Air Freight and Company, S.A. de C.V."), - ("57 ", "Toluca", "Federal Express Holdings (México) y Compañía, S.N.C. de C.V."), - ("59 ", "Tuxpan", "Administración Portuaria Integral de Tuxpan, S.A. de C.V."), - ( - "6 ", - "Aeropuerto Internacional de la Ciudad de México", - "American Airlines de México, S.A. de C.V.", - ), - ("60 ", "Tuxpan", "Fenoresinas, S.A. de C.V."), - ("61 ", "Tuxpan", "Terminal Marítima de Tuxpan, S.A. de C.V."), - ("62 ", "Tuxpan", "Terminales Marítimas Transunisa, S.A. de C.V."), - ("63 ", "Veracruz", "Administración Portuaria Integral de Veracruz, S.A. de C.V."), - ("64 ", "Veracruz", "Almacenadora Golmex, S.A. de C.V."), - ("66 ", "Veracruz", "CIF Almacenajes y Servicios, S.A. de C.V."), - ("67 ", "Veracruz", "Corporación Integral de Comercio Exterior, S.A. de C.V."), - ( - "69 ", - "Veracruz", - "Internacional de Contenedores Asociados de Veracruz, S.A. de C.V.", - ), - ( - "7 ", - "Aeropuerto Internacional de la Ciudad de México", - "Braniff Air Freight and Company, S.A. de C.V.", - ), - ("71 ", "Veracruz", "Reparación Integral de Contenedores, S.A. de C.V."), - ("73 ", "Veracruz", "Terminales de Cargas Especializadas, S.A. de C.V."), - ("74 ", "Veracruz", "Vopak Terminals México, S.A. de C.V."), - ("75 ", "Veracruz", "Vopak Terminals, S.A. de C.V."), - ("76 ", "Manzanillo", "Cemex México, S.A. de C.V."), - ("77 ", "Manzanillo", "Corporación Multimodal, S.A. de C.V."), - ("78 ", "Ensenada", "Administración Portuaria Integral de Ensenada, S.A. de C.V."), - ( - "8 ", - "Aeropuerto Internacional de la Ciudad de México", - "Iberia de México, S.A.", - ), - ("81 ", "Tuxpan", "Frigoríficos Especializados de Tuxpan, S.A. de C.V."), - ("82 ", "Veracruz", "SSA México, S.A. de C.V."), - ( - "9 ", - "Aeropuerto Internacional de la Ciudad de México", - "Compañía Mexicana de Aviación, S.A. de C.V.", - ), - ("98 ", "Veracruz", "Corporación Portuaria de Veracruz, S.A. de C.V."), -] diff --git a/backend/api/v1/modules/public/reference_data/customs_warehouses/test_customs_warehouses.py b/backend/api/v1/modules/public/reference_data/customs_warehouses/test_customs_warehouses.py deleted file mode 100644 index 9d23e837..00000000 --- a/backend/api/v1/modules/public/reference_data/customs_warehouses/test_customs_warehouses.py +++ /dev/null @@ -1,48 +0,0 @@ -import pytest -from api.v1.modules.public.reference_data.customs_warehouses.routes import router -from fastapi import FastAPI -from fastapi.testclient import TestClient - -app = FastAPI() -app.include_router(router) -client = TestClient(app) - - -@pytest.mark.usefixtures("client", "access_token") -def test_list_customs_warehouses(client, access_token): - headers = {"Authorization": f"Bearer {access_token}"} - response = client.get("/customs-warehouses/", headers=headers) - assert response.status_code == 200 - assert "items" in response.json() - assert "page" in response.json() - assert "page_size" in response.json() - - -@pytest.mark.usefixtures("client", "access_token") -def test_get_customs_warehouse_not_found(client, access_token): - headers = {"Authorization": f"Bearer {access_token}"} - response = client.get( - "/customs-warehouses/invalid_key/invalid_customs", headers=headers - ) - assert response.status_code == 404 - - -def test_create_customs_warehouse_forbidden(): - response = client.post( - "/customs-warehouses/", - json={"key": "TST", "customs": "TST", "description": "Test"}, - ) - assert response.status_code in (403, 405, 404) - - -def test_update_customs_warehouse_forbidden(): - response = client.put( - "/customs-warehouses/TST/TST", - json={"key": "TST", "customs": "TST", "description": "Test"}, - ) - assert response.status_code in (403, 405, 404) - - -def test_delete_customs_warehouse_forbidden(): - response = client.delete("/customs-warehouses/TST/TST") - assert response.status_code in (403, 405, 404) diff --git a/backend/api/v1/modules/public/reference_data/identifiers/dto.py b/backend/api/v1/modules/public/reference_data/identifiers/dto.py deleted file mode 100644 index 87220269..00000000 --- a/backend/api/v1/modules/public/reference_data/identifiers/dto.py +++ /dev/null @@ -1,10 +0,0 @@ -from pydantic import BaseModel, ConfigDict, Field - - -class IdentifierDTO(BaseModel): - key: str = Field(..., max_length=10) - description: str = Field(..., max_length=2000) - level: str = Field(..., max_length=1) - complement: str = Field(..., max_length=5000) - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/public/reference_data/identifiers/models.py b/backend/api/v1/modules/public/reference_data/identifiers/models.py deleted file mode 100644 index 8f6f48ab..00000000 --- a/backend/api/v1/modules/public/reference_data/identifiers/models.py +++ /dev/null @@ -1,26 +0,0 @@ -from core.database import Base -from sqlalchemy import PrimaryKeyConstraint, String -from sqlalchemy.orm import Mapped, mapped_column - - -class IdentifierCatalog(Base): - __tablename__ = "identifiers" - __table_args__ = ( - PrimaryKeyConstraint("key", name="identifiers_pkey"), - {"schema": "public", "extend_existing": True}, - ) - - key: Mapped[str] = mapped_column( - String(10), primary_key=True, nullable=False) # clave del identificador - description: Mapped[str] = mapped_column( - String(2000), nullable=False - ) # descripción - level: Mapped[str] = mapped_column( - String(1), nullable=False - ) # nivel (G, P, etc) - complement: Mapped[str] = mapped_column( - String(5000), nullable=False - ) # complemento / instrucciones - - def __repr__(self): - return f"" diff --git a/backend/api/v1/modules/public/reference_data/identifiers/routes.py b/backend/api/v1/modules/public/reference_data/identifiers/routes.py deleted file mode 100644 index 1b6594b7..00000000 --- a/backend/api/v1/modules/public/reference_data/identifiers/routes.py +++ /dev/null @@ -1,129 +0,0 @@ -from typing import Any, Dict - -from core.database import get_core_db -from core.security import get_current_user, has_role -from fastapi import APIRouter, Depends, HTTPException, Query -from sqlalchemy import or_ -from sqlalchemy.orm import Session - -from .dto import IdentifierDTO -from .models import IdentifierCatalog -from ..rbac import require_ref_view - -router = APIRouter(prefix="/identifiers") - - -@router.get( - "/", - response_model=Dict[str, Any], - dependencies=[Depends(require_ref_view("cat_identifiers.view"))], -) -async def list_identifiers( - page: int = Query(1, ge=1, description="Número de página"), - page_size: int = Query(50, ge=1, le=1000, description="Tamaño de página"), - level: str = Query(None, description="Filtrar por nivel (G o P)"), - q: str = Query(None, description="Búsqueda general"), - search: str = Query(None, description="Término de búsqueda"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - skip = (page - 1) * page_size - query = db.query(IdentifierCatalog) - - if search: - search_filter = f"%{search}%" - query = query.filter( - or_( - IdentifierCatalog.key.ilike(search_filter), - IdentifierCatalog.description.ilike(search_filter), - IdentifierCatalog.level.ilike(search_filter), - IdentifierCatalog.complement.ilike(search_filter) - ) - ) - - if level: - query = query.filter(IdentifierCatalog.nivel == level) - - if q: - search_term = f"%{q}%" - query = query.filter( - or_( - IdentifierCatalog.key.ilike(search_term), - IdentifierCatalog.description.ilike(search_term), - IdentifierCatalog.complement.ilike(search_term) - ) - ) - - items = query.offset(skip).limit(page_size).all() - total = query.count() - return { - "items": [IdentifierDTO.model_validate(obj) for obj in items], - "total": total, - "page": page, - "page_size": page_size, - } - - -@router.get( - "/{key}", - response_model=IdentifierDTO, - dependencies=[Depends(require_ref_view("cat_identifiers.view"))], -) -async def get_identifier( - key: str, - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - obj = db.query(IdentifierCatalog).filter(IdentifierCatalog.key == key).first() - if not obj: - raise HTTPException(status_code=404, detail="Not found") - return obj - - -@router.post("/", response_model=IdentifierDTO, status_code=201) -async def create_identifier( - data: IdentifierDTO, - db: Session = Depends(get_core_db), - current_user: dict = Depends(has_role("admin")), -): - # Check if already exists - existing = db.query(IdentifierCatalog).filter(IdentifierCatalog.key == data.key).first() - if existing: - raise HTTPException(status_code=400, detail="Identifier with this key already exists") - - obj = IdentifierCatalog(**data.dict()) - db.add(obj) - db.commit() - db.refresh(obj) - return obj - - -@router.put("/{key}", response_model=IdentifierDTO) -async def update_identifier( - key: str, - data: IdentifierDTO, - db: Session = Depends(get_core_db), - current_user: dict = Depends(has_role("admin")), -): - obj = db.query(IdentifierCatalog).filter(IdentifierCatalog.key == key).first() - if not obj: - raise HTTPException(status_code=404, detail="Not found") - for field, value in data.dict().items(): - setattr(obj, field, value) - db.commit() - db.refresh(obj) - return obj - - -@router.delete("/{key}", status_code=204) -async def delete_identifier( - key: str, - db: Session = Depends(get_core_db), - current_user: dict = Depends(has_role("admin")), -): - obj = db.query(IdentifierCatalog).filter(IdentifierCatalog.key == key).first() - if not obj: - raise HTTPException(status_code=404, detail="Not found") - db.delete(obj) - db.commit() - return None diff --git a/backend/api/v1/modules/public/reference_data/identifiers/seed.py b/backend/api/v1/modules/public/reference_data/identifiers/seed.py deleted file mode 100644 index 53283cfc..00000000 --- a/backend/api/v1/modules/public/reference_data/identifiers/seed.py +++ /dev/null @@ -1,186 +0,0 @@ -import logging -from sqlalchemy.orm import Session -from .models import IdentifierCatalog - -logger = logging.getLogger(__name__) - -IDENTIFIERS_DATA = [ - {"key": "A3", "description": "REGULARIZACION DE MERCANCIAS (IMPORTACION DEFINITIVA).", "level": "G", "complement": "Identificar conforme a los supuestos de la clave de documento A3 del apendice 2."}, - {"key": "AC", "description": "ALMACEN GENERAL DE DEPOSITO CERTIFICADO.", "level": "G", "complement": "Identificar a un almacen general de deposito certificado."}, - {"key": "AE", "description": "EMPRESA DE COMERCIO EXTERIOR.", "level": "G", "complement": "Declarar la autorizacion de empresa de comercio exterior."}, - {"key": "AF", "description": "ACTIVO FIJO.", "level": "G", "complement": "Identificar el activo fijo, Unicamente cuando la clave de documento no sea exclusiva para dicha mercancia."}, - {"key": "AG", "description": "ALMACEN GENERAL DE DEPOSITO FISCAL.", "level": "G", "complement": "Identificar a un almacen general de deposito."}, - {"key": "AI", "description": "OPERACIONES DE COMERCIO EXTERIOR CON AMPARO.", "level": "G", "complement": "Declarar operaciones de comercio exterior que se realizan con amparo."}, - {"key": "AL", "description": "MERCANCIA ORIGINARIA IMPORTADA AL AMPARO DE ALADI.", "level": "P", "complement": "Declarar preferencia arancelaria de la ALADI."}, - {"key": "AP", "description": "APLICA PAGO VIRTUAL", "level": "G", "complement": "Declarar cuando en el pedimento se señalen exclusivamente las siguientes formas de pago: 5, 6, 8, 9, 13, 14, 16, 18, 21 y 22 de conformidad con el Apéndice 13."}, - {"key": "AR", "description": "CONSULTA ARANCELARIA.", "level": "P", "complement": "Declarar consulta sobre clasificacion arancelaria a la autoridad competente."}, - {"key": "AT", "description": "AVISO DE TRANSITO.", "level": "G", "complement": "Avisar sobre el transito interno a la exportacion."}, - {"key": "AV", "description": "AVISO ELECTRONICO DE IMPORTACION Y EXPORTACION.", "level": "G", "complement": "Indicar en los previos de consolidado el uso del aviso electronico de importacion y exportacion por cada remesa presentada ante el modulo de seleccion automatizado."}, - {"key": "B2", "description": "BIENES DEL ARTÍCULO 2 DE LA LEY DEL IEPS.", "level": "P", "complement": "Identificar las mercancías conforme al artículo 2, fracción I, incisos D) y H), de la Ley del IEPS."}, - {"key": "BB", "description": "EXPORTACION DEFINITIVA Y RETORNO VIRTUAL.", "level": "G", "complement": "Identificar la exportacion definitiva virtual de mercancia (productos terminados) que enajenen residentes en el pais a recinto fiscalizado para la elaboracion, transformacion o reparacion."}, - {"key": "BR", "description": "EXPORTACION TEMPORAL DE MERCANCIAS FUNGIBLES Y SU RETORNO.", "level": "G", "complement": "Identificar mercancias listadas en el Anexo 12."}, - {"key": "C5", "description": "DEPOSITO FISCAL PARA LA INDUSTRIA AUTOMOTRIZ.", "level": "G", "complement": "Identificar a la industria automotriz terminal autorizada."}, - {"key": "C9", "description": "CERTIFICADO DE USO FINAL.", "level": "P", "complement": "Indicar que la mercancia sujeta a cuota compensatoria cumple con la excepcion que establece el decreto publicado en el DOF el dia 2 de agosto de 1994, relativo a aceros planos recubiertos y placas en hoja."}, - {"key": "CC", "description": "CARTA DE CUPO.", "level": "G", "complement": "Identificar las mercancias que se almacenaran en deposito fiscal."}, - {"key": "CD", "description": "CERTIFICADO CON DISPENSA TEMPORAL.", "level": "P", "complement": "Declarar la informacion relativa a los certificados con dispensa de acuerdo al Tratado de Libre Comercio correspondiente."}, - {"key": "CE", "description": "CERTIFICADO DE ELEGIBILIDAD.", "level": "P", "complement": "Declarar el certificado de elegibilidad de mercancias no originarias importadas bajo TLC."}, - {"key": "CF", "description": "REGISTRO ANTE LA SECRETARIA DE ECONOMIA DE EMPRESAS UBICADAS EN LA FRANJA O REGION FRONTERIZA./PREFERENCIA ARANCELARIA PARA EMPRESAS UBICADAS EN LA FRANJA O REGION FRONTERIZA.", "level": "G", "complement": "Identificar a la empresa que cuente con registro ante la SE de conformidad con el Decreto por el que se establece el impuesto general de importacion para la region fronteriza y la franja fronteriza norte./Declarar tasas preferenciales conforme al Decreto por el que se establece el impuesto general de importacion para la region fronteriza y la franja fronteriza norte."}, - {"key": "CI", "description": "CERTIFICACION EN MATERIA DE IVA E IEPS.", "level": "G", "complement": "Identificar las operaciones de las empresas que hayan obtenido la certificacionn en materia de IVA e IEPS."}, - {"key": "CO", "description": "CONDONACION DE CREDITOS FISCALES.", "level": "G", "complement": "Condonacion emitida de conformidad con el Transitorio Tercero de la Ley de Ingresos de la Federacion para el ejercicio fiscal de 2013, publicada en el DOF el 17 de diciembre de 2012."}, - {"key": "CR", "description": "RECINTO FISCALIZADO.", "level": "G", "complement": "Identificar el recinto en el que se encuentre la mercancia en deposito ante la aduana o para su introduccion al mismo."}, - {"key": "CS", "description": "COPIA SIMPLE.", "level": "G", "complement": "Declarar uso copia simple en el despacho de las mercancias al amparo de la R.G. 3.1.18., segundo parrafo, fracc. II."}, - {"key": "DA", "description": "DESPACHO ANTICIPADO", "level": "G", "complement": "Indicar que se trata de una operación de comercio exterior que se sujeta a despacho anticipado."}, - {"key": "DC", "description": "CLASIFICACION DEL CUPO.", "level": "P", "complement": "Identificar el tipo de cupo utilizado."}, - {"key": "DD", "description": "DESPACHO A DOMICILIO A LA EXPORTACION.", "level": "G", "complement": "Declarar que se cuenta con autorizacion para el despacho de las mercancias por lugar distinto o en dia u hora inhabil."}, - {"key": "DE", "description": "DESPERDICIOS.", "level": "G", "complement": "Indicar que se trata de desperdicios derivados de los procesos productivos de mercancias que se hubieran importado temporalmente por empresas con Programa IMMEX."}, - {"key": "DH", "description": "DATOS DE IMPORTACIÓN DE\u00a0HIDROCARBUROS", "level": "P", "complement": "Identificar el medio de transporte y, en su caso, el medidor con el que cuenta. Tratándose de importación por medio de ductos deberán declararse los complementos 1 y 2. Tratándose de importación por medios distintos de ductos deberá declararse el complemento 2. No obstante lo anterior, podrán declararse ambos complementos cuando se requieran."}, - {"key": "DI", "description": "DOCUMENTO DE INCREMENTABLE (CFDI)", "level": "G", "complement": "Declarar el folio del CFDI correspondiente al incrementable de la contratación del servicio de la importación de un vehículo usado, conforme a la Regla 3. 5.10."}, - {"key": "DN", "description": "DONACION POR PARTE DE LAS EMPRESAS CON PROGRAMA IMMEX.", "level": "G", "complement": "Indicar que se trata de la donacion de desperdicios, maquinaria y/o equipos obsoletos."}, - {"key": "DP", "description": "INTRODUCCION Y EXTRACCION DE DEPOSITO FISCAL PARA EXPOSICION Y VENTA DE ARTICULOS PROMOCIONALES.", "level": "P", "complement": "Identificar a los articulos promocionales de conformidad con la R.G. 4.5.27."}, - {"key": "DR", "description": "RECTIFICACION POR DISCREPANCIA DOCUMENTAL.", "level": "P", "complement": "Rectificar los datos asentados en el pedimento, de conformidad con la R.G. 4.5.7."}, - {"key": "DS", "description": "DESTRUCCION DE MERCANCIAS EN DEPOSITO FISCAL PARA LA EXPOSICION Y VENTA.", "level": "P", "complement": "Indicar la destrucciOn de mercancias extranjeras y nacionales conforme a la R.G. 4.5.22."}, - {"key": "DT", "description": "OPERACIONES SUJETAS AL ART. 303 DEL TLCAN.", "level": "P", "complement": "Senalar supuesto de aplicacion para la determinacion y pago del IGI de los insumos no originarios de la region del TLCAN."}, - {"key": "DU", "description": "OPERACIONES SUJETAS A LOS ARTS. 14DE LA DECISION O 15DE EL TLCAELC.", "level": "P", "complement": "Senalar el supuesto de aplicacion para la determinacion y pago del IGI de los insumos no originarios conforme TLCUE o TLCAELC."}, - {"key": "DV", "description": "VENTA DE MERCANCIAS A MISIONES DIPLOMATICAS Y CONSULARES CUANDO CUENTE CON FRANQUICIA DIPLOMATICA.", "level": "P", "complement": "Declarar autorizacion para la venta de mercancias a misiones diplomaticas y consulares o a los organismos internacionales, regla 4.5.25."}, - {"key": "EA", "description": "EXCEPCION DE AVISO AUTOMATICO DE IMPORTACION / EXPORTACION.", "level": "P", "complement": "Exceptuar la presentacion del aviso automatico a que se refiere Acuerdo que establece la clasificacion y codificacion de mercancias cuya importacion esta sujeta al requisito de permiso previo por parte de la Secretaria de Economia, publicado en el DOF el 6 de junio de 2007."}, - {"key": "EB", "description": "ENVASES Y EMPAQUES.", "level": "P", "complement": "Identificar los envases y empaques reutilizables de empresas con Programa IMMEX y a los que se refiere la R.G. 4.3.2."}, - {"key": "EC", "description": "EXCEPCION DE PAGO DE CUOTA COMPENSATORIA.", "level": "P", "complement": "Indicar que la mercancia no se encuentra sujeta al pago de cuota compensatoria."}, - {"key": "ED", "description": "DOCUMENTO DIGITALIZADO.", "level": "G", "complement": "Identificar a un documento digitalizado anexo al pedimento."}, - {"key": "EF", "description": "ESTIMULO FISCAL.", "level": "P", "complement": "Senalar cuando apliquen el Decreto por el que se establece un estImulo fiscal a la importacion o enajenacion de los productos que se indican."}, - {"key": "EI", "description": "AUTORIZACION DE DEPOSITO FISCAL TEMPORAL PARA EXPOSICIONES INTERNACIONALES DE MERCANCIAS.", "level": "G", "complement": "Identificar el local autorizado de deposito fiscal de conformidad con la R.G. 4.5.29."}, - {"key": "EM", "description": "EMPRESA DE MENSAJERIA Y PAQUETERIA.", "level": "G", "complement": "Identificar a las empresas de mensajeria y paqueterIa."}, - {"key": "EN", "description": "NO APLICACION DE LA NORMA OFICIAL MEXICANA.", "level": "P", "complement": "Identificar que la mercancía no está sujeta al cumplimiento de la NOM de conformidad con: El Anexo 2.4.1 del Acuerdo por el que la Secretaría de Economía emite reglas y criterios de carácter general en materia de comercio exterior. Políticas y procedimientos para la evaluación de la conformidad. Procedimientos de certificación y verificación de productos sujetos al cumplimiento de NOM's, competencia de la SE."}, - {"key": "EP", "description": "DECLARACION DE CURP./EXCEPCION DE INSCRIPCION AL PADRON DE IMPORTADORES.", "level": "G", "complement": "Identificar el tipo de excepcion para no declarar el RFC./Identificar el tipo de excepcion de conformidad con lo establecido en la R.G. 1.3.1."}, - {"key": "ES", "description": "ESTADO DE LA MERCANCIA.", "level": "P", "complement": "\u00danicamente para determinar la aplicación de regulaciones o restricciones no arancelarias, de conformidad con el estado de la mercancía., \u00danicamente para determinar la aplicación de regulaciones o restricciones no arancelarias, de conformidad con el estado de la mercancía; o para el caso de mercancías remanufacturadas importadas con tratamiento arancelario preferencial del TIPAT."}, - {"key": "EX", "description": "EXENCION DE CUENTA ADUANERA DE GARANTIA.", "level": "P", "complement": "Indicar excepcion de la presentacion de la cuenta aduanera de garantia de mercancias sujetas a precio estimado."}, - {"key": "F8", "description": "DEPOSITO FISCAL PARA EXPOSICION Y VENTA (MERCANCIAS NACIONALES O NACIONALIZADAS).", "level": "G", "complement": "1) Introduccion de mercancia nacional o nacionalizada a deposito fiscal para exposiciOn y venta, conforme a la R.G. 4.5.19., fracc. II. 2) Exportacion definitiva virtual de mercancia nacional o nacionalizada conforme a la R.G. 4.5.19., fracc. II.3) ExtracciOn de deposito fiscal de mercancias por devoluciOn para reincorporarse al mercado nacional, conforme a la R.G. 4.5.23., fracc. II. 4) Desistimiento del rEgimen de exportacion definitiva de las mercancias a deposito fiscal por devoluciOn, conforme a la R.G. 4.5.23., fracc. II."}, - {"key": "FC", "description": "FRACCI\u00d3N CORRELACIONADA", "level": "P", "complement": "Se deberán declarar las claves de acuerdo con el supuesto que corresponda: 1. Cuando la fracción arancelaria declarada en el complemento 2 pertenezca a la tarifa anterior.; 2. Cuando la fracción arancelaria declarada en el complemento 2 corresponda a la nueva tarifa."}, - {"key": "FI", "description": "FACTOR DE ACTUALIZACION CON INDICE NACIONAL DE PRECIOS AL CONSUMIDOR.", "level": "G", "complement": "Actualizar las contribuciones aplicando factor de actualizaciOn con base en el INPC."}, - {"key": "FR", "description": "FECHA QUE RIGE.", "level": "G", "complement": "Declarar cuando la fecha de entrada es igual a la fecha de pago, en aduanas con deposito ante la aduana."}, - {"key": "FT", "description": "FOLIO DE TRAMITE GENERADO POR LA VENTANILLA DIGITAL", "level": "G", "complement": "Operaciones en las que se requiera presentar anexo al pedimento una constancia, aviso o solicitud de autorización a que se refiere el segundo párrafo de la regla 2.4.11."}, - {"key": "FV", "description": "FACTOR DE ACTUALIZACION CON VARIACION CAMBIARIA.", "level": "G", "complement": "Actualizar las contribuciones aplicando factor de actualizaciOn con base en el tipo de cambio."}, - {"key": "G9", "description": "IMPORTACI\u00d3N DEFINITIVA DE RESIDENTES EN TERRITORIO NACIONAL DE MERCANC\u00cdAS QUE SE RETIRAN DE UN RECINTO FISCALIZADO ESTRAT\u00c9GICO.", "level": "G", "complement": "Indicar que la operación se realiza conforme a la regla 4.8.6., fracción III, segundo párrafo."}, - {"key": "GA", "description": "CUENTA ADUANERA DE GARANTIA.", "level": "P", "complement": "Indicar la presentacion de una cuenta aduanera de garantia."}, - {"key": "GS", "description": "EXPORTACION TEMPORAL Y RETORNO DE DISPOSITIVOS ELECTRONICOS QUE ESTABLECE LA REGLA 3.7.34.", "level": "P", "complement": "Identificar mercancias acompanadas por un dispositivo electronico, o de radiofrecuencia de localizacion, distinto de los integrados en los medios de transporte, de conformidad con la regla 3.7.34."}, - {"key": "HC", "description": "OPERACIONES SECTOR HIDROCARBUROS.", "level": "G", "complement": "Indicar que se tratan de operaciones conforme a lo establecido en la regla 3.7.33."}, - {"key": "Hl", "description": "TIPO DE GASOLINA.", "level": "P", "complement": "Declarar dependiendo el índice de octanaje."}, - {"key": "IA", "description": "CERTIFICADO DE APROBACION PARA PRODUCCION DE PARTES AERONAUTICAS.", "level": "P", "complement": "Identificar a las empresas inscritas en el Padron de Produccion Aeroespacial que cuentan con el Certificado de Aprobacion para Produccion emitido por la SCT."}, - {"key": "IC", "description": "EMPRESA CERTIFICADA.", "level": "G", "complement": "Senalar que se trata de una empresa certificada."}, - {"key": "ID", "description": "IMPORTACION DEFINITIVA DE VEHICULOS O EN FRANQUICIA DIPLOMATICA CON AUTORIZACION DE LA ADMINISTRACION GENERAL JURIDICA.", "level": "G", "complement": "Importacion definitiva de vehiculos con autorizacion \u00a0Importacion definitiva de vehiculos en franquicia diplomatica con autorizacion."}, - {"key": "II", "description": "INVENTARIO INICIAL DE EMPRESAS DENOMINADAS DUTY FREE.", "level": "P", "complement": "Declarar descargo del inventario inicial."}, - {"key": "IM", "description": "EMPRESAS CON PROGRAMA IMMEX.", "level": "G", "complement": "Indicar el numero de autorizacion de empresa IMMEX proporcionado por la Secretaria de Economia, incluso RFE que cuenten con dicho programa."}, - {"key": "IN", "description": "INCIDENCIA.", "level": "P", "complement": "Indicar el supuesto en que se realiza la rectificacion."}, - {"key": "IR", "description": "RECINTO FISCALIZADO ESTRATEGICO.", "level": "G", "complement": "Declarar la clave del RFE del inmueble habilitado."}, - {"key": "IS", "description": "MERCANCIAS EXENTAS DE IMPUESTOS AL COMERCIO EXTERIOR.", "level": "P", "complement": "Identificar mercancias por las que no se pagan los impuestos al comercio exterior al amparo del articulo 61 de la Ley."}, - {"key": "J4", "description": "RETORNO DE MERCANCIA DE PROCEDENCIA EXTRANJERA.", "level": "G", "complement": "Retorno de mercancia extranjera de recinto fiscalizado estrategico."}, - {"key": "LD", "description": "DESPACHO POR LUGAR DISTINTO.", "level": "G", "complement": "Declarar autorizacion para the despacho aduanero por lugar distinto, conforme a lo establecido en el articulo 10 de la Ley."}, - {"key": "LP", "description": "LISTA DE ESCASO ABASTO", "level": "P", "complement": "Identificar cuando la mercancía haya sido producida con materiales de escaso abasto listados en el Apéndice 1 del Anexo 4-A del TIPAT. Declarar el número de producto que le corresponda al material de escaso abasto listado en la primera columna del Apéndice 1 del Anexo 4-A del TIPAT. Declarar el número de producto que le corresponda al segundo material de escaso abasto, listado en la primera columna del Apéndice 1 del Anexo 4-A del TIPAT."}, - {"key": "LR", "description": "IMPORTACION POR PEQUENOS CONTRIBUYENTES.", "level": "G", "complement": "Senalar que se trata de importacion de mercancias mediante pedimento simplificado."}, - {"key": "M7", "description": "OPINION FAVORABLE DE LA SE.", "level": "G", "complement": "Declarar opinion favorable de la SE, para las mercancias del Anexo 12, conforme al ArtIculo 116 de la Ley."}, - {"key": "MA", "description": "EMBALAJES DE MADERA.", "level": "P", "complement": "Señalar que se trata de embalajes de madera que cumplen con la Norma Oficial Mexicana NOM-144-SEMARNAT-2017."}, - {"key": "MB", "description": "MARBETES /O PRECINTOS.", "level": "P", "complement": "Declarar marbetes y/o precintos que se coloquen en envases que contengan bebidas alcoholicas, de conformidad con la R.G. 5.1.8. de la RMF."}, - {"key": "MC", "description": "MARCA.", "level": "P", "complement": "MARCA NOMINATIVA, INNOMINADA, TRIDIMENCIONAL O MIXTA, QUE IDENTIFICA EL PRODUCTO."}, - {"key": "MD", "description": "MENAJE DE DIPLOMATICOS.", "level": "G", "complement": "Senalar para diplomaticos acreditados conforme al articulo 61 fracc. I de la Ley, articulo 90 y 91 del Reglamento y la R.G. 3.2.6."}, - {"key": "ME", "description": "MATERIAL DE ENSAMBLE.", "level": "P", "complement": "Indicar que la mercancia es material de ensamble."}, - {"key": "MI", "description": "IMPORTACION DEFINITIVA DE MUESTRAS AMPARADAS BAJO UN PROTOCOLO DE INVESTIGACION.", "level": "G", "complement": "Indicar para muestras amparadas bajo un protocolo de investigacion en humanos, conforme a la R.G. 3.1.4."}, - {"key": "MJ", "description": "OPERACIONES DE EMPRESAS DE MENSAJERIA Y PAQUETERIA DE MERCANCIAS NO SUJETAS AL PAGO DEL IGIE E IVA.", "level": "G", "complement": "Indicar para operaciones realizadas conforme a la regla 3.7.5., en relación con la regla 3.7.36., fracción I, cuando el valor en aduana de las mercancías no es mayor a 50 dólares."}, - {"key": "MM", "description": "IMPORTACION DEFINITIVA DE MUESTRAS Y MUESTRARIOS.", "level": "P", "complement": "Indicar para mercancias destinadas a demostracion o levantamiento de pedidos de conformidad con la R.G. 3.1.2."}, - {"key": "MP", "description": "PEDIMENTO SIMPLIFICADO.", "level": "G", "complement": "Indicar para operaciones por empresas de mensajeria de mexicanos residentes en el extranjero de conformidad con la R.G. 3.7.3., fracc. VI."}, - {"key": "MR", "description": "REGISTRO PARA LA TOMA DE MUESTRAS, PELIGROSAS O PARA QUE LAS QUE SE REQUIERA DE INSTALACIONES O EQUIPOS ESPECIALES PARA LA TOMA DE LAS MISMAS.", "level": "P", "complement": "Indicar que se trata de mercancias esteriles, radiactivas, peligrosas o para las que se requiera de instalaciones o equipos especiales para la toma de muestras, conforme a la R.G. 3.1.3. y el Anexo 23."}, - {"key": "MS", "description": "MODALIDAD DE SERVICIOS DE EMPRESAS CON PROGRAMA IMMEX.", "level": "G", "complement": "Indicar la actividad de servicios que corresponda a la empresa con Programa IMMEX."}, - {"key": "MT", "description": "MONTO TOTAL DEL VALOR EN DOLARES A EJERCER POR MERCANCIA TEXTIL.", "level": "G", "complement": "Declarar el importe estimado en dolares por mercancia textil (Anexo III, Decreto para el fomento de la industria manufacturera, maquiladora y de servicios de exportacion) de empresas IMMEX del Sector Textil y Confeccion 8\u00e2\u20ac\u009d."}, - {"key": "MV", "description": "ANO MODELO DEL VEHICULO.", "level": "P", "complement": "Indicar el ano y modelo del vehIculo a importar y, en su caso, el precio estimado que corresponda."}, - {"key": "NA", "description": "MERCANCIAS CON PREFERENCIA ARANCELARIA ALADI SENALADAS EN EL ACUERDO.", "level": "P", "complement": "Indicar las mercancias senaladas en el acuerdo ALADI correspondiente."}, - {"key": "NE", "description": "EXCEPCION DE CUMPLIR CON EL ANEXO 21.", "level": "P", "complement": "Identificar que la mercancia no corresponde a las listadas en el Anexo 21 (aduanas autorizadas para tramitar el despacho aduanero)."}, - {"key": "NR", "description": "OPERACION EN LA QUE LAS MERCANCIAS NO INGRESAN A RECINTO FISCALIZADO.", "level": "G", "complement": "Identificar las operaciones realizadas por empresas certificadas, de mercancías que no ingresaron a Recinto Fiscalizado, de conformidad con los lineamientos que para tal efecto emita la AGA, mismos que se darán a conocer en el Portal del SAT."}, - {"key": "NS", "description": "EXCEPCION DE INSCRIPCION EN LOS PADRONES DE IMPORTADORES EXPORTADORES SECTORIALES.", "level": "P", "complement": "Identificar las mercancias exceptuadas del Anexo 10, apartado A, conforme al: Acuerdo que establece la clasificacion y codificacion de los productos quimicos esenciales cuya importacion o exportacion esta sujeta a la presentacion de un aviso previo ante la Secretaria de Salud, publicado en el DOF el 30 de junio de 2007, modificado el 1 de junio de 2010. Acuerdo que establece la clasificacion y codificacion de mercancias y productos cuya importacion, exportacion, internacion o salida esta sujeta a regulacion sanitaria por parte de la Secretaria de Salud, publicado en el DOF el 27 de septiembre de 2007 modificado mediante acuerdos publicados en el mismo Organo informativo el 23 de enero, 30 de julio de 2009, 1 de junio y 09 de diciembre de 2010. Acuerdo que establece la clasificacion y codificacion de mercancias cuya importacion y exportacion esta sujeta a autorizacion por parte de la Secretaria de EnergIa, publicado en el DOF el 2 de marzo de 2012 y modificado mediante acuerdo publicado en el mismo Organo informativo el 18 de junio de 2012. Lo establecido en la Regla 3.1.2. de las RGCE. Las claves 401, 501, 601, 701 y 801 serAn aplicables cuando no se trate de mercancia contemplada en el Acuerdo que establece la clasificacion y codificacion de las mercancias cuya importacion o exportacion estan sujetas a regulacion por parte de la Secretaria de la Defensa Nacional\u00e2\u20ac\u009d, publicado en el DOF el 30 de junio de 2007. Identificar las mercancias exceptuadas del Anexo 10 apartado B, conforme al: Acuerdo por el que la Secretaria de Economia emite reglas y criterios de caracter general en materia de Comercio Exterior\u00e2\u20ac\u009d, publicado en el DOF el 6 de Julio de 2007 y su posterior modificacion, publicada en el mismo Organo informativo el 18 de marzo de 2011."}, - {"key": "NT", "description": "NOTA DE TRATADO.", "level": "P", "complement": "Identificar la mercancia con preferencia arancelaria prevista en el Decreto por el que se establezca la tasa aplicable del impuesto general de importacion para las mercancias originarias de conformidad con los tratados de libre comercio que mexico tenga suscritos."}, - {"key": "NZ", "description": "MERCANCIA QUE NO SE HA BENEFICIADO DEL SUGAR REEXPORT PROGRAM\u00e2\u20ac\u009d DE LOS ESTADOS UNIDOS DE AMERICA.", "level": "P", "complement": "Senalar que se presenta declaracion escrita del exportador en la que manifieste que la mercancia no se ha beneficiado del programa."}, - {"key": "OC", "description": "OPERACI\u00d3N TRAMITADA EN FASE DE CONTINGENCIA.", "level": "G", "complement": "Identificar los pedimentos tramitados durante la fase de contingencia de la Ventanilla Digital o del SAAI para la validación del pedimento."}, - {"key": "OE", "description": "OPERADOR ECONOMICO AUTORIZADO.", "level": "G", "complement": "Identificar a los Proveedores internacionales, que cuenten con una certificacion vigente del Operador EconOmico Autorizado en su pais, y se haya firmado un Acuerdo de Reconocimiento Mutuo con mexico."}, - {"key": "OM", "description": "MERCANCIA ORIGINARIA DE MEXICO.", "level": "P", "complement": "Declarar que la mercancia es originaria de mexico, de conformidad con las reglas de la Secretaria de Economia 3.2.7. y 3.4.14."}, - {"key": "OV", "description": "OPERACION VULNERABLE.", "level": "P", "complement": "Unicamente para las mercancias cuya clasificacion arancelaria se encuentre listada en el Anexo A de la Resolucion por la que se Expiden los Formatos Oficiales de los Avisos e Informes que deben presentar Quienes Realicen Actividades Vulnerables, publicada en el DOF el 30 de agosto de 2013."}, - {"key": "PA", "description": "CUMPLIMIENTO DE LA NORMA OFICIAL MEXICANA, PARA VERIFICARSE EN UN ALMACEN GENERAL DE DEPOSITO AUTORIZADO.", "level": "P", "complement": "Indicar que la NOM se cumplirá conforme al Artículo 6, fracción II del Acuerdo de NOM's vigente."}, - {"key": "PB", "description": "CUMPLIMIENTO DE NORMA OFICIAL MEXICANA PARA SU VERIFICACION DENTRO DEL TERRITORIO NACIONAL, EN UN DOMICILIO PARTICULAR.", "level": "P", "complement": "Indicar que la NOM se cumplirá conforme al Artículo 6, fracción III del Acuerdo de NOM's vigente."}, - {"key": "PC", "description": "PEDIMENTO CONSOLIDADO.", "level": "G", "complement": "Indicar para el cierre de un pedimento consolidado."}, - {"key": "PD", "description": "PARTE II.", "level": "G", "complement": "Senalar el despacho de mercancias con pedimentos Parte II, conforme a la R.G. 3.1.18., segundo parrafo, fracc. I."}, - {"key": "PG", "description": "MERCANCIA PELIGROSA.", "level": "P", "complement": "Indicar que se trata de mercancia peligrosa conforme a la R.G. 3.1.5. y al Apendice 19 del Anexo 22."}, - {"key": "PH", "description": "PEDIMENTO ELECTRONICO SIMPLIFICADO.", "level": "G", "complement": "Identificar a un pedimento electronico simplificado conforme a la R.G. 7.3.6., fracciones I y II."}, - {"key": "PI", "description": "INSPECCION PREVIA.", "level": "G", "complement": "Indicar que se trata de operaciones de conformidad con las R.G. 3.7.28. y 7.3.1., fraccion IX"}, - {"key": "PL", "description": "PRELIBERACION DE MERCANCIAS.", "level": "G", "complement": "Indicar que se trata de una operacion de comercio exterior que se sujeta a preliberacion."}, - {"key": "PM", "description": "PRESENTACION DE LA MERCANCIA.", "level": "P", "complement": "Indicar que se trata de las mercancias mencionadas en la R.G. 3.1.18.."}, - {"key": "PO", "description": "PROVEEDOR DE OR\u00cdGEN.", "level": "P", "complement": "Declarar los datos de las mercancías a las que se aplique una preferencia arancelaria al amparo de acuerdos y tratados comerciales suscritos por México, en operaciones de importación, por partida."}, - {"key": "PP", "description": "PROGRAMA DE PROMOCION SECTORIAL.", "level": "G", "complement": "Identificar operaciones al amparo del PROSEC."}, - {"key": "PR", "description": "PROPORCION DETERMINADA.", "level": "P", "complement": "Declarar el pago el impuesto general de importacion correspondiente a los bienes no originarios importados temporalmente, conforme a la R.G. 16.4. del TLCAN."}, - {"key": "PS", "description": "SECTOR AUTORIZADO AL AMPARO DE PROSEC.", "level": "P", "complement": "Determinar el arancel correspondiente a las mercancias importadas al amparo del Decreto por el que se establecen diversos Programas de Promocion Sectorial, pulicado en el DOF el 2 de agosto de 2002 y sus reformas."}, - {"key": "PT", "description": "EXPORTACION O RETORNO DE PRODUCTO TERMINADO.", "level": "P", "complement": "Especificar que se trata de producto terminado de mercancias elaboradas, transformadas o reparadas en recinto fiscalizado o por empresas con programa IMMEX."}, - {"key": "PV", "description": "PRUEBA DE VALOR.", "level": "P", "complement": "Indicar que el agente aduanal cuenta con la documentacion y medios de prueba necesarios para comprobar el valor declarado de conformidad con la fraccion III del articulo 59 de la Ley."}, - {"key": "PZ", "description": "AMPLIACION DEL PLAZO PARA EL RETORNO DE MERCANCIA IMPORTADA O EXPORTADA TEMPORALMENTE.", "level": "G", "complement": "Declarar que se cuenta con una prorroga para el retorno de la mercancia."}, - {"key": "RA", "description": "RETORNO DE RACKS.", "level": "P", "complement": "Indicar que se retornan racks que se introdujeron a deposito fiscal con la clave de pedimento F2."}, - {"key": "RC", "description": "CONSECUTIVOS DE FACTURAS O REMESAS.", "level": "G", "complement": "Indicar en el cierre de pedimentos consolidados para senalar el rango de remesas moduladas."}, - {"key": "RD", "description": "RETORNO A DEPOSITO FISCAL DE LA INDUSTRIA AUTOMOTRIZ DE MERCANCIA EXPORTADA EN DEFINITIVA.", "level": "G", "complement": "Retorno de mercancías extraídas para su exportación definitiva conforme a la regla 4.5.26., fracción IV., Retorno de mercancías extraídas para su exportación definitiva conforme a la regla 4.5.31., fracción IV."}, - {"key": "RE", "description": "IMPORTACION DEFINITIVA DE MERCANCIAS (REGULARIZACION).", "level": "G", "complement": "Indicar que se trata de operaciones de la R.G. 2.5.1."}, - {"key": "RF", "description": "CUOTA COMPENSATORIA BASADA EN PRECIO DE REFERENCIA.", "level": "P", "complement": "Identificar cuando se den los supuestos del pago de cuotas compensatorias basadas en precios de referencia."}, - {"key": "RL", "description": "RESPONSABLE SOLIDARIO.", "level": "G", "complement": "Identificar el responsable solidario de las mercancias que ingresan a DepOsito Fiscal las personas fIsicas o morales residentes en el extranjero."}, - {"key": "RO", "description": "REVISION EN ORIGEN POR PARTE DE EMPRESAS CERTIFICADAS.", "level": "G", "complement": "Identificar the despacho de mercancias de empresas certificadas mediante el procedimiento de revision en origen, conforme al articulo 98 de la Ley y la R.G. 7.3.3., fraccion XVIII."}, - {"key": "RP", "description": "RETORNO de residuos peligrosos generados por empresas con Programa IMMEX.", "level": "P", "complement": "Identificar que se trata de mercancía considerada como residuos peligrosos, conforme al \u201cAcuerdo que establece la clasificación y codificación de mercancías cuya importación y exportación está sujeta a regulación por parte de la Secretaría de Medio Ambiente y Recursos Naturales\u201d, publicado en el DOF el 19 de diciembre de 2012 y sus posteriores modificaciones."}, - {"key": "RQ", "description": "IMPORTACION DEFINITIVA DE REMOLQUES, SEMIRREMOLQUES Y PORTACONTENEDORES.", "level": "G", "complement": "Indicar que se trata de una importacion definitiva de remolques, semirremolques y portacontenedores."}, - {"key": "RT", "description": "REEXPEDICION POR TERCEROS.", "level": "G", "complement": "Identificar la reexpedicion de mercancias de la franja o region fronteriza por una persona distinta al importador."}, - {"key": "SB", "description": "IMPORTACION DE ORGANISMOS GENETICAMENTE MODIFICADOS.", "level": "P", "complement": "Identificar mercancias cuya importacion requiere autorizacion por parte de la SE y SAGARPA."}, - {"key": "SC", "description": "EXCEPCION DE PAGO DE MEDIDA DE TRANSICION.", "level": "P", "complement": "Indicar que la mercancia no se encuentra sujeta al pago de la medida de transicion."}, - {"key": "SF", "description": "CLAVE DE UNIDAD AUTORIZADA DEL ALMACEN GENERAL DE DEPOSITO.", "level": "G", "complement": "Identificar la unidad autorizada, conforme al Anexo 13., Identificar la unidad autorizada, conforme la autorización otorgada para prestar el servicio de almacenamiento de mercancías en depósito fiscal y colocar marbetes o precintos."}, - {"key": "SH", "description": "AUTORIZACION DEL SAT./AUTORIZACION DEL SAT.", "level": "G", "complement": "Identificar las operaciones: Autorizaciones otorgadas por el SAT. Franquicia diplomatica./Indicar que la importacion de las mercancias cuenta con una resoluciOn particular otorgada por el SAT."}, - {"key": "SM", "description": "EXCEPCION DE LA DECLARACION DE MARBETES.", "level": "P", "complement": "Indicar que por la naturaleza de las mercancias no se esta obligado a la declaracion de marbetes."}, - {"key": "SO", "description": "SOCIO COMERCIAL CERTIFICADO.", "level": "G", "complement": "Identificar a los contribuyentes que participan en el manejo, almacenaje, custodia y/o traslado de las mercancías de comercio exterior inscritos en el Registro en el Esquema de Certificación de Empresa bajo la modalidad de Socio Comercial Certificado."}, - {"key": "SP", "description": "(Se deroga)", "level": "G", "complement": "- - -"}, - {"key": "ST", "description": "OPERACIONES SUJETAS AL ART. 303 DEL TLCAN.", "level": "G", "complement": "Senalar en el pedimento el supuesto de aplicacion para la determinacion y pago del IGI de los insumos no originarios de la region del TLCAN."}, - {"key": "SU", "description": "OPERACIONES SUJETAS A LOS ARTICULOS 14 DE LA DECISION O 15 DEL TLCAELC.", "level": "G", "complement": "Senalar en el pedimento el supuesto de aplicacion para la determinacion y pago del IGI de los insumos no originarios conforme TLCUE o TLCAELC."}, - {"key": "TB", "description": "TRANSITO INTERNO POR ADUANAS Y MERCANCIAS ESPECIFICAS.", "level": "P", "complement": "Indicar que se trata de un transito conforme a lo establecido en la R.G. 4.6.4."}, - {"key": "TC", "description": "CORRELACION DE LAS FRACCIONES ARANCELARIAS.", "level": "P", "complement": "Declarar la fraccion correlacionada de acuerdo a lo establecido en los Decretos por los que se establece la tasa aplicable en los Tratados de Libre Comercio Suscritos por mexico."}, - {"key": "TD", "description": "TIPO DE DESISTIMIENTO Y RETORNO.", "level": "G", "complement": "Conforme a los supuestos de la clave de documento K1 del apendice 2."}, - {"key": "TF", "description": "TRANSMISION DE FACTURAS.", "level": "G", "complement": "Indicar que las facturas serAn transmitidas antes de presentar las remesas de un pedimento consolidado ante el modulo de seleccion automatizada."}, - {"key": "TI", "description": "TRANSITO INTERFRONTERIZO.", "level": "G", "complement": "Indicar que se trata de un transito conforme a lo establecido en la R.G. 4.6.11."}, - {"key": "TL", "description": "MERCANCIA ORIGINARIA AL AMPARO DE TRATADOS DE LIBRE COMERCIO.", "level": "P", "complement": "Declarar una preferencia arancelaria al amparo de un tratado suscrito por mexico."}, - {"key": "TM", "description": "TRANSITO INTERNACIONAL.", "level": "G", "complement": "Indicar que se trata de un transito conforme a lo establecido en las R.G. 4.6.22. y 4.6.23."}, - {"key": "TR", "description": "TRASPASO DE MERCANCIAS EN DEPOSITO FISCAL.", "level": "G", "complement": "Indicar operaciones de traspaso de mercancias conforme la R.G. 4.6.22., fraccs. III y IV."}, - {"key": "TU", "description": "TRANSFERENCIA DE MERCANCIAS (OPERACIONES VIRTUALES), CON PEDIMENTO UNICO.", "level": "G", "complement": "Se\u00f1alar en operaciones de transferencia de mercanc\u00edas que se importen temporalmente a trav\u00e9s del Pedimento \u00danico, de conformidad con las reglas 4.3.20., fracci\u00f3n I, inciso c) vigente hasta el 20 de junio de 2016 o 7.3.1., Apartado A, fracci\u00f3n XIV."}, - {"key": "TV", "description": "TOTAL DE MERCANCIA EXTRAIDA DE DEPOSITO FISCAL.", "level": "P", "complement": "Indicar que se trata de extracciones realizadas conforme a la R.G. 4.5.20."}, - {"key": "UM", "description": "USO DE LA MERCANCIA.", "level": "P", "complement": "Indicar el uso de la mercancia, asI como la exencion de impuestos."}, - {"key": "UP", "description": "UNIDADES PROTOTIPO.", "level": "G", "complement": "Identificar a las operaciones realizadas de conformidad con la R.G. 4.5.31."}, - {"key": "V1", "description": "TRANSFERENCIAS DE MERCANCIAS.", "level": "G", "complement": "Indicar conforme a los supuestos de la clave de documento V1 del apendice 2 del Anexo 22, R.G. 4.3.19. y articulo 86 de la Ley."}, - {"key": "V2", "description": "TRANSFERENCIA DE MERCANCIAS IMPORTADAS CON CUENTA ADUANERA.", "level": "G", "complement": "Senalar en el supuesto de la R.G. 1.6.30."}, - {"key": "V3", "description": "EXTRACCION DE DEPOSITO FISCAL DE BIENES PARA SU RETORNO O EXPORTACION VIRTUAL (IA).", "level": "G", "complement": "Identificar las operaciones de transferencia que realice la industria automotriz terminal o manufacturera de vehiculos de autotransporte."}, - {"key": "V4", "description": "RETORNO VIRTUAL DERIVADO DE LA CONSTANCIA DE TRANSFERENCIA DE MERCANCIAS.", "level": "G", "complement": "Indicar conforme al supuesto de la clave de documento V4 del Apendice 2 del Anexo 22 and la R.G. 4.3.11., fracc. II."}, - {"key": "V5", "description": "TRANSFERENCIAS DE MERCANCIAS DE EMPRESAS CERTIFICADAS O RFE A EMPRESAS RESIDENTES EN EL PAIS.", "level": "G", "complement": "Indicar conforme a los supuestos de la clave de documento V5 del Ap\u00e9ndice 2 del Anexo 22 y las Reglas 7.3.1., Apartado C, fracci\u00f3n VI o 7.3.3., fracci\u00f3n XIV \u00f3 4.8.6."}, - {"key": "V6", "description": "TRANSFERENCIAS DE MERCANCIAS SUJETAS A CUPO.", "level": "G", "complement": "Indicar conforme al supuesto de la clave de documento V6 del Apendice 2 del Anexo 22 and la R.G. 1.6.6."}, - {"key": "V7", "description": "TRANSFERENCIAS DEL SECTOR AZUCARERO.", "level": "G", "complement": "Indicar conforme al supuesto de la clave de documento V7del Apendice 2 del Anexo 22 and la R.G. 4.3.7."}, - {"key": "V8", "description": "TRANSFERENCIAS DE MERCANCIAS EXTRANJERAS, NACIONALES Y NACIONALIZADAS DE TIENDAS LIBRES DE IMPUESTOS (DUTY FREE).", "level": "G", "complement": "Indicar conforme al supuesto de la clave de documento V8 del Apendice 2 del Anexo 22 and la R.G. 4.5.21., fracc. II."}, - {"key": "V9", "description": "TRANSFERENCIAS DE MERCANCIAS POR DONACION.", "level": "G", "complement": "Indicar conforme al supuesto de la clave de documento V9 del Apendice 2 del Anexo 22 and la R.G. 3.3.10."}, - {"key": "VC", "description": "IMPORTACION DEFINITIVA DE VEHICULOS USADOS EN EL ESTADO DE CHIHUAHUA.", "level": "G", "complement": "Indicar la importacion definitiva de vehiculos usados, de conformidad con el Acuerdo que establece el programa para que el Estado de Chihuahua garantice contribuciones en la importacion definitiva de vehiculos automotores usados que circulan en dicha Entidad\u00e2\u20ac\u009d, publicado en el DOF el 30 de octubre de 2012."}, - {"key": "VF", "description": "IMPORTACION DEFINITIVA DE VEHICULOS USADOS A LA FRANJA O REGION FRONTERIZA NORTE.", "level": "G", "complement": "Indicar la importaci\u00f3n definitiva de veh\u00edculos usados conforme a las reglas 3.5.6, 3.5.11. y 3.5.13."}, - {"key": "VJ", "description": "FRONTERIZACION DE VEHICULOS.", "level": "G", "complement": "Indicar la importacion definitiva de vehiculos usados, de conformidad con el Acuerdo por el que se establece el Programa para que los Gobiernos Locales Garanticen Contribuciones en la Importacion Definitiva de VehIculos Automotores Usados destinados a permanecer en la Franja y RegiOn Fronteriza Norte, publicado en el DOF el 11 de abril de 2011."}, - {"key": "VN", "description": "IMPORTACION DEFINITIVA DE VEHICULOS NUEVOS.", "level": "G", "complement": "Indicar la importacion definitiva de vehiculos nuevos."}, - {"key": "VT", "description": "IMPORTACION DE AUTOBUSES, CAMIONES Y TRACTOCAMIONES USADOS PARA EL TRANSPORTE DE PERSONAS Y MERCANCIAS.", "level": "P", "complement": "Indicar el tipo y capacidad del vehIculo solo cuando se trate de las fracciones 8702.10.05 y 8704.22.07."}, - {"key": "VU", "description": "IMPORTACION DEFINITIVA DE VEHICULOS USADOS.", "level": "G", "complement": "Indicar la importacion definitiva de vehiculos usados, conforme a las R.G. 3.5.4. y 3.5.7."}, - {"key": "XL", "description": "PRESENTACION DE MERCANCIA EN TRANSPORTE SOBREDIMENSIONADO.", "level": "G", "complement": "Identificar la mercancia que se presenta en vehiculos con caracteristicas sobredimensionadas."}, - {"key": "XP", "description": "EXCEPCION AL CUMPLIMIENTO DE REGULACIONES Y RESTRICCIONES NO ARANCELARIAS.", "level": "P", "complement": "Indicar para exceptuar el cumplimiento de un permiso, excepto NOMs."}, - {"key": "XV", "description": "EXPORTACION DE VEHICULOS DE LA INDUSTRIA AUTOMOTRIZ TERMINAL OR MANUFACTURERA DE VEHICULOS DE AUTOTRANSPORTE.", "level": "G", "complement": "Exportacion de vehiculos, a los cuales se les incorporaron opciones especiales por parte de empresas con Programa IMMEX."}, - {"key": "ZC", "description": "CONTENIDO DE AZUCAR.", "level": "P", "complement": "Indicar el contenido de azucar de las mercancias cuyas fracciones arancelarias tengan un arancel mixto."}, -] - -def seed_identifiers(db: Session): - """Seed Identifiers catalog data""" - logger.info("Seeding Identifiers...") - for item in IDENTIFIERS_DATA: - db_item = db.query(IdentifierCatalog).filter(IdentifierCatalog.key == item["key"]).first() - if not db_item: - logger.info(f"Adding identifier: {item['key']}") - new_item = IdentifierCatalog(**item) - db.add(new_item) - else: - # Update fields if changed - db_item.description = item["description"] - db_item.level = item["level"] - db_item.complement = item["complement"] - db.commit() - logger.info("Identifiers seeding completed.") diff --git a/backend/api/v1/modules/public/reference_data/incoterms/dto.py b/backend/api/v1/modules/public/reference_data/incoterms/dto.py deleted file mode 100644 index 03c40872..00000000 --- a/backend/api/v1/modules/public/reference_data/incoterms/dto.py +++ /dev/null @@ -1,9 +0,0 @@ -from pydantic import BaseModel, ConfigDict, Field - - -class IncotermDTO(BaseModel): - code: str = Field(..., min_length=1, max_length=5) - description_es: str - description_en: str - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/public/reference_data/incoterms/models.py b/backend/api/v1/modules/public/reference_data/incoterms/models.py deleted file mode 100644 index 65028280..00000000 --- a/backend/api/v1/modules/public/reference_data/incoterms/models.py +++ /dev/null @@ -1,18 +0,0 @@ -from core.database import Base -from sqlalchemy import PrimaryKeyConstraint, String -from sqlalchemy.orm import Mapped, mapped_column - - -class Incoterm(Base): - __tablename__ = "incoterms" # GIncoterm - __table_args__ = ( - PrimaryKeyConstraint("code", name="incoterms_pkey"), - {"schema": "public", "extend_existing": True}, - ) - - code: Mapped[str] = mapped_column(String(5), nullable=False) - description_es: Mapped[str] = mapped_column(String(256), nullable=False) - description_en: Mapped[str] = mapped_column(String(256), nullable=False) - - def __repr__(self): - return f"" diff --git a/backend/api/v1/modules/public/reference_data/incoterms/routes.py b/backend/api/v1/modules/public/reference_data/incoterms/routes.py deleted file mode 100644 index 634ac861..00000000 --- a/backend/api/v1/modules/public/reference_data/incoterms/routes.py +++ /dev/null @@ -1,118 +0,0 @@ -from typing import Any, Dict - -from core.database import get_core_db -from core.security import get_current_user, has_role -from fastapi import APIRouter, Depends, HTTPException, Query -from sqlalchemy.orm import Session -from sqlalchemy import or_ - -from .dto import IncotermDTO -from .models import Incoterm -from ..rbac import require_ref_view - -router = APIRouter(prefix="/incoterms") - - -@router.get( - "/", - response_model=Dict[str, Any], - dependencies=[Depends(require_ref_view("ref_incoterms.view"))], -) -async def list_incoterms( - page: int = Query(1, ge=1, description="Número de página"), - page_size: int = Query(50, ge=1, le=100, description="Tamaño de página"), - code: str = Query(None, description="Filtrar por clave"), - description: str = Query(None, description="Filtrar por descripción"), - search: str = Query(None, description="Término de búsqueda"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - skip = (page - 1) * page_size - query = db.query(Incoterm) - - if search: - search_filter = f"%{search}%" - query = query.filter( - or_( - Incoterm.code.ilike(search_filter), - Incoterm.description_es.ilike(search_filter), - Incoterm.description_en.ilike(search_filter) - ) - ) - - if code: - query = query.filter(Incoterm.code.ilike(f"%{code}%")) - if description: - query = query.filter( - (Incoterm.description_es.ilike(f"%{description}%")) | - (Incoterm.description_en.ilike(f"%{description}%")) - ) - - items = query.offset(skip).limit(page_size).all() - total = query.count() - return { - "items": [IncotermDTO.model_validate(obj) for obj in items], - "total": total, - "page": page, - "page_size": page_size, - } - - -@router.get( - "/{key}", - response_model=IncotermDTO, - dependencies=[Depends(require_ref_view("ref_incoterms.view"))], -) -async def get_incoterm( - key: str, - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - obj = db.query(Incoterm).filter(Incoterm.code == key).first() - if not obj: - raise HTTPException(status_code=404, detail="Not found") - return IncotermDTO.model_validate(obj) - - -@router.post("/", response_model=IncotermDTO, status_code=201) -async def create_incoterm( - data: IncotermDTO, - db: Session = Depends(get_core_db), - current_user: dict = Depends(has_role("admin")), -): - obj = Incoterm(**data.model_dump()) - db.add(obj) - db.commit() - db.refresh(obj) - return IncotermDTO.model_validate(obj) - - -@router.put("/{key}", response_model=IncotermDTO) -async def update_incoterm( - key: str, - data: IncotermDTO, - db: Session = Depends(get_core_db), - current_user: dict = Depends(has_role("admin")), -): - obj = db.query(Incoterm).filter(Incoterm.code == key).first() - if not obj: - raise HTTPException(status_code=404, detail="Not found") - for field, value in data.model_dump().items(): - setattr(obj, field, value) - db.commit() - db.refresh(obj) - return IncotermDTO.model_validate(obj) - - -@router.delete("/{key}", status_code=204) -async def delete_incoterm( - key: str, - db: Session = Depends(get_core_db), - current_user: dict = Depends(has_role("admin")), -): - obj = db.query(Incoterm).filter(Incoterm.code == key).first() - if not obj: - raise HTTPException(status_code=404, detail="Not found") - db.delete(obj) - db.commit() - return None diff --git a/backend/api/v1/modules/public/reference_data/incoterms/seed.py b/backend/api/v1/modules/public/reference_data/incoterms/seed.py deleted file mode 100644 index a9974e13..00000000 --- a/backend/api/v1/modules/public/reference_data/incoterms/seed.py +++ /dev/null @@ -1,14 +0,0 @@ -seed = [ - ("EXW", "A LA SALIDA", "EX WORKS"), - ("FCA", "SIN PAGO DE TRANSPORTE PRINCIPAL", "FREE CARRIER"), - ("CPT", "TRANSPORTE PAGADO HASTA", "CARRIAGE PAID TO"), - ("CIP", "TRANSPORTE Y SEGUROS PAGADOS", "CARRIAGE AND INSURANCE PAID TO"), - ("DPU", "ENTREGA EN LUGAR DESCARGADO", "DELIVERED AT PLACE UNLOADED"), - ("DAP", "ENTREGA EN LUGAR", "DELIVERED AT PLACE"), - ("DAF", "ENTREGADO EN FRONTERA", "DELIVERED AT FRONTIER"), - ("DDP", "ENTREGA DERECHOS PAGADOS", "DELIVERED DUTY PAID"), - ("FAS", "PUERTO DE EMBARQUE CONVENIDO", "FREE ALONGSIDE SHIP"), - ("FOB", "PUERTO DE EMBARQUE CONVENIDO", "FREE ON BOARD"), - ("CFR", "COSTO Y FLETE", "COST AND FREIGHT"), - ("CIF", "COSTO, SEGURO Y FLETE", "COST, INSURANCE AND FREIGHT"), -] diff --git a/backend/api/v1/modules/public/reference_data/incoterms/test_incoterms.py b/backend/api/v1/modules/public/reference_data/incoterms/test_incoterms.py deleted file mode 100644 index a8394a68..00000000 --- a/backend/api/v1/modules/public/reference_data/incoterms/test_incoterms.py +++ /dev/null @@ -1,40 +0,0 @@ -import pytest -from api.v1.modules.public.reference_data.incoterms.routes import router -from fastapi import FastAPI -from fastapi.testclient import TestClient - -app = FastAPI() -app.include_router(router) -client = TestClient(app) - - -@pytest.mark.usefixtures("client", "access_token") -def test_list_incoterms(client, access_token): - headers = {"Authorization": f"Bearer {access_token}"} - response = client.get("/incoterms/", headers=headers) - assert response.status_code == 200 - assert "items" in response.json() - assert "page" in response.json() - assert "page_size" in response.json() - - -@pytest.mark.usefixtures("client", "access_token") -def test_get_incoterm_not_found(client, access_token): - headers = {"Authorization": f"Bearer {access_token}"} - response = client.get("/incoterms/invalid_key", headers=headers) - assert response.status_code == 404 - - -def test_create_incoterm_forbidden(): - response = client.post("/incoterms/", json={"key": "TST", "description": "Test"}) - assert response.status_code in (403, 405, 404) - - -def test_update_incoterm_forbidden(): - response = client.put("/incoterms/TST", json={"key": "TST", "description": "Test"}) - assert response.status_code in (403, 405, 404) - - -def test_delete_incoterm_forbidden(): - response = client.delete("/incoterms/TST") - assert response.status_code in (403, 405, 404) diff --git a/backend/api/v1/modules/public/reference_data/invoice_types/dto.py b/backend/api/v1/modules/public/reference_data/invoice_types/dto.py deleted file mode 100644 index 0488a2a2..00000000 --- a/backend/api/v1/modules/public/reference_data/invoice_types/dto.py +++ /dev/null @@ -1,13 +0,0 @@ -from typing import Optional - -from pydantic import BaseModel, ConfigDict, Field - - -class InvoiceTypeDTO(BaseModel): - key: str = Field(..., min_length=1, max_length=5) - description: str - note: Optional[str] = None - type: Optional[str] = None - operation: Optional[str] = None - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/public/reference_data/invoice_types/models.py b/backend/api/v1/modules/public/reference_data/invoice_types/models.py deleted file mode 100644 index 792cda91..00000000 --- a/backend/api/v1/modules/public/reference_data/invoice_types/models.py +++ /dev/null @@ -1,20 +0,0 @@ -from core.database import Base -from sqlalchemy import PrimaryKeyConstraint, String -from sqlalchemy.orm import Mapped, mapped_column - - -class InvoiceType(Base): - __tablename__ = "invoice_types" # GTiposFactura - __table_args__ = ( - PrimaryKeyConstraint("key", name="invoice_types_pkey"), - {"schema": "public", "extend_existing": True}, # opcional - ) - - key: Mapped[str] = mapped_column(String(5), nullable=False) # clave del tipo de factura - description: Mapped[str] = mapped_column(String(50), nullable=False) # descripción oficial (en español) - note: Mapped[str] = mapped_column(String(500))# observación o comentario adicional - type: Mapped[str] = mapped_column(String(15))# tipo (MATERIAL, fixed asset, both) - operation: Mapped[str] = mapped_column(String(5)) # operación (imp, exp, both) - - def __repr__(self): - return f"" diff --git a/backend/api/v1/modules/public/reference_data/invoice_types/routes.py b/backend/api/v1/modules/public/reference_data/invoice_types/routes.py deleted file mode 100644 index 9fb47e3d..00000000 --- a/backend/api/v1/modules/public/reference_data/invoice_types/routes.py +++ /dev/null @@ -1,120 +0,0 @@ -from typing import Any, Dict, Optional - -from core.database import get_core_db -from core.security import get_current_user, has_role -from fastapi import APIRouter, Depends, HTTPException, Query -from sqlalchemy.orm import Session -from sqlalchemy import or_ - -from .dto import InvoiceTypeDTO -from .models import InvoiceType -from ..rbac import require_ref_view - -router = APIRouter(prefix="/invoice-types") - - -@router.get( - "/", - response_model=dict, - dependencies=[Depends(require_ref_view("ref_invoice_types.view"))], -) -def list_invoice_types( - page: int = Query(1, ge=1), - page_size: int = Query(50, ge=1, le=100), - type: Optional[str] = Query(None, description="Filter by type"), - operation: Optional[str] = Query(None, description="Filter by operation type (imp, exp, both)"), - search: str = Query(None, description="Término de búsqueda"), - db: Session = Depends(get_core_db), -): - query = db.query(InvoiceType) - - if search: - search_filter = f"%{search}%" - query = query.filter( - or_( - InvoiceType.key.ilike(search_filter), - InvoiceType.description.ilike(search_filter), - InvoiceType.note.ilike(search_filter), - InvoiceType.type.ilike(search_filter), - InvoiceType.operation.ilike(search_filter) - ) - ) - - # Filter by operation if provided - if operation: - query = query.filter( - (InvoiceType.operation == operation) | ( - InvoiceType.operation == "both") - ) - - if type == "imp" and operation == "CR": - query = query.filter(InvoiceType.operation != "exp") - - total = query.count() - items = query.offset((page - 1) * page_size).limit(page_size).all() - return { - "items": [InvoiceTypeDTO.model_validate(obj) for obj in items], - "total": total, - "page": page, - "page_size": page_size, - } - - -@router.get( - "/{key}", - response_model=InvoiceTypeDTO, - dependencies=[Depends(require_ref_view("ref_invoice_types.view"))], -) -def get_invoice_type( - key: str, - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - obj = db.query(InvoiceType).filter(InvoiceType.key == key).first() - if not obj: - raise HTTPException(status_code=404, detail="Not found") - return InvoiceTypeDTO.model_validate(obj) - - -@router.post("/", response_model=InvoiceTypeDTO, status_code=201) -def create_invoice_type( - data: InvoiceTypeDTO, - db: Session = Depends(get_core_db), - current_user: dict = Depends(has_role("admin")), -): - obj = InvoiceType(**data.model_dump()) - db.add(obj) - db.commit() - db.refresh(obj) - return InvoiceTypeDTO.model_validate(obj) - - -@router.put("/{key}", response_model=InvoiceTypeDTO) -def update_invoice_type( - key: str, - data: InvoiceTypeDTO, - db: Session = Depends(get_core_db), - current_user: dict = Depends(has_role("admin")), -): - obj = db.query(InvoiceType).filter(InvoiceType.key == key).first() - if not obj: - raise HTTPException(status_code=404, detail="Not found") - for field, value in data.model_dump().items(): - setattr(obj, field, value) - db.commit() - db.refresh(obj) - return InvoiceTypeDTO.model_validate(obj) - - -@router.delete("/{key}", status_code=204) -def delete_invoice_type( - key: str, - db: Session = Depends(get_core_db), - current_user: dict = Depends(has_role("admin")), -): - obj = db.query(InvoiceType).filter(InvoiceType.key == key).first() - if not obj: - raise HTTPException(status_code=404, detail="Not found") - db.delete(obj) - db.commit() - return None diff --git a/backend/api/v1/modules/public/reference_data/invoice_types/seed.py b/backend/api/v1/modules/public/reference_data/invoice_types/seed.py deleted file mode 100644 index 1b42742a..00000000 --- a/backend/api/v1/modules/public/reference_data/invoice_types/seed.py +++ /dev/null @@ -1,83 +0,0 @@ -seed = [ - # === TIPOS DE IMPORTACION === - ( - "TEM", - "IMPORTACION TEMPORAL", - "IMPORTACION TEMPORAL DE MATERIA PRIMA, COMPONENTES O MATERIALES PARA SER PROCESADOS Y POSTERIORMENTE EXPORTADOS.", - "both", - "imp", - ), - ( - "DEF", - "IMPORTACION DEFINITIVA", - "IMPORTACION DEFINITIVA PARA NACIONALIZACION DE MERCANCIA QUE PERMANECE EN TERRITORIO NACIONAL.", - "both", - "imp", - ), - ( - "MEX", - "COMPRAS MEXICANAS", - "IMPORTACION DE MERCANCIA NACIONAL ADQUIRIDA DE PROVEEDORES MEXICANOS PARA INCORPORAR A PROCESO PRODUCTIVO.", - "both", - "imp", - ), - ( - "CR", - "CAMBIO DE REGIMEN", - "IMPORTACION POR CAMBIO DE REGIMEN DE MERCANCIA TEMPORAL QUE SE NACIONALIZA O RETORNA.", - "both", - "imp", - ), - ( - "REP", - "REPARACION", - "REPARACION DE ACTIVOS FIJOS", - "fixed asset", - "imp", - ), - - # === TIPOS DE EXPORTACION === - ("DONAC", "DONACION", "", "both", "exp"), - ("EXDEF", "EXPORTACION DEFINITIVA", "", "material", "exp"), - ( - "MATDE", - "MATERIA PRIMA O MATERIAL DEVUELTO", - "ESTE PROCESO CONSISTE EN SOLO DESCARGAR LAS PARTES DADAS DE ALTA EN MATERIALES QUE SON RETORNADAS SIN NINGUNA MODIFICACION (A1)", - "material", - "exp", - ), - ( - "NODES", - "NO HACE DESCARGA", - "ESTE PROCESO DE ACTUALIZACION CONSISTE EN EXPORTAR UNA MERCANCIA Y NO DESCARGAR, POR LO TANTO NO EXISTE REPORTE DE DESCARGAS Y NO AFECTA SALDOS.", - "both", - "exp", - ), - ( - "PTERM", - "PRODUCTO TERMINADO Y VIRTUALES", - "EL PRODUCTO TERMINADO Y VIRTUALES DESCARGARAN: 1) APARTIR DE LOS COMPONENTES DE CADA PRODUCTO TERMINADO REGISTRADO EN LAS PARTIDAS DE EXPORTACION. 2) POR PARTE, CON LAS OPCIONES DE PODER DESCARGAR POR SUSTITUTO Y POR CLASE EN CASO DE INSUFICIENCIAS DEL COMPONENTE.", - "material", - "exp", - ), - ( - "REPAR", - "REPARACION", - "PROCESO QUE CONSISTE EN DOS ETAPAS: 1) DESCARGA EL PRODUCTO DE REPARACION QUE SE IMPORTO PARA REPARA, 2) DESCARGA EL LISTADO DE COMPONENTES QUE SE AGREGO AL PRODUCTO DE REPARACION", - "material", - "exp", - ), - ("SCRAP", "SCRAP", "", "both", "exp"), - ( - "VEMEX", - "VENTAS EN MEXICO", - "ESTE PROCESO CONSISTE EN LA VENTA EN EL MERCADO NACIONAL DE LOS PRODUCTOS.", - "both", - "exp", - ), - ("VIRTU", "VIRTUALES", "", "material", "exp"), - - # === ACTIVOS FIJOS (AMBAS OPERACIONES) === - ("AFIJO", "ACTIVO FIJO", "", "fixed asset", "exp"), - ("REEXP", "REEXPEDICION", "", "fixed asset", "exp"), -] diff --git a/backend/api/v1/modules/public/reference_data/invoice_types/test_invoice_types.py b/backend/api/v1/modules/public/reference_data/invoice_types/test_invoice_types.py deleted file mode 100644 index 5a314b93..00000000 --- a/backend/api/v1/modules/public/reference_data/invoice_types/test_invoice_types.py +++ /dev/null @@ -1,44 +0,0 @@ -import pytest -from api.v1.modules.public.reference_data.invoice_types.routes import router -from fastapi import FastAPI -from fastapi.testclient import TestClient - -app = FastAPI() -app.include_router(router) -client = TestClient(app) - - -@pytest.mark.usefixtures("client", "access_token") -def test_list_invoice_types(client, access_token): - headers = {"Authorization": f"Bearer {access_token}"} - response = client.get("/invoice-types/", headers=headers) - assert response.status_code == 200 - assert "items" in response.json() - assert "page" in response.json() - assert "page_size" in response.json() - - -@pytest.mark.usefixtures("client", "access_token") -def test_get_invoice_type_not_found(client, access_token): - headers = {"Authorization": f"Bearer {access_token}"} - response = client.get("/invoice-types/invalid_key", headers=headers) - assert response.status_code == 404 - - -def test_create_invoice_type_forbidden(): - response = client.post( - "/invoice-types/", json={"key": "TST", "description": "Test"} - ) - assert response.status_code in (403, 405, 404) - - -def test_update_invoice_type_forbidden(): - response = client.put( - "/invoice-types/TST", json={"key": "TST", "description": "Test"} - ) - assert response.status_code in (403, 405, 404) - - -def test_delete_invoice_type_forbidden(): - response = client.delete("/invoice-types/TST") - assert response.status_code in (403, 405, 404) diff --git a/backend/api/v1/modules/public/reference_data/license_exceptions/dto.py b/backend/api/v1/modules/public/reference_data/license_exceptions/dto.py deleted file mode 100644 index 7eef9278..00000000 --- a/backend/api/v1/modules/public/reference_data/license_exceptions/dto.py +++ /dev/null @@ -1,8 +0,0 @@ -from pydantic import BaseModel, ConfigDict, Field - - -class LicenseExceptionDTO(BaseModel): - key: str = Field(..., min_length=1, max_length=10) - description: str = Field(..., min_length=1, max_length=500) - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/public/reference_data/license_exceptions/models.py b/backend/api/v1/modules/public/reference_data/license_exceptions/models.py deleted file mode 100644 index ba58a680..00000000 --- a/backend/api/v1/modules/public/reference_data/license_exceptions/models.py +++ /dev/null @@ -1,20 +0,0 @@ -from core.database import Base -from sqlalchemy import PrimaryKeyConstraint, String -from sqlalchemy.orm import Mapped, mapped_column - - -class LicenseException(Base): - __tablename__ = "license_exceptions" - __table_args__ = ( - PrimaryKeyConstraint("key", name="license_exceptions_pkey"), - {"schema": "public", "extend_existing": True}, - ) - - key: Mapped[str] = mapped_column( - String(10), primary_key=True, nullable=False) # clave del simbolo - description: Mapped[str] = mapped_column( - String(500), nullable=False - ) # descripción oficial (en español) - - def __repr__(self): - return f"" diff --git a/backend/api/v1/modules/public/reference_data/license_exceptions/routes.py b/backend/api/v1/modules/public/reference_data/license_exceptions/routes.py deleted file mode 100644 index c04dfbc0..00000000 --- a/backend/api/v1/modules/public/reference_data/license_exceptions/routes.py +++ /dev/null @@ -1,122 +0,0 @@ -from typing import Any, Dict - -from core.database import get_core_db -from core.security import get_current_user, has_role -from fastapi import APIRouter, Depends, HTTPException, Query -from sqlalchemy import or_ -from sqlalchemy.orm import Session - -from .dto import LicenseExceptionDTO -from .models import LicenseException -from ..rbac import require_ref_view - -router = APIRouter(prefix="/license-exceptions") - - -@router.get( - "/", - response_model=Dict[str, Any], - dependencies=[Depends(require_ref_view("ref_pedimento_regimens.view"))], -) -async def list_license_exceptions( - page: int = Query(1, ge=1, description="Número de página"), - page_size: int = Query(50, ge=1, le=1000, description="Tamaño de página"), - q: str = Query(None, description="Búsqueda general"), - search: str = Query(None, description="Término de búsqueda"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - skip = (page - 1) * page_size - query = db.query(LicenseException) - - if search: - search_filter = f"%{search}%" - query = query.filter( - or_( - LicenseException.key.ilike(search_filter), - LicenseException.description.ilike(search_filter) - ) - ) - - if q: - search_term = f"%{q}%" - query = query.filter( - or_( - LicenseException.key.ilike(search_term), - LicenseException.description.ilike(search_term) - ) - ) - - items = query.offset(skip).limit(page_size).all() - total = query.count() - return { - "items": [LicenseExceptionDTO.model_validate(obj) for obj in items], - "total": total, - "page": page, - "page_size": page_size, - } - - -@router.get( - "/{key}", - response_model=LicenseExceptionDTO, - dependencies=[Depends(require_ref_view("ref_pedimento_regimens.view"))], -) -async def get_license_exception( - key: str, - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - obj = db.query(LicenseException).filter(LicenseException.key == key).first() - if not obj: - raise HTTPException(status_code=404, detail="Not found") - return obj - - -@router.post("/", response_model=LicenseExceptionDTO, status_code=201) -async def create_license_exception( - data: LicenseExceptionDTO, - db: Session = Depends(get_core_db), - current_user: dict = Depends(has_role("admin")), -): - # Check if already exists - existing = db.query(LicenseException).filter(LicenseException.key == data.key).first() - if existing: - raise HTTPException(status_code=400, detail="License exception with this key already exists") - - obj = LicenseException(**data.dict()) - db.add(obj) - db.commit() - db.refresh(obj) - return obj - - -@router.put("/{key}", response_model=LicenseExceptionDTO) -async def update_license_exception( - key: str, - data: LicenseExceptionDTO, - db: Session = Depends(get_core_db), - current_user: dict = Depends(has_role("admin")), -): - obj = db.query(LicenseException).filter(LicenseException.key == key).first() - if not obj: - raise HTTPException(status_code=404, detail="Not found") - for field, value in data.dict().items(): - setattr(obj, field, value) - db.commit() - db.refresh(obj) - return obj - - -@router.delete("/{key}", status_code=204) -async def delete_license_exception( - key: str, - db: Session = Depends(get_core_db), - current_user: dict = Depends(has_role("admin")), -): - obj = db.query(LicenseException).filter(LicenseException.key == key).first() - if not obj: - raise HTTPException(status_code=404, detail="Not found") - db.delete(obj) - db.commit() - return None diff --git a/backend/api/v1/modules/public/reference_data/license_exceptions/seed.py b/backend/api/v1/modules/public/reference_data/license_exceptions/seed.py deleted file mode 100644 index fcd4de76..00000000 --- a/backend/api/v1/modules/public/reference_data/license_exceptions/seed.py +++ /dev/null @@ -1,41 +0,0 @@ -import logging -from sqlalchemy.orm import Session -from .models import LicenseException - -logger = logging.getLogger(__name__) - -LICENSE_EXCEPTIONS_DATA = [ - {"key": "NLR", "description": "Licencia no requerida (NLR)."}, - {"key": "LVS", "description": "Los envíos de valor limitado (LVS)."}, - {"key": "EGB", "description": "Los envíos a los países los países del Grupo B (EGB)."}, - {"key": "CIV", "description": "Los usuarios finales Civil (CIV)."}, - {"key": "TSR", "description": "Tecnología y software a restricciones (TSR)."}, - {"key": "APP", "description": "Informática (APP)."}, - {"key": "TMP", "description": "Temporales de las importaciones, exportaciones y reexportaciones (TMP)."}, - {"key": "RPL", "description": "Mantenimiento y sustitución de piezas y equipos (RPL)."}, - {"key": "GFT", "description": "Los gobiernos, Orgs. internacionales, las inspecciones internacionales en virtud del Convenio de Armas Químicas, y la Estación Espacial Internacional (GOB). Regalo parcelas y las donaciones humanitarias (GFT)."}, - {"key": "TSU", "description": "Tecnología y software libre (TSU)."}, - {"key": "BAG", "description": "Equipaje (BAG)."}, - {"key": "AVS", "description": "Las aeronaves y buques (AVS)."}, - {"key": "APR", "description": "Adicional reexportación permisiva (APR)."}, - {"key": "ENC", "description": "Cifrado de productos, software y tecnología (ENC)."}, - {"key": "AGR", "description": "Productos básicos agrícolas (AGR)."}, - {"key": "CCD", "description": "Dispositivos de Comunicaciones del Consumidor (CCD)."}, -] - -def seed_license_exceptions(db: Session): - """Seed License Exceptions catalog data""" - logger.info("Seeding License Exceptions...") - for item in LICENSE_EXCEPTIONS_DATA: - db_item = db.query(LicenseException).filter(LicenseException.key == item["key"]).first() - if not db_item: - logger.info(f"Adding license exception: {item['key']}") - new_item = LicenseException(**item) - db.add(new_item) - else: - # Update description if it changed - if db_item.description != item["description"]: - logger.info(f"Updating license exception: {item['key']}") - db_item.description = item["description"] - db.commit() - logger.info("License Exceptions seeding completed.") diff --git a/backend/api/v1/modules/public/reference_data/material_types/dto.py b/backend/api/v1/modules/public/reference_data/material_types/dto.py deleted file mode 100644 index 1bcdc3ee..00000000 --- a/backend/api/v1/modules/public/reference_data/material_types/dto.py +++ /dev/null @@ -1,9 +0,0 @@ -from pydantic import BaseModel, ConfigDict, Field - - -class MaterialTypeDTO(BaseModel): - key: str = Field(..., min_length=1, max_length=10) - type: str = Field(..., min_length=1, max_length=15) - description: str - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/public/reference_data/material_types/models.py b/backend/api/v1/modules/public/reference_data/material_types/models.py deleted file mode 100644 index 5862649a..00000000 --- a/backend/api/v1/modules/public/reference_data/material_types/models.py +++ /dev/null @@ -1,21 +0,0 @@ -from core.database import Base -from sqlalchemy import PrimaryKeyConstraint, String -from sqlalchemy.orm import Mapped, mapped_column - - -class MaterialType(Base): - __tablename__ = "material_types" # STipoMat QTipoActFijo - __table_args__ = ( - PrimaryKeyConstraint("key", name="material_types_pkey"), - {"schema": "public", "extend_existing": True}, - ) - - key: Mapped[str] = mapped_column( - String(10), primary_key=True, nullable=False) # clave del material - type: Mapped[str] = mapped_column(String(15), nullable=False) # tipo - description: Mapped[str] = mapped_column( - String(256), nullable=False - ) # descripción oficial (en español) - - def __repr__(self): - return f"" diff --git a/backend/api/v1/modules/public/reference_data/material_types/routes.py b/backend/api/v1/modules/public/reference_data/material_types/routes.py deleted file mode 100644 index 94948c85..00000000 --- a/backend/api/v1/modules/public/reference_data/material_types/routes.py +++ /dev/null @@ -1,113 +0,0 @@ -from typing import Any, Dict - -from core.database import get_core_db -from core.security import get_current_user, has_role -from fastapi import APIRouter, Depends, HTTPException, Query -from sqlalchemy.orm import Session -from sqlalchemy import or_ - -from .dto import MaterialTypeDTO -from .models import MaterialType -from ..rbac import require_ref_view - -router = APIRouter(prefix="/material-types") - - -@router.get( - "/", - response_model=Dict[str, Any], - dependencies=[Depends(require_ref_view("ref_material_types.view"))], -) -async def list_material_types( - page: int = Query(1, ge=1, description="Número de página"), - page_size: int = Query(50, ge=1, le=1000, description="Tamaño de página"), - type: str = Query(None, description="Filtrar por tipo (ACTIVO FIJO, MATERIALES, PRODUCTOS)"), - search: str = Query(None, description="Término de búsqueda"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - skip = (page - 1) * page_size - query = db.query(MaterialType) - - if search: - search_filter = f"%{search}%" - query = query.filter( - or_( - MaterialType.key.ilike(search_filter), - MaterialType.type.ilike(search_filter), - MaterialType.description.ilike(search_filter) - ) - ) - - # Aplicar filtro por tipo si se proporciona - if type: - query = query.filter(MaterialType.type == type) - - items = query.offset(skip).limit(page_size).all() - total = query.count() - return { - "items": [MaterialTypeDTO.model_validate(obj) for obj in items], - "total": total, - "page": page, - "page_size": page_size, - } - - -@router.get( - "/{key}", - response_model=MaterialTypeDTO, - dependencies=[Depends(require_ref_view("ref_material_types.view"))], -) -async def get_material_type( - key: str, - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - obj = db.query(MaterialType).filter(MaterialType.key == key).first() - if not obj: - raise HTTPException(status_code=404, detail="Not found") - return obj - - -@router.post("/", response_model=MaterialTypeDTO, status_code=201) -async def create_material_type( - data: MaterialTypeDTO, - db: Session = Depends(get_core_db), - current_user: dict = Depends(has_role("admin")), -): - obj = MaterialType(**data.dict()) - db.add(obj) - db.commit() - db.refresh(obj) - return obj - - -@router.put("/{key}", response_model=MaterialTypeDTO) -async def update_material_type( - key: str, - data: MaterialTypeDTO, - db: Session = Depends(get_core_db), - current_user: dict = Depends(has_role("admin")), -): - obj = db.query(MaterialType).filter(MaterialType.key == key).first() - if not obj: - raise HTTPException(status_code=404, detail="Not found") - for field, value in data.dict().items(): - setattr(obj, field, value) - db.commit() - db.refresh(obj) - return obj - - -@router.delete("/{key}", status_code=204) -async def delete_material_type( - key: str, - db: Session = Depends(get_core_db), - current_user: dict = Depends(has_role("admin")), -): - obj = db.query(MaterialType).filter(MaterialType.key == key).first() - if not obj: - raise HTTPException(status_code=404, detail="Not found") - db.delete(obj) - db.commit() - return None diff --git a/backend/api/v1/modules/public/reference_data/material_types/seed.py b/backend/api/v1/modules/public/reference_data/material_types/seed.py deleted file mode 100644 index 55c85356..00000000 --- a/backend/api/v1/modules/public/reference_data/material_types/seed.py +++ /dev/null @@ -1,36 +0,0 @@ -seed = [ - ("AUXIL", "AUXILIARES.", "MATERIALES"), - ("COMB", "COMBUSTIBLES", "MATERIALES"), - ("DESP", "DESPERDICIO", "PRODUCTOS"), - ("EM", "EMPAQUES", "MATERIALES"), - ("ETIFOLL", "ETIQUETAS Y FOLLETOS", "MATERIALES"), - ("EV", "ENVASE VACIO", "MATERIALES"), - ("LUBRI", "LUBRICANTES", "MATERIALES"), - ("MCONS", "MATERIAL CONSUMIBLE", "MATERIALES"), - ("MERMA", "MERMA", "PRODUCTOS"), - ("MP", "MATERIA PRIMA", "MATERIALES"), - ("MU", "MUESTRAS", "MATERIALES"), - ("OTRM", "OTROS MATERIALES", "MATERIALES"), - ("PT", "PRODUCTO TERMINADO", "PRODUCTOS"), - ("REPARA", "REPARACION", "MATERIALES"), - ("RT", "RETRABAJO", "MATERIALES"), - ("SC", "SCRAP (MERMA Y DESPERDICIO)", "PRODUCTOS"), - ("SE", "SUB-ENSAMBLE", "PRODUCTOS"), - ("CONSU", "CONSUMIBLES", "ACTIVO FIJO"), - ("EAGRI", "EQUIPO AGRICOLA", "ACTIVO FIJO"), - ("ECOMP", "EQUIPO DE COMPUTO", "ACTIVO FIJO"), - ("ECOMU", "EQUIPO DE COMUNICACION", "ACTIVO FIJO"), - ("EDIF", "EDIFICIOS", "ACTIVO FIJO"), - ("EEMPA", "EQUIPO DE EMPAQUE", "ACTIVO FIJO"), - ("EOFIC", "EQUIPO DE OFICINA", "ACTIVO FIJO"), - ("EQSEQ", "EQUIPO DE SEGURIDAD", "ACTIVO FIJO"), - ("EQUI", "EQUIPO", "ACTIVO FIJO"), - ("ERIEG", "EQUIPO DE RIEGO", "ACTIVO FIJO"), - ("ETRAN", "EQUIPO DE TRANSPORTE", "ACTIVO FIJO"), - ("HERRA", "HERRAMIENTAS", "ACTIVO FIJO"), - ("INSTA", "INSTALACION", "ACTIVO FIJO"), - ("MAQEQ", "MAQUINARIA Y EQUIPO", "ACTIVO FIJO"), - ("MAQUI", "MAQUINARIA", "ACTIVO FIJO"), - ("REFAC", "REFACCIONES", "ACTIVO FIJO"), - ("TERR", "TERRRENOS", "ACTIVO FIJO"), -] diff --git a/backend/api/v1/modules/public/reference_data/material_types/test_material_types.py b/backend/api/v1/modules/public/reference_data/material_types/test_material_types.py deleted file mode 100644 index 1c5d950c..00000000 --- a/backend/api/v1/modules/public/reference_data/material_types/test_material_types.py +++ /dev/null @@ -1,44 +0,0 @@ -import pytest -from api.v1.modules.public.reference_data.material_types.routes import router -from fastapi import FastAPI -from fastapi.testclient import TestClient - -app = FastAPI() -app.include_router(router) -client = TestClient(app) - - -@pytest.mark.usefixtures("client", "access_token") -def test_list_material_types(client, access_token): - headers = {"Authorization": f"Bearer {access_token}"} - response = client.get("/material-types/", headers=headers) - assert response.status_code == 200 - assert "items" in response.json() - assert "page" in response.json() - assert "page_size" in response.json() - - -@pytest.mark.usefixtures("client", "access_token") -def test_get_material_type_not_found(client, access_token): - headers = {"Authorization": f"Bearer {access_token}"} - response = client.get("/material-types/invalid_key", headers=headers) - assert response.status_code == 404 - - -def test_create_material_type_forbidden(): - response = client.post( - "/material-types/", json={"key": "TST", "description": "Test"} - ) - assert response.status_code in (403, 405, 404) - - -def test_update_material_type_forbidden(): - response = client.put( - "/material-types/TST", json={"key": "TST", "description": "Test"} - ) - assert response.status_code in (403, 405, 404) - - -def test_delete_material_type_forbidden(): - response = client.delete("/material-types/TST") - assert response.status_code in (403, 405, 404) diff --git a/backend/api/v1/modules/public/reference_data/payment_methods/dto.py b/backend/api/v1/modules/public/reference_data/payment_methods/dto.py deleted file mode 100644 index b65ec3b2..00000000 --- a/backend/api/v1/modules/public/reference_data/payment_methods/dto.py +++ /dev/null @@ -1,8 +0,0 @@ -from pydantic import BaseModel, ConfigDict, Field - - -class PaymentMethodDTO(BaseModel): - key: str = Field(..., min_length=1, max_length=2) - description: str - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/public/reference_data/payment_methods/models.py b/backend/api/v1/modules/public/reference_data/payment_methods/models.py deleted file mode 100644 index 35870726..00000000 --- a/backend/api/v1/modules/public/reference_data/payment_methods/models.py +++ /dev/null @@ -1,17 +0,0 @@ -from core.database import Base -from sqlalchemy import PrimaryKeyConstraint, String -from sqlalchemy.orm import Mapped, mapped_column - - -class PaymentMethod(Base): - __tablename__ = "payment_methods" # GFormaPago - __table_args__ = ( - PrimaryKeyConstraint("key", name="payment_methods_pkey"), - {"schema": "public", "extend_existing": True}, # opcional - ) - - key: Mapped[str] = mapped_column(String(2), nullable=False) - description: Mapped[str] = mapped_column(String(100), nullable=False) - - def __repr__(self): - return f"" diff --git a/backend/api/v1/modules/public/reference_data/payment_methods/routes.py b/backend/api/v1/modules/public/reference_data/payment_methods/routes.py deleted file mode 100644 index 8731f4aa..00000000 --- a/backend/api/v1/modules/public/reference_data/payment_methods/routes.py +++ /dev/null @@ -1,106 +0,0 @@ -from typing import Any, Dict - -from core.database import get_core_db -from core.security import get_current_user, has_role -from fastapi import APIRouter, Depends, HTTPException, Query -from sqlalchemy.orm import Session -from sqlalchemy import or_ - -from .dto import PaymentMethodDTO -from .models import PaymentMethod -from ..rbac import require_ref_view - -router = APIRouter(prefix="/payment-methods") - - -@router.get( - "/", - response_model=Dict[str, Any], - dependencies=[Depends(require_ref_view("ref_payment_methods.view"))], -) -def list_payment_methods( - page: int = Query(1, ge=1, description="Número de página"), - page_size: int = Query(50, ge=1, le=100, description="Tamaño de página"), - search: str = Query(None, description="Término de búsqueda"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - skip = (page - 1) * page_size - query = db.query(PaymentMethod) - - if search: - search_filter = f"%{search}%" - query = query.filter( - or_( - PaymentMethod.key.ilike(search_filter), - PaymentMethod.description.ilike(search_filter) - ) - ) - items = query.offset(skip).limit(page_size).all() - total = query.count() - return { - "items": [PaymentMethodDTO.model_validate(obj) for obj in items], - "total": total, - "page": page, - "page_size": page_size, - } - - -@router.get( - "/{key}", - response_model=PaymentMethodDTO, - dependencies=[Depends(require_ref_view("ref_payment_methods.view"))], -) -def get_payment_method( - key: str, - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - obj = db.query(PaymentMethod).filter(PaymentMethod.key == key).first() - if not obj: - raise HTTPException(status_code=404, detail="Not found") - return obj - - -@router.post("/", response_model=PaymentMethodDTO, status_code=201) -def create_payment_method( - data: PaymentMethodDTO, - db: Session = Depends(get_core_db), - current_user: dict = Depends(has_role("admin")), -): - obj = PaymentMethod(**data.dict()) - db.add(obj) - db.commit() - db.refresh(obj) - return obj - - -@router.put("/{key}", response_model=PaymentMethodDTO) -def update_payment_method( - key: str, - data: PaymentMethodDTO, - db: Session = Depends(get_core_db), - current_user: dict = Depends(has_role("admin")), -): - obj = db.query(PaymentMethod).filter(PaymentMethod.key == key).first() - if not obj: - raise HTTPException(status_code=404, detail="Not found") - for field, value in data.dict().items(): - setattr(obj, field, value) - db.commit() - db.refresh(obj) - return obj - - -@router.delete("/{key}", status_code=204) -def delete_payment_method( - key: str, - db: Session = Depends(get_core_db), - current_user: dict = Depends(has_role("admin")), -): - obj = db.query(PaymentMethod).filter(PaymentMethod.key == key).first() - if not obj: - raise HTTPException(status_code=404, detail="Not found") - db.delete(obj) - db.commit() - return None diff --git a/backend/api/v1/modules/public/reference_data/payment_methods/seed.py b/backend/api/v1/modules/public/reference_data/payment_methods/seed.py deleted file mode 100644 index 6ca6b29d..00000000 --- a/backend/api/v1/modules/public/reference_data/payment_methods/seed.py +++ /dev/null @@ -1,25 +0,0 @@ -seed = [ - ("0", "EFECTIVO."), - ("10", "DEROGADA. --- (CERTIFICADOS ESPECIALES DE TESORERIA PUBLICO)"), - ("11", "DEROGADA. --- (CERTIFICADOS ESPECIALES DE TESORERIA PRIVADO)"), - ("12", "COMPENSACION."), - ("13", "PAGO YA EFECTUADO."), - ("14", "CONDONACIONES."), - ("15", "CUENTAS ADUANERAS DE GARANTIA POR PRECIOS ESTIMADOS"), - ("16", "ACREDITAMIENTO."), - ("18", "ESTIMULO FISCAL."), - ("19", "OTROS MEDIOS DE GARANTIA."), - ("2", "FIANZA."), - ( - "20", - "DEROGADA. --- (PAGO CONFORME AL ARTICULO 7 DE LA LEY DE INGRESOS DE LA FEDERACION, VIGENTE)", - ), - ("21", "CRÉDITO EN IVA E IEPS."), - ("22", "GARANTÍA EN IVA E IEPS."), - ("4", "DEPOSITO EN CUENTA ADUANERA."), - ("5", "TEMPORAL NO SUJETA A IMPUESTOS."), - ("6", "PENDIENTE DE PAGO."), - ("7", "CARGO A PARTIDA PRESUPUESTAL GOBIERNO FEDERAL."), - ("8", "FRANQUICIA."), - ("9", "EXENTO DE PAGO."), -] diff --git a/backend/api/v1/modules/public/reference_data/payment_methods/test_payment_methods.py b/backend/api/v1/modules/public/reference_data/payment_methods/test_payment_methods.py deleted file mode 100644 index 4d80a60e..00000000 --- a/backend/api/v1/modules/public/reference_data/payment_methods/test_payment_methods.py +++ /dev/null @@ -1,44 +0,0 @@ -import pytest -from api.v1.modules.public.reference_data.payment_methods.routes import router -from fastapi import FastAPI -from fastapi.testclient import TestClient - -app = FastAPI() -app.include_router(router) -client = TestClient(app) - - -@pytest.mark.usefixtures("client", "access_token") -def test_list_payment_methods(client, access_token): - headers = {"Authorization": f"Bearer {access_token}"} - response = client.get("/payment-methods/", headers=headers) - assert response.status_code == 200 - assert "items" in response.json() - assert "page" in response.json() - assert "page_size" in response.json() - - -@pytest.mark.usefixtures("client", "access_token") -def test_get_payment_method_not_found(client, access_token): - headers = {"Authorization": f"Bearer {access_token}"} - response = client.get("/payment-methods/invalid_key", headers=headers) - assert response.status_code == 404 - - -def test_create_payment_method_forbidden(): - response = client.post( - "/payment-methods/", json={"key": "TST", "description": "Test"} - ) - assert response.status_code in (403, 405, 404) - - -def test_update_payment_method_forbidden(): - response = client.put( - "/payment-methods/TST", json={"key": "TST", "description": "Test"} - ) - assert response.status_code in (403, 405, 404) - - -def test_delete_payment_method_forbidden(): - response = client.delete("/payment-methods/TST") - assert response.status_code in (403, 405, 404) diff --git a/backend/api/v1/modules/public/reference_data/pedimento_codes/dto.py b/backend/api/v1/modules/public/reference_data/pedimento_codes/dto.py deleted file mode 100644 index ac7dc60c..00000000 --- a/backend/api/v1/modules/public/reference_data/pedimento_codes/dto.py +++ /dev/null @@ -1,8 +0,0 @@ -from pydantic import BaseModel, ConfigDict, Field - - -class PedimentoCodeDTO(BaseModel): - code: str = Field(..., min_length=1, max_length=3) - description: str - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/public/reference_data/pedimento_codes/models.py b/backend/api/v1/modules/public/reference_data/pedimento_codes/models.py deleted file mode 100644 index 691f5117..00000000 --- a/backend/api/v1/modules/public/reference_data/pedimento_codes/models.py +++ /dev/null @@ -1,28 +0,0 @@ -from typing import TYPE_CHECKING, List - -from core.database import Base -from sqlalchemy import PrimaryKeyConstraint, String -from sqlalchemy.orm import Mapped, mapped_column, relationship - -if TYPE_CHECKING: - from ..code_pedimento_regimens.models import CodePedimentoRegimen - - -class PedimentoCode(Base): - __tablename__ = "pedimento_codes" # GClavePed - __table_args__ = ( - PrimaryKeyConstraint("code", name="pedimento_codes_pkey"), - {"schema": "public", "extend_existing": True}, # esquema del anexo 22 - ) - - code: Mapped[str] = mapped_column(String(3), nullable=False) - description: Mapped[str] = mapped_column(String(250), nullable=False) - - # Relación con los regímenes asociados - # GClavePedRegimen - regimens: Mapped[List["CodePedimentoRegimen"]] = relationship( - "CodePedimentoRegimen", uselist=True, back_populates="pedimento" - ) - - def __repr__(self): - return f"" diff --git a/backend/api/v1/modules/public/reference_data/pedimento_codes/routes.py b/backend/api/v1/modules/public/reference_data/pedimento_codes/routes.py deleted file mode 100644 index 9d1bead0..00000000 --- a/backend/api/v1/modules/public/reference_data/pedimento_codes/routes.py +++ /dev/null @@ -1,106 +0,0 @@ -from typing import Any, Dict - -from core.database import get_core_db -from core.security import get_current_user, has_role -from fastapi import APIRouter, Depends, HTTPException, Query -from sqlalchemy.orm import Session -from sqlalchemy import or_ - -from .dto import PedimentoCodeDTO -from .models import PedimentoCode -from ..rbac import require_ref_view - -router = APIRouter(prefix="/pedimento-codes") - - -@router.get( - "/", - response_model=Dict[str, Any], - dependencies=[Depends(require_ref_view("ref_pedimento_codes.view"))], -) -def list_pedimento_codes( - page: int = Query(1, ge=1, description="Número de página"), - page_size: int = Query(50, ge=1, le=1000, description="Tamaño de página"), - search: str = Query(None, description="Término de búsqueda"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - skip = (page - 1) * page_size - query = db.query(PedimentoCode) - - if search: - search_filter = f"%{search}%" - query = query.filter( - or_( - PedimentoCode.code.ilike(search_filter), - PedimentoCode.description.ilike(search_filter) - ) - ) - items = query.offset(skip).limit(page_size).all() - total = query.count() - return { - "items": [PedimentoCodeDTO.model_validate(obj) for obj in items], - "total": total, - "page": page, - "page_size": page_size, - } - - -@router.get( - "/{code}", - response_model=PedimentoCodeDTO, - dependencies=[Depends(require_ref_view("ref_pedimento_codes.view"))], -) -def get_pedimento_code( - code: str, - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - obj = db.query(PedimentoCode).filter(PedimentoCode.code == code).first() - if not obj: - raise HTTPException(status_code=404, detail="Not found") - return obj - - -@router.post("/", response_model=PedimentoCodeDTO, status_code=201) -def create_pedimento_code( - data: PedimentoCodeDTO, - db: Session = Depends(get_core_db), - current_user: dict = Depends(has_role("admin")), -): - obj = PedimentoCode(**data.dict()) - db.add(obj) - db.commit() - db.refresh(obj) - return obj - - -@router.put("/{code}", response_model=PedimentoCodeDTO) -def update_pedimento_code( - code: str, - data: PedimentoCodeDTO, - db: Session = Depends(get_core_db), - current_user: dict = Depends(has_role("admin")), -): - obj = db.query(PedimentoCode).filter(PedimentoCode.code == code).first() - if not obj: - raise HTTPException(status_code=404, detail="Not found") - for field, value in data.dict().items(): - setattr(obj, field, value) - db.commit() - db.refresh(obj) - return obj - - -@router.delete("/{code}", status_code=204) -def delete_pedimento_code( - code: str, - db: Session = Depends(get_core_db), - current_user: dict = Depends(has_role("admin")), -): - obj = db.query(PedimentoCode).filter(PedimentoCode.code == code).first() - if not obj: - raise HTTPException(status_code=404, detail="Not found") - db.delete(obj) - db.commit() - return None diff --git a/backend/api/v1/modules/public/reference_data/pedimento_codes/seed.py b/backend/api/v1/modules/public/reference_data/pedimento_codes/seed.py deleted file mode 100644 index 48971d9c..00000000 --- a/backend/api/v1/modules/public/reference_data/pedimento_codes/seed.py +++ /dev/null @@ -1,220 +0,0 @@ -seed = [ - ("A1", "IMPORTACION O EXPORTACION DEFINITIVA."), - ( - "A2", - "IMPORTACION TEMPORAL DE BIENES DISTINTOS A LOS DE ACTIVO FIJO POR PARTE DE EMPRESAS CON PITEX.", - ), - ("A3", "REGULARIZACION DE MERCANCIAS (IMPORTACION DEFINITIVA)."), - ("A4", "INTRODUCCION PARA DEPOSITO FISCAL (AGD)."), - ("A5", "INTRODUCCION A DEPOSITO FISCAL EN LOCAL AUTORIZADO."), - ( - "A6", - "IMPORTACIÓN TEMPORAL DE BIENES DE ACTIVO FIJO POR PARTE DE EMPRESAS CON PITEX.", - ), - ( - "A7", - "IMPORTACION TEMPORAL DE MERCANCIAS PARA RETORNO Y REEXPORTACION.", - ), - ( - "A8", - "IMPORTACION TEMPORAL DE MERCANCIAS E INSUMOS ASOCIADOS A PROCESOS INDUSTRIALES.", - ), - ( - "A9", - "IMPORTACION TEMPORAL DE MERCANCIAS BAJO ESQUEMAS DE CONTROL ADUANERO.", - ), - ( - "AA", - "IMPORTACION TEMPORAL DE MERCANCIAS EN OPERACIONES DIVERSAS.", - ), - ( - "AD", - "IMPORTACIÓN TEMPORAL DE MERCANCIAS DESTINADAS A CONVENCIONES Y CONGRESOS INTERNACIONALES (ARTICULO 106, FRACCION III, INCISO A) DE LA LEY).", - ), - ("AF", "IMPORTACION TEMPORAL DE BIENES DE ACTIVO FIJO (IMMEX)."), - ( - "AJ", - "IMPORTACION Y EXPORTACION TEMPORAL DE ENVASES DE MERCANCIAS (ARTICULOS 106, FRACCION II, INCISO B) Y 116, FRACCION II, INCISO A) DE LA LEY).", - ), - ( - "BA", - "IMPORTACION Y EXPORTACION TEMPORAL DE BIENES PARA SER RETORNADOS EN SU MISMO ESTADO. (ARTICULO 106, FRACCIONES II, INCISOS A) Y C), Y IV, INCISO B) DE LA LEY).", - ), - ("BB", "EXPORTACION, IMPORTACION Y RETORNOS VIRTUALES."), - ( - "BC", - "IMPORTACION Y EXPORTACION TEMPORAL DE MERCANCIAS DESTINADAS A EVENTOS CULTURALES O DEPORTIVOS (ARTICULO 106, FRACCION III, INCISO B DE LA LEY).", - ), - ( - "BD", - "IMPORTACION Y EXPORTACION TEMPORAL DE EQUIPO PARA FILMACION (ARTICULOS 106, FRACCION III, INCISO C) Y 116, FRACCION II INCISO D) DE LA LEY).", - ), - ( - "BE", - "IMPORTACION Y EXPORTACION TEMPORAL DE VEHICULOS DE PRUEBA (ARTICULO 106, FRACCION III, INCISO D) DE LA LEY).", - ), - ( - "BF", - "EXPORTACION TEMPORAL DE MERCANCIAS DESTINADAS A EXPOSICIONES, CONVENCIONES O EVENTOS CULTURALES O DEPORTIVOS (ARTICULO 116, FRACCION III DE LA LEY).", - ), - ( - "BH", - "IMPORTACION TEMPORAL DE CONTENEDORES, AVIONES, HELICOPTEROS, EMBARCACIONES Y CARROS DE FERROCARRIL (ARTICULO 106, FRACCION V, INCISOS A), B) Y E) DE LA LEY).", - ), - ("BI", "IMPORTACION TEMPORAL (ARTICULO 106, FRACCION III, INCISO E) DE LA LEY)."), - ( - "BM", - "EXPORTACION TEMPORAL DE MERCANCIAS PARA SU TRANSFORMACION, ELABORACION O REPARACION (ARTICULO 117 DE LA LEY).", - ), - ( - "BO", - "EXPORTACION TEMPORAL PARA REPARACION O SUSTITUCION Y RETORNO AL PAIS (IMMEX, RFE U OPERADOR ECONOMICO AUTORIZADO.", - ), - ( - "BP", - "IMPORTACION Y EXPORTACION TEMPORAL DE MUESTRAS O MUESTRARIOS (ARTICULOS 106, FRACCION II, INCISO D) Y 116, FRACCION II, INCISO C) DE LA LEY).", - ), - ("BR", "EXPORTACION TEMPORAL Y RETORNO DE MERCANCIAS FUNGIBLES."), - ( - "C1", - "IMPORTACION DEFINITIVA A LA FRANJA FRONTERIZA NORTE Y REGION FRONTERIZA AL AMPARO DEL „DECRETO DE LA FRANJA O REGION FRONTERIZA“ (DOF 24/12/2008 Y SUS POSTERIORES MODIFICACIONES).", - ), - ( - "C2", - "IMPORTACION DEFINITIVA AL INTERIOR DEL PAIS DE MERCANCIAS PROCEDENTES DE LA FRANJA FRONTERIZA NORTE Y REGION FRONTERIZA.", - ), - ("C3", "EXTRACCION DE DEPOSITO FISCAL DE FRANJA O REGION FRONTERIZA (AGD)."), - ("CT", "PEDIMENTO COMPLEMENTARIO."), - ("D1", "RETORNO POR SUSTITUCION."), - ( - "E1", - "EXTRACCION DE DEPOSITO FISCAL DE BIENES QUE SERAN SUJETOS A TRANSFORMACION, ELABORACION O REPARACION (AGD).", - ), - ("E2", "EXTRACCION DE DEPOSITO FISCAL DE BIENES DE ACTIVO FIJO (AGD)."), - ("E3", "EXTRACCION DE DEPOSITO FISCAL EN LOCAL AUTORIZADO (INSUMOS)."), - ("E4", "EXTRACCION DE DEPOSITO FISCAL EN LOCAL AUTORIZADO (ACTIVO FIJO)."), - ("F2", "INTRODUCCION A DEPOSITO FISCAL (IA)."), - ("F3", "EXTRACCION DE DEPOSITO FISCAL (IA)."), - ("F4", "CAMBIO DE REGIMEN DE INSUMOS O DE MERCANCIA EXPORTADA TEMPORALMENTE."), - ("F5", "CAMBIO DE REGIMEN DE MERCANCÍAS DE IMPORTACIÓN TEMPORAL A DEFINITIVA."), - ( - "F8", - "INTRODUCCION Y EXTRACCION DE DEPOSITO FISCAL DE MERCANCIAS NACIONALES O NACIONALIZADAS EN TIENDAS LIBRES DE IMPUESTOS (DUTY FREE).", - ), - ( - "F9", - "INTRODUCCION Y EXTRACCION DE DEPOSITO FISCAL DE MERCANCIAS EXTRANJERAS PARA EXPOSICION Y VENTA DE MERCANCIAS EN TIENDAS LIBRES DE IMPUESTOS (DUTY FREE).", - ), - ("G1", "EXTRACCION DE DEPOSITO FISCAL (AGD)."), - ( - "G2", - "EXTRACCION DE DEPOSITO FISCAL EN LOCAL AUTORIZADO PARA SU IMPORTACION DEFINITIVA.", - ), - ( - "G6", - "INFORME DE EXTRACCION DE DEPOSITO FISCAL DE MERCANCIAS NACIONALES O NACIONALIZADAS VENDIDAS EN TIENDAS LIBRES DE IMPUESTOS (DUTY FREE).", - ), - ( - "G7", - "INFORME DE EXTRACCION DE DEPOSITO FISCAL DE MERCANCIAS EXTRANJERAS VENDIDAS EN TIENDAS LIBRES DE IMPUESTOS (DUTY FREE).", - ), - ("G8", "REINCORPORAR AL MERCADO NACIONAL (RFE)."), - ( - "G9", - "TRANSFERENCIA DE MERCANCÍAS DE RECINTO FISCALIZADO ESTRATEGICO NO COLINDANTE CON LA ADUANA (RETIRO VIRTUAL PARA IMPORTACIÓN DEFINTIVA POR RESIDENTES EN TERRITORIO NACIONAL).", - ), - ("GC", "GLOBAL COMPLEMENTARIO."), - ("H1", "RETORNO DE MERCANCIAS EN SU MISMO ESTADO."), - ( - "H3", - "IMPORTACION TEMPORAL DE ACTIVO FIJO POR PARTE DE MAQUILADORAS.", - ), - ("H8", "RETORNO DE ENVASES."), - ( - "I1", - "IMPORTACION, EXPORTACION Y RETORNO DE MERCANCIAS ELABORADAS, TRANSFORMADAS O REPARADAS.", - ), - ( - "IN", - "IMPORTACION TEMPORAL DE BIENES QUE SERAN SUJETOS A TRANSFORMACION, ELABORACION O REPARACION (IMMEX).", - ), - ( - "J1", - "EXPORTACION DEFINITIVA DE INSUMOS ELABORADOS O TRANSFORMADOS EN RECINTO FISCALIZADO.", - ), - ( - "J2", - "EXPORTACION DEFINITIVA DE MAQUINARIA Y EQUIPO ELABORADO O TRANSFORMADO EN RECINTO FISCALIZADO.", - ), - ( - "J3", - "RETORNO Y EXPORTACION DE INSUMOS ELABORADOS O TRANSFORMADOS EN RECINTO FISCALIZADO.", - ), - ("J4", "RETORNO DE MERCANCIAS EXTRANJERAS (RFE)."), - ("K1", "DESISTIMIENTO DE REGIMEN Y RETORNO DE MERCANCIAS POR DEVOLUCION."), - ("K2", "EXTRACCION DE DEPOSITO FISCAL POR DESISTIMIENTO O TRANSFERENCIAS (AGD)."), - ( - "K3", - "EXTRACCION DE DEPOSITO FISCAL EN LOCAL AUTORIZADO PARA RETORNO O TRANSFERENCIA.", - ), - ("L1", "PEQUEÑA IMPORTACION DEFINITIVA."), - ("M1", "INTRODUCCION Y EXPORTACION DE INSUMOS."), - ("M2", "INTRODUCCION Y EXPORTACION DE MAQUINARIA Y EQUIPO."), - ("M3", "INTRODUCCION DE MERCANCIAS (RFE)."), - ("M4", "INTRODUCCION DE ACTIVO FIJO (RFE)."), - ("M5", "INTRODUCCION DE MERCANCIA NACIONAL O NACIONALIZADA (RFE)."), - ( - "P1", - "REEXPEDICION DE MERCANCIAS DE FRANJA FRONTERIZA O REGION FRONTERIZA AL INTERIOR DEL PAIS.", - ), - ("R1", "RECTIFICACION DE PEDIMENTOS."), - ("RT", "RETORNO DE MERCANCIAS (IMMEX)."), - ( - "S2", - "IMPORTACION Y EXPORTACION DE MERCANCIAS PARA RETORNAR EN SU MISMO ESTADO (ARTICULO 86 DE LA LEY).", - ), - ("T1", "IMPORTACION Y EXPORTACION POR EMPRESAS DE MENSAJERIA."), - ("T3", "TRANSITO INTERNO."), - ("T6", "TRANSITO INTERNACIONAL POR TERRITORIO EXTRANJERO."), - ("T7", "TRANSITO INTERNACIONAL POR TERRITORIO NACIONAL."), - ("T9", "TRANSITO INTERNACIONAL DE TRANSMIGRANTES."), - ( - "V1", - "TRANSFERENCIAS DE MERCANCIAS (IMPORTACION TEMPORAL VIRTUAL; INTRODUCCION VIRTUAL A DEPOSITO FISCAL O A RECINTO FISCALIZADO ESTRATEGICO; RETORNO VIRTUAL; EXPORTACION VIRTUAL DE PROVEEDORES NACIONALES).", - ), - ( - "V2", - "TRANSFERENCIAS DE MERCANCIAS IMPORTADAS CON CUENTA ADUANERA (EXPORTACION E IMPORTACION VIRTUAL).", - ), - ( - "V3", - "EXTRACCION DE DEPOSITO FISCAL DE BIENES PARA SU RETORNO O EXPORTACION VIRTUAL (IA).", - ), - ( - "V4", - "RETORNO VIRTUAL DERIVADO DE LA CONSTANCIA DE TRANSFERENCIA DE MERCANCIAS (IA).", - ), - ( - "V5", - "TRANSFERENCIAS DE MERCANCIAS DE EMPRESAS CERTIFICADAS (RETORNO VIRTUAL PARA IMPORTACION DEFINITIVA).", - ), - ( - "V6", - "TRANSFERENCIAS DE MERCANCIAS SUJETAS A CUPO (IMPORTACION DEFINITIVA Y RETORNO VIRTUAL).", - ), - ( - "V7", - "TRANSFERENCIAS DEL SECTOR AZUCARERO (EXPORTACION VIRTUAL E IMPORTACION TEMPORAL VIRTUAL).", - ), - ( - "V8", - "TRANSFERENCIA DE MERCANCIAS EN DEPOSITO FISCAL PARA LA EXPOSICION Y VENTA DE MERCANCIAS EXTRANJERAS, NACIONALES Y NACIONALIZADAS DE TIENDAS LIBRES DE IMPUESTOS (DUTY FREE).", - ), - ("V9", "TRANSFERENCIAS DE MERCANCIAS POR DONACION"), - ("VD", "VIRTUALES DIVERSOS."), - ( - "VF", - "IMPORTACION DEFINITIVA DE VEHICULOS USADOS A LA FRANJA O REGION FRONTERIZA NORTE.", - ), - ("VU", "IMPORTACION DEFINITIVA DE VEHICULOS USADOS."), -] diff --git a/backend/api/v1/modules/public/reference_data/pedimento_codes/test_pedimento_codes.py b/backend/api/v1/modules/public/reference_data/pedimento_codes/test_pedimento_codes.py deleted file mode 100644 index 4c337331..00000000 --- a/backend/api/v1/modules/public/reference_data/pedimento_codes/test_pedimento_codes.py +++ /dev/null @@ -1,44 +0,0 @@ -import pytest -from api.v1.modules.public.reference_data.pedimento_codes.routes import router -from fastapi import FastAPI -from fastapi.testclient import TestClient - -app = FastAPI() -app.include_router(router) -client = TestClient(app) - - -@pytest.mark.usefixtures("client", "access_token") -def test_list_pedimento_codes(client, access_token): - headers = {"Authorization": f"Bearer {access_token}"} - response = client.get("/pedimento-codes/", headers=headers) - assert response.status_code == 200 - assert "items" in response.json() - assert "page" in response.json() - assert "page_size" in response.json() - - -@pytest.mark.usefixtures("client", "access_token") -def test_get_pedimento_code_not_found(client, access_token): - headers = {"Authorization": f"Bearer {access_token}"} - response = client.get("/pedimento-codes/invalid_code", headers=headers) - assert response.status_code == 404 - - -def test_create_pedimento_code_forbidden(): - response = client.post( - "/pedimento-codes/", json={"code": "TST", "description": "Test"} - ) - assert response.status_code in (403, 405, 404) - - -def test_update_pedimento_code_forbidden(): - response = client.put( - "/pedimento-codes/TST", json={"code": "TST", "description": "Test"} - ) - assert response.status_code in (403, 405, 404) - - -def test_delete_pedimento_code_forbidden(): - response = client.delete("/pedimento-codes/TST") - assert response.status_code in (403, 405, 404) diff --git a/backend/api/v1/modules/public/reference_data/pedimento_regimens/dto.py b/backend/api/v1/modules/public/reference_data/pedimento_regimens/dto.py deleted file mode 100644 index 7e116920..00000000 --- a/backend/api/v1/modules/public/reference_data/pedimento_regimens/dto.py +++ /dev/null @@ -1,8 +0,0 @@ -from pydantic import BaseModel, ConfigDict, Field - - -class RegimenPedimentoDTO(BaseModel): - code: str = Field(..., min_length=1, max_length=3) - description: str - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/public/reference_data/pedimento_regimens/models.py b/backend/api/v1/modules/public/reference_data/pedimento_regimens/models.py deleted file mode 100644 index a881a7ca..00000000 --- a/backend/api/v1/modules/public/reference_data/pedimento_regimens/models.py +++ /dev/null @@ -1,32 +0,0 @@ -from typing import TYPE_CHECKING, List - -from core.database import Base -from sqlalchemy import PrimaryKeyConstraint, String -from sqlalchemy.orm import Mapped, mapped_column, relationship - -if TYPE_CHECKING: - from ..code_pedimento_regimens.models import CodePedimentoRegimen - - -class RegimenPedimento(Base): - __tablename__ = "pedimento_regimens" # GRegimenPed - __table_args__ = ( - PrimaryKeyConstraint("code", name="pedimento_regimens_pkey"), - {"schema": "public", "extend_existing": True}, - ) - - code: Mapped[str] = mapped_column( - String(3), nullable=False - ) # código tipo "01", "31" - description: Mapped[str] = mapped_column( - String(100), nullable=False - ) # nombre legal en español - - # Relación con Claves de Pedimento - # GClavePedRegimen - claves_pedimento: Mapped[List["CodePedimentoRegimen"]] = relationship( - "CodePedimentoRegimen", uselist=True, back_populates="regimen" - ) - - def __repr__(self): - return f"" diff --git a/backend/api/v1/modules/public/reference_data/pedimento_regimens/routes.py b/backend/api/v1/modules/public/reference_data/pedimento_regimens/routes.py deleted file mode 100644 index a9ca228b..00000000 --- a/backend/api/v1/modules/public/reference_data/pedimento_regimens/routes.py +++ /dev/null @@ -1,106 +0,0 @@ -from typing import Any, Dict - -from core.database import get_core_db -from core.security import get_current_user, has_role -from fastapi import APIRouter, Depends, HTTPException, Query -from sqlalchemy.orm import Session -from sqlalchemy import or_ - -from .dto import RegimenPedimentoDTO -from .models import RegimenPedimento -from ..rbac import require_ref_view - -router = APIRouter(prefix="/pedimento-regimens") - - -@router.get( - "/", - response_model=Dict[str, Any], - dependencies=[Depends(require_ref_view("ref_pedimento_regimens.view"))], -) -def list_pedimento_regimens( - page: int = Query(1, ge=1, description="Número de página"), - page_size: int = Query(50, ge=1, le=100, description="Tamaño de página"), - search: str = Query(None, description="Término de búsqueda"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - skip = (page - 1) * page_size - query = db.query(RegimenPedimento) - - if search: - search_filter = f"%{search}%" - query = query.filter( - or_( - RegimenPedimento.code.ilike(search_filter), - RegimenPedimento.description.ilike(search_filter) - ) - ) - items = query.offset(skip).limit(page_size).all() - total = query.count() - return { - "items": [RegimenPedimentoDTO.model_validate(obj) for obj in items], - "total": total, - "page": page, - "page_size": page_size, - } - - -@router.get( - "/{key}", - response_model=RegimenPedimentoDTO, - dependencies=[Depends(require_ref_view("ref_pedimento_regimens.view"))], -) -def get_pedimento_regimen( - key: str, - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - obj = db.query(RegimenPedimento).filter(RegimenPedimento.code == key).first() - if not obj: - raise HTTPException(status_code=404, detail="Not found") - return RegimenPedimentoDTO.model_validate(obj) - - -@router.post("/", response_model=RegimenPedimentoDTO, status_code=201) -def create_pedimento_regimen( - data: RegimenPedimentoDTO, - db: Session = Depends(get_core_db), - current_user: dict = Depends(has_role("admin")), -): - obj = RegimenPedimento(**data.model_dump()) - db.add(obj) - db.commit() - db.refresh(obj) - return RegimenPedimentoDTO.model_validate(obj) - - -@router.put("/{key}", response_model=RegimenPedimentoDTO) -def update_pedimento_regimen( - key: str, - data: RegimenPedimentoDTO, - db: Session = Depends(get_core_db), - current_user: dict = Depends(has_role("admin")), -): - obj = db.query(RegimenPedimento).filter(RegimenPedimento.code == key).first() - if not obj: - raise HTTPException(status_code=404, detail="Not found") - for field, value in data.model_dump().items(): - setattr(obj, field, value) - db.commit() - db.refresh(obj) - return RegimenPedimentoDTO.model_validate(obj) - - -@router.delete("/{key}", status_code=204) -def delete_pedimento_regimen( - key: str, - db: Session = Depends(get_core_db), - current_user: dict = Depends(has_role("admin")), -): - obj = db.query(RegimenPedimento).filter(RegimenPedimento.code == key).first() - if not obj: - raise HTTPException(status_code=404, detail="Not found") - db.delete(obj) - db.commit() - return None diff --git a/backend/api/v1/modules/public/reference_data/pedimento_regimens/seed.py b/backend/api/v1/modules/public/reference_data/pedimento_regimens/seed.py deleted file mode 100644 index 386ea1d1..00000000 --- a/backend/api/v1/modules/public/reference_data/pedimento_regimens/seed.py +++ /dev/null @@ -1,18 +0,0 @@ -seed = [ - ("DFI", "DEPOSITO FISCAL."), - ("ETE", "TEMPORALES DE EXPORTACION PARA ELABORACION, TRANSFORMACION O REPARACION."), - ("ETR", "TEMPORALES DE EXPORTACION PARA RETORNAR AL PAIS EN EL MISMO ESTADO."), - ("EXD", "DEFINITIVO DE EXPORTACIÓN."), - ("IMD", "DEFINITIVO DE IMPORTACIÓN."), - ( - "ITE", - "TEMPORALES DE IMPORTACION PARA ELABORACION, TRANSFORMACION O REPARACION PARA EMPRESAS CON PROGRAMA I", - ), - ( - "ITR", - "TEMPORALES DE IMPORTACION PARA RETORNAR AL EXTRANJERO EN EL MISMO ESTADO.", - ), - ("RFE", "ELABORACION, TRANSFORMACION O REPARACION EN RECINTO FISCALIZADO."), - ("RFS", "RECINTO FISCALIZADO ESTRATEGICO."), - ("TRA", "TRANSITOS."), -] diff --git a/backend/api/v1/modules/public/reference_data/pedimento_regimens/test_pedimento_regimens.py b/backend/api/v1/modules/public/reference_data/pedimento_regimens/test_pedimento_regimens.py deleted file mode 100644 index 0120ddd3..00000000 --- a/backend/api/v1/modules/public/reference_data/pedimento_regimens/test_pedimento_regimens.py +++ /dev/null @@ -1,44 +0,0 @@ -import pytest -from api.v1.modules.public.reference_data.pedimento_regimens.routes import router -from fastapi import FastAPI -from fastapi.testclient import TestClient - -app = FastAPI() -app.include_router(router) -client = TestClient(app) - - -@pytest.mark.usefixtures("client", "access_token") -def test_list_pedimento_regimens(client, access_token): - headers = {"Authorization": f"Bearer {access_token}"} - response = client.get("/pedimento-regimens/", headers=headers) - assert response.status_code == 200 - assert "items" in response.json() - assert "page" in response.json() - assert "page_size" in response.json() - - -@pytest.mark.usefixtures("client", "access_token") -def test_get_pedimento_regimen_not_found(client, access_token): - headers = {"Authorization": f"Bearer {access_token}"} - response = client.get("/pedimento-regimens/invalid_key", headers=headers) - assert response.status_code == 404 - - -def test_create_pedimento_regimen_forbidden(): - response = client.post( - "/pedimento-regimens/", json={"code": "TST", "description": "Test"} - ) - assert response.status_code in (403, 405, 404) - - -def test_update_pedimento_regimen_forbidden(): - response = client.put( - "/pedimento-regimens/TST", json={"code": "TST", "description": "Test"} - ) - assert response.status_code in (403, 405, 404) - - -def test_delete_pedimento_regimen_forbidden(): - response = client.delete("/pedimento-regimens/TST") - assert response.status_code in (403, 405, 404) diff --git a/backend/api/v1/modules/public/reference_data/pedimento_transport_catalog/dto.py b/backend/api/v1/modules/public/reference_data/pedimento_transport_catalog/dto.py deleted file mode 100644 index 5289649a..00000000 --- a/backend/api/v1/modules/public/reference_data/pedimento_transport_catalog/dto.py +++ /dev/null @@ -1,10 +0,0 @@ -from pydantic import BaseModel, ConfigDict, Field - - -class PedimentoTransportCatalogDTO(BaseModel): - code: str = Field(..., min_length=1, max_length=3) - transport_en: str - transport_es: str - payment_date_code: str = Field(..., min_length=1, max_length=1) - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/public/reference_data/pedimento_transport_catalog/models.py b/backend/api/v1/modules/public/reference_data/pedimento_transport_catalog/models.py deleted file mode 100644 index 95554872..00000000 --- a/backend/api/v1/modules/public/reference_data/pedimento_transport_catalog/models.py +++ /dev/null @@ -1,26 +0,0 @@ -from core.database import Base -from sqlalchemy import CheckConstraint, PrimaryKeyConstraint, String -from sqlalchemy.orm import Mapped, mapped_column - - -class PedimentoTransportCatalog(Base): - __tablename__ = "pedimento_transport_catalog" - __table_args__ = ( - PrimaryKeyConstraint("code", name="pedimento_transport_catalog_pkey"), - CheckConstraint( - "payment_date_code IN ('E', 'P')", - name="pedimento_transport_catalog_payment_date_code_chk", - ), - {"schema": "public", "extend_existing": True}, - ) - - code: Mapped[str] = mapped_column(String(3), nullable=False) - transport_en: Mapped[str] = mapped_column(String(80), nullable=False) - transport_es: Mapped[str] = mapped_column(String(120), nullable=False) - payment_date_code: Mapped[str] = mapped_column(String(1), nullable=False) - - def __repr__(self) -> str: - return ( - f"" - ) diff --git a/backend/api/v1/modules/public/reference_data/pedimento_transport_catalog/routes.py b/backend/api/v1/modules/public/reference_data/pedimento_transport_catalog/routes.py deleted file mode 100644 index 18392d80..00000000 --- a/backend/api/v1/modules/public/reference_data/pedimento_transport_catalog/routes.py +++ /dev/null @@ -1,115 +0,0 @@ -from typing import Any, Dict - -from core.database import get_core_db -from core.security import get_current_user -from fastapi import APIRouter, Depends, HTTPException, Query -from sqlalchemy.orm import Session -from sqlalchemy import or_ - -from .dto import PedimentoTransportCatalogDTO -from .models import PedimentoTransportCatalog -from ..rbac import require_ref_view - -router = APIRouter(prefix="/pedimento-transport-catalog") - - -@router.get( - "/", - response_model=Dict[str, Any], - dependencies=[Depends(require_ref_view("ref_transport_types.view"))], -) -async def list_pedimento_transport_catalog( - page: int = Query(1, ge=1, description="Numero de pagina"), - page_size: int = Query(100, ge=1, le=200, description="Tamano de pagina"), - search: str = Query(None, description="Término de búsqueda"), - db: Session = Depends(get_core_db), -): - skip = (page - 1) * page_size - query = db.query(PedimentoTransportCatalog) - - if search: - search_filter = f"%{search}%" - query = query.filter( - or_( - PedimentoTransportCatalog.code.ilike(search_filter), - PedimentoTransportCatalog.transport_en.ilike(search_filter), - PedimentoTransportCatalog.transport_es.ilike(search_filter), - PedimentoTransportCatalog.payment_date_code.ilike(search_filter) - ) - ).order_by(PedimentoTransportCatalog.code.asc()) - items = query.offset(skip).limit(page_size).all() - total = query.count() - return { - "items": [PedimentoTransportCatalogDTO.model_validate(obj) for obj in items], - "total": total, - "page": page, - "page_size": page_size, - } - - -@router.get( - "/{code}", - response_model=PedimentoTransportCatalogDTO, - dependencies=[Depends(require_ref_view("ref_transport_types.view"))], -) -async def get_pedimento_transport_catalog(code: str, db: Session = Depends(get_core_db)): - obj = ( - db.query(PedimentoTransportCatalog) - .filter(PedimentoTransportCatalog.code == code) - .first() - ) - if not obj: - raise HTTPException(status_code=404, detail="Not found") - return obj - - -@router.post("/", response_model=PedimentoTransportCatalogDTO, status_code=201) -async def create_pedimento_transport_catalog( - data: PedimentoTransportCatalogDTO, - db: Session = Depends(get_core_db), - user=Depends(get_current_user), -): - obj = PedimentoTransportCatalog(**data.model_dump()) - db.add(obj) - db.commit() - db.refresh(obj) - return obj - - -@router.put("/{code}", response_model=PedimentoTransportCatalogDTO) -async def update_pedimento_transport_catalog( - code: str, - data: PedimentoTransportCatalogDTO, - db: Session = Depends(get_core_db), - user=Depends(get_current_user), -): - obj = ( - db.query(PedimentoTransportCatalog) - .filter(PedimentoTransportCatalog.code == code) - .first() - ) - if not obj: - raise HTTPException(status_code=404, detail="Not found") - for field, value in data.model_dump().items(): - setattr(obj, field, value) - db.commit() - db.refresh(obj) - return obj - - -@router.delete("/{code}", status_code=204) -async def delete_pedimento_transport_catalog( - code: str, - db: Session = Depends(get_core_db), - user=Depends(get_current_user), -): - obj = ( - db.query(PedimentoTransportCatalog) - .filter(PedimentoTransportCatalog.code == code) - .first() - ) - if not obj: - raise HTTPException(status_code=404, detail="Not found") - db.delete(obj) - db.commit() - return None diff --git a/backend/api/v1/modules/public/reference_data/pedimento_transport_catalog/seed.py b/backend/api/v1/modules/public/reference_data/pedimento_transport_catalog/seed.py deleted file mode 100644 index 2742e590..00000000 --- a/backend/api/v1/modules/public/reference_data/pedimento_transport_catalog/seed.py +++ /dev/null @@ -1,16 +0,0 @@ -seed = [ - ("1", "MARITIME", "MARITIMO", "E"), - ("2", "DOUBLE-TRACK RAIL", "FERROVIARIO DE DOBLE VIA", "P"), - ("3", "ROAD-RAIL", "CARRETERO-FERROVIARIO", "P"), - ("4", "AIR", "AEREO.", "E"), - ("5", "POSTAL", "POSTAL.", "P"), - ("6", "RAIL", "FERROVIARIO.", "P"), - ("7", "ROAD", "CARRETERO.", "P"), - ("8", "PIPELINE", "TUBERIA.", "P"), - ("10", "CABLE", "CABLES.", "P"), - ("11", "DUCT", "DUCTOS.", "P"), - ("12", "PEDESTRIAN", "PEATONAL.", "P"), - ("98", "NOT DECLARED TRANSPORT MODE", "NO SE DECLARA MEDIO DE TRANSPORTE", "P"), - ("98E", "NOT DECLARED TRANSPORT MODE", "NO SE DECLARA MEDIO DE TRANSPORTE", "E"), - ("99", "OTHERS", "OTROS.", "P"), -] diff --git a/backend/api/v1/modules/public/reference_data/rbac.py b/backend/api/v1/modules/public/reference_data/rbac.py deleted file mode 100644 index baf920b7..00000000 --- a/backend/api/v1/modules/public/reference_data/rbac.py +++ /dev/null @@ -1,25 +0,0 @@ -from typing import Callable - -from core.database import get_core_db -from core.security import get_current_user, validate_access_to_resource -from fastapi import Depends, Query -from sqlalchemy.orm import Session - - -def require_ref_view(permission_code: str) -> Callable: - """ - Dependency factory to enforce reference-data view permissions per company. - - Endpoints using this dependency must receive `company_id` in query params. - """ - - async def _dep( - company_id: int = Query(..., description="Company ID"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), - ) -> bool: - validate_access_to_resource(db, company_id, current_user, [permission_code]) - return True - - return _dep - diff --git a/backend/api/v1/modules/public/reference_data/router.py b/backend/api/v1/modules/public/reference_data/router.py deleted file mode 100644 index a5d59a64..00000000 --- a/backend/api/v1/modules/public/reference_data/router.py +++ /dev/null @@ -1,142 +0,0 @@ -""" -Router principal de API v1 -Agrega todos los módulos de la aplicación -""" - -from fastapi import APIRouter - -from .agency_tariff_codes.routes import router as agency_tariff_codes_router -from .carta_porte_codes.routes import router as carta_porte_router -from .code_pedimento_regimens.routes import router as code_pedimento_regimens_router -from .containers.routes import router as containers_router -from .countries.routes import router as countries_router -from .currency_types.routes import router as currency_types_router -from .customs_sections.routes import router as customs_sections_router -from .customs_warehouses.routes import router as customs_warehouses_router -from .identifiers.routes import router as identifiers_catalog_router -from .incoterms.routes import router as incoterms_router -from .invoice_types.routes import router as invoice_types_router -from .license_exceptions.routes import router as license_exceptions_router -from .material_types.routes import router as material_types_router -from .payment_methods.routes import router as payment_methods_router -from .pedimento_transport_catalog.routes import router as pedimento_transport_catalog_router -from .pedimento_codes.routes import router as pedimento_codes_router -from .pedimento_regimens.routes import router as pedimento_regimens_router -from .states.routes import router as states_router -from .trailer_types.routes import router as trailer_types_router -from .transport_modes.routes import router as transport_modes_router -from .transport_types.routes import router as transport_types_router -from .valuation_methods.routes import router as valuation_methods_router - -# Router principal -router = APIRouter() - -# Registrar módulos -router.include_router( - agency_tariff_codes_router, - prefix="/reference_data", - tags=["public / reference_data / agency_tariff_codes"], -) -router.include_router( - pedimento_transport_catalog_router, - prefix="/reference_data", - tags=["public / reference_data / pedimento_transport_catalog"], -) -router.include_router( - pedimento_codes_router, - prefix="/reference_data", - tags=["public / reference_data / pedimento_codes"], -) -router.include_router( - payment_methods_router, - prefix="/reference_data", - tags=["public / reference_data / payment_methods"], -) -router.include_router( - containers_router, - prefix="/reference_data", - tags=["public / reference_data / containers"], -) -router.include_router( - countries_router, - prefix="/reference_data", - tags=["public / reference_data / countries"], -) -router.include_router( - material_types_router, - prefix="/reference_data", - tags=["public / reference_data / material_types"], -) -router.include_router( - identifiers_catalog_router, - prefix="/reference_data", - tags=["public / reference_data / identifiers"], -) -router.include_router( - currency_types_router, - prefix="/reference_data", - tags=["public / reference_data / currency_types"], -) -router.include_router( - states_router, prefix="/reference_data", tags=["public / reference_data / states"] -) -router.include_router( - trailer_types_router, - prefix="/reference_data", - tags=["public / reference_data / trailer_types"], -) -router.include_router( - transport_types_router, - prefix="/reference_data", - tags=["public / reference_data / transport_types"], -) -router.include_router( - customs_warehouses_router, - prefix="/reference_data", - tags=["public / reference_data / customs_warehouses"], -) -router.include_router( - valuation_methods_router, - prefix="/reference_data", - tags=["public / reference_data / valuation_methods"], -) -router.include_router( - transport_modes_router, - prefix="/reference_data", - tags=["public / reference_data / transport_modes"], -) -router.include_router( - carta_porte_router, - prefix="/reference_data", - tags=["public / reference_data / carta_porte_codes"], -) -router.include_router( - customs_sections_router, - prefix="/reference_data", - tags=["public / reference_data / customs_sections"], -) -router.include_router( - invoice_types_router, - prefix="/reference_data", - tags=["public / reference_data / invoice_types"], -) -router.include_router( - code_pedimento_regimens_router, - prefix="/reference_data", - tags=["public / reference_data / code_pedimento_regimens"], -) -router.include_router( - pedimento_regimens_router, - prefix="/reference_data", - tags=["public / reference_data / pedimento_regimens"], -) -router.include_router( - incoterms_router, - prefix="/reference_data", - tags=["public / reference_data / incoterms"], -) -router.include_router( - license_exceptions_router, - prefix="/reference_data", - tags=["public / reference_data / license_exceptions"], -) diff --git a/backend/api/v1/modules/public/reference_data/states/dto.py b/backend/api/v1/modules/public/reference_data/states/dto.py deleted file mode 100644 index 8262e875..00000000 --- a/backend/api/v1/modules/public/reference_data/states/dto.py +++ /dev/null @@ -1,12 +0,0 @@ -from typing import Optional - -from pydantic import BaseModel, ConfigDict, Field - - -class StateDTO(BaseModel): - m3_key: str = Field(..., min_length=1, max_length=3) - description: str - mex_key: Optional[str] = None - ame_key: Optional[str] = None - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/public/reference_data/states/models.py b/backend/api/v1/modules/public/reference_data/states/models.py deleted file mode 100644 index b1156aac..00000000 --- a/backend/api/v1/modules/public/reference_data/states/models.py +++ /dev/null @@ -1,22 +0,0 @@ -from typing import Optional - -from core.database import Base -from sqlalchemy import ForeignKey, PrimaryKeyConstraint, String -from sqlalchemy.orm import Mapped, mapped_column - - -class State(Base): - __tablename__ = "states" # GEstados - __table_args__ = ( - PrimaryKeyConstraint("m3_key", "description", name="states_pkey"), - {"schema": "public", "extend_existing": True}, - ) - - m3_key: Mapped[str] = mapped_column(ForeignKey("public.countries.m3_key"), nullable=False) - description: Mapped[str] = mapped_column( - String(50), nullable=False - ) # valor legal en español - mex_key: Mapped[Optional[str]] = mapped_column(String(3)) - - def __repr__(self): - return f"" diff --git a/backend/api/v1/modules/public/reference_data/states/routes.py b/backend/api/v1/modules/public/reference_data/states/routes.py deleted file mode 100644 index 165e4d0e..00000000 --- a/backend/api/v1/modules/public/reference_data/states/routes.py +++ /dev/null @@ -1,113 +0,0 @@ -from typing import Any, Dict - -from core.database import get_core_db -from core.security import get_current_user, has_role -from fastapi import APIRouter, Depends, HTTPException, Query -from sqlalchemy.orm import Session -from sqlalchemy import or_ - -from .dto import StateDTO -from .models import State -from ..rbac import require_ref_view - -router = APIRouter(prefix="/states") - - -@router.get( - "/", - response_model=Dict[str, Any], - dependencies=[Depends(require_ref_view("ref_states.view"))], -) -async def list_states( - page: int = Query(1, ge=1, description="Número de página"), - page_size: int = Query(50, ge=1, le=100, description="Tamaño de página"), - search: str = Query(None, description="Término de búsqueda"), - country_id: str = Query(None, description="Filtrar por país (m3_key)"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - skip = (page - 1) * page_size - query = db.query(State) - - if country_id: - query = query.filter(State.m3_key == country_id) - - if search: - search_filter = f"%{search}%" - query = query.filter( - or_( - State.m3_key.ilike(search_filter), - State.description.ilike(search_filter) - ) - ) - items = query.offset(skip).limit(page_size).all() - total = query.count() - return { - "items": [StateDTO.model_validate(obj) for obj in items], - "total": total, - "page": page, - "page_size": page_size, - } - - -@router.get( - "/{m3_key}", - response_model=StateDTO, - dependencies=[Depends(require_ref_view("ref_states.view"))], -) -async def get_state( - m3_key: str, - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - obj = db.query(State).filter(State.m3_key == m3_key).first() - if not obj: - raise HTTPException(status_code=404, detail="Not found") - return obj - - -@router.post("/", response_model=StateDTO, status_code=201) -async def create_state( - data: StateDTO, - db: Session = Depends(get_core_db), - current_user: dict = Depends(has_role("admin")), -): - - obj = State(**data.dict()) - db.add(obj) - db.commit() - db.refresh(obj) - return obj - - -@router.put("/{m3_key}", response_model=StateDTO) -async def update_state( - m3_key: str, - data: StateDTO, - db: Session = Depends(get_core_db), - current_user: dict = Depends(has_role("admin")), -): - - obj = db.query(State).filter(State.m3_key == m3_key).first() - if not obj: - raise HTTPException(status_code=404, detail="Not found") - for field, value in data.dict().items(): - setattr(obj, field, value) - db.commit() - db.refresh(obj) - return obj - - -@router.delete("/{m3_key}", status_code=204) -async def delete_state( - m3_key: str, - db: Session = Depends(get_core_db), - current_user: dict = Depends(has_role("admin")), -): - - obj = db.query(State).filter(State.m3_key == m3_key).first() - if not obj: - raise HTTPException(status_code=404, detail="Not found") - db.delete(obj) - db.commit() - return None diff --git a/backend/api/v1/modules/public/reference_data/states/seed.py b/backend/api/v1/modules/public/reference_data/states/seed.py deleted file mode 100644 index 14377c48..00000000 --- a/backend/api/v1/modules/public/reference_data/states/seed.py +++ /dev/null @@ -1,97 +0,0 @@ -seed = [ -('MEX', 'Aguascalientes', 'AGU'), -('MEX', 'Baja California', 'BCN'), -('MEX', 'Baja California Sur', 'BCS'), -('MEX', 'Campeche', 'CAM'), -('MEX', 'Chiapas', 'CHP'), -('MEX', 'Chihuahua', 'CHH'), -('MEX', 'Coahuila', 'COA'), -('MEX', 'Colima', 'COL'), -('MEX', 'Ciudad de México', 'CMX'), -('MEX', 'Durango', 'DUR'), -('MEX', 'Guanajuato', 'GUA'), -('MEX', 'Guerrero', 'GRO'), -('MEX', 'Hidalgo', 'HID'), -('MEX', 'Jalisco', 'JAL'), -('MEX', 'Estado de México', 'MEX'), -('MEX', 'Michoacán', 'MIC'), -('MEX', 'Morelos', 'MOR'), -('MEX', 'Nayarit', 'NAY'), -('MEX', 'Nuevo León', 'NLE'), -('MEX', 'Oaxaca', 'OAX'), -('MEX', 'Puebla', 'PUE'), -('MEX', 'Querétaro', 'QUE'), -('MEX', 'Quintana Roo', 'ROO'), -('MEX', 'San Luis Potosí', 'SLP'), -('MEX', 'Sinaloa', 'SIN'), -('MEX', 'Sonora', 'SON'), -('MEX', 'Tabasco', 'TAB'), -('MEX', 'Tamaulipas', 'TAM'), -('MEX', 'Tlaxcala', 'TLA'), -('MEX', 'Veracruz', 'VER'), -('MEX', 'Yucatán', 'YUC'), -('MEX', 'Zacatecas', 'ZAC'), -('USA', 'Alabama', 'AL'), -('USA', 'Alaska', 'AK'), -('USA', 'Arizona', 'AZ'), -('USA', 'Arkansas', 'AR'), -('USA', 'California', 'CA'), -('USA', 'Carolina del Norte', 'NC'), -('USA', 'Carolina del Sur', 'SC'), -('USA', 'Colorado', 'CO'), -('USA', 'Connecticut', 'CT'), -('USA', 'Dakota del Norte', 'ND'), -('USA', 'Dakota del Sur', 'SD'), -('USA', 'Delaware', 'DE'), -('USA', 'Florida', 'FL'), -('USA', 'Georgia', 'GA'), -('USA', 'Hawái', 'HI'), -('USA', 'Idaho', 'ID'), -('USA', 'Illinois', 'IL'), -('USA', 'Indiana', 'IN'), -('USA', 'Iowa', 'IA'), -('USA', 'Kansas', 'KS'), -('USA', 'Kentucky', 'KY'), -('USA', 'Luisiana', 'LA'), -('USA', 'Maine', 'ME'), -('USA', 'Maryland', 'MD'), -('USA', 'Massachusetts', 'MA'), -('USA', 'Míchigan', 'MI'), -('USA', 'Minnesota', 'MN'), -('USA', 'Misisipi', 'MS'), -('USA', 'Misuri', 'MO'), -('USA', 'Montana', 'MT'), -('USA', 'Nebraska', 'NE'), -('USA', 'Nevada', 'NV'), -('USA', 'Nueva Jersey', 'NJ'), -('USA', 'Nueva York', 'NY'), -('USA', 'Nuevo Hampshire', 'NH'), -('USA', 'Nuevo México', 'NM'), -('USA', 'Ohio', 'OH'), -('USA', 'Oklahoma', 'OK'), -('USA', 'Oregón', 'OR'), -('USA', 'Pensilvania', 'PA'), -('USA', 'Rhode Island', 'RI'), -('USA', 'Tennessee', 'TN'), -('USA', 'Texas', 'TX'), -('USA', 'Utah', 'UT'), -('USA', 'Vermont', 'VT'), -('USA', 'Virginia', 'VA'), -('USA', 'Virginia Occidental', 'WV'), -('USA', 'Washington', 'WA'), -('USA', 'Wisconsin', 'WI'), -('USA', 'Wyoming', 'WY'), -('CAN', 'Ontario', 'ON'), -('CAN', 'Quebec', 'QC'), -('CAN', 'Nueva Escocia', 'NS'), -('CAN', 'Nuevo Brunswick', 'NB'), -('CAN', 'Manitoba', 'MB'), -('CAN', 'Columbia Británica', 'BC'), -('CAN', 'Isla del Príncipe Eduardo', 'PE'), -('CAN', 'Saskatchewan', 'SK'), -('CAN', 'Alberta', 'AB'), -('CAN', 'Terranova y Labrador', 'NL'), -('CAN', 'Territorios del Noroeste', 'NT'), -('CAN', 'Yukón', 'YT'), -('CAN', 'Nunavut', 'UN'), -] diff --git a/backend/api/v1/modules/public/reference_data/states/test_states.py b/backend/api/v1/modules/public/reference_data/states/test_states.py deleted file mode 100644 index b38b5202..00000000 --- a/backend/api/v1/modules/public/reference_data/states/test_states.py +++ /dev/null @@ -1,40 +0,0 @@ -import pytest -from api.v1.modules.public.reference_data.states.routes import router -from fastapi import FastAPI -from fastapi.testclient import TestClient - -app = FastAPI() -app.include_router(router) -client = TestClient(app) - - -@pytest.mark.usefixtures("client", "access_token") -def test_list_states(client, access_token): - headers = {"Authorization": f"Bearer {access_token}"} - response = client.get("/states/", headers=headers) - assert response.status_code == 200 - assert "items" in response.json() - assert "page" in response.json() - assert "page_size" in response.json() - - -@pytest.mark.usefixtures("client", "access_token") -def test_get_state_not_found(client, access_token): - headers = {"Authorization": f"Bearer {access_token}"} - response = client.get("/states/invalid_key", headers=headers) - assert response.status_code == 404 - - -def test_create_state_forbidden(): - response = client.post("/states/", json={"key": "TST", "description": "Test"}) - assert response.status_code in (403, 405, 404) - - -def test_update_state_forbidden(): - response = client.put("/states/TST", json={"key": "TST", "description": "Test"}) - assert response.status_code in (403, 405, 404) - - -def test_delete_state_forbidden(): - response = client.delete("/states/TST") - assert response.status_code in (403, 405, 404) diff --git a/backend/api/v1/modules/public/reference_data/trailer_types/dto.py b/backend/api/v1/modules/public/reference_data/trailer_types/dto.py deleted file mode 100644 index 2da78789..00000000 --- a/backend/api/v1/modules/public/reference_data/trailer_types/dto.py +++ /dev/null @@ -1,28 +0,0 @@ -from typing import Optional - -from pydantic import BaseModel, ConfigDict, Field - - -class TrailerTypeListItemDTO(BaseModel): - """Catálogo público GTipoTrailer (listado UI).""" - - trailer_type_key: str = Field(..., max_length=2) - description: Optional[str] = None - - model_config = ConfigDict(from_attributes=True) - - -class TrailerTypeBaseDTO(BaseModel): - trailer_type_key: str - description: Optional[str] - company_id: str - tenant_id: str - - -class TrailerTypeCreateDTO(TrailerTypeBaseDTO): - pass - - -class TrailerTypeResponseDTO(TrailerTypeBaseDTO): - class Config: - from_attributes = True diff --git a/backend/api/v1/modules/public/reference_data/trailer_types/models.py b/backend/api/v1/modules/public/reference_data/trailer_types/models.py deleted file mode 100644 index 9a3698c9..00000000 --- a/backend/api/v1/modules/public/reference_data/trailer_types/models.py +++ /dev/null @@ -1,13 +0,0 @@ -from api.v1.common.base_models import TenantScopedMixin, TimestampMixin -from core.database import Base -from sqlalchemy import Column, ForeignKeyConstraint, String - - -class TrailerType(Base, TimestampMixin): - __tablename__ = "trailer_type" - __table_args__ = ( - {"schema": "public"}, - ) - - trailer_type_key = Column(String(2), primary_key=True, nullable=False) - description = Column(String(100), nullable=True) diff --git a/backend/api/v1/modules/public/reference_data/trailer_types/routes.py b/backend/api/v1/modules/public/reference_data/trailer_types/routes.py deleted file mode 100644 index 9cdbbea5..00000000 --- a/backend/api/v1/modules/public/reference_data/trailer_types/routes.py +++ /dev/null @@ -1,62 +0,0 @@ -from typing import Any, Dict - -from core.database import get_core_db -from fastapi import APIRouter, Depends, HTTPException, Query -from sqlalchemy.orm import Session - -from . import dto, services -from ..rbac import require_ref_view - -router = APIRouter() - - -@router.get( - "/trailer-types/", - response_model=Dict[str, Any], - dependencies=[Depends(require_ref_view("trailers.view"))], -) -def list_trailer_types( - page: int = Query(1, ge=1, description="Número de página"), - page_size: int = Query(50, ge=1, le=100, description="Tamaño de página"), - search: str = Query(None, description="Término de búsqueda"), - db: Session = Depends(get_core_db), -): - skip = (page - 1) * page_size - items, total = services.TrailerTypeService.list_trailer_types(db, skip, page_size, search) - return { - "items": [dto.TrailerTypeListItemDTO.model_validate(obj) for obj in items], - "total": total, - "page": page, - "page_size": page_size, - } - - -@router.get( - "/trailer-types/{trailer_type_key}", - response_model=dto.TrailerTypeResponseDTO, - dependencies=[Depends(require_ref_view("trailers.view"))], -) -def get_trailer_type(trailer_type_key: str, db: Session = Depends(get_core_db)): - trailer_type = services.TrailerTypeService.get_trailer_type_by_key( - db, trailer_type_key - ) - if not trailer_type: - raise HTTPException(status_code=404, detail="Trailer type not found") - return trailer_type - - -@router.post("/trailer-types", response_model=dto.TrailerTypeResponseDTO) -def create_trailer_type( - trailer_type_data: dto.TrailerTypeCreateDTO, db: Session = Depends(get_core_db) -): - return services.TrailerTypeService.create_trailer_type(db, trailer_type_data) - - -@router.delete( - "/trailer-types/{trailer_type_key}", response_model=dto.TrailerTypeResponseDTO -) -def delete_trailer_type(trailer_type_key: str, db: Session = Depends(get_core_db)): - trailer_type = services.TrailerTypeService.delete_trailer_type(db, trailer_type_key) - if not trailer_type: - raise HTTPException(status_code=404, detail="Trailer type not found") - return trailer_type diff --git a/backend/api/v1/modules/public/reference_data/trailer_types/seed.py b/backend/api/v1/modules/public/reference_data/trailer_types/seed.py deleted file mode 100644 index e172cf3e..00000000 --- a/backend/api/v1/modules/public/reference_data/trailer_types/seed.py +++ /dev/null @@ -1,45 +0,0 @@ -seed = [ - ("20", "20 ft Sea container open top"), - ("2B", "20 ft Sea container - closed top"), - ("40", "40 ft Sea container - open top"), - ("4B", "40 ft Sea container - closed top"), - ("BI", "Beverage rack trailer"), - ("CB", "Gooseneck trailer"), - ("CH", "Chassis"), - ("CL", "Other lenght Sea container - closed top"), - ("CU", "Other length Sea container - open top"), - ("CZ", "Refrigerated Container"), - ("DD", "Double Drop Trailer"), - ("DT", "Drop Back Trailer"), - ("FR", "Flat rack trailer"), - ("FT", "Flatbed/platform trailer"), - ("HC", "Hopper Trailer (covered)"), - ("HE", "Horse trailer"), - ("HO", "Hopper Trailer (open)"), - ("HP", "Hopper Trailer (covered Pneumatic Discharged)"), - ("L1", "Tank trailer (liquids) not heated/not insulated"), - ("L2", "Tank trailer (liquids) heated/not insulated"), - ("L3", "Tank trailer (liquids) not heated/insulated"), - ("L4", "Tank trailer (liquids) heated/insulate"), - ("LP", "Logging, pipe or pole trailer"), - ("LT", "Livestock trailer"), - ("NC", "No Equipment"), - ("OE", "Other"), - ("RD", "Fixed Rack, Double Drop Trailer"), - ("RG", "Gondola closed"), - ("RO", "Gondola open"), - ("RS", "Fixed Rack, Single Drop Trailer"), - ("SD", "Single Drop Trailer"), - ("T1", "Tank trailer (gas) not heated/ not insulated"), - ("T2", "Tank trailer (gas) heated/not insulated"), - ("T3", "Tank trailer (gas) not heated/insulated"), - ("T4", "Tank trailer (gas) heated/insulate"), - ("T5", "Tank trailer (chemicals) not heated/not insulated"), - ("T6", "Tank trailer (chemicals) heated/not insulated"), - ("T7", "Tank trailer (chemicals) not heated/insulated"), - ("T8", "Tank trailer (chemicals) heated/insulate"), - ("TC", "Auto car/trailer"), - ("TK", "Tank trailer (food grade liquids)"), - ("TL", "Semi-truck trailer"), - ("TW", "Controlled temperature trailer"), -] \ No newline at end of file diff --git a/backend/api/v1/modules/public/reference_data/trailer_types/services.py b/backend/api/v1/modules/public/reference_data/trailer_types/services.py deleted file mode 100644 index d36f3e3d..00000000 --- a/backend/api/v1/modules/public/reference_data/trailer_types/services.py +++ /dev/null @@ -1,51 +0,0 @@ -from typing import List, Tuple - -from sqlalchemy.orm import Session -from sqlalchemy import or_ - -from . import dto, models - - -class TrailerTypeService: - @staticmethod - def list_trailer_types( - db: Session, skip: int = 0, limit: int = 50, search: str = None - ) -> Tuple[List[models.TrailerType], int]: - q = db.query(models.TrailerType).order_by(models.TrailerType.trailer_type_key) - - if search: - search_filter = f"%{search}%" - q = q.filter( - or_( - models.TrailerType.trailer_type_key.ilike(search_filter), - models.TrailerType.description.ilike(search_filter) - ) - ) - - total = q.count() - items = q.offset(skip).limit(limit).all() - return items, total - - @staticmethod - def get_trailer_type_by_key(db: Session, trailer_type_key: str): - return ( - db.query(models.TrailerType) - .filter(models.TrailerType.trailer_type_key == trailer_type_key) - .first() - ) - - @staticmethod - def create_trailer_type(db: Session, trailer_type_data: dto.TrailerTypeCreateDTO): - new_trailer_type = models.TrailerType(**trailer_type_data.dict()) - db.add(new_trailer_type) - db.commit() - db.refresh(new_trailer_type) - return new_trailer_type - - @staticmethod - def delete_trailer_type(db: Session, trailer_type_key: str): - trailer_type = TrailerTypeService.get_trailer_type_by_key(db, trailer_type_key) - if trailer_type: - db.delete(trailer_type) - db.commit() - return trailer_type diff --git a/backend/api/v1/modules/public/reference_data/transport_modes/dto.py b/backend/api/v1/modules/public/reference_data/transport_modes/dto.py deleted file mode 100644 index 0923d4fb..00000000 --- a/backend/api/v1/modules/public/reference_data/transport_modes/dto.py +++ /dev/null @@ -1,8 +0,0 @@ -from pydantic import BaseModel, ConfigDict, Field - - -class TransportModeDTO(BaseModel): - key: str = Field(..., min_length=1, max_length=3) - name: str - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/public/reference_data/transport_modes/models.py b/backend/api/v1/modules/public/reference_data/transport_modes/models.py deleted file mode 100644 index 8eecc40e..00000000 --- a/backend/api/v1/modules/public/reference_data/transport_modes/models.py +++ /dev/null @@ -1,17 +0,0 @@ -from core.database import Base -from sqlalchemy import PrimaryKeyConstraint, String -from sqlalchemy.orm import Mapped, mapped_column - - -class TransportMode(Base): - __tablename__ = "transport_modes" # GModTransporte - __table_args__ = ( - PrimaryKeyConstraint("key", name="transport_modes_pkey"), - {"schema": "public", "extend_existing": True}, # opcional - ) - - key: Mapped[str] = mapped_column(String(3), nullable=False) - name: Mapped[str] = mapped_column(String(30), nullable=False) - - def __repr__(self): - return f"" diff --git a/backend/api/v1/modules/public/reference_data/transport_modes/routes.py b/backend/api/v1/modules/public/reference_data/transport_modes/routes.py deleted file mode 100644 index 0f480f64..00000000 --- a/backend/api/v1/modules/public/reference_data/transport_modes/routes.py +++ /dev/null @@ -1,99 +0,0 @@ -from typing import Any, Dict - -from core.database import get_core_db -from core.security import get_current_user -from fastapi import APIRouter, Depends, HTTPException, Query -from sqlalchemy.orm import Session -from sqlalchemy import or_ - -from .dto import TransportModeDTO -from .models import TransportMode -from ..rbac import require_ref_view - -router = APIRouter(prefix="/transport-modes") - - -@router.get( - "/", - response_model=Dict[str, Any], - dependencies=[Depends(require_ref_view("ref_transport_modes.view"))], -) -async def list_transport_modes( - page: int = Query(1, ge=1, description="Número de página"), - page_size: int = Query(50, ge=1, le=100, description="Tamaño de página"), - search: str = Query(None, description="Término de búsqueda"), - db: Session = Depends(get_core_db), -): - skip = (page - 1) * page_size - query = db.query(TransportMode) - - if search: - search_filter = f"%{search}%" - query = query.filter( - or_( - TransportMode.key.ilike(search_filter), - TransportMode.name.ilike(search_filter) - ) - ) - items = query.offset(skip).limit(page_size).all() - total = query.count() - return { - "items": [TransportModeDTO.model_validate(obj) for obj in items], - "total": total, - "page": page, - "page_size": page_size, - } - - -@router.get( - "/{key}", - response_model=TransportModeDTO, - dependencies=[Depends(require_ref_view("ref_transport_modes.view"))], -) -async def get_transport_mode(key: str, db: Session = Depends(get_core_db)): - obj = db.query(TransportMode).filter(TransportMode.key == key).first() - if not obj: - raise HTTPException(status_code=404, detail="Not found") - return obj - - -@router.post("/", response_model=TransportModeDTO, status_code=201) -async def create_transport_mode( - data: TransportModeDTO, - db: Session = Depends(get_core_db), - user=Depends(get_current_user), -): - obj = TransportMode(**data.dict()) - db.add(obj) - db.commit() - db.refresh(obj) - return obj - - -@router.put("/{key}", response_model=TransportModeDTO) -async def update_transport_mode( - key: str, - data: TransportModeDTO, - db: Session = Depends(get_core_db), - user=Depends(get_current_user), -): - obj = db.query(TransportMode).filter(TransportMode.key == key).first() - if not obj: - raise HTTPException(status_code=404, detail="Not found") - for field, value in data.dict().items(): - setattr(obj, field, value) - db.commit() - db.refresh(obj) - return obj - - -@router.delete("/{key}", status_code=204) -async def delete_transport_mode( - key: str, db: Session = Depends(get_core_db), user=Depends(get_current_user) -): - obj = db.query(TransportMode).filter(TransportMode.key == key).first() - if not obj: - raise HTTPException(status_code=404, detail="Not found") - db.delete(obj) - db.commit() - return None diff --git a/backend/api/v1/modules/public/reference_data/transport_modes/seed.py b/backend/api/v1/modules/public/reference_data/transport_modes/seed.py deleted file mode 100644 index 6790d279..00000000 --- a/backend/api/v1/modules/public/reference_data/transport_modes/seed.py +++ /dev/null @@ -1,12 +0,0 @@ -seed = [ - ("10", "VESSEL"), - ("11", "VESSEL CONTAINER"), - ("12", "BORDER WATER BORNE (CAN -MEX )"), - ("20", "RAIL"), - ("21", "RAIL CONTAINER"), - ("30", "TRUCK"), - ("31", "TRUCK CONTAINER"), - ("40", "AIR"), - ("41", "AIR CONTAINER"), - ("50", "MAIL"), -] diff --git a/backend/api/v1/modules/public/reference_data/transport_modes/test_transport_modes.py b/backend/api/v1/modules/public/reference_data/transport_modes/test_transport_modes.py deleted file mode 100644 index f88d414c..00000000 --- a/backend/api/v1/modules/public/reference_data/transport_modes/test_transport_modes.py +++ /dev/null @@ -1,44 +0,0 @@ -import pytest -from api.v1.modules.public.reference_data.transport_modes.routes import router -from fastapi import FastAPI -from fastapi.testclient import TestClient - -app = FastAPI() -app.include_router(router) -client = TestClient(app) - - -@pytest.mark.usefixtures("client", "access_token") -def test_list_transport_modes(client, access_token): - headers = {"Authorization": f"Bearer {access_token}"} - response = client.get("/transport-modes/", headers=headers) - assert response.status_code == 200 - assert "items" in response.json() - assert "page" in response.json() - assert "page_size" in response.json() - - -@pytest.mark.usefixtures("client", "access_token") -def test_get_transport_mode_not_found(client, access_token): - headers = {"Authorization": f"Bearer {access_token}"} - response = client.get("/transport-modes/invalid_key", headers=headers) - assert response.status_code == 404 - - -def test_create_transport_mode_forbidden(): - response = client.post( - "/transport-modes/", json={"key": "TST", "description": "Test"} - ) - assert response.status_code in (403, 405, 404) - - -def test_update_transport_mode_forbidden(): - response = client.put( - "/transport-modes/TST", json={"key": "TST", "description": "Test"} - ) - assert response.status_code in (403, 405, 404) - - -def test_delete_transport_mode_forbidden(): - response = client.delete("/transport-modes/TST") - assert response.status_code in (403, 405, 404) diff --git a/backend/api/v1/modules/public/reference_data/transport_types/dto.py b/backend/api/v1/modules/public/reference_data/transport_types/dto.py deleted file mode 100644 index a026e496..00000000 --- a/backend/api/v1/modules/public/reference_data/transport_types/dto.py +++ /dev/null @@ -1,8 +0,0 @@ -from pydantic import BaseModel, ConfigDict, Field - - -class TransportTypeDTO(BaseModel): - transport_code: str = Field(..., min_length=1, max_length=2) - description: str - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/public/reference_data/transport_types/models.py b/backend/api/v1/modules/public/reference_data/transport_types/models.py deleted file mode 100644 index 0838f66b..00000000 --- a/backend/api/v1/modules/public/reference_data/transport_types/models.py +++ /dev/null @@ -1,21 +0,0 @@ -from core.database import Base -from sqlalchemy import PrimaryKeyConstraint, String -from sqlalchemy.orm import Mapped, mapped_column - - -class TransportType(Base): - __tablename__ = "transport_types" # GTiposTransporte - __table_args__ = ( - PrimaryKeyConstraint("transport_code", name="transport_types_pkey"), - {"schema": "public", "extend_existing": True}, - ) - - transport_code: Mapped[str] = mapped_column( - String(2), nullable=False - ) # código SAT o interno - description: Mapped[str] = mapped_column( - String(100), nullable=False - ) # descripción del medio de transporte - - def __repr__(self): - return f"" diff --git a/backend/api/v1/modules/public/reference_data/transport_types/routes.py b/backend/api/v1/modules/public/reference_data/transport_types/routes.py deleted file mode 100644 index fdfc47a9..00000000 --- a/backend/api/v1/modules/public/reference_data/transport_types/routes.py +++ /dev/null @@ -1,113 +0,0 @@ -from typing import Any, Dict - -from core.database import get_core_db -from core.security import get_current_user -from fastapi import APIRouter, Depends, HTTPException, Query -from sqlalchemy.orm import Session -from sqlalchemy import or_ - -from .dto import TransportTypeDTO -from .models import TransportType -from ..rbac import require_ref_view - -router = APIRouter(prefix="/transport-types") - - -@router.get( - "/", - response_model=Dict[str, Any], - dependencies=[Depends(require_ref_view("ref_transport_types.view"))], -) -def list_transport_types( - page: int = Query(1, ge=1, description="Número de página"), - page_size: int = Query(50, ge=1, le=100, description="Tamaño de página"), - search: str = Query(None, description="Término de búsqueda"), - db: Session = Depends(get_core_db), -): - skip = (page - 1) * page_size - query = db.query(TransportType) - - if search: - search_filter = f"%{search}%" - query = query.filter( - or_( - TransportType.transport_code.ilike(search_filter), - TransportType.description.ilike(search_filter) - ) - ) - items = query.offset(skip).limit(page_size).all() - total = query.count() - return { - "items": [TransportTypeDTO.model_validate(obj) for obj in items], - "total": total, - "page": page, - "page_size": page_size, - } - - -@router.get( - "/{transport_code}", - response_model=TransportTypeDTO, - dependencies=[Depends(require_ref_view("ref_transport_types.view"))], -) -def get_transport_type(transport_code: str, db: Session = Depends(get_core_db)): - obj = ( - db.query(TransportType) - .filter(TransportType.transport_code == transport_code) - .first() - ) - if not obj: - raise HTTPException(status_code=404, detail="Not found") - return obj - - -@router.post("/", response_model=TransportTypeDTO, status_code=201) -def create_transport_type( - data: TransportTypeDTO, - db: Session = Depends(get_core_db), - user=Depends(get_current_user), -): - obj = TransportType(**data.dict()) - db.add(obj) - db.commit() - db.refresh(obj) - return obj - - -@router.put("/{transport_code}", response_model=TransportTypeDTO) -def update_transport_type( - transport_code: str, - data: TransportTypeDTO, - db: Session = Depends(get_core_db), - user=Depends(get_current_user), -): - obj = ( - db.query(TransportType) - .filter(TransportType.transport_code == transport_code) - .first() - ) - if not obj: - raise HTTPException(status_code=404, detail="Not found") - for field, value in data.dict().items(): - setattr(obj, field, value) - db.commit() - db.refresh(obj) - return obj - - -@router.delete("/{transport_code}", status_code=204) -def delete_transport_type( - transport_code: str, - db: Session = Depends(get_core_db), - user=Depends(get_current_user), -): - obj = ( - db.query(TransportType) - .filter(TransportType.transport_code == transport_code) - .first() - ) - if not obj: - raise HTTPException(status_code=404, detail="Not found") - db.delete(obj) - db.commit() - return None diff --git a/backend/api/v1/modules/public/reference_data/transport_types/seed.py b/backend/api/v1/modules/public/reference_data/transport_types/seed.py deleted file mode 100644 index 5868b5c6..00000000 --- a/backend/api/v1/modules/public/reference_data/transport_types/seed.py +++ /dev/null @@ -1,22 +0,0 @@ -seed = [ - ("AR", "Armored Truck"), - ("AU", "Automobiles"), - ("BT", "Box Truck"), - ("BU", "Bus"), - ("BV", "Beverage Truck (Refer or not)"), - ("BY", "Bicycle"), - ("CO", "Construction Vehicle (general)"), - ("EV", "Emergency Vehicle (general)"), - ("FE", "Ferry"), - ("FM", "Farm Tractor"), - ("GB", "Garbage Truck"), - ("MC", "Motorcycle"), - ("OC", "Other"), - ("PM", "Pick-up Truck w/camper"), - ("PN", "Panel Truck"), - ("PU", "Pickup Truck"), - ("PV", "Passenger"), - ("RV", "Recreation Vehicle (RV)"), - ("TR", "Semi Tracker"), - ("TV", "Van"), -] diff --git a/backend/api/v1/modules/public/reference_data/transport_types/test_transport_types.py b/backend/api/v1/modules/public/reference_data/transport_types/test_transport_types.py deleted file mode 100644 index c2215df0..00000000 --- a/backend/api/v1/modules/public/reference_data/transport_types/test_transport_types.py +++ /dev/null @@ -1,44 +0,0 @@ -import pytest -from api.v1.modules.public.reference_data.transport_types.routes import router -from fastapi import FastAPI -from fastapi.testclient import TestClient - -app = FastAPI() -app.include_router(router) -client = TestClient(app) - - -@pytest.mark.usefixtures("client", "access_token") -def test_list_transport_types(client, access_token): - headers = {"Authorization": f"Bearer {access_token}"} - response = client.get("/transport-types/", headers=headers) - assert response.status_code == 200 - assert "items" in response.json() - assert "page" in response.json() - assert "page_size" in response.json() - - -@pytest.mark.usefixtures("client", "access_token") -def test_get_transport_type_not_found(client, access_token): - headers = {"Authorization": f"Bearer {access_token}"} - response = client.get("/transport-types/invalid_code", headers=headers) - assert response.status_code == 404 - - -def test_create_transport_type_forbidden(): - response = client.post( - "/transport-types/", json={"transport_code": "TST", "description": "Test"} - ) - assert response.status_code in (403, 405, 404) - - -def test_update_transport_type_forbidden(): - response = client.put( - "/transport-types/TST", json={"transport_code": "TST", "description": "Test"} - ) - assert response.status_code in (403, 405, 404) - - -def test_delete_transport_type_forbidden(): - response = client.delete("/transport-types/TST") - assert response.status_code in (403, 405, 404) diff --git a/backend/api/v1/modules/public/reference_data/valuation_methods/dto.py b/backend/api/v1/modules/public/reference_data/valuation_methods/dto.py deleted file mode 100644 index 00978582..00000000 --- a/backend/api/v1/modules/public/reference_data/valuation_methods/dto.py +++ /dev/null @@ -1,8 +0,0 @@ -from pydantic import BaseModel, ConfigDict, Field - - -class ValuationMethodDTO(BaseModel): - key: str = Field(..., min_length=1, max_length=2) - description: str - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/public/reference_data/valuation_methods/models.py b/backend/api/v1/modules/public/reference_data/valuation_methods/models.py deleted file mode 100644 index cdfe6588..00000000 --- a/backend/api/v1/modules/public/reference_data/valuation_methods/models.py +++ /dev/null @@ -1,17 +0,0 @@ -from core.database import Base -from sqlalchemy import PrimaryKeyConstraint, String -from sqlalchemy.orm import Mapped, mapped_column - - -class ValuationMethod(Base): - __tablename__ = "valuation_methods" # GMetValor - __table_args__ = ( - PrimaryKeyConstraint("key", name="valuation_methods_pkey"), - {"schema": "public", "extend_existing": True}, - ) - - key: Mapped[str] = mapped_column(String(2), nullable=False) - description: Mapped[str] = mapped_column(String(200), nullable=False) - - def __repr__(self): - return f"" diff --git a/backend/api/v1/modules/public/reference_data/valuation_methods/routes.py b/backend/api/v1/modules/public/reference_data/valuation_methods/routes.py deleted file mode 100644 index 899f80f3..00000000 --- a/backend/api/v1/modules/public/reference_data/valuation_methods/routes.py +++ /dev/null @@ -1,106 +0,0 @@ -from typing import Any, Dict - -from core.database import get_core_db -from core.security import get_current_user, has_role -from fastapi import APIRouter, Depends, HTTPException, Query -from sqlalchemy.orm import Session -from sqlalchemy import or_ - -from .dto import ValuationMethodDTO -from .models import ValuationMethod -from ..rbac import require_ref_view - -router = APIRouter(prefix="/valuation-methods") - - -@router.get( - "/", - response_model=Dict[str, Any], - dependencies=[Depends(require_ref_view("ref_valuation_methods.view"))], -) -async def list_valuation_methods( - page: int = Query(1, ge=1, description="Número de página"), - page_size: int = Query(50, ge=1, le=100, description="Tamaño de página"), - search: str = Query(None, description="Término de búsqueda"), - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - skip = (page - 1) * page_size - query = db.query(ValuationMethod) - - if search: - search_filter = f"%{search}%" - query = query.filter( - or_( - ValuationMethod.key.ilike(search_filter), - ValuationMethod.description.ilike(search_filter) - ) - ) - items = query.offset(skip).limit(page_size).all() - total = query.count() - return { - "items": [ValuationMethodDTO.model_validate(obj) for obj in items], - "total": total, - "page": page, - "page_size": page_size, - } - - -@router.get( - "/{key}", - response_model=ValuationMethodDTO, - dependencies=[Depends(require_ref_view("ref_valuation_methods.view"))], -) -async def get_valuation_method( - key: str, - db: Session = Depends(get_core_db), - current_user: dict = Depends(get_current_user), -): - obj = db.query(ValuationMethod).filter(ValuationMethod.key == key).first() - if not obj: - raise HTTPException(status_code=404, detail="Not found") - return obj - - -@router.post("/", response_model=ValuationMethodDTO, status_code=201) -async def create_valuation_method( - data: ValuationMethodDTO, - db: Session = Depends(get_core_db), - current_user: dict = Depends(has_role("admin")), -): - obj = ValuationMethod(**data.dict()) - db.add(obj) - db.commit() - db.refresh(obj) - return obj - - -@router.put("/{key}", response_model=ValuationMethodDTO) -async def update_valuation_method( - key: str, - data: ValuationMethodDTO, - db: Session = Depends(get_core_db), - current_user: dict = Depends(has_role("admin")), -): - obj = db.query(ValuationMethod).filter(ValuationMethod.key == key).first() - if not obj: - raise HTTPException(status_code=404, detail="Not found") - for field, value in data.dict().items(): - setattr(obj, field, value) - db.commit() - db.refresh(obj) - return obj - - -@router.delete("/{key}", status_code=204) -async def delete_valuation_method( - key: str, - db: Session = Depends(get_core_db), - current_user: dict = Depends(has_role("admin")), -): - obj = db.query(ValuationMethod).filter(ValuationMethod.key == key).first() - if not obj: - raise HTTPException(status_code=404, detail="Not found") - db.delete(obj) - db.commit() - return None diff --git a/backend/api/v1/modules/public/reference_data/valuation_methods/seed.py b/backend/api/v1/modules/public/reference_data/valuation_methods/seed.py deleted file mode 100644 index 1542540a..00000000 --- a/backend/api/v1/modules/public/reference_data/valuation_methods/seed.py +++ /dev/null @@ -1,9 +0,0 @@ -seed = [ - ("0", "VALOR COMERCIAL (CLAVE USADA SOLO A LA EXPORTACION)."), - ("1", "VALOR DE TRANSACCION DE LAS MERCANCIAS."), - ("2", "VALOR DE TRANSACCION DE MERCANCIAS IDENTICAS."), - ("3", "VALOR DE TRANSACCION DE MERCANCIAS SIMILARES."), - ("4", "VALOR DE PRECIO UNITARIO DE VENTA."), - ("5", "VALOR RECONSTRUIDO."), - ("6", "ULTIMO RECURSO"), -] diff --git a/backend/api/v1/modules/public/reference_data/valuation_methods/test_valuation_methods.py b/backend/api/v1/modules/public/reference_data/valuation_methods/test_valuation_methods.py deleted file mode 100644 index 9fb8384b..00000000 --- a/backend/api/v1/modules/public/reference_data/valuation_methods/test_valuation_methods.py +++ /dev/null @@ -1,44 +0,0 @@ -import pytest -from api.v1.modules.public.reference_data.valuation_methods.routes import router -from fastapi import FastAPI -from fastapi.testclient import TestClient - -app = FastAPI() -app.include_router(router) -client = TestClient(app) - - -@pytest.mark.usefixtures("client", "access_token") -def test_list_valuation_methods(client, access_token): - headers = {"Authorization": f"Bearer {access_token}"} - response = client.get("/valuation-methods/", headers=headers) - assert response.status_code == 200 - assert "items" in response.json() - assert "page" in response.json() - assert "page_size" in response.json() - - -@pytest.mark.usefixtures("client", "access_token") -def test_get_valuation_method_not_found(client, access_token): - headers = {"Authorization": f"Bearer {access_token}"} - response = client.get("/valuation-methods/invalid_key", headers=headers) - assert response.status_code == 404 - - -def test_create_valuation_method_forbidden(): - response = client.post( - "/valuation-methods/", json={"key": "TST", "description": "Test"} - ) - assert response.status_code in (403, 405, 404) - - -def test_update_valuation_method_forbidden(): - response = client.put( - "/valuation-methods/TST", json={"key": "TST", "description": "Test"} - ) - assert response.status_code in (403, 405, 404) - - -def test_delete_valuation_method_forbidden(): - response = client.delete("/valuation-methods/TST") - assert response.status_code in (403, 405, 404) diff --git a/backend/api/v1/modules/public/router.py b/backend/api/v1/modules/public/router.py deleted file mode 100644 index 22589835..00000000 --- a/backend/api/v1/modules/public/router.py +++ /dev/null @@ -1,14 +0,0 @@ -""" -Router principal de API v1 -Agrega todos los módulos de la aplicación -""" - -from fastapi import APIRouter - -from .reference_data.router import router as reference_data_router - -# Router principal -router = APIRouter() - -# Registrar módulos -router.include_router(reference_data_router, prefix="/public") diff --git a/backend/api/v1/modules/sitar/README.md b/backend/api/v1/modules/sitar/README.md deleted file mode 100644 index 53a9aa0f..00000000 --- a/backend/api/v1/modules/sitar/README.md +++ /dev/null @@ -1,320 +0,0 @@ -# SITAR API Module - -Módulo reorganizado para integración con la API externa de SITAR (Sistema de Información de Aranceles). - -## Estructura - -Cada recurso ahora tiene su propia carpeta con separación de responsabilidades: - -``` -backend/api/v1/modules/sitar/ -├── common/ # Servicio base compartido -│ ├── __init__.py -│ └── base_service.py # SitarAPIBaseService con autenticación -├── tlcs/ # TLCS (Tratados de Libre Comercio) -│ ├── __init__.py -│ ├── schemas.py # TLCSResponse -│ ├── service.py # TLCSService -│ └── router.py # Endpoints FastAPI -├── fracciones/ # Fracciones arancelarias mexicanas -├── fracciones_usa/ # Fracciones USA -├── regulaciones/ # Regulaciones y restricciones -├── prosec/ # PROSEC -├── precios_estimados/ # Precios estimados -├── fundamentos_tlc/ # Fundamentos TLC -├── aladi2/ # ALADI2 -├── cuotas2/ # Cuotas compensatorias -├── cupos/ # Cupos de importación -├── fracciones_anteriores/ # Historial de fracciones -├── informacion_general/ # Información general -├── ieps/ # IEPS -├── noms/ # Normas Oficiales Mexicanas -├── rcg2/ # Reglas de Carácter General -├── reit/ # REIT -├── requisito_previo/ # Requisitos previos -├── vehiculos_marcas/ # Marcas de vehículos -├── vehiculos_modelos/ # Modelos de vehículos -├── __init__.py # Exporta todos los servicios -└── main_router.py # Router principal con todos los endpoints -``` - -## Uso del Servicio - -### Desde cualquier parte del código - -Cada recurso tiene su propio servicio **singleton** que maneja automáticamente la autenticación: - -```python -from api.v1.modules.sitar.tlcs import TLCSService -from api.v1.modules.sitar.fracciones import FraccionesService - -# Obtener instancia singleton -tlcs_service = TLCSService.get_instance() - -# Buscar TLCS (async) -tlcs_data = await tlcs_service.search( - fraccion="84716001", - pais="USA", - limit=100 -) - -# Procesar resultados -for tlcs in tlcs_data: - print(f"Tasa: {tlcs.TASATXT}, País: {tlcs.PAIS}") -``` - -### Desde código síncrono - -Si estás en contexto síncrono, usa `asyncio.run()`: - -```python -import asyncio -from api.v1.modules.sitar.tlcs import TLCSService - -tlcs_service = TLCSService.get_instance() -tlcs_data = asyncio.run(tlcs_service.search(fraccion="84716001", pais="USA")) -``` - -## Métodos Disponibles - -### TLCS (Tratados de Libre Comercio) - -- `search_tlcs(fraccion, pais, nico, skip, limit)` - Buscar TLCS -- `get_tlcs_by_id(sysid, fraccion)` - Obtener TLCS por ID - -### Fracciones Arancelarias - -- `search_fracciones(fraccion, nico, skip, limit)` - Buscar fracciones mexicanas -- `get_fraccion_by_id(sysid)` - Obtener fracción por ID -- `search_fracciones_usa(fraccion, skip, limit)` - Buscar fracciones USA -- `get_fraccion_usa_by_id(consecutivo)` - Obtener fracción USA por ID -- `search_fracciones_anteriores(fraccion_actual, fraccion_anterior, skip, limit)` - Buscar historial de fracciones -- `get_fracciones_anteriores_by_id(sysid)` - Obtener historial por ID - -### Regulaciones y Restricciones - -- `search_regulaciones(fraccion, nico, skip, limit)` - Buscar regulaciones -- `get_regulacion_by_id(sysid)` - Obtener regulación por ID -- `search_noms(fraccion, pais, nico, skip, limit)` - Buscar Normas Oficiales Mexicanas -- `get_noms_by_id(sysid)` - Obtener NOM por ID -- `search_requisito_previo(fraccion, nico, skip, limit)` - Buscar requisitos previos -- `get_requisito_previo_by_id(sysid)` - Obtener requisito previo por ID - -### PROSEC y Programas de Promoción - -- `search_prosec(fraccion, nico, skip, limit)` - Buscar PROSEC -- `get_prosec_by_id(sysid)` - Obtener PROSEC por ID -- `search_reit(fraccion, nico, skip, limit)` - Buscar REIT -- `get_reit_by_id(sysid)` - Obtener REIT por ID - -### Precios e Impuestos - -- `search_precios_estimados(fraccion, nico, skip, limit)` - Buscar precios estimados -- `get_precio_estimado_by_id(sysid)` - Obtener precio estimado por ID -- `search_ieps(fraccion, nico, skip, limit)` - Buscar IEPS -- `get_ieps_by_id(consecutivo)` - Obtener IEPS por ID - -### Fundamentos y Acuerdos - -- `search_fundamentos_tlc(fraccion, nico, tipat_only, skip, limit)` - Buscar fundamentos TLC -- `get_fundamento_tlc_by_id(sysid)` - Obtener fundamento por ID -- `search_aladi2(fraccion, pais, nico, skip, limit)` - Buscar ALADI2 -- `get_aladi2_by_id(sysid)` - Obtener ALADI2 por ID - -### Cuotas y Cupos - -- `search_cuotas2(fraccion, pais, nico, skip, limit)` - Buscar cuotas compensatorias -- `get_cuotas2_by_id(sysid)` - Obtener cuota por ID -- `search_cupos(fraccion, nico, skip, limit)` - Buscar cupos de importación -- `get_cupos_by_id(sysid)` - Obtener cupo por ID - -### Reglas de Carácter General - -- `search_rcg2(fraccion, nico, skip, limit)` - Buscar RCG2 -- `get_rcg2_by_id(sysid)` - Obtener RCG2 por ID - -### Información General - -- `search_informacion_general(fraccion, nico, skip, limit)` - Buscar información general -- `get_informacion_general_by_id(sysid)` - Obtener información general por ID - -### Vehículos - -- `search_vehiculos_marcas(fraccion, marca, skip, limit)` - Buscar marcas de vehículos -- `get_vehiculos_marcas_by_id(sysid)` - Obtener marca por ID -- `search_vehiculos_modelos(fraccion, marca, modelo, skip, limit)` - Buscar modelos de vehículos -- `get_vehiculos_modelos_by_id(sysid)` - Obtener modelo por ID - -## Configuración - -Requiere las siguientes variables de entorno: - -```bash -SITAR_API_URL=https://api.sitar.example.com -SITAR_API_USER=your_username -SITAR_API_PASSWORD=your_password -``` - -## Servicios Disponibles - -Cada módulo sigue el mismo patrón con métodos `search()` y `get_by_id()`: - -### TLCS - `TLCSService` - -```python -from api.v1.modules.sitar.tlcs import TLCSService -service = TLCSService.get_instance() -await service.search(fraccion="84716001", pais="USA", nico=None, skip=0, limit=100) -await service.get_by_id(sysid=123, fraccion="84716001") -``` - -### Fracciones - `FraccionesService` - -```python -from api.v1.modules.sitar.fracciones import FraccionesService -service = FraccionesService.get_instance() -await service.search(fraccion="84716001", nico=None, skip=0, limit=100) -await service.get_by_id(sysid=123) -``` - -### Fracciones USA - `FraccionesUSAService` - -```python -from api.v1.modules.sitar.fracciones_usa import FraccionesUSAService -service = FraccionesUSAService.get_instance() -await service.search(fraccion="84716001", skip=0, limit=100) -await service.get_by_id(consecutivo=123) -``` - -### Otros servicios disponibles: - -- **RegulacionesService** - Regulaciones y restricciones -- **ProsecService** - PROSEC (Programa de Promoción Sectorial) -- **PreciosEstimadosService** - Precios estimados -- **FundamentosTLCService** - Fundamentos de tratados de libre comercio -- **Aladi2Service** - ALADI2 (Asociación Latinoamericana de Integración) -- **Cuotas2Service** - Cuotas compensatorias -- **CuposService** - Cupos de importación -- **FraccionesAnterioresService** - Historial de fracciones -- **InformacionGeneralService** - Información general de fracciones -- **IepsService** - IEPS (Impuesto Especial sobre Producción y Servicios) -- **NomsService** - Normas Oficiales Mexicanas -- **Rcg2Service** - Reglas de Carácter General -- **ReitService** - Registro de Empresas de Industria Terminal -- **RequisitoPrevioService** - Requisitos previos -- **VehiculosMarcasService** - Marcas de vehículos -- **VehiculosModelosService** - Modelos de vehículos - -```### Desde endpoints asíncronos - -```python -from fastapi import APIRouter, HTTPException -from api.v1.modules.sitar.tlcs import TLCSService - -router = APIRouter() - -@router.get("/tariff/{fraccion}") -async def get_tariff_info(fraccion: str, country: str = "USA"): - """Endpoint que consulta información arancelaria""" - try: - tlcs_service = TLCSService.get_instance() - data = await tlcs_service.search(fraccion=fraccion, pais=country) - return {"success": True, "data": data} - except Exception as e: - raise HTTPException(status_code=500, detail=str(e)) -``` -## Arquitectura - -### Servicio Base Compartido - -Todos los servicios heredan de `SitarAPIBaseService` que maneja: - -- Autenticación con token caching -- Renovación automática de tokens -- Manejo de errores HTTP -- Timeout configurable - -### Singleton Pattern - -Cada servicio usa el patrón singleton para: - -- Compartir la conexión HTTP -- Reutilizar tokens de autenticación -- Evitar múltiples instancias - -### Separación de Responsabilidades - -- **schemas.py**: Modelos Pydantic para validación de datos -- **service.py**: Lógica de negocio y llamadas a API -- **router.py**: Endpoints FastAPI (opcional) -- **__init__.py**: Exportaciones públicas - -## Notas Importantes - -1. **Autenticación**: El token se cachea y renueva automáticamente -2. **Timeouts**: Configurado a 10 segundos por defecto -3. **Límites**: Máximo 1000 registros por consulta -4. **Errores**: Todos los servicios lanzan excepciones httpx en caso de error -5. **Async/Sync**: Los servicios son async, usa `asyncio.run()` en código síncrono - -``` - -Esto expondrá endpoints como: -- `GET /api/v1/sitar/tlcs/` - Buscar TLCS -- `GET /api/v1/sitar/fracciones/` - Buscar fracciones -- etc. - -## Ejemplo Completo - -```python -from api.v1.modules.sitar import SitarAPIService - -async def get_tariff_info(fraccion: str, country: str): - """Obtener información arancelaria completa""" - sitar = SitarAPIService.get_instance() - - try: - # Buscar TLCS - tlcs = await sitar.search_tlcs(fraccion=fraccion, pais=country) - - if tlcs: - first_tlcs = tlcs[0] - return { - "rate": first_tlcs.TASATXT, - "adv_impo": float(first_tlcs.TASA1NUM or 0.0), - "country": first_tlcs.PAIS - } - - # Si no hay TLCS, buscar fracción general - fracciones = await sitar.search_fracciones(fraccion=fraccion) - - if fracciones: - first_frac = fracciones[0] - return { - "rate": first_frac.ADVIMPOTXT, - "adv_impo": float(first_frac.ADVIMPONUM or 0.0), - "description": first_frac.DESCRIPCION - } - - return None - - except Exception as e: - print(f"Error: {e}") - return None -``` -## Manejo de Errores - -El servicio lanza excepciones `httpx.HTTPError` en caso de error: - -```python -import httpx - -try: - tlcs = await sitar.search_tlcs(fraccion="invalid") -except httpx.HTTPStatusError as e: - print(f"HTTP Error: {e.response.status_code}") -except httpx.RequestError as e: - print(f"Request Error: {e}") -except Exception as e: - print(f"General Error: {e}") -``` diff --git a/backend/api/v1/modules/sitar/__init__.py b/backend/api/v1/modules/sitar/__init__.py deleted file mode 100644 index ebea325b..00000000 --- a/backend/api/v1/modules/sitar/__init__.py +++ /dev/null @@ -1,90 +0,0 @@ -""" -SITAR API Module - -Módulo reorganizado para integración con la API externa de SITAR. -Cada recurso tiene su propia carpeta con schemas, service y router. - -Estructura: - backend/api/v1/modules/sitar/ - ├── common/ # Base service compartido - ├── tlcs/ # TLCS (Tratados de Libre Comercio) - ├── fracciones/ # Fracciones arancelarias mexicanas - ├── fracciones_usa/ # Fracciones arancelarias USA - ├── regulaciones/ # Regulaciones y restricciones - ├── prosec/ # PROSEC - ├── precios_estimados/ # Precios estimados - ├── fundamentos_tlc/ # Fundamentos TLC - ├── aladi2/ # ALADI2 - ├── cuotas2/ # Cuotas compensatorias - ├── cupos/ # Cupos de importación - ├── fracciones_anteriores/ # Historial de fracciones - ├── informacion_general/ # Información general - ├── ieps/ # IEPS - ├── noms/ # Normas Oficiales Mexicanas - ├── rcg2/ # Reglas de Carácter General - ├── reit/ # REIT - ├── requisito_previo/ # Requisitos previos - ├── vehiculos_marcas/ # Marcas de vehículos - └── vehiculos_modelos/ # Modelos de vehículos - -Uso: - # Importar servicios específicos - from api.v1.modules.sitar.tlcs import TLCSService - from api.v1.modules.sitar.fracciones import FraccionesService - - # Usar servicios - tlcs_service = TLCSService.get_instance() - data = await tlcs_service.search(fraccion="84716001") - - # Importar routers para FastAPI - from api.v1.modules.sitar.tlcs import router as tlcs_router - from api.v1.modules.sitar.fracciones import router as fracciones_router - - app.include_router(tlcs_router, prefix="/api/v1/sitar/tlcs", tags=["sitar-tlcs"]) -""" - -from .common import SitarAPIBaseService - -# Import all services for convenient access -from .tlcs import TLCSService -from .fracciones import FraccionesService -from .fracciones_usa import FraccionesUSAService -from .regulaciones import RegulacionesService -from .prosec import ProsecService -from .precios_estimados import PreciosEstimadosService -from .fundamentos_tlc import FundamentosTLCService -from .aladi2 import Aladi2Service -from .cuotas2 import Cuotas2Service -from .cupos import CuposService -from .fracciones_anteriores import FraccionesAnterioresService -from .informacion_general import InformacionGeneralService -from .ieps import IepsService -from .noms import NomsService -from .rcg2 import Rcg2Service -from .reit import ReitService -from .requisito_previo import RequisitoPrevioService -from .vehiculos_marcas import VehiculosMarcasService -from .vehiculos_modelos import VehiculosModelosService - -__all__ = [ - "SitarAPIBaseService", - "TLCSService", - "FraccionesService", - "FraccionesUSAService", - "RegulacionesService", - "ProsecService", - "PreciosEstimadosService", - "FundamentosTLCService", - "Aladi2Service", - "Cuotas2Service", - "CuposService", - "FraccionesAnterioresService", - "InformacionGeneralService", - "IepsService", - "NomsService", - "Rcg2Service", - "ReitService", - "RequisitoPrevioService", - "VehiculosMarcasService", - "VehiculosModelosService", -] diff --git a/backend/api/v1/modules/sitar/aladi2/__init__.py b/backend/api/v1/modules/sitar/aladi2/__init__.py deleted file mode 100644 index 2dfbf48c..00000000 --- a/backend/api/v1/modules/sitar/aladi2/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -"""Aladi2 Module""" - -from .schemas import Aladi2Response -from .service import Aladi2Service -from .router import router - -__all__ = ["Aladi2Response", "Aladi2Service", "router"] diff --git a/backend/api/v1/modules/sitar/aladi2/router.py b/backend/api/v1/modules/sitar/aladi2/router.py deleted file mode 100644 index 8e3b625d..00000000 --- a/backend/api/v1/modules/sitar/aladi2/router.py +++ /dev/null @@ -1,37 +0,0 @@ -"""Aladi2 Router""" - -from typing import Optional, List -from fastapi import APIRouter, HTTPException, Query -from fastapi import Depends - -from core.security import get_current_user -from .service import Aladi2Service -from .schemas import Aladi2Response - -router = APIRouter() - - -@router.get("/", response_model=List[Aladi2Response]) -async def search( - fraccion: Optional[str] = Query(None), - pais: Optional[str] = Query(None), - nico: Optional[str] = Query(None), - skip: int = Query(0, ge=0), - limit: int = Query(100, ge=1, le=1000), - current_user: dict = Depends(get_current_user), -): - try: - service = Aladi2Service.get_instance() - return await service.search( - fraccion=fraccion, pais=pais, nico=nico, skip=skip, limit=limit - ) - except Exception as e: - raise HTTPException(status_code=500, detail=str(e)) - - -@router.get("/{sysid}", response_model=Aladi2Response) -async def get_by_id(sysid: int, current_user: dict = Depends(get_current_user)): - try: - return await Aladi2Service.get_instance().get_by_id(sysid) - except Exception as e: - raise HTTPException(status_code=404, detail=str(e)) diff --git a/backend/api/v1/modules/sitar/aladi2/schemas.py b/backend/api/v1/modules/sitar/aladi2/schemas.py deleted file mode 100644 index ac136931..00000000 --- a/backend/api/v1/modules/sitar/aladi2/schemas.py +++ /dev/null @@ -1,23 +0,0 @@ -"""ALADI2 Schemas""" - -from typing import Optional -from pydantic import BaseModel, ConfigDict, Field - - -class Aladi2Response(BaseModel): - """ALADI2 (Asociación Latinoamericana de Integración)""" - - FRACCION: Optional[str] = Field(None, max_length=10) - ACUERDO: Optional[str] = Field(None, max_length=49) - PAIS: Optional[str] = Field(None, max_length=3) - TASATXT: Optional[str] = Field(None, max_length=19) - TASANUM: Optional[str] = None - TASACALCULADA: Optional[str] = Field(None, max_length=19) - TIPOTASA: Optional[int] = None - DOF: Optional[str] = Field(None, max_length=8) - NOTAS: Optional[str] = Field(None, max_length=499) - OBSERVACIONES: Optional[str] = None - NICO: Optional[str] = Field(None, max_length=2) - SYSID: int - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/sitar/aladi2/service.py b/backend/api/v1/modules/sitar/aladi2/service.py deleted file mode 100644 index dccf24b7..00000000 --- a/backend/api/v1/modules/sitar/aladi2/service.py +++ /dev/null @@ -1,37 +0,0 @@ -"""Aladi2 Service""" - -from typing import Optional, List -from ..common import SitarAPIBaseService -from .schemas import Aladi2Response - - -class Aladi2Service(SitarAPIBaseService): - _instance: Optional["Aladi2Service"] = None - - @classmethod - def get_instance(cls) -> "Aladi2Service": - if cls._instance is None: - cls._instance = cls() - return cls._instance - - async def search( - self, - fraccion: Optional[str] = None, - pais: Optional[str] = None, - nico: Optional[str] = None, - skip: int = 0, - limit: int = 100, - ) -> List[Aladi2Response]: - params = {"skip": skip, "limit": min(limit, 1000)} - if fraccion: - params["fraccion"] = fraccion - if pais: - params["pais"] = pais - if nico: - params["nico"] = nico - data = await self._make_request("GET", "/api/v1/aladi2/", params=params) - return [Aladi2Response(**item) for item in data] - - async def get_by_id(self, sysid: int) -> Aladi2Response: - data = await self._make_request("GET", f"/api/v1/aladi2/{sysid}") - return Aladi2Response(**data) diff --git a/backend/api/v1/modules/sitar/common/__init__.py b/backend/api/v1/modules/sitar/common/__init__.py deleted file mode 100644 index 974c7610..00000000 --- a/backend/api/v1/modules/sitar/common/__init__.py +++ /dev/null @@ -1,5 +0,0 @@ -"""SITAR API Common Module""" - -from .base_service import SitarAPIBaseService - -__all__ = ["SitarAPIBaseService"] diff --git a/backend/api/v1/modules/sitar/common/base_service.py b/backend/api/v1/modules/sitar/common/base_service.py deleted file mode 100644 index 05d104ca..00000000 --- a/backend/api/v1/modules/sitar/common/base_service.py +++ /dev/null @@ -1,185 +0,0 @@ -""" -SITAR API Base Service - -Base service class for SITAR API authentication and HTTP requests. -All specific resource services inherit from this. -""" - -import os -from typing import Optional, Dict, Any, List, Tuple -from datetime import datetime, timedelta -import httpx - - -class SitarAPIBaseService: - """Base service for SITAR API integration with authentication""" - - _token: Optional[str] = None - _token_expires: Optional[datetime] = None - - @staticmethod - def _parse_paginated_list_and_total(data: Any) -> Tuple[List[Any], int]: - """ - Paginated list endpoints (fracciones, fracciones-usa) return: - { "data": [...], "total", "page", "limit", "total_pages" }. - Older responses may be a plain JSON array; then total is len(rows) for that page only. - """ - if isinstance(data, list): - return data, len(data) - if isinstance(data, dict) and isinstance(data.get("data"), list): - rows = data["data"] - raw_total = data.get("total") - total = int(raw_total) if raw_total is not None else len(rows) - return rows, total - raise ValueError( - f"Unexpected SITAR list response shape: {type(data).__name__}" - ) - - @staticmethod - def _unwrap_paginated_list(data: Any) -> List[Any]: - rows, _ = SitarAPIBaseService._parse_paginated_list_and_total(data) - return rows - - def __init__(self): - """Initialize base service with API credentials""" - self.base_url = os.getenv("SITAR_API_URL") - self.username = os.getenv("SITAR_API_USER") - self.password = os.getenv("SITAR_API_PASSWORD") - self.timeout = 30.0 - - if not all([self.base_url, self.username, self.password]): - raise ValueError( - "Missing SITAR API configuration. " - "Set SITAR_API_URL, SITAR_API_USER, and SITAR_API_PASSWORD environment variables." - ) - - async def _get_token(self) -> str: - """ - Get authentication token, refreshing if necessary - - Returns: - str: Bearer token for API authentication - - Raises: - httpx.HTTPError: If authentication fails - """ - # Return cached token if still valid - if self._token and self._token_expires and datetime.now() < self._token_expires: - return self._token - - # Authenticate and get new token - login_url = f"{self.base_url}/fractions/api/v1/auth/login" - payload = {"username": self.username, "password": self.password} - - async with httpx.AsyncClient(timeout=self.timeout) as client: - response = await client.post(login_url, json=payload) - response.raise_for_status() - - data = response.json() - self._token = data.get("access_token") or data.get("token") - - if not self._token: - raise ValueError("No token received from SITAR API") - - # Set token expiration (assume 1 hour if not specified) - self._token_expires = datetime.now() + timedelta(hours=1) - - return self._token - - async def _make_request( - self, - method: str, - endpoint: str, - params: Optional[Dict[str, Any]] = None, - json_data: Optional[Dict[str, Any]] = None, - ) -> Any: - """ - Make authenticated request to SITAR API - - Args: - method: HTTP method (GET, POST, etc.) - endpoint: API endpoint path - params: Query parameters - json_data: JSON body data - - Returns: - JSON response data - - Raises: - httpx.HTTPError: If request fails - """ - token = await self._get_token() - # Ensure no double slash between fractures and endpoint - endpoint = endpoint.lstrip("/") - url = f"{self.base_url}/fractions/{endpoint}" - headers = { - "Authorization": f"Bearer {token}", - "Content-Type": "application/json", - } - - async with httpx.AsyncClient(timeout=self.timeout) as client: - response = await client.request( - method=method, - url=url, - params=params, - json=json_data, - headers=headers, - ) - response.raise_for_status() - - # DEBUG LOGGING for SITAR inspection - if "fracciones" in url: - import logging - logger = logging.getLogger(__name__) - try: - data = response.json() - return data - except Exception: - logger.error(f"Error parsing SITAR API Response Body: {response.text}") - pass - - return response.json() - - def _get_token_sync(self) -> str: - """Same token cache as async path; safe for sync validators (no running asyncio loop).""" - if self._token and self._token_expires and datetime.now() < self._token_expires: - return self._token - - login_url = f"{self.base_url}/fractions/api/v1/auth/login" - payload = {"username": self.username, "password": self.password} - - with httpx.Client(timeout=self.timeout) as client: - response = client.post(login_url, json=payload) - response.raise_for_status() - data = response.json() - token = data.get("access_token") or data.get("token") - if not token: - raise ValueError("No token received from SITAR API") - self._token = token - self._token_expires = datetime.now() + timedelta(hours=1) - return token - - def _make_request_sync( - self, - method: str, - endpoint: str, - params: Optional[Dict[str, Any]] = None, - json_data: Optional[Dict[str, Any]] = None, - ) -> Any: - token = self._get_token_sync() - endpoint = endpoint.lstrip("/") - url = f"{self.base_url}/fractions/{endpoint}" - headers = { - "Authorization": f"Bearer {token}", - "Content-Type": "application/json", - } - with httpx.Client(timeout=self.timeout) as client: - response = client.request( - method=method, - url=url, - params=params, - json=json_data, - headers=headers, - ) - response.raise_for_status() - return response.json() diff --git a/backend/api/v1/modules/sitar/cuotas2/__init__.py b/backend/api/v1/modules/sitar/cuotas2/__init__.py deleted file mode 100644 index 9576083d..00000000 --- a/backend/api/v1/modules/sitar/cuotas2/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -"""Cuotas2 Module""" - -from .schemas import Cuotas2Response -from .service import Cuotas2Service -from .router import router - -__all__ = ["Cuotas2Response", "Cuotas2Service", "router"] diff --git a/backend/api/v1/modules/sitar/cuotas2/router.py b/backend/api/v1/modules/sitar/cuotas2/router.py deleted file mode 100644 index e206b152..00000000 --- a/backend/api/v1/modules/sitar/cuotas2/router.py +++ /dev/null @@ -1,35 +0,0 @@ -"""Cuotas2 Router""" - -from typing import Optional, List -from fastapi import APIRouter, HTTPException, Query, Depends -from core.security import get_current_user -from .service import Cuotas2Service -from .schemas import Cuotas2Response - -router = APIRouter() - - -@router.get("/", response_model=List[Cuotas2Response]) -async def search( - fraccion: Optional[str] = Query(None), - pais: Optional[str] = Query(None), - nico: Optional[str] = Query(None), - skip: int = Query(0, ge=0), - limit: int = Query(100, ge=1, le=1000), - current_user: dict = Depends(get_current_user), -): - try: - service = Cuotas2Service.get_instance() - return await service.search( - fraccion=fraccion, pais=pais, nico=nico, skip=skip, limit=limit - ) - except Exception as e: - raise HTTPException(status_code=500, detail=str(e)) - - -@router.get("/{sysid}", response_model=Cuotas2Response) -async def get_by_id(sysid: int, current_user: dict = Depends(get_current_user)): - try: - return await Cuotas2Service.get_instance().get_by_id(sysid) - except Exception as e: - raise HTTPException(status_code=404, detail=str(e)) diff --git a/backend/api/v1/modules/sitar/cuotas2/schemas.py b/backend/api/v1/modules/sitar/cuotas2/schemas.py deleted file mode 100644 index 74ca8ca2..00000000 --- a/backend/api/v1/modules/sitar/cuotas2/schemas.py +++ /dev/null @@ -1,29 +0,0 @@ -"""Cuotas2 Schemas""" - -from typing import Optional -from pydantic import BaseModel, ConfigDict, Field - - -class Cuotas2Response(BaseModel): - """Cuotas compensatorias""" - - FRACCION: Optional[str] = Field(None, max_length=10) - PRODUCTO: Optional[str] = Field(None, max_length=300) - PAIS: Optional[str] = Field(None, max_length=3) - DOF: Optional[str] = Field(None, max_length=8) - DOF2: Optional[str] = Field(None, max_length=149) - EMPRESA: Optional[str] = Field(None, max_length=100) - INCOTERM: Optional[str] = Field(None, max_length=10) - DOLARES: Optional[str] = None - CLAVEMONEDA: Optional[str] = Field(None, max_length=3) - CLAVEUM: Optional[str] = Field(None, max_length=2) - PORCENTAJE: Optional[str] = None - DESCRIPCIONDOLARES: Optional[str] = Field(None, max_length=50) - NOTAS: Optional[str] = None - ESTATUS: Optional[str] = Field(None, max_length=19) - NUMERORESOLUCION: Optional[int] = None - RESOLUCION: Optional[str] = Field(None, max_length=150) - NICO: Optional[str] = Field("", max_length=2) - SYSID: int - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/sitar/cuotas2/service.py b/backend/api/v1/modules/sitar/cuotas2/service.py deleted file mode 100644 index 9edbbd06..00000000 --- a/backend/api/v1/modules/sitar/cuotas2/service.py +++ /dev/null @@ -1,37 +0,0 @@ -"""Cuotas2 Service""" - -from typing import Optional, List -from ..common import SitarAPIBaseService -from .schemas import Cuotas2Response - - -class Cuotas2Service(SitarAPIBaseService): - _instance: Optional["Cuotas2Service"] = None - - @classmethod - def get_instance(cls) -> "Cuotas2Service": - if cls._instance is None: - cls._instance = cls() - return cls._instance - - async def search( - self, - fraccion: Optional[str] = None, - pais: Optional[str] = None, - nico: Optional[str] = None, - skip: int = 0, - limit: int = 100, - ) -> List[Cuotas2Response]: - params = {"skip": skip, "limit": min(limit, 1000)} - if fraccion: - params["fraccion"] = fraccion - if pais: - params["pais"] = pais - if nico: - params["nico"] = nico - data = await self._make_request("GET", "/api/v1/cuotas2/", params=params) - return [Cuotas2Response(**item) for item in data] - - async def get_by_id(self, sysid: int) -> Cuotas2Response: - data = await self._make_request("GET", f"/api/v1/cuotas2/{sysid}") - return Cuotas2Response(**data) diff --git a/backend/api/v1/modules/sitar/cupos/__init__.py b/backend/api/v1/modules/sitar/cupos/__init__.py deleted file mode 100644 index d415348d..00000000 --- a/backend/api/v1/modules/sitar/cupos/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -"""Cupos Module""" - -from .schemas import CuposResponse -from .service import CuposService -from .router import router - -__all__ = ["CuposResponse", "CuposService", "router"] diff --git a/backend/api/v1/modules/sitar/cupos/router.py b/backend/api/v1/modules/sitar/cupos/router.py deleted file mode 100644 index b2da9438..00000000 --- a/backend/api/v1/modules/sitar/cupos/router.py +++ /dev/null @@ -1,34 +0,0 @@ -"""Cupos Router""" - -from typing import Optional, List -from fastapi import APIRouter, HTTPException, Query, Depends -from core.security import get_current_user -from .service import CuposService -from .schemas import CuposResponse - -router = APIRouter() - - -@router.get("/", response_model=List[CuposResponse]) -async def search( - fraccion: Optional[str] = Query(None), - nico: Optional[str] = Query(None), - skip: int = Query(0, ge=0), - limit: int = Query(100, ge=1, le=1000), - current_user: dict = Depends(get_current_user), -): - try: - service = CuposService.get_instance() - return await service.search( - fraccion=fraccion, nico=nico, skip=skip, limit=limit - ) - except Exception as e: - raise HTTPException(status_code=500, detail=str(e)) - - -@router.get("/{sysid}", response_model=CuposResponse) -async def get_by_id(sysid: int, current_user: dict = Depends(get_current_user)): - try: - return await CuposService.get_instance().get_by_id(sysid) - except Exception as e: - raise HTTPException(status_code=404, detail=str(e)) diff --git a/backend/api/v1/modules/sitar/cupos/schemas.py b/backend/api/v1/modules/sitar/cupos/schemas.py deleted file mode 100644 index d607d6d5..00000000 --- a/backend/api/v1/modules/sitar/cupos/schemas.py +++ /dev/null @@ -1,24 +0,0 @@ -"""Cupos Schemas""" - -from typing import Optional -from pydantic import BaseModel, ConfigDict, Field - - -class CuposResponse(BaseModel): - """Cupos de importación""" - - FRACCION: Optional[str] = Field(None, max_length=10) - NUMEROCUPO: Optional[int] = None - DOF: Optional[str] = Field(None, max_length=8) - PERMISO: Optional[str] = Field(None, max_length=2) - CONDICION: Optional[str] = Field(None, max_length=199) - VIGENCIA: Optional[str] = Field(None, max_length=8) - PAIS: Optional[str] = Field(None, max_length=5) - OBSERVACIONES: Optional[str] = Field(None, max_length=5000) - ADVIMPO: Optional[str] = Field(None, max_length=20) - ADVEXPO: Optional[str] = Field(None, max_length=20) - TIPOOPERACION: Optional[int] = None - NICO: Optional[str] = Field("", max_length=2) - SYSID: int - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/sitar/cupos/service.py b/backend/api/v1/modules/sitar/cupos/service.py deleted file mode 100644 index dd308ffe..00000000 --- a/backend/api/v1/modules/sitar/cupos/service.py +++ /dev/null @@ -1,34 +0,0 @@ -"""Cupos Service""" - -from typing import Optional, List -from ..common import SitarAPIBaseService -from .schemas import CuposResponse - - -class CuposService(SitarAPIBaseService): - _instance: Optional["CuposService"] = None - - @classmethod - def get_instance(cls) -> "CuposService": - if cls._instance is None: - cls._instance = cls() - return cls._instance - - async def search( - self, - fraccion: Optional[str] = None, - nico: Optional[str] = None, - skip: int = 0, - limit: int = 100, - ) -> List[CuposResponse]: - params = {"skip": skip, "limit": min(limit, 1000)} - if fraccion: - params["fraccion"] = fraccion - if nico: - params["nico"] = nico - data = await self._make_request("GET", "/api/v1/cupos/", params=params) - return [CuposResponse(**item) for item in data] - - async def get_by_id(self, sysid: int) -> CuposResponse: - data = await self._make_request("GET", f"/api/v1/cupos/{sysid}") - return CuposResponse(**data) diff --git a/backend/api/v1/modules/sitar/fracciones/__init__.py b/backend/api/v1/modules/sitar/fracciones/__init__.py deleted file mode 100644 index c2251ff3..00000000 --- a/backend/api/v1/modules/sitar/fracciones/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -"""Fracciones Module""" - -from .schemas import FraccionesResponse -from .service import FraccionesService -from .router import router - -__all__ = ["FraccionesResponse", "FraccionesService", "router"] diff --git a/backend/api/v1/modules/sitar/fracciones/router.py b/backend/api/v1/modules/sitar/fracciones/router.py deleted file mode 100644 index 48e88c27..00000000 --- a/backend/api/v1/modules/sitar/fracciones/router.py +++ /dev/null @@ -1,43 +0,0 @@ -"""Fracciones Router""" - -from typing import Optional, List -from fastapi import APIRouter, HTTPException, Query, Depends -from core.security import get_current_user -from .service import FraccionesService -from .schemas import FraccionesResponse - -router = APIRouter() - - -@router.get("/", response_model=List[FraccionesResponse]) -async def search_fracciones( - fraccion: Optional[str] = Query(None, description="Fracción arancelaria"), - nico: Optional[str] = Query(None, description="NICO"), - skip: int = Query(0, ge=0, description="Registros a saltar"), - limit: int = Query(100, ge=1, le=1000, description="Máximo de registros"), - current_user: dict = Depends(get_current_user), -): - """Search Mexican tariff fractions""" - try: - service = FraccionesService.get_instance() - return await service.search( - fraccion=fraccion, nico=nico, skip=skip, limit=limit - ) - except Exception as e: - raise HTTPException( - status_code=500, detail=f"Error fetching Fracciones data: {str(e)}" - ) - - -@router.get("/{sysid}", response_model=FraccionesResponse) -async def get_fraccion_by_id( - sysid: int, current_user: dict = Depends(get_current_user) -): - """Get single Fraccion record by SYSID""" - try: - service = FraccionesService.get_instance() - return await service.get_by_id(sysid) - except Exception as e: - raise HTTPException( - status_code=404, detail=f"Fraccion record not found: {str(e)}" - ) diff --git a/backend/api/v1/modules/sitar/fracciones/schemas.py b/backend/api/v1/modules/sitar/fracciones/schemas.py deleted file mode 100644 index 1e01b4af..00000000 --- a/backend/api/v1/modules/sitar/fracciones/schemas.py +++ /dev/null @@ -1,38 +0,0 @@ -"""Fracciones Schemas""" - -from decimal import Decimal -from typing import Optional -from pydantic import BaseModel, ConfigDict, Field - - -class FraccionesResponse(BaseModel): - """Fracciones arancelarias mexicanas""" - - FRACCION: Optional[str] = Field(None, max_length=10) - FRACCIONPUNTO: Optional[str] = Field(None, max_length=10) - DESCRIPCION: Optional[str] = None - UMCLAVE: Optional[str] = Field(None, max_length=2) - UMABREVIACION: Optional[str] = Field(None, max_length=4) - ADVIMPOTXT: Optional[str] = Field(None, max_length=19) - ADVIMPONUM: Optional[Decimal] = None - TIPOTASAADVIMPO: Optional[int] = None - ADVEXPOTXT: Optional[str] = Field(None, max_length=19) - ADVEXPONUM: Optional[Decimal] = None - TIPOTASAADVEXPO: Optional[int] = None - TASAIVAFRANJA: Optional[Decimal] = None - TASAIVAINTERIOR: Optional[Decimal] = None - TASAISAN: Optional[Decimal] = None - ARANCELMIXTO: Optional[str] = Field(None, max_length=1) - TASAMIXTA: Optional[Decimal] = None - DOF: Optional[str] = Field(None, max_length=8) - NOTAS: Optional[str] = None - HISTORICO: Optional[str] = None - ARANCELESPECIFICO: Optional[str] = Field(None, max_length=1) - TIPOVEHICULO: Optional[str] = Field(None, max_length=1) - APLICAISAN: Optional[str] = Field(None, max_length=1) - APLICAIEPS: Optional[str] = Field(None, max_length=1) - NIVEL: Optional[int] = None - NICO: Optional[str] = Field(None, max_length=14) - SYSID: Optional[int] = None - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/sitar/fracciones/service.py b/backend/api/v1/modules/sitar/fracciones/service.py deleted file mode 100644 index cf848f1d..00000000 --- a/backend/api/v1/modules/sitar/fracciones/service.py +++ /dev/null @@ -1,91 +0,0 @@ -"""Fracciones Service""" - -import asyncio -from typing import Optional, List, Tuple -from ..common import SitarAPIBaseService -from .schemas import FraccionesResponse - - -class FraccionesService(SitarAPIBaseService): - """Service for Fracciones operations""" - - _instance: Optional["FraccionesService"] = None - - @classmethod - def get_instance(cls) -> "FraccionesService": - """Get singleton instance""" - if cls._instance is None: - cls._instance = cls() - return cls._instance - - async def search_with_total( - self, - fraccion: Optional[str] = None, - nico: Optional[str] = None, - description: Optional[str] = None, - nivel: Optional[int] = None, - skip: int = 0, - limit: int = 100, - ) -> Tuple[List[FraccionesResponse], int]: - """Search Mexican tariff fractions; total matches SITAR PaginatedFraccionesResponse.total.""" - params = {"skip": skip, "limit": min(limit, 1000)} - if fraccion: - params["fraccion"] = fraccion - if nico: - params["nico"] = nico - if description: - params["descripcion"] = description - if nivel is not None: - params["nivel"] = nivel - - raw = await self._make_request("GET", "/api/v1/fracciones/", params=params) - rows, total = self._parse_paginated_list_and_total(raw) - return [FraccionesResponse(**item) for item in rows], total - - async def search( - self, - fraccion: Optional[str] = None, - nico: Optional[str] = None, - description: Optional[str] = None, - nivel: Optional[int] = None, - skip: int = 0, - limit: int = 100, - ) -> List[FraccionesResponse]: - """Search Mexican tariff fractions""" - items, _ = await self.search_with_total( - fraccion=fraccion, - nico=nico, - description=description, - nivel=nivel, - skip=skip, - limit=limit, - ) - return items - - async def get_by_id(self, sysid: int) -> FraccionesResponse: - """Get single Fraccion record by SYSID""" - data = await self._make_request("GET", f"/api/v1/fracciones/{sysid}") - return FraccionesResponse(**data) - - @classmethod - def search_sync( - cls, - fraccion: Optional[str] = None, - nico: Optional[str] = None, - description: Optional[str] = None, - nivel: Optional[int] = None, - skip: int = 0, - limit: int = 100, - ) -> List[FraccionesResponse]: - """Search Mexican tariff fractions (sync wrapper for use in Celery/sync context).""" - service = cls.get_instance() - return asyncio.run( - service.search( - fraccion=fraccion, - nico=nico, - description=description, - nivel=nivel, - skip=skip, - limit=limit, - ) - ) diff --git a/backend/api/v1/modules/sitar/fracciones_anteriores/__init__.py b/backend/api/v1/modules/sitar/fracciones_anteriores/__init__.py deleted file mode 100644 index 94237c91..00000000 --- a/backend/api/v1/modules/sitar/fracciones_anteriores/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -"""FraccionesAnteriores Module""" - -from .schemas import FraccionesAnterioresResponse -from .service import FraccionesAnterioresService -from .router import router - -__all__ = ["FraccionesAnterioresResponse", "FraccionesAnterioresService", "router"] diff --git a/backend/api/v1/modules/sitar/fracciones_anteriores/router.py b/backend/api/v1/modules/sitar/fracciones_anteriores/router.py deleted file mode 100644 index 387a741c..00000000 --- a/backend/api/v1/modules/sitar/fracciones_anteriores/router.py +++ /dev/null @@ -1,37 +0,0 @@ -"""FraccionesAnteriores Router""" - -from typing import Optional, List -from fastapi import APIRouter, HTTPException, Query, Depends -from core.security import get_current_user -from .service import FraccionesAnterioresService -from .schemas import FraccionesAnterioresResponse - -router = APIRouter() - - -@router.get("/", response_model=List[FraccionesAnterioresResponse]) -async def search( - fraccion_actual: Optional[str] = Query(None), - fraccion_anterior: Optional[str] = Query(None), - skip: int = Query(0, ge=0), - limit: int = Query(100, ge=1, le=1000), - current_user: dict = Depends(get_current_user), -): - try: - service = FraccionesAnterioresService.get_instance() - return await service.search( - fraccion_actual=fraccion_actual, - fraccion_anterior=fraccion_anterior, - skip=skip, - limit=limit, - ) - except Exception as e: - raise HTTPException(status_code=500, detail=str(e)) - - -@router.get("/{sysid}", response_model=FraccionesAnterioresResponse) -async def get_by_id(sysid: int, current_user: dict = Depends(get_current_user)): - try: - return await FraccionesAnterioresService.get_instance().get_by_id(sysid) - except Exception as e: - raise HTTPException(status_code=404, detail=str(e)) diff --git a/backend/api/v1/modules/sitar/fracciones_anteriores/schemas.py b/backend/api/v1/modules/sitar/fracciones_anteriores/schemas.py deleted file mode 100644 index 20b4757d..00000000 --- a/backend/api/v1/modules/sitar/fracciones_anteriores/schemas.py +++ /dev/null @@ -1,14 +0,0 @@ -"""Fracciones Anteriores Schemas""" - -from typing import Optional -from pydantic import BaseModel, ConfigDict, Field - - -class FraccionesAnterioresResponse(BaseModel): - """Fracciones arancelarias anteriores (histórico)""" - - FRACCIONACTUAL: Optional[str] = Field(None, max_length=10) - FRACCIONANTERIOR: Optional[str] = Field(None, max_length=10) - SYSID: int - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/sitar/fracciones_anteriores/service.py b/backend/api/v1/modules/sitar/fracciones_anteriores/service.py deleted file mode 100644 index 1002c805..00000000 --- a/backend/api/v1/modules/sitar/fracciones_anteriores/service.py +++ /dev/null @@ -1,56 +0,0 @@ -"""FraccionesAnteriores Service""" - -import asyncio -from typing import Optional, List -from ..common import SitarAPIBaseService -from .schemas import FraccionesAnterioresResponse - - -class FraccionesAnterioresService(SitarAPIBaseService): - _instance: Optional["FraccionesAnterioresService"] = None - - @classmethod - def get_instance(cls) -> "FraccionesAnterioresService": - if cls._instance is None: - cls._instance = cls() - return cls._instance - - async def search( - self, - fraccion_actual: Optional[str] = None, - fraccion_anterior: Optional[str] = None, - skip: int = 0, - limit: int = 100, - ) -> List[FraccionesAnterioresResponse]: - params = {"skip": skip, "limit": min(limit, 1000)} - if fraccion_actual: - params["fraccion_actual"] = fraccion_actual - if fraccion_anterior: - params["fraccion_anterior"] = fraccion_anterior - data = await self._make_request( - "GET", "/api/v1/fracciones-anteriores/", params=params - ) - return [FraccionesAnterioresResponse(**item) for item in data] - - async def get_by_id(self, sysid: int) -> FraccionesAnterioresResponse: - data = await self._make_request("GET", f"/api/v1/fracciones-anteriores/{sysid}") - return FraccionesAnterioresResponse(**data) - - @classmethod - def search_sync( - cls, - fraccion_actual: Optional[str] = None, - fraccion_anterior: Optional[str] = None, - skip: int = 0, - limit: int = 100, - ) -> List[FraccionesAnterioresResponse]: - """Search historical fractions (sync wrapper for use in Celery/sync context).""" - service = cls.get_instance() - return asyncio.run( - service.search( - fraccion_actual=fraccion_actual, - fraccion_anterior=fraccion_anterior, - skip=skip, - limit=limit, - ) - ) diff --git a/backend/api/v1/modules/sitar/fracciones_usa/__init__.py b/backend/api/v1/modules/sitar/fracciones_usa/__init__.py deleted file mode 100644 index 57c44d27..00000000 --- a/backend/api/v1/modules/sitar/fracciones_usa/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -"""Fracciones USA Module""" - -from .schemas import FraccionesUSAResponse -from .service import FraccionesUSAService -from .router import router - -__all__ = ["FraccionesUSAResponse", "FraccionesUSAService", "router"] diff --git a/backend/api/v1/modules/sitar/fracciones_usa/catalog_resolve.py b/backend/api/v1/modules/sitar/fracciones_usa/catalog_resolve.py deleted file mode 100644 index 087ba1c5..00000000 --- a/backend/api/v1/modules/sitar/fracciones_usa/catalog_resolve.py +++ /dev/null @@ -1,124 +0,0 @@ -""" -Resolve American (US) tariff fraction codes against SITAR fracciones-usa. -Used by sync validators and CSV/layout enrichment (no asyncio.run). -""" - -from __future__ import annotations - -import re -from typing import List, Optional, Tuple - -from .schemas import FraccionesUSAResponse -from .service import FraccionesUSAService - - -def normalize_american_fraction_code_candidates(raw_code: str) -> List[str]: - """ - Map user input to candidate strings to query SITAR (dotted HTS vs digits-only). - Mirrors logic in items imports/validators/common.py. - """ - normalized_raw = (raw_code or "").strip() - if not normalized_raw: - return [] - - digits_only = re.sub(r"[.\s\-]", "", normalized_raw) - - candidates: List[str] = [] - candidates.append(normalized_raw) - - if len(digits_only) == 10: - candidates.append( - f"{digits_only[:4]}.{digits_only[4:6]}.{digits_only[6:8]}.{digits_only[8:10]}" - ) - elif len(digits_only) == 8: - candidates.append( - f"{digits_only[:4]}.{digits_only[4:6]}.{digits_only[6:8]}" - ) - - candidates.append(digits_only) - - seen: set[str] = set() - deduped: List[str] = [] - for c in candidates: - if not c or c in seen: - continue - seen.add(c) - deduped.append(c) - return deduped - - -def _digits(code: str) -> str: - return re.sub(r"[.\s\-]", "", code or "") - - -def canonical_code_from_sitar_row(item: FraccionesUSAResponse) -> str: - """Same precedence as USTariffFractionMapper.to_domain for stored line value.""" - return ( - (item.FRACCION_CON_PUNTO or item.FRACCION_MOSTRAR or item.FRACCION_SIN_PUNTO or "") - .strip() - ) - - -def resolve_american_fraction_from_sitar( - raw_code: str, -) -> Optional[Tuple[FraccionesUSAResponse, str]]: - """ - Returns (SITAR row, canonical_code) if found, else None. - canonical_code is suitable for persisting on line customs / exports. - """ - if not (raw_code or "").strip(): - return None - - target_digits = _digits(raw_code) - if not target_digits: - return None - - candidates = normalize_american_fraction_code_candidates(raw_code) - tried_searches: set[tuple[Optional[str], Optional[str]]] = set() - - for cand in candidates: - clean = cand.replace(".", "").replace("-", "").replace(" ", "") - if clean.isdigit() and len(clean) >= 4: - search_frac, search_desc = cand, None - else: - search_frac, search_desc = None, cand - - key = (search_frac, search_desc) - if key in tried_searches: - continue - tried_searches.add(key) - - rows = FraccionesUSAService.search_sync( - fraccion=search_frac, - descripcion=search_desc, - skip=0, - limit=100, - ) - for row in rows: - canon = canonical_code_from_sitar_row(row) - if _digits(canon) == target_digits: - return (row, canon) - - return None - - -MAX_AMERICAN_FRACTION_DB_LEN = 16 - - -def store_canonical_american_code(canon: str) -> str: - """Fit SITAR canonical HTS string into DB column (VARCHAR 16).""" - s = (canon or "").strip() - if len(s) <= MAX_AMERICAN_FRACTION_DB_LEN: - return s - digits = re.sub(r"[.\s\-]", "", s) - return digits[:MAX_AMERICAN_FRACTION_DB_LEN] - - -def american_fraction_ad_valorem_from_row(item: FraccionesUSAResponse) -> Optional[float]: - """Parse TARIFA1 like USTariffFractionMapper.""" - if not item.TARIFA1: - return None - try: - return float(str(item.TARIFA1).replace("%", "").strip()) - except (ValueError, TypeError): - return None diff --git a/backend/api/v1/modules/sitar/fracciones_usa/router.py b/backend/api/v1/modules/sitar/fracciones_usa/router.py deleted file mode 100644 index e4c79a8e..00000000 --- a/backend/api/v1/modules/sitar/fracciones_usa/router.py +++ /dev/null @@ -1,41 +0,0 @@ -"""Fracciones USA Router""" - -from typing import Optional, List -from fastapi import APIRouter, HTTPException, Query, Depends -from core.security import get_current_user -from .service import FraccionesUSAService -from .schemas import FraccionesUSAResponse - -router = APIRouter() - - -@router.get("/", response_model=List[FraccionesUSAResponse]) -async def search_fracciones_usa( - fraccion: Optional[str] = Query(None, description="Fracción arancelaria USA"), - skip: int = Query(0, ge=0, description="Registros a saltar"), - limit: int = Query(100, ge=1, le=1000, description="Máximo de registros"), - current_user: dict = Depends(get_current_user), -): - """Search USA tariff fractions""" - try: - service = FraccionesUSAService.get_instance() - return await service.search(fraccion=fraccion, skip=skip, limit=limit) - except Exception as e: - raise HTTPException( - status_code=500, detail=f"Error fetching USA Fracciones data: {str(e)}" - ) - - -@router.get("/{consecutivo}", response_model=FraccionesUSAResponse) -async def get_fraccion_usa_by_id( - consecutivo: int, - current_user: dict = Depends(get_current_user), -): - """Get single USA Fraccion record by CONSECUTIVO""" - try: - service = FraccionesUSAService.get_instance() - return await service.get_by_id(consecutivo) - except Exception as e: - raise HTTPException( - status_code=404, detail=f"USA Fraccion record not found: {str(e)}" - ) diff --git a/backend/api/v1/modules/sitar/fracciones_usa/schemas.py b/backend/api/v1/modules/sitar/fracciones_usa/schemas.py deleted file mode 100644 index 36e02fa3..00000000 --- a/backend/api/v1/modules/sitar/fracciones_usa/schemas.py +++ /dev/null @@ -1,23 +0,0 @@ -"""Fracciones USA Schemas""" - -from typing import Optional -from pydantic import BaseModel, ConfigDict, Field - - -class FraccionesUSAResponse(BaseModel): - """USA tariff fractions""" - - FRACCION_SIN_PUNTO: Optional[str] = None - FRACCION_CON_PUNTO: Optional[str] = None - FRACCION_MOSTRAR: Optional[str] = None - ESPECIFICO: Optional[str] = None - NIVEL: Optional[int] = None - DESCRIPCION: Optional[str] = None - UNIDADCANTIDAD: Optional[str] = None - TARIFA1: Optional[str] = None - TLC: Optional[str] = None - TARIFA2: Optional[str] = None - NOTAS: Optional[str] = None - CONSECUTIVO: int - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/sitar/fracciones_usa/service.py b/backend/api/v1/modules/sitar/fracciones_usa/service.py deleted file mode 100644 index 56608c61..00000000 --- a/backend/api/v1/modules/sitar/fracciones_usa/service.py +++ /dev/null @@ -1,93 +0,0 @@ -"""Fracciones USA Service""" - -import logging -from typing import Optional, List, Tuple - -from ..common import SitarAPIBaseService -from .schemas import FraccionesUSAResponse - -logger = logging.getLogger(__name__) - - - -class FraccionesUSAService(SitarAPIBaseService): - """Service for USA Fracciones operations""" - - _instance: Optional["FraccionesUSAService"] = None - - @classmethod - def get_instance(cls) -> "FraccionesUSAService": - """Get singleton instance""" - if cls._instance is None: - cls._instance = cls() - return cls._instance - - async def search_with_total( - self, - fraccion: Optional[str] = None, - descripcion: Optional[str] = None, - skip: int = 0, - limit: int = 100, - ) -> Tuple[List[FraccionesUSAResponse], int]: - """Search USA tariff fractions; total matches SITAR PaginatedFraccionesUSAResponse.total.""" - params = {"skip": skip, "limit": min(limit, 1000)} - if fraccion: - params["fraccion"] = fraccion - if descripcion: - params["descripcion"] = descripcion - - raw = await self._make_request("GET", "api/v1/fracciones-usa/", params=params) - rows, total = self._parse_paginated_list_and_total(raw) - return [FraccionesUSAResponse(**item) for item in rows], total - - async def search( - self, - fraccion: Optional[str] = None, - descripcion: Optional[str] = None, - skip: int = 0, - limit: int = 100, - ) -> List[FraccionesUSAResponse]: - """Search USA tariff fractions""" - items, _ = await self.search_with_total( - fraccion=fraccion, - descripcion=descripcion, - skip=skip, - limit=limit, - ) - return items - - async def get_by_id(self, consecutivo: int) -> FraccionesUSAResponse: - """Get single USA Fraccion record by CONSECUTIVO""" - data = await self._make_request("GET", f"api/v1/fracciones-usa/{consecutivo}") - return FraccionesUSAResponse(**data) - - @classmethod - def search_sync( - cls, - fraccion: Optional[str] = None, - descripcion: Optional[str] = None, - skip: int = 0, - limit: int = 100, - ) -> List[FraccionesUSAResponse]: - """ - Synchronous SITAR search for use from sync validators (FastAPI async routes - run sync code on the event loop; asyncio.run must not be used there). - """ - try: - service = cls.get_instance() - except ValueError: - return [] - - params: dict = {"skip": skip, "limit": min(limit, 1000)} - if fraccion: - params["fraccion"] = fraccion - if descripcion: - params["descripcion"] = descripcion - - try: - raw = service._make_request_sync("GET", "api/v1/fracciones-usa/", params=params) - rows, _ = service._parse_paginated_list_and_total(raw) - return [FraccionesUSAResponse(**item) for item in rows] - except Exception as exc: - logger.warning("SITAR fracciones-usa search_sync failed: %s", exc) - return [] diff --git a/backend/api/v1/modules/sitar/fundamentos_tlc/__init__.py b/backend/api/v1/modules/sitar/fundamentos_tlc/__init__.py deleted file mode 100644 index 57253bec..00000000 --- a/backend/api/v1/modules/sitar/fundamentos_tlc/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -"""Fundamentos TLC Module""" - -from .schemas import FundamentosTLCResponse -from .service import FundamentosTLCService -from .router import router - -__all__ = ["FundamentosTLCResponse", "FundamentosTLCService", "router"] diff --git a/backend/api/v1/modules/sitar/fundamentos_tlc/router.py b/backend/api/v1/modules/sitar/fundamentos_tlc/router.py deleted file mode 100644 index d4cce318..00000000 --- a/backend/api/v1/modules/sitar/fundamentos_tlc/router.py +++ /dev/null @@ -1,37 +0,0 @@ -"""Fundamentos TLC Router""" - -from typing import Optional, List -from fastapi import APIRouter, HTTPException, Query, Depends -from core.security import get_current_user -from .service import FundamentosTLCService -from .schemas import FundamentosTLCResponse - -router = APIRouter() - - -@router.get("/", response_model=List[FundamentosTLCResponse]) -async def search_fundamentos( - fraccion: Optional[str] = Query(None), - nico: Optional[str] = Query(None), - tipat_only: bool = Query(False), - skip: int = Query(0, ge=0), - limit: int = Query(100, ge=1, le=1000), - current_user: dict = Depends(get_current_user), -): - try: - service = FundamentosTLCService.get_instance() - return await service.search( - fraccion=fraccion, nico=nico, tipat_only=tipat_only, skip=skip, limit=limit - ) - except Exception as e: - raise HTTPException(status_code=500, detail=str(e)) - - -@router.get("/{sysid}", response_model=FundamentosTLCResponse) -async def get_fundamento_by_id( - sysid: int, current_user: dict = Depends(get_current_user) -): - try: - return await FundamentosTLCService.get_instance().get_by_id(sysid) - except Exception as e: - raise HTTPException(status_code=404, detail=str(e)) diff --git a/backend/api/v1/modules/sitar/fundamentos_tlc/schemas.py b/backend/api/v1/modules/sitar/fundamentos_tlc/schemas.py deleted file mode 100644 index d05c328c..00000000 --- a/backend/api/v1/modules/sitar/fundamentos_tlc/schemas.py +++ /dev/null @@ -1,26 +0,0 @@ -"""Fundamentos TLC Schemas""" - -from typing import Optional -from pydantic import BaseModel, ConfigDict, Field - - -class FundamentosTLCResponse(BaseModel): - """Fundamentos de tratados de libre comercio""" - - FRACCION: Optional[str] = Field(None, max_length=8) - PAIS: Optional[str] = Field(None, max_length=3) - ACUERDO: Optional[str] = Field(None, max_length=5) - TASA1TXT: Optional[str] = Field(None, max_length=19) - TASA1NUM: Optional[str] = None - TASA2TXT: Optional[str] = Field(None, max_length=19) - TASA2NUM: Optional[str] = None - FUNDAMENTO1: Optional[str] = None - FUNDAMENTO2: Optional[str] = None - PERIODO: Optional[str] = Field(None, max_length=299) - MODALIDAD: Optional[str] = Field(None, max_length=999) - DOF: Optional[str] = Field(None, max_length=8) - NOTAS: Optional[str] = Field(None, max_length=20) - NICO: Optional[str] = Field("", max_length=2) - SYSID: int - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/sitar/fundamentos_tlc/service.py b/backend/api/v1/modules/sitar/fundamentos_tlc/service.py deleted file mode 100644 index a1c99254..00000000 --- a/backend/api/v1/modules/sitar/fundamentos_tlc/service.py +++ /dev/null @@ -1,40 +0,0 @@ -"""Fundamentos TLC Service""" - -from typing import Optional, List -from ..common import SitarAPIBaseService -from .schemas import FundamentosTLCResponse - - -class FundamentosTLCService(SitarAPIBaseService): - _instance: Optional["FundamentosTLCService"] = None - - @classmethod - def get_instance(cls) -> "FundamentosTLCService": - if cls._instance is None: - cls._instance = cls() - return cls._instance - - async def search( - self, - fraccion: Optional[str] = None, - nico: Optional[str] = None, - tipat_only: bool = False, - skip: int = 0, - limit: int = 100, - ) -> List[FundamentosTLCResponse]: - params = {"skip": skip, "limit": min(limit, 1000)} - if fraccion: - params["fraccion"] = fraccion - if nico: - params["nico"] = nico - endpoint = ( - "/api/v1/fundamentos-tlc/tipat" - if tipat_only - else "/api/v1/fundamentos-tlc/" - ) - data = await self._make_request("GET", endpoint, params=params) - return [FundamentosTLCResponse(**item) for item in data] - - async def get_by_id(self, sysid: int) -> FundamentosTLCResponse: - data = await self._make_request("GET", f"/api/v1/fundamentos-tlc/{sysid}") - return FundamentosTLCResponse(**data) diff --git a/backend/api/v1/modules/sitar/ieps/__init__.py b/backend/api/v1/modules/sitar/ieps/__init__.py deleted file mode 100644 index d24fdf7b..00000000 --- a/backend/api/v1/modules/sitar/ieps/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -"""Ieps Module""" - -from .schemas import IepsResponse -from .service import IepsService -from .router import router - -__all__ = ["IepsResponse", "IepsService", "router"] diff --git a/backend/api/v1/modules/sitar/ieps/router.py b/backend/api/v1/modules/sitar/ieps/router.py deleted file mode 100644 index 6d9c1ac5..00000000 --- a/backend/api/v1/modules/sitar/ieps/router.py +++ /dev/null @@ -1,34 +0,0 @@ -"""Ieps Router""" - -from typing import Optional, List -from fastapi import APIRouter, HTTPException, Query, Depends -from core.security import get_current_user -from .service import IepsService -from .schemas import IepsResponse - -router = APIRouter() - - -@router.get("/", response_model=List[IepsResponse]) -async def search( - fraccion: Optional[str] = Query(None), - nico: Optional[str] = Query(None), - skip: int = Query(0, ge=0), - limit: int = Query(100, ge=1, le=1000), - current_user: dict = Depends(get_current_user), -): - try: - service = IepsService.get_instance() - return await service.search( - fraccion=fraccion, nico=nico, skip=skip, limit=limit - ) - except Exception as e: - raise HTTPException(status_code=500, detail=str(e)) - - -@router.get("/{consecutivo}", response_model=IepsResponse) -async def get_by_id(consecutivo: int, current_user: dict = Depends(get_current_user)): - try: - return await IepsService.get_instance().get_by_id(consecutivo) - except Exception as e: - raise HTTPException(status_code=404, detail=str(e)) diff --git a/backend/api/v1/modules/sitar/ieps/schemas.py b/backend/api/v1/modules/sitar/ieps/schemas.py deleted file mode 100644 index 2966f4ab..00000000 --- a/backend/api/v1/modules/sitar/ieps/schemas.py +++ /dev/null @@ -1,19 +0,0 @@ -"""IEPS Schemas""" - -from typing import Optional -from pydantic import BaseModel, ConfigDict, Field - - -class IepsResponse(BaseModel): - """IEPS (Impuesto Especial sobre Producción y Servicios)""" - - FRACCION_SIN_PUNTO: Optional[str] = Field(None, max_length=10) - FRACCION_CON_PUNTO: Optional[str] = Field(None, max_length=10) - FUNDAMENTO: Optional[str] = Field(None, max_length=1000) - CONDICION: Optional[str] = Field(None, max_length=5000) - TASA: Optional[str] = None - TASAESPECIFICO: Optional[str] = None - NICO: Optional[str] = Field("", max_length=2) - CONSECUTIVO: int - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/sitar/ieps/service.py b/backend/api/v1/modules/sitar/ieps/service.py deleted file mode 100644 index 72db617c..00000000 --- a/backend/api/v1/modules/sitar/ieps/service.py +++ /dev/null @@ -1,34 +0,0 @@ -"""Ieps Service""" - -from typing import Optional, List -from ..common import SitarAPIBaseService -from .schemas import IepsResponse - - -class IepsService(SitarAPIBaseService): - _instance: Optional["IepsService"] = None - - @classmethod - def get_instance(cls) -> "IepsService": - if cls._instance is None: - cls._instance = cls() - return cls._instance - - async def search( - self, - fraccion: Optional[str] = None, - nico: Optional[str] = None, - skip: int = 0, - limit: int = 100, - ) -> List[IepsResponse]: - params = {"skip": skip, "limit": min(limit, 1000)} - if fraccion: - params["fraccion"] = fraccion - if nico: - params["nico"] = nico - data = await self._make_request("GET", "/api/v1/ieps/", params=params) - return [IepsResponse(**item) for item in data] - - async def get_by_id(self, consecutivo: int) -> IepsResponse: - data = await self._make_request("GET", f"/api/v1/ieps/{consecutivo}") - return IepsResponse(**data) diff --git a/backend/api/v1/modules/sitar/informacion_general/__init__.py b/backend/api/v1/modules/sitar/informacion_general/__init__.py deleted file mode 100644 index ea0001eb..00000000 --- a/backend/api/v1/modules/sitar/informacion_general/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -"""InformacionGeneral Module""" - -from .schemas import InformacionGeneralResponse -from .service import InformacionGeneralService -from .router import router - -__all__ = ["InformacionGeneralResponse", "InformacionGeneralService", "router"] diff --git a/backend/api/v1/modules/sitar/informacion_general/router.py b/backend/api/v1/modules/sitar/informacion_general/router.py deleted file mode 100644 index 9209c9f7..00000000 --- a/backend/api/v1/modules/sitar/informacion_general/router.py +++ /dev/null @@ -1,34 +0,0 @@ -"""InformacionGeneral Router""" - -from typing import Optional, List -from fastapi import APIRouter, HTTPException, Query, Depends -from core.security import get_current_user -from .service import InformacionGeneralService -from .schemas import InformacionGeneralResponse - -router = APIRouter() - - -@router.get("/", response_model=List[InformacionGeneralResponse]) -async def search( - fraccion: Optional[str] = Query(None), - nico: Optional[str] = Query(None), - skip: int = Query(0, ge=0), - limit: int = Query(100, ge=1, le=1000), - current_user: dict = Depends(get_current_user), -): - try: - service = InformacionGeneralService.get_instance() - return await service.search( - fraccion=fraccion, nico=nico, skip=skip, limit=limit - ) - except Exception as e: - raise HTTPException(status_code=500, detail=str(e)) - - -@router.get("/{sysid}", response_model=InformacionGeneralResponse) -async def get_by_id(sysid: int, current_user: dict = Depends(get_current_user)): - try: - return await InformacionGeneralService.get_instance().get_by_id(sysid) - except Exception as e: - raise HTTPException(status_code=404, detail=str(e)) diff --git a/backend/api/v1/modules/sitar/informacion_general/schemas.py b/backend/api/v1/modules/sitar/informacion_general/schemas.py deleted file mode 100644 index b0c68238..00000000 --- a/backend/api/v1/modules/sitar/informacion_general/schemas.py +++ /dev/null @@ -1,20 +0,0 @@ -"""Información General Schemas""" - -from typing import Optional -from pydantic import BaseModel, ConfigDict, Field - - -class InformacionGeneralResponse(BaseModel): - """Información general de fracciones""" - - OBSERVACIONES: Optional[str] = Field(None, max_length=1000) - NOTAOBSERVACIONES: Optional[str] = Field(None, max_length=1000) - FRACCION: Optional[str] = Field(None, max_length=10) - TIPOOPERACION: Optional[int] = None - ORDEN: Optional[int] = None - LEYENDA: Optional[str] = None - NOTA: Optional[str] = None - NICO: Optional[str] = Field("", max_length=2) - SYSID: int - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/sitar/informacion_general/service.py b/backend/api/v1/modules/sitar/informacion_general/service.py deleted file mode 100644 index 4b47f8db..00000000 --- a/backend/api/v1/modules/sitar/informacion_general/service.py +++ /dev/null @@ -1,36 +0,0 @@ -"""InformacionGeneral Service""" - -from typing import Optional, List -from ..common import SitarAPIBaseService -from .schemas import InformacionGeneralResponse - - -class InformacionGeneralService(SitarAPIBaseService): - _instance: Optional["InformacionGeneralService"] = None - - @classmethod - def get_instance(cls) -> "InformacionGeneralService": - if cls._instance is None: - cls._instance = cls() - return cls._instance - - async def search( - self, - fraccion: Optional[str] = None, - nico: Optional[str] = None, - skip: int = 0, - limit: int = 100, - ) -> List[InformacionGeneralResponse]: - params = {"skip": skip, "limit": min(limit, 1000)} - if fraccion: - params["fraccion"] = fraccion - if nico: - params["nico"] = nico - data = await self._make_request( - "GET", "/api/v1/informacion-general/", params=params - ) - return [InformacionGeneralResponse(**item) for item in data] - - async def get_by_id(self, sysid: int) -> InformacionGeneralResponse: - data = await self._make_request("GET", f"/api/v1/informacion-general/{sysid}") - return InformacionGeneralResponse(**data) diff --git a/backend/api/v1/modules/sitar/noms/__init__.py b/backend/api/v1/modules/sitar/noms/__init__.py deleted file mode 100644 index 4262ff90..00000000 --- a/backend/api/v1/modules/sitar/noms/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -"""Noms Module""" - -from .schemas import NomsResponse -from .service import NomsService -from .router import router - -__all__ = ["NomsResponse", "NomsService", "router"] diff --git a/backend/api/v1/modules/sitar/noms/router.py b/backend/api/v1/modules/sitar/noms/router.py deleted file mode 100644 index 13ef23c7..00000000 --- a/backend/api/v1/modules/sitar/noms/router.py +++ /dev/null @@ -1,35 +0,0 @@ -"""Noms Router""" - -from typing import Optional, List -from fastapi import APIRouter, HTTPException, Query, Depends -from core.security import get_current_user -from .service import NomsService -from .schemas import NomsResponse - -router = APIRouter() - - -@router.get("/", response_model=List[NomsResponse]) -async def search( - fraccion: Optional[str] = Query(None), - pais: Optional[str] = Query(None), - nico: Optional[str] = Query(None), - skip: int = Query(0, ge=0), - limit: int = Query(100, ge=1, le=1000), - current_user: dict = Depends(get_current_user), -): - try: - service = NomsService.get_instance() - return await service.search( - fraccion=fraccion, pais=pais, nico=nico, skip=skip, limit=limit - ) - except Exception as e: - raise HTTPException(status_code=500, detail=str(e)) - - -@router.get("/{sysid}", response_model=NomsResponse) -async def get_by_id(sysid: int, current_user: dict = Depends(get_current_user)): - try: - return await NomsService.get_instance().get_by_id(sysid) - except Exception as e: - raise HTTPException(status_code=404, detail=str(e)) diff --git a/backend/api/v1/modules/sitar/noms/schemas.py b/backend/api/v1/modules/sitar/noms/schemas.py deleted file mode 100644 index e1ad6777..00000000 --- a/backend/api/v1/modules/sitar/noms/schemas.py +++ /dev/null @@ -1,26 +0,0 @@ -"""NOMs Schemas""" - -from typing import Optional -from pydantic import BaseModel, ConfigDict, Field - - -class NomsResponse(BaseModel): - """Normas Oficiales Mexicanas""" - - FRACCION: Optional[str] = Field("", max_length=10) - IMPORTACION: Optional[str] = Field("", max_length=1) - EXPORTACION: Optional[str] = Field("", max_length=1) - PERMISO: Optional[str] = Field("", max_length=19) - ACUERDO: Optional[str] = Field("", max_length=100) - CONDICION: Optional[str] = Field("") - FUNDAMENTO: Optional[str] = Field("") - DOF: Optional[str] = Field("", max_length=8) - FORMATONOM: Optional[str] = Field("", max_length=100) - INSTRUCCIONES: Optional[str] = Field("", max_length=100) - CRITERIO: Optional[str] = Field("", max_length=254) - COMPLEMENTO: Optional[str] = Field("", max_length=999) - NICO: Optional[str] = Field("", max_length=2) - PAIS: Optional[str] = Field("", max_length=3) - SYSID: int - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/sitar/noms/service.py b/backend/api/v1/modules/sitar/noms/service.py deleted file mode 100644 index bc2231af..00000000 --- a/backend/api/v1/modules/sitar/noms/service.py +++ /dev/null @@ -1,37 +0,0 @@ -"""Noms Service""" - -from typing import Optional, List -from ..common import SitarAPIBaseService -from .schemas import NomsResponse - - -class NomsService(SitarAPIBaseService): - _instance: Optional["NomsService"] = None - - @classmethod - def get_instance(cls) -> "NomsService": - if cls._instance is None: - cls._instance = cls() - return cls._instance - - async def search( - self, - fraccion: Optional[str] = None, - pais: Optional[str] = None, - nico: Optional[str] = None, - skip: int = 0, - limit: int = 100, - ) -> List[NomsResponse]: - params = {"skip": skip, "limit": min(limit, 1000)} - if fraccion: - params["fraccion"] = fraccion - if pais: - params["pais"] = pais - if nico: - params["nico"] = nico - data = await self._make_request("GET", "/api/v1/noms/", params=params) - return [NomsResponse(**item) for item in data] - - async def get_by_id(self, sysid: int) -> NomsResponse: - data = await self._make_request("GET", f"/api/v1/noms/{sysid}") - return NomsResponse(**data) diff --git a/backend/api/v1/modules/sitar/precios_estimados/__init__.py b/backend/api/v1/modules/sitar/precios_estimados/__init__.py deleted file mode 100644 index 8fb56810..00000000 --- a/backend/api/v1/modules/sitar/precios_estimados/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -"""Precios Estimados Module""" - -from .schemas import PreciosEstimados2Response -from .service import PreciosEstimadosService -from .router import router - -__all__ = ["PreciosEstimados2Response", "PreciosEstimadosService", "router"] diff --git a/backend/api/v1/modules/sitar/precios_estimados/router.py b/backend/api/v1/modules/sitar/precios_estimados/router.py deleted file mode 100644 index a063fc7e..00000000 --- a/backend/api/v1/modules/sitar/precios_estimados/router.py +++ /dev/null @@ -1,34 +0,0 @@ -"""Precios Estimados Router""" - -from typing import Optional, List -from fastapi import APIRouter, HTTPException, Query, Depends -from core.security import get_current_user -from .service import PreciosEstimadosService -from .schemas import PreciosEstimados2Response - -router = APIRouter() - - -@router.get("/", response_model=List[PreciosEstimados2Response]) -async def search_precios( - fraccion: Optional[str] = Query(None), - nico: Optional[str] = Query(None), - skip: int = Query(0, ge=0), - limit: int = Query(100, ge=1, le=1000), - current_user: dict = Depends(get_current_user), -): - try: - service = PreciosEstimadosService.get_instance() - return await service.search( - fraccion=fraccion, nico=nico, skip=skip, limit=limit - ) - except Exception as e: - raise HTTPException(status_code=500, detail=str(e)) - - -@router.get("/{sysid}", response_model=PreciosEstimados2Response) -async def get_precio_by_id(sysid: int, current_user: dict = Depends(get_current_user)): - try: - return await PreciosEstimadosService.get_instance().get_by_id(sysid) - except Exception as e: - raise HTTPException(status_code=404, detail=str(e)) diff --git a/backend/api/v1/modules/sitar/precios_estimados/schemas.py b/backend/api/v1/modules/sitar/precios_estimados/schemas.py deleted file mode 100644 index b3f18ed3..00000000 --- a/backend/api/v1/modules/sitar/precios_estimados/schemas.py +++ /dev/null @@ -1,17 +0,0 @@ -"""Precios Estimados Schemas""" - -from typing import Optional -from pydantic import BaseModel, ConfigDict, Field - - -class PreciosEstimados2Response(BaseModel): - """Precios Estimados""" - - FRACCION: Optional[str] = Field(None, max_length=10) - UMC: Optional[str] = Field(None, max_length=2) - PRECIO: Optional[str] = None - DOF: Optional[str] = Field(None, max_length=8) - NICO: Optional[str] = Field("", max_length=2) - SYSID: int - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/sitar/precios_estimados/service.py b/backend/api/v1/modules/sitar/precios_estimados/service.py deleted file mode 100644 index 312d58a8..00000000 --- a/backend/api/v1/modules/sitar/precios_estimados/service.py +++ /dev/null @@ -1,36 +0,0 @@ -"""Precios Estimados Service""" - -from typing import Optional, List -from ..common import SitarAPIBaseService -from .schemas import PreciosEstimados2Response - - -class PreciosEstimadosService(SitarAPIBaseService): - _instance: Optional["PreciosEstimadosService"] = None - - @classmethod - def get_instance(cls) -> "PreciosEstimadosService": - if cls._instance is None: - cls._instance = cls() - return cls._instance - - async def search( - self, - fraccion: Optional[str] = None, - nico: Optional[str] = None, - skip: int = 0, - limit: int = 100, - ) -> List[PreciosEstimados2Response]: - params = {"skip": skip, "limit": min(limit, 1000)} - if fraccion: - params["fraccion"] = fraccion - if nico: - params["nico"] = nico - data = await self._make_request( - "GET", "/api/v1/precios-estimados2/", params=params - ) - return [PreciosEstimados2Response(**item) for item in data] - - async def get_by_id(self, sysid: int) -> PreciosEstimados2Response: - data = await self._make_request("GET", f"/api/v1/precios-estimados2/{sysid}") - return PreciosEstimados2Response(**data) diff --git a/backend/api/v1/modules/sitar/prosec/__init__.py b/backend/api/v1/modules/sitar/prosec/__init__.py deleted file mode 100644 index 6cf9bfc2..00000000 --- a/backend/api/v1/modules/sitar/prosec/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -"""PROSEC Module""" - -from .schemas import ProsecResponse -from .service import ProsecService -from .router import router - -__all__ = ["ProsecResponse", "ProsecService", "router"] diff --git a/backend/api/v1/modules/sitar/prosec/router.py b/backend/api/v1/modules/sitar/prosec/router.py deleted file mode 100644 index 22a15267..00000000 --- a/backend/api/v1/modules/sitar/prosec/router.py +++ /dev/null @@ -1,42 +0,0 @@ -"""PROSEC Router""" - -from typing import Optional, List -from fastapi import APIRouter, HTTPException, Query, Depends -from core.security import get_current_user -from .service import ProsecService -from .schemas import ProsecResponse - -router = APIRouter() - - -@router.get("/", response_model=List[ProsecResponse]) -async def search_prosec( - fraccion: Optional[str] = Query(None), - nico: Optional[str] = Query(None), - sector: Optional[str] = Query(None), - articulo: Optional[str] = Query(None), - skip: int = Query(0, ge=0), - limit: int = Query(100, ge=1, le=1000), - current_user: dict = Depends(get_current_user), -): - try: - service = ProsecService.get_instance() - return await service.search( - fraccion=fraccion, - nico=nico, - sector=sector, - articulo=articulo, - skip=skip, - limit=limit, - ) - except Exception as e: - raise HTTPException(status_code=500, detail=f"Error: {str(e)}") - - -@router.get("/{sysid}", response_model=ProsecResponse) -async def get_prosec_by_id(sysid: int, current_user: dict = Depends(get_current_user)): - try: - service = ProsecService.get_instance() - return await service.get_by_id(sysid) - except Exception as e: - raise HTTPException(status_code=404, detail=f"Not found: {str(e)}") diff --git a/backend/api/v1/modules/sitar/prosec/schemas.py b/backend/api/v1/modules/sitar/prosec/schemas.py deleted file mode 100644 index ee69df2f..00000000 --- a/backend/api/v1/modules/sitar/prosec/schemas.py +++ /dev/null @@ -1,21 +0,0 @@ -"""PROSEC Schemas — aligned with SITAR OpenAPI ProsecResponse.""" - -from typing import Optional -from pydantic import BaseModel, ConfigDict, Field - - -class ProsecResponse(BaseModel): - """PROSEC (Programa de Promoción Sectorial).""" - - FRACCION: Optional[str] = Field("", max_length=10) - ARTICULO: Optional[str] = Field("", max_length=3) - SECTOR: Optional[str] = Field("", max_length=6) - TASATXT: Optional[str] = Field("", max_length=19) - TASANUM: Optional[str] = Field(default="0") - TIPOTASA: Optional[int] = Field(default=0) - DOF: Optional[str] = Field("", max_length=8) - OBSERVACION: Optional[str] = "" - NICO: Optional[str] = Field("", max_length=2) - SYSID: int - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/sitar/prosec/service.py b/backend/api/v1/modules/sitar/prosec/service.py deleted file mode 100644 index f4a9c7bf..00000000 --- a/backend/api/v1/modules/sitar/prosec/service.py +++ /dev/null @@ -1,43 +0,0 @@ -"""PROSEC Service""" - -from typing import Optional, List -from ..common import SitarAPIBaseService -from .schemas import ProsecResponse - - -class ProsecService(SitarAPIBaseService): - """Service for PROSEC operations""" - - _instance: Optional["ProsecService"] = None - - @classmethod - def get_instance(cls) -> "ProsecService": - if cls._instance is None: - cls._instance = cls() - return cls._instance - - async def search( - self, - fraccion: Optional[str] = None, - nico: Optional[str] = None, - sector: Optional[str] = None, - articulo: Optional[str] = None, - skip: int = 0, - limit: int = 100, - ) -> List[ProsecResponse]: - params = {"skip": skip, "limit": min(limit, 1000)} - if fraccion: - params["fraccion"] = fraccion - if nico: - params["nico"] = nico - if sector: - params["sector"] = sector - if articulo: - params["articulo"] = articulo - - data = await self._make_request("GET", "/api/v1/prosec/", params=params) - return [ProsecResponse(**item) for item in data] - - async def get_by_id(self, sysid: int) -> ProsecResponse: - data = await self._make_request("GET", f"/api/v1/prosec/{sysid}") - return ProsecResponse(**data) diff --git a/backend/api/v1/modules/sitar/rcg2/__init__.py b/backend/api/v1/modules/sitar/rcg2/__init__.py deleted file mode 100644 index 6cc53a00..00000000 --- a/backend/api/v1/modules/sitar/rcg2/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -"""Rcg2 Module""" - -from .schemas import Rcg2Response -from .service import Rcg2Service -from .router import router - -__all__ = ["Rcg2Response", "Rcg2Service", "router"] diff --git a/backend/api/v1/modules/sitar/rcg2/router.py b/backend/api/v1/modules/sitar/rcg2/router.py deleted file mode 100644 index d07dac54..00000000 --- a/backend/api/v1/modules/sitar/rcg2/router.py +++ /dev/null @@ -1,34 +0,0 @@ -"""Rcg2 Router""" - -from typing import Optional, List -from fastapi import APIRouter, HTTPException, Query, Depends -from core.security import get_current_user -from .service import Rcg2Service -from .schemas import Rcg2Response - -router = APIRouter() - - -@router.get("/", response_model=List[Rcg2Response]) -async def search( - fraccion: Optional[str] = Query(None), - nico: Optional[str] = Query(None), - skip: int = Query(0, ge=0), - limit: int = Query(100, ge=1, le=1000), - current_user: dict = Depends(get_current_user), -): - try: - service = Rcg2Service.get_instance() - return await service.search( - fraccion=fraccion, nico=nico, skip=skip, limit=limit - ) - except Exception as e: - raise HTTPException(status_code=500, detail=str(e)) - - -@router.get("/{sysid}", response_model=Rcg2Response) -async def get_by_id(sysid: int, current_user: dict = Depends(get_current_user)): - try: - return await Rcg2Service.get_instance().get_by_id(sysid) - except Exception as e: - raise HTTPException(status_code=404, detail=str(e)) diff --git a/backend/api/v1/modules/sitar/rcg2/schemas.py b/backend/api/v1/modules/sitar/rcg2/schemas.py deleted file mode 100644 index 3f90814b..00000000 --- a/backend/api/v1/modules/sitar/rcg2/schemas.py +++ /dev/null @@ -1,25 +0,0 @@ -"""RCG2 Schemas""" - -from typing import Optional -from pydantic import BaseModel, ConfigDict, Field - - -class Rcg2Response(BaseModel): - """Reglas de Carácter General""" - - FRACCION: Optional[str] = Field(None, max_length=10) - ANEXO: Optional[str] = Field(None, max_length=19) - SECTOR: Optional[str] = None - REGIMEN: Optional[str] = None - CONDICION: Optional[str] = None - ADUANAS: Optional[str] = Field(None, max_length=1000) - DOF: Optional[str] = Field(None, max_length=8) - ARCHIVOCRITERIO: Optional[str] = Field(None, max_length=1000) - TIPO: Optional[str] = Field(None, max_length=1) - IMPO: Optional[int] = None - EXPO: Optional[int] = None - WEB_DOCUMENTO_ID: Optional[int] = None - NICO: Optional[str] = Field("", max_length=2) - SYSID: int - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/sitar/rcg2/service.py b/backend/api/v1/modules/sitar/rcg2/service.py deleted file mode 100644 index 4e04ac29..00000000 --- a/backend/api/v1/modules/sitar/rcg2/service.py +++ /dev/null @@ -1,34 +0,0 @@ -"""Rcg2 Service""" - -from typing import Optional, List -from ..common import SitarAPIBaseService -from .schemas import Rcg2Response - - -class Rcg2Service(SitarAPIBaseService): - _instance: Optional["Rcg2Service"] = None - - @classmethod - def get_instance(cls) -> "Rcg2Service": - if cls._instance is None: - cls._instance = cls() - return cls._instance - - async def search( - self, - fraccion: Optional[str] = None, - nico: Optional[str] = None, - skip: int = 0, - limit: int = 100, - ) -> List[Rcg2Response]: - params = {"skip": skip, "limit": min(limit, 1000)} - if fraccion: - params["fraccion"] = fraccion - if nico: - params["nico"] = nico - data = await self._make_request("GET", "/api/v1/rcg2/", params=params) - return [Rcg2Response(**item) for item in data] - - async def get_by_id(self, sysid: int) -> Rcg2Response: - data = await self._make_request("GET", f"/api/v1/rcg2/{sysid}") - return Rcg2Response(**data) diff --git a/backend/api/v1/modules/sitar/regulaciones/__init__.py b/backend/api/v1/modules/sitar/regulaciones/__init__.py deleted file mode 100644 index cff4beb3..00000000 --- a/backend/api/v1/modules/sitar/regulaciones/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -"""Regulaciones Module""" - -from .schemas import RegulacionesResponse -from .service import RegulacionesService -from .router import router - -__all__ = ["RegulacionesResponse", "RegulacionesService", "router"] diff --git a/backend/api/v1/modules/sitar/regulaciones/router.py b/backend/api/v1/modules/sitar/regulaciones/router.py deleted file mode 100644 index ea1359b7..00000000 --- a/backend/api/v1/modules/sitar/regulaciones/router.py +++ /dev/null @@ -1,37 +0,0 @@ -"""Regulaciones Router""" - -from typing import Optional, List -from fastapi import APIRouter, HTTPException, Query, Depends -from core.security import get_current_user -from .service import RegulacionesService -from .schemas import RegulacionesResponse - -router = APIRouter() - - -@router.get("/", response_model=List[RegulacionesResponse]) -async def search_regulaciones( - fraccion: Optional[str] = Query(None, description="Fracción arancelaria"), - nico: Optional[str] = Query(None, description="NICO"), - skip: int = Query(0, ge=0), - limit: int = Query(100, ge=1, le=1000), - current_user: dict = Depends(get_current_user), -): - try: - service = RegulacionesService.get_instance() - return await service.search( - fraccion=fraccion, nico=nico, skip=skip, limit=limit - ) - except Exception as e: - raise HTTPException(status_code=500, detail=f"Error: {str(e)}") - - -@router.get("/{sysid}", response_model=RegulacionesResponse) -async def get_regulacion_by_id( - sysid: int, current_user: dict = Depends(get_current_user) -): - try: - service = RegulacionesService.get_instance() - return await service.get_by_id(sysid) - except Exception as e: - raise HTTPException(status_code=404, detail=f"Not found: {str(e)}") diff --git a/backend/api/v1/modules/sitar/regulaciones/schemas.py b/backend/api/v1/modules/sitar/regulaciones/schemas.py deleted file mode 100644 index c33387cf..00000000 --- a/backend/api/v1/modules/sitar/regulaciones/schemas.py +++ /dev/null @@ -1,18 +0,0 @@ -"""Regulaciones Schemas""" - -from typing import Optional -from pydantic import BaseModel, ConfigDict, Field - - -class RegulacionesResponse(BaseModel): - """Regulaciones y restricciones""" - - FRACCION: Optional[str] = Field(None, max_length=10) - PERMISO: Optional[str] = Field(None, max_length=2) - ACUERDO: Optional[str] = Field(None, max_length=15) - CLAVE: Optional[str] = Field(None, max_length=10) - DESCRIPCION: Optional[str] = None - NICO: Optional[str] = Field("", max_length=2) - SYSID: int - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/sitar/regulaciones/service.py b/backend/api/v1/modules/sitar/regulaciones/service.py deleted file mode 100644 index ca534e0d..00000000 --- a/backend/api/v1/modules/sitar/regulaciones/service.py +++ /dev/null @@ -1,37 +0,0 @@ -"""Regulaciones Service""" - -from typing import Optional, List -from ..common import SitarAPIBaseService -from .schemas import RegulacionesResponse - - -class RegulacionesService(SitarAPIBaseService): - """Service for Regulaciones operations""" - - _instance: Optional["RegulacionesService"] = None - - @classmethod - def get_instance(cls) -> "RegulacionesService": - if cls._instance is None: - cls._instance = cls() - return cls._instance - - async def search( - self, - fraccion: Optional[str] = None, - nico: Optional[str] = None, - skip: int = 0, - limit: int = 100, - ) -> List[RegulacionesResponse]: - params = {"skip": skip, "limit": min(limit, 1000)} - if fraccion: - params["fraccion"] = fraccion - if nico: - params["nico"] = nico - - data = await self._make_request("GET", "/api/v1/regulaciones/", params=params) - return [RegulacionesResponse(**item) for item in data] - - async def get_by_id(self, sysid: int) -> RegulacionesResponse: - data = await self._make_request("GET", f"/api/v1/regulaciones/{sysid}") - return RegulacionesResponse(**data) diff --git a/backend/api/v1/modules/sitar/reit/__init__.py b/backend/api/v1/modules/sitar/reit/__init__.py deleted file mode 100644 index e462b3c0..00000000 --- a/backend/api/v1/modules/sitar/reit/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -"""Reit Module""" - -from .schemas import ReitResponse -from .service import ReitService -from .router import router - -__all__ = ["ReitResponse", "ReitService", "router"] diff --git a/backend/api/v1/modules/sitar/reit/router.py b/backend/api/v1/modules/sitar/reit/router.py deleted file mode 100644 index e5204115..00000000 --- a/backend/api/v1/modules/sitar/reit/router.py +++ /dev/null @@ -1,34 +0,0 @@ -"""Reit Router""" - -from typing import Optional, List -from fastapi import APIRouter, HTTPException, Query, Depends -from core.security import get_current_user -from .service import ReitService -from .schemas import ReitResponse - -router = APIRouter() - - -@router.get("/", response_model=List[ReitResponse]) -async def search( - fraccion: Optional[str] = Query(None), - nico: Optional[str] = Query(None), - skip: int = Query(0, ge=0), - limit: int = Query(100, ge=1, le=1000), - current_user: dict = Depends(get_current_user), -): - try: - service = ReitService.get_instance() - return await service.search( - fraccion=fraccion, nico=nico, skip=skip, limit=limit - ) - except Exception as e: - raise HTTPException(status_code=500, detail=str(e)) - - -@router.get("/{sysid}", response_model=ReitResponse) -async def get_by_id(sysid: int, current_user: dict = Depends(get_current_user)): - try: - return await ReitService.get_instance().get_by_id(sysid) - except Exception as e: - raise HTTPException(status_code=404, detail=str(e)) diff --git a/backend/api/v1/modules/sitar/reit/schemas.py b/backend/api/v1/modules/sitar/reit/schemas.py deleted file mode 100644 index bea940d6..00000000 --- a/backend/api/v1/modules/sitar/reit/schemas.py +++ /dev/null @@ -1,24 +0,0 @@ -"""REIT Schemas""" - -from typing import Optional -from pydantic import BaseModel, ConfigDict, Field - - -class ReitResponse(BaseModel): - """Registro de Empresas de Industria Terminal""" - - FRACCION: Optional[str] = Field(None, max_length=10) - ARTICULO: Optional[str] = Field(None, max_length=50) - FUNDAMENTO: Optional[str] = Field(None, max_length=1500) - ACUERDO: Optional[str] = Field(None, max_length=500) - PERMISO: Optional[str] = Field(None, max_length=2) - DOF: Optional[str] = Field(None, max_length=8) - DOCUMENTO: Optional[str] = Field(None, max_length=200) - TEMPORALIDAD: Optional[int] = None - TEMPORALIDADSERVICIO: Optional[int] = None - TEMPORALIDADCERTIFICADA: Optional[int] = None - WEB_DOCUMENTO_ID: Optional[int] = None - NICO: Optional[str] = Field("", max_length=2) - SYSID: int - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/sitar/reit/service.py b/backend/api/v1/modules/sitar/reit/service.py deleted file mode 100644 index 86d871c9..00000000 --- a/backend/api/v1/modules/sitar/reit/service.py +++ /dev/null @@ -1,34 +0,0 @@ -"""Reit Service""" - -from typing import Optional, List -from ..common import SitarAPIBaseService -from .schemas import ReitResponse - - -class ReitService(SitarAPIBaseService): - _instance: Optional["ReitService"] = None - - @classmethod - def get_instance(cls) -> "ReitService": - if cls._instance is None: - cls._instance = cls() - return cls._instance - - async def search( - self, - fraccion: Optional[str] = None, - nico: Optional[str] = None, - skip: int = 0, - limit: int = 100, - ) -> List[ReitResponse]: - params = {"skip": skip, "limit": min(limit, 1000)} - if fraccion: - params["fraccion"] = fraccion - if nico: - params["nico"] = nico - data = await self._make_request("GET", "/api/v1/reit/", params=params) - return [ReitResponse(**item) for item in data] - - async def get_by_id(self, sysid: int) -> ReitResponse: - data = await self._make_request("GET", f"/api/v1/reit/{sysid}") - return ReitResponse(**data) diff --git a/backend/api/v1/modules/sitar/requisito_previo/__init__.py b/backend/api/v1/modules/sitar/requisito_previo/__init__.py deleted file mode 100644 index 65b96cf6..00000000 --- a/backend/api/v1/modules/sitar/requisito_previo/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -"""RequisitoPrevio Module""" - -from .schemas import RequisitoPrevioResponse -from .service import RequisitoPrevioService -from .router import router - -__all__ = ["RequisitoPrevioResponse", "RequisitoPrevioService", "router"] diff --git a/backend/api/v1/modules/sitar/requisito_previo/router.py b/backend/api/v1/modules/sitar/requisito_previo/router.py deleted file mode 100644 index aa2354f5..00000000 --- a/backend/api/v1/modules/sitar/requisito_previo/router.py +++ /dev/null @@ -1,34 +0,0 @@ -"""RequisitoPrevio Router""" - -from typing import Optional, List -from fastapi import APIRouter, HTTPException, Query, Depends -from core.security import get_current_user -from .service import RequisitoPrevioService -from .schemas import RequisitoPrevioResponse - -router = APIRouter() - - -@router.get("/", response_model=List[RequisitoPrevioResponse]) -async def search( - fraccion: Optional[str] = Query(None), - nico: Optional[str] = Query(None), - skip: int = Query(0, ge=0), - limit: int = Query(100, ge=1, le=1000), - current_user: dict = Depends(get_current_user), -): - try: - service = RequisitoPrevioService.get_instance() - return await service.search( - fraccion=fraccion, nico=nico, skip=skip, limit=limit - ) - except Exception as e: - raise HTTPException(status_code=500, detail=str(e)) - - -@router.get("/{sysid}", response_model=RequisitoPrevioResponse) -async def get_by_id(sysid: int, current_user: dict = Depends(get_current_user)): - try: - return await RequisitoPrevioService.get_instance().get_by_id(sysid) - except Exception as e: - raise HTTPException(status_code=404, detail=str(e)) diff --git a/backend/api/v1/modules/sitar/requisito_previo/schemas.py b/backend/api/v1/modules/sitar/requisito_previo/schemas.py deleted file mode 100644 index 75082e71..00000000 --- a/backend/api/v1/modules/sitar/requisito_previo/schemas.py +++ /dev/null @@ -1,27 +0,0 @@ -"""Requisito Previo Schemas""" - -from typing import Optional -from pydantic import BaseModel, ConfigDict, Field - - -class RequisitoPrevioResponse(BaseModel): - """Requisitos previos""" - - FRACCION: Optional[str] = Field(None, max_length=8) - DESCRIPCION: Optional[str] = Field(None, max_length=1999) - OBSERVACION: Optional[str] = Field(None, max_length=1999) - DOF: Optional[str] = Field(None, max_length=8) - VIGENCIA: Optional[str] = Field(None, max_length=8) - NUMEROACUERDO: Optional[int] = None - NUMEROCRITERIO: Optional[int] = None - NUMEROFORMATO: Optional[int] = None - NUMEROINSTRUCCIONES: Optional[int] = None - HISTORICO: Optional[str] = Field(None, max_length=2000) - NOTA: Optional[str] = Field(None, max_length=5000) - PERMISO: Optional[str] = Field(None, max_length=2) - IMPO: Optional[int] = None - EXPO: Optional[int] = None - NICO: Optional[str] = Field("", max_length=2) - SYSID: int - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/sitar/requisito_previo/service.py b/backend/api/v1/modules/sitar/requisito_previo/service.py deleted file mode 100644 index 84c74ac5..00000000 --- a/backend/api/v1/modules/sitar/requisito_previo/service.py +++ /dev/null @@ -1,36 +0,0 @@ -"""RequisitoPrevio Service""" - -from typing import Optional, List -from ..common import SitarAPIBaseService -from .schemas import RequisitoPrevioResponse - - -class RequisitoPrevioService(SitarAPIBaseService): - _instance: Optional["RequisitoPrevioService"] = None - - @classmethod - def get_instance(cls) -> "RequisitoPrevioService": - if cls._instance is None: - cls._instance = cls() - return cls._instance - - async def search( - self, - fraccion: Optional[str] = None, - nico: Optional[str] = None, - skip: int = 0, - limit: int = 100, - ) -> List[RequisitoPrevioResponse]: - params = {"skip": skip, "limit": min(limit, 1000)} - if fraccion: - params["fraccion"] = fraccion - if nico: - params["nico"] = nico - data = await self._make_request( - "GET", "/api/v1/requisito-previo/", params=params - ) - return [RequisitoPrevioResponse(**item) for item in data] - - async def get_by_id(self, sysid: int) -> RequisitoPrevioResponse: - data = await self._make_request("GET", f"/api/v1/requisito-previo/{sysid}") - return RequisitoPrevioResponse(**data) diff --git a/backend/api/v1/modules/sitar/router.py b/backend/api/v1/modules/sitar/router.py deleted file mode 100644 index 4370839c..00000000 --- a/backend/api/v1/modules/sitar/router.py +++ /dev/null @@ -1,90 +0,0 @@ -""" -SITAR API Main Router - -Router principal que incluye todos los sub-routers de SITAR. -Registra este router en tu aplicación FastAPI principal. - -Usage: - from api.v1.modules.sitar.main_router import router as sitar_router - - app.include_router( - sitar_router, - prefix="/api/v1/sitar", - tags=["sitar"] - ) -""" - -from fastapi import APIRouter - -# Import all sub-routers -from .tlcs.router import router as tlcs_router -from .fracciones.router import router as fracciones_router -from .fracciones_usa.router import router as fracciones_usa_router -from .regulaciones.router import router as regulaciones_router -from .prosec.router import router as prosec_router -from .precios_estimados.router import router as precios_estimados_router -from .fundamentos_tlc.router import router as fundamentos_tlc_router -from .aladi2.router import router as aladi2_router -from .cuotas2.router import router as cuotas2_router -from .cupos.router import router as cupos_router -from .fracciones_anteriores.router import router as fracciones_anteriores_router -from .informacion_general.router import router as informacion_general_router -from .ieps.router import router as ieps_router -from .noms.router import router as noms_router -from .rcg2.router import router as rcg2_router -from .reit.router import router as reit_router -from .requisito_previo.router import router as requisito_previo_router -from .vehiculos_marcas.router import router as vehiculos_marcas_router -from .vehiculos_modelos.router import router as vehiculos_modelos_router - -# Create main router -router = APIRouter() - -# Include all sub-routers with their prefixes -router.include_router(tlcs_router, prefix="/tlcs", tags=["sitar-tlcs"]) -router.include_router( - fracciones_router, prefix="/fracciones", tags=["sitar-fracciones"] -) -router.include_router( - fracciones_usa_router, prefix="/fracciones-usa", tags=["sitar-fracciones-usa"] -) -router.include_router( - regulaciones_router, prefix="/regulaciones", tags=["sitar-regulaciones"] -) -router.include_router(prosec_router, prefix="/prosec", tags=["sitar-prosec"]) -router.include_router( - precios_estimados_router, - prefix="/precios-estimados", - tags=["sitar-precios-estimados"], -) -router.include_router( - fundamentos_tlc_router, prefix="/fundamentos-tlc", tags=["sitar-fundamentos-tlc"] -) -router.include_router(aladi2_router, prefix="/aladi2", tags=["sitar-aladi2"]) -router.include_router(cuotas2_router, prefix="/cuotas2", tags=["sitar-cuotas2"]) -router.include_router(cupos_router, prefix="/cupos", tags=["sitar-cupos"]) -router.include_router( - fracciones_anteriores_router, - prefix="/fracciones-anteriores", - tags=["sitar-fracciones-anteriores"], -) -router.include_router( - informacion_general_router, - prefix="/informacion-general", - tags=["sitar-informacion-general"], -) -router.include_router(ieps_router, prefix="/ieps", tags=["sitar-ieps"]) -router.include_router(noms_router, prefix="/noms", tags=["sitar-noms"]) -router.include_router(rcg2_router, prefix="/rcg2", tags=["sitar-rcg2"]) -router.include_router(reit_router, prefix="/reit", tags=["sitar-reit"]) -router.include_router( - requisito_previo_router, prefix="/requisito-previo", tags=["sitar-requisito-previo"] -) -router.include_router( - vehiculos_marcas_router, prefix="/vehiculos-marcas", tags=["sitar-vehiculos-marcas"] -) -router.include_router( - vehiculos_modelos_router, - prefix="/vehiculos-modelos", - tags=["sitar-vehiculos-modelos"], -) diff --git a/backend/api/v1/modules/sitar/tlcs/__init__.py b/backend/api/v1/modules/sitar/tlcs/__init__.py deleted file mode 100644 index 4ebb568d..00000000 --- a/backend/api/v1/modules/sitar/tlcs/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -"""TLCS Module""" - -from .schemas import TLCSResponse -from .service import TLCSService -from .router import router - -__all__ = ["TLCSResponse", "TLCSService", "router"] diff --git a/backend/api/v1/modules/sitar/tlcs/router.py b/backend/api/v1/modules/sitar/tlcs/router.py deleted file mode 100644 index 0da8b4d3..00000000 --- a/backend/api/v1/modules/sitar/tlcs/router.py +++ /dev/null @@ -1,44 +0,0 @@ -"""TLCS Router""" - -from typing import Optional, List -from fastapi import APIRouter, HTTPException, Query, Depends -from core.security import get_current_user -from .service import TLCSService -from .schemas import TLCSResponse - -router = APIRouter() - - -@router.get("/", response_model=List[TLCSResponse]) -async def search_tlcs( - fraccion: Optional[str] = Query(None, description="Fracción arancelaria"), - pais: Optional[str] = Query(None, description="Código de país"), - nico: Optional[str] = Query(None, description="NICO"), - skip: int = Query(0, ge=0, description="Registros a saltar"), - limit: int = Query(100, ge=1, le=1000, description="Máximo de registros"), - current_user: dict = Depends(get_current_user), -): - """Search TLCS records""" - try: - service = TLCSService.get_instance() - return await service.search( - fraccion=fraccion, pais=pais, nico=nico, skip=skip, limit=limit - ) - except Exception as e: - raise HTTPException( - status_code=500, detail=f"Error fetching TLCS data: {str(e)}" - ) - - -@router.get("/{sysid}/{fraccion}", response_model=TLCSResponse) -async def get_tlcs_by_id( - sysid: int, - fraccion: str, - current_user: dict = Depends(get_current_user), -): - """Get single TLCS record by SYSID and FRACCION""" - try: - service = TLCSService.get_instance() - return await service.get_by_id(sysid, fraccion) - except Exception as e: - raise HTTPException(status_code=404, detail=f"TLCS record not found: {str(e)}") diff --git a/backend/api/v1/modules/sitar/tlcs/schemas.py b/backend/api/v1/modules/sitar/tlcs/schemas.py deleted file mode 100644 index 281bbc48..00000000 --- a/backend/api/v1/modules/sitar/tlcs/schemas.py +++ /dev/null @@ -1,24 +0,0 @@ -"""TLCS Schemas — aligned with SITAR OpenAPI TLCSResponse.""" - -from typing import Optional -from pydantic import BaseModel, ConfigDict, Field - - -class TLCSResponse(BaseModel): - """TLCS (Tratados de Libre Comercio) response model.""" - - FRACCION: str = Field(max_length=10) - PAIS: str = Field(max_length=3) - ORDEN: Optional[int] = None - TASATXT: Optional[str] = Field(None, max_length=44) - TIPOTASA: Optional[int] = None - TASA1NUM: Optional[str] = None - FACTOR1: Optional[str] = None - TASA2NUM: Optional[str] = None - FACTOR2: Optional[str] = None - DOF: Optional[str] = Field(None, max_length=8) - NOTAS: Optional[str] = Field(None, max_length=999) - NICO: Optional[str] = Field("", max_length=2) - SYSID: int - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/sitar/tlcs/service.py b/backend/api/v1/modules/sitar/tlcs/service.py deleted file mode 100644 index f9cef291..00000000 --- a/backend/api/v1/modules/sitar/tlcs/service.py +++ /dev/null @@ -1,55 +0,0 @@ -"""TLCS Service""" - -from typing import Optional, List -from ..common import SitarAPIBaseService -from .schemas import TLCSResponse - - -class TLCSService(SitarAPIBaseService): - """Service for TLCS operations""" - - _instance: Optional["TLCSService"] = None - - @classmethod - def get_instance(cls) -> "TLCSService": - """Get singleton instance""" - if cls._instance is None: - cls._instance = cls() - return cls._instance - - async def search( - self, - fraccion: Optional[str] = None, - pais: Optional[str] = None, - nico: Optional[str] = None, - skip: int = 0, - limit: int = 100, - ) -> List[TLCSResponse]: - """ - Search TLCS records - - Args: - fraccion: Tariff fraction code - pais: Country code - nico: NICO code - skip: Records to skip - limit: Maximum records to return - - Returns: - List of TLCS records - """ - params = {"skip": skip, "limit": min(limit, 1000)} - if fraccion: - params["fraccion"] = fraccion[:8] - if pais: - params["pais"] = pais - if nico: - params["nico"] = nico - - data = await self._make_request("GET", "/api/v1/tlcs/", params=params) - return [TLCSResponse(**item) for item in data] - - async def get_by_id(self, sysid: int, fraccion: str) -> TLCSResponse: - """Get single TLCS record by SYSID and FRACCION""" - data = await self._make_request("GET", f"/api/v1/tlcs/{sysid}/{fraccion}") - return TLCSResponse(**data) diff --git a/backend/api/v1/modules/sitar/vehiculos_marcas/__init__.py b/backend/api/v1/modules/sitar/vehiculos_marcas/__init__.py deleted file mode 100644 index 2ef02a73..00000000 --- a/backend/api/v1/modules/sitar/vehiculos_marcas/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -"""VehiculosMarcas Module""" - -from .schemas import VehiculosMarcasResponse -from .service import VehiculosMarcasService -from .router import router - -__all__ = ["VehiculosMarcasResponse", "VehiculosMarcasService", "router"] diff --git a/backend/api/v1/modules/sitar/vehiculos_marcas/router.py b/backend/api/v1/modules/sitar/vehiculos_marcas/router.py deleted file mode 100644 index 3c792c13..00000000 --- a/backend/api/v1/modules/sitar/vehiculos_marcas/router.py +++ /dev/null @@ -1,34 +0,0 @@ -"""VehiculosMarcas Router""" - -from typing import Optional, List -from fastapi import APIRouter, HTTPException, Query, Depends -from core.security import get_current_user -from .service import VehiculosMarcasService -from .schemas import VehiculosMarcasResponse - -router = APIRouter() - - -@router.get("/", response_model=List[VehiculosMarcasResponse]) -async def search( - fraccion: Optional[str] = Query(None), - marca: Optional[str] = Query(None), - skip: int = Query(0, ge=0), - limit: int = Query(100, ge=1, le=1000), - current_user: dict = Depends(get_current_user), -): - try: - service = VehiculosMarcasService.get_instance() - return await service.search( - fraccion=fraccion, marca=marca, skip=skip, limit=limit - ) - except Exception as e: - raise HTTPException(status_code=500, detail=str(e)) - - -@router.get("/{sysid}", response_model=VehiculosMarcasResponse) -async def get_by_id(sysid: int, current_user: dict = Depends(get_current_user)): - try: - return await VehiculosMarcasService.get_instance().get_by_id(sysid) - except Exception as e: - raise HTTPException(status_code=404, detail=str(e)) diff --git a/backend/api/v1/modules/sitar/vehiculos_marcas/schemas.py b/backend/api/v1/modules/sitar/vehiculos_marcas/schemas.py deleted file mode 100644 index 0c3ea9b8..00000000 --- a/backend/api/v1/modules/sitar/vehiculos_marcas/schemas.py +++ /dev/null @@ -1,14 +0,0 @@ -"""Vehiculos Marcas Schemas""" - -from typing import Optional -from pydantic import BaseModel, ConfigDict, Field - - -class VehiculosMarcasResponse(BaseModel): - """Marcas de vehículos""" - - FRACCION: Optional[str] = Field(None, max_length=8) - MARCA: Optional[str] = Field(None, max_length=60) - SYSID: int - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/sitar/vehiculos_marcas/service.py b/backend/api/v1/modules/sitar/vehiculos_marcas/service.py deleted file mode 100644 index 46681203..00000000 --- a/backend/api/v1/modules/sitar/vehiculos_marcas/service.py +++ /dev/null @@ -1,36 +0,0 @@ -"""VehiculosMarcas Service""" - -from typing import Optional, List -from ..common import SitarAPIBaseService -from .schemas import VehiculosMarcasResponse - - -class VehiculosMarcasService(SitarAPIBaseService): - _instance: Optional["VehiculosMarcasService"] = None - - @classmethod - def get_instance(cls) -> "VehiculosMarcasService": - if cls._instance is None: - cls._instance = cls() - return cls._instance - - async def search( - self, - fraccion: Optional[str] = None, - marca: Optional[str] = None, - skip: int = 0, - limit: int = 100, - ) -> List[VehiculosMarcasResponse]: - params = {"skip": skip, "limit": min(limit, 1000)} - if fraccion: - params["fraccion"] = fraccion - if marca: - params["marca"] = marca - data = await self._make_request( - "GET", "/api/v1/vehiculos-marcas/", params=params - ) - return [VehiculosMarcasResponse(**item) for item in data] - - async def get_by_id(self, sysid: int) -> VehiculosMarcasResponse: - data = await self._make_request("GET", f"/api/v1/vehiculos-marcas/{sysid}") - return VehiculosMarcasResponse(**data) diff --git a/backend/api/v1/modules/sitar/vehiculos_modelos/__init__.py b/backend/api/v1/modules/sitar/vehiculos_modelos/__init__.py deleted file mode 100644 index e2f516f6..00000000 --- a/backend/api/v1/modules/sitar/vehiculos_modelos/__init__.py +++ /dev/null @@ -1,7 +0,0 @@ -"""VehiculosModelos Module""" - -from .schemas import VehiculosModelosResponse -from .service import VehiculosModelosService -from .router import router - -__all__ = ["VehiculosModelosResponse", "VehiculosModelosService", "router"] diff --git a/backend/api/v1/modules/sitar/vehiculos_modelos/router.py b/backend/api/v1/modules/sitar/vehiculos_modelos/router.py deleted file mode 100644 index 58110333..00000000 --- a/backend/api/v1/modules/sitar/vehiculos_modelos/router.py +++ /dev/null @@ -1,35 +0,0 @@ -"""VehiculosModelos Router""" - -from typing import Optional, List -from fastapi import APIRouter, HTTPException, Query, Depends -from core.security import get_current_user -from .service import VehiculosModelosService -from .schemas import VehiculosModelosResponse - -router = APIRouter() - - -@router.get("/", response_model=List[VehiculosModelosResponse]) -async def search( - fraccion: Optional[str] = Query(None), - marca: Optional[str] = Query(None), - modelo: Optional[str] = Query(None), - skip: int = Query(0, ge=0), - limit: int = Query(100, ge=1, le=1000), - current_user: dict = Depends(get_current_user), -): - try: - service = VehiculosModelosService.get_instance() - return await service.search( - fraccion=fraccion, marca=marca, modelo=modelo, skip=skip, limit=limit - ) - except Exception as e: - raise HTTPException(status_code=500, detail=str(e)) - - -@router.get("/{sysid}", response_model=VehiculosModelosResponse) -async def get_by_id(sysid: int, current_user: dict = Depends(get_current_user)): - try: - return await VehiculosModelosService.get_instance().get_by_id(sysid) - except Exception as e: - raise HTTPException(status_code=404, detail=str(e)) diff --git a/backend/api/v1/modules/sitar/vehiculos_modelos/schemas.py b/backend/api/v1/modules/sitar/vehiculos_modelos/schemas.py deleted file mode 100644 index 1fdb8d7a..00000000 --- a/backend/api/v1/modules/sitar/vehiculos_modelos/schemas.py +++ /dev/null @@ -1,37 +0,0 @@ -"""Vehiculos Modelos Schemas""" - -from typing import Optional -from pydantic import BaseModel, ConfigDict, Field - - -class VehiculosModelosResponse(BaseModel): - """Modelos de vehículos""" - - FRACCION: Optional[str] = Field(None, max_length=8) - MARCA: Optional[str] = Field(None, max_length=60) - MODELO: Optional[str] = Field(None, max_length=80) - TIPO: Optional[str] = Field(None, max_length=1) - CLAVEUM: Optional[str] = Field(None, max_length=2) - A20: Optional[str] = Field(None, max_length=19) - A19: Optional[str] = Field(None, max_length=19) - A18: Optional[str] = Field(None, max_length=19) - A17: Optional[str] = Field(None, max_length=19) - A16: Optional[str] = Field(None, max_length=19) - A15: Optional[str] = Field(None, max_length=19) - A14: Optional[str] = Field(None, max_length=19) - A13: Optional[str] = Field(None, max_length=19) - A12: Optional[str] = Field(None, max_length=19) - A11: Optional[str] = Field(None, max_length=19) - A10: Optional[str] = Field(None, max_length=19) - A9: Optional[str] = Field(None, max_length=19) - A8: Optional[str] = Field(None, max_length=19) - A7: Optional[str] = Field(None, max_length=19) - A6: Optional[str] = Field(None, max_length=19) - A5: Optional[str] = Field(None, max_length=19) - A4: Optional[str] = Field(None, max_length=19) - A3: Optional[str] = Field(None, max_length=19) - A2: Optional[str] = Field(None, max_length=19) - A1: Optional[str] = Field(None, max_length=19) - SYSID: int - - model_config = ConfigDict(from_attributes=True) diff --git a/backend/api/v1/modules/sitar/vehiculos_modelos/service.py b/backend/api/v1/modules/sitar/vehiculos_modelos/service.py deleted file mode 100644 index 8dd18399..00000000 --- a/backend/api/v1/modules/sitar/vehiculos_modelos/service.py +++ /dev/null @@ -1,39 +0,0 @@ -"""VehiculosModelos Service""" - -from typing import Optional, List -from ..common import SitarAPIBaseService -from .schemas import VehiculosModelosResponse - - -class VehiculosModelosService(SitarAPIBaseService): - _instance: Optional["VehiculosModelosService"] = None - - @classmethod - def get_instance(cls) -> "VehiculosModelosService": - if cls._instance is None: - cls._instance = cls() - return cls._instance - - async def search( - self, - fraccion: Optional[str] = None, - marca: Optional[str] = None, - modelo: Optional[str] = None, - skip: int = 0, - limit: int = 100, - ) -> List[VehiculosModelosResponse]: - params = {"skip": skip, "limit": min(limit, 1000)} - if fraccion: - params["fraccion"] = fraccion - if marca: - params["marca"] = marca - if modelo: - params["modelo"] = modelo - data = await self._make_request( - "GET", "/api/v1/vehiculos-modelos/", params=params - ) - return [VehiculosModelosResponse(**item) for item in data] - - async def get_by_id(self, sysid: int) -> VehiculosModelosResponse: - data = await self._make_request("GET", f"/api/v1/vehiculos-modelos/{sysid}") - return VehiculosModelosResponse(**data) diff --git a/backend/api/v1/router.py b/backend/api/v1/router.py index 3bad355d..3571b0ae 100644 --- a/backend/api/v1/router.py +++ b/backend/api/v1/router.py @@ -1,33 +1,20 @@ """ Router principal de API v1 -Agrega todos los módulos de la aplicación (Reload) """ from fastapi import APIRouter -# Importar routers de módulos from .modules.core.router import router as core_router -from .modules.a76.router import router as a76_router -from .modules.a24.router import router as a24_router -from .modules.public.router import router as public_router -from .modules.sitar.router import router as sitar_router +from .modules.example.routes import router as example_router -# Router principal router = APIRouter() -# Registrar módulos router.include_router(core_router) -router.include_router(a76_router) -router.include_router(a24_router) -router.include_router(public_router) -router.include_router(sitar_router, prefix="/sitar") +router.include_router(example_router, prefix="/example", tags=["example"]) -# Health check @router.get("/status") def status(): """Health check de la API""" - return {"status": "DEBUG_ACTIVE", "version": "1.0.0-TEST", "api": "v1"} - - + return {"status": "ok", "version": "1.0.0", "api": "v1"} diff --git a/backend/core/celery_app.py b/backend/core/celery_app.py index 338b5cac..08b1c581 100644 --- a/backend/core/celery_app.py +++ b/backend/core/celery_app.py @@ -19,7 +19,7 @@ logger.info( # Configurar broker y backend explícitamente en el constructor celery_app = Celery( - "anexo76_tasks", + "app_tasks", broker=valkey_url, backend=valkey_url, ) @@ -93,69 +93,12 @@ def _reset_rls_context_from_task(task_id=None, task=None, **_): reset_rls_context_tokens(token_t, token_c) delattr(task, _RLS_TOKENS_ATTR) -# ---------------------------------------------------------------------------- -# Import models in correct order for SQLAlchemy relationship resolution -# MUST happen AFTER celery_app exists to avoid circular imports during -# initialization when models trigger route/task imports. -# ---------------------------------------------------------------------------- -# Orden: PedimentoCode y RegimenPedimento antes de CodePedimentoRegimen (mapper) -from api.v1.modules.public.reference_data.pedimento_codes.models import PedimentoCode -from api.v1.modules.public.reference_data.pedimento_regimens.models import RegimenPedimento -from api.v1.modules.public.reference_data.code_pedimento_regimens.models import ( - CodePedimentoRegimen, -) -# InvoiceType debe cargarse antes de InvoiceHeader (FK invoice_header.invoice_type -> public.invoice_types.key) -from api.v1.modules.public.reference_data.invoice_types.models import InvoiceType # noqa: F401 -# CustomsSection debe cargarse antes de InvoiceComplianceMx (FK invoice_compliance_mx.aduana -> public.customs_sections.customs_code) -from api.v1.modules.public.reference_data.customs_sections.models import CustomsSection # noqa: F401 - -# CRITICAL: FaLineItem must be imported BEFORE LineItem -from api.v1.modules.a24.fa.fa_item_lines.models import FaLineItem # noqa: F401 -from api.v1.modules.a76.items.models import LineItem # noqa: F401 -# CRITICAL: BalanceMovement must be loaded before DischargeDetail (FK a24.balance_movement) -from api.v1.modules.a24.balance_movements.models import BalanceMovement # noqa: F401 -from api.v1.modules.a24.discharges.models import DischargeHeader, DischargeDetail # noqa: F401 - celery_app.conf.update( include=[ - "api.v1.modules.a76.reports.importacion.facturas.task", - "api.v1.modules.a76.reports.importacion.consolidados.task", - "api.v1.modules.a76.reports.importacion.packing_list.task", - "api.v1.modules.a76.reports.exportacion.aviso_consolidado.task", - "api.v1.modules.a76.reports.movements.invoices.tasks", - "api.v1.modules.a76.reports.movements.saldos.tasks", - "api.v1.modules.a76.reports.exportacion.descargo.task", - "api.v1.modules.a76.layouts_csv.facturas.tasks", - "api.v1.modules.a76.layouts_csv.exportacion.tasks", - "api.v1.modules.a76.layouts_csv.cambio_regimen_regularizacion.tasks", - "api.v1.modules.a76.layouts_csv.customs_brokers.tasks", - "api.v1.modules.a76.layouts_csv.clients_and_providers.tasks", - "api.v1.modules.a76.layouts_csv.pedmientos.tasks", - "api.v1.modules.a76.layouts_csv.exchange_rate.tasks", - "api.v1.modules.a76.layouts_csv.us_tariff_fractions.tasks", - "api.v1.modules.a76.layouts_csv.classes.tasks", - "api.v1.modules.a76.layouts_csv.parts.tasks", - "api.v1.modules.a76.layouts_csv.boms.tasks", - "api.v1.modules.a76.layouts_csv.vehicles.tasks", - "api.v1.modules.a76.layouts_csv.drivers.tasks", - "api.v1.modules.a76.layouts_csv.trailers.tasks", - "api.v1.modules.a76.layouts_csv.transportistas.tasks", - "api.v1.modules.a76.reports.exportacion.transmission.MAINX30.task", - "api.v1.modules.a76.reports.importacion.transmission.temporal.MAINX30.task", - "api.v1.modules.a76.reports.importacion.transmission.definitive.MAINX30.task", - "api.v1.modules.a76.reports.importacion.winsaai.invoices.task", - "api.v1.modules.a76.reports.importacion.winsaai.pedimentos.task", "api.v1.modules.core.help_center.tasks", - "api.v1.modules.core.help_center.tasks", - "api.v1.modules.a76.invoices.imports.process.task", - "api.v1.modules.a76.invoices.imports.revert.task", - "api.v1.modules.a76.invoices.exports.process.task", - "api.v1.modules.a76.invoices.exports.revert.task", - "api.v1.modules.a76.layouts_csv.common.victor", - "api.v1.modules.a76.factura_cove.tasks", - "api.v1.modules.a76.expediente_archivos.tasks", - "api.v1.modules.a76.reports.xml_rb_systems.task", - ] # Ruta al módulo donde están las tareas + # Agrega aquí las tareas de tu proyecto: + # "api.v1.modules.example.tasks", + ] ) # Configuraciones adicionales diff --git a/backend/core/config.py b/backend/core/config.py index 0bcb52a9..ff4be272 100644 --- a/backend/core/config.py +++ b/backend/core/config.py @@ -11,16 +11,24 @@ class Settings(BaseSettings): """Configuración de la aplicación""" # Application - APP_NAME: str = "Anexo76" + APP_NAME: str = "Mi Aplicación" # Sobreescribible con APP_VERSION (Dockerfile/Jenkins: build-arg + ENV) o entorno en runtime APP_VERSION: str = "dev-local" DEBUG: bool = True ENVIRONMENT: str = "development" + # Auth local para desarrollo (sin Keycloak/Hub) + # Nunca activar en producción. + DEV_LOCAL_AUTH: bool = False + DEV_LOCAL_AUTH_EMAIL: str = "dev@local.test" + DEV_LOCAL_AUTH_NAME: str = "Dev User" + DEV_LOCAL_AUTH_TENANT_ID: int = 1 + DEV_LOCAL_AUTH_COMPANY_ID: int = 1 + # Database - Core (Shared) - CORE_DB_HOST: str = "postgres-a76" + CORE_DB_HOST: str = "postgres" CORE_DB_PORT: int = 5432 - CORE_DB_NAME: str = "anexo76_core" + CORE_DB_NAME: str = "app_core" CORE_DB_USER: str = "postgres" CORE_DB_PASSWORD: str = "postgres" @@ -80,7 +88,7 @@ class Settings(BaseSettings): SMTP_PORT: int = 587 SMTP_USER: str = "" SMTP_PASSWORD: str = "" - SMTP_FROM_NAME: str = "Sistema Anexo76" + SMTP_FROM_NAME: str = "Mi Aplicación" SMTP_USE_TLS: bool = True # CSV imports (layouts_csv): redis = base64 en Valkey; minio = S3 + referencia en Redis @@ -88,7 +96,7 @@ class Settings(BaseSettings): S3_ENDPOINT_URL: str = "http://minio:9000" S3_ACCESS_KEY: str = "" S3_SECRET_KEY: str = "" - S3_BUCKET: str = "anexo76" + S3_BUCKET: str = "app" S3_REGION: str = "us-east-1" S3_USE_SSL: bool = False # Logos, certificados, help (si no quieres MinIO aquí, pon false Y CSV_IMPORT_STORAGE=redis) diff --git a/backend/core/email.py b/backend/core/email.py index c0880988..79b46497 100644 --- a/backend/core/email.py +++ b/backend/core/email.py @@ -52,7 +52,7 @@ class EmailService:

- Reporte de Facturas - Sistema Anexo76 + Reporte de Facturas

{body_text}

diff --git a/backend/core/exceptions.py b/backend/core/exceptions.py index 1d457804..6c73e4f7 100644 --- a/backend/core/exceptions.py +++ b/backend/core/exceptions.py @@ -1,5 +1,5 @@ """ -Sistema centralizado de excepciones personalizadas para Anexo76 +Sistema centralizado de excepciones personalizadas """ from typing import Optional, List, Dict, Any diff --git a/backend/core/middleware.py b/backend/core/middleware.py index caa441f4..89d70014 100644 --- a/backend/core/middleware.py +++ b/backend/core/middleware.py @@ -119,6 +119,10 @@ class LicenseValidationMiddleware(BaseHTTPMiddleware): Fail-closed: si el Hub no responde o la licencia es inválida, se bloquea el acceso. """ async def dispatch(self, request: Request, call_next: Callable): + # En modo local (DEV_LOCAL_AUTH) no hay Hub — saltar validación de licencia. + if settings.DEV_LOCAL_AUTH: + return await call_next(request) + exempt_paths = [ "/api/docs", "/api/redoc", "/openapi.json", "/api/v1/auth", "/api/v1/status", "/api/health", diff --git a/backend/core/security.py b/backend/core/security.py index 215c5ac7..cb188c99 100644 --- a/backend/core/security.py +++ b/backend/core/security.py @@ -33,27 +33,32 @@ _tenant_id_hub_by_local: Dict[int, int] = {} # Security scheme security = HTTPBearer() -# Sistemas válidos del selector de app (SCAF / SCAII) -_VALID_SYSTEMS = frozenset(("fixed_asset", "inventory")) - - def get_active_system(request: Request) -> Optional[str]: - """Sistema activo (SCAF/SCAII): header ``X-Active-System`` (autoritativo del cliente) - con fallback a la cookie ``active_system``. No depende de ``request.state`` porque - ``TenantMiddleware`` hace short-circuit en rutas ``/api/``.""" - value = request.headers.get("x-active-system") or request.cookies.get("active_system") - return value if value in _VALID_SYSTEMS else None + """Sistema activo: header ``X-Active-System`` o cookie ``active_system``.""" + return request.headers.get("x-active-system") or request.cookies.get("active_system") or None async def verify_token(token: str, tenant_id_override: str = None) -> Dict[str, Any]: """ Verifica un token JWT llamando al Hub central. + Si DEV_LOCAL_AUTH=True y el token es HS256 local, lo verifica sin Hub. """ - # Cache key incluye el override para que distintos tenants no se mezclen cache_key = (token, tenant_id_override) if cache_key in token_cache: return token_cache[cache_key] + # Shortcut para tokens de desarrollo local + if settings.DEV_LOCAL_AUTH: + try: + header = jwt.get_unverified_header(token) + if header.get("alg") == "HS256": + payload = jwt.decode(token, settings.SECRET_KEY, algorithms=["HS256"]) + if payload.get("dev_local"): + token_cache[cache_key] = payload + return payload + except JWTError as e: + raise HTTPException(status_code=401, detail=f"Dev token inválido: {e}") + try: headers: Dict[str, str] = {"Authorization": f"Bearer {token}"} if tenant_id_override: @@ -69,7 +74,7 @@ async def verify_token(token: str, tenant_id_override: str = None) -> Dict[str, user_info = response.json() token_cache[cache_key] = user_info return user_info - + logger.error(f"Hub token verification failed with status {response.status_code}") raise HTTPException(status_code=401, detail="Could not validate credentials") @@ -119,76 +124,14 @@ def _ensure_company_exists( hub_user: Optional[Dict[str, Any]] = None, ) -> None: """ - Garantiza al menos una empresa en a76.company para el tenant. + STUB — implementa este método con el modelo de compañía de tu proyecto. - Primera vez: CompanyService.create_company_manually (seed catálogos por compañía), - relación user_tenants y bootstrap_super_admin para el usuario del token. + Debe garantizar que exista al menos una empresa para el tenant y que el + usuario del token (hub_user["sub"]) tenga un registro en user_tenants. """ - from api.v1.modules.a76.general_catalogs.company.models import Company - from api.v1.modules.a76.general_catalogs.company.dto import CompanyCreateDTO - from api.v1.modules.a76.general_catalogs.company.service import CompanyService - from api.v1.modules.core.permissions.service import PermissionService - from api.v1.modules.core.user_tenant.models import UserTenant - - kc = hub_user.get("sub") if hub_user else None - - try: - company = db.query(Company).filter(Company.tenant_id == tenant_id).first() - - if not company: - svc = CompanyService(db) - username = "System" - if hub_user: - username = ( - hub_user.get("preferred_username") - or hub_user.get("email") - or hub_user.get("name") - or "System" - ) - company = svc.create_company_manually( - CompanyCreateDTO(name=tenant_name), - tenant_id=tenant_id, - username=username, - ) - logger.info( - "Empresa creada vía CompanyService para tenant id=%s name=%r company_id=%s", - tenant_id, - tenant_name, - company.id, - ) - - if not company: - logger.warning( - "No hay empresa para tenant %s tras intento de creación automática", tenant_id - ) - return - - if company.name != tenant_name: - company.name = tenant_name - db.commit() - logger.info("Empresa actualizada para tenant id=%s: '%s'", tenant_id, tenant_name) - - if kc: - ut = ( - db.query(UserTenant) - .filter( - UserTenant.keycloak_user_id == kc, - UserTenant.tenant_id == tenant_id, - UserTenant.company_id == company.id, - ) - .first() - ) - if not ut: - _ensure_user_tenant_for_company(db, kc, tenant_id, company.id) - PermissionService(db).bootstrap_super_admin(kc, company.id) - - except HTTPException: - raise - except Exception as e: - db.rollback() - logger.warning( - "No se pudo asegurar empresa/usuario para tenant %s: %s", tenant_id, e - ) + logger.debug( + "_ensure_company_exists: no implementado en la plantilla (tenant_id=%s)", tenant_id + ) def _repair_user_company_link_if_needed( @@ -196,41 +139,14 @@ def _repair_user_company_link_if_needed( tenant_id_effective: int, hub_user: Optional[Dict[str, Any]], ) -> None: - """Si ya hay empresa pero el usuario no tiene user_tenants, enlaza y hace bootstrap.""" + """STUB — implementa con el modelo de compañía de tu proyecto.""" if not hub_user or not hub_user.get("sub"): return - from api.v1.modules.a76.general_catalogs.company.models import Company from api.v1.modules.core.permissions.service import PermissionService from api.v1.modules.core.user_tenant.models import UserTenant - kc = hub_user["sub"] - company = ( - db.query(Company) - .filter(Company.tenant_id == tenant_id_effective) - .first() - ) - if not company: - return - ut = ( - db.query(UserTenant) - .filter( - UserTenant.keycloak_user_id == kc, - UserTenant.tenant_id == tenant_id_effective, - UserTenant.company_id == company.id, - ) - .first() - ) - if ut: - return - try: - _ensure_user_tenant_for_company(db, kc, tenant_id_effective, company.id) - PermissionService(db).bootstrap_super_admin(kc, company.id) - except Exception as e: - logger.warning( - "No se pudo reparar enlace usuario-compañía tenant=%s: %s", - tenant_id_effective, - e, - ) + # Sin modelo de compañía en la plantilla, no hay empresa que verificar. + # Implementa esta función cuando definas tu tabla de compañías. def _ensure_tenant_synced( @@ -342,6 +258,10 @@ async def get_current_user( # Copia local para poder normalizar tenant_id sin mutar el objeto cacheado user_info = dict(user_info) + # Modo local: el token ya trae todo. Saltar sincronización con el Hub. + if settings.DEV_LOCAL_AUTH and user_info.get("dev_local"): + return user_info + # Sincronizar tenant desde Hub a BD local (solo la primera vez por tenant) tenant_id = user_info.get("tenant_id") tenant_slug = user_info.get("tenant_slug") @@ -552,24 +472,8 @@ def resolve_tenant_id_required( raise HTTPException(status_code=400, detail="Invalid tenant ID in token") if is_hub_admin(current_user): - if db is not None and company_id is not None: - from sqlalchemy.exc import SQLAlchemyError as _SAError - try: - from api.v1.modules.a76.general_catalogs.company.models import Company - company = db.query(Company).filter(Company.id == company_id).first() - if company and company.tenant_id: - return int(company.tenant_id) - except _SAError as exc: - # Un error de BD no debe escalar silenciosamente a acceso global - logger.error( - "Error de BD al resolver tenant para hub_admin company_id=%s: %s", - company_id, - exc, - ) - raise HTTPException( - status_code=500, detail="Error al resolver el tenant_id" - ) - # Sin company_id disponible → sentinel None para acceso global sin filtro de tenant + # Sin modelo de compañía en la plantilla → acceso global sin filtro de tenant. + # Implementa la consulta a tu tabla de compañías si necesitas resolución exacta. return None raise HTTPException(status_code=400, detail="Tenant ID not found in user data") @@ -717,26 +621,9 @@ def validate_company_access( if not tenant_id: return False - try: - from api.v1.modules.a76.general_catalogs.company.models import Company - - company = ( - db.query(Company) - .filter(Company.id == company_id, Company.tenant_id == tenant_id) - .first() - ) - - if not company: - logger.warning( - "validate_company_access: No se encontró la compañía %s para el tenant %s", - company_id, - tenant_id, - ) - - return company is not None - except Exception as e: - logger.error("Error validating company access: %s", e) - return False + # Sin modelo de compañía en la plantilla → delega solo en user_has_app_company_membership. + # Implementa la consulta a tu tabla de compañías para validación estricta. + return True def validate_access_to_resource( @@ -783,19 +670,8 @@ def validate_access_to_resource( if not validate_company_access(db, company_id, current_user): raise HTTPException(status_code=403, detail="Access denied to this company") - # Si no hay tenant_id, intentamos recuperarlo de la empresa - if not tenant_id: - from sqlalchemy.exc import SQLAlchemyError as _SAError - try: - from api.v1.modules.a76.general_catalogs.company.models import Company - company = db.query(Company).filter(Company.id == company_id).first() - if company: - tenant_id = company.tenant_id - except _SAError as exc: - logger.error( - "Error de BD al resolver tenant company_id=%s: %s", company_id, exc - ) - raise HTTPException(status_code=500, detail="Error al resolver el tenant_id") + # Sin modelo de compañía en la plantilla no se puede resolver tenant_id desde company. + # Implementa esta lógica cuando definas tu tabla de compañías. # Si aún no hay tenant_id y no es admin, error 400 if not tenant_id and not is_global_admin and not is_me_endpoint: diff --git a/backend/main.py b/backend/main.py index 2fc08859..f4a7990e 100644 --- a/backend/main.py +++ b/backend/main.py @@ -1,5 +1,5 @@ """ -Anexo76 - Aplicación SaaS para gestión de comercio exterior +Mi Aplicación Backend API con FastAPI + Keycloak + SQLAlchemy """ @@ -13,8 +13,6 @@ from contextlib import asynccontextmanager # Core Modules (Secondary) import core.celery_app # Initialize Celery App -from api.v1.modules.a76.audit_log.middleware import UserContextMiddleware # Middleware de Contexto de Usuario (Audit Log) -from api.v1.modules.a76.audit_log.register import register_audit from api.v1.router import router as api_v1_router from core.config import settings from core.storage_s3 import ensure_s3_bucket @@ -34,9 +32,9 @@ logging.basicConfig( # Crear aplicación FastAPI app = FastAPI( - title="Anexo76 API", + title="Mi Aplicación API", version=settings.APP_VERSION, - description="Aplicación SaaS para gestión de comercio exterior conforme a Anexos 24, 30 y 22 del SAT", + description="Aplicación web multi-tenant con autenticación Workspace", docs_url="/api/docs" if settings.DEBUG else None, redoc_url="/api/redoc" if settings.DEBUG else None, openapi_url="/api/openapi.json" if settings.DEBUG else None, @@ -53,7 +51,7 @@ def run_migrations(): # Inicializar la base de datos async def on_startup(): """Evento de inicio de la aplicación""" - logger.info("Iniciando la aplicación Anexo76...") + logger.info("Iniciando la aplicación...") #init_db() run_migrations() if settings.use_s3_object_storage: @@ -67,7 +65,6 @@ if settings.DEBUG: app.add_middleware(TenantMiddleware) app.add_middleware(LicenseValidationMiddleware) -app.add_middleware(UserContextMiddleware) # CORS debe ser el último en añadirse para que sea el más externo # y cubra todas las respuestas, incluyendo las de los middlewares internos @@ -83,7 +80,6 @@ app.add_middleware( async def lifespan(app: FastAPI): # Centraliza startup para evitar on_event() (deprecated en FastAPI) await on_startup() - register_audit() yield app.router.lifespan_context = lifespan @@ -106,7 +102,7 @@ app.include_router(api_v1_router, prefix="/api/v1") async def root(): """Root endpoint""" return { - "name": "Anexo76 API", + "name": "Mi Aplicación API", "version": settings.APP_VERSION, "status": "running", "docs": "/api/docs" if settings.DEBUG else "disabled in production", diff --git a/backend/tests/README.md b/backend/tests/README.md deleted file mode 100644 index ad75670f..00000000 --- a/backend/tests/README.md +++ /dev/null @@ -1,158 +0,0 @@ -# Backend test strategy (Anexo24) - -## Test suites - -- `tests/e2e/`: full business flow (catalogs -> import -> export -> balances/discharges). -- `tests/integration/`: process API behavior and edge cases. -- `tests/unit/`: critical algorithms only (no trivial CRUD tests). - ---- - -## Vista del flujo de tests (backend) - -### Dónde vive todo - -| Capa | Ruta | Rol | -|------|------|-----| -| Infra | [conftest.py](conftest.py) | DB en transacción + rollback, Celery eager, app mínima con router de process, overrides de DB/usuario, tareas import/export **inline** en la misma sesión | -| Datos | [fixtures/builders.py](fixtures/builders.py) | Catálogos, tenant/company, facturas import/export con líneas enlazadas | -| E2E | [e2e/test_inventory_flow_anexo24.py](e2e/test_inventory_flow_anexo24.py) | Flujo negocio completo import → export → saldos/descargas | -| Integración | [integration/test_import_process_api.py](integration/test_import_process_api.py), [integration/test_export_process_api.py](integration/test_export_process_api.py) | API HTTP + reglas por tipo de factura, saldo, doble proceso | -| Unit | [unit/invoices/test_balance_algorithm.py](unit/invoices/test_balance_algorithm.py), [unit/invoices/test_currency_conversion_imports.py](unit/invoices/test_currency_conversion_imports.py) | Algoritmos puros / subprocesos sin HTTP | - -### Infraestructura (flujo común) - -```mermaid -flowchart TB - subgraph fixtures [Fixtures conftest] - db[db_session: conexion + begin + Session] - roll[teardown: rollback + close] - celery[celery_eager autouse: memory broker] - app[app: FastAPI + process router] - client[TestClient] - end - db --> roll - celery --> app - db --> app - app --> client - subgraph inline [Monkeypatch rutas] - imp[process_invoice_task -> main_process import en db_session] - exp[process_export_invoice_task -> main_process export en db_session] - end - app --> inline -``` - -- **BD**: `TEST_DATABASE_URL` o `CORE_DATABASE_URL` o `settings.core_database_url`. Cada test corre dentro de una transacción; al terminar **no persiste** nada (rollback). -- **Celery**: sin Redis; `task_always_eager` para que el endpoint dispare lógica síncrona. -- **Seguridad en tests**: `validate_access_to_resource` se reemplaza por un stub que devuelve `tenant_id` del usuario fake (alineado con el fixture `test_tenant`). - -### Builders: qué se construye antes de llamar a la API - -Orden típico en casi todos los tests que tocan proceso: - -1. `ensure_reference_data`: tipos de factura (`TEM`, `DEF`, `MEX`, `DONAC`) y régimen `A1` si no existen. -2. Fixture `test_tenant`: asigna IDs efímeros altos y crea tenant + company; el `app` usa ese `tenant_id` en el usuario fake. -3. `create_business_catalogs`: proveedor/cliente/destinatario (con dirección idempotente vía `_ensure_client_provider_address`), agente aduanal, UoM, part number FA, tipo cambio, etc. -4. `create_import_invoice_with_line` / `create_export_invoice_with_line` según el caso. - -### Parches al pipeline (import / export) - -Los tests **no** ejecutan validadores pesados del proceso real; sustituyen funciones en `main_process` para aislar el comportamiento de saldos y API: - -**Import** (varios tests): `pre_validators` devuelve la línea creada; se anulan reviews de clase, tipo de cambio, pesos, SISIMP, `_validate_lines`. - -**Export**: análogo con `pre_validators` + reviews de clase, TC, asignación, cantidad vs peso, costo, series. - -**Caso especial** — `test_process_endpoint_prevents_double_processing_import` en [integration/test_export_process_api.py](integration/test_export_process_api.py): usa `_patch_import_pipeline_without_prevalidators` (sin parchear `pre_validators`) para que el **segundo** `POST /process` recorra el flujo real y dispare la validación de “ya procesado”. - -### Mapa por archivo y edge cases - -#### E2E — [e2e/test_inventory_flow_anexo24.py](e2e/test_inventory_flow_anexo24.py) - -- **Flujo**: datos ref + tenant/company + catálogos → factura import `TEM` qty 10 → `POST .../process` → comprueba `BalanceMovement` tipo `ENTRY` → factura export ligada a import/línea qty 4 → `POST process` → comprueba `CONSUMPTION`, `DischargeHeader`, `DischargeDetail` con `movement_id`. -- **Edge implícito**: saldo neto por línea de import ≥ 0 y < 10 tras consumo parcial (no agota todo el inventario del escenario). - -#### Integración import — [integration/test_import_process_api.py](integration/test_import_process_api.py) - -| Test | Qué valida | -|------|------------| -| `test_process_import_endpoint_creates_balance_entries` | `TEM` + process → al menos un `ENTRY` por factura/línea | -| `test_process_import_def_does_not_create_balance_entries` | `DEF` + process → **ningún** movimiento de saldo (edge: tipo operación no genera entradas de balance) | - -#### Integración export + doble proceso — [integration/test_export_process_api.py](integration/test_export_process_api.py) - -| Test | Qué valida | -|------|------------| -| `_prepare_inventory` + `test_process_export_endpoint_consumes_existing_balances` | Tras import `TEM` qty 10, export qty 3 → existen `CONSUMPTION` | -| `test_process_export_prevents_negative_balance` | Export qty 99 vs saldo 10 → `ValidationException` y **cero** consumos para esa factura export | -| `test_process_endpoint_prevents_double_processing_import` | Segundo `POST` misma factura import → `ValidationException`; exactamente **un** `ENTRY` (no duplicar PEPS) | - -#### Unit saldos / FIFO — [unit/invoices/test_balance_algorithm.py](unit/invoices/test_balance_algorithm.py) - -- **`test_net_balance_accounts_for_returns_and_entry_void`**: `_net_balance_for_lot` con ENTRY, CONSUMPTION, RETURN, ENTRY_VOID (fórmula documentada en el docstring: 10 - 4 + 1 - 2 = 5). -- **`test_fifo_consumption_algorithm_uses_oldest_lots_first`**: `compare_balances` con dos `AvailableLot` (órdenes 1 y 2), demanda 6; parche `_resolve_import_uom` → PZA; comprueba consumo 3+3 y `quantity_used == 6`. - -#### Unit IVA / moneda — [unit/invoices/test_currency_conversion_imports.py](unit/invoices/test_currency_conversion_imports.py) - -- Tres monedas en `assign_values_iva_lines`: **FOREIGN (ME)**, **LOCAL (MN)**, **MANUAL (MC)** con `SimpleNamespace` (sin BD). - -### Diagrama de flujo de negocio (E2E + integración export) - -```mermaid -sequenceDiagram - participant T as Test - participant API as POST process - participant DB as Session PostgreSQL - T->>DB: seed ref + catalogs + import invoice - T->>API: process import - API->>DB: ENTRY movements - T->>DB: export invoice linked - T->>API: process export - alt qty OK - API->>DB: CONSUMPTION + discharges - else qty over balance - API-->>T: ValidationException - end -``` - -### Resumen de edge cases explícitos en la suite - -- **Tipo factura import**: `TEM` genera saldo; `DEF` no. -- **Saldo insuficiente en export**: error y sin consumos. -- **Doble procesamiento import**: error en segundo POST y una sola entrada. -- **Neteo de movimientos**: retornos y anulaciones de entrada en el cálculo de saldo. -- **FIFO**: reparto entre lotes por `order_peps`. -- **Moneda / IVA**: tres modos de captura y conversión esperada. - -### BD compartida / CI - -Si `TEST_DATABASE_URL` apunta a una base con datos previos, los builders reutilizan filas existentes donde aplica (por ejemplo direcciones de clientes vía `_ensure_client_provider_address`). Para máximo aislamiento, usar una BD o esquema dedicado a tests. - ---- - -## Prerequisites - -- PostgreSQL test database available. -- Environment variable: - - `TEST_DATABASE_URL=postgresql://user:pass@127.0.0.1:5432/db_name` (sustituye por el host real alcanzable desde el runner; no uses el literal `host` de los ejemplos genéricos) - -## Run commands - -- Fast subset (CI gate): - - `pytest -q tests/unit tests/integration` -- Full suite: - - `pytest -q tests` - - `pytest tests -v -ra` - - `pytest tests -v -ra -s` -- E2E only: - - `pytest -q tests/e2e` - -## CI recommendations - -- Pull request gate: - - Run unit + integration on every PR. -- Nightly / main branch: - - Run full suite including E2E. -- Keep Celery eager mode for deterministic process endpoint tests: - - `task_always_eager=True` - - `task_eager_propagates=True` diff --git a/backend/tests/__init__.py b/backend/tests/__init__.py deleted file mode 100644 index 8b137891..00000000 --- a/backend/tests/__init__.py +++ /dev/null @@ -1 +0,0 @@ - diff --git a/backend/tests/conftest.py b/backend/tests/conftest.py deleted file mode 100644 index f9ec607e..00000000 --- a/backend/tests/conftest.py +++ /dev/null @@ -1,157 +0,0 @@ -from collections.abc import Generator - -import pytest -from fastapi import FastAPI -from fastapi.testclient import TestClient -from sqlalchemy import create_engine -from sqlalchemy.orm import Session, sessionmaker - -# Importar modelos con relaciones string para registrar mappers antes de tests. -# Evita errores tipo: expression 'Pedimentos' failed to locate a name. -from api.v1.modules.a76.pedmientos.models.pedimentos import Pedimentos # noqa: F401 -from api.v1.modules.a76.invoices.models import InvoiceHeader -from api.v1.modules.a76.transportation.trailers.models import Trailer # noqa: F401 -from api.v1.modules.a76.transportation.transporters.models import Transporter # noqa: F401 -from api.v1.modules.a76.transportation.vehicles.models import Vehicle # noqa: F401 -from api.v1.modules.a76.invoices.imports.process import routes as process_routes -from api.v1.modules.a76.invoices.imports.process import main_process as import_main_process -from api.v1.modules.a76.invoices.exports.process import main_process as export_main_process -from core.celery_app import celery_app -from core.config import settings -from core.database import get_core_db -from core.security import get_current_user -from tests.fixtures.builders import ( - EphemeralOrgIds, - allocate_ephemeral_tenant_company_ids, - ensure_tenant_company, -) - - -TEST_DB_URL = ( - __import__("os").environ.get("TEST_DATABASE_URL") - or __import__("os").environ.get("CORE_DATABASE_URL") - or settings.core_database_url -) - -engine = create_engine(TEST_DB_URL, future=True) -TestingSessionLocal = sessionmaker( - bind=engine, - autoflush=False, - autocommit=False, - expire_on_commit=False, - class_=Session, - join_transaction_mode="create_savepoint", -) - - -@pytest.fixture -def db_session() -> Generator[Session, None, None]: - connection = engine.connect() - transaction = connection.begin() - session = TestingSessionLocal(bind=connection) - try: - yield session - finally: - session.close() - transaction.rollback() - connection.close() - - -@pytest.fixture -def test_tenant(db_session: Session) -> EphemeralOrgIds: - """Tenant y company dedicados por test (IDs efímeros en rango alto).""" - tid, cid = allocate_ephemeral_tenant_company_ids(db_session) - ensure_tenant_company(db_session, tenant_id=tid, company_id=cid) - return EphemeralOrgIds(tenant_id=tid, company_id=cid) - - -@pytest.fixture(autouse=True) -def celery_eager() -> Generator[None, None, None]: - prev_broker = celery_app.conf.broker_url - prev_backend = celery_app.conf.result_backend - prev_always_eager = celery_app.conf.task_always_eager - prev_propagates = celery_app.conf.task_eager_propagates - prev_store_result = celery_app.conf.task_store_eager_result - prev_ignore_result = celery_app.conf.task_ignore_result - - # Aislar Celery de infraestructura externa en tests (sin Redis/Valkey). - celery_app.conf.broker_url = "memory://" - celery_app.conf.result_backend = "cache+memory://" - celery_app.conf.task_always_eager = True - celery_app.conf.task_eager_propagates = True - celery_app.conf.task_store_eager_result = False - celery_app.conf.task_ignore_result = True - try: - yield - finally: - celery_app.conf.broker_url = prev_broker - celery_app.conf.result_backend = prev_backend - celery_app.conf.task_always_eager = prev_always_eager - celery_app.conf.task_eager_propagates = prev_propagates - celery_app.conf.task_store_eager_result = prev_store_result - celery_app.conf.task_ignore_result = prev_ignore_result - - -@pytest.fixture -def app( - db_session: Session, - test_tenant: EphemeralOrgIds, - monkeypatch: pytest.MonkeyPatch, -) -> FastAPI: - test_app = FastAPI() - test_app.include_router(process_routes.router, prefix="/api/v1/a76") - - def _override_get_db() -> Generator[Session, None, None]: - yield db_session - - async def _override_current_user(): - return {"sub": "test-user", "tenant_id": test_tenant.tenant_id} - - # validate_access_to_resource is imported directly in the routes module. - def _override_validate_access_to_resource(db, company_id, current_user, required_permissions=None, **kwargs): - return int(current_user["tenant_id"]) - - monkeypatch.setattr(process_routes, "validate_access_to_resource", _override_validate_access_to_resource) - - class _InlineResult: - def __init__(self, task_id: str): - self.id = task_id - - def _run_import_inline(*, args=None, **kwargs): - if args is None: - args = [] - invoice_id, tenant_id, company_id = args - invoice = db_session.get(InvoiceHeader, int(invoice_id)) - if invoice is None: - return _InlineResult("missing-import-invoice") - import_main_process.main_process(db_session, invoice, str(tenant_id), str(company_id)) - db_session.flush() - return _InlineResult(f"inline-import-{invoice_id}") - - def _run_export_inline(*, args=None, **kwargs): - if args is None: - args = [] - invoice_id, tenant_id, company_id = args - invoice = db_session.get(InvoiceHeader, int(invoice_id)) - if invoice is None: - return _InlineResult("missing-export-invoice") - export_main_process.main_process(db_session, invoice, str(tenant_id), str(company_id)) - db_session.flush() - return _InlineResult(f"inline-export-{invoice_id}") - - class _InlineTask: - def __init__(self, runner): - self.apply_async = runner - - # Evita worker/redis y obliga ejecución inline con la misma sesión. - monkeypatch.setattr(process_routes, "process_invoice_task", _InlineTask(_run_import_inline)) - monkeypatch.setattr(process_routes, "process_export_invoice_task", _InlineTask(_run_export_inline)) - - test_app.dependency_overrides[get_core_db] = _override_get_db - test_app.dependency_overrides[get_current_user] = _override_current_user - return test_app - - -@pytest.fixture -def client(app: FastAPI) -> TestClient: - return TestClient(app) diff --git a/backend/tests/e2e/test_inventory_flow_anexo24.py b/backend/tests/e2e/test_inventory_flow_anexo24.py deleted file mode 100644 index 0daab7a8..00000000 --- a/backend/tests/e2e/test_inventory_flow_anexo24.py +++ /dev/null @@ -1,111 +0,0 @@ -from decimal import Decimal - -from api.v1.modules.a24.balance_movements.models import BalanceMovement, MovementType -from api.v1.modules.a24.discharges.models import DischargeDetail, DischargeHeader -from api.v1.modules.a76.invoices.imports.process import main_process as import_main -from api.v1.modules.a76.invoices.exports.process import main_process as export_main -from tests.fixtures.builders import ( - create_business_catalogs, - create_export_invoice_with_line, - create_import_invoice_with_line, - ensure_reference_data, -) - - -def _patch_import_pipeline(monkeypatch, import_line): - monkeypatch.setattr(import_main, "pre_validators", lambda *args, **kwargs: [import_line]) - monkeypatch.setattr(import_main, "review_classes", lambda *args, **kwargs: None) - monkeypatch.setattr(import_main, "review_exchange_rate", lambda *args, **kwargs: None) - monkeypatch.setattr(import_main, "review_weights_kgs", lambda *args, **kwargs: None) - monkeypatch.setattr(import_main, "review_weights_lbs", lambda *args, **kwargs: None) - monkeypatch.setattr(import_main, "_validate_sisimp_limits", lambda *args, **kwargs: None) - monkeypatch.setattr(import_main, "_validate_lines", lambda *args, **kwargs: ([], {})) - - -def _patch_export_pipeline(monkeypatch, export_line): - monkeypatch.setattr(export_main, "pre_validators", lambda *args, **kwargs: [export_line]) - monkeypatch.setattr(export_main, "review_class", lambda *args, **kwargs: None) - monkeypatch.setattr(export_main, "review_exchange_rate", lambda *args, **kwargs: None) - monkeypatch.setattr(export_main, "assign_values", lambda *args, **kwargs: None) - monkeypatch.setattr(export_main, "review_qty_vs_weight", lambda *args, **kwargs: None) - monkeypatch.setattr(export_main, "review_unit_cost", lambda *args, **kwargs: None) - monkeypatch.setattr(export_main, "review_qty_series", lambda *args, **kwargs: None) - - -def test_e2e_inventory_flow_import_then_export(client, db_session, monkeypatch, test_tenant): - """ - E2E principal: - - Alta catálogos/base - - Procesa factura IMP -> genera entry - - Procesa factura EXP -> genera consumo/descarga - - Valida decremento de saldo sin negativos - """ - tid, cid = test_tenant.tenant_id, test_tenant.company_id - ensure_reference_data(db_session) - catalogs = create_business_catalogs(db_session, tenant_id=tid, company_id=cid) - - import_invoice, import_line = create_import_invoice_with_line( - db_session, tid, cid, catalogs, invoice_type="TEM", invoice_number="IMP-E2E-01", qty=Decimal("10") - ) - _patch_import_pipeline(monkeypatch, import_line) - - response = client.post(f"/api/v1/a76/invoices/{import_invoice.id}/process?company_id={cid}") - assert response.status_code == 200 - - entry_movements = ( - db_session.query(BalanceMovement) - .filter( - BalanceMovement.import_invoice_id == import_invoice.id, - BalanceMovement.import_item_line_id == import_line.id, - BalanceMovement.movement_type == MovementType.ENTRY, - ) - .all() - ) - assert entry_movements - assert sum(Decimal(str(m.quantity or 0)) for m in entry_movements) > 0 - - export_invoice, export_line = create_export_invoice_with_line( - db_session, tid, cid, catalogs, import_invoice, import_line, qty=Decimal("4") - ) - _patch_export_pipeline(monkeypatch, export_line) - - response = client.post(f"/api/v1/a76/invoices/{export_invoice.id}/process?company_id={cid}") - assert response.status_code == 200 - - consumptions = ( - db_session.query(BalanceMovement) - .filter( - BalanceMovement.source_invoice_id == export_invoice.id, - BalanceMovement.movement_type == MovementType.CONSUMPTION, - ) - .all() - ) - assert consumptions, "El proceso de exportación debe generar consumos" - - discharge_headers = ( - db_session.query(DischargeHeader) - .filter(DischargeHeader.source_invoice_id == export_invoice.id) - .all() - ) - assert discharge_headers - - discharge_details = ( - db_session.query(DischargeDetail) - .filter(DischargeDetail.export_item_line_id == export_line.id) - .all() - ) - assert discharge_details - assert all(detail.movement_id is not None for detail in discharge_details) - - net_balance = db_session.query(BalanceMovement).filter( - BalanceMovement.import_item_line_id == import_line.id - ).all() - signed = Decimal("0") - for mov in net_balance: - qty = Decimal(str(mov.quantity or 0)) - if mov.movement_type in {MovementType.CONSUMPTION, MovementType.WASTE, MovementType.SCRAP, MovementType.DESTRUCTION}: - signed -= qty - else: - signed += qty - assert signed >= 0 - assert signed < Decimal("10") diff --git a/backend/tests/fixtures/__init__.py b/backend/tests/fixtures/__init__.py deleted file mode 100644 index 8b137891..00000000 --- a/backend/tests/fixtures/__init__.py +++ /dev/null @@ -1 +0,0 @@ - diff --git a/backend/tests/fixtures/builders.py b/backend/tests/fixtures/builders.py deleted file mode 100644 index 15cf6800..00000000 --- a/backend/tests/fixtures/builders.py +++ /dev/null @@ -1,457 +0,0 @@ -from __future__ import annotations - -import uuid -from datetime import date, datetime -from decimal import Decimal -from typing import NamedTuple - -from sqlalchemy.orm import Session - -from api.v1.modules.a24.fa.fa_item_lines.models import FaLineItem -from api.v1.modules.a76.clients_and_providers.models import ( - ClientOrProviderEnum, - ClientProvider, - ClientProviderAddress, -) -from api.v1.modules.a76.customs_brokers.models import CustomsBroker -from api.v1.modules.a76.general_catalogs.company.models import Company -from api.v1.modules.a76.general_catalogs.exchange_rate.models import ExchangeRate -from api.v1.modules.a76.general_catalogs.units_of_measure.models import UnitOfMeasure -from api.v1.modules.a76.invoices.models import ( - Currency, - InvoiceComplianceMx, - InvoiceFinancials, - InvoiceHeader, - InvoiceLogistics, - InvoiceStatus, - OperationType, -) -from api.v1.modules.a76.items.line_customs.models import LineCustom -from api.v1.modules.a76.items.line_descriptions.models import LineDescription -from api.v1.modules.a76.items.line_financials.models import LineFinancial -from api.v1.modules.a76.items.line_quantities.models import LineQuantity -from api.v1.modules.a76.items.models import LineItem -from api.v1.modules.core.tenants.models import Tenant, TenantType -from api.v1.modules.public.reference_data.invoice_types.models import InvoiceType -from api.v1.modules.public.reference_data.pedimento_regimens.models import RegimenPedimento - - -class EphemeralOrgIds(NamedTuple): - """Tenant + company creados por test (IDs altos, baja colisión con datos reales / CI).""" - - tenant_id: int - company_id: int - - -def allocate_ephemeral_tenant_company_ids(db: Session) -> tuple[int, int]: - """ - Genera par (tenant_id, company_id) no usados en esta sesión. - Mismo entero para ambas PKs es válido (tablas distintas). - """ - for _ in range(64): - n = uuid.uuid4().int - tid = 1_500_000_000 + (n % 99_000_000) - if db.get(Tenant, tid) is not None: - continue - if db.get(Company, tid) is not None: - continue - return tid, tid - raise RuntimeError( - "No se pudo asignar tenant/company efímero para tests (reintentar o revisar datos en BD)." - ) - - -def ensure_reference_data(db: Session) -> None: - for key, desc in [ - ("TEM", "IMPORTACION TEMPORAL"), - ("DEF", "IMPORTACION DEFINITIVA"), - ("MEX", "COMPRAS MEXICANAS"), - ("DONAC", "DONACION"), - ]: - if db.get(InvoiceType, key) is None: - operation = "exp" if key == "DONAC" else "imp" - db.add( - InvoiceType( - key=key, - description=desc, - note="seed for tests", - type="both", - operation=operation, - ) - ) - - if db.get(RegimenPedimento, "A1") is None: - db.add( - RegimenPedimento( - code="A1", - description="Regimen de prueba", - ) - ) - db.flush() - - -def ensure_tenant_company(db: Session, tenant_id: int, company_id: int) -> Company: - tenant = db.get(Tenant, tenant_id) - if tenant is None: - tenant = Tenant( - id=tenant_id, - name=f"Tenant {tenant_id}", - slug=f"pytest-{tenant_id}-{uuid.uuid4().hex[:10]}", - type=TenantType.SHARED, - keycloak_realm="test", - is_active=True, - ) - db.add(tenant) - # El FK de a76.company → core.tenants exige que el tenant exista en esta transacción - # antes del INSERT de company; un solo flush al final puede ordenar mal (Tenant híbrido Column+Mapped). - db.flush() - - company = db.get(Company, company_id) - if company is None: - company = Company( - id=company_id, - tenant_id=tenant_id, - name=f"Company {company_id}", - rfc="TST010101AAA", - prosec=False, - ) - db.add(company) - db.flush() - return company - - -def _ensure_client_provider_address( - db: Session, - client: ClientProvider, - *, - tenant_id: int, - company_id: int, - country: str, -) -> None: - """ - Garantiza que el cliente tenga fila de dirección. Si el ORM no tiene `address` - cargada pero en BD ya existe (datos previos en CI / DB compartida), reutiliza - esa fila en lugar de INSERT duplicado (pkey id = client_id). - """ - if client.address is not None: - return - existing = ( - db.query(ClientProviderAddress) - .filter(ClientProviderAddress.client_id == client.id) - .first() - ) - if existing is not None: - client.address = existing - return - client.address = ClientProviderAddress( - id=client.id, - client_id=client.id, - tenant_id=tenant_id, - company_id=company_id, - country=country, - ) - - -def create_business_catalogs(db: Session, tenant_id: int, company_id: int) -> dict: - provider = ( - db.query(ClientProvider) - .filter( - ClientProvider.tenant_id == tenant_id, - ClientProvider.company_id == company_id, - ClientProvider.name == "Proveedor Test", - ) - .first() - ) - if provider is None: - provider = ClientProvider( - tenant_id=tenant_id, - company_id=company_id, - name="Proveedor Test", - client_or_provider=ClientOrProviderEnum.BOTH, - is_active=True, - ) - db.add(provider) - db.flush() - _ensure_client_provider_address( - db, - provider, - tenant_id=tenant_id, - company_id=company_id, - country="MEX", - ) - - sold_to = ( - db.query(ClientProvider) - .filter( - ClientProvider.tenant_id == tenant_id, - ClientProvider.company_id == company_id, - ClientProvider.name == "Cliente Test", - ) - .first() - ) - if sold_to is None: - sold_to = ClientProvider( - tenant_id=tenant_id, - company_id=company_id, - name="Cliente Test", - client_or_provider=ClientOrProviderEnum.BOTH, - is_active=True, - ) - db.add(sold_to) - db.flush() - _ensure_client_provider_address( - db, - sold_to, - tenant_id=tenant_id, - company_id=company_id, - country="USA", - ) - - shipped_to = ( - db.query(ClientProvider) - .filter( - ClientProvider.tenant_id == tenant_id, - ClientProvider.company_id == company_id, - ClientProvider.name == "Destinatario Test", - ) - .first() - ) - if shipped_to is None: - shipped_to = ClientProvider( - tenant_id=tenant_id, - company_id=company_id, - name="Destinatario Test", - client_or_provider=ClientOrProviderEnum.BOTH, - is_active=True, - ) - db.add(shipped_to) - db.flush() - _ensure_client_provider_address( - db, - shipped_to, - tenant_id=tenant_id, - company_id=company_id, - country="USA", - ) - - broker = ( - db.query(CustomsBroker) - .filter( - CustomsBroker.tenant_id == tenant_id, - CustomsBroker.company_id == company_id, - CustomsBroker.broker_key == "A1234", - ) - .first() - ) - if broker is None: - broker = CustomsBroker( - tenant_id=tenant_id, - company_id=company_id, - broker_key="A1234", - name="Agente Test", - ) - db.add(broker) - db.flush() - - uom = ( - db.query(UnitOfMeasure) - .filter( - UnitOfMeasure.tenant_id == tenant_id, - UnitOfMeasure.company_id == company_id, - UnitOfMeasure.code == "PZA", - ) - .first() - ) - if uom is None: - uom = UnitOfMeasure( - tenant_id=tenant_id, - company_id=company_id, - code="PZA", - description="Pieza", - ) - db.add(uom) - db.flush() - - exr = ( - db.query(ExchangeRate) - .filter( - ExchangeRate.tenant_id == tenant_id, - ExchangeRate.company_id == company_id, - ExchangeRate.date == datetime(2026, 3, 17, 0, 0, 0), - ) - .first() - ) - if exr is None: - exr = ExchangeRate( - tenant_id=tenant_id, - company_id=company_id, - date=datetime(2026, 3, 17, 0, 0, 0), - value=Decimal("17.250000"), - local_currency="MN", - foreign_currency="ME", - ) - db.add(exr) - - db.flush() - return { - "provider_id": provider.id, - "sold_to_id": sold_to.id, - "shipped_to_id": shipped_to.id, - "broker_id": broker.id, - "uom_id": uom.id, - } - - -def create_import_invoice_with_line( - db: Session, - tenant_id: int, - company_id: int, - catalogs: dict, - invoice_type: str = "TEM", - invoice_number: str = "IMP-0001", - qty: Decimal = Decimal("10"), -) -> tuple[InvoiceHeader, LineItem]: - invoice = InvoiceHeader( - tenant_id=tenant_id, - company_id=company_id, - system="fixed_asset", - operation_type=OperationType.IMP, - invoice_type=invoice_type, - document_type="A1", - invoice_number=invoice_number, - invoice_date=date(2026, 3, 17), - status=InvoiceStatus.PENDING, - ) - invoice.compliance_mx = InvoiceComplianceMx( - tenant_id=tenant_id, - company_id=company_id, - provider_id=catalogs["provider_id"], - sold_to_id=catalogs["sold_to_id"], - shipped_to_id=catalogs["shipped_to_id"], - customs_broker_id=catalogs["broker_id"], - is_pedimento_pending=True, - was_reviewed_by_company=True, - ) - invoice.financials = InvoiceFinancials( - tenant_id=tenant_id, - company_id=company_id, - currency=Currency.FOREIGN, - exchange_rate=Decimal("17.250000"), - iva_factor="16", - ) - invoice.logistics = InvoiceLogistics( - tenant_id=tenant_id, - company_id=company_id, - weight_type="kgs", - equipment_reviewed=True, - ) - db.add(invoice) - db.flush() - - line = LineItem( - tenant_id=tenant_id, - company_id=company_id, - invoice_id=invoice.id, - line_number=1, - unit_of_measure=catalogs["uom_id"], - ) - line.quantity = LineQuantity( - quantity=qty, - net_weight=Decimal("50"), - gross_weight=Decimal("55"), - package_quantity=1, - ) - line.financial = LineFinancial( - unit_cost_capture=Decimal("10"), - value_usd=Decimal("100"), - value_mxn=Decimal("1725"), - ) - line.customs = LineCustom(origin_country="MEX", fraction_type="GENERAL") - line.description = LineDescription(has_serial=False) - db.add(line) - db.flush() - return invoice, line - - -def create_export_invoice_with_line( - db: Session, - tenant_id: int, - company_id: int, - catalogs: dict, - source_import_invoice: InvoiceHeader, - source_import_line: LineItem, - qty: Decimal = Decimal("4"), -) -> tuple[InvoiceHeader, LineItem]: - invoice = InvoiceHeader( - tenant_id=tenant_id, - company_id=company_id, - system="fixed_asset", - operation_type=OperationType.EXP, - invoice_type="DONAC", - document_type="A1", - invoice_number="EXP-0001", - invoice_date=date(2026, 3, 18), - status=InvoiceStatus.PENDING, - ) - invoice.compliance_mx = InvoiceComplianceMx( - tenant_id=tenant_id, - company_id=company_id, - provider_id=catalogs["provider_id"], - sold_to_id=catalogs["sold_to_id"], - shipped_to_id=catalogs["shipped_to_id"], - customs_broker_id=catalogs["broker_id"], - is_pedimento_pending=True, - was_reviewed_by_company=True, - ) - invoice.financials = InvoiceFinancials( - tenant_id=tenant_id, - company_id=company_id, - currency=Currency.FOREIGN, - exchange_rate=Decimal("17.250000"), - iva_factor="16", - ) - invoice.logistics = InvoiceLogistics( - tenant_id=tenant_id, - company_id=company_id, - weight_type="kgs", - equipment_reviewed=True, - ) - db.add(invoice) - db.flush() - - line = LineItem( - tenant_id=tenant_id, - company_id=company_id, - invoice_id=invoice.id, - line_number=1, - unit_of_measure=catalogs["uom_id"], - ) - line.quantity = LineQuantity( - quantity=qty, - net_weight=Decimal("20"), - gross_weight=Decimal("22"), - package_quantity=1, - ) - line.financial = LineFinancial( - unit_cost_capture=Decimal("12"), - value_usd=Decimal("48"), - value_mxn=Decimal("828"), - ) - line.customs = LineCustom(origin_country="MEX", origin_procedure="TEM", fraction_type="GENERAL") - line.description = LineDescription(has_serial=False) - db.add(line) - db.flush() - - db.add( - FaLineItem( - id=line.id, - tenant_id=tenant_id, - company_id=company_id, - discharge=True, - search_invoice=source_import_invoice.invoice_number, - search_line=source_import_line.line_number, - is_subitem=False, - ) - ) - db.flush() - return invoice, line diff --git a/backend/tests/integration/test_export_process_api.py b/backend/tests/integration/test_export_process_api.py deleted file mode 100644 index ed51d68b..00000000 --- a/backend/tests/integration/test_export_process_api.py +++ /dev/null @@ -1,133 +0,0 @@ -from decimal import Decimal - -import pytest - -from api.v1.modules.a24.balance_movements.models import BalanceMovement, MovementType -from api.v1.modules.a76.invoices.exports.process import main_process as export_main -from api.v1.modules.a76.invoices.imports.process import main_process as import_main -from core.exceptions import ValidationException -from tests.fixtures.builders import ( - EphemeralOrgIds, - create_business_catalogs, - create_export_invoice_with_line, - create_import_invoice_with_line, - ensure_reference_data, -) - - -def _patch_import_pipeline(monkeypatch, import_line): - monkeypatch.setattr(import_main, "pre_validators", lambda *args, **kwargs: [import_line]) - monkeypatch.setattr(import_main, "review_classes", lambda *args, **kwargs: None) - monkeypatch.setattr(import_main, "review_exchange_rate", lambda *args, **kwargs: None) - monkeypatch.setattr(import_main, "review_weights_kgs", lambda *args, **kwargs: None) - monkeypatch.setattr(import_main, "review_weights_lbs", lambda *args, **kwargs: None) - monkeypatch.setattr(import_main, "_validate_sisimp_limits", lambda *args, **kwargs: None) - monkeypatch.setattr(import_main, "_validate_lines", lambda *args, **kwargs: ([], {})) - - -def _patch_import_pipeline_without_prevalidators(monkeypatch): - monkeypatch.setattr(import_main, "review_classes", lambda *args, **kwargs: None) - monkeypatch.setattr(import_main, "review_exchange_rate", lambda *args, **kwargs: None) - monkeypatch.setattr(import_main, "review_weights_kgs", lambda *args, **kwargs: None) - monkeypatch.setattr(import_main, "review_weights_lbs", lambda *args, **kwargs: None) - monkeypatch.setattr(import_main, "_validate_sisimp_limits", lambda *args, **kwargs: None) - monkeypatch.setattr(import_main, "_validate_lines", lambda *args, **kwargs: ([], {})) - - -def _patch_export_pipeline(monkeypatch, export_line): - monkeypatch.setattr(export_main, "pre_validators", lambda *args, **kwargs: [export_line]) - monkeypatch.setattr(export_main, "review_class", lambda *args, **kwargs: None) - monkeypatch.setattr(export_main, "review_exchange_rate", lambda *args, **kwargs: None) - monkeypatch.setattr(export_main, "assign_values", lambda *args, **kwargs: None) - monkeypatch.setattr(export_main, "review_qty_vs_weight", lambda *args, **kwargs: None) - monkeypatch.setattr(export_main, "review_unit_cost", lambda *args, **kwargs: None) - monkeypatch.setattr(export_main, "review_qty_series", lambda *args, **kwargs: None) - - -def _prepare_inventory(client, db_session, monkeypatch, test_tenant: EphemeralOrgIds): - tid, cid = test_tenant.tenant_id, test_tenant.company_id - ensure_reference_data(db_session) - catalogs = create_business_catalogs(db_session, tenant_id=tid, company_id=cid) - import_invoice, import_line = create_import_invoice_with_line( - db_session, tid, cid, catalogs, invoice_type="TEM", invoice_number="IMP-EXP-01", qty=Decimal("10") - ) - _patch_import_pipeline(monkeypatch, import_line) - resp = client.post(f"/api/v1/a76/invoices/{import_invoice.id}/process?company_id={cid}") - assert resp.status_code == 200 - return catalogs, import_invoice, import_line - - -def test_process_export_endpoint_consumes_existing_balances(client, db_session, monkeypatch, test_tenant): - catalogs, import_invoice, import_line = _prepare_inventory( - client, db_session, monkeypatch, test_tenant - ) - tid, cid = test_tenant.tenant_id, test_tenant.company_id - export_invoice, export_line = create_export_invoice_with_line( - db_session, tid, cid, catalogs, import_invoice, import_line, qty=Decimal("3") - ) - _patch_export_pipeline(monkeypatch, export_line) - - resp = client.post(f"/api/v1/a76/invoices/{export_invoice.id}/process?company_id={cid}") - assert resp.status_code == 200 - - consumptions = ( - db_session.query(BalanceMovement) - .filter( - BalanceMovement.source_invoice_id == export_invoice.id, - BalanceMovement.movement_type == MovementType.CONSUMPTION, - ) - .all() - ) - assert consumptions - - -def test_process_export_prevents_negative_balance(client, db_session, monkeypatch, test_tenant): - catalogs, import_invoice, import_line = _prepare_inventory( - client, db_session, monkeypatch, test_tenant - ) - tid, cid = test_tenant.tenant_id, test_tenant.company_id - export_invoice, export_line = create_export_invoice_with_line( - db_session, tid, cid, catalogs, import_invoice, import_line, qty=Decimal("99") - ) - _patch_export_pipeline(monkeypatch, export_line) - - with pytest.raises(ValidationException): - client.post(f"/api/v1/a76/invoices/{export_invoice.id}/process?company_id={cid}") - - # Debe no crear consumos al existir insuficiencia de saldo. - consumptions = ( - db_session.query(BalanceMovement) - .filter( - BalanceMovement.source_invoice_id == export_invoice.id, - BalanceMovement.movement_type == MovementType.CONSUMPTION, - ) - .all() - ) - assert consumptions == [] - - -def test_process_endpoint_prevents_double_processing_import(client, db_session, monkeypatch, test_tenant): - tid, cid = test_tenant.tenant_id, test_tenant.company_id - ensure_reference_data(db_session) - catalogs = create_business_catalogs(db_session, tenant_id=tid, company_id=cid) - invoice, line = create_import_invoice_with_line( - db_session, tid, cid, catalogs, invoice_type="TEM", invoice_number="IMP-DOUBLE-01", qty=Decimal("5") - ) - _patch_import_pipeline_without_prevalidators(monkeypatch) - - first = client.post(f"/api/v1/a76/invoices/{invoice.id}/process?company_id={cid}") - assert first.status_code == 200 - with pytest.raises(ValidationException): - client.post(f"/api/v1/a76/invoices/{invoice.id}/process?company_id={cid}") - - # No debe duplicar entradas para la misma factura/línea procesada. - entries = ( - db_session.query(BalanceMovement) - .filter( - BalanceMovement.import_invoice_id == invoice.id, - BalanceMovement.import_item_line_id == line.id, - BalanceMovement.movement_type == MovementType.ENTRY, - ) - .all() - ) - assert len(entries) == 1 diff --git a/backend/tests/integration/test_import_process_api.py b/backend/tests/integration/test_import_process_api.py deleted file mode 100644 index 956f759c..00000000 --- a/backend/tests/integration/test_import_process_api.py +++ /dev/null @@ -1,63 +0,0 @@ -from decimal import Decimal - -from api.v1.modules.a24.balance_movements.models import BalanceMovement, MovementType -from api.v1.modules.a76.invoices.imports.process import main_process as import_main -from tests.fixtures.builders import ( - create_business_catalogs, - create_import_invoice_with_line, - ensure_reference_data, -) - - -def _patch_import_pipeline(monkeypatch, import_line): - monkeypatch.setattr(import_main, "pre_validators", lambda *args, **kwargs: [import_line]) - monkeypatch.setattr(import_main, "review_classes", lambda *args, **kwargs: None) - monkeypatch.setattr(import_main, "review_exchange_rate", lambda *args, **kwargs: None) - monkeypatch.setattr(import_main, "review_weights_kgs", lambda *args, **kwargs: None) - monkeypatch.setattr(import_main, "review_weights_lbs", lambda *args, **kwargs: None) - monkeypatch.setattr(import_main, "_validate_sisimp_limits", lambda *args, **kwargs: None) - monkeypatch.setattr(import_main, "_validate_lines", lambda *args, **kwargs: ([], {})) - - -def test_process_import_endpoint_creates_balance_entries(client, db_session, monkeypatch, test_tenant): - tid, cid = test_tenant.tenant_id, test_tenant.company_id - ensure_reference_data(db_session) - catalogs = create_business_catalogs(db_session, tenant_id=tid, company_id=cid) - invoice, line = create_import_invoice_with_line( - db_session, tid, cid, catalogs, invoice_type="TEM", invoice_number="IMP-INT-01", qty=Decimal("7") - ) - _patch_import_pipeline(monkeypatch, line) - - resp = client.post(f"/api/v1/a76/invoices/{invoice.id}/process?company_id={cid}") - assert resp.status_code == 200 - - entries = ( - db_session.query(BalanceMovement) - .filter( - BalanceMovement.import_invoice_id == invoice.id, - BalanceMovement.import_item_line_id == line.id, - BalanceMovement.movement_type == MovementType.ENTRY, - ) - .all() - ) - assert len(entries) >= 1 - - -def test_process_import_def_does_not_create_balance_entries(client, db_session, monkeypatch, test_tenant): - tid, cid = test_tenant.tenant_id, test_tenant.company_id - ensure_reference_data(db_session) - catalogs = create_business_catalogs(db_session, tenant_id=tid, company_id=cid) - invoice, line = create_import_invoice_with_line( - db_session, tid, cid, catalogs, invoice_type="DEF", invoice_number="IMP-DEF-01", qty=Decimal("7") - ) - _patch_import_pipeline(monkeypatch, line) - - resp = client.post(f"/api/v1/a76/invoices/{invoice.id}/process?company_id={cid}") - assert resp.status_code == 200 - - entries = ( - db_session.query(BalanceMovement) - .filter(BalanceMovement.import_invoice_id == invoice.id) - .all() - ) - assert entries == [] diff --git a/backend/tests/integration/test_rls_tenant_company.py b/backend/tests/integration/test_rls_tenant_company.py deleted file mode 100644 index cdd9c641..00000000 --- a/backend/tests/integration/test_rls_tenant_company.py +++ /dev/null @@ -1,202 +0,0 @@ -"""Pruebas de aislamiento Row-Level Security por ``tenant_id`` y ``company_id``. - -Las políticas se crean en la migración -``d1a2b3c4e5f6_enable_rls_tenant_company`` y dependen de las GUCs -``app.tenant_id`` / ``app.company_id`` fijadas por la aplicación. - -Como ``postgres`` hace BYPASSRLS por defecto, los tests cambian de rol -dentro de la transacción a un usuario sin ese atributo (``anexo76_rls_test``) -antes de contar filas. Si la conexión de pruebas no tiene privilegios para -crear el rol, el set completo se salta con ``pytest.skip``. -""" - -from __future__ import annotations - -import uuid -from typing import Iterator - -import pytest -from sqlalchemy import text -from sqlalchemy.exc import DBAPIError, ProgrammingError -from sqlalchemy.orm import Session - -from tests.conftest import TestingSessionLocal, engine -from tests.fixtures.builders import ensure_tenant_company - - -TEST_ROLE = "anexo76_rls_test" -RLS_SCHEMAS = ("core", "a76", "a24", "public") - - -@pytest.fixture(scope="module") -def rls_role() -> str: - """Crea (idempotente) un rol no-superusuario con los privilegios mínimos - necesarios para ejercitar las políticas RLS en los tests.""" - try: - with engine.begin() as conn: - conn.execute( - text( - f""" - DO $$ - BEGIN - IF NOT EXISTS (SELECT 1 FROM pg_roles WHERE rolname = '{TEST_ROLE}') THEN - CREATE ROLE {TEST_ROLE} NOLOGIN NOBYPASSRLS; - END IF; - END $$; - """ - ) - ) - for schema in RLS_SCHEMAS: - conn.execute(text(f'GRANT USAGE ON SCHEMA "{schema}" TO {TEST_ROLE}')) - conn.execute( - text( - f'GRANT SELECT, INSERT, UPDATE, DELETE ON ALL TABLES IN SCHEMA "{schema}" TO {TEST_ROLE}' - ) - ) - conn.execute( - text( - f'GRANT USAGE, SELECT ON ALL SEQUENCES IN SCHEMA "{schema}" TO {TEST_ROLE}' - ) - ) - except ProgrammingError as exc: - pytest.skip(f"No hay privilegio para preparar rol de pruebas RLS: {exc}") - return TEST_ROLE - - -@pytest.fixture -def rls_session() -> Iterator[Session]: - """Sesión con transacción externa + rollback (no contamina BD real).""" - connection = engine.connect() - transaction = connection.begin() - session = TestingSessionLocal(bind=connection) - try: - yield session - finally: - session.close() - transaction.rollback() - connection.close() - - -def _allocate_id() -> int: - return 1_700_000_000 + (uuid.uuid4().int % 90_000_000) - - -def _bootstrap_two_tenants(session: Session) -> tuple[tuple[int, int], tuple[int, int]]: - tid_a, tid_b = _allocate_id(), _allocate_id() - cid_a, cid_b = _allocate_id(), _allocate_id() - ensure_tenant_company(session, tenant_id=tid_a, company_id=cid_a) - ensure_tenant_company(session, tenant_id=tid_b, company_id=cid_b) - return (tid_a, cid_a), (tid_b, cid_b) - - -def _seed_clients(session: Session, tenant_id: int, company_id: int, *, count: int) -> None: - """Inserta filas en ``a76.clients_and_providers`` con SQL crudo para - aislarnos del ORM y medir filtrado puro a nivel de BD.""" - for i in range(count): - session.execute( - text( - """ - INSERT INTO a76.clients_and_providers - (tenant_id, company_id, name, client_or_provider, is_active) - VALUES - (:tid, :cid, :name, 'BOTH', true) - """ - ), - {"tid": tenant_id, "cid": company_id, "name": f"Seed {tenant_id}/{company_id}/{i}"}, - ) - session.flush() - - -def _count_under_role( - session: Session, - tenant_id: int | None, - company_id: int | None, - *, - role: str, - table: str = "a76.clients_and_providers", -) -> int: - """Cuenta filas tras cambiar a un rol sin BYPASSRLS y fijar el contexto.""" - session.execute(text(f"SET LOCAL ROLE {role}")) - session.execute( - text("SELECT set_config('app.tenant_id', :t, true), set_config('app.company_id', :c, true)"), - { - "t": "" if tenant_id is None else str(tenant_id), - "c": "" if company_id is None else str(company_id), - }, - ) - try: - return int(session.execute(text(f"SELECT count(*) FROM {table}")).scalar() or 0) - finally: - session.execute(text("RESET ROLE")) - - -def test_tenant_isolation_hides_rows_from_other_tenant( - rls_session: Session, rls_role: str -) -> None: - (tid_a, cid_a), (tid_b, cid_b) = _bootstrap_two_tenants(rls_session) - _seed_clients(rls_session, tid_a, cid_a, count=3) - _seed_clients(rls_session, tid_b, cid_b, count=2) - - visible_a = _count_under_role(rls_session, tid_a, None, role=rls_role) - visible_b = _count_under_role(rls_session, tid_b, None, role=rls_role) - - assert visible_a == 3, "Tenant A debe ver solo sus 3 filas" - assert visible_b == 2, "Tenant B debe ver solo sus 2 filas" - - -def test_company_scope_narrows_within_tenant( - rls_session: Session, rls_role: str -) -> None: - (tid_a, cid_a), _ = _bootstrap_two_tenants(rls_session) - - second_company_id = _allocate_id() - ensure_tenant_company(rls_session, tenant_id=tid_a, company_id=second_company_id) - - _seed_clients(rls_session, tid_a, cid_a, count=4) - _seed_clients(rls_session, tid_a, second_company_id, count=7) - - without_company = _count_under_role(rls_session, tid_a, None, role=rls_role) - with_company_a = _count_under_role(rls_session, tid_a, cid_a, role=rls_role) - with_second = _count_under_role(rls_session, tid_a, second_company_id, role=rls_role) - - assert without_company == 11, "Sin company context el tenant ve ambas compañías" - assert with_company_a == 4 - assert with_second == 7 - - -def test_missing_tenant_context_returns_zero_rows( - rls_session: Session, rls_role: str -) -> None: - (tid_a, cid_a), _ = _bootstrap_two_tenants(rls_session) - _seed_clients(rls_session, tid_a, cid_a, count=5) - - visible = _count_under_role(rls_session, None, None, role=rls_role) - assert visible == 0, "Sin app.tenant_id la política debe devolver 0 filas" - - -def test_insert_violation_respects_tenant_policy( - rls_session: Session, rls_role: str -) -> None: - """La cláusula ``WITH CHECK`` debe rechazar inserts fuera de contexto.""" - (tid_a, cid_a), (tid_b, _) = _bootstrap_two_tenants(rls_session) - - rls_session.execute(text(f"SET LOCAL ROLE {rls_role}")) - rls_session.execute( - text("SELECT set_config('app.tenant_id', :t, true), set_config('app.company_id', :c, true)"), - {"t": str(tid_a), "c": str(cid_a)}, - ) - try: - with pytest.raises(DBAPIError): - rls_session.execute( - text( - """ - INSERT INTO a76.clients_and_providers - (tenant_id, company_id, name, client_or_provider, is_active) - VALUES - (:tid, :cid, 'Cross-tenant attempt', 'BOTH', true) - """ - ), - {"tid": tid_b, "cid": cid_a}, - ) - finally: - rls_session.rollback() diff --git a/backend/tests/unit/a76/layouts_csv/test_csv_header_hints.py b/backend/tests/unit/a76/layouts_csv/test_csv_header_hints.py deleted file mode 100644 index 14a5b007..00000000 --- a/backend/tests/unit/a76/layouts_csv/test_csv_header_hints.py +++ /dev/null @@ -1,143 +0,0 @@ -"""Tests for CSV template header hints (inline) and import lookup aliases (stdlib unittest).""" - -import unittest - -from api.v1.modules.a76.csv_templates.registry import ( - _normalize_header_for_match, - _TEMPLATE_HEADERS, - get_download_headers, -) -from api.v1.modules.a76.layouts_csv.common.template_locale import ( - download_header_cell, - display_header_for_locale, -) -from api.v1.modules.a76.layouts_csv.clients_and_providers import template_config as cp_tc - - -class TestRegistryCsvDownloadSmoke(unittest.TestCase): - """Smoke: cada template_id del registry tiene fila de descarga misma longitud que columnas base.""" - - def test_download_headers_length_matches_registry_for_all_templates(self): - for tid in sorted(_TEMPLATE_HEADERS.keys()): - row = get_download_headers(tid, "es") - self.assertIsNotNone(row, tid) - self.assertEqual(len(row), len(_TEMPLATE_HEADERS[tid]), tid) - - def test_customs_brokers_lookup_resolves_decorated_tip_header(self): - from api.v1.modules.a76.layouts_csv.customs_brokers import template_config as cb_tc - - lookup = cb_tc.build_normalized_lookup(_normalize_header_for_match) - hdr0 = get_download_headers("customs_brokers", "es")[0] - self.assertEqual(lookup[_normalize_header_for_match(hdr0)], "TIPO") - - def test_imp_temp_header_lookup_resolves_decorated_e_document(self): - from api.v1.modules.a76.layouts_csv.facturas import template_config as ft_tc - - lookup = ft_tc.build_normalized_lookup("imp_temp_header", _normalize_header_for_match) - for cell in get_download_headers("imp_temp_header", "es"): - if lookup.get(_normalize_header_for_match(cell)) == "E DOCUMENT": - self.assertIn("(S, N)", cell) - return - self.fail("E DOCUMENT column missing from imp_temp_header download row") - - def test_pedimentos_lookup_resolves_tipo_operacion_decorated(self): - from api.v1.modules.a76.layouts_csv.pedmientos import template_config as ped_tc - - lookup = ped_tc.build_normalized_lookup(_normalize_header_for_match, "pedimentos") - for cell in get_download_headers("pedimentos", "es"): - if lookup.get(_normalize_header_for_match(cell)) == "TIPO_OPERACION": - self.assertIn("(I, E)", cell) - return - self.fail("TIPO_OPERACION column missing from pedimentos download row") - - def test_material_classes_lookup_resolves_revfisica_enum_header(self): - from api.v1.modules.a76.layouts_csv.classes import template_config as cl_tc - - lookup = cl_tc.build_normalized_lookup(_normalize_header_for_match) - for cell in get_download_headers("material_classes", "es"): - if lookup.get(_normalize_header_for_match(cell)) == "REVFISICA": - self.assertIn("(1, 0)", cell) - return - self.fail("REVFISICA column missing from material_classes download row") - - -class TestCsvHeaderHints(unittest.TestCase): - def test_download_header_cell_enum_within_limit(self): - col = { - "canonical": "TIPO", - "csv_hint": {"kind": "enum_codes", "codes": ["C", "P", "A"]}, - } - self.assertEqual(download_header_cell(col, "es"), "TIPO (C, P, A)") - col_en = {**col, "labels": {"en": "Entity type"}} - self.assertEqual(download_header_cell(col_en, "en"), "Entity type (C, P, A)") - - def test_download_header_cell_enum_over_limit_ignored(self): - col = { - "canonical": "X", - "csv_hint": {"kind": "enum_codes", "codes": ["a", "b", "c", "d"]}, - } - self.assertEqual(download_header_cell(col, "es"), "X") - - def test_download_header_cell_digits_es_en(self): - col = {"canonical": "NUMERO", "csv_hint": {"kind": "digits", "n": 7}} - self.assertEqual(download_header_cell(col, "es"), "NUMERO (7 dígitos)") - self.assertEqual(download_header_cell(col, "en"), "NUMERO (7 digits)") - - def test_clients_providers_lookup_accepts_extended_headers(self): - lookup = cp_tc.build_normalized_lookup(_normalize_header_for_match) - self.assertEqual(lookup[_normalize_header_for_match("PROCEDENCIA")], "PROCEDENCIA") - self.assertEqual( - lookup[_normalize_header_for_match("PROCEDENCIA (N, E)")], - "PROCEDENCIA", - ) - self.assertEqual(lookup[_normalize_header_for_match("TIPO")], "TIPO") - self.assertEqual(lookup[_normalize_header_for_match("TIPO (C, P, A)")], "TIPO") - self.assertEqual( - lookup[_normalize_header_for_match("Origin (foreign/domestic) (N, E)")], - "PROCEDENCIA", - ) - self.assertEqual( - lookup[_normalize_header_for_match("Entity type (C, P, A)")], - "TIPO", - ) - self.assertEqual( - lookup[_normalize_header_for_match("RFC (máx. 30 caracteres)")], - "RFC", - ) - self.assertEqual( - lookup[_normalize_header_for_match("SHORT_NAME (máx. 10 caracteres)")], - "SHORT_NAME", - ) - self.assertEqual( - lookup[_normalize_header_for_match("Short code (max. 10 characters)")], - "SHORT_NAME", - ) - - def test_download_header_cell_max_chars(self): - col = {"canonical": "RFC", "csv_hint": {"kind": "max_chars", "n": 30}} - self.assertEqual(download_header_cell(col, "es"), "RFC (máx. 30 caracteres)") - self.assertEqual(download_header_cell(col, "en"), "RFC (max. 30 characters)") - col_one = {"canonical": "X", "csv_hint": {"kind": "max_chars", "n": 1}} - self.assertEqual(download_header_cell(col_one, "es"), "X (máx. 1 carácter)") - self.assertEqual(download_header_cell(col_one, "en"), "X (max. 1 character)") - - def test_download_header_cell_enum_then_max_chars(self): - col = { - "canonical": "TIPO", - "csv_hint": [ - {"kind": "enum_codes", "codes": ["C", "P", "A"]}, - {"kind": "max_chars", "n": 1}, - ], - } - self.assertEqual(download_header_cell(col, "es"), "TIPO (C, P, A) (máx. 1 carácter)") - - def test_display_header_unchanged_without_hint(self): - col = {"canonical": "ZZZ_NO_SCHEMA_LEN"} - self.assertEqual( - download_header_cell(col, "es"), - display_header_for_locale(col, "es"), - ) - - -if __name__ == "__main__": - unittest.main() diff --git a/backend/tests/unit/core/test_license_middleware.py b/backend/tests/unit/core/test_license_middleware.py deleted file mode 100644 index 55a277cb..00000000 --- a/backend/tests/unit/core/test_license_middleware.py +++ /dev/null @@ -1,21 +0,0 @@ -from core.middleware import _is_token_issue_message - - -def test_is_token_issue_message_detects_expired_token_in_spanish(): - assert _is_token_issue_message("Token inválido o expirado") is True - - -def test_is_token_issue_message_detects_expired_token_in_english(): - assert _is_token_issue_message("Invalid or expired token") is True - - -def test_is_token_issue_message_detects_detail_reason_combo(): - assert _is_token_issue_message( - "Access denied", - "jwt signature validation failed", - "token malformed", - ) is True - - -def test_is_token_issue_message_does_not_flag_real_license_error(): - assert _is_token_issue_message("Sin licencia asignada para este tenant") is False diff --git a/backend/tests/unit/general_catalogs/doda/test_doda_alta_payloads.py b/backend/tests/unit/general_catalogs/doda/test_doda_alta_payloads.py deleted file mode 100644 index 4d18d0e0..00000000 --- a/backend/tests/unit/general_catalogs/doda/test_doda_alta_payloads.py +++ /dev/null @@ -1,69 +0,0 @@ -from __future__ import annotations - -import json -from types import SimpleNamespace - -import pytest - -from api.v1.modules.a76.general_catalogs.doda.alta_service import DodaAltaService - - -def test_build_consulta_payload_appends_numero_transaccion(monkeypatch): - service = DodaAltaService(db=None) - monkeypatch.setattr( - service, - "build_alta_payload", - lambda **kwargs: {"base": "payload"}, - ) - monkeypatch.setattr( - service, - "_latest_alta_log", - lambda *args, **kwargs: SimpleNamespace( - id=9, - result_json=json.dumps({"numero_transaccion": "TX-001"}), - integration_number="INT-001", - ), - ) - - payload = service.build_consulta_payload( - doda_id=1, tenant_id=1, company_id=1, variant="doda", user_email="u@test.com" - ) - assert payload["base"] == "payload" - assert payload["numero_transaccion"] == "TX-001" - - -def test_build_consulta_payload_fails_when_numero_transaccion_missing(monkeypatch): - service = DodaAltaService(db=None) - monkeypatch.setattr(service, "build_alta_payload", lambda **kwargs: {}) - monkeypatch.setattr( - service, - "_latest_alta_log", - lambda *args, **kwargs: SimpleNamespace( - id=10, result_json=json.dumps({"other": "value"}), integration_number="INT-001" - ), - ) - - with pytest.raises(ValueError, match="numero_transaccion"): - service.build_consulta_payload( - doda_id=1, tenant_id=1, company_id=1, variant="doda", user_email="" - ) - - -def test_build_eliminar_payload_appends_numero_integracion(monkeypatch): - service = DodaAltaService(db=None) - monkeypatch.setattr(service, "build_alta_payload", lambda **kwargs: {"base": "payload"}) - monkeypatch.setattr( - service, - "_latest_alta_log", - lambda *args, **kwargs: SimpleNamespace( - id=11, - result_json="{}", - integration_number="INT-900", - ), - ) - - payload = service.build_eliminar_payload( - doda_id=2, tenant_id=1, company_id=1, variant="doda", user_email="user@test.com" - ) - assert payload["base"] == "payload" - assert payload["numero_integracion"] == "INT-900" diff --git a/backend/tests/unit/general_catalogs/doda/test_doda_export.py b/backend/tests/unit/general_catalogs/doda/test_doda_export.py deleted file mode 100644 index 0b3bb2a5..00000000 --- a/backend/tests/unit/general_catalogs/doda/test_doda_export.py +++ /dev/null @@ -1,72 +0,0 @@ -from types import SimpleNamespace - -import pytest - -from api.v1.modules.a76.general_catalogs.doda.export_service import ( - DodaExportFormat, - build_export_text, - doda_row_values, - parse_export_params, -) - - -def test_parse_export_params_valid(): - d0, d1, fmt, mode = parse_export_params("2024-01-15", "2024-01-20", "csv", "formatted") - assert d0 == 20240115 - assert d1 == 20240120 - assert fmt == DodaExportFormat.csv - assert mode == "formatted" - - -def test_parse_export_params_rejects_inverted_range(): - with pytest.raises(ValueError, match="date_from"): - parse_export_params("2024-02-01", "2024-01-01", "csv", "formatted") - - -def _minimal_row(): - return SimpleNamespace( - id=1, - integration_number="INT1", - doda_date=20240110, - doda_time=1430, - dispatch_customs="64", - customs_sections=None, - patent="1234", - pedimentos=None, - caat=None, - transport_identification=None, - fast_id=None, - operation_type="I", - responsible=None, - carrier=None, - shipments=None, - pedimento_type=None, - original_chain=None, - serial_number=None, - electronic_signature=None, - transaction_number=None, - status="PENDIENTE", - linq_sat_qr=None, - sat_certificate=None, - sat_digital_seal=None, - xml_doda_sent_path=None, - xml_doda_response_path=None, - sat_original_chain=None, - customs_clearance=None, - unique_badge_number=None, - last_user=None, - ) - - -def test_doda_row_values_length_matches_headers(): - row = _minimal_row() - assert len(doda_row_values(row, date_mode="formatted")) == 30 - - -def test_build_export_text_includes_header_and_row(): - row = _minimal_row() - text = build_export_text([row], export_format=DodaExportFormat.csv, date_mode="formatted") - lines = text.strip().split("\r\n") - assert "SYSID" in lines[0] - assert "INT1" in lines[1] - assert lines[1].startswith("1,") diff --git a/backend/tests/unit/general_catalogs/doda/test_doda_external_service.py b/backend/tests/unit/general_catalogs/doda/test_doda_external_service.py deleted file mode 100644 index 8b5367f3..00000000 --- a/backend/tests/unit/general_catalogs/doda/test_doda_external_service.py +++ /dev/null @@ -1,65 +0,0 @@ -from __future__ import annotations - -from typing import Any, Dict - -from api.v1.modules.a76.general_catalogs.doda.external_service import DodaExternalService - - -class _FakeResponse: - def __init__(self, payload: Dict[str, Any]): - self._payload = payload - - def raise_for_status(self) -> None: - return None - - def json(self) -> Dict[str, Any]: - return self._payload - - -class _FakeClient: - calls = [] - - def __init__(self, *args, **kwargs): - self.kwargs = kwargs - - def __enter__(self): - return self - - def __exit__(self, exc_type, exc, tb): - return False - - def post(self, url: str, json: Dict[str, Any]): - _FakeClient.calls.append(("POST", url, json)) - return _FakeResponse({"task_id": "t-1", "status": "queued", "message": "ok"}) - - def get(self, url: str): - _FakeClient.calls.append(("GET", url, None)) - return _FakeResponse({"task_id": "t-1", "status": "done", "message": "ok"}) - - -def test_external_service_supports_consulta_and_eliminar(monkeypatch): - from api.v1.modules.a76.general_catalogs.doda import external_service as module - - _FakeClient.calls = [] - monkeypatch.setattr(module, "httpx", module.httpx) - monkeypatch.setattr(module.httpx, "Client", _FakeClient) - - service = DodaExternalService() - service.base_url = "http://example.test" - - payload = {"foo": "bar"} - consulta = service.post_consulta(payload) - consulta_status = service.get_consulta_status("abc123") - eliminar = service.post_eliminar(payload) - eliminar_status = service.get_eliminar_status("abc123") - - assert consulta["task_id"] == "t-1" - assert consulta_status["status"] == "done" - assert eliminar["status"] == "queued" - assert eliminar_status["task_id"] == "t-1" - assert _FakeClient.calls == [ - ("POST", "http://example.test/api/v1/doda/consulta", payload), - ("GET", "http://example.test/api/v1/doda/consulta-status/abc123", None), - ("POST", "http://example.test/api/v1/doda/eliminar", payload), - ("GET", "http://example.test/api/v1/doda/eliminar-status/abc123", None), - ] diff --git a/backend/tests/unit/general_catalogs/doda/test_doda_print.py b/backend/tests/unit/general_catalogs/doda/test_doda_print.py deleted file mode 100644 index f635e74d..00000000 --- a/backend/tests/unit/general_catalogs/doda/test_doda_print.py +++ /dev/null @@ -1,130 +0,0 @@ -from unittest.mock import patch - -import pytest -from fastapi import FastAPI -from fastapi.testclient import TestClient -from sqlalchemy.orm import Session - -from api.v1.modules.a76.general_catalogs.doda.fingerprint import build_doda_fingerprint -from api.v1.modules.a76.general_catalogs.doda.models import Doda -from api.v1.modules.a76.general_catalogs.doda import routes as doda_routes -from core import storage_s3 -from core.config import settings -from core.database import get_core_db -from core.s3_keys import doda_report_pdf_key -from core.security import get_current_user -from tests.conftest import allocate_ephemeral_tenant_company_ids, ensure_tenant_company - - -@pytest.mark.usefixtures("db_session") -def test_doda_fingerprint_changes_when_field_changes(db_session: Session): - tid, cid = allocate_ephemeral_tenant_company_ids(db_session) - ensure_tenant_company(db_session, tenant_id=tid, company_id=cid) - d = Doda( - tenant_id=tid, - company_id=cid, - integration_number="A1", - sat_digital_seal="x", - ) - db_session.add(d) - db_session.commit() - fp1 = build_doda_fingerprint(db_session, d.id) - d.patent = "1234" - db_session.commit() - fp2 = build_doda_fingerprint(db_session, d.id) - assert fp1 != fp2 - - -def _print_client(db_session: Session, test_tenant) -> TestClient: - app = FastAPI() - app.include_router(doda_routes.router) - - def _override_get_db(): - yield db_session - - async def _user(): - return {"sub": "test", "tenant_id": test_tenant.tenant_id} - - app.dependency_overrides[get_core_db] = _override_get_db - app.dependency_overrides[get_current_user] = _user - return TestClient(app) - - -@patch.object( - doda_routes, - "validate_access_to_resource", - lambda db, company_id, current_user, required_permissions=None, require_all=True: int( - current_user["tenant_id"] - ), -) -def test_print_uses_s3_cache_when_fingerprint_matches( - db_session: Session, test_tenant, monkeypatch: pytest.MonkeyPatch -): - monkeypatch.setattr(settings, "S3_FILE_STORAGE", True, raising=False) - monkeypatch.setattr(settings, "CSV_IMPORT_STORAGE", "redis", raising=False) - - tid, cid = test_tenant.tenant_id, test_tenant.company_id - ensure_tenant_company(db_session, tenant_id=tid, company_id=cid) - d = Doda( - tenant_id=tid, - company_id=cid, - integration_number="X", - sat_digital_seal="s", - ) - db_session.add(d) - db_session.commit() - - fp = build_doda_fingerprint(db_session, d.id) - d.doda_report_source_fingerprint = fp - d.doda_report_pdf_path = doda_report_pdf_key(tid, cid, d.id) - db_session.commit() - - called = {"render": 0} - - class _NoRender: - def build_pdf_for_doda(self, db, doda): - called["render"] += 1 - raise AssertionError("should not render when S3 cache hits") - - monkeypatch.setattr(doda_routes, "DodaReportPdfService", lambda: _NoRender()) - monkeypatch.setattr(storage_s3, "object_exists", lambda key: True) - monkeypatch.setattr(storage_s3, "get_object_bytes", lambda key: b"%PDF-1.4 cached") - - client = _print_client(db_session, test_tenant) - resp = client.get(f"/doda/{d.id}/print?company_id={cid}") - assert resp.status_code == 200 - assert resp.content == b"%PDF-1.4 cached" - assert called["render"] == 0 - - -@patch.object( - doda_routes, - "validate_access_to_resource", - lambda db, company_id, current_user, required_permissions=None, require_all=True: int( - current_user["tenant_id"] - ), -) -def test_print_422_without_digital_seal( - db_session: Session, test_tenant, monkeypatch: pytest.MonkeyPatch -): - monkeypatch.setattr(settings, "S3_FILE_STORAGE", True, raising=False) - - tid, cid = test_tenant.tenant_id, test_tenant.company_id - ensure_tenant_company(db_session, tenant_id=tid, company_id=cid) - d = Doda( - tenant_id=tid, company_id=cid, integration_number="N", sat_digital_seal=" " - ) - db_session.add(d) - db_session.commit() - - put_calls = [] - - def _put(key, body, content_type="application/octet-stream"): - put_calls.append((key, body, content_type)) - - monkeypatch.setattr(storage_s3, "put_object_bytes", _put) - - client = _print_client(db_session, test_tenant) - resp = client.get(f"/doda/{d.id}/print?company_id={cid}") - assert resp.status_code == 422 - assert put_calls == [] diff --git a/backend/tests/unit/general_catalogs/fractions/test_tariff_fraction_mapper.py b/backend/tests/unit/general_catalogs/fractions/test_tariff_fraction_mapper.py deleted file mode 100644 index 06e61c8c..00000000 --- a/backend/tests/unit/general_catalogs/fractions/test_tariff_fraction_mapper.py +++ /dev/null @@ -1,57 +0,0 @@ -from api.v1.modules.a76.general_catalogs.fractions.tariff_fractions.service import ( - TariffFractionMapper, -) -from api.v1.modules.a76.general_catalogs.fractions.us_tariff_fractions.dto import ( - USTariffFractionResponseDTO, -) -from api.v1.modules.sitar.fracciones_usa.schemas import FraccionesUSAResponse - - -def test_to_domain_usa_keeps_separate_code_and_fraction(): - row = FraccionesUSAResponse( - CONSECUTIVO=10, - FRACCION_SIN_PUNTO="1234567890", - FRACCION_CON_PUNTO="1234.56.78.90", - DESCRIPCION="Test", - UNIDADCANTIDAD="KG", - TARIFA1="5%", - TARIFA2="0%", - ) - - mapped = TariffFractionMapper.to_domain_usa(row) - - assert mapped.code == "1234567890" - assert mapped.fraction == "1234.56.78.90" - - -def test_to_domain_usa_formats_fraction_when_only_code_available(): - row = FraccionesUSAResponse( - CONSECUTIVO=11, - FRACCION_SIN_PUNTO="9876543210", - FRACCION_CON_PUNTO=None, - FRACCION_MOSTRAR=None, - DESCRIPCION="Fallback", - UNIDADCANTIDAD="PZA", - TARIFA1="7.5%", - TARIFA2="0%", - ) - - mapped = TariffFractionMapper.to_domain_usa(row) - - assert mapped.code == "9876543210" - assert mapped.fraction == "9876.54.32.10" - - -def test_us_response_dto_preserves_fraction_when_provided(): - dto = USTariffFractionResponseDTO.model_validate( - { - "id": 1, - "code": "1111.22.33.44", - "fraction": "1111.22.33.44", - "description": "DTO test", - "created_at": "2026-01-01T00:00:00Z", - "updated_at": "2026-01-01T00:00:00Z", - } - ) - assert dto.code == "1111223344" - assert dto.fraction == "1111.22.33.44" diff --git a/backend/tests/unit/invoices/test_balance_algorithm.py b/backend/tests/unit/invoices/test_balance_algorithm.py deleted file mode 100644 index 70b6c1df..00000000 --- a/backend/tests/unit/invoices/test_balance_algorithm.py +++ /dev/null @@ -1,120 +0,0 @@ -from datetime import date -from decimal import Decimal - -from api.v1.modules.a24.balance_movements.models import BalanceMovement, MovementType -from api.v1.modules.a76.invoices.exports.process.sub_process.compare_balances import compare_balances -from api.v1.modules.a76.invoices.exports.process.sub_process.discharge_types import ( - AvailableLot, - DownloadEntry, -) -from api.v1.modules.a76.invoices.exports.process.sub_process.fill_available_balances import ( - _net_balance_for_lot, -) -from core.exceptions import ErrorCollector -from tests.fixtures.builders import ( - create_business_catalogs, - create_import_invoice_with_line, - ensure_reference_data, -) - - -def test_net_balance_accounts_for_returns_and_entry_void(db_session, test_tenant): - """ - Balance neto esperado: - entry 10 - consumption 4 + return 1 - entry_void 2 = 5 - """ - tid, cid = test_tenant.tenant_id, test_tenant.company_id - ensure_reference_data(db_session) - catalogs = create_business_catalogs(db_session, tenant_id=tid, company_id=cid) - import_invoice, import_line = create_import_invoice_with_line( - db_session, - tid, - cid, - catalogs, - invoice_type="TEM", - invoice_number="IMP-UNIT-01", - qty=Decimal("10"), - ) - - line_id = import_line.id - tenant_id = import_invoice.tenant_id - company_id = import_invoice.company_id - import_invoice_id = import_invoice.id - for idx, (mtype, qty) in enumerate( - [ - (MovementType.ENTRY, Decimal("10")), - (MovementType.CONSUMPTION, Decimal("4")), - (MovementType.RETURN, Decimal("1")), - (MovementType.ENTRY_VOID, Decimal("2")), - ], - start=1, - ): - db_session.add( - BalanceMovement( - id=10000 + idx, - tenant_id=tenant_id, - company_id=company_id, - import_invoice_id=import_invoice_id, - import_item_line_id=line_id, - movement_type=mtype, - quantity=qty, - order_peps=10000 + idx, - operation_date=date(2026, 3, 20), - ) - ) - db_session.flush() - - balance = _net_balance_for_lot(db_session, line_id, date(2026, 3, 21)) - assert balance == Decimal("5") - - -def test_fifo_consumption_algorithm_uses_oldest_lots_first(db_session, monkeypatch): - """ - Verifica distribución FIFO (PEPS): - - lote1 (order=1, qty=3), lote2 (order=2, qty=5), demanda=6 - - resultado: lote1 consume 3, lote2 consume 3, faltante 0 - """ - monkeypatch.setattr( - "api.v1.modules.a76.invoices.exports.process.sub_process.compare_balances._resolve_import_uom", - lambda db, import_item_line_id: "PZA", - ) - - entry = DownloadEntry( - origin_procedure="TEM", - export_line=1, - part_number="PN-1", - class_code="CLS", - quantity=Decimal("6"), - quantity_used=Decimal("0"), - unit_of_measure="PZA", - import_invoice="IMP-1", - import_line=1, - line_item_id=1, - available_lots=[ - AvailableLot( - import_item_line_id=11, - import_invoice_id=101, - part_number_id=None, - available_qty=Decimal("3"), - value_me=Decimal("0"), - value_mn=Decimal("0"), - order_peps=1, - ), - AvailableLot( - import_item_line_id=12, - import_invoice_id=101, - part_number_id=None, - available_qty=Decimal("5"), - value_me=Decimal("0"), - value_mn=Decimal("0"), - order_peps=2, - ), - ], - ) - - errors = ErrorCollector() - compare_balances(db_session, export_invoice=None, to_discharge=[entry], errors=errors) # type: ignore[arg-type] - assert not errors.has_errors() - assert entry.quantity_used == Decimal("6") - assert entry.available_lots[0].consumed_qty == Decimal("3") - assert entry.available_lots[1].consumed_qty == Decimal("3") diff --git a/backend/tests/unit/invoices/test_currency_conversion_imports.py b/backend/tests/unit/invoices/test_currency_conversion_imports.py deleted file mode 100644 index aac88f8e..00000000 --- a/backend/tests/unit/invoices/test_currency_conversion_imports.py +++ /dev/null @@ -1,72 +0,0 @@ -from decimal import Decimal -from types import SimpleNamespace - -from api.v1.modules.a76.invoices.models import Currency -from api.v1.modules.a76.invoices.imports.process.sub_process.assing_values_def_mex import ( - assign_values_iva_lines, -) - - -def _mk_line(qty: str, capture: str): - return SimpleNamespace( - quantity=SimpleNamespace(quantity=Decimal(qty)), - financial=SimpleNamespace( - unit_cost_capture=Decimal(capture), - unit_cost_usd=None, - unit_cost_mxn=None, - unit_cost_mc=None, - sub_import_value_usd=None, - sub_import_value_mxn=None, - sub_import_value_mc=None, - vat_usd=None, - vat_mxn=None, - vat_mc=None, - value_usd=None, - value_mxn=None, - value_mc=None, - ), - ) - - -def _mk_invoice(currency: Currency): - return SimpleNamespace( - financials=SimpleNamespace( - currency=currency, - exchange_rate=Decimal("17.25"), - exchange_rate_mm=Decimal("1.10"), - iva_factor="16", - ) - ) - - -def test_assign_values_iva_lines_currency_me(): - invoice = _mk_invoice(Currency.FOREIGN) - line = _mk_line("10", "2") - assign_values_iva_lines(invoice, [line]) - - assert line.financial.sub_import_value_usd == Decimal("20") - assert line.financial.vat_usd == Decimal("3.2") - assert line.financial.value_usd == Decimal("23.2") - assert line.financial.unit_cost_mxn == Decimal("34.50") - - -def test_assign_values_iva_lines_currency_mn(): - invoice = _mk_invoice(Currency.LOCAL) - line = _mk_line("5", "100") - assign_values_iva_lines(invoice, [line]) - - assert line.financial.sub_import_value_mxn == Decimal("500") - assert line.financial.vat_mxn == Decimal("80") - assert line.financial.value_mxn == Decimal("580") - assert line.financial.unit_cost_usd == Decimal("5.797101449275362318840579710") - - -def test_assign_values_iva_lines_currency_mc(): - invoice = _mk_invoice(Currency.MANUAL) - line = _mk_line("4", "3") - assign_values_iva_lines(invoice, [line]) - - assert line.financial.sub_import_value_mc == Decimal("12") - assert line.financial.vat_mc == Decimal("1.92") - assert line.financial.value_mc == Decimal("13.92") - assert line.financial.unit_cost_usd == Decimal("3.30") diff --git a/backend/tests/unit/test_csv_reader_encoding.py b/backend/tests/unit/test_csv_reader_encoding.py deleted file mode 100644 index b2f7487b..00000000 --- a/backend/tests/unit/test_csv_reader_encoding.py +++ /dev/null @@ -1,150 +0,0 @@ -from pathlib import Path - -from api.v1.modules.a76.layouts_csv.common.csv_reader import ( - CsvReadPlan, - detect_text_encoding, - inspect_csv, - iter_csv_rows, - iter_csv_rows_with_plan, -) -from api.v1.modules.a76.layouts_csv.classes.template_config import ( - detect_headers_or_data as detect_classes_headers, - row_from_template as row_from_classes_template, -) -from api.v1.modules.a76.layouts_csv.parts.template_config import detect_headers_or_data as detect_parts_headers -from api.v1.modules.a76.layouts_csv.pedmientos.template_config import ( - detect_headers_or_data as detect_pedimentos_headers, - parse_pedimento_col_a, -) - - -def _write_bytes(tmp_path: Path, name: str, payload: bytes) -> Path: - file_path = tmp_path / name - file_path.write_bytes(payload) - return file_path - - -def test_detect_text_encoding_handles_truncated_utf8_sample(tmp_path: Path): - # Regression: "ó" in "Descripción" empieza en byte 21 (0xC3 0xB3). - # sample_bytes=22 lee bytes 0-21, terminando en 0xC3 (primer byte de ó, secuencia incompleta). - # El código viejo: raw.decode("utf-8-sig") fallaba → caía a cp1252 → mojibake. - # El código nuevo: decoder incremental tolera el corte → retorna utf-8-sig. - payload = "DESCRIPCION\nDescripción español\n".encode("utf-8") - file_path = _write_bytes(tmp_path, "truncated_utf8.csv", payload) - - enc = detect_text_encoding(str(file_path), sample_bytes=22) - - assert enc in ("utf-8", "utf-8-sig"), ( - f"Got {enc!r} — el archivo UTF-8 con corte de muestra a mitad de multibyte " - "fue detectado como cp1252, produciendo mojibake (español / Descripción)" - ) - - -def test_utf8_enie_at_sample_boundary_not_detected_as_cp1252(tmp_path: Path): - # Regresión directa del bug mojibake reportado en producción. - # Construye un payload donde 'ñ' (0xC3 0xB1 en UTF-8) cae exactamente en el byte 19, - # y sample_bytes=20 lee sólo 0xC3 (primer byte) — secuencia incompleta. - # Resultado esperado: utf-8 / utf-8-sig (no cp1252). - header = b"CLASE,DESC\n" # 11 bytes - row = "C01,español\n".encode("utf-8") # ñ en bytes 19-20 del payload total - payload = header + row - file_path = _write_bytes(tmp_path, "regression_mojibake.csv", payload) - - enc = detect_text_encoding(str(file_path), sample_bytes=20) - - assert enc in ("utf-8", "utf-8-sig"), ( - f"Got {enc!r} en lugar de utf-8 — leer como cp1252 produciría " - "'español' en lugar de 'español'" - ) - - -def test_iter_csv_rows_preserves_utf8_values(tmp_path: Path): - payload = "CLASE,DESCRIPCION ESPAÑOL\nCLASE01,Clase prueba español\n".encode("utf-8") - file_path = _write_bytes(tmp_path, "utf8_values.csv", payload) - - rows = list(iter_csv_rows(str(file_path))) - - assert len(rows) == 1 - _, row = rows[0] - assert row["DESCRIPCION ESPAÑOL"] == "Clase prueba español" - - -def test_iter_csv_rows_keeps_cp1252_compatibility(tmp_path: Path): - payload = "CLASE,DESCRIPCION ESPAÑOL\nCLASE01,Descripción\n".encode("cp1252") - file_path = _write_bytes(tmp_path, "cp1252_values.csv", payload) - - rows = list(iter_csv_rows(str(file_path))) - - assert len(rows) == 1 - _, row = rows[0] - assert row["DESCRIPCION ESPAÑOL"] == "Descripción" - - -def test_parts_detect_headers_or_data_handles_cp1252(tmp_path: Path): - payload = "NUMERO DE PARTE,DESCRIPCION EN ESPAÑOL\nP-01,Descripción\n".encode("cp1252") - file_path = _write_bytes(tmp_path, "parts_cp1252.csv", payload) - - fieldnames, has_header = detect_parts_headers(str(file_path), lambda s: (s or "").strip().upper()) - - assert has_header is True - assert fieldnames is None - - -def test_pedimentos_detect_headers_or_data_handles_cp1252_data_first_row(tmp_path: Path): - payload = "24,1234,1234567,I,A1\n".encode("cp1252") - file_path = _write_bytes(tmp_path, "pedimentos_cp1252_data.csv", payload) - - fieldnames, has_header = detect_pedimentos_headers( - str(file_path), - lambda s: (s or "").strip().upper(), - parse_pedimento_col_a, - ) - - assert has_header is False - assert fieldnames is not None - - -def test_classes_detect_headers_or_data_handles_cp1252(tmp_path: Path): - payload = "CLAVE CLASE;DESCRIPCION ESPAÑOL\nC01;Descripción\n".encode("cp1252") - file_path = _write_bytes(tmp_path, "classes_cp1252_semicolon.csv", payload) - - fieldnames, has_header = detect_classes_headers(str(file_path), lambda s: (s or "").strip().upper()) - - assert has_header is True - assert fieldnames is None - - -def test_classes_row_from_template_recovers_collapsed_header_with_semicolon(): - row = {"CLAVE CLASE,DESCRIPCION ESPAÑOL": "C01;Descripción;Description"} - mapped = row_from_classes_template(row, lambda s: (s or "").strip().upper()) - assert mapped["CLASE"] == "C01" - - -def test_iter_csv_rows_with_plan_headerless_and_semicolon(tmp_path: Path): - payload = "C01;Descripcion 1\nC02;Descripcion 2\n".encode("utf-8") - file_path = _write_bytes(tmp_path, "headerless_semicolon.csv", payload) - plan = CsvReadPlan( - header_mode="headerless", - fieldnames=["CLASE", "DESCRIPCIONE"], - ) - - rows = list(iter_csv_rows_with_plan(str(file_path), plan)) - - assert len(rows) == 2 - assert rows[0][1]["CLASE"] == "C01" - assert rows[1][1]["DESCRIPCIONE"] == "Descripcion 2" - - -def test_inspect_csv_auto_mode_switches_to_headerless(tmp_path: Path): - payload = "C01,Descripcion\n".encode("utf-8") - file_path = _write_bytes(tmp_path, "auto_mode.csv", payload) - plan = CsvReadPlan( - header_mode="auto", - fieldnames=["CLASE", "DESCRIPCIONE"], - headerless_first_cell_values={"C01"}, - ) - - metadata = inspect_csv(str(file_path), plan) - - assert metadata.has_header is False - assert metadata.fieldnames == ["CLASE", "DESCRIPCIONE"] diff --git a/docker-compose.e2e.yml b/docker-compose.e2e.yml index 96213017..9bdb1dd6 100644 --- a/docker-compose.e2e.yml +++ b/docker-compose.e2e.yml @@ -15,17 +15,17 @@ # E2E_FRONTEND_IMAGE — imagen temporal del frontend con VITE_API_URL=localhost services: - postgres-a76: + postgres-app: image: postgres:18-alpine environment: - POSTGRES_DB: anexo76_core + POSTGRES_DB: app_core POSTGRES_USER: postgres POSTGRES_PASSWORD: postgres POSTGRES_INITDB_ARGS: "--encoding=UTF8" networks: - e2e-net healthcheck: - test: ["CMD-SHELL", "pg_isready -U postgres -d anexo76_core || exit 1"] + test: ["CMD-SHELL", "pg_isready -U postgres -d app_core || exit 1"] interval: 5s timeout: 3s retries: 10 @@ -65,9 +65,9 @@ services: - ENVIRONMENT=e2e - PYTHONUNBUFFERED=1 - PYTHONDONTWRITEBYTECODE=1 - - CORE_DB_HOST=postgres-a76 + - CORE_DB_HOST=postgres-app - CORE_DB_PORT=5432 - - CORE_DB_NAME=anexo76_core + - CORE_DB_NAME=app_core - CORE_DB_USER=postgres - CORE_DB_PASSWORD=postgres # Keycloak — apunta a Workspace real; el cliente anexo76-frontend ya @@ -97,7 +97,7 @@ services: ports: - "8000:8000" depends_on: - postgres-a76: + postgres-app: condition: service_healthy minio: condition: service_healthy diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 3ea6ba6e..c3f08eef 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -1,10 +1,10 @@ services: # PostgreSQL - Base de datos core (app) - postgres-a76: + postgres-app: image: postgres:18-alpine - container_name: anexo76-postgres-a76 + container_name: app-postgres-app environment: - POSTGRES_DB: anexo76_core + POSTGRES_DB: app_core POSTGRES_USER: postgres POSTGRES_PASSWORD: ${POSTGRES_APP_PASSWORD:-postgres} POSTGRES_INITDB_ARGS: "--encoding=UTF8" @@ -16,7 +16,7 @@ services: - backend-net restart: unless-stopped healthcheck: - test: [ "CMD-SHELL", "pg_isready -U postgres -d anexo76_core || exit 1" ] + test: [ "CMD-SHELL", "pg_isready -U postgres -d app_core || exit 1" ] interval: 5s timeout: 3s retries: 10 @@ -37,7 +37,7 @@ services: # Backend - FastAPI backend: image: dev.aduanasoft.com/anexo76/backend:latest - container_name: anexo76-backend + container_name: app-backend environment: - DEBUG=${DEBUG:-True} - ENVIRONMENT=${ENVIRONMENT:-production} @@ -45,13 +45,13 @@ services: - PYTHONDONTWRITEBYTECODE=1 - CORE_DB_HOST=${CORE_DB_HOST:-postgres-a76} - CORE_DB_PORT=${CORE_DB_PORT:-5432} - - CORE_DB_NAME=${CORE_DB_NAME:-anexo76_core} + - CORE_DB_NAME=${CORE_DB_NAME:-app_core} - CORE_DB_USER=${CORE_DB_USER:-postgres} - CORE_DB_PASSWORD=${POSTGRES_APP_PASSWORD:-postgres} # Keycloak — fuente única de autenticación: Workspace (workspace.aduanasoft.com) - KEYCLOAK_SERVER_URL=${KEYCLOAK_SERVER_URL:-https://workspace.aduanasoft.com/kcauth} - KEYCLOAK_REALM=${KEYCLOAK_REALM:-master} - - KEYCLOAK_CLIENT_ID=${KEYCLOAK_CLIENT_ID:-anexo76-frontend} + - KEYCLOAK_CLIENT_ID=${KEYCLOAK_CLIENT_ID:-app-frontend} - KEYCLOAK_CLIENT_SECRET=${KEYCLOAK_CLIENT_SECRET:-} - CORS_ORIGINS=${CORS_ORIGINS:-https://anexo76-dev.aduanasoft.com,http://localhost:3000} - SITAR_API_URL=${SITAR_API_URL} @@ -79,7 +79,7 @@ services: - S3_ENDPOINT_URL=${S3_ENDPOINT_URL:-http://minio:9000} - S3_ACCESS_KEY=${S3_ACCESS_KEY:-${MINIO_ROOT_USER:-minioadmin}} - S3_SECRET_KEY=${S3_SECRET_KEY:-${MINIO_ROOT_PASSWORD:-minioadmin}} - - S3_BUCKET=${S3_BUCKET:-anexo76} + - S3_BUCKET=${S3_BUCKET:-app} - S3_REGION=${S3_REGION:-us-east-1} - S3_USE_SSL=${S3_USE_SSL:-false} - S3_FILE_STORAGE=${S3_FILE_STORAGE:-true} @@ -87,7 +87,7 @@ services: ports: - "3467:8000" depends_on: - postgres-a76: + postgres-app: condition: service_healthy minio: condition: service_healthy @@ -129,7 +129,7 @@ services: - SPOKE_URLS=${SPOKE_URLS:-""} - CORE_DB_HOST=${CORE_DB_HOST:-postgres-a76} - CORE_DB_PORT=${CORE_DB_PORT:-5432} - - CORE_DB_NAME=${CORE_DB_NAME:-anexo76_core} + - CORE_DB_NAME=${CORE_DB_NAME:-app_core} - CORE_DB_USER=${CORE_DB_USER:-postgres} - CORE_DB_PASSWORD=${POSTGRES_APP_PASSWORD:-postgres} - SITAR_API_URL=${SITAR_API_URL} @@ -144,7 +144,7 @@ services: - S3_ENDPOINT_URL=${S3_ENDPOINT_URL:-http://minio:9000} - S3_ACCESS_KEY=${S3_ACCESS_KEY:-${MINIO_ROOT_USER:-minioadmin}} - S3_SECRET_KEY=${S3_SECRET_KEY:-${MINIO_ROOT_PASSWORD:-minioadmin}} - - S3_BUCKET=${S3_BUCKET:-anexo76} + - S3_BUCKET=${S3_BUCKET:-app} - S3_REGION=${S3_REGION:-us-east-1} - S3_USE_SSL=${S3_USE_SSL:-false} - S3_FILE_STORAGE=${S3_FILE_STORAGE:-true} @@ -167,7 +167,7 @@ services: - SPOKE_URLS=${SPOKE_URLS:-""} - CORE_DB_HOST=${CORE_DB_HOST:-postgres-a76} - CORE_DB_PORT=${CORE_DB_PORT:-5432} - - CORE_DB_NAME=${CORE_DB_NAME:-anexo76_core} + - CORE_DB_NAME=${CORE_DB_NAME:-app_core} - CORE_DB_USER=${CORE_DB_USER:-postgres} - CORE_DB_PASSWORD=${POSTGRES_APP_PASSWORD:-postgres} - SITAR_API_URL=${SITAR_API_URL} @@ -182,7 +182,7 @@ services: - S3_ENDPOINT_URL=${S3_ENDPOINT_URL:-http://minio:9000} - S3_ACCESS_KEY=${S3_ACCESS_KEY:-${MINIO_ROOT_USER:-minioadmin}} - S3_SECRET_KEY=${S3_SECRET_KEY:-${MINIO_ROOT_PASSWORD:-minioadmin}} - - S3_BUCKET=${S3_BUCKET:-anexo76} + - S3_BUCKET=${S3_BUCKET:-app} - S3_REGION=${S3_REGION:-us-east-1} - S3_USE_SSL=${S3_USE_SSL:-false} - S3_FILE_STORAGE=${S3_FILE_STORAGE:-true} @@ -206,7 +206,7 @@ services: minio: image: minio/minio:RELEASE.2025-09-07T16-13-09Z - container_name: anexo76-minio + container_name: app-minio command: server /data --console-address ":9001" environment: MINIO_ROOT_USER: ${MINIO_ROOT_USER:-minioadmin} @@ -234,7 +234,7 @@ services: # Frontend - SvelteKit frontend: image: dev.aduanasoft.com/anexo76/frontend:latest - container_name: anexo76-frontend + container_name: app-frontend environment: - NODE_ENV=${NODE_ENV:-production} - VITE_API_URL=${VITE_API_URL:-https://anexo76-dev.aduanasoft.com/api} @@ -250,11 +250,11 @@ services: # Debe ser el mismo Keycloak donde el usuario tiene su sesión de Workspace. - VITE_KEYCLOAK_URL=${VITE_KEYCLOAK_URL:-https://workspace.aduanasoft.com/kcauth} - VITE_KEYCLOAK_REALM=${VITE_KEYCLOAK_REALM:-master} - - VITE_KEYCLOAK_CLIENT_ID=${VITE_KEYCLOAK_CLIENT_ID:-anexo76-frontend} + - VITE_KEYCLOAK_CLIENT_ID=${VITE_KEYCLOAK_CLIENT_ID:-app-frontend} # KEYCLOAK_URL: URL que usa SvelteKit server-side para intercambiar código por tokens. - KEYCLOAK_URL=${KEYCLOAK_URL:-https://workspace.aduanasoft.com/kcauth} - KEYCLOAK_REALM=${KEYCLOAK_REALM:-master} - - KEYCLOAK_CLIENT_ID=${KEYCLOAK_CLIENT_ID:-anexo76-frontend} + - KEYCLOAK_CLIENT_ID=${KEYCLOAK_CLIENT_ID:-app-frontend} - KEYCLOAK_CLIENT_SECRET=${KEYCLOAK_CLIENT_SECRET:-} # SvelteKit ORIGIN — determina url.origin en load functions y el flag secure de cookies. # ⚠️ Docker Compose carga el .env raíz automáticamente. Si ese .env tiene diff --git a/docker-compose.yml b/docker-compose.yml index ad7e7cda..46212daa 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,22 +1,25 @@ +name: app + services: - # PostgreSQL - Base de datos core (app) - postgres-a76: + + # ── Base de datos ──────────────────────────────────────────────────────────── + postgres: image: postgres:18-alpine - container_name: anexo76-postgres-a76 + container_name: app-postgres environment: - POSTGRES_DB: anexo76_core - POSTGRES_USER: postgres + POSTGRES_DB: ${CORE_DB_NAME:-app_core} + POSTGRES_USER: ${CORE_DB_USER:-postgres} POSTGRES_PASSWORD: ${POSTGRES_APP_PASSWORD:-postgres} POSTGRES_INITDB_ARGS: "--encoding=UTF8" ports: - "5432:5432" volumes: - - postgres_app_data:/var/lib/postgresql + - postgres_data:/var/lib/postgresql networks: - - backend-net + - app-backend restart: unless-stopped healthcheck: - test: [ "CMD-SHELL", "pg_isready -U postgres -d anexo76_core || exit 1" ] + test: ["CMD-SHELL", "pg_isready -U ${CORE_DB_USER:-postgres} -d ${CORE_DB_NAME:-app_core} || exit 1"] interval: 5s timeout: 3s retries: 10 @@ -34,71 +37,66 @@ services: memory: 256M shm_size: 128mb - # Backend - FastAPI + # ── Backend FastAPI ────────────────────────────────────────────────────────── backend: build: context: ./backend dockerfile: Dockerfile args: - BUILDKIT_INLINE_CACHE=1 - image: anexo76-backend:latest - container_name: anexo76-backend + image: app-backend:latest + container_name: app-backend environment: - DEBUG=${DEBUG:-True} - ENVIRONMENT=${ENVIRONMENT:-development} - PYTHONUNBUFFERED=1 - PYTHONDONTWRITEBYTECODE=1 - - CORE_DB_HOST=${CORE_DB_HOST:-postgres-a76} + - DEV_LOCAL_AUTH=${DEV_LOCAL_AUTH:-False} + - DEV_LOCAL_AUTH_EMAIL=${DEV_LOCAL_AUTH_EMAIL:-dev@local.test} + - DEV_LOCAL_AUTH_NAME=${DEV_LOCAL_AUTH_NAME:-Dev User} + - DEV_LOCAL_AUTH_TENANT_ID=${DEV_LOCAL_AUTH_TENANT_ID:-1} + - DEV_LOCAL_AUTH_COMPANY_ID=${DEV_LOCAL_AUTH_COMPANY_ID:-1} + - CORE_DB_HOST=${CORE_DB_HOST:-postgres} - CORE_DB_PORT=${CORE_DB_PORT:-5432} - - CORE_DB_NAME=${CORE_DB_NAME:-anexo76_core} + - CORE_DB_NAME=${CORE_DB_NAME:-app_core} - CORE_DB_USER=${CORE_DB_USER:-postgres} - CORE_DB_PASSWORD=${POSTGRES_APP_PASSWORD:-postgres} - # Keycloak — apunta al Keycloak del Hub (ya no tiene Keycloak propio) - + - SECRET_KEY=${SECRET_KEY:-change-this-secret-key-in-production} - CORS_ORIGINS=${CORS_ORIGINS:-http://localhost:5173,http://localhost:3000} - - SITAR_API_URL=${SITAR_API_URL} - - SITAR_API_USER=${SITAR_API_USER} - - SITAR_API_PASSWORD=${SITAR_API_PASSWORD} - - COVE_FIEL_HASH_KEY=${COVE_FIEL_HASH_KEY} - - COVE_FIEL_HASH_IV=${COVE_FIEL_HASH_IV} - - COVE_API_URL=${COVE_API_URL:-https://api.vu.aduanasoft.com} - - COVE_API_VERIFY_SSL=${COVE_API_VERIFY_SSL:-False} - VALKEY_URL=${VALKEY_URL:-redis://valkey:6379/0} - CENTRAL_SERVER_URL=${CENTRAL_SERVER_URL:-""} - SYNC_SECRET_TOKEN=${SYNC_SECRET_TOKEN:-change-this-sync-token-in-production} - SPOKE_URLS=${SPOKE_URLS:-""} - # Hub — URL interna para validación de licencias - - HUB_URL=${HUB_URL:-http://host.docker.internal:8001} + - HUB_URL=${WORKSPACE_URL:-http://host.docker.internal:8001} - HUB_ADMIN_EMAIL=${HUB_ADMIN_EMAIL:-} - HUB_ADMIN_PASSWORD=${HUB_ADMIN_PASSWORD:-} - - APP_PUBLIC_URL=${APP_PUBLIC_URL:-http://localhost:3001} - - CSV_IMPORT_STORAGE=${CSV_IMPORT_STORAGE:-minio} + - APP_PUBLIC_URL=${APP_PUBLIC_URL:-http://localhost:5173} - S3_ENDPOINT_URL=${S3_ENDPOINT_URL:-http://minio:9000} - - S3_ACCESS_KEY=${S3_ACCESS_KEY:-${MINIO_ROOT_USER:-minioadmin}} - - S3_SECRET_KEY=${S3_SECRET_KEY:-${MINIO_ROOT_PASSWORD:-minioadmin}} - - S3_BUCKET=${S3_BUCKET:-anexo76} + - S3_ACCESS_KEY=${S3_ACCESS_KEY:-minioadmin} + - S3_SECRET_KEY=${S3_SECRET_KEY:-minioadmin} + - S3_BUCKET=${S3_BUCKET:-app} - S3_REGION=${S3_REGION:-us-east-1} - S3_USE_SSL=${S3_USE_SSL:-false} - S3_FILE_STORAGE=${S3_FILE_STORAGE:-true} - S3_PRESIGNED_EXPIRES_SECONDS=${S3_PRESIGNED_EXPIRES_SECONDS:-3600} + - CSV_IMPORT_STORAGE=${CSV_IMPORT_STORAGE:-minio} ports: - "8000:8000" depends_on: - postgres-a76: + postgres: condition: service_healthy volumes: - ./backend:/app - backend_cache:/app/__pycache__ - backend_uploads:/app/uploads networks: - - backend-net - - frontend-net - - hub-net + - app-backend + - app-frontend + - app-hub restart: unless-stopped - command: [ "uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--reload", "--log-level", "info" ] - # El lifespan corre Alembic antes de servir; 1.ª subida a DB vacía puede tardar varios minutos (E2E/CI) + command: ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000", "--reload", "--log-level", "info"] healthcheck: - test: [ "CMD-SHELL", "curl -f http://localhost:8000/api/health || exit 1" ] + test: ["CMD-SHELL", "curl -f http://localhost:8000/api/health || exit 1"] interval: 20s timeout: 10s retries: 20 @@ -115,34 +113,33 @@ services: reservations: memory: 512M - # Frontend - SvelteKit + # ── Frontend SvelteKit ─────────────────────────────────────────────────────── frontend: build: context: ./frontend dockerfile: Dockerfile args: - BUILDKIT_INLINE_CACHE=1 - image: anexo76-frontend:latest - container_name: anexo76-frontend + image: app-frontend:latest + container_name: app-frontend environment: - NODE_ENV=${NODE_ENV:-development} + - DEV_LOCAL_AUTH=${DEV_LOCAL_AUTH:-false} + - BACKEND_URL=${BACKEND_URL:-http://backend:8000} - VITE_API_URL=${VITE_API_URL:-http://localhost:8000/api/} - INTERNAL_API_URL=${INTERNAL_API_URL:-http://backend:8000/api/} - - VITE_KEYCLOAK_URL=${VITE_KEYCLOAK_URL:-http://localhost:8085/kcauth} - - VITE_KEYCLOAK_REALM=${VITE_KEYCLOAK_REALM:-master} - - VITE_KEYCLOAK_CLIENT_ID=${VITE_KEYCLOAK_CLIENT_ID:-anexo76-frontend} - # SSR server-side — usa URL interna del contenedor Keycloak (más rápido, sin salir a la LAN) - - KEYCLOAK_URL=${KEYCLOAK_URL:-http://hub-keycloak:8080/kcauth} + # Hub y Keycloak se derivan de WORKSPACE_URL (una sola fuente de verdad) + - VITE_HUB_URL=${WORKSPACE_URL:-http://localhost:8001} + - HUB_URL=${WORKSPACE_URL:-http://localhost:8001} + - INTERNAL_HUB_URL=${WORKSPACE_URL:-http://localhost:8001} + - VITE_KEYCLOAK_URL=${WORKSPACE_URL:-http://localhost:8085}/kcauth + - KEYCLOAK_URL=${WORKSPACE_URL:-http://localhost:8085}/kcauth + - VITE_KEYCLOAK_REALM=${KEYCLOAK_REALM:-master} - KEYCLOAK_REALM=${KEYCLOAK_REALM:-master} - - KEYCLOAK_CLIENT_ID=${KEYCLOAK_CLIENT_ID:-anexo76-frontend} - # Hub — URL pública para el browser y URL interna para server-side - - VITE_HUB_URL=${VITE_HUB_URL:-http://localhost:8001} - - HUB_URL=${HUB_URL:-http://localhost:8001} - - INTERNAL_HUB_URL=${INTERNAL_HUB_URL:-http://host.docker.internal:8001} - # CORS / CSRF — trusted origins para svelte.config.js - - CORS_ORIGINS=${CORS_ORIGINS:-http://localhost:5173,http://localhost:3001} + - VITE_KEYCLOAK_CLIENT_ID=${KEYCLOAK_CLIENT_ID:-app-frontend} + - KEYCLOAK_CLIENT_ID=${KEYCLOAK_CLIENT_ID:-app-frontend} + - CORS_ORIGINS=${CORS_ORIGINS:-http://localhost:5173} - TRUSTED_ORIGINS=${TRUSTED_ORIGINS:-} - # SvelteKit ORIGIN — evita que request.url.origin use la IP/puerto interno del contenedor - ORIGIN=${ORIGIN:-http://localhost:5173} ports: - "5173:5173" @@ -153,12 +150,12 @@ services: - ./frontend:/app - frontend_node_modules:/app/node_modules networks: - - frontend-net - - hub-net + - app-frontend + - app-hub restart: unless-stopped - command: [ "pnpm", "run", "dev", "--", "--host", "0.0.0.0" ] + command: ["pnpm", "run", "dev", "--", "--host", "0.0.0.0"] healthcheck: - test: [ "CMD-SHELL", "wget --no-verbose --tries=1 --spider http://localhost:5173/ || exit 1" ] + test: ["CMD-SHELL", "wget --no-verbose --tries=1 --spider http://localhost:5173/ || exit 1"] interval: 15s timeout: 5s retries: 5 @@ -169,42 +166,27 @@ services: max-size: "10m" max-file: "3" - # Celery Worker + # ── Celery Worker ──────────────────────────────────────────────────────────── celery_worker: build: ./backend - container_name: worker + image: app-worker:latest + container_name: app-worker command: celery -A core.celery_app worker --loglevel=info environment: - DEBUG=${DEBUG:-True} - ENVIRONMENT=${ENVIRONMENT:-development} - PYTHONUNBUFFERED=1 - - PYTHONDONTWRITEBYTECODE=1 - - VALKEY_URL=${VALKEY_URL:-redis://valkey:6379/0} - - CENTRAL_SERVER_URL=${CENTRAL_SERVER_URL:-""} - - SYNC_SECRET_TOKEN=${SYNC_SECRET_TOKEN:-change-this-sync-token-in-production} - - SPOKE_URLS=${SPOKE_URLS:-""} - - CORE_DB_HOST=${CORE_DB_HOST:-postgres-a76} + - CORE_DB_HOST=${CORE_DB_HOST:-postgres} - CORE_DB_PORT=${CORE_DB_PORT:-5432} - - CORE_DB_NAME=${CORE_DB_NAME:-anexo76_core} + - CORE_DB_NAME=${CORE_DB_NAME:-app_core} - CORE_DB_USER=${CORE_DB_USER:-postgres} - CORE_DB_PASSWORD=${POSTGRES_APP_PASSWORD:-postgres} - - VALKEY_URL=redis://valkey:6379/0 - - SITAR_API_URL=${SITAR_API_URL} - - SITAR_API_USER=${SITAR_API_USER} - - SITAR_API_PASSWORD=${SITAR_API_PASSWORD} - - COVE_FIEL_HASH_KEY=${COVE_FIEL_HASH_KEY} - - COVE_FIEL_HASH_IV=${COVE_FIEL_HASH_IV} - - COVE_API_URL=${COVE_API_URL:-https://api.vu.aduanasoft.com} - - DODA_API_BASE_URL=${DODA_API_BASE_URL} - - DODA_API_VERIFY_SSL=${DODA_API_VERIFY_SSL:-False} - - CSV_IMPORT_STORAGE=${CSV_IMPORT_STORAGE:-minio} + - VALKEY_URL=${VALKEY_URL:-redis://valkey:6379/0} - S3_ENDPOINT_URL=${S3_ENDPOINT_URL:-http://minio:9000} - - S3_ACCESS_KEY=${S3_ACCESS_KEY:-${MINIO_ROOT_USER:-minioadmin}} - - S3_SECRET_KEY=${S3_SECRET_KEY:-${MINIO_ROOT_PASSWORD:-minioadmin}} - - S3_BUCKET=${S3_BUCKET:-anexo76} - - S3_REGION=${S3_REGION:-us-east-1} - - S3_USE_SSL=${S3_USE_SSL:-false} - - S3_FILE_STORAGE=${S3_FILE_STORAGE:-true} + - S3_ACCESS_KEY=${S3_ACCESS_KEY:-minioadmin} + - S3_SECRET_KEY=${S3_SECRET_KEY:-minioadmin} + - S3_BUCKET=${S3_BUCKET:-app} + - CSV_IMPORT_STORAGE=${CSV_IMPORT_STORAGE:-minio} depends_on: backend: condition: service_healthy @@ -215,39 +197,21 @@ services: - backend_cache:/app/__pycache__ - backend_uploads:/app/uploads networks: - - backend-net + - app-backend - # Celery Beat + # ── Celery Beat ────────────────────────────────────────────────────────────── celery_beat: build: ./backend - container_name: celery_beat + image: app-celery-beat:latest + container_name: app-celery-beat command: celery -A core.celery_app beat --loglevel=info environment: - - VALKEY_URL=${VALKEY_URL:-redis://valkey:6379/0} - - CENTRAL_SERVER_URL=${CENTRAL_SERVER_URL:-""} - - SYNC_SECRET_TOKEN=${SYNC_SECRET_TOKEN:-change-this-sync-token-in-production} - - SPOKE_URLS=${SPOKE_URLS:-""} - - CORE_DB_HOST=${CORE_DB_HOST:-postgres-a76} + - CORE_DB_HOST=${CORE_DB_HOST:-postgres} - CORE_DB_PORT=${CORE_DB_PORT:-5432} - - CORE_DB_NAME=${CORE_DB_NAME:-anexo76_core} + - CORE_DB_NAME=${CORE_DB_NAME:-app_core} - CORE_DB_USER=${CORE_DB_USER:-postgres} - CORE_DB_PASSWORD=${POSTGRES_APP_PASSWORD:-postgres} - - SITAR_API_URL=${SITAR_API_URL} - - SITAR_API_USER=${SITAR_API_USER} - - SITAR_API_PASSWORD=${SITAR_API_PASSWORD} - - COVE_FIEL_HASH_KEY=${COVE_FIEL_HASH_KEY} - - COVE_FIEL_HASH_IV=${COVE_FIEL_HASH_IV} - - COVE_API_URL=${COVE_API_URL:-https://api.vu.aduanasoft.com} - - DODA_API_BASE_URL=${DODA_API_BASE_URL} - - DODA_API_VERIFY_SSL=${DODA_API_VERIFY_SSL:-False} - - CSV_IMPORT_STORAGE=${CSV_IMPORT_STORAGE:-minio} - - S3_ENDPOINT_URL=${S3_ENDPOINT_URL:-http://minio:9000} - - S3_ACCESS_KEY=${S3_ACCESS_KEY:-${MINIO_ROOT_USER:-minioadmin}} - - S3_SECRET_KEY=${S3_SECRET_KEY:-${MINIO_ROOT_PASSWORD:-minioadmin}} - - S3_BUCKET=${S3_BUCKET:-anexo76} - - S3_REGION=${S3_REGION:-us-east-1} - - S3_USE_SSL=${S3_USE_SSL:-false} - - S3_FILE_STORAGE=${S3_FILE_STORAGE:-true} + - VALKEY_URL=${VALKEY_URL:-redis://valkey:6379/0} depends_on: backend: condition: service_healthy @@ -256,22 +220,23 @@ services: volumes: - ./backend:/app - backend_cache:/app/__pycache__ - - backend_uploads:/app/uploads networks: - - backend-net + - app-backend + # ── Valkey (Redis) ─────────────────────────────────────────────────────────── valkey: image: valkey/valkey:7.2 - container_name: valkey + container_name: app-valkey restart: always ports: - "6379:6379" networks: - - backend-net + - app-backend + # ── MinIO (object storage) ─────────────────────────────────────────────────── minio: image: minio/minio:RELEASE.2025-09-07T16-13-09Z - container_name: anexo76-minio + container_name: app-minio command: server /data --console-address ":9001" environment: MINIO_ROOT_USER: ${MINIO_ROOT_USER:-minioadmin} @@ -282,10 +247,10 @@ services: volumes: - minio_data:/data networks: - - backend-net + - app-backend restart: unless-stopped healthcheck: - test: [ "CMD-SHELL", "curl -f http://127.0.0.1:9000/minio/health/live || exit 1" ] + test: ["CMD-SHELL", "curl -f http://127.0.0.1:9000/minio/health/live || exit 1"] interval: 10s timeout: 5s retries: 5 @@ -297,7 +262,7 @@ services: max-file: "3" volumes: - postgres_app_data: + postgres_data: driver: local frontend_node_modules: driver: local @@ -309,11 +274,10 @@ volumes: driver: local networks: - backend-net: + app-backend: driver: bridge - frontend-net: + app-frontend: driver: bridge - - hub-net: + app-hub: external: true name: aduanasoft-hub_default diff --git a/frontend/.env.example b/frontend/.env.example index 28f46d99..04b39b8d 100644 --- a/frontend/.env.example +++ b/frontend/.env.example @@ -1,7 +1,13 @@ # ─── Copia este archivo a .env y ajusta los valores para dev local ───────────── # ─── Para PRODUCCIÓN ver el bloque al final de este archivo ────────────────── -# API de Anexo76 (frontend y SSR) +# Auth local (sin Keycloak/Hub) — pon true para desarrollo standalone +# El backend también necesita DEV_LOCAL_AUTH=True +DEV_LOCAL_AUTH=false +# URL interna del backend (usada por el servidor SvelteKit en SSR, no por el browser) +BACKEND_URL=http://backend:8000 + +# API de Mi Aplicación (frontend y SSR) VITE_API_URL=http://localhost:8000/api/ INTERNAL_API_URL=http://localhost:8000/api/ @@ -13,11 +19,11 @@ INTERNAL_HUB_URL=http://localhost:8001 # Keycloak — URL pública que el BROWSER usará (se bakea en el build) VITE_KEYCLOAK_URL=http://localhost:8085/kcauth VITE_KEYCLOAK_REALM=master -VITE_KEYCLOAK_CLIENT_ID=anexo76-frontend +VITE_KEYCLOAK_CLIENT_ID=app-frontend # Keycloak — URL interna que el SERVIDOR usará (no va al browser) KEYCLOAK_URL=http://localhost:8085/kcauth KEYCLOAK_REALM=master -KEYCLOAK_CLIENT_ID=anexo76-frontend +KEYCLOAK_CLIENT_ID=app-frontend # KEYCLOAK_CLIENT_SECRET= # solo si el cliente KC no es público # SvelteKit — necesario para cookies secure y URLs SSR correctas. diff --git a/frontend/src/app.html b/frontend/src/app.html index 26eb1a95..50c211d1 100644 --- a/frontend/src/app.html +++ b/frontend/src/app.html @@ -3,7 +3,7 @@ - Anexo76 - Gestión de Comercio Exterior + Mi Aplicación - - - - -

- - {m['invoice_item_fa.prerequisites.title']()} -
- -

{message}

-

- {m['invoice_item_fa.prerequisites.register_hint']()} - - {m['invoice_item_fa.prerequisites.agents_link']()} - - {m['invoice_item_fa.prerequisites.and']()} - - {m['invoice_item_fa.prerequisites.clients_link']()} - . -

- - - - {m['invoice_item_fa.prerequisites.cancel']()} - {m['invoice_item_fa.prerequisites.accept']()} - - - diff --git a/frontend/src/lib/components/dashboard/activity-feed.svelte b/frontend/src/lib/components/dashboard/activity-feed.svelte deleted file mode 100644 index 0e51198a..00000000 --- a/frontend/src/lib/components/dashboard/activity-feed.svelte +++ /dev/null @@ -1,127 +0,0 @@ - - - - -
-
- - - {m.dashboard_recent_activity()} - - {m.dashboard_latest_operations()} -
- {#if activities.length > 0} - {activities.length} {m.dashboard_records_suffix()} - {/if} -
-
- - {#if activities.length === 0} -
- -

{m.dashboard_no_recent_activity()}

-
- {:else} -
- -
- -
- {#each activities as activity, idx} - {@const Icon = getIcon(activity.type)} -
-
-
- -
-
-
-
-

{activity.title}

- - {formatDate(activity.timestamp)} - -
- {#if activity.description} -

{activity.description}

- {/if} -
-
- {/each} -
-
- {/if} -
-
diff --git a/frontend/src/lib/components/dashboard/clients_and_providers/columns.ts b/frontend/src/lib/components/dashboard/clients_and_providers/columns.ts deleted file mode 100644 index 48ea85e6..00000000 --- a/frontend/src/lib/components/dashboard/clients_and_providers/columns.ts +++ /dev/null @@ -1,169 +0,0 @@ -import { renderComponent, renderSnippet } from "$lib/components/ui/data-table/index.js"; -import { m } from '$lib/i18n/messages'; -import { createRawSnippet } from "svelte"; -import DataTableActions from "./data-table-actions.svelte"; - -export type ClientsProvidersColumnOptions = { - canEdit?: boolean; - canDelete?: boolean; -}; - -export function createColumns(onSuccess: () => void, options: ClientsProvidersColumnOptions = {}) { - const { canEdit = false, canDelete = false } = options; - return [ - { - accessorKey: "id", - header: m['common.col_id'](), - cell: ({ row }) => { - const snippet = createRawSnippet((getData) => { - const { val } = getData(); - return { render: () => `${val}` }; - }); - return renderSnippet(snippet, { val: row.original.id }); - } - }, - { - accessorKey: "rfc", - header: m['common.col_rfc_tax'](), - cell: ({ row }) => { - const snippet = createRawSnippet((getData) => { - const { val } = getData(); - return { render: () => `${val}` }; - }); - return renderSnippet(snippet, { val: row.original.rfc || row.original.tax_id || '—' }); - } - }, - { - accessorKey: "name", - header: m['common.col_name'](), - cell: ({ row }) => { - const snippet = createRawSnippet((getData) => { - const { val } = getData(); - return { render: () => `
${val}
` }; - }); - return renderSnippet(snippet, { val: row.original.name }); - } - }, - { - id: "streets", - header: m['common.col_street'](), - cell: ({ row }) => { - const snippet = createRawSnippet((getData) => { - const { val } = getData(); - return { render: () => `
${val || '-'}
` }; - }); - return renderSnippet(snippet, { val: row.original.address?.streets }); - } - }, - { - id: "exterior_number", - header: m['common.col_ext_num'](), - cell: ({ row }) => { - const snippet = createRawSnippet((getData) => { - const { val } = getData(); - return { render: () => `
${val || '-'}
` }; - }); - return renderSnippet(snippet, { val: row.original.address?.exterior_number }); - } - }, - { - id: "postal_code", - header: m['common.col_zip'](), - cell: ({ row }) => { - const snippet = createRawSnippet((getData) => { - const { val } = getData(); - return { render: () => `${val || '-'}` }; - }); - return renderSnippet(snippet, { val: row.original.address?.postal_code }); - } - }, - { - id: "neighborhood", - header: m['common.col_neighborhood'](), - cell: ({ row }) => { - const snippet = createRawSnippet((getData) => { - const { val } = getData(); - return { render: () => `
${val || '-'}
` }; - }); - return renderSnippet(snippet, { val: row.original.address?.neighborhood }); - } - }, - { - id: "city", - header: m['common.col_city'](), - cell: ({ row }) => { - const snippet = createRawSnippet((getData) => { - const { val } = getData(); - return { render: () => `
${val || '-'}
` }; - }); - return renderSnippet(snippet, { val: row.original.address?.city }); - } - }, - { - id: "country", - header: m['common.col_country'](), - cell: ({ row }) => { - const snippet = createRawSnippet((getData) => { - const { val } = getData(); - return { render: () => `
${val || '-'}
` }; - }); - // Busca en address.country, si no existe pone null - const country = row.original.address?.country; - return renderSnippet(snippet, { val: country }); - } - }, - { - accessorKey: "client_or_provider", - header: m['common.col_type'](), - cell: ({ row }) => { - const snippet = createRawSnippet((getData) => { - const { val } = getData(); - const map = { client: 'Cliente', provider: 'Proveedor', both: 'Ambos' }; - const colors = { - client: 'bg-blue-100 text-blue-800 dark:bg-blue-900 dark:text-blue-300', - provider: 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300', - both: 'bg-purple-100 text-purple-800 dark:bg-purple-900 dark:text-purple-300' - }; - return { - render: () => `${map[val] || val}` - }; - }); - return renderSnippet(snippet, { val: row.original.client_or_provider }); - } - }, - // --- CORRECCIÓN AQUÍ --- - { - accessorKey: "is_active", - header: m['common.col_status'](), - cell: ({ row }) => { - const snippet = createRawSnippet((getData) => { - const { val } = getData(); - - - const isActive = !!val; - - const text = isActive ? 'Activo' : 'Inactivo'; - const color = isActive - ? 'bg-green-100 text-green-800 dark:bg-green-900 dark:text-green-300' - : 'bg-red-100 text-red-800 dark:bg-red-900 dark:text-red-300'; - - return { - render: () => `${text}` - }; - }); - return renderSnippet(snippet, { val: row.original.is_active }); - } - }, - { - id: "actions", - header: m['common.col_actions'](), - cell: ({ row }) => - renderComponent(DataTableActions, { - item: row.original, - onSuccess, - canEdit, - canDelete - }) - } - ]; -} \ No newline at end of file diff --git a/frontend/src/lib/components/dashboard/clients_and_providers/create-edit-dialog.svelte b/frontend/src/lib/components/dashboard/clients_and_providers/create-edit-dialog.svelte deleted file mode 100644 index 469de742..00000000 --- a/frontend/src/lib/components/dashboard/clients_and_providers/create-edit-dialog.svelte +++ /dev/null @@ -1,558 +0,0 @@ - - - - - - - {isEditing ? "Editar" : "Nuevo"} Cliente/Proveedor - - - {isEditing - ? "Modifica los datos del cliente o proveedor." - : "Completa los datos para crear un nuevo cliente o proveedor."} - - - -
- {#if error} -
- {error} -
- {/if} - - -
-

Información Básica

- -
-
- - -
-
- - -
- -
- - -
-
- -
- - -
- -
-
- - -
- -
- - -
-
-
- - -
-

Dirección

- -
- - -
- -
-
- - -
- -
- - -
-
- -
-
- - -
- -
- - -
-
- -
-
- - -
- -
- - -
- -
- - -
-
- -
- - -
- -
-
- - -
-
- - -
-
- -
- - -
-
- - -
-

Programas

- -
-
- - -
- -
- - -
- -
- - -
-
-
- - - - - -
-
-
diff --git a/frontend/src/lib/components/dashboard/clients_and_providers/data-table-actions.svelte b/frontend/src/lib/components/dashboard/clients_and_providers/data-table-actions.svelte deleted file mode 100644 index de0ee204..00000000 --- a/frontend/src/lib/components/dashboard/clients_and_providers/data-table-actions.svelte +++ /dev/null @@ -1,111 +0,0 @@ - - - - - {#snippet child({ props })} - - {/snippet} - - - - Acciones - - Copiar ID - - - Copiar {isForeign(item) ? 'TAX-ID' : 'RFC'} - - - - Ver detalles - {#if canEdit} - goto(`/dashboard/clients_and_providers/edit/${item.id}`)}> - Editar - - - {isToggling ? 'Cambiando...' : item.is_active === true ? 'Desactivar' : 'Activar'} - - {/if} - {#if canDelete} - - Eliminar - {/if} - - - - - \ No newline at end of file diff --git a/frontend/src/lib/components/dashboard/clients_and_providers/data-table.svelte b/frontend/src/lib/components/dashboard/clients_and_providers/data-table.svelte deleted file mode 100644 index 4255092c..00000000 --- a/frontend/src/lib/components/dashboard/clients_and_providers/data-table.svelte +++ /dev/null @@ -1,194 +0,0 @@ - - -
-
- - - {#each table.getHeaderGroups() as headerGroup (headerGroup.id)} - - {#each headerGroup.headers as header (header.id)} - - {#if !header.isPlaceholder} - - {/if} - - {/each} - - {/each} - - - {#each table.getRowModel().rows as row (row.id)} - onRowClick && onRowClick(row.original)} - > - {#each row.getVisibleCells() as cell (cell.id)} - - - - {/each} - - {:else} - - - No hay resultados. - - - {/each} - - - {#if hasMore} - - -
- {#if loading} -
-
- Cargando más... -
- {:else} -
- Desplázate para cargar más -
- {/if} -
-
-
- {/if} -
-
-
-
-
-
-
diff --git a/frontend/src/lib/components/dashboard/clients_and_providers/delete-dialog.svelte b/frontend/src/lib/components/dashboard/clients_and_providers/delete-dialog.svelte deleted file mode 100644 index 3cfe2b5f..00000000 --- a/frontend/src/lib/components/dashboard/clients_and_providers/delete-dialog.svelte +++ /dev/null @@ -1,110 +0,0 @@ - - - - - - ¿Estás seguro? - -

Esta acción no se puede deshacer. Se eliminará permanentemente este cliente/proveedor:

- {#if item} -
-
- ID: - {item.id} -
-
- {item ? (isForeign(item) ? 'TAX-ID:' : 'RFC:') : 'RFC:'} - {item.rfc} -
-
- Nombre: - {item.name} -
-
- Tipo: - - {item.client_or_provider === 'client' ? 'Cliente' : - item.client_or_provider === 'provider' ? 'Proveedor' : - item.client_or_provider === 'both' ? 'Ambos' : 'N/A'} - -
-
- {/if} - {#if error} -
- {error} -
- {/if} -
-
- - Cancelar - - {#if loading} - - {/if} - Eliminar - - -
-
diff --git a/frontend/src/lib/components/dashboard/clients_and_providers/details-dialog.svelte b/frontend/src/lib/components/dashboard/clients_and_providers/details-dialog.svelte deleted file mode 100644 index 3b86bd06..00000000 --- a/frontend/src/lib/components/dashboard/clients_and_providers/details-dialog.svelte +++ /dev/null @@ -1,241 +0,0 @@ - - - - - - Detalles del Cliente/Proveedor - - Información completa del cliente o proveedor - - - - {#if item} -
- -
-

Información Básica

- -
-
- ID - {item.id} -
- -
- - {isForeign(item) ? "TAX-ID" : "RFC"} - - {item.rfc} -
-
- -
- Nombre - {item.name} -
- - {#if item.curp} -
- CURP - {item.curp} -
- {/if} - - -
- - -
-

Clasificación

- -
-
- Tipo - {#if item.client_or_provider === 'client'} - - Cliente - - {:else if item.client_or_provider === 'provider'} - - Proveedor - - {:else if item.client_or_provider === 'both'} - - Ambos - - {:else} - No especificado - {/if} -
- -
- Procedencia - - {isForeign(item) ? "Extranjero" : "Nacional"} - -
- -
- Estado - {#if item.is_active === true} - - Activo - - {:else} - - Inactivo - - {/if} -
-
- - -
- - - {#if item.address} -
-

Dirección

- -
- {#if item.address.streets} -
- Calles - {item.address.streets} -
- {/if} - -
- {#if item.address.neighborhood} -
- Colonia - {item.address.neighborhood} -
- {/if} - - {#if item.address.postal_code} -
- Código Postal - {item.address.postal_code} -
- {/if} -
- -
- {#if item.address.municipality} -
- Municipio - {item.address.municipality} -
- {/if} - - {#if item.address.city} -
- Ciudad - {item.address.city} -
- {/if} - - {#if item.address.state} -
- Estado - {item.address.state} -
- {/if} -
- - {#if item.address.country} -
- País - {item.address.country} -
- {/if} - - {#if item.address.email || item.address.phone || item.address.contact} -
- {#if item.address.email} -
- Email - {item.address.email} -
- {/if} - {#if item.address.phone} -
- Teléfono - {item.address.phone} -
- {/if} - {#if item.address.contact} -
- Contacto - {item.address.contact} -
- {/if} -
- {/if} -
- - -
- {/if} - - - {#if item.programs} -
-

Programas

- - {#if item.programs.program} -
- Programa - {item.programs.program} -
- {/if} - - {#if item.programs.program_number || item.programs.manufacturer_id} -
- {#if item.programs.program_number} -
- Número - {item.programs.program_number} -
- {/if} - {#if item.programs.manufacturer_id} -
- Manufacturer ID - {item.programs.manufacturer_id} -
- {/if} -
- {/if} -
- {/if} -
- {/if} - - - - -
-
diff --git a/frontend/src/lib/components/dashboard/csv-upload/ConfigFooter.svelte b/frontend/src/lib/components/dashboard/csv-upload/ConfigFooter.svelte deleted file mode 100644 index bf1c468b..00000000 --- a/frontend/src/lib/components/dashboard/csv-upload/ConfigFooter.svelte +++ /dev/null @@ -1,106 +0,0 @@ - - - -
-
-
- - {csvMsg('config_prefix')}: {csvMsg(TAB_LABEL[activeTab] ?? activeTab)} -
- - {#if currentFields.length > 0} -
- {#each currentFields as field} -
- {#if field.type !== 'boolean'} - - {/if} - - {#if field.type === 'text'} - - {:else if field.type === 'boolean'} -
- - -
- {:else if field.type === 'select' && field.options} - - {:else if field.type === 'radio' && field.options} - - {#each field.options as opt} -
- - -
- {/each} -
- {/if} -
- {/each} -
- {:else} -
- {csvMsg('config_empty')} -
- {/if} -
-
diff --git a/frontend/src/lib/components/dashboard/csv-upload/CsvParamsBar.svelte b/frontend/src/lib/components/dashboard/csv-upload/CsvParamsBar.svelte deleted file mode 100644 index 8629bc97..00000000 --- a/frontend/src/lib/components/dashboard/csv-upload/CsvParamsBar.svelte +++ /dev/null @@ -1,141 +0,0 @@ - - - -
-
-
- -
-
- - {csvMsg('params_header')} -
- {#each globalCsvParams as param} -
- - -
- {/each} -
- - - {#if currentTabFields.length > 0} -
- {csvMsg('config_prefix')}: {csvMsg(TAB_LABEL[activeTab] ?? activeTab)} - {#each currentTabFields as field} -
- {#if field.type !== 'boolean'} - - {/if} - {#if field.type === 'select' && field.options} - - {:else if field.type === 'radio' && field.options} -
- {#each field.options as opt} - - {/each} -
- {:else if field.type === 'boolean'} - - {/if} -
- {/each} -
- {/if} -
-
-
diff --git a/frontend/src/lib/components/dashboard/csv-upload/CsvPendingImportsSheet.svelte b/frontend/src/lib/components/dashboard/csv-upload/CsvPendingImportsSheet.svelte deleted file mode 100644 index 1344f2cd..00000000 --- a/frontend/src/lib/components/dashboard/csv-upload/CsvPendingImportsSheet.svelte +++ /dev/null @@ -1,201 +0,0 @@ - - - - - - - - {csvMsg('pending.title')} - - {csvMsg('pending.description')} - - -
- -
-
- {#if rows.length === 0 && !refreshing} -

{csvMsg('pending.empty')}

- {:else if rows.length === 0 && refreshing} -

{csvMsg('pending.checking')}

- {:else} -
    - {#each rows as row (row.jobId)} -
  • -
    - {row.label || profileLabel(row.profile)} -
    -
    {profileLabel(row.profile)}
    - {#if row.totalRows != null || row.validRows != null} -
    - {#if row.totalRows != null} - {csvMsg('pending.total_rows')}: {row.totalRows} - {/if} - {#if row.validRows != null} - {csvMsg('pending.valid_rows')}: {row.validRows} - {/if} -
    - {/if} -
    {row.jobId}
    -
    - - -
    -
  • - {/each} -
- {/if} -
-
-
diff --git a/frontend/src/lib/components/dashboard/csv-upload/ProcessingResultModal.svelte b/frontend/src/lib/components/dashboard/csv-upload/ProcessingResultModal.svelte deleted file mode 100644 index 295bc5b7..00000000 --- a/frontend/src/lib/components/dashboard/csv-upload/ProcessingResultModal.svelte +++ /dev/null @@ -1,622 +0,0 @@ - - - - - -
-
- {#if isPending} - - {:else if isFinished && !hasErrors} - - {:else} - - {/if} -
- -
- - {#if isPending} - {csvMsg('modal.title_pending')} - {:else if isFinished} - {hasErrors ? csvMsg('modal.title_warning') : csvMsg('modal.title_success')} - {/if} - - - {#if isPending} - {csvMsg('modal.desc_pending')} - {:else if isFinished} - {csvMsg('modal.desc_done')} - {/if} - -
-
- - -
- - {#if isPending} -
- -
- {csvMsg('modal.total_rows')} - {scanResults.total_rows || 0} -
- - -
- {csvMsg('modal.valid_rows')} - {scanResults.valid_rows || 0} -
- - -
- {csvMsg('modal.errors')} - {scanResults.error_count || 0} -
-
- - {#if scanResults.message} -

- {scanResults.message} -

- {/if} - {#if scanErrorSummary.length > 0} -
- {#each scanErrorSummary as item} - - {(item as { reason?: string; count?: number }).reason ?? '—'} · {(item as { count?: number }) - .count ?? 0} - - {/each} -
- {/if} - - {#if scanResults.error_count > 0} -
- -
-

{csvMsg('modal.scan_problems_title')}

-

- {csvMsg('modal.scan_problems_body')} -

-
-
-
-
-
- {csvMsg('modal.errors_heading')} -
-
- {#if scanResults.errors && scanResults.errors.length > 0} - - {csvFmt('modal.errors_badge', { shown: scanErrorsShown, total: scanErrorsTotal })} - - {/if} - -
-
- {#if scanResults.errors && scanResults.errors.length > 0} -
- - - - - - - - - - - {#each scanResults.errors as err} - - - - - - - {/each} - -
{csvMsg('modal.th_line')}{csvMsg('modal.th_column')}{csvMsg('modal.th_message')}{csvMsg('modal.th_solution')}
{err.line}{err.col || '-'}{err.msg || '-'}{err.solution || '-'}
-
- {#if scanErrorsTruncated} -

- {csvMsg('modal.errors_truncated')} -

- {/if} - {:else} -

- {csvFmt('modal.errors_missing_detail', { count: scanResults.error_count })} -

- {/if} -
- {:else} -
- -
-

{csvMsg('modal.scan_ok_title')}

-

{csvMsg('modal.scan_ok_body')}

-
-
- {/if} - {/if} - - - {#if isFinished} -
-
-
-
- - {csvMsg('modal.inserted')} -
- {insertedCount} -
-
-
- - {csvMsg('modal.updated')} -
- {updatedCount} -
-
-
- - {csvMsg('modal.rejected')} -
- {totalSkipped} - {#if totalSkipped > 0 && commitResults.skipped_details && commitResults.skipped_details.length > 0} -

- {csvMsg('modal.rejected_hint')} -

- {/if} -
-
- -
- {#if refGaps > 0} - -
-

{csvMsg('modal.ref_gaps_title')}

-

- {csvFmt('modal.ref_gaps_body', { n: refGaps })} -

-
- {:else} - -
-

{csvMsg('modal.ref_state_title')}

-

- {refReady ? csvMsg('modal.ref_state_ok') : csvMsg('modal.ref_state_other')} -

-
- {/if} -
- - {#if commitResults.message} -

- {commitResults.message} -

- {/if} - {#if commitSkippedSummary.length > 0} -
-

- {csvMsg('modal.skipped_reasons_heading')} -

-
- {#each commitSkippedSummary as item} - - {(item as { reason?: string; count?: number }).reason ?? '—'} · {(item as { - count?: number; - }).count ?? 0} - - {/each} -
-
- {/if} - - - {#if commitResults.skipped_details && commitResults.skipped_details.length > 0} -
-
-
- {csvMsg('modal.commit_errors_heading')} -
-
- - {csvFmt('modal.rows_badge', { n: commitResults.skipped_details.length })} - - -
-
-
- - - - - - - - - - - {#each commitResults.skipped_details as detail} - - - - - - - {/each} - -
{csvMsg('modal.th_line')}{csvMsg('modal.th_reference')}{csvMsg('modal.th_reason')}{csvMsg('modal.th_solution')}
{detail.line}{detail.invoice || '-'}{detail.reason ?? '-'}{detail.solution || '-'}
-
-
- {/if} -
- {/if} -
- - -
- {#if isPending && isUploading} -
-

{csvMsg('modal.importing_records')}

-
-
-
-
- {/if} -
- {#if isPending} - - - {:else if isFinished} - - {/if} -
-
- - - - diff --git a/frontend/src/lib/components/dashboard/csv-upload/UniversalUploadModal.svelte b/frontend/src/lib/components/dashboard/csv-upload/UniversalUploadModal.svelte deleted file mode 100644 index 721cd901..00000000 --- a/frontend/src/lib/components/dashboard/csv-upload/UniversalUploadModal.svelte +++ /dev/null @@ -1,123 +0,0 @@ - - - - - - - {#if config.icon} - - {/if} - Importar {config.title} - - - Selecciona tu archivo CSV para procesar. -
- - La configuración activa del pie de página se aplicará a esta carga. - -
-
- -
- -
-
- - {#if file} - -
- {file.name} - {(file.size / 1024).toFixed(2)} KB -
- {:else} - - Arrastra tu archivo aquí o haz clic - Soporta CSV, TXT - {/if} -
-
- - - {#if Object.keys(currentSettings).length > 0} -
- Configuración Activa: -
    - {#each Object.entries(currentSettings) as [key, value]} -
  • {key}: {value}
  • - {/each} -
-
- {/if} -
- - - - - -
-
diff --git a/frontend/src/lib/components/dashboard/csv-upload/UploadLauncherGrid.svelte b/frontend/src/lib/components/dashboard/csv-upload/UploadLauncherGrid.svelte deleted file mode 100644 index 8896a5cb..00000000 --- a/frontend/src/lib/components/dashboard/csv-upload/UploadLauncherGrid.svelte +++ /dev/null @@ -1,307 +0,0 @@ - - -
- {#snippet cardTitle(itemId: string)} -
{csvMsg(`items.${itemId}`)}
- {/snippet} - - {#if groupedItems.ungrouped.length > 0} -
- {#each groupedItems.ungrouped as item} -
handleDragEnter(e, item.id, item.disabled || gridLocked)} - ondragleave={handleDragLeave} - ondragover={(e) => handleDragOver(e, item.disabled || gridLocked)} - ondrop={(e) => handleDrop(e, item)} - oncontextmenu={(e) => handleContextMenu(e, item)} - role="button" - tabindex={item.disabled || gridLocked ? -1 : 0} - onclick={() => handleClick(item.id, item.disabled || gridLocked)} - onkeydown={(e) => - !item.disabled && !gridLocked && e.key === 'Enter' && handleClick(item.id)} - > - handleFileChange(e, item)} - disabled={item.disabled || gridLocked} - /> - - - {#if item.disabled} -
- - {csvMsg('soon')} - -
- {/if} - - - {#if !item.disabled && !gridLocked && dragOverId === item.id} -
- -
- {csvMsg('drop_here')} - {:else} -
- -
- {@render cardTitle(item.id)} - {/if} -
-
-
- {/each} -
- {/if} - - {#each Object.entries(groupedItems.groups) as [groupName, groupItems]} -
-

- {csvMsg(`groups.${groupName}`)} -

-
- {#each groupItems as item} -
handleDragEnter(e, item.id, item.disabled || gridLocked)} - ondragleave={handleDragLeave} - ondragover={(e) => handleDragOver(e, item.disabled || gridLocked)} - ondrop={(e) => handleDrop(e, item)} - oncontextmenu={(e) => handleContextMenu(e, item)} - role="button" - tabindex={item.disabled || gridLocked ? -1 : 0} - onclick={() => handleClick(item.id, item.disabled || gridLocked)} - onkeydown={(e) => - !item.disabled && !gridLocked && e.key === 'Enter' && handleClick(item.id)} - > - handleFileChange(e, item)} - disabled={item.disabled || gridLocked} - /> - - - {#if item.disabled} -
- - {csvMsg('soon')} - -
- {/if} - - - {#if !item.disabled && !gridLocked && dragOverId === item.id} -
- -
- {csvMsg('drop_here')} - {:else} -
- -
- {@render cardTitle(item.id)} - {/if} -
-
-
- {/each} -
-
- {/each} -
diff --git a/frontend/src/lib/components/dashboard/customs_brokers/columns.ts b/frontend/src/lib/components/dashboard/customs_brokers/columns.ts deleted file mode 100644 index f1d21b3e..00000000 --- a/frontend/src/lib/components/dashboard/customs_brokers/columns.ts +++ /dev/null @@ -1,204 +0,0 @@ -import type { ColumnDef } from "@tanstack/table-core"; -import { m } from '$lib/i18n/messages'; -import { renderComponent, renderSnippet } from "$lib/components/ui/data-table/index.js"; -import { createRawSnippet } from "svelte"; -import DataTableActions from "./data-table-actions.svelte"; -import type { CustomsBroker } from "$lib/api/dashboard/a76/customs-brokers"; - -export type { CustomsBroker }; - -export function createColumns(onSuccess?: () => void): ColumnDef[] { - return [ - { - accessorKey: "id", - header: m['common.col_id'](), - cell: ({ row }) => { - const idSnippet = createRawSnippet<[{ id: number }]>((getId) => { - const { id } = getId(); - return { - render: () => - `${id}` - }; - }); - return renderSnippet(idSnippet, { id: row.original.id }); - } - }, - { - accessorKey: "broker_key", - header: m['common.col_key'](), - cell: ({ row }) => { - const keySnippet = createRawSnippet<[{ key: string }]>((getKey) => { - const { key } = getKey(); - return { - render: () => - `${key}` - }; - }); - return renderSnippet(keySnippet, { key: row.original.broker_key }); - } - }, - { - accessorKey: "type", - header: m['common.col_type'](), - cell: ({ row }) => { - const typeSnippet = createRawSnippet<[{ type: string | null | undefined }]>((getType) => { - const { type } = getType(); - let display = type || '-'; - if (type === 'MEX') display = 'Agente Aduanal Mexicano'; - else if (type === 'USA') display = 'Agente Aduanal Americano (Broker)'; - - return { - render: () => `
${display}
` - }; - }); - return renderSnippet(typeSnippet, { type: row.original.type }); - } - }, - { - accessorKey: "name", - header: m['common.col_full_name'](), - cell: ({ row }) => { - const nameSnippet = createRawSnippet<[{ name: string | null | undefined }]>((getName) => { - const { name } = getName(); - return { - render: () => `
${name || '-'}
` - }; - }); - return renderSnippet(nameSnippet, { name: row.original.name }); - } - }, - { - accessorKey: "address", - header: m['common.col_address'](), - cell: ({ row }) => { - const addressSnippet = createRawSnippet<[{ address: string | null | undefined }]>((getAddress) => { - const { address } = getAddress(); - return { - render: () => `
${address || '-'}
` - }; - }); - return renderSnippet(addressSnippet, { address: row.original.address }); - } - }, - { - accessorKey: "postal_code", - header: m['common.col_zip'](), - cell: ({ row }) => { - const postalSnippet = createRawSnippet<[{ postal: string | null | undefined }]>((getPostal) => { - const { postal } = getPostal(); - return { - render: () => - postal - ? `${postal}` - : `-` - }; - }); - return renderSnippet(postalSnippet, { postal: row.original.postal_code }); - } - }, - { - accessorKey: "city", - header: m['common.col_city'](), - cell: ({ row }) => { - const citySnippet = createRawSnippet<[{ city: string | null | undefined }]>((getCity) => { - const { city } = getCity(); - return { - render: () => `
${city || '-'}
` - }; - }); - return renderSnippet(citySnippet, { city: row.original.city }); - } - }, - { - accessorKey: "state", - header: m['common.col_status'](), - cell: ({ row }) => { - const stateSnippet = createRawSnippet<[{ state: string | null | undefined }]>((getState) => { - const { state } = getState(); - return { - render: () => `
${state || '-'}
` - }; - }); - return renderSnippet(stateSnippet, { state: row.original.state }); - } - }, - { - accessorKey: "phone", - header: m['common.col_phone'](), - cell: ({ row }) => { - const phoneSnippet = createRawSnippet<[{ phone: string | null | undefined }]>((getPhone) => { - const { phone } = getPhone(); - return { - render: () => `
${phone || '-'}
` - }; - }); - return renderSnippet(phoneSnippet, { phone: row.original.phone }); - } - }, - { - accessorKey: "fax", - header: m['common.col_fax'](), - cell: ({ row }) => { - const faxSnippet = createRawSnippet<[{ fax: string | null | undefined }]>((getFax) => { - const { fax } = getFax(); - return { - render: () => `
${fax || '-'}
` - }; - }); - return renderSnippet(faxSnippet, { fax: row.original.fax }); - } - }, - { - accessorKey: "license", - header: m['common.col_patent'](), - cell: ({ row }) => { - const licenseSnippet = createRawSnippet<[{ license: string | null | undefined }]>((getLicense) => { - const { license } = getLicense(); - return { - render: () => - license - ? `${license}` - : `-` - }; - }); - return renderSnippet(licenseSnippet, { license: row.original.license }); - } - }, - { - accessorKey: "tax_id", - header: m['common.col_rfc'](), - cell: ({ row }) => { - const taxIdSnippet = createRawSnippet<[{ taxId: string | null | undefined }]>((getTaxId) => { - const { taxId } = getTaxId(); - return { - render: () => `
${taxId || '-'}
` - }; - }); - return renderSnippet(taxIdSnippet, { taxId: row.original.tax_id }); - } - }, - { - accessorKey: "email", - header: m['common.col_email'](), - cell: ({ row }) => { - const emailSnippet = createRawSnippet<[{ email: string | null | undefined }]>((getEmail) => { - const { email } = getEmail(); - return { - render: () => `
${email || '-'}
` - }; - }); - return renderSnippet(emailSnippet, { email: row.original.email }); - } - }, - { - id: "actions", - header: m['common.col_actions'](), - cell: ({ row }) => { - return renderComponent(DataTableActions, { - broker: row.original, - onSuccess - }); - } - } - ]; -} diff --git a/frontend/src/lib/components/dashboard/customs_brokers/create-dialog.svelte b/frontend/src/lib/components/dashboard/customs_brokers/create-dialog.svelte deleted file mode 100644 index c7fd69d3..00000000 --- a/frontend/src/lib/components/dashboard/customs_brokers/create-dialog.svelte +++ /dev/null @@ -1,422 +0,0 @@ - - - - - - - {mode === 'create' ? 'Nuevo Agente Aduanal' : 'Editar Agente Aduanal'} - - - Ingresa los datos generales del agente. La configuración de VU y Personal se gestiona - aparte. - - - -
e.preventDefault()}> -
-
-

Identificación

-
-
- - { - const val = e.currentTarget.value.toUpperCase().replace(/[^A-Z0-9]/g, ''); - if (val.length > 5) { - brokerKeyError = true; - formData.broker_key = val.slice(0, 5); - e.currentTarget.value = formData.broker_key; - - clearTimeout(brokerKeyTimeout); - brokerKeyTimeout = setTimeout(() => { - brokerKeyError = false; - }, 3000); - } else { - brokerKeyError = false; - formData.broker_key = val; - e.currentTarget.value = formData.broker_key; - } - }} - placeholder="Ej. 550" - maxlength={6} - class={brokerKeyError ? 'border-red-500 focus-visible:ring-red-500' : ''} - disabled={mode === 'edit' || loading} - /> - {#if brokerKeyError} -

- La clave no debe superar los 5 caracteres -

- {/if} -
-
- - { - const val = e.currentTarget.value.replace(/\D/g, ''); - if (val.length > 4) { - licenseError = true; - formData.license = val.slice(0, 4); - e.currentTarget.value = formData.license; - - clearTimeout(licenseTimeout); - licenseTimeout = setTimeout(() => { - licenseError = false; - }, 3000); - } else { - licenseError = false; - formData.license = val; - } - }} - placeholder="Ej. 3421" - maxlength={5} - class={licenseError ? 'border-red-500 focus-visible:ring-red-500' : ''} - disabled={loading} - /> - {#if licenseError} -

- La patente no debe superar los 4 dígitos -

- {/if} -
-
-
-
- - -
-
- - { - formData.tax_id = e.currentTarget.value - .toUpperCase() - .replace(/[^A-Z0-9&Ñ]/g, '') - .slice(0, 13); - e.currentTarget.value = formData.tax_id; - }} - placeholder="RFC de la agencia" - maxlength={13} - disabled={loading} - /> -
-
- - { - formData.personal_id = e.currentTarget.value - .toUpperCase() - .replace(/[^A-Z0-9]/g, '') - .slice(0, 18); - e.currentTarget.value = formData.personal_id; - }} - placeholder="CURP (Opcional)" - maxlength={18} - disabled={loading} - /> -
-
- - { - formData.contact = e.currentTarget.value.replace( - /[^a-zA-Z0-9\sñÑáéíóúÁÉÍÓÚ\-\.,]/g, - '' - ); - e.currentTarget.value = formData.contact; - }} - placeholder="Persona de contacto" - disabled={loading} - /> -
-
-
- - - -
-

Contacto

-
-
- - { - formData.phone = e.currentTarget.value.replace(/[^\d\s\-\+\(\)]/g, ''); - e.currentTarget.value = formData.phone; - }} - disabled={loading} - /> -
-
- - { - formData.fax = e.currentTarget.value.replace(/[^\d\s\-\+\(\)]/g, ''); - e.currentTarget.value = formData.fax; - }} - disabled={loading} - /> -
-
- - -
-
-
- - - -
-

Dirección Fiscal

- -
- - -
- -
-
- - { - formData.postal_code = e.currentTarget.value.replace(/[^a-zA-Z0-9]/g, ''); - }} - /> -
-
- - -
-
- - -
-
-
-
- - -
-
-
-
-
- - - - - -
-
diff --git a/frontend/src/lib/components/dashboard/customs_brokers/data-table-actions.svelte b/frontend/src/lib/components/dashboard/customs_brokers/data-table-actions.svelte deleted file mode 100644 index 5e203e5b..00000000 --- a/frontend/src/lib/components/dashboard/customs_brokers/data-table-actions.svelte +++ /dev/null @@ -1,73 +0,0 @@ - - - - - {#snippet child({ props })} - - {/snippet} - - - - Acciones - Copiar clave - {#if broker.tax_id} - Copiar RFC - {/if} - - - - Ver detalles - goto(`/dashboard/customs_brokers/edit/${broker.broker_key}`)} - > - Editar - - - Eliminar - - - - - - - diff --git a/frontend/src/lib/components/dashboard/customs_brokers/data-table.svelte b/frontend/src/lib/components/dashboard/customs_brokers/data-table.svelte deleted file mode 100644 index e06daaa4..00000000 --- a/frontend/src/lib/components/dashboard/customs_brokers/data-table.svelte +++ /dev/null @@ -1,80 +0,0 @@ - - -
-
- - - {#each table.getHeaderGroups() as headerGroup (headerGroup.id)} - - {#each headerGroup.headers as header (header.id)} - - {#if !header.isPlaceholder} - - {/if} - - {/each} - - {/each} - - - - {#each table.getRowModel().rows as row (row.id)} - onRowClick?.(row.original)} - ondblclick={() => onRowDoubleClick?.(row.original)} - class="cursor-pointer transition-colors hover:bg-muted/50 {selectedId && - idField && - row.original[idField] === selectedId - ? 'bg-muted' - : ''}" - > - {#each row.getVisibleCells() as cell (cell.id)} - - - - {/each} - - {:else} - - - No hay resultados. - - - {/each} - - -
-
diff --git a/frontend/src/lib/components/dashboard/customs_brokers/delete-dialog.svelte b/frontend/src/lib/components/dashboard/customs_brokers/delete-dialog.svelte deleted file mode 100644 index 0658bd95..00000000 --- a/frontend/src/lib/components/dashboard/customs_brokers/delete-dialog.svelte +++ /dev/null @@ -1,104 +0,0 @@ - - - - - - ¿Estás seguro? - -

Esta acción no se puede deshacer. Se eliminará permanentemente este agente aduanal:

- {#if broker} -
-
- Clave: - {broker.broker_key} -
- {#if broker.name} -
- Nombre: - {broker.name} -
- {/if} - {#if broker.license} -
- Patente: - {broker.license} -
- {/if} -
- {/if} - {#if error} -
- {error} -
- {/if} -
-
- - Cancelar - - {#if loading} - - Eliminando... - {:else} - Eliminar - {/if} - - -
-
diff --git a/frontend/src/lib/components/dashboard/customs_brokers/details-dialog.svelte b/frontend/src/lib/components/dashboard/customs_brokers/details-dialog.svelte deleted file mode 100644 index 6d1a0a04..00000000 --- a/frontend/src/lib/components/dashboard/customs_brokers/details-dialog.svelte +++ /dev/null @@ -1,192 +0,0 @@ - - - - - - Detalles del Agente Aduanal - - Información completa del agente aduanal - - - - {#if broker} -
- -
-

Información Básica

- -
-
- Clave - {broker.broker_key} -
- - {#if broker.license} -
- Patente - {broker.license} -
- {/if} -
- - {#if broker.name} -
- Nombre - {broker.name} -
- {/if} - - {#if broker.type} -
- Tipo - {broker.type} -
- {/if} - - {#if broker.company} -
- Empresa - {broker.company} -
- {/if} - - -
- - -
-

Información de Contacto

- - {#if broker.contact} -
- Contacto - {broker.contact} -
- {/if} - -
- {#if broker.phone} -
- Teléfono - {broker.phone} -
- {/if} - - {#if broker.fax} -
- Fax - {broker.fax} -
- {/if} -
- - {#if broker.email} - - {/if} - - -
- - -
-

Dirección

- - {#if broker.address} -
- Dirección - {broker.address} -
- {/if} - -
- {#if broker.postal_code} -
- C.P. - {broker.postal_code} -
- {/if} - - {#if broker.city} -
- Ciudad - {broker.city} -
- {/if} - - {#if broker.state} -
- Estado - {broker.state} -
- {/if} -
- - {#if broker.country} -
- País - {broker.country} -
- {/if} - - -
- - -
-

Información Fiscal

- - {#if broker.tax_id} -
- RFC - {broker.tax_id} -
- {/if} - - {#if broker.personal_id} -
- CURP - {broker.personal_id} -
- {/if} - - {#if broker.position} -
- Posición - {broker.position} -
- {/if} -
-
- {:else} -
- No hay información disponible -
- {/if} - - - - -
-
diff --git a/frontend/src/lib/components/dashboard/customs_brokers/edit-dialog.svelte b/frontend/src/lib/components/dashboard/customs_brokers/edit-dialog.svelte deleted file mode 100644 index e78104fa..00000000 --- a/frontend/src/lib/components/dashboard/customs_brokers/edit-dialog.svelte +++ /dev/null @@ -1,485 +0,0 @@ - - - - - - Editar Agente Aduanal - - Modifica los datos del agente aduanal {broker?.broker_key}. - - - -
- {#if error} -
- {error} -
- {/if} - - -
-

Información Básica

- -
-
- - -
- -
- - -
-
- -
- - -
- -
-
- - { - const val = e.currentTarget.value.replace(/\D/g, ''); - if (val.length > 4) { - licenseError = true; - formData.license = val.slice(0, 4); - e.currentTarget.value = formData.license; - - clearTimeout(licenseTimeout); - licenseTimeout = setTimeout(() => { - licenseError = false; - }, 3000); - } else { - licenseError = false; - formData.license = val; - } - }} - placeholder="Número de patente" - maxlength={5} - class={licenseError ? 'border-red-500 focus-visible:ring-red-500' : ''} - disabled={loading} - /> - {#if licenseError} -

- La patente no debe superar los 4 dígitos -

- {/if} -
- -
- - -
-
-
- - -
-

Información de Contacto

- -
-
- - { - formData.phone = e.currentTarget.value.replace(/[^\d\s\-\+\(\)]/g, ''); - e.currentTarget.value = formData.phone; - }} - placeholder="Número telefónico" - maxlength={30} - disabled={loading} - /> -
- -
- - { - formData.fax = e.currentTarget.value.replace(/[^\d\s\-\+\(\)]/g, ''); - e.currentTarget.value = formData.fax; - }} - placeholder="Número de fax" - maxlength={30} - disabled={loading} - /> -
-
- -
- - -
- -
- - { - formData.contact = e.currentTarget.value.replace( - /[^a-zA-Z0-9\sñÑáéíóúÁÉÍÓÚ\-\.,]/g, - '' - ); - e.currentTarget.value = formData.contact; - }} - placeholder="Nombre del contacto" - maxlength={80} - disabled={loading} - /> -
-
- - -
-

Dirección

- -
- - -
- -
-
- - { - formData.postal_code = e.currentTarget.value.replace(/[^a-zA-Z0-9]/g, ''); - }} - /> -
- -
- - -
- -
- - -
-
- -
- - -
-
- - -
-

Información Fiscal

- -
-
- - { - formData.tax_id = e.currentTarget.value - .toUpperCase() - .replace(/[^A-Z0-9&Ñ]/g, '') - .slice(0, 13); - e.currentTarget.value = formData.tax_id; - }} - placeholder="RFC" - maxlength={13} - disabled={loading} - /> -
- -
- - { - formData.personal_id = e.currentTarget.value - .toUpperCase() - .replace(/[^A-Z0-9]/g, '') - .slice(0, 18); - e.currentTarget.value = formData.personal_id; - }} - placeholder="CURP" - maxlength={18} - disabled={loading} - /> -
-
- -
- - -
-
- - - - - -
-
-
diff --git a/frontend/src/lib/components/dashboard/despacho/doda/doda-alta-log-columns.ts b/frontend/src/lib/components/dashboard/despacho/doda/doda-alta-log-columns.ts deleted file mode 100644 index d887ad21..00000000 --- a/frontend/src/lib/components/dashboard/despacho/doda/doda-alta-log-columns.ts +++ /dev/null @@ -1,119 +0,0 @@ -import type { DodaAltaLog } from '$lib/api/dashboard/a76/doda-alta-log'; -import type { ColumnDef } from '@tanstack/table-core'; -import { createRawSnippet } from 'svelte'; -import { renderSnippet } from '$lib/components/ui/data-table'; - -function formatDateTime(raw?: string | null): string { - if (!raw) return '-'; - try { - return new Date(raw).toLocaleString('es-MX', { - day: '2-digit', - month: '2-digit', - year: 'numeric', - hour: '2-digit', - minute: '2-digit' - }); - } catch { - return raw; - } -} - -const STATUS_CLASSES: Record = { - success: 'bg-green-100 text-green-800 dark:bg-green-900/30 dark:text-green-400', - failed: 'bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-400', - failure: 'bg-red-100 text-red-800 dark:bg-red-900/30 dark:text-red-400', - pending: 'bg-yellow-100 text-yellow-800 dark:bg-yellow-900/30 dark:text-yellow-400', - processing: 'bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-400', - started: 'bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-400' -}; - -function statusBadge(status: string | null | undefined) { - const s = (status || '').toLowerCase(); - const cls = STATUS_CLASSES[s] || 'bg-gray-100 text-gray-700'; - return createRawSnippet(() => ({ - render: () => - `${status || '-'}` - })); -} - -export function createAltaLogColumns(): ColumnDef[] { - return [ - { - accessorKey: 'id', - header: '#', - size: 60, - cell: ({ row }) => row.original.id - }, - { - accessorKey: 'doda_id', - header: 'DODA ID', - size: 80, - cell: ({ row }) => row.original.doda_id ?? '-' - }, - { - accessorKey: 'integration_number', - header: 'No. Integración', - cell: ({ row }) => row.original.integration_number || '-' - }, - { - accessorKey: 'variant', - header: 'Tipo', - size: 70, - cell: ({ row }) => (row.original.variant || '-').toUpperCase() - }, - { - accessorKey: 'patent', - header: 'Patente', - size: 80, - cell: ({ row }) => row.original.patent || '-' - }, - { - accessorKey: 'dispatch_customs', - header: 'Aduana', - size: 80, - cell: ({ row }) => row.original.dispatch_customs || '-' - }, - { - accessorKey: 'operation_type', - header: 'Operación', - size: 90, - cell: ({ row }) => row.original.operation_type || '-' - }, - { - accessorKey: 'status', - header: 'Estatus', - size: 110, - cell: ({ row }) => renderSnippet(statusBadge(row.original.status), {}) - }, - { - accessorKey: 'task_id', - header: 'Task ID', - cell: ({ row }) => { - const id = row.original.task_id || ''; - const snippet = createRawSnippet(() => ({ - render: () => - `${id || '-'}` - })); - return renderSnippet(snippet, {}); - } - }, - { - accessorKey: 'message', - header: 'Mensaje', - cell: ({ row }) => { - const msg = row.original.message || ''; - const snippet = createRawSnippet(() => ({ - render: () => - `${msg || '-'}` - })); - return renderSnippet(snippet, {}); - } - }, - { - accessorKey: 'created_at', - header: 'Fecha', - size: 130, - cell: ({ row }) => formatDateTime(row.original.created_at) - } - ]; -} diff --git a/frontend/src/lib/components/dashboard/despacho/doda/doda-alta-log-dialog.svelte b/frontend/src/lib/components/dashboard/despacho/doda/doda-alta-log-dialog.svelte deleted file mode 100644 index 0b4ccb24..00000000 --- a/frontend/src/lib/components/dashboard/despacho/doda/doda-alta-log-dialog.svelte +++ /dev/null @@ -1,203 +0,0 @@ - - - - - - {title} - {#if item} - - Registro #{item.id} — DODA {item.doda_id ?? '-'} — {(item.variant || 'doda').toUpperCase()} - - {/if} - - -
- {#if error} -
- {error} -
- {/if} - - - {#if item} -
-
-

No. Integración

-

{item.integration_number || '-'}

-
-
-

Responsable

-

{item.responsible || '-'}

-
-
-

Patente

-

{item.patent || '-'}

-
-
-

Aduana Despacho

-

{item.dispatch_customs || '-'}

-
-
-

Tipo Operación

-

{item.operation_type || '-'}

-
-
-

Task ID

-

{item.task_id || '-'}

-
-
-

Fecha Alta

-

- {item.created_at - ? new Date(item.created_at).toLocaleString('es-MX') - : '-'} -

-
-
- {/if} - - -
-

Estado

-
-
- - (formData.status = (e.target as HTMLInputElement).value || null)} - placeholder="pending / success / failed" - disabled={loading} - /> -
-
- - (formData.message = (e.target as HTMLInputElement).value || null)} - placeholder="Mensaje del servicio externo" - disabled={loading} - /> -
- {#if item?.result_json} -
- -
{(() => {
-								try { return JSON.stringify(JSON.parse(item.result_json || '{}'), null, 2); }
-								catch { return item.result_json || ''; }
-							})()}
-
- {/if} -
-
-
- - - -
- - {#if isEdit} - - {/if} -
-
-
-
diff --git a/frontend/src/lib/components/dashboard/despacho/doda/doda-export-excel-dialog.svelte b/frontend/src/lib/components/dashboard/despacho/doda/doda-export-excel-dialog.svelte deleted file mode 100644 index ccb2e2be..00000000 --- a/frontend/src/lib/components/dashboard/despacho/doda/doda-export-excel-dialog.svelte +++ /dev/null @@ -1,184 +0,0 @@ - - - - - - {m['sidebar.doda_alta.export_excel_badge']()} — {m['sidebar.doda_alta.export_report_heading']()} - - -
- {m['sidebar.doda_alta.export_excel_badge']()} -
- -
-

- {m['sidebar.doda_alta.export_report_heading']()} -

- -
-
- - -
-
- - -
-
- -
- -
- -
- - -
- -
-
- - { - if (v === 'csv' || v === 'xls' || v === 'txt') fileFormat = v; - }} - > - - .{fileFormat} - - - .csv (coma) - .xls (tabulador, Excel) - .txt (|) - - -
-
-
- - - - -
-
diff --git a/frontend/src/lib/components/dashboard/despacho/doda/doda-progress-dialog.svelte b/frontend/src/lib/components/dashboard/despacho/doda/doda-progress-dialog.svelte deleted file mode 100644 index a7498cd4..00000000 --- a/frontend/src/lib/components/dashboard/despacho/doda/doda-progress-dialog.svelte +++ /dev/null @@ -1,224 +0,0 @@ - - - - - - {title} - {description} {variantLabel} — Task ID: {taskId} - - -
- {#if state === 'SUCCESS'} -
- -

{m['sidebar.doda_alta.progress_success']()}

-
- {#if result} -
- {#each Object.entries(result) as [key, value]} - {#if key !== 'state' && value && typeof value === 'string'} -
-
{key.replace(/_/g, ' ')}:
-
{value}
-
- {/if} - {/each} -
- {/if} - - {:else if state === 'FAILURE'} -
- -
-

{m['sidebar.doda_alta.progress_error']()}

- {#if errorMsg} -

{errorMsg}

- {/if} -
-
- - {:else} -
-
- -

{currentStep}

-
- {#if progress > 0} - -

{progress}%

- {/if} -
- {/if} - - {#if taskId} -
-

Task ID:

-

{taskId}

-
- {/if} -
- - - {#if isTerminal} - - {:else} - - {/if} - -
-
diff --git a/frontend/src/lib/components/dashboard/digitalizacion/columns.ts b/frontend/src/lib/components/dashboard/digitalizacion/columns.ts deleted file mode 100644 index 88b01139..00000000 --- a/frontend/src/lib/components/dashboard/digitalizacion/columns.ts +++ /dev/null @@ -1,149 +0,0 @@ -import type { ExpedienteArchivo } from '$lib/api/dashboard/a76/expediente-archivos'; -import { m } from '$lib/i18n/messages'; -import type { ColumnDef } from '@tanstack/table-core'; -import { createRawSnippet } from 'svelte'; -import { renderComponent, renderSnippet } from '$lib/components/ui/data-table'; -import DataTableActions from './data-table-actions.svelte'; -import EDocumentCell from './e-document-cell.svelte'; - -function formatDate(dateStr?: string | null): string { - if (!dateStr) return '-'; - const raw = String(dateStr); - const ymd = raw.includes('T') ? raw.split('T')[0] : raw; - const parts = ymd.split('-').map(Number); - if (parts.length === 3 && parts.every((part) => Number.isFinite(part))) { - const [year, month, day] = parts; - return new Date(year, month - 1, day).toLocaleDateString('es-MX', { - day: '2-digit', - month: '2-digit', - year: 'numeric' - }); - } - return new Date(raw).toLocaleDateString('es-MX', { - day: '2-digit', - month: '2-digit', - year: 'numeric' - }); -} - -export function createColumns( - onSuccess?: () => void, - onDigitalizar?: (item: ExpedienteArchivo) => void, - onAcuse?: (item: ExpedienteArchivo) => void -): ColumnDef[] { - return [ - { - id: 'select', - header: ({ table }) => { - const isAllSelected = table.getIsAllPageRowsSelected(); - const isSomeSelected = table.getIsSomePageRowsSelected(); - - const selectAllSnippet = createRawSnippet<[ - { checked: boolean; indeterminate: boolean; onchange: (event: Event) => void } - ]>((getProps) => { - const { checked, indeterminate, onchange } = getProps(); - return { - render: () => `
- -
`, - setup: (node) => { - const input = node.querySelector('input') as HTMLInputElement | null; - if (!input) return; - input.indeterminate = indeterminate; - input.addEventListener('change', onchange); - } - }; - }); - - return renderSnippet(selectAllSnippet, { - checked: isAllSelected, - indeterminate: isSomeSelected && !isAllSelected, - onchange: (event: Event) => { - table.toggleAllPageRowsSelected(!!(event.target as HTMLInputElement).checked); - } - }); - }, - cell: ({ row }) => { - const checkboxSnippet = createRawSnippet<[ - { selected: boolean; onchange: (event: Event) => void } - ]>((getProps) => { - const { selected, onchange } = getProps(); - return { - render: () => `
- -
`, - setup: (node) => { - const input = node.querySelector('input') as HTMLInputElement | null; - if (!input) return; - input.addEventListener('click', (event) => event.stopPropagation()); - input.addEventListener('change', onchange); - } - }; - }); - - return renderSnippet(checkboxSnippet, { - selected: row.getIsSelected(), - onchange: (event: Event) => { - event.stopPropagation(); - row.toggleSelected(!!(event.target as HTMLInputElement).checked); - } - }); - }, - enableSorting: false, - enableHiding: false - }, - { - accessorKey: 'id', - header: m['common.col_consecutive'](), - cell: ({ row }) => row.original.id - }, - { - accessorKey: 'tipo_documento', - header: m['common.col_document_type'](), - cell: ({ row }) => row.original.tipo_documento || '-' - }, - { - accessorKey: 'rfc_consulta', - header: m['common.col_rfc_query'](), - cell: ({ row }) => row.original.rfc_consulta || '-' - }, - { - accessorKey: 'nombre_archivo', - header: m['common.col_file'](), - cell: ({ row }) => row.original.nombre_archivo || '-' - }, - { - accessorKey: 'e_document', - header: m['common.col_edocument'](), - cell: ({ row }) => renderComponent(EDocumentCell, { item: row.original }) - }, - { - accessorKey: 'fecha_digitalizacion', - header: m['common.col_date'](), - cell: ({ row }) => formatDate(row.original.fecha_digitalizacion) - }, - { - accessorKey: 'num_operacion', - header: m['common.col_vu_operation'](), - cell: ({ row }) => row.original.num_operacion || '-' - }, - { - id: 'actions', - header: m['common.col_actions'](), - size: 88, - cell: ({ row }) => - renderComponent(DataTableActions, { - item: row.original, - onSuccess, - onDigitalizar, - onAcuse - }) - } - ]; -} diff --git a/frontend/src/lib/components/dashboard/digitalizacion/create-edit-dialog.svelte b/frontend/src/lib/components/dashboard/digitalizacion/create-edit-dialog.svelte deleted file mode 100644 index ab40bd35..00000000 --- a/frontend/src/lib/components/dashboard/digitalizacion/create-edit-dialog.svelte +++ /dev/null @@ -1,447 +0,0 @@ - - - - - - {title} - {#if isEdit} - - Modifica el documento digitalizado {item?.id}. - - {:else} - - Captura un nuevo documento digitalizado. - - {/if} - - -
{ e.preventDefault(); handleSubmit(); }} class="space-y-6"> - {#if error} -
- {error} -
- {/if} - -
-
-

Documento

-
-
- - (formData.tipo_documento = v || null)} - disabled={loading || docTypesLoading} - > - - - {docTypesLoading - ? 'Cargando tipos...' - : selectedDocType - ? `${selectedDocType.code}${selectedDocType.description ? ` — ${selectedDocType.description}` : ''}` - : 'Selecciona tipo...'} - - - - {#each docTypes as dt} - - {dt.code}{dt.description ? ` — ${dt.description}` : ''} - - {/each} - - -
- -
- - (formData.fecha_digitalizacion = (e.target as HTMLInputElement).value || null)} - disabled={loading} - /> -
- -
- - (formData.rfc_consulta = (e.target as HTMLInputElement).value.toUpperCase())} - placeholder="RFC para consulta" - maxlength={13} - disabled={loading} - /> -
- -
- - { - selectedFile = file; - formData.archivo_digitalizado_en = file.name; - formData.nombre_archivo = file.name; - }} - /> -
-
-
- - - -
-

Consulta y referencia

-
-
- - (formData.e_document = (e.target as HTMLInputElement).value)} - placeholder="E-Document" - disabled={loading} - /> -
- -
- - (formData.num_operacion = (e.target as HTMLInputElement).value)} - placeholder="Número de operación" - disabled={loading} - /> -
- -
- - onBrokerSelected(v || undefined)} - disabled={loading || brokersLoading} - > - - - {brokersLoading - ? 'Cargando agentes...' - : selectedBroker - ? `${selectedBroker.license}${selectedBroker.name ? ` — ${selectedBroker.name}` : ''}` - : 'Selecciona agente...'} - - - - {#each brokers as broker} - - {broker.license}{broker.name ? ` — ${broker.name}` : ''} - - {/each} - - -
- -
- -
- - -
-
-
-
-
- - - - - -
-
-
- - diff --git a/frontend/src/lib/components/dashboard/digitalizacion/data-table-actions.svelte b/frontend/src/lib/components/dashboard/digitalizacion/data-table-actions.svelte deleted file mode 100644 index 8729c3d9..00000000 --- a/frontend/src/lib/components/dashboard/digitalizacion/data-table-actions.svelte +++ /dev/null @@ -1,151 +0,0 @@ - - - - - {#snippet child({ props })} - - {/snippet} - - - Acciones - - - onDigitalizar?.(item)}> - - {m['sidebar.digitalizacion.action_digitalizar']()} - - - {#if item.status === 'success'} - - - {m['sidebar.digitalizacion.action_download_zip']()} - - {/if} - - {#if item.status === 'success'} - downloadArtifact('acuse', `acuse_${baseName}.pdf`)}> - - {m['sidebar.digitalizacion.action_acuse']()} - - {/if} - - {#if item.envio_xml_path} - downloadArtifact('envio-xml', `envio_${baseName}.xml`)}> - - {m['sidebar.digitalizacion.action_envio_xml']()} - - {/if} - - {#if item.respuesta_xml_path} - downloadArtifact('respuesta-xml', `respuesta_${baseName}.xml`)}> - - {m['sidebar.digitalizacion.action_respuesta_xml']()} - - {/if} - - {#if item.consulta_envio_xml_path} - downloadArtifact('consulta-envio-xml', `consulta_envio_${baseName}.xml`)}> - - {m['sidebar.digitalizacion.action_consulta_envio_xml']()} - - {/if} - - {#if item.consulta_respuesta_xml_path} - downloadArtifact('consulta-respuesta-xml', `consulta_respuesta_${baseName}.xml`)}> - - {m['sidebar.digitalizacion.action_consulta_respuesta_xml']()} - - {/if} - - - - (editOpen = true)}> - - {m['sidebar.digitalizacion.action_edit']()} - - - - - {m['sidebar.digitalizacion.action_delete']()} - - - - - diff --git a/frontend/src/lib/components/dashboard/digitalizacion/digitalizar-dialog.svelte b/frontend/src/lib/components/dashboard/digitalizacion/digitalizar-dialog.svelte deleted file mode 100644 index 33ec0215..00000000 --- a/frontend/src/lib/components/dashboard/digitalizacion/digitalizar-dialog.svelte +++ /dev/null @@ -1,176 +0,0 @@ - - - - - - {m['sidebar.digitalizacion.digitalizar_title']()} - {m['sidebar.digitalizacion.digitalizar_subtitle']()} - - -
{ e.preventDefault(); handleSubmit(); }} class="space-y-6"> - {#if error} -
- {error} -
- {/if} - -
- -
- - -
- - -
- - -
- - -
- - - {#if nombreArchivo} -

{nombreArchivo}

- {/if} -
-
- - - - - -
-
-
diff --git a/frontend/src/lib/components/dashboard/digitalizacion/e-document-cell.svelte b/frontend/src/lib/components/dashboard/digitalizacion/e-document-cell.svelte deleted file mode 100644 index 34e5c739..00000000 --- a/frontend/src/lib/components/dashboard/digitalizacion/e-document-cell.svelte +++ /dev/null @@ -1,19 +0,0 @@ - - - - {#if item.e_document} - - {item.e_document} - {:else} - - - {/if} - diff --git a/frontend/src/lib/components/dashboard/digitalizacion/progress-dialog.svelte b/frontend/src/lib/components/dashboard/digitalizacion/progress-dialog.svelte deleted file mode 100644 index f05115d0..00000000 --- a/frontend/src/lib/components/dashboard/digitalizacion/progress-dialog.svelte +++ /dev/null @@ -1,304 +0,0 @@ - - - - - - {m['sidebar.digitalizacion.progress_title']()} - {#if nombreArchivo} - {nombreArchivo} - {/if} - - -
- {#if state === 'SUCCESS'} - -
- -

{m['sidebar.digitalizacion.progress_success']()}

-
- {#if result} -
- {#if result.e_document} -
-
E-Document:
-
{result.e_document}
-
- {/if} - {#if result.numero_operacion} -
-
Núm. Operación:
-
{result.numero_operacion}
-
- {/if} -
- {/if} - {#if result?.acuese_digitalizacion_pdf_base64 || (recordId != null && companyId != null)} - - {/if} - - {:else if state === 'FAILURE'} - -
- -
-

{errorMsg}

- {#if errorDetail} - {#if errorDetail.codigo} -

Código: {errorDetail.codigo}

- {/if} - {#if errorDetail.paso} -

Paso: {errorDetail.paso}

- {/if} - {#if errorDetail.sugerencias?.length} -
    - {#each errorDetail.sugerencias as s} -
  • {s}
  • - {/each} -
- {/if} - {/if} -
-
- - {:else} - -
-
- -

{currentStep}

-
- -

{progress}%

-
- {/if} - - {#if taskId || externalTaskId || requestId} -
-

Task App:

-

{taskId}

- {#if externalTaskId} -

Task API:

-

{externalTaskId}

- {/if} - {#if requestId} -

Request ID:

-

{requestId}

- {/if} -
- {/if} -
- - - {#if isTerminal} - - {:else} - - {/if} - -
-
diff --git a/frontend/src/lib/components/dashboard/exchange_rate/columns.ts b/frontend/src/lib/components/dashboard/exchange_rate/columns.ts deleted file mode 100644 index a2f89c0b..00000000 --- a/frontend/src/lib/components/dashboard/exchange_rate/columns.ts +++ /dev/null @@ -1,52 +0,0 @@ -import type { ColumnDef } from '@tanstack/table-core'; -import { m } from '$lib/i18n/messages'; -import type { ExchangeRate } from '$lib/api/dashboard/a76/general_catalogs/exchange-rate'; -import { renderComponent } from '$lib/components/ui/data-table'; -import DataTableActions from './data-table-actions.svelte'; - -export function createColumns( - onSuccess?: () => void, - { canEdit = true, canDelete = true }: { canEdit?: boolean; canDelete?: boolean } = {} -): ColumnDef[] { - return [ - { - accessorKey: 'date', - header: m['common.col_date'](), - cell: ({ row }) => { - const dateStr = row.original.date; - if (!dateStr) return 'N/A'; - const date = new Date(dateStr); - return date.toLocaleDateString('es-MX'); - } - }, - { - accessorKey: 'value', - header: m['common.col_value'](), - cell: ({ row }) => { - const value = row.original.value; - if (value === null || value === undefined) return 'N/A'; - return Number(value).toFixed(6); - } - }, - { - accessorKey: 'local_currency', - header: m['common.col_local_currency'](), - cell: ({ row }) => row.original.local_currency ?? 'N/A' - }, - { - accessorKey: 'foreign_currency', - header: m['common.col_foreign_currency'](), - cell: ({ row }) => row.original.foreign_currency ?? 'N/A' - }, - { - id: 'actions', - header: m['common.col_actions'](), - cell: ({ row }) => { - return renderComponent(DataTableActions, { - item: row.original, - onSuccess - }); - } - } - ]; -} \ No newline at end of file diff --git a/frontend/src/lib/components/dashboard/exchange_rate/create-edit-dialog.svelte b/frontend/src/lib/components/dashboard/exchange_rate/create-edit-dialog.svelte deleted file mode 100644 index 3ab0e7c7..00000000 --- a/frontend/src/lib/components/dashboard/exchange_rate/create-edit-dialog.svelte +++ /dev/null @@ -1,335 +0,0 @@ - - - - - - - e.preventDefault()} - > -
- {#if isMissingRateContext} -
-
- -
-
-

- {title} -

-

- {m.exchange_rate_required_description()} -

-
-
- {:else} - - - - {title} - - - {/if} - -
{ - e.preventDefault(); - handleSubmit(); - }} - class="space-y-6 p-6 pt-4" - > - {#if error} -
- - {error} -
- {/if} - -
-
- -
- - {#if isMissingRateContext} -
- {m.exchange_rate_required_badge()} -
- {/if} -
-
- -
- -
-
- $ -
- -
-
- -

{m.exchange_rate_example_suffix()} 24.1234

-
-
-
- - - - - -
-
-
-
-
- - - - - {m.exchange_rate_confirm_title()} - - {#if isEdit} - {m.exchange_rate_confirm_description_update({ value: String(formData.value), date: formData.date })} - {:else} - {m.exchange_rate_confirm_description_create({ value: String(formData.value), date: formData.date })} - {/if} - - - - (showConfirmation = false)}>{m.exchange_rate_cancel()} - {m.exchange_rate_confirm_action()} - - - diff --git a/frontend/src/lib/components/dashboard/exchange_rate/data-table-actions.svelte b/frontend/src/lib/components/dashboard/exchange_rate/data-table-actions.svelte deleted file mode 100644 index f0e12284..00000000 --- a/frontend/src/lib/components/dashboard/exchange_rate/data-table-actions.svelte +++ /dev/null @@ -1,105 +0,0 @@ - - - - - {#snippet child({ props })} - - {/snippet} - - - Acciones - - {#if canEdit} - - - Editar - - - {/if} - {#if canDelete} - - {#if loading} - - {:else} - - {/if} - Eliminar - - {/if} - - - - diff --git a/frontend/src/lib/components/dashboard/exchange_rate/data-table.svelte b/frontend/src/lib/components/dashboard/exchange_rate/data-table.svelte deleted file mode 100644 index 72fb9bf3..00000000 --- a/frontend/src/lib/components/dashboard/exchange_rate/data-table.svelte +++ /dev/null @@ -1,106 +0,0 @@ - - -
- - - {#each table.getHeaderGroups() as headerGroup (headerGroup.id)} - - {#each headerGroup.headers as header (header.id)} - - {#if !header.isPlaceholder} - - {/if} - - {/each} - - {/each} - - - {#each table.getRowModel().rows as row (row.id)} - - {#each row.getVisibleCells() as cell (cell.id)} - - - - {/each} - - {:else} - - - No hay resultados. - - - {/each} - - -
- -
-
- Total: {totalItems} registros -
-
- - -
-
diff --git a/frontend/src/lib/components/dashboard/exchange_rate/exchange-rate-guard.svelte b/frontend/src/lib/components/dashboard/exchange_rate/exchange-rate-guard.svelte deleted file mode 100644 index 33164161..00000000 --- a/frontend/src/lib/components/dashboard/exchange_rate/exchange-rate-guard.svelte +++ /dev/null @@ -1,75 +0,0 @@ - - -{#if browser} - -{/if} diff --git a/frontend/src/lib/components/dashboard/export/manifest/columns.ts b/frontend/src/lib/components/dashboard/export/manifest/columns.ts deleted file mode 100644 index dc519ee6..00000000 --- a/frontend/src/lib/components/dashboard/export/manifest/columns.ts +++ /dev/null @@ -1,101 +0,0 @@ -import { renderSnippet } from "$lib/components/ui/data-table/index.js"; -import { m } from '$lib/i18n/messages'; -import { createRawSnippet } from "svelte"; -import type { ColumnDef } from "@tanstack/table-core"; -import type { Manifest } from "$lib/api/dashboard/a76/manifests"; - -export function createColumns(): ColumnDef[] { - return [ - { - accessorKey: "manifest_number", - header: m['common.col_manifest'](), - cell: ({ row }) => { - const manifestSnippet = createRawSnippet<[{ value: string | null }]>((getVal) => { - const { value } = getVal(); - return { - render: () => `${value || '-'}` - }; - }); - return renderSnippet(manifestSnippet, { value: row.original.manifest_number ?? null }); - } - }, - { - accessorKey: "carrier_code", - header: m['common.col_transporter'](), - cell: ({ row }) => { - const carrierSnippet = createRawSnippet<[{ value: string | null }]>((getVal) => { - const { value } = getVal(); - return { - render: () => `
${value || '-'}
` - }; - }); - return renderSnippet(carrierSnippet, { value: row.original.carrier_code ?? null }); - } - }, - { - accessorKey: "person_in_charge", - header: m['common.col_person_in_charge'](), - cell: ({ row }) => { - return row.original.person_in_charge || "-"; - } - }, - { - accessorKey: "foreign_exit_port", - header: m['common.col_mx_exit_port'](), - cell: ({ row }) => { - return row.original.foreign_exit_port || "-"; - } - }, - { - accessorKey: "destination_port", - header: m['common.col_dest_port'](), - cell: ({ row }) => { - return row.original.destination_port || "-"; - } - }, - { - accessorKey: "entry_port", - header: m['common.col_arrival_port'](), - cell: ({ row }) => { - return row.original.entry_port || "-"; - } - }, - { - accessorKey: "entry_date", - header: m['common.col_entry_date'](), - cell: ({ row }) => { - const dateSnippet = createRawSnippet<[{ value: number | null }]>((getVal) => { - const { value } = getVal(); - let formattedDate = '-'; - if (value) { - const dateStr = value.toString(); - if (dateStr.length === 8) { - formattedDate = `${dateStr.substring(6, 8)}/${dateStr.substring(4, 6)}/${dateStr.substring(0, 4)}`; - } - } - return { - render: () => `
${formattedDate}
` - }; - }); - return renderSnippet(dateSnippet, { value: row.original.entry_date ?? null }); - } - }, - { - accessorKey: "status", - header: m['common.col_status'](), - cell: ({ row }) => { - const statusSnippet = createRawSnippet<[{ status: string | null }]>((getS) => { - const { status } = getS(); - let colorClass = "bg-muted text-muted-foreground"; - if (status === 'RECIBIDO') colorClass = "bg-green-100 text-green-700 dark:bg-green-900/30 dark:text-green-400"; - if (status === 'PENDIENTE') colorClass = "bg-yellow-100 text-yellow-700 dark:bg-yellow-900/30 dark:text-yellow-400"; - - return { - render: () => `${status || 'S.E.'}` - }; - }); - return renderSnippet(statusSnippet, { status: row.original.status ?? null }); - } - } - ]; -} diff --git a/frontend/src/lib/components/dashboard/export/manifest/create-dialog.svelte b/frontend/src/lib/components/dashboard/export/manifest/create-dialog.svelte deleted file mode 100644 index 92e47893..00000000 --- a/frontend/src/lib/components/dashboard/export/manifest/create-dialog.svelte +++ /dev/null @@ -1,171 +0,0 @@ - - - - - - Nuevo Manifiesto - - Ingresa los datos generales del nuevo manifiesto de exportación. - - - -
-
-

- Información General -

-
-
- - -
-
- - -
-
-
-
- - -
-
- - -
-
-
- - - -
-

Logística

-
-
- - -
-
- - -
-
- - -
-
-
- - - -
- - - -
- -
- - - -
- -
diff --git a/frontend/src/lib/components/dashboard/invoices/edit/items/fa/item-sheet-fa.svelte b/frontend/src/lib/components/dashboard/invoices/edit/items/fa/item-sheet-fa.svelte deleted file mode 100644 index c9b5ab4c..00000000 --- a/frontend/src/lib/components/dashboard/invoices/edit/items/fa/item-sheet-fa.svelte +++ /dev/null @@ -1,1055 +0,0 @@ - - - - - -
-
-
-
- -
-
- - {isEditMode ? 'Editar Partida' : 'Nueva Partida - Activo Fijo'} - -

- Factura: {invoice?.invoice_number || m['invoice_item_fa.item_sheet.not_available_short']()} -

-
-
- -
-
- -
{ - const path = (e.target as HTMLElement).getAttribute('data-item-validation-path'); - if (path) onValidationFieldChange?.(path); - }} - onchange={(e) => { - const path = (e.target as HTMLElement).getAttribute('data-item-validation-path'); - if (path) onValidationFieldChange?.(path); - }} - > -
- - {#if editingItem} - {#if showRepairBlock} - -
-
- -

Los campos marcados con * son obligatorios.

- { - editingItem.fa_data = editingItem.fa_data || {}; - editingItem.fa_data.discharge = v === 'si'; - }} - class="flex gap-4" - > -
- - -
-
- - -
-
-
- -
-
- -
- (showExportInvoiceModal = true)} - tabindex={0} - onkeydown={(event) => - openOnEnterOrSpace(event, () => (showExportInvoiceModal = true))} - /> - -
-
-
- -
- selectedExportInvoiceId && !loadingExportLines && (showExportLinePicker = true)} - tabindex={!selectedExportInvoiceId || loadingExportLines ? -1 : 0} - onkeydown={(event) => - openOnEnterOrSpace( - event, - () => - selectedExportInvoiceId && - !loadingExportLines && - (showExportLinePicker = true) - )} - /> - -
-
-
- - { - editingItem.fa_data = editingItem.fa_data || {}; - editingItem.fa_data.search_type = v ?? 'Factura'; - }} - > - - - {editingItem.fa_data?.search_type || m['invoice_item_fa.repair.search_placeholder']()} - - - - Factura - NumParte - Clase - - -
-
-
- {:else if showLinkToImportBlock} -
-
- - { - editingItem.fa_data = editingItem.fa_data || {}; - editingItem.fa_data.discharge = v === 'si'; - }} - class="flex gap-4" - > -
- - -
-
- - -
-
-
- -
-
- - { - editingItem.fa_data = editingItem.fa_data || {}; - editingItem.fa_data.movement_type_import = v || 'TEM'; - selectedImportInvoiceId = null; - importInvoiceLines = []; - }} - > - - {editingItem.fa_data?.movement_type_import === 'DEF' ? 'DEF' : 'TEM'} - - - {m['invoice_item_fa.repair.temporal']()} - {m['invoice_item_fa.repair.definitive']()} - - -
-
- -
- (showImportInvoiceModal = true)} - tabindex={0} - onkeydown={(event) => - openOnEnterOrSpace(event, () => (showImportInvoiceModal = true))} - /> - -
-
-
- -
- selectedImportInvoiceId && !loadingImportLines && (showImportLinePicker = true)} - tabindex={!selectedImportInvoiceId || loadingImportLines ? -1 : 0} - onkeydown={(event) => - openOnEnterOrSpace( - event, - () => - selectedImportInvoiceId && - !loadingImportLines && - (showImportLinePicker = true) - )} - /> - -
-
-
- - { - editingItem.fa_data = editingItem.fa_data || {}; - editingItem.fa_data.search_type = v ?? undefined; - }} - > - - - {editingItem.fa_data?.search_type || m['invoice_item_fa.repair.search_placeholder']()} - - - - Factura - NumParte - - -
-
-
- {/if} - -
-
-
-
-

{m['invoice_item_fa.main_data.legend']()}

-
-
- {#if editingItem.quantity && editingItem.financial && editingItem.customs} - - {/if} - -
-
-
- -
-
-
-

{m['invoice_item_fa.configuration']?.() || 'Configuración'}

-
-
- {#if editingItem.description} - - {/if} -
-
-
-
- - - - {#each visibleTabs as tab} - - {tab.label} - - {/each} - - -
- -
- {#if editingItem.description && editingItem.customs && editingItem.quantity} - - {/if} - {#if editingItem.financial && editingItem.quantity} - - {/if} - - -
-
- - - {#if editingItem.description} - - {/if} - - - - {#if editingItem.description && editingItem.series} - - {/if} - - - {#if visibility.showLabelingTab} - - - - {/if} - - {#if visibility.showIdentifiersTab} - - - - {/if} -
-
- {:else} -
- -

{m['invoice_item_fa.repair.loading_item_data']()}

-
- {/if} -
-
- -
- {#if saveError?.length} -
- -
- {#each saveError as row} -

- {#if row.field}{row.field}: {/if}{row.message} -

- {/each} -
- -
- {/if} -
- - - {#if !isReadOnly} - - {/if} -
-
- -
-
- - - - - - - - - {m['invoice_item_fa.repair.select_line_title']()} - -
- {#each exportInvoiceLines as lineItem} - {@const num = lineItem.line_number ?? lineItem.id} - - {/each} -
-
-
- - - - - -
-
- -
- {m['invoice_item_fa.repair.import_title']()} -
- - {m['invoice_item_fa.repair.import_description']()} - -
- -
- {#if loadingImportLines} -
- -

{m['invoice_item_fa.repair.loading_invoice_items']()}

-
- {:else if importInvoiceLines.every(l => !l.has_balance)} -
- -

{m['invoice_item_fa.repair.no_balance']()}

-

{m['invoice_item_fa.repair.no_balance_description']()}

-
- {:else} -
- {#each importInvoiceLines as lineItem} - {#if lineItem.has_balance} - - {/if} - {/each} -
- {/if} -
- - -

- Mostrando {importInvoiceLines.filter(l => l.has_balance).length} partidas con saldo -

- -
-
-
\ No newline at end of file diff --git a/frontend/src/lib/components/dashboard/invoices/edit/items/fa/location-selector-dialog.svelte b/frontend/src/lib/components/dashboard/invoices/edit/items/fa/location-selector-dialog.svelte deleted file mode 100644 index 60e26c46..00000000 --- a/frontend/src/lib/components/dashboard/invoices/edit/items/fa/location-selector-dialog.svelte +++ /dev/null @@ -1,324 +0,0 @@ - - - - - - {m.invoice_selectors_location_title()} - - - {#if showRegisterForm} - -
{ - e.preventDefault(); - handleRegisterSubmit(); - }} - > -
- {#if formError} -
- {formError} -
- {/if} -
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
- -
- -
-
-
-
-
- - -
-
- {:else} - -
-
- - -
- -
- -
- {#if loading} -
- -
- {:else if error} -
-

{error}

-
- {:else} -
- - - - - - - - - {#each filtered as loc} - handleSelect(loc)} - onkeydown={(event) => handleRowKeydown(event, loc)} - > - - - - {/each} - {#if filtered.length === 0} - - - - {/if} - -
{m.invoice_selectors_location_column_key()}{m.invoice_selectors_location_column_location()}
{loc.clave_localizacion ?? '—'}{loc.localizacion ?? '—'}
- {m.invoice_selectors_location_no_results()} -
-
- {/if} -
- -
- {#if onClear} - - {/if} - -
- {/if} -
-
diff --git a/frontend/src/lib/components/dashboard/invoices/edit/items/fa/main-data.svelte b/frontend/src/lib/components/dashboard/invoices/edit/items/fa/main-data.svelte deleted file mode 100644 index 1636f9b2..00000000 --- a/frontend/src/lib/components/dashboard/invoices/edit/items/fa/main-data.svelte +++ /dev/null @@ -1,516 +0,0 @@ - - - - - - - -
- {m['invoice_item_fa.main_data.legend']()} -

- Los campos marcados con * son obligatorios. -

- -
- -
- -
- !disabled && !isDischargeActive && (showClassDialog = true)} - data-item-validation-path="class_id" - /> - -
- {#if (lineItem as any).class_description} -

{(lineItem as any).class_description}

- {/if} -
- - -
- - recalculateFinancials('quantity')} - class="h-8 text-xs text-right" - data-item-validation-path="quantity.quantity" - /> - - {#if isDischargeActive && lineItem.fa_data?.source_balance !== undefined && Number(quantities.quantity) > Number(lineItem.fa_data.source_balance)} -

- ⚠️ Excede saldo disponible ({lineItem.fa_data.source_balance}) -

- {/if} -
- -
- -
- !disabled && !isDischargeActive && (showUnitDialog = true)} - data-item-validation-path="unit_of_measure" - /> - - -
-
- - -
- -
- recalculateFinancials('unit_cost')} - class="h-8 text-xs text-right flex-1" - data-item-validation-path="financial.unit_cost_capture" - /> - - {invoice?.financials?.currency_type || 'USD'} -
-
- -
- -
- recalculateFinancials('total')} - class="h-8 text-xs text-right flex-1" - /> - - {invoice?.financials?.currency_type || 'USD'} -
-
- -
- -
- !disabled && (showFractionDialog = true)} - data-item-validation-path="customs.fraction" - /> - - -
-
- - -
- -
- !disabled && (showCountryDialog = true)} - data-item-validation-path="customs.origin_country" - /> - - -
-
- -
-
- - - -
- - -
- Advalorem: - - {customs.rate || '0'} - -
-
-
-
diff --git a/frontend/src/lib/components/dashboard/invoices/edit/items/fa/package-dialog.svelte b/frontend/src/lib/components/dashboard/invoices/edit/items/fa/package-dialog.svelte deleted file mode 100644 index 28ec3b9f..00000000 --- a/frontend/src/lib/components/dashboard/invoices/edit/items/fa/package-dialog.svelte +++ /dev/null @@ -1,178 +0,0 @@ - - - - - - CATALOGO DE BULTOS - - -
-
- - -
-
- -
- {#if loading} -
- -
- {:else if error} -
-

{error}

-
- {:else} -
- - - - - - - - - - - - {#each filteredPackages as pkg, i} - handleSelect(pkg)} - onkeydown={(event) => handleRowKeydown(event, pkg)} - > - - - - - - - {/each} - {#if filteredPackages.length === 0} - - - - {/if} - -
ClaveDescripción EspañolDescripción InglésPeso UnitarioCódigo ACE
{pkg.key || ''}{pkg.description_es || ''}{pkg.description_en || ''}{pkg.weight_unit || ''}{pkg.code_ace || ''}
- No se encontraron resultados -
-
- {/if} -
- -
- {#if onClear} - - {/if} - -
-
-
diff --git a/frontend/src/lib/components/dashboard/invoices/edit/items/fa/packages-section.svelte b/frontend/src/lib/components/dashboard/invoices/edit/items/fa/packages-section.svelte deleted file mode 100644 index b07afea8..00000000 --- a/frontend/src/lib/components/dashboard/invoices/edit/items/fa/packages-section.svelte +++ /dev/null @@ -1,278 +0,0 @@ - - -
- {m['invoice_item_fa.packages.legend']()} -

- Los campos marcados con * son obligatorios. -

- -
-
- - -
- -
- -
- !disabled && (packageDialogOpen = true)} - /> - - - -
-
-
-
-
- -
-
- -
-
- -
-
- - -
-
{m['invoice_item_fa.packages.weights']()}
-
-
- - -
- -
- - -
- -
- - {weightUnitLabel} -
-
-
- -
-
- - -
- -
- - -
- -
- -
-
- -
- !disabled && (americanFractionDialogOpen = true)} - data-item-validation-path="customs.american_fraction" - /> - -
-
- -
- - Advalorem: {customs.advalorem_american || '0.00'} -
-
- -
-
- - -
- -
- - -
- -
- - -
- -
-
- - - diff --git a/frontend/src/lib/components/dashboard/invoices/edit/items/fa/part-number-dialog.svelte b/frontend/src/lib/components/dashboard/invoices/edit/items/fa/part-number-dialog.svelte deleted file mode 100644 index a33a6a3d..00000000 --- a/frontend/src/lib/components/dashboard/invoices/edit/items/fa/part-number-dialog.svelte +++ /dev/null @@ -1,243 +0,0 @@ - - - - - -
-
- -
- Números de Parte -
- - Busca y selecciona un número de parte del inventario maestro. - -
- -
-
- - - {#if isSearching} -
- -
- {/if} -
-
- -
-
- - - - Número de Parte - Descripción (ES) - Descripción (EN) - Clase - - - - - {#each parts as part (part.id)} - handleSelect(part)} - > - - {part.part_number} - - -
- {part.description_spanish || '-'} -
-
- -
- {part.description_english || '-'} -
-
- - - {part.part_class || '-'} - - - -
- -
-
-
- {:else} - {#if !isSearching} - - -
-
- -
-

No hay resultados para esta búsqueda

-

Verifica el número de parte o la descripción

-
-
-
- {/if} - {/each} - - - {#if hasMore} - - -
- {#if isLoadingMore} -
- - Cargando más números de parte... -
- {/if} -
-
-
- {/if} -
-
-
-
- - -
- - Mostrando {parts.length} de {totalItems} registros -
-
- -
-
-
-
diff --git a/frontend/src/lib/components/dashboard/invoices/edit/items/fa/payment-method-dialog.svelte b/frontend/src/lib/components/dashboard/invoices/edit/items/fa/payment-method-dialog.svelte deleted file mode 100644 index 9858bd80..00000000 --- a/frontend/src/lib/components/dashboard/invoices/edit/items/fa/payment-method-dialog.svelte +++ /dev/null @@ -1,166 +0,0 @@ - - - - - - CATALOGO DE FORMAS DE PAGO - - -
-
- - -
-
- -
- {#if loading} -
- -
- {:else if error} -
-

{error}

-
- {:else} -
- - - - - - - - - {#each filteredMethods as method, i} - handleSelect(method)} - onkeydown={(event) => handleRowKeydown(event, method)} - > - - - - {/each} - {#if filteredMethods.length === 0} - - - - {/if} - -
ClaveDescripción
{method.key || ''}{method.description || ''}
- No se encontraron resultados -
-
- {/if} -
- -
- {#if onClear} - - {/if} - -
-
-
diff --git a/frontend/src/lib/components/dashboard/invoices/edit/items/fa/state-dialog.svelte b/frontend/src/lib/components/dashboard/invoices/edit/items/fa/state-dialog.svelte deleted file mode 100644 index 59019343..00000000 --- a/frontend/src/lib/components/dashboard/invoices/edit/items/fa/state-dialog.svelte +++ /dev/null @@ -1,170 +0,0 @@ - - - - - - CATALOGO DE ESTADOS - - -
-
- - -
-
- -
- {#if loading} -
- -
- {:else if error} -
-

{error}

-
- {:else} -
- - - - - - - - - - - {#each filteredItems as item} - handleSelect(item)} - onkeydown={(event) => handleRowKeydown(event, item)} - > - - - - - - {/each} - {#if filteredItems.length === 0} - - - - {/if} - -
Clave M3Clave MexClave AmeDescripción
{item.m3_key || ''}{item.mex_key || ''}{item.ame_key || ''}{item.description || ''}
- No se encontraron resultados -
-
- {/if} -
- -
- {#if onClear} - - {/if} - -
-
-
diff --git a/frontend/src/lib/components/dashboard/invoices/edit/items/fa/summary-section.svelte b/frontend/src/lib/components/dashboard/invoices/edit/items/fa/summary-section.svelte deleted file mode 100644 index 743ec718..00000000 --- a/frontend/src/lib/components/dashboard/invoices/edit/items/fa/summary-section.svelte +++ /dev/null @@ -1,126 +0,0 @@ - - -
-
- {m['invoice_item_fa.summary.general_data']()} - -
{m['invoice_item_fa.summary.return_quantity_subitems']()}
-
-
{m['invoice_item_fa.summary.temporary']()}: {formatNumber(quantities.quantity_temp_export)}
-
{m['invoice_item_fa.summary.replacement_or_change']()}: 0.00000000
-
{m['invoice_item_fa.summary.definitive']()}: 0.00000000
-
{m['invoice_item_fa.summary.returned_values']()}: {formatNumber(financials.value_returned_usd)}
-
{m['invoice_item_fa.summary.returned_values']()}: {formatNumber(financials.value_returned_mxn)}
-
-
-
{m['invoice_item_fa.summary.weights_kilos']()}
-
{m['invoice_item_fa.summary.weights_pounds']()}
-
{m['invoice_item_fa.summary.net']()}: {formatNumber(quantities.net_weight)}
-
0.00000000
-
{m['invoice_item_fa.summary.gross']()}: {formatNumber(quantities.gross_weight)}
-
0.00000000
-
-
- - -
- {m['invoice_item_fa.summary.costs_values']()} - -
-
{m['invoice_item_fa.summary.dollars']()}
-
{m['invoice_item_fa.summary.pesos']()}
-
{m['invoice_item_fa.summary.cost']()}: {formatNumber(financials.unit_cost_usd)}
-
{formatNumber(financials.unit_cost_mxn)}
-
{m['invoice_item_fa.summary.value']()}: {formatNumber(financials.value_usd)}
-
{formatNumber(financials.value_mxn)}
-
{m['invoice_item_fa.summary.customs_value']()}: {formatNumber(financials.customs_value_usd)}
-
{m['invoice_item_fa.summary.customs_value']()}: {formatNumber(financials.customs_value_mxn)}
-
{m['invoice_item_fa.summary.capture_cost']()}: {formatNumber(financials.unit_cost_capture)} {invoiceCurrency}
-
-
{m['invoice_item_fa.summary.capture_value']()}: {formatNumber(financials.value_mc)} {invoiceCurrency}
-
-
-
diff --git a/frontend/src/lib/components/dashboard/invoices/edit/items/fa/tab-continuation.svelte b/frontend/src/lib/components/dashboard/invoices/edit/items/fa/tab-continuation.svelte deleted file mode 100644 index 3c3d7c75..00000000 --- a/frontend/src/lib/components/dashboard/invoices/edit/items/fa/tab-continuation.svelte +++ /dev/null @@ -1,362 +0,0 @@ - - -
- -
- {#if visibility.showContinuationTaxPayment} - -
-
- {m['invoice_item_fa.continuation.tax_paid']()} - - -
- - -
-
- - -
-
-
-
-
- -
- - -
- -
- -
-
- {/if} - - {#if visibility.showContinuationIgi} - -
-
- - -
-
- - -
- -
- {/if} - - {#if visibility.showFdaFcc} -
-
FDA / FCC
-
-
- - -
-
- - -
-
- - -
- -
-
- {/if} - - {#if visibility.showCertificateOfOrigin} - -
-
- {m['invoice_item_fa.continuation.has_certificate_of_origin']()} - - -
- - -
-
- - -
-
-
-
- - - - -
- -
- {/if} - - {#if visibility.showExportValuationFields || visibility.showValuationFields} - -
-
Valoración
-
-
- - -
-
- - -
-
- - -
- -
-
- {/if} - - {#if visibility.showContinuationLocation} - -
-
- -
- - -
- -
- -
- {/if} - - {#if visibility.showContinuationMilitary} - -
- - -
- - {/if} - - {#if visibility.showContinuationOwnOmitAnnex && lineItem.fa_data} -
-
- - -
-
- - -
- -
- {/if} - - {#if visibility.showContinuationLotEntry} - -
-
- - -
-
- - -
- -
- {/if} -
- - -
- {#if visibility.showEighthRule} - -
-
- -
- -
-
-
-
- - -
-
- - -
-
- -
- {/if} - - {#if visibility.showContinuationConsiderA31} - -
- - -
- - {/if} - - {#if visibility.showContinuationExtraDescription} - -
- - -
- - {/if} -
-
- \ No newline at end of file diff --git a/frontend/src/lib/components/dashboard/invoices/edit/items/fa/tab-identifiers.svelte b/frontend/src/lib/components/dashboard/invoices/edit/items/fa/tab-identifiers.svelte deleted file mode 100644 index aac60c0d..00000000 --- a/frontend/src/lib/components/dashboard/invoices/edit/items/fa/tab-identifiers.svelte +++ /dev/null @@ -1,395 +0,0 @@ - - -
- {#if visibility.showMexicanIdEnhanced} - -
- - Activos / Num. Etiquetado (MEX) - - -
- -
- - -
- - - - {m['invoice_item_fa.identifiers.asset_number']()} - Num. Factura - Línea - Imagen - {#if !disabled} - Acciones - {/if} - - - - - {#if lineItem.series && lineItem.series.length > 0} - {#each lineItem.series as asset, index} - - {asset.number_id} - {asset.import_invoice || '-'} - {asset.import_line || '-'} - - {#if asset.image_path} -
- -
- {:else} - - - {/if} -
- {#if !disabled} - - -
- - -
-
- {/if} -
- - {/each} - {:else} - - - No hay activos registrados. - - - {/if} -
-
-
-
- {:else} - -
- - Tabla de Identificadores - - -
- -
- - -
- - - - Clave - Compl. 1 - Compl. 2 - Compl. 3 - {#if !disabled} - Acciones - {/if} - - - - - {#if lineItem.identifiers && lineItem.identifiers.length > 0} - {#each lineItem.identifiers as idDetail, index} - - {idDetail.identifier_code} - {idDetail.complement1 || '-'} - {idDetail.complement2 || '-'} - {idDetail.complement3 || '-'} - {#if !disabled} - -
- - -
-
- {/if} -
- - {/each} - {:else} - - - No hay identificadores registrados. - - - {/if} -
-
-
-
- {/if} -
- - - - - - {isEditing ? 'Editar' : 'Insertar'} Identificador - - Ingrese los detalles del identificador para esta partida. - - - -
-
- - -
-
- - -
-
- - -
-
- -
- - -
-
- -
-
- - -
-
- - -
-
- - -
-
-
- - - - - -
-
- - - - - - {isEditing ? 'Editar' : 'Insertar'} {m['invoice_item_fa.identifiers.asset_tag_title']()} - - Detalles del etiquetado de activos para Compras Mexicanas. - - - -
- -
-
- -

{currentMexAsset.import_invoice || '-'}

-
-
- -

{currentMexAsset.import_line || '-'}

-
-
- - -
-
- - -
-
- -
-
- {currentMexAsset.image_path || 'Ningún archivo seleccionado'} -
- -
-
-
-
- - - - - -
-
- - diff --git a/frontend/src/lib/components/dashboard/invoices/edit/items/fa/tab-labeling.svelte b/frontend/src/lib/components/dashboard/invoices/edit/items/fa/tab-labeling.svelte deleted file mode 100644 index 054bcc6e..00000000 --- a/frontend/src/lib/components/dashboard/invoices/edit/items/fa/tab-labeling.svelte +++ /dev/null @@ -1,285 +0,0 @@ - - -
- {#if visibility.showLabelingLeftSection} - -
- {m['invoice_item_fa.labeling.legend']()} - - {#if visibility.showLabelingStandard} -
-
- - -
-
- - -
- -
- {/if} - -
- {#if visibility.showLabelingEnhanced} - {#if visibility.showLabelingQuantity} -
- - -
- - {/if} - {#if visibility.showLabelingValuationValue} -
- - -
- - {/if} - {/if} -
- - {#if visibility.showLabelingEnhanced} - {#if visibility.showLabelingValuationMethod} -
- -
- - -
-
- - {/if} - - {#if visibility.showUsageReason} -
- - -
- - {/if} - {/if} - - - {#if visibility.showLabelingObservations} -
- - -
- - {/if} -
- {/if} - - {#if visibility.showLabelingEnhanced} - -
- {m['invoice_item_fa.labeling.assets_series']()} - -
- -
- - -
- - - - # - {m['invoice_item_fa.labeling.asset_number_short']()} - Factura - Línea - {#if !disabled} - {m['invoice_item_fa.labeling.actions_short']()} - {/if} - - - - - {#each lineItem.series || [] as asset, i} - - {asset.row || i+1} - {asset.number_id || '-'} - {asset.import_invoice || '-'} - {asset.import_line || '-'} - {#if !disabled} - -
- - -
-
- {/if} -
- - {:else} - - - No hay activos. - - - {/each} -
-
-
- - {#if selectedAssetIndex !== null} -
-
Editar #{editingAsset.row}
-
-
- - -
-
- - -
-
- - -
-
-
- - -
-
- {/if} -
- {/if} -
- - diff --git a/frontend/src/lib/components/dashboard/invoices/edit/items/fa/tab-series.svelte b/frontend/src/lib/components/dashboard/invoices/edit/items/fa/tab-series.svelte deleted file mode 100644 index 82e4aee7..00000000 --- a/frontend/src/lib/components/dashboard/invoices/edit/items/fa/tab-series.svelte +++ /dev/null @@ -1,465 +0,0 @@ - - -
- Series - -
-
- { if (descriptions) descriptions.has_serial = v; }} - /> - - -
-
- - - -
-
- - -
- - - - Línea - Serie - Modelo - Sub modelo - Núm. ID - Acciones - - - - {#if seriesList.length === 0} - - - No hay series registradas. Usa "Nueva serie" para agregar una. - - - {:else} - {#each seriesList as serie, i (i)} - hasSerial && !disabled && selectForEdit(i)} - > - - {serie.row ?? i + 1} - - {serie.serial_numbers || '-'} - - - {serie.model || '-'} - - - {serie.sub_model || '-'} - - {serie.number_id || '-'} - - - - - - {/each} - {/if} - - -
- - - {#if currentSerie && selectedSeriesIndex !== null} -
-
-
-
- - {selectedSeriesIndex >= (seriesList.length - 1) && !currentSerie?.id - ? 'Capturando Nueva serie' - : `Editando serie (línea ${currentSerie.row ?? selectedSeriesIndex + 1})`} - -
-
- - {#if !disabled} - - {/if} - -
-
-
-
- -
- {invoiceNumber || '-'} -
-
-
- -
- {invoiceLine || '-'} -
-
-
- - - -
-
- - handleSerieInput(e, 'serial_numbers')} - /> - -
-
- - handleSerieInput(e, 'model')} - /> - -
-
- -
- {partNumber || '-'} -
-
-
- - handleSerieInput(e, 'sub_model')} - /> - -
-
- - handleSerieInput(e, 'number_id')} - /> - -
-
- {#if !disabled} -
- -
- {/if} - -
- {/if} - -
- {#if !hasSerial} - Los datos capturados se conservan, pero la edición queda deshabilitada mientras "Lleva serie" esté apagado. - {:else} - Se permiten hasta {maxSeriesAllowed === Infinity ? 'ilimitadas' : maxSeriesAllowed} series para esta partida. - {/if} -
-
- - diff --git a/frontend/src/lib/components/dashboard/invoices/edit/items/fa/tariff-fraction-dialog.svelte b/frontend/src/lib/components/dashboard/invoices/edit/items/fa/tariff-fraction-dialog.svelte deleted file mode 100644 index 4c25a2ff..00000000 --- a/frontend/src/lib/components/dashboard/invoices/edit/items/fa/tariff-fraction-dialog.svelte +++ /dev/null @@ -1,16 +0,0 @@ - - - diff --git a/frontend/src/lib/components/dashboard/invoices/edit/items/fa/unit-of-measure-dialog.svelte b/frontend/src/lib/components/dashboard/invoices/edit/items/fa/unit-of-measure-dialog.svelte deleted file mode 100644 index 2912988b..00000000 --- a/frontend/src/lib/components/dashboard/invoices/edit/items/fa/unit-of-measure-dialog.svelte +++ /dev/null @@ -1,197 +0,0 @@ - - - - - - CATALOGOS DE UNIDADES DE MEDIDA - - -
-
- - -
-
- -
- {#if loading} -
- -
- {:else if error} -
-

{error}

-
- {:else} -
- - - - - - - - - - - - - {#each filteredUnits as unit, i} - handleSelect(unit)} - onkeydown={(event) => handleRowKeydown(event, unit)} - > - - - - - - - - {/each} - {#if filteredUnits.length === 0} - - - - {/if} - -
U.M.Descripción EspañolAbrév. InglésClave AduanaClave AmericanaClave O.M.A.
{unit.code || ''}{unit.description || ''}{unit.description_en || ''}{unit.customs_code || ''}{unit.american_code || ''}{unit.oma_code || ''}
- No se encontraron resultados -
-
- -
-
- - - - -
-
- {/if} -
- -
- {#if onClear} - - {/if} - -
-
-
diff --git a/frontend/src/lib/components/dashboard/invoices/edit/items/fa/valuation-method-selector.svelte b/frontend/src/lib/components/dashboard/invoices/edit/items/fa/valuation-method-selector.svelte deleted file mode 100644 index 03472cee..00000000 --- a/frontend/src/lib/components/dashboard/invoices/edit/items/fa/valuation-method-selector.svelte +++ /dev/null @@ -1,117 +0,0 @@ - - - - - - {m.invoice_selectors_valuation_method_title()} - - {m.invoice_selectors_valuation_method_description()} - - - -
-
- - -
-
- -
- {#if loading} -
- -
- {:else} - - - - {m.invoice_selectors_valuation_method_column_code()} - {m.invoice_selectors_valuation_method_column_description()} - - - - {#each filteredMethods as method} - handleSelect(method)} - > - {method.key} - {method.description} - - {:else} - - - {m.invoice_selectors_valuation_method_empty()} - - - {/each} - - - {/if} -
-
- {#if onClear} - - {/if} - -
-
-
diff --git a/frontend/src/lib/components/dashboard/invoices/edit/items/focus-item-sheet-field.ts b/frontend/src/lib/components/dashboard/invoices/edit/items/focus-item-sheet-field.ts deleted file mode 100644 index 87d0e4fe..00000000 --- a/frontend/src/lib/components/dashboard/invoices/edit/items/focus-item-sheet-field.ts +++ /dev/null @@ -1,75 +0,0 @@ -import { tick } from 'svelte'; - -const LINE_PREFIX = /^line\[\d+\]\./i; - -/** Strip `line[N].` prefix so the caller gets a plain suffix like `quantity.quantity`. */ -export function normalizeLineItemFieldPath(raw: string): string { - return (raw || '').replace(LINE_PREFIX, '').replace(/^body\./i, '').trim(); -} - -export type ItemSheetTabValue = - | 'generales' - | 'continuacion' - | 'series' - | 'etiquetado' - | 'identificadores'; - -interface FieldMapping { - id: string; - tab: ItemSheetTabValue; -} - -/** Maps API field-path suffixes (after stripping `line[N].`) to focusable DOM elements. */ -export const FIELD_PATH_TO_DOM_ID: Record = { - class_id: { id: 'clase', tab: 'generales' }, - unit_of_measure: { id: 'um', tab: 'generales' }, - 'quantity.quantity': { id: 'cantidad', tab: 'generales' }, - 'quantity.net_weight': { id: 'peso_neto', tab: 'generales' }, - 'quantity.gross_weight': { id: 'peso_bruto', tab: 'generales' }, - 'financial.unit_cost_capture': { id: 'costo_unitario', tab: 'generales' }, - 'customs.fraction': { id: 'fraccion', tab: 'generales' }, - 'customs.fraction_type': { id: 'tipo_tarifa', tab: 'generales' }, - 'customs.origin_country': { id: 'pais_origen', tab: 'generales' }, - 'customs.american_fraction': { id: 'fraccion_americana', tab: 'generales' }, - 'description.description_spanish': { id: 'desc_espanol', tab: 'generales' }, - 'description.description_english': { id: 'desc_ingles', tab: 'generales' }, - 'fa_data.subitem_number': { id: 'subitem_number', tab: 'generales' }, -}; - -/** - * Switches to the right sheet tab (if needed) and focuses the field that maps to `fieldPath`. - * `fieldPath` should be the normalized suffix (e.g. `quantity.quantity`), not the raw API path. - * Returns `true` when focus succeeded, `false` when the path is unknown or element not found. - */ -export async function focusItemSheetField( - fieldPath: string, - setActiveTab?: (tab: ItemSheetTabValue) => void -): Promise { - const mapping = FIELD_PATH_TO_DOM_ID[fieldPath]; - if (!mapping) return false; - - if (setActiveTab) { - setActiveTab(mapping.tab); - await tick(); - await new Promise((r) => requestAnimationFrame(() => r())); - } - - const root = document.querySelector('[data-invoice-item-sheet="fixed_asset"]'); - const el = root - ? root.querySelector(`#${mapping.id}`) - : (document.getElementById(mapping.id) as HTMLElement | null); - if (!el) return false; - - el.scrollIntoView({ behavior: 'smooth', block: 'center', inline: 'nearest' }); - try { - el.focus({ preventScroll: true }); - } catch { - try { - el.focus(); - } catch { - /* non-focusable element */ - } - } - - return true; -} diff --git a/frontend/src/lib/components/dashboard/invoices/edit/items/inv/item-sheet-inv.svelte b/frontend/src/lib/components/dashboard/invoices/edit/items/inv/item-sheet-inv.svelte deleted file mode 100644 index b399a96d..00000000 --- a/frontend/src/lib/components/dashboard/invoices/edit/items/inv/item-sheet-inv.svelte +++ /dev/null @@ -1,745 +0,0 @@ - - - - - - - - - - - - - {#if saveError?.length} -
- -
- {#each saveError as row} -

- {#if row.field}{row.field}: {/if}{row.message} -

- {/each} -
- -
- {/if} - - {#if editingItem} -
-

- {m.invoice_item_inv.required_fields_hint()} -

- - - {m.invoice_item_inv.tab_general()} - {m.invoice_item_inv.tab_classification()} - {m.invoice_item_inv.tab_quantities()} - {m.invoice_item_inv.tab_other()} - - - - - - {#if !isTargetingPreset} -
-

{m.invoice_item_inv.invoice_info_title()} ({systemLabel})

- {#if !invoice?.id} -
- ⚠️ {m.invoice_item_inv.invoice_unsaved_warning()} -
- {:else} -
-
- {m.invoice_item_inv.invoice_id()} - {invoice.id} -
-
- {m.invoice_item_inv.operation_type()} - {invoice.operation_type || 'N/A'} -
-
- {m.invoice_item_inv.invoice_number()} - {invoice.invoice_number || 'Pendiente'} -
-
- {m.invoice_item_inv.system()} - {systemLabel} -
-
- {/if} -
- {/if} - -
-
- -
- (showClassDialog = true)} - tabindex="0" - onkeydown={(event) => - openOnEnterOrSpace(event, () => (showClassDialog = true))} - /> - -
- {#if (editingItem as any).class_description} -

{(editingItem as any).class_description}

- {/if} -
- -
- - {#if editingItem?.quantity} - - {/if} -
-
- -
- (showUnitDialog = true)} - tabindex="0" - onkeydown={(event) => - openOnEnterOrSpace(event, () => (showUnitDialog = true))} - /> - -
-
- -
- - {#if editingItem?.financial} - - {/if} -
-
- -
- (showCountryDialog = true)} - tabindex="0" - onkeydown={(event) => - openOnEnterOrSpace(event, () => (showCountryDialog = true))} - /> - -
-
- -
- -
- (showFractionDialog = true)} - tabindex="0" - onkeydown={(event) => - openOnEnterOrSpace(event, () => (showFractionDialog = true))} - /> - -
-
-
- - {#if editingItem?.customs} - - {/if} -
-
- -
-
- - -
-
- - -
-
- -
-
- - -
-
- - -
-
- - -
-
- - {#if editingItem?.description} - - {/if} -
-
- - {#if editingItem?.description} - - {/if} -
-
- -
-
- - {#if editingItem} -
- (showPartDialog = true)} - tabindex="0" - onkeydown={(event) => - openOnEnterOrSpace(event, () => (showPartDialog = true))} - /> - -
- {/if} -
-
- - {#if editingItem?.description} - - {/if} -
-
-
- - - -
-
- - {#if editingItem?.customs} -
- (showFractionDialog = true)} - tabindex="0" - onkeydown={(event) => - openOnEnterOrSpace(event, () => (showFractionDialog = true))} - /> - -
- {/if} -
- -
-
- - {#if editingItem?.description} - - {/if} -
-
- - -
- {:else} -
- -

{m.invoice_item_inv.loading_item_data()}

-
- {/if} - - diff --git a/frontend/src/lib/components/dashboard/invoices/edit/items/items-tab-form.svelte b/frontend/src/lib/components/dashboard/invoices/edit/items/items-tab-form.svelte deleted file mode 100644 index ef235658..00000000 --- a/frontend/src/lib/components/dashboard/invoices/edit/items/items-tab-form.svelte +++ /dev/null @@ -1,1502 +0,0 @@ - - -
{ - if (highlightFieldId !== ITEMS_LINE_PLACEHOLDER) return; - onDismissHighlightForField?.(ITEMS_LINE_PLACEHOLDER); - }} - onchangecapture={() => { - if (highlightFieldId !== ITEMS_LINE_PLACEHOLDER) return; - onDismissHighlightForField?.(ITEMS_LINE_PLACEHOLDER); - }} -> -
- (partidaNoticeOpen = false)} - onDismissRow={dismissPartidaNoticeRow} - /> -
- -
-
-
-

{m.invoice_edit_items_title()}

-

- {m.invoice_edit_items_subtitle()} -

-
-
- - - -
-
- -
- - - - toggleSort('line_number')} - > -
- {m.invoice_edit_items_column_line()} - {#if sortField === 'line_number'} -
- {#if sortOrder === 'asc'} - - {:else} - - {/if} -
- {/if} -
-
- {#if operationType === 1} - {m.invoice_edit_items_column_impo_invoice()} - {m.invoice_edit_items_column_line()} - {m.invoice_edit_items_column_ps()} - {m.invoice_edit_items_imported_quantity()} - {m.invoice_edit_items_column_class()} - toggleSort('part_number_display')} - > -
- {m.invoice_edit_items_column_part_number()} - {#if sortField === 'part_number_display'} -
- {#if sortOrder === 'asc'} - - {:else} - - {/if} -
- {/if} -
-
- toggleSort('class_description')} - > -
- {m.invoice_edit_items_column_description()} - {#if sortField === 'class_description'} -
- {#if sortOrder === 'asc'} - - {:else} - - {/if} -
- {/if} -
-
- {m.invoice_edit_items_column_has_subitem()} - {m.invoice_edit_items_column_main_item()} - {:else if showCrTrackingHeader} - {m.invoice_edit_items_column_impo_invoice()} - {m.invoice_edit_items_column_line()} - {m.invoice_edit_items_column_ps()} - {m.invoice_edit_items_column_class()} - {m.invoice_edit_items_column_class_description()} - {m.invoice_edit_items_imported_quantity()} - {m.invoice_edit_items_column_um()} - {m.invoice_edit_items_column_preference()} - {m.invoice_edit_items_column_has_subitem()} - {m.invoice_edit_items_column_main_item()} - {:else if invoiceType === 'REP' || invoiceType === 'REPAR'} - {m.invoice_edit_items_column_impo_invoice()} - {m.invoice_edit_items_column_line()} - {m.invoice_edit_items_column_ps()} - {m.invoice_edit_items_column_class()} - toggleSort('part_number_display')} - > -
- {m.invoice_edit_items_column_part_number()} - {#if sortField === 'part_number_display'} -
- {#if sortOrder === 'asc'} - - {:else} - - {/if} -
- {/if} -
-
- {m.invoice_edit_items_column_class_description()} - {m.invoice_edit_items_imported_quantity()} - {m.invoice_edit_items_column_um()} - {m.invoice_edit_items_column_preference()} - {m.invoice_edit_items_column_has_subitem()} - {m.invoice_edit_items_column_main_item()} - {:else} - {m.invoice_edit_items_column_ps()} - toggleSort('class_code')} - > -
- {m.invoice_edit_items_column_class()} - {#if sortField === 'class_code'} - {#if sortOrder === 'asc'}{:else}{/if} - {:else} - - {/if} -
-
- toggleSort('class_description')} - > -
- {m.invoice_edit_items_column_class_description()} - {#if sortField === 'class_description'} - {#if sortOrder === 'asc'}{:else}{/if} - {:else} - - {/if} -
-
- toggleSort('quantity')} - > -
- {m.invoice_edit_items_column_quantity()} - {#if sortField === 'quantity'} - {#if sortOrder === 'asc'}{:else}{/if} - {:else} - - {/if} -
-
- toggleSort('unit_of_measure_code')} - > -
- {m.invoice_edit_items_column_um()} - {#if sortField === 'unit_of_measure_code'} - {#if sortOrder === 'asc'}{:else}{/if} - {:else} - - {/if} -
-
- toggleSort('reference_number')} - > -
- {m.invoice_edit_items_column_preference()} - {#if sortField === 'reference_number'} - {#if sortOrder === 'asc'}{:else}{/if} - {:else} - - {/if} -
-
- {m.invoice_edit_items_column_has_subitem()} - toggleSort('warehouse')} - > -
- {m.invoice_edit_items_column_main_item()} - {#if sortField === 'warehouse'} - {#if sortOrder === 'asc'}{:else}{/if} - {:else} - - {/if} -
-
- {/if} - {m.invoice_edit_items_column_actions()} -
-
- - {#if displayedItems.length === 0} - - - {m.invoice_edit_items_no_items_available()} - - - {:else} - {#each displayedItems as item (item.id)} - handleRowClick(item)} - ondblclick={() => handleEdit(item)} - onfocus={() => handleRowClick(item)} - class="group/item-row cursor-pointer transition-colors hover:bg-muted/50 {focusedLine?.id === - item.id - ? 'bg-muted ring-1 ring-primary/20 ring-inset' - : ''}" - > - {#if operationType === 1} - {item.line_number} - {item.fa_data?.search_invoice || '-'} - {item.fa_data?.search_line || '-'} - {item.is_subitem ? 'S' : 'P'} - {item.quantity?.quantity || '0'} - {item.class_code || '-'} - {item.part_number_display || '-'} - - {item.description?.description_spanish || '-'} - - {item.fa_data?.contains_subitems ? m.invoice_edit_observations_yes() : m.invoice_edit_observations_no()} - {item.warehouse || '-'} - {:else if showCrTrackingHeader} - {item.line_number} - {item.fa_data?.search_invoice || '-'} - {item.fa_data?.search_line || '-'} - {item.is_subitem ? 'S' : 'P'} - {item.class_code || '-'} - - {item.description?.description_spanish || '-'} - - {item.quantity?.quantity || '0'} - {item.unit_of_measure_code || '-'} - {item.reference_number || '-'} - {item.fa_data?.contains_subitems ? m.invoice_edit_observations_yes() : m.invoice_edit_observations_no()} - {item.warehouse || '-'} - {:else if invoiceType === 'REP' || invoiceType === 'REPAR'} - {item.line_number} - {item.fa_data?.search_invoice || '-'} - {item.fa_data?.search_line || '-'} - {item.is_subitem ? 'S' : 'P'} - {item.class_code || '-'} - {item.part_number_display || '-'} - - {item.description?.description_spanish || '-'} - - {item.quantity?.quantity || '0'} - {item.unit_of_measure_code || '-'} - {item.reference_number || '-'} - {item.fa_data?.contains_subitems ? m.invoice_edit_observations_yes() : m.invoice_edit_observations_no()} - {item.warehouse || '-'} - {:else} - {item.line_number} - {item.is_subitem ? 'S' : 'P'} - {item.class_code || '-'} - {item.class_description || '-'} - {item.quantity?.quantity || '0'} - {item.unit_of_measure_code || '-'} - {item.reference_number || '-'} - {item.fa_data?.contains_subitems ? m.invoice_edit_observations_yes() : m.invoice_edit_observations_no()} - {item.warehouse || '-'} - {/if} - -
- - -
-
-
- {/each} - {#if isLoadingMore} - - - {m.invoice_edit_items_loaded_more_items()} - - - {/if} - {/if} -
-
-
- - {#if flattenedLines.length > 0} -
- {m.invoice_edit_items_showing_lines({ displayed: String(displayedItems.length), total: String(flattenedLines.length) })} -
- {/if} -
- -
- {#if operationType === 1} - -
-

- {m.invoice_edit_items_spanish_description_label()} -

-
- {#if focusedLine} -

- {focusedLine.description?.description_spanish || m.invoice_edit_items_no_description_short()} -

- {:else} -

- {m.invoice_edit_items_select_row_to_view_description()} -

- {/if} -
-
- {/if} - - -
-
-

{m.invoice_edit_items_quantities()}

-
-
-
- {m.invoice_edit_items_column_part_number()} {items.length || 0} -
-
- {m.invoice_edit_items_bultos()} 0 -
-
-
- {m.invoice_edit_items_imported()}{imported || 0}
- {m.invoice_edit_items_net_weight()} {net_weight || 0}
- {m.invoice_edit_items_gross_weight()} {gross_weight || 0}
-
- -

- {m.invoice_edit_items_import_values_title()} -

- {m.invoice_edit_items_dollars()}0 USD
- {m.invoice_edit_items_pesos()} 0 MXN
- {m.invoice_edit_items_capture_value()} 0 USD - -

- spacer -

- - {m.invoice_edit_items_customs_value_short()}0 USD
- {m.invoice_edit_items_customs_value_short()} 0 MXN
-
-
-
- - - - - - Confirmar Eliminación - - ¿Está seguro que desea eliminar este item? Esta acción no se puede deshacer. - - - - - - - - - - - { - if (!open) { - showUsePresetDialog = false; - selectedPreset = null; - } - }} -> - - -
-
- {m.invoice_edit_items_use_template()} - - {m.invoice_edit_items_use_template_description()} - -
-
- - -
-
- - -
- -
-
-
- - -
-
- -
- {#if isLoadingPresets} -
- - {m.invoice_edit_items_loading()} -
- {:else if filteredPresets.length === 0} -
- -

{m.invoice_edit_items_no_templates_found()}

-
- {:else} - {#each filteredPresets as preset} - - {/each} - {/if} -
-
- - -
- {#if !selectedPreset} -
-
- -
-

{m.invoice_edit_items_select_template_to_view()}

-
- {:else} -
- -
-
-

- {selectedPreset.name} -

-

- {selectedPreset.description || m.invoice_edit_items_no_description_available()} -

-
-
-
- {m.invoice_edit_items_created_label()} -
-
- {selectedPreset.created_at - ? new Date(selectedPreset.created_at).toLocaleDateString(undefined, { - dateStyle: 'long' - }) - : '-'} -
-
-
- - -
- - - - # - {m.invoice_edit_items_item_description()} - {m.invoice_edit_items_quantity_short()} - Costo (USD) - - - - {#if selectedPresetItems.length === 0} - - -
- - {m.invoice_edit_items_template_empty_items()} -
-
-
- {:else} - {#each selectedPresetItems as item, i} - - - {i + 1} - - -
- - {item?.description?.description_spanish || m.invoice_edit_items_no_description()} - - {#if item.reference_number} - - REF: {item.reference_number} - - {/if} -
-
- - {item?.quantity?.quantity || 0} - - - ${(item?.financial?.unit_cost_usd || 0).toLocaleString(undefined, { - minimumFractionDigits: 2 - })} - -
- {/each} - {/if} -
-
-
-
- {/if} -
-
- - -
- - -
-
-
- - { - if (!open) { - createPresetName = ''; - createPresetDescription = ''; - } - }} -> - - - {m.invoice_edit_items_create_template_dialog_title()} - {m.invoice_edit_items_create_template_dialog_description()} - > - - -
-
-
- - -
-
- - -
-
- - \ No newline at end of file diff --git a/frontend/src/lib/components/dashboard/pedimentos/edit/digitization-tab-form.svelte b/frontend/src/lib/components/dashboard/pedimentos/edit/digitization-tab-form.svelte deleted file mode 100644 index 36c3c0ee..00000000 --- a/frontend/src/lib/components/dashboard/pedimentos/edit/digitization-tab-form.svelte +++ /dev/null @@ -1,642 +0,0 @@ - - - - - -
- - - - Línea - Tipo Documento - Ruta Archivo PDF - E-Document - Núm. Operación V.U - Observaciones - Acciones - - - - {#if !formData?.digitalizaciones || formData.digitalizaciones.length === 0} - - - No hay digitalizaciones registradas - - - {:else if paginatedDigitalizaciones.length === 0} - - - No hay datos en esta página - - - {:else} - {#each paginatedDigitalizaciones as digitalizacion, index} - - {digitalizacion.linea} - {digitalizacion.tipo_documento} - {digitalizacion.ruta_archivo_pdf} - {digitalizacion.e_document} - {digitalizacion.numero_operacion_vu} - - {digitalizacion.observaciones} - - -
- - -
-
-
- {/each} - {/if} -
-
-
- - - {#if formData?.digitalizaciones && formData.digitalizaciones.length > 0} -
- - - - Página {currentPage + 1} de {totalPages || 1} - - - -
- {/if} - - -
- - - - - - - - - -
-
-
- - - - - - Digitalización {currentDigitalizacion.linea} - - -
-
- - -
- -
- -
- - -
-

- Solo lectura. Usa el botón para elegir desde el catálogo de tipos de documento. -

-
- -
- -
- - -
-
- -
- -
- - -
-
- -
- - -
- -
- - -
-
- - - {/if} - - - {#if showNotas} - - - Notas del Pedimento - - - -
- - - - Usuario - Creada - Comentario - - - - Fecha - Hora - - - - - {#if notasPedimento.length === 0} - - - No hay notas registradas - - - {:else} - {#each visibleNotas as nota} - - {nota.usuario} - {nota.fecha} - {nota.hora} - {nota.comentario} - - {/each} - {/if} - -
-
- - -
-
- - - - Página {currentNotasPage + 1} de {totalNotasPages || 1} - - - -
-
- - -
- - - -
-
-
- {/if} - - - {#if showSeleccionAutomatizada} - - - Selección Automatizada - - -
-
- - -
- -
- - { - if (v) seleccionAutomatizadaFormData.primera_revision = v; - }} - > - - - {seleccionAutomatizadaFormData.primera_revision || 'Seleccionar'} - - - - Rojo - Verde - - -
- -
- - -
- -
- - { - if (v) seleccionAutomatizadaFormData.segunda_revision = v; - }} - > - - - {seleccionAutomatizadaFormData.segunda_revision || 'Seleccionar'} - - - - Rojo - Verde - - -
- -
- - -
-
-
-
- {/if} - - - {#if showMultas} - - - Multas - - -
- - { - if (v) multasFormData.cargo = v; - }} - > - - - {multasFormData.cargo || 'Seleccionar'} - - - - cargo oficina - cargo cliente - - -
-
-
- {/if} -
- - -
- - - - - - - - - - - - - - - - - - - - - -
-
- - - - - - Embarque Parcial de Mercancías - - -
- -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
- - -
-
-

Mercancías del Embarque

-
- - - -
-
- - -
- - - - # - Descripción de Mercancía - Cantidad UMC - Cantidad UMT - Peso - - - - {#if mercancias.length === 0} - - - No hay mercancías registradas - - - {:else} - {#each mercancias as mercancia, index} - - {index + 1} - {mercancia.descripcion} - {mercancia.cantidad_umc.toFixed(3)} - {mercancia.cantidad_umt.toFixed(3)} - {mercancia.peso.toFixed(3)} - - {/each} - {/if} - -
-
- - -
- - - - -
- - -
-
- - -
- -
- - -
- -
- - -
- -
- - -
- -
- - -
- -
- - -
-
- - -
-
- -
- - -
-
-
-
-
- - - - - -
-
- - - - - - Mercancías del Embarque Parcial - - -
-
- - -
- -
-
- - -
- -
- - -
- -
- - -
-
-
- - - - - -
-
- - - - - - Diferencias en contribuciones - - -
-
- - -
- -
- - -
- -
- - -
-
- - - - - -
-
- - - - - - Notas del Pedimento - - -
-
- - -
-
- - - - - -
-
diff --git a/frontend/src/lib/components/dashboard/pedimentos/edit/package-transportation-tab-form.svelte b/frontend/src/lib/components/dashboard/pedimentos/edit/package-transportation-tab-form.svelte deleted file mode 100644 index 0edd271b..00000000 --- a/frontend/src/lib/components/dashboard/pedimentos/edit/package-transportation-tab-form.svelte +++ /dev/null @@ -1,1942 +0,0 @@ - - - - - -
- -
- - - - Bultos - - -
- - -
-
- - -
-
- - -
-
- - -
-
-
- - - - - Transportes - - -
- - - - Identificación - País - Acciones - - - - {#if !formData?.transportes || formData.transportes.length === 0} - - - No hay transportes registrados - - - {:else} - {#each formData.transportes as transporte, index} - - {transporte.identificacion_fiscal} - {transporte.pais} - -
- - -
-
-
- {/each} - {/if} -
-
-
-
- - - - -
-
-
-
- - - - - Guías - - -
- - - - Guía - Identificador - Acciones - - - - {#if !formData?.guias || formData.guias.length === 0} - - - No hay guías registradas - - - {:else} - {#each formData.guias as guia, index} - - {guia.guia} - {guia.identificador} - -
- - -
-
-
- {/each} - {/if} -
-
-
-
- - - -
-
-
- - -
- - - - Transportes, Precintos - - -
- - - - Número - Identificación - Acciones - - - - {#if !formData?.precintos || formData.precintos.length === 0} - - - No hay precintos registrados - - - {:else} - {#each formData.precintos as precinto, index} - - {precinto.numero} - {precinto.identificacion} - -
- - -
-
-
- {/each} - {/if} -
-
-
-
- - - -
-
-
- - - - - Contenedores - - -
- - - - Número - Tipo - - - - {#if !formData?.contenedores || formData.contenedores.length === 0} - - - No hay contenedores registrados - - - {:else} - {#each formData.contenedores as contenedor, index} - - {contenedor.numero} - {contenedor.tipo} - -
- - -
-
-
- {/each} - {/if} -
-
-
-
- - - - - -
-
-
-
-
- - - - - - Transportes del Pedimento - - -
-
- -
- - -
-
- -
-
- - -
-
- - -
-
- -
- - -
- -
- - -
- -
-
- - -
-
- - v && (currentTransporte.estado = v)} - > - - {currentTransporte.estado ? estadosMexico.find(e => e.clave === currentTransporte.estado)?.nombre || currentTransporte.estado : 'Seleccionar estado'} - - - {#each estadosMexico as estado} - {estado.clave} - {estado.nombre} - {/each} - - -
-
- -
- -
- c.m3_key === currentTransporte.pais)?.description_es || currentTransporte.pais : '')} - oninput={(e) => (searchCountry = (e.target as HTMLInputElement).value)} - onfocus={() => (showCountryDropdown = true)} - onblur={() => setTimeout(() => (showCountryDropdown = false), 200)} - class="w-full" - /> - {#if showCountryDropdown && filteredCountries.length > 0} -
- {#each filteredCountries as country} - - {/each} -
- {/if} -
-
- -
-
- - -
-
- - -
-
-
- - - - - -
-
- - - - - - - {editingPrecintoIndex !== null ? 'Editar Precinto' : 'Nuevo Precinto'} - - - -
-
- - -
- -
- - -
-
- - - - - -
-
- - - - - - - {editingContenedorIndex !== null ? 'Editar Contenedor' : 'Nuevo Contenedor'} - - - -
-
- - -
- -
- - v && (currentContenedor.tipo = v)} - > - - {currentContenedor.tipo || 'Seleccionar tipo'} - - - {#each tiposContenedor as tipo} - {tipo} - {/each} - - -
-
- - - - - -
-
- - - (isTransportesPrecintosDialogOpen = open)}> - - - Transportes y Precintos - -
- -
-

Listado de transportes separados por comas

-
-
- - - -
-
- - -
-
-
- - -
-

Listado de precintos separados por comas

-
- - -
-
-
- - - - -
-
- - - (isTransportistasDialogOpen = open)}> - - - Catálogo de Transportistas - -
-
-
- - -
-
- -
-
-
- - - - Clave - Nombre - RFC - Acciones - - - - {#if filteredTransportistas.length === 0} - - - {searchTransportista ? 'No se encontraron transportistas con ese criterio.' : 'No hay transportistas en el catálogo. Haz clic en "Nuevo" para agregar.'} - - - {:else} - {#each filteredTransportistas as transportista, index} - - {transportista.clave} - {transportista.nombre} - {transportista.rfc} - -
- - - -
-
-
- {/each} - {/if} -
-
-
-
- - - -
-
- - - (isTransportistaFormDialogOpen = open)}> - - - {editingTransportistaIndex !== null ? 'Editar' : 'Nuevo'} Transportista - - {#if currentCatalogoTransportista} -
- -
- - -
-
- - -
- - -
-
- - -
-
- - -
-
- - -
- - -
- - -
-
- - v && (currentCatalogoTransportista.pais = v)} - > - - {currentCatalogoTransportista.pais ? countries.find(c => c.m3_key === currentCatalogoTransportista.pais)?.description_es || currentCatalogoTransportista.pais : 'Seleccionar país'} - - - {#if countries.length === 0} - Cargando países... - {:else} - {#each countries as country} - {country.description_es} - {/each} - {/if} - - -
-
- - v && (currentCatalogoTransportista.estado = v)} - > - - {currentCatalogoTransportista.estado ? estadosMexico.find(e => e.clave === currentCatalogoTransportista.estado)?.nombre || currentCatalogoTransportista.estado : 'Seleccionar estado'} - - - {#each estadosMexico as estado} - {estado.clave} - {estado.nombre} - {/each} - - -
-
- - -
- - -
- - -
-
- - -
-
- - -
-
- - -
-
-

# DE GAFETES

-
- -
-
- -
- - - - Línea - Número Gafete Único - Nombre - Acciones - - - - {#if currentCatalogoTransportista.gafetes && currentCatalogoTransportista.gafetes.length > 0} - {#each currentCatalogoTransportista.gafetes as gafete, index} - - {gafete.linea} - {gafete.numeroGafeteUnico} - {gafete.nombreTransportista} - -
- - -
-
-
- {/each} - {:else} - - - Sin gafetes registrados - - - {/if} -
-
-
- -
- - -
-
-
- {/if} - - - - -
-
- - - (isGuiasDialogOpen = open)}> - - - Guías de Pedimento - -
-
- - -
- -
- - v && (currentGuia.identificador = v)} - > - - {currentGuia.identificador || 'Seleccionar identificador'} - - - Maestra - - -
-
- - - - - -
-
- - - (isGafeteDialogOpen = open)}> - - - GAFETES UNICOS - -
-
-
- - -
-
- - -
-
- -
- - -
- -
- - -
-
- - - - - -
-
diff --git a/frontend/src/lib/components/dashboard/pedimentos/edit/payments-tab-form.svelte b/frontend/src/lib/components/dashboard/pedimentos/edit/payments-tab-form.svelte deleted file mode 100644 index fb004236..00000000 --- a/frontend/src/lib/components/dashboard/pedimentos/edit/payments-tab-form.svelte +++ /dev/null @@ -1,188 +0,0 @@ - - - - - Información de Pagos - - Gestiona la información de pagos del pedimento - - - -
-
- -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- -
-
-
-
diff --git a/frontend/src/lib/components/dashboard/pedimentos/edit/pedimento-selector-dialog.svelte b/frontend/src/lib/components/dashboard/pedimentos/edit/pedimento-selector-dialog.svelte deleted file mode 100644 index da8755f0..00000000 --- a/frontend/src/lib/components/dashboard/pedimentos/edit/pedimento-selector-dialog.svelte +++ /dev/null @@ -1,227 +0,0 @@ - - - - - - Seleccionar Pedimento - - Busca y selecciona el pedimento registrado para asociarlo a la digitalización. - - - -
- - -
- -
- {#if loading && pedimentos.length === 0} -
- -

Cargando catálogo...

-
- {:else if filteredPedimentos.length === 0} -
-

No se encontraron pedimentos.

-
- {:else} - - - - - - - - - - - - {#each filteredPedimentos as pedimento} - handleSelect(pedimento)} - > - - - - - - - {/each} - -
IDPedimentoClaveRégimenEstado
{pedimento.id} -
- - {buildPedimentoLabel(pedimento)} -
-
{pedimento.pedimento_code || '-'}{pedimento.regime || '-'} - {#if pedimento.status} - - {pedimento.status} - - {:else} - - Sin estado - - {/if} -
- -
- {#if loadingMore} - - {/if} -
- {/if} -
- - -
- Mostrando {filteredPedimentos.length} de {totalItems || pedimentos.length} registro(s) cargados -
- {#if onClear} - - {/if} - -
-
-
\ No newline at end of file diff --git a/frontend/src/lib/components/dashboard/pedimentos/edit/temp.svelte b/frontend/src/lib/components/dashboard/pedimentos/edit/temp.svelte deleted file mode 100644 index a540a118..00000000 --- a/frontend/src/lib/components/dashboard/pedimentos/edit/temp.svelte +++ /dev/null @@ -1,771 +0,0 @@ - - - - - -
- - - - - -
- - -
- {#if activeTab === 'DTA'} -
-
- - -
-
- - -

(dejar en cero para calcular automático)

-
-
- - -

(dejar en cero para calcular automático)

-
-
- {:else if activeTab === 'PREV'} -
-
- - -
-
- {:else if activeTab === 'ECI'} -
-
- - -
-
- - -

- (dejar en cero para que el sistema calcule el importe) -

-
-
- {:else if activeTab === 'MULT'} -
-
- - -
-
- - -
-
- {:else if activeTab === 'REC'} -
-
- - -
-
- - -
-
- {/if} -
- - -
- - - - Contribución - T.T. - Tasa - F.P. - Importe - Gravamen - Abreviación - F.P. - Importe - Acciones - - - - {#if !formData?.contribuciones || formData.contribuciones.length === 0} - - - No hay contribuciones registradas - - - {:else} - {#each formData.contribuciones as contribucion, index} - - {contribucion.contribucion} - {contribucion.tipo_tasa} - {contribucion.tasa.toLocaleString('es-MX', { - minimumFractionDigits: 2, - maximumFractionDigits: 5 - })} - {contribucion.forma_pago} - {contribucion.importe.toLocaleString('es-MX', { - minimumFractionDigits: 2, - maximumFractionDigits: 2 - })} - {contribucion.gravamen} - {contribucion.abreviacion} - {contribucion.forma_pago_2} - {contribucion.importe_2.toLocaleString('es-MX', { - minimumFractionDigits: 2, - maximumFractionDigits: 2 - })} - -
- - -
-
-
- {/each} - {/if} -
-
-
- - -
-
-
- - formData && (formData.calculo_manual = checked === true)} - /> - -
- -
- - formData && (formData.operaciones_regla_31_40 = checked === true)} - /> - -
-
- -
- - - -
-
-
-
- - - - - - - {editingIndex !== null ? 'Editar Contribución' : 'Nueva Contribución'} - - - -
- -
-

Tasas Pedimento - Registro 509

- -
- - v && (currentContribucion.contribucion = v)} - > - - {currentContribucion.contribucion || 'Seleccionar'} - - - {#each opcionesContribuciones as opcion} - {opcion} - {/each} - - -
- -
- -
- v && (currentContribucion.tipo_tasa = v)} - > - - {currentContribucion.tipo_tasa || 'Seleccionar'} - - - Porcentual - Específico - Cuota minima (DTA) - Cuota fija (DTA) - Tasa de descuento sobre ad valorem - Factor de aplicación sobre tigie. - Al millar (DTA) - Tasa de descuento sobre el arancel específico - Tasa especifica sobre precios de referencia - Tasa especifica sobre precios de referencia con UM - - - -
-
- -
- - -
-
- - -
-

Contribuciones Generales - Registro 510

- - - {#if currentContribucion.contribuciones_generales && currentContribucion.contribuciones_generales.length > 0} - - - - Clave Contribución - Forma de Pago - Importe - - - - {#each currentContribucion.contribuciones_generales as contribGen, i} - - {contribGen.clave_contribucion} - {contribGen.clave_forma_pago} - ${contribGen.importe_contribucion.toFixed(2)} - - {/each} - -
- {:else} -

No hay contribuciones generales registradas

- {/if} - -
- - - -
-
-
- - - - - -
-
- - - - - - Insertando - - -
-
- - -
- -
- - -
- -
- - -
- -
- - -
- -
- - -
- -
- - -
-
- - - - - -
-
diff --git a/frontend/src/lib/components/dashboard/pedimentos/edit/transport-tab-form.svelte b/frontend/src/lib/components/dashboard/pedimentos/edit/transport-tab-form.svelte deleted file mode 100644 index 17ffeeb1..00000000 --- a/frontend/src/lib/components/dashboard/pedimentos/edit/transport-tab-form.svelte +++ /dev/null @@ -1,98 +0,0 @@ - - - - - Medios de Transporte - - Gestiona la información de transporte del pedimento - - - -
-
- -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
-
-
-
-
diff --git a/frontend/src/lib/components/dashboard/pedimentos/edit/validation-tab-form.svelte b/frontend/src/lib/components/dashboard/pedimentos/edit/validation-tab-form.svelte deleted file mode 100644 index 3c5d8c35..00000000 --- a/frontend/src/lib/components/dashboard/pedimentos/edit/validation-tab-form.svelte +++ /dev/null @@ -1,151 +0,0 @@ - - - - - Validación del Pedimento - - Gestiona la documentación de validación del pedimento - - - -
-
- -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
- - -
-
-
-
-
diff --git a/frontend/src/lib/components/dashboard/ports/columns.ts b/frontend/src/lib/components/dashboard/ports/columns.ts deleted file mode 100644 index da9ff703..00000000 --- a/frontend/src/lib/components/dashboard/ports/columns.ts +++ /dev/null @@ -1,54 +0,0 @@ -import type { Port } from '$lib/api/dashboard/a76/general_catalogs/ports'; -import { m } from '$lib/i18n/messages'; -import type { ColumnDef } from '@tanstack/table-core'; -import { renderComponent } from '$lib/components/ui/data-table'; -import DataTableActions from './data-table-actions.svelte'; - -export function createColumns( - onSuccess: () => void, - permissions: { canEdit: boolean; canDelete: boolean } = { canEdit: true, canDelete: true } -): ColumnDef[] { - return [ - { - accessorKey: 'port_code', - header: m['common.col_port_code'](), - }, - { - accessorKey: 'description', - header: m['common.col_description'](), - cell: ({ row }) => row.original.description || '-' - }, - { - accessorKey: 'location_code', - header: m['common.col_location_code'](), - }, - { - accessorKey: 'location_description', - header: m['common.col_location'](), - cell: ({ row }) => row.original.location_description || '-' - }, - { - accessorKey: 'port_type', - header: m['common.col_type'](), - cell: ({ row }) => { - const type = row.original.port_type; - if (type === 'ENTRY') return 'Entrada'; - if (type === 'EXIT') return 'Salida'; - if (type === 'BOTH') return 'Ambos'; - return type; - } - }, - { - id: 'actions', - header: m['common.col_actions'](), - cell: ({ row }) => { - return renderComponent(DataTableActions, { - item: row.original, - onSuccess, - canEdit: permissions.canEdit, - canDelete: permissions.canDelete - }); - } - } - ]; -} diff --git a/frontend/src/lib/components/dashboard/ports/create-edit-dialog.svelte b/frontend/src/lib/components/dashboard/ports/create-edit-dialog.svelte deleted file mode 100644 index 7c0df5e7..00000000 --- a/frontend/src/lib/components/dashboard/ports/create-edit-dialog.svelte +++ /dev/null @@ -1,229 +0,0 @@ - - - - - - {title} - - -
- {#if error} -
{error}
- {/if} - -
- - -
- -
- - -
- -
- - -
- -
- - -
- -
- -
- - - {formData.port_type === 'ENTRY' - ? 'Entrada' - : formData.port_type === 'EXIT' - ? 'Salida' - : formData.port_type === 'BOTH' - ? 'Ambos' - : 'Seleccionar'} - - - Entrada - Salida - Ambos - - -
-
-
- - - - - -
-
diff --git a/frontend/src/lib/components/dashboard/ports/data-table-actions.svelte b/frontend/src/lib/components/dashboard/ports/data-table-actions.svelte deleted file mode 100644 index 0a2835ee..00000000 --- a/frontend/src/lib/components/dashboard/ports/data-table-actions.svelte +++ /dev/null @@ -1,68 +0,0 @@ - - -{#if canEdit || canDelete} - - - {#snippet child({ props })} - - {/snippet} - - - Acciones - {#if canEdit} - editDialogOpen = true}> - - Editar - - {/if} - {#if canDelete} - deleteDialogOpen = true} class="text-destructive"> - - Eliminar - - {/if} - - -{/if} - -{#if canEdit} - -{/if} - -{#if canDelete} - -{/if} diff --git a/frontend/src/lib/components/dashboard/ports/delete-dialog.svelte b/frontend/src/lib/components/dashboard/ports/delete-dialog.svelte deleted file mode 100644 index cb45775b..00000000 --- a/frontend/src/lib/components/dashboard/ports/delete-dialog.svelte +++ /dev/null @@ -1,117 +0,0 @@ - - - - - - ¿Estás seguro? - -

Esta acción no se puede deshacer. Se eliminará permanentemente este puerto:

- {#if item} -
-
- Código: - {item.port_code} -
- {#if item.description} -
- Descripción: - {item.description} -
- {/if} - {#if item.location_code} -
- Ubicación: - {item.location_code} -
- {/if} -
- {:else} -
- Cargando información del puerto... -
- {/if} - {#if error} -
- {error} -
- {/if} -
-
- - Cancelar - - {#if loading} - - Eliminando... - {:else} - Eliminar - {/if} - - -
-
diff --git a/frontend/src/lib/components/dashboard/reference_data/code_pedimento_regimens/columns.ts b/frontend/src/lib/components/dashboard/reference_data/code_pedimento_regimens/columns.ts deleted file mode 100644 index 9197fa01..00000000 --- a/frontend/src/lib/components/dashboard/reference_data/code_pedimento_regimens/columns.ts +++ /dev/null @@ -1,86 +0,0 @@ -import type { ColumnDef } from "@tanstack/table-core"; -import { m } from '$lib/i18n/messages'; -import { renderComponent, renderSnippet } from "$lib/components/ui/data-table/index.js"; -import { createRawSnippet } from "svelte"; -import DataTableActions from "./data-table-actions.svelte"; - -export type CodePedimentoRegimen = { - id: number; - pedimento_code: string; - regimen_code: string | null; - type_code: string | null; -}; - -export function createColumns( - onSuccess?: () => void, - permissions: { canEdit: boolean; canDelete: boolean } = { canEdit: true, canDelete: true } -): ColumnDef[] { - return [ - { - accessorKey: "pedimento_code", - header: m['common.col_pedimento_code'](), - cell: ({ row }) => { - const codeSnippet = createRawSnippet<[{ code: string }]>((getCode) => { - const { code } = getCode(); - return { - render: () => - `${code}` - }; - }); - return renderSnippet(codeSnippet, { code: row.original.pedimento_code }); - } - }, - { - accessorKey: "regimen_code", - header: m['common.col_regime_code'](), - cell: ({ row }) => { - const regimenSnippet = createRawSnippet<[{ code: string | null }]>((getCode) => { - const { code } = getCode(); - if (code) { - return { - render: () => - `${code}` - }; - } - return { - render: () => `N/A` - }; - }); - return renderSnippet(regimenSnippet, { code: row.original.regimen_code }); - } - }, - { - accessorKey: "type_code", - header: m['common.col_type'](), - cell: ({ row }) => { - const typeSnippet = createRawSnippet<[{ type: string | null }]>((getType) => { - const { type } = getType(); - if (type) { - return { - render: () => - `${type}` - }; - } - return { - render: () => `N/A` - }; - }); - return renderSnippet(typeSnippet, { type: row.original.type_code }); - } - }, - { - id: "actions", - cell: ({ row }) => { - return renderComponent(DataTableActions, { - item: row.original, - onSuccess, - canEdit: permissions.canEdit, - canDelete: permissions.canDelete - }); - } - } - ]; -} - -// Mantener compatibilidad hacia atrás -export const columns = createColumns(); diff --git a/frontend/src/lib/components/dashboard/reference_data/code_pedimento_regimens/create-edit-dialog.svelte b/frontend/src/lib/components/dashboard/reference_data/code_pedimento_regimens/create-edit-dialog.svelte deleted file mode 100644 index 3ad663b5..00000000 --- a/frontend/src/lib/components/dashboard/reference_data/code_pedimento_regimens/create-edit-dialog.svelte +++ /dev/null @@ -1,200 +0,0 @@ - - - - - - - {isEditing ? "Editar" : "Nuevo"} Código Pedimento - Régimen - - - {isEditing - ? "Modifica los datos de la relación código pedimento - régimen." - : "Completa los datos para crear una nueva relación."} - - - -
- {#if error} -
- {error} -
- {/if} - -
- - -
- -
- - -

Opcional

-
- -
- - -

Opcional

-
- - - - - -
-
-
diff --git a/frontend/src/lib/components/dashboard/reference_data/code_pedimento_regimens/data-table-actions.svelte b/frontend/src/lib/components/dashboard/reference_data/code_pedimento_regimens/data-table-actions.svelte deleted file mode 100644 index 3c8d5b80..00000000 --- a/frontend/src/lib/components/dashboard/reference_data/code_pedimento_regimens/data-table-actions.svelte +++ /dev/null @@ -1,82 +0,0 @@ - - - - - {#snippet child({ props })} - - {/snippet} - - - - Acciones - - - Copiar ID - - - - Ver detalles - - - - {#if canEdit || canDelete} - - - {#if canEdit} - alert('Módulo de edición no disponible para Catálogos Públicos')} - > - - Editar - - {/if} - {#if canDelete} - alert('Módulo de eliminación no disponible para Catálogos Públicos')} - class="text-destructive" - > - - Eliminar - - {/if} - - {/if} - - - - diff --git a/frontend/src/lib/components/dashboard/reference_data/code_pedimento_regimens/data-table.svelte b/frontend/src/lib/components/dashboard/reference_data/code_pedimento_regimens/data-table.svelte deleted file mode 100644 index 1e44175d..00000000 --- a/frontend/src/lib/components/dashboard/reference_data/code_pedimento_regimens/data-table.svelte +++ /dev/null @@ -1,125 +0,0 @@ - - -
-
- - - {#each table.getHeaderGroups() as headerGroup (headerGroup.id)} - - {#each headerGroup.headers as header (header.id)} - - {/each} - - {/each} - - - {#each table.getRowModel().rows as row (row.id)} - - {#each row.getVisibleCells() as cell (cell.id)} - - {/each} - - {:else} - - - - {/each} - - - {#if hasMore} - - - - {/if} - -
- {#if !header.isPlaceholder} - - {/if} -
- -
- No hay resultados. -
-
- {#if loading} -
-
- Cargando más registros... -
- {:else} -
- - Desplázate para cargar más - -
- {/if} -
-
-
-
diff --git a/frontend/src/lib/components/dashboard/reference_data/code_pedimento_regimens/delete-dialog.svelte b/frontend/src/lib/components/dashboard/reference_data/code_pedimento_regimens/delete-dialog.svelte deleted file mode 100644 index a582d114..00000000 --- a/frontend/src/lib/components/dashboard/reference_data/code_pedimento_regimens/delete-dialog.svelte +++ /dev/null @@ -1,119 +0,0 @@ - - - - - - ¿Estás seguro? - -

Esta acción no se puede deshacer. Se eliminará permanentemente este registro:

- {#if item} -
-
- ID: - {item.id} -
-
- Código Pedimento: - {item.pedimento_code} -
- {#if item.regimen_code} -
- Código Régimen: - {item.regimen_code} -
- {/if} -
- {/if} - {#if error} -
- {error} -
- {/if} -
-
- - Cancelar - - {#if loading} - - - - - {/if} - Eliminar - - -
-
diff --git a/frontend/src/lib/components/dashboard/reference_data/code_pedimento_regimens/details-dialog.svelte b/frontend/src/lib/components/dashboard/reference_data/code_pedimento_regimens/details-dialog.svelte deleted file mode 100644 index 9837b2d2..00000000 --- a/frontend/src/lib/components/dashboard/reference_data/code_pedimento_regimens/details-dialog.svelte +++ /dev/null @@ -1,84 +0,0 @@ - - - - - - Detalles del Registro - - Información completa de la relación código pedimento - régimen - - - - {#if item} -
-
-
- ID - {item.id} -
- -
- -
-
- Código Pedimento - - {item.pedimento_code} - -
- -
- -
-
- Código Régimen - {#if item.regimen_code} - - {item.regimen_code} - - {:else} - No especificado - {/if} -
- -
- -
-
- Tipo - {#if item.type_code} - - {item.type_code} - - {:else} - No especificado - {/if} -
-
-
- {/if} - - - - -
-
diff --git a/frontend/src/lib/components/dashboard/reference_data/containers/columns.ts b/frontend/src/lib/components/dashboard/reference_data/containers/columns.ts deleted file mode 100644 index 1de081c9..00000000 --- a/frontend/src/lib/components/dashboard/reference_data/containers/columns.ts +++ /dev/null @@ -1,58 +0,0 @@ -import type { ColumnDef } from "@tanstack/table-core"; -import { m } from '$lib/i18n/messages'; -import { renderComponent, renderSnippet } from "$lib/components/ui/data-table/index.js"; -import { createRawSnippet } from "svelte"; -import DataTableActions from "./data-table-actions.svelte"; - -export type Container = { - key: string; - description: string; -}; - -export function createColumns( - onSuccess: () => void, - permissions: { canEdit: boolean; canDelete: boolean } = { canEdit: true, canDelete: true } -): ColumnDef[] { - return [ - { - accessorKey: "key", - header: m['common.col_code'](), - cell: ({ row }) => { - const codeSnippet = createRawSnippet<[{ code: string }]>((getCode) => { - const { code } = getCode(); - return { - render: () => - `${code}` - }; - }); - return renderSnippet(codeSnippet, { code: row.original.key }); - } - }, - { - accessorKey: "description", - header: m['common.col_description'](), - cell: ({ row }) => { - const descSnippet = createRawSnippet<[{ description: string }]>((getDesc) => { - const { description } = getDesc(); - return { - render: () => `
${description}
` - }; - }); - return renderSnippet(descSnippet, { description: row.original.description }); - } - }, - { - id: "actions", - cell: ({ row }) => { - return renderComponent(DataTableActions, { - item: row.original, - onSuccess, - canEdit: permissions.canEdit, - canDelete: permissions.canDelete - }); - } - } - ]; -} - -// Legacy export removed diff --git a/frontend/src/lib/components/dashboard/reference_data/containers/create-edit-dialog.svelte b/frontend/src/lib/components/dashboard/reference_data/containers/create-edit-dialog.svelte deleted file mode 100644 index e623f831..00000000 --- a/frontend/src/lib/components/dashboard/reference_data/containers/create-edit-dialog.svelte +++ /dev/null @@ -1,184 +0,0 @@ - - - - - - - {isEditing ? "Editar" : "Nuevo"} Contenedor - - - {isEditing - ? "Modifica los datos del contenedor." - : "Completa los datos para crear un nuevo contenedor."} - - - -
- {#if error} -
- {error} -
- {/if} - -
- - -
- -
- - -
- - - - - -
-
-
diff --git a/frontend/src/lib/components/dashboard/reference_data/containers/data-table-actions.svelte b/frontend/src/lib/components/dashboard/reference_data/containers/data-table-actions.svelte deleted file mode 100644 index 6fbfa25c..00000000 --- a/frontend/src/lib/components/dashboard/reference_data/containers/data-table-actions.svelte +++ /dev/null @@ -1,78 +0,0 @@ - - - - - {#snippet child({ props })} - - {/snippet} - - - - Acciones - - - Copiar Código - - (showDetailsDialog = true)}> - - Ver detalles - - - - {#if canEdit || canDelete} - - - {#if canEdit} - alert('Módulo de edición no disponible para Catálogos Públicos')} - > - - Editar - - {/if} - {#if canDelete} - alert('Módulo de eliminación no disponible para Catálogos Públicos')} - class="text-destructive" - > - - Eliminar - - {/if} - - {/if} - - - - diff --git a/frontend/src/lib/components/dashboard/reference_data/containers/data-table.svelte b/frontend/src/lib/components/dashboard/reference_data/containers/data-table.svelte deleted file mode 100644 index 1e44175d..00000000 --- a/frontend/src/lib/components/dashboard/reference_data/containers/data-table.svelte +++ /dev/null @@ -1,125 +0,0 @@ - - -
-
- - - {#each table.getHeaderGroups() as headerGroup (headerGroup.id)} - - {#each headerGroup.headers as header (header.id)} - - {/each} - - {/each} - - - {#each table.getRowModel().rows as row (row.id)} - - {#each row.getVisibleCells() as cell (cell.id)} - - {/each} - - {:else} - - - - {/each} - - - {#if hasMore} - - - - {/if} - -
- {#if !header.isPlaceholder} - - {/if} -
- -
- No hay resultados. -
-
- {#if loading} -
-
- Cargando más registros... -
- {:else} -
- - Desplázate para cargar más - -
- {/if} -
-
-
-
diff --git a/frontend/src/lib/components/dashboard/reference_data/containers/delete-dialog.svelte b/frontend/src/lib/components/dashboard/reference_data/containers/delete-dialog.svelte deleted file mode 100644 index 330855b9..00000000 --- a/frontend/src/lib/components/dashboard/reference_data/containers/delete-dialog.svelte +++ /dev/null @@ -1,94 +0,0 @@ - - - - - - ¿Estás seguro? - -

Esta acción no se puede deshacer. Se eliminará permanentemente este contenedor:

- {#if item} -
-
- Código: - {item.key} -
-
- Descripción: - {item.description} -
-
- {/if} - {#if error} -
- {error} -
- {/if} -
-
- - Cancelar - - {#if loading} - - {/if} - Eliminar - - -
-
diff --git a/frontend/src/lib/components/dashboard/reference_data/containers/details-dialog.svelte b/frontend/src/lib/components/dashboard/reference_data/containers/details-dialog.svelte deleted file mode 100644 index 6dc69608..00000000 --- a/frontend/src/lib/components/dashboard/reference_data/containers/details-dialog.svelte +++ /dev/null @@ -1,57 +0,0 @@ - - - - - - Detalles del Contenedor - - Información completa del contenedor - - - - {#if item} -
-
-
- Código - - {item.key} - -
- -
- -
-
- Descripción -

{item.description}

-
- -
-
- {/if} - - - - -
-
diff --git a/frontend/src/lib/components/dashboard/reference_data/countries/columns.ts b/frontend/src/lib/components/dashboard/reference_data/countries/columns.ts deleted file mode 100644 index 0b4547ff..00000000 --- a/frontend/src/lib/components/dashboard/reference_data/countries/columns.ts +++ /dev/null @@ -1,102 +0,0 @@ -import type { ColumnDef } from "@tanstack/table-core"; -import { m } from '$lib/i18n/messages'; -import { renderComponent, renderSnippet } from "$lib/components/ui/data-table/index.js"; -import { createRawSnippet } from "svelte"; -import DataTableActions from "./data-table-actions.svelte"; - -export type Country = { - m3_key: string; - mex_key: string; - ame_key: string; - description_es: string; - description_en: string; -}; - -export function createColumns( - onSuccess: () => void, - permissions: { canEdit: boolean; canDelete: boolean } = { canEdit: true, canDelete: true } -): ColumnDef[] { - return [ - { - accessorKey: "m3_key", - header: m['common.col_key_m3'](), - cell: ({ row }) => { - const codeSnippet = createRawSnippet<[{ code: string }]>((getCode) => { - const { code } = getCode(); - return { - render: () => - `${code}` - }; - }); - return renderSnippet(codeSnippet, { code: row.original.m3_key }); - } - }, - { - accessorKey: "mex_key", - header: m['common.col_key_mx'](), - cell: ({ row }) => { - const codeSnippet = createRawSnippet<[{ code: string }]>((getCode) => { - const { code } = getCode(); - return { - render: () => - `${code}` - }; - }); - return renderSnippet(codeSnippet, { code: row.original.mex_key }); - } - }, - { - accessorKey: "ame_key", - header: m['common.col_key_ame'](), - cell: ({ row }) => { - const codeSnippet = createRawSnippet<[{ code: string }]>((getCode) => { - const { code } = getCode(); - return { - render: () => - `${code}` - }; - }); - return renderSnippet(codeSnippet, { code: row.original.ame_key }); - } - }, - { - accessorKey: "description_es", - header: m['common.col_desc_es'](), - cell: ({ row }) => { - const descSnippet = createRawSnippet<[{ description: string }]>((getDesc) => { - const { description } = getDesc(); - return { - render: () => `
${description}
` - }; - }); - return renderSnippet(descSnippet, { description: row.original.description_es }); - } - }, - { - accessorKey: "description_en", - header: m['common.col_desc_en'](), - cell: ({ row }) => { - const descSnippet = createRawSnippet<[{ description: string }]>((getDesc) => { - const { description } = getDesc(); - return { - render: () => `
${description}
` - }; - }); - return renderSnippet(descSnippet, { description: row.original.description_en }); - } - }, - { - id: "actions", - cell: ({ row }) => { - return renderComponent(DataTableActions, { - item: row.original, - onSuccess, - canEdit: permissions.canEdit, - canDelete: permissions.canDelete - }); - } - } - ]; -} - -// Legacy export removed diff --git a/frontend/src/lib/components/dashboard/reference_data/countries/create-edit-dialog.svelte b/frontend/src/lib/components/dashboard/reference_data/countries/create-edit-dialog.svelte deleted file mode 100644 index 98d141b8..00000000 --- a/frontend/src/lib/components/dashboard/reference_data/countries/create-edit-dialog.svelte +++ /dev/null @@ -1,239 +0,0 @@ - - - - - - - {isEditing ? "Editar" : "Nuevo"} País - - - {isEditing - ? "Modifica los datos del país." - : "Completa los datos para crear un nuevo país."} - - - -
- {#if error} -
- {error} -
- {/if} - -
-
- - -
- -
- - -
- -
- - -
-
- -
- - -
- -
- - -
- - - - - -
-
-
diff --git a/frontend/src/lib/components/dashboard/reference_data/countries/data-table-actions.svelte b/frontend/src/lib/components/dashboard/reference_data/countries/data-table-actions.svelte deleted file mode 100644 index 42759622..00000000 --- a/frontend/src/lib/components/dashboard/reference_data/countries/data-table-actions.svelte +++ /dev/null @@ -1,74 +0,0 @@ - - - - - {#snippet child({ props })} - - {/snippet} - - - - Acciones - - - Copiar Clave M3 - - (showDetailsDialog = true)}> - - Ver detalles - - - - {#if canEdit || canDelete} - - - {#if canEdit} - (alert('Módulo de edición no disponible para Catálogos Públicos'))}> - - Editar - - {/if} - {#if canDelete} - (alert('Módulo de eliminación no disponible para Catálogos Públicos'))} class="text-destructive"> - - Eliminar - - {/if} - - {/if} - - - - - diff --git a/frontend/src/lib/components/dashboard/reference_data/countries/data-table.svelte b/frontend/src/lib/components/dashboard/reference_data/countries/data-table.svelte deleted file mode 100644 index 261494f0..00000000 --- a/frontend/src/lib/components/dashboard/reference_data/countries/data-table.svelte +++ /dev/null @@ -1,127 +0,0 @@ - - -
-
- - - {#each table.getHeaderGroups() as headerGroup (headerGroup.id)} - - {#each headerGroup.headers as header (header.id)} - - {/each} - - {/each} - - - {#each table.getRowModel().rows as row (row.id)} - - {#each row.getVisibleCells() as cell (cell.id)} - - {/each} - - {:else} - - - - {/each} - - - {#if hasMore} - - - - {/if} - -
- {#if !header.isPlaceholder} - - {/if} -
- -
- No hay resultados. -
-
- {#if loading} -
-
- Cargando más registros... -
- {:else} -
- - Desplázate para cargar más - -
- {/if} -
-
-
-
diff --git a/frontend/src/lib/components/dashboard/reference_data/countries/delete-dialog.svelte b/frontend/src/lib/components/dashboard/reference_data/countries/delete-dialog.svelte deleted file mode 100644 index 2f28142c..00000000 --- a/frontend/src/lib/components/dashboard/reference_data/countries/delete-dialog.svelte +++ /dev/null @@ -1,116 +0,0 @@ - - - - - - ¿Estás seguro? - -

Esta acción no se puede deshacer. Se eliminará permanentemente este país:

- {#if item} -
-
- Clave M3: - {item.m3_key} -
-
- Clave MX: - {item.mex_key} -
-
- Descripción: - {item.description_es} -
-
- {/if} - {#if error} -
- {error} -
- {/if} -
-
- - Cancelar - - {#if loading} - - - - - {/if} - Eliminar - - -
-
diff --git a/frontend/src/lib/components/dashboard/reference_data/countries/details-dialog.svelte b/frontend/src/lib/components/dashboard/reference_data/countries/details-dialog.svelte deleted file mode 100644 index a5e24653..00000000 --- a/frontend/src/lib/components/dashboard/reference_data/countries/details-dialog.svelte +++ /dev/null @@ -1,79 +0,0 @@ - - - - - - Detalles del País - - Información completa del país - - - - {#if item} -
-
-
- Clave M3 - - {item.m3_key} - -
- -
- Clave MX - - {item.mex_key} - -
- -
- Clave AME - - {item.ame_key} - -
-
- - -
-
- Descripción en Español -

{item.description_es}

-
- -
- -
-
- Descripción en Inglés -

{item.description_en}

-
- -
-
- {/if} - - - - -
-
diff --git a/frontend/src/lib/components/dashboard/reference_data/currency_types/columns.ts b/frontend/src/lib/components/dashboard/reference_data/currency_types/columns.ts deleted file mode 100644 index 3ae2f482..00000000 --- a/frontend/src/lib/components/dashboard/reference_data/currency_types/columns.ts +++ /dev/null @@ -1,72 +0,0 @@ -import type { ColumnDef } from "@tanstack/table-core"; -import { m } from '$lib/i18n/messages'; -import { renderComponent, renderSnippet } from "$lib/components/ui/data-table/index.js"; -import { createRawSnippet } from "svelte"; -import DataTableActions from "./data-table-actions.svelte"; - -export type CurrencyType = { - code: string; - currency_name: string; - country_description: string; -}; - -export function createColumns( - onSuccess: () => void, - permissions: { canEdit: boolean; canDelete: boolean } = { canEdit: true, canDelete: true } -): ColumnDef[] { - return [ - { - accessorKey: "code", - header: m['common.col_code'](), - cell: ({ row }) => { - const codeSnippet = createRawSnippet<[{ code: string }]>((getCode) => { - const { code } = getCode(); - return { - render: () => - `${code}` - }; - }); - return renderSnippet(codeSnippet, { code: row.original.code }); - } - }, - { - accessorKey: "currency_name", - header: m['common.col_currency_name'](), - cell: ({ row }) => { - const nameSnippet = createRawSnippet<[{ name: string }]>((getName) => { - const { name } = getName(); - return { - render: () => `
${name}
` - }; - }); - return renderSnippet(nameSnippet, { name: row.original.currency_name }); - } - }, - { - accessorKey: "country_description", - header: m['common.col_country_desc'](), - cell: ({ row }) => { - const descSnippet = createRawSnippet<[{ description: string }]>((getDesc) => { - const { description } = getDesc(); - return { - render: () => `
${description}
` - }; - }); - return renderSnippet(descSnippet, { description: row.original.country_description }); - } - }, - { - id: "actions", - cell: ({ row }) => { - return renderComponent(DataTableActions, { - item: row.original, - onSuccess, - canEdit: permissions.canEdit, - canDelete: permissions.canDelete - }); - } - } - ]; -} - -// Legacy export removed diff --git a/frontend/src/lib/components/dashboard/reference_data/currency_types/create-edit-dialog.svelte b/frontend/src/lib/components/dashboard/reference_data/currency_types/create-edit-dialog.svelte deleted file mode 100644 index c7eebfad..00000000 --- a/frontend/src/lib/components/dashboard/reference_data/currency_types/create-edit-dialog.svelte +++ /dev/null @@ -1,206 +0,0 @@ - - - - - - - {isEditing ? "Editar" : "Nuevo"} Tipo de Moneda - - - {isEditing - ? "Modifica los datos del tipo de moneda." - : "Completa los datos para crear un nuevo tipo de moneda."} - - - -
- {#if error} -
- {error} -
- {/if} - -
- - -

Código ISO de 3 caracteres

-
- -
- - -
- -
- - -
- - -
- {/if} -
-
-
- + -
-{/if} - - diff --git a/frontend/src/routes/api-sveltekit/classes/+server.ts b/frontend/src/routes/api-sveltekit/classes/+server.ts deleted file mode 100644 index c7df9b71..00000000 --- a/frontend/src/routes/api-sveltekit/classes/+server.ts +++ /dev/null @@ -1,91 +0,0 @@ -import type { RequestHandler } from './$types'; -import { getAccessTokenFromCookies } from '$lib/server/access-token-cookie'; - -export const GET: RequestHandler = async ({ cookies, url }) => { - const token = getAccessTokenFromCookies(cookies); - - // Obtener company_id de la cookie - const companyId = cookies.get('active_company_id'); - - if (!companyId) { - return new Response( - JSON.stringify({ error: 'No company selected' }), - { - status: 400, - headers: { - 'Content-Type': 'application/json' - } - } - ); - } - - // Configurar la URL de la API usando las variables de entorno - let apiUrl = process.env.INTERNAL_API_URL; - if (!apiUrl) { - apiUrl = process.env.VITE_API_URL; - // Reemplazar 'localhost' con 'backend' para llamadas desde el servidor (SSR) - apiUrl = apiUrl?.replace('localhost', 'backend').replace('127.0.0.1', 'backend'); - } - - // Normalizar la URL - const baseUrl = apiUrl?.endsWith('/') ? apiUrl : `${apiUrl}/`; - - // Get query parameters and add company_id - const searchParams = new URLSearchParams(url.search); - searchParams.set('company_id', companyId); - const queryString = searchParams.toString(); - - try { - const fetchUrl = `${baseUrl}v1/a76/classes?${queryString}`; - - const response = await fetch( - fetchUrl, - { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${token}` - } - } - ); - - if (!response.ok) { - const errorText = await response.text(); - console.error('Error response from backend:', errorText); - return new Response( - JSON.stringify({ - error: 'Failed to fetch classes', - details: errorText - }), - { - status: response.status, - headers: { - 'Content-Type': 'application/json' - } - } - ); - } - - const data = await response.json(); - return new Response(JSON.stringify(data), { - status: 200, - headers: { - 'Content-Type': 'application/json' - } - }); - } catch (error) { - console.error('Error in classes API route:', error); - return new Response( - JSON.stringify({ - error: 'Internal server error', - message: error instanceof Error ? error.message : 'Unknown error' - }), - { - status: 500, - headers: { - 'Content-Type': 'application/json' - } - } - ); - } -}; diff --git a/frontend/src/routes/api-sveltekit/classes/[id]/+server.ts b/frontend/src/routes/api-sveltekit/classes/[id]/+server.ts deleted file mode 100644 index b96b5e55..00000000 --- a/frontend/src/routes/api-sveltekit/classes/[id]/+server.ts +++ /dev/null @@ -1,87 +0,0 @@ -import type { RequestHandler } from './$types'; -import { getAccessTokenFromCookies } from '$lib/server/access-token-cookie'; - -export const GET: RequestHandler = async ({ cookies, url, params }) => { - const token = getAccessTokenFromCookies(cookies); - const { id } = params; - - // Obtener company_id de la cookie o query params - const companyId = cookies.get('active_company_id') || url.searchParams.get('company_id'); - - if (!companyId) { - return new Response( - JSON.stringify({ error: 'No company selected' }), - { - status: 400, - headers: { - 'Content-Type': 'application/json' - } - } - ); - } - - // Configurar la URL de la API usando las variables de entorno - let apiUrl = process.env.INTERNAL_API_URL; - if (!apiUrl) { - apiUrl = process.env.VITE_API_URL; - // Reemplazar 'localhost' con 'backend' para llamadas desde el servidor (SSR) - apiUrl = apiUrl?.replace('localhost', 'backend').replace('127.0.0.1', 'backend'); - } - - // Normalizar la URL - const baseUrl = apiUrl?.endsWith('/') ? apiUrl : `${apiUrl}/`; - - try { - const fetchUrl = `${baseUrl}v1/a76/classes/${id}?company_id=${companyId}`; - - const response = await fetch( - fetchUrl, - { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${token}` - } - } - ); - - if (!response.ok) { - const errorText = await response.text(); - console.error('Error response from backend:', errorText); - return new Response( - JSON.stringify({ - error: 'Failed to fetch class', - details: errorText - }), - { - status: response.status, - headers: { - 'Content-Type': 'application/json' - } - } - ); - } - - const data = await response.json(); - return new Response(JSON.stringify(data), { - status: 200, - headers: { - 'Content-Type': 'application/json' - } - }); - } catch (error) { - console.error('Error in class API route:', error); - return new Response( - JSON.stringify({ - error: 'Internal server error', - message: error instanceof Error ? error.message : 'Unknown error' - }), - { - status: 500, - headers: { - 'Content-Type': 'application/json' - } - } - ); - } -}; diff --git a/frontend/src/routes/api-sveltekit/company/my-companies/+server.ts b/frontend/src/routes/api-sveltekit/company/my-companies/+server.ts index ec834697..24fe4f36 100644 --- a/frontend/src/routes/api-sveltekit/company/my-companies/+server.ts +++ b/frontend/src/routes/api-sveltekit/company/my-companies/+server.ts @@ -26,7 +26,7 @@ export const GET: RequestHandler = async ({ cookies, fetch }) => { const baseUrl = apiUrl?.endsWith('/') ? apiUrl : `${apiUrl}/`; try { - const response = await fetch(`${baseUrl}v1/a76/company/my-companies`, { + const response = await fetch(`${baseUrl}v1/auth/my-companies`, { headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' diff --git a/frontend/src/routes/api-sveltekit/countries/+server.ts b/frontend/src/routes/api-sveltekit/countries/+server.ts deleted file mode 100644 index 83954ed0..00000000 --- a/frontend/src/routes/api-sveltekit/countries/+server.ts +++ /dev/null @@ -1,60 +0,0 @@ -import { json } from '@sveltejs/kit'; -import type { RequestHandler } from './$types'; - -export const GET: RequestHandler = async ({ fetch, url }) => { - // Obtener company_id de los parámetros de búsqueda - const company_id = url.searchParams.get('company_id'); - - if (!company_id) { - return json({ error: "El campo 'company_id' es obligatorio." }, { status: 400 }); - } - - // Configurar la URL de la API usando las variables de entorno - let apiUrl = process.env.INTERNAL_API_URL; - if (!apiUrl) { - apiUrl = process.env.VITE_API_URL; - // Reemplazar 'localhost' con 'backend' para llamadas desde el servidor (SSR) - apiUrl = apiUrl?.replace('localhost', 'backend').replace('127.0.0.1', 'backend'); - } - - // Normalizar la URL - const baseUrl = apiUrl?.endsWith('/') ? apiUrl : `${apiUrl}/`; - - try { - // Endpoint de países - const allCountries: any[] = []; - let page = 1; - let hasMore = true; - - while (hasMore) { - const response = await fetch( - `${baseUrl}v1/public/reference_data/countries/?company_id=${company_id}&page=${page}&page_size=100`, - { - headers: { - 'Content-Type': 'application/json' - } - } - ); - - if (!response.ok) { - console.error('Error fetching countries:', response.status, response.statusText); - const errorText = await response.text(); - console.error('Error response:', errorText); - return json({ error: 'Failed to fetch countries' }, { status: response.status }); - } - - const data = await response.json(); - allCountries.push(...(data.items || [])); - - // Si hay menos items que el page_size, es la última página - hasMore = data.items && data.items.length === 100; - page++; - } - - return json(allCountries); - } catch (error) { - console.error('Error fetching countries:', error); - return json({ error: 'Internal server error' }, { status: 500 }); - } -}; - diff --git a/frontend/src/routes/api-sveltekit/fda-catalog/+server.ts b/frontend/src/routes/api-sveltekit/fda-catalog/+server.ts deleted file mode 100644 index 5b3df145..00000000 --- a/frontend/src/routes/api-sveltekit/fda-catalog/+server.ts +++ /dev/null @@ -1,283 +0,0 @@ -import { json } from '@sveltejs/kit'; -import type { RequestHandler } from './$types'; -import { getAuthTokens, authenticatedFetch } from '$lib/server/api'; -import * as fs from 'fs'; - -// Helper to log to a file for debugging -const debugLog = (msg: string) => { - try { - const timestamp = new Date().toISOString(); - fs.appendFileSync('/tmp/fda_proxy_debug.log', `[${timestamp}] ${msg}\n`); - } catch (e) { } -}; - -// Precise cleaners to ensure only business data is sent to backend -const cleanFDA01 = (d: any) => { - if (!d) return null; - return { - prod_code: d.prod_code || null, - commodity_desc: d.commodity_desc || null, - brand_name: d.brand_name || null, - disclaimer: d.disclaimer || null, - pgm_code: d.pgm_code || null, - proc_code: d.proc_code || null, - intnd_use_code: d.intnd_use_code || null, - intnd_use_desc: d.intnd_use_desc || null, - temp_qual: d.temp_qual || null, - temp_type: d.temp_type || null, - temp_degrees: d.temp_degrees || null, - temp_negative: d.temp_negative || null, - temp_location: d.temp_location || null, - quantity_1: d.quantity_1 || null, - qty_uom_1: d.qty_uom_1 || null, - quantity_2: d.quantity_2 || null, - qty_uom_2: d.qty_uom_2 || null, - quantity_3: d.quantity_3 || null, - qty_uom_3: d.qty_uom_3 || null, - ctry_prod: d.ctry_prod || null, - ctry_source: d.ctry_source || null, - ctry_growth: d.ctry_growth || null, - ctry_refusal: d.ctry_refusal || null, - ctry_shipping: d.ctry_shipping || null, - manuf_key: d.manuf_key || null, - shipper_key: d.shipper_key || null, - ult_cons_key: d.ult_cons_key || null, - fda_imp_key: d.fda_imp_key || null, - pn_subm_key: d.pn_subm_key || null, - consol_key: d.consol_key || null, - producer_key: d.producer_key || null, - owner_key: d.owner_key || null, - deli_party_key: d.deli_party_key || null, - grower_key: d.grower_key || null, - dev_ini_imp_key: d.dev_ini_imp_key || null, - lacf_cont_1: d.lacf_cont_1 || null, - lacf_cont_2: d.lacf_cont_2 || null, - lacf_cont_3: d.lacf_cont_3 || null, - pn_transmitter_key: d.pn_transmitter_key || null - }; -}; - -const cleanFDA23 = (d: any) => ({ - line: typeof d.line === 'number' ? d.line : (parseInt(d.line) || 1), - aoc_code: d.aoc_code || '', - aoc_qual: d.aoc_qual || null -}); - -const cleanFDA04 = (d: any) => ({ - line: typeof d.line === 'number' ? d.line : (parseInt(d.line) || 1), - ele_name: d.ele_name || '', - ele_qty: (d.ele_qty === '' || d.ele_qty === null || d.ele_qty === undefined) ? null : parseFloat(d.ele_qty), - ele_qty_uom: d.ele_qty_uom || null, - ele_pctg: (d.ele_pctg === '' || d.ele_pctg === null || d.ele_pctg === undefined) ? null : parseFloat(d.ele_pctg) -}); - -const cleanFDA25 = (d: any) => ({ - line: typeof d.line === 'number' ? d.line : (parseInt(d.line) || 1), - lot_number: d.lot_number || '', - production_start_date: d.production_start_date || null, - production_end_date: d.production_end_date || null -}); - -async function saveNestedData(id: string, companyId: string, nested: any, cookies: any, fetch: any) { - debugLog(`saveNestedData started for id: ${id}`); - const { fda01, fda23_codes, fda04_elements, fda25_lots } = nested; - - // 1. FDA01 Specifications - if (fda01) { - debugLog(`Saving FDA01`); - const res = await authenticatedFetch( - `v1/a76/fda-catalog/${id}/specifications?company_id=${companyId}`, - { method: 'PUT', body: JSON.stringify(cleanFDA01(fda01)) }, - cookies, fetch - ); - if (!res.ok) { - const txt = await res.text(); - debugLog(`Error FDA01: ${res.status} - ${txt}`); - throw new Error(`Error en FDA01: ${res.status} - ${txt}`); - } - } - - // 2. FDA23 Affirmation Codes - debugLog(`Processing FDA23`); - const resExist23 = await authenticatedFetch(`v1/a76/fda-catalog/${id}/affirmation-codes?company_id=${companyId}`, { method: 'GET' }, cookies, fetch); - if (resExist23.ok) { - const items = await resExist23.json(); - debugLog(`Deleting ${items.length} existing FDA23 items`); - for (const item of items) { - await authenticatedFetch(`v1/a76/fda-catalog/${id}/affirmation-codes/${item.id}?company_id=${companyId}`, { method: 'DELETE' }, cookies, fetch); - } - } - if (fda23_codes && Array.isArray(fda23_codes)) { - for (const code of fda23_codes) { - if (!code.aoc_code) continue; - const res = await authenticatedFetch(`v1/a76/fda-catalog/${id}/affirmation-codes?company_id=${companyId}`, { method: 'POST', body: JSON.stringify(cleanFDA23(code)) }, cookies, fetch); - if (!res.ok) { - const txt = await res.text(); - debugLog(`Error FDA23: ${res.status} - ${txt}`); - throw new Error(`Error en FDA23: ${res.status} - ${txt}`); - } - } - } - - // 3. FDA04 Constituent Elements - debugLog(`Processing FDA04`); - const resExist04 = await authenticatedFetch(`v1/a76/fda-catalog/${id}/constituent-elements?company_id=${companyId}`, { method: 'GET' }, cookies, fetch); - if (resExist04.ok) { - const items = await resExist04.json(); - debugLog(`Deleting ${items.length} existing FDA04 items`); - for (const item of items) { - await authenticatedFetch(`v1/a76/fda-catalog/${id}/constituent-elements/${item.id}?company_id=${companyId}`, { method: 'DELETE' }, cookies, fetch); - } - } - if (fda04_elements && Array.isArray(fda04_elements)) { - for (const ele of fda04_elements) { - if (!ele.ele_name) continue; - debugLog(`Saving FDA04 item: ${ele.ele_name}`); - const res = await authenticatedFetch(`v1/a76/fda-catalog/${id}/constituent-elements?company_id=${companyId}`, { method: 'POST', body: JSON.stringify(cleanFDA04(ele)) }, cookies, fetch); - if (!res.ok) { - const txt = await res.text(); - debugLog(`Error FDA04: ${res.status} - ${txt}`); - throw new Error(`Error en FDA04: ${res.status} - ${txt}`); - } - } - } - - // 4. FDA25 Lot Productions - debugLog(`Processing FDA25`); - const resExist25 = await authenticatedFetch(`v1/a76/fda-catalog/${id}/lot-productions?company_id=${companyId}`, { method: 'GET' }, cookies, fetch); - if (resExist25.ok) { - const items = await resExist25.json(); - debugLog(`Deleting ${items.length} existing FDA25 items`); - for (const item of items) { - await authenticatedFetch(`v1/a76/fda-catalog/${id}/lot-productions/${item.id}?company_id=${companyId}`, { method: 'DELETE' }, cookies, fetch); - } - } - if (fda25_lots && Array.isArray(fda25_lots)) { - for (const lot of fda25_lots) { - if (!lot.lot_number) continue; - const res = await authenticatedFetch(`v1/a76/fda-catalog/${id}/lot-productions?company_id=${companyId}`, { method: 'POST', body: JSON.stringify(cleanFDA25(lot)) }, cookies, fetch); - if (!res.ok) { - const txt = await res.text(); - debugLog(`Error FDA25: ${res.status} - ${txt}`); - throw new Error(`Error en FDA25: ${res.status} - ${txt}`); - } - } - } - debugLog(`saveNestedData finished successfully`); -} - -export const GET: RequestHandler = async ({ url, cookies, fetch }) => { - const companyId = url.searchParams.get('company_id') || cookies.get('active_company_id'); - const skip = url.searchParams.get('skip') || '0'; - const limit = url.searchParams.get('limit') || '50'; - const search = url.searchParams.get('search') || ''; - - if (!companyId) return json({ error: 'Compañía no seleccionada' }, { status: 400 }); - - try { - let apiUrl = `v1/a76/fda-catalog/?company_id=${companyId}&skip=${skip}&limit=${limit}`; - if (search) apiUrl += `&search=${encodeURIComponent(search)}`; - - const response = await authenticatedFetch(apiUrl, { method: 'GET' }, cookies, fetch); - if (!response.ok) return json({ error: 'Error al cargar registros FDA' }, { status: response.status }); - - const data = await response.json(); - return json(data); - } catch (error) { - return json({ error: 'Error de conexión' }, { status: 500 }); - } -}; - -export const POST: RequestHandler = async ({ request, cookies, fetch }) => { - debugLog(`POST started`); - const companyId = cookies.get('active_company_id'); - if (!companyId) return json({ error: 'Compañía no seleccionada' }, { status: 400 }); - - try { - const body = await request.json(); - const mainRecord = { - fda_key: body.fda_key, - description: body.description, - fda_code: body.fda_code || null, - requirements: body.requirements || null, - manufacturer_number: body.manufacturer_number || null, - country_of_production: body.country_of_production || null, - storage_status: body.storage_status || null, - warehouse_code: body.warehouse_code || null, - call_atl: body.call_atl || null - }; - - debugLog(`Creating main record`); - const res = await authenticatedFetch(`v1/a76/fda-catalog/?company_id=${companyId}`, { method: 'POST', body: JSON.stringify(mainRecord) }, cookies, fetch); - const data = await res.json(); - if (!res.ok) { - debugLog(`Error creating main record: ${res.status}`); - return json({ error: data.detail || 'Error al crear FDA' }, { status: res.status }); - } - - await saveNestedData(data.id.toString(), companyId, body, cookies, fetch); - debugLog(`POST finished successfully`); - return json(data); - } catch (error: any) { - debugLog(`POST Crash: ${error.message}`); - console.error('POST Error:', error); - return json({ error: error.message || 'Error al procesar el guardado' }, { status: 500 }); - } -}; - -export const PUT: RequestHandler = async ({ request, url, cookies, fetch }) => { - debugLog(`PUT started`); - const id = url.searchParams.get('id'); - const companyId = url.searchParams.get('company_id') || cookies.get('active_company_id'); - - debugLog(`PUT params: id=${id}, companyId=${companyId}`); - if (!id || !companyId) return json({ error: 'ID o compañía no proporcionada' }, { status: 400 }); - - try { - const body = await request.json(); - const mainRecord = { - fda_key: body.fda_key, - description: body.description, - fda_code: body.fda_code || null, - requirements: body.requirements || null, - manufacturer_number: body.manufacturer_number || null, - country_of_production: body.country_of_production || null, - storage_status: body.storage_status || null, - warehouse_code: body.warehouse_code || null, - call_atl: body.call_atl || null - }; - - debugLog(`Updating main record id=${id}`); - const res = await authenticatedFetch(`v1/a76/fda-catalog/${id}?company_id=${companyId}`, { method: 'PUT', body: JSON.stringify(mainRecord) }, cookies, fetch); - const data = await res.json(); - if (!res.ok) { - debugLog(`Error updating main record: ${res.status}`); - return json({ error: data.detail || 'Error al actualizar FDA' }, { status: res.status }); - } - - await saveNestedData(id, companyId, body, cookies, fetch); - debugLog(`PUT finished successfully`); - return json(data); - } catch (error: any) { - debugLog(`PUT Crash: ${error.message}`); - console.error('PUT Error:', error); - return json({ error: error.message || 'Error al procesar la actualización' }, { status: 500 }); - } -}; - -export const DELETE: RequestHandler = async ({ url, cookies, fetch }) => { - const id = url.searchParams.get('id'); - const companyId = url.searchParams.get('company_id') || cookies.get('active_company_id'); - if (!id || !companyId) return json({ error: 'ID o compañía no proporcionada' }, { status: 400 }); - - try { - const res = await authenticatedFetch(`v1/a76/fda-catalog/${id}?company_id=${companyId}`, { method: 'DELETE' }, cookies, fetch); - if (!res.ok) { - const data = await res.json(); - return json({ error: data.detail || 'Error al eliminar FDA' }, { status: res.status }); - } - return json({ success: true }); - } catch (error) { - return json({ error: 'Error de conexión' }, { status: 500 }); - } -}; diff --git a/frontend/src/routes/api-sveltekit/fda-catalog/[id]/+server.ts b/frontend/src/routes/api-sveltekit/fda-catalog/[id]/+server.ts deleted file mode 100644 index 1c904105..00000000 --- a/frontend/src/routes/api-sveltekit/fda-catalog/[id]/+server.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { json } from '@sveltejs/kit'; -import type { RequestHandler } from './$types'; -import { getAuthTokens, authenticatedFetch } from '$lib/server/api'; - -export const GET: RequestHandler = async ({ params, url, cookies, fetch }) => { - const { accessToken } = getAuthTokens(cookies); - if (!accessToken) return json({ error: 'No autorizado' }, { status: 401 }); - - const companyId = url.searchParams.get('company_id') || cookies.get('active_company_id'); - const id = params.id; - - if (!companyId) return json({ error: 'Compañía no seleccionada' }, { status: 400 }); - - try { - const response = await authenticatedFetch( - `v1/a76/fda-catalog/${id}?company_id=${companyId}`, - { method: 'GET', cache: 'no-store' }, - cookies, fetch - ); - - if (!response.ok) { - return json({ error: 'Error al cargar registro FDA' }, { status: response.status }); - } - - const data = await response.json(); - return json(data); - } catch (error) { - console.error('Error in fda-catalog detail API:', error); - return json({ error: 'Error de conexión' }, { status: 500 }); - } -}; diff --git a/frontend/src/routes/api-sveltekit/fda-catalog/[id]/affirmation-codes/+server.ts b/frontend/src/routes/api-sveltekit/fda-catalog/[id]/affirmation-codes/+server.ts deleted file mode 100644 index 21e9777b..00000000 --- a/frontend/src/routes/api-sveltekit/fda-catalog/[id]/affirmation-codes/+server.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { json } from '@sveltejs/kit'; -import type { RequestHandler } from './$types'; -import { getAuthTokens, authenticatedFetch } from '$lib/server/api'; - -export const GET: RequestHandler = async ({ params, url, cookies, fetch }) => { - const { accessToken } = getAuthTokens(cookies); - if (!accessToken) return json({ error: 'No autorizado' }, { status: 401 }); - - const companyId = url.searchParams.get('company_id') || cookies.get('active_company_id'); - const id = params.id; - - if (!companyId) return json({ error: 'Compañía no seleccionada' }, { status: 400 }); - - try { - const response = await authenticatedFetch( - `v1/a76/fda-catalog/${id}/affirmation-codes?company_id=${companyId}`, - { method: 'GET', cache: 'no-store' }, - cookies, fetch - ); - - if (!response.ok) { - return json({ error: 'Error al cargar affirmation codes' }, { status: response.status }); - } - - const data = await response.json(); - return json(data); - } catch (error) { - console.error('Error in affirmation-codes API:', error); - return json({ error: 'Error de conexión' }, { status: 500 }); - } -}; diff --git a/frontend/src/routes/api-sveltekit/fda-catalog/[id]/constituent-elements/+server.ts b/frontend/src/routes/api-sveltekit/fda-catalog/[id]/constituent-elements/+server.ts deleted file mode 100644 index b2944e68..00000000 --- a/frontend/src/routes/api-sveltekit/fda-catalog/[id]/constituent-elements/+server.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { json } from '@sveltejs/kit'; -import type { RequestHandler } from './$types'; -import { getAuthTokens, authenticatedFetch } from '$lib/server/api'; - -export const GET: RequestHandler = async ({ params, url, cookies, fetch }) => { - const { accessToken } = getAuthTokens(cookies); - if (!accessToken) return json({ error: 'No autorizado' }, { status: 401 }); - - const companyId = url.searchParams.get('company_id') || cookies.get('active_company_id'); - const id = params.id; - - if (!companyId) return json({ error: 'Compañía no seleccionada' }, { status: 400 }); - - try { - const response = await authenticatedFetch( - `v1/a76/fda-catalog/${id}/constituent-elements?company_id=${companyId}`, - { method: 'GET', cache: 'no-store' }, - cookies, fetch - ); - - if (!response.ok) { - return json({ error: 'Error al cargar elementos constitutivos' }, { status: response.status }); - } - - const data = await response.json(); - return json(data); - } catch (error) { - console.error('Error in constituent-elements API:', error); - return json({ error: 'Error de conexión' }, { status: 500 }); - } -}; diff --git a/frontend/src/routes/api-sveltekit/fda-catalog/[id]/lot-productions/+server.ts b/frontend/src/routes/api-sveltekit/fda-catalog/[id]/lot-productions/+server.ts deleted file mode 100644 index c9ed9e69..00000000 --- a/frontend/src/routes/api-sveltekit/fda-catalog/[id]/lot-productions/+server.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { json } from '@sveltejs/kit'; -import type { RequestHandler } from './$types'; -import { getAuthTokens, authenticatedFetch } from '$lib/server/api'; - -export const GET: RequestHandler = async ({ params, url, cookies, fetch }) => { - const { accessToken } = getAuthTokens(cookies); - if (!accessToken) return json({ error: 'No autorizado' }, { status: 401 }); - - const companyId = url.searchParams.get('company_id') || cookies.get('active_company_id'); - const id = params.id; - - if (!companyId) return json({ error: 'Compañía no seleccionada' }, { status: 400 }); - - try { - const response = await authenticatedFetch( - `v1/a76/fda-catalog/${id}/lot-productions?company_id=${companyId}`, - { method: 'GET', cache: 'no-store' }, - cookies, fetch - ); - - if (!response.ok) { - return json({ error: 'Error al cargar lot productions' }, { status: response.status }); - } - - const data = await response.json(); - return json(data); - } catch (error) { - console.error('Error in lot-productions API:', error); - return json({ error: 'Error de conexión' }, { status: 500 }); - } -}; diff --git a/frontend/src/routes/api-sveltekit/fda-catalog/[id]/specifications/+server.ts b/frontend/src/routes/api-sveltekit/fda-catalog/[id]/specifications/+server.ts deleted file mode 100644 index e80c4d6c..00000000 --- a/frontend/src/routes/api-sveltekit/fda-catalog/[id]/specifications/+server.ts +++ /dev/null @@ -1,31 +0,0 @@ -import { json } from '@sveltejs/kit'; -import type { RequestHandler } from './$types'; -import { getAuthTokens, authenticatedFetch } from '$lib/server/api'; - -export const GET: RequestHandler = async ({ params, url, cookies, fetch }) => { - const { accessToken } = getAuthTokens(cookies); - if (!accessToken) return json({ error: 'No autorizado' }, { status: 401 }); - - const companyId = url.searchParams.get('company_id') || cookies.get('active_company_id'); - const id = params.id; - - if (!companyId) return json({ error: 'Compañía no seleccionada' }, { status: 400 }); - - try { - const response = await authenticatedFetch( - `v1/a76/fda-catalog/${id}/specifications?company_id=${companyId}`, - { method: 'GET', cache: 'no-store' }, - cookies, fetch - ); - - if (!response.ok) { - return json({ error: 'Error al cargar especificaciones' }, { status: response.status }); - } - - const data = await response.json(); - return json(data); - } catch (error) { - console.error('Error in specifications API:', error); - return json({ error: 'Error de conexión' }, { status: 500 }); - } -}; diff --git a/frontend/src/routes/api-sveltekit/historical-tariff-fractions/rate/+server.ts b/frontend/src/routes/api-sveltekit/historical-tariff-fractions/rate/+server.ts deleted file mode 100644 index c8c00757..00000000 --- a/frontend/src/routes/api-sveltekit/historical-tariff-fractions/rate/+server.ts +++ /dev/null @@ -1,72 +0,0 @@ -import type { RequestHandler } from './$types'; -import { getAccessTokenFromCookies } from '$lib/server/access-token-cookie'; - -export const GET: RequestHandler = async ({ cookies, url }) => { - const token = getAccessTokenFromCookies(cookies); - - // Configurar la URL de la API usando las variables de entorno - let apiUrl = process.env.INTERNAL_API_URL; - if (!apiUrl) { - apiUrl = process.env.VITE_API_URL; - // Reemplazar 'localhost' con 'backend' para llamadas desde el servidor (SSR) - apiUrl = apiUrl?.replace('localhost', 'backend').replace('127.0.0.1', 'backend'); - } - - // Normalizar la URL - const baseUrl = apiUrl?.endsWith('/') ? apiUrl : `${apiUrl}/`; - - // Get query parameters (historical_fraction, country, fraction_type, invoice_date, sector) - const searchParams = new URLSearchParams(url.search); - const queryString = searchParams.toString(); - - try { - const fetchUrl = `${baseUrl}v1/a76/fractions/historical-tariff-fractions/rate?${queryString}`; - - const response = await fetch(fetchUrl, { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${token}` - } - }); - - if (!response.ok) { - const errorText = await response.text(); - console.error('Error response from backend:', errorText); - return new Response( - JSON.stringify({ - error: 'Failed to fetch historical tariff rate', - details: errorText - }), - { - status: response.status, - headers: { - 'Content-Type': 'application/json' - } - } - ); - } - - const data = await response.json(); - return new Response(JSON.stringify(data), { - status: 200, - headers: { - 'Content-Type': 'application/json' - } - }); - } catch (error) { - console.error('Error in historical-tariff-fractions/rate API route:', error); - return new Response( - JSON.stringify({ - error: 'Internal server error', - message: error instanceof Error ? error.message : 'Unknown error' - }), - { - status: 500, - headers: { - 'Content-Type': 'application/json' - } - } - ); - } -}; diff --git a/frontend/src/routes/api-sveltekit/packages/+server.ts b/frontend/src/routes/api-sveltekit/packages/+server.ts deleted file mode 100644 index 2aedcbb5..00000000 --- a/frontend/src/routes/api-sveltekit/packages/+server.ts +++ /dev/null @@ -1,81 +0,0 @@ -import type { RequestHandler } from './$types'; -import { getAccessTokenFromCookies } from '$lib/server/access-token-cookie'; - -export const GET: RequestHandler = async ({ cookies, url }) => { - const token = getAccessTokenFromCookies(cookies); - - // Obtener company_id de la cookie - const companyId = cookies.get('active_company_id'); - - if (!companyId) { - return new Response( - JSON.stringify({ error: 'No company selected' }), - { - status: 400, - headers: { - 'Content-Type': 'application/json' - } - } - ); - } - - // Configurar la URL de la API usando las variables de entorno - let apiUrl = process.env.INTERNAL_API_URL; - if (!apiUrl) { - apiUrl = process.env.VITE_API_URL; - // Reemplazar 'localhost' con 'backend' para llamadas desde el servidor (SSR) - apiUrl = apiUrl?.replace('localhost', 'backend').replace('127.0.0.1', 'backend'); - } - - // Normalizar la URL - const baseUrl = apiUrl?.endsWith('/') ? apiUrl : `${apiUrl}/`; - - // Get query parameters and add company_id - const searchParams = new URLSearchParams(url.search); - searchParams.set('company_id', companyId); - const queryString = searchParams.toString(); - - try { - const fetchUrl = `${baseUrl}v1/a76/packages?${queryString}`; - - const response = await fetch( - fetchUrl, - { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${token}` - } - } - ); - - const data = await response.json(); - - if (!response.ok) { - return new Response(JSON.stringify(data), { - status: response.status, - headers: { - 'Content-Type': 'application/json' - } - }); - } - - return new Response(JSON.stringify(data), { - status: 200, - headers: { - 'Content-Type': 'application/json' - } - }); - } catch (error) { - console.error('Error fetching packages:', error); - return new Response( - JSON.stringify({ error: 'Failed to fetch packages' }), - { - status: 500, - headers: { - 'Content-Type': 'application/json' - } - } - ); - } -}; diff --git a/frontend/src/routes/api-sveltekit/parts/+server.ts b/frontend/src/routes/api-sveltekit/parts/+server.ts deleted file mode 100644 index 7a199c18..00000000 --- a/frontend/src/routes/api-sveltekit/parts/+server.ts +++ /dev/null @@ -1,93 +0,0 @@ -import type { RequestHandler } from './$types'; -import { getAccessTokenFromCookies } from '$lib/server/access-token-cookie'; - -export const GET: RequestHandler = async ({ cookies, url }) => { - const token = getAccessTokenFromCookies(cookies); - - // Obtener company_id de la cookie - const companyId = cookies.get('active_company_id'); - - if (!companyId) { - return new Response( - JSON.stringify({ error: 'No company selected' }), - { - status: 400, - headers: { - 'Content-Type': 'application/json' - } - } - ); - } - - // Configurar la URL de la API usando las variables de entorno - let apiUrl = process.env.INTERNAL_API_URL; - if (!apiUrl) { - apiUrl = process.env.VITE_API_URL; - // Reemplazar 'localhost' con 'backend' para llamadas desde el servidor (SSR) - apiUrl = apiUrl?.replace('localhost', 'backend').replace('127.0.0.1', 'backend'); - } - - // Normalizar la URL - const baseUrl = apiUrl?.endsWith('/') ? apiUrl : `${apiUrl}/`; - - // Get query parameters and add company_id - const searchParams = new URLSearchParams(url.search); - searchParams.set('company_id', companyId); - const queryString = searchParams.toString(); - - try { - const fetchUrl = `${baseUrl}v1/a76/parts?${queryString}`; - console.log(`[Parts API] Fetching from backend: ${fetchUrl}`); - console.log(`[Parts API] Token present: ${!!token}`); - - const response = await fetch( - fetchUrl, - { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${token}` - } - } - ); - - if (!response.ok) { - const errorText = await response.text(); - console.error('Error response from backend:', errorText); - return new Response( - JSON.stringify({ - error: 'Failed to fetch parts', - details: errorText - }), - { - status: response.status, - headers: { - 'Content-Type': 'application/json' - } - } - ); - } - - const data = await response.json(); - return new Response(JSON.stringify(data), { - status: 200, - headers: { - 'Content-Type': 'application/json' - } - }); - } catch (error) { - console.error('Error in parts API route:', error); - return new Response( - JSON.stringify({ - error: 'Internal server error', - message: error instanceof Error ? error.message : 'Unknown error' - }), - { - status: 500, - headers: { - 'Content-Type': 'application/json' - } - } - ); - } -}; diff --git a/frontend/src/routes/api-sveltekit/parts/[id]/+server.ts b/frontend/src/routes/api-sveltekit/parts/[id]/+server.ts deleted file mode 100644 index 121925f3..00000000 --- a/frontend/src/routes/api-sveltekit/parts/[id]/+server.ts +++ /dev/null @@ -1,87 +0,0 @@ -import type { RequestHandler } from './$types'; -import { getAccessTokenFromCookies } from '$lib/server/access-token-cookie'; - -export const GET: RequestHandler = async ({ cookies, url, params }) => { - const token = getAccessTokenFromCookies(cookies); - const { id } = params; - - // Obtener company_id de la cookie o query params - const companyId = cookies.get('active_company_id') || url.searchParams.get('company_id'); - - if (!companyId) { - return new Response( - JSON.stringify({ error: 'No company selected' }), - { - status: 400, - headers: { - 'Content-Type': 'application/json' - } - } - ); - } - - // Configurar la URL de la API usando las variables de entorno - let apiUrl = process.env.INTERNAL_API_URL; - if (!apiUrl) { - apiUrl = process.env.VITE_API_URL; - // Reemplazar 'localhost' con 'backend' para llamadas desde el servidor (SSR) - apiUrl = apiUrl?.replace('localhost', 'backend').replace('127.0.0.1', 'backend'); - } - - // Normalizar la URL - const baseUrl = apiUrl?.endsWith('/') ? apiUrl : `${apiUrl}/`; - - try { - const fetchUrl = `${baseUrl}v1/a76/parts/${id}?company_id=${companyId}`; - - const response = await fetch( - fetchUrl, - { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${token}` - } - } - ); - - if (!response.ok) { - const errorText = await response.text(); - console.error('Error response from backend:', errorText); - return new Response( - JSON.stringify({ - error: 'Failed to fetch part', - details: errorText - }), - { - status: response.status, - headers: { - 'Content-Type': 'application/json' - } - } - ); - } - - const data = await response.json(); - return new Response(JSON.stringify(data), { - status: 200, - headers: { - 'Content-Type': 'application/json' - } - }); - } catch (error) { - console.error('Error in part API route:', error); - return new Response( - JSON.stringify({ - error: 'Internal server error', - message: error instanceof Error ? error.message : 'Unknown error' - }), - { - status: 500, - headers: { - 'Content-Type': 'application/json' - } - } - ); - } -}; diff --git a/frontend/src/routes/api-sveltekit/payment-methods/+server.ts b/frontend/src/routes/api-sveltekit/payment-methods/+server.ts deleted file mode 100644 index 6352ec09..00000000 --- a/frontend/src/routes/api-sveltekit/payment-methods/+server.ts +++ /dev/null @@ -1,65 +0,0 @@ -import type { RequestHandler } from './$types'; -import { getAccessTokenFromCookies } from '$lib/server/access-token-cookie'; - -export const GET: RequestHandler = async ({ cookies, url }) => { - const token = getAccessTokenFromCookies(cookies); - - // Configurar la URL de la API usando las variables de entorno - let apiUrl = process.env.INTERNAL_API_URL; - if (!apiUrl) { - apiUrl = process.env.VITE_API_URL; - // Reemplazar 'localhost' con 'backend' para llamadas desde el servidor (SSR) - apiUrl = apiUrl?.replace('localhost', 'backend').replace('127.0.0.1', 'backend'); - } - - // Normalizar la URL - const baseUrl = apiUrl?.endsWith('/') ? apiUrl : `${apiUrl}/`; - - // Get query parameters - const searchParams = new URLSearchParams(url.search); - const queryString = searchParams.toString(); - - try { - const fetchUrl = `${baseUrl}v1/public/reference_data/payment-methods${queryString ? `?${queryString}` : ''}`; - - const response = await fetch( - fetchUrl, - { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${token}` - } - } - ); - - const data = await response.json(); - - if (!response.ok) { - return new Response(JSON.stringify(data), { - status: response.status, - headers: { - 'Content-Type': 'application/json' - } - }); - } - - return new Response(JSON.stringify(data), { - status: 200, - headers: { - 'Content-Type': 'application/json' - } - }); - } catch (error) { - console.error('Error fetching payment methods:', error); - return new Response( - JSON.stringify({ error: 'Failed to fetch payment methods' }), - { - status: 500, - headers: { - 'Content-Type': 'application/json' - } - } - ); - } -}; diff --git a/frontend/src/routes/api-sveltekit/tariff-fractions/+server.ts b/frontend/src/routes/api-sveltekit/tariff-fractions/+server.ts deleted file mode 100644 index b45505ac..00000000 --- a/frontend/src/routes/api-sveltekit/tariff-fractions/+server.ts +++ /dev/null @@ -1,65 +0,0 @@ -import type { RequestHandler } from './$types'; -import { getAccessTokenFromCookies } from '$lib/server/access-token-cookie'; - -export const GET: RequestHandler = async ({ cookies, url }) => { - const token = getAccessTokenFromCookies(cookies); - - // Configurar la URL de la API usando las variables de entorno - let apiUrl = process.env.INTERNAL_API_URL; - if (!apiUrl) { - apiUrl = process.env.VITE_API_URL; - // Reemplazar 'localhost' con 'backend' para llamadas desde el servidor (SSR) - apiUrl = apiUrl?.replace('localhost', 'backend').replace('127.0.0.1', 'backend'); - } - - // Normalizar la URL - const baseUrl = apiUrl?.endsWith('/') ? apiUrl : `${apiUrl}/`; - - // Get query parameters - tariff_fractions es catálogo global (no requiere company_id) - const searchParams = new URLSearchParams(url.search); - const queryString = searchParams.toString(); - - try { - const fetchUrl = `${baseUrl}v1/a76/tariff-fractions?${queryString}`; - - const response = await fetch( - fetchUrl, - { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${token}` - } - } - ); - - const data = await response.json(); - - if (!response.ok) { - return new Response(JSON.stringify(data), { - status: response.status, - headers: { - 'Content-Type': 'application/json' - } - }); - } - - return new Response(JSON.stringify(data), { - status: 200, - headers: { - 'Content-Type': 'application/json' - } - }); - } catch (error) { - console.error('Error fetching tariff fractions:', error); - return new Response( - JSON.stringify({ error: 'Failed to fetch tariff fractions' }), - { - status: 500, - headers: { - 'Content-Type': 'application/json' - } - } - ); - } -}; diff --git a/frontend/src/routes/api-sveltekit/units-of-measure/+server.ts b/frontend/src/routes/api-sveltekit/units-of-measure/+server.ts deleted file mode 100644 index 59cb616e..00000000 --- a/frontend/src/routes/api-sveltekit/units-of-measure/+server.ts +++ /dev/null @@ -1,81 +0,0 @@ -import type { RequestHandler } from './$types'; -import { getAccessTokenFromCookies } from '$lib/server/access-token-cookie'; - -export const GET: RequestHandler = async ({ cookies, url }) => { - const token = getAccessTokenFromCookies(cookies); - - // Obtener company_id de la cookie - const companyId = cookies.get('active_company_id'); - - if (!companyId) { - return new Response( - JSON.stringify({ error: 'No company selected' }), - { - status: 400, - headers: { - 'Content-Type': 'application/json' - } - } - ); - } - - // Configurar la URL de la API usando las variables de entorno - let apiUrl = process.env.INTERNAL_API_URL; - if (!apiUrl) { - apiUrl = process.env.VITE_API_URL; - // Reemplazar 'localhost' con 'backend' para llamadas desde el servidor (SSR) - apiUrl = apiUrl?.replace('localhost', 'backend').replace('127.0.0.1', 'backend'); - } - - // Normalizar la URL - const baseUrl = apiUrl?.endsWith('/') ? apiUrl : `${apiUrl}/`; - - // Get query parameters and add company_id - const searchParams = new URLSearchParams(url.search); - searchParams.set('company_id', companyId); - const queryString = searchParams.toString(); - - try { - const fetchUrl = `${baseUrl}v1/a76/units-of-measure?${queryString}`; - - const response = await fetch( - fetchUrl, - { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${token}` - } - } - ); - - const data = await response.json(); - - if (!response.ok) { - return new Response(JSON.stringify(data), { - status: response.status, - headers: { - 'Content-Type': 'application/json' - } - }); - } - - return new Response(JSON.stringify(data), { - status: 200, - headers: { - 'Content-Type': 'application/json' - } - }); - } catch (error) { - console.error('Error fetching units of measure:', error); - return new Response( - JSON.stringify({ error: 'Failed to fetch units of measure' }), - { - status: 500, - headers: { - 'Content-Type': 'application/json' - } - } - ); - } -}; diff --git a/frontend/src/routes/api-sveltekit/units-of-measure/[id]/+server.ts b/frontend/src/routes/api-sveltekit/units-of-measure/[id]/+server.ts deleted file mode 100644 index 1165254a..00000000 --- a/frontend/src/routes/api-sveltekit/units-of-measure/[id]/+server.ts +++ /dev/null @@ -1,87 +0,0 @@ -import type { RequestHandler } from './$types'; -import { getAccessTokenFromCookies } from '$lib/server/access-token-cookie'; - -export const GET: RequestHandler = async ({ cookies, params, url }) => { - const token = getAccessTokenFromCookies(cookies); - const { id } = params; - - // Obtener company_id de la cookie o query params - const companyId = cookies.get('active_company_id') || url.searchParams.get('company_id'); - - if (!companyId) { - return new Response( - JSON.stringify({ error: 'No company selected' }), - { - status: 400, - headers: { - 'Content-Type': 'application/json' - } - } - ); - } - - // Configurar la URL de la API usando las variables de entorno - let apiUrl = process.env.INTERNAL_API_URL; - if (!apiUrl) { - apiUrl = process.env.VITE_API_URL; - // Reemplazar 'localhost' con 'backend' para llamadas desde el servidor (SSR) - apiUrl = apiUrl?.replace('localhost', 'backend').replace('127.0.0.1', 'backend'); - } - - // Normalizar la URL - const baseUrl = apiUrl?.endsWith('/') ? apiUrl : `${apiUrl}/`; - - try { - const fetchUrl = `${baseUrl}v1/a76/units-of-measure/${id}?company_id=${companyId}`; - - const response = await fetch( - fetchUrl, - { - method: 'GET', - headers: { - 'Content-Type': 'application/json', - Authorization: `Bearer ${token}` - } - } - ); - - if (!response.ok) { - const errorText = await response.text(); - console.error('Error response from backend:', errorText); - return new Response( - JSON.stringify({ - error: 'Failed to fetch unit of measure', - details: errorText - }), - { - status: response.status, - headers: { - 'Content-Type': 'application/json' - } - } - ); - } - - const data = await response.json(); - return new Response(JSON.stringify(data), { - status: 200, - headers: { - 'Content-Type': 'application/json' - } - }); - } catch (error) { - console.error('Error in unit of measure API route:', error); - return new Response( - JSON.stringify({ - error: 'Internal server error', - message: error instanceof Error ? error.message : 'Unknown error' - }), - { - status: 500, - headers: { - 'Content-Type': 'application/json' - } - } - ); - } -}; diff --git a/frontend/src/routes/auth/callback/+page.server.ts b/frontend/src/routes/auth/callback/+page.server.ts index 428d8770..42cade05 100644 --- a/frontend/src/routes/auth/callback/+page.server.ts +++ b/frontend/src/routes/auth/callback/+page.server.ts @@ -42,7 +42,7 @@ export const load: PageServerLoad = async ({ url, cookies, fetch }) => { // En producción o fuera de Docker, usar VITE_KEYCLOAK_URL como fallback const KEYCLOAK_URL = process.env.KEYCLOAK_URL || process.env.VITE_KEYCLOAK_URL || 'http://localhost:8080'; const KEYCLOAK_REALM = process.env.KEYCLOAK_REALM || process.env.VITE_KEYCLOAK_REALM || 'master'; - const KEYCLOAK_CLIENT_ID = process.env.KEYCLOAK_CLIENT_ID || process.env.VITE_KEYCLOAK_CLIENT_ID || 'anexo76-backend'; + const KEYCLOAK_CLIENT_ID = process.env.KEYCLOAK_CLIENT_ID || process.env.VITE_KEYCLOAK_CLIENT_ID || 'app-backend'; const KEYCLOAK_CLIENT_SECRET = process.env.KEYCLOAK_CLIENT_SECRET || ''; // La redirect_uri debe coincidir exactamente con la registrada en Keycloak. diff --git a/frontend/src/routes/auth/post-logout/+server.ts b/frontend/src/routes/auth/post-logout/+server.ts index 97807e33..5c3ba8d5 100644 --- a/frontend/src/routes/auth/post-logout/+server.ts +++ b/frontend/src/routes/auth/post-logout/+server.ts @@ -4,7 +4,7 @@ import { getWorkspaceLoginUrl } from '$lib/server/workspace-auth'; /** * KC redirects here after completing the logout flow. - * This URL is covered by the app's registered wildcard in KC (e.g. anexo76-dev.aduanasoft.com/*). + * This URL is covered by the app's registered wildcard in KC (e.g. mi-app.dominio.com/*). * We then send the user to workspace login so it can apply myApps() launcher logic. */ export const GET: RequestHandler = async ({ request, url }) => { diff --git a/frontend/src/routes/auth/sso/+page.server.ts b/frontend/src/routes/auth/sso/+page.server.ts index de466ea3..e56cdaf8 100644 --- a/frontend/src/routes/auth/sso/+page.server.ts +++ b/frontend/src/routes/auth/sso/+page.server.ts @@ -1,5 +1,5 @@ /** - * SSO auto-login page for Anexo76. + * SSO auto-login page for Mi Aplicación. * The Hub App Launcher redirects here with ?relay= after generating a relay token. * This server-side load function exchanges the relay token for KC tokens via the * Hub backend, sets HttpOnly cookies, and redirects to /dashboard. @@ -103,7 +103,7 @@ export const load: PageServerLoad = async ({ url, cookies }) => { // ── Refresh proactivo ──────────────────────────────────────────────────── // Los tokens del relay fueron emitidos por KC via el browser (iss=IP:8085). // El Hub backend valida contra KC interno (hub-keycloak:8080) → issuer mismatch → 401. - // Refrescando aquí: Anexo76 backend → Hub → KC interno → iss=hub-keycloak:8080 → válido. + // Refrescando aquí: Mi Aplicación backend → Hub → KC interno → iss=hub-keycloak:8080 → válido. if (typeof tokens.refresh_token === 'string') { try { const internalApiUrl = ( diff --git a/frontend/src/routes/dashboard/+layout.server.ts b/frontend/src/routes/dashboard/+layout.server.ts index 7e09f726..a3f98a97 100644 --- a/frontend/src/routes/dashboard/+layout.server.ts +++ b/frontend/src/routes/dashboard/+layout.server.ts @@ -1,4 +1,3 @@ -import { redirect } from '@sveltejs/kit'; import { env } from '$env/dynamic/private'; import type { LayoutServerLoad } from './$types'; import { @@ -7,88 +6,55 @@ import { getUserCompanies, clearAuthTokens } from '$lib/server/api'; -import { - redirectToWorkspaceLogin -} from '$lib/server/workspace-auth'; -import { - extractAllowedSystemsFromToken, - mergeTokenClaims, - resolveActiveCompanyId, - resolveSystemGate, - setActiveSystemCookie, - redirectToWorkspaceBase -} from '$lib/server/system-gate'; +import { redirectToWorkspaceLogin } from '$lib/server/workspace-auth'; +import { resolveActiveCompanyId } from '$lib/server/system-gate'; import { fetchMyApps } from '$lib/server/workspace-apps'; +const DEV_LOCAL_AUTH = (env.DEV_LOCAL_AUTH ?? '').toLowerCase() === 'true'; + export const load: LayoutServerLoad = async ({ cookies, url, fetch }) => { - // Verificar si existe el token en las cookies const { accessToken } = getAuthTokens(cookies); - console.log('[dashboard layout] access_token presente:', !!accessToken, '| url:', url.pathname); if (!accessToken) { redirectToWorkspaceLogin(cookies, url); } - // Validar el token con el backend y obtener datos del usuario - // La función validateAuth maneja automáticamente el refresh de tokens - const redirectOnFail = undefined; - try { - // Primero my-companies: ejecuta get_current_user y puede crear tenant/empresa/usuario - // antes de /me/profile en validateAuth (evita SSR sin la empresa recién provisionada). + // my-companies primero: ejecuta get_current_user y provisiona tenant/usuario si aplica. const companies = await getUserCompanies(cookies, fetch); - - const userData = await validateAuth(cookies, fetch, redirectOnFail); - - const tokenClaims = mergeTokenClaims(userData, accessToken); - const gate = resolveSystemGate({ - tokenClaims, - cookieSystem: cookies.get('active_system') ?? null, - requestedSystem: url.searchParams.get('active_system') ?? null - }); + const userData = await validateAuth(cookies, fetch, undefined); const activeCompanyId = resolveActiveCompanyId(cookies, companies); - const allowedSystemsFromToken = extractAllowedSystemsFromToken(tokenClaims); - if (gate.action === 'redirect_workspace') { - redirectToWorkspaceBase(); - } - // Si el JWT no trae el claim allowed_systems pero el gate resolvió un sistema, - // usar el sistema activo como mínimo para que el store pueda inicializarse. - const allowedSystems = - allowedSystemsFromToken.length > 0 ? allowedSystemsFromToken : [gate.activeSystem]; - setActiveSystemCookie(cookies, gate.activeSystem); - // Cargar los tenants del usuario desde Hub (fuente de verdad multi-tenant) + // Modo local: no hay Hub. Saltar fetch de tenants/apps (evita timeouts por navegación). let userTenants: { id: number; name: string; slug: string }[] = []; - try { - const hubUrl = (env.INTERNAL_HUB_URL || env.HUB_URL || 'http://localhost:8001').replace(/\/+$/, ''); - const tenantOverride = cookies.get('sso_tenant_id'); - const tenantsRes = await fetch(`${hubUrl}/api/v1/auth/my-tenants`, { - headers: { - 'Authorization': `Bearer ${accessToken}`, - ...(tenantOverride ? { 'X-Tenant-Override': tenantOverride } : {}), - }, - }); - if (tenantsRes.ok) { - userTenants = await tenantsRes.json(); - } - } catch { - // No bloquear el dashboard si falla la carga de tenants - } + let myApps: { apps: unknown[]; routing: unknown } = { apps: [], routing: null }; - // Cargar las apps del Workspace accesibles al usuario (Hub, fuente de verdad). - // Releer el token de la cookie: validateAuth pudo haberlo refrescado y la variable - // `accessToken` capturada al inicio quedaría vencida → el Hub respondería 401. - // fetchMyApps nunca lanza: degrada a lista vacía si el Hub falla. - const freshAccessToken = getAuthTokens(cookies).accessToken ?? accessToken; - const myApps = await fetchMyApps(freshAccessToken, fetch, cookies.get('sso_tenant_id')); + if (!DEV_LOCAL_AUTH) { + try { + const hubUrl = (env.INTERNAL_HUB_URL || env.HUB_URL || 'http://localhost:8001').replace(/\/+$/, ''); + const tenantOverride = cookies.get('sso_tenant_id'); + const tenantsRes = await fetch(`${hubUrl}/api/v1/auth/my-tenants`, { + headers: { + 'Authorization': `Bearer ${accessToken}`, + ...(tenantOverride ? { 'X-Tenant-Override': tenantOverride } : {}) + } + }); + if (tenantsRes.ok) { + userTenants = await tenantsRes.json(); + } + } catch { + // No bloquear el dashboard si falla la carga de tenants + } + + const freshAccessToken = getAuthTokens(cookies).accessToken ?? accessToken; + myApps = await fetchMyApps(freshAccessToken, fetch, cookies.get('sso_tenant_id')); + } return { authenticated: true, - user: { ...userData, token: accessToken, allowedSystems }, + user: { ...userData, token: accessToken }, companies, activeCompanyId: activeCompanyId ?? undefined, - activeSystem: gate.activeSystem, - allowedSystems, userTenants, workspaceApps: myApps.apps, appRouting: myApps.routing, @@ -100,7 +66,7 @@ export const load: LayoutServerLoad = async ({ cookies, url, fetch }) => { throw error; } - // Para cualquier otro error (conexión, etc), limpiar token y redirigir + // Cualquier otro error: limpiar token y redirigir al login clearAuthTokens(cookies); redirectToWorkspaceLogin(cookies, url); } diff --git a/frontend/src/routes/dashboard/+layout.svelte b/frontend/src/routes/dashboard/+layout.svelte index 4ed1d2ba..22ecb95b 100644 --- a/frontend/src/routes/dashboard/+layout.svelte +++ b/frontend/src/routes/dashboard/+layout.svelte @@ -8,11 +8,9 @@ import { Separator } from '$lib/components/ui/separator/index.js'; import * as Sidebar from '$lib/components/ui/sidebar/index.js'; import { companyStore } from '$lib/stores/company.svelte'; - import ExchangeRateGuard from '$lib/components/dashboard/exchange_rate/exchange-rate-guard.svelte'; import SessionTimeoutWarning from '$lib/components/session-timeout-warning.svelte'; import { page } from '$app/state'; import { browser } from '$app/environment'; - import { CSV_IMPORT_SESSION_CHANGED, readCsvImportSession } from '$lib/csv-import-session'; import { createSessionManager, destroySessionManager, @@ -28,34 +26,6 @@ type LicenseError = { type: string; message: string; status: number }; let { data, children }: { data: LayoutData & { licenseError?: LicenseError }; children: any } = $props(); - let csvImportBanner = $state(false); - let csvImportBannerLabel = $state(null); - - function updateCsvImportBanner() { - if (!browser) { - csvImportBanner = false; - csvImportBannerLabel = null; - return; - } - const path = page.url.pathname; - const cid = companyStore.activeCompany?.id; - if (path.includes('/dashboard/csv-upload')) { - csvImportBanner = false; - csvImportBannerLabel = null; - return; - } - const session = readCsvImportSession(cid); - csvImportBanner = session != null; - const raw = session?.label; - csvImportBannerLabel = typeof raw === 'string' && raw.trim() ? raw.trim() : null; - } - - $effect(() => { - if (!browser) return; - page.url.pathname; - companyStore.activeCompany?.id; - updateCsvImportBanner(); - }); // Hacer disponible el usuario en el contexto para los componentes hijos. // El sidebar ya lee de page.data directamente; este contexto lo usan otros componentes. @@ -206,12 +176,9 @@ await goto(`${page.url.pathname}${page.url.search}${page.url.hash}`, { invalidateAll: true }); }; window.addEventListener('companyChanged', handleCompanyChange); - window.addEventListener(CSV_IMPORT_SESSION_CHANGED, updateCsvImportBanner); - updateCsvImportBanner(); return () => { window.removeEventListener('companyChanged', handleCompanyChange); - window.removeEventListener(CSV_IMPORT_SESSION_CHANGED, updateCsvImportBanner); window.removeEventListener(SESSION_EXPIRED_EVENT, handleSessionExpired); }; }); @@ -244,31 +211,10 @@ id="dashboard-main-content" class="flex flex-1 flex-col min-h-0 gap-4 overflow-y-auto overflow-x-hidden p-4 pt-0" > - {#if csvImportBanner} - - - {#if csvImportBannerLabel} - Importación CSV en curso: {csvImportBannerLabel}. - {:else} - Importación CSV en curso para esta empresa. - {/if} - - Ir a importación masiva - - {/if} - {@render children?.()}
-{#if !page.url.pathname.includes('/dashboard/invoices') && !page.url.pathname.includes('/dashboard/pedimentos')} - -{/if} - - {/if} diff --git a/frontend/src/routes/dashboard/+page.svelte b/frontend/src/routes/dashboard/+page.svelte index ff21053a..9323e5d8 100644 --- a/frontend/src/routes/dashboard/+page.svelte +++ b/frontend/src/routes/dashboard/+page.svelte @@ -1,283 +1,45 @@
- {#snippet headerSection()} -
+
+

+ + Dashboard +

+

+ Plantilla base — agrega tus módulos aquí. +

+
+ +
+ +
+
+ +
-
- {m.sidebar_dashboard()} - {#if stats?.company_name} - / - {stats.company_name} - {/if} -
-

- {greeting()}{m.dashboard_team_suffix()} -

-

- {#if stats?.company_name} - - - {m.dashboard_operational_summary()} - - {:else} - {m.dashboard_management_system()} - {/if} +

Compañía activa

+

+ {companyStore.activeCompany?.name ?? '—'}

-
- - -
- {/snippet} - {@render headerSection()} - - {#if error} - - - {m.exchange_rate_error_title()} - {error} - - {/if} - - {#if loading} - -
- {#each Array(6) as _} -
-
-
-
-
-
-
- {/each} -
- -
- {#each Array(2) as _} - - -
-
-
- -
-
-
- {/each} -
- {:else if stats} - -
- - -
- + + +
+
-
- +
+

Mi cuenta

+

Perfil y configuración

-
- -
-
- -
-
- -
-
- -
- - -
- -
-
- -
- - -
- -
-
- -
-
- - -
- -
-
-
- {(stats.total_invoices.value + stats.total_pedimentos.value).toLocaleString()} -
-
{m.dashboard_total_documents()}
-
-
-
- {(stats.total_clients.value + stats.total_providers.value).toLocaleString()} -
-
{m.dashboard_total_contacts()}
-
-
-
- {stats.active_items.value.toLocaleString()} -
-
{m.dashboard_active_items_stat()}
-
- -
- {/if} + +
diff --git a/frontend/src/routes/dashboard/account/+page.server.ts b/frontend/src/routes/dashboard/account/+page.server.ts index 4b0d6c3d..d4e06e56 100644 --- a/frontend/src/routes/dashboard/account/+page.server.ts +++ b/frontend/src/routes/dashboard/account/+page.server.ts @@ -1,72 +1,6 @@ -/** - * Server-side load y actions para gestión de perfil de usuario - */ -import { fail } from '@sveltejs/kit'; -import { authenticatedFetch } from '$lib/server/api'; -import type { Actions, PageServerLoad } from './$types'; +import type { PageServerLoad } from './$types'; export const load: PageServerLoad = async ({ parent }) => { - // El layout /dashboard ya cargó los datos del usuario via validateAuth. - // Reutilizamos esos datos en lugar de hacer una llamada duplicada. const { user } = await parent(); - return { - profile: user ?? null, - error: user ? null : 'Error al cargar el perfil' - }; -}; - -export const actions: Actions = { - updateProfile: async ({ request, cookies, fetch }) => { - const formData = await request.formData(); - - // Construir objeto de actualización desde FormData - const updateData: Record = {}; - - for (const [key, value] of formData.entries()) { - if (key === 'avatar') continue; - if (value && value !== '') { - updateData[key] = value; - } - } - - // No incluir avatar_url en el PUT /me/profile: el POST /me/avatar ya persistió - // el valor correcto (clave S3 o ruta local) en UserTenant. Enviar aquí la URL - // pública sobrescribiría esa clave y rompería el endpoint de servicio de imágenes. - - try { - const response = await authenticatedFetch( - 'v1/core/users/me/profile', - { - method: 'PUT', - headers: { - 'Content-Type': 'application/json' - }, - body: JSON.stringify(updateData) - }, - cookies, - fetch, - '/login' - ); - - if (!response.ok) { - const errorData = await response.json().catch(() => ({})); - return fail(response.status, { - error: errorData.detail || 'Error al actualizar el perfil', - values: updateData - }); - } - - const updatedProfile = await response.json(); - return { - success: true, - profile: updatedProfile - }; - } catch (err: any) { - console.error('Error updating profile:', err); - return fail(500, { - error: 'Error al actualizar el perfil', - values: updateData - }); - } - } + return { user }; }; diff --git a/frontend/src/routes/dashboard/account/+page.svelte b/frontend/src/routes/dashboard/account/+page.svelte index ca59bf3c..95dd0051 100644 --- a/frontend/src/routes/dashboard/account/+page.svelte +++ b/frontend/src/routes/dashboard/account/+page.svelte @@ -1,243 +1,30 @@ -
-
-

{m['common.title_account']()}

-

- {m['common.sub_account']()} -

+
+
+

+ + Mi cuenta +

+

Perfil y configuración personal.

- - {#if !profile && serverError} - - -
-

{serverError}

- -
-
-
- {:else if profile} -
{ - - saving = true; - error = ''; - success = ''; - return async ({ update }) => { - // reset:false evita que SvelteKit limpie el formulario nativo. - // update() ya llama a invalidateAll internamente. - await update({ reset: false }); - saving = false; - }; - }} - > -
- - - - Foto de Perfil - Esta imagen se administra desde Workspace - - -
-
- - - {getInitials(profile)} - -
- - - -
-
- -
-

- La foto de perfil se sincroniza desde Workspace en login, refresh de sesión y carga de perfil. -

-

- Para cambiarla, actualízala en Workspace y vuelve a iniciar sesión. -

-
-
-
-
- - - - - Información Personal - Actualiza tus datos personales - - -
-
- - -
- -
- - -
-
- -
- - -
-
-
- - - - - Información de Cuenta - Datos de tu cuenta en el sistema - - -
-
- - -
- -
- - -
-
-
- - -

- Para cambiar tu email accede a tu perfil en el portal de Aduanasoft. -

-
-
-
-
- - - {#if error} -
-

{error}

-
+ + + Perfil + + + {#if data.user} +
Nombre: {data.user.name ?? data.user.preferred_username ?? '—'}
+
Email: {data.user.email ?? '—'}
+ {:else} +

No hay datos de usuario disponibles.

{/if} - - {#if success} -
-

{success}

-
- {/if} - - -
-
- - -
-
- - {/if} +
+
diff --git a/frontend/src/routes/dashboard/admin/+layout.svelte b/frontend/src/routes/dashboard/admin/+layout.svelte deleted file mode 100644 index a8772908..00000000 --- a/frontend/src/routes/dashboard/admin/+layout.svelte +++ /dev/null @@ -1,36 +0,0 @@ - - -
-
-
-
- {#each adminRoutes as route} - {@const Icon = route.icon} - - {/each} -
-
-
- - {@render children()} -
diff --git a/frontend/src/routes/dashboard/admin/roles/+page.svelte b/frontend/src/routes/dashboard/admin/roles/+page.svelte deleted file mode 100644 index a0106308..00000000 --- a/frontend/src/routes/dashboard/admin/roles/+page.svelte +++ /dev/null @@ -1,540 +0,0 @@ - - -
-
-

{m['common.title_roles']()}

-

{m['common.sub_roles']()}

-
- -
- -
-
-
-

- - Roles -

- -
- - {#if loading} -

Cargando...

- {:else if roles.length === 0} -
-

No hay roles disponibles

- -
- {:else} -
- {#each roles as role (role.id)} -
- -
- - -
-
- {/each} -
- {/if} -
-
- - -
- {#if selectedRole} -
-
-
-

- - Permisos de "{selectedRole.name}" -

-

- {rolePermissions.length} permisos asignados -

-
- -
- - - - - Módulo - Acción - Descripción - Acciones - - - - {#if loadingPermissions} - - - Cargando permisos... - - - {:else if rolePermissions.length === 0} - - - Este rol no tiene permisos asignados - - - {:else} - {#each rolePermissions as rp (rp.id)} - - - {rp.permission?.module} - - {rp.permission?.action} - - {rp.permission?.description || '-'} - - - - - - {/each} - {/if} - -
-
- {:else} -
- -

Selecciona un rol para ver sus permisos

-
- {/if} -
-
-
- - - - - - Agregar Permisos a "{selectedRole?.name}" - - -
-
- updatePermissionsByModule()} - class="w-full" - /> -
- - {#if availablePermissions.length === 0} -

- No hay permisos disponibles para asignar -

- {:else} -
- {#each [...permissionsByModule] as [module, permissions] (module)} -
-

- {module} - ({permissions.length} permisos, {new Set(permissions.map(p => p.id)).size} únicos) -

-
- {#each permissions as permission (permission.id)} -
- togglePermission(permission.id)} - /> -
- -
- {permission.code} -
-
-
- {/each} -
-
- {/each} -
- {/if} -
- - - - - -
-
- - - - - - - {editingRole ? 'Editar Rol' : 'Nuevo Rol'} - - - -
-
- - -
- -
- - -
- -
- - - - - -
- - - {#if showPreview} -
-
- {@html renderMarkdown(content)} -
-
- {/if} - {:else} - -
-
-
- {#if content_type === 'pdf'} -
- -
-

{m['common.help_config_pdf']()}

- {:else if content_type === 'video'} -
- -
-

{m['common.help_config_video']()}

- {:else} -
- -
-

{m['common.help_config_document']()}

- {/if} -

{m['common.help_upload_hint']()}

-
- - {#if file_url} -
-
-
- -
-
-

{m['common.help_file_loaded']()}

-

- {getFileNameFromPath(file_url)} -

-

- {mime_type} • {file_size ? (file_size / 1024 / 1024).toFixed(2) : '??'} MB -

-
-
-
- - -
-
- {:else} - - {/if} -
-
- {/if} - -
-
diff --git a/frontend/src/routes/dashboard/invoices/+page.server.ts b/frontend/src/routes/dashboard/invoices/+page.server.ts deleted file mode 100644 index 2ee27026..00000000 --- a/frontend/src/routes/dashboard/invoices/+page.server.ts +++ /dev/null @@ -1,139 +0,0 @@ -import type { PageServerLoad } from './$types'; -import { redirect } from '@sveltejs/kit'; -import { - getAuthTokens, - authenticatedFetch -} from '$lib/server/api'; -import { redirectToWorkspaceLogin } from '$lib/server/workspace-auth'; - -export const load: PageServerLoad = async ({ fetch, cookies, url, parent }) => { - const invoiceType = url.searchParams.get('invoice_type'); - const operationType = url.searchParams.get('operation_type'); - - if (!operationType) { - throw redirect(302, '/dashboard'); - } - - // Esperar a que el layout padre valide/refresque el token - const parentData = await parent(); - - // Verificar autenticación - const tokens = getAuthTokens(cookies); - const accessToken = tokens.accessToken; - - if (!accessToken) { - redirectToWorkspaceLogin(cookies, url); - } - - try { - // Obtener company_id de múltiples fuentes (en orden de prioridad): - // 1. URL query param (permite cambiar vía navegación) - // 2. Cookie active_company_id (setted por el team-switcher) - // 3. Primera compañía del usuario (fallback) - const companyIdParam = url.searchParams.get('company_id'); - const cookieCompanyId = cookies.get('active_company_id'); - - const companyId = companyIdParam - ? parseInt(companyIdParam) - : cookieCompanyId - ? parseInt(cookieCompanyId) - : parentData.companies?.[0]?.id; - - // Si aún no hay companyId, mostrar error - if (!companyId) { - return { - items: [], - total: 0, - page: 1, - page_size: 50, - error: 'No se encontró una compañía seleccionada', - companies: parentData.companies || [] - }; - } - - // Construir parámetros de consulta - const params = new URLSearchParams({ - company_id: companyId.toString(), - page: '1', - page_size: '50' - }); - - // Agregar filtros opcionales desde query parameters - const invoiceNumber = url.searchParams.get('invoice_number'); - const projectNumber = url.searchParams.get('project_number'); - const year = url.searchParams.get('year'); - - if (invoiceType) params.append('invoice_type', invoiceType); - if (operationType) params.append('operation_type', operationType); - if (invoiceNumber) params.append('invoice_number', invoiceNumber); - if (projectNumber) params.append('project_number', projectNumber); - if (year) params.append('year', year); - - // Cargar facturas e invoice types en paralelo - const [response, invoiceTypesResponse] = await Promise.all([ - authenticatedFetch( - `v1/a76/invoices?${params.toString()}`, - {}, - cookies, - fetch, - '/login' - ), - authenticatedFetch( - 'v1/public/reference_data/invoice-types?page=1&page_size=100', - {}, - cookies, - fetch, - '/login' - ) - ]); - - if (!response.ok) { - return { - items: [], - total: 0, - page: 1, - page_size: 50, - error: 'Error al cargar facturas', - companies: parentData.companies || [], - currentCompanyId: companyId, - filters: { - invoice_type: invoiceType, - operation_type: operationType - } - }; - } - - const data = await response.json(); - const invoiceTypesData = invoiceTypesResponse.ok ? await invoiceTypesResponse.json() : { items: [] }; - - return { - items: data.items || [], - total: data.total || 0, - page: data.page || 1, - page_size: data.page_size || 50, - companies: parentData.companies || [], - currentCompanyId: companyId, - invoiceTypes: invoiceTypesData.items || [], - filters: { - invoice_type: invoiceType, - operation_type: operationType, - invoice_number: invoiceNumber, - project_number: projectNumber, - year: year - } - }; - } catch (error) { - console.error('Error loading invoices:', error); - if (error instanceof Response && error.status >= 300 && error.status < 400) { - throw error; // Propagate redirects - } - return { - items: [], - total: 0, - page: 1, - page_size: 50, - error: 'Error al cargar facturas', - companies: parentData.companies || [] - }; - } -}; diff --git a/frontend/src/routes/dashboard/invoices/+page.svelte b/frontend/src/routes/dashboard/invoices/+page.svelte deleted file mode 100644 index 96018723..00000000 --- a/frontend/src/routes/dashboard/invoices/+page.svelte +++ /dev/null @@ -1,2677 +0,0 @@ - - -{#if !$permissionsHydrated} -
- -

Verificando permisos…

-
-{:else if !canViewInvoiceSection} -
- -
-{:else} -
- - {m.invoice_list_skip_to_actions()} - -
-
-

{viewTitle}

-

{m.invoice_list_header_description()}

-
-
- - {#if canCreateInvoice} - - {/if} -
-
- - - - {#if error} - - - Error - {error} - - - {/if} - - - -
-
- {m.invoice_list_card_invoice_list_title()} -
-
- {#if filters.operation_type === 'exp'} - - {/if} - - - - - - -
-
-
- -
- (sorting = newSorting)} - /> -
-
-
- -
- {m.invoice_list_summary_showing()} {allItems.length} {m.invoice_list_summary_of()} {totalItems} {m.invoice_list_summary_records()} - - {m.invoice_list_filters_active_filters()}: {Object.values(filters).filter((value) => value !== '').length} -
- -
- - - - - - Copiar Factura - - Indica el número que tendrá la factura copiada. - - -
- - { if (e.key === 'Enter') handleCopyConfirm(); }} - /> -
- - - - -
-
- - - - - {m.invoice_list_cove_dialog_title()} - - {m.invoice_list_cove_dialog_description_prefix()} - {selectedInvoice?.invoice_number}. - - - - - -
- - - - {#snippet children()} -
-
- -
-
-
- {m.invoice_list_cove_dialog_destination()} -
-
- {selectedCoveRecipient?.label || m.invoice_list_cove_dialog_select_email()} -
-
- {selectedCoveRecipient?.email || m.invoice_list_cove_dialog_fallback_email()} -
-
-
- {/snippet} -
- - {#if coveRecipientsLoading} -
- {m.invoice_list_cove_dialog_loading_emails()} -
- {:else if coveRecipientOptions.length === 0} -
- {m.invoice_list_cove_dialog_no_emails()} -
- {:else} - {#each coveRecipientOptions as recipient} - - {#snippet children({ selected })} -
- - {recipient.label} - {#if selected} - {m.invoice_list_cove_dialog_selected_badge()} - {/if} - - - {recipient.description} - -
- {/snippet} -
- {/each} - {/if} -
-
- {#if coveRecipientsError} -

{coveRecipientsError}

- {/if} -
-
-
- - - - - -
-
- - - - - - - {progressErrorTitle} - -
- {progressErrorDescription} -
-
-
- - (isProgressErrorOpen = false)}> - Cerrar - - -
-
- - - - - - {selectedInvoice?.operation_type === 'exp' - ? m.invoice_list_dialogs_revert_title_export() - : m.invoice_list_dialogs_revert_title_import()} - - - {m.invoice_list_dialogs_revert_description_intro()} {selectedInvoice?.invoice_number}. - {m.invoice_list_dialogs_revert_description_warning()} - {m.invoice_list_dialogs_revert_description_question()} - - - - {m.invoice_list_actions_cancel()} - {m.invoice_list_actions_continue()} - - - - - - - - {m.invoice_list_dialogs_winsaai_title()} - - {m.invoice_list_dialogs_winsaai_description_intro()} {selectedInvoice?.invoice_number} - {m.invoice_list_dialogs_winsaai_of_type()} {selectedInvoice?.document_type} - {m.invoice_list_dialogs_winsaai_description_process()} - {m.invoice_list_dialogs_winsaai_description_question()} - - - - {m.invoice_list_actions_cancel()} - {m.invoice_list_actions_continue()} - - - - - {#if contextMenuOpen && contextMenuInvoice} - - - -
-

{contextMenuInvoice.invoice_number ?? 'Factura'}

-
- {#snippet cmItem(icon: any, label: string, action: () => void)} - {@const Icon = icon} - - {/snippet} - {@render cmItem(Copy, 'Copiar Factura', () => { - contextMenuOpen = false; - openCopyDialog(contextMenuInvoice!); - })} - {@render cmItem(Download, 'Exportar Partidas', () => { - openExportItemsDialog(contextMenuInvoice!); - })} - {@render cmItem(ArrowRightLeft, 'Copiar Encabezado a SCAII', () => { - const invId = contextMenuInvoice!.id; - invoicesApi.copyInvoiceHeader(invId, companyStore.activeCompany!.id) - .then((res) => { - if (res.error) { toast.error(res.error); return; } - toast.success('Encabezado copiado'); - reloadData(); - }); - })} -
- {@render cmItem(Route, 'Interfaz Carta Porte', () => { - const inv = contextMenuInvoice!; - downloadBlob( - invoicesApi.downloadCartaPorte(inv.id, companyStore.activeCompany!.id), - `${inv.invoice_number ?? 'factura'}_carta_porte.csv` - ); - })} - {@render cmItem(Truck, 'Interfaz GM Transport', () => { - const inv = contextMenuInvoice!; - downloadBlob( - invoicesApi.downloadGmTransport(inv.id, companyStore.activeCompany!.id), - `${inv.invoice_number ?? 'factura'}_gm_transport.csv` - ); - })} - {@render cmItem(Truck, 'Interfaz TFC', () => { - const inv = contextMenuInvoice!; - downloadBlob( - invoicesApi.downloadTfc(inv.id, companyStore.activeCompany!.id), - `${inv.invoice_number ?? 'factura'}_tfc.csv` - ); - })} - {@render cmItem(FileText, 'Interfaz Aviso Traslado', () => { - const inv = contextMenuInvoice!; - downloadBlob( - invoicesApi.downloadAvisoCruce(inv.id, companyStore.activeCompany!.id), - `${inv.invoice_number ?? 'factura'}_aviso_cruce.csv` - ); - })} - {@render cmItem(Route, 'Interfaz Carta Porte Consolidada', () => { - const inv = contextMenuInvoice!; - downloadBlob( - invoicesApi.downloadCartaPorteConsolidada(inv.id, companyStore.activeCompany!.id), - `${inv.invoice_number ?? 'factura'}_carta_porte_consolidada.csv` - ); - })} - {@render cmItem(Building2, 'Interfaz CAAAREM', () => { - const inv = contextMenuInvoice!; - downloadBlob( - invoicesApi.downloadCaaarem(inv.id, companyStore.activeCompany!.id), - `${inv.invoice_number ?? 'factura'}_caaarem.csv` - ); - })} - {@render cmItem(ArrowRightLeft, 'Interfaz AAduanal_RS', () => { - const inv = contextMenuInvoice!; - downloadBlob( - invoicesApi.downloadAaduanalRs(inv.id, companyStore.activeCompany!.id), - `${inv.invoice_number ?? 'factura'}_aaduanal_rs.csv` - ); - })} - {@render cmItem(Route, 'Interfaz Carta Porte Genesis', () => { - const inv = contextMenuInvoice!; - downloadBlob( - invoicesApi.downloadCpGenesis(inv.id, companyStore.activeCompany!.id), - `${inv.invoice_number ?? 'factura'}_cp_genesis.csv` - ); - })} -
- {@render cmItem(Clipboard, 'Copiar Factura (Portapapeles)', () => handleCopyToClipboard(contextMenuInvoice!))} -
- {/if} - - {#if showVuSubmenu} - - - -
- - - - -
- {/if} - - - - - - - {#if companyStore.activeCompany} - - - {/if} - - - - - Exportar a - - Selecciona el formato para - {exportItemsInvoice?.invoice_number ?? 'la factura'}. - - -
- (exportItemsFormat = v as 'csv' | 'xlsx' | 'txt')} - class="flex flex-col gap-3" - > -
- - -
-
- - -
-
- - -
-
-
- - - - -
-
- - {#if showDetailsDialog && selectedInvoice} - (showDetailsDialog = false)} /> - {/if} - - {#if showDeleteDialog} - allItems.find((i) => i.id === id)) - .filter((i): i is Invoice => i !== undefined)} - onClose={() => { - showDeleteDialog = false; - selectedInvoiceIds = []; - }} - onSuccess={() => { - showDeleteDialog = false; - selectedInvoiceIds = []; - handleSuccess(); - }} - /> - {/if} -
-{/if} diff --git a/frontend/src/routes/dashboard/invoices/edit/[id]/+page.server.ts b/frontend/src/routes/dashboard/invoices/edit/[id]/+page.server.ts deleted file mode 100644 index cc231385..00000000 --- a/frontend/src/routes/dashboard/invoices/edit/[id]/+page.server.ts +++ /dev/null @@ -1,172 +0,0 @@ -import type { PageServerLoad } from './$types'; -import { error, redirect } from '@sveltejs/kit'; -import { getAuthTokens, getActiveCompanyId, authenticatedFetch } from '$lib/server/api'; -import { redirectToWorkspaceLogin } from '$lib/server/workspace-auth'; - -export const load: PageServerLoad = async ({ params, cookies, fetch, url }) => { - const { accessToken } = getAuthTokens(cookies); - - if (!accessToken) { - redirectToWorkspaceLogin(cookies, url); - } - - // Obtener el company_id de la cookie - const companyId = await getActiveCompanyId(cookies, fetch); - - if (!companyId) { - throw error(400, 'No se encontró una compañía seleccionada'); - } - - // Leer filtros de query parameters para preseleccionar en creación - const operationTypeParam = url.searchParams.get('operation_type'); - const invoiceTypeParam = url.searchParams.get('invoice_type'); - - // Validar que operation_type sea 'exp' o 'imp' - let parsedOperationType: string | null = null; - if (operationTypeParam && (operationTypeParam === 'exp' || operationTypeParam === 'imp')) { - parsedOperationType = operationTypeParam; - } - - // Para creación ('new'): siempre operation_type; invoice_type obligatorio solo en importación. - if (params.id === 'new' && !parsedOperationType) { - throw redirect(302, '/dashboard'); - } - if (params.id === 'new' && parsedOperationType === 'imp' && !invoiceTypeParam) { - throw redirect(302, '/dashboard'); - } - - try { - let invoiceData: any = null; - let catalogsData: any = {}; - let defaultSettings: any = null; - let isCreate = false; - let legendsData: any = { items: [] }; - - // Fetch Default Settings separately if applicable (for creation) - const settingsPromise = (params.id === 'new' && parsedOperationType && invoiceTypeParam && companyId) - ? authenticatedFetch( - `v1/a76/invoice-settings/${invoiceTypeParam}?operation_type=${parsedOperationType}&company_id=${companyId}`, - {}, - cookies, - fetch - ).then(res => res.ok ? res.json() : null).catch(err => { - console.error('Error fetching defaults:', err); - return null; - }) - : Promise.resolve(null); - - - if (params.id === 'new') { - isCreate = true; - const creationQs = new URLSearchParams({ - company_id: String(companyId), - operation_type: parsedOperationType || '' - }); - if (invoiceTypeParam) { - creationQs.set('invoice_type', invoiceTypeParam); - } - // Call Consolidated Creation Endpoint (RBAC: tipo de alta en query) - const [creationResponse, settingsResult] = await Promise.all([ - authenticatedFetch(`v1/a76/invoices/creation-data?${creationQs.toString()}`, {}, cookies, fetch), - settingsPromise - ]); - - if (creationResponse.ok) { - catalogsData = await creationResponse.json(); - } else if (creationResponse.status === 403) { - throw error(403, 'No tienes permiso para crear este tipo de factura.'); - } else if (creationResponse.status === 401) { - throw error(401, 'Tu sesión expiró. Vuelve a iniciar sesión.'); - } else if (creationResponse.status === 422) { - throw error(422, 'Parámetros inválidos para crear factura.'); - } else { - console.error('Error fetching creation data:', creationResponse.status); - throw error(creationResponse.status, 'No fue posible preparar el formulario de creación.'); - } - - if (settingsResult && settingsResult.settings) { - defaultSettings = settingsResult.settings; - } - - } else { - // Edition Mode - const invoiceId = parseInt(params.id); - if (isNaN(invoiceId)) { - throw error(400, 'ID de factura inválido'); - } - - // Call Consolidated Edition Endpoint - const editionResponse = await authenticatedFetch( - `v1/a76/invoices/${invoiceId}/edition-data?company_id=${companyId}`, - {}, - cookies, - fetch - ); - - if (!editionResponse.ok) { - if (editionResponse.status === 404) { - throw error(404, 'Factura no encontrada'); - } - throw error(editionResponse.status, 'Error al cargar la factura'); - } - - const editionData = await editionResponse.json(); - catalogsData = editionData; // It includes catalogs + invoice - invoiceData = editionData.invoice; - } - - // Leyendas fijas (para observaciones) - try { - const legendsResponse = await authenticatedFetch( - `v1/a76/legends/?company_id=${companyId}&page=1&page_size=200`, - { method: 'GET' }, - cookies, - fetch - ); - if (legendsResponse.ok) { - legendsData = await legendsResponse.json(); - } - } catch (e) { - // No bloquear la carga de factura por fallo en leyendas - legendsData = { items: [] }; - } - - // Map snake_case response to camelCase props expected by Svelte Page - // Providing empty arrays as defaults if something is missing - return { - invoice: invoiceData, - invoiceId: params.id === 'new' ? null : parseInt(params.id), - isCreate: isCreate, - invoiceTypes: catalogsData.invoice_types || [], - customsBrokers: catalogsData.customs_brokers || [], - clients: catalogsData.clients || [], - providers: catalogsData.providers || [], - currencyTypes: catalogsData.currency_types || [], - transportTypes: catalogsData.transport_types || [], - transporters: catalogsData.transporters || [], - vehicles: catalogsData.vehicles || [], - drivers: catalogsData.drivers || [], - trailers: catalogsData.trailers || [], - customsSections: catalogsData.customs_sections || [], - codePedimentoRegimens: catalogsData.code_pedimento_regimens || [], - seals: catalogsData.seals || [], - incoterms: catalogsData.incoterms || [], - valuationMethods: catalogsData.valuation_methods || [], - pedimentos: catalogsData.pedimentos || [], - transportModes: catalogsData.transport_modes || [], - legends: legendsData?.items || [], - defaultSettings: defaultSettings, - filters: { - operation_type: parsedOperationType, - invoice_type: invoiceTypeParam || null - } - }; - - } catch (err: any) { - console.error('Error in load function:', err); - if (err && typeof err.status === 'number' && [401, 403, 404, 422].includes(err.status)) { - throw err; - } - throw error(500, 'Error interno al cargar la página'); - } -}; diff --git a/frontend/src/routes/dashboard/invoices/edit/[id]/+page.svelte b/frontend/src/routes/dashboard/invoices/edit/[id]/+page.svelte deleted file mode 100644 index 274e0b9a..00000000 --- a/frontend/src/routes/dashboard/invoices/edit/[id]/+page.svelte +++ /dev/null @@ -1,1316 +0,0 @@ - - -
- -
-
-
- -

- {#if data.isCreate} - {m.invoice_edit_new_title()} - {:else} - {m.invoice_edit_page_invoice_prefix()}{data.invoice.id} - {/if} - {#if invoiceType} - {@const headerInvoiceTypeInfo = data.invoiceTypes?.find((t) => t.key === invoiceType)} - {#if headerInvoiceTypeInfo} - - - {headerInvoiceTypeInfo.description} - - {/if} - {/if} -

- - {operationTypeText} - - {#if data.isCreate} - {m.invoice_edit_draft_badge()} - {/if} -
-

- {#if !data.isCreate && data.invoice.invoice_number} - {m.invoice_edit_invoice_number_prefix()} {data.invoice.invoice_number} - {:else} - {m.invoice_edit_edit_details()} - {/if} -

-
-
- - - - -
- - - - - - - - - - - - - - - - - - - - {#if invoiceType !== 'MEX'} - - - - {/if} - -
-
- - - { - uiStore.isExchangeRateDialogOpen = false; - // Si es factura nueva y no tiene fecha, poner la de hoy - if (data.isCreate && !InvoiceTopFieldsFormData.invoice_date) { - InvoiceTopFieldsFormData.invoice_date = new Date().toISOString().split('T')[0]; - } - - // Actualizar el tipo de cambio mostrado - if (InvoiceTopFieldsFormData.invoice_date && companyStore?.activeCompany?.id) { - getExchangeRateByDate( - InvoiceTopFieldsFormData.invoice_date, - companyStore.activeCompany.id - ).then((rate) => { - if (rate) calculatedExchangeRate = rate.value; - }); - } - }} -/> -
-
- - -
- - - - {m.invoice_edit_tabs_general()} - - - - {m.invoice_edit_tabs_observations()} - - - - {m.invoice_edit_tabs_items()} - - - - {m.invoice_edit_tabs_others()} - - {#if invoiceType !== 'MEX'} - - - {m.invoice_edit_tabs_continuation()} - - {/if} - -
-
- - -
- - -
-
-
- - (showPrerequisitesModal = false)} - onCancel={() => goto(invoicesListPath())} -/> diff --git a/frontend/src/routes/dashboard/invoices/items/presets/+page.svelte b/frontend/src/routes/dashboard/invoices/items/presets/+page.svelte deleted file mode 100644 index 3fc358b1..00000000 --- a/frontend/src/routes/dashboard/invoices/items/presets/+page.svelte +++ /dev/null @@ -1,542 +0,0 @@ - - -
-
-
- -
-

Plantillas de Partidas

-

- Gestiona, edita y prueba plantillas reutilizables sin perderte en la UI. -

-
-
-
- - -
-
- -
-
-

Total plantillas

-

{presets.length}

-
-
-

Partidas totales

-

{totalPresetItems}

-
-
-
-

Tip

-

Usa plantillas para acelerar la captura.

-
- -
-
- - - -
-
- Listado de plantillas - Mira, filtra y edita tus plantillas guardadas. -
-
- - -
-
-
- - - - - Nombre - Descripción - Items - Fecha - Acciones - - - - {#if isLoading} - - - - - - {:else} - {#each filteredPresets as preset} - - {preset.name} - - {preset.description || 'Sin descripción'} - - - {preset.items?.length || 0} partidas - - - {preset.created_at - ? new Date(preset.created_at).toLocaleDateString() - : '-'} - - -
- - -
-
-
- {/each} - {#if filteredPresets.length === 0} - - -
- -

No hay plantillas que coincidan con tu búsqueda.

- -
-
-
- {/if} - {/if} -
-
-
-
-
- - - - -
-
-
- -
-
- - {isEditMode ? 'Editar Plantilla' : 'Nueva Plantilla'} - - - Configura los items que compondrán esta plantilla. - -
-
-
- - -
-
- -
-
- -
-

- Información General -

-
-
- - -
-
- - -
-
-
- - -
-
-
-

- Partidas de la Plantilla -

-

- Estas partidas se inyectarán en la factura al cargar la plantilla. -

-
-
-
- - -
- -
-
- -
- - - - # - Descripción (ES) - Cantidad - Precio (USD) - Acciones - - - - {#if !selectedPreset.items || selectedPreset.items.length === 0} - - -
- -

No hay partidas agregadas aún.

- -
-
-
- {:else} - {#each selectedPreset.items as item, i} - - {i + 1} - -
- - {item?.description?.description_spanish || 'Sin descripción'} - - {#if item.reference_number} - - Ref: {item.reference_number} - - {/if} -
-
- {item?.quantity?.quantity || 0} - - ${(item?.financial?.unit_cost_usd || 0).toLocaleString()} - - -
- - -
-
-
- {/each} - {/if} -
-
-
- - {#if selectedPreset.items && selectedPreset.items.length > 0} -
-

- Total de partidas: {selectedPreset.items.length} -

-
- {/if} -
-
-
-
-
- - -{#if itemSystem === 'fixed_asset'} - (showItemSheet = false)} - isSaving={false} - /> -{:else} - (showItemSheet = false)} - isSaving={false} - /> -{/if} diff --git a/frontend/src/routes/dashboard/invoices/settings/+page.server.ts b/frontend/src/routes/dashboard/invoices/settings/+page.server.ts deleted file mode 100644 index e6caab03..00000000 --- a/frontend/src/routes/dashboard/invoices/settings/+page.server.ts +++ /dev/null @@ -1,265 +0,0 @@ -import type { PageServerLoad } from './$types'; -import { error, redirect } from '@sveltejs/kit'; -import { getAuthTokens, getActiveCompanyId, authenticatedFetch } from '$lib/server/api'; -import { redirectToWorkspaceLogin } from '$lib/server/workspace-auth'; - -export const load: PageServerLoad = async ({ cookies, fetch, url }) => { - const { accessToken } = getAuthTokens(cookies); - - if (!accessToken) { - redirectToWorkspaceLogin(cookies, url); - } - - const companyId = await getActiveCompanyId(cookies, fetch); - if (!companyId) { - throw error(400, 'No se encontró una compañía seleccionada'); - } - - const operationTypeParam = url.searchParams.get('operation_type'); - const invoiceTypeParam = url.searchParams.get('invoice_type'); - - let parsedOperationType: string | null = null; - if (operationTypeParam && (operationTypeParam === 'exp' || operationTypeParam === 'imp')) { - parsedOperationType = operationTypeParam; - } - - // Load necessary reference data for the settings form - // We need: InvoiceTypes, CustomsBrokers, Incoterms, etc. to populate the reusable forms - - // We load minimal data initially, but to support all tabs we need more. - // Reuse the same calls as in edit/[id] to ensure we have data for the dropdowns - - const invoiceTypesPromise = authenticatedFetch( - 'v1/public/reference_data/invoice-types?page=1&page_size=100', - {}, - cookies, - fetch - ); - - const customsBrokersPromise = authenticatedFetch( - `v1/a76/customs-brokers/?company_id=${companyId}&page=1&page_size=100`, - {}, - cookies, - fetch - ); - - const clientsPromise = authenticatedFetch( - `v1/a76/clients-providers/?company_id=${companyId}&type=client&page=1&page_size=1000`, - {}, - cookies, - fetch - ); - - const providersPromise = authenticatedFetch( - `v1/a76/clients-providers/?company_id=${companyId}&type=provider&page=1&page_size=1000`, - {}, - cookies, - fetch - ); - - const currencyTypesPromise = authenticatedFetch( - 'v1/public/reference_data/currency-types/?page=1&page_size=100', - {}, - cookies, - fetch - ); - - const transportTypesPromise = authenticatedFetch( - 'v1/public/reference_data/transport-types/?page=1&page_size=100', - {}, - cookies, - fetch - ); - - const transportersPromise = authenticatedFetch( - `v1/a76/transportation/transporters/?company_id=${companyId}&page=1&page_size=100`, - {}, - cookies, - fetch - ); - - const vehiclesPromise = authenticatedFetch( - `v1/a76/transportation/vehicles/?company_id=${companyId}&page=1&page_size=100`, - {}, - cookies, - fetch - ); - - const driversPromise = authenticatedFetch( - `v1/a76/transportation/drivers/?company_id=${companyId}`, - {}, - cookies, - fetch - ); - - const trailersPromise = authenticatedFetch( - `v1/a76/transportation/trailers/?company_id=${companyId}&page=1&page_size=100`, - {}, - cookies, - fetch - ); - - const incotermsPromise = authenticatedFetch( - 'v1/public/reference_data/incoterms/?page=1&page_size=100', - {}, - cookies, - fetch - ); - - const customsSectionsPromise = authenticatedFetch( - 'v1/public/reference_data/customs-sections/?page=1&page_size=100', - {}, - cookies, - fetch - ); - - const codePedimentoRegimensPromise = authenticatedFetch( - 'v1/public/reference_data/code-pedimento-regimens/?page=1&page_size=1000', - {}, - cookies, - fetch - ); - - const transportModesPromise = authenticatedFetch( - 'v1/public/reference_data/transport-modes/?page=1&page_size=100', - {}, - cookies, - fetch - ); - - const valuationMethodsPromise = authenticatedFetch( - 'v1/public/reference_data/valuation-methods/?page=1&page_size=100', - {}, - cookies, - fetch - ); - - const legendsPromise = authenticatedFetch( - `v1/a76/legends/?company_id=${companyId}&page=1&page_size=200`, - {}, - cookies, - fetch - ); - - const sealsPromise = authenticatedFetch( - `v1/a76/seals/?company_id=${companyId}&page=1&page_size=100`, - {}, - cookies, - fetch - ); - - const enclosuresPromise = authenticatedFetch( - 'v1/public/reference_data/customs-warehouses/?page=1&page_size=100', - {}, - cookies, - fetch - ); - - const pedimentosPromise = authenticatedFetch( - `v1/a76/pedimentos/?company_id=${companyId}&page=1&page_size=100`, - {}, - cookies, - fetch - ); - - try { - const [ - invoiceTypesResponse, - customsBrokersResponse, - clientsResponse, - providersResponse, - currencyTypesResponse, - transportTypesResponse, - transportersResponse, - vehiclesResponse, - driversResponse, - trailersResponse, - incotermsResponse, - customsSectionsResponse, - codePedimentoRegimensResponse, - transportModesResponse, - valuationMethodsResponse, - legendsResponse, - sealsResponse, - enclosuresResponse, - pedimentosResponse - ] = await Promise.all([ - invoiceTypesPromise, - customsBrokersPromise, - clientsPromise, - providersPromise, - currencyTypesPromise, - transportTypesPromise, - transportersPromise, - vehiclesPromise, - driversPromise, - trailersPromise, - incotermsPromise, - customsSectionsPromise, - codePedimentoRegimensPromise, - transportModesPromise, - valuationMethodsPromise, - legendsPromise, - sealsPromise, - enclosuresPromise, - pedimentosPromise - ]); - - const invoiceTypes = invoiceTypesResponse.ok ? await invoiceTypesResponse.json() : { items: [] }; - const customsBrokers = customsBrokersResponse.ok ? await customsBrokersResponse.json() : { items: [] }; - const clients = clientsResponse.ok ? await clientsResponse.json() : { items: [] }; - const providers = providersResponse.ok ? await providersResponse.json() : { items: [] }; - const currencyTypes = currencyTypesResponse.ok ? await currencyTypesResponse.json() : { items: [] }; - const transportTypes = transportTypesResponse.ok ? await transportTypesResponse.json() : { items: [] }; - const transporters = transportersResponse.ok ? await transportersResponse.json() : { items: [] }; - const vehicles = vehiclesResponse.ok ? await vehiclesResponse.json() : { items: [] }; - const drivers = driversResponse.ok ? await driversResponse.json() : { items: [] }; - const trailers = trailersResponse.ok ? await trailersResponse.json() : { items: [] }; - const incoterms = incotermsResponse.ok ? await incotermsResponse.json() : { items: [] }; - const customsSections = customsSectionsResponse.ok ? await customsSectionsResponse.json() : { items: [] }; - const codePedimentoRegimens = codePedimentoRegimensResponse.ok ? await codePedimentoRegimensResponse.json() : { items: [] }; - const transportModes = transportModesResponse.ok ? await transportModesResponse.json() : { items: [] }; - const valuationMethods = valuationMethodsResponse.ok ? await valuationMethodsResponse.json() : { items: [] }; - const legends = legendsResponse.ok ? await legendsResponse.json() : { items: [] }; - const seals = sealsResponse.ok ? await sealsResponse.json() : { items: [] }; - const enclosures = enclosuresResponse.ok ? await enclosuresResponse.json() : { items: [] }; - const pedimentos = pedimentosResponse.ok ? await pedimentosResponse.json() : { items: [] }; - - return { - invoiceTypes: invoiceTypes.items || [], - customsBrokers: customsBrokers.items || [], - clients: clients.items || [], - providers: providers.items || [], - currencyTypes: currencyTypes.items || [], - transportTypes: transportTypes.items || [], - transporters: transporters.items || [], - vehicles: vehicles.items || [], - drivers: drivers.items || [], - trailers: trailers.items || [], - incoterms: incoterms.items || [], - customsSections: customsSections.items || [], - codePedimentoRegimens: codePedimentoRegimens.items || [], - transportModes: transportModes.items || [], - valuationMethods: valuationMethods.items || [], - legends: legends.items || [], - seals: seals.items || [], - enclosure: enclosures.items || [], - pedimentos: pedimentos.items || [], - companyId, - filters: { - operation_type: parsedOperationType, - invoice_type: invoiceTypeParam || null - } - }; - - } catch (err) { - console.error('Error loading settings data:', err); - return { - invoiceTypes: [], - customsBrokers: [], - incoterms: [], - customsSections: [], - companyId - }; - } -}; diff --git a/frontend/src/routes/dashboard/invoices/settings/+page.svelte b/frontend/src/routes/dashboard/invoices/settings/+page.svelte deleted file mode 100644 index 7b77885c..00000000 --- a/frontend/src/routes/dashboard/invoices/settings/+page.svelte +++ /dev/null @@ -1,866 +0,0 @@ - - - -
- -
-
-
- -

- Configuración de Facturas -

- {#if selectedOperationType} - - {operationTypeText} - - {/if} - {#if selectedInvoiceType} - - {invoiceTypesForOperation.find((t: any) => t.key === selectedInvoiceType)?.description || - data.invoiceTypes.find((t: any) => t.key === selectedInvoiceType)?.description || - selectedInvoiceType} - - {/if} -
-

- Define los valores predeterminados para la creación de facturas. -

-
-
- - - -
- {#if !selectedOperationType || selectedOperationType !== 'imp'} - - - Contexto de Configuración - Selecciona el contexto para editar sus valores por defecto. - - -
- - -
-
-
- {/if} - - {#if selectedInvoiceType && selectedOperationType} - {#if isLoading} -
-
- -

Cargando configuración...

-
-
- {:else} -
- -
-
-
- -
-

Valores Predeterminados del Encabezado

-
- - -
-
- - - - - - - - - -
-
- -
-
-

Configuración de Partidas

-

- La configuración predeterminada de partidas es limitada desde esta sección. Los valores se heredarán de los maestros de productos cuando sea posible. -

-
-
-
- - - - - - -
- {#if invoiceType !== 'MEX'} -
-

Captura de Valores por Defecto

- -
- {/if} -
-
- - -
-
-
-

- {#if selectedOperationType === 'exp'} - Parámetros de Exportación — {data.invoiceTypes?.find((t: any) => t.key === invoiceType)?.description || invoiceType} - {:else} - {#if invoiceType === 'CR' || invoiceType === 'REP' || invoiceType === 'REPAR'} - Parámetros de Cambio de Régimen/Reparación — {invoiceType} - {:else if invoiceType === 'DEF'} - Parámetros Adicionales SSisDef - {:else if invoiceType === 'MEX'} - Parámetros Adicionales SSICM - {:else} - Parámetros Adicionales SSimp - {/if} - {/if} -

- - -
- - -
-
- - {#if invoiceType === 'CR' || selectedOperationType === 'exp' || invoiceType === 'REP' || invoiceType === 'REPAR'} - - {:else if invoiceType === 'DEF'} - - {:else if invoiceType === 'MEX'} - - {:else} - - {/if} -
-
-
-
-
- {/if} - {:else} -
-
- -
-

Sin Contexto de Edición

-

- Selecciona un tipo de operación y factura para comenzar a editar los valores globales por defecto. -

-
- {/if} -
-
- - -
-
- - {#if selectedInvoiceType && selectedOperationType} -
- - - - General - - - - Observaciones - - - - Partidas - - - - Otros - - - - Cont. - - - - Continuación - - -
- {/if} - -
- - - -
-
-
-
diff --git a/frontend/src/routes/dashboard/pedimentos/+page.server.ts b/frontend/src/routes/dashboard/pedimentos/+page.server.ts deleted file mode 100644 index 174ce6b5..00000000 --- a/frontend/src/routes/dashboard/pedimentos/+page.server.ts +++ /dev/null @@ -1,90 +0,0 @@ -import type { PageServerLoad } from './$types'; -import { redirect } from '@sveltejs/kit'; -import { getAuthTokens, authenticatedFetch } from '$lib/server/api'; -import { redirectToWorkspaceLogin } from '$lib/server/workspace-auth'; - -export const load: PageServerLoad = async ({ fetch, cookies, url, parent }) => { - // Esperar a que el layout padre valide/refresque el token - const parentData = await parent(); - - // Verificar autenticación - const { accessToken } = getAuthTokens(cookies); - - if (!accessToken) { - redirectToWorkspaceLogin(cookies, url); - } - - try { - // Obtener company_id de múltiples fuentes (en orden de prioridad): - // 1. URL query param (permite cambiar vía navegación) - // 2. Cookie active_company_id (setted por el team-switcher) - // 3. Primera compañía del usuario (fallback) - const companyIdParam = url.searchParams.get('company_id'); - const cookieCompanyId = cookies.get('active_company_id'); - - const companyId = companyIdParam - ? parseInt(companyIdParam) - : cookieCompanyId - ? parseInt(cookieCompanyId) - : parentData.companies?.[0]?.id; - - // Si aún no hay companyId, mostrar error - if (!companyId) { - return { - items: [], - total: 0, - page: 1, - page_size: 50, - error: 'No se encontró una compañía seleccionada', - companies: parentData.companies || [] - }; - } - - // Misma idea que facturas: lista desde API; permisos en cliente vía syncCompanyPermissions + userHasPermission - const response = await authenticatedFetch( - `v1/a76/pedimentos?company_id=${companyId}&page=1&page_size=50`, - {}, - cookies, - fetch, - '/login' - ); - - if (!response.ok) { - const message = - response.status === 403 - ? 'No tiene permiso para ver pedimentos' - : 'Error al cargar pedimentos'; - - return { - items: [], - total: 0, - page: 1, - page_size: 50, - error: message, - companies: parentData.companies || [], - currentCompanyId: companyId - }; - } - - const data = await response.json(); - - return { - items: data.items || [], - total: data.total || 0, - page: data.page || 1, - page_size: data.page_size || 50, - companies: parentData.companies || [], - currentCompanyId: companyId - }; - } catch (error) { - console.error('Error loading pedimentos:', error); - return { - items: [], - total: 0, - page: 1, - page_size: 50, - error: 'Error al cargar pedimentos', - companies: parentData.companies || [] - }; - } -}; diff --git a/frontend/src/routes/dashboard/pedimentos/+page.svelte b/frontend/src/routes/dashboard/pedimentos/+page.svelte deleted file mode 100644 index a85bb4e0..00000000 --- a/frontend/src/routes/dashboard/pedimentos/+page.svelte +++ /dev/null @@ -1,711 +0,0 @@ - - -{#if !$permissionsHydrated} -
- -

Verificando permisos…

-
-{:else if !canViewList} -
- -
-{:else} -
- -
-
-

{m['sidebar.pedimentos.pedimento_management']()}

-

{m['common.sub_pedimentos']()}

-
-
- - {#if canNuevoPedimentoAction} - - {/if} -
-
- - - {#if error} - - - Error - {error} - - - {/if} - - - - -
-
- Listado de Pedimentos -
-
- - - -
-
-
- - -
- (sorting = newSorting)} - /> -
-
-
- -
- Mostrando {allItems.length} de {totalItems} registros - - Filtros activos: {Object.values(filters).filter((value) => value !== '').length} -
- -
- - - -
-{/if} - - - - - - ¿Eliminar pedimento? - - Esta acción no se puede deshacer. El pedimento será eliminado permanentemente. - - -
- - -
-
-
- - - - - ¿Generar Interface Agente Aduanal? - - Se generará el reporte WINSAAI para el pedimento seleccionado. -
- - -
-
-
-
- - -
-
-
- -{#if showProgressDialog && currentTaskId} - (showProgressDialog = false)} - onComplete={onPdfComplete} - /> -{/if} diff --git a/frontend/src/routes/dashboard/pedimentos/edit/[id]/+page.server.ts b/frontend/src/routes/dashboard/pedimentos/edit/[id]/+page.server.ts deleted file mode 100644 index 65f89aea..00000000 --- a/frontend/src/routes/dashboard/pedimentos/edit/[id]/+page.server.ts +++ /dev/null @@ -1,118 +0,0 @@ -import type { PageServerLoad } from './$types'; -import { error, redirect } from '@sveltejs/kit'; -import { getAuthTokens, getActiveCompanyId, authenticatedFetch } from '$lib/server/api'; -import { redirectToWorkspaceLogin } from '$lib/server/workspace-auth'; - -export const load: PageServerLoad = async ({ params, cookies, fetch, url }) => { - const { accessToken } = getAuthTokens(cookies); - - if (!accessToken) { - redirectToWorkspaceLogin(cookies, url); - } - - // Obtener el company_id de la cookie para ambas ramas (new y edit) - const companyId = await getActiveCompanyId(cookies, fetch); - - if (!companyId) { - throw error(400, 'No se encontró una compañía seleccionada'); - } - - // Si el ID es "new", es una creación - if (params.id === 'new') { - try { - // Call consolidated creation endpoint - const response = await authenticatedFetch( - `v1/a76/pedimentos/creation-data?company_id=${companyId}`, - {}, - cookies, - fetch - ); - - if (!response.ok) { - console.error('Error fetching creation data:', response.status); - if (response.status === 403) { - throw error(403, 'No tiene permiso para crear pedimentos'); - } - if (response.status === 401) { - throw error(401, 'Tu sesión expiró. Vuelve a iniciar sesión.'); - } - if (response.status === 422) { - throw error(422, 'Parámetros inválidos para crear pedimento.'); - } - throw error(response.status, 'No fue posible preparar el formulario de creación.'); - } - - const data = await response.json(); - - return { - pedimento: null, - pedimentoId: null, - isCreate: true, - pedimentoCodes: data.pedimento_codes || [], - customsSections: data.customs_sections || [], - customsBrokers: data.customs_brokers || [], - clients: data.clients || [], - codePedimentoRegimens: data.code_pedimento_regimens || [], - transportTypes: data.transport_types || [], - transportModes: data.transport_modes || [], - pedimentoTransportCatalog: data.pedimento_transport_catalog || [] - }; - } catch (e) { - console.error('❌ Error loading new pedimento data:', e); - if (e && typeof e === 'object' && 'status' in e) { - throw e; - } - throw error(500, 'Error al cargar catálogos de creación'); - } - } - - const pedimentoId = parseInt(params.id); - if (isNaN(pedimentoId)) { - throw error(400, 'ID de pedimento inválido'); - } - - try { - // Call consolidated edition endpoint - const response = await authenticatedFetch( - `v1/a76/pedimentos/${pedimentoId}/edition-data?company_id=${companyId}`, - {}, - cookies, - fetch - ); - - if (!response.ok) { - if (response.status === 404) { - throw error(404, 'Pedimento no encontrado'); - } - if (response.status === 401) { - redirectToWorkspaceLogin(cookies, url); - } - if (response.status === 403) { - throw error(403, 'No tiene permiso para editar pedimentos'); - } - throw error(response.status, 'Error al cargar el pedimento'); - } - - const data = await response.json(); - - return { - pedimento: data.pedimento, - pedimentoId: data.pedimento_id, - isCreate: false, - pedimentoCodes: data.pedimento_codes || [], - customsSections: data.customs_sections || [], - customsBrokers: data.customs_brokers || [], - clients: data.clients || [], - codePedimentoRegimens: data.code_pedimento_regimens || [], - transportTypes: data.transport_types || [], - transportModes: data.transport_modes || [], - pedimentoTransportCatalog: data.pedimento_transport_catalog || [] - }; - } catch (e) { - console.error('Error loading pedimento:', e); - if (e instanceof Error && 'status' in e) { - throw e; - } - throw error(500, 'Error al cargar el pedimento'); - } -}; diff --git a/frontend/src/routes/dashboard/pedimentos/edit/[id]/+page.svelte b/frontend/src/routes/dashboard/pedimentos/edit/[id]/+page.svelte deleted file mode 100644 index f3b8f5f1..00000000 --- a/frontend/src/routes/dashboard/pedimentos/edit/[id]/+page.svelte +++ /dev/null @@ -1,1345 +0,0 @@ - - -{#if !$permissionsHydrated} -
- -

Verificando permisos…

-
-{:else if !canUsePedimentoEditor} -
- -
-{:else} -
- -
-
-
- -

- {#if data.isCreate} - Nuevo Pedimento - {:else} - Pedimento #{data.pedimento?.id ?? ''} - {/if} -

- {#if data.isCreate} - Nuevo - {/if} - {#if generalFormData?.status || data.pedimento?.status} - - {generalFormData?.status || data.pedimento?.status} - - {/if} - {#if data.pedimento?.pedimento_rectification_origin || otrosDatosFormData?.es_rectificacion === 'si'} - R1 - {/if} -
-

- {#if generalFormData?.year && generalFormData?.customs_office && generalFormData?.license && generalFormData?.pedimento_number} - Número: {generalFormData.year}-{(generalFormData.customs_office ?? '').toString().slice(0, 2)}-{generalFormData.license}-{generalFormData.pedimento_number} - {:else if !data.isCreate && data.pedimento?.pedimento_number} - Número: {data.pedimento?.year ?? ''}-{(data.pedimento?.customs_office ?? '').toString().slice(0, 2)}-{data - .pedimento?.license ?? ''}-{data.pedimento?.pedimento_number ?? ''} - {:else} - Edita los detalles del pedimento - {/if} -

-
-
- - - - - { - if (v) activeTab = v; - }} - class="space-y-4" - > - -
- - - - - - - - - - - - - - - - - - - - - - - - {#key data.pedimento?.id} - - {/key} - - - - -
- - -
-
- -
- - - - General - - - - Observ. - - - - - - Bultos/Trans. - - - - - Contribuc. - - - - Cuentas/Comp. - - - - Otros - - - - Digital. - - -
- - -
- - -
-
-
-
- - - {#if data.isCreate} - (showPrerequisitesModal = false)} - onCancel={() => goto('/dashboard/pedimentos')} - /> - {/if} -
-{/if} diff --git a/frontend/src/routes/dashboard/reference_data/+page.svelte b/frontend/src/routes/dashboard/reference_data/+page.svelte deleted file mode 100644 index e69de29b..00000000 diff --git a/frontend/src/routes/dashboard/reference_data/code_pedimento_regimens/+page.server.ts b/frontend/src/routes/dashboard/reference_data/code_pedimento_regimens/+page.server.ts deleted file mode 100644 index 96ac3a7b..00000000 --- a/frontend/src/routes/dashboard/reference_data/code_pedimento_regimens/+page.server.ts +++ /dev/null @@ -1,86 +0,0 @@ -import type { PageServerLoad } from './$types'; -import { getAuthTokens, authenticatedFetch } from '$lib/server/api'; - -export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => { - // Esperar a que el layout padre valide/refresque el token - await parent(); - - const { accessToken } = getAuthTokens(cookies); - - if (!accessToken) { - return { - error: 'No authenticated', - items: [], - total: 0, - page: 1, - page_size: 50 - }; - } - - try { - // Obtener parámetros de paginación de la URL - const page = parseInt(url.searchParams.get('page') || '1'); - const pageSize = parseInt(url.searchParams.get('page_size') || '50'); - - const parentData = await parent(); - const cookieCompanyId = cookies.get('active_company_id'); - const companyId = cookieCompanyId - ? parseInt(cookieCompanyId) - : parentData.companies?.[0]?.id; - - if (!companyId) { - return { - error: 'No se encontró una compañía seleccionada', - items: [], - total: 0, - page, - page_size: pageSize - }; - } - - // Usar authenticatedFetch para manejar automáticamente el refresh de tokens - const response = await authenticatedFetch( - `v1/public/reference_data/code-pedimento-regimens/?page=${page}&page_size=${pageSize}&company_id=${companyId}`, - {}, - cookies, - fetch - ); - - if (!response.ok) { - const errorText = await response.text(); - console.error('📊 [Code Pedimento Regimens] API Error:', { - status: response.status, - statusText: response.statusText, - error: errorText - }); - - return { - status: response.status, - error: `Error ${response.status}: ${response.statusText}`, - items: [], - total: 0, - page: page, - page_size: pageSize - }; - } - - const data = await response.json(); - - return { - items: data.items || [], - total: data.total || 0, - page: data.page || page, - page_size: data.page_size || pageSize, - error: null - }; - } catch (error) { - console.error('📊 [Code Pedimento Regimens] Load error:', error); - return { - error: 'Error loading data', - items: [], - total: 0, - page: 1, - page_size: 50 - }; - } -}; diff --git a/frontend/src/routes/dashboard/reference_data/code_pedimento_regimens/+page.svelte b/frontend/src/routes/dashboard/reference_data/code_pedimento_regimens/+page.svelte deleted file mode 100644 index 4e4cacbe..00000000 --- a/frontend/src/routes/dashboard/reference_data/code_pedimento_regimens/+page.svelte +++ /dev/null @@ -1,192 +0,0 @@ - - -
- {#if isError} - - {:else} -
-
-

{m['sidebar.reference_data.codes_pedimento_regimen']()}

-

- {m['common.sub_code_pedimento_regimens']()} -

-
-
- -
-
- - {#if error} -
- {error} -
- {/if} - - - -
- Listado de Pedimento - Regímenes -
- -
-
-
- -
- -
-
-
- -
- Mostrando {allItems.length} de {totalItems} registros -
- {/if} -
diff --git a/frontend/src/routes/dashboard/reference_data/containers/+page.server.ts b/frontend/src/routes/dashboard/reference_data/containers/+page.server.ts deleted file mode 100644 index b306934b..00000000 --- a/frontend/src/routes/dashboard/reference_data/containers/+page.server.ts +++ /dev/null @@ -1,87 +0,0 @@ -import type { PageServerLoad } from './$types'; -import { getAuthTokens, authenticatedFetch } from '$lib/server/api'; - -export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => { - // Esperar a que el layout padre valide/refresque el token - await parent(); - - const { accessToken } = getAuthTokens(cookies); - - if (!accessToken) { - return { - error: 'No authenticated', - items: [], - total: 0, - page: 1, - page_size: 50 - }; - } - - try { - // Obtener parámetros de paginación de la URL - const page = parseInt(url.searchParams.get('page') || '1'); - const pageSize = parseInt(url.searchParams.get('page_size') || '50'); - - const parentData = await parent(); - const cookieCompanyId = cookies.get('active_company_id'); - const companyId = cookieCompanyId - ? parseInt(cookieCompanyId) - : parentData.companies?.[0]?.id; - - if (!companyId) { - return { - error: 'No se encontró una compañía seleccionada', - items: [], - total: 0, - page, - page_size: pageSize - }; - } - - // Usar authenticatedFetch para manejar automáticamente el refresh de tokens - const response = await authenticatedFetch( - `v1/public/reference_data/containers/?page=${page}&page_size=${pageSize}&company_id=${companyId}`, - {}, - cookies, - fetch - ); - - if (!response.ok) { - const errorText = await response.text(); - console.error('📊 [Containers] API Error:', { - status: response.status, - statusText: response.statusText, - error: errorText - }); - - return { - status: response.status, - error: `Error ${response.status}: ${response.statusText}`, - items: [], - total: 0, - page: page, - page_size: pageSize - }; - } - - const data = await response.json(); - - return { - items: data.items || [], - total: data.total || 0, - page: data.page || page, - page_size: data.page_size || pageSize, - status: response.status, - error: null - }; - } catch (error) { - console.error('📊 [Containers] Load error:', error); - return { - error: 'Error loading data', - items: [], - total: 0, - page: 1, - page_size: 50 - }; - } -}; diff --git a/frontend/src/routes/dashboard/reference_data/containers/+page.svelte b/frontend/src/routes/dashboard/reference_data/containers/+page.svelte deleted file mode 100644 index 13e35219..00000000 --- a/frontend/src/routes/dashboard/reference_data/containers/+page.svelte +++ /dev/null @@ -1,172 +0,0 @@ - - -
-
-
-

{m['sidebar.reference_data.containers']()}

-

{m['common.sub_containers']()}

-
-
- - {#if !isError && canCreate} - - {/if} -
-
- - {#if isError} - - {:else} - - -
- Listado de Contenedores -
- -
-
-
- -
- -
-
-
- -
Mostrando {allItems.length} de {totalItems} registros
- {/if} -
diff --git a/frontend/src/routes/dashboard/reference_data/countries/+page.server.ts b/frontend/src/routes/dashboard/reference_data/countries/+page.server.ts deleted file mode 100644 index fa2b8a6e..00000000 --- a/frontend/src/routes/dashboard/reference_data/countries/+page.server.ts +++ /dev/null @@ -1,87 +0,0 @@ -import type { PageServerLoad } from './$types'; -import { getAuthTokens, authenticatedFetch } from '$lib/server/api'; - -export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => { - // Esperar a que el layout padre valide/refresque el token - await parent(); - - const { accessToken } = getAuthTokens(cookies); - - if (!accessToken) { - return { - error: 'No authenticated', - items: [], - total: 0, - page: 1, - page_size: 50 - }; - } - - try { - // Obtener parámetros de paginación de la URL - const page = parseInt(url.searchParams.get('page') || '1'); - const pageSize = parseInt(url.searchParams.get('page_size') || '50'); - - const parentData = await parent(); - const cookieCompanyId = cookies.get('active_company_id'); - const companyId = cookieCompanyId - ? parseInt(cookieCompanyId) - : parentData.companies?.[0]?.id; - - if (!companyId) { - return { - error: 'No se encontró una compañía seleccionada', - items: [], - total: 0, - page, - page_size: pageSize - }; - } - - // Usar authenticatedFetch para manejar automáticamente el refresh de tokens - const response = await authenticatedFetch( - `v1/public/reference_data/countries/?page=${page}&page_size=${pageSize}&company_id=${companyId}`, - {}, - cookies, - fetch - ); - - if (!response.ok) { - const errorText = await response.text(); - console.error('📊 [Countries] API Error:', { - status: response.status, - statusText: response.statusText, - error: errorText - }); - - return { - status: response.status, - error: `Error ${response.status}: ${response.statusText}`, - items: [], - total: 0, - page: page, - page_size: pageSize - }; - } - - const data = await response.json(); - - return { - items: data.items || [], - total: data.total || 0, - page: data.page || page, - page_size: data.page_size || pageSize, - status: response.status, - error: null - }; - } catch (error) { - console.error('📊 [Countries] Load error:', error); - return { - error: 'Error loading data', - items: [], - total: 0, - page: 1, - page_size: 50 - }; - } -}; diff --git a/frontend/src/routes/dashboard/reference_data/countries/+page.svelte b/frontend/src/routes/dashboard/reference_data/countries/+page.svelte deleted file mode 100644 index 81ef63cd..00000000 --- a/frontend/src/routes/dashboard/reference_data/countries/+page.svelte +++ /dev/null @@ -1,222 +0,0 @@ - - -
- {#if isError} - - {:else} - -
-
-

- {m['sidebar.reference_data.countries']()} -

-

- {m['common.sub_countries']()} -

-
-
- - {#if canCreate} - - {/if} -
-
- - {#if error} -
- {error} -
- {/if} - - - -
- Listado de Países -
- -
-
-
- -
- -
-
-
- -
Mostrando {allItems.length} de {totalItems} registros
- {/if} -
diff --git a/frontend/src/routes/dashboard/reference_data/currency_types/+page.server.ts b/frontend/src/routes/dashboard/reference_data/currency_types/+page.server.ts deleted file mode 100644 index dd26cf22..00000000 --- a/frontend/src/routes/dashboard/reference_data/currency_types/+page.server.ts +++ /dev/null @@ -1,87 +0,0 @@ -import type { PageServerLoad } from './$types'; -import { getAuthTokens, authenticatedFetch } from '$lib/server/api'; - -export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => { - // Esperar a que el layout padre valide/refresque el token - await parent(); - - const { accessToken } = getAuthTokens(cookies); - - if (!accessToken) { - return { - error: 'No authenticated', - items: [], - total: 0, - page: 1, - page_size: 50 - }; - } - - try { - // Obtener parámetros de paginación de la URL - const page = parseInt(url.searchParams.get('page') || '1'); - const pageSize = parseInt(url.searchParams.get('page_size') || '50'); - - const parentData = await parent(); - const cookieCompanyId = cookies.get('active_company_id'); - const companyId = cookieCompanyId - ? parseInt(cookieCompanyId) - : parentData.companies?.[0]?.id; - - if (!companyId) { - return { - error: 'No se encontró una compañía seleccionada', - items: [], - total: 0, - page, - page_size: pageSize - }; - } - - // Usar authenticatedFetch para manejar automáticamente el refresh de tokens - const response = await authenticatedFetch( - `v1/public/reference_data/currency-types/?page=${page}&page_size=${pageSize}&company_id=${companyId}`, - {}, - cookies, - fetch - ); - - if (!response.ok) { - const errorText = await response.text(); - console.error('📊 [CurrencyTypes] API Error:', { - status: response.status, - statusText: response.statusText, - error: errorText - }); - - return { - status: response.status, - error: `Error ${response.status}: ${response.statusText}`, - items: [], - total: 0, - page: page, - page_size: pageSize - }; - } - - const data = await response.json(); - - return { - items: data.items || [], - total: data.total || 0, - page: data.page || page, - page_size: data.page_size || pageSize, - status: response.status, - error: null - }; - } catch (error) { - console.error('📊 [CurrencyTypes] Load error:', error); - return { - error: 'Error loading data', - items: [], - total: 0, - page: 1, - page_size: 50 - }; - } -}; diff --git a/frontend/src/routes/dashboard/reference_data/currency_types/+page.svelte b/frontend/src/routes/dashboard/reference_data/currency_types/+page.svelte deleted file mode 100644 index 714a9c73..00000000 --- a/frontend/src/routes/dashboard/reference_data/currency_types/+page.svelte +++ /dev/null @@ -1,208 +0,0 @@ - - -
- {#if isError} - - {:else} -
-
-

{m['sidebar.reference_data.currency_types']()}

-

{m['common.sub_currency_types']()}

-
-
- - {#if canCreate} - - {/if} -
-
- - {#if error} -
- {error} -
- {/if} - - - -
- Listado de Tipos de Moneda -
- -
-
-
- -
- -
-
-
- -
Mostrando {allItems.length} de {totalItems} registros
- {/if} -
diff --git a/frontend/src/routes/dashboard/reference_data/customs_sections/+page.server.ts b/frontend/src/routes/dashboard/reference_data/customs_sections/+page.server.ts deleted file mode 100644 index cf0c58e7..00000000 --- a/frontend/src/routes/dashboard/reference_data/customs_sections/+page.server.ts +++ /dev/null @@ -1,88 +0,0 @@ -import type { PageServerLoad } from './$types'; -import { getAuthTokens, authenticatedFetch } from '$lib/server/api'; - -export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => { - // Esperar a que el layout padre valide/refresque el token - await parent(); - - const { accessToken } = getAuthTokens(cookies); - - if (!accessToken) { - return { - error: 'No authenticated', - items: [], - total: 0, - page: 1, - page_size: 50 - }; - } - - try { - // Obtener parámetros de paginación de la URL - const page = parseInt(url.searchParams.get('page') || '1'); - const pageSize = parseInt(url.searchParams.get('page_size') || '50'); - - const parentData = await parent(); - const cookieCompanyId = cookies.get('active_company_id'); - const companyId = cookieCompanyId - ? parseInt(cookieCompanyId) - : parentData.companies?.[0]?.id; - - if (!companyId) { - return { - error: 'No se encontró una compañía seleccionada', - items: [], - total: 0, - page, - page_size: pageSize - }; - } - - // Usar authenticatedFetch para manejar automáticamente el refresh de tokens - const response = await authenticatedFetch( - `v1/public/reference_data/customs-sections/?page=${page}&page_size=${pageSize}&company_id=${companyId}`, - {}, - cookies, - fetch - ); - - if (!response.ok) { - const errorText = await response.text(); - console.error('📊 [CustomsSections] API Error:', { - status: response.status, - statusText: response.statusText, - error: errorText - }); - - return { - status: response.status, - error: `Error ${response.status}: ${response.statusText}`, - items: [], - total: 0, - page: page, - page_size: pageSize - }; - } - - const data = await response.json(); - - return { - items: data.items || [], - total: data.total || 0, - page: data.page || page, - page_size: data.page_size || pageSize, - companyId, - status: response.status, - error: null - }; - } catch (error) { - console.error('📊 [CustomsSections] Load error:', error); - return { - error: 'Error loading data', - items: [], - total: 0, - page: 1, - page_size: 50 - }; - } -}; diff --git a/frontend/src/routes/dashboard/reference_data/customs_sections/+page.svelte b/frontend/src/routes/dashboard/reference_data/customs_sections/+page.svelte deleted file mode 100644 index 2023dc02..00000000 --- a/frontend/src/routes/dashboard/reference_data/customs_sections/+page.svelte +++ /dev/null @@ -1,195 +0,0 @@ - - -
- {#if !$permissionsHydrated} -
- -

Verificando permisos…

-
- {:else if !canView || isError} - - {:else} - -
-
-

{m['sidebar.reference_data.customs_sections']()}

-

{m['common.sub_customs_sections']()}

-
-
- - {#if canCreate} - - {/if} -
-
- - {#if error} -
- {error} -
- {/if} - - - -
- Listado de Secciones Aduanales -
- -
-
-
- -
- -
-
-
- -
- Mostrando {allItems.length} de {totalItems} registros -
- {/if} -
diff --git a/frontend/src/routes/dashboard/reference_data/customs_warehouses/+page.server.ts b/frontend/src/routes/dashboard/reference_data/customs_warehouses/+page.server.ts deleted file mode 100644 index a1b6cf50..00000000 --- a/frontend/src/routes/dashboard/reference_data/customs_warehouses/+page.server.ts +++ /dev/null @@ -1,87 +0,0 @@ -import type { PageServerLoad } from './$types'; -import { getAuthTokens, authenticatedFetch } from '$lib/server/api'; - -export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => { - // Esperar a que el layout padre valide/refresque el token - await parent(); - - const { accessToken } = getAuthTokens(cookies); - - if (!accessToken) { - return { - error: 'No authenticated', - items: [], - total: 0, - page: 1, - page_size: 50 - }; - } - - try { - // Obtener parámetros de paginación de la URL - const page = parseInt(url.searchParams.get('page') || '1'); - const pageSize = parseInt(url.searchParams.get('page_size') || '50'); - - const parentData = await parent(); - const cookieCompanyId = cookies.get('active_company_id'); - const companyId = cookieCompanyId - ? parseInt(cookieCompanyId) - : parentData.companies?.[0]?.id; - - if (!companyId) { - return { - error: 'No se encontró una compañía seleccionada', - items: [], - total: 0, - page, - page_size: pageSize - }; - } - - // Usar authenticatedFetch para manejar automáticamente el refresh de tokens - const response = await authenticatedFetch( - `v1/public/reference_data/customs-warehouses/?page=${page}&page_size=${pageSize}&company_id=${companyId}`, - {}, - cookies, - fetch - ); - - if (!response.ok) { - const errorText = await response.text(); - console.error('📊 [CustomsWarehouses] API Error:', { - status: response.status, - statusText: response.statusText, - error: errorText - }); - - return { - status: response.status, - error: `Error ${response.status}: ${response.statusText}`, - items: [], - total: 0, - page: page, - page_size: pageSize - }; - } - - const data = await response.json(); - - return { - items: data.items || [], - total: data.total || 0, - page: data.page || page, - page_size: data.page_size || pageSize, - status: response.status, - error: null - }; - } catch (error) { - console.error('📊 [CustomsWarehouses] Load error:', error); - return { - error: 'Error loading data', - items: [], - total: 0, - page: 1, - page_size: 50 - }; - } -}; diff --git a/frontend/src/routes/dashboard/reference_data/customs_warehouses/+page.svelte b/frontend/src/routes/dashboard/reference_data/customs_warehouses/+page.svelte deleted file mode 100644 index f3c83ed0..00000000 --- a/frontend/src/routes/dashboard/reference_data/customs_warehouses/+page.svelte +++ /dev/null @@ -1,183 +0,0 @@ - - -
- {#if isError} - - {:else} -
-
-

{m['sidebar.reference_data.customs_warehouses']()}

-

{m['common.sub_customs_warehouses']()}

-
-
- - {#if canCreate} - - {/if} -
-
- - {#if error} -
- {error} -
- {/if} - - - -
- Listado de Recintos Fiscalizados -
- -
-
-
- -
- -
-
-
- -
Mostrando {allItems.length} de {totalItems} registros
- {/if} -
diff --git a/frontend/src/routes/dashboard/reference_data/document_types_digitization/+page.server.ts b/frontend/src/routes/dashboard/reference_data/document_types_digitization/+page.server.ts deleted file mode 100644 index 23052018..00000000 --- a/frontend/src/routes/dashboard/reference_data/document_types_digitization/+page.server.ts +++ /dev/null @@ -1,98 +0,0 @@ -import type { PageServerLoad } from './$types'; -import { getAuthTokens, authenticatedFetch } from '$lib/server/api'; - -export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => { - const parentData = await parent(); - const { accessToken } = getAuthTokens(cookies); - - if (!accessToken) { - return { - error: 'No authenticated', - items: [], - total: 0, - page: 1, - page_size: 50, - status: 401 - }; - } - - try { - const page = parseInt(url.searchParams.get('page') || '1'); - const pageSize = parseInt(url.searchParams.get('page_size') || '50'); - const search = url.searchParams.get('search') || ''; - const cookieCompanyId = cookies.get('active_company_id'); - const companyId = cookieCompanyId ? parseInt(cookieCompanyId) : parentData.companies?.[0]?.id; - - if (!companyId) { - return { - error: 'No se encontró una compañía seleccionada', - items: [], - total: 0, - page, - page_size: pageSize, - status: 400 - }; - } - - const params = new URLSearchParams({ - page: page.toString(), - page_size: pageSize.toString(), - company_id: companyId.toString() - }); - - if (search) { - params.append('search', search); - } - - const response = await authenticatedFetch( - `v1/a76/document-types-digitization/?${params.toString()}`, - {}, - cookies, - fetch - ); - - if (!response.ok) { - const message = - response.status === 403 - ? 'No tiene permiso para ver tipos de documento para digitalización' - : `Error ${response.status}: ${response.statusText}`; - if (response.status !== 403) { - const errorText = await response.text(); - console.error('📊 [Document Types Digitization] API Error:', { - status: response.status, - statusText: response.statusText, - error: errorText - }); - } - return { - error: message, - items: [], - total: 0, - page, - page_size: pageSize, - status: response.status - }; - } - - const data = await response.json(); - - return { - items: data.items || [], - total: data.total || 0, - page: data.page || page, - page_size: data.page_size || pageSize, - error: null, - status: 200 - }; - } catch (error) { - console.error('📊 [Document Types Digitization] Load error:', error); - return { - error: 'Error loading data', - items: [], - total: 0, - page: 1, - page_size: 50, - status: 500 - }; - } -}; \ No newline at end of file diff --git a/frontend/src/routes/dashboard/reference_data/document_types_digitization/+page.svelte b/frontend/src/routes/dashboard/reference_data/document_types_digitization/+page.svelte deleted file mode 100644 index d8394660..00000000 --- a/frontend/src/routes/dashboard/reference_data/document_types_digitization/+page.svelte +++ /dev/null @@ -1,197 +0,0 @@ - - -{#if isError} -
- -
-{:else} -
-
-
-

{m['sidebar.reference_data.document_types_digitization']()}

-

- {m['common.sub_document_types_digitization']()} -

-
-
- - {#if error} -
- {error} -
- {/if} - - - -
- Listado de tipos de documento -
- -
-
-
- -
- -
-
-
- -
Mostrando {allItems.length} de {totalItems} registros
-
-{/if} \ No newline at end of file diff --git a/frontend/src/routes/dashboard/reference_data/incoterms/+page.server.ts b/frontend/src/routes/dashboard/reference_data/incoterms/+page.server.ts deleted file mode 100644 index 4d3799a5..00000000 --- a/frontend/src/routes/dashboard/reference_data/incoterms/+page.server.ts +++ /dev/null @@ -1,94 +0,0 @@ -import type { PageServerLoad } from './$types'; -import { getAuthTokens, authenticatedFetch } from '$lib/server/api'; - -export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => { - // Esperar a que el layout padre valide/refresque el token - await parent(); - - const { accessToken } = getAuthTokens(cookies); - - if (!accessToken) { - return { - error: 'No authenticated', - items: [], - total: 0, - page: 1, - page_size: 50 - }; - } - - try { - // Obtener parámetros de paginación y filtros de la URL - const page = parseInt(url.searchParams.get('page') || '1'); - const pageSize = parseInt(url.searchParams.get('page_size') || '50'); - const code = url.searchParams.get('code') || ''; - const description = url.searchParams.get('description') || ''; - - const parentData = await parent(); - const cookieCompanyId = cookies.get('active_company_id'); - const companyId = cookieCompanyId - ? parseInt(cookieCompanyId) - : parentData.companies?.[0]?.id; - - if (!companyId) { - return { - error: 'No se encontró una compañía seleccionada', - items: [], - total: 0, - page, - page_size: pageSize - }; - } - - // Construir URL con filtros - let endpoint = `v1/public/reference_data/incoterms/?page=${page}&page_size=${pageSize}&company_id=${companyId}`; - if (code) endpoint += `&code=${encodeURIComponent(code)}`; - if (description) endpoint += `&description=${encodeURIComponent(description)}`; - - // Usar authenticatedFetch para manejar automáticamente el refresh de tokens - const response = await authenticatedFetch( - endpoint, - {}, - cookies, - fetch - ); - - if (!response.ok) { - const errorText = await response.text(); - console.error('📊 [Incoterms] API Error:', { - status: response.status, - statusText: response.statusText, - error: errorText - }); - - return { - status: response.status, - error: `Error ${response.status}: ${response.statusText}`, - items: [], - total: 0, - page: page, - page_size: pageSize - }; - } - - const data = await response.json(); - - return { - items: data.items || [], - total: data.total || 0, - page: data.page || page, - page_size: data.page_size || pageSize, - status: response.status, - error: null - }; - } catch (error) { - console.error('📊 [Incoterms] Load error:', error); - return { - error: 'Error loading data', - items: [], - total: 0, - page: 1, - page_size: 50 - }; - } -}; diff --git a/frontend/src/routes/dashboard/reference_data/incoterms/+page.svelte b/frontend/src/routes/dashboard/reference_data/incoterms/+page.svelte deleted file mode 100644 index a1b20453..00000000 --- a/frontend/src/routes/dashboard/reference_data/incoterms/+page.svelte +++ /dev/null @@ -1,174 +0,0 @@ - - -{#if isError} -
- -
-{:else} -
- {#if error} -
- {error} -
- {/if} - - - -
- Listado de Incoterms -
- - -
-
-
- -
- -
-
-
- -
Mostrando {allItems.length} de {totalItems} registros
-
-{/if} diff --git a/frontend/src/routes/dashboard/reference_data/invoice_types/+page.server.ts b/frontend/src/routes/dashboard/reference_data/invoice_types/+page.server.ts deleted file mode 100644 index c944db5a..00000000 --- a/frontend/src/routes/dashboard/reference_data/invoice_types/+page.server.ts +++ /dev/null @@ -1,87 +0,0 @@ -import type { PageServerLoad } from './$types'; -import { getAuthTokens, authenticatedFetch } from '$lib/server/api'; - -export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => { - // Esperar a que el layout padre valide/refresque el token - await parent(); - - const { accessToken } = getAuthTokens(cookies); - - if (!accessToken) { - return { - error: 'No authenticated', - items: [], - total: 0, - page: 1, - page_size: 50 - }; - } - - try { - // Obtener parámetros de paginación de la URL - const page = parseInt(url.searchParams.get('page') || '1'); - const pageSize = parseInt(url.searchParams.get('page_size') || '50'); - - const parentData = await parent(); - const cookieCompanyId = cookies.get('active_company_id'); - const companyId = cookieCompanyId - ? parseInt(cookieCompanyId) - : parentData.companies?.[0]?.id; - - if (!companyId) { - return { - error: 'No se encontró una compañía seleccionada', - items: [], - total: 0, - page, - page_size: pageSize - }; - } - - // Usar authenticatedFetch para manejar automáticamente el refresh de tokens - const response = await authenticatedFetch( - `v1/public/reference_data/invoice-types/?page=${page}&page_size=${pageSize}&company_id=${companyId}`, - {}, - cookies, - fetch - ); - - if (!response.ok) { - const errorText = await response.text(); - console.error('📊 [Invoice Types] API Error:', { - status: response.status, - statusText: response.statusText, - error: errorText - }); - - return { - status: response.status, - error: `Error ${response.status}: ${response.statusText}`, - items: [], - total: 0, - page: page, - page_size: pageSize - }; - } - - const data = await response.json(); - - return { - items: data.items || [], - total: data.total || 0, - page: data.page || page, - page_size: data.page_size || pageSize, - status: response.status, - error: null - }; - } catch (error) { - console.error('📊 [Invoice Types] Load error:', error); - return { - error: 'Error loading data', - items: [], - total: 0, - page: 1, - page_size: 50 - }; - } -}; diff --git a/frontend/src/routes/dashboard/reference_data/invoice_types/+page.svelte b/frontend/src/routes/dashboard/reference_data/invoice_types/+page.svelte deleted file mode 100644 index d0b9f716..00000000 --- a/frontend/src/routes/dashboard/reference_data/invoice_types/+page.svelte +++ /dev/null @@ -1,185 +0,0 @@ - - -
- {#if isError} - - {:else} -
-
-

{m['sidebar.reference_data.invoice_types']()}

-

{m['common.sub_invoice_types']()}

-
-
- - {#if canCreate} - - {/if} -
-
- - {#if error} -
- {error} -
- {/if} - - - -
- Listado de Tipos de Factura -
- -
-
-
- -
- -
-
-
- -
Mostrando {allItems.length} de {totalItems} registros
- {/if} -
diff --git a/frontend/src/routes/dashboard/reference_data/material_types/+page.server.ts b/frontend/src/routes/dashboard/reference_data/material_types/+page.server.ts deleted file mode 100644 index 196dd23c..00000000 --- a/frontend/src/routes/dashboard/reference_data/material_types/+page.server.ts +++ /dev/null @@ -1,86 +0,0 @@ -import type { PageServerLoad } from './$types'; -import { getAuthTokens, authenticatedFetch } from '$lib/server/api'; - -export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => { - // Esperar a que el layout padre valide/refresque el token - await parent(); - - const { accessToken } = getAuthTokens(cookies); - - if (!accessToken) { - return { - error: 'No authenticated', - items: [], - total: 0, - page: 1, - page_size: 50 - }; - } - - try { - // Obtener parámetros de paginación de la URL - const page = parseInt(url.searchParams.get('page') || '1'); - const pageSize = parseInt(url.searchParams.get('page_size') || '50'); - - const parentData = await parent(); - const cookieCompanyId = cookies.get('active_company_id'); - const companyId = cookieCompanyId - ? parseInt(cookieCompanyId) - : parentData.companies?.[0]?.id; - - if (!companyId) { - return { - error: 'No se encontró una compañía seleccionada', - items: [], - total: 0, - page, - page_size: pageSize - }; - } - - // Usar authenticatedFetch para manejar automáticamente el refresh de tokens - const response = await authenticatedFetch( - `v1/public/reference_data/material-types/?page=${page}&page_size=${pageSize}&company_id=${companyId}`, - {}, - cookies, - fetch - ); - - if (!response.ok) { - const errorText = await response.text(); - console.error('📊 [Material Types] API Error:', { - status: response.status, - statusText: response.statusText, - error: errorText - }); - - return { - status: response.status, - error: `Error ${response.status}: ${response.statusText}`, - items: [], - total: 0, - page: page, - page_size: pageSize - }; - } - - const data = await response.json(); - - return { - items: data.items || [], - total: data.total || 0, - page: data.page || page, - page_size: data.page_size || pageSize, - error: null - }; - } catch (error) { - console.error('📊 [Material Types] Load error:', error); - return { - error: 'Error loading data', - items: [], - total: 0, - page: 1, - page_size: 50 - }; - } -}; diff --git a/frontend/src/routes/dashboard/reference_data/material_types/+page.svelte b/frontend/src/routes/dashboard/reference_data/material_types/+page.svelte deleted file mode 100644 index 07216932..00000000 --- a/frontend/src/routes/dashboard/reference_data/material_types/+page.svelte +++ /dev/null @@ -1,165 +0,0 @@ - - -
- {#if isError} - - {:else} - -
-
-

{m['sidebar.reference_data.material_types']()}

-

{m['common.sub_material_types']()}

-
-
- -
-
- - {#if error} -
- {error} -
- {/if} - - - -
- Listado de Tipos de Material -
- -
-
-
- -
- -
-
-
- -
Mostrando {allItems.length} de {totalItems} registros
- {/if} -
diff --git a/frontend/src/routes/dashboard/reference_data/payment_methods/+page.server.ts b/frontend/src/routes/dashboard/reference_data/payment_methods/+page.server.ts deleted file mode 100644 index c38acc95..00000000 --- a/frontend/src/routes/dashboard/reference_data/payment_methods/+page.server.ts +++ /dev/null @@ -1,87 +0,0 @@ -import type { PageServerLoad } from './$types'; -import { getAuthTokens, authenticatedFetch } from '$lib/server/api'; - -export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => { - // Esperar a que el layout padre valide/refresque el token - await parent(); - - const { accessToken } = getAuthTokens(cookies); - - if (!accessToken) { - return { - error: 'No authenticated', - items: [], - total: 0, - page: 1, - page_size: 50 - }; - } - - try { - // Obtener parámetros de paginación de la URL - const page = parseInt(url.searchParams.get('page') || '1'); - const pageSize = parseInt(url.searchParams.get('page_size') || '50'); - - const parentData = await parent(); - const cookieCompanyId = cookies.get('active_company_id'); - const companyId = cookieCompanyId - ? parseInt(cookieCompanyId) - : parentData.companies?.[0]?.id; - - if (!companyId) { - return { - error: 'No se encontró una compañía seleccionada', - items: [], - total: 0, - page, - page_size: pageSize - }; - } - - // Usar authenticatedFetch para manejar automáticamente el refresh de tokens - const response = await authenticatedFetch( - `v1/public/reference_data/payment-methods/?page=${page}&page_size=${pageSize}&company_id=${companyId}`, - {}, - cookies, - fetch - ); - - if (!response.ok) { - const errorText = await response.text(); - console.error('📊 [Payment Methods] API Error:', { - status: response.status, - statusText: response.statusText, - error: errorText - }); - - return { - status: response.status, - error: `Error ${response.status}: ${response.statusText}`, - items: [], - total: 0, - page: page, - page_size: pageSize - }; - } - - const data = await response.json(); - - return { - items: data.items || [], - total: data.total || 0, - page: data.page || page, - page_size: data.page_size || pageSize, - companyId, - error: null - }; - } catch (error) { - console.error('📊 [Payment Methods] Load error:', error); - return { - error: 'Error loading data', - items: [], - total: 0, - page: 1, - page_size: 50 - }; - } -}; diff --git a/frontend/src/routes/dashboard/reference_data/payment_methods/+page.svelte b/frontend/src/routes/dashboard/reference_data/payment_methods/+page.svelte deleted file mode 100644 index 249a69c8..00000000 --- a/frontend/src/routes/dashboard/reference_data/payment_methods/+page.svelte +++ /dev/null @@ -1,163 +0,0 @@ - - -
- {#if !canView} - - {:else if isError} - - {:else} - -
-
-

{m['sidebar.reference_data.payment_methods']()}

-

{m['common.sub_payment_methods']()}

-
-
- -
-
- - {#if error} -
- {error} -
- {/if} - - - -
- Listado de Métodos de Pago -
- -
-
-
- -
- -
-
-
- -
Mostrando {allItems.length} de {totalItems} registros
- {/if} -
diff --git a/frontend/src/routes/dashboard/reference_data/pedimento_codes/+page.server.ts b/frontend/src/routes/dashboard/reference_data/pedimento_codes/+page.server.ts deleted file mode 100644 index d8286116..00000000 --- a/frontend/src/routes/dashboard/reference_data/pedimento_codes/+page.server.ts +++ /dev/null @@ -1,87 +0,0 @@ -import type { PageServerLoad } from './$types'; -import { getAuthTokens, authenticatedFetch } from '$lib/server/api'; - -export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => { - // Esperar a que el layout padre valide/refresque el token - await parent(); - - const { accessToken } = getAuthTokens(cookies); - - if (!accessToken) { - return { - error: 'No authenticated', - items: [], - total: 0, - page: 1, - page_size: 50 - }; - } - - try { - // Obtener parámetros de paginación de la URL - const page = parseInt(url.searchParams.get('page') || '1'); - const pageSize = parseInt(url.searchParams.get('page_size') || '50'); - - const parentData = await parent(); - const cookieCompanyId = cookies.get('active_company_id'); - const companyId = cookieCompanyId - ? parseInt(cookieCompanyId) - : parentData.companies?.[0]?.id; - - if (!companyId) { - return { - error: 'No se encontró una compañía seleccionada', - items: [], - total: 0, - page, - page_size: pageSize - }; - } - - // Usar authenticatedFetch para manejar automáticamente el refresh de tokens - const response = await authenticatedFetch( - `v1/public/reference_data/pedimento-codes/?page=${page}&page_size=${pageSize}&company_id=${companyId}`, - {}, - cookies, - fetch - ); - - if (!response.ok) { - const errorText = await response.text(); - console.error('📊 [Pedimento Codes] API Error:', { - status: response.status, - statusText: response.statusText, - error: errorText - }); - - return { - status: response.status, - error: `Error ${response.status}: ${response.statusText}`, - items: [], - total: 0, - page: page, - page_size: pageSize - }; - } - - const data = await response.json(); - - return { - items: data.items || [], - total: data.total || 0, - page: data.page || page, - page_size: data.page_size || pageSize, - companyId, - error: null - }; - } catch (error) { - console.error('📊 [Pedimento Codes] Load error:', error); - return { - error: 'Error loading data', - items: [], - total: 0, - page: 1, - page_size: 50 - }; - } -}; diff --git a/frontend/src/routes/dashboard/reference_data/pedimento_codes/+page.svelte b/frontend/src/routes/dashboard/reference_data/pedimento_codes/+page.svelte deleted file mode 100644 index 060f2dbe..00000000 --- a/frontend/src/routes/dashboard/reference_data/pedimento_codes/+page.svelte +++ /dev/null @@ -1,175 +0,0 @@ - - -
- {#if !canView || isError} - - {:else} - -
-
-

{m['sidebar.reference_data.pedimento_codes']()}

-

{m['common.sub_pedimento_codes']()}

-
-
- -
-
- - {#if error} -
- {error} -
- {/if} - - - -
- Listado de Claves de Pedimento -
- -
-
-
- -
- -
-
-
- -
- Mostrando {allItems.length} de {totalItems} registros -
- {/if} -
diff --git a/frontend/src/routes/dashboard/reference_data/pedimento_regimens/+page.server.ts b/frontend/src/routes/dashboard/reference_data/pedimento_regimens/+page.server.ts deleted file mode 100644 index 17b81930..00000000 --- a/frontend/src/routes/dashboard/reference_data/pedimento_regimens/+page.server.ts +++ /dev/null @@ -1,88 +0,0 @@ -import type { PageServerLoad } from './$types'; -import { getAuthTokens, authenticatedFetch } from '$lib/server/api'; - -export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => { - // Esperar a que el layout padre valide/refresque el token - await parent(); - - const { accessToken } = getAuthTokens(cookies); - - if (!accessToken) { - return { - error: 'No authenticated', - items: [], - total: 0, - page: 1, - page_size: 50 - }; - } - - try { - // Obtener parámetros de paginación de la URL - const page = parseInt(url.searchParams.get('page') || '1'); - const pageSize = parseInt(url.searchParams.get('page_size') || '50'); - - const parentData = await parent(); - const cookieCompanyId = cookies.get('active_company_id'); - const companyId = cookieCompanyId - ? parseInt(cookieCompanyId) - : parentData.companies?.[0]?.id; - - if (!companyId) { - return { - error: 'No se encontró una compañía seleccionada', - items: [], - total: 0, - page, - page_size: pageSize - }; - } - - // Usar authenticatedFetch para manejar automáticamente el refresh de tokens - const response = await authenticatedFetch( - `v1/public/reference_data/pedimento-regimens/?page=${page}&page_size=${pageSize}&company_id=${companyId}`, - {}, - cookies, - fetch - ); - - if (!response.ok) { - const errorText = await response.text(); - console.error('📊 [Pedimento Regimens] API Error:', { - status: response.status, - statusText: response.statusText, - error: errorText - }); - - return { - status: response.status, - error: `Error ${response.status}: ${response.statusText}`, - items: [], - total: 0, - page: page, - page_size: pageSize - }; - } - - const data = await response.json(); - - return { - items: data.items || [], - total: data.total || 0, - page: data.page || page, - page_size: data.page_size || pageSize, - companyId, - status: response.status, - error: null - }; - } catch (error) { - console.error('📊 [Pedimento Regimens] Load error:', error); - return { - error: 'Error loading data', - items: [], - total: 0, - page: 1, - page_size: 50 - }; - } -}; diff --git a/frontend/src/routes/dashboard/reference_data/pedimento_regimens/+page.svelte b/frontend/src/routes/dashboard/reference_data/pedimento_regimens/+page.svelte deleted file mode 100644 index f95dd6cf..00000000 --- a/frontend/src/routes/dashboard/reference_data/pedimento_regimens/+page.svelte +++ /dev/null @@ -1,194 +0,0 @@ - - -
- {#if !canView || isError} - - {:else} -
-
-

{m['sidebar.reference_data.pedimento_regimes']()}

-

{m['common.sub_pedimento_regimens']()}

-
-
- - {#if canCreate} - - {/if} -
-
- - {#if error} -
- {error} -
- {/if} - - - -
- Listado de Regímenes -
- -
-
-
- -
- -
-
-
- -
- Mostrando {allItems.length} de {totalItems} registros -
- {/if} -
diff --git a/frontend/src/routes/dashboard/reference_data/sectors/+page.server.ts b/frontend/src/routes/dashboard/reference_data/sectors/+page.server.ts deleted file mode 100644 index 656becde..00000000 --- a/frontend/src/routes/dashboard/reference_data/sectors/+page.server.ts +++ /dev/null @@ -1,89 +0,0 @@ -import type { PageServerLoad } from './$types'; -import { getAuthTokens, authenticatedFetch } from '$lib/server/api'; - -export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => { - await parent(); - - const { accessToken } = getAuthTokens(cookies); - - if (!accessToken) { - return { - error: 'No authenticated', - items: [], - total: 0, - page: 1, - page_size: 50 - }; - } - - try { - const page = parseInt(url.searchParams.get('page') || '1'); - const pageSize = parseInt(url.searchParams.get('page_size') || '50'); - - const parentData = await parent(); - const cookieCompanyId = cookies.get('active_company_id'); - const companyId = cookieCompanyId - ? parseInt(cookieCompanyId) - : parentData.companies?.[0]?.id; - - if (!companyId) { - return { - error: 'No se encontró una compañía seleccionada', - items: [], - total: 0, - page, - page_size: pageSize - }; - } - - const params = new URLSearchParams({ - page: page.toString(), - page_size: pageSize.toString(), - company_id: companyId.toString() - }); - - const response = await authenticatedFetch( - `v1/a76/sectors/?${params.toString()}`, - {}, - cookies, - fetch - ); - - if (!response.ok) { - const errorText = await response.text(); - console.error('📊 [Sectors] API Error:', { - status: response.status, - statusText: response.statusText, - error: errorText - }); - - return { - status: response.status, - error: `Error ${response.status}: ${response.statusText}`, - items: [], - total: 0, - page, - page_size: pageSize - }; - } - - const data = await response.json(); - - return { - items: data.items || [], - total: data.total || 0, - page: data.page || page, - page_size: data.page_size || pageSize, - error: null - }; - } catch (error) { - console.error('📊 [Sectors] Load error:', error); - return { - error: 'Error loading data', - items: [], - total: 0, - page: 1, - page_size: 50 - }; - } -}; diff --git a/frontend/src/routes/dashboard/reference_data/sectors/+page.svelte b/frontend/src/routes/dashboard/reference_data/sectors/+page.svelte deleted file mode 100644 index 4713d9d6..00000000 --- a/frontend/src/routes/dashboard/reference_data/sectors/+page.svelte +++ /dev/null @@ -1,243 +0,0 @@ - - -
-
-
-

{m['sidebar.reference_data.sectors']()}

-

{m['common.sub_sectors']()}

-
-
- - {#if canCreate} - - {/if} -
-
- - {#if isError} - - {:else} - - -
- Listado de Sectores -
- -
-
-
- -
- (selectedIds = ids)} - onRowClick={(row) => (selectedIds = selectedIds.includes(row.id) ? [] : [row.id])} - onRowDoubleClick={(row) => { if (canEdit) handleEdit(row); }} - /> -
-
-
- -
Mostrando {allItems.length} de {totalItems} registros
- -
-
-
- {#if canEdit} - - {/if} - {#if canDelete} - - {/if} -
-
-
- {/if} -
- - - diff --git a/frontend/src/routes/dashboard/reference_data/states/+page.server.ts b/frontend/src/routes/dashboard/reference_data/states/+page.server.ts deleted file mode 100644 index 70984d75..00000000 --- a/frontend/src/routes/dashboard/reference_data/states/+page.server.ts +++ /dev/null @@ -1,90 +0,0 @@ -import type { PageServerLoad } from './$types'; -import { getAuthTokens, authenticatedFetch } from '$lib/server/api'; - -export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => { - // Esperar a que el layout padre valide/refresque el token - await parent(); - - const { accessToken } = getAuthTokens(cookies); - - if (!accessToken) { - return { - error: 'No authenticated', - items: [], - total: 0, - page: 1, - page_size: 50 - }; - } - - try { - // Obtener parámetros de paginación de la URL - const page = parseInt(url.searchParams.get('page') || '1'); - const pageSize = parseInt(url.searchParams.get('page_size') || '50'); - - // 🛡️ Buscamos el ID de la compañía (indispensable para el backend) - const companyIdStr = url.searchParams.get('company_id') || - cookies.get('active_company_id') || - cookies.get('activeCompanyId') || - cookies.get('company_id'); - - // Si no hay ID, devolvemos estructura vacía y evitamos el 422 de FastAPI - if (!companyIdStr) { - return { - items: [], - total: 0, - page: page, - page_size: pageSize, - error: null, - status: 200 - }; - } - - const companyId = parseInt(companyIdStr); - - // 🛡️ Inyectamos el company_id en la Query String - const response = await authenticatedFetch( - `v1/public/reference_data/states/?company_id=${companyId}&page=${page}&page_size=${pageSize}`, - {}, - cookies, - fetch - ); - - if (!response.ok) { - const errorText = await response.text(); - console.error('📊 [States] API Error:', { - status: response.status, - statusText: response.statusText, - error: errorText - }); - - return { - status: response.status, - error: `Error ${response.status}: ${response.statusText}`, - items: [], - total: 0, - page: page, - page_size: pageSize - }; - } - - const data = await response.json(); - - return { - items: data.items || [], - total: data.total || 0, - page: data.page || page, - page_size: data.page_size || pageSize, - error: null - }; - } catch (error) { - console.error('📊 [States] Load error:', error); - return { - error: 'Error loading data', - items: [], - total: 0, - page: 1, - page_size: 50 - }; - } -}; \ No newline at end of file diff --git a/frontend/src/routes/dashboard/reference_data/states/+page.svelte b/frontend/src/routes/dashboard/reference_data/states/+page.svelte deleted file mode 100644 index 69387421..00000000 --- a/frontend/src/routes/dashboard/reference_data/states/+page.svelte +++ /dev/null @@ -1,186 +0,0 @@ - - -
- {#if isError} - - {:else} -
-
-

{m['sidebar.reference_data.states']()}

-

{m['common.sub_states']()}

-
-
- -
-
- - {#if error} -
- {error} -
- {/if} - - - -
- Listado de Estados -
- -
-
-
- -
- -
-
-
- -
- Mostrando {allItems.length} de {totalItems} registros -
- {/if} -
diff --git a/frontend/src/routes/dashboard/reference_data/transport_modes/+page.server.ts b/frontend/src/routes/dashboard/reference_data/transport_modes/+page.server.ts deleted file mode 100644 index 9e355dec..00000000 --- a/frontend/src/routes/dashboard/reference_data/transport_modes/+page.server.ts +++ /dev/null @@ -1,92 +0,0 @@ -import type { PageServerLoad } from './$types'; -import { getAuthTokens, authenticatedFetch } from '$lib/server/api'; - -export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => { - // Esperar a que el layout padre valide/refresque el token - await parent(); - - const { accessToken } = getAuthTokens(cookies); - - if (!accessToken) { - return { - error: 'No authenticated', - items: [], - total: 0, - page: 1, - page_size: 50 - }; - } - - try { - // Obtener parámetros de paginación de la URL - const page = parseInt(url.searchParams.get('page') || '1'); - const pageSize = parseInt(url.searchParams.get('page_size') || '50'); - - - const companyIdStr = url.searchParams.get('company_id') || - cookies.get('active_company_id') || - cookies.get('company_id') || - cookies.get('activeCompanyId'); - - - if (!companyIdStr) { - console.log('⏳ [Transport Modes] SSR: Sin company_id, delegando fetch al cliente.'); - return { - items: [], - total: 0, - page: page, - page_size: pageSize, - status: 200, - error: null - }; - } - - const companyId = parseInt(companyIdStr); - - // 🛡️ Ahora SÍ le mandamos el company_id a FastAPI - const response = await authenticatedFetch( - `v1/public/reference_data/transport-modes/?company_id=${companyId}&page=${page}&page_size=${pageSize}`, - {}, - cookies, - fetch - ); - - if (!response.ok) { - const errorText = await response.text(); - console.error('📊 [Transport Modes] API Error:', { - status: response.status, - statusText: response.statusText, - error: errorText - }); - - return { - status: response.status, - error: `Error ${response.status}: ${response.statusText}`, - items: [], - total: 0, - page: page, - page_size: pageSize - }; - } - - const data = await response.json(); - - return { - items: data.items || [], - total: data.total || 0, - page: data.page || page, - page_size: data.page_size || pageSize, - status: response.status, - error: null - }; - } catch (error) { - console.error('📊 [Transport Modes] Load error:', error); - return { - error: 'Error loading data', - items: [], - total: 0, - page: 1, - page_size: 50 - }; - } -}; \ No newline at end of file diff --git a/frontend/src/routes/dashboard/reference_data/transport_modes/+page.svelte b/frontend/src/routes/dashboard/reference_data/transport_modes/+page.svelte deleted file mode 100644 index f98c7478..00000000 --- a/frontend/src/routes/dashboard/reference_data/transport_modes/+page.svelte +++ /dev/null @@ -1,202 +0,0 @@ - - -
- {#if isError} - - {:else} -
-
-

{m['sidebar.reference_data.transportation_modes']()}

-

- {m['common.sub_transport_modes']()} -

-
-
- - {#if canCreate} - - {/if} -
-
- - {#if error} -
- {error} -
- {/if} - - - -
- Listado de Métodos de Transporte -
- -
-
-
- -
- -
-
-
- -
- Mostrando {allItems.length} de {totalItems} registros -
- {/if} -
diff --git a/frontend/src/routes/dashboard/reference_data/transport_types/+page.server.ts b/frontend/src/routes/dashboard/reference_data/transport_types/+page.server.ts deleted file mode 100644 index a2d67715..00000000 --- a/frontend/src/routes/dashboard/reference_data/transport_types/+page.server.ts +++ /dev/null @@ -1,90 +0,0 @@ -import type { PageServerLoad } from './$types'; -import { getAuthTokens, authenticatedFetch } from '$lib/server/api'; - -export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => { - // Esperar a que el layout padre valide/refresque el token - await parent(); - - const { accessToken } = getAuthTokens(cookies); - - if (!accessToken) { - return { - error: 'No authenticated', - items: [], - total: 0, - page: 1, - page_size: 50 - }; - } - - try { - // Obtener parámetros de paginación de la URL - const page = parseInt(url.searchParams.get('page') || '1'); - const pageSize = parseInt(url.searchParams.get('page_size') || '50'); - - // 🛡️ Buscamos el ID de la compañía (indispensable para el backend) - const companyIdStr = url.searchParams.get('company_id') || - cookies.get('active_company_id') || - cookies.get('activeCompanyId') || - cookies.get('company_id'); - - // Si no hay ID, devolvemos estructura vacía y evitamos el 422 - if (!companyIdStr) { - return { - items: [], - total: 0, - page: page, - page_size: pageSize, - error: null, - status: 200 - }; - } - - const companyId = parseInt(companyIdStr); - - // 🛡️ Inyectamos el company_id en la Query String - const response = await authenticatedFetch( - `v1/public/reference_data/transport-types/?company_id=${companyId}&page=${page}&page_size=${pageSize}`, - {}, - cookies, - fetch - ); - - if (!response.ok) { - const errorText = await response.text(); - console.error('📊 [Transport Types] API Error:', { - status: response.status, - statusText: response.statusText, - error: errorText - }); - - return { - status: response.status, - error: `Error ${response.status}: ${response.statusText}`, - items: [], - total: 0, - page: page, - page_size: pageSize - }; - } - - const data = await response.json(); - - return { - items: data.items || [], - total: data.total || 0, - page: data.page || page, - page_size: data.page_size || pageSize, - error: null - }; - } catch (error) { - console.error('📊 [Transport Types] Load error:', error); - return { - error: 'Error loading data', - items: [], - total: 0, - page: 1, - page_size: 50 - }; - } -}; \ No newline at end of file diff --git a/frontend/src/routes/dashboard/reference_data/transport_types/+page.svelte b/frontend/src/routes/dashboard/reference_data/transport_types/+page.svelte deleted file mode 100644 index b5462afb..00000000 --- a/frontend/src/routes/dashboard/reference_data/transport_types/+page.svelte +++ /dev/null @@ -1,163 +0,0 @@ - - -
- {#if isError} - - {:else} - -
-
-

{m['sidebar.reference_data.transportation_types']()}

-

{m['common.sub_transport_types']()}

-
-
- -
-
- - {#if error} -
- {error} -
- {/if} - - - -
- Listado de Tipos de Transporte -
- -
-
-
- -
- -
-
-
- -
Mostrando {allItems.length} de {totalItems} registros
- {/if} -
diff --git a/frontend/src/routes/dashboard/reference_data/valuation_methods/+page.server.ts b/frontend/src/routes/dashboard/reference_data/valuation_methods/+page.server.ts deleted file mode 100644 index 0bfe3ce6..00000000 --- a/frontend/src/routes/dashboard/reference_data/valuation_methods/+page.server.ts +++ /dev/null @@ -1,91 +0,0 @@ -import type { PageServerLoad } from './$types'; -import { getAuthTokens, authenticatedFetch } from '$lib/server/api'; - -export const load: PageServerLoad = async ({ cookies, fetch, url, parent }) => { - // Esperar a que el layout padre valide/refresque el token - await parent(); - - const { accessToken } = getAuthTokens(cookies); - - if (!accessToken) { - return { - error: 'No authenticated', - items: [], - total: 0, - page: 1, - page_size: 50 - }; - } - - try { - // Obtener parámetros de paginación de la URL - const page = parseInt(url.searchParams.get('page') || '1'); - const pageSize = parseInt(url.searchParams.get('page_size') || '50'); - - // 🛡️ Buscamos el ID de la compañía en URL o Cookies - const companyIdStr = url.searchParams.get('company_id') || - cookies.get('active_company_id') || - cookies.get('activeCompanyId') || - cookies.get('company_id'); - - // 🛡️ Si no hay ID, abortamos elegantemente para evitar el 422 - if (!companyIdStr) { - return { - items: [], - total: 0, - page: page, - page_size: pageSize, - status: 200, - error: null - }; - } - - const companyId = parseInt(companyIdStr); - - // 🛡️ Inyectamos el company_id en la petición al backend - const response = await authenticatedFetch( - `v1/public/reference_data/valuation-methods/?company_id=${companyId}&page=${page}&page_size=${pageSize}`, - {}, - cookies, - fetch - ); - - if (!response.ok) { - const errorText = await response.text(); - console.error('📊 [Valuation Methods] API Error:', { - status: response.status, - statusText: response.statusText, - error: errorText - }); - - return { - status: response.status, - error: `Error ${response.status}: ${response.statusText}`, - items: [], - total: 0, - page: page, - page_size: pageSize - }; - } - - const data = await response.json(); - - return { - items: data.items || [], - total: data.total || 0, - page: data.page || page, - page_size: data.page_size || pageSize, - status: response.status, - error: null - }; - } catch (error) { - console.error('📊 [Valuation Methods] Load error:', error); - return { - error: 'Error loading data', - items: [], - total: 0, - page: 1, - page_size: 50 - }; - } -}; \ No newline at end of file diff --git a/frontend/src/routes/dashboard/reference_data/valuation_methods/+page.svelte b/frontend/src/routes/dashboard/reference_data/valuation_methods/+page.svelte deleted file mode 100644 index 08ef4f00..00000000 --- a/frontend/src/routes/dashboard/reference_data/valuation_methods/+page.svelte +++ /dev/null @@ -1,174 +0,0 @@ - - -
- {#if isError} - - {:else} -
-
-

{m['sidebar.reference_data.valuation_methods']()}

-

{m['common.sub_valuation_methods']()}

-
-
- - {#if canCreate} - - {/if} -
-
- - {#if error} -
- {error} -
- {/if} - - - -
- Listado de Métodos de Valoración -
- -
-
-
- -
- -
-
-
- -
Mostrando {allItems.length} de {totalItems} registros
- {/if} -
diff --git a/frontend/src/routes/dashboard/reports/invoices/+page.server.ts b/frontend/src/routes/dashboard/reports/invoices/+page.server.ts deleted file mode 100644 index b4131ab5..00000000 --- a/frontend/src/routes/dashboard/reports/invoices/+page.server.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type { PageServerLoad } from './$types'; -import { getAuthTokens } from '$lib/server/api'; -import { redirectToWorkspaceLogin } from '$lib/server/workspace-auth'; - -export const load: PageServerLoad = async ({ cookies, url }) => { - const { accessToken } = getAuthTokens(cookies); - - if (!accessToken) { - redirectToWorkspaceLogin(cookies, url); - } - - return { - title: 'Reportes de Facturas' - }; -}; diff --git a/frontend/src/routes/dashboard/reports/invoices/+page.svelte b/frontend/src/routes/dashboard/reports/invoices/+page.svelte deleted file mode 100644 index 8a6284aa..00000000 --- a/frontend/src/routes/dashboard/reports/invoices/+page.svelte +++ /dev/null @@ -1,2951 +0,0 @@ - - - - -
- -
-
-

- {#if activeReportType === 'invoices'} - - {m['common.title_invoice_report']()} - {:else if activeReportType === 'partes_descargadas'} - - - Partes Descargadas - {:else} - - - Saldos Temporales - {/if} -

- - V2.0 - -
-
- {#if activeReportType === 'invoices'} - Reportes de Movimientos Impo/Expo - {:else if activeReportType === 'partes_descargadas'} - Reportes de Partes Descargadas - {:else} - Reportes de Control Fiscal - {/if} -
-
- - - - {#if isError} - window.location.reload()} - /> - {:else} - - -
- {#each menuOptions as item} - {#if item.items && item.items.length > 0} - - - {#snippet child({ props })} - - {/snippet} - - - {item.label} - -
- {#each item.items as subItem} - { - if (subItem === 'Partes Descargadas') { - activeReportType = 'partes_descargadas'; - showResults = false; - } else { - toast.info(`Seleccionado: ${subItem}`); - } - }}> - {subItem} - - {/each} -
-
-
- {:else} - - {/if} - {/each} -
- - - - -
- {#if activeReportType === 'invoices'} - - - - - Periodo y Clasificación - - - -
-
- - { - const input = e.currentTarget; - if (input && typeof input.showPicker === 'function') { - input.showPicker(); - } - }} - /> -
-
- - { - const input = e.currentTarget; - if (input && typeof input.showPicker === 'function') { - input.showPicker(); - } - }} - /> -
-
- - - -
- -
- -
- {#each Object.keys(types.import) as key} -
- - -
- {/each} -
-
- - -
- -
- -
-
- - -
-
- - -
-
- - -
-
- - -
- {#each Object.keys(types.export.additional) as key} -
- - -
- {/each} -
-
-
- - -
- -
- {#each Object.keys(types.other) as key} -
- { - if (key === 'TODAS') handleTodasChange(v as boolean); - }} - /> - -
- {/each} -
-
-
-
-
- - - - - - Filtros e Identificadores - - - -
- {#each [{ label: 'Proveedor', key: 'provider' as const }, { label: 'Vendido a', key: 'soldTo' as const }, { label: 'Clave de Pedimento', key: 'pedimentoKey' as const }] as item} -
- -
- - -
-
- {/each} -
- -
- -
- -
- - -
- - -
- -
- -
- - -
-
- - -
-
- - -
-
-
-
-
-
- - - - - - Configuración Final - - - -
-
- - -
- - -
-
- - -
-
-
- -
- - -
- - -
-
- - -
-
- - -
-
-
-
- -
- -
- -
-
- - -
- - -
-
- - -
-
-
-
- - -
- - -
-
- - -
-
-
-
- -
- -
-
- - -
-
- - -
-
- - -
-
-
-
- - - - -
- {:else if activeReportType === 'saldos'} - - - - - - Periodo y Clasificación - - - - -
- -
-
- - - - - -
-
-
- - - -
- - {#if saldosRangeType === 'pedimento' || saldosRangeType === 'parts' || saldosRangeType === 'classes'} -
-
- -
- - -
-
-
- -
- - -
-
-
- {/if} - - - {#if saldosRangeType === 'payment_date' || saldosRangeType === 'invoice_date' || saldosRangeType === 'parts' || saldosRangeType === 'classes'} -
-
- - { - const input = e.currentTarget; - if (input && typeof input.showPicker === 'function') { - input.showPicker(); - } - }} - /> -
-
- - { - const input = e.currentTarget; - if (input && typeof input.showPicker === 'function') { - input.showPicker(); - } - }} - /> -
-
- {/if} -
- - - -
- -
-
- - -
-
- - -
-
-
-
- - - {#each [{ label: 'PARTE/CLASE', key: 'assetClass' as const }, { label: 'ACTIVO FIJO', key: 'assetType' as const }, { label: 'CLAVE PED', key: 'pedimentoKey' as const }, { label: 'CLIENTE', key: 'provider' as const }, { label: 'LOCALIZACION', key: 'location' as const }] as item} -
- -
- - -
-
- {/each} -
-
-
- - - - - - Filtros e Identificadores - - - -
- -
- - -
- - -
-
- - -
-
-
- - - - -
- - -
- - -
-
- - -
-
- - -
-
-
- - - - -
- - -
- - -
-
- - -
-
-
- - - - -
- - (filters.omitLowBalance = v === 'yes')} - class="flex flex-wrap gap-6" - > -
- - -
-
- - -
-
-
- - - - -
- - -
- - -
-
- - -
-
- - -
-
-
- - - - -
- - -
- - -
-
- - -
-
- - -
-
-
-
-
-
- - - - - - Configuración Final - - - -
- -
- {#each reportToggleOptions as opt} -
-
-
- -
- -
- -
- {/each} -
-
-
- - - - -
- {:else if activeReportType === 'partes_descargadas'} -
- -
- {/if} -
- - - { - if (!v) showResults = false; - }} - > - - -
-
- - - {reportTitle} - - - {results.length} registros encontrados • {currencyLabel} - -
-
- - - - Cerrar - -
-
-
- -
-
- - - - {#each config.reportType === 'normal' ? ['PEDIMENTO', 'CLAVE', 'FACTURA', 'FECHA FACT', 'VALOR COM.', 'TIPO OPER.', 'ESTATUS', 'PROYECTO'] : ['PEDIMENTO', 'FACTURA', 'FECHA FACT', 'PROVEEDOR', 'CLIENTE', 'CANTIDAD', 'DESC. ESPAÑOL', 'TIPO OPER.'] as header} - - {/each} - - - - {#each results as row} - - {#if config.reportType === 'normal'} - - - - - - - - - {:else} - {@const detailRow = row as MovementItemDetailed} - - - - - - - - - {/if} - - {/each} - -
- {header} -
{row.Pedimento || '-'}{row.ClavePed || '-'}{row.Factura}{formatDateFromYYYYMMDD(row.FechaFactura)}${formatCurrency(row.ValorComercialMN)} - - {row.TipoMovTemDef} - - - - {row.Estatus || 'A'} - - {row.BaseDeDatos}{detailRow.Pedimento || '-'}{detailRow.Factura}{formatDateFromYYYYMMDD(detailRow.FechaFactura)}{detailRow.Proveedor || '-'}{detailRow.VendidoA || '-'}{detailRow.CantidadIE || '0'}{detailRow.DescripcionE || '-'} - - {detailRow.TipoMovTemDef} - -
-
-
-
-
- - - - - - - Seleccionar {dialogType === 'pedimentoKey' - ? 'Clave de Pedimento' - : dialogType === 'assetClass' || dialogType === 'classFrom' || dialogType === 'classTo' - ? 'Clase' - : dialogType === 'assetType' - ? 'Tipo de Activo' - : dialogType === 'location' - ? 'Localización' - : dialogType === 'provider' - ? 'Proveedor' - : dialogType === 'soldTo' - ? 'Cliente' - : dialogType?.startsWith('pedimento') - ? 'Pedimento' - : 'Parte'} - - - Busca y selecciona {dialogType === 'pedimentoKey' - ? 'una clave de pedimento' - : dialogType === 'assetClass' || dialogType === 'classFrom' || dialogType === 'classTo' - ? 'una clase' - : dialogType === 'assetType' - ? 'un tipo de activo' - : dialogType === 'location' - ? 'una localización' - : dialogType === 'provider' - ? 'un proveedor' - : dialogType === 'soldTo' - ? 'un cliente' - : dialogType?.startsWith('pedimento') - ? 'un pedimento' - : 'una parte'} de la lista - - - -
-
- - -
- -
- {#if dialogType === 'pedimentoKey' || dialogType === 'assetClass' || dialogType === 'classFrom' || dialogType === 'classTo' || dialogType === 'assetType' || dialogType === 'location' || dialogType?.startsWith('pedimento') || dialogType?.startsWith('part')} - - - - - - - - - - {#if filteredItems.length === 0} - - - - {:else} - {#each filteredItems as item} - selectItem(item)} - > - - - - - {/each} - {/if} - -
CódigoDescripciónAcción
- No se encontraron resultados -
- {dialogType === 'pedimentoKey' - ? item.code || '-' - : dialogType === 'assetClass' || - dialogType === 'classFrom' || - dialogType === 'classTo' - ? item.class_code || '-' - : dialogType === 'assetType' - ? item.key || '-' - : dialogType === 'location' - ? item.location_code || '-' - : dialogType?.startsWith('pedimento') - ? item.pedimento_number || '-' - : item.part_number || '-'} - - {dialogType === 'pedimentoKey' - ? item.description || '-' - : dialogType === 'assetClass' || - dialogType === 'classFrom' || - dialogType === 'classTo' - ? item.description_es || '-' - : dialogType === 'assetType' - ? item.description || '-' - : dialogType === 'location' - ? item.location_description || '-' - : dialogType?.startsWith('pedimento') - ? `Aduana: ${item.customs_office} - Pat.: ${item.license}` - : item.description_spanish || '-'} - - -
- {:else} - - - - - - - - - - - - - - - - - {#if filteredItems.length === 0} - - - - {:else} - {#each filteredItems as item} - selectItem(item)} - > - - - - - - - - - - - - {/each} - {/if} - -
ClaveNombreTipoRFCCallesNúm. ExtCPColoniaCiudadAcción
- No se encontraron resultados -
{item.id || '-'}{item.name || '-'} - - {item.client_or_provider === 'provider' - ? 'P' - : item.client_or_provider === 'client' - ? 'C' - : 'A'} - - {item.rfc || '-'}{item.address?.streets || '-'}{item.address?.exterior_number || '-'}{item.address?.postal_code || '-'}{item.address?.neighborhood || '-'}{item.address?.city || '-'} - -
- {/if} -
-
- - - - -
-
- - -{#if contextMenu.open} -
- -
-{/if} - - {/if} -
- - diff --git a/frontend/src/routes/dashboard/reports/vencimiento/+page.server.ts b/frontend/src/routes/dashboard/reports/vencimiento/+page.server.ts deleted file mode 100644 index cc752309..00000000 --- a/frontend/src/routes/dashboard/reports/vencimiento/+page.server.ts +++ /dev/null @@ -1,15 +0,0 @@ -import type { PageServerLoad } from './$types'; -import { getAuthTokens } from '$lib/server/api'; -import { redirectToWorkspaceLogin } from '$lib/server/workspace-auth'; - -export const load: PageServerLoad = async ({ cookies, url }) => { - const { accessToken } = getAuthTokens(cookies); - - if (!accessToken) { - redirectToWorkspaceLogin(cookies, url); - } - - return { - title: 'Reporte de Vencimiento' - }; -}; diff --git a/frontend/src/routes/dashboard/reports/vencimiento/+page.svelte b/frontend/src/routes/dashboard/reports/vencimiento/+page.svelte deleted file mode 100644 index 5c29fff5..00000000 --- a/frontend/src/routes/dashboard/reports/vencimiento/+page.svelte +++ /dev/null @@ -1,313 +0,0 @@ - - -
- -
-
-

- - {m['sidebar.reports.expiration']()} -

-
-
{m['common.sub_expiration_report']()}
-
- - - -
- - - - - - Vencimiento - - - - -
-

- Facturas próximas a vencer dentro de los -

-
- - días. -
-
- - -
-

- Tipo de Moneda -

- -
- - -
-
- - -
-
-
-
-
- - - - - - Filtrar por - - - - -
-

- Cliente -

- (clientId = normalizeSelectValue(v))} - > - - - {#if clientId} - {@const selected = clientOptions.find((c) => String(c.id) === clientId)} - {selected?.name ?? selectPlaceholder()} - {:else} - {selectPlaceholder()} - {/if} - - - - Todos - {#if clientOptions.length} - {#each clientOptions as item} - {item.name} - {/each} - {:else} -
Sin opciones
- {/if} -
-
-
- - -
-

- Omitir cantidades con balance menor a -

- -
-
-
- - - - - - Filtro Opcional - - - -
-
- - -
-
- - -
- {#if usarFechaCorte} -
- - -
- {/if} -
-
-
- - - - -
-
- - -
-
- - -
-
-
- - - - -
-
-
diff --git a/frontend/src/routes/dashboard/roles/+page.server.ts b/frontend/src/routes/dashboard/roles/+page.server.ts new file mode 100644 index 00000000..45fd039d --- /dev/null +++ b/frontend/src/routes/dashboard/roles/+page.server.ts @@ -0,0 +1,5 @@ +import type { PageServerLoad } from './$types'; + +export const load: PageServerLoad = async () => { + return {}; +}; diff --git a/frontend/src/routes/dashboard/roles/+page.svelte b/frontend/src/routes/dashboard/roles/+page.svelte new file mode 100644 index 00000000..938eb899 --- /dev/null +++ b/frontend/src/routes/dashboard/roles/+page.svelte @@ -0,0 +1,26 @@ + + +
+
+

+ + Roles y permisos +

+

+ Gestión de roles y control de acceso. +

+
+ + + + Roles del sistema + Implementa aquí la gestión de roles y permisos de tu proyecto. + + +

Sección en construcción.

+
+
+
diff --git a/frontend/src/routes/dashboard/settings/general/+page.svelte b/frontend/src/routes/dashboard/settings/general/+page.svelte index ebd030fb..0f1206df 100644 --- a/frontend/src/routes/dashboard/settings/general/+page.svelte +++ b/frontend/src/routes/dashboard/settings/general/+page.svelte @@ -1,212 +1,30 @@ - Configuración General | Anexo 76 + Configuración General -
- {#if !canView} - - {:else} -
-
-

{m['common.title_settings']()}

-

- {m['common.sub_settings']()} -

-
- - -
- - -
-
- - {#if loading && !resolvedSettings} -
- -

Cargando configuraciones...

-
- {:else} -
-
- {#if activeSystem === 'ssisgen'} - - {:else} - - {/if} -
-
- {/if} - {/if} -
- -{#if canView} - -
-
- {#if loading} -
- - Cargando... -
- {/if} - {#if canEdit} - - {:else} -

- (Solo lectura: se requiere settings_general.edit para guardar) -

- {/if} -
+
+
+

+ + Configuración General +

+

+ Ajustes globales de la aplicación. +

-{/if} + + + + Configuración del sistema + Agrega aquí los ajustes de configuración de tu proyecto. + + +

Sección en construcción.

+
+
+
diff --git a/frontend/src/routes/dashboard/tasks/+page.ts b/frontend/src/routes/dashboard/tasks/+page.ts index 2239e38f..ea941f2b 100644 --- a/frontend/src/routes/dashboard/tasks/+page.ts +++ b/frontend/src/routes/dashboard/tasks/+page.ts @@ -2,5 +2,5 @@ import { redirect } from '@sveltejs/kit'; import type { PageLoad } from './$types'; export const load: PageLoad = () => { - throw redirect(303, '/dashboard/audit_logs?tab=tasks'); + throw redirect(303, '/dashboard'); }; diff --git a/frontend/src/routes/dashboard/users/+page.server.ts b/frontend/src/routes/dashboard/users/+page.server.ts index ab301f4d..45fd039d 100644 --- a/frontend/src/routes/dashboard/users/+page.server.ts +++ b/frontend/src/routes/dashboard/users/+page.server.ts @@ -1,6 +1,5 @@ import type { PageServerLoad } from './$types'; -export const load: PageServerLoad = async ({ locals }) => { - // Los datos se cargarán desde el cliente +export const load: PageServerLoad = async () => { return {}; }; diff --git a/frontend/src/routes/dashboard/users/+page.svelte b/frontend/src/routes/dashboard/users/+page.svelte index 586a327c..4ecfca5e 100644 --- a/frontend/src/routes/dashboard/users/+page.svelte +++ b/frontend/src/routes/dashboard/users/+page.svelte @@ -1,3588 +1,23 @@ -{#if !canView} - -{:else if managementView === 'list'} -
- -
-
-

{m['common.title_users']()}

-

- {m['common.sub_users']()} -

-
-
- - - - - - - Usuarios - - - - Roles y Permisos - - - - - -
- {#if canCreate} - - - {/if} -
- -
- - {#if stats} -
- - - Total Usuarios - - - -
{stats.total_users}
-
-
- - - - Activos - - - -
{stats.active_users}
-
-
- - - - Disponibles - - - -
{stats.users_available}
-

- de {stats.max_users_allowed} permitidos -

-
-
- - - - Uso de Licencia - - -
{stats.usage_percentage.toFixed(1)}%
-
-
= 70 && - stats.usage_percentage < 90} - class:bg-red-600={stats.usage_percentage >= 90} - style="width: {stats.usage_percentage}%" - >
-
-
-
-
- {/if} - - - - -
-
- Usuarios - Listado de todos los usuarios de la plataforma -
-
- - -
-
-
- - - - - Usuario - Nombre Completo - Email - Estado - Acciones - - - - {#if loading} - - -
- - Cargando usuarios... -
-
-
- {:else if users.length === 0} - - - No se encontraron usuarios - - - {:else} - {#each users as user (user.id)} - - -
- {user.username} - {#if user.email_verified} - Verificado - {/if} -
-
- {user.first_name} {user.last_name} - {user.email} - - {#if user.enabled} - Activo - {:else} - Inactivo - {/if} - - -
- - - - - Gestionar Roles - - - - - - - Cambiar Contraseña - - - - - - - Editar Usuario - - - - - - - Eliminar Usuario - -
-
-
- {/each} - {/if} -
-
-
-
-
-
- - - - {#if !canAccessRolesTab} -
- -
- {:else} -
- -
-
-
-

- - Roles -

- -
- - {#if rolesState.length === 0} -
No hay roles disponibles
- {:else} -
- {#each rolesState as role (role.id)} -
- -
- - -
-
- {/each} -
- {/if} -
-
- - -
- {#if selectedRoleForPermissions} -
-
-
-

- - Permisos de "{selectedRoleForPermissions.name}" -

-

- {#if rolePermissionsSearchQuery.trim()} - Mostrando {filteredRolePermissions.length} de {rolePermissionsVisibleCount}{' '} - permisos asignados - {:else} - {rolePermissionsVisibleCount} permisos asignados - {/if} -

-
-
-
- - -
- -
-
- - - - - Módulo - Acción - Permiso - Acciones - - - - {#if loadingPermissionsState} - - - Cargando permisos... - - - {:else if rolePermissionsVisibleCount === 0} - - - Este rol no tiene permisos asignados - - - {:else if filteredRolePermissions.length === 0} - - - No hay coincidencias para "{rolePermissionsSearchQuery}" - - - {:else} - {#each filteredRolePermissions as rp (rp.id)} - - - {rp.permission?.module - ? getModuleLabel(rp.permission.module) - : '-'} - - - {rp.permission?.action - ? getActionLabel(rp.permission.action) - : '-'} - - - {#if rp.permission} -
-
{formatPermissionLabel(rp.permission)}
- {rp.permission.code} -
- {:else} - - - {/if} -
- - - -
- {/each} - {/if} -
-
-
- {:else} -
- -

Selecciona un rol para ver sus permisos

-
- {/if} -
-
- {/if} -
-
+
+
+

+ + Usuarios +

+

Gestión de usuarios y accesos.

-{:else} - {@const modules = manageTargetType === 'user' ? modulesForUserDialog : modulesForRoleDialog} - {@const selectedModule = - manageTargetType === 'user' ? selectedModuleForUserDialog : selectedModuleForRoleDialog} - {@const totalSelected = - manageTargetType === 'user' ? selectedUserPermissionIds.length : selectedPermissionIds.length} - {@const selectedIds = - manageTargetType === 'user' ? selectedUserPermissionIds : selectedPermissionIds} - - -
- -
-
- -
-
-

- {#if manageTargetType === 'user'} - Permisos de {selectedUser?.username} - {:else} - Permisos del Rol "{selectedRoleForPermissions?.name}" - {/if} -

-

Configuración de acceso granular y seguridad

-
-
- -
-
- {#if manageTargetType === 'user'} - {selectedUserPermissionIds.length} seleccionados - {:else} - {selectedPermissionIds.length} seleccionados - {/if} - Estado por asignar -
- - -
-
- -
- - - - -
-
- {#if modules.length === 0} -
- -

No se encontraron permisos disponibles

-
- {:else} - {#each modules as { module, permissions } (module)} - {#if selectedModule === 'all' || selectedModule === module} -
-
-

- {selectedModule === 'all' ? 'Resumen Global' : (selectedModule || '').replace('_', ' ')} -

-
-
-

- {allPermissionsState.filter((p) => { - const matchesSearch = - !permissionSearchQuery || - p.code.toLowerCase().includes(permissionSearchQuery.toLowerCase()) || - p.description?.toLowerCase().includes(permissionSearchQuery.toLowerCase()); - - const isSubModuleParent = [ - 'general_catalogs', 'fractions', 'transports', 'goods', - 'pedimentos', 'invoice_imp', 'invoice_exp', 'export', - 'users', 'reports', 'roles' - ].includes(p.module); - - const selectedSub = manageTargetType === 'user' ? selectedSubModuleForUserDialog : selectedSubModuleForRoleDialog; - - const matchesSub = - !isSubModuleParent || - (() => { - if (selectedSub === 'all') return true; - const selectedParts = selectedSub.split('.'); - const codeParts = p.code.split('.'); - return codeParts.length === selectedParts.length + 1 && - codeParts.slice(0, selectedParts.length).join('.') === selectedSub; - })(); - - const currentModule = selectedModule === 'all' ? p.module : selectedModule; - return p.module === currentModule && matchesSearch && matchesSub; - }).length} activos para selección -

-
-
-
- - -
-
- {@const selectedSub = - manageTargetType === 'user' - ? selectedSubModuleForUserDialog - : selectedSubModuleForRoleDialog} - {@const filtered = permissions.filter((p: Permission) => { - const matchesSearch = - !permissionSearchQuery || - formatPermissionLabel(p) - .toLowerCase() - .includes(permissionSearchQuery.toLowerCase()) || - p.code.toLowerCase().includes(permissionSearchQuery.toLowerCase()); - const isSubModuleParent = [ - 'general_catalogs', - 'fractions', - 'transports', - 'goods', - 'pedimentos', - 'invoice_imp', - 'invoice_exp', - 'export', - 'users', - 'reports', - 'roles' - ].includes(module); - const matchesSub = - !isSubModuleParent || - (() => { - const selectedSub = manageTargetType === 'user' ? selectedSubModuleForUserDialog : selectedSubModuleForRoleDialog; - if (selectedSub === 'all') return true; - - const selectedParts = selectedSub.split('.'); - const codeParts = p.code.split('.'); - - // Verificamos que el código empiece con el prefijo exacto - // Y que tenga exactamente un nivel más de profundidad (la acción) - const isImmediateChild = codeParts.length === selectedParts.length + 1; - const startsWithExact = codeParts.slice(0, selectedParts.length).join('.') === selectedSub; - - return isImmediateChild && startsWithExact; - })(); - return matchesSearch && matchesSub; - })} - - {#if filtered.length > 0} -
-
-
-
- -
-
-

- {getModuleLabel(module)} -

-

- Gestiona el acceso para el componente {module} -

-
-
- -
- -
- {#each filtered as permission (permission.id)} - - -
- manageTargetType === 'user' - ? toggleUserPermission(permission.id) - : togglePermission(permission.id)} - > -
-
- - manageTargetType === 'user' - ? toggleUserPermission(permission.id) - : togglePermission(permission.id)} - class="pointer-events-none h-5 w-5 rounded-md" - /> -
-
- -
- - {getActionLabel(permission.action)} - - - {permission.code} - -
-
-
-
- {/each} -
-
- {/if} - {/if} - {/each} - {/if} -
-
-
-
-{/if} - - - - - - - - Invitar Usuario - - Se generará un enlace de registro válido por 48 horas y de un solo uso. - - - - {#if inviteResult} - -
-
-

Invitación generada

-

- Enviada a {inviteResult.email}. - También puedes copiar el enlace manualmente: -

-
-
- -
- - {inviteResult.invite_url} - - -
-
-
- - - - {:else} - -
-
- - -
-
- - (inviteRoleId = Number(v))}> - - {availableRoles.find((r) => r.id === inviteRoleId)?.name ?? 'Selecciona un rol'} - - - {#each availableRoles as role} - {role.name} - {/each} - - -
-
- - - - - {/if} -
-
- - - - - - - - Códigos de Invitación - - - Comparte un código para que los usuarios se unan directamente a esta empresa. - - - - -
- {#if inviteCodesLoading} -
- - Cargando códigos... -
- {:else if inviteCodes.length === 0} -

- No hay códigos activos. Crea uno para compartir. -

- {:else} -
- {#each inviteCodes as ic (ic.id)} -
- {ic.code} -
- {ic.role} - - {ic.uses_count}{ic.max_uses !== null ? `/${ic.max_uses}` : ''} usos - - {formatExpiry(ic.expires_at)} -
-
- - - - - Copiar código - - - - - - Revocar código - -
-
- {/each} -
- {/if} - - - {#if showCreateCodeForm} -
-

Nuevo código

-
-
- - (codeForm.role = v)}> - - {availableRoles.find((r) => r.code === codeForm.role)?.name ?? 'Selecciona un rol'} - - - {#each availableRoles as role} - {role.name} - {/each} - - -
-
- - -
-
-
- - -
-
- - -
-
- {/if} -
- - - - - -
-
- - - - - Crear Nuevo Usuario - - Ingresa la información del nuevo usuario. Se enviará un correo para configurar su - contraseña. - - -
-
-
- - -
-
- - -
-
-
- - -
-
- - -
-
- - -

Mínimo 8 caracteres

-
-
- - - - -
-
- - - - - - Editar Usuario - - Modifica la información del usuario {selectedUser?.username} - - -
-
-
- - -
-
- - -
-
-
- - -
-
- - -
-
- - -
-
- - - - -
-
- - - - - - Cambiar Contraseña - - Establece una nueva contraseña para {selectedUser?.username} - - -
-
- - -
-
- - -
-
- - -
-
- - - - -
-
- - - - - - ¿Eliminar usuario? - - ¿Estás seguro de que deseas eliminar a - {selectedUser?.username}? -

- {#if deleteLoadingCount} - Verificando membresías... - {:else if deleteUserTenantCount > 1} - - ⚠️ Este usuario pertenece a - {deleteUserTenantCount} tenants. Puedes eliminarlo solo del - tenant actual o de todos. - - {/if} -
-
- - Cancelar - - {#if deleteLoadingCount} - - - {:else if deleteUserTenantCount > 1} - - - handleDelete(false, 'current')} - class="text-destructive-foreground bg-destructive hover:bg-destructive/90" - > - - Eliminar (este tenant) - - - - handleDelete(false, 'all')} - class="text-destructive-foreground bg-destructive hover:bg-destructive/90" - > - - Eliminar (todos) - - {:else} - - - handleDelete(false, 'current')} - class="text-destructive-foreground bg-destructive hover:bg-destructive/90" - > - - Eliminar Permanente - - {/if} - -
-
- - - - - Gestionar Roles - {selectedUser?.username} - Asigna o remueve roles para este usuario - - -
- -
- - {#if loadingRoles} -
- -
- {:else if userRoles.length === 0} -
- Este usuario no tiene roles asignados -
- {:else} -
- {#each userRoles as userRole} -
-
- -
-
- {userRole.company_role?.name || 'Rol desconocido'} -
-
- Asignado el {new Date(userRole.created_at).toLocaleDateString()} -
-
-
- -
- {/each} -
- {/if} -
- - -
- - {#if unassignedRoles.length === 0} -

- No hay roles disponibles. Para cambiar de rol, primero remueve el rol actual usando el - botón de eliminar arriba. -

- {:else} -
- (selectedRoleId = v ? parseInt(v) : 0)} - > - - {selectedRoleId && selectedRoleId !== 0 - ? availableRoles.find( - (r) => - r.id === - (typeof selectedRoleId === 'string' - ? parseInt(selectedRoleId) - : selectedRoleId) - )?.name || 'Selecciona un rol' - : 'Selecciona un rol'} - - - {#each unassignedRoles as role} - {role.name} - {/each} - - - -
- {/if} -
-
- - - - - -
-
- - - - - - Permisos Individuales - {selectedUser?.username} - - Concede permisos extra o revoca permisos del rol para este usuario específico - - - -
- {#if loadingUserPermissions} -
- -
- {:else if effectivePermissions} - -
-
-
- {effectiveInheritedVisibleCount} -
-
Del Rol
-
-
-
- {effectiveGrantedVisibleCount} -
-
Concedidos Extra
-
-
-
- {effectiveRevokedVisibleCount} -
-
Revocados
-
-
- -
- - -
- -
-
- - -
-
- - -
-
- - - {#if effectiveGrantedVisibleCount > 0} -
-
- - - {filteredGrantedPermissions.length}/{effectiveGrantedVisibleCount} - -
-
- {#if filteredGrantedPermissions.length === 0} -
- Sin coincidencias en permisos concedidos -
- {:else} - {#each filteredGrantedPermissions as perm} -
-
-
- {getModuleLabel(perm.module)} - {getActionLabel(perm.action)} -
-
{formatPermissionLabel(perm)}
- {perm.code} -
- -
- {/each} - {/if} -
-
- {/if} - - - {#if effectiveRevokedVisibleCount > 0} -
-
- - - {filteredRevokedPermissions.length}/{effectiveRevokedVisibleCount} - -
-
- {#if filteredRevokedPermissions.length === 0} -
- Sin coincidencias en permisos revocados -
- {:else} - {#each filteredRevokedPermissions as perm} -
-
-
- {getModuleLabel(perm.module)} - {getActionLabel(perm.action)} -
-
- {formatPermissionLabel(perm)} -
- {perm.code} -
- -
- {/each} - {/if} -
-
- {/if} - - - {#if effectiveInheritedVisibleCount > 0} -
-
- - - {filteredInheritedPermissions.length}/{effectiveInheritedVisibleCount} - -
-
- {#if filteredInheritedPermissions.length === 0} -
- Sin coincidencias en permisos heredados -
- {:else} - {#each filteredInheritedPermissions as perm} -
-
-
- {getModuleLabel(perm.module)} - {getActionLabel(perm.action)} -
-
{formatPermissionLabel(perm)}
- {perm.code} -
- {#if !effectivePermissions.revoked_permissions.some((r) => r.id === perm.id)} - - {/if} -
- {/each} - {/if} -
-
- {/if} - - {#if userEffectiveHasActiveListFilters && !hasUserEffectiveSearchMatches} -
- No hay permisos que coincidan con tu búsqueda o con los filtros de módulo y acción en - concedidos, revocados o heredados. -
- {/if} - {/if} -
- - - -
- - -
-
-
- - - - - - - {editingRole ? 'Editar Rol' : 'Nuevo Rol'} - - - -
-
- - -
- -
- - -
- -
- -